// Определения программных констант и переменных (параметры эффектов и т.п.)

// При запуске микроконтроллера выполняется инициализация всех параметров значениями, сохраненными в EEPROM (функция loadSettings() в eeprom.ino)
// При этом если флаг EEPROM_OK в строке 7 этого файла был изменен и не совпадает со считанными яз ячейки #0 значением -
// выполняется инициализация параметров значениями по умолчанию (функция loadSettings() в eeprom.ino, ветвь if (isInitialized) -> false )

#define EEPROM_OK     0x5D                // Флаг, показывающий, что EEPROM инициализирована корректными данными 

// ******************* ОПРЕДЕЛЕНИЯ ПАРАМЕТРОВ подключения к сети *********************

// Внимание!!! Если вы меняете эти значения ПОСЛЕ того, как прошивка уже хотя бы раз была загружена в плату и выполнялась,
// чтобы изменения вступили в силу нужно также изменить значение константы EEPROM_OK в строке 7 этого файла (см.выше)
// или в Arduino IDE в меню "Инструменты" -> "Erase Flash" выбрать значение "All Flash Contents"

// ------------------------ Локальная сеть ---------------------

#ifndef NETWORK_SSID
#define NETWORK_SSID ""                   // Имя WiFi сети
#endif

#ifndef NETWORK_PASS
#define NETWORK_PASS ""                   // Пароль для подключения к WiFi сети
#endif

#ifndef DEFAULT_IP
#define DEFAULT_IP {192, 168, 0, 100}     // Сетевой адрес устройства по умолчанию
#endif

#ifndef DEFAULT_PORT
#define DEFAULT_PORT 2390                 // Сетевой порт устройства по умолчанию
#endif

#ifndef GTW
#define GTW     1                         // Последняя цифра в IP адресе роутера. Обычно IP роутера 192.168.0.*1*, но некоторые роутеры имеют адрес 192.168.0.100 или 192.168.0.254
#endif                                    // Тогда здесь вместо *1* должно быть 100 или 254 соответственно

// ------------------------ Сервер времени ---------------------

#define DEFAULT_NTP_SERVER "ru.pool.ntp.org"     // NTP сервер по умолчанию "time.nist.gov"
#define DEFAULT_AP_NAME    "PanelAP"             // Имя точки доступа по умолчанию 
#define DEFAULT_AP_PASS    "12341234"            // Пароль точки доступа по умолчанию

// ------------------------ MQTT parameters --------------------

#if (USE_MQTT == 1)

#ifndef DEFAULT_MQTT_SERVER
#define DEFAULT_MQTT_SERVER "mqtt.by"            // MQTT сервер
#endif

#ifndef DEFAULT_MQTT_USER
#define DEFAULT_MQTT_USER   ""                   // Имя mqtt-пользователя    (!!! укажите имя пользователя для вашего соединения - зарегистрируйтесь на mqtt.by -> смотрите в личном кабинете !!!)
#endif

#ifndef DEFAULT_MQTT_PASS
#define DEFAULT_MQTT_PASS   ""                   // Пароль mqtt-пользователя (!!! укажите пароль вашего соединения - зарегистрируйтесь на mqtt.by -> смотрите в личном кабинете !!!)
#endif

#ifndef DEFAULT_MQTT_PORT
#define DEFAULT_MQTT_PORT   1883                 // Порт mqtt-соединения
#endif

#ifndef DEFAULT_MQTT_PREFIX
#define DEFAULT_MQTT_PREFIX "af7cd12a"           // Префикс топика сообщения уникальный для вашего устройства
#endif

#endif

// ---------------------------------------------------------------

#define EEPROM_MAX    4096       // Максимальный размер EEPROM доступный для использования
#define EFFECT_EEPROM  400       // Начальная ячейка eeprom с параметрами эффектов, 5 байт на эффект
#define TEXT_EEPROM   1000       // Начальная ячейка eeprom с текстом бегущих строк

#define COLOR_ORDER    GRB       // Порядок цветов на ленте. Если цвет отображается некорректно - меняйте. Начать можно с RGB

#ifndef BRIGHTNESS
#define BRIGHTNESS      32       // Яркость матрицы по-умолчанию 0..255
#endif

// ------------ Настройки региона погоды и часового пояса

#ifndef TIME_ZONE
#define TIME_ZONE        7           // Смещение часового пояса от UTC
#endif

#ifndef WEATHER_REGION_YDX
#define WEATHER_REGION_YDX 62        // Код региона погоды по Yandex
#endif

#ifndef WEATHER_REGION_OWTH
#define WEATHER_REGION_OWTH 1502026  // Код региона погоды по OpenWeatherMap
#endif

// Список и порядок эффектов, передаваемый в приложение на смартфоне. Данный список попадает в комбобокс выбора, 
// чей индекс передается из приложения в контроллер матрицы для выбора, поэтому порядок должен соответствовать 
// списку эффектов, определенному ниже

#if (USE_SD == 1)                      
#define EFFECT_LIST F("Часы,Лампа,Снегопад,Кубик,Радуга,Пейнтбол,Огонь,The Matrix,Шарики,Звездопад,Конфетти," \
                      "Цветной шум,Облака,Лава,Плазма,Радужные переливы,Павлин,Зебра,Шумящий лес,Морской прибой,Смена цвета," \
                      "Светлячки,Водоворот,Циклон,Мерцание,Северное сияние,Тени,Лабиринт,Змейка,Тетрис,Арканоид," \
                      "Палитра,Спектрум,Синусы,Вышиванка,Дождь,Камин,Стрелки,Анимация,Погода,Жизнь,Узоры,Рубик,Звёзды,Штора,Трафик," \
                      "Слайды,Рассвет,SD-Карта")
#else
#define EFFECT_LIST F("Часы,Лампа,Снегопад,Кубик,Радуга,Пейнтбол,Огонь,The Matrix,Шарики,Звездопад,Конфетти," \
                      "Цветной шум,Облака,Лава,Плазма,Радужные переливы,Павлин,Зебра,Шумящий лес,Морской прибой,Смена цвета," \
                      "Светлячки,Водоворот,Циклон,Мерцание,Северное сияние,Тени,Лабиринт,Змейка,Тетрис,Арканоид," \
                      "Палитра,Спектрум,Синусы,Вышиванка,Дождь,Камин,Стрелки,Анимация,Погода,Жизнь,Узоры,Рубик,Звёзды,Штора,Трафик," \
                      "Слайды,Рассвет")
#endif

// Ниже представлен список эффектов, поддерживаемых прошивкой. Нумерация - сплошная от ID = 0 до ID = MAX_EFFECT
// Если какие-то эффекты вам не нужны - перенесите их в конец списка, исправьте нумерацию и поправьте MAX_EFFECT так,
// чтобы ненужные эффекты имели номера бОльшие MAX_EFFECT.
// Так как перебор эффектов идет от 0 до MAX_EFFECT-1, эффекты с номерами за этим диапазоном не будут отображаться.
// Также требуется исправить список эффектов EFFECT_LIST (см.выше) так, чтобы названия и порядок эффектов
// соответствовал списку, определенному ниже. Отключенные эффекты не должны присутствовать в EFFECT_LIST

// ID эффектов
#define MC_CLOCK                 0         // Режим отображения часов (когда "Ночные часы" или просто "Часы" на черном фоне, а не как оверлей поверх других эффектов
#define MC_FILL_COLOR            1
#define MC_SNOW                  2
#define MC_BALL                  3
#define MC_RAINBOW               4
#define MC_PAINTBALL             5
#define MC_FIRE                  6
#define MC_MATRIX                7
#define MC_BALLS                 8
#define MC_STARFALL              9
#define MC_SPARKLES             10
#define MC_NOISE_MADNESS        11
#define MC_NOISE_CLOUD          12
#define MC_NOISE_LAVA           13
#define MC_NOISE_PLASMA         14
#define MC_NOISE_RAINBOW        15
#define MC_NOISE_RAINBOW_STRIP  16
#define MC_NOISE_ZEBRA          17
#define MC_NOISE_FOREST         18
#define MC_NOISE_OCEAN          19
#define MC_COLORS               20
#define MC_LIGHTERS             21
#define MC_SWIRL                22
#define MC_CYCLON               23
#define MC_FLICKER              24
#define MC_PACIFICA             25
#define MC_SHADOWS              26
#define MC_MAZE                 27
#define MC_SNAKE                28
#define MC_TETRIS               29
#define MC_ARKANOID             30
#define MC_PALETTE              31
#define MC_ANALYZER             32
#define MC_PRIZMATA             33
#define MC_MUNCH                34
#define MC_RAIN                 35
#define MC_FIRE2                36
#define MC_ARROWS               37
#define MC_IMAGE                38
#define MC_WEATHER              39
#define MC_LIFE                 40
#define MC_PATTERNS             41
#define MC_RUBIK                42
#define MC_STARS                43
#define MC_STARS2               44
#define MC_TRAFFIC              45
#define MC_SLIDE                46
#define MC_DAWN_ALARM           47
#define MC_SDCARD               48

