3778 lines
148 KiB
C
3778 lines
148 KiB
C
/* AUTO-GENERATED by GUI Editor — do not edit manually */
|
||
#include "gui_generated.h"
|
||
#include "lvgl.h"
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <math.h>
|
||
#include <stdbool.h>
|
||
|
||
/* Fonts */
|
||
LV_FONT_DECLARE(montserrat_16_ru_en)
|
||
|
||
/* Images */
|
||
LV_IMG_DECLARE(img_valve)
|
||
LV_IMG_DECLARE(img_fan)
|
||
LV_IMG_DECLARE(img_channel)
|
||
LV_IMG_DECLARE(img_heater)
|
||
LV_IMG_DECLARE(img_cooler)
|
||
LV_IMG_DECLARE(img_pipe)
|
||
LV_IMG_DECLARE(img_electric_heater)
|
||
LV_IMG_DECLARE(img_filter_g4)
|
||
LV_IMG_DECLARE(img_filter_h13)
|
||
|
||
/* Color scheme - minimalistic dark theme */
|
||
#define COLOR_BG_DARK 0x0F1419 /* Очень темный фон */
|
||
#define COLOR_BG_PANEL 0x1A1F26 /* Фон панелей */
|
||
#define COLOR_BG_MODULE 0x252B33 /* Фон модулей */
|
||
#define COLOR_BG_BTN 0x2D333B /* Фон кнопок */
|
||
#define COLOR_BORDER 0x3D444D /* Границы */
|
||
#define COLOR_TEXT 0xD0D4D8 /* Светлый текст */
|
||
#define COLOR_TEXT_DIM 0x6E7681 /* Приглушенный текст */
|
||
#define COLOR_TEXT_ACCENT 0x58A6FF /* Акцентный текст */
|
||
#define COLOR_ACCENT 0x3FB950 /* Зеленый для активных */
|
||
#define COLOR_WARNING 0xD29922 /* Желтый для предупреждений */
|
||
#define COLOR_DANGER 0xF85149 /* Красный для аварий */
|
||
|
||
lv_obj_t *scr_main = NULL;
|
||
lv_obj_t *scr_screen1 = NULL; /* Меню */
|
||
lv_obj_t *scr_screen2 = NULL; /* Журнал */
|
||
lv_obj_t *scr_screen3 = NULL; /* Уставка параметров */
|
||
lv_obj_t *scr_screen4 = NULL; /* Настройки (после пароля) */
|
||
|
||
/* System state structure (like ModBus data) */
|
||
typedef struct {
|
||
int16_t temp_room; /* Температура комнаты x10 */
|
||
int16_t hum_room; /* Влажность комнаты x10 */
|
||
int16_t temp_channel; /* Температура канала x10 */
|
||
int16_t hum_channel; /* Влажность канала x10 */
|
||
uint16_t room_num; /* Номер комнаты */
|
||
uint8_t is_master; /* 1=Мастер, 0=Слейв */
|
||
uint8_t has_signal; /* Есть ли сигнал при слейве */
|
||
uint8_t season; /* 0=Зима, 1=Лето, 2=Переход */
|
||
uint8_t mode; /* 0=Остановка, 1=Дежурный, 2=Рабочий */
|
||
uint8_t valve_inlet; /* Входной клапан 0-100% */
|
||
uint8_t valve_recirc; /* Клапан рециркуляции 0-100% */
|
||
uint8_t filter_g4_clean; /* 0=Грязный, 1=Чистый */
|
||
uint8_t kws_valve; /* KWS клапан 0-100% */
|
||
uint8_t kws_mode; /* 0=Охлаждение, 1=Нагрев */
|
||
uint8_t pww_valve; /* PWW клапан 0-100% */
|
||
uint8_t pww_mode; /* 0=Охлаждение, 1=Нагрев */
|
||
uint8_t heater_on; /* Электронагреватель 0=Выкл, 1=Вкл */
|
||
uint8_t fan_on; /* Вентилятор 0=Выкл, 1=Вкл */
|
||
uint8_t filter_h13_clean; /* 0=Грязный, 1=Чистый */
|
||
uint16_t year;
|
||
uint8_t month;
|
||
uint8_t day;
|
||
uint8_t hour;
|
||
uint8_t minute;
|
||
} SysState_t;
|
||
|
||
static SysState_t state = {
|
||
.temp_room = 0,
|
||
.hum_room = 0,
|
||
.temp_channel = 200, /* 20.0°C default - prevents false freezing alarm */
|
||
.hum_channel = 0,
|
||
.room_num = 101,
|
||
.is_master = 1,
|
||
.has_signal = 1,
|
||
.season = 0, /* will be recalculated from date in gui_init */
|
||
.mode = 1, /* Дежурный по умолчанию */
|
||
.valve_inlet = 0,
|
||
.valve_recirc = 0,
|
||
.filter_g4_clean = 0, /* Фильтр загрязнен для демонстрации */
|
||
.kws_valve = 0,
|
||
.kws_mode = 1,
|
||
.pww_valve = 0,
|
||
.pww_mode = 0,
|
||
.heater_on = 0,
|
||
.fan_on = 0,
|
||
.filter_h13_clean = 1,
|
||
.year = 2026,
|
||
.month = 3,
|
||
.day = 10,
|
||
.hour = 12,
|
||
.minute = 0
|
||
};
|
||
|
||
/* Module configuration for drag&drop */
|
||
typedef enum {
|
||
MODULE_VALVE_INLET = 0,
|
||
MODULE_VALVE_RECIRC,
|
||
MODULE_FILTER_G4,
|
||
MODULE_KWS,
|
||
MODULE_PWW,
|
||
MODULE_HEATER,
|
||
MODULE_FAN,
|
||
MODULE_FILTER_H13,
|
||
MODULE_CHANNEL,
|
||
MODULE_COUNT
|
||
} module_type_t;
|
||
|
||
typedef struct {
|
||
module_type_t type;
|
||
int enabled; /* 1=активен (на линии), 0=неактивен (внизу) */
|
||
int position; /* позиция в ряду (0-7) */
|
||
lv_obj_t *obj; /* указатель на объект */
|
||
lv_obj_t *img; /* указатель на изображение */
|
||
lv_obj_t *label_name; /* название модуля */
|
||
lv_obj_t *label_value; /* указатель на значение */
|
||
} module_config_t;
|
||
|
||
static module_config_t modules[MODULE_COUNT] = {
|
||
{MODULE_VALVE_INLET, 1, 0, NULL, NULL, NULL, NULL},
|
||
{MODULE_VALVE_RECIRC, 1, 1, NULL, NULL, NULL, NULL},
|
||
{MODULE_FILTER_G4, 1, 2, NULL, NULL, NULL, NULL},
|
||
{MODULE_KWS, 1, 3, NULL, NULL, NULL, NULL},
|
||
{MODULE_PWW, 1, 4, NULL, NULL, NULL, NULL},
|
||
{MODULE_HEATER, 1, 5, NULL, NULL, NULL, NULL},
|
||
{MODULE_FAN, 1, 6, NULL, NULL, NULL, NULL},
|
||
{MODULE_FILTER_H13, 1, 7, NULL, NULL, NULL, NULL},
|
||
{MODULE_CHANNEL, 1, 8, NULL, NULL, NULL, NULL},
|
||
};
|
||
|
||
/* Module dimensions and positions */
|
||
#define MODULE_WIDTH 70
|
||
#define MODULE_HEIGHT 95
|
||
#define MODULE_SPACING 3
|
||
#define MODULES_Y 40 /* Fixed Y position for all modules */
|
||
/* Scale factor: 256=100% (no transform, saves memory), 140~55% */
|
||
#define MODULE_SCALE 256 /* Changed from 140 to avoid out-of-memory issues */
|
||
|
||
/* Panel for schema (needed globally for drag) */
|
||
static lv_obj_t *schema_panel = NULL;
|
||
|
||
/* Mode control buttons */
|
||
static lv_obj_t *btn_stop_mode = NULL;
|
||
static lv_obj_t *btn_standby_mode = NULL;
|
||
static lv_obj_t *btn_work_mode = NULL;
|
||
static lv_obj_t *lbl_stop_mode = NULL;
|
||
static lv_obj_t *lbl_standby_mode = NULL;
|
||
static lv_obj_t *lbl_work_mode = NULL;
|
||
|
||
/* Settings screen module objects (separate from main screen) */
|
||
static lv_obj_t *settings_module_obj[MODULE_COUNT] = {NULL};
|
||
static lv_obj_t *settings_module_img[MODULE_COUNT] = {NULL};
|
||
static lv_obj_t *settings_module_label_name[MODULE_COUNT] = {NULL};
|
||
static lv_obj_t *settings_module_label_value[MODULE_COUNT] = {NULL};
|
||
|
||
/* Long press timer for settings screen */
|
||
static lv_timer_t *longpress_timer = NULL;
|
||
static int longpress_module_idx = -1;
|
||
static bool longpress_fired = false; /* suppress click after long press */
|
||
|
||
/* Alarm log structure */
|
||
#define MAX_ALARMS 20
|
||
typedef struct {
|
||
char message[80];
|
||
char timestamp[20];
|
||
int active;
|
||
int confirmed; /* 1=подтверждена */
|
||
char confirm_timestamp[20]; /* дата подтверждения */
|
||
} alarm_entry_t;
|
||
|
||
static alarm_entry_t alarm_log[MAX_ALARMS];
|
||
static int alarm_count = 0;
|
||
|
||
/* Pagination state for alarm log */
|
||
static int alarm_page = 0;
|
||
#define ALARMS_PER_PAGE 4
|
||
|
||
/* Pagination state for setpoints (Work mode) */
|
||
static int setpoint_work_page = 0;
|
||
#define SETPOINTS_PER_PAGE 4
|
||
|
||
/* Pagination state for setpoints (Standby mode) */
|
||
static int setpoint_standby_page = 0;
|
||
|
||
/* Pagination state for config screen */
|
||
static int config_page = 0;
|
||
#define CONFIG_ITEMS_PER_PAGE 6
|
||
|
||
/* Settings/Setpoints for Work and Standby modes */
|
||
typedef struct {
|
||
float temp; /* Температура */
|
||
float hum; /* Влажность */
|
||
float valve_inlet; /* Клапан притока */
|
||
float valve_recirc; /* Клапан рециркуляции */
|
||
uint8_t heater; /* 0=ВЫКЛ, 1=ВКЛ, 2=АВТО */
|
||
uint8_t fan; /* 0=ВЫКЛ, 1=ВКЛ */
|
||
uint8_t kws_auto; /* 1=АВТО, 0=ручной режим */
|
||
float kws_manual; /* Значение для ручного режима */
|
||
uint8_t pww_auto; /* 1=АВТО, 0=ручной режим */
|
||
float pww_manual; /* Значение для ручного режима */
|
||
} setpoint_mode_t;
|
||
|
||
static setpoint_mode_t setpoint_work = {
|
||
.temp = 22.0f, .hum = 50.0f, .valve_inlet = 75.0f, .valve_recirc = 25.0f,
|
||
.heater = 2, .fan = 1, .kws_auto = 1, .kws_manual = 0, .pww_auto = 1, .pww_manual = 0
|
||
};
|
||
|
||
static setpoint_mode_t setpoint_standby = {
|
||
.temp = 18.0f, .hum = 45.0f, .valve_inlet = 35.0f, .valve_recirc = 40.0f,
|
||
.heater = 0, .fan = 1, .kws_auto = 1, .kws_manual = 0, .pww_auto = 1, .pww_manual = 0
|
||
};
|
||
|
||
/* Configuration parameters (Настройки - с паролем) */
|
||
typedef struct {
|
||
float temp_room_offset;
|
||
float hum_room_offset;
|
||
float temp_channel_offset;
|
||
float hum_channel_offset;
|
||
float valve_inlet_offset;
|
||
float valve_recirc_offset;
|
||
uint8_t has_recirc_valve;
|
||
uint8_t has_heater;
|
||
float pid_pww_kp;
|
||
float pid_pww_ti;
|
||
float pid_pww_d;
|
||
float pid_kws_kp;
|
||
float pid_kws_ti;
|
||
float pid_kws_d;
|
||
uint8_t force_mode_enabled;
|
||
uint8_t force_mode_value;
|
||
uint8_t key_swap_enabled;
|
||
} config_t;
|
||
|
||
static config_t config = {
|
||
.temp_room_offset = 0.0f,
|
||
.hum_room_offset = 0.0f,
|
||
.temp_channel_offset = 0.0f,
|
||
.hum_channel_offset = 0.0f,
|
||
.valve_inlet_offset = 0.0f,
|
||
.valve_recirc_offset = 0.0f,
|
||
.has_recirc_valve = 1,
|
||
.has_heater = 1,
|
||
.pid_pww_kp = 1.0f,
|
||
.pid_pww_ti = 10.0f,
|
||
.pid_pww_d = 0.0f,
|
||
.pid_kws_kp = 1.0f,
|
||
.pid_kws_ti = 10.0f,
|
||
.pid_kws_d = 0.0f,
|
||
.force_mode_enabled = 0,
|
||
.force_mode_value = 1,
|
||
.key_swap_enabled = 1
|
||
};
|
||
|
||
/* Filter replacement dates */
|
||
static char filter_g4_date[12] = "01.01.2026";
|
||
static char filter_h13_date[12] = "01.01.2026";
|
||
|
||
/* ── Modbus Slave Emulation ──────────────────────── */
|
||
typedef struct {
|
||
/* Command registers (written by master/UI setpoints) */
|
||
float target_temp; /* Target room temperature °C */
|
||
float target_hum; /* Target room humidity % */
|
||
uint8_t cmd_valve_inlet; /* Commanded inlet valve 0-100% */
|
||
uint8_t cmd_valve_recirc; /* Commanded recirc valve 0-100% */
|
||
uint8_t cmd_kws_valve; /* Commanded KWS valve 0-100% */
|
||
uint8_t cmd_pww_valve; /* Commanded PWW valve 0-100% */
|
||
uint8_t cmd_heater; /* Commanded heater 0/1 */
|
||
uint8_t cmd_fan; /* Commanded fan 0/1 */
|
||
uint8_t kws_auto; /* KWS auto mode flag */
|
||
uint8_t pww_auto; /* PWW auto mode flag */
|
||
|
||
/* Feedback registers (simulated sensor readings) */
|
||
int16_t fb_temp_room; /* Simulated room temp x10 */
|
||
int16_t fb_hum_room; /* Simulated room humidity x10 */
|
||
int16_t fb_temp_channel; /* Simulated channel temp x10 */
|
||
int16_t fb_hum_channel; /* Simulated channel humidity x10 */
|
||
} modbus_slave_t;
|
||
|
||
static modbus_slave_t slave = {
|
||
.target_temp = 22.0f,
|
||
.target_hum = 50.0f,
|
||
.cmd_valve_inlet = 0,
|
||
.cmd_valve_recirc = 0,
|
||
.cmd_kws_valve = 0,
|
||
.cmd_pww_valve = 0,
|
||
.cmd_heater = 0,
|
||
.cmd_fan = 0,
|
||
.kws_auto = 1,
|
||
.pww_auto = 1,
|
||
.fb_temp_room = 200,
|
||
.fb_hum_room = 450,
|
||
.fb_temp_channel = 200,
|
||
.fb_hum_channel = 500,
|
||
};
|
||
|
||
/* Password and settings */
|
||
#define PASSWORD "1234"
|
||
static char password_input[10] = "";
|
||
static lv_obj_t *popup_password = NULL;
|
||
static lv_obj_t *lbl_password_display = NULL;
|
||
static int password_target = 0; /* 0=настройки, 1=очистка журнала фильтров */
|
||
|
||
/* Parameter editor */
|
||
static lv_obj_t *popup_param_editor = NULL;
|
||
static lv_obj_t *lbl_param_value = NULL;
|
||
static char param_value_str[20] = "";
|
||
static float *param_being_edited = NULL;
|
||
static char param_name[50] = "";
|
||
static int param_is_percentage = 0;
|
||
|
||
/* Configuration parameters */
|
||
/* (moved to config_t structure above) */
|
||
|
||
/* UI element references for updates */
|
||
static lv_obj_t *lbl_temp_room = NULL;
|
||
static lv_obj_t *lbl_hum_room = NULL;
|
||
static lv_obj_t *lbl_temp_channel = NULL;
|
||
static lv_obj_t *lbl_hum_channel = NULL;
|
||
static lv_obj_t *lbl_room_num = NULL;
|
||
static lv_obj_t *lbl_master_slave = NULL;
|
||
static lv_obj_t *lbl_season = NULL;
|
||
static lv_obj_t *lbl_mode = NULL;
|
||
static lv_obj_t *lbl_datetime = NULL;
|
||
|
||
/* Popup windows */
|
||
static lv_obj_t *popup_readings = NULL;
|
||
static lv_obj_t *popup_alarm = NULL;
|
||
static lv_obj_t *popup_module_config = NULL;
|
||
|
||
/* Setpoints screen UI */
|
||
static lv_obj_t *lbl_work_temp = NULL;
|
||
static lv_obj_t *lbl_work_hum = NULL;
|
||
static lv_obj_t *lbl_standby_temp = NULL;
|
||
static lv_obj_t *lbl_standby_hum = NULL;
|
||
|
||
/* Forward declarations */
|
||
static void create_popup_readings(void);
|
||
static void create_popup_alarm(const char *message);
|
||
static void add_alarm_to_log(const char *message, int active);
|
||
static void btn_log_cb(lv_event_t *e);
|
||
static void btn_settings_cb(lv_event_t *e);
|
||
static void btn_config_cb(lv_event_t *e);
|
||
static void create_popup_password(int target);
|
||
static void password_keypad_cb(lv_event_t *e);
|
||
static void create_popup_param_editor(const char *name, float *param_ptr, int is_percentage);
|
||
static void param_keypad_cb(lv_event_t *e);
|
||
static void close_popup_cb(lv_event_t *e);
|
||
static void btn_back_to_main_cb(lv_event_t *e);
|
||
static void create_screen_main(void);
|
||
static void create_screen_screen1(void);
|
||
static void create_screen_config(void);
|
||
static void create_screen_screen2(void);
|
||
static void update_module_display(int module_idx);
|
||
static void update_settings_module_display(int module_idx);
|
||
static void refresh_setpoints_display(void);
|
||
static void module_settings_click_cb(lv_event_t *e);
|
||
static void module_settings_press_cb(lv_event_t *e);
|
||
static void module_settings_longpress_timer_cb(lv_timer_t *timer);
|
||
static void create_popup_module_config(int module_idx);
|
||
static void btn_confirm_alarm_cb(lv_event_t *e);
|
||
static void btn_alarm_page_prev_cb(lv_event_t *e);
|
||
static void btn_alarm_page_next_cb(lv_event_t *e);
|
||
static void btn_clear_log_cb(lv_event_t *e);
|
||
static void btn_setpoint_work_prev_cb(lv_event_t *e);
|
||
static void btn_setpoint_work_next_cb(lv_event_t *e);
|
||
static void btn_setpoint_standby_prev_cb(lv_event_t *e);
|
||
static void btn_setpoint_standby_next_cb(lv_event_t *e);
|
||
static void btn_config_page_prev_cb(lv_event_t *e);
|
||
static void btn_config_page_next_cb(lv_event_t *e);
|
||
static void create_screen_screen3(void);
|
||
static lv_obj_t* create_module_widget(lv_obj_t *parent, int module_idx, int x, int y);
|
||
static void update_mode_button_colors(void);
|
||
static void sync_setpoints_to_slave(void);
|
||
static void modbus_simulation_timer_cb(lv_timer_t *timer);
|
||
|
||
/* Calculate season from month: 0=Winter, 1=Summer, 2=Transition */
|
||
static uint8_t calc_season_from_month(uint8_t month) {
|
||
if (month == 12 || month == 1 || month == 2) return 0; /* Зима */
|
||
if (month == 6 || month == 7 || month == 8) return 1; /* Лето */
|
||
return 2; /* Переход (весна/осень) */
|
||
}
|
||
|
||
/* Get image for module type */
|
||
static const lv_image_dsc_t* get_module_image(int module_idx) {
|
||
switch (module_idx) {
|
||
case MODULE_VALVE_INLET:
|
||
case MODULE_VALVE_RECIRC:
|
||
return &img_valve;
|
||
case MODULE_FILTER_G4:
|
||
return &img_filter_g4;
|
||
case MODULE_KWS:
|
||
return state.kws_mode ? &img_heater : &img_cooler;
|
||
case MODULE_PWW:
|
||
return state.pww_mode ? &img_heater : &img_cooler;
|
||
case MODULE_HEATER:
|
||
return &img_electric_heater;
|
||
case MODULE_FAN:
|
||
return &img_fan;
|
||
case MODULE_FILTER_H13:
|
||
return &img_filter_h13;
|
||
case MODULE_CHANNEL:
|
||
return &img_channel;
|
||
default:
|
||
return &img_pipe;
|
||
}
|
||
}
|
||
|
||
/* Get name for module type */
|
||
static const char* get_module_name(int module_idx) {
|
||
switch (module_idx) {
|
||
case MODULE_VALVE_INLET:
|
||
return "Вход";
|
||
case MODULE_VALVE_RECIRC:
|
||
return "Рецирк";
|
||
case MODULE_FILTER_G4:
|
||
return "G4";
|
||
case MODULE_KWS:
|
||
return "KWS";
|
||
case MODULE_PWW:
|
||
return "PWW";
|
||
case MODULE_HEATER:
|
||
return "Нагрев";
|
||
case MODULE_FAN:
|
||
return "Вент";
|
||
case MODULE_FILTER_H13:
|
||
return "H13";
|
||
case MODULE_CHANNEL:
|
||
return "Канал";
|
||
default:
|
||
return "";
|
||
}
|
||
}
|
||
|
||
/* Settings screen: Module click handler (short press) */
|
||
static void module_settings_click_cb(lv_event_t *e) {
|
||
lv_event_code_t code = lv_event_get_code(e);
|
||
int module_idx = (int)(intptr_t)lv_event_get_user_data(e);
|
||
|
||
if (code == LV_EVENT_CLICKED) {
|
||
/* If long press already fired, just consume the click */
|
||
if (longpress_fired) {
|
||
longpress_fired = false;
|
||
return;
|
||
}
|
||
/* Cancel long press timer - this was a short click, not a long press */
|
||
if (longpress_timer) {
|
||
lv_timer_del(longpress_timer);
|
||
longpress_timer = NULL;
|
||
longpress_module_idx = -1;
|
||
}
|
||
/* Short click - open module config popup */
|
||
create_popup_module_config(module_idx);
|
||
}
|
||
}
|
||
|
||
/* Settings screen: Module press handler (start long press timer) */
|
||
static void module_settings_press_cb(lv_event_t *e) {
|
||
int module_idx = (int)(intptr_t)lv_event_get_user_data(e);
|
||
|
||
/* Reset long press flag */
|
||
longpress_fired = false;
|
||
|
||
/* Cancel existing timer if any */
|
||
if (longpress_timer) {
|
||
lv_timer_del(longpress_timer);
|
||
longpress_timer = NULL;
|
||
}
|
||
|
||
/* Start long press timer (500ms) */
|
||
longpress_module_idx = module_idx;
|
||
longpress_timer = lv_timer_create(module_settings_longpress_timer_cb, 500, NULL);
|
||
lv_timer_set_repeat_count(longpress_timer, 1);
|
||
}
|
||
|
||
/* Settings screen: Long press timer callback */
|
||
static void module_settings_longpress_timer_cb(lv_timer_t *timer) {
|
||
(void)timer;
|
||
|
||
if (longpress_module_idx >= 0 && longpress_module_idx < MODULE_COUNT) {
|
||
/* Toggle module enabled state */
|
||
modules[longpress_module_idx].enabled = !modules[longpress_module_idx].enabled;
|
||
|
||
int enabled = modules[longpress_module_idx].enabled;
|
||
|
||
/* Update image opacity */
|
||
if (settings_module_img[longpress_module_idx]) {
|
||
lv_obj_set_style_img_opa(settings_module_img[longpress_module_idx],
|
||
enabled ? LV_OPA_COVER : LV_OPA_30, 0);
|
||
}
|
||
|
||
/* Update label colors (grey when disabled) */
|
||
if (settings_module_label_name[longpress_module_idx]) {
|
||
lv_obj_set_style_text_color(settings_module_label_name[longpress_module_idx],
|
||
lv_color_hex(enabled ? COLOR_TEXT_DIM : 0x555555), 0);
|
||
}
|
||
if (settings_module_label_value[longpress_module_idx]) {
|
||
lv_obj_set_style_text_color(settings_module_label_value[longpress_module_idx],
|
||
lv_color_hex(enabled ? COLOR_TEXT_ACCENT : 0x555555), 0);
|
||
}
|
||
|
||
update_settings_module_display(longpress_module_idx);
|
||
}
|
||
|
||
longpress_timer = NULL;
|
||
longpress_module_idx = -1;
|
||
longpress_fired = true; /* prevent click callback from opening popup */
|
||
}
|
||
|
||
/* Create a module widget with image - simplified, no container box */
|
||
static lv_obj_t* create_module_widget(lv_obj_t *parent, int module_idx, int x, int y) {
|
||
/* Container for module - transparent, no border */
|
||
lv_obj_t *cont = lv_obj_create(parent);
|
||
lv_obj_set_pos(cont, x, y);
|
||
lv_obj_set_size(cont, MODULE_WIDTH, MODULE_HEIGHT);
|
||
lv_obj_set_style_bg_opa(cont, LV_OPA_TRANSP, 0); /* Transparent background */
|
||
lv_obj_set_style_border_width(cont, 0, 0); /* No border */
|
||
lv_obj_set_style_pad_all(cont, 2, 0);
|
||
lv_obj_clear_flag(cont, LV_OBJ_FLAG_SCROLLABLE);
|
||
lv_obj_clear_flag(cont, LV_OBJ_FLAG_CLICKABLE); /* Not clickable on main screen */
|
||
|
||
/* Image */
|
||
lv_obj_t *img = lv_image_create(cont);
|
||
lv_image_set_src(img, get_module_image(module_idx));
|
||
lv_image_set_scale(img, MODULE_SCALE);
|
||
lv_obj_align(img, LV_ALIGN_CENTER, 0, 0);
|
||
|
||
/* Module name label */
|
||
lv_obj_t *lbl_name = lv_label_create(cont);
|
||
lv_label_set_text(lbl_name, get_module_name(module_idx));
|
||
lv_obj_set_style_text_font(lbl_name, &montserrat_16_ru_en, 0);
|
||
|
||
/* Value label */
|
||
lv_obj_t *lbl_value = lv_label_create(cont);
|
||
lv_obj_set_style_text_font(lbl_value, &montserrat_16_ru_en, 0);
|
||
|
||
if (module_idx == MODULE_CHANNEL) {
|
||
/* Channel: name at top (same as others), value centered on black square */
|
||
lv_obj_set_style_text_color(lbl_name, lv_color_hex(COLOR_TEXT_DIM), 0);
|
||
lv_obj_align(lbl_name, LV_ALIGN_TOP_MID, 0, -5);
|
||
lv_obj_set_style_text_color(lbl_value, lv_color_hex(COLOR_TEXT_ACCENT), 0);
|
||
lv_obj_align(lbl_value, LV_ALIGN_CENTER, 0, 5);
|
||
lv_obj_set_style_text_align(lbl_value, LV_TEXT_ALIGN_CENTER, 0);
|
||
} else {
|
||
/* Standard: name at top, value at bottom */
|
||
lv_obj_set_style_text_color(lbl_name, lv_color_hex(COLOR_TEXT_DIM), 0);
|
||
lv_obj_align(lbl_name, LV_ALIGN_TOP_MID, 0, -5);
|
||
lv_obj_set_style_text_color(lbl_value, lv_color_hex(COLOR_TEXT_ACCENT), 0);
|
||
lv_obj_align(lbl_value, LV_ALIGN_BOTTOM_MID, 0, -2);
|
||
}
|
||
|
||
/* Grey out disabled modules on main screen too */
|
||
if (!modules[module_idx].enabled) {
|
||
lv_obj_set_style_img_opa(img, LV_OPA_30, 0);
|
||
lv_obj_set_style_text_color(lbl_name, lv_color_hex(0x555555), 0);
|
||
lv_obj_set_style_text_color(lbl_value, lv_color_hex(0x555555), 0);
|
||
}
|
||
|
||
modules[module_idx].obj = cont;
|
||
modules[module_idx].img = img;
|
||
modules[module_idx].label_name = lbl_name;
|
||
modules[module_idx].label_value = lbl_value;
|
||
|
||
return cont;
|
||
}
|
||
|
||
/* Apply setpoints to system state based on current mode */
|
||
static void apply_mode_setpoints(void) {
|
||
setpoint_mode_t *sp = (state.mode == 2) ? &setpoint_work : &setpoint_standby;
|
||
|
||
if (state.mode == 0) {
|
||
/* Остановка - выключаем активные элементы */
|
||
state.heater_on = 0;
|
||
state.fan_on = 0;
|
||
} else {
|
||
/* Дежурный или Рабочий режим - применяем уставки */
|
||
/* Only apply if module is enabled */
|
||
if (modules[MODULE_VALVE_INLET].enabled)
|
||
state.valve_inlet = (uint8_t)sp->valve_inlet;
|
||
if (modules[MODULE_VALVE_RECIRC].enabled)
|
||
state.valve_recirc = (uint8_t)sp->valve_recirc;
|
||
|
||
/* Вентилятор - выключен если модуль отключён */
|
||
state.fan_on = modules[MODULE_FAN].enabled ? sp->fan : 0;
|
||
|
||
/* Электронагреватель */
|
||
if (!modules[MODULE_HEATER].enabled) {
|
||
state.heater_on = 0;
|
||
} else if (sp->heater == 0) {
|
||
state.heater_on = 0;
|
||
} else if (sp->heater == 1) {
|
||
state.heater_on = 1;
|
||
}
|
||
/* heater == 2 (АВТО) - управляется автоматикой */
|
||
|
||
/* KWS и PWW */
|
||
if (modules[MODULE_KWS].enabled && !sp->kws_auto) {
|
||
state.kws_valve = (uint8_t)sp->kws_manual;
|
||
}
|
||
if (modules[MODULE_PWW].enabled && !sp->pww_auto) {
|
||
state.pww_valve = (uint8_t)sp->pww_manual;
|
||
}
|
||
}
|
||
|
||
/* Sync setpoints to modbus slave registers */
|
||
sync_setpoints_to_slave();
|
||
}
|
||
|
||
/* Write current setpoints to modbus slave command registers */
|
||
static void sync_setpoints_to_slave(void) {
|
||
setpoint_mode_t *sp = (state.mode == 2) ? &setpoint_work : &setpoint_standby;
|
||
|
||
if (state.mode == 0) {
|
||
slave.target_temp = 15.0f; /* maintain minimum when stopped */
|
||
slave.target_hum = 40.0f;
|
||
slave.cmd_valve_inlet = 0;
|
||
slave.cmd_valve_recirc = 0;
|
||
slave.cmd_kws_valve = 0;
|
||
slave.cmd_pww_valve = 0;
|
||
slave.cmd_heater = 0;
|
||
slave.cmd_fan = 0;
|
||
slave.kws_auto = 1;
|
||
slave.pww_auto = 1;
|
||
} else {
|
||
slave.target_temp = sp->temp;
|
||
slave.target_hum = sp->hum;
|
||
slave.cmd_valve_inlet = (uint8_t)sp->valve_inlet;
|
||
slave.cmd_valve_recirc = (uint8_t)sp->valve_recirc;
|
||
slave.cmd_fan = modules[MODULE_FAN].enabled ? sp->fan : 0;
|
||
slave.cmd_heater = (modules[MODULE_HEATER].enabled && sp->heater == 1) ? 1 : 0;
|
||
slave.kws_auto = sp->kws_auto;
|
||
slave.pww_auto = sp->pww_auto;
|
||
if (!sp->kws_auto)
|
||
slave.cmd_kws_valve = (uint8_t)sp->kws_manual;
|
||
if (!sp->pww_auto)
|
||
slave.cmd_pww_valve = (uint8_t)sp->pww_manual;
|
||
}
|
||
}
|
||
|
||
/* Update mode button colors based on current mode */
|
||
static void update_mode_button_colors(void) {
|
||
if (!btn_stop_mode || !btn_standby_mode || !btn_work_mode) return;
|
||
if (!lbl_stop_mode || !lbl_standby_mode || !lbl_work_mode) return;
|
||
|
||
/* Reset all buttons to default color */
|
||
lv_obj_set_style_bg_color(btn_stop_mode, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_bg_color(btn_standby_mode, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_bg_color(btn_work_mode, lv_color_hex(COLOR_BG_BTN), 0);
|
||
|
||
/* Reset text colors to original */
|
||
lv_obj_set_style_text_color(lbl_stop_mode, lv_color_hex(COLOR_DANGER), 0);
|
||
lv_obj_set_style_text_color(lbl_standby_mode, lv_color_hex(COLOR_WARNING), 0);
|
||
lv_obj_set_style_text_color(lbl_work_mode, lv_color_hex(COLOR_ACCENT), 0);
|
||
|
||
/* Highlight active mode button with background and change text to black */
|
||
if (state.mode == 0) {
|
||
lv_obj_set_style_bg_color(btn_stop_mode, lv_color_hex(COLOR_DANGER), 0);
|
||
lv_obj_set_style_text_color(lbl_stop_mode, lv_color_hex(0x000000), 0);
|
||
} else if (state.mode == 1) {
|
||
lv_obj_set_style_bg_color(btn_standby_mode, lv_color_hex(COLOR_WARNING), 0);
|
||
lv_obj_set_style_text_color(lbl_standby_mode, lv_color_hex(0x000000), 0);
|
||
} else if (state.mode == 2) {
|
||
lv_obj_set_style_bg_color(btn_work_mode, lv_color_hex(COLOR_ACCENT), 0);
|
||
lv_obj_set_style_text_color(lbl_work_mode, lv_color_hex(0x000000), 0);
|
||
}
|
||
}
|
||
|
||
/* Button callbacks */
|
||
static void btn_stop_cb(lv_event_t *e) {
|
||
(void)e;
|
||
state.mode = 0;
|
||
apply_mode_setpoints();
|
||
update_mode_button_colors();
|
||
gui_update_values();
|
||
}
|
||
|
||
static void btn_standby_cb(lv_event_t *e) {
|
||
(void)e;
|
||
state.mode = 1;
|
||
apply_mode_setpoints();
|
||
update_mode_button_colors();
|
||
gui_update_values();
|
||
}
|
||
|
||
static void btn_work_cb(lv_event_t *e) {
|
||
(void)e;
|
||
state.mode = 2;
|
||
apply_mode_setpoints();
|
||
update_mode_button_colors();
|
||
gui_update_values();
|
||
}
|
||
|
||
static void btn_menu_cb(lv_event_t *e) {
|
||
(void)e;
|
||
if (!scr_screen1) {
|
||
create_screen_screen1();
|
||
}
|
||
lv_scr_load(scr_screen1);
|
||
}
|
||
|
||
static void btn_back_to_main_cb(lv_event_t *e) {
|
||
(void)e;
|
||
/* Free non-main screens to save memory */
|
||
if (scr_screen1) { lv_obj_del(scr_screen1); scr_screen1 = NULL; }
|
||
if (scr_screen2) { lv_obj_del(scr_screen2); scr_screen2 = NULL; }
|
||
if (scr_screen3) { lv_obj_del(scr_screen3); scr_screen3 = NULL; }
|
||
if (scr_screen4) { lv_obj_del(scr_screen4); scr_screen4 = NULL; }
|
||
|
||
/* Rebuild main screen so it picks up all state changes */
|
||
if (scr_main) {
|
||
lv_obj_del(scr_main);
|
||
scr_main = NULL;
|
||
}
|
||
create_screen_main();
|
||
lv_scr_load(scr_main);
|
||
}
|
||
|
||
static void btn_readings_cb(lv_event_t *e) {
|
||
(void)e;
|
||
create_popup_readings();
|
||
}
|
||
|
||
static void btn_confirm_alarm_cb(lv_event_t *e) {
|
||
int alarm_idx = (int)(intptr_t)lv_event_get_user_data(e);
|
||
if (alarm_idx >= 0 && alarm_idx < alarm_count) {
|
||
alarm_log[alarm_idx].confirmed = 1;
|
||
alarm_log[alarm_idx].active = 0;
|
||
snprintf(alarm_log[alarm_idx].confirm_timestamp, 20, "%02d.%02d.%04d %02d:%02d",
|
||
state.day, state.month, state.year, state.hour, state.minute);
|
||
}
|
||
/* Refresh journal screen */
|
||
if (scr_screen2) {
|
||
lv_obj_del(scr_screen2);
|
||
scr_screen2 = NULL;
|
||
}
|
||
create_screen_screen2();
|
||
lv_scr_load(scr_screen2);
|
||
}
|
||
|
||
static void btn_alarm_page_prev_cb(lv_event_t *e) {
|
||
(void)e;
|
||
if (alarm_page > 0) {
|
||
alarm_page--;
|
||
if (scr_screen2) {
|
||
lv_obj_del(scr_screen2);
|
||
scr_screen2 = NULL;
|
||
}
|
||
create_screen_screen2();
|
||
lv_scr_load(scr_screen2);
|
||
}
|
||
}
|
||
|
||
static void btn_alarm_page_next_cb(lv_event_t *e) {
|
||
(void)e;
|
||
int total_pages = (alarm_count + ALARMS_PER_PAGE - 1) / ALARMS_PER_PAGE;
|
||
if (alarm_page < total_pages - 1) {
|
||
alarm_page++;
|
||
if (scr_screen2) {
|
||
lv_obj_del(scr_screen2);
|
||
scr_screen2 = NULL;
|
||
}
|
||
create_screen_screen2();
|
||
lv_scr_load(scr_screen2);
|
||
}
|
||
}
|
||
|
||
static void btn_clear_log_cb(lv_event_t *e) {
|
||
(void)e;
|
||
/* Check if any active alarms */
|
||
int has_active = 0;
|
||
for (int i = 0; i < alarm_count; i++) {
|
||
if (alarm_log[i].active) {
|
||
has_active = 1;
|
||
break;
|
||
}
|
||
}
|
||
if (!has_active) {
|
||
alarm_count = 0;
|
||
alarm_page = 0;
|
||
/* Refresh journal screen */
|
||
if (scr_screen2) {
|
||
lv_obj_del(scr_screen2);
|
||
scr_screen2 = NULL;
|
||
}
|
||
create_screen_screen2();
|
||
lv_scr_load(scr_screen2);
|
||
}
|
||
}
|
||
|
||
/* Setpoints pagination callbacks - Work mode */
|
||
static void btn_setpoint_work_prev_cb(lv_event_t *e) {
|
||
(void)e;
|
||
if (setpoint_work_page > 0) {
|
||
setpoint_work_page--;
|
||
if (scr_screen3) {
|
||
lv_obj_del(scr_screen3);
|
||
scr_screen3 = NULL;
|
||
}
|
||
create_screen_screen3();
|
||
lv_scr_load(scr_screen3);
|
||
}
|
||
}
|
||
|
||
static void btn_setpoint_work_next_cb(lv_event_t *e) {
|
||
(void)e;
|
||
/* 8 parameters total, 4 per page = 2 pages */
|
||
if (setpoint_work_page < 1) {
|
||
setpoint_work_page++;
|
||
if (scr_screen3) {
|
||
lv_obj_del(scr_screen3);
|
||
scr_screen3 = NULL;
|
||
}
|
||
create_screen_screen3();
|
||
lv_scr_load(scr_screen3);
|
||
}
|
||
}
|
||
|
||
/* Setpoints pagination callbacks - Standby mode */
|
||
static void btn_setpoint_standby_prev_cb(lv_event_t *e) {
|
||
(void)e;
|
||
if (setpoint_standby_page > 0) {
|
||
setpoint_standby_page--;
|
||
if (scr_screen3) {
|
||
lv_obj_del(scr_screen3);
|
||
scr_screen3 = NULL;
|
||
}
|
||
create_screen_screen3();
|
||
lv_scr_load(scr_screen3);
|
||
}
|
||
}
|
||
|
||
static void btn_setpoint_standby_next_cb(lv_event_t *e) {
|
||
(void)e;
|
||
/* 8 parameters total, 4 per page = 2 pages */
|
||
if (setpoint_standby_page < 1) {
|
||
setpoint_standby_page++;
|
||
if (scr_screen3) {
|
||
lv_obj_del(scr_screen3);
|
||
scr_screen3 = NULL;
|
||
}
|
||
create_screen_screen3();
|
||
lv_scr_load(scr_screen3);
|
||
}
|
||
}
|
||
|
||
/* Config pagination callbacks */
|
||
static void btn_config_page_prev_cb(lv_event_t *e) {
|
||
(void)e;
|
||
if (config_page > 0) {
|
||
config_page--;
|
||
if (scr_screen4) {
|
||
lv_obj_del(scr_screen4);
|
||
scr_screen4 = NULL;
|
||
}
|
||
create_screen_config();
|
||
lv_scr_load(scr_screen4);
|
||
}
|
||
}
|
||
|
||
static void btn_config_page_next_cb(lv_event_t *e) {
|
||
(void)e;
|
||
/* Total 2 pages now */
|
||
if (config_page < 1) {
|
||
config_page++;
|
||
if (scr_screen4) {
|
||
lv_obj_del(scr_screen4);
|
||
scr_screen4 = NULL;
|
||
}
|
||
create_screen_config();
|
||
lv_scr_load(scr_screen4);
|
||
}
|
||
}
|
||
|
||
static void close_popup_cb(lv_event_t *e) {
|
||
lv_obj_t *popup = lv_event_get_user_data(e);
|
||
if (popup) {
|
||
lv_obj_del(popup);
|
||
}
|
||
if (popup == popup_readings) popup_readings = NULL;
|
||
if (popup == popup_alarm) popup_alarm = NULL;
|
||
if (popup == popup_module_config) {
|
||
popup_module_config = NULL;
|
||
/* Just update module displays, don't rebuild entire screen */
|
||
for (int i = 0; i < MODULE_COUNT; i++) {
|
||
update_settings_module_display(i);
|
||
}
|
||
}
|
||
if (popup == popup_password) {
|
||
popup_password = NULL;
|
||
memset(password_input, 0, sizeof(password_input));
|
||
}
|
||
if (popup == popup_param_editor) {
|
||
popup_param_editor = NULL;
|
||
memset(param_value_str, 0, sizeof(param_value_str));
|
||
param_being_edited = NULL;
|
||
}
|
||
}
|
||
|
||
static void btn_log_cb(lv_event_t *e) {
|
||
(void)e;
|
||
if (!scr_screen2) {
|
||
create_screen_screen2();
|
||
}
|
||
lv_scr_load(scr_screen2);
|
||
}
|
||
|
||
static void btn_settings_cb(lv_event_t *e) {
|
||
(void)e;
|
||
if (!scr_screen3) {
|
||
create_screen_screen3();
|
||
}
|
||
lv_scr_load(scr_screen3);
|
||
}
|
||
|
||
static void btn_config_cb(lv_event_t *e) {
|
||
(void)e;
|
||
password_target = 0;
|
||
create_popup_password(0);
|
||
}
|
||
|
||
/* Edit parameter callbacks - Work mode */
|
||
static void edit_work_temp_cb(lv_event_t *e) {
|
||
(void)e;
|
||
create_popup_param_editor("Темп.", &setpoint_work.temp, 0);
|
||
}
|
||
|
||
static void edit_work_hum_cb(lv_event_t *e) {
|
||
(void)e;
|
||
create_popup_param_editor("Влажность (Рабочий)", &setpoint_work.hum, 1);
|
||
}
|
||
|
||
static void edit_work_valve_inlet_cb(lv_event_t *e) {
|
||
(void)e;
|
||
create_popup_param_editor("Клапан притока (Рабочий)", &setpoint_work.valve_inlet, 1);
|
||
}
|
||
|
||
static void edit_work_valve_recirc_cb(lv_event_t *e) {
|
||
(void)e;
|
||
create_popup_param_editor("Клапан рециркуляции (Рабочий)", &setpoint_work.valve_recirc, 1);
|
||
}
|
||
|
||
/* Edit parameter callbacks - Standby mode */
|
||
static void edit_standby_temp_cb(lv_event_t *e) {
|
||
(void)e;
|
||
create_popup_param_editor("Температура (Дежурный)", &setpoint_standby.temp, 0);
|
||
}
|
||
|
||
static void edit_standby_hum_cb(lv_event_t *e) {
|
||
(void)e;
|
||
create_popup_param_editor("Влажность (Дежурный)", &setpoint_standby.hum, 1);
|
||
}
|
||
|
||
static void edit_standby_valve_inlet_cb(lv_event_t *e) {
|
||
(void)e;
|
||
create_popup_param_editor("Клапан притока (Дежурный)", &setpoint_standby.valve_inlet, 1);
|
||
}
|
||
|
||
static void edit_standby_valve_recirc_cb(lv_event_t *e) {
|
||
(void)e;
|
||
create_popup_param_editor("Клапан рециркуляции (Дежурный)", &setpoint_standby.valve_recirc, 1);
|
||
}
|
||
|
||
/* Heater mode callbacks */
|
||
static void edit_work_heater_cb(lv_event_t *e) {
|
||
(void)e;
|
||
setpoint_work.heater = (setpoint_work.heater + 1) % 3;
|
||
if (state.mode == 2) {
|
||
apply_mode_setpoints();
|
||
gui_update_values();
|
||
}
|
||
refresh_setpoints_display();
|
||
}
|
||
|
||
static void edit_standby_heater_cb(lv_event_t *e) {
|
||
(void)e;
|
||
setpoint_standby.heater = (setpoint_standby.heater + 1) % 3;
|
||
if (state.mode == 1) {
|
||
apply_mode_setpoints();
|
||
gui_update_values();
|
||
}
|
||
refresh_setpoints_display();
|
||
}
|
||
|
||
/* Fan toggle callbacks */
|
||
static void edit_work_fan_cb(lv_event_t *e) {
|
||
(void)e;
|
||
setpoint_work.fan = !setpoint_work.fan;
|
||
if (state.mode == 2) {
|
||
apply_mode_setpoints();
|
||
gui_update_values();
|
||
}
|
||
refresh_setpoints_display();
|
||
}
|
||
|
||
static void edit_standby_fan_cb(lv_event_t *e) {
|
||
(void)e;
|
||
setpoint_standby.fan = !setpoint_standby.fan;
|
||
if (state.mode == 1) {
|
||
apply_mode_setpoints();
|
||
gui_update_values();
|
||
}
|
||
refresh_setpoints_display();
|
||
}
|
||
|
||
/* KWS mode callbacks */
|
||
static void edit_work_kws_cb(lv_event_t *e) {
|
||
(void)e;
|
||
if (setpoint_work.kws_auto) {
|
||
setpoint_work.kws_auto = 0;
|
||
setpoint_work.kws_manual = 50.0f;
|
||
if (state.mode == 2) {
|
||
apply_mode_setpoints();
|
||
gui_update_values();
|
||
}
|
||
refresh_setpoints_display();
|
||
} else {
|
||
create_popup_param_editor("KWS ручной (Рабочий)", &setpoint_work.kws_manual, 1);
|
||
}
|
||
}
|
||
|
||
static void edit_standby_kws_cb(lv_event_t *e) {
|
||
(void)e;
|
||
if (setpoint_standby.kws_auto) {
|
||
setpoint_standby.kws_auto = 0;
|
||
setpoint_standby.kws_manual = 50.0f;
|
||
if (state.mode == 1) {
|
||
apply_mode_setpoints();
|
||
gui_update_values();
|
||
}
|
||
refresh_setpoints_display();
|
||
} else {
|
||
create_popup_param_editor("KWS ручной (Дежурный)", &setpoint_standby.kws_manual, 1);
|
||
}
|
||
}
|
||
|
||
/* PWW mode callbacks */
|
||
static void edit_work_pww_cb(lv_event_t *e) {
|
||
(void)e;
|
||
if (setpoint_work.pww_auto) {
|
||
setpoint_work.pww_auto = 0;
|
||
setpoint_work.pww_manual = 50.0f;
|
||
if (state.mode == 2) {
|
||
apply_mode_setpoints();
|
||
gui_update_values();
|
||
}
|
||
refresh_setpoints_display();
|
||
} else {
|
||
create_popup_param_editor("PWW ручной (Рабочий)", &setpoint_work.pww_manual, 1);
|
||
}
|
||
}
|
||
|
||
static void edit_standby_pww_cb(lv_event_t *e) {
|
||
(void)e;
|
||
if (setpoint_standby.pww_auto) {
|
||
setpoint_standby.pww_auto = 0;
|
||
setpoint_standby.pww_manual = 50.0f;
|
||
if (state.mode == 1) {
|
||
apply_mode_setpoints();
|
||
gui_update_values();
|
||
}
|
||
refresh_setpoints_display();
|
||
} else {
|
||
create_popup_param_editor("PWW ручной (Дежурный)", &setpoint_standby.pww_manual, 1);
|
||
}
|
||
}
|
||
|
||
/* Configuration edit callbacks */
|
||
static void edit_config_offset_cb(lv_event_t *e) {
|
||
float *offset_ptr = (float*)lv_event_get_user_data(e);
|
||
create_popup_param_editor("Коррекция", offset_ptr, 0);
|
||
}
|
||
|
||
static void toggle_recirc_valve_cb(lv_event_t *e) {
|
||
(void)e;
|
||
config.has_recirc_valve = !config.has_recirc_valve;
|
||
/* Invalidate screen3 so it rebuilds on next visit */
|
||
if (scr_screen3) {
|
||
lv_obj_del(scr_screen3);
|
||
scr_screen3 = NULL;
|
||
}
|
||
}
|
||
|
||
static void toggle_heater_cb(lv_event_t *e) {
|
||
(void)e;
|
||
config.has_heater = !config.has_heater;
|
||
/* Invalidate screen3 so it rebuilds on next visit */
|
||
if (scr_screen3) {
|
||
lv_obj_del(scr_screen3);
|
||
scr_screen3 = NULL;
|
||
}
|
||
}
|
||
|
||
static void edit_pid_pww_kp_cb(lv_event_t *e) {
|
||
(void)e;
|
||
create_popup_param_editor("PWW Kp", &config.pid_pww_kp, 0);
|
||
}
|
||
|
||
static void edit_pid_pww_ti_cb(lv_event_t *e) {
|
||
(void)e;
|
||
create_popup_param_editor("PWW Ti", &config.pid_pww_ti, 0);
|
||
}
|
||
|
||
static void edit_pid_pww_d_cb(lv_event_t *e) {
|
||
(void)e;
|
||
create_popup_param_editor("PWW D", &config.pid_pww_d, 0);
|
||
}
|
||
|
||
static void edit_pid_kws_kp_cb(lv_event_t *e) {
|
||
(void)e;
|
||
create_popup_param_editor("KWS Kp", &config.pid_kws_kp, 0);
|
||
}
|
||
|
||
static void edit_pid_kws_ti_cb(lv_event_t *e) {
|
||
(void)e;
|
||
create_popup_param_editor("KWS Ti", &config.pid_kws_ti, 0);
|
||
}
|
||
|
||
static void edit_pid_kws_d_cb(lv_event_t *e) {
|
||
(void)e;
|
||
create_popup_param_editor("KWS D", &config.pid_kws_d, 0);
|
||
}
|
||
|
||
static void toggle_force_mode_cb(lv_event_t *e) {
|
||
(void)e;
|
||
config.force_mode_enabled = !config.force_mode_enabled;
|
||
/* Rebuild config screen to reflect toggle change */
|
||
if (scr_screen4) {
|
||
lv_obj_del(scr_screen4);
|
||
scr_screen4 = NULL;
|
||
}
|
||
create_screen_config();
|
||
lv_scr_load(scr_screen4);
|
||
}
|
||
|
||
static void toggle_key_swap_cb(lv_event_t *e) {
|
||
(void)e;
|
||
config.key_swap_enabled = !config.key_swap_enabled;
|
||
/* Rebuild config screen to reflect toggle change */
|
||
if (scr_screen4) {
|
||
lv_obj_del(scr_screen4);
|
||
scr_screen4 = NULL;
|
||
}
|
||
create_screen_config();
|
||
lv_scr_load(scr_screen4);
|
||
}
|
||
|
||
/* Save setpoints callback (screen3) */
|
||
static void btn_save_setpoints_cb(lv_event_t *e) {
|
||
(void)e;
|
||
apply_mode_setpoints();
|
||
gui_update_values();
|
||
}
|
||
|
||
/* Default setpoints callback (screen3) */
|
||
static void btn_default_setpoints_cb(lv_event_t *e) {
|
||
(void)e;
|
||
setpoint_work.temp = 22.0f; setpoint_work.hum = 50.0f;
|
||
setpoint_work.valve_inlet = 75.0f; setpoint_work.valve_recirc = 25.0f;
|
||
setpoint_work.heater = 2; setpoint_work.fan = 1;
|
||
setpoint_work.kws_auto = 1; setpoint_work.kws_manual = 0;
|
||
setpoint_work.pww_auto = 1; setpoint_work.pww_manual = 0;
|
||
|
||
setpoint_standby.temp = 18.0f; setpoint_standby.hum = 45.0f;
|
||
setpoint_standby.valve_inlet = 35.0f; setpoint_standby.valve_recirc = 40.0f;
|
||
setpoint_standby.heater = 0; setpoint_standby.fan = 1;
|
||
setpoint_standby.kws_auto = 1; setpoint_standby.kws_manual = 0;
|
||
setpoint_standby.pww_auto = 1; setpoint_standby.pww_manual = 0;
|
||
|
||
apply_mode_setpoints();
|
||
refresh_setpoints_display();
|
||
}
|
||
|
||
/* Save config callback (screen4) */
|
||
static void btn_save_config_cb(lv_event_t *e) {
|
||
(void)e;
|
||
apply_mode_setpoints();
|
||
gui_update_values();
|
||
}
|
||
|
||
/* Default config callback (screen4) */
|
||
static void btn_default_config_cb(lv_event_t *e) {
|
||
(void)e;
|
||
config.temp_room_offset = 0.0f;
|
||
config.hum_room_offset = 0.0f;
|
||
config.temp_channel_offset = 0.0f;
|
||
config.hum_channel_offset = 0.0f;
|
||
config.valve_inlet_offset = 0.0f;
|
||
config.valve_recirc_offset = 0.0f;
|
||
config.pid_pww_kp = 1.0f; config.pid_pww_ti = 10.0f; config.pid_pww_d = 0.0f;
|
||
config.pid_kws_kp = 1.0f; config.pid_kws_ti = 10.0f; config.pid_kws_d = 0.0f;
|
||
config.force_mode_enabled = 0; config.force_mode_value = 1;
|
||
config.key_swap_enabled = 1;
|
||
|
||
if (scr_screen4) {
|
||
lv_obj_del(scr_screen4);
|
||
scr_screen4 = NULL;
|
||
}
|
||
create_screen_config();
|
||
lv_scr_load(scr_screen4);
|
||
}
|
||
|
||
/* Date/Time editor popup */
|
||
static lv_obj_t *popup_datetime = NULL;
|
||
static lv_obj_t *roller_day = NULL;
|
||
static lv_obj_t *roller_month = NULL;
|
||
static lv_obj_t *roller_year = NULL;
|
||
static lv_obj_t *roller_hour = NULL;
|
||
static lv_obj_t *roller_minute = NULL;
|
||
|
||
static void datetime_ok_cb(lv_event_t *e) {
|
||
(void)e;
|
||
if (roller_day && roller_month && roller_year && roller_hour && roller_minute) {
|
||
state.day = lv_roller_get_selected(roller_day) + 1;
|
||
state.month = lv_roller_get_selected(roller_month) + 1;
|
||
state.year = 2024 + lv_roller_get_selected(roller_year);
|
||
state.hour = lv_roller_get_selected(roller_hour);
|
||
state.minute = lv_roller_get_selected(roller_minute);
|
||
}
|
||
if (popup_datetime) {
|
||
lv_obj_del(popup_datetime);
|
||
popup_datetime = NULL;
|
||
roller_day = roller_month = roller_year = roller_hour = roller_minute = NULL;
|
||
}
|
||
/* Recreate config screen to show updated datetime */
|
||
if (scr_screen4) {
|
||
lv_obj_del(scr_screen4);
|
||
scr_screen4 = NULL;
|
||
}
|
||
create_screen_config();
|
||
lv_scr_load(scr_screen4);
|
||
}
|
||
|
||
static void datetime_close_cb(lv_event_t *e) {
|
||
(void)e;
|
||
if (popup_datetime) {
|
||
lv_obj_del(popup_datetime);
|
||
popup_datetime = NULL;
|
||
roller_day = roller_month = roller_year = roller_hour = roller_minute = NULL;
|
||
}
|
||
}
|
||
|
||
static void create_popup_datetime(void) {
|
||
if (popup_datetime) return;
|
||
|
||
popup_datetime = lv_obj_create(lv_screen_active());
|
||
lv_obj_set_size(popup_datetime, 420, 280);
|
||
lv_obj_center(popup_datetime);
|
||
lv_obj_set_style_bg_color(popup_datetime, lv_color_hex(COLOR_BG_PANEL), 0);
|
||
lv_obj_set_style_radius(popup_datetime, 8, 0);
|
||
lv_obj_set_style_border_width(popup_datetime, 1, 0);
|
||
lv_obj_set_style_border_color(popup_datetime, lv_color_hex(COLOR_BORDER), 0);
|
||
lv_obj_clear_flag(popup_datetime, LV_OBJ_FLAG_SCROLLABLE);
|
||
|
||
/* Title */
|
||
lv_obj_t *lbl = lv_label_create(popup_datetime);
|
||
lv_label_set_text(lbl, "Установка даты и времени");
|
||
lv_obj_set_pos(lbl, 20, 10);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
|
||
/* Close button (X) */
|
||
lv_obj_t *btn_close = lv_btn_create(popup_datetime);
|
||
lv_obj_set_pos(btn_close, 370, 6);
|
||
lv_obj_set_size(btn_close, 32, 32);
|
||
lv_obj_set_style_bg_color(btn_close, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(btn_close, 4, 0);
|
||
lbl = lv_label_create(btn_close);
|
||
lv_label_set_text(lbl, "X");
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_DANGER), 0);
|
||
lv_obj_center(lbl);
|
||
lv_obj_add_event_cb(btn_close, datetime_close_cb, LV_EVENT_CLICKED, NULL);
|
||
|
||
/* Date section label */
|
||
lbl = lv_label_create(popup_datetime);
|
||
lv_label_set_text(lbl, "Дата:");
|
||
lv_obj_set_pos(lbl, 20, 50);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_DIM), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
|
||
/* Day roller */
|
||
char days[31*4];
|
||
days[0] = '\0';
|
||
for (int i = 1; i <= 31; i++) {
|
||
char tmp[5];
|
||
snprintf(tmp, sizeof(tmp), "%02d\n", i);
|
||
strcat(days, tmp);
|
||
}
|
||
days[strlen(days)-1] = '\0';
|
||
|
||
roller_day = lv_roller_create(popup_datetime);
|
||
lv_roller_set_options(roller_day, days, LV_ROLLER_MODE_NORMAL);
|
||
lv_roller_set_selected(roller_day, state.day - 1, LV_ANIM_OFF);
|
||
lv_obj_set_pos(roller_day, 80, 45);
|
||
lv_obj_set_size(roller_day, 60, 80);
|
||
lv_obj_set_style_text_font(roller_day, &montserrat_16_ru_en, 0);
|
||
lv_obj_set_style_bg_color(roller_day, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_text_color(roller_day, lv_color_hex(0xFFFFFF), LV_PART_SELECTED);
|
||
|
||
/* Month roller */
|
||
roller_month = lv_roller_create(popup_datetime);
|
||
lv_roller_set_options(roller_month, "01\n02\n03\n04\n05\n06\n07\n08\n09\n10\n11\n12", LV_ROLLER_MODE_NORMAL);
|
||
lv_roller_set_selected(roller_month, state.month - 1, LV_ANIM_OFF);
|
||
lv_obj_set_pos(roller_month, 150, 45);
|
||
lv_obj_set_size(roller_month, 60, 80);
|
||
lv_obj_set_style_text_font(roller_month, &montserrat_16_ru_en, 0);
|
||
lv_obj_set_style_bg_color(roller_month, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_text_color(roller_month, lv_color_hex(0xFFFFFF), LV_PART_SELECTED);
|
||
|
||
/* Year roller (2024-2035) */
|
||
roller_year = lv_roller_create(popup_datetime);
|
||
lv_roller_set_options(roller_year, "2024\n2025\n2026\n2027\n2028\n2029\n2030\n2031\n2032\n2033\n2034\n2035", LV_ROLLER_MODE_NORMAL);
|
||
lv_roller_set_selected(roller_year, state.year - 2024, LV_ANIM_OFF);
|
||
lv_obj_set_pos(roller_year, 220, 45);
|
||
lv_obj_set_size(roller_year, 80, 80);
|
||
lv_obj_set_style_text_font(roller_year, &montserrat_16_ru_en, 0);
|
||
lv_obj_set_style_bg_color(roller_year, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_text_color(roller_year, lv_color_hex(0xFFFFFF), LV_PART_SELECTED);
|
||
|
||
/* Time section label */
|
||
lbl = lv_label_create(popup_datetime);
|
||
lv_label_set_text(lbl, "Время:");
|
||
lv_obj_set_pos(lbl, 20, 140);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_DIM), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
|
||
/* Hour roller */
|
||
char hours[24*4];
|
||
hours[0] = '\0';
|
||
for (int i = 0; i < 24; i++) {
|
||
char tmp[5];
|
||
snprintf(tmp, sizeof(tmp), "%02d\n", i);
|
||
strcat(hours, tmp);
|
||
}
|
||
hours[strlen(hours)-1] = '\0';
|
||
|
||
roller_hour = lv_roller_create(popup_datetime);
|
||
lv_roller_set_options(roller_hour, hours, LV_ROLLER_MODE_NORMAL);
|
||
lv_roller_set_selected(roller_hour, state.hour, LV_ANIM_OFF);
|
||
lv_obj_set_pos(roller_hour, 80, 135);
|
||
lv_obj_set_size(roller_hour, 60, 80);
|
||
lv_obj_set_style_text_font(roller_hour, &montserrat_16_ru_en, 0);
|
||
lv_obj_set_style_bg_color(roller_hour, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_text_color(roller_hour, lv_color_hex(0xFFFFFF), LV_PART_SELECTED);
|
||
|
||
lbl = lv_label_create(popup_datetime);
|
||
lv_label_set_text(lbl, ":");
|
||
lv_obj_set_pos(lbl, 145, 160);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
|
||
/* Minute roller */
|
||
char minutes[60*4];
|
||
minutes[0] = '\0';
|
||
for (int i = 0; i < 60; i++) {
|
||
char tmp[5];
|
||
snprintf(tmp, sizeof(tmp), "%02d\n", i);
|
||
strcat(minutes, tmp);
|
||
}
|
||
minutes[strlen(minutes)-1] = '\0';
|
||
|
||
roller_minute = lv_roller_create(popup_datetime);
|
||
lv_roller_set_options(roller_minute, minutes, LV_ROLLER_MODE_NORMAL);
|
||
lv_roller_set_selected(roller_minute, state.minute, LV_ANIM_OFF);
|
||
lv_obj_set_pos(roller_minute, 160, 135);
|
||
lv_obj_set_size(roller_minute, 60, 80);
|
||
lv_obj_set_style_text_font(roller_minute, &montserrat_16_ru_en, 0);
|
||
lv_obj_set_style_bg_color(roller_minute, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_text_color(roller_minute, lv_color_hex(0xFFFFFF), LV_PART_SELECTED);
|
||
|
||
/* OK button */
|
||
lv_obj_t *btn_ok = lv_btn_create(popup_datetime);
|
||
lv_obj_set_pos(btn_ok, 160, 230);
|
||
lv_obj_set_size(btn_ok, 100, 40);
|
||
lv_obj_set_style_bg_color(btn_ok, lv_color_hex(COLOR_ACCENT), 0);
|
||
lv_obj_set_style_radius(btn_ok, 6, 0);
|
||
lbl = lv_label_create(btn_ok);
|
||
lv_label_set_text(lbl, "OK");
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(0xFFFFFF), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
lv_obj_center(lbl);
|
||
lv_obj_add_event_cb(btn_ok, datetime_ok_cb, LV_EVENT_CLICKED, NULL);
|
||
}
|
||
|
||
static void edit_datetime_cb(lv_event_t *e) {
|
||
(void)e;
|
||
create_popup_datetime();
|
||
}
|
||
|
||
/* Password keypad callback */
|
||
static void password_keypad_cb(lv_event_t *e) {
|
||
const char *key = lv_event_get_user_data(e);
|
||
int len = strlen(password_input);
|
||
|
||
if (strcmp(key, "C") == 0) {
|
||
memset(password_input, 0, sizeof(password_input));
|
||
} else if (strcmp(key, "OK") == 0) {
|
||
if (strcmp(password_input, PASSWORD) == 0) {
|
||
/* Correct password - close popup */
|
||
if (popup_password) {
|
||
lv_obj_del(popup_password);
|
||
popup_password = NULL;
|
||
lbl_password_display = NULL;
|
||
}
|
||
memset(password_input, 0, sizeof(password_input));
|
||
|
||
/* Navigate to config screen */
|
||
if (password_target == 0) {
|
||
if (!scr_screen4) {
|
||
create_screen_config();
|
||
}
|
||
lv_scr_load(scr_screen4);
|
||
}
|
||
} else {
|
||
/* Wrong password */
|
||
if (lbl_password_display) {
|
||
lv_label_set_text(lbl_password_display, "НЕВЕРНЫЙ ПАРОЛЬ!");
|
||
lv_obj_set_style_text_color(lbl_password_display, lv_color_hex(COLOR_DANGER), 0);
|
||
}
|
||
memset(password_input, 0, sizeof(password_input));
|
||
}
|
||
} else if (len < 8) {
|
||
strcat(password_input, key);
|
||
}
|
||
|
||
/* Update display */
|
||
if (lbl_password_display) {
|
||
char display[20] = "";
|
||
for (int i = 0; i < (int)strlen(password_input); i++) {
|
||
strcat(display, "*");
|
||
}
|
||
if (strlen(display) == 0) {
|
||
lv_label_set_text(lbl_password_display, "Введите пароль");
|
||
lv_obj_set_style_text_color(lbl_password_display, lv_color_hex(COLOR_TEXT_DIM), 0);
|
||
} else {
|
||
lv_label_set_text(lbl_password_display, display);
|
||
lv_obj_set_style_text_color(lbl_password_display, lv_color_hex(COLOR_ACCENT), 0);
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Create password popup with keypad */
|
||
static void create_popup_password(int target) {
|
||
if (popup_password) return;
|
||
|
||
password_target = target;
|
||
memset(password_input, 0, sizeof(password_input));
|
||
|
||
popup_password = lv_obj_create(lv_screen_active());
|
||
lv_obj_set_size(popup_password, 300, 420);
|
||
lv_obj_center(popup_password);
|
||
lv_obj_set_style_bg_color(popup_password, lv_color_hex(COLOR_BG_PANEL), 0);
|
||
lv_obj_set_style_radius(popup_password, 8, 0);
|
||
lv_obj_set_style_border_width(popup_password, 1, 0);
|
||
lv_obj_set_style_border_color(popup_password, lv_color_hex(COLOR_BORDER), 0);
|
||
lv_obj_clear_flag(popup_password, LV_OBJ_FLAG_SCROLLABLE);
|
||
|
||
/* Title */
|
||
lv_obj_t *lbl = lv_label_create(popup_password);
|
||
lv_label_set_text(lbl, "Введите пароль");
|
||
lv_obj_set_pos(lbl, 20, 10);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
|
||
/* Close button (X) - справа */
|
||
lv_obj_t *btn_close = lv_btn_create(popup_password);
|
||
lv_obj_set_pos(btn_close, 230, 6);
|
||
lv_obj_set_size(btn_close, 32, 32);
|
||
lv_obj_set_style_bg_color(btn_close, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(btn_close, 4, 0);
|
||
lbl = lv_label_create(btn_close);
|
||
lv_label_set_text(lbl, "X");
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_DANGER), 0);
|
||
lv_obj_center(lbl);
|
||
lv_obj_add_event_cb(btn_close, close_popup_cb, LV_EVENT_CLICKED, popup_password);
|
||
|
||
/* Password display */
|
||
lbl_password_display = lv_label_create(popup_password);
|
||
lv_label_set_text(lbl_password_display, "_ _ _ _");
|
||
lv_obj_set_pos(lbl_password_display, 105, 50);
|
||
lv_obj_set_style_text_color(lbl_password_display, lv_color_hex(COLOR_TEXT_DIM), 0);
|
||
lv_obj_set_style_text_font(lbl_password_display, &montserrat_16_ru_en, 0);
|
||
|
||
/* Numeric keypad 3x4 */
|
||
const char *keys[12] = {"1", "2", "3", "4", "5", "6", "7", "8", "9", "C", "0", "OK"};
|
||
int key_size = 60;
|
||
int key_spacing = 8;
|
||
int start_x = 30;
|
||
int start_y = 90;
|
||
|
||
for (int i = 0; i < 12; i++) {
|
||
int row = i / 3;
|
||
int col = i % 3;
|
||
|
||
lv_obj_t *btn = lv_btn_create(popup_password);
|
||
lv_obj_set_pos(btn, start_x + col * (key_size + key_spacing),
|
||
start_y + row * (key_size + key_spacing));
|
||
lv_obj_set_size(btn, key_size, key_size);
|
||
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(btn, 6, 0);
|
||
|
||
lbl = lv_label_create(btn);
|
||
lv_label_set_text(lbl, keys[i]);
|
||
if (strcmp(keys[i], "C") == 0) {
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_WARNING), 0);
|
||
} else if (strcmp(keys[i], "OK") == 0) {
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_ACCENT), 0);
|
||
} else {
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
|
||
}
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
lv_obj_center(lbl);
|
||
|
||
lv_obj_add_event_cb(btn, password_keypad_cb, LV_EVENT_CLICKED, (void*)keys[i]);
|
||
}
|
||
}
|
||
|
||
/* Parameter editor keypad callback */
|
||
static void param_keypad_cb(lv_event_t *e) {
|
||
const char *key = lv_event_get_user_data(e);
|
||
int len = strlen(param_value_str);
|
||
|
||
if (strcmp(key, "C") == 0) {
|
||
memset(param_value_str, 0, sizeof(param_value_str));
|
||
} else if (strcmp(key, "<") == 0) {
|
||
/* Backspace - remove last character */
|
||
if (len > 0) {
|
||
param_value_str[len - 1] = '\0';
|
||
}
|
||
} else if (strcmp(key, ".") == 0) {
|
||
if (strchr(param_value_str, '.') == NULL) {
|
||
if (len == 0 || (len == 1 && param_value_str[0] == '-')) {
|
||
strcat(param_value_str, "0.");
|
||
} else {
|
||
strcat(param_value_str, ".");
|
||
}
|
||
}
|
||
} else if (strcmp(key, "-") == 0) {
|
||
if (len == 0) {
|
||
strcat(param_value_str, "-");
|
||
} else if (param_value_str[0] == '-') {
|
||
/* Remove minus */
|
||
memmove(param_value_str, param_value_str + 1, strlen(param_value_str));
|
||
} else {
|
||
/* Add minus at beginning */
|
||
memmove(param_value_str + 1, param_value_str, strlen(param_value_str) + 1);
|
||
param_value_str[0] = '-';
|
||
}
|
||
} else if (strcmp(key, "OK") == 0) {
|
||
if (param_being_edited && strlen(param_value_str) > 0) {
|
||
float val = (float)atof(param_value_str);
|
||
if (param_is_percentage) {
|
||
if (val < 0.0f) val = 0.0f;
|
||
if (val > 100.0f) val = 100.0f;
|
||
}
|
||
*param_being_edited = val;
|
||
}
|
||
if (popup_param_editor) {
|
||
lv_obj_del(popup_param_editor);
|
||
popup_param_editor = NULL;
|
||
lbl_param_value = NULL;
|
||
}
|
||
memset(param_value_str, 0, sizeof(param_value_str));
|
||
param_being_edited = NULL;
|
||
|
||
/* Apply setpoints to state so main screen reflects changes */
|
||
apply_mode_setpoints();
|
||
|
||
/* Refresh the current screen */
|
||
if (lv_screen_active() == scr_screen3) {
|
||
refresh_setpoints_display();
|
||
} else if (lv_screen_active() == scr_screen4) {
|
||
/* Rebuild config screen to show updated value */
|
||
lv_obj_del(scr_screen4);
|
||
scr_screen4 = NULL;
|
||
create_screen_config();
|
||
lv_scr_load(scr_screen4);
|
||
}
|
||
return;
|
||
} else if (len < 10) {
|
||
strcat(param_value_str, key);
|
||
}
|
||
|
||
if (lbl_param_value) {
|
||
if (strlen(param_value_str) == 0) {
|
||
lv_label_set_text(lbl_param_value, "0");
|
||
} else {
|
||
lv_label_set_text(lbl_param_value, param_value_str);
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Create parameter editor popup */
|
||
static void create_popup_param_editor(const char *name, float *param_ptr, int is_percentage) {
|
||
if (popup_param_editor) return;
|
||
|
||
param_being_edited = param_ptr;
|
||
param_is_percentage = is_percentage;
|
||
strncpy(param_name, name, 49);
|
||
param_name[49] = '\0';
|
||
memset(param_value_str, 0, sizeof(param_value_str));
|
||
snprintf(param_value_str, sizeof(param_value_str), "%.1f", *param_ptr);
|
||
|
||
popup_param_editor = lv_obj_create(lv_screen_active());
|
||
lv_obj_set_size(popup_param_editor, 360, 380);
|
||
lv_obj_center(popup_param_editor);
|
||
lv_obj_set_style_bg_color(popup_param_editor, lv_color_hex(COLOR_BG_PANEL), 0);
|
||
lv_obj_set_style_radius(popup_param_editor, 8, 0);
|
||
lv_obj_set_style_border_width(popup_param_editor, 1, 0);
|
||
lv_obj_set_style_border_color(popup_param_editor, lv_color_hex(COLOR_BORDER), 0);
|
||
lv_obj_clear_flag(popup_param_editor, LV_OBJ_FLAG_SCROLLABLE);
|
||
|
||
/* Title - shorter */
|
||
lv_obj_t *lbl = lv_label_create(popup_param_editor);
|
||
lv_label_set_text(lbl, param_name);
|
||
lv_obj_set_pos(lbl, 10, 10);
|
||
lv_obj_set_width(lbl, 260);
|
||
lv_label_set_long_mode(lbl, LV_LABEL_LONG_CLIP);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_DIM), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
|
||
/* Close button (X) - справа */
|
||
lv_obj_t *btn_close = lv_btn_create(popup_param_editor);
|
||
lv_obj_set_pos(btn_close, 290, 8);
|
||
lv_obj_set_size(btn_close, 32, 32);
|
||
lv_obj_set_style_bg_color(btn_close, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(btn_close, 4, 0);
|
||
lbl = lv_label_create(btn_close);
|
||
lv_label_set_text(lbl, "X");
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_DANGER), 0);
|
||
lv_obj_center(lbl);
|
||
lv_obj_add_event_cb(btn_close, close_popup_cb, LV_EVENT_CLICKED, popup_param_editor);
|
||
|
||
/* Value display with unit */
|
||
lbl_param_value = lv_label_create(popup_param_editor);
|
||
lv_label_set_text(lbl_param_value, param_value_str);
|
||
lv_obj_set_pos(lbl_param_value, 130, 50);
|
||
lv_obj_set_style_text_color(lbl_param_value, lv_color_hex(COLOR_ACCENT), 0);
|
||
lv_obj_set_style_text_font(lbl_param_value, &montserrat_16_ru_en, 0);
|
||
|
||
/* Unit label */
|
||
lbl = lv_label_create(popup_param_editor);
|
||
lv_label_set_text(lbl, is_percentage ? "%" : "°C");
|
||
lv_obj_set_pos(lbl, 210, 50);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_DIM), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
|
||
/* Numeric keypad 4x4 with decimal, minus, backspace */
|
||
const char *keys[16] = {"1", "2", "3", "C", "4", "5", "6", "<", "7", "8", "9", "-", "0", ".", "0", "."};
|
||
int key_size = 55;
|
||
int key_spacing = 6;
|
||
int start_x = 25;
|
||
int start_y = 85;
|
||
|
||
/* First 3 rows (12 buttons) */
|
||
for (int i = 0; i < 12; i++) {
|
||
int row = i / 4;
|
||
int col = i % 4;
|
||
|
||
lv_obj_t *btn = lv_btn_create(popup_param_editor);
|
||
lv_obj_set_pos(btn, start_x + col * (key_size + key_spacing),
|
||
start_y + row * (key_size + key_spacing));
|
||
lv_obj_set_size(btn, key_size, key_size);
|
||
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(btn, 6, 0);
|
||
|
||
lbl = lv_label_create(btn);
|
||
lv_label_set_text(lbl, keys[i]);
|
||
if (strcmp(keys[i], "C") == 0) {
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_DANGER), 0);
|
||
} else if (strcmp(keys[i], "<") == 0 || strcmp(keys[i], ".") == 0 || strcmp(keys[i], "-") == 0) {
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_WARNING), 0);
|
||
} else {
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
|
||
}
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
lv_obj_center(lbl);
|
||
|
||
lv_obj_add_event_cb(btn, param_keypad_cb, LV_EVENT_CLICKED, (void*)keys[i]);
|
||
}
|
||
|
||
/* Bottom row: 0, dot, OK */
|
||
lv_obj_t *btn = lv_btn_create(popup_param_editor);
|
||
lv_obj_set_pos(btn, start_x, start_y + 3 * (key_size + key_spacing));
|
||
lv_obj_set_size(btn, key_size, key_size);
|
||
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(btn, 6, 0);
|
||
lbl = lv_label_create(btn);
|
||
lv_label_set_text(lbl, "0");
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
lv_obj_center(lbl);
|
||
lv_obj_add_event_cb(btn, param_keypad_cb, LV_EVENT_CLICKED, (void*)"0");
|
||
|
||
btn = lv_btn_create(popup_param_editor);
|
||
lv_obj_set_pos(btn, start_x + (key_size + key_spacing), start_y + 3 * (key_size + key_spacing));
|
||
lv_obj_set_size(btn, key_size, key_size);
|
||
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(btn, 6, 0);
|
||
lbl = lv_label_create(btn);
|
||
lv_label_set_text(lbl, ".");
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_WARNING), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
lv_obj_center(lbl);
|
||
lv_obj_add_event_cb(btn, param_keypad_cb, LV_EVENT_CLICKED, (void*)".");
|
||
|
||
btn = lv_btn_create(popup_param_editor);
|
||
lv_obj_set_pos(btn, start_x + 2 * (key_size + key_spacing), start_y + 3 * (key_size + key_spacing));
|
||
lv_obj_set_size(btn, key_size * 2 + key_spacing, key_size);
|
||
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_ACCENT), 0);
|
||
lv_obj_set_style_radius(btn, 6, 0);
|
||
lbl = lv_label_create(btn);
|
||
lv_label_set_text(lbl, "OK");
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(0xFFFFFF), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
lv_obj_center(lbl);
|
||
lv_obj_add_event_cb(btn, param_keypad_cb, LV_EVENT_CLICKED, (void*)"OK");
|
||
}
|
||
|
||
/* ── Slider infrastructure for module config popup ── */
|
||
typedef struct {
|
||
float *target;
|
||
lv_obj_t *lbl_value;
|
||
float scale; /* slider_val * scale = real_val (e.g. 0.1 or 0.01) */
|
||
const char *fmt; /* printf format */
|
||
} slider_ud_t;
|
||
|
||
#define MAX_SLIDER_UD 6
|
||
static slider_ud_t slider_ud_pool[MAX_SLIDER_UD];
|
||
static int slider_ud_idx = 0;
|
||
|
||
static void config_slider_cb(lv_event_t *e) {
|
||
slider_ud_t *ud = (slider_ud_t*)lv_event_get_user_data(e);
|
||
lv_obj_t *slider = lv_event_get_target(e);
|
||
int val = lv_slider_get_value(slider);
|
||
float fval = val * ud->scale;
|
||
*ud->target = fval;
|
||
if (ud->lbl_value) {
|
||
char buf[24];
|
||
snprintf(buf, sizeof(buf), ud->fmt, fval);
|
||
lv_label_set_text(ud->lbl_value, buf);
|
||
}
|
||
}
|
||
|
||
/* Helper: create a label + slider + value row inside parent */
|
||
static void add_slider_row(lv_obj_t *parent, int y, const char *name,
|
||
float *target, float min_f, float max_f,
|
||
float scale, const char *fmt) {
|
||
if (slider_ud_idx >= MAX_SLIDER_UD) return;
|
||
slider_ud_t *ud = &slider_ud_pool[slider_ud_idx++];
|
||
ud->target = target;
|
||
ud->scale = scale;
|
||
ud->fmt = fmt;
|
||
|
||
/* Label */
|
||
lv_obj_t *lbl = lv_label_create(parent);
|
||
lv_label_set_text(lbl, name);
|
||
lv_obj_set_pos(lbl, 15, y + 2);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
|
||
/* Value label */
|
||
lv_obj_t *lbl_val = lv_label_create(parent);
|
||
char buf[24];
|
||
snprintf(buf, sizeof(buf), fmt, *target);
|
||
lv_label_set_text(lbl_val, buf);
|
||
lv_obj_set_pos(lbl_val, 300, y + 2);
|
||
lv_obj_set_style_text_color(lbl_val, lv_color_hex(COLOR_ACCENT), 0);
|
||
lv_obj_set_style_text_font(lbl_val, &montserrat_16_ru_en, 0);
|
||
ud->lbl_value = lbl_val;
|
||
|
||
/* Slider */
|
||
lv_obj_t *slider = lv_slider_create(parent);
|
||
lv_obj_set_pos(slider, 15, y + 26);
|
||
lv_obj_set_size(slider, 340, 12);
|
||
lv_slider_set_range(slider, (int32_t)(min_f / scale), (int32_t)(max_f / scale));
|
||
lv_slider_set_value(slider, (int32_t)(*target / scale), LV_ANIM_OFF);
|
||
lv_obj_set_style_bg_color(slider, lv_color_hex(COLOR_BG_BTN), LV_PART_MAIN);
|
||
lv_obj_set_style_bg_color(slider, lv_color_hex(COLOR_ACCENT), LV_PART_INDICATOR);
|
||
lv_obj_set_style_bg_color(slider, lv_color_hex(COLOR_TEXT), LV_PART_KNOB);
|
||
lv_obj_set_style_pad_all(slider, 4, LV_PART_KNOB);
|
||
lv_obj_add_event_cb(slider, config_slider_cb, LV_EVENT_VALUE_CHANGED, ud);
|
||
}
|
||
|
||
/* Create popup window for module configuration (settings screen) - slider-based */
|
||
static void create_popup_module_config(int module_idx) {
|
||
if (popup_module_config) {
|
||
lv_obj_del(popup_module_config);
|
||
}
|
||
slider_ud_idx = 0; /* reset slider userdata pool */
|
||
|
||
popup_module_config = lv_obj_create(lv_screen_active());
|
||
lv_obj_set_size(popup_module_config, 400, 350);
|
||
lv_obj_center(popup_module_config);
|
||
lv_obj_set_style_bg_color(popup_module_config, lv_color_hex(COLOR_BG_PANEL), 0);
|
||
lv_obj_set_style_radius(popup_module_config, 12, 0);
|
||
lv_obj_set_style_border_width(popup_module_config, 3, 0);
|
||
lv_obj_set_style_border_color(popup_module_config, lv_color_hex(COLOR_TEXT_ACCENT), 0);
|
||
lv_obj_clear_flag(popup_module_config, LV_OBJ_FLAG_SCROLLABLE);
|
||
|
||
/* Title */
|
||
char title[50];
|
||
snprintf(title, sizeof(title), "НАСТРОЙКА: %s", get_module_name(module_idx));
|
||
lv_obj_t *lbl = lv_label_create(popup_module_config);
|
||
lv_label_set_text(lbl, title);
|
||
lv_obj_set_pos(lbl, 20, 15);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
|
||
/* Close button (X) */
|
||
lv_obj_t *btn_close = lv_btn_create(popup_module_config);
|
||
lv_obj_set_pos(btn_close, 330, 10);
|
||
lv_obj_set_size(btn_close, 35, 35);
|
||
lv_obj_set_style_bg_color(btn_close, lv_color_hex(COLOR_DANGER), 0);
|
||
lv_obj_set_style_radius(btn_close, 6, 0);
|
||
lbl = lv_label_create(btn_close);
|
||
lv_label_set_text(lbl, "X");
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(0xFFFFFF), 0);
|
||
lv_obj_center(lbl);
|
||
lv_obj_add_event_cb(btn_close, close_popup_cb, LV_EVENT_CLICKED, popup_module_config);
|
||
|
||
int y = 55;
|
||
char buf[100];
|
||
|
||
switch (module_idx) {
|
||
case MODULE_VALVE_INLET:
|
||
add_slider_row(popup_module_config, y, "Коррекция:",
|
||
&config.valve_inlet_offset, -10.0f, 10.0f, 0.1f, "%+.1f%%");
|
||
break;
|
||
|
||
case MODULE_VALVE_RECIRC:
|
||
add_slider_row(popup_module_config, y, "Коррекция:",
|
||
&config.valve_recirc_offset, -10.0f, 10.0f, 0.1f, "%+.1f%%");
|
||
y += 60;
|
||
/* Toggle has_recirc_valve */
|
||
lbl = lv_label_create(popup_module_config);
|
||
snprintf(buf, sizeof(buf), "Наличие: %s", config.has_recirc_valve ? "ЕСТЬ" : "НЕТ");
|
||
lv_label_set_text(lbl, buf);
|
||
lv_obj_set_pos(lbl, 30, y);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
{
|
||
lv_obj_t *btn = lv_btn_create(popup_module_config);
|
||
lv_obj_set_pos(btn, 250, y - 5);
|
||
lv_obj_set_size(btn, 100, 35);
|
||
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(btn, 4, 0);
|
||
lbl = lv_label_create(btn);
|
||
lv_label_set_text(lbl, "Переключ");
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_WARNING), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
lv_obj_center(lbl);
|
||
lv_obj_add_event_cb(btn, toggle_recirc_valve_cb, LV_EVENT_CLICKED, NULL);
|
||
}
|
||
break;
|
||
|
||
case MODULE_HEATER:
|
||
lbl = lv_label_create(popup_module_config);
|
||
snprintf(buf, sizeof(buf), "Наличие: %s", config.has_heater ? "ЕСТЬ" : "НЕТ");
|
||
lv_label_set_text(lbl, buf);
|
||
lv_obj_set_pos(lbl, 30, y);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
{
|
||
lv_obj_t *btn = lv_btn_create(popup_module_config);
|
||
lv_obj_set_pos(btn, 250, y - 5);
|
||
lv_obj_set_size(btn, 100, 35);
|
||
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(btn, 4, 0);
|
||
lbl = lv_label_create(btn);
|
||
lv_label_set_text(lbl, "Переключ");
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_WARNING), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
lv_obj_center(lbl);
|
||
lv_obj_add_event_cb(btn, toggle_heater_cb, LV_EVENT_CLICKED, NULL);
|
||
}
|
||
break;
|
||
|
||
case MODULE_KWS:
|
||
lbl = lv_label_create(popup_module_config);
|
||
lv_label_set_text(lbl, "ПИД регулятор KWS:");
|
||
lv_obj_set_pos(lbl, 15, y);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_WARNING), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
y += 25;
|
||
add_slider_row(popup_module_config, y, "Kp:",
|
||
&config.pid_kws_kp, 0.0f, 10.0f, 0.01f, "%.2f");
|
||
y += 50;
|
||
add_slider_row(popup_module_config, y, "Ti:",
|
||
&config.pid_kws_ti, 0.0f, 100.0f, 0.1f, "%.1f");
|
||
y += 50;
|
||
add_slider_row(popup_module_config, y, "D:",
|
||
&config.pid_kws_d, 0.0f, 10.0f, 0.01f, "%.2f");
|
||
break;
|
||
|
||
case MODULE_PWW:
|
||
lbl = lv_label_create(popup_module_config);
|
||
lv_label_set_text(lbl, "ПИД регулятор PWW:");
|
||
lv_obj_set_pos(lbl, 15, y);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_WARNING), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
y += 25;
|
||
add_slider_row(popup_module_config, y, "Kp:",
|
||
&config.pid_pww_kp, 0.0f, 10.0f, 0.01f, "%.2f");
|
||
y += 50;
|
||
add_slider_row(popup_module_config, y, "Ti:",
|
||
&config.pid_pww_ti, 0.0f, 100.0f, 0.1f, "%.1f");
|
||
y += 50;
|
||
add_slider_row(popup_module_config, y, "D:",
|
||
&config.pid_pww_d, 0.0f, 10.0f, 0.01f, "%.2f");
|
||
break;
|
||
|
||
case MODULE_CHANNEL:
|
||
add_slider_row(popup_module_config, y, "Темп. коррекция:",
|
||
&config.temp_channel_offset, -10.0f, 10.0f, 0.1f, "%+.1f°C");
|
||
y += 60;
|
||
add_slider_row(popup_module_config, y, "Влажн. коррекция:",
|
||
&config.hum_channel_offset, -10.0f, 10.0f, 0.1f, "%+.1f%%");
|
||
break;
|
||
|
||
default:
|
||
lbl = lv_label_create(popup_module_config);
|
||
lv_label_set_text(lbl, "Нет доступных настроек\nдля этого модуля");
|
||
lv_obj_set_pos(lbl, 100, 150);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_DIM), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
lv_obj_set_style_text_align(lbl, LV_TEXT_ALIGN_CENTER, 0);
|
||
break;
|
||
}
|
||
}
|
||
|
||
/* Create popup window for sensor readings */
|
||
static void create_popup_readings(void) {
|
||
if (popup_readings) return;
|
||
|
||
popup_readings = lv_obj_create(lv_screen_active());
|
||
lv_obj_set_size(popup_readings, 400, 280);
|
||
lv_obj_center(popup_readings);
|
||
lv_obj_set_style_bg_color(popup_readings, lv_color_hex(COLOR_BG_PANEL), 0);
|
||
lv_obj_set_style_radius(popup_readings, 12, 0);
|
||
lv_obj_set_style_border_width(popup_readings, 3, 0);
|
||
lv_obj_set_style_border_color(popup_readings, lv_color_hex(COLOR_TEXT_ACCENT), 0);
|
||
lv_obj_clear_flag(popup_readings, LV_OBJ_FLAG_SCROLLABLE);
|
||
|
||
/* Title */
|
||
lv_obj_t *lbl = lv_label_create(popup_readings);
|
||
lv_label_set_text(lbl, "ПОКАЗАНИЯ ДАТЧИКОВ");
|
||
lv_obj_set_pos(lbl, 10, 15);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
|
||
/* Close button (X) - справа */
|
||
lv_obj_t *btn_close = lv_btn_create(popup_readings);
|
||
lv_obj_set_pos(btn_close, 330, 10);
|
||
lv_obj_set_size(btn_close, 35, 35);
|
||
lv_obj_set_style_bg_color(btn_close, lv_color_hex(COLOR_DANGER), 0);
|
||
lv_obj_set_style_radius(btn_close, 6, 0);
|
||
lbl = lv_label_create(btn_close);
|
||
lv_label_set_text(lbl, "X");
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(0xFFFFFF), 0);
|
||
lv_obj_center(lbl);
|
||
lv_obj_add_event_cb(btn_close, close_popup_cb, LV_EVENT_CLICKED, popup_readings);
|
||
|
||
/* Room sensors */
|
||
int y = 55;
|
||
char buf[64];
|
||
|
||
lbl = lv_label_create(popup_readings);
|
||
lv_label_set_text(lbl, "КОМНАТА:");
|
||
lv_obj_set_pos(lbl, 30, y);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_WARNING), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
|
||
y += 28;
|
||
lbl = lv_label_create(popup_readings);
|
||
snprintf(buf, sizeof(buf), "Температура: %.1f°C", state.temp_room / 10.0f);
|
||
lv_label_set_text(lbl, buf);
|
||
lv_obj_set_pos(lbl, 40, y);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_DANGER), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
|
||
y += 26;
|
||
lbl = lv_label_create(popup_readings);
|
||
snprintf(buf, sizeof(buf), "Влажность: %.1f%%", state.hum_room / 10.0f);
|
||
lv_label_set_text(lbl, buf);
|
||
lv_obj_set_pos(lbl, 40, y);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_ACCENT), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
|
||
/* Channel sensors */
|
||
y += 35;
|
||
lbl = lv_label_create(popup_readings);
|
||
lv_label_set_text(lbl, "КАНАЛ ПРИТОКА:");
|
||
lv_obj_set_pos(lbl, 30, y);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_WARNING), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
|
||
y += 28;
|
||
lbl = lv_label_create(popup_readings);
|
||
snprintf(buf, sizeof(buf), "Температура: %.1f°C", state.temp_channel / 10.0f);
|
||
lv_label_set_text(lbl, buf);
|
||
lv_obj_set_pos(lbl, 40, y);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_DANGER), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
|
||
y += 26;
|
||
lbl = lv_label_create(popup_readings);
|
||
snprintf(buf, sizeof(buf), "Влажность: %.1f%%", state.hum_channel / 10.0f);
|
||
lv_label_set_text(lbl, buf);
|
||
lv_obj_set_pos(lbl, 40, y);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_ACCENT), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
}
|
||
|
||
/* Add alarm to log */
|
||
static void add_alarm_to_log(const char *message, int active) {
|
||
if (alarm_count < MAX_ALARMS) {
|
||
strncpy(alarm_log[alarm_count].message, message, 79);
|
||
alarm_log[alarm_count].message[79] = '\0';
|
||
snprintf(alarm_log[alarm_count].timestamp, 20, "%02d.%02d.%04d %02d:%02d",
|
||
state.day, state.month, state.year, state.hour, state.minute);
|
||
alarm_log[alarm_count].active = active;
|
||
alarm_log[alarm_count].confirmed = 0;
|
||
alarm_log[alarm_count].confirm_timestamp[0] = '\0';
|
||
alarm_count++;
|
||
}
|
||
}
|
||
|
||
/* Create alarm popup (does NOT add to log - caller must add separately) */
|
||
static void create_popup_alarm(const char *message) {
|
||
if (popup_alarm) {
|
||
lv_obj_del(popup_alarm);
|
||
}
|
||
|
||
popup_alarm = lv_obj_create(lv_screen_active());
|
||
lv_obj_set_size(popup_alarm, 400, 240);
|
||
lv_obj_center(popup_alarm);
|
||
lv_obj_set_style_bg_color(popup_alarm, lv_color_hex(COLOR_DANGER), 0);
|
||
lv_obj_set_style_radius(popup_alarm, 12, 0);
|
||
lv_obj_set_style_border_width(popup_alarm, 4, 0);
|
||
lv_obj_set_style_border_color(popup_alarm, lv_color_hex(0xFF0000), 0);
|
||
lv_obj_clear_flag(popup_alarm, LV_OBJ_FLAG_SCROLLABLE);
|
||
|
||
/* Title */
|
||
lv_obj_t *lbl = lv_label_create(popup_alarm);
|
||
lv_label_set_text(lbl, "АВАРИЯ");
|
||
lv_obj_set_pos(lbl, 20, 15);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(0xFFFFFF), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
|
||
/* Close button (X) - справа в углу */
|
||
lv_obj_t *btn_close = lv_btn_create(popup_alarm);
|
||
lv_obj_set_pos(btn_close, 330, 10);
|
||
lv_obj_set_size(btn_close, 35, 35);
|
||
lv_obj_set_style_bg_color(btn_close, lv_color_hex(0x800000), 0);
|
||
lv_obj_set_style_radius(btn_close, 6, 0);
|
||
lbl = lv_label_create(btn_close);
|
||
lv_label_set_text(lbl, "X");
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(0xFFFFFF), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
lv_obj_center(lbl);
|
||
lv_obj_add_event_cb(btn_close, close_popup_cb, LV_EVENT_CLICKED, popup_alarm);
|
||
|
||
/* Message */
|
||
lbl = lv_label_create(popup_alarm);
|
||
lv_label_set_text(lbl, message);
|
||
lv_obj_set_pos(lbl, 30, 70);
|
||
lv_obj_set_width(lbl, 340);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(0xFFFFFF), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
lv_label_set_long_mode(lbl, LV_LABEL_LONG_WRAP);
|
||
lv_obj_set_style_text_align(lbl, LV_TEXT_ALIGN_CENTER, 0);
|
||
}
|
||
|
||
static void create_screen_main(void) {
|
||
lv_obj_t *scr = lv_obj_create(NULL);
|
||
scr_main = scr;
|
||
lv_obj_set_style_bg_color(scr, lv_color_hex(COLOR_BG_DARK), 0);
|
||
lv_obj_set_style_bg_opa(scr, LV_OPA_COVER, 0);
|
||
lv_obj_clear_flag(scr, LV_OBJ_FLAG_SCROLLABLE);
|
||
|
||
/* === STATUS PANEL === (нижняя часть экрана) */
|
||
lv_obj_t *panel_status = lv_obj_create(scr);
|
||
lv_obj_set_pos(panel_status, 10, 300);
|
||
lv_obj_set_size(panel_status, 240, 170);
|
||
lv_obj_set_style_bg_color(panel_status, lv_color_hex(COLOR_BG_PANEL), 0);
|
||
lv_obj_set_style_radius(panel_status, 6, 0);
|
||
lv_obj_set_style_border_width(panel_status, 0, 0);
|
||
lv_obj_clear_flag(panel_status, LV_OBJ_FLAG_SCROLLABLE);
|
||
|
||
lv_obj_t *lbl = lv_label_create(panel_status);
|
||
lv_label_set_text(lbl, "Состояние");
|
||
lv_obj_set_pos(lbl, 10, 5);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_DIM), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
|
||
lbl_room_num = lv_label_create(panel_status);
|
||
lv_label_set_text(lbl_room_num, "Комната: 101");
|
||
lv_obj_set_pos(lbl_room_num, 10, 30);
|
||
lv_obj_set_style_text_color(lbl_room_num, lv_color_hex(COLOR_TEXT), 0);
|
||
lv_obj_set_style_text_font(lbl_room_num, &montserrat_16_ru_en, 0);
|
||
|
||
lbl_master_slave = lv_label_create(panel_status);
|
||
lv_label_set_text(lbl_master_slave, "Мастер");
|
||
lv_obj_set_pos(lbl_master_slave, 10, 55);
|
||
lv_obj_set_style_text_color(lbl_master_slave, lv_color_hex(COLOR_ACCENT), 0);
|
||
lv_obj_set_style_text_font(lbl_master_slave, &montserrat_16_ru_en, 0);
|
||
|
||
lbl_season = lv_label_create(panel_status);
|
||
{
|
||
const char *seasons[] = {"Зима", "Лето", "Переход"};
|
||
char sbuf[32];
|
||
snprintf(sbuf, sizeof(sbuf), "Сезон: %s", seasons[state.season % 3]);
|
||
lv_label_set_text(lbl_season, sbuf);
|
||
}
|
||
lv_obj_set_pos(lbl_season, 10, 80);
|
||
lv_obj_set_style_text_color(lbl_season, lv_color_hex(COLOR_TEXT_ACCENT), 0);
|
||
lv_obj_set_style_text_font(lbl_season, &montserrat_16_ru_en, 0);
|
||
|
||
lbl_mode = lv_label_create(panel_status);
|
||
lv_label_set_text(lbl_mode, "Режим: Дежурный");
|
||
lv_obj_set_pos(lbl_mode, 10, 105);
|
||
lv_obj_set_style_text_color(lbl_mode, lv_color_hex(COLOR_WARNING), 0);
|
||
lv_obj_set_style_text_font(lbl_mode, &montserrat_16_ru_en, 0);
|
||
|
||
lbl_datetime = lv_label_create(panel_status);
|
||
{
|
||
char dt_buf[32];
|
||
snprintf(dt_buf, sizeof(dt_buf), "%02d.%02d.%04d %02d:%02d",
|
||
state.day, state.month, state.year, state.hour, state.minute);
|
||
lv_label_set_text(lbl_datetime, dt_buf);
|
||
}
|
||
lv_obj_set_pos(lbl_datetime, 10, 130);
|
||
lv_obj_set_style_text_color(lbl_datetime, lv_color_hex(COLOR_TEXT_DIM), 0);
|
||
lv_obj_set_style_text_font(lbl_datetime, &montserrat_16_ru_en, 0);
|
||
|
||
/* === CLIMATE PANEL === */
|
||
lv_obj_t *panel_climate = lv_obj_create(scr);
|
||
lv_obj_set_pos(panel_climate, 260, 300);
|
||
lv_obj_set_size(panel_climate, 230, 170);
|
||
lv_obj_set_style_bg_color(panel_climate, lv_color_hex(COLOR_BG_PANEL), 0);
|
||
lv_obj_set_style_radius(panel_climate, 6, 0);
|
||
lv_obj_set_style_border_width(panel_climate, 0, 0);
|
||
lv_obj_clear_flag(panel_climate, LV_OBJ_FLAG_SCROLLABLE);
|
||
|
||
lbl = lv_label_create(panel_climate);
|
||
lv_label_set_text(lbl, "Комната");
|
||
lv_obj_set_pos(lbl, 10, 5);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_DIM), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
|
||
lbl = lv_label_create(panel_climate);
|
||
lv_label_set_text(lbl, "Температура:");
|
||
lv_obj_set_pos(lbl, 10, 35);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_DIM), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
|
||
lbl_temp_room = lv_label_create(panel_climate);
|
||
lv_label_set_text(lbl_temp_room, "--.-°C");
|
||
lv_obj_set_pos(lbl_temp_room, 130, 35);
|
||
lv_obj_set_style_text_color(lbl_temp_room, lv_color_hex(COLOR_DANGER), 0);
|
||
lv_obj_set_style_text_font(lbl_temp_room, &montserrat_16_ru_en, 0);
|
||
|
||
lbl = lv_label_create(panel_climate);
|
||
lv_label_set_text(lbl, "Влажность:");
|
||
lv_obj_set_pos(lbl, 10, 65);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_DIM), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
|
||
lbl_hum_room = lv_label_create(panel_climate);
|
||
lv_label_set_text(lbl_hum_room, "--.-%");
|
||
lv_obj_set_pos(lbl_hum_room, 130, 65);
|
||
lv_obj_set_style_text_color(lbl_hum_room, lv_color_hex(COLOR_TEXT_ACCENT), 0);
|
||
lv_obj_set_style_text_font(lbl_hum_room, &montserrat_16_ru_en, 0);
|
||
|
||
/* Channel info */
|
||
lbl = lv_label_create(panel_climate);
|
||
lv_label_set_text(lbl, "Канал:");
|
||
lv_obj_set_pos(lbl, 10, 100);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_DIM), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
|
||
lbl_temp_channel = lv_label_create(panel_climate);
|
||
lv_label_set_text(lbl_temp_channel, "--.-°C");
|
||
lv_obj_set_pos(lbl_temp_channel, 70, 100);
|
||
lv_obj_set_style_text_color(lbl_temp_channel, lv_color_hex(COLOR_TEXT), 0);
|
||
lv_obj_set_style_text_font(lbl_temp_channel, &montserrat_16_ru_en, 0);
|
||
|
||
lbl_hum_channel = lv_label_create(panel_climate);
|
||
lv_label_set_text(lbl_hum_channel, "--%");
|
||
lv_obj_set_pos(lbl_hum_channel, 150, 100);
|
||
lv_obj_set_style_text_color(lbl_hum_channel, lv_color_hex(COLOR_TEXT), 0);
|
||
lv_obj_set_style_text_font(lbl_hum_channel, &montserrat_16_ru_en, 0);
|
||
|
||
/* === CONTROL BUTTONS === (правая часть нижней половины) */
|
||
int btn_x = 500;
|
||
int btn_y = 300;
|
||
|
||
/* Stop button */
|
||
btn_stop_mode = lv_btn_create(scr);
|
||
lv_obj_set_pos(btn_stop_mode, btn_x, btn_y);
|
||
lv_obj_set_size(btn_stop_mode, 140, 100);
|
||
lv_obj_set_style_bg_color(btn_stop_mode, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(btn_stop_mode, 6, 0);
|
||
lbl_stop_mode = lv_label_create(btn_stop_mode);
|
||
lv_label_set_text(lbl_stop_mode, "ОСТАНОВ");
|
||
lv_obj_set_style_text_color(lbl_stop_mode, lv_color_hex(COLOR_DANGER), 0);
|
||
lv_obj_set_style_text_font(lbl_stop_mode, &montserrat_16_ru_en, 0);
|
||
lv_obj_center(lbl_stop_mode);
|
||
lv_obj_add_event_cb(btn_stop_mode, btn_stop_cb, LV_EVENT_CLICKED, NULL);
|
||
|
||
/* Standby button */
|
||
btn_standby_mode = lv_btn_create(scr);
|
||
lv_obj_set_pos(btn_standby_mode, btn_x + 150, btn_y);
|
||
lv_obj_set_size(btn_standby_mode, 140, 48);
|
||
lv_obj_set_style_bg_color(btn_standby_mode, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(btn_standby_mode, 6, 0);
|
||
lbl_standby_mode = lv_label_create(btn_standby_mode);
|
||
lv_label_set_text(lbl_standby_mode, "Дежурный");
|
||
lv_obj_set_style_text_color(lbl_standby_mode, lv_color_hex(COLOR_WARNING), 0);
|
||
lv_obj_set_style_text_font(lbl_standby_mode, &montserrat_16_ru_en, 0);
|
||
lv_obj_center(lbl_standby_mode);
|
||
lv_obj_add_event_cb(btn_standby_mode, btn_standby_cb, LV_EVENT_CLICKED, NULL);
|
||
|
||
/* Work button */
|
||
btn_work_mode = lv_btn_create(scr);
|
||
lv_obj_set_pos(btn_work_mode, btn_x + 150, btn_y + 52);
|
||
lv_obj_set_size(btn_work_mode, 140, 48);
|
||
lv_obj_set_style_bg_color(btn_work_mode, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(btn_work_mode, 6, 0);
|
||
lbl_work_mode = lv_label_create(btn_work_mode);
|
||
lv_label_set_text(lbl_work_mode, "Рабочий");
|
||
lv_obj_set_style_text_color(lbl_work_mode, lv_color_hex(COLOR_ACCENT), 0);
|
||
lv_obj_set_style_text_font(lbl_work_mode, &montserrat_16_ru_en, 0);
|
||
lv_obj_center(lbl_work_mode);
|
||
lv_obj_add_event_cb(btn_work_mode, btn_work_cb, LV_EVENT_CLICKED, NULL);
|
||
|
||
/* Menu button */
|
||
lv_obj_t *btn = lv_btn_create(scr);
|
||
lv_obj_set_pos(btn, btn_x, btn_y + 108);
|
||
lv_obj_set_size(btn, 140, 48);
|
||
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(btn, 6, 0);
|
||
lbl = lv_label_create(btn);
|
||
lv_label_set_text(lbl, "Меню");
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
lv_obj_center(lbl);
|
||
lv_obj_add_event_cb(btn, btn_menu_cb, LV_EVENT_CLICKED, NULL);
|
||
|
||
/* Readings button */
|
||
btn = lv_btn_create(scr);
|
||
lv_obj_set_pos(btn, btn_x + 150, btn_y + 108);
|
||
lv_obj_set_size(btn, 140, 48);
|
||
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(btn, 6, 0);
|
||
lbl = lv_label_create(btn);
|
||
lv_label_set_text(lbl, "Показания");
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_ACCENT), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
lv_obj_center(lbl);
|
||
lv_obj_add_event_cb(btn, btn_readings_cb, LV_EVENT_CLICKED, NULL);
|
||
|
||
/* Update mode button colors based on current state */
|
||
update_mode_button_colors();
|
||
|
||
/* === EQUIPMENT SCHEMA AREA === (верхняя половина экрана) */
|
||
schema_panel = lv_obj_create(scr);
|
||
lv_obj_set_pos(schema_panel, 10, 10);
|
||
lv_obj_set_size(schema_panel, 780, 280);
|
||
lv_obj_set_style_bg_color(schema_panel, lv_color_hex(COLOR_BG_PANEL), 0);
|
||
lv_obj_set_style_radius(schema_panel, 6, 0);
|
||
lv_obj_set_style_border_width(schema_panel, 0, 0);
|
||
lv_obj_clear_flag(schema_panel, LV_OBJ_FLAG_SCROLLABLE);
|
||
|
||
/* Title hint */
|
||
lv_obj_t *hint1 = lv_label_create(schema_panel);
|
||
lv_label_set_text(hint1, "Схема установки:");
|
||
lv_obj_set_pos(hint1, 10, 5);
|
||
lv_obj_set_style_text_color(hint1, lv_color_hex(COLOR_TEXT_DIM), 0);
|
||
lv_obj_set_style_text_font(hint1, &montserrat_16_ru_en, 0);
|
||
|
||
/* Layout: pipe_img + module + pipe_img + ... + pipe_img
|
||
* Use pipe image (64x64) cropped into narrow containers between modules.
|
||
* Container clips image to show only a horizontal strip. */
|
||
#define PIPE_W 12
|
||
#define PIPE_H 64
|
||
int total_w = MODULE_COUNT * MODULE_WIDTH + (MODULE_COUNT + 1) * PIPE_W;
|
||
int start_x = (780 - total_w) / 2;
|
||
int pipe_y = MODULES_Y + (MODULE_HEIGHT - PIPE_H) / 2;
|
||
|
||
for (int i = 0; i < MODULE_COUNT; i++) {
|
||
int pipe_x = start_x + i * (MODULE_WIDTH + PIPE_W);
|
||
/* Pipe image in a clipping container */
|
||
lv_obj_t *pcont = lv_obj_create(schema_panel);
|
||
lv_obj_set_pos(pcont, pipe_x, pipe_y);
|
||
lv_obj_set_size(pcont, PIPE_W, PIPE_H);
|
||
lv_obj_set_style_pad_all(pcont, 0, 0);
|
||
lv_obj_set_style_bg_opa(pcont, LV_OPA_TRANSP, 0);
|
||
lv_obj_set_style_border_width(pcont, 0, 0);
|
||
lv_obj_clear_flag(pcont, LV_OBJ_FLAG_SCROLLABLE | LV_OBJ_FLAG_CLICKABLE);
|
||
lv_obj_t *pimg = lv_image_create(pcont);
|
||
lv_image_set_src(pimg, &img_pipe);
|
||
lv_obj_align(pimg, LV_ALIGN_CENTER, 0, 0);
|
||
|
||
int mod_x = pipe_x + PIPE_W;
|
||
create_module_widget(schema_panel, i, mod_x, MODULES_Y);
|
||
}
|
||
/* Final pipe after last module */
|
||
{
|
||
int pipe_x = start_x + MODULE_COUNT * (MODULE_WIDTH + PIPE_W);
|
||
lv_obj_t *pcont = lv_obj_create(schema_panel);
|
||
lv_obj_set_pos(pcont, pipe_x, pipe_y);
|
||
lv_obj_set_size(pcont, PIPE_W, PIPE_H);
|
||
lv_obj_set_style_pad_all(pcont, 0, 0);
|
||
lv_obj_set_style_bg_opa(pcont, LV_OPA_TRANSP, 0);
|
||
lv_obj_set_style_border_width(pcont, 0, 0);
|
||
lv_obj_clear_flag(pcont, LV_OBJ_FLAG_SCROLLABLE | LV_OBJ_FLAG_CLICKABLE);
|
||
lv_obj_t *pimg = lv_image_create(pcont);
|
||
lv_image_set_src(pimg, &img_pipe);
|
||
lv_obj_align(pimg, LV_ALIGN_CENTER, 0, 0);
|
||
}
|
||
|
||
/* Update all module displays with current values */
|
||
for (int i = 0; i < MODULE_COUNT; i++) {
|
||
update_module_display(i);
|
||
}
|
||
|
||
/* Update mode button colors to reflect initial state */
|
||
update_mode_button_colors();
|
||
}
|
||
|
||
static void create_screen_screen1(void) {
|
||
lv_obj_t *scr = lv_obj_create(NULL);
|
||
scr_screen1 = scr;
|
||
lv_obj_set_style_bg_color(scr, lv_color_hex(COLOR_BG_DARK), 0);
|
||
lv_obj_set_style_bg_opa(scr, LV_OPA_COVER, 0);
|
||
lv_obj_clear_flag(scr, LV_OBJ_FLAG_SCROLLABLE);
|
||
|
||
/* Title */
|
||
lv_obj_t *lbl = lv_label_create(scr);
|
||
lv_label_set_text(lbl, "МЕНЮ");
|
||
lv_obj_set_pos(lbl, 370, 30);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
|
||
int btn_width = 300;
|
||
int btn_height = 55;
|
||
int x = 250;
|
||
int y = 90;
|
||
int spacing = 15;
|
||
|
||
/* Button: Back to Main Screen */
|
||
lv_obj_t *btn = lv_btn_create(scr);
|
||
lv_obj_set_pos(btn, x, y);
|
||
lv_obj_set_size(btn, btn_width, btn_height);
|
||
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(btn, 6, 0);
|
||
lbl = lv_label_create(btn);
|
||
lv_label_set_text(lbl, "Главный экран");
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_ACCENT), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
lv_obj_center(lbl);
|
||
lv_obj_add_event_cb(btn, btn_back_to_main_cb, LV_EVENT_CLICKED, NULL);
|
||
|
||
y += btn_height + spacing;
|
||
|
||
/* Button: Log (Журнал) */
|
||
btn = lv_btn_create(scr);
|
||
lv_obj_set_pos(btn, x, y);
|
||
lv_obj_set_size(btn, btn_width, btn_height);
|
||
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(btn, 6, 0);
|
||
lbl = lv_label_create(btn);
|
||
lv_label_set_text(lbl, "Журнал аварий");
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_WARNING), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
lv_obj_center(lbl);
|
||
lv_obj_add_event_cb(btn, btn_log_cb, LV_EVENT_CLICKED, NULL);
|
||
|
||
y += btn_height + spacing;
|
||
|
||
/* Button: Settings Parameters (Уставка параметров) */
|
||
btn = lv_btn_create(scr);
|
||
lv_obj_set_pos(btn, x, y);
|
||
lv_obj_set_size(btn, btn_width, btn_height);
|
||
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(btn, 6, 0);
|
||
lbl = lv_label_create(btn);
|
||
lv_label_set_text(lbl, "Уставка параметров");
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
lv_obj_center(lbl);
|
||
lv_obj_add_event_cb(btn, btn_settings_cb, LV_EVENT_CLICKED, NULL);
|
||
|
||
y += btn_height + spacing;
|
||
|
||
/* Button: Configuration (Настройки - требует пароль) */
|
||
btn = lv_btn_create(scr);
|
||
lv_obj_set_pos(btn, x, y);
|
||
lv_obj_set_size(btn, btn_width, btn_height);
|
||
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(btn, 6, 0);
|
||
lbl = lv_label_create(btn);
|
||
lv_label_set_text(lbl, "Настройки");
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_DANGER), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
lv_obj_center(lbl);
|
||
lv_obj_add_event_cb(btn, btn_config_cb, LV_EVENT_CLICKED, NULL);
|
||
}
|
||
|
||
static void create_screen_screen2(void) {
|
||
lv_obj_t *scr = lv_obj_create(NULL);
|
||
scr_screen2 = scr;
|
||
lv_obj_set_style_bg_color(scr, lv_color_hex(COLOR_BG_DARK), 0);
|
||
lv_obj_set_style_bg_opa(scr, LV_OPA_COVER, 0);
|
||
lv_obj_clear_flag(scr, LV_OBJ_FLAG_SCROLLABLE);
|
||
|
||
/* Title */
|
||
lv_obj_t *lbl = lv_label_create(scr);
|
||
lv_label_set_text(lbl, "ЖУРНАЛ АВАРИЙ");
|
||
lv_obj_set_pos(lbl, 300, 15);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
|
||
/* Date/Time display */
|
||
char datetime_buf[32];
|
||
snprintf(datetime_buf, sizeof(datetime_buf), "%02d.%02d.%04d %02d:%02d",
|
||
state.day, state.month, state.year, state.hour, state.minute);
|
||
lbl = lv_label_create(scr);
|
||
lv_label_set_text(lbl, datetime_buf);
|
||
lv_obj_set_pos(lbl, 620, 15);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_DIM), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
|
||
/* Log panel */
|
||
lv_obj_t *log_panel = lv_obj_create(scr);
|
||
lv_obj_set_pos(log_panel, 20, 50);
|
||
lv_obj_set_size(log_panel, 760, 310);
|
||
lv_obj_set_style_bg_color(log_panel, lv_color_hex(COLOR_BG_PANEL), 0);
|
||
lv_obj_set_style_radius(log_panel, 8, 0);
|
||
lv_obj_set_style_border_width(log_panel, 1, 0);
|
||
lv_obj_set_style_border_color(log_panel, lv_color_hex(COLOR_BORDER), 0);
|
||
lv_obj_clear_flag(log_panel, LV_OBJ_FLAG_SCROLLABLE);
|
||
|
||
/* Calculate pagination */
|
||
int total_pages = (alarm_count + ALARMS_PER_PAGE - 1) / ALARMS_PER_PAGE;
|
||
if (total_pages == 0) total_pages = 1;
|
||
if (alarm_page >= total_pages) alarm_page = total_pages - 1;
|
||
if (alarm_page < 0) alarm_page = 0;
|
||
|
||
int start_idx = alarm_page * ALARMS_PER_PAGE;
|
||
int end_idx = start_idx + ALARMS_PER_PAGE;
|
||
if (end_idx > alarm_count) end_idx = alarm_count;
|
||
|
||
/* Display log entries for current page */
|
||
int y = 10;
|
||
if (alarm_count == 0) {
|
||
lbl = lv_label_create(log_panel);
|
||
lv_label_set_text(lbl, "Аварий нет");
|
||
lv_obj_set_pos(lbl, 320, 150);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_ACCENT), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
} else {
|
||
for (int i = start_idx; i < end_idx; i++) {
|
||
/* Alarm entry container */
|
||
lv_obj_t *entry_cont = lv_obj_create(log_panel);
|
||
lv_obj_set_pos(entry_cont, 5, y);
|
||
lv_obj_set_size(entry_cont, 740, 75);
|
||
lv_obj_set_style_bg_color(entry_cont, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(entry_cont, 6, 0);
|
||
lv_obj_set_style_border_width(entry_cont, 1, 0);
|
||
lv_obj_set_style_border_color(entry_cont, alarm_log[i].active ? lv_color_hex(COLOR_DANGER) : lv_color_hex(COLOR_BORDER), 0);
|
||
lv_obj_clear_flag(entry_cont, LV_OBJ_FLAG_SCROLLABLE);
|
||
|
||
/* Alarm message */
|
||
char buf[120];
|
||
snprintf(buf, sizeof(buf), "%s %s", alarm_log[i].message, alarm_log[i].timestamp);
|
||
lbl = lv_label_create(entry_cont);
|
||
lv_label_set_text(lbl, buf);
|
||
lv_obj_set_pos(lbl, 10, 8);
|
||
lv_obj_set_width(lbl, 550);
|
||
lv_label_set_long_mode(lbl, LV_LABEL_LONG_CLIP);
|
||
if (alarm_log[i].active) {
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_DANGER), 0);
|
||
} else {
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
|
||
}
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
|
||
/* Confirm button or confirmed status */
|
||
if (alarm_log[i].confirmed) {
|
||
/* Show confirmed status */
|
||
lbl = lv_label_create(entry_cont);
|
||
lv_label_set_text(lbl, "Подтверждено");
|
||
lv_obj_set_pos(lbl, 580, 10);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_ACCENT), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
|
||
lbl = lv_label_create(entry_cont);
|
||
lv_label_set_text(lbl, alarm_log[i].confirm_timestamp);
|
||
lv_obj_set_pos(lbl, 580, 35);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_DIM), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
} else {
|
||
/* Confirm button - centered vertically */
|
||
lv_obj_t *btn_confirm = lv_btn_create(entry_cont);
|
||
lv_obj_set_pos(btn_confirm, 580, 17);
|
||
lv_obj_set_size(btn_confirm, 130, 40);
|
||
lv_obj_set_style_bg_color(btn_confirm, lv_color_hex(COLOR_BG_MODULE), 0);
|
||
lv_obj_set_style_radius(btn_confirm, 4, 0);
|
||
lbl = lv_label_create(btn_confirm);
|
||
lv_label_set_text(lbl, "Подтвердить");
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_ACCENT), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
lv_obj_center(lbl);
|
||
lv_obj_add_event_cb(btn_confirm, btn_confirm_alarm_cb, LV_EVENT_CLICKED, (void*)(intptr_t)i);
|
||
}
|
||
|
||
y += 80;
|
||
}
|
||
}
|
||
|
||
/* Buttons row */
|
||
int btn_y = 370;
|
||
|
||
/* Left arrow button */
|
||
lv_obj_t *btn = lv_btn_create(scr);
|
||
lv_obj_set_pos(btn, 300, btn_y);
|
||
lv_obj_set_size(btn, 60, 40);
|
||
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(btn, 6, 0);
|
||
lbl = lv_label_create(btn);
|
||
lv_label_set_text(lbl, "<");
|
||
lv_obj_set_style_text_color(lbl, alarm_page > 0 ? lv_color_hex(COLOR_TEXT) : lv_color_hex(COLOR_TEXT_DIM), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
lv_obj_center(lbl);
|
||
lv_obj_add_event_cb(btn, btn_alarm_page_prev_cb, LV_EVENT_CLICKED, NULL);
|
||
|
||
/* Page indicator */
|
||
char page_buf[16];
|
||
snprintf(page_buf, sizeof(page_buf), "%d / %d", alarm_page + 1, total_pages);
|
||
lbl = lv_label_create(scr);
|
||
lv_label_set_text(lbl, page_buf);
|
||
lv_obj_set_pos(lbl, 380, btn_y + 10);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
|
||
/* Right arrow button */
|
||
btn = lv_btn_create(scr);
|
||
lv_obj_set_pos(btn, 440, btn_y);
|
||
lv_obj_set_size(btn, 60, 40);
|
||
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(btn, 6, 0);
|
||
lbl = lv_label_create(btn);
|
||
lv_label_set_text(lbl, ">");
|
||
lv_obj_set_style_text_color(lbl, alarm_page < total_pages - 1 ? lv_color_hex(COLOR_TEXT) : lv_color_hex(COLOR_TEXT_DIM), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
lv_obj_center(lbl);
|
||
lv_obj_add_event_cb(btn, btn_alarm_page_next_cb, LV_EVENT_CLICKED, NULL);
|
||
|
||
/* Bottom buttons row */
|
||
btn_y = 420;
|
||
|
||
/* Clear Log button */
|
||
btn = lv_btn_create(scr);
|
||
lv_obj_set_pos(btn, 250, btn_y);
|
||
lv_obj_set_size(btn, 160, 40);
|
||
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(btn, 6, 0);
|
||
lbl = lv_label_create(btn);
|
||
lv_label_set_text(lbl, "Очистить журнал");
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_DANGER), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
lv_obj_center(lbl);
|
||
lv_obj_add_event_cb(btn, btn_clear_log_cb, LV_EVENT_CLICKED, NULL);
|
||
|
||
/* Back to Menu button */
|
||
btn = lv_btn_create(scr);
|
||
lv_obj_set_pos(btn, 430, btn_y);
|
||
lv_obj_set_size(btn, 120, 40);
|
||
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(btn, 6, 0);
|
||
lbl = lv_label_create(btn);
|
||
lv_label_set_text(lbl, "Меню");
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
lv_obj_center(lbl);
|
||
lv_obj_add_event_cb(btn, btn_menu_cb, LV_EVENT_CLICKED, NULL);
|
||
}
|
||
|
||
static void create_screen_screen3(void) {
|
||
lv_obj_t *scr = lv_obj_create(NULL);
|
||
scr_screen3 = scr;
|
||
lv_obj_set_style_bg_color(scr, lv_color_hex(COLOR_BG_DARK), 0);
|
||
lv_obj_set_style_bg_opa(scr, LV_OPA_COVER, 0);
|
||
lv_obj_clear_flag(scr, LV_OBJ_FLAG_SCROLLABLE);
|
||
|
||
/* Title */
|
||
lv_obj_t *lbl = lv_label_create(scr);
|
||
lv_label_set_text(lbl, "УСТАВКА ПАРАМЕТРОВ");
|
||
lv_obj_set_pos(lbl, 290, 10);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
|
||
char buf[64];
|
||
int row_h = 38;
|
||
const char *heater_modes[] = {"ВЫКЛ", "ВКЛ", "АВТО"};
|
||
|
||
/* Left panel - Work mode - NO SCROLL, with pagination */
|
||
lv_obj_t *panel_work = lv_obj_create(scr);
|
||
lv_obj_set_pos(panel_work, 15, 40);
|
||
lv_obj_set_size(panel_work, 380, 270);
|
||
lv_obj_set_style_bg_color(panel_work, lv_color_hex(COLOR_BG_PANEL), 0);
|
||
lv_obj_set_style_radius(panel_work, 6, 0);
|
||
lv_obj_set_style_border_width(panel_work, 0, 0);
|
||
lv_obj_clear_flag(panel_work, LV_OBJ_FLAG_SCROLLABLE);
|
||
|
||
lbl = lv_label_create(panel_work);
|
||
lv_label_set_text(lbl, "РАБОЧИЙ");
|
||
lv_obj_set_pos(lbl, 10, 5);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_ACCENT), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
|
||
/* Page indicator for Work mode */
|
||
snprintf(buf, sizeof(buf), "%d/2", setpoint_work_page + 1);
|
||
lbl = lv_label_create(panel_work);
|
||
lv_label_set_text(lbl, buf);
|
||
lv_obj_set_pos(lbl, 330, 5);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_DIM), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
|
||
int y = 32;
|
||
lv_obj_t *btn;
|
||
|
||
/* Work mode parameters - page 0: Temp, Hum, Valve inlet, Valve recirc */
|
||
/* Work mode parameters - page 1: Heater, Fan, KWS, PWW */
|
||
if (setpoint_work_page == 0) {
|
||
/* Temperature */
|
||
btn = lv_btn_create(panel_work);
|
||
lv_obj_set_pos(btn, 5, y);
|
||
lv_obj_set_size(btn, 350, row_h);
|
||
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(btn, 4, 0);
|
||
snprintf(buf, sizeof(buf), "Температура: %.1f°C", setpoint_work.temp);
|
||
lbl_work_temp = lv_label_create(btn);
|
||
lv_label_set_text(lbl_work_temp, buf);
|
||
lv_obj_center(lbl_work_temp);
|
||
lv_obj_set_style_text_color(lbl_work_temp, lv_color_hex(COLOR_TEXT), 0);
|
||
lv_obj_set_style_text_font(lbl_work_temp, &montserrat_16_ru_en, 0);
|
||
lv_obj_add_event_cb(btn, edit_work_temp_cb, LV_EVENT_CLICKED, NULL);
|
||
|
||
y += row_h + 6;
|
||
/* Humidity */
|
||
btn = lv_btn_create(panel_work);
|
||
lv_obj_set_pos(btn, 5, y);
|
||
lv_obj_set_size(btn, 350, row_h);
|
||
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(btn, 4, 0);
|
||
snprintf(buf, sizeof(buf), "Влажность: %.1f%%", setpoint_work.hum);
|
||
lbl_work_hum = lv_label_create(btn);
|
||
lv_label_set_text(lbl_work_hum, buf);
|
||
lv_obj_center(lbl_work_hum);
|
||
lv_obj_set_style_text_color(lbl_work_hum, lv_color_hex(COLOR_TEXT), 0);
|
||
lv_obj_set_style_text_font(lbl_work_hum, &montserrat_16_ru_en, 0);
|
||
lv_obj_add_event_cb(btn, edit_work_hum_cb, LV_EVENT_CLICKED, NULL);
|
||
|
||
y += row_h + 6;
|
||
/* Valve inlet */
|
||
btn = lv_btn_create(panel_work);
|
||
lv_obj_set_pos(btn, 5, y);
|
||
lv_obj_set_size(btn, 350, row_h);
|
||
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(btn, 4, 0);
|
||
snprintf(buf, sizeof(buf), "Клапан притока: %.1f%%", setpoint_work.valve_inlet);
|
||
lbl = lv_label_create(btn);
|
||
lv_label_set_text(lbl, buf);
|
||
lv_obj_center(lbl);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
lv_obj_add_event_cb(btn, edit_work_valve_inlet_cb, LV_EVENT_CLICKED, NULL);
|
||
|
||
y += row_h + 6;
|
||
/* Valve recirc */
|
||
btn = lv_btn_create(panel_work);
|
||
lv_obj_set_pos(btn, 5, y);
|
||
lv_obj_set_size(btn, 350, row_h);
|
||
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(btn, 4, 0);
|
||
snprintf(buf, sizeof(buf), "Клапан рецирк: %.1f%%", setpoint_work.valve_recirc);
|
||
lbl = lv_label_create(btn);
|
||
lv_label_set_text(lbl, buf);
|
||
lv_obj_center(lbl);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
lv_obj_add_event_cb(btn, edit_work_valve_recirc_cb, LV_EVENT_CLICKED, NULL);
|
||
} else {
|
||
/* Page 1: Heater, Fan, KWS, PWW */
|
||
/* Heater mode */
|
||
btn = lv_btn_create(panel_work);
|
||
lv_obj_set_pos(btn, 5, y);
|
||
lv_obj_set_size(btn, 350, row_h);
|
||
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(btn, 4, 0);
|
||
snprintf(buf, sizeof(buf), "Электронагрев: %s", heater_modes[setpoint_work.heater]);
|
||
lbl = lv_label_create(btn);
|
||
lv_label_set_text(lbl, buf);
|
||
lv_obj_center(lbl);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
lv_obj_add_event_cb(btn, edit_work_heater_cb, LV_EVENT_CLICKED, NULL);
|
||
|
||
y += row_h + 6;
|
||
/* Fan toggle */
|
||
btn = lv_btn_create(panel_work);
|
||
lv_obj_set_pos(btn, 5, y);
|
||
lv_obj_set_size(btn, 350, row_h);
|
||
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(btn, 4, 0);
|
||
snprintf(buf, sizeof(buf), "Вентилятор: %s", setpoint_work.fan ? "ВКЛ" : "ВЫКЛ");
|
||
lbl = lv_label_create(btn);
|
||
lv_label_set_text(lbl, buf);
|
||
lv_obj_center(lbl);
|
||
lv_obj_set_style_text_color(lbl, setpoint_work.fan ? lv_color_hex(COLOR_ACCENT) : lv_color_hex(COLOR_TEXT_DIM), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
lv_obj_add_event_cb(btn, edit_work_fan_cb, LV_EVENT_CLICKED, NULL);
|
||
|
||
y += row_h + 6;
|
||
/* KWS mode */
|
||
btn = lv_btn_create(panel_work);
|
||
lv_obj_set_pos(btn, 5, y);
|
||
lv_obj_set_size(btn, 350, row_h);
|
||
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(btn, 4, 0);
|
||
if (setpoint_work.kws_auto) {
|
||
snprintf(buf, sizeof(buf), "KWS: АВТО");
|
||
} else {
|
||
snprintf(buf, sizeof(buf), "KWS: %.1f%%", setpoint_work.kws_manual);
|
||
}
|
||
lbl = lv_label_create(btn);
|
||
lv_label_set_text(lbl, buf);
|
||
lv_obj_center(lbl);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
lv_obj_add_event_cb(btn, edit_work_kws_cb, LV_EVENT_CLICKED, NULL);
|
||
|
||
y += row_h + 6;
|
||
/* PWW mode */
|
||
btn = lv_btn_create(panel_work);
|
||
lv_obj_set_pos(btn, 5, y);
|
||
lv_obj_set_size(btn, 350, row_h);
|
||
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(btn, 4, 0);
|
||
if (setpoint_work.pww_auto) {
|
||
snprintf(buf, sizeof(buf), "PWW: АВТО");
|
||
} else {
|
||
snprintf(buf, sizeof(buf), "PWW: %.1f%%", setpoint_work.pww_manual);
|
||
}
|
||
lbl = lv_label_create(btn);
|
||
lv_label_set_text(lbl, buf);
|
||
lv_obj_center(lbl);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
lv_obj_add_event_cb(btn, edit_work_pww_cb, LV_EVENT_CLICKED, NULL);
|
||
}
|
||
|
||
/* Pagination buttons for Work panel */
|
||
btn = lv_btn_create(panel_work);
|
||
lv_obj_set_pos(btn, 80, 216);
|
||
lv_obj_set_size(btn, 80, 38);
|
||
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(btn, 4, 0);
|
||
lbl = lv_label_create(btn);
|
||
lv_label_set_text(lbl, "<");
|
||
lv_obj_set_style_text_color(lbl, setpoint_work_page > 0 ? lv_color_hex(COLOR_TEXT) : lv_color_hex(COLOR_TEXT_DIM), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
lv_obj_center(lbl);
|
||
lv_obj_add_event_cb(btn, btn_setpoint_work_prev_cb, LV_EVENT_CLICKED, NULL);
|
||
|
||
/* Page indicator */
|
||
snprintf(buf, sizeof(buf), "%d/2", setpoint_work_page + 1);
|
||
lbl = lv_label_create(panel_work);
|
||
lv_label_set_text(lbl, buf);
|
||
lv_obj_set_pos(lbl, 180, 226);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_DIM), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
|
||
btn = lv_btn_create(panel_work);
|
||
lv_obj_set_pos(btn, 210, 216);
|
||
lv_obj_set_size(btn, 80, 38);
|
||
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(btn, 4, 0);
|
||
lbl = lv_label_create(btn);
|
||
lv_label_set_text(lbl, ">");
|
||
lv_obj_set_style_text_color(lbl, setpoint_work_page < 1 ? lv_color_hex(COLOR_TEXT) : lv_color_hex(COLOR_TEXT_DIM), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
lv_obj_center(lbl);
|
||
lv_obj_add_event_cb(btn, btn_setpoint_work_next_cb, LV_EVENT_CLICKED, NULL);
|
||
|
||
/* Right panel - Standby mode - NO SCROLL, with pagination */
|
||
lv_obj_t *panel_standby = lv_obj_create(scr);
|
||
lv_obj_set_pos(panel_standby, 405, 40);
|
||
lv_obj_set_size(panel_standby, 380, 270);
|
||
lv_obj_set_style_bg_color(panel_standby, lv_color_hex(COLOR_BG_PANEL), 0);
|
||
lv_obj_set_style_radius(panel_standby, 6, 0);
|
||
lv_obj_set_style_border_width(panel_standby, 0, 0);
|
||
lv_obj_clear_flag(panel_standby, LV_OBJ_FLAG_SCROLLABLE);
|
||
|
||
lbl = lv_label_create(panel_standby);
|
||
lv_label_set_text(lbl, "ДЕЖУРНЫЙ");
|
||
lv_obj_set_pos(lbl, 10, 5);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_WARNING), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
|
||
/* Page indicator for Standby mode */
|
||
snprintf(buf, sizeof(buf), "%d/2", setpoint_standby_page + 1);
|
||
lbl = lv_label_create(panel_standby);
|
||
lv_label_set_text(lbl, buf);
|
||
lv_obj_set_pos(lbl, 330, 5);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_DIM), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
|
||
y = 32;
|
||
|
||
/* Standby mode parameters - page 0: Temp, Hum, Valve inlet, Valve recirc */
|
||
/* Standby mode parameters - page 1: Heater, Fan, KWS, PWW */
|
||
if (setpoint_standby_page == 0) {
|
||
/* Standby Temperature */
|
||
btn = lv_btn_create(panel_standby);
|
||
lv_obj_set_pos(btn, 5, y);
|
||
lv_obj_set_size(btn, 350, row_h);
|
||
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(btn, 4, 0);
|
||
snprintf(buf, sizeof(buf), "Температура: %.1f°C", setpoint_standby.temp);
|
||
lbl_standby_temp = lv_label_create(btn);
|
||
lv_label_set_text(lbl_standby_temp, buf);
|
||
lv_obj_center(lbl_standby_temp);
|
||
lv_obj_set_style_text_color(lbl_standby_temp, lv_color_hex(COLOR_TEXT), 0);
|
||
lv_obj_set_style_text_font(lbl_standby_temp, &montserrat_16_ru_en, 0);
|
||
lv_obj_add_event_cb(btn, edit_standby_temp_cb, LV_EVENT_CLICKED, NULL);
|
||
|
||
y += row_h + 6;
|
||
/* Standby Humidity */
|
||
btn = lv_btn_create(panel_standby);
|
||
lv_obj_set_pos(btn, 5, y);
|
||
lv_obj_set_size(btn, 350, row_h);
|
||
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(btn, 4, 0);
|
||
snprintf(buf, sizeof(buf), "Влажность: %.1f%%", setpoint_standby.hum);
|
||
lbl_standby_hum = lv_label_create(btn);
|
||
lv_label_set_text(lbl_standby_hum, buf);
|
||
lv_obj_center(lbl_standby_hum);
|
||
lv_obj_set_style_text_color(lbl_standby_hum, lv_color_hex(COLOR_TEXT), 0);
|
||
lv_obj_set_style_text_font(lbl_standby_hum, &montserrat_16_ru_en, 0);
|
||
lv_obj_add_event_cb(btn, edit_standby_hum_cb, LV_EVENT_CLICKED, NULL);
|
||
|
||
y += row_h + 6;
|
||
/* Standby Valve inlet */
|
||
btn = lv_btn_create(panel_standby);
|
||
lv_obj_set_pos(btn, 5, y);
|
||
lv_obj_set_size(btn, 350, row_h);
|
||
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(btn, 4, 0);
|
||
snprintf(buf, sizeof(buf), "Клапан притока: %.1f%%", setpoint_standby.valve_inlet);
|
||
lbl = lv_label_create(btn);
|
||
lv_label_set_text(lbl, buf);
|
||
lv_obj_center(lbl);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
lv_obj_add_event_cb(btn, edit_standby_valve_inlet_cb, LV_EVENT_CLICKED, NULL);
|
||
|
||
y += row_h + 6;
|
||
/* Standby Valve recirc */
|
||
btn = lv_btn_create(panel_standby);
|
||
lv_obj_set_pos(btn, 5, y);
|
||
lv_obj_set_size(btn, 350, row_h);
|
||
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(btn, 4, 0);
|
||
snprintf(buf, sizeof(buf), "Клапан рецирк: %.1f%%", setpoint_standby.valve_recirc);
|
||
lbl = lv_label_create(btn);
|
||
lv_label_set_text(lbl, buf);
|
||
lv_obj_center(lbl);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
lv_obj_add_event_cb(btn, edit_standby_valve_recirc_cb, LV_EVENT_CLICKED, NULL);
|
||
} else {
|
||
/* Page 1: Heater, Fan, KWS, PWW */
|
||
/* Heater mode */
|
||
btn = lv_btn_create(panel_standby);
|
||
lv_obj_set_pos(btn, 5, y);
|
||
lv_obj_set_size(btn, 350, row_h);
|
||
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(btn, 4, 0);
|
||
snprintf(buf, sizeof(buf), "Электронагрев: %s", heater_modes[setpoint_standby.heater]);
|
||
lbl = lv_label_create(btn);
|
||
lv_label_set_text(lbl, buf);
|
||
lv_obj_center(lbl);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
lv_obj_add_event_cb(btn, edit_standby_heater_cb, LV_EVENT_CLICKED, NULL);
|
||
|
||
y += row_h + 6;
|
||
/* Fan toggle */
|
||
btn = lv_btn_create(panel_standby);
|
||
lv_obj_set_pos(btn, 5, y);
|
||
lv_obj_set_size(btn, 350, row_h);
|
||
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(btn, 4, 0);
|
||
snprintf(buf, sizeof(buf), "Вентилятор: %s", setpoint_standby.fan ? "ВКЛ" : "ВЫКЛ");
|
||
lbl = lv_label_create(btn);
|
||
lv_label_set_text(lbl, buf);
|
||
lv_obj_center(lbl);
|
||
lv_obj_set_style_text_color(lbl, setpoint_standby.fan ? lv_color_hex(COLOR_ACCENT) : lv_color_hex(COLOR_TEXT_DIM), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
lv_obj_add_event_cb(btn, edit_standby_fan_cb, LV_EVENT_CLICKED, NULL);
|
||
|
||
y += row_h + 6;
|
||
/* KWS mode */
|
||
btn = lv_btn_create(panel_standby);
|
||
lv_obj_set_pos(btn, 5, y);
|
||
lv_obj_set_size(btn, 350, row_h);
|
||
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(btn, 4, 0);
|
||
if (setpoint_standby.kws_auto) {
|
||
snprintf(buf, sizeof(buf), "KWS: АВТО");
|
||
} else {
|
||
snprintf(buf, sizeof(buf), "KWS: %.1f%%", setpoint_standby.kws_manual);
|
||
}
|
||
lbl = lv_label_create(btn);
|
||
lv_label_set_text(lbl, buf);
|
||
lv_obj_center(lbl);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
lv_obj_add_event_cb(btn, edit_standby_kws_cb, LV_EVENT_CLICKED, NULL);
|
||
|
||
y += row_h + 6;
|
||
/* PWW mode */
|
||
btn = lv_btn_create(panel_standby);
|
||
lv_obj_set_pos(btn, 5, y);
|
||
lv_obj_set_size(btn, 350, row_h);
|
||
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(btn, 4, 0);
|
||
if (setpoint_standby.pww_auto) {
|
||
snprintf(buf, sizeof(buf), "PWW: АВТО");
|
||
} else {
|
||
snprintf(buf, sizeof(buf), "PWW: %.1f%%", setpoint_standby.pww_manual);
|
||
}
|
||
lbl = lv_label_create(btn);
|
||
lv_label_set_text(lbl, buf);
|
||
lv_obj_center(lbl);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
lv_obj_add_event_cb(btn, edit_standby_pww_cb, LV_EVENT_CLICKED, NULL);
|
||
}
|
||
|
||
/* Pagination buttons for Standby panel */
|
||
btn = lv_btn_create(panel_standby);
|
||
lv_obj_set_pos(btn, 80, 216);
|
||
lv_obj_set_size(btn, 80, 38);
|
||
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(btn, 4, 0);
|
||
lbl = lv_label_create(btn);
|
||
lv_label_set_text(lbl, "<");
|
||
lv_obj_set_style_text_color(lbl, setpoint_standby_page > 0 ? lv_color_hex(COLOR_TEXT) : lv_color_hex(COLOR_TEXT_DIM), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
lv_obj_center(lbl);
|
||
lv_obj_add_event_cb(btn, btn_setpoint_standby_prev_cb, LV_EVENT_CLICKED, NULL);
|
||
|
||
/* Page indicator */
|
||
snprintf(buf, sizeof(buf), "%d/2", setpoint_standby_page + 1);
|
||
lbl = lv_label_create(panel_standby);
|
||
lv_label_set_text(lbl, buf);
|
||
lv_obj_set_pos(lbl, 180, 226);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_DIM), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
|
||
btn = lv_btn_create(panel_standby);
|
||
lv_obj_set_pos(btn, 210, 216);
|
||
lv_obj_set_size(btn, 80, 38);
|
||
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(btn, 4, 0);
|
||
lbl = lv_label_create(btn);
|
||
lv_label_set_text(lbl, ">");
|
||
lv_obj_set_style_text_color(lbl, setpoint_standby_page < 1 ? lv_color_hex(COLOR_TEXT) : lv_color_hex(COLOR_TEXT_DIM), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
lv_obj_center(lbl);
|
||
lv_obj_add_event_cb(btn, btn_setpoint_standby_next_cb, LV_EVENT_CLICKED, NULL);
|
||
|
||
/* Common parameters section */
|
||
lv_obj_t *panel_common = lv_obj_create(scr);
|
||
lv_obj_set_pos(panel_common, 15, 320);
|
||
lv_obj_set_size(panel_common, 770, 60);
|
||
lv_obj_set_style_bg_color(panel_common, lv_color_hex(COLOR_BG_PANEL), 0);
|
||
lv_obj_set_style_radius(panel_common, 6, 0);
|
||
lv_obj_set_style_border_width(panel_common, 0, 0);
|
||
lv_obj_clear_flag(panel_common, LV_OBJ_FLAG_SCROLLABLE);
|
||
|
||
lbl = lv_label_create(panel_common);
|
||
lv_label_set_text(lbl, "Общие:");
|
||
lv_obj_set_pos(lbl, 10, 3);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_DIM), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
|
||
snprintf(buf, sizeof(buf), "Фильтр G4: %s", filter_g4_date);
|
||
lbl = lv_label_create(panel_common);
|
||
lv_label_set_text(lbl, buf);
|
||
lv_obj_set_pos(lbl, 10, 28);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
|
||
snprintf(buf, sizeof(buf), "Фильтр H13: %s", filter_h13_date);
|
||
lbl = lv_label_create(panel_common);
|
||
lv_label_set_text(lbl, buf);
|
||
lv_obj_set_pos(lbl, 250, 28);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
|
||
/* Buttons row */
|
||
int btn_y = 395;
|
||
|
||
btn = lv_btn_create(scr);
|
||
lv_obj_set_pos(btn, 200, btn_y);
|
||
lv_obj_set_size(btn, 110, 38);
|
||
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(btn, 4, 0);
|
||
lbl = lv_label_create(btn);
|
||
lv_label_set_text(lbl, "Сохранить");
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_ACCENT), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
lv_obj_center(lbl);
|
||
lv_obj_add_event_cb(btn, btn_save_setpoints_cb, LV_EVENT_CLICKED, NULL);
|
||
|
||
btn = lv_btn_create(scr);
|
||
lv_obj_set_pos(btn, 330, btn_y);
|
||
lv_obj_set_size(btn, 140, 38);
|
||
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(btn, 4, 0);
|
||
lbl = lv_label_create(btn);
|
||
lv_label_set_text(lbl, "По умолчанию");
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_WARNING), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
lv_obj_center(lbl);
|
||
lv_obj_add_event_cb(btn, btn_default_setpoints_cb, LV_EVENT_CLICKED, NULL);
|
||
|
||
btn = lv_btn_create(scr);
|
||
lv_obj_set_pos(btn, 490, btn_y);
|
||
lv_obj_set_size(btn, 100, 38);
|
||
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(btn, 4, 0);
|
||
lbl = lv_label_create(btn);
|
||
lv_label_set_text(lbl, "Меню");
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
lv_obj_center(lbl);
|
||
lv_obj_add_event_cb(btn, btn_menu_cb, LV_EVENT_CLICKED, NULL);
|
||
}
|
||
|
||
/* ============ SCREEN 4: НАСТРОЙКИ (config, after password) ============ */
|
||
static void create_screen_config(void) {
|
||
lv_obj_t *scr = lv_obj_create(NULL);
|
||
scr_screen4 = scr;
|
||
lv_obj_set_style_bg_color(scr, lv_color_hex(COLOR_BG_DARK), 0);
|
||
lv_obj_set_style_bg_opa(scr, LV_OPA_COVER, 0);
|
||
lv_obj_clear_flag(scr, LV_OBJ_FLAG_SCROLLABLE);
|
||
|
||
char buf[64];
|
||
|
||
/* Header */
|
||
lv_obj_t *lbl = lv_label_create(scr);
|
||
lv_label_set_text(lbl, "НАСТРОЙКИ");
|
||
lv_obj_set_pos(lbl, 340, 5);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
|
||
/* === MODULE SCHEMA AREA === (top part, like main screen) */
|
||
lv_obj_t *schema_panel = lv_obj_create(scr);
|
||
lv_obj_set_pos(schema_panel, 10, 30);
|
||
lv_obj_set_size(schema_panel, 780, 150);
|
||
lv_obj_set_style_bg_color(schema_panel, lv_color_hex(COLOR_BG_PANEL), 0);
|
||
lv_obj_set_style_radius(schema_panel, 6, 0);
|
||
lv_obj_set_style_border_width(schema_panel, 0, 0);
|
||
lv_obj_clear_flag(schema_panel, LV_OBJ_FLAG_SCROLLABLE);
|
||
|
||
lbl = lv_label_create(schema_panel);
|
||
lv_label_set_text(lbl, "Модули (нажмите для настройки, удерживайте для вкл/выкл):");
|
||
lv_obj_set_pos(lbl, 10, 5);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_DIM), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
|
||
/* Create all modules with pipe image connectors (centered) */
|
||
int total_w2 = MODULE_COUNT * MODULE_WIDTH + (MODULE_COUNT + 1) * PIPE_W;
|
||
int start_x = (780 - total_w2) / 2;
|
||
int modules_y = 30;
|
||
int pipe_sy = modules_y + (MODULE_HEIGHT - PIPE_H) / 2;
|
||
|
||
for (int i = 0; i < MODULE_COUNT; i++) {
|
||
int pipe_x = start_x + i * (MODULE_WIDTH + PIPE_W);
|
||
/* Pipe image in clipping container */
|
||
lv_obj_t *p = lv_obj_create(schema_panel);
|
||
lv_obj_set_pos(p, pipe_x, pipe_sy);
|
||
lv_obj_set_size(p, PIPE_W, PIPE_H);
|
||
lv_obj_set_style_pad_all(p, 0, 0);
|
||
lv_obj_set_style_bg_opa(p, LV_OPA_TRANSP, 0);
|
||
lv_obj_set_style_border_width(p, 0, 0);
|
||
lv_obj_clear_flag(p, LV_OBJ_FLAG_SCROLLABLE | LV_OBJ_FLAG_CLICKABLE);
|
||
lv_obj_t *pimg = lv_image_create(p);
|
||
lv_image_set_src(pimg, &img_pipe);
|
||
lv_obj_align(pimg, LV_ALIGN_CENTER, 0, 0);
|
||
|
||
int x_pos = pipe_x + PIPE_W;
|
||
|
||
/* Container for module - transparent */
|
||
lv_obj_t *cont = lv_obj_create(schema_panel);
|
||
lv_obj_set_pos(cont, x_pos, modules_y);
|
||
lv_obj_set_size(cont, MODULE_WIDTH, MODULE_HEIGHT);
|
||
lv_obj_set_style_bg_opa(cont, LV_OPA_TRANSP, 0);
|
||
lv_obj_set_style_border_width(cont, 0, 0);
|
||
lv_obj_set_style_pad_all(cont, 2, 0);
|
||
lv_obj_clear_flag(cont, LV_OBJ_FLAG_SCROLLABLE);
|
||
lv_obj_add_flag(cont, LV_OBJ_FLAG_CLICKABLE);
|
||
|
||
/* Add click and press event handlers */
|
||
lv_obj_add_event_cb(cont, module_settings_click_cb, LV_EVENT_CLICKED, (void*)(intptr_t)i);
|
||
lv_obj_add_event_cb(cont, module_settings_press_cb, LV_EVENT_PRESSED, (void*)(intptr_t)i);
|
||
|
||
/* Image */
|
||
lv_obj_t *img = lv_image_create(cont);
|
||
lv_image_set_src(img, get_module_image(i));
|
||
lv_image_set_scale(img, MODULE_SCALE);
|
||
lv_obj_align(img, LV_ALIGN_CENTER, 0, 0);
|
||
|
||
/* Module name label */
|
||
lv_obj_t *lbl_name = lv_label_create(cont);
|
||
lv_label_set_text(lbl_name, get_module_name(i));
|
||
lv_obj_set_style_text_font(lbl_name, &montserrat_16_ru_en, 0);
|
||
|
||
/* Value label */
|
||
lv_obj_t *lbl_value = lv_label_create(cont);
|
||
lv_obj_set_style_text_font(lbl_value, &montserrat_16_ru_en, 0);
|
||
|
||
if (i == MODULE_CHANNEL) {
|
||
/* Channel: name at top (same as others), value centered on black square */
|
||
lv_obj_set_style_text_color(lbl_name, lv_color_hex(modules[i].enabled ? COLOR_TEXT_DIM : 0x555555), 0);
|
||
lv_obj_align(lbl_name, LV_ALIGN_TOP_MID, 0, -5);
|
||
lv_obj_set_style_text_color(lbl_value, lv_color_hex(modules[i].enabled ? COLOR_TEXT_ACCENT : 0x555555), 0);
|
||
lv_obj_align(lbl_value, LV_ALIGN_CENTER, 0, 5);
|
||
lv_obj_set_style_text_align(lbl_value, LV_TEXT_ALIGN_CENTER, 0);
|
||
} else {
|
||
/* Standard: name at top, value at bottom */
|
||
lv_obj_set_style_text_color(lbl_name, lv_color_hex(modules[i].enabled ? COLOR_TEXT_DIM : 0x555555), 0);
|
||
lv_obj_align(lbl_name, LV_ALIGN_TOP_MID, 0, -5);
|
||
lv_obj_set_style_text_color(lbl_value, lv_color_hex(modules[i].enabled ? COLOR_TEXT_ACCENT : 0x555555), 0);
|
||
lv_obj_align(lbl_value, LV_ALIGN_BOTTOM_MID, 0, -2);
|
||
}
|
||
|
||
/* Set opacity based on enabled state */
|
||
if (!modules[i].enabled) {
|
||
lv_obj_set_style_img_opa(img, LV_OPA_30, 0);
|
||
}
|
||
|
||
settings_module_obj[i] = cont;
|
||
settings_module_img[i] = img;
|
||
settings_module_label_name[i] = lbl_name;
|
||
settings_module_label_value[i] = lbl_value;
|
||
}
|
||
/* Final pipe connector after last module */
|
||
{
|
||
int pipe_x = start_x + MODULE_COUNT * (MODULE_WIDTH + PIPE_W);
|
||
lv_obj_t *p = lv_obj_create(schema_panel);
|
||
lv_obj_set_pos(p, pipe_x, pipe_sy);
|
||
lv_obj_set_size(p, PIPE_W, PIPE_H);
|
||
lv_obj_set_style_pad_all(p, 0, 0);
|
||
lv_obj_set_style_bg_opa(p, LV_OPA_TRANSP, 0);
|
||
lv_obj_set_style_border_width(p, 0, 0);
|
||
lv_obj_clear_flag(p, LV_OBJ_FLAG_SCROLLABLE | LV_OBJ_FLAG_CLICKABLE);
|
||
lv_obj_t *pimg = lv_image_create(p);
|
||
lv_image_set_src(pimg, &img_pipe);
|
||
lv_obj_align(pimg, LV_ALIGN_CENTER, 0, 0);
|
||
}
|
||
|
||
/* Update all settings module displays with current values */
|
||
for (int i = 0; i < MODULE_COUNT; i++) {
|
||
update_settings_module_display(i);
|
||
}
|
||
|
||
/* === CONFIGURATION PANEL === (bottom part) */
|
||
lv_obj_t *panel_main = lv_obj_create(scr);
|
||
lv_obj_set_pos(panel_main, 15, 190);
|
||
lv_obj_set_size(panel_main, 770, 160);
|
||
lv_obj_set_style_bg_color(panel_main, lv_color_hex(COLOR_BG_PANEL), 0);
|
||
lv_obj_set_style_radius(panel_main, 6, 0);
|
||
lv_obj_set_style_border_width(panel_main, 0, 0);
|
||
lv_obj_clear_flag(panel_main, LV_OBJ_FLAG_SCROLLABLE);
|
||
|
||
/* Page indicator */
|
||
snprintf(buf, sizeof(buf), "%d/2", config_page + 1);
|
||
lbl = lv_label_create(panel_main);
|
||
lv_label_set_text(lbl, buf);
|
||
lv_obj_set_pos(lbl, 720, 5);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_DIM), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
|
||
lv_obj_t *btn;
|
||
int y = 35;
|
||
|
||
/* PAGE 0: Sensor corrections - 2 columns */
|
||
if (config_page == 0) {
|
||
lbl = lv_label_create(panel_main);
|
||
lv_label_set_text(lbl, "Коррекция датчиков");
|
||
lv_obj_set_pos(lbl, 10, 5);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_WARNING), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
|
||
const char *labels[] = {
|
||
"Темп. помещ:",
|
||
"Влажн. помещ:",
|
||
"Темп. канала:",
|
||
"Влажн. канала:"
|
||
};
|
||
float *offsets[] = {
|
||
&config.temp_room_offset,
|
||
&config.hum_room_offset,
|
||
&config.temp_channel_offset,
|
||
&config.hum_channel_offset
|
||
};
|
||
|
||
int col_w = 370;
|
||
for (int i = 0; i < 4; i++) {
|
||
int col = i % 2;
|
||
int row = i / 2;
|
||
int bx = 10 + col * (col_w + 10);
|
||
int by = y + row * 32;
|
||
|
||
btn = lv_btn_create(panel_main);
|
||
lv_obj_set_pos(btn, bx, by);
|
||
lv_obj_set_size(btn, col_w, 28);
|
||
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(btn, 4, 0);
|
||
|
||
lv_obj_t *lbl_name = lv_label_create(btn);
|
||
lv_label_set_text(lbl_name, labels[i]);
|
||
lv_obj_align(lbl_name, LV_ALIGN_LEFT_MID, 15, 0);
|
||
lv_obj_set_style_text_color(lbl_name, lv_color_hex(COLOR_TEXT), 0);
|
||
lv_obj_set_style_text_font(lbl_name, &montserrat_16_ru_en, 0);
|
||
|
||
snprintf(buf, sizeof(buf), "%+.1f", *offsets[i]);
|
||
lv_obj_t *lbl_val = lv_label_create(btn);
|
||
lv_label_set_text(lbl_val, buf);
|
||
lv_obj_align(lbl_val, LV_ALIGN_RIGHT_MID, -15, 0);
|
||
lv_obj_set_style_text_color(lbl_val, lv_color_hex(COLOR_ACCENT), 0);
|
||
lv_obj_set_style_text_font(lbl_val, &montserrat_16_ru_en, 0);
|
||
|
||
lv_obj_add_event_cb(btn, edit_config_offset_cb, LV_EVENT_CLICKED, offsets[i]);
|
||
}
|
||
}
|
||
/* PAGE 1: Режим работы и Дата/время - 2 columns */
|
||
else if (config_page == 1) {
|
||
lbl = lv_label_create(panel_main);
|
||
lv_label_set_text(lbl, "Режим работы");
|
||
lv_obj_set_pos(lbl, 10, 5);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_WARNING), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
|
||
int col_w = 370;
|
||
|
||
/* Column 1: Force mode */
|
||
btn = lv_btn_create(panel_main);
|
||
lv_obj_set_pos(btn, 10, y);
|
||
lv_obj_set_size(btn, col_w, 28);
|
||
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(btn, 4, 0);
|
||
|
||
lbl = lv_label_create(btn);
|
||
lv_label_set_text(lbl, "Принуд. режим:");
|
||
lv_obj_align(lbl, LV_ALIGN_LEFT_MID, 15, 0);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
|
||
lbl = lv_label_create(btn);
|
||
lv_label_set_text(lbl, config.force_mode_enabled ? "ВКЛ" : "ВЫКЛ");
|
||
lv_obj_align(lbl, LV_ALIGN_RIGHT_MID, -15, 0);
|
||
lv_obj_set_style_text_color(lbl, config.force_mode_enabled ? lv_color_hex(COLOR_WARNING) : lv_color_hex(COLOR_TEXT_DIM), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
lv_obj_add_event_cb(btn, toggle_force_mode_cb, LV_EVENT_CLICKED, NULL);
|
||
|
||
/* Column 2: Key swap */
|
||
btn = lv_btn_create(panel_main);
|
||
lv_obj_set_pos(btn, 10 + col_w + 10, y);
|
||
lv_obj_set_size(btn, col_w, 28);
|
||
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(btn, 4, 0);
|
||
|
||
lbl = lv_label_create(btn);
|
||
lv_label_set_text(lbl, "Перемена ключа:");
|
||
lv_obj_align(lbl, LV_ALIGN_LEFT_MID, 15, 0);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
|
||
lbl = lv_label_create(btn);
|
||
lv_label_set_text(lbl, config.key_swap_enabled ? "ВКЛ" : "ВЫКЛ");
|
||
lv_obj_align(lbl, LV_ALIGN_RIGHT_MID, -15, 0);
|
||
lv_obj_set_style_text_color(lbl, config.key_swap_enabled ? lv_color_hex(COLOR_ACCENT) : lv_color_hex(COLOR_TEXT_DIM), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
lv_obj_add_event_cb(btn, toggle_key_swap_cb, LV_EVENT_CLICKED, NULL);
|
||
|
||
/* Date/time - row 2 spanning full width */
|
||
y += 42;
|
||
lbl = lv_label_create(panel_main);
|
||
lv_label_set_text(lbl, "Дата и время");
|
||
lv_obj_set_pos(lbl, 10, y);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_WARNING), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
|
||
y += 25;
|
||
btn = lv_btn_create(panel_main);
|
||
lv_obj_set_pos(btn, 10, y);
|
||
lv_obj_set_size(btn, col_w, 28);
|
||
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(btn, 4, 0);
|
||
|
||
snprintf(buf, sizeof(buf), "%02d.%02d.%04d %02d:%02d",
|
||
state.day, state.month, state.year, state.hour, state.minute);
|
||
lbl = lv_label_create(btn);
|
||
lv_label_set_text(lbl, buf);
|
||
lv_obj_center(lbl);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_ACCENT), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
lv_obj_add_event_cb(btn, edit_datetime_cb, LV_EVENT_CLICKED, NULL);
|
||
}
|
||
|
||
/* Pagination buttons - below content */
|
||
btn = lv_btn_create(scr);
|
||
lv_obj_set_pos(btn, 300, 365);
|
||
lv_obj_set_size(btn, 80, 30);
|
||
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(btn, 4, 0);
|
||
lbl = lv_label_create(btn);
|
||
lv_label_set_text(lbl, "<");
|
||
lv_obj_set_style_text_color(lbl, config_page > 0 ? lv_color_hex(COLOR_TEXT) : lv_color_hex(COLOR_TEXT_DIM), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
lv_obj_center(lbl);
|
||
lv_obj_add_event_cb(btn, btn_config_page_prev_cb, LV_EVENT_CLICKED, NULL);
|
||
|
||
/* Page indicator */
|
||
lbl = lv_label_create(scr);
|
||
snprintf(buf, sizeof(buf), "%d/2", config_page + 1);
|
||
lv_label_set_text(lbl, buf);
|
||
lv_obj_set_pos(lbl, 395, 372);
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_DIM), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
|
||
btn = lv_btn_create(scr);
|
||
lv_obj_set_pos(btn, 445, 365);
|
||
lv_obj_set_size(btn, 80, 30);
|
||
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(btn, 4, 0);
|
||
lbl = lv_label_create(btn);
|
||
lv_label_set_text(lbl, ">");
|
||
lv_obj_set_style_text_color(lbl, config_page < 1 ? lv_color_hex(COLOR_TEXT) : lv_color_hex(COLOR_TEXT_DIM), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
lv_obj_center(lbl);
|
||
lv_obj_add_event_cb(btn, btn_config_page_next_cb, LV_EVENT_CLICKED, NULL);
|
||
|
||
/* Bottom buttons - using screen3 style colors */
|
||
btn = lv_btn_create(scr);
|
||
lv_obj_set_pos(btn, 200, 410);
|
||
lv_obj_set_size(btn, 110, 38);
|
||
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(btn, 4, 0);
|
||
lbl = lv_label_create(btn);
|
||
lv_label_set_text(lbl, "Сохранить");
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_ACCENT), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
lv_obj_center(lbl);
|
||
lv_obj_add_event_cb(btn, btn_save_config_cb, LV_EVENT_CLICKED, NULL);
|
||
|
||
btn = lv_btn_create(scr);
|
||
lv_obj_set_pos(btn, 330, 410);
|
||
lv_obj_set_size(btn, 140, 38);
|
||
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(btn, 4, 0);
|
||
lbl = lv_label_create(btn);
|
||
lv_label_set_text(lbl, "По умолчанию");
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_WARNING), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
lv_obj_center(lbl);
|
||
lv_obj_add_event_cb(btn, btn_default_config_cb, LV_EVENT_CLICKED, NULL);
|
||
|
||
btn = lv_btn_create(scr);
|
||
lv_obj_set_pos(btn, 490, 410);
|
||
lv_obj_set_size(btn, 100, 38);
|
||
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
|
||
lv_obj_set_style_radius(btn, 4, 0);
|
||
lbl = lv_label_create(btn);
|
||
lv_label_set_text(lbl, "Меню");
|
||
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
|
||
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
|
||
lv_obj_center(lbl);
|
||
lv_obj_add_event_cb(btn, btn_menu_cb, LV_EVENT_CLICKED, NULL);
|
||
}
|
||
|
||
void gui_update_values(void) {
|
||
char buf[64];
|
||
|
||
/* Recalculate season from date */
|
||
state.season = calc_season_from_month(state.month);
|
||
|
||
/* Update room climate */
|
||
if (lbl_temp_room) {
|
||
snprintf(buf, sizeof(buf), "%.1f°C", state.temp_room / 10.0f);
|
||
lv_label_set_text(lbl_temp_room, buf);
|
||
}
|
||
|
||
if (lbl_hum_room) {
|
||
snprintf(buf, sizeof(buf), "%.1f%%", state.hum_room / 10.0f);
|
||
lv_label_set_text(lbl_hum_room, buf);
|
||
}
|
||
|
||
/* Update channel climate */
|
||
if (lbl_temp_channel) {
|
||
snprintf(buf, sizeof(buf), "%.1f°C", state.temp_channel / 10.0f);
|
||
lv_label_set_text(lbl_temp_channel, buf);
|
||
}
|
||
|
||
if (lbl_hum_channel) {
|
||
snprintf(buf, sizeof(buf), "%.1f%%", state.hum_channel / 10.0f);
|
||
lv_label_set_text(lbl_hum_channel, buf);
|
||
}
|
||
|
||
/* Update system status */
|
||
if (lbl_room_num) {
|
||
snprintf(buf, sizeof(buf), "Комната: %d", state.room_num);
|
||
lv_label_set_text(lbl_room_num, buf);
|
||
}
|
||
|
||
if (lbl_master_slave) {
|
||
if (state.is_master) {
|
||
lv_label_set_text(lbl_master_slave, "Мастер");
|
||
lv_obj_set_style_text_color(lbl_master_slave, lv_color_hex(COLOR_ACCENT), 0);
|
||
} else {
|
||
snprintf(buf, sizeof(buf), "Слейв %s", state.has_signal ? "(OK)" : "(НЕТ)");
|
||
lv_label_set_text(lbl_master_slave, buf);
|
||
lv_obj_set_style_text_color(lbl_master_slave,
|
||
state.has_signal ? lv_color_hex(COLOR_WARNING) : lv_color_hex(COLOR_DANGER), 0);
|
||
}
|
||
}
|
||
|
||
if (lbl_season) {
|
||
const char *seasons[] = {"Зима", "Лето", "Переход"};
|
||
const uint32_t colors[] = {COLOR_TEXT_ACCENT, COLOR_DANGER, COLOR_WARNING};
|
||
snprintf(buf, sizeof(buf), "Сезон: %s", seasons[state.season % 3]);
|
||
lv_label_set_text(lbl_season, buf);
|
||
lv_obj_set_style_text_color(lbl_season, lv_color_hex(colors[state.season % 3]), 0);
|
||
}
|
||
|
||
if (lbl_mode) {
|
||
const char *modes[] = {"Остановка", "Дежурный", "Рабочий"};
|
||
const uint32_t colors[] = {COLOR_DANGER, COLOR_WARNING, COLOR_ACCENT};
|
||
snprintf(buf, sizeof(buf), "Режим: %s", modes[state.mode % 3]);
|
||
lv_label_set_text(lbl_mode, buf);
|
||
lv_obj_set_style_text_color(lbl_mode, lv_color_hex(colors[state.mode % 3]), 0);
|
||
}
|
||
|
||
if (lbl_datetime) {
|
||
snprintf(buf, sizeof(buf), "%02d.%02d.%04d %02d:%02d",
|
||
state.day, state.month, state.year, state.hour, state.minute);
|
||
lv_label_set_text(lbl_datetime, buf);
|
||
}
|
||
|
||
/* Update equipment modules */
|
||
for (int i = 0; i < MODULE_COUNT; i++) {
|
||
update_module_display(i);
|
||
}
|
||
}
|
||
|
||
/* Update display of a single module */
|
||
static void update_module_display(int module_idx) {
|
||
if (module_idx < 0 || module_idx >= MODULE_COUNT) return;
|
||
if (!modules[module_idx].label_value) return;
|
||
|
||
/* If module is disabled, show "ВЫКЛ" in grey */
|
||
if (!modules[module_idx].enabled) {
|
||
lv_label_set_text(modules[module_idx].label_value, "ВЫКЛ");
|
||
lv_obj_set_style_text_color(modules[module_idx].label_value, lv_color_hex(0x555555), 0);
|
||
return;
|
||
}
|
||
|
||
char buf[32];
|
||
|
||
switch (module_idx) {
|
||
case MODULE_VALVE_INLET:
|
||
snprintf(buf, sizeof(buf), "%d%%", state.valve_inlet);
|
||
lv_label_set_text(modules[module_idx].label_value, buf);
|
||
break;
|
||
case MODULE_VALVE_RECIRC:
|
||
snprintf(buf, sizeof(buf), "%d%%", state.valve_recirc);
|
||
lv_label_set_text(modules[module_idx].label_value, buf);
|
||
break;
|
||
case MODULE_FILTER_G4:
|
||
lv_label_set_text(modules[module_idx].label_value,
|
||
state.filter_g4_clean ? "OK" : "!");
|
||
lv_obj_set_style_text_color(modules[module_idx].label_value,
|
||
state.filter_g4_clean ? lv_color_hex(COLOR_ACCENT) : lv_color_hex(COLOR_DANGER), 0);
|
||
break;
|
||
case MODULE_KWS:
|
||
snprintf(buf, sizeof(buf), "%d%%", state.kws_valve);
|
||
lv_label_set_text(modules[module_idx].label_value, buf);
|
||
break;
|
||
case MODULE_PWW:
|
||
snprintf(buf, sizeof(buf), "%d%%", state.pww_valve);
|
||
lv_label_set_text(modules[module_idx].label_value, buf);
|
||
break;
|
||
case MODULE_HEATER:
|
||
lv_label_set_text(modules[module_idx].label_value,
|
||
state.heater_on ? "ВКЛ" : "ВЫКЛ");
|
||
lv_obj_set_style_text_color(modules[module_idx].label_value,
|
||
state.heater_on ? lv_color_hex(COLOR_DANGER) : lv_color_hex(COLOR_TEXT_DIM), 0);
|
||
break;
|
||
case MODULE_FAN:
|
||
lv_label_set_text(modules[module_idx].label_value,
|
||
state.fan_on ? "ВКЛ" : "ВЫКЛ");
|
||
lv_obj_set_style_text_color(modules[module_idx].label_value,
|
||
state.fan_on ? lv_color_hex(COLOR_ACCENT) : lv_color_hex(COLOR_DANGER), 0);
|
||
break;
|
||
case MODULE_FILTER_H13:
|
||
lv_label_set_text(modules[module_idx].label_value,
|
||
state.filter_h13_clean ? "OK" : "!");
|
||
lv_obj_set_style_text_color(modules[module_idx].label_value,
|
||
state.filter_h13_clean ? lv_color_hex(COLOR_ACCENT) : lv_color_hex(COLOR_DANGER), 0);
|
||
break;
|
||
case MODULE_CHANNEL:
|
||
/* Display channel temperature and humidity */
|
||
snprintf(buf, sizeof(buf), "%.1f°\n%.0f%%", state.temp_channel / 10.0f, state.hum_channel / 10.0f);
|
||
lv_label_set_text(modules[module_idx].label_value, buf);
|
||
break;
|
||
}
|
||
}
|
||
|
||
/* Update display of a single module on settings screen */
|
||
static void update_settings_module_display(int module_idx) {
|
||
if (module_idx < 0 || module_idx >= MODULE_COUNT) return;
|
||
if (!settings_module_label_value[module_idx]) return;
|
||
|
||
char buf[32];
|
||
|
||
switch (module_idx) {
|
||
case MODULE_VALVE_INLET:
|
||
snprintf(buf, sizeof(buf), "%d%%", state.valve_inlet);
|
||
lv_label_set_text(settings_module_label_value[module_idx], buf);
|
||
break;
|
||
case MODULE_VALVE_RECIRC:
|
||
snprintf(buf, sizeof(buf), "%d%%", state.valve_recirc);
|
||
lv_label_set_text(settings_module_label_value[module_idx], buf);
|
||
break;
|
||
case MODULE_FILTER_G4:
|
||
lv_label_set_text(settings_module_label_value[module_idx],
|
||
state.filter_g4_clean ? "OK" : "!");
|
||
lv_obj_set_style_text_color(settings_module_label_value[module_idx],
|
||
state.filter_g4_clean ? lv_color_hex(COLOR_ACCENT) : lv_color_hex(COLOR_DANGER), 0);
|
||
break;
|
||
case MODULE_KWS:
|
||
snprintf(buf, sizeof(buf), "%d%%", state.kws_valve);
|
||
lv_label_set_text(settings_module_label_value[module_idx], buf);
|
||
break;
|
||
case MODULE_PWW:
|
||
snprintf(buf, sizeof(buf), "%d%%", state.pww_valve);
|
||
lv_label_set_text(settings_module_label_value[module_idx], buf);
|
||
break;
|
||
case MODULE_HEATER:
|
||
lv_label_set_text(settings_module_label_value[module_idx],
|
||
state.heater_on ? "ВКЛ" : "ВЫКЛ");
|
||
lv_obj_set_style_text_color(settings_module_label_value[module_idx],
|
||
state.heater_on ? lv_color_hex(COLOR_DANGER) : lv_color_hex(COLOR_TEXT_DIM), 0);
|
||
break;
|
||
case MODULE_FAN:
|
||
lv_label_set_text(settings_module_label_value[module_idx],
|
||
state.fan_on ? "ВКЛ" : "ВЫКЛ");
|
||
lv_obj_set_style_text_color(settings_module_label_value[module_idx],
|
||
state.fan_on ? lv_color_hex(COLOR_ACCENT) : lv_color_hex(COLOR_DANGER), 0);
|
||
break;
|
||
case MODULE_FILTER_H13:
|
||
lv_label_set_text(settings_module_label_value[module_idx],
|
||
state.filter_h13_clean ? "OK" : "!");
|
||
lv_obj_set_style_text_color(settings_module_label_value[module_idx],
|
||
state.filter_h13_clean ? lv_color_hex(COLOR_ACCENT) : lv_color_hex(COLOR_DANGER), 0);
|
||
break;
|
||
case MODULE_CHANNEL:
|
||
/* Display channel temperature and humidity */
|
||
snprintf(buf, sizeof(buf), "%.1f°\n%.0f%%", state.temp_channel / 10.0f, state.hum_channel / 10.0f);
|
||
lv_label_set_text(settings_module_label_value[module_idx], buf);
|
||
break;
|
||
}
|
||
}
|
||
|
||
/* Refresh setpoints display on screen 3 */
|
||
static void refresh_setpoints_display(void) {
|
||
/* Recreate screen 3 only if it exists */
|
||
if (scr_screen3) {
|
||
lv_obj_del(scr_screen3);
|
||
scr_screen3 = NULL;
|
||
create_screen_screen3();
|
||
lv_scr_load(scr_screen3);
|
||
}
|
||
|
||
/* Invalidate screen4 so it rebuilds on next entry */
|
||
if (scr_screen4) {
|
||
lv_obj_del(scr_screen4);
|
||
scr_screen4 = NULL;
|
||
}
|
||
}
|
||
|
||
void gui_init(void) {
|
||
/* Calculate season from current month */
|
||
state.season = calc_season_from_month(state.month);
|
||
|
||
/* Create and load only main screen initially (lazy loading for memory) */
|
||
create_screen_main();
|
||
lv_scr_load(scr_main);
|
||
|
||
/* Проверка аварийных ситуаций после загрузки главного экрана */
|
||
if (!state.filter_g4_clean) {
|
||
add_alarm_to_log("ФИЛЬТР G4 ЗАСОРИЛСЯ - Замените фильтр", 1);
|
||
}
|
||
|
||
if (state.temp_channel < 50) {
|
||
add_alarm_to_log("УГРОЗА ЗАМОРОЗКИ - Установка остановлена", 1);
|
||
state.mode = 0;
|
||
update_mode_button_colors();
|
||
}
|
||
|
||
/* Show alarm popup if needed (only one at a time) */
|
||
if (!state.filter_g4_clean) {
|
||
create_popup_alarm("ФИЛЬТР G4 ЗАСОРИЛСЯ - Замените фильтр");
|
||
} else if (state.temp_channel < 50) {
|
||
create_popup_alarm("УГРОЗА ЗАМОРОЗКИ - Установка остановлена");
|
||
}
|
||
|
||
/* Update all module displays with initial data */
|
||
gui_update_values();
|
||
|
||
/* Start Modbus simulation timer (every 2 seconds) */
|
||
sync_setpoints_to_slave();
|
||
lv_timer_create(modbus_simulation_timer_cb, 2000, NULL);
|
||
|
||
/* Other screens created on demand when user navigates to them */
|
||
}
|
||
|
||
/* ── ModBus Simulation ────────────────────────────── */
|
||
|
||
/* Timer-based Modbus slave simulation */
|
||
static void modbus_simulation_timer_cb(lv_timer_t *timer) {
|
||
(void)timer;
|
||
|
||
/* === Slave simulation: process commands and generate sensor feedback === */
|
||
int16_t target_temp_x10 = (int16_t)(slave.target_temp * 10);
|
||
int16_t target_hum_x10 = (int16_t)(slave.target_hum * 10);
|
||
|
||
/* Room temperature drifts toward target */
|
||
if (slave.fb_temp_room < target_temp_x10) {
|
||
slave.fb_temp_room += 1 + (rand() % 2);
|
||
if (slave.fb_temp_room > target_temp_x10) slave.fb_temp_room = target_temp_x10;
|
||
} else if (slave.fb_temp_room > target_temp_x10) {
|
||
slave.fb_temp_room -= 1 + (rand() % 2);
|
||
if (slave.fb_temp_room < target_temp_x10) slave.fb_temp_room = target_temp_x10;
|
||
} else {
|
||
slave.fb_temp_room += (rand() % 3) - 1;
|
||
}
|
||
|
||
/* Room humidity drifts toward target */
|
||
if (slave.fb_hum_room < target_hum_x10) {
|
||
slave.fb_hum_room += 1 + (rand() % 3);
|
||
if (slave.fb_hum_room > target_hum_x10) slave.fb_hum_room = target_hum_x10;
|
||
} else if (slave.fb_hum_room > target_hum_x10) {
|
||
slave.fb_hum_room -= 1 + (rand() % 3);
|
||
if (slave.fb_hum_room < target_hum_x10) slave.fb_hum_room = target_hum_x10;
|
||
} else {
|
||
slave.fb_hum_room += (rand() % 3) - 1;
|
||
}
|
||
|
||
/* Channel temperature: slightly above room target, influenced by KWS/PWW */
|
||
int16_t ch_target = target_temp_x10;
|
||
if (state.mode != 0) {
|
||
ch_target += 20;
|
||
if (slave.cmd_kws_valve > 0 && state.kws_mode == 0)
|
||
ch_target -= slave.cmd_kws_valve / 5;
|
||
if (slave.cmd_pww_valve > 0 && state.pww_mode == 1)
|
||
ch_target += slave.cmd_pww_valve / 5;
|
||
if (slave.cmd_heater)
|
||
ch_target += 30;
|
||
} else {
|
||
ch_target = 150;
|
||
}
|
||
if (slave.fb_temp_channel < ch_target) {
|
||
slave.fb_temp_channel += 1 + (rand() % 2);
|
||
if (slave.fb_temp_channel > ch_target) slave.fb_temp_channel = ch_target;
|
||
} else if (slave.fb_temp_channel > ch_target) {
|
||
slave.fb_temp_channel -= 1 + (rand() % 2);
|
||
if (slave.fb_temp_channel < ch_target) slave.fb_temp_channel = ch_target;
|
||
}
|
||
|
||
/* Channel humidity */
|
||
slave.fb_hum_channel = target_hum_x10 + 50 + (rand() % 30) - 15;
|
||
|
||
/* === Master reads feedback from slave === */
|
||
state.temp_room = slave.fb_temp_room;
|
||
state.hum_room = slave.fb_hum_room;
|
||
state.temp_channel = slave.fb_temp_channel;
|
||
state.hum_channel = slave.fb_hum_channel;
|
||
|
||
/* Valve positions reflect slave commands */
|
||
if (state.mode != 0) {
|
||
state.valve_inlet = slave.cmd_valve_inlet;
|
||
state.valve_recirc = slave.cmd_valve_recirc;
|
||
state.fan_on = slave.cmd_fan;
|
||
state.heater_on = slave.cmd_heater;
|
||
|
||
/* Auto KWS/PWW: simple proportional response */
|
||
if (slave.kws_auto && modules[MODULE_KWS].enabled) {
|
||
int16_t err = state.temp_room - target_temp_x10;
|
||
if (state.kws_mode == 0) { /* cooling */
|
||
state.kws_valve = (err > 0) ? (uint8_t)(err > 100 ? 100 : err) : 0;
|
||
} else { /* heating */
|
||
state.kws_valve = (err < 0) ? (uint8_t)((-err) > 100 ? 100 : (-err)) : 0;
|
||
}
|
||
} else if (!slave.kws_auto) {
|
||
state.kws_valve = slave.cmd_kws_valve;
|
||
}
|
||
if (slave.pww_auto && modules[MODULE_PWW].enabled) {
|
||
int16_t err = state.temp_room - target_temp_x10;
|
||
if (state.pww_mode == 0) { /* cooling */
|
||
state.pww_valve = (err > 0) ? (uint8_t)(err > 100 ? 100 : err) : 0;
|
||
} else { /* heating */
|
||
state.pww_valve = (err < 0) ? (uint8_t)((-err) > 100 ? 100 : (-err)) : 0;
|
||
}
|
||
} else if (!slave.pww_auto) {
|
||
state.pww_valve = slave.cmd_pww_valve;
|
||
}
|
||
} else {
|
||
state.valve_inlet = 0;
|
||
state.valve_recirc = 0;
|
||
state.kws_valve = 0;
|
||
state.pww_valve = 0;
|
||
state.fan_on = 0;
|
||
state.heater_on = 0;
|
||
}
|
||
|
||
/* Increment simulated clock */
|
||
state.minute++;
|
||
if (state.minute >= 60) {
|
||
state.minute = 0;
|
||
state.hour++;
|
||
if (state.hour >= 24) {
|
||
state.hour = 0;
|
||
state.day++;
|
||
if (state.day > 28) {
|
||
state.day = 1;
|
||
state.month++;
|
||
if (state.month > 12) {
|
||
state.month = 1;
|
||
state.year++;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
gui_update_values();
|
||
}
|
||
|
||
void gui_modbus_init(void) {
|
||
/* No-op for simulation */
|
||
}
|
||
|
||
void gui_modbus_task(void *arg) {
|
||
(void)arg;
|
||
/* Replaced by modbus_simulation_timer_cb (LVGL timer) */
|
||
}
|