Suche dny, rucni rezim. Netestovano

v2
Ondřej Hruška 5 months ago
parent 88c215393b
commit b128efecd3
  1. 2
      CMakeLists.txt
  2. 15
      src/app_config.c
  3. 29
      src/app_config.h
  4. 20
      src/app_io.c
  5. 2
      src/app_io.h
  6. 9
      src/ds_rtc.c
  7. 1
      src/ds_rtc.h
  8. 78
      src/screens/app_gui.c
  9. 11
      src/screens/app_gui.h
  10. 2
      src/screens/gui_event.h
  11. 6
      src/screens/screen_cyklus.c
  12. 4
      src/screens/screen_delka_zalivky.c
  13. 15
      src/screens/screen_home.c
  14. 4
      src/screens/screen_kompenzace_tlaku.c
  15. 68
      src/screens/screen_manual_control.c
  16. 4
      src/screens/screen_moisture_calib.c
  17. 4
      src/screens/screen_program_edit.c
  18. 4
      src/screens/screen_program_prehled.c
  19. 4
      src/screens/screen_set_moisture_threshold.c
  20. 4
      src/screens/screen_set_time.c
  21. 18
      src/screens/screen_settings.c
  22. 44
      src/screens/screen_suche_dny.c

@ -31,6 +31,8 @@ add_executable(zavlaha
src/screens/screen_kompenzace_tlaku.c
src/screens/screen_program_prehled.c
src/screens/screen_program_edit.c
src/screens/screen_suche_dny.c
src/screens/screen_manual_control.c
src/app_io.c
src/app_config.c)