#if (USE_SD == 1)
#define MAX_EFFECT              49         // количество эффектов, определенных в прошивке
#else
#define MAX_EFFECT              48         // количество эффектов, определенных в прошивке
#endif

#define MAX_SPEC_EFFECT         11         // количество эффектов быстрого доступа определенных в прошивке -> 0..10
#define SPECIAL_EFFECTS_START  120         // Некоторые специальные "служебные" эффекты имеют код >= SPECIAL_EFFECTS_START

                                           // Внимание !!! - для идентификатора режима используется тип int8_t (-128..127, нужно значение -1), поэтому код режима не может быть больше 127 !!!
#define MC_DRAW                123         // Режим рисования картинки на телефоне
#define MC_LOADIMAGE           124         // Режим загрузки изображения с телефона
#define MC_TEXT                125         // Режим бегущей строки (для отображения IP адреса) - бегущая строка на черном фоне
#define MC_DAWN_ALARM_SPIRAL   126         // Специальный режим, вызывается из DEMO_DAWN_ALARM для ламп на круговой матрице - огонек по спирали
#define MC_DAWN_ALARM_SQUARE   127         // Специальный режим, вызывается из DEMO_DAWN_ALARM для плоских матриц - огонек по спирали на плоскости матрицы
// ---------------------------------

// *************************************************************************

enum  eModes   {NORMAL, COLOR, TEXT}   parseMode; // Текущий режим парсера
enum  eSources {NONE, BOTH, UDP, MQTT} cmdSource; // Источник команды; NONE - нет значения; BOTH - любой, UDP-клиент, MQTT-клиент

// ****************** ОПРЕДЕЛЕНИЯ ПАРАМЕТРОВ БУДИЛЬНИКА ********************

#if (USE_MP3 == 1)
// SD карточка в MP3 плеере (DFPlayer) содержит в корне три папки - "1","2" и "3"
// Папка "1" содержит MP3 файлы звуков, проигрываемых при наступлении события будильника
// Папка "2" содержит MP3 файлы звуков, проигрываемых при наступлении события рассвета
// Папка "3" содержит MP3 файлы звуков, проигрываемых в макросе {A} бегущей строки
// DFPlayer не имеет возможности считывать имена файлов, только возможность получить количество файлов в папке.
// Команда на воспроизведение звука означает - играть файл с индексом (номером) N из папки M
// Номера файлов определяются таблицей размещения файлов SD-карты (FAT) и формируются в порядке очереди записи файлов на чистую флэшку
// Так, файл записанный в папку первым получает номер 1, второй - 2 и так далее и никак не зависит от имен файлов
// Данные массивы содержат отображаемые в приложении имена звуков в порядке, соответствующем нумерации записанных в папки файлов.

// Список звуков для комбобокса "Звук будильника" в приложении на смартфоне
#define ALARM_SOUND_LIST   F("One Step Over,In the Death Car,Труба зовет,Маяк,Mister Sandman,Шкатулка,Banana Phone,Carol of the Bells")
// Список звуков для комбобокса "Звук рассвета" в приложении на смартфоне
#define DAWN_SOUND_LIST    F("Птицы,Гроза,Прибой,Дождь,Ручей,Мантра,La Petite Fille De La Mer")
// Список звуков для макроса {A} бегущей строки
#define NOTIFY_SOUND_LIST  F("Piece Of Сake,Swiftly,Pristine,Goes Without Saying,Inflicted,Eventually,Point Blank,Spring Board," \
                             "To The Point,Serious Strike,Jingle Bells,Happy New Year,Christmas Bells,Door Knock,Alarm Signal," \
                             "Viber Message,Viber Call,Text Message,Old Clock 1,Old Clock 2,Old Clock 3")

#endif

// ********************* ОПРЕДЕЛЕНИЯ ПАРАМЕТРОВ ЧАСОВ **********************

#define D_CLOCK_SPEED 254                   // скорость перемещения эффекта часов по умолчанию (мс) - если больше 240 - часы стоят, не прокручиваются
#define D_CLOCK_SPEED_MIN 2
#define D_CLOCK_SPEED_MAX 254

uint8_t CLOCK_SIZE = 0;                     // Размер горизонтальных часов: 0 - авто; 1 - цифры 3x5; 2 - цифры 5х7
uint8_t CLOCK_ORIENT = 0;                   // Ориентация отображения часов: 0 горизонтальные, 1 вертикальные (только для режима часов с цифрами 3х5; Часы шрифтом 5x7 всегда горизонтальные
uint8_t CLOCK_MOVE_DIVIDER = 3;             // Делитель такта таймера эффектов для движения часов оверлея (режим прокручивания часов по горизонтали)
uint8_t CLOCK_MOVE_CNT = CLOCK_MOVE_DIVIDER;// Текущее значения счетчика сдвига 

uint8_t clockScrollSpeed = D_CLOCK_SPEED;   // Скорость прокрутки часов
uint8_t c_size = 0;                         // тип часов: 0 - авто; 1 - шрифт 3x5; 2 - шрифт 5х7; вычисляется в зависимости от настроек и параметров матрицы 
bool allow_two_row = false;                 // высота матрицы позволяет размещать календарь и часы с температурой в две строки  

// Позиции отображения часов, календаря, погоды; 
// Вычисляется в зависимости от выбранного шрифта часов 3x5 или 5x7, вертикальные/горизонтальные
uint8_t  CALENDAR_X;                        // Позиция вывода календаря по X
uint8_t  CALENDAR_Y;                        // Позиция вывода календаря по Y
uint8_t  CLOCK_X;                           // Позиция вывода часов по X
uint8_t  CLOCK_Y;                           // Позиция вывода часов по Y
int8_t   CLOCK_WX;                          // Позиция вывода погоды по X в малых часах / Реальная ширина отрисованных часов в больших часах
int8_t   CLOCK_WY;                          // Позиция вывода погоды по Y

uint8_t  CALENDAR_W;                        // Ширина вывода календаря
uint8_t  CALENDAR_H;                        // Высота вывода календаря по Y
uint8_t  CLOCK_W;                           // Ширина вывода часов по X
uint8_t  CLOCK_H;                           // Высота вывода часов по Y
uint8_t  CLOCK_WW;                          // Ширина вывода погоды по X
uint8_t  CLOCK_WH;                          // Высота вывода погоды по Y

int8_t   CLOCK_XC    = 0;                   // Текущая позиция часов оверлея по оси Х с учетом скроллинга
int8_t   CALENDAR_XC = 0;                   // Текущая позиция календаря оверлея по оси Х 
int8_t   CLOCK_FX    = 0;                   // Реальная X позиция отрисовки первой цифры блока часов и температуры
int8_t   CLOCK_LX    = 0;                   // Реальная X позиция отрисовки последней цифры часов - температура будет выводиться от этой позиции влево, чтобы выровнять температуру по правому краю часов

uint8_t  COLOR_MODE = 0;                    // Режим цвета часов
                                            //  0 - монохром
                                            //  1 - радужная смена (каждая цифра)
                                            //  2 - радужная смена (часы, точки, минуты)
                                            //  3 - заданные цвета (часы, точки, минуты) - HOUR_COLOR, DOT_COLOR, MIN_COLOR в clock.ino

#define  MIN_NIGHT_CLOCK_BRIGHTNESS 16      // Минимальная яркость ночных часов при которой диоды еще светятся
uint8_t  nightClockBrightness = 16;         // Яркость отображения ночных часов
uint8_t  nightClockColor = 0;               // Цвет ночных часов: 0 - R; 1 - G; 2 - B; 3 - C; 4 - M; 5 - Y; 6 - W;
bool     needTurnOffClock = false;          // Выключать индикатор часов TM1637 при выключении устройства (true); если False - при отключении панели индикатор TM1637 продолжает показывать текущее время
 
