diff --git a/firmware/GyverLamp_v1.4.1/GyverLamp_v1.4.1.ino b/firmware/GyverLamp_v1.4.1/GyverLamp_v1.4.1.ino new file mode 100644 index 00000000..9c6ea971 --- /dev/null +++ b/firmware/GyverLamp_v1.4.1/GyverLamp_v1.4.1.ino @@ -0,0 +1,273 @@ +/* + Скетч к проекту "Многофункциональный RGB светильник" + Страница проекта (схемы, описания): https://alexgyver.ru/GyverLamp/ + Исходники на GitHub: https://github.com/AlexGyver/GyverLamp/ + Нравится, как написан код? Поддержи автора! https://alexgyver.ru/support_alex/ + Автор: AlexGyver, AlexGyver Technologies, 2019 + https://AlexGyver.ru/ +*/ + +/* + Версия 1.4: + - Исправлен баг при смене режимов + - Исправлены тормоза в режиме точки доступа + + Дополнение: + Есть константа const char HOSTNAME[] = "GyverLamp"; + Значение которую можно вводить вместо адреса в программе + HOSTNAME это имя клиента сети +*/ + +// Ссылка для менеджера плат: +// http://arduino.esp8266.com/stable/package_esp8266com_index.json + +// ============= НАСТРОЙКИ ============= +// -------- ВРЕМЯ ------- +#define GMT 3 // смещение (москва 3) +#define NTP_ADDRESS "europe.pool.ntp.org" // сервер времени + +// -------- РАССВЕТ ------- +#define DAWN_BRIGHT 200 // макс. яркость рассвета +#define DAWN_TIMEOUT 1 // сколько рассвет светит после времени будильника, минут + +// ---------- МАТРИЦА --------- +#define BRIGHTNESS 40 // стандартная маскимальная яркость (0-255) +#define CURRENT_LIMIT 2000 // лимит по току в миллиамперах, автоматически управляет яркостью (пожалей свой блок питания!) 0 - выключить лимит + +#define WIDTH 16 // ширина матрицы +#define HEIGHT 16 // высота матрицы + +#define COLOR_ORDER GRB // порядок цветов на ленте. Если цвет отображается некорректно - меняйте. Начать можно с RGB + +#define MATRIX_TYPE 0 // тип матрицы: 0 - зигзаг, 1 - параллельная +#define CONNECTION_ANGLE 0 // угол подключения: 0 - левый нижний, 1 - левый верхний, 2 - правый верхний, 3 - правый нижний +#define STRIP_DIRECTION 0 // направление ленты из угла: 0 - вправо, 1 - вверх, 2 - влево, 3 - вниз +// при неправильной настройке матрицы вы получите предупреждение "Wrong matrix parameters! Set to default" +// шпаргалка по настройке матрицы здесь! https://alexgyver.ru/matrix_guide/ + +// --------- ESP -------- +const char HOSTNAME[] = "PaprikaLamp"; +#define ESP_MODE 1 +// 0 - точка доступа +// 1 - локальный +byte IP_AP[] = {192, 168, 4, 4}; // статический IP точки доступа (менять только последнюю цифру) + +//#define WIFIMGR_PORTAL_TIMEOUT 1 +//#define WIFIMGR_SET_MANUAL_IP +#ifdef WIFIMGR_SET_MANUAL_IP + char static_ip[16] = "192.168.1.56"; + char static_gw[16] = "192.168.1.1"; + char static_sn[16] = "255.255.255.0"; +#endif + +// ----- AP (точка доступа) ------- +#define AP_SSID "PaprikaLamp" +#define AP_PASS "12345678" +#define AP_PORT 8888 + +// -------- Менеджер WiFi --------- +#define AC_SSID "AutoAP" +#define AC_PASS "12345678" + +// ============= ДЛЯ РАЗРАБОТЧИКОВ ============= +#define LED_PIN 2 // пин ленты +#define BTN_PIN 4 +#define MODE_AMOUNT 18 + +#define NUM_LEDS WIDTH * HEIGHT +#define SEGMENTS 1 // диодов в одном "пикселе" (для создания матрицы из кусков ленты) +// ---------------- БИБЛИОТЕКИ ----------------- +#define FASTLED_INTERRUPT_RETRY_COUNT 0 +#define FASTLED_ALLOW_INTERRUPTS 0 +#define FASTLED_ESP8266_RAW_PIN_ORDER +#define NTP_INTERVAL 60 * 1000 // обновление (1 минута) + +#include "timerMinim.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// ------------------- ТИПЫ -------------------- +CRGB leds[NUM_LEDS]; +WiFiServer server(80); +WiFiUDP Udp; +WiFiUDP ntpUDP; +NTPClient timeClient(ntpUDP, NTP_ADDRESS, GMT * 3600, NTP_INTERVAL); +timerMinim timeTimer(3000); +GButton touch(BTN_PIN, LOW_PULL, NORM_OPEN); + +// ----------------- ПЕРЕМЕННЫЕ ------------------ +const char* autoConnectSSID = AC_SSID; +const char* autoConnectPass = AC_PASS; +const char AP_NameChar[] = AP_SSID; +const char WiFiPassword[] = AP_PASS; +unsigned int localPort = AP_PORT; +char packetBuffer[UDP_TX_PACKET_MAX_SIZE + 1]; //buffer to hold incoming packet +String inputBuffer; +static const byte maxDim = max(WIDTH, HEIGHT); +struct { + byte brightness = 50; + byte speed = 30; + byte scale = 40; +} modes[MODE_AMOUNT]; + +struct { + boolean state = false; + int time = 0; +} alarm[7]; + +byte dawnOffsets[] = {5, 10, 15, 20, 25, 30, 40, 50, 60}; +byte dawnMode; +boolean dawnFlag = false; +long thisTime; +boolean manualOff = false; + +int8_t currentMode = 0; +boolean loadingFlag = true; +boolean ONflag = true; +uint32_t eepromTimer; +boolean settChanged = false; +// Конфетти, Огонь, Радуга верт., Радуга гориз., Смена цвета, +// Безумие 3D, Облака 3D, Лава 3D, Плазма 3D, Радуга 3D, +// Павлин 3D, Зебра 3D, Лес 3D, Океан 3D, + +unsigned char matrixValue[8][16]; + +void setup() { + //ESP.wdtDisable(); + //ESP.wdtEnable(WDTO_8S); + wifi_station_set_hostname(const_cast(HOSTNAME)); + + // ЛЕНТА + FastLED.addLeds(leds, NUM_LEDS)/*.setCorrection( TypicalLEDStrip )*/; + FastLED.setBrightness(BRIGHTNESS); + if (CURRENT_LIMIT > 0) FastLED.setMaxPowerInVoltsAndMilliamps(5, CURRENT_LIMIT); + FastLED.clear(); + FastLED.show(); + + touch.setStepTimeout(100); + touch.setClickTimeout(500); + + Serial.begin(115200); + Serial.println(); + + // WI-FI + if (ESP_MODE == 0) { // режим точки доступа + WiFi.softAPConfig(IPAddress(IP_AP[0], IP_AP[1], IP_AP[2], IP_AP[3]), + IPAddress(192, 168, 4, 1), + IPAddress(255, 255, 255, 0)); + + WiFi.softAP(AP_NameChar, WiFiPassword); + IPAddress myIP = WiFi.softAPIP(); + Serial.print("Access point Mode"); + Serial.print("AP IP address: "); + Serial.println(myIP); + + server.begin(); + } else { // подключаемся к роутеру + Serial.print("WiFi manager"); + WiFiManager wifiManager; + + #ifdef WIFIMGR_SET_MANUAL_IP + IPAddress _ip, _gw, _sn; + _ip.fromString(static_ip); + _gw.fromString(static_gw); + _sn.fromString(static_sn); + wifiManager.setSTAStaticIPConfig(_ip, _gw, _sn); + #endif + #ifdef WIFIMGR_PORTAL_TIMEOUT + wifiManager.setConfigPortalTimeout(WIFIMGR_PORTAL_TIMEOUT); + #endif + wifiManager.autoConnect(HOSTNAME); + + wifiManager.setDebugOutput(false); + //wifiManager.resetSettings(); + + Serial.print("Connected! IP address: "); + Serial.println("local ip"); + Serial.println(WiFi.localIP()); + Serial.println(WiFi.gatewayIP()); + Serial.println(WiFi.subnetMask()); + server.begin(); + } + Serial.printf("UDP server on port %d\n", localPort); + Udp.begin(localPort); + + // EEPROM + EEPROM.begin(202); + delay(50); + if (EEPROM.read(198) != 20) { // первый запуск + EEPROM.write(198, 20); + EEPROM.commit(); + + for (byte i = 0; i < MODE_AMOUNT; i++) { + EEPROM.put(3 * i + 40, modes[i]); + EEPROM.commit(); + } + for (byte i = 0; i < 7; i++) { + EEPROM.write(5 * i, alarm[i].state); // рассвет + eeWriteInt(5 * i + 1, alarm[i].time); + EEPROM.commit(); + } + EEPROM.write(199, 0); // рассвет + EEPROM.write(200, 0); // режим + EEPROM.commit(); + } + for (byte i = 0; i < MODE_AMOUNT; i++) { + EEPROM.get(3 * i + 40, modes[i]); + } + for (byte i = 0; i < 7; i++) { + alarm[i].state = EEPROM.read(5 * i); + alarm[i].time = eeGetInt(5 * i + 1); + } + dawnMode = EEPROM.read(199); + currentMode = (int8_t)EEPROM.read(200); + + // отправляем настройки + sendCurrent(); + char reply[inputBuffer.length() + 1]; + inputBuffer.toCharArray(reply, inputBuffer.length() + 1); + Udp.beginPacket(Udp.remoteIP(), Udp.remotePort()); + Udp.write(reply); + Udp.endPacket(); + + timeClient.begin(); + memset(matrixValue, 0, sizeof(matrixValue)); + + randomSeed(micros()); +} + +void loop() { + parseUDP(); + effectsTick(); + eepromTick(); + timeTick(); + buttonTick(); + //ESP.wdtFeed(); // пнуть собаку + yield(); +} + +void eeWriteInt(int pos, int val) { + byte* p = (byte*) &val; + EEPROM.write(pos, *p); + EEPROM.write(pos + 1, *(p + 1)); + EEPROM.write(pos + 2, *(p + 2)); + EEPROM.write(pos + 3, *(p + 3)); + EEPROM.commit(); +} + +int eeGetInt(int pos) { + int val; + byte* p = (byte*) &val; + *p = EEPROM.read(pos); + *(p + 1) = EEPROM.read(pos + 1); + *(p + 2) = EEPROM.read(pos + 2); + *(p + 3) = EEPROM.read(pos + 3); + return val; +} diff --git a/firmware/GyverLamp_v1.4.1/button.ino b/firmware/GyverLamp_v1.4.1/button.ino new file mode 100644 index 00000000..8b30f598 --- /dev/null +++ b/firmware/GyverLamp_v1.4.1/button.ino @@ -0,0 +1,59 @@ +boolean brightDirection; + +void buttonTick() { + touch.tick(); + if (touch.isSingle()) { + if (dawnFlag) { + manualOff = true; + dawnFlag = false; + loadingFlag = true; + FastLED.setBrightness(modes[currentMode].brightness); + changePower(); + } else { + if (ONflag) { + ONflag = false; + changePower(); + } else { + ONflag = true; + changePower(); + } + } + } + + if (ONflag && touch.isDouble()) { + if (++currentMode >= MODE_AMOUNT) currentMode = 0; + FastLED.setBrightness(modes[currentMode].brightness); + loadingFlag = true; + settChanged = true; + eepromTimer = millis(); + FastLED.clear(); + delay(1); + } + if (ONflag && touch.isTriple()) { + if (--currentMode < 0) currentMode = 0; + FastLED.setBrightness(modes[currentMode].brightness); + loadingFlag = true; + settChanged = true; + eepromTimer = millis(); + FastLED.clear(); + delay(1); + } + + if (ONflag && touch.isHolded()) { + brightDirection = !brightDirection; + } + if (ONflag && touch.isStep()) { + if (brightDirection) { + if (modes[currentMode].brightness < 10) modes[currentMode].brightness += 1; + else if (modes[currentMode].brightness < 250) modes[currentMode].brightness += 5; + else modes[currentMode].brightness = 255; + } else { + if (modes[currentMode].brightness > 15) modes[currentMode].brightness -= 5; + else if (modes[currentMode].brightness > 1) modes[currentMode].brightness -= 1; + else modes[currentMode].brightness = 1; + } + FastLED.setBrightness(modes[currentMode].brightness); + settChanged = true; + eepromTimer = millis(); + } +} diff --git a/firmware/GyverLamp_v1.4.1/eeprom.ino b/firmware/GyverLamp_v1.4.1/eeprom.ino new file mode 100644 index 00000000..3c25dc11 --- /dev/null +++ b/firmware/GyverLamp_v1.4.1/eeprom.ino @@ -0,0 +1,25 @@ +void saveEEPROM() { + EEPROM.put(3 * currentMode + 40, modes[currentMode]); + EEPROM.commit(); +} + +void eepromTick() { + if (settChanged && millis() - eepromTimer > 30000) { + settChanged = false; + eepromTimer = millis(); + saveEEPROM(); + if (EEPROM.read(200) != currentMode) EEPROM.write(200, currentMode); + EEPROM.commit(); + } +} + +void saveAlarm(byte almNumber) { + EEPROM.write(5 * almNumber, alarm[almNumber].state); // рассвет + eeWriteInt(5 * almNumber + 1, alarm[almNumber].time); + EEPROM.commit(); +} + +void saveDawnMmode() { + EEPROM.write(199, dawnMode); // рассвет + EEPROM.commit(); +} diff --git a/firmware/GyverLamp_v1.4.1/effectTicker.ino b/firmware/GyverLamp_v1.4.1/effectTicker.ino new file mode 100644 index 00000000..583616b8 --- /dev/null +++ b/firmware/GyverLamp_v1.4.1/effectTicker.ino @@ -0,0 +1,72 @@ +uint32_t effTimer; + +void effectsTick() { + if (!dawnFlag) { + if (ONflag && millis() - effTimer >= ((currentMode < 5 || currentMode > 13) ? modes[currentMode].speed : 50) ) { + effTimer = millis(); + switch (currentMode) { + case 0: sparklesRoutine(); + break; + case 1: fireRoutine(); + break; + case 2: rainbowVertical(); + break; + case 3: rainbowHorizontal(); + break; + case 4: colorsRoutine(); + break; + case 5: madnessNoise(); + break; + case 6: cloudNoise(); + break; + case 7: lavaNoise(); + break; + case 8: plasmaNoise(); + break; + case 9: rainbowNoise(); + break; + case 10: rainbowStripeNoise(); + break; + case 11: zebraNoise(); + break; + case 12: forestNoise(); + break; + case 13: oceanNoise(); + break; + case 14: colorRoutine(); + break; + case 15: snowRoutine(); + break; + case 16: matrixRoutine(); + break; + case 17: lightersRoutine(); + break; + } + FastLED.show(); + } + } +} + +void changePower() { + if (ONflag) { + effectsTick(); + for (int i = 0; i < modes[currentMode].brightness; i += 8) { + FastLED.setBrightness(i); + delay(1); + FastLED.show(); + } + FastLED.setBrightness(modes[currentMode].brightness); + delay(2); + FastLED.show(); + } else { + effectsTick(); + for (int i = modes[currentMode].brightness; i > 8; i -= 8) { + FastLED.setBrightness(i); + delay(1); + FastLED.show(); + } + FastLED.clear(); + delay(2); + FastLED.show(); + } +} diff --git a/firmware/GyverLamp_v1.4.1/effects.ino b/firmware/GyverLamp_v1.4.1/effects.ino new file mode 100644 index 00000000..a536e6f0 --- /dev/null +++ b/firmware/GyverLamp_v1.4.1/effects.ino @@ -0,0 +1,333 @@ +// ================================= ЭФФЕКТЫ ==================================== + +// --------------------------------- конфетти ------------------------------------ +void sparklesRoutine() { + for (byte i = 0; i < modes[0].scale; i++) { + byte x = random(0, WIDTH); + byte y = random(0, HEIGHT); + if (getPixColorXY(x, y) == 0) + leds[getPixelNumber(x, y)] = CHSV(random(0, 255), 255, 255); + } + fader(70); +} + +// функция плавного угасания цвета для всех пикселей +void fader(byte step) { + for (byte i = 0; i < WIDTH; i++) { + for (byte j = 0; j < HEIGHT; j++) { + fadePixel(i, j, step); + } + } +} +void fadePixel(byte i, byte j, byte step) { // новый фейдер + int pixelNum = getPixelNumber(i, j); + if (getPixColor(pixelNum) == 0) return; + + if (leds[pixelNum].r >= 30 || + leds[pixelNum].g >= 30 || + leds[pixelNum].b >= 30) { + leds[pixelNum].fadeToBlackBy(step); + } else { + leds[pixelNum] = 0; + } +} + +// -------------------------------------- огонь --------------------------------------------- +// эффект "огонь" +#define SPARKLES 1 // вылетающие угольки вкл выкл +unsigned char line[WIDTH]; +int pcnt = 0; + +//these values are substracetd from the generated values to give a shape to the animation +const unsigned char valueMask[8][16] PROGMEM = { + {32 , 0 , 0 , 0 , 0 , 0 , 0 , 32 , 32 , 0 , 0 , 0 , 0 , 0 , 0 , 32 }, + {64 , 0 , 0 , 0 , 0 , 0 , 0 , 64 , 64 , 0 , 0 , 0 , 0 , 0 , 0 , 64 }, + {96 , 32 , 0 , 0 , 0 , 0 , 32 , 96 , 96 , 32 , 0 , 0 , 0 , 0 , 32 , 96 }, + {128, 64 , 32 , 0 , 0 , 32 , 64 , 128, 128, 64 , 32 , 0 , 0 , 32 , 64 , 128}, + {160, 96 , 64 , 32 , 32 , 64 , 96 , 160, 160, 96 , 64 , 32 , 32 , 64 , 96 , 160}, + {192, 128, 96 , 64 , 64 , 96 , 128, 192, 192, 128, 96 , 64 , 64 , 96 , 128, 192}, + {255, 160, 128, 96 , 96 , 128, 160, 255, 255, 160, 128, 96 , 96 , 128, 160, 255}, + {255, 192, 160, 128, 128, 160, 192, 255, 255, 192, 160, 128, 128, 160, 192, 255} +}; + +//these are the hues for the fire, +//should be between 0 (red) to about 25 (yellow) +const unsigned char hueMask[8][16] PROGMEM = { + {1 , 11, 19, 25, 25, 22, 11, 1 , 1 , 11, 19, 25, 25, 22, 11, 1 }, + {1 , 8 , 13, 19, 25, 19, 8 , 1 , 1 , 8 , 13, 19, 25, 19, 8 , 1 }, + {1 , 8 , 13, 16, 19, 16, 8 , 1 , 1 , 8 , 13, 16, 19, 16, 8 , 1 }, + {1 , 5 , 11, 13, 13, 13, 5 , 1 , 1 , 5 , 11, 13, 13, 13, 5 , 1 }, + {1 , 5 , 11, 11, 11, 11, 5 , 1 , 1 , 5 , 11, 11, 11, 11, 5 , 1 }, + {0 , 1 , 5 , 8 , 8 , 5 , 1 , 0 , 0 , 1 , 5 , 8 , 8 , 5 , 1 , 0 }, + {0 , 0 , 1 , 5 , 5 , 1 , 0 , 0 , 0 , 0 , 1 , 5 , 5 , 1 , 0 , 0 }, + {0 , 0 , 0 , 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 0 , 0 , 0 } +}; + +void fireRoutine() { + if (loadingFlag) { + loadingFlag = false; + //FastLED.clear(); + generateLine(); + } + if (pcnt >= 100) { + shiftUp(); + generateLine(); + pcnt = 0; + } + drawFrame(pcnt); + pcnt += 30; +} + +// Randomly generate the next line (matrix row) + +void generateLine() { + for (uint8_t x = 0; x < WIDTH; x++) { + line[x] = random(64, 255); + } +} + +void shiftUp() { + for (uint8_t y = HEIGHT - 1; y > 0; y--) { + for (uint8_t x = 0; x < WIDTH; x++) { + uint8_t newX = x; + if (x > 15) newX = x - 15; + if (y > 7) continue; + matrixValue[y][newX] = matrixValue[y - 1][newX]; + } + } + + for (uint8_t x = 0; x < WIDTH; x++) { + uint8_t newX = x; + if (x > 15) newX = x - 15; + matrixValue[0][newX] = line[newX]; + } +} + +// draw a frame, interpolating between 2 "key frames" +// @param pcnt percentage of interpolation + +void drawFrame(int pcnt) { + int nextv; + + //each row interpolates with the one before it + for (unsigned char y = HEIGHT - 1; y > 0; y--) { + for (unsigned char x = 0; x < WIDTH; x++) { + uint8_t newX = x; + if (x > 15) newX = x - 15; + if (y < 8) { + nextv = + (((100.0 - pcnt) * matrixValue[y][newX] + + pcnt * matrixValue[y - 1][newX]) / 100.0) + - pgm_read_byte(&(valueMask[y][newX])); + + CRGB color = CHSV( + modes[1].scale * 2.5 + pgm_read_byte(&(hueMask[y][newX])), // H + 255, // S + (uint8_t)max(0, nextv) // V + ); + + leds[getPixelNumber(x, y)] = color; + } else if (y == 8 && SPARKLES) { + if (random(0, 20) == 0 && getPixColorXY(x, y - 1) != 0) drawPixelXY(x, y, getPixColorXY(x, y - 1)); + else drawPixelXY(x, y, 0); + } else if (SPARKLES) { + + // старая версия для яркости + if (getPixColorXY(x, y - 1) > 0) + drawPixelXY(x, y, getPixColorXY(x, y - 1)); + else drawPixelXY(x, y, 0); + + } + } + } + + //first row interpolates with the "next" line + for (unsigned char x = 0; x < WIDTH; x++) { + uint8_t newX = x; + if (x > 15) newX = x - 15; + CRGB color = CHSV( + modes[1].scale * 2.5 + pgm_read_byte(&(hueMask[0][newX])), // H + 255, // S + (uint8_t)(((100.0 - pcnt) * matrixValue[0][newX] + pcnt * line[newX]) / 100.0) // V + ); + leds[getPixelNumber(newX, 0)] = color; + } +} + +byte hue; +// ---------------------------------------- радуга ------------------------------------------ +void rainbowVertical() { + hue += 2; + for (byte j = 0; j < HEIGHT; j++) { + CHSV thisColor = CHSV((byte)(hue + j * modes[2].scale), 255, 255); + for (byte i = 0; i < WIDTH; i++) + drawPixelXY(i, j, thisColor); + } +} +void rainbowHorizontal() { + hue += 2; + for (byte i = 0; i < WIDTH; i++) { + CHSV thisColor = CHSV((byte)(hue + i * modes[3].scale), 255, 255); + for (byte j = 0; j < HEIGHT; j++) + drawPixelXY(i, j, thisColor); //leds[getPixelNumber(i, j)] = thisColor; + } +} + +// ---------------------------------------- ЦВЕТА ------------------------------------------ +void colorsRoutine() { + hue += modes[4].scale; + for (int i = 0; i < NUM_LEDS; i++) { + leds[i] = CHSV(hue, 255, 255); + } +} + +// --------------------------------- ЦВЕТ ------------------------------------ +void colorRoutine() { + for (int i = 0; i < NUM_LEDS; i++) { + leds[i] = CHSV(modes[14].scale * 2.5, 255, 255); + } +} + +// ------------------------------ снегопад 2.0 -------------------------------- +void snowRoutine() { + // сдвигаем всё вниз + for (byte x = 0; x < WIDTH; x++) { + for (byte y = 0; y < HEIGHT - 1; y++) { + drawPixelXY(x, y, getPixColorXY(x, y + 1)); + } + } + + for (byte x = 0; x < WIDTH; x++) { + // заполняем случайно верхнюю строку + // а также не даём двум блокам по вертикали вместе быть + if (getPixColorXY(x, HEIGHT - 2) == 0 && (random(0, modes[15].scale) == 0)) + drawPixelXY(x, HEIGHT - 1, 0xE0FFFF - 0x101010 * random(0, 4)); + else + drawPixelXY(x, HEIGHT - 1, 0x000000); + } +} + +// ------------------------------ МАТРИЦА ------------------------------ +void matrixRoutine() { + for (byte 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)); + else if (thisColor < 0x002000) + drawPixelXY(x, HEIGHT - 1, 0); + else + drawPixelXY(x, HEIGHT - 1, thisColor - 0x002000); + } + + // сдвигаем всё вниз + for (byte x = 0; x < WIDTH; x++) { + for (byte y = 0; y < HEIGHT - 1; y++) { + drawPixelXY(x, y, getPixColorXY(x, y + 1)); + } + } +} + +// ----------------------------- СВЕТЛЯКИ ------------------------------ +#define LIGHTERS_AM 100 +int lightersPos[2][LIGHTERS_AM]; +int8_t lightersSpeed[2][LIGHTERS_AM]; +CHSV lightersColor[LIGHTERS_AM]; +byte loopCounter; + +int angle[LIGHTERS_AM]; +int speedV[LIGHTERS_AM]; +int8_t angleSpeed[LIGHTERS_AM]; + +void lightersRoutine() { + if (loadingFlag) { + loadingFlag = false; + randomSeed(millis()); + for (byte i = 0; i < LIGHTERS_AM; i++) { + lightersPos[0][i] = random(0, WIDTH * 10); + lightersPos[1][i] = random(0, HEIGHT * 10); + lightersSpeed[0][i] = random(-10, 10); + lightersSpeed[1][i] = random(-10, 10); + lightersColor[i] = CHSV(random(0, 255), 255, 255); + } + } + FastLED.clear(); + if (++loopCounter > 20) loopCounter = 0; + for (byte i = 0; i < modes[17].scale; i++) { + if (loopCounter == 0) { // меняем скорость каждые 255 отрисовок + lightersSpeed[0][i] += random(-3, 4); + lightersSpeed[1][i] += random(-3, 4); + lightersSpeed[0][i] = constrain(lightersSpeed[0][i], -20, 20); + lightersSpeed[1][i] = constrain(lightersSpeed[1][i], -20, 20); + } + + lightersPos[0][i] += lightersSpeed[0][i]; + lightersPos[1][i] += lightersSpeed[1][i]; + + if (lightersPos[0][i] < 0) lightersPos[0][i] = (WIDTH - 1) * 10; + if (lightersPos[0][i] >= WIDTH * 10) lightersPos[0][i] = 0; + + if (lightersPos[1][i] < 0) { + lightersPos[1][i] = 0; + lightersSpeed[1][i] = -lightersSpeed[1][i]; + } + if (lightersPos[1][i] >= (HEIGHT - 1) * 10) { + lightersPos[1][i] = (HEIGHT - 1) * 10; + lightersSpeed[1][i] = -lightersSpeed[1][i]; + } + drawPixelXY(lightersPos[0][i] / 10, lightersPos[1][i] / 10, lightersColor[i]); + } +} + +/* + void lightersRoutine() { + if (loadingFlag) { + loadingFlag = false; + randomSeed(millis()); + for (byte i = 0; i < LIGHTERS_AM; i++) { + lightersPos[0][i] = random(0, WIDTH * 10); + lightersPos[1][i] = random(0, HEIGHT * 10); + + lightersColor[i] = CHSV(random(0, 255), 255, 255); + + speedV[i] = random(5, 10); + angle[i] = random(0, 360); + angleSpeed[i] = random(-10, 10); + } + } + FastLED.clear(); + if (++loopCounter > 20) loopCounter = 0; + + for (byte i = 0; i < modes[17].scale; i++) { + if (loopCounter == 0) { // меняем скорость каждые 255 отрисовок + angleSpeed[i] += random(-3, 4); + angleSpeed[i] = constrain(angleSpeed[i], -15, 15); + } + + lightersPos[0][i] += speedV[i] * cos(radians(angle[i])); + lightersPos[1][i] += speedV[i] * sin(radians(angle[i])); + + if (lightersPos[0][i] < 0) lightersPos[0][i] = (WIDTH - 1) * 10; + if (lightersPos[0][i] >= WIDTH * 10) lightersPos[0][i] = 0; + + if (lightersPos[1][i] < 0) { + lightersPos[1][i] = 0; + angle[i] = 360 - angle[i]; + } else { + angle[i] += angleSpeed[i]; + } + + if (lightersPos[1][i] >= (HEIGHT - 1) * 10) { + lightersPos[1][i] = (HEIGHT - 1) * 10; + angle[i] = 360 - angle[i]; + } else { + angle[i] += angleSpeed[i]; + } + + if (angle[i] > 360) angle[i] = 360 - angle[i]; + if (angle[i] < 0) angle[i] = 360 + angle[i]; + + drawPixelXY(lightersPos[0][i] / 10, lightersPos[1][i] / 10, lightersColor[i]); + } + } +*/ diff --git a/firmware/GyverLamp_v1.4.1/noiseEffects.ino b/firmware/GyverLamp_v1.4.1/noiseEffects.ino new file mode 100644 index 00000000..06c14cbc --- /dev/null +++ b/firmware/GyverLamp_v1.4.1/noiseEffects.ino @@ -0,0 +1,200 @@ +// ******************* НАСТРОЙКИ ***************** +// "масштаб" эффектов. Чем меньше, тем крупнее! +#define MADNESS_SCALE 100 +#define CLOUD_SCALE 30 +#define LAVA_SCALE 50 +#define PLASMA_SCALE 30 +#define RAINBOW_SCALE 30 +#define RAINBOW_S_SCALE 20 +#define ZEBRA_SCALE 30 +#define FOREST_SCALE 120 +#define OCEAN_SCALE 90 + +// ***************** ДЛЯ РАЗРАБОТЧИКОВ ****************** + +// The 16 bit version of our coordinates +static uint16_t x; +static uint16_t y; +static uint16_t z; + +uint16_t speed = 20; // speed is set dynamically once we've started up +uint16_t scale = 30; // scale is set dynamically once we've started up + +// This is the array that we keep our computed noise values in +#define MAX_DIMENSION (max(WIDTH, HEIGHT)) +#if (WIDTH > HEIGHT) +uint8_t noise[WIDTH][WIDTH]; +#else +uint8_t noise[HEIGHT][HEIGHT]; +#endif + +CRGBPalette16 currentPalette( PartyColors_p ); +uint8_t colorLoop = 1; +uint8_t ihue = 0; + +void madnessNoise() { + if (loadingFlag) { + loadingFlag = false; + scale = modes[5].scale; + speed = modes[5].speed; + } + fillnoise8(); + for (int i = 0; i < WIDTH; i++) { + for (int j = 0; j < HEIGHT; j++) { + CRGB thisColor = CHSV(noise[j][i], 255, noise[i][j]); + drawPixelXY(i, j, thisColor); //leds[getPixelNumber(i, j)] = CHSV(noise[j][i], 255, noise[i][j]); + } + } + ihue += 1; +} +void rainbowNoise() { + if (loadingFlag) { + loadingFlag = false; + currentPalette = RainbowColors_p; + scale = modes[9].scale; + speed = modes[9].speed; + colorLoop = 1; + } + fillNoiseLED(); +} +void rainbowStripeNoise() { + if (loadingFlag) { + loadingFlag = false; + currentPalette = RainbowStripeColors_p; + scale = modes[10].scale; + speed = modes[10].speed; + colorLoop = 1; + } + fillNoiseLED(); +} +void zebraNoise() { + if (loadingFlag) { + loadingFlag = false; + // 'black out' all 16 palette entries... + fill_solid( currentPalette, 16, CRGB::Black); + // and set every fourth one to white. + currentPalette[0] = CRGB::White; + currentPalette[4] = CRGB::White; + currentPalette[8] = CRGB::White; + currentPalette[12] = CRGB::White; + scale = modes[11].scale; + speed = modes[11].speed; + colorLoop = 1; + } + fillNoiseLED(); +} +void forestNoise() { + if (loadingFlag) { + loadingFlag = false; + currentPalette = ForestColors_p; + scale = modes[12].scale; + speed = modes[12].speed; + colorLoop = 0; + } + fillNoiseLED(); +} +void oceanNoise() { + if (loadingFlag) { + loadingFlag = false; + currentPalette = OceanColors_p; + scale = modes[13].scale; + speed = modes[13].speed; + colorLoop = 0; + } + + fillNoiseLED(); +} +void plasmaNoise() { + if (loadingFlag) { + loadingFlag = false; + currentPalette = PartyColors_p; + scale = modes[8].scale; + speed = modes[8].speed; + colorLoop = 1; + } + fillNoiseLED(); +} +void cloudNoise() { + if (loadingFlag) { + loadingFlag = false; + currentPalette = CloudColors_p; + scale = modes[6].scale; + speed = modes[6].speed; + colorLoop = 0; + } + fillNoiseLED(); +} +void lavaNoise() { + if (loadingFlag) { + loadingFlag = false; + currentPalette = LavaColors_p; + scale = modes[7].scale; + speed = modes[7].speed; + colorLoop = 0; + } + fillNoiseLED(); +} + +// ******************* СЛУЖЕБНЫЕ ******************* +void fillNoiseLED() { + uint8_t dataSmoothing = 0; + if ( speed < 50) { + dataSmoothing = 200 - (speed * 4); + } + for (int i = 0; i < MAX_DIMENSION; i++) { + int ioffset = scale * i; + for (int j = 0; j < MAX_DIMENSION; j++) { + int joffset = scale * j; + + uint8_t data = inoise8(x + ioffset, y + joffset, z); + + data = qsub8(data, 16); + data = qadd8(data, scale8(data, 39)); + + if ( dataSmoothing ) { + uint8_t olddata = noise[i][j]; + uint8_t newdata = scale8( olddata, dataSmoothing) + scale8( data, 256 - dataSmoothing); + data = newdata; + } + + noise[i][j] = data; + } + } + z += speed; + + // apply slow drift to X and Y, just for visual variation. + x += speed / 8; + y -= speed / 16; + + for (int i = 0; i < WIDTH; i++) { + for (int j = 0; j < HEIGHT; j++) { + uint8_t index = noise[j][i]; + uint8_t bri = noise[i][j]; + // if this palette is a 'loop', add a slowly-changing base value + if ( colorLoop) { + index += ihue; + } + // brighten up, as the color palette itself often contains the + // light/dark dynamic range desired + if ( bri > 127 ) { + bri = 255; + } else { + bri = dim8_raw( bri * 2); + } + CRGB color = ColorFromPalette( currentPalette, index, bri); + drawPixelXY(i, j, color); //leds[getPixelNumber(i, j)] = color; + } + } + ihue += 1; +} + +void fillnoise8() { + for (int i = 0; i < MAX_DIMENSION; i++) { + int ioffset = scale * i; + for (int j = 0; j < MAX_DIMENSION; j++) { + int joffset = scale * j; + noise[i][j] = inoise8(x + ioffset, y + joffset, z); + } + } + z += speed; +} diff --git a/firmware/GyverLamp_v1.4.1/parsing.ino b/firmware/GyverLamp_v1.4.1/parsing.ino new file mode 100644 index 00000000..dc955bb3 --- /dev/null +++ b/firmware/GyverLamp_v1.4.1/parsing.ino @@ -0,0 +1,102 @@ +void parseUDP() { + int packetSize = Udp.parsePacket(); + if (packetSize) { + int n = Udp.read(packetBuffer, UDP_TX_PACKET_MAX_SIZE); + packetBuffer[n] = 0; + inputBuffer = packetBuffer; + + if (inputBuffer.startsWith("DEB")) { + inputBuffer = "OK " + timeClient.getFormattedTime(); + } else if (inputBuffer.startsWith("GET")) { + sendCurrent(); + } else if (inputBuffer.startsWith("EFF")) { + saveEEPROM(); + currentMode = (byte)inputBuffer.substring(3).toInt(); + loadingFlag = true; + FastLED.clear(); + delay(1); + sendCurrent(); + FastLED.setBrightness(modes[currentMode].brightness); + } else if (inputBuffer.startsWith("BRI")) { + modes[currentMode].brightness = inputBuffer.substring(3).toInt(); + FastLED.setBrightness(modes[currentMode].brightness); + settChanged = true; + eepromTimer = millis(); + } else if (inputBuffer.startsWith("SPD")) { + modes[currentMode].speed = inputBuffer.substring(3).toInt(); + loadingFlag = true; + settChanged = true; + eepromTimer = millis(); + } else if (inputBuffer.startsWith("SCA")) { + modes[currentMode].scale = inputBuffer.substring(3).toInt(); + loadingFlag = true; + settChanged = true; + eepromTimer = millis(); + } else if (inputBuffer.startsWith("P_ON")) { + ONflag = true; + changePower(); + sendCurrent(); + } else if (inputBuffer.startsWith("P_OFF")) { + ONflag = false; + changePower(); + sendCurrent(); + } else if (inputBuffer.startsWith("ALM_SET")) { + byte alarmNum = (char)inputBuffer[7] - '0'; + alarmNum -= 1; + if (inputBuffer.indexOf("ON") != -1) { + alarm[alarmNum].state = true; + inputBuffer = "alm #" + String(alarmNum + 1) + " ON"; + } else if (inputBuffer.indexOf("OFF") != -1) { + alarm[alarmNum].state = false; + inputBuffer = "alm #" + String(alarmNum + 1) + " OFF"; + } else { + int almTime = inputBuffer.substring(8).toInt(); + alarm[alarmNum].time = almTime; + byte hour = floor(almTime / 60); + byte minute = almTime - hour * 60; + inputBuffer = "alm #" + String(alarmNum + 1) + + " " + String(hour) + + ":" + String(minute); + } + saveAlarm(alarmNum); + } else if (inputBuffer.startsWith("ALM_GET")) { + sendAlarms(); + } else if (inputBuffer.startsWith("DAWN")) { + dawnMode = inputBuffer.substring(4).toInt() - 1; + saveDawnMmode(); + } + + char reply[inputBuffer.length() + 1]; + inputBuffer.toCharArray(reply, inputBuffer.length() + 1); + Udp.beginPacket(Udp.remoteIP(), Udp.remotePort()); + Udp.write(reply); + Udp.endPacket(); + } +} + +void sendCurrent() { + inputBuffer = "CURR"; + inputBuffer += " "; + inputBuffer += String(currentMode); + inputBuffer += " "; + inputBuffer += String(modes[currentMode].brightness); + inputBuffer += " "; + inputBuffer += String(modes[currentMode].speed); + inputBuffer += " "; + inputBuffer += String(modes[currentMode].scale); + inputBuffer += " "; + inputBuffer += String(ONflag); +} + +void sendAlarms() { + inputBuffer = "ALMS "; + for (byte i = 0; i < 7; i++) { + inputBuffer += String(alarm[i].state); + inputBuffer += " "; + } + for (byte i = 0; i < 7; i++) { + inputBuffer += String(alarm[i].time); + inputBuffer += " "; + } + inputBuffer += (dawnMode + 1); +} diff --git a/firmware/GyverLamp_v1.4.1/time.ino b/firmware/GyverLamp_v1.4.1/time.ino new file mode 100644 index 00000000..a746d55c --- /dev/null +++ b/firmware/GyverLamp_v1.4.1/time.ino @@ -0,0 +1,36 @@ +void timeTick() { + if (ESP_MODE == 1) { + if (timeTimer.isReady()) { + timeClient.update(); + byte thisDay = timeClient.getDay(); + if (thisDay == 0) thisDay = 7; // воскресенье это 0 + thisDay--; + thisTime = timeClient.getHours() * 60 + timeClient.getMinutes(); + + // проверка рассвета + if (alarm[thisDay].state && // день будильника + thisTime >= (alarm[thisDay].time - dawnOffsets[dawnMode]) && // позже начала + thisTime < (alarm[thisDay].time + DAWN_TIMEOUT) ) { // раньше конца + минута + if (!manualOff) { + // величина рассвета 0-255 + int dawnPosition = 255 * ((float)(thisTime - (alarm[thisDay].time - dawnOffsets[dawnMode])) / dawnOffsets[dawnMode]); + dawnPosition = constrain(dawnPosition, 0, 255); + CHSV dawnColor = CHSV(map(dawnPosition, 0, 255, 10, 35), + map(dawnPosition, 0, 255, 255, 170), + map(dawnPosition, 0, 255, 10, DAWN_BRIGHT)); + fill_solid(leds, NUM_LEDS, dawnColor); + FastLED.setBrightness(255); + FastLED.show(); + dawnFlag = true; + } + } else { + if (dawnFlag) { + dawnFlag = false; + manualOff = false; + FastLED.setBrightness(modes[currentMode].brightness); + } + } + + } + } +} diff --git a/firmware/GyverLamp_v1.4.1/timerMinim.h b/firmware/GyverLamp_v1.4.1/timerMinim.h new file mode 100644 index 00000000..643a7f6a --- /dev/null +++ b/firmware/GyverLamp_v1.4.1/timerMinim.h @@ -0,0 +1,36 @@ +// мини-класс таймера, версия 1.0 + +class timerMinim +{ + public: + timerMinim(uint32_t interval); // объявление таймера с указанием интервала + void setInterval(uint32_t interval); // установка интервала работы таймера + boolean isReady(); // возвращает true, когда пришло время. Сбрасывается в false сам (AUTO) или вручную (MANUAL) + void reset(); // ручной сброс таймера на установленный интервал + + private: + uint32_t _timer = 0; + uint32_t _interval = 0; +}; + +timerMinim::timerMinim(uint32_t interval) { + _interval = interval; + _timer = millis(); +} + +void timerMinim::setInterval(uint32_t interval) { + _interval = interval; +} + +boolean timerMinim::isReady() { + if ((long)millis() - _timer >= _interval) { + _timer = millis(); + return true; + } else { + return false; + } +} + +void timerMinim::reset() { + _timer = millis(); +} diff --git a/firmware/GyverLamp_v1.4.1/utility.ino b/firmware/GyverLamp_v1.4.1/utility.ino new file mode 100644 index 00000000..6110de57 --- /dev/null +++ b/firmware/GyverLamp_v1.4.1/utility.ino @@ -0,0 +1,87 @@ +// служебные функции + +// залить все +void fillAll(CRGB color) { + for (int i = 0; i < NUM_LEDS; i++) { + leds[i] = color; + } +} + +// функция отрисовки точки по координатам X Y +void drawPixelXY(int8_t x, int8_t y, CRGB color) { + if (x < 0 || x > WIDTH - 1 || y < 0 || y > HEIGHT - 1) return; + int thisPixel = getPixelNumber(x, y) * SEGMENTS; + for (byte i = 0; i < SEGMENTS; i++) { + leds[thisPixel + i] = color; + } +} + +// функция получения цвета пикселя по его номеру +uint32_t getPixColor(int thisSegm) { + int thisPixel = thisSegm * SEGMENTS; + if (thisPixel < 0 || thisPixel > NUM_LEDS - 1) return 0; + return (((uint32_t)leds[thisPixel].r << 16) | ((long)leds[thisPixel].g << 8 ) | (long)leds[thisPixel].b); +} + +// функция получения цвета пикселя в матрице по его координатам +uint32_t getPixColorXY(int8_t x, int8_t y) { + return getPixColor(getPixelNumber(x, y)); +} + +// **************** НАСТРОЙКА МАТРИЦЫ **************** +#if (CONNECTION_ANGLE == 0 && STRIP_DIRECTION == 0) +#define _WIDTH WIDTH +#define THIS_X x +#define THIS_Y y + +#elif (CONNECTION_ANGLE == 0 && STRIP_DIRECTION == 1) +#define _WIDTH HEIGHT +#define THIS_X y +#define THIS_Y x + +#elif (CONNECTION_ANGLE == 1 && STRIP_DIRECTION == 0) +#define _WIDTH WIDTH +#define THIS_X x +#define THIS_Y (HEIGHT - y - 1) + +#elif (CONNECTION_ANGLE == 1 && STRIP_DIRECTION == 3) +#define _WIDTH HEIGHT +#define THIS_X (HEIGHT - y - 1) +#define THIS_Y x + +#elif (CONNECTION_ANGLE == 2 && STRIP_DIRECTION == 2) +#define _WIDTH WIDTH +#define THIS_X (WIDTH - x - 1) +#define THIS_Y (HEIGHT - y - 1) + +#elif (CONNECTION_ANGLE == 2 && STRIP_DIRECTION == 3) +#define _WIDTH HEIGHT +#define THIS_X (HEIGHT - y - 1) +#define THIS_Y (WIDTH - x - 1) + +#elif (CONNECTION_ANGLE == 3 && STRIP_DIRECTION == 2) +#define _WIDTH WIDTH +#define THIS_X (WIDTH - x - 1) +#define THIS_Y y + +#elif (CONNECTION_ANGLE == 3 && STRIP_DIRECTION == 1) +#define _WIDTH HEIGHT +#define THIS_X y +#define THIS_Y (WIDTH - x - 1) + +#else +#define _WIDTH WIDTH +#define THIS_X x +#define THIS_Y y +#pragma message "Wrong matrix parameters! Set to default" + +#endif + +// получить номер пикселя в ленте по координатам +uint16_t getPixelNumber(int8_t x, int8_t y) { + if ((THIS_Y % 2 == 0) || MATRIX_TYPE) { // если чётная строка + return (THIS_Y * _WIDTH + THIS_X); + } else { // если нечётная строка + return (THIS_Y * _WIDTH + _WIDTH - THIS_X - 1); + } +}