#include <Arduino.h>
#include <SPI.h>
#include <U8x8lib.h>
#include <FastLED.h>
#include <EncButton.h>
#define NUM_LEDS 8
#define COUNT_STR 8
CRGB leds[NUM_LEDS];
EncButton eb(2, 3, 4); // 4 - key, 3, 2 - turn
U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8 = U8X8_SSD1306_128X64_NONAME_HW_I2C();
unsigned long lastTime = millis();
void handleAction();
void setup() {
Serial.begin(9600);
u8x8.begin();
// показаны значения по умолчанию
eb.setBtnLevel(LOW);
eb.setClickTimeout(500);
eb.setDebTimeout(50);
eb.setHoldTimeout(600);
eb.setStepTimeout(200);
eb.setEncReverse(0);
eb.setEncType(EB_STEP4_LOW);
eb.setFastTimeout(30);
// сбросить счётчик энкодера
eb.counter = 0;
FastLED.addLeds<NEOPIXEL, 6>(leds, NUM_LEDS); // 6 - led
eb.attach(handleAction);
display();
}
char nameMode[3][7] {
"Static",
"LGBT",
"Blink"
};
char name[3][COUNT_STR][13] {
{
"Menu (^.^)",
"",
"Mode: ",
"R: ", // 0-255
"G: ",
"B: ",
"Brigthness: ", // 0-255
"",
},
{
"Menu (^.^)",
"",
"Mode: ",
"DeltaHue: ",
"Brightness: ",
"Delay: ",
"",
"",
},
{
"Menu (^.^)",
"",
"Mode: ",
"R: ",
"G: ",
"B: ",
"Brigthness: ",
"Delay: ",
},
};
typedef enum Mode {
staticMode = 0,
dynamicMode = 1,
blinkMode = 2,
}Mode;
typedef struct State {
// menu
int cursor = 2;
bool onClick = false;
// led
Mode mode = Mode::dynamicMode;
CRGB color = CRGB(0, 255, 3);
uint8_t brightness = 158;
uint8_t delayl = 30;
uint8_t hue = 50;
uint8_t dhue = 3;
bool hasChanged = true;
} State;
State state;
int inc(int shift, int base, int num, int dt) {
return (num - shift + dt) % base + shift;
}
int dec(int shift, int base, int num, int dt) {
return ((num - shift - dt) % base + base) % base + shift;
}
void handleAction() {
if (!eb.action())
return;
switch (eb.action()) {
case EB_PRESS:
state.onClick = !state.onClick;
state.hasChanged = true;
break;
case EB_TURN:
if (state.mode == Mode::staticMode) {
if (!state.onClick) {
if (eb.left()) {
state.cursor = inc(2, 5, state.cursor, 1);
} else if (eb.right()) {
state.cursor = dec(2, 5, state.cursor, 1);
}
state.hasChanged = true;
}
if (state.onClick) {
switch (state.cursor) {
case 2:
if (eb.left()) {
state.mode = inc(0, 3, state.mode, 1);
} else if (eb.right()) {
state.mode = dec(0, 3, state.mode, 1);
}
break;
case 3:
if (eb.left()) {
state.color[0] = inc(0, 255, state.color[0], 5);
} else if (eb.right()) {
state.color[0] = dec(0, 255, state.color[0], 5);
}
break;
case 4:
if (eb.left()) {
state.color[1] = inc(0, 255, state.color[1], 5);
} else if (eb.right()) {
state.color[1] = dec(0, 255, state.color[1], 5);
}
break;
case 5:
if (eb.left()) {
state.color[2] = inc(0, 255, state.color[2], 5);
} else if (eb.right()) {
state.color[2] = dec(0, 255, state.color[2], 5);
}
break;
case 6:
if (eb.left()) {
state.brightness = inc(0, 255, state.brightness, 5);
} else if (eb.right()) {
state.brightness = dec(0, 255, state.brightness, 5);
}
break;
}
state.hasChanged = true;
}
} else if (state.mode == Mode::dynamicMode) {
if (!state.onClick) {
if (eb.left()) {
state.cursor = inc(2, 4, state.cursor, 1);
} else if (eb.right()) {
state.cursor = dec(2, 4, state.cursor, 1);
}
state.hasChanged = true;
}
if (state.onClick) {
switch (state.cursor) {
case 2:
if (eb.left()) {
state.mode = inc(0, 3, state.mode, 1);
} else if (eb.right()) {
state.mode = dec(0, 3, state.mode, 1);
}
break;
case 3:
if (eb.left()) {
state.dhue = inc(0, 255, state.dhue, 1);
} else if (eb.right()) {
state.dhue = dec(0, 255, state.dhue, 1);
}
break;
case 4:
if (eb.left()) {
state.brightness = inc(0, 255, state.brightness, 5);
} else if (eb.right()) {
state.brightness = dec(0, 255, state.brightness, 5);
}
break;
case 5:
if (eb.left()) {
state.delayl = inc(5, 200, state.delayl, 1);
} else if (eb.right()) {
state.color[2] = dec(5, 200, state.delayl, 1);
}
break;
}
state.hasChanged = true;
}
} else if (state.mode == Mode::blinkMode) {
if (!state.onClick) {
if (eb.left()) {
state.cursor = inc(2, 6, state.cursor, 1);
} else if (eb.right()) {
state.cursor = dec(2, 6, state.cursor, 1);
}
state.hasChanged = true;
}
if (state.onClick) {
switch (state.cursor) {
case 2:
if (eb.left()) {
state.mode = inc(0, 3, state.mode, 1);
} else if (eb.right()) {
state.mode = dec(0, 3, state.mode, 1);
}
break;
case 3:
if (eb.left()) {
state.color[0] = inc(0, 255, state.color[0], 5);
} else if (eb.right()) {
state.color[0] = dec(0, 255, state.color[0], 5);
}
break;
case 4:
if (eb.left()) {
state.color[1] = inc(0, 255, state.color[1], 5);
} else if (eb.right()) {
state.color[1] = dec(0, 255, state.color[1], 5);
}
break;
case 5:
if (eb.left()) {
state.color[2] = inc(0, 255, state.color[2], 5);
} else if (eb.right()) {
state.color[2] = dec(0, 255, state.color[2], 5);
}
break;
case 6:
if (eb.left()) {
state.brightness = inc(0, 255, state.brightness, 5);
} else if (eb.right()) {
state.brightness = dec(0, 255, state.brightness, 5);
}
break;
case 7:
if (eb.left()) {
state.delayl = inc(5, 200, state.delayl, 5);
} else if (eb.right()) {
state.delayl = dec(5, 200, state.delayl, 5);
}
break;
}
state.hasChanged = true;
}
}
break;
}
// где-то внутри функции мы будем знать, что изменили состояние
// либо же, будем знать что любое событие изменяет состояние
}
char strTmp[16] = {0};
void displayMain() {
for (int i = 0; i < COUNT_STR; i++) {
sprintf(strTmp, "%s%s", i == state.cursor ? "-" : "", name[state.mode][i]/*i == 5 ? nameMode[state.mode] : ""*/);
if (i == 2)
sprintf(strTmp, "%s%s", strTmp, nameMode[0]);
if (i == 3)
sprintf(strTmp, "%s%d", strTmp, state.color[0]);
if (i == 4)
sprintf(strTmp, "%s%d", strTmp, state.color[1]);
if (i == 5)
sprintf(strTmp, "%s%d", strTmp, state.color[2]);
if (i == 6)
sprintf(strTmp, "%s%d", strTmp, state.brightness);
u8x8.setFont(u8x8_font_chroma48medium8_r);
u8x8.setInverseFont(i == state.cursor && state.onClick);
u8x8.drawString(0, i, strTmp);
}
}
void displayBlink() {
for (int i = 0; i < COUNT_STR; i++) {
sprintf(strTmp, "%s%s", i == state.cursor ? "-" : "", name[state.mode][i]/*i == 5 ? nameMode[state.mode] : ""*/);
if (i == 2)
sprintf(strTmp, "%s%s", strTmp, nameMode[2]);
if (i == 3)
sprintf(strTmp, "%s%d", strTmp, state.color[0]);
if (i == 4)
sprintf(strTmp, "%s%d", strTmp, state.color[1]);
if (i == 5)
sprintf(strTmp, "%s%d", strTmp, state.color[2]);
if (i == 6)
sprintf(strTmp, "%s%d", strTmp, state.brightness);
if (i == 7)
sprintf(strTmp, "%s%d", strTmp, state.delayl);
u8x8.setFont(u8x8_font_chroma48medium8_r);
u8x8.setInverseFont(i == state.cursor && state.onClick);
u8x8.drawString(0, i, strTmp);
}
}
void displayLgbt() {
for (int i = 0; i < COUNT_STR; i++) {
sprintf(strTmp, "%s%s", i == state.cursor ? "-" : "", name[state.mode][i]/*i == 5 ? nameMode[state.mode] : ""*/);
if (i == 2)
sprintf(strTmp, "%s%s", strTmp, nameMode[1]);
if (i == 3)
sprintf(strTmp, "%s%d", strTmp, state.dhue);
if (i == 4)
sprintf(strTmp, "%s%d", strTmp, state.brightness);
if (i == 5)
sprintf(strTmp, "%s%d", strTmp, state.delayl);
u8x8.setFont(u8x8_font_chroma48medium8_r);
u8x8.setInverseFont(i == state.cursor && state.onClick);
u8x8.drawString(0, i, strTmp);
}
}
void display() {
// в простейшем случае, функция каждый раз перерисовывает содержание дисплея исходя из состояния.
// конечно, это можно оптимизировать, храня копию предыдущего состояния и перерисовывать только измененные элементы UI
u8x8.clear();
switch(state.mode) {
case Mode::staticMode:
displayMain();
break;
case Mode::dynamicMode:
displayLgbt();
break;
case Mode::blinkMode:
displayBlink();
break;
}
}
void ledStatic() {
FastLED.setBrightness(state.brightness);
for (int i = 0; i < NUM_LEDS; i++) {
leds[i] = state.color;
}
FastLED.show();
}
void ledLgbt() {
if ((millis() - lastTime) < state.delayl)
return;
fill_rainbow(leds, NUM_LEDS, state.hue, state.dhue);
FastLED.show();
state.hue = (state.hue + 1) % 255;
FastLED.delay(state.delayl);
lastTime = millis();
}
void ledBlink() {
if ((millis() - lastTime) < state.delayl)
return;
FastLED.setBrightness(state.brightness);
for (int i = 0; i < NUM_LEDS; i++) {
leds[i] = state.color;
}
FastLED.show();
for (int i = 0; i < NUM_LEDS; i++) {
leds[i] = CRGB::White;
}
FastLED.show();
}
void led() {
switch (state.mode) {
case Mode::staticMode:
ledStatic();
break;
case Mode::dynamicMode:
ledLgbt();
break;
case Mode::blinkMode:
ledBlink();
break;
}
}
void loop(void) {
eb.tick();
// не стоит каждый раз перерисовывать дисплей.
// Только если состояние изменилось
if (state.hasChanged) {
display();
state.hasChanged = false;
}
// как часто стоит обновлять ленту уже зависит от режима
led();
}