229 lines
7.8 KiB
C

/*
* 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 <err.h>
#include <string.h>
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;
}