mirror of
https://github.com/gunner47/GyverLamp.git
synced 2025-08-07 09:00:30 +03:00
Добавлен режим автоматического переключения избранных эффектов; Реорганизован код, исправлены ошибки
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -28,6 +28,11 @@ ScreenManager
|
||||
ShowScreen метод: принимает номер "экрана", устанавливает свойство видимости в true для этого экрана и в false для остальных экранов; вызывает перерисовку "экранов"
|
||||
RebuildAllScreens метод: перерисовывает "экраны", делает их блоки видимыми или видимыми в зависимости от выставленных свойств видимости
|
||||
CloseApplication метод: закрывает приложение
|
||||
Color_DarkGrey свойство (константа): код цвета "тёмно-серый"
|
||||
Color_LightGrey свойство (константа): код цвета "светло-серый"
|
||||
Color_LightLightGrey свойство (константа): код цвета "светло-светло-серый"
|
||||
Color_LightGreyBackground свойство (константа): код цвета "светло-серый" для фона
|
||||
Color_Red свойство (константа): код цвета "красный"
|
||||
|
||||
MainScreen
|
||||
Visible свойство: "Видимость главного экрана"
|
||||
@@ -60,7 +65,7 @@ ConnectivityScreen
|
||||
RemoveConnectionButtonClick метод (событие): действия при нажатии на кнопку "Удалить"
|
||||
SaveAllConnectionButtonClick метод (событие): действия при нажатии на кнопку "Сканировать и добавить"
|
||||
DeviceSocketListViewAfterPicking метод (событие): действия при выборе устройства из списка сохранённых и его подключение
|
||||
BaсkButtonClick метод (событие): действия при UI кнопки "Назад" на "экране" устройств
|
||||
BaсkButtonClick метод (событие): действия при нажатии кнопки "Назад" на "экране" устройств
|
||||
UpdateConnectivityStatus метод: обновить статус устройства "Подключен"/"Отключен" на "экране" устройств
|
||||
UpdateCurrentDeviceSocket метод: отобразить текущее устройство (DeviceManager.CurrentDeviceSocket) в полях ввода IP адреса и порта на "экране" устройств
|
||||
UpdateCurrentDevicesSockets метод: отобразить текущий список сохранённых устройств (DeviceManager.Devices) на "экране" устройств
|
||||
@@ -70,17 +75,31 @@ ConnectivityScreen
|
||||
ModesScreen
|
||||
Visible свойство: "Видимость экрана эффектов"
|
||||
ModesListViewAfterPicking метод (событие): действия при выборе эффекта из списка эффектов
|
||||
BaсkButtonClick метод (событие): действия при UI кнопки "Назад" на "экране" эффектов
|
||||
BaсkButtonClick метод (событие): действия при нажатии кнопки "Назад" на "экране" эффектов
|
||||
|
||||
FavoritesScreen
|
||||
Visible свойство: "Видимость экрана устройств"
|
||||
BaсkButtonClick метод (событие): действия при UI кнопки "Назад" на "экране" избранных эффектов
|
||||
Initialized свойство: "Экран автоматического переключения избранных эффектов инициализирован"
|
||||
BlockControlEventsEmitting свойство: "Не генерировать события от элементов управления" (переключателей, лист пикеров и т.д.)
|
||||
BaсkButtonClick метод (событие): действия при нажатии кнопки "Назад" на "экране" избранных эффектов
|
||||
OnOffSwitchChanged метод (событие): действия при установке/снятии переключателя включения режима избранных эффектов
|
||||
IntervalListPickerAfterPicking метод (событие): действия при выборе интервала времени (статической состявляющей) между автоматическими переключениями избранных эффектов
|
||||
DispersionListPickerAfterPicking метод (событие): действия при выборе разброса времени (случайной состявляющей) между автоматическими переключениями избранных эффектов
|
||||
ListViewIconClick метод (событие): действия при добавлении/удалении эффекта в/из список избранных
|
||||
ListViewelementTouchUp метод (событие): служебный метод, необходимый для корректной отрисовки фона элемента в списке избранных эффектов
|
||||
Initialize метод: настраивает и заполняющий специфический (кастомный) компонент ListViewer
|
||||
MarkListItemAsFavorite метод: устанавливает признак включения/исключения эффекта в избранные в свойстве "состояние режима избранных эффектов"
|
||||
UpdateScreen метод: обновить все элементы управления на "экране" избранных эффектов согласно модели данных
|
||||
UpdateControls метод: обновить отображение элементов управления (статусы enabled/disabled) в зависимости от сохранённых свойств (модели данных)
|
||||
SetFavoriteSettings метод: меняет состояние "экрана" избранных эффектов (устанавливает недоступность элементов управления и отображает анимацию) и вызывает DeviceManager.SetFavoriteSettings
|
||||
SetIntervalListPicker метод: иниицализирует значение поля выбора интервала времени автоматической смены избранных эффектов согласно модели данных
|
||||
SetDispersionListPicker метод: иниицализирует значение поля выбора разброса времени автоматической смены избранных эффектов согласно модели данных
|
||||
|
||||
AlarmScreen
|
||||
Visible свойство: "Видимость экрана устройств"
|
||||
Initialized свойство: "Экран управления будильниками инициализирован"
|
||||
BlockControlEventsEmitting свойство: "Не генерировать события от элементов управления" (переключателей, тайм пикеров и т.д.); нужно, например, чтобы предотвратить switch.changed при его enable/disable
|
||||
BaсkButtonClick метод (событие): действия при UI кнопки "Назад" на "экране" будильника
|
||||
BaсkButtonClick метод (событие): действия при нажатии кнопки "Назад" на "экране" будильника
|
||||
MonSwitchChanged метод (событие): действия при установке/снятии переключателя включения будильника
|
||||
TueSwitchChanged метод (событие): действия при установке/снятии переключателя включения будильника
|
||||
WedSwitchChanged метод (событие): действия при установке/снятии переключателя включения будильника
|
||||
@@ -106,6 +125,7 @@ TimerScreen
|
||||
AnimationTimerOn свойство: "таймер, управляющий анимацией функции таймера лампы, включен"
|
||||
RemainingSeconds свойство: "время до срабатывания таймера лампы" и одновремнно "время до окончания работы таймера, управляющего анимацией функции таймера лампы"
|
||||
TimerTickMs свойство: "шаг таймера, управляющего анимацией функции таймера лампы"
|
||||
BaсkButtonClick метод (событие): действия при нажатии кнопки "Назад" на "экране" таймера
|
||||
TimeListPickerAfterPicking метод (событие): действия при выборе пользователем времени до срабатывания таймера лампы (отправка команды модулю)
|
||||
UpdateControls метод: обновить отображение элементов управления (статусы enabled/disabled) в зависимости от сохранённых свойств (модели данных)
|
||||
UpdateScreen метод: обновить все элементы управления на "экране" управления таймером согласно модели данных
|
||||
@@ -114,13 +134,13 @@ TimerScreen
|
||||
HandleAnimation метод: обработать таймер, управляющий анимацией функции таймера лампы (запуск, если не запущен, принудительная остановка, если нужно)
|
||||
AnimationTimerTimerNow метод: действия при срабатывании события таймера, управляющий анимацией функции таймера лампы (показать/скрыть иконку, обновить текст оставшегося времени)
|
||||
StopAnimationTimer метод: остановить таймер, управляющий анимацией функции таймера лампы, сбросить его сохранённое состояние в исходное (модель данных)
|
||||
BaсkButtonClick метод (событие): действия при UI кнопки "Назад" на "экране" таймера
|
||||
|
||||
DeviceManager
|
||||
Connected свойство: подключено ли устройство (лампа)? точнее, был ли получен ответ на последнюю высланную ему команду
|
||||
State свойство: полученное командой GET состояние устройства (CURR...), список (не строка)
|
||||
Devices свойство: список устройств (хранимое свойство)
|
||||
CurrentDeviceSocket свойство: текущее устройство (хранимое свойство)
|
||||
FavoritesState свойство: текущее состояние избранных эффектов (вкл/выкл, интервал, разброс, список эффектов)
|
||||
AlarmState свойство: текущее состояние будильников по дням (вкл/выкл, время каждого, время срабатывания перед "рассветом")
|
||||
TimerState свойство: текущее состояние таймера (ответ команды TMR_GET)
|
||||
Modes свойство: список доступных режимов, захардкожен одновременно в лампе и в приложении, должен совпадать
|
||||
@@ -158,6 +178,14 @@ DeviceManager
|
||||
GetTimerTimeOption метод: получить выбранное пользователем значение предуст ановки времени таймера (из свойства TimerState)
|
||||
GetTimerRemainingSeconds метод: получить оставшееся до срабатывания таймера время (из свойства TimerState)
|
||||
SetTimerSettings метод: отправить команду установки таймера TMR_SET
|
||||
GetFavoriteSettings метод: отправить команду запроса состояния избранных эффектов FAV_GET на устройство
|
||||
SetFavoriteSettings метод: отправить команду установки состояния избранных эффектов FAV_SET
|
||||
GeFavoritesModeOnOff метод: получить состояние (включен/исключён в избранные) для заданного эффекта (из свойства FavoritesState)
|
||||
SeFavoritesModeOnOff метод: установить состояние (включен/исключён в избранные) для заданного эффекта (из свойства FavoritesState)
|
||||
GeFavoritesOnOff метод: получить состояние вкл/выкл режима избранных эффектов (из свойства FavoritesState)
|
||||
GeFavoritesInterval метод: получить интервал времени смены избранных эффектов (из свойства FavoritesState)
|
||||
GeFavoritesDispersion метод: получить разброс времени смены избранных эффектов (из свойства FavoritesState)
|
||||
GeFavoritesModesList метод: получить список эффектов с указанным состояние (включен/исключён в избранные) для каждого из них
|
||||
|
||||
DiscoverManager
|
||||
TimeoutMs свойство: время ожидания ответа от устройств на multicast команду DISCOVER
|
||||
|
239
firmware/GyverLamp_v1.4/EepromManager.h
Normal file
239
firmware/GyverLamp_v1.4/EepromManager.h
Normal file
@@ -0,0 +1,239 @@
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
* Схема использования EEPROM памяти:
|
||||
* Используются адреса:
|
||||
* Начало Длина Описание
|
||||
* массив alarm
|
||||
* 0 1 будильник пн вкл/выкл
|
||||
* 1-2 2 будильник пн время в минутах от начала суток (0 - 1440), должно быть 2 байта
|
||||
* 3 1 будильник вт вкл/выкл
|
||||
* 4-5 2 будильник вт время в минутах от начала суток (0 - 1440), должно быть 2 байта
|
||||
* 6 1 будильник ср вкл/выкл
|
||||
* 7-8 2 будильник ср время в минутах от начала суток (0 - 1440), должно быть 2 байта
|
||||
* 9 1 будильник чт вкл/выкл
|
||||
* 10-11 2 будильник чт время в минутах от начала суток (0 - 1440), должно быть 2 байта
|
||||
* 12 1 будильник пт вкл/выкл
|
||||
* 13-14 2 будильник пт время в минутах от начала суток (0 - 1440), должно быть 2 байта
|
||||
* 15 1 будильник сб вкл/выкл
|
||||
* 16-17 2 будильник сб время в минутах от начала суток (0 - 1440), должно быть 2 байта
|
||||
* 18 1 будильник вс вкл/выкл
|
||||
* 19-20 2 будильник вс время в минутах от начала суток (0 - 1440), должно быть 2 байта
|
||||
* массив modes
|
||||
* 21-23 3 режим №1: яркость, скорость, масштаб (по одному байту)
|
||||
* 24-26 3 режим №2: яркость, скорость, масштаб (по одному байту)
|
||||
* 27-29 3 режим №3: яркость, скорость, масштаб (по одному байту)
|
||||
* 30-32 3 режим №4: яркость, скорость, масштаб (по одному байту)
|
||||
* 33-35 3 режим №5: яркость, скорость, масштаб (по одному байту)
|
||||
* 36-38 3 режим №6: яркость, скорость, масштаб (по одному байту)
|
||||
* 39-41 3 режим №7: яркость, скорость, масштаб (по одному байту)
|
||||
* 42-44 3 режим №8: яркость, скорость, масштаб (по одному байту)
|
||||
* 45-47 3 режим №9: яркость, скорость, масштаб (по одному байту)
|
||||
* 48-50 3 режим №10: яркость, скорость, масштаб (по одному байту)
|
||||
* 51-53 3 режим №11: яркость, скорость, масштаб (по одному байту)
|
||||
* 54-56 3 режим №12: яркость, скорость, масштаб (по одному байту)
|
||||
* 57-59 3 режим №13: яркость, скорость, масштаб (по одному байту)
|
||||
* 60-62 3 режим №14: яркость, скорость, масштаб (по одному байту)
|
||||
* 63-65 3 режим №15: яркость, скорость, масштаб (по одному байту)
|
||||
* 66-68 3 режим №16: яркость, скорость, масштаб (по одному байту)
|
||||
* 69-71 3 режим №17: яркость, скорость, масштаб (по одному байту)
|
||||
* 72-74 3 режим №18: яркость, скорость, масштаб (по одному байту)
|
||||
* 75-77 3 режим №19: яркость, скорость, масштаб (по одному байту)
|
||||
* 78-80 3 режим №20: яркость, скорость, масштаб (по одному байту)
|
||||
*
|
||||
* 111-134 24 настройки режима избранных эффектов (интервал - 2 байта; разброс - 2 байта; вкл/выкл каждого эффекта - 20 (MODE_AMOUNT) байт; вкл/выкл не хранится в EEPROM)
|
||||
*
|
||||
* 198 1 признак первого запуска (определяет необходимость первоначальной записи всех хранимых настроек)
|
||||
* 199 1 время до "рассвета" (dawnMode)
|
||||
* 200 1 текущий режим (currentMode)
|
||||
*
|
||||
* Не используются адреса:
|
||||
* 81-110 30 резерв, можно добавить ещё 10 эффектов
|
||||
* 135-197 63 если добавить ещё 10 эффектов, начальный адрес неиспользуемой памяти сдвинется с 135 на 145
|
||||
*/
|
||||
|
||||
#include <EEPROM.h>
|
||||
#include "Types.h"
|
||||
|
||||
#define EEPROM_TOTAL_BYTES_USED (201U) // общий размер используемой EEPROM памяти (сумма всех хранимых настроек + 1 байт)
|
||||
#define EEPROM_ALARM_START_ADDRESS (0U) // начальный адрес в EEPROM памяти для записи настроек будильников
|
||||
#define EEPROM_MODES_START_ADDRESS (21U) // начальный адрес в EEPROM памяти для записи настроек эффектов (яркость, скорость, масштаб)
|
||||
#define EEPROM_FAVORITES_START_ADDRESS (111U) // начальный адрес в EEPROM памяти для записи настроек режима избранных эффектов
|
||||
#define EEPROM_FIRST_RUN_ADDRESS (198U) // адрес в EEPROM памяти для записи признака первого запуска (определяет необходимость первоначальной записи всех хранимых настроек)
|
||||
#define EEPROM_DAWN_MODE_ADDRESS (199U) // адрес в EEPROM памяти для записи времени до "рассвета"
|
||||
#define EEPROM_CURRENT_MODE_ADDRESS (200U) // адрес в EEPROM памяти для записи номера текущего эффекта лампы
|
||||
|
||||
#define EEPROM_ALARM_STRUCT_SIZE (3U) // 1 байт - вкл/выкл; 2 байта - время от начала суток в минутах (0 - 1440)
|
||||
#define EEPROM_MODE_STRUCT_SIZE (3U) // 1 байт - яркость; 1 байт - скорость; 1 байт - масштаб
|
||||
|
||||
#define EEPROM_FIRST_RUN_MARK (22U) // счисло-метка, если ещё не записно в EEPROM_FIRST_RUN_ADDRESS, значит нужно проинициализировать EEPROM и записать все первоначальные настройки
|
||||
#define EEPROM_WRITE_DELAY (30000UL) // отсрочка записи в EEPROM после последнего изменения хранимых настроек, позволяет уменьшить количество операций записи в EEPROM
|
||||
|
||||
|
||||
class EepromManager
|
||||
{
|
||||
public:
|
||||
static void InitEepromSettings(ModeType modes[], AlarmType alarms[], uint8_t* dawnMode, int8_t* currentMode,
|
||||
void (*readFavoritesSettings)(), void (*saveFavoritesSettings)())
|
||||
{
|
||||
// записываем в EEPROM начальное состояние настроек, если их там ещё нет
|
||||
EEPROM.begin(EEPROM_TOTAL_BYTES_USED);
|
||||
delay(50);
|
||||
|
||||
if (EEPROM.read(EEPROM_FIRST_RUN_ADDRESS) != EEPROM_FIRST_RUN_MARK)
|
||||
{
|
||||
EEPROM.write(EEPROM_FIRST_RUN_ADDRESS, EEPROM_FIRST_RUN_MARK);
|
||||
EEPROM.commit();
|
||||
|
||||
for (uint8_t i = 0; i < MODE_AMOUNT; i++)
|
||||
{
|
||||
EEPROM.put(EEPROM_MODES_START_ADDRESS + EEPROM_ALARM_STRUCT_SIZE * i, modes[i]);
|
||||
EEPROM.commit();
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < 7; i++)
|
||||
{
|
||||
EEPROM.write(EEPROM_ALARM_START_ADDRESS + EEPROM_ALARM_STRUCT_SIZE * i, alarms[i].State);
|
||||
WriteUint16(EEPROM_ALARM_START_ADDRESS + EEPROM_ALARM_STRUCT_SIZE * i + 1, alarms[i].Time);
|
||||
EEPROM.commit();
|
||||
}
|
||||
|
||||
EEPROM.write(EEPROM_DAWN_MODE_ADDRESS, 0);
|
||||
EEPROM.write(EEPROM_CURRENT_MODE_ADDRESS, 0);
|
||||
|
||||
saveFavoritesSettings();
|
||||
|
||||
EEPROM.commit();
|
||||
}
|
||||
|
||||
// инициализируем настройки лампы значениями из EEPROM
|
||||
for (uint8_t i = 0; i < MODE_AMOUNT; i++)
|
||||
{
|
||||
EEPROM.get(EEPROM_MODES_START_ADDRESS + EEPROM_MODE_STRUCT_SIZE * i, modes[i]);
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < 7; i++)
|
||||
{
|
||||
alarms[i].State = EEPROM.read(EEPROM_ALARM_START_ADDRESS + EEPROM_ALARM_STRUCT_SIZE * i);
|
||||
alarms[i].Time = ReadInt16(EEPROM_ALARM_START_ADDRESS + EEPROM_ALARM_STRUCT_SIZE * i + 1);
|
||||
}
|
||||
|
||||
readFavoritesSettings();
|
||||
|
||||
*dawnMode = EEPROM.read(EEPROM_DAWN_MODE_ADDRESS);
|
||||
*currentMode = EEPROM.read(EEPROM_CURRENT_MODE_ADDRESS);
|
||||
}
|
||||
|
||||
static void SaveModesSettings(int8_t* currentMode, ModeType modes[])
|
||||
{
|
||||
EEPROM.put(3 * (*currentMode) + EEPROM_MODES_START_ADDRESS, modes[*currentMode]);
|
||||
EEPROM.commit();
|
||||
}
|
||||
|
||||
static void HandleEepromTick(bool* settChanged, uint32_t* eepromTimer, int8_t* currentMode, ModeType modes[], void (*saveFavoritesSettings)())
|
||||
{
|
||||
if (*settChanged && millis() - *eepromTimer > EEPROM_WRITE_DELAY)
|
||||
{
|
||||
*settChanged = false;
|
||||
*eepromTimer = millis();
|
||||
SaveModesSettings(currentMode, modes);
|
||||
if (EEPROM.read(EEPROM_CURRENT_MODE_ADDRESS) != *currentMode)
|
||||
{
|
||||
EEPROM.write(EEPROM_CURRENT_MODE_ADDRESS, *currentMode);
|
||||
}
|
||||
saveFavoritesSettings();
|
||||
EEPROM.commit();
|
||||
}
|
||||
}
|
||||
|
||||
static void SaveAlarmsSettings(uint8_t* alarmNumber, AlarmType alarms[])
|
||||
{
|
||||
EEPROM.write(5 * (*alarmNumber), alarms[*alarmNumber].State);
|
||||
WriteUint16((uint16_t)(5 * (*alarmNumber) + 1), alarms[*alarmNumber].Time);
|
||||
EEPROM.commit();
|
||||
}
|
||||
|
||||
static void SaveDawnMode(uint8_t* dawnMode)
|
||||
{
|
||||
EEPROM.write(EEPROM_DAWN_MODE_ADDRESS, *dawnMode);
|
||||
EEPROM.commit();
|
||||
}
|
||||
|
||||
static uint16_t ReadUint16(uint16_t address)
|
||||
{
|
||||
uint16_t val;
|
||||
uint8_t* p = (uint8_t*)&val;
|
||||
*p = EEPROM.read(address);
|
||||
*(p + 1) = EEPROM.read(address + 1);
|
||||
return val;
|
||||
}
|
||||
|
||||
static void WriteUint16(uint16_t address, uint16_t val)
|
||||
{
|
||||
uint8_t* p = (uint8_t*)&val;
|
||||
EEPROM.write(address, *p);
|
||||
EEPROM.write(address + 1, *(p + 1));
|
||||
EEPROM.commit();
|
||||
}
|
||||
|
||||
static int16_t ReadInt16(uint16_t address)
|
||||
{
|
||||
int16_t val;
|
||||
uint8_t* p = (uint8_t*)&val;
|
||||
*p = EEPROM.read(address);
|
||||
*(p + 1) = EEPROM.read(address + 1);
|
||||
return val;
|
||||
}
|
||||
|
||||
static void WriteInt16(uint16_t address, int16_t val)
|
||||
{
|
||||
uint8_t* p = (uint8_t*)&val;
|
||||
EEPROM.write(address, *p);
|
||||
EEPROM.write(address + 1, *(p + 1));
|
||||
EEPROM.commit();
|
||||
}
|
||||
|
||||
static uint32_t ReadUint32(uint16_t address)
|
||||
{
|
||||
uint32_t val;
|
||||
uint8_t* p = (uint8_t*)&val;
|
||||
*p = EEPROM.read(address);
|
||||
*(p + 1) = EEPROM.read(address + 1);
|
||||
*(p + 2) = EEPROM.read(address + 2);
|
||||
*(p + 3) = EEPROM.read(address + 3);
|
||||
return val;
|
||||
}
|
||||
|
||||
static void WriteUint32(uint16_t address, uint32_t val)
|
||||
{
|
||||
uint8_t* p = (uint8_t*)&val;
|
||||
EEPROM.write(address, *p);
|
||||
EEPROM.write(address + 1, *(p + 1));
|
||||
EEPROM.write(address + 2, *(p + 2));
|
||||
EEPROM.write(address + 3, *(p + 3));
|
||||
EEPROM.commit();
|
||||
}
|
||||
|
||||
static int32_t ReadInt32(uint16_t address)
|
||||
{
|
||||
int32_t val;
|
||||
uint8_t* p = (uint8_t*)&val;
|
||||
*p = EEPROM.read(address);
|
||||
*(p + 1) = EEPROM.read(address + 1);
|
||||
*(p + 2) = EEPROM.read(address + 2);
|
||||
*(p + 3) = EEPROM.read(address + 3);
|
||||
return val;
|
||||
}
|
||||
|
||||
static void WriteInt32(uint16_t address, int32_t val)
|
||||
{
|
||||
uint8_t* p = (uint8_t*)&val;
|
||||
EEPROM.write(address, *p);
|
||||
EEPROM.write(address + 1, *(p + 1));
|
||||
EEPROM.write(address + 2, *(p + 2));
|
||||
EEPROM.write(address + 3, *(p + 3));
|
||||
EEPROM.commit();
|
||||
}
|
||||
|
||||
private:
|
||||
};
|
245
firmware/GyverLamp_v1.4/FavoritesManager.h
Normal file
245
firmware/GyverLamp_v1.4/FavoritesManager.h
Normal file
@@ -0,0 +1,245 @@
|
||||
#pragma once
|
||||
#include <EEPROM.h>
|
||||
#include "EepromManager.h"
|
||||
|
||||
#define DEFAULT_FAVORITES_INTERVAL (300U) // значение по умолчанию для интервала переключения избпранных эффектов в секундах
|
||||
#define DEFAULT_FAVORITES_DISPERSION (0U) // значение по умолчанию для разброса интервала переключения избпранных эффектов в секундах
|
||||
|
||||
|
||||
class FavoritesManager
|
||||
{
|
||||
public:
|
||||
static bool FavoritesRunning; // флаг "работает режим автоматической смены избранных эффектов"
|
||||
static uint16_t Interval; // статический интервал (время между сменами эффектов)
|
||||
static uint16_t Dispersion; // дополнительный динамический (случайный) интервал (время между сменами эффектов)
|
||||
static uint8_t FavoriteModes[MODE_AMOUNT]; // массив, каждый элемент которого соответствует флагу "эффект №... добавлен в избранные"
|
||||
|
||||
static void SetStatus(char* statusText) // помещает в statusText состояние режима работы избранных эффектов
|
||||
{
|
||||
char buff[6];
|
||||
statusText = "FAV ";
|
||||
|
||||
statusText = strcat(statusText, FavoritesRunning ? "1" : "0");
|
||||
statusText = strcat(statusText, " ");
|
||||
|
||||
itoa(Interval, buff, 10);
|
||||
statusText = strcat(statusText, buff);
|
||||
statusText = strcat(statusText, " ");
|
||||
buff[0] = '\0';
|
||||
|
||||
itoa(Dispersion, buff, 10);
|
||||
statusText = strcat(statusText, buff);
|
||||
statusText = strcat(statusText, " ");
|
||||
buff[0] = '\0';
|
||||
|
||||
for (uint8_t i = 0; i < MODE_AMOUNT; i++)
|
||||
{
|
||||
itoa((uint8_t)FavoriteModes[i], buff, 10);
|
||||
statusText = strcat(statusText, buff);
|
||||
if (i < MODE_AMOUNT - 1) statusText = strcat(statusText, " ");
|
||||
buff[0] = '\0';
|
||||
}
|
||||
|
||||
statusText = strcat(statusText, "\0");
|
||||
}
|
||||
|
||||
static void ConfigureFavorites(const char* statusText) // принимает statusText, парсит его и инициализирует свойства класса значениями из statusText'а
|
||||
{
|
||||
FavoritesRunning = getFavoritesRunning(statusText);
|
||||
Interval = getInterval(statusText);
|
||||
Dispersion = getDispersion(statusText);
|
||||
for (uint8_t i = 0; i < MODE_AMOUNT; i++)
|
||||
{
|
||||
FavoriteModes[i] = getModeOnOff(statusText, i);
|
||||
}
|
||||
}
|
||||
|
||||
static bool HandleFavorites( // функция, обрабатывающая циклическое переключение избранных эффектов; возвращает true, если эффект был переключен
|
||||
bool* ONflag,
|
||||
int8_t* currentMode,
|
||||
bool* loadingFlag
|
||||
#ifdef USE_NTP
|
||||
, bool* dawnFlag
|
||||
#endif
|
||||
)
|
||||
{
|
||||
if (!FavoritesRunning ||
|
||||
!*ONflag // лампа не переключается на следующий эффект при выключенной матрице
|
||||
#ifdef USE_NTP
|
||||
|| *dawnFlag // лампа не переключается на следующий эффект при включенном будильнике
|
||||
#endif
|
||||
)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (nextModeAt == 0) // лампа не переключается на следующий эффект сразу после включения режима избранных эффектов
|
||||
{
|
||||
nextModeAt = getNexTime();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (millis() >= nextModeAt)
|
||||
{
|
||||
*currentMode = getNextFavoriteMode(currentMode);
|
||||
*loadingFlag = true;
|
||||
nextModeAt = getNexTime();
|
||||
|
||||
#ifdef GENERAL_DEBUG
|
||||
Serial.printf("Переключение на следующий избранный режим: %d\n\n", (*currentMode));
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void ReadFavoritesFromEeprom()
|
||||
{
|
||||
Interval = EepromManager::ReadUint16(EEPROM_FAVORITES_START_ADDRESS);
|
||||
Dispersion = EepromManager::ReadUint16(EEPROM_FAVORITES_START_ADDRESS + 2);
|
||||
|
||||
for (uint8_t i = 0; i < MODE_AMOUNT; i++)
|
||||
{
|
||||
FavoriteModes[i] = EEPROM.read(EEPROM_FAVORITES_START_ADDRESS + i + 4);
|
||||
FavoriteModes[i] = FavoriteModes[i] > 0 ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void SaveFavoritesToEeprom()
|
||||
{
|
||||
// FavoritesRunning в EEPROM не сохраняем для экономии ресурса памяти
|
||||
EepromManager::WriteUint16(EEPROM_FAVORITES_START_ADDRESS, Interval);
|
||||
EepromManager::WriteUint16(EEPROM_FAVORITES_START_ADDRESS + 2, Dispersion);
|
||||
|
||||
for (uint8_t i = 0; i < MODE_AMOUNT; i++)
|
||||
{
|
||||
EEPROM.put(EEPROM_FAVORITES_START_ADDRESS + i + 4, FavoriteModes[i] > 0 ? 1 : 0);
|
||||
}
|
||||
|
||||
EEPROM.commit();
|
||||
}
|
||||
|
||||
private:
|
||||
static uint32_t nextModeAt; // ближайшее время переключения на следующий избранный эффект (millis())
|
||||
|
||||
static bool isStatusTextCorrect(const char* statusText) // валидирует statusText (проверяет, правильное ли коичество компонентов он содержит)
|
||||
{
|
||||
char buff[64];
|
||||
strcpy(buff, statusText);
|
||||
|
||||
uint8_t lexCount = 0;
|
||||
char* p = strtok(buff, " ");
|
||||
while (p != NULL) // пока есть лексемы...
|
||||
{
|
||||
lexCount++;
|
||||
p = strtok(NULL, " ");
|
||||
}
|
||||
|
||||
return lexCount == getStatusTextNormalComponentsCount();
|
||||
}
|
||||
|
||||
static uint8_t getStatusTextNormalComponentsCount() // возвращает правильное ли коичество компонентов для statusText в зависимости от определённого формата команды и количества эффектов
|
||||
{
|
||||
// "FAV 0/1 <цифра> <цифра> <массив цифр 0/1 для каждого режима>" (вкл/выкл, интервал в секундах, разброс в секундах, вкл/выкл каждого эффекта в избранные)
|
||||
return
|
||||
1 + // "FAV"
|
||||
1 + // On/Off
|
||||
1 + // интервал
|
||||
1 + // разброс
|
||||
MODE_AMOUNT; // 0/1 для каждого эффекта
|
||||
}
|
||||
|
||||
static bool getFavoritesRunning(const char* statusText) // возвращает признак вкл/выкл режима избранных эффектов из statusText
|
||||
{
|
||||
char lexem[2];
|
||||
memset(lexem, 0, 2);
|
||||
strcpy(lexem, getLexNo(statusText, 1));
|
||||
return lexem != NULL
|
||||
? !strcmp(lexem, "1")
|
||||
: false;
|
||||
}
|
||||
|
||||
static uint16_t getInterval(const char* statusText) // возвращает интервал (постоянную составляющую) переключения избранных эффектов из statusText
|
||||
{
|
||||
char lexem[6];
|
||||
memset(lexem, 0, 6);
|
||||
strcpy(lexem, getLexNo(statusText, 2));
|
||||
return lexem != NULL
|
||||
? atoi((const char*)lexem)
|
||||
: DEFAULT_FAVORITES_INTERVAL;
|
||||
}
|
||||
|
||||
static uint16_t getDispersion(const char* statusText) // возвращает разброс (случайную составляющую) интервала переключения избранных эффектов из statusText
|
||||
{
|
||||
char lexem[6];
|
||||
memset(lexem, 0, 6);
|
||||
strcpy(lexem, getLexNo(statusText, 3));
|
||||
return lexem != NULL
|
||||
? atoi((const char*)lexem)
|
||||
: DEFAULT_FAVORITES_DISPERSION;
|
||||
}
|
||||
|
||||
static bool getModeOnOff(const char* statusText, uint8_t modeId) // возвращает признак включения указанного эффекта в избранные эффекты
|
||||
{
|
||||
char lexem[2];
|
||||
memset(lexem, 0, 2);
|
||||
strcpy(lexem, getLexNo(statusText, modeId + 4));
|
||||
return lexem != NULL
|
||||
? !strcmp(lexem, "1")
|
||||
: false;
|
||||
}
|
||||
|
||||
static char* getLexNo(const char* statusText, uint8_t pos) // служебная функция, разбивает команду statusText на лексемы ("слова", разделённые пробелами) и возвращает указанную по счёту лексему
|
||||
{
|
||||
if (!isStatusTextCorrect(statusText))
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const uint8_t buffSize = 64;
|
||||
char buff[buffSize];
|
||||
memset(buff, 0, buffSize);
|
||||
strcpy(buff, statusText);
|
||||
|
||||
uint8_t lexPos = 0;
|
||||
char* p = strtok(buff, " ");
|
||||
while (p != NULL) // пока есть лексемы...
|
||||
{
|
||||
if (lexPos == pos)
|
||||
{
|
||||
return p;
|
||||
}
|
||||
|
||||
p = strtok(NULL, " ");
|
||||
lexPos++;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int8_t getNextFavoriteMode(int8_t* currentMode) // возвращает следующий (случайный) включенный в избранные эффект
|
||||
{
|
||||
int8_t result = *currentMode;
|
||||
|
||||
for (int8_t tryNo = 0; tryNo <= random(0, MODE_AMOUNT); tryNo++)// случайное количество попыток определения следующего эффекта; без этого будет выбран следующий (избранный) по порядку после текущего
|
||||
{
|
||||
for (uint8_t i = (result + 1); i <= (result + MODE_AMOUNT); i++)
|
||||
{
|
||||
if (FavoriteModes[i < MODE_AMOUNT ? i : i - MODE_AMOUNT] > 0)
|
||||
{
|
||||
result = i < MODE_AMOUNT ? i : i - MODE_AMOUNT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static uint32_t getNexTime() // определяет время следующего переключения на следующий избранный эффект
|
||||
{
|
||||
return millis() + Interval * 1000 + random(0, Dispersion + 1) * 1000;
|
||||
}
|
||||
};
|
@@ -44,6 +44,9 @@
|
||||
- Добавлено взаимодействие с android приложением по управлению будильниками
|
||||
--- 14.08.2019
|
||||
- Добавлена функция таймера отключения
|
||||
--- 26.08.2019
|
||||
- Добавлен режим автоматического переключения избранных эффектов
|
||||
- Реорганизован код, исправлены ошибки
|
||||
*/
|
||||
|
||||
// Ссылка для менеджера плат:
|
||||
@@ -112,6 +115,7 @@ uint8_t AP_STATIC_IP[] = {192, 168, 4, 1}; // статичес
|
||||
#define FASTLED_ALLOW_INTERRUPTS (0U)
|
||||
#define FASTLED_ESP8266_RAW_PIN_ORDER
|
||||
|
||||
#include "Types.h"
|
||||
#include "timerMinim.h"
|
||||
#include <FastLED.h>
|
||||
#include <ESP8266WiFi.h>
|
||||
@@ -130,6 +134,8 @@ uint8_t AP_STATIC_IP[] = {192, 168, 4, 1}; // статичес
|
||||
#include "OtaManager.h"
|
||||
#endif
|
||||
#include "TimerManager.h"
|
||||
#include "FavoritesManager.h"
|
||||
#include "EepromManager.h"
|
||||
|
||||
// --- ИНИЦИАЛИЗАЦИЯ ОБЪЕКТОВ ----------
|
||||
CRGB leds[NUM_LEDS];
|
||||
@@ -158,34 +164,25 @@ char packetBuffer[UDP_TX_PACKET_MAX_SIZE + 1]; // buffer to hold in
|
||||
String inputBuffer;
|
||||
static const uint8_t maxDim = max(WIDTH, HEIGHT);
|
||||
|
||||
struct
|
||||
{
|
||||
uint8_t brightness = 50;
|
||||
uint8_t speed = 30;
|
||||
uint8_t scale = 40;
|
||||
} modes[MODE_AMOUNT];
|
||||
|
||||
struct
|
||||
{
|
||||
boolean state = false;
|
||||
int16_t time = 0;
|
||||
} alarm[7];
|
||||
ModeType modes[MODE_AMOUNT];
|
||||
AlarmType alarms[7];
|
||||
|
||||
uint8_t dawnOffsets[] = {5, 10, 15, 20, 25, 30, 40, 50, 60};// опции для выпадающего списка параметра "время перед 'рассветом'" (будильник)
|
||||
uint8_t dawnMode;
|
||||
boolean dawnFlag = false;
|
||||
bool dawnFlag = false;
|
||||
long thisTime;
|
||||
boolean manualOff = false;
|
||||
bool manualOff = false;
|
||||
|
||||
int8_t currentMode = 0;
|
||||
boolean loadingFlag = true;
|
||||
boolean ONflag = true;
|
||||
bool loadingFlag = true;
|
||||
bool ONflag = true;
|
||||
uint32_t eepromTimer;
|
||||
boolean settChanged = false;
|
||||
bool settChanged = false;
|
||||
|
||||
// Конфетти, Огонь, Радуга верт., Радуга гориз., Смена цвета,
|
||||
// Конфетти, Огонь, Радуга вертикальная, Радуга горизонтальная, Смена цвета,
|
||||
// Безумие 3D, Облака 3D, Лава 3D, Плазма 3D, Радуга 3D,
|
||||
// Павлин 3D, Зебра 3D, Лес 3D, Океан 3D,
|
||||
// Цвет, Снег, Матрица, Светлячки, Светлячки со шлейфом, Белый свет
|
||||
|
||||
unsigned char matrixValue[8][16];
|
||||
|
||||
@@ -194,6 +191,12 @@ bool TimerManager::TimerHasFired = false;
|
||||
uint8_t TimerManager::TimerOption = 1U;
|
||||
uint64_t TimerManager::TimeToFire = 0ULL;
|
||||
|
||||
bool FavoritesManager::FavoritesRunning = false;
|
||||
uint16_t FavoritesManager::Interval = DEFAULT_FAVORITES_INTERVAL;
|
||||
uint16_t FavoritesManager::Dispersion = DEFAULT_FAVORITES_DISPERSION;
|
||||
uint8_t FavoritesManager::FavoriteModes[MODE_AMOUNT] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||
uint32_t FavoritesManager::nextModeAt = 0UL;
|
||||
|
||||
|
||||
void setup()
|
||||
{
|
||||
@@ -286,47 +289,12 @@ void setup()
|
||||
Serial.printf("Порт UDP сервера: %u\n", localPort);
|
||||
Udp.begin(localPort);
|
||||
|
||||
// EEPROM
|
||||
EEPROM.begin(202);
|
||||
delay(50);
|
||||
if (EEPROM.read(198) != 20) // первый запуск
|
||||
{
|
||||
EEPROM.write(198, 20);
|
||||
EEPROM.commit();
|
||||
EepromManager::InitEepromSettings( // инициализация EEPROM; запись начального состояния настроек, если их там ещё нет; инициализация настроек лампы значениями из EEPROM
|
||||
modes, alarms, &dawnMode, ¤tMode,
|
||||
&(FavoritesManager::ReadFavoritesFromEeprom),
|
||||
&(FavoritesManager::SaveFavoritesToEeprom));
|
||||
|
||||
for (uint8_t i = 0; i < MODE_AMOUNT; i++)
|
||||
{
|
||||
EEPROM.put(3 * i + 40, modes[i]);
|
||||
EEPROM.commit();
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < 7; i++)
|
||||
{
|
||||
EEPROM.write(5 * i, alarm[i].state); // рассвет
|
||||
eeWriteInt(5 * i + 1, alarm[i].time);
|
||||
EEPROM.commit();
|
||||
}
|
||||
|
||||
EEPROM.write(199, 0); // рассвет
|
||||
EEPROM.write(200, 0); // режим
|
||||
EEPROM.commit();
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < MODE_AMOUNT; i++)
|
||||
{
|
||||
EEPROM.get(3 * i + 40, modes[i]);
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < 7; i++)
|
||||
{
|
||||
alarm[i].state = EEPROM.read(5 * i);
|
||||
alarm[i].time = eeGetInt(5 * i + 1);
|
||||
}
|
||||
|
||||
dawnMode = EEPROM.read(199);
|
||||
currentMode = (int8_t)EEPROM.read(200);
|
||||
|
||||
sendCurrent(); // отправляем настройки
|
||||
sendCurrent(); // отправляем настройки (куда?)
|
||||
char reply[inputBuffer.length() + 1];
|
||||
inputBuffer.toCharArray(reply, inputBuffer.length() + 1);
|
||||
Udp.beginPacket(Udp.remoteIP(), Udp.remotePort());
|
||||
@@ -347,7 +315,7 @@ void loop()
|
||||
{
|
||||
parseUDP();
|
||||
effectsTick();
|
||||
eepromTick();
|
||||
EepromManager::HandleEepromTick(&settChanged, &eepromTimer, ¤tMode, modes, &(FavoritesManager::SaveFavoritesToEeprom));
|
||||
#ifdef USE_NTP
|
||||
timeTick();
|
||||
#endif
|
||||
@@ -358,29 +326,19 @@ void loop()
|
||||
otaManager.HandleOtaUpdate(); // ожидание и обработка команды на обновление прошивки по воздуху
|
||||
#endif
|
||||
TimerManager::HandleTimer(&ONflag, &changePower); // обработка событий таймера отключения лампы
|
||||
if (FavoritesManager::HandleFavorites( // обработка режима избранных эффектов
|
||||
&ONflag,
|
||||
¤tMode,
|
||||
&loadingFlag
|
||||
#ifdef USE_NTP
|
||||
, &dawnFlag
|
||||
#endif
|
||||
))
|
||||
{
|
||||
FastLED.setBrightness(modes[currentMode].Brightness);
|
||||
FastLED.clear();
|
||||
delay(1);
|
||||
}
|
||||
ESP.wdtFeed(); // пнуть собаку
|
||||
yield(); // обработать все "служебные" задачи: WiFi подключение и т.д.
|
||||
}
|
||||
|
||||
|
||||
void eeWriteInt(int16_t pos, int16_t val)
|
||||
{
|
||||
uint8_t* p = (uint8_t*) &val;
|
||||
EEPROM.write(pos, *p);
|
||||
EEPROM.write(pos + 1, *(p + 1));
|
||||
EEPROM.write(pos + 2, *(p + 2));
|
||||
EEPROM.write(pos + 3, *(p + 3));
|
||||
EEPROM.commit();
|
||||
}
|
||||
|
||||
|
||||
int16_t eeGetInt(int16_t pos)
|
||||
{
|
||||
int16_t val;
|
||||
uint8_t* p = (uint8_t*) &val;
|
||||
*p = EEPROM.read(pos);
|
||||
*(p + 1) = EEPROM.read(pos + 1);
|
||||
*(p + 2) = EEPROM.read(pos + 2);
|
||||
*(p + 3) = EEPROM.read(pos + 3);
|
||||
return val;
|
||||
yield(); // обработать все "служебные" задачи: wdt, WiFi подключение и т.д. (?)
|
||||
}
|
||||
|
@@ -1,3 +1,4 @@
|
||||
#pragma once
|
||||
/*
|
||||
* 11.07.2019
|
||||
* Класс, который отслеживает действия пользователя по запросу обновления прошивки по воздуху и выполняет эту прошивку.
|
||||
|
@@ -1,3 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
class TimerManager
|
||||
{
|
||||
public:
|
||||
@@ -7,7 +10,7 @@ class TimerManager
|
||||
static uint64_t TimeToFire; // время, в которое должен сработать таймер (millis)
|
||||
|
||||
static void HandleTimer( // функция, обрабатывающая срабатывание таймера, гасит матрицу
|
||||
bool *ONflag,
|
||||
bool* ONflag,
|
||||
void (*changePower)())
|
||||
{
|
||||
if (!TimerManager::TimerHasFired &&
|
||||
|
15
firmware/GyverLamp_v1.4/Types.h
Normal file
15
firmware/GyverLamp_v1.4/Types.h
Normal file
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
struct AlarmType
|
||||
{
|
||||
bool State = false;
|
||||
uint16_t Time = 0;
|
||||
};
|
||||
|
||||
struct ModeType
|
||||
{
|
||||
uint8_t Brightness = 50;
|
||||
uint8_t Speed = 30;
|
||||
uint8_t Scale = 40;
|
||||
};
|
@@ -1,5 +1,5 @@
|
||||
#ifdef ESP_USE_BUTTON
|
||||
boolean brightDirection;
|
||||
bool brightDirection;
|
||||
|
||||
void buttonTick()
|
||||
{
|
||||
@@ -11,7 +11,7 @@ void buttonTick()
|
||||
manualOff = true;
|
||||
dawnFlag = false;
|
||||
loadingFlag = true;
|
||||
FastLED.setBrightness(modes[currentMode].brightness);
|
||||
FastLED.setBrightness(modes[currentMode].Brightness);
|
||||
changePower();
|
||||
}
|
||||
else
|
||||
@@ -24,7 +24,7 @@ void buttonTick()
|
||||
if (ONflag && touch.isDouble())
|
||||
{
|
||||
if (++currentMode >= MODE_AMOUNT) currentMode = 0;
|
||||
FastLED.setBrightness(modes[currentMode].brightness);
|
||||
FastLED.setBrightness(modes[currentMode].Brightness);
|
||||
loadingFlag = true;
|
||||
settChanged = true;
|
||||
eepromTimer = millis();
|
||||
@@ -35,7 +35,7 @@ void buttonTick()
|
||||
if (ONflag && touch.isTriple())
|
||||
{
|
||||
if (--currentMode < 0) currentMode = MODE_AMOUNT - 1;
|
||||
FastLED.setBrightness(modes[currentMode].brightness);
|
||||
FastLED.setBrightness(modes[currentMode].Brightness);
|
||||
loadingFlag = true;
|
||||
settChanged = true;
|
||||
eepromTimer = millis();
|
||||
@@ -64,22 +64,22 @@ void buttonTick()
|
||||
{
|
||||
if (brightDirection)
|
||||
{
|
||||
if (modes[currentMode].brightness < 10) modes[currentMode].brightness += 1;
|
||||
else if (modes[currentMode].brightness < 250) modes[currentMode].brightness += 5;
|
||||
else modes[currentMode].brightness = 255;
|
||||
if (modes[currentMode].Brightness < 10) modes[currentMode].Brightness += 1;
|
||||
else if (modes[currentMode].Brightness < 250) modes[currentMode].Brightness += 5;
|
||||
else modes[currentMode].Brightness = 255;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (modes[currentMode].brightness > 15) modes[currentMode].brightness -= 5;
|
||||
else if (modes[currentMode].brightness > 1) modes[currentMode].brightness -= 1;
|
||||
else modes[currentMode].brightness = 0;
|
||||
if (modes[currentMode].Brightness > 15) modes[currentMode].Brightness -= 5;
|
||||
else if (modes[currentMode].Brightness > 1) modes[currentMode].Brightness -= 1;
|
||||
else modes[currentMode].Brightness = 0;
|
||||
}
|
||||
FastLED.setBrightness(modes[currentMode].brightness);
|
||||
FastLED.setBrightness(modes[currentMode].Brightness);
|
||||
settChanged = true;
|
||||
eepromTimer = millis();
|
||||
|
||||
#ifdef GENERAL_DEBUG
|
||||
Serial.printf("New brightness value: %d\n", modes[currentMode].brightness);
|
||||
Serial.printf("New brightness value: %d\n", modes[currentMode].Brightness);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
@@ -1,30 +0,0 @@
|
||||
void saveEEPROM()
|
||||
{
|
||||
EEPROM.put(3 * currentMode + 40, modes[currentMode]);
|
||||
EEPROM.commit();
|
||||
}
|
||||
|
||||
void eepromTick()
|
||||
{
|
||||
if (settChanged && millis() - eepromTimer > 30000)
|
||||
{
|
||||
settChanged = false;
|
||||
eepromTimer = millis();
|
||||
saveEEPROM();
|
||||
if (EEPROM.read(200) != currentMode) EEPROM.write(200, currentMode);
|
||||
EEPROM.commit();
|
||||
}
|
||||
}
|
||||
|
||||
void saveAlarm(byte almNumber)
|
||||
{
|
||||
EEPROM.write(5 * almNumber, alarm[almNumber].state); // рассвет
|
||||
eeWriteInt(5 * almNumber + 1, alarm[almNumber].time);
|
||||
EEPROM.commit();
|
||||
}
|
||||
|
||||
void saveDawnMmode()
|
||||
{
|
||||
EEPROM.write(199, dawnMode); // рассвет
|
||||
EEPROM.commit();
|
||||
}
|
@@ -4,51 +4,32 @@ void effectsTick()
|
||||
{
|
||||
if (!dawnFlag)
|
||||
{
|
||||
if (ONflag && (millis() - effTimer >= ((currentMode < 5 || currentMode > 13) ? modes[currentMode].speed : 50)))
|
||||
if (ONflag && (millis() - effTimer >= ((currentMode < 5 || currentMode > 13) ? modes[currentMode].Speed : 50)))
|
||||
{
|
||||
effTimer = millis();
|
||||
switch (currentMode)
|
||||
{
|
||||
case 0: sparklesRoutine();
|
||||
break;
|
||||
case 1: fireRoutine();
|
||||
break;
|
||||
case 2: rainbowVertical();
|
||||
break;
|
||||
case 3: rainbowHorizontal();
|
||||
break;
|
||||
case 4: colorsRoutine();
|
||||
break;
|
||||
case 5: madnessNoise();
|
||||
break;
|
||||
case 6: cloudNoise();
|
||||
break;
|
||||
case 7: lavaNoise();
|
||||
break;
|
||||
case 8: plasmaNoise();
|
||||
break;
|
||||
case 9: rainbowNoise();
|
||||
break;
|
||||
case 10: rainbowStripeNoise();
|
||||
break;
|
||||
case 11: zebraNoise();
|
||||
break;
|
||||
case 12: forestNoise();
|
||||
break;
|
||||
case 13: oceanNoise();
|
||||
break;
|
||||
case 14: colorRoutine();
|
||||
break;
|
||||
case 15: snowRoutine();
|
||||
break;
|
||||
case 16: matrixRoutine();
|
||||
break;
|
||||
case 17: lightersRoutine();
|
||||
break;
|
||||
case 18: lightBalls();
|
||||
break;
|
||||
case 19: whiteColor();
|
||||
break;
|
||||
case 0: sparklesRoutine(); break;
|
||||
case 1: fireRoutine(); break;
|
||||
case 2: rainbowVertical(); break;
|
||||
case 3: rainbowHorizontal(); break;
|
||||
case 4: colorsRoutine(); break;
|
||||
case 5: madnessNoise(); break;
|
||||
case 6: cloudNoise(); break;
|
||||
case 7: lavaNoise(); break;
|
||||
case 8: plasmaNoise(); break;
|
||||
case 9: rainbowNoise(); break;
|
||||
case 10: rainbowStripeNoise(); break;
|
||||
case 11: zebraNoise(); break;
|
||||
case 12: forestNoise(); break;
|
||||
case 13: oceanNoise(); break;
|
||||
case 14: colorRoutine(); break;
|
||||
case 15: snowRoutine(); break;
|
||||
case 16: matrixRoutine(); break;
|
||||
case 17: lightersRoutine(); break;
|
||||
case 18: lightBalls(); break;
|
||||
case 19: whiteColor(); break;
|
||||
default: break;
|
||||
}
|
||||
FastLED.show();
|
||||
}
|
||||
@@ -60,20 +41,20 @@ void changePower()
|
||||
if (ONflag)
|
||||
{
|
||||
effectsTick();
|
||||
for (uint8_t i = 0; i < modes[currentMode].brightness; i = constrain(i + 8, 0, modes[currentMode].brightness))
|
||||
for (uint8_t i = 0; i < modes[currentMode].Brightness; i = constrain(i + 8, 0, modes[currentMode].Brightness))
|
||||
{
|
||||
FastLED.setBrightness(i);
|
||||
delay(1);
|
||||
FastLED.show();
|
||||
}
|
||||
FastLED.setBrightness(modes[currentMode].brightness);
|
||||
FastLED.setBrightness(modes[currentMode].Brightness);
|
||||
delay(2);
|
||||
FastLED.show();
|
||||
}
|
||||
else
|
||||
{
|
||||
effectsTick();
|
||||
for (uint8_t i = modes[currentMode].brightness; i > 0; i = constrain(i - 8, 0, modes[currentMode].brightness))
|
||||
for (uint8_t i = modes[currentMode].Brightness; i > 0; i = constrain(i - 8, 0, modes[currentMode].Brightness))
|
||||
{
|
||||
FastLED.setBrightness(i);
|
||||
delay(1);
|
||||
|
@@ -3,7 +3,7 @@
|
||||
// ------------- конфетти --------------
|
||||
void sparklesRoutine()
|
||||
{
|
||||
for (uint8_t i = 0; i < modes[0].scale; i++)
|
||||
for (uint8_t i = 0; i < modes[0].Scale; i++)
|
||||
{
|
||||
uint8_t x = random(0, WIDTH);
|
||||
uint8_t y = random(0, HEIGHT);
|
||||
@@ -144,7 +144,7 @@ void drawFrame(int32_t pcnt)
|
||||
- pgm_read_byte(&(valueMask[y][newX]));
|
||||
|
||||
CRGB color = CHSV(
|
||||
modes[1].scale * 2.5 + pgm_read_byte(&(hueMask[y][newX])), // H
|
||||
modes[1].Scale * 2.5 + pgm_read_byte(&(hueMask[y][newX])), // H
|
||||
255, // S
|
||||
(uint8_t)max(0, nextv) // V
|
||||
);
|
||||
@@ -172,7 +172,7 @@ void drawFrame(int32_t pcnt)
|
||||
uint8_t newX = x;
|
||||
if (x > 15) newX = x - 15;
|
||||
CRGB color = CHSV(
|
||||
modes[1].scale * 2.5 + pgm_read_byte(&(hueMask[0][newX])), // H
|
||||
modes[1].Scale * 2.5 + pgm_read_byte(&(hueMask[0][newX])), // H
|
||||
255, // S
|
||||
(uint8_t)(((100.0 - pcnt) * matrixValue[0][newX] + pcnt * line[newX]) / 100.0) // V
|
||||
);
|
||||
@@ -187,7 +187,7 @@ void rainbowVertical()
|
||||
hue += 2;
|
||||
for (uint8_t j = 0; j < HEIGHT; j++)
|
||||
{
|
||||
CHSV thisColor = CHSV((uint8_t)(hue + j * modes[2].scale), 255, 255);
|
||||
CHSV thisColor = CHSV((uint8_t)(hue + j * modes[2].Scale), 255, 255);
|
||||
for (uint8_t i = 0; i < WIDTH; i++)
|
||||
drawPixelXY(i, j, thisColor);
|
||||
}
|
||||
@@ -198,7 +198,7 @@ void rainbowHorizontal()
|
||||
hue += 2;
|
||||
for (uint8_t i = 0; i < WIDTH; i++)
|
||||
{
|
||||
CHSV thisColor = CHSV((uint8_t)(hue + i * modes[3].scale), 255, 255);
|
||||
CHSV thisColor = CHSV((uint8_t)(hue + i * modes[3].Scale), 255, 255);
|
||||
for (uint8_t j = 0; j < HEIGHT; j++)
|
||||
drawPixelXY(i, j, thisColor); //leds[getPixelNumber(i, j)] = thisColor;
|
||||
}
|
||||
@@ -207,7 +207,7 @@ void rainbowHorizontal()
|
||||
// ------------- цвета -----------------
|
||||
void colorsRoutine()
|
||||
{
|
||||
hue += modes[4].scale;
|
||||
hue += modes[4].Scale;
|
||||
for (int32_t i = 0; i < NUM_LEDS; i++)
|
||||
{
|
||||
leds[i] = CHSV(hue, 255, 255);
|
||||
@@ -219,7 +219,7 @@ void colorRoutine()
|
||||
{
|
||||
for (int32_t i = 0; i < NUM_LEDS; i++)
|
||||
{
|
||||
leds[i] = CHSV(modes[14].scale * 2.5, 255, 255);
|
||||
leds[i] = CHSV(modes[14].Scale * 2.5, 255, 255);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,7 +239,7 @@ void snowRoutine()
|
||||
{
|
||||
// заполняем случайно верхнюю строку
|
||||
// а также не даём двум блокам по вертикали вместе быть
|
||||
if (getPixColorXY(x, HEIGHT - 2) == 0 && (random(0, 100 - modes[15].scale) == 0))
|
||||
if (getPixColorXY(x, HEIGHT - 2) == 0 && (random(0, 100 - modes[15].Scale) == 0))
|
||||
drawPixelXY(x, HEIGHT - 1, 0xE0FFFF - 0x101010 * random(0, 4));
|
||||
else
|
||||
drawPixelXY(x, HEIGHT - 1, 0x000000);
|
||||
@@ -254,7 +254,7 @@ void matrixRoutine()
|
||||
// заполняем случайно верхнюю строку
|
||||
uint32_t thisColor = getPixColorXY(x, HEIGHT - 1);
|
||||
if (thisColor == 0)
|
||||
drawPixelXY(x, HEIGHT - 1, 0x00FF00 * (random(0, 100 - modes[16].scale) == 0));
|
||||
drawPixelXY(x, HEIGHT - 1, 0x00FF00 * (random(0, 100 - modes[16].Scale) == 0));
|
||||
else if (thisColor < 0x002000)
|
||||
drawPixelXY(x, HEIGHT - 1, 0);
|
||||
else
|
||||
@@ -299,7 +299,7 @@ void lightersRoutine()
|
||||
}
|
||||
FastLED.clear();
|
||||
if (++loopCounter > 20) loopCounter = 0;
|
||||
for (uint8_t i = 0; i < modes[17].scale; i++)
|
||||
for (uint8_t i = 0; i < modes[17].Scale; i++)
|
||||
{
|
||||
if (loopCounter == 0) // меняем скорость каждые 255 отрисовок
|
||||
{
|
||||
|
@@ -37,8 +37,8 @@ void madnessNoise()
|
||||
if (loadingFlag)
|
||||
{
|
||||
loadingFlag = false;
|
||||
scale = modes[5].scale;
|
||||
speed = modes[5].speed;
|
||||
scale = modes[5].Scale;
|
||||
speed = modes[5].Speed;
|
||||
}
|
||||
fillnoise8();
|
||||
for (uint8_t i = 0; i < WIDTH; i++)
|
||||
@@ -58,8 +58,8 @@ void rainbowNoise()
|
||||
{
|
||||
loadingFlag = false;
|
||||
currentPalette = RainbowColors_p;
|
||||
scale = modes[9].scale;
|
||||
speed = modes[9].speed;
|
||||
scale = modes[9].Scale;
|
||||
speed = modes[9].Speed;
|
||||
colorLoop = 1;
|
||||
}
|
||||
fillNoiseLED();
|
||||
@@ -71,8 +71,8 @@ void rainbowStripeNoise()
|
||||
{
|
||||
loadingFlag = false;
|
||||
currentPalette = RainbowStripeColors_p;
|
||||
scale = modes[10].scale;
|
||||
speed = modes[10].speed;
|
||||
scale = modes[10].Scale;
|
||||
speed = modes[10].Speed;
|
||||
colorLoop = 1;
|
||||
}
|
||||
fillNoiseLED();
|
||||
@@ -90,8 +90,8 @@ void zebraNoise()
|
||||
currentPalette[4] = CRGB::White;
|
||||
currentPalette[8] = CRGB::White;
|
||||
currentPalette[12] = CRGB::White;
|
||||
scale = modes[11].scale;
|
||||
speed = modes[11].speed;
|
||||
scale = modes[11].Scale;
|
||||
speed = modes[11].Speed;
|
||||
colorLoop = 1;
|
||||
}
|
||||
fillNoiseLED();
|
||||
@@ -103,8 +103,8 @@ void forestNoise()
|
||||
{
|
||||
loadingFlag = false;
|
||||
currentPalette = ForestColors_p;
|
||||
scale = modes[12].scale;
|
||||
speed = modes[12].speed;
|
||||
scale = modes[12].Scale;
|
||||
speed = modes[12].Speed;
|
||||
colorLoop = 0;
|
||||
}
|
||||
fillNoiseLED();
|
||||
@@ -116,8 +116,8 @@ void oceanNoise()
|
||||
{
|
||||
loadingFlag = false;
|
||||
currentPalette = OceanColors_p;
|
||||
scale = modes[13].scale;
|
||||
speed = modes[13].speed;
|
||||
scale = modes[13].Scale;
|
||||
speed = modes[13].Speed;
|
||||
colorLoop = 0;
|
||||
}
|
||||
|
||||
@@ -130,8 +130,8 @@ void plasmaNoise()
|
||||
{
|
||||
loadingFlag = false;
|
||||
currentPalette = PartyColors_p;
|
||||
scale = modes[8].scale;
|
||||
speed = modes[8].speed;
|
||||
scale = modes[8].Scale;
|
||||
speed = modes[8].Speed;
|
||||
colorLoop = 1;
|
||||
}
|
||||
fillNoiseLED();
|
||||
@@ -143,8 +143,8 @@ void cloudNoise()
|
||||
{
|
||||
loadingFlag = false;
|
||||
currentPalette = CloudColors_p;
|
||||
scale = modes[6].scale;
|
||||
speed = modes[6].speed;
|
||||
scale = modes[6].Scale;
|
||||
speed = modes[6].Speed;
|
||||
colorLoop = 0;
|
||||
}
|
||||
fillNoiseLED();
|
||||
@@ -156,8 +156,8 @@ void lavaNoise()
|
||||
{
|
||||
loadingFlag = false;
|
||||
currentPalette = LavaColors_p;
|
||||
scale = modes[7].scale;
|
||||
speed = modes[7].speed;
|
||||
scale = modes[7].Scale;
|
||||
speed = modes[7].Speed;
|
||||
colorLoop = 0;
|
||||
}
|
||||
fillNoiseLED();
|
||||
|
@@ -30,19 +30,19 @@ void parseUDP()
|
||||
|
||||
else if (inputBuffer.startsWith("EFF"))
|
||||
{
|
||||
saveEEPROM();
|
||||
EepromManager::SaveModesSettings(¤tMode, modes);
|
||||
currentMode = (byte)inputBuffer.substring(3).toInt();
|
||||
loadingFlag = true;
|
||||
FastLED.clear();
|
||||
delay(1);
|
||||
sendCurrent();
|
||||
FastLED.setBrightness(modes[currentMode].brightness);
|
||||
FastLED.setBrightness(modes[currentMode].Brightness);
|
||||
}
|
||||
|
||||
else if (inputBuffer.startsWith("BRI"))
|
||||
{
|
||||
modes[currentMode].brightness = constrain(inputBuffer.substring(3).toInt(), 1, 255);
|
||||
FastLED.setBrightness(modes[currentMode].brightness);
|
||||
modes[currentMode].Brightness = constrain(inputBuffer.substring(3).toInt(), 1, 255);
|
||||
FastLED.setBrightness(modes[currentMode].Brightness);
|
||||
settChanged = true;
|
||||
eepromTimer = millis();
|
||||
sendCurrent();
|
||||
@@ -50,7 +50,7 @@ void parseUDP()
|
||||
|
||||
else if (inputBuffer.startsWith("SPD"))
|
||||
{
|
||||
modes[currentMode].speed = inputBuffer.substring(3).toInt();
|
||||
modes[currentMode].Speed = inputBuffer.substring(3).toInt();
|
||||
loadingFlag = true;
|
||||
settChanged = true;
|
||||
eepromTimer = millis();
|
||||
@@ -59,7 +59,7 @@ void parseUDP()
|
||||
|
||||
else if (inputBuffer.startsWith("SCA"))
|
||||
{
|
||||
modes[currentMode].scale = inputBuffer.substring(3).toInt();
|
||||
modes[currentMode].Scale = inputBuffer.substring(3).toInt();
|
||||
loadingFlag = true;
|
||||
settChanged = true;
|
||||
eepromTimer = millis();
|
||||
@@ -82,27 +82,27 @@ void parseUDP()
|
||||
|
||||
else if (inputBuffer.startsWith("ALM_SET"))
|
||||
{
|
||||
byte alarmNum = (char)inputBuffer[7] - '0';
|
||||
uint8_t alarmNum = (char)inputBuffer[7] - '0';
|
||||
alarmNum -= 1;
|
||||
if (inputBuffer.indexOf("ON") != -1)
|
||||
{
|
||||
alarm[alarmNum].state = true;
|
||||
alarms[alarmNum].State = true;
|
||||
sendAlarms();
|
||||
}
|
||||
else if (inputBuffer.indexOf("OFF") != -1)
|
||||
{
|
||||
alarm[alarmNum].state = false;
|
||||
alarms[alarmNum].State = false;
|
||||
sendAlarms();
|
||||
}
|
||||
else
|
||||
{
|
||||
int32_t almTime = inputBuffer.substring(8).toInt();
|
||||
alarm[alarmNum].time = almTime;
|
||||
byte hour = floor(almTime / 60);
|
||||
byte minute = almTime - hour * 60;
|
||||
int32_t alarmTime = inputBuffer.substring(8).toInt();
|
||||
alarms[alarmNum].Time = alarmTime;
|
||||
uint8_t hour = floor(alarmTime / 60);
|
||||
uint8_t minute = alarmTime - hour * 60;
|
||||
sendAlarms();
|
||||
}
|
||||
saveAlarm(alarmNum);
|
||||
EepromManager::SaveAlarmsSettings(&alarmNum, alarms);
|
||||
}
|
||||
|
||||
else if (inputBuffer.startsWith("ALM_GET"))
|
||||
@@ -113,7 +113,7 @@ void parseUDP()
|
||||
else if (inputBuffer.startsWith("DAWN"))
|
||||
{
|
||||
dawnMode = inputBuffer.substring(4).toInt() - 1;
|
||||
saveDawnMmode();
|
||||
EepromManager::SaveDawnMode(&dawnMode);
|
||||
sendAlarms();
|
||||
}
|
||||
|
||||
@@ -146,6 +146,20 @@ void parseUDP()
|
||||
sendTimer();
|
||||
}
|
||||
|
||||
else if (inputBuffer.startsWith("FAV_GET"))
|
||||
{
|
||||
sendFavorites();
|
||||
}
|
||||
|
||||
else if (inputBuffer.startsWith("FAV_SET"))
|
||||
{
|
||||
FavoritesManager::ConfigureFavorites(inputBuffer.c_str());
|
||||
//FavoritesManager::SetStatus(inputBuffer);
|
||||
sendFavorites();
|
||||
settChanged = true;
|
||||
eepromTimer = millis();
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
inputBuffer = "";
|
||||
@@ -182,11 +196,11 @@ void sendCurrent()
|
||||
inputBuffer += " ";
|
||||
inputBuffer += String(currentMode);
|
||||
inputBuffer += " ";
|
||||
inputBuffer += String(modes[currentMode].brightness);
|
||||
inputBuffer += String(modes[currentMode].Brightness);
|
||||
inputBuffer += " ";
|
||||
inputBuffer += String(modes[currentMode].speed);
|
||||
inputBuffer += String(modes[currentMode].Speed);
|
||||
inputBuffer += " ";
|
||||
inputBuffer += String(modes[currentMode].scale);
|
||||
inputBuffer += String(modes[currentMode].Scale);
|
||||
inputBuffer += " ";
|
||||
inputBuffer += String(ONflag);
|
||||
inputBuffer += " ";
|
||||
@@ -205,21 +219,19 @@ void sendCurrent()
|
||||
#else
|
||||
inputBuffer += String(millis());
|
||||
#endif
|
||||
|
||||
#ifdef GENERAL_DEBUG
|
||||
Serial.println(inputBuffer);
|
||||
#endif
|
||||
}
|
||||
|
||||
void sendAlarms()
|
||||
{
|
||||
inputBuffer = "ALMS ";
|
||||
for (byte i = 0; i < 7; i++) {
|
||||
inputBuffer += String(alarm[i].state);
|
||||
for (byte i = 0; i < 7; i++)
|
||||
{
|
||||
inputBuffer += String(alarms[i].State);
|
||||
inputBuffer += " ";
|
||||
}
|
||||
for (byte i = 0; i < 7; i++) {
|
||||
inputBuffer += String(alarm[i].time);
|
||||
for (byte i = 0; i < 7; i++)
|
||||
{
|
||||
inputBuffer += String(alarms[i].Time);
|
||||
inputBuffer += " ";
|
||||
}
|
||||
inputBuffer += (dawnMode + 1);
|
||||
@@ -235,3 +247,19 @@ void sendTimer()
|
||||
inputBuffer += " ";
|
||||
inputBuffer += String(TimerManager::TimerRunning ? (uint16_t)floor((TimerManager::TimeToFire - millis()) / 1000) : 0);
|
||||
}
|
||||
|
||||
void sendFavorites()
|
||||
{
|
||||
inputBuffer = "FAV";
|
||||
inputBuffer += " ";
|
||||
inputBuffer += String((uint8_t)FavoritesManager::FavoritesRunning);
|
||||
inputBuffer += " ";
|
||||
inputBuffer += String((uint16_t)FavoritesManager::Interval);
|
||||
inputBuffer += " ";
|
||||
inputBuffer += String((uint16_t)FavoritesManager::Dispersion);
|
||||
for (uint8_t i = 0; i < MODE_AMOUNT; i++)
|
||||
{
|
||||
inputBuffer += " ";
|
||||
inputBuffer += String((uint8_t)FavoritesManager::FavoriteModes[i]);
|
||||
}
|
||||
}
|
||||
|
@@ -52,14 +52,14 @@ void timeTick()
|
||||
thisTime = timeClient.getHours() * 60 + timeClient.getMinutes();
|
||||
|
||||
// проверка рассвета
|
||||
if (alarm[thisDay].state && // день будильника
|
||||
thisTime >= (alarm[thisDay].time - dawnOffsets[dawnMode]) && // позже начала
|
||||
thisTime < (alarm[thisDay].time + DAWN_TIMEOUT)) // раньше конца + минута
|
||||
if (alarms[thisDay].State && // день будильника
|
||||
thisTime >= (alarms[thisDay].Time - dawnOffsets[dawnMode]) && // позже начала
|
||||
thisTime < (alarms[thisDay].Time + DAWN_TIMEOUT)) // раньше конца + минута
|
||||
{
|
||||
if (!manualOff) // будильник не был выключен вручную (из приложения или кнопкой)
|
||||
{
|
||||
// величина рассвета 0-255
|
||||
int32_t dawnPosition = 255 * ((float)(thisTime - (alarm[thisDay].time - dawnOffsets[dawnMode])) / dawnOffsets[dawnMode]);
|
||||
int32_t dawnPosition = 255 * ((float)(thisTime - (alarms[thisDay].Time - dawnOffsets[dawnMode])) / dawnOffsets[dawnMode]);
|
||||
dawnPosition = constrain(dawnPosition, 0, 255);
|
||||
CHSV dawnColor = CHSV(map(dawnPosition, 0, 255, 10, 35),
|
||||
map(dawnPosition, 0, 255, 255, 170),
|
||||
@@ -118,4 +118,4 @@ void resolveNtpServerAddress(bool &ntpServerAddressResolved) // ф
|
||||
ntpServerAddressResolved = true;
|
||||
}
|
||||
}
|
||||
#endif USE_NTP
|
||||
#endif
|
||||
|
@@ -5,7 +5,7 @@ class timerMinim
|
||||
public:
|
||||
timerMinim(uint32_t interval); // объявление таймера с указанием интервала
|
||||
void setInterval(uint32_t interval); // установка интервала работы таймера
|
||||
boolean isReady(); // возвращает true, когда пришло время. Сбрасывается в false сам (AUTO) или вручную (MANUAL)
|
||||
bool isReady(); // возвращает true, когда пришло время. Сбрасывается в false сам (AUTO) или вручную (MANUAL)
|
||||
void reset(); // ручной сброс таймера на установленный интервал
|
||||
|
||||
private:
|
||||
@@ -24,7 +24,7 @@ void timerMinim::setInterval(uint32_t interval)
|
||||
_interval = interval;
|
||||
}
|
||||
|
||||
boolean timerMinim::isReady()
|
||||
bool timerMinim::isReady()
|
||||
{
|
||||
if ((long)millis() - _timer >= _interval)
|
||||
{
|
||||
|
Reference in New Issue
Block a user