From e1c537c971ed1e86f2cb311926957a5102eab7f6 Mon Sep 17 00:00:00 2001 From: gunner47 Date: Tue, 16 Jul 2019 22:54:03 +0300 Subject: [PATCH] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=BE=20=D0=B2=D1=8B=D0=BA=D0=BB=D1=8E=D1=87=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=B1=D1=83=D0=B4=D0=B8=D0=BB=D1=8C=D0=BD?= =?UTF-8?q?=D0=B8=D0=BA=D0=B0,=20=D0=B5=D1=81=D0=BB=D0=B8=20=D0=BE=D0=BD?= =?UTF-8?q?=20=D1=81=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=D0=BB=20=D0=BF?= =?UTF-8?q?=D1=80=D0=B8=20=D0=B2=D1=8B=D0=BA=D0=BB=D1=8E=D1=87=D0=B5=D0=BD?= =?UTF-8?q?=D0=BD=D0=BE=D0=B9=20=D0=BC=D0=B0=D1=82=D1=80=D0=B8=D1=86=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- firmware/GyverLamp_v1.4/GyverLamp_v1.4.ino | 16 +++++- firmware/GyverLamp_v1.4/button.ino | 12 +---- firmware/GyverLamp_v1.4/effectTicker.ino | 2 +- firmware/GyverLamp_v1.4/effects.ino | 54 ++++++++++----------- firmware/GyverLamp_v1.4/noiseEffects.ino | 2 +- firmware/GyverLamp_v1.4/parsing.ino | 2 +- firmware/GyverLamp_v1.4/time.ino | 5 +- testing/Testing_v1.4.xlsx | Bin 11204 -> 11580 bytes 8 files changed, 49 insertions(+), 44 deletions(-) diff --git a/firmware/GyverLamp_v1.4/GyverLamp_v1.4.ino b/firmware/GyverLamp_v1.4/GyverLamp_v1.4.ino index cd60951..7c9613f 100644 --- a/firmware/GyverLamp_v1.4/GyverLamp_v1.4.ino +++ b/firmware/GyverLamp_v1.4/GyverLamp_v1.4.ino @@ -11,6 +11,18 @@ Версия 1.4: - Исправлен баг при смене режимов - Исправлены тормоза в режиме точки доступа + --- 08.07.2019 + - Исправлены параметры и процесс подключения к WiFi сети (таймаут 7 секунд) и развёртываия WiFi точки доступа (параметры имени/пароля) + - Добавлено "#define USE_NTP" - позволяет запретить обращаться в интернет + - Добавлено "#define ESP_USE_BUTTON - позволяет собирать лампу без физической кнопки, иначе яркость эффектов самопроизвольно растёт до максимальной + - Переработаны параметры IP адресов, STA_STATIC_IP теперь пустой по умолчанию - избавляет от путаницы с IP адресами из неправильных диапазонов + - Добавлено "#define GENERAL_DEBUG" - выводит в Serial некоторые отладочные сообщения + - Добавлено "#define WIFIMAN_DEBUG (true)" - выводит в Serial отладочные сообщения библиотеки WiFiManager + - Добавлена таблица с тест кейсами + - Форматирование кода, комментарии + --- 11.07.2019 + - Исправлена ошибка невыключения матрицы после срабатывания будильника, если до будильника матрица была выключенной + - Дополнена таблица с тест кейсами */ // Ссылка для менеджера плат: @@ -19,9 +31,9 @@ // ============= НАСТРОЙКИ ============= // --- ВРЕМЯ --------------------------- -#define USE_NTP // закомментировать или удалить эту строку, если не нужно, чтобы устройство не лезло в интернет +#define USE_NTP // закомментировать или удалить эту строку, если нужно, чтобы устройство не лезло в интернет #define GMT (3) // часовой пояс (москва 3) -#define NTP_ADDRESS "europe.pool.ntp.org" // сервер времени +#define NTP_ADDRESS ("europe.pool.ntp.org") // сервер времени // --- РАССВЕТ ------------------------- #define DAWN_BRIGHT (200U) // максимальная яркость рассвета (0-255) diff --git a/firmware/GyverLamp_v1.4/button.ino b/firmware/GyverLamp_v1.4/button.ino index ba5d4ca..4f1df84 100644 --- a/firmware/GyverLamp_v1.4/button.ino +++ b/firmware/GyverLamp_v1.4/button.ino @@ -16,16 +16,8 @@ void buttonTick() } else { - if (ONflag) - { - ONflag = false; - changePower(); - } - else - { - ONflag = true; - changePower(); - } + ONflag = !ONflag; + changePower(); } } diff --git a/firmware/GyverLamp_v1.4/effectTicker.ino b/firmware/GyverLamp_v1.4/effectTicker.ino index da60dbd..bb3bbdf 100644 --- a/firmware/GyverLamp_v1.4/effectTicker.ino +++ b/firmware/GyverLamp_v1.4/effectTicker.ino @@ -4,7 +4,7 @@ void effectsTick() { if (!dawnFlag) { - if (ONflag && millis() - effTimer >= ((currentMode < 5 || currentMode > 13) ? modes[currentMode].speed : 50) ) + if (ONflag && (millis() - effTimer >= ((currentMode < 5 || currentMode > 13) ? modes[currentMode].speed : 50))) { effTimer = millis(); switch (currentMode) diff --git a/firmware/GyverLamp_v1.4/effects.ino b/firmware/GyverLamp_v1.4/effects.ino index 0975fbf..655499b 100644 --- a/firmware/GyverLamp_v1.4/effects.ino +++ b/firmware/GyverLamp_v1.4/effects.ino @@ -3,10 +3,10 @@ // ------------- конфетти -------------- void sparklesRoutine() { - for (byte i = 0; i < modes[0].scale; i++) + for (uint8_t i = 0; i < modes[0].scale; i++) { - byte x = random(0, WIDTH); - byte y = random(0, HEIGHT); + uint8_t x = random(0, WIDTH); + uint8_t y = random(0, HEIGHT); if (getPixColorXY(x, y) == 0) leds[getPixelNumber(x, y)] = CHSV(random(0, 255), 255, 255); } @@ -14,18 +14,18 @@ void sparklesRoutine() } // функция плавного угасания цвета для всех пикселей -void fader(byte step) +void fader(uint8_t step) { - for (byte i = 0; i < WIDTH; i++) + for (uint8_t i = 0; i < WIDTH; i++) { - for (byte j = 0; j < HEIGHT; j++) + for (uint8_t j = 0; j < HEIGHT; j++) { fadePixel(i, j, step); } } } -void fadePixel(byte i, byte j, byte step) // новый фейдер +void fadePixel(uint8_t i, uint8_t j, uint8_t step) // новый фейдер { int32_t pixelNum = getPixelNumber(i, j); if (getPixColor(pixelNum) == 0) return; @@ -180,15 +180,15 @@ void drawFrame(int32_t pcnt) } } -byte hue; +uint8_t hue; // ------------- радуга ---------------- void rainbowVertical() { hue += 2; - for (byte j = 0; j < HEIGHT; j++) + for (uint8_t j = 0; j < HEIGHT; j++) { - CHSV thisColor = CHSV((byte)(hue + j * modes[2].scale), 255, 255); - for (byte i = 0; i < WIDTH; i++) + CHSV thisColor = CHSV((uint8_t)(hue + j * modes[2].scale), 255, 255); + for (uint8_t i = 0; i < WIDTH; i++) drawPixelXY(i, j, thisColor); } } @@ -196,10 +196,10 @@ void rainbowVertical() void rainbowHorizontal() { hue += 2; - for (byte i = 0; i < WIDTH; i++) + for (uint8_t i = 0; i < WIDTH; i++) { - CHSV thisColor = CHSV((byte)(hue + i * modes[3].scale), 255, 255); - for (byte j = 0; j < HEIGHT; j++) + CHSV thisColor = CHSV((uint8_t)(hue + i * modes[3].scale), 255, 255); + for (uint8_t j = 0; j < HEIGHT; j++) drawPixelXY(i, j, thisColor); //leds[getPixelNumber(i, j)] = thisColor; } } @@ -227,19 +227,19 @@ void colorRoutine() void snowRoutine() { // сдвигаем всё вниз - for (byte x = 0; x < WIDTH; x++) + for (uint8_t x = 0; x < WIDTH; x++) { - for (byte y = 0; y < HEIGHT - 1; y++) + for (uint8_t y = 0; y < HEIGHT - 1; y++) { drawPixelXY(x, y, getPixColorXY(x, y + 1)); } } - for (byte x = 0; x < WIDTH; x++) + for (uint8_t x = 0; x < WIDTH; x++) { // заполняем случайно верхнюю строку // а также не даём двум блокам по вертикали вместе быть - if (getPixColorXY(x, HEIGHT - 2) == 0 && (random(0, modes[15].scale) == 0)) + if (getPixColorXY(x, HEIGHT - 2) == 0 && (random(0, 100 - modes[15].scale) == 0)) drawPixelXY(x, HEIGHT - 1, 0xE0FFFF - 0x101010 * random(0, 4)); else drawPixelXY(x, HEIGHT - 1, 0x000000); @@ -249,12 +249,12 @@ void snowRoutine() // ------------- матрица --------------- void matrixRoutine() { - for (byte x = 0; x < WIDTH; x++) + for (uint8_t x = 0; x < WIDTH; x++) { // заполняем случайно верхнюю строку uint32_t thisColor = getPixColorXY(x, HEIGHT - 1); if (thisColor == 0) - drawPixelXY(x, HEIGHT - 1, 0x00FF00 * (random(0, modes[16].scale) == 0)); + drawPixelXY(x, HEIGHT - 1, 0x00FF00 * (random(0, 100 - modes[16].scale) == 0)); else if (thisColor < 0x002000) drawPixelXY(x, HEIGHT - 1, 0); else @@ -262,9 +262,9 @@ void matrixRoutine() } // сдвигаем всё вниз - for (byte x = 0; x < WIDTH; x++) + for (uint8_t x = 0; x < WIDTH; x++) { - for (byte y = 0; y < HEIGHT - 1; y++) + for (uint8_t y = 0; y < HEIGHT - 1; y++) { drawPixelXY(x, y, getPixColorXY(x, y + 1)); } @@ -276,7 +276,7 @@ void matrixRoutine() int32_t lightersPos[2][LIGHTERS_AM]; int8_t lightersSpeed[2][LIGHTERS_AM]; CHSV lightersColor[LIGHTERS_AM]; -byte loopCounter; +uint8_t loopCounter; int32_t angle[LIGHTERS_AM]; int32_t speedV[LIGHTERS_AM]; @@ -288,7 +288,7 @@ void lightersRoutine() { loadingFlag = false; randomSeed(millis()); - for (byte i = 0; i < LIGHTERS_AM; i++) + for (uint8_t i = 0; i < LIGHTERS_AM; i++) { lightersPos[0][i] = random(0, WIDTH * 10); lightersPos[1][i] = random(0, HEIGHT * 10); @@ -299,7 +299,7 @@ void lightersRoutine() } FastLED.clear(); if (++loopCounter > 20) loopCounter = 0; - for (byte i = 0; i < modes[17].scale; i++) + for (uint8_t i = 0; i < modes[17].scale; i++) { if (loopCounter == 0) // меняем скорость каждые 255 отрисовок { @@ -336,7 +336,7 @@ void lightersRoutine() { loadingFlag = false; randomSeed(millis()); - for (byte i = 0; i < LIGHTERS_AM; i++) + for (uint8_t i = 0; i < LIGHTERS_AM; i++) { lightersPos[0][i] = random(0, WIDTH * 10); lightersPos[1][i] = random(0, HEIGHT * 10); @@ -351,7 +351,7 @@ void lightersRoutine() FastLED.clear(); if (++loopCounter > 20) loopCounter = 0; - for (byte i = 0; i < modes[17].scale; i++) + for (uint8_t i = 0; i < modes[17].scale; i++) { if (loopCounter == 0) // меняем скорость каждые 255 отрисовок { diff --git a/firmware/GyverLamp_v1.4/noiseEffects.ino b/firmware/GyverLamp_v1.4/noiseEffects.ino index 46303b6..ae8ebc6 100644 --- a/firmware/GyverLamp_v1.4/noiseEffects.ino +++ b/firmware/GyverLamp_v1.4/noiseEffects.ino @@ -167,7 +167,7 @@ void lavaNoise() void fillNoiseLED() { uint8_t dataSmoothing = 0; - if ( speed < 50) + if (speed < 50) { dataSmoothing = 200 - (speed * 4); } diff --git a/firmware/GyverLamp_v1.4/parsing.ino b/firmware/GyverLamp_v1.4/parsing.ino index 49325ad..f427d10 100644 --- a/firmware/GyverLamp_v1.4/parsing.ino +++ b/firmware/GyverLamp_v1.4/parsing.ino @@ -45,7 +45,7 @@ void parseUDP() Serial.printf("New brightness value: %d\n", inputBuffer.substring(3).toInt()); #endif - modes[currentMode].brightness = constrain(inputBuffer.substring(3).toInt(), 0, 255); + modes[currentMode].brightness = constrain(inputBuffer.substring(3).toInt(), 1, 255); FastLED.setBrightness(modes[currentMode].brightness); settChanged = true; eepromTimer = millis(); diff --git a/firmware/GyverLamp_v1.4/time.ino b/firmware/GyverLamp_v1.4/time.ino index 30ffac8..adf80db 100644 --- a/firmware/GyverLamp_v1.4/time.ino +++ b/firmware/GyverLamp_v1.4/time.ino @@ -16,7 +16,7 @@ void timeTick() thisTime >= (alarm[thisDay].time - dawnOffsets[dawnMode]) && // позже начала thisTime < (alarm[thisDay].time + DAWN_TIMEOUT)) // раньше конца + минута { - if (!manualOff) + if (!manualOff) // будильник не был выключен вручную (из приложения или кнопкой) { // величина рассвета 0-255 int32_t dawnPosition = 255 * ((float)(thisTime - (alarm[thisDay].time - dawnOffsets[dawnMode])) / dawnOffsets[dawnMode]); @@ -26,6 +26,7 @@ void timeTick() map(dawnPosition, 0, 255, 10, DAWN_BRIGHT)); fill_solid(leds, NUM_LEDS, dawnColor); FastLED.setBrightness(255); + delay(1); FastLED.show(); dawnFlag = true; } @@ -36,7 +37,7 @@ void timeTick() { dawnFlag = false; manualOff = false; - FastLED.setBrightness(modes[currentMode].brightness); + changePower(); // выключение матрицы или установка яркости текущего эффекта в засисимости от того, была ли включена лампа до срабатывания будильника } } } diff --git a/testing/Testing_v1.4.xlsx b/testing/Testing_v1.4.xlsx index 6c99b37ccd6fa7ab304d116d7c4b5fead080d69d..865dab1ede142fe8d440ab40d4dbee6d534366f0 100644 GIT binary patch delta 5165 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 B4A%ev