Work on the second stats screen
This commit is contained in:
		
							
								
								
									
										2
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								Makefile
									
									
									
									
									
								
							| @ -2,7 +2,7 @@ | |||||||
| .include "config.mk" | .include "config.mk" | ||||||
|  |  | ||||||
| CC=clang | CC=clang | ||||||
| CFLAGS=-std=c11 -Wall ${SQLITE3_CFLAGS} | 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/screen.c | SRCS=src/main.c src/util.c src/lcd.c src/ths.c src/button.c src/screen.c | ||||||
|  | |||||||
							
								
								
									
										17
									
								
								src/main.c
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								src/main.c
									
									
									
									
									
								
							| @ -91,14 +91,17 @@ int main(int argc, char *const *argv) { | |||||||
|                         GLOBAL_OPTS.data_pins[7]); |                         GLOBAL_OPTS.data_pins[7]); | ||||||
|     setup_signals(); |     setup_signals(); | ||||||
|     open_database(); |     open_database(); | ||||||
|  |     initialize_util_queries(DATABASE); | ||||||
|     pthread_t bg_update; |     pthread_t bg_update; | ||||||
|     start_update_thread(&bg_update); |     start_update_thread(&bg_update); | ||||||
|     ScreenManager *screen_manager = screen_manager_new(lcd, handle, |     ScreenManager *screen_manager = screen_manager_new(DATABASE, | ||||||
|  |                                                        lcd, handle, | ||||||
|                                                        GLOBAL_OPTS.back_pin, |                                                        GLOBAL_OPTS.back_pin, | ||||||
|                                                        GLOBAL_OPTS.up_pin, |                                                        GLOBAL_OPTS.up_pin, | ||||||
|                                                        GLOBAL_OPTS.down_pin, |                                                        GLOBAL_OPTS.down_pin, | ||||||
|                                                        GLOBAL_OPTS.sel_pin); |                                                        GLOBAL_OPTS.sel_pin); | ||||||
|     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()); | ||||||
|     while (RUNNING) { |     while (RUNNING) { | ||||||
|         lock_stat_globals(); |         lock_stat_globals(); | ||||||
|         uint32_t temp = LAST_TEMP; |         uint32_t temp = LAST_TEMP; | ||||||
| @ -115,6 +118,7 @@ int main(int argc, char *const *argv) { | |||||||
|         err(1, "join of background thread failed"); |         err(1, "join of background thread failed"); | ||||||
|     } |     } | ||||||
|     // this needs to be done after bg_update exits |     // this needs to be done after bg_update exits | ||||||
|  |     cleanup_util_queries(); | ||||||
|     sqlite3_close(DATABASE); |     sqlite3_close(DATABASE); | ||||||
|     pthread_mutex_destroy(&STAT_MUTEX); |     pthread_mutex_destroy(&STAT_MUTEX); | ||||||
|     gpio_close(handle); |     gpio_close(handle); | ||||||
| @ -386,12 +390,12 @@ void parse_config_file(const char *path) { | |||||||
|             .action = parse_uint_callback, |             .action = parse_uint_callback, | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|             .directive = "down_pin", |             .directive = "up_pin", | ||||||
|             .type = FIGPAR_TYPE_NONE, |             .type = FIGPAR_TYPE_NONE, | ||||||
|             .action = parse_uint_callback, |             .action = parse_uint_callback, | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|             .directive = "up_pin", |             .directive = "down_pin", | ||||||
|             .type = FIGPAR_TYPE_NONE, |             .type = FIGPAR_TYPE_NONE, | ||||||
|             .action = parse_uint_callback, |             .action = parse_uint_callback, | ||||||
|         }, |         }, | ||||||
| @ -430,6 +434,9 @@ void parse_config_file(const char *path) { | |||||||
| static void exit_signal_callback(int sig) { | static void exit_signal_callback(int sig) { | ||||||
|     LOG_VERBOSE("Caught signal %d. Exiting...\n", sig); |     LOG_VERBOSE("Caught signal %d. Exiting...\n", sig); | ||||||
|     RUNNING = false; |     RUNNING = false; | ||||||
|  |     if (signal(sig, SIG_DFL) == SIG_ERR) { | ||||||
|  |         err(1, "resetting signal handler failed"); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| #define SIGNAL_SETUP_CHECKED(sig) \ | #define SIGNAL_SETUP_CHECKED(sig) \ | ||||||
| @ -497,13 +504,13 @@ static void create_db_table() { | |||||||
|     LOG_VERBOSE("Ensured env_data table existance\n"); |     LOG_VERBOSE("Ensured env_data table existance\n"); | ||||||
| } | } | ||||||
|  |  | ||||||
| static const char *UPDATE_STATES_QUERY = | static const char *UPDATE_STATS_QUERY = | ||||||
|     "INSERT INTO env_data (time, temp, humid) " |     "INSERT INTO env_data (time, temp, humid) " | ||||||
|     "VALUES(?, ?, ?);"; |     "VALUES(?, ?, ?);"; | ||||||
| static void *update_thread_action(void *_ignored) { | static void *update_thread_action(void *_ignored) { | ||||||
|     create_db_table(); |     create_db_table(); | ||||||
|     sqlite3_stmt *insert_statement = NULL; |     sqlite3_stmt *insert_statement = NULL; | ||||||
|     int status = sqlite3_prepare_v2(DATABASE, UPDATE_STATES_QUERY, -1, |     int status = sqlite3_prepare_v2(DATABASE, UPDATE_STATS_QUERY, -1, | ||||||
|                                     &insert_statement, NULL); |                                     &insert_statement, NULL); | ||||||
|     if (status != SQLITE_OK) { |     if (status != SQLITE_OK) { | ||||||
|         errx(1, "could not compile SQL query. sqlite3 error follows:\n%s", |         errx(1, "could not compile SQL query. sqlite3 error follows:\n%s", | ||||||
|  | |||||||
							
								
								
									
										297
									
								
								src/screen.c
									
									
									
									
									
								
							
							
						
						
									
										297
									
								
								src/screen.c
									
									
									
									
									
								
							| @ -8,11 +8,12 @@ | |||||||
|  * version. See the LICENSE file for more information. |  * version. See the LICENSE file for more information. | ||||||
|  */ |  */ | ||||||
| #include "screen.h" | #include "screen.h" | ||||||
| #include "util.h" |  | ||||||
| #include "ths.h" | #include "ths.h" | ||||||
|  |  | ||||||
| #include <err.h> | #include <err.h> | ||||||
| #include <inttypes.h> | #include <inttypes.h> | ||||||
|  | #include <limits.h> | ||||||
|  | #include <string.h> | ||||||
|  |  | ||||||
|  |  | ||||||
| void screen_init(Screen *screen, const char *name, | void screen_init(Screen *screen, const char *name, | ||||||
| @ -30,10 +31,11 @@ void screen_delete(Screen *screen) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| ScreenManager *screen_manager_new(LCD *lcd, gpio_handle_t handle, | 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 back_pin, gpio_pin_t up_pin, | ||||||
|                                   gpio_pin_t down_pin, gpio_pin_t sel_pin) { |                                   gpio_pin_t down_pin, gpio_pin_t sel_pin) { | ||||||
|     ScreenManager *sm = malloc_checked(sizeof(ScreenManager)); |     ScreenManager *sm = malloc_checked(sizeof(ScreenManager)); | ||||||
|  |     sm->db = db; | ||||||
|     sm->lcd = lcd; |     sm->lcd = lcd; | ||||||
|     sm->screens = NULL; |     sm->screens = NULL; | ||||||
|     sm->screen_count = 0; |     sm->screen_count = 0; | ||||||
| @ -133,7 +135,7 @@ void screen_manager_dispatch(ScreenManager *sm, uint32_t temp, uint32_t humid) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| bool stats_screen_dispatch(StatsScreen *screen, SensorState *state) { | static bool stats_screen_dispatch(StatsScreen *screen, SensorState *state) { | ||||||
|     if (state->back_down) { |     if (state->back_down) { | ||||||
|         return true; |         return true; | ||||||
|     } |     } | ||||||
| @ -168,3 +170,292 @@ StatsScreen *stats_screen_new() { | |||||||
|     s->last_temp = 0; |     s->last_temp = 0; | ||||||
|     return s; |     return s; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | static const char *PERIOD_LABELS[] = { | ||||||
|  |     "HOUR", | ||||||
|  |     "DAY", | ||||||
|  |     "WEEK", | ||||||
|  |     "MONTH", | ||||||
|  |     "YEAR",  | ||||||
|  | }; | ||||||
|  | static const size_t NPERIOD = sizeof(PERIOD_LABELS) / sizeof(char *); | ||||||
|  |  | ||||||
|  | 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; | ||||||
|  |         switch (screen->period) { | ||||||
|  |         case 0: | ||||||
|  |         case 1: | ||||||
|  |         case 2: | ||||||
|  |             screen->ds.max_stage = DATE_SEL_DAY; | ||||||
|  |             break; | ||||||
|  |         case 3: | ||||||
|  |             screen->ds.max_stage = DATE_SEL_MONTH; | ||||||
|  |             break; | ||||||
|  |         case 4: | ||||||
|  |             screen->ds.max_stage = DATE_SEL_YEAR; | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |         screen->need_redraw = true; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void date_sel_init(DateSelection *ds, int start_line, int start_col, | ||||||
|  |                    DateSelectionStage max_stage) { | ||||||
|  |     ds->max_stage = max_stage; | ||||||
|  |     ds->start_line = start_line; | ||||||
|  |     ds->start_col = start_col; | ||||||
|  |     memset(&ds->start_time, 0, sizeof(UtilDate)); // min date | ||||||
|  |     memset(&ds->end_time, 0xff, sizeof(UtilDate)); // max date | ||||||
|  |     date_sel_reset(ds); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void date_sel_reset(DateSelection *ds) { | ||||||
|  |     ds->year = -1; | ||||||
|  |     ds->stage = DATE_SEL_YEAR; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static void date_sel_clamp_time(DateSelection *ds) { | ||||||
|  |     if (ds->year > ds->end_time.local_year) { | ||||||
|  |         ds->year = ds->end_time.local_year; | ||||||
|  |     } | ||||||
|  |     if (ds->year == ds->end_time.local_year && | ||||||
|  |         ds->month > ds->end_time.local_month) { | ||||||
|  |         ds->month = ds->end_time.local_month; | ||||||
|  |     } | ||||||
|  |     if (ds->month == ds->end_time.local_year && | ||||||
|  |         ds->month == ds->end_time.local_month && | ||||||
|  |         ds->day > ds->end_time.local_day) { | ||||||
|  |         printf("Clamp Day High!\n"); | ||||||
|  |         ds->day = ds->end_time.local_day; | ||||||
|  |     } | ||||||
|  |     if (ds->year < ds->start_time.local_year) { | ||||||
|  |         ds->year = ds->start_time.local_year; | ||||||
|  |     } | ||||||
|  |     if (ds->year == ds->start_time.local_year && | ||||||
|  |         ds->month < ds->start_time.local_month) { | ||||||
|  |         ds->month = ds->start_time.local_month; | ||||||
|  |     } | ||||||
|  |     if (ds->year == ds->start_time.local_year && | ||||||
|  |         ds->month == ds->start_time.local_month && | ||||||
|  |         ds->day < ds->start_time.local_day) { | ||||||
|  |         printf("Clamp Day Low!\n"); | ||||||
|  |         ds->day = ds->start_time.local_day; | ||||||
|  |     } | ||||||
|  |     printf("Max: %d %d %d\n", ds->end_time.local_year, ds->end_time.local_month, ds->end_time.local_day); | ||||||
|  |     printf("Min: %d %d %d\n", ds->start_time.local_year, ds->start_time.local_month, ds->start_time.local_day); | ||||||
|  |     printf("Cur: %d %d %d\n", ds->year, ds->month, ds->day); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static void date_sel_cleanup(DateSelection *ds) { | ||||||
|  |     if (ds->month < 1) { | ||||||
|  |         ds->month = 12 - ds->month; | ||||||
|  |         --ds->year; | ||||||
|  |     } else if (ds->month > 12) { | ||||||
|  |         ds->month = (ds->month % 13) + 1; | ||||||
|  |         ++ds->year; | ||||||
|  |     } | ||||||
|  |     int ndays = days_in_month(ds->month, ds->year); | ||||||
|  |     if (ds->day == 33) { | ||||||
|  |         ds->day = ndays; | ||||||
|  |     } else if (ds->day < 1) { | ||||||
|  |         ds->day = 33; // this means last day of month | ||||||
|  |         --ds->month; | ||||||
|  |         date_sel_cleanup(ds); | ||||||
|  |     } else if (ds->day > ndays) { | ||||||
|  |         ds->day = 1; | ||||||
|  |         ++ds->month; | ||||||
|  |         date_sel_cleanup(ds); | ||||||
|  |     } | ||||||
|  |     date_sel_clamp_time(ds); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static void date_sel_add_units(DateSelection *ds, int n) { | ||||||
|  |     switch (ds->stage) { | ||||||
|  |     case DATE_SEL_YEAR: | ||||||
|  |         ds->year += n; | ||||||
|  |         break; | ||||||
|  |     case DATE_SEL_MONTH: | ||||||
|  |         ds->month += n; | ||||||
|  |         break; | ||||||
|  |     case DATE_SEL_DAY: | ||||||
|  |         ds->day += n; | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |     date_sel_cleanup(ds); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | DateSelectionState date_sel_dispatch(DateSelection *ds, SensorState *state) { | ||||||
|  |     if (ds->year == -1) { | ||||||
|  |         time_t utc_time = time(NULL); | ||||||
|  |         struct tm local_time; | ||||||
|  |         localtime_r(&utc_time, &local_time); | ||||||
|  |         ds->year = local_time.tm_year + 1900; | ||||||
|  |         if (ds->max_stage > DATE_SEL_YEAR) { | ||||||
|  |             ds->month = local_time.tm_mon + 1; | ||||||
|  |         } | ||||||
|  |         if (ds->max_stage > DATE_SEL_MONTH) { | ||||||
|  |             ds->day = local_time.tm_mday; | ||||||
|  |         } | ||||||
|  |         date_sel_cleanup(ds); | ||||||
|  |     } | ||||||
|  |     if (ds->max_stage < DATE_SEL_MONTH) { | ||||||
|  |         ds->month = 1; | ||||||
|  |     } | ||||||
|  |     if (ds->max_stage < DATE_SEL_DAY) { | ||||||
|  |         ds->day = 1; | ||||||
|  |     } | ||||||
|  |     if (state->back_down) { | ||||||
|  |         if (ds->stage == DATE_SEL_YEAR) { | ||||||
|  |             return DATE_SEL_BACK; | ||||||
|  |         } | ||||||
|  |         --ds->stage; | ||||||
|  |     } | ||||||
|  |     if (state->up_down || state->down_down) { | ||||||
|  |         date_sel_add_units(ds, state->up_down - state->down_down); | ||||||
|  |     } | ||||||
|  |     if (state->sel_down) { | ||||||
|  |         if (ds->stage == ds->max_stage) { | ||||||
|  |             return DATE_SEL_DONE; | ||||||
|  |         } | ||||||
|  |         ++ds->stage; | ||||||
|  |     } | ||||||
|  |     int buf_size = 17 - ds->start_col; | ||||||
|  |     char buff[buf_size]; | ||||||
|  |     int cursor_pos; | ||||||
|  |     const char *format; | ||||||
|  |     switch (ds->stage) { | ||||||
|  |     case DATE_SEL_YEAR: | ||||||
|  |         cursor_pos = 0; | ||||||
|  |         format = ">%04d/%02d/%02d"; | ||||||
|  |         break; | ||||||
|  |     case DATE_SEL_MONTH: | ||||||
|  |         cursor_pos = 5; | ||||||
|  |         format = "%04d/>%02d/%02d"; | ||||||
|  |         break; | ||||||
|  |     case DATE_SEL_DAY: | ||||||
|  |         cursor_pos = 8; | ||||||
|  |         format = "%04d/%02d/>%02d"; | ||||||
|  |         break; | ||||||
|  |     default: | ||||||
|  |         LOG_VERBOSE("Date selector tried to select bad field\n"); | ||||||
|  |         return DATE_SEL_BACK; | ||||||
|  |     } | ||||||
|  |     snprintf(buff, buf_size, format, ds->year, ds->month, ds->day); | ||||||
|  |     cursor_pos += ds->start_col; | ||||||
|  |     lcd_move_to(state->lcd, ds->start_line, ds->start_col); | ||||||
|  |     lcd_write_string(state->lcd, buff); | ||||||
|  |     lcd_display_control(state->lcd, LCD_CURSOR_BLINK, LCD_CURSOR_ON, | ||||||
|  |                         LCD_DISPLAY_ON); | ||||||
|  |     lcd_move_to(state->lcd, ds->start_line, cursor_pos); | ||||||
|  |     return DATE_SEL_CONTINUE; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | 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; | ||||||
|  |         UtilDate start, end; | ||||||
|  |         if (!get_database_limits(state->db, PERIOD_LABELS[screen->period], | ||||||
|  |                                  &start, &end)) { | ||||||
|  |             warnx("failed to query database limits"); | ||||||
|  |             --screen->stage; | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         screen->ds.start_time = start; | ||||||
|  |         screen->ds.end_time = end; | ||||||
|  |     } | ||||||
|  |     if (screen->need_redraw) { | ||||||
|  |         lcd_clear(state->lcd); | ||||||
|  |         lcd_move_to(state->lcd, 0, 0); | ||||||
|  |         lcd_write_string(state->lcd, "Start: "); | ||||||
|  |         lcd_move_to(state->lcd, 1, 0); | ||||||
|  |         screen->need_redraw = false; | ||||||
|  |         DateSelectionState dss = date_sel_dispatch(&screen->ds, state); | ||||||
|  |         switch (dss) { | ||||||
|  |         case DATE_SEL_BACK: | ||||||
|  |             screen->need_redraw = true; | ||||||
|  |             lcd_display_control(state->lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF, | ||||||
|  |                                 LCD_DISPLAY_ON); | ||||||
|  |             --screen->stage; | ||||||
|  |             return; | ||||||
|  |         case DATE_SEL_CONTINUE: | ||||||
|  |             break; // ignore | ||||||
|  |         case DATE_SEL_DONE: | ||||||
|  |             ++screen->stage; | ||||||
|  |             screen->need_redraw = true; | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     }  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | 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->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: | ||||||
|  |         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) { | ||||||
|  |             lcd_clear(state->lcd); | ||||||
|  |             lcd_move_to(state->lcd, 0, 0); | ||||||
|  |             lcd_write_string(state->lcd, "Some data!"); | ||||||
|  |             screen->need_redraw = false; | ||||||
|  |         } | ||||||
|  |         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; | ||||||
|  |     date_sel_init(&s->ds, 1, 0, DATE_SEL_DAY); | ||||||
|  |     return s; | ||||||
|  | } | ||||||
|  | |||||||
							
								
								
									
										74
									
								
								src/screen.h
									
									
									
									
									
								
							
							
						
						
									
										74
									
								
								src/screen.h
									
									
									
									
									
								
							| @ -12,11 +12,14 @@ | |||||||
|  |  | ||||||
| #include "button.h" | #include "button.h" | ||||||
| #include "lcd.h" | #include "lcd.h" | ||||||
|  | #include "util.h" | ||||||
|  |  | ||||||
| #include <time.h> | #include <time.h> | ||||||
| #include <libgpio.h> | #include <libgpio.h> | ||||||
|  | #include <sqlite3.h> | ||||||
|  |  | ||||||
| typedef struct { | typedef struct { | ||||||
|  |     sqlite3 *db; | ||||||
|     LCD *lcd; |     LCD *lcd; | ||||||
|     uint32_t temp; |     uint32_t temp; | ||||||
|     uint32_t humid; |     uint32_t humid; | ||||||
| @ -52,6 +55,7 @@ void screen_init(Screen *screen, const char *name, | |||||||
| void screen_delete(Screen *screen); | void screen_delete(Screen *screen); | ||||||
|  |  | ||||||
| typedef struct { | typedef struct { | ||||||
|  |     sqlite3 *db; | ||||||
|     LCD *lcd; |     LCD *lcd; | ||||||
|     Button *back_btn; |     Button *back_btn; | ||||||
|     Button *up_btn; |     Button *up_btn; | ||||||
| @ -68,10 +72,11 @@ typedef struct { | |||||||
| } ScreenManager; | } ScreenManager; | ||||||
|  |  | ||||||
| /* | /* | ||||||
|  * Crate a new ScreenManager with the given LCD and buttons |  * Crate a new ScreenManager with the given DB, LCD and buttons. DB and LCD | ||||||
|  |  * still owned by the caller! | ||||||
|  * Return: the new ScreenManager |  * Return: the new ScreenManager | ||||||
|  */ |  */ | ||||||
| ScreenManager *screen_manager_new(LCD *lcd, gpio_handle_t handle, | 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 back_pin, gpio_pin_t up_pin, | ||||||
|                                   gpio_pin_t down_pin, gpio_pin_t sel_pin); |                                   gpio_pin_t down_pin, gpio_pin_t sel_pin); | ||||||
|  |  | ||||||
| @ -99,6 +104,71 @@ typedef struct { | |||||||
|     time_t last_min; |     time_t last_min; | ||||||
| } StatsScreen; | } StatsScreen; | ||||||
|  |  | ||||||
|  | /* | ||||||
|  |  * Create a new stats screen. This screen will display the current temp. and | ||||||
|  |  * humidity data from a THS sensor. | ||||||
|  |  */ | ||||||
| StatsScreen *stats_screen_new(void); | StatsScreen *stats_screen_new(void); | ||||||
|  |  | ||||||
|  | typedef enum { | ||||||
|  |     DATE_SEL_YEAR, | ||||||
|  |     DATE_SEL_MONTH, | ||||||
|  |     DATE_SEL_DAY, | ||||||
|  | } DateSelectionStage; | ||||||
|  |  | ||||||
|  | typedef struct { | ||||||
|  |     int year; | ||||||
|  |     int month; | ||||||
|  |     int day; | ||||||
|  |     DateSelectionStage stage; | ||||||
|  |     DateSelectionStage max_stage; | ||||||
|  |     int start_line; | ||||||
|  |     int start_col; | ||||||
|  |     UtilDate start_time; | ||||||
|  |     UtilDate end_time; | ||||||
|  | } DateSelection; | ||||||
|  |  | ||||||
|  | typedef enum { | ||||||
|  |     DATE_SEL_BACK, | ||||||
|  |     DATE_SEL_CONTINUE, | ||||||
|  |     DATE_SEL_DONE | ||||||
|  | } DateSelectionState; | ||||||
|  |  | ||||||
|  | /* | ||||||
|  |  * Initialize a DateSelection. START_LINE and START_COL are the first line and | ||||||
|  |  * column where it should draw the widget. MAX_STAGE is the most specific field | ||||||
|  |  * to select. | ||||||
|  |  */ | ||||||
|  | void date_sel_init(DateSelection *ds, int start_line, int start_col, | ||||||
|  |                    DateSelectionStage max_stage); | ||||||
|  |  | ||||||
|  | /* | ||||||
|  |  * Reset the date on DS. | ||||||
|  |  */ | ||||||
|  | void date_sel_reset(DateSelection *ds); | ||||||
|  |  | ||||||
|  | /* | ||||||
|  |  * Dispatch a DateSelection by processing STATE to continue the selection. | ||||||
|  |  * Return: weather the user backed out, is still working, or is done | ||||||
|  |  */ | ||||||
|  | DateSelectionState date_sel_dispatch(DateSelection *ds, SensorState *state); | ||||||
|  |  | ||||||
|  | typedef struct { | ||||||
|  |     Screen parent; | ||||||
|  |     enum { | ||||||
|  |         STATS_BY_SELECT_PERIOD = 0, | ||||||
|  |         STATS_BY_SELECT_START, | ||||||
|  |         STATS_BY_SHOWING, | ||||||
|  |     } stage; | ||||||
|  |     bool need_redraw; | ||||||
|  |     int period; | ||||||
|  |     DateSelection ds; | ||||||
|  | } StatsByScreen; | ||||||
|  |  | ||||||
|  | /* | ||||||
|  |  * Create a new stats by screen. This screen will display data from the database | ||||||
|  |  * by hour, day, week, month, and year. | ||||||
|  |  */ | ||||||
|  | StatsByScreen *stats_by_screen_new(void); | ||||||
|  |  | ||||||
| #endif | #endif | ||||||
|  | |||||||
							
								
								
									
										100
									
								
								src/util.c
									
									
									
									
									
								
							
							
						
						
									
										100
									
								
								src/util.c
									
									
									
									
									
								
							| @ -45,3 +45,103 @@ void *strdup_checked(const char *str) { | |||||||
|     } |     } | ||||||
|     return new_str; |     return new_str; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | int days_in_month(int m, int y) { | ||||||
|  |     switch (m) { | ||||||
|  |     case 2: | ||||||
|  |         if (y % 4 == 0 && (y % 100 != 0 || y % 400 == 0)) { | ||||||
|  |             return 29; | ||||||
|  |         } else { | ||||||
|  |             return 28; | ||||||
|  |         } | ||||||
|  |         break; | ||||||
|  |     case 1: | ||||||
|  |     case 3: | ||||||
|  |     case 5: | ||||||
|  |     case 7: | ||||||
|  |     case 8: | ||||||
|  |     case 10: | ||||||
|  |     case 12: | ||||||
|  |         return 31; | ||||||
|  |     default: | ||||||
|  |         return 30; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // is this even a C program anymore? | ||||||
|  | static const char *DB_LIMITS_QUERY_STR = | ||||||
|  |     "SELECT\n" | ||||||
|  |     "MinUTC,\n" | ||||||
|  |     // labels below are for debugging | ||||||
|  |     "strftime('%Y', MinUTC, 'unixepoch') as MinUTCYear,\n" | ||||||
|  |     "strftime('%m', MinUTC, 'unixepoch') as MinUTCMonth,\n" | ||||||
|  |     "strftime('%e', MinUTC, 'unixepoch') as MinUTCDay,\n" | ||||||
|  |     "MinLocal,\n" | ||||||
|  |     "strftime('%Y', MinLocal, 'unixepoch') as MinLocalYear,\n" | ||||||
|  |     "strftime('%m', MinLocal, 'unixepoch') as MinLocalMonth,\n" | ||||||
|  |     "strftime('%e', MinLocal, 'unixepoch') as MinLocalDay,\n" | ||||||
|  |     "MaxUTC,\n" | ||||||
|  |     "strftime('%Y', MaxUTC, 'unixepoch') as MaxUTCYear,\n" | ||||||
|  |     "strftime('%m', MaxUTC, 'unixepoch') as MaxUTCMonth,\n" | ||||||
|  |     "strftime('%e', MaxUTC, 'unixepoch') as MaxUTCDay,\n" | ||||||
|  |     "MaxLocal,\n" | ||||||
|  |     "strftime('%Y', MaxLocal, 'unixepoch') as MaxLocalYear,\n" | ||||||
|  |     "strftime('%m', MaxLocal, 'unixepoch') as MaxLocalMonth,\n" | ||||||
|  |     "strftime('%e', MaxLocal, 'unixepoch') as MaxLocalDay\n" | ||||||
|  |     "FROM\n" | ||||||
|  |     "(SELECT\n" | ||||||
|  |     "unixepoch(min(time), 'unixepoch', 'start of ' || ?1) as MinUTC,\n" | ||||||
|  |     "unixepoch(max(time), 'unixepoch', '+1 ' || ?1, 'start of ' || ?1, '-1 second') as MaxUTC,\n" | ||||||
|  |     "unixepoch(min(time), 'unixepoch', 'localtime', 'start of ' || ?1) as MinLocal,\n" | ||||||
|  |     "unixepoch(max(time), 'unixepoch', 'localtime', '+1 ' || ?1, 'start of ' || ?1, '-1 second') as MaxLocal\n" | ||||||
|  |     "FROM env_data);"; | ||||||
|  | static sqlite3_stmt *DB_LIMITS_QUERY; | ||||||
|  | void initialize_util_queries(sqlite3 *db) { | ||||||
|  |     int status = sqlite3_prepare_v2(db, DB_LIMITS_QUERY_STR, -1, | ||||||
|  |                                     &DB_LIMITS_QUERY, NULL); | ||||||
|  |     if (status != SQLITE_OK) { | ||||||
|  |         errx(1, "failed to compile limits query: %s", sqlite3_errstr(status)); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void cleanup_util_queries() { | ||||||
|  |     sqlite3_finalize(DB_LIMITS_QUERY); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool get_database_limits(sqlite3 *db, const char *period, UtilDate *start, | ||||||
|  |                          UtilDate *end) { | ||||||
|  |     if (strcasecmp(period, "week") == 0 || strcasecmp(period, "hour") == 0) { | ||||||
|  |         period = "day"; | ||||||
|  |     } | ||||||
|  |     bool success = true; | ||||||
|  |     sqlite3_bind_text(DB_LIMITS_QUERY, 1, period, -1, SQLITE_TRANSIENT); | ||||||
|  |     int status = sqlite3_step(DB_LIMITS_QUERY); | ||||||
|  |     if (status == SQLITE_ROW) { | ||||||
|  |         if (start) { | ||||||
|  |             start->utc = sqlite3_column_int64(DB_LIMITS_QUERY, 0); | ||||||
|  |             start->utc_year = sqlite3_column_int64(DB_LIMITS_QUERY, 1); | ||||||
|  |             start->utc_month = sqlite3_column_int64(DB_LIMITS_QUERY, 2); | ||||||
|  |             start->utc_day = sqlite3_column_int64(DB_LIMITS_QUERY, 3); | ||||||
|  |             start->local = sqlite3_column_int64(DB_LIMITS_QUERY, 4); | ||||||
|  |             start->local_year = sqlite3_column_int64(DB_LIMITS_QUERY, 5); | ||||||
|  |             start->local_month = sqlite3_column_int64(DB_LIMITS_QUERY, 6); | ||||||
|  |             start->local_day = sqlite3_column_int64(DB_LIMITS_QUERY, 7); | ||||||
|  |         } | ||||||
|  |         if (end) { | ||||||
|  |             end->utc = sqlite3_column_int64(DB_LIMITS_QUERY, 8); | ||||||
|  |             end->utc_year = sqlite3_column_int64(DB_LIMITS_QUERY, 9); | ||||||
|  |             end->utc_month = sqlite3_column_int64(DB_LIMITS_QUERY, 10); | ||||||
|  |             end->utc_day = sqlite3_column_int64(DB_LIMITS_QUERY, 11); | ||||||
|  |             end->local = sqlite3_column_int64(DB_LIMITS_QUERY, 12); | ||||||
|  |             end->local_year = sqlite3_column_int64(DB_LIMITS_QUERY, 13); | ||||||
|  |             end->local_month = sqlite3_column_int64(DB_LIMITS_QUERY, 14); | ||||||
|  |             end->local_day = sqlite3_column_int64(DB_LIMITS_QUERY, 15); | ||||||
|  |         } | ||||||
|  |     } else { | ||||||
|  |         success = false; | ||||||
|  |     } | ||||||
|  |     // unbind the string so it can be freed by the caller | ||||||
|  |     sqlite3_bind_null(DB_LIMITS_QUERY, 1); | ||||||
|  |     sqlite3_reset(DB_LIMITS_QUERY); | ||||||
|  |     return success; | ||||||
|  | } | ||||||
|  | |||||||
							
								
								
									
										34
									
								
								src/util.h
									
									
									
									
									
								
							
							
						
						
									
										34
									
								
								src/util.h
									
									
									
									
									
								
							| @ -17,6 +17,7 @@ | |||||||
| #include <stdbool.h> | #include <stdbool.h> | ||||||
| #include <sys/types.h> | #include <sys/types.h> | ||||||
| #include <libgpio.h> | #include <libgpio.h> | ||||||
|  | #include <sqlite3.h> | ||||||
|  |  | ||||||
| typedef struct { | typedef struct { | ||||||
|     char *config_path; // path to config file |     char *config_path; // path to config file | ||||||
| @ -76,4 +77,37 @@ void *strdup_checked(const char *str); | |||||||
|  */ |  */ | ||||||
| #define LOG_VERBOSE(...) if (GLOBAL_OPTS.verbose) {fprintf(stderr, __VA_ARGS__);} | #define LOG_VERBOSE(...) if (GLOBAL_OPTS.verbose) {fprintf(stderr, __VA_ARGS__);} | ||||||
|  |  | ||||||
|  | /* | ||||||
|  |  * Return: the number of days in month M. 1 is January. Y is the year. | ||||||
|  |  */ | ||||||
|  | int days_in_month(int m, int y); | ||||||
|  |  | ||||||
|  | /* | ||||||
|  |  * Initialize SQL queries used by this file. | ||||||
|  |  */ | ||||||
|  | void initialize_util_queries(sqlite3 *db); | ||||||
|  |  | ||||||
|  | /* | ||||||
|  |  * Cleanup SQL queries used by this file. | ||||||
|  |  */ | ||||||
|  | void cleanup_util_queries(void); | ||||||
|  |  | ||||||
|  | typedef struct { | ||||||
|  |     int64_t utc; | ||||||
|  |     int utc_year; | ||||||
|  |     int utc_month; | ||||||
|  |     int utc_day; | ||||||
|  |     int64_t local; | ||||||
|  |     int local_year; | ||||||
|  |     int local_month; | ||||||
|  |     int local_day; | ||||||
|  | } UtilDate; | ||||||
|  |  | ||||||
|  | /* | ||||||
|  |  * Return the START of the first and END of the last PERIOD (ex. week) of DB. | ||||||
|  |  * Return: false if an error occurred, true otherwise. | ||||||
|  |  */ | ||||||
|  | bool get_database_limits(sqlite3 *db, const char *period, UtilDate *start, | ||||||
|  |                          UtilDate *end); | ||||||
|  |  | ||||||
| #endif | #endif | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user