diff --git a/src/ui/screen.c b/src/ui/screen.c index 91aaf3e..1fb50f3 100644 --- a/src/ui/screen.c +++ b/src/ui/screen.c @@ -148,7 +148,7 @@ static bool stats_screen_dispatch(StatsScreen *screen, SensorState *state) { lcd_clear(state->lcd); lcd_write_string(state->lcd, "temp humi time"); char buff[17]; - int cur_len = snprintf(buff, sizeof(buff), "%.1f%c %3" PRIu32 "%% ", + int cur_len = snprintf(buff, sizeof(buff), "%-4.1f%c %3" PRIu32 "%% ", convert_temperature(state->temp), GLOBAL_OPTS.temp_unit, state->humid); struct tm lt; diff --git a/src/ui/statrange.c b/src/ui/statrange.c index 4eb0010..ea2abc8 100644 --- a/src/ui/statrange.c +++ b/src/ui/statrange.c @@ -9,6 +9,7 @@ */ #include "statrange.h" +#include #include #include #include @@ -92,6 +93,10 @@ static void stat_range_show_data(StatRangeScreen *screen, SensorState *state) { screen->stage = STAT_RANGE_START_DATE; return; } + if (state->sel_down) { + screen->need_redraw = true; + screen->view = (screen->view + 1) % STAT_RANGE_NVIEW; + } if (screen->need_redraw) { screen->need_redraw = false; uint64_t start = to_uts_timestamp(&screen->sds, &screen->sts); @@ -108,17 +113,45 @@ static void stat_range_show_data(StatRangeScreen *screen, SensorState *state) { --screen->stage; return; } + char desc_line[17]; + char data_line[17]; + char humid_str[6]; + switch (screen->view) { + case STAT_RANGE_AVG: + snprintf(desc_line, 17, "%" PRIi64 "pts:", data.npoints); + if (data.npoints) { // prevent UB + snprintf(data_line, 17, "%.1f%c %d%%", + convert_temperature(data.avgtemp), + GLOBAL_OPTS.temp_unit, data.avghumid); + } + break; + case STAT_RANGE_TEMP: + strcpy(desc_line, "Max Min"); + if (data.npoints) { // prevent UB + snprintf(data_line, 17, "%-4.1f%c %.1f%c", + convert_temperature(data.maxtemp), GLOBAL_OPTS.temp_unit, + convert_temperature(data.mintemp), GLOBAL_OPTS.temp_unit); + } + break; + case STAT_RANGE_HUMID: + strcpy(desc_line, "Max Min"); + if (data.npoints) { // prevent UB + snprintf(data_line, 17, "%s %d%%", + pad_humid_str(data.maxhumid, humid_str, 6), + data.minhumid); + } + break; + default: + warnx("attempt to draw bad stat range view"); + --screen->stage; + screen->need_redraw = true; + break; + } lcd_clear(state->lcd); lcd_move_to(state->lcd, 0, 0); - char points_line[17]; - snprintf(points_line, 17, "%" PRIi64 "pts:", data.npoints); - lcd_write_string(state->lcd, points_line); + lcd_write_string(state->lcd, desc_line); lcd_move_to(state->lcd, 1, 0); if (data.npoints) { - char data_line[17]; - snprintf(data_line, 17, "%.1f%c %d%%", - convert_temperature(data.temp), - GLOBAL_OPTS.temp_unit, data.humid); lcd_write_string(state->lcd, data_line); } else { lcd_write_string(state->lcd, "No data!"); @@ -134,6 +167,7 @@ static bool stat_range_screen_dispatch(StatRangeScreen *screen, switch (screen->stage) { case STAT_RANGE_START_DATE: if(stat_range_handle_date(screen, &screen->sds, state, "Start Date:")) { + screen->view = STAT_RANGE_AVG; date_sel_reset(&screen->sds); time_sel_reset(&screen->sts); date_sel_reset(&screen->eds); @@ -166,6 +200,7 @@ StatRangeScreen *stat_range_screen_new(void) { (ScreenCleanupFunc) free); s->need_redraw = true; s->stage = STAT_RANGE_START_DATE; + s->view = STAT_RANGE_AVG; date_sel_init(&s->sds, PERIOD_DAY); time_sel_init(&s->sts, &s->sds, true); date_sel_init(&s->eds, PERIOD_DAY); diff --git a/src/ui/statrange.h b/src/ui/statrange.h index c99e62c..8879c70 100644 --- a/src/ui/statrange.h +++ b/src/ui/statrange.h @@ -22,6 +22,12 @@ typedef struct { STAT_RANGE_END_TIME, STAT_RANGE_SHOW } stage; + enum { + STAT_RANGE_AVG = 0, + STAT_RANGE_TEMP, + STAT_RANGE_HUMID, + STAT_RANGE_NVIEW + } view; bool need_redraw; DateSelection sds; TimeSelection sts; diff --git a/src/ui/statsby.c b/src/ui/statsby.c index 1fe6109..990c56a 100644 --- a/src/ui/statsby.c +++ b/src/ui/statsby.c @@ -10,6 +10,7 @@ #include "statsby.h" #include +#include static void stats_by_select_period(StatsByScreen *screen, SensorState *state) { if (state->up_down) { @@ -67,17 +68,91 @@ static void stats_by_select_start(StatsByScreen *screen, SensorState *state) { } } +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; - } else if (screen->need_redraw) { + 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); - lcd_move_to(state->lcd, 0, 0); if (screen->period == PERIOD_HOUR && screen->offset_scale == 0 && screen->ds.year == screen->ds.start_time.local_year && @@ -95,39 +170,7 @@ static void stats_by_show_stats(StatsByScreen *screen, SensorState *state) { } screen->ulimit_reached = data.upper_bound; screen->blimit_reached = data.lower_bound; - char period_string[17]; - switch (screen->period) { - case PERIOD_YEAR: - snprintf(period_string, 17, "Year>%d", data.year); - break; - case PERIOD_MONTH: - snprintf(period_string, 17, "Month>%d-%d", data.year, - data.month); - break; - case PERIOD_WEEK: - snprintf(period_string, 17, "Week>%d-%d-%d", data.year, - data.month, data.day); - break; - case PERIOD_DAY: - snprintf(period_string, 17, "Day>%d-%d-%d", data.year, - data.month, data.day); - break; - case PERIOD_HOUR: - snprintf(period_string, 17, "Hour>%d-%d %d:00", data.month, - data.day, data.hour); - break; - } - lcd_write_string(state->lcd, period_string); - lcd_move_to(state->lcd, 1, 0); - if (data.npoints) { - char data_string[17]; - snprintf(data_string, 17, "T:%.1f%c H:%d%%", - convert_temperature(data.temp), GLOBAL_OPTS.temp_unit, - data.humid); - lcd_write_string(state->lcd, data_string); - } else { - lcd_write_string(state->lcd, "No data!"); - } + stats_by_draw_current_view(screen, state, &data); } if (!screen->ulimit_reached && state->up_down) { ++screen->offset_scale; @@ -178,6 +221,7 @@ StatsByScreen *stats_by_screen_new() { 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; } diff --git a/src/ui/statsby.h b/src/ui/statsby.h index 376eb25..7c9b6da 100644 --- a/src/ui/statsby.h +++ b/src/ui/statsby.h @@ -19,6 +19,12 @@ typedef struct { STATS_BY_SELECT_START, STATS_BY_SHOWING, } stage; + enum { + STATS_BY_AVG = 0, + STATS_BY_TEMP, + STATS_BY_HUMID, + STATS_BY_NVIEW, + } view; bool need_redraw; UtilPeriod period; DateSelection ds; diff --git a/src/util.c b/src/util.c index b88769d..e1d5cc4 100644 --- a/src/util.c +++ b/src/util.c @@ -129,13 +129,21 @@ static const char *AVG_FOR_PERIOD_QUERY_STR = "etime >= (select max(time) from env_data) as ulimit,\n" "stime <= (select min(time) from env_data) as blimit,\n" "atemp,\n" - "ahumid\n" + "ahumid,\n" + "maxtemp,\n" + "mintemp,\n" + "maxhumid,\n" + "minhumid\n" "FROM (SELECT\n" "unixepoch(printf('%04d-%02d-%02d', ?1, ?2, ?3), 'utc', printf('%d %s', ?4 * ?6, ?5)) as stime,\n" "unixepoch(printf('%04d-%02d-%02d', ?1, ?2, ?3), 'utc', printf('%d %s', (?4 + 1) * ?6, ?5)) as etime,\n" "count(time) AS ndata,\n" "round(avg(temp)) AS atemp,\n" - "round(avg(humid)) AS ahumid\n" + "round(avg(humid)) AS ahumid,\n" + "max(temp) AS maxtemp,\n" + "min(temp) AS mintemp,\n" + "max(humid) AS maxhumid,\n" + "min(humid) AS minhumid\n" "FROM env_data\n" "WHERE time >= stime\n" "AND time <= etime);\n"; @@ -145,10 +153,11 @@ static const char *DATA_POINT_QUERY_STR = "UNION ALL\n" "SELECT min(time) IS NULL, min(time), temp, humid FROM env_data WHERE time > ?1\n" "UNION ALL\n" - "SELECT false, * FROM env_data WHERE time == ?1;"; + "SELECT false, time, temp, humid FROM env_data WHERE time == ?1;"; static sqlite3_stmt *DATA_POINT_QUERY; static const char *AVG_FOR_RANGE_QUERY_STR = - "SELECT count(time), round(avg(temp)), round(avg(humid)) FROM env_data\n" + "SELECT count(time), round(avg(temp)), round(avg(humid)),\n" + " max(temp), min(temp), max(humid), min(humid) FROM env_data\n" "WHERE time >= ?1 AND time <= ?2;"; static sqlite3_stmt *AVG_FOR_RANGE_QUERY; void initialize_util_queries(sqlite3 *db) { @@ -257,8 +266,12 @@ bool get_average_for_period(sqlite3 *db, int year, int month, int day, data->npoints = sqlite3_column_int64(AVG_FOR_PERIOD_QUERY, 4); data->upper_bound = sqlite3_column_int64(AVG_FOR_PERIOD_QUERY, 5); data->lower_bound = sqlite3_column_int64(AVG_FOR_PERIOD_QUERY, 6); - data->temp = sqlite3_column_int(AVG_FOR_PERIOD_QUERY, 7); - data->humid = sqlite3_column_int(AVG_FOR_PERIOD_QUERY, 8); + data->avgtemp = sqlite3_column_int(AVG_FOR_PERIOD_QUERY, 7); + data->avghumid = sqlite3_column_int(AVG_FOR_PERIOD_QUERY, 8); + data->maxtemp = sqlite3_column_int(AVG_FOR_PERIOD_QUERY, 9); + data->mintemp = sqlite3_column_int(AVG_FOR_PERIOD_QUERY, 10); + data->maxhumid = sqlite3_column_int(AVG_FOR_PERIOD_QUERY, 11); + data->minhumid = sqlite3_column_int(AVG_FOR_PERIOD_QUERY, 12); } sqlite3_reset(AVG_FOR_PERIOD_QUERY); return success; @@ -325,8 +338,12 @@ bool get_average_for_range(sqlite3 *db, uint64_t start, uint64_t end, success = false; } else if (data) { data->npoints = sqlite3_column_int64(AVG_FOR_RANGE_QUERY, 0); - data->temp = sqlite3_column_int(AVG_FOR_RANGE_QUERY, 1); - data->humid = sqlite3_column_int(AVG_FOR_RANGE_QUERY, 2); + data->avgtemp = sqlite3_column_int(AVG_FOR_RANGE_QUERY, 1); + data->avghumid = sqlite3_column_int(AVG_FOR_RANGE_QUERY, 2); + data->maxtemp = sqlite3_column_int(AVG_FOR_RANGE_QUERY, 3); + data->mintemp = sqlite3_column_int(AVG_FOR_RANGE_QUERY, 4); + data->maxhumid = sqlite3_column_int(AVG_FOR_RANGE_QUERY, 5); + data->minhumid = sqlite3_column_int(AVG_FOR_RANGE_QUERY, 6); } sqlite3_reset(AVG_FOR_RANGE_QUERY); return success; @@ -352,3 +369,13 @@ float convert_temperature(int dk) { return 0.0f; } } + +char *pad_humid_str(int humid, char *buf, size_t buf_size) { + int fmt_len = snprintf(buf, buf_size, "%d%%", humid); + ssize_t remaining = buf_size - fmt_len - 1; + if (remaining > 0) { + memset(buf + fmt_len, ' ', remaining); + buf[buf_size - 1] = '\0'; + } + return buf; +} diff --git a/src/util.h b/src/util.h index a663e24..1f78ad6 100644 --- a/src/util.h +++ b/src/util.h @@ -138,8 +138,12 @@ bool get_database_limits(sqlite3 *db, UtilPeriod period, UtilDate *start, typedef struct { int64_t npoints; - int temp; - int humid; + int avgtemp; + int avghumid; + int maxtemp; + int maxhumid; + int mintemp; + int minhumid; int year; int month; int day; @@ -154,8 +158,8 @@ typedef struct { * Return: false if an error occurred, true otherwise. */ bool get_average_for_period(sqlite3 *db, int year, int month, int day, - int64_t count, UtilPeriod period, - UtilAveragePeriod *data); + int64_t count, UtilPeriod period, + UtilAveragePeriod *data); typedef struct { int64_t time; @@ -180,8 +184,12 @@ bool get_data_point_info(sqlite3 *db, int64_t time, UtilDataPointInfo *info); typedef struct { int64_t npoints; - int temp; - int humid; + int avgtemp; + int avghumid; + int maxtemp; + int mintemp; + int maxhumid; + int minhumid; } UtilAverageRange; /* @@ -196,4 +204,10 @@ bool get_average_for_range(sqlite3 *db, uint64_t start, uint64_t end, */ float convert_temperature(int dk); +/* + * Fill BUF with at most BUF_SIZE characters (including the null byte), by + * padding "HUMID%" to BUF_SIZE - 1 characters. + */ +char *pad_humid_str(int humid, char *buf, size_t buf_size); + #endif