bool     showWeatherInClock = true;         // Показывать текущую температуру при отображении малых (неподвижных) часов
bool     showDateInClock = true;            // Показывать дату при отображении часов
uint8_t  showDateDuration = 2;              // на 2 секунды
uint8_t  showDateInterval = 240;            // через каждые 240 секунд
bool     showDateState = false;             // false - отображаются часы; true - отображается дата
bool     showWeatherState = false;          // false - отображаются часы; true - отображается температура в больших часах
uint32_t showDateStateLastChange = 0;       // Время, когда отображение часов сменилось на отображение календаря и наоборот

int8_t   hrs = 0, mins = 0;                 // Для получения текущего времени. Инициализировано как 01.01.1970 00:00
int8_t   secs = 0, aday = 1, amnth = 1;
int16_t  ayear = 1970;
bool     dotFlag;                           // флаг: в часах рисуется точки 

// ************************ НАСТРОЙКИ БЕГУЩЕЙ СТРОКИ ***********************

#define D_TEXT_SPEED 100                    // скорость перемещения эффекта часов по умолчанию (мс)
#define D_TEXT_SPEED_MIN 2
#define D_TEXT_SPEED_MAX 254

#define MIRR_V 0                            // отразить текст по вертикали (0 / 1)
#define MIRR_H 0                            // отразить текст по горизонтали (0 / 1)

#if (BIG_FONT == 0)
  #define LET_WIDTH 5                       // ширина буквы шрифта
  #define LET_HEIGHT 8                      // высота буквы шрифта
  #define SPACE 1                           // пробел между буквами
#elif (BIG_FONT == 1)
  #define LET_WIDTH 10                      // ширина буквы шрифта
  #define LET_HEIGHT 16                     // высота буквы шрифта
  #define SPACE 2                           // пробел между буквами
#else  
  #define LET_WIDTH 8                       // ширина буквы шрифта
  #define LET_HEIGHT 13                     // высота буквы шрифта
  #define SPACE 1                           // пробел между буквами
#endif

uint8_t  textScrollSpeed = D_TEXT_SPEED;    // Скорость прокрутки оверлея бегущей строки

uint16_t TEXT_INTERVAL = 90;                // Интервал (время в секундах) между отображением строк
                                            // следующая бегущая строка будет отображена через указанное кол-во секунд
                                            // 0 - отключено, следующая строка отображается после завершения текущей
                                         
uint8_t   COLOR_TEXT_MODE = 0;              // Режим цвета текста бегущей строки и самой бегущей строки
                                            //  0 - монохром
                                            //  1 - радужная смена (каждая цифра)
                                            //  2 - каждая цифра свой цвет
                                         
// --- Параметры отображения текущей бегущей строки
int16_t  textShowTime = -1;                 // Если больше нуля - сколько времени отображать бегущую строку в секундах; Если 0 - используется textShowCount; В самой строке спец-макросом может быть указано кол-во секунд
uint8_t  textShowCount = 1;                 // Сколько раз прокручивать текст бегущей строки поверх эффектов; По умолчанию - 1; В самой строке спец-макросом может быть указано число 
uint8_t  textCurrentCount = 0;              // Сколько раз текст очередной бегущей строки поверх эффектов уже показан
bool     useSpecialTextColor = false;       // В текущей бегущей строке был задан цвет, которым она должна отображаться
uint32_t specialTextColor = 0xffffff;       // Цвет отображения бегущей строки, может быть указан макросом в строке. Если не указан - используются глобальные настройки цвета бегущей строки
bool     useSpecialBackColor = false;       // В текущей бегущей строке был задан цвет фона, на котором она должна отображаться
uint32_t specialBackColor = 0x000000;       // Цвет фона отображения бегущей строки, может быть указан макросом в строке. Если не указан - наложение на текущий эффект
int8_t   specialTextEffect = -1;            // Эффект, который нужно включить при начале отображения строки текста, может быть указан макросом в строке.
int8_t   specialTextEffectParam = -1;       // Параметр для эффекта (см. выше). Например эффект MC_SDCARD имеет более 40 подэффектов. Номер подэффекта хранится в этой переменной, извлекается из макроса {E}
int8_t   saveEffectBeforeText = -1;         // Если указан эффект на котором отображать строку, сохранить текущий эффект тут, чтобы по завершению строки восстановить его
int8_t   currentTextLineIdx = -1;           // Какая строка из массива строк показывается в текущий момент
int8_t   nextTextLineIdx = -1;              // Какую следующую строку показывать, может быть указан макросом в строке. Если указан - интервал отображения игнорируется, строка показывается сразу
bool     textHasDateTime = false;           // Строка содержит макрос отображения текущего времени - ее нужно пересчитывать каждый раз при отрисовке; Если макроса времени нет - рассчитать текст строки один раз на этапе инициализации
bool     textHasMultiColor = false;         // Строка содержит несколько (до 6) макросов, задающих цвет строки
String   currentText = "";                  // Текст текущей отображаемой строки
String   syncText = "";                     // Текст текущей отображаемой строки не очищенный от макросов
bool     ignoreTextOverlaySettingforEffect = false; // Показывать бегущую строку для эффекта, даже если настройки эффекта это запрещают; В строке может быть указано, какую строку показывать следующей - ее надо показывать даже если настройки текущего эффекта это запрещают
int8_t   sequenceIdx = -1;                  // Если нулевая строка массива содержит последовательность отображения строк (начинается с # или имеет значение ##) - указывает на позицию какой будет следующая отображаемая строка
int8_t   editIdx = -1;                      // Индекс строки, передаваемой в телефон на запрос строки по индексу
uint16_t crc = 0;                           // Контрольная сумма загруженного массива строк. Рассчитывается при загрузке, проверяется перед сохранением 
int16_t  memoryAvail = 0;                   // Сколько памяти осталось под хранение строк; Устанавливается при загрузке / сохранении массива строк
#define  MAX_COLORS 12                      // сколько макросов цвета может содержать строка
int16_t  textColorPos[MAX_COLORS+1];        // Позиции начала цвета в строке
uint32_t textColor[MAX_COLORS+1];           // Цвета в строке

// --- 

uint32_t textStartTime = 0;                 // Время начала отображения текущей бегущей строки
uint32_t textLastTime = 0;                  // Время завершения отображения последней показанной бегущей строки
uint32_t textLastSend = 0;                  // Время последней отправки очередной строки списка строк в телефон
uint32_t textAllowBegin = 0;                // Для строк с макросом {S} - время начала допустимого интервала отображения unixTime
uint32_t textAllowEnd = 4294967295;         // Для строк с макросом {S} - время конца допустимого интервала отображения unixTime

bool     loadingTextFlag = false;           // Флаг инициализации показа строки в режиме MC_TEXT (показ IP адреса); для новой строки оверлейного текста используется специальный вызов prepareNextText() в custom.ino
bool     showTextNow = false;               // флаг: что в настоящий момент нужно отображать оверлеем: true - оверлей бегущей строки; false - оверлей часов;
bool     fullTextFlag = false;              // флаг: текст бегущей строки показан полностью (строка убежала)
bool     gameOverFlag = false;              // флаг: демо-игра завершена

String   textLines[36];                     // Массив строк для отображения 0..9A..Z; 

// --- 

typedef struct  {                           // Структура описателя событий постоянного контроля
  time_t moment;                            // unixtime - ближайший момент времени наступления события
  uint32_t before;                          // за сколько секунд до события начинать отображать строку
  uint32_t after;                           // сколько секунд после наступления события отображать строку-заместитель
  uint8_t  index_b;                         // индекс в массиве строк textLines отображаемой строки ДО события, привязанной к этому событию
  int8_t   index_a;                         // индекс в массиве строк textLines отображаемой строки ПОСЛЕ события, привязанной к этому событию (-1 если строки замены не указано)
} Moment;

#define MOMENTS_NUM  6                      // максимальное количество отслеживаемых событий для строк, содержащих макрос {P}
Moment   moments[MOMENTS_NUM];              // массив с моментами времени событий для бегущих строк с макросом {P}
bool     moment_active = false;             // В текущий момент происходит отображение строки, привязанной к отслеживаемому событию.
int8_t   momentIdx = -1;                    // Индекс строки в массиве moments для активного текущего непрерывно отслеживаемого события
int8_t   momentTextIdx = -1;                // Индекс строки в массиве textLines для активного текущего непрерывно отслеживаемого события
uint32_t textCheckTime = 0;                 // Время контроля списка событий непрерывного отслеживания

