From 93490cf06c6ff45da71971bc794453df23bff4b6 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 22 Feb 2021 21:50:58 +0300 Subject: [PATCH] 0.11b --- docs/Протокол/GyverLamp_UDP.xlsx | Bin 13917 -> 13920 bytes firmware/GyverLamp2/GyverLamp2.ino | 70 ++++-- firmware/GyverLamp2/Time.h | 2 +- firmware/GyverLamp2/button.ino | 3 +- firmware/GyverLamp2/data.h | 29 ++- firmware/GyverLamp2/eeprom.ino | 25 +- firmware/GyverLamp2/effects.ino | 127 +++++++++-- firmware/GyverLamp2/fire2D.ino | 13 +- firmware/GyverLamp2/mString.h | 315 ++++++++++++++++++++++++++ firmware/GyverLamp2/palettes.h | 3 + firmware/GyverLamp2/parsing.ino | 95 +++++--- firmware/GyverLamp2/presetManager.ino | 17 +- firmware/GyverLamp2/startup.ino | 10 + firmware/GyverLamp2/time.ino | 71 +++--- firmware/GyverLamp2/timerMillis.h | 7 +- 15 files changed, 648 insertions(+), 139 deletions(-) create mode 100644 firmware/GyverLamp2/mString.h diff --git a/docs/Протокол/GyverLamp_UDP.xlsx b/docs/Протокол/GyverLamp_UDP.xlsx index 288c4cdaccbba7209c1b1ddbe317eea047fa73c4..070e54e658a1340c0dc6f98fefb7897f4523d632 100644 GIT binary patch delta 7480 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 delta 7594 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 // общение по UDP #include // епром #include "ESP8266httpUpdate.h" // OTA +#include "mString.h" // стринг билдер // ------------------- ДАТА -------------------- Config cfg; Preset preset[MAX_PRESETS]; Dawn dawn; +Palette pal; WiFiServer server(80); WiFiUDP Udp; WiFiUDP ntpUDP; @@ -94,43 +113,48 @@ NTPClient ntp(ntpUDP); CRGB leds[MAX_LEDS]; Time now; Button btn(BTN_PIN); -timerMillis EEtmr(EE_TOUT), turnoffTmr; +timerMillis EEtmr(EE_TOUT), turnoffTmr, connTmr(120000), dawnTmr; TimeRandom trnd; VolAnalyzer vol(A0), low, high; FastFilter phot; byte btnClicks = 0, brTicks = 0; unsigned char matrixValue[11][16]; -bool gotNTP = false; +bool gotNTP = false, gotTime = false; void blink8(CRGB color); // ------------------- SETUP -------------------- void setup() { - delay(800); + delay(2000); // ждём старта есп memset(matrixValue, 0, sizeof(matrixValue)); #ifdef DEBUG_SERIAL Serial.begin(115200); DEBUGLN(); #endif EEPROM.begin(512); // старт епром - startStrip(); // старт ленты + startStrip(); // старт ленты btn.setLevel(digitalRead(BTN_PIN)); // смотрим что за кнопка EE_startup(); // читаем епром +#ifndef SKIP_WIFI checkUpdate(); // индикация было ли обновление - showRGB(); // показываем ргб + showRGB(); // показываем ргб checkGroup(); // показываем или меняем адрес checkButton(); // проверяем кнопку на удержание startWiFi(); // старт вайфай setupTime(); // выставляем время +#endif setupADC(); // настраиваем анализ presetRotation(true); // форсировать смену режима } void loop() { timeTicker(); // обновляем время +#ifndef SKIP_WIFI + tryReconnect(); // пробуем переподключиться если WiFi упал yield(); parsing(); // ловим данные yield(); +#endif checkEEupdate(); // сохраняем епром presetRotation(0); // смена режимов по расписанию effectsRoutine(); // мигаем diff --git a/firmware/GyverLamp2/Time.h b/firmware/GyverLamp2/Time.h index c9c9138..7b759ee 100644 --- a/firmware/GyverLamp2/Time.h +++ b/firmware/GyverLamp2/Time.h @@ -3,7 +3,7 @@ class Time { byte sec = 0; byte min = 0; byte hour = 0; - byte day = 0; // пн 0, вт 2.. вс 6 + byte day = 0; int ms = 0; uint32_t weekMs = 0; uint32_t weekS = 0; diff --git a/firmware/GyverLamp2/button.ino b/firmware/GyverLamp2/button.ino index b7a5921..6f9665c 100644 --- a/firmware/GyverLamp2/button.ino +++ b/firmware/GyverLamp2/button.ino @@ -17,8 +17,7 @@ void button() { DEBUGLN(btnClicks); switch (btnClicks) { case 1: - setPower(!cfg.state); - sendToSlaves(0, cfg.state); + controlHandler(!cfg.state); break; case 2: changePreset(1); diff --git a/firmware/GyverLamp2/data.h b/firmware/GyverLamp2/data.h index 111492e..d90453f 100644 --- a/firmware/GyverLamp2/data.h +++ b/firmware/GyverLamp2/data.h @@ -48,18 +48,21 @@ const char *OTAfile[] = { "module_1200.bin", }; -const char *NTPservers[] = { - "pool.ntp.org", - "europe.pool.ntp.org", - "ntp1.stratum2.ru", - "ntp2.stratum2.ru", - "ntp.msk-ix.ru", +const char NTPserver[] = "pool.ntp.org"; +//"pool.ntp.org" +//"europe.pool.ntp.org" +//"ntp1.stratum2.ru" +//"ntp2.stratum2.ru" +//"ntp.msk-ix.ru" + +struct Palette { + byte size = 1; + byte strip[16 * 3]; }; #define CFG_SIZE 13 struct Config { byte GMT = 3; // часовой пояс +13 - byte NTP = 1; // 1..5 ВЫЧЕСТЬ 1 byte bright = 100; // яркость byte adcMode = 1; // режим ацп (1 выкл, 2 ярк, 3 муз) byte minBright = 0; // мин яркость @@ -71,9 +74,11 @@ struct Config { byte maxCur = 5; // макс ток (мА/100) byte workFrom = 0; // часы работы (0,1.. 23) byte workTo = 0; // часы работы (0,1.. 23) - int16_t length = 100; // длина ленты - int16_t width = 1; // ширина матрицы - byte mTurn = 0; + byte matrix = 1; // тип матрицы 1.. 8 + + int16_t length = 100; // длина ленты + int16_t width = 1; // ширина матрицы + uint32_t cityID = 1; // city ID byte state = 1; // состояние 0 выкл, 1 вкл byte group = 1; // группа девайса (1-10) @@ -82,8 +87,8 @@ struct Config { byte presetAmount = 1; // количество режимов byte manualOff = 0; // выключали вручную? int8_t curPreset = 0; // текущий режим - int16_t minLight = 0; // мин освещённость - int16_t maxLight = 1023; // макс освещённость + int16_t minLight = 0; // мин освещённость + int16_t maxLight = 1023;// макс освещённость char ssid[32]; // логин wifi char pass[32]; // пароль wifi byte version = GL_VERSION; diff --git a/firmware/GyverLamp2/eeprom.ino b/firmware/GyverLamp2/eeprom.ino index 3df66ca..c410bf4 100644 --- a/firmware/GyverLamp2/eeprom.ino +++ b/firmware/GyverLamp2/eeprom.ino @@ -1,6 +1,7 @@ bool EEcfgFlag = false; bool EEdawnFlag = false; bool EEpresetFlag = false; +bool EEpalFlag = false; void EE_startup() { // старт епром @@ -8,18 +9,23 @@ void EE_startup() { EEPROM.write(511, EE_KEY); EEPROM.put(0, cfg); EEPROM.put(sizeof(cfg), dawn); - EEPROM.put(sizeof(cfg) + sizeof(dawn), preset); + EEPROM.put(sizeof(cfg) + sizeof(dawn), pal); + EEPROM.put(sizeof(cfg) + sizeof(dawn) + sizeof(pal), preset); EEPROM.commit(); blink8(CRGB::Pink); DEBUGLN("First start"); } EEPROM.get(0, cfg); EEPROM.get(sizeof(cfg), dawn); - EEPROM.get(sizeof(cfg) + sizeof(dawn), preset); + EEPROM.get(sizeof(cfg) + sizeof(dawn), pal); + EEPROM.get(sizeof(cfg) + sizeof(dawn) + sizeof(pal), preset); + + DEBUG("EEPR size: "); + DEBUGLN(sizeof(cfg) + sizeof(dawn) + sizeof(pal) + sizeof(preset)); // запускаем всё - //trnd.setChannel(cfg.group); FastLED.setMaxPowerInVoltsAndMilliamps(STRIP_VOLT, cfg.maxCur * 100); + updPal(); } void EE_updateCfg() { @@ -34,6 +40,10 @@ void EE_updatePreset() { EEpresetFlag = true; EEtmr.restart(); } +void EE_updatePal() { + EEpalFlag = true; + EEtmr.restart(); +} void checkEEupdate() { if (EEtmr.isReady()) { if (EEcfgFlag || EEdawnFlag || EEpresetFlag) { @@ -47,11 +57,16 @@ void checkEEupdate() { EEPROM.put(sizeof(cfg), dawn); DEBUGLN("save dawn"); } + if (EEpalFlag) { + EEpalFlag = false; + EEPROM.put(sizeof(cfg) + sizeof(dawn), pal); + DEBUGLN("save pal"); + } if (EEpresetFlag) { EEpresetFlag = false; - EEPROM.put(sizeof(cfg) + sizeof(dawn), preset); + EEPROM.put(sizeof(cfg) + sizeof(dawn) + sizeof(pal), preset); DEBUGLN("save preset"); - } + } EEPROM.commit(); } EEtmr.stop(); diff --git a/firmware/GyverLamp2/effects.ino b/firmware/GyverLamp2/effects.ino index ca3f965..b6b18f8 100644 --- a/firmware/GyverLamp2/effects.ino +++ b/firmware/GyverLamp2/effects.ino @@ -1,12 +1,30 @@ void effectsRoutine() { static timerMillis effTmr(30, true); static byte prevEff = 255; + + if (dawnTmr.running()) { + if (effTmr.isReady()) { + fill_solid(leds, MAX_LEDS, ColorFromPalette(HeatColors_p, dawnTmr.getLength8(), scaleFF(dawnTmr.getLength8(), dawn.bright), LINEARBLEND)); + FastLED.show(); + } + if (dawnTmr.isReady()) dawnTmr.stop(); + return; + } + if (cfg.state && effTmr.isReady()) { int thisLength = getLength(); byte thisScale = getScale(); int thisWidth = (cfg.deviceType > 1) ? cfg.width : 1; + byte thisBright = getBright(); + + if (turnoffTmr.running()) thisBright = scaleFF(thisBright, 255 - turnoffTmr.getLength8()); + if (turnoffTmr.isReady()) { + turnoffTmr.stop(); + setPower(0); + return; + } + FastLED.setBrightness(thisBright); - FastLED.setBrightness(getBright()); if (prevEff != CUR_PRES.effect) { FastLED.clear(); prevEff = CUR_PRES.effect; @@ -18,10 +36,10 @@ void effectsRoutine() { FOR_j(0, cfg.length) { FOR_i(0, cfg.width) { leds[getPix(i, j)] = ColorFromPalette(paletteArr[CUR_PRES.palette - 1], - inoise8( - i * (thisScale / 5) - cfg.width * (thisScale / 5) / 2, - j * (thisScale / 5) - cfg.length * (thisScale / 5) / 2, - (now.weekMs >> 1) * CUR_PRES.speed / 255), + scalePal(inoise8( + i * (thisScale / 5) - cfg.width * (thisScale / 5) / 2, + j * (thisScale / 5) - cfg.length * (thisScale / 5) / 2, + (now.weekMs >> 1) * CUR_PRES.speed / 255)), 255, LINEARBLEND); } } @@ -29,12 +47,13 @@ void effectsRoutine() { } else { FOR_i(0, cfg.length) { leds[i] = ColorFromPalette(paletteArr[CUR_PRES.palette - 1], - inoise8(i * (thisScale / 5) - cfg.length * (thisScale / 5) / 2, - (now.weekMs >> 1) * CUR_PRES.speed / 255), + scalePal(inoise8(i * (thisScale / 5) - cfg.length * (thisScale / 5) / 2, + (now.weekMs >> 1) * CUR_PRES.speed / 255)), 255, LINEARBLEND); } } break; + case 2: // ==================================== ЦВЕТ ==================================== { fill_solid(leds, cfg.length * thisWidth, CHSV(CUR_PRES.color, thisScale, CUR_PRES.min)); @@ -47,11 +66,12 @@ void effectsRoutine() { } } break; + case 3: // ================================= СМЕНА ЦВЕТА ================================= { - CRGB thisColor = ColorFromPalette(paletteArr[CUR_PRES.palette - 1], (now.weekMs >> 5) * CUR_PRES.speed / 255, CUR_PRES.min, LINEARBLEND); + CRGB thisColor = ColorFromPalette(paletteArr[CUR_PRES.palette - 1], scalePal((now.weekMs >> 5) * CUR_PRES.speed / 255), CUR_PRES.min, LINEARBLEND); fill_solid(leds, cfg.length * thisWidth, thisColor); - thisColor = ColorFromPalette(paletteArr[CUR_PRES.palette - 1], (now.weekMs >> 5) * CUR_PRES.speed / 255, CUR_PRES.max, LINEARBLEND); + thisColor = ColorFromPalette(paletteArr[CUR_PRES.palette - 1], scalePal((now.weekMs >> 5) * CUR_PRES.speed / 255), CUR_PRES.max, LINEARBLEND); if (CUR_PRES.fromCenter) { fillStrip(cfg.length / 2, cfg.length / 2 + thisLength / 2, thisColor); fillStrip(cfg.length / 2 - thisLength / 2, cfg.length / 2, thisColor); @@ -60,6 +80,7 @@ void effectsRoutine() { } } break; + case 4: // ================================== ГРАДИЕНТ ================================== if (CUR_PRES.fromCenter) { FOR_i(cfg.length / 2, cfg.length) { @@ -67,7 +88,7 @@ void effectsRoutine() { if (CUR_PRES.soundReact == GL_REACT_LEN) bright = (i < cfg.length / 2 + thisLength / 2) ? (CUR_PRES.max) : (CUR_PRES.min); CRGB thisColor = ColorFromPalette( paletteArr[CUR_PRES.palette - 1], // (x*1.9 + 25) / 255 - быстрый мап 0..255 в 0.1..2 - (i * (thisScale * 1.9 + 25) / cfg.length) + ((now.weekMs >> 3) * (CUR_PRES.speed - 128) / 128), + scalePal((i * (thisScale * 1.9 + 25) / cfg.length) + ((now.weekMs >> 3) * (CUR_PRES.speed - 128) / 128)), bright, LINEARBLEND); if (cfg.deviceType > 1) fillRow(i, thisColor); else leds[i] = thisColor; @@ -81,16 +102,41 @@ void effectsRoutine() { if (CUR_PRES.soundReact == GL_REACT_LEN) bright = (i < thisLength) ? (CUR_PRES.max) : (CUR_PRES.min); CRGB thisColor = ColorFromPalette( paletteArr[CUR_PRES.palette - 1], // (x*1.9 + 25) / 255 - быстрый мап 0..255 в 0.1..2 - (i * (thisScale * 1.9 + 25) / cfg.length) + ((now.weekMs >> 3) * (CUR_PRES.speed - 128) / 128), + scalePal((i * (thisScale * 1.9 + 25) / cfg.length) + ((now.weekMs >> 3) * (CUR_PRES.speed - 128) / 128)), bright, LINEARBLEND); if (cfg.deviceType > 1) fillRow(i, thisColor); else leds[i] = thisColor; } } break; + case 5: // =================================== ЧАСТИЦЫ =================================== FOR_i(0, cfg.length * cfg.width) leds[i].fadeToBlackBy(70); - if (cfg.deviceType > 1) { + { + uint16_t rndVal = 0; + FOR_i(0, thisScale / 8 + 1) { + rndVal = rndVal * 2053 + 13849; // random2053 алгоритм + int homeX = inoise16(i * 100000000ul + (now.weekMs << 3) * CUR_PRES.speed / 255); + homeX = map(homeX, 10000, 55000, 0, cfg.length); + int offsX = inoise8(i * 2500 + (now.weekMs >> 1) * CUR_PRES.speed / 255) - 128; + offsX = cfg.length / 2 * offsX / 128; + int thisX = homeX + offsX; + + if (cfg.deviceType > 1) { + int homeY = inoise16(i * 100000000ul + 2000000000ul + (now.weekMs << 3) * CUR_PRES.speed / 255); + homeY = map(homeY, 10000, 55000, 0, cfg.width); + int offsY = inoise8(i * 2500 + 30000 + (now.weekMs >> 1) * CUR_PRES.speed / 255) - 128; + offsY = cfg.length / 2 * offsY / 128; + int thisY = homeY + offsY; + setPix(thisX, thisY, CHSV(CUR_PRES.rnd ? rndVal : CUR_PRES.color, 255, 255)); + } else { + setLED(thisX, CHSV(CUR_PRES.rnd ? rndVal : CUR_PRES.color, 255, 255)); + } + } + } + /* + FOR_i(0, cfg.length * cfg.width) leds[i].fadeToBlackBy(70); + if (cfg.deviceType > 1) { uint16_t rndVal = 0; FOR_i(0, thisScale / 8) { int thisY = inoise16(i * 100000000ul + (now.weekMs << 6) * CUR_PRES.speed / 255); @@ -102,7 +148,7 @@ void effectsRoutine() { if (thisY >= 0 && thisY < cfg.length && thisX >= 0 && thisX < cfg.width) leds[getPix(thisX, thisY)] = CHSV(CUR_PRES.rnd ? rndVal : CUR_PRES.color, 255, 255); } - } else { + } else { uint16_t rndVal = 0; FOR_i(0, thisScale / 8) { int thisPos = inoise16(i * 100000000ul + (now.weekMs << 6) * CUR_PRES.speed / 255); @@ -110,12 +156,13 @@ void effectsRoutine() { rndVal = rndVal * 2053 + 13849; // random2053 алгоритм if (thisPos >= 0 && thisPos < cfg.length) leds[thisPos] = CHSV(CUR_PRES.rnd ? rndVal : CUR_PRES.color, 255, 255); } - } + }*/ break; + case 6: // ==================================== ОГОНЬ ==================================== { if (cfg.deviceType > 1) { // 2D огонь - fireRoutine(); + fireRoutine(CUR_PRES.speed / 2); } else { // 1D огонь static byte heat[MAX_LEDS]; CRGBPalette16 gPal; @@ -138,16 +185,21 @@ void effectsRoutine() { } } break; + case 7: // ================================== КОНФЕТТИ ================================== FOR_i(0, (thisScale >> 3) + 1) { - byte x = random(0, cfg.length * cfg.width); + int x = random(0, cfg.length * cfg.width); if (leds[x] == CRGB(0, 0, 0)) leds[x] = CHSV(CUR_PRES.rnd ? random(0, 255) : CUR_PRES.color, 255, 255); } FOR_i(0, cfg.length * cfg.width) { - if (leds[i].r >= 10 || leds[i].g >= 10 || leds[i].b >= 10) leds[i].fadeToBlackBy(CUR_PRES.speed / 2); + if (leds[i].r >= 10 || leds[i].g >= 10 || leds[i].b >= 10) leds[i].fadeToBlackBy(CUR_PRES.speed / 2 + 1); else leds[i] = 0; } break; + + case 8: // ================================== ПОГОДА ================================== + + break; } // выводим нажатия кнопки @@ -203,6 +255,13 @@ void fillRow(int row, CRGB color) { FOR_i(cfg.width * row, cfg.width * (row + 1)) leds[i] = color; } +void updPal() { + for (int i = 0; i < 16; i++) { + paletteArr[0][i] = CRGB(pal.strip[i * 3], pal.strip[i * 3 + 1], pal.strip[i * 3 + 2]); + } + if (pal.size < 16) paletteArr[0][pal.size] = paletteArr[0][0]; +} + void blink8(CRGB color) { FOR_i(0, 3) { fill_solid(leds, 8, color); @@ -214,15 +273,37 @@ void blink8(CRGB color) { } } +byte scalePal(byte val) { + if (CUR_PRES.palette == 1) val = val * pal.size / 16; + return val; +} + +void setPix(int x, int y, CRGB color) { + if (y >= 0 && y < cfg.length && x >= 0 && x < cfg.width) leds[getPix(x, y)] = color; +} +void setLED(int x, CRGB color) { + if (x >= 0 && x < cfg.length) leds[x] = color; +} + // получить номер пикселя в ленте по координатам uint16_t getPix(int x, int y) { - if (cfg.mTurn) { - byte b = x; - x = y; - y = b; + int matrixW; + if (cfg.matrix == 2 || cfg.matrix == 4 || cfg.matrix == 6 || cfg.matrix == 8) matrixW = cfg.length; + else matrixW = cfg.width; + int thisX, thisY; + switch (cfg.matrix) { + case 1: thisX = x; thisY = y; break; + case 2: thisX = y; thisY = x; break; + case 3: thisX = x; thisY = (cfg.length - y - 1); break; + case 4: thisX = (cfg.length - y - 1); thisY = x; break; + case 5: thisX = (cfg.width - x - 1); thisY = (cfg.length - y - 1); break; + case 6: thisX = (cfg.length - y - 1); thisY = (cfg.width - x - 1); break; + case 7: thisX = (cfg.width - x - 1); thisY = y; break; + case 8: thisX = y; thisY = (cfg.width - x - 1); break; } - if ( !(y & 1) || (cfg.deviceType - 2) ) return (y * cfg.width + x); // если чётная строка - else return (y * cfg.width + cfg.width - x - 1); // если нечётная строка + + if ( !(thisY & 1) || (cfg.deviceType - 2) ) return (thisY * matrixW + thisX); // чётная строка + else return (thisY * matrixW + matrixW - thisX - 1); // нечётная строка } /* целочисленный мап diff --git a/firmware/GyverLamp2/fire2D.ino b/firmware/GyverLamp2/fire2D.ino index d79a278..295c70c 100644 --- a/firmware/GyverLamp2/fire2D.ino +++ b/firmware/GyverLamp2/fire2D.ino @@ -27,10 +27,15 @@ const unsigned char hueMask[11][16] PROGMEM = { byte fireLine[100]; -void fireRoutine() { - shiftUp(); - FOR_i(0, cfg.width) fireLine[i] = random(64, 255); - drawFrame(30); +void fireRoutine(byte speed) { + static byte count = 0; + if (count >= 100) { + shiftUp(); + FOR_i(0, cfg.width) fireLine[i] = random(64, 255); + count = 0; + } + drawFrame(count); + count += speed; } void shiftUp() { diff --git a/firmware/GyverLamp2/mString.h b/firmware/GyverLamp2/mString.h new file mode 100644 index 0000000..f979cbb --- /dev/null +++ b/firmware/GyverLamp2/mString.h @@ -0,0 +1,315 @@ +// TODO +// защита от переполнения + +char* mUtoa(uint32_t value, char *buffer, bool clear = 1); +char* mLtoa(int32_t value, char *buffer, bool clear = 1); +char* mFtoa(double value, int8_t decimals, char *buffer); + +char* mUtoa(uint32_t value, char *buffer, bool clear) { + buffer += 11; + if (clear) *--buffer = 0; + do { + *--buffer = value % 10 + '0'; + value /= 10; + } while (value != 0); + return buffer; +} + +char* mLtoa(int32_t value, char *buffer, bool clear) { + bool minus = value < 0; + if (minus) value = -value; + buffer = mUtoa(value, buffer, clear); + if (minus) *--buffer = '-'; + return buffer; +} + +char* mFtoa(double value, int8_t decimals, char *buffer) { + int32_t mant = (int32_t)value; + value -= mant; + uint32_t exp = 1; + while (decimals--) exp *= 10; + exp *= (float)value; + /*buffer += 9; + buffer = mUtoa(exp, buffer); + --buffer = '.'; + buffer -= 11; + buffer = mLtoa(mant, buffer, 0);*/ + buffer = ltoa(mant, buffer, DEC); + byte len = strlen(buffer); + *(buffer + len++) = '.'; + ltoa(exp, buffer + len++, DEC); + return buffer; +} + +class mString { + public: + int size = 0; + char* buf; + // system*this = buf; + uint16_t length() { + return strlen(buf); + } + void clear() { + buf[0] = 0; + } + + // constructor + mString(char* buffer, int newSize) { + //*this = buf; + buf = buffer; + size = newSize; + } + /*mString (const char c) { + init(); + add(c); + } + mString (const char* data) { + init(); + add(data); + } + mString (const __FlashStringHelper *data) { + init(); + add(data); + } + mString (uint32_t value) { + init(); + add(value); + } + mString (int32_t value) { + init(); + add(value); + } + mString (uint16_t value) { + init(); + add(value); + } + mString (int16_t value) { + init(); + add(value); + } + mString (uint8_t value) { + init(); + add(value); + } + mString (int8_t value) { + init(); + add(value); + } + mString (double value, byte dec = 2) { + init(); + add(value, dec); + }*/ + + // add + mString& add(const char c) { + byte len = length(); + buf[len++] = c; + buf[len++] = 0; + return *this; + } + mString& add(const char* data) { + /*byte len = length(); + do { + buf[len] = *(data++); + } while (buf[len++] != 0);*/ + strcpy(buf + length(), data); + return *this; + } + mString& add(const __FlashStringHelper *data) { + PGM_P p = reinterpret_cast(data); + strcpy_P(buf + length(), p); + return *this; + /*do { + buf[len] = (char)pgm_read_byte_near(p++); + } while (buf[len++] != 0); + */ + } + mString& add(uint32_t value) { + //char buf[11]; + //return add(mUtoa(value, buf)); + utoa(value, buf + length(), DEC); + return *this; + } + mString& add(uint16_t value) { + return add((uint32_t)value); + } + mString& add(uint8_t value) { + return add((uint32_t)value); + } + mString& add(int32_t value) { + //char buf[11]; + //return add(mLtoa(value, buf)); + ltoa(value, buf + length(), DEC); + return *this; + } + mString& add(int16_t value) { + return add((int32_t)value); + } + mString& add(int8_t value) { + return add((int32_t)value); + } + mString& add(double value, int8_t dec = 2) { + char buf[20]; + return add(mFtoa(value, dec, buf)); + //dtostrf(value, dec, DEC, buf+length()); + //return *this; + } + + // add += + mString& operator += (const char c) { + return add(c); + } + mString& operator += (const char* data) { + return add(data); + } + mString& operator += (const __FlashStringHelper *data) { + return add(data); + } + mString& operator += (uint32_t value) { + return add(value); + } + mString& operator += (int32_t value) { + return add(value); + } + mString& operator += (uint16_t value) { + return add(value); + } + mString& operator += (int16_t value) { + return add(value); + } + mString& operator += (uint8_t value) { + return add(value); + } + mString& operator += (int8_t value) { + return add(value); + } + mString& operator += (double value) { + return add(value); + } + + // assign + mString& operator = (const char c) { + clear(); + return add(c); + } + mString& operator = (const char* data) { + clear(); + return add(data); + } + mString& operator = (const __FlashStringHelper *data) { + clear(); + return add(data); + } + mString& operator = (uint32_t value) { + clear(); + return add(value); + } + mString& operator = (int32_t value) { + clear(); + return add(value); + } + mString& operator = (uint16_t value) { + clear(); + return add(value); + } + mString& operator = (int16_t value) { + clear(); + return add(value); + } + mString& operator = (uint8_t value) { + clear(); + return add(value); + } + mString& operator = (int8_t value) { + clear(); + return add(value); + } + mString& operator = (double value) { + clear(); + return add(value); + } + + // compare + bool operator == (const char c) { + return (buf[0] == c && buf[1] == 0); + } + bool operator == (const char* data) { + return !strcmp(buf, data); + } + bool operator == (uint32_t value) { + char valBuf[11]; + return !strcmp(buf, utoa(value, valBuf, DEC)); + } + bool operator == (int32_t value) { + char valBuf[11]; + return !strcmp(buf, ltoa(value, valBuf, DEC)); + } + bool operator == (float value) { + char valBuf[20]; + return !strcmp(buf, mFtoa(value, 2, valBuf)); + } + char operator [] (uint16_t index) const { + return (index < size ? buf[index] : 0); + } + char& operator [] (uint16_t index) { + return buf[index]; + } + + + // convert & parse + uint32_t toInt() { + return atoi(buf); + } + float toFloat() { + return atof(buf); + } + const char* c_str() { + return buf; + } + bool startsWith(const char *data) { + return strlen(data) == strspn(buf, data); + } + + int indexOf(char ch, uint16_t fromIndex = 0) { + if (fromIndex >= length()) return -1; + const char* temp = strchr(buf + fromIndex, ch); + if (temp == NULL) return -1; + return temp - buf; + } + int parseBytes(byte* data, int len, char div = ',', char ter = NULL) { + int b = 0, c = 0; + data[b] = 0; + while (true) { + if (buf[c] == div) { + b++; + c++; + if (b == len) return b; + data[b] = 0; + continue; + } + if (buf[c] == ter || b == len) return b + 1; + data[b] *= 10; + data[b] += buf[c] - '0'; + c++; + } + } + int parseInts(int* data, int len, char div = ',', char ter = NULL) { + int b = 0, c = 0; + data[b] = 0; + while (true) { + if (buf[c] == div) { + b++; + c++; + if (b == len) return b; + data[b] = 0; + continue; + } + if (buf[c] == ter || b == len) return b + 1; + data[b] *= 10; + data[b] += buf[c] - '0'; + c++; + } + } + private: + +}; diff --git a/firmware/GyverLamp2/palettes.h b/firmware/GyverLamp2/palettes.h index 5bc2846..7669120 100644 --- a/firmware/GyverLamp2/palettes.h +++ b/firmware/GyverLamp2/palettes.h @@ -1,6 +1,8 @@ #include // лента // http://soliton.vm.bytemark.co.uk/pub/cpt-city/ +CRGBPalette16 customPal; + DEFINE_GRADIENT_PALETTE( Fire_gp ) { 0, 0, 0, 0, 128, 255, 0, 0, @@ -221,6 +223,7 @@ DEFINE_GRADIENT_PALETTE ( aurora_gp ) { }; CRGBPalette16 paletteArr[] = { + customPal, HeatColors_p, Fire_gp, LavaColors_p, diff --git a/firmware/GyverLamp2/parsing.ino b/firmware/GyverLamp2/parsing.ino index c56432e..d8afc24 100644 --- a/firmware/GyverLamp2/parsing.ino +++ b/firmware/GyverLamp2/parsing.ino @@ -9,35 +9,47 @@ void parsing() { buf[n] = NULL; DEBUGLN(buf); // пакет вида <ключ>,<канал>,<тип>,<дата1>,<дата2>... + mString pars(buf, sizeof(buf)); + if (!pars.startsWith(GL_KEY)) return; // не наш ключ + byte keyLen = strlen(GL_KEY); - byte keyLen = strchr(buf, ',') - buf; // indexof - if (strncmp(buf, GL_KEY, keyLen)) return; // не наш ключ - - byte data[MAX_PRESETS * PRES_SIZE + keyLen]; + byte data[MAX_PRESETS * PRES_SIZE + 5]; memset(data, 0, MAX_PRESETS * PRES_SIZE + keyLen); int count = 0; char *str, *p = buf + keyLen; // сдвиг до даты char *ssid, *pass; + uint32_t city = 0; + uint16_t stripL, stripW; while ((str = strtok_r(p, ",", &p)) != NULL) { - data[count++] = atoi(str); - if (count == 4) ssid = str; - if (count == 5) pass = str; + uint32_t thisInt = atoi(str); + data[count++] = (byte)thisInt; + if (data[1] == 0) { + if (count == 4) ssid = str; + if (count == 5) pass = str; + } + if (data[1] == 1) { + if (count == 16) stripL = thisInt; + if (count == 17) stripW = thisInt; + if (count == 18) city = thisInt; + } } // широковещательный запрос времени для local устройств в сети AP лампы if (data[0] == 0 && cfg.WiFimode && !gotNTP) { - now.hour = data[1]; - now.min = data[2]; + now.day = data[1]; + now.hour = data[2]; + now.min = data[3]; + now.sec = data[4]; now.setMs(0); } if (data[0] != cfg.group) return; // не наш адрес, выходим - switch (data[1]) { // тип 0 - control, 1 - config, 2 - effects, 3 - dawn + switch (data[1]) { // тип 0 - control, 1 - config, 2 - effects, 3 - dawn, 4 - from master, 5 - palette case 0: DEBUGLN("Control"); switch (data[2]) { - case 0: setPower(0); break; // выкл - case 1: setPower(1); break; // вкл + case 0: controlHandler(0); break; // выкл + case 1: controlHandler(1); break; // вкл case 2: cfg.minLight = phot.getRaw(); break; // мин яркость case 3: cfg.maxLight = phot.getRaw(); break; // макс яркость case 4: changePreset(-1); break; // пред пресет @@ -54,12 +66,16 @@ void parsing() { case 12: if (gotNTP) { // OTA обновление, если есть интернет cfg.update = 1; EE_updCfg(); - delay(100); FastLED.clear(); FastLED.show(); char OTA[60]; - strcpy(OTA, OTAhost); - strcpy(OTA + strlen(OTAhost), OTAfile[data[3]]); + mString ota(OTA, 60); + ota.clear(); + ota += OTAhost; + ota += OTAfile[data[3]]; + DEBUG("Update to "); + DEBUGLN(OTA); + delay(100); ESPhttpUpdate.update(OTA); } break; case 13: // выключить через @@ -77,15 +93,14 @@ void parsing() { FOR_i(0, CFG_SIZE) { *((byte*)&cfg + i) = data[i + 2]; // загоняем в структуру } - cfg.mTurn = data[21]; - cfg.length = data[17] | (data[16] << 8); // склеиваем - cfg.width = data[20] | (data[19] << 8); // склеиваем + cfg.length = stripL; + cfg.width = stripW; + cfg.cityID = city; if (cfg.length > MAX_LEDS) cfg.length = MAX_LEDS; if (cfg.deviceType == GL_TYPE_STRIP) cfg.width = 1; if (cfg.length * cfg.width > MAX_LEDS) cfg.width = MAX_LEDS / cfg.length; ntp.setTimeOffset((cfg.GMT - 13) * 3600); - ntp.setPoolServerName(NTPservers[cfg.NTP - 1]); FastLED.setMaxPowerInVoltsAndMilliamps(STRIP_VOLT, cfg.maxCur * 100); if (cfg.adcMode == GL_ADC_BRI) switchToPhot(); else if (cfg.adcMode == GL_ADC_MIC) switchToMic(); @@ -100,6 +115,7 @@ void parsing() { *((byte*)&preset + j * PRES_SIZE + i) = data[j * PRES_SIZE + i + 3]; // загоняем в структуру } } + if (!cfg.rotation) setPreset(data[cfg.presetAmount * PRES_SIZE + 3] - 1); EE_updatePreset(); presetRotation(true); // форсировать смену режима break; @@ -121,9 +137,25 @@ void parsing() { EE_updateCfg(); } break; + + case 5: DEBUGLN("Palette"); + FOR_i(0, 1 + 16 * 3) { + *((byte*)&pal + i) = data[i + 2]; // загоняем в структуру + } + updPal(); + EE_updatePal(); + break; + + case 6: DEBUGLN("Time"); + if (!cfg.WiFimode) { // если мы AP + now.day = data[2]; + now.hour = data[3]; + now.min = data[4]; + } + gotTime = true; + break; } FastLED.clear(); // на всякий случай - } } @@ -131,18 +163,19 @@ void sendToSlaves(byte data1, byte data2) { if (cfg.role == GL_MASTER) { IPAddress ip = WiFi.localIP(); ip[3] = 255; - char reply[20] = GL_KEY; - byte keylen = strlen(GL_KEY); - reply[keylen++] = ','; - reply[keylen++] = cfg.group + '0'; - reply[keylen++] = ','; - reply[keylen++] = '4'; - reply[keylen++] = ','; - reply[keylen++] = data1 + '0'; - reply[keylen++] = ','; - itoa(data2, reply + (keylen++), DEC); - DEBUG("Sending: "); + char reply[20]; + mString packet(reply, sizeof(reply)); + packet.clear(); + packet += GL_KEY; + packet += ','; + packet += cfg.group; + packet += ",4,"; + packet += data1; + packet += ','; + packet += data2; + + DEBUG("Sending to Slaves: "); DEBUGLN(reply); FOR_i(0, 3) { diff --git a/firmware/GyverLamp2/presetManager.ino b/firmware/GyverLamp2/presetManager.ino index d8a2259..2a24cf0 100644 --- a/firmware/GyverLamp2/presetManager.ino +++ b/firmware/GyverLamp2/presetManager.ino @@ -30,14 +30,29 @@ void setPreset(byte pres) { } } -void setPower(bool state) { +void controlHandler(bool state) { + if (turnoffTmr.running()) { + turnoffTmr.stop(); + DEBUGLN("stop off timer"); + return; + } + if (dawnTmr.running()) { + dawnTmr.stop(); + DEBUGLN("stop dawn timer"); + return; + } if (state) cfg.manualOff = 0; if (cfg.state && !state) cfg.manualOff = 1; + setPower(state); +} + +void setPower(bool state) { cfg.state = state; if (!state) { delay(100); // чтобы пролететь мин. частоту обновления FastLED.clear(); FastLED.show(); } + sendToSlaves(0, cfg.state); DEBUGLN(state ? "Power on" : "Power off"); } diff --git a/firmware/GyverLamp2/startup.ino b/firmware/GyverLamp2/startup.ino index 7a8a139..d7cf5b5 100644 --- a/firmware/GyverLamp2/startup.ino +++ b/firmware/GyverLamp2/startup.ino @@ -135,6 +135,7 @@ void setupLocal() { delay(50); } if (connect) { + connTmr.stop(); blink8(CRGB::Green); server.begin(); DEBUG("Connected! Local IP: "); @@ -147,6 +148,7 @@ void setupLocal() { failCount++; tmr = millis(); if (failCount >= 3) { + connTmr.restart(); // попробуем позже setupAP(); return; /*DEBUGLN("Reboot to AP!"); @@ -172,5 +174,13 @@ void checkUpdate() { DEBUG("Update to current"); } cfg.update = 0; + EE_updCfg(); + } +} + +void tryReconnect() { + if (connTmr.isReady()) { + DEBUGLN("Reconnect"); + setupLocal(); } } diff --git a/firmware/GyverLamp2/time.ino b/firmware/GyverLamp2/time.ino index acc5804..a0ab478 100644 --- a/firmware/GyverLamp2/time.ino +++ b/firmware/GyverLamp2/time.ino @@ -1,7 +1,7 @@ void setupTime() { ntp.setUpdateInterval(NTP_UPD_PRD / 2 * 60000ul); // меньше в два раза, ибо апдейт вручную ntp.setTimeOffset((cfg.GMT - 13) * 3600); - ntp.setPoolServerName(NTPservers[cfg.NTP - 1]); + ntp.setPoolServerName(NTPserver); if (cfg.WiFimode) { // если подключены - запрашиваем время с сервера ntp.begin(); @@ -16,8 +16,7 @@ void timeTicker() { updateTime(); // обновляем время sendTimeToSlaves(); // отправляем время слейвам trnd.update(now.hour, now.min, now.sec); // обновляем рандомайзер - if (gotNTP) checkWorkTime(); // проверяем расписание, если подключены к Интернет - checkTurnoff(); // проверяем таймер отключения + if (gotNTP || gotTime) checkWorkTime(); // проверяем расписание, если знаем время } } @@ -26,68 +25,68 @@ void updateTime() { now.sec = ntp.getSeconds(); now.min = ntp.getMinutes(); now.hour = ntp.getHours(); - now.day = ntp.getDay(); - now.day = (now.day == 0) ? 6 : (now.day - 1); // перевод из вс0 в пн0 + now.day = ntp.getDay(); // вс 0, сб 6 now.weekMs = now.getWeekS() * 1000ul + ntp.getMillis(); now.setMs(ntp.getMillis()); if (now.min % NTP_UPD_PRD == 0 && now.sec == 0) { // берём время с интернета каждую NTP_UPD_PRD минуту, ставим флаг что данные с NTP получены, значит мы онлайн if (ntp.update() && !gotNTP) gotNTP = true; } + checkDawn(); } else { // если нет now.tick(); // тикаем своим счётчиком } } void sendTimeToSlaves() { - if (!cfg.WiFimode) { // если мы AP + if (!cfg.WiFimode) { // если мы AP static byte prevSec = 0; if (prevSec != now.sec) { // новая секунда prevSec = now.sec; - if (now.min % 1 == 0 && now.sec == 0) sendTime(); // ровно каждые 5 мин отправляем время + if (now.min % 5 == 0 && now.sec == 0) sendTime(); // ровно каждые 5 мин отправляем время } } } -void checkTurnoff() { - if (turnoffTmr.isReady()) { - turnoffTmr.stop(); - setPower(0); +void checkDawn() { + if (now.sec == 0 && dawn.state[now.day] && !dawnTmr.running()) { // рассвет включен но не запущен + int dawnMinute = dawn.hour[now.day] * 60 + dawn.minute[now.day] - dawn.time; + if (dawnMinute < 0) dawnMinute += 1440; + if (dawnMinute == now.hour * 60 + now.min) { + DEBUGLN("dawn start"); + dawnTmr.setInterval(dawn.time * 60000ul); + dawnTmr.restart(); + } } } void checkWorkTime() { - if (!isWorkTime(now.hour, cfg.workFrom, cfg.workTo)) { - if (cfg.state) { - cfg.state = false; - FastLED.clear(); - FastLED.show(); - } - } else { - if (!cfg.state && !cfg.manualOff) { - cfg.state = true; - } + static byte prevState = 2; // для первого запуска + byte curState = isWorkTime(now.hour, cfg.workFrom, cfg.workTo); + if (prevState != curState) { // переключение расписания + prevState = curState; + if (curState && !cfg.state && !cfg.manualOff) setPower(1); // нужно включить, а лампа выключена и не выключалась вручную + if (!curState && cfg.state) setPower(0); // нужно выключить, а лампа включена } } void sendTime() { IPAddress ip = WiFi.localIP(); ip[3] = 255; - char reply[20] = GL_KEY; - byte keylen = strlen(GL_KEY); - reply[keylen++] = ','; - reply[keylen++] = 0 + '0'; - reply[keylen++] = ','; - char hours[4]; - itoa(now.hour, hours, DEC); - strncpy(reply + keylen, hours, 3); - keylen += strlen(hours); - reply[keylen++] = ','; - char mins[4]; - itoa(now.min, mins, DEC); - strncpy(reply + keylen, mins, 3); - keylen += strlen(mins); - reply[keylen++] = NULL; + char reply[25] = GL_KEY; + mString packet(reply, sizeof(reply)); + packet.clear(); + packet += GL_KEY; + packet += ','; + packet += 0; + packet += ','; + packet += now.day; + packet += ','; + packet += now.hour; + packet += ','; + packet += now.min; + packet += ','; + packet += now.sec; DEBUG("Sending time: "); DEBUGLN(reply); diff --git a/firmware/GyverLamp2/timerMillis.h b/firmware/GyverLamp2/timerMillis.h index c25c63a..4879f9d 100644 --- a/firmware/GyverLamp2/timerMillis.h +++ b/firmware/GyverLamp2/timerMillis.h @@ -12,7 +12,6 @@ class timerMillis { } boolean isReady() { if (_active && millis() - _tmr >= _interval) { - //_tmr += _interval; reset(); return true; } @@ -28,6 +27,12 @@ class timerMillis { void stop() { _active = false; } + bool running() { + return _active; + } + byte getLength8() { + return (millis() - _tmr) * 255ul / _interval; + } private: uint32_t _tmr = 0;