/* * screen.c - Simple menu system * Copyright (C) 2024 Alexander Rosenberg * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation, either version 3 of the License, or (at your option) any later * version. See the LICENSE file for more information. */ #include "screen.h" #include #include #include #include void screen_init(Screen *screen, const char *name, ScreenDispatchFunc dispatch_func, ScreenCleanupFunc cleanup_func) { screen->dispatch_func = dispatch_func; screen->cleanup_func = cleanup_func; screen->name = strdup_checked(name); } void screen_delete(Screen *screen) { free(screen->name); if (screen->cleanup_func) { screen->cleanup_func(screen); } } ScreenManager *screen_manager_new(sqlite3 *db, LCD *lcd, gpio_handle_t handle, gpio_pin_t back_pin, gpio_pin_t up_pin, gpio_pin_t down_pin, gpio_pin_t sel_pin) { ScreenManager *sm = malloc_checked(sizeof(ScreenManager)); sm->db = db; sm->lcd = lcd; sm->screens = NULL; sm->screen_count = 0; sm->cur_screen = 0; sm->up_btn = button_new(handle, up_pin); sm->down_btn = button_new(handle, down_pin); sm->back_btn = button_new(handle, back_pin); sm->sel_btn = button_new(handle, sel_pin); sm->cur_menu_line = 0; sm->last_drawn_menu_line = 0; sm->force_draw = true; return sm; } void screen_manager_delete(ScreenManager *sm) { for (size_t i = 0; i < sm->screen_count; ++i) { screen_delete(sm->screens[i]); } free(sm->screens); button_delete(sm->up_btn); button_delete(sm->down_btn); button_delete(sm->back_btn); button_delete(sm->sel_btn); free(sm); } void screen_manager_add(ScreenManager *sm, Screen *screen) { sm->screens = realloc_checked(sm->screens, sizeof(Screen *) * ++sm->screen_count); sm->screens[sm->screen_count - 1] = screen; } static void screen_manager_dispatch_select_menu(ScreenManager *sm) { if (button_pressed(sm->up_btn) && sm->cur_menu_line != 0) { --sm->cur_menu_line; } if (button_pressed(sm->down_btn) && sm->cur_menu_line < sm->screen_count - 1) { ++sm->cur_menu_line; } // poll the button, but don't do anything with the state button_pressed(sm->back_btn); if (sm->cur_menu_line != sm->last_drawn_menu_line || sm->force_draw) { sm->force_draw = false; sm->last_drawn_menu_line = sm->cur_menu_line; lcd_clear(sm->lcd); lcd_move_to(sm->lcd, 0, 0); if (sm->screen_count == 1) { lcd_write_char(sm->lcd, '*'); lcd_write_string(sm->lcd, sm->screens[0]->name); } else if (sm->cur_menu_line >= sm->screen_count - 1) { lcd_write_char(sm->lcd, ' '); lcd_write_string(sm->lcd, sm->screens[sm->screen_count - 2]->name); lcd_move_to(sm->lcd, 1, 0); lcd_write_char(sm->lcd, '*'); lcd_write_string(sm->lcd, sm->screens[sm->screen_count - 1]->name); } else { lcd_write_char(sm->lcd, '*'); lcd_write_string(sm->lcd, sm->screens[sm->cur_menu_line]->name); lcd_move_to(sm->lcd, 1, 0); lcd_write_char(sm->lcd, ' '); lcd_write_string(sm->lcd, sm->screens[sm->cur_menu_line + 1]->name); } } if (button_pressed(sm->sel_btn)) { sm->cur_screen = sm->cur_menu_line; sm->force_draw = true; } } void screen_manager_dispatch(ScreenManager *sm, uint32_t temp, uint32_t humid) { if (!sm->screen_count) { errx(1, "attempt to display empty screen manager"); } if (sm->cur_screen < 0) { screen_manager_dispatch_select_menu(sm); } else { Screen *cs = sm->screens[sm->cur_screen]; if (cs->dispatch_func) { SensorState state = { .lcd = sm->lcd, .db = sm->db, .temp = temp, .humid = humid, .back_down = button_pressed(sm->back_btn), .up_down = button_pressed(sm->up_btn), .down_down = button_pressed(sm->down_btn), .sel_down = button_pressed(sm->sel_btn), .force_draw = sm->force_draw, }; if (cs->dispatch_func(cs, &state)) { // if this is true, it means return to the main menu sm->cur_screen = -1; sm->force_draw = true; } else { sm->force_draw = false; } } } } static bool stats_screen_dispatch(StatsScreen *screen, SensorState *state) { if (state->back_down) { return true; } time_t cur_time = time(NULL); if (state->force_draw || state->temp != screen->last_temp || state->humid != screen->last_humid || screen->last_min != cur_time / 60) { screen->last_temp = state->temp; screen->last_humid = state->humid; screen->last_min = cur_time / 60; lcd_clear(state->lcd); lcd_write_string(state->lcd, "temp humi time"); char buff[17]; int cur_len; if (state->temp == UINT32_MAX || state->humid == UINT32_MAX) { cur_len = snprintf(buff, sizeof(buff), "ERROR! "); } else { cur_len = snprintf(buff, sizeof(buff), "%-4.1f%c %3" PRIu32 "%% ", convert_temperature(state->temp), GLOBAL_OPTS.temp_unit, state->humid); } struct tm lt; localtime_r(&cur_time, <); strftime(buff + cur_len, sizeof(buff) - cur_len, "%H:%M", <); lcd_move_to(state->lcd, 1, 0); lcd_write_string(state->lcd, buff); } return false; } StatsScreen *stats_screen_new() { StatsScreen *s = malloc_checked(sizeof(StatsScreen)); screen_init(&s->parent, "Current Stats", (ScreenDispatchFunc) stats_screen_dispatch, (ScreenCleanupFunc) free); s->last_humid = 0; s->last_temp = 0; return s; }