// *************************** ПОДКЛЮЧЕНИЕ К СЕТИ **************************

WiFiUDP  udp;                               // Объект транспорта сетевых пакетов
                                            // к длине +1 байт на \0 - терминальный символ. Это буферы для загрузки имен/пароля из EEPROM. Значения задаются в define выше
char     apName[11] = DEFAULT_AP_NAME;      // Имя сети в режиме точки доступа
char     apPass[17] = DEFAULT_AP_PASS;      // Пароль подключения к точке доступа
uint8_t  IP_STA[]   = DEFAULT_IP;           // Статический адрес в локальной сети WiFi по умолчанию при первом запуске. Потом - загружается из настроек, сохраненных в EEPROM
String   ssid       = NETWORK_SSID;         // SSID (имя) вашего роутера (конфигурируется подключением через точку доступа и сохранением в файловой системе микроконтроллера)
String   pass       = NETWORK_PASS;         // пароль роутера

uint16_t localPort  = DEFAULT_PORT;         // локальный порт на котором слушаются входящие команды управления от приложения на смартфоне, передаваемые через локальную сеть

// ------------------------ MQTT parameters --------------------

#if (USE_MQTT == 1)

WiFiClient m_client;                             // Объект для работы с удалёнными хостами - соединение с MQTT-сервером
PubSubClient mqtt(m_client);                     // Объект соединения с MQTT сервером

String mqtt_client_name = "";                    // Имя для регистрации клиента на сервере MQTT

// Код работы с MQTT-каналом ориентирован на использование MQTT-брокера mqtt.by

#define  TOPIC_CMD      "cmd"                    // Топик - получение команды управления от клиента
#define  TOPIC_DTA      "dta"                    // Топик - отправка запрошенных данных клиенту
#define  TOPIC_ERR      "err"                    // Топик - отправка уведомлений об ошибке клиенту
#define  TOPIC_STA      "sta"                    // Топик - отправка уведомления о (ре)старте микроконтроллера
#define  TOPIC_ALM      "alm"                    // Топик - отправка клиенту сообщений о событиях будильника
#define  TOPIC_AMD      "amd"                    // Топик - отправка клиенту сообщений о событиях авторежимов по времени
#define  TOPIC_WTR      "wtr"                    // Топик - отправка клиенту сообщений о событиях погоды
#define  TOPIC_TME      "tme"                    // Топик - отправка клиенту сообщений о событиях времени
#define  TOPIC_PWR      "pwr"                    // Топик - отправка клиенту сообщений о включении/выключении устройства
#define  TOPIC_SDC      "sdc"                    // Топик - отправка клиенту сообщений о событиях SD-карты
#define  TOPIC_TXT      "txt"                    // Топик - отправка клиенту сообщений о событиях бегущей строки
#define  TOPIC_STT      "stt"                    // Топик - отправка клиенту сообщений о текущем статусе параметров устройства - основной набор параметров (пакет)
#define  TOPIC_E131     "e131"                   // Топик - отправка клиенту сообщений о событиях передачи данных по протоколу E131

#define  MQTT_CONNECT_TIMEOUT 1000               // Таймаут попытки установить соединение с MQTT сервером
#define  MQTT_RECONNECT_PERIOD 300000            // Интервал между попытками соединения с MQTT сервером, если соединение не установилось из-за неответа сервера по таймауту

bool     useMQTT = true;                         // Использовать канал управления через MQTT - флаг намерения    // При отключении из приложения set_useMQTT(false) устанавливается соответствующее состояние (параметр QA), состояние 'намерение отключить MQTT'
bool     stopMQTT = false;                       // Использовать канал управления через MQTT - флаг результата   // которое должно быть отправлено на MQTT-сервер, значит реально состояние 'MQTT остановлен' - только после отправки флага QA на сервер
char     mqtt_server[25] = "";                   // Имя сервера MQTT
char     mqtt_user[15]   = "";                   // Логин от сервера
char     mqtt_pass[15]   = "";                   // Пароль от сервера
char     mqtt_prefix[31] = "";                   // Префикс топика сообщения
uint16_t mqtt_port       = DEFAULT_MQTT_PORT;    // Порт для подключения к серверу MQTT

// Выделение места под массив команд, поступающих от MQTT-сервера
// Callback на поступление команды от MQTT сервера происходит асинхронно, и если предыдущая
// команда еще не обработалась - происходит новый вызов обработчика команд, который не реентерабелен -
// это приводит к краху приложения. Чтобы избежать этого поступающие команды будем складывать в очередь 
// и выполнять их в основном цикле программы
#define  QSIZE_IN 8                         // размер очереди команд от MQTT
#define  QSIZE_OUT 96                       // размер очереди исходящих сообщений MQTT
String   cmdQueue[QSIZE_IN];                // Кольцевой буфер очереди полученных команд от MQTT
String   tpcQueue[QSIZE_OUT];               // Кольцевой буфер очереди отправки команд в MQTT (topic)
String   outQueue[QSIZE_OUT];               // Кольцевой буфер очереди отправки команд в MQTT (message)
bool     rtnQueue[QSIZE_OUT];               // Кольцевой буфер очереди отправки команд в MQTT (retain)
uint8_t  queueWriteIdx = 0;                 // позиция записи в очередь обработки полученных команд
uint8_t  queueReadIdx = 0;                  // позиция чтения из очереди обработки полученных команд
uint8_t  queueLength = 0;                   // количество команд в очереди обработки полученных команд
uint8_t  outQueueWriteIdx = 0;              // позиция записи в очередь отправки MQTT сообщений
uint8_t  outQueueReadIdx = 0;               // позиция чтения из очереди отправки MQTT сообщений
uint8_t  outQueueLength = 0;                // количество команд в очереди отправки MQTT сообщений

String   last_mqtt_server = "";
uint16_t last_mqtt_port = 0;

String   changed_keys = "";                 // Строка, содержащая список измененных параметров, чье состояние требуется отправить серверу
bool     mqtt_connecting = false;           // Выполняется подключение к MQTT (еще не установлено)
bool     mqtt_topic_subscribed = false;     // Подписка на топик команд выполнена
uint8_t  mqtt_conn_cnt = 0;                 // Счетчик попыток подключения для форматирования вывода
uint32_t mqtt_conn_last;                    // Время последней попытки подключения к MQTT-серверу
uint32_t uptime_send_last;                  // Время последней отправки uptime к MQTT-серверу по инициативе устройства
uint32_t nextMqttConnectTime;               // При недоступности MQTT сервера определяет время следующей попытки соединения
#endif

// ---------------------------------------------------------------

bool     useSoftAP = false;                 // использовать режим точки доступа
bool     wifi_connected = false;            // true - подключение к wifi сети выполнена  
bool     ap_connected = false;              // true - работаем в режиме точки доступа;
bool     wifi_print_ip = false;             // Включен режим отображения текущего IP на индикаторе TM1637
bool     wifi_print_ip_text = false;        // Включен режим отображения текущего IP на матрице
uint8_t  wifi_print_idx = 0;                // Индекс отображаемой на индикаторе TM1637 тетрады IP адреса
String   wifi_current_ip = "";              // Отображаемый в бегущей строке IP адрес лампы

// **************** СИНХРОНИЗАЦИЯ ЧАСОВ ЧЕРЕЗ ИНТЕРНЕТ *******************

IPAddress timeServerIP;
#define  NTP_PACKET_SIZE 48                 // NTP время - в первых 48 байтах сообщения
uint16_t SYNC_TIME_PERIOD = 60;             // Период синхронизации в минутах по умолчанию
uint8_t  packetBuffer[NTP_PACKET_SIZE];     // буфер для хранения входящих и исходящих пакетов NTP

int8_t   timeZoneOffset = TIME_ZONE;        // смещение часового пояса от UTC
uint32_t ntp_t = 0;                         // Время, прошедшее с запроса данных с NTP-сервера (таймаут)
uint8_t  ntp_cnt = 0;                       // Счетчик попыток получить данные от сервера
bool     init_time = false;                 // Флаг false - время не инициализировано; true - время инициализировано
bool     refresh_time = true;               // Флаг true - пришло время выполнить синхронизацию часов с сервером NTP
bool     useNtp = true;                     // Использовать синхронизацию времени с NTP-сервером
bool     getNtpInProgress = false;          // Запрос времени с NTP сервера в процессе выполнения
char     ntpServerName[31] = "";            // Используемый сервер NTP

