#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include "RTClib.h"
RTC_DS1307 rtc;
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// prototype: https://wokwi.com/arduino/projects/310918781473915457
// button states
byte up_state = HIGH;
byte down_state = HIGH;
byte left_state = HIGH;
byte right_state = HIGH;
byte ok_state = HIGH;
byte back_state = HIGH;
// system start time
int time_started = millis();
// button state debounce
int up_state_changed = time_started;
int down_state_changed = time_started;
int left_state_changed = time_started;
int right_state_changed = time_started;
int ok_state_changed = time_started;
int back_state_changed = time_started;
int RowNumber = 1;
int row_selection = 1;
int start_line = 20;
int line = start_line;
int line_height = 9;
bool bluethooth = true;
int rowNumber = 1;
int columnNumber = 1;
// menu
// int menu_index = 0;
int menu_index = 0;
int menu_y_index = 0;
int menu_y_max = 4;
int menu_x_index = 0;
int menu_x_max[] = {1, 1, 1, 1, 1};
// Light module control
int rgb[3] = {100, 100, 100};
int rgb2[3] = {100, 100, 100};
int white[1] = {100};
bool left = true;
bool right = true;
const char *current_color = "";
int current_module = 0;
int current_color_index = 0;
void setupButtons()
{
pinMode(2, INPUT_PULLUP);
pinMode(3, INPUT_PULLUP);
pinMode(4, INPUT_PULLUP);
pinMode(5, INPUT_PULLUP);
pinMode(6, INPUT_PULLUP);
pinMode(7, INPUT_PULLUP);
}
int getPercentage()
{
return current_module == 1 ? rgb[current_color_index] : rgb2[current_color_index];
}
void drawTextLine(const char *text)
{
display.setTextSize(0.9);
display.setTextColor(SSD1306_WHITE);
display.println(text);
}
void drawTextLine(const char *text, double size)
{
display.setTextSize(size);
display.setTextColor(SSD1306_WHITE);
display.println(text);
}
void drawTextLine(const char *text, bool inverted)
{
display.setTextSize(0.9);
inverted ? display.setTextColor(BLACK, WHITE)
: display.setTextColor(WHITE, BLACK);
display.print(text);
}
void drawTextLine(const char *text, int color)
{
display.setTextSize(0.9);
display.setTextColor(color);
display.println(text);
}
void drawNewTextLine(const char *text)
{
line += line_height;
display.setCursor(10, line);
drawTextLine(text);
}
void setTextColor(bool inverted)
{
inverted ? display.setTextColor(BLACK, WHITE)
: display.setTextColor(WHITE, BLACK);
}
void drawNewTextLine(const char *text, bool inverted, bool setCursor)
{
line += line_height;
if (setCursor)
{
display.setCursor(10, line);
}
setTextColor(inverted);
display.println(text);
}
void drawNewTextLine(const char *text, bool inverted)
{
line += line_height;
display.setCursor(10, line);
setTextColor(inverted);
display.println(text);
}
void drawText(const char *text, int color)
{
display.setTextSize(0.9);
display.setTextColor(color);
display.print(text);
}
void drawText(const char *text)
{
drawText(text, SSD1306_WHITE);
}
void updateStateAndMilis(byte pin, byte &state, int &state_changed, int debounce_time)
{
state = HIGH;
byte new_state = digitalRead(pin);
if (new_state == LOW && (time_started == state_changed ||
static_cast<int>(millis()) - state_changed > debounce_time))
{
state_changed = millis();
state = LOW;
}
}
void readButtons()
{
const int debounce_time = 250;
updateStateAndMilis(2, up_state, up_state_changed, debounce_time);
updateStateAndMilis(3, down_state, down_state_changed, debounce_time);
updateStateAndMilis(4, left_state, left_state_changed, debounce_time);
updateStateAndMilis(5, right_state, right_state_changed, debounce_time);
updateStateAndMilis(6, ok_state, ok_state_changed, 1000);
updateStateAndMilis(7, back_state, back_state_changed, debounce_time);
}
void drawCurrentTime(void)
{
display.setCursor(90, 5);
drawTextLine("00:00");
}
void drawTemp()
{
display.setCursor(40, 5);
drawTextLine("20c");
}
void resetPointLineHeight()
{
line = start_line;
}
void drawMenuLine(const char *text)
{
const bool invert = rowNumber == menu_y_index;
drawNewTextLine(text, invert);
rowNumber = rowNumber + 1;
}
void resetMenuIndexes()
{
menu_x_index = 0;
menu_y_index = 0;
}
void activateMenu(int menuIndex)
{
menu_x_index = 1;
menu_y_index = 1;
menu_index = menuIndex;
}
void drawMenu()
{
if (menu_index != 1)
{
return;
}
resetPointLineHeight();
rowNumber = 1;
line = line - 10;
drawMenuLine("edit profiles");
drawMenuLine("bluetooth");
drawMenuLine("manual control");
drawMenuLine("exit");
if (ok_state == LOW)
{
if (menu_y_index == 4)
{
resetMenuIndexes();
return;
}
activateMenu(menu_y_index + 1);
}
}
void drawHorizontalMenu(const char *text, bool selectable, bool create_new_line)
{
const bool invert = selectable && rowNumber == menu_y_index && columnNumber == menu_x_index;
create_new_line ? drawNewTextLine(text, invert, false) : drawTextLine(text, invert);
rowNumber = create_new_line ? rowNumber + 1 : rowNumber;
if (selectable)
{
columnNumber = create_new_line ? 1 : columnNumber + 1;
}
}
void drawHorizontalMenu(int number, bool selectable, bool create_new_line)
{
char num_char[3 + sizeof(char)];
sprintf(num_char, "%d", number);
drawHorizontalMenu(num_char, selectable, create_new_line);
}
void drawPercentageHorizontalMenuItem(int percentage, bool jumpToNextLine)
{
drawHorizontalMenu(percentage, true, jumpToNextLine);
if (!jumpToNextLine) {
drawHorizontalMenu("/", false, false);
}
}
void drawHorizontalPercentageMenu(const char *title, int y_index, int number_of_items, int color[])
{
menu_x_max[y_index] = number_of_items;
drawHorizontalMenu(title, false, false);
int index = 1;
while (index <= number_of_items) {
drawPercentageHorizontalMenuItem(color[index-1], index == number_of_items);
index++;
}
}
void drawSetupProfile()
{
if (menu_index != 2)
{
return;
}
resetPointLineHeight();
line = line - 10;
drawNewTextLine("Profile: Sunrise");
drawHorizontalPercentageMenu("RGB 1: ", 2, 3, rgb);
drawHorizontalPercentageMenu("RGB 2: ", 2, 3, rgb2);
drawHorizontalPercentageMenu("White: ", 1, 1, white);
}
void drawHome()
{
if (menu_index != 0)
{
return;
}
resetPointLineHeight();
drawNewTextLine("Profile: Sunrise");
drawNewTextLine("time: 07:00-7:30");
drawNewTextLine("next: Day");
}
void drawOnOff(bool is_on)
{
drawHorizontalMenu((is_on ? "On" : "Off"), true, false);
}
const char * getCurrentColorBasedOnMenuXindex(int menu_x_index)
{
switch (menu_x_index)
{
case 1:
return "red";
break;
case 2:
return "green";
break;
case 3:
return "blue";
break;
default:
return "white";
break;
}
}
void drawManualControll()
{
if (menu_index != 4)
{
return;
}
resetPointLineHeight();
line = line - 10;
rowNumber = 1;
columnNumber = 1;
drawNewTextLine("Manual control");
drawHorizontalPercentageMenu(" RGB 1: ", 1, 3, rgb);
drawHorizontalPercentageMenu(" RGB 2: ", 2, 3, rgb2);
drawHorizontalPercentageMenu(" White: ", 3, 1, white);
menu_x_max[4] = 2;
drawHorizontalMenu(" L/R: ", false, false);
drawOnOff(left);
drawHorizontalMenu("/", false, false);
drawOnOff(right);
if (ok_state == LOW)
{
if (menu_y_index == 4)
{
left = menu_x_index == 1 ? !left : left;
right = menu_x_index == 2 ? !right : right;
return;
}
menu_index = 5;
current_color = getCurrentColorBasedOnMenuXindex(menu_x_index);
current_color_index = menu_x_index - 1;
current_module = menu_y_index;
}
}
void drawPercentage(int percentage, const char * header)
{
display.setTextSize(0.9);
display.setTextColor(SSD1306_WHITE);
display.println(header);
display.setTextSize(1.98);
display.setCursor(40, display.getCursorY());
display.println(" /\\");
display.setTextSize(2);
display.setCursor(40, display.getCursorY());
display.print(percentage < 100 ? "0" : "");
display.print(percentage < 10 ? "0" : "");
display.print(percentage);
display.println("%");
display.setTextSize(1.9);
display.setCursor(40, display.getCursorY());
display.println(" \\/");
}
int whenIntegerIsMaxSetItToMinimumAndViceVersa(int value, int minimum, int maximum)
{
if (value >= minimum && value <= maximum)
{
return value;
}
if (value < minimum)
{
return maximum;
}
if (value > maximum)
{
return minimum;
}
return value;
}
int addOrSubtractOneOrTenKeepingValueWithingRangeWhenIntegerIsMaxSetItToMinimumAndViceVersa(byte state_button_vertical, byte state_button_horizontal, bool add, int value, int minumm, int maximum)
{
int step = state_button_horizontal == LOW ? -10 : -1;
step = add ? abs(step) : step;
if (state_button_vertical == HIGH && state_button_horizontal == HIGH)
{
step = 0;
}
return whenIntegerIsMaxSetItToMinimumAndViceVersa(value + step, minumm, maximum);
}
void updatePercentageWhenPressingButton(int color[], int current_color_index)
{
color[current_color_index] = addOrSubtractOneOrTenKeepingValueWithingRangeWhenIntegerIsMaxSetItToMinimumAndViceVersa(down_state, left_state, false, getPercentage(), 1, 100);
color[current_color_index] = addOrSubtractOneOrTenKeepingValueWithingRangeWhenIntegerIsMaxSetItToMinimumAndViceVersa(up_state, right_state, true, getPercentage(), 1, 100);
}
void drawPercentageSeletor()
{
if (
menu_index != 5)
{
return;
}
if (current_module < 1 || strcmp( current_color, "") == 0)
{
menu_index = 0;
return;
}
if (current_module <= 2)
{
char buffer[15];
sprintf(buffer, "RGB %s: %s", current_module == 1 ? "1" : "2", current_color);
drawPercentage(getPercentage(), buffer);
} else {
drawPercentage(*white, "WHITE: ");
}
switch(current_module) {
case 1:
updatePercentageWhenPressingButton(rgb, current_color_index);
break;
case 2:
updatePercentageWhenPressingButton(rgb2, current_color_index);
break;
default:
updatePercentageWhenPressingButton(white, current_color_index);
break;
}
}
void toggleMenu(int show, bool toggle)
{
if (!toggle)
{
return;
}
menu_index = show;
resetMenuIndexes();
}
void navigate()
{
menu_y_index = addOrSubtractOneOrTenKeepingValueWithingRangeWhenIntegerIsMaxSetItToMinimumAndViceVersa(up_state, HIGH, false, menu_y_index, 1, menu_y_max);
menu_y_index = addOrSubtractOneOrTenKeepingValueWithingRangeWhenIntegerIsMaxSetItToMinimumAndViceVersa(down_state, HIGH, true, menu_y_index, 1, menu_y_max);
menu_x_index = addOrSubtractOneOrTenKeepingValueWithingRangeWhenIntegerIsMaxSetItToMinimumAndViceVersa(left_state, HIGH, false, menu_x_index, 1, menu_x_max[menu_y_index]);
menu_x_index = addOrSubtractOneOrTenKeepingValueWithingRangeWhenIntegerIsMaxSetItToMinimumAndViceVersa(right_state, HIGH, true, menu_x_index, 1, menu_x_max[menu_y_index]);
toggleMenu(menu_x_index + menu_y_index > 0 ? 1 : 0, back_state == HIGH && menu_index == 0);
toggleMenu(menu_index - 1, back_state == LOW && menu_index >= 0);
}
void drawTopbar()
{
drawTemp();
drawCurrentTime();
}
void setupRTC()
{
#ifndef ESP8266
while (!Serial); // wait for serial port to connect. Needed for native USB
#endif
if (! rtc.begin()) {
Serial.println("Couldn't find RTC");
Serial.flush();
abort();
}
if (! rtc.isrunning()) {
Serial.println("RTC is NOT running, let's set the time!");
// When time needs to be set on a new device, or after a power loss, the
// following line sets the RTC to the date & time this sketch was compiled
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
// This line sets the RTC with an explicit date & time, for example to set
// January 21, 2014 at 3am you would call:
// rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));
}
}
void setupLCD()
{
// DELETE ME shortcut to debug
// menu_index = 5;current_module = 1;current_color = "red";
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3D))
{ // Address 0x3D for 128x64
Serial.println(F("SSD1306 allocation failed"));
for (;;)
; // Don't proceed, loop forever
}
// SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
}
void setup()
{
Serial.begin(9600);
setupButtons();
setupLCD();
setupRTC();
}
void loop()
{
display.clearDisplay();
readButtons();
navigate();
drawTopbar();
drawHome();
drawSetupProfile();
drawMenu();
drawManualControll();
drawPercentageSeletor();
display.display();
}