/* AUTO-GENERATED by GUI Editor — do not edit manually */ #include "gui_generated.h" #include "lvgl.h" #include #include #include #include /* 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 = 220, .hum_room = 450, .temp_channel = 200, .hum_channel = 500, .room_num = 101, .is_master = 1, .has_signal = 1, .season = 0, .mode = 1, .valve_inlet = 75, .valve_recirc = 25, .filter_g4_clean = 0, /* Фильтр загрязнен для демонстрации */ .kws_valve = 50, .kws_mode = 1, .pww_valve = 30, .pww_mode = 0, /* PWW в режиме охлаждения для демонстрации */ .heater_on = 0, .fan_on = 1, .filter_h13_clean = 1, .year = 2026, .month = 2, .day = 26, .hour = 12, .minute = 30 }; /* 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; /* указатель на значение */ lv_obj_t *pipe_placeholder; /* труба-заглушка когда модуль отключен */ } module_config_t; static module_config_t modules[MODULE_COUNT] = { {MODULE_VALVE_INLET, 1, 0, NULL, NULL, NULL, NULL, NULL}, {MODULE_VALVE_RECIRC, 1, 1, NULL, NULL, NULL, NULL, NULL}, {MODULE_FILTER_G4, 1, 2, NULL, NULL, NULL, NULL, NULL}, {MODULE_KWS, 1, 3, NULL, NULL, NULL, NULL, NULL}, {MODULE_PWW, 1, 4, NULL, NULL, NULL, NULL, NULL}, {MODULE_HEATER, 1, 5, NULL, NULL, NULL, NULL, NULL}, {MODULE_FAN, 1, 6, NULL, NULL, NULL, NULL, NULL}, {MODULE_FILTER_H13, 1, 7, NULL, NULL, NULL, NULL, NULL}, {MODULE_CHANNEL, 1, 8, NULL, NULL, NULL, NULL, NULL}, }; /* Module dimensions and positions */ #define MODULE_WIDTH 70 #define MODULE_HEIGHT 95 #define MODULE_SPACING 3 #define MODULES_ACTIVE_Y 20 #define MODULES_INACTIVE_Y 175 #define MODULE_SCALE 140 /* Scale factor: 256=100%, 140~55% for 64x64 images */ /* Panel for schema (needed globally for drag) */ static lv_obj_t *schema_panel = NULL; /* Pipe placeholders for inactive modules in active line */ static lv_obj_t *pipe_placeholders[MODULE_COUNT] = {NULL}; /* Drag state for modules */ static lv_obj_t *dragged_module = NULL; static int dragged_module_idx = -1; static lv_point_t drag_start_pos; /* Alarm log structure */ #define MAX_ALARMS 20 typedef struct { char message[80]; char timestamp[20]; int active; } alarm_entry_t; static alarm_entry_t alarm_log[MAX_ALARMS]; static int alarm_count = 0; /* 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"; /* 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; /* Equipment UI references */ static lv_obj_t *panel_schema = NULL; /* Popup windows */ static lv_obj_t *popup_readings = NULL; static lv_obj_t *popup_alarm = 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_config(void); static void create_screen_screen2(void); static void module_drag_cb(lv_event_t *e); static void update_module_display(int module_idx); static void refresh_setpoints_display(void); static void btn_reset_alarm_cb(lv_event_t *e); static void btn_clear_log_cb(lv_event_t *e); static lv_obj_t* create_module_widget(lv_obj_t *parent, int module_idx, int x, int y); static void update_module_positions(void); /* 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 ""; } } /* Module drag event handler */ static void module_drag_cb(lv_event_t *e) { lv_event_code_t code = lv_event_get_code(e); lv_obj_t *obj = lv_event_get_target(e); int module_idx = (int)(intptr_t)lv_event_get_user_data(e); if (code == LV_EVENT_PRESSING) { lv_indev_t *indev = lv_indev_active(); if (indev == NULL) return; lv_point_t vect; lv_indev_get_vect(indev, &vect); lv_coord_t x = lv_obj_get_x(obj) + vect.x; lv_coord_t y = lv_obj_get_y(obj) + vect.y; lv_obj_set_pos(obj, x, y); } else if (code == LV_EVENT_RELEASED) { /* Check if module should be enabled/disabled based on Y position */ lv_coord_t y = lv_obj_get_y(obj); int threshold = (MODULES_ACTIVE_Y + MODULES_INACTIVE_Y) / 2; if (y < threshold) { modules[module_idx].enabled = 1; } else { modules[module_idx].enabled = 0; } /* Reposition all modules */ update_module_positions(); } } /* Update positions of all modules (pipe line is continuous background) */ static void update_module_positions(void) { int active_x = 50; /* Start after the inlet pipe */ int inactive_x = 10; for (int i = 0; i < MODULE_COUNT; i++) { if (modules[i].obj == NULL) continue; if (modules[i].enabled) { /* Module is active - position it on the active line */ lv_obj_set_pos(modules[i].obj, active_x, MODULES_ACTIVE_Y); active_x += MODULE_WIDTH + MODULE_SPACING; } else { /* Module is inactive - move to inactive area, pipe line shows through */ active_x += MODULE_WIDTH + MODULE_SPACING; /* Reserve space on pipe line */ lv_obj_set_pos(modules[i].obj, inactive_x, MODULES_INACTIVE_Y); inactive_x += MODULE_WIDTH + MODULE_SPACING; } } } /* Create a module widget with image */ static lv_obj_t* create_module_widget(lv_obj_t *parent, int module_idx, int x, int y) { /* Container for module */ 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_color(cont, lv_color_hex(COLOR_BG_MODULE), 0); lv_obj_set_style_radius(cont, 5, 0); lv_obj_set_style_border_width(cont, 1, 0); lv_obj_set_style_border_color(cont, lv_color_hex(COLOR_BORDER), 0); lv_obj_set_style_pad_all(cont, 2, 0); lv_obj_clear_flag(cont, LV_OBJ_FLAG_SCROLLABLE); /* Add drag flag */ lv_obj_add_flag(cont, LV_OBJ_FLAG_CLICKABLE); lv_obj_add_event_cb(cont, module_drag_cb, LV_EVENT_PRESSING, (void*)(intptr_t)module_idx); lv_obj_add_event_cb(cont, module_drag_cb, LV_EVENT_RELEASED, (void*)(intptr_t)module_idx); /* Module name label at top */ lv_obj_t *lbl_name = lv_label_create(cont); lv_label_set_text(lbl_name, get_module_name(module_idx)); lv_obj_align(lbl_name, LV_ALIGN_TOP_MID, 0, 0); lv_obj_set_style_text_color(lbl_name, lv_color_hex(COLOR_TEXT_DIM), 0); lv_obj_set_style_text_font(lbl_name, &montserrat_16_ru_en, 0); /* 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, 5); /* Value label at bottom */ lv_obj_t *lbl_value = lv_label_create(cont); lv_obj_align(lbl_value, LV_ALIGN_BOTTOM_MID, 0, -2); lv_obj_set_style_text_color(lbl_value, lv_color_hex(COLOR_TEXT_ACCENT), 0); lv_obj_set_style_text_font(lbl_value, &montserrat_16_ru_en, 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 { /* Дежурный или Рабочий режим - применяем уставки */ state.valve_inlet = (uint8_t)sp->valve_inlet; state.valve_recirc = (uint8_t)sp->valve_recirc; /* Вентилятор по уставке */ state.fan_on = sp->fan; /* Электронагреватель по уставке */ if (sp->heater == 0) { state.heater_on = 0; /* ВЫКЛ */ } else if (sp->heater == 1) { state.heater_on = 1; /* ВКЛ */ } /* heater == 2 (АВТО) - управляется автоматикой */ /* KWS и PWW */ if (!sp->kws_auto) { state.kws_valve = (uint8_t)sp->kws_manual; } if (!sp->pww_auto) { state.pww_valve = (uint8_t)sp->pww_manual; } } } /* Button callbacks */ static void btn_stop_cb(lv_event_t *e) { (void)e; state.mode = 0; apply_mode_setpoints(); gui_update_values(); } static void btn_standby_cb(lv_event_t *e) { (void)e; state.mode = 1; apply_mode_setpoints(); gui_update_values(); } static void btn_work_cb(lv_event_t *e) { (void)e; state.mode = 2; apply_mode_setpoints(); gui_update_values(); } static void btn_menu_cb(lv_event_t *e) { (void)e; if (scr_screen1) lv_scr_load(scr_screen1); } static void btn_back_to_main_cb(lv_event_t *e) { (void)e; if (scr_main) lv_scr_load(scr_main); } static void btn_readings_cb(lv_event_t *e) { (void)e; create_popup_readings(); } static void btn_reset_alarm_cb(lv_event_t *e) { (void)e; /* Reset first active alarm */ for (int i = 0; i < alarm_count; i++) { if (alarm_log[i].active) { alarm_log[i].active = 0; break; } } /* 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_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; /* Refresh journal screen */ if (scr_screen2) { lv_obj_del(scr_screen2); scr_screen2 = NULL; } create_screen_screen2(); lv_scr_load(scr_screen2); } } 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_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) lv_scr_load(scr_screen2); } static void btn_settings_cb(lv_event_t *e) { (void)e; if (scr_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; /* Apply immediately if in work mode */ if (state.mode == 2) { if (setpoint_work.heater == 0) state.heater_on = 0; else if (setpoint_work.heater == 1) state.heater_on = 1; 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; /* Apply immediately if in standby mode */ if (state.mode == 1) { if (setpoint_standby.heater == 0) state.heater_on = 0; else if (setpoint_standby.heater == 1) state.heater_on = 1; 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; /* Apply immediately if in work mode */ if (state.mode == 2) { state.fan_on = setpoint_work.fan; gui_update_values(); } refresh_setpoints_display(); } static void edit_standby_fan_cb(lv_event_t *e) { (void)e; setpoint_standby.fan = !setpoint_standby.fan; /* Apply immediately if in standby mode */ if (state.mode == 1) { state.fan_on = setpoint_standby.fan; 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; /* Apply immediately if in work mode */ if (state.mode == 2) { state.kws_valve = (uint8_t)setpoint_work.kws_manual; 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; /* Apply immediately if in standby mode */ if (state.mode == 1) { state.kws_valve = (uint8_t)setpoint_standby.kws_manual; 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; /* Apply immediately if in work mode */ if (state.mode == 2) { state.pww_valve = (uint8_t)setpoint_work.pww_manual; 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; /* Apply immediately if in standby mode */ if (state.mode == 1) { state.pww_valve = (uint8_t)setpoint_standby.pww_manual; 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; refresh_setpoints_display(); } static void toggle_heater_cb(lv_event_t *e) { (void)e; config.has_heater = !config.has_heater; refresh_setpoints_display(); } 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; refresh_setpoints_display(); } static void toggle_key_swap_cb(lv_event_t *e) { (void)e; config.key_swap_enabled = !config.key_swap_enabled; refresh_setpoints_display(); } /* 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); /* 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); /* 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); /* 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); 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); /* 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) 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; refresh_setpoints_display(); 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"); } /* 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_count++; } } /* Create alarm popup */ static void create_popup_alarm(const char *message) { if (popup_alarm) { lv_obj_del(popup_alarm); } add_alarm_to_log(message, 1); 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, 275); lv_obj_set_size(panel_status, 240, 195); 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); lv_label_set_text(lbl_season, "Сезон: Зима"); 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, 135); 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, 275); lv_obj_set_size(panel_climate, 230, 195); 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, "22.0°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, "45%"); 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, "20.0°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, "50%"); 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 = 275; /* Stop button */ lv_obj_t *btn = lv_btn_create(scr); lv_obj_set_pos(btn, btn_x, btn_y); lv_obj_set_size(btn, 140, 100); 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_stop_cb, LV_EVENT_CLICKED, NULL); /* Standby button */ btn = lv_btn_create(scr); lv_obj_set_pos(btn, btn_x + 150, btn_y); 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_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_standby_cb, LV_EVENT_CLICKED, NULL); /* Work button */ btn = lv_btn_create(scr); lv_obj_set_pos(btn, btn_x + 150, btn_y + 52); 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_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_work_cb, LV_EVENT_CLICKED, NULL); /* Menu button */ 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); /* === EQUIPMENT SCHEMA AREA === (верхняя половина экрана) */ schema_panel = lv_obj_create(scr); lv_obj_set_pos(schema_panel, 10, 10); lv_obj_set_size(schema_panel, 780, 255); 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); /* Hint labels */ lv_obj_t *hint1 = lv_label_create(schema_panel); lv_label_set_text(hint1, "Активные модули (перетащите вниз для отключения):"); lv_obj_set_pos(hint1, 10, 2); 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); lv_obj_t *hint2 = lv_label_create(schema_panel); lv_label_set_text(hint2, "Неактивные (перетащите вверх для включения):"); lv_obj_set_pos(hint2, 10, 155); lv_obj_set_style_text_color(hint2, lv_color_hex(COLOR_TEXT_DIM), 0); lv_obj_set_style_text_font(hint2, &montserrat_16_ru_en, 0); /* Create continuous pipe background line FIRST (so modules appear on top) */ /* Calculate total width needed for all module positions */ int pipe_x = 5; int pipe_y = MODULES_ACTIVE_Y + 25; int pipe_segment_width = 35; /* Scaled pipe image width approximately */ int total_modules_width = 50 + MODULE_COUNT * (MODULE_WIDTH + MODULE_SPACING) + 50; /* Create pipe segments to form continuous line */ for (int px = pipe_x; px < total_modules_width; px += pipe_segment_width) { lv_obj_t *pipe_seg = lv_image_create(schema_panel); lv_image_set_src(pipe_seg, &img_pipe); lv_image_set_scale(pipe_seg, MODULE_SCALE); lv_obj_set_pos(pipe_seg, px, pipe_y); } /* Create all modules (they will appear on top of pipe line) */ int active_x = 50; /* Start after the inlet pipe */ int inactive_x = 10; for (int i = 0; i < MODULE_COUNT; i++) { int x_pos, y_pos; if (modules[i].enabled) { x_pos = active_x; y_pos = MODULES_ACTIVE_Y; active_x += MODULE_WIDTH + MODULE_SPACING; } else { x_pos = inactive_x; y_pos = MODULES_INACTIVE_Y; inactive_x += MODULE_WIDTH + MODULE_SPACING; } create_module_widget(schema_panel, i, x_pos, y_pos); /* No more individual pipe placeholders needed - continuous pipe line handles it */ pipe_placeholders[i] = NULL; } /* Update all module displays with current values */ for (int i = 0; i < MODULE_COUNT; i++) { update_module_display(i); } } 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(0x2C3E50), 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, 20); lv_obj_set_style_text_color(lbl, lv_color_hex(0xECF0F1), 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, 20); lv_obj_set_style_text_color(lbl, lv_color_hex(0xBDC3C7), 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, 60); lv_obj_set_size(log_panel, 760, 300); lv_obj_set_style_bg_color(log_panel, lv_color_hex(0x34495E), 0); lv_obj_set_style_radius(log_panel, 8, 0); lv_obj_set_style_border_width(log_panel, 2, 0); lv_obj_set_style_border_color(log_panel, lv_color_hex(0x7F8C8D), 0); /* Display log entries */ int y = 10; if (alarm_count == 0) { lbl = lv_label_create(log_panel); lv_label_set_text(lbl, "Аварий нет"); lv_obj_set_pos(lbl, 320, 130); 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 = 0; i < alarm_count && i < 8; i++) { char buf[150]; snprintf(buf, sizeof(buf), "%s - %s", alarm_log[i].message, alarm_log[i].timestamp); lbl = lv_label_create(log_panel); lv_label_set_text(lbl, buf); lv_obj_set_pos(lbl, 10, y); lv_obj_set_width(lbl, 730); 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_DIM), 0); } lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0); y += 35; } } /* Buttons */ int btn_y = 380; /* Clear Log button */ lv_obj_t *btn = lv_btn_create(scr); lv_obj_set_pos(btn, 50, 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); /* Reset Alarm button */ btn = lv_btn_create(scr); lv_obj_set_pos(btn, 230, 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_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_reset_alarm_cb, LV_EVENT_CLICKED, NULL); /* Back to Menu button */ btn = lv_btn_create(scr); lv_obj_set_pos(btn, 600, 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(0xFFFFFF), 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 = 34; /* Left panel - Work mode - SCROLLABLE */ lv_obj_t *panel_work = lv_obj_create(scr); lv_obj_set_pos(panel_work, 15, 40); lv_obj_set_size(panel_work, 380, 310); 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_set_scroll_dir(panel_work, LV_DIR_VER); lv_obj_set_scrollbar_mode(panel_work, LV_SCROLLBAR_MODE_AUTO); lbl = lv_label_create(panel_work); lv_label_set_text(lbl, "РАБОЧИЙ"); lv_obj_set_pos(lbl, 10, 8); 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); int y = 38; /* Temperature */ lv_obj_t *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); y += row_h + 6; /* 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); const char *heater_modes[] = {"ВЫКЛ", "ВКЛ", "АВТО"}; 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); /* Right panel - Standby mode - SCROLLABLE */ lv_obj_t *panel_standby = lv_obj_create(scr); lv_obj_set_pos(panel_standby, 405, 40); lv_obj_set_size(panel_standby, 380, 310); 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_set_scroll_dir(panel_standby, LV_DIR_VER); lv_obj_set_scrollbar_mode(panel_standby, LV_SCROLLBAR_MODE_AUTO); lbl = lv_label_create(panel_standby); lv_label_set_text(lbl, "ДЕЖУРНЫЙ"); lv_obj_set_pos(lbl, 10, 8); 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 = 38; /* 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); y += row_h + 6; /* 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); /* Common parameters section */ lv_obj_t *panel_common = lv_obj_create(scr); lv_obj_set_pos(panel_common, 15, 355); lv_obj_set_size(panel_common, 770, 70); 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, 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); 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); snprintf(buf, sizeof(buf), "%02d:%02d %02d.%02d.%d", state.hour, state.minute, state.day, state.month, state.year); lbl = lv_label_create(panel_common); lv_label_set_text(lbl, buf); lv_obj_set_pos(lbl, 510, 28); 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); lbl = lv_label_create(panel_common); lv_label_set_text(lbl, "(для ввода даты замены)"); lv_obj_set_pos(lbl, 10, 48); 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); /* Buttons row */ int btn_y = 435; 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); 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); 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, 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); /* Left panel - Sensor corrections - SCROLLABLE */ lv_obj_t *panel_left = lv_obj_create(scr); lv_obj_set_pos(panel_left, 10, 45); lv_obj_set_size(panel_left, 385, 340); lv_obj_set_style_bg_color(panel_left, lv_color_hex(COLOR_BG_PANEL), 0); lv_obj_set_style_radius(panel_left, 8, 0); lv_obj_set_style_border_width(panel_left, 0, 0); lv_obj_set_style_pad_all(panel_left, 8, 0); lv_obj_set_scroll_dir(panel_left, LV_DIR_VER); lv_obj_set_scrollbar_mode(panel_left, LV_SCROLLBAR_MODE_AUTO); lbl = lv_label_create(panel_left); 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); lv_obj_t *btn; int y = 35; const char *labels[] = { "Темп. помещения:", "Влажн. помещения:", "Темп. в канале:", "Влажн. в канале:", "Клапан притока:", "Клапан рецирк.:" }; float *offsets[] = { &config.temp_room_offset, &config.hum_room_offset, &config.temp_channel_offset, &config.hum_channel_offset, &config.valve_inlet_offset, &config.valve_recirc_offset }; for (int i = 0; i < 6; i++) { btn = lv_btn_create(panel_left); lv_obj_set_pos(btn, 0, y); lv_obj_set_size(btn, 350, 36); lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0); lv_obj_set_style_radius(btn, 4, 0); lv_obj_set_style_pad_all(btn, 0, 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, 10, 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, -10, 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]); y += 40; } /* Toggles for equipment presence */ y += 10; btn = lv_btn_create(panel_left); lv_obj_set_pos(btn, 0, y); lv_obj_set_size(btn, 350, 36); lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0); lv_obj_set_style_radius(btn, 4, 0); lv_obj_set_style_pad_all(btn, 0, 0); lbl = lv_label_create(btn); lv_label_set_text(lbl, "Клапан рецирк.:"); lv_obj_align(lbl, LV_ALIGN_LEFT_MID, 10, 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.has_recirc_valve ? "ЕСТЬ" : "НЕТ"); lv_obj_align(lbl, LV_ALIGN_RIGHT_MID, -10, 0); lv_obj_set_style_text_color(lbl, config.has_recirc_valve ? 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_recirc_valve_cb, LV_EVENT_CLICKED, NULL); y += 40; btn = lv_btn_create(panel_left); lv_obj_set_pos(btn, 0, y); lv_obj_set_size(btn, 350, 36); lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0); lv_obj_set_style_radius(btn, 4, 0); lv_obj_set_style_pad_all(btn, 0, 0); lbl = lv_label_create(btn); lv_label_set_text(lbl, "Электронагрев.:"); lv_obj_align(lbl, LV_ALIGN_LEFT_MID, 10, 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.has_heater ? "ЕСТЬ" : "НЕТ"); lv_obj_align(lbl, LV_ALIGN_RIGHT_MID, -10, 0); lv_obj_set_style_text_color(lbl, config.has_heater ? 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_heater_cb, LV_EVENT_CLICKED, NULL); /* Right panel - PID and mode settings - SCROLLABLE */ lv_obj_t *panel_right = lv_obj_create(scr); lv_obj_set_pos(panel_right, 405, 45); lv_obj_set_size(panel_right, 385, 340); lv_obj_set_style_bg_color(panel_right, lv_color_hex(COLOR_BG_PANEL), 0); lv_obj_set_style_radius(panel_right, 8, 0); lv_obj_set_style_border_width(panel_right, 0, 0); lv_obj_set_style_pad_all(panel_right, 8, 0); lv_obj_set_scroll_dir(panel_right, LV_DIR_VER); lv_obj_set_scrollbar_mode(panel_right, LV_SCROLLBAR_MODE_AUTO); lbl = lv_label_create(panel_right); 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); y = 35; /* PWW PID - separate buttons for each parameter */ lbl = lv_label_create(panel_right); lv_label_set_text(lbl, "PWW:"); lv_obj_set_pos(lbl, 10, 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); y += 25; /* PWW Kp */ btn = lv_btn_create(panel_right); lv_obj_set_pos(btn, 0, y); lv_obj_set_size(btn, 110, 32); lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0); lv_obj_set_style_radius(btn, 4, 0); lv_obj_set_style_pad_all(btn, 0, 0); snprintf(buf, sizeof(buf), "Kp=%.2f", config.pid_pww_kp); 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_pid_pww_kp_cb, LV_EVENT_CLICKED, NULL); /* PWW Ti */ btn = lv_btn_create(panel_right); lv_obj_set_pos(btn, 115, y); lv_obj_set_size(btn, 110, 32); lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0); lv_obj_set_style_radius(btn, 4, 0); lv_obj_set_style_pad_all(btn, 0, 0); snprintf(buf, sizeof(buf), "Ti=%.2f", config.pid_pww_ti); 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_pid_pww_ti_cb, LV_EVENT_CLICKED, NULL); /* PWW D */ btn = lv_btn_create(panel_right); lv_obj_set_pos(btn, 230, y); lv_obj_set_size(btn, 110, 32); lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0); lv_obj_set_style_radius(btn, 4, 0); lv_obj_set_style_pad_all(btn, 0, 0); snprintf(buf, sizeof(buf), "D=%.2f", config.pid_pww_d); 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_pid_pww_d_cb, LV_EVENT_CLICKED, NULL); y += 42; /* KWS PID - separate buttons for each parameter */ lbl = lv_label_create(panel_right); lv_label_set_text(lbl, "KWS:"); lv_obj_set_pos(lbl, 10, 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); y += 25; /* KWS Kp */ btn = lv_btn_create(panel_right); lv_obj_set_pos(btn, 0, y); lv_obj_set_size(btn, 110, 32); lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0); lv_obj_set_style_radius(btn, 4, 0); lv_obj_set_style_pad_all(btn, 0, 0); snprintf(buf, sizeof(buf), "Kp=%.2f", config.pid_kws_kp); 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_pid_kws_kp_cb, LV_EVENT_CLICKED, NULL); /* KWS Ti */ btn = lv_btn_create(panel_right); lv_obj_set_pos(btn, 115, y); lv_obj_set_size(btn, 110, 32); lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0); lv_obj_set_style_radius(btn, 4, 0); lv_obj_set_style_pad_all(btn, 0, 0); snprintf(buf, sizeof(buf), "Ti=%.2f", config.pid_kws_ti); 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_pid_kws_ti_cb, LV_EVENT_CLICKED, NULL); /* KWS D */ btn = lv_btn_create(panel_right); lv_obj_set_pos(btn, 230, y); lv_obj_set_size(btn, 110, 32); lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0); lv_obj_set_style_radius(btn, 4, 0); lv_obj_set_style_pad_all(btn, 0, 0); snprintf(buf, sizeof(buf), "D=%.2f", config.pid_kws_d); 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_pid_kws_d_cb, LV_EVENT_CLICKED, NULL); y += 42; lbl = lv_label_create(panel_right); 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 += 30; btn = lv_btn_create(panel_right); lv_obj_set_pos(btn, 0, y); lv_obj_set_size(btn, 350, 36); lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0); lv_obj_set_style_radius(btn, 4, 0); lv_obj_set_style_pad_all(btn, 0, 0); lbl = lv_label_create(btn); lv_label_set_text(lbl, "Принуд. режим:"); lv_obj_align(lbl, LV_ALIGN_LEFT_MID, 10, 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, -10, 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); y += 40; btn = lv_btn_create(panel_right); lv_obj_set_pos(btn, 0, y); lv_obj_set_size(btn, 350, 36); lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0); lv_obj_set_style_radius(btn, 4, 0); lv_obj_set_style_pad_all(btn, 0, 0); lbl = lv_label_create(btn); lv_label_set_text(lbl, "Перемена ключа:"); lv_obj_align(lbl, LV_ALIGN_LEFT_MID, 10, 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, -10, 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); y += 45; lbl = lv_label_create(panel_right); 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 += 30; btn = lv_btn_create(panel_right); lv_obj_set_pos(btn, 0, y); lv_obj_set_size(btn, 350, 36); lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0); lv_obj_set_style_radius(btn, 4, 0); lv_obj_set_style_pad_all(btn, 0, 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); /* Buttons */ btn = lv_btn_create(scr); lv_obj_set_pos(btn, 200, 410); lv_obj_set_size(btn, 120, 40); lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_ACCENT), 0); lv_obj_set_style_radius(btn, 8, 0); lbl = lv_label_create(btn); lv_label_set_text(lbl, "Сохранить"); 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); btn = lv_btn_create(scr); lv_obj_set_pos(btn, 340, 410); lv_obj_set_size(btn, 140, 40); lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_WARNING), 0); lv_obj_set_style_radius(btn, 8, 0); lbl = lv_label_create(btn); lv_label_set_text(lbl, "По умолчанию"); 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); btn = lv_btn_create(scr); lv_obj_set_pos(btn, 500, 410); lv_obj_set_size(btn, 100, 40); lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_TEXT_ACCENT), 0); lv_obj_set_style_radius(btn, 8, 0); lbl = lv_label_create(btn); lv_label_set_text(lbl, "Меню"); 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, btn_menu_cb, LV_EVENT_CLICKED, NULL); } void gui_update_values(void) { char buf[64]; /* 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; 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; } } /* Refresh setpoints display on screen 3 */ static void refresh_setpoints_display(void) { /* Remember which screen was active */ lv_obj_t *current_scr = lv_screen_active(); int was_on_screen3 = (current_scr == scr_screen3); int was_on_screen4 = (current_scr == scr_screen4); /* Recreate screen 3 to show updated values */ if (scr_screen3) { lv_obj_del(scr_screen3); scr_screen3 = NULL; } create_screen_screen3(); /* Recreate screen 4 to show updated values */ if (scr_screen4) { lv_obj_del(scr_screen4); scr_screen4 = NULL; } create_screen_config(); /* Load appropriate screen */ if (was_on_screen3) { lv_scr_load(scr_screen3); } else if (was_on_screen4) { lv_scr_load(scr_screen4); } } void gui_init(void) { /* Add some example alarms to the log */ add_alarm_to_log("Система запущена", 0); add_alarm_to_log("Фильтр G4 заменен", 0); create_screen_main(); create_screen_screen1(); create_screen_screen2(); create_screen_screen3(); create_screen_config(); lv_scr_load(scr_main); /* Проверка аварийных ситуаций после загрузки главного экрана */ if (!state.filter_g4_clean) { create_popup_alarm("ФИЛЬТР G4 ЗАСОРИЛСЯ\n\nЗамените фильтр немедленно.\nСистема работает в аварийном режиме."); state.mode = 0; } if (state.temp_channel < 50) { create_popup_alarm("УГРОЗА ЗАМОРОЗКИ\n\nУстановка остановлена.\nПроверьте журнал и примите меры."); state.mode = 0; } } /* ── ModBus ──────────────────────────────────────── */ void gui_modbus_init(void) { /* No-op for simulation */ } void gui_modbus_task(void *arg) { (void)arg; /* Simulation: update with dummy values */ while (1) { state.temp_room = 220 + (rand() % 40); state.hum_room = 450 + (rand() % 100); state.temp_channel = 200 + (rand() % 30); state.hum_channel = 500 + (rand() % 80); /* Simulate valve changes based on mode */ if (state.mode == 2) { state.valve_inlet = 70 + (rand() % 30); state.valve_recirc = 10 + (rand() % 20); state.kws_valve = 40 + (rand() % 30); state.pww_valve = 30 + (rand() % 25); state.fan_on = 1; } else if (state.mode == 1) { state.valve_inlet = 30 + (rand() % 20); state.valve_recirc = 40 + (rand() % 20); state.kws_valve = 20 + (rand() % 15); state.pww_valve = 15 + (rand() % 15); state.fan_on = 1; } 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; } gui_update_values(); /* Sleep simulation - requires FreeRTOS or use SDL delay */ } }