// *************************** Yandex.Погода *****************************

WiFiClient w_client;                        // Объект для работы с удалёнными хостами - соединение с сервером погоды
bool     init_weather = false;              // Флаг: true - погода получена; false - погода не получена / не актуальна

#if (USE_WEATHER == 1)
uint8_t  useWeather = 1;                    // Использовать получение текущей погоды с погодного сервера 0 - не использовать; 1 - Yandex; 2 - OpenWeatherMap
uint32_t regionID = WEATHER_REGION_YDX;     // Код региона по Yandex
uint32_t regionID2 = WEATHER_REGION_OWTH;   // Код региона по OpenWeatherMap
String   skyColor;                          // Рекомендованный цвет фона погоды
String   weather;                           // Состояние - "Ясно", "Облачно", "Дождь" и т.д.
String   dayTime;                           // "Темное время суток" / "Светлое время суток"
bool     isNight;                           // день / ночь
int8_t   temperature;                       // Текущая температура 
int16_t  weather_code;                      // код погодных условий для OpenWeatherMap

String   icon;                              // код иконки, содержит зашифрованный статус погодных условий 
uint32_t weather_t = 0;                     // Время, прошедшее с запроса данных с сервера погоды (таймаут)
uint8_t  weather_cnt = 0;                   // Счетчик попыток получить данные от сервера
uint32_t weather_time;                      // Время последнего получения погоды;
uint8_t  weatherActualityDuration = 2;      // Какой период времени в часах после получения погоды считать ее актуальной (на случай, если сервер перестал отвечать)
uint16_t SYNC_WEATHER_PERIOD = 15;          // Период обновления информации о текущей погоде в минутах по умолчанию
uint8_t  refresh_weather = true;            // Флаг: пришло время очередного получения погоды с сервера
bool     getWeatherInProgress = false;      // Запрос погоды сервера в процессе выполнения
bool     weather_ok = true;                 // Погода получена

// API-идентификатор сервиса получения погоды - смотрите раздел Wiki - Настройка получения информации о погоде
// https://github.com/vvip-68/GyverPanelWiFi/wiki/Настройка-получения-информации-о-погоде
#ifndef  WEATHER_API_KEY
#define  WEATHER_API_KEY "6a4ba421859c9f4166697758b68d889b"
#endif
#endif

bool     useTemperatureColor = true;        // Для дневных часов: true - выводить температуру специальным цветом, в зависимости от значения температуры; 0 - не использовать градации цвета
bool     useTemperatureColorNight = false;  // Для ночных часов:  true - выводить температуру специальным цветом, в зависимости от значения температуры; 0 - не использовать градации цвета

#define  cold_less_30 "#3300FF"             // -39..-30
#define  cold_29_20   "#0000FF"             // -29..-20
#define  cold_19_10   "#0077FF"             // -19..-10
#define  cold_9_4     "#00FFFF"             //  -9..-4
#define  zero_3_3     "#808080"             //  -3..+3
#define  hot_4_9      "#AA5511"             //  +4..+9
#define  hot_10_19    "#773300"             // +10..+19
#define  hot_20_29    "#A01F03"             // +20..+29
#define  hot_30_great "#FF0000"             // +30..+39

// *********************** ДЛЯ БУДИЛЬНИКА-РАССВЕТ ************************

bool     isAlarming = false;                // Сработал будильник "рассвет"
bool     isPlayAlarmSound = false;          // Сработал настоящий будильник - играется звук будильника
bool     isAlarmStopped = false;            // Сработавший будильник "рассвет" остановлен пользователем

uint8_t  alarmWeekDay = 0;                  // Битовая маска дней недели будильника
uint8_t  alarmDuration = 1;                 // Проигрывать звук будильнике N минут после срабатывания (по окончанию рассвета)

uint8_t  alarmHour[7]   = {0,0,0,0,0,0,0};  // Часы времени срабатывания будильника по дням недели
uint8_t  alarmMinute[7] = {0,0,0,0,0,0,0};  // Минуты времени срабатывания будильника по дням недели

int8_t   dawnHour = 0;                      // Часы времени начала рассвета
int8_t   dawnMinute = 0;                    // Минуты времени начала рассвета
uint8_t  dawnWeekDay = 0;                   // День недели времени начала рассвета (0 - выключено, 1..7 - пн..вс)
uint8_t  dawnDuration = 10;                 // Продолжительность "рассвета" по настройкам в минутах
uint8_t  realDawnDuration = 0;              // Продолжительность "рассвета" по вычисленному времени срабатывания будильника
uint8_t  alarmEffect = MC_DAWN_ALARM;       // Какой эффект используется для будильника "рассвет". Могут быть обычные эффекты - их яркость просто будет постепенно увеличиваться

// **************************** MP3 ПЛЕЕР *****************************

bool isDfPlayerOk = false;                  // MP3-Player корректно инициализирован и готов к использованию

#if (USE_MP3 == 1)

class    Mp3Notify;

#if defined(ESP32)

#if (DFPLAYER_TYPE == 0)
typedef  DFMiniMp3<HardwareSerial, Mp3Notify, Mp3ChipOriginal> DfMp3;
#endif
#if (DFPLAYER_TYPE == 1)
typedef  DFMiniMp3<HardwareSerial, Mp3Notify, Mp3ChipMH2024K16SS> DfMp3;
#endif
#define mp3Serial Serial2

#else

#if (DFPLAYER_TYPE == 0)
typedef  DFMiniMp3<SoftwareSerial, Mp3Notify, Mp3ChipOriginal> DfMp3;
#endif
#if (DFPLAYER_TYPE == 1)
typedef  DFMiniMp3<SoftwareSerial, Mp3Notify, Mp3ChipMH2024K16SS> DfMp3;
#endif
SoftwareSerial mp3Serial;

#endif


DfMp3    dfPlayer(mp3Serial);

int16_t  alarmSoundsCount = 0;              // Кол-во файлов звуков в папке '01' на SD-карте
int16_t  dawnSoundsCount = 0;               // Кол-во файлов звуков в папке '02' на SD-карте
int16_t  noteSoundsCount = 0;               // Кол-во файлов звуков в папке '03' на SD-карте
uint8_t  soundFolder = 0;                   // Текущая используемая папка со звуками 
uint8_t  soundFile = 0;                     // Текущий используемый файл звука в папке
int8_t   fadeSoundDirection = 1;            // направление изменения громкости звука: 1 - увеличение; -1 - уменьшение
uint8_t  fadeSoundStepCounter = 0;          // счетчик шагов изменения громкости, которое осталось сделать
bool     useAlarmSound = false;             // Использовать звуки в будильнике
int8_t   alarmSound = 0;                    // Звук будильника - номер файла в папке SD:/01 [-1 не использовать; 0 - случайный; 1..N] номер файла
int8_t   dawnSound = 0;                     // Звук рассвета   - номер файла в папке SD:/02 [-1 не использовать; 0 - случайный; 1..N] номер файла
int8_t   runTextSound = -1;                 // Номер звука в папке SD://03 на sd-карте, который воспроизводится вместе с отображением бегущей строки
bool     runTextSoundRepeat = false;        // Флаг - повторять в цикле: false - играть один раз; true - зацикливать
uint8_t  maxAlarmVolume = 30;               // Максимальная громкость будильника (1..30)
#endif

// ************************* КНОПКА УПРАВЛЕНИЯ *************************

#if (BUTTON_TYPE == 0)
  GButton butt(PIN_BTN, LOW_PULL, NORM_OPEN);    // Для сенсорной кнопки
#else
  GButton butt(PIN_BTN, HIGH_PULL, NORM_OPEN);   // Для обычной кнопки
#endif

bool     isButtonHold = false;              // Кнопка нажата и удерживается
uint8_t  bCounter = 0;                      // Счетчик нажатия на кнопку

// ************************** Дисплей TM1637 **************************

#if (USE_TM1637 == 1)
TM1637Display display(CLK, DIO);
bool     aDirection = true;                 // Направление счетчика изменения яркости, отображаемом на индикаторе при регулировке
uint8_t  aCounter = 0;                      // Текущая яркость индикатора при плавном изменении яркости
uint32_t fade_time;                         // Для плавного изменения яркости индикатора при сработавшем будильнике
#endif

