diff --git a/.cursor/rules/build.mdc b/.cursor/rules/build.mdc new file mode 100644 index 0000000..3c3811b --- /dev/null +++ b/.cursor/rules/build.mdc @@ -0,0 +1,52 @@ +--- +description: Правила компиляции и загрузки прошивки (PlatformIO) +alwaysApply: true +--- + +# Правила компиляции проекта LedMatrix + +Проект собирается через **PlatformIO**. Конфигурация — `platformio.ini`. + +## Окружения (environments) + +| Окружение | Плата | Платформа | +|------------|-----------------|-------------| +| `esp12e` | ESP8266 ESP-12E | espressif8266 | +| `esp32dev` | ESP32 Dev Module | espressif32 | + +## Команды + +- **Сборка всего:** + `pio run` + или (если `pio` не в PATH): + `& "$env:USERPROFILE\.platformio\penv\Scripts\platformio.exe" run` + +- **Сборка одного окружения:** + `pio run -e esp12e` + `pio run -e esp32dev` + +- **Загрузка прошивки:** + `pio run -e esp32dev -t upload` + Порт задаётся в `platformio.ini` (`upload_port`) или через флаг: + `pio run -e esp32dev -t upload --upload-port COM4` + +- **Монитор порта:** + `pio device monitor -e esp32dev` + (скорость в `platformio.ini`: `monitor_speed`) + +## Где лежат прошивки + +- ESP8266: `.pio/build/esp12e/firmware.bin` +- ESP32: `.pio/build/esp32dev/firmware.bin` + +## Зависимости + +- Общие библиотеки заданы в `[common]` в `platformio.ini`. +- Для ESP32 дополнительно: EspSoftwareSerial. LittleFS встроен в ядро (LittleFS.h). +- При ошибке `ModuleNotFoundError: intelhex` — установить в окружение PlatformIO: + `& "$env:USERPROFILE\.platformio\penv\Scripts\pip.exe" install intelhex` + +## PowerShell + +В PowerShell не использовать `&&`. Разделитель команд — `;`, например: +`cd d:\project\LedMatrix; pio run` diff --git a/.cursor/rules/effects.mdc b/.cursor/rules/effects.mdc new file mode 100644 index 0000000..f280fda --- /dev/null +++ b/.cursor/rules/effects.mdc @@ -0,0 +1,115 @@ +--- +description: Правила создания и подключения новых режимов (эффектов) в LedMatrix +globs: src/effects/**/*.h, src/core/effect/**/* +--- + +# Создание нового режима (эффекта) + +## 1. Класс эффекта + +- Базовый класс: `Effect` (`src/effects/effect.h`). +- Подключать: `#include "effects/effect.h"`. + +### Методы + +| Метод | Обязательный | Описание | +|-------|----------------|----------| +| `on_update()` | **Да** | Вызывается каждый кадр. Вся логика и отрисовка эффекта. | +| `on_init()` | Нет | Один раз при включении режима. Задать `set_fps()`, начальное состояние. | +| `on_clear()` | Нет | Один раз при смене режима, до `on_init()`. По умолчанию — `FastLED.clear()`. | +| `on_render()` | Нет | Отдельный шаг отрисовки (если нужен). | +| `is_end()` | Нет | По умолчанию `true`. Вернуть `false`, чтобы автомод не переключал режим (держать не более 2+ секунд). | + +### FPS и очистка + +- В `on_init()` вызвать `set_fps(20..60)` — лимит кадров в секунду. +- Скорость анимации лучше задавать своими счётчиками/шагами внутри `on_update()`, а не только FPS. + +## 2. Размещение файла + +- Путь: **`src/effects/effects_impl/<имя>.h`** (например `spiral.h`, `pulse_rings.h`). +- Имя класса — в PascalCase, совпадает с именем файла без расширения (например класс `Spiral` в `spiral.h`). +- Эффект можно сделать только в заголовке (header-only) или добавить `.cpp` по необходимости. + +## 3. Регистрация в фабрике + +Файлы: **`src/core/effect/EffectFactory.h`** (enum), **`src/core/effect/EffectFactory.cpp`** (switch). + +1. **`EffectFactory.h`** — в `enum class EffectId` перед `Count` добавить новый идентификатор со следующим свободным числом (сейчас последний режим — `PulseRings = 31`, следующий будет `32`): + ```cpp + NewEffect = 32, + Count + ``` + Значение `Count` пересчитается автоматически (сентинел «после последнего id»). + +2. **`EffectFactory.cpp`** — **include** класса эффекта (в блоке с остальными): + ```cpp + #include "effects/effects_impl/имя_файла.h" + ``` + +3. В **`switch`** в `getEffectInfo()` добавить строку **до** `default`: + ```cpp + EFFECT_CASE(NewEffect); + ``` + Макрос ожидает, что **имя класса совпадает с именем варианта enum** (как у существующих `EFFECT_CASE(SlowRandom);` → класс `SlowRandom`). + +## 4. Добавление в список по умолчанию + +Файл: **`src/configs/DefaultEffectList.h`**. + +В массив **`DefaultEffects::effectIds`** добавить элемент `EffectId::…` в нужном порядке (порядок в списке = порядок переключения при старте и после сброса): + +```cpp +EffectId::NewEffect, +``` + +Размер массива задаётся списком инициализаторов — **вручную считать элементы не нужно**. Убрать режим из стартового списка — удалить или закомментировать соответствующую строку. + +Список используется в **`StaticEffectStorage`** и при построении дефолтов в **`FileEffectStorage`** (без дублирования через временное хранилище). + +## 5. API матрицы и цвета + +- **Матрица** — глобальный объект `LedMatrix` (`libs/led_matrix.h`): + - `LedMatrix.width()`, `LedMatrix.height()` — размеры. + - `LedMatrix.at(x, y)` — ссылка на пиксель `CRGB&`. + - `LedMatrix.clear()` — очистка. + - `LedMatrix.fader(step)` — затемнение всех пикселей (для шлейфа/следов). + - `LedMatrix.rangeX()`, `LedMatrix.rangeY()` — диапазоны для циклов по x/y. + - `LedMatrix.drawLine()`, `LedMatrix.drawRect()` — примитивы при необходимости. + +- **Размеры матрицы (обязательно учитывать):** + - Матрица может быть **разных размеров** и в том числе **прямоугольной** (ширина ≠ высота). + - Не закладываться на квадрат или фиксированные значения. Все расчёты позиций, радиусов и масштабов делать **в рантайме** по `LedMatrix.width()` и `LedMatrix.height()`. + - Использовать `std::min(LedMatrix.width(), LedMatrix.height())` или отдельно ширину/высоту, когда форма эффекта зависит от размера (круги, надписи, фигуры). + +- **Константы** из `configs/matrix.h`: `LEDS_WIDTH`, `LEDS_HEIGHT` — только если действительно нужны на этапе компиляции; для переносимости между разными матрицами предпочтительны `width()`/`height()`. + +- **Цвета** (FastLED): + - `CRGB(r, g, b)`, `CRGB(0xRRGGBB)`. + - `CHSV(hue, saturation, value)` — hue 0–255, sat 0–255, value 0–255. + - `qadd8(a, b)` — сложение с насыщением (чтобы не уходить в белый при наложении). + +## 6. Минимальный пример + +(Используются `LedMatrix.width()` и `LedMatrix.height()` — эффект корректен при любых размерах и форме матрицы.) + +```cpp +// src/effects/effects_impl/my_effect.h +#pragma once +#include "effects/effect.h" + +class MyEffect : public Effect { +public: + void on_init() override { + set_fps(30); + } + void on_update() override { + LedMatrix.fader(40); + index_t x = random(LedMatrix.width()); + index_t y = random(LedMatrix.height()); + LedMatrix.at(x, y) = CHSV(random8(), 255, 255); + } +}; +``` + +После добавления класса не забыть: **`EffectFactory.h`** (новый `EffectId`), **`EffectFactory.cpp`** (include + `EFFECT_CASE`), **`DefaultEffectList.h`** (при необходимости — `EffectId::…` в `DefaultEffects::effectIds`). diff --git a/README.md b/README.md index cf85c25..b79b22e 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,198 @@ # LedMatrix -LedMatrix based on ws2812b/ws2811 leds and esp8266 microchip + +Прошивка для светодиодной матрицы на базе **WS2812B/WS2811** и микроконтроллеров **ESP8266** или **ESP32**. Поддержка множества визуальных режимов, управление кнопкой, ИК-пультом, автомод и сохранение настроек. + +## Возможности + +- **31 визуальный режим** в прошивке (огонь, дождь, конфетти, змейка, спираль, кольца и др.; id 1–31, 0 — ошибка) +- Переключение режимов кнопкой или ИК-пультом +- Автоматическая смена режима по таймеру (опционально) +- Сохранение текущего режима и списка в файл (LittleFS) или статический список +- Ограничение потребления по току (опционально) +- Поддержка разных типов матриц (зигзаг/параллельная), угла подключения и направления ленты + +## Поддерживаемые платы + + +| Окружение | Плата | Платформа | +| ---------- | ---------------- | ------------- | +| `esp12e` | ESP8266 ESP-12E | espressif8266 | +| `esp32dev` | ESP32 Dev Module | espressif32 | + + +## Сборка и загрузка + +Проект собирается через **PlatformIO**. + +```bash +# Сборка для ESP32 +pio run -e esp32dev + +# Сборка для ESP8266 +pio run -e esp12e + +# Загрузка прошивки (ESP32) +pio run -e esp32dev -t upload + +# Загрузка файловой системы (настройки) +pio run -e esp32dev -t uploadfs +``` + +Прошивки: + `.pio/build/esp32dev/firmware.bin` и `.pio/build/esp12e/firmware.bin`. + +Подробнее — в [.cursor/rules/build.mdc](.cursor/rules/build.mdc). + +## Настройка + +Основные параметры задаются в `**src/configs/matrix.h**`: + +- **LEDS_WIDTH**, **LEDS_HEIGHT** — размер матрицы (по умолчанию 16×16) +- **LEDS_PIN** — пин данных ленты (ESP32: 22, ESP8266: D2) +- **LEDS_BRIGHTNRSS** — яркость 0–255 +- **LEDS_COLOR_ORDER** — порядок цветов (GRB/RGB и т.д.) +- **LEDS_MATRIX_TYPE**, **LEDS_CONNECTION_ANGLE**, **LEDS_STRIP_DIRECTION** — тип матрицы и подключение +- **BTN_PIN** — пин кнопки +- **BTN_ENABLE**, **IR_ENABLE**, **RELAY_ENABLE** — включение кнопки, ИК, реле +- **SAVE_TO_EEPROM** — `true` для сохранения в файл (LittleFS), `false` — только статический список режимов +- **AUTOMOD_INTERVAL**, **AUTOMOD_DEF_STATE** — интервал и состояние автомода + +## Режимы (эффекты) + + +| № | Режим | Описание (кратко) | +| --- | ---------------------- | -------------------------------- | +| 1 | SlowRandom | Медленная смена случайных цветов | +| 2 | SimpleRainbow | Радужная заливка | +| 3 | Dribs | Капли | +| 4 | Rain | Дождь | +| 5 | AllRandom | Случайные пиксели | +| 6 | Snow | Снег | +| 7 | Fire | Огонь | +| 8 | TheMatrix | Матрица (зелёный код) | +| 9 | SimpleBalls | Шарики со следами | +| 10 | Confetti | Конфетти | +| 11 | Starfall | Падающие звёзды | +| 12 | DynamicSquare | Динамический квадрат | +| 13 | RandomRain | Случайный дождь | +| 14 | RainbowRain | Радужный дождь | +| 15 | Points | Движущиеся точки | +| 16 | RainbowPoint | Радужная точка | +| 17 | RainbowStaticPoint | Статичная радужная точка | +| 18 | TextMode | Текст | +| 19 | Mouse | Указатель мыши | +| 20 | Pacman | Пакман | +| 21 | CircularPoint | Круговая точка | +| 22 | ZigZag | Зигзаг | +| 23 | HorizontalRainbowPoint | Горизонтальная радужная точка | +| 24 | Ny2020 | Новогодний | +| 25 | DribsAllSide | Капли со всех сторон | +| 26 | Snake | Змейка | +| 27 | RadialFire | Радиальный огонь | +| 28 | RadialPattern | Радиальный узор | +| 29 | CrazyBees | Пчёлы | +| 30 | Spiral | Спираль | +| 31 | PulseRings | Импульсные кольца | + + +## Сохранение эффектов в память + +### Режимы работы + +В **`src/configs/matrix.h`** задаётся флаг **`SAVE_TO_EEPROM`**: + +| Значение | Хранилище | Поведение | +|----------|-----------|-----------| +| `true` | **FileEffectStorage** | Список режимов и текущий индекс хранятся в файле на **LittleFS**. После перезагрузки восстанавливаются последний выбранный режим и порядок режимов. | +| `false` | **StaticEffectStorage** | Список фиксированный (зашит в коде), текущий режим **не сохраняется** — после перезагрузки всегда стартует с последнего в списке. | + +При `SAVE_TO_EEPROM = true` используется файл с именем из **`SAVE_TO_EEPROM_FILE`** (по умолчанию `"mods.txt"`). Файл создаётся в корне LittleFS (путь `/mods.txt`). + +### Что сохраняется (при сохранении в файл) + +- **Текущий индекс** — номер режима в списке (0, 1, 2, …), который сейчас показывается. +- **Список эффектов** — последовательность записей вида «id эффекта + служебный savedIndex». Порядок записей = порядок переключения кнопкой «вперёд/назад». + +Запись в файл происходит при смене режима (текущий индекс) и при изменении списка (добавление/удаление эффектов через API хранилища). Формат бинарный (см. `EffectInfo::serialize` в `src/core/effect/EffectInfo.h`). + +### Первый запуск с сохранением в файл + +1. Соберите прошивку с `SAVE_TO_EEPROM = true`. +2. Один раз загрузите файловую систему (раздел LittleFS): + `pio run -e esp32dev -t uploadfs` + (для ESP8266 — `pio run -e esp12e -t uploadfs`). +3. При первом запуске файла `mods.txt` нет или он пустой — тогда автоматически создаётся **список по умолчанию** из `DefaultEffects::effectIds` в `src/configs/DefaultEffectList.h` (см. раздел ниже), и текущий режим ставится в 0. + +### Сброс списка к «заводскому» + +При использовании **FileEffectStorage** сброс к списку по умолчанию выполняется вызовом **`reset()`** у хранилища: список очищается и снова заполняется из **того же** списка по умолчанию — массива `DefaultEffects::effectIds` в `src/configs/DefaultEffectList.h`. Текущий индекс становится 0. В прошивке вызов `reset()` нужно добавить в код (например, по длинному удержанию кнопки или отдельной команде), если такая функция нужна. + + +## Как менять список режимов +Единственное место, где задаётся **порядок и набор эффектов по умолчанию** (при старте и после сброса): +**`src/configs/DefaultEffectList.h`** — массив `DefaultEffects::effectIds` (элементы типа `EffectId::…`). +| Что сделать | Как | +|-------------|-----| +| **Добавить режим в список** | Вставить строку `EffectId::ИмяРежима,` (имя варианта из `enum class EffectId` в `src/core/effect/EffectFactory.h`). Номера из таблицы режимов в README — это те же значения id. | +| **Убрать режим** | Удалить или закомментировать соответствующую строку. | +| **Поменять порядок** | Переставить строки в массиве: сверху вниз = порядок переключения кнопкой «вперёд». | +| **Повторить режим в списке** | Добавить тот же `EffectId::…` ещё раз — режим будет встречаться несколько раз при переключении. | +Размер массива **не задаётся вручную**: компилятор выводит его из числа элементов в `{ … }`. + +Этот список используется и при **`SAVE_TO_EEPROM = false`** (**StaticEffectStorage**), и при **`SAVE_TO_EEPROM = true`** при первом запуске, пустом файле и после **`reset()`** (**FileEffectStorage**): дублировать настройку в других файлах не нужно. + +После правок пересоберите прошивку (`pio run` …) и при необходимости загрузите её; при использовании LittleFS, если нужно заново применить «заводской» список на уже прошивавшемся устройстве, сотрите файл настроек или вызовите **`reset()`** в коде (если он у вас подключён). + +После изменения пересоберите прошивку и загрузите её. + +### Список по умолчанию при сохранении в файл + +При **`SAVE_TO_EEPROM = true`** при первом запуске (или после сброса через `reset()`) список берётся из того же **`StaticEffectStorage::createDefaultEffectsList()`**: создаётся временный `StaticEffectStorage`, и его список копируется в файл. Поэтому правки в **`StaticEffectStorage.h`** в методе `createDefaultEffectsList()` влияют и на «стартовый» список при работе с файлом. + +Итого: чтобы изменить **начальный** набор и порядок режимов (и при статике, и при сохранении в файл), редактируйте **`createDefaultEffectsList()`** в **`StaticEffectStorage.h`**: добавляйте/удаляйте/переставляйте вызовы `internalAddEffect(id)` по нужным `id` (1–31). + + +## Структура проекта + +``` +LedMatrix/ +├── platformio.ini # Конфигурация PlatformIO +├── src/ +│ ├── main.cpp # Точка входа, setup/loop +│ ├── myapplication.* # Приложение: кнопка, ИК, автомод, менеджер эффектов +│ ├── configs/ +│ │ ├── matrix.h # Настройки матрицы и пинов +│ │ ├── DefaultEffectList.h # Список режимов по умолчанию (порядок и набор) +│ │ └── constants.h # Константы, index_t +│ ├── core/ +│ │ ├── effect/ # EffectManager, EffectFactory, хранилища +│ │ ├── file/ # Работа с файлами (LSF) +│ │ └── ... +│ ├── effects/ +│ │ ├── effect.h # Базовый класс Effect +│ │ └── effects_impl/ # Реализации режимов (*.h, при необходимости *.cpp) +│ ├── controls/ # Кнопка, ИК, автомод +│ ├── events/ # События, наблюдатель +│ ├── libs/ # LedMatrix, отладка, диапазоны +│ └── modules/ # Реле и др. +└── .cursor/rules/ # Правила сборки и создания эффектов +``` + +## Добавление нового режима + +1. Создать класс, наследующий `Effect`, в `src/effects/effects_impl/<имя>.h`. +2. Реализовать минимум `on_update()`; в `on_init()` при необходимости вызвать `set_fps()`. +3. В **`EffectFactory.h`**: добавить новый вариант в `enum class EffectId` перед `Count` (следующий свободный номер). +4. В **`EffectFactory.cpp`**: `#include` заголовка эффекта и строка `EFFECT_CASE(ИмяКакВEnum);` (имя класса должно совпадать с именем в enum). +5. В **`DefaultEffectList.h`**: при необходимости добавить `EffectId::…` в `DefaultEffects::effectIds`, чтобы режим попал в стартовый список. + +Подробная инструкция — в [.cursor/rules/effects.mdc](.cursor/rules/effects.mdc). + +## Зависимости + +- **FastLED** — работа с лентой +- **GyverButton** — обработка кнопки +- **IRremote** — ИК-приём (опционально) +- **Vector** — список эффектов +- **EspSoftwareSerial** — только для ESP32 (если используется) + diff --git a/platformio.ini b/platformio.ini index 9d468b5..2e9610f 100644 --- a/platformio.ini +++ b/platformio.ini @@ -14,15 +14,19 @@ lib_deps = fastled/FastLED@3.8.0 gyverlibs/GyverButton@^3.8 z3t0/IRremote@^4.2.1 + janelia-arduino/Vector@^1.2.0 monitor_speed = 115200 upload_speed = 921600 +filesystem = littlefs +; NO_LED_FEEDBACK_CODE — без индикации на LED (см. документацию IRremote) +build_flags = -DNO_LED_FEEDBACK_CODE [env:esp12e] board = esp12e platform = espressif8266@2.6.3 framework = arduino -build_flags = -DESP12F -board_build.filesystem = littlefs +build_flags = ${common.build_flags} -DESP12F +board_build.filesystem = ${common.filesystem} lib_deps = ${common.lib_deps} monitor_speed = ${common.monitor_speed} @@ -32,7 +36,8 @@ upload_speed = ${common.upload_speed} board = esp32dev platform = platformio/espressif32 framework = arduino -build_flags = -DESP32DEV +build_flags = ${common.build_flags} -DESP32DEV +board_build.filesystem = ${common.filesystem} lib_deps = ${common.lib_deps} plerup/EspSoftwareSerial@^8.2.0 diff --git a/src/configs/DefaultEffectList.h b/src/configs/DefaultEffectList.h new file mode 100644 index 0000000..f5af0e4 --- /dev/null +++ b/src/configs/DefaultEffectList.h @@ -0,0 +1,46 @@ +#pragma once + +#include "core/effect/EffectFactory.h" + +#include + +// Список id эффектов по умолчанию (начальный список при инициализации / сбросе памяти). +// Можно удалять, добавлять, менять порядок, дублировать эффекты — размер kIds выводится автоматически. +// +// Данные constexpr обычно лежат во flash (.rodata), а не в оперативной памяти; в RAM — только копия +// в векторе хранилища эффектов во время работы. +namespace DefaultEffects { + static constexpr EffectId effectIds[] = { + EffectId::SlowRandom, + EffectId::SimpleRainbow, + EffectId::Dribs, + EffectId::Rain, + EffectId::AllRandom, + EffectId::Snow, + EffectId::Fire, + EffectId::TheMatrix, + EffectId::SimpleBalls, + EffectId::Confetti, + EffectId::Starfall, + EffectId::DynamicSquare, + EffectId::RandomRain, + EffectId::RainbowRain, + EffectId::Points, + EffectId::RainbowPoint, + EffectId::RainbowStaticPoint, + EffectId::TextMode, + EffectId::Mouse, + EffectId::Pacman, + EffectId::CircularPoint, + EffectId::ZigZag, + EffectId::HorizontalRainbowPoint, + EffectId::NY2020, + EffectId::DribsAllSide, + EffectId::Snake, + EffectId::RadialFire, + EffectId::RadialPattern, + EffectId::CrazyBees, + EffectId::Spiral, + EffectId::PulseRings, + }; +} // namespace DefaultEffects diff --git a/src/configs/matrix.h b/src/configs/matrix.h index 4e6f3fa..9c13c91 100644 --- a/src/configs/matrix.h +++ b/src/configs/matrix.h @@ -36,6 +36,7 @@ #define BTN_ENABLE true // подключить управление кнопкой #define BUTTON_STEP_TIMEOUT (100U) // каждые BUTTON_STEP_TIMEOUT мс будет генерироваться событие удержания кнопки (для регулировки яркости) #define BUTTON_CLICK_TIMEOUT (500U) // максимальное время между нажатиями кнопки в мс, до достижения которого считается серия последовательных нажатий +#define BUTTON_HOLD_RESET_MS (5000U) // удержание кнопки столько мс — сброс списка режимов на заводской #define AUTOMOD_INTERVAL (10000U) // кол-во времни между автоматическим переключением режима #define AUTOMOD_DEF_STATE false // Начальное состояние автомода. true - вкл, false - выкл @@ -46,11 +47,16 @@ #define RELAY_ENABLE false // подключить урпавление через ИК приёмник #define RELAY_DELAY 100 +// =============== Настройки Сохранения в память ================== + +#define SAVE_TO_EEPROM false // сохранять настройки в EEPROM +#define SAVE_TO_EEPROM_FILE "mods.txt" // имя файла для сохранения настроек, если используется файловая система + // ===================== Платформозависимые настройки ===================== #ifdef ESP32DEV #define LEDS_PIN (22) // пин к которому подключены светодиоды -#define BTN_PIN (23) // пин кнопки +#define BTN_PIN (5) // пин кнопки #define IR_RECEIVE_PIN (24) // пин к которому подключен ИК приёмник #define RELAY_PIN (25) // пин управления реле diff --git a/src/configs/window.h b/src/configs/window.h index 0a4d91f..452336c 100644 --- a/src/configs/window.h +++ b/src/configs/window.h @@ -31,7 +31,7 @@ // ----------- ----------- ^---------- ----------^ // 0---------3 0---------3 0---------3 0---------3 -// ===================== Настройки Кнопки ===================== +// ===================== Настройки управления ===================== #define BTN_ENABLE true // подключить управление кнопкой #define BUTTON_STEP_TIMEOUT (100U) // каждые BUTTON_STEP_TIMEOUT мс будет генерироваться событие удержания кнопки (для регулировки яркости) @@ -46,6 +46,11 @@ #define RELAY_ENABLE false // подключить урпавление через ИК приёмник #define RELAY_DELAY 100 +// =============== Настройки Сохранения в память ================== + +#define SAVE_TO_EEPROM true // сохранять настройки в EEPROM +#define SAVE_TO_EEPROM_FILE "mods.txt" // имя файла для сохранения настроек, если используется файловая система + // ===================== Платформозависимые настройки ===================== #ifdef ESP32DEV @@ -56,9 +61,9 @@ #else // if DESP12F -#define LEDS_PIN (D4) // пин к которому подключены светодиоды -#define BTN_PIN (D2) // пин кнопки -#define IR_RECEIVE_PIN (D5) // пин к которому подключен ИК приёмник +#define LEDS_PIN (D2) // пин к которому подключены светодиоды +#define BTN_PIN (D5) // пин кнопки +#define IR_RECEIVE_PIN (D6) // пин к которому подключен ИК приёмник #define RELAY_PIN (D7) // пин управления реле #endif // ESP32DEV \ No newline at end of file diff --git a/src/controls/automode.cpp b/src/controls/automode.cpp index 26a6129..2ad395d 100644 --- a/src/controls/automode.cpp +++ b/src/controls/automode.cpp @@ -17,10 +17,9 @@ AutoChangeMode::~AutoChangeMode() { void AutoChangeMode::onTick() { if (isEnable()) { - if (millis() - _savedTime > _delay /*&& EffectsList::getInstance().effectIsEnd()*/) { - auto ev = ChangeModEvent({EventType::ChangeMode, ChangeModEvent::Type::Next, 0}); - Observable::notify(&ev); - out("AutoControl: next mode\n"); + if (millis() - _savedTime > _delay) { + Observable::notify(EventType::ChangeMode, false, ChangeModeEventRequest::Type::Next); + logInfo("AutoControl: next mode\n"); } } } @@ -50,7 +49,7 @@ void AutoChangeMode::setDelay(unsigned long delay) { _delay = delay; } -void AutoChangeMode::handleEvent(Event *event) { +void AutoChangeMode::handleEvent(const Event *event) { if (event->type == EventType::ChangeMode) { // сменился мод, сбрасываем таймер if (isEnable()) { diff --git a/src/controls/automode.h b/src/controls/automode.h index db99cf7..a2e56a2 100644 --- a/src/controls/automode.h +++ b/src/controls/automode.h @@ -33,5 +33,5 @@ class AutoChangeMode: public IObserver { // Получение текущего состояния автоматического переключения режимов bool isEnable(); - virtual void handleEvent(Event *event) override; + virtual void handleEvent(const Event *event) override; }; \ No newline at end of file diff --git a/src/controls/button.cpp b/src/controls/button.cpp index 6d5ac01..16ffeda 100644 --- a/src/controls/button.cpp +++ b/src/controls/button.cpp @@ -5,26 +5,27 @@ Button::Button(int8_t pin, bool type, bool dir) : touch(GButton(pin, type, dir)) { touch.setStepTimeout(BUTTON_STEP_TIMEOUT); touch.setClickTimeout(BUTTON_CLICK_TIMEOUT); + touch.setTimeout(BUTTON_HOLD_RESET_MS); // удержание дольше — сброс списка режимов (isHold) } void Button::onTick() { touch.tick(); + if (touch.isHolded()) { + Observable::notify(EventType::ResetModesList); + return; + } uint8_t clickCount = touch.hasClicks() ? touch.getClicks() : 0U; switch (clickCount) { case 1U: { - auto ev = ChangeModEvent({EventType::ChangeMode, ChangeModEvent::Type::Next}); - // auto ev = ChangeBoolEvent({EventType::ChangePowerState, true}); - Observable::notify(&ev); + Observable::notify(EventType::ChangeMode, true, ChangeModeEventRequest::Type::Next); break; } case 2U: { - auto ev = Event({EventType::ChangePowerState}); - Observable::notify(&ev); + Observable::notify(EventType::ChangePowerState); break; } case 3U: { - auto ev = Event({EventType::ChangeAutoMod}); - Observable::notify(&ev); + Observable::notify(EventType::ChangeAutoMod); break; } default: diff --git a/src/controls/ir.cpp b/src/controls/ir.cpp index 6f318c6..8764ba9 100644 --- a/src/controls/ir.cpp +++ b/src/controls/ir.cpp @@ -14,23 +14,19 @@ void IR::onTick() { if (IrReceiver.decode()) { switch (IrReceiver.decodedIRData.command) { case 0x45: { // << - auto ev = ChangeModEvent({EventType::ChangeMode, ChangeModEvent::Type::Previous}); - Observable::notify(&ev); + Observable::notify(EventType::ChangeMode, true, ChangeModeEventRequest::Type::Previous); break; } case 0x47: { // play - auto ev = ChangeBoolEvent({EventType::SetPowerState, true}); - Observable::notify(&ev); + Observable::notify(EventType::SetPowerState, true); break; } case 0x4a: { // || - auto ev = ChangeBoolEvent({EventType::SetPowerState, false}); - Observable::notify(&ev); + Observable::notify(EventType::SetPowerState, false); break; } case 0x48: { // >> - auto ev = ChangeModEvent({EventType::ChangeMode, ChangeModEvent::Type::Next}); - Observable::notify(&ev); + Observable::notify(EventType::ChangeMode, true, ChangeModeEventRequest::Type::Next); break; } } diff --git a/src/core/Serializer.h b/src/core/Serializer.h new file mode 100644 index 0000000..40d20ed --- /dev/null +++ b/src/core/Serializer.h @@ -0,0 +1,54 @@ +#pragma once + +#include "core/common_interfaces/ISerializable.h" +#include "core/common_interfaces/IStream.h" + +#include +#include +#include + +template +class Serializer +{ +public: + ~Serializer() = default; + + // For simple types (int, float, etc.) + template + static typename std::enable_if::value, bool>::type + serialize(const SimpleType& value, IStream& stream) { + return stream.write(&value, sizeof(SimpleType)); + } + + template + static typename std::enable_if::value, bool>::type + deserialize(SimpleType& value, const IStream& stream) { + return stream.read(&value, sizeof(SimpleType)); + } + + template + static typename std::enable_if::value, size_t>::type + typeSize() { + return sizeof(SimpleType); + } + + + // For types derived from ISerializable + template + static typename std::enable_if::value, bool>::type + serialize(const CustomType& value, IStream& stream) { + return static_cast(value).serialize(stream); + } + + template + static typename std::enable_if::value, bool>::type + deserialize(CustomType& value, const IStream& stream) { + return static_cast(value).deserialize(stream); + } + + template + static typename std::enable_if::value, size_t>::type + typeSize() { + return CustomType::typeSize(); + } +}; diff --git a/src/core/Variable/FileSavableVariable.h b/src/core/Variable/FileSavableVariable.h new file mode 100644 index 0000000..48e58d5 --- /dev/null +++ b/src/core/Variable/FileSavableVariable.h @@ -0,0 +1,61 @@ +#pragma once + +#include "Variable.h" +#include "core/common_interfaces/ISaveable.h" +#include "core/Serializer.h" +#include "core/file/IFileSavable.h" + +template +class FileSavableVariable: public Variable, public ISaveable, public IFileSaveable +{ + // delete copy constructor and assignment operator + FileSavableVariable(const FileSavableVariable&) = delete; + FileSavableVariable& operator=(const FileSavableVariable&) = delete; +public: + FileSavableVariable() = delete; + FileSavableVariable(IFileHandler* fileHandler, + uint32_t offset = 0, + const T& defaultValue = {}, + bool loadOnCreate = true) + { + this->_fileHandler = fileHandler; + this->_offset = offset; + if (!loadOnCreate || !load()) { + this->_value = defaultValue; + save(); + } + } + + // move constructor and assignment operator + FileSavableVariable(FileSavableVariable&& other) { + this->_fileHandler = other._fileHandler; + this->_offset = other._offset; + this->_value = other._value; + + other._fileHandler = nullptr; + } + + FileSavableVariable& operator=(FileSavableVariable&& other) { + this->_fileHandler = other._fileHandler; + this->_offset = other._offset; + this->_value = other._value; + + other._fileHandler = nullptr; + return *this; + } + + virtual void set(const T& value) override { + if (this->_value != value) { + this->_value = value; + save(); + } + } + + virtual bool save() override { + return Serializer::serialize(this->_value, *this); + } + + virtual bool load() override { + return Serializer::deserialize(this->_value, *this); + } +}; \ No newline at end of file diff --git a/src/core/Variable/Variable.h b/src/core/Variable/Variable.h new file mode 100644 index 0000000..206729d --- /dev/null +++ b/src/core/Variable/Variable.h @@ -0,0 +1,26 @@ +#pragma once + +#include "core/Serializer.h" + +template +class Variable +{ +protected: + T _value; +public: + Variable() = default; + Variable(const T& value): _value(value) {} + ~Variable() = default; + + virtual const T& get() const { + return _value; + } + + virtual void set(const T& value) { + _value = value; + } + + virtual size_t size() const { + return Serializer::template typeSize(); + } +}; \ No newline at end of file diff --git a/src/core/common_interfaces/ISaveable.h b/src/core/common_interfaces/ISaveable.h new file mode 100644 index 0000000..94a2373 --- /dev/null +++ b/src/core/common_interfaces/ISaveable.h @@ -0,0 +1,10 @@ +#pragma once + +class ISaveable +{ +public: + virtual ~ISaveable() = default; + + virtual bool save() = 0; + virtual bool load() = 0; +}; \ No newline at end of file diff --git a/src/core/common_interfaces/ISerializable.h b/src/core/common_interfaces/ISerializable.h new file mode 100644 index 0000000..e961403 --- /dev/null +++ b/src/core/common_interfaces/ISerializable.h @@ -0,0 +1,12 @@ +#pragma once + +#include "core/common_interfaces/IStream.h" + +class ISerializable +{ +public: + virtual ~ISerializable() = default; + + virtual bool serialize(IStream& stream) const = 0; + virtual bool deserialize(const IStream& stream) = 0; +}; \ No newline at end of file diff --git a/src/core/common_interfaces/IStream.h b/src/core/common_interfaces/IStream.h new file mode 100644 index 0000000..b37c302 --- /dev/null +++ b/src/core/common_interfaces/IStream.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +class IStream +{ +public: + virtual ~IStream() = default; + + virtual bool write(const void* data, size_t size, size_t seekOffset = 0) = 0; + virtual bool read(void* data, size_t size, size_t seekOffset = 0) const = 0; +}; diff --git a/src/core/effect/EffectFactory.cpp b/src/core/effect/EffectFactory.cpp new file mode 100644 index 0000000..d74a9d9 --- /dev/null +++ b/src/core/effect/EffectFactory.cpp @@ -0,0 +1,111 @@ +#include "EffectFactory.h" + +#include "effects/erroreffect.h" +#include "effects/effects_impl/slow_random.h" +#include "effects/effects_impl/simple_rainbow.h" +#include "effects/effects_impl/dribs.h" +#include "effects/effects_impl/rain.h" +#include "effects/effects_impl/all_random.h" +#include "effects/effects_impl/snow.h" +#include "effects/effects_impl/fire.h" +#include "effects/effects_impl/the_matrix.h" +#include "effects/effects_impl/simple_balls.h" +#include "effects/effects_impl/confetti.h" +#include "effects/effects_impl/starfall.h" +#include "effects/effects_impl/dynamic_square.h" +#include "effects/effects_impl/random_rain.h" +#include "effects/effects_impl/rainbow_rain.h" +#include "effects/effects_impl/points.h" +#include "effects/effects_impl/rainbow_point.h" +#include "effects/effects_impl/rainbow_static_point.h" +#include "effects/effects_impl/text.h" +#include "effects/effects_impl/mouse.h" +#include "effects/effects_impl/pacman.h" +#include "effects/effects_impl/circular_point.h" +#include "effects/effects_impl/zigzag.h" +#include "effects/effects_impl/horizontal_rainbow_point.h" +#include "effects/effects_impl/ny2020.h" +#include "effects/effects_impl/dribs_all_side.h" +#include "effects/effects_impl/snake/snake.h" +#include "effects/effects_impl/radial_fire.h" +#include "effects/effects_impl/radial_pattern.h" +#include "effects/effects_impl/crazy_bees.h" +#include "effects/effects_impl/spiral.h" +#include "effects/effects_impl/pulse_rings.h" + +#include "libs/StdFeatures.h" + +using EffectCreator = std::unique_ptr (*)(); + +template +static std::unique_ptr makeEffect() { + return std::make_unique(); +} + +template +static constexpr EffectCreator effectCreator() { + return makeEffect; +} + +struct EffectCreationInfo { + const char* effect_name; + EffectCreator effect_creator; +}; + +#define EFFECT_CASE(type) \ + case static_cast(EffectId::type): \ + return EffectCreationInfo{#type, effectCreator()}; + +static EffectCreationInfo getEffectInfo(uint32_t effect_id) { + switch (effect_id) { + EFFECT_CASE(ErrorEffect); + EFFECT_CASE(SlowRandom); + EFFECT_CASE(SimpleRainbow); + EFFECT_CASE(Dribs); + EFFECT_CASE(Rain); + EFFECT_CASE(AllRandom); + EFFECT_CASE(Snow); + EFFECT_CASE(Fire); + EFFECT_CASE(TheMatrix); + EFFECT_CASE(SimpleBalls); + EFFECT_CASE(Confetti); + EFFECT_CASE(Starfall); + EFFECT_CASE(DynamicSquare); + EFFECT_CASE(RandomRain); + EFFECT_CASE(RainbowRain); + EFFECT_CASE(Points); + EFFECT_CASE(RainbowPoint); + EFFECT_CASE(RainbowStaticPoint); + EFFECT_CASE(TextMode); + EFFECT_CASE(Mouse); + EFFECT_CASE(Pacman); + EFFECT_CASE(CircularPoint); + EFFECT_CASE(ZigZag); + EFFECT_CASE(HorizontalRainbowPoint); + EFFECT_CASE(NY2020); + EFFECT_CASE(DribsAllSide); + EFFECT_CASE(Snake); + EFFECT_CASE(RadialFire); + EFFECT_CASE(RadialPattern); + EFFECT_CASE(CrazyBees); + EFFECT_CASE(Spiral); + EFFECT_CASE(PulseRings); + + default: + return EffectCreationInfo{"ErrorEffect", effectCreator()}; + } +} + +uint32_t EffectFactory::getEffectCount() { + return static_cast(EffectId::Count); +} + +std::unique_ptr EffectFactory::createEffect(uint32_t effect_id) { + return getEffectInfo(effect_id).effect_creator(); +} + +const char* EffectFactory::getEffectName(uint32_t effect_id) { + return getEffectInfo(effect_id).effect_name; +} + +#undef EFFECT_CASE \ No newline at end of file diff --git a/src/core/effect/EffectFactory.h b/src/core/effect/EffectFactory.h new file mode 100644 index 0000000..b50d5eb --- /dev/null +++ b/src/core/effect/EffectFactory.h @@ -0,0 +1,51 @@ +#pragma once + +#include "effects/effect.h" + +#include +#include + +enum class EffectId : uint32_t { + ErrorEffect = 0, + SlowRandom = 1, + SimpleRainbow = 2, + Dribs = 3, + Rain = 4, + AllRandom = 5, + Snow = 6, + Fire = 7, + TheMatrix = 8, + SimpleBalls = 9, + Confetti = 10, + Starfall = 11, + DynamicSquare = 12, + RandomRain = 13, + RainbowRain = 14, + Points = 15, + RainbowPoint = 16, + RainbowStaticPoint = 17, + TextMode = 18, + Mouse = 19, + Pacman = 20, + CircularPoint = 21, + ZigZag = 22, + HorizontalRainbowPoint = 23, + NY2020 = 24, + DribsAllSide = 25, + Snake = 26, + RadialFire = 27, + RadialPattern = 28, + CrazyBees = 29, + Spiral = 30, + PulseRings = 31, + Count +}; + +class EffectFactory { +public: + static std::unique_ptr createEffect(uint32_t effect_id); + static const char* getEffectName(uint32_t effect_id); + static uint32_t getEffectCount(); +}; + + diff --git a/src/core/effect/EffectInfo.h b/src/core/effect/EffectInfo.h new file mode 100644 index 0000000..6a662d7 --- /dev/null +++ b/src/core/effect/EffectInfo.h @@ -0,0 +1,40 @@ +# pragma once + +#include "core/common_interfaces/ISerializable.h" + +#include + +class EffectInfo : public ISerializable +{ +public: + uint32_t id; + uint32_t savedIndex; + + EffectInfo() : id(0), savedIndex(0) {} + EffectInfo(uint32_t id, uint32_t savedIndex) : id(id), savedIndex(savedIndex) {} + + virtual bool serialize(IStream& stream) const override { + return stream.write(&id, sizeof(id), 0) && stream.write(&savedIndex, sizeof(savedIndex), sizeof(id)); + } + + virtual bool deserialize(const IStream& stream) override { + return stream.read(&id, sizeof(id), 0) && stream.read(&savedIndex, sizeof(savedIndex), sizeof(id)); + } + + static size_t typeSize() { + return sizeof(id) + sizeof(savedIndex); + } + + bool operator==(const EffectInfo& other) const { + return id == other.id && savedIndex == other.savedIndex; + } + + bool operator!=(const EffectInfo& other) const { + return !(*this == other); + } + + static const EffectInfo& getErrorEffectInfo() { + static const EffectInfo errorEffectInfo(0, 0); + return errorEffectInfo; + } +}; \ No newline at end of file diff --git a/src/core/effect/EffectManager.cpp b/src/core/effect/EffectManager.cpp new file mode 100644 index 0000000..12fab69 --- /dev/null +++ b/src/core/effect/EffectManager.cpp @@ -0,0 +1,75 @@ +#include "EffectManager.h" +#include "libs/debug_lib.h" + +EffectManager::EffectManager(IEffectStorage& storage) + : _storage(storage) +{ + Observable::subscribe(EventType::ChangeMode, this); + + this->updateEffect(); +} + +void EffectManager::onTick() { + this->onCheckRequestedEffectChange(); + this->onTickEffect(); +} + +void EffectManager::onCheckRequestedEffectChange() { + // Обработка отложенного запроса на смену эффекта + if (_pendingRequest.type != ChangeModeEventRequest::Type::None && (!_currentEffect || _currentEffect->is_end() || _pendingRequest.hardReset)) { + uint16_t requestedModIndex = 0; + if (_pendingRequest.type == ChangeModeEventRequest::Type::Set) { + requestedModIndex = _pendingRequest.modNum; + } else if (_pendingRequest.type == ChangeModeEventRequest::Type::Next) { + requestedModIndex = (_pendingRequest.modNumOffset + _storage.getCurrentIndex()) % _storage.size(); + } else if (_pendingRequest.type == ChangeModeEventRequest::Type::Previous) { + requestedModIndex = (_storage.size() + _storage.getCurrentIndex() - (_pendingRequest.modNumOffset % _storage.size())) % _storage.size(); + } + + if (requestedModIndex != _storage.getCurrentIndex()) { + _storage.setCurrentIndex(requestedModIndex); + this->updateEffect(); + } + + _pendingRequest = ChangeModeEventRequest(); + } +} + +void EffectManager::onTickEffect() { + if (_currentEffect && _fpsManager.needUpdate()) { + _currentEffect->on_update(); + _currentEffect->on_render(); + FastLED.show(); + } +} + +void EffectManager::updateEffect() { + const EffectInfo& effectInfo = _storage.getEffectInfo(_storage.getCurrentIndex()); + if (_currentEffect) { + _currentEffect->on_clear(); + } + _currentEffect = EffectFactory::createEffect(effectInfo.id); + _currentEffect->on_init(); + _fpsManager.setTargetFPS(_currentEffect->get_fps()); + + Observable::notify(EventType::ModChanged, effectInfo.id, _storage.getCurrentIndex()); +} + +void EffectManager::setEffect(uint32_t index) { + if (index != _storage.getCurrentIndex()) { + _storage.setCurrentIndex(index); + this->updateEffect(); + } +} + +float EffectManager::getCurrentFPS() const { + return _fpsManager.getRealFPS(); +} + +void EffectManager::handleEvent(const Event* event) { + if (event->type == EventType::ChangeMode) { + const ChangeModeEvent* changeEvent = static_cast(event); + _pendingRequest = _pendingRequest + changeEvent->request; + this->onTick(); + } +} diff --git a/src/core/effect/EffectManager.h b/src/core/effect/EffectManager.h new file mode 100644 index 0000000..9139524 --- /dev/null +++ b/src/core/effect/EffectManager.h @@ -0,0 +1,41 @@ +#pragma once + +#include "events/observer.h" +#include "storage/IEffectStorage.h" +#include "EffectInfo.h" +#include "EffectFactory.h" +#include "FpsManager.h" +#include "effects/effect.h" +#include "events/events.h" + +#include + +class EffectManager : public IObserver +{ +public: + EffectManager(IEffectStorage& storage); + ~EffectManager() = default; + + // Управление эффектами + void updateEffect(); // обновить эффект, эффектом из хранилища + void setEffect(uint32_t index); // установить эффект по индексу + + // Цикл отложенного переключения эффектов + void onTick(); + + // Получение текущего FPS + float getCurrentFPS() const; + + // Обработка событий (реализация IObserver) + void handleEvent(const Event* event) override; + +private: + + void onCheckRequestedEffectChange(); + void onTickEffect(); + + IEffectStorage& _storage; // Ссылка на хранилище эффектов + std::unique_ptr _currentEffect; // Указатель на текущий активный эффект + ChangeModeEventRequest _pendingRequest; // Ожидающий запрос на смену эффекта + FpsManager _fpsManager; // Менеджер FPS +}; diff --git a/src/core/effect/FpsManager.h b/src/core/effect/FpsManager.h new file mode 100644 index 0000000..da7ca7a --- /dev/null +++ b/src/core/effect/FpsManager.h @@ -0,0 +1,48 @@ +#pragma once + +#include +#include + +class FpsManager { + public: + FpsManager() = default; + ~FpsManager() = default; + + float getRealFPS() const { + return (float)_real_fps / 10; + } + + void setTargetFPS(uint16_t fps) { + _target_fps = fps; + } + + bool needUpdate() { + bool need = false; + unsigned long tick_size = 1000000 / _target_fps; + + // проверяем соответствие фпса указанному в режиме + if (micros() - _prev_micros > tick_size) { + // корректировка таймера + // если фпс меньше чем указано в режиме + if (micros() - _prev_micros > tick_size * 2) { + // считаем реальный фпс + _real_fps = (_real_fps + 1000000 * 10 / (micros() - _prev_micros - tick_size)) / 2; + // следующей итерацией обязательно вызываем on_update + корректируем время + // чтобы prev_micros сильно не уходило от текущего времени + _prev_micros = micros() - tick_size; + } else { + // иначе просто прибавляем размер тика в микросекундах + _prev_micros += tick_size; + // считаем реальный фпс + _real_fps = (_real_fps + _target_fps * 10) / 2; + } + need = true; + } + return need; + } + + private: + unsigned long _prev_micros; + unsigned long _real_fps; + uint16_t _target_fps = 30; +}; diff --git a/src/core/effect/storage/FileEffectStorage.h b/src/core/effect/storage/FileEffectStorage.h new file mode 100644 index 0000000..ddbffe4 --- /dev/null +++ b/src/core/effect/storage/FileEffectStorage.h @@ -0,0 +1,247 @@ +# pragma once + +#include "core/file/IFileHandler.h" +#include "core/Variable/FileSavableVariable.h" +#include "libs/debug_lib.h" +#include "IEffectStorage.h" +#include "configs/DefaultEffectList.h" + +#include +#include "vector" +#include + +class FileEffectStorage : public IEffectStorage +{ +private: + std::unique_ptr _fileHandler; + FileSavableVariable _currentEffectIndex; + std::vector> _effects; + uint32_t _offset; +public: + FileEffectStorage(std::unique_ptr&& fileHandler) : _fileHandler(std::move(fileHandler)), _currentEffectIndex(_fileHandler.get(), 0, 0) { + _offset = _currentEffectIndex.size(); + + size_t savedEffectAmount = (_fileHandler->size() - _currentEffectIndex.size()) / EffectInfo::typeSize(); + for (size_t i = 0; i < savedEffectAmount; i++) { + internalAddEffect(EffectInfo(), true); + // удалить режим из памяти можно только занулив savedIndex, + // поэтому, если встречаем удалённый режим, дальше загружать не нужно + if (_effects.back().get().id == 0) { + internalRemoveEffect(); + break; + } + } + + if (_effects.empty()) { + createDefaultEffectsList(); + } + + if (_currentEffectIndex.get() >= _effects.size()) { + logError("Incorrect currentEffectIndex. Value is out of effects size\n"); + _currentEffectIndex.set(0); + } + } + virtual ~FileEffectStorage() {}; + + virtual const EffectInfo& getEffectInfo(uint32_t index) const override { + if (index >= _effects.size()) { + logError("Cannot get effect info: index is out of range\n"); + return EffectInfo::getErrorEffectInfo(); + } + return _effects[index].get(); + } + + virtual uint32_t getCurrentIndex() const override { + return _currentEffectIndex.get(); + } + + virtual void setCurrentIndex(uint32_t index) override { + if (index >= _effects.size()) { + logError("Cannot set current index: index is out of range\n"); + return; + } + _currentEffectIndex.set(index); + } + + virtual void addEffect(uint32_t effectId) override { + if (effectId == 0) { + logError("Cannot add effect with id 0\n"); + return; + } + internalAddEffect(EffectInfo(effectId, generateSavedIndex()), false); + } + + virtual void addEffect(uint32_t effectId, uint32_t position) override { + if (position > _effects.size()) { + logError("Cannot add effect: position is out of range\n"); + return; + } + + std::vector localEffects; + localEffects.reserve(_effects.size() - position); + + // сохраняем эффекты, которые будут сдвинуты + for (size_t i = position; i < _effects.size(); i++) { + localEffects.push_back(_effects[i].get()); + } + // удаляем их из файла + for (size_t i = 0; i < localEffects.size(); i++) { + internalRemoveEffect(); + } + // добавляем новый эффект + internalAddEffect(EffectInfo(effectId, generateSavedIndex()), false); + // возвращаем сдвинутые эффекты обратно + for(const auto& effect : localEffects) { + internalAddEffect(effect, false); + } + } + + virtual void removeEffect() override { + if (_effects.empty()) { + logError("Cannot remove effect: effects list is empty\n"); + return; + } + _effects[_effects.size() - 1].set({0, 0}); + internalRemoveEffect(); + } + + virtual void removeEffect(uint32_t position) override { + if (position >= _effects.size()) { + logError("Cannot remove effect: position is out of range\n"); + return; + } + + std::vector localEffects; + localEffects.reserve(_effects.size() - position - 1); + // сохраняем эффекты, которые будут сдвинуты + for (size_t i = position + 1; i < _effects.size(); i++) { + localEffects.push_back(_effects[i].get()); + } + // помечаем последний эффект как удалённый + _effects[_effects.size() - 1].set({0, 0}); + // удаляем эффекты из файла + for (size_t i = 0; i < localEffects.size() + 1; i++) { + internalRemoveEffect(); + } + // возвращаем сдвинутые эффекты обратно + for(const auto& effect : localEffects) { + internalAddEffect(effect, false); + } + } + + virtual size_t size() const override { + return _effects.size(); + } + + virtual void reset() override { + clear(); + createDefaultEffectsList(); + logInfo("Effects list resetted\n"); + } + + virtual void clear() override { + for (size_t i = 0; i < _effects.size(); i++) { + _effects[i].set({0, 0}); + } + _effects.clear(); + _offset = _currentEffectIndex.size(); + _currentEffectIndex.set(0); + } +private: + uint32_t generateSavedIndex(uint32_t tryCount = 0) { + uint32_t generated_index = random(0xFFFFFFFE) + 1; + for (const auto& effect : _effects) { + if (effect.get().savedIndex == generated_index && tryCount < 10) { + tryCount++; + generated_index = generateSavedIndex(tryCount); + } + } + return generated_index; + } + + void internalAddEffect(const EffectInfo& effectInfo, bool loadOnCreate) { + _effects.emplace_back(_fileHandler.get(), _offset, std::move(effectInfo), loadOnCreate); + _offset += EffectInfo::typeSize(); + } + + void internalRemoveEffect() { + if (_effects.empty()) { + logError("Cannot remove effect: effects list is empty\n"); + return; + } + + _effects.pop_back(); + _offset -= EffectInfo::typeSize(); + + if (_currentEffectIndex.get() >= _effects.size()) { + _currentEffectIndex.set(0); + } + } + + void createDefaultEffectsList() { + logInfo("Creating default effects list\n"); + for (EffectId id : DefaultEffects::effectIds) { + internalAddEffect(EffectInfo(static_cast(id), generateSavedIndex()), false); + } + _currentEffectIndex.set(0); + } +}; + +// Test code: +// Lsf12eFileHandler fileHandler; +// //LittleFS.remove("/mods.txt"); +// listFiles(); + +// fileHandler.open("mods.txt"); + +// FileEffectStorage storage(&fileHandler); + +// logInfo("Current index: %u\n", storage.getCurrentIndex()); +// logInfo("Total effects: %zu\n", storage.size()); +// printAllMods(storage); + +// storage.reset(); +// logInfo("After reset:\n"); +// logInfo("Current index: %u\n", storage.getCurrentIndex()); +// logInfo("Total effects: %zu\n", storage.size()); +// printAllMods(storage); + +// storage.removeEffect(5); +// storage.removeEffect(5); +// storage.removeEffect(100); +// storage.removeEffect(storage.size()); +// logInfo("After removeEffect(5) and removeEffect(28):\n"); +// logInfo("Current index: %u\n", storage.getCurrentIndex()); +// logInfo("Total effects: %zu\n", storage.size()); +// printAllMods(storage); + +// for (size_t i = storage.size() + 1; i > 0; i--) { +// storage.removeEffect(); +// } +// logInfo("After removing all but one effect:\n"); +// logInfo("Current index: %u\n", storage.getCurrentIndex()); +// logInfo("Total effects: %zu\n", storage.size()); +// printAllMods(storage); + +// storage.addEffect(5); +// storage.addEffect(10); +// storage.addEffect(15, 1); +// storage.addEffect(8, 0); +// storage.addEffect(12, 10); +// logInfo("After adding effects:\n"); +// logInfo("Current index: %u\n", storage.getCurrentIndex()); +// logInfo("Total effects: %zu\n", storage.size()); +// printAllMods(storage); + +// storage.setCurrentIndex(2); +// storage.setCurrentIndex(100); +// logInfo("After setting current index to 2 and 100:\n"); +// logInfo("Current index: %u\n", storage.getCurrentIndex()); + +// EffectInfo info = storage.getEffectInfo(2); +// logInfo("Effect at index 2: ID=%u, SavedIndex=%u\n", info.id, info.savedIndex); + +// storage.clear(); +// logInfo("After clear:\n"); +// logInfo("Current index: %u\n", storage.getCurrentIndex()); +// logInfo("Total effects: %zu\n", storage.size()); \ No newline at end of file diff --git a/src/core/effect/storage/IEffectStorage.h b/src/core/effect/storage/IEffectStorage.h new file mode 100644 index 0000000..c6edeab --- /dev/null +++ b/src/core/effect/storage/IEffectStorage.h @@ -0,0 +1,27 @@ +# pragma once + +#include "core/effect/EffectInfo.h" + +#include + +class IEffectStorage +{ +public: + virtual ~IEffectStorage() = default; + + virtual const EffectInfo& getEffectInfo(uint32_t index) const = 0; + virtual uint32_t getCurrentIndex() const = 0; + + virtual void addEffect(uint32_t effectId) = 0; + // не очень эффективная функция, лучше не использовать + virtual void addEffect(uint32_t effectId, uint32_t position) = 0; + virtual void removeEffect() = 0; + // не очень эффективная функция, лучше не использовать + virtual void removeEffect(uint32_t position) = 0; + virtual size_t size() const = 0; + virtual void reset() = 0; + virtual void clear() = 0; +protected: + friend class EffectManager; + virtual void setCurrentIndex(uint32_t index) = 0; +}; diff --git a/src/core/effect/storage/StaticEffectStorage.h b/src/core/effect/storage/StaticEffectStorage.h new file mode 100644 index 0000000..4364c41 --- /dev/null +++ b/src/core/effect/storage/StaticEffectStorage.h @@ -0,0 +1,112 @@ +# pragma once + +#include "IEffectStorage.h" + +#include "configs/DefaultEffectList.h" +#include "core/effect/EffectInfo.h" +#include "libs/debug_lib.h" + +#include "vector" + +class StaticEffectStorage : public IEffectStorage +{ +private: + std::vector _effects; + uint32_t _currentEffectIndex = 0; +public: + StaticEffectStorage() { + createDefaultEffectsList(); + _currentEffectIndex = _effects.size() - 1; + } + + virtual ~StaticEffectStorage() {}; + + virtual const EffectInfo& getEffectInfo(uint32_t index) const override{ + if (index >= _effects.size()) { + logError("Cannot get effect info: index is out of range\n"); + return EffectInfo::getErrorEffectInfo(); + } + return _effects[index]; + } + + virtual uint32_t getCurrentIndex() const override{ + return _currentEffectIndex; + } + + virtual void setCurrentIndex(uint32_t index) override{ + if (index >= _effects.size()) { + logError("Cannot set current index: index is out of range\n"); + return; + } + _currentEffectIndex = index; + } + + virtual void addEffect(uint32_t effectId) override { + internalAddEffect(effectId); + } + + virtual void addEffect(uint32_t effectId, uint32_t position) override{ + if (position > _effects.size()) { + logError("Cannot add effect: position is out of range\n"); + return; + } + + _effects.insert(_effects.begin() + position, EffectInfo(effectId, 1)); + } + + virtual void removeEffect() override { + internalRemoveEffect(); + } + + virtual void removeEffect(uint32_t position) override { + if (position >= _effects.size()) { + logError("Cannot remove effect: position is out of range\n"); + return; + } + + _effects.erase(_effects.begin() + position); + + if (_currentEffectIndex >= _effects.size()) { + _currentEffectIndex = 0; + } + } + + virtual size_t size() const override { + return _effects.size(); + } + + virtual void reset() override { + clear(); + createDefaultEffectsList(); + } + + virtual void clear() override { + _effects.clear(); + _currentEffectIndex = 0; + } +private: + void createDefaultEffectsList() { + for (EffectId id : DefaultEffects::effectIds) { + internalAddEffect(static_cast(id)); + } + } + + void internalAddEffect(uint32_t effectId) { + // второе значение - это заглушка, т.к. в статическом хранилище не нужно + // хранить идентификатор эффекта в памяти (1 означает, что эффект валидный) + _effects.emplace_back(effectId, 1); + } + + void internalRemoveEffect() { + if (_effects.empty()) { + logError("Cannot remove effect: effects list is empty\n"); + return; + } + + _effects.pop_back(); + + if (_currentEffectIndex >= _effects.size()) { + _currentEffectIndex = 0; + } + } +}; diff --git a/src/core/file/IFileHandler.h b/src/core/file/IFileHandler.h new file mode 100644 index 0000000..d55d51b --- /dev/null +++ b/src/core/file/IFileHandler.h @@ -0,0 +1,20 @@ +#pragma once + +#include "core/common_interfaces/IStream.h" + +#include +#include + +class IFileHandler : public IStream +{ +public: + virtual ~IFileHandler() = default; + + virtual void open(const char* path) = 0; + virtual void close() = 0; + + virtual bool write(const void* data, size_t size, size_t seekOffset = 0) override = 0; + virtual bool read(void* data, size_t size, size_t seekOffset = 0) const override = 0; + virtual bool seek(size_t position) = 0; + virtual size_t size() const = 0; +}; \ No newline at end of file diff --git a/src/core/file/IFileSavable.h b/src/core/file/IFileSavable.h new file mode 100644 index 0000000..50cffaf --- /dev/null +++ b/src/core/file/IFileSavable.h @@ -0,0 +1,54 @@ +#pragma once + +#include "core/file/IFileHandler.h" +#include "core/common_interfaces/IStream.h" +#include "libs/debug_lib.h" + +#include +#include + +class IFileSaveable : public IStream +{ +protected: + IFileHandler* _fileHandler; + uint32_t _offset; +public: + virtual ~IFileSaveable() = default; + + virtual bool write(const void* data, size_t size, size_t seekOffset = 0) override { + if (_fileHandler == nullptr) { + logError("Error writing file: file handler is not set\n"); + return false; + } + + if (!_fileHandler->seek(_offset + seekOffset)) { + logError("Error writing file: failed to seek to offset %u\n", _offset + seekOffset); + return false; + } + + if (!_fileHandler->write(data, size)) { + logError("Error writing file: failed to write data\n"); + return false; + } + + return true; + } + + virtual bool read(void* data, size_t size, size_t seekOffset = 0) const override { + if (_fileHandler == nullptr) { + logError("Error reading file: file handler is not set\n"); + return false; + } + + if (!_fileHandler->seek(_offset + seekOffset)) { + logError("Error reading file: failed to seek to offset %u\n", _offset + seekOffset); + return false; + } + + if (!_fileHandler->read(data, size)) { + return false; + } + + return true; + } +}; \ No newline at end of file diff --git a/src/core/file/Lsf12eFileHandler.h b/src/core/file/Lsf12eFileHandler.h new file mode 100644 index 0000000..e018cfc --- /dev/null +++ b/src/core/file/Lsf12eFileHandler.h @@ -0,0 +1,111 @@ +#pragma once + +#include "IFileHandler.h" +#include "libs/debug_lib.h" + +#include "LittleFS.h" + +class Lsf12eFileHandler : public IFileHandler { + File _file; + + Lsf12eFileHandler(const Lsf12eFileHandler&) = delete; + Lsf12eFileHandler& operator= (const Lsf12eFileHandler&) = delete; + Lsf12eFileHandler(Lsf12eFileHandler&& other) = delete; + Lsf12eFileHandler& operator= (Lsf12eFileHandler&& other) = delete; +public: + Lsf12eFileHandler() { + static bool isMounted = false; + if (!isMounted && LittleFS.begin()) { + isMounted = true; + } + } + + Lsf12eFileHandler(const char* path) : Lsf12eFileHandler() { + open(path); + } + + virtual ~Lsf12eFileHandler() { + close(); + } + + virtual void open(const char* path) override { + if (strlen(path) > 32) { + logError("filename is too long"); + return; + } + if (_file) { + logInfo("file is already open, closing it first"); + return; + } + + char buffer[33]; + strncpy(buffer + 1, path, 32); + buffer[0] = '/'; + + _file = LittleFS.open(buffer, "r+"); + if (!_file) { + _file = LittleFS.open(buffer, "w+"); + if (!_file) { + logError("failed to create file"); + } + } + } + + virtual void close() override { + if (_file) { + _file.close(); + } + } + + virtual bool write(const void* data, size_t size, size_t seekOffset = 0) override { + if (!_file) { + logError("file is not open for writing"); + return false; + } + + size_t written = _file.write(reinterpret_cast(data), size); + if (written != size) { + logError("failed to write all data"); + return false; + } + + _file.flush(); + + return true; + } + + virtual bool read(void* data, size_t size, size_t seekOffset = 0) const override { + if (!_file) { + logError("file is not open for reading"); + return false; + } + + size_t readBytes = const_cast(_file).read(reinterpret_cast(data), size); + if (readBytes != size) { + return false; + } + + return true; + } + + virtual bool seek(size_t position) override { + if (!_file) { + logError("file is not open for seeking"); + return false; + } + if (_file.position() == position) { + return true; + } + + return _file.seek(position, SeekSet); + } + + virtual size_t size() const override { + if (!_file) { + logError("file is not open to get size"); + return 0; + } + + return _file.size(); + } +}; \ No newline at end of file diff --git a/src/core/file/Lsf32FileHandler.h b/src/core/file/Lsf32FileHandler.h new file mode 100644 index 0000000..ac436ab --- /dev/null +++ b/src/core/file/Lsf32FileHandler.h @@ -0,0 +1,105 @@ +#pragma once + +#include "IFileHandler.h" +#include + +class Lsf32FileHandler : public IFileHandler { +/* FILE *_file = nullptr; + + Lsf32FileHandler(const Lsf32FileHandler&) = delete; + Lsf32FileHandler& operator= (const Lsf32FileHandler&) = delete; +public: + Lsf32FileHandler() = default; + virtual ~Lsf32FileHandler() { + close(); + }; + + Lsf32FileHandler(Lsf32FileHandler&& other) { + _file = other._file; + other._file = nullptr; + } + + Lsf32FileHandler& operator= (Lsf32FileHandler&& other) { + _file = other._file; + other._file = nullptr; + return *this; + } + + virtual void open(const char* path) override { + if (strlen(path) > 32) { + logError("Error opening file: filename is too long\n"); + return; + } + logError("called open with path: %s\n", path); + if ((_file = fopen(path, "r+")) == nullptr) { + logError("file not opened\n"); + if ((_file = fopen(path, "w+")) == nullptr) { + logError("Error opening file: %s, failed to create file\n", path); + } + } else { + logError("file opened successfully\n"); + } + } + + virtual void close() override { + if (_file != nullptr) { + fclose(_file); + _file = nullptr; + } + } + + virtual bool write(const void* data, size_t size) override { + if (_file == nullptr) { + logError("Error writing file: file is not open\n"); + return false; + } + + if (fwrite(data, size, 1, _file) != 1) { + logError("Error writing file: failed to write data\n"); + return false; + } + + return true; + } + + virtual bool read(void* data, size_t size) const override { + if (!_file) { + logError("Error reading file: file is not open\n"); + return false; + } + + if (fread(data, size, 1, _file) != 1) { + return false; + } + + return true; + } + + virtual bool seek(size_t position) override { + if (_file == nullptr) { + logError("Error seeking file: file is not open\n"); + return false; + } + + if (fseek(_file, position, SEEK_SET) != 0) { + logError("Error seeking file: failed to seek to position %zu\n", position); + return false; + } + + return true; + } + + virtual size_t size() const override { + if (_file == nullptr) { + logError("Error getting file size: file is not open\n"); + return 0; + } + + long currentPos = ftell(_file); + fseek(_file, 0, SEEK_END); + long fileSize = ftell(_file); + fseek(_file, currentPos, SEEK_SET); + + return static_cast(fileSize); + }*/ +}; \ No newline at end of file diff --git a/src/core/file/LsfFileHandler.h b/src/core/file/LsfFileHandler.h new file mode 100644 index 0000000..18da2f9 --- /dev/null +++ b/src/core/file/LsfFileHandler.h @@ -0,0 +1,120 @@ +#pragma once + +#include "IFileHandler.h" +#include "libs/debug_lib.h" +#include + +#if defined(ESP32) +using namespace fs; +#endif + +class LsfFileHandler : public IFileHandler { + File _file; + + LsfFileHandler(const LsfFileHandler&) = delete; + LsfFileHandler& operator=(const LsfFileHandler&) = delete; + LsfFileHandler(LsfFileHandler&&) = delete; + LsfFileHandler& operator=(LsfFileHandler&&) = delete; +public: + LsfFileHandler() { + static bool isMounted = false; + if (!isMounted) { +#if defined(ESP8266) + isMounted = LittleFS.begin(); +#elif defined(ESP32) + // Сначала без форматирования, при неудаче — с форматированием (пустой/битый раздел). + isMounted = LittleFS.begin(false) || LittleFS.begin(true); +#else +# error "LsfFileHandler: unsupported platform" +#endif + } + } + + LsfFileHandler(const char* path) : LsfFileHandler() { + open(path); + } + + virtual ~LsfFileHandler() { + close(); + } + + virtual void open(const char* path) override { + if (strlen(path) > 32) { + logError("Error opening file: filename is too long\n"); + return; + } + if (_file) { + logError("file is already open, closing it first\n"); + close(); + } + + char buffer[34]; + buffer[0] = '/'; + strncpy(buffer + 1, path, 32); + buffer[33] = '\0'; + + _file = LittleFS.open(buffer, "r+"); + if (!_file) { + _file = LittleFS.open(buffer, "w+"); + if (!_file) { + logError("Error opening file: %s, failed to create file\n", path); + } + } + } + + virtual void close() override { + if (_file) { + _file.close(); + } + } + + virtual bool write(const void* data, size_t size, size_t seekOffset = 0) override { + if (!_file) { + logError("Error writing file: file is not open\n"); + return false; + } + if (seekOffset != 0 && !_file.seek(seekOffset, SeekSet)) { + logError("Error writing file: failed to seek to offset %zu\n", seekOffset); + return false; + } + size_t written = _file.write(reinterpret_cast(data), size); + if (written != size) { + logError("Error writing file: failed to write data\n"); + return false; + } + _file.flush(); + return true; + } + + virtual bool read(void* data, size_t size, size_t seekOffset = 0) const override { + if (!_file) { + logError("Error reading file: file is not open\n"); + return false; + } + if (seekOffset != 0 && !const_cast(_file).seek(seekOffset, SeekSet)) { + logError("Error reading file: failed to seek to offset %zu\n", seekOffset); + return false; + } + size_t readBytes = const_cast(_file).read(reinterpret_cast(data), size); + if (readBytes != size) { + return false; + } + return true; + } + + virtual bool seek(size_t position) override { + if (!_file) { + logError("Error seeking file: file is not open\n"); + return false; + } + return _file.seek(position, SeekSet); + } + + virtual size_t size() const override { + if (!_file) { + logError("Error getting file size: file is not open\n"); + return 0; + } + return _file.size(); + } +}; diff --git a/src/effect_list/effectslist.cpp b/src/effect_list/effectslist.cpp deleted file mode 100644 index c631aec..0000000 --- a/src/effect_list/effectslist.cpp +++ /dev/null @@ -1,215 +0,0 @@ -#include "effectslist.h" -#include "erroreffect.h" - -#include "effects/slow_random.h" -#include "effects/simple_rainbow.h" -#include "effects/dribs.h" -#include "effects/rain.h" -#include "effects/all_random.h" -#include "effects/snow.h" -#include "effects/fire.h" -#include "effects/the_matrix.h" -#include "effects/simple_balls.h" -#include "effects/confetti.h" -#include "effects/starfall.h" -#include "effects/dynamic_square.h" -#include "effects/random_rain.h" -#include "effects/rainbow_rain.h" -#include "effects/points.h" -#include "effects/rainbow_point.h" -#include "effects/rainbow_static_point.h" -#include "effects/text.h" -#include "effects/mouse.h" -#include "effects/pacman.h" -#include "effects/circular_point.h" -#include "effects/zigzag.h" -#include "effects/horizontal_rainbow_point.h" -#include "effects/ny2020.h" -#include "effects/dribs_all_side.h" -#include "effects/snake/snake.h" -#include "effects/radial_fire.h" -#include "effects/radial_pattern.h" -#include "effects/crazy_bees.h" - -using EffectFactory = Effect* (*)(); - -template -Effect *makeEffect() { - return new T(); -} - -template -constexpr EffectFactory effectFactory() { - return makeEffect; -} - -auto effectsFactories = { - effectFactory(), - effectFactory(), - effectFactory(), - effectFactory(), - effectFactory(), - effectFactory(), - effectFactory(), - effectFactory(), - effectFactory(), - effectFactory(), - effectFactory(), - effectFactory(), - effectFactory(), - effectFactory(), - effectFactory(), - effectFactory(), - effectFactory(), - effectFactory(), - - effectFactory(), - effectFactory(), - effectFactory(), - effectFactory(), - effectFactory(), - effectFactory(), - effectFactory(), - effectFactory(), - effectFactory(), - effectFactory(), - effectFactory(), -}; - -static Effect *getNewEffectInstance(const uint8_t& num) { - if (num >= effectsFactories.size()) { - return nullptr; - } else { - return effectsFactories.begin()[num](); - } -} - -// private - -EffectsList::EffectsList() { - curEffect = nullptr; - Observable::subscribe(EventType::ChangeMode, this); -}; - -EffectsList::~EffectsList() { - Observable::unsubscribe(EventType::ChangeMode, this); -}; - -Effect *EffectsList::getCurEffect() const { - return curEffect; -} - -// public -EffectsList& EffectsList::getInstance() { - static EffectsList instance; - return instance; -} - -void EffectsList::setErrorEffect() { - clearCurEffect(); - curEffect = new ErrorEffect(); - curNum = -1; -} - -uint8_t EffectsList::getCurEffectNum() const { - return curNum; -} - -void EffectsList::clearCurEffect() { - if (getCurEffect()) { - delete curEffect; - curEffect = nullptr; - curNum = -1; - } -} - -void EffectsList::setEffect(const uint8_t &num) { - if (num > effectsFactories.size()) { - out("ERROR: Effect number out of range\n"); - setErrorEffect(); - return; - } - - clearCurEffect(); - curEffect = getNewEffectInstance(num); - - if (!curEffect) { - out("ERROR: Effect not crated\n"); - setErrorEffect(); - return; - } - - curEffect->on_clear(); - curEffect->on_init(); - curNum = num; -} - -void EffectsList::nextEffect() { - if (curNum + 1 >= (uint8_t)effectsFactories.size()) { - setEffect(0); - } else { - setEffect(curNum + 1); - } -} - -void EffectsList::prevEffect() { - if (curNum - 1 < 0) { - setEffect(effectsFactories.size() - 1); - } else { - setEffect(curNum - 1); - } -} - -//перезапустить текущий эффект -void EffectsList::reloadCurEff() { - setEffect(curNum); -} - -void EffectsList::onTick() { - if (curEffect == nullptr) - return; - unsigned long tick_size = 1000000 / curEffect->get_fps(); - - // проверяем соответствие фпса указанному в режиме - if (micros() - prev_micros > tick_size) { - curEffect->on_update(); - curEffect->on_render(); - - // корректировка таймера - // если фпс меньше чем указано в режиме - if (micros() - prev_micros > tick_size * 2) { - // считаем реальный фпс - fps = (fps + 1000000 * 10 / (micros() - prev_micros - tick_size)) / 2; - // следующей итерацией обязательно вызываем on_update + корректируем время - // чтобы prev_micros сильно не уходило от текущего времени - prev_micros = micros() - tick_size; - } else { - // иначе просто прибавляем размер тика в микросекундах - prev_micros += tick_size; - // считаем реальный фпс - fps = (fps + curEffect->get_fps() * 10) / 2; - } - FastLED.show(); - } -} - -float EffectsList::getCurFPS() { - return (float)fps / 10; -} - -bool EffectsList::effectIsEnd() { - return curEffect->is_end(); -} - -void EffectsList::handleEvent(Event *event) { - if (event->type == EventType::ChangeMode) { - ChangeModEvent *ev = static_cast(event); - if (ev->type == ChangeModEvent::Type::Next) { - nextEffect(); - } else if (ev->type == ChangeModEvent::Type::Previous) { - prevEffect(); - } else if (ev->type == ChangeModEvent::Type::Set) { - setEffect(ev->id); - } - } -} \ No newline at end of file diff --git a/src/effect_list/effectslist.h b/src/effect_list/effectslist.h deleted file mode 100644 index a773a03..0000000 --- a/src/effect_list/effectslist.h +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once -#include -#include "events/observer.h" - -class Effect; - -class EffectsList : public IObserver -{ -private: - uint8_t curNum = 0; - Effect *curEffect; - unsigned long prev_micros; - unsigned long fps; - - // singlton property - // Конструкторы и оператор присваивания недоступны клиентам - EffectsList(); - ~EffectsList(); - EffectsList(const EffectsList& ); - EffectsList& operator=(EffectsList& ); - Effect *getCurEffect() const; - void clearCurEffect(); - -public: - static EffectsList& getInstance(); - void setErrorEffect(); - uint8_t getCurEffectNum() const; - void setEffect(const uint8_t &num); - void nextEffect(); - void prevEffect(); - void reloadCurEff(); - void onTick(); - float getCurFPS(); - bool effectIsEnd(); - virtual void handleEvent(Event *event) override; -}; diff --git a/src/effect_list/effect.h b/src/effects/effect.h similarity index 97% rename from src/effect_list/effect.h rename to src/effects/effect.h index 2dbd2be..c963579 100644 --- a/src/effect_list/effect.h +++ b/src/effects/effect.h @@ -48,7 +48,7 @@ class Effect * однако скорость режима лучше регулировать кодом внутри режима. * По умолчанию 60. * */ - uint8_t get_fps() { + uint8_t get_fps() const { return _fps; } @@ -58,7 +58,7 @@ class Effect * нужно установить флаг в false. Нужно обязательно следить чтобы флаг устанавливался в true * и был таковым на протяжении 2 и более секунд. */ - bool is_end() { + virtual bool is_end() const { return _is_end; } }; diff --git a/src/effect_list/effects/all_random.h b/src/effects/effects_impl/all_random.h similarity index 90% rename from src/effect_list/effects/all_random.h rename to src/effects/effects_impl/all_random.h index ba4eb33..c2f7301 100644 --- a/src/effect_list/effects/all_random.h +++ b/src/effects/effects_impl/all_random.h @@ -1,6 +1,6 @@ #pragma once -#include "effect_list/effect.h" +#include "effects/effect.h" class AllRandom : public Effect { diff --git a/src/effect_list/effects/circular_point.h b/src/effects/effects_impl/circular_point.h similarity index 98% rename from src/effect_list/effects/circular_point.h rename to src/effects/effects_impl/circular_point.h index 9ca7f07..b1cc08f 100644 --- a/src/effect_list/effects/circular_point.h +++ b/src/effects/effects_impl/circular_point.h @@ -1,6 +1,6 @@ #pragma once -#include "effect_list/effect.h" +#include "effects/effect.h" #define ACCURACY 10 diff --git a/src/effect_list/effects/confetti.h b/src/effects/effects_impl/confetti.h similarity index 94% rename from src/effect_list/effects/confetti.h rename to src/effects/effects_impl/confetti.h index 5368bd3..b4d1fa3 100644 --- a/src/effect_list/effects/confetti.h +++ b/src/effects/effects_impl/confetti.h @@ -1,6 +1,6 @@ #pragma once -#include "effect_list/effect.h" +#include "effects/effect.h" class Confetti : public Effect { diff --git a/src/effect_list/effects/crazy_bees.h b/src/effects/effects_impl/crazy_bees.h similarity index 79% rename from src/effect_list/effects/crazy_bees.h rename to src/effects/effects_impl/crazy_bees.h index 5102708..729d97e 100644 --- a/src/effect_list/effects/crazy_bees.h +++ b/src/effects/effects_impl/crazy_bees.h @@ -1,6 +1,6 @@ #pragma once // Source: https://editor.soulmatelights.com/gallery/651-crazy-bees -#include "effect_list/effect.h" +#include "effects/effect.h" class Bee { uint8_t posX, posY, aimX, aimY, hue; @@ -27,10 +27,12 @@ class Bee { void run() { - LedMatrix.at(aimX + 1, aimY) += CHSV(hue, 255, 255); - LedMatrix.at(aimX, aimY + 1) += CHSV(hue, 255, 255); - LedMatrix.at(aimX - 1, aimY) += CHSV(hue, 255, 255); - LedMatrix.at(aimX, aimY - 1) += CHSV(hue, 255, 255); + const index_t w = LedMatrix.width(); + const index_t h = LedMatrix.height(); + if (aimX + 1 < w) LedMatrix.at(aimX + 1, aimY) += CHSV(hue, 255, 255); + if (aimY + 1 < h) LedMatrix.at(aimX, aimY + 1) += CHSV(hue, 255, 255); + if (aimX > 0) LedMatrix.at(aimX - 1, aimY) += CHSV(hue, 255, 255); + if (aimY > 0) LedMatrix.at(aimX, aimY - 1) += CHSV(hue, 255, 255); if (posX != aimX || posY != aimY) { LedMatrix.at(posX, posY) = CHSV(hue, 60, 255); int8_t error2 = error * 2; diff --git a/src/effect_list/effects/dribs.h b/src/effects/effects_impl/dribs.h similarity index 96% rename from src/effect_list/effects/dribs.h rename to src/effects/effects_impl/dribs.h index 212f86f..b6da72f 100644 --- a/src/effect_list/effects/dribs.h +++ b/src/effects/effects_impl/dribs.h @@ -1,6 +1,6 @@ #pragma once -#include "effect_list/effect.h" +#include "effects/effect.h" class Dribs : public Effect { diff --git a/src/effect_list/effects/dribs_all_side.h b/src/effects/effects_impl/dribs_all_side.h similarity index 98% rename from src/effect_list/effects/dribs_all_side.h rename to src/effects/effects_impl/dribs_all_side.h index 49c6920..0589f12 100644 --- a/src/effect_list/effects/dribs_all_side.h +++ b/src/effects/effects_impl/dribs_all_side.h @@ -1,6 +1,6 @@ #pragma once -#include "effect_list/effect.h" +#include "effects/effect.h" #include "libs/coord.h" #define MAX_SNAKE 10 diff --git a/src/effect_list/effects/dynamic_square.h b/src/effects/effects_impl/dynamic_square.h similarity index 95% rename from src/effect_list/effects/dynamic_square.h rename to src/effects/effects_impl/dynamic_square.h index 6d95bd9..653af6e 100644 --- a/src/effect_list/effects/dynamic_square.h +++ b/src/effects/effects_impl/dynamic_square.h @@ -1,6 +1,6 @@ #pragma once -#include "effect_list/effect.h" +#include "effects/effect.h" class DynamicSquare : public Effect { diff --git a/src/effect_list/effects/fire.h b/src/effects/effects_impl/fire.h similarity index 99% rename from src/effect_list/effects/fire.h rename to src/effects/effects_impl/fire.h index a0b31bd..5b05a4b 100644 --- a/src/effect_list/effects/fire.h +++ b/src/effects/effects_impl/fire.h @@ -1,6 +1,6 @@ #pragma once -#include "effect_list/effect.h" +#include "effects/effect.h" class Fire : public Effect { diff --git a/src/effect_list/effects/horizontal_rainbow_point.h b/src/effects/effects_impl/horizontal_rainbow_point.h similarity index 98% rename from src/effect_list/effects/horizontal_rainbow_point.h rename to src/effects/effects_impl/horizontal_rainbow_point.h index cd40521..c488f03 100644 --- a/src/effect_list/effects/horizontal_rainbow_point.h +++ b/src/effects/effects_impl/horizontal_rainbow_point.h @@ -1,6 +1,6 @@ #pragma once -#include "effect_list/effect.h" +#include "effects/effect.h" #define ACCURACY 100 diff --git a/src/effect_list/effects/mouse.cpp b/src/effects/effects_impl/mouse.cpp similarity index 100% rename from src/effect_list/effects/mouse.cpp rename to src/effects/effects_impl/mouse.cpp diff --git a/src/effect_list/effects/mouse.h b/src/effects/effects_impl/mouse.h similarity index 80% rename from src/effect_list/effects/mouse.h rename to src/effects/effects_impl/mouse.h index 1caac78..fe9ffe6 100644 --- a/src/effect_list/effects/mouse.h +++ b/src/effects/effects_impl/mouse.h @@ -1,6 +1,6 @@ #pragma once -#include "effect_list/effect.h" +#include "effects/effect.h" class Mouse : public Effect { diff --git a/src/effect_list/effects/ny2020.h b/src/effects/effects_impl/ny2020.cpp similarity index 80% rename from src/effect_list/effects/ny2020.h rename to src/effects/effects_impl/ny2020.cpp index 79081f9..982da4b 100644 --- a/src/effect_list/effects/ny2020.h +++ b/src/effects/effects_impl/ny2020.cpp @@ -1,9 +1,6 @@ -#pragma once +#include "ny2020.h" -#include "effect_list/effect.h" - -#define NY_SPRITE_W 10 -#define NY_SPRITE_H 10 +#include "effects/effect.h" #define R 0xff0000 #define DR 0x400000 @@ -19,7 +16,7 @@ #define O 0xff8000 #define P 0xffc0c0 -static const uint32_t sprite1[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { +static constexpr uint32_t sprite1[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { 0, 0, 0, 0, W, 0, DR, DR, 0, 0, 0, 0, 0, W, DB, W, DR, DR, 0, 0, 0, 0, W, DB, DB, DB, W, DR, 0, 0, @@ -32,7 +29,7 @@ static const uint32_t sprite1[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { W, W, W, W, W, Y, Y, W, W, W, }; -static const uint32_t sprite2[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { +static constexpr uint32_t sprite2[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { 0, 0, 0, 0, W, 0, 0, 0, 0, 0, 0, 0, 0, W, DB, W, 0, 0, 0, 0, 0, 0, W, DB, DB, DB, W, 0, 0, 0, @@ -45,7 +42,7 @@ static const uint32_t sprite2[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { W, W, W, W, W, W, W, W, W, W, }; -static const uint32_t sprite3[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { +static constexpr uint32_t sprite3[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, G, 0, 0, 0, 0, 0, 0, 0, 0, G, G, G, 0, 0, 0, 0, @@ -58,7 +55,7 @@ static const uint32_t sprite3[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { 0, 0, 0, 0, G, 0, 0, 0, 0, 0, }; -static const uint32_t sprite4[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { +static constexpr uint32_t sprite4[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, G, 0, 0, 0, 0, 0, 0, 0, 0, G, G, G, 0, 0, 0, 0, @@ -71,7 +68,7 @@ static const uint32_t sprite4[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { 0, 0, 0, 0, G, 0, 0, 0, 0, 0, }; -static const uint32_t sprite5[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { +static constexpr uint32_t sprite5[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, W, 0, 0, 0, 0, 0, 0, 0, 0, W, G, W, 0, 0, 0, 0, @@ -84,7 +81,7 @@ static const uint32_t sprite5[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { 0, 0, 0, G, G, G, 0, 0, 0, 0, }; -static const uint32_t sprite6[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { +static constexpr uint32_t sprite6[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, Y, 0, 0, 0, 0, 0, 0, 0, 0, G, G, G, 0, 0, 0, 0, @@ -97,7 +94,7 @@ static const uint32_t sprite6[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { 0, 0, 0, G, G, G, 0, 0, 0, 0, }; -static const uint32_t sprite7[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { +static constexpr uint32_t sprite7[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, C, 0, 0, 0, 0, 0, 0, 0, C, 0, C, 0, C, 0, 0, 0, @@ -110,7 +107,7 @@ static const uint32_t sprite7[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { 0, 0, 0, 0, C, 0, 0, 0, 0, 0, }; -static const uint32_t sprite8[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { +static constexpr uint32_t sprite8[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, R, 0, 0, 0, 0, 0, R, 0, 0, R, 0, R, 0, 0, 0, R, 0, R, 0, @@ -123,7 +120,7 @@ static const uint32_t sprite8[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { DG, DG, DG, DG, R, DG, DG, DG, DG, 0, }; -static const uint32_t sprite9[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { +static constexpr uint32_t sprite9[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, BR, 0, 0, 0, 0, 0, 0, 0, 0, BR, BR, 0, 0, 0, 0, 0, 0, @@ -136,7 +133,7 @@ static const uint32_t sprite9[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { 0, 0, 0, BR, 0, BR, 0, BR, 0, 0, }; -static const uint32_t sprite10[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { +static constexpr uint32_t sprite10[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { 0, 0, 0, 0, 0, R, R, R, 0, 0, 0, 0, 0, 0, R, R, 0, R, 0, 0, 0, 0, 0, R, R, R, 0, W, W, 0, @@ -149,7 +146,7 @@ static const uint32_t sprite10[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { W, W, W, W, W, W, W, W, W, 0, }; -static const uint32_t sprite11[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { +static constexpr uint32_t sprite11[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { 0, 0, 0, BR, BR, BR, 0, 0, 0, 0, 0, BR, BR, BR, BR, BR, BR, BR, 0, 0, 0, 0, W, W, W, W, W, 0, 0, 0, @@ -162,7 +159,7 @@ static const uint32_t sprite11[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { 0, 0, W, W, W, W, W, 0, 0, 0, }; -static const uint32_t sprite12[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { +static constexpr uint32_t sprite12[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, R, R, R, R, R, R, R, R, 0, 0, W, W, W, W, W, W, 0, R, 0, @@ -175,7 +172,7 @@ static const uint32_t sprite12[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { 0, R, R, R, R, R, R, 0, 0, 0, }; -static const uint32_t sprite13[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { +static constexpr uint32_t sprite13[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { 0, 0, 0, 0, BR, BR, 0, 0, 0, 0, 0, 0, 0, 0, G, G, 0, 0, 0, 0, 0, 0, 0, 0, G, G, 0, 0, 0, 0, @@ -188,7 +185,7 @@ static const uint32_t sprite13[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { 0, 0, G, G, G, G, G, G, 0, 0, }; -static const uint32_t sprite14[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { +static constexpr uint32_t sprite14[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { 0, 0, 0, 0, 0, R, R, R, 0, 0, 0, 0, 0, 0, R, R, R, 0, R, 0, 0, 0, 0, 0, P, P, P, 0, W, W, @@ -214,7 +211,7 @@ static const uint32_t sprite15[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { 0, 0, 0, G, R, G, 0, 0, 0, 0, }; -static const uint32_t sprite16[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { +static constexpr uint32_t sprite16[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { 0, 0, 0, Y, 0, 0, 0, 0, 0, 0, 0, 0, Y, Y, 0, 0, 0, 0, 0, 0, 0, 0, Y, 0, 0, 0, 0, 0, 0, 0, @@ -227,7 +224,7 @@ static const uint32_t sprite16[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { 0, 0, LB, LB, LB, LB, 0, 0, 0, 0, }; -static const uint32_t sprite17[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { +static constexpr uint32_t sprite17[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { 0, 0, G, G, R, G, G, 0, 0, 0, 0, 0, G, R, R, R, G, 0, 0, 0, 0, 0, 0, W, W, W, 0, 0, 0, 0, @@ -240,7 +237,7 @@ static const uint32_t sprite17[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { BR, BR, BR, BR, BR, BR, BR, BR, BR, 0, }; -static const uint32_t sprite18[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { +static constexpr uint32_t sprite18[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { 0, 0, 0, 0, 0, W, W, R, 0, 0, 0, 0, 0, R, W, W, R, R, W, 0, 0, 0, 0, R, R, 0, 0, W, W, 0, @@ -253,7 +250,7 @@ static const uint32_t sprite18[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { 0, W, W, 0, 0, 0, 0, 0, 0, 0, }; -static const uint32_t sprite19[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { +static constexpr uint32_t sprite19[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { 0, 0, W, W, W, W, W, W, W, 0, 0, 0, W, W, W, W, W, W, W, 0, 0, 0, 0, R, R, R, R, R, 0, 0, @@ -280,8 +277,6 @@ static const uint32_t sprite19[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { #undef O #undef P -#define NY_COUNT (LEDS_WIDTH / NY_SPRITE_W + 2) - static const uint32_t * const sprites[] = { sprite1, sprite2, sprite3, sprite4, sprite5, sprite6, sprite7, @@ -291,40 +286,30 @@ static const uint32_t * const sprites[] = #define NY_TYPES (sizeof(sprites) / sizeof(sprites[0])) -class NY2020 : public Effect +void NY2020::on_init() { -public: - NY2020() {} + phase = 0; + for (int i = 0 ; i < NY_COUNT ; ++i) { + items[i] = random8(NY_TYPES); + } - void on_init() - { - phase = 0; - for (int i = 0 ; i < NY_COUNT ; ++i) { - items[i] = random8(NY_TYPES); - } + set_fps(10); +} - set_fps(10); +void NY2020::on_update() +{ + FastLED.clear(); + for (int i = 0 ; i < NY_COUNT ; ++i) { + int x = i * (NY_SPRITE_W + 1) - phase; + int y = (LEDS_HEIGHT - NY_SPRITE_H) / 2; + LedMatrix.drawSprite(x, y, sprites[items[i]]); } - - void on_update() + phase = (phase + 1) % (NY_SPRITE_W + 1); + if (phase == 0) { - FastLED.clear(); - for (int i = 0 ; i < NY_COUNT ; ++i) { - int x = i * (NY_SPRITE_W + 1) - phase; - int y = (LEDS_HEIGHT - NY_SPRITE_H) / 2; - LedMatrix.drawSprite(x, y, sprites[items[i]]); - } - phase = (phase + 1) % (NY_SPRITE_W + 1); - if (phase == 0) - { - for (int i = 1 ; i < NY_COUNT ; ++i) { - items[i - 1] = items[i]; - } - items[NY_COUNT - 1] = random8(NY_TYPES); + for (int i = 1 ; i < NY_COUNT ; ++i) { + items[i - 1] = items[i]; } + items[NY_COUNT - 1] = random8(NY_TYPES); } - -private: - int phase; - int items[NY_COUNT]; -}; +} diff --git a/src/effects/effects_impl/ny2020.h b/src/effects/effects_impl/ny2020.h new file mode 100644 index 0000000..a524693 --- /dev/null +++ b/src/effects/effects_impl/ny2020.h @@ -0,0 +1,21 @@ +#pragma once + +#include "effects/effect.h" + +#define NY_SPRITE_W 10 +#define NY_SPRITE_H 10 + +#define NY_COUNT (LEDS_WIDTH / NY_SPRITE_W + 2) + +class NY2020 : public Effect +{ +public: + NY2020() {} + + void on_init(); + void on_update(); + +private: + int phase; + int items[NY_COUNT]; +}; diff --git a/src/effect_list/effects/pacman.h b/src/effects/effects_impl/pacman.h similarity index 99% rename from src/effect_list/effects/pacman.h rename to src/effects/effects_impl/pacman.h index 4bedda6..96ad8bf 100644 --- a/src/effect_list/effects/pacman.h +++ b/src/effects/effects_impl/pacman.h @@ -1,6 +1,6 @@ #pragma once -#include "effect_list/effect.h" +#include "effects/effect.h" #define PACMAN_W 10 #define PACMAN_H 10 diff --git a/src/effect_list/effects/points.h b/src/effects/effects_impl/points.h similarity index 99% rename from src/effect_list/effects/points.h rename to src/effects/effects_impl/points.h index 946b6c4..c0a633e 100644 --- a/src/effect_list/effects/points.h +++ b/src/effects/effects_impl/points.h @@ -1,6 +1,6 @@ #pragma once -#include "effect_list/effect.h" +#include "effects/effect.h" #define ACCURACY 100 #define MAX_VEC_SIZE 20 diff --git a/src/effects/effects_impl/pulse_rings.h b/src/effects/effects_impl/pulse_rings.h new file mode 100644 index 0000000..1c9a003 --- /dev/null +++ b/src/effects/effects_impl/pulse_rings.h @@ -0,0 +1,90 @@ +#pragma once + +#include "effects/effect.h" + +// Расходящиеся от центра кольца, которые затухают +class PulseRings : public Effect +{ + static constexpr uint8_t maxRings = 3; + static constexpr float expandSpeed = 0.35f; + static constexpr float fadeStart = 3.0f; // радиус, с которого начинается затухание + + struct Ring { + float radius = 0; + uint8_t hue = 0; + bool active = false; + }; + Ring _rings[maxRings]; + uint8_t _hueOffset = 0; + + static float dist(float cx, float cy, index_t x, index_t y) { + float dx = (float)x - cx; + float dy = (float)y - cy; + return sqrtf(dx * dx + dy * dy); + } + +public: + PulseRings() = default; + + void on_init() override { + set_fps(35); + _hueOffset = random8(255); + for (uint8_t i = 0; i < maxRings; i++) { + _rings[i].active = false; + _rings[i].radius = 0; + } + } + + void on_update() override { + LedMatrix.fader(25); + + const index_t w = LedMatrix.width(); + const index_t h = LedMatrix.height(); + const float cx = (w - 1) / 2.0f; + const float cy = (h - 1) / 2.0f; + const float maxR = dist(cx, cy, 0, 0) + 2.0f; + + // Спавн нового кольца, если есть свободный слот + for (uint8_t i = 0; i < maxRings; i++) { + if (!_rings[i].active) { + _rings[i].active = true; + _rings[i].radius = 0.5f; + _rings[i].hue = _hueOffset; + _hueOffset += 30; + break; + } + } + + for (index_t y = 0; y < h; y++) { + for (index_t x = 0; x < w; x++) { + float d = dist(cx, cy, x, y); + + for (uint8_t i = 0; i < maxRings; i++) { + if (!_rings[i].active) continue; + + float r = _rings[i].radius; + float diff = fabsf(d - r); + if (diff < 0.8f) { + uint8_t bright = 200; + if (r > fadeStart) { + bright = (uint8_t)(bright * (maxR - r) / (maxR - fadeStart)); + } + auto& pix = LedMatrix.at(x, y); + CRGB add = CHSV(_rings[i].hue, 255, bright); + pix.r = qadd8(pix.r, add.r); + pix.g = qadd8(pix.g, add.g); + pix.b = qadd8(pix.b, add.b); + } + } + } + } + + for (uint8_t i = 0; i < maxRings; i++) { + if (!_rings[i].active) continue; + _rings[i].radius += expandSpeed; + if (_rings[i].radius >= maxR) { + _rings[i].active = false; + } + } + } +}; diff --git a/src/effect_list/effects/radial_fire.h b/src/effects/effects_impl/radial_fire.h similarity index 98% rename from src/effect_list/effects/radial_fire.h rename to src/effects/effects_impl/radial_fire.h index a401125..71e0bea 100644 --- a/src/effect_list/effects/radial_fire.h +++ b/src/effects/effects_impl/radial_fire.h @@ -1,7 +1,7 @@ #pragma once // Source: https://editor.soulmatelights.com/gallery/1570-radialfire // This variant: https://editor.soulmatelights.com/gallery/2777-radial-fire -#include "effect_list/effect.h" +#include "effects/effect.h" #define C_X (LEDS_WIDTH / 2) #define C_Y (LEDS_HEIGHT / 2) diff --git a/src/effect_list/effects/radial_pattern.h b/src/effects/effects_impl/radial_pattern.h similarity index 97% rename from src/effect_list/effects/radial_pattern.h rename to src/effects/effects_impl/radial_pattern.h index 38483bc..0e241be 100644 --- a/src/effect_list/effects/radial_pattern.h +++ b/src/effects/effects_impl/radial_pattern.h @@ -1,7 +1,7 @@ #pragma once // Source: https://editor.soulmatelights.com/gallery/1586-radial-pattern // This variant: https://editor.soulmatelights.com/gallery/2778-radialpattern -#include "effect_list/effect.h" +#include "effects/effect.h" #define C_X (LEDS_WIDTH / 2) #define C_Y (LEDS_HEIGHT / 2) diff --git a/src/effect_list/effects/rain.h b/src/effects/effects_impl/rain.h similarity index 90% rename from src/effect_list/effects/rain.h rename to src/effects/effects_impl/rain.h index 0d3162b..d2d5469 100644 --- a/src/effect_list/effects/rain.h +++ b/src/effects/effects_impl/rain.h @@ -1,10 +1,9 @@ #pragma once -#include "effect_list/effect.h" - +#include "effects/effect.h" class Rain : public Effect { - uint8_t step = 2; + uint8_t step{2}; public: Rain() {} diff --git a/src/effect_list/effects/rainbow_point.h b/src/effects/effects_impl/rainbow_point.h similarity index 98% rename from src/effect_list/effects/rainbow_point.h rename to src/effects/effects_impl/rainbow_point.h index 18f1d0c..c7697fe 100644 --- a/src/effect_list/effects/rainbow_point.h +++ b/src/effects/effects_impl/rainbow_point.h @@ -2,7 +2,7 @@ #define ACCURACY 100 -#include "effect_list/effect.h" +#include "effects/effect.h" class RainbowPoint : public Effect { diff --git a/src/effect_list/effects/rainbow_rain.h b/src/effects/effects_impl/rainbow_rain.h similarity index 95% rename from src/effect_list/effects/rainbow_rain.h rename to src/effects/effects_impl/rainbow_rain.h index 48c93c9..0fa9589 100644 --- a/src/effect_list/effects/rainbow_rain.h +++ b/src/effects/effects_impl/rainbow_rain.h @@ -1,6 +1,6 @@ #pragma once -#include "effect_list/effect.h" +#include "effects/effect.h" class RainbowRain : public Effect { diff --git a/src/effect_list/effects/rainbow_static_point.h b/src/effects/effects_impl/rainbow_static_point.h similarity index 97% rename from src/effect_list/effects/rainbow_static_point.h rename to src/effects/effects_impl/rainbow_static_point.h index 70b2569..63590d4 100644 --- a/src/effect_list/effects/rainbow_static_point.h +++ b/src/effects/effects_impl/rainbow_static_point.h @@ -2,7 +2,7 @@ #define ACCURACY 10 -#include "effect_list/effect.h" +#include "effects/effect.h" class RainbowStaticPoint : public Effect { diff --git a/src/effect_list/effects/random_rain.h b/src/effects/effects_impl/random_rain.h similarity index 94% rename from src/effect_list/effects/random_rain.h rename to src/effects/effects_impl/random_rain.h index 3c85762..03e597f 100644 --- a/src/effect_list/effects/random_rain.h +++ b/src/effects/effects_impl/random_rain.h @@ -1,6 +1,6 @@ #pragma once -#include "effect_list/effect.h" +#include "effects/effect.h" class RandomRain : public Effect { diff --git a/src/effect_list/effects/simple_balls.h b/src/effects/effects_impl/simple_balls.h similarity index 98% rename from src/effect_list/effects/simple_balls.h rename to src/effects/effects_impl/simple_balls.h index f6666b4..24ce5de 100644 --- a/src/effect_list/effects/simple_balls.h +++ b/src/effects/effects_impl/simple_balls.h @@ -1,6 +1,6 @@ #pragma once -#include "effect_list/effect.h" +#include "effects/effect.h" class SimpleBalls : public Effect { diff --git a/src/effect_list/effects/simple_rainbow.h b/src/effects/effects_impl/simple_rainbow.h similarity index 94% rename from src/effect_list/effects/simple_rainbow.h rename to src/effects/effects_impl/simple_rainbow.h index 4924c0f..ef02724 100644 --- a/src/effect_list/effects/simple_rainbow.h +++ b/src/effects/effects_impl/simple_rainbow.h @@ -1,6 +1,6 @@ #pragma once -#include "effect_list/effect.h" +#include "effects/effect.h" class SimpleRainbow : public Effect { diff --git a/src/effect_list/effects/slow_random.h b/src/effects/effects_impl/slow_random.h similarity index 98% rename from src/effect_list/effects/slow_random.h rename to src/effects/effects_impl/slow_random.h index 48b975e..4141526 100644 --- a/src/effect_list/effects/slow_random.h +++ b/src/effects/effects_impl/slow_random.h @@ -1,6 +1,6 @@ #pragma once -#include "effect_list/effect.h" +#include "effects/effect.h" class SlowRandom : public Effect { diff --git a/src/effect_list/effects/snake/a_star_ai.h b/src/effects/effects_impl/snake/a_star_ai.h similarity index 96% rename from src/effect_list/effects/snake/a_star_ai.h rename to src/effects/effects_impl/snake/a_star_ai.h index 89b1fad..f08d82c 100644 --- a/src/effect_list/effects/snake/a_star_ai.h +++ b/src/effects/effects_impl/snake/a_star_ai.h @@ -81,7 +81,7 @@ class AStarSnakeAI : public SnakeAI { template void printMas(Action &&action, const char *text) const { - out("------------------- %s -----------------\n", text); + logInfo("------------------- %s -----------------\n", text); for (uint8_t i = 0; i < LEDS_WIDTH; ++i) { for (uint8_t j = 0; j < LEDS_HEIGHT; ++j) { action(nodes[i][j]); @@ -92,7 +92,7 @@ class AStarSnakeAI : public SnakeAI { } void debug(Coord head, Coord apple) const { - out("head x: %d y: %d apple x: %d y: %d\n", head.x, head.y, apple.x, apple.y); + logInfo("head x: %d y: %d apple x: %d y: %d\n", head.x, head.y, apple.x, apple.y); printMas([](const Node &node) { out("%03d", node.cost); }, "cost"); //printMas([](const Node &node) { out("%03d", node.dist); }, "dist"); diff --git a/src/effect_list/effects/snake/simple_ai.h b/src/effects/effects_impl/snake/simple_ai.h similarity index 100% rename from src/effect_list/effects/snake/simple_ai.h rename to src/effects/effects_impl/snake/simple_ai.h diff --git a/src/effect_list/effects/snake/snake.h b/src/effects/effects_impl/snake/snake.h similarity index 89% rename from src/effect_list/effects/snake/snake.h rename to src/effects/effects_impl/snake/snake.h index 0fd1863..3af0171 100644 --- a/src/effect_list/effects/snake/snake.h +++ b/src/effects/effects_impl/snake/snake.h @@ -1,7 +1,7 @@ #pragma once #include "libs/queue.h" -#include "effect_list/effect.h" +#include "effects/effect.h" #include "simple_ai.h" #include "a_star_ai.h" #include "with_fallback_ai.h" @@ -14,17 +14,18 @@ class Snake : public Effect bool apple_flag, end_game; uint8_t aiType = 3; SnakeAI *ai; + uint64_t startTime; uint8_t tick, step = 3; // Если нужно, можно вызвать этот метод. Выводит отладочную информацию в терминал void debug() { - out("vector %d\n", vector); - out("head.x = %d head.y = %d\n", head.x, head.y); - out("butt.x = %d butt.y = %d\n", butt.x, butt.y); - out("apple.x = %d apple.y = %d\n\n", apple.x, apple.y); + logInfo("vector %d\n", vector); + logInfo("head.x = %d head.y = %d\n", head.x, head.y); + logInfo("butt.x = %d butt.y = %d\n", butt.x, butt.y); + logInfo("apple.x = %d apple.y = %d\n\n", apple.x, apple.y); snake.debug(); - out("---\n"); + logInfo("---\n"); } // Метод выполняется каждый тик. Тут вся логика @@ -86,7 +87,7 @@ class Snake : public Effect delay(100); } - out("End game: Score: %d\n", snake.size()); + logInfo("End game: Score: %d\n", snake.size()); //delay(100); //FastLED.clear(); @@ -149,6 +150,8 @@ class Snake : public Effect apple_flag = false; end_game = false; + + startTime = millis(); } SnakeAI* make_ai() { @@ -165,7 +168,7 @@ class Snake : public Effect } public: - void on_init() override { + virtual void on_init() override { set_fps(40); tick = 0; button = Trend::none; @@ -180,10 +183,14 @@ class Snake : public Effect } } - void on_update() override { + virtual void on_update() override { tick = (tick + 1) % step; if (!tick) { snakeRoutine(); } } + + virtual bool is_end() const override { + return millis() - startTime < 5000; // если с момента старта прошло меньше 5 секунд, то режим не переключится + } }; diff --git a/src/effect_list/effects/snake/snake_ai.h b/src/effects/effects_impl/snake/snake_ai.h similarity index 100% rename from src/effect_list/effects/snake/snake_ai.h rename to src/effects/effects_impl/snake/snake_ai.h diff --git a/src/effect_list/effects/snake/snake_constants.h b/src/effects/effects_impl/snake/snake_constants.h similarity index 100% rename from src/effect_list/effects/snake/snake_constants.h rename to src/effects/effects_impl/snake/snake_constants.h diff --git a/src/effect_list/effects/snake/with_fallback_ai.h b/src/effects/effects_impl/snake/with_fallback_ai.h similarity index 100% rename from src/effect_list/effects/snake/with_fallback_ai.h rename to src/effects/effects_impl/snake/with_fallback_ai.h diff --git a/src/effect_list/effects/snow.h b/src/effects/effects_impl/snow.h similarity index 98% rename from src/effect_list/effects/snow.h rename to src/effects/effects_impl/snow.h index 4aa1f3f..ca5f2fa 100644 --- a/src/effect_list/effects/snow.h +++ b/src/effects/effects_impl/snow.h @@ -1,6 +1,6 @@ #pragma once -#include "effect_list/effect.h" +#include "effects/effect.h" #include "libs/led_matrix.h" class Snow : public Effect diff --git a/src/effects/effects_impl/spiral.h b/src/effects/effects_impl/spiral.h new file mode 100644 index 0000000..baa4161 --- /dev/null +++ b/src/effects/effects_impl/spiral.h @@ -0,0 +1,50 @@ +#pragma once + +#include "effects/effect.h" + +// Вращающаяся спираль из центра с плавной радужной сменой цвета +class Spiral : public Effect +{ + static constexpr float angleStep = 0.12f; // скорость вращения + static constexpr float radiusStep = 0.4f; // шаг радиуса на виток (плотность спирали) + static constexpr uint8_t hueStep = 8; // шаг оттенка по длине спирали + + float _angle = 0; + uint8_t _hueOffset = 0; + +public: + Spiral() = default; + + void on_init() override { + set_fps(40); + _angle = 0; + _hueOffset = random8(255); + } + + void on_update() override { + LedMatrix.fader(35); + + const index_t w = LedMatrix.width(); + const index_t h = LedMatrix.height(); + const float cx = (w - 1) / 2.0f; + const float cy = (h - 1) / 2.0f; + const float maxR = sqrtf(cx * cx + cy * cy) + 1.0f; + + // Рисуем точки спирали: r от 0 до maxR, угол = _angle + r * k + for (float r = 0.5f; r < maxR; r += 0.45f) { + float a = _angle + r * radiusStep; + float x = cx + cosf(a) * r; + float y = cy + sinf(a) * r; + index_t ix = (index_t)(x + 0.5f); + index_t iy = (index_t)(y + 0.5f); + + if (ix < w && iy < h) { + uint8_t hue = (uint8_t)(_hueOffset + (uint8_t)(r * hueStep)); + LedMatrix.at(ix, iy) = CHSV(hue, 255, 255); + } + } + + _angle += angleStep; + _hueOffset += 2; + } +}; diff --git a/src/effect_list/effects/starfall.h b/src/effects/effects_impl/starfall.h similarity index 98% rename from src/effect_list/effects/starfall.h rename to src/effects/effects_impl/starfall.h index 28bdbec..944ce6f 100644 --- a/src/effect_list/effects/starfall.h +++ b/src/effects/effects_impl/starfall.h @@ -1,6 +1,6 @@ #pragma once -#include "effect_list/effect.h" +#include "effects/effect.h" class Starfall : public Effect { diff --git a/src/effect_list/effects/text.h b/src/effects/effects_impl/text.h similarity index 95% rename from src/effect_list/effects/text.h rename to src/effects/effects_impl/text.h index 8f68f78..a861afc 100644 --- a/src/effect_list/effects/text.h +++ b/src/effects/effects_impl/text.h @@ -1,6 +1,6 @@ #pragma once -#include "effect_list/effect.h" +#include "effects/effect.h" #include "libs/fonts.h" class TextMode : public Effect { @@ -44,7 +44,7 @@ class TextMode : public Effect { if (convert_char_utf8_to_cp1251(str, pos)) { size++; } else { - out("Unknown symbol: pos %d: '%c' %d\n", pos, str[pos], str[pos]); + logError("Unknown symbol: pos %d: '%c' %d\n", pos, str[pos], str[pos]); return nullptr; } pos++; @@ -74,7 +74,7 @@ class TextMode : public Effect { } else if (ch >= 192) { return ch - 97; } - out("Undefined symbols: %c\n", ch); + logError("Undefined symbols: %c\n", ch); return 0; } diff --git a/src/effect_list/effects/the_matrix.h b/src/effects/effects_impl/the_matrix.h similarity index 96% rename from src/effect_list/effects/the_matrix.h rename to src/effects/effects_impl/the_matrix.h index 0888eae..b8f3986 100644 --- a/src/effect_list/effects/the_matrix.h +++ b/src/effects/effects_impl/the_matrix.h @@ -1,6 +1,6 @@ #pragma once -#include "effect_list/effect.h" +#include "effects/effect.h" class TheMatrix : public Effect { diff --git a/src/effect_list/effects/zigzag.h b/src/effects/effects_impl/zigzag.h similarity index 96% rename from src/effect_list/effects/zigzag.h rename to src/effects/effects_impl/zigzag.h index ec9dd73..9bb32ca 100644 --- a/src/effect_list/effects/zigzag.h +++ b/src/effects/effects_impl/zigzag.h @@ -1,6 +1,6 @@ #pragma once -#include "effect_list/effect.h" +#include "effects/effect.h" class ZigZag : public Effect { diff --git a/src/effect_list/erroreffect.h b/src/effects/erroreffect.h similarity index 83% rename from src/effect_list/erroreffect.h rename to src/effects/erroreffect.h index 9a38f9d..425b222 100644 --- a/src/effect_list/erroreffect.h +++ b/src/effects/erroreffect.h @@ -9,6 +9,6 @@ class ErrorEffect : public Effect void on_update() { FastLED.showColor(CRGB::Red); - out("ERROR state"); + logError("ERROR state"); } }; diff --git a/src/events/ChangeModeEventRequest.h b/src/events/ChangeModeEventRequest.h new file mode 100644 index 0000000..6c7a1ba --- /dev/null +++ b/src/events/ChangeModeEventRequest.h @@ -0,0 +1,85 @@ +#pragma once + +#include "stdint.h" + +struct ChangeModeEventRequest { + enum class Type { + None, + Set, + Next, + Previous + }; + Type type; + uint16_t modNum; // на какой конкретно режим сменить. Учитывается только при Type::Set + int32_t modNumOffset; // сдвиг относительно текущего мода. Учитывается только при Type::Next и Type::Previous + bool hardReset; + + ChangeModeEventRequest() : type(Type::None), modNum(0), modNumOffset(0), hardReset(false) {} + ChangeModeEventRequest(Type t, bool h, uint16_t modNum = 0, int32_t modNumOffset = 0) : + type(t), + modNum(modNum), + modNumOffset(modNumOffset), + hardReset(h) + { + if (t == Type::Set) { + modNumOffset = 0; + } else if (t == Type::Next || t == Type::Previous) { + modNum = 0; + } + } + + // Оператор сложения для объединения запросов + ChangeModeEventRequest operator+(const ChangeModeEventRequest& other) const { + // Если текущий запрос - None, возвращаем другой + if (type == Type::None) { + return other; + } + + // Если другой запрос - None, возвращаем текущий + if (other.type == Type::None) { + return *this; + } + + // Приоритет: Set > Next/Previous + if (other.type == Type::Set) { + return other; + } + + // Если оба Next или Previous, объединяем offset'ы + if (type == other.type) { + return ChangeModeEventRequest( + type, + hardReset || other.hardReset, + modNum, + modNumOffset + other.modNumOffset + ); + } + + // Если разные типы Next/Previous, рассчитываем итоговый offset + int32_t finalOffset = 0; + Type finalType = Type::Next; // по умолчанию Next + + if (type == Type::Next && other.type == Type::Previous) { + finalOffset = modNumOffset - other.modNumOffset; + } else if (type == Type::Previous && other.type == Type::Next) { + finalOffset = other.modNumOffset - modNumOffset; + } + + // Определяем итоговый тип на основе знака offset + if (finalOffset > 0) { + finalType = Type::Next; + } else if (finalOffset < 0) { + finalType = Type::Previous; + finalOffset = -finalOffset; // делаем положительным для Previous + } else { + finalType = Type::None; // если offset = 0, то никакого изменения + } + + return ChangeModeEventRequest( + finalType, + hardReset || other.hardReset, + modNum, + finalOffset + ); + } +}; \ No newline at end of file diff --git a/src/events/events.h b/src/events/events.h index 8038bef..d538609 100644 --- a/src/events/events.h +++ b/src/events/events.h @@ -1,45 +1,55 @@ #pragma once +#include "ChangeModeEventRequest.h" + enum class EventType { - ChangeAutoMod = 0, - ChangePowerState, // Event - SetPowerState, // ChangeBoolEvent - ChangeBrightness, // ChangeIntEvent - ChangeSpeed, // ChangeIntEvent - ChangeMode, // ChangeModEvent - ChangeModVar, // ChangeModVarEvent - EventAmount // Используется, чтобы знать сколько всего ивентов +// Тип ивена // Структура данных ивента + ChangeAutoMod = 0, // ChangeBoolEvent + ChangePowerState, // Event + SetPowerState, // ChangeBoolEvent + ChangeBrightness, // ChangeIntEvent + ChangeSpeed, // ChangeIntEvent + ChangeMode, // ChangeModeEventRequest + ModChanged, // ModChangedEvent + ChangeModVar, // ChangeModVarEvent + ResetModesList, // Event — сброс списка режимов на заводской + EventAmount // Используется, чтобы знать сколько всего ивентов }; struct Event { - EventType type; - Event(EventType t) : type(t) {} - virtual ~Event() = default; + EventType type; + Event(EventType t) : type(t) {} + virtual ~Event() = default; }; struct ChangeBoolEvent : public Event { - bool new_val; - ChangeBoolEvent(EventType t, bool v) : Event(t), new_val(v) {} + bool new_val; + ChangeBoolEvent(EventType t, bool v) : Event(t), new_val(v) {} }; struct ChangeIntEvent : public Event { - int new_val; - ChangeIntEvent(EventType t, int v) : Event(t), new_val(v) {} + int new_val; + ChangeIntEvent(EventType t, int v) : Event(t), new_val(v) {} }; -struct ChangeModEvent : public Event { - enum class Type { - Next, - Previous, - Set // установить конкретный мод по id - }; - Type type; - uint8_t id; - ChangeModEvent(EventType et, Type t, uint8_t i = 0) : Event(et), type(t), id(i) {} +struct ChangeModeEvent : public Event { + ChangeModeEventRequest request; + ChangeModeEvent( + EventType et, + bool hardReset, + ChangeModeEventRequest::Type type, + uint16_t modIndex = 0, + int32_t modOffset = 1) : + Event(et), request(type, hardReset, modIndex, modOffset) {} }; +struct ModChangedEvent : public Event { + uint8_t id; + uint8_t num; + ModChangedEvent(EventType et, uint8_t i = 0, uint8_t n = 0) : Event(et), id(i), num(n) {} +}; struct ChangeModVarEvent : public Event { - int val; - int offset; - ChangeModVarEvent(EventType t, int v, int o) : Event(t), val(v), offset(o) {} + int val; + int offset; + ChangeModVarEvent(EventType t, int v, int o) : Event(t), val(v), offset(o) {} }; \ No newline at end of file diff --git a/src/events/observer.cpp b/src/events/observer.cpp index 68c93cb..48edc23 100644 --- a/src/events/observer.cpp +++ b/src/events/observer.cpp @@ -6,7 +6,7 @@ int Observable::type_to_int(EventType etype) { if (etype >= EventType::ChangeAutoMod && etype < EventType::EventAmount) { return static_cast(etype); } else { - out("Error check EventType: out of range\n"); + logError("Check EventType: out of range\n"); return 0; } } @@ -51,7 +51,7 @@ void Observable::instanceAddObserver(EventType etype, IObserver *observer) { } } if (observer_location > -1) { - out("Error addObserver: observer exists on list %d, 0x%lx", (int)etype, (uint64_t)observer); + logError("addObserver: observer exists on list %d, 0x%lx", (int)etype, (uint64_t)observer); return; } // если не нашли пустую ячейку расширяем память @@ -81,11 +81,11 @@ void Observable::instanceRemoveObserver(EventType etype, IObserver *observer) { if (observer_location != -1) { _observerList[type].second[observer_location] = nullptr; } else { - out("Error removeObserver: EventType not found\n"); + logError("removeObserver: EventType not found\n"); } } -void Observable::instanceNotify(Event *event) { +void Observable::instanceNotify(const Event *event) { int type = type_to_int(event->type); for (int i = 0; i < _observerList[type].first; ++i) { @@ -102,7 +102,3 @@ void Observable::subscribe(EventType etype, IObserver *observer) { void Observable::unsubscribe(EventType etype, IObserver *observer) { Observable::instance().instanceRemoveObserver(etype, observer); } - -void Observable::notify(Event *event) { - Observable::instance().instanceNotify(event); -} \ No newline at end of file diff --git a/src/events/observer.h b/src/events/observer.h index 379ce29..3fc510e 100644 --- a/src/events/observer.h +++ b/src/events/observer.h @@ -11,33 +11,37 @@ // реализуем метод handleEvent class IObserver { public: - virtual void handleEvent(Event *event) = 0; - virtual ~IObserver() = default; + virtual void handleEvent(const Event *event) = 0; + virtual ~IObserver() = default; }; class Observable { private: - std::pair *_observerList; + std::pair *_observerList; - // проверка корретного типа и возврат id в виде int. 0 при ошибке - int type_to_int(EventType etype); + // проверка корретного типа и возврат id в виде int. 0 при ошибке + int type_to_int(EventType etype); - Observable(); - ~Observable(); - Observable(const Observable &) = delete; - Observable &operator=(const Observable &) = delete; + Observable(); + ~Observable(); + Observable(const Observable &) = delete; + Observable &operator=(const Observable &) = delete; - static Observable &instance(); + static Observable &instance(); - void instanceAddObserver(EventType etype, IObserver *observer); - void instanceRemoveObserver(EventType etype, IObserver *observer); - void instanceNotify(Event *event); + void instanceAddObserver(EventType etype, IObserver *observer); + void instanceRemoveObserver(EventType etype, IObserver *observer); + void instanceNotify(const Event *event); public: - // подписаться на события определённого типа - static void subscribe(EventType etype, IObserver *observer); - // отписаться на события определённого типа - static void unsubscribe(EventType etype, IObserver *observer); - // уведомить всех подписчиков о событии - static void notify(Event *event); + // подписаться на события определённого типа + static void subscribe(EventType etype, IObserver *observer); + // отписаться на события определённого типа + static void unsubscribe(EventType etype, IObserver *observer); + // уведомить всех подписчиков о событии + template + static void notify(EventType etype, Args... args) { + const T event = T(etype, args...); + instance().instanceNotify(&event); + } }; \ No newline at end of file diff --git a/src/libs/StdFeatures.h b/src/libs/StdFeatures.h new file mode 100644 index 0000000..3dd29a1 --- /dev/null +++ b/src/libs/StdFeatures.h @@ -0,0 +1,13 @@ +#pragma once + +#include +#include + +namespace std { + // Реализация make_unique для C++11 + template + std::unique_ptr make_unique(Args&&... args) + { + return std::unique_ptr(new T(std::forward(args)...)); + } +} \ No newline at end of file diff --git a/src/libs/debug_lib.cpp b/src/libs/debug_lib.cpp new file mode 100644 index 0000000..0cfd23b --- /dev/null +++ b/src/libs/debug_lib.cpp @@ -0,0 +1,30 @@ +#include "debug_lib.h" + +#include "configs/constants.h" + +#if DEBUG + #include + + void debugSetup() + { + Serial.begin(115200); + } + + const size_t out(const char *szFormat, ...) + { + va_list argptr; + va_start(argptr, szFormat); + char *szBuffer = 0; + const size_t nBufferLength = vsnprintf(szBuffer, 0, szFormat, argptr) + 1; + if (nBufferLength == 1) return 0; + szBuffer = (char *) malloc(nBufferLength); + if (! szBuffer) return - nBufferLength; + vsnprintf(szBuffer, nBufferLength, szFormat, argptr); + Serial.print(szBuffer); + free(szBuffer); + return nBufferLength - 1; + } +#else + void debugSetup() {}; + const size_t out(const char *szFormat, ...) {}; +#endif diff --git a/src/libs/debug_lib.h b/src/libs/debug_lib.h index c0e8e5d..869e6fb 100644 --- a/src/libs/debug_lib.h +++ b/src/libs/debug_lib.h @@ -1,40 +1,19 @@ #pragma once -#if DEBUG - #include +#include "stddef.h" - inline void debug_setup() - { - Serial.begin(115200); - } - /* - * SerialPrintf - * Реализует функциональность printf в Serial.print - * Применяется для отладочной печати - * Параметры как у printf - * Возвращает - * 0 - ошибка формата - * отрицательное чило - нехватка памяти, модуль числа равен запрашиваемой памяти - * положительное число - количество символов, выведенное в Serial - */ - inline const size_t out(const char *szFormat, ...) - { - va_list argptr; - va_start(argptr, szFormat); - char *szBuffer = 0; - const size_t nBufferLength = vsnprintf(szBuffer, 0, szFormat, argptr) + 1; - if (nBufferLength == 1) return 0; - szBuffer = (char *) malloc(nBufferLength); - if (! szBuffer) return - nBufferLength; - vsnprintf(szBuffer, nBufferLength, szFormat, argptr); - Serial.print(szBuffer); - free(szBuffer); - return nBufferLength - 1; - } +void debugSetup(); - #define outln(fmt, ...) out(fmt "\n", ##__VA_ARGS__) -#else -#define debug_setup(); -#define out(fmt, ...); -#define outln(fmt, ...); -#endif +/* + * Реализует функциональность printf в Serial.print + * Применяется для отладочной печати + * Параметры как у printf + * Возвращает + * 0 - ошибка формата + * отрицательное число - нехватка памяти, модуль числа равен запрашиваемой памяти + * положительное число - количество символов, выведенное в Serial + */ +const size_t out(const char *szFormat, ...); + +#define logInfo(fmt, ...) out("[INFO] " fmt, ##__VA_ARGS__) +#define logError(fmt, ...) out("[ERROR] " fmt, ##__VA_ARGS__) diff --git a/src/libs/queue.h b/src/libs/queue.h index eb3bad7..a53d751 100644 --- a/src/libs/queue.h +++ b/src/libs/queue.h @@ -26,7 +26,7 @@ class Queue buf[end_pos] = tr; end_pos = (end_pos + 1) % max_size; } else { - out("Queue error: overflow buffer\n"); + logError("Queue error: overflow buffer\n"); } } @@ -57,7 +57,7 @@ class Queue } void debug() const { - out("bgn: %d end %d size %d\n", start_pos, end_pos, size()); + logInfo("bgn: %d end %d size %d\n", start_pos, end_pos, size()); for (int i = 0; i < max_size; ++i) { out("%d ", int(buf[i])); } diff --git a/src/main.cpp b/src/main.cpp index 0829b07..14d3a92 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,6 +2,75 @@ MyApplication app; +// void listFiles(const char* dirname = "/") { +// Dir dir = LittleFS.openDir(dirname); + +// Serial.println("Listing files:"); +// while (dir.next()) { +// Serial.printf("FILE: %s\tSIZE: %d bytes\n", +// dir.fileName().c_str(), +// dir.fileSize()); +// } +// } + +// void printFileIfExists(const char* path) { +// if (!LittleFS.exists(path)) { +// Serial.printf("File '%s' does not exist.\n", path); +// return; +// } + +// File file = LittleFS.open(path, "r"); +// if (!file) { +// Serial.printf("Failed to open file '%s'\n", path); +// return; +// } + +// Serial.printf("Contents of '%s':\n", path); +// while (file.available()) { +// Serial.write(file.read()); +// } + +// file.close(); +// Serial.println(); // перенос строки после вывода +// } + +// void printFileByByte(const char* path) { +// if (!LittleFS.exists(path)) { +// Serial.printf("File '%s' does not exist.\n", path); +// return; +// } + +// File file = LittleFS.open(path, "r"); +// if (!file) { +// Serial.printf("Failed to open file '%s'\n", path); +// return; +// } + +// Serial.printf("Contents of '%s' by byte:\n'", path); +// int byteCount = 1; +// while (file.available()) { +// uint8_t byte = 0; +// file.read(reinterpret_cast(&byte), 1); +// Serial.printf("%02X", byte); +// if (byteCount % 4 == 0) { +// Serial.printf(" "); +// } +// byteCount++; +// } +// Serial.printf("'"); + +// file.close(); +// Serial.println(); // перенос строки после вывода +// } + +// void printAllMods(FileEffectStorage &storage) { +// logInfo("Current effects:\n"); +// for (size_t i = 0; i < storage.size(); i++) { +// EffectInfo info = storage.getEffectInfo(i); +// logInfo("Effect %zu: ID=%u, SavedIndex=%u\n", i, info.id, info.savedIndex); +// } +// } + void setup() { app.onInit(); } diff --git a/src/myapplication.cpp b/src/myapplication.cpp index 0e808d4..422b4ea 100644 --- a/src/myapplication.cpp +++ b/src/myapplication.cpp @@ -1,7 +1,14 @@ #include "myapplication.h" -#include "libs/led_matrix.h" -#include "effect_list/effectslist.h" +#include "core/effect/EffectManager.h" +#include "libs/StdFeatures.h" + +#if SAVE_TO_EEPROM +# include "core/file/LsfFileHandler.h" +# include "core/effect/storage/FileEffectStorage.h" +#else +# include "core/effect/storage/StaticEffectStorage.h" +#endif MyApplication::MyApplication() : _isPowerOn(true), @@ -18,18 +25,22 @@ MyApplication::MyApplication() : { Observable::subscribe(EventType::ChangePowerState, this); Observable::subscribe(EventType::SetPowerState, this); + Observable::subscribe(EventType::ChangeMode, this); + Observable::subscribe(EventType::ResetModesList, this); }; MyApplication::~MyApplication() { Observable::unsubscribe(EventType::ChangePowerState, this); Observable::unsubscribe(EventType::SetPowerState, this); + Observable::unsubscribe(EventType::ChangeMode, this); + Observable::unsubscribe(EventType::ResetModesList, this); } // лучше всё по максимому инициализировать тут void MyApplication::onInit() { randomSeed(millis() + analogRead(A0)); random16_set_seed(millis() + analogRead(A0)); - debug_setup(); + debugSetup(); LedMatrix.setup(); #if IR_ENABLE _ir.onInit(IR_RECEIVE_PIN); @@ -37,10 +48,12 @@ void MyApplication::onInit() { #if RELAY_ENABLE _relay.onInit(); #endif - - EffectsList::getInstance(); // инициализируем EffectsList, чтобы сработало уведомление о новом режиме - auto ev = ChangeModEvent({EventType::ChangeMode, ChangeModEvent::Type::Set, 0}); - Observable::notify(&ev); +#if SAVE_TO_EEPROM + _effectStorage = std::make_unique(std::make_unique(SAVE_TO_EEPROM_FILE)); +#else + _effectStorage = std::make_unique(); +#endif + _effectManager = std::make_unique(*_effectStorage.get()); } void MyApplication::onTick() { @@ -49,7 +62,7 @@ void MyApplication::onTick() { #endif { if (_isPowerOn) { - EffectsList::getInstance().onTick(); + _effectManager->onTick(); _autoMod.onTick(); } #if BTN_ENABLE @@ -76,11 +89,21 @@ void MyApplication::setPowerState(bool newState) { } } -void MyApplication::handleEvent(Event *event) { +void MyApplication::handleEvent(const Event *event) { if (event->type == EventType::ChangePowerState) { setPowerState(!_isPowerOn); } else if (event->type == EventType::SetPowerState) { - ChangeBoolEvent *ev = static_cast(event); + const ChangeBoolEvent *ev = static_cast(event); setPowerState(ev->new_val); + } else if (event->type == EventType::ChangeAutoMod) { + const ChangeBoolEvent *ev = static_cast(event); + _autoMod.setIsEnable(ev->new_val); + } else if (event->type == EventType::ChangeMode) { // включить питание при попытках сменить режима + if (!_isPowerOn) { + setPowerState(true); + } + } else if (event->type == EventType::ResetModesList) { + _effectStorage->reset(); + _effectManager->setEffect(_effectStorage->size() - 1); } } \ No newline at end of file diff --git a/src/myapplication.h b/src/myapplication.h index 7b49e1b..02d85f0 100644 --- a/src/myapplication.h +++ b/src/myapplication.h @@ -1,9 +1,10 @@ #pragma once -#include #include "configs/constants.h" #include "events/observer.h" #include "controls/automode.h" +#include "core/effect/storage/IEffectStorage.h" +#include "core/effect/EffectManager.h" #if BTN_ENABLE # include "controls/button.h" @@ -15,10 +16,15 @@ # include "modules/relay.h" #endif +#include +#include + class MyApplication : public IObserver { private: bool _isPowerOn; AutoChangeMode _autoMod; + std::unique_ptr _effectStorage; + std::unique_ptr _effectManager; #if BTN_ENABLE Button _button; #endif @@ -40,5 +46,5 @@ class MyApplication : public IObserver { ~MyApplication(); void onTick(); void onInit(); - virtual void handleEvent(Event *event) override; + virtual void handleEvent(const Event *event) override; }; \ No newline at end of file