This commit is contained in:
Alex
2021-02-22 21:50:58 +03:00
parent d5f70a4646
commit 93490cf06c
15 changed files with 648 additions and 139 deletions

View File

@@ -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 <WiFiUdp.h> // общение по UDP
#include <EEPROM.h> // епром
#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(); // мигаем

View File

@@ -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;

View File

@@ -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);

View File

@@ -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;

View File

@@ -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();

View File

@@ -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); // нечётная строка
}
/*
целочисленный мап

View File

@@ -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() {

View File

@@ -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<PGM_P>(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:
};

View File

@@ -1,6 +1,8 @@
#include <FastLED.h> // лента
// 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,

View File

@@ -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) {

View File

@@ -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");
}

View File

@@ -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();
}
}

View File

@@ -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);

View File

@@ -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;