// ******************** ВКЛЮЧЕНИЕ РЕЖИМОВ ПО ВРЕМЕНИ *********************

bool     AM1_running = false;               // Режим 1 по времени - работает
uint8_t  AM1_hour = 0;                      // Режим 1 по времени - часы
uint8_t  AM1_minute = 0;                    // Режим 1 по времени - минуты
int8_t   AM1_effect_id = -3;                // Режим 1 по времени - ID эффекта или -3 - выключено (не используется); -2 - выключить матрицу (черный экран); -1 - ночные часы, 0 - случайный, 1 и далее - эффект EFFECT_LIST
bool     AM2_running = false;               // Режим 2 по времени - работает
uint8_t  AM2_hour = 0;                      // Режим 2 по времени - часы
uint8_t  AM2_minute = 0;                    // Режим 2 по времени - минуты
int8_t   AM2_effect_id = -3;                // Режим 2 по времени - ID эффекта или -3 - выключено (не используется); -2 - выключить матрицу (черный экран); -1 - ночные часы, 0 - случайный, 1 и далее - эффект EFFECT_LIST
bool     AM3_running = false;               // Режим 3 по времени - работает
uint8_t  AM3_hour = 0;                      // Режим 3 по времени - часы
uint8_t  AM3_minute = 0;                    // Режим 3 по времени - минуты
int8_t   AM3_effect_id = -3;                // Режим 3 по времени - ID эффекта или -3 - выключено (не используется); -2 - выключить матрицу (черный экран); -1 - ночные часы, 0 - случайный, 1 и далее - эффект EFFECT_LIST
bool     AM4_running = false;               // Режим 4 по времени - работает
uint8_t  AM4_hour = 0;                      // Режим 4 по времени - часы
uint8_t  AM4_minute = 0;                    // Режим 4 по времени - минуты
int8_t   AM4_effect_id = -3;                // Режим 4 по времени - ID эффекта или -3 - выключено (не используется); -2 - выключить матрицу (черный экран); -1 - ночные часы, 0 - случайный, 1 и далее - эффект EFFECT_LIST
bool     dawn_running = false;              // Режим по времени "Рассвет" - работает
uint8_t  dawn_hour = 0;                     // Режим по времени "Рассвет" - часы
uint8_t  dawn_minute = 0;                   // Режим по времени "Рассвет" - минуты
int8_t   dawn_effect_id = -3;               // Режим по времени "Рассвет" - ID эффекта или -3 - выключено (не используется); -2 - выключить матрицу (черный экран); -1 - ночные часы, 0 - случайный, 1 и далее - эффект EFFECT_LIST
bool     dusk_running = false;              // Режим по времени "Закат" - работает
uint8_t  dusk_hour = 0;                     // Режим по времени "Закат" - часы
uint8_t  dusk_minute = 0;                   // Режим по времени "Закат" - минуты
int8_t   dusk_effect_id = -3;               // Режим по времени "Закат" - ID эффекта или -3 - выключено (не используется); -2 - выключить матрицу (черный экран); -1 - ночные часы, 0 - случайный, 1 и далее - эффект EFFECT_LIST

// ************************* РАБОТА С SD КАРТОЙ *************************

#if (USE_SD == 1)

#define  MAX_FILES 126                      //  больше нельзя - переполнение индекса массива для типа int8_t -128..127; Значение 127 и < 0 - специальные значения для перехода по циклу эффектов

String   nameFiles[MAX_FILES];              // Список имен файлов, найденных в папке на SD-карте
bool     isSdCardReady = false;             // Флаг - SD карта инициализирована корректно
bool     play_file_finished = false;        // Флаг - воспроизведение эффекта завершено
bool     wait_play_finished = (WAIT_PLAY_FINISHED == 1); // Флаг - true - переключаться на следующий эффект только когда весь файл показан; false - прерывать показ по истечении времени эффекта
uint8_t  countFiles = 0;                    // Количество найденных файлов эффектов, найденных на SD-карте
int8_t   sf_file_idx = -1;                  // Эффект, проигрываемый с SD-карты - индекс в массиве nameFiles

#endif


// ************************* ПРОЧИЕ ПЕРЕМЕННЫЕ *************************

bool     repeat_play = (REPEAT_PLAY == 1);  // Флаг - true - повторное проигрывание текущего эффекта, если время не вышло; false - перейти к следующему эффекту после однократного проигрывания текущего (игры, SD-карта)
uint8_t  fadeMode = 4;                      // Фаза режима плавной смены эффекта
bool     modeDir;                           // Направление перехода к следующему / предыдущему режиму

// ---------------------------------------------------------------

#define  D_GAME_SPEED        150            // скорость игр по умолчанию (мс)
#define  D_EFFECT_SPEED       80            // скорость эффектов по умолчанию (мс)
#define  D_EFFECT_SPEED_MIN    0            // пределы регулировки скорости
#define  D_EFFECT_SPEED_MAX  255

// ---------------------------------------------------------------

#define  AUTOPLAY_PERIOD      60            // время между авто сменой режимов (секунды)
#define  IDLE_TIME            30            // время бездействия (в минутах, по умолчанию) после которого запускается автосмена режимов, если разрешена в настройках
#define  SMOOTH_CHANGE         0            // плавная смена режимов через чёрный

// ---------------------------------------------------------------

bool     eepromModified = false;            // флаг: EEPROM изменен, требует сохранения

// ---------------------------------------------------------------

uint32_t globalColor      = 0xffffff;       // цвет панели в режиме "Лампа" по умолчанию
uint32_t globalClockColor = 0xffffff;       // цвет часов в режиме MC_COLOR - режим цвета "Монохром" по умолчанию
uint32_t globalTextColor  = 0xffffff;       // цвет бегущей строки MC_TEXT в режиме цвета "Монохром" по умолчанию

uint32_t drawColor        = 0xffffff;       // цвет рисования
bool     sendImageFlag    = false;          // флаг: режим отправки изображения с матрицы в приложение на телефоне
uint8_t  sendImageRow     = 0;              // Индекс отправляемого изображения (по строкам)
uint8_t  sendImageCol     = 0;              // Индекс отправляемого изображения (по колонкам)
eSources sendImageSrc     = NONE;           // Источник запроса изображения

// ---------------------------------------------------------------

bool     specialMode = false;               // Спец.режим, включенный вручную со смартфона или с кнопок быстрого включения режима
bool     specialClock = false;              // Отображение часов в спец.режиме - как отдельный эффект "Часы" на черном фоне, а не как оверлей поверх других эффектов
uint8_t  specialBrightness = 1;             // Яркость в спец.режиме

bool     isTurnedOff = false;               // Включен черный экран (т.е. всё выключено)
bool     isNightClock = false;              // Включен режим ночных часов

int8_t   specialModeId = -1;                // Номер текущего спецрежима
int8_t   manualModeId = -1;                 // Номер текущего режима

bool     useRandomSequence = true;          // Флаг: случайный выбор режима
bool     clockOverlayEnabled = true;        // Доступность оверлея часов поверх эффектов
bool     textOverlayEnabled = true;         // Доступность оверлея бегущей строки поверх эффектов
bool     overlayDelayed = false;            // используется для указания был ли оверлей на предыдущем цикле отрисовки
int8_t   y_overlay_low  = 0;                // Вычисленная позиция оверлея матрицы - во всю ширину, область отображения текста бегущей строки / часов / календаря
int8_t   y_overlay_high = 0;

int8_t   debug_hours = -1;                  // Для отладки позиционирования часов
int8_t   debug_mins = -1;                   // Для отладки позиционирования часов
int8_t   debug_temperature;                 // Для отладки позиционирования часов

// ---------------------------------------------------------------

// Сервер не может инициировать отправку сообщения клиенту - только в ответ на запрос клиента
// Следующие две переменные хранят сообщения, формируемые по инициативе сервера и отправляются в ответ на ближайший запрос от клиента,
// например в ответ на периодический ping - в команде sendAcknowledge();

String   cmd95 = "";                        // Строка, формируемая sendPageParams(95) для отправки по инициативе сервера
String   cmd96 = "";                        // Строка, формируемая sendPageParams(96) для отправки по инициативе сервера

// ---------------------------------------------------------------

uint8_t  globalBrightness = BRIGHTNESS;     // текущая яркость бегущей строки и часов (общая)
uint8_t  contrast = 255;                    // контрастность эффектов по отношению к яркости часов / бегущей строки (общей яркости) => 0.255 -> 10..100%
bool     brightDirection = false;           // true - увеличение яркости; false - уменьшение яркости при регулировке кнопкой

