Save temperature and humidity in sqlite3 database
This commit is contained in:
		
							
								
								
									
										89
									
								
								src/main.c
									
									
									
									
									
								
							
							
						
						
									
										89
									
								
								src/main.c
									
									
									
									
									
								
							| @ -28,6 +28,7 @@ | ||||
| #include <pthread.h> | ||||
| #include <stdatomic.h> | ||||
| #include <signal.h> | ||||
| #include <sqlite3.h> | ||||
|  | ||||
| /* | ||||
|  * Print help message to standard output. | ||||
| @ -41,24 +42,28 @@ void parse_arguments(int argc, char *const *argv); | ||||
|  * Parse config file PATH. | ||||
|  */ | ||||
| void parse_config_file(const char *path); | ||||
| /* | ||||
|  * Read the temp. and humid., store it in the configured database, and output it | ||||
|  * to the given uint32_t pointers. | ||||
|  */ | ||||
| void update_stats(THS *ths, struct timespec *cur_time); | ||||
| /* | ||||
|  * Setup signals used to ensure clean termination. | ||||
|  */ | ||||
| void setup_signals(void); | ||||
| /* | ||||
|  * Open an sqlite3 database connection. | ||||
|  */ | ||||
| void open_database(void); | ||||
| /* | ||||
|  * Read the temp. and humid., store it in the configured database, and output it | ||||
|  * to the given uint32_t pointers. | ||||
|  */ | ||||
| void update_stats(THS *ths, sqlite3_stmt *insert_statement); | ||||
| /* | ||||
|  * Start the thread used to update the temp. and humid. and record them. | ||||
|  */ | ||||
| void start_update_thread(pthread_t *thread); | ||||
|  | ||||
| // cross thread variables | ||||
| sqlite3 *DATABASE; | ||||
| pthread_mutex_t STAT_MUTEX; | ||||
| uint32_t LAST_TEMP, LAST_HUMID; | ||||
| time_t LAST_READ_SEC;  | ||||
| _Atomic bool RUNNING = true; | ||||
| /* | ||||
|  * Lock the cross thread variables above. | ||||
| @ -85,6 +90,7 @@ int main(int argc, char *const *argv) { | ||||
|                         GLOBAL_OPTS.data_pins[5], GLOBAL_OPTS.data_pins[6], | ||||
|                         GLOBAL_OPTS.data_pins[7]); | ||||
|     setup_signals(); | ||||
|     open_database(); | ||||
|     pthread_t bg_update; | ||||
|     start_update_thread(&bg_update); | ||||
|     ScreenManager *screen_manager = screen_manager_new(lcd, handle, | ||||
| @ -105,9 +111,11 @@ int main(int argc, char *const *argv) { | ||||
|     screen_manager_delete(screen_manager); | ||||
|     lcd_close(lcd); | ||||
|     if (pthread_join(bg_update, NULL) != 0) { | ||||
|         sqlite3_close(DATABASE); // hopefully prevent data loss | ||||
|         err(1, "join of background thread failed"); | ||||
|     } | ||||
|     // this needs to be done after bg_update exits | ||||
|     sqlite3_close(DATABASE); | ||||
|     pthread_mutex_destroy(&STAT_MUTEX); | ||||
|     gpio_close(handle); | ||||
|     cleanup_options(&GLOBAL_OPTS); | ||||
| @ -281,8 +289,9 @@ static void set_options_from_entries(struct figpar_config *entries, | ||||
|     GLOBAL_OPTS.fail_limit = entries[8].value.u_num; | ||||
|  | ||||
|     entries[9].type = FIGPAR_TYPE_NONE; | ||||
|     GLOBAL_OPTS.lcd_version = entries[9].value.str; | ||||
|     LOG_VERBOSE("Using lcd_version: \"%s\"\n", GLOBAL_OPTS.lcd_version); | ||||
|     GLOBAL_OPTS.database_location = entries[9].value.str; | ||||
|     LOG_VERBOSE("Using database_location: \"%s\"\n", | ||||
|                 GLOBAL_OPTS.database_location); | ||||
|  | ||||
|     GLOBAL_OPTS.refresh_time = entries[10].value.u_num; | ||||
|  | ||||
| @ -360,10 +369,10 @@ void parse_config_file(const char *path) { | ||||
|             .value = {.u_num = DEFAULT_FAIL_LIMIT}, | ||||
|         }, | ||||
|         { | ||||
|             .directive = "lcd_version", | ||||
|             .directive = "database_location", | ||||
|             .type = FIGPAR_TYPE_STR, | ||||
|             .action = parse_str_callback, | ||||
|             .value = {.str = strdup_default_opt(DEFAULT_LCD_VERSION)}, | ||||
|             .value = {.str = strdup_default_opt(DEFAULT_DATABASE_LOCATION)}, | ||||
|         }, | ||||
|         { | ||||
|             .directive = "refresh_time", | ||||
| @ -432,23 +441,74 @@ void setup_signals() { | ||||
|     SIGNAL_SETUP_CHECKED(SIGINT); | ||||
| } | ||||
|  | ||||
| void update_stats(THS *ths, struct timespec *cur_time) { | ||||
| void open_database() { | ||||
|     int status = sqlite3_config(SQLITE_CONFIG_SERIALIZED); | ||||
|     if (status != SQLITE_OK) { | ||||
|         errx(1, "failed to enable multi-thread mode for sqlite: %s", | ||||
|              sqlite3_errstr(status)); | ||||
|     } | ||||
|     status = sqlite3_open(GLOBAL_OPTS.database_location, &DATABASE); | ||||
|     if (status != SQLITE_OK) { | ||||
|         sqlite3_close(DATABASE); | ||||
|         errx(1, "failed to open database: %s", | ||||
|              sqlite3_errstr(status)); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void update_stats(THS *ths, sqlite3_stmt *insert_statement) { | ||||
|     if (GLOBAL_OPTS.fail_key && ths_read_fails(ths) > GLOBAL_OPTS.fail_limit) { | ||||
|         errx(1, "THS fail limit reached"); | ||||
|     } | ||||
|     uint32_t temp = ths_read_temp(ths); | ||||
|     uint32_t humid = ths_read_humid(ths); | ||||
|     time_t read_time = time(NULL); | ||||
|     LOG_VERBOSE("Temp. and humid. read\n"); | ||||
|     // TODO store in database | ||||
|  | ||||
|     lock_stat_globals(); | ||||
|     LAST_TEMP = temp; | ||||
|     LAST_HUMID = humid; | ||||
|     LAST_READ_SEC = cur_time->tv_sec; | ||||
|     unlock_stat_globals(); | ||||
|  | ||||
|     sqlite3_reset(insert_statement); | ||||
|     sqlite3_bind_int64(insert_statement, 1, read_time); | ||||
|     sqlite3_bind_int(insert_statement, 2, temp); | ||||
|     sqlite3_bind_int(insert_statement, 3, humid); | ||||
|     int status = sqlite3_step(insert_statement); | ||||
|     if (status != SQLITE_DONE) { | ||||
|         errx(1, "failed to insert temp. and humid. data into database: %s", | ||||
|              sqlite3_errstr(status)); | ||||
|     } | ||||
| } | ||||
|  | ||||
| static const char *CREATE_DB_TABLE_QUERY = | ||||
|     "CREATE TABLE IF NOT EXISTS env_data(" | ||||
|     "time INTEGER PRIMARY KEY," | ||||
|     "temp INTEGER," | ||||
|     "humid INTEGER" | ||||
|     ");"; | ||||
| static void create_db_table() { | ||||
|     char *errmsg; | ||||
|     int status = sqlite3_exec(DATABASE, CREATE_DB_TABLE_QUERY, NULL, NULL, | ||||
|                               &errmsg); | ||||
|     if (status != SQLITE_OK) { | ||||
|         errx(1, "could not create table. sqlite3 error follows:\n%s", | ||||
|              errmsg ? errmsg : "No message generated"); | ||||
|     } | ||||
|     LOG_VERBOSE("Ensured env_data table existance\n"); | ||||
| } | ||||
|  | ||||
| static const char *UPDATE_STATES_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, | ||||
|                                     &insert_statement, NULL); | ||||
|     if (status != SQLITE_OK) { | ||||
|         errx(1, "could not compile SQL query. sqlite3 error follows:\n%s", | ||||
|              sqlite3_errstr(status)); | ||||
|     } | ||||
|     sigset_t to_block; | ||||
|     sigemptyset(&to_block); | ||||
|     sigaddset(&to_block, SIGTERM); | ||||
| @ -468,11 +528,12 @@ static void *update_thread_action(void *_ignored) { | ||||
|         clock_gettime(CLOCK_UPTIME, &cur_time); | ||||
|         timespecsub(&cur_time, &last_update, &diff_time); | ||||
|         if (timespeccmp(&diff_time, &refresh_time, >=)) { | ||||
|             update_stats(ths, &cur_time); | ||||
|             update_stats(ths, insert_statement); | ||||
|             last_update = cur_time; | ||||
|         } | ||||
|         usleep(10 * 1000); // 10ms, a reasonable delay | ||||
|     } | ||||
|     sqlite3_finalize(insert_statement); | ||||
|     ths_close(ths); | ||||
|     return NULL; | ||||
| } | ||||
|  | ||||
							
								
								
									
										10
									
								
								src/screen.c
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								src/screen.c
									
									
									
									
									
								
							| @ -37,7 +37,7 @@ ScreenManager *screen_manager_new(LCD *lcd, gpio_handle_t handle, | ||||
|     sm->lcd = lcd; | ||||
|     sm->screens = NULL; | ||||
|     sm->screen_count = 0; | ||||
|     sm->cur_scren = -1; | ||||
|     sm->cur_screen = 0; | ||||
|     sm->up_btn = button_new(handle, up_pin); | ||||
|     sm->down_btn = button_new(handle, down_pin); | ||||
|     sm->back_btn = button_new(handle, back_pin); | ||||
| @ -98,7 +98,7 @@ static void screen_manager_dispatch_select_menu(ScreenManager *sm) { | ||||
|         } | ||||
|     } | ||||
|     if (button_pressed(sm->sel_btn)) { | ||||
|         sm->cur_scren = sm->cur_menu_line; | ||||
|         sm->cur_screen = sm->cur_menu_line; | ||||
|         sm->force_draw = true; | ||||
|     } | ||||
| } | ||||
| @ -107,10 +107,10 @@ void screen_manager_dispatch(ScreenManager *sm, uint32_t temp, uint32_t humid) { | ||||
|     if (!sm->screen_count) { | ||||
|         errx(1, "attempt to display empty screen manager"); | ||||
|     } | ||||
|     if (sm->cur_scren < 0) { | ||||
|     if (sm->cur_screen < 0) { | ||||
|         screen_manager_dispatch_select_menu(sm); | ||||
|     } else { | ||||
|         Screen *cs = sm->screens[sm->cur_scren]; | ||||
|         Screen *cs = sm->screens[sm->cur_screen]; | ||||
|         if (cs->dispatch_func) { | ||||
|             SensorState state = { | ||||
|                 .lcd = sm->lcd, | ||||
| @ -124,7 +124,7 @@ void screen_manager_dispatch(ScreenManager *sm, uint32_t temp, uint32_t humid) { | ||||
|             }; | ||||
|             if (cs->dispatch_func(cs, &state)) { | ||||
|                 // if this is true, it means return to the main menu | ||||
|                 sm->cur_scren = -1; | ||||
|                 sm->cur_screen = -1; | ||||
|                 sm->force_draw = true; | ||||
|             } else { | ||||
|                 sm->force_draw = false; | ||||
|  | ||||
| @ -61,7 +61,7 @@ typedef struct { | ||||
|     size_t screen_count; | ||||
|  | ||||
|     // internal | ||||
|     ssize_t cur_scren; | ||||
|     ssize_t cur_screen; | ||||
|     size_t cur_menu_line; | ||||
|     size_t last_drawn_menu_line; | ||||
|     bool force_draw; | ||||
|  | ||||
| @ -20,7 +20,7 @@ void cleanup_options(Options *opts) { | ||||
|     FREE_CHECKED(opts->temp_key); | ||||
|     FREE_CHECKED(opts->humid_key); | ||||
|     FREE_CHECKED(opts->fail_key); | ||||
|     FREE_CHECKED(opts->lcd_version); | ||||
|     FREE_CHECKED(opts->database_location); | ||||
| } | ||||
|  | ||||
| void *malloc_checked(size_t n) { | ||||
|  | ||||
| @ -28,7 +28,7 @@ typedef struct { | ||||
|     gpio_pin_t rs_pin; | ||||
|     gpio_pin_t rw_pin; | ||||
|     gpio_pin_t data_pins[8]; | ||||
|     char *lcd_version; // Is LCD jp or eu | ||||
|     char *database_location; // location of sqlite3 database | ||||
|     char *temp_key; // sysctl key for temperature | ||||
|     char *humid_key; // sysctl key for humidity | ||||
|     char *fail_key; // sysctl key for number of fails | ||||
|  | ||||
		Reference in New Issue
	
	Block a user