@ -9,10 +9,10 @@
#define APPCONFIG_EE_BASE_ADDR 0
// TODO sensible defaults, loading and saving to/from EE
struct AppConfig app_config;
static const struct AppConfig DEFAULTS = {
.magic_version = APPCONFIG_MAGIC1,
.circuit_base_time_s = 300, // for a test
.circuit_time_percent = {100, 100, 100, 100}, // for a test
.scheduler_enable = true,
@ -26,6 +26,8 @@ static const struct AppConfig DEFAULTS = {
.moisture_dry = 2800,
.moisture_wet = 1600,
.moisture_threshold_percent = 70,
.magic2 = APPCONFIG_MAGIC2,
.scheduler_dry_days = 0,
};
static void load_fallback_config() {
@ -36,7 +38,7 @@ static void load_fallback_config() {
void appconfig_load()
{
uint8_t magic = 0;
if (0 > ee_read(APPCONFIG_EE_BASE_ADDR, &magic, 1) || magic != APPCONFIG_VERSION) {
if (0 > ee_read(APPCONFIG_EE_BASE_ADDR, &magic, 1) || magic != APPCONFIG_MAGIC1) {
printf("Couldn't read EE to check magic!\r\n");
load_fallback_config();
return;
@ -45,6 +47,12 @@ void appconfig_load()
if (0 > ee_read(APPCONFIG_EE_BASE_ADDR, (uint8_t *) &app_config, APPCONFIG_SIZE)) {
printf("Couldn't read EE to load config!\r\n");
load_fallback_config();
return;
}
if (app_config.magic2 != APPCONFIG_MAGIC2) {
app_config.magic2 = APPCONFIG_MAGIC2;
app_config.scheduler_dry_days = 0;
}
}
@ -53,7 +61,8 @@ void appconfig_save()
// printf("PERSISTENCE DISABLED - NOT SAVING\r\n");
// return; // FIXME - when all GUIs are finished, add persistence
// whatever, just write everything
app_config.magic_version = APPCONFIG_VERSION;
app_config.magic_version = APPCONFIG_MAGIC1;
app_config.magic2 = APPCONFIG_MAGIC2;
if (0 > ee_write(APPCONFIG_EE_BASE_ADDR, (const uint8_t *) &app_config, APPCONFIG_SIZE)) {
printf("Couldn't write EE!");
}

@ -13,26 +13,29 @@
#define MENU_AUTOEXIT_TIME_S 120
struct __attribute__((packed)) ScheduleTime {
bool enable;
uint8_t h;
uint8_t m;
bool enable; /// True if the schedule time is enabled
uint8_t h; /// Hour
uint8_t m; /// Minute
};
struct __attribute__((packed)) AppConfig {
uint8_t magic_version;
uint16_t circuit_base_time_s;
uint16_t circuit_time_percent[CIRCUIT_COUNT]; // 0% can be used to disable a branch
bool scheduler_enable;
struct ScheduleTime schedules[SCHEDULE_COUNT];
bool moisture_enable;
uint16_t moisture_dry;
uint16_t moisture_wet;
uint8_t moisture_threshold_percent;
uint8_t magic_version; /// Magic (byte reserved)
uint16_t circuit_base_time_s; /// Base time for each branch (scaled by percent)
uint16_t circuit_time_percent[CIRCUIT_COUNT]; /// Relative time of each branch 0% can be used to disable a branch
bool scheduler_enable; /// Enable automatic watering
struct ScheduleTime schedules[SCHEDULE_COUNT]; /// Scheduled times in a day
bool moisture_enable; /// Enable blocking watering by high soil humidity
uint16_t moisture_dry; /// Saved humidity value as 0%
uint16_t moisture_wet; /// Saved humidity value as 100%
uint8_t moisture_threshold_percent; /// % of soil humidity that deactivates watering
uint8_t magic2;
uint8_t scheduler_dry_days; /// Days to skip between watering
};
#define APPCONFIG_SIZE sizeof(struct AppConfig)
#define APPCONFIG_VERSION 0b11100001
#define APPCONFIG_MAGIC1 0b11100001
#define APPCONFIG_MAGIC2 0b00111001
extern struct AppConfig app_config;

@ -9,21 +9,29 @@
#include "pinout.h"
#include "app_config.h"
void set_relays(bool one, bool two, bool three, bool four)
{
gpio_put(PIN_RE1, one);
gpio_put(PIN_RE2, two);
gpio_put(PIN_RE3, three);
gpio_put(PIN_RE1, four);
gpio_put(PIN_RE4, four);
}
void open_valve(uint8_t num)
{
gpio_put(PIN_RE1, num == 1);
gpio_put(PIN_RE2, num == 2);
gpio_put(PIN_RE3, num == 3);
gpio_put(PIN_RE4, num == 4);
set_relays(num == 1, num == 2, num == 3, num == 4);
}
void set_one_relay(uint8_t num, bool on)
{
if (num < 1 || num > 4) {
return;
}
if (num == 1) { gpio_put(PIN_RE1, on); }
if (num == 2) { gpio_put(PIN_RE2, on); }
if (num == 3) { gpio_put(PIN_RE3, on); }
if (num == 4) { gpio_put(PIN_RE4, on); }
}
uint16_t moisture_read()

@ -13,6 +13,8 @@ void set_relays(bool one, bool two, bool three, bool four);
/** Open valve, 1,2,3,4; 0=close all */
void open_valve(uint8_t num);
void set_one_relay(uint8_t num, bool on);
uint16_t moisture_read();
uint16_t moisture_convert(uint16_t moisture);

@ -1,5 +1,5 @@
/**
* TODO file description
* Functions to access the RTC (DS3231)
*/
#include <pico/stdlib.h>
@ -35,11 +35,13 @@ static int rtc_write(uint8_t start, const uint8_t *data, size_t len) {
int rtc_get_time(struct rtc_time *dest) {
int rv;
uint8_t buf[3];
TRY(rtc_read(0x00, buf, 3));
uint8_t buf[4];
TRY(rtc_read(0x00, buf, 4));
dest->second = (buf[0] & 15) + (buf[0] >> 4) * 10;
dest->minute = (buf[1] & 15) + (buf[1] >> 4) * 10;
dest->weekday = buf[3] & 7;
if (dest->weekday > 0) dest->weekday -= 1; // Normalize to 0-6, with failsafe in case we got zero, should not happen.
if (buf[2] & 0x40) {
// 12h time?
@ -60,5 +62,6 @@ int rtc_set_time(const struct rtc_time *time) {
buf[2] = (time->hour % 10) + ((time->hour / 10) << 4);
TRY(rtc_write(0x00, buf, 3));
// just let the week counter go on its own - we only care about it incrementing
return 0;
}

@ -10,6 +10,7 @@ struct rtc_time {
uint8_t hour;
uint8_t minute;
uint8_t second;
uint8_t weekday; // 0-6, We use this as a counter for dry days
};
int rtc_get_time(struct rtc_time *dest);

@ -28,12 +28,53 @@ static void draw_common_overlay();
//stratch string buffer
char sbuf[100];
void gui_read_moisture() {
void gui_read_moisture()
{
s_app.moisture_pt = moisture_convert(s_app.moisture_raw = moisture_read());
}
static void read_time_and_moisture() {
/**
* Get days remaining to next watering day
*
* @return
*/
uint8_t days_to_next_watering()
{
if (app_config.scheduler_dry_days == 0) {
return 0;
}
uint8_t wd = s_app.rtc_time.weekday;
uint8_t last_wd = s_app.last_watering_day_wd;
uint8_t normalized_next = (last_wd + 1 + app_config.scheduler_dry_days) % 7;
if (wd > normalized_next) {
normalized_next += 7;
}
return normalized_next - wd;
}
static void read_time_and_moisture()
{
uint8_t prev_wd = s_app.rtc_time.weekday;
rtc_get_time(&s_app.rtc_time); // now is a good time to update timestamp - we could just increment it though
// decide if today is a watering day
if (app_config.scheduler_dry_days == 0) {
s_app.today_is_watering_day = true;
s_app.last_watering_day_wd = s_app.rtc_time.weekday;
} else {
uint8_t cur_wd = s_app.rtc_time.weekday;
if (cur_wd != prev_wd) {
s_app.today_is_watering_day = false;
uint8_t now_normalized = cur_wd + (cur_wd < s_app.last_watering_day_wd ? 7 : 0);
uint8_t elapsed_days = now_normalized - s_app.last_watering_day_wd;
if (elapsed_days >= app_config.scheduler_dry_days) {
s_app.today_is_watering_day = true;
s_app.last_watering_day_wd = cur_wd;
}
}
}
gui_read_moisture();
}
@ -44,11 +85,19 @@ void gui_init()
read_time_and_moisture();
s_app.today_is_watering_day = true;
s_app.last_watering_day_wd = s_app.rtc_time.weekday;
LcdBuffer_Init(&lcd, CGROM_A00, CGRAM_CZ);
}
static bool blink_state = 0;
bool is_valve_control_screen()
{
return s_app.screen == screen_cyklus || s_app.screen == screen_manual_control;
}
void gui_loop_iter(GuiEvent message)
{
uint32_t tickNow = timestamp_ms();
@ -56,7 +105,7 @@ void gui_loop_iter(GuiEvent message)
// Time program logic - since this runs very often, we are guaranteed not to miss a cycle - unless there is a power outage
// exactly in the scheduled watering time
if (s_app.screen != screen_cyklus && app_config.scheduler_enable) {
if (!is_valve_control_screen() && app_config.scheduler_enable && (s_app.today_is_watering_day || app_config.scheduler_dry_days == 0)) { // if dry days=0, is_wd should always be true, but making sure here
if (s_app.cycle_time_checking) {
if (s_app.rtc_time.hour != s_app.last_cycle_time.hour
|| s_app.rtc_time.minute != s_app.last_cycle_time.minute) {
@ -88,7 +137,7 @@ void gui_loop_iter(GuiEvent message)
}
// screen auto-close
if (s_app.screen != screen_home && s_app.screen != screen_cyklus) {
if (s_app.screen != screen_home && !is_valve_control_screen()) {
if ((tickNow - s_app.screen_open_time) >= (MENU_AUTOEXIT_TIME_S * 1000)) {
switch_screen(screen_home, true);
}
@ -154,13 +203,17 @@ void gui_loop_iter(GuiEvent message)
/** Switch to a different screen handler.
* If "init" is true, immediately call it with the init event. */
void switch_screen(screen_t pScreen, bool init)
static void switch_screen_ex(screen_t pScreen, bool init, bool deinit_current)
{
if (s_app.screen == pScreen) {
// already, no op
return;
}
if (s_app.screen && deinit_current) {
s_app.screen(GUI_EVENT_SCREEN_DEINIT);
}
s_app.screen_open_time = timestamp_ms();
s_app.screen = pScreen;
@ -178,6 +231,21 @@ void switch_screen(screen_t pScreen, bool init)
}
}
void switch_screen(screen_t pScreen, bool init)
{
switch_screen_ex(pScreen, init, true);
}
void switch_to_subscreen(screen_t pScreen)
{
switch_screen_ex(pScreen, true, false);
}
void switch_to_parent_screen(screen_t pScreen)
{
switch_screen_ex(pScreen, false, true);
}
/** Draw GUI common to all screens */
static void draw_common_overlay()
{

@ -45,6 +45,9 @@ void gui_init();
*/
void switch_screen(screen_t pScreen, bool init);
void switch_to_subscreen(screen_t pScreen);
void switch_to_parent_screen(screen_t pScreen);
void request_paint();
// prototypes for screen handlers
@ -59,6 +62,10 @@ void screen_moisture_calib(GuiEvent event);
void screen_delka_zalivky(GuiEvent event);
void screen_kompenzace_tlaku(GuiEvent event);
void screen_program_prehled(GuiEvent event);
void screen_suche_dny(GuiEvent event);
void screen_manual_control(GuiEvent event);
uint8_t days_to_next_watering();
extern int pgm_edit_slot;
void screen_program_edit(GuiEvent event);
@ -66,6 +73,7 @@ void screen_program_edit(GuiEvent event);
void gui_read_moisture();
// This is actually the global app state!
struct State {
struct rtc_time rtc_time;
uint16_t moisture_raw;
@ -85,6 +93,9 @@ struct State {
uint32_t screen_open_time;
bool today_is_watering_day;
uint8_t last_watering_day_wd;
};
extern struct State s_app;

@ -13,6 +13,8 @@ typedef enum GuiEvent {
GUI_EVENT_PAINT,
/// Used as the argument when initing a screen
GUI_EVENT_SCREEN_INIT,
/// Screen is about to close (this will NOT be called for middle pages when auto-closing a multi-level hierarchy!)
GUI_EVENT_SCREEN_DEINIT,
/// Time tick, used to carry timing to the screen functions.
/// This tick has 10ms interval
GUI_EVENT_SCREEN_TICK_10MS,

@ -82,6 +82,10 @@ void screen_cyklus(GuiEvent event)
case GUI_EVENT_KEY_D:
end_cycle();
switch_screen(screen_home, true);
return;
break;
case GUI_EVENT_SCREEN_DEINIT:
open_valve(0); // close all - this should not be needed here, this screen does not autoclose.
break;
}
}

@ -38,12 +38,12 @@ void screen_delka_zalivky(GuiEvent event)
case GUI_EVENT_KEY_A: // Confirm
if (cursor > 0) {
settings_scratch.circuit_base_time_s = value;
switch_screen(screen_settings, false);
switch_to_parent_screen(screen_settings);
}
break;
case GUI_EVENT_KEY_D: // CANCEL
switch_screen(screen_settings, false);
switch_to_parent_screen(screen_settings);
break;
default:

@ -49,6 +49,7 @@ void screen_home(GuiEvent event)
} else {
int found = -1;
int first = -1;
bool wrapped_around = false;
uint16_t m_now = s_app.rtc_time.hour * 60 + s_app.rtc_time.minute;
for (int i = 0; i < SCHEDULE_COUNT; i++) {
if (!app_config.schedules[i].enable) {
@ -68,27 +69,37 @@ void screen_home(GuiEvent event)
if (found == -1) {
found = first;
wrapped_around = true;
}
if (found == -1) {
LcdBuffer_Write(&lcd, 1, 0, "Program nenastaven! ");
} else {
uint8_t days = days_to_next_watering();
if (days > 0 && (!s_app.today_is_watering_day || wrapped_around)) {
snprintf(sbuf, sbuf_len, "Další závlaha za %d d", days);
} else {
snprintf(sbuf, sbuf_len, "Další závlaha %d:%02d ",
app_config.schedules[found].h, app_config.schedules[found].m);
}
LcdBuffer_Write(&lcd, 1, 0, sbuf);
}
}
LcdBuffer_Write(&lcd, 2, 0, "🅰Spustit cyklus");
LcdBuffer_Write(&lcd, 3, 0, "🅱Nastavení");
LcdBuffer_Write(&lcd, 3, 0, "🅱Nastavení 🅳Test");
break;
case GUI_EVENT_KEY_A:
switch_screen(screen_cyklus, true); // fake cycle
switch_screen(screen_cyklus, true); // manually started cycle
break;
case GUI_EVENT_KEY_B:
switch_screen(screen_settings, true);
break;
case GUI_EVENT_KEY_D:
switch_screen(screen_manual_control, true);
break;
}
}

@ -46,11 +46,11 @@ void screen_kompenzace_tlaku(GuiEvent event)
case GUI_EVENT_KEY_A: // Confirm
memcpy(settings_scratch.circuit_time_percent, ratios, sizeof(settings_scratch.circuit_time_percent));
switch_screen(screen_settings, false);
switch_to_parent_screen(screen_settings);
break;
case GUI_EVENT_KEY_D: // CANCEL
switch_screen(screen_settings, false);
switch_to_parent_screen(screen_settings);
break;
case GUI_EVENT_KEY_1:

@ -0,0 +1,68 @@
#include <stdio.h>
#include "app_gui.h"
#include "gui_event.h"
#include "app_io.h"
#include "lcd.h"
static bool valve1, valve2, valve3, valve4;
void screen_manual_control(GuiEvent event)
{
switch (event) {
case GUI_EVENT_SCREEN_INIT:
open_valve(0); // make sure all are closed
valve1 = valve2 = valve3 = valve4 = false;
break;
case GUI_EVENT_PAINT:
LcdBuffer_Write(&lcd, 0, 0, "== MANUÁLNÍ REŽIM ==");
LcdBuffer_Write(&lcd, 1, 0, "1-4 ventil,0 vyp.vše");
LcdBuffer_SetCursor(&lcd, 2, 0, CURSOR_BOTH);
snprintf(sbuf, sbuf_len, " %s %s %s %s ",
valve1 ? "█1█" : " 1 ",
valve2 ? "█2█" : " 2 ",
valve3 ? "█3█" : " 3 ",
valve4 ? "█4█" : " 4 ");
LcdBuffer_Write(&lcd, 2, 0, sbuf);
LcdBuffer_Write(&lcd, 3, 0, "🅳Konec");
break;
case GUI_EVENT_KEY_D: // CANCEL
open_valve(0);
switch_screen(screen_home, true);
break;
case GUI_EVENT_KEY_0:
valve1 = valve2 = valve3 = valve4 = false;
open_valve(0);
request_paint();
break;
case GUI_EVENT_KEY_1:
valve1 = !valve1;
set_one_relay(1, valve1);
break;
case GUI_EVENT_KEY_2:
valve2 = !valve2;
set_one_relay(2, valve2);
break;
case GUI_EVENT_KEY_3:
valve3 = !valve3;
set_one_relay(3, valve3);
break;
case GUI_EVENT_KEY_4:
valve4 = !valve4;
set_one_relay(4, valve4);
break;
case GUI_EVENT_SCREEN_DEINIT:
open_valve(0); // close all
break;
}
}

@ -42,12 +42,12 @@ void screen_moisture_calib(GuiEvent event)
} else {
settings_scratch.moisture_dry = sample_dry;
settings_scratch.moisture_wet = s_app.moisture_raw;
switch_screen(screen_settings, false);
switch_to_parent_screen(screen_settings);
}
break;
case GUI_EVENT_KEY_D: // CANCEL
switch_screen(screen_settings, false);
switch_to_parent_screen(screen_settings);
break;
}
}

@ -42,7 +42,7 @@ void screen_program_edit(GuiEvent event)
case GUI_EVENT_KEY_A: // Confirm
if (!time.enable || cursor == 4) {
memcpy(&settings_scratch.schedules[pgm_edit_slot], &time, sizeof(time));
switch_screen(screen_program_prehled, false);
switch_to_parent_screen(screen_program_prehled);
}
break;
@ -54,7 +54,7 @@ void screen_program_edit(GuiEvent event)
break;
case GUI_EVENT_KEY_D: // CANCEL
switch_screen(screen_program_prehled, false);
switch_to_parent_screen(screen_program_prehled);
break;
default:

@ -44,7 +44,7 @@ void screen_program_prehled(GuiEvent event)
break;
case GUI_EVENT_KEY_D: // CANCEL
switch_screen(screen_settings, false);
switch_to_parent_screen(screen_settings);
break;
case GUI_EVENT_KEY_1:
@ -52,7 +52,7 @@ void screen_program_prehled(GuiEvent event)
case GUI_EVENT_KEY_3:
case GUI_EVENT_KEY_4:
pgm_edit_slot = event - '1'; // 0-3
switch_screen(screen_program_edit, true);
switch_to_subscreen(screen_program_edit);
break;
}
}

@ -37,12 +37,12 @@ void screen_set_moisture_threshold(GuiEvent event)
case GUI_EVENT_KEY_A: // Confirm
if (cursor > 0) {
settings_scratch.moisture_threshold_percent = threshold;
switch_screen(screen_settings, false);
switch_to_parent_screen(screen_settings);
}
break;
case GUI_EVENT_KEY_D: // CANCEL
switch_screen(screen_settings, false);
switch_to_parent_screen(screen_settings);
break;
default:

@ -32,12 +32,12 @@ void screen_set_time(GuiEvent event)
case GUI_EVENT_KEY_A: // Confirm
if (cursor == 4) {
rtc_set_time(&time);
switch_screen(screen_settings, false);
switch_to_parent_screen(screen_settings);
}
break;
case GUI_EVENT_KEY_D: // CANCEL
switch_screen(screen_settings, false);
switch_to_parent_screen(screen_settings);
break;
default:

@ -43,6 +43,8 @@ void screen_settings(GuiEvent event)
case 2:
LcdBuffer_Write(&lcd, 0, 0, "1. Délka zálivky");
LcdBuffer_Write(&lcd, 1, 0, "2. Kompenzace tlaku");
snprintf(sbuf, sbuf_len, "3. Suché dny (%d)", settings_scratch.scheduler_dry_days);
LcdBuffer_Write(&lcd, 2, 0, sbuf);
break;
case 3:
@ -96,11 +98,11 @@ void screen_settings(GuiEvent event)
break;
case GUI_EVENT_KEY_2: // Upravit program
switch_screen(screen_program_prehled, true);
switch_to_subscreen(screen_program_prehled);
break;
case GUI_EVENT_KEY_3: // Nastavit cas
switch_screen(screen_set_time, true);
switch_to_subscreen(screen_set_time);
break;
}
break;
@ -108,11 +110,15 @@ void screen_settings(GuiEvent event)
case 2:
switch (event) {
case GUI_EVENT_KEY_1: // Délka zálivky
switch_screen(screen_delka_zalivky, true);
switch_to_subscreen(screen_delka_zalivky);
break;
case GUI_EVENT_KEY_2: // Kompenzace tlaku
switch_screen(screen_kompenzace_tlaku, true);
switch_to_subscreen(screen_kompenzace_tlaku);
break;
case GUI_EVENT_KEY_3: // Nastavit suche dny
switch_to_subscreen(screen_suche_dny);
break;
}
break;
@ -125,11 +131,11 @@ void screen_settings(GuiEvent event)
break;
case GUI_EVENT_KEY_2: // Kalibrace vlhkomeru
switch_screen(screen_moisture_calib, true);
switch_to_subscreen(screen_moisture_calib);
break;
case GUI_EVENT_KEY_3: // Nastavit prah
switch_screen(screen_set_moisture_threshold, true);
switch_to_subscreen(screen_set_moisture_threshold);
break;
}
break;

@ -0,0 +1,44 @@
#include <stdio.h>
#include "app_gui.h"
#include "gui_event.h"
#include "app_io.h"
#include "lcd.h"
#include "app_config.h"
static int value;
void screen_suche_dny(GuiEvent event)
{
switch (event) {
case GUI_EVENT_SCREEN_INIT:
value = app_config.scheduler_dry_days;
break;
case GUI_EVENT_PAINT:
LcdBuffer_Write(&lcd, 0, 0, "Vynechat dny mezi");
LcdBuffer_Write(&lcd, 1, 0, "auto. zálivkou (0-5)");
LcdBuffer_SetCursor(&lcd, 2, 0, CURSOR_BOTH);
snprintf(sbuf, sbuf_len, "%d d", value);
LcdBuffer_Write(&lcd, 2, 0, sbuf);
LcdBuffer_Write(&lcd, 3, 0, "🅳Zrušit 🅰OK");
break;
case GUI_EVENT_KEY_A: // Confirm
settings_scratch.scheduler_dry_days = value;
switch_to_parent_screen(screen_settings);
break;
case GUI_EVENT_KEY_D: // CANCEL
switch_to_parent_screen(screen_settings);
break;
default:
if (event >= '0' && event <= '5') {
value = (uint8_t) (event - '0');
request_paint();
}
}
}
Loading…
Cancel
Save