// ---------------------------------------------------------------

uint8_t  effectScaleParam[MAX_EFFECT];      // Динамический параметр эффекта, параметр #1
uint8_t  effectScaleParam2[MAX_EFFECT];     // Динамический параметр эффекта, параметр #2
uint8_t  effectContrast[MAX_EFFECT];        // Динамический параметр эффекта - контрастность (яркость эффекта относительно яркости часов или текста бегущей строки)
uint8_t  effectSpeed[MAX_EFFECT];           // Динамический параметр эффекта - скорость
bool     manualMode = false;                // флаг: true - ручное управление эффектами; false - в режиме Autoplay
bool     loadingFlag = true;                // флаг: инициализация параметров эффекта

// ---------------------------------------------------------------

uint32_t idleTime = ((uint32_t)IDLE_TIME * 60 * 1000UL);  // минуты -> миллисекунды
uint32_t autoplayTime = ((uint32_t)AUTOPLAY_PERIOD * 1000UL); // секунды -> миллисекунды
uint32_t autoplayTimer;                                   // время до автоматического перехода в демо-режим
uint32_t upTime = 0;                                      // время работы системы с последней перезагрузки

// ---------------------------------------------------------------

int8_t   thisMode = 0;                      // текущий режим - id
String   effect_name = "";                  // текущий режим - название

// ---------------------------------------------------------------

#define  GLOBAL_COLOR_1 CRGB::Green         // основной цвет №1 для игр
#define  GLOBAL_COLOR_2 CRGB::Orange        // основной цвет №2 для игр
#define  SCORE_SIZE 1                       // размер символов счёта в игре. 0 - маленький для 8х8 (шрифт 3х5), 1 - большой (шрифт 5х7)

bool     gameDemo = true;                   // Игры Тетрис, Лабиринт, Змейка работают в демо-режиме
bool     gamePaused = false;                // Игра на паузе (при ручном управлении игрой) 
uint8_t  buttons;                           // Эмуляция кнопок управления играми в демо-режиме

// ---------------------------------------------------------------

timerMinim gameTimer(D_GAME_SPEED);         // Таймер скорости игр
timerMinim effectTimer(D_EFFECT_SPEED);     // Таймер скорости эффекта (шага выполнения эффекта)
timerMinim clockTimer(D_CLOCK_SPEED);       // Таймер смещения оверлея часов
timerMinim textTimer(D_TEXT_SPEED);         // Таймер смещения оверлея часов
timerMinim changeTimer(70);                 // Таймер шага плавной смены режима - Fade
timerMinim halfsecTimer(500);               // Полусекундный таймер точек часов
timerMinim idleTimer(idleTime);             // Таймер бездействия ручного управления для автоперехода в демо-режим 
timerMinim dawnTimer(1000);                 // Таймер шага рассвета для будильника "рассвет" 
timerMinim alarmSoundTimer(4294967295);     // Таймер выключения звука будильника
timerMinim fadeSoundTimer(4294967295);      // Таймер плавного включения / выключения звука
timerMinim saveSettingsTimer(15000);        // Таймер отложенного сохранения настроек
timerMinim ntpSyncTimer(1000 * 60 * SYNC_TIME_PERIOD);    // Сверяем время с NTP-сервером через SYNC_TIME_PERIOD минут
#if (USE_WEATHER == 1)
timerMinim weatherTimer(1000 * 60 * SYNC_WEATHER_PERIOD); // Получаем текущую погоду каждые SYNC_WEATHER_PERIOD минут
#endif

// ********************* ДЛЯ ПАРСЕРА КОМАНДНЫХ ПАКЕТОВ *************************

#define    BUF_MAX_SIZE  4096               // максимальный размер выделяемого буфера для коммуникации по UDP каналу
#define    PARSE_AMOUNT  16                 // максимальное количество значений в массиве, который хотим получить
#define    header '$'                       // стартовый символ управляющей посылки
#define    divider ' '                      // разделительный символ
#define    ending ';'                       // завершающий символ
 
int32_t    intData[PARSE_AMOUNT];           // массив численных значений после парсинга - для WiFi часы время синхронизации может быть отрицательным +
                                            // период синхронизации может быть больше 255 мин - нужен тип int32_t
char       incomeBuffer[BUF_MAX_SIZE];      // Буфер для приема строки команды из wifi udp сокета; также используется для загрузки строк из EEPROM
char       replyBuffer[8];                  // ответ клиенту - подтверждения получения команды: "ack;/r/n/0"

uint8_t    ackCounter = 0;                  // счетчик отправляемых ответов для создания уникальности номера ответа

#if (USE_MQTT == 1)
#define    BUF_MQTT_SIZE  384               // максимальный размер выделяемого буфера для входящих сообщений по MQTT каналу
char       incomeMqttBuffer[BUF_MQTT_SIZE]; // Буфер для приема строки команды из MQTT
#endif

// **************** ДЛЯ СИНХРОННОЙ РАБОТЫ УСТРОЙСТВ И ПРИЕМА ПОТОКА E1.31 ******************

#if (USE_E131 == 1)

ESPAsyncE131 *e131;                         // Объект приемника данных по протоколу E1.31 в UDP пакетах по WiFi
e131_packet_t e131_packet;                  // Пакет данных формата E1.31

// Режимы работы панели - Самостоятельный без поддержки синхронизации, Мастер (ведущий), Слушатель (подчиненный)
enum  eWorkModes   {
  STANDALONE, 
  MASTER, 
  SLAVE
} workMode, prevWorkMode; 

// Режим синхронизации
enum  eSyncModes   { // Данные в E1.31 пакете  
  PHYSIC,            // соответствуют физическому порядку следования диодов в цепочке соединения матрицы (самый быстрый)
  LOGIC,             // соответствуют логическому порядку, начиная с левого верхнего угла X,Y=0,0, далее вправо по X? затем вниз по Y (медленный, но позволяет правильно работать устройствам с разным типом подключения и размером матриц)  
  COMMAND            // пакет не содержит данных RGB светодиодов. Мастер отправляет ведомым команды на включение того или иного режима, которые ведомые устройства обрабатывают самостоятельно.
} syncMode, prevSyncMode;

#define    MAX_UNIVERCE_COUNT 7             // Сколько вселенных выделено на одну группу 7 * 170 = 1190 диодов в группе
uint16_t   START_UNIVERSE = 1;              // Номер начальной вселенной для прослушивания E1.31 (DMX) пакетов
uint8_t    UNIVERSE_COUNT = 2;              // Общее количество прослушиваемых вселенных; Одна вселенная - 170 диодов. Для матрицы 16x16 достаточно двух вселенных
uint16_t   END_UNIVERSE = 2;                // Номер конечной вселенной

uint8_t    syncGroup = 0, prevSyncGroup;    // Номер группы синхронизации (одна группа может содержать до 7 вселенных (по протоколу управления E1.31 DMX MULTICAST), т.е для Группы 0 - 1..7 вселенные, для Группы 1 - 8..14 вселенные и т.д.
uint32_t   e131_last_packet;                // Время поступления последнего E1.31 пакета.
uint8_t    e131_send_delay = 0;             // Задержка миллисекунд после отправки данных мастером в канал E1.31 и до отображения сформированных данных на стороне мастера на матрицу - для визуальной синхронизации панелей MASTER-SLAVE
uint8_t    e131_cid[16];                    // Идентификатор устройства для отправки пакета мастером по протоколу E1.31

uint16_t   frameCnt = 0;                    // Количество принятых фреймов по потоку E1.31 за интервал времени
uint32_t   last_fps_time = 0;               // Последнее время отображения / расчета FPS потока
                             
                                            // В режиме синхронизации SLAVE / COMMAND
uint8_t    syncEffectSpeed;                 // Скорость эффекта
uint8_t    syncEffectContrast;              // Контраст эффекта 
uint8_t    syncEffectParam1;                // Параметр эффекта 1
uint8_t    syncEffectParam2;                // Параметр эффекта 2

uint8_t    masterWidth;                     // ширина матрицы MASTER устройства при получении пакета синхронизации
uint8_t    masterHeight;                    // высота матрицы MASTER устройства при получении пакета синхронизации

