/* * statsby.c - Screen for view stats by a specified period (day, week, etc.) * 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 "statsby.h" #include #include static void stats_by_select_period(StatsByScreen *screen, SensorState *state) { if (state->up_down) { screen->period = (screen->period + 1) % NPERIOD; screen->need_redraw = true; } if (state->down_down) { if (--screen->period < 0) { screen->period = NPERIOD - 1; } screen->need_redraw = true; } if (screen->need_redraw) { lcd_clear(state->lcd); lcd_move_to(state->lcd, 0, 0); lcd_write_string(state->lcd, "Period:"); lcd_move_to(state->lcd, 1, 0); lcd_write_string(state->lcd, ">"); lcd_write_string(state->lcd, PERIOD_LABELS[screen->period]); lcd_move_to(state->lcd, 1, 0); lcd_display_control(state->lcd, LCD_CURSOR_BLINK, LCD_CURSOR_ON, LCD_DISPLAY_ON); screen->need_redraw = false; } if (state->sel_down) { ++screen->stage; date_sel_set_period(&screen->ds, screen->period); screen->need_redraw = true; } } static void stats_by_select_start(StatsByScreen *screen, SensorState *state) { if (state->up_down || state->down_down || state->sel_down || state->back_down) { screen->need_redraw = true; } if (screen->need_redraw) { screen->need_redraw = false; DateSelectionState dss = date_sel_dispatch(&screen->ds, state, "Start:"); switch (dss) { case DATE_SEL_CONTINUE: break; // ignore case DATE_SEL_DONE: ++screen->stage; screen->offset_scale = 0; screen->need_redraw = true; screen->blimit_reached = false; screen->ulimit_reached = false; break; case DATE_SEL_BACK: case DATE_SEL_ERROR: --screen->stage; screen->need_redraw = true; break; } } } static void stats_by_draw_current_view (StatsByScreen *screen, SensorState *state, UtilAveragePeriod *data) { char desc_string[17]; char data_string[17]; char humid_str[6]; switch (screen->view) { case STATS_BY_AVG: // switch-ception switch (screen->period) { case PERIOD_YEAR: snprintf(desc_string, 17, "Year>%d", data->year); break; case PERIOD_MONTH: snprintf(desc_string, 17, "Month>%d-%d", data->year, data->month); break; case PERIOD_WEEK: snprintf(desc_string, 17, "Week>%d-%d-%d", data->year, data->month, data->day); break; case PERIOD_DAY: snprintf(desc_string, 17, "Day>%d-%d-%d", data->year, data->month, data->day); break; case PERIOD_HOUR: snprintf(desc_string, 17, "Hour>%d-%d %d:00", data->month, data->day, data->hour); break; } if (data->npoints) { // prevent UB snprintf(data_string, 17, "T:%.1f%c H:%d%%", convert_temperature(data->avgtemp), GLOBAL_OPTS.temp_unit, data->avghumid); } break; case STATS_BY_TEMP: strcpy(desc_string, "Max Min"); if (data->npoints) { // prevent UB snprintf(data_string, 17, "%-4.1f%c %.1f%c", convert_temperature(data->maxtemp), GLOBAL_OPTS.temp_unit, convert_temperature(data->mintemp), GLOBAL_OPTS.temp_unit); } break; case STATS_BY_HUMID: if (data->npoints) { // prevent UB strcpy(desc_string, "Max Min"); snprintf(data_string, 17, "%s %d%%", pad_humid_str(data->maxhumid, humid_str, 6), data->minhumid); } break; default: warnx("attempt to draw bad stats-by view"); --screen->stage; screen->need_redraw = true; break; } lcd_move_to(state->lcd, 0, 0); lcd_write_string(state->lcd, desc_string); lcd_move_to(state->lcd, 1, 0); if (data->npoints) { lcd_write_string(state->lcd, data_string); } else { lcd_write_string(state->lcd, "No data!"); } } static void stats_by_show_stats(StatsByScreen *screen, SensorState *state) { if (state->back_down) { screen->stage = STATS_BY_SELECT_PERIOD; screen->need_redraw = true; screen->ds.stage = DATE_SEL_YEAR; screen->view = STATS_BY_AVG; return; } if (state->sel_down) { screen->view = (screen->view + 1) % STATS_BY_NVIEW; screen->need_redraw = true; } if (screen->need_redraw) { screen->need_redraw = false; lcd_display_control(state->lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF, LCD_DISPLAY_ON); lcd_clear(state->lcd); if (screen->period == PERIOD_HOUR && screen->offset_scale == 0 && screen->ds.year == screen->ds.start_time.local_year && screen->ds.month == screen->ds.start_time.local_month && screen->ds.day == screen->ds.start_time.local_day) { screen->offset_scale = screen->ds.start_time.local_hour; } UtilAveragePeriod data; if (!get_average_for_period(state->db, screen->ds.year, screen->ds.month, screen->ds.day, screen->offset_scale, screen->period, &data)) { lcd_write_string(state->lcd, "Query failed!"); warnx("failed to query average temperature and humidity"); return; } screen->ulimit_reached = data.upper_bound; screen->blimit_reached = data.lower_bound; stats_by_draw_current_view(screen, state, &data); } if (!screen->ulimit_reached && state->up_down) { ++screen->offset_scale; screen->need_redraw = true; } if (!screen->blimit_reached && state->down_down) { --screen->offset_scale; screen->need_redraw = true; } } static bool stats_by_screen_dispatch(StatsByScreen *screen, SensorState *state) { if (state->force_draw) { screen->need_redraw = true; } if (state->back_down) { if (screen->stage == STATS_BY_SELECT_PERIOD) { screen->period = PERIOD_HOUR; screen->need_redraw = true; lcd_display_control(state->lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF, LCD_DISPLAY_ON); date_sel_reset(&screen->ds); return true; } } switch (screen->stage) { case STATS_BY_SELECT_PERIOD: stats_by_select_period(screen, state); break; case STATS_BY_SELECT_START: stats_by_select_start(screen, state); break; case STATS_BY_SHOWING: stats_by_show_stats(screen, state); break; default: LOG_VERBOSE("Attempt to show bad stats by screen\n"); return true; } return false; } StatsByScreen *stats_by_screen_new() { StatsByScreen *s = malloc_checked(sizeof(StatsByScreen)); screen_init(&s->parent, "Stats by", (ScreenDispatchFunc) stats_by_screen_dispatch, (ScreenCleanupFunc) free); s->need_redraw = true; s->stage = STATS_BY_SELECT_PERIOD; s->period = PERIOD_HOUR; s->view = STATS_BY_AVG; date_sel_init(&s->ds, PERIOD_DAY); return s; }