hardware-hmi-display-ui/main/gui_generated.c

3304 lines
129 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* AUTO-GENERATED by GUI Editor — do not edit manually */
#include "gui_generated.h"
#include "lvgl.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
/* 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;
/* Mode control buttons */
static lv_obj_t *btn_stop_mode = NULL;
static lv_obj_t *btn_standby_mode = NULL;
static lv_obj_t *btn_work_mode = NULL;
static lv_obj_t *lbl_stop_mode = NULL;
static lv_obj_t *lbl_standby_mode = NULL;
static lv_obj_t *lbl_work_mode = NULL;
/* 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;
int confirmed; /* 1=подтверждена */
char confirm_timestamp[20]; /* дата подтверждения */
} alarm_entry_t;
static alarm_entry_t alarm_log[MAX_ALARMS];
static int alarm_count = 0;
/* Pagination state for alarm log */
static int alarm_page = 0;
#define ALARMS_PER_PAGE 4
/* Pagination state for setpoints (Work mode) */
static int setpoint_work_page = 0;
#define SETPOINTS_PER_PAGE 4
/* Pagination state for setpoints (Standby mode) */
static int setpoint_standby_page = 0;
/* Pagination state for config screen */
static int config_page = 0;
#define CONFIG_ITEMS_PER_PAGE 6
/* Settings/Setpoints for Work and Standby modes */
typedef struct {
float temp; /* Температура */
float hum; /* Влажность */
float valve_inlet; /* Клапан притока */
float valve_recirc; /* Клапан рециркуляции */
uint8_t heater; /* 0=ВЫКЛ, 1=ВКЛ, 2=АВТО */
uint8_t fan; /* 0=ВЫКЛ, 1=ВКЛ */
uint8_t kws_auto; /* 1=АВТО, 0=ручной режим */
float kws_manual; /* Значение для ручного режима */
uint8_t pww_auto; /* 1=АВТО, 0=ручной режим */
float pww_manual; /* Значение для ручного режима */
} setpoint_mode_t;
static setpoint_mode_t setpoint_work = {
.temp = 22.0f, .hum = 50.0f, .valve_inlet = 75.0f, .valve_recirc = 25.0f,
.heater = 2, .fan = 1, .kws_auto = 1, .kws_manual = 0, .pww_auto = 1, .pww_manual = 0
};
static setpoint_mode_t setpoint_standby = {
.temp = 18.0f, .hum = 45.0f, .valve_inlet = 35.0f, .valve_recirc = 40.0f,
.heater = 0, .fan = 1, .kws_auto = 1, .kws_manual = 0, .pww_auto = 1, .pww_manual = 0
};
/* Configuration parameters (Настройки - с паролем) */
typedef struct {
float temp_room_offset;
float hum_room_offset;
float temp_channel_offset;
float hum_channel_offset;
float valve_inlet_offset;
float valve_recirc_offset;
uint8_t has_recirc_valve;
uint8_t has_heater;
float pid_pww_kp;
float pid_pww_ti;
float pid_pww_d;
float pid_kws_kp;
float pid_kws_ti;
float pid_kws_d;
uint8_t force_mode_enabled;
uint8_t force_mode_value;
uint8_t key_swap_enabled;
} config_t;
static config_t config = {
.temp_room_offset = 0.0f,
.hum_room_offset = 0.0f,
.temp_channel_offset = 0.0f,
.hum_channel_offset = 0.0f,
.valve_inlet_offset = 0.0f,
.valve_recirc_offset = 0.0f,
.has_recirc_valve = 1,
.has_heater = 1,
.pid_pww_kp = 1.0f,
.pid_pww_ti = 10.0f,
.pid_pww_d = 0.0f,
.pid_kws_kp = 1.0f,
.pid_kws_ti = 10.0f,
.pid_kws_d = 0.0f,
.force_mode_enabled = 0,
.force_mode_value = 1,
.key_swap_enabled = 1
};
/* Filter replacement dates */
static char filter_g4_date[12] = "01.01.2026";
static char filter_h13_date[12] = "01.01.2026";
/* 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_confirm_alarm_cb(lv_event_t *e);
static void btn_alarm_page_prev_cb(lv_event_t *e);
static void btn_alarm_page_next_cb(lv_event_t *e);
static void btn_clear_log_cb(lv_event_t *e);
static void btn_setpoint_work_prev_cb(lv_event_t *e);
static void btn_setpoint_work_next_cb(lv_event_t *e);
static void btn_setpoint_standby_prev_cb(lv_event_t *e);
static void btn_setpoint_standby_next_cb(lv_event_t *e);
static void btn_config_page_prev_cb(lv_event_t *e);
static void btn_config_page_next_cb(lv_event_t *e);
static void create_screen_screen3(void);
static lv_obj_t* create_module_widget(lv_obj_t *parent, int module_idx, int x, int y);
static void update_module_positions(void);
static void update_mode_button_colors(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 and pipe placeholders */
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);
lv_obj_clear_flag(modules[i].obj, LV_OBJ_FLAG_HIDDEN);
/* Hide pipe placeholder if exists */
if (pipe_placeholders[i]) {
lv_obj_add_flag(pipe_placeholders[i], LV_OBJ_FLAG_HIDDEN);
}
active_x += MODULE_WIDTH + MODULE_SPACING;
} else {
/* Module is inactive - move to inactive area */
lv_obj_set_pos(modules[i].obj, inactive_x, MODULES_INACTIVE_Y);
lv_obj_clear_flag(modules[i].obj, LV_OBJ_FLAG_HIDDEN);
/* Show pipe placeholder on active line */
if (pipe_placeholders[i]) {
lv_obj_set_pos(pipe_placeholders[i], active_x, MODULES_ACTIVE_Y);
lv_obj_clear_flag(pipe_placeholders[i], LV_OBJ_FLAG_HIDDEN);
}
active_x += MODULE_WIDTH + MODULE_SPACING;
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, 0);
/* 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;
}
}
}
/* Update mode button colors based on current mode */
static void update_mode_button_colors(void) {
if (!btn_stop_mode || !btn_standby_mode || !btn_work_mode) return;
if (!lbl_stop_mode || !lbl_standby_mode || !lbl_work_mode) return;
/* Reset all buttons to default color */
lv_obj_set_style_bg_color(btn_stop_mode, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_bg_color(btn_standby_mode, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_bg_color(btn_work_mode, lv_color_hex(COLOR_BG_BTN), 0);
/* Reset text colors to original */
lv_obj_set_style_text_color(lbl_stop_mode, lv_color_hex(COLOR_DANGER), 0);
lv_obj_set_style_text_color(lbl_standby_mode, lv_color_hex(COLOR_WARNING), 0);
lv_obj_set_style_text_color(lbl_work_mode, lv_color_hex(COLOR_ACCENT), 0);
/* Highlight active mode button with background and change text to black */
if (state.mode == 0) {
lv_obj_set_style_bg_color(btn_stop_mode, lv_color_hex(COLOR_DANGER), 0);
lv_obj_set_style_text_color(lbl_stop_mode, lv_color_hex(0x000000), 0);
} else if (state.mode == 1) {
lv_obj_set_style_bg_color(btn_standby_mode, lv_color_hex(COLOR_WARNING), 0);
lv_obj_set_style_text_color(lbl_standby_mode, lv_color_hex(0x000000), 0);
} else if (state.mode == 2) {
lv_obj_set_style_bg_color(btn_work_mode, lv_color_hex(COLOR_ACCENT), 0);
lv_obj_set_style_text_color(lbl_work_mode, lv_color_hex(0x000000), 0);
}
}
/* Button callbacks */
static void btn_stop_cb(lv_event_t *e) {
(void)e;
state.mode = 0;
apply_mode_setpoints();
update_mode_button_colors();
gui_update_values();
}
static void btn_standby_cb(lv_event_t *e) {
(void)e;
state.mode = 1;
apply_mode_setpoints();
update_mode_button_colors();
gui_update_values();
}
static void btn_work_cb(lv_event_t *e) {
(void)e;
state.mode = 2;
apply_mode_setpoints();
update_mode_button_colors();
gui_update_values();
}
static void btn_menu_cb(lv_event_t *e) {
(void)e;
if (scr_screen1) 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_confirm_alarm_cb(lv_event_t *e) {
int alarm_idx = (int)(intptr_t)lv_event_get_user_data(e);
if (alarm_idx >= 0 && alarm_idx < alarm_count) {
alarm_log[alarm_idx].confirmed = 1;
alarm_log[alarm_idx].active = 0;
snprintf(alarm_log[alarm_idx].confirm_timestamp, 20, "%02d.%02d.%04d %02d:%02d",
state.day, state.month, state.year, state.hour, state.minute);
}
/* Refresh journal screen */
if (scr_screen2) {
lv_obj_del(scr_screen2);
scr_screen2 = NULL;
}
create_screen_screen2();
lv_scr_load(scr_screen2);
}
static void btn_alarm_page_prev_cb(lv_event_t *e) {
(void)e;
if (alarm_page > 0) {
alarm_page--;
if (scr_screen2) {
lv_obj_del(scr_screen2);
scr_screen2 = NULL;
}
create_screen_screen2();
lv_scr_load(scr_screen2);
}
}
static void btn_alarm_page_next_cb(lv_event_t *e) {
(void)e;
int total_pages = (alarm_count + ALARMS_PER_PAGE - 1) / ALARMS_PER_PAGE;
if (alarm_page < total_pages - 1) {
alarm_page++;
if (scr_screen2) {
lv_obj_del(scr_screen2);
scr_screen2 = NULL;
}
create_screen_screen2();
lv_scr_load(scr_screen2);
}
}
static void btn_clear_log_cb(lv_event_t *e) {
(void)e;
/* Check if any active alarms */
int has_active = 0;
for (int i = 0; i < alarm_count; i++) {
if (alarm_log[i].active) {
has_active = 1;
break;
}
}
if (!has_active) {
alarm_count = 0;
alarm_page = 0;
/* Refresh journal screen */
if (scr_screen2) {
lv_obj_del(scr_screen2);
scr_screen2 = NULL;
}
create_screen_screen2();
lv_scr_load(scr_screen2);
}
}
/* Setpoints pagination callbacks - Work mode */
static void btn_setpoint_work_prev_cb(lv_event_t *e) {
(void)e;
if (setpoint_work_page > 0) {
setpoint_work_page--;
if (scr_screen3) {
lv_obj_del(scr_screen3);
scr_screen3 = NULL;
}
create_screen_screen3();
lv_scr_load(scr_screen3);
}
}
static void btn_setpoint_work_next_cb(lv_event_t *e) {
(void)e;
/* 8 parameters total, 4 per page = 2 pages */
if (setpoint_work_page < 1) {
setpoint_work_page++;
if (scr_screen3) {
lv_obj_del(scr_screen3);
scr_screen3 = NULL;
}
create_screen_screen3();
lv_scr_load(scr_screen3);
}
}
/* Setpoints pagination callbacks - Standby mode */
static void btn_setpoint_standby_prev_cb(lv_event_t *e) {
(void)e;
if (setpoint_standby_page > 0) {
setpoint_standby_page--;
if (scr_screen3) {
lv_obj_del(scr_screen3);
scr_screen3 = NULL;
}
create_screen_screen3();
lv_scr_load(scr_screen3);
}
}
static void btn_setpoint_standby_next_cb(lv_event_t *e) {
(void)e;
/* 8 parameters total, 4 per page = 2 pages */
if (setpoint_standby_page < 1) {
setpoint_standby_page++;
if (scr_screen3) {
lv_obj_del(scr_screen3);
scr_screen3 = NULL;
}
create_screen_screen3();
lv_scr_load(scr_screen3);
}
}
/* Config pagination callbacks */
static void btn_config_page_prev_cb(lv_event_t *e) {
(void)e;
if (config_page > 0) {
config_page--;
if (scr_screen4) {
lv_obj_del(scr_screen4);
scr_screen4 = NULL;
}
create_screen_config();
lv_scr_load(scr_screen4);
}
}
static void btn_config_page_next_cb(lv_event_t *e) {
(void)e;
/* Total ~15 items, 6 per page = 3 pages */
if (config_page < 2) {
config_page++;
if (scr_screen4) {
lv_obj_del(scr_screen4);
scr_screen4 = NULL;
}
create_screen_config();
lv_scr_load(scr_screen4);
}
}
static void close_popup_cb(lv_event_t *e) {
lv_obj_t *popup = lv_event_get_user_data(e);
if (popup) {
lv_obj_del(popup);
}
if (popup == popup_readings) popup_readings = NULL;
if (popup == popup_alarm) popup_alarm = NULL;
if (popup == popup_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);
lv_obj_set_style_text_color(roller_day, lv_color_hex(0xFFFFFF), LV_PART_SELECTED);
/* Month roller */
roller_month = lv_roller_create(popup_datetime);
lv_roller_set_options(roller_month, "01\n02\n03\n04\n05\n06\n07\n08\n09\n10\n11\n12", LV_ROLLER_MODE_NORMAL);
lv_roller_set_selected(roller_month, state.month - 1, LV_ANIM_OFF);
lv_obj_set_pos(roller_month, 150, 45);
lv_obj_set_size(roller_month, 60, 80);
lv_obj_set_style_text_font(roller_month, &montserrat_16_ru_en, 0);
lv_obj_set_style_bg_color(roller_month, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_text_color(roller_month, lv_color_hex(0xFFFFFF), LV_PART_SELECTED);
/* Year roller (2024-2035) */
roller_year = lv_roller_create(popup_datetime);
lv_roller_set_options(roller_year, "2024\n2025\n2026\n2027\n2028\n2029\n2030\n2031\n2032\n2033\n2034\n2035", LV_ROLLER_MODE_NORMAL);
lv_roller_set_selected(roller_year, state.year - 2024, LV_ANIM_OFF);
lv_obj_set_pos(roller_year, 220, 45);
lv_obj_set_size(roller_year, 80, 80);
lv_obj_set_style_text_font(roller_year, &montserrat_16_ru_en, 0);
lv_obj_set_style_bg_color(roller_year, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_text_color(roller_year, lv_color_hex(0xFFFFFF), LV_PART_SELECTED);
/* Time section label */
lbl = lv_label_create(popup_datetime);
lv_label_set_text(lbl, "Время:");
lv_obj_set_pos(lbl, 20, 140);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_DIM), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
/* Hour roller */
char hours[24*4];
hours[0] = '\0';
for (int i = 0; i < 24; i++) {
char tmp[5];
snprintf(tmp, sizeof(tmp), "%02d\n", i);
strcat(hours, tmp);
}
hours[strlen(hours)-1] = '\0';
roller_hour = lv_roller_create(popup_datetime);
lv_roller_set_options(roller_hour, hours, LV_ROLLER_MODE_NORMAL);
lv_roller_set_selected(roller_hour, state.hour, LV_ANIM_OFF);
lv_obj_set_pos(roller_hour, 80, 135);
lv_obj_set_size(roller_hour, 60, 80);
lv_obj_set_style_text_font(roller_hour, &montserrat_16_ru_en, 0);
lv_obj_set_style_bg_color(roller_hour, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_text_color(roller_hour, lv_color_hex(0xFFFFFF), LV_PART_SELECTED);
lbl = lv_label_create(popup_datetime);
lv_label_set_text(lbl, ":");
lv_obj_set_pos(lbl, 145, 160);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
/* Minute roller */
char minutes[60*4];
minutes[0] = '\0';
for (int i = 0; i < 60; i++) {
char tmp[5];
snprintf(tmp, sizeof(tmp), "%02d\n", i);
strcat(minutes, tmp);
}
minutes[strlen(minutes)-1] = '\0';
roller_minute = lv_roller_create(popup_datetime);
lv_roller_set_options(roller_minute, minutes, LV_ROLLER_MODE_NORMAL);
lv_roller_set_selected(roller_minute, state.minute, LV_ANIM_OFF);
lv_obj_set_pos(roller_minute, 160, 135);
lv_obj_set_size(roller_minute, 60, 80);
lv_obj_set_style_text_font(roller_minute, &montserrat_16_ru_en, 0);
lv_obj_set_style_bg_color(roller_minute, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_text_color(roller_minute, lv_color_hex(0xFFFFFF), LV_PART_SELECTED);
/* OK button */
lv_obj_t *btn_ok = lv_btn_create(popup_datetime);
lv_obj_set_pos(btn_ok, 160, 230);
lv_obj_set_size(btn_ok, 100, 40);
lv_obj_set_style_bg_color(btn_ok, lv_color_hex(COLOR_ACCENT), 0);
lv_obj_set_style_radius(btn_ok, 6, 0);
lbl = lv_label_create(btn_ok);
lv_label_set_text(lbl, "OK");
lv_obj_set_style_text_color(lbl, lv_color_hex(0xFFFFFF), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_center(lbl);
lv_obj_add_event_cb(btn_ok, datetime_ok_cb, LV_EVENT_CLICKED, NULL);
}
static void edit_datetime_cb(lv_event_t *e) {
(void)e;
create_popup_datetime();
}
/* Password keypad callback */
static void password_keypad_cb(lv_event_t *e) {
const char *key = lv_event_get_user_data(e);
int len = strlen(password_input);
if (strcmp(key, "C") == 0) {
memset(password_input, 0, sizeof(password_input));
} else if (strcmp(key, "OK") == 0) {
if (strcmp(password_input, PASSWORD) == 0) {
/* Correct password - close popup */
if (popup_password) {
lv_obj_del(popup_password);
popup_password = NULL;
lbl_password_display = NULL;
}
memset(password_input, 0, sizeof(password_input));
/* Navigate to config screen */
if (password_target == 0) {
if (scr_screen4) 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_log[alarm_count].confirmed = 0;
alarm_log[alarm_count].confirm_timestamp[0] = '\0';
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, 300);
lv_obj_set_size(panel_status, 240, 170);
lv_obj_set_style_bg_color(panel_status, lv_color_hex(COLOR_BG_PANEL), 0);
lv_obj_set_style_radius(panel_status, 6, 0);
lv_obj_set_style_border_width(panel_status, 0, 0);
lv_obj_clear_flag(panel_status, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_t *lbl = lv_label_create(panel_status);
lv_label_set_text(lbl, "Состояние");
lv_obj_set_pos(lbl, 10, 5);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_DIM), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lbl_room_num = lv_label_create(panel_status);
lv_label_set_text(lbl_room_num, "Комната: 101");
lv_obj_set_pos(lbl_room_num, 10, 30);
lv_obj_set_style_text_color(lbl_room_num, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl_room_num, &montserrat_16_ru_en, 0);
lbl_master_slave = lv_label_create(panel_status);
lv_label_set_text(lbl_master_slave, "Мастер");
lv_obj_set_pos(lbl_master_slave, 10, 55);
lv_obj_set_style_text_color(lbl_master_slave, lv_color_hex(COLOR_ACCENT), 0);
lv_obj_set_style_text_font(lbl_master_slave, &montserrat_16_ru_en, 0);
lbl_season = lv_label_create(panel_status);
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, 130);
lv_obj_set_style_text_color(lbl_datetime, lv_color_hex(COLOR_TEXT_DIM), 0);
lv_obj_set_style_text_font(lbl_datetime, &montserrat_16_ru_en, 0);
/* === CLIMATE PANEL === */
lv_obj_t *panel_climate = lv_obj_create(scr);
lv_obj_set_pos(panel_climate, 260, 300);
lv_obj_set_size(panel_climate, 230, 170);
lv_obj_set_style_bg_color(panel_climate, lv_color_hex(COLOR_BG_PANEL), 0);
lv_obj_set_style_radius(panel_climate, 6, 0);
lv_obj_set_style_border_width(panel_climate, 0, 0);
lv_obj_clear_flag(panel_climate, LV_OBJ_FLAG_SCROLLABLE);
lbl = lv_label_create(panel_climate);
lv_label_set_text(lbl, "Комната");
lv_obj_set_pos(lbl, 10, 5);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_DIM), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lbl = lv_label_create(panel_climate);
lv_label_set_text(lbl, "Температура:");
lv_obj_set_pos(lbl, 10, 35);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_DIM), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lbl_temp_room = lv_label_create(panel_climate);
lv_label_set_text(lbl_temp_room, "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 = 300;
/* Stop button */
btn_stop_mode = lv_btn_create(scr);
lv_obj_set_pos(btn_stop_mode, btn_x, btn_y);
lv_obj_set_size(btn_stop_mode, 140, 100);
lv_obj_set_style_bg_color(btn_stop_mode, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn_stop_mode, 6, 0);
lbl_stop_mode = lv_label_create(btn_stop_mode);
lv_label_set_text(lbl_stop_mode, "ОСТАНОВ");
lv_obj_set_style_text_color(lbl_stop_mode, lv_color_hex(COLOR_DANGER), 0);
lv_obj_set_style_text_font(lbl_stop_mode, &montserrat_16_ru_en, 0);
lv_obj_center(lbl_stop_mode);
lv_obj_add_event_cb(btn_stop_mode, btn_stop_cb, LV_EVENT_CLICKED, NULL);
/* Standby button */
btn_standby_mode = lv_btn_create(scr);
lv_obj_set_pos(btn_standby_mode, btn_x + 150, btn_y);
lv_obj_set_size(btn_standby_mode, 140, 48);
lv_obj_set_style_bg_color(btn_standby_mode, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn_standby_mode, 6, 0);
lbl_standby_mode = lv_label_create(btn_standby_mode);
lv_label_set_text(lbl_standby_mode, "Дежурный");
lv_obj_set_style_text_color(lbl_standby_mode, lv_color_hex(COLOR_WARNING), 0);
lv_obj_set_style_text_font(lbl_standby_mode, &montserrat_16_ru_en, 0);
lv_obj_center(lbl_standby_mode);
lv_obj_add_event_cb(btn_standby_mode, btn_standby_cb, LV_EVENT_CLICKED, NULL);
/* Work button */
btn_work_mode = lv_btn_create(scr);
lv_obj_set_pos(btn_work_mode, btn_x + 150, btn_y + 52);
lv_obj_set_size(btn_work_mode, 140, 48);
lv_obj_set_style_bg_color(btn_work_mode, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn_work_mode, 6, 0);
lbl_work_mode = lv_label_create(btn_work_mode);
lv_label_set_text(lbl_work_mode, "Рабочий");
lv_obj_set_style_text_color(lbl_work_mode, lv_color_hex(COLOR_ACCENT), 0);
lv_obj_set_style_text_font(lbl_work_mode, &montserrat_16_ru_en, 0);
lv_obj_center(lbl_work_mode);
lv_obj_add_event_cb(btn_work_mode, btn_work_cb, LV_EVENT_CLICKED, NULL);
/* Menu button */
lv_obj_t *btn = lv_btn_create(scr);
lv_obj_set_pos(btn, btn_x, btn_y + 108);
lv_obj_set_size(btn, 140, 48);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 6, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, "Меню");
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_center(lbl);
lv_obj_add_event_cb(btn, btn_menu_cb, LV_EVENT_CLICKED, NULL);
/* Readings button */
btn = lv_btn_create(scr);
lv_obj_set_pos(btn, btn_x + 150, btn_y + 108);
lv_obj_set_size(btn, 140, 48);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 6, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, "Показания");
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_ACCENT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_center(lbl);
lv_obj_add_event_cb(btn, btn_readings_cb, LV_EVENT_CLICKED, NULL);
/* Update mode button colors based on current state */
update_mode_button_colors();
/* === EQUIPMENT SCHEMA AREA === (верхняя половина экрана) */
schema_panel = lv_obj_create(scr);
lv_obj_set_pos(schema_panel, 10, 10);
lv_obj_set_size(schema_panel, 780, 280);
lv_obj_set_style_bg_color(schema_panel, lv_color_hex(COLOR_BG_PANEL), 0);
lv_obj_set_style_radius(schema_panel, 6, 0);
lv_obj_set_style_border_width(schema_panel, 0, 0);
lv_obj_clear_flag(schema_panel, LV_OBJ_FLAG_SCROLLABLE);
/* 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 edge pipes (at the start and end of the line) */
lv_obj_t *pipe_start = lv_image_create(schema_panel);
lv_image_set_src(pipe_start, &img_pipe);
lv_image_set_scale(pipe_start, MODULE_SCALE);
lv_obj_set_pos(pipe_start, 5, pipe_y);
lv_obj_t *pipe_end = lv_image_create(schema_panel);
lv_image_set_src(pipe_end, &img_pipe);
lv_image_set_scale(pipe_end, MODULE_SCALE);
lv_obj_set_pos(pipe_end, total_modules_width - 40, pipe_y);
/* Create all modules and pipe placeholders */
int active_x = 50; /* Start after the inlet pipe */
int inactive_x = 10;
for (int i = 0; i < MODULE_COUNT; i++) {
/* Create pipe placeholder for this module slot */
pipe_placeholders[i] = lv_image_create(schema_panel);
lv_image_set_src(pipe_placeholders[i], &img_pipe);
lv_image_set_scale(pipe_placeholders[i], MODULE_SCALE);
/* Will be positioned by update_module_positions */
int x_pos, y_pos;
if (modules[i].enabled) {
x_pos = active_x;
y_pos = MODULES_ACTIVE_Y;
lv_obj_add_flag(pipe_placeholders[i], LV_OBJ_FLAG_HIDDEN);
active_x += MODULE_WIDTH + MODULE_SPACING;
} else {
/* Module inactive - show pipe placeholder on active line */
lv_obj_set_pos(pipe_placeholders[i], active_x, MODULES_ACTIVE_Y);
active_x += MODULE_WIDTH + MODULE_SPACING;
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);
}
/* Update all module displays with current values */
for (int i = 0; i < MODULE_COUNT; i++) {
update_module_display(i);
}
/* Update mode button colors to reflect initial state */
update_mode_button_colors();
}
static void create_screen_screen1(void) {
lv_obj_t *scr = lv_obj_create(NULL);
scr_screen1 = scr;
lv_obj_set_style_bg_color(scr, lv_color_hex(COLOR_BG_DARK), 0);
lv_obj_set_style_bg_opa(scr, LV_OPA_COVER, 0);
lv_obj_clear_flag(scr, LV_OBJ_FLAG_SCROLLABLE);
/* Title */
lv_obj_t *lbl = lv_label_create(scr);
lv_label_set_text(lbl, "МЕНЮ");
lv_obj_set_pos(lbl, 370, 30);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
int btn_width = 300;
int btn_height = 55;
int x = 250;
int y = 90;
int spacing = 15;
/* Button: Back to Main Screen */
lv_obj_t *btn = lv_btn_create(scr);
lv_obj_set_pos(btn, x, y);
lv_obj_set_size(btn, btn_width, btn_height);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 6, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, "Главный экран");
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_ACCENT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_center(lbl);
lv_obj_add_event_cb(btn, btn_back_to_main_cb, LV_EVENT_CLICKED, NULL);
y += btn_height + spacing;
/* Button: Log (Журнал) */
btn = lv_btn_create(scr);
lv_obj_set_pos(btn, x, y);
lv_obj_set_size(btn, btn_width, btn_height);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 6, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, "Журнал аварий");
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_WARNING), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_center(lbl);
lv_obj_add_event_cb(btn, btn_log_cb, LV_EVENT_CLICKED, NULL);
y += btn_height + spacing;
/* Button: Settings Parameters (Уставка параметров) */
btn = lv_btn_create(scr);
lv_obj_set_pos(btn, x, y);
lv_obj_set_size(btn, btn_width, btn_height);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 6, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, "Уставка параметров");
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_center(lbl);
lv_obj_add_event_cb(btn, btn_settings_cb, LV_EVENT_CLICKED, NULL);
y += btn_height + spacing;
/* Button: Configuration (Настройки - требует пароль) */
btn = lv_btn_create(scr);
lv_obj_set_pos(btn, x, y);
lv_obj_set_size(btn, btn_width, btn_height);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 6, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, "Настройки");
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_DANGER), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_center(lbl);
lv_obj_add_event_cb(btn, btn_config_cb, LV_EVENT_CLICKED, NULL);
}
static void create_screen_screen2(void) {
lv_obj_t *scr = lv_obj_create(NULL);
scr_screen2 = scr;
lv_obj_set_style_bg_color(scr, lv_color_hex(COLOR_BG_DARK), 0);
lv_obj_set_style_bg_opa(scr, LV_OPA_COVER, 0);
lv_obj_clear_flag(scr, LV_OBJ_FLAG_SCROLLABLE);
/* Title */
lv_obj_t *lbl = lv_label_create(scr);
lv_label_set_text(lbl, "ЖУРНАЛ АВАРИЙ");
lv_obj_set_pos(lbl, 300, 15);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
/* Date/Time display */
char datetime_buf[32];
snprintf(datetime_buf, sizeof(datetime_buf), "%02d.%02d.%04d %02d:%02d",
state.day, state.month, state.year, state.hour, state.minute);
lbl = lv_label_create(scr);
lv_label_set_text(lbl, datetime_buf);
lv_obj_set_pos(lbl, 620, 15);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_DIM), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
/* Log panel */
lv_obj_t *log_panel = lv_obj_create(scr);
lv_obj_set_pos(log_panel, 20, 50);
lv_obj_set_size(log_panel, 760, 310);
lv_obj_set_style_bg_color(log_panel, lv_color_hex(COLOR_BG_PANEL), 0);
lv_obj_set_style_radius(log_panel, 8, 0);
lv_obj_set_style_border_width(log_panel, 1, 0);
lv_obj_set_style_border_color(log_panel, lv_color_hex(COLOR_BORDER), 0);
lv_obj_clear_flag(log_panel, LV_OBJ_FLAG_SCROLLABLE);
/* Calculate pagination */
int total_pages = (alarm_count + ALARMS_PER_PAGE - 1) / ALARMS_PER_PAGE;
if (total_pages == 0) total_pages = 1;
if (alarm_page >= total_pages) alarm_page = total_pages - 1;
if (alarm_page < 0) alarm_page = 0;
int start_idx = alarm_page * ALARMS_PER_PAGE;
int end_idx = start_idx + ALARMS_PER_PAGE;
if (end_idx > alarm_count) end_idx = alarm_count;
/* Display log entries for current page */
int y = 10;
if (alarm_count == 0) {
lbl = lv_label_create(log_panel);
lv_label_set_text(lbl, "Аварий нет");
lv_obj_set_pos(lbl, 320, 150);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_ACCENT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
} else {
for (int i = start_idx; i < end_idx; i++) {
/* Alarm entry container */
lv_obj_t *entry_cont = lv_obj_create(log_panel);
lv_obj_set_pos(entry_cont, 5, y);
lv_obj_set_size(entry_cont, 740, 75);
lv_obj_set_style_bg_color(entry_cont, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(entry_cont, 6, 0);
lv_obj_set_style_border_width(entry_cont, 1, 0);
lv_obj_set_style_border_color(entry_cont, alarm_log[i].active ? lv_color_hex(COLOR_DANGER) : lv_color_hex(COLOR_BORDER), 0);
lv_obj_clear_flag(entry_cont, LV_OBJ_FLAG_SCROLLABLE);
/* Alarm message */
char buf[120];
snprintf(buf, sizeof(buf), "%s %s", alarm_log[i].message, alarm_log[i].timestamp);
lbl = lv_label_create(entry_cont);
lv_label_set_text(lbl, buf);
lv_obj_set_pos(lbl, 10, 8);
lv_obj_set_width(lbl, 550);
lv_label_set_long_mode(lbl, LV_LABEL_LONG_CLIP);
if (alarm_log[i].active) {
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_DANGER), 0);
} else {
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
}
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
/* Confirm button or confirmed status */
if (alarm_log[i].confirmed) {
/* Show confirmed status */
lbl = lv_label_create(entry_cont);
lv_label_set_text(lbl, "Подтверждено");
lv_obj_set_pos(lbl, 580, 10);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_ACCENT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lbl = lv_label_create(entry_cont);
lv_label_set_text(lbl, alarm_log[i].confirm_timestamp);
lv_obj_set_pos(lbl, 580, 35);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_DIM), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
} else {
/* Confirm button - centered vertically */
lv_obj_t *btn_confirm = lv_btn_create(entry_cont);
lv_obj_set_pos(btn_confirm, 580, 17);
lv_obj_set_size(btn_confirm, 130, 40);
lv_obj_set_style_bg_color(btn_confirm, lv_color_hex(COLOR_BG_MODULE), 0);
lv_obj_set_style_radius(btn_confirm, 4, 0);
lbl = lv_label_create(btn_confirm);
lv_label_set_text(lbl, "Подтвердить");
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_ACCENT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_center(lbl);
lv_obj_add_event_cb(btn_confirm, btn_confirm_alarm_cb, LV_EVENT_CLICKED, (void*)(intptr_t)i);
}
y += 80;
}
}
/* Buttons row */
int btn_y = 370;
/* Left arrow button */
lv_obj_t *btn = lv_btn_create(scr);
lv_obj_set_pos(btn, 300, btn_y);
lv_obj_set_size(btn, 60, 40);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 6, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, "<");
lv_obj_set_style_text_color(lbl, alarm_page > 0 ? lv_color_hex(COLOR_TEXT) : lv_color_hex(COLOR_TEXT_DIM), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_center(lbl);
lv_obj_add_event_cb(btn, btn_alarm_page_prev_cb, LV_EVENT_CLICKED, NULL);
/* Page indicator */
char page_buf[16];
snprintf(page_buf, sizeof(page_buf), "%d / %d", alarm_page + 1, total_pages);
lbl = lv_label_create(scr);
lv_label_set_text(lbl, page_buf);
lv_obj_set_pos(lbl, 380, btn_y + 10);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
/* Right arrow button */
btn = lv_btn_create(scr);
lv_obj_set_pos(btn, 440, btn_y);
lv_obj_set_size(btn, 60, 40);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 6, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, ">");
lv_obj_set_style_text_color(lbl, alarm_page < total_pages - 1 ? lv_color_hex(COLOR_TEXT) : lv_color_hex(COLOR_TEXT_DIM), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_center(lbl);
lv_obj_add_event_cb(btn, btn_alarm_page_next_cb, LV_EVENT_CLICKED, NULL);
/* Bottom buttons row */
btn_y = 420;
/* Clear Log button */
btn = lv_btn_create(scr);
lv_obj_set_pos(btn, 250, btn_y);
lv_obj_set_size(btn, 160, 40);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 6, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, "Очистить журнал");
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_DANGER), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_center(lbl);
lv_obj_add_event_cb(btn, btn_clear_log_cb, LV_EVENT_CLICKED, NULL);
/* Back to Menu button */
btn = lv_btn_create(scr);
lv_obj_set_pos(btn, 430, btn_y);
lv_obj_set_size(btn, 120, 40);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 6, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, "Меню");
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_center(lbl);
lv_obj_add_event_cb(btn, btn_menu_cb, LV_EVENT_CLICKED, NULL);
}
static void create_screen_screen3(void) {
lv_obj_t *scr = lv_obj_create(NULL);
scr_screen3 = scr;
lv_obj_set_style_bg_color(scr, lv_color_hex(COLOR_BG_DARK), 0);
lv_obj_set_style_bg_opa(scr, LV_OPA_COVER, 0);
lv_obj_clear_flag(scr, LV_OBJ_FLAG_SCROLLABLE);
/* Title */
lv_obj_t *lbl = lv_label_create(scr);
lv_label_set_text(lbl, "УСТАВКА ПАРАМЕТРОВ");
lv_obj_set_pos(lbl, 290, 10);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
char buf[64];
int row_h = 38;
const char *heater_modes[] = {"ВЫКЛ", "ВКЛ", "АВТО"};
/* Left panel - Work mode - NO SCROLL, with pagination */
lv_obj_t *panel_work = lv_obj_create(scr);
lv_obj_set_pos(panel_work, 15, 40);
lv_obj_set_size(panel_work, 380, 270);
lv_obj_set_style_bg_color(panel_work, lv_color_hex(COLOR_BG_PANEL), 0);
lv_obj_set_style_radius(panel_work, 6, 0);
lv_obj_set_style_border_width(panel_work, 0, 0);
lv_obj_clear_flag(panel_work, LV_OBJ_FLAG_SCROLLABLE);
lbl = lv_label_create(panel_work);
lv_label_set_text(lbl, "РАБОЧИЙ");
lv_obj_set_pos(lbl, 10, 5);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_ACCENT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
/* Page indicator for Work mode */
snprintf(buf, sizeof(buf), "%d/2", setpoint_work_page + 1);
lbl = lv_label_create(panel_work);
lv_label_set_text(lbl, buf);
lv_obj_set_pos(lbl, 330, 5);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_DIM), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
int y = 32;
lv_obj_t *btn;
/* Work mode parameters - page 0: Temp, Hum, Valve inlet, Valve recirc */
/* Work mode parameters - page 1: Heater, Fan, KWS, PWW */
if (setpoint_work_page == 0) {
/* Temperature */
btn = lv_btn_create(panel_work);
lv_obj_set_pos(btn, 5, y);
lv_obj_set_size(btn, 350, row_h);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
snprintf(buf, sizeof(buf), "Температура: %.1f°C", setpoint_work.temp);
lbl_work_temp = lv_label_create(btn);
lv_label_set_text(lbl_work_temp, buf);
lv_obj_center(lbl_work_temp);
lv_obj_set_style_text_color(lbl_work_temp, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl_work_temp, &montserrat_16_ru_en, 0);
lv_obj_add_event_cb(btn, edit_work_temp_cb, LV_EVENT_CLICKED, NULL);
y += row_h + 6;
/* Humidity */
btn = lv_btn_create(panel_work);
lv_obj_set_pos(btn, 5, y);
lv_obj_set_size(btn, 350, row_h);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
snprintf(buf, sizeof(buf), "Влажность: %.1f%%", setpoint_work.hum);
lbl_work_hum = lv_label_create(btn);
lv_label_set_text(lbl_work_hum, buf);
lv_obj_center(lbl_work_hum);
lv_obj_set_style_text_color(lbl_work_hum, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl_work_hum, &montserrat_16_ru_en, 0);
lv_obj_add_event_cb(btn, edit_work_hum_cb, LV_EVENT_CLICKED, NULL);
y += row_h + 6;
/* Valve inlet */
btn = lv_btn_create(panel_work);
lv_obj_set_pos(btn, 5, y);
lv_obj_set_size(btn, 350, row_h);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
snprintf(buf, sizeof(buf), "Клапан притока: %.1f%%", setpoint_work.valve_inlet);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, buf);
lv_obj_center(lbl);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_add_event_cb(btn, edit_work_valve_inlet_cb, LV_EVENT_CLICKED, NULL);
y += row_h + 6;
/* Valve recirc */
btn = lv_btn_create(panel_work);
lv_obj_set_pos(btn, 5, y);
lv_obj_set_size(btn, 350, row_h);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
snprintf(buf, sizeof(buf), "Клапан рецирк: %.1f%%", setpoint_work.valve_recirc);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, buf);
lv_obj_center(lbl);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_add_event_cb(btn, edit_work_valve_recirc_cb, LV_EVENT_CLICKED, NULL);
} else {
/* Page 1: Heater, Fan, KWS, PWW */
/* Heater mode */
btn = lv_btn_create(panel_work);
lv_obj_set_pos(btn, 5, y);
lv_obj_set_size(btn, 350, row_h);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
snprintf(buf, sizeof(buf), "Электронагрев: %s", heater_modes[setpoint_work.heater]);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, buf);
lv_obj_center(lbl);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_add_event_cb(btn, edit_work_heater_cb, LV_EVENT_CLICKED, NULL);
y += row_h + 6;
/* Fan toggle */
btn = lv_btn_create(panel_work);
lv_obj_set_pos(btn, 5, y);
lv_obj_set_size(btn, 350, row_h);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
snprintf(buf, sizeof(buf), "Вентилятор: %s", setpoint_work.fan ? "ВКЛ" : "ВЫКЛ");
lbl = lv_label_create(btn);
lv_label_set_text(lbl, buf);
lv_obj_center(lbl);
lv_obj_set_style_text_color(lbl, setpoint_work.fan ? lv_color_hex(COLOR_ACCENT) : lv_color_hex(COLOR_TEXT_DIM), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_add_event_cb(btn, edit_work_fan_cb, LV_EVENT_CLICKED, NULL);
y += row_h + 6;
/* KWS mode */
btn = lv_btn_create(panel_work);
lv_obj_set_pos(btn, 5, y);
lv_obj_set_size(btn, 350, row_h);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
if (setpoint_work.kws_auto) {
snprintf(buf, sizeof(buf), "KWS: АВТО");
} else {
snprintf(buf, sizeof(buf), "KWS: %.1f%%", setpoint_work.kws_manual);
}
lbl = lv_label_create(btn);
lv_label_set_text(lbl, buf);
lv_obj_center(lbl);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_add_event_cb(btn, edit_work_kws_cb, LV_EVENT_CLICKED, NULL);
y += row_h + 6;
/* PWW mode */
btn = lv_btn_create(panel_work);
lv_obj_set_pos(btn, 5, y);
lv_obj_set_size(btn, 350, row_h);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
if (setpoint_work.pww_auto) {
snprintf(buf, sizeof(buf), "PWW: АВТО");
} else {
snprintf(buf, sizeof(buf), "PWW: %.1f%%", setpoint_work.pww_manual);
}
lbl = lv_label_create(btn);
lv_label_set_text(lbl, buf);
lv_obj_center(lbl);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_add_event_cb(btn, edit_work_pww_cb, LV_EVENT_CLICKED, NULL);
}
/* Pagination buttons for Work panel */
btn = lv_btn_create(panel_work);
lv_obj_set_pos(btn, 80, 216);
lv_obj_set_size(btn, 80, 38);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, "<");
lv_obj_set_style_text_color(lbl, setpoint_work_page > 0 ? lv_color_hex(COLOR_TEXT) : lv_color_hex(COLOR_TEXT_DIM), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_center(lbl);
lv_obj_add_event_cb(btn, btn_setpoint_work_prev_cb, LV_EVENT_CLICKED, NULL);
/* Page indicator */
snprintf(buf, sizeof(buf), "%d/2", setpoint_work_page + 1);
lbl = lv_label_create(panel_work);
lv_label_set_text(lbl, buf);
lv_obj_set_pos(lbl, 180, 226);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_DIM), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
btn = lv_btn_create(panel_work);
lv_obj_set_pos(btn, 210, 216);
lv_obj_set_size(btn, 80, 38);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, ">");
lv_obj_set_style_text_color(lbl, setpoint_work_page < 1 ? lv_color_hex(COLOR_TEXT) : lv_color_hex(COLOR_TEXT_DIM), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_center(lbl);
lv_obj_add_event_cb(btn, btn_setpoint_work_next_cb, LV_EVENT_CLICKED, NULL);
/* Right panel - Standby mode - NO SCROLL, with pagination */
lv_obj_t *panel_standby = lv_obj_create(scr);
lv_obj_set_pos(panel_standby, 405, 40);
lv_obj_set_size(panel_standby, 380, 270);
lv_obj_set_style_bg_color(panel_standby, lv_color_hex(COLOR_BG_PANEL), 0);
lv_obj_set_style_radius(panel_standby, 6, 0);
lv_obj_set_style_border_width(panel_standby, 0, 0);
lv_obj_clear_flag(panel_standby, LV_OBJ_FLAG_SCROLLABLE);
lbl = lv_label_create(panel_standby);
lv_label_set_text(lbl, "ДЕЖУРНЫЙ");
lv_obj_set_pos(lbl, 10, 5);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_WARNING), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
/* Page indicator for Standby mode */
snprintf(buf, sizeof(buf), "%d/2", setpoint_standby_page + 1);
lbl = lv_label_create(panel_standby);
lv_label_set_text(lbl, buf);
lv_obj_set_pos(lbl, 330, 5);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_DIM), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
y = 32;
/* Standby mode parameters - page 0: Temp, Hum, Valve inlet, Valve recirc */
/* Standby mode parameters - page 1: Heater, Fan, KWS, PWW */
if (setpoint_standby_page == 0) {
/* Standby Temperature */
btn = lv_btn_create(panel_standby);
lv_obj_set_pos(btn, 5, y);
lv_obj_set_size(btn, 350, row_h);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
snprintf(buf, sizeof(buf), "Температура: %.1f°C", setpoint_standby.temp);
lbl_standby_temp = lv_label_create(btn);
lv_label_set_text(lbl_standby_temp, buf);
lv_obj_center(lbl_standby_temp);
lv_obj_set_style_text_color(lbl_standby_temp, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl_standby_temp, &montserrat_16_ru_en, 0);
lv_obj_add_event_cb(btn, edit_standby_temp_cb, LV_EVENT_CLICKED, NULL);
y += row_h + 6;
/* Standby Humidity */
btn = lv_btn_create(panel_standby);
lv_obj_set_pos(btn, 5, y);
lv_obj_set_size(btn, 350, row_h);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
snprintf(buf, sizeof(buf), "Влажность: %.1f%%", setpoint_standby.hum);
lbl_standby_hum = lv_label_create(btn);
lv_label_set_text(lbl_standby_hum, buf);
lv_obj_center(lbl_standby_hum);
lv_obj_set_style_text_color(lbl_standby_hum, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl_standby_hum, &montserrat_16_ru_en, 0);
lv_obj_add_event_cb(btn, edit_standby_hum_cb, LV_EVENT_CLICKED, NULL);
y += row_h + 6;
/* Standby Valve inlet */
btn = lv_btn_create(panel_standby);
lv_obj_set_pos(btn, 5, y);
lv_obj_set_size(btn, 350, row_h);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
snprintf(buf, sizeof(buf), "Клапан притока: %.1f%%", setpoint_standby.valve_inlet);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, buf);
lv_obj_center(lbl);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_add_event_cb(btn, edit_standby_valve_inlet_cb, LV_EVENT_CLICKED, NULL);
y += row_h + 6;
/* Standby Valve recirc */
btn = lv_btn_create(panel_standby);
lv_obj_set_pos(btn, 5, y);
lv_obj_set_size(btn, 350, row_h);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
snprintf(buf, sizeof(buf), "Клапан рецирк: %.1f%%", setpoint_standby.valve_recirc);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, buf);
lv_obj_center(lbl);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_add_event_cb(btn, edit_standby_valve_recirc_cb, LV_EVENT_CLICKED, NULL);
} else {
/* Page 1: Heater, Fan, KWS, PWW */
/* Heater mode */
btn = lv_btn_create(panel_standby);
lv_obj_set_pos(btn, 5, y);
lv_obj_set_size(btn, 350, row_h);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
snprintf(buf, sizeof(buf), "Электронагрев: %s", heater_modes[setpoint_standby.heater]);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, buf);
lv_obj_center(lbl);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_add_event_cb(btn, edit_standby_heater_cb, LV_EVENT_CLICKED, NULL);
y += row_h + 6;
/* Fan toggle */
btn = lv_btn_create(panel_standby);
lv_obj_set_pos(btn, 5, y);
lv_obj_set_size(btn, 350, row_h);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
snprintf(buf, sizeof(buf), "Вентилятор: %s", setpoint_standby.fan ? "ВКЛ" : "ВЫКЛ");
lbl = lv_label_create(btn);
lv_label_set_text(lbl, buf);
lv_obj_center(lbl);
lv_obj_set_style_text_color(lbl, setpoint_standby.fan ? lv_color_hex(COLOR_ACCENT) : lv_color_hex(COLOR_TEXT_DIM), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_add_event_cb(btn, edit_standby_fan_cb, LV_EVENT_CLICKED, NULL);
y += row_h + 6;
/* KWS mode */
btn = lv_btn_create(panel_standby);
lv_obj_set_pos(btn, 5, y);
lv_obj_set_size(btn, 350, row_h);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
if (setpoint_standby.kws_auto) {
snprintf(buf, sizeof(buf), "KWS: АВТО");
} else {
snprintf(buf, sizeof(buf), "KWS: %.1f%%", setpoint_standby.kws_manual);
}
lbl = lv_label_create(btn);
lv_label_set_text(lbl, buf);
lv_obj_center(lbl);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_add_event_cb(btn, edit_standby_kws_cb, LV_EVENT_CLICKED, NULL);
y += row_h + 6;
/* PWW mode */
btn = lv_btn_create(panel_standby);
lv_obj_set_pos(btn, 5, y);
lv_obj_set_size(btn, 350, row_h);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
if (setpoint_standby.pww_auto) {
snprintf(buf, sizeof(buf), "PWW: АВТО");
} else {
snprintf(buf, sizeof(buf), "PWW: %.1f%%", setpoint_standby.pww_manual);
}
lbl = lv_label_create(btn);
lv_label_set_text(lbl, buf);
lv_obj_center(lbl);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_add_event_cb(btn, edit_standby_pww_cb, LV_EVENT_CLICKED, NULL);
}
/* Pagination buttons for Standby panel */
btn = lv_btn_create(panel_standby);
lv_obj_set_pos(btn, 80, 216);
lv_obj_set_size(btn, 80, 38);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, "<");
lv_obj_set_style_text_color(lbl, setpoint_standby_page > 0 ? lv_color_hex(COLOR_TEXT) : lv_color_hex(COLOR_TEXT_DIM), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_center(lbl);
lv_obj_add_event_cb(btn, btn_setpoint_standby_prev_cb, LV_EVENT_CLICKED, NULL);
/* Page indicator */
snprintf(buf, sizeof(buf), "%d/2", setpoint_standby_page + 1);
lbl = lv_label_create(panel_standby);
lv_label_set_text(lbl, buf);
lv_obj_set_pos(lbl, 180, 226);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_DIM), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
btn = lv_btn_create(panel_standby);
lv_obj_set_pos(btn, 210, 216);
lv_obj_set_size(btn, 80, 38);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, ">");
lv_obj_set_style_text_color(lbl, setpoint_standby_page < 1 ? lv_color_hex(COLOR_TEXT) : lv_color_hex(COLOR_TEXT_DIM), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_center(lbl);
lv_obj_add_event_cb(btn, btn_setpoint_standby_next_cb, LV_EVENT_CLICKED, NULL);
/* Common parameters section */
lv_obj_t *panel_common = lv_obj_create(scr);
lv_obj_set_pos(panel_common, 15, 320);
lv_obj_set_size(panel_common, 770, 60);
lv_obj_set_style_bg_color(panel_common, lv_color_hex(COLOR_BG_PANEL), 0);
lv_obj_set_style_radius(panel_common, 6, 0);
lv_obj_set_style_border_width(panel_common, 0, 0);
lv_obj_clear_flag(panel_common, LV_OBJ_FLAG_SCROLLABLE);
lbl = lv_label_create(panel_common);
lv_label_set_text(lbl, "Общие:");
lv_obj_set_pos(lbl, 10, 3);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_DIM), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
snprintf(buf, sizeof(buf), "Фильтр G4: %s", filter_g4_date);
lbl = lv_label_create(panel_common);
lv_label_set_text(lbl, buf);
lv_obj_set_pos(lbl, 10, 28);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
snprintf(buf, sizeof(buf), "Фильтр H13: %s", filter_h13_date);
lbl = lv_label_create(panel_common);
lv_label_set_text(lbl, buf);
lv_obj_set_pos(lbl, 250, 28);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
/* Buttons row */
int btn_y = 395;
btn = lv_btn_create(scr);
lv_obj_set_pos(btn, 200, btn_y);
lv_obj_set_size(btn, 110, 38);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, "Сохранить");
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_ACCENT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_center(lbl);
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);
/* Single unified panel - NO SCROLL, with pagination */
lv_obj_t *panel_main = lv_obj_create(scr);
lv_obj_set_pos(panel_main, 15, 40);
lv_obj_set_size(panel_main, 770, 310);
lv_obj_set_style_bg_color(panel_main, lv_color_hex(COLOR_BG_PANEL), 0);
lv_obj_set_style_radius(panel_main, 6, 0);
lv_obj_set_style_border_width(panel_main, 0, 0);
lv_obj_clear_flag(panel_main, LV_OBJ_FLAG_SCROLLABLE);
/* Page indicator */
snprintf(buf, sizeof(buf), "%d/3", config_page + 1);
lbl = lv_label_create(panel_main);
lv_label_set_text(lbl, buf);
lv_obj_set_pos(lbl, 720, 5);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_DIM), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_t *btn;
int y = 35;
/* PAGE 0: Sensor corrections (4 параметра чтобы не налазить на кнопки) */
if (config_page == 0) {
lbl = lv_label_create(panel_main);
lv_label_set_text(lbl, "Коррекция датчиков");
lv_obj_set_pos(lbl, 10, 5);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_WARNING), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
const char *labels[] = {
"Темп. помещения:",
"Влажн. помещения:",
"Темп. в канале:",
"Влажн. в канале:"
};
float *offsets[] = {
&config.temp_room_offset,
&config.hum_room_offset,
&config.temp_channel_offset,
&config.hum_channel_offset
};
for (int i = 0; i < 4; i++) {
btn = lv_btn_create(panel_main);
lv_obj_set_pos(btn, 10, y);
lv_obj_set_size(btn, 740, 40);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
lv_obj_t *lbl_name = lv_label_create(btn);
lv_label_set_text(lbl_name, labels[i]);
lv_obj_align(lbl_name, LV_ALIGN_LEFT_MID, 15, 0);
lv_obj_set_style_text_color(lbl_name, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl_name, &montserrat_16_ru_en, 0);
snprintf(buf, sizeof(buf), "%+.1f", *offsets[i]);
lv_obj_t *lbl_val = lv_label_create(btn);
lv_label_set_text(lbl_val, buf);
lv_obj_align(lbl_val, LV_ALIGN_RIGHT_MID, -15, 0);
lv_obj_set_style_text_color(lbl_val, lv_color_hex(COLOR_ACCENT), 0);
lv_obj_set_style_text_font(lbl_val, &montserrat_16_ru_en, 0);
lv_obj_add_event_cb(btn, edit_config_offset_cb, LV_EVENT_CLICKED, offsets[i]);
y += 50;
}
}
/* PAGE 1: Remaining sensor corrections + Equipment and PID regulators */
else if (config_page == 1) {
lbl = lv_label_create(panel_main);
lv_label_set_text(lbl, "Коррекция датчиков (продолжение)");
lv_obj_set_pos(lbl, 10, 5);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_WARNING), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
/* Клапан притока */
btn = lv_btn_create(panel_main);
lv_obj_set_pos(btn, 10, y);
lv_obj_set_size(btn, 360, 40);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
lv_obj_t *lbl_name = lv_label_create(btn);
lv_label_set_text(lbl_name, "Клапан притока:");
lv_obj_align(lbl_name, LV_ALIGN_LEFT_MID, 15, 0);
lv_obj_set_style_text_color(lbl_name, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl_name, &montserrat_16_ru_en, 0);
snprintf(buf, sizeof(buf), "%+.1f", config.valve_inlet_offset);
lv_obj_t *lbl_val = lv_label_create(btn);
lv_label_set_text(lbl_val, buf);
lv_obj_align(lbl_val, LV_ALIGN_RIGHT_MID, -15, 0);
lv_obj_set_style_text_color(lbl_val, lv_color_hex(COLOR_ACCENT), 0);
lv_obj_set_style_text_font(lbl_val, &montserrat_16_ru_en, 0);
lv_obj_add_event_cb(btn, edit_config_offset_cb, LV_EVENT_CLICKED, &config.valve_inlet_offset);
/* Клапан рециркуляции */
btn = lv_btn_create(panel_main);
lv_obj_set_pos(btn, 390, y);
lv_obj_set_size(btn, 360, 40);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
lbl_name = lv_label_create(btn);
lv_label_set_text(lbl_name, "Клапан рецирк.:");
lv_obj_align(lbl_name, LV_ALIGN_LEFT_MID, 15, 0);
lv_obj_set_style_text_color(lbl_name, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl_name, &montserrat_16_ru_en, 0);
snprintf(buf, sizeof(buf), "%+.1f", config.valve_recirc_offset);
lbl_val = lv_label_create(btn);
lv_label_set_text(lbl_val, buf);
lv_obj_align(lbl_val, LV_ALIGN_RIGHT_MID, -15, 0);
lv_obj_set_style_text_color(lbl_val, lv_color_hex(COLOR_ACCENT), 0);
lv_obj_set_style_text_font(lbl_val, &montserrat_16_ru_en, 0);
lv_obj_add_event_cb(btn, edit_config_offset_cb, LV_EVENT_CLICKED, &config.valve_recirc_offset);
y += 55;
/* Оборудование */
lbl = lv_label_create(panel_main);
lv_label_set_text(lbl, "Оборудование");
lv_obj_set_pos(lbl, 10, y);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_WARNING), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
y += 25;
/* Toggles for equipment presence */
btn = lv_btn_create(panel_main);
lv_obj_set_pos(btn, 10, y);
lv_obj_set_size(btn, 360, 40);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, "Клапан рецирк.:");
lv_obj_align(lbl, LV_ALIGN_LEFT_MID, 15, 0);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, config.has_recirc_valve ? "ЕСТЬ" : "НЕТ");
lv_obj_align(lbl, LV_ALIGN_RIGHT_MID, -15, 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);
btn = lv_btn_create(panel_main);
lv_obj_set_pos(btn, 390, y);
lv_obj_set_size(btn, 360, 40);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, "Электронагрев.:");
lv_obj_align(lbl, LV_ALIGN_LEFT_MID, 15, 0);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, config.has_heater ? "ЕСТЬ" : "НЕТ");
lv_obj_align(lbl, LV_ALIGN_RIGHT_MID, -15, 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);
}
/* PAGE 2: PID regulators, Mode settings and Date/Time */
else {
/* PID regulators */
lbl = lv_label_create(panel_main);
lv_label_set_text(lbl, "ПИД регуляторы");
lv_obj_set_pos(lbl, 10, 5);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_WARNING), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
/* PWW PID */
lbl = lv_label_create(panel_main);
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_main);
lv_obj_set_pos(btn, 10, y);
lv_obj_set_size(btn, 120, 36);
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), "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_main);
lv_obj_set_pos(btn, 140, y);
lv_obj_set_size(btn, 120, 36);
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), "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_main);
lv_obj_set_pos(btn, 270, y);
lv_obj_set_size(btn, 120, 36);
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), "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 += 46;
/* KWS PID */
lbl = lv_label_create(panel_main);
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_main);
lv_obj_set_pos(btn, 10, y);
lv_obj_set_size(btn, 120, 36);
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), "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_main);
lv_obj_set_pos(btn, 140, y);
lv_obj_set_size(btn, 120, 36);
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), "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_main);
lv_obj_set_pos(btn, 270, y);
lv_obj_set_size(btn, 120, 36);
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), "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 += 55;
lbl = lv_label_create(panel_main);
lv_label_set_text(lbl, "Режим работы");
lv_obj_set_pos(lbl, 10, y);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_WARNING), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
y += 25;
btn = lv_btn_create(panel_main);
lv_obj_set_pos(btn, 10, y);
lv_obj_set_size(btn, 740, 40);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, "Принуд. режим:");
lv_obj_align(lbl, LV_ALIGN_LEFT_MID, 15, 0);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, config.force_mode_enabled ? "ВКЛ" : "ВЫКЛ");
lv_obj_align(lbl, LV_ALIGN_RIGHT_MID, -15, 0);
lv_obj_set_style_text_color(lbl, config.force_mode_enabled ? lv_color_hex(COLOR_WARNING) : lv_color_hex(COLOR_TEXT_DIM), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_add_event_cb(btn, toggle_force_mode_cb, LV_EVENT_CLICKED, NULL);
y += 45;
btn = lv_btn_create(panel_main);
lv_obj_set_pos(btn, 10, y);
lv_obj_set_size(btn, 740, 40);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, "Перемена ключа:");
lv_obj_align(lbl, LV_ALIGN_LEFT_MID, 15, 0);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, config.key_swap_enabled ? "ВКЛ" : "ВЫКЛ");
lv_obj_align(lbl, LV_ALIGN_RIGHT_MID, -15, 0);
lv_obj_set_style_text_color(lbl, config.key_swap_enabled ? lv_color_hex(COLOR_ACCENT) : lv_color_hex(COLOR_TEXT_DIM), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_add_event_cb(btn, toggle_key_swap_cb, LV_EVENT_CLICKED, NULL);
y += 55;
lbl = lv_label_create(panel_main);
lv_label_set_text(lbl, "Дата и время");
lv_obj_set_pos(lbl, 10, y);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_WARNING), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
y += 30;
btn = lv_btn_create(panel_main);
lv_obj_set_pos(btn, 10, y);
lv_obj_set_size(btn, 740, 40);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
snprintf(buf, sizeof(buf), "%02d.%02d.%04d %02d:%02d",
state.day, state.month, state.year, state.hour, state.minute);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, buf);
lv_obj_center(lbl);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_ACCENT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_add_event_cb(btn, edit_datetime_cb, LV_EVENT_CLICKED, NULL);
}
/* Pagination buttons - below content */
btn = lv_btn_create(scr);
lv_obj_set_pos(btn, 240, 365);
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, config_page > 0 ? lv_color_hex(COLOR_TEXT) : lv_color_hex(COLOR_TEXT_DIM), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_center(lbl);
lv_obj_add_event_cb(btn, btn_config_page_prev_cb, LV_EVENT_CLICKED, NULL);
/* Page indicator */
lbl = lv_label_create(scr);
snprintf(buf, sizeof(buf), "%d/3", config_page + 1);
lv_label_set_text(lbl, buf);
lv_obj_set_pos(lbl, 365, 375);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_DIM), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
btn = lv_btn_create(scr);
lv_obj_set_pos(btn, 430, 365);
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, config_page < 2 ? lv_color_hex(COLOR_TEXT) : lv_color_hex(COLOR_TEXT_DIM), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_center(lbl);
lv_obj_add_event_cb(btn, btn_config_page_next_cb, LV_EVENT_CLICKED, NULL);
/* Bottom buttons - using screen3 style colors */
btn = lv_btn_create(scr);
lv_obj_set_pos(btn, 200, 420);
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, 420);
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, 420);
lv_obj_set_size(btn, 100, 38);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, "Меню");
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_center(lbl);
lv_obj_add_event_cb(btn, btn_menu_cb, LV_EVENT_CLICKED, NULL);
}
void gui_update_values(void) {
char buf[64];
/* 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) {
/* Журнал начинается пустым - аварии добавляются при необходимости */
create_screen_main();
create_screen_screen1();
create_screen_screen3();
create_screen_config();
lv_scr_load(scr_main);
/* Проверка аварийных ситуаций после загрузки главного экрана */
if (!state.filter_g4_clean) {
add_alarm_to_log("ФИЛЬТР G4 ЗАСОРИЛСЯ - Замените фильтр", 1);
state.mode = 0;
update_mode_button_colors();
}
if (state.temp_channel < 50) {
add_alarm_to_log("УГРОЗА ЗАМОРОЗКИ - Установка остановлена", 1);
state.mode = 0;
update_mode_button_colors();
}
/* Create screen2 after alarms are added */
create_screen_screen2();
/* Show alarm popup if needed */
if (!state.filter_g4_clean) {
create_popup_alarm("ФИЛЬТР G4 ЗАСОРИЛСЯ - Замените фильтр");
} else if (state.temp_channel < 50) {
create_popup_alarm("УГРОЗА ЗАМОРОЗКИ - Установка остановлена");
}
}
/* ── 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 */
}
}