#define    E131_FPS_INTERVAL  10000         // Отображать FPS получаемого потока каждые 10000 мс (10 сек); Установите 0  если отображать не нужно;
#define    E131_TIMEOUT  2000               // Таймаут в миллисекундах получения потока E1.31. Если данных нет - переключаемся на самостоятельную работу

#endif

bool       streaming = false;               // В настоящее время идет передача (MASTER) или прием (SLAVE) пакетов E1.31
bool       e131_streaming = false;          // Устройство работает в режиме отправки E1.31 пакетов (MASTER) или происходит прием E1.31 пакетов (SLAVE)
bool       e131_wait_command = false;       // Устройство работает в режиме прием E1.31 пакетов команд (SLAVE, COMMAND)

// --------------- ВРЕМЕННЫЕ ПЕРЕМЕННЫЕ ПАРСЕРА ------------------

uint32_t   prevColor;
bool       recievedFlag;                         // буфер содержит принятые данные
bool       parseStarted;
uint8_t    parse_index;
String     string_convert = "";
String     receiveText = "";
bool       haveIncomeData = false;
char       incomingByte;

int16_t    bufIdx = 0;                           // Могут приниматься пакеты > 255 байт - тип int16_t
int16_t    packetSize = 0;

// ************************* УПРАВЛЕНИЕ МАТРИЦЕЙ *******************************

String     host_name = "";                       // Имя для регистрации в сети
uint16_t   CURRENT_LIMIT = 10000;                // лимит по току в миллиамперах, автоматически управляет яркостью (пожалей свой блок питания!) 0 - выключить лимит

uint8_t    mapWIDTH = WIDTH;                     // полная ширина ширина матрицы 1..127 при использовании карты индексов
uint8_t    mapHEIGHT = HEIGHT;                   // полная высота матрицы 1..127 при использовании карты индексов

uint8_t    sWIDTH = WIDTH;                       // ширина одного сегмента матрицы 1..127
uint8_t    sHEIGHT = HEIGHT;                     // высота одного сегмента матрицы 1..127
uint8_t    sMATRIX_TYPE = MATRIX_TYPE;           // тип соединения диодов в сегменте матрицы: 0 - зигзаг, 1 - параллельная, 2 - карта индексов
uint8_t    sCONNECTION_ANGLE = CONNECTION_ANGLE; // угол подключения диодов в сегменте: 0 - левый нижний, 1 - левый верхний, 2 - правый верхний, 3 - правый нижний
uint8_t    sSTRIP_DIRECTION = STRIP_DIRECTION;   // направление ленты из угла сегмента: 0 - вправо, 1 - вверх, 2 - влево, 3 - вниз

uint8_t    mWIDTH = META_MATRIX_WIDTH;           // количество сегментов в ширину 1..9
uint8_t    mHEIGHT = META_MATRIX_HEIGHT;         // количество сегментов в высоту 1..9
uint8_t    mTYPE = META_MATRIX_TYPE;             // соединение сегментов мета-матрицы: 0 - зигзаг, 1 - параллельная
uint8_t    mANGLE = META_MATRIX_ANGLE;           // угол 1-го сегмента мета-матрицы: 0 - левый нижний, 1 - левый верхний, 2 - правый верхний, 3 - правый нижний
uint8_t    mDIRECTION = META_MATRIX_DIRECTION;   // направление следующих сегментов мета-матрицы из угла: 0 - вправо, 1 - вверх, 2 - влево, 3 - вниз

uint8_t    pWIDTH = sWIDTH * mWIDTH;
uint8_t    pHEIGHT = sHEIGHT * mHEIGHT;

uint32_t   NUM_LEDS = pWIDTH * pHEIGHT;
uint8_t    maxDim   = max(pWIDTH, pHEIGHT);
uint8_t    minDim   = min(pWIDTH, pHEIGHT);

#define    MAX_MAP_FILES 10                      // Один файл карты индексов - одно разрешение WxH матрицы
String     mapFiles[MAX_MAP_FILES];              // Список имен файлов карт индексов, найденных в корне файловой системы
uint8_t    mapListLen = 0;

#if (BIG_FONT == 0)
  // Шрифт размером 5x8
  uint32_t OVERLAY_SIZE = pHEIGHT < 17 ? pWIDTH * pHEIGHT : pWIDTH * 17;
#elif (BIG_FONT == 1)
  // Шрифт размером 10x16
  uint32_t OVERLAY_SIZE  =  pWIDTH * 21;            // высотв шрифта 16 + 3 строки диакритич символов над знакоместом и две - под знакоместом
#else
  // Шрифт размером 8x13
  uint32_t OVERLAY_SIZE =   pWIDTH * 18;            // высотв шрифта 13 + 3 строки диакритич символов над знакоместом и две - под знакоместом
#endif

// Часы могут отображаться: 
// - вертикальные при высоте матрицы >= 11 и ширине >= 7; 
// - горизонтальные при ширене матрицы >= 15 и высоте >= 5
//   минимальная ширина горизонтальных часов - 15 символов, т.к если большие часы не помещаются по ширине - автоматически используются маленькие часы
// Вычисление - в checkClockOrigin()
bool   allowVertical, allowHorizontal;

CRGB       *leds;                               // обращение к светодиодам матрицы через этот массив
CRGB       *overlayLEDs;                        // буфер оверлея; по максимуму - для бегущей строки во всю ширину матрицы или отображение календаря / вертикальных часов - 11 высота

#define    POWER_ON              HIGH           // Для включения питания матрицы (через MOSFET) подавать на пин POWER_PIN высокий уровень
#define    POWER_OFF             LOW            // Для вЫключения питания матрицы (через MOSFET) подавать на пин POWER_PIN низкий уровень

#define    TRUE_RANDOM
#define    DEBUG_MEM 0                          // Вкл/выкл вывод отладочных сообщений об объеме свободной памяти при смене эффектов

// *****************************************************************************
// *****************************************************************************
// *****************************************************************************
// *****************************************************************************
// *****************************************************************************

// ---------------- ДЛЯ ЭФФЕКТА "КАРТИНКИ" (СЛАЙДЫ) --------------
String     pictureStorage;                      // "SD" - на карте памяти или "FS" - в файловой системе контроллера или "" - из внктреннего эффекта "Погода"
uint8_t    pictureWidth;                        // Ширина картинок (слайдов)
uint8_t    pictureHeight;                       // Высота картинок (слайдов)
String     pictureList;                         // Строка имен файлов картинок, разделенных запятыми, если они найдены в файловой системе SD или FS
uint8_t    pictureIndexMax;                     // Количество найденных картинок, пригодных к использованию
uint8_t    pictureIndex;                        // Индекс в массиве pictureList - текущий слайд
uint8_t    weatherIndex;                        // Индекс анимации "Погода"

// ------------------ ФАЙЛОВАЯ СИСТЕМА SPIFFS --------------------

bool       spiffs_ok = false;                    // Флаг - файловая система SPIFFS доступна для использования
size_t     spiffs_total_bytes;                   // Доступно байт в SPIFFS
size_t     spiffs_used_bytes;                    // Использовано байт в SPIFFS
int8_t     eeprom_backup = 0;                    // Флаг - backup настроек 0 - нeт; 1 - FS; 2 - SD; 3 - FS и SD

// ------------------- ВРЕМЕННЫЕ ПЕРЕМЕННЫЕ ----------------------

bool       saveSpecialMode = specialMode;
int8_t     saveSpecialModeId = specialModeId;
uint8_t    saveMode;
uint8_t    tmpSaveMode = 0;
uint8_t    resourcesMode = 0;
bool       mandatoryStopText = false;

// ----------------- ОБЩИЕ ПЕРЕМЕННЫЕ ЭФФЕКТОВ -------------------

uint8_t    phase = 0;          // фаза эффекта
uint8_t    hue;
int16_t    loopCounter;
int16_t    loopCounter2;
uint32_t   lastMillis;
uint32_t   lastMillisX;
uint32_t   lastMillisY;

// ---------------------------------------------------------------

#define FOR_i(from, to) for(int i = (from); i < (to); i++)
#define FOR_j(from, to) for(int j = (from); j < (to); j++)
#define FOR_k(from, to) for(int k = (from); k < (to); k++)
#define FOR_n(from, to) for(int n = (from); n < (to); n++)
#define FOR_x(from, to) for(int x = (from); x < (to); x++)
#define FOR_y(from, to) for(int y = (from); y < (to); y++)

// ---------------------------------------------------------------
