From 1a999b1fc458ebff3d723a6c0c933d695ea651b2 Mon Sep 17 00:00:00 2001 From: npo6ka Date: Wed, 11 Dec 2019 00:44:07 +0300 Subject: [PATCH 01/75] add old mods --- src/effects/01_simple_rainbow.h | 33 ++++++ src/effects/02_dribs.h | 43 ++++++++ src/effects/03_rain.h | 34 ++++++ src/effects/04_all_random.h | 23 ++++ src/effects/05_snow.h | 63 +++++++++++ src/effects/06_fire.h | 144 ++++++++++++++++++++++++++ src/effects/07_the_matrix.h | 34 ++++++ src/effects/08_simple_balls.h | 85 +++++++++++++++ src/effects/09_confetti.h | 26 +++++ src/effects/10_starfall.h | 51 +++++++++ src/effects/12_random_rain.h | 44 ++++++++ src/effects/13_rainbow_rain.h | 42 ++++++++ src/effects/14_points.h | 132 +++++++++++++++++++++++ src/effects/15_rainbow_point.h | 91 ++++++++++++++++ src/effects/16_rainbow_static_point.h | 44 ++++++++ 15 files changed, 889 insertions(+) create mode 100644 src/effects/01_simple_rainbow.h create mode 100644 src/effects/02_dribs.h create mode 100644 src/effects/03_rain.h create mode 100644 src/effects/04_all_random.h create mode 100644 src/effects/05_snow.h create mode 100644 src/effects/06_fire.h create mode 100644 src/effects/07_the_matrix.h create mode 100644 src/effects/08_simple_balls.h create mode 100644 src/effects/09_confetti.h create mode 100644 src/effects/10_starfall.h create mode 100644 src/effects/12_random_rain.h create mode 100644 src/effects/13_rainbow_rain.h create mode 100644 src/effects/14_points.h create mode 100644 src/effects/15_rainbow_point.h create mode 100644 src/effects/16_rainbow_static_point.h diff --git a/src/effects/01_simple_rainbow.h b/src/effects/01_simple_rainbow.h new file mode 100644 index 0000000..ca68732 --- /dev/null +++ b/src/effects/01_simple_rainbow.h @@ -0,0 +1,33 @@ +#pragma once + +#define data ((SimpleRaibowData *)global_data) + +struct SimpleRaibowData +{ + int tick; + int phaseShift; +}; + +void simple_rainbow_prepare() +{ + data->tick = 0; + data->phaseShift = 10; + + eff_set_ups(20); +} + +void simple_rainbow_update() +{ + uint8_t x, y; + data->tick = data->tick % (MAX_HSV + 1); + + for (x = 0; x < 10; x++) { + for (y = 0; y < 10; y++) { + getPix(x, y) = CHSV((data->tick + x + y * data->phaseShift / 2) % (MAX_HSV + 1), 255, 255); + } + } + + data->tick += 1; +} + +#undef data diff --git a/src/effects/02_dribs.h b/src/effects/02_dribs.h new file mode 100644 index 0000000..bfcfc9e --- /dev/null +++ b/src/effects/02_dribs.h @@ -0,0 +1,43 @@ +#pragma once + +#define data ((DribsData *)global_data) + +struct DribsData +{ + uint8_t cur_drib[WIDTH]; + uint8_t lenght; +}; + +void dribs_prepare() +{ + data->lenght = 12; + + eff_set_ups(20); +} + +void dribs_update() +{ + for (int i = 0; i < WIDTH; ++i) { + if (data->cur_drib[i] == 0 && random8(40) == 0) { + data->cur_drib[i] = 1; + } else if (data->cur_drib[i] != 0) { + data->cur_drib[i] += 1; + if (data->cur_drib[i] >= data->lenght + HEIGHT) { + data->cur_drib[i] = 0; + } + } + } + + int step = MAX_BRIGHTNESS / data->lenght; + for (int i = 0; i < WIDTH; ++i) { + for (int j = 0; j < HEIGHT; ++j) { + if (j < data->cur_drib[i] && data->cur_drib[i] != 0) { + getPix(i, j) = CRGB(0, 0, max(MAX_BRIGHTNESS - (data->cur_drib[i] - j - 1) * step, 0)); + } else { + getPix(i, j) = 0x0; + } + } + } +} + +#undef data diff --git a/src/effects/03_rain.h b/src/effects/03_rain.h new file mode 100644 index 0000000..b90d3b4 --- /dev/null +++ b/src/effects/03_rain.h @@ -0,0 +1,34 @@ +#pragma once + +#include "effect.h" + +class Rain : public Effect +{ + uint8_t step = 2; + +public: + Rain() {} + + void on_tick() { + int i, j; + + for (i = 0; i < WIDTH; ++i) { + for (j = 0; j < HEIGHT; ++j) { + CRGB cl = getPix(i, j); + + if (random8(100) == 0) { + int asd = 0; + cl = CRGB(0, 0, MAX_BRIGHTNESS); + } else if (cl.b > 0) { + if (cl.b > step) { + cl = CRGB(0, 0, cl.b - step); + } else { + cl = CRGB(0, 0, 0); + } + } + + getPix(i, j) = cl; + } + } + } +}; diff --git a/src/effects/04_all_random.h b/src/effects/04_all_random.h new file mode 100644 index 0000000..89184af --- /dev/null +++ b/src/effects/04_all_random.h @@ -0,0 +1,23 @@ +#pragma once + +#include "effect.h" + +class AllRandom : public Effect +{ + uint8_t step = 2; + +public: + AllRandom() {} + + void on_init() { + + } + + void on_tick() { + int i; + + for (i = 0; i < LEDS_CNT; i++) { + getPix(i / 10, i % 10) = CRGB(random8(255), random8(255), random8(255)); + } + } +}; diff --git a/src/effects/05_snow.h b/src/effects/05_snow.h new file mode 100644 index 0000000..01ffaa4 --- /dev/null +++ b/src/effects/05_snow.h @@ -0,0 +1,63 @@ +#pragma once + +#define data ((SnowData *)global_data) + +struct SnowData +{ + uint8_t step; + uint8_t tick; + int density; + bool direction; +}; + +void snow_prepare() +{ + data->step = 20; + data->density = 10; + data->tick = 0; + data->direction = false; + eff_set_ups(60); +} + +void snow_update() +{ + if (data->tick >= data->step) { + data->direction = !data->direction; + // сдвигаем вниз + for (int8_t y = HEIGHT - 1; y >= 0; y--) { + bool dir = data->direction; + + for (uint8_t x = 0; x < WIDTH; x++) { + if (getPixColor(x, y)) { + if (y + 1 < HEIGHT) { + if (dir) { + setPixColor(x + 1, y + 1, getPixColor(x, y)); + } else { + setPixColor(x - 1, y + 1, getPixColor(x, y)); + } + dir = !dir; + } + + setPixColor(x, y, 0); + } + } + } + + for (uint8_t x = 0; x < WIDTH - 1; x++) { + // заполняем случайно верхнюю строку + // а также не даём двум блокам по вертикали вместе быть + if (getPixColor(x, 1) == 0 && (random(0, data->density) == 0)) { + setPixColor(x, 0, 0xE0FFFF - 0x101010 * random(0, 4)); + x++; + } else { + setPixColor(x, 0, 0x000000); + } + } + + data->tick = 0; + } else { + data->tick++; + } +} + +#undef data diff --git a/src/effects/06_fire.h b/src/effects/06_fire.h new file mode 100644 index 0000000..55efb6f --- /dev/null +++ b/src/effects/06_fire.h @@ -0,0 +1,144 @@ +#pragma once + +#include "effect.h" + +class Fire : public Effect +{ + //these values are substracetd from the generated values to give a shape to the animation + const unsigned char valueMask[8][16] PROGMEM = { + {32 , 0 , 0 , 0 , 0 , 0 , 0 , 32 , 32 , 0 , 0 , 0 , 0 , 0 , 0 , 32 }, + {64 , 0 , 0 , 0 , 0 , 0 , 0 , 64 , 64 , 0 , 0 , 0 , 0 , 0 , 0 , 64 }, + {96 , 32 , 0 , 0 , 0 , 0 , 32 , 96 , 96 , 32 , 0 , 0 , 0 , 0 , 32 , 96 }, + {128, 64 , 32 , 0 , 0 , 32 , 64 , 128, 128, 64 , 32 , 0 , 0 , 32 , 64 , 128}, + {160, 96 , 64 , 32 , 32 , 64 , 96 , 160, 160, 96 , 64 , 32 , 32 , 64 , 96 , 160}, + {192, 128, 96 , 64 , 64 , 96 , 128, 192, 192, 128, 96 , 64 , 64 , 96 , 128, 192}, + {255, 160, 128, 96 , 96 , 128, 160, 255, 255, 160, 128, 96 , 96 , 128, 160, 255}, + {255, 192, 160, 128, 128, 160, 192, 255, 255, 192, 160, 128, 128, 160, 192, 255} + }; + + //these are the hues for the fire, + //should be between 0 (red) to about 25 (yellow) + const unsigned char hueMask[8][16] PROGMEM = { + {1 , 11, 19, 25, 25, 22, 11, 1 , 1 , 11, 19, 25, 25, 22, 11, 1 }, + {1 , 8 , 13, 19, 25, 19, 8 , 1 , 1 , 8 , 13, 19, 25, 19, 8 , 1 }, + {1 , 8 , 13, 16, 19, 16, 8 , 1 , 1 , 8 , 13, 16, 19, 16, 8 , 1 }, + {1 , 5 , 11, 13, 13, 13, 5 , 1 , 1 , 5 , 11, 13, 13, 13, 5 , 1 }, + {1 , 5 , 11, 11, 11, 11, 5 , 1 , 1 , 5 , 11, 11, 11, 11, 5 , 1 }, + {0 , 1 , 5 , 8 , 8 , 5 , 1 , 0 , 0 , 1 , 5 , 8 , 8 , 5 , 1 , 0 }, + {0 , 0 , 1 , 5 , 5 , 1 , 0 , 0 , 0 , 0 , 1 , 5 , 5 , 1 , 0 , 0 }, + {0 , 0 , 0 , 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 0 , 0 , 0 } + }; + + unsigned char matrixValue[8][16]; + unsigned char line[WIDTH]; + int pcnt = 0; + uint8_t hue_add = 0; // добавка цвета в огонь (от 0 до 230) - меняет весь цвет пламени + bool sparkless = true; // вылетающие угольки вкл выкл + +public: + Fire() {} + + void on_init() { + generateLine(); + memset(matrixValue, 0, sizeof(matrixValue)); + } + + void on_tick() { + if (pcnt >= 100) { + shiftUp(); + generateLine(); + pcnt = 0; + } + + drawFrame(pcnt); + pcnt += 30; + } + + // Randomly generate the next line (matrix row) + void generateLine() { + for (uint8_t x = 0; x < WIDTH; x++) { + line[x] = max(random8(255), random8(255)); + } + } + + //shift all values in the matrix up one row + + void shiftUp() { + for (uint8_t y = HEIGHT - 1; y > 0; y--) { + for (uint8_t x = 0; x < WIDTH; x++) { + uint8_t newX = x; + if (x > 15) newX = x - 15; + if (y > 7) continue; + matrixValue[y][newX] = matrixValue[y - 1][newX]; + } + } + + for (uint8_t x = 0; x < WIDTH; x++) { + uint8_t newX = x; + if (x > 15) { + newX = x - 15; + } + matrixValue[0][newX] = line[newX]; + } + } + + // draw a frame, interpolating between 2 "key frames" + // @param pcnt percentage of interpolation + + void drawFrame(int pcnt) { + int nextv; + + //each row interpolates with the one before it + for (uint8_t y = HEIGHT - 1; y > 0; y--) { + for (uint8_t x = 0; x < WIDTH; x++) { + uint8_t newX = x; + if (x > 15) { + newX = x - 15; + } + if (y < 8) { + uint8_t val = pgm_read_byte(&(valueMask[y][newX])); + nextv = (((100.0 - pcnt) * matrixValue[y][newX] + + pcnt * matrixValue[y - 1][newX]) / 100.0) + - pgm_read_byte(&(valueMask[y][newX])); + + CRGB color = CHSV( + hue_add + pgm_read_byte(&(hueMask[y][newX])), // H + 255, // S + (uint8_t)max(0, nextv) // V + ); + + setPixColor(x, y, color); + } else if (y == 8 && sparkless) { + if (random8(20) == 0 && getPixColor(x, y - 1) != 0) { + setPixColor(x, y, getPixColor(x, y - 1)); + } else { + setPixColor(x, y, 0); + } + } else if (sparkless) { + // старая версия для яркости + if (getPixColor(x, y - 1) > 0) { + setPixColor(x, y, getPixColor(x, y - 1)); + } else { + setPixColor(x, y, 0); + } + } + } + } + + //first row interpolates with the "next" line + for (unsigned char x = 0; x < WIDTH; x++) { + uint8_t newX = x; + if (x > 15) { + newX = x - 15; + } + CRGB color = CHSV( + hue_add + pgm_read_byte(&(hueMask[0][newX])), // H + 255, // S + (uint8_t)(((100.0 - pcnt) * matrixValue[0][newX] + pcnt * line[newX]) / 100.0) // V + ); + + setPixColor(newX, 0, color); + } + } +}; + diff --git a/src/effects/07_the_matrix.h b/src/effects/07_the_matrix.h new file mode 100644 index 0000000..f9e9a22 --- /dev/null +++ b/src/effects/07_the_matrix.h @@ -0,0 +1,34 @@ +#pragma once + +#include "effect.h" + +class TheMatrix : public Effect +{ + +public: + TheMatrix() {} + + void on_init() { + + } + + void on_tick() { + for (uint8_t x = 0; x < WIDTH; x++) { + // заполняем случайно верхнюю строку + uint32_t thisColor = getPixColor(x, HEIGHT - 1); + if (thisColor == 0) + setPixColor(x, HEIGHT - 1, 0x00FF00 * (random(0, 10) == 0)); + else if (thisColor < 0x002000) + setPixColor(x, HEIGHT - 1, 0); + else + setPixColor(x, HEIGHT - 1, thisColor - 0x002000); + } + + // сдвигаем всё вниз + for (uint8_t x = 0; x < WIDTH; x++) { + for (uint8_t y = 0; y < HEIGHT - 1; y++) { + setPixColor(x, y, getPixColor(x, y + 1)); + } + } + } +}; diff --git a/src/effects/08_simple_balls.h b/src/effects/08_simple_balls.h new file mode 100644 index 0000000..ff9176f --- /dev/null +++ b/src/effects/08_simple_balls.h @@ -0,0 +1,85 @@ +#pragma once + +#include "effect.h" + +class SimpleBalls : public Effect +{ + static const uint8_t balls_amount = 3; + int coord[balls_amount][2]; + int8_t vector[balls_amount][2]; + CRGB ballColors[balls_amount]; + bool ball_track = true; // (0 / 1) - вкл/выкл следы шариков + uint8_t track_step = 70; // длина хвоста шарика (чем больше цифра, тем хвост короче) + bool draw_walls = false; // режим с рисованием препятствий для шаров + uint32_t wall_color = 0x00ff00; + +public: + SimpleBalls() {} + + void on_init() { + for (uint8_t j = 0; j < balls_amount; j++) { + int sign; + + // забиваем случайными данными + coord[j][0] = WIDTH / 2 * 10; + random(2) ? sign = 1 : sign = -1; + vector[j][0] = random(4, 15) * sign; + coord[j][1] = HEIGHT / 2 * 10; + random(2) ? sign = 1 : sign = -1; + vector[j][1] = random(4, 15) * sign; + ballColors[j] = CHSV(random(0, 9) * 28, 255, 255); + } + } + + void on_tick() { + if (!ball_track) // если режим БЕЗ следов шариков + FastLED.clear(); // очистить + else { // режим со следами + fader(track_step); + } + + getLeds()[0].fadeToBlackBy(1); + + // движение шариков + for (uint8_t j = 0; j < balls_amount; j++) { + // отскок от нарисованных препятствий + if (draw_walls) { + uint32_t thisColor = getPixColor(coord[j][0] / 10 + 1, coord[j][1] / 10); + if (thisColor == wall_color/* && vector[j][0] > 0*/) { + vector[j][0] = -vector[j][0]; + } + thisColor = getPixColor(coord[j][0] / 10 - 1, coord[j][1] / 10); + if (thisColor == wall_color/* && vector[j][0] < 0*/) { + vector[j][0] = -vector[j][0]; + } + thisColor = getPixColor(coord[j][0] / 10, coord[j][1] / 10 + 1); + if (thisColor == wall_color/* && vector[j][1] > 0*/) { + vector[j][1] = -vector[j][1]; + } + thisColor = getPixColor(coord[j][0] / 10, coord[j][1] / 10 - 1); + if (thisColor == wall_color/* && vector[j][1] < 0*/) { + vector[j][1] = -vector[j][1]; + } + } + + // движение шариков + for (uint8_t i = 0; i < 2; i++) { + coord[j][i] += vector[j][i]; + if (coord[j][i] < 0) { + coord[j][i] = 0; + vector[j][i] = -vector[j][i]; + } + } + + if (coord[j][0] > (WIDTH - 1) * 10) { + coord[j][0] = (WIDTH - 1) * 10; + vector[j][0] = -vector[j][0]; + } + if (coord[j][1] > (HEIGHT - 1) * 10) { + coord[j][1] = (HEIGHT - 1) * 10; + vector[j][1] = -vector[j][1]; + } + setPixColor(coord[j][0] / 10, coord[j][1] / 10, ballColors[j]); + } + } +}; diff --git a/src/effects/09_confetti.h b/src/effects/09_confetti.h new file mode 100644 index 0000000..4a91532 --- /dev/null +++ b/src/effects/09_confetti.h @@ -0,0 +1,26 @@ +#pragma once + +#include "effect.h" + +class Confetti : public Effect +{ + uint8_t brightness_step = 30; + uint8_t density = 3; +public: + Confetti() {} + + void on_init() { + + } + + void on_tick() { + for (uint8_t i = 0; i < density; i++) { + uint8_t x = random(WIDTH); + uint8_t y = random(HEIGHT); + if (!getPixColor(x, y)) { + setPixColor(x, y, CHSV(random(0, 255), 255, 255)); + } + fader(brightness_step); + } + } +}; diff --git a/src/effects/10_starfall.h b/src/effects/10_starfall.h new file mode 100644 index 0000000..2d793c2 --- /dev/null +++ b/src/effects/10_starfall.h @@ -0,0 +1,51 @@ +#pragma once + +#include "effect.h" + +class Starfall : public Effect +{ + uint8_t saturation = 150; + uint8_t density = 60; + uint8_t tail_step = 100; +public: + Starfall () {} + + void on_init() { + + } + + void on_tick() { + // заполняем головами комет левую и верхнюю линию + for (uint8_t i = HEIGHT / 2; i < HEIGHT; i++) { + if (!getPixColor(0, i) + && (random(0, density) == 0) + && i + 1 < HEIGHT && !getPixColor(0, i + 1) + && i - 1 >= 0 && !getPixColor(0, i - 1)) { + setPixColor(0, i, CHSV(random(0, 200), saturation, 255)); + } + } + for (uint8_t i = 0; i < WIDTH / 2; i++) { + if (!getPixColor(i, HEIGHT - 1) + && (random(0, density) == 0) + && i + 1 < HEIGHT && !getPixColor(i + 1, HEIGHT - 1) + && i - 1 >= 0 && !getPixColor(i - 1, HEIGHT - 1)) { + setPixColor(i, HEIGHT - 1, CHSV(random(0, 200), saturation, 255)); + } + } + + // сдвигаем по диагонали + for (uint8_t y = 0; y < HEIGHT - 1; y++) { + for (uint8_t x = WIDTH - 1; x > 0; x--) { + setPixColor(x, y, getPixColor(x - 1, y + 1)); + } + } + + // уменьшаем яркость левой и верхней линии, формируем "хвосты" + for (uint8_t i = HEIGHT / 2; i < HEIGHT; i++) { + fadePix(0, i, tail_step); + } + for (uint8_t i = 0; i < WIDTH / 2; i++) { + fadePix(i, HEIGHT - 1, tail_step); + } + } +}; diff --git a/src/effects/12_random_rain.h b/src/effects/12_random_rain.h new file mode 100644 index 0000000..4d90ea0 --- /dev/null +++ b/src/effects/12_random_rain.h @@ -0,0 +1,44 @@ +#pragma once + +#define data ((RandomRainData *)global_data) + +struct RandomRainData +{ + uint8_t step; +}; + +void random_rain_prepare() +{ + data->step = 2; + eff_set_ups(30); +} + +void random_rain_update() +{ + int i, j; + + for (i = 0; i < WIDTH; ++i) { + for (j = 0; j < HEIGHT; ++j) { + fadePix(i, j, data->step); + + CRGB cl = getPix(i, j); + + if (random16(500) == 0) { + int asd = 0; + cl.r = MAX_BRIGHTNESS; + } + if (random16(500) == 0) { + int asd = 0; + cl.g = MAX_BRIGHTNESS; + } + if (random16(500) == 0) { + int asd = 0; + cl.b = MAX_BRIGHTNESS; + } + + setPixColor(i, j, cl); + } + } +} + +#undef data diff --git a/src/effects/13_rainbow_rain.h b/src/effects/13_rainbow_rain.h new file mode 100644 index 0000000..8dee97d --- /dev/null +++ b/src/effects/13_rainbow_rain.h @@ -0,0 +1,42 @@ +#pragma once + +#define data ((RainbowRainData *)global_data) + +#define RAINBOW_TICK_SIZE 4 //кол-во тиков до инкремента тика радуги + +struct RainbowRainData +{ + uint8_t step; + int tick; +}; + +void rainbow_rain_prepare() +{ + data->step = 2; + data->tick = 0; + eff_set_ups(40); +} + +void rainbow_rain_update() +{ + int i, j; + + for (i = 0; i < WIDTH; ++i) { + for (j = 0; j < HEIGHT; ++j) { + fadePix(i, j, data->step); + + CRGB cl = getPix(i, j); + + if (random8(200) == 0) { + int asd = 0; + cl = CHSV(data->tick / RAINBOW_TICK_SIZE, 255, 255); + } + + setPixColor(i, j, cl); + } + } + + data->tick = (data->tick + 1) % ((MAX_HSV + 1) * RAINBOW_TICK_SIZE); +} + +#undef data diff --git a/src/effects/14_points.h b/src/effects/14_points.h new file mode 100644 index 0000000..f130bc1 --- /dev/null +++ b/src/effects/14_points.h @@ -0,0 +1,132 @@ +#pragma once + +#define data ((PointsData *)global_data) + +#define ACCURACY 100 +#define MAX_VEC_SIZE 10 +#define POINTS_AMNT 3 + +typedef struct Point +{ + int32_t x; + int32_t y; + int32_t vec_x; + int32_t vec_y; + CRGB color; +} Point; + +struct PointsData +{ + uint32_t point_size; + uint32_t bright_radius; + Point points[POINTS_AMNT]; + int tick; +}; + +//arg2: horizontal barrier = true or vertical = false +static void gen_vector(Point &pnt, bool horVer) +{ + int16_t dir = horVer ? 1 : -1; + + pnt.vec_x = (pnt.vec_x > 0 ? -dir : dir) * random(0, MAX_VEC_SIZE); + pnt.vec_y = (pnt.vec_y > 0 ? dir : -dir) * random(0, MAX_VEC_SIZE); + + if (pnt.vec_y == 0 && pnt.vec_x == 0) { + gen_vector(pnt, horVer); + } +} + +static void move_point(Point &pnt) +{ + pnt.x += pnt.vec_x; + pnt.y += pnt.vec_y; + + if (pnt.x < 0) { + pnt.x = 0; + gen_vector(pnt, true); + } else if (pnt.x >= ACCURACY * WIDTH) { + pnt.x = ACCURACY * WIDTH - 1; + gen_vector(pnt, true); + } + + if (pnt.y < 0) { + pnt.y = 0; + gen_vector(pnt, false); + } else if (pnt.y >= ACCURACY * HEIGHT) { + pnt.y = ACCURACY * HEIGHT - 1; + gen_vector(pnt, false); + } +} + +// return float val in range 0 .. 1 +static float get_func_brithtness(int distance) +{ + if (distance <= data->point_size) { + return 1; + } else if (distance >= data->point_size + data->bright_radius) { + return 0; + } else { + float val = (float)(distance - data->point_size) / data->bright_radius; + return ( 7.3890560 /* e^2 */ ) / (50 * val + 7) - 0.13; + } +} + +static void render_point(Point pnt) +{ + int i, j; + for (i = 0; i < WIDTH; ++i) { + for(j = 0; j < HEIGHT; ++j) { + int loc_x = i * ACCURACY + ACCURACY / 2; + int loc_y = j * ACCURACY + ACCURACY / 2; + + int distance = sqrt((loc_x - pnt.x) * (loc_x - pnt.x) + (loc_y - pnt.y) * (loc_y - pnt.y)); + + float bright = get_func_brithtness(distance); + CRGB clr = getPix(i, j); + clr.r = qadd8(clr.r, (float)pnt.color.r * bright); + clr.g = qadd8(clr.g, (float)pnt.color.g * bright); + clr.b = qadd8(clr.b, (float)pnt.color.b * bright); + setPixColor(i, j, clr); + } + } +} + +void points_prepare() +{ + int i; + data->tick = 0; + data->point_size = 50; + data->bright_radius = 400; + + for (i = 0; i < POINTS_AMNT; ++i) { + data->points[i].x = random16(0, (WIDTH - 1) * ACCURACY); + data->points[i].y = random16(0, (HEIGHT - 1) * ACCURACY); + + data->points[i].vec_x = (int32_t)random(0, MAX_VEC_SIZE * 2) - MAX_VEC_SIZE; + data->points[i].vec_y = (int32_t)random(0, MAX_VEC_SIZE * 2) - MAX_VEC_SIZE; + data->points[i].color = CHSV(random8(), 255, 255); + } + + if (POINTS_AMNT >= 3) { + data->points[0].color = 0xff; + data->points[1].color = 0xff00; + data->points[2].color = 0xff0000; + } + + eff_set_ups(60); +} + +void points_update() +{ + FastLED.clear(); + + int i; + + data->tick++; + for (i = 0; i < POINTS_AMNT; ++i) { + move_point(data->points[i]); + render_point(data->points[i]); + } +} + +#undef data diff --git a/src/effects/15_rainbow_point.h b/src/effects/15_rainbow_point.h new file mode 100644 index 0000000..ca7b49e --- /dev/null +++ b/src/effects/15_rainbow_point.h @@ -0,0 +1,91 @@ +#pragma once + +#define data ((RainbowPointData *)global_data) + +#define ACCURACY 100 +#define MAX_VEC_SIZE 10 +#define RAINBOW_TICK_SIZE 4 //кол-во тиков до инкремента тика радуги + +struct RainbowPointData +{ + int32_t x; + int32_t y; + int32_t vec_x; + int32_t vec_y; + int tick; +}; + +//arg2: horizontal barrier = true or vertical = false +void rainbow_point_gen_vector(bool horVer) { + int16_t dir = horVer ? 1 : -1; + + data->vec_x = (data->vec_x > 0 ? -dir : dir) * random(0, MAX_VEC_SIZE); + data->vec_y = (data->vec_y > 0 ? dir : -dir) * random(0, MAX_VEC_SIZE); + + if (data->vec_y == 0 && data->vec_x == 0) { + rainbow_point_gen_vector(horVer); + } +} + +void rainbow_point_move_point() { + data->x += data->vec_x; + data->y += data->vec_y; + + if (data->x < 0) { + data->x = 0; + rainbow_point_gen_vector(true); + } else if (data->x >= ACCURACY * WIDTH) { + data->x = ACCURACY * WIDTH - 1; + rainbow_point_gen_vector(true); + } + + if (data->y < 0) { + data->y = 0; + rainbow_point_gen_vector(false); + } else if (data->y >= ACCURACY * HEIGHT) { + data->y = ACCURACY * HEIGHT - 1; + rainbow_point_gen_vector(false); + } +} + +void rainbow_point_render_point() { + int i, j; + for (i = 0; i < WIDTH; ++i) { + for(j = 0; j < HEIGHT; ++j) { + int loc_x = i * ACCURACY + ACCURACY / 2; + int loc_y = j * ACCURACY + ACCURACY / 2; + + int distance = sqrt((loc_x - data->x) * (loc_x - data->x) + (loc_y - data->y) * (loc_y - data->y)); + + float chsv = (distance / 8 + data->tick / RAINBOW_TICK_SIZE) % MAX_HSV; + + setPixColor(i, j, CHSV(chsv, 255, 255)); + } + } +} + +void rainbow_point_prepare() +{ + data->tick = 0; + data->x = random16(0, (WIDTH - 1) * ACCURACY); + data->y = random16(0, (HEIGHT - 1) * ACCURACY); + + data->vec_x = (int32_t)random(0, MAX_VEC_SIZE * 2) - MAX_VEC_SIZE; + data->vec_y = (int32_t)random(0, MAX_VEC_SIZE * 2) - MAX_VEC_SIZE; + + eff_set_ups(60); +} + +void rainbow_point_update() +{ + FastLED.clear(); + + int i; + + data->tick = (data->tick + 1) % ((MAX_HSV + 1) * RAINBOW_TICK_SIZE); + + rainbow_point_move_point(); + rainbow_point_render_point(); +} + +#undef data diff --git a/src/effects/16_rainbow_static_point.h b/src/effects/16_rainbow_static_point.h new file mode 100644 index 0000000..6dc1e44 --- /dev/null +++ b/src/effects/16_rainbow_static_point.h @@ -0,0 +1,44 @@ +#pragma once + +#define data ((RainbowStaticPointData *)global_data) + +#define ACCURACY 10 +#define RAINBOW_TICK_SIZE 4 //кол-во тиков до инкремента тика радуги + +struct RainbowStaticPointData +{ + int tick; +}; + +static void rainbow_static_point_render_point() { + int i, j; + int x = WIDTH / 2 * ACCURACY; + int y = HEIGHT / 2 * ACCURACY; + + for (i = 0; i < WIDTH; ++i) { + for(j = 0; j < HEIGHT; ++j) { + int loc_x = i * ACCURACY + ACCURACY / 2; + int loc_y = j * ACCURACY + ACCURACY / 2; + + int distance = sqrt((loc_x - x) * (loc_x - x) + (loc_y - y) * (loc_y - y)); + + float chsv = (distance + data->tick / RAINBOW_TICK_SIZE) % MAX_HSV; + + setPixColor(i, j, CHSV(chsv, 255, 255)); + } + } +} + +void rainbow_static_point_prepare() +{ + data->tick = 0; + eff_set_ups(60); +} + +void rainbow_static_point_update() +{ + data->tick = (data->tick + 1) % ((MAX_HSV + 1) * RAINBOW_TICK_SIZE); + rainbow_static_point_render_point(); +} + +#undef data From f624812bdc9a76fd5c08fa7061db15ffa0609333 Mon Sep 17 00:00:00 2001 From: npo6ka Date: Sat, 14 Dec 2019 19:15:12 +0300 Subject: [PATCH 02/75] minor changes --- .vscode/extensions.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .vscode/extensions.json diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..272828b --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ] +} \ No newline at end of file From 9434e85b60f1167afce02ce520f4b2cb39623681 Mon Sep 17 00:00:00 2001 From: npo6ka Date: Sat, 14 Dec 2019 19:27:01 +0300 Subject: [PATCH 03/75] add SimpleRainbow effect --- src/button/button_handler.cpp | 2 +- src/effects/01_simple_rainbow.h | 42 ++++++++++++++++----------------- src/effects/effectslist.cpp | 8 +++---- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/button/button_handler.cpp b/src/button/button_handler.cpp index 5a9ad1e..d0eb903 100644 --- a/src/button/button_handler.cpp +++ b/src/button/button_handler.cpp @@ -11,7 +11,7 @@ GButton touch(BTN_PIN, LOW_PULL, NORM_OPEN); -bool auto_mode = true; +bool auto_mode = false; long int auto_mode_cnt = 0; void setup_buttons() { diff --git a/src/effects/01_simple_rainbow.h b/src/effects/01_simple_rainbow.h index ca68732..165e83d 100644 --- a/src/effects/01_simple_rainbow.h +++ b/src/effects/01_simple_rainbow.h @@ -1,33 +1,33 @@ #pragma once -#define data ((SimpleRaibowData *)global_data) +#include "effect.h" -struct SimpleRaibowData +class SimpleRainbow : public Effect { int tick; int phaseShift; -}; +public: + SimpleRainbow() {} -void simple_rainbow_prepare() -{ - data->tick = 0; - data->phaseShift = 10; + void on_init() + { + tick = 0; + phaseShift = 10; - eff_set_ups(20); -} + set_fps(30); + } -void simple_rainbow_update() -{ - uint8_t x, y; - data->tick = data->tick % (MAX_HSV + 1); + void on_update() + { + uint8_t x, y; + tick = tick % (MAX_HSV + 1); - for (x = 0; x < 10; x++) { - for (y = 0; y < 10; y++) { - getPix(x, y) = CHSV((data->tick + x + y * data->phaseShift / 2) % (MAX_HSV + 1), 255, 255); + for (x = 0; x < HEIGHT; x++) { + for (y = 0; y < WIDTH; y++) { + getPix(x, y) = CHSV((tick + x + y * phaseShift / 2) % (MAX_HSV + 1), 255, 255); + } } - } - - data->tick += 1; -} -#undef data + tick += 1; + } +}; \ No newline at end of file diff --git a/src/effects/effectslist.cpp b/src/effects/effectslist.cpp index 6e3cc19..e860ed9 100644 --- a/src/effects/effectslist.cpp +++ b/src/effects/effectslist.cpp @@ -1,8 +1,8 @@ #include "effectslist.h" #include "00_slow_random.h" -/*#include "01_simple_rainbow.h" -#include "02_dribs.h" +#include "01_simple_rainbow.h" +/*#include "02_dribs.h" #include "03_rain.h" #include "04_all_random.h" #include "05_show.h" @@ -50,9 +50,9 @@ Effect *EffectsList::getNewEffectInstance(int num) { return new DynamicSquare(); case 1: return new SlowRandom(); - /*case 2: + case 2: return new SimpleRainbow(); - case 3: + /*case 3: return new Dribs(); case 4: return new Rain(); From a24a0557af62feec508b780a17ef494919a72ecc Mon Sep 17 00:00:00 2001 From: npo6ka Date: Sat, 14 Dec 2019 20:04:02 +0300 Subject: [PATCH 04/75] add dribs effect --- src/effects/00_slow_random.h | 8 ++--- src/effects/02_dribs.h | 59 ++++++++++++++++++------------------ src/effects/effectslist.cpp | 8 ++--- src/effects/lib_led.h | 3 ++ 4 files changed, 40 insertions(+), 38 deletions(-) diff --git a/src/effects/00_slow_random.h b/src/effects/00_slow_random.h index 92437c6..c5be1fd 100644 --- a/src/effects/00_slow_random.h +++ b/src/effects/00_slow_random.h @@ -27,8 +27,8 @@ class SlowRandom : public Effect uint8_t i, j; - for (i = 0; i < WIDTH; i++) { - for (j = 0; j < HEIGHT; j++) { + for (i = 0; i < HEIGHT; i++) { + for (j = 0; j < WIDTH; j++) { CRGB cur_cl = getPixColor(i, j); inc_val[i][j] = gen_led(cur_cl.r) << 4; @@ -43,8 +43,8 @@ class SlowRandom : public Effect void on_update() { uint8_t i, j, buf; - for (i = 0; i < WIDTH; i++) { - for (j = 0; j < HEIGHT; j++) { + for (i = 0; i < HEIGHT; i++) { + for (j = 0; j < WIDTH; j++) { CRGB cur_cl = getPixColor(i, j); buf = proc_val(cur_cl.r, (inc_val[i][j] >> 4) & 0x3) << 4; buf |= proc_val(cur_cl.g, (inc_val[i][j] >> 2) & 0x3) << 2; diff --git a/src/effects/02_dribs.h b/src/effects/02_dribs.h index bfcfc9e..fcb4b65 100644 --- a/src/effects/02_dribs.h +++ b/src/effects/02_dribs.h @@ -1,43 +1,42 @@ #pragma once -#define data ((DribsData *)global_data) +#include "effect.h" -struct DribsData +#define WIDTH 10 + +class Dribs : public Effect { uint8_t cur_drib[WIDTH]; uint8_t lenght; -}; - -void dribs_prepare() -{ - data->lenght = 12; - - eff_set_ups(20); -} +public: + void on_init() + { + lenght = 12; + set_fps(30); + } -void dribs_update() -{ - for (int i = 0; i < WIDTH; ++i) { - if (data->cur_drib[i] == 0 && random8(40) == 0) { - data->cur_drib[i] = 1; - } else if (data->cur_drib[i] != 0) { - data->cur_drib[i] += 1; - if (data->cur_drib[i] >= data->lenght + HEIGHT) { - data->cur_drib[i] = 0; + void on_update() + { + for (int i = 0; i < WIDTH; ++i) { + if (cur_drib[i] == 0 && random8(40) == 0) { + cur_drib[i] = 1; + } else if (cur_drib[i] != 0) { + cur_drib[i] += 1; + if (cur_drib[i] >= lenght + WIDTH) { + cur_drib[i] = 0; + } } } - } - int step = MAX_BRIGHTNESS / data->lenght; - for (int i = 0; i < WIDTH; ++i) { - for (int j = 0; j < HEIGHT; ++j) { - if (j < data->cur_drib[i] && data->cur_drib[i] != 0) { - getPix(i, j) = CRGB(0, 0, max(MAX_BRIGHTNESS - (data->cur_drib[i] - j - 1) * step, 0)); - } else { - getPix(i, j) = 0x0; + int step = MAX_BRIGHTNESS / lenght; + for (int j = 0; j < WIDTH; ++j) { + for (int i = 0; i < HEIGHT; ++i) { + if (cur_drib[j] != 0 && i < cur_drib[j]) { + getPix(HEIGHT - i - 1, j) = CRGB(0, 0, max(MAX_BRIGHTNESS - (cur_drib[j] - i - 1) * step, 0)); + } else { + getPix(HEIGHT - i - 1, j) = 0x0; + } } } } -} - -#undef data +}; \ No newline at end of file diff --git a/src/effects/effectslist.cpp b/src/effects/effectslist.cpp index e860ed9..de29f27 100644 --- a/src/effects/effectslist.cpp +++ b/src/effects/effectslist.cpp @@ -2,8 +2,8 @@ #include "00_slow_random.h" #include "01_simple_rainbow.h" -/*#include "02_dribs.h" -#include "03_rain.h" +#include "02_dribs.h" +/*#include "03_rain.h" #include "04_all_random.h" #include "05_show.h" #include "06_fire.h" @@ -52,9 +52,9 @@ Effect *EffectsList::getNewEffectInstance(int num) { return new SlowRandom(); case 2: return new SimpleRainbow(); - /*case 3: + case 3: return new Dribs(); - case 4: + /*case 4: return new Rain(); case 5: return new AllRandom(); diff --git a/src/effects/lib_led.h b/src/effects/lib_led.h index b13cfed..ddc5f09 100644 --- a/src/effects/lib_led.h +++ b/src/effects/lib_led.h @@ -100,6 +100,7 @@ static CRGB &getPix(int x, int y) { if (x < 0 || x >= WIDTH || y < 0 || y >= HEIGHT) { Serial.print("Value out of range in function getPix "); Serial.print(x); + Serial.print(" "); Serial.println(y); return leds[0]; } @@ -115,6 +116,7 @@ static uint32_t getPixColor(int x, int y) { if (x < 0 || x >= WIDTH || y < 0 || y >= HEIGHT) { Serial.print("Value out of range in function getPixColor "); Serial.print(x); + Serial.print(" "); Serial.println(y); return 0; } @@ -126,6 +128,7 @@ static void setPixColor(int x, int y, CRGB color) { if (x < 0 || x >= WIDTH || y < 0 || y >= HEIGHT) { Serial.print("Value out of range in function setPixColor "); Serial.print(x); + Serial.print(" "); Serial.println(y); return; } From ccc67a908d82dd34339faa21a3114ac6c591ccc5 Mon Sep 17 00:00:00 2001 From: npo6ka Date: Sat, 14 Dec 2019 20:11:21 +0300 Subject: [PATCH 05/75] add rain effect --- src/effects/02_dribs.h | 2 -- src/effects/03_rain.h | 17 +++++++++-------- src/effects/effectslist.cpp | 8 ++++---- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/effects/02_dribs.h b/src/effects/02_dribs.h index fcb4b65..3955c71 100644 --- a/src/effects/02_dribs.h +++ b/src/effects/02_dribs.h @@ -2,8 +2,6 @@ #include "effect.h" -#define WIDTH 10 - class Dribs : public Effect { uint8_t cur_drib[WIDTH]; diff --git a/src/effects/03_rain.h b/src/effects/03_rain.h index b90d3b4..f197a81 100644 --- a/src/effects/03_rain.h +++ b/src/effects/03_rain.h @@ -9,15 +9,18 @@ class Rain : public Effect public: Rain() {} - void on_tick() { + void on_init() { + set_fps(60); + } + + void on_update() { int i, j; - for (i = 0; i < WIDTH; ++i) { - for (j = 0; j < HEIGHT; ++j) { - CRGB cl = getPix(i, j); + for (i = 0; i < HEIGHT; ++i) { + for (j = 0; j < WIDTH; ++j) { + CRGB &cl = getPix(i, j); - if (random8(100) == 0) { - int asd = 0; + if (random8(255) == 0) { cl = CRGB(0, 0, MAX_BRIGHTNESS); } else if (cl.b > 0) { if (cl.b > step) { @@ -26,8 +29,6 @@ class Rain : public Effect cl = CRGB(0, 0, 0); } } - - getPix(i, j) = cl; } } } diff --git a/src/effects/effectslist.cpp b/src/effects/effectslist.cpp index de29f27..5dbfc0c 100644 --- a/src/effects/effectslist.cpp +++ b/src/effects/effectslist.cpp @@ -3,8 +3,8 @@ #include "00_slow_random.h" #include "01_simple_rainbow.h" #include "02_dribs.h" -/*#include "03_rain.h" -#include "04_all_random.h" +#include "03_rain.h" +/*#include "04_all_random.h" #include "05_show.h" #include "06_fire.h" #include "07_the_matrix.h" @@ -54,9 +54,9 @@ Effect *EffectsList::getNewEffectInstance(int num) { return new SimpleRainbow(); case 3: return new Dribs(); - /*case 4: + case 4: return new Rain(); - case 5: + /*case 5: return new AllRandom(); case 6: return new Snow(); From 19b70c84cd551342279d1c70c9bf6f991bedb2b6 Mon Sep 17 00:00:00 2001 From: npo6ka Date: Sat, 14 Dec 2019 20:27:14 +0300 Subject: [PATCH 06/75] add allRandom effect --- src/effects/04_all_random.h | 7 +++---- src/effects/effectslist.cpp | 8 ++++---- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/effects/04_all_random.h b/src/effects/04_all_random.h index 89184af..8185a60 100644 --- a/src/effects/04_all_random.h +++ b/src/effects/04_all_random.h @@ -4,20 +4,19 @@ class AllRandom : public Effect { - uint8_t step = 2; public: AllRandom() {} void on_init() { - + set_fps(30); } - void on_tick() { + void on_update() { int i; for (i = 0; i < LEDS_CNT; i++) { - getPix(i / 10, i % 10) = CRGB(random8(255), random8(255), random8(255)); + getPix(i / WIDTH, i % WIDTH) = CRGB(random8(255), random8(255), random8(255)); } } }; diff --git a/src/effects/effectslist.cpp b/src/effects/effectslist.cpp index 5dbfc0c..d6f77b3 100644 --- a/src/effects/effectslist.cpp +++ b/src/effects/effectslist.cpp @@ -4,8 +4,8 @@ #include "01_simple_rainbow.h" #include "02_dribs.h" #include "03_rain.h" -/*#include "04_all_random.h" -#include "05_show.h" +#include "04_all_random.h" +/*#include "05_show.h" #include "06_fire.h" #include "07_the_matrix.h" #include "08_simple_balls.h" @@ -56,9 +56,9 @@ Effect *EffectsList::getNewEffectInstance(int num) { return new Dribs(); case 4: return new Rain(); - /*case 5: + case 5: return new AllRandom(); - case 6: + /*case 6: return new Snow(); case 7: return new Fire(); From 80d028b6fccb412d6813aa536b5589b0d0a4c7f2 Mon Sep 17 00:00:00 2001 From: npo6ka Date: Sat, 14 Dec 2019 22:47:15 +0300 Subject: [PATCH 07/75] add snow effect --- src/effects/05_snow.h | 92 ++++++++++++++++++------------------- src/effects/effectslist.cpp | 8 ++-- 2 files changed, 50 insertions(+), 50 deletions(-) diff --git a/src/effects/05_snow.h b/src/effects/05_snow.h index 01ffaa4..d6dc6f4 100644 --- a/src/effects/05_snow.h +++ b/src/effects/05_snow.h @@ -1,63 +1,63 @@ #pragma once -#define data ((SnowData *)global_data) +#include "effect.h" -struct SnowData +class Snow : public Effect { uint8_t step; uint8_t tick; int density; bool direction; -}; +public: + Snow() {} + + void on_init() + { + step = 20; + density = 10; + tick = 0; + direction = false; + set_fps(60); + } -void snow_prepare() -{ - data->step = 20; - data->density = 10; - data->tick = 0; - data->direction = false; - eff_set_ups(60); -} - -void snow_update() -{ - if (data->tick >= data->step) { - data->direction = !data->direction; - // сдвигаем вниз - for (int8_t y = HEIGHT - 1; y >= 0; y--) { - bool dir = data->direction; - - for (uint8_t x = 0; x < WIDTH; x++) { - if (getPixColor(x, y)) { - if (y + 1 < HEIGHT) { - if (dir) { - setPixColor(x + 1, y + 1, getPixColor(x, y)); - } else { - setPixColor(x - 1, y + 1, getPixColor(x, y)); + void on_update() + { + if (tick >= step) { + direction = !direction; + // сдвигаем вниз + for (int8_t x = 0; x < HEIGHT; x++) { + bool dir = direction; + + for (uint8_t y = 0; y < WIDTH; y++) { + if (getPixColor(x, y)) { + if (x - 1 >= 0) { + if (dir) { + if (y + 1 < WIDTH) setPixColor(x - 1, y + 1, getPixColor(x, y)); + } else { + if (y - 1 >= 0) setPixColor(x - 1, y - 1, getPixColor(x, y)); + } + dir = !dir; } - dir = !dir; - } - setPixColor(x, y, 0); + setPixColor(x, y, 0); + } } } - } - for (uint8_t x = 0; x < WIDTH - 1; x++) { - // заполняем случайно верхнюю строку - // а также не даём двум блокам по вертикали вместе быть - if (getPixColor(x, 1) == 0 && (random(0, data->density) == 0)) { - setPixColor(x, 0, 0xE0FFFF - 0x101010 * random(0, 4)); - x++; - } else { - setPixColor(x, 0, 0x000000); + for (uint8_t x = 0; x < WIDTH - 1; x++) { + // заполняем случайно верхнюю строку + // а также не даём двум блокам по вертикали вместе быть + if (getPixColor(HEIGHT - 2, x) == 0 && (random(0, density) == 0)) { + setPixColor(HEIGHT - 1, x, 0xE0FFFF - 0x101010 * random(0, 4)); + x++; + } else { + setPixColor(HEIGHT - 1, x, 0x000000); + } } - } - data->tick = 0; - } else { - data->tick++; + tick = 0; + } else { + tick++; + } } -} - -#undef data +}; \ No newline at end of file diff --git a/src/effects/effectslist.cpp b/src/effects/effectslist.cpp index d6f77b3..3941569 100644 --- a/src/effects/effectslist.cpp +++ b/src/effects/effectslist.cpp @@ -5,8 +5,8 @@ #include "02_dribs.h" #include "03_rain.h" #include "04_all_random.h" -/*#include "05_show.h" -#include "06_fire.h" +#include "05_snow.h" +/*#include "06_fire.h" #include "07_the_matrix.h" #include "08_simple_balls.h" #include "09_confetti.h" @@ -58,9 +58,9 @@ Effect *EffectsList::getNewEffectInstance(int num) { return new Rain(); case 5: return new AllRandom(); - /*case 6: + case 6: return new Snow(); - case 7: + /*case 7: return new Fire(); case 8: return new TheMatrix(); From db02a2c825cf038a697ed8a64d316cee97c84dfe Mon Sep 17 00:00:00 2001 From: npo6ka Date: Sat, 14 Dec 2019 23:08:50 +0300 Subject: [PATCH 08/75] add fire mode --- src/effects/06_fire.h | 16 ++++++++-------- src/effects/effectslist.cpp | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/effects/06_fire.h b/src/effects/06_fire.h index 55efb6f..c9b8c2f 100644 --- a/src/effects/06_fire.h +++ b/src/effects/06_fire.h @@ -5,7 +5,7 @@ class Fire : public Effect { //these values are substracetd from the generated values to give a shape to the animation - const unsigned char valueMask[8][16] PROGMEM = { + const unsigned char valueMask[8][16] = { {32 , 0 , 0 , 0 , 0 , 0 , 0 , 32 , 32 , 0 , 0 , 0 , 0 , 0 , 0 , 32 }, {64 , 0 , 0 , 0 , 0 , 0 , 0 , 64 , 64 , 0 , 0 , 0 , 0 , 0 , 0 , 64 }, {96 , 32 , 0 , 0 , 0 , 0 , 32 , 96 , 96 , 32 , 0 , 0 , 0 , 0 , 32 , 96 }, @@ -18,7 +18,7 @@ class Fire : public Effect //these are the hues for the fire, //should be between 0 (red) to about 25 (yellow) - const unsigned char hueMask[8][16] PROGMEM = { + const unsigned char hueMask[8][16] = { {1 , 11, 19, 25, 25, 22, 11, 1 , 1 , 11, 19, 25, 25, 22, 11, 1 }, {1 , 8 , 13, 19, 25, 19, 8 , 1 , 1 , 8 , 13, 19, 25, 19, 8 , 1 }, {1 , 8 , 13, 16, 19, 16, 8 , 1 , 1 , 8 , 13, 16, 19, 16, 8 , 1 }, @@ -32,7 +32,7 @@ class Fire : public Effect unsigned char matrixValue[8][16]; unsigned char line[WIDTH]; int pcnt = 0; - uint8_t hue_add = 0; // добавка цвета в огонь (от 0 до 230) - меняет весь цвет пламени + uint8_t hue_add = 75; // добавка цвета в огонь (от 0 до 230) - меняет весь цвет пламени bool sparkless = true; // вылетающие угольки вкл выкл public: @@ -43,7 +43,7 @@ class Fire : public Effect memset(matrixValue, 0, sizeof(matrixValue)); } - void on_tick() { + void on_update() { if (pcnt >= 100) { shiftUp(); generateLine(); @@ -96,13 +96,13 @@ class Fire : public Effect newX = x - 15; } if (y < 8) { - uint8_t val = pgm_read_byte(&(valueMask[y][newX])); + uint8_t val = valueMask[y][newX]; nextv = (((100.0 - pcnt) * matrixValue[y][newX] + pcnt * matrixValue[y - 1][newX]) / 100.0) - - pgm_read_byte(&(valueMask[y][newX])); + - valueMask[y][newX]; CRGB color = CHSV( - hue_add + pgm_read_byte(&(hueMask[y][newX])), // H + hue_add + hueMask[y][newX], // H 255, // S (uint8_t)max(0, nextv) // V ); @@ -132,7 +132,7 @@ class Fire : public Effect newX = x - 15; } CRGB color = CHSV( - hue_add + pgm_read_byte(&(hueMask[0][newX])), // H + hue_add + hueMask[0][newX], // H 255, // S (uint8_t)(((100.0 - pcnt) * matrixValue[0][newX] + pcnt * line[newX]) / 100.0) // V ); diff --git a/src/effects/effectslist.cpp b/src/effects/effectslist.cpp index 3941569..3b4561f 100644 --- a/src/effects/effectslist.cpp +++ b/src/effects/effectslist.cpp @@ -6,8 +6,8 @@ #include "03_rain.h" #include "04_all_random.h" #include "05_snow.h" -/*#include "06_fire.h" -#include "07_the_matrix.h" +#include "06_fire.h" +/*#include "07_the_matrix.h" #include "08_simple_balls.h" #include "09_confetti.h" #include "10_starfall.h"*/ @@ -60,9 +60,9 @@ Effect *EffectsList::getNewEffectInstance(int num) { return new AllRandom(); case 6: return new Snow(); - /*case 7: + case 7: return new Fire(); - case 8: + /*case 8: return new TheMatrix(); case 9: return new SimpleBalls(); From 26d3e574dd6f4967ec6d392a1fd8e8d39f5debde Mon Sep 17 00:00:00 2001 From: npo6ka Date: Sat, 14 Dec 2019 23:33:33 +0300 Subject: [PATCH 09/75] change fire color --- src/effects/06_fire.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/effects/06_fire.h b/src/effects/06_fire.h index c9b8c2f..b6e717b 100644 --- a/src/effects/06_fire.h +++ b/src/effects/06_fire.h @@ -32,7 +32,7 @@ class Fire : public Effect unsigned char matrixValue[8][16]; unsigned char line[WIDTH]; int pcnt = 0; - uint8_t hue_add = 75; // добавка цвета в огонь (от 0 до 230) - меняет весь цвет пламени + uint8_t hue_add = 73; // добавка цвета в огонь (от 0 до 230) - меняет весь цвет пламени bool sparkless = true; // вылетающие угольки вкл выкл public: From 40e2cab931951ca65b3c57a29599d7a8b7b4bc2f Mon Sep 17 00:00:00 2001 From: npo6ka Date: Sat, 14 Dec 2019 23:47:38 +0300 Subject: [PATCH 10/75] fix led preset and add matrix mode --- src/effects/06_fire.h | 3 +-- src/effects/07_the_matrix.h | 4 ++-- src/effects/effectslist.cpp | 8 ++++---- src/effects/lib_led.h | 2 +- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/effects/06_fire.h b/src/effects/06_fire.h index b6e717b..3c15764 100644 --- a/src/effects/06_fire.h +++ b/src/effects/06_fire.h @@ -32,7 +32,7 @@ class Fire : public Effect unsigned char matrixValue[8][16]; unsigned char line[WIDTH]; int pcnt = 0; - uint8_t hue_add = 73; // добавка цвета в огонь (от 0 до 230) - меняет весь цвет пламени + uint8_t hue_add = 0; // добавка цвета в огонь (от 0 до 230) - меняет весь цвет пламени bool sparkless = true; // вылетающие угольки вкл выкл public: @@ -96,7 +96,6 @@ class Fire : public Effect newX = x - 15; } if (y < 8) { - uint8_t val = valueMask[y][newX]; nextv = (((100.0 - pcnt) * matrixValue[y][newX] + pcnt * matrixValue[y - 1][newX]) / 100.0) - valueMask[y][newX]; diff --git a/src/effects/07_the_matrix.h b/src/effects/07_the_matrix.h index f9e9a22..599eea2 100644 --- a/src/effects/07_the_matrix.h +++ b/src/effects/07_the_matrix.h @@ -9,10 +9,10 @@ class TheMatrix : public Effect TheMatrix() {} void on_init() { - + set_fps(30); } - void on_tick() { + void on_update() { for (uint8_t x = 0; x < WIDTH; x++) { // заполняем случайно верхнюю строку uint32_t thisColor = getPixColor(x, HEIGHT - 1); diff --git a/src/effects/effectslist.cpp b/src/effects/effectslist.cpp index 3b4561f..fd4fa77 100644 --- a/src/effects/effectslist.cpp +++ b/src/effects/effectslist.cpp @@ -7,8 +7,8 @@ #include "04_all_random.h" #include "05_snow.h" #include "06_fire.h" -/*#include "07_the_matrix.h" -#include "08_simple_balls.h" +#include "07_the_matrix.h" +/*#include "08_simple_balls.h" #include "09_confetti.h" #include "10_starfall.h"*/ #include "11_dynamic_square.h" @@ -62,9 +62,9 @@ Effect *EffectsList::getNewEffectInstance(int num) { return new Snow(); case 7: return new Fire(); - /*case 8: + case 8: return new TheMatrix(); - case 9: + /*case 9: return new SimpleBalls(); case 10: return new Confetti(); diff --git a/src/effects/lib_led.h b/src/effects/lib_led.h index ddc5f09..5554e9a 100644 --- a/src/effects/lib_led.h +++ b/src/effects/lib_led.h @@ -59,7 +59,7 @@ static void drawLine(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, CRGB color) */ static void led_setup() { - FastLED.addLeds(leds, LEDS_CNT); + FastLED.addLeds(leds, LEDS_CNT).setCorrection(TypicalLEDStrip); FastLED.setMaxPowerInVoltsAndMilliamps(5, CURRENT_LIMIT); FastLED.clear(); From 1daa5c50517968eadb196ff5a1b316eb69a179cc Mon Sep 17 00:00:00 2001 From: npo6ka Date: Sun, 15 Dec 2019 13:22:45 +0300 Subject: [PATCH 11/75] add simple balls and confetti modes --- src/effects/08_simple_balls.h | 2 +- src/effects/09_confetti.h | 2 +- src/effects/effectslist.cpp | 8 ++++---- src/main.cpp | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/effects/08_simple_balls.h b/src/effects/08_simple_balls.h index ff9176f..27680ab 100644 --- a/src/effects/08_simple_balls.h +++ b/src/effects/08_simple_balls.h @@ -31,7 +31,7 @@ class SimpleBalls : public Effect } } - void on_tick() { + void on_update() { if (!ball_track) // если режим БЕЗ следов шариков FastLED.clear(); // очистить else { // режим со следами diff --git a/src/effects/09_confetti.h b/src/effects/09_confetti.h index 4a91532..23c8e11 100644 --- a/src/effects/09_confetti.h +++ b/src/effects/09_confetti.h @@ -13,7 +13,7 @@ class Confetti : public Effect } - void on_tick() { + void on_update() { for (uint8_t i = 0; i < density; i++) { uint8_t x = random(WIDTH); uint8_t y = random(HEIGHT); diff --git a/src/effects/effectslist.cpp b/src/effects/effectslist.cpp index fd4fa77..b037fbc 100644 --- a/src/effects/effectslist.cpp +++ b/src/effects/effectslist.cpp @@ -8,9 +8,9 @@ #include "05_snow.h" #include "06_fire.h" #include "07_the_matrix.h" -/*#include "08_simple_balls.h" +#include "08_simple_balls.h" #include "09_confetti.h" -#include "10_starfall.h"*/ +/*#include "10_starfall.h"*/ #include "11_dynamic_square.h" /* #include "testmode.h"*/ @@ -64,11 +64,11 @@ Effect *EffectsList::getNewEffectInstance(int num) { return new Fire(); case 8: return new TheMatrix(); - /*case 9: + case 9: return new SimpleBalls(); case 10: return new Confetti(); - case 11: + /*case 11: return new Starfall(); case 12: return new TestMode(); diff --git a/src/main.cpp b/src/main.cpp index 487b3fd..2b50ba5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -12,6 +12,8 @@ void setup() { // инициализация кнопок setup_buttons(); + + FastLED.setBrightness(64); } //unsigned long tick = 0; @@ -21,8 +23,6 @@ void loop() { EffectsList::getInstance().onTick(); tick_buttons(); - - FastLED.setBrightness(64); // проверка реального тпс работы микроконтроллера /*tps++; From c66205510052c0009808da0759261fb406a13c2d Mon Sep 17 00:00:00 2001 From: npo6ka Date: Sun, 15 Dec 2019 13:31:35 +0300 Subject: [PATCH 12/75] add starfall mode --- src/effects/10_starfall.h | 4 ++-- src/effects/effectslist.cpp | 28 ++++++++++++++-------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/effects/10_starfall.h b/src/effects/10_starfall.h index 2d793c2..0316fa9 100644 --- a/src/effects/10_starfall.h +++ b/src/effects/10_starfall.h @@ -11,10 +11,10 @@ class Starfall : public Effect Starfall () {} void on_init() { - + set_fps(30); } - void on_tick() { + void on_update() { // заполняем головами комет левую и верхнюю линию for (uint8_t i = HEIGHT / 2; i < HEIGHT; i++) { if (!getPixColor(0, i) diff --git a/src/effects/effectslist.cpp b/src/effects/effectslist.cpp index b037fbc..03d4a38 100644 --- a/src/effects/effectslist.cpp +++ b/src/effects/effectslist.cpp @@ -10,7 +10,7 @@ #include "07_the_matrix.h" #include "08_simple_balls.h" #include "09_confetti.h" -/*#include "10_starfall.h"*/ +#include "10_starfall.h" #include "11_dynamic_square.h" /* #include "testmode.h"*/ @@ -47,30 +47,30 @@ void EffectsList::init() { Effect *EffectsList::getNewEffectInstance(int num) { switch (num) { case 0: - return new DynamicSquare(); - case 1: return new SlowRandom(); - case 2: + case 1: return new SimpleRainbow(); - case 3: + case 2: return new Dribs(); - case 4: + case 3: return new Rain(); - case 5: + case 4: return new AllRandom(); - case 6: + case 5: return new Snow(); - case 7: + case 6: return new Fire(); - case 8: + case 7: return new TheMatrix(); - case 9: + case 8: return new SimpleBalls(); - case 10: + case 9: return new Confetti(); - /*case 11: + case 10: return new Starfall(); - case 12: + case 11: + return new DynamicSquare(); + /*case 12: return new TestMode(); case 13: return new Rain(); From ebb48eff43548fe28a5411502a3fc8bb268ab3d6 Mon Sep 17 00:00:00 2001 From: npo6ka Date: Sun, 15 Dec 2019 13:55:50 +0300 Subject: [PATCH 13/75] add rains effects --- src/effects/00_slow_random.h | 4 +- src/effects/12_random_rain.h | 74 +++++++++++++++++------------------ src/effects/13_rainbow_rain.h | 58 +++++++++++++-------------- src/effects/effectslist.cpp | 10 +++-- 4 files changed, 73 insertions(+), 73 deletions(-) diff --git a/src/effects/00_slow_random.h b/src/effects/00_slow_random.h index c5be1fd..46fd76b 100644 --- a/src/effects/00_slow_random.h +++ b/src/effects/00_slow_random.h @@ -5,7 +5,7 @@ class SlowRandom : public Effect { uint8_t inc_val[WIDTH][HEIGHT]; - int step = 2; + int step; public: SlowRandom() {} @@ -22,7 +22,7 @@ class SlowRandom : public Effect } void on_init() { - memset8(inc_val, 0, LEDS_CNT); + step = 2; set_fps(120); uint8_t i, j; diff --git a/src/effects/12_random_rain.h b/src/effects/12_random_rain.h index 4d90ea0..96fa906 100644 --- a/src/effects/12_random_rain.h +++ b/src/effects/12_random_rain.h @@ -1,44 +1,42 @@ #pragma once -#define data ((RandomRainData *)global_data) +#include "effect.h" -struct RandomRainData +class RandomRain : public Effect { uint8_t step; -}; -void random_rain_prepare() -{ - data->step = 2; - eff_set_ups(30); -} - -void random_rain_update() -{ - int i, j; - - for (i = 0; i < WIDTH; ++i) { - for (j = 0; j < HEIGHT; ++j) { - fadePix(i, j, data->step); - - CRGB cl = getPix(i, j); - - if (random16(500) == 0) { - int asd = 0; - cl.r = MAX_BRIGHTNESS; - } - if (random16(500) == 0) { - int asd = 0; - cl.g = MAX_BRIGHTNESS; - } - if (random16(500) == 0) { - int asd = 0; - cl.b = MAX_BRIGHTNESS; - } - - setPixColor(i, j, cl); - } - } -} - -#undef data +public: + RandomRain() {} + + void on_init() + { + step = 2; + set_fps(30); + } + + void on_update() + { + int i, j; + + for (i = 0; i < WIDTH; ++i) { + for (j = 0; j < HEIGHT; ++j) { + fadePix(i, j, step); + + CRGB cl = getPix(i, j); + + if (random16(500) == 0) { + cl.r = MAX_BRIGHTNESS; + } + if (random16(500) == 0) { + cl.g = MAX_BRIGHTNESS; + } + if (random16(500) == 0) { + cl.b = MAX_BRIGHTNESS; + } + + setPixColor(i, j, cl); + } + } + } +}; \ No newline at end of file diff --git a/src/effects/13_rainbow_rain.h b/src/effects/13_rainbow_rain.h index 8dee97d..a04b581 100644 --- a/src/effects/13_rainbow_rain.h +++ b/src/effects/13_rainbow_rain.h @@ -1,42 +1,42 @@ #pragma once -#define data ((RainbowRainData *)global_data) +#include "effect.h" -#define RAINBOW_TICK_SIZE 4 //кол-во тиков до инкремента тика радуги - -struct RainbowRainData +class RainbowRain : public Effect { - uint8_t step; + uint8_t fade_step; + uint8_t rainbow_step; int tick; -}; -void rainbow_rain_prepare() -{ - data->step = 2; - data->tick = 0; - eff_set_ups(40); -} +public: + RainbowRain() {} -void rainbow_rain_update() -{ - int i, j; + void on_init() + { + fade_step = 3; + rainbow_step = 4; + tick = 0; + set_fps(40); + } - for (i = 0; i < WIDTH; ++i) { - for (j = 0; j < HEIGHT; ++j) { - fadePix(i, j, data->step); + void on_update() + { + int i, j; - CRGB cl = getPix(i, j); + for (i = 0; i < WIDTH; ++i) { + for (j = 0; j < HEIGHT; ++j) { + fadePix(i, j, fade_step); - if (random8(200) == 0) { - int asd = 0; - cl = CHSV(data->tick / RAINBOW_TICK_SIZE, 255, 255); - } + CRGB cl = getPix(i, j); - setPixColor(i, j, cl); - } - } + if (random16(300) == 0) { + cl = CHSV(tick / rainbow_step, 255, 255); + } - data->tick = (data->tick + 1) % ((MAX_HSV + 1) * RAINBOW_TICK_SIZE); -} + setPixColor(i, j, cl); + } + } -#undef data + tick = (tick + 1) % ((MAX_HSV + 1) * rainbow_step); + } +}; \ No newline at end of file diff --git a/src/effects/effectslist.cpp b/src/effects/effectslist.cpp index 03d4a38..2f92767 100644 --- a/src/effects/effectslist.cpp +++ b/src/effects/effectslist.cpp @@ -12,6 +12,8 @@ #include "09_confetti.h" #include "10_starfall.h" #include "11_dynamic_square.h" +#include "12_random_rain.h" +#include "13_rainbow_rain.h" /* #include "testmode.h"*/ @@ -70,11 +72,11 @@ Effect *EffectsList::getNewEffectInstance(int num) { return new Starfall(); case 11: return new DynamicSquare(); - /*case 12: - return new TestMode(); + case 12: + return new RandomRain(); case 13: - return new Rain(); - case 14: + return new RainbowRain(); + /*case 14: return new Rain();*/ //синусоида с рандомными параметрами default: From 3056ee74029b4a72db75037dfca0a3e158468ae5 Mon Sep 17 00:00:00 2001 From: npo6ka Date: Sun, 15 Dec 2019 14:12:21 +0300 Subject: [PATCH 14/75] add points effect --- src/effects/14_points.h | 193 ++++++++++++++++++------------------ src/effects/effectslist.cpp | 5 +- 2 files changed, 100 insertions(+), 98 deletions(-) diff --git a/src/effects/14_points.h b/src/effects/14_points.h index f130bc1..b952316 100644 --- a/src/effects/14_points.h +++ b/src/effects/14_points.h @@ -1,6 +1,6 @@ #pragma once -#define data ((PointsData *)global_data) +#include "effect.h" #define ACCURACY 100 #define MAX_VEC_SIZE 10 @@ -15,118 +15,119 @@ typedef struct Point CRGB color; } Point; -struct PointsData +class Points : public Effect { uint32_t point_size; uint32_t bright_radius; Point points[POINTS_AMNT]; int tick; -}; -//arg2: horizontal barrier = true or vertical = false -static void gen_vector(Point &pnt, bool horVer) -{ - int16_t dir = horVer ? 1 : -1; + //arg2: horizontal barrier = true or vertical = false + void gen_vector(Point &pnt, bool horVer) + { + int16_t dir = horVer ? 1 : -1; - pnt.vec_x = (pnt.vec_x > 0 ? -dir : dir) * random(0, MAX_VEC_SIZE); - pnt.vec_y = (pnt.vec_y > 0 ? dir : -dir) * random(0, MAX_VEC_SIZE); + pnt.vec_x = (pnt.vec_x > 0 ? -dir : dir) * random(0, MAX_VEC_SIZE); + pnt.vec_y = (pnt.vec_y > 0 ? dir : -dir) * random(0, MAX_VEC_SIZE); - if (pnt.vec_y == 0 && pnt.vec_x == 0) { - gen_vector(pnt, horVer); - } -} + if (pnt.vec_y == 0 && pnt.vec_x == 0) { + gen_vector(pnt, horVer); + } + } -static void move_point(Point &pnt) -{ - pnt.x += pnt.vec_x; - pnt.y += pnt.vec_y; - - if (pnt.x < 0) { - pnt.x = 0; - gen_vector(pnt, true); - } else if (pnt.x >= ACCURACY * WIDTH) { - pnt.x = ACCURACY * WIDTH - 1; - gen_vector(pnt, true); - } - - if (pnt.y < 0) { - pnt.y = 0; - gen_vector(pnt, false); - } else if (pnt.y >= ACCURACY * HEIGHT) { - pnt.y = ACCURACY * HEIGHT - 1; - gen_vector(pnt, false); - } -} - -// return float val in range 0 .. 1 -static float get_func_brithtness(int distance) -{ - if (distance <= data->point_size) { - return 1; - } else if (distance >= data->point_size + data->bright_radius) { - return 0; - } else { - float val = (float)(distance - data->point_size) / data->bright_radius; - return ( 7.3890560 /* e^2 */ ) / (50 * val + 7) - 0.13; - } -} - -static void render_point(Point pnt) -{ - int i, j; - for (i = 0; i < WIDTH; ++i) { - for(j = 0; j < HEIGHT; ++j) { - int loc_x = i * ACCURACY + ACCURACY / 2; - int loc_y = j * ACCURACY + ACCURACY / 2; - - int distance = sqrt((loc_x - pnt.x) * (loc_x - pnt.x) + (loc_y - pnt.y) * (loc_y - pnt.y)); - - float bright = get_func_brithtness(distance); - CRGB clr = getPix(i, j); - clr.r = qadd8(clr.r, (float)pnt.color.r * bright); - clr.g = qadd8(clr.g, (float)pnt.color.g * bright); - clr.b = qadd8(clr.b, (float)pnt.color.b * bright); - setPixColor(i, j, clr); + void move_point(Point &pnt) + { + pnt.x += pnt.vec_x; + pnt.y += pnt.vec_y; + + if (pnt.x < 0) { + pnt.x = 0; + gen_vector(pnt, true); + } else if (pnt.x >= ACCURACY * WIDTH) { + pnt.x = ACCURACY * WIDTH - 1; + gen_vector(pnt, true); + } + + if (pnt.y < 0) { + pnt.y = 0; + gen_vector(pnt, false); + } else if (pnt.y >= ACCURACY * HEIGHT) { + pnt.y = ACCURACY * HEIGHT - 1; + gen_vector(pnt, false); + } } - } -} -void points_prepare() -{ - int i; - data->tick = 0; - data->point_size = 50; - data->bright_radius = 400; - - for (i = 0; i < POINTS_AMNT; ++i) { - data->points[i].x = random16(0, (WIDTH - 1) * ACCURACY); - data->points[i].y = random16(0, (HEIGHT - 1) * ACCURACY); - - data->points[i].vec_x = (int32_t)random(0, MAX_VEC_SIZE * 2) - MAX_VEC_SIZE; - data->points[i].vec_y = (int32_t)random(0, MAX_VEC_SIZE * 2) - MAX_VEC_SIZE; - data->points[i].color = CHSV(random8(), 255, 255); + // return float val in range 0 .. 1 + float get_func_brithtness(uint32_t distance) + { + if (distance <= point_size) { + return 1; + } else if (distance >= point_size + bright_radius) { + return 0; + } else { + float val = (float)(distance - point_size) / bright_radius; + return ( 7.3890560 /* e^2 */ ) / (50 * val + 7) - 0.13; + } } - if (POINTS_AMNT >= 3) { - data->points[0].color = 0xff; - data->points[1].color = 0xff00; - data->points[2].color = 0xff0000; + void render_point(Point pnt) + { + int i, j; + for (i = 0; i < WIDTH; ++i) { + for(j = 0; j < HEIGHT; ++j) { + int loc_x = i * ACCURACY + ACCURACY / 2; + int loc_y = j * ACCURACY + ACCURACY / 2; + + int distance = sqrt((loc_x - pnt.x) * (loc_x - pnt.x) + (loc_y - pnt.y) * (loc_y - pnt.y)); + + float bright = get_func_brithtness(distance); + CRGB clr = getPix(i, j); + clr.r = qadd8(clr.r, (float)pnt.color.r * bright); + clr.g = qadd8(clr.g, (float)pnt.color.g * bright); + clr.b = qadd8(clr.b, (float)pnt.color.b * bright); + setPixColor(i, j, clr); + } + } } - eff_set_ups(60); -} +public: + Points() {} -void points_update() -{ - FastLED.clear(); + void on_init() + { + int i; + tick = 0; + point_size = 50; + bright_radius = 400; + + for (i = 0; i < POINTS_AMNT; ++i) { + points[i].x = random16(0, (WIDTH - 1) * ACCURACY); + points[i].y = random16(0, (HEIGHT - 1) * ACCURACY); + + points[i].vec_x = (int32_t)random(0, MAX_VEC_SIZE * 2) - MAX_VEC_SIZE; + points[i].vec_y = (int32_t)random(0, MAX_VEC_SIZE * 2) - MAX_VEC_SIZE; + points[i].color = CHSV(random8(), 255, 255); + } - int i; + if (POINTS_AMNT >= 3) { + points[0].color = 0xff; + points[1].color = 0xff00; + points[2].color = 0xff0000; + } - data->tick++; - for (i = 0; i < POINTS_AMNT; ++i) { - move_point(data->points[i]); - render_point(data->points[i]); + set_fps(60); } -} -#undef data + void on_update() + { + FastLED.clear(); + + int i; + + tick++; + for (i = 0; i < POINTS_AMNT; ++i) { + move_point(points[i]); + render_point(points[i]); + } + } +}; \ No newline at end of file diff --git a/src/effects/effectslist.cpp b/src/effects/effectslist.cpp index 2f92767..ba852bc 100644 --- a/src/effects/effectslist.cpp +++ b/src/effects/effectslist.cpp @@ -14,6 +14,7 @@ #include "11_dynamic_square.h" #include "12_random_rain.h" #include "13_rainbow_rain.h" +#include "14_points.h" /* #include "testmode.h"*/ @@ -76,8 +77,8 @@ Effect *EffectsList::getNewEffectInstance(int num) { return new RandomRain(); case 13: return new RainbowRain(); - /*case 14: - return new Rain();*/ + case 14: + return new Points(); //синусоида с рандомными параметрами default: return NULL; From fc27e4c2335374c1d9f4f3ca4a4edfa7761f1319 Mon Sep 17 00:00:00 2001 From: npo6ka Date: Sun, 15 Dec 2019 14:20:20 +0300 Subject: [PATCH 15/75] add rainbowPoint effect --- src/effects/15_rainbow_point.h | 119 +++++++++++++++++---------------- src/effects/effectslist.cpp | 3 + 2 files changed, 63 insertions(+), 59 deletions(-) diff --git a/src/effects/15_rainbow_point.h b/src/effects/15_rainbow_point.h index ca7b49e..19e41f0 100644 --- a/src/effects/15_rainbow_point.h +++ b/src/effects/15_rainbow_point.h @@ -1,91 +1,92 @@ #pragma once -#define data ((RainbowPointData *)global_data) - #define ACCURACY 100 #define MAX_VEC_SIZE 10 #define RAINBOW_TICK_SIZE 4 //кол-во тиков до инкремента тика радуги -struct RainbowPointData +#include "effect.h" + +class RainbowPoint : public Effect { int32_t x; int32_t y; int32_t vec_x; int32_t vec_y; int tick; -}; -//arg2: horizontal barrier = true or vertical = false -void rainbow_point_gen_vector(bool horVer) { - int16_t dir = horVer ? 1 : -1; + //arg2: horizontal barrier = true or vertical = false + void rainbow_point_gen_vector(bool horVer) { + int16_t dir = horVer ? 1 : -1; - data->vec_x = (data->vec_x > 0 ? -dir : dir) * random(0, MAX_VEC_SIZE); - data->vec_y = (data->vec_y > 0 ? dir : -dir) * random(0, MAX_VEC_SIZE); + vec_x = (vec_x > 0 ? -dir : dir) * random(0, MAX_VEC_SIZE); + vec_y = (vec_y > 0 ? dir : -dir) * random(0, MAX_VEC_SIZE); - if (data->vec_y == 0 && data->vec_x == 0) { - rainbow_point_gen_vector(horVer); - } -} - -void rainbow_point_move_point() { - data->x += data->vec_x; - data->y += data->vec_y; - - if (data->x < 0) { - data->x = 0; - rainbow_point_gen_vector(true); - } else if (data->x >= ACCURACY * WIDTH) { - data->x = ACCURACY * WIDTH - 1; - rainbow_point_gen_vector(true); + if (vec_y == 0 && vec_x == 0) { + rainbow_point_gen_vector(horVer); + } } - if (data->y < 0) { - data->y = 0; - rainbow_point_gen_vector(false); - } else if (data->y >= ACCURACY * HEIGHT) { - data->y = ACCURACY * HEIGHT - 1; - rainbow_point_gen_vector(false); + void rainbow_point_move_point() { + x += vec_x; + y += vec_y; + + if (x < 0) { + x = 0; + rainbow_point_gen_vector(true); + } else if (x >= ACCURACY * WIDTH) { + x = ACCURACY * WIDTH - 1; + rainbow_point_gen_vector(true); + } + + if (y < 0) { + y = 0; + rainbow_point_gen_vector(false); + } else if (y >= ACCURACY * HEIGHT) { + y = ACCURACY * HEIGHT - 1; + rainbow_point_gen_vector(false); + } } -} -void rainbow_point_render_point() { - int i, j; - for (i = 0; i < WIDTH; ++i) { - for(j = 0; j < HEIGHT; ++j) { - int loc_x = i * ACCURACY + ACCURACY / 2; - int loc_y = j * ACCURACY + ACCURACY / 2; + void rainbow_point_render_point() { + int i, j; + for (i = 0; i < WIDTH; ++i) { + for(j = 0; j < HEIGHT; ++j) { + int loc_x = i * ACCURACY + ACCURACY / 2; + int loc_y = j * ACCURACY + ACCURACY / 2; - int distance = sqrt((loc_x - data->x) * (loc_x - data->x) + (loc_y - data->y) * (loc_y - data->y)); + int distance = sqrt((loc_x - x) * (loc_x - x) + (loc_y - y) * (loc_y - y)); - float chsv = (distance / 8 + data->tick / RAINBOW_TICK_SIZE) % MAX_HSV; + float chsv = (distance / 8 + tick / RAINBOW_TICK_SIZE) % MAX_HSV; - setPixColor(i, j, CHSV(chsv, 255, 255)); + setPixColor(i, j, CHSV(chsv, 255, 255)); + } } } -} -void rainbow_point_prepare() -{ - data->tick = 0; - data->x = random16(0, (WIDTH - 1) * ACCURACY); - data->y = random16(0, (HEIGHT - 1) * ACCURACY); +public: + RainbowPoint() {} - data->vec_x = (int32_t)random(0, MAX_VEC_SIZE * 2) - MAX_VEC_SIZE; - data->vec_y = (int32_t)random(0, MAX_VEC_SIZE * 2) - MAX_VEC_SIZE; + void on_init() + { + tick = 0; + x = random16(0, (WIDTH - 1) * ACCURACY); + y = random16(0, (HEIGHT - 1) * ACCURACY); - eff_set_ups(60); -} + vec_x = (int32_t)random(0, MAX_VEC_SIZE * 2) - MAX_VEC_SIZE; + vec_y = (int32_t)random(0, MAX_VEC_SIZE * 2) - MAX_VEC_SIZE; -void rainbow_point_update() -{ - FastLED.clear(); + set_fps(60); + } - int i; + void on_update() + { + FastLED.clear(); - data->tick = (data->tick + 1) % ((MAX_HSV + 1) * RAINBOW_TICK_SIZE); + int i; - rainbow_point_move_point(); - rainbow_point_render_point(); -} + tick = (tick + 1) % ((MAX_HSV + 1) * RAINBOW_TICK_SIZE); -#undef data + rainbow_point_move_point(); + rainbow_point_render_point(); + } +}; diff --git a/src/effects/effectslist.cpp b/src/effects/effectslist.cpp index ba852bc..9e1577a 100644 --- a/src/effects/effectslist.cpp +++ b/src/effects/effectslist.cpp @@ -15,6 +15,7 @@ #include "12_random_rain.h" #include "13_rainbow_rain.h" #include "14_points.h" +#include "15_rainbow_point.h" /* #include "testmode.h"*/ @@ -79,6 +80,8 @@ Effect *EffectsList::getNewEffectInstance(int num) { return new RainbowRain(); case 14: return new Points(); + case 15: + return new RainbowPoint(); //синусоида с рандомными параметрами default: return NULL; From f568cb8633d68de662ac5a468317e17130419c4a Mon Sep 17 00:00:00 2001 From: npo6ka Date: Sun, 15 Dec 2019 14:25:17 +0300 Subject: [PATCH 16/75] add RainbowStaticPoint effect --- src/effects/15_rainbow_point.h | 2 - src/effects/16_rainbow_static_point.h | 55 ++++++++++++++------------- src/effects/effectslist.cpp | 3 ++ 3 files changed, 31 insertions(+), 29 deletions(-) diff --git a/src/effects/15_rainbow_point.h b/src/effects/15_rainbow_point.h index 19e41f0..eeb7456 100644 --- a/src/effects/15_rainbow_point.h +++ b/src/effects/15_rainbow_point.h @@ -82,8 +82,6 @@ class RainbowPoint : public Effect { FastLED.clear(); - int i; - tick = (tick + 1) % ((MAX_HSV + 1) * RAINBOW_TICK_SIZE); rainbow_point_move_point(); diff --git a/src/effects/16_rainbow_static_point.h b/src/effects/16_rainbow_static_point.h index 6dc1e44..27fc03c 100644 --- a/src/effects/16_rainbow_static_point.h +++ b/src/effects/16_rainbow_static_point.h @@ -1,44 +1,45 @@ #pragma once -#define data ((RainbowStaticPointData *)global_data) - #define ACCURACY 10 #define RAINBOW_TICK_SIZE 4 //кол-во тиков до инкремента тика радуги -struct RainbowStaticPointData +#include "effect.h" + +class RainbowStaticPoint : public Effect { int tick; -}; -static void rainbow_static_point_render_point() { - int i, j; - int x = WIDTH / 2 * ACCURACY; - int y = HEIGHT / 2 * ACCURACY; + void rainbow_static_point_render_point() { + int i, j; + int x = WIDTH / 2 * ACCURACY; + int y = HEIGHT / 2 * ACCURACY; - for (i = 0; i < WIDTH; ++i) { - for(j = 0; j < HEIGHT; ++j) { - int loc_x = i * ACCURACY + ACCURACY / 2; - int loc_y = j * ACCURACY + ACCURACY / 2; + for (i = 0; i < WIDTH; ++i) { + for(j = 0; j < HEIGHT; ++j) { + int loc_x = i * ACCURACY + ACCURACY / 2; + int loc_y = j * ACCURACY + ACCURACY / 2; - int distance = sqrt((loc_x - x) * (loc_x - x) + (loc_y - y) * (loc_y - y)); + int distance = sqrt((loc_x - x) * (loc_x - x) + (loc_y - y) * (loc_y - y)); - float chsv = (distance + data->tick / RAINBOW_TICK_SIZE) % MAX_HSV; + float chsv = (distance + tick / RAINBOW_TICK_SIZE) % MAX_HSV; - setPixColor(i, j, CHSV(chsv, 255, 255)); + setPixColor(i, j, CHSV(chsv, 255, 255)); + } } } -} -void rainbow_static_point_prepare() -{ - data->tick = 0; - eff_set_ups(60); -} +public: + RainbowStaticPoint() {} -void rainbow_static_point_update() -{ - data->tick = (data->tick + 1) % ((MAX_HSV + 1) * RAINBOW_TICK_SIZE); - rainbow_static_point_render_point(); -} + void on_init() + { + tick = 0; + set_fps(60); + } -#undef data + void on_update() + { + tick = (tick + 1) % ((MAX_HSV + 1) * RAINBOW_TICK_SIZE); + rainbow_static_point_render_point(); + } +}; \ No newline at end of file diff --git a/src/effects/effectslist.cpp b/src/effects/effectslist.cpp index 9e1577a..001734f 100644 --- a/src/effects/effectslist.cpp +++ b/src/effects/effectslist.cpp @@ -16,6 +16,7 @@ #include "13_rainbow_rain.h" #include "14_points.h" #include "15_rainbow_point.h" +#include "16_rainbow_static_point.h" /* #include "testmode.h"*/ @@ -82,6 +83,8 @@ Effect *EffectsList::getNewEffectInstance(int num) { return new Points(); case 15: return new RainbowPoint(); + case 16: + return new RainbowStaticPoint(); //синусоида с рандомными параметрами default: return NULL; From 147d830c53f0e75972ef5b404e659824524066e0 Mon Sep 17 00:00:00 2001 From: npo6ka Date: Thu, 19 Dec 2019 02:20:38 +0300 Subject: [PATCH 17/75] tmp fix matrix positions --- src/effects/16_rainbow_static_point.h | 10 ++- src/effects/lib_led.h | 122 +++++++++++++++++++++++++- src/main.cpp | 2 +- 3 files changed, 128 insertions(+), 6 deletions(-) diff --git a/src/effects/16_rainbow_static_point.h b/src/effects/16_rainbow_static_point.h index 27fc03c..31dba0d 100644 --- a/src/effects/16_rainbow_static_point.h +++ b/src/effects/16_rainbow_static_point.h @@ -38,8 +38,14 @@ class RainbowStaticPoint : public Effect } void on_update() - { + {/* tick = (tick + 1) % ((MAX_HSV + 1) * RAINBOW_TICK_SIZE); - rainbow_static_point_render_point(); + rainbow_static_point_render_point();*/ + getPix(0, 0) = 0xffffff; + getPix(0, 15) = 0xff00ff; + + //leds[0] = 0xffffff; + + } }; \ No newline at end of file diff --git a/src/effects/lib_led.h b/src/effects/lib_led.h index 5554e9a..eb0e135 100644 --- a/src/effects/lib_led.h +++ b/src/effects/lib_led.h @@ -17,7 +17,7 @@ extern CRGB leds[LEDS_CNT]; // Получить номер пикселя по координатам -static uint16_t getPixNum(int x, int y); +static uint16_t getPixNum(const uint8_t x, const uint8_t y); // получить объект пикселя по координатам static CRGB &getPix(int x, int y); @@ -66,6 +66,122 @@ static void led_setup() pinMode(LED_BUILTIN, OUTPUT); } + +#define MATRIX_TYPE (0U) // тип матрицы: 0 - зигзаг, 1 - параллельная +#define CONNECTION_ANGLE (1U) // угол подключения: 0 - левый нижний, 1 - левый верхний, 2 - правый верхний, 3 - правый нижний +#define STRIP_DIRECTION (0U) // направление ленты из угла: 0 - вправо, 1 - вверх, 2 - влево, 3 - вниз + // при неправильной настройке матрицы вы получите предупреждение "Wrong matrix parameters! Set to default" + // шпаргалка по настройке матрицы здесь! https://alexgyver.ru/matrix_guide/ + + +// ************* НАСТРОЙКА МАТРИЦЫ ***** +#if (CONNECTION_ANGLE == 0 && STRIP_DIRECTION == 0) +#define _WIDTH WIDTH +#define THIS_X x +#define THIS_Y y + +#elif (CONNECTION_ANGLE == 0 && STRIP_DIRECTION == 1) +#define _WIDTH HEIGHT +#define THIS_X y +#define THIS_Y x + +#elif (CONNECTION_ANGLE == 1 && STRIP_DIRECTION == 0) +#define _WIDTH WIDTH +#define THIS_X x +#define THIS_Y (HEIGHT - y - 1) + +#elif (CONNECTION_ANGLE == 1 && STRIP_DIRECTION == 3) +#define _WIDTH HEIGHT +#define THIS_X (HEIGHT - y - 1) +#define THIS_Y x + +#elif (CONNECTION_ANGLE == 2 && STRIP_DIRECTION == 2) +#define _WIDTH WIDTH +#define THIS_X (WIDTH - x - 1) +#define THIS_Y (HEIGHT - y - 1) + +#elif (CONNECTION_ANGLE == 2 && STRIP_DIRECTION == 3) +#define _WIDTH HEIGHT +#define THIS_X (HEIGHT - y - 1) +#define THIS_Y (WIDTH - x - 1) + +#elif (CONNECTION_ANGLE == 3 && STRIP_DIRECTION == 2) +#define _WIDTH WIDTH +#define THIS_X (WIDTH - x - 1) +#define THIS_Y y + +#elif (CONNECTION_ANGLE == 3 && STRIP_DIRECTION == 1) +#define _WIDTH HEIGHT +#define THIS_X y +#define THIS_Y (WIDTH - x - 1) + +#else +#define _WIDTH WIDTH +#define THIS_X x +#define THIS_Y y +#pragma message "Wrong matrix parameters! Set to default" + +#endif + +// получить номер пикселя в ленте по координатам +static uint16_t getPixNum(const uint8_t x, const uint8_t y) +{ + //if (x % 2 == 0) return x * WIDTH + WIDTH - y - 1; + //else return x * WIDTH + y; + if ((THIS_Y % 2 == 0) || MATRIX_TYPE) // если чётная строка + { + return (THIS_Y * _WIDTH + THIS_X); + } + else // если нечётная строка + { + return (THIS_Y * _WIDTH + _WIDTH - THIS_X - 1); + } +} +/* +#define THIS_X (WIDTH - x - 1) +#define THIS_Y (HEIGHT - y - 1) + + +0, 0 +(HEIGHT - x - 1) * WIDTH + y (0,2,4...) H(x) +(HEIGHT - x - 1) * WIDTH + WIDTH - y - 1 (1,3,5...) H(x) + +0, 1 +y * HEIGHT + (HEIGHT - x - 1) (0,2,4...) W(y) +y * HEIGHT + x (1,3,5...) W(y) + +1, 0 +x * WIDTH + y (0,2,4...) H(x) +x * WIDTH + WIDTH - y - 1 (1,3,5...) H(x) + + +1, 3 +y * HEIGHT + x (0,2,4...) W(y) +y * HEIGHT + (HEIGHT - x - 1) (1,3,5...) W(y) + +2, 2 +x * WIDTH + WIDTH - y - 1 (0,2,4...) H(x) +x * WIDTH + y (1,3,5...) H(x) + +2, 3 +(WIDTH - y - 1) * HEIGHT + (HEIGHT - x - 1) (0,2,4...) W(y) +(WIDTH - y - 1) * HEIGHT + x (1,3,5...) W(y) + +3, 1 +(WIDTH - y - 1) * HEIGHT + x (0,2,4...) W(y) +(WIDTH - y - 1) * HEIGHT + (HEIGHT - x - 1) (1,3,5...) W(y) + +3, 2 +(HEIGHT - x - 1) * WIDTH + WIDTH - y - 1 (0,2,4...) H(x) +(HEIGHT - x - 1) * WIDTH + y (1,3,5...) H(x) + + + + + + +*/ +/* static uint16_t getPixNum(int x, int y) { if (x < 0 || x >= WIDTH || y < 0 || y >= HEIGHT) { Serial.print("Value out of range in function getPixNum"); @@ -93,8 +209,8 @@ static uint16_t getPixNum(int x, int y) { ->9 0 1 2 3 4 5 6 7 8 9 ^ входной провод - */ -} + +}*/ static CRGB &getPix(int x, int y) { if (x < 0 || x >= WIDTH || y < 0 || y >= HEIGHT) { diff --git a/src/main.cpp b/src/main.cpp index 2b50ba5..34c40c9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -13,7 +13,7 @@ void setup() { // инициализация кнопок setup_buttons(); - FastLED.setBrightness(64); + FastLED.setBrightness(150); } //unsigned long tick = 0; From b665928d9c0f114ce01c71d48841adf6412ca916 Mon Sep 17 00:00:00 2001 From: npo6ka Date: Thu, 19 Dec 2019 12:25:58 +0300 Subject: [PATCH 18/75] fix getPixNum function and remove deprecated defines --- src/effects/00_slow_random.h | 12 +- src/effects/01_simple_rainbow.h | 4 +- src/effects/02_dribs.h | 4 +- src/effects/03_rain.h | 2 +- src/effects/11_dynamic_square.h | 2 +- src/effects/12_random_rain.h | 6 +- src/effects/13_rainbow_rain.h | 2 +- src/effects/15_rainbow_point.h | 4 +- src/effects/16_rainbow_static_point.h | 12 +- src/effects/lib_led.h | 263 +++++++++++--------------- 10 files changed, 130 insertions(+), 181 deletions(-) diff --git a/src/effects/00_slow_random.h b/src/effects/00_slow_random.h index 46fd76b..d9f8a18 100644 --- a/src/effects/00_slow_random.h +++ b/src/effects/00_slow_random.h @@ -15,7 +15,7 @@ class SlowRandom : public Effect uint8_t val = random8(2); if (val > 0) { - color_val = random8(MAX_BRIGHTNESS); + color_val = random8(255); } return val; @@ -57,7 +57,7 @@ class SlowRandom : public Effect } uint8_t proc_val(uint8_t &color_val, uint8_t val) { - if (color_val == 0 || color_val == MAX_BRIGHTNESS) { + if (color_val == 0 || color_val == 255) { val = 0; } @@ -68,13 +68,13 @@ class SlowRandom : public Effect if (color_val < step) { color_val = 0; } else { - color_val = (color_val - step) % (MAX_BRIGHTNESS + 1); + color_val = (color_val - step) % 256; } } else if (val == 1) { - if (color_val + step > MAX_BRIGHTNESS) { - color_val = MAX_BRIGHTNESS; + if (color_val + step > 255) { + color_val = 255; } else { - color_val = (color_val + step) % (MAX_BRIGHTNESS + 1); + color_val = (color_val + step) % 256; } } diff --git a/src/effects/01_simple_rainbow.h b/src/effects/01_simple_rainbow.h index 165e83d..cb8b77a 100644 --- a/src/effects/01_simple_rainbow.h +++ b/src/effects/01_simple_rainbow.h @@ -20,11 +20,11 @@ class SimpleRainbow : public Effect void on_update() { uint8_t x, y; - tick = tick % (MAX_HSV + 1); + tick = tick % (256); for (x = 0; x < HEIGHT; x++) { for (y = 0; y < WIDTH; y++) { - getPix(x, y) = CHSV((tick + x + y * phaseShift / 2) % (MAX_HSV + 1), 255, 255); + getPix(x, y) = CHSV((tick + x + y * phaseShift / 2) % (256), 255, 255); } } diff --git a/src/effects/02_dribs.h b/src/effects/02_dribs.h index 3955c71..f6a42b3 100644 --- a/src/effects/02_dribs.h +++ b/src/effects/02_dribs.h @@ -26,11 +26,11 @@ class Dribs : public Effect } } - int step = MAX_BRIGHTNESS / lenght; + int step = 255 / lenght; for (int j = 0; j < WIDTH; ++j) { for (int i = 0; i < HEIGHT; ++i) { if (cur_drib[j] != 0 && i < cur_drib[j]) { - getPix(HEIGHT - i - 1, j) = CRGB(0, 0, max(MAX_BRIGHTNESS - (cur_drib[j] - i - 1) * step, 0)); + getPix(HEIGHT - i - 1, j) = CRGB(0, 0, max(255 - (cur_drib[j] - i - 1) * step, 0)); } else { getPix(HEIGHT - i - 1, j) = 0x0; } diff --git a/src/effects/03_rain.h b/src/effects/03_rain.h index f197a81..654bef9 100644 --- a/src/effects/03_rain.h +++ b/src/effects/03_rain.h @@ -21,7 +21,7 @@ class Rain : public Effect CRGB &cl = getPix(i, j); if (random8(255) == 0) { - cl = CRGB(0, 0, MAX_BRIGHTNESS); + cl = CRGB(0, 0, 255); } else if (cl.b > 0) { if (cl.b > step) { cl = CRGB(0, 0, cl.b - step); diff --git a/src/effects/11_dynamic_square.h b/src/effects/11_dynamic_square.h index 5db11da..e8dc57a 100644 --- a/src/effects/11_dynamic_square.h +++ b/src/effects/11_dynamic_square.h @@ -5,7 +5,7 @@ class DynamicSquare : public Effect { uint8_t lenght = 2; - uint8_t step = MAX_BRIGHTNESS / lenght; + uint8_t step = 255 / lenght; uint8_t radius = WIDTH > HEIGHT ? (HEIGHT / 2): (WIDTH / 2); //uint32_t color = 0x0000ff; uint8_t hsv = 0; diff --git a/src/effects/12_random_rain.h b/src/effects/12_random_rain.h index 96fa906..40bb81a 100644 --- a/src/effects/12_random_rain.h +++ b/src/effects/12_random_rain.h @@ -26,13 +26,13 @@ class RandomRain : public Effect CRGB cl = getPix(i, j); if (random16(500) == 0) { - cl.r = MAX_BRIGHTNESS; + cl.r = 255; } if (random16(500) == 0) { - cl.g = MAX_BRIGHTNESS; + cl.g = 255; } if (random16(500) == 0) { - cl.b = MAX_BRIGHTNESS; + cl.b = 255; } setPixColor(i, j, cl); diff --git a/src/effects/13_rainbow_rain.h b/src/effects/13_rainbow_rain.h index a04b581..13d2b3e 100644 --- a/src/effects/13_rainbow_rain.h +++ b/src/effects/13_rainbow_rain.h @@ -37,6 +37,6 @@ class RainbowRain : public Effect } } - tick = (tick + 1) % ((MAX_HSV + 1) * rainbow_step); + tick = (tick + 1) % (256 * rainbow_step); } }; \ No newline at end of file diff --git a/src/effects/15_rainbow_point.h b/src/effects/15_rainbow_point.h index eeb7456..7e20fcd 100644 --- a/src/effects/15_rainbow_point.h +++ b/src/effects/15_rainbow_point.h @@ -56,7 +56,7 @@ class RainbowPoint : public Effect int distance = sqrt((loc_x - x) * (loc_x - x) + (loc_y - y) * (loc_y - y)); - float chsv = (distance / 8 + tick / RAINBOW_TICK_SIZE) % MAX_HSV; + float chsv = (distance / 8 + tick / RAINBOW_TICK_SIZE) % 255; setPixColor(i, j, CHSV(chsv, 255, 255)); } @@ -82,7 +82,7 @@ class RainbowPoint : public Effect { FastLED.clear(); - tick = (tick + 1) % ((MAX_HSV + 1) * RAINBOW_TICK_SIZE); + tick = (tick + 1) % (256 * RAINBOW_TICK_SIZE); rainbow_point_move_point(); rainbow_point_render_point(); diff --git a/src/effects/16_rainbow_static_point.h b/src/effects/16_rainbow_static_point.h index 31dba0d..8c8bcda 100644 --- a/src/effects/16_rainbow_static_point.h +++ b/src/effects/16_rainbow_static_point.h @@ -21,7 +21,7 @@ class RainbowStaticPoint : public Effect int distance = sqrt((loc_x - x) * (loc_x - x) + (loc_y - y) * (loc_y - y)); - float chsv = (distance + tick / RAINBOW_TICK_SIZE) % MAX_HSV; + float chsv = (distance + tick / RAINBOW_TICK_SIZE) % 255; setPixColor(i, j, CHSV(chsv, 255, 255)); } @@ -38,11 +38,11 @@ class RainbowStaticPoint : public Effect } void on_update() - {/* - tick = (tick + 1) % ((MAX_HSV + 1) * RAINBOW_TICK_SIZE); - rainbow_static_point_render_point();*/ - getPix(0, 0) = 0xffffff; - getPix(0, 15) = 0xff00ff; + { + tick = (tick + 1) % (256 * RAINBOW_TICK_SIZE); + rainbow_static_point_render_point(); + //getPix(0, 0) = 0xffffff; + //getPix(0, 15) = 0xff00ff; //leds[0] = 0xffffff; diff --git a/src/effects/lib_led.h b/src/effects/lib_led.h index eb0e135..9836355 100644 --- a/src/effects/lib_led.h +++ b/src/effects/lib_led.h @@ -10,10 +10,88 @@ #define WIDTH 16 #define HEIGHT 16 #define LEDS_CNT WIDTH * HEIGHT -#define MAX_BRIGHTNESS 255 -#define MAX_HSV 255 #define CURRENT_LIMIT (2000U) // лимит по току в миллиамперах, автоматически управляет яркостью (пожалей свой блок питания!) 0 - выключить лимит +#define MATRIX_TYPE (0U) // тип матрицы: 0 - зигзаг, 1 - параллельная +#define CONNECTION_ANGLE (2U) // угол подключения матрицы (0-3): 0 - левый нижний, 1 - левый верхний, 2 - правый верхний, 3 - правый нижний + // 1---------2 + // ----------- + // ----------- + // ----------- + // 0---------3 +#define STRIP_DIRECTION (2U) // направление ленты из угла (0-1): 0 - горизонтальное (из углов влево или вправо) + // 1>>>------2 1------<<<2 1---------2 1---------2 + // ----------- ----------- ----------- ----------- + // ----------- ----------- ----------- ----------- + // ----------- ----------- ----------- ----------- + // 0---------3 0---------3 0>>>------3 0------<<<3 + + // 1 - Вертикальное (из углов вверх или вниз) + // 1---------2 1---------2 1---------2 1---------2 + // v---------- ----------v ----------- ----------- + // v---------- ----------v ^---------- ----------^ + // ----------- ----------- ^---------- ----------^ + // 0---------3 0---------3 0---------3 0---------3 + + +// ************* НАСТРОЙКА МАТРИЦЫ ***** +//hooks for old vertion +#if (STRIP_DIRECTION == 2) +#define STRIP_DIRECTION 0 +#endif +#if (STRIP_DIRECTION == 3) +#define STRIP_DIRECTION 1 +#endif + +#if (CONNECTION_ANGLE == 0 && STRIP_DIRECTION == 0) +#define _WIDTH WIDTH +#define THIS_X (HEIGHT - x - 1) +#define THIS_Y y + +#elif (CONNECTION_ANGLE == 0 && STRIP_DIRECTION == 1) +#define _WIDTH HEIGHT +#define THIS_X y +#define THIS_Y (HEIGHT - x - 1) + +#elif (CONNECTION_ANGLE == 1 && STRIP_DIRECTION == 0) +#define _WIDTH WIDTH +#define THIS_X x +#define THIS_Y y + +#elif (CONNECTION_ANGLE == 1 && STRIP_DIRECTION == 1) +#define _WIDTH HEIGHT +#define THIS_X y +#define THIS_Y x + +#elif (CONNECTION_ANGLE == 2 && STRIP_DIRECTION == 0) +#define _WIDTH WIDTH +#define THIS_X y +#define THIS_Y (WIDTH - y - 1) + +#elif (CONNECTION_ANGLE == 2 && STRIP_DIRECTION == 1) +#define _WIDTH HEIGHT +#define THIS_X (WIDTH - y - 1) +#define THIS_Y (HEIGHT - x - 1) + +#elif (CONNECTION_ANGLE == 3 && STRIP_DIRECTION == 0) +#define _WIDTH WIDTH +#define THIS_X (HEIGHT - x - 1) +#define THIS_Y (WIDTH - y - 1) + +#elif (CONNECTION_ANGLE == 3 && STRIP_DIRECTION == 1) +#define _WIDTH HEIGHT +#define THIS_X (WIDTH - y - 1) +#define THIS_Y x + +#else +#define _WIDTH WIDTH +#define THIS_X x +#define THIS_Y y +#pragma message "Wrong matrix parameters! Set to default" + +#endif + + extern CRGB leds[LEDS_CNT]; // Получить номер пикселя по координатам @@ -66,158 +144,34 @@ static void led_setup() pinMode(LED_BUILTIN, OUTPUT); } - -#define MATRIX_TYPE (0U) // тип матрицы: 0 - зигзаг, 1 - параллельная -#define CONNECTION_ANGLE (1U) // угол подключения: 0 - левый нижний, 1 - левый верхний, 2 - правый верхний, 3 - правый нижний -#define STRIP_DIRECTION (0U) // направление ленты из угла: 0 - вправо, 1 - вверх, 2 - влево, 3 - вниз - // при неправильной настройке матрицы вы получите предупреждение "Wrong matrix parameters! Set to default" - // шпаргалка по настройке матрицы здесь! https://alexgyver.ru/matrix_guide/ - - -// ************* НАСТРОЙКА МАТРИЦЫ ***** -#if (CONNECTION_ANGLE == 0 && STRIP_DIRECTION == 0) -#define _WIDTH WIDTH -#define THIS_X x -#define THIS_Y y - -#elif (CONNECTION_ANGLE == 0 && STRIP_DIRECTION == 1) -#define _WIDTH HEIGHT -#define THIS_X y -#define THIS_Y x - -#elif (CONNECTION_ANGLE == 1 && STRIP_DIRECTION == 0) -#define _WIDTH WIDTH -#define THIS_X x -#define THIS_Y (HEIGHT - y - 1) - -#elif (CONNECTION_ANGLE == 1 && STRIP_DIRECTION == 3) -#define _WIDTH HEIGHT -#define THIS_X (HEIGHT - y - 1) -#define THIS_Y x - -#elif (CONNECTION_ANGLE == 2 && STRIP_DIRECTION == 2) -#define _WIDTH WIDTH -#define THIS_X (WIDTH - x - 1) -#define THIS_Y (HEIGHT - y - 1) - -#elif (CONNECTION_ANGLE == 2 && STRIP_DIRECTION == 3) -#define _WIDTH HEIGHT -#define THIS_X (HEIGHT - y - 1) -#define THIS_Y (WIDTH - x - 1) - -#elif (CONNECTION_ANGLE == 3 && STRIP_DIRECTION == 2) -#define _WIDTH WIDTH -#define THIS_X (WIDTH - x - 1) -#define THIS_Y y - -#elif (CONNECTION_ANGLE == 3 && STRIP_DIRECTION == 1) -#define _WIDTH HEIGHT -#define THIS_X y -#define THIS_Y (WIDTH - x - 1) - -#else -#define _WIDTH WIDTH -#define THIS_X x -#define THIS_Y y -#pragma message "Wrong matrix parameters! Set to default" - -#endif - // получить номер пикселя в ленте по координатам static uint16_t getPixNum(const uint8_t x, const uint8_t y) { - //if (x % 2 == 0) return x * WIDTH + WIDTH - y - 1; - //else return x * WIDTH + y; - if ((THIS_Y % 2 == 0) || MATRIX_TYPE) // если чётная строка - { - return (THIS_Y * _WIDTH + THIS_X); - } - else // если нечётная строка - { - return (THIS_Y * _WIDTH + _WIDTH - THIS_X - 1); - } -} -/* -#define THIS_X (WIDTH - x - 1) -#define THIS_Y (HEIGHT - y - 1) - - -0, 0 -(HEIGHT - x - 1) * WIDTH + y (0,2,4...) H(x) -(HEIGHT - x - 1) * WIDTH + WIDTH - y - 1 (1,3,5...) H(x) - -0, 1 -y * HEIGHT + (HEIGHT - x - 1) (0,2,4...) W(y) -y * HEIGHT + x (1,3,5...) W(y) - -1, 0 -x * WIDTH + y (0,2,4...) H(x) -x * WIDTH + WIDTH - y - 1 (1,3,5...) H(x) - - -1, 3 -y * HEIGHT + x (0,2,4...) W(y) -y * HEIGHT + (HEIGHT - x - 1) (1,3,5...) W(y) - -2, 2 -x * WIDTH + WIDTH - y - 1 (0,2,4...) H(x) -x * WIDTH + y (1,3,5...) H(x) - -2, 3 -(WIDTH - y - 1) * HEIGHT + (HEIGHT - x - 1) (0,2,4...) W(y) -(WIDTH - y - 1) * HEIGHT + x (1,3,5...) W(y) - -3, 1 -(WIDTH - y - 1) * HEIGHT + x (0,2,4...) W(y) -(WIDTH - y - 1) * HEIGHT + (HEIGHT - x - 1) (1,3,5...) W(y) - -3, 2 -(HEIGHT - x - 1) * WIDTH + WIDTH - y - 1 (0,2,4...) H(x) -(HEIGHT - x - 1) * WIDTH + y (1,3,5...) H(x) - - - - - - -*/ -/* -static uint16_t getPixNum(int x, int y) { - if (x < 0 || x >= WIDTH || y < 0 || y >= HEIGHT) { - Serial.print("Value out of range in function getPixNum"); - Serial.print(x); - Serial.println(y); + // x - номер строки + // y - номер столбца + // матрица нумеруется сверху вниз по x, слева направо по y (как все массивы в си) + if (x < 0 || x >= HEIGHT || y < 0 || y >= WIDTH) { + out("Value out of range in function getPixNum %d %d\n", x, y); return 0; } - //для тестов на эмуляторе необходим данный вариант: - //return WIDTH * HEIGHT - (y * WIDTH + x + 1); - - return WIDTH * HEIGHT - (y & 0x1 ? (y * WIDTH + x + 1) : ((y + 1) * WIDTH - x)); - /* текущая матрица в рабочей железке - x 0 1 2 3 4 5 6 7 8 9 - y - 0 99 98 97 96 95 94 93 92 91 90 - 1 80 81 82 83 84 85 86 87 88 89 - 2 79 78 77 76 75 74 73 72 71 70 - 3 60 61 62 63 64 65 66 67 68 69 - 4 59 58 57 56 55 54 53 52 51 50 - 5 40 41 42 43 44 45 46 47 48 49 - 6 39 38 37 36 35 34 33 32 31 30 - 7 20 21 22 23 24 25 26 27 28 29 - 8 19 18 17 16 15 14 13 12 11 10 - ->9 0 1 2 3 4 5 6 7 8 9 - ^ -входной провод - -}*/ + if ( +#if (STRIP_DIRECTION == 0) + x % 2 == 0 +#else + y % 2 == 0 +#endif + || MATRIX_TYPE) // если чётная строка + { + return (THIS_Y * _WIDTH + THIS_X); + } else { // если нечётная строка + return (THIS_Y * _WIDTH + _WIDTH - THIS_X - 1); + } +} static CRGB &getPix(int x, int y) { - if (x < 0 || x >= WIDTH || y < 0 || y >= HEIGHT) { - Serial.print("Value out of range in function getPix "); - Serial.print(x); - Serial.print(" "); - Serial.println(y); + if (x < 0 || x >= HEIGHT || y < 0 || y >= WIDTH) { + out("Value out of range in function getPix %d %d\n", x, y); return leds[0]; } @@ -225,15 +179,12 @@ static CRGB &getPix(int x, int y) { } static uint32_t getPixColor(CRGB val) { - return (((uint32_t)val.r << 16) | ((uint16_t)val.g << 8 ) | val.b); + return (((uint32_t)val.r << 16) | ((uint32_t)val.g << 8 ) | val.b); } static uint32_t getPixColor(int x, int y) { - if (x < 0 || x >= WIDTH || y < 0 || y >= HEIGHT) { - Serial.print("Value out of range in function getPixColor "); - Serial.print(x); - Serial.print(" "); - Serial.println(y); + if (x < 0 || x >= HEIGHT || y < 0 || y >= WIDTH) { + out("Value out of range in function getPixColor %d %d\n", x, y); return 0; } @@ -241,11 +192,9 @@ static uint32_t getPixColor(int x, int y) { } static void setPixColor(int x, int y, CRGB color) { - if (x < 0 || x >= WIDTH || y < 0 || y >= HEIGHT) { - Serial.print("Value out of range in function setPixColor "); - Serial.print(x); - Serial.print(" "); - Serial.println(y); + if (x < 0 || x >= HEIGHT || y < 0 || y >= WIDTH) { + out("Value out of range in function setPixColor %d %d\n", x, y); + return; } From 5c9cd8292e1e3154dbc00dad2e8339795c6feadb Mon Sep 17 00:00:00 2001 From: npo6ka Date: Thu, 19 Dec 2019 23:03:09 +0300 Subject: [PATCH 19/75] fix XY matrix problem --- src/effects/16_rainbow_static_point.h | 6 --- src/effects/lib_led.h | 75 +++++++++++---------------- 2 files changed, 31 insertions(+), 50 deletions(-) diff --git a/src/effects/16_rainbow_static_point.h b/src/effects/16_rainbow_static_point.h index 8c8bcda..cb9d831 100644 --- a/src/effects/16_rainbow_static_point.h +++ b/src/effects/16_rainbow_static_point.h @@ -41,11 +41,5 @@ class RainbowStaticPoint : public Effect { tick = (tick + 1) % (256 * RAINBOW_TICK_SIZE); rainbow_static_point_render_point(); - //getPix(0, 0) = 0xffffff; - //getPix(0, 15) = 0xff00ff; - - //leds[0] = 0xffffff; - - } }; \ No newline at end of file diff --git a/src/effects/lib_led.h b/src/effects/lib_led.h index 9836355..33eb533 100644 --- a/src/effects/lib_led.h +++ b/src/effects/lib_led.h @@ -19,7 +19,7 @@ // ----------- // ----------- // 0---------3 -#define STRIP_DIRECTION (2U) // направление ленты из угла (0-1): 0 - горизонтальное (из углов влево или вправо) +#define STRIP_DIRECTION (0U) // направление ленты из угла (0-1): 0 - горизонтальное (из углов влево или вправо) // 1>>>------2 1------<<<2 1---------2 1---------2 // ----------- ----------- ----------- ----------- // ----------- ----------- ----------- ----------- @@ -37,61 +37,48 @@ // ************* НАСТРОЙКА МАТРИЦЫ ***** //hooks for old vertion #if (STRIP_DIRECTION == 2) -#define STRIP_DIRECTION 0 +# define STRIP_DIRECTION 0 +#elif (STRIP_DIRECTION == 3) +# define STRIP_DIRECTION 1 #endif -#if (STRIP_DIRECTION == 3) -#define STRIP_DIRECTION 1 + +#if (STRIP_DIRECTION == 0) +# define _WIDTH WIDTH +#elif (STRIP_DIRECTION == 1) +# define _WIDTH HEIGHT #endif #if (CONNECTION_ANGLE == 0 && STRIP_DIRECTION == 0) -#define _WIDTH WIDTH -#define THIS_X (HEIGHT - x - 1) -#define THIS_Y y - +# define THIS_X (HEIGHT - x - 1) +# define THIS_Y y #elif (CONNECTION_ANGLE == 0 && STRIP_DIRECTION == 1) -#define _WIDTH HEIGHT -#define THIS_X y -#define THIS_Y (HEIGHT - x - 1) - +# define THIS_X y +# define THIS_Y (HEIGHT - x - 1) #elif (CONNECTION_ANGLE == 1 && STRIP_DIRECTION == 0) -#define _WIDTH WIDTH -#define THIS_X x -#define THIS_Y y - +# define THIS_X x +# define THIS_Y y #elif (CONNECTION_ANGLE == 1 && STRIP_DIRECTION == 1) -#define _WIDTH HEIGHT -#define THIS_X y -#define THIS_Y x - +# define THIS_X y +# define THIS_Y x #elif (CONNECTION_ANGLE == 2 && STRIP_DIRECTION == 0) -#define _WIDTH WIDTH -#define THIS_X y -#define THIS_Y (WIDTH - y - 1) - +# define THIS_X x +# define THIS_Y (WIDTH - y - 1) #elif (CONNECTION_ANGLE == 2 && STRIP_DIRECTION == 1) -#define _WIDTH HEIGHT -#define THIS_X (WIDTH - y - 1) -#define THIS_Y (HEIGHT - x - 1) - +# define THIS_X (WIDTH - y - 1) +# define THIS_Y (HEIGHT - x - 1) #elif (CONNECTION_ANGLE == 3 && STRIP_DIRECTION == 0) -#define _WIDTH WIDTH -#define THIS_X (HEIGHT - x - 1) -#define THIS_Y (WIDTH - y - 1) - +# define THIS_X (HEIGHT - x - 1) +# define THIS_Y (WIDTH - y - 1) #elif (CONNECTION_ANGLE == 3 && STRIP_DIRECTION == 1) -#define _WIDTH HEIGHT -#define THIS_X (WIDTH - y - 1) -#define THIS_Y x - +# define THIS_X (WIDTH - y - 1) +# define THIS_Y x #else -#define _WIDTH WIDTH -#define THIS_X x -#define THIS_Y y -#pragma message "Wrong matrix parameters! Set to default" - +# define _WIDTH WIDTH +# define THIS_X x +# define THIS_Y y +# pragma message "Wrong matrix parameters! Set to default" #endif - extern CRGB leds[LEDS_CNT]; // Получить номер пикселя по координатам @@ -163,9 +150,9 @@ static uint16_t getPixNum(const uint8_t x, const uint8_t y) #endif || MATRIX_TYPE) // если чётная строка { - return (THIS_Y * _WIDTH + THIS_X); + return (THIS_X * _WIDTH + THIS_Y); } else { // если нечётная строка - return (THIS_Y * _WIDTH + _WIDTH - THIS_X - 1); + return (THIS_X * _WIDTH + _WIDTH - THIS_Y - 1); } } From 1c88a7c41d2df32a9d58014f32cb28b1fb99a517 Mon Sep 17 00:00:00 2001 From: npo6ka Date: Thu, 19 Dec 2019 23:37:01 +0300 Subject: [PATCH 20/75] fix 16 and 0 mods --- src/effects/00_slow_random.h | 35 +++++++++++---------------- src/effects/16_rainbow_static_point.h | 8 +++--- src/effects/lib_led.h | 2 +- src/main.cpp | 1 + 4 files changed, 20 insertions(+), 26 deletions(-) diff --git a/src/effects/00_slow_random.h b/src/effects/00_slow_random.h index d9f8a18..af93654 100644 --- a/src/effects/00_slow_random.h +++ b/src/effects/00_slow_random.h @@ -4,7 +4,7 @@ class SlowRandom : public Effect { - uint8_t inc_val[WIDTH][HEIGHT]; + uint8_t inc_val[LEDS_CNT]; int step; public: @@ -25,34 +25,27 @@ class SlowRandom : public Effect step = 2; set_fps(120); - uint8_t i, j; + uint8_t i; - for (i = 0; i < HEIGHT; i++) { - for (j = 0; j < WIDTH; j++) { - CRGB cur_cl = getPixColor(i, j); + for (i = 0; i < LEDS_CNT; i++) { + CRGB &cur_cl = getLeds()[i]; - inc_val[i][j] = gen_led(cur_cl.r) << 4; - inc_val[i][j] |= gen_led(cur_cl.g) << 2; - inc_val[i][j] |= gen_led(cur_cl.b); - - setPixColor(i, j, getPixColor(cur_cl)); - } + inc_val[i] = gen_led(cur_cl.r) << 4; + inc_val[i] |= gen_led(cur_cl.g) << 2; + inc_val[i] |= gen_led(cur_cl.b); } } void on_update() { - uint8_t i, j, buf; + uint8_t i, buf; - for (i = 0; i < HEIGHT; i++) { - for (j = 0; j < WIDTH; j++) { - CRGB cur_cl = getPixColor(i, j); - buf = proc_val(cur_cl.r, (inc_val[i][j] >> 4) & 0x3) << 4; - buf |= proc_val(cur_cl.g, (inc_val[i][j] >> 2) & 0x3) << 2; - buf |= proc_val(cur_cl.b, inc_val[i][j] & 0x3); - inc_val[i][j] = buf; + for (i = 0; i < LEDS_CNT; i++) { + CRGB &cur_cl = getLeds()[i]; - setPixColor(i, j, getPixColor(cur_cl)); - } + buf = proc_val(cur_cl.r, (inc_val[i] >> 4) & 0x3) << 4; + buf |= proc_val(cur_cl.g, (inc_val[i] >> 2) & 0x3) << 2; + buf |= proc_val(cur_cl.b, inc_val[i] & 0x3); + inc_val[i] = buf; } } diff --git a/src/effects/16_rainbow_static_point.h b/src/effects/16_rainbow_static_point.h index cb9d831..66a4618 100644 --- a/src/effects/16_rainbow_static_point.h +++ b/src/effects/16_rainbow_static_point.h @@ -11,11 +11,11 @@ class RainbowStaticPoint : public Effect void rainbow_static_point_render_point() { int i, j; - int x = WIDTH / 2 * ACCURACY; - int y = HEIGHT / 2 * ACCURACY; + int x = HEIGHT / 2 * ACCURACY; + int y = WIDTH / 2 * ACCURACY; - for (i = 0; i < WIDTH; ++i) { - for(j = 0; j < HEIGHT; ++j) { + for (i = 0; i < HEIGHT; ++i) { + for(j = 0; j < WIDTH; ++j) { int loc_x = i * ACCURACY + ACCURACY / 2; int loc_y = j * ACCURACY + ACCURACY / 2; diff --git a/src/effects/lib_led.h b/src/effects/lib_led.h index 33eb533..1f070dd 100644 --- a/src/effects/lib_led.h +++ b/src/effects/lib_led.h @@ -8,7 +8,7 @@ #define DATA_PIN (2U) // номер порта к которому подключены светодиоды #define WIDTH 16 -#define HEIGHT 16 +#define HEIGHT 8 #define LEDS_CNT WIDTH * HEIGHT #define CURRENT_LIMIT (2000U) // лимит по току в миллиамперах, автоматически управляет яркостью (пожалей свой блок питания!) 0 - выключить лимит diff --git a/src/main.cpp b/src/main.cpp index 34c40c9..5a7e9d9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,6 +14,7 @@ void setup() { setup_buttons(); FastLED.setBrightness(150); + EffectsList::getInstance().setEffect(0); } //unsigned long tick = 0; From 3fe88f9a916f262970637ae64bafa89c6dfc5bdf Mon Sep 17 00:00:00 2001 From: npo6ka Date: Thu, 19 Dec 2019 23:46:29 +0300 Subject: [PATCH 21/75] fix 02 and 03 mods --- src/effects/02_dribs.h | 22 +++++++++++----------- src/effects/03_rain.h | 24 +++++++++++------------- src/main.cpp | 2 +- 3 files changed, 23 insertions(+), 25 deletions(-) diff --git a/src/effects/02_dribs.h b/src/effects/02_dribs.h index f6a42b3..37d4129 100644 --- a/src/effects/02_dribs.h +++ b/src/effects/02_dribs.h @@ -15,24 +15,24 @@ class Dribs : public Effect void on_update() { - for (int i = 0; i < WIDTH; ++i) { - if (cur_drib[i] == 0 && random8(40) == 0) { - cur_drib[i] = 1; - } else if (cur_drib[i] != 0) { - cur_drib[i] += 1; - if (cur_drib[i] >= lenght + WIDTH) { - cur_drib[i] = 0; + for (int j = 0; j < WIDTH; ++j) { + if (cur_drib[j] == 0 && random8(40) == 0) { + cur_drib[j] = 1; + } else if (cur_drib[j] != 0) { + cur_drib[j] += 1; + if (cur_drib[j] >= lenght + WIDTH) { + cur_drib[j] = 0; } } } int step = 255 / lenght; - for (int j = 0; j < WIDTH; ++j) { - for (int i = 0; i < HEIGHT; ++i) { + for (int i = 0; i < HEIGHT; ++i) { + for (int j = 0; j < WIDTH; ++j) { if (cur_drib[j] != 0 && i < cur_drib[j]) { - getPix(HEIGHT - i - 1, j) = CRGB(0, 0, max(255 - (cur_drib[j] - i - 1) * step, 0)); + getPix(i, j) = CRGB(0, 0, max(255 - (cur_drib[j] - i - 1) * step, 0)); } else { - getPix(HEIGHT - i - 1, j) = 0x0; + getPix(i, j) = 0x0; } } } diff --git a/src/effects/03_rain.h b/src/effects/03_rain.h index 654bef9..0009bd7 100644 --- a/src/effects/03_rain.h +++ b/src/effects/03_rain.h @@ -14,20 +14,18 @@ class Rain : public Effect } void on_update() { - int i, j; + int i; + + for (i = 0; i < LEDS_CNT; ++i) { + CRGB &cl = getLeds()[i]; - for (i = 0; i < HEIGHT; ++i) { - for (j = 0; j < WIDTH; ++j) { - CRGB &cl = getPix(i, j); - - if (random8(255) == 0) { - cl = CRGB(0, 0, 255); - } else if (cl.b > 0) { - if (cl.b > step) { - cl = CRGB(0, 0, cl.b - step); - } else { - cl = CRGB(0, 0, 0); - } + if (random8(255) == 0) { + cl = CRGB(0, 0, 255); + } else if (cl.b > 0) { + if (cl.b > step) { + cl = CRGB(0, 0, cl.b - step); + } else { + cl = CRGB(0, 0, 0); } } } diff --git a/src/main.cpp b/src/main.cpp index 5a7e9d9..d16b5ae 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,7 +14,7 @@ void setup() { setup_buttons(); FastLED.setBrightness(150); - EffectsList::getInstance().setEffect(0); + EffectsList::getInstance().setEffect(2); } //unsigned long tick = 0; From db64decd2d24921cceb1ab4ebe73de4b0ad39a84 Mon Sep 17 00:00:00 2001 From: npo6ka Date: Fri, 20 Dec 2019 00:03:50 +0300 Subject: [PATCH 22/75] fix 04 and 05 effects --- src/effects/04_all_random.h | 4 ++-- src/effects/05_snow.h | 16 ++++++++-------- src/main.cpp | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/effects/04_all_random.h b/src/effects/04_all_random.h index 8185a60..e20cddf 100644 --- a/src/effects/04_all_random.h +++ b/src/effects/04_all_random.h @@ -9,14 +9,14 @@ class AllRandom : public Effect AllRandom() {} void on_init() { - set_fps(30); + set_fps(15); } void on_update() { int i; for (i = 0; i < LEDS_CNT; i++) { - getPix(i / WIDTH, i % WIDTH) = CRGB(random8(255), random8(255), random8(255)); + getLeds()[i] = CRGB(random8(255), random8(255), random8(255)); } } }; diff --git a/src/effects/05_snow.h b/src/effects/05_snow.h index d6dc6f4..9a627fd 100644 --- a/src/effects/05_snow.h +++ b/src/effects/05_snow.h @@ -25,21 +25,21 @@ class Snow : public Effect if (tick >= step) { direction = !direction; // сдвигаем вниз - for (int8_t x = 0; x < HEIGHT; x++) { + for (int8_t x = HEIGHT - 1; x >= 0; --x) { bool dir = direction; for (uint8_t y = 0; y < WIDTH; y++) { if (getPixColor(x, y)) { - if (x - 1 >= 0) { + if (x + 1 < HEIGHT) { if (dir) { - if (y + 1 < WIDTH) setPixColor(x - 1, y + 1, getPixColor(x, y)); + if (y + 1 < WIDTH) getPix(x + 1, y + 1) = getPixColor(x, y); } else { - if (y - 1 >= 0) setPixColor(x - 1, y - 1, getPixColor(x, y)); + if (y - 1 >= 0) getPix(x + 1, y - 1) = getPixColor(x, y); } dir = !dir; } - setPixColor(x, y, 0); + getPix(x, y) = 0x0; } } } @@ -47,11 +47,11 @@ class Snow : public Effect for (uint8_t x = 0; x < WIDTH - 1; x++) { // заполняем случайно верхнюю строку // а также не даём двум блокам по вертикали вместе быть - if (getPixColor(HEIGHT - 2, x) == 0 && (random(0, density) == 0)) { - setPixColor(HEIGHT - 1, x, 0xE0FFFF - 0x101010 * random(0, 4)); + if (getPixColor(1, x) == 0 && (random(0, density) == 0)) { + getPix(0, x) = 0xE0FFFF - 0x101010 * random(0, 4); x++; } else { - setPixColor(HEIGHT - 1, x, 0x000000); + getPix(0, x) = 0x000000; } } diff --git a/src/main.cpp b/src/main.cpp index d16b5ae..aafae17 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,7 +14,7 @@ void setup() { setup_buttons(); FastLED.setBrightness(150); - EffectsList::getInstance().setEffect(2); + EffectsList::getInstance().setEffect(5); } //unsigned long tick = 0; From 4a1fcc7715e46dd2dc2facda826fb1859f930a8b Mon Sep 17 00:00:00 2001 From: npo6ka Date: Fri, 20 Dec 2019 00:36:45 +0300 Subject: [PATCH 23/75] fix 07 and 08 mods --- src/effects/07_the_matrix.h | 29 +++++++++++++++-------------- src/effects/08_simple_balls.h | 12 ++++++------ src/effects/lib_led.h | 4 ++-- src/main.cpp | 2 +- 4 files changed, 24 insertions(+), 23 deletions(-) diff --git a/src/effects/07_the_matrix.h b/src/effects/07_the_matrix.h index 599eea2..3c4416f 100644 --- a/src/effects/07_the_matrix.h +++ b/src/effects/07_the_matrix.h @@ -4,6 +4,7 @@ class TheMatrix : public Effect { + uint8_t fade = 0x20; public: TheMatrix() {} @@ -13,22 +14,22 @@ class TheMatrix : public Effect } void on_update() { - for (uint8_t x = 0; x < WIDTH; x++) { - // заполняем случайно верхнюю строку - uint32_t thisColor = getPixColor(x, HEIGHT - 1); - if (thisColor == 0) - setPixColor(x, HEIGHT - 1, 0x00FF00 * (random(0, 10) == 0)); - else if (thisColor < 0x002000) - setPixColor(x, HEIGHT - 1, 0); - else - setPixColor(x, HEIGHT - 1, thisColor - 0x002000); - } - // сдвигаем всё вниз - for (uint8_t x = 0; x < WIDTH; x++) { - for (uint8_t y = 0; y < HEIGHT - 1; y++) { - setPixColor(x, y, getPixColor(x, y + 1)); + for (uint8_t x = HEIGHT - 1; x > 0 ; --x) { + for (uint8_t y = 0; y < WIDTH; ++y) { + getPix(x, y) = getPixColor(x - 1, y); } } + + for (uint8_t y = 0; y < WIDTH; y++) { + uint32_t thisColor = getPixColor(0, y); + + if (thisColor == 0) + // заполняем случайно верхнюю строку + getPix(0, y) = 0x00FF00 * (random(0, 10) == 0); + else + // или просто делайем фейд пикеля + fadePix(0, y, fade); + } } }; diff --git a/src/effects/08_simple_balls.h b/src/effects/08_simple_balls.h index 27680ab..28dbf62 100644 --- a/src/effects/08_simple_balls.h +++ b/src/effects/08_simple_balls.h @@ -21,10 +21,10 @@ class SimpleBalls : public Effect int sign; // забиваем случайными данными - coord[j][0] = WIDTH / 2 * 10; + coord[j][0] = HEIGHT / 2 * 10; random(2) ? sign = 1 : sign = -1; vector[j][0] = random(4, 15) * sign; - coord[j][1] = HEIGHT / 2 * 10; + coord[j][1] = WIDTH / 2 * 10; random(2) ? sign = 1 : sign = -1; vector[j][1] = random(4, 15) * sign; ballColors[j] = CHSV(random(0, 9) * 28, 255, 255); @@ -71,12 +71,12 @@ class SimpleBalls : public Effect } } - if (coord[j][0] > (WIDTH - 1) * 10) { - coord[j][0] = (WIDTH - 1) * 10; + if (coord[j][0] > (HEIGHT - 1) * 10) { + coord[j][0] = (HEIGHT - 1) * 10; vector[j][0] = -vector[j][0]; } - if (coord[j][1] > (HEIGHT - 1) * 10) { - coord[j][1] = (HEIGHT - 1) * 10; + if (coord[j][1] > (WIDTH - 1) * 10) { + coord[j][1] = (WIDTH - 1) * 10; vector[j][1] = -vector[j][1]; } setPixColor(coord[j][0] / 10, coord[j][1] / 10, ballColors[j]); diff --git a/src/effects/lib_led.h b/src/effects/lib_led.h index 1f070dd..201ab4c 100644 --- a/src/effects/lib_led.h +++ b/src/effects/lib_led.h @@ -193,8 +193,8 @@ static CRGB* getLeds(void) { } static void fader(uint8_t step) { - for (uint8_t i = 0; i < WIDTH; i++) { - for (uint8_t j = 0; j < HEIGHT; j++) { + for (uint8_t i = 0; i < HEIGHT; i++) { + for (uint8_t j = 0; j < WIDTH; j++) { fadePix(i, j, step); } } diff --git a/src/main.cpp b/src/main.cpp index aafae17..a47fb02 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,7 +14,7 @@ void setup() { setup_buttons(); FastLED.setBrightness(150); - EffectsList::getInstance().setEffect(5); + EffectsList::getInstance().setEffect(8); } //unsigned long tick = 0; From 79fbdb92fc3da1ee36f74c88af1ddd6948209a9a Mon Sep 17 00:00:00 2001 From: npo6ka Date: Fri, 20 Dec 2019 00:54:58 +0300 Subject: [PATCH 24/75] fix 09 and 11 mods --- src/effects/06_fire.h | 4 ++-- src/effects/09_confetti.h | 9 +++++---- src/effects/11_dynamic_square.h | 20 +++++++++----------- src/effects/lib_led.h | 4 ++-- src/main.cpp | 2 +- 5 files changed, 19 insertions(+), 20 deletions(-) diff --git a/src/effects/06_fire.h b/src/effects/06_fire.h index 3c15764..f8c2bcf 100644 --- a/src/effects/06_fire.h +++ b/src/effects/06_fire.h @@ -44,14 +44,14 @@ class Fire : public Effect } void on_update() { - if (pcnt >= 100) { + /*if (pcnt >= 100) { shiftUp(); generateLine(); pcnt = 0; } drawFrame(pcnt); - pcnt += 30; + pcnt += 30;*/ } // Randomly generate the next line (matrix row) diff --git a/src/effects/09_confetti.h b/src/effects/09_confetti.h index 23c8e11..8631517 100644 --- a/src/effects/09_confetti.h +++ b/src/effects/09_confetti.h @@ -10,16 +10,17 @@ class Confetti : public Effect Confetti() {} void on_init() { - + set_fps(30); } void on_update() { for (uint8_t i = 0; i < density; i++) { - uint8_t x = random(WIDTH); - uint8_t y = random(HEIGHT); + uint8_t x = random(HEIGHT); + uint8_t y = random(WIDTH); if (!getPixColor(x, y)) { - setPixColor(x, y, CHSV(random(0, 255), 255, 255)); + getPix(x, y) = CHSV(random(0, 255), 255, 255); } + fader(brightness_step); } } diff --git a/src/effects/11_dynamic_square.h b/src/effects/11_dynamic_square.h index e8dc57a..c0fc7cf 100644 --- a/src/effects/11_dynamic_square.h +++ b/src/effects/11_dynamic_square.h @@ -4,31 +4,29 @@ class DynamicSquare : public Effect { - uint8_t lenght = 2; - uint8_t step = 255 / lenght; + uint8_t fade_step = 192; uint8_t radius = WIDTH > HEIGHT ? (HEIGHT / 2): (WIDTH / 2); //uint32_t color = 0x0000ff; uint8_t hsv = 0; + uint8_t cur_ring = 0; public: DynamicSquare() {} void on_init() { - + set_fps(10); } void on_update() { - static uint8_t cur_ring = 0; - CRGB color = CHSV(hsv, 255, 255); - fader(step); - drawLine(cur_ring, cur_ring, WIDTH - cur_ring - 1, cur_ring, color); - drawLine(cur_ring, cur_ring, cur_ring, HEIGHT - cur_ring - 1, color); - drawLine(WIDTH - cur_ring - 1, HEIGHT - cur_ring - 1, cur_ring, HEIGHT - cur_ring - 1, color); - drawLine(WIDTH - cur_ring - 1, HEIGHT - cur_ring - 1, WIDTH - cur_ring - 1, cur_ring, color); + fader(fade_step); + drawLine(cur_ring, cur_ring, HEIGHT - cur_ring - 1, cur_ring, color); + drawLine(cur_ring, cur_ring, cur_ring, WIDTH - cur_ring - 1, color); + drawLine(HEIGHT - cur_ring - 1, WIDTH - cur_ring - 1, cur_ring, WIDTH - cur_ring - 1, color); + drawLine(HEIGHT - cur_ring - 1, WIDTH - cur_ring - 1, HEIGHT - cur_ring - 1, cur_ring, color); - cur_ring = (cur_ring + 1) % (radius * 2); + cur_ring = (cur_ring + 1) % (radius); hsv = (hsv + 1) % 256; //delay(25); } diff --git a/src/effects/lib_led.h b/src/effects/lib_led.h index 201ab4c..9ce9e8c 100644 --- a/src/effects/lib_led.h +++ b/src/effects/lib_led.h @@ -215,9 +215,9 @@ static void drawLine(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, CRGB color) int16_t error = deltaX - deltaY; int32_t error2; - setPixColor(x2, y2, color); + getPix(x2, y2) = color; while (x1 != x2 || y1 != y2) { - setPixColor(x1, y1, color); + getPix(x1, y1) = color; error2 = error * 2; if (error2 > -deltaY) { diff --git a/src/main.cpp b/src/main.cpp index a47fb02..629834c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,7 +14,7 @@ void setup() { setup_buttons(); FastLED.setBrightness(150); - EffectsList::getInstance().setEffect(8); + EffectsList::getInstance().setEffect(11); } //unsigned long tick = 0; From d7cb0726afd38df7d8765860bf61dfc2275b0a21 Mon Sep 17 00:00:00 2001 From: npo6ka Date: Fri, 20 Dec 2019 23:39:43 +0300 Subject: [PATCH 25/75] fix lib --- platformio.ini | 3 ++- src/effects/lib_led.h | 15 ++++----------- src/main.cpp | 2 +- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/platformio.ini b/platformio.ini index c7ef45d..ea64f8c 100644 --- a/platformio.ini +++ b/platformio.ini @@ -13,4 +13,5 @@ platform = espressif8266 board = esp12e framework = arduino build_flags = -Wno-unused-function -monitor_speed = 115200 \ No newline at end of file +monitor_speed = 115200 +upload_speed = 921600 \ No newline at end of file diff --git a/src/effects/lib_led.h b/src/effects/lib_led.h index 9ce9e8c..af37cca 100644 --- a/src/effects/lib_led.h +++ b/src/effects/lib_led.h @@ -65,13 +65,13 @@ # define THIS_Y (WIDTH - y - 1) #elif (CONNECTION_ANGLE == 2 && STRIP_DIRECTION == 1) # define THIS_X (WIDTH - y - 1) -# define THIS_Y (HEIGHT - x - 1) +# define THIS_Y x #elif (CONNECTION_ANGLE == 3 && STRIP_DIRECTION == 0) # define THIS_X (HEIGHT - x - 1) # define THIS_Y (WIDTH - y - 1) #elif (CONNECTION_ANGLE == 3 && STRIP_DIRECTION == 1) -# define THIS_X (WIDTH - y - 1) -# define THIS_Y x +# define THIS_X (WIDTH - y - 1) +# define THIS_Y (HEIGHT - x - 1) #else # define _WIDTH WIDTH # define THIS_X x @@ -142,14 +142,7 @@ static uint16_t getPixNum(const uint8_t x, const uint8_t y) return 0; } - if ( -#if (STRIP_DIRECTION == 0) - x % 2 == 0 -#else - y % 2 == 0 -#endif - || MATRIX_TYPE) // если чётная строка - { + if (THIS_X % 2 == 0 || MATRIX_TYPE) { // если чётная строка return (THIS_X * _WIDTH + THIS_Y); } else { // если нечётная строка return (THIS_X * _WIDTH + _WIDTH - THIS_Y - 1); diff --git a/src/main.cpp b/src/main.cpp index 629834c..ed96dc4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,7 +14,7 @@ void setup() { setup_buttons(); FastLED.setBrightness(150); - EffectsList::getInstance().setEffect(11); + EffectsList::getInstance().setEffect(16); } //unsigned long tick = 0; From aafe1fdca366971b76c559b6afdc37eb555af384 Mon Sep 17 00:00:00 2001 From: npo6ka Date: Sat, 21 Dec 2019 00:32:02 +0300 Subject: [PATCH 26/75] fix 10 mod --- src/effects/10_starfall.h | 36 ++++++++++++++++++------------------ src/main.cpp | 2 +- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/effects/10_starfall.h b/src/effects/10_starfall.h index 0316fa9..3cc6e43 100644 --- a/src/effects/10_starfall.h +++ b/src/effects/10_starfall.h @@ -11,41 +11,41 @@ class Starfall : public Effect Starfall () {} void on_init() { - set_fps(30); + set_fps(20); } void on_update() { // заполняем головами комет левую и верхнюю линию - for (uint8_t i = HEIGHT / 2; i < HEIGHT; i++) { - if (!getPixColor(0, i) + for (uint8_t i = 0; i <= HEIGHT - 3; i++) { + if (!getPixColor(i, WIDTH - 1) && (random(0, density) == 0) - && i + 1 < HEIGHT && !getPixColor(0, i + 1) - && i - 1 >= 0 && !getPixColor(0, i - 1)) { - setPixColor(0, i, CHSV(random(0, 200), saturation, 255)); + && i + 1 < HEIGHT && !getPixColor(i + 1, WIDTH - 1) + && i - 1 >= 0 && !getPixColor(i - 1, WIDTH - 1)) { + getPix(i, WIDTH - 1) = CHSV(random(0, 200), saturation, 255); } } - for (uint8_t i = 0; i < WIDTH / 2; i++) { - if (!getPixColor(i, HEIGHT - 1) + for (uint8_t i = 3; i < WIDTH; i++) { + if (!getPixColor(0, i) && (random(0, density) == 0) - && i + 1 < HEIGHT && !getPixColor(i + 1, HEIGHT - 1) - && i - 1 >= 0 && !getPixColor(i - 1, HEIGHT - 1)) { - setPixColor(i, HEIGHT - 1, CHSV(random(0, 200), saturation, 255)); + && i + 1 < WIDTH && !getPixColor(0, i + 1) + && i - 1 >= 0 && !getPixColor(0, i - 1)) { + getPix(0, i) = CHSV(random(0, 200), saturation, 255); } } // сдвигаем по диагонали - for (uint8_t y = 0; y < HEIGHT - 1; y++) { - for (uint8_t x = WIDTH - 1; x > 0; x--) { - setPixColor(x, y, getPixColor(x - 1, y + 1)); + for (uint8_t x = HEIGHT - 1; x > 0; x--) { + for (int16_t y = WIDTH - 2; y >= 0; y--) { + getPix(x, y) = getPixColor(x - 1, y + 1); } } // уменьшаем яркость левой и верхней линии, формируем "хвосты" - for (uint8_t i = HEIGHT / 2; i < HEIGHT; i++) { - fadePix(0, i, tail_step); + for (uint8_t i = 0; i <= HEIGHT - 3; i++) { + fadePix(i, WIDTH - 1, tail_step); } - for (uint8_t i = 0; i < WIDTH / 2; i++) { - fadePix(i, HEIGHT - 1, tail_step); + for (uint8_t i = 3; i < WIDTH; i++) { + fadePix(0, i, tail_step); } } }; diff --git a/src/main.cpp b/src/main.cpp index ed96dc4..3c10121 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,7 +14,7 @@ void setup() { setup_buttons(); FastLED.setBrightness(150); - EffectsList::getInstance().setEffect(16); + EffectsList::getInstance().setEffect(10); } //unsigned long tick = 0; From 9b9919241c33e7f0094f43f51773f6a201aaea5f Mon Sep 17 00:00:00 2001 From: npo6ka Date: Sat, 21 Dec 2019 00:54:45 +0300 Subject: [PATCH 27/75] fix 11 and 12 mods --- .vscode/settings.json | 10 ++++++++++ src/effects/11_dynamic_square.h | 23 +++++++++++++++-------- src/effects/12_random_rain.h | 29 ++++++++++++----------------- src/main.cpp | 2 +- 4 files changed, 38 insertions(+), 26 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..63fe915 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,10 @@ +{ + "files.associations": { + "array": "cpp", + "*.tcc": "cpp", + "functional": "cpp", + "istream": "cpp", + "tuple": "cpp", + "utility": "cpp" + } +} \ No newline at end of file diff --git a/src/effects/11_dynamic_square.h b/src/effects/11_dynamic_square.h index c0fc7cf..bc0d5e1 100644 --- a/src/effects/11_dynamic_square.h +++ b/src/effects/11_dynamic_square.h @@ -5,7 +5,7 @@ class DynamicSquare : public Effect { uint8_t fade_step = 192; - uint8_t radius = WIDTH > HEIGHT ? (HEIGHT / 2): (WIDTH / 2); + uint8_t radius = WIDTH > HEIGHT ? (HEIGHT): (WIDTH); //uint32_t color = 0x0000ff; uint8_t hsv = 0; uint8_t cur_ring = 0; @@ -14,17 +14,24 @@ class DynamicSquare : public Effect DynamicSquare() {} void on_init() { - set_fps(10); + set_fps(12); } - void on_update() { - CRGB color = CHSV(hsv, 255, 255); + void draw_rectangle(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, CRGB color) { + drawLine(x1, y1, x1, y2, color); + drawLine(x1, y1, x2, y1, color); + drawLine(x1, y2, x2, y2, color); + drawLine(x2, y1, x2, y2, color); + } + void on_update() { fader(fade_step); - drawLine(cur_ring, cur_ring, HEIGHT - cur_ring - 1, cur_ring, color); - drawLine(cur_ring, cur_ring, cur_ring, WIDTH - cur_ring - 1, color); - drawLine(HEIGHT - cur_ring - 1, WIDTH - cur_ring - 1, cur_ring, WIDTH - cur_ring - 1, color); - drawLine(HEIGHT - cur_ring - 1, WIDTH - cur_ring - 1, HEIGHT - cur_ring - 1, cur_ring, color); + + if (cur_ring <= radius / 2) { + draw_rectangle(cur_ring, cur_ring, HEIGHT - cur_ring - 1, WIDTH - cur_ring - 1, CHSV(hsv, 255, 255)); + } else { + draw_rectangle(radius - cur_ring + 1, radius - cur_ring + 1, HEIGHT - (radius - cur_ring + 1) - 1, WIDTH - (radius - cur_ring + 1) - 1, CHSV(hsv, 255, 255)); + } cur_ring = (cur_ring + 1) % (radius); hsv = (hsv + 1) % 256; diff --git a/src/effects/12_random_rain.h b/src/effects/12_random_rain.h index 40bb81a..3827f46 100644 --- a/src/effects/12_random_rain.h +++ b/src/effects/12_random_rain.h @@ -17,25 +17,20 @@ class RandomRain : public Effect void on_update() { - int i, j; + int i; - for (i = 0; i < WIDTH; ++i) { - for (j = 0; j < HEIGHT; ++j) { - fadePix(i, j, step); + for (i = 0; i < LEDS_CNT; ++i) { + CRGB &cl = getLeds()[i]; + cl.fadeToBlackBy(step); - CRGB cl = getPix(i, j); - - if (random16(500) == 0) { - cl.r = 255; - } - if (random16(500) == 0) { - cl.g = 255; - } - if (random16(500) == 0) { - cl.b = 255; - } - - setPixColor(i, j, cl); + if (random16(500) == 0) { + cl.r = 255; + } + if (random16(500) == 0) { + cl.g = 255; + } + if (random16(500) == 0) { + cl.b = 255; } } } diff --git a/src/main.cpp b/src/main.cpp index 3c10121..8937220 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,7 +14,7 @@ void setup() { setup_buttons(); FastLED.setBrightness(150); - EffectsList::getInstance().setEffect(10); + EffectsList::getInstance().setEffect(12); } //unsigned long tick = 0; From db79713529546217b220a16605cecda49583e139 Mon Sep 17 00:00:00 2001 From: npo6ka Date: Sat, 21 Dec 2019 01:03:23 +0300 Subject: [PATCH 28/75] fix 13 and 15 mods --- src/effects/13_rainbow_rain.h | 17 ++++++----------- src/effects/15_rainbow_point.h | 18 +++++++++--------- src/main.cpp | 2 +- 3 files changed, 16 insertions(+), 21 deletions(-) diff --git a/src/effects/13_rainbow_rain.h b/src/effects/13_rainbow_rain.h index 13d2b3e..db817ab 100644 --- a/src/effects/13_rainbow_rain.h +++ b/src/effects/13_rainbow_rain.h @@ -21,19 +21,14 @@ class RainbowRain : public Effect void on_update() { - int i, j; + int i; - for (i = 0; i < WIDTH; ++i) { - for (j = 0; j < HEIGHT; ++j) { - fadePix(i, j, fade_step); + for (i = 0; i < LEDS_CNT; ++i) { + CRGB &cl = getLeds()[i]; + cl.fadeToBlackBy(fade_step); - CRGB cl = getPix(i, j); - - if (random16(300) == 0) { - cl = CHSV(tick / rainbow_step, 255, 255); - } - - setPixColor(i, j, cl); + if (random16(300) == 0) { + cl = CHSV(tick / rainbow_step, 255, 255); } } diff --git a/src/effects/15_rainbow_point.h b/src/effects/15_rainbow_point.h index 7e20fcd..b7b3c1a 100644 --- a/src/effects/15_rainbow_point.h +++ b/src/effects/15_rainbow_point.h @@ -33,24 +33,24 @@ class RainbowPoint : public Effect if (x < 0) { x = 0; rainbow_point_gen_vector(true); - } else if (x >= ACCURACY * WIDTH) { - x = ACCURACY * WIDTH - 1; + } else if (x >= ACCURACY * HEIGHT) { + x = ACCURACY * HEIGHT - 1; rainbow_point_gen_vector(true); } if (y < 0) { y = 0; rainbow_point_gen_vector(false); - } else if (y >= ACCURACY * HEIGHT) { - y = ACCURACY * HEIGHT - 1; + } else if (y >= ACCURACY * WIDTH) { + y = ACCURACY * WIDTH - 1; rainbow_point_gen_vector(false); } } void rainbow_point_render_point() { int i, j; - for (i = 0; i < WIDTH; ++i) { - for(j = 0; j < HEIGHT; ++j) { + for (i = 0; i < HEIGHT; ++i) { + for(j = 0; j < WIDTH; ++j) { int loc_x = i * ACCURACY + ACCURACY / 2; int loc_y = j * ACCURACY + ACCURACY / 2; @@ -58,7 +58,7 @@ class RainbowPoint : public Effect float chsv = (distance / 8 + tick / RAINBOW_TICK_SIZE) % 255; - setPixColor(i, j, CHSV(chsv, 255, 255)); + getPix(i, j) = CHSV(chsv, 255, 255); } } } @@ -69,8 +69,8 @@ class RainbowPoint : public Effect void on_init() { tick = 0; - x = random16(0, (WIDTH - 1) * ACCURACY); - y = random16(0, (HEIGHT - 1) * ACCURACY); + x = random16(0, (HEIGHT - 1) * ACCURACY); + y = random16(0, (WIDTH - 1) * ACCURACY); vec_x = (int32_t)random(0, MAX_VEC_SIZE * 2) - MAX_VEC_SIZE; vec_y = (int32_t)random(0, MAX_VEC_SIZE * 2) - MAX_VEC_SIZE; diff --git a/src/main.cpp b/src/main.cpp index 8937220..8a7be17 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,7 +14,7 @@ void setup() { setup_buttons(); FastLED.setBrightness(150); - EffectsList::getInstance().setEffect(12); + EffectsList::getInstance().setEffect(15); } //unsigned long tick = 0; From 18e7c83b604d34b645df76708d54d64c6f79e194 Mon Sep 17 00:00:00 2001 From: npo6ka Date: Sat, 21 Dec 2019 01:18:26 +0300 Subject: [PATCH 29/75] fix 14 mod --- src/effects/14_points.h | 23 +++++++++++------------ src/effects/16_rainbow_static_point.h | 2 +- src/main.cpp | 2 +- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/effects/14_points.h b/src/effects/14_points.h index b952316..b49a501 100644 --- a/src/effects/14_points.h +++ b/src/effects/14_points.h @@ -43,16 +43,16 @@ class Points : public Effect if (pnt.x < 0) { pnt.x = 0; gen_vector(pnt, true); - } else if (pnt.x >= ACCURACY * WIDTH) { - pnt.x = ACCURACY * WIDTH - 1; + } else if (pnt.x >= ACCURACY * HEIGHT) { + pnt.x = ACCURACY * HEIGHT - 1; gen_vector(pnt, true); } if (pnt.y < 0) { pnt.y = 0; gen_vector(pnt, false); - } else if (pnt.y >= ACCURACY * HEIGHT) { - pnt.y = ACCURACY * HEIGHT - 1; + } else if (pnt.y >= ACCURACY * WIDTH) { + pnt.y = ACCURACY * WIDTH - 1; gen_vector(pnt, false); } } @@ -73,19 +73,18 @@ class Points : public Effect void render_point(Point pnt) { int i, j; - for (i = 0; i < WIDTH; ++i) { - for(j = 0; j < HEIGHT; ++j) { + for (i = 0; i < HEIGHT; ++i) { + for(j = 0; j < WIDTH; ++j) { int loc_x = i * ACCURACY + ACCURACY / 2; int loc_y = j * ACCURACY + ACCURACY / 2; int distance = sqrt((loc_x - pnt.x) * (loc_x - pnt.x) + (loc_y - pnt.y) * (loc_y - pnt.y)); float bright = get_func_brithtness(distance); - CRGB clr = getPix(i, j); + CRGB &clr = getPix(i, j); clr.r = qadd8(clr.r, (float)pnt.color.r * bright); clr.g = qadd8(clr.g, (float)pnt.color.g * bright); clr.b = qadd8(clr.b, (float)pnt.color.b * bright); - setPixColor(i, j, clr); } } } @@ -101,8 +100,8 @@ class Points : public Effect bright_radius = 400; for (i = 0; i < POINTS_AMNT; ++i) { - points[i].x = random16(0, (WIDTH - 1) * ACCURACY); - points[i].y = random16(0, (HEIGHT - 1) * ACCURACY); + points[i].x = random16(0, (HEIGHT - 1) * ACCURACY); + points[i].y = random16(0, (WIDTH - 1) * ACCURACY); points[i].vec_x = (int32_t)random(0, MAX_VEC_SIZE * 2) - MAX_VEC_SIZE; points[i].vec_y = (int32_t)random(0, MAX_VEC_SIZE * 2) - MAX_VEC_SIZE; @@ -110,8 +109,8 @@ class Points : public Effect } if (POINTS_AMNT >= 3) { - points[0].color = 0xff; - points[1].color = 0xff00; + points[0].color = 0x0000ff; + points[1].color = 0x00ff00; points[2].color = 0xff0000; } diff --git a/src/effects/16_rainbow_static_point.h b/src/effects/16_rainbow_static_point.h index 66a4618..ce55684 100644 --- a/src/effects/16_rainbow_static_point.h +++ b/src/effects/16_rainbow_static_point.h @@ -23,7 +23,7 @@ class RainbowStaticPoint : public Effect float chsv = (distance + tick / RAINBOW_TICK_SIZE) % 255; - setPixColor(i, j, CHSV(chsv, 255, 255)); + getPix(i, j) = CHSV(chsv, 255, 255); } } } diff --git a/src/main.cpp b/src/main.cpp index 8a7be17..50459c8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,7 +14,7 @@ void setup() { setup_buttons(); FastLED.setBrightness(150); - EffectsList::getInstance().setEffect(15); + EffectsList::getInstance().setEffect(14); } //unsigned long tick = 0; From 6fb6a5ad37383f8448c87a2c1c2e2a0d56a93049 Mon Sep 17 00:00:00 2001 From: npo6ka Date: Sat, 21 Dec 2019 01:34:18 +0300 Subject: [PATCH 30/75] fire stage 1 --- src/effects/06_fire.h | 20 +++++++++----------- src/main.cpp | 2 +- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/effects/06_fire.h b/src/effects/06_fire.h index f8c2bcf..420c9be 100644 --- a/src/effects/06_fire.h +++ b/src/effects/06_fire.h @@ -44,14 +44,14 @@ class Fire : public Effect } void on_update() { - /*if (pcnt >= 100) { + if (pcnt >= 100) { shiftUp(); generateLine(); pcnt = 0; } drawFrame(pcnt); - pcnt += 30;*/ + pcnt += 30; } // Randomly generate the next line (matrix row) @@ -64,8 +64,8 @@ class Fire : public Effect //shift all values in the matrix up one row void shiftUp() { - for (uint8_t y = HEIGHT - 1; y > 0; y--) { - for (uint8_t x = 0; x < WIDTH; x++) { + for (uint8_t y = WIDTH - 1; y > 0; y--) { + for (uint8_t x = 0; x < HEIGHT; x++) { uint8_t newX = x; if (x > 15) newX = x - 15; if (y > 7) continue; @@ -73,7 +73,7 @@ class Fire : public Effect } } - for (uint8_t x = 0; x < WIDTH; x++) { + for (uint8_t x = 0; x < HEIGHT; x++) { uint8_t newX = x; if (x > 15) { newX = x - 15; @@ -89,8 +89,8 @@ class Fire : public Effect int nextv; //each row interpolates with the one before it - for (uint8_t y = HEIGHT - 1; y > 0; y--) { - for (uint8_t x = 0; x < WIDTH; x++) { + for (uint8_t y = WIDTH - 1; y > 0; y--) { + for (uint8_t x = 0; x < HEIGHT; x++) { uint8_t newX = x; if (x > 15) { newX = x - 15; @@ -125,18 +125,16 @@ class Fire : public Effect } //first row interpolates with the "next" line - for (unsigned char x = 0; x < WIDTH; x++) { + for (unsigned char x = 0; x < HEIGHT; x++) { uint8_t newX = x; if (x > 15) { newX = x - 15; } - CRGB color = CHSV( + getPix(newX, 0) = CHSV( hue_add + hueMask[0][newX], // H 255, // S (uint8_t)(((100.0 - pcnt) * matrixValue[0][newX] + pcnt * line[newX]) / 100.0) // V ); - - setPixColor(newX, 0, color); } } }; diff --git a/src/main.cpp b/src/main.cpp index 50459c8..14ec61f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,7 +14,7 @@ void setup() { setup_buttons(); FastLED.setBrightness(150); - EffectsList::getInstance().setEffect(14); + EffectsList::getInstance().setEffect(6); } //unsigned long tick = 0; From 31e86ac751912b555ea0ac4142531f37b45ef7a4 Mon Sep 17 00:00:00 2001 From: npo6ka Date: Sat, 21 Dec 2019 01:58:55 +0300 Subject: [PATCH 31/75] fix crash on 00 mods --- src/effects/00_slow_random.h | 7 ++++--- src/effects/07_the_matrix.h | 2 +- src/effects/lib_led.h | 2 +- src/main.cpp | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/effects/00_slow_random.h b/src/effects/00_slow_random.h index af93654..7762738 100644 --- a/src/effects/00_slow_random.h +++ b/src/effects/00_slow_random.h @@ -15,7 +15,7 @@ class SlowRandom : public Effect uint8_t val = random8(2); if (val > 0) { - color_val = random8(255); + color_val = random8(); } return val; @@ -25,7 +25,7 @@ class SlowRandom : public Effect step = 2; set_fps(120); - uint8_t i; + uint16_t i; for (i = 0; i < LEDS_CNT; i++) { CRGB &cur_cl = getLeds()[i]; @@ -37,7 +37,8 @@ class SlowRandom : public Effect } void on_update() { - uint8_t i, buf; + uint16_t i; + uint8_t buf; for (i = 0; i < LEDS_CNT; i++) { CRGB &cur_cl = getLeds()[i]; diff --git a/src/effects/07_the_matrix.h b/src/effects/07_the_matrix.h index 3c4416f..a89fd7d 100644 --- a/src/effects/07_the_matrix.h +++ b/src/effects/07_the_matrix.h @@ -4,7 +4,7 @@ class TheMatrix : public Effect { - uint8_t fade = 0x20; + uint8_t fade = 0x30; public: TheMatrix() {} diff --git a/src/effects/lib_led.h b/src/effects/lib_led.h index af37cca..288b220 100644 --- a/src/effects/lib_led.h +++ b/src/effects/lib_led.h @@ -8,7 +8,7 @@ #define DATA_PIN (2U) // номер порта к которому подключены светодиоды #define WIDTH 16 -#define HEIGHT 8 +#define HEIGHT 16 #define LEDS_CNT WIDTH * HEIGHT #define CURRENT_LIMIT (2000U) // лимит по току в миллиамперах, автоматически управляет яркостью (пожалей свой блок питания!) 0 - выключить лимит diff --git a/src/main.cpp b/src/main.cpp index 14ec61f..2672147 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,7 +14,7 @@ void setup() { setup_buttons(); FastLED.setBrightness(150); - EffectsList::getInstance().setEffect(6); + EffectsList::getInstance().setEffect(7); } //unsigned long tick = 0; From 72cee40bad7e61d4512422a0ceea5aed7dd42298 Mon Sep 17 00:00:00 2001 From: npo6ka Date: Sat, 21 Dec 2019 02:25:38 +0300 Subject: [PATCH 32/75] fix fire effect --- src/effects/06_fire.h | 28 ++++++++++++++-------------- src/effects/lib_led.h | 2 +- src/main.cpp | 2 +- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/effects/06_fire.h b/src/effects/06_fire.h index 420c9be..7064160 100644 --- a/src/effects/06_fire.h +++ b/src/effects/06_fire.h @@ -64,8 +64,8 @@ class Fire : public Effect //shift all values in the matrix up one row void shiftUp() { - for (uint8_t y = WIDTH - 1; y > 0; y--) { - for (uint8_t x = 0; x < HEIGHT; x++) { + for (uint8_t y = HEIGHT - 1; y > 0; y--) { + for (uint8_t x = 0; x < WIDTH; x++) { uint8_t newX = x; if (x > 15) newX = x - 15; if (y > 7) continue; @@ -73,7 +73,7 @@ class Fire : public Effect } } - for (uint8_t x = 0; x < HEIGHT; x++) { + for (uint8_t x = 0; x < WIDTH; x++) { uint8_t newX = x; if (x > 15) { newX = x - 15; @@ -89,8 +89,8 @@ class Fire : public Effect int nextv; //each row interpolates with the one before it - for (uint8_t y = WIDTH - 1; y > 0; y--) { - for (uint8_t x = 0; x < HEIGHT; x++) { + for (uint8_t y = HEIGHT - 1; y > 0; y--) { + for (uint8_t x = 0; x < WIDTH - 1; x++) { uint8_t newX = x; if (x > 15) { newX = x - 15; @@ -106,31 +106,31 @@ class Fire : public Effect (uint8_t)max(0, nextv) // V ); - setPixColor(x, y, color); + getPix(HEIGHT - 1 - y, x) = color; } else if (y == 8 && sparkless) { - if (random8(20) == 0 && getPixColor(x, y - 1) != 0) { - setPixColor(x, y, getPixColor(x, y - 1)); + if (random8(20) == 0 && getPixColor(HEIGHT - y, x) != 0) { + getPix(HEIGHT - 1 - y, x) = getPixColor(HEIGHT - y, x); } else { - setPixColor(x, y, 0); + getPix(HEIGHT - 1 - y, x) = 0; } } else if (sparkless) { // старая версия для яркости - if (getPixColor(x, y - 1) > 0) { - setPixColor(x, y, getPixColor(x, y - 1)); + if (getPixColor(HEIGHT - y, x) > 0) { + getPix(HEIGHT - 1 - y, x) = getPixColor(HEIGHT - y, x); } else { - setPixColor(x, y, 0); + getPix(HEIGHT - 1 - y, x) = 0; } } } } //first row interpolates with the "next" line - for (unsigned char x = 0; x < HEIGHT; x++) { + for (unsigned char x = 0; x < WIDTH; x++) { uint8_t newX = x; if (x > 15) { newX = x - 15; } - getPix(newX, 0) = CHSV( + getPix(HEIGHT - 1, newX) = CHSV( hue_add + hueMask[0][newX], // H 255, // S (uint8_t)(((100.0 - pcnt) * matrixValue[0][newX] + pcnt * line[newX]) / 100.0) // V diff --git a/src/effects/lib_led.h b/src/effects/lib_led.h index 288b220..af37cca 100644 --- a/src/effects/lib_led.h +++ b/src/effects/lib_led.h @@ -8,7 +8,7 @@ #define DATA_PIN (2U) // номер порта к которому подключены светодиоды #define WIDTH 16 -#define HEIGHT 16 +#define HEIGHT 8 #define LEDS_CNT WIDTH * HEIGHT #define CURRENT_LIMIT (2000U) // лимит по току в миллиамперах, автоматически управляет яркостью (пожалей свой блок питания!) 0 - выключить лимит diff --git a/src/main.cpp b/src/main.cpp index 2672147..14ec61f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,7 +14,7 @@ void setup() { setup_buttons(); FastLED.setBrightness(150); - EffectsList::getInstance().setEffect(7); + EffectsList::getInstance().setEffect(6); } //unsigned long tick = 0; From eca6de24df7027774b57fc558bcc8b985e8c6010 Mon Sep 17 00:00:00 2001 From: npo6ka Date: Sat, 21 Dec 2019 02:27:50 +0300 Subject: [PATCH 33/75] small fix fire effect --- src/effects/06_fire.h | 2 +- src/effects/lib_led.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/effects/06_fire.h b/src/effects/06_fire.h index 7064160..44d8827 100644 --- a/src/effects/06_fire.h +++ b/src/effects/06_fire.h @@ -90,7 +90,7 @@ class Fire : public Effect //each row interpolates with the one before it for (uint8_t y = HEIGHT - 1; y > 0; y--) { - for (uint8_t x = 0; x < WIDTH - 1; x++) { + for (uint8_t x = 0; x < WIDTH; x++) { uint8_t newX = x; if (x > 15) { newX = x - 15; diff --git a/src/effects/lib_led.h b/src/effects/lib_led.h index af37cca..288b220 100644 --- a/src/effects/lib_led.h +++ b/src/effects/lib_led.h @@ -8,7 +8,7 @@ #define DATA_PIN (2U) // номер порта к которому подключены светодиоды #define WIDTH 16 -#define HEIGHT 8 +#define HEIGHT 16 #define LEDS_CNT WIDTH * HEIGHT #define CURRENT_LIMIT (2000U) // лимит по току в миллиамперах, автоматически управляет яркостью (пожалей свой блок питания!) 0 - выключить лимит From 195fe3d1efda47d455448e60e9f1f6cc7bee7138 Mon Sep 17 00:00:00 2001 From: npo6ka Date: Sat, 21 Dec 2019 13:21:57 +0300 Subject: [PATCH 34/75] add constants file --- src/button/button_handler.cpp | 8 +---- src/constants.h | 43 +++++++++++++++++++++++++++ src/effects/15_rainbow_point.h | 18 ++++++----- src/effects/16_rainbow_static_point.h | 10 ++++--- src/effects/lib_led.h | 37 +++-------------------- 5 files changed, 64 insertions(+), 52 deletions(-) create mode 100644 src/constants.h diff --git a/src/button/button_handler.cpp b/src/button/button_handler.cpp index d0eb903..c5642aa 100644 --- a/src/button/button_handler.cpp +++ b/src/button/button_handler.cpp @@ -3,15 +3,9 @@ #include "debug_lib.h" #include "effects/effectslist.h" -#define BTN_PIN (4U) // пин кнопки (D2) -#define BUTTON_STEP_TIMEOUT (100U) // каждые BUTTON_STEP_TIMEOUT мс будет генерироваться событие удержания кнопки (для регулировки яркости) -#define BUTTON_CLICK_TIMEOUT (500U) // максимальное время между нажатиями кнопки в мс, до достижения которого считается серия последовательных нажатий - -#define AUTOMOD_INTERVAL (5000U) // кол-во времни между автоматическим переключением режима - GButton touch(BTN_PIN, LOW_PULL, NORM_OPEN); -bool auto_mode = false; +bool auto_mode = DEFAULT_AUTOMOD; long int auto_mode_cnt = 0; void setup_buttons() { diff --git a/src/constants.h b/src/constants.h new file mode 100644 index 0000000..2b400c9 --- /dev/null +++ b/src/constants.h @@ -0,0 +1,43 @@ +#pragma once + +#define DEBUG (true) + +// ===================== Настройки Матрицы ===================== + +#define DATA_PIN (2U) // номер порта к которому подключены светодиоды + +#define WIDTH (16) +#define HEIGHT (16) +#define LEDS_CNT WIDTH * HEIGHT +#define CURRENT_LIMIT (2000U) // лимит по току в миллиамперах, автоматически управляет яркостью (пожалей свой блок питания!) 0 - выключить лимит +#define COLOR_ORDER (GRB) // порядок цветов на ленте. Если цвет отображается некорректно - меняйте. Начать можно с RGB + +#define MATRIX_TYPE (0U) // тип матрицы: 0 - зигзаг, 1 - параллельная +#define CONNECTION_ANGLE (3U) // угол подключения матрицы (0-3): 0 - левый нижний, 1 - левый верхний, 2 - правый верхний, 3 - правый нижний + // 1---------2 + // ----------- + // ----------- + // ----------- + // 0---------3 +#define STRIP_DIRECTION (1U) // направление ленты из угла (0-1): 0 - горизонтальное (из углов влево или вправо) + // 1>>>------2 1------<<<2 1---------2 1---------2 + // ----------- ----------- ----------- ----------- + // ----------- ----------- ----------- ----------- + // ----------- ----------- ----------- ----------- + // 0---------3 0---------3 0>>>------3 0------<<<3 + + // 1 - Вертикальное (из углов вверх или вниз) + // 1---------2 1---------2 1---------2 1---------2 + // v---------- ----------v ----------- ----------- + // v---------- ----------v ^---------- ----------^ + // ----------- ----------- ^---------- ----------^ + // 0---------3 0---------3 0---------3 0---------3 + +// ===================== Настройки Кнопки ===================== + +#define BTN_PIN (4U) // пин кнопки (D2) +#define BUTTON_STEP_TIMEOUT (100U) // каждые BUTTON_STEP_TIMEOUT мс будет генерироваться событие удержания кнопки (для регулировки яркости) +#define BUTTON_CLICK_TIMEOUT (500U) // максимальное время между нажатиями кнопки в мс, до достижения которого считается серия последовательных нажатий + +#define AUTOMOD_INTERVAL (5000U) // кол-во времни между автоматическим переключением режима +#define DEFAULT_AUTOMOD false // Начальное состояние автомода. true - вкл, false - выкл \ No newline at end of file diff --git a/src/effects/15_rainbow_point.h b/src/effects/15_rainbow_point.h index b7b3c1a..5164ffd 100644 --- a/src/effects/15_rainbow_point.h +++ b/src/effects/15_rainbow_point.h @@ -1,8 +1,6 @@ #pragma once #define ACCURACY 100 -#define MAX_VEC_SIZE 10 -#define RAINBOW_TICK_SIZE 4 //кол-во тиков до инкремента тика радуги #include "effect.h" @@ -13,13 +11,15 @@ class RainbowPoint : public Effect int32_t vec_x; int32_t vec_y; int tick; + uint8_t max_vec_size = 10; + uint8_t tick_size = 4; //кол-во тиков до инкремента тика радуги //arg2: horizontal barrier = true or vertical = false void rainbow_point_gen_vector(bool horVer) { int16_t dir = horVer ? 1 : -1; - vec_x = (vec_x > 0 ? -dir : dir) * random(0, MAX_VEC_SIZE); - vec_y = (vec_y > 0 ? dir : -dir) * random(0, MAX_VEC_SIZE); + vec_x = (vec_x > 0 ? -dir : dir) * random(0, max_vec_size); + vec_y = (vec_y > 0 ? dir : -dir) * random(0, max_vec_size); if (vec_y == 0 && vec_x == 0) { rainbow_point_gen_vector(horVer); @@ -56,7 +56,7 @@ class RainbowPoint : public Effect int distance = sqrt((loc_x - x) * (loc_x - x) + (loc_y - y) * (loc_y - y)); - float chsv = (distance / 8 + tick / RAINBOW_TICK_SIZE) % 255; + float chsv = (distance / 8 + tick / tick_size) % 255; getPix(i, j) = CHSV(chsv, 255, 255); } @@ -72,8 +72,8 @@ class RainbowPoint : public Effect x = random16(0, (HEIGHT - 1) * ACCURACY); y = random16(0, (WIDTH - 1) * ACCURACY); - vec_x = (int32_t)random(0, MAX_VEC_SIZE * 2) - MAX_VEC_SIZE; - vec_y = (int32_t)random(0, MAX_VEC_SIZE * 2) - MAX_VEC_SIZE; + vec_x = (int32_t)random(0, max_vec_size * 2) - max_vec_size; + vec_y = (int32_t)random(0, max_vec_size * 2) - max_vec_size; set_fps(60); } @@ -82,9 +82,11 @@ class RainbowPoint : public Effect { FastLED.clear(); - tick = (tick + 1) % (256 * RAINBOW_TICK_SIZE); + tick = (tick + 1) % (256 * tick_size); rainbow_point_move_point(); rainbow_point_render_point(); } }; + +#undef ACCURACY \ No newline at end of file diff --git a/src/effects/16_rainbow_static_point.h b/src/effects/16_rainbow_static_point.h index ce55684..a7cd5ab 100644 --- a/src/effects/16_rainbow_static_point.h +++ b/src/effects/16_rainbow_static_point.h @@ -1,13 +1,13 @@ #pragma once #define ACCURACY 10 -#define RAINBOW_TICK_SIZE 4 //кол-во тиков до инкремента тика радуги #include "effect.h" class RainbowStaticPoint : public Effect { int tick; + uint8_t tick_size = 4; //кол-во тиков до инкремента тика радуги void rainbow_static_point_render_point() { int i, j; @@ -21,7 +21,7 @@ class RainbowStaticPoint : public Effect int distance = sqrt((loc_x - x) * (loc_x - x) + (loc_y - y) * (loc_y - y)); - float chsv = (distance + tick / RAINBOW_TICK_SIZE) % 255; + float chsv = (distance + tick / tick_size) % 255; getPix(i, j) = CHSV(chsv, 255, 255); } @@ -39,7 +39,9 @@ class RainbowStaticPoint : public Effect void on_update() { - tick = (tick + 1) % (256 * RAINBOW_TICK_SIZE); + tick = (tick + 1) % (256 * tick_size); rainbow_static_point_render_point(); } -}; \ No newline at end of file +}; + +#undef ACCURACY \ No newline at end of file diff --git a/src/effects/lib_led.h b/src/effects/lib_led.h index 288b220..d4ede26 100644 --- a/src/effects/lib_led.h +++ b/src/effects/lib_led.h @@ -1,40 +1,10 @@ #pragma once -#define DEBUG true - #include "FastLED.h" +#include "constants.h" #include "debug_lib.h" -#define DATA_PIN (2U) // номер порта к которому подключены светодиоды - -#define WIDTH 16 -#define HEIGHT 16 -#define LEDS_CNT WIDTH * HEIGHT -#define CURRENT_LIMIT (2000U) // лимит по току в миллиамперах, автоматически управляет яркостью (пожалей свой блок питания!) 0 - выключить лимит - -#define MATRIX_TYPE (0U) // тип матрицы: 0 - зигзаг, 1 - параллельная -#define CONNECTION_ANGLE (2U) // угол подключения матрицы (0-3): 0 - левый нижний, 1 - левый верхний, 2 - правый верхний, 3 - правый нижний - // 1---------2 - // ----------- - // ----------- - // ----------- - // 0---------3 -#define STRIP_DIRECTION (0U) // направление ленты из угла (0-1): 0 - горизонтальное (из углов влево или вправо) - // 1>>>------2 1------<<<2 1---------2 1---------2 - // ----------- ----------- ----------- ----------- - // ----------- ----------- ----------- ----------- - // ----------- ----------- ----------- ----------- - // 0---------3 0---------3 0>>>------3 0------<<<3 - - // 1 - Вертикальное (из углов вверх или вниз) - // 1---------2 1---------2 1---------2 1---------2 - // v---------- ----------v ----------- ----------- - // v---------- ----------v ^---------- ----------^ - // ----------- ----------- ^---------- ----------^ - // 0---------3 0---------3 0---------3 0---------3 - - -// ************* НАСТРОЙКА МАТРИЦЫ ***** +// ************* НАСТРОЙКА МАТРИЦЫ ************** //hooks for old vertion #if (STRIP_DIRECTION == 2) # define STRIP_DIRECTION 0 @@ -93,6 +63,7 @@ static uint32_t getPixColor(CRGB val); // получить 24-битный код цвета по координатам static uint32_t getPixColor(int x, int y); +// УСТАРЕВШАЯ ФУНКЦИЯ. Лучше использовать getPix(x, y) = color // установить цвет пикселя по координатам. // Структура CRGB поддерживает автоматическую // конвертацию из CHSV и из int значений. @@ -124,7 +95,7 @@ static void drawLine(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, CRGB color) */ static void led_setup() { - FastLED.addLeds(leds, LEDS_CNT).setCorrection(TypicalLEDStrip); + FastLED.addLeds(leds, LEDS_CNT).setCorrection(TypicalLEDStrip); FastLED.setMaxPowerInVoltsAndMilliamps(5, CURRENT_LIMIT); FastLED.clear(); From 74f544ce15f31c091991ffc04b9b17d6bd145ee0 Mon Sep 17 00:00:00 2001 From: npo6ka Date: Thu, 26 Dec 2019 00:29:04 +0300 Subject: [PATCH 35/75] add text mode --- src/constants.h | 6 +- src/effects/17_text.h | 172 ++++++++++++++++++++++++++++++++++++ src/effects/effectslist.cpp | 3 + src/effects/fonts.h | 172 ++++++++++++++++++++++++++++++++++++ src/effects/lib_led.h | 7 +- src/main.cpp | 2 +- 6 files changed, 356 insertions(+), 6 deletions(-) create mode 100644 src/effects/17_text.h create mode 100644 src/effects/fonts.h diff --git a/src/constants.h b/src/constants.h index 2b400c9..7df34c8 100644 --- a/src/constants.h +++ b/src/constants.h @@ -6,9 +6,9 @@ #define DATA_PIN (2U) // номер порта к которому подключены светодиоды -#define WIDTH (16) -#define HEIGHT (16) -#define LEDS_CNT WIDTH * HEIGHT +#define WIDTH (16) // ширина матрицы +#define HEIGHT (16) // высота матрицы +#define LEDS_CNT (WIDTH * HEIGHT) // общее число светодиодов в матрице #define CURRENT_LIMIT (2000U) // лимит по току в миллиамперах, автоматически управляет яркостью (пожалей свой блок питания!) 0 - выключить лимит #define COLOR_ORDER (GRB) // порядок цветов на ленте. Если цвет отображается некорректно - меняйте. Начать можно с RGB diff --git a/src/effects/17_text.h b/src/effects/17_text.h new file mode 100644 index 0000000..c225361 --- /dev/null +++ b/src/effects/17_text.h @@ -0,0 +1,172 @@ +#pragma once + +#include "effect.h" +#include "fonts.h" + +class TextMode : public Effect +{ + uint8_t *printed_text; + uint32_t speed = 1; + uint32_t tick; + const uint8_t font[SYM_AMNT][SYM_SIZE] = DEFAULT_FONTS; + + uint8_t convert_char_utf8_to_cp1251(const char *str, uint32_t &pos) { + if (str[pos] < 0x80) { + return str[pos]; + } else if (str[pos] == 0xd0) { + pos++; + + if (str[pos] >= 0x90 && str[pos] <= 0xbf) { + return (uint16_t) str[pos] - 0xcfd0; + } else if (str[pos] == 0x81) { + return (uint16_t) str[pos] - 0xcfd9; + } + } else if (str[pos] == 0xd1) { + pos++; + + if (str[pos] >= 0x80 && str[pos] <= 0x8f) { + return (uint16_t) str[pos] - 0xd090; + } else if (str[pos] == 0x91) { + return (uint16_t) str[pos] - 0xd0d9; + } + } + + if (str[pos] == '\0') pos--; // фикс чтоб внешний цикл не ушёл в бесконечность + return 0; + } + + uint8_t *convert_utf8_to_cp1251(const char *str) { + uint32_t pos = 0; + uint32_t size = 0; + + if (str == nullptr) return nullptr; + while (str[pos] != '\0') { + if (convert_char_utf8_to_cp1251(str, pos)){ + size++; + } else { + out("Unknown symbol: pos %d: '%c' %d\n", pos, str[pos], str[pos]); + return nullptr; + } + pos++; + } + + uint8_t *ret_str = new uint8_t[size + 1]; + pos = 0; + uint32_t npos = 0; + + while (str[pos] != '\0') { + ret_str[npos] = convert_char_utf8_to_cp1251(str, pos); + + if (ret_str[npos]){ + npos++; + } + pos++; + } + + ret_str[npos] = '\0'; + + return ret_str; + } + + uint8_t get_char_num_in_font(uint8_t ch) { + if (ch >= 32 && ch <=126) { + return ch - 32; + } else if(ch >= 192) { + return ch - 97; + } + out("Undefined symbols: %c\n", ch); + + return 0; + } + + void draw_symbol(uint8_t ch, int16_t x_offset, int16_t y_offset) { + ch = get_char_num_in_font(ch); + + uint8_t byte_num = 0; + uint8_t cur_byte = 0; + + // нивротебенная хуйня для хуёвых шрифтов + for(uint8_t i = 0; i < LET_WIDTH; ++i) { + for (uint8_t j = 0; j < LET_HEIGHT; ++j) { + uint8_t cur_bit = (i * LET_HEIGHT + j) % 8; + if (cur_bit == 0) { + cur_byte = font[ch][byte_num]; + byte_num++; + } + + if (cur_byte & (1 << cur_bit)) { + if (x_offset + j >= 0 && x_offset + j < HEIGHT && y_offset + i >= 0 && y_offset + i < WIDTH) { + getPix(x_offset + j, y_offset + i) = 0xff00ff; + } + } + } + } + + // збс чётко всё + /*for(uint8_t i = 0; i < LET_HEIGHT; ++i) { + for (uint8_t j = 0; j < LET_WIDTH; ++j) { + uint8_t cur_bit = (i * LET_WIDTH + j) % 8; + if (cur_bit == 0) { + cur_byte = font[ch][byte_num]; + byte_num++; + } + out("%x %x\n", cur_byte, cur_bit); + if (cur_byte & (1 << cur_bit)) { + getPix(i, j) = 0xff00ff; + } + } + }*/ + } + + void draw_text() { + if (printed_text == nullptr) return; + + uint32_t pos = 0; + int32_t sym_pos = 0; + + while (printed_text[pos] != '\0') { + sym_pos = WIDTH + pos * (LET_WIDTH + SPACE) - tick / speed; + + if (sym_pos > -LET_WIDTH && sym_pos < WIDTH) { + out("%d %d\n", pos, sym_pos); + draw_symbol(printed_text[pos], (HEIGHT - LET_HEIGHT) / 2, sym_pos); + } + + pos++; + } + + if (sym_pos <= -LET_WIDTH) { + tick = 0; + } + } + +public: + TextMode() {} + ~TextMode() { + if (printed_text) { + free(printed_text); + } + } + + void set_text(const char *text) + { + if (printed_text) { + free(printed_text); + } + + printed_text = convert_utf8_to_cp1251(text); + } + + void on_init() + { + set_text("ебучий текст bleat'"); + set_fps(20); + } + + void on_update() + { + on_clear(); + draw_text(); + tick++; + } +}; \ No newline at end of file diff --git a/src/effects/effectslist.cpp b/src/effects/effectslist.cpp index 001734f..c5bd5ef 100644 --- a/src/effects/effectslist.cpp +++ b/src/effects/effectslist.cpp @@ -17,6 +17,7 @@ #include "14_points.h" #include "15_rainbow_point.h" #include "16_rainbow_static_point.h" +#include "17_text.h" /* #include "testmode.h"*/ @@ -85,6 +86,8 @@ Effect *EffectsList::getNewEffectInstance(int num) { return new RainbowPoint(); case 16: return new RainbowStaticPoint(); + case 17: + return new TextMode(); //синусоида с рандомными параметрами default: return NULL; diff --git a/src/effects/fonts.h b/src/effects/fonts.h new file mode 100644 index 0000000..25b905f --- /dev/null +++ b/src/effects/fonts.h @@ -0,0 +1,172 @@ +#pragma once + +#define LET_WIDTH 5 // ширина буквы шрифта +#define LET_HEIGHT 8 // высота буквы шрифта +#define SPACE 1 // пробел + +#define SYM_AMNT 159 // количество символов в массиве +#define SYM_SIZE 5 // кол-во байт занимаемых 1 символом + +#define DEFAULT_FONTS { \ + { 0x00, 0x00, 0x00, 0x00, 0x00}, /* 0x20 32 */\ + { 0x00, 0x00, 0x6f, 0x00, 0x00}, /* ! 0x21 33 */\ + { 0x00, 0x07, 0x00, 0x07, 0x00}, /* " 0x22 34 */\ + { 0x14, 0x7f, 0x14, 0x7f, 0x14}, /* # 0x23 35 */\ + { 0x00, 0x07, 0x04, 0x1e, 0x00}, /* $ 0x24 36 */\ + { 0x23, 0x13, 0x08, 0x64, 0x62}, /* % 0x25 37 */\ + { 0x36, 0x49, 0x56, 0x20, 0x50}, /* & 0x26 38 */\ + { 0x00, 0x00, 0x07, 0x00, 0x00}, /* ' 0x27 39 */\ + { 0x00, 0x1c, 0x22, 0x41, 0x00}, /* ( 0x28 40 */\ + { 0x00, 0x41, 0x22, 0x1c, 0x00}, /* ) 0x29 41 */\ + { 0x14, 0x08, 0x3e, 0x08, 0x14}, /* * 0x2a 42 */\ + { 0x08, 0x08, 0x3e, 0x08, 0x08}, /* + 0x2b 43 */\ + { 0x00, 0x50, 0x30, 0x00, 0x00}, /* , 0x2c 44 */\ + { 0x08, 0x08, 0x08, 0x08, 0x08}, /* - 0x2d 45 */\ + { 0x00, 0x60, 0x60, 0x00, 0x00}, /* . 0x2e 46 */\ + { 0x20, 0x10, 0x08, 0x04, 0x02}, /* / 0x2f 47 */\ + { 0x3e, 0x51, 0x49, 0x45, 0x3e}, /* 0 0x30 48 */\ + { 0x00, 0x42, 0x7f, 0x40, 0x00}, /* 1 0x31 49 */\ + { 0x42, 0x61, 0x51, 0x49, 0x46}, /* 2 0x32 50 */\ + { 0x21, 0x41, 0x45, 0x4b, 0x31}, /* 3 0x33 51 */\ + { 0x18, 0x14, 0x12, 0x7f, 0x10}, /* 4 0x34 52 */\ + { 0x27, 0x45, 0x45, 0x45, 0x39}, /* 5 0x35 53 */\ + { 0x3c, 0x4a, 0x49, 0x49, 0x30}, /* 6 0x36 54 */\ + { 0x01, 0x71, 0x09, 0x05, 0x03}, /* 7 0x37 55 */\ + { 0x36, 0x49, 0x49, 0x49, 0x36}, /* 8 0x38 56 */\ + { 0x06, 0x49, 0x49, 0x29, 0x1e}, /* 9 0x39 57 */\ + { 0x00, 0x36, 0x36, 0x00, 0x00}, /* : 0x3a 58 */\ + { 0x00, 0x56, 0x36, 0x00, 0x00}, /* ; 0x3b 59 */\ + { 0x08, 0x14, 0x22, 0x41, 0x00}, /* < 0x3c 60 */\ + { 0x14, 0x14, 0x14, 0x14, 0x14}, /* = 0x3d 61 */\ + { 0x00, 0x41, 0x22, 0x14, 0x08}, /* > 0x3e 62 */\ + { 0x02, 0x01, 0x51, 0x09, 0x06}, /* ? 0x3f 63 */\ + { 0x3e, 0x41, 0x5d, 0x49, 0x4e}, /* @ 0x40 64 */\ + { 0x7e, 0x09, 0x09, 0x09, 0x7e}, /* A 0x41 65 */\ + { 0x7f, 0x49, 0x49, 0x49, 0x36}, /* B 0x42 66 */\ + { 0x3e, 0x41, 0x41, 0x41, 0x22}, /* C 0x43 67 */\ + { 0x7f, 0x41, 0x41, 0x41, 0x3e}, /* D 0x44 68 */\ + { 0x7f, 0x49, 0x49, 0x49, 0x41}, /* E 0x45 69 */\ + { 0x7f, 0x09, 0x09, 0x09, 0x01}, /* F 0x46 70 */\ + { 0x3e, 0x41, 0x49, 0x49, 0x7a}, /* G 0x47 71 */\ + { 0x7f, 0x08, 0x08, 0x08, 0x7f}, /* H 0x48 72 */\ + { 0x00, 0x41, 0x7f, 0x41, 0x00}, /* I 0x49 73 */\ + { 0x20, 0x40, 0x41, 0x3f, 0x01}, /* J 0x4a 74 */\ + { 0x7f, 0x08, 0x14, 0x22, 0x41}, /* K 0x4b 75 */\ + { 0x7f, 0x40, 0x40, 0x40, 0x40}, /* L 0x4c 76 */\ + { 0x7f, 0x02, 0x0c, 0x02, 0x7f}, /* M 0x4d 77 */\ + { 0x7f, 0x04, 0x08, 0x10, 0x7f}, /* N 0x4e 78 */\ + { 0x3e, 0x41, 0x41, 0x41, 0x3e}, /* O 0x4f 79 */\ + { 0x7f, 0x09, 0x09, 0x09, 0x06}, /* P 0x50 80 */\ + { 0x3e, 0x41, 0x51, 0x21, 0x5e}, /* Q 0x51 81 */\ + { 0x7f, 0x09, 0x19, 0x29, 0x46}, /* R 0x52 82 */\ + { 0x46, 0x49, 0x49, 0x49, 0x31}, /* S 0x53 83 */\ + { 0x01, 0x01, 0x7f, 0x01, 0x01}, /* T 0x54 84 */\ + { 0x3f, 0x40, 0x40, 0x40, 0x3f}, /* U 0x55 85 */\ + { 0x0f, 0x30, 0x40, 0x30, 0x0f}, /* V 0x56 86 */\ + { 0x3f, 0x40, 0x30, 0x40, 0x3f}, /* W 0x57 87 */\ + { 0x63, 0x14, 0x08, 0x14, 0x63}, /* X 0x58 88 */\ + { 0x07, 0x08, 0x70, 0x08, 0x07}, /* Y 0x59 89 */\ + { 0x61, 0x51, 0x49, 0x45, 0x43}, /* Z 0x5a 90 */\ + { 0x3c, 0x4a, 0x49, 0x29, 0x1e}, /* [ 0x5b 91 */\ + { 0x02, 0x04, 0x08, 0x10, 0x20}, /* \ 0x5c 92 */\ + { 0x00, 0x41, 0x7f, 0x00, 0x00}, /* ] 0x5d 93 */\ + { 0x04, 0x02, 0x01, 0x02, 0x04}, /* ^ 0x5e 94 */\ + { 0x40, 0x40, 0x40, 0x40, 0x40}, /* _ 0x5f 95 */\ + { 0x00, 0x00, 0x03, 0x04, 0x00}, /* ` 0x60 96 */\ + { 0x20, 0x54, 0x54, 0x54, 0x78}, /* a 0x61 97 */\ + { 0x7f, 0x48, 0x44, 0x44, 0x38}, /* b 0x62 98 */\ + { 0x38, 0x44, 0x44, 0x44, 0x20}, /* c 0x63 99 */\ + { 0x38, 0x44, 0x44, 0x48, 0x7f}, /* d 0x64 100 */\ + { 0x38, 0x54, 0x54, 0x54, 0x18}, /* e 0x65 101 */\ + { 0x08, 0x7e, 0x09, 0x01, 0x02}, /* f 0x66 102 */\ + { 0x0c, 0x52, 0x52, 0x52, 0x3e}, /* g 0x67 103 */\ + { 0x7f, 0x08, 0x04, 0x04, 0x78}, /* h 0x68 104 */\ + { 0x00, 0x44, 0x7d, 0x40, 0x00}, /* i 0x69 105 */\ + { 0x20, 0x40, 0x44, 0x3d, 0x00}, /* j 0x6a 106 */\ + { 0x00, 0x7f, 0x10, 0x28, 0x44}, /* k 0x6b 107 */\ + { 0x00, 0x41, 0x7f, 0x40, 0x00}, /* l 0x6c 108 */\ + { 0x7c, 0x04, 0x18, 0x04, 0x78}, /* m 0x6d 109 */\ + { 0x7c, 0x08, 0x04, 0x04, 0x78}, /* n 0x6e 110 */\ + { 0x38, 0x44, 0x44, 0x44, 0x38}, /* o 0x6f 111 */\ + { 0x7c, 0x14, 0x14, 0x14, 0x08}, /* p 0x70 112 */\ + { 0x08, 0x14, 0x14, 0x18, 0x7c}, /* q 0x71 113 */\ + { 0x7c, 0x08, 0x04, 0x04, 0x08}, /* r 0x72 114 */\ + { 0x48, 0x54, 0x54, 0x54, 0x20}, /* s 0x73 115 */\ + { 0x04, 0x3f, 0x44, 0x40, 0x20}, /* t 0x74 116 */\ + { 0x3c, 0x40, 0x40, 0x20, 0x7c}, /* u 0x75 117 */\ + { 0x1c, 0x20, 0x40, 0x20, 0x1c}, /* v 0x76 118 */\ + { 0x3c, 0x40, 0x30, 0x40, 0x3c}, /* w 0x77 119 */\ + { 0x44, 0x28, 0x10, 0x28, 0x44}, /* x 0x78 120 */\ + { 0x0c, 0x50, 0x50, 0x50, 0x3c}, /* y 0x79 121 */\ + { 0x44, 0x64, 0x54, 0x4c, 0x44}, /* z 0x7a 122 */\ + { 0x00, 0x08, 0x36, 0x41, 0x41}, /* { 0x7b 123 */\ + { 0x00, 0x00, 0x7f, 0x00, 0x00}, /* | 0x7c 124 */\ + { 0x41, 0x41, 0x36, 0x08, 0x00}, /* } 0x7d 125 */\ + { 0x04, 0x02, 0x04, 0x08, 0x04}, /* ~ 0x7e 126 */\ + \ + { 0x7e, 0x09, 0x09, 0x09, 0x7e}, /* А 192 */\ + { 0x7F, 0x49, 0x49, 0x49, 0x71}, /* Б */\ + { 0x7f, 0x49, 0x49, 0x49, 0x36}, /* В */\ + { 0x7F, 0x01, 0x01, 0x01, 0x01}, /* Г */\ + { 0x60, 0x3E, 0x21, 0x3F, 0x60}, /* Д */\ + { 0x7f, 0x49, 0x49, 0x49, 0x41}, /* Е */\ + { 0x76, 0x08, 0x7F, 0x08, 0x76}, /* Ж */\ + { 0x21, 0x41, 0x45, 0x4b, 0x31}, /* З */\ + { 0x7F, 0x20, 0x10, 0x08, 0x7F}, /* И */\ + { 0x7E, 0x20, 0x11, 0x08, 0x7E}, /* Й */\ + { 0x7f, 0x08, 0x14, 0x22, 0x41}, /* К */\ + { 0x70, 0x0E, 0x01, 0x01, 0x7F}, /* Л */\ + { 0x7f, 0x02, 0x0c, 0x02, 0x7f}, /* М */\ + { 0x7f, 0x08, 0x08, 0x08, 0x7f}, /* Н */\ + { 0x3e, 0x41, 0x41, 0x41, 0x3e}, /* О */\ + { 0x7F, 0x01, 0x01, 0x01, 0x7F}, /* П */\ + { 0x7f, 0x09, 0x09, 0x09, 0x06}, /* Р */\ + { 0x3e, 0x41, 0x41, 0x41, 0x22}, /* С */\ + { 0x01, 0x01, 0x7f, 0x01, 0x01}, /* Т */\ + { 0x07, 0x48, 0x48, 0x48, 0x7F}, /* У */\ + { 0x1C, 0x22, 0x7F, 0x22, 0x1C}, /* Ф */\ + { 0x63, 0x14, 0x08, 0x14, 0x63}, /* Х */\ + { 0x7F, 0x40, 0x40, 0x7F, 0xC0}, /* Ц */\ + { 0x07, 0x08, 0x08, 0x08, 0x7F}, /* Ч */\ + { 0x7F, 0x40, 0x7F, 0x40, 0x7F}, /* Ш */\ + { 0x7F, 0x40, 0x7F, 0x40, 0xFF}, /* Щ */\ + { 0x01, 0x7F, 0x48, 0x48, 0x70}, /* Ъ */\ + { 0x7F, 0x48, 0x70, 0x00, 0x7F}, /* Ы */\ + { 0x00, 0x7F, 0x48, 0x48, 0x70}, /* Ь */\ + { 0x22, 0x41, 0x49, 0x49, 0x3E}, /* Э */\ + { 0x7F, 0x08, 0x3E, 0x41, 0x3E}, /* Ю */\ + { 0x46, 0x29, 0x19, 0x09, 0x7F}, /* Я 223 */\ + \ + { 0x20, 0x54, 0x54, 0x54, 0x78}, /*a 224 */\ + { 0x3c, 0x4a, 0x4a, 0x49, 0x31}, /*б */\ + { 0x7c, 0x54, 0x54, 0x28, 0x00}, /*в */\ + { 0x7c, 0x04, 0x04, 0x04, 0x0c}, /*г */\ + { 0xe0, 0x54, 0x4c, 0x44, 0xfc}, /*д */\ + { 0x38, 0x54, 0x54, 0x54, 0x18}, /*e */\ + { 0x6c, 0x10, 0x7c, 0x10, 0x6c}, /*ж */\ + { 0x44, 0x44, 0x54, 0x54, 0x28}, /*з */\ + { 0x7c, 0x20, 0x10, 0x08, 0x7c}, /*и */\ + { 0x7c, 0x41, 0x22, 0x11, 0x7c}, /*й */\ + { 0x7c, 0x10, 0x28, 0x44, 0x00}, /*к */\ + { 0x20, 0x44, 0x3c, 0x04, 0x7c}, /*л */\ + { 0x7c, 0x08, 0x10, 0x08, 0x7c}, /*м */\ + { 0x7c, 0x10, 0x10, 0x10, 0x7c}, /*н */\ + { 0x38, 0x44, 0x44, 0x44, 0x38}, /*o */\ + { 0x7c, 0x04, 0x04, 0x04, 0x7c}, /*п */\ + { 0x7C, 0x14, 0x14, 0x14, 0x08}, /*p */\ + { 0x38, 0x44, 0x44, 0x44, 0x20}, /*c */\ + { 0x04, 0x04, 0x7c, 0x04, 0x04}, /*т */\ + { 0x0C, 0x50, 0x50, 0x50, 0x3C}, /*у */\ + { 0x30, 0x48, 0xfc, 0x48, 0x30}, /*ф */\ + { 0x44, 0x28, 0x10, 0x28, 0x44}, /*x */\ + { 0x7c, 0x40, 0x40, 0x40, 0xfc}, /*ц */\ + { 0x0c, 0x10, 0x10, 0x10, 0x7c}, /*ч */\ + { 0x7c, 0x40, 0x7c, 0x40, 0x7c}, /*ш */\ + { 0x7c, 0x40, 0x7c, 0x40, 0xfc}, /*щ */\ + { 0x04, 0x7c, 0x50, 0x50, 0x20}, /*ъ */\ + { 0x7c, 0x50, 0x50, 0x20, 0x7c}, /*ы */\ + { 0x7c, 0x50, 0x50, 0x20, 0x00}, /*ь */\ + { 0x28, 0x44, 0x54, 0x54, 0x38}, /*э */\ + { 0x7c, 0x10, 0x38, 0x44, 0x38}, /*ю */\ + { 0x08, 0x54, 0x34, 0x14, 0x7c} /*я 255 */\ + }; \ No newline at end of file diff --git a/src/effects/lib_led.h b/src/effects/lib_led.h index d4ede26..c5d14e2 100644 --- a/src/effects/lib_led.h +++ b/src/effects/lib_led.h @@ -105,9 +105,12 @@ static void led_setup() // получить номер пикселя в ленте по координатам static uint16_t getPixNum(const uint8_t x, const uint8_t y) { - // x - номер строки - // y - номер столбца + // x - номер строки (в циклах ассоциируется с HEIGHT) + // y - номер столбца (в циклах ассоциируется с WIDTH) // матрица нумеруется сверху вниз по x, слева направо по y (как все массивы в си) + // 00 01 02 + // 10 11 12 + // 20 21 22 if (x < 0 || x >= HEIGHT || y < 0 || y >= WIDTH) { out("Value out of range in function getPixNum %d %d\n", x, y); return 0; diff --git a/src/main.cpp b/src/main.cpp index 14ec61f..7aad084 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,7 +14,7 @@ void setup() { setup_buttons(); FastLED.setBrightness(150); - EffectsList::getInstance().setEffect(6); + EffectsList::getInstance().setEffect(17); } //unsigned long tick = 0; From 5fce95239e73581622062b8af588b3f390890780 Mon Sep 17 00:00:00 2001 From: npo6ka Date: Thu, 26 Dec 2019 00:33:47 +0300 Subject: [PATCH 36/75] small fixes --- src/effects/17_text.h | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/effects/17_text.h b/src/effects/17_text.h index c225361..0e50f9e 100644 --- a/src/effects/17_text.h +++ b/src/effects/17_text.h @@ -6,7 +6,7 @@ class TextMode : public Effect { uint8_t *printed_text; - uint32_t speed = 1; + uint32_t speed = 5; uint32_t tick; const uint8_t font[SYM_AMNT][SYM_SIZE] = DEFAULT_FONTS; @@ -128,7 +128,6 @@ class TextMode : public Effect sym_pos = WIDTH + pos * (LET_WIDTH + SPACE) - tick / speed; if (sym_pos > -LET_WIDTH && sym_pos < WIDTH) { - out("%d %d\n", pos, sym_pos); draw_symbol(printed_text[pos], (HEIGHT - LET_HEIGHT) / 2, sym_pos); } @@ -159,8 +158,8 @@ class TextMode : public Effect void on_init() { - set_text("ебучий текст bleat'"); - set_fps(20); + set_text("Снега нет, но вы дежитесь..."); + set_fps(60); } void on_update() From b52fac899d0b7c090094a2eac97e5f64ce0f00ce Mon Sep 17 00:00:00 2001 From: npo6ka Date: Mon, 30 Dec 2019 15:08:14 +0300 Subject: [PATCH 37/75] add new mods and fix double free --- src/constants.h | 2 +- src/effects/17_text.h | 2 +- src/effects/18_mouse.cpp | 59 +++++++++++++++++++++++++++++ src/effects/18_mouse.h | 15 ++++++++ src/effects/19_pacman.h | 75 +++++++++++++++++++++++++++++++++++++ src/effects/effectslist.cpp | 10 ++++- src/main.cpp | 2 +- 7 files changed, 161 insertions(+), 4 deletions(-) create mode 100644 src/effects/18_mouse.cpp create mode 100644 src/effects/18_mouse.h create mode 100644 src/effects/19_pacman.h diff --git a/src/constants.h b/src/constants.h index 7df34c8..d1ff618 100644 --- a/src/constants.h +++ b/src/constants.h @@ -13,7 +13,7 @@ #define COLOR_ORDER (GRB) // порядок цветов на ленте. Если цвет отображается некорректно - меняйте. Начать можно с RGB #define MATRIX_TYPE (0U) // тип матрицы: 0 - зигзаг, 1 - параллельная -#define CONNECTION_ANGLE (3U) // угол подключения матрицы (0-3): 0 - левый нижний, 1 - левый верхний, 2 - правый верхний, 3 - правый нижний +#define CONNECTION_ANGLE (1U) // угол подключения матрицы (0-3): 0 - левый нижний, 1 - левый верхний, 2 - правый верхний, 3 - правый нижний // 1---------2 // ----------- // ----------- diff --git a/src/effects/17_text.h b/src/effects/17_text.h index 0e50f9e..6066519 100644 --- a/src/effects/17_text.h +++ b/src/effects/17_text.h @@ -158,7 +158,7 @@ class TextMode : public Effect void on_init() { - set_text("Снега нет, но вы дежитесь..."); + set_text("Снега нет, но вы держитесь..."); set_fps(60); } diff --git a/src/effects/18_mouse.cpp b/src/effects/18_mouse.cpp new file mode 100644 index 0000000..999de3e --- /dev/null +++ b/src/effects/18_mouse.cpp @@ -0,0 +1,59 @@ +#include "18_mouse.h" + +Mouse::Mouse() +{ +} + +void Mouse::on_init() +{ + x = 0; +} + +#define W 12 +#define H 10 + +static uint32_t mouse1[W*H] PROGMEM = { + 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x00ffff, 0x00ffff, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0xffffff, 0xffffff, 0x00ffff, 0x000000, 0x000000, + 0xffffff, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0xffffff, 0xffffff, 0x000000, 0x000000, 0x000000, + 0x000000, 0xffffff, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0xffffff, 0xffffff, 0x000000, + 0xffffff, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0xffffff, 0xffffff, 0x400000, + 0x000000, 0xffffff, 0x000000, 0x000000, 0x000000, 0xffffff, 0xffffff, 0x000000, 0xffffff, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0xffffff, 0x000000, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, 0xffffff, 0x000000, 0x000000, 0xffffff, 0x000000, 0x000000, 0x000000, 0x000000, +}; + +static uint32_t mouse2[W*H] PROGMEM = { + 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x00ffff, 0x00ffff, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0xffffff, 0xffffff, 0x00ffff, 0x000000, 0x000000, + 0x000000, 0xffffff, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0xffffff, 0xffffff, 0x000000, 0x000000, 0x000000, + 0xffffff, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0xffffff, 0xffffff, 0x000000, + 0x000000, 0xffffff, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0xffffff, 0xffffff, 0x400000, + 0x000000, 0xffffff, 0x000000, 0x000000, 0x000000, 0xffffff, 0xffffff, 0x000000, 0xffffff, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0xffffff, 0x000000, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0xffffff, 0x000000, 0x000000, 0xffffff, 0x000000, 0x000000, 0x000000, +}; + +static void drawSprite(int x, int y, uint32_t *spr) +{ + for (int i = 0 ; i < W ; ++i) + { + for (int j = 0 ; j < H ; ++j) + { + int v = pgm_read_dword(spr + i + j * W); + setPixColor(x + i, y + j, CRGB(v)); + } + } +} + +void Mouse::on_update() +{ + fader(255); + x = (x + 1) % WIDTH; + drawSprite(x, 0, x % 2 ? mouse1 : mouse2); + drawSprite(x - WIDTH, 0, x % 2 ? mouse1 : mouse2); +} diff --git a/src/effects/18_mouse.h b/src/effects/18_mouse.h new file mode 100644 index 0000000..6f0f921 --- /dev/null +++ b/src/effects/18_mouse.h @@ -0,0 +1,15 @@ +#pragma once + +#include "Effects/effect.h" + + + +class Mouse : public Effect +{ +public: + Mouse(); + void on_init(); + void on_update(); +private: + int x; +}; diff --git a/src/effects/19_pacman.h b/src/effects/19_pacman.h new file mode 100644 index 0000000..c7997eb --- /dev/null +++ b/src/effects/19_pacman.h @@ -0,0 +1,75 @@ +#pragma once + +#include "Effects/effect.h" + +#define PACMAN_W 10 +#define PACMAN_H 10 + +static const uint32_t pacman1[PACMAN_W*PACMAN_H] PROGMEM = { + 0x000000, 0x000000, 0x000000, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0x000000, 0x000000, 0x000000, + 0x000000, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0x000000, + 0x000000, 0xffff00, 0x000000, 0x000000, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0x000000, + 0xffff00, 0xffff00, 0x000000, 0x000000, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0xffff00, + 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0xffff00, + 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0xffff00, + 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0xffff00, + 0x000000, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0x000000, + 0x000000, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0x000000, + 0x000000, 0x000000, 0x000000, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0x000000, 0x000000, 0x000000, +}; + +static const uint32_t pacman2[PACMAN_W*PACMAN_H] PROGMEM = { + 0x000000, 0x000000, 0x000000, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0x000000, 0x000000, 0x000000, + 0x000000, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0x000000, + 0x000000, 0xffff00, 0x000000, 0x000000, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0x000000, + 0xffff00, 0xffff00, 0x000000, 0x000000, 0xffff00, 0xffff00, 0xffff00, 0x000000, 0x000000, 0x000000, + 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, + 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, + 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0x000000, 0x000000, 0x000000, + 0x000000, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0x000000, + 0x000000, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0x000000, + 0x000000, 0x000000, 0x000000, 0xffff00, 0xffff00, 0xffff00, 0xffff00, 0x000000, 0x000000, 0x000000, +}; + +class Pacman : public Effect +{ +public: + Pacman() {} + + void on_init() + { + phase = 0; + set_fps(5); + } + + void on_update() + { + fader(255); + CRGB c(255, 255, 255); + for (int i = PACMAN_W - phase ; i < WIDTH ; i += 4) + { + setPixColor(i, PACMAN_H / 2 - 1, c); + setPixColor(i + 1, PACMAN_H / 2 - 1, c); + setPixColor(i, PACMAN_H / 2, c); + setPixColor(i + 1, PACMAN_H / 2, c); + } + drawSprite(0, 0, PACMAN_W, PACMAN_H, phase / 2 ? pacman1 : pacman2); + phase = (phase + 1) % 4; + } +private: + static void drawSprite(int x, int y, int w, int h, const uint32_t *spr) + { + for (int i = 0 ; i < w ; ++i) + { + for (int j = 0 ; j < h ; ++j) + { + int v = pgm_read_dword(spr + i + j * w); + if (v) + setPixColor(x + i, y + j, CRGB(v)); + } + } + } + +private: + int phase; +}; diff --git a/src/effects/effectslist.cpp b/src/effects/effectslist.cpp index c5bd5ef..4c739d0 100644 --- a/src/effects/effectslist.cpp +++ b/src/effects/effectslist.cpp @@ -18,6 +18,8 @@ #include "15_rainbow_point.h" #include "16_rainbow_static_point.h" #include "17_text.h" +#include "18_mouse.h" +#include "19_pacman.h" /* #include "testmode.h"*/ @@ -35,6 +37,7 @@ EffectsList::EffectsList() { void EffectsList::init() { amnt = MAX_EFFECTS; Effect *eff = NULL; + curEffect = nullptr; while(eff == NULL && amnt >= 0) { amnt--; @@ -88,6 +91,10 @@ Effect *EffectsList::getNewEffectInstance(int num) { return new RainbowStaticPoint(); case 17: return new TextMode(); + case 18: + return new Mouse(); + case 19: + return new Pacman(); //синусоида с рандомными параметрами default: return NULL; @@ -109,7 +116,8 @@ int EffectsList::getCurEffectNum() { void EffectsList::clearCurEffect() { if (getCurEffect()) { - free(curEffect); + delete curEffect; + curEffect = nullptr; } } diff --git a/src/main.cpp b/src/main.cpp index 7aad084..2a077af 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,7 +14,7 @@ void setup() { setup_buttons(); FastLED.setBrightness(150); - EffectsList::getInstance().setEffect(17); + //EffectsList::getInstance().setEffect(19); } //unsigned long tick = 0; From d8ebbf31ab949de7cdb74930e6587b0b5beee9d4 Mon Sep 17 00:00:00 2001 From: npo6ka Date: Mon, 30 Dec 2019 16:32:43 +0300 Subject: [PATCH 38/75] add more effects --- src/constants.h | 2 +- src/effects/20_circular_point.h | 85 ++++++ src/effects/21_zigzag.h | 61 ++++ src/effects/22_horizontal_rainbow_point.h | 97 +++++++ src/effects/23_shadereffect.h | 38 +++ src/effects/23_test_shader.h | 27 ++ src/effects/23_vecmath.h | 157 ++++++++++ src/effects/24_ny2020.h | 337 ++++++++++++++++++++++ src/effects/effect.h | 5 +- src/effects/effectslist.cpp | 16 +- src/main.cpp | 8 +- 11 files changed, 825 insertions(+), 8 deletions(-) create mode 100644 src/effects/20_circular_point.h create mode 100644 src/effects/21_zigzag.h create mode 100644 src/effects/22_horizontal_rainbow_point.h create mode 100644 src/effects/23_shadereffect.h create mode 100644 src/effects/23_test_shader.h create mode 100644 src/effects/23_vecmath.h create mode 100644 src/effects/24_ny2020.h diff --git a/src/constants.h b/src/constants.h index d1ff618..4acab4e 100644 --- a/src/constants.h +++ b/src/constants.h @@ -1,6 +1,6 @@ #pragma once -#define DEBUG (true) +#define DEBUG (false) // ===================== Настройки Матрицы ===================== diff --git a/src/effects/20_circular_point.h b/src/effects/20_circular_point.h new file mode 100644 index 0000000..94fd9fc --- /dev/null +++ b/src/effects/20_circular_point.h @@ -0,0 +1,85 @@ +#pragma once + +#include "Effects/effect.h" + +#define ACCURACY 10 + +class CircularPoint : public Effect +{ + typedef struct Point + { + int32_t x; + int32_t y; + uint8_t hue; + uint32_t pr; //point_radius + uint32_t br; //bright_radius + } Point; + + uint8_t tick; + Point p1; + uint32_t v; + uint8_t rainbow_tick_size; + +private: + static float get_func_brithtness(uint32_t distance, Point pnt) + { + if (distance <= pnt.pr) { + return 1; + } else if (distance >= pnt.pr + pnt.br) { + return 0; + } else { + float val = (float)(distance - pnt.pr) / pnt.br; + return ( 7.389056f /* e^2 */ ) / (50 * val + 7) - 0.13f; + } + } + + static void render_point(Point pnt) + { + int i, j; + for (i = 0; i < HEIGHT; ++i) { + for(j = 0; j < WIDTH; ++j) { + int loc_x = i * ACCURACY + ACCURACY / 2; + int loc_y = j * ACCURACY + ACCURACY / 2; + + uint32_t distance = sqrt((loc_x - pnt.x) * (loc_x - pnt.x) + (loc_y - pnt.y) * (loc_y - pnt.y)); + + float bright = get_func_brithtness(distance, pnt) * 0.7f; + CRGB &clr = getPix(i, j); + CRGB pnt_clr = CHSV(pnt.hue, 255, 255); + clr.r = qadd8(clr.r, (float)pnt_clr.r * bright); + clr.g = qadd8(clr.g, (float)pnt_clr.g * bright); + clr.b = qadd8(clr.b, (float)pnt_clr.b * bright); + } + } + } + +public: + CircularPoint() {} + + void on_init() { + tick = 0; + v = 1; + + p1.hue = 0; + p1.pr = (min(HEIGHT, WIDTH) / 4 + 1) * ACCURACY; + p1.br = 0; + rainbow_tick_size = 1; + } + + //tick 0 .. 255 -> 0 .. 2 * pi + float get_pi_tick(uint8_t tick) { + return 3.141592f * 2 * tick / 255; + } + + void on_update(void) { + float angle = get_pi_tick(tick); + float move_radius = 0.7f;// 0.7 .. 0.9 + p1.x = ACCURACY * HEIGHT * (move_radius * cos(angle) + 1) / 2; + p1.y = ACCURACY * WIDTH * (move_radius * sin(angle) + 1) / 2; + p1.hue += rainbow_tick_size; + + fader(5); + render_point(p1); + tick = tick + 3; + } +}; diff --git a/src/effects/21_zigzag.h b/src/effects/21_zigzag.h new file mode 100644 index 0000000..1ea837e --- /dev/null +++ b/src/effects/21_zigzag.h @@ -0,0 +1,61 @@ +#pragma once + +#include "Effects/effect.h" + +#define ACCURACY 100 +#define MIN_VEC_SIZE 5 +#define MAX_VEC_SIZE 10 +#define RAINBOW_TICK_SIZE 4 //кол-во тиков до инкремента тика радуги + +class ZigZag : public Effect +{ + uint8_t x; + uint8_t y; + bool dir; + uint8_t hue; + uint8_t step; + uint8_t tick; + +public: + ZigZag() {} + + void on_init() { + x = 0; + y = 0; + step = 2; + dir = true; + hue = random8(); + tick = 0; + } + + void on_update(void) { + fader(1); + setPixColor(x, y, CHSV(hue, 255, 255)); + + tick++; + if (tick >= step) { + tick = 0; + hue++; + } + + if (dir) { + if (x + 1 >= HEIGHT) { + y++; + dir = !dir; + } else { + x++; + } + } else { + if (x - 1 < 0) { + y++; + dir = !dir; + } else { + x--; + } + } + + if (y >= WIDTH) { + y = 0; + } + } +}; diff --git a/src/effects/22_horizontal_rainbow_point.h b/src/effects/22_horizontal_rainbow_point.h new file mode 100644 index 0000000..4a298f5 --- /dev/null +++ b/src/effects/22_horizontal_rainbow_point.h @@ -0,0 +1,97 @@ +#pragma once + +#include "Effects/effect.h" + +#define ACCURACY 100 + +#define RAINBOW_TICK_SIZE 4 //кол-во тиков до инкремента тика радуги + +class HorizontalRainbowPoint : public Effect +{ + int32_t x; + int32_t y; + int32_t vec_x; + int32_t vec_y; + int32_t tick; + int32_t min_vec_size = 5; + int32_t max_vec_size = 10; + uint8_t tick_size = 4; //кол-во тиков до инкремента тика радуги + +private: + //arg2: horizontal barrier = true or vertical = false + void rainbow_point_gen_vector(bool horVer) { + int16_t dir = horVer ? 1 : -1; + + vec_x = (vec_x > 0 ? -dir : dir) * random(min_vec_size, max_vec_size); + vec_y = (vec_y > 0 ? dir : -dir) * random(min_vec_size, max_vec_size); + + if (vec_y == 0 && vec_x == 0) { + rainbow_point_gen_vector(horVer); + } + } + + void rainbow_point_move_point() { + x += vec_x; + y += vec_y; + + if (x < 0) { + x = 0; + rainbow_point_gen_vector(true); + } else if (x >= ACCURACY * HEIGHT) { + x = ACCURACY * HEIGHT - 1; + rainbow_point_gen_vector(true); + } + + if (y < 0) { + y += ACCURACY * WIDTH - 1; + } else if (y >= ACCURACY * WIDTH) { + y = 0; + } + } + + int32_t point_distance(int32_t x1, int32_t y1, int32_t x2, int32_t y2) { + return sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); + } + + void rainbow_point_render_point() { + int i, j; + + for (i = 0; i < HEIGHT; ++i) { + for(j = 0; j < WIDTH; ++j) { + int32_t loc_x = i * ACCURACY + ACCURACY / 2; + int32_t loc_y = j * ACCURACY + ACCURACY / 2; + + int32_t dis1 = point_distance(x, y, loc_x, loc_y); + int32_t dis2 = point_distance(x, y, loc_x, loc_y - WIDTH * ACCURACY); + int32_t dis3 = point_distance(x, y, loc_x, loc_y + WIDTH * ACCURACY); + + dis1 = dis1 < dis2 ? dis1 : dis2; + dis1 = dis1 < dis3 ? dis1 : dis3; + + float chsv = (dis1 / 8 + tick / RAINBOW_TICK_SIZE) % 256; + + setPixColor(i, j, CHSV(chsv, 255, 255)); + } + } + } +public: + HorizontalRainbowPoint() {} + + void on_init() { + tick = 0; + x = random16(0, (HEIGHT - 1) * ACCURACY); + y = random16(0, (WIDTH - 1) * ACCURACY); + + vec_x = (int32_t)random(0, max_vec_size * 2) - max_vec_size; + vec_y = (int32_t)random(0, max_vec_size * 2) - max_vec_size; + } + + void on_update(void) { + FastLED.clear(); + + tick = (tick + 1) % (256 * tick_size); + + rainbow_point_move_point(); + rainbow_point_render_point(); + } +}; diff --git a/src/effects/23_shadereffect.h b/src/effects/23_shadereffect.h new file mode 100644 index 0000000..64e4242 --- /dev/null +++ b/src/effects/23_shadereffect.h @@ -0,0 +1,38 @@ +#ifndef SHADEREFFECT_H +#define SHADEREFFECT_H + +#include "Effects/effect.h" +#include "Effects/23_vecmath.h" + + +#define IN +#define OUT + +class ShaderEffect : public Effect { +public: + IN vec2 fragCoord; + IN const vec2 resolution = vec2(WIDTH, HEIGHT); + IN float time = 0.0; + + OUT vec3 fragColor; + + void on_update() override + {} + + virtual void on_render() + { + time += 1.0 / 60.0; + for (uint8_t x = 0; x < HEIGHT; x++) { + for (uint8_t y = 0; y < WIDTH; y++) { + fragCoord.x = x; + fragCoord.y = WIDTH - y; + on_fragment(); + getPix(y, x) = clamp(fragColor, 0.f, 1.f); + } + } + } + + virtual void on_fragment() = 0; +}; + +#endif // SHADEREFFECT_H diff --git a/src/effects/23_test_shader.h b/src/effects/23_test_shader.h new file mode 100644 index 0000000..2058e7c --- /dev/null +++ b/src/effects/23_test_shader.h @@ -0,0 +1,27 @@ +#pragma once + +#include "Effects/23_shadereffect.h" + +class TestShader : public ShaderEffect +{ +public: + TestShader() = default; + + void on_fragment() override { + vec2 position = (fragCoord.xy() / resolution.xy()) - 0.5; + position *= 1.25; + position.y *= dot(position, position); + + float den = 0.05; + float amp = 0.2; + float freq = 16.; + float offset = 0.1; + + vec3 colour = vec3 (0.23, 0.1, 0.3) * + ((1.0f / fabs((position.y + (amp * fsin((position.x + time * offset) *freq)))) * den) + + (1.0f / fabs((position.y + (amp * fsin((position.x + time * offset) *freq+.7f)))) * den) + + (1.0f / fabs((position.y + (amp * fsin((position.x + time * offset) *freq-.7f)))) * den)); + + fragColor = colour; + } +}; diff --git a/src/effects/23_vecmath.h b/src/effects/23_vecmath.h new file mode 100644 index 0000000..b944947 --- /dev/null +++ b/src/effects/23_vecmath.h @@ -0,0 +1,157 @@ +#pragma once + +#define DEF_VEC2_ARITH_OP(op1, op2) \ + inline vec2& operator op1 (const vec2& v) \ + { x op1 v.x; y op1 v.y; return *this; } \ + \ + inline vec2 operator op2 (const vec2& v) \ + { vec2 ret(*this); ret op1 v; return ret; } \ + \ + inline vec2 operator op2 (float v) \ + { vec2 ret(*this); ret op1 v; return ret; } + +#define DEF_VEC3_ARITH_OP(op1, op2) \ + inline vec3& operator op1 (const vec3& v) \ + { x op1 v.x; y op1 v.y; z op1 v.z; return *this; } \ + \ + inline vec3 operator op2 (const vec3& v) \ + { vec3 ret(*this); ret op1 v; return ret; } \ + \ + inline vec3 operator op2 (float v) \ + { vec3 ret(*this); ret op1 v; return ret; } + + +#define DEF_VEC2_OF(v1, v2) \ + vec2 v1##v2() const \ + { return { v1, v2 }; } \ + +struct vec2 { + float x; + float y; + + inline vec2() : x(0.f), y(0.f) + {} + + inline vec2(float x, float y) : x(x), y(y) + {} + + inline vec2(float xy) : vec2(xy, xy) + {} + + DEF_VEC2_OF(x, x) + DEF_VEC2_OF(y, y) + DEF_VEC2_OF(x, y) + DEF_VEC2_OF(y, x) + + DEF_VEC2_ARITH_OP(+=, +) + DEF_VEC2_ARITH_OP(-=, -) + DEF_VEC2_ARITH_OP(*=, *) + DEF_VEC2_ARITH_OP(/=, /) +}; + +struct vec3 : public vec2 { + float z; + + inline vec3() : vec2(0.f), z(0.f) + {} + + inline vec3(float x, float y, float z) : vec2(x, y), z(z) + {} + + inline vec3(vec2 xy, float z) : vec2(xy), z(z) + {} + + vec3(float xyz) : vec2(xyz), z(xyz) + {} + + DEF_VEC2_OF(y, y) + DEF_VEC2_OF(x, z) + DEF_VEC2_OF(y, z) + DEF_VEC2_OF(z, x) + DEF_VEC2_OF(z, y) + + DEF_VEC3_ARITH_OP(+=, +) + DEF_VEC3_ARITH_OP(-=, -) + DEF_VEC3_ARITH_OP(*=, *) + DEF_VEC3_ARITH_OP(/=, /) + + operator CRGB() const + { return CRGB(x * 255, y * 255, z * 255); } +}; + +/** min */ + +inline float min(float a, float b) +{ return a > b ? b : a; } + +inline vec2 min(vec2 a, vec2 b) +{ return { min(a.x, b.x), min(b.x, b.y) }; } + +inline vec3 min(vec3 a, vec3 b) +{ return { min(a.x, b.x), min(b.x, b.y), min(b.z, b.z) }; } + +/** max */ + +inline float max(float a, float b) +{ return a > b ? a : b; } + +inline vec2 max(vec2 a, vec2 b) +{ return { max(a.x, b.x), max(b.x, b.y) }; } + +inline vec3 max(vec3 a, vec3 b) +{ return { max(a.x, b.x), max(b.x, b.y), max(b.z, b.z) }; } + +/** clamp */ + +inline float clamp(float v, float vMin, float vMax) +{ return max(min(v, vMax), vMin); } + +inline vec2 clamp(vec2 v, vec2 vMin, vec2 vMax) +{ return { clamp(v.x, vMin.x, vMax.x), clamp(v.y, vMin.y, vMax.y) }; } + +inline vec3 clamp(vec3 v, vec3 vMin, vec3 vMax) +{ return { clamp(v.x, vMin.x, vMax.x), clamp(v.y, vMin.y, vMax.y), clamp(v.z, vMin.z, vMax.z) }; } + +/** abs */ + +inline float fabs(float v) +{ return v >= 0.f ? v : -v; } + +inline vec2 fabs(vec2 v) +{ return { fabs(v.x), fabs(v.y) }; } + +inline vec3 fabs(vec3 v) +{ return { fabs(v.x), fabs(v.y), fabs(v.z) }; } + +/** dot */ + +inline float dot(float a, float b) +{ return a * b; } + +inline float dot(vec2 a, vec2 b) +{ return a.x * b.x + a.y * b.y; } + +inline vec3 dot(vec3 a, vec3 b) +{ return a.x * b.x + a.y * b.y + a.z * b.z; } + +/** sin */ + +inline float fsin(float rad) +{ return sin16(rad * 65535 / 3.14) / 32767.0; } + +inline vec2 fsin(vec2 rad) +{ return { fsin(rad.x), fsin(rad.y) }; } + +inline vec3 fsin(vec3 rad) +{ return { fsin(rad.x), fsin(rad.y), fsin(rad.z) }; } + +/** cos */ + +inline float fcos(float rad) +{ return cos16(rad * 65535 / 3.14f) / 32767.0f; } + +inline vec2 fcos(vec2 rad) +{ return { fcos(rad.x), fcos(rad.y) }; } + +inline vec3 fcos(vec3 rad) +{ return { fcos(rad.x), fcos(rad.y), fcos(rad.z) }; } diff --git a/src/effects/24_ny2020.h b/src/effects/24_ny2020.h new file mode 100644 index 0000000..48e034d --- /dev/null +++ b/src/effects/24_ny2020.h @@ -0,0 +1,337 @@ +#pragma once + +#include "Effects/effect.h" + +#define NY_SPRITE_W 10 +#define NY_SPRITE_H 10 + +#define R 0xff0000 +#define DR 0x400000 +#define G 0x00ff00 +#define DG 0x004000 +#define B 0x0000ff +#define DB 0x000040 +#define Y 0xffff00 +#define C 0x00ffff +#define W 0xffffff +#define BR 0x402010 +#define LB 0xc08040 +#define O 0xff8000 +#define P 0xffc0c0 + +static const 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, + 0, W, DB, DB, DB, DB, DB, W, 0, 0, + W, DB, DB, DB, DB, DB, DB, DB, W, 0, + 0, BR, BR, BR, BR, BR, BR, BR, 0, 0, + 0, BR, Y, Y, BR, Y, Y, BR, 0, 0, + 0, BR, Y, Y, BR, Y, Y, BR, 0, 0, + 0, BR, BR, BR, BR, Y, Y, BR, 0, 0, + W, W, W, W, W, Y, Y, W, W, W, +}; + +static const 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, + 0, W, DB, DB, DB, DB, DB, W, 0, 0, + 0, 0, BR, BR, BR, BR, BR, 0, 0, 0, + 0, 0, BR, C, C, C, BR, 0, 0, 0, + 0, 0, BR, C, C, C, BR, 0, 0, 0, + 0, 0, BR, BR, BR, BR, BR, 0, 0, 0, + 0, W, BR, BR, BR, BR, BR, W, W, 0, + W, W, W, W, W, W, W, W, W, W, +}; + +static const 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, + 0, 0, 0, 0, G, 0, 0, 0, 0, 0, + 0, 0, 0, G, G, G, 0, 0, 0, 0, + 0, 0, G, G, G, G, G, 0, 0, 0, + 0, 0, 0, 0, G, 0, 0, 0, 0, 0, + 0, 0, G, G, G, G, G, 0, 0, 0, + 0, G, G, G, G, G, G, G, 0, 0, + 0, 0, 0, 0, G, 0, 0, 0, 0, 0, +}; + +static const 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, + 0, 0, 0, 0, G, 0, 0, 0, 0, 0, + 0, 0, 0, G, G, G, 0, 0, 0, 0, + 0, 0, G, G, G, G, G, 0, 0, 0, + 0, 0, 0, 0, G, 0, 0, 0, 0, 0, + 0, 0, G, G, G, G, G, 0, 0, 0, + 0, G, G, G, G, G, G, G, 0, 0, + 0, 0, 0, 0, G, 0, 0, 0, 0, 0, +}; + +static const 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, + 0, 0, W, G, G, G, W, 0, 0, 0, + 0, W, G, G, G, G, G, W, 0, 0, + 0, 0, W, G, G, G, W, 0, 0, 0, + 0, W, G, G, G, G, G, W, 0, 0, + W, G, G, G, G, G, G, G, W, 0, + 0, W, W, G, G, G, W, W, 0, 0, + 0, 0, 0, G, G, G, 0, 0, 0, 0, +}; + +static const 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, + 0, 0, R, O, G, O, Y, 0, 0, 0, + 0, 0, 0, G, Y, G, 0, 0, 0, 0, + 0, 0, G, G, G, G, G, 0, 0, 0, + 0, 0, Y, G, G, G, Y, 0, 0, 0, + 0, R, G, O, G, O, G, R, 0, 0, + O, G, G, G, R, G, G, G, O, 0, + 0, 0, 0, G, G, G, 0, 0, 0, 0, +}; + +static const 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, + 0, C, 0, C, C, C, 0, C, 0, 0, + 0, 0, C, 0, C, 0, C, 0, 0, 0, + C, C, C, C, C, C, C, C, C, 0, + 0, 0, C, 0, C, 0, C, 0, 0, 0, + 0, C, 0, C, C, C, 0, C, 0, 0, + 0, 0, C, 0, C, 0, C, 0, 0, 0, + 0, 0, 0, 0, C, 0, 0, 0, 0, 0, +}; + +static const 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, + R, 0, 0, R, 0, R, 0, 0, R, 0, + 0, R, R, R, R, R, R, R, 0, 0, + DG, DG, DG, DG, R, DG, DG, DG, DG, 0, + DG, DG, DG, DG, R, DG, DG, DG, DG, 0, + R, R, R, R, R, R, R, R, R, 0, + DG, DG, DG, DG, R, DG, DG, DG, DG, 0, + DG, DG, DG, DG, R, DG, DG, DG, DG, 0, +}; + +static const 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, + 0, LB, LB, 0, 0, 0, 0, 0, 0, 0, + BR, LB, LB, 0, 0, 0, 0, 0, 0, 0, + 0, 0, LB, LB, LB, LB, LB, LB, LB, 0, + 0, 0, LB, LB, LB, LB, LB, LB, 0, 0, + 0, LB, LB, LB, LB, LB, LB, LB, 0, 0, + 0, BR, 0, LB, 0, LB, 0, LB, 0, 0, + 0, 0, 0, BR, 0, BR, 0, BR, 0, 0, +}; + +static const 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, + 0, 0, 0, R, R, R, R, W, W, 0, + 0, 0, R, R, R, R, R, 0, 0, 0, + 0, 0, R, R, R, R, R, R, 0, 0, + 0, R, R, R, R, R, R, R, 0, 0, + 0, R, R, R, R, R, R, R, 0, 0, + W, W, W, W, W, W, W, W, W, 0, + W, W, W, W, W, W, W, W, W, 0, +}; + +static const 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, + 0, W, W, 0, W, 0, W, W, 0, 0, + 0, 0, W, W, W, W, W, 0, 0, 0, + 0, 0, R, R, R, R, R, 0, 0, 0, + 0, W, W, W, R, W, W, W, 0, 0, + W, W, W, W, W, R, W, W, W, 0, + 0, W, W, W, 0, R, W, W, 0, 0, + 0, 0, W, W, W, W, W, 0, 0, 0, +}; + +static const 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, + 0, W, R, W, R, W, R, 0, R, 0, + 0, R, R, R, W, R, R, 0, R, 0, + 0, W, R, W, R, W, R, 0, R, 0, + 0, W, W, W, W, W, W, 0, R, 0, + 0, R, R, R, R, R, R, R, R, 0, + 0, R, R, R, R, R, R, 0, 0, 0, + 0, R, R, R, R, R, R, 0, 0, 0, +}; + +static const 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, + 0, 0, 0, 0, G, G, 0, 0, 0, 0, + 0, 0, 0, G, G, G, G, 0, 0, 0, + 0, 0, G, G, G, G, G, G, 0, 0, + 0, 0, W, W, W, W, W, W, 0, 0, + 0, 0, R, R, R, R, R, R, 0, 0, + 0, 0, G, G, G, G, G, G, 0, 0, + 0, 0, G, G, G, G, G, G, 0, 0, +}; + +static const 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, + 0, 0, 0, 0, 0, P, P, 0, W, W, + 0, 0, 0, 0, 0, R, DR, 0, 0, 0, + 0, 0, P, R, R, DR, DR, 0, 0, R, + 0, 0, 0, 0, DR, DR, DR, 0, R, R, + BR, 0, 0, DR, DR, DR, DR, R, R, R, + BR, 0, R, R, R, R, R, R, R, R, + 0, BR, BR, BR, BR, BR, BR, BR, BR, BR, +}; + +static const uint32_t sprite15[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { + 0, 0, 0, R, 0, R, 0, 0, 0, 0, + 0, 0, G, R, R, R, G, 0, 0, 0, + 0, R, G, R, 0, R, G, R, 0, 0, + G, R, R, 0, 0, 0, R, R, G, 0, + G, G, 0, 0, 0, 0, 0, G, G, 0, + R, R, 0, 0, 0, 0, 0, R, R, 0, + R, G, G, 0, 0, 0, G, G, R, 0, + 0, G, R, 0, 0, R, R, G, 0, 0, + 0, 0, R, G, R, G, R, 0, 0, 0, + 0, 0, 0, G, R, G, 0, 0, 0, 0, +}; + +static const 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, + 0, 0, W, W, 0, 0, 0, 0, 0, 0, + 0, 0, W, W, 0, 0, 0, 0, 0, 0, + 0, 0, W, W, 0, 0, 0, 0, 0, LB, + 0, 0, W, W, 0, 0, 0, 0, 0, LB, + 0, LB, W, W, LB, 0, 0, 0, LB, 0, + 0, LB, LB, LB, LB, LB, LB, LB, 0, 0, + 0, 0, LB, LB, LB, LB, 0, 0, 0, 0, +}; + +static const 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, + 0, 0, 0, W, W, W, 0, 0, 0, 0, + 0, 0, W, W, W, W, W, 0, 0, 0, + 0, 0, BR, W, BR, W, BR, 0, 0, 0, + 0, BR, BR, W, BR, W, BR, BR, 0, 0, + 0, BR, BR, BR, BR, W, BR, BR, 0, 0, + BR, BR, BR, BR, BR, BR, BR, BR, BR, 0, + BR, BR, BR, BR, BR, BR, BR, BR, BR, 0, +}; + +static const 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, + 0, 0, 0, 0, 0, 0, R, W, W, 0, + 0, 0, 0, 0, 0, W, R, R, 0, 0, + 0, 0, 0, 0, R, W, W, 0, 0, 0, + 0, 0, 0, W, R, R, 0, 0, 0, 0, + 0, 0, R, W, W, 0, 0, 0, 0, 0, + 0, W, R, R, 0, 0, 0, 0, 0, 0, + 0, W, W, 0, 0, 0, 0, 0, 0, 0, +}; + +static const 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, + 0, 0, 0, R, R, G, R, R, 0, 0, + 0, 0, 0, R, G, G, G, R, 0, 0, + 0, 0, 0, R, R, G, R, R, 0, 0, + 0, 0, R, R, R, R, W, W, 0, 0, + 0, R, R, R, R, R, W, W, 0, 0, + W, W, R, R, R, R, W, 0, 0, 0, + W, W, R, R, R, R, 0, 0, 0, 0, +}; + +#undef R +#undef DR +#undef G +#undef DG +#undef B +#undef DB +#undef Y +#undef C +#undef W +#undef BR +#undef LB +#undef O +#undef P + +#define NY_COUNT (WIDTH / NY_SPRITE_W + 2) + +static const uint32_t * const sprites[] = +{ + sprite1, sprite2, sprite3, sprite4, sprite5, sprite6, sprite7, + sprite8, sprite9, sprite10, sprite11, sprite12, sprite13, + sprite14, sprite15, sprite16, sprite17, sprite18, sprite19, +}; + +#define NY_TYPES (sizeof(sprites) / sizeof(sprites[0])) + +class NY2020 : public Effect +{ +public: + NY2020() {} + + void on_init() + { + phase = 0; + for (int i = 0 ; i < NY_COUNT ; ++i) + items[i] = random8(NY_TYPES); + + set_fps(10); + } + + void on_update() + { + FastLED.clear(); + for (int i = 0 ; i < NY_COUNT ; ++i) + drawSprite(i * (NY_SPRITE_W + 1) - phase, 0, 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); + } + } +private: + static void drawSprite(int x, int y, const uint32_t *spr) + { + for (int i = 0 ; i < NY_SPRITE_W ; ++i) + { + for (int j = 0 ; j < NY_SPRITE_H ; ++j) + { + int v = pgm_read_dword(spr + i + j * NY_SPRITE_W); + setPixColor(y + j, x + i, CRGB(v)); + } + } + } + +private: + int phase; + int items[NY_COUNT]; +}; diff --git a/src/effects/effect.h b/src/effects/effect.h index 29db693..a728395 100644 --- a/src/effects/effect.h +++ b/src/effects/effect.h @@ -7,11 +7,12 @@ class Effect uint8_t fps = 60; public: - Effect() { } + Effect() = default; + virtual ~Effect() = default; + /* Инициализация режима, установка начальный значений. * Выполняется единожды, при выборе нового режима. * */ - virtual void on_init() {} /* on_update обязательно нужно описать в наследукмом классе. diff --git a/src/effects/effectslist.cpp b/src/effects/effectslist.cpp index 4c739d0..f2e166c 100644 --- a/src/effects/effectslist.cpp +++ b/src/effects/effectslist.cpp @@ -20,6 +20,11 @@ #include "17_text.h" #include "18_mouse.h" #include "19_pacman.h" +#include "20_circular_point.h" +#include "21_zigzag.h" +#include "22_horizontal_rainbow_point.h" +#include "23_test_shader.h" +#include "24_ny2020.h" /* #include "testmode.h"*/ @@ -95,7 +100,16 @@ Effect *EffectsList::getNewEffectInstance(int num) { return new Mouse(); case 19: return new Pacman(); - //синусоида с рандомными параметрами + case 20: + return new CircularPoint(); + case 21: + return new ZigZag(); + case 22: + return new HorizontalRainbowPoint(); + case 23: + return new TestShader(); + case 24: + return new NY2020(); default: return NULL; } diff --git a/src/main.cpp b/src/main.cpp index 2a077af..e325c61 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,11 +14,11 @@ void setup() { setup_buttons(); FastLED.setBrightness(150); - //EffectsList::getInstance().setEffect(19); + EffectsList::getInstance().setEffect(20); } -//unsigned long tick = 0; -//int tps = 0; +unsigned long tick = 0; +int tps = 0; void loop() { EffectsList::getInstance().onTick(); @@ -28,7 +28,7 @@ void loop() { // проверка реального тпс работы микроконтроллера /*tps++; if (millis() > tick * 1000 ) { - out("tps: %d fps: %.1f\n", tps, effects->getCurFPS()); + out("tps: %d fps: %.1f\n", tps, EffectsList::getInstance().getCurFPS()); tick++; tps = 0; }*/ From f0102283731649c7484cf75992b9d7fdf78c7722 Mon Sep 17 00:00:00 2001 From: npo6ka Date: Mon, 30 Dec 2019 16:58:24 +0300 Subject: [PATCH 39/75] fix out of bound coordinates --- src/constants.h | 2 +- src/effects/18_mouse.cpp | 5 ++++- src/effects/19_pacman.h | 15 ++++++++++----- src/effects/24_ny2020.h | 4 +++- src/main.cpp | 2 +- 5 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/constants.h b/src/constants.h index 4acab4e..d1ff618 100644 --- a/src/constants.h +++ b/src/constants.h @@ -1,6 +1,6 @@ #pragma once -#define DEBUG (false) +#define DEBUG (true) // ===================== Настройки Матрицы ===================== diff --git a/src/effects/18_mouse.cpp b/src/effects/18_mouse.cpp index 999de3e..56b1586 100644 --- a/src/effects/18_mouse.cpp +++ b/src/effects/18_mouse.cpp @@ -6,6 +6,7 @@ Mouse::Mouse() void Mouse::on_init() { + set_fps(10); x = 0; } @@ -45,7 +46,9 @@ static void drawSprite(int x, int y, uint32_t *spr) for (int j = 0 ; j < H ; ++j) { int v = pgm_read_dword(spr + i + j * W); - setPixColor(x + i, y + j, CRGB(v)); + if (y + j >= 0 && y + j < HEIGHT && x + i >= 0 && x + i < WIDTH) { + setPixColor(y + j, x + i, CRGB(v)); + } } } } diff --git a/src/effects/19_pacman.h b/src/effects/19_pacman.h index c7997eb..a6da2be 100644 --- a/src/effects/19_pacman.h +++ b/src/effects/19_pacman.h @@ -48,15 +48,20 @@ class Pacman : public Effect CRGB c(255, 255, 255); for (int i = PACMAN_W - phase ; i < WIDTH ; i += 4) { - setPixColor(i, PACMAN_H / 2 - 1, c); - setPixColor(i + 1, PACMAN_H / 2 - 1, c); - setPixColor(i, PACMAN_H / 2, c); - setPixColor(i + 1, PACMAN_H / 2, c); + setPixCl(PACMAN_H / 2 - 1, i, c); + setPixCl(PACMAN_H / 2 - 1, i + 1, c); + setPixCl(PACMAN_H / 2, i, c); + setPixCl(PACMAN_H / 2, i + 1, c); } drawSprite(0, 0, PACMAN_W, PACMAN_H, phase / 2 ? pacman1 : pacman2); phase = (phase + 1) % 4; } private: + static void setPixCl(int16_t x, int16_t y, CRGB cl) { + if (x >= 0 && x < HEIGHT && y >= 0 && y < WIDTH) + getPix(x, y) = cl; + } + static void drawSprite(int x, int y, int w, int h, const uint32_t *spr) { for (int i = 0 ; i < w ; ++i) @@ -65,7 +70,7 @@ class Pacman : public Effect { int v = pgm_read_dword(spr + i + j * w); if (v) - setPixColor(x + i, y + j, CRGB(v)); + setPixCl(y + j, x + i, CRGB(v)); } } } diff --git a/src/effects/24_ny2020.h b/src/effects/24_ny2020.h index 48e034d..784abc2 100644 --- a/src/effects/24_ny2020.h +++ b/src/effects/24_ny2020.h @@ -326,7 +326,9 @@ class NY2020 : public Effect for (int j = 0 ; j < NY_SPRITE_H ; ++j) { int v = pgm_read_dword(spr + i + j * NY_SPRITE_W); - setPixColor(y + j, x + i, CRGB(v)); + if (y + j >= 0 && y + j < HEIGHT && x + i >= 0 && x + i < WIDTH) { + setPixColor(y + j, x + i, CRGB(v)); + } } } } diff --git a/src/main.cpp b/src/main.cpp index e325c61..1959a1d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,7 +14,7 @@ void setup() { setup_buttons(); FastLED.setBrightness(150); - EffectsList::getInstance().setEffect(20); + EffectsList::getInstance().setEffect(18); } unsigned long tick = 0; From 6d6f8d48cb34d5cb746eb1b932b620d250414e53 Mon Sep 17 00:00:00 2001 From: npo6ka Date: Mon, 30 Dec 2019 17:13:56 +0300 Subject: [PATCH 40/75] fix shaders --- .vscode/settings.json | 30 +++++- src/effects/23_shadereffect.h | 5 +- src/effects/23_test_shader.h | 8 +- src/effects/{23_vecmath.h => lib_glsl.h} | 131 +++++++++++++++-------- src/main.cpp | 2 +- 5 files changed, 123 insertions(+), 53 deletions(-) rename src/effects/{23_vecmath.h => lib_glsl.h} (60%) diff --git a/.vscode/settings.json b/.vscode/settings.json index 63fe915..47c21fe 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,6 +5,34 @@ "functional": "cpp", "istream": "cpp", "tuple": "cpp", - "utility": "cpp" + "utility": "cpp", + "atomic": "cpp", + "cctype": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "cstdarg": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "deque": "cpp", + "list": "cpp", + "unordered_map": "cpp", + "vector": "cpp", + "exception": "cpp", + "fstream": "cpp", + "initializer_list": "cpp", + "iosfwd": "cpp", + "limits": "cpp", + "new": "cpp", + "ostream": "cpp", + "numeric": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "streambuf": "cpp", + "cinttypes": "cpp", + "type_traits": "cpp", + "typeinfo": "cpp" } } \ No newline at end of file diff --git a/src/effects/23_shadereffect.h b/src/effects/23_shadereffect.h index 64e4242..d5533cd 100644 --- a/src/effects/23_shadereffect.h +++ b/src/effects/23_shadereffect.h @@ -2,12 +2,13 @@ #define SHADEREFFECT_H #include "Effects/effect.h" -#include "Effects/23_vecmath.h" - +#include "Effects/lib_glsl.h" #define IN #define OUT +using namespace glsl; + class ShaderEffect : public Effect { public: IN vec2 fragCoord; diff --git a/src/effects/23_test_shader.h b/src/effects/23_test_shader.h index 2058e7c..401d8b3 100644 --- a/src/effects/23_test_shader.h +++ b/src/effects/23_test_shader.h @@ -17,10 +17,10 @@ class TestShader : public ShaderEffect float freq = 16.; float offset = 0.1; - vec3 colour = vec3 (0.23, 0.1, 0.3) * - ((1.0f / fabs((position.y + (amp * fsin((position.x + time * offset) *freq)))) * den) - + (1.0f / fabs((position.y + (amp * fsin((position.x + time * offset) *freq+.7f)))) * den) - + (1.0f / fabs((position.y + (amp * fsin((position.x + time * offset) *freq-.7f)))) * den)); + vec3 colour = vec3 (0.33, 0.2, 0.4) * + ((1.0f / gl_abs((position.y + (amp * sin((position.x + time * offset) *freq)))) * den) + + (1.0f / gl_abs((position.y + (amp * sin((position.x + time * offset) *freq+.7f)))) * den) + + (1.0f / gl_abs((position.y + (amp * sin((position.x + time * offset) *freq-.7f)))) * den)); fragColor = colour; } diff --git a/src/effects/23_vecmath.h b/src/effects/lib_glsl.h similarity index 60% rename from src/effects/23_vecmath.h rename to src/effects/lib_glsl.h index b944947..a02c697 100644 --- a/src/effects/23_vecmath.h +++ b/src/effects/lib_glsl.h @@ -1,5 +1,9 @@ #pragma once +#include "pixeltypes.h" + +namespace glsl { + #define DEF_VEC2_ARITH_OP(op1, op2) \ inline vec2& operator op1 (const vec2& v) \ { x op1 v.x; y op1 v.y; return *this; } \ @@ -61,7 +65,7 @@ struct vec3 : public vec2 { inline vec3(vec2 xy, float z) : vec2(xy), z(z) {} - vec3(float xyz) : vec2(xyz), z(xyz) + inline vec3(float xyz) : vec2(xyz), z(xyz) {} DEF_VEC2_OF(y, y) @@ -75,83 +79,120 @@ struct vec3 : public vec2 { DEF_VEC3_ARITH_OP(*=, *) DEF_VEC3_ARITH_OP(/=, /) - operator CRGB() const + inline operator CRGB() const { return CRGB(x * 255, y * 255, z * 255); } }; -/** min */ +// +// Trigonometry +// + +// sin + +#define gl_sin glsl::sin + +inline float sin(float rad) +{ return static_cast(sin16(rad * 65535 / 3.14f)) / 32767.0f; } + +inline vec2 sin(const vec2& rad) +{ return { sin(rad.x), sin(rad.y) }; } + +inline vec3 sin(const vec3& rad) +{ return { sin(rad.x), sin(rad.y), sin(rad.z) }; } + +// cos + +#define gl_cos glsl::cos + +inline float cos(float rad) +{ return static_cast(cos16(rad * 65535 / 3.14f)) / 32767.0f; } + +inline vec2 cos(const vec2& rad) +{ return { cos(rad.x), cos(rad.y) }; } + +inline vec3 cos(const vec3& rad) +{ return { cos(rad.x), cos(rad.y), cos(rad.z) }; } + +// +// Mathematics +// + +// abs + +//#define gl_abs glsl::abs + +inline float gl_abs(float v) +{ return v >= 0 ? v : -v; } + +inline vec2 gl_abs(const vec2& v) +{ return { gl_abs(v.x), gl_abs(v.y) }; } + +inline vec3 abs(const vec3& v) +{ return { gl_abs(v.x), gl_abs(v.y), gl_abs(v.z) }; } + +// ceil + +#define gl_ceil glsl::ceil + +inline float ceil(float v) +{ return std::ceil(v); } + +inline vec2 ceil(const vec2& v) +{ return { ceil(v.x), ceil(v.y) }; } + +inline vec3 ceil(const vec3& v) +{ return { ceil(v.x), ceil(v.y), ceil(v.z) }; } + +// min + +#define gl_min glsl::min inline float min(float a, float b) { return a > b ? b : a; } -inline vec2 min(vec2 a, vec2 b) +inline vec2 min(const vec2& a, const vec2& b) { return { min(a.x, b.x), min(b.x, b.y) }; } -inline vec3 min(vec3 a, vec3 b) +inline vec3 min(const vec3& a, const vec3& b) { return { min(a.x, b.x), min(b.x, b.y), min(b.z, b.z) }; } -/** max */ +// max + +#define gl_max glsl::max inline float max(float a, float b) { return a > b ? a : b; } -inline vec2 max(vec2 a, vec2 b) +inline vec2 max(const vec2& a, const vec2& b) { return { max(a.x, b.x), max(b.x, b.y) }; } -inline vec3 max(vec3 a, vec3 b) +inline vec3 max(const vec3& a, const vec3& b) { return { max(a.x, b.x), max(b.x, b.y), max(b.z, b.z) }; } -/** clamp */ +// clamp + +#define gl_clamp glsl::clamp inline float clamp(float v, float vMin, float vMax) { return max(min(v, vMax), vMin); } -inline vec2 clamp(vec2 v, vec2 vMin, vec2 vMax) +inline vec2 clamp(const vec2& v, const vec2& vMin, const vec2& vMax) { return { clamp(v.x, vMin.x, vMax.x), clamp(v.y, vMin.y, vMax.y) }; } -inline vec3 clamp(vec3 v, vec3 vMin, vec3 vMax) +inline vec3 clamp(const vec3& v, const vec3& vMin, const vec3& vMax) { return { clamp(v.x, vMin.x, vMax.x), clamp(v.y, vMin.y, vMax.y), clamp(v.z, vMin.z, vMax.z) }; } -/** abs */ - -inline float fabs(float v) -{ return v >= 0.f ? v : -v; } +// dot -inline vec2 fabs(vec2 v) -{ return { fabs(v.x), fabs(v.y) }; } - -inline vec3 fabs(vec3 v) -{ return { fabs(v.x), fabs(v.y), fabs(v.z) }; } - -/** dot */ +#define gl_dot glsl::dot inline float dot(float a, float b) { return a * b; } -inline float dot(vec2 a, vec2 b) +inline float dot(const vec2& a, const vec2& b) { return a.x * b.x + a.y * b.y; } -inline vec3 dot(vec3 a, vec3 b) +inline vec3 dot(const vec3& a, const vec3& b) { return a.x * b.x + a.y * b.y + a.z * b.z; } -/** sin */ - -inline float fsin(float rad) -{ return sin16(rad * 65535 / 3.14) / 32767.0; } - -inline vec2 fsin(vec2 rad) -{ return { fsin(rad.x), fsin(rad.y) }; } - -inline vec3 fsin(vec3 rad) -{ return { fsin(rad.x), fsin(rad.y), fsin(rad.z) }; } - -/** cos */ - -inline float fcos(float rad) -{ return cos16(rad * 65535 / 3.14f) / 32767.0f; } - -inline vec2 fcos(vec2 rad) -{ return { fcos(rad.x), fcos(rad.y) }; } - -inline vec3 fcos(vec3 rad) -{ return { fcos(rad.x), fcos(rad.y), fcos(rad.z) }; } +} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 1959a1d..82c187a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,7 +14,7 @@ void setup() { setup_buttons(); FastLED.setBrightness(150); - EffectsList::getInstance().setEffect(18); + EffectsList::getInstance().setEffect(16); } unsigned long tick = 0; From b15783b708051a47734b856ea092ed20378155a7 Mon Sep 17 00:00:00 2001 From: npo6ka Date: Mon, 30 Dec 2019 17:18:59 +0300 Subject: [PATCH 41/75] fix warnings --- src/effects/20_circular_point.h | 2 ++ src/effects/21_zigzag.h | 5 ----- src/effects/fonts.h | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/effects/20_circular_point.h b/src/effects/20_circular_point.h index 94fd9fc..3e320d3 100644 --- a/src/effects/20_circular_point.h +++ b/src/effects/20_circular_point.h @@ -83,3 +83,5 @@ class CircularPoint : public Effect tick = tick + 3; } }; + +#undef ACCURACY \ No newline at end of file diff --git a/src/effects/21_zigzag.h b/src/effects/21_zigzag.h index 1ea837e..5e1ae22 100644 --- a/src/effects/21_zigzag.h +++ b/src/effects/21_zigzag.h @@ -2,11 +2,6 @@ #include "Effects/effect.h" -#define ACCURACY 100 -#define MIN_VEC_SIZE 5 -#define MAX_VEC_SIZE 10 -#define RAINBOW_TICK_SIZE 4 //кол-во тиков до инкремента тика радуги - class ZigZag : public Effect { uint8_t x; diff --git a/src/effects/fonts.h b/src/effects/fonts.h index 25b905f..5a017a7 100644 --- a/src/effects/fonts.h +++ b/src/effects/fonts.h @@ -169,4 +169,4 @@ { 0x28, 0x44, 0x54, 0x54, 0x38}, /*э */\ { 0x7c, 0x10, 0x38, 0x44, 0x38}, /*ю */\ { 0x08, 0x54, 0x34, 0x14, 0x7c} /*я 255 */\ - }; \ No newline at end of file + }; From eb4bb3d7ec99618e929f671f3dab776e9cb9bc1b Mon Sep 17 00:00:00 2001 From: AHTOH108 Date: Mon, 30 Dec 2019 21:27:34 +0300 Subject: [PATCH 42/75] fix free corruption --- src/constants.h | 6 +++--- src/effects/17_text.h | 6 ++++-- src/main.cpp | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/constants.h b/src/constants.h index d1ff618..6a2e974 100644 --- a/src/constants.h +++ b/src/constants.h @@ -9,11 +9,11 @@ #define WIDTH (16) // ширина матрицы #define HEIGHT (16) // высота матрицы #define LEDS_CNT (WIDTH * HEIGHT) // общее число светодиодов в матрице -#define CURRENT_LIMIT (2000U) // лимит по току в миллиамперах, автоматически управляет яркостью (пожалей свой блок питания!) 0 - выключить лимит +#define CURRENT_LIMIT (500U) // лимит по току в миллиамперах, автоматически управляет яркостью (пожалей свой блок питания!) 0 - выключить лимит #define COLOR_ORDER (GRB) // порядок цветов на ленте. Если цвет отображается некорректно - меняйте. Начать можно с RGB #define MATRIX_TYPE (0U) // тип матрицы: 0 - зигзаг, 1 - параллельная -#define CONNECTION_ANGLE (1U) // угол подключения матрицы (0-3): 0 - левый нижний, 1 - левый верхний, 2 - правый верхний, 3 - правый нижний +#define CONNECTION_ANGLE (2U) // угол подключения матрицы (0-3): 0 - левый нижний, 1 - левый верхний, 2 - правый верхний, 3 - правый нижний // 1---------2 // ----------- // ----------- @@ -40,4 +40,4 @@ #define BUTTON_CLICK_TIMEOUT (500U) // максимальное время между нажатиями кнопки в мс, до достижения которого считается серия последовательных нажатий #define AUTOMOD_INTERVAL (5000U) // кол-во времни между автоматическим переключением режима -#define DEFAULT_AUTOMOD false // Начальное состояние автомода. true - вкл, false - выкл \ No newline at end of file +#define DEFAULT_AUTOMOD true // Начальное состояние автомода. true - вкл, false - выкл \ No newline at end of file diff --git a/src/effects/17_text.h b/src/effects/17_text.h index 6066519..83d7ea2 100644 --- a/src/effects/17_text.h +++ b/src/effects/17_text.h @@ -143,14 +143,15 @@ class TextMode : public Effect TextMode() {} ~TextMode() { if (printed_text) { - free(printed_text); + delete printed_text; } } void set_text(const char *text) { if (printed_text) { - free(printed_text); + delete printed_text; + printed_text = nullptr; } printed_text = convert_utf8_to_cp1251(text); @@ -158,6 +159,7 @@ class TextMode : public Effect void on_init() { + printed_text = nullptr; set_text("Снега нет, но вы держитесь..."); set_fps(60); } diff --git a/src/main.cpp b/src/main.cpp index 82c187a..1959a1d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,7 +14,7 @@ void setup() { setup_buttons(); FastLED.setBrightness(150); - EffectsList::getInstance().setEffect(16); + EffectsList::getInstance().setEffect(18); } unsigned long tick = 0; From ff7d652d2d9a04f1aebc5c983c3cf8fb8129e9b2 Mon Sep 17 00:00:00 2001 From: npo6ka Date: Fri, 17 Jan 2020 00:54:59 +0300 Subject: [PATCH 43/75] add property --- src/effects/18_mouse.cpp | 2 +- src/effects/effect.h | 1 + src/main.cpp | 2 +- src/properties/Property.h | 69 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 src/properties/Property.h diff --git a/src/effects/18_mouse.cpp b/src/effects/18_mouse.cpp index 56b1586..f7e11aa 100644 --- a/src/effects/18_mouse.cpp +++ b/src/effects/18_mouse.cpp @@ -6,7 +6,7 @@ Mouse::Mouse() void Mouse::on_init() { - set_fps(10); + set_fps(30); x = 0; } diff --git a/src/effects/effect.h b/src/effects/effect.h index a728395..891bc95 100644 --- a/src/effects/effect.h +++ b/src/effects/effect.h @@ -1,6 +1,7 @@ #pragma once #include "lib_led.h" +#include "properties/Property.h" class Effect { diff --git a/src/main.cpp b/src/main.cpp index 82c187a..1959a1d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,7 +14,7 @@ void setup() { setup_buttons(); FastLED.setBrightness(150); - EffectsList::getInstance().setEffect(16); + EffectsList::getInstance().setEffect(18); } unsigned long tick = 0; diff --git a/src/properties/Property.h b/src/properties/Property.h new file mode 100644 index 0000000..1fbee6b --- /dev/null +++ b/src/properties/Property.h @@ -0,0 +1,69 @@ +#pragma once + +enum { + PR_UINT8 = 0, + PR_UINT16, + PR_UINT32, + PR_UINT64, + PR_FLOAT, + PR_DOUBLE, + PR_CHAR, + UNSUPPORT +}; + +template +class Property +{ + T val; + + uint8_t get_type_for_val(uint8_t val) { + return PR_UINT8; + } + + uint8_t get_type_for_val(uint16_t val) { + return PR_UINT16; + } + + uint8_t get_type_for_val(uint32_t val) { + return PR_UINT32; + } + + uint8_t get_type_for_val(uint64_t val) { + return PR_UINT64; + } + + uint8_t get_type_for_val(float val) { + return PR_FLOAT; + } + + uint8_t get_type_for_val(double val) { + return PR_DOUBLE; + } + + uint8_t get_type_for_val(char *val) { + return PR_CHAR; + } + + uint8_t get_type_for_val(T val) { + return UNSUPPORT; + } +public: + Property() = default; + ~Property() = default; + + T get() { + return val; + } + + void set(T new_val) { + val = new_val; + } + + size_t get_size() { + return sizeof(val); + } + + uint8_t get_type() { + return get_type_for_val(val); + } +}; From 9f61238064655f2391157062896acf8bae5f88fb Mon Sep 17 00:00:00 2001 From: npo6ka Date: Sun, 6 Dec 2020 16:04:04 +0300 Subject: [PATCH 44/75] minor changes --- .vscode/extensions.json | 12 ++++++------ src/constants.h | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 272828b..e80666b 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,7 +1,7 @@ { - // See http://go.microsoft.com/fwlink/?LinkId=827846 - // for the documentation about the extensions.json format - "recommendations": [ - "platformio.platformio-ide" - ] -} \ No newline at end of file + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ] +} diff --git a/src/constants.h b/src/constants.h index 6a2e974..5f02d2e 100644 --- a/src/constants.h +++ b/src/constants.h @@ -9,7 +9,7 @@ #define WIDTH (16) // ширина матрицы #define HEIGHT (16) // высота матрицы #define LEDS_CNT (WIDTH * HEIGHT) // общее число светодиодов в матрице -#define CURRENT_LIMIT (500U) // лимит по току в миллиамперах, автоматически управляет яркостью (пожалей свой блок питания!) 0 - выключить лимит +#define CURRENT_LIMIT (300U) // лимит по току в миллиамперах, автоматически управляет яркостью (пожалей свой блок питания!) 0 - выключить лимит #define COLOR_ORDER (GRB) // порядок цветов на ленте. Если цвет отображается некорректно - меняйте. Начать можно с RGB #define MATRIX_TYPE (0U) // тип матрицы: 0 - зигзаг, 1 - параллельная From 52c447eeb639ffcef8fa2aeffac3f93e6f678ea9 Mon Sep 17 00:00:00 2001 From: npo6ka Date: Tue, 29 Dec 2020 13:57:58 +0300 Subject: [PATCH 45/75] tmp commit --- .vscode/settings.json | 3 +- platformio.ini | 5 +- src/constants.h | 21 ++- src/effects/00_slow_random.h | 8 +- src/effects/01_simple_rainbow.h | 2 +- src/effects/effectslist.cpp | 32 ++--- src/effects/effectslist.h | 3 + src/effects/erroreffect.h | 2 +- src/main.cpp | 4 +- src/properties/Property.h | 111 +++++++++------- src/properties/memory_manager.cpp | 199 ++++++++++++++++++++++++++++ src/properties/memory_manager.h | 9 ++ src/properties/property_storage.cpp | 46 +++++++ src/properties/property_storage.h | 36 +++++ 14 files changed, 405 insertions(+), 76 deletions(-) create mode 100644 src/properties/memory_manager.cpp create mode 100644 src/properties/memory_manager.h create mode 100644 src/properties/property_storage.cpp create mode 100644 src/properties/property_storage.h diff --git a/.vscode/settings.json b/.vscode/settings.json index 47c21fe..4bca28e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -33,6 +33,7 @@ "streambuf": "cpp", "cinttypes": "cpp", "type_traits": "cpp", - "typeinfo": "cpp" + "typeinfo": "cpp", + "string": "cpp" } } \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index ea64f8c..23a232a 100644 --- a/platformio.ini +++ b/platformio.ini @@ -1,4 +1,4 @@ -;PlatformIO Project Configuration File +; PlatformIO Project Configuration File ; ; Build options: build flags, source filter ; Upload options: custom upload port, speed and extra flags @@ -14,4 +14,5 @@ board = esp12e framework = arduino build_flags = -Wno-unused-function monitor_speed = 115200 -upload_speed = 921600 \ No newline at end of file +upload_speed = 921600 +lib_deps = janelia-arduino/Vector@^1.2.0 diff --git a/src/constants.h b/src/constants.h index 5f02d2e..376e2ab 100644 --- a/src/constants.h +++ b/src/constants.h @@ -6,10 +6,10 @@ #define DATA_PIN (2U) // номер порта к которому подключены светодиоды -#define WIDTH (16) // ширина матрицы -#define HEIGHT (16) // высота матрицы +#define WIDTH (20) // ширина матрицы +#define HEIGHT (10) // высота матрицы #define LEDS_CNT (WIDTH * HEIGHT) // общее число светодиодов в матрице -#define CURRENT_LIMIT (300U) // лимит по току в миллиамперах, автоматически управляет яркостью (пожалей свой блок питания!) 0 - выключить лимит +#define CURRENT_LIMIT (1000U) // лимит по току в миллиамперах, автоматически управляет яркостью (пожалей свой блок питания!) 0 - выключить лимит #define COLOR_ORDER (GRB) // порядок цветов на ленте. Если цвет отображается некорректно - меняйте. Начать можно с RGB #define MATRIX_TYPE (0U) // тип матрицы: 0 - зигзаг, 1 - параллельная @@ -39,5 +39,16 @@ #define BUTTON_STEP_TIMEOUT (100U) // каждые BUTTON_STEP_TIMEOUT мс будет генерироваться событие удержания кнопки (для регулировки яркости) #define BUTTON_CLICK_TIMEOUT (500U) // максимальное время между нажатиями кнопки в мс, до достижения которого считается серия последовательных нажатий -#define AUTOMOD_INTERVAL (5000U) // кол-во времни между автоматическим переключением режима -#define DEFAULT_AUTOMOD true // Начальное состояние автомода. true - вкл, false - выкл \ No newline at end of file +#define AUTOMOD_INTERVAL (15000U) // кол-во времни между автоматическим переключением режима +#define DEFAULT_AUTOMOD true // Начальное состояние автомода. true - вкл, false - выкл + +// ============= Настройки Сохранения в память ================ + +#define MOD_LIST_OFFSET 64 // начальная точка записи адресов на режимы в ПЗУ +#define MOD_LIST_MAX_SIZE 128 // максимальное количество режимов, которое можно записать в ПЗУ +#define MOD_DATA_OFFSET MOD_LIST_OFFSET + MOD_LIST_MAX_SIZE * 2 // 2 байта для хранения адреса на данные режима +#define MOD_DATA_SIZE 448 // размер буфера под данные режимов + +// =================== Настройки Режимов ====================== + +#define STARTING_MOD 0 \ No newline at end of file diff --git a/src/effects/00_slow_random.h b/src/effects/00_slow_random.h index 7762738..6bf0858 100644 --- a/src/effects/00_slow_random.h +++ b/src/effects/00_slow_random.h @@ -5,10 +5,11 @@ class SlowRandom : public Effect { uint8_t inc_val[LEDS_CNT]; - int step; - + Property step = {0, 0}; public: - SlowRandom() {} + SlowRandom() { + //*step = &Property(0, 0); + } uint8_t gen_led(uint8_t &color_val) { @@ -24,6 +25,7 @@ class SlowRandom : public Effect void on_init() { step = 2; set_fps(120); + Property step(0, 0); uint16_t i; diff --git a/src/effects/01_simple_rainbow.h b/src/effects/01_simple_rainbow.h index cb8b77a..19c8674 100644 --- a/src/effects/01_simple_rainbow.h +++ b/src/effects/01_simple_rainbow.h @@ -4,8 +4,8 @@ class SimpleRainbow : public Effect { + Property phaseShift; int tick; - int phaseShift; public: SimpleRainbow() {} diff --git a/src/effects/effectslist.cpp b/src/effects/effectslist.cpp index f2e166c..6101ac2 100644 --- a/src/effects/effectslist.cpp +++ b/src/effects/effectslist.cpp @@ -2,7 +2,7 @@ #include "00_slow_random.h" #include "01_simple_rainbow.h" -#include "02_dribs.h" +/*#include "02_dribs.h" #include "03_rain.h" #include "04_all_random.h" #include "05_snow.h" @@ -24,12 +24,10 @@ #include "21_zigzag.h" #include "22_horizontal_rainbow_point.h" #include "23_test_shader.h" -#include "24_ny2020.h" +#include "24_ny2020.h"*/ /* #include "testmode.h"*/ -#define MAX_EFFECTS 50 - EffectsList& EffectsList::getInstance() { static EffectsList instance; return instance; @@ -40,21 +38,25 @@ EffectsList::EffectsList() { } void EffectsList::init() { - amnt = MAX_EFFECTS; + amnt = 0; Effect *eff = NULL; - curEffect = nullptr; + curEffect = NULL; + + do { + if (eff) { delete eff; } - while(eff == NULL && amnt >= 0) { - amnt--; eff = getNewEffectInstance(amnt); - } - if (eff == NULL) { + if (eff) { + mods_size[amnt] = PropertyStorage::instance().get_mod_size(); + amnt++; + } + } while (eff != NULL && amnt <= MAX_EFFECTS); + + if (amnt == 0) { setErrorEffect(); } else { - curNum = amnt; - setEffect(eff); - amnt += 1; + setEffect(STARTING_MOD); } } @@ -64,7 +66,7 @@ Effect *EffectsList::getNewEffectInstance(int num) { return new SlowRandom(); case 1: return new SimpleRainbow(); - case 2: + /*case 2: return new Dribs(); case 3: return new Rain(); @@ -109,7 +111,7 @@ Effect *EffectsList::getNewEffectInstance(int num) { case 23: return new TestShader(); case 24: - return new NY2020(); + return new NY2020();*/ default: return NULL; } diff --git a/src/effects/effectslist.h b/src/effects/effectslist.h index e7a24ad..314ead3 100644 --- a/src/effects/effectslist.h +++ b/src/effects/effectslist.h @@ -2,6 +2,8 @@ #include "erroreffect.h" +#define MAX_EFFECTS 25 + class EffectsList { private: @@ -16,6 +18,7 @@ class EffectsList Effect *curEffect; unsigned long prev_micros; unsigned long fps; + uint8_t mods_size[MAX_EFFECTS]; void init(); void setEffect(Effect *eff); diff --git a/src/effects/erroreffect.h b/src/effects/erroreffect.h index 1e9723e..7143aab 100644 --- a/src/effects/erroreffect.h +++ b/src/effects/erroreffect.h @@ -8,6 +8,6 @@ class ErrorEffect : public Effect ErrorEffect() {} void on_update() { - FastLED.showColor(CRGB::Red); + FastLED.showColor(0x008800); } }; diff --git a/src/main.cpp b/src/main.cpp index 1959a1d..758690e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -13,8 +13,8 @@ void setup() { // инициализация кнопок setup_buttons(); - FastLED.setBrightness(150); - EffectsList::getInstance().setEffect(18); + FastLED.setBrightness(50); + EffectsList::getInstance().setEffect(0); } unsigned long tick = 0; diff --git a/src/properties/Property.h b/src/properties/Property.h index 1fbee6b..1fa407f 100644 --- a/src/properties/Property.h +++ b/src/properties/Property.h @@ -1,69 +1,88 @@ #pragma once -enum { - PR_UINT8 = 0, - PR_UINT16, - PR_UINT32, - PR_UINT64, - PR_FLOAT, - PR_DOUBLE, - PR_CHAR, - UNSUPPORT +#include "debug_lib.h" +#include "property_storage.h" + +class ISaveable { +public: + virtual ~ISaveable() = default; + virtual void save() const = 0; + virtual void load() = 0; }; -template -class Property -{ - T val; +class IProperty : ISaveable { +public: + virtual ~IProperty() = default; + virtual void save() const = 0; + virtual void load() = 0; + virtual uint16_t size() const = 0; +}; + +template +class Property : IProperty { + T m_value; + T min_val; + T max_val; - uint8_t get_type_for_val(uint8_t val) { - return PR_UINT8; +public: + Property(T value, T min, T max) : m_value(value), min_val(min), max_val(max) + { + PropertyStorage::instance().add(this); } + Property(T min, T max) : Property({}, min, max) { } + Property(T val) : Property(val, {}, {}) { } + Property() : Property({}, {}, {}) { } - uint8_t get_type_for_val(uint16_t val) { - return PR_UINT16; + ~Property() + { + PropertyStorage::instance().clear(this); } - uint8_t get_type_for_val(uint32_t val) { - return PR_UINT32; - } + Property(const Property&) = delete; + Property(Property&&) = delete; - uint8_t get_type_for_val(uint64_t val) { - return PR_UINT64; - } + Property& operator=(const Property&) = delete; + Property& operator=(Property&&) = delete; - uint8_t get_type_for_val(float val) { - return PR_FLOAT; + Property& operator=(T value) + { + m_value = value; + return *this; } - uint8_t get_type_for_val(double val) { - return PR_DOUBLE; - } + operator T() const + { return m_value; } - uint8_t get_type_for_val(char *val) { - return PR_CHAR; - } + T& operator*() + { return m_value; } - uint8_t get_type_for_val(T val) { - return UNSUPPORT; - } -public: - Property() = default; - ~Property() = default; + const T& operator*() const + { return m_value; } - T get() { - return val; - } + T& operator->() + { return m_value; } + + const T& operator->() const + { return m_value; } + + void save() const override + {} + + void load() override + {} - void set(T new_val) { - val = new_val; + uint16_t size() const override + { + return sizeof(T); } - size_t get_size() { - return sizeof(val); + T get_min() + { + return min_val; } - uint8_t get_type() { - return get_type_for_val(val); + T get_max() + { + return max_val; } }; diff --git a/src/properties/memory_manager.cpp b/src/properties/memory_manager.cpp new file mode 100644 index 0000000..c3fa4a5 --- /dev/null +++ b/src/properties/memory_manager.cpp @@ -0,0 +1,199 @@ +#include "debug_lib.h" +#include +#include "constants.h" + +static uint16_t read_address(uint32_t offset) { + if (offset < 4095) { + uint16_t ret_val = EEPROM.read(offset); + ret_val = ret_val << 8 | EEPROM.read(offset + 1); + return ret_val; + } else { + out("Error reading ROM: out of memory\n"); + } + + return 0; +} + +static void write_address(uint32_t offset, uint16_t data) { + if (offset < 4095) { + EEPROM.write(offset + 1, data & 0xff); + EEPROM.write(offset, (data & 0xff00) >> 8); + } else { + out("Error writing to ROM: out of memory\n"); + } +} + +static uint8_t find_free_slot(uint16_t &slot) { + //поиск пустого места среди адресов на дату режимов + while (read_address(MOD_LIST_OFFSET + slot * 2) != 0) { + if (slot >= MOD_LIST_MAX_SIZE) { + return 1; // Достигнуто максимальное кол-во режимов в ПЗУ + } + slot++; + } + + return 0; +} + +static uint16_t get_mod_size(uint16_t mod_addr) { + uint8_t mod_id = EEPROM.read(mod_addr); + if (mod_id < mods_size) { + return mods[mod_id]->size + 1; + } + return -1; +} + +static void memory_fragmentation() { + uint16_t cur_mod = 0; + + while (read_address(MOD_LIST_OFFSET + cur_mod * 2) != 0 && cur_mod < MOD_LIST_MAX_SIZE) { + uint16_t old_mod_addr = read_address(MOD_LIST_OFFSET + cur_mod * 2); + uint16_t mod_size = get_mod_size(old_mod_addr); + uint16_t new_mod_addr = MOD_DATA_OFFSET; + + if (cur_mod != 0) { + uint16_t prev_mod_addr = read_address(MOD_LIST_OFFSET + (cur_mod - 1) * 2); + new_mod_addr = prev_mod_addr + get_mod_size(prev_mod_addr); + } + + if (old_mod_addr != new_mod_addr) { + for (int i = 0; i < mod_size; ++i) { + EEPROM.write(new_mod_addr + i, EEPROM.read(old_mod_addr + i)); + } + write_address(MOD_LIST_OFFSET + cur_mod * 2, new_mod_addr); + } + + cur_mod++; + } + +} + +static uint8_t find_free_address(uint16_t &addr, uint16_t free_slot) { + addr = MOD_DATA_OFFSET; + + // считаеим следующий незанятый адрес, если 0, то начало дата сета + if (free_slot) { + uint16_t prev_addr = read_address(MOD_LIST_OFFSET + (free_slot - 1) * 2); + uint16_t mod_size = get_mod_size(prev_addr); + + if (mod_size == -1) { + return 2; // прочитан неверный id режима + } + addr = prev_addr + mod_size; + } + + return 0; +} + +static int8_t add_mod(uint8_t mod_id, Mode *m) { + uint16_t write_addres = MOD_DATA_OFFSET; + uint16_t free_slot = 0; + uint8_t err = 0; + + if (find_free_slot(free_slot)) { + return 1; // Достигнуто максимальное кол-во режимов в ПЗУ + } + + err = find_free_address(write_addres, free_slot); + if (err) { + return err; + } + + if (write_addres + m->size + 1 > MOD_DATA_OFFSET + MOD_DATA_SIZE) { + memory_fragmentation(); + + err = find_free_address(write_addres, free_slot); + if (err) { + return err; + } + + if (write_addres + m->size + 1 > MOD_DATA_OFFSET + MOD_DATA_SIZE) { + return 3; // закончилась память для данных режиов + } + } + + write_address(MOD_LIST_OFFSET + free_slot * 2, write_addres); + EEPROM.write(write_addres, mod_id); + + //init all paramets for mod + for (int i = 0; i < m->size; ++i) { + EEPROM.write(write_addres + i + 1, i + mod_id); + } + return 0; +} + +void add_mod(uint8_t mod_id) { + uint8_t err = add_mod(mod_id, mods[mod_id]); + + switch(err) { + case 1: + out("Достигнуто максимальное кол-во режимов в ПЗУ\n"); + break; + case 2: + out("прочитан неверный id режима\n"); + break; + case 3: + out("Закончилась память для данных режиов\n"); + break; + } +} + +void remove_mode(uint8_t mod_num) { + uint16_t cur_addr = MOD_LIST_OFFSET + mod_num * 2; + + if (cur_addr >= MOD_DATA_OFFSET || !read_address(cur_addr)) { + out("Удаление несуществующего мода\n"); + return; + } + + while (cur_addr + 2 < MOD_DATA_OFFSET && read_address(cur_addr + 2) != 0) { + write_address(cur_addr, read_address(cur_addr + 2)); + cur_addr += 2; + } + + if (cur_addr < MOD_DATA_OFFSET) { + write_address(cur_addr, 0); + } +} + +void validate_memory() { + uint8_t slot = 0; + while (slot < MOD_LIST_MAX_SIZE && read_address(MOD_LIST_OFFSET + slot * 2) != 0) { + uint16_t addr = read_address(MOD_LIST_OFFSET + slot * 2); + uint16_t mod_size = get_mod_size(addr); + + if (addr + mod_size >= MOD_DATA_OFFSET + MOD_DATA_SIZE) { + remove_mode(slot); + out("Modsize changed for mod id: %d. Removed this mod of memory\n", slot); + } else { + if (slot + 1 < MOD_LIST_MAX_SIZE && read_address(MOD_LIST_OFFSET + (slot + 1) * 2) != 0) { + uint16_t next_mod_addr = read_address(MOD_LIST_OFFSET + (slot + 1) * 2); + if (next_mod_addr - addr < mod_size) { + remove_mode(slot); + out("Modsize changed for mod id: %d. Removed this mod of memory\n", slot); + } else { + slot++; + } + } else { + slot++; + } + } + } +} + +void debug_mem(uint16_t addr, uint8_t size) { + out("0x%02x: ", addr); + for (int i = 0; i < size; ++i) { + out("0x%02x ", EEPROM.read(addr + i)); + } + out("\n"); +} + +void print_all_mods() { + int addr = MOD_LIST_OFFSET; + while (read_address(addr) != 0 && addr < MOD_DATA_OFFSET) { + debug_mem(read_address(addr), get_mod_size(read_address(addr))); + addr += 2; + } + out("\n\n"); +} \ No newline at end of file diff --git a/src/properties/memory_manager.h b/src/properties/memory_manager.h new file mode 100644 index 0000000..b55a8cf --- /dev/null +++ b/src/properties/memory_manager.h @@ -0,0 +1,9 @@ +#pragma once + +#include "inttypes.h" + +void add_mod(uint8_t mod_id); +void remove_mode(uint8_t mod_num); +void validate_memory(); +void debug_mem(uint16_t addr, uint8_t size); +void print_all_mods(); \ No newline at end of file diff --git a/src/properties/property_storage.cpp b/src/properties/property_storage.cpp new file mode 100644 index 0000000..91a13fc --- /dev/null +++ b/src/properties/property_storage.cpp @@ -0,0 +1,46 @@ +#include "property.h" + +void PropertyStorage::set_offset(uint16_t offset) { + addr_offset = offset; +} +uint16_t PropertyStorage::get_offset() { + return addr_offset; +} + +uint8_t PropertyStorage::get_mod_size() { + uint32_t size = 0; + + for (auto it : items) { + size += it->size(); + } + + if (size > 0xff) { + out("ERROR: Mod size is very big. More then 255\n"); + return 0; + } + return size; +} + +void PropertyStorage::add(IProperty* item) +{ items.push_back(item); } + +void PropertyStorage::clear(IProperty* item) +{ + if (items.size() > 0) { + items.clear(); + } +} + +void PropertyStorage::load() +{ + for (auto it : items) { + it->load(); + } +} + +void PropertyStorage::save() const +{ + for (auto it : items) { + it->save(); + } +} \ No newline at end of file diff --git a/src/properties/property_storage.h b/src/properties/property_storage.h new file mode 100644 index 0000000..268ece8 --- /dev/null +++ b/src/properties/property_storage.h @@ -0,0 +1,36 @@ +#pragma once + +#include + +class IProperty; + +class PropertyStorage { + std::vector items; + uint16_t addr_offset = -1; + + PropertyStorage() = default; + ~PropertyStorage() = default; + + PropertyStorage(const PropertyStorage&) = delete; + PropertyStorage(PropertyStorage&&) = delete; + + PropertyStorage& operator=(const PropertyStorage&) = delete; + PropertyStorage& operator=(PropertyStorage&&) = delete; + +public: + static PropertyStorage& instance() + { + static PropertyStorage obj; + return obj; + } + + void set_offset(uint16_t offset); + uint16_t get_offset(); + uint8_t get_mod_size(); + + void add(IProperty* item); + void clear(IProperty* item); + + void save() const; + void load(); +}; \ No newline at end of file From 0d852d8bf2ef7c6525f0d50e76dacf8e8acff71f Mon Sep 17 00:00:00 2001 From: "alexey.ivanov" Date: Mon, 12 Feb 2024 12:44:42 +0300 Subject: [PATCH 46/75] memory test --- data/2.txt | 0 platformio.ini | 18 +++- src/effects/08_simple_balls.h | 1 + src/effects/effectslist.h | 4 +- src/main.cpp | 114 +++++++++++++++++++++-- src/properties/Property.h | 6 +- src/properties/memory_manager.cpp | 134 ++++++++++++++-------------- src/properties/property_storage.cpp | 4 +- src/properties/property_storage.h | 12 +-- 9 files changed, 200 insertions(+), 93 deletions(-) create mode 100644 data/2.txt diff --git a/data/2.txt b/data/2.txt new file mode 100644 index 0000000..e69de29 diff --git a/platformio.ini b/platformio.ini index 23a232a..c8e3a41 100644 --- a/platformio.ini +++ b/platformio.ini @@ -9,10 +9,20 @@ ; https://docs.platformio.org/page/projectconf.html [env:esp12e] -platform = espressif8266 board = esp12e + +; [env:esp8285] +; board = esp8285 +; upload_protocol = esptool +; upload_resetmethod = nodemcu + + + +; [common options] +platform = espressif8266@2.6.3 framework = arduino -build_flags = -Wno-unused-function +board_build.filesystem = littlefs +build_flags = -Wno-unused-function -Wno-register +lib_deps = fastled/FastLED@^3.4.0, janelia-arduino/Vector@^1.2.0 monitor_speed = 115200 -upload_speed = 921600 -lib_deps = janelia-arduino/Vector@^1.2.0 +upload_speed = 921600 \ No newline at end of file diff --git a/src/effects/08_simple_balls.h b/src/effects/08_simple_balls.h index 28dbf62..27832a3 100644 --- a/src/effects/08_simple_balls.h +++ b/src/effects/08_simple_balls.h @@ -75,6 +75,7 @@ class SimpleBalls : public Effect coord[j][0] = (HEIGHT - 1) * 10; vector[j][0] = -vector[j][0]; } + if (coord[j][1] > (WIDTH - 1) * 10) { coord[j][1] = (WIDTH - 1) * 10; vector[j][1] = -vector[j][1]; diff --git a/src/effects/effectslist.h b/src/effects/effectslist.h index 314ead3..9ed8092 100644 --- a/src/effects/effectslist.h +++ b/src/effects/effectslist.h @@ -10,9 +10,9 @@ class EffectsList // singlton property // Конструкторы и оператор присваивания недоступны клиентам EffectsList(); - EffectsList( const EffectsList& ); + EffectsList( const EffectsList& ); EffectsList& operator=( EffectsList& ); - + int amnt; int curNum = 0; Effect *curEffect; diff --git a/src/main.cpp b/src/main.cpp index 758690e..33609c0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,12 +1,15 @@ #include "button/button_handler.h" #include "effects/effectslist.h" +#include "effects/lib_led.h" +#include "LittleFS.h" // все настройки матрицы находятся в lib_led.h // инициализация светодиодов -CRGB leds[LEDS_CNT]; +/*CRGB leds[LEDS_CNT]; void setup() { randomSeed(millis() + analogRead(A0)); + random16_set_seed(millis() + analogRead(A0)); debug_setup(); led_setup(); @@ -17,8 +20,8 @@ void setup() { EffectsList::getInstance().setEffect(0); } -unsigned long tick = 0; -int tps = 0; +// unsigned long tick = 0; +// int tps = 0; void loop() { EffectsList::getInstance().onTick(); @@ -26,10 +29,103 @@ void loop() { tick_buttons(); // проверка реального тпс работы микроконтроллера - /*tps++; - if (millis() > tick * 1000 ) { - out("tps: %d fps: %.1f\n", tps, EffectsList::getInstance().getCurFPS()); - tick++; - tps = 0; - }*/ + // tps++; + // if (millis() > tick * 1000 ) { + // out("tps: %d fps: %.1f\n", tps, EffectsList::getInstance().getCurFPS()); + // tick++; + // tps = 0; + // } +}*/ + + + +void setup() { + Serial.begin(115200); + + uint8_t val = beatsin8(4); + + + + + // if (!LittleFS.begin()) { + // Serial.println("An Error has occurred while mounting LittleFS"); + // return; + // } + + /*FSInfo fsi; + LittleFS.info(fsi); + printf("\ttotalBytes: %d\n\t usedBytes: %d\n\t blockSize: %d\n\t pageSize: \ + %d\n\t maxOpenFiles: %d\n\t maxPathLength: %d\n", fsi.totalBytes, + fsi.usedBytes, fsi.blockSize, fsi.pageSize, fsi.maxOpenFiles, fsi.maxPathLength);*/ + + // Dir dir = LittleFS.openDir("/"); + // while (dir.next()) { + // Serial.println(dir.fileName()); + // if(dir.fileSize()) { + // File f = dir.openFile("r"); + // Serial.println(f.size()); + // } + // } + + + + // File fp = LittleFS.open("/2.txt", "r+"); + // if (!fp) { + // Serial.println("Failed to open file for reading"); + // return; + // } + + // fp.seek(0, SeekSet); + // uint16_t vals[] = {0x12, 0x15, 0x22, 0x34, 0x38, 0x55, 0x5b, 0x60, 0x65, 0x68, 0x70, 0x72, 0x74, 0x76, 0x78, 0x80, 0x82, 0x84, 0x86, 0x88, 0x90, 0x92, 0x94, 0x96, 0x98, 0x100}; + // uint8_t size = sizeof(vals) / sizeof(vals[0]); + // Serial.printf("size: %d\n", size); + + // fp.seek(0, SeekSet); + // for (uint16_t i = 0; i < vals[size - 1]; ++i) { + // fp.write(0); + // } + + // fp.seek(0, SeekSet); + // for (uint16_t i = 0; i < size; ++i) { + // fp.write(vals[i]); + // fp.seek(vals[i], SeekSet); + // } + // fp.write("e"); + + // fp.seek(0, SeekSet); + // Serial.println("File Content:"); + // while (fp.available()) { + // Serial.printf("%d ", fp.read()); + // } + + // unsigned long myTime1, myTime2; + // myTime1 = micros(); + + // fp.seek(0, SeekSet); + // uint8_t val; + // for (int i = 0; i < 26; ++i) { + // val = fp.read(); + // // Serial.printf("%x ", val); + // fp.seek(val, SeekSet); + // } + // myTime2 = micros(); + // Serial.printf("time diff1: %lu\n", myTime2 - myTime1); + + + // uint16_t vals[] = {0x12, 0x15, 0x22, 0x34, 0x38, 0x55, 0x5b, 0x60, 0x65, 0x68, 0x70, 0x72, 0x74, 0x76, 0x78, 0x80, 0x82, 0x84, 0x86, 0x88, 0x90, 0x92, 0x94, 0x96, 0x98, 0x100}; + // uint16_t vals2[] = {0x12, 0x15, 0x22, 0x34, 0x38, 0x55, 0x5b, 0x60, 0x65, 0x68, 0x70, 0x72, 0x74, 0x76, 0x78, 0x80, 0x82, 0x84, 0x86, 0x88, 0x90, 0x92, 0x94, 0x96, 0x98, 0x100}; + // myTime1 = micros(); + // for (int i = 0; i < 26; ++i) { + // val = vals[i]; + // vals2[i] = val; + // } + // vals2[0]++; + // myTime2 = micros(); + // Serial.printf("time diff2: %lu\n", myTime2 - myTime1); + + // fp.close(); +} + +void loop() { + } \ No newline at end of file diff --git a/src/properties/Property.h b/src/properties/Property.h index 1fa407f..f60f281 100644 --- a/src/properties/Property.h +++ b/src/properties/Property.h @@ -17,7 +17,7 @@ class IProperty : ISaveable { virtual void load() = 0; virtual uint16_t size() const = 0; }; - + template class Property : IProperty { T m_value; @@ -64,10 +64,10 @@ class Property : IProperty { const T& operator->() const { return m_value; } - + void save() const override {} - + void load() override {} diff --git a/src/properties/memory_manager.cpp b/src/properties/memory_manager.cpp index c3fa4a5..354a52b 100644 --- a/src/properties/memory_manager.cpp +++ b/src/properties/memory_manager.cpp @@ -3,43 +3,43 @@ #include "constants.h" static uint16_t read_address(uint32_t offset) { - if (offset < 4095) { - uint16_t ret_val = EEPROM.read(offset); - ret_val = ret_val << 8 | EEPROM.read(offset + 1); - return ret_val; - } else { - out("Error reading ROM: out of memory\n"); - } + // if (offset < 4095) { + // uint16_t ret_val = EEPROM.read(offset); + // ret_val = ret_val << 8 | EEPROM.read(offset + 1); + // return ret_val; + // } else { + // out("Error reading ROM: out of memory\n"); + // } return 0; } static void write_address(uint32_t offset, uint16_t data) { - if (offset < 4095) { - EEPROM.write(offset + 1, data & 0xff); - EEPROM.write(offset, (data & 0xff00) >> 8); - } else { - out("Error writing to ROM: out of memory\n"); - } + // if (offset < 4095) { + // EEPROM.write(offset + 1, data & 0xff); + // EEPROM.write(offset, (data & 0xff00) >> 8); + // } else { + // out("Error writing to ROM: out of memory\n"); + // } } static uint8_t find_free_slot(uint16_t &slot) { //поиск пустого места среди адресов на дату режимов - while (read_address(MOD_LIST_OFFSET + slot * 2) != 0) { - if (slot >= MOD_LIST_MAX_SIZE) { - return 1; // Достигнуто максимальное кол-во режимов в ПЗУ - } - slot++; - } - + // while (read_address(MOD_LIST_OFFSET + slot * 2) != 0) { + // if (slot >= MOD_LIST_MAX_SIZE) { + // return 1; // Достигнуто максимальное кол-во режимов в ПЗУ + // } + // slot++; + // } + return 0; } static uint16_t get_mod_size(uint16_t mod_addr) { - uint8_t mod_id = EEPROM.read(mod_addr); - if (mod_id < mods_size) { - return mods[mod_id]->size + 1; - } + // uint8_t mod_id = EEPROM.read(mod_addr); + // if (mod_id < mods_size) { + // return mods[mod_id]->size + 1; + // } return -1; } @@ -55,7 +55,7 @@ static void memory_fragmentation() { uint16_t prev_mod_addr = read_address(MOD_LIST_OFFSET + (cur_mod - 1) * 2); new_mod_addr = prev_mod_addr + get_mod_size(prev_mod_addr); } - + if (old_mod_addr != new_mod_addr) { for (int i = 0; i < mod_size; ++i) { EEPROM.write(new_mod_addr + i, EEPROM.read(old_mod_addr + i)); @@ -85,57 +85,57 @@ static uint8_t find_free_address(uint16_t &addr, uint16_t free_slot) { return 0; } -static int8_t add_mod(uint8_t mod_id, Mode *m) { - uint16_t write_addres = MOD_DATA_OFFSET; - uint16_t free_slot = 0; - uint8_t err = 0; +// static int8_t add_mod(uint8_t mod_id, Mode *m) { + // uint16_t write_addres = MOD_DATA_OFFSET; + // uint16_t free_slot = 0; + // uint8_t err = 0; - if (find_free_slot(free_slot)) { - return 1; // Достигнуто максимальное кол-во режимов в ПЗУ - } + // if (find_free_slot(free_slot)) { + // return 1; // Достигнуто максимальное кол-во режимов в ПЗУ + // } - err = find_free_address(write_addres, free_slot); - if (err) { - return err; - } + // err = find_free_address(write_addres, free_slot); + // if (err) { + // return err; + // } - if (write_addres + m->size + 1 > MOD_DATA_OFFSET + MOD_DATA_SIZE) { - memory_fragmentation(); + // if (write_addres + m->size + 1 > MOD_DATA_OFFSET + MOD_DATA_SIZE) { + // memory_fragmentation(); - err = find_free_address(write_addres, free_slot); - if (err) { - return err; - } + // err = find_free_address(write_addres, free_slot); + // if (err) { + // return err; + // } - if (write_addres + m->size + 1 > MOD_DATA_OFFSET + MOD_DATA_SIZE) { - return 3; // закончилась память для данных режиов - } - } + // if (write_addres + m->size + 1 > MOD_DATA_OFFSET + MOD_DATA_SIZE) { + // return 3; // закончилась память для данных режиов + // } + // } - write_address(MOD_LIST_OFFSET + free_slot * 2, write_addres); - EEPROM.write(write_addres, mod_id); + // write_address(MOD_LIST_OFFSET + free_slot * 2, write_addres); + // EEPROM.write(write_addres, mod_id); - //init all paramets for mod - for (int i = 0; i < m->size; ++i) { - EEPROM.write(write_addres + i + 1, i + mod_id); - } - return 0; -} + // //init all paramets for mod + // for (int i = 0; i < m->size; ++i) { + // EEPROM.write(write_addres + i + 1, i + mod_id); + // } +// return 0; +// } void add_mod(uint8_t mod_id) { - uint8_t err = add_mod(mod_id, mods[mod_id]); - - switch(err) { - case 1: - out("Достигнуто максимальное кол-во режимов в ПЗУ\n"); - break; - case 2: - out("прочитан неверный id режима\n"); - break; - case 3: - out("Закончилась память для данных режиов\n"); - break; - } + // uint8_t err = add_mod(mod_id, mods[mod_id]); + + // switch(err) { + // case 1: + // out("Достигнуто максимальное кол-во режимов в ПЗУ\n"); + // break; + // case 2: + // out("прочитан неверный id режима\n"); + // break; + // case 3: + // out("Закончилась память для данных режиов\n"); + // break; + // } } void remove_mode(uint8_t mod_num) { diff --git a/src/properties/property_storage.cpp b/src/properties/property_storage.cpp index 91a13fc..0561de4 100644 --- a/src/properties/property_storage.cpp +++ b/src/properties/property_storage.cpp @@ -25,9 +25,9 @@ void PropertyStorage::add(IProperty* item) { items.push_back(item); } void PropertyStorage::clear(IProperty* item) -{ +{ if (items.size() > 0) { - items.clear(); + items.clear(); } } diff --git a/src/properties/property_storage.h b/src/properties/property_storage.h index 268ece8..088706d 100644 --- a/src/properties/property_storage.h +++ b/src/properties/property_storage.h @@ -7,16 +7,16 @@ class IProperty; class PropertyStorage { std::vector items; uint16_t addr_offset = -1; - + PropertyStorage() = default; ~PropertyStorage() = default; - + PropertyStorage(const PropertyStorage&) = delete; PropertyStorage(PropertyStorage&&) = delete; - + PropertyStorage& operator=(const PropertyStorage&) = delete; PropertyStorage& operator=(PropertyStorage&&) = delete; - + public: static PropertyStorage& instance() { @@ -27,10 +27,10 @@ class PropertyStorage { void set_offset(uint16_t offset); uint16_t get_offset(); uint8_t get_mod_size(); - + void add(IProperty* item); void clear(IProperty* item); - + void save() const; void load(); }; \ No newline at end of file From 2efa3c0c6d35bd14a9daac9e4f517986bbfa1db8 Mon Sep 17 00:00:00 2001 From: "alexey.ivanov" Date: Mon, 12 Feb 2024 12:46:47 +0300 Subject: [PATCH 47/75] update gitignore --- .gitignore | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 89cc49c..b9f3806 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,2 @@ .pio -.vscode/.browse.c_cpp.db* -.vscode/c_cpp_properties.json -.vscode/launch.json -.vscode/ipch +.vscode From fc9603c5e12f90c7f32122d14ab441cceb77877f Mon Sep 17 00:00:00 2001 From: "alexey.ivanov" Date: Mon, 12 Feb 2024 13:36:35 +0300 Subject: [PATCH 48/75] rm cached --- .vscode/extensions.json | 7 ---- .vscode/settings.json | 39 ----------------------- src/properties/{Property.h => property.h} | 0 3 files changed, 46 deletions(-) delete mode 100644 .vscode/extensions.json delete mode 100644 .vscode/settings.json rename src/properties/{Property.h => property.h} (100%) diff --git a/.vscode/extensions.json b/.vscode/extensions.json deleted file mode 100644 index e80666b..0000000 --- a/.vscode/extensions.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - // See http://go.microsoft.com/fwlink/?LinkId=827846 - // for the documentation about the extensions.json format - "recommendations": [ - "platformio.platformio-ide" - ] -} diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 4bca28e..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "files.associations": { - "array": "cpp", - "*.tcc": "cpp", - "functional": "cpp", - "istream": "cpp", - "tuple": "cpp", - "utility": "cpp", - "atomic": "cpp", - "cctype": "cpp", - "clocale": "cpp", - "cmath": "cpp", - "cstdarg": "cpp", - "cstdint": "cpp", - "cstdio": "cpp", - "cstdlib": "cpp", - "cwchar": "cpp", - "cwctype": "cpp", - "deque": "cpp", - "list": "cpp", - "unordered_map": "cpp", - "vector": "cpp", - "exception": "cpp", - "fstream": "cpp", - "initializer_list": "cpp", - "iosfwd": "cpp", - "limits": "cpp", - "new": "cpp", - "ostream": "cpp", - "numeric": "cpp", - "sstream": "cpp", - "stdexcept": "cpp", - "streambuf": "cpp", - "cinttypes": "cpp", - "type_traits": "cpp", - "typeinfo": "cpp", - "string": "cpp" - } -} \ No newline at end of file diff --git a/src/properties/Property.h b/src/properties/property.h similarity index 100% rename from src/properties/Property.h rename to src/properties/property.h From d15567abefde848f987f419fe5ebf770d0cbe213 Mon Sep 17 00:00:00 2001 From: "alexey.ivanov" Date: Thu, 15 Feb 2024 11:11:15 +0300 Subject: [PATCH 49/75] Add cpp on desktop memory tests --- cpp_memory/effect_list/effect.h | 55 +++ cpp_memory/effect_list/effects/slow_random.h | 19 + .../effect_list/effects/slow_random_2.h | 20 + cpp_memory/effect_list/effectslist.cpp | 198 +++++++++ cpp_memory/effect_list/effectslist.h | 45 ++ cpp_memory/effect_list/erroreffect.h | 13 + cpp_memory/main.cpp | 227 ++++++++++ cpp_memory/main.exe | Bin 0 -> 411317 bytes cpp_memory/memory/memory.h | 109 +++++ cpp_memory/memory/memory_manger.h | 407 ++++++++++++++++++ cpp_memory/memory/property.h | 103 +++++ cpp_memory/memory/property_storage.cpp | 35 ++ cpp_memory/memory/property_storage.h | 27 ++ cpp_memory/mods.dat | Bin 0 -> 65536 bytes 14 files changed, 1258 insertions(+) create mode 100644 cpp_memory/effect_list/effect.h create mode 100644 cpp_memory/effect_list/effects/slow_random.h create mode 100644 cpp_memory/effect_list/effects/slow_random_2.h create mode 100644 cpp_memory/effect_list/effectslist.cpp create mode 100644 cpp_memory/effect_list/effectslist.h create mode 100644 cpp_memory/effect_list/erroreffect.h create mode 100644 cpp_memory/main.cpp create mode 100644 cpp_memory/main.exe create mode 100644 cpp_memory/memory/memory.h create mode 100644 cpp_memory/memory/memory_manger.h create mode 100644 cpp_memory/memory/property.h create mode 100644 cpp_memory/memory/property_storage.cpp create mode 100644 cpp_memory/memory/property_storage.h create mode 100644 cpp_memory/mods.dat diff --git a/cpp_memory/effect_list/effect.h b/cpp_memory/effect_list/effect.h new file mode 100644 index 0000000..a94aa72 --- /dev/null +++ b/cpp_memory/effect_list/effect.h @@ -0,0 +1,55 @@ +#pragma once + +#include "stdio.h" +#include "stdint.h" +#include "../memory/property.h" + +class Effect +{ + uint8_t fps = 60; + +public: + Effect() = default; + virtual ~Effect() = default; + + /* Инициализация режима, установка начальный значений. + * Выполняется единожды, при выборе нового режима. + * */ + virtual void on_init() {} + + /* on_update обязательно нужно описать в наследукмом классе. + * Обновление логики эффекта. + * Часть цикла работы режима. + * */ + virtual void on_update() = 0; + + /* on_render может быть переинициализирован в наследукмом классе. + * Обновление графики эффекта. + * Часть цикла работы режима. + * */ + virtual void on_render() {} + + /* on_clear может быть переинициализирован в наследукмом классе. + * Выполняется единожды, при выборе нового режима, перед вызовом on_init. + * Действия с состоянием матрицы оставшейся после предыдущего режима. + * По умолчанию всё стрирается. + * */ + virtual void on_clear() { + + } + + /* get_fps устанавливает максимальный фпс для режима. Может регулировать скорость режима, + * однако скорость режима лучше регулировать кодом внутри режима. + * */ + void set_fps(uint8_t val) { + fps = val; + } + + /* get_fps возвращает максимально допустимый фпс для режима. ФПС может регулировать скорость режима, + * однако скорость режима лучше регулировать кодом внутри режима. + * По умолчанию 60. + * */ + uint8_t get_fps() { + return fps; + } +}; diff --git a/cpp_memory/effect_list/effects/slow_random.h b/cpp_memory/effect_list/effects/slow_random.h new file mode 100644 index 0000000..21585e5 --- /dev/null +++ b/cpp_memory/effect_list/effects/slow_random.h @@ -0,0 +1,19 @@ +#pragma once + +#include "../effect.h" + +class SlowRandom : public Effect +{ + Property step; + +public: + SlowRandom() {} + + void on_init() { + printf("Slow1 Init\n"); + } + + void on_update() { + printf("Slow1 update\n"); + } +}; diff --git a/cpp_memory/effect_list/effects/slow_random_2.h b/cpp_memory/effect_list/effects/slow_random_2.h new file mode 100644 index 0000000..386f8c6 --- /dev/null +++ b/cpp_memory/effect_list/effects/slow_random_2.h @@ -0,0 +1,20 @@ +#pragma once + +#include "../effect.h" + +class SlowRandom2 : public Effect +{ + Property step; + Property val; + +public: + SlowRandom2() {} + + void on_init() { + printf("Slow2 Init\n"); + } + + void on_update() { + printf("Slow2 update\n"); + } +}; diff --git a/cpp_memory/effect_list/effectslist.cpp b/cpp_memory/effect_list/effectslist.cpp new file mode 100644 index 0000000..409d522 --- /dev/null +++ b/cpp_memory/effect_list/effectslist.cpp @@ -0,0 +1,198 @@ +#include "effectslist.h" +#include "erroreffect.h" +#include "stdio.h" +#include "initializer_list" + +#include "effects/slow_random.h" +#include "effects/slow_random_2.h" +/* +#include "testmode.h"*/ + +using EffectFactory = Effect* (*)(); + + +template +Effect *makeEffect() { + return new T(); +} + +template +constexpr EffectFactory effectFactory() { + return makeEffect; +} + +auto effectsFactories = { + effectFactory(), + effectFactory(), +}; + +static Effect *newEffect(const uint8_t& id) { + if (id >= effectsFactories.size()) { + return nullptr; + } else { + return effectsFactories.begin()[id](); + } +} + +void EffectsList::deleteCurEffect() { + if (getCurEffect()) { + delete curEffect; + curEffect = nullptr; + curId = -1; + } +} + +Effect *EffectsList::getCurEffect() const { + return curEffect; +} + +uint8_t EffectsList::getCurEffectId() const { + return curId; +} + +EffectsList& EffectsList::getInstance() { + static EffectsList instance; + return instance; +} + +void EffectsList::setErrorEffect() { + if (curEffect) { + delete curEffect; + } + curEffect = new ErrorEffect(); + curId = -1; +} + +void EffectsList::setEffectWithoutInit(const uint8_t id) { + if (id >= effectsFactories.size()) { + printf("EffectList: Effect id out of range\n"); + setErrorEffect(); + return; + } + + deleteCurEffect(); + curEffect = newEffect(id); + + if (!curEffect) { + printf("EffectList: Effect not created\n"); + setErrorEffect(); + return; + } + + curId = id; +} + +void EffectsList::initEffect() { + uint16_t eff_size = 0; + MemoryManager::instance().load_mod_size(eff_size); + if (eff_size == PropertyStorage::instance().size()) { + PropertyStorage::instance().load_all_propertyes(); + curEffect->on_clear(); + curEffect->on_init(); + } else { // всё плохо, почему то изменился размер режима + out("EffectList: Effect size differs from stored in memory\n"); + removeEffect(); + } +} + +// инициализирует эффект получая текущий эффект из памяти +void EffectsList::setEffect() { + MemoryManager::instance().load_mod_id(curId); + setEffectWithoutInit(curId); + initEffect(); +} + +// инициализирует эффект по указанному номеру 0 .. size +void EffectsList::setEffect(uint8_t num) { + if (MemoryManager::instance().set_mod(num + 1)) { + setEffect(); + } else { + out("EffectList: Try to set non-existent mode: %d\n", num); + } +} + +void EffectsList::nextEffect() { + MemoryManager::instance().next_mod(); + setEffect(); +} + +void EffectsList::prevEffect() { + MemoryManager::instance().prev_mod(); + setEffect(); +} + +void EffectsList::init() { + int eff_amount = MemoryManager::instance().get_mod_amount(); + if (eff_amount == 0) { // нет режимов, значит создать все эффекты с 0 до конца списка + for (int i = 0; i < effectsFactories.size(); ++i) { + addEffect(i, false); + } + MemoryManager::instance().set_mod(1); + } + setEffect(); +} + +//перезапустить текущий эффект +void EffectsList::reloadEff() { + setEffect(); +} + +void EffectsList::onTick() { + curEffect->on_update(); + curEffect->on_render(); +} + +float EffectsList::getCurFPS() { + return (float)fps / 10; +} + +bool EffectsList::addEffect(uint8_t id, bool is_init) { + setEffectWithoutInit(id); + if (curId == -1) { + return false; + } + if (!MemoryManager::instance().add_mod(id, PropertyStorage::instance().size())) { + out("EffectList: Error adding effect on memory"); + return false; + } + PropertyStorage::instance().save_all_propertyes(); + if (is_init) { + initEffect(); + } + return true; +} + +bool EffectsList::addEffect(uint8_t id) { + return addEffect(id, true); +} + +void EffectsList::removeEffect() { + MemoryManager::instance().remove_mod(); + setEffect(); +} + +void EffectsList::removeEffect(uint8_t num) { + if (MemoryManager::instance().get_cur_mod_num() == num) { + removeEffect(); + } else { + MemoryManager::instance().remove_mod(num); + } +} + +void EffectsList::clear() { + MemoryManager::instance().remove_all_mods(); +} + +void EffectsList::getEffList() { + if (curId != -1) { + uint8_t *ids = MemoryManager::instance().get_mod_list(); + delete[] ids; + } +} + +// getEffList +// getEffAmount +// getCurEffNum + +// setPropVal +// getPropVal diff --git a/cpp_memory/effect_list/effectslist.h b/cpp_memory/effect_list/effectslist.h new file mode 100644 index 0000000..d4907e7 --- /dev/null +++ b/cpp_memory/effect_list/effectslist.h @@ -0,0 +1,45 @@ +#pragma once +#include + +class Effect; + +class EffectsList +{ +private: + uint8_t curId = 0; + Effect *curEffect; + unsigned long prev_micros; + unsigned long fps; + + // singlton property + // Конструкторы и оператор присваивания недоступны клиентам + EffectsList() {}; + EffectsList(const EffectsList& ) = delete; + EffectsList& operator=(EffectsList& ) = delete; + Effect *getCurEffect() const; + void deleteCurEffect(); + void setEffectWithoutInit(const uint8_t id); + void initEffect(); + void setEffect(); + bool addEffect(uint8_t id, bool is_init); + +public: + static EffectsList& getInstance(); + void setErrorEffect(); + uint8_t getCurEffectId() const; + void setEffect(uint8_t num); + void nextEffect(); + void prevEffect(); + void reloadEff(); + void onTick(); + float getCurFPS(); + void getEffList(); + + bool addEffect(uint8_t id); + void removeEffect(); + void removeEffect(uint8_t num); + void clear(); + + + void init(); +}; diff --git a/cpp_memory/effect_list/erroreffect.h b/cpp_memory/effect_list/erroreffect.h new file mode 100644 index 0000000..de070f9 --- /dev/null +++ b/cpp_memory/effect_list/erroreffect.h @@ -0,0 +1,13 @@ +#pragma once + +#include "effect.h" + +class ErrorEffect : public Effect +{ +public: + ErrorEffect() {} + + void on_update() { + printf("ERROR state"); + } +}; diff --git a/cpp_memory/main.cpp b/cpp_memory/main.cpp new file mode 100644 index 0000000..1ad7291 --- /dev/null +++ b/cpp_memory/main.cpp @@ -0,0 +1,227 @@ +#include +#include "effect_list/effectslist.h" +#include "memory/property.h" +#include "memory/memory_manger.h" + +void setup() { + EffectsList::getInstance().setEffect(0); +} + + +template +bool check_mod(MemoryManager &mmg, F func, uint8_t req_mod_num, uint8_t req_mod_amnt) { + func(); + uint8_t mod_num = mmg.get_cur_mod_num(); + uint8_t mod_amount = mmg.get_mod_amount(); + if (mod_num != req_mod_num || mod_amount != req_mod_amnt) { + printf("Mod Test failed: %d:%d / %d:%d\n", mod_num, req_mod_num, mod_amount, req_mod_amnt); + return false; + } + return true; +} + +bool check_mods() { + MemoryManager &mmg = MemoryManager::instance(); + + bool flag = true; + auto add_new_mods = [&mmg](uint8_t id, uint16_t size, uint8_t val) { + if (mmg.add_mod(id, size)) { + for (int i = 0; i < size; ++i) { + mmg.save_mod_var(i, val); + } + } + }; + + // memory rebalance test + mmg.clear_memory(); + add_new_mods(0xf0, 8196, 1); // used: 8196+ + add_new_mods(0xf1, 8196, 2); // used: 16392+ + mmg.remove_mod(1); + add_new_mods(0xf3, 4096, 3); // used: 20488+ + add_new_mods(0xf4, 8196, 4); // used: 28684+ + add_new_mods(0xf5, 4096, 5); // used: 32780+ + add_new_mods(0xf6, 8196, 6); // used: 40976+ + add_new_mods(0xf7, 8196, 7); // used: 49172+ + add_new_mods(0xf8, 4096, 8); // used: 53268+ + add_new_mods(0xf9, 4096, 9); // used: 57364+ + add_new_mods(0xfa, 8196, 0xa); // used: 65560+ должно не хватить памяти + mmg.remove_mod(6); // delete f7 + mmg.remove_mod(6); // delete f8 + add_new_mods(0xf7, 8196, 0xc); + add_new_mods(0xf8, 4096, 0xd); + add_new_mods(0xfb, 8196, 0xb); // out of memory + mmg.remove_mod(); // delete f8 + add_new_mods(0xfb, 8196, 0xb); + add_new_mods(0xf8, 4096, 0xd); // out of memory + + flag &= check_mod(mmg, [&mmg]{ mmg.set_mod(10); }, 9, 9); + flag &= check_mod(mmg, [&mmg]{ mmg.remove_all_mods(); }, 0, 0); + + flag &= check_mod(mmg, [&mmg]{ mmg.add_mod(0, 4); }, 1, 1); + flag &= check_mod(mmg, [&mmg]{ mmg.add_mod(1, 8); }, 2, 2); + flag &= check_mod(mmg, [&mmg]{ mmg.add_mod(2, 4); }, 3, 3); + flag &= check_mod(mmg, [&mmg]{ mmg.set_mod(0); }, 3, 3); + flag &= check_mod(mmg, [&mmg]{ mmg.set_mod(2); }, 2, 3); + flag &= check_mod(mmg, [&mmg]{ mmg.set_mod(5); }, 2, 3); + flag &= check_mod(mmg, [&mmg]{ mmg.add_mod(3, 16); }, 4, 4); + flag &= check_mod(mmg, [&mmg]{ mmg.set_mod(2); }, 2, 4); + flag &= check_mod(mmg, [&mmg]{ mmg.remove_mod(); }, 2, 3); + flag &= check_mod(mmg, [&mmg]{ mmg.remove_mod(); }, 2, 2); + flag &= check_mod(mmg, [&mmg]{ mmg.remove_mod(); }, 1, 1); + flag &= check_mod(mmg, [&mmg]{ mmg.remove_mod(); }, 0, 0); + flag &= check_mod(mmg, [&mmg]{ mmg.remove_mod(); }, 0, 0); + + flag &= check_mod(mmg, [&mmg]{ mmg.add_mod(0, 4); }, 1, 1); + flag &= check_mod(mmg, [&mmg]{ mmg.add_mod(1, 16); }, 2, 2); + flag &= check_mod(mmg, [&mmg]{ mmg.add_mod(2, 32); }, 3, 3); + flag &= check_mod(mmg, [&mmg]{ mmg.add_mod(3, 20); }, 4, 4); + flag &= check_mod(mmg, [&mmg]{ mmg.add_mod(4, 12); }, 5, 5); + flag &= check_mod(mmg, [&mmg]{ mmg.add_mod(5, 16); }, 6, 6); + + uint8_t* mods = mmg.get_mod_list(); + if (mods[0] != 0 && mods[1] != 1 && mods[2] != 2 && + mods[3] != 3 && mods[4] != 4 && mods[5] != 5) + { + flag = false; + } + delete[] mods; + + flag &= check_mod(mmg, [&mmg]{ mmg.remove_mod(3); }, 5, 5); + flag &= check_mod(mmg, [&mmg]{ mmg.remove_mod(10); }, 5, 5); + flag &= check_mod(mmg, [&mmg]{ mmg.remove_mod(5); }, 4, 4); + + flag &= check_mod(mmg, [&mmg]{ mmg.set_mod(1); }, 1, 4); + flag &= check_mod(mmg, [&mmg]{ mmg.prev_mod(); }, 4, 4); + flag &= check_mod(mmg, [&mmg]{ mmg.next_mod(); }, 1, 4); + flag &= check_mod(mmg, [&mmg]{ mmg.next_mod(); }, 2, 4); + flag &= check_mod(mmg, [&mmg]{ mmg.next_mod(); }, 3, 4); + flag &= check_mod(mmg, [&mmg]{ mmg.next_mod(); }, 4, 4); + flag &= check_mod(mmg, [&mmg]{ mmg.prev_mod(); }, 3, 4); + + flag &= check_mod(mmg, [&mmg]{ mmg.remove_mod(3); }, 3, 3); + flag &= check_mod(mmg, [&mmg]{ mmg.remove_mod(1); }, 2, 2); + flag &= check_mod(mmg, [&mmg]{ mmg.remove_mod(1); }, 1, 1); + flag &= check_mod(mmg, [&mmg]{ mmg.add_mod(1, 16); }, 2, 2); + flag &= check_mod(mmg, [&mmg]{ mmg.add_mod(2, 32); }, 3, 3); + flag &= check_mod(mmg, [&mmg]{ mmg.remove_all_mods(); }, 0, 0); + + flag &= check_mod(mmg, [&mmg]{ mmg.prev_mod(); }, 0, 0); + flag &= check_mod(mmg, [&mmg]{ mmg.next_mod(); }, 0, 0); + flag &= check_mod(mmg, [&mmg]{ mmg.remove_mod(); }, 0, 0); + flag &= check_mod(mmg, [&mmg]{ mmg.remove_mod(1); }, 0, 0); + flag &= check_mod(mmg, [&mmg]{ mmg.set_mod(1); }, 0, 0); + flag &= check_mod(mmg, [&mmg]{ mmg.set_mod(0); }, 0, 0); + flag &= check_mod(mmg, [&mmg]{ mmg.remove_all_mods(); }, 0, 0); + + return flag; +} + +struct decimal { + uint32_t bits[4]; +}; + +void decimal_print(decimal val) { + printf("%08x %08x %08x\n", val.bits[1], val.bits[2], val.bits[3]); +} + +void decimal_add(decimal val1, decimal val2, decimal *res) { + *res = {}; + res->bits[3] += val1.bits[3] + val2.bits[3]; + res->bits[2] += ((val1.bits[3] >> 16) + (val2.bits[3] >> 16) + (((val1.bits[3] & 0xffff) + (val2.bits[3] & 0xffff)) >> 16)) >> 16; + + res->bits[2] += val1.bits[2] + val2.bits[2]; + res->bits[1] += ((val1.bits[2] >> 16) + (val2.bits[2] >> 16) + (((val1.bits[2] & 0xffff) + (val2.bits[2] & 0xffff)) >> 16)) >> 16; + + res->bits[1] += val1.bits[1] + val2.bits[1]; + + decimal_print(val1); + decimal_print(val2); + decimal_print(*res); +} + +void decimal_mul(decimal val, uint32_t mult, decimal *res) { + decimal tmp = {}; + uint32_t tmp1, tmp2, tmp3, tmp4; + + // eeee * 5678 = 0000 0000 50B3 F390 + // ffff * 5678 = 0000 5677 A988 0000 + // eeee * 1234 = 0000 10FD 4458 0000 + // ffff * 1234 = 1233 EDCC 0000 0000 + // --------------------------------- + // sum = 1234 5541 3E93 F390 + + for (int i = 3; i > 0; --i) { + tmp1 = (val.bits[i] & 0xffff) * (mult & 0xffff); + tmp2 = (val.bits[i] >> 16) * (mult & 0xffff); + tmp3 = (val.bits[i] & 0xffff) * (mult >> 16); + tmp4 = (val.bits[i] >> 16) * (mult >> 16); + + if (i > 0) { + res->bits[i - 1] += (tmp2 >> 16) + (tmp3 >> 16) + tmp4 + (((tmp1 >> 16) + (tmp2 & 0xffff) + (tmp3 & 0xffff) + (res->bits[i] >> 16)) >> 16); + } + res->bits[i] += tmp1 + (tmp2 << 16) + (tmp3 << 16); + } + + /*decimal_print(val); + printf("%08x\n", mult); + decimal_print(*res);*/ +} + +// https://okcalc.com/ru/ + +void decimal_mul(decimal val1, decimal val2, decimal *res) { + decimal tmp = {}; + decimal_mul(val1, val2.bits[3], res); + decimal_mul(val1, val2.bits[2], &tmp); + tmp.bits[1] = tmp.bits[2]; + tmp.bits[2] = tmp.bits[3]; + tmp.bits[3] = 0; + + decimal_add(*res, tmp, res); + decimal_mul(val1, val2.bits[1], &tmp); + tmp.bits[1] = tmp.bits[3]; + tmp.bits[2] = 0; + tmp.bits[3] = 0; + + decimal_add(*res, tmp, res); + + decimal_print(val1); + decimal_print(val2); + decimal_print(*res); +} + +int main(int argc, char* argv[]) { + setup(); + + decimal a = {0, 0, 0, 0xffffeeee}; + decimal b = {0, 0, 0, 0x12345678}; + decimal res = {}; + + decimal_add(a, b, &res); + decimal_mul(a, b, &res); + + decimal a1 = {0, 0xfb18f5c2, 0x3e93f390, 0x12344566}; //0xfb18f5c23e93f39012344566 + decimal b1 = {0, 0xffffffff, 0xffffffff, 0x12345678}; //0xffffffffffffffff12345678 + // mul result 6b899db0 9183abbf 5c88cbd0 + + +#define UINT128(hi, lo) (((__uint128_t) (hi)) << 64 | (lo)) + + decimal_mul(a1, b1, &res); + __uint128_t val3 = UINT128(0xfb18f5c2, 0x3e93f39012344566); + __uint128_t val4 = UINT128(0xffffffff, 0xffffffff12345678); + val3 *= val4; + val3 = (val3 << 32) >> 32; // reduce to 96 bits + + + //check_mods(); + + /*EffectsList::getInstance().onTick(); + SavedVar val = 1; + printf("a = %d\n", val.get());*/ + + // EffectsList::getInstance().nextEffect(); + + return 0; +} + diff --git a/cpp_memory/main.exe b/cpp_memory/main.exe new file mode 100644 index 0000000000000000000000000000000000000000..3b4f8760174591b07815c77bc5f42cc58dd7ffe9 GIT binary patch literal 411317 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%0AL*4Qnz8+LpjNaw zc2`&1kgz999$IA9%BO-|aU8n7xRO0W>$M#yIU6w?uVt`85J4?{2YPNVE~yJJ0n2Rj z70A)GfQ&m+gl9;(uWxY95V-WlqKc?FGl6=xRD(<>HX(_cPFfPFz(xH?sIoLJnS4<8 zX@G6Qr85JkH`K&}2>K{r?z3>SO6*_z{!!oK4Pl%Z^y%o2h3eZ&Ge zc0RcjeI8sogd$Le&E*?UTM(Q2#y8~}mm$Upi&XgQ{g}OC&4~% zHy6-)H=*oTq3S!h#Auff#%NbQ{Um)-jdp93lhHD*oHiMawFAx-(XU{_Ih>0zy&1^{ zUJuv%!n{b$NFNGZ;B~X-&+paE_OVVeQ`8@F>N-w)!GBw*PNV-sv_0q((P8yyfd-u) ztrqu^=zZc|9{sksS4LkI_fYhGaj%LFX(0X8(P`pd8|@JH`sgRcy)k;HxHm^17x$Lv zFU0+HG;0_6ZjDY7_m1cqaqo=QiFt4Sf|i*)=;m z)ZmLTMz^VR7VdW;gTSh{40S4R%=<0g`xbTf4orgbJ#WnWjo$luwE*5X^F43O`?cQt z2DJs=U*~(?nD?u^_jPJ5ykFsa-kA3bz4!aoRCxc3?|Eb1&-30lt3r5>o`d(iQQ37( zd)1&X>-V^CZdOd^K zwzck4`Il(IS$Hr-_I(df5;Z7f7bM9{c^!Ye38o2a#~yr|=Kr^m%O|bhYSL*WP4b+N zL@RcN3x!f$-_)$~?=j^84;tih55!R1nWWGd_a`wL*yQBDqbc~HF`i3eXo|liDKy4k zJd9vNb8BlmrVg~OW%8(D3Kn?bF+?Fhkw#ibBAo{sxa~y#XcK5YnRsq+flTaZ!r?wb z6Vq3?U>Y%NWi}=?HU&ldl9>%{wb*jJqp6|0UKJpJe#M1Vns7sJ!a5=RB?+Ys)P$A2 z2^)kkvd|4o*26gveYz%`*VMrU7M%zVzp-bMO43WijbcxRO6KQ$h^5nTh`~5^(-0OaEEo=@CdqjBD;$n;3@BvAP zZt@dpab+Zu;JHLs`-!x;lSw3*BkOU-=YyIA@u^3dwY+mZd7A2hG%79dvz|arbzd5l zmiTm1VhEp@`QO)6b7}Fh6DcT>{#H_;5IvPjq~#4=>;^DPh@MO((&COMkq%%z#r;|n zl{a^YMPBQe@jzFSl-RdVpN*6#6*TsNm%}tnZ+n}3RxI6MzLlpHY`iZ@|>oQW-QP( z`Wz&sFLH!(a0;bS7ST za!r{$>bB`it|rPQdc#ko+oqRXI1uU1WiZCmpsclQ^1WoR37Ke9=S6dV@J>zNVPRb#kog zkX}^e=Pr_|c7%3L8ZF|UQ66298u#_>XpA;!+F5weh|@@=!zcu6k^~xNy$hp)Ai$Y~ zJ2b^?K52}$6bz7@>Lbx8SNl-*x8Z}U-6g@3MtRVOA|x$75{>dw3d-&h9Hgje%Fv14 zU7~T`Pr)J4xqc#zR9xYue?wi9pwFa$CXx56{6}0BFyw2X|DFPkPY}FP`49UD4Q$cM z)LhZPqlEIG_XzPJuA^}c3H_lL!XNku4Pr+sV%7`Hzu|$pTsC&MwW6D)A#Y28)Zy3U z0Y7;iDjU`@&yn1#Y)ri@=pPL5p}P^LR{2AwBTS?JPAgqzl#!A&nbgVPX~JLm2~F^- zQ(#rF313D#RsKH=sXXfL>pmCKBYN9Uqh{tE>nTL zlY#@1Y9EP48Bv)Gp(JBtl4Pfj`Gq9W6{1e*Iv0 z{X`8i8233pl8(pAG}Rn8zKQjmA4`*6ltQM_{_de=ZD9B47EM#$QeV@)vzwctNY*c0 z3K zm&|Ob-{0BUx({d18tiA%u!a1w-sEV^sOkG2x+GxM(?oy4sM21)8~Ylt_oJg>>RJ;G zMsFd0KAqU;y^X{aVaf-<_H?zX{6A=V*jDnn-W?ITECt&P^|(C0OW|p7M}{6t5#jPk znSNh$%w|5)%KzFGpn^Jc{liD1(=W6xsjbd4v52lVO-W`PO(l`e?c%HQ9Y2vqs!Aet z)YdfbY(|SQR+G&&9`((Vwa#(j^w@FGBOjAW-q0-Rb)O6DCkOa!O+820Tg-6S%N{nz z3NT7aRX{v5h1d*;Wv_SBOA~jRx^|@@*3UXFk0Y&)1D9-B3Ym_DtuCYrVk4;*baS?8 zsxo=hSReCYX`;=3B8_y|LmKK@Wx4yQR93ZfceKv#DcFh18uGmJI%tQTnvM0_FHC*e zHoFQ8`SDcR(+#7o=#Uqua?c|rJ7#ffEu5`AU={LO>t3$zYe0T)sjc@-M|rb%&f3s| z4cK2pueDqk8t^&b$U07$L>pS)A{%x!HOR(U)8eVRIjv4NxXntWt>N$L0a02y=lbl?KRzUTq0)UcXc+ZdE_gdm%^7uvscx*uQe0qm~pDQ z8oEw0eCfuoRKV>75{xxQ&Y;f9R z>_o+ErhNs>0QG-07P4ZKqzVbst|}8Qu6^_NMM-XKrti4V5Cg^0G=0{cFG$G4kp(nR zLBf~%X;ODjLv+G=i}~hp$c%O%xAQ>+q(+z@+aF)+)&jzfDOqyuy5@Xd%3*+Fa}$XZ)uPaIWHKjFj6nVAF-<4Y75 zbvEH;<1vZr<3Q4e=B#EHcOQT>+4RSl8#aAtk);gU(eRA5TrZJ6>l?NEBM|AsJDNzb zT;22GWidumf+vQp+6s{2s%hR=)7_D%=OCx*`q22;*e?~sr@y;p0Yj&{|2=3whJluC zPYcS*xUpCG07`;viBvGBjQf~L=cVBIvOl7f4&xe?f(2!7;eM#z?#ovzpd(ap_P|%c zU!eELE;z9C2|#*4E|koT^kf_!^ZHq zM1r-PVr1n}XmYUhXXtW<+&y}ysL)!yK;u7^U0086L7CPq__>3^SBqS39ub-nEPdWD zclsg~s`_XIw{=1=;yQ=v;f8c|sE*1?L3Rid>DRsMQhy1wu}Ork|-eP~ar!i@A8 z$7*x_Lpw}ZDLx(5R*XN5YU{;UquP4$wdlExXnY3!K1!=ad(i7qZO!<{s2DGbao^hi6F{xAfLS7P4;?JFiO!kJ{yiqG(~igYgnS z4Sm6A!D;P%Hq#Y5tJfYgBo&*MCuEBCnEK-LBis8Nsco!jS1~)>(b(GF8TY(70*J4c zUc(Y9maRQ|zX@|FB4&lm7={7P(!d%o%Q(U;_t{x;pY({q2n^#Pb*)_(I)JpGdl#gQ z5fe zuI6TJ6hB!Nn2qU&7f)m4?2gAq+-1c|O7QiKo;CNE?^N~Z&7F{tg%sKg7O{6*16WA1 z(_Yl6&sn(~GmOPliyvbI%rb`qCsxqK_+a=(P8j|GMCNdJ=rDKpj;uqECr7pvVh1wk ze*A>_IKvxaQ#qswb9?g$-{f#?F2bK2e#k|*-E>6mxN~mgFZ^K-J8}tra=5+dRQ^P` z!)%0iIo!}LlE}pi-c9AMSP}l@aEr4rH(Jc$#5g8d90|{S5xN%kv>$)&)>?4;H z;Aj1C{BTpXseBQcjJpW$a_AT40^zCLDk+jZ44Z?uqe(9ql^uS*4h!%3ebb>v<&2Tq zvzYHj4Pf!>bpVR=gTD=^@8b&m7|g0o$Oj&c95qEky$kAKt>LhKNwz)Q!DdwM&`eVF z_BYsm7=*LIwm6j)L+d=!0@K{1^7t*@OH1DoF@j-Dz*hD06~{4LHEL)i6IV&8pQ^%F z*{Fx0z!RPlOnEf{WlhFVJ=}Uk;jqle_&lU=4u8VT z6cJ{t2p@+g(72(rWshDkh|3n@n2+gHqZj5Bpp>dOM!nSNMe-0-1)6m9;;h%fJ~Ywn zvMwD&3wXo2EK-F_ z{!8{8K4q&ymlS+Be+4+yG8f|{`>|2R-BvCixyZt*!4F>~_H;F2g_^v-N5ALd3-5hv zgSy`uv=(e!i=oG~!PeTO9?;ZWnvwKwR$sDW3?5f%7;jjc@&Fk16)TU`?#X=fEN=8q zgb5)(e4%m@_}xo2xyB<&r*aVo>JclCX~1P4(zDmy`&?`mTi>!*J!<8>07fnbF`^l9 z>M2WNpKC*8K1fdr}%c_^{@yy=rg8F)o%xP#We`imc3R?C{ja(hBX7wxmhCS7z1*}>9+Ad++%x-Kt zk&eR#^}1EK1rpf9O_I3LSEt^#CbCfuHKn|^$4qqYgVOexsRLQ4>@hP2$_k?~a~xUW zG-j^DavzIfB15`1=*NJn+*w#g>T+khanCmH&Bpz@abGd+zr>yIuxQ$2j(22T)R+a1 ztcx16(2;dfV-`EI9tkcNP^%vwj*M~zwUv?DXvV>URlK5EQHN7hG;sd8j})R+?-W)^$QCWkq~9<$kD@)t@- zQDYXiTDgqWLfyZkPn#863m-c^K}P}H?F<^+vyFSRaldZdSB(2FaVI)k7_|6AhpU3@ z_#}sGf9P_Bj~$=va2*d_uGX>RQyi|zq041Ac6_SCMKyG}ZpMyJbGSZ+E|93qFA&W_JS4EXiH9)=Hhf|5pKtq zJ2d*HOS5jeG~Bl1D;%0;)1~z_T^d;1@d}4F)O2YhO_%1+c6_Bn%VxSXSEf5@3EfrB zDaJi$-225{?Yv}Mn)2H5)ecQ`?Rcd_(_6YUsHICYSv$VQq46tS8nx1;v8o;4;Lr$_ zE=^4B_(q3Tq;zR0YR9V_+Je%hxu+dJ!J$PbU7Bs$@oI;*nRIDROS9wK9h#oerEQrV zKh2@>7+qS2+3|!!^Dnxz?y}=M92#%YrG1tiuW@LCMVEF~c6_HpV=B6|n6l%w4$Y$I z((=iU*Euw9qD$)~J6`Y5Jc%ytkL-AZLjxnaG$69$yByjE(WTLk9dC4K%|n-lI(EFt zp$!gQ8rRtI(;XVj(4{Sm9Y4dN!3$m5wAk@xhh{5uX_R8ecRRE;p-V#&JKo~ZMuaZy zKkRs`Ljw=GwBg9%z)qNhT9Lywc;w)Jn8VdFqqoo7&?@-7Vw@zhEI_$c@k*&lB}?_7EJr zsiIqhQT-enQcC(0gHt&*XdfyVp1^cq7lt;XCk69p*uoPKXVT_ihtXi5r@?Qbfj5jY z2xAJJG%FZoB&)&AS@`JNqtxLWwC<;SoTtEdAmjCr))Fz%f-|~C@Q^7S4O+_Xt$iqV z=6uk7jI*mM>yyKtgf?n3ljLg#5J@P1=gn}KEOJ=mH0~IFCivdGR!dFld!Z|}bSH^% z>=w6oh~KE`PBCCs&x&?zRKg?(>1hKHz$< z9>Y|xHn@(#4IBv1atiSRYK~!Ma|xn`Ah^&T;xOCUhjvIXT>>|lUmR(Ixs4p-Xq3@r z5~bE)SUq2b^2acU(D|b+Sd@}K+JfaN`D0ITNJ{?L1B^(qVFI(%h8A~<4MZehSP3WH^NrD5<5MOgFH(Gi5vwEg>!VHY%G4K~_B)8*m%Ojqr>1SOY@w`Ak>+%@* zt2KlhlcPMU!Q32w4UoZX>EsR;1%N_I%_T9orS2nAaf2E9Tx9JA^eic5t+LG4(*u;L}c@xmokH z52BcR!GybzhM&@?-|FoJ9vW?lIh~4(+ko1ehCN42 zj!o7WPYUklHZj0Ks$*<*{X7bj+JhE3?%zangHvAj`ZC=XCjWzlChKisW&EI9)Km$r zP3D4J7o~tb))b{Sh2zPN<7~VxR{?vGl&is<(%A}z!=`RS&qw~Oo(}j*bgq{Qgo)9@ zd7dIptnswv!Q6D_Do{OU@~NBgt)8rXhIg%(kJ_-40JfqjG`V!qvxW)s7hc$hs+?QI-R4{)?hcSlzr{kaAYzL86ztwW$5b=_V3hwKRj(Q-Tmnp5$wtCncp)VjKIB;~=;2Bi2}M zmjc9da9>sij#6e*6aY3sECJzfS;I^TfKO|4ojIl~{2eP-Cl{L~`kTYb!cVy`R^tUa zM!R8zSPyG7Y>WC`7i$S#wbbu(u`sMdtiYkKoMT?NU{AYmj+`@wA^X6DceRP1@QLQeHk4hGye0v$o-MLn9i0zodUP80C`@rQj3!b&gRu)w$ zsasi0rIc#pv}rth4{dWsiMzwe6}J&*N^)1yeUP)`W*x)H z3gUcMy2*AJaY8TkcKH6n&?ej0Bk8o$%C{-?41CWuMb$0hR?kmeTDPc9qb0|C@V(NM zoVZcl>dF3?i2;^fsX{q(s0=1PSLM&@IT>_EZ*g7qt&Zf`7H+GAT*{40+&1dzhKG8}1XRw%Cbs}A*m~1v| zZZQd1S!1M_*oRh2DPbMS?2L4kGSX=nGL(@cZW;NCE+h7#B~WJCrXmdbLYJ6n@rykT z&vi9)`%AMp+8>YH3jZ57>hPT6QGd;){wau%N{cx=q>A{N0p-*zPA`lIy#lH_!&~O@ z{vnN5&EoV%ekgjjQZH`Mk}mbAPlm6Q9wTYdZwv`4RryU_Ix`{vtiZ}QwuomI~|%|at@+rh+2PN|p7QK2;(mnAkXTT`)o zb7DgUjJB6z1zCq<+g#T{3vkLe$^ZvB)bnEM10CChM3)>z8gz zEL*x^#fgJUgZ1JnyRJC;T z^3^JbPZibG8>{h&NRo{k*QqG)piAY_4V#t2hb* z2@DfRftx&iQ>q5(o7rlxkjzv0+GCy?A|8Mps(o-bjCWf%R<2M5yhC(sLcFNqe5iuL zBY3lE>B@?w)zwQ+QX~1eT;Gr4{gzD?)hjAiR&GEXj;6=DDu#26zCDrmg}kq>SiZ4( zg&N2E$_*NOJRi2WPZRjGvT|L;=F0UID4cj&wr=AxwUl?jsaU#REz|cKtCy~-P|K4q zDpr_0tX8-bNv{fe5z3`mNsr|#)hcn~4PTnJC9u2_klGVc+M71djrug>LT<+^nhtB`0PeBgUpzjV`DP-xW6CskF@ z1CLlvjrCp$)GW%1#!fpDP-(XbdkrsrfLyi3#e18H-Kf)DhQpJ4!AKbrz zEB{Z1bqBDASXfW%*3xN`>O@k$`*yx3=>$m1Zc{_^d6N@}9nnEhS&TsSSHQAa**F$d zA8oUehvBey(X3pv40Yv)Z$|#@MkDk%!u9`0%i(V`!pCX^belo7EVLOT{_kira?;xj ziPmceR`=0ngxxlS^Z~RPKmC7g#{X+G{!g?Sk=rqn5DqdQ2G5U;MNMZV5?w9+R)9X< zb5qMMbYy%U^>xZtQKAGd$YM4Q5rj)EYdV`?za@vpceT;Q4AezaXJmYfU3bq=7Y}d2 zODDS%zXOs8b(y;4v_U#>051iy?>gepWGJ<)f|l3o*tK#`Oo*`)CXIH8t1O zV!@=c;#mhFHYYIC=o!#o0ICBG9hI2{W$ceunMa_=XK~FmO>_VX@Ne95gxfSq)=|H0 z5I^$$;eDW38NXyK9S%52!n7|LE7O;ZmHs7TxnD9+&2Kqu`M+c=?@Pu?{gSbK-!K6z zW$dhJ--V-0@~p=Y;PeQvdejB*X0-4A=<~qI7&lvn>&*X;z4w5RtGM>Z=kDFvy;~%$ zda-0{ZMiqOVjE%HaksFIu_=*_Ey)5GMa4FTP;3)QNMdS8LL7&LR3Oev0c;>nhrGOh z2q}>IcmzoDlJ|a&2`QxgzuzhM-d)K8f?xT4eyl$CYU-IYXU?2CbEaI7IYMhAZ!Q#0 zT<$}e2^UjarF2;acsQ;xc|R~IIlT<9k=+Eq`^zDIWcIpdg_-eH*$*VxSCmx;Rc)&`?OuTE)5N;^;>Vajws6OmK9T3^NH6Bc zEWs@-d&GJjo(aO|V~u+rzR%PxHt4vj*cxmRUe$I%YuC<2_yUW#puT|E*2m`K@W7_c zxN-m=Rqep`&kJ=#gNXKDgNy3ptZ;v^VrTs@)-C;Oyxf-Nb&J-CSXtGtP>z?zup4RV z+18D5nm7-O4@Gdoz1fHm9j8WOF}nH%kRaiTMlD`+9KW?NrcTFreoV`rZCLgnH&(Hf z+gNXNFnq5VZ^9S*;lu#n-QFT54vGUM47Oj_6RCohmOa=UF3u*Vm`|D2agVHuZh5_CdcUv%A6+gM`3rv)F1do8e=| za2$p(;d6p;+?CP3Td6nOdb+oC?d)m6O3>z3alh14-y4Kq>D;71NT+24U?M6%4Tt9YEpUmNfDW9=3g-reo0ZxVk%T|2 zej;=77*w(ScuGV&4 z+IEK|5vz5Rf(zBpx%+;UR!h@^aJHARC7sn=i3~RxKF_L9b^eaDDIc?xk5S6Uq^MJY zTA8c?q#_){SXJ;z7BrZ}bQ`4lq9(PyD;^?1ZD8o}a?=9ys2OC-Vz< zGQY!X2;m$yaD2yD3vjvf0LEDb^%(L!NI-=8EjYeO51~E-$M^6Q6DnEo7xWPN-{JT( z9#Qy0n2^32v2QrYb9x8wUtl%v+jt&ZKq0Vs=k{DR;-FzRt48cbYg3^)0!-Y~i^+}* zD2wNhz~moL8(y=L@Q>*SQ7tnauPROwcm;UJ;(sD*!R75#96$-u0YQ~4L|DSDs`q<8 zg7=D)4jvT>-i*{~9V~K!_lg1?oH4;-GWrZjUp(Tp+mVK21+oB7#`MlV8h)O<5KQ6g z!q&o+xE9f`W3}Qno0*~taGv`1dd*C^3VRHPNjA53s{H_?A|Jj5?c%7bpduehe4Dj& zc5K?-DkAq5Gs<(I&rcGOkEW?eX6S>#{bR~k<#e_7@M~0&k1Kz@h;oJ(XTDfV$IeT7 z+eD+A9fR)cC@ZQvJoTI^p=C360JTf;mbgY0nn!#On70w^Umv zFHZxAp^+k1c|9|T#@P@7T_d~e-Fqu{3XqVF~u%1JY`6heKn%pt%MfRE&kR6}P?Ga8QmY)Cxcb@nfowBr5QBI5bBh%sJF&pD%2 zbyTckcFq~Sx?ni{^EO}^FZzz^OH?GMoY9+Wis;c3(C9l=zPPn%#F}-EgT+rFQQ|hH zTyDZ{QDL!Wlk+_#FEh#gI*FRLM&DaZGZAvk*Z#XJtI_omgYg;*!iUWz@vyl>*FsHZ z<)jHDeQeE=3otno^0tv@B6pELw`BoEY92e6Cd!+?4wyEWzYdrzcm7)5vSQ&1%sa6ty=KXLtW7RjwR*+ewO9uf zjW3lqLV1nM!#H7Plj3*f1s8#iA~Kg>7=vka=MF}Tmvy)o3U9_Nq;+$T_JZ5Dsf{+Np0)U|a>m-wR&@(_e6 zl-hJI{-nc_4BNCvhrOc1(g^GB7Jt^hLi#$zU$id+-{$V#?c%T6Ujlzer}&%pm8n?q zckQcyZ(G|YA|+~76{--jbJvcY0^9u9Fg5UF*btH0G;3VT=+^BW_>!THZ!iHn+IEVl z4jQ4eOct^>{Tt*N1wRUbK~ThM)0d)Un?wa+Gg8sm)&9R&e?k;#dqRlh@!B}sWLz|M zOcL%Iij(%KAio9Iz_bE}wyikos=ABgXvLLO zxUQ~C?*V)fS<%tGS?sS%l%otUVxZYF4%8_gPDcnnu*6)agTdN6q1m){t=(yDG;h~2 zWucf>+hMxJ^*XjHh+U4Gz<2a+zoZq5#5d^J+Q>>3fZ{?~Z>&@8Q}0KHbKayQheq-Z zHt*0N8_*Xth4V;qb6wpPAW4D*9-qG$NQoA|qwpal7-$2@V7nN|^d1%i=V4aPeFblB zAo+iG14$v8KfHm2et!cAJ$3_$N@zBa&>h-9LT|8vB;@lqkT5K~fn!-Z!NN#X;5N}3aLkS zJq=2XRRIk$ml^RdNMu>u1sJ&xDW|G=SMs~MuHv8wD zPba<#o~e>sH{rV~&XH;kBlV(@#W|mG=b^MxOiHmF2OIfkOP+>5;XY)hHwhwBiV=Mc zlp>VUEk2hf2PWLlnG`kWwDv6D)ZMdC-KLys9K><0*o;J02}&-k+mGPcSm*R!fx{z` z9#eWR;^n75x3?AB^^@ak8Q;#-w4HkAVyshHV1-3-%?p>1dz1?2e7umgE4DK=L>p8W z?d-Axq4Tl9tn>r6c+8nww{OPvsp-ijRH?ti2s%7!?A}*^k(jjU5?=REaECK$@-Axx zNQ{mYvHB6L*LiWiiuw91GbGq)>lu@H0N(mETtF+=B#^kck|}23@i^kptgOw@0pj!3 znMdcNiqKP9I$JxY%$VL{nciGYObhf2jw8;~ljjSe3i9XNy&p5hsk=9EH3A!!onJT= zy1kB=_L{aG^a1z$%84`Leq{UFA+j^ENlI1zB2z>^wk&^duX;0|>0%M$uwi|9H~;QLPc8y34X@+I*osFiS((?h5u z;Ap^eMmT?z`5lW=BcdlTJyALnj+yi@^HMk#<4N7uA4-aLpEQJySO*BMe$P0aFxaR% zuLX$JgvkP0;9v(+1?+%h8y-bF3n6M97L-MF4^r-8R>Ij2$F=kj(Ocm-h$r<=2Av$* zDMXK3M6Cm~Q@@`$)iB{GMDGTO_Yo!w_!u1b(!&D20LSO>C`2uUsC8IS7SZEK`53bj z&OgKP1U-cFBRHPHlX|>AM2FTP$tJYTZ-G&AC_KMJ+UM}h7zYo(W|kZeM+6Ss`lGR# zuwHYoV;vAJ>euZ=Rjty@hDTvj2}e0S#AXB>4R}_Mx>m;tgO|UaLlBK zaCm#zVmzto1{X{I9*s%CN)yiRm_bKZU2>1Gi>hC6bA!KzFwoCS)vU7~UqC6GDpbLam0x`|$K<;lyK>t;xcDMH(U*AOfP8*?{tzJ0~vxL z7CXPRjYxdmDOV+8WWcbFFMDkf-_SuAT&R5{sM{o{Ee&gF*^LcYn04dIc8r~IhqTSG zGH_R_YIb*RhI=%}wv0Np=S=$wOKpN4fu&63-HG$$9j!RMXGeJq4-OlAc!&748YhNB zA)!7Zexr4fv$k*Qy3D3mDDdL}!ga$m^uNOMLp-xP*Xmx;tO1Mfl_M1o3R*?!>4IK> z=ke6v<0<%vNl%vf6w;Dyy1_LMnD9S=lBq|G#~)Ryd_FBWVXAU90Q^P)SgiomnLN}` z3g#LQm76NmGzxtV_2Ho8HHw1gLh#Q}(HOa&HKurxIvI*q-Q>850vMJh&gRz$I-!b~ z?^QT!9cu{gFm!P49tG?3i0$3&Vr?DOj%Wfgc;=Ksb?y|mr9KNk&Px?6Hx;TtqDM3` zSSl!JGr~=0^m%J>F?<_T$YhaT%Jq}C z!nYkyaaZ>?Xx;@y42;}TAH$}gBDJmLmN_NwHHZR9eN^%Q5|QaI90!&=y-dFtBC8Qk z8MqxQYIGdrK-y_+F6L z&Qx1c1!FNk$~fAJip2+*dtsyP78^Bysvyr2n6cTRXl2-xDn5xJuU-uDQvV-7m#O1` z3)im|PewEG;g0|%r+L!iovz7_OSM1aG*4CjGVQN&nx`p$ zx%SsP&C`{?LiNReQ z;h7||+S3frWHD5G*26OeTlN%5Ho`LX~Aqb{}_EPihp6+VeO(lf)$Lc@mzu!nwX|N zC!Ez-W0|f!f2=(O#>ogru7mQovx`exxFE7itcs_;gBanabp)HY3tz!$*7*)c(+gTJ z>20^c=jz~(N7J+h&BG0so3~}hy6>pw&YQU2TzqrQC+YFG8vh)=s9ouW)Cn)gM#IaoakZ!%-dn7JV2^9` z%WCtm=dZ_70C;vi_8TZcc~rpfoZ&oK?Dg4tT-UM!S4YmpO_Gtv}<}?iOE^ z#pkPTxwxZ!t2iz*i{SG_xla5g8Sj9Xr+|B&n{=&?C3PGsQ4BU^;Mf_ zuIdmCEf1HqtY5JL$3@pIgkgK(+$obWTKqXyvH??6)u++u=i`&m;x94PX@$d;&fabn z_E#Mian^2XR{?*EC5Z!6S#00v+SR&clK8t0l8!PFuIk)OIVp6vjhVoJrAn&O^cT%$NE?y>z3v+zk` zw)P#NZ?ZUB`;OB$MawGBJFKdUI?Ufw11BFPl8=ZA=s?_lf@G4->CglV0%*Wo!UQDEYtpLw11jduKfoc z&Da!7ijf^LL_{)?GvZ~GNR^AW^{m0|)M7@QJPI#P#jAjsDxm(Y2+e2^|7Jl{3$K|c zekKdPgkTeM9&Ks?B~BS73U{?QhZgt`u>gY0z8lVZ-;gf z87R5kzcFB8eg1ZEC0PYe{0V{ErfDVDHk}iFh0A2oF0`#c6 z1n3AY1=DNq5}-ThE&&FZy9BgT-z7k|y-R>@dwaledV;$I=nL)=pikc=K$pEsK)cOd z0zpJ@alitbA_!W@pq#q|7{I#()PH@K0G)mB5=by-=q>^J{iR@f>{75wXqJNM4lM=K z8!QEfeEw1}!@^6!xElaxHapr|+W5%{udyTnaRp1CUMYel4%QQt@pVHJl@Z z?*-q(tYqeN4Z-%wAqB&!>A=|NHC2(J|A43PI5@nJJ~5&NcdP_|R? zvK5CvI(x+<@#Bbi7=fR{lN`B0oBVzVTt5R75%;!Eb)9paMp1m0iTkPz7o*y2avh}1 z0Li;;AYi;~(#|k2{>j97K%cN@h~!s8$s262PM-W>C~i(TZjwm;CKPv(i96F26S+WF zW2X%l$qEz1nsMRFuNgp%3B`TPW+5oV%?`zV#l)%FOcJB#o49YWW!3m2z6L6CS|6W? zapd$)>;DJ?;Inv|O{v^}%-@e!Ky2D+Jy)f96mi(NcOmL@ZF?{MHi3nv`q<{4Gpm1X z+up0asNFM)bBcr+wUdX3>G3BIe^AAlPufI-bBc9f?Sby}iRR2DNP)PWE&Q0C;FuyI z73u913vrLn29bGdq~uuyI*Yb-?6^#HMXKo&&OGJq)?Vli_aQW{E*8=GdLoiRp>qeh znt%9Ch?;>k1-&se9{mMM<rp_O)Aq z+7|KsVTB)1wIqNwt=l(s;%ii`;s-jo3fOg+Dn31|_$t<2c%*u~xjz)4(6DKa4;yxb zL?xTW`%5RD85Tb^6vWoCp3(f47qL&(f1VN8mz~EHB6ffFDV8}Jis+!Jcu96c-)@YL zkYjVIsHr}IL^x0B=q>-&tgNH;K8r!+KS0KW>zOpTAwdn+OmkW5<&c0*-HCoC z27$VqZ8^!tK?GX1c3U^X*YV@tA~DRVU4y`J*sLH`k%I%Zp=>y;JG{0$pZ6zR$mEBI>1XL{eu7wVDtB+~Nd+`R>Ju*B8{+qNxM z>Xg`9N9HcaWZ|zEI}_&Oiz^CAqT3OAB_4$8iN3h9h%_us3-M=!*H61hgI}hhi|=46 znnX)=iW^6;fv!Qqy?By6;>Pg|xC@>;@x<1eoTOaA6~;3Y9w-I@?_t(Ne5HwZtYeTV z&v|b=f%x*b;}t72sfM5)Vkq;~n~=zUc$~M#x#B;JZk^$da0cAiYrN!;g8Pk!?VpEI zLx`QRO-V64ITO2#Haia2aw`-GS{45eufWnb_-}h5DaYwEmawN!4f>1u__*b7F_!~# zqiMHmnFT*w%n5Zefka~NC^pTYQ$jjNWi+iZcX{N2Vw7B&g`;*AwQR3S zk|u)*1ea4~{$il)cQH`v#X$V?7XxM93<3VWi-9tKF)-^TLok1em#Vikt-p zO*8vdPMJFukfB9@5^BN~(7j5TC5|=uK8IRxj_6YA&Rj`1=ZKWrfB<`ri2exu>KqY$ zQTog|BKl)0);~wYAP+%Mmi%)>3`;UBbdHE&X@qsFb42tN(x=W5(U*bGo+F~a1b%&v zh`urv%X38ZRlpaVBVtGuLhLyr`fK3F2+wmwtZ^-)?KvXGH<$o(j)*}cbQX1vXfN`N zfPL|?&1liK z@`RAHV$zq&b|V4W=e6(4e<)Axr#BM|&pvyzo_UD#5?%1Zl@?7+BS&W}2$C#Irgw<8WvEsy0>N zzv-|tC)hIoTy62U(Z-mk>D_*F`Dxqjt=J3kbDg-}!9_Z)LGbfBxB>J40akUkc45=O zj`lU`LxPqPFK8-_(v&zwq8D|dF&dIW$>#rsjvA-)=S2NdM@`VV*7RP|om29!bTqaa zunqb||GSQ!?3~}RW6ESxoSp*xT2~Ca62L{P+Ek(9H#!h|6I7t);coFCI=IQ1zqJ*! zUL0BcPaT4d2o@vpe{@;ce6Vd>{%N)oI&P7Hi$&DUmzniK@mt*_i^FmAahV8q@SoIC z%>g={HrGo!YJDimWcrlirIh?(NLYKpw5lP7*k>V~j4SNS;RfG@2Z6oVF> zeg~4+N(2eS(7G&(uE9CZ^+;CL_Z%nXWmWDuP6qkn#af`uXo_=L=>I}U7e;V(@})?8U%@iBOYaZr$NtM?Ro ztGYVcsR}IW=<4oSyR{dV&#USgO*Y!j!O;7bNe~;TXSoh<3mBrNKjfVFz?r#`1gL(yptI6K%^XeG|OB zzVUpZlO|5XaaSjgD%pc>PDwS#`$72R>5k3VYQCSr)twAxNaDr86BjY|klS z*k^4r-ZTP*30G(F^ey+Sb9?Z%j!U{WbzP3DKewYlA1AyMEqdzmbwb1sw~Jt=R@@{(@GVH2!Msbi@~75v2f+_Yw&4 zwQ91nNz@Ig*449m-CXhY1Y2zgQdQ!qxT`e4CNUe(pZGo|-_g$_I7V;eI9w5)B#QhL zuLRRaHfrp}N+hUqbYi+yVwZ{E0@%FC(PgHcaBRoJU-cpcOf>);>sYEI#Z!fuOA&ts z5?_ud+5(Te3$N?p+mFY+7M@SQ@nL#yhUY7AdPEvmlLEplosQ-YXetFlfO(xB!3yvTS?}q#X zo)js4L{Zu;LObG7B((*_hk-1g9fg#dO7!+e(oTbz+T&_T%KfeZNs9nGg-4OJ9*)6y z_){dk-2iZ`V~LIwU(h5Shs0y>1SFjf-&8z`q+BzbO%F-B6pqDsNK#lO!>9TVlR)*R ztm&o*=kC!+t;ltcBA4P4n=0y|8XrmM`qSNC{TprltB}CfpYEXk=c5dM^Ip||D;#Zj z)UTjr5pMWvUTD;&g?m~zi+yDN!*9`y+U+AcID_VHUAa^2t7XsK1rS|$6z&Itj&_~u zXxAg=Iy|b8->w?@7I@#ypxNkd*Jro{^L}{Wi$_)3mgxNl#-^K0rRnak(w{NtiN7L& ztu)<1r9Te9AHkz4{V*I4;!!_e)a0K}?4$(QXh5G~QagA5D{8K)*%ef?TUGOG0LcYQ zRRNn(J_S5>nF`R|Uj;gC1)d94fbO6ITz>u+JgNfE!SO5}_3Nhsp_8GPu>GFBShwFb zCW~`-0nTa_cZy3!@chj00O~h*RJFGF9aXfdE?SKWSK?9q(L##d2kf>!RBZbrz5Uhf zgeh4(5~>;9LCuB(b{3DS*>pH~e38EdMO?KZjq?p;+LlyeZ5|TL!K3hcGG#@t;7t&-#MZ$OA&k}@2U{|3h^c=CqkCyYT)@atq%j>=a8ZcpQhF)SeTI0TKt zqX<0{jx+G!H-_AE!oX?v(a3NtUWg=f@r>mtjpI}tn%Bd>7LQ`z-Ej2ML-xHEjuxoDDl58*#;BOYNJ;fDa1*fos&>Q~H3huw^6rbIAGK*)D`TM*_u`7CuR0$` zY_tV=5Yf`PSsi`)x>G%lQz}1v`z0-0`hhhOhbZF-WbypCZJTjt=a$w^sOaKxCo>5d z)57Ai{SuAKXPn_*Brd#Xpitvtq6{KQjR8@^oeK)L&pG9Q6}8p*yi-z(+#+gWe!(dr z@*X||W65gnFdAVjjK zukMIWte%SiwR!9y9L^nleUPuuGM&rYjZ%zpAAmRYKDbIbVd-q~FFbIHiukGHguBr) z^KN*{aGtAYOUF)q;VG_|v-cxJe_Nyo3{H2x0+@~}mw**Lbh+h0^1nGMeLmab>wFNW zI8Hi-C_jU~{S+RAsq-#kDeLfCg#22C&h2Vfn=dY4t&$kIqj)MhOjr0O(n{>?YLpNp zA9hwtXFdL%E1eAp;_ZhYcGe@`r%mVl!nD)gIZvQ-K7*&zV6+&;@rMKWueWpB1B%{9 zkw3q4zTpFl9y@10p9-)Y!g90eoDp`;#Q%=Y898O=Oq|xv8DZy)uyf|$gZlUs zT!llX!YNb&cp;+aeX=LmV<)b|_{a z1*dmBeX3T7qj-)RL?)vS{GDG&d^`x#Bk2-izY8Iqcyc@J)^LY? z2cmDpQ~e*NLr^IgXjJ7lqOn~HyFaM$9>4@Cae8~`gLT+%j^|70kyd4W9olJfH}6El zw!GLSRi6Z=ulxbLm zj^c9>j9p2X&LWRVoXD`xC)~Ttek~jCLra!Y6eQe(CV^K6PERF~!;CW?SwCHX6ed8> zd_uWAZYEalPOn`49!w99(EPAfTOznWg}+)%vY@Wwuu*VXIldJ|<#5<3G$*?ctb~)D zl)bFn)t8kIuzf6li&)Ca`sgK9E=X0vX-@z9#!499&*rhxzUGO=Na?P?tMZgHphpo~ zWasyRd4iMjAUxMRp^lsSdGgE?i;yubEG{8+ir-~bW`l+*@+GD_!v=-kdv#QB34i1RmIHGKk}Zp+s%CiNk+JTV{xI4Z`H3pWRmW@ z9Q|j|#ReR@{&x(SN>dz}KW?%TxDR;{c1R2~Zo2Fo)pqb?CSHpC$=I8N=l|nh)lNej z2%m(d-#-aUk39*i5}K2+bcasD(i@zF4f*_&unY^IgdOEyio7g2nP(}~rO3-uoN2+s zJCRo;l+aA-E0I^Ks7zAdh`dV0Rl!GVVzctqBAdAn`CJuHkF5GSl=#}x%sTyQD~*|`&?QCwNDTtSZ-=y?ey ziMN>qqW=Wl;;O>8sfwL2`bz)-w?OYj?S=l}N5cJ0eu^!dF30Z1tvxt+Lqa6nUz!w# zb{a`>t;dGM0qVS13xA(SlXJp-CO6)PzCTVIH!jsO@rvpPxUYcWktL+RdJ%~TBZNzi zu`Srr-rB>@>|%dNxA;)e14xi?A2-0FiY>=);v$;v-e7vA&sbk65U~;Pq+B zyIk(gkl}DiuSuaQa3fMh)a%cPEE6vKbR7dMMlIXBTiSLTqvJ=59#W+owx#sIJGhU< z#3mmdB{BZYGT60(f+Ey*BFgHSz_;-%Pek@qsb<56_H08(_ExKwMv!7(5xJs6r{)|WpxEVzvk?_%k^m=l$I07%p^6c^m(UIU%FxRRB=l{rXgq}Yrt zT)Evc*-%cf4>A$ug`Y@u+T<`5`+YwaHNz}pHzpVzT{vMcj}9h(=+GAIg2R_~mxX{3D<7$c2eBI%q%=;S%M02p}p+k3>F)5*GD zhW8OXWd$&D-(~LC+&Wg-xZkTTjrl2Je~hQ%pn?A=AjNonK};`1#q>f{j0;gQ-d#{%Qe_ zevxq|QUK+3ac4(`VofuVLnlx_>>+Fk#D>#R*l3_m@DqFkD0(DCM;!_LD^TGDJjHWO z%1$8jPNX3!5@V8COp|SKxFAQH3Z#K zA3bbJW#=wFVmyCGl3ElFd=tBbSKrV$VVHvrHYWNuq>VmiJikWT=o7|6nG^k~@hn3! zRyADH*+eB{5(G6p(=OL;7PGUg>8F9|C-9h>&Ync!@Hhg#tO8ln*>f27Z}9&ZkE-cx zMaWm+{T&`v)7h=Irj4dlN{8Y(#`95lVkkVXrVCB9s_9DO;R)U72IHX|i*7d_Rny7r zf6p4W3A$$VqzO{J?r+AUdR_8T&11UPnPz|U->Xr;DG;tVD;qge&P**SodK4MAfzxS z#2tk+wd6)AmVg&zkw|4dx?&Z~ozj`1UEq=?dQ*gTc6AfZBzjBmvAnCQFc>w66!xFF zP*Rg7mr4c|c(Hh!b2Xr)b8xxA$!2E2q*l;36sQ!II%6_ZLMl@HFtKDP3l;)pu(W1`k+w`ZGn=;UIm$;92m>a#B6$4^s8PEai@S({KR zPgJ~P`IV6Zm+zuF%sDBbd$v*&LWs#xaLVj#Je7?uDb>8nv{SO^8dGf*Q!}%JSw*(Q zvN@5>QH6s^9IBI6GGu$gpxkCmGVQ>bN zn1%iXrZ|&?zrjh7=bA=Xrhtw%huYW#@J5qfBrK&ez8lak^g=)kTnw(XD;WBJ0U_jyqWF~WlWUubal(~LGFokDXGlPDWb$38eYntLV z5i6>elrm+Kz*!DGsfmbXQ>H<&!l}S@Oyn{4DhMY{!%#tI8IhZXgwzouH7V<}5mHgh zc^`64bw`_8s(uVHKhx(c2zOJisx(mz;p9tvrERcjLXqPqpr`Hda@%r31RP^zQ8rGz zCg!4Vk#7tIXC(=AlVj>NDHLTW;m1u@oeuS2r<hUC|^&5(2^x$bT)$ovD z?^4oax~hLR$pB1g&(N7l&dmN$5r}y(9;l%>Tf#&c-5+Nv(WHVPes$Qad6wVK)LGaO zS??wbfgL0p+u`XkMR%UF$U}iJo*fJ^=T#Ov9AeJaLkvfYls{VR@CbQsxHSu6sDZULBu5oBWgqTG1a443*VqWJ^E;#nbKw}fNeQcfe6 znSlp%)3_}!H>y|8c;Jr`D}4+VrCAWhfk5d|EN1X)R%ycwt!jhu!K7D(f+bD*)mH0U zWA)0l=$Ya?ot`31H_SR8fTEHPJU<+mE9lm%C?Ah-Gj8ZFN~;|ggaNWz;d~cbiS6_C zMZpM;?MObA6oxh@j`gv}TQ6CmB^XOsh906fPC&y>t+wfe*c6tz8of($JFgDh98Py7 z+QuL1?C1tA3&y;tUq#arnv_^o4+g22m5^~g_<1%G^9!GNIoXrG08izj!}_NEiA;eV z{q*2w<}?BSFg1p^z7i^ zh2{PbU*gs4vC&9dVw}w#PKPR3e%G>gVS9S;f>O>N`C;}Awdg@KUNCjl z)3k}I2HE(T*(TNj(;=1L%)`mTmQa&GPCqLqM&Nm99vJvkxE=V=_Lvyu;}GHq%#Xq% z)i{LobiMC))sML{JxqVMF+nHAFYRXX)>XhDk zmf;=Mus9BDiZR}^Rp-ctcx{g2+;9jskmwO@o)MtgA?yT$G)XZ&YQh(4?EnK~4i;p; zg<;JoZ(P*f{hU0a`4W6_h{)N39Ucg0y3ISBtfMuyB`A|T5;Z;t8nQ7Nr*j~kXo{7#rYi$n484BT^sD?~5L9PzSNq8kK#&Z)#>p>;di}K~lyaT?C&WI-mpcaB ze#ft|f~R%<1CAbG1_R;i0@$WRA>JN@`6>{O;XAogg&UmkT;WEClZBgfPJdK^*hwk1 z!szbr2;pq?`I{ZiFy86pPchyVU;)hhsl~fZnn~aFXV> z+k#xCnz_i{j%y}Tu!}SQ9VTrsy|@!0R(xvt_Z}6ZxhTx)cOg8m?}TICigj$8zonTm;p^WaHQj0@X{+l8x;eY4H6}qD z*Fv=}EJ`wgVhn4ON}z0lcAm;oOVqe+7A+`Zpo-@kk+d342D8H4j1vSqh(Zu z-@i~jrl>U92E1%me6KQS87Sh0N&NgZ3RPtq)5TdqKPaph)*7HH8_}q zv1tx5<*FPoH5r{iVMU!FYs$4G3hZ7(4P=#+G|E%5ErKvWS?;`2@q)HH`8_j4HEnqE z)zVN*SmoAZvg*eq33ilgUR7F83$+Avb{0zGNI)|1lA20e)yv6sp^Aq{n*!9HU@?^0GU#TH8}Vq8Yb)w`D3vie3X%g+_oyNs$#W3&co$!}F< zB^4A|d^Oo7Rh0=eF|8vjySBy7SFE%}3UL+WLm2AS-~;_wE4Vc%9K2%`+|{-&6n9pc zTBDVz90|DA7Xn$yB?_3Vl(@=znitub*XfYxnb{+8EuqfutMaVxPZ!Z9D!&s`7rDR) z8O`Y^59TPPeHwiwPZzn!RK&#E5!9;BMigld)rzy`X{DyXM3tM}wv1JkeI1F4%BEaI zm=+(s?@v|P9B3+56*iE3g+QypzF~+pZ_Dk2)Wh0+^w4;^lL)ajSEK0&D>RpC#qTnz zHQTiMxV;};bB7_aWeHrvg}!A?6Zn0N)*qwnj6thKcCSMu&?NN!2dv6Q!>pdO7T5n8v)&J(SCWhE z3c9DIZD<#PHeH66oWsgnGDw~U471#s3i>M()5M?F3&I8ct>_k7V>UKPU{NmINWmID zjF(haw8_$R@sY;vt4G9A?$p@lRCBXN8J9hcyWDQ_5YB+)(^ZLTL8SMG$f<(ksTwFL znqqqKKi)qE@ovBk z!U^1wAfs&t*XMQgobbMYH&jl*1VG!Ml*^2oH$h0%#lW1pHDh`(F~L+6;!3a9J4ifV z0Z-V5Q;Wjf{gA!E;n-jqP7fIf{+7@~Mp~QT3j)-#4O)39v>SSgf;dHOtOj$1yg0Po zrlcX}Vybf6j-sa8XLr*gdT@c#ZI$wHGf`f^=53}9 zd2PtZ+ORMp%hu(m*7_VQ-`ZF)r|L>zG2|562i^el1=P*f`CJjulRcy#iKSX1rimN3 za>28UTtaUMQ=^}@FNX!8?dMUSZ!DYn?EqCKNixOHP|_sSV{H#fXvWFk1?E!`Eg1-< zGzcsT{J5MJ%vr0+Z^NRTX433#cZLCq&)DJZVRpAwxJ@k9k=%aGVD1Te{k__DH14y5 z%r_=z_DoE_{as;EOKNG9423gO**$kE0CWzd5mv(}+(NVJP)AGI%B3ya7(r2>BiLqz z3O5XNMM}0<+%RG&VnQgGJi**Lr$I_`c^q55)Yclkl|v1Kq&r%)%u0xI!#Ap^Y#EUV zw%b{r)P36K*W*UDQbAN$Hos>FunV+ zUbA@ChpxA|q0U5TXs~q%lV>(H$;TaK_1LC|1P$Du#04oc@~e)i*Q4?f8X0tI%Z{Tc zB(>3KwC+#150tye#!{0Uhdp4SEn(wxWGI@9#q0fbp?zKx)$A%1(3m@DPVyqM4C%l)+=_t#jaxU^W>-=2Lmap`f|9cMIjw8 zP#SZRVKmPXv>;9ibG?4KsC!pK*I| zBeRsLMEM(E%X6^S8(#xa84xen_*&J!7y~m~WtVMDEuqG$Q-ib&!MBq9ov2t2NT!q})_z{(gf;L!dA(J<5fRd3O{ZG$gr6thu_(}qQVT4HU_ zIabAd9bjY*Yib=}l;LSM$7nkY8Lh`^&uoQ$)t+2Nvbv9jqWR1M!A?w{iS*XXJgx}M z>;iQpWtl#01SnQlp;Fb1G~12LXpONtVAdkbRZ7ce#qziiow8VSh?#Y=f_9##uP``wY!XDylvKBFXqJZpIMs4P4T8PI43dEm zjp;h};aijrkz{xU0y0b>y^p`|ApW`qmvud>{%_RII#RqvnXU=6eySfu4*s7g>m zedf}Gjz!TFS7K)~rB*_hX$cK(?K}Kd^r@9rBrEM`qqhmG?yU8_YCnf!AWMlF9-)ZR zhGALRRyIZrAm}vMFQs&%P>(aJAQy7N6gL}({T9Gcfqa==ueH9c?oxWkuna`>FhxG2 zWI@-YG-j{Y`lgxQX;peeAW24QC)QoUk`t^P=63ca>}m?L|5(*DRE%DyDMhzWSl0S7 zGpc!-hIOMwN!v2{Qy@Jwnv{^c3VBdDtA-TA9d_EO#0$@qD3iT9DU;HQe7Y}{3rJqA zX5#>&-VS8NtuLT53e(V(xmY(!Nigu`>}J>9IgqS5OoAQlVZ}LYoUE{|lVgJN38q$W zG3Ecq>84pK(GP`^Y+LN0)cmU~cmZGkSJF+l3=U{nDQu^kvk-fKiJqqh|6)Qmq7p7_~$*YH7fz15!GNQG;_9=%8WE z3(Mc(=6V%kQ)XD5CoBi7QXZNm^{>yyv~bU{DjUPQS$-u3u)62inG*pCIhf`fqjEG9 zRGTs{gr^o;_(q7CBt)$W7#>`$vdYKkVRMKEk~I@BK-eCpO>CzJ)J?0%$_FQ~M6mco{~^|8b% znjSeZt?s`)YwRdK$d2N*Im5RewCZ&KfRR;;B()@46v!m24C=AAAk3e{P@TH*RUFg( zasDPW3X_0C11{l^Pfi&YbhHNB(S}>yb%Zvnj_i+pM&f1+u|Hx>(9G>6YRKy;3A_x9^j#`xZ|;J%EFlA zp{SVq6_n*Z8p?{WPdi6V!oy4mi0*f<@=)F~Sq^Xda#`*zMCsSdG-JI7V(!<)Q7J!I z8jZ-o?!Di_$(kun)CEnx_R!U6zNjN3G57ElJqSy?KTq`_=C6Au#16(7m2&UAg0)tlql^&vH>^hlcjzdVcLCQxqs3e0QmvWCB+==)(7hE6<+*xfh zf<~2R#+)WOs0j(>pe|HrcBw1`>7G$Qk_SN!O&xc?^v;BD18B8G7S5FJO1Rdl#Id&^ zV-w0FJ$l*P3ipSvb}o`dG3S{rD0M4c(mN&#TH$N=aV5^qJ*afE*CY#eBJN^ar%3Ep zDc$6ukl&jqtC0cVbZz6Y{ZZF_#;2k;0c1o|e+hI<#x4N9ZHjTwOzXU*;GpX71I2bU zzx!ixRB_5;l<(ejHP~udS(z*YbaHe;{lCCXPhkF-n|zh{>zsWGuVspNMz5Dndjyw~ zI{yiY&5r*d`)_|8Klx{Lxnf)&G7`@MJQw4+3(rwJzsFMp(XjxJ5(bsq;c|A-do^G0 z#!K9VmwOLipTsNrS-KvlW7`i?j43mLtP@H%4MQWRyE4TeX{D1tOm46CuQ{^UAs@n5d^*7*0Eq4>t59^ ze`?tXV3v`{f9{m&zePqxygxwNF+rMdngjwO^LGyS;;&aokj!>vVBT+%Zcr>kr78_VwtQ2ru)$Z50L~73~p0Q1Lmr@SSElim|^RS zQf|m8@CsA$523(PZ<(BUi-lRTOcrcq#)1uU&_r2#zbpXoT0&kghwKkvfAkFk%})@9zDO;;RDh5f)peB2iaJ=S+_* z24R=V+6~@N@bu(7;KBlb62O~4H-+CxNCdyO>{pq628Zt>S>@44rS3UXNcl1o^^Ai- z)0Ktp{T#&W_RG;4N#_Y!?UsVi!5xaf9WYb{Xw)OK6BTE<-lr_ZhcFL1=S5Izs`kl= zN98n(L$&+m8As)r&&%3f3_1e<5OfOHRGk5_{kdlNc}?u(qjEX`_sUmyv2 zC+%n3#nxk>UoPdjviv$(d<+UkDV|~v@9-Z(Az)0nPQ%0%U}1dl0)yFoN&*G$vQvaZ zo9j3Ie`omV!ELywMs3LOBFJ#rBTZ*eBB}{l!J#2?pDbD?r^DrbN)A~jD|D=p zE6#ng5G}AAmx+OpkgumAX_!Q(rwSZ2g623~Fi#hp0JpSPCY@z6dznmu=^XC`*>pfA zBU4HtA}7lF9yzf|4sDVp6J+KVIkZPMG|8Fk<(W-#Y?EBQRIY83+nVH#Ci#{odHDo6 zuSs4Dt-Yj4W^O%gnQ!&(#|Y?nPZPC*NCV~pZW4l7sdse;iD6-z9Mmr19=ASl_YF^M zm9_11V0u@IE_~8*^5#A6RnOujJ4JUcF30-@Bj}qdkM&^hxSeZXrZLab!uU*DjM@d*lpE&)i}N zPi(V_xwl+(5X1cwa*S1f#>LJe*P|0j%ygBKcu*GIA~TV3$hb@A+THeMCPltC3GpUJ zyxBD%FW!_9ZwkvdM~$QM&EeB%5C7K_!Wt_EVUB+AYBNWF8q@bFT25;vEz5i4@TGDTW7f;z{I^tQE~4SqSVjp{SfF18 z(6PrXAfmmTu*v}|bNB6`T(sFeYq{wuP~Hle(auvM9p@2y-GPYoJmI4S&6I8ew}U zu~L?aHTw291w-6uV0@caX&6-d1ybsPb-b4_vB}Ndx&Mu0P=9?628tD67|*MQ#sFm2 z7$|E#X^=Va#xXelIxy%DROVLT7x zc^S`;7*=?=1U&p4xSaF(YGqg#UsvJf-on@W_)?$p`8-_CV|;xJujr5X`geL?!7Gx) z)v1wcyyO_VX5kf^k5_yJU+egK3tsY4yu4j_C9c9Na*$zn(e+`vK1J8(={iQ&xA02- z7_a#A4Eqnden;1z`BIA{^*anfi(t71Eql}g52&pcLhdq=2OvmYIntftH8ILv@wgSP zarRxC?7@h0G*YUiu8KHh^1l-jRYycssYk2{IZX?)tU9W}MJ01RX(FrrEcFOIyvV>R zhGjV>r?}s6y!HE{X*u5gDs9Y(7i7aBz4nEb3HISOnOP=B?9!&^=jB+29n#|HkQ|S- z!=ZBcE{t4CmcT@y{;N!heR@f3y)1o!t5*&r;x3uqFP;5ze4}#o%F(m2RG{(%qbJ7A zG{#M*S2pY-MiPJ^C46w%7gdJGafLAkTnMlfVD`#c2VjCYOE&c6;SalB9MjLwRl7h*)402Y0B-BW0-HLtbv&9kwu6oHQ%8i`#5 zDgnlE_#%fSM7Q&pOpk-vYB|vZppm-dLR9)#e?c|7>z)!{tQ|0Aozc{u!!tq$k4-+%q;a9)A`g{#APBtE^X!+F3@wK|;F zufnUtF$n2Um;Z-no_Uz`nOo+?_nlkjB^YRKnU}P`xn*9GH#oPnF8nRyzzj8@U(ur@&VzRg5pXwU42`Vomx|MpbQ6L#D$c4XZ?olVDN z>|C_YkwbD~gpFyy4zL3@4@XRB4tfh}%GnpG)}?T~ZMNZQXP>eUCDZ`hX=WDHvh*mm z7OcXR#eSgGl7u4K96R?&g!hO=lPY`jc2gQ6OP)6XN@LnQ6`SB4(vXsx{M&rgLc7fX ziEUGFAz_LWY(C*NaA#P|~}kdxe1-hG%{jd1^2)MIj`HYvGV>t*zqu3a4OwbJ!9O|37&lC zDUqURX?&N79?(frbU&q$l*vG*FBlpQkJvvqk zpu^T&^BiKM`(@@nS$CA;hFg0fk!XX-ywAY@Inqov1B8}Db8Pm%K@vs0v$5kaT8QoO z*E(L09OkttDRa%$^QH}39kq7+>SO&w%BWs^{IIFs=dq@datpE9 z$E>gDJ>Q+SFJOjTfPJ>u+POj2V!5G5)-KbycgxAxV}$7VPsuT&qCrQy}i|2gC{6?+F6Cv;m^sf z(44l>4s7p1MQhv)Oe=A3zt)yAs=syG0rOKxG4rTq)0F0%PEDl>%^^xTh@3R?PA55G z#$uD?sFh1e1>jku8~6ZBULAWRH`dRaPPv+Ym^$>ZAF5m5mZzKT*;`DrK8=1= z|B~j@eR7s&O|`2JYg}k*B`tKGjW*AZktg*6EI9e;>pZ>B>7?GVi{l;%0#vq4`J!I5 zi$fS;#pe!FKKnIxI_Gl4@ta>ic5`eVi`}wQVF#N$7pI&7(3>+Ldd;@WzYgL(Y5R#q z57yRPVq4-p|EkzEp8`9r)Mug+x(7-`k@$c+Zr z>sGgP{u6I(UC}6gN8Z@V+&%bK8P~P`4Nczfx|VkiQe2E@FP=kqzJbS0;H)g3^YEy( ztn3cBoc;7(PuJaexu4+cA-*2x>uJ88FuFwFI~6ubq`;k#Vht@yyD;D>-&8D3@_)mbiIOC#Kn2tcoMIOIuCimj*mP6 znZzz>qfta)NyqMj6}nN&v6^awo{*my|2wJ**;+;=~zLp4XRp$gQ$8f0d~W7XC-%&;v62f*h();jsg?MukI? zT*QfBAaEvXEegjGQLBs{2!9lJrFt0G=^AaiKbUm;tR^(Yq%#IY zD`lTS4XV6JdVS}p-gbcFkQ&d?{MRJMwP^yy6oC%fxy|=HXtoC=j>9DHmvMcr4B||w zl@|Orkp_DU>Wprzk$l!7`9;j@tB%RZ-h^-qY_!q;4BQGImFh&@Nj2I=z*BLv#YMyN z#cxPaj)oOwBy)X5IWJGYit?QI8&W@l)o<(o_l(*DLqKfV{c_eZwPVN8JF;=kZQL?8 z1elp;2x@QQ-k92lVK-2W@HKJrwedk5T=Uq~^>XA5^6Z~mX7#*B<){ngWIv$H9g5>v zSh&J`qwHFAv7vIv{c;)>{V=>>wwR*gshgnFHq&R(1X$f1^aHKanONIEH-g46Y1NvZ ztqFA8PE4+cHK*ZsnE1#HI2`?Xw=8`a6)SQ-wufE#CGhWH_eb%18Gkg)BZn>c0{{S-))7E&GL%shOO=77!q)+00CIFtA0Qf1w z695f-j>YU*h{TCtB_IJ(Ox>qTFs4M?NY?=UqA$w$4`c#|3)=08Q)3G3w5GHF1Gdb1 z|3pwR)f9ZWJVfBF2ur?tTeIJzd5gY?-Za_$RaayL3~%nrLm*Fudy`8!%GG|y{Zg`C zmM+uV5l+hZSz+OL!gA#Uxf5hfqQiEM^e1rHWcSwsryx{K-49%S$Q(UI5j^LumLhzSHc|agenNUHa3Jli7{f1) z9g%Lkx(A_{S0ZdEmW|FicdfKe3L|9JOw&vX(nghP$@s0b(!0-~a#20;Y@4Tl;K zO+pS5%`qes39e!UL{QO)2Y3>XRXjKAs*A^JR8;)x?#3HkR^zR!?&2=r6?Z-UpI6nd zrhB?)dI0zL{~h0g^i;q8yjQPYy*j(QdRI~AXNjYD#*E%o#amMrehj?vPVa3R!vqY@ zY+MIPEn%Z7+|OvWdoy&Fq7#QO@m6WIJS~xDcu^%nZ^>ZG1WHc71C_KQ+#gXZvKH3{ zgsnXBYWj4Th)i{IDpQ%`d*d?`_*;y>Tkt2$71@ZG{yLrhi4LFB;Ts%`?{Ux%x1|=O zk{ZH?H?&&(32C8r#Ppl!!1|=-{2O3aE^`(jrjMb6D4oJ-cIotk)x>rYqQwu^=L(3p zQ%QZ91inRwNT4en+(*;K;jadN(v_AVre8sa>xj0N4)@XF5jy;i4no-?Ru&8NC>bMC zEDOungLYiV8a4|BgOAbdLE9(bfk6{HFwpiC26j}S^0gwf;cv8uL2gOJ znD-WT$G~Q5;*tT!L91b94d@^41xOC6F1QUu4~`rX&OI4*SlrTXmLR5A&>`6R>jCRloZk{ts4otkwjYg6kqT`4ToAHm#di3t6jwt)KaF28Z|S}_XqUDuF?3DcA9~hel{JXmNRTEF={Xj9Q4Sc zAsx+F23CZSZYB3y5@}(VBK3^WZpNaM<9k$Tn zdpbl0;E+p)z34EI4#Vg$h7ObHFr5x(;vlz`iE_5ypgINDGLEel-ZhT0dED69@ZF~` z!Zs|%aUVx!XT6>EG_D$ZZ4}s+{{07pacfbJG=}?*3+K?>V5`{r1>St>tnd->aIdj) z&xOcJIq1QjGuH!!7YmKZ61yT?M-?D09>S&#P2|2ca{roA9!5&0PKx26SU7nUByeC z7#$(nH@x35B1+XZ2V=1a^$`8Tp{j6BOE@&V9TTNFRFRE-acq8YE5_1#1cNE|PlLzbyjA#RNL+p}+Zy8m0h?-5?vLH_dco^GwaS>}(suYdo1+cw5-Sj2-qk>Mh1w+b?zbHt`F$SNLwB@T4^IO*Mv0SXo2 zlrZKlyJ1J0k;d5gTi`HG!|h+f;U&my7%nSvHQtuo9ogCq?_x%bO7zyoUU4!6c9@|C zt5;+?wx7X1JR9*yB{o>0ChQTRV_44%8+#hN;}%Ewu!up+h`llKxh6a)vODGp*#+T4 zj3&JMIu7soXXDps#Wowp`LqGexc+-a+Mz_&1RrhCG1%JFk~X{fYlGV?XbDN1haB3F zBLC801Y_AnCnIAe4&Bg<^uuG~#wWmAk$1y=V_|DlC$2<;dWfIm2*hOg;s1eB$ZC0;|*Ky z1=@(|B3#)mV(izB?#!r+BX@&QO@_c8hlKl1rHF|Em2O;n5&3MwAFzo?#Go5aR^JHu zDfVYVUuPMU9>-?zx_+bN(cxem?15g1UGsLK z2FscPhrf@zRP3D;*)81b3eYA)OGIZPI*WSPzKGCesvz@l4VFA6vQV7Low2Axre?$F zTar<$em*o>MOu-jp~pzc4Rlyxk4#QWnNNJgP#cZEGw~<5v(H0JUrL8nM7xm=chRAf z4$six4|I5&4x&5iWGk$<$0)OC3ozPsYcR%`v<27^#}|Y}Bl!%ch4XpbQ+dXf#+NuDw&a*Pe8@%=uT?yNWHD-H4a7bn1E?a zE%^j0j-u$}7m4`u@IFj|5s&l_<)9eP!IKHp#g?WljDxYC;W+%&;7=4ruO*1-E9tPB zXt&Yf0XqDa4o}nJ6*|04haNg?rGqGz;@P%yeh5vBJGTml+ZQ843zPrB$Shu)r=7ph z;YA9meMP5$Q*M~*>3oI&OC$VGAB;HLw^dm^G&gi&~S z{uuAw7tkEWXo`jpi0D*ss&73LZ6ADkN;q#ms3Qif5_^*w3&MSmqRrkWM$?Ao z8I5PeXc;SVl~WKNI5M1b5_m*@k=--JPIbK^Q!v`iznbbC?pk7)TOxXXob@_^vhOcsZfZfNrsV6eu``v<#e|&V(bMw8Sroen}m_tk?QuxiM}6j##v4}8QF;H zEVfrIIZKsA_!4g<@| zyNbfgiH8Tx3gglGKJcwPpmMRNT~4GFi{X($LKMcGXzWM0y)$T+xD>jAajDRgMqqe*O8y%Yb7n)CW;1{LQg#^7!%|^ud?L~`)Ry5zFZWLOP zdmmsNPFXz$KpLJ?P6vedL%K>^j38h~yevXG`q&Ys>TMi~S}`gz5EF4KPTjaYS=miO z*>&lZT@1c=rLyZT!sH9HU135@Be3>@)b+^)kzd*-vD3LSIi0UWI@j9ifZx}d3|jPp z9ah*z5$SwR>3Ch}zeU<1OuuO;9722A$2cFWyEq{)>K&N@z4HnpnW%F<*=x?ai7ro%|)VrSm;|yr1b`=^;3)9O1SRbI!cK;HyT{gf{20Xm2Uirldy|$ zma7$TdGE>2RzP^~wPb1od(y!C_3>SiZ4!&Yt&vlLU1r3Y(Tdt)NkNa{Sl~XYlQ464z0)D8_o-i0M6aXs{dnaVZ<$aeHanEc`9TpWqJPf|&jQ z9ezi&7wFJU2RVS4XE(X_V#s7t`u?%-##y3^xim1v!F%J|P57(9pGwm zjhNm-howYYNr%;Rko`@PDwBmOr-ifU;7(`^KVjo&r3*&&P(>K!3|o^^Qx${xCDJhV z#h1bGcOw3THr9N^^b6_q5<0A+!%a9Ccj2HvM5np-0J0{f=w0^Hw1eaI$$U#>$8jW1@ACX53 zq(0a+JXlB@yiN zq#MKJa+Av)ZuE5rjDi1-<$pYam}eY7`}`WlO#K?MNA14Ub+*s|IUIFG_46Y}{9|wh z;aY^V!4du^{9k1OS`7UO!O-;9yxvw;J7(|{h%Q2&^i7+rjb*Y;-`BoiVm(*OEi`Y$ zPfl)@Nl(T1GH2Uoc*$GC-718E;)pHg`%1Z!>~qOqWKO_&E*{T$*rvDHxMKS;dm%VC z?+VBCvfhlM(r29M-SJdnG}*ya5u&<-+oC<3`Bi za>S~9YZT?!L3u);bT%T++ay0NsG2q|H*Z;J{4SBu<1YX$%bWbbk3S2xFSehT#UhLd z%s)-C3x7$)KN0+SHvgz3{`w5~mxDiM^B<7Je^v(k8^PaU^AAnpug-u!3p-Qup3F;= z_(K;-0jcai8u*Fe=lz&hJNd`u<}WcOD&% zg@CmAC;kg1|7FSimjv@~0zYq){6ez+&jjnAk3uT8cRju*ng7mU{u1yP+1m}jpUnSm zF#j5I5}W_nB>shxJ#Bs51pd6m(tZaf@t>Ume?FRX-s~9TL=m|BX4(7$K_y&+-EQrl zgqxFuYqxO=lW?K91hTxGU8UZGlW=pBD}h&#u|1^Lyg3~`TVi2E=@T+SYumG7_&ai?U6i)Dygl_Boo z3~_JSxS;a*F+<#-J*EAEHu?W#fYEGCM;Y z*0wTgC-2QrzgKMB*)#`1;C|JZl_PqjVW3gJddMJ|?g;{Aw*KXG*Do^KMa=Xl=<+L3BDPU&vNq>94z@>^5JXO1>=*FaTmeJ{qeVl zNWSx(d>GdnEplAPxcS2*ZjDS<*(r35d%Tc6JTu&F8RCvC$V}d=8RBY3WF}89%nWx! zhPaUL#18E=m0%d2Mp~HwN?ffS-3kz9^aBHb6T2 zMQJg6fz1D$Wd1XP(=P)*Z+-k)GXL*``8&YRI~#W-^WPNA-$VM_Z$RIc%ztw*e-w`r z@xI2}oBn8|R|bCG)A$x=`u4b7R^@gvEH~`y>B^%M@_3iy?bV0cgDv3a9gcT|zYvcF z@E*sv+WPYRC3HiuJ!9bKJ&s-Wp!%>Tn7lOs$Ova6OmveNAOxM+(xysqyzO($NY`%2sa^&Y< z*|;G4=G&V>6esID*Ks*(cI z@vi|t?>U*uPt);l0zdCUxg-4f`$Oq#Wx4K?q<={!`3HZ`HIhG7yOU1;HQ?W3+fP1K zk*HTEW|00S@bey+JHnq&i@Wx|mpj5=0{*DYPo|{6?E>EyOq+l3^S+xHvWUR^Gc(CQ z_<4iQRP`%e`uUhF@%Ei$3JRQl(DMkRfh_?)@9=q+;I{c)?Lx7u9Z#pvI>_5nC;e%A z^`Z9WbMW&%q1($(`56Ksyp`zUl=*4NI6tkVl)ZD=_Uc2d>v7;+NOy#P8~D3dOM8xV z<_F`4x(xhl%t2tfLGq{SN79vNEAiXA5S)>eerUQ>ARYgD@E5ns^b3>t&&@#pZNzW4 zZ#z=I#tcIHYPWAYQomY>-`4-AB>h`5(0@Jn+jb`Z;O9L%cf@{UaC3<__1qEtTj}PM zy>;7;q`#ihU$qnE5B?&%{B}hDF_=8@MxxuR|5#+b75u!;5I+=iOy@B>gqu z=ly4q6%p8e{M=sJ{DYr&s@>l7M_<66~9npV1@i*^y`rE+IJLB%ieT*^a6nJOcj%52~a9^qw z{Jbabj_|Li^zHt+UsC!(_d!Jc2S0BjdxewV+rC}M?Hl>sHtD7Buq1ggDNa16M(N&Y<6P_R_V`P#3!jp4{o4j3B5;3KZrdRp|3>ih2DBLJy7=wo)|B%m z43)I3!*Q_N+hZlW9BPm%3(C{#o*^%Y(LYb08x&@jwj`I>J#Zf%s1q7kurzr z^3wr%yq)ZhtV8z@zirRlr2J$u?~0B=J8`3|pUp}9A$uU4PX99Sci8D~uRW<9=>UJH z-Og>V{!|`4;4iZ0-5Z_yyV{K>GHN%do)jJlee89!6MW=}dLp08OG-pbMt`~!^<*OWc^BJrocW>jGq~Ti z9Q?dz?e_XPmFGtA^RBk@lGC>*>}m54Cu+;EJd@uert~L*pSP&x^+bMu6nN7(3EN4$o12@rl;8JVVo1&+vj`kH_RG7^;pJUwP&eS^-7Ng}68=r;=1r6G_`Waa zGg(ifZvkFy){1iqsqmw5EsDu6@0W@xlL(wI8h6oq&^h?+Wm2ipTg{rb6o14Qv-P@| zhzP0kc@VDp<URP_sy4>vZ!O`YhcZ>-zI;~&fx~zA)ki}n7H2Sep^28oq48h za9jQ|Y+wyUZvItk=?e+hPV2LkW#UyYv}+q%!ex`gXx?wLg0MNoH3qS9bH zAb%GCvaJaH*KC=;7|tmOQ*H0Q1GB)G`L+Wh9r8m;zspX)QBaltruM@kb5}sq`Ju2B z*_q@Qo+9>IHw@WEjKSwu7w~v z%T+dDS0+(K9*94ce-eHD@vxu0ecLbsBQSl7P3L+~RfW z^tWP@nhPpV#&y^@_IvIwMke8U0&tYZ|4U&;%iQLL&GD*wZG25t#kn(D>a`iQ=U1It zJ*mEYUX^xyc{AeU8!M}{8C4bW+Qx=)b+z*v>Z=;!+SJ;H#wNvodR0?1E^8CY9f5ihSecYI@geQjJ5a!IUxO~{^FeokZ4l-h=KtD0Dj)M8>)d2D)Bb5+xM zjGxp{8?P;|gN~Jylrsf4kXY?Bg{E%trKMF3F2@Ub4g~g4vWB}6IV*sUiB`hK_r^c$9kXXaK8N$?@#S?4msuJcrrKNsu zRnw`})6Xj(*HB4Pnza*JE2?5-_32d=jZKx>gu1%%HRVm^74fR3+U9s|1rnfA5JqLO zf|4T1DUIb&>%_Y9dCl4xwGD@kvYAdVuWPB2%DQCLEQ~eAwT7zlrnw6#%eBq(nyO%@ zCatNezPz>p!f>@UURnRQb4yc|R#{b1i>syeEp=K2 zd0T0HW93*CYaVj&QAdrfE3cnhSw7_8W248~VmQTWn5&_@zDk={6(>%SX6f98r44Y} z>XwEIZIR7=tR~cHs+uox^-j&DRuwHxQfDAfsIG>)BDHEN^(w7V6jx)@G1}Csdh%k* zVR<#>y0*CmSwya@Dz(}MxJ*Mu6`7*(yeeBtMPqDXGpY#kUE5GvQPVU}i`UdP+aS`X zigXqVO7T<68&K7nG?AtH#ug|`QOcFHqewsAxS$*!&|DR_6N)ueohQXeLh}@OgjOA^ zs4H)7uB~1O1?ubPsm$OZ6=fYZ3?)Ij&PBaMlFg;%^>K%yN%l`g*H}RnyL2jSkLpgwLFrU9B7strHY#Rg zlhbUC)zvVvro656bfI)4}X^ zRDhE>#mXuh4%JE5s?Hp5RG&YP%Z?BR*x@oPX(jPM0y zFwr0+s=Z>Bs;S0<%uBwjIjdlzwM?{(GwK=_OfPS!Y^)!Z@K~*M9C?g}B5SeTKY;@* z(S|Blkwk70lPnff_0d!Nf-7F>xF+-oJ6M~Xt&Yf(s8p4;Xjp|KOS9RnS*fZ6epROw zj?~;z7e}o?Lx!L2s5rvQn_4R3W0m6_GAx>iM+IOIbu5WyytK5swh0NbW21g?bm&m! z`&9d7B`cLRm1?c%>=KPgqIxA)H>A99Ql|*_~4V5Y@ z!-qp*Qetk^yjpZllNw?zaa9oBDx#`G>G;NmIC^@!qnF)aQhiX>AmRF)E4$TXk7dh7 z(Ny`kzpu}y8miS`G&B))3SC1TIvz1dtkfzOOs^_$#z3?g1J+3m)s3D(RLma9YBOu= zt1yPF!tk^{ro~Q~i6J8mXp)CmvBa45^jeG+%j?F;(V$k#qDm(;o>5y@Ii9-cQpjtH zi=_YepGk~AYR=R-$IoeQs+cpk1vwag+W6@+4 zHoT%KKEnR1YON}$aN>7BjyB<_5#=N5F%v;5Ef9>K+J=g{7R)t~?h54_E4` zv}JJ+E;Uw>1+@(g@q!u*E#ghJ4a3P}afb1B6(EQ~A|RDj)reCijW<^!)*P>_tDTET zRZ~+9c_FM+9;*eRqOqP}@?-&xYMO~k5&Vh)PeZvQ37&!AVtskM20!r%4D#{YObX3I zB-YeefdMs<7B=9bNM|e=IzGI%;aKhTqvil|4hAZ7#+QyCI&@CF3N4y1bq)>N3o7v5 z{OL!Hu$e{(rV%z%#?%q@a(E11@h>{h1z2pvyo!nutz!-^J$y9z)NoN=hmIOC^3Z~j z1%;~AeU%$gB}VFU;O7x;m+**+p+i}kPdXUuHj}sdYV6?61T&(tYHrKCjF2K(WqN5i z6sL@o)>F?wC9V(&_k*L=Hd4vQE2R$-K{%|mMl%hAaUI!4w^Ln9{zAT18gDA6yb2vj zv?wg816VAsz52SQ{+hOe=YSjIm z&QWl4oN7xy^mpFO{m>yj^TP1=Fx#d5FFMk(U2z9pDf4=e%2PAS=T5l)cDOc%`&x3D zI?u^<>q_Ma6keys4v3@u5pxbSfnw63+*CypRfDm(B&Ab?=3(lr8G~#ZWMPzzYmT$h zQHn{{7IHh+oD0{5UUsFysb``|nGoYT8eo!qu8;1jDrzRKi8|}7htB#LTqkXF#VaZ@ zubxRcP?c0gGOVpB>aeUDR9D?)ww--RP_MDQZE&Uyx?iW0F>mG#J=KPkmDbmG)N1-( z@K&3IdnS7?VR8!W z8V*U0=uWCvSyxxyH1E8f*DfI_5!*@KXzddVq(8$P(jmza-AVQ0bRGI& zl2DOEljcA7OnA7MqdF##JDJ(yg8y7QJx<0*Cm{390&(4vd6=1wY)WuMaO*vP`2&wx{sQ)pm8rfTEKCB(JT znyNaCJ)3u4$7FHI=NUR`thuVCvT?Y$$t08t%;SzYYZ{M8QXuK{JS=Bci}}3j|B+Fj z#jHew;ZR7I)Cj||KDCp+*@CrQn!Eg`>z6wn_nAF++R2=>##`e* zPjBatt>;xbq%^{M&*Ov7OY-*&+@X zE2TT_*M1`XXgjMp?r345X{Xgq<9WC%=(yO4=hlbF$U=Q!#?|ZsRS%`j+vIshsNN)VE`!!WW6*W%OB<{gMbu&$Y1#zx}is4d!j%X}`JV?;H*h*Vr_K4opR z9OqK$x6HP%K0{p7X9$(dzk^SoBS~{KdXIzXAh(4{skZRCaJX4J^q{roy6^*two;UY zlMXTnFf!j3hN#n}D6)aMGW-ZW8%~;jjl4;vLEdUD%QCWlT5B%LTA3v>1>zglr&q6rogJvxyqL=in?y5_AS6tkmx!L3io* z>N>rv1^O9fDBrSqklR1G@U?-sG;oA0cmW1wnxssVnKwZxk(3fP2ZR=%g60k6hz9bL(1B@~}gWQ5r<^FWS{*O7EGqzEb|DC&$a({D&a&3O&}ZN3(MCv3&>F6B@AuQlHZZzkI2 z@cZH3E!wqu)=zuN3-5spvf^6bwzdnvv^nxV5#Nt|7|8`wDCei2 z_Ovg37+GWC(mh!ZWrbQa^P#NQvvB%u*2nboan?6kkWw^d;C_(uP1aT-Zq52OD-Tl4 z-t_9(p7zCWvwo9}i@(W!G`m;g;#%|3?B|I7T=omu*_`xR^M&j`5%o{m?`3C!O1W5T zzDL%Y32Qx=lc@Y`5-tJrOY;Ym3JL+)Jq^}}`lp%yZ7o4fHLB^HH7rfd_R0iBs6BN zNktJVT8lDx8toS%-)7yAZDd8)!ks!10p-!`Cne>D?ANmiOEv?qEegUu3jZrC+qp=a zWulqlp57X`A;%rx2-ilC5B!aV#JVhD|b_Ef-hV&dEkD;X8w!R z9jT&`4s=vx19^qImITI*GR8gy2Iw|ei5d_)${71p%3%x`Dasi8RLZ$5`-be$Xd#E9 zjDZ|Fm2wyZMkmHTm2$|)WJ)kn)FFpXr5wh9(TTB7fgwXcz?a!8P}Fd(H96G!nQO?O z$W_cMK>`jXMKRLbjz&7ZjF27eQ}k;_0%J!RW1j*;!CYfKLEWYKg!SK+PVY1)f*E_6 z`i7VFxtOuyNXea6kk-3I7)8_(K=XN}hk>CCE;GGEs}qVDV?{rM2;&hJAv8~7m> z{5)&CN&}KtiH(Y!g1KO|PI|1?H|l8iC=FSW&96dB4825x&=F2I``03|1`rbME%=Kj z3E|Aq*^(H|j8KB7Nr(;wB}Ci>g@CY!KHUt^n4 zNhF(GK{otKzZhc+^V-ll`gt<+D*e12`h{`aQDyCVg>;eiw(9h6G?~=ruujE%de|ydC;f5IzlkDG6VO{v`> zQJMTSP9EbVk^D4H9t4s6G)^7_k^D4H9t4s6G)^9b7tPHk{ByHR-Z9p4Cl3D*xi8`5 zG5iwAmvHhRh~!H+c@RYMC7e76BKZ171F?762M?-U!j(s-npmA=yek0Lu)ZYhP zLeRejO36(;w2&kw2tAsfS`&80o)D!f^dvk|Ntr5@OyC1au+*a{R9T zXFB^cfkd;tf2H^Z@VFveD4GM_t=Lt>>D*>#!Kj=q*{vxu{!U%pPNV&fl zd##xj9hemz^b3qT=<3VVV9V*8P-062b2E%>tmmyf7y3YC>v`*Cy7)3gc`g#SX1)}8 zErP_}ihO||DsGAyt@)yl*(zjfw2_3lm9=`xM=NUo%_Y|L7R>jE^_XSMAA+GZ%^Ig^ z7V%zBoed=@g4q%73M1ED;b+3KoSq54L4-HL_e2B%FmWQLL}o|io(LpSFn>#|CFV2X zSMfs%Q;ZdUBis=ION5yJN!P*f5z&MiL}N9~Z_S^`pO=KMN5UkBVn!1(E|TdnUZl$} zny=%B`uBEo9cm{Dyw!Y-#`x$^+s$8_O5ir5jjHd(paKVh6f@>SAh^3RzqU)w{H>gx zKWlUo2|$X8`JzaglIRF^k(+mgp0O*@Gog2g@J^^hHg5!?6Ja03e@hIo3Uw0FDu}UF z-U+QGpeV+7LLU+EqtMr(-1*A6=G)Ho9wCdV$leM4ljTzjk zg{|9)&`m_RDRhe@P=24(UqxjguIKe1sXoA3Phx5%MVj}EnnQVi7%`Q1Q_DjFdBda& z2z^}?sg0Q}Q2Ekx_OQ`(&CJUO{~p6+f$lLZQJjTG;KC7YvQ-db%p*<(>C`s89~z1S zl_hW@>)8nxiNe7;#}Osa+AN7571KP>0|&^N(cZ2rbhh`zz1JK) z^&Yxv1CaiJe7H>hzU=cN6Iwse`sJgP#0Aw6&`_hq?2{k5x=da{8-1{kTrpNU5iQb* z6Ire~ZX$AXybpLUD6!WtBsgpogQXG!KpQX&35p9s33K=;NNM$qhs>VPkK~;{hORQe zb(L|Qa#A6yC$yDX_N{cegfB~p?Z65@fk8S$Pm+`;L+??4AvoJZcM|E&&@XM*5@d9M z`uF6D-|H)>uUQ$o(tWuNgB)C5tA9$p!KeCP9OaIL#N}zY{0X)5pUBGzGmFb5xco6S z>mSR@u(h4GxI7D&F+4sU!($}iZNM!+CP-)nj{Ljz-3l^Y0j9U(Kz-+S%3s4wAJ%~p3GUIB)yb-Ig zsiJF)8x65Ii*6k&LuMD&NHw!9d}mm!oN6~YsE z9UEoE?AQTThaDSgwJ(z_OLZ%Dv5csuRFJm=Uv{lAF(5>kCGJ0>wie4zA zml;;E&GZ6N|AnO9qgzE6$msjJg*|_YXvA@>mA6>N4z$!~cXdmBG1pSxzO}mS-S~%C z>U*@7`m(H5)F#DTV0GECJ9JBZxK+2*H&`w8HC3y}&d_Vn@)F730o7K>=xs2A{Q)gd z)i*?8k);Z&zT}AnmP_ibx~0CascR2mip)iC@pl{7=343#nwI()rlme>X{irXTAj9a z_OrTGtjLa?LtDv-fI{ZzZYnlP#ST!hp(=)wWtK52R-~7JO~|=OU7w?3C{Ok2I!k?o&QhO~vwG|g zk@T~+*tYCtsgJc;>a%N>`hc3HK80qfkD6KP^JJF#(3qt@5oW26eOcVrU*`h<_AKE`9I&+1s}138xZ)QzP+T4Slt z&sc4CYQrt{Nf=bim9hdpin_Q;Mmu$@t3yTyTk2~lmin%VrM^&Nsc(%~>Z>7El)wLB zsV{k0>YE&v`nratzLR08FJ4&c+ZC4j>V#Eh?{C}3QeS?s)HfTfB6U@LH^EY0Jh0Ta z3M}>20W?Z+nYsHCWe+##YEo3S!#~=xW<^!|?xUV0+C8-!zOJ3?>{NJ{`bSJ_i*3w) zmfE`AQk$@&)xJe0G00Lop<9}rt79#-E4kHX)B0L!r*KQ{`K^3=x{BpmYDaIwQk!vG zYFlkfZIEqs*rLZP=D}8{tu@5(M%2*tTB+*^mf8i{Qu{e8=0jDikEM2Nwu)_mL#^0a zDW$?vdmmeB7h_B9M{KE`g)Oy5u+_#=G#V*$Ct*HizG9l6m>rh6&3YmSw`MR|7OVAg zrT+1(O<7`PUc=J(5=-24z+gK2yKJ)!tIeS1T#_SJptb%^vE5ipGrP=gQ+v@#&dxWZ zLp7|I+^n0oVjf5H*1JRIJ(!D{8!`W+A-#&e8hR^)S>-JltaQ+-9o?kHE$VKZcP64e z#%G541!mIba?GAJ^P5Dp$NbDRzrd{BTpnH?hT4f}NBHirc@O1vBNhW7&=HONHex=G zMGmtE3mcegt0;2ve?lmreU6}LIsSh9Nz>-(c>F|ekl<4PV*5CyUt z+@!Ywlq~^VOM&SQz}yT>dw_-;fyu)jwdpL@156?Q5_bL?XVElVcmOPh>}dHaU-Uj0 zQKC*t1yP153Y~>fOmr(HVU0eZ^aSMe5MbI|8jb}e7Qj_bTmhI%fgwLi=yNqF-2qlz z4-D=6o6f550aF%`dISQtq)Cfj{cOVKVN?qe8Xg3$jsVF=1JfRWnFdT(0A?OAodK8& zfkDG4{D%yqX;2pL_6T$0z(UvfLf;D>;T24J29h64GAfm@Q; z!&IC*4=>pm+QQn<+XrbHwW99uri~$PLtjkU^MsFV40W|KEm_u1;6H2(wReVIh@%%1 zBEujIz@L$HFIQZ^p`VSRuT>fPcaXX#UsAvO+L*oqv!6F1&=0mT{RHL(*vA`=1f~cH zk?MwDx!XNj6Rz7m+QNDC7&vWOgx#-L;snSk#2>YyA>WiVZ5E})7_GqeUdctNQ(oMx z+vB}tZ|S|PQ{tDsmwZ-wFZrH3u=kSBLP_Yu?YkRC7KFu!0qyRiuI%W{6FFw|~PcmvO)sus&-*=`3e1|6DwyFwFHaQSdyDOp88M zLNOUQtt@os62n}BK{ z z<#ZS2@1QJoQSwlo?sriNK>34n6lx9%=>Y}Uyh1LNQY5f|MQ7%e1D6?IZzk#yYMak`l)LoR} zpim7b$+GSo1Ii&7h9xLxfYRuq#6kH3zVVR2w1ZNB&wfY>jTljRwQn)-PhcJZrnrSD z)Dv~!2*fPUfP#hOv}qM940BxwN^}8p(br$R zX>}?LbNv~VJWOQMGoYt+rTy+Y=T$g~-UCCUO zFEoyJV9P(EFwFHnDD(FwuBhEp?2gHB_C8KZF(|jWC@VlY5+4Rha6Jpkt1imFL8;x( zi5X4)6?IbTLAk+2xeJt$`#Ul3gYuG#k`J#eKfsBZ2FiCX%H^P3eV`NbJSYbbbW(l- z{NKB_@9puC`gYu1w@&G6+201aGfwKQ#CuM)slE++>GeMa)#EH2MlrLPAKZ0`c z!A?vz>ee2II4R>n`L&C3At;lEIx)WmrD=VKJ1JG5+~cCG1!Y`; z6Y~xz@3<)a;XCt3I5AT}$u4wKE(PT_7v&jHjvVR4{2P>4U6jM%akZnIm`3u!L!Fd+ zK)Jz1`3RJeqn((2;FT}AC})6DewY(;H7MUH3eWoGNSNBOanR#$nlt=tv;L;UdK8V+ z41EY!R%deB%g`_m3I-v-V4&j&%?x#PAi>0-vaZbk^`so$=B{tj7e}Motz_p*$hlNJI@2A3z&3rRs-`nFzMuU0rN_L zoG*ac8XzYht@Wk=IVS-_CP}By0$@H4kaIUMy#wTI24+iuoW2L(UP*wQalm{ZAZI=> z0|VsT2}~9+>B{SEVCardI{Rd!e;W`W=V)NY2FR%cCK@2;7GNd@$aw;oVQJ*>3g=8E z=L#}`EAR^E7$t`=2ja{f-a%SDFpjj({trnB=@V7de3 zEC41iK+es;Yz~m~3^02K$oT@8&jaN2JqY*p0^}SC%+>%o<-m*%kaHz4G>A6wld9i& z0+^xzIX%Gi4v-Tbq-j$E}|_bcfxC6-ix z`7zOF!$x5<+soU?S!d`d|)6cZ2f;rU=J`D2^QlOcqYk zVWt5yF#yvD%<=%t<)lvl<{r`qnM$Y6OQes&6#M4uZ@{ck82`K<1hZ`lz{~_DAGS`X z&kA5l0x(YlvnBxZFVZIfQ#cIzz@O9UQwvN<0OmGe)&yWS0kbIpL(d*iKPdHSg-1S% z@^FXmA*UTHH4;;di*N!vaBMJE1$q>w2v_+^B6bEa(cM^%KV~s7WdWE@U^)UY?~y(M zn7&x0h+=&wU0I9;rYrz6515Vs%+7kgY%;# z6)`xj2va-4wb@?xxTka5shSZo4UT z=h{u7yV7n7-EnqP#NA{;VtwdNu^U4x#9PYP>n7o%w^kmaFf3;(D4o^mxmp#5xo!c4 zmN+Q|L#I`~L}1T+T49*$PoVTHVXl4&7Q~qA2Zdp-Jw_v6sun2I2yW&YtuV|r36#8M zPRpOGT49*$LQu2-t{W7FxqbyoRB`PO5nju0P#ETV9hA0RU_amuy$DBDne6(HxdSl8 zN;0|Eq9Jn8GmE zG*HS)IjzOWMZ!Lui#mm2u8Tn_3b5*pz-UUczdlbW3`>3sl)?b6?-Yi)b~^&|Tu>9e z%wSNC#~)E-J|^SrMi-?XXZO1(SK|y_yhcqql@M0_L79l(1mz1*o^o+zkHPb@_)DZU z3}=g6l+$o_$dOJ=9F%uml&e7*c$5?KASf5OD8C0~os04nC~vqZCZtv$?Ub`WD9c=w zqe0o^qLhFV9qZ&e50s}|lxslw%0+n?l>B3yTyKI>c^+ z!w&*`-Bg9)61)@?yg_SoO>r+7#)qm~b%k>L| zVXl>+&_YV0Me?@TcL3A5g!S=nd!JAkmi&97S%itrzw6SC=Dm92xrSpTr zu;jk*L3-9o>P(W=fJjL`7??ICnQX|Om57a37?yknDBTybKK_=UuQ1GY2`Gh&n9DyG zYZZpMegjIEYDN8X@iH))lI)+0PZWkFe@AI8W_|o~(H}KZVwh_%C`GE2{c|x+VVLVQ zP|52rU{!6o$Dz0VTSWH9QbJ-n4#D80P9T4)s*| zhJV{KSYep!NKiU1;jl>5p$I>gZO2x@e0FS^FZnT1#{K-a$T)3%=HK; zTiTeb&X?l z7Hfz`O;|zZoh`OeVOYb>pmZuO|9t&ZVVEoD1o&!zJ_i6(9Dq3*nC`P#XaBlR&MZk0@3=HlFoUiME z=?uW!2Tae!>>I_9k|-~ZZB!U8<@Z4;Q(R~mwXjN`xxQBz=IVbExGv$e{9Bum3d3BJ zK!Z6o0pv0<}%U|c`6o$FJCQ1c!`RlyT$&6vHlR)WQ$XvHV z{Rn45)rV?@VXlin+0w#X9ll(*DhzY|mgL0KbG@Z7#Kq6b$`yOG0s4NHrlGrV1n;x5 z)Mh5e00iaty9*Jg{26)_M{oEWQvV!)FfPgWvJ)7t-MuE`xk_Njk5WC4G#HpIN)8$# zM_@TMz;p%3xd)i<1LSN5=9K_BeWqYW3d_lS`Rj8uFq;D8%mZdXfSgN#`8Yt%-M~Zx ztAmRw@tL7=vKB@9f zEQ5im3y^a%FqHRna_WJJ2gq3g4COtYoL>TSL4cg+fT6snlk)*EO9SNm2uv(M&R(Z# z+7$tEMgl`Rr_<*oV6F|2a~3em1LQOVbF-4u+fnKXuf57nE1f>~0kbYZ&L4s42+-#r zz&xzv>?plJ_new0dg(@X1S8%uTKXs69eQt0nD`la^3=l$|7C4 ze*;X1lH;GR-ZN!cq?0okn1_`d|9l+}OkIFJX93d{Ag2|W)&M!z0P~8HGXUP>trrgh zvot`?OThFfIsWjLCF0?cS7 z$G?941sE#RbmhL^>G1ggIi~?bWtvXT<-kk`kn<`qRHo_V?2cQ4Qv&3i2+XDcIW54< zRC4^w{V`xZ4v_N$Fl9=Pzn#aIV5}Y>X9X}d0dj5!=KBCSPXH5Ba{Tl41~Bw=Q93(+ z4Gg_mFXu%2`ucOuEcBHDas~r)bAX%)z(fP&lmqi*fSg6Z3=5ER6EK^V9Dn<40A@^p zoUeiTT*>h-_x#z?=hNAFJTO}V34^j32G?Q;(>@c@0k0A_%aGHJ)?o?16OlRlAfSIG@N{)ZIUj@t?0dgJyrd7%D_rq6!=?ReYB`|GDj(@(g&&C)aK+a%b z=srj~JD&gy)#Y?{t^np{CC5Ksi-Exh{nE<04VXKX9RGYh4NP8uoIeBeu#)3nUjGJW z?*KV_;4UBC+miP2&(|1Wh6Knt1DK6Uj=w(3ff*ej=P_X32$1s^V2T3d>{SLI3y?Df zm?;5rE(GSQ067l=QxYKOePHMr-*kSMSB~|c06F7;F=5_xa>{{;1<1J=n7jZvw*hlO zfShN5**iea2f!>3kVEg#op}qtLznv`S|e`0G!FC*T|Lsu1;Nm-fJASgcj!972^cVC zSdDG*fao2%f9vSy1m;nk#XT6>^YhDn_#L`Ud!p8QF!T=HKKSb^o+o_{Os~)1NFRkM_ARfx zD$J=BrpOm_1TdcmV9J0QGLZH0PirwStqS9x*1f>255T+u%(ei`Uey?99>n_ir!^Cp z)&R^hVAcm1*SCsa~Uw} z12Fdivn>Gg3NT}aq}S(bU|JOh6Lp{Vhjx(d4#3O=X2`+m)4B?nngGnpz^n_v>|Tqy z6M&fl%#cIU>vKIYH366pfms)T(a(WC0T|j(cL=&a+4lO^yUlk^F|`E9oX_g?jAHx$M{tr5UXEKIM@ z3}DtMjDK3Kz-&?&|FmufX2{6&X>|e9sxba(eFDt-0LQ>0x`BNDh9lqttLDX)V<&&MY)KY>ztoRhM54DW5Z zC}Tj$yOc1LQw*ORL8gR_j1`7cZzsx?%=HR*!itZ%UQ`(7+6GGJO6Ee>FH)6^%r$sE zW0`$}^$<~%#c~}yno7$&k1@=JilTL0 z#u~y93Y&9Pp?jB7E@BKY&e5HM#E#x}iqJu{0)DsU<90nD|%Qm?D?vzPQ< zKp5O|eN2fF5q|cvL&+Hnx;tM49D=lBN{+uiCBSS}asc)z_vOH}DmjEn1kP6%FrNp= z*#^wg069Z0!gEala!P<{50JAA7+N@`dY0;0;)j8`IY3VTHi!w3(*(>qCCA^+&j6Dj zASdr)w5tJf=naX10dhKlS+C?E;s{(`^nM2USUUUUEygo=0dkH5W^5Wc1@^PMv=1k> zD2BeLLz_gj0dl%kT2vbofpz`>n2Bl9B956$&~7L>{$+7DFf#+>{0o@R1LRCaA6OP3 zrvsQRO3naisH==|zTN|-E}q5m-(gFjP;|mFbhf!D>?r9ECZ%JK+dzk3=EJn8oe~t({%dW1x#UpoR5K_dYVqo0n6df0dlH=c{o7M zy}(Qikn*0Xp--Nv55UwZ zIRHBX`(ZZ5EFTBRnE*_ylH;GR%YmV~ldim81BP~?k@@o1r`P3p_9;Nl1YoXEa{Tp~ z4@?#?>GZi3m}`|Be|`450{80zukZg;*HxqG&wNL7^XMds!3y~tt|^+0uvBXlqF*Ac!K*@$!wY#W_k&Os}&wsRO`Xf&C~8pkStS*|cezL*<;*%*NNJuq2^ zb6OL9<&3%#a~Xy4PwP@(+5<3u2Btdz(;wsgydzj2|Fk9oQ>-xl_IU=F4u$cTvu8WT z00Ee0V50EDJXj`Gx&IcJvH;93S3{ow%xqwK6s8y{a%m@GHv?06Bs+H!ShND*`dbN&bgQi5_=9@;|}WpoU!iHmYL zJ=bJY?C+Llc_d%pk(}j`T;q{UPjI;<=XoSkFYMCiFq-7LD5J|flIdABx8!_}>=E9?3C}0IKG+~tuR^++D=kv!334H}2Lbzbk0obQoLPr18wF7`++ z_DJsZNY3*}-iDEyE!qB(^6@y8JLt}w5-T(MfE>|PWed$ z@9G?lgsII<1n=q`4g1`ISJl?R&M+)=&UXWF#;a<>fFTL^;yI4q!7z;mC#v3I9Waz1 zcc4-CuixfT_Za}V@4FD1M%{V)^QikcccyG{9|1$V&G*MSRxfR;`-ev6vRnko$pd=$|KDm8UeKE`)O+NnnO~Fns5+MEPMud0c#b zzMz7^T<(R?2Z}%rUrGFOxko{w+$8T9Ku9k4GQ@>PC4F6sFchdPvfksepdJVnGAU45 zoO%G4#iL1AjRx_lXqab<}Cp%wzJlpwnV7zJ3?B))T$s_#y5M=Rn_3u|QCW0mP!@ktWq~26sMT(9r z!pvG}b5)#3(&y2N`jn`*UXZcef$K#P$g*CzuhUs3Wx?%Xmue4v$}}dwa7TR;T}@s^ z=ai{E+@jh;_7c7-f2rJ8s&Zd|+ah)ty2*3=#x@1xr_UT%GG#&$u8Vx>bVOBk1RFCR ziy5gg{AEli3|B*16LCz+yGU!oY-K}{mLnlVWm*%)6(gR|#}6}QHl+}N8CRGN1D6^& zd-iOSK_foj@dhxsBtj|-U8MFfdJL}~YyrjF)=@pZQq|Mn!LzY^mK3O--gF?>Q+ndX z8)$s^1Fp#N;qHieVbo?uRb3`4NY8glqc)SCkmv)Vq31w|cYH|WmM7G>Wj`moic`D# zfvWF+Rdi>B+SMoqKTr#44Rc-+aUMf{uIhVhLq%gf8rDRO=lYc=Y8=V&h*5X;i*Si@ z?2DP(EXuSU*Jb^Jaz25|Yw}-%+ttMfae1X~SE)Y@YFD3{z-5uTUHu~pD`?zOoPnK9 zIK7-RR@64sV)??S|N6P?+yUE4J9~{R>-+fGdAO9mE{`8a-03lsW)BI*jH8w^l^jZg z%Kc4M?!SPS5!xN7o_4BoFLz(G&!`U4npIG_Z_A+E8(^TIa&OC^+y^2nvfS&dnwk=I zi&Vz+JzfK<=}P#4RMH|##83;>uCb-4%{p@DD&?gux+ z&eYrZ&8>Kj-v$iT^mJp|ykofyZw1oZW|D6dZ{arKaGXJCQXt>xJBZtau#XirjoR=5 z_>*n;<&fa+D@i2zPrLG;)NS~v6S&=Y7+hXorS^Fj_=fbK-;rofp!T^l1OFMP{0E(R zX?1L5VZw)S+Ej%(Pu_TYMC6lm8=@(US`m^IDjj4yDg106)hcH>Pz0uM5+t>DUvw#-;A5Yf;@~je8Jsyn$+59sp1a zKA(C-v{>0W)?C$6**LtZs;;pjQR}Y397NhV3OU|%h;lnH?V>Dz5s>wuP-`Kqs-fA( zVns#sdPO172AbW#qY^8??}5_k!hAuLG&3oZv*-2j-ZU7mw;YY)8$_%sY)=cIG)#Kp zg@o~wX&vZBq~*`_*o^y>ZSVy0Be}k+R1pzwCDs5#KIu2NA|TBR)~i;c3~7%Oyb{a(Gwj}c-;V` zVCZLf(jfxXf>u=v{)#g~y93P~-c@r4nmu^~wMe@|VOdj7^^^s1P)#Yypr+ieYD!CU zRg*oKNmLZ=B=}+3#MFHgrF9nhDt@Vyq>;}BrGvRtZzbiBeWoZRA|h;$uXce$q~$jk zwtK4~C@<-URBG2(TEpmO&nwLRtD4R%@X-f|$xR2HA-a#<_~g*?C=s4UJz#{n}! zp91$q`;6=yQ+AGm>Q$WVJOFMi*8x)ZXob77ol}n-Ix?{HCS~V}rg&*hc|&Dgm2)bx z7&T7XIsF_YQ5I{EsU4q#^uQ`Xb#OFnDC;1UPYT?p&>|Adjj|Ymqr2_pdisW{r&N~- z?G98=V?|s~soeeNAhAB|8*ii7_H_%v8TrNv(f2J~Z2lmw-<*S4te{~1))on%Wt6j*m zw{B27b`n0*P99s#Q8(pYhl!%|NdVPZub_c#cSOy`ot#e=JGb*pVL%6(Ffb8Gz)QsfvDqvREFO|g> zRTfcP^O}h4JgJ!NoVwo$V@M{r0h(p9*H`b2-{<4s2n(BP1xDjRYm`P zgR*lsBvPrk1J#S`m7S>$dIQ-x5AI2|)6kOuamb(Rkw@=r6F41^dkOTqgpx;b-o_ zZObRV81nh{bUtsdhj&3As(FS!$|L7z^9?&KZ5pTLNlM-S+CD#wdIf<(dGAtrAL@(Q zYYoUkpD6O~3(<}Mrp(635<`!Tu?e)lo%*oF3Mt1x?PlZP*@sxD%^Y3CZRV|@ z_>Yl_M+c9Q%2az8g&c1MqP9*uJ$Q^%v?sS?onY_=vU79*mxWKCVn0j#kg{_NP0r85 z{hHc_cy$3u7Bn|?=Zk%{3|FWLc)I6%i!<4)`Xz~QVq7J6_4T&Lr zCW6zQFZPX?ePw5TW3=*(-lV20uy535;2XutH_9NxtuqNF-)Nk`ZNhJGhQUx$Am4cO zFm4m-Qu@ZeC@gB-4Sjf$1bIrn(V2m7+^>A&axnV)#wH9KrEh%bn-;YjZ5jB+U){dJ z%|-CRGU}>p%*r>tzlr-MX$7n~j^CBzjL7_EWF-ubYJ3V|!hVdI`vTwBG`u|tl z_W;M4p7rhBCBbRa!L&(g7-$umVN60?t)!JDnzp*L_iW)mCnVj)IM;q!?S9hkd9}Oj zu4Ks^6ScVsZgT_9kaRpG5YZBF$^lNBwkAyn16T00H1$BHTtm{TKpE3?28T)Erp-Wq z&-?%Hx4WNwjs|kx=Y9Ua&-=X3`{(;U9vg?nzZgUIKmPnqIQ}2?*UM*Qe_hAa;>QP# zF9kc8onJU`cFph z9zcxhk!}3To16jQDljI)WvfuOYg8xS&j-FKFBW@fB z;N_F0hWm(iyJBK8xG=8{hD zZ?QO+u6fe$d(wZ?lRlnH`cY3hG~U08v}Q$AiSGDVkg2;~%+u{Gfg_^jw2YRKq`&G(-}a=x;z|FEmM;4JgBkrU zV_sV!W8Sb-Sg|0j2*s+nTlk=2{zb(1pqSS>i&0?qj9;-UbDs1)Px=d<^y|5#zwAjr?Mc7kNq^CkzL!h-9Z&j#C;g@;olZvV z@qQzhbb6Pd9d#Ex>9;)TKjBHgnM*o7-)&1@@uc7Oq|-NT1(ld#AH}MT?={6MYCT`b z>cW^)M9#b|t#}C41+Dd&6&V$^)>&2;hR(wFAIh*@&cYATuBkh+zv7icsV;jtj&uC; zGBzF=Q&olhhiBJF^ZShCEGvgn2P<|f4yW!S0xZb%EBGlOB3kaqXc3`r!zne??oF{$PlfLgs|Eeebm0Z$KdeX0Y((ifFAH_zj?fLy&(ocKRuX)n%d(z+J zNxzp%`dLrvM2q9C;gBoec6*< zz*ae2M0;QET2GxF?*T!aAc!~hUGSve^rRp5q_29?59N~ntS9}JC;f;g{W(wi;at*J zJn6SR>0_Ss7d`1ma!Idw((icEk9yKy@}!UDl3w?u-}R)Ad(v-t(vRkn-t?ru;z^(J zq~GzRkLQx!@ucs2(&s$s|I3p;lS_KvlYY;We%h1%$DZ`LT++8a>GwV93!e1LVGImD;mX8CzDB9W6`L2@BE+N?p#Mm&ZalBka@Gz@N>P8C9iM+nDu_u>Dqs?XvoM zh;~g~ko^^}7)!58&&P3&KQCkBk#Bc;!njc;`)j0G=8)tpXV21t)JH<5ZmMVz_5Acp zUS)M5m-IJz(l2<@Z+X&R_N1@ml785ezT!#0?MeTfmM)_1*D|6`jyUZ@IpPdIixJn6 zR)j{ATRDVqMi_B));eg!X`SUd&d^!d{yQ1A%Mtew?V6%@QF6PG7@a?9MP2rC#)x~f zjE#&Dr|cSO#Qi=z#x#`Ut28I|k&r2>1}&WF>-Z@k!t;Na;dvQ#nvaY+LzalT-G>-; zcnP2Ao%ng2)>-zKp|i04ub_8gJ0~*g9->`SH)MasBdT;-_HrEOIDMap^>W5}oU&`A zsQa|!EMu&6MCv0UQ`^!C37MuhbBd@F(Q;Kr%Sh5`UKV`B>ExIveb|JGm-O47^l?x6HBb5vYw059-ca=hyvl;hp#WzmCs(hA6Q{~mN* zcXWvHu635<-OyRs{hE~(>lwTH*^-Z|4N4KGUgwmT~i&|U-5V@9hSWu$2tC@jEzUW!%aWx z_N=mNq^Rpi&N7}$_vJkq37J}uR!GS7yLHrwXt^b$WhCiMPx?Ji`e{%4J3Q&Pb4jmz z()T^-bDs2X^`zg)CB5cJr?>vFQ6T+1HRDNtvnTy-F6k?t^t+z)aZmc6V7palDEj@C zT+*NQq~GzRAN8dFt|xszm-Gvs^xK~FF;DvEJ?ZyyNniG)-}0m%@uYvolYT#!^aW4) zO;7q^Px?=L(qGLb{j4Ybh9~`yC;h{o^un`wP6tkV()T>+1yB0(p7cYxq@VPpU-zWH zD(69&0j5_x>4$SkpYx<&^Q7PRq%U~VkK~g6ZcqAEPx?Ji`iv)iESK~dPx`JWeczKl zrlpJOg6=THR?xDt(tKoPWylgab6i@1Sabg#^lF0mvy7clS?x<48FeK?XJPw)%dlNm zRu9pxsS9$P#;c|hy`_NL9^KbE-j}iQ2(I-yfAZPn$I!(4y}Gkbk0-wikJhBEnneH9 zkKrkR4^c)MLj^0L`o9m;KP2_vD}AV<#sv&YYR=zp0q44UiUcKS;O}>Vb6-70f&eJM zq0Zk|fOC}DPO`|)zASo5Vop6J@lJ<>erIC`w|z(gm-1)e+>um_h9Flx$cH`1o(Fl! zgWU2UFME)?9^{J(BGw=ipN3|lA`-haH|32NIhv*p%Q#e!Z5L8HqO*-S%YH%LGsinE zd)=z=8ZH>aKloZfmA+PhM`75rYmXK>oBe9N(k<}2VPgdxqTugaK8h#r5aHxe4tWv~ z=~WI%0U5ix)3~tw){}VwhZ+rm|d3TpneHoCBh4VE)_La^XDc*g* zl@?asYufe39|r>dFc5@)jsS8?L=@-zZa{V|eclVmQH#$NKxAbl>>`NZ3a=*HP5S=D zI3P4;InGCcLpyKU(w6{{^#;fJMZ&S{`XV59MBN8OR_2gZWe*D$_&uUAA?s_v8MkD; z6E9j=LGt+XKbQKPpjR&-#5LquK;-imT%UhTIQYZx5uEoJWXz!Y3?P&RHJ|@Nd;rn5 z-veY9hSON)oL`5RAdVsFac?k>)e40`Eh$wsnkRz5pKLf}uK!}1<-2%k6^bR2MsZ5UZ z2Y}pH8iKU&XMoV3w&IO@&_{WL+KvFSiyX!MgLtlXFi=$G&Z&aFiLo((sB`cwqm7iNK%mGK+{{B61W;oItKLQBdli@l8 zx$sdy?pWjN*8!1Po%`oYfY6_gu`lnzhgM&_;U{6c#plg{+!ON!=X0DOmOkeJxo%PY zJwWW3-vs2gh4XPhj#!X?2Z*faxvaZ@>|0sl_W`+O>HNljiuu&C>u&%e_xU-WV}QsR zjYG}>a^2!n2ZUB_)Xs692V`N+%xoV4M4r@eoL>cGPxT;33wHrIZ0Y=mfb610d;ZaX z1}o5gdYt}sf>>TX28i5I{%Xb0kQzjIs@zia@WH71)_S)l=UBo%8H*a62y}J z6+muVRFC}>zAZRsdgEtL<(dx{O=icmg=|Y!_GwuQmbM6Ti4!yMWADp8q5u z+O8zQSId$FG-meuMc~+w&jG^kxQ+=~{}qt0!BXw#H+}+9_bgc<_4#XnkSx-L>+`n& zS$Vxl^&}v>mR-w$&{s;eT~)%dEbRgEZgADoKMu(L>rKed0dn8+{4W7=^K~Z9e*~mp z_16~&a@h37eL&tWEZ|lg`f0?Evb`#@%~i{aS>WtioWBQiNxlZWH{k!dhGW2)vuvLLg#Pply8=i@Mdn&BO)sJSx|R7ifV1#= z(;HU+8B?_%al8u15zEq#0df>A-+=$;`rHBJj`+nPUj&5yG|ry@A}eZ+^QIf<3uwsg z!u;N#ikdhd!vAxeBfz1@^mU)UgCLge-v@|Y!CnGnOz_}*-Vca9Q~xL+bJi&MG$1RM ze|`y&j)n6jK=|Jn-rn#D_=QHJ{dXTY^83_W`d|7a{0I}YH;w}GBFMPyy~cX0(u6)% zgiiuz!P4ghAazAWQZE8Re~0k@j~06UZmYV{DV(;bn!vecdF%%Pv3vPNK<>WL^z%!A z*tO{I0CL}ognNLH722-1{&QF{XJ*>(24vjw;d=qu29=K079cCu+TneG$opg5hc^M) zwWR+FARQ}u|1%)t7S0y|DX7tee;58AAh+>f;>tN6{#o>n;bxTvwZkA;1Igj zc?uAFbXNh9JBpmMU)#JNI5(_b{tzICEXYkj?pxA-m2j-g|0O`~DNEPdowNl7cP*-W zz#%{D-g(PQ@H00=*#34vz{ar4pM__EGX@+UWB5nmG$8V8)ZEf7K(3>Oe$kq-R$16W zMLj1YVXfM1_fqI%Y4`z9?ONmF!+_8q=R^Pc1R!%(2KYCCTvfKSb-@+r1YE9RLQ4QK z=Mo35;me>JQ@u>^-vi{XCH?h3hY^PXt)-6xviBFwaghK*`|}!S0}#DlCeA-VI95Er z2uSBmCZ8V%*7VxRkN|XL{555YV zIpsrY`^KNA|2=F(!cjn|y3kbL4G2UTIHv(QG$(r*O@(Ix8CRAjYczYo&uHPfj&L@B zbI0C@qpy1(9X z6MYJZ_Tk$A`8;}A=avOPu301HSwM&h6c9T10XeE{2e|M)K=xsj#`!0JybL~K6o_xX zb~r1+=Vjm=QIW8Axr^`XVVqi=KL?yK6^A72OMq-!mVOzKeM{$W`4l{6dG$L1DOgm0 z7Z96jfpGArXNS56*#YGC0`-pc#s>gd!7uISj{$PQvf`6~=rt_g$3e6x+d*FVx4@yE z(b9h#5c<=QKLX^g)$gzS7swlyUEc)Ap2A6_FiX}ufYSkI9SO$)nX~wu0OXqG!$m;o zbwIk8D}d}POHsEpX#$&55Ym1dIJYf6KLp6D7S4|Xa!th#_9`p1S9x^K90j+4BhRh) zIQ<+Thb_Cl49HEWr{npte+kaY8>G(|AT|6~<2(h3?j6GUexkyEwSWF0Amf&QewZLu z)P0;F)|~PifZVpD7m&kqrd?kFWY6*sZJQvZ4e8$u2!hVYY~Kk;!QwLqNZs=41wc+) zI@bWXYjN%o#G-m1Aone*4+1i#>_TOgf<@t)<@tXNoM&N$_Uiut#GZ5im>{6!BjvGw zg%gWfy1KcZRJxVcdfJme8?Cjr{C8z_wVQ6qU(H4A> z=wZi9pmtm9{CAS@SL8NYTa{*`wg4?Q(tg@K%|En)S9Ri@Z|p^U;%iuMKh{mxI{of3 zP1)!-`@LGX-A~rq-73^!pJI$6?zD!&>ut7Uc#Vz9dYZ8J(Iqiyl};z=?{w0_Mx_Pp z8h(4Id_s5C70Xl)``rBbQ}0RU&o3=Me)eo~vi!u;r;>AzFP|3RAWAGg*J%ow%K2y}+K8jymkImpd%tj0C(rz=;kATrjg)ufxQV}PgKgzLkBwbTK# z)8n@%7tc?hOE=oxompCKC3GxbUN}?5Vkgqz(w!)lw+w9dGk|0B1Qr2YypuLB_S40w zER~;HN(_kt;Nq34&9u_BbW9c{RAt6q*AL1 z$qt_gVw7(YZ>dbGn_c2s?vv3jLkAD3!+{c=CZFNhcAI@3*8E6qx) znlig-BH01KFGq3N#GrUDQzpk_`1Y+J}&!^|V7Bk~UTyEq$Utw*!d z`Epc;!0cWUvXB?pGmRD|Bm`D@tE~QwL|f@sfPU(kVF~DN6lFO_xMCR$SF<(huL+i5 z#Fz@QDY_^zUdKhaBftaxHd|vHisQOZ!n)W9L-DBxl(7WWqkUd134Kr|P_*{KN(HcD zn$6(lz-(j%Ihn0d4r~-dnJ@>VHOApMrDI|c@{G|Kqc4^yJBWPFG6S1Bo!AUvMLABX ze6!KsY(=uKj}+xMA^F8~xsOtUUp6~r%f;e&wVqZlu|D4_pFi_-v)-?llWG&OO+#IinatMN%+}%nXJvb_S$5&*QT<)HkIwQDdjaQ&mylDCu?aH zyCKbleNyGP_(d&6*@7xV!k3Z-wusV=8=Fnp_*_OCCsfmiSUgVA@#1{HTwZ++H>My| zE*_l*m#)M%xjCUl=QOxQmXkVF z4B2$!c?pYo2`BOrPUak=8hq(5Aw>)9Ya`Ei_M`ft~_dTu_fHa*9T56g>F$a zPW3B~$P{IyJw_ipYKrlT;YazqCr?qvE@G5p`0OdlH;f+T9yx-FvX9`$_-CF%#e`%M z#Uw?}uJovKA|-|&l+zGcSMwC$)RYY8ZlRX^IJ6XhO zwN9C0p0$voc6tX*F?RluRE(!}>J(#VA;maZM@G226l3P%4aE;fP($$pUX)+x6e`Lz zgdF2+ol34(?=jp!8JrlkEmJw|LjqC`X!(LIwUY>(deba^@( zrZGL${-;atY@EjE)jnv1UbQRzot$2bY?Nvz4NFjCS3%hou8c}KjK%BaXdA18!>xSA z3b)BBH{7Z#TBqGHSk|@KSg~q!0F_&64nPlAkOPP!bzwGDW}%l&8Y|xhP{k{io zEEk=1H!^S$Cm3)}$XGSd04=;qC>!4@V6su&@?->T6dwbyLsdmKiB&cXKsD=vY*cgR z&&CW+=>tdtll1@!Zz6W^Bhzgd%biriSi?-MVM@V2bK(nAy3<=2%bmnBvD8%MU^!ze zI$^1cbH(!7T3YS*&f;<>n^@>Jod;AWhf94Nay$cqc9-4DpI?}rU+Cg;aksy-RGDAG zep6Xv;CgqHr2sI^NjZ|_6BGRM*D$geuEBkJi&+&iNcv@|KL|`U3jVzXW0w}BhA@?z-f zore;7i|kt(>*&u%`O3& z)K;6Kp5_DbI3BB@M=*FG`#=|e7WiE}JDkeP@SKeWc4=lnW8Ry0YlbRP8Ig@a zrHR6r=U0SDF)o=+e#Ihi=0n%g#f9`?ku%wfkkoT-af&jG9tbLGHOd6#BChFs@W4cS z6KWzs1iUrQG6$m!@R?O?vKvE;>3TL-AwXLkrxG;UJf-uQ<#BBIQoaUVfai3(wcMy) z3bfGD2YvbE!cwRax8Q^#b^bJo*=@5%tKSn7d}$Q5qi7@eZc>Y67WAACkF9a&jNepb zL1{H?hN)BRYDy09oRlL;mf3q4Sq#_Uo>3^tY9q6nvMY}nSxn70%F+WKOi9;st+c>_ z7`afl=w8tkt)`a>a69$Pr*SpCshKp^gBd!QZ4oZxJk(9hlLS*X>y~=JLjo4W_BE`R zx%ah|Cik#RrVYa0#EfaU6VOn=0vhgcZ7kjb7WCmxIiw*dJRb^}lvy0#s9Z|4BeT>X{h z=!dYucqZ4uE9A-KTvAJgSUi%8b@L8QNJH5bBpyxvEaYQKN|rm?cHWyqX%Hb-I_Rt_ zJIsL?m@>0!Obengb!K66K%>zhhXtSk3YP!s?7p!8 z=N$48>UnA2TKi}hSYSB=`#@!Y-6Uo*A7}{Hol6s`U3u%IptTNiW9ZYX1dl{Owwt7A1ueLSF{keKI9f0+fI_M%Q_2JVv8A!dViy44v= z8g6yQlHD0ftbuPi41$dnq$$`pXenqApmOI+`&(!?jNgy~3~;FEx=LEpfzc6`{{-MB1a z?#=Kvb@O7VI#dYzz8&&yyW4fsa5x=u2TOip4*$=E7!{FS+5;`q-yjB$8H+Lc~j zJKqyfrbgQ1^wwceoQ;L1xeYip(%tT+HureRh)joP;9N+nIT%;zs;APTeTbx$jH3j( z&`M1mCWX1!D2j`i|Bc;zO&(}->1eZF9WHs4cB@Nk9VaPA3ZYh`OX&5hD?eB1!M2OGgbON>eFW!z7s4CcHNDKwL~+GPn1fd;p-us>TOxF}t85_SqMx51#0N!X*X8x)03i)zze z2TrIlK&iLsl>;=TZVXWB?fL+vZj_V*x9TOAfrygh;7$FoHv;?T4ddwz)BFIc`Ows0 z)PSy$P5zLsF;vBM&_E4s+e<&waaiUk4-uP^C*h0P+imv58fepW#!|u+=QV|?9APGvU1wOU9#@#TYVvF|HmZ5;qB`5=Y0J}Lthb_d zPX$|*g;uNFKUGqLt%^3dHLV4Qn-#U@ZR|w{Bpa<^+6=9Y9cx8vsHL~8gIXKSidt)J z>`@1_Hd@298Cru@wkldfYh%k=2@W?aYIonqeo~7zG}$Ju_QJ3PaS3FB+9oCmY%@1Q zLWE#Q33}sN`NXrqlAsCV62e>E88$=+;u0*C^hWlOHfRDRA-wgiRgSc~^v)KzNAI?W z>Gh7cGRWFV*X=d?i9cjJeZ!OQsyG-!)tjoS)jQxh*zj`BBOJIh?NE4o!a=({EIvmJ z;$>|NyU5P=thebq06(zx>rm*L*xl64K(sey9mK%)YZ!yJwDcxy7{}UA4Wk76*xE&P z=&boT+>_=^Z|}Uhyo^#J%*sG8cLFo`WD7KludqZDBTJ--mnn1f0UK0o7&d%0>^NtL z4Lyg#oa%$R=&%n;)OQJj%_jTcAh!@$#TQjjZmVS=3=knV%c30QzE}pr01*L6cNQa8IbA&v`Uj* zcr~F$w~1mKD~?Q2=E%Uvq}z_JvKmEif;c*86usMFqYT{2u+fLu(1;Sm zE*VA%VtWe?!JwE4Z4&q_@@y42Tf$W(-cY#K(_$_oHCW`s*O;L* zmSYkf<_?FbE_P+Io$YM)>Uho{&*3ee!7~cvpV0`0lcUb22Ju&j!||QOgVFr9W~SBx zP4UFVaQe<&b$h#olOiKNG?%4`gwBodl1zFf{o z@9r!wonWqeP51`14rwyjj82EUN^5xlK1!=I2W3~2_iY~tBc+$_O0$2(8!XJBIUM3} ze5cq@Vp%DZt;8FSE4t|%b_m$hM0j^V5AWib{~k785u01*2lx%c8c(W}%O^We@oxuY zYW5&yDeDGkXm*fFg6053fedrm<2Hk=9iErc?!Xa@-t0Z7AeOw8cG0NJ+jd#Z<7`ehLO~3sec^brM--gsLn5*F*>@8z+R+f>a=+G z6+Uc>?`7iabMs4hYxNX;J+=&W4n{Av7%&0jmf5+4e=7r@k1p@f+u8=s+94t(Pmi9B z#xS#0=2V3jE;O(&&Ah`tt4ky6@nq?wGl9LfqLghzE$xf&sCtxg_{S(6GDBuYp@ok_ zEFst8le_eW5qfSDA%y>1L_QikuCj=g{Okg5W}~v57|(fRj(-zb@?Yvtg2ns}Iu_3; zqKsjRhoG7Q@u!SVDt3_ZLWth`gi;3_OUxb1tXO2-mxEGD&Mr1tUnqAVPKWIRhek=G zU9jByKYS49r&n4)iOfB)86E2jXGASA5`~n+dk0UF;~{ds+hrzA{QxWVem0iDamtL8 zq-b5VLXe&0pwsm-_fdTGHqxtBwBU1|}*oxCmu_IoT?g@V)X)8fq$4 zFHI->{}xe#Q+>^HH$H?SX))V9EXmsPNqqA0n9jXxZJxxyQcyTn$!^%VO67lhiSuMPWv_6J$e zDc5Oj55i7qciq3V*#|aJ|KQlzXL81 zrIQZGMrt!)#z)oWEi?_!WOr1ER>{>v<27CdN{1PnI^Yem&5eH1rlF; zIb1XQO`WZ|7fLdz)XHWQQ4e&n;Rx1!p07LflEYzq;4V?C1cz~^e`AHc!{flp@dDlE zdcbP)Jm*>xw6TGhc>61?-i{-OVo#6JA&!2=%;`M{t;5Y$J z7!%ZA>z9*mg^o%#D*ZZ?FZB9PxMtc{%>i8JW%b&rkkLeQ+^BefG$`iQWP)MF^v}y}w(O1sT zCw9(PHh?3XcPi}*yeHe;$7{82+M`J1p4E*f+URG8Q+Yduc;GCQ4cYU(^X--lnfVr8 zPLK2RK0{D^)7Iujf`_yy;F^uqZl$}Ew4fCvKi#?v5%ELLF93s5VGEwWxkA zS-;SB;>pKVBYw^=*uUmmYi$JqvE6>Q(pukyAD235wXxQyp2T~W6*jmn;#JS>)>0qw zpuptqa;w^G_YjUxZnoH{QLP{%CW~p4_l>X#;AMP9K!H!4IFa;(&_uFUxRM ztQXVXCUvF6Vm}jnjA$`M1hy}$7So#wc#?fg!sq$~BCX%8RMP?->^BkCltj|jmi#S$ zvX>^~oxB~qmKS$TFt*Ua>uG>l8+p&7Zh+^ZlFnj6sra2a=KqIlLWllg>kt z$(qeVQrYOOb9}^PFIl_X={8#ZH9t`xnvl<~V4Tauh<9Y~G#l%64WLAIrQJ#uM*j+r zI+032Prt{V_F@595`zsV!|WS{2_MvG3t3J^>?ojiBx)jj4?5p}L3I=tn{dNj=50#Y($Mr$M2Bs7$Jmjeyuf z_Dh<@q?n8+7_H2x#eVad2D+xwMow~-B*?1UV(hDesJ43J<^xr<@VE~}ha800rTiul|dXM5(B^2%(C!H`L zA3Jcp@m6HKSQmNfYy}_cU}b?YPYf44!GK<pI;ZgsK_yHli@ zV@^!Fa5XDAD$gOo_BL00JIDwd$yVC!QISM#EFQO;+-e4){^WV~1d6BzqC`!~@WYJn zhsa20agp7%I#SIdk@wgdQgpQ-#>4k+rd?bN(~(MniK{b0{f48ym4dm24Z1xE`?ScA z7j-L79%|a4GL=OwuBt4XFI%Q@FCN0xyk*Iz*=5#*jH7%34~*iPwVO3md!4=-2+PeL zA{(XCCdLeCq*&n=H;;w8Mdl@B*DBmM&8Mtn6?vN3r$PXQdth>BJwQE!=qC5a?c&8T zhSAw}`_g7d))?hh-{?t|Q{>2idL46MsN{mUwP$cjUb0!Tj z^YT_JEvT-kwJ$fCwJMUUWMZTW)C8zWoMe%mRoA+yN}TO@)%0G$I`;#wmih zkJZ+I=}*kZlzWwq()l8#i-Fy7gVGK1_+(9PJe%T0&|Vh9(6OH5gXn0eThqXhlU!u@ zYL5>ZG%}dY;xW012chh@Bp5GLFg`3Pb7Ds%JeE$7{@Xn(?;{js4vobSzKVgxf$AX< zMHs+o*YoMQ`SYjVlgyuAT7LZO+2myTiKkB`=N?}^U0%ehMV%a~SlCm(nSXp)c2lOYRvl1q zmsZTSjdY{hp*b=hU1Sxx0jL}8EjEcfqgBHf49GLi(71SNu{fRbH=R6*dZyiNq&;IA zMQ8G&k&P-=a`E?~6pmd^E0?hUL&a-{k{pU~s%?+qqOJe(6e|5uoAXJUtGPxia>}KC zabU+$cCpQ-6j?(F4>)T++xL1YQ9xgt6Q%4@HeCu;k&&zyZ9u}>CX)WpnTq+`besyk zt!lLi7wFLArA$?LNsSkg!UGvw)$|(IDZw!xU4qJPCuu)zZ)4l6i_pjD=c%u}CpM8uU}rcvnGWmFbtvXD;>1Nxu#B&4f_7q`|_ znYqDMTFz93R#TpDWgIf6k^A_g&K^rHPD)#ZZG@DQfVpO}- z^GnS%?G(b*qV$=w8LyVxYowD;r|Agia`#XVC~t|fdLY8C>ltpzd?D7rTD< zQ~1u2@UK3};efSvhsqZ!nvEB60dVS>z?(uMa}zvoQyWQHP87o?{1t8CGPQ& zO!#u`vK}s(omtlokY1Wn8BUHjjzB0Sn#Egk5x4WCt;1~wgsP43&Y(3yX));T4E=vt CAiuEy literal 0 HcmV?d00001 diff --git a/cpp_memory/memory/memory.h b/cpp_memory/memory/memory.h new file mode 100644 index 0000000..67ac894 --- /dev/null +++ b/cpp_memory/memory/memory.h @@ -0,0 +1,109 @@ +#pragma once + +// #include "LittleFS.h" +#include +#include +#include + +#define out printf +#define MAX_FILE_SIZE 65536 +#define STEP + +class Memory { + FILE *fp = nullptr; + + // size_t size() { + // fseek(fp, 0L, SEEK_END); + // return ftell(fp); + // } + + void open_file(const char *filename) { + fp = fopen(filename, "r+"); + if (!fp) { + if (fp = fopen(filename, "w")) { + fclose(fp); + } + fp = fopen(filename, "r+"); + } + fseek(fp, MAX_FILE_SIZE - 1, SEEK_SET); + fputc('\0', fp); + } + + // template + // void validate_memory(uint32_t addr, T &val) { + // size_t size = size(); + // if (size < addr + sizeof(val)) { + + // } + // } + + Memory(const Memory&) = delete; + Memory& operator= (const Memory&) = delete; + +public: + Memory() {} + Memory(const char* filename) { + open_file(filename/*"mods.dat"*/); + } + + ~Memory() { + fclose(fp); + } + + Memory(Memory&& other){ + fp = other.fp; + other.fp = nullptr; + }; + Memory& operator= (Memory&& other) { + fp = other.fp; + other.fp = nullptr; + return *this; + } + + template + bool read(uint32_t addr, T &val) { + if (!fp) { + out("Error reading ROM: memory file is NULL\n"); + return false; + } + if (addr + sizeof(T) < MAX_FILE_SIZE) { + fseek(fp, addr, SEEK_SET); + return fread(&val, sizeof(T), 1, fp) == 1; + } else { + out("Error reading ROM: position out of memory\n"); + } + + return false; + } + + template + bool write(uint32_t addr, const T val) { + if (!fp) { + out("Error writing ROM: memory file is NULL\n"); + return false; + } + if (addr + sizeof(T) < MAX_FILE_SIZE) { + fseek(fp, addr, SEEK_SET); + // printf("write to 0x%x, res: %d\n", addr, fwrite(&val, sizeof(T), 1, fp) == 1); + fwrite(&val, sizeof(T), 1, fp); + return true; + } else { + out("Error writing ROM: position out of memory\n"); + } + + return false; + } + + void clear() { + if (!fp) { + out("Error writing ROM: memory file is NULL\n"); + return; + } + + fseek(fp, 0, SEEK_SET); + uint8_t val = 0; + for (uint32_t i = 0; i < MAX_FILE_SIZE; ++i) { + fwrite(&val, sizeof(val), 1, fp); + } + } +}; \ No newline at end of file diff --git a/cpp_memory/memory/memory_manger.h b/cpp_memory/memory/memory_manger.h new file mode 100644 index 0000000..d2282b3 --- /dev/null +++ b/cpp_memory/memory/memory_manger.h @@ -0,0 +1,407 @@ +#pragma once + +// #include "LittleFS.h" +#include +#include "memory.h" +#include "string.h" +#include "math.h" + +#define MAX_MODS_SIZE 256 +#define MM_FILE_NAME "mods.dat" + +class MemoryManager { + struct Mod { + uint16_t next_addr; + uint8_t id; + uint16_t size; + }; + + struct DefaultData { + uint16_t cur_mod; + uint16_t first_mod_addr; + }; + + uint16_t cur_mod_addr; // инициализируется при создании. 0, если режимов нет. + Memory memory; + + MemoryManager(const char* filename) { + memory = Memory{filename}; + memory.read(offsetof(DefaultData, cur_mod), cur_mod_addr); + } + + MemoryManager(const MemoryManager&) = delete; + MemoryManager& operator= (const MemoryManager&) = delete; + + // возвращает 0 если режимов нет + uint16_t get_first_mod_addr() { + uint16_t addr = 0; + memory.read(offsetof(DefaultData, first_mod_addr), addr); + return addr; + } + + bool set_cur_mod(uint16_t val) { + if (val == 0 || val >= sizeof(DefaultData)) { + cur_mod_addr = val; +// write delay + memory.write(offsetof(DefaultData, cur_mod), val); + return true; + } else { + out("MemoryManager: Error set_cur_mod: out of memory\n"); + return false; + } + } + + void mod_memory_shift(uint16_t src_addr, uint16_t dst_addr) { + Mod mod; + memory.read(src_addr, mod); + memory.write(dst_addr, mod); + + //printf("src:%d dst:%d next_addr:%d id:%d size:%d\n", ); + for (uint16_t i = 0; i < mod.size; ++i) { + uint8_t tmp; + memory.read(src_addr + sizeof(Mod) + i, tmp); + memory.write(dst_addr + sizeof(Mod) + i, tmp); + } + + if (cur_mod_addr == src_addr) { + set_cur_mod(dst_addr); + } + } + + // ребалансировка памяти, нужна когда закончилось место в конце файла и были + // удалены какие то режимы. Т.е. в памяти есть пустые не используемы участки. + // Все данные смещаются так, чтобы закрыть неиспользуемые пробелы памяти + void rebalance_mod_list() { + uint16_t cur_addr = get_first_mod_addr(); + uint16_t offset = 0; + Mod mod; + + if (!cur_addr) { // если режимов нет, ливаем + return; + } + + if (cur_addr != sizeof(DefaultData)) { + mod_memory_shift(cur_addr, sizeof(DefaultData)); + cur_addr = sizeof(DefaultData); + memory.write(offsetof(DefaultData, first_mod_addr), cur_addr); + } + memory.read(cur_addr, mod); + + while (mod.next_addr) { + offset = mod.next_addr - cur_addr - mod.size - sizeof(Mod); + if (offset) { + uint16_t cur_offset = mod.next_addr - offset; + mod_memory_shift(mod.next_addr, cur_offset); + mod.next_addr = cur_offset; + } + memory.write(cur_addr, mod.next_addr); + cur_addr = mod.next_addr; + memory.read(cur_addr, mod); + } + } + + bool add_mod_with_rebalace(uint8_t mod_id, uint16_t mod_size, bool need_rebalace) { + uint16_t addr = cur_mod_addr; + uint16_t addr_tmp = addr; + uint32_t new_addr; + uint16_t size; + + if (addr) { + while (addr_tmp) { // ищем адрес последнего режима + memory.read(addr, addr_tmp); // читаем значение по текущему адресу и кладём в переменную этого адреса + addr = addr_tmp? addr_tmp : addr; + } + memory.read(addr + offsetof(Mod, size), size); // читаем сайз последнего режима + new_addr = (uint32_t)addr + size + sizeof(Mod); // добавляем к текущему адресу сайз и размер структуры Mod, получаем адрес следующего режима + } else { + addr = offsetof(DefaultData, first_mod_addr); + new_addr = sizeof(DefaultData); + } + + if (new_addr + sizeof(Mod) + mod_size < MAX_FILE_SIZE) { + memory.write(addr, static_cast(new_addr)); + memory.write(new_addr, Mod{0, mod_id, mod_size}); + set_cur_mod(new_addr); + return true; + } else { + if (need_rebalace) { + rebalance_mod_list(); + return add_mod_with_rebalace(mod_id, mod_size, false); + } else { + out("MemoryManager: Error add_new_mod: out of memory\n"); + return false; + } + } + } + + uint16_t get_prev_link_addr(uint16_t addr) { + if (!addr || !cur_mod_addr) { + return 0; + } + + if (addr < sizeof(DefaultData)) { + out("MemoryManager: Error get_prev_link_addr\n"); + return 0; + } + + uint16_t prev_addr = offsetof(DefaultData, first_mod_addr); + uint16_t cur_addr = get_first_mod_addr(); + while (cur_addr != addr && cur_addr) { + prev_addr = cur_addr; + memory.read(cur_addr, cur_addr); + } + + if (cur_addr == addr) { + return prev_addr; + } else { + out("MemoryManager: Error get_prev_link_addr: mod not found\n"); + } + return 0; + } + + uint16_t get_mod_addr_by_num(uint8_t num, uint16_t &prev_addr) { + prev_addr = offsetof(DefaultData, first_mod_addr); + uint16_t cur_addr = get_first_mod_addr(); + uint16_t mod_num = 1; + + if (cur_addr) { + for (; mod_num != num && cur_addr; ++mod_num) { + prev_addr = cur_addr; + memory.read(cur_addr, cur_addr); + } + if (mod_num == num) { + return cur_addr; + } else { + out("MemoryManager: Error get_mod_addr_by_num: mod not found\n"); + } + } + return 0; + } + + uint16_t get_mod_addr_by_num(uint8_t num) { + uint16_t prev_addr; + return get_mod_addr_by_num(num, prev_addr); + } + + bool remove_mode_by_addr(uint16_t addr, uint16_t prev_addr) { + uint16_t next_addr; + + if (!cur_mod_addr || !addr || !prev_addr) { + return false; + } + + memory.read(addr, next_addr); + memory.write(prev_addr, next_addr); + + if (!next_addr && prev_addr == offsetof(DefaultData, first_mod_addr)) { // если режимов больше нет + set_cur_mod(0); + } else if (cur_mod_addr == addr) { // если удалённый режим был текущим + if (!next_addr) { // если удалили последний режим + set_cur_mod(prev_addr); // устанавливаем в качестве текущего предыдущий + } else { + set_cur_mod(next_addr); // иначе следующий + } + } + + return true; + } + + bool remove_mode_by_addr(uint16_t addr) { + return remove_mode_by_addr(addr, get_prev_link_addr(addr)); + } + +public: + ~MemoryManager() {} + + static MemoryManager& instance() { + static MemoryManager instance(MM_FILE_NAME); + return instance; + } + + void clear_memory() { + memory.clear(); + } + + // ----------------- Функции изменения списка модов ----------------- + + // Добавление нового режима в конец списка с заданным id и size. + // После добавления режима, указатель на текущий режим установится + // на только что добавленный. + // Все поля аргументов режима могут содержать мусор, поэтому нужно их + // инициализировать. Значения аргументов режима устанавливается + // функцией save_mod_var + bool add_mod(uint8_t id, uint16_t size) { + return add_mod_with_rebalace(id, size, true); + } + + // Удаление текущего выбранного режима + // При удалении в качестве текущего выбирается следующий режим, + // если он существует, иначе предыдущий. Если режимов не осталось, + // текущей режим будет установлен в 0. + bool remove_mod() { + return remove_mode_by_addr(cur_mod_addr); + } + + // Удаление режима в определённой позиции. + bool remove_mod(uint8_t num) { + uint16_t prev_addr; + uint16_t addr = get_mod_addr_by_num(num, prev_addr); + return remove_mode_by_addr(addr, prev_addr); + } + + // Уданение всех режимов из памяти (по сути зануление указателя на первый режим) + void remove_all_mods() { + set_cur_mod(0); + memory.write(offsetof(DefaultData, first_mod_addr), 0); + } + + // --------------- Функции выбора текущего режима ---------------- + + // Сменить указатель текущего режима на следующий. + // Если текущий режим последний, то выбирается первый. + void next_mod() { + if (!cur_mod_addr) { + return; + } + uint16_t addr = 0; + memory.read(cur_mod_addr, cur_mod_addr); + + if (!cur_mod_addr) { + cur_mod_addr = get_first_mod_addr(); + } + set_cur_mod(cur_mod_addr); + } + + // Сменить указатель текущего режима на предыдущий. + // Если текущий режим первый, то выбирается последний. + void prev_mod() { + if (!cur_mod_addr) { + return; + } + cur_mod_addr = get_prev_link_addr(cur_mod_addr); + if (cur_mod_addr == offsetof(DefaultData, first_mod_addr)) { + uint16_t addr = cur_mod_addr; + while (addr) { + cur_mod_addr = addr; + memory.read(addr, addr); + } + } + set_cur_mod(cur_mod_addr); + } + + // Установить указатель текущего режима в конкретную позицию. + // начальный режим имеет индекс 1 + bool set_mod(uint8_t num) { + if (!cur_mod_addr || num == 0) { + return false; + } + uint16_t mod_addr = get_first_mod_addr(); + for(int i = 1; i < num && mod_addr; ++i) { + memory.read(mod_addr, mod_addr); + } + if (mod_addr) { + return set_cur_mod(mod_addr); + } else { + return false; + } + } + + // получить номер текущего режима в памяти + // начальный режим имеет индекс 1 + uint8_t get_cur_mod_num() { + if (!cur_mod_addr) { + return 0; + } + uint8_t num = 1; + uint16_t addr = get_first_mod_addr(); + while (addr != cur_mod_addr && addr) { + memory.read(addr, addr); + num++; + } + return num; + } + + // получить кол-во режимов из памяти + uint8_t get_mod_amount() { + uint16_t addr = get_first_mod_addr(); + uint8_t mods_amt = 0; + + while (addr) { + memory.read(addr, addr); + mods_amt++; + } + return mods_amt; + } + + // получить id всех режимов из памяти + // возвращает область памяти со списком режимов, поэтому + // нужно обязательно её очистить после использования вызовом delete[] + uint8_t *get_mod_list() { + uint16_t faddr = get_first_mod_addr(); + uint16_t addr = faddr; + uint8_t mods_amt = 0; + + while (addr) { + memory.read(addr, addr); + mods_amt++; + } + uint8_t *ids = new uint8_t[mods_amt]; + uint8_t cur_mod = 0; + while (faddr) { + memory.read(faddr + offsetof(Mod, id), ids[cur_mod]); + cur_mod++; + memory.read(faddr, faddr); + } + return ids; + } + + // --------------- Функции работы с текущим режимом ---------------- + + void load_mod_id(uint8_t &val) { + if (!cur_mod_addr) { + return; + } + memory.read(cur_mod_addr + offsetof(Mod, id), val); + } + + void load_mod_size(uint16_t &val) { + if (!cur_mod_addr) { + return; + } + memory.read(cur_mod_addr + offsetof(Mod, size), val); + } + + template + bool load_mod_var(uint8_t offset, T &val) { + if (!cur_mod_addr) { + return false; + } + uint16_t size; + memory.read(cur_mod_addr + offsetof(Mod, size), size); + if (offset + sizeof(T) <= size) { + memory.read(cur_mod_addr + sizeof(Mod) + offset, val); + return true; + } else { + out("MemoryManager: Error load_mod_var: offset out of range\n"); + } + return false; + } + + // Сохранение значений текущего режима в память. + // offset считается относительно начала переменных самого режима. + // offset = 0 это первый аргумент режима + // offset = sizeof(first_var) это второй аргумент и т.д. + template + bool save_mod_var(uint16_t offset, T &val) { + uint16_t size; + memory.read(cur_mod_addr + offsetof(Mod, size), size); + if (offset + sizeof(T) <= size) { + // write delay; + memory.write(cur_mod_addr + sizeof(Mod) + offset, val); + return true; + } else { + out("MemoryManager: Error save_mod_var: offset out of range\n"); + } + return false; + } +}; \ No newline at end of file diff --git a/cpp_memory/memory/property.h b/cpp_memory/memory/property.h new file mode 100644 index 0000000..b2729cd --- /dev/null +++ b/cpp_memory/memory/property.h @@ -0,0 +1,103 @@ +#pragma once + +#include "property_storage.h" +#include "memory_manger.h" +#include "stdio.h" +#include "stdint.h" + +class ISaveable { + uint16_t offset = -1; +public: + virtual ~ISaveable() = default; + virtual void save() = 0; + virtual void load() = 0; + virtual void set_offset(uint16_t val) { + offset = val; + } + virtual uint16_t get_offset() { + return offset; + } +}; + +class IProperty : public ISaveable { +public: + virtual ~IProperty() = default; + virtual void save() = 0; + virtual void load() = 0; + virtual size_t size() const = 0; +}; + +template +class Property : public IProperty { + T m_val; + T max_val; + T min_val; + + Property(const Property&) = delete; + Property(Property&&) = delete; + Property& operator=(const Property&) = delete; + Property& operator=(Property&&) = delete; + + // установка значения без сохранения в память. + // Возвращает: 0 если значение успешно установлено + // 1 если входное значение равно старому значению + // 2 если установлен диапозон и значение не попадает в установленный диапозон + bool set_without_save(T new_val) { + if (m_val != new_val) { //старое значение не равно новому + if (min_val == max_val || // не определены занчения min max + new_val >= min_val && new_val <= max_val) // новое значение попадает в диапозон + { + m_val = new_val; + return 0; + } else { + return 2; + } + } + return 1; + } +public: + + Property(T val, T min, T max): m_val(min), min_val(min), max_val(max) { + set_without_save(val); // устанавливает новое значение m_val, либо оставляет min_val + PropertyStorage::instance().add_property(this); // + инициализация offset + printf("new var\n"); + } + Property(T min, T max) : Property({}, min, max) { } + Property(T val) : Property(val, {}, {}) { } + Property() : Property({}, {}, {}) { } + + ~Property() + { + PropertyStorage::instance().clear(); + } + + T get() { + return m_val; + } + + void set(T new_val) { + if (set_without_save(new_val) == 0) { + save(); + } + } + + // void *row() { + // return &m_val; + // } + + void save() override { + MemoryManager::instance().save_mod_var(get_offset(), m_val); + } + + void load() override { + T val = {}; + MemoryManager::instance().load_mod_var(get_offset(), val); + if (set_without_save(val) == 2) { + out("Error load Property: value out of defined range\n"); + } + } + + size_t size() const override { + return sizeof(T); + } +}; \ No newline at end of file diff --git a/cpp_memory/memory/property_storage.cpp b/cpp_memory/memory/property_storage.cpp new file mode 100644 index 0000000..84b5102 --- /dev/null +++ b/cpp_memory/memory/property_storage.cpp @@ -0,0 +1,35 @@ +#include "property_storage.h" +#include "property.h" + +void PropertyStorage::add_property(IProperty *prop) { + if (props.size()) { + prop->set_offset(size()); + } else { + prop->set_offset(0); + } + + props.push_back(prop); +} + +void PropertyStorage::load_all_propertyes() { + for (auto &it: props) { + it->load(); + } +} + +void PropertyStorage::save_all_propertyes() { + for (auto &it: props) { + it->save(); + } +} + +void PropertyStorage::clear() { + if (props.size()) { + props.clear(); + } +} + +uint16_t PropertyStorage::size() { + IProperty* prop = props.back(); + return prop->get_offset() + prop->size(); +} \ No newline at end of file diff --git a/cpp_memory/memory/property_storage.h b/cpp_memory/memory/property_storage.h new file mode 100644 index 0000000..5fdb223 --- /dev/null +++ b/cpp_memory/memory/property_storage.h @@ -0,0 +1,27 @@ +#pragma once + +#include "vector" +#include "stdint.h" + +class IProperty; + +class PropertyStorage { + std::vector props; + + // singlton property + // Конструктор копирования и оператор присваивания копированием недоступны + PropertyStorage() {}; + PropertyStorage(const PropertyStorage& ) = delete; + PropertyStorage& operator=(const PropertyStorage& ) = delete; +public: + static PropertyStorage& instance() { + static PropertyStorage instance; + return instance; + } + + void add_property(IProperty *prop); + void load_all_propertyes(); + void save_all_propertyes(); + void clear(); + uint16_t size(); +}; diff --git a/cpp_memory/mods.dat b/cpp_memory/mods.dat new file mode 100644 index 0000000000000000000000000000000000000000..f97ae3cc020b2379b9f0c143dd55b98d9a00f56c GIT binary patch literal 65536 zcmeI(J5EAD6b9fUTug|VP*4*JLP;#Ft&NpSpkNKOx&R87!cy1^uc(dZ-UI`oqau^< zZ{~64-bucCPDGQBDq5L&+(o~c)vHOgqTYYYadfg@*Df=Ub`1W%J*_YDFTJzM^6Z}D z*=X)Lu8af-5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7e?bpnH@O+>#f1PBly zK!5-N0t5&UAV7dX@dCs5ZB(O52oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z0D&R|#%+K!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1d0%N{`$^-fUXcAK!5-N0t5&UAV7cs0RqJfysUn*AK+04 z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7csfg%K6*N1!w&=Ud#2oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkLH2!V*-dNv~0RqAydjJ3c literal 0 HcmV?d00001 From b808e3abf633e2abf2012b8288a9ccf45a292c13 Mon Sep 17 00:00:00 2001 From: "alexey.ivanov" Date: Thu, 15 Feb 2024 11:13:26 +0300 Subject: [PATCH 50/75] remove slag code --- cpp_memory/main.cpp | 95 --------------------------------------------- 1 file changed, 95 deletions(-) diff --git a/cpp_memory/main.cpp b/cpp_memory/main.cpp index 1ad7291..6ef0297 100644 --- a/cpp_memory/main.cpp +++ b/cpp_memory/main.cpp @@ -116,104 +116,9 @@ bool check_mods() { return flag; } -struct decimal { - uint32_t bits[4]; -}; - -void decimal_print(decimal val) { - printf("%08x %08x %08x\n", val.bits[1], val.bits[2], val.bits[3]); -} - -void decimal_add(decimal val1, decimal val2, decimal *res) { - *res = {}; - res->bits[3] += val1.bits[3] + val2.bits[3]; - res->bits[2] += ((val1.bits[3] >> 16) + (val2.bits[3] >> 16) + (((val1.bits[3] & 0xffff) + (val2.bits[3] & 0xffff)) >> 16)) >> 16; - - res->bits[2] += val1.bits[2] + val2.bits[2]; - res->bits[1] += ((val1.bits[2] >> 16) + (val2.bits[2] >> 16) + (((val1.bits[2] & 0xffff) + (val2.bits[2] & 0xffff)) >> 16)) >> 16; - - res->bits[1] += val1.bits[1] + val2.bits[1]; - - decimal_print(val1); - decimal_print(val2); - decimal_print(*res); -} - -void decimal_mul(decimal val, uint32_t mult, decimal *res) { - decimal tmp = {}; - uint32_t tmp1, tmp2, tmp3, tmp4; - - // eeee * 5678 = 0000 0000 50B3 F390 - // ffff * 5678 = 0000 5677 A988 0000 - // eeee * 1234 = 0000 10FD 4458 0000 - // ffff * 1234 = 1233 EDCC 0000 0000 - // --------------------------------- - // sum = 1234 5541 3E93 F390 - - for (int i = 3; i > 0; --i) { - tmp1 = (val.bits[i] & 0xffff) * (mult & 0xffff); - tmp2 = (val.bits[i] >> 16) * (mult & 0xffff); - tmp3 = (val.bits[i] & 0xffff) * (mult >> 16); - tmp4 = (val.bits[i] >> 16) * (mult >> 16); - - if (i > 0) { - res->bits[i - 1] += (tmp2 >> 16) + (tmp3 >> 16) + tmp4 + (((tmp1 >> 16) + (tmp2 & 0xffff) + (tmp3 & 0xffff) + (res->bits[i] >> 16)) >> 16); - } - res->bits[i] += tmp1 + (tmp2 << 16) + (tmp3 << 16); - } - - /*decimal_print(val); - printf("%08x\n", mult); - decimal_print(*res);*/ -} - -// https://okcalc.com/ru/ - -void decimal_mul(decimal val1, decimal val2, decimal *res) { - decimal tmp = {}; - decimal_mul(val1, val2.bits[3], res); - decimal_mul(val1, val2.bits[2], &tmp); - tmp.bits[1] = tmp.bits[2]; - tmp.bits[2] = tmp.bits[3]; - tmp.bits[3] = 0; - - decimal_add(*res, tmp, res); - decimal_mul(val1, val2.bits[1], &tmp); - tmp.bits[1] = tmp.bits[3]; - tmp.bits[2] = 0; - tmp.bits[3] = 0; - - decimal_add(*res, tmp, res); - - decimal_print(val1); - decimal_print(val2); - decimal_print(*res); -} - int main(int argc, char* argv[]) { setup(); - decimal a = {0, 0, 0, 0xffffeeee}; - decimal b = {0, 0, 0, 0x12345678}; - decimal res = {}; - - decimal_add(a, b, &res); - decimal_mul(a, b, &res); - - decimal a1 = {0, 0xfb18f5c2, 0x3e93f390, 0x12344566}; //0xfb18f5c23e93f39012344566 - decimal b1 = {0, 0xffffffff, 0xffffffff, 0x12345678}; //0xffffffffffffffff12345678 - // mul result 6b899db0 9183abbf 5c88cbd0 - - -#define UINT128(hi, lo) (((__uint128_t) (hi)) << 64 | (lo)) - - decimal_mul(a1, b1, &res); - __uint128_t val3 = UINT128(0xfb18f5c2, 0x3e93f39012344566); - __uint128_t val4 = UINT128(0xffffffff, 0xffffffff12345678); - val3 *= val4; - val3 = (val3 << 32) >> 32; // reduce to 96 bits - - //check_mods(); /*EffectsList::getInstance().onTick(); From 318cce8b65106d33d15aed799f39423778d5a81a Mon Sep 17 00:00:00 2001 From: npo6ka Date: Tue, 27 May 2025 14:51:56 +0300 Subject: [PATCH 51/75] replace memory functions --- platformio.ini | 1 + .../properties/memory_handler.h | 0 src/properties/memory_manager.cpp | 199 --------- src/properties/memory_manager.h | 410 +++++++++++++++++- src/properties/property.h | 116 ++--- src/properties/property_storage.cpp | 53 +-- src/properties/property_storage.h | 43 +- 7 files changed, 508 insertions(+), 314 deletions(-) rename cpp_memory/memory/memory.h => src/properties/memory_handler.h (100%) delete mode 100644 src/properties/memory_manager.cpp diff --git a/platformio.ini b/platformio.ini index 9d468b5..69c2c6f 100644 --- a/platformio.ini +++ b/platformio.ini @@ -14,6 +14,7 @@ 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 diff --git a/cpp_memory/memory/memory.h b/src/properties/memory_handler.h similarity index 100% rename from cpp_memory/memory/memory.h rename to src/properties/memory_handler.h diff --git a/src/properties/memory_manager.cpp b/src/properties/memory_manager.cpp deleted file mode 100644 index db844b4..0000000 --- a/src/properties/memory_manager.cpp +++ /dev/null @@ -1,199 +0,0 @@ -#include "libs/debug_lib.h" -#include -#include "configs/constants.h" - -static uint16_t read_address(uint32_t offset) { - // if (offset < 4095) { - // uint16_t ret_val = EEPROM.read(offset); - // ret_val = ret_val << 8 | EEPROM.read(offset + 1); - // return ret_val; - // } else { - // out("Error reading ROM: out of memory\n"); - // } - - return 0; -} - -static void write_address(uint32_t offset, uint16_t data) { - // if (offset < 4095) { - // EEPROM.write(offset + 1, data & 0xff); - // EEPROM.write(offset, (data & 0xff00) >> 8); - // } else { - // out("Error writing to ROM: out of memory\n"); - // } -} - -static uint8_t find_free_slot(uint16_t &slot) { - //поиск пустого места среди адресов на дату режимов - // while (read_address(MOD_LIST_OFFSET + slot * 2) != 0) { - // if (slot >= MOD_LIST_MAX_SIZE) { - // return 1; // Достигнуто максимальное кол-во режимов в ПЗУ - // } - // slot++; - // } - - return 0; -} - -static uint16_t get_mod_size(uint16_t mod_addr) { - // uint8_t mod_id = EEPROM.read(mod_addr); - // if (mod_id < mods_size) { - // return mods[mod_id]->size + 1; - // } - return -1; -} - -static void memory_fragmentation() { - uint16_t cur_mod = 0; - - while (read_address(MOD_LIST_OFFSET + cur_mod * 2) != 0 && cur_mod < MOD_LIST_MAX_SIZE) { - uint16_t old_mod_addr = read_address(MOD_LIST_OFFSET + cur_mod * 2); - uint16_t mod_size = get_mod_size(old_mod_addr); - uint16_t new_mod_addr = MOD_DATA_OFFSET; - - if (cur_mod != 0) { - uint16_t prev_mod_addr = read_address(MOD_LIST_OFFSET + (cur_mod - 1) * 2); - new_mod_addr = prev_mod_addr + get_mod_size(prev_mod_addr); - } - - if (old_mod_addr != new_mod_addr) { - for (int i = 0; i < mod_size; ++i) { - EEPROM.write(new_mod_addr + i, EEPROM.read(old_mod_addr + i)); - } - write_address(MOD_LIST_OFFSET + cur_mod * 2, new_mod_addr); - } - - cur_mod++; - } - -} - -static uint8_t find_free_address(uint16_t &addr, uint16_t free_slot) { - addr = MOD_DATA_OFFSET; - - // считаеим следующий незанятый адрес, если 0, то начало дата сета - if (free_slot) { - uint16_t prev_addr = read_address(MOD_LIST_OFFSET + (free_slot - 1) * 2); - uint16_t mod_size = get_mod_size(prev_addr); - - if (mod_size == -1) { - return 2; // прочитан неверный id режима - } - addr = prev_addr + mod_size; - } - - return 0; -} - -// static int8_t add_mod(uint8_t mod_id, Mode *m) { - // uint16_t write_addres = MOD_DATA_OFFSET; - // uint16_t free_slot = 0; - // uint8_t err = 0; - - // if (find_free_slot(free_slot)) { - // return 1; // Достигнуто максимальное кол-во режимов в ПЗУ - // } - - // err = find_free_address(write_addres, free_slot); - // if (err) { - // return err; - // } - - // if (write_addres + m->size + 1 > MOD_DATA_OFFSET + MOD_DATA_SIZE) { - // memory_fragmentation(); - - // err = find_free_address(write_addres, free_slot); - // if (err) { - // return err; - // } - - // if (write_addres + m->size + 1 > MOD_DATA_OFFSET + MOD_DATA_SIZE) { - // return 3; // закончилась память для данных режиов - // } - // } - - // write_address(MOD_LIST_OFFSET + free_slot * 2, write_addres); - // EEPROM.write(write_addres, mod_id); - - // //init all paramets for mod - // for (int i = 0; i < m->size; ++i) { - // EEPROM.write(write_addres + i + 1, i + mod_id); - // } -// return 0; -// } - -void add_mod(uint8_t mod_id) { - // uint8_t err = add_mod(mod_id, mods[mod_id]); - - // switch(err) { - // case 1: - // out("Достигнуто максимальное кол-во режимов в ПЗУ\n"); - // break; - // case 2: - // out("прочитан неверный id режима\n"); - // break; - // case 3: - // out("Закончилась память для данных режиов\n"); - // break; - // } -} - -void remove_mode(uint8_t mod_num) { - uint16_t cur_addr = MOD_LIST_OFFSET + mod_num * 2; - - if (cur_addr >= MOD_DATA_OFFSET || !read_address(cur_addr)) { - out("Удаление несуществующего мода\n"); - return; - } - - while (cur_addr + 2 < MOD_DATA_OFFSET && read_address(cur_addr + 2) != 0) { - write_address(cur_addr, read_address(cur_addr + 2)); - cur_addr += 2; - } - - if (cur_addr < MOD_DATA_OFFSET) { - write_address(cur_addr, 0); - } -} - -void validate_memory() { - uint8_t slot = 0; - while (slot < MOD_LIST_MAX_SIZE && read_address(MOD_LIST_OFFSET + slot * 2) != 0) { - uint16_t addr = read_address(MOD_LIST_OFFSET + slot * 2); - uint16_t mod_size = get_mod_size(addr); - - if (addr + mod_size >= MOD_DATA_OFFSET + MOD_DATA_SIZE) { - remove_mode(slot); - out("Modsize changed for mod id: %d. Removed this mod of memory\n", slot); - } else { - if (slot + 1 < MOD_LIST_MAX_SIZE && read_address(MOD_LIST_OFFSET + (slot + 1) * 2) != 0) { - uint16_t next_mod_addr = read_address(MOD_LIST_OFFSET + (slot + 1) * 2); - if (next_mod_addr - addr < mod_size) { - remove_mode(slot); - out("Modsize changed for mod id: %d. Removed this mod of memory\n", slot); - } else { - slot++; - } - } else { - slot++; - } - } - } -} - -void debug_mem(uint16_t addr, uint8_t size) { - out("0x%02x: ", addr); - for (int i = 0; i < size; ++i) { - out("0x%02x ", EEPROM.read(addr + i)); - } - out("\n"); -} - -void print_all_mods() { - int addr = MOD_LIST_OFFSET; - while (read_address(addr) != 0 && addr < MOD_DATA_OFFSET) { - debug_mem(read_address(addr), get_mod_size(read_address(addr))); - addr += 2; - } - out("\n\n"); -} \ No newline at end of file diff --git a/src/properties/memory_manager.h b/src/properties/memory_manager.h index b55a8cf..c12dbff 100644 --- a/src/properties/memory_manager.h +++ b/src/properties/memory_manager.h @@ -1,9 +1,407 @@ #pragma once -#include "inttypes.h" +// #include "LittleFS.h" +#include +#include "memory_handler.h" +#include "string.h" +#include "math.h" +#include -void add_mod(uint8_t mod_id); -void remove_mode(uint8_t mod_num); -void validate_memory(); -void debug_mem(uint16_t addr, uint8_t size); -void print_all_mods(); \ No newline at end of file +#define MAX_MODS_SIZE 256 +#define MM_FILE_NAME "mods.dat" + +class MemoryManager { + struct Mod { + uint16_t next_addr; + uint8_t id; + uint16_t size; + }; + + struct DefaultData { + uint16_t cur_mod; + uint16_t first_mod_addr; + }; + + uint16_t cur_mod_addr; // инициализируется при создании. 0, если режимов нет. + Memory memory; + + MemoryManager(const char* filename) { + memory = Memory{filename}; + memory.read(offsetof(DefaultData, cur_mod), cur_mod_addr); + } + + MemoryManager(const MemoryManager&) = delete; + MemoryManager& operator= (const MemoryManager&) = delete; + + // возвращает 0 если режимов нет + uint16_t get_first_mod_addr() { + uint16_t addr = 0; + memory.read(offsetof(DefaultData, first_mod_addr), addr); + return addr; + } + + bool set_cur_mod(uint16_t val) { + if (val == 0 || val >= sizeof(DefaultData)) { + cur_mod_addr = val; +// write delay + memory.write(offsetof(DefaultData, cur_mod), val); + return true; + } else { + out("MemoryManager: Error set_cur_mod: out of memory\n"); + return false; + } + } + + void mod_memory_shift(uint16_t src_addr, uint16_t dst_addr) { + Mod mod; + memory.read(src_addr, mod); + memory.write(dst_addr, mod); + + //printf("src:%d dst:%d next_addr:%d id:%d size:%d\n", ); + for (uint16_t i = 0; i < mod.size; ++i) { + uint8_t tmp; + memory.read(src_addr + sizeof(Mod) + i, tmp); + memory.write(dst_addr + sizeof(Mod) + i, tmp); + } + + if (cur_mod_addr == src_addr) { + set_cur_mod(dst_addr); + } + } + + // ребалансировка памяти, нужна когда закончилось место в конце файла и были + // удалены какие то режимы. Т.е. в памяти есть пустые не используемы участки. + // Все данные смещаются так, чтобы закрыть неиспользуемые пробелы памяти + void rebalance_mod_list() { + uint16_t cur_addr = get_first_mod_addr(); + uint16_t offset = 0; + Mod mod; + + if (!cur_addr) { // если режимов нет, ливаем + return; + } + + if (cur_addr != sizeof(DefaultData)) { + mod_memory_shift(cur_addr, sizeof(DefaultData)); + cur_addr = sizeof(DefaultData); + memory.write(offsetof(DefaultData, first_mod_addr), cur_addr); + } + memory.read(cur_addr, mod); + + while (mod.next_addr) { + offset = mod.next_addr - cur_addr - mod.size - sizeof(Mod); + if (offset) { + uint16_t cur_offset = mod.next_addr - offset; + mod_memory_shift(mod.next_addr, cur_offset); + mod.next_addr = cur_offset; + } + memory.write(cur_addr, mod.next_addr); + cur_addr = mod.next_addr; + memory.read(cur_addr, mod); + } + } + + bool add_mod_with_rebalace(uint8_t mod_id, uint16_t mod_size, bool need_rebalace) { + uint16_t addr = cur_mod_addr; + uint16_t addr_tmp = addr; + uint32_t new_addr; + uint16_t size; + + if (addr) { + while (addr_tmp) { // ищем адрес последнего режима + memory.read(addr, addr_tmp); // читаем значение по текущему адресу и кладём в переменную этого адреса + addr = addr_tmp? addr_tmp : addr; + } + memory.read(addr + offsetof(Mod, size), size); // читаем сайз последнего режима + new_addr = (uint32_t)addr + size + sizeof(Mod); // добавляем к текущему адресу сайз и размер структуры Mod, получаем адрес следующего режима + } else { + addr = offsetof(DefaultData, first_mod_addr); + new_addr = sizeof(DefaultData); + } + + if (new_addr + sizeof(Mod) + mod_size < MAX_FILE_SIZE) { + memory.write(addr, static_cast(new_addr)); + memory.write(new_addr, Mod{0, mod_id, mod_size}); + set_cur_mod(new_addr); + return true; + } else { + if (need_rebalace) { + rebalance_mod_list(); + return add_mod_with_rebalace(mod_id, mod_size, false); + } else { + out("MemoryManager: Error add_new_mod: out of memory\n"); + return false; + } + } + } + + uint16_t get_prev_link_addr(uint16_t addr) { + if (!addr || !cur_mod_addr) { + return 0; + } + + if (addr < sizeof(DefaultData)) { + out("MemoryManager: Error get_prev_link_addr\n"); + return 0; + } + + uint16_t prev_addr = offsetof(DefaultData, first_mod_addr); + uint16_t cur_addr = get_first_mod_addr(); + while (cur_addr != addr && cur_addr) { + prev_addr = cur_addr; + memory.read(cur_addr, cur_addr); + } + + if (cur_addr == addr) { + return prev_addr; + } else { + out("MemoryManager: Error get_prev_link_addr: mod not found\n"); + } + return 0; + } + + uint16_t get_mod_addr_by_num(uint8_t num, uint16_t &prev_addr) { + prev_addr = offsetof(DefaultData, first_mod_addr); + uint16_t cur_addr = get_first_mod_addr(); + uint16_t mod_num = 1; + + if (cur_addr) { + for (; mod_num != num && cur_addr; ++mod_num) { + prev_addr = cur_addr; + memory.read(cur_addr, cur_addr); + } + if (mod_num == num) { + return cur_addr; + } else { + out("MemoryManager: Error get_mod_addr_by_num: mod not found\n"); + } + } + return 0; + } + + uint16_t get_mod_addr_by_num(uint8_t num) { + uint16_t prev_addr; + return get_mod_addr_by_num(num, prev_addr); + } + + bool remove_mode_by_addr(uint16_t addr, uint16_t prev_addr) { + uint16_t next_addr; + + if (!cur_mod_addr || !addr || !prev_addr) { + return false; + } + + memory.read(addr, next_addr); + memory.write(prev_addr, next_addr); + + if (!next_addr && prev_addr == offsetof(DefaultData, first_mod_addr)) { // если режимов больше нет + set_cur_mod(0); + } else if (cur_mod_addr == addr) { // если удалённый режим был текущим + if (!next_addr) { // если удалили последний режим + set_cur_mod(prev_addr); // устанавливаем в качестве текущего предыдущий + } else { + set_cur_mod(next_addr); // иначе следующий + } + } + + return true; + } + + bool remove_mode_by_addr(uint16_t addr) { + return remove_mode_by_addr(addr, get_prev_link_addr(addr)); + } + +public: + ~MemoryManager() {} + + static MemoryManager& instance() { + static MemoryManager instance(MM_FILE_NAME); + return instance; + } + + void clear_memory() { + memory.clear(); + } + + // ----------------- Функции изменения списка модов ----------------- + + // Добавление нового режима в конец списка с заданным id и size. + // После добавления режима, указатель на текущий режим установится + // на только что добавленный. + // Все поля аргументов режима могут содержать мусор, поэтому нужно их + // инициализировать. Значения аргументов режима устанавливается + // функцией save_mod_var + bool add_mod(uint8_t id, uint16_t size) { + return add_mod_with_rebalace(id, size, true); + } + + // Удаление текущего выбранного режима + // При удалении в качестве текущего выбирается следующий режим, + // если он существует, иначе предыдущий. Если режимов не осталось, + // текущей режим будет установлен в 0. + bool remove_mod() { + return remove_mode_by_addr(cur_mod_addr); + } + + // Удаление режима в определённой позиции. + bool remove_mod(uint8_t num) { + uint16_t prev_addr; + uint16_t addr = get_mod_addr_by_num(num, prev_addr); + return remove_mode_by_addr(addr, prev_addr); + } + + // Уданение всех режимов из памяти (по сути зануление указателя на первый режим) + void remove_all_mods() { + set_cur_mod(0); + memory.write(offsetof(DefaultData, first_mod_addr), 0); + } + + // --------------- Функции выбора текущего режима ---------------- + + // Сменить указатель текущего режима на следующий. + // Если текущий режим последний, то выбирается первый. + void next_mod() { + if (!cur_mod_addr) { + return; + } + uint16_t addr = 0; + memory.read(cur_mod_addr, cur_mod_addr); + + if (!cur_mod_addr) { + cur_mod_addr = get_first_mod_addr(); + } + set_cur_mod(cur_mod_addr); + } + + // Сменить указатель текущего режима на предыдущий. + // Если текущий режим первый, то выбирается последний. + void prev_mod() { + if (!cur_mod_addr) { + return; + } + cur_mod_addr = get_prev_link_addr(cur_mod_addr); + if (cur_mod_addr == offsetof(DefaultData, first_mod_addr)) { + uint16_t addr = cur_mod_addr; + while (addr) { + cur_mod_addr = addr; + memory.read(addr, addr); + } + } + set_cur_mod(cur_mod_addr); + } + + // Установить указатель текущего режима в конкретную позицию. + // начальный режим имеет индекс 1 + bool set_mod(uint8_t num) { + if (!cur_mod_addr || num == 0) { + return false; + } + uint16_t mod_addr = get_first_mod_addr(); + for(int i = 1; i < num && mod_addr; ++i) { + memory.read(mod_addr, mod_addr); + } + if (mod_addr) { + return set_cur_mod(mod_addr); + } else { + return false; + } + } + + // получить номер текущего режима в памяти + // начальный режим имеет индекс 1 + uint8_t get_cur_mod_num() { + if (!cur_mod_addr) { + return 0; + } + uint8_t num = 1; + uint16_t addr = get_first_mod_addr(); + while (addr != cur_mod_addr && addr) { + memory.read(addr, addr); + num++; + } + return num; + } + + // получить кол-во режимов из памяти + uint8_t get_mod_amount() { + uint16_t addr = get_first_mod_addr(); + uint8_t mods_amt = 0; + + while (addr) { + memory.read(addr, addr); + mods_amt++; + } + return mods_amt; + } + + // получить id всех режимов из памяти + // возвращает область памяти со списком режимов + std::unique_ptr get_mod_list() { + uint16_t faddr = get_first_mod_addr(); + uint16_t addr = faddr; + uint8_t mods_amt = 0; + + while (addr) { + memory.read(addr, addr); + mods_amt++; + } + std::unique_ptr ids(new uint8_t[mods_amt]); + uint8_t cur_mod = 0; + while (faddr) { + memory.read(faddr + offsetof(Mod, id), ids[cur_mod]); + cur_mod++; + memory.read(faddr, faddr); + } + return ids; + } + + // --------------- Функции работы с текущим режимом ---------------- + + void load_mod_id(uint8_t &val) { + if (!cur_mod_addr) { + return; + } + memory.read(cur_mod_addr + offsetof(Mod, id), val); + } + + void load_mod_size(uint16_t &val) { + if (!cur_mod_addr) { + return; + } + memory.read(cur_mod_addr + offsetof(Mod, size), val); + } + + template + bool load_mod_var(uint8_t offset, T &val) { + if (!cur_mod_addr) { + return false; + } + uint16_t size; + memory.read(cur_mod_addr + offsetof(Mod, size), size); + if (offset + sizeof(T) <= size) { + memory.read(cur_mod_addr + sizeof(Mod) + offset, val); + return true; + } else { + out("MemoryManager: Error load_mod_var: offset out of range\n"); + } + return false; + } + + // Сохранение значений текущего режима в память. + // offset считается относительно начала переменных самого режима. + // offset = 0 это первый аргумент режима + // offset = sizeof(first_var) это второй аргумент и т.д. + template + bool save_mod_var(uint16_t offset, T &val) { + uint16_t size; + memory.read(cur_mod_addr + offsetof(Mod, size), size); + if (offset + sizeof(T) <= size) { + // write delay; + memory.write(cur_mod_addr + sizeof(Mod) + offset, val); + return true; + } else { + out("MemoryManager: Error save_mod_var: offset out of range\n"); + } + return false; + } +}; \ No newline at end of file diff --git a/src/properties/property.h b/src/properties/property.h index bcccb57..ed0a443 100644 --- a/src/properties/property.h +++ b/src/properties/property.h @@ -1,33 +1,65 @@ #pragma once -#include "libs/debug_lib.h" #include "property_storage.h" +#include "memory_manager.h" +#include "stdint.h" class ISaveable { + uint16_t offset = -1; public: virtual ~ISaveable() = default; - virtual void save() const = 0; + virtual void save() = 0; virtual void load() = 0; + virtual void set_offset(uint16_t val) { + offset = val; + } + virtual uint16_t get_offset() { + return offset; + } }; -class IProperty : ISaveable { +class IProperty : public ISaveable { public: virtual ~IProperty() = default; - virtual void save() const = 0; + virtual void save() = 0; virtual void load() = 0; - virtual uint16_t size() const = 0; + virtual size_t size() const = 0; }; template -class Property : IProperty { - T m_value; - T min_val; +class Property : public IProperty { + T m_val; T max_val; + T min_val; + + Property(const Property&) = delete; + Property(Property&&) = delete; + Property& operator=(const Property&) = delete; + Property& operator=(Property&&) = delete; + // установка значения без сохранения в память. + // Возвращает: 0 если значение успешно установлено + // 1 если входное значение равно старому значению + // 2 если установлен диапозон и значение не попадает в установленный диапозон + bool set_without_save(T new_val) { + if (m_val != new_val) { //старое значение не равно новому + if (min_val == max_val || // не определены занчения min max + new_val >= min_val && new_val <= max_val) // новое значение попадает в диапозон + { + m_val = new_val; + return 0; + } else { + return 2; + } + } + return 1; + } public: - Property(T value, T min, T max) : m_value(value), min_val(min), max_val(max) - { - PropertyStorage::instance().add(this); + + Property(T val, T min, T max): m_val(min), min_val(min), max_val(max) { + set_without_save(val); // устанавливает новое значение m_val, либо оставляет min_val + PropertyStorage::instance().add_property(this); // + инициализация offset + printf("new var\n"); } Property(T min, T max) : Property({}, min, max) { } Property(T val) : Property(val, {}, {}) { } @@ -35,54 +67,36 @@ class Property : IProperty { ~Property() { - PropertyStorage::instance().clear(this); + PropertyStorage::instance().clear(); } - Property(const Property&) = delete; - Property(Property&&) = delete; - - Property& operator=(const Property&) = delete; - Property& operator=(Property&&) = delete; - - Property& operator=(T value) - { - m_value = value; - return *this; + T get() { + return m_val; } - operator T() const - { return m_value; } - - T& operator*() - { return m_value; } - - const T& operator*() const - { return m_value; } - - T& operator->() - { return m_value; } - - const T& operator->() const - { return m_value; } - - void save() const override - {} + void set(T new_val) { + if (set_without_save(new_val) == 0) { + save(); + } + } - void load() override - {} + // void *row() { + // return &m_val; + // } - uint16_t size() const override - { - return sizeof(T); + void save() override { + MemoryManager::instance().save_mod_var(get_offset(), m_val); } - T get_min() - { - return min_val; + void load() override { + T val = {}; + MemoryManager::instance().load_mod_var(get_offset(), val); + if (set_without_save(val) == 2) { + out("Error load Property: value out of defined range\n"); + } } - T get_max() - { - return max_val; + size_t size() const override { + return sizeof(T); } -}; +}; \ No newline at end of file diff --git a/src/properties/property_storage.cpp b/src/properties/property_storage.cpp index 0561de4..84b5102 100644 --- a/src/properties/property_storage.cpp +++ b/src/properties/property_storage.cpp @@ -1,46 +1,35 @@ +#include "property_storage.h" #include "property.h" -void PropertyStorage::set_offset(uint16_t offset) { - addr_offset = offset; -} -uint16_t PropertyStorage::get_offset() { - return addr_offset; -} - -uint8_t PropertyStorage::get_mod_size() { - uint32_t size = 0; - - for (auto it : items) { - size += it->size(); +void PropertyStorage::add_property(IProperty *prop) { + if (props.size()) { + prop->set_offset(size()); + } else { + prop->set_offset(0); } - if (size > 0xff) { - out("ERROR: Mod size is very big. More then 255\n"); - return 0; - } - return size; + props.push_back(prop); } -void PropertyStorage::add(IProperty* item) -{ items.push_back(item); } - -void PropertyStorage::clear(IProperty* item) -{ - if (items.size() > 0) { - items.clear(); +void PropertyStorage::load_all_propertyes() { + for (auto &it: props) { + it->load(); } } -void PropertyStorage::load() -{ - for (auto it : items) { - it->load(); +void PropertyStorage::save_all_propertyes() { + for (auto &it: props) { + it->save(); } } -void PropertyStorage::save() const -{ - for (auto it : items) { - it->save(); +void PropertyStorage::clear() { + if (props.size()) { + props.clear(); } +} + +uint16_t PropertyStorage::size() { + IProperty* prop = props.back(); + return prop->get_offset() + prop->size(); } \ No newline at end of file diff --git a/src/properties/property_storage.h b/src/properties/property_storage.h index 088706d..5fdb223 100644 --- a/src/properties/property_storage.h +++ b/src/properties/property_storage.h @@ -1,36 +1,27 @@ #pragma once -#include +#include "vector" +#include "stdint.h" class IProperty; class PropertyStorage { - std::vector items; - uint16_t addr_offset = -1; - - PropertyStorage() = default; - ~PropertyStorage() = default; - - PropertyStorage(const PropertyStorage&) = delete; - PropertyStorage(PropertyStorage&&) = delete; - - PropertyStorage& operator=(const PropertyStorage&) = delete; - PropertyStorage& operator=(PropertyStorage&&) = delete; + std::vector props; + // singlton property + // Конструктор копирования и оператор присваивания копированием недоступны + PropertyStorage() {}; + PropertyStorage(const PropertyStorage& ) = delete; + PropertyStorage& operator=(const PropertyStorage& ) = delete; public: - static PropertyStorage& instance() - { - static PropertyStorage obj; - return obj; + static PropertyStorage& instance() { + static PropertyStorage instance; + return instance; } - void set_offset(uint16_t offset); - uint16_t get_offset(); - uint8_t get_mod_size(); - - void add(IProperty* item); - void clear(IProperty* item); - - void save() const; - void load(); -}; \ No newline at end of file + void add_property(IProperty *prop); + void load_all_propertyes(); + void save_all_propertyes(); + void clear(); + uint16_t size(); +}; From eb4f3db354e433356c7ee0adbbb4c773813b331f Mon Sep 17 00:00:00 2001 From: npo6ka Date: Tue, 27 May 2025 18:34:41 +0300 Subject: [PATCH 52/75] split memory manager into two files --- src/properties/memory_manager.cpp | 338 ++++++++++++++++++++++++++++ src/properties/memory_manager.h | 351 +++--------------------------- 2 files changed, 367 insertions(+), 322 deletions(-) create mode 100644 src/properties/memory_manager.cpp diff --git a/src/properties/memory_manager.cpp b/src/properties/memory_manager.cpp new file mode 100644 index 0000000..d7c607e --- /dev/null +++ b/src/properties/memory_manager.cpp @@ -0,0 +1,338 @@ +#include "memory_manager.h" + +MemoryManager::MemoryManager(const char* filename) { + memory = MemoryHandler{}; + memory.openFile(filename); + memory.read(offsetof(DefaultData, cur_mod), cur_mod_addr); +} + +uint16_t MemoryManager::get_first_mod_addr() { + uint16_t addr = 0; + memory.read(offsetof(DefaultData, first_mod_addr), addr); + return addr; +} + +bool MemoryManager::set_cur_mod(uint16_t val) { + if (val == 0 || val >= sizeof(DefaultData)) { + cur_mod_addr = val; + memory.write(offsetof(DefaultData, cur_mod), val); + return true; + } else { + out("MemoryManager: Error set_cur_mod: out of memory\n"); + return false; + } +} + +void MemoryManager::mod_memory_shift(uint16_t src_addr, uint16_t dst_addr) { + Mod mod; + memory.read(src_addr, mod); + memory.write(dst_addr, mod); + + for (uint16_t i = 0; i < mod.size; ++i) { + uint8_t tmp; + memory.read(src_addr + sizeof(Mod) + i, tmp); + memory.write(dst_addr + sizeof(Mod) + i, tmp); + } + + if (cur_mod_addr == src_addr) { + set_cur_mod(dst_addr); + } +} + +void MemoryManager::rebalance_mod_list() { + uint16_t cur_addr = get_first_mod_addr(); + uint16_t offset = 0; + Mod mod; + + if (!cur_addr) { + return; + } + + if (cur_addr != sizeof(DefaultData)) { + mod_memory_shift(cur_addr, sizeof(DefaultData)); + cur_addr = sizeof(DefaultData); + memory.write(offsetof(DefaultData, first_mod_addr), cur_addr); + } + memory.read(cur_addr, mod); + + while (mod.next_addr) { + offset = mod.next_addr - cur_addr - mod.size - sizeof(Mod); + if (offset) { + uint16_t cur_offset = mod.next_addr - offset; + mod_memory_shift(mod.next_addr, cur_offset); + mod.next_addr = cur_offset; + } + memory.write(cur_addr, mod.next_addr); + cur_addr = mod.next_addr; + memory.read(cur_addr, mod); + } +} + +bool MemoryManager::add_mod_with_rebalace(uint8_t mod_id, uint16_t mod_size, bool need_rebalace) { + uint16_t addr = cur_mod_addr; + uint16_t addr_tmp = addr; + uint32_t new_addr; + uint16_t size; + + if (addr) { + while (addr_tmp) { + memory.read(addr, addr_tmp); + addr = addr_tmp? addr_tmp : addr; + } + memory.read(addr + offsetof(Mod, size), size); + new_addr = (uint32_t)addr + size + sizeof(Mod); + } else { + addr = offsetof(DefaultData, first_mod_addr); + new_addr = sizeof(DefaultData); + } + + if (new_addr + sizeof(Mod) + mod_size < MAX_FILE_SIZE) { + memory.write(addr, static_cast(new_addr)); + memory.write(new_addr, Mod{0, mod_id, mod_size}); + set_cur_mod(new_addr); + return true; + } else { + if (need_rebalace) { + rebalance_mod_list(); + return add_mod_with_rebalace(mod_id, mod_size, false); + } else { + out("MemoryManager: Error add_new_mod: out of memory\n"); + return false; + } + } +} + +uint16_t MemoryManager::get_prev_link_addr(uint16_t addr) { + if (!addr || !cur_mod_addr) { + return 0; + } + + if (addr < sizeof(DefaultData)) { + out("MemoryManager: Error get_prev_link_addr\n"); + return 0; + } + + uint16_t prev_addr = offsetof(DefaultData, first_mod_addr); + uint16_t cur_addr = get_first_mod_addr(); + while (cur_addr != addr && cur_addr) { + prev_addr = cur_addr; + memory.read(cur_addr, cur_addr); + } + + if (cur_addr == addr) { + return prev_addr; + } else { + out("MemoryManager: Error get_prev_link_addr: mod not found\n"); + } + return 0; +} + +uint16_t MemoryManager::get_mod_addr_by_num(uint8_t num, uint16_t &prev_addr) { + prev_addr = offsetof(DefaultData, first_mod_addr); + uint16_t cur_addr = get_first_mod_addr(); + uint16_t mod_num = 1; + + if (cur_addr) { + for (; mod_num != num && cur_addr; ++mod_num) { + prev_addr = cur_addr; + memory.read(cur_addr, cur_addr); + } + if (mod_num == num) { + return cur_addr; + } else { + out("MemoryManager: Error get_mod_addr_by_num: mod not found\n"); + } + } + return 0; +} + +uint16_t MemoryManager::get_mod_addr_by_num(uint8_t num) { + uint16_t prev_addr; + return get_mod_addr_by_num(num, prev_addr); +} + +bool MemoryManager::remove_mode_by_addr(uint16_t addr, uint16_t prev_addr) { + uint16_t next_addr; + + if (!cur_mod_addr || !addr || !prev_addr) { + return false; + } + + memory.read(addr, next_addr); + memory.write(prev_addr, next_addr); + + if (!next_addr && prev_addr == offsetof(DefaultData, first_mod_addr)) { + set_cur_mod(0); + } else if (cur_mod_addr == addr) { + if (!next_addr) { + set_cur_mod(prev_addr); + } else { + set_cur_mod(next_addr); + } + } + + return true; +} + +bool MemoryManager::remove_mode_by_addr(uint16_t addr) { + return remove_mode_by_addr(addr, get_prev_link_addr(addr)); +} + +MemoryManager::~MemoryManager() {} + +MemoryManager& MemoryManager::instance() { + static MemoryManager instance(MM_FILE_NAME); + return instance; +} + +void MemoryManager::clear_meёmory() { + memory.clear(); +} + +bool MemoryManager::add_mod(uint8_t id, uint16_t size) { + return add_mod_with_rebalace(id, size, true); +} + +bool MemoryManager::remove_mod() { + return remove_mode_by_addr(cur_mod_addr); +} + +bool MemoryManager::remove_mod(uint8_t num) { + uint16_t prev_addr; + uint16_t addr = get_mod_addr_by_num(num, prev_addr); + return remove_mode_by_addr(addr, prev_addr); +} + +void MemoryManager::remove_all_mods() { + set_cur_mod(0); + memory.write(offsetof(DefaultData, first_mod_addr), 0); +} + +void MemoryManager::next_mod() { + if (!cur_mod_addr) { + return; + } + uint16_t addr = 0; + memory.read(cur_mod_addr, cur_mod_addr); + + if (!cur_mod_addr) { + cur_mod_addr = get_first_mod_addr(); + } + set_cur_mod(cur_mod_addr); +} + +void MemoryManager::prev_mod() { + if (!cur_mod_addr) { + return; + } + cur_mod_addr = get_prev_link_addr(cur_mod_addr); + if (cur_mod_addr == offsetof(DefaultData, first_mod_addr)) { + uint16_t addr = cur_mod_addr; + while (addr) { + cur_mod_addr = addr; + memory.read(addr, addr); + } + } + set_cur_mod(cur_mod_addr); +} + +bool MemoryManager::set_mod(uint8_t num) { + if (!cur_mod_addr || num == 0) { + return false; + } + uint16_t mod_addr = get_first_mod_addr(); + for(int i = 1; i < num && mod_addr; ++i) { + memory.read(mod_addr, mod_addr); + } + if (mod_addr) { + return set_cur_mod(mod_addr); + } else { + return false; + } +} + +uint8_t MemoryManager::get_cur_mod_num() { + if (!cur_mod_addr) { + return 0; + } + uint8_t num = 1; + uint16_t addr = get_first_mod_addr(); + while (addr != cur_mod_addr && addr) { + memory.read(addr, addr); + num++; + } + return num; +} + +uint8_t MemoryManager::get_mod_amount() { + uint16_t addr = get_first_mod_addr(); + uint8_t mods_amt = 0; + + while (addr) { + memory.read(addr, addr); + mods_amt++; + } + return mods_amt; +} + +std::unique_ptr MemoryManager::get_mod_list() { + uint16_t faddr = get_first_mod_addr(); + uint16_t addr = faddr; + uint8_t mods_amt = 0; + + while (addr) { + memory.read(addr, addr); + mods_amt++; + } + std::unique_ptr ids(new uint8_t[mods_amt]); + uint8_t cur_mod = 0; + while (faddr) { + memory.read(faddr + offsetof(Mod, id), ids[cur_mod]); + cur_mod++; + memory.read(faddr, faddr); + } + return ids; +} + +void MemoryManager::load_mod_id(uint8_t &val) { + if (!cur_mod_addr) { + return; + } + memory.read(cur_mod_addr + offsetof(Mod, id), val); +} + +void MemoryManager::load_mod_size(uint16_t &val) { + if (!cur_mod_addr) { + return; + } + memory.read(cur_mod_addr + offsetof(Mod, size), val); +} + +template +bool MemoryManager::load_mod_var(uint8_t offset, T &val) { + if (!cur_mod_addr) { + return false; + } + uint16_t size; + memory.read(cur_mod_addr + offsetof(Mod, size), size); + if (offset + sizeof(T) <= size) { + memory.read(cur_mod_addr + sizeof(Mod) + offset, val); + return true; + } else { + out("MemoryManager: Error load_mod_var: offset out of range\n"); + } + return false; +} + +template +bool MemoryManager::save_mod_var(uint16_t offset, T &val) { + uint16_t size; + memory.read(cur_mod_addr + offsetof(Mod, size), size); + if (offset + sizeof(T) <= size) { + memory.write(cur_mod_addr + sizeof(Mod) + offset, val); + return true; + } else { + out("MemoryManager: Error save_mod_var: offset out of range\n"); + } + return false; +} \ No newline at end of file diff --git a/src/properties/memory_manager.h b/src/properties/memory_manager.h index c12dbff..844a1aa 100644 --- a/src/properties/memory_manager.h +++ b/src/properties/memory_manager.h @@ -23,205 +23,32 @@ class MemoryManager { }; uint16_t cur_mod_addr; // инициализируется при создании. 0, если режимов нет. - Memory memory; - - MemoryManager(const char* filename) { - memory = Memory{filename}; - memory.read(offsetof(DefaultData, cur_mod), cur_mod_addr); - } + MemoryHandler memory; + MemoryManager(const char* filename); MemoryManager(const MemoryManager&) = delete; MemoryManager& operator= (const MemoryManager&) = delete; // возвращает 0 если режимов нет - uint16_t get_first_mod_addr() { - uint16_t addr = 0; - memory.read(offsetof(DefaultData, first_mod_addr), addr); - return addr; - } - - bool set_cur_mod(uint16_t val) { - if (val == 0 || val >= sizeof(DefaultData)) { - cur_mod_addr = val; -// write delay - memory.write(offsetof(DefaultData, cur_mod), val); - return true; - } else { - out("MemoryManager: Error set_cur_mod: out of memory\n"); - return false; - } - } - - void mod_memory_shift(uint16_t src_addr, uint16_t dst_addr) { - Mod mod; - memory.read(src_addr, mod); - memory.write(dst_addr, mod); - - //printf("src:%d dst:%d next_addr:%d id:%d size:%d\n", ); - for (uint16_t i = 0; i < mod.size; ++i) { - uint8_t tmp; - memory.read(src_addr + sizeof(Mod) + i, tmp); - memory.write(dst_addr + sizeof(Mod) + i, tmp); - } - - if (cur_mod_addr == src_addr) { - set_cur_mod(dst_addr); - } - } + uint16_t get_first_mod_addr(); + bool set_cur_mod(uint16_t val); + void mod_memory_shift(uint16_t src_addr, uint16_t dst_addr); // ребалансировка памяти, нужна когда закончилось место в конце файла и были // удалены какие то режимы. Т.е. в памяти есть пустые не используемы участки. // Все данные смещаются так, чтобы закрыть неиспользуемые пробелы памяти - void rebalance_mod_list() { - uint16_t cur_addr = get_first_mod_addr(); - uint16_t offset = 0; - Mod mod; - - if (!cur_addr) { // если режимов нет, ливаем - return; - } - - if (cur_addr != sizeof(DefaultData)) { - mod_memory_shift(cur_addr, sizeof(DefaultData)); - cur_addr = sizeof(DefaultData); - memory.write(offsetof(DefaultData, first_mod_addr), cur_addr); - } - memory.read(cur_addr, mod); - - while (mod.next_addr) { - offset = mod.next_addr - cur_addr - mod.size - sizeof(Mod); - if (offset) { - uint16_t cur_offset = mod.next_addr - offset; - mod_memory_shift(mod.next_addr, cur_offset); - mod.next_addr = cur_offset; - } - memory.write(cur_addr, mod.next_addr); - cur_addr = mod.next_addr; - memory.read(cur_addr, mod); - } - } - - bool add_mod_with_rebalace(uint8_t mod_id, uint16_t mod_size, bool need_rebalace) { - uint16_t addr = cur_mod_addr; - uint16_t addr_tmp = addr; - uint32_t new_addr; - uint16_t size; - - if (addr) { - while (addr_tmp) { // ищем адрес последнего режима - memory.read(addr, addr_tmp); // читаем значение по текущему адресу и кладём в переменную этого адреса - addr = addr_tmp? addr_tmp : addr; - } - memory.read(addr + offsetof(Mod, size), size); // читаем сайз последнего режима - new_addr = (uint32_t)addr + size + sizeof(Mod); // добавляем к текущему адресу сайз и размер структуры Mod, получаем адрес следующего режима - } else { - addr = offsetof(DefaultData, first_mod_addr); - new_addr = sizeof(DefaultData); - } - - if (new_addr + sizeof(Mod) + mod_size < MAX_FILE_SIZE) { - memory.write(addr, static_cast(new_addr)); - memory.write(new_addr, Mod{0, mod_id, mod_size}); - set_cur_mod(new_addr); - return true; - } else { - if (need_rebalace) { - rebalance_mod_list(); - return add_mod_with_rebalace(mod_id, mod_size, false); - } else { - out("MemoryManager: Error add_new_mod: out of memory\n"); - return false; - } - } - } - - uint16_t get_prev_link_addr(uint16_t addr) { - if (!addr || !cur_mod_addr) { - return 0; - } - - if (addr < sizeof(DefaultData)) { - out("MemoryManager: Error get_prev_link_addr\n"); - return 0; - } - - uint16_t prev_addr = offsetof(DefaultData, first_mod_addr); - uint16_t cur_addr = get_first_mod_addr(); - while (cur_addr != addr && cur_addr) { - prev_addr = cur_addr; - memory.read(cur_addr, cur_addr); - } - - if (cur_addr == addr) { - return prev_addr; - } else { - out("MemoryManager: Error get_prev_link_addr: mod not found\n"); - } - return 0; - } - - uint16_t get_mod_addr_by_num(uint8_t num, uint16_t &prev_addr) { - prev_addr = offsetof(DefaultData, first_mod_addr); - uint16_t cur_addr = get_first_mod_addr(); - uint16_t mod_num = 1; - - if (cur_addr) { - for (; mod_num != num && cur_addr; ++mod_num) { - prev_addr = cur_addr; - memory.read(cur_addr, cur_addr); - } - if (mod_num == num) { - return cur_addr; - } else { - out("MemoryManager: Error get_mod_addr_by_num: mod not found\n"); - } - } - return 0; - } - - uint16_t get_mod_addr_by_num(uint8_t num) { - uint16_t prev_addr; - return get_mod_addr_by_num(num, prev_addr); - } - - bool remove_mode_by_addr(uint16_t addr, uint16_t prev_addr) { - uint16_t next_addr; - - if (!cur_mod_addr || !addr || !prev_addr) { - return false; - } - - memory.read(addr, next_addr); - memory.write(prev_addr, next_addr); - - if (!next_addr && prev_addr == offsetof(DefaultData, first_mod_addr)) { // если режимов больше нет - set_cur_mod(0); - } else if (cur_mod_addr == addr) { // если удалённый режим был текущим - if (!next_addr) { // если удалили последний режим - set_cur_mod(prev_addr); // устанавливаем в качестве текущего предыдущий - } else { - set_cur_mod(next_addr); // иначе следующий - } - } - - return true; - } - - bool remove_mode_by_addr(uint16_t addr) { - return remove_mode_by_addr(addr, get_prev_link_addr(addr)); - } + void rebalance_mod_list(); + bool add_mod_with_rebalace(uint8_t mod_id, uint16_t mod_size, bool need_rebalace); + uint16_t get_prev_link_addr(uint16_t addr); + uint16_t get_mod_addr_by_num(uint8_t num, uint16_t &prev_addr); + uint16_t get_mod_addr_by_num(uint8_t num); + bool remove_mode_by_addr(uint16_t addr, uint16_t prev_addr); + bool remove_mode_by_addr(uint16_t addr); public: - ~MemoryManager() {} - - static MemoryManager& instance() { - static MemoryManager instance(MM_FILE_NAME); - return instance; - } - - void clear_memory() { - memory.clear(); - } + ~MemoryManager(); + static MemoryManager& instance(); + void clear_meёmory(); // ----------------- Функции изменения списка модов ----------------- @@ -231,177 +58,57 @@ class MemoryManager { // Все поля аргументов режима могут содержать мусор, поэтому нужно их // инициализировать. Значения аргументов режима устанавливается // функцией save_mod_var - bool add_mod(uint8_t id, uint16_t size) { - return add_mod_with_rebalace(id, size, true); - } + bool add_mod(uint8_t id, uint16_t size); // Удаление текущего выбранного режима // При удалении в качестве текущего выбирается следующий режим, // если он существует, иначе предыдущий. Если режимов не осталось, // текущей режим будет установлен в 0. - bool remove_mod() { - return remove_mode_by_addr(cur_mod_addr); - } + bool remove_mod(); // Удаление режима в определённой позиции. - bool remove_mod(uint8_t num) { - uint16_t prev_addr; - uint16_t addr = get_mod_addr_by_num(num, prev_addr); - return remove_mode_by_addr(addr, prev_addr); - } + bool remove_mod(uint8_t num); // Уданение всех режимов из памяти (по сути зануление указателя на первый режим) - void remove_all_mods() { - set_cur_mod(0); - memory.write(offsetof(DefaultData, first_mod_addr), 0); - } + void remove_all_mods(); // --------------- Функции выбора текущего режима ---------------- // Сменить указатель текущего режима на следующий. // Если текущий режим последний, то выбирается первый. - void next_mod() { - if (!cur_mod_addr) { - return; - } - uint16_t addr = 0; - memory.read(cur_mod_addr, cur_mod_addr); - - if (!cur_mod_addr) { - cur_mod_addr = get_first_mod_addr(); - } - set_cur_mod(cur_mod_addr); - } + void next_mod(); // Сменить указатель текущего режима на предыдущий. // Если текущий режим первый, то выбирается последний. - void prev_mod() { - if (!cur_mod_addr) { - return; - } - cur_mod_addr = get_prev_link_addr(cur_mod_addr); - if (cur_mod_addr == offsetof(DefaultData, first_mod_addr)) { - uint16_t addr = cur_mod_addr; - while (addr) { - cur_mod_addr = addr; - memory.read(addr, addr); - } - } - set_cur_mod(cur_mod_addr); - } + void prev_mod(); // Установить указатель текущего режима в конкретную позицию. // начальный режим имеет индекс 1 - bool set_mod(uint8_t num) { - if (!cur_mod_addr || num == 0) { - return false; - } - uint16_t mod_addr = get_first_mod_addr(); - for(int i = 1; i < num && mod_addr; ++i) { - memory.read(mod_addr, mod_addr); - } - if (mod_addr) { - return set_cur_mod(mod_addr); - } else { - return false; - } - } + bool set_mod(uint8_t num); // получить номер текущего режима в памяти // начальный режим имеет индекс 1 - uint8_t get_cur_mod_num() { - if (!cur_mod_addr) { - return 0; - } - uint8_t num = 1; - uint16_t addr = get_first_mod_addr(); - while (addr != cur_mod_addr && addr) { - memory.read(addr, addr); - num++; - } - return num; - } + uint8_t get_cur_mod_num(); // получить кол-во режимов из памяти - uint8_t get_mod_amount() { - uint16_t addr = get_first_mod_addr(); - uint8_t mods_amt = 0; - - while (addr) { - memory.read(addr, addr); - mods_amt++; - } - return mods_amt; - } + uint8_t get_mod_amount(); // получить id всех режимов из памяти // возвращает область памяти со списком режимов - std::unique_ptr get_mod_list() { - uint16_t faddr = get_first_mod_addr(); - uint16_t addr = faddr; - uint8_t mods_amt = 0; - - while (addr) { - memory.read(addr, addr); - mods_amt++; - } - std::unique_ptr ids(new uint8_t[mods_amt]); - uint8_t cur_mod = 0; - while (faddr) { - memory.read(faddr + offsetof(Mod, id), ids[cur_mod]); - cur_mod++; - memory.read(faddr, faddr); - } - return ids; - } + std::unique_ptr get_mod_list(); // --------------- Функции работы с текущим режимом ---------------- - void load_mod_id(uint8_t &val) { - if (!cur_mod_addr) { - return; - } - memory.read(cur_mod_addr + offsetof(Mod, id), val); - } - - void load_mod_size(uint16_t &val) { - if (!cur_mod_addr) { - return; - } - memory.read(cur_mod_addr + offsetof(Mod, size), val); - } + void load_mod_id(uint8_t &val); + void load_mod_size(uint16_t &val); template - bool load_mod_var(uint8_t offset, T &val) { - if (!cur_mod_addr) { - return false; - } - uint16_t size; - memory.read(cur_mod_addr + offsetof(Mod, size), size); - if (offset + sizeof(T) <= size) { - memory.read(cur_mod_addr + sizeof(Mod) + offset, val); - return true; - } else { - out("MemoryManager: Error load_mod_var: offset out of range\n"); - } - return false; - } + bool load_mod_var(uint8_t offset, T &val); // Сохранение значений текущего режима в память. // offset считается относительно начала переменных самого режима. // offset = 0 это первый аргумент режима // offset = sizeof(first_var) это второй аргумент и т.д. template - bool save_mod_var(uint16_t offset, T &val) { - uint16_t size; - memory.read(cur_mod_addr + offsetof(Mod, size), size); - if (offset + sizeof(T) <= size) { - // write delay; - memory.write(cur_mod_addr + sizeof(Mod) + offset, val); - return true; - } else { - out("MemoryManager: Error save_mod_var: offset out of range\n"); - } - return false; - } + bool save_mod_var(uint16_t offset, T &val); }; \ No newline at end of file From 676c5885edd2dd8e16acd5fc1f21219b84b59085 Mon Sep 17 00:00:00 2001 From: npo6ka Date: Tue, 27 May 2025 18:35:27 +0300 Subject: [PATCH 53/75] refactoring file handler --- src/properties/memory_handler.h | 96 +++++++++++++++------------------ 1 file changed, 44 insertions(+), 52 deletions(-) diff --git a/src/properties/memory_handler.h b/src/properties/memory_handler.h index 67ac894..41afc3a 100644 --- a/src/properties/memory_handler.h +++ b/src/properties/memory_handler.h @@ -1,7 +1,6 @@ #pragma once -// #include "LittleFS.h" -#include +#include "LittleFS.h" #include #include @@ -9,66 +8,59 @@ #define MAX_FILE_SIZE 65536 #define STEP -class Memory { - FILE *fp = nullptr; +class MemoryHandler { + FILE *_fp = nullptr; - // size_t size() { - // fseek(fp, 0L, SEEK_END); - // return ftell(fp); - // } - - void open_file(const char *filename) { - fp = fopen(filename, "r+"); - if (!fp) { - if (fp = fopen(filename, "w")) { - fclose(fp); - } - fp = fopen(filename, "r+"); - } - fseek(fp, MAX_FILE_SIZE - 1, SEEK_SET); - fputc('\0', fp); - } - - // template - // void validate_memory(uint32_t addr, T &val) { - // size_t size = size(); - // if (size < addr + sizeof(val)) { - - // } - // } - - Memory(const Memory&) = delete; - Memory& operator= (const Memory&) = delete; + MemoryHandler(const MemoryHandler&) = delete; + MemoryHandler& operator= (const MemoryHandler&) = delete; public: - Memory() {} - Memory(const char* filename) { - open_file(filename/*"mods.dat"*/); - } + MemoryHandler() {} - ~Memory() { - fclose(fp); + ~MemoryHandler() { + if (_fp) { + fclose(_fp); + } } - Memory(Memory&& other){ - fp = other.fp; - other.fp = nullptr; + MemoryHandler(MemoryHandler&& other){ + _fp = other._fp; + other._fp = nullptr; }; - Memory& operator= (Memory&& other) { - fp = other.fp; - other.fp = nullptr; + MemoryHandler& operator= (MemoryHandler&& other) { + _fp = other._fp; + other._fp = nullptr; return *this; } + void openFile(const char *filename) { + if (strlen(filename) > 32) { + out("Error opening file: filename is too long\n"); + return; + } + if (!_fp) { + if (_fp = fopen(filename, "w")) { + fclose(_fp); + } + _fp = fopen(filename, "r+"); + } + if (fseek(_fp, MAX_FILE_SIZE - 1, SEEK_SET) != 0) { + out("Error opening file: failed to seek to end of file\n"); + _fp = nullptr; + return; + } + fputc('\0', _fp); + } + template bool read(uint32_t addr, T &val) { - if (!fp) { + if (!_fp) { out("Error reading ROM: memory file is NULL\n"); return false; } if (addr + sizeof(T) < MAX_FILE_SIZE) { - fseek(fp, addr, SEEK_SET); - return fread(&val, sizeof(T), 1, fp) == 1; + fseek(_fp, addr, SEEK_SET); + return fread(&val, sizeof(T), 1, _fp) == 1; } else { out("Error reading ROM: position out of memory\n"); } @@ -78,14 +70,14 @@ class Memory { template bool write(uint32_t addr, const T val) { - if (!fp) { + if (!_fp) { out("Error writing ROM: memory file is NULL\n"); return false; } if (addr + sizeof(T) < MAX_FILE_SIZE) { - fseek(fp, addr, SEEK_SET); + fseek(_fp, addr, SEEK_SET); // printf("write to 0x%x, res: %d\n", addr, fwrite(&val, sizeof(T), 1, fp) == 1); - fwrite(&val, sizeof(T), 1, fp); + fwrite(&val, sizeof(T), 1, _fp); return true; } else { out("Error writing ROM: position out of memory\n"); @@ -95,15 +87,15 @@ class Memory { } void clear() { - if (!fp) { + if (!_fp) { out("Error writing ROM: memory file is NULL\n"); return; } - fseek(fp, 0, SEEK_SET); + fseek(_fp, 0, SEEK_SET); uint8_t val = 0; for (uint32_t i = 0; i < MAX_FILE_SIZE; ++i) { - fwrite(&val, sizeof(val), 1, fp); + fwrite(&val, sizeof(val), 1, _fp); } } }; \ No newline at end of file From eadf4fa07c91d921f32bf9cff32e783699ca18fa Mon Sep 17 00:00:00 2001 From: npo6ka Date: Wed, 9 Jul 2025 22:27:10 +0300 Subject: [PATCH 54/75] refactoring effect_list with memeory save --- cpp_memory/effect_list/effect.h | 55 --- cpp_memory/effect_list/effects/slow_random.h | 19 - .../effect_list/effects/slow_random_2.h | 20 - cpp_memory/effect_list/effectslist.cpp | 198 --------- cpp_memory/effect_list/effectslist.h | 45 -- cpp_memory/effect_list/erroreffect.h | 13 - cpp_memory/main.cpp | 132 ------ cpp_memory/main.exe | Bin 411317 -> 0 bytes cpp_memory/memory/memory_manger.h | 407 ------------------ cpp_memory/memory/property.h | 103 ----- cpp_memory/memory/property_storage.cpp | 35 -- cpp_memory/memory/property_storage.h | 27 -- cpp_memory/mods.dat | Bin 65536 -> 0 bytes platformio.ini | 5 +- src/controls/automode.cpp | 4 +- src/effect_list/effect.h | 1 + src/effect_list/effect_factory.cpp | 102 +++++ src/effect_list/effect_factory.h | 11 + src/effect_list/effect_manger.cpp | 132 ++++++ src/effect_list/effect_manger.h | 47 ++ src/effect_list/effects/dribs_all_side.h | 2 + src/effect_list/effects/rain.h | 7 +- src/effect_list/effectslist.cpp | 4 +- src/effect_list/effectslist.h | 4 +- src/effect_list/fps_manager.h | 48 +++ src/events/events.h | 9 +- .../memory_handler.h => libs/file_handler.h} | 20 +- src/main.cpp | 15 +- src/myapplication.cpp | 8 +- src/properties/memory_manager.cpp | 137 +++--- src/properties/memory_manager.h | 23 +- src/properties/property.h | 108 +++-- src/properties/property_storage.cpp | 27 +- src/properties/property_storage.h | 11 +- 34 files changed, 551 insertions(+), 1228 deletions(-) delete mode 100644 cpp_memory/effect_list/effect.h delete mode 100644 cpp_memory/effect_list/effects/slow_random.h delete mode 100644 cpp_memory/effect_list/effects/slow_random_2.h delete mode 100644 cpp_memory/effect_list/effectslist.cpp delete mode 100644 cpp_memory/effect_list/effectslist.h delete mode 100644 cpp_memory/effect_list/erroreffect.h delete mode 100644 cpp_memory/main.cpp delete mode 100644 cpp_memory/main.exe delete mode 100644 cpp_memory/memory/memory_manger.h delete mode 100644 cpp_memory/memory/property.h delete mode 100644 cpp_memory/memory/property_storage.cpp delete mode 100644 cpp_memory/memory/property_storage.h delete mode 100644 cpp_memory/mods.dat create mode 100644 src/effect_list/effect_factory.cpp create mode 100644 src/effect_list/effect_factory.h create mode 100644 src/effect_list/effect_manger.cpp create mode 100644 src/effect_list/effect_manger.h create mode 100644 src/effect_list/fps_manager.h rename src/{properties/memory_handler.h => libs/file_handler.h} (84%) diff --git a/cpp_memory/effect_list/effect.h b/cpp_memory/effect_list/effect.h deleted file mode 100644 index a94aa72..0000000 --- a/cpp_memory/effect_list/effect.h +++ /dev/null @@ -1,55 +0,0 @@ -#pragma once - -#include "stdio.h" -#include "stdint.h" -#include "../memory/property.h" - -class Effect -{ - uint8_t fps = 60; - -public: - Effect() = default; - virtual ~Effect() = default; - - /* Инициализация режима, установка начальный значений. - * Выполняется единожды, при выборе нового режима. - * */ - virtual void on_init() {} - - /* on_update обязательно нужно описать в наследукмом классе. - * Обновление логики эффекта. - * Часть цикла работы режима. - * */ - virtual void on_update() = 0; - - /* on_render может быть переинициализирован в наследукмом классе. - * Обновление графики эффекта. - * Часть цикла работы режима. - * */ - virtual void on_render() {} - - /* on_clear может быть переинициализирован в наследукмом классе. - * Выполняется единожды, при выборе нового режима, перед вызовом on_init. - * Действия с состоянием матрицы оставшейся после предыдущего режима. - * По умолчанию всё стрирается. - * */ - virtual void on_clear() { - - } - - /* get_fps устанавливает максимальный фпс для режима. Может регулировать скорость режима, - * однако скорость режима лучше регулировать кодом внутри режима. - * */ - void set_fps(uint8_t val) { - fps = val; - } - - /* get_fps возвращает максимально допустимый фпс для режима. ФПС может регулировать скорость режима, - * однако скорость режима лучше регулировать кодом внутри режима. - * По умолчанию 60. - * */ - uint8_t get_fps() { - return fps; - } -}; diff --git a/cpp_memory/effect_list/effects/slow_random.h b/cpp_memory/effect_list/effects/slow_random.h deleted file mode 100644 index 21585e5..0000000 --- a/cpp_memory/effect_list/effects/slow_random.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -#include "../effect.h" - -class SlowRandom : public Effect -{ - Property step; - -public: - SlowRandom() {} - - void on_init() { - printf("Slow1 Init\n"); - } - - void on_update() { - printf("Slow1 update\n"); - } -}; diff --git a/cpp_memory/effect_list/effects/slow_random_2.h b/cpp_memory/effect_list/effects/slow_random_2.h deleted file mode 100644 index 386f8c6..0000000 --- a/cpp_memory/effect_list/effects/slow_random_2.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include "../effect.h" - -class SlowRandom2 : public Effect -{ - Property step; - Property val; - -public: - SlowRandom2() {} - - void on_init() { - printf("Slow2 Init\n"); - } - - void on_update() { - printf("Slow2 update\n"); - } -}; diff --git a/cpp_memory/effect_list/effectslist.cpp b/cpp_memory/effect_list/effectslist.cpp deleted file mode 100644 index 409d522..0000000 --- a/cpp_memory/effect_list/effectslist.cpp +++ /dev/null @@ -1,198 +0,0 @@ -#include "effectslist.h" -#include "erroreffect.h" -#include "stdio.h" -#include "initializer_list" - -#include "effects/slow_random.h" -#include "effects/slow_random_2.h" -/* -#include "testmode.h"*/ - -using EffectFactory = Effect* (*)(); - - -template -Effect *makeEffect() { - return new T(); -} - -template -constexpr EffectFactory effectFactory() { - return makeEffect; -} - -auto effectsFactories = { - effectFactory(), - effectFactory(), -}; - -static Effect *newEffect(const uint8_t& id) { - if (id >= effectsFactories.size()) { - return nullptr; - } else { - return effectsFactories.begin()[id](); - } -} - -void EffectsList::deleteCurEffect() { - if (getCurEffect()) { - delete curEffect; - curEffect = nullptr; - curId = -1; - } -} - -Effect *EffectsList::getCurEffect() const { - return curEffect; -} - -uint8_t EffectsList::getCurEffectId() const { - return curId; -} - -EffectsList& EffectsList::getInstance() { - static EffectsList instance; - return instance; -} - -void EffectsList::setErrorEffect() { - if (curEffect) { - delete curEffect; - } - curEffect = new ErrorEffect(); - curId = -1; -} - -void EffectsList::setEffectWithoutInit(const uint8_t id) { - if (id >= effectsFactories.size()) { - printf("EffectList: Effect id out of range\n"); - setErrorEffect(); - return; - } - - deleteCurEffect(); - curEffect = newEffect(id); - - if (!curEffect) { - printf("EffectList: Effect not created\n"); - setErrorEffect(); - return; - } - - curId = id; -} - -void EffectsList::initEffect() { - uint16_t eff_size = 0; - MemoryManager::instance().load_mod_size(eff_size); - if (eff_size == PropertyStorage::instance().size()) { - PropertyStorage::instance().load_all_propertyes(); - curEffect->on_clear(); - curEffect->on_init(); - } else { // всё плохо, почему то изменился размер режима - out("EffectList: Effect size differs from stored in memory\n"); - removeEffect(); - } -} - -// инициализирует эффект получая текущий эффект из памяти -void EffectsList::setEffect() { - MemoryManager::instance().load_mod_id(curId); - setEffectWithoutInit(curId); - initEffect(); -} - -// инициализирует эффект по указанному номеру 0 .. size -void EffectsList::setEffect(uint8_t num) { - if (MemoryManager::instance().set_mod(num + 1)) { - setEffect(); - } else { - out("EffectList: Try to set non-existent mode: %d\n", num); - } -} - -void EffectsList::nextEffect() { - MemoryManager::instance().next_mod(); - setEffect(); -} - -void EffectsList::prevEffect() { - MemoryManager::instance().prev_mod(); - setEffect(); -} - -void EffectsList::init() { - int eff_amount = MemoryManager::instance().get_mod_amount(); - if (eff_amount == 0) { // нет режимов, значит создать все эффекты с 0 до конца списка - for (int i = 0; i < effectsFactories.size(); ++i) { - addEffect(i, false); - } - MemoryManager::instance().set_mod(1); - } - setEffect(); -} - -//перезапустить текущий эффект -void EffectsList::reloadEff() { - setEffect(); -} - -void EffectsList::onTick() { - curEffect->on_update(); - curEffect->on_render(); -} - -float EffectsList::getCurFPS() { - return (float)fps / 10; -} - -bool EffectsList::addEffect(uint8_t id, bool is_init) { - setEffectWithoutInit(id); - if (curId == -1) { - return false; - } - if (!MemoryManager::instance().add_mod(id, PropertyStorage::instance().size())) { - out("EffectList: Error adding effect on memory"); - return false; - } - PropertyStorage::instance().save_all_propertyes(); - if (is_init) { - initEffect(); - } - return true; -} - -bool EffectsList::addEffect(uint8_t id) { - return addEffect(id, true); -} - -void EffectsList::removeEffect() { - MemoryManager::instance().remove_mod(); - setEffect(); -} - -void EffectsList::removeEffect(uint8_t num) { - if (MemoryManager::instance().get_cur_mod_num() == num) { - removeEffect(); - } else { - MemoryManager::instance().remove_mod(num); - } -} - -void EffectsList::clear() { - MemoryManager::instance().remove_all_mods(); -} - -void EffectsList::getEffList() { - if (curId != -1) { - uint8_t *ids = MemoryManager::instance().get_mod_list(); - delete[] ids; - } -} - -// getEffList -// getEffAmount -// getCurEffNum - -// setPropVal -// getPropVal diff --git a/cpp_memory/effect_list/effectslist.h b/cpp_memory/effect_list/effectslist.h deleted file mode 100644 index d4907e7..0000000 --- a/cpp_memory/effect_list/effectslist.h +++ /dev/null @@ -1,45 +0,0 @@ -#pragma once -#include - -class Effect; - -class EffectsList -{ -private: - uint8_t curId = 0; - Effect *curEffect; - unsigned long prev_micros; - unsigned long fps; - - // singlton property - // Конструкторы и оператор присваивания недоступны клиентам - EffectsList() {}; - EffectsList(const EffectsList& ) = delete; - EffectsList& operator=(EffectsList& ) = delete; - Effect *getCurEffect() const; - void deleteCurEffect(); - void setEffectWithoutInit(const uint8_t id); - void initEffect(); - void setEffect(); - bool addEffect(uint8_t id, bool is_init); - -public: - static EffectsList& getInstance(); - void setErrorEffect(); - uint8_t getCurEffectId() const; - void setEffect(uint8_t num); - void nextEffect(); - void prevEffect(); - void reloadEff(); - void onTick(); - float getCurFPS(); - void getEffList(); - - bool addEffect(uint8_t id); - void removeEffect(); - void removeEffect(uint8_t num); - void clear(); - - - void init(); -}; diff --git a/cpp_memory/effect_list/erroreffect.h b/cpp_memory/effect_list/erroreffect.h deleted file mode 100644 index de070f9..0000000 --- a/cpp_memory/effect_list/erroreffect.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include "effect.h" - -class ErrorEffect : public Effect -{ -public: - ErrorEffect() {} - - void on_update() { - printf("ERROR state"); - } -}; diff --git a/cpp_memory/main.cpp b/cpp_memory/main.cpp deleted file mode 100644 index 6ef0297..0000000 --- a/cpp_memory/main.cpp +++ /dev/null @@ -1,132 +0,0 @@ -#include -#include "effect_list/effectslist.h" -#include "memory/property.h" -#include "memory/memory_manger.h" - -void setup() { - EffectsList::getInstance().setEffect(0); -} - - -template -bool check_mod(MemoryManager &mmg, F func, uint8_t req_mod_num, uint8_t req_mod_amnt) { - func(); - uint8_t mod_num = mmg.get_cur_mod_num(); - uint8_t mod_amount = mmg.get_mod_amount(); - if (mod_num != req_mod_num || mod_amount != req_mod_amnt) { - printf("Mod Test failed: %d:%d / %d:%d\n", mod_num, req_mod_num, mod_amount, req_mod_amnt); - return false; - } - return true; -} - -bool check_mods() { - MemoryManager &mmg = MemoryManager::instance(); - - bool flag = true; - auto add_new_mods = [&mmg](uint8_t id, uint16_t size, uint8_t val) { - if (mmg.add_mod(id, size)) { - for (int i = 0; i < size; ++i) { - mmg.save_mod_var(i, val); - } - } - }; - - // memory rebalance test - mmg.clear_memory(); - add_new_mods(0xf0, 8196, 1); // used: 8196+ - add_new_mods(0xf1, 8196, 2); // used: 16392+ - mmg.remove_mod(1); - add_new_mods(0xf3, 4096, 3); // used: 20488+ - add_new_mods(0xf4, 8196, 4); // used: 28684+ - add_new_mods(0xf5, 4096, 5); // used: 32780+ - add_new_mods(0xf6, 8196, 6); // used: 40976+ - add_new_mods(0xf7, 8196, 7); // used: 49172+ - add_new_mods(0xf8, 4096, 8); // used: 53268+ - add_new_mods(0xf9, 4096, 9); // used: 57364+ - add_new_mods(0xfa, 8196, 0xa); // used: 65560+ должно не хватить памяти - mmg.remove_mod(6); // delete f7 - mmg.remove_mod(6); // delete f8 - add_new_mods(0xf7, 8196, 0xc); - add_new_mods(0xf8, 4096, 0xd); - add_new_mods(0xfb, 8196, 0xb); // out of memory - mmg.remove_mod(); // delete f8 - add_new_mods(0xfb, 8196, 0xb); - add_new_mods(0xf8, 4096, 0xd); // out of memory - - flag &= check_mod(mmg, [&mmg]{ mmg.set_mod(10); }, 9, 9); - flag &= check_mod(mmg, [&mmg]{ mmg.remove_all_mods(); }, 0, 0); - - flag &= check_mod(mmg, [&mmg]{ mmg.add_mod(0, 4); }, 1, 1); - flag &= check_mod(mmg, [&mmg]{ mmg.add_mod(1, 8); }, 2, 2); - flag &= check_mod(mmg, [&mmg]{ mmg.add_mod(2, 4); }, 3, 3); - flag &= check_mod(mmg, [&mmg]{ mmg.set_mod(0); }, 3, 3); - flag &= check_mod(mmg, [&mmg]{ mmg.set_mod(2); }, 2, 3); - flag &= check_mod(mmg, [&mmg]{ mmg.set_mod(5); }, 2, 3); - flag &= check_mod(mmg, [&mmg]{ mmg.add_mod(3, 16); }, 4, 4); - flag &= check_mod(mmg, [&mmg]{ mmg.set_mod(2); }, 2, 4); - flag &= check_mod(mmg, [&mmg]{ mmg.remove_mod(); }, 2, 3); - flag &= check_mod(mmg, [&mmg]{ mmg.remove_mod(); }, 2, 2); - flag &= check_mod(mmg, [&mmg]{ mmg.remove_mod(); }, 1, 1); - flag &= check_mod(mmg, [&mmg]{ mmg.remove_mod(); }, 0, 0); - flag &= check_mod(mmg, [&mmg]{ mmg.remove_mod(); }, 0, 0); - - flag &= check_mod(mmg, [&mmg]{ mmg.add_mod(0, 4); }, 1, 1); - flag &= check_mod(mmg, [&mmg]{ mmg.add_mod(1, 16); }, 2, 2); - flag &= check_mod(mmg, [&mmg]{ mmg.add_mod(2, 32); }, 3, 3); - flag &= check_mod(mmg, [&mmg]{ mmg.add_mod(3, 20); }, 4, 4); - flag &= check_mod(mmg, [&mmg]{ mmg.add_mod(4, 12); }, 5, 5); - flag &= check_mod(mmg, [&mmg]{ mmg.add_mod(5, 16); }, 6, 6); - - uint8_t* mods = mmg.get_mod_list(); - if (mods[0] != 0 && mods[1] != 1 && mods[2] != 2 && - mods[3] != 3 && mods[4] != 4 && mods[5] != 5) - { - flag = false; - } - delete[] mods; - - flag &= check_mod(mmg, [&mmg]{ mmg.remove_mod(3); }, 5, 5); - flag &= check_mod(mmg, [&mmg]{ mmg.remove_mod(10); }, 5, 5); - flag &= check_mod(mmg, [&mmg]{ mmg.remove_mod(5); }, 4, 4); - - flag &= check_mod(mmg, [&mmg]{ mmg.set_mod(1); }, 1, 4); - flag &= check_mod(mmg, [&mmg]{ mmg.prev_mod(); }, 4, 4); - flag &= check_mod(mmg, [&mmg]{ mmg.next_mod(); }, 1, 4); - flag &= check_mod(mmg, [&mmg]{ mmg.next_mod(); }, 2, 4); - flag &= check_mod(mmg, [&mmg]{ mmg.next_mod(); }, 3, 4); - flag &= check_mod(mmg, [&mmg]{ mmg.next_mod(); }, 4, 4); - flag &= check_mod(mmg, [&mmg]{ mmg.prev_mod(); }, 3, 4); - - flag &= check_mod(mmg, [&mmg]{ mmg.remove_mod(3); }, 3, 3); - flag &= check_mod(mmg, [&mmg]{ mmg.remove_mod(1); }, 2, 2); - flag &= check_mod(mmg, [&mmg]{ mmg.remove_mod(1); }, 1, 1); - flag &= check_mod(mmg, [&mmg]{ mmg.add_mod(1, 16); }, 2, 2); - flag &= check_mod(mmg, [&mmg]{ mmg.add_mod(2, 32); }, 3, 3); - flag &= check_mod(mmg, [&mmg]{ mmg.remove_all_mods(); }, 0, 0); - - flag &= check_mod(mmg, [&mmg]{ mmg.prev_mod(); }, 0, 0); - flag &= check_mod(mmg, [&mmg]{ mmg.next_mod(); }, 0, 0); - flag &= check_mod(mmg, [&mmg]{ mmg.remove_mod(); }, 0, 0); - flag &= check_mod(mmg, [&mmg]{ mmg.remove_mod(1); }, 0, 0); - flag &= check_mod(mmg, [&mmg]{ mmg.set_mod(1); }, 0, 0); - flag &= check_mod(mmg, [&mmg]{ mmg.set_mod(0); }, 0, 0); - flag &= check_mod(mmg, [&mmg]{ mmg.remove_all_mods(); }, 0, 0); - - return flag; -} - -int main(int argc, char* argv[]) { - setup(); - - //check_mods(); - - /*EffectsList::getInstance().onTick(); - SavedVar val = 1; - printf("a = %d\n", val.get());*/ - - // EffectsList::getInstance().nextEffect(); - - return 0; -} - diff --git a/cpp_memory/main.exe b/cpp_memory/main.exe deleted file mode 100644 index 3b4f8760174591b07815c77bc5f42cc58dd7ffe9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 411317 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%0AL*4Qnz8+LpjNaw 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 z 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 CAiuEy diff --git a/cpp_memory/memory/memory_manger.h b/cpp_memory/memory/memory_manger.h deleted file mode 100644 index d2282b3..0000000 --- a/cpp_memory/memory/memory_manger.h +++ /dev/null @@ -1,407 +0,0 @@ -#pragma once - -// #include "LittleFS.h" -#include -#include "memory.h" -#include "string.h" -#include "math.h" - -#define MAX_MODS_SIZE 256 -#define MM_FILE_NAME "mods.dat" - -class MemoryManager { - struct Mod { - uint16_t next_addr; - uint8_t id; - uint16_t size; - }; - - struct DefaultData { - uint16_t cur_mod; - uint16_t first_mod_addr; - }; - - uint16_t cur_mod_addr; // инициализируется при создании. 0, если режимов нет. - Memory memory; - - MemoryManager(const char* filename) { - memory = Memory{filename}; - memory.read(offsetof(DefaultData, cur_mod), cur_mod_addr); - } - - MemoryManager(const MemoryManager&) = delete; - MemoryManager& operator= (const MemoryManager&) = delete; - - // возвращает 0 если режимов нет - uint16_t get_first_mod_addr() { - uint16_t addr = 0; - memory.read(offsetof(DefaultData, first_mod_addr), addr); - return addr; - } - - bool set_cur_mod(uint16_t val) { - if (val == 0 || val >= sizeof(DefaultData)) { - cur_mod_addr = val; -// write delay - memory.write(offsetof(DefaultData, cur_mod), val); - return true; - } else { - out("MemoryManager: Error set_cur_mod: out of memory\n"); - return false; - } - } - - void mod_memory_shift(uint16_t src_addr, uint16_t dst_addr) { - Mod mod; - memory.read(src_addr, mod); - memory.write(dst_addr, mod); - - //printf("src:%d dst:%d next_addr:%d id:%d size:%d\n", ); - for (uint16_t i = 0; i < mod.size; ++i) { - uint8_t tmp; - memory.read(src_addr + sizeof(Mod) + i, tmp); - memory.write(dst_addr + sizeof(Mod) + i, tmp); - } - - if (cur_mod_addr == src_addr) { - set_cur_mod(dst_addr); - } - } - - // ребалансировка памяти, нужна когда закончилось место в конце файла и были - // удалены какие то режимы. Т.е. в памяти есть пустые не используемы участки. - // Все данные смещаются так, чтобы закрыть неиспользуемые пробелы памяти - void rebalance_mod_list() { - uint16_t cur_addr = get_first_mod_addr(); - uint16_t offset = 0; - Mod mod; - - if (!cur_addr) { // если режимов нет, ливаем - return; - } - - if (cur_addr != sizeof(DefaultData)) { - mod_memory_shift(cur_addr, sizeof(DefaultData)); - cur_addr = sizeof(DefaultData); - memory.write(offsetof(DefaultData, first_mod_addr), cur_addr); - } - memory.read(cur_addr, mod); - - while (mod.next_addr) { - offset = mod.next_addr - cur_addr - mod.size - sizeof(Mod); - if (offset) { - uint16_t cur_offset = mod.next_addr - offset; - mod_memory_shift(mod.next_addr, cur_offset); - mod.next_addr = cur_offset; - } - memory.write(cur_addr, mod.next_addr); - cur_addr = mod.next_addr; - memory.read(cur_addr, mod); - } - } - - bool add_mod_with_rebalace(uint8_t mod_id, uint16_t mod_size, bool need_rebalace) { - uint16_t addr = cur_mod_addr; - uint16_t addr_tmp = addr; - uint32_t new_addr; - uint16_t size; - - if (addr) { - while (addr_tmp) { // ищем адрес последнего режима - memory.read(addr, addr_tmp); // читаем значение по текущему адресу и кладём в переменную этого адреса - addr = addr_tmp? addr_tmp : addr; - } - memory.read(addr + offsetof(Mod, size), size); // читаем сайз последнего режима - new_addr = (uint32_t)addr + size + sizeof(Mod); // добавляем к текущему адресу сайз и размер структуры Mod, получаем адрес следующего режима - } else { - addr = offsetof(DefaultData, first_mod_addr); - new_addr = sizeof(DefaultData); - } - - if (new_addr + sizeof(Mod) + mod_size < MAX_FILE_SIZE) { - memory.write(addr, static_cast(new_addr)); - memory.write(new_addr, Mod{0, mod_id, mod_size}); - set_cur_mod(new_addr); - return true; - } else { - if (need_rebalace) { - rebalance_mod_list(); - return add_mod_with_rebalace(mod_id, mod_size, false); - } else { - out("MemoryManager: Error add_new_mod: out of memory\n"); - return false; - } - } - } - - uint16_t get_prev_link_addr(uint16_t addr) { - if (!addr || !cur_mod_addr) { - return 0; - } - - if (addr < sizeof(DefaultData)) { - out("MemoryManager: Error get_prev_link_addr\n"); - return 0; - } - - uint16_t prev_addr = offsetof(DefaultData, first_mod_addr); - uint16_t cur_addr = get_first_mod_addr(); - while (cur_addr != addr && cur_addr) { - prev_addr = cur_addr; - memory.read(cur_addr, cur_addr); - } - - if (cur_addr == addr) { - return prev_addr; - } else { - out("MemoryManager: Error get_prev_link_addr: mod not found\n"); - } - return 0; - } - - uint16_t get_mod_addr_by_num(uint8_t num, uint16_t &prev_addr) { - prev_addr = offsetof(DefaultData, first_mod_addr); - uint16_t cur_addr = get_first_mod_addr(); - uint16_t mod_num = 1; - - if (cur_addr) { - for (; mod_num != num && cur_addr; ++mod_num) { - prev_addr = cur_addr; - memory.read(cur_addr, cur_addr); - } - if (mod_num == num) { - return cur_addr; - } else { - out("MemoryManager: Error get_mod_addr_by_num: mod not found\n"); - } - } - return 0; - } - - uint16_t get_mod_addr_by_num(uint8_t num) { - uint16_t prev_addr; - return get_mod_addr_by_num(num, prev_addr); - } - - bool remove_mode_by_addr(uint16_t addr, uint16_t prev_addr) { - uint16_t next_addr; - - if (!cur_mod_addr || !addr || !prev_addr) { - return false; - } - - memory.read(addr, next_addr); - memory.write(prev_addr, next_addr); - - if (!next_addr && prev_addr == offsetof(DefaultData, first_mod_addr)) { // если режимов больше нет - set_cur_mod(0); - } else if (cur_mod_addr == addr) { // если удалённый режим был текущим - if (!next_addr) { // если удалили последний режим - set_cur_mod(prev_addr); // устанавливаем в качестве текущего предыдущий - } else { - set_cur_mod(next_addr); // иначе следующий - } - } - - return true; - } - - bool remove_mode_by_addr(uint16_t addr) { - return remove_mode_by_addr(addr, get_prev_link_addr(addr)); - } - -public: - ~MemoryManager() {} - - static MemoryManager& instance() { - static MemoryManager instance(MM_FILE_NAME); - return instance; - } - - void clear_memory() { - memory.clear(); - } - - // ----------------- Функции изменения списка модов ----------------- - - // Добавление нового режима в конец списка с заданным id и size. - // После добавления режима, указатель на текущий режим установится - // на только что добавленный. - // Все поля аргументов режима могут содержать мусор, поэтому нужно их - // инициализировать. Значения аргументов режима устанавливается - // функцией save_mod_var - bool add_mod(uint8_t id, uint16_t size) { - return add_mod_with_rebalace(id, size, true); - } - - // Удаление текущего выбранного режима - // При удалении в качестве текущего выбирается следующий режим, - // если он существует, иначе предыдущий. Если режимов не осталось, - // текущей режим будет установлен в 0. - bool remove_mod() { - return remove_mode_by_addr(cur_mod_addr); - } - - // Удаление режима в определённой позиции. - bool remove_mod(uint8_t num) { - uint16_t prev_addr; - uint16_t addr = get_mod_addr_by_num(num, prev_addr); - return remove_mode_by_addr(addr, prev_addr); - } - - // Уданение всех режимов из памяти (по сути зануление указателя на первый режим) - void remove_all_mods() { - set_cur_mod(0); - memory.write(offsetof(DefaultData, first_mod_addr), 0); - } - - // --------------- Функции выбора текущего режима ---------------- - - // Сменить указатель текущего режима на следующий. - // Если текущий режим последний, то выбирается первый. - void next_mod() { - if (!cur_mod_addr) { - return; - } - uint16_t addr = 0; - memory.read(cur_mod_addr, cur_mod_addr); - - if (!cur_mod_addr) { - cur_mod_addr = get_first_mod_addr(); - } - set_cur_mod(cur_mod_addr); - } - - // Сменить указатель текущего режима на предыдущий. - // Если текущий режим первый, то выбирается последний. - void prev_mod() { - if (!cur_mod_addr) { - return; - } - cur_mod_addr = get_prev_link_addr(cur_mod_addr); - if (cur_mod_addr == offsetof(DefaultData, first_mod_addr)) { - uint16_t addr = cur_mod_addr; - while (addr) { - cur_mod_addr = addr; - memory.read(addr, addr); - } - } - set_cur_mod(cur_mod_addr); - } - - // Установить указатель текущего режима в конкретную позицию. - // начальный режим имеет индекс 1 - bool set_mod(uint8_t num) { - if (!cur_mod_addr || num == 0) { - return false; - } - uint16_t mod_addr = get_first_mod_addr(); - for(int i = 1; i < num && mod_addr; ++i) { - memory.read(mod_addr, mod_addr); - } - if (mod_addr) { - return set_cur_mod(mod_addr); - } else { - return false; - } - } - - // получить номер текущего режима в памяти - // начальный режим имеет индекс 1 - uint8_t get_cur_mod_num() { - if (!cur_mod_addr) { - return 0; - } - uint8_t num = 1; - uint16_t addr = get_first_mod_addr(); - while (addr != cur_mod_addr && addr) { - memory.read(addr, addr); - num++; - } - return num; - } - - // получить кол-во режимов из памяти - uint8_t get_mod_amount() { - uint16_t addr = get_first_mod_addr(); - uint8_t mods_amt = 0; - - while (addr) { - memory.read(addr, addr); - mods_amt++; - } - return mods_amt; - } - - // получить id всех режимов из памяти - // возвращает область памяти со списком режимов, поэтому - // нужно обязательно её очистить после использования вызовом delete[] - uint8_t *get_mod_list() { - uint16_t faddr = get_first_mod_addr(); - uint16_t addr = faddr; - uint8_t mods_amt = 0; - - while (addr) { - memory.read(addr, addr); - mods_amt++; - } - uint8_t *ids = new uint8_t[mods_amt]; - uint8_t cur_mod = 0; - while (faddr) { - memory.read(faddr + offsetof(Mod, id), ids[cur_mod]); - cur_mod++; - memory.read(faddr, faddr); - } - return ids; - } - - // --------------- Функции работы с текущим режимом ---------------- - - void load_mod_id(uint8_t &val) { - if (!cur_mod_addr) { - return; - } - memory.read(cur_mod_addr + offsetof(Mod, id), val); - } - - void load_mod_size(uint16_t &val) { - if (!cur_mod_addr) { - return; - } - memory.read(cur_mod_addr + offsetof(Mod, size), val); - } - - template - bool load_mod_var(uint8_t offset, T &val) { - if (!cur_mod_addr) { - return false; - } - uint16_t size; - memory.read(cur_mod_addr + offsetof(Mod, size), size); - if (offset + sizeof(T) <= size) { - memory.read(cur_mod_addr + sizeof(Mod) + offset, val); - return true; - } else { - out("MemoryManager: Error load_mod_var: offset out of range\n"); - } - return false; - } - - // Сохранение значений текущего режима в память. - // offset считается относительно начала переменных самого режима. - // offset = 0 это первый аргумент режима - // offset = sizeof(first_var) это второй аргумент и т.д. - template - bool save_mod_var(uint16_t offset, T &val) { - uint16_t size; - memory.read(cur_mod_addr + offsetof(Mod, size), size); - if (offset + sizeof(T) <= size) { - // write delay; - memory.write(cur_mod_addr + sizeof(Mod) + offset, val); - return true; - } else { - out("MemoryManager: Error save_mod_var: offset out of range\n"); - } - return false; - } -}; \ No newline at end of file diff --git a/cpp_memory/memory/property.h b/cpp_memory/memory/property.h deleted file mode 100644 index b2729cd..0000000 --- a/cpp_memory/memory/property.h +++ /dev/null @@ -1,103 +0,0 @@ -#pragma once - -#include "property_storage.h" -#include "memory_manger.h" -#include "stdio.h" -#include "stdint.h" - -class ISaveable { - uint16_t offset = -1; -public: - virtual ~ISaveable() = default; - virtual void save() = 0; - virtual void load() = 0; - virtual void set_offset(uint16_t val) { - offset = val; - } - virtual uint16_t get_offset() { - return offset; - } -}; - -class IProperty : public ISaveable { -public: - virtual ~IProperty() = default; - virtual void save() = 0; - virtual void load() = 0; - virtual size_t size() const = 0; -}; - -template -class Property : public IProperty { - T m_val; - T max_val; - T min_val; - - Property(const Property&) = delete; - Property(Property&&) = delete; - Property& operator=(const Property&) = delete; - Property& operator=(Property&&) = delete; - - // установка значения без сохранения в память. - // Возвращает: 0 если значение успешно установлено - // 1 если входное значение равно старому значению - // 2 если установлен диапозон и значение не попадает в установленный диапозон - bool set_without_save(T new_val) { - if (m_val != new_val) { //старое значение не равно новому - if (min_val == max_val || // не определены занчения min max - new_val >= min_val && new_val <= max_val) // новое значение попадает в диапозон - { - m_val = new_val; - return 0; - } else { - return 2; - } - } - return 1; - } -public: - - Property(T val, T min, T max): m_val(min), min_val(min), max_val(max) { - set_without_save(val); // устанавливает новое значение m_val, либо оставляет min_val - PropertyStorage::instance().add_property(this); // + инициализация offset - printf("new var\n"); - } - Property(T min, T max) : Property({}, min, max) { } - Property(T val) : Property(val, {}, {}) { } - Property() : Property({}, {}, {}) { } - - ~Property() - { - PropertyStorage::instance().clear(); - } - - T get() { - return m_val; - } - - void set(T new_val) { - if (set_without_save(new_val) == 0) { - save(); - } - } - - // void *row() { - // return &m_val; - // } - - void save() override { - MemoryManager::instance().save_mod_var(get_offset(), m_val); - } - - void load() override { - T val = {}; - MemoryManager::instance().load_mod_var(get_offset(), val); - if (set_without_save(val) == 2) { - out("Error load Property: value out of defined range\n"); - } - } - - size_t size() const override { - return sizeof(T); - } -}; \ No newline at end of file diff --git a/cpp_memory/memory/property_storage.cpp b/cpp_memory/memory/property_storage.cpp deleted file mode 100644 index 84b5102..0000000 --- a/cpp_memory/memory/property_storage.cpp +++ /dev/null @@ -1,35 +0,0 @@ -#include "property_storage.h" -#include "property.h" - -void PropertyStorage::add_property(IProperty *prop) { - if (props.size()) { - prop->set_offset(size()); - } else { - prop->set_offset(0); - } - - props.push_back(prop); -} - -void PropertyStorage::load_all_propertyes() { - for (auto &it: props) { - it->load(); - } -} - -void PropertyStorage::save_all_propertyes() { - for (auto &it: props) { - it->save(); - } -} - -void PropertyStorage::clear() { - if (props.size()) { - props.clear(); - } -} - -uint16_t PropertyStorage::size() { - IProperty* prop = props.back(); - return prop->get_offset() + prop->size(); -} \ No newline at end of file diff --git a/cpp_memory/memory/property_storage.h b/cpp_memory/memory/property_storage.h deleted file mode 100644 index 5fdb223..0000000 --- a/cpp_memory/memory/property_storage.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -#include "vector" -#include "stdint.h" - -class IProperty; - -class PropertyStorage { - std::vector props; - - // singlton property - // Конструктор копирования и оператор присваивания копированием недоступны - PropertyStorage() {}; - PropertyStorage(const PropertyStorage& ) = delete; - PropertyStorage& operator=(const PropertyStorage& ) = delete; -public: - static PropertyStorage& instance() { - static PropertyStorage instance; - return instance; - } - - void add_property(IProperty *prop); - void load_all_propertyes(); - void save_all_propertyes(); - void clear(); - uint16_t size(); -}; diff --git a/cpp_memory/mods.dat b/cpp_memory/mods.dat deleted file mode 100644 index f97ae3cc020b2379b9f0c143dd55b98d9a00f56c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 65536 zcmeI(J5EAD6b9fUTug|VP*4*JLP;#Ft&NpSpkNKOx&R87!cy1^uc(dZ-UI`oqau^< zZ{~64-bucCPDGQBDq5L&+(o~c)vHOgqTYYYadfg@*Df=Ub`1W%J*_YDFTJzM^6Z}D z*=X)Lu8af-5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7e?bpnH@O+>#f1PBly zK!5-N0t5&UAV7dX@dCs5ZB(O52oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z0D&R|#%+K!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1d0%N{`$^-fUXcAK!5-N0t5&UAV7cs0RqJfysUn*AK+04 z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7csfg%K6*N1!w&=Ud#2oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkLH2!V*-dNv~0RqAydjJ3c diff --git a/platformio.ini b/platformio.ini index 69c2c6f..6cd83d1 100644 --- a/platformio.ini +++ b/platformio.ini @@ -17,13 +17,14 @@ lib_deps = janelia-arduino/Vector@^1.2.0 monitor_speed = 115200 upload_speed = 921600 +filesystem = littlefs [env:esp12e] board = esp12e platform = espressif8266@2.6.3 framework = arduino build_flags = -DESP12F -board_build.filesystem = littlefs +board_build.filesystem = ${common.filesystem} lib_deps = ${common.lib_deps} monitor_speed = ${common.monitor_speed} @@ -34,8 +35,10 @@ board = esp32dev platform = platformio/espressif32 framework = arduino build_flags = -DESP32DEV +board_build.filesystem = ${common.filesystem} lib_deps = ${common.lib_deps} plerup/EspSoftwareSerial@^8.2.0 + lorol/LittleFS_esp32@^1.0.6 monitor_speed = ${common.monitor_speed} upload_speed = ${common.upload_speed} diff --git a/src/controls/automode.cpp b/src/controls/automode.cpp index 26a6129..42b4b92 100644 --- a/src/controls/automode.cpp +++ b/src/controls/automode.cpp @@ -17,8 +17,8 @@ AutoChangeMode::~AutoChangeMode() { void AutoChangeMode::onTick() { if (isEnable()) { - if (millis() - _savedTime > _delay /*&& EffectsList::getInstance().effectIsEnd()*/) { - auto ev = ChangeModEvent({EventType::ChangeMode, ChangeModEvent::Type::Next, 0}); + if (millis() - _savedTime > _delay) { + auto ev = ChangeModEvent({EventType::ChangeMode, ChangeModEvent::Type::Next, 0, false}); Observable::notify(&ev); out("AutoControl: next mode\n"); } diff --git a/src/effect_list/effect.h b/src/effect_list/effect.h index 2dbd2be..2af4910 100644 --- a/src/effect_list/effect.h +++ b/src/effect_list/effect.h @@ -1,6 +1,7 @@ #pragma once #include "libs/led_matrix.h" +#include "properties/property.h" class Effect { diff --git a/src/effect_list/effect_factory.cpp b/src/effect_list/effect_factory.cpp new file mode 100644 index 0000000..ae300ef --- /dev/null +++ b/src/effect_list/effect_factory.cpp @@ -0,0 +1,102 @@ +#include "effect_factory.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 EffectCreator = Effect* (*)(); + +template +static Effect *makeEffect() { + return new T(); +} + +template +static constexpr EffectCreator effectCreator() { + return makeEffect; +} + +struct EffectInfo { + const char* effect_name; + EffectCreator effect_creator; +}; + +#define EFFECT_CASE(id, name, type) case id: return EffectInfo{name, effectCreator()}; +#define EFFECT_COUNT 28 + +static EffectInfo getEffectInfo(int effect_id) { + switch (effect_id) { + EFFECT_CASE(0, "SlowRandom", SlowRandom) + EFFECT_CASE(1, "SimpleRainbow", SimpleRainbow) + EFFECT_CASE(2, "Dribs", Dribs) + EFFECT_CASE(3, "Rain", Rain) + EFFECT_CASE(4, "AllRandom", AllRandom) + EFFECT_CASE(5, "Snow", Snow) + EFFECT_CASE(6, "Fire", Fire) + EFFECT_CASE(7, "TheMatrix", TheMatrix) + EFFECT_CASE(8, "SimpleBalls", SimpleBalls) + EFFECT_CASE(9, "Confetti", Confetti) + EFFECT_CASE(10, "Starfall", Starfall) + EFFECT_CASE(11, "DynamicSquare", DynamicSquare) + EFFECT_CASE(12, "RandomRain", RandomRain) + EFFECT_CASE(13, "RainbowRain", RainbowRain) + EFFECT_CASE(14, "Points", Points) + EFFECT_CASE(15, "RainbowPoint", RainbowPoint) + EFFECT_CASE(16, "RainbowStaticPoint", RainbowStaticPoint) + EFFECT_CASE(17, "Text", TextMode) + EFFECT_CASE(18, "Mouse", Mouse) + EFFECT_CASE(19, "Pacman", Pacman) + EFFECT_CASE(20, "CircularPoint", CircularPoint) + EFFECT_CASE(21, "Zigzag", ZigZag) + EFFECT_CASE(22, "HorizontalRainbowPoint", HorizontalRainbowPoint) + EFFECT_CASE(23, "Ny2020", NY2020) + EFFECT_CASE(24, "DribsAllSide", DribsAllSide) + EFFECT_CASE(25, "Snake", Snake) + EFFECT_CASE(26, "RadialFire", RadialFire) + EFFECT_CASE(27, "RadialPattern", RadialPattern) + EFFECT_CASE(28, "CrazyBees", CrazyBees) + // при добавлении нового эффекта, не забудь обновить EFFECT_COUNT + + default: + return EffectInfo{"Error", effectCreator()}; + } +} + +uint16_t EffectFactory::getEffectCount() { + return EFFECT_COUNT; +} + +Effect* EffectFactory::createEffect(int effect_id) { + return getEffectInfo(effect_id).effect_creator(); +} + +const char* EffectFactory::getEffectName(int effect_id) { + return getEffectInfo(effect_id).effect_name; +} diff --git a/src/effect_list/effect_factory.h b/src/effect_list/effect_factory.h new file mode 100644 index 0000000..3b7e5dc --- /dev/null +++ b/src/effect_list/effect_factory.h @@ -0,0 +1,11 @@ +#include +#include "effect.h" + +class EffectFactory { + public: + static Effect* createEffect(int effect_id); + static const char* getEffectName(int effect_id); + static uint16_t getEffectCount(); +}; + + diff --git a/src/effect_list/effect_manger.cpp b/src/effect_list/effect_manger.cpp new file mode 100644 index 0000000..a7db824 --- /dev/null +++ b/src/effect_list/effect_manger.cpp @@ -0,0 +1,132 @@ +#include "effect_manger.h" + +#include "fps_manager.h" +#include "effect_factory.h" +#include "properties/memory_manager.h" +#include "properties/property_storage.h" + +EffectsManager::EffectsManager() { + Observable::subscribe(EventType::ChangeMode, this); +}; + +EffectsManager::~EffectsManager() { + Observable::unsubscribe(EventType::ChangeMode, this); +}; + +void EffectsManager::init() { + setEffect(MemoryManager::instance().get_cur_mod_num()); +} + +void EffectsManager::clearEffect() { + if (_effect) { + delete _effect; + _effect = nullptr; + } +} + +void EffectsManager::setErrorEffect() { + clearEffect(); + _effect = EffectFactory::createEffect(-1); + _effect->on_init(); +} + +uint8_t EffectsManager::getEffectId() const { + return MemoryManager::instance().load_mod_id(); +} + +uint8_t EffectsManager::getEffectNum() const { + return MemoryManager::instance().get_cur_mod_num(); +} + +std::string EffectsManager::getEffectName() const { + uint8_t id = getEffectId(); + if (id > 0 && id < EffectFactory::getEffectCount()) { + return ""; + } + + return EffectFactory::getEffectName(id); +} + + +void EffectsManager::syncEffect() { + clearEffect(); + _effect = EffectFactory::createEffect(MemoryManager::instance().load_mod_id()); + + if (!_effect) { + out("ERROR: Effect not crated\n"); + setErrorEffect(); + return; + } + + PropertyStorage::instance().load_all_propertyes(); + + _effect->on_clear(); + _effect->on_init(); + + auto ev = ModChangedEvent({ + EventType::ModChanged, + MemoryManager::instance().load_mod_id(), + MemoryManager::instance().get_cur_mod_num() + }); + Observable::notify(&ev); +} + +void EffectsManager::setEffect(const uint8_t num) { + if (!MemoryManager::instance().set_mod(num)) { + out("ERROR: Effect number out of range\n"); + setErrorEffect(); + return; + } + + syncEffect(); +} + +void EffectsManager::nextEffect() { + MemoryManager::instance().next_mod(); + syncEffect(); +} + +void EffectsManager::prevEffect() { + MemoryManager::instance().prev_mod(); + syncEffect(); +} + + +void EffectsManager::onTick() { + if (_reqEffect.type != Request::Type::None && (_effect->is_end() || _reqEffect.hardReset)) { + if (_reqEffect.type == Request::Type::Next) { + nextEffect(); + } else if (_reqEffect.type == Request::Type::Previous) { + prevEffect(); + } else if (_reqEffect.type == Request::Type::Set) { + setEffect(_reqEffect.num); + } + _reqEffect.type = Request::Type::None; + } + + if (!_effect) + return; + + if (_fpsManager.needUpdate()) { + _effect->on_update(); + _effect->on_render(); + FastLED.show(); + } +} + +float EffectsManager::getCurFPS() { + return _fpsManager.getRealFPS(); +} + +void EffectsManager::handleEvent(Event *event) { + if (event->type == EventType::ChangeMode) { + ChangeModEvent *ev = static_cast(event); + if (ev->type == ChangeModEvent::Type::Next) { + _reqEffect = { Request::Type::Next, 0, ev->hardReset }; + } else if (ev->type == ChangeModEvent::Type::Previous) { + _reqEffect = { Request::Type::Previous, 0, ev->hardReset }; + } else if (ev->type == ChangeModEvent::Type::Set) { + _reqEffect = { Request::Type::Set, ev->id, ev->hardReset }; + } + } +} \ No newline at end of file diff --git a/src/effect_list/effect_manger.h b/src/effect_list/effect_manger.h new file mode 100644 index 0000000..d524bdb --- /dev/null +++ b/src/effect_list/effect_manger.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include "events/observer.h" +#include "fps_manager.h" + +class Effect; +class FPSManager; + +class EffectsManager : public IObserver +{ + struct Request { + enum class Type { + None, + Set, + Next, + Previous + }; + Type type; + uint8_t num; + bool hardReset; + }; + void clearEffect(); + void syncEffect(); + +public: + EffectsManager(); + ~EffectsManager(); + void init(); + void setErrorEffect(); + + uint8_t getEffectId() const; + uint8_t getEffectNum() const; + std::string getEffectName() const; + + void setEffect(const uint8_t num); + void nextEffect(); + void prevEffect(); + + void onTick(); + float getCurFPS(); + void handleEvent(Event *event) override; +private: + Request _reqEffect = { Request::Type::None, 0, false }; + Effect* _effect = nullptr; + FPSManager _fpsManager; +}; diff --git a/src/effect_list/effects/dribs_all_side.h b/src/effect_list/effects/dribs_all_side.h index 361a0c5..a661e48 100644 --- a/src/effect_list/effects/dribs_all_side.h +++ b/src/effect_list/effects/dribs_all_side.h @@ -51,6 +51,8 @@ class DribsAllSide : public Effect snake.pos.x = 0; snake.pos.y = random16(LEDS_HEIGHT); break; + default: + break; } } diff --git a/src/effect_list/effects/rain.h b/src/effect_list/effects/rain.h index 0d3162b..237bfe2 100644 --- a/src/effect_list/effects/rain.h +++ b/src/effect_list/effects/rain.h @@ -1,10 +1,9 @@ #pragma once #include "effect_list/effect.h" - class Rain : public Effect { - uint8_t step = 2; + Property step{2}; public: Rain() {} @@ -20,8 +19,8 @@ class Rain : public Effect if (random8(255) == 0) { led = CRGB(0, 0, 255); } else if (led.b > 0) { - if (led.b > step) { - led = CRGB(0, 0, led.b - step); + if (led.b > step.get()) { + led = CRGB(0, 0, led.b - step.get()); } else { led = CRGB(0, 0, 0); } diff --git a/src/effect_list/effectslist.cpp b/src/effect_list/effectslist.cpp index c631aec..062ad50 100644 --- a/src/effect_list/effectslist.cpp +++ b/src/effect_list/effectslist.cpp @@ -1,4 +1,4 @@ -#include "effectslist.h" +/*#include "effectslist.h" #include "erroreffect.h" #include "effects/slow_random.h" @@ -212,4 +212,4 @@ void EffectsList::handleEvent(Event *event) { setEffect(ev->id); } } -} \ No newline at end of file +}*/ \ No newline at end of file diff --git a/src/effect_list/effectslist.h b/src/effect_list/effectslist.h index a773a03..b3b4b39 100644 --- a/src/effect_list/effectslist.h +++ b/src/effect_list/effectslist.h @@ -1,4 +1,4 @@ -#pragma once +/*#pragma once #include #include "events/observer.h" @@ -33,4 +33,4 @@ class EffectsList : public IObserver float getCurFPS(); bool effectIsEnd(); virtual void handleEvent(Event *event) override; -}; +};*/ diff --git a/src/effect_list/fps_manager.h b/src/effect_list/fps_manager.h new file mode 100644 index 0000000..d3e32d3 --- /dev/null +++ b/src/effect_list/fps_manager.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/events/events.h b/src/events/events.h index 8038bef..945176c 100644 --- a/src/events/events.h +++ b/src/events/events.h @@ -7,6 +7,7 @@ enum class EventType { ChangeBrightness, // ChangeIntEvent ChangeSpeed, // ChangeIntEvent ChangeMode, // ChangeModEvent + ModChanged, // ModChangedEvent ChangeModVar, // ChangeModVarEvent EventAmount // Используется, чтобы знать сколько всего ивентов }; @@ -35,9 +36,15 @@ struct ChangeModEvent : public Event { }; Type type; uint8_t id; - ChangeModEvent(EventType et, Type t, uint8_t i = 0) : Event(et), type(t), id(i) {} + bool hardReset; + ChangeModEvent(EventType et, Type t, uint8_t i = 0, bool h = true) : Event(et), type(t), id(i), hardReset(h) {} }; +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; diff --git a/src/properties/memory_handler.h b/src/libs/file_handler.h similarity index 84% rename from src/properties/memory_handler.h rename to src/libs/file_handler.h index 41afc3a..65ddea4 100644 --- a/src/properties/memory_handler.h +++ b/src/libs/file_handler.h @@ -8,26 +8,22 @@ #define MAX_FILE_SIZE 65536 #define STEP -class MemoryHandler { +class FileHandler { FILE *_fp = nullptr; - MemoryHandler(const MemoryHandler&) = delete; - MemoryHandler& operator= (const MemoryHandler&) = delete; + FileHandler(const FileHandler&) = delete; + FileHandler& operator= (const FileHandler&) = delete; public: - MemoryHandler() {} + FileHandler() = default; - ~MemoryHandler() { - if (_fp) { - fclose(_fp); - } - } + ~FileHandler() = default; - MemoryHandler(MemoryHandler&& other){ + FileHandler(FileHandler&& other){ _fp = other._fp; other._fp = nullptr; }; - MemoryHandler& operator= (MemoryHandler&& other) { + FileHandler& operator= (FileHandler&& other) { _fp = other._fp; other._fp = nullptr; return *this; @@ -39,7 +35,7 @@ class MemoryHandler { return; } if (!_fp) { - if (_fp = fopen(filename, "w")) { + if ((_fp = fopen(filename, "w")) != nullptr) { fclose(_fp); } _fp = fopen(filename, "r+"); diff --git a/src/main.cpp b/src/main.cpp index cda051d..b4cbedf 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,21 +1,20 @@ #include "myapplication.h" +#include "properties/memory_manager.h" +#include MyApplication app; void setup() { + if (!LittleFS.begin()) { + Serial.println("An Error has occurred while mounting LittleFS"); + } app.onInit(); } void loop() { - app.onTick(); -} - -//#include "LittleFS.h" + //app.onTick(); - // if (!LittleFS.begin()) { - // Serial.println("An Error has occurred while mounting LittleFS"); - // return; - // } +} /*FSInfo fsi; LittleFS.info(fsi); diff --git a/src/myapplication.cpp b/src/myapplication.cpp index 0e808d4..121181d 100644 --- a/src/myapplication.cpp +++ b/src/myapplication.cpp @@ -38,9 +38,9 @@ void MyApplication::onInit() { _relay.onInit(); #endif - EffectsList::getInstance(); // инициализируем EffectsList, чтобы сработало уведомление о новом режиме - auto ev = ChangeModEvent({EventType::ChangeMode, ChangeModEvent::Type::Set, 0}); - Observable::notify(&ev); + //EffectsList::getInstance(); // инициализируем EffectsList, чтобы сработало уведомление о новом режиме + //auto ev = ChangeModEvent({EventType::ChangeMode, ChangeModEvent::Type::Set, 0}); + //Observable::notify(&ev); } void MyApplication::onTick() { @@ -49,7 +49,7 @@ void MyApplication::onTick() { #endif { if (_isPowerOn) { - EffectsList::getInstance().onTick(); + //EffectsList::getInstance().onTick(); _autoMod.onTick(); } #if BTN_ENABLE diff --git a/src/properties/memory_manager.cpp b/src/properties/memory_manager.cpp index d7c607e..20288ad 100644 --- a/src/properties/memory_manager.cpp +++ b/src/properties/memory_manager.cpp @@ -1,21 +1,21 @@ #include "memory_manager.h" MemoryManager::MemoryManager(const char* filename) { - memory = MemoryHandler{}; - memory.openFile(filename); - memory.read(offsetof(DefaultData, cur_mod), cur_mod_addr); + _file = FileHandler{}; + _file.openFile(filename); + _file.read(offsetof(DefaultData, cur_mod), _cur_mod_addr); } uint16_t MemoryManager::get_first_mod_addr() { uint16_t addr = 0; - memory.read(offsetof(DefaultData, first_mod_addr), addr); + _file.read(offsetof(DefaultData, first_mod_addr), addr); return addr; } bool MemoryManager::set_cur_mod(uint16_t val) { if (val == 0 || val >= sizeof(DefaultData)) { - cur_mod_addr = val; - memory.write(offsetof(DefaultData, cur_mod), val); + _cur_mod_addr = val; + _file.write(offsetof(DefaultData, cur_mod), val); return true; } else { out("MemoryManager: Error set_cur_mod: out of memory\n"); @@ -25,16 +25,16 @@ bool MemoryManager::set_cur_mod(uint16_t val) { void MemoryManager::mod_memory_shift(uint16_t src_addr, uint16_t dst_addr) { Mod mod; - memory.read(src_addr, mod); - memory.write(dst_addr, mod); + _file.read(src_addr, mod); + _file.write(dst_addr, mod); for (uint16_t i = 0; i < mod.size; ++i) { uint8_t tmp; - memory.read(src_addr + sizeof(Mod) + i, tmp); - memory.write(dst_addr + sizeof(Mod) + i, tmp); + _file.read(src_addr + sizeof(Mod) + i, tmp); + _file.write(dst_addr + sizeof(Mod) + i, tmp); } - if (cur_mod_addr == src_addr) { + if (_cur_mod_addr == src_addr) { set_cur_mod(dst_addr); } } @@ -51,9 +51,9 @@ void MemoryManager::rebalance_mod_list() { if (cur_addr != sizeof(DefaultData)) { mod_memory_shift(cur_addr, sizeof(DefaultData)); cur_addr = sizeof(DefaultData); - memory.write(offsetof(DefaultData, first_mod_addr), cur_addr); + _file.write(offsetof(DefaultData, first_mod_addr), cur_addr); } - memory.read(cur_addr, mod); + _file.read(cur_addr, mod); while (mod.next_addr) { offset = mod.next_addr - cur_addr - mod.size - sizeof(Mod); @@ -62,24 +62,24 @@ void MemoryManager::rebalance_mod_list() { mod_memory_shift(mod.next_addr, cur_offset); mod.next_addr = cur_offset; } - memory.write(cur_addr, mod.next_addr); + _file.write(cur_addr, mod.next_addr); cur_addr = mod.next_addr; - memory.read(cur_addr, mod); + _file.read(cur_addr, mod); } } bool MemoryManager::add_mod_with_rebalace(uint8_t mod_id, uint16_t mod_size, bool need_rebalace) { - uint16_t addr = cur_mod_addr; + uint16_t addr = _cur_mod_addr; uint16_t addr_tmp = addr; uint32_t new_addr; uint16_t size; if (addr) { while (addr_tmp) { - memory.read(addr, addr_tmp); + _file.read(addr, addr_tmp); addr = addr_tmp? addr_tmp : addr; } - memory.read(addr + offsetof(Mod, size), size); + _file.read(addr + offsetof(Mod, size), size); new_addr = (uint32_t)addr + size + sizeof(Mod); } else { addr = offsetof(DefaultData, first_mod_addr); @@ -87,8 +87,8 @@ bool MemoryManager::add_mod_with_rebalace(uint8_t mod_id, uint16_t mod_size, boo } if (new_addr + sizeof(Mod) + mod_size < MAX_FILE_SIZE) { - memory.write(addr, static_cast(new_addr)); - memory.write(new_addr, Mod{0, mod_id, mod_size}); + _file.write(addr, static_cast(new_addr)); + _file.write(new_addr, Mod{0, mod_id, mod_size}); set_cur_mod(new_addr); return true; } else { @@ -103,7 +103,7 @@ bool MemoryManager::add_mod_with_rebalace(uint8_t mod_id, uint16_t mod_size, boo } uint16_t MemoryManager::get_prev_link_addr(uint16_t addr) { - if (!addr || !cur_mod_addr) { + if (!addr || !_cur_mod_addr) { return 0; } @@ -116,7 +116,7 @@ uint16_t MemoryManager::get_prev_link_addr(uint16_t addr) { uint16_t cur_addr = get_first_mod_addr(); while (cur_addr != addr && cur_addr) { prev_addr = cur_addr; - memory.read(cur_addr, cur_addr); + _file.read(cur_addr, cur_addr); } if (cur_addr == addr) { @@ -135,7 +135,7 @@ uint16_t MemoryManager::get_mod_addr_by_num(uint8_t num, uint16_t &prev_addr) { if (cur_addr) { for (; mod_num != num && cur_addr; ++mod_num) { prev_addr = cur_addr; - memory.read(cur_addr, cur_addr); + _file.read(cur_addr, cur_addr); } if (mod_num == num) { return cur_addr; @@ -154,16 +154,16 @@ uint16_t MemoryManager::get_mod_addr_by_num(uint8_t num) { bool MemoryManager::remove_mode_by_addr(uint16_t addr, uint16_t prev_addr) { uint16_t next_addr; - if (!cur_mod_addr || !addr || !prev_addr) { + if (!_cur_mod_addr || !addr || !prev_addr) { return false; } - memory.read(addr, next_addr); - memory.write(prev_addr, next_addr); + _file.read(addr, next_addr); + _file.write(prev_addr, next_addr); if (!next_addr && prev_addr == offsetof(DefaultData, first_mod_addr)) { set_cur_mod(0); - } else if (cur_mod_addr == addr) { + } else if (_cur_mod_addr == addr) { if (!next_addr) { set_cur_mod(prev_addr); } else { @@ -178,15 +178,13 @@ bool MemoryManager::remove_mode_by_addr(uint16_t addr) { return remove_mode_by_addr(addr, get_prev_link_addr(addr)); } -MemoryManager::~MemoryManager() {} - MemoryManager& MemoryManager::instance() { static MemoryManager instance(MM_FILE_NAME); return instance; } -void MemoryManager::clear_meёmory() { - memory.clear(); +void MemoryManager::clear_memory() { + _file.clear(); } bool MemoryManager::add_mod(uint8_t id, uint16_t size) { @@ -194,7 +192,7 @@ bool MemoryManager::add_mod(uint8_t id, uint16_t size) { } bool MemoryManager::remove_mod() { - return remove_mode_by_addr(cur_mod_addr); + return remove_mode_by_addr(_cur_mod_addr); } bool MemoryManager::remove_mod(uint8_t num) { @@ -205,44 +203,43 @@ bool MemoryManager::remove_mod(uint8_t num) { void MemoryManager::remove_all_mods() { set_cur_mod(0); - memory.write(offsetof(DefaultData, first_mod_addr), 0); + _file.write(offsetof(DefaultData, first_mod_addr), 0); } void MemoryManager::next_mod() { - if (!cur_mod_addr) { + if (!_cur_mod_addr) { return; } - uint16_t addr = 0; - memory.read(cur_mod_addr, cur_mod_addr); + _file.read(_cur_mod_addr, _cur_mod_addr); - if (!cur_mod_addr) { - cur_mod_addr = get_first_mod_addr(); + if (!_cur_mod_addr) { + _cur_mod_addr = get_first_mod_addr(); } - set_cur_mod(cur_mod_addr); + set_cur_mod(_cur_mod_addr); } void MemoryManager::prev_mod() { - if (!cur_mod_addr) { + if (!_cur_mod_addr) { return; } - cur_mod_addr = get_prev_link_addr(cur_mod_addr); - if (cur_mod_addr == offsetof(DefaultData, first_mod_addr)) { - uint16_t addr = cur_mod_addr; + _cur_mod_addr = get_prev_link_addr(_cur_mod_addr); + if (_cur_mod_addr == offsetof(DefaultData, first_mod_addr)) { + uint16_t addr = _cur_mod_addr; while (addr) { - cur_mod_addr = addr; - memory.read(addr, addr); + _cur_mod_addr = addr; + _file.read(addr, addr); } } - set_cur_mod(cur_mod_addr); + set_cur_mod(_cur_mod_addr); } bool MemoryManager::set_mod(uint8_t num) { - if (!cur_mod_addr || num == 0) { + if (!_cur_mod_addr || num == 0) { return false; } uint16_t mod_addr = get_first_mod_addr(); for(int i = 1; i < num && mod_addr; ++i) { - memory.read(mod_addr, mod_addr); + _file.read(mod_addr, mod_addr); } if (mod_addr) { return set_cur_mod(mod_addr); @@ -252,13 +249,13 @@ bool MemoryManager::set_mod(uint8_t num) { } uint8_t MemoryManager::get_cur_mod_num() { - if (!cur_mod_addr) { + if (!_cur_mod_addr) { return 0; } uint8_t num = 1; uint16_t addr = get_first_mod_addr(); - while (addr != cur_mod_addr && addr) { - memory.read(addr, addr); + while (addr != _cur_mod_addr && addr) { + _file.read(addr, addr); num++; } return num; @@ -269,7 +266,7 @@ uint8_t MemoryManager::get_mod_amount() { uint8_t mods_amt = 0; while (addr) { - memory.read(addr, addr); + _file.read(addr, addr); mods_amt++; } return mods_amt; @@ -281,42 +278,44 @@ std::unique_ptr MemoryManager::get_mod_list() { uint8_t mods_amt = 0; while (addr) { - memory.read(addr, addr); + _file.read(addr, addr); mods_amt++; } std::unique_ptr ids(new uint8_t[mods_amt]); uint8_t cur_mod = 0; while (faddr) { - memory.read(faddr + offsetof(Mod, id), ids[cur_mod]); + _file.read(faddr + offsetof(Mod, id), ids[cur_mod]); cur_mod++; - memory.read(faddr, faddr); + _file.read(faddr, faddr); } return ids; } -void MemoryManager::load_mod_id(uint8_t &val) { - if (!cur_mod_addr) { - return; +uint8_t MemoryManager::load_mod_id() { + uint8_t ret_val = 0; + if (_cur_mod_addr) { + _file.read(_cur_mod_addr + offsetof(Mod, id), ret_val); } - memory.read(cur_mod_addr + offsetof(Mod, id), val); + return ret_val; } -void MemoryManager::load_mod_size(uint16_t &val) { - if (!cur_mod_addr) { - return; +uint16_t MemoryManager::load_mod_size() { + uint16_t ret_val = 0; + if (_cur_mod_addr) { + _file.read(_cur_mod_addr + offsetof(Mod, size), ret_val); } - memory.read(cur_mod_addr + offsetof(Mod, size), val); + return ret_val; } template bool MemoryManager::load_mod_var(uint8_t offset, T &val) { - if (!cur_mod_addr) { + if (!_cur_mod_addr) { return false; } uint16_t size; - memory.read(cur_mod_addr + offsetof(Mod, size), size); + _file.read(_cur_mod_addr + offsetof(Mod, size), size); if (offset + sizeof(T) <= size) { - memory.read(cur_mod_addr + sizeof(Mod) + offset, val); + _file.read(_cur_mod_addr + sizeof(Mod) + offset, val); return true; } else { out("MemoryManager: Error load_mod_var: offset out of range\n"); @@ -327,12 +326,12 @@ bool MemoryManager::load_mod_var(uint8_t offset, T &val) { template bool MemoryManager::save_mod_var(uint16_t offset, T &val) { uint16_t size; - memory.read(cur_mod_addr + offsetof(Mod, size), size); + _file.read(_cur_mod_addr + offsetof(Mod, size), size); if (offset + sizeof(T) <= size) { - memory.write(cur_mod_addr + sizeof(Mod) + offset, val); + _file.write(_cur_mod_addr + sizeof(Mod) + offset, val); return true; } else { out("MemoryManager: Error save_mod_var: offset out of range\n"); } return false; -} \ No newline at end of file +} diff --git a/src/properties/memory_manager.h b/src/properties/memory_manager.h index 844a1aa..251213b 100644 --- a/src/properties/memory_manager.h +++ b/src/properties/memory_manager.h @@ -1,10 +1,6 @@ #pragma once -// #include "LittleFS.h" -#include -#include "memory_handler.h" -#include "string.h" -#include "math.h" +#include "libs/file_handler.h" #include #define MAX_MODS_SIZE 256 @@ -22,9 +18,6 @@ class MemoryManager { uint16_t first_mod_addr; }; - uint16_t cur_mod_addr; // инициализируется при создании. 0, если режимов нет. - MemoryHandler memory; - MemoryManager(const char* filename); MemoryManager(const MemoryManager&) = delete; MemoryManager& operator= (const MemoryManager&) = delete; @@ -46,9 +39,11 @@ class MemoryManager { bool remove_mode_by_addr(uint16_t addr); public: - ~MemoryManager(); + ~MemoryManager() = default; static MemoryManager& instance(); - void clear_meёmory(); + + // Занулить всю память + void clear_memory(); // ----------------- Функции изменения списка модов ----------------- @@ -99,8 +94,8 @@ class MemoryManager { // --------------- Функции работы с текущим режимом ---------------- - void load_mod_id(uint8_t &val); - void load_mod_size(uint16_t &val); + uint8_t load_mod_id(); + uint16_t load_mod_size(); template bool load_mod_var(uint8_t offset, T &val); @@ -111,4 +106,8 @@ class MemoryManager { // offset = sizeof(first_var) это второй аргумент и т.д. template bool save_mod_var(uint16_t offset, T &val); + +private: + uint16_t _cur_mod_addr; // инициализируется при создании. 0, если режимов нет. + FileHandler _file; }; \ No newline at end of file diff --git a/src/properties/property.h b/src/properties/property.h index ed0a443..f7468bc 100644 --- a/src/properties/property.h +++ b/src/properties/property.h @@ -4,6 +4,28 @@ #include "memory_manager.h" #include "stdint.h" +// костыль для получения имени типа без использования RTTI +namespace type_names { + template + inline const char* get_type_name() { return "unknown"; } + + #define DEFINE_TYPE_NAME(type) \ + template<> \ + inline const char* get_type_name() { return #type; } + + DEFINE_TYPE_NAME(uint8_t); + DEFINE_TYPE_NAME(uint16_t); + DEFINE_TYPE_NAME(uint32_t); + DEFINE_TYPE_NAME(uint64_t); + DEFINE_TYPE_NAME(int8_t); + DEFINE_TYPE_NAME(int16_t); + DEFINE_TYPE_NAME(int32_t); + DEFINE_TYPE_NAME(int64_t); + DEFINE_TYPE_NAME(float); + DEFINE_TYPE_NAME(double); + DEFINE_TYPE_NAME(bool); +} + class ISaveable { uint16_t offset = -1; public: @@ -16,87 +38,89 @@ class ISaveable { virtual uint16_t get_offset() { return offset; } + virtual size_t size() const = 0; }; class IProperty : public ISaveable { public: - virtual ~IProperty() = default; - virtual void save() = 0; - virtual void load() = 0; - virtual size_t size() const = 0; + virtual ~IProperty(); + virtual const char* type_name() const = 0; + virtual void save() override = 0; + virtual void load() override = 0; + virtual size_t size() const override = 0; }; template class Property : public IProperty { - T m_val; - T max_val; - T min_val; - Property(const Property&) = delete; Property(Property&&) = delete; Property& operator=(const Property&) = delete; Property& operator=(Property&&) = delete; - // установка значения без сохранения в память. - // Возвращает: 0 если значение успешно установлено - // 1 если входное значение равно старому значению - // 2 если установлен диапозон и значение не попадает в установленный диапозон - bool set_without_save(T new_val) { - if (m_val != new_val) { //старое значение не равно новому - if (min_val == max_val || // не определены занчения min max - new_val >= min_val && new_val <= max_val) // новое значение попадает в диапозон - { - m_val = new_val; - return 0; - } else { - return 2; - } +protected: + virtual void set_without_save(T new_val) { + if ((_min_value == _max_value) || // не определены занчения min max + (new_val >= _min_value && new_val <= _max_value)) // новое значение попадает в диапозон + { + _value = new_val; } - return 1; } -public: - Property(T val, T min, T max): m_val(min), min_val(min), max_val(max) { - set_without_save(val); // устанавливает новое значение m_val, либо оставляет min_val - PropertyStorage::instance().add_property(this); // + инициализация offset - printf("new var\n"); +public: + Property(T value, T min, T max): _value(), _min_value(min), _max_value(max) { + set_without_save(value); + uint16_t offset = PropertyStorage::instance().add_property(this); + set_offset(offset); } Property(T min, T max) : Property({}, min, max) { } Property(T val) : Property(val, {}, {}) { } Property() : Property({}, {}, {}) { } - ~Property() - { + virtual ~Property() { PropertyStorage::instance().clear(); } T get() { - return m_val; + return _value; } void set(T new_val) { - if (set_without_save(new_val) == 0) { + if (_value != new_val) { + set_without_save(new_val); save(); } } - // void *row() { - // return &m_val; - // } - void save() override { - MemoryManager::instance().save_mod_var(get_offset(), m_val); + MemoryManager::instance().save_mod_var(get_offset(), _value); } void load() override { - T val = {}; - MemoryManager::instance().load_mod_var(get_offset(), val); - if (set_without_save(val) == 2) { - out("Error load Property: value out of defined range\n"); - } + MemoryManager::instance().load_mod_var(get_offset(), _value); } size_t size() const override { return sizeof(T); } + + const char* type_name() const override { + return type_names::get_type_name(); + } + + bool has_min_max() const { + return _min_value != _max_value; + } + + T get_min() const { + return _min_value; + } + + T get_max() const { + return _max_value; + } + +private: + T _value; + T _min_value; + T _max_value; }; \ No newline at end of file diff --git a/src/properties/property_storage.cpp b/src/properties/property_storage.cpp index 84b5102..21235f9 100644 --- a/src/properties/property_storage.cpp +++ b/src/properties/property_storage.cpp @@ -1,35 +1,40 @@ #include "property_storage.h" #include "property.h" -void PropertyStorage::add_property(IProperty *prop) { - if (props.size()) { - prop->set_offset(size()); - } else { - prop->set_offset(0); +uint16_t PropertyStorage::add_property(IProperty *prop) { + uint16_t offset = 0; + if (_props.size()) { + offset = size(); } - props.push_back(prop); + _props.push_back(prop); + + return offset; +} + +std::vector PropertyStorage::get_props() const { + return _props; } void PropertyStorage::load_all_propertyes() { - for (auto &it: props) { + for (auto &it: _props) { it->load(); } } void PropertyStorage::save_all_propertyes() { - for (auto &it: props) { + for (auto &it: _props) { it->save(); } } void PropertyStorage::clear() { - if (props.size()) { - props.clear(); + if (_props.size()) { + _props.clear(); } } uint16_t PropertyStorage::size() { - IProperty* prop = props.back(); + IProperty* prop = _props.back(); return prop->get_offset() + prop->size(); } \ No newline at end of file diff --git a/src/properties/property_storage.h b/src/properties/property_storage.h index 5fdb223..e30fe4b 100644 --- a/src/properties/property_storage.h +++ b/src/properties/property_storage.h @@ -6,11 +6,10 @@ class IProperty; class PropertyStorage { - std::vector props; - // singlton property // Конструктор копирования и оператор присваивания копированием недоступны - PropertyStorage() {}; + PropertyStorage() = default; + ~PropertyStorage() = default; PropertyStorage(const PropertyStorage& ) = delete; PropertyStorage& operator=(const PropertyStorage& ) = delete; public: @@ -19,9 +18,13 @@ class PropertyStorage { return instance; } - void add_property(IProperty *prop); + uint16_t add_property(IProperty *prop); + std::vector get_props() const; + void load_all_propertyes(); void save_all_propertyes(); void clear(); uint16_t size(); +private: + std::vector _props; }; From 4dd2286ad4653080f6ef526cb9322a4cccf71bf7 Mon Sep 17 00:00:00 2001 From: npo6ka Date: Sun, 19 Oct 2025 17:06:06 +0300 Subject: [PATCH 55/75] refactoring debug_lib --- src/libs/debug_lib.cpp | 30 +++++++++++++++++++++++++ src/libs/debug_lib.h | 51 +++++++++++++----------------------------- src/myapplication.cpp | 2 +- 3 files changed, 46 insertions(+), 37 deletions(-) create mode 100644 src/libs/debug_lib.cpp 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/myapplication.cpp b/src/myapplication.cpp index 121181d..694ee15 100644 --- a/src/myapplication.cpp +++ b/src/myapplication.cpp @@ -29,7 +29,7 @@ MyApplication::~MyApplication() { 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); From d64217da1d5c5ed576eda1e07d7ffecb78be31d3 Mon Sep 17 00:00:00 2001 From: npo6ka Date: Sun, 19 Oct 2025 17:06:17 +0300 Subject: [PATCH 56/75] fix problem with NY2020 --- src/effect_list/effects/ny2020.cpp | 315 +++++++++++++++++++++++++++++ src/effect_list/effects/ny2020.h | 313 +--------------------------- 2 files changed, 317 insertions(+), 311 deletions(-) create mode 100644 src/effect_list/effects/ny2020.cpp diff --git a/src/effect_list/effects/ny2020.cpp b/src/effect_list/effects/ny2020.cpp new file mode 100644 index 0000000..a77c962 --- /dev/null +++ b/src/effect_list/effects/ny2020.cpp @@ -0,0 +1,315 @@ +#include "ny2020.h" + +#include "effect_list/effect.h" + +#define R 0xff0000 +#define DR 0x400000 +#define G 0x00ff00 +#define DG 0x004000 +#define B 0x0000ff +#define DB 0x000040 +#define Y 0xffff00 +#define C 0x00ffff +#define W 0xffffff +#define BR 0x402010 +#define LB 0xc08040 +#define O 0xff8000 +#define P 0xffc0c0 + +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, + 0, W, DB, DB, DB, DB, DB, W, 0, 0, + W, DB, DB, DB, DB, DB, DB, DB, W, 0, + 0, BR, BR, BR, BR, BR, BR, BR, 0, 0, + 0, BR, Y, Y, BR, Y, Y, BR, 0, 0, + 0, BR, Y, Y, BR, Y, Y, BR, 0, 0, + 0, BR, BR, BR, BR, Y, Y, BR, 0, 0, + W, W, W, W, W, Y, Y, W, W, W, +}; + +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, + 0, W, DB, DB, DB, DB, DB, W, 0, 0, + 0, 0, BR, BR, BR, BR, BR, 0, 0, 0, + 0, 0, BR, C, C, C, BR, 0, 0, 0, + 0, 0, BR, C, C, C, BR, 0, 0, 0, + 0, 0, BR, BR, BR, BR, BR, 0, 0, 0, + 0, W, BR, BR, BR, BR, BR, W, W, 0, + W, W, W, W, W, W, W, W, W, W, +}; + +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, + 0, 0, 0, 0, G, 0, 0, 0, 0, 0, + 0, 0, 0, G, G, G, 0, 0, 0, 0, + 0, 0, G, G, G, G, G, 0, 0, 0, + 0, 0, 0, 0, G, 0, 0, 0, 0, 0, + 0, 0, G, G, G, G, G, 0, 0, 0, + 0, G, G, G, G, G, G, G, 0, 0, + 0, 0, 0, 0, G, 0, 0, 0, 0, 0, +}; + +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, + 0, 0, 0, 0, G, 0, 0, 0, 0, 0, + 0, 0, 0, G, G, G, 0, 0, 0, 0, + 0, 0, G, G, G, G, G, 0, 0, 0, + 0, 0, 0, 0, G, 0, 0, 0, 0, 0, + 0, 0, G, G, G, G, G, 0, 0, 0, + 0, G, G, G, G, G, G, G, 0, 0, + 0, 0, 0, 0, G, 0, 0, 0, 0, 0, +}; + +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, + 0, 0, W, G, G, G, W, 0, 0, 0, + 0, W, G, G, G, G, G, W, 0, 0, + 0, 0, W, G, G, G, W, 0, 0, 0, + 0, W, G, G, G, G, G, W, 0, 0, + W, G, G, G, G, G, G, G, W, 0, + 0, W, W, G, G, G, W, W, 0, 0, + 0, 0, 0, G, G, G, 0, 0, 0, 0, +}; + +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, + 0, 0, R, O, G, O, Y, 0, 0, 0, + 0, 0, 0, G, Y, G, 0, 0, 0, 0, + 0, 0, G, G, G, G, G, 0, 0, 0, + 0, 0, Y, G, G, G, Y, 0, 0, 0, + 0, R, G, O, G, O, G, R, 0, 0, + O, G, G, G, R, G, G, G, O, 0, + 0, 0, 0, G, G, G, 0, 0, 0, 0, +}; + +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, + 0, C, 0, C, C, C, 0, C, 0, 0, + 0, 0, C, 0, C, 0, C, 0, 0, 0, + C, C, C, C, C, C, C, C, C, 0, + 0, 0, C, 0, C, 0, C, 0, 0, 0, + 0, C, 0, C, C, C, 0, C, 0, 0, + 0, 0, C, 0, C, 0, C, 0, 0, 0, + 0, 0, 0, 0, C, 0, 0, 0, 0, 0, +}; + +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, + R, 0, 0, R, 0, R, 0, 0, R, 0, + 0, R, R, R, R, R, R, R, 0, 0, + DG, DG, DG, DG, R, DG, DG, DG, DG, 0, + DG, DG, DG, DG, R, DG, DG, DG, DG, 0, + R, R, R, R, R, R, R, R, R, 0, + DG, DG, DG, DG, R, DG, DG, DG, DG, 0, + DG, DG, DG, DG, R, DG, DG, DG, DG, 0, +}; + +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, + 0, LB, LB, 0, 0, 0, 0, 0, 0, 0, + BR, LB, LB, 0, 0, 0, 0, 0, 0, 0, + 0, 0, LB, LB, LB, LB, LB, LB, LB, 0, + 0, 0, LB, LB, LB, LB, LB, LB, 0, 0, + 0, LB, LB, LB, LB, LB, LB, LB, 0, 0, + 0, BR, 0, LB, 0, LB, 0, LB, 0, 0, + 0, 0, 0, BR, 0, BR, 0, BR, 0, 0, +}; + +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, + 0, 0, 0, R, R, R, R, W, W, 0, + 0, 0, R, R, R, R, R, 0, 0, 0, + 0, 0, R, R, R, R, R, R, 0, 0, + 0, R, R, R, R, R, R, R, 0, 0, + 0, R, R, R, R, R, R, R, 0, 0, + W, W, W, W, W, W, W, W, W, 0, + W, W, W, W, W, W, W, W, W, 0, +}; + +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, + 0, W, W, 0, W, 0, W, W, 0, 0, + 0, 0, W, W, W, W, W, 0, 0, 0, + 0, 0, R, R, R, R, R, 0, 0, 0, + 0, W, W, W, R, W, W, W, 0, 0, + W, W, W, W, W, R, W, W, W, 0, + 0, W, W, W, 0, R, W, W, 0, 0, + 0, 0, W, W, W, W, W, 0, 0, 0, +}; + +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, + 0, W, R, W, R, W, R, 0, R, 0, + 0, R, R, R, W, R, R, 0, R, 0, + 0, W, R, W, R, W, R, 0, R, 0, + 0, W, W, W, W, W, W, 0, R, 0, + 0, R, R, R, R, R, R, R, R, 0, + 0, R, R, R, R, R, R, 0, 0, 0, + 0, R, R, R, R, R, R, 0, 0, 0, +}; + +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, + 0, 0, 0, 0, G, G, 0, 0, 0, 0, + 0, 0, 0, G, G, G, G, 0, 0, 0, + 0, 0, G, G, G, G, G, G, 0, 0, + 0, 0, W, W, W, W, W, W, 0, 0, + 0, 0, R, R, R, R, R, R, 0, 0, + 0, 0, G, G, G, G, G, G, 0, 0, + 0, 0, G, G, G, G, G, G, 0, 0, +}; + +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, + 0, 0, 0, 0, 0, P, P, 0, W, W, + 0, 0, 0, 0, 0, R, DR, 0, 0, 0, + 0, 0, P, R, R, DR, DR, 0, 0, R, + 0, 0, 0, 0, DR, DR, DR, 0, R, R, + BR, 0, 0, DR, DR, DR, DR, R, R, R, + BR, 0, R, R, R, R, R, R, R, R, + 0, BR, BR, BR, BR, BR, BR, BR, BR, BR, +}; + +static const uint32_t sprite15[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { + 0, 0, 0, R, 0, R, 0, 0, 0, 0, + 0, 0, G, R, R, R, G, 0, 0, 0, + 0, R, G, R, 0, R, G, R, 0, 0, + G, R, R, 0, 0, 0, R, R, G, 0, + G, G, 0, 0, 0, 0, 0, G, G, 0, + R, R, 0, 0, 0, 0, 0, R, R, 0, + R, G, G, 0, 0, 0, G, G, R, 0, + 0, G, R, 0, 0, R, R, G, 0, 0, + 0, 0, R, G, R, G, R, 0, 0, 0, + 0, 0, 0, G, R, G, 0, 0, 0, 0, +}; + +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, + 0, 0, W, W, 0, 0, 0, 0, 0, 0, + 0, 0, W, W, 0, 0, 0, 0, 0, 0, + 0, 0, W, W, 0, 0, 0, 0, 0, LB, + 0, 0, W, W, 0, 0, 0, 0, 0, LB, + 0, LB, W, W, LB, 0, 0, 0, LB, 0, + 0, LB, LB, LB, LB, LB, LB, LB, 0, 0, + 0, 0, LB, LB, LB, LB, 0, 0, 0, 0, +}; + +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, + 0, 0, 0, W, W, W, 0, 0, 0, 0, + 0, 0, W, W, W, W, W, 0, 0, 0, + 0, 0, BR, W, BR, W, BR, 0, 0, 0, + 0, BR, BR, W, BR, W, BR, BR, 0, 0, + 0, BR, BR, BR, BR, W, BR, BR, 0, 0, + BR, BR, BR, BR, BR, BR, BR, BR, BR, 0, + BR, BR, BR, BR, BR, BR, BR, BR, BR, 0, +}; + +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, + 0, 0, 0, 0, 0, 0, R, W, W, 0, + 0, 0, 0, 0, 0, W, R, R, 0, 0, + 0, 0, 0, 0, R, W, W, 0, 0, 0, + 0, 0, 0, W, R, R, 0, 0, 0, 0, + 0, 0, R, W, W, 0, 0, 0, 0, 0, + 0, W, R, R, 0, 0, 0, 0, 0, 0, + 0, W, W, 0, 0, 0, 0, 0, 0, 0, +}; + +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, + 0, 0, 0, R, R, G, R, R, 0, 0, + 0, 0, 0, R, G, G, G, R, 0, 0, + 0, 0, 0, R, R, G, R, R, 0, 0, + 0, 0, R, R, R, R, W, W, 0, 0, + 0, R, R, R, R, R, W, W, 0, 0, + W, W, R, R, R, R, W, 0, 0, 0, + W, W, R, R, R, R, 0, 0, 0, 0, +}; + +#undef R +#undef DR +#undef G +#undef DG +#undef B +#undef DB +#undef Y +#undef C +#undef W +#undef BR +#undef LB +#undef O +#undef P + +static const uint32_t * const sprites[] = +{ + sprite1, sprite2, sprite3, sprite4, sprite5, sprite6, sprite7, + sprite8, sprite9, sprite10, sprite11, sprite12, sprite13, + sprite14, sprite15, sprite16, sprite17, sprite18, sprite19, +}; + +#define NY_TYPES (sizeof(sprites) / sizeof(sprites[0])) + +void NY2020::on_init() +{ + phase = 0; + for (int i = 0 ; i < NY_COUNT ; ++i) { + items[i] = random8(NY_TYPES); + } + + 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]]); + } + 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); + } +} diff --git a/src/effect_list/effects/ny2020.h b/src/effect_list/effects/ny2020.h index 79081f9..5dc7fcc 100644 --- a/src/effect_list/effects/ny2020.h +++ b/src/effect_list/effects/ny2020.h @@ -5,324 +5,15 @@ #define NY_SPRITE_W 10 #define NY_SPRITE_H 10 -#define R 0xff0000 -#define DR 0x400000 -#define G 0x00ff00 -#define DG 0x004000 -#define B 0x0000ff -#define DB 0x000040 -#define Y 0xffff00 -#define C 0x00ffff -#define W 0xffffff -#define BR 0x402010 -#define LB 0xc08040 -#define O 0xff8000 -#define P 0xffc0c0 - -static const 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, - 0, W, DB, DB, DB, DB, DB, W, 0, 0, - W, DB, DB, DB, DB, DB, DB, DB, W, 0, - 0, BR, BR, BR, BR, BR, BR, BR, 0, 0, - 0, BR, Y, Y, BR, Y, Y, BR, 0, 0, - 0, BR, Y, Y, BR, Y, Y, BR, 0, 0, - 0, BR, BR, BR, BR, Y, Y, BR, 0, 0, - W, W, W, W, W, Y, Y, W, W, W, -}; - -static const 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, - 0, W, DB, DB, DB, DB, DB, W, 0, 0, - 0, 0, BR, BR, BR, BR, BR, 0, 0, 0, - 0, 0, BR, C, C, C, BR, 0, 0, 0, - 0, 0, BR, C, C, C, BR, 0, 0, 0, - 0, 0, BR, BR, BR, BR, BR, 0, 0, 0, - 0, W, BR, BR, BR, BR, BR, W, W, 0, - W, W, W, W, W, W, W, W, W, W, -}; - -static const 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, - 0, 0, 0, 0, G, 0, 0, 0, 0, 0, - 0, 0, 0, G, G, G, 0, 0, 0, 0, - 0, 0, G, G, G, G, G, 0, 0, 0, - 0, 0, 0, 0, G, 0, 0, 0, 0, 0, - 0, 0, G, G, G, G, G, 0, 0, 0, - 0, G, G, G, G, G, G, G, 0, 0, - 0, 0, 0, 0, G, 0, 0, 0, 0, 0, -}; - -static const 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, - 0, 0, 0, 0, G, 0, 0, 0, 0, 0, - 0, 0, 0, G, G, G, 0, 0, 0, 0, - 0, 0, G, G, G, G, G, 0, 0, 0, - 0, 0, 0, 0, G, 0, 0, 0, 0, 0, - 0, 0, G, G, G, G, G, 0, 0, 0, - 0, G, G, G, G, G, G, G, 0, 0, - 0, 0, 0, 0, G, 0, 0, 0, 0, 0, -}; - -static const 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, - 0, 0, W, G, G, G, W, 0, 0, 0, - 0, W, G, G, G, G, G, W, 0, 0, - 0, 0, W, G, G, G, W, 0, 0, 0, - 0, W, G, G, G, G, G, W, 0, 0, - W, G, G, G, G, G, G, G, W, 0, - 0, W, W, G, G, G, W, W, 0, 0, - 0, 0, 0, G, G, G, 0, 0, 0, 0, -}; - -static const 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, - 0, 0, R, O, G, O, Y, 0, 0, 0, - 0, 0, 0, G, Y, G, 0, 0, 0, 0, - 0, 0, G, G, G, G, G, 0, 0, 0, - 0, 0, Y, G, G, G, Y, 0, 0, 0, - 0, R, G, O, G, O, G, R, 0, 0, - O, G, G, G, R, G, G, G, O, 0, - 0, 0, 0, G, G, G, 0, 0, 0, 0, -}; - -static const 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, - 0, C, 0, C, C, C, 0, C, 0, 0, - 0, 0, C, 0, C, 0, C, 0, 0, 0, - C, C, C, C, C, C, C, C, C, 0, - 0, 0, C, 0, C, 0, C, 0, 0, 0, - 0, C, 0, C, C, C, 0, C, 0, 0, - 0, 0, C, 0, C, 0, C, 0, 0, 0, - 0, 0, 0, 0, C, 0, 0, 0, 0, 0, -}; - -static const 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, - R, 0, 0, R, 0, R, 0, 0, R, 0, - 0, R, R, R, R, R, R, R, 0, 0, - DG, DG, DG, DG, R, DG, DG, DG, DG, 0, - DG, DG, DG, DG, R, DG, DG, DG, DG, 0, - R, R, R, R, R, R, R, R, R, 0, - DG, DG, DG, DG, R, DG, DG, DG, DG, 0, - DG, DG, DG, DG, R, DG, DG, DG, DG, 0, -}; - -static const 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, - 0, LB, LB, 0, 0, 0, 0, 0, 0, 0, - BR, LB, LB, 0, 0, 0, 0, 0, 0, 0, - 0, 0, LB, LB, LB, LB, LB, LB, LB, 0, - 0, 0, LB, LB, LB, LB, LB, LB, 0, 0, - 0, LB, LB, LB, LB, LB, LB, LB, 0, 0, - 0, BR, 0, LB, 0, LB, 0, LB, 0, 0, - 0, 0, 0, BR, 0, BR, 0, BR, 0, 0, -}; - -static const 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, - 0, 0, 0, R, R, R, R, W, W, 0, - 0, 0, R, R, R, R, R, 0, 0, 0, - 0, 0, R, R, R, R, R, R, 0, 0, - 0, R, R, R, R, R, R, R, 0, 0, - 0, R, R, R, R, R, R, R, 0, 0, - W, W, W, W, W, W, W, W, W, 0, - W, W, W, W, W, W, W, W, W, 0, -}; - -static const 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, - 0, W, W, 0, W, 0, W, W, 0, 0, - 0, 0, W, W, W, W, W, 0, 0, 0, - 0, 0, R, R, R, R, R, 0, 0, 0, - 0, W, W, W, R, W, W, W, 0, 0, - W, W, W, W, W, R, W, W, W, 0, - 0, W, W, W, 0, R, W, W, 0, 0, - 0, 0, W, W, W, W, W, 0, 0, 0, -}; - -static const 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, - 0, W, R, W, R, W, R, 0, R, 0, - 0, R, R, R, W, R, R, 0, R, 0, - 0, W, R, W, R, W, R, 0, R, 0, - 0, W, W, W, W, W, W, 0, R, 0, - 0, R, R, R, R, R, R, R, R, 0, - 0, R, R, R, R, R, R, 0, 0, 0, - 0, R, R, R, R, R, R, 0, 0, 0, -}; - -static const 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, - 0, 0, 0, 0, G, G, 0, 0, 0, 0, - 0, 0, 0, G, G, G, G, 0, 0, 0, - 0, 0, G, G, G, G, G, G, 0, 0, - 0, 0, W, W, W, W, W, W, 0, 0, - 0, 0, R, R, R, R, R, R, 0, 0, - 0, 0, G, G, G, G, G, G, 0, 0, - 0, 0, G, G, G, G, G, G, 0, 0, -}; - -static const 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, - 0, 0, 0, 0, 0, P, P, 0, W, W, - 0, 0, 0, 0, 0, R, DR, 0, 0, 0, - 0, 0, P, R, R, DR, DR, 0, 0, R, - 0, 0, 0, 0, DR, DR, DR, 0, R, R, - BR, 0, 0, DR, DR, DR, DR, R, R, R, - BR, 0, R, R, R, R, R, R, R, R, - 0, BR, BR, BR, BR, BR, BR, BR, BR, BR, -}; - -static const uint32_t sprite15[NY_SPRITE_W*NY_SPRITE_H] PROGMEM = { - 0, 0, 0, R, 0, R, 0, 0, 0, 0, - 0, 0, G, R, R, R, G, 0, 0, 0, - 0, R, G, R, 0, R, G, R, 0, 0, - G, R, R, 0, 0, 0, R, R, G, 0, - G, G, 0, 0, 0, 0, 0, G, G, 0, - R, R, 0, 0, 0, 0, 0, R, R, 0, - R, G, G, 0, 0, 0, G, G, R, 0, - 0, G, R, 0, 0, R, R, G, 0, 0, - 0, 0, R, G, R, G, R, 0, 0, 0, - 0, 0, 0, G, R, G, 0, 0, 0, 0, -}; - -static const 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, - 0, 0, W, W, 0, 0, 0, 0, 0, 0, - 0, 0, W, W, 0, 0, 0, 0, 0, 0, - 0, 0, W, W, 0, 0, 0, 0, 0, LB, - 0, 0, W, W, 0, 0, 0, 0, 0, LB, - 0, LB, W, W, LB, 0, 0, 0, LB, 0, - 0, LB, LB, LB, LB, LB, LB, LB, 0, 0, - 0, 0, LB, LB, LB, LB, 0, 0, 0, 0, -}; - -static const 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, - 0, 0, 0, W, W, W, 0, 0, 0, 0, - 0, 0, W, W, W, W, W, 0, 0, 0, - 0, 0, BR, W, BR, W, BR, 0, 0, 0, - 0, BR, BR, W, BR, W, BR, BR, 0, 0, - 0, BR, BR, BR, BR, W, BR, BR, 0, 0, - BR, BR, BR, BR, BR, BR, BR, BR, BR, 0, - BR, BR, BR, BR, BR, BR, BR, BR, BR, 0, -}; - -static const 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, - 0, 0, 0, 0, 0, 0, R, W, W, 0, - 0, 0, 0, 0, 0, W, R, R, 0, 0, - 0, 0, 0, 0, R, W, W, 0, 0, 0, - 0, 0, 0, W, R, R, 0, 0, 0, 0, - 0, 0, R, W, W, 0, 0, 0, 0, 0, - 0, W, R, R, 0, 0, 0, 0, 0, 0, - 0, W, W, 0, 0, 0, 0, 0, 0, 0, -}; - -static const 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, - 0, 0, 0, R, R, G, R, R, 0, 0, - 0, 0, 0, R, G, G, G, R, 0, 0, - 0, 0, 0, R, R, G, R, R, 0, 0, - 0, 0, R, R, R, R, W, W, 0, 0, - 0, R, R, R, R, R, W, W, 0, 0, - W, W, R, R, R, R, W, 0, 0, 0, - W, W, R, R, R, R, 0, 0, 0, 0, -}; - -#undef R -#undef DR -#undef G -#undef DG -#undef B -#undef DB -#undef Y -#undef C -#undef W -#undef BR -#undef LB -#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, - sprite8, sprite9, sprite10, sprite11, sprite12, sprite13, - sprite14, sprite15, sprite16, sprite17, sprite18, sprite19, -}; - -#define NY_TYPES (sizeof(sprites) / sizeof(sprites[0])) - class NY2020 : public Effect { public: NY2020() {} - void on_init() - { - phase = 0; - for (int i = 0 ; i < NY_COUNT ; ++i) { - items[i] = random8(NY_TYPES); - } - - set_fps(10); - } - - void 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]]); - } - 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); - } - } + void on_init(); + void on_update(); private: int phase; From 0d13173bd77f6e8506b31125d51821b1d9e8a40a Mon Sep 17 00:00:00 2001 From: npo6ka Date: Sun, 19 Oct 2025 17:07:09 +0300 Subject: [PATCH 57/75] move fps manager --- src/{effect_list/fps_manager.h => core/effect/FpsManager.h} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename src/{effect_list/fps_manager.h => core/effect/FpsManager.h} (95%) diff --git a/src/effect_list/fps_manager.h b/src/core/effect/FpsManager.h similarity index 95% rename from src/effect_list/fps_manager.h rename to src/core/effect/FpsManager.h index d3e32d3..da7ca7a 100644 --- a/src/effect_list/fps_manager.h +++ b/src/core/effect/FpsManager.h @@ -3,10 +3,10 @@ #include #include -class FPSManager { +class FpsManager { public: - FPSManager() = default; - ~FPSManager() = default; + FpsManager() = default; + ~FpsManager() = default; float getRealFPS() const { return (float)_real_fps / 10; From 6368e910eb813ce76c05109000a6a77008764c49 Mon Sep 17 00:00:00 2001 From: npo6ka Date: Sun, 19 Oct 2025 17:07:35 +0300 Subject: [PATCH 58/75] move effect factory --- src/core/effect/EffectFactory.cpp | 103 +++++++++++++++++++++++++++++ src/core/effect/EffectFactory.h | 14 ++++ src/effect_list/effect_factory.cpp | 102 ---------------------------- src/effect_list/effect_factory.h | 11 --- 4 files changed, 117 insertions(+), 113 deletions(-) create mode 100644 src/core/effect/EffectFactory.cpp create mode 100644 src/core/effect/EffectFactory.h delete mode 100644 src/effect_list/effect_factory.cpp delete mode 100644 src/effect_list/effect_factory.h diff --git a/src/core/effect/EffectFactory.cpp b/src/core/effect/EffectFactory.cpp new file mode 100644 index 0000000..ac34652 --- /dev/null +++ b/src/core/effect/EffectFactory.cpp @@ -0,0 +1,103 @@ +#include "EffectFactory.h" + +#include "effect_list/erroreffect.h" +#include "effect_list/effects/slow_random.h" +#include "effect_list/effects/simple_rainbow.h" +#include "effect_list/effects/dribs.h" +#include "effect_list/effects/rain.h" +#include "effect_list/effects/all_random.h" +#include "effect_list/effects/snow.h" +#include "effect_list/effects/fire.h" +#include "effect_list/effects/the_matrix.h" +#include "effect_list/effects/simple_balls.h" +#include "effect_list/effects/confetti.h" +#include "effect_list/effects/starfall.h" +#include "effect_list/effects/dynamic_square.h" +#include "effect_list/effects/random_rain.h" +#include "effect_list/effects/rainbow_rain.h" +#include "effect_list/effects/points.h" +#include "effect_list/effects/rainbow_point.h" +#include "effect_list/effects/rainbow_static_point.h" +#include "effect_list/effects/text.h" +#include "effect_list/effects/mouse.h" +#include "effect_list/effects/pacman.h" +#include "effect_list/effects/circular_point.h" +#include "effect_list/effects/zigzag.h" +#include "effect_list/effects/horizontal_rainbow_point.h" +#include "effect_list/effects/ny2020.h" +#include "effect_list/effects/dribs_all_side.h" +#include "effect_list/effects/snake/snake.h" +#include "effect_list/effects/radial_fire.h" +#include "effect_list/effects/radial_pattern.h" +#include "effect_list/effects/crazy_bees.h" + +using EffectCreator = Effect* (*)(); + +template +static Effect *makeEffect() { + return new T(); +} + +template +static constexpr EffectCreator effectCreator() { + return makeEffect; +} + +struct EffectInfo { + const char* effect_name; + EffectCreator effect_creator; +}; + +#define EFFECT_CASE(id, name, type) case id: return EffectInfo{name, effectCreator()}; +#define EFFECT_COUNT 29 // при добавлении нового эффекта, не забудь обновить EFFECT_COUNT + +static EffectInfo getEffectInfo(uint32_t effect_id) { + switch (effect_id) { + EFFECT_CASE(0, "Error", ErrorEffect); + EFFECT_CASE(1, "SlowRandom", SlowRandom); + EFFECT_CASE(2, "SimpleRainbow", SimpleRainbow); + EFFECT_CASE(3, "Dribs", Dribs); + EFFECT_CASE(4, "Rain", Rain); + EFFECT_CASE(5, "AllRandom", AllRandom); + EFFECT_CASE(6, "Snow", Snow); + EFFECT_CASE(7, "Fire", Fire); + EFFECT_CASE(8, "TheMatrix", TheMatrix); + EFFECT_CASE(9, "SimpleBalls", SimpleBalls); + EFFECT_CASE(10, "Confetti", Confetti); + EFFECT_CASE(11, "Starfall", Starfall); + EFFECT_CASE(12, "DynamicSquare", DynamicSquare); + EFFECT_CASE(13, "RandomRain", RandomRain); + EFFECT_CASE(14, "RainbowRain", RainbowRain); + EFFECT_CASE(15, "Points", Points); + EFFECT_CASE(16, "RainbowPoint", RainbowPoint); + EFFECT_CASE(17, "RainbowStaticPoint", RainbowStaticPoint); + EFFECT_CASE(18, "Text", TextMode); + EFFECT_CASE(19, "Mouse", Mouse); + EFFECT_CASE(20, "Pacman", Pacman); + EFFECT_CASE(21, "CircularPoint", CircularPoint); + EFFECT_CASE(22, "Zigzag", ZigZag); + EFFECT_CASE(23, "HorizontalRainbowPoint", HorizontalRainbowPoint); + EFFECT_CASE(24, "Ny2020", NY2020); + EFFECT_CASE(25, "DribsAllSide", DribsAllSide); + EFFECT_CASE(26, "Snake", Snake); + EFFECT_CASE(27, "RadialFire", RadialFire); + EFFECT_CASE(28, "RadialPattern", RadialPattern); + EFFECT_CASE(29, "CrazyBees", CrazyBees); + // при добавлении нового эффекта, не забудь обновить EFFECT_COUNT + + default: + return EffectInfo{"Error", effectCreator()}; + } +} + +uint32_t EffectFactory::getEffectCount() { + return EFFECT_COUNT; +} + +Effect* 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; +} diff --git a/src/core/effect/EffectFactory.h b/src/core/effect/EffectFactory.h new file mode 100644 index 0000000..82a2200 --- /dev/null +++ b/src/core/effect/EffectFactory.h @@ -0,0 +1,14 @@ +#pragma once + +#include "effect_list/effect.h" + +#include + +class EffectFactory { + public: + static Effect* createEffect(uint32_t effect_id); + static const char* getEffectName(uint32_t effect_id); + static uint32_t getEffectCount(); +}; + + diff --git a/src/effect_list/effect_factory.cpp b/src/effect_list/effect_factory.cpp deleted file mode 100644 index ae300ef..0000000 --- a/src/effect_list/effect_factory.cpp +++ /dev/null @@ -1,102 +0,0 @@ -#include "effect_factory.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 EffectCreator = Effect* (*)(); - -template -static Effect *makeEffect() { - return new T(); -} - -template -static constexpr EffectCreator effectCreator() { - return makeEffect; -} - -struct EffectInfo { - const char* effect_name; - EffectCreator effect_creator; -}; - -#define EFFECT_CASE(id, name, type) case id: return EffectInfo{name, effectCreator()}; -#define EFFECT_COUNT 28 - -static EffectInfo getEffectInfo(int effect_id) { - switch (effect_id) { - EFFECT_CASE(0, "SlowRandom", SlowRandom) - EFFECT_CASE(1, "SimpleRainbow", SimpleRainbow) - EFFECT_CASE(2, "Dribs", Dribs) - EFFECT_CASE(3, "Rain", Rain) - EFFECT_CASE(4, "AllRandom", AllRandom) - EFFECT_CASE(5, "Snow", Snow) - EFFECT_CASE(6, "Fire", Fire) - EFFECT_CASE(7, "TheMatrix", TheMatrix) - EFFECT_CASE(8, "SimpleBalls", SimpleBalls) - EFFECT_CASE(9, "Confetti", Confetti) - EFFECT_CASE(10, "Starfall", Starfall) - EFFECT_CASE(11, "DynamicSquare", DynamicSquare) - EFFECT_CASE(12, "RandomRain", RandomRain) - EFFECT_CASE(13, "RainbowRain", RainbowRain) - EFFECT_CASE(14, "Points", Points) - EFFECT_CASE(15, "RainbowPoint", RainbowPoint) - EFFECT_CASE(16, "RainbowStaticPoint", RainbowStaticPoint) - EFFECT_CASE(17, "Text", TextMode) - EFFECT_CASE(18, "Mouse", Mouse) - EFFECT_CASE(19, "Pacman", Pacman) - EFFECT_CASE(20, "CircularPoint", CircularPoint) - EFFECT_CASE(21, "Zigzag", ZigZag) - EFFECT_CASE(22, "HorizontalRainbowPoint", HorizontalRainbowPoint) - EFFECT_CASE(23, "Ny2020", NY2020) - EFFECT_CASE(24, "DribsAllSide", DribsAllSide) - EFFECT_CASE(25, "Snake", Snake) - EFFECT_CASE(26, "RadialFire", RadialFire) - EFFECT_CASE(27, "RadialPattern", RadialPattern) - EFFECT_CASE(28, "CrazyBees", CrazyBees) - // при добавлении нового эффекта, не забудь обновить EFFECT_COUNT - - default: - return EffectInfo{"Error", effectCreator()}; - } -} - -uint16_t EffectFactory::getEffectCount() { - return EFFECT_COUNT; -} - -Effect* EffectFactory::createEffect(int effect_id) { - return getEffectInfo(effect_id).effect_creator(); -} - -const char* EffectFactory::getEffectName(int effect_id) { - return getEffectInfo(effect_id).effect_name; -} diff --git a/src/effect_list/effect_factory.h b/src/effect_list/effect_factory.h deleted file mode 100644 index 3b7e5dc..0000000 --- a/src/effect_list/effect_factory.h +++ /dev/null @@ -1,11 +0,0 @@ -#include -#include "effect.h" - -class EffectFactory { - public: - static Effect* createEffect(int effect_id); - static const char* getEffectName(int effect_id); - static uint16_t getEffectCount(); -}; - - From 658d6dd9593b224167bbfb53385640741225d668 Mon Sep 17 00:00:00 2001 From: npo6ka Date: Sun, 19 Oct 2025 17:08:28 +0300 Subject: [PATCH 59/75] Add File savable variable and Effect storage --- src/core/Serializer.h | 54 ++++ src/core/Variable/FileSavableVariable.h | 40 +++ src/core/Variable/Variable.h | 26 ++ src/core/common_interfaces/ISaveable.h | 10 + src/core/common_interfaces/ISerializable.h | 12 + src/core/common_interfaces/IStream.h | 12 + src/core/effect/EffectInfo.h | 35 +++ src/core/effect/storage/FileEffectStorage.h | 246 ++++++++++++++++++ src/core/effect/storage/IEffectStorage.h | 24 ++ src/core/effect/storage/StaticEffectStorage.h | 130 +++++++++ src/core/file/IFileHandler.h | 18 ++ src/core/file/IFileSavable.h | 53 ++++ src/core/file/LsfFileHandler.h | 104 ++++++++ 13 files changed, 764 insertions(+) create mode 100644 src/core/Serializer.h create mode 100644 src/core/Variable/FileSavableVariable.h create mode 100644 src/core/Variable/Variable.h create mode 100644 src/core/common_interfaces/ISaveable.h create mode 100644 src/core/common_interfaces/ISerializable.h create mode 100644 src/core/common_interfaces/IStream.h create mode 100644 src/core/effect/EffectInfo.h create mode 100644 src/core/effect/storage/FileEffectStorage.h create mode 100644 src/core/effect/storage/IEffectStorage.h create mode 100644 src/core/effect/storage/StaticEffectStorage.h create mode 100644 src/core/file/IFileHandler.h create mode 100644 src/core/file/IFileSavable.h create mode 100644 src/core/file/LsfFileHandler.h 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..62db3f1 --- /dev/null +++ b/src/core/Variable/FileSavableVariable.h @@ -0,0 +1,40 @@ +#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 +{ +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(); + } + } + + virtual void set(const T& value) override { + if (this->_value != value) { + this->_value = value; + save(); + } + } + + virtual bool save() { + return Serializer::serialize(this->_value, *this); + } + + virtual bool load() { + 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/EffectInfo.h b/src/core/effect/EffectInfo.h new file mode 100644 index 0000000..fa1c21a --- /dev/null +++ b/src/core/effect/EffectInfo.h @@ -0,0 +1,35 @@ +# 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); + } +}; \ No newline at end of file diff --git a/src/core/effect/storage/FileEffectStorage.h b/src/core/effect/storage/FileEffectStorage.h new file mode 100644 index 0000000..1f3419f --- /dev/null +++ b/src/core/effect/storage/FileEffectStorage.h @@ -0,0 +1,246 @@ +# pragma once + +#include "core/file/IFileHandler.h" +#include "core/Variable/FileSavableVariable.h" +#include "libs/debug_lib.h" +#include "IEffectStorage.h" +#include "StaticEffectStorage.h" + +#include +#include "vector" + +class FileEffectStorage : public IEffectStorage +{ +private: + IFileHandler *_fileHandler; + FileSavableVariable _currentEffectIndex; + std::vector> _effects; + uint32_t _offset; +public: + FileEffectStorage(IFileHandler *fileHandler) : _fileHandler(fileHandler), _currentEffectIndex(fileHandler, 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 EffectInfo getEffectInfo(uint32_t index) const override { + if (index >= _effects.size()) { + logError("Cannot get effect info: index is out of range\n"); + return EffectInfo(); + } + 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(); + } + + 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, _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"); + StaticEffectStorage defaultStorage; + for (size_t i = 0; i < defaultStorage.size(); i++) { + internalAddEffect(EffectInfo(defaultStorage.getEffectInfo(i).id, generateSavedIndex()), false); + } + _currentEffectIndex.set(0); + } +}; + +// Test code: +// LsfFileHandler 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..ed2cf4d --- /dev/null +++ b/src/core/effect/storage/IEffectStorage.h @@ -0,0 +1,24 @@ +# pragma once + +#include "core/effect/EffectInfo.h" + +#include + +class IEffectStorage +{ +public: + virtual ~IEffectStorage() = default; + + virtual EffectInfo getEffectInfo(uint32_t index) const = 0; + virtual uint32_t getCurrentIndex() const = 0; + virtual void setCurrentIndex(uint32_t index) = 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; +}; diff --git a/src/core/effect/storage/StaticEffectStorage.h b/src/core/effect/storage/StaticEffectStorage.h new file mode 100644 index 0000000..72f18b2 --- /dev/null +++ b/src/core/effect/storage/StaticEffectStorage.h @@ -0,0 +1,130 @@ +# pragma once + +#include "core/effect/EffectInfo.h" +#include "IEffectStorage.h" + +#include "vector" + +class StaticEffectStorage : public IEffectStorage +{ +private: + std::vector _effects; + uint32_t _currentEffectIndex = 0; +public: + StaticEffectStorage() { + createDefaultEffectsList(); + } + + virtual ~StaticEffectStorage() {}; + + virtual EffectInfo getEffectInfo(uint32_t index) const override{ + if (index >= _effects.size()) { + return EffectInfo(); + } + return _effects[index]; + } + + virtual uint32_t getCurrentIndex() const override{ + return _currentEffectIndex; + } + + virtual void setCurrentIndex(uint32_t index) override{ + if (index >= _effects.size()) { + 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()) { + 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()) { + 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() { + // Тут можно комментить не нужные эффекты, чтобы они не попадали в начальный список эффектов + internalAddEffect(1); // SlowRandom + internalAddEffect(2); // SimpleRainbow + internalAddEffect(3); // Dribs + internalAddEffect(4); // Rain + internalAddEffect(5); // AllRandom + internalAddEffect(6); // Snow + internalAddEffect(7); // Fire + internalAddEffect(8); // TheMatrix + internalAddEffect(9); // SimpleBalls + internalAddEffect(10); // Confetti + internalAddEffect(11); // Starfall + internalAddEffect(12); // DynamicSquare + internalAddEffect(13); // RandomRain + internalAddEffect(14); // RainbowRain + internalAddEffect(15); // Points + internalAddEffect(16); // RainbowPoint + internalAddEffect(17); // RainbowStaticPoint + internalAddEffect(18); // Text + internalAddEffect(19); // Mouse + internalAddEffect(20); // Pacman + internalAddEffect(21); // CircularPoint + internalAddEffect(22); // Zigzag + internalAddEffect(23); // HorizontalRainbowPoint + internalAddEffect(24); // Ny2020 + internalAddEffect(25); // DribsAllSide + internalAddEffect(26); // Snake + internalAddEffect(27); // RadialFire + internalAddEffect(28); // RadialPattern + internalAddEffect(29); // CrazyBees + } + + void internalAddEffect(uint32_t effectId) { + // второе значение - это заглушка, т.к. в статическом хранилище не нужно + // хранить идентификатор эффекта в памяти (1 означает, что эффект валидный) + _effects.emplace_back(effectId, 1); + } + + void internalRemoveEffect() { + if (_effects.empty()) { + 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..530ace3 --- /dev/null +++ b/src/core/file/IFileHandler.h @@ -0,0 +1,18 @@ +#pragma once + +#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..a579a1b --- /dev/null +++ b/src/core/file/IFileSavable.h @@ -0,0 +1,53 @@ +#pragma once + +#include "core/file/IFileHandler.h" +#include "core/common_interfaces/IStream.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) { + printf("Error writing file: file handler is not set\n"); + return false; + } + + if (!_fileHandler->seek(_offset + seekOffset)) { + printf("Error writing file: failed to seek to offset %u\n", _offset + seekOffset); + return false; + } + + if (!_fileHandler->write(data, size)) { + printf("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) { + printf("Error reading file: file handler is not set\n"); + return false; + } + + if (!_fileHandler->seek(_offset + seekOffset)) { + printf("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/LsfFileHandler.h b/src/core/file/LsfFileHandler.h new file mode 100644 index 0000000..8636a7c --- /dev/null +++ b/src/core/file/LsfFileHandler.h @@ -0,0 +1,104 @@ +#pragma once + +#include "IFileHandler.h" +#include "libs/debug_lib.h" + +#include "LittleFS.h" + +class LsfFileHandler : public IFileHandler { + File _file; + + LsfFileHandler(const LsfFileHandler&) = delete; + LsfFileHandler& operator= (const LsfFileHandler&) = delete; + LsfFileHandler(LsfFileHandler&& other) = delete; + LsfFileHandler& operator= (LsfFileHandler&& other) = delete; +public: + LsfFileHandler() { + static bool isMounted = false; + if (!isMounted && LittleFS.begin()) { + isMounted = true; + } + }; + virtual ~LsfFileHandler() { + 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; + } + + 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 From 7b9e81cd84b420a282d04e40449c5163cd4e8b28 Mon Sep 17 00:00:00 2001 From: npo6ka Date: Tue, 28 Oct 2025 22:50:48 +0300 Subject: [PATCH 60/75] Add EffectManager --- src/configs/matrix.h | 5 +- src/controls/automode.cpp | 2 +- src/controls/button.cpp | 2 +- src/controls/ir.cpp | 4 +- src/core/Variable/FileSavableVariable.h | 4 +- src/core/effect/EffectFactory.cpp | 18 ++- src/core/effect/EffectFactory.h | 3 +- src/core/effect/EffectInfo.h | 5 + src/core/effect/EffectManager.cpp | 63 ++++++++ src/core/effect/EffectManager.h | 37 +++++ src/core/effect/storage/FileEffectStorage.h | 11 +- src/core/effect/storage/IEffectStorage.h | 2 +- src/core/effect/storage/StaticEffectStorage.h | 13 +- src/core/file/IFileHandler.h | 2 + src/core/file/IFileSavable.h | 11 +- src/core/file/LsfFileHandler.h | 9 +- src/effect_list/effect.h | 1 - src/effect_list/effect_manger.cpp | 4 +- src/effect_list/effect_manger.h | 5 +- src/effect_list/effects/rain.h | 6 +- src/events/ChangeModeEventRequest.h | 85 ++++++++++ src/events/events.h | 65 ++++---- src/libs/StdFeatures.h | 13 ++ src/libs/file_handler.h | 5 +- src/main.cpp | 150 ++++++++---------- src/myapplication.cpp | 19 +++ src/myapplication.h | 8 +- src/properties/memory_manager.cpp | 3 +- src/properties/memory_manager.h | 4 +- src/properties/property.h | 4 +- src/properties/property_storage.cpp | 4 +- src/properties/property_storage.h | 4 +- 32 files changed, 403 insertions(+), 168 deletions(-) create mode 100644 src/core/effect/EffectManager.cpp create mode 100644 src/core/effect/EffectManager.h create mode 100644 src/events/ChangeModeEventRequest.h create mode 100644 src/libs/StdFeatures.h diff --git a/src/configs/matrix.h b/src/configs/matrix.h index be4591f..02f0eef 100644 --- a/src/configs/matrix.h +++ b/src/configs/matrix.h @@ -48,10 +48,7 @@ // =============== Настройки Сохранения в память ================== -#define MOD_LIST_OFFSET 64 // начальная точка записи адресов на режимы в ПЗУ -#define MOD_LIST_MAX_SIZE 128 // максимальное количество режимов, которое можно записать в ПЗУ -#define MOD_DATA_OFFSET MOD_LIST_OFFSET + MOD_LIST_MAX_SIZE * 2 // 2 байта для хранения адреса на данные режима -#define MOD_DATA_SIZE 448 // размер буфера под данные режимов +#define SAVE_TO_EEPROM true // сохранять настройки в EEPROM // ===================== Платформозависимые настройки ===================== #ifdef ESP32DEV diff --git a/src/controls/automode.cpp b/src/controls/automode.cpp index 42b4b92..41bdeab 100644 --- a/src/controls/automode.cpp +++ b/src/controls/automode.cpp @@ -18,7 +18,7 @@ AutoChangeMode::~AutoChangeMode() { void AutoChangeMode::onTick() { if (isEnable()) { if (millis() - _savedTime > _delay) { - auto ev = ChangeModEvent({EventType::ChangeMode, ChangeModEvent::Type::Next, 0, false}); + ChangeModeEvent ev(EventType::ChangeMode, false, ChangeModeEventRequest::Type::Next); Observable::notify(&ev); out("AutoControl: next mode\n"); } diff --git a/src/controls/button.cpp b/src/controls/button.cpp index 6d5ac01..1b1e236 100644 --- a/src/controls/button.cpp +++ b/src/controls/button.cpp @@ -12,7 +12,7 @@ void Button::onTick() { uint8_t clickCount = touch.hasClicks() ? touch.getClicks() : 0U; switch (clickCount) { case 1U: { - auto ev = ChangeModEvent({EventType::ChangeMode, ChangeModEvent::Type::Next}); + ChangeModeEvent ev(EventType::ChangeMode, true, ChangeModeEventRequest::Type::Next); // auto ev = ChangeBoolEvent({EventType::ChangePowerState, true}); Observable::notify(&ev); break; diff --git a/src/controls/ir.cpp b/src/controls/ir.cpp index 6f318c6..cd35ead 100644 --- a/src/controls/ir.cpp +++ b/src/controls/ir.cpp @@ -14,7 +14,7 @@ void IR::onTick() { if (IrReceiver.decode()) { switch (IrReceiver.decodedIRData.command) { case 0x45: { // << - auto ev = ChangeModEvent({EventType::ChangeMode, ChangeModEvent::Type::Previous}); + ChangeModeEvent ev(EventType::ChangeMode, true, ChangeModeEventRequest::Type::Previous); Observable::notify(&ev); break; } @@ -29,7 +29,7 @@ void IR::onTick() { break; } case 0x48: { // >> - auto ev = ChangeModEvent({EventType::ChangeMode, ChangeModEvent::Type::Next}); + ChangeModeEvent ev(EventType::ChangeMode, true, ChangeModeEventRequest::Type::Next); Observable::notify(&ev); break; } diff --git a/src/core/Variable/FileSavableVariable.h b/src/core/Variable/FileSavableVariable.h index 62db3f1..88bff64 100644 --- a/src/core/Variable/FileSavableVariable.h +++ b/src/core/Variable/FileSavableVariable.h @@ -30,11 +30,11 @@ class FileSavableVariable: public Variable, public ISaveable, public IFileSav } } - virtual bool save() { + virtual bool save() override { return Serializer::serialize(this->_value, *this); } - virtual bool load() { + virtual bool load() override { return Serializer::deserialize(this->_value, *this); } }; \ No newline at end of file diff --git a/src/core/effect/EffectFactory.cpp b/src/core/effect/EffectFactory.cpp index ac34652..6f76040 100644 --- a/src/core/effect/EffectFactory.cpp +++ b/src/core/effect/EffectFactory.cpp @@ -31,11 +31,13 @@ #include "effect_list/effects/radial_pattern.h" #include "effect_list/effects/crazy_bees.h" -using EffectCreator = Effect* (*)(); +#include "libs/StdFeatures.h" + +using EffectCreator = std::unique_ptr (*)(); template -static Effect *makeEffect() { - return new T(); +static std::unique_ptr makeEffect() { + return std::make_unique(); } template @@ -43,15 +45,15 @@ static constexpr EffectCreator effectCreator() { return makeEffect; } -struct EffectInfo { +struct EffectCreationInfo { const char* effect_name; EffectCreator effect_creator; }; -#define EFFECT_CASE(id, name, type) case id: return EffectInfo{name, effectCreator()}; +#define EFFECT_CASE(id, name, type) case id: return EffectCreationInfo{name, effectCreator()}; #define EFFECT_COUNT 29 // при добавлении нового эффекта, не забудь обновить EFFECT_COUNT -static EffectInfo getEffectInfo(uint32_t effect_id) { +static EffectCreationInfo getEffectInfo(uint32_t effect_id) { switch (effect_id) { EFFECT_CASE(0, "Error", ErrorEffect); EFFECT_CASE(1, "SlowRandom", SlowRandom); @@ -86,7 +88,7 @@ static EffectInfo getEffectInfo(uint32_t effect_id) { // при добавлении нового эффекта, не забудь обновить EFFECT_COUNT default: - return EffectInfo{"Error", effectCreator()}; + return EffectCreationInfo{"Error", effectCreator()}; } } @@ -94,7 +96,7 @@ uint32_t EffectFactory::getEffectCount() { return EFFECT_COUNT; } -Effect* EffectFactory::createEffect(uint32_t effect_id) { +std::unique_ptr EffectFactory::createEffect(uint32_t effect_id) { return getEffectInfo(effect_id).effect_creator(); } diff --git a/src/core/effect/EffectFactory.h b/src/core/effect/EffectFactory.h index 82a2200..2769566 100644 --- a/src/core/effect/EffectFactory.h +++ b/src/core/effect/EffectFactory.h @@ -3,10 +3,11 @@ #include "effect_list/effect.h" #include +#include class EffectFactory { public: - static Effect* createEffect(uint32_t effect_id); + 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 index fa1c21a..6a662d7 100644 --- a/src/core/effect/EffectInfo.h +++ b/src/core/effect/EffectInfo.h @@ -32,4 +32,9 @@ class EffectInfo : public ISerializable 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..0966809 --- /dev/null +++ b/src/core/effect/EffectManager.cpp @@ -0,0 +1,63 @@ +#include "EffectManager.h" +#include "libs/debug_lib.h" + +EffectManager::EffectManager(IEffectStorage& storage) + : _storage(storage) +{ + Observable::subscribe(EventType::ChangeMode, this); + + this->updateEffect(); +} + +void EffectManager::onTick() { + // Обработка отложенного запроса на смену эффекта + if (_pendingRequest.type != ChangeModeEventRequest::Type::None && (_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::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()); + + ModChangedEvent modChangedEvent(EventType::ModChanged, effectInfo.id, _storage.getCurrentIndex()); + Observable::notify(&modChangedEvent); +} + +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(Event* event) { + if (event->type == EventType::ChangeMode) { + 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..7f88ffa --- /dev/null +++ b/src/core/effect/EffectManager.h @@ -0,0 +1,37 @@ +#pragma once + +#include "events/observer.h" +#include "storage/IEffectStorage.h" +#include "EffectInfo.h" +#include "EffectFactory.h" +#include "FpsManager.h" +#include "effect_list/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(Event* event) override; + +private: + IEffectStorage& _storage; // Ссылка на хранилище эффектов + std::unique_ptr _currentEffect; // Указатель на текущий активный эффект + ChangeModeEventRequest _pendingRequest; // Ожидающий запрос на смену эффекта + FpsManager _fpsManager; // Менеджер FPS +}; diff --git a/src/core/effect/storage/FileEffectStorage.h b/src/core/effect/storage/FileEffectStorage.h index 1f3419f..97fe498 100644 --- a/src/core/effect/storage/FileEffectStorage.h +++ b/src/core/effect/storage/FileEffectStorage.h @@ -8,16 +8,17 @@ #include #include "vector" +#include class FileEffectStorage : public IEffectStorage { private: - IFileHandler *_fileHandler; + std::unique_ptr _fileHandler; FileSavableVariable _currentEffectIndex; std::vector> _effects; uint32_t _offset; public: - FileEffectStorage(IFileHandler *fileHandler) : _fileHandler(fileHandler), _currentEffectIndex(fileHandler, 0, 0) { + 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(); @@ -42,10 +43,10 @@ class FileEffectStorage : public IEffectStorage } virtual ~FileEffectStorage() {}; - virtual EffectInfo getEffectInfo(uint32_t index) const override { + 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(); + return EffectInfo::getErrorEffectInfo(); } return _effects[index].get(); } @@ -158,7 +159,7 @@ class FileEffectStorage : public IEffectStorage } void internalAddEffect(const EffectInfo& effectInfo, bool loadOnCreate) { - _effects.emplace_back(_fileHandler, _offset, std::move(effectInfo), loadOnCreate); + _effects.emplace_back(_fileHandler.get(), _offset, std::move(effectInfo), loadOnCreate); _offset += EffectInfo::typeSize(); } diff --git a/src/core/effect/storage/IEffectStorage.h b/src/core/effect/storage/IEffectStorage.h index ed2cf4d..c76bb99 100644 --- a/src/core/effect/storage/IEffectStorage.h +++ b/src/core/effect/storage/IEffectStorage.h @@ -9,7 +9,7 @@ class IEffectStorage public: virtual ~IEffectStorage() = default; - virtual EffectInfo getEffectInfo(uint32_t index) const = 0; + virtual const EffectInfo& getEffectInfo(uint32_t index) const = 0; virtual uint32_t getCurrentIndex() const = 0; virtual void setCurrentIndex(uint32_t index) = 0; virtual void addEffect(uint32_t effectId) = 0; diff --git a/src/core/effect/storage/StaticEffectStorage.h b/src/core/effect/storage/StaticEffectStorage.h index 72f18b2..86bc4b4 100644 --- a/src/core/effect/storage/StaticEffectStorage.h +++ b/src/core/effect/storage/StaticEffectStorage.h @@ -1,8 +1,10 @@ # pragma once -#include "core/effect/EffectInfo.h" #include "IEffectStorage.h" +#include "core/effect/EffectInfo.h" +#include "libs/debug_lib.h" + #include "vector" class StaticEffectStorage : public IEffectStorage @@ -17,9 +19,10 @@ class StaticEffectStorage : public IEffectStorage virtual ~StaticEffectStorage() {}; - virtual EffectInfo getEffectInfo(uint32_t index) const override{ + virtual const EffectInfo& getEffectInfo(uint32_t index) const override{ if (index >= _effects.size()) { - return EffectInfo(); + logError("Cannot get effect info: index is out of range\n"); + return EffectInfo::getErrorEffectInfo(); } return _effects[index]; } @@ -30,6 +33,7 @@ class StaticEffectStorage : public IEffectStorage 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; @@ -41,6 +45,7 @@ class StaticEffectStorage : public IEffectStorage 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; } @@ -53,6 +58,7 @@ class StaticEffectStorage : public IEffectStorage virtual void removeEffect(uint32_t position) override { if (position >= _effects.size()) { + logError("Cannot remove effect: position is out of range\n"); return; } @@ -118,6 +124,7 @@ class StaticEffectStorage : public IEffectStorage void internalRemoveEffect() { if (_effects.empty()) { + logError("Cannot remove effect: effects list is empty\n"); return; } diff --git a/src/core/file/IFileHandler.h b/src/core/file/IFileHandler.h index 530ace3..d55d51b 100644 --- a/src/core/file/IFileHandler.h +++ b/src/core/file/IFileHandler.h @@ -1,5 +1,7 @@ #pragma once +#include "core/common_interfaces/IStream.h" + #include #include diff --git a/src/core/file/IFileSavable.h b/src/core/file/IFileSavable.h index a579a1b..50cffaf 100644 --- a/src/core/file/IFileSavable.h +++ b/src/core/file/IFileSavable.h @@ -2,6 +2,7 @@ #include "core/file/IFileHandler.h" #include "core/common_interfaces/IStream.h" +#include "libs/debug_lib.h" #include #include @@ -16,17 +17,17 @@ class IFileSaveable : public IStream virtual bool write(const void* data, size_t size, size_t seekOffset = 0) override { if (_fileHandler == nullptr) { - printf("Error writing file: file handler is not set\n"); + logError("Error writing file: file handler is not set\n"); return false; } if (!_fileHandler->seek(_offset + seekOffset)) { - printf("Error writing file: failed to seek to offset %u\n", _offset + seekOffset); + logError("Error writing file: failed to seek to offset %u\n", _offset + seekOffset); return false; } if (!_fileHandler->write(data, size)) { - printf("Error writing file: failed to write data\n"); + logError("Error writing file: failed to write data\n"); return false; } @@ -35,12 +36,12 @@ class IFileSaveable : public IStream virtual bool read(void* data, size_t size, size_t seekOffset = 0) const override { if (_fileHandler == nullptr) { - printf("Error reading file: file handler is not set\n"); + logError("Error reading file: file handler is not set\n"); return false; } if (!_fileHandler->seek(_offset + seekOffset)) { - printf("Error reading file: failed to seek to offset %u\n", _offset + seekOffset); + logError("Error reading file: failed to seek to offset %u\n", _offset + seekOffset); return false; } diff --git a/src/core/file/LsfFileHandler.h b/src/core/file/LsfFileHandler.h index 8636a7c..b47625f 100644 --- a/src/core/file/LsfFileHandler.h +++ b/src/core/file/LsfFileHandler.h @@ -18,10 +18,15 @@ class LsfFileHandler : public IFileHandler { if (!isMounted && LittleFS.begin()) { isMounted = true; } - }; + } + + LsfFileHandler(const char* path) : LsfFileHandler() { + open(path); + } + virtual ~LsfFileHandler() { close(); - }; + } virtual void open(const char* path) override { if (strlen(path) > 32) { diff --git a/src/effect_list/effect.h b/src/effect_list/effect.h index 2af4910..2dbd2be 100644 --- a/src/effect_list/effect.h +++ b/src/effect_list/effect.h @@ -1,7 +1,6 @@ #pragma once #include "libs/led_matrix.h" -#include "properties/property.h" class Effect { diff --git a/src/effect_list/effect_manger.cpp b/src/effect_list/effect_manger.cpp index a7db824..0cc100d 100644 --- a/src/effect_list/effect_manger.cpp +++ b/src/effect_list/effect_manger.cpp @@ -1,4 +1,4 @@ -#include "effect_manger.h" +/*#include "effect_manger.h" #include "fps_manager.h" #include "effect_factory.h" @@ -129,4 +129,4 @@ void EffectsManager::handleEvent(Event *event) { _reqEffect = { Request::Type::Set, ev->id, ev->hardReset }; } } -} \ No newline at end of file +}*/ \ No newline at end of file diff --git a/src/effect_list/effect_manger.h b/src/effect_list/effect_manger.h index d524bdb..7ad7b88 100644 --- a/src/effect_list/effect_manger.h +++ b/src/effect_list/effect_manger.h @@ -1,4 +1,4 @@ -#pragma once +/*#pragma once #include #include "events/observer.h" @@ -43,5 +43,6 @@ class EffectsManager : public IObserver private: Request _reqEffect = { Request::Type::None, 0, false }; Effect* _effect = nullptr; - FPSManager _fpsManager; + FpsManager _fpsManager; }; +*/ \ No newline at end of file diff --git a/src/effect_list/effects/rain.h b/src/effect_list/effects/rain.h index 237bfe2..62da4ce 100644 --- a/src/effect_list/effects/rain.h +++ b/src/effect_list/effects/rain.h @@ -3,7 +3,7 @@ #include "effect_list/effect.h" class Rain : public Effect { - Property step{2}; + uint8_t step{2}; public: Rain() {} @@ -19,8 +19,8 @@ class Rain : public Effect if (random8(255) == 0) { led = CRGB(0, 0, 255); } else if (led.b > 0) { - if (led.b > step.get()) { - led = CRGB(0, 0, led.b - step.get()); + if (led.b > step) { + led = CRGB(0, 0, led.b - step); } else { led = CRGB(0, 0, 0); } 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 945176c..168b958 100644 --- a/src/events/events.h +++ b/src/events/events.h @@ -1,52 +1,53 @@ #pragma once +#include "ChangeModeEventRequest.h" + enum class EventType { - ChangeAutoMod = 0, - ChangePowerState, // Event - SetPowerState, // ChangeBoolEvent - ChangeBrightness, // ChangeIntEvent - ChangeSpeed, // ChangeIntEvent - ChangeMode, // ChangeModEvent - ModChanged, // ModChangedEvent - ChangeModVar, // ChangeModVarEvent - EventAmount // Используется, чтобы знать сколько всего ивентов + ChangeAutoMod = 0, + ChangePowerState, // Event + SetPowerState, // ChangeBoolEvent + ChangeBrightness, // ChangeIntEvent + ChangeSpeed, // ChangeIntEvent + ChangeMode, // ChangeModEvent + ModChanged, // ModChangedEvent + ChangeModVar, // ChangeModVarEvent + 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; - bool hardReset; - ChangeModEvent(EventType et, Type t, uint8_t i = 0, bool h = true) : Event(et), type(t), id(i), hardReset(h) {} +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) {} + 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/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/file_handler.h b/src/libs/file_handler.h index 65ddea4..939a8dc 100644 --- a/src/libs/file_handler.h +++ b/src/libs/file_handler.h @@ -1,10 +1,9 @@ -#pragma once +/*#pragma once #include "LittleFS.h" #include #include -#define out printf #define MAX_FILE_SIZE 65536 #define STEP @@ -94,4 +93,4 @@ class FileHandler { fwrite(&val, sizeof(val), 1, _fp); } } -}; \ No newline at end of file +};*/ \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index b4cbedf..14d3a92 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,90 +1,80 @@ #include "myapplication.h" -#include "properties/memory_manager.h" -#include 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() { - if (!LittleFS.begin()) { - Serial.println("An Error has occurred while mounting LittleFS"); - } app.onInit(); } void loop() { - //app.onTick(); - + app.onTick(); } - - /*FSInfo fsi; - LittleFS.info(fsi); - printf("\ttotalBytes: %d\n\t usedBytes: %d\n\t blockSize: %d\n\t pageSize: \ - %d\n\t maxOpenFiles: %d\n\t maxPathLength: %d\n", fsi.totalBytes, - fsi.usedBytes, fsi.blockSize, fsi.pageSize, fsi.maxOpenFiles, fsi.maxPathLength);*/ - - // Dir dir = LittleFS.openDir("/"); - // while (dir.next()) { - // Serial.println(dir.fileName()); - // if(dir.fileSize()) { - // File f = dir.openFile("r"); - // Serial.println(f.size()); - // } - // } - - - - // File fp = LittleFS.open("/2.txt", "r+"); - // if (!fp) { - // Serial.println("Failed to open file for reading"); - // return; - // } - - // fp.seek(0, SeekSet); - // uint16_t vals[] = {0x12, 0x15, 0x22, 0x34, 0x38, 0x55, 0x5b, 0x60, 0x65, 0x68, 0x70, 0x72, 0x74, 0x76, 0x78, 0x80, 0x82, 0x84, 0x86, 0x88, 0x90, 0x92, 0x94, 0x96, 0x98, 0x100}; - // uint8_t size = sizeof(vals) / sizeof(vals[0]); - // Serial.printf("size: %d\n", size); - - // fp.seek(0, SeekSet); - // for (uint16_t i = 0; i < vals[size - 1]; ++i) { - // fp.write(0); - // } - - // fp.seek(0, SeekSet); - // for (uint16_t i = 0; i < size; ++i) { - // fp.write(vals[i]); - // fp.seek(vals[i], SeekSet); - // } - // fp.write("e"); - - // fp.seek(0, SeekSet); - // Serial.println("File Content:"); - // while (fp.available()) { - // Serial.printf("%d ", fp.read()); - // } - - // unsigned long myTime1, myTime2; - // myTime1 = micros(); - - // fp.seek(0, SeekSet); - // uint8_t val; - // for (int i = 0; i < 26; ++i) { - // val = fp.read(); - // // Serial.printf("%x ", val); - // fp.seek(val, SeekSet); - // } - // myTime2 = micros(); - // Serial.printf("time diff1: %lu\n", myTime2 - myTime1); - - - // uint16_t vals[] = {0x12, 0x15, 0x22, 0x34, 0x38, 0x55, 0x5b, 0x60, 0x65, 0x68, 0x70, 0x72, 0x74, 0x76, 0x78, 0x80, 0x82, 0x84, 0x86, 0x88, 0x90, 0x92, 0x94, 0x96, 0x98, 0x100}; - // uint16_t vals2[] = {0x12, 0x15, 0x22, 0x34, 0x38, 0x55, 0x5b, 0x60, 0x65, 0x68, 0x70, 0x72, 0x74, 0x76, 0x78, 0x80, 0x82, 0x84, 0x86, 0x88, 0x90, 0x92, 0x94, 0x96, 0x98, 0x100}; - // myTime1 = micros(); - // for (int i = 0; i < 26; ++i) { - // val = vals[i]; - // vals2[i] = val; - // } - // vals2[0]++; - // myTime2 = micros(); - // Serial.printf("time diff2: %lu\n", myTime2 - myTime1); - - // fp.close(); diff --git a/src/myapplication.cpp b/src/myapplication.cpp index 694ee15..3df932d 100644 --- a/src/myapplication.cpp +++ b/src/myapplication.cpp @@ -2,6 +2,15 @@ #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), @@ -37,6 +46,15 @@ void MyApplication::onInit() { #if RELAY_ENABLE _relay.onInit(); #endif +#if SAVE_TO_EEPROM + _effectStorage = std::make_unique(std::make_unique("mods.txt")); +#else + _effectStorage = std::make_unique(); +#endif + _effectManager = std::make_unique(*_effectStorage.get()); + + ChangeModeEvent evt(EventType::ModChanged, true, ChangeModeEventRequest::Type::Set, 2, 0); + Observable::notify(&evt); //EffectsList::getInstance(); // инициализируем EffectsList, чтобы сработало уведомление о новом режиме //auto ev = ChangeModEvent({EventType::ChangeMode, ChangeModEvent::Type::Set, 0}); @@ -49,6 +67,7 @@ void MyApplication::onTick() { #endif { if (_isPowerOn) { + _effectManager->onTick(); //EffectsList::getInstance().onTick(); _autoMod.onTick(); } diff --git a/src/myapplication.h b/src/myapplication.h index 7b49e1b..a80e74a 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 diff --git a/src/properties/memory_manager.cpp b/src/properties/memory_manager.cpp index 20288ad..16941f0 100644 --- a/src/properties/memory_manager.cpp +++ b/src/properties/memory_manager.cpp @@ -1,4 +1,4 @@ -#include "memory_manager.h" +/*#include "memory_manager.h" MemoryManager::MemoryManager(const char* filename) { _file = FileHandler{}; @@ -335,3 +335,4 @@ bool MemoryManager::save_mod_var(uint16_t offset, T &val) { } return false; } +*/ \ No newline at end of file diff --git a/src/properties/memory_manager.h b/src/properties/memory_manager.h index 251213b..0a6baf9 100644 --- a/src/properties/memory_manager.h +++ b/src/properties/memory_manager.h @@ -1,4 +1,4 @@ -#pragma once +/*#pragma once #include "libs/file_handler.h" #include @@ -110,4 +110,4 @@ class MemoryManager { private: uint16_t _cur_mod_addr; // инициализируется при создании. 0, если режимов нет. FileHandler _file; -}; \ No newline at end of file +};*/ \ No newline at end of file diff --git a/src/properties/property.h b/src/properties/property.h index f7468bc..a2794fb 100644 --- a/src/properties/property.h +++ b/src/properties/property.h @@ -1,4 +1,4 @@ -#pragma once +/*#pragma once #include "property_storage.h" #include "memory_manager.h" @@ -123,4 +123,4 @@ class Property : public IProperty { T _value; T _min_value; T _max_value; -}; \ No newline at end of file +};*/ \ No newline at end of file diff --git a/src/properties/property_storage.cpp b/src/properties/property_storage.cpp index 21235f9..bd35bc7 100644 --- a/src/properties/property_storage.cpp +++ b/src/properties/property_storage.cpp @@ -1,4 +1,4 @@ -#include "property_storage.h" +/*#include "property_storage.h" #include "property.h" uint16_t PropertyStorage::add_property(IProperty *prop) { @@ -37,4 +37,4 @@ void PropertyStorage::clear() { uint16_t PropertyStorage::size() { IProperty* prop = _props.back(); return prop->get_offset() + prop->size(); -} \ No newline at end of file +}*/ \ No newline at end of file diff --git a/src/properties/property_storage.h b/src/properties/property_storage.h index e30fe4b..2abd966 100644 --- a/src/properties/property_storage.h +++ b/src/properties/property_storage.h @@ -1,4 +1,4 @@ -#pragma once +/*#pragma once #include "vector" #include "stdint.h" @@ -27,4 +27,4 @@ class PropertyStorage { uint16_t size(); private: std::vector _props; -}; +};*/ From c40b0a713915c25a15496de4817cb66d897d9e09 Mon Sep 17 00:00:00 2001 From: npo6ka Date: Sat, 1 Nov 2025 19:51:25 +0200 Subject: [PATCH 61/75] Refactoring + Fix saving problem --- src/core/Variable/FileSavableVariable.h | 21 +++++++++++++++++++++ src/core/effect/EffectManager.cpp | 15 ++++++++++++++- src/core/effect/EffectManager.h | 4 ++++ src/core/effect/storage/IEffectStorage.h | 5 ++++- src/core/file/LsfFileHandler.h | 2 ++ src/events/events.h | 10 +++++----- src/myapplication.cpp | 3 --- 7 files changed, 50 insertions(+), 10 deletions(-) diff --git a/src/core/Variable/FileSavableVariable.h b/src/core/Variable/FileSavableVariable.h index 88bff64..48e58d5 100644 --- a/src/core/Variable/FileSavableVariable.h +++ b/src/core/Variable/FileSavableVariable.h @@ -8,6 +8,9 @@ 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, @@ -23,6 +26,24 @@ class FileSavableVariable: public Variable, public ISaveable, public IFileSav } } + // 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; diff --git a/src/core/effect/EffectManager.cpp b/src/core/effect/EffectManager.cpp index 0966809..a168151 100644 --- a/src/core/effect/EffectManager.cpp +++ b/src/core/effect/EffectManager.cpp @@ -10,8 +10,13 @@ EffectManager::EffectManager(IEffectStorage& storage) } void EffectManager::onTick() { + this->onCheckRequestedEffectChange(); + this->onTickEffect(); +} + +void EffectManager::onCheckRequestedEffectChange() { // Обработка отложенного запроса на смену эффекта - if (_pendingRequest.type != ChangeModeEventRequest::Type::None && (_currentEffect->is_end() || _pendingRequest.hardReset)) { + 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; @@ -30,6 +35,14 @@ void EffectManager::onTick() { } } +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) { diff --git a/src/core/effect/EffectManager.h b/src/core/effect/EffectManager.h index 7f88ffa..eb99af5 100644 --- a/src/core/effect/EffectManager.h +++ b/src/core/effect/EffectManager.h @@ -30,6 +30,10 @@ class EffectManager : public IObserver void handleEvent(Event* event) override; private: + + void onCheckRequestedEffectChange(); + void onTickEffect(); + IEffectStorage& _storage; // Ссылка на хранилище эффектов std::unique_ptr _currentEffect; // Указатель на текущий активный эффект ChangeModeEventRequest _pendingRequest; // Ожидающий запрос на смену эффекта diff --git a/src/core/effect/storage/IEffectStorage.h b/src/core/effect/storage/IEffectStorage.h index c76bb99..c6edeab 100644 --- a/src/core/effect/storage/IEffectStorage.h +++ b/src/core/effect/storage/IEffectStorage.h @@ -11,7 +11,7 @@ class IEffectStorage virtual const EffectInfo& getEffectInfo(uint32_t index) const = 0; virtual uint32_t getCurrentIndex() const = 0; - virtual void setCurrentIndex(uint32_t index) = 0; + virtual void addEffect(uint32_t effectId) = 0; // не очень эффективная функция, лучше не использовать virtual void addEffect(uint32_t effectId, uint32_t position) = 0; @@ -21,4 +21,7 @@ class IEffectStorage 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/file/LsfFileHandler.h b/src/core/file/LsfFileHandler.h index b47625f..15445b6 100644 --- a/src/core/file/LsfFileHandler.h +++ b/src/core/file/LsfFileHandler.h @@ -69,6 +69,8 @@ class LsfFileHandler : public IFileHandler { return false; } + _file.flush(); + return true; } diff --git a/src/events/events.h b/src/events/events.h index 168b958..f469799 100644 --- a/src/events/events.h +++ b/src/events/events.h @@ -4,11 +4,11 @@ enum class EventType { ChangeAutoMod = 0, - ChangePowerState, // Event - SetPowerState, // ChangeBoolEvent - ChangeBrightness, // ChangeIntEvent - ChangeSpeed, // ChangeIntEvent - ChangeMode, // ChangeModEvent + ChangePowerState, // ChangePowerStateEvent + SetPowerState, // SetPowerStateEvent + ChangeBrightness, // ChangeBrightnessEvent + ChangeSpeed, // ChangeSpeedEvent + ChangeMode, // ChangeModeEvent ModChanged, // ModChangedEvent ChangeModVar, // ChangeModVarEvent EventAmount // Используется, чтобы знать сколько всего ивентов diff --git a/src/myapplication.cpp b/src/myapplication.cpp index 3df932d..b18249c 100644 --- a/src/myapplication.cpp +++ b/src/myapplication.cpp @@ -53,9 +53,6 @@ void MyApplication::onInit() { #endif _effectManager = std::make_unique(*_effectStorage.get()); - ChangeModeEvent evt(EventType::ModChanged, true, ChangeModeEventRequest::Type::Set, 2, 0); - Observable::notify(&evt); - //EffectsList::getInstance(); // инициализируем EffectsList, чтобы сработало уведомление о новом режиме //auto ev = ChangeModEvent({EventType::ChangeMode, ChangeModEvent::Type::Set, 0}); //Observable::notify(&ev); From 8a8a2e0d5872fcd1437355ff15995b2dfbc9a687 Mon Sep 17 00:00:00 2001 From: npo6ka Date: Sat, 1 Nov 2025 20:41:26 +0200 Subject: [PATCH 62/75] Refactoring events and configs --- data/2.txt | 0 src/configs/matrix.h | 1 + src/configs/window.h | 31 +++++++++++++++++++++++++++---- src/controls/button.cpp | 4 ++-- src/controls/ir.cpp | 4 ++-- src/events/events.h | 13 +++++++------ src/myapplication.cpp | 18 ++++++++++-------- 7 files changed, 49 insertions(+), 22 deletions(-) delete mode 100644 data/2.txt diff --git a/data/2.txt b/data/2.txt deleted file mode 100644 index e69de29..0000000 diff --git a/src/configs/matrix.h b/src/configs/matrix.h index 02f0eef..51c59c0 100644 --- a/src/configs/matrix.h +++ b/src/configs/matrix.h @@ -49,6 +49,7 @@ // =============== Настройки Сохранения в память ================== #define SAVE_TO_EEPROM true // сохранять настройки в EEPROM +#define SAVE_TO_EEPROM_FILE "mods.txt" // имя файла для сохранения настроек, если используется файловая система // ===================== Платформозависимые настройки ===================== #ifdef ESP32DEV diff --git a/src/configs/window.h b/src/configs/window.h index 2b15df7..c6af43d 100644 --- a/src/configs/window.h +++ b/src/configs/window.h @@ -33,10 +33,9 @@ // ----------- ----------- ^---------- ----------^ // 0---------3 0---------3 0---------3 0---------3 -// ===================== Настройки Кнопки ===================== +// ===================== Настройки управления ===================== #define BTN_ENABLE true // подключить управление кнопкой -#define BTN_PIN (D2) // пин кнопки #define BUTTON_STEP_TIMEOUT (100U) // каждые BUTTON_STEP_TIMEOUT мс будет генерироваться событие удержания кнопки (для регулировки яркости) #define BUTTON_CLICK_TIMEOUT (500U) // максимальное время между нажатиями кнопки в мс, до достижения которого считается серия последовательных нажатий @@ -44,5 +43,29 @@ #define AUTOMOD_DEF_STATE true // Начальное состояние автомода. true - вкл, false - выкл #define IR_ENABLE false // подключить урпавление через ИК приёмник -#define IR_RECEIVE_PIN (D5) // пин к которому подключен ИК приёмник -#define DECODE_SAMSUNG // тип ИК пульта которые даёт команды \ No newline at end of file +#define DECODE_SAMSUNG // тип ИК пульта которые даёт команды + +#define RELAY_ENABLE false // подключить урпавление через ИК приёмник +#define RELAY_DELAY 100 + +// =============== Настройки Сохранения в память ================== + +#define SAVE_TO_EEPROM true // сохранять настройки в EEPROM +#define SAVE_TO_EEPROM_FILE "mods.txt" // имя файла для сохранения настроек, если используется файловая система + +// ===================== Платформозависимые настройки ===================== +#ifdef ESP32DEV + +#define LEDS_PIN (22) // пин к которому подключены светодиоды +#define BTN_PIN (23) // пин кнопки +#define IR_RECEIVE_PIN (24) // пин к которому подключен ИК приёмник +#define RELAY_PIN (25) // пин управления реле + +#else // if DESP12F + +#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/button.cpp b/src/controls/button.cpp index 1b1e236..03244d6 100644 --- a/src/controls/button.cpp +++ b/src/controls/button.cpp @@ -18,12 +18,12 @@ void Button::onTick() { break; } case 2U: { - auto ev = Event({EventType::ChangePowerState}); + auto ev = Event(EventType::ChangePowerState); Observable::notify(&ev); break; } case 3U: { - auto ev = Event({EventType::ChangeAutoMod}); + auto ev = Event(EventType::ChangeAutoMod); Observable::notify(&ev); break; } diff --git a/src/controls/ir.cpp b/src/controls/ir.cpp index cd35ead..825926a 100644 --- a/src/controls/ir.cpp +++ b/src/controls/ir.cpp @@ -19,12 +19,12 @@ void IR::onTick() { break; } case 0x47: { // play - auto ev = ChangeBoolEvent({EventType::SetPowerState, true}); + auto ev = ChangeBoolEvent(EventType::SetPowerState, true); Observable::notify(&ev); break; } case 0x4a: { // || - auto ev = ChangeBoolEvent({EventType::SetPowerState, false}); + auto ev = ChangeBoolEvent(EventType::SetPowerState, false); Observable::notify(&ev); break; } diff --git a/src/events/events.h b/src/events/events.h index f469799..6c4ae74 100644 --- a/src/events/events.h +++ b/src/events/events.h @@ -3,12 +3,13 @@ #include "ChangeModeEventRequest.h" enum class EventType { - ChangeAutoMod = 0, - ChangePowerState, // ChangePowerStateEvent - SetPowerState, // SetPowerStateEvent - ChangeBrightness, // ChangeBrightnessEvent - ChangeSpeed, // ChangeSpeedEvent - ChangeMode, // ChangeModeEvent +// Тип ивена // Структура данных ивента + ChangeAutoMod = 0, // ChangeBoolEvent + ChangePowerState, // Event + SetPowerState, // ChangeBoolEvent + ChangeBrightness, // ChangeIntEvent + ChangeSpeed, // ChangeIntEvent + ChangeMode, // ChangeModeEventRequest ModChanged, // ModChangedEvent ChangeModVar, // ChangeModVarEvent EventAmount // Используется, чтобы знать сколько всего ивентов diff --git a/src/myapplication.cpp b/src/myapplication.cpp index b18249c..156f2f2 100644 --- a/src/myapplication.cpp +++ b/src/myapplication.cpp @@ -1,7 +1,5 @@ #include "myapplication.h" -#include "libs/led_matrix.h" -#include "effect_list/effectslist.h" #include "core/effect/EffectManager.h" #include "libs/StdFeatures.h" @@ -27,11 +25,13 @@ MyApplication::MyApplication() : { Observable::subscribe(EventType::ChangePowerState, this); Observable::subscribe(EventType::SetPowerState, this); + Observable::subscribe(EventType::ChangeMode, this); }; MyApplication::~MyApplication() { Observable::unsubscribe(EventType::ChangePowerState, this); Observable::unsubscribe(EventType::SetPowerState, this); + Observable::unsubscribe(EventType::ChangeMode, this); } // лучше всё по максимому инициализировать тут @@ -47,15 +47,11 @@ void MyApplication::onInit() { _relay.onInit(); #endif #if SAVE_TO_EEPROM - _effectStorage = std::make_unique(std::make_unique("mods.txt")); + _effectStorage = std::make_unique(std::make_unique(SAVE_TO_EEPROM_FILE)); #else _effectStorage = std::make_unique(); #endif _effectManager = std::make_unique(*_effectStorage.get()); - - //EffectsList::getInstance(); // инициализируем EffectsList, чтобы сработало уведомление о новом режиме - //auto ev = ChangeModEvent({EventType::ChangeMode, ChangeModEvent::Type::Set, 0}); - //Observable::notify(&ev); } void MyApplication::onTick() { @@ -65,7 +61,6 @@ void MyApplication::onTick() { { if (_isPowerOn) { _effectManager->onTick(); - //EffectsList::getInstance().onTick(); _autoMod.onTick(); } #if BTN_ENABLE @@ -98,5 +93,12 @@ void MyApplication::handleEvent(Event *event) { } else if (event->type == EventType::SetPowerState) { ChangeBoolEvent *ev = static_cast(event); setPowerState(ev->new_val); + } else if (event->type == EventType::ChangeAutoMod) { + ChangeBoolEvent *ev = static_cast(event); + _autoMod.setIsEnable(ev->new_val); + } else if (event->type == EventType::ChangeMode) { // включить питание при попытках сменить режима + if (!_isPowerOn) { + setPowerState(true); + } } } \ No newline at end of file From 968c38a245cd94acb167996424101dddf777519b Mon Sep 17 00:00:00 2001 From: npo6ka Date: Sat, 1 Nov 2025 20:43:47 +0200 Subject: [PATCH 63/75] Rename effect folder and remove usless file --- src/core/effect/EffectFactory.cpp | 60 ++-- src/core/effect/EffectFactory.h | 10 +- src/core/effect/EffectManager.h | 2 +- src/effect_list/effect_manger.cpp | 132 ------- src/effect_list/effect_manger.h | 48 --- src/effect_list/effectslist.cpp | 215 ----------- src/effect_list/effectslist.h | 36 -- src/{effect_list => effects}/effect.h | 0 .../effects_impl}/all_random.h | 2 +- .../effects_impl}/circular_point.h | 2 +- .../effects_impl}/confetti.h | 2 +- .../effects_impl}/crazy_bees.h | 2 +- .../effects => effects/effects_impl}/dribs.h | 2 +- .../effects_impl}/dribs_all_side.h | 4 +- .../effects_impl}/dynamic_square.h | 2 +- .../effects => effects/effects_impl}/fire.h | 2 +- .../effects_impl}/horizontal_rainbow_point.h | 2 +- .../effects_impl}/mouse.cpp | 0 .../effects => effects/effects_impl}/mouse.h | 2 +- .../effects_impl}/ny2020.cpp | 2 +- .../effects => effects/effects_impl}/ny2020.h | 2 +- .../effects => effects/effects_impl}/pacman.h | 2 +- .../effects => effects/effects_impl}/points.h | 2 +- .../effects_impl}/radial_fire.h | 2 +- .../effects_impl}/radial_pattern.h | 2 +- .../effects => effects/effects_impl}/rain.h | 2 +- .../effects_impl}/rainbow_point.h | 2 +- .../effects_impl}/rainbow_rain.h | 2 +- .../effects_impl}/rainbow_static_point.h | 2 +- .../effects_impl}/random_rain.h | 2 +- .../effects_impl}/simple_balls.h | 2 +- .../effects_impl}/simple_rainbow.h | 2 +- .../effects_impl}/slow_random.h | 2 +- .../effects_impl}/snake/a_star_ai.h | 0 .../effects_impl}/snake/simple_ai.h | 0 .../effects_impl}/snake/snake.h | 2 +- .../effects_impl}/snake/snake_lib.h | 0 .../effects_impl}/snake/with_fallback_ai.h | 0 .../effects => effects/effects_impl}/snow.h | 2 +- .../effects_impl}/starfall.h | 2 +- .../effects => effects/effects_impl}/text.h | 2 +- .../effects_impl}/the_matrix.h | 2 +- .../effects => effects/effects_impl}/zigzag.h | 2 +- src/{effect_list => effects}/erroreffect.h | 0 src/properties/memory_manager.cpp | 338 ------------------ src/properties/memory_manager.h | 113 ------ src/properties/property.h | 126 ------- src/properties/property_storage.cpp | 40 --- src/properties/property_storage.h | 30 -- 49 files changed, 67 insertions(+), 1145 deletions(-) delete mode 100644 src/effect_list/effect_manger.cpp delete mode 100644 src/effect_list/effect_manger.h delete mode 100644 src/effect_list/effectslist.cpp delete mode 100644 src/effect_list/effectslist.h rename src/{effect_list => effects}/effect.h (100%) rename src/{effect_list/effects => effects/effects_impl}/all_random.h (90%) rename src/{effect_list/effects => effects/effects_impl}/circular_point.h (98%) rename src/{effect_list/effects => effects/effects_impl}/confetti.h (94%) rename src/{effect_list/effects => effects/effects_impl}/crazy_bees.h (98%) rename src/{effect_list/effects => effects/effects_impl}/dribs.h (96%) rename src/{effect_list/effects => effects/effects_impl}/dribs_all_side.h (95%) rename src/{effect_list/effects => effects/effects_impl}/dynamic_square.h (95%) rename src/{effect_list/effects => effects/effects_impl}/fire.h (99%) rename src/{effect_list/effects => effects/effects_impl}/horizontal_rainbow_point.h (98%) rename src/{effect_list/effects => effects/effects_impl}/mouse.cpp (100%) rename src/{effect_list/effects => effects/effects_impl}/mouse.h (80%) rename src/{effect_list/effects => effects/effects_impl}/ny2020.cpp (99%) rename src/{effect_list/effects => effects/effects_impl}/ny2020.h (89%) rename src/{effect_list/effects => effects/effects_impl}/pacman.h (99%) rename src/{effect_list/effects => effects/effects_impl}/points.h (99%) rename src/{effect_list/effects => effects/effects_impl}/radial_fire.h (98%) rename src/{effect_list/effects => effects/effects_impl}/radial_pattern.h (97%) rename src/{effect_list/effects => effects/effects_impl}/rain.h (94%) rename src/{effect_list/effects => effects/effects_impl}/rainbow_point.h (98%) rename src/{effect_list/effects => effects/effects_impl}/rainbow_rain.h (95%) rename src/{effect_list/effects => effects/effects_impl}/rainbow_static_point.h (97%) rename src/{effect_list/effects => effects/effects_impl}/random_rain.h (94%) rename src/{effect_list/effects => effects/effects_impl}/simple_balls.h (98%) rename src/{effect_list/effects => effects/effects_impl}/simple_rainbow.h (94%) rename src/{effect_list/effects => effects/effects_impl}/slow_random.h (98%) rename src/{effect_list/effects => effects/effects_impl}/snake/a_star_ai.h (100%) rename src/{effect_list/effects => effects/effects_impl}/snake/simple_ai.h (100%) rename src/{effect_list/effects => effects/effects_impl}/snake/snake.h (99%) rename src/{effect_list/effects => effects/effects_impl}/snake/snake_lib.h (100%) rename src/{effect_list/effects => effects/effects_impl}/snake/with_fallback_ai.h (100%) rename src/{effect_list/effects => effects/effects_impl}/snow.h (98%) rename src/{effect_list/effects => effects/effects_impl}/starfall.h (98%) rename src/{effect_list/effects => effects/effects_impl}/text.h (99%) rename src/{effect_list/effects => effects/effects_impl}/the_matrix.h (96%) rename src/{effect_list/effects => effects/effects_impl}/zigzag.h (96%) rename src/{effect_list => effects}/erroreffect.h (100%) delete mode 100644 src/properties/memory_manager.cpp delete mode 100644 src/properties/memory_manager.h delete mode 100644 src/properties/property.h delete mode 100644 src/properties/property_storage.cpp delete mode 100644 src/properties/property_storage.h diff --git a/src/core/effect/EffectFactory.cpp b/src/core/effect/EffectFactory.cpp index 6f76040..29045f8 100644 --- a/src/core/effect/EffectFactory.cpp +++ b/src/core/effect/EffectFactory.cpp @@ -1,35 +1,35 @@ #include "EffectFactory.h" -#include "effect_list/erroreffect.h" -#include "effect_list/effects/slow_random.h" -#include "effect_list/effects/simple_rainbow.h" -#include "effect_list/effects/dribs.h" -#include "effect_list/effects/rain.h" -#include "effect_list/effects/all_random.h" -#include "effect_list/effects/snow.h" -#include "effect_list/effects/fire.h" -#include "effect_list/effects/the_matrix.h" -#include "effect_list/effects/simple_balls.h" -#include "effect_list/effects/confetti.h" -#include "effect_list/effects/starfall.h" -#include "effect_list/effects/dynamic_square.h" -#include "effect_list/effects/random_rain.h" -#include "effect_list/effects/rainbow_rain.h" -#include "effect_list/effects/points.h" -#include "effect_list/effects/rainbow_point.h" -#include "effect_list/effects/rainbow_static_point.h" -#include "effect_list/effects/text.h" -#include "effect_list/effects/mouse.h" -#include "effect_list/effects/pacman.h" -#include "effect_list/effects/circular_point.h" -#include "effect_list/effects/zigzag.h" -#include "effect_list/effects/horizontal_rainbow_point.h" -#include "effect_list/effects/ny2020.h" -#include "effect_list/effects/dribs_all_side.h" -#include "effect_list/effects/snake/snake.h" -#include "effect_list/effects/radial_fire.h" -#include "effect_list/effects/radial_pattern.h" -#include "effect_list/effects/crazy_bees.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 "libs/StdFeatures.h" diff --git a/src/core/effect/EffectFactory.h b/src/core/effect/EffectFactory.h index 2769566..62336b8 100644 --- a/src/core/effect/EffectFactory.h +++ b/src/core/effect/EffectFactory.h @@ -1,15 +1,15 @@ #pragma once -#include "effect_list/effect.h" +#include "effects/effect.h" #include #include class EffectFactory { - public: - static std::unique_ptr createEffect(uint32_t effect_id); - static const char* getEffectName(uint32_t effect_id); - static uint32_t getEffectCount(); +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/EffectManager.h b/src/core/effect/EffectManager.h index eb99af5..b479cb4 100644 --- a/src/core/effect/EffectManager.h +++ b/src/core/effect/EffectManager.h @@ -5,7 +5,7 @@ #include "EffectInfo.h" #include "EffectFactory.h" #include "FpsManager.h" -#include "effect_list/effect.h" +#include "effects/effect.h" #include "events/events.h" #include diff --git a/src/effect_list/effect_manger.cpp b/src/effect_list/effect_manger.cpp deleted file mode 100644 index 0cc100d..0000000 --- a/src/effect_list/effect_manger.cpp +++ /dev/null @@ -1,132 +0,0 @@ -/*#include "effect_manger.h" - -#include "fps_manager.h" -#include "effect_factory.h" -#include "properties/memory_manager.h" -#include "properties/property_storage.h" - -EffectsManager::EffectsManager() { - Observable::subscribe(EventType::ChangeMode, this); -}; - -EffectsManager::~EffectsManager() { - Observable::unsubscribe(EventType::ChangeMode, this); -}; - -void EffectsManager::init() { - setEffect(MemoryManager::instance().get_cur_mod_num()); -} - -void EffectsManager::clearEffect() { - if (_effect) { - delete _effect; - _effect = nullptr; - } -} - -void EffectsManager::setErrorEffect() { - clearEffect(); - _effect = EffectFactory::createEffect(-1); - _effect->on_init(); -} - -uint8_t EffectsManager::getEffectId() const { - return MemoryManager::instance().load_mod_id(); -} - -uint8_t EffectsManager::getEffectNum() const { - return MemoryManager::instance().get_cur_mod_num(); -} - -std::string EffectsManager::getEffectName() const { - uint8_t id = getEffectId(); - if (id > 0 && id < EffectFactory::getEffectCount()) { - return ""; - } - - return EffectFactory::getEffectName(id); -} - - -void EffectsManager::syncEffect() { - clearEffect(); - _effect = EffectFactory::createEffect(MemoryManager::instance().load_mod_id()); - - if (!_effect) { - out("ERROR: Effect not crated\n"); - setErrorEffect(); - return; - } - - PropertyStorage::instance().load_all_propertyes(); - - _effect->on_clear(); - _effect->on_init(); - - auto ev = ModChangedEvent({ - EventType::ModChanged, - MemoryManager::instance().load_mod_id(), - MemoryManager::instance().get_cur_mod_num() - }); - Observable::notify(&ev); -} - -void EffectsManager::setEffect(const uint8_t num) { - if (!MemoryManager::instance().set_mod(num)) { - out("ERROR: Effect number out of range\n"); - setErrorEffect(); - return; - } - - syncEffect(); -} - -void EffectsManager::nextEffect() { - MemoryManager::instance().next_mod(); - syncEffect(); -} - -void EffectsManager::prevEffect() { - MemoryManager::instance().prev_mod(); - syncEffect(); -} - - -void EffectsManager::onTick() { - if (_reqEffect.type != Request::Type::None && (_effect->is_end() || _reqEffect.hardReset)) { - if (_reqEffect.type == Request::Type::Next) { - nextEffect(); - } else if (_reqEffect.type == Request::Type::Previous) { - prevEffect(); - } else if (_reqEffect.type == Request::Type::Set) { - setEffect(_reqEffect.num); - } - _reqEffect.type = Request::Type::None; - } - - if (!_effect) - return; - - if (_fpsManager.needUpdate()) { - _effect->on_update(); - _effect->on_render(); - FastLED.show(); - } -} - -float EffectsManager::getCurFPS() { - return _fpsManager.getRealFPS(); -} - -void EffectsManager::handleEvent(Event *event) { - if (event->type == EventType::ChangeMode) { - ChangeModEvent *ev = static_cast(event); - if (ev->type == ChangeModEvent::Type::Next) { - _reqEffect = { Request::Type::Next, 0, ev->hardReset }; - } else if (ev->type == ChangeModEvent::Type::Previous) { - _reqEffect = { Request::Type::Previous, 0, ev->hardReset }; - } else if (ev->type == ChangeModEvent::Type::Set) { - _reqEffect = { Request::Type::Set, ev->id, ev->hardReset }; - } - } -}*/ \ No newline at end of file diff --git a/src/effect_list/effect_manger.h b/src/effect_list/effect_manger.h deleted file mode 100644 index 7ad7b88..0000000 --- a/src/effect_list/effect_manger.h +++ /dev/null @@ -1,48 +0,0 @@ -/*#pragma once - -#include -#include "events/observer.h" -#include "fps_manager.h" - -class Effect; -class FPSManager; - -class EffectsManager : public IObserver -{ - struct Request { - enum class Type { - None, - Set, - Next, - Previous - }; - Type type; - uint8_t num; - bool hardReset; - }; - void clearEffect(); - void syncEffect(); - -public: - EffectsManager(); - ~EffectsManager(); - void init(); - void setErrorEffect(); - - uint8_t getEffectId() const; - uint8_t getEffectNum() const; - std::string getEffectName() const; - - void setEffect(const uint8_t num); - void nextEffect(); - void prevEffect(); - - void onTick(); - float getCurFPS(); - void handleEvent(Event *event) override; -private: - Request _reqEffect = { Request::Type::None, 0, false }; - Effect* _effect = nullptr; - FpsManager _fpsManager; -}; -*/ \ No newline at end of file diff --git a/src/effect_list/effectslist.cpp b/src/effect_list/effectslist.cpp deleted file mode 100644 index 062ad50..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 b3b4b39..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 100% rename from src/effect_list/effect.h rename to src/effects/effect.h 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 98% rename from src/effect_list/effects/crazy_bees.h rename to src/effects/effects_impl/crazy_bees.h index 5102708..04852ad 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; 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 95% rename from src/effect_list/effects/dribs_all_side.h rename to src/effects/effects_impl/dribs_all_side.h index a661e48..8dd20a2 100644 --- a/src/effect_list/effects/dribs_all_side.h +++ b/src/effects/effects_impl/dribs_all_side.h @@ -1,7 +1,7 @@ #pragma once -#include "effect_list/effect.h" -#include "effect_list/effects/snake/snake_lib.h" +#include "effects/effect.h" +#include "effects/effects_impl/snake/snake_lib.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.cpp b/src/effects/effects_impl/ny2020.cpp similarity index 99% rename from src/effect_list/effects/ny2020.cpp rename to src/effects/effects_impl/ny2020.cpp index a77c962..982da4b 100644 --- a/src/effect_list/effects/ny2020.cpp +++ b/src/effects/effects_impl/ny2020.cpp @@ -1,6 +1,6 @@ #include "ny2020.h" -#include "effect_list/effect.h" +#include "effects/effect.h" #define R 0xff0000 #define DR 0x400000 diff --git a/src/effect_list/effects/ny2020.h b/src/effects/effects_impl/ny2020.h similarity index 89% rename from src/effect_list/effects/ny2020.h rename to src/effects/effects_impl/ny2020.h index 5dc7fcc..a524693 100644 --- a/src/effect_list/effects/ny2020.h +++ b/src/effects/effects_impl/ny2020.h @@ -1,6 +1,6 @@ #pragma once -#include "effect_list/effect.h" +#include "effects/effect.h" #define NY_SPRITE_W 10 #define NY_SPRITE_H 10 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/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 94% rename from src/effect_list/effects/rain.h rename to src/effects/effects_impl/rain.h index 62da4ce..d2d5469 100644 --- a/src/effect_list/effects/rain.h +++ b/src/effects/effects_impl/rain.h @@ -1,6 +1,6 @@ #pragma once -#include "effect_list/effect.h" +#include "effects/effect.h" class Rain : public Effect { uint8_t step{2}; 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 100% rename from src/effect_list/effects/snake/a_star_ai.h rename to src/effects/effects_impl/snake/a_star_ai.h 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 99% rename from src/effect_list/effects/snake/snake.h rename to src/effects/effects_impl/snake/snake.h index 0fd1863..06121ed 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" diff --git a/src/effect_list/effects/snake/snake_lib.h b/src/effects/effects_impl/snake/snake_lib.h similarity index 100% rename from src/effect_list/effects/snake/snake_lib.h rename to src/effects/effects_impl/snake/snake_lib.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/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 99% rename from src/effect_list/effects/text.h rename to src/effects/effects_impl/text.h index 8f68f78..bd5d8e5 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 { 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 100% rename from src/effect_list/erroreffect.h rename to src/effects/erroreffect.h diff --git a/src/properties/memory_manager.cpp b/src/properties/memory_manager.cpp deleted file mode 100644 index 16941f0..0000000 --- a/src/properties/memory_manager.cpp +++ /dev/null @@ -1,338 +0,0 @@ -/*#include "memory_manager.h" - -MemoryManager::MemoryManager(const char* filename) { - _file = FileHandler{}; - _file.openFile(filename); - _file.read(offsetof(DefaultData, cur_mod), _cur_mod_addr); -} - -uint16_t MemoryManager::get_first_mod_addr() { - uint16_t addr = 0; - _file.read(offsetof(DefaultData, first_mod_addr), addr); - return addr; -} - -bool MemoryManager::set_cur_mod(uint16_t val) { - if (val == 0 || val >= sizeof(DefaultData)) { - _cur_mod_addr = val; - _file.write(offsetof(DefaultData, cur_mod), val); - return true; - } else { - out("MemoryManager: Error set_cur_mod: out of memory\n"); - return false; - } -} - -void MemoryManager::mod_memory_shift(uint16_t src_addr, uint16_t dst_addr) { - Mod mod; - _file.read(src_addr, mod); - _file.write(dst_addr, mod); - - for (uint16_t i = 0; i < mod.size; ++i) { - uint8_t tmp; - _file.read(src_addr + sizeof(Mod) + i, tmp); - _file.write(dst_addr + sizeof(Mod) + i, tmp); - } - - if (_cur_mod_addr == src_addr) { - set_cur_mod(dst_addr); - } -} - -void MemoryManager::rebalance_mod_list() { - uint16_t cur_addr = get_first_mod_addr(); - uint16_t offset = 0; - Mod mod; - - if (!cur_addr) { - return; - } - - if (cur_addr != sizeof(DefaultData)) { - mod_memory_shift(cur_addr, sizeof(DefaultData)); - cur_addr = sizeof(DefaultData); - _file.write(offsetof(DefaultData, first_mod_addr), cur_addr); - } - _file.read(cur_addr, mod); - - while (mod.next_addr) { - offset = mod.next_addr - cur_addr - mod.size - sizeof(Mod); - if (offset) { - uint16_t cur_offset = mod.next_addr - offset; - mod_memory_shift(mod.next_addr, cur_offset); - mod.next_addr = cur_offset; - } - _file.write(cur_addr, mod.next_addr); - cur_addr = mod.next_addr; - _file.read(cur_addr, mod); - } -} - -bool MemoryManager::add_mod_with_rebalace(uint8_t mod_id, uint16_t mod_size, bool need_rebalace) { - uint16_t addr = _cur_mod_addr; - uint16_t addr_tmp = addr; - uint32_t new_addr; - uint16_t size; - - if (addr) { - while (addr_tmp) { - _file.read(addr, addr_tmp); - addr = addr_tmp? addr_tmp : addr; - } - _file.read(addr + offsetof(Mod, size), size); - new_addr = (uint32_t)addr + size + sizeof(Mod); - } else { - addr = offsetof(DefaultData, first_mod_addr); - new_addr = sizeof(DefaultData); - } - - if (new_addr + sizeof(Mod) + mod_size < MAX_FILE_SIZE) { - _file.write(addr, static_cast(new_addr)); - _file.write(new_addr, Mod{0, mod_id, mod_size}); - set_cur_mod(new_addr); - return true; - } else { - if (need_rebalace) { - rebalance_mod_list(); - return add_mod_with_rebalace(mod_id, mod_size, false); - } else { - out("MemoryManager: Error add_new_mod: out of memory\n"); - return false; - } - } -} - -uint16_t MemoryManager::get_prev_link_addr(uint16_t addr) { - if (!addr || !_cur_mod_addr) { - return 0; - } - - if (addr < sizeof(DefaultData)) { - out("MemoryManager: Error get_prev_link_addr\n"); - return 0; - } - - uint16_t prev_addr = offsetof(DefaultData, first_mod_addr); - uint16_t cur_addr = get_first_mod_addr(); - while (cur_addr != addr && cur_addr) { - prev_addr = cur_addr; - _file.read(cur_addr, cur_addr); - } - - if (cur_addr == addr) { - return prev_addr; - } else { - out("MemoryManager: Error get_prev_link_addr: mod not found\n"); - } - return 0; -} - -uint16_t MemoryManager::get_mod_addr_by_num(uint8_t num, uint16_t &prev_addr) { - prev_addr = offsetof(DefaultData, first_mod_addr); - uint16_t cur_addr = get_first_mod_addr(); - uint16_t mod_num = 1; - - if (cur_addr) { - for (; mod_num != num && cur_addr; ++mod_num) { - prev_addr = cur_addr; - _file.read(cur_addr, cur_addr); - } - if (mod_num == num) { - return cur_addr; - } else { - out("MemoryManager: Error get_mod_addr_by_num: mod not found\n"); - } - } - return 0; -} - -uint16_t MemoryManager::get_mod_addr_by_num(uint8_t num) { - uint16_t prev_addr; - return get_mod_addr_by_num(num, prev_addr); -} - -bool MemoryManager::remove_mode_by_addr(uint16_t addr, uint16_t prev_addr) { - uint16_t next_addr; - - if (!_cur_mod_addr || !addr || !prev_addr) { - return false; - } - - _file.read(addr, next_addr); - _file.write(prev_addr, next_addr); - - if (!next_addr && prev_addr == offsetof(DefaultData, first_mod_addr)) { - set_cur_mod(0); - } else if (_cur_mod_addr == addr) { - if (!next_addr) { - set_cur_mod(prev_addr); - } else { - set_cur_mod(next_addr); - } - } - - return true; -} - -bool MemoryManager::remove_mode_by_addr(uint16_t addr) { - return remove_mode_by_addr(addr, get_prev_link_addr(addr)); -} - -MemoryManager& MemoryManager::instance() { - static MemoryManager instance(MM_FILE_NAME); - return instance; -} - -void MemoryManager::clear_memory() { - _file.clear(); -} - -bool MemoryManager::add_mod(uint8_t id, uint16_t size) { - return add_mod_with_rebalace(id, size, true); -} - -bool MemoryManager::remove_mod() { - return remove_mode_by_addr(_cur_mod_addr); -} - -bool MemoryManager::remove_mod(uint8_t num) { - uint16_t prev_addr; - uint16_t addr = get_mod_addr_by_num(num, prev_addr); - return remove_mode_by_addr(addr, prev_addr); -} - -void MemoryManager::remove_all_mods() { - set_cur_mod(0); - _file.write(offsetof(DefaultData, first_mod_addr), 0); -} - -void MemoryManager::next_mod() { - if (!_cur_mod_addr) { - return; - } - _file.read(_cur_mod_addr, _cur_mod_addr); - - if (!_cur_mod_addr) { - _cur_mod_addr = get_first_mod_addr(); - } - set_cur_mod(_cur_mod_addr); -} - -void MemoryManager::prev_mod() { - if (!_cur_mod_addr) { - return; - } - _cur_mod_addr = get_prev_link_addr(_cur_mod_addr); - if (_cur_mod_addr == offsetof(DefaultData, first_mod_addr)) { - uint16_t addr = _cur_mod_addr; - while (addr) { - _cur_mod_addr = addr; - _file.read(addr, addr); - } - } - set_cur_mod(_cur_mod_addr); -} - -bool MemoryManager::set_mod(uint8_t num) { - if (!_cur_mod_addr || num == 0) { - return false; - } - uint16_t mod_addr = get_first_mod_addr(); - for(int i = 1; i < num && mod_addr; ++i) { - _file.read(mod_addr, mod_addr); - } - if (mod_addr) { - return set_cur_mod(mod_addr); - } else { - return false; - } -} - -uint8_t MemoryManager::get_cur_mod_num() { - if (!_cur_mod_addr) { - return 0; - } - uint8_t num = 1; - uint16_t addr = get_first_mod_addr(); - while (addr != _cur_mod_addr && addr) { - _file.read(addr, addr); - num++; - } - return num; -} - -uint8_t MemoryManager::get_mod_amount() { - uint16_t addr = get_first_mod_addr(); - uint8_t mods_amt = 0; - - while (addr) { - _file.read(addr, addr); - mods_amt++; - } - return mods_amt; -} - -std::unique_ptr MemoryManager::get_mod_list() { - uint16_t faddr = get_first_mod_addr(); - uint16_t addr = faddr; - uint8_t mods_amt = 0; - - while (addr) { - _file.read(addr, addr); - mods_amt++; - } - std::unique_ptr ids(new uint8_t[mods_amt]); - uint8_t cur_mod = 0; - while (faddr) { - _file.read(faddr + offsetof(Mod, id), ids[cur_mod]); - cur_mod++; - _file.read(faddr, faddr); - } - return ids; -} - -uint8_t MemoryManager::load_mod_id() { - uint8_t ret_val = 0; - if (_cur_mod_addr) { - _file.read(_cur_mod_addr + offsetof(Mod, id), ret_val); - } - return ret_val; -} - -uint16_t MemoryManager::load_mod_size() { - uint16_t ret_val = 0; - if (_cur_mod_addr) { - _file.read(_cur_mod_addr + offsetof(Mod, size), ret_val); - } - return ret_val; -} - -template -bool MemoryManager::load_mod_var(uint8_t offset, T &val) { - if (!_cur_mod_addr) { - return false; - } - uint16_t size; - _file.read(_cur_mod_addr + offsetof(Mod, size), size); - if (offset + sizeof(T) <= size) { - _file.read(_cur_mod_addr + sizeof(Mod) + offset, val); - return true; - } else { - out("MemoryManager: Error load_mod_var: offset out of range\n"); - } - return false; -} - -template -bool MemoryManager::save_mod_var(uint16_t offset, T &val) { - uint16_t size; - _file.read(_cur_mod_addr + offsetof(Mod, size), size); - if (offset + sizeof(T) <= size) { - _file.write(_cur_mod_addr + sizeof(Mod) + offset, val); - return true; - } else { - out("MemoryManager: Error save_mod_var: offset out of range\n"); - } - return false; -} -*/ \ No newline at end of file diff --git a/src/properties/memory_manager.h b/src/properties/memory_manager.h deleted file mode 100644 index 0a6baf9..0000000 --- a/src/properties/memory_manager.h +++ /dev/null @@ -1,113 +0,0 @@ -/*#pragma once - -#include "libs/file_handler.h" -#include - -#define MAX_MODS_SIZE 256 -#define MM_FILE_NAME "mods.dat" - -class MemoryManager { - struct Mod { - uint16_t next_addr; - uint8_t id; - uint16_t size; - }; - - struct DefaultData { - uint16_t cur_mod; - uint16_t first_mod_addr; - }; - - MemoryManager(const char* filename); - MemoryManager(const MemoryManager&) = delete; - MemoryManager& operator= (const MemoryManager&) = delete; - - // возвращает 0 если режимов нет - uint16_t get_first_mod_addr(); - bool set_cur_mod(uint16_t val); - void mod_memory_shift(uint16_t src_addr, uint16_t dst_addr); - - // ребалансировка памяти, нужна когда закончилось место в конце файла и были - // удалены какие то режимы. Т.е. в памяти есть пустые не используемы участки. - // Все данные смещаются так, чтобы закрыть неиспользуемые пробелы памяти - void rebalance_mod_list(); - bool add_mod_with_rebalace(uint8_t mod_id, uint16_t mod_size, bool need_rebalace); - uint16_t get_prev_link_addr(uint16_t addr); - uint16_t get_mod_addr_by_num(uint8_t num, uint16_t &prev_addr); - uint16_t get_mod_addr_by_num(uint8_t num); - bool remove_mode_by_addr(uint16_t addr, uint16_t prev_addr); - bool remove_mode_by_addr(uint16_t addr); - -public: - ~MemoryManager() = default; - static MemoryManager& instance(); - - // Занулить всю память - void clear_memory(); - - // ----------------- Функции изменения списка модов ----------------- - - // Добавление нового режима в конец списка с заданным id и size. - // После добавления режима, указатель на текущий режим установится - // на только что добавленный. - // Все поля аргументов режима могут содержать мусор, поэтому нужно их - // инициализировать. Значения аргументов режима устанавливается - // функцией save_mod_var - bool add_mod(uint8_t id, uint16_t size); - - // Удаление текущего выбранного режима - // При удалении в качестве текущего выбирается следующий режим, - // если он существует, иначе предыдущий. Если режимов не осталось, - // текущей режим будет установлен в 0. - bool remove_mod(); - - // Удаление режима в определённой позиции. - bool remove_mod(uint8_t num); - - // Уданение всех режимов из памяти (по сути зануление указателя на первый режим) - void remove_all_mods(); - - // --------------- Функции выбора текущего режима ---------------- - - // Сменить указатель текущего режима на следующий. - // Если текущий режим последний, то выбирается первый. - void next_mod(); - - // Сменить указатель текущего режима на предыдущий. - // Если текущий режим первый, то выбирается последний. - void prev_mod(); - - // Установить указатель текущего режима в конкретную позицию. - // начальный режим имеет индекс 1 - bool set_mod(uint8_t num); - - // получить номер текущего режима в памяти - // начальный режим имеет индекс 1 - uint8_t get_cur_mod_num(); - - // получить кол-во режимов из памяти - uint8_t get_mod_amount(); - - // получить id всех режимов из памяти - // возвращает область памяти со списком режимов - std::unique_ptr get_mod_list(); - - // --------------- Функции работы с текущим режимом ---------------- - - uint8_t load_mod_id(); - uint16_t load_mod_size(); - - template - bool load_mod_var(uint8_t offset, T &val); - - // Сохранение значений текущего режима в память. - // offset считается относительно начала переменных самого режима. - // offset = 0 это первый аргумент режима - // offset = sizeof(first_var) это второй аргумент и т.д. - template - bool save_mod_var(uint16_t offset, T &val); - -private: - uint16_t _cur_mod_addr; // инициализируется при создании. 0, если режимов нет. - FileHandler _file; -};*/ \ No newline at end of file diff --git a/src/properties/property.h b/src/properties/property.h deleted file mode 100644 index a2794fb..0000000 --- a/src/properties/property.h +++ /dev/null @@ -1,126 +0,0 @@ -/*#pragma once - -#include "property_storage.h" -#include "memory_manager.h" -#include "stdint.h" - -// костыль для получения имени типа без использования RTTI -namespace type_names { - template - inline const char* get_type_name() { return "unknown"; } - - #define DEFINE_TYPE_NAME(type) \ - template<> \ - inline const char* get_type_name() { return #type; } - - DEFINE_TYPE_NAME(uint8_t); - DEFINE_TYPE_NAME(uint16_t); - DEFINE_TYPE_NAME(uint32_t); - DEFINE_TYPE_NAME(uint64_t); - DEFINE_TYPE_NAME(int8_t); - DEFINE_TYPE_NAME(int16_t); - DEFINE_TYPE_NAME(int32_t); - DEFINE_TYPE_NAME(int64_t); - DEFINE_TYPE_NAME(float); - DEFINE_TYPE_NAME(double); - DEFINE_TYPE_NAME(bool); -} - -class ISaveable { - uint16_t offset = -1; -public: - virtual ~ISaveable() = default; - virtual void save() = 0; - virtual void load() = 0; - virtual void set_offset(uint16_t val) { - offset = val; - } - virtual uint16_t get_offset() { - return offset; - } - virtual size_t size() const = 0; -}; - -class IProperty : public ISaveable { -public: - virtual ~IProperty(); - virtual const char* type_name() const = 0; - virtual void save() override = 0; - virtual void load() override = 0; - virtual size_t size() const override = 0; -}; - -template -class Property : public IProperty { - Property(const Property&) = delete; - Property(Property&&) = delete; - Property& operator=(const Property&) = delete; - Property& operator=(Property&&) = delete; - -protected: - virtual void set_without_save(T new_val) { - if ((_min_value == _max_value) || // не определены занчения min max - (new_val >= _min_value && new_val <= _max_value)) // новое значение попадает в диапозон - { - _value = new_val; - } - } - -public: - Property(T value, T min, T max): _value(), _min_value(min), _max_value(max) { - set_without_save(value); - uint16_t offset = PropertyStorage::instance().add_property(this); - set_offset(offset); - } - Property(T min, T max) : Property({}, min, max) { } - Property(T val) : Property(val, {}, {}) { } - Property() : Property({}, {}, {}) { } - - virtual ~Property() { - PropertyStorage::instance().clear(); - } - - T get() { - return _value; - } - - void set(T new_val) { - if (_value != new_val) { - set_without_save(new_val); - save(); - } - } - - void save() override { - MemoryManager::instance().save_mod_var(get_offset(), _value); - } - - void load() override { - MemoryManager::instance().load_mod_var(get_offset(), _value); - } - - size_t size() const override { - return sizeof(T); - } - - const char* type_name() const override { - return type_names::get_type_name(); - } - - bool has_min_max() const { - return _min_value != _max_value; - } - - T get_min() const { - return _min_value; - } - - T get_max() const { - return _max_value; - } - -private: - T _value; - T _min_value; - T _max_value; -};*/ \ No newline at end of file diff --git a/src/properties/property_storage.cpp b/src/properties/property_storage.cpp deleted file mode 100644 index bd35bc7..0000000 --- a/src/properties/property_storage.cpp +++ /dev/null @@ -1,40 +0,0 @@ -/*#include "property_storage.h" -#include "property.h" - -uint16_t PropertyStorage::add_property(IProperty *prop) { - uint16_t offset = 0; - if (_props.size()) { - offset = size(); - } - - _props.push_back(prop); - - return offset; -} - -std::vector PropertyStorage::get_props() const { - return _props; -} - -void PropertyStorage::load_all_propertyes() { - for (auto &it: _props) { - it->load(); - } -} - -void PropertyStorage::save_all_propertyes() { - for (auto &it: _props) { - it->save(); - } -} - -void PropertyStorage::clear() { - if (_props.size()) { - _props.clear(); - } -} - -uint16_t PropertyStorage::size() { - IProperty* prop = _props.back(); - return prop->get_offset() + prop->size(); -}*/ \ No newline at end of file diff --git a/src/properties/property_storage.h b/src/properties/property_storage.h deleted file mode 100644 index 2abd966..0000000 --- a/src/properties/property_storage.h +++ /dev/null @@ -1,30 +0,0 @@ -/*#pragma once - -#include "vector" -#include "stdint.h" - -class IProperty; - -class PropertyStorage { - // singlton property - // Конструктор копирования и оператор присваивания копированием недоступны - PropertyStorage() = default; - ~PropertyStorage() = default; - PropertyStorage(const PropertyStorage& ) = delete; - PropertyStorage& operator=(const PropertyStorage& ) = delete; -public: - static PropertyStorage& instance() { - static PropertyStorage instance; - return instance; - } - - uint16_t add_property(IProperty *prop); - std::vector get_props() const; - - void load_all_propertyes(); - void save_all_propertyes(); - void clear(); - uint16_t size(); -private: - std::vector _props; -};*/ From 7b7be84e5d408648ba638a3dc606881527c28209 Mon Sep 17 00:00:00 2001 From: npo6ka Date: Sat, 1 Nov 2025 21:04:32 +0200 Subject: [PATCH 64/75] Add is_end effect check to snake --- src/effects/effect.h | 4 ++-- src/effects/effects_impl/snake/snake.h | 11 +++++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/effects/effect.h b/src/effects/effect.h index 2dbd2be..c963579 100644 --- a/src/effects/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/effects/effects_impl/snake/snake.h b/src/effects/effects_impl/snake/snake.h index 06121ed..a810bf7 100644 --- a/src/effects/effects_impl/snake/snake.h +++ b/src/effects/effects_impl/snake/snake.h @@ -14,6 +14,7 @@ class Snake : public Effect bool apple_flag, end_game; uint8_t aiType = 3; SnakeAI *ai; + uint64_t startTime; uint8_t tick, step = 3; @@ -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 секунд, то режим не переключится + } }; From 459ca3b033fba736f43e7bf25b5a7386c25a6140 Mon Sep 17 00:00:00 2001 From: npo6ka Date: Sat, 1 Nov 2025 21:10:50 +0200 Subject: [PATCH 65/75] Rename Lsf file handler and add esp32 handler --- src/core/effect/storage/FileEffectStorage.h | 2 +- .../{LsfFileHandler.h => Lsf12eFileHandler.h} | 16 +-- src/core/file/Lsf32FileHandlery.h | 105 ++++++++++++++++++ src/myapplication.cpp | 4 +- 4 files changed, 116 insertions(+), 11 deletions(-) rename src/core/file/{LsfFileHandler.h => Lsf12eFileHandler.h} (84%) create mode 100644 src/core/file/Lsf32FileHandlery.h diff --git a/src/core/effect/storage/FileEffectStorage.h b/src/core/effect/storage/FileEffectStorage.h index 97fe498..0c057b3 100644 --- a/src/core/effect/storage/FileEffectStorage.h +++ b/src/core/effect/storage/FileEffectStorage.h @@ -188,7 +188,7 @@ class FileEffectStorage : public IEffectStorage }; // Test code: -// LsfFileHandler fileHandler; +// Lsf12eFileHandler fileHandler; // //LittleFS.remove("/mods.txt"); // listFiles(); diff --git a/src/core/file/LsfFileHandler.h b/src/core/file/Lsf12eFileHandler.h similarity index 84% rename from src/core/file/LsfFileHandler.h rename to src/core/file/Lsf12eFileHandler.h index 15445b6..e018cfc 100644 --- a/src/core/file/LsfFileHandler.h +++ b/src/core/file/Lsf12eFileHandler.h @@ -5,26 +5,26 @@ #include "LittleFS.h" -class LsfFileHandler : public IFileHandler { +class Lsf12eFileHandler : public IFileHandler { File _file; - LsfFileHandler(const LsfFileHandler&) = delete; - LsfFileHandler& operator= (const LsfFileHandler&) = delete; - LsfFileHandler(LsfFileHandler&& other) = delete; - LsfFileHandler& operator= (LsfFileHandler&& other) = delete; + Lsf12eFileHandler(const Lsf12eFileHandler&) = delete; + Lsf12eFileHandler& operator= (const Lsf12eFileHandler&) = delete; + Lsf12eFileHandler(Lsf12eFileHandler&& other) = delete; + Lsf12eFileHandler& operator= (Lsf12eFileHandler&& other) = delete; public: - LsfFileHandler() { + Lsf12eFileHandler() { static bool isMounted = false; if (!isMounted && LittleFS.begin()) { isMounted = true; } } - LsfFileHandler(const char* path) : LsfFileHandler() { + Lsf12eFileHandler(const char* path) : Lsf12eFileHandler() { open(path); } - virtual ~LsfFileHandler() { + virtual ~Lsf12eFileHandler() { close(); } diff --git a/src/core/file/Lsf32FileHandlery.h b/src/core/file/Lsf32FileHandlery.h new file mode 100644 index 0000000..ac436ab --- /dev/null +++ b/src/core/file/Lsf32FileHandlery.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/myapplication.cpp b/src/myapplication.cpp index 156f2f2..ff1368a 100644 --- a/src/myapplication.cpp +++ b/src/myapplication.cpp @@ -4,7 +4,7 @@ #include "libs/StdFeatures.h" #if SAVE_TO_EEPROM -# include "core/file/LsfFileHandler.h" +# include "core/file/Lsf12eFileHandler.h" # include "core/effect/storage/FileEffectStorage.h" #else # include "core/effect/storage/StaticEffectStorage.h" @@ -47,7 +47,7 @@ void MyApplication::onInit() { _relay.onInit(); #endif #if SAVE_TO_EEPROM - _effectStorage = std::make_unique(std::make_unique(SAVE_TO_EEPROM_FILE)); + _effectStorage = std::make_unique(std::make_unique(SAVE_TO_EEPROM_FILE)); #else _effectStorage = std::make_unique(); #endif From 738481a1740694111441a779137b5409054b02fa Mon Sep 17 00:00:00 2001 From: npo6ka Date: Sat, 1 Nov 2025 22:07:51 +0200 Subject: [PATCH 66/75] event refactoring --- src/controls/automode.cpp | 7 +++--- src/controls/automode.h | 2 +- src/controls/button.cpp | 10 +++----- src/controls/ir.cpp | 12 +++------ src/core/effect/EffectManager.cpp | 7 +++--- src/core/effect/EffectManager.h | 2 +- src/events/observer.cpp | 6 +---- src/events/observer.h | 42 +++++++++++++++++-------------- src/myapplication.cpp | 6 ++--- src/myapplication.h | 2 +- 10 files changed, 43 insertions(+), 53 deletions(-) diff --git a/src/controls/automode.cpp b/src/controls/automode.cpp index 41bdeab..2ad395d 100644 --- a/src/controls/automode.cpp +++ b/src/controls/automode.cpp @@ -18,9 +18,8 @@ AutoChangeMode::~AutoChangeMode() { void AutoChangeMode::onTick() { if (isEnable()) { if (millis() - _savedTime > _delay) { - ChangeModeEvent ev(EventType::ChangeMode, false, ChangeModeEventRequest::Type::Next); - Observable::notify(&ev); - out("AutoControl: next mode\n"); + 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 03244d6..3f45bc1 100644 --- a/src/controls/button.cpp +++ b/src/controls/button.cpp @@ -12,19 +12,15 @@ void Button::onTick() { uint8_t clickCount = touch.hasClicks() ? touch.getClicks() : 0U; switch (clickCount) { case 1U: { - ChangeModeEvent ev(EventType::ChangeMode, true, ChangeModeEventRequest::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 825926a..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: { // << - ChangeModeEvent ev(EventType::ChangeMode, true, ChangeModeEventRequest::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: { // >> - ChangeModeEvent ev(EventType::ChangeMode, true, ChangeModeEventRequest::Type::Next); - Observable::notify(&ev); + Observable::notify(EventType::ChangeMode, true, ChangeModeEventRequest::Type::Next); break; } } diff --git a/src/core/effect/EffectManager.cpp b/src/core/effect/EffectManager.cpp index a168151..12fab69 100644 --- a/src/core/effect/EffectManager.cpp +++ b/src/core/effect/EffectManager.cpp @@ -52,8 +52,7 @@ void EffectManager::updateEffect() { _currentEffect->on_init(); _fpsManager.setTargetFPS(_currentEffect->get_fps()); - ModChangedEvent modChangedEvent(EventType::ModChanged, effectInfo.id, _storage.getCurrentIndex()); - Observable::notify(&modChangedEvent); + Observable::notify(EventType::ModChanged, effectInfo.id, _storage.getCurrentIndex()); } void EffectManager::setEffect(uint32_t index) { @@ -67,9 +66,9 @@ float EffectManager::getCurrentFPS() const { return _fpsManager.getRealFPS(); } -void EffectManager::handleEvent(Event* event) { +void EffectManager::handleEvent(const Event* event) { if (event->type == EventType::ChangeMode) { - ChangeModeEvent* changeEvent = static_cast(event); + 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 index b479cb4..9139524 100644 --- a/src/core/effect/EffectManager.h +++ b/src/core/effect/EffectManager.h @@ -27,7 +27,7 @@ class EffectManager : public IObserver float getCurrentFPS() const; // Обработка событий (реализация IObserver) - void handleEvent(Event* event) override; + void handleEvent(const Event* event) override; private: diff --git a/src/events/observer.cpp b/src/events/observer.cpp index 68c93cb..13c9425 100644 --- a/src/events/observer.cpp +++ b/src/events/observer.cpp @@ -85,7 +85,7 @@ void Observable::instanceRemoveObserver(EventType etype, IObserver *observer) { } } -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/myapplication.cpp b/src/myapplication.cpp index ff1368a..13ae569 100644 --- a/src/myapplication.cpp +++ b/src/myapplication.cpp @@ -87,14 +87,14 @@ 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) { - ChangeBoolEvent *ev = static_cast(event); + const ChangeBoolEvent *ev = static_cast(event); _autoMod.setIsEnable(ev->new_val); } else if (event->type == EventType::ChangeMode) { // включить питание при попытках сменить режима if (!_isPowerOn) { diff --git a/src/myapplication.h b/src/myapplication.h index a80e74a..02d85f0 100644 --- a/src/myapplication.h +++ b/src/myapplication.h @@ -46,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 From c0673fabde77292dcd66a22fc4d40c5994ac971b Mon Sep 17 00:00:00 2001 From: npo6ka Date: Sat, 1 Nov 2025 22:29:55 +0200 Subject: [PATCH 67/75] remove usless file_handler and refactoring debug message --- src/effects/effects_impl/snake/a_star_ai.h | 4 +- src/effects/effects_impl/snake/snake.h | 12 +-- src/effects/effects_impl/text.h | 4 +- src/effects/erroreffect.h | 2 +- src/events/observer.cpp | 6 +- src/libs/file_handler.h | 96 ---------------------- src/libs/queue.h | 4 +- 7 files changed, 16 insertions(+), 112 deletions(-) delete mode 100644 src/libs/file_handler.h diff --git a/src/effects/effects_impl/snake/a_star_ai.h b/src/effects/effects_impl/snake/a_star_ai.h index 9e1dbfb..e725c5b 100644 --- a/src/effects/effects_impl/snake/a_star_ai.h +++ b/src/effects/effects_impl/snake/a_star_ai.h @@ -80,7 +80,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]); @@ -91,7 +91,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/effects/effects_impl/snake/snake.h b/src/effects/effects_impl/snake/snake.h index a810bf7..3af0171 100644 --- a/src/effects/effects_impl/snake/snake.h +++ b/src/effects/effects_impl/snake/snake.h @@ -20,12 +20,12 @@ class Snake : public Effect // Если нужно, можно вызвать этот метод. Выводит отладочную информацию в терминал 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"); } // Метод выполняется каждый тик. Тут вся логика @@ -87,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(); diff --git a/src/effects/effects_impl/text.h b/src/effects/effects_impl/text.h index bd5d8e5..a861afc 100644 --- a/src/effects/effects_impl/text.h +++ b/src/effects/effects_impl/text.h @@ -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/effects/erroreffect.h b/src/effects/erroreffect.h index 9a38f9d..425b222 100644 --- a/src/effects/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/observer.cpp b/src/events/observer.cpp index 13c9425..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,7 +81,7 @@ 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"); } } diff --git a/src/libs/file_handler.h b/src/libs/file_handler.h deleted file mode 100644 index 939a8dc..0000000 --- a/src/libs/file_handler.h +++ /dev/null @@ -1,96 +0,0 @@ -/*#pragma once - -#include "LittleFS.h" -#include -#include - -#define MAX_FILE_SIZE 65536 -#define STEP - -class FileHandler { - FILE *_fp = nullptr; - - FileHandler(const FileHandler&) = delete; - FileHandler& operator= (const FileHandler&) = delete; - -public: - FileHandler() = default; - - ~FileHandler() = default; - - FileHandler(FileHandler&& other){ - _fp = other._fp; - other._fp = nullptr; - }; - FileHandler& operator= (FileHandler&& other) { - _fp = other._fp; - other._fp = nullptr; - return *this; - } - - void openFile(const char *filename) { - if (strlen(filename) > 32) { - out("Error opening file: filename is too long\n"); - return; - } - if (!_fp) { - if ((_fp = fopen(filename, "w")) != nullptr) { - fclose(_fp); - } - _fp = fopen(filename, "r+"); - } - if (fseek(_fp, MAX_FILE_SIZE - 1, SEEK_SET) != 0) { - out("Error opening file: failed to seek to end of file\n"); - _fp = nullptr; - return; - } - fputc('\0', _fp); - } - - template - bool read(uint32_t addr, T &val) { - if (!_fp) { - out("Error reading ROM: memory file is NULL\n"); - return false; - } - if (addr + sizeof(T) < MAX_FILE_SIZE) { - fseek(_fp, addr, SEEK_SET); - return fread(&val, sizeof(T), 1, _fp) == 1; - } else { - out("Error reading ROM: position out of memory\n"); - } - - return false; - } - - template - bool write(uint32_t addr, const T val) { - if (!_fp) { - out("Error writing ROM: memory file is NULL\n"); - return false; - } - if (addr + sizeof(T) < MAX_FILE_SIZE) { - fseek(_fp, addr, SEEK_SET); - // printf("write to 0x%x, res: %d\n", addr, fwrite(&val, sizeof(T), 1, fp) == 1); - fwrite(&val, sizeof(T), 1, _fp); - return true; - } else { - out("Error writing ROM: position out of memory\n"); - } - - return false; - } - - void clear() { - if (!_fp) { - out("Error writing ROM: memory file is NULL\n"); - return; - } - - fseek(_fp, 0, SEEK_SET); - uint8_t val = 0; - for (uint32_t i = 0; i < MAX_FILE_SIZE; ++i) { - fwrite(&val, sizeof(val), 1, _fp); - } - } -};*/ \ No newline at end of file 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])); } From 4881747ad7f5380c9b255fe06344dd9377b5cd45 Mon Sep 17 00:00:00 2001 From: npo6ka Date: Sun, 2 Nov 2025 15:02:51 +0200 Subject: [PATCH 68/75] add autoselecting LSF include --- .../file/{Lsf32FileHandlery.h => Lsf32FileHandler.h} | 0 src/core/file/LsfFileHandler.h | 9 +++++++++ src/myapplication.cpp | 4 ++-- 3 files changed, 11 insertions(+), 2 deletions(-) rename src/core/file/{Lsf32FileHandlery.h => Lsf32FileHandler.h} (100%) create mode 100644 src/core/file/LsfFileHandler.h diff --git a/src/core/file/Lsf32FileHandlery.h b/src/core/file/Lsf32FileHandler.h similarity index 100% rename from src/core/file/Lsf32FileHandlery.h rename to src/core/file/Lsf32FileHandler.h diff --git a/src/core/file/LsfFileHandler.h b/src/core/file/LsfFileHandler.h new file mode 100644 index 0000000..450b7a8 --- /dev/null +++ b/src/core/file/LsfFileHandler.h @@ -0,0 +1,9 @@ +#pragma once + +#ifndef DESP12F +# include "Lsf12eFileHandler.h" + using LsfFileHandler = Lsf12eFileHandler; +#else +# include "Lsf32FileHandler.h" + using LsfFileHandler = Lsf32FileHandler; +#endif diff --git a/src/myapplication.cpp b/src/myapplication.cpp index 13ae569..bb37b4c 100644 --- a/src/myapplication.cpp +++ b/src/myapplication.cpp @@ -4,7 +4,7 @@ #include "libs/StdFeatures.h" #if SAVE_TO_EEPROM -# include "core/file/Lsf12eFileHandler.h" +# include "core/file/LsfFileHandler.h" # include "core/effect/storage/FileEffectStorage.h" #else # include "core/effect/storage/StaticEffectStorage.h" @@ -47,7 +47,7 @@ void MyApplication::onInit() { _relay.onInit(); #endif #if SAVE_TO_EEPROM - _effectStorage = std::make_unique(std::make_unique(SAVE_TO_EEPROM_FILE)); + _effectStorage = std::make_unique(std::make_unique(SAVE_TO_EEPROM_FILE)); #else _effectStorage = std::make_unique(); #endif From 1f615f85d694f2c0a3bb8b2c0f84b12c2ff790ee Mon Sep 17 00:00:00 2001 From: npo6ka Date: Sun, 15 Mar 2026 19:22:55 +0200 Subject: [PATCH 69/75] Added support esp32 filehandler --- platformio.ini | 1 - src/configs/matrix.h | 4 +- src/core/file/LsfFileHandler.h | 121 +++++++++++++++++++++++++++++++-- 3 files changed, 118 insertions(+), 8 deletions(-) diff --git a/platformio.ini b/platformio.ini index 6cd83d1..db1460c 100644 --- a/platformio.ini +++ b/platformio.ini @@ -39,6 +39,5 @@ board_build.filesystem = ${common.filesystem} lib_deps = ${common.lib_deps} plerup/EspSoftwareSerial@^8.2.0 - lorol/LittleFS_esp32@^1.0.6 monitor_speed = ${common.monitor_speed} upload_speed = ${common.upload_speed} diff --git a/src/configs/matrix.h b/src/configs/matrix.h index 51c59c0..8f7b1e1 100644 --- a/src/configs/matrix.h +++ b/src/configs/matrix.h @@ -48,14 +48,14 @@ // =============== Настройки Сохранения в память ================== -#define SAVE_TO_EEPROM true // сохранять настройки в EEPROM +#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/core/file/LsfFileHandler.h b/src/core/file/LsfFileHandler.h index 450b7a8..18da2f9 100644 --- a/src/core/file/LsfFileHandler.h +++ b/src/core/file/LsfFileHandler.h @@ -1,9 +1,120 @@ #pragma once -#ifndef DESP12F -# include "Lsf12eFileHandler.h" - using LsfFileHandler = Lsf12eFileHandler; +#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 -# include "Lsf32FileHandler.h" - using LsfFileHandler = Lsf32FileHandler; +# 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(); + } +}; From bc5a648d5911d58c19b454818573ac761abfa0cb Mon Sep 17 00:00:00 2001 From: npo6ka Date: Sun, 15 Mar 2026 19:24:23 +0200 Subject: [PATCH 70/75] add new effects and cursor rulse --- .cursor/rules/build.mdc | 52 ++++++++++ .cursor/rules/effects.mdc | 114 ++++++++++++++++++++++ README.md | 193 +++++++++++++++++++++++++++++++++++++- 3 files changed, 358 insertions(+), 1 deletion(-) create mode 100644 .cursor/rules/build.mdc create mode 100644 .cursor/rules/effects.mdc 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..b84b677 --- /dev/null +++ b/.cursor/rules/effects.mdc @@ -0,0 +1,114 @@ +--- +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.cpp`**. + +1. **Include** (в блоке с остальными эффектами): + ```cpp + #include "effects/effects_impl/имя_файла.h" + ``` + +2. **Новый id**: следующий свободный номер (сейчас последний — 31, следующий будет 32). + +3. **Switch**: добавить строку перед `default`: + ```cpp + EFFECT_CASE(32, "ИмяВСписке", ClassName); + ``` + - Первый аргумент — уникальный `effect_id`. + - Второй — строковое имя режима (отображается в списке). + - Третий — имя класса эффекта. + +4. **Счётчик**: увеличить константу: + ```cpp + #define EFFECT_COUNT 33 // было 32 + ``` + +## 4. Добавление в список по умолчанию + +Файл: **`src/core/effect/storage/StaticEffectStorage.h`**. + +В методе `createDefaultEffectsList()` добавить (с нужным id и комментарием): +```cpp +internalAddEffect(32); // ClassName или краткое имя +``` + +Так режим попадёт в стартовый список при сбросе настроек и будет доступен при переключении кнопкой/автомодом. + +## 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.cpp** (include + EFFECT_CASE + EFFECT_COUNT) и **StaticEffectStorage.h** (internalAddEffect). diff --git a/README.md b/README.md index cf85c25..beeefcc 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,193 @@ # LedMatrix -LedMatrix based on ws2812b/ws2811 leds and esp8266 microchip + +Прошивка для светодиодной матрицы на базе **WS2812B/WS2811** и микроконтроллеров **ESP8266** или **ESP32**. Поддержка множества визуальных режимов, управление кнопкой, ИК-пультом, автомод и сохранение настроек. + +## Возможности + +- **32 визуальных режима** (огонь, дождь, конфетти, змейка, спираль, кольца и др.) +- Переключение режимов кнопкой или ИК-пультом +- Автоматическая смена режима по таймеру (опционально) +- Сохранение текущего режима и списка в файл (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 | Text | Текст | +| 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` нет или он пустой — тогда автоматически создаётся **список по умолчанию** (такой же, как в `StaticEffectStorage`, см. ниже), и текущий режим ставится в 0. + +### Сброс списка к «заводскому» + +При использовании **FileEffectStorage** сброс к списку по умолчанию выполняется вызовом **`reset()`** у хранилища: список очищается и заполняется тем же набором эффектов, что и в `StaticEffectStorage::createDefaultEffectsList()`. Текущий индекс становится 0. В текущей прошивке вызов `reset()` нужно добавить в код (например, по длинному удержанию кнопки или отдельной команде), если такая функция нужна. + + +## Как менять список работающих режимов + +### Статический список (без сохранения в файл) + +При **`SAVE_TO_EEPROM = false`** список режимов задаётся единственным местом — методом **`createDefaultEffectsList()`** в файле **`src/core/effect/storage/StaticEffectStorage.h`**. + +- **Включить режим в список** — добавить строку: + `internalAddEffect(); // краткий комментарий` + Число `` — это номер эффекта из таблицы в README (1–31, 0 — зарезервирован под ошибку). +- **Убрать режим из списка** — закомментировать или удалить соответствующую строку `internalAddEffect(...)`. +- **Изменить порядок** — менять порядок вызовов `internalAddEffect(...)`; порядок вызовов = порядок переключения кнопкой. + +После изменения пересоберите прошивку и загрузите её. + +### Список по умолчанию при сохранении в файл + +При **`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 # Настройки матрицы и пинов +│ │ └── 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.cpp**: добавить `#include`, `EFFECT_CASE(id, "Name", ClassName)`, увеличить `EFFECT_COUNT`. +4. В **StaticEffectStorage.h**: в `createDefaultEffectsList()` добавить `internalAddEffect(id)`. + +Подробная инструкция — в [.cursor/rules/effects.mdc](.cursor/rules/effects.mdc). + +## Зависимости + +- **FastLED** — работа с лентой +- **GyverButton** — обработка кнопки +- **IRremote** — ИК-приём (опционально) +- **Vector** — список эффектов +- **EspSoftwareSerial** — только для ESP32 (если используется) + From 0915a8a8da104222db685eb7fa8d4946fa8db22c Mon Sep 17 00:00:00 2001 From: npo6ka Date: Sun, 15 Mar 2026 19:26:46 +0200 Subject: [PATCH 71/75] add new mods --- src/core/effect/EffectFactory.cpp | 6 +- src/core/effect/storage/StaticEffectStorage.h | 3 + src/effects/effects_impl/crazy_bees.h | 10 ++- src/effects/effects_impl/pulse_rings.h | 90 +++++++++++++++++++ src/effects/effects_impl/spiral.h | 50 +++++++++++ 5 files changed, 154 insertions(+), 5 deletions(-) create mode 100644 src/effects/effects_impl/pulse_rings.h create mode 100644 src/effects/effects_impl/spiral.h diff --git a/src/core/effect/EffectFactory.cpp b/src/core/effect/EffectFactory.cpp index 29045f8..09d3878 100644 --- a/src/core/effect/EffectFactory.cpp +++ b/src/core/effect/EffectFactory.cpp @@ -30,6 +30,8 @@ #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" @@ -51,7 +53,7 @@ struct EffectCreationInfo { }; #define EFFECT_CASE(id, name, type) case id: return EffectCreationInfo{name, effectCreator()}; -#define EFFECT_COUNT 29 // при добавлении нового эффекта, не забудь обновить EFFECT_COUNT +#define EFFECT_COUNT 32 // при добавлении нового эффекта, не забудь обновить EFFECT_COUNT static EffectCreationInfo getEffectInfo(uint32_t effect_id) { switch (effect_id) { @@ -85,6 +87,8 @@ static EffectCreationInfo getEffectInfo(uint32_t effect_id) { EFFECT_CASE(27, "RadialFire", RadialFire); EFFECT_CASE(28, "RadialPattern", RadialPattern); EFFECT_CASE(29, "CrazyBees", CrazyBees); + EFFECT_CASE(30, "Spiral", Spiral); + EFFECT_CASE(31, "PulseRings", PulseRings); // при добавлении нового эффекта, не забудь обновить EFFECT_COUNT default: diff --git a/src/core/effect/storage/StaticEffectStorage.h b/src/core/effect/storage/StaticEffectStorage.h index 86bc4b4..d8770d0 100644 --- a/src/core/effect/storage/StaticEffectStorage.h +++ b/src/core/effect/storage/StaticEffectStorage.h @@ -15,6 +15,7 @@ class StaticEffectStorage : public IEffectStorage public: StaticEffectStorage() { createDefaultEffectsList(); + _currentEffectIndex = _effects.size() - 1; } virtual ~StaticEffectStorage() {}; @@ -114,6 +115,8 @@ class StaticEffectStorage : public IEffectStorage internalAddEffect(27); // RadialFire internalAddEffect(28); // RadialPattern internalAddEffect(29); // CrazyBees + internalAddEffect(30); // Spiral + internalAddEffect(31); // PulseRings } void internalAddEffect(uint32_t effectId) { diff --git a/src/effects/effects_impl/crazy_bees.h b/src/effects/effects_impl/crazy_bees.h index 04852ad..729d97e 100644 --- a/src/effects/effects_impl/crazy_bees.h +++ b/src/effects/effects_impl/crazy_bees.h @@ -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/effects/effects_impl/pulse_rings.h b/src/effects/effects_impl/pulse_rings.h new file mode 100644 index 0000000..e5a5a9a --- /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 = 4; + 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 += 42; + 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)(200.0f * (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/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; + } +}; From 99aa7f0a63e201cd2e9cd80a32ebeb765b532bec Mon Sep 17 00:00:00 2001 From: npo6ka Date: Sun, 15 Mar 2026 20:40:41 +0200 Subject: [PATCH 72/75] Add modlist Reset when holding button 5 sec --- src/configs/matrix.h | 1 + src/controls/button.cpp | 5 +++++ src/core/effect/storage/FileEffectStorage.h | 1 + src/events/events.h | 1 + src/myapplication.cpp | 5 +++++ 5 files changed, 13 insertions(+) diff --git a/src/configs/matrix.h b/src/configs/matrix.h index 8f7b1e1..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 - выкл diff --git a/src/controls/button.cpp b/src/controls/button.cpp index 3f45bc1..16ffeda 100644 --- a/src/controls/button.cpp +++ b/src/controls/button.cpp @@ -5,10 +5,15 @@ 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: { diff --git a/src/core/effect/storage/FileEffectStorage.h b/src/core/effect/storage/FileEffectStorage.h index 0c057b3..6e8a94b 100644 --- a/src/core/effect/storage/FileEffectStorage.h +++ b/src/core/effect/storage/FileEffectStorage.h @@ -136,6 +136,7 @@ class FileEffectStorage : public IEffectStorage virtual void reset() override { clear(); createDefaultEffectsList(); + logInfo("Effects list resetted\n"); } virtual void clear() override { diff --git a/src/events/events.h b/src/events/events.h index 6c4ae74..d538609 100644 --- a/src/events/events.h +++ b/src/events/events.h @@ -12,6 +12,7 @@ enum class EventType { ChangeMode, // ChangeModeEventRequest ModChanged, // ModChangedEvent ChangeModVar, // ChangeModVarEvent + ResetModesList, // Event — сброс списка режимов на заводской EventAmount // Используется, чтобы знать сколько всего ивентов }; diff --git a/src/myapplication.cpp b/src/myapplication.cpp index bb37b4c..422b4ea 100644 --- a/src/myapplication.cpp +++ b/src/myapplication.cpp @@ -26,12 +26,14 @@ 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); } // лучше всё по максимому инициализировать тут @@ -100,5 +102,8 @@ void MyApplication::handleEvent(const Event *event) { if (!_isPowerOn) { setPowerState(true); } + } else if (event->type == EventType::ResetModesList) { + _effectStorage->reset(); + _effectManager->setEffect(_effectStorage->size() - 1); } } \ No newline at end of file From 2a96f2be03506635403c07736681ba37a5535fe5 Mon Sep 17 00:00:00 2001 From: npo6ka Date: Sun, 15 Mar 2026 21:40:32 +0200 Subject: [PATCH 73/75] Fix pulse_rings --- src/effects/effects_impl/pulse_rings.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/effects/effects_impl/pulse_rings.h b/src/effects/effects_impl/pulse_rings.h index e5a5a9a..1c9a003 100644 --- a/src/effects/effects_impl/pulse_rings.h +++ b/src/effects/effects_impl/pulse_rings.h @@ -5,7 +5,7 @@ // Расходящиеся от центра кольца, которые затухают class PulseRings : public Effect { - static constexpr uint8_t maxRings = 4; + static constexpr uint8_t maxRings = 3; static constexpr float expandSpeed = 0.35f; static constexpr float fadeStart = 3.0f; // радиус, с которого начинается затухание @@ -50,7 +50,7 @@ class PulseRings : public Effect _rings[i].active = true; _rings[i].radius = 0.5f; _rings[i].hue = _hueOffset; - _hueOffset += 42; + _hueOffset += 30; break; } } @@ -67,7 +67,7 @@ class PulseRings : public Effect if (diff < 0.8f) { uint8_t bright = 200; if (r > fadeStart) { - bright = (uint8_t)(200.0f * (maxR - r) / (maxR - fadeStart)); + bright = (uint8_t)(bright * (maxR - r) / (maxR - fadeStart)); } auto& pix = LedMatrix.at(x, y); CRGB add = CHSV(_rings[i].hue, 255, bright); From c6a630df3dc5e06d073e812c96251f2cfa409e89 Mon Sep 17 00:00:00 2001 From: npo6ka Date: Sun, 19 Apr 2026 17:29:25 +0300 Subject: [PATCH 74/75] moved the list of mods to the config folder --- .cursor/rules/effects.mdc | 35 ++++----- platformio.ini | 6 +- src/configs/DefaultEffectList.h | 46 +++++++++++ src/core/effect/EffectFactory.cpp | 76 ++++++++++--------- src/core/effect/EffectFactory.h | 36 +++++++++ src/core/effect/storage/FileEffectStorage.h | 7 +- src/core/effect/storage/StaticEffectStorage.h | 36 +-------- 7 files changed, 150 insertions(+), 92 deletions(-) create mode 100644 src/configs/DefaultEffectList.h diff --git a/.cursor/rules/effects.mdc b/.cursor/rules/effects.mdc index b84b677..f280fda 100644 --- a/.cursor/rules/effects.mdc +++ b/.cursor/rules/effects.mdc @@ -33,38 +33,39 @@ globs: src/effects/**/*.h, src/core/effect/**/* ## 3. Регистрация в фабрике -Файл: **`src/core/effect/EffectFactory.cpp`**. +Файлы: **`src/core/effect/EffectFactory.h`** (enum), **`src/core/effect/EffectFactory.cpp`** (switch). -1. **Include** (в блоке с остальными эффектами): +1. **`EffectFactory.h`** — в `enum class EffectId` перед `Count` добавить новый идентификатор со следующим свободным числом (сейчас последний режим — `PulseRings = 31`, следующий будет `32`): ```cpp - #include "effects/effects_impl/имя_файла.h" + NewEffect = 32, + Count ``` + Значение `Count` пересчитается автоматически (сентинел «после последнего id»). -2. **Новый id**: следующий свободный номер (сейчас последний — 31, следующий будет 32). - -3. **Switch**: добавить строку перед `default`: +2. **`EffectFactory.cpp`** — **include** класса эффекта (в блоке с остальными): ```cpp - EFFECT_CASE(32, "ИмяВСписке", ClassName); + #include "effects/effects_impl/имя_файла.h" ``` - - Первый аргумент — уникальный `effect_id`. - - Второй — строковое имя режима (отображается в списке). - - Третий — имя класса эффекта. -4. **Счётчик**: увеличить константу: +3. В **`switch`** в `getEffectInfo()` добавить строку **до** `default`: ```cpp - #define EFFECT_COUNT 33 // было 32 + EFFECT_CASE(NewEffect); ``` + Макрос ожидает, что **имя класса совпадает с именем варианта enum** (как у существующих `EFFECT_CASE(SlowRandom);` → класс `SlowRandom`). ## 4. Добавление в список по умолчанию -Файл: **`src/core/effect/storage/StaticEffectStorage.h`**. +Файл: **`src/configs/DefaultEffectList.h`**. + +В массив **`DefaultEffects::effectIds`** добавить элемент `EffectId::…` в нужном порядке (порядок в списке = порядок переключения при старте и после сброса): -В методе `createDefaultEffectsList()` добавить (с нужным id и комментарием): ```cpp -internalAddEffect(32); // ClassName или краткое имя +EffectId::NewEffect, ``` -Так режим попадёт в стартовый список при сбросе настроек и будет доступен при переключении кнопкой/автомодом. +Размер массива задаётся списком инициализаторов — **вручную считать элементы не нужно**. Убрать режим из стартового списка — удалить или закомментировать соответствующую строку. + +Список используется в **`StaticEffectStorage`** и при построении дефолтов в **`FileEffectStorage`** (без дублирования через временное хранилище). ## 5. API матрицы и цвета @@ -111,4 +112,4 @@ public: }; ``` -После добавления класса не забыть: **EffectFactory.cpp** (include + EFFECT_CASE + EFFECT_COUNT) и **StaticEffectStorage.h** (internalAddEffect). +После добавления класса не забыть: **`EffectFactory.h`** (новый `EffectId`), **`EffectFactory.cpp`** (include + `EFFECT_CASE`), **`DefaultEffectList.h`** (при необходимости — `EffectId::…` в `DefaultEffects::effectIds`). diff --git a/platformio.ini b/platformio.ini index db1460c..2e9610f 100644 --- a/platformio.ini +++ b/platformio.ini @@ -18,12 +18,14 @@ lib_deps = 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 +build_flags = ${common.build_flags} -DESP12F board_build.filesystem = ${common.filesystem} lib_deps = ${common.lib_deps} @@ -34,7 +36,7 @@ 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} 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/core/effect/EffectFactory.cpp b/src/core/effect/EffectFactory.cpp index 09d3878..d74a9d9 100644 --- a/src/core/effect/EffectFactory.cpp +++ b/src/core/effect/EffectFactory.cpp @@ -52,52 +52,52 @@ struct EffectCreationInfo { EffectCreator effect_creator; }; -#define EFFECT_CASE(id, name, type) case id: return EffectCreationInfo{name, effectCreator()}; -#define EFFECT_COUNT 32 // при добавлении нового эффекта, не забудь обновить EFFECT_COUNT +#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(0, "Error", ErrorEffect); - EFFECT_CASE(1, "SlowRandom", SlowRandom); - EFFECT_CASE(2, "SimpleRainbow", SimpleRainbow); - EFFECT_CASE(3, "Dribs", Dribs); - EFFECT_CASE(4, "Rain", Rain); - EFFECT_CASE(5, "AllRandom", AllRandom); - EFFECT_CASE(6, "Snow", Snow); - EFFECT_CASE(7, "Fire", Fire); - EFFECT_CASE(8, "TheMatrix", TheMatrix); - EFFECT_CASE(9, "SimpleBalls", SimpleBalls); - EFFECT_CASE(10, "Confetti", Confetti); - EFFECT_CASE(11, "Starfall", Starfall); - EFFECT_CASE(12, "DynamicSquare", DynamicSquare); - EFFECT_CASE(13, "RandomRain", RandomRain); - EFFECT_CASE(14, "RainbowRain", RainbowRain); - EFFECT_CASE(15, "Points", Points); - EFFECT_CASE(16, "RainbowPoint", RainbowPoint); - EFFECT_CASE(17, "RainbowStaticPoint", RainbowStaticPoint); - EFFECT_CASE(18, "Text", TextMode); - EFFECT_CASE(19, "Mouse", Mouse); - EFFECT_CASE(20, "Pacman", Pacman); - EFFECT_CASE(21, "CircularPoint", CircularPoint); - EFFECT_CASE(22, "Zigzag", ZigZag); - EFFECT_CASE(23, "HorizontalRainbowPoint", HorizontalRainbowPoint); - EFFECT_CASE(24, "Ny2020", NY2020); - EFFECT_CASE(25, "DribsAllSide", DribsAllSide); - EFFECT_CASE(26, "Snake", Snake); - EFFECT_CASE(27, "RadialFire", RadialFire); - EFFECT_CASE(28, "RadialPattern", RadialPattern); - EFFECT_CASE(29, "CrazyBees", CrazyBees); - EFFECT_CASE(30, "Spiral", Spiral); - EFFECT_CASE(31, "PulseRings", PulseRings); - // при добавлении нового эффекта, не забудь обновить EFFECT_COUNT + 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{"Error", effectCreator()}; + return EffectCreationInfo{"ErrorEffect", effectCreator()}; } } uint32_t EffectFactory::getEffectCount() { - return EFFECT_COUNT; + return static_cast(EffectId::Count); } std::unique_ptr EffectFactory::createEffect(uint32_t effect_id) { @@ -107,3 +107,5 @@ std::unique_ptr EffectFactory::createEffect(uint32_t effect_id) { 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 index 62336b8..b50d5eb 100644 --- a/src/core/effect/EffectFactory.h +++ b/src/core/effect/EffectFactory.h @@ -5,6 +5,42 @@ #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); diff --git a/src/core/effect/storage/FileEffectStorage.h b/src/core/effect/storage/FileEffectStorage.h index 6e8a94b..ddbffe4 100644 --- a/src/core/effect/storage/FileEffectStorage.h +++ b/src/core/effect/storage/FileEffectStorage.h @@ -4,7 +4,7 @@ #include "core/Variable/FileSavableVariable.h" #include "libs/debug_lib.h" #include "IEffectStorage.h" -#include "StaticEffectStorage.h" +#include "configs/DefaultEffectList.h" #include #include "vector" @@ -180,9 +180,8 @@ class FileEffectStorage : public IEffectStorage void createDefaultEffectsList() { logInfo("Creating default effects list\n"); - StaticEffectStorage defaultStorage; - for (size_t i = 0; i < defaultStorage.size(); i++) { - internalAddEffect(EffectInfo(defaultStorage.getEffectInfo(i).id, generateSavedIndex()), false); + for (EffectId id : DefaultEffects::effectIds) { + internalAddEffect(EffectInfo(static_cast(id), generateSavedIndex()), false); } _currentEffectIndex.set(0); } diff --git a/src/core/effect/storage/StaticEffectStorage.h b/src/core/effect/storage/StaticEffectStorage.h index d8770d0..4364c41 100644 --- a/src/core/effect/storage/StaticEffectStorage.h +++ b/src/core/effect/storage/StaticEffectStorage.h @@ -2,6 +2,7 @@ #include "IEffectStorage.h" +#include "configs/DefaultEffectList.h" #include "core/effect/EffectInfo.h" #include "libs/debug_lib.h" @@ -85,38 +86,9 @@ class StaticEffectStorage : public IEffectStorage } private: void createDefaultEffectsList() { - // Тут можно комментить не нужные эффекты, чтобы они не попадали в начальный список эффектов - internalAddEffect(1); // SlowRandom - internalAddEffect(2); // SimpleRainbow - internalAddEffect(3); // Dribs - internalAddEffect(4); // Rain - internalAddEffect(5); // AllRandom - internalAddEffect(6); // Snow - internalAddEffect(7); // Fire - internalAddEffect(8); // TheMatrix - internalAddEffect(9); // SimpleBalls - internalAddEffect(10); // Confetti - internalAddEffect(11); // Starfall - internalAddEffect(12); // DynamicSquare - internalAddEffect(13); // RandomRain - internalAddEffect(14); // RainbowRain - internalAddEffect(15); // Points - internalAddEffect(16); // RainbowPoint - internalAddEffect(17); // RainbowStaticPoint - internalAddEffect(18); // Text - internalAddEffect(19); // Mouse - internalAddEffect(20); // Pacman - internalAddEffect(21); // CircularPoint - internalAddEffect(22); // Zigzag - internalAddEffect(23); // HorizontalRainbowPoint - internalAddEffect(24); // Ny2020 - internalAddEffect(25); // DribsAllSide - internalAddEffect(26); // Snake - internalAddEffect(27); // RadialFire - internalAddEffect(28); // RadialPattern - internalAddEffect(29); // CrazyBees - internalAddEffect(30); // Spiral - internalAddEffect(31); // PulseRings + for (EffectId id : DefaultEffects::effectIds) { + internalAddEffect(static_cast(id)); + } } void internalAddEffect(uint32_t effectId) { From fc1260d7e4812f19c5da52eb962c1a8410585523 Mon Sep 17 00:00:00 2001 From: npo6ka Date: Sun, 19 Apr 2026 17:45:22 +0300 Subject: [PATCH 75/75] fix readme --- README.md | 41 +++++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index beeefcc..b79b22e 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ ## Возможности -- **32 визуальных режима** (огонь, дождь, конфетти, змейка, спираль, кольца и др.) +- **31 визуальный режим** в прошивке (огонь, дождь, конфетти, змейка, спираль, кольца и др.; id 1–31, 0 — ошибка) - Переключение режимов кнопкой или ИК-пультом - Автоматическая смена режима по таймеру (опционально) - Сохранение текущего режима и списка в файл (LittleFS) или статический список @@ -79,11 +79,11 @@ pio run -e esp32dev -t uploadfs | 15 | Points | Движущиеся точки | | 16 | RainbowPoint | Радужная точка | | 17 | RainbowStaticPoint | Статичная радужная точка | -| 18 | Text | Текст | +| 18 | TextMode | Текст | | 19 | Mouse | Указатель мыши | | 20 | Pacman | Пакман | | 21 | CircularPoint | Круговая точка | -| 22 | Zigzag | Зигзаг | +| 22 | ZigZag | Зигзаг | | 23 | HorizontalRainbowPoint | Горизонтальная радужная точка | | 24 | Ny2020 | Новогодний | | 25 | DribsAllSide | Капли со всех сторон | @@ -121,24 +121,27 @@ pio run -e esp32dev -t uploadfs 2. Один раз загрузите файловую систему (раздел LittleFS): `pio run -e esp32dev -t uploadfs` (для ESP8266 — `pio run -e esp12e -t uploadfs`). -3. При первом запуске файла `mods.txt` нет или он пустой — тогда автоматически создаётся **список по умолчанию** (такой же, как в `StaticEffectStorage`, см. ниже), и текущий режим ставится в 0. +3. При первом запуске файла `mods.txt` нет или он пустой — тогда автоматически создаётся **список по умолчанию** из `DefaultEffects::effectIds` в `src/configs/DefaultEffectList.h` (см. раздел ниже), и текущий режим ставится в 0. ### Сброс списка к «заводскому» -При использовании **FileEffectStorage** сброс к списку по умолчанию выполняется вызовом **`reset()`** у хранилища: список очищается и заполняется тем же набором эффектов, что и в `StaticEffectStorage::createDefaultEffectsList()`. Текущий индекс становится 0. В текущей прошивке вызов `reset()` нужно добавить в код (например, по длинному удержанию кнопки или отдельной команде), если такая функция нужна. +При использовании **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**): дублировать настройку в других файлах не нужно. -При **`SAVE_TO_EEPROM = false`** список режимов задаётся единственным местом — методом **`createDefaultEffectsList()`** в файле **`src/core/effect/storage/StaticEffectStorage.h`**. - -- **Включить режим в список** — добавить строку: - `internalAddEffect(); // краткий комментарий` - Число `` — это номер эффекта из таблицы в README (1–31, 0 — зарезервирован под ошибку). -- **Убрать режим из списка** — закомментировать или удалить соответствующую строку `internalAddEffect(...)`. -- **Изменить порядок** — менять порядок вызовов `internalAddEffect(...)`; порядок вызовов = порядок переключения кнопкой. +После правок пересоберите прошивку (`pio run` …) и при необходимости загрузите её; при использовании LittleFS, если нужно заново применить «заводской» список на уже прошивавшемся устройстве, сотрите файл настроек или вызовите **`reset()`** в коде (если он у вас подключён). После изменения пересоберите прошивку и загрузите её. @@ -158,8 +161,9 @@ LedMatrix/ │ ├── main.cpp # Точка входа, setup/loop │ ├── myapplication.* # Приложение: кнопка, ИК, автомод, менеджер эффектов │ ├── configs/ -│ │ ├── matrix.h # Настройки матрицы и пинов -│ │ └── constants.h # Константы, index_t +│ │ ├── matrix.h # Настройки матрицы и пинов +│ │ ├── DefaultEffectList.h # Список режимов по умолчанию (порядок и набор) +│ │ └── constants.h # Константы, index_t │ ├── core/ │ │ ├── effect/ # EffectManager, EffectFactory, хранилища │ │ ├── file/ # Работа с файлами (LSF) @@ -178,8 +182,9 @@ LedMatrix/ 1. Создать класс, наследующий `Effect`, в `src/effects/effects_impl/<имя>.h`. 2. Реализовать минимум `on_update()`; в `on_init()` при необходимости вызвать `set_fps()`. -3. В **EffectFactory.cpp**: добавить `#include`, `EFFECT_CASE(id, "Name", ClassName)`, увеличить `EFFECT_COUNT`. -4. В **StaticEffectStorage.h**: в `createDefaultEffectsList()` добавить `internalAddEffect(id)`. +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).