Add stat range menu
This commit is contained in:
		
							
								
								
									
										16
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								Makefile
									
									
									
									
									
								
							| @ -6,7 +6,8 @@ CFLAGS=-g -std=c11 -Wall ${SQLITE3_CFLAGS} | |||||||
| LD=clang | LD=clang | ||||||
| LDFLAGS=-lgpio -lfigpar -lpthread ${SQLITE3_LDFLAGS} | LDFLAGS=-lgpio -lfigpar -lpthread ${SQLITE3_LDFLAGS} | ||||||
| SRCS=src/main.c src/util.c src/lcd.c src/ths.c src/button.c src/ui/screen.c\ | SRCS=src/main.c src/util.c src/lcd.c src/ths.c src/button.c src/ui/screen.c\ | ||||||
|      src/ui/datesel.c src/ui/statsby.c src/ui/datapoints.c src/ui/timesel.c |      src/ui/datesel.c src/ui/statsby.c src/ui/datapoints.c src/ui/timesel.c\ | ||||||
|  |      src/ui/statrange.c | ||||||
| PROG=rpi4b-temp-humidity | PROG=rpi4b-temp-humidity | ||||||
|  |  | ||||||
| OBJS=${SRCS:C/^src/bin/:C/.c$/.o/} | OBJS=${SRCS:C/^src/bin/:C/.c$/.o/} | ||||||
| @ -15,24 +16,29 @@ bin/${PROG}: ${OBJS} | |||||||
|  |  | ||||||
| bin/main.o bin/util.o bin/lcd.o bin/ths.o bin/button.o: src/util.h | bin/main.o bin/util.o bin/lcd.o bin/ths.o bin/button.o: src/util.h | ||||||
| bin/ui/screen.o bin/ui/statsby.o bin/ui/datesel.o: src/util.h | bin/ui/screen.o bin/ui/statsby.o bin/ui/datesel.o: src/util.h | ||||||
| bin/ui/datapoints.o bin/ui/timesel.o: src/util.h | bin/ui/datapoints.o bin/ui/timesel.o bin/ui/statrange.o: src/util.h | ||||||
|  |  | ||||||
| bin/main.o bin/lcd.o bin/screen.o bin/ui/datesel.o: src/lcd.h | bin/main.o bin/lcd.o bin/screen.o bin/ui/datesel.o: src/lcd.h | ||||||
| bin/ui/statsby.o bin/ui/timesel.o: src/lcd.h | bin/ui/statsby.o bin/ui/timesel.o bin/ui/statrange.o: src/lcd.h | ||||||
|  | bin/ui/datapoints.o: src/lcd.h | ||||||
|  |  | ||||||
| bin/main.o bin/ui/screen.o bin/ui/statsby.o: src/ui/screen.h | bin/main.o bin/ui/screen.o bin/ui/statsby.o: src/ui/screen.h | ||||||
| bin/ui/datesel.o bin/ui/datapoints.o bin/ui/timesel.o: src/ui/screen.h | bin/ui/datesel.o bin/ui/datapoints.o bin/ui/timesel.o: src/ui/screen.h | ||||||
|  | bin/ui/statrange.o: src/ui/screen.h | ||||||
|  |  | ||||||
| bin/main.o bin/ui/datesel.o bin/ui/statsby.o: src/ui/datesel.h | bin/main.o bin/ui/datesel.o bin/ui/statsby.o: src/ui/datesel.h | ||||||
| bin/ui/datapoints.o bin/ui/timesel.o: src/ui/datesel.h | bin/ui/datapoints.o bin/ui/timesel.o bin/ui/statrange.o: src/ui/datesel.h | ||||||
|  |  | ||||||
| bin/main.o bin/ths.o bin/ui/screen.o bin/ui/statsby.o: src/ths.h | bin/main.o bin/ths.o bin/ui/screen.o bin/ui/statsby.o: src/ths.h | ||||||
| bin/ui/timesel.o: src/ths.h | bin/ui/timesel.o src/ui/statrange.o: src/ths.h | ||||||
|  |  | ||||||
| bin/main.o bin/ui/timesel.o bin/ui/datapoints.o: src/ui/timesel.h | bin/main.o bin/ui/timesel.o bin/ui/datapoints.o: src/ui/timesel.h | ||||||
|  | bin/ui/statrange.o: src/ui/timesel.h | ||||||
|  |  | ||||||
| bin/main.o bin/button.o bin/ui/screen.o: src/button.h | bin/main.o bin/button.o bin/ui/screen.o: src/button.h | ||||||
| bin/main.o bin/ui/statsby.o: src/ui/statsby.h | bin/main.o bin/ui/statsby.o: src/ui/statsby.h | ||||||
| bin/main.o bin/ui/datapoints.o: src/ui/datapoints.h | bin/main.o bin/ui/datapoints.o: src/ui/datapoints.h | ||||||
|  | bin/main.o bin/ui/statrange.o: src/ui/statrange.h | ||||||
|  |  | ||||||
| ${OBJS}: ${.TARGET:C/^bin/src/:C/.o$/.c/} | ${OBJS}: ${.TARGET:C/^bin/src/:C/.o$/.c/} | ||||||
| 	@mkdir -p ${.TARGET:H} | 	@mkdir -p ${.TARGET:H} | ||||||
|  | |||||||
| @ -15,6 +15,7 @@ | |||||||
| #include "ui/screen.h" | #include "ui/screen.h" | ||||||
| #include "ui/statsby.h" | #include "ui/statsby.h" | ||||||
| #include "ui/datapoints.h" | #include "ui/datapoints.h" | ||||||
|  | #include "ui/statrange.h" | ||||||
|  |  | ||||||
| #include <unistd.h> | #include <unistd.h> | ||||||
| #include <err.h> | #include <err.h> | ||||||
| @ -105,6 +106,7 @@ int main(int argc, char *const *argv) { | |||||||
|     screen_manager_add(screen_manager, (Screen *) stats_screen_new()); |     screen_manager_add(screen_manager, (Screen *) stats_screen_new()); | ||||||
|     screen_manager_add(screen_manager, (Screen *) stats_by_screen_new()); |     screen_manager_add(screen_manager, (Screen *) stats_by_screen_new()); | ||||||
|     screen_manager_add(screen_manager, (Screen *) data_points_screen_new()); |     screen_manager_add(screen_manager, (Screen *) data_points_screen_new()); | ||||||
|  |     screen_manager_add(screen_manager, (Screen *) stat_range_screen_new()); | ||||||
|     while (RUNNING) { |     while (RUNNING) { | ||||||
|         lock_stat_globals(); |         lock_stat_globals(); | ||||||
|         uint32_t temp = LAST_TEMP; |         uint32_t temp = LAST_TEMP; | ||||||
|  | |||||||
| @ -21,8 +21,7 @@ static bool data_points_select_date(DataPointsScreen *screen, | |||||||
|     } |     } | ||||||
|     if (screen->need_redraw) { |     if (screen->need_redraw) { | ||||||
|         screen->need_redraw = false; |         screen->need_redraw = false; | ||||||
|         DateSelectionState dss = date_sel_dispatch(&screen->ds, state, |         DateSelectionState dss = date_sel_dispatch(&screen->ds, state, "Date:"); | ||||||
|                                                    PERIOD_DAY, "Date:"); |  | ||||||
|         switch (dss) { |         switch (dss) { | ||||||
|         case DATE_SEL_CONTINUE: |         case DATE_SEL_CONTINUE: | ||||||
|             break; // ignore |             break; // ignore | ||||||
| @ -33,6 +32,8 @@ static bool data_points_select_date(DataPointsScreen *screen, | |||||||
|         case DATE_SEL_BACK: |         case DATE_SEL_BACK: | ||||||
|         case DATE_SEL_ERROR: |         case DATE_SEL_ERROR: | ||||||
|             screen->need_redraw = true; |             screen->need_redraw = true; | ||||||
|  |             date_sel_reset(&screen->ds); | ||||||
|  |             time_sel_reset(&screen->ts); | ||||||
|             return true; |             return true; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @ -65,7 +66,7 @@ static void data_points_select_time(DataPointsScreen *screen, | |||||||
|     } |     } | ||||||
|     if (screen->need_redraw) { |     if (screen->need_redraw) { | ||||||
|         screen->need_redraw = false; |         screen->need_redraw = false; | ||||||
|         TimeSelState tss = time_sel_dispatch(&screen->ts, state); |         TimeSelState tss = time_sel_dispatch(&screen->ts, state, "Time:"); | ||||||
|         switch (tss) { |         switch (tss) { | ||||||
|         case TIME_SEL_CONTINUE: |         case TIME_SEL_CONTINUE: | ||||||
|             break; // ignore |             break; // ignore | ||||||
| @ -85,6 +86,8 @@ static void data_points_select_time(DataPointsScreen *screen, | |||||||
| static void data_points_show(DataPointsScreen *screen, SensorState *state) { | static void data_points_show(DataPointsScreen *screen, SensorState *state) { | ||||||
|     if (state->back_down) { |     if (state->back_down) { | ||||||
|         screen->stage = DP_STAGE_DATE; |         screen->stage = DP_STAGE_DATE; | ||||||
|  |         screen->ds.stage = DATE_SEL_YEAR; | ||||||
|  |         screen->ts.stage = TS_STAGE_HOUR; | ||||||
|         screen->need_redraw = true; |         screen->need_redraw = true; | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
| @ -158,7 +161,7 @@ DataPointsScreen *data_points_screen_new() { | |||||||
|                 (ScreenCleanupFunc) free); |                 (ScreenCleanupFunc) free); | ||||||
|     s->need_redraw = true; |     s->need_redraw = true; | ||||||
|     s->stage = DP_STAGE_DATE; |     s->stage = DP_STAGE_DATE; | ||||||
|     date_sel_init(&s->ds, DATE_SEL_DAY); |     date_sel_init(&s->ds, PERIOD_DAY); | ||||||
|     time_sel_init(&s->ts, &s->ds); |     time_sel_init(&s->ts, &s->ds, false); | ||||||
|     return s; |     return s; | ||||||
| } | } | ||||||
|  | |||||||
| @ -12,10 +12,10 @@ | |||||||
| #include <string.h> | #include <string.h> | ||||||
| #include <err.h> | #include <err.h> | ||||||
|  |  | ||||||
| void date_sel_init(DateSelection *ds, DateSelectionStage max_stage) { | void date_sel_init(DateSelection *ds, UtilPeriod limit_period) { | ||||||
|     ds->max_stage = max_stage; |  | ||||||
|     memset(&ds->start_time, 0, sizeof(UtilDate)); // min date |     memset(&ds->start_time, 0, sizeof(UtilDate)); // min date | ||||||
|     memset(&ds->end_time, 0xff, sizeof(UtilDate)); // max date |     memset(&ds->end_time, 0xff, sizeof(UtilDate)); // max date | ||||||
|  |     date_sel_set_period(ds, limit_period); | ||||||
|     date_sel_reset(ds); |     date_sel_reset(ds); | ||||||
| } | } | ||||||
|  |  | ||||||
| @ -24,6 +24,23 @@ void date_sel_reset(DateSelection *ds) { | |||||||
|     ds->stage = DATE_SEL_YEAR; |     ds->stage = DATE_SEL_YEAR; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void date_sel_set_period(DateSelection *ds, UtilPeriod limit_period) { | ||||||
|  |     ds->limit_period = limit_period; | ||||||
|  |     switch (limit_period) { | ||||||
|  |     case PERIOD_HOUR: | ||||||
|  |     case PERIOD_DAY: | ||||||
|  |     case PERIOD_WEEK: | ||||||
|  |         ds->max_stage = DATE_SEL_DAY; | ||||||
|  |         break; | ||||||
|  |     case PERIOD_MONTH: | ||||||
|  |         ds->max_stage = DATE_SEL_MONTH; | ||||||
|  |         break; | ||||||
|  |     case PERIOD_YEAR: | ||||||
|  |         ds->max_stage = DATE_SEL_YEAR; | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| static void date_sel_clamp_time(DateSelection *ds) { | static void date_sel_clamp_time(DateSelection *ds) { | ||||||
|     if (ds->year > ds->end_time.local_year) { |     if (ds->year > ds->end_time.local_year) { | ||||||
|         ds->year = ds->end_time.local_year; |         ds->year = ds->end_time.local_year; | ||||||
| @ -99,10 +116,9 @@ static void date_sel_add_units(DateSelection *ds, int n) { | |||||||
|  |  | ||||||
| DateSelectionState date_sel_dispatch(DateSelection *ds, | DateSelectionState date_sel_dispatch(DateSelection *ds, | ||||||
|                                      SensorState *state, |                                      SensorState *state, | ||||||
|                                      UtilPeriod limit_period, |  | ||||||
|                                      const char *label) { |                                      const char *label) { | ||||||
|     UtilDate start, end; |     UtilDate start, end; | ||||||
|     if (!get_database_limits(state->db, limit_period, &start, &end)) { |     if (!get_database_limits(state->db, ds->limit_period, &start, &end)) { | ||||||
|         warnx("failed to query database limits"); |         warnx("failed to query database limits"); | ||||||
|         lcd_display_control(state->lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF, |         lcd_display_control(state->lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF, | ||||||
|                             LCD_DISPLAY_ON); |                             LCD_DISPLAY_ON); | ||||||
|  | |||||||
| @ -25,6 +25,7 @@ typedef struct { | |||||||
|     int day; |     int day; | ||||||
|     DateSelectionStage stage; |     DateSelectionStage stage; | ||||||
|     DateSelectionStage max_stage; |     DateSelectionStage max_stage; | ||||||
|  |     UtilPeriod limit_period; | ||||||
|     UtilDate start_time; |     UtilDate start_time; | ||||||
|     UtilDate end_time; |     UtilDate end_time; | ||||||
| } DateSelection; | } DateSelection; | ||||||
| @ -37,23 +38,27 @@ typedef enum { | |||||||
| } DateSelectionState; | } DateSelectionState; | ||||||
|  |  | ||||||
| /* | /* | ||||||
|  * Initialize a DateSelection. MAX_STAGE is the most specific field |  * Initialize a DateSelection. LIMIT_PERIOD is the most specific field | ||||||
|  * to select. |  * to select and the period by which to select. | ||||||
|  */ |  */ | ||||||
| void date_sel_init(DateSelection *ds, DateSelectionStage max_stage); | void date_sel_init(DateSelection *ds, UtilPeriod limit_period); | ||||||
|  |  | ||||||
| /* | /* | ||||||
|  * Reset the date on DS. |  * Reset the date on DS. | ||||||
|  */ |  */ | ||||||
| void date_sel_reset(DateSelection *ds); | void date_sel_reset(DateSelection *ds); | ||||||
|  |  | ||||||
|  | /* | ||||||
|  |  * Set the LIMIT_PERIOD for DS. | ||||||
|  |  */ | ||||||
|  | void date_sel_set_period(DateSelection *ds, UtilPeriod limit_period); | ||||||
|  |  | ||||||
| /* | /* | ||||||
|  * Dispatch a DateSelection by processing STATE to continue the selection. |  * Dispatch a DateSelection by processing STATE to continue the selection. | ||||||
|  * Return: weather the user backed out, is still working, or is done |  * Return: weather the user backed out, is still working, or is done | ||||||
|  */ |  */ | ||||||
| DateSelectionState date_sel_dispatch(DateSelection *ds, | DateSelectionState date_sel_dispatch(DateSelection *ds, | ||||||
|                                      SensorState *state, |                                      SensorState *state, | ||||||
|                                      UtilPeriod limit_period, |  | ||||||
|                                      const char *label); |                                      const char *label); | ||||||
|  |  | ||||||
| #endif | #endif | ||||||
|  | |||||||
							
								
								
									
										173
									
								
								src/ui/statrange.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										173
									
								
								src/ui/statrange.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,173 @@ | |||||||
|  | /* | ||||||
|  |  * statrange.c - Screen that displays stats between two times | ||||||
|  |  * 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 "statrange.h" | ||||||
|  | #include "../ths.h" | ||||||
|  |  | ||||||
|  | #include <inttypes.h> | ||||||
|  | #include <time.h> | ||||||
|  | #include <err.h> | ||||||
|  |  | ||||||
|  | // return true to go back | ||||||
|  | static bool stat_range_handle_date(StatRangeScreen *screen, DateSelection *ds, | ||||||
|  |                                    SensorState *state, const char *label) { | ||||||
|  |     if (state->down_down || state->up_down || state->back_down || | ||||||
|  |         state->sel_down) { | ||||||
|  |         screen->need_redraw = true; | ||||||
|  |     } | ||||||
|  |     if (screen->need_redraw) { | ||||||
|  |         screen->need_redraw = false; | ||||||
|  |         DateSelectionState res = date_sel_dispatch(ds, state, label); | ||||||
|  |         switch (res) { | ||||||
|  |         case DATE_SEL_CONTINUE: | ||||||
|  |             break; // ignore | ||||||
|  |         case DATE_SEL_DONE: | ||||||
|  |             ++screen->stage; | ||||||
|  |             screen->need_redraw = true; | ||||||
|  |             break; | ||||||
|  |         case DATE_SEL_BACK: | ||||||
|  |         case DATE_SEL_ERROR: | ||||||
|  |             screen->need_redraw = true; | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return false; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static void stat_range_handle_time(StatRangeScreen *screen, TimeSelection *ts, | ||||||
|  |                                   SensorState *state, const char *label) { | ||||||
|  |     if (state->down_down || state->up_down || state->back_down || | ||||||
|  |         state->sel_down) { | ||||||
|  |         screen->need_redraw = true; | ||||||
|  |     } | ||||||
|  |     if (screen->need_redraw) { | ||||||
|  |         screen->need_redraw = false; | ||||||
|  |         TimeSelState res = time_sel_dispatch(ts, state, label); | ||||||
|  |         switch (res) { | ||||||
|  |         case TIME_SEL_CONTINUE: | ||||||
|  |             break; // ignore | ||||||
|  |         case TIME_SEL_DONE: | ||||||
|  |             ++screen->stage; | ||||||
|  |             screen->need_redraw = true; | ||||||
|  |             break; | ||||||
|  |         case TIME_SEL_BACK: | ||||||
|  |         case TIME_SEL_ERROR: | ||||||
|  |             screen->need_redraw = true; | ||||||
|  |             --screen->stage; | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static uint64_t to_uts_timestamp(DateSelection *ds, TimeSelection *ts) { | ||||||
|  |     struct tm broken_time = { | ||||||
|  |         .tm_sec = ts->second, | ||||||
|  |         .tm_min = ts->minute, | ||||||
|  |         .tm_hour = ts->hour, | ||||||
|  |         .tm_mday = ds->day, | ||||||
|  |         .tm_mon = ds->month - 1, | ||||||
|  |         .tm_year = ds->year - 1900, | ||||||
|  |         .tm_isdst = -1, | ||||||
|  |     }; | ||||||
|  |     time_t it = mktime(&broken_time); | ||||||
|  |     if (it < 0) { | ||||||
|  |         warnx("failed to convert to UTC timestamp"); | ||||||
|  |         return 0; | ||||||
|  |     } | ||||||
|  |     return it; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static void stat_range_show_data(StatRangeScreen *screen, SensorState *state) { | ||||||
|  |     if (state->back_down) { | ||||||
|  |         screen->need_redraw = true; | ||||||
|  |         screen->sds.stage = DATE_SEL_YEAR; | ||||||
|  |         screen->sts.stage = TS_STAGE_HOUR; | ||||||
|  |         screen->eds.stage = DATE_SEL_YEAR; | ||||||
|  |         screen->ets.stage = TS_STAGE_HOUR; | ||||||
|  |         screen->stage = STAT_RANGE_START_DATE; | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |     if (screen->need_redraw) { | ||||||
|  |         screen->need_redraw = false; | ||||||
|  |         uint64_t start = to_uts_timestamp(&screen->sds, &screen->sts); | ||||||
|  |         uint64_t end = to_uts_timestamp(&screen->eds, &screen->ets); | ||||||
|  |         if (start > end) { | ||||||
|  |             uint64_t t = end; | ||||||
|  |             end = start; | ||||||
|  |             start = t; | ||||||
|  |         } | ||||||
|  |         UtilAverageRange data; | ||||||
|  |         if (!get_average_for_range(state->db, start, end, &data)) { | ||||||
|  |             warnx("could not get average for range: %" PRIu64 "-%" PRIu64, | ||||||
|  |                   start, end); | ||||||
|  |             --screen->stage; | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         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_move_to(state->lcd, 1, 0); | ||||||
|  |         if (data.npoints) { | ||||||
|  |             char data_line[17]; | ||||||
|  |             snprintf(data_line, 17, "%.1fF %d%%", DK_TO_F(data.temp), data.humid); | ||||||
|  |             lcd_write_string(state->lcd, data_line); | ||||||
|  |         } else { | ||||||
|  |             lcd_write_string(state->lcd, "No data!"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static bool stat_range_screen_dispatch(StatRangeScreen *screen, | ||||||
|  |                                        SensorState *state) { | ||||||
|  |     if (state->force_draw) { | ||||||
|  |         screen->need_redraw = true; | ||||||
|  |     } | ||||||
|  |     switch (screen->stage) { | ||||||
|  |     case STAT_RANGE_START_DATE: | ||||||
|  |         if(stat_range_handle_date(screen, &screen->sds, state, "Start Date:")) { | ||||||
|  |             date_sel_reset(&screen->sds); | ||||||
|  |             time_sel_reset(&screen->sts); | ||||||
|  |             date_sel_reset(&screen->eds); | ||||||
|  |             time_sel_reset(&screen->ets); | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  |         break; | ||||||
|  |     case STAT_RANGE_START_TIME: | ||||||
|  |         stat_range_handle_time(screen, &screen->sts, state, "Start Time:"); | ||||||
|  |         break; | ||||||
|  |     case STAT_RANGE_END_DATE: | ||||||
|  |         if (stat_range_handle_date(screen, &screen->eds, state, "End Date:")) { | ||||||
|  |             --screen->stage; | ||||||
|  |         } | ||||||
|  |         break; | ||||||
|  |     case STAT_RANGE_END_TIME: | ||||||
|  |         stat_range_handle_time(screen, &screen->ets, state, "End Time:"); | ||||||
|  |         break; | ||||||
|  |     case STAT_RANGE_SHOW: | ||||||
|  |         stat_range_show_data(screen, state); | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |     return false; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | StatRangeScreen *stat_range_screen_new(void) { | ||||||
|  |     StatRangeScreen *s = malloc_checked(sizeof(StatRangeScreen)); | ||||||
|  |     screen_init(&s->parent, "Average range", | ||||||
|  |                 (ScreenDispatchFunc) stat_range_screen_dispatch, | ||||||
|  |                 (ScreenCleanupFunc) free); | ||||||
|  |     s->need_redraw = true; | ||||||
|  |     s->stage = STAT_RANGE_START_DATE; | ||||||
|  |     date_sel_init(&s->sds, PERIOD_DAY); | ||||||
|  |     time_sel_init(&s->sts, &s->sds, true); | ||||||
|  |     date_sel_init(&s->eds, PERIOD_DAY); | ||||||
|  |     time_sel_init(&s->ets, &s->eds, true); | ||||||
|  |     return s; | ||||||
|  | } | ||||||
							
								
								
									
										38
									
								
								src/ui/statrange.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/ui/statrange.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,38 @@ | |||||||
|  | /* | ||||||
|  |  * statrange.h - Screen that displays stats between two times | ||||||
|  |  * 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. | ||||||
|  |  */ | ||||||
|  | #ifndef INCLUDED_STATRANGE_H | ||||||
|  | #define INCLUDED_STATRANGE_H | ||||||
|  |  | ||||||
|  | #include "datesel.h" | ||||||
|  | #include "timesel.h" | ||||||
|  |  | ||||||
|  | typedef struct { | ||||||
|  |     Screen parent; | ||||||
|  |     enum { | ||||||
|  |         STAT_RANGE_START_DATE, | ||||||
|  |         STAT_RANGE_START_TIME, | ||||||
|  |         STAT_RANGE_END_DATE, | ||||||
|  |         STAT_RANGE_END_TIME, | ||||||
|  |         STAT_RANGE_SHOW | ||||||
|  |     } stage; | ||||||
|  |     bool need_redraw; | ||||||
|  |     DateSelection sds; | ||||||
|  |     TimeSelection sts; | ||||||
|  |     DateSelection eds; | ||||||
|  |     TimeSelection ets; | ||||||
|  | } StatRangeScreen; | ||||||
|  |  | ||||||
|  | /* | ||||||
|  |  * Create a new stat range screen. The screen will display average data between | ||||||
|  |  * two times. | ||||||
|  |  */ | ||||||
|  | StatRangeScreen *stat_range_screen_new(void); | ||||||
|  |  | ||||||
|  | #endif | ||||||
| @ -37,19 +37,7 @@ static void stats_by_select_period(StatsByScreen *screen, SensorState *state) { | |||||||
|     } |     } | ||||||
|     if (state->sel_down) { |     if (state->sel_down) { | ||||||
|         ++screen->stage; |         ++screen->stage; | ||||||
|         switch (screen->period) { |         date_sel_set_period(&screen->ds, screen->period); | ||||||
|         case PERIOD_HOUR: |  | ||||||
|         case PERIOD_DAY: |  | ||||||
|         case PERIOD_WEEK: |  | ||||||
|             screen->ds.max_stage = DATE_SEL_DAY; |  | ||||||
|             break; |  | ||||||
|         case PERIOD_MONTH: |  | ||||||
|             screen->ds.max_stage = DATE_SEL_MONTH; |  | ||||||
|             break; |  | ||||||
|         case PERIOD_YEAR: |  | ||||||
|             screen->ds.max_stage = DATE_SEL_YEAR; |  | ||||||
|             break; |  | ||||||
|         } |  | ||||||
|         screen->need_redraw = true; |         screen->need_redraw = true; | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -60,8 +48,7 @@ static void stats_by_select_start(StatsByScreen *screen, SensorState *state) { | |||||||
|     } |     } | ||||||
|     if (screen->need_redraw) { |     if (screen->need_redraw) { | ||||||
|         screen->need_redraw = false; |         screen->need_redraw = false; | ||||||
|         DateSelectionState dss = date_sel_dispatch(&screen->ds, state, |         DateSelectionState dss = date_sel_dispatch(&screen->ds, state, "Start:"); | ||||||
|                                                    screen->period, "Start:"); |  | ||||||
|         switch (dss) { |         switch (dss) { | ||||||
|         case DATE_SEL_CONTINUE: |         case DATE_SEL_CONTINUE: | ||||||
|             break; // ignore |             break; // ignore | ||||||
| @ -99,8 +86,8 @@ static void stats_by_show_stats(StatsByScreen *screen, SensorState *state) { | |||||||
|             screen->ds.day == screen->ds.start_time.local_day) { |             screen->ds.day == screen->ds.start_time.local_day) { | ||||||
|             screen->offset_scale = screen->ds.start_time.local_hour; |             screen->offset_scale = screen->ds.start_time.local_hour; | ||||||
|         } |         } | ||||||
|         UtilAverageRange data; |         UtilAveragePeriod data; | ||||||
|         if (!get_average_for_range(state->db, screen->ds.year, screen->ds.month, |         if (!get_average_for_period(state->db, screen->ds.year, screen->ds.month, | ||||||
|                                    screen->ds.day, screen->offset_scale, |                                    screen->ds.day, screen->offset_scale, | ||||||
|                                    screen->period, &data)) { |                                    screen->period, &data)) { | ||||||
|             lcd_write_string(state->lcd, "Query failed!"); |             lcd_write_string(state->lcd, "Query failed!"); | ||||||
| @ -190,6 +177,6 @@ StatsByScreen *stats_by_screen_new() { | |||||||
|     s->need_redraw = true; |     s->need_redraw = true; | ||||||
|     s->stage = STATS_BY_SELECT_PERIOD; |     s->stage = STATS_BY_SELECT_PERIOD; | ||||||
|     s->period = PERIOD_HOUR; |     s->period = PERIOD_HOUR; | ||||||
|     date_sel_init(&s->ds, DATE_SEL_DAY); |     date_sel_init(&s->ds, PERIOD_DAY); | ||||||
|     return s; |     return s; | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										107
									
								
								src/ui/timesel.c
									
									
									
									
									
								
							
							
						
						
									
										107
									
								
								src/ui/timesel.c
									
									
									
									
									
								
							| @ -11,11 +11,10 @@ | |||||||
|  |  | ||||||
| #include <err.h> | #include <err.h> | ||||||
|  |  | ||||||
| void time_sel_init(TimeSelection *ts, DateSelection *ds) { | void time_sel_init(TimeSelection *ts, DateSelection *ds, bool do_second) { | ||||||
|     ts->hour = 0; |  | ||||||
|     ts->minute = 0; |  | ||||||
|     ts->hour_set = false; |  | ||||||
|     ts->ds = ds; |     ts->ds = ds; | ||||||
|  |     ts->do_second = do_second; | ||||||
|  |     time_sel_reset(ts); | ||||||
| } | } | ||||||
|  |  | ||||||
| static void time_sel_clamp(TimeSelection *ts) { | static void time_sel_clamp(TimeSelection *ts) { | ||||||
| @ -30,6 +29,12 @@ static void time_sel_clamp(TimeSelection *ts) { | |||||||
|                 ts->minute > ts->ds->end_time.local_minute) { |                 ts->minute > ts->ds->end_time.local_minute) { | ||||||
|                 ts->minute = ts->ds->end_time.local_minute; |                 ts->minute = ts->ds->end_time.local_minute; | ||||||
|             } |             } | ||||||
|  |  | ||||||
|  |             if (ts->hour == ts->ds->end_time.local_hour && | ||||||
|  |                 ts->minute == ts->ds->end_time.local_minute && | ||||||
|  |                 ts->second > ts->ds->end_time.local_second) { | ||||||
|  |                 ts->second = ts->ds->end_time.local_second; | ||||||
|  |             } | ||||||
|         } else if (ts->ds->start_time.local_year == ts->ds->year && |         } else if (ts->ds->start_time.local_year == ts->ds->year && | ||||||
|                    ts->ds->start_time.local_month == ts->ds->month && |                    ts->ds->start_time.local_month == ts->ds->month && | ||||||
|                    ts->ds->start_time.local_day == ts->ds->day) { |                    ts->ds->start_time.local_day == ts->ds->day) { | ||||||
| @ -40,34 +45,57 @@ static void time_sel_clamp(TimeSelection *ts) { | |||||||
|                 ts->minute < ts->ds->start_time.local_minute) { |                 ts->minute < ts->ds->start_time.local_minute) { | ||||||
|                 ts->minute = ts->ds->start_time.local_minute; |                 ts->minute = ts->ds->start_time.local_minute; | ||||||
|             } |             } | ||||||
|  |             if (ts->hour == ts->ds->start_time.local_hour && | ||||||
|  |                 ts->minute == ts->ds->start_time.local_minute && | ||||||
|  |                 ts->second > ts->ds->start_time.local_second) { | ||||||
|  |                 ts->second = ts->ds->start_time.local_second; | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     }  |     }  | ||||||
| } | } | ||||||
|  |  | ||||||
| static void time_sel_add(TimeSelection *ts, int amount) { | static void time_sel_add(TimeSelection *ts, int amount) { | ||||||
|     if (!ts->hour_set) { |     switch (ts->stage) { | ||||||
|         ts->hour = (ts->hour + amount) % 24; |     case TS_STAGE_HOUR: | ||||||
|     } else { |         ts->hour += amount; | ||||||
|         if (ts->minute + amount >= 60) { |         break; | ||||||
|             ts->hour = (ts->hour + 1) % 24; |     case TS_STAGE_MINUTE: | ||||||
|         } else if (ts->minute + amount < 0) { |         ts->minute += amount; | ||||||
|             ts->hour = (ts->hour - 1) % 24; |         break; | ||||||
|  |     case TS_STAGE_SECOND: | ||||||
|  |         ts->second += amount; | ||||||
|  |         break; | ||||||
|     } |     } | ||||||
|         ts->minute = (ts->minute + amount) % 60; |     if (ts->second >= 60) { | ||||||
|  |         ++ts->minute; | ||||||
|  |     } else if (ts->second < 0) { | ||||||
|  |         --ts->minute; | ||||||
|     } |     } | ||||||
|  |     if (ts->minute >= 60) { | ||||||
|  |         ++ts->hour; | ||||||
|  |     } else if (ts->minute  < 0) { | ||||||
|  |         --ts->hour; | ||||||
|  |     } | ||||||
|  |     ts->hour %= 24; | ||||||
|  |     ts->minute %= 60; | ||||||
|  |     ts->second %= 60; | ||||||
|     if (ts->hour < 0) { |     if (ts->hour < 0) { | ||||||
|         ts->hour = 24 + ts->hour; |         ts->hour += 24; | ||||||
|     } |     } | ||||||
|     if (ts->minute < 0) { |     if (ts->minute < 0) { | ||||||
|         ts->minute = 60 + ts->minute; |         ts->minute += 60; | ||||||
|  |     } | ||||||
|  |     if (ts->second < 0) { | ||||||
|  |         ts->second += 60; | ||||||
|     } |     } | ||||||
|     time_sel_clamp(ts); |     time_sel_clamp(ts); | ||||||
| } | } | ||||||
|  |  | ||||||
| TimeSelState time_sel_dispatch(TimeSelection *ts, SensorState *state) { | TimeSelState time_sel_dispatch(TimeSelection *ts, SensorState *state, | ||||||
|  |                                const char *label) { | ||||||
|     if (state->back_down) { |     if (state->back_down) { | ||||||
|         if (ts->hour_set) { |         if (ts->stage != TS_STAGE_HOUR) { | ||||||
|             ts->hour_set = false; |             --ts->stage; | ||||||
|         } else { |         } else { | ||||||
|             lcd_display_control(state->lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF, |             lcd_display_control(state->lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF, | ||||||
|                                 LCD_DISPLAY_ON); |                                 LCD_DISPLAY_ON); | ||||||
| @ -75,12 +103,13 @@ TimeSelState time_sel_dispatch(TimeSelection *ts, SensorState *state) { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     if (state->sel_down) { |     if (state->sel_down) { | ||||||
|         if (!ts->hour_set) { |         if ((ts->do_second && ts->stage == TS_STAGE_SECOND) || | ||||||
|             ts->hour_set = true; |             (!ts->do_second && ts->stage == TS_STAGE_MINUTE)) { | ||||||
|         } else { |  | ||||||
|             lcd_display_control(state->lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF, |             lcd_display_control(state->lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF, | ||||||
|                                 LCD_DISPLAY_ON); |                                 LCD_DISPLAY_ON); | ||||||
|             return TIME_SEL_DONE; |             return TIME_SEL_DONE; | ||||||
|  |         } else { | ||||||
|  |             ++ts->stage; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     if (ts->ds) { |     if (ts->ds) { | ||||||
| @ -97,18 +126,40 @@ TimeSelState time_sel_dispatch(TimeSelection *ts, SensorState *state) { | |||||||
|     time_sel_add(ts, state->up_down - state->down_down); |     time_sel_add(ts, state->up_down - state->down_down); | ||||||
|     lcd_clear(state->lcd); |     lcd_clear(state->lcd); | ||||||
|     lcd_move_to(state->lcd, 0, 0); |     lcd_move_to(state->lcd, 0, 0); | ||||||
|     lcd_write_string(state->lcd, "Time:"); |     lcd_write_string(state->lcd, label); | ||||||
|     const char *format; |     char time_line[17]; | ||||||
|     int cursor_pos; |     int cursor_pos; | ||||||
|     if (!ts->hour_set) { |     if (ts->do_second) { | ||||||
|  |         const char *format; | ||||||
|  |         switch (ts->stage) { | ||||||
|  |         case TS_STAGE_HOUR: | ||||||
|  |             format = ">%02d:%02d:%02d"; | ||||||
|  |             cursor_pos = 0; | ||||||
|  |             break; | ||||||
|  |         case TS_STAGE_MINUTE:   | ||||||
|  |             format = "%02d:>%02d:%02d"; | ||||||
|  |             cursor_pos = 3; | ||||||
|  |             break; | ||||||
|  |         case TS_STAGE_SECOND: | ||||||
|  |             format = "%02d:%02d:>%02d"; | ||||||
|  |             cursor_pos = 6; | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |         snprintf(time_line, 17, format, ts->hour, ts->minute, ts->second); | ||||||
|  |     } else { | ||||||
|  |         const char *format; | ||||||
|  |         switch (ts->stage) { | ||||||
|  |         case TS_STAGE_HOUR: | ||||||
|             format = ">%02d:%02d"; |             format = ">%02d:%02d"; | ||||||
|             cursor_pos = 0; |             cursor_pos = 0; | ||||||
|     } else { |             break; | ||||||
|  |         default:  // minute or second (which shouldn't happen) | ||||||
|             format = "%02d:>%02d"; |             format = "%02d:>%02d"; | ||||||
|             cursor_pos = 3; |             cursor_pos = 3; | ||||||
|  |             break; | ||||||
|         } |         } | ||||||
|     char time_line[17]; |  | ||||||
|         snprintf(time_line, 17, format, ts->hour, ts->minute); |         snprintf(time_line, 17, format, ts->hour, ts->minute); | ||||||
|  |     } | ||||||
|     lcd_move_to(state->lcd, 1, 0); |     lcd_move_to(state->lcd, 1, 0); | ||||||
|     lcd_write_string(state->lcd, time_line); |     lcd_write_string(state->lcd, time_line); | ||||||
|     lcd_move_to(state->lcd, 1, cursor_pos); |     lcd_move_to(state->lcd, 1, cursor_pos); | ||||||
| @ -116,3 +167,9 @@ TimeSelState time_sel_dispatch(TimeSelection *ts, SensorState *state) { | |||||||
|                         LCD_DISPLAY_ON); |                         LCD_DISPLAY_ON); | ||||||
|     return TIME_SEL_CONTINUE; |     return TIME_SEL_CONTINUE; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void time_sel_reset(TimeSelection *ts) { | ||||||
|  |     ts->hour = 0; | ||||||
|  |     ts->minute = 0; | ||||||
|  |     ts->stage = TS_STAGE_HOUR; | ||||||
|  | } | ||||||
|  | |||||||
| @ -16,9 +16,15 @@ | |||||||
|  |  | ||||||
| typedef struct { | typedef struct { | ||||||
|     DateSelection *ds; |     DateSelection *ds; | ||||||
|  |     enum { | ||||||
|  |         TS_STAGE_HOUR, | ||||||
|  |         TS_STAGE_MINUTE, | ||||||
|  |         TS_STAGE_SECOND | ||||||
|  |     } stage; | ||||||
|     int hour; |     int hour; | ||||||
|     bool hour_set; |  | ||||||
|     int minute; |     int minute; | ||||||
|  |     bool do_second; | ||||||
|  |     int second; | ||||||
| } TimeSelection; | } TimeSelection; | ||||||
|  |  | ||||||
| typedef enum { | typedef enum { | ||||||
| @ -32,12 +38,18 @@ typedef enum { | |||||||
|  * Initialize the TimeSelection TS. Optionally, get the year, month, and day, |  * Initialize the TimeSelection TS. Optionally, get the year, month, and day, | ||||||
|  * and limits from DS. |  * and limits from DS. | ||||||
|  */ |  */ | ||||||
| void time_sel_init(TimeSelection *ts, DateSelection *ds); | void time_sel_init(TimeSelection *ts, DateSelection *ds, bool do_second); | ||||||
|  |  | ||||||
| /* | /* | ||||||
|  * Dispatch (process input and draw) the TimeSelection TS according to STATE. |  * Dispatch (process input and draw) the TimeSelection TS according to STATE. | ||||||
|  * Return: the current state of the selection (back, continue, done). |  * Return: the current state of the selection (back, continue, done). | ||||||
|  */ |  */ | ||||||
| TimeSelState time_sel_dispatch(TimeSelection *ts, SensorState *state); | TimeSelState time_sel_dispatch(TimeSelection *ts, SensorState *state, | ||||||
|  |                                const char *label); | ||||||
|  |  | ||||||
|  | /* | ||||||
|  |  * Reset TS back to 00:00. | ||||||
|  |  */ | ||||||
|  | void time_sel_reset(TimeSelection *ts); | ||||||
|  |  | ||||||
| #endif | #endif | ||||||
|  | |||||||
							
								
								
									
										136
									
								
								src/util.c
									
									
									
									
									
								
							
							
						
						
									
										136
									
								
								src/util.c
									
									
									
									
									
								
							| @ -88,24 +88,28 @@ static const char *DB_LIMITS_QUERY_STR = | |||||||
|     "strftime('%e', MinUTC, 'unixepoch') as MinUTCDay,\n" |     "strftime('%e', MinUTC, 'unixepoch') as MinUTCDay,\n" | ||||||
|     "strftime('%H', RealMin, 'unixepoch') as MinUTCHour,\n" |     "strftime('%H', RealMin, 'unixepoch') as MinUTCHour,\n" | ||||||
|     "strftime('%M', RealMin, 'unixepoch') as MinUTCMin,\n" |     "strftime('%M', RealMin, 'unixepoch') as MinUTCMin,\n" | ||||||
|  |     "strftime('%S', RealMin, 'unixepoch') as MinUTCSec,\n" | ||||||
|     "MinLocal,\n" |     "MinLocal,\n" | ||||||
|     "strftime('%Y', MinLocal, 'unixepoch') as MinLocalYear,\n" |     "strftime('%Y', MinLocal, 'unixepoch') as MinLocalYear,\n" | ||||||
|     "strftime('%m', MinLocal, 'unixepoch') as MinLocalMonth,\n" |     "strftime('%m', MinLocal, 'unixepoch') as MinLocalMonth,\n" | ||||||
|     "strftime('%e', MinLocal, 'unixepoch') as MinLocalDay,\n" |     "strftime('%e', MinLocal, 'unixepoch') as MinLocalDay,\n" | ||||||
|     "strftime('%H', RealMin, 'unixepoch', 'localtime') as MinLocalHour,\n" |     "strftime('%H', RealMin, 'unixepoch', 'localtime') as MinLocalHour,\n" | ||||||
|     "strftime('%M', RealMin, 'unixepoch', 'localtime') as MinLocalMin,\n" |     "strftime('%M', RealMin, 'unixepoch', 'localtime') as MinLocalMin,\n" | ||||||
|  |     "strftime('%S', RealMin, 'unixepoch', 'localtime') as MinLocalSec,\n" | ||||||
|     "MaxUTC,\n" |     "MaxUTC,\n" | ||||||
|     "strftime('%Y', MaxUTC, 'unixepoch') as MaxUTCYear,\n" |     "strftime('%Y', MaxUTC, 'unixepoch') as MaxUTCYear,\n" | ||||||
|     "strftime('%m', MaxUTC, 'unixepoch') as MaxUTCMonth,\n" |     "strftime('%m', MaxUTC, 'unixepoch') as MaxUTCMonth,\n" | ||||||
|     "strftime('%e', MaxUTC, 'unixepoch') as MaxUTCDay,\n" |     "strftime('%e', MaxUTC, 'unixepoch') as MaxUTCDay,\n" | ||||||
|     "strftime('%H', RealMax, 'unixepoch') as MaxUTCHour,\n" |     "strftime('%H', RealMax, 'unixepoch') as MaxUTCHour,\n" | ||||||
|     "strftime('%M', RealMax, 'unixepoch') as MaxUTCMin,\n" |     "strftime('%M', RealMax, 'unixepoch') as MaxUTCMin,\n" | ||||||
|  |     "strftime('%S', RealMax, 'unixepoch') as MaxUTCSec,\n" | ||||||
|     "MaxLocal,\n" |     "MaxLocal,\n" | ||||||
|     "strftime('%Y', MaxLocal, 'unixepoch') as MaxLocalYear,\n" |     "strftime('%Y', MaxLocal, 'unixepoch') as MaxLocalYear,\n" | ||||||
|     "strftime('%m', MaxLocal, 'unixepoch') as MaxLocalMonth,\n" |     "strftime('%m', MaxLocal, 'unixepoch') as MaxLocalMonth,\n" | ||||||
|     "strftime('%e', MaxLocal, 'unixepoch') as MaxLocalDay,\n" |     "strftime('%e', MaxLocal, 'unixepoch') as MaxLocalDay,\n" | ||||||
|     "strftime('%H', RealMax, 'unixepoch', 'localtime') as MaxLocalHour,\n" |     "strftime('%H', RealMax, 'unixepoch', 'localtime') as MaxLocalHour,\n" | ||||||
|     "strftime('%M', RealMax, 'unixepoch', 'localtime') as MaxLocalMin\n" |     "strftime('%M', RealMax, 'unixepoch', 'localtime') as MaxLocalMin,\n" | ||||||
|  |     "strftime('%S', RealMax, 'unixepoch', 'localtime') as MaxLocalSec\n" | ||||||
|     "FROM\n" |     "FROM\n" | ||||||
|     "(SELECT\n" |     "(SELECT\n" | ||||||
|     "RealMax, RealMin,\n" |     "RealMax, RealMin,\n" | ||||||
| @ -115,7 +119,7 @@ static const char *DB_LIMITS_QUERY_STR = | |||||||
|     "unixepoch(RealMax, 'unixepoch', 'localtime', '+1 ' || ?1, 'start of ' || ?1, '-1 second') as MaxLocal\n" |     "unixepoch(RealMax, 'unixepoch', 'localtime', '+1 ' || ?1, 'start of ' || ?1, '-1 second') as MaxLocal\n" | ||||||
|     "FROM (SELECT max(time) as RealMax, min(time) as RealMin FROM env_data));"; |     "FROM (SELECT max(time) as RealMax, min(time) as RealMin FROM env_data));"; | ||||||
| static sqlite3_stmt *DB_LIMITS_QUERY; | static sqlite3_stmt *DB_LIMITS_QUERY; | ||||||
| static const char *AVG_FOR_RANGE_QUERY_STR = | static const char *AVG_FOR_PERIOD_QUERY_STR = | ||||||
|     "SELECT\n" |     "SELECT\n" | ||||||
|     "strftime('%Y', stime, 'unixepoch', 'localtime') as y,\n" |     "strftime('%Y', stime, 'unixepoch', 'localtime') as y,\n" | ||||||
|     "strftime('%m', stime, 'unixepoch', 'localtime') as m,\n" |     "strftime('%m', stime, 'unixepoch', 'localtime') as m,\n" | ||||||
| @ -133,9 +137,9 @@ static const char *AVG_FOR_RANGE_QUERY_STR = | |||||||
|     "round(avg(temp)) AS atemp,\n" |     "round(avg(temp)) AS atemp,\n" | ||||||
|     "round(avg(humid)) AS ahumid\n" |     "round(avg(humid)) AS ahumid\n" | ||||||
|     "FROM env_data\n" |     "FROM env_data\n" | ||||||
|     "WHERE time > stime\n" |     "WHERE time >= stime\n" | ||||||
|     "AND time < etime);\n"; |     "AND time <= etime);\n"; | ||||||
| static sqlite3_stmt *AVG_FOR_RANGE_QUERY; | static sqlite3_stmt *AVG_FOR_PERIOD_QUERY; | ||||||
| static const char *DATA_POINT_QUERY_STR = | static const char *DATA_POINT_QUERY_STR = | ||||||
|     "SELECT max(time) IS NULL, max(time), temp, humid FROM env_data WHERE time < ?1\n" |     "SELECT max(time) IS NULL, max(time), temp, humid FROM env_data WHERE time < ?1\n" | ||||||
|     "UNION ALL\n" |     "UNION ALL\n" | ||||||
| @ -143,28 +147,38 @@ static const char *DATA_POINT_QUERY_STR = | |||||||
|     "UNION ALL\n" |     "UNION ALL\n" | ||||||
|     "SELECT false, * FROM env_data WHERE time == ?1;"; |     "SELECT false, * FROM env_data WHERE time == ?1;"; | ||||||
| static sqlite3_stmt *DATA_POINT_QUERY; | 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" | ||||||
|  |     "WHERE time >= ?1 AND time <= ?2;"; | ||||||
|  | static sqlite3_stmt *AVG_FOR_RANGE_QUERY; | ||||||
| void initialize_util_queries(sqlite3 *db) { | void initialize_util_queries(sqlite3 *db) { | ||||||
|     int status = sqlite3_prepare_v2(db, DB_LIMITS_QUERY_STR, -1, |     int status = sqlite3_prepare_v2(db, DB_LIMITS_QUERY_STR, -1, | ||||||
|                                     &DB_LIMITS_QUERY, NULL); |                                     &DB_LIMITS_QUERY, NULL); | ||||||
|     if (status != SQLITE_OK) { |     if (status != SQLITE_OK) { | ||||||
|         errx(1, "failed to compile limits query: %s", sqlite3_errstr(status)); |         errx(1, "failed to compile limits query: %s", sqlite3_errstr(status)); | ||||||
|     } |     } | ||||||
|     status = sqlite3_prepare_v2(db, AVG_FOR_RANGE_QUERY_STR, -1, |     status = sqlite3_prepare_v2(db, AVG_FOR_PERIOD_QUERY_STR, -1, | ||||||
|                                 &AVG_FOR_RANGE_QUERY, NULL); |                                 &AVG_FOR_PERIOD_QUERY, NULL); | ||||||
|     if (status != SQLITE_OK) { |     if (status != SQLITE_OK) { | ||||||
|         errx(1, "failed to compile range average query: %s", sqlite3_errstr(status)); |         errx(1, "failed to compile period average query: %s", sqlite3_errstr(status)); | ||||||
|     } |     } | ||||||
|     status = sqlite3_prepare_v2(db, DATA_POINT_QUERY_STR, -1, |     status = sqlite3_prepare_v2(db, DATA_POINT_QUERY_STR, -1, | ||||||
|                                 &DATA_POINT_QUERY, NULL); |                                 &DATA_POINT_QUERY, NULL); | ||||||
|     if (status != SQLITE_OK) { |     if (status != SQLITE_OK) { | ||||||
|         errx(1, "failed to compile data point query: %s", sqlite3_errstr(status)); |         errx(1, "failed to compile data point query: %s", sqlite3_errstr(status)); | ||||||
|     } |     } | ||||||
|  |     status = sqlite3_prepare_v2(db, AVG_FOR_RANGE_QUERY_STR, -1, | ||||||
|  |                                 &AVG_FOR_RANGE_QUERY, NULL); | ||||||
|  |     if (status != SQLITE_OK) { | ||||||
|  |         errx(1, "failed to compile range average query: %s", sqlite3_errstr(status)); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| void cleanup_util_queries() { | void cleanup_util_queries() { | ||||||
|     sqlite3_finalize(DB_LIMITS_QUERY); |     sqlite3_finalize(DB_LIMITS_QUERY); | ||||||
|     sqlite3_finalize(AVG_FOR_RANGE_QUERY); |     sqlite3_finalize(AVG_FOR_PERIOD_QUERY); | ||||||
|     sqlite3_finalize(DATA_POINT_QUERY); |     sqlite3_finalize(DATA_POINT_QUERY); | ||||||
|  |     sqlite3_finalize(AVG_FOR_RANGE_QUERY); | ||||||
| } | } | ||||||
|  |  | ||||||
| bool get_database_limits(sqlite3 *db, UtilPeriod period, UtilDate *start, | bool get_database_limits(sqlite3 *db, UtilPeriod period, UtilDate *start, | ||||||
| @ -184,26 +198,30 @@ bool get_database_limits(sqlite3 *db, UtilPeriod period, UtilDate *start, | |||||||
|             start->utc_day = sqlite3_column_int64(DB_LIMITS_QUERY, 3); |             start->utc_day = sqlite3_column_int64(DB_LIMITS_QUERY, 3); | ||||||
|             start->utc_hour = sqlite3_column_int64(DB_LIMITS_QUERY, 4); |             start->utc_hour = sqlite3_column_int64(DB_LIMITS_QUERY, 4); | ||||||
|             start->utc_minute = sqlite3_column_int64(DB_LIMITS_QUERY, 5); |             start->utc_minute = sqlite3_column_int64(DB_LIMITS_QUERY, 5); | ||||||
|             start->local = sqlite3_column_int64(DB_LIMITS_QUERY, 6); |             start->utc_second = sqlite3_column_int64(DB_LIMITS_QUERY, 6); | ||||||
|             start->local_year = sqlite3_column_int64(DB_LIMITS_QUERY, 7); |             start->local = sqlite3_column_int64(DB_LIMITS_QUERY, 7); | ||||||
|             start->local_month = sqlite3_column_int64(DB_LIMITS_QUERY, 8); |             start->local_year = sqlite3_column_int64(DB_LIMITS_QUERY, 8); | ||||||
|             start->local_day = sqlite3_column_int64(DB_LIMITS_QUERY, 9); |             start->local_month = sqlite3_column_int64(DB_LIMITS_QUERY, 9); | ||||||
|             start->local_hour = sqlite3_column_int64(DB_LIMITS_QUERY, 10); |             start->local_day = sqlite3_column_int64(DB_LIMITS_QUERY, 10); | ||||||
|             start->local_minute = sqlite3_column_int64(DB_LIMITS_QUERY, 11); |             start->local_hour = sqlite3_column_int64(DB_LIMITS_QUERY, 11); | ||||||
|  |             start->local_minute = sqlite3_column_int64(DB_LIMITS_QUERY, 12); | ||||||
|  |             start->local_second = sqlite3_column_int64(DB_LIMITS_QUERY, 13); | ||||||
|         } |         } | ||||||
|         if (end) { |         if (end) { | ||||||
|             end->utc = sqlite3_column_int64(DB_LIMITS_QUERY, 12); |             end->utc = sqlite3_column_int64(DB_LIMITS_QUERY, 14); | ||||||
|             end->utc_year = sqlite3_column_int64(DB_LIMITS_QUERY, 13); |             end->utc_year = sqlite3_column_int64(DB_LIMITS_QUERY, 15); | ||||||
|             end->utc_month = sqlite3_column_int64(DB_LIMITS_QUERY, 14); |             end->utc_month = sqlite3_column_int64(DB_LIMITS_QUERY, 16); | ||||||
|             end->utc_day = sqlite3_column_int64(DB_LIMITS_QUERY, 15); |             end->utc_day = sqlite3_column_int64(DB_LIMITS_QUERY, 17); | ||||||
|             end->utc_hour = sqlite3_column_int64(DB_LIMITS_QUERY, 16); |             end->utc_hour = sqlite3_column_int64(DB_LIMITS_QUERY, 18); | ||||||
|             end->utc_minute = sqlite3_column_int64(DB_LIMITS_QUERY, 17); |             end->utc_minute = sqlite3_column_int64(DB_LIMITS_QUERY, 19); | ||||||
|             end->local = sqlite3_column_int64(DB_LIMITS_QUERY, 18); |             end->utc_second = sqlite3_column_int64(DB_LIMITS_QUERY, 20); | ||||||
|             end->local_year = sqlite3_column_int64(DB_LIMITS_QUERY, 19); |             end->local = sqlite3_column_int64(DB_LIMITS_QUERY, 21); | ||||||
|             end->local_month = sqlite3_column_int64(DB_LIMITS_QUERY, 20); |             end->local_year = sqlite3_column_int64(DB_LIMITS_QUERY, 22); | ||||||
|             end->local_day = sqlite3_column_int64(DB_LIMITS_QUERY, 21); |             end->local_month = sqlite3_column_int64(DB_LIMITS_QUERY, 23); | ||||||
|             end->local_hour = sqlite3_column_int64(DB_LIMITS_QUERY, 22); |             end->local_day = sqlite3_column_int64(DB_LIMITS_QUERY, 24); | ||||||
|             end->local_minute = sqlite3_column_int64(DB_LIMITS_QUERY, 23); |             end->local_hour = sqlite3_column_int64(DB_LIMITS_QUERY, 25); | ||||||
|  |             end->local_minute = sqlite3_column_int64(DB_LIMITS_QUERY, 26); | ||||||
|  |             end->local_second = sqlite3_column_int64(DB_LIMITS_QUERY, 27); | ||||||
|         } |         } | ||||||
|     } else { |     } else { | ||||||
|         success = false; |         success = false; | ||||||
| @ -212,39 +230,37 @@ bool get_database_limits(sqlite3 *db, UtilPeriod period, UtilDate *start, | |||||||
|     return success; |     return success; | ||||||
| } | } | ||||||
|  |  | ||||||
| bool get_average_for_range(sqlite3 *db, int year, int month, int day, | bool get_average_for_period(sqlite3 *db, int year, int month, int day, | ||||||
|                            int64_t count, UtilPeriod period, |                            int64_t count, UtilPeriod period, | ||||||
|                            UtilAverageRange *data) { |                            UtilAveragePeriod *data) { | ||||||
|     bool success = true; |     bool success = true; | ||||||
|     sqlite3_bind_int(AVG_FOR_RANGE_QUERY, 1, year); |     sqlite3_bind_int(AVG_FOR_PERIOD_QUERY, 1, year); | ||||||
|     sqlite3_bind_int(AVG_FOR_RANGE_QUERY, 2, month); |     sqlite3_bind_int(AVG_FOR_PERIOD_QUERY, 2, month); | ||||||
|     sqlite3_bind_int(AVG_FOR_RANGE_QUERY, 3, day); |     sqlite3_bind_int(AVG_FOR_PERIOD_QUERY, 3, day); | ||||||
|     sqlite3_bind_int64(AVG_FOR_RANGE_QUERY, 4, count); |     sqlite3_bind_int64(AVG_FOR_PERIOD_QUERY, 4, count); | ||||||
|     if (period == PERIOD_WEEK) { |     if (period == PERIOD_WEEK) { | ||||||
|         sqlite3_bind_text(AVG_FOR_RANGE_QUERY, 5, "days", -1, SQLITE_STATIC); |         sqlite3_bind_text(AVG_FOR_PERIOD_QUERY, 5, "days", -1, SQLITE_STATIC); | ||||||
|         sqlite3_bind_int(AVG_FOR_RANGE_QUERY, 6, 7); |         sqlite3_bind_int(AVG_FOR_PERIOD_QUERY, 6, 7); | ||||||
|     } else { |     } else { | ||||||
|         sqlite3_bind_text(AVG_FOR_RANGE_QUERY, 5, PERIOD_LABELS[period], -1, |         sqlite3_bind_text(AVG_FOR_PERIOD_QUERY, 5, PERIOD_LABELS[period], -1, | ||||||
|                           SQLITE_STATIC); |                           SQLITE_STATIC); | ||||||
|         sqlite3_bind_int(AVG_FOR_RANGE_QUERY, 6, 1); |         sqlite3_bind_int(AVG_FOR_PERIOD_QUERY, 6, 1); | ||||||
|     } |     } | ||||||
|     int status = sqlite3_step(AVG_FOR_RANGE_QUERY); |     int status = sqlite3_step(AVG_FOR_PERIOD_QUERY); | ||||||
|     if (status == SQLITE_ROW) { |     if (status != SQLITE_ROW) { | ||||||
|         if (data) { |  | ||||||
|             data->year = sqlite3_column_int(AVG_FOR_RANGE_QUERY, 0); |  | ||||||
|             data->month = sqlite3_column_int(AVG_FOR_RANGE_QUERY, 1); |  | ||||||
|             data->day = sqlite3_column_int(AVG_FOR_RANGE_QUERY, 2); |  | ||||||
|             data->hour = sqlite3_column_int(AVG_FOR_RANGE_QUERY, 3); |  | ||||||
|             data->npoints = sqlite3_column_int(AVG_FOR_RANGE_QUERY, 4); |  | ||||||
|             data->upper_bound = sqlite3_column_int64(AVG_FOR_RANGE_QUERY, 5); |  | ||||||
|             data->lower_bound = sqlite3_column_int64(AVG_FOR_RANGE_QUERY, 6); |  | ||||||
|             data->temp = sqlite3_column_int(AVG_FOR_RANGE_QUERY, 7); |  | ||||||
|             data->humid = sqlite3_column_int(AVG_FOR_RANGE_QUERY, 8); |  | ||||||
|         } |  | ||||||
|     } else { |  | ||||||
|         success = false; |         success = false; | ||||||
|  |     } else if (data) { | ||||||
|  |         data->year = sqlite3_column_int(AVG_FOR_PERIOD_QUERY, 0); | ||||||
|  |         data->month = sqlite3_column_int(AVG_FOR_PERIOD_QUERY, 1); | ||||||
|  |         data->day = sqlite3_column_int(AVG_FOR_PERIOD_QUERY, 2); | ||||||
|  |         data->hour = sqlite3_column_int(AVG_FOR_PERIOD_QUERY, 3); | ||||||
|  |         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); | ||||||
|     } |     } | ||||||
|     sqlite3_reset(AVG_FOR_RANGE_QUERY); |     sqlite3_reset(AVG_FOR_PERIOD_QUERY); | ||||||
|     return success; |     return success; | ||||||
| } | } | ||||||
|  |  | ||||||
| @ -299,3 +315,19 @@ bool get_data_point_info(sqlite3 *db, int64_t time, UtilDataPointInfo *info) { | |||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | bool get_average_for_range(sqlite3 *db, uint64_t start, uint64_t end, | ||||||
|  |                            UtilAverageRange *data) { | ||||||
|  |     bool success = true; | ||||||
|  |     sqlite3_bind_int64(AVG_FOR_RANGE_QUERY, 1, start); | ||||||
|  |     sqlite3_bind_int64(AVG_FOR_RANGE_QUERY, 2, end); | ||||||
|  |     if (sqlite3_step(AVG_FOR_RANGE_QUERY) != SQLITE_ROW) { | ||||||
|  |         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); | ||||||
|  |     } | ||||||
|  |     sqlite3_reset(AVG_FOR_RANGE_QUERY); | ||||||
|  |     return success; | ||||||
|  | } | ||||||
|  | |||||||
							
								
								
									
										25
									
								
								src/util.h
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								src/util.h
									
									
									
									
									
								
							| @ -99,12 +99,14 @@ typedef struct { | |||||||
|     int utc_day; |     int utc_day; | ||||||
|     int utc_hour; |     int utc_hour; | ||||||
|     int utc_minute; |     int utc_minute; | ||||||
|  |     int utc_second; | ||||||
|     int64_t local; |     int64_t local; | ||||||
|     int local_year; |     int local_year; | ||||||
|     int local_month; |     int local_month; | ||||||
|     int local_day; |     int local_day; | ||||||
|     int local_hour; |     int local_hour; | ||||||
|     int local_minute; |     int local_minute; | ||||||
|  |     int local_second; | ||||||
| } UtilDate; | } UtilDate; | ||||||
|  |  | ||||||
| enum { | enum { | ||||||
| @ -120,14 +122,14 @@ extern const size_t NPERIOD; | |||||||
|  |  | ||||||
| /* | /* | ||||||
|  * Return the START of the first and END of the last PERIOD (ex. week) of DB. |  * Return the START of the first and END of the last PERIOD (ex. week) of DB. | ||||||
|  * Also get the hour and minute limits for the first and last day. |  * Also get the hour, minute, and second limits for the first and last day. | ||||||
|  * Return: false if an error occurred, true otherwise. |  * Return: false if an error occurred, true otherwise. | ||||||
|  */ |  */ | ||||||
| bool get_database_limits(sqlite3 *db, UtilPeriod period, UtilDate *start, | bool get_database_limits(sqlite3 *db, UtilPeriod period, UtilDate *start, | ||||||
|                          UtilDate *end); |                          UtilDate *end); | ||||||
|  |  | ||||||
| typedef struct { | typedef struct { | ||||||
|     int npoints; |     int64_t npoints; | ||||||
|     int temp; |     int temp; | ||||||
|     int humid; |     int humid; | ||||||
|     int year; |     int year; | ||||||
| @ -136,16 +138,16 @@ typedef struct { | |||||||
|     int hour; |     int hour; | ||||||
|     bool lower_bound; |     bool lower_bound; | ||||||
|     bool upper_bound; |     bool upper_bound; | ||||||
| } UtilAverageRange; | } UtilAveragePeriod; | ||||||
|  |  | ||||||
| /* | /* | ||||||
|  * Get the average temp. and humid. between YEAR-MONTH-DAY + COUNT * PERIOD and |  * Get the average temp. and humid. between YEAR-MONTH-DAY + COUNT * PERIOD and | ||||||
|  * YEAR-MONTH-DAY + (COUNT + 1) * PERIOD. |  * YEAR-MONTH-DAY + (COUNT + 1) * PERIOD. | ||||||
|  * Return: false if an error occurred, true otherwise. |  * Return: false if an error occurred, true otherwise. | ||||||
|  */ |  */ | ||||||
| bool get_average_for_range(sqlite3 *db, int year, int month, int day, | bool get_average_for_period(sqlite3 *db, int year, int month, int day, | ||||||
|                            int64_t count, UtilPeriod period, |                            int64_t count, UtilPeriod period, | ||||||
|                            UtilAverageRange *data); |                            UtilAveragePeriod *data); | ||||||
|  |  | ||||||
| typedef struct { | typedef struct { | ||||||
|     int64_t time; |     int64_t time; | ||||||
| @ -168,4 +170,17 @@ typedef struct { | |||||||
|  */ |  */ | ||||||
| bool get_data_point_info(sqlite3 *db, int64_t time, UtilDataPointInfo *info); | bool get_data_point_info(sqlite3 *db, int64_t time, UtilDataPointInfo *info); | ||||||
|  |  | ||||||
|  | typedef struct { | ||||||
|  |     int64_t npoints; | ||||||
|  |     int temp; | ||||||
|  |     int humid; | ||||||
|  | } UtilAverageRange; | ||||||
|  |  | ||||||
|  | /* | ||||||
|  |  * Get the average DATA for the range of UTC times START to END (inclusive). | ||||||
|  |  * Return: false if an error occurred, true otherwise. | ||||||
|  |  */ | ||||||
|  | bool get_average_for_range(sqlite3 *db, uint64_t start, uint64_t end, | ||||||
|  |                            UtilAverageRange *data); | ||||||
|  |  | ||||||
| #endif | #endif | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user