lvgl-hmi-display-ui/src/gui_generated.c

2845 lines
108 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;
/* Pipe placeholders for inactive modules in active line */
static lv_obj_t *pipe_placeholders[MODULE_COUNT] = {NULL};
/* Drag state for modules */
static lv_obj_t *dragged_module = NULL;
static int dragged_module_idx = -1;
static lv_point_t drag_start_pos;
/* Alarm log structure */
#define MAX_ALARMS 20
typedef struct {
char message[80];
char timestamp[20];
int active;
} alarm_entry_t;
static alarm_entry_t alarm_log[MAX_ALARMS];
static int alarm_count = 0;
/* Settings/Setpoints for Work and Standby modes */
typedef struct {
float temp; /* Температура */
float hum; /* Влажность */
float valve_inlet; /* Клапан притока */
float valve_recirc; /* Клапан рециркуляции */
uint8_t heater; /* 0=ВЫКЛ, 1=ВКЛ, 2=АВТО */
uint8_t fan; /* 0=ВЫКЛ, 1=ВКЛ */
uint8_t kws_auto; /* 1=АВТО, 0=ручной режим */
float kws_manual; /* Значение для ручного режима */
uint8_t pww_auto; /* 1=АВТО, 0=ручной режим */
float pww_manual; /* Значение для ручного режима */
} setpoint_mode_t;
static setpoint_mode_t setpoint_work = {
.temp = 22.0f, .hum = 50.0f, .valve_inlet = 75.0f, .valve_recirc = 25.0f,
.heater = 2, .fan = 1, .kws_auto = 1, .kws_manual = 0, .pww_auto = 1, .pww_manual = 0
};
static setpoint_mode_t setpoint_standby = {
.temp = 18.0f, .hum = 45.0f, .valve_inlet = 35.0f, .valve_recirc = 40.0f,
.heater = 0, .fan = 1, .kws_auto = 1, .kws_manual = 0, .pww_auto = 1, .pww_manual = 0
};
/* Configuration parameters (Настройки - с паролем) */
typedef struct {
float temp_room_offset;
float hum_room_offset;
float temp_channel_offset;
float hum_channel_offset;
float valve_inlet_offset;
float valve_recirc_offset;
uint8_t has_recirc_valve;
uint8_t has_heater;
float pid_pww_kp;
float pid_pww_ti;
float pid_pww_d;
float pid_kws_kp;
float pid_kws_ti;
float pid_kws_d;
uint8_t force_mode_enabled;
uint8_t force_mode_value;
uint8_t key_swap_enabled;
} config_t;
static config_t config = {
.temp_room_offset = 0.0f,
.hum_room_offset = 0.0f,
.temp_channel_offset = 0.0f,
.hum_channel_offset = 0.0f,
.valve_inlet_offset = 0.0f,
.valve_recirc_offset = 0.0f,
.has_recirc_valve = 1,
.has_heater = 1,
.pid_pww_kp = 1.0f,
.pid_pww_ti = 10.0f,
.pid_pww_d = 0.0f,
.pid_kws_kp = 1.0f,
.pid_kws_ti = 10.0f,
.pid_kws_d = 0.0f,
.force_mode_enabled = 0,
.force_mode_value = 1,
.key_swap_enabled = 1
};
/* Filter replacement dates */
static char filter_g4_date[12] = "01.01.2026";
static char filter_h13_date[12] = "01.01.2026";
/* Password and settings */
#define PASSWORD "1234"
static char password_input[10] = "";
static lv_obj_t *popup_password = NULL;
static lv_obj_t *lbl_password_display = NULL;
static int password_target = 0; /* 0=настройки, 1=очистка журнала фильтров */
/* Parameter editor */
static lv_obj_t *popup_param_editor = NULL;
static lv_obj_t *lbl_param_value = NULL;
static char param_value_str[20] = "";
static float *param_being_edited = NULL;
static char param_name[50] = "";
static int param_is_percentage = 0;
/* Configuration parameters */
/* (moved to config_t structure above) */
/* UI element references for updates */
static lv_obj_t *lbl_temp_room = NULL;
static lv_obj_t *lbl_hum_room = NULL;
static lv_obj_t *lbl_temp_channel = NULL;
static lv_obj_t *lbl_hum_channel = NULL;
static lv_obj_t *lbl_room_num = NULL;
static lv_obj_t *lbl_master_slave = NULL;
static lv_obj_t *lbl_season = NULL;
static lv_obj_t *lbl_mode = NULL;
static lv_obj_t *lbl_datetime = NULL;
/* Equipment UI references */
static lv_obj_t *panel_schema = NULL;
/* Popup windows */
static lv_obj_t *popup_readings = NULL;
static lv_obj_t *popup_alarm = NULL;
/* Setpoints screen UI */
static lv_obj_t *lbl_work_temp = NULL;
static lv_obj_t *lbl_work_hum = NULL;
static lv_obj_t *lbl_standby_temp = NULL;
static lv_obj_t *lbl_standby_hum = NULL;
/* Forward declarations */
static void create_popup_readings(void);
static void create_popup_alarm(const char *message);
static void add_alarm_to_log(const char *message, int active);
static void btn_log_cb(lv_event_t *e);
static void btn_settings_cb(lv_event_t *e);
static void btn_config_cb(lv_event_t *e);
static void create_popup_password(int target);
static void password_keypad_cb(lv_event_t *e);
static void create_popup_param_editor(const char *name, float *param_ptr, int is_percentage);
static void param_keypad_cb(lv_event_t *e);
static void close_popup_cb(lv_event_t *e);
static void btn_back_to_main_cb(lv_event_t *e);
static void create_screen_config(void);
static void create_screen_screen2(void);
static void module_drag_cb(lv_event_t *e);
static void update_module_display(int module_idx);
static void refresh_setpoints_display(void);
static void btn_reset_alarm_cb(lv_event_t *e);
static void btn_clear_log_cb(lv_event_t *e);
static lv_obj_t* create_module_widget(lv_obj_t *parent, int module_idx, int x, int y);
static void update_module_positions(void);
/* Get image for module type */
static const lv_image_dsc_t* get_module_image(int module_idx) {
switch (module_idx) {
case MODULE_VALVE_INLET:
case MODULE_VALVE_RECIRC:
return &img_valve;
case MODULE_FILTER_G4:
return &img_filter_g4;
case MODULE_KWS:
return state.kws_mode ? &img_heater : &img_cooler;
case MODULE_PWW:
return state.pww_mode ? &img_heater : &img_cooler;
case MODULE_HEATER:
return &img_electric_heater;
case MODULE_FAN:
return &img_fan;
case MODULE_FILTER_H13:
return &img_filter_h13;
case MODULE_CHANNEL:
return &img_channel;
default:
return &img_pipe;
}
}
/* Get name for module type */
static const char* get_module_name(int module_idx) {
switch (module_idx) {
case MODULE_VALVE_INLET:
return "Вход";
case MODULE_VALVE_RECIRC:
return "Рецирк";
case MODULE_FILTER_G4:
return "G4";
case MODULE_KWS:
return "KWS";
case MODULE_PWW:
return "PWW";
case MODULE_HEATER:
return "Нагрев";
case MODULE_FAN:
return "Вент";
case MODULE_FILTER_H13:
return "H13";
case MODULE_CHANNEL:
return "Канал";
default:
return "";
}
}
/* Module drag event handler */
static void module_drag_cb(lv_event_t *e) {
lv_event_code_t code = lv_event_get_code(e);
lv_obj_t *obj = lv_event_get_target(e);
int module_idx = (int)(intptr_t)lv_event_get_user_data(e);
if (code == LV_EVENT_PRESSING) {
lv_indev_t *indev = lv_indev_active();
if (indev == NULL) return;
lv_point_t vect;
lv_indev_get_vect(indev, &vect);
lv_coord_t x = lv_obj_get_x(obj) + vect.x;
lv_coord_t y = lv_obj_get_y(obj) + vect.y;
lv_obj_set_pos(obj, x, y);
}
else if (code == LV_EVENT_RELEASED) {
/* Check if module should be enabled/disabled based on Y position */
lv_coord_t y = lv_obj_get_y(obj);
int threshold = (MODULES_ACTIVE_Y + MODULES_INACTIVE_Y) / 2;
if (y < threshold) {
modules[module_idx].enabled = 1;
} else {
modules[module_idx].enabled = 0;
}
/* Reposition all modules */
update_module_positions();
}
}
/* Update positions of all modules (pipe line is continuous background) */
static void update_module_positions(void) {
int active_x = 50; /* Start after the inlet pipe */
int inactive_x = 10;
for (int i = 0; i < MODULE_COUNT; i++) {
if (modules[i].obj == NULL) continue;
if (modules[i].enabled) {
/* Module is active - position it on the active line */
lv_obj_set_pos(modules[i].obj, active_x, MODULES_ACTIVE_Y);
active_x += MODULE_WIDTH + MODULE_SPACING;
} else {
/* Module is inactive - move to inactive area, pipe line shows through */
active_x += MODULE_WIDTH + MODULE_SPACING; /* Reserve space on pipe line */
lv_obj_set_pos(modules[i].obj, inactive_x, MODULES_INACTIVE_Y);
inactive_x += MODULE_WIDTH + MODULE_SPACING;
}
}
}
/* Create a module widget with image */
static lv_obj_t* create_module_widget(lv_obj_t *parent, int module_idx, int x, int y) {
/* Container for module */
lv_obj_t *cont = lv_obj_create(parent);
lv_obj_set_pos(cont, x, y);
lv_obj_set_size(cont, MODULE_WIDTH, MODULE_HEIGHT);
lv_obj_set_style_bg_color(cont, lv_color_hex(COLOR_BG_MODULE), 0);
lv_obj_set_style_radius(cont, 5, 0);
lv_obj_set_style_border_width(cont, 1, 0);
lv_obj_set_style_border_color(cont, lv_color_hex(COLOR_BORDER), 0);
lv_obj_set_style_pad_all(cont, 2, 0);
lv_obj_clear_flag(cont, LV_OBJ_FLAG_SCROLLABLE);
/* Add drag flag */
lv_obj_add_flag(cont, LV_OBJ_FLAG_CLICKABLE);
lv_obj_add_event_cb(cont, module_drag_cb, LV_EVENT_PRESSING, (void*)(intptr_t)module_idx);
lv_obj_add_event_cb(cont, module_drag_cb, LV_EVENT_RELEASED, (void*)(intptr_t)module_idx);
/* Module name label at top */
lv_obj_t *lbl_name = lv_label_create(cont);
lv_label_set_text(lbl_name, get_module_name(module_idx));
lv_obj_align(lbl_name, LV_ALIGN_TOP_MID, 0, 0);
lv_obj_set_style_text_color(lbl_name, lv_color_hex(COLOR_TEXT_DIM), 0);
lv_obj_set_style_text_font(lbl_name, &montserrat_16_ru_en, 0);
/* Image */
lv_obj_t *img = lv_image_create(cont);
lv_image_set_src(img, get_module_image(module_idx));
lv_image_set_scale(img, MODULE_SCALE);
lv_obj_align(img, LV_ALIGN_CENTER, 0, 5);
/* Value label at bottom */
lv_obj_t *lbl_value = lv_label_create(cont);
lv_obj_align(lbl_value, LV_ALIGN_BOTTOM_MID, 0, -2);
lv_obj_set_style_text_color(lbl_value, lv_color_hex(COLOR_TEXT_ACCENT), 0);
lv_obj_set_style_text_font(lbl_value, &montserrat_16_ru_en, 0);
modules[module_idx].obj = cont;
modules[module_idx].img = img;
modules[module_idx].label_name = lbl_name;
modules[module_idx].label_value = lbl_value;
return cont;
}
/* Apply setpoints to system state based on current mode */
static void apply_mode_setpoints(void) {
setpoint_mode_t *sp = (state.mode == 2) ? &setpoint_work : &setpoint_standby;
if (state.mode == 0) {
/* Остановка - выключаем только активные элементы (вентилятор, нагреватель) */
/* Клапаны остаются на своих позициях */
state.heater_on = 0;
state.fan_on = 0;
} else {
/* Дежурный или Рабочий режим - применяем уставки */
state.valve_inlet = (uint8_t)sp->valve_inlet;
state.valve_recirc = (uint8_t)sp->valve_recirc;
/* Вентилятор по уставке */
state.fan_on = sp->fan;
/* Электронагреватель по уставке */
if (sp->heater == 0) {
state.heater_on = 0; /* ВЫКЛ */
} else if (sp->heater == 1) {
state.heater_on = 1; /* ВКЛ */
}
/* heater == 2 (АВТО) - управляется автоматикой */
/* KWS и PWW */
if (!sp->kws_auto) {
state.kws_valve = (uint8_t)sp->kws_manual;
}
if (!sp->pww_auto) {
state.pww_valve = (uint8_t)sp->pww_manual;
}
}
}
/* Button callbacks */
static void btn_stop_cb(lv_event_t *e) {
(void)e;
state.mode = 0;
apply_mode_setpoints();
gui_update_values();
}
static void btn_standby_cb(lv_event_t *e) {
(void)e;
state.mode = 1;
apply_mode_setpoints();
gui_update_values();
}
static void btn_work_cb(lv_event_t *e) {
(void)e;
state.mode = 2;
apply_mode_setpoints();
gui_update_values();
}
static void btn_menu_cb(lv_event_t *e) {
(void)e;
if (scr_screen1) lv_scr_load(scr_screen1);
}
static void btn_back_to_main_cb(lv_event_t *e) {
(void)e;
if (scr_main) lv_scr_load(scr_main);
}
static void btn_readings_cb(lv_event_t *e) {
(void)e;
create_popup_readings();
}
static void btn_reset_alarm_cb(lv_event_t *e) {
(void)e;
/* Reset first active alarm */
for (int i = 0; i < alarm_count; i++) {
if (alarm_log[i].active) {
alarm_log[i].active = 0;
break;
}
}
/* Refresh journal screen */
if (scr_screen2) {
lv_obj_del(scr_screen2);
scr_screen2 = NULL;
}
create_screen_screen2();
lv_scr_load(scr_screen2);
}
static void btn_clear_log_cb(lv_event_t *e) {
(void)e;
/* Check if any active alarms */
int has_active = 0;
for (int i = 0; i < alarm_count; i++) {
if (alarm_log[i].active) {
has_active = 1;
break;
}
}
if (!has_active) {
alarm_count = 0;
/* Refresh journal screen */
if (scr_screen2) {
lv_obj_del(scr_screen2);
scr_screen2 = NULL;
}
create_screen_screen2();
lv_scr_load(scr_screen2);
}
}
static void close_popup_cb(lv_event_t *e) {
lv_obj_t *popup = lv_event_get_user_data(e);
if (popup) {
lv_obj_del(popup);
}
if (popup == popup_readings) popup_readings = NULL;
if (popup == popup_alarm) popup_alarm = NULL;
if (popup == popup_password) {
popup_password = NULL;
memset(password_input, 0, sizeof(password_input));
}
if (popup == popup_param_editor) {
popup_param_editor = NULL;
memset(param_value_str, 0, sizeof(param_value_str));
param_being_edited = NULL;
}
}
static void btn_log_cb(lv_event_t *e) {
(void)e;
if (scr_screen2) lv_scr_load(scr_screen2);
}
static void btn_settings_cb(lv_event_t *e) {
(void)e;
if (scr_screen3) lv_scr_load(scr_screen3);
}
static void btn_config_cb(lv_event_t *e) {
(void)e;
password_target = 0;
create_popup_password(0);
}
/* Edit parameter callbacks - Work mode */
static void edit_work_temp_cb(lv_event_t *e) {
(void)e;
create_popup_param_editor("Темп.", &setpoint_work.temp, 0);
}
static void edit_work_hum_cb(lv_event_t *e) {
(void)e;
create_popup_param_editor("Влажность (Рабочий)", &setpoint_work.hum, 1);
}
static void edit_work_valve_inlet_cb(lv_event_t *e) {
(void)e;
create_popup_param_editor("Клапан притока (Рабочий)", &setpoint_work.valve_inlet, 1);
}
static void edit_work_valve_recirc_cb(lv_event_t *e) {
(void)e;
create_popup_param_editor("Клапан рециркуляции (Рабочий)", &setpoint_work.valve_recirc, 1);
}
/* Edit parameter callbacks - Standby mode */
static void edit_standby_temp_cb(lv_event_t *e) {
(void)e;
create_popup_param_editor("Температура (Дежурный)", &setpoint_standby.temp, 0);
}
static void edit_standby_hum_cb(lv_event_t *e) {
(void)e;
create_popup_param_editor("Влажность (Дежурный)", &setpoint_standby.hum, 1);
}
static void edit_standby_valve_inlet_cb(lv_event_t *e) {
(void)e;
create_popup_param_editor("Клапан притока (Дежурный)", &setpoint_standby.valve_inlet, 1);
}
static void edit_standby_valve_recirc_cb(lv_event_t *e) {
(void)e;
create_popup_param_editor("Клапан рециркуляции (Дежурный)", &setpoint_standby.valve_recirc, 1);
}
/* Heater mode callbacks */
static void edit_work_heater_cb(lv_event_t *e) {
(void)e;
setpoint_work.heater = (setpoint_work.heater + 1) % 3;
/* Apply immediately if in work mode */
if (state.mode == 2) {
if (setpoint_work.heater == 0) state.heater_on = 0;
else if (setpoint_work.heater == 1) state.heater_on = 1;
gui_update_values();
}
refresh_setpoints_display();
}
static void edit_standby_heater_cb(lv_event_t *e) {
(void)e;
setpoint_standby.heater = (setpoint_standby.heater + 1) % 3;
/* Apply immediately if in standby mode */
if (state.mode == 1) {
if (setpoint_standby.heater == 0) state.heater_on = 0;
else if (setpoint_standby.heater == 1) state.heater_on = 1;
gui_update_values();
}
refresh_setpoints_display();
}
/* Fan toggle callbacks */
static void edit_work_fan_cb(lv_event_t *e) {
(void)e;
setpoint_work.fan = !setpoint_work.fan;
/* Apply immediately if in work mode */
if (state.mode == 2) {
state.fan_on = setpoint_work.fan;
gui_update_values();
}
refresh_setpoints_display();
}
static void edit_standby_fan_cb(lv_event_t *e) {
(void)e;
setpoint_standby.fan = !setpoint_standby.fan;
/* Apply immediately if in standby mode */
if (state.mode == 1) {
state.fan_on = setpoint_standby.fan;
gui_update_values();
}
refresh_setpoints_display();
}
/* KWS mode callbacks */
static void edit_work_kws_cb(lv_event_t *e) {
(void)e;
if (setpoint_work.kws_auto) {
setpoint_work.kws_auto = 0;
setpoint_work.kws_manual = 50.0f;
/* Apply immediately if in work mode */
if (state.mode == 2) {
state.kws_valve = (uint8_t)setpoint_work.kws_manual;
gui_update_values();
}
refresh_setpoints_display();
} else {
create_popup_param_editor("KWS ручной (Рабочий)", &setpoint_work.kws_manual, 1);
}
}
static void edit_standby_kws_cb(lv_event_t *e) {
(void)e;
if (setpoint_standby.kws_auto) {
setpoint_standby.kws_auto = 0;
setpoint_standby.kws_manual = 50.0f;
/* Apply immediately if in standby mode */
if (state.mode == 1) {
state.kws_valve = (uint8_t)setpoint_standby.kws_manual;
gui_update_values();
}
refresh_setpoints_display();
} else {
create_popup_param_editor("KWS ручной (Дежурный)", &setpoint_standby.kws_manual, 1);
}
}
/* PWW mode callbacks */
static void edit_work_pww_cb(lv_event_t *e) {
(void)e;
if (setpoint_work.pww_auto) {
setpoint_work.pww_auto = 0;
setpoint_work.pww_manual = 50.0f;
/* Apply immediately if in work mode */
if (state.mode == 2) {
state.pww_valve = (uint8_t)setpoint_work.pww_manual;
gui_update_values();
}
refresh_setpoints_display();
} else {
create_popup_param_editor("PWW ручной (Рабочий)", &setpoint_work.pww_manual, 1);
}
}
static void edit_standby_pww_cb(lv_event_t *e) {
(void)e;
if (setpoint_standby.pww_auto) {
setpoint_standby.pww_auto = 0;
setpoint_standby.pww_manual = 50.0f;
/* Apply immediately if in standby mode */
if (state.mode == 1) {
state.pww_valve = (uint8_t)setpoint_standby.pww_manual;
gui_update_values();
}
refresh_setpoints_display();
} else {
create_popup_param_editor("PWW ручной (Дежурный)", &setpoint_standby.pww_manual, 1);
}
}
/* Configuration edit callbacks */
static void edit_config_offset_cb(lv_event_t *e) {
float *offset_ptr = (float*)lv_event_get_user_data(e);
create_popup_param_editor("Коррекция", offset_ptr, 0);
}
static void toggle_recirc_valve_cb(lv_event_t *e) {
(void)e;
config.has_recirc_valve = !config.has_recirc_valve;
refresh_setpoints_display();
}
static void toggle_heater_cb(lv_event_t *e) {
(void)e;
config.has_heater = !config.has_heater;
refresh_setpoints_display();
}
static void edit_pid_pww_kp_cb(lv_event_t *e) {
(void)e;
create_popup_param_editor("PWW Kp", &config.pid_pww_kp, 0);
}
static void edit_pid_pww_ti_cb(lv_event_t *e) {
(void)e;
create_popup_param_editor("PWW Ti", &config.pid_pww_ti, 0);
}
static void edit_pid_pww_d_cb(lv_event_t *e) {
(void)e;
create_popup_param_editor("PWW D", &config.pid_pww_d, 0);
}
static void edit_pid_kws_kp_cb(lv_event_t *e) {
(void)e;
create_popup_param_editor("KWS Kp", &config.pid_kws_kp, 0);
}
static void edit_pid_kws_ti_cb(lv_event_t *e) {
(void)e;
create_popup_param_editor("KWS Ti", &config.pid_kws_ti, 0);
}
static void edit_pid_kws_d_cb(lv_event_t *e) {
(void)e;
create_popup_param_editor("KWS D", &config.pid_kws_d, 0);
}
static void toggle_force_mode_cb(lv_event_t *e) {
(void)e;
config.force_mode_enabled = !config.force_mode_enabled;
refresh_setpoints_display();
}
static void toggle_key_swap_cb(lv_event_t *e) {
(void)e;
config.key_swap_enabled = !config.key_swap_enabled;
refresh_setpoints_display();
}
/* Date/Time editor popup */
static lv_obj_t *popup_datetime = NULL;
static lv_obj_t *roller_day = NULL;
static lv_obj_t *roller_month = NULL;
static lv_obj_t *roller_year = NULL;
static lv_obj_t *roller_hour = NULL;
static lv_obj_t *roller_minute = NULL;
static void datetime_ok_cb(lv_event_t *e) {
(void)e;
if (roller_day && roller_month && roller_year && roller_hour && roller_minute) {
state.day = lv_roller_get_selected(roller_day) + 1;
state.month = lv_roller_get_selected(roller_month) + 1;
state.year = 2024 + lv_roller_get_selected(roller_year);
state.hour = lv_roller_get_selected(roller_hour);
state.minute = lv_roller_get_selected(roller_minute);
}
if (popup_datetime) {
lv_obj_del(popup_datetime);
popup_datetime = NULL;
roller_day = roller_month = roller_year = roller_hour = roller_minute = NULL;
}
/* Recreate config screen to show updated datetime */
if (scr_screen4) {
lv_obj_del(scr_screen4);
scr_screen4 = NULL;
}
create_screen_config();
lv_scr_load(scr_screen4);
}
static void datetime_close_cb(lv_event_t *e) {
(void)e;
if (popup_datetime) {
lv_obj_del(popup_datetime);
popup_datetime = NULL;
roller_day = roller_month = roller_year = roller_hour = roller_minute = NULL;
}
}
static void create_popup_datetime(void) {
if (popup_datetime) return;
popup_datetime = lv_obj_create(lv_screen_active());
lv_obj_set_size(popup_datetime, 420, 280);
lv_obj_center(popup_datetime);
lv_obj_set_style_bg_color(popup_datetime, lv_color_hex(COLOR_BG_PANEL), 0);
lv_obj_set_style_radius(popup_datetime, 8, 0);
lv_obj_set_style_border_width(popup_datetime, 1, 0);
lv_obj_set_style_border_color(popup_datetime, lv_color_hex(COLOR_BORDER), 0);
lv_obj_clear_flag(popup_datetime, LV_OBJ_FLAG_SCROLLABLE);
/* Title */
lv_obj_t *lbl = lv_label_create(popup_datetime);
lv_label_set_text(lbl, "Установка даты и времени");
lv_obj_set_pos(lbl, 20, 10);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
/* Close button (X) */
lv_obj_t *btn_close = lv_btn_create(popup_datetime);
lv_obj_set_pos(btn_close, 370, 6);
lv_obj_set_size(btn_close, 32, 32);
lv_obj_set_style_bg_color(btn_close, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn_close, 4, 0);
lbl = lv_label_create(btn_close);
lv_label_set_text(lbl, "X");
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_DANGER), 0);
lv_obj_center(lbl);
lv_obj_add_event_cb(btn_close, datetime_close_cb, LV_EVENT_CLICKED, NULL);
/* Date section label */
lbl = lv_label_create(popup_datetime);
lv_label_set_text(lbl, "Дата:");
lv_obj_set_pos(lbl, 20, 50);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_DIM), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
/* Day roller */
char days[31*4];
days[0] = '\0';
for (int i = 1; i <= 31; i++) {
char tmp[5];
snprintf(tmp, sizeof(tmp), "%02d\n", i);
strcat(days, tmp);
}
days[strlen(days)-1] = '\0';
roller_day = lv_roller_create(popup_datetime);
lv_roller_set_options(roller_day, days, LV_ROLLER_MODE_NORMAL);
lv_roller_set_selected(roller_day, state.day - 1, LV_ANIM_OFF);
lv_obj_set_pos(roller_day, 80, 45);
lv_obj_set_size(roller_day, 60, 80);
lv_obj_set_style_text_font(roller_day, &montserrat_16_ru_en, 0);
lv_obj_set_style_bg_color(roller_day, lv_color_hex(COLOR_BG_BTN), 0);
/* Month roller */
roller_month = lv_roller_create(popup_datetime);
lv_roller_set_options(roller_month, "01\n02\n03\n04\n05\n06\n07\n08\n09\n10\n11\n12", LV_ROLLER_MODE_NORMAL);
lv_roller_set_selected(roller_month, state.month - 1, LV_ANIM_OFF);
lv_obj_set_pos(roller_month, 150, 45);
lv_obj_set_size(roller_month, 60, 80);
lv_obj_set_style_text_font(roller_month, &montserrat_16_ru_en, 0);
lv_obj_set_style_bg_color(roller_month, lv_color_hex(COLOR_BG_BTN), 0);
/* Year roller (2024-2035) */
roller_year = lv_roller_create(popup_datetime);
lv_roller_set_options(roller_year, "2024\n2025\n2026\n2027\n2028\n2029\n2030\n2031\n2032\n2033\n2034\n2035", LV_ROLLER_MODE_NORMAL);
lv_roller_set_selected(roller_year, state.year - 2024, LV_ANIM_OFF);
lv_obj_set_pos(roller_year, 220, 45);
lv_obj_set_size(roller_year, 80, 80);
lv_obj_set_style_text_font(roller_year, &montserrat_16_ru_en, 0);
lv_obj_set_style_bg_color(roller_year, lv_color_hex(COLOR_BG_BTN), 0);
/* Time section label */
lbl = lv_label_create(popup_datetime);
lv_label_set_text(lbl, "Время:");
lv_obj_set_pos(lbl, 20, 140);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_DIM), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
/* Hour roller */
char hours[24*4];
hours[0] = '\0';
for (int i = 0; i < 24; i++) {
char tmp[5];
snprintf(tmp, sizeof(tmp), "%02d\n", i);
strcat(hours, tmp);
}
hours[strlen(hours)-1] = '\0';
roller_hour = lv_roller_create(popup_datetime);
lv_roller_set_options(roller_hour, hours, LV_ROLLER_MODE_NORMAL);
lv_roller_set_selected(roller_hour, state.hour, LV_ANIM_OFF);
lv_obj_set_pos(roller_hour, 80, 135);
lv_obj_set_size(roller_hour, 60, 80);
lv_obj_set_style_text_font(roller_hour, &montserrat_16_ru_en, 0);
lv_obj_set_style_bg_color(roller_hour, lv_color_hex(COLOR_BG_BTN), 0);
lbl = lv_label_create(popup_datetime);
lv_label_set_text(lbl, ":");
lv_obj_set_pos(lbl, 145, 160);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
/* Minute roller */
char minutes[60*4];
minutes[0] = '\0';
for (int i = 0; i < 60; i++) {
char tmp[5];
snprintf(tmp, sizeof(tmp), "%02d\n", i);
strcat(minutes, tmp);
}
minutes[strlen(minutes)-1] = '\0';
roller_minute = lv_roller_create(popup_datetime);
lv_roller_set_options(roller_minute, minutes, LV_ROLLER_MODE_NORMAL);
lv_roller_set_selected(roller_minute, state.minute, LV_ANIM_OFF);
lv_obj_set_pos(roller_minute, 160, 135);
lv_obj_set_size(roller_minute, 60, 80);
lv_obj_set_style_text_font(roller_minute, &montserrat_16_ru_en, 0);
lv_obj_set_style_bg_color(roller_minute, lv_color_hex(COLOR_BG_BTN), 0);
/* OK button */
lv_obj_t *btn_ok = lv_btn_create(popup_datetime);
lv_obj_set_pos(btn_ok, 160, 230);
lv_obj_set_size(btn_ok, 100, 40);
lv_obj_set_style_bg_color(btn_ok, lv_color_hex(COLOR_ACCENT), 0);
lv_obj_set_style_radius(btn_ok, 6, 0);
lbl = lv_label_create(btn_ok);
lv_label_set_text(lbl, "OK");
lv_obj_set_style_text_color(lbl, lv_color_hex(0xFFFFFF), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_center(lbl);
lv_obj_add_event_cb(btn_ok, datetime_ok_cb, LV_EVENT_CLICKED, NULL);
}
static void edit_datetime_cb(lv_event_t *e) {
(void)e;
create_popup_datetime();
}
/* Password keypad callback */
static void password_keypad_cb(lv_event_t *e) {
const char *key = lv_event_get_user_data(e);
int len = strlen(password_input);
if (strcmp(key, "C") == 0) {
memset(password_input, 0, sizeof(password_input));
} else if (strcmp(key, "OK") == 0) {
if (strcmp(password_input, PASSWORD) == 0) {
/* Correct password - close popup */
if (popup_password) {
lv_obj_del(popup_password);
popup_password = NULL;
lbl_password_display = NULL;
}
memset(password_input, 0, sizeof(password_input));
/* Navigate to config screen */
if (password_target == 0) {
if (scr_screen4) lv_scr_load(scr_screen4);
}
} else {
/* Wrong password */
if (lbl_password_display) {
lv_label_set_text(lbl_password_display, "НЕВЕРНЫЙ ПАРОЛЬ!");
lv_obj_set_style_text_color(lbl_password_display, lv_color_hex(COLOR_DANGER), 0);
}
memset(password_input, 0, sizeof(password_input));
}
} else if (len < 8) {
strcat(password_input, key);
}
/* Update display */
if (lbl_password_display) {
char display[20] = "";
for (int i = 0; i < (int)strlen(password_input); i++) {
strcat(display, "*");
}
if (strlen(display) == 0) {
lv_label_set_text(lbl_password_display, "Введите пароль");
lv_obj_set_style_text_color(lbl_password_display, lv_color_hex(COLOR_TEXT_DIM), 0);
} else {
lv_label_set_text(lbl_password_display, display);
lv_obj_set_style_text_color(lbl_password_display, lv_color_hex(COLOR_ACCENT), 0);
}
}
}
/* Create password popup with keypad */
static void create_popup_password(int target) {
if (popup_password) return;
password_target = target;
memset(password_input, 0, sizeof(password_input));
popup_password = lv_obj_create(lv_screen_active());
lv_obj_set_size(popup_password, 300, 420);
lv_obj_center(popup_password);
lv_obj_set_style_bg_color(popup_password, lv_color_hex(COLOR_BG_PANEL), 0);
lv_obj_set_style_radius(popup_password, 8, 0);
lv_obj_set_style_border_width(popup_password, 1, 0);
lv_obj_set_style_border_color(popup_password, lv_color_hex(COLOR_BORDER), 0);
lv_obj_clear_flag(popup_password, LV_OBJ_FLAG_SCROLLABLE);
/* Title */
lv_obj_t *lbl = lv_label_create(popup_password);
lv_label_set_text(lbl, "Введите пароль");
lv_obj_set_pos(lbl, 20, 10);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
/* Close button (X) - справа */
lv_obj_t *btn_close = lv_btn_create(popup_password);
lv_obj_set_pos(btn_close, 230, 6);
lv_obj_set_size(btn_close, 32, 32);
lv_obj_set_style_bg_color(btn_close, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn_close, 4, 0);
lbl = lv_label_create(btn_close);
lv_label_set_text(lbl, "X");
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_DANGER), 0);
lv_obj_center(lbl);
lv_obj_add_event_cb(btn_close, close_popup_cb, LV_EVENT_CLICKED, popup_password);
/* Password display */
lbl_password_display = lv_label_create(popup_password);
lv_label_set_text(lbl_password_display, "_ _ _ _");
lv_obj_set_pos(lbl_password_display, 105, 50);
lv_obj_set_style_text_color(lbl_password_display, lv_color_hex(COLOR_TEXT_DIM), 0);
lv_obj_set_style_text_font(lbl_password_display, &montserrat_16_ru_en, 0);
/* Numeric keypad 3x4 */
const char *keys[12] = {"1", "2", "3", "4", "5", "6", "7", "8", "9", "C", "0", "OK"};
int key_size = 60;
int key_spacing = 8;
int start_x = 30;
int start_y = 90;
for (int i = 0; i < 12; i++) {
int row = i / 3;
int col = i % 3;
lv_obj_t *btn = lv_btn_create(popup_password);
lv_obj_set_pos(btn, start_x + col * (key_size + key_spacing),
start_y + row * (key_size + key_spacing));
lv_obj_set_size(btn, key_size, key_size);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 6, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, keys[i]);
if (strcmp(keys[i], "C") == 0) {
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_WARNING), 0);
} else if (strcmp(keys[i], "OK") == 0) {
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_ACCENT), 0);
} else {
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
}
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_center(lbl);
lv_obj_add_event_cb(btn, password_keypad_cb, LV_EVENT_CLICKED, (void*)keys[i]);
}
}
/* Parameter editor keypad callback */
static void param_keypad_cb(lv_event_t *e) {
const char *key = lv_event_get_user_data(e);
int len = strlen(param_value_str);
if (strcmp(key, "C") == 0) {
memset(param_value_str, 0, sizeof(param_value_str));
} else if (strcmp(key, "<") == 0) {
/* Backspace - remove last character */
if (len > 0) {
param_value_str[len - 1] = '\0';
}
} else if (strcmp(key, ".") == 0) {
if (strchr(param_value_str, '.') == NULL) {
if (len == 0 || (len == 1 && param_value_str[0] == '-')) {
strcat(param_value_str, "0.");
} else {
strcat(param_value_str, ".");
}
}
} else if (strcmp(key, "-") == 0) {
if (len == 0) {
strcat(param_value_str, "-");
} else if (param_value_str[0] == '-') {
/* Remove minus */
memmove(param_value_str, param_value_str + 1, strlen(param_value_str));
} else {
/* Add minus at beginning */
memmove(param_value_str + 1, param_value_str, strlen(param_value_str) + 1);
param_value_str[0] = '-';
}
} else if (strcmp(key, "OK") == 0) {
if (param_being_edited && strlen(param_value_str) > 0) {
float val = (float)atof(param_value_str);
if (param_is_percentage) {
if (val < 0.0f) val = 0.0f;
if (val > 100.0f) val = 100.0f;
}
*param_being_edited = val;
}
if (popup_param_editor) {
lv_obj_del(popup_param_editor);
popup_param_editor = NULL;
lbl_param_value = NULL;
}
memset(param_value_str, 0, sizeof(param_value_str));
param_being_edited = NULL;
refresh_setpoints_display();
return;
} else if (len < 10) {
strcat(param_value_str, key);
}
if (lbl_param_value) {
if (strlen(param_value_str) == 0) {
lv_label_set_text(lbl_param_value, "0");
} else {
lv_label_set_text(lbl_param_value, param_value_str);
}
}
}
/* Create parameter editor popup */
static void create_popup_param_editor(const char *name, float *param_ptr, int is_percentage) {
if (popup_param_editor) return;
param_being_edited = param_ptr;
param_is_percentage = is_percentage;
strncpy(param_name, name, 49);
param_name[49] = '\0';
memset(param_value_str, 0, sizeof(param_value_str));
snprintf(param_value_str, sizeof(param_value_str), "%.1f", *param_ptr);
popup_param_editor = lv_obj_create(lv_screen_active());
lv_obj_set_size(popup_param_editor, 360, 380);
lv_obj_center(popup_param_editor);
lv_obj_set_style_bg_color(popup_param_editor, lv_color_hex(COLOR_BG_PANEL), 0);
lv_obj_set_style_radius(popup_param_editor, 8, 0);
lv_obj_set_style_border_width(popup_param_editor, 1, 0);
lv_obj_set_style_border_color(popup_param_editor, lv_color_hex(COLOR_BORDER), 0);
lv_obj_clear_flag(popup_param_editor, LV_OBJ_FLAG_SCROLLABLE);
/* Title - shorter */
lv_obj_t *lbl = lv_label_create(popup_param_editor);
lv_label_set_text(lbl, param_name);
lv_obj_set_pos(lbl, 10, 10);
lv_obj_set_width(lbl, 260);
lv_label_set_long_mode(lbl, LV_LABEL_LONG_CLIP);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_DIM), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
/* Close button (X) - справа */
lv_obj_t *btn_close = lv_btn_create(popup_param_editor);
lv_obj_set_pos(btn_close, 290, 8);
lv_obj_set_size(btn_close, 32, 32);
lv_obj_set_style_bg_color(btn_close, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn_close, 4, 0);
lbl = lv_label_create(btn_close);
lv_label_set_text(lbl, "X");
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_DANGER), 0);
lv_obj_center(lbl);
lv_obj_add_event_cb(btn_close, close_popup_cb, LV_EVENT_CLICKED, popup_param_editor);
/* Value display with unit */
lbl_param_value = lv_label_create(popup_param_editor);
lv_label_set_text(lbl_param_value, param_value_str);
lv_obj_set_pos(lbl_param_value, 130, 50);
lv_obj_set_style_text_color(lbl_param_value, lv_color_hex(COLOR_ACCENT), 0);
lv_obj_set_style_text_font(lbl_param_value, &montserrat_16_ru_en, 0);
/* Unit label */
lbl = lv_label_create(popup_param_editor);
lv_label_set_text(lbl, is_percentage ? "%" : "°C");
lv_obj_set_pos(lbl, 210, 50);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_DIM), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
/* Numeric keypad 4x4 with decimal, minus, backspace */
const char *keys[16] = {"1", "2", "3", "C", "4", "5", "6", "<", "7", "8", "9", "-", "0", ".", "0", "."};
int key_size = 55;
int key_spacing = 6;
int start_x = 25;
int start_y = 85;
/* First 3 rows (12 buttons) */
for (int i = 0; i < 12; i++) {
int row = i / 4;
int col = i % 4;
lv_obj_t *btn = lv_btn_create(popup_param_editor);
lv_obj_set_pos(btn, start_x + col * (key_size + key_spacing),
start_y + row * (key_size + key_spacing));
lv_obj_set_size(btn, key_size, key_size);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 6, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, keys[i]);
if (strcmp(keys[i], "C") == 0) {
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_DANGER), 0);
} else if (strcmp(keys[i], "<") == 0 || strcmp(keys[i], ".") == 0 || strcmp(keys[i], "-") == 0) {
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_WARNING), 0);
} else {
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
}
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_center(lbl);
lv_obj_add_event_cb(btn, param_keypad_cb, LV_EVENT_CLICKED, (void*)keys[i]);
}
/* Bottom row: 0, dot, OK */
lv_obj_t *btn = lv_btn_create(popup_param_editor);
lv_obj_set_pos(btn, start_x, start_y + 3 * (key_size + key_spacing));
lv_obj_set_size(btn, key_size, key_size);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 6, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, "0");
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_center(lbl);
lv_obj_add_event_cb(btn, param_keypad_cb, LV_EVENT_CLICKED, (void*)"0");
btn = lv_btn_create(popup_param_editor);
lv_obj_set_pos(btn, start_x + (key_size + key_spacing), start_y + 3 * (key_size + key_spacing));
lv_obj_set_size(btn, key_size, key_size);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 6, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, ".");
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_WARNING), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_center(lbl);
lv_obj_add_event_cb(btn, param_keypad_cb, LV_EVENT_CLICKED, (void*)".");
btn = lv_btn_create(popup_param_editor);
lv_obj_set_pos(btn, start_x + 2 * (key_size + key_spacing), start_y + 3 * (key_size + key_spacing));
lv_obj_set_size(btn, key_size * 2 + key_spacing, key_size);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_ACCENT), 0);
lv_obj_set_style_radius(btn, 6, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, "OK");
lv_obj_set_style_text_color(lbl, lv_color_hex(0xFFFFFF), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_center(lbl);
lv_obj_add_event_cb(btn, param_keypad_cb, LV_EVENT_CLICKED, (void*)"OK");
}
/* Create popup window for sensor readings */
static void create_popup_readings(void) {
if (popup_readings) return;
popup_readings = lv_obj_create(lv_screen_active());
lv_obj_set_size(popup_readings, 400, 280);
lv_obj_center(popup_readings);
lv_obj_set_style_bg_color(popup_readings, lv_color_hex(COLOR_BG_PANEL), 0);
lv_obj_set_style_radius(popup_readings, 12, 0);
lv_obj_set_style_border_width(popup_readings, 3, 0);
lv_obj_set_style_border_color(popup_readings, lv_color_hex(COLOR_TEXT_ACCENT), 0);
lv_obj_clear_flag(popup_readings, LV_OBJ_FLAG_SCROLLABLE);
/* Title */
lv_obj_t *lbl = lv_label_create(popup_readings);
lv_label_set_text(lbl, "ПОКАЗАНИЯ ДАТЧИКОВ");
lv_obj_set_pos(lbl, 10, 15);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
/* Close button (X) - справа */
lv_obj_t *btn_close = lv_btn_create(popup_readings);
lv_obj_set_pos(btn_close, 330, 10);
lv_obj_set_size(btn_close, 35, 35);
lv_obj_set_style_bg_color(btn_close, lv_color_hex(COLOR_DANGER), 0);
lv_obj_set_style_radius(btn_close, 6, 0);
lbl = lv_label_create(btn_close);
lv_label_set_text(lbl, "X");
lv_obj_set_style_text_color(lbl, lv_color_hex(0xFFFFFF), 0);
lv_obj_center(lbl);
lv_obj_add_event_cb(btn_close, close_popup_cb, LV_EVENT_CLICKED, popup_readings);
/* Room sensors */
int y = 55;
char buf[64];
lbl = lv_label_create(popup_readings);
lv_label_set_text(lbl, "КОМНАТА:");
lv_obj_set_pos(lbl, 30, y);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_WARNING), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
y += 28;
lbl = lv_label_create(popup_readings);
snprintf(buf, sizeof(buf), "Температура: %.1f°C", state.temp_room / 10.0f);
lv_label_set_text(lbl, buf);
lv_obj_set_pos(lbl, 40, y);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_DANGER), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
y += 26;
lbl = lv_label_create(popup_readings);
snprintf(buf, sizeof(buf), "Влажность: %.1f%%", state.hum_room / 10.0f);
lv_label_set_text(lbl, buf);
lv_obj_set_pos(lbl, 40, y);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_ACCENT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
/* Channel sensors */
y += 35;
lbl = lv_label_create(popup_readings);
lv_label_set_text(lbl, "КАНАЛ ПРИТОКА:");
lv_obj_set_pos(lbl, 30, y);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_WARNING), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
y += 28;
lbl = lv_label_create(popup_readings);
snprintf(buf, sizeof(buf), "Температура: %.1f°C", state.temp_channel / 10.0f);
lv_label_set_text(lbl, buf);
lv_obj_set_pos(lbl, 40, y);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_DANGER), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
y += 26;
lbl = lv_label_create(popup_readings);
snprintf(buf, sizeof(buf), "Влажность: %.1f%%", state.hum_channel / 10.0f);
lv_label_set_text(lbl, buf);
lv_obj_set_pos(lbl, 40, y);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_ACCENT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
}
/* Add alarm to log */
static void add_alarm_to_log(const char *message, int active) {
if (alarm_count < MAX_ALARMS) {
strncpy(alarm_log[alarm_count].message, message, 79);
alarm_log[alarm_count].message[79] = '\0';
snprintf(alarm_log[alarm_count].timestamp, 20, "%02d.%02d.%04d %02d:%02d",
state.day, state.month, state.year, state.hour, state.minute);
alarm_log[alarm_count].active = active;
alarm_count++;
}
}
/* Create alarm popup */
static void create_popup_alarm(const char *message) {
if (popup_alarm) {
lv_obj_del(popup_alarm);
}
add_alarm_to_log(message, 1);
popup_alarm = lv_obj_create(lv_screen_active());
lv_obj_set_size(popup_alarm, 400, 240);
lv_obj_center(popup_alarm);
lv_obj_set_style_bg_color(popup_alarm, lv_color_hex(COLOR_DANGER), 0);
lv_obj_set_style_radius(popup_alarm, 12, 0);
lv_obj_set_style_border_width(popup_alarm, 4, 0);
lv_obj_set_style_border_color(popup_alarm, lv_color_hex(0xFF0000), 0);
lv_obj_clear_flag(popup_alarm, LV_OBJ_FLAG_SCROLLABLE);
/* Title */
lv_obj_t *lbl = lv_label_create(popup_alarm);
lv_label_set_text(lbl, "АВАРИЯ");
lv_obj_set_pos(lbl, 20, 15);
lv_obj_set_style_text_color(lbl, lv_color_hex(0xFFFFFF), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
/* Close button (X) - справа в углу */
lv_obj_t *btn_close = lv_btn_create(popup_alarm);
lv_obj_set_pos(btn_close, 330, 10);
lv_obj_set_size(btn_close, 35, 35);
lv_obj_set_style_bg_color(btn_close, lv_color_hex(0x800000), 0);
lv_obj_set_style_radius(btn_close, 6, 0);
lbl = lv_label_create(btn_close);
lv_label_set_text(lbl, "X");
lv_obj_set_style_text_color(lbl, lv_color_hex(0xFFFFFF), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_center(lbl);
lv_obj_add_event_cb(btn_close, close_popup_cb, LV_EVENT_CLICKED, popup_alarm);
/* Message */
lbl = lv_label_create(popup_alarm);
lv_label_set_text(lbl, message);
lv_obj_set_pos(lbl, 30, 70);
lv_obj_set_width(lbl, 340);
lv_obj_set_style_text_color(lbl, lv_color_hex(0xFFFFFF), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_label_set_long_mode(lbl, LV_LABEL_LONG_WRAP);
lv_obj_set_style_text_align(lbl, LV_TEXT_ALIGN_CENTER, 0);
}
static void create_screen_main(void) {
lv_obj_t *scr = lv_obj_create(NULL);
scr_main = scr;
lv_obj_set_style_bg_color(scr, lv_color_hex(COLOR_BG_DARK), 0);
lv_obj_set_style_bg_opa(scr, LV_OPA_COVER, 0);
lv_obj_clear_flag(scr, LV_OBJ_FLAG_SCROLLABLE);
/* === STATUS PANEL === (нижняя часть экрана) */
lv_obj_t *panel_status = lv_obj_create(scr);
lv_obj_set_pos(panel_status, 10, 275);
lv_obj_set_size(panel_status, 240, 195);
lv_obj_set_style_bg_color(panel_status, lv_color_hex(COLOR_BG_PANEL), 0);
lv_obj_set_style_radius(panel_status, 6, 0);
lv_obj_set_style_border_width(panel_status, 0, 0);
lv_obj_clear_flag(panel_status, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_t *lbl = lv_label_create(panel_status);
lv_label_set_text(lbl, "Состояние");
lv_obj_set_pos(lbl, 10, 5);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_DIM), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lbl_room_num = lv_label_create(panel_status);
lv_label_set_text(lbl_room_num, "Комната: 101");
lv_obj_set_pos(lbl_room_num, 10, 30);
lv_obj_set_style_text_color(lbl_room_num, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl_room_num, &montserrat_16_ru_en, 0);
lbl_master_slave = lv_label_create(panel_status);
lv_label_set_text(lbl_master_slave, "Мастер");
lv_obj_set_pos(lbl_master_slave, 10, 55);
lv_obj_set_style_text_color(lbl_master_slave, lv_color_hex(COLOR_ACCENT), 0);
lv_obj_set_style_text_font(lbl_master_slave, &montserrat_16_ru_en, 0);
lbl_season = lv_label_create(panel_status);
lv_label_set_text(lbl_season, "Сезон: Зима");
lv_obj_set_pos(lbl_season, 10, 80);
lv_obj_set_style_text_color(lbl_season, lv_color_hex(COLOR_TEXT_ACCENT), 0);
lv_obj_set_style_text_font(lbl_season, &montserrat_16_ru_en, 0);
lbl_mode = lv_label_create(panel_status);
lv_label_set_text(lbl_mode, "Режим: Дежурный");
lv_obj_set_pos(lbl_mode, 10, 105);
lv_obj_set_style_text_color(lbl_mode, lv_color_hex(COLOR_WARNING), 0);
lv_obj_set_style_text_font(lbl_mode, &montserrat_16_ru_en, 0);
lbl_datetime = lv_label_create(panel_status);
{
char dt_buf[32];
snprintf(dt_buf, sizeof(dt_buf), "%02d.%02d.%04d %02d:%02d",
state.day, state.month, state.year, state.hour, state.minute);
lv_label_set_text(lbl_datetime, dt_buf);
}
lv_obj_set_pos(lbl_datetime, 10, 135);
lv_obj_set_style_text_color(lbl_datetime, lv_color_hex(COLOR_TEXT_DIM), 0);
lv_obj_set_style_text_font(lbl_datetime, &montserrat_16_ru_en, 0);
/* === CLIMATE PANEL === */
lv_obj_t *panel_climate = lv_obj_create(scr);
lv_obj_set_pos(panel_climate, 260, 275);
lv_obj_set_size(panel_climate, 230, 195);
lv_obj_set_style_bg_color(panel_climate, lv_color_hex(COLOR_BG_PANEL), 0);
lv_obj_set_style_radius(panel_climate, 6, 0);
lv_obj_set_style_border_width(panel_climate, 0, 0);
lv_obj_clear_flag(panel_climate, LV_OBJ_FLAG_SCROLLABLE);
lbl = lv_label_create(panel_climate);
lv_label_set_text(lbl, "Комната");
lv_obj_set_pos(lbl, 10, 5);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_DIM), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lbl = lv_label_create(panel_climate);
lv_label_set_text(lbl, "Температура:");
lv_obj_set_pos(lbl, 10, 35);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_DIM), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lbl_temp_room = lv_label_create(panel_climate);
lv_label_set_text(lbl_temp_room, "22.0°C");
lv_obj_set_pos(lbl_temp_room, 130, 35);
lv_obj_set_style_text_color(lbl_temp_room, lv_color_hex(COLOR_DANGER), 0);
lv_obj_set_style_text_font(lbl_temp_room, &montserrat_16_ru_en, 0);
lbl = lv_label_create(panel_climate);
lv_label_set_text(lbl, "Влажность:");
lv_obj_set_pos(lbl, 10, 65);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_DIM), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lbl_hum_room = lv_label_create(panel_climate);
lv_label_set_text(lbl_hum_room, "45%");
lv_obj_set_pos(lbl_hum_room, 130, 65);
lv_obj_set_style_text_color(lbl_hum_room, lv_color_hex(COLOR_TEXT_ACCENT), 0);
lv_obj_set_style_text_font(lbl_hum_room, &montserrat_16_ru_en, 0);
/* Channel info */
lbl = lv_label_create(panel_climate);
lv_label_set_text(lbl, "Канал:");
lv_obj_set_pos(lbl, 10, 100);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_DIM), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lbl_temp_channel = lv_label_create(panel_climate);
lv_label_set_text(lbl_temp_channel, "20.0°C");
lv_obj_set_pos(lbl_temp_channel, 70, 100);
lv_obj_set_style_text_color(lbl_temp_channel, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl_temp_channel, &montserrat_16_ru_en, 0);
lbl_hum_channel = lv_label_create(panel_climate);
lv_label_set_text(lbl_hum_channel, "50%");
lv_obj_set_pos(lbl_hum_channel, 150, 100);
lv_obj_set_style_text_color(lbl_hum_channel, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl_hum_channel, &montserrat_16_ru_en, 0);
/* === CONTROL BUTTONS === (правая часть нижней половины) */
int btn_x = 500;
int btn_y = 275;
/* Stop button */
lv_obj_t *btn = lv_btn_create(scr);
lv_obj_set_pos(btn, btn_x, btn_y);
lv_obj_set_size(btn, 140, 100);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 6, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, "ОСТАНОВ");
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_DANGER), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_center(lbl);
lv_obj_add_event_cb(btn, btn_stop_cb, LV_EVENT_CLICKED, NULL);
/* Standby button */
btn = lv_btn_create(scr);
lv_obj_set_pos(btn, btn_x + 150, btn_y);
lv_obj_set_size(btn, 140, 48);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 6, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, "Дежурный");
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_WARNING), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_center(lbl);
lv_obj_add_event_cb(btn, btn_standby_cb, LV_EVENT_CLICKED, NULL);
/* Work button */
btn = lv_btn_create(scr);
lv_obj_set_pos(btn, btn_x + 150, btn_y + 52);
lv_obj_set_size(btn, 140, 48);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 6, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, "Рабочий");
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_ACCENT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_center(lbl);
lv_obj_add_event_cb(btn, btn_work_cb, LV_EVENT_CLICKED, NULL);
/* Menu button */
btn = lv_btn_create(scr);
lv_obj_set_pos(btn, btn_x, btn_y + 108);
lv_obj_set_size(btn, 140, 48);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 6, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, "Меню");
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_center(lbl);
lv_obj_add_event_cb(btn, btn_menu_cb, LV_EVENT_CLICKED, NULL);
/* Readings button */
btn = lv_btn_create(scr);
lv_obj_set_pos(btn, btn_x + 150, btn_y + 108);
lv_obj_set_size(btn, 140, 48);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 6, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, "Показания");
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_ACCENT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_center(lbl);
lv_obj_add_event_cb(btn, btn_readings_cb, LV_EVENT_CLICKED, NULL);
/* === EQUIPMENT SCHEMA AREA === (верхняя половина экрана) */
schema_panel = lv_obj_create(scr);
lv_obj_set_pos(schema_panel, 10, 10);
lv_obj_set_size(schema_panel, 780, 255);
lv_obj_set_style_bg_color(schema_panel, lv_color_hex(COLOR_BG_PANEL), 0);
lv_obj_set_style_radius(schema_panel, 6, 0);
lv_obj_set_style_border_width(schema_panel, 0, 0);
lv_obj_clear_flag(schema_panel, LV_OBJ_FLAG_SCROLLABLE);
/* Hint labels */
lv_obj_t *hint1 = lv_label_create(schema_panel);
lv_label_set_text(hint1, "Активные модули (перетащите вниз для отключения):");
lv_obj_set_pos(hint1, 10, 2);
lv_obj_set_style_text_color(hint1, lv_color_hex(COLOR_TEXT_DIM), 0);
lv_obj_set_style_text_font(hint1, &montserrat_16_ru_en, 0);
lv_obj_t *hint2 = lv_label_create(schema_panel);
lv_label_set_text(hint2, "Неактивные (перетащите вверх для включения):");
lv_obj_set_pos(hint2, 10, 155);
lv_obj_set_style_text_color(hint2, lv_color_hex(COLOR_TEXT_DIM), 0);
lv_obj_set_style_text_font(hint2, &montserrat_16_ru_en, 0);
/* Create continuous pipe background line FIRST (so modules appear on top) */
/* Calculate total width needed for all module positions */
int pipe_x = 5;
int pipe_y = MODULES_ACTIVE_Y + 25;
int pipe_segment_width = 35; /* Scaled pipe image width approximately */
int total_modules_width = 50 + MODULE_COUNT * (MODULE_WIDTH + MODULE_SPACING) + 50;
/* Create pipe segments to form continuous line */
for (int px = pipe_x; px < total_modules_width; px += pipe_segment_width) {
lv_obj_t *pipe_seg = lv_image_create(schema_panel);
lv_image_set_src(pipe_seg, &img_pipe);
lv_image_set_scale(pipe_seg, MODULE_SCALE);
lv_obj_set_pos(pipe_seg, px, pipe_y);
}
/* Create all modules (they will appear on top of pipe line) */
int active_x = 50; /* Start after the inlet pipe */
int inactive_x = 10;
for (int i = 0; i < MODULE_COUNT; i++) {
int x_pos, y_pos;
if (modules[i].enabled) {
x_pos = active_x;
y_pos = MODULES_ACTIVE_Y;
active_x += MODULE_WIDTH + MODULE_SPACING;
} else {
x_pos = inactive_x;
y_pos = MODULES_INACTIVE_Y;
inactive_x += MODULE_WIDTH + MODULE_SPACING;
}
create_module_widget(schema_panel, i, x_pos, y_pos);
/* No more individual pipe placeholders needed - continuous pipe line handles it */
pipe_placeholders[i] = NULL;
}
/* Update all module displays with current values */
for (int i = 0; i < MODULE_COUNT; i++) {
update_module_display(i);
}
}
static void create_screen_screen1(void) {
lv_obj_t *scr = lv_obj_create(NULL);
scr_screen1 = scr;
lv_obj_set_style_bg_color(scr, lv_color_hex(COLOR_BG_DARK), 0);
lv_obj_set_style_bg_opa(scr, LV_OPA_COVER, 0);
lv_obj_clear_flag(scr, LV_OBJ_FLAG_SCROLLABLE);
/* Title */
lv_obj_t *lbl = lv_label_create(scr);
lv_label_set_text(lbl, "МЕНЮ");
lv_obj_set_pos(lbl, 370, 30);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
int btn_width = 300;
int btn_height = 55;
int x = 250;
int y = 90;
int spacing = 15;
/* Button: Back to Main Screen */
lv_obj_t *btn = lv_btn_create(scr);
lv_obj_set_pos(btn, x, y);
lv_obj_set_size(btn, btn_width, btn_height);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 6, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, "Главный экран");
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_ACCENT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_center(lbl);
lv_obj_add_event_cb(btn, btn_back_to_main_cb, LV_EVENT_CLICKED, NULL);
y += btn_height + spacing;
/* Button: Log (Журнал) */
btn = lv_btn_create(scr);
lv_obj_set_pos(btn, x, y);
lv_obj_set_size(btn, btn_width, btn_height);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 6, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, "Журнал аварий");
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_WARNING), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_center(lbl);
lv_obj_add_event_cb(btn, btn_log_cb, LV_EVENT_CLICKED, NULL);
y += btn_height + spacing;
/* Button: Settings Parameters (Уставка параметров) */
btn = lv_btn_create(scr);
lv_obj_set_pos(btn, x, y);
lv_obj_set_size(btn, btn_width, btn_height);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 6, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, "Уставка параметров");
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_center(lbl);
lv_obj_add_event_cb(btn, btn_settings_cb, LV_EVENT_CLICKED, NULL);
y += btn_height + spacing;
/* Button: Configuration (Настройки - требует пароль) */
btn = lv_btn_create(scr);
lv_obj_set_pos(btn, x, y);
lv_obj_set_size(btn, btn_width, btn_height);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 6, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, "Настройки");
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_DANGER), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_center(lbl);
lv_obj_add_event_cb(btn, btn_config_cb, LV_EVENT_CLICKED, NULL);
}
static void create_screen_screen2(void) {
lv_obj_t *scr = lv_obj_create(NULL);
scr_screen2 = scr;
lv_obj_set_style_bg_color(scr, lv_color_hex(0x2C3E50), 0);
lv_obj_set_style_bg_opa(scr, LV_OPA_COVER, 0);
lv_obj_clear_flag(scr, LV_OBJ_FLAG_SCROLLABLE);
/* Title */
lv_obj_t *lbl = lv_label_create(scr);
lv_label_set_text(lbl, "ЖУРНАЛ АВАРИЙ");
lv_obj_set_pos(lbl, 300, 20);
lv_obj_set_style_text_color(lbl, lv_color_hex(0xECF0F1), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
/* Date/Time display */
char datetime_buf[32];
snprintf(datetime_buf, sizeof(datetime_buf), "%02d.%02d.%04d %02d:%02d",
state.day, state.month, state.year, state.hour, state.minute);
lbl = lv_label_create(scr);
lv_label_set_text(lbl, datetime_buf);
lv_obj_set_pos(lbl, 620, 20);
lv_obj_set_style_text_color(lbl, lv_color_hex(0xBDC3C7), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
/* Log panel */
lv_obj_t *log_panel = lv_obj_create(scr);
lv_obj_set_pos(log_panel, 20, 60);
lv_obj_set_size(log_panel, 760, 300);
lv_obj_set_style_bg_color(log_panel, lv_color_hex(0x34495E), 0);
lv_obj_set_style_radius(log_panel, 8, 0);
lv_obj_set_style_border_width(log_panel, 2, 0);
lv_obj_set_style_border_color(log_panel, lv_color_hex(0x7F8C8D), 0);
/* Display log entries */
int y = 10;
if (alarm_count == 0) {
lbl = lv_label_create(log_panel);
lv_label_set_text(lbl, "Аварий нет");
lv_obj_set_pos(lbl, 320, 130);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_ACCENT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
} else {
for (int i = 0; i < alarm_count && i < 8; i++) {
char buf[150];
snprintf(buf, sizeof(buf), "%s - %s",
alarm_log[i].message, alarm_log[i].timestamp);
lbl = lv_label_create(log_panel);
lv_label_set_text(lbl, buf);
lv_obj_set_pos(lbl, 10, y);
lv_obj_set_width(lbl, 730);
if (alarm_log[i].active) {
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_DANGER), 0);
} else {
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_DIM), 0);
}
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
y += 35;
}
}
/* Buttons */
int btn_y = 380;
/* Clear Log button */
lv_obj_t *btn = lv_btn_create(scr);
lv_obj_set_pos(btn, 50, btn_y);
lv_obj_set_size(btn, 160, 40);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 6, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, "Очистить журнал");
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_DANGER), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_center(lbl);
lv_obj_add_event_cb(btn, btn_clear_log_cb, LV_EVENT_CLICKED, NULL);
/* Reset Alarm button */
btn = lv_btn_create(scr);
lv_obj_set_pos(btn, 230, btn_y);
lv_obj_set_size(btn, 160, 40);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 6, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, "Сбросить аварию");
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_WARNING), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_center(lbl);
lv_obj_add_event_cb(btn, btn_reset_alarm_cb, LV_EVENT_CLICKED, NULL);
/* Back to Menu button */
btn = lv_btn_create(scr);
lv_obj_set_pos(btn, 600, btn_y);
lv_obj_set_size(btn, 120, 40);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 6, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, "Меню");
lv_obj_set_style_text_color(lbl, lv_color_hex(0xFFFFFF), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_center(lbl);
lv_obj_add_event_cb(btn, btn_menu_cb, LV_EVENT_CLICKED, NULL);
}
static void create_screen_screen3(void) {
lv_obj_t *scr = lv_obj_create(NULL);
scr_screen3 = scr;
lv_obj_set_style_bg_color(scr, lv_color_hex(COLOR_BG_DARK), 0);
lv_obj_set_style_bg_opa(scr, LV_OPA_COVER, 0);
lv_obj_clear_flag(scr, LV_OBJ_FLAG_SCROLLABLE);
/* Title */
lv_obj_t *lbl = lv_label_create(scr);
lv_label_set_text(lbl, "УСТАВКА ПАРАМЕТРОВ");
lv_obj_set_pos(lbl, 290, 10);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
char buf[64];
int row_h = 34;
/* Left panel - Work mode - SCROLLABLE */
lv_obj_t *panel_work = lv_obj_create(scr);
lv_obj_set_pos(panel_work, 15, 40);
lv_obj_set_size(panel_work, 380, 310);
lv_obj_set_style_bg_color(panel_work, lv_color_hex(COLOR_BG_PANEL), 0);
lv_obj_set_style_radius(panel_work, 6, 0);
lv_obj_set_style_border_width(panel_work, 0, 0);
lv_obj_set_scroll_dir(panel_work, LV_DIR_VER);
lv_obj_set_scrollbar_mode(panel_work, LV_SCROLLBAR_MODE_AUTO);
lbl = lv_label_create(panel_work);
lv_label_set_text(lbl, "РАБОЧИЙ");
lv_obj_set_pos(lbl, 10, 8);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_ACCENT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
int y = 38;
/* Temperature */
lv_obj_t *btn = lv_btn_create(panel_work);
lv_obj_set_pos(btn, 5, y);
lv_obj_set_size(btn, 350, row_h);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
snprintf(buf, sizeof(buf), "Температура: %.1f°C", setpoint_work.temp);
lbl_work_temp = lv_label_create(btn);
lv_label_set_text(lbl_work_temp, buf);
lv_obj_center(lbl_work_temp);
lv_obj_set_style_text_color(lbl_work_temp, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl_work_temp, &montserrat_16_ru_en, 0);
lv_obj_add_event_cb(btn, edit_work_temp_cb, LV_EVENT_CLICKED, NULL);
y += row_h + 6;
/* Humidity */
btn = lv_btn_create(panel_work);
lv_obj_set_pos(btn, 5, y);
lv_obj_set_size(btn, 350, row_h);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
snprintf(buf, sizeof(buf), "Влажность: %.1f%%", setpoint_work.hum);
lbl_work_hum = lv_label_create(btn);
lv_label_set_text(lbl_work_hum, buf);
lv_obj_center(lbl_work_hum);
lv_obj_set_style_text_color(lbl_work_hum, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl_work_hum, &montserrat_16_ru_en, 0);
lv_obj_add_event_cb(btn, edit_work_hum_cb, LV_EVENT_CLICKED, NULL);
y += row_h + 6;
/* Valve inlet */
btn = lv_btn_create(panel_work);
lv_obj_set_pos(btn, 5, y);
lv_obj_set_size(btn, 350, row_h);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
snprintf(buf, sizeof(buf), "Клапан притока: %.1f%%", setpoint_work.valve_inlet);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, buf);
lv_obj_center(lbl);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_add_event_cb(btn, edit_work_valve_inlet_cb, LV_EVENT_CLICKED, NULL);
y += row_h + 6;
/* Valve recirc */
btn = lv_btn_create(panel_work);
lv_obj_set_pos(btn, 5, y);
lv_obj_set_size(btn, 350, row_h);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
snprintf(buf, sizeof(buf), "Клапан рецирк: %.1f%%", setpoint_work.valve_recirc);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, buf);
lv_obj_center(lbl);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_add_event_cb(btn, edit_work_valve_recirc_cb, LV_EVENT_CLICKED, NULL);
y += row_h + 6;
/* Heater mode */
btn = lv_btn_create(panel_work);
lv_obj_set_pos(btn, 5, y);
lv_obj_set_size(btn, 350, row_h);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
const char *heater_modes[] = {"ВЫКЛ", "ВКЛ", "АВТО"};
snprintf(buf, sizeof(buf), "Электронагрев: %s", heater_modes[setpoint_work.heater]);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, buf);
lv_obj_center(lbl);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_add_event_cb(btn, edit_work_heater_cb, LV_EVENT_CLICKED, NULL);
y += row_h + 6;
/* Fan toggle */
btn = lv_btn_create(panel_work);
lv_obj_set_pos(btn, 5, y);
lv_obj_set_size(btn, 350, row_h);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
snprintf(buf, sizeof(buf), "Вентилятор: %s", setpoint_work.fan ? "ВКЛ" : "ВЫКЛ");
lbl = lv_label_create(btn);
lv_label_set_text(lbl, buf);
lv_obj_center(lbl);
lv_obj_set_style_text_color(lbl, setpoint_work.fan ? lv_color_hex(COLOR_ACCENT) : lv_color_hex(COLOR_TEXT_DIM), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_add_event_cb(btn, edit_work_fan_cb, LV_EVENT_CLICKED, NULL);
y += row_h + 6;
/* KWS mode */
btn = lv_btn_create(panel_work);
lv_obj_set_pos(btn, 5, y);
lv_obj_set_size(btn, 350, row_h);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
if (setpoint_work.kws_auto) {
snprintf(buf, sizeof(buf), "KWS: АВТО");
} else {
snprintf(buf, sizeof(buf), "KWS: %.1f%%", setpoint_work.kws_manual);
}
lbl = lv_label_create(btn);
lv_label_set_text(lbl, buf);
lv_obj_center(lbl);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_add_event_cb(btn, edit_work_kws_cb, LV_EVENT_CLICKED, NULL);
y += row_h + 6;
/* PWW mode */
btn = lv_btn_create(panel_work);
lv_obj_set_pos(btn, 5, y);
lv_obj_set_size(btn, 350, row_h);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
if (setpoint_work.pww_auto) {
snprintf(buf, sizeof(buf), "PWW: АВТО");
} else {
snprintf(buf, sizeof(buf), "PWW: %.1f%%", setpoint_work.pww_manual);
}
lbl = lv_label_create(btn);
lv_label_set_text(lbl, buf);
lv_obj_center(lbl);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_add_event_cb(btn, edit_work_pww_cb, LV_EVENT_CLICKED, NULL);
/* Right panel - Standby mode - SCROLLABLE */
lv_obj_t *panel_standby = lv_obj_create(scr);
lv_obj_set_pos(panel_standby, 405, 40);
lv_obj_set_size(panel_standby, 380, 310);
lv_obj_set_style_bg_color(panel_standby, lv_color_hex(COLOR_BG_PANEL), 0);
lv_obj_set_style_radius(panel_standby, 6, 0);
lv_obj_set_style_border_width(panel_standby, 0, 0);
lv_obj_set_scroll_dir(panel_standby, LV_DIR_VER);
lv_obj_set_scrollbar_mode(panel_standby, LV_SCROLLBAR_MODE_AUTO);
lbl = lv_label_create(panel_standby);
lv_label_set_text(lbl, "ДЕЖУРНЫЙ");
lv_obj_set_pos(lbl, 10, 8);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_WARNING), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
y = 38;
/* Standby Temperature */
btn = lv_btn_create(panel_standby);
lv_obj_set_pos(btn, 5, y);
lv_obj_set_size(btn, 350, row_h);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
snprintf(buf, sizeof(buf), "Температура: %.1f°C", setpoint_standby.temp);
lbl_standby_temp = lv_label_create(btn);
lv_label_set_text(lbl_standby_temp, buf);
lv_obj_center(lbl_standby_temp);
lv_obj_set_style_text_color(lbl_standby_temp, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl_standby_temp, &montserrat_16_ru_en, 0);
lv_obj_add_event_cb(btn, edit_standby_temp_cb, LV_EVENT_CLICKED, NULL);
y += row_h + 6;
/* Standby Humidity */
btn = lv_btn_create(panel_standby);
lv_obj_set_pos(btn, 5, y);
lv_obj_set_size(btn, 350, row_h);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
snprintf(buf, sizeof(buf), "Влажность: %.1f%%", setpoint_standby.hum);
lbl_standby_hum = lv_label_create(btn);
lv_label_set_text(lbl_standby_hum, buf);
lv_obj_center(lbl_standby_hum);
lv_obj_set_style_text_color(lbl_standby_hum, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl_standby_hum, &montserrat_16_ru_en, 0);
lv_obj_add_event_cb(btn, edit_standby_hum_cb, LV_EVENT_CLICKED, NULL);
y += row_h + 6;
/* Standby Valve inlet */
btn = lv_btn_create(panel_standby);
lv_obj_set_pos(btn, 5, y);
lv_obj_set_size(btn, 350, row_h);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
snprintf(buf, sizeof(buf), "Клапан притока: %.1f%%", setpoint_standby.valve_inlet);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, buf);
lv_obj_center(lbl);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_add_event_cb(btn, edit_standby_valve_inlet_cb, LV_EVENT_CLICKED, NULL);
y += row_h + 6;
/* Standby Valve recirc */
btn = lv_btn_create(panel_standby);
lv_obj_set_pos(btn, 5, y);
lv_obj_set_size(btn, 350, row_h);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
snprintf(buf, sizeof(buf), "Клапан рецирк: %.1f%%", setpoint_standby.valve_recirc);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, buf);
lv_obj_center(lbl);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_add_event_cb(btn, edit_standby_valve_recirc_cb, LV_EVENT_CLICKED, NULL);
y += row_h + 6;
/* Heater mode */
btn = lv_btn_create(panel_standby);
lv_obj_set_pos(btn, 5, y);
lv_obj_set_size(btn, 350, row_h);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
snprintf(buf, sizeof(buf), "Электронагрев: %s", heater_modes[setpoint_standby.heater]);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, buf);
lv_obj_center(lbl);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_add_event_cb(btn, edit_standby_heater_cb, LV_EVENT_CLICKED, NULL);
y += row_h + 6;
/* Fan toggle */
btn = lv_btn_create(panel_standby);
lv_obj_set_pos(btn, 5, y);
lv_obj_set_size(btn, 350, row_h);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
snprintf(buf, sizeof(buf), "Вентилятор: %s", setpoint_standby.fan ? "ВКЛ" : "ВЫКЛ");
lbl = lv_label_create(btn);
lv_label_set_text(lbl, buf);
lv_obj_center(lbl);
lv_obj_set_style_text_color(lbl, setpoint_standby.fan ? lv_color_hex(COLOR_ACCENT) : lv_color_hex(COLOR_TEXT_DIM), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_add_event_cb(btn, edit_standby_fan_cb, LV_EVENT_CLICKED, NULL);
y += row_h + 6;
/* KWS mode */
btn = lv_btn_create(panel_standby);
lv_obj_set_pos(btn, 5, y);
lv_obj_set_size(btn, 350, row_h);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
if (setpoint_standby.kws_auto) {
snprintf(buf, sizeof(buf), "KWS: АВТО");
} else {
snprintf(buf, sizeof(buf), "KWS: %.1f%%", setpoint_standby.kws_manual);
}
lbl = lv_label_create(btn);
lv_label_set_text(lbl, buf);
lv_obj_center(lbl);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_add_event_cb(btn, edit_standby_kws_cb, LV_EVENT_CLICKED, NULL);
y += row_h + 6;
/* PWW mode */
btn = lv_btn_create(panel_standby);
lv_obj_set_pos(btn, 5, y);
lv_obj_set_size(btn, 350, row_h);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
if (setpoint_standby.pww_auto) {
snprintf(buf, sizeof(buf), "PWW: АВТО");
} else {
snprintf(buf, sizeof(buf), "PWW: %.1f%%", setpoint_standby.pww_manual);
}
lbl = lv_label_create(btn);
lv_label_set_text(lbl, buf);
lv_obj_center(lbl);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_add_event_cb(btn, edit_standby_pww_cb, LV_EVENT_CLICKED, NULL);
/* Common parameters section */
lv_obj_t *panel_common = lv_obj_create(scr);
lv_obj_set_pos(panel_common, 15, 355);
lv_obj_set_size(panel_common, 770, 70);
lv_obj_set_style_bg_color(panel_common, lv_color_hex(COLOR_BG_PANEL), 0);
lv_obj_set_style_radius(panel_common, 6, 0);
lv_obj_set_style_border_width(panel_common, 0, 0);
lv_obj_clear_flag(panel_common, LV_OBJ_FLAG_SCROLLABLE);
lbl = lv_label_create(panel_common);
lv_label_set_text(lbl, "Общие параметры:");
lv_obj_set_pos(lbl, 10, 5);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_DIM), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
snprintf(buf, sizeof(buf), "Фильтр G4: %s", filter_g4_date);
lbl = lv_label_create(panel_common);
lv_label_set_text(lbl, buf);
lv_obj_set_pos(lbl, 10, 28);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
snprintf(buf, sizeof(buf), "Фильтр H13: %s", filter_h13_date);
lbl = lv_label_create(panel_common);
lv_label_set_text(lbl, buf);
lv_obj_set_pos(lbl, 250, 28);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
snprintf(buf, sizeof(buf), "%02d:%02d %02d.%02d.%d", state.hour, state.minute, state.day, state.month, state.year);
lbl = lv_label_create(panel_common);
lv_label_set_text(lbl, buf);
lv_obj_set_pos(lbl, 510, 28);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_ACCENT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lbl = lv_label_create(panel_common);
lv_label_set_text(lbl, "(для ввода даты замены)");
lv_obj_set_pos(lbl, 10, 48);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_DIM), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
/* Buttons row */
int btn_y = 435;
btn = lv_btn_create(scr);
lv_obj_set_pos(btn, 200, btn_y);
lv_obj_set_size(btn, 110, 38);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, "Сохранить");
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_ACCENT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_center(lbl);
btn = lv_btn_create(scr);
lv_obj_set_pos(btn, 330, btn_y);
lv_obj_set_size(btn, 140, 38);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, "По умолчанию");
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_WARNING), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_center(lbl);
btn = lv_btn_create(scr);
lv_obj_set_pos(btn, 490, btn_y);
lv_obj_set_size(btn, 100, 38);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, "Меню");
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_center(lbl);
lv_obj_add_event_cb(btn, btn_menu_cb, LV_EVENT_CLICKED, NULL);
}
/* ============ SCREEN 4: НАСТРОЙКИ (config, after password) ============ */
static void create_screen_config(void) {
lv_obj_t *scr = lv_obj_create(NULL);
scr_screen4 = scr;
lv_obj_set_style_bg_color(scr, lv_color_hex(COLOR_BG_DARK), 0);
lv_obj_set_style_bg_opa(scr, LV_OPA_COVER, 0);
lv_obj_clear_flag(scr, LV_OBJ_FLAG_SCROLLABLE);
char buf[64];
/* Header */
lv_obj_t *lbl = lv_label_create(scr);
lv_label_set_text(lbl, "НАСТРОЙКИ");
lv_obj_set_pos(lbl, 340, 10);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
/* Left panel - Sensor corrections - SCROLLABLE */
lv_obj_t *panel_left = lv_obj_create(scr);
lv_obj_set_pos(panel_left, 10, 45);
lv_obj_set_size(panel_left, 385, 340);
lv_obj_set_style_bg_color(panel_left, lv_color_hex(COLOR_BG_PANEL), 0);
lv_obj_set_style_radius(panel_left, 8, 0);
lv_obj_set_style_border_width(panel_left, 0, 0);
lv_obj_set_style_pad_all(panel_left, 8, 0);
lv_obj_set_scroll_dir(panel_left, LV_DIR_VER);
lv_obj_set_scrollbar_mode(panel_left, LV_SCROLLBAR_MODE_AUTO);
lbl = lv_label_create(panel_left);
lv_label_set_text(lbl, "Коррекция датчиков");
lv_obj_set_pos(lbl, 10, 5);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_WARNING), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_t *btn;
int y = 35;
const char *labels[] = {
"Темп. помещения:",
"Влажн. помещения:",
"Темп. в канале:",
"Влажн. в канале:",
"Клапан притока:",
"Клапан рецирк.:"
};
float *offsets[] = {
&config.temp_room_offset,
&config.hum_room_offset,
&config.temp_channel_offset,
&config.hum_channel_offset,
&config.valve_inlet_offset,
&config.valve_recirc_offset
};
for (int i = 0; i < 6; i++) {
btn = lv_btn_create(panel_left);
lv_obj_set_pos(btn, 0, y);
lv_obj_set_size(btn, 350, 36);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
lv_obj_set_style_pad_all(btn, 0, 0);
lv_obj_t *lbl_name = lv_label_create(btn);
lv_label_set_text(lbl_name, labels[i]);
lv_obj_align(lbl_name, LV_ALIGN_LEFT_MID, 10, 0);
lv_obj_set_style_text_color(lbl_name, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl_name, &montserrat_16_ru_en, 0);
snprintf(buf, sizeof(buf), "%+.1f", *offsets[i]);
lv_obj_t *lbl_val = lv_label_create(btn);
lv_label_set_text(lbl_val, buf);
lv_obj_align(lbl_val, LV_ALIGN_RIGHT_MID, -10, 0);
lv_obj_set_style_text_color(lbl_val, lv_color_hex(COLOR_ACCENT), 0);
lv_obj_set_style_text_font(lbl_val, &montserrat_16_ru_en, 0);
lv_obj_add_event_cb(btn, edit_config_offset_cb, LV_EVENT_CLICKED, offsets[i]);
y += 40;
}
/* Toggles for equipment presence */
y += 10;
btn = lv_btn_create(panel_left);
lv_obj_set_pos(btn, 0, y);
lv_obj_set_size(btn, 350, 36);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
lv_obj_set_style_pad_all(btn, 0, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, "Клапан рецирк.:");
lv_obj_align(lbl, LV_ALIGN_LEFT_MID, 10, 0);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, config.has_recirc_valve ? "ЕСТЬ" : "НЕТ");
lv_obj_align(lbl, LV_ALIGN_RIGHT_MID, -10, 0);
lv_obj_set_style_text_color(lbl, config.has_recirc_valve ? lv_color_hex(COLOR_ACCENT) : lv_color_hex(COLOR_TEXT_DIM), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_add_event_cb(btn, toggle_recirc_valve_cb, LV_EVENT_CLICKED, NULL);
y += 40;
btn = lv_btn_create(panel_left);
lv_obj_set_pos(btn, 0, y);
lv_obj_set_size(btn, 350, 36);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
lv_obj_set_style_pad_all(btn, 0, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, "Электронагрев.:");
lv_obj_align(lbl, LV_ALIGN_LEFT_MID, 10, 0);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, config.has_heater ? "ЕСТЬ" : "НЕТ");
lv_obj_align(lbl, LV_ALIGN_RIGHT_MID, -10, 0);
lv_obj_set_style_text_color(lbl, config.has_heater ? lv_color_hex(COLOR_ACCENT) : lv_color_hex(COLOR_TEXT_DIM), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_add_event_cb(btn, toggle_heater_cb, LV_EVENT_CLICKED, NULL);
/* Right panel - PID and mode settings - SCROLLABLE */
lv_obj_t *panel_right = lv_obj_create(scr);
lv_obj_set_pos(panel_right, 405, 45);
lv_obj_set_size(panel_right, 385, 340);
lv_obj_set_style_bg_color(panel_right, lv_color_hex(COLOR_BG_PANEL), 0);
lv_obj_set_style_radius(panel_right, 8, 0);
lv_obj_set_style_border_width(panel_right, 0, 0);
lv_obj_set_style_pad_all(panel_right, 8, 0);
lv_obj_set_scroll_dir(panel_right, LV_DIR_VER);
lv_obj_set_scrollbar_mode(panel_right, LV_SCROLLBAR_MODE_AUTO);
lbl = lv_label_create(panel_right);
lv_label_set_text(lbl, "ПИД регуляторы");
lv_obj_set_pos(lbl, 10, 5);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_WARNING), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
y = 35;
/* PWW PID - separate buttons for each parameter */
lbl = lv_label_create(panel_right);
lv_label_set_text(lbl, "PWW:");
lv_obj_set_pos(lbl, 10, y);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_ACCENT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
y += 25;
/* PWW Kp */
btn = lv_btn_create(panel_right);
lv_obj_set_pos(btn, 0, y);
lv_obj_set_size(btn, 110, 32);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
lv_obj_set_style_pad_all(btn, 0, 0);
snprintf(buf, sizeof(buf), "Kp=%.2f", config.pid_pww_kp);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, buf);
lv_obj_center(lbl);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_add_event_cb(btn, edit_pid_pww_kp_cb, LV_EVENT_CLICKED, NULL);
/* PWW Ti */
btn = lv_btn_create(panel_right);
lv_obj_set_pos(btn, 115, y);
lv_obj_set_size(btn, 110, 32);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
lv_obj_set_style_pad_all(btn, 0, 0);
snprintf(buf, sizeof(buf), "Ti=%.2f", config.pid_pww_ti);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, buf);
lv_obj_center(lbl);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_add_event_cb(btn, edit_pid_pww_ti_cb, LV_EVENT_CLICKED, NULL);
/* PWW D */
btn = lv_btn_create(panel_right);
lv_obj_set_pos(btn, 230, y);
lv_obj_set_size(btn, 110, 32);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
lv_obj_set_style_pad_all(btn, 0, 0);
snprintf(buf, sizeof(buf), "D=%.2f", config.pid_pww_d);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, buf);
lv_obj_center(lbl);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_add_event_cb(btn, edit_pid_pww_d_cb, LV_EVENT_CLICKED, NULL);
y += 42;
/* KWS PID - separate buttons for each parameter */
lbl = lv_label_create(panel_right);
lv_label_set_text(lbl, "KWS:");
lv_obj_set_pos(lbl, 10, y);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_ACCENT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
y += 25;
/* KWS Kp */
btn = lv_btn_create(panel_right);
lv_obj_set_pos(btn, 0, y);
lv_obj_set_size(btn, 110, 32);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
lv_obj_set_style_pad_all(btn, 0, 0);
snprintf(buf, sizeof(buf), "Kp=%.2f", config.pid_kws_kp);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, buf);
lv_obj_center(lbl);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_add_event_cb(btn, edit_pid_kws_kp_cb, LV_EVENT_CLICKED, NULL);
/* KWS Ti */
btn = lv_btn_create(panel_right);
lv_obj_set_pos(btn, 115, y);
lv_obj_set_size(btn, 110, 32);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
lv_obj_set_style_pad_all(btn, 0, 0);
snprintf(buf, sizeof(buf), "Ti=%.2f", config.pid_kws_ti);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, buf);
lv_obj_center(lbl);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_add_event_cb(btn, edit_pid_kws_ti_cb, LV_EVENT_CLICKED, NULL);
/* KWS D */
btn = lv_btn_create(panel_right);
lv_obj_set_pos(btn, 230, y);
lv_obj_set_size(btn, 110, 32);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
lv_obj_set_style_pad_all(btn, 0, 0);
snprintf(buf, sizeof(buf), "D=%.2f", config.pid_kws_d);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, buf);
lv_obj_center(lbl);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_add_event_cb(btn, edit_pid_kws_d_cb, LV_EVENT_CLICKED, NULL);
y += 42;
lbl = lv_label_create(panel_right);
lv_label_set_text(lbl, "Режим работы");
lv_obj_set_pos(lbl, 10, y);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_WARNING), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
y += 30;
btn = lv_btn_create(panel_right);
lv_obj_set_pos(btn, 0, y);
lv_obj_set_size(btn, 350, 36);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
lv_obj_set_style_pad_all(btn, 0, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, "Принуд. режим:");
lv_obj_align(lbl, LV_ALIGN_LEFT_MID, 10, 0);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, config.force_mode_enabled ? "ВКЛ" : "ВЫКЛ");
lv_obj_align(lbl, LV_ALIGN_RIGHT_MID, -10, 0);
lv_obj_set_style_text_color(lbl, config.force_mode_enabled ? lv_color_hex(COLOR_WARNING) : lv_color_hex(COLOR_TEXT_DIM), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_add_event_cb(btn, toggle_force_mode_cb, LV_EVENT_CLICKED, NULL);
y += 40;
btn = lv_btn_create(panel_right);
lv_obj_set_pos(btn, 0, y);
lv_obj_set_size(btn, 350, 36);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
lv_obj_set_style_pad_all(btn, 0, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, "Перемена ключа:");
lv_obj_align(lbl, LV_ALIGN_LEFT_MID, 10, 0);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, config.key_swap_enabled ? "ВКЛ" : "ВЫКЛ");
lv_obj_align(lbl, LV_ALIGN_RIGHT_MID, -10, 0);
lv_obj_set_style_text_color(lbl, config.key_swap_enabled ? lv_color_hex(COLOR_ACCENT) : lv_color_hex(COLOR_TEXT_DIM), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_add_event_cb(btn, toggle_key_swap_cb, LV_EVENT_CLICKED, NULL);
y += 45;
lbl = lv_label_create(panel_right);
lv_label_set_text(lbl, "Дата и время");
lv_obj_set_pos(lbl, 10, y);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_WARNING), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
y += 30;
btn = lv_btn_create(panel_right);
lv_obj_set_pos(btn, 0, y);
lv_obj_set_size(btn, 350, 36);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_BG_BTN), 0);
lv_obj_set_style_radius(btn, 4, 0);
lv_obj_set_style_pad_all(btn, 0, 0);
snprintf(buf, sizeof(buf), "%02d.%02d.%04d %02d:%02d",
state.day, state.month, state.year, state.hour, state.minute);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, buf);
lv_obj_center(lbl);
lv_obj_set_style_text_color(lbl, lv_color_hex(COLOR_TEXT_ACCENT), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_add_event_cb(btn, edit_datetime_cb, LV_EVENT_CLICKED, NULL);
/* Buttons */
btn = lv_btn_create(scr);
lv_obj_set_pos(btn, 200, 410);
lv_obj_set_size(btn, 120, 40);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_ACCENT), 0);
lv_obj_set_style_radius(btn, 8, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, "Сохранить");
lv_obj_set_style_text_color(lbl, lv_color_hex(0xFFFFFF), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_center(lbl);
btn = lv_btn_create(scr);
lv_obj_set_pos(btn, 340, 410);
lv_obj_set_size(btn, 140, 40);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_WARNING), 0);
lv_obj_set_style_radius(btn, 8, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, "По умолчанию");
lv_obj_set_style_text_color(lbl, lv_color_hex(0xFFFFFF), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_center(lbl);
btn = lv_btn_create(scr);
lv_obj_set_pos(btn, 500, 410);
lv_obj_set_size(btn, 100, 40);
lv_obj_set_style_bg_color(btn, lv_color_hex(COLOR_TEXT_ACCENT), 0);
lv_obj_set_style_radius(btn, 8, 0);
lbl = lv_label_create(btn);
lv_label_set_text(lbl, "Меню");
lv_obj_set_style_text_color(lbl, lv_color_hex(0xFFFFFF), 0);
lv_obj_set_style_text_font(lbl, &montserrat_16_ru_en, 0);
lv_obj_center(lbl);
lv_obj_add_event_cb(btn, btn_menu_cb, LV_EVENT_CLICKED, NULL);
}
void gui_update_values(void) {
char buf[64];
/* Update room climate */
if (lbl_temp_room) {
snprintf(buf, sizeof(buf), "%.1f°C", state.temp_room / 10.0f);
lv_label_set_text(lbl_temp_room, buf);
}
if (lbl_hum_room) {
snprintf(buf, sizeof(buf), "%.1f%%", state.hum_room / 10.0f);
lv_label_set_text(lbl_hum_room, buf);
}
/* Update channel climate */
if (lbl_temp_channel) {
snprintf(buf, sizeof(buf), "%.1f°C", state.temp_channel / 10.0f);
lv_label_set_text(lbl_temp_channel, buf);
}
if (lbl_hum_channel) {
snprintf(buf, sizeof(buf), "%.1f%%", state.hum_channel / 10.0f);
lv_label_set_text(lbl_hum_channel, buf);
}
/* Update system status */
if (lbl_room_num) {
snprintf(buf, sizeof(buf), "Комната: %d", state.room_num);
lv_label_set_text(lbl_room_num, buf);
}
if (lbl_master_slave) {
if (state.is_master) {
lv_label_set_text(lbl_master_slave, "Мастер");
lv_obj_set_style_text_color(lbl_master_slave, lv_color_hex(COLOR_ACCENT), 0);
} else {
snprintf(buf, sizeof(buf), "Слейв %s", state.has_signal ? "(OK)" : "(НЕТ)");
lv_label_set_text(lbl_master_slave, buf);
lv_obj_set_style_text_color(lbl_master_slave,
state.has_signal ? lv_color_hex(COLOR_WARNING) : lv_color_hex(COLOR_DANGER), 0);
}
}
if (lbl_season) {
const char *seasons[] = {"Зима", "Лето", "Переход"};
const uint32_t colors[] = {COLOR_TEXT_ACCENT, COLOR_DANGER, COLOR_WARNING};
snprintf(buf, sizeof(buf), "Сезон: %s", seasons[state.season % 3]);
lv_label_set_text(lbl_season, buf);
lv_obj_set_style_text_color(lbl_season, lv_color_hex(colors[state.season % 3]), 0);
}
if (lbl_mode) {
const char *modes[] = {"Остановка", "Дежурный", "Рабочий"};
const uint32_t colors[] = {COLOR_DANGER, COLOR_WARNING, COLOR_ACCENT};
snprintf(buf, sizeof(buf), "Режим: %s", modes[state.mode % 3]);
lv_label_set_text(lbl_mode, buf);
lv_obj_set_style_text_color(lbl_mode, lv_color_hex(colors[state.mode % 3]), 0);
}
if (lbl_datetime) {
snprintf(buf, sizeof(buf), "%02d.%02d.%04d %02d:%02d",
state.day, state.month, state.year, state.hour, state.minute);
lv_label_set_text(lbl_datetime, buf);
}
/* Update equipment modules */
for (int i = 0; i < MODULE_COUNT; i++) {
update_module_display(i);
}
}
/* Update display of a single module */
static void update_module_display(int module_idx) {
if (module_idx < 0 || module_idx >= MODULE_COUNT) return;
if (!modules[module_idx].label_value) return;
char buf[32];
switch (module_idx) {
case MODULE_VALVE_INLET:
snprintf(buf, sizeof(buf), "%d%%", state.valve_inlet);
lv_label_set_text(modules[module_idx].label_value, buf);
break;
case MODULE_VALVE_RECIRC:
snprintf(buf, sizeof(buf), "%d%%", state.valve_recirc);
lv_label_set_text(modules[module_idx].label_value, buf);
break;
case MODULE_FILTER_G4:
lv_label_set_text(modules[module_idx].label_value,
state.filter_g4_clean ? "OK" : "!");
lv_obj_set_style_text_color(modules[module_idx].label_value,
state.filter_g4_clean ? lv_color_hex(COLOR_ACCENT) : lv_color_hex(COLOR_DANGER), 0);
break;
case MODULE_KWS:
snprintf(buf, sizeof(buf), "%d%%", state.kws_valve);
lv_label_set_text(modules[module_idx].label_value, buf);
break;
case MODULE_PWW:
snprintf(buf, sizeof(buf), "%d%%", state.pww_valve);
lv_label_set_text(modules[module_idx].label_value, buf);
break;
case MODULE_HEATER:
lv_label_set_text(modules[module_idx].label_value,
state.heater_on ? "ВКЛ" : "ВЫКЛ");
lv_obj_set_style_text_color(modules[module_idx].label_value,
state.heater_on ? lv_color_hex(COLOR_DANGER) : lv_color_hex(COLOR_TEXT_DIM), 0);
break;
case MODULE_FAN:
lv_label_set_text(modules[module_idx].label_value,
state.fan_on ? "ВКЛ" : "ВЫКЛ");
lv_obj_set_style_text_color(modules[module_idx].label_value,
state.fan_on ? lv_color_hex(COLOR_ACCENT) : lv_color_hex(COLOR_DANGER), 0);
break;
case MODULE_FILTER_H13:
lv_label_set_text(modules[module_idx].label_value,
state.filter_h13_clean ? "OK" : "!");
lv_obj_set_style_text_color(modules[module_idx].label_value,
state.filter_h13_clean ? lv_color_hex(COLOR_ACCENT) : lv_color_hex(COLOR_DANGER), 0);
break;
case MODULE_CHANNEL:
/* Display channel temperature and humidity */
snprintf(buf, sizeof(buf), "%.1f°\n%.0f%%", state.temp_channel / 10.0f, state.hum_channel / 10.0f);
lv_label_set_text(modules[module_idx].label_value, buf);
break;
}
}
/* Refresh setpoints display on screen 3 */
static void refresh_setpoints_display(void) {
/* Remember which screen was active */
lv_obj_t *current_scr = lv_screen_active();
int was_on_screen3 = (current_scr == scr_screen3);
int was_on_screen4 = (current_scr == scr_screen4);
/* Recreate screen 3 to show updated values */
if (scr_screen3) {
lv_obj_del(scr_screen3);
scr_screen3 = NULL;
}
create_screen_screen3();
/* Recreate screen 4 to show updated values */
if (scr_screen4) {
lv_obj_del(scr_screen4);
scr_screen4 = NULL;
}
create_screen_config();
/* Load appropriate screen */
if (was_on_screen3) {
lv_scr_load(scr_screen3);
} else if (was_on_screen4) {
lv_scr_load(scr_screen4);
}
}
void gui_init(void) {
/* Add some example alarms to the log */
add_alarm_to_log("Система запущена", 0);
add_alarm_to_log("Фильтр G4 заменен", 0);
create_screen_main();
create_screen_screen1();
create_screen_screen2();
create_screen_screen3();
create_screen_config();
lv_scr_load(scr_main);
/* Проверка аварийных ситуаций после загрузки главного экрана */
if (!state.filter_g4_clean) {
create_popup_alarm("ФИЛЬТР G4 ЗАСОРИЛСЯ\n\nЗамените фильтр немедленно.\nСистема работает в аварийном режиме.");
state.mode = 0;
}
if (state.temp_channel < 50) {
create_popup_alarm("УГРОЗА ЗАМОРОЗКИ\n\nУстановка остановлена.\nПроверьте журнал и примите меры.");
state.mode = 0;
}
}
/* ── ModBus ──────────────────────────────────────── */
void gui_modbus_init(void) {
/* No-op for simulation */
}
void gui_modbus_task(void *arg) {
(void)arg;
/* Simulation: update with dummy values */
while (1) {
state.temp_room = 220 + (rand() % 40);
state.hum_room = 450 + (rand() % 100);
state.temp_channel = 200 + (rand() % 30);
state.hum_channel = 500 + (rand() % 80);
/* Simulate valve changes based on mode */
if (state.mode == 2) {
state.valve_inlet = 70 + (rand() % 30);
state.valve_recirc = 10 + (rand() % 20);
state.kws_valve = 40 + (rand() % 30);
state.pww_valve = 30 + (rand() % 25);
state.fan_on = 1;
} else if (state.mode == 1) {
state.valve_inlet = 30 + (rand() % 20);
state.valve_recirc = 40 + (rand() % 20);
state.kws_valve = 20 + (rand() % 15);
state.pww_valve = 15 + (rand() % 15);
state.fan_on = 1;
} else {
state.valve_inlet = 0;
state.valve_recirc = 0;
state.kws_valve = 0;
state.pww_valve = 0;
state.fan_on = 0;
state.heater_on = 0;
}
gui_update_values();
/* Sleep simulation - requires FreeRTOS or use SDL delay */
}
}