diff --git a/docs/Протокол/GyverLamp_UDP.xlsx b/docs/Протокол/GyverLamp_UDP.xlsx index 288c4cd..070e54e 100644 Binary files a/docs/Протокол/GyverLamp_UDP.xlsx and b/docs/Протокол/GyverLamp_UDP.xlsx differ diff --git a/firmware/GyverLamp2/GyverLamp2.ino b/firmware/GyverLamp2/GyverLamp2.ino index 88ec886..33b09ed 100644 --- a/firmware/GyverLamp2/GyverLamp2.ino +++ b/firmware/GyverLamp2/GyverLamp2.ino @@ -1,20 +1,36 @@ -// 0.10 -// исправлена обработка ключа -// добавлена совместимость с nodemcu -// поворот матрицы -// обновление прошивок для разных схем -// исправлен цвет огня -// индикация обновления при запуске +/* + Версия 0.11b + Добавлен редактор палитр + Исправлены мелкие баги в эффектах + Переподключение к роутеру после сброса сети + Настройка ориентации матрицы из приложения + Переработан эффект "Частицы" + Добавлена скорость огня + Переключение на новый/выбранный режим при редактировании + Отправка времени из сервиса (для АР) + Выключение по таймеру теперь плавное + Добавлен рассвет -// мигает 8: -// красным - не смог подключиться к АР -// зелёным - смог подключиться к АР -// жёлтым - создал свою АП -// бирюзовым - успешно обновился на новую версию -// синим - обновился на ту же версию -// розовым - сброс всех настроек (первый запуск) + TODO: + плавная смена режимов + Аккуратнее со светомузыкой! + 4 клика вкл выкл смену? + Mqtt? + Базовый пак + Предложения Серёги крутского + Убрать аплод? + Огонь 2018/2020? + Взять огонь отсюда https://community.alexgyver.ru/threads/wifi-lampa-budilnik-obsuzhdenie-proshivki-ot-gunner47.2418/page-72#post-33652 + Вернуть искры + Эффект погода https://it4it.club/topic/40-esp8266-i-parsing-pogodyi-s-openweathermap/ + Эффект часы +*/ -// Generic ESP8266, 4MB (FS:2MB OTA) +// ВНИМАНИЕ! ВНИМАНИЕ! ВНИМАНИЕ! ВНИМАНИЕ! ВНИМАНИЕ! ВНИМАНИЕ! ВНИМАНИЕ! +// ДЛЯ КОМПИЛЯЦИИ ПРОШИВКИ ПОД NODEMCU/WEMOS/ESP01/ESP12 ВЫБИРАТЬ +// Инструменты/Плата Generic ESP8266 +// Инструменты/Flash Size 4MB (FS:2MB OTA) +// При прошивке с других прошивок лампы поставить: Инструменты/Erase Flash/All Flash Contents // ESP core 2.7.4+ http://arduino.esp8266.com/stable/package_esp8266com_index.json // FastLED 3.4.0+ https://github.com/FastLED/FastLED/releases @@ -49,11 +65,12 @@ const char AP_NameChar[] = "GyverLamp2"; const char WiFiPassword[] = "12345678"; // ------------ Прочее ------------- -#define GL_VERSION 010 +#define GL_VERSION 011 // код версии прошивки #define EE_TOUT 30000 // таймаут сохранения епром после изменения, мс -#define DEBUG_SERIAL // закомментируй чтобы выключить отладку (скорость 115200) -#define EE_KEY 44 // ключ сброса WiFi (измени для сброса всех настроек) +//#define DEBUG_SERIAL // закомментируй чтобы выключить отладку (скорость 115200) +#define EE_KEY 50 // ключ сброса WiFi (измени для сброса всех настроек) #define NTP_UPD_PRD 5 // период обновления времени с NTP сервера, минут +//#define SKIP_WIFI // пропустить подключение к вафле (для отладки) // ------------ БИЛДЕР ------------- //#define MAX_LEDS 1200 @@ -82,11 +99,13 @@ const char WiFiPassword[] = "12345678"; #include // общение по 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;