Work on the second stats screen
This commit is contained in:
		
							
								
								
									
										17
									
								
								src/main.c
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								src/main.c
									
									
									
									
									
								
							| @ -91,14 +91,17 @@ int main(int argc, char *const *argv) { | ||||
|                         GLOBAL_OPTS.data_pins[7]); | ||||
|     setup_signals(); | ||||
|     open_database(); | ||||
|     initialize_util_queries(DATABASE); | ||||
|     pthread_t 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.up_pin, | ||||
|                                                        GLOBAL_OPTS.down_pin, | ||||
|                                                        GLOBAL_OPTS.sel_pin); | ||||
|     screen_manager_add(screen_manager, (Screen *) stats_screen_new()); | ||||
|     screen_manager_add(screen_manager, (Screen *) stats_by_screen_new()); | ||||
|     while (RUNNING) { | ||||
|         lock_stat_globals(); | ||||
|         uint32_t temp = LAST_TEMP; | ||||
| @ -115,6 +118,7 @@ int main(int argc, char *const *argv) { | ||||
|         err(1, "join of background thread failed"); | ||||
|     } | ||||
|     // this needs to be done after bg_update exits | ||||
|     cleanup_util_queries(); | ||||
|     sqlite3_close(DATABASE); | ||||
|     pthread_mutex_destroy(&STAT_MUTEX); | ||||
|     gpio_close(handle); | ||||
| @ -386,12 +390,12 @@ void parse_config_file(const char *path) { | ||||
|             .action = parse_uint_callback, | ||||
|         }, | ||||
|         { | ||||
|             .directive = "down_pin", | ||||
|             .directive = "up_pin", | ||||
|             .type = FIGPAR_TYPE_NONE, | ||||
|             .action = parse_uint_callback, | ||||
|         }, | ||||
|         { | ||||
|             .directive = "up_pin", | ||||
|             .directive = "down_pin", | ||||
|             .type = FIGPAR_TYPE_NONE, | ||||
|             .action = parse_uint_callback, | ||||
|         }, | ||||
| @ -430,6 +434,9 @@ void parse_config_file(const char *path) { | ||||
| static void exit_signal_callback(int sig) { | ||||
|     LOG_VERBOSE("Caught signal %d. Exiting...\n", sig); | ||||
|     RUNNING = false; | ||||
|     if (signal(sig, SIG_DFL) == SIG_ERR) { | ||||
|         err(1, "resetting signal handler failed"); | ||||
|     } | ||||
| } | ||||
|  | ||||
| #define SIGNAL_SETUP_CHECKED(sig) \ | ||||
| @ -497,13 +504,13 @@ static void create_db_table() { | ||||
|     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) " | ||||
|     "VALUES(?, ?, ?);"; | ||||
| static void *update_thread_action(void *_ignored) { | ||||
|     create_db_table(); | ||||
|     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); | ||||
|     if (status != SQLITE_OK) { | ||||
|         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. | ||||
|  */ | ||||
| #include "screen.h" | ||||
| #include "util.h" | ||||
| #include "ths.h" | ||||
|  | ||||
| #include <err.h> | ||||
| #include <inttypes.h> | ||||
| #include <limits.h> | ||||
| #include <string.h> | ||||
|  | ||||
|  | ||||
| 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 down_pin, gpio_pin_t sel_pin) { | ||||
|     ScreenManager *sm = malloc_checked(sizeof(ScreenManager)); | ||||
|     sm->db = db; | ||||
|     sm->lcd = lcd; | ||||
|     sm->screens = NULL; | ||||
|     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) { | ||||
|         return true; | ||||
|     } | ||||
| @ -168,3 +170,292 @@ StatsScreen *stats_screen_new() { | ||||
|     s->last_temp = 0; | ||||
|     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 "lcd.h" | ||||
| #include "util.h" | ||||
|  | ||||
| #include <time.h> | ||||
| #include <libgpio.h> | ||||
| #include <sqlite3.h> | ||||
|  | ||||
| typedef struct { | ||||
|     sqlite3 *db; | ||||
|     LCD *lcd; | ||||
|     uint32_t temp; | ||||
|     uint32_t humid; | ||||
| @ -52,6 +55,7 @@ void screen_init(Screen *screen, const char *name, | ||||
| void screen_delete(Screen *screen); | ||||
|  | ||||
| typedef struct { | ||||
|     sqlite3 *db; | ||||
|     LCD *lcd; | ||||
|     Button *back_btn; | ||||
|     Button *up_btn; | ||||
| @ -68,10 +72,11 @@ typedef struct { | ||||
| } 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 | ||||
|  */ | ||||
| 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 down_pin, gpio_pin_t sel_pin); | ||||
|  | ||||
| @ -99,6 +104,71 @@ typedef struct { | ||||
|     time_t last_min; | ||||
| } 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); | ||||
|  | ||||
| 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 | ||||
|  | ||||
							
								
								
									
										100
									
								
								src/util.c
									
									
									
									
									
								
							
							
						
						
									
										100
									
								
								src/util.c
									
									
									
									
									
								
							| @ -45,3 +45,103 @@ void *strdup_checked(const char *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 <sys/types.h> | ||||
| #include <libgpio.h> | ||||
| #include <sqlite3.h> | ||||
|  | ||||
| typedef struct { | ||||
|     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__);} | ||||
|  | ||||
| /* | ||||
|  * 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 | ||||
|  | ||||
		Reference in New Issue
	
	Block a user