diff --git a/src/app_config.c b/src/app_config.c index 8bdef49..c14d567 100644 --- a/src/app_config.c +++ b/src/app_config.c @@ -13,15 +13,16 @@ struct AppConfig app_config; static const struct AppConfig DEFAULTS = { - .circuit_base_time_s = 10, // for a test - .circuit_time_percent = {30, 60, 90, 100}, // for a test + .circuit_base_time_s = 300, // for a test + .circuit_time_percent = {100, 100, 100, 100}, // for a test + .scheduler_enable = true, .schedules = { - {true, 6, 45}, - {false, 0, 0}, - {false, 0, 0}, + {true, 7, 0}, + {true, 14, 0}, + {true, 20, 0}, {false, 0, 0} }, - .moisture_enable = true, + .moisture_enable = false, .moisture_dry = 2800, .moisture_wet = 1600, .moisture_threshold_percent = 70, @@ -35,13 +36,13 @@ 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_VERSION) { printf("Couldn't read EE to check magic!\r\n"); load_fallback_config(); return; } - if (0 != ee_read(APPCONFIG_EE_BASE_ADDR, (uint8_t *) &app_config, APPCONFIG_SIZE)) { + 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(); } @@ -49,11 +50,11 @@ void appconfig_load() void appconfig_save() { - printf("PERSISTENCE DISABLED - NOT SAVING\r\n"); - return; // FIXME - when all GUIs are finished, add persistence - +// printf("PERSISTENCE DISABLED - NOT SAVING\r\n"); +// return; // FIXME - when all GUIs are finished, add persistence // whatever, just write everything - if (0 != ee_write(APPCONFIG_EE_BASE_ADDR, (const uint8_t *) &app_config, APPCONFIG_SIZE)) { + app_config.magic_version = APPCONFIG_VERSION; + if (0 > ee_write(APPCONFIG_EE_BASE_ADDR, (const uint8_t *) &app_config, APPCONFIG_SIZE)) { printf("Couldn't write EE!"); } } diff --git a/src/screens/app_gui.c b/src/screens/app_gui.c index 7310e1b..b99d63b 100644 --- a/src/screens/app_gui.c +++ b/src/screens/app_gui.c @@ -8,33 +8,81 @@ #include "lcd/lcdbuf.h" #include "lcd.h" #include "app_config.h" +#include "app_io.h" struct State s_app = {}; struct LcdBuffer lcd = {}; /** Schedule paint (the screen func will be called with the PAINT event argument */ -void request_paint() { +void request_paint() +{ s_app.paint_needed = true; } /** Draw the common overlay / HUD (with temperatures and heater status) */ static void draw_common_overlay(); +//stratch string buffer char sbuf[100]; +void gui_read_moisture() { + s_app.moisture_pt = moisture_convert(s_app.moisture_raw = moisture_read()); +} + +static void read_time_and_moisture() { + rtc_get_time(&s_app.rtc_time); // now is a good time to update timestamp - we could just increment it though + gui_read_moisture(); +} + /** Main loop */ void gui_init() { switch_screen(screen_home, true); - rtc_get_time(&s_app.rtc_time); + + read_time_and_moisture(); LcdBuffer_Init(&lcd, CGROM_A00, CGRAM_CZ); } -void gui_loop_iter(GuiEvent message) { +void gui_loop_iter(GuiEvent message) +{ uint32_t tickNow = timestamp_ms(); + // 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 (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) { + s_app.cycle_time_checking = false; + } + } else { + for (int i = 0; i < SCHEDULE_COUNT; i++) { + if (!app_config.schedules[i].enable) { + continue; + } + + if (s_app.rtc_time.hour == app_config.schedules[i].h + && s_app.rtc_time.minute == app_config.schedules[i].m) { + // cycle can preempt all screens + + // Check if moisture is OK + if (app_config.moisture_enable) { + if (s_app.moisture_pt > app_config.moisture_threshold_percent) { + // watering not needed + break; + } + } + + switch_screen(screen_cyklus, true); + break; + } + } + } + } + // screen auto-close if (s_app.screen != screen_home && s_app.screen != screen_cyklus) { if ((tickNow - s_app.screen_open_time) >= (MENU_AUTOEXIT_TIME_S * 1000)) { @@ -45,7 +93,7 @@ void gui_loop_iter(GuiEvent message) { // Read RTC every second if (tickNow - s_app.last_1s_time >= 1000) { s_app.screen(GUI_EVENT_SCREEN_TICK_1S); - rtc_get_time(&s_app.rtc_time); // now is a good time to update timestamp - we could just increment it though + read_time_and_moisture(); s_app.last_1s_time = tickNow; } @@ -83,7 +131,8 @@ 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) { +void switch_screen(screen_t pScreen, bool init) +{ if (s_app.screen == pScreen) { // already, no op return; @@ -107,11 +156,13 @@ void switch_screen(screen_t pScreen, bool init) { } /** Draw GUI common to all screens */ -static void draw_common_overlay() { +static void draw_common_overlay() +{ // TODO } /** Play input sound effect if this is an input event */ -void input_sound_effect() { +void input_sound_effect() +{ // TODO } diff --git a/src/screens/app_gui.h b/src/screens/app_gui.h index 4e77128..930dff4 100644 --- a/src/screens/app_gui.h +++ b/src/screens/app_gui.h @@ -64,8 +64,14 @@ extern int pgm_edit_slot; void screen_program_edit(GuiEvent event); // XXX other prototypes +void gui_read_moisture(); + struct State { struct rtc_time rtc_time; + uint16_t moisture_raw; + uint16_t moisture_pt; + struct rtc_time last_cycle_time; + bool cycle_time_checking; /// Repaint was requested from the screen code bool paint_needed; @@ -77,6 +83,7 @@ struct State { uint32_t last_100ms_time; uint32_t last_1s_time; + uint32_t screen_open_time; }; diff --git a/src/screens/screen_cyklus.c b/src/screens/screen_cyklus.c index 41dbf9d..2f06d82 100644 --- a/src/screens/screen_cyklus.c +++ b/src/screens/screen_cyklus.c @@ -11,15 +11,19 @@ #include "app_io.h" #include "app_config.h" +#define MIN_CYCLE_TIME_S 70 // more than 60 + static int phase = 0; static uint32_t phase_seconds = 0; static uint32_t phase_seconds_max = 0; -static void end_cycle() { +static void end_cycle() +{ open_valve(0); } -static void start_branch() { +static void start_branch() +{ phase_seconds_max = ((uint32_t) app_config.circuit_base_time_s * (uint32_t) app_config.circuit_time_percent[phase]) / 100; open_valve(phase + 1); } @@ -30,6 +34,9 @@ void screen_cyklus(GuiEvent event) case GUI_EVENT_SCREEN_INIT: phase = 0; phase_seconds = 0; + s_app.last_cycle_time.hour = s_app.rtc_time.hour; + s_app.last_cycle_time.minute = s_app.rtc_time.minute; + s_app.cycle_time_checking = true; start_branch(); break; @@ -52,24 +59,24 @@ void screen_cyklus(GuiEvent event) case GUI_EVENT_PAINT: LcdBuffer_Write(&lcd, 0, 0, "==ZÁVLAHOVÝ CYKLUS=="); + LcdBuffer_Write(&lcd, 3, 0, "🅳 Ukončit"); snprintf(sbuf, sbuf_len, "Okruh %d/%d %3d/%d s ", phase + 1, 4, phase_seconds, phase_seconds_max); LcdBuffer_Write(&lcd, 1, 0, sbuf); - uint32_t pieces = (phase_seconds * 40) / phase_seconds_max; + uint32_t pieces = (phase_seconds * 40) / phase_seconds_max; char *buf = sbuf; for (int i = 0; i < 20; i++) { - if ((i*2 + 1) == pieces) { + if ((i * 2 + 1) == pieces) { buf += sprintf(buf, "▌"); } else if (i * 2 < pieces) { buf += sprintf(buf, "█"); - } else { + } else { buf += sprintf(buf, " "); } } LcdBuffer_Write(&lcd, 2, 0, sbuf); - LcdBuffer_Write(&lcd, 3, 0, "🅳 Ukončit"); break; case GUI_EVENT_KEY_D: diff --git a/src/screens/screen_home.c b/src/screens/screen_home.c index c698931..2227750 100644 --- a/src/screens/screen_home.c +++ b/src/screens/screen_home.c @@ -12,42 +12,82 @@ #include "app_io.h" #include "app_config.h" -static uint16_t moisture_pt = 0; - void screen_home(GuiEvent event) { switch (event) { case GUI_EVENT_SCREEN_INIT: // pass case GUI_EVENT_SCREEN_TICK_100MS: - moisture_pt = moisture_convert(moisture_read()); + if (app_config.moisture_enable) { + gui_read_moisture(); + } request_paint(); break; case GUI_EVENT_PAINT: - sprintf(sbuf, " %2d:%02d:%02d %3d%% 🌢 ", - s_app.rtc_time.hour, - s_app.rtc_time.minute, - s_app.rtc_time.second, - moisture_pt); + + if (app_config.moisture_enable) { + sprintf(sbuf, " %2d:%02d:%02d %3d%% 🌢%s", + s_app.rtc_time.hour, + s_app.rtc_time.minute, + s_app.rtc_time.second, + s_app.moisture_pt, + s_app.moisture_pt > app_config.moisture_threshold_percent ? "↑" : " " + ); + } else { + sprintf(sbuf, " %2d:%02d:%02d", + s_app.rtc_time.hour, + s_app.rtc_time.minute, + s_app.rtc_time.second); + } LcdBuffer_Write(&lcd, 0, 0, sbuf); - LcdBuffer_Write(&lcd, 1, 0, "Plán. závlaha 7:15"); - LcdBuffer_Write(&lcd, 2, 0, "❶Spustit ❷Přeskočit"); - LcdBuffer_Write(&lcd, 3, 0, "🅰Nastavení"); - break; + // Find the next due schedule + if (!app_config.scheduler_enable) { + LcdBuffer_Write(&lcd, 1, 0, "Čas. program vypnutý"); + } else { + int found = -1; + int first = -1; + 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) { + continue; + } - case GUI_EVENT_KEY_1: - // TODO - switch_screen(screen_cyklus, true); // fake cycle - break; + if (first == -1) { + first = i; + } + + uint16_t m = app_config.schedules[i].h * 60 + app_config.schedules[i].m; + if (m >= m_now) { + found = i; + break; + } + } - case GUI_EVENT_KEY_2: - // TODO + if (found == -1) { + found = first; + } + + if (found == -1) { + LcdBuffer_Write(&lcd, 1, 0, "Program nenastaven!"); + } 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í"); break; case GUI_EVENT_KEY_A: + switch_screen(screen_cyklus, true); // fake cycle + break; + + case GUI_EVENT_KEY_B: switch_screen(screen_settings, true); break; } diff --git a/src/screens/screen_moisture_calib.c b/src/screens/screen_moisture_calib.c index ed05be4..c3e3bc2 100644 --- a/src/screens/screen_moisture_calib.c +++ b/src/screens/screen_moisture_calib.c @@ -5,8 +5,6 @@ #include "app_config.h" static int phase = 0; -static int tick_counter = 0; -static uint16_t last_moisture = 0; static uint16_t sample_dry = 0; @@ -15,15 +13,10 @@ void screen_moisture_calib(GuiEvent event) switch (event) { case GUI_EVENT_SCREEN_INIT: phase = 0; - tick_counter = 0; // pass through - case GUI_EVENT_SCREEN_TICK_10MS: - tick_counter++; - if (tick_counter == 25) { - request_paint(); // will read new measurement - tick_counter = 0; - } - last_moisture = moisture_read(); + case GUI_EVENT_SCREEN_TICK_100MS: + gui_read_moisture(); + request_paint(); // will read new measurement break; case GUI_EVENT_PAINT: @@ -35,7 +28,7 @@ void screen_moisture_calib(GuiEvent event) LcdBuffer_Write(&lcd, 1, 0, "půdy (vlhkost 100%)"); } - snprintf(sbuf, sbuf_len, "Vlhkost %4d/4095", last_moisture); + snprintf(sbuf, sbuf_len, "Vlhkost %4d/4095", s_app.moisture_raw); LcdBuffer_Write(&lcd, 2, 0, sbuf); LcdBuffer_Write(&lcd, 3, 0, "🅳Zrušit 🅰Potvrdit"); @@ -43,12 +36,12 @@ void screen_moisture_calib(GuiEvent event) case GUI_EVENT_KEY_A: // Confirm if (phase == 0) { - sample_dry = last_moisture; + sample_dry = s_app.moisture_raw; phase++; LcdBuffer_Clear(&lcd); // make sure there isnt overlap with previous step } else { settings_scratch.moisture_dry = sample_dry; - settings_scratch.moisture_wet = last_moisture; + settings_scratch.moisture_wet = s_app.moisture_raw; switch_screen(screen_settings, false); } break; diff --git a/src/screens/screen_program_edit.c b/src/screens/screen_program_edit.c index 0ef6c5b..8ca8f27 100644 --- a/src/screens/screen_program_edit.c +++ b/src/screens/screen_program_edit.c @@ -59,6 +59,12 @@ void screen_program_edit(GuiEvent event) default: if (event >= '0' && event <= '9') { + if (!time.enable) { + time.enable = true; + // make sure there is no old content + LcdBuffer_Clear(&lcd); + } + if (cursor == 0) { // it showed the current time, but now we are entering something, so it must be reset! time.h = 0;