From 81eb22e83a1367b7e6813cac943a7c6c1a0eb528 Mon Sep 17 00:00:00 2001 From: Alexander Rosenberg Date: Fri, 1 Mar 2024 05:43:37 -0800 Subject: [PATCH] Save temperature and humidity in sqlite3 database --- Makefile | 2 +- config.mk | 2 +- src/main.c | 89 +++++++++++++++++++++++++++++++++++++++++++--------- src/screen.c | 10 +++--- src/screen.h | 2 +- src/util.c | 2 +- src/util.h | 2 +- 7 files changed, 85 insertions(+), 24 deletions(-) diff --git a/Makefile b/Makefile index b431507..e69d3f0 100644 --- a/Makefile +++ b/Makefile @@ -27,7 +27,7 @@ ${OBJS}: ${.TARGET:C/^bin/src/:C/.o$/.c/} -DDEFAULT_HUMID_KEY="\"${DEFAULT_HUMID_KEY}\""\ -DDEFAULT_FAIL_KEY="\"${DEFAULT_FAIL_KEY}\""\ -DDEFAULT_FAIL_LIMIT="${DEFAULT_FAIL_LIMIT}"\ --DDEFAULT_LCD_VERSION="\"${DEFAULT_LCD_VERSION}\""\ +-DDEFAULT_DATABASE_LOCATION="\"${DEFAULT_DATABASE_LOCATION}\""\ -DDEFAULT_REFRESH_TIME="${DEFAULT_REFRESH_TIME}"\ -o ${@} ${.TARGET:C/^bin/src/:C/.o$/.c/} diff --git a/config.mk b/config.mk index f61da81..f245740 100644 --- a/config.mk +++ b/config.mk @@ -9,5 +9,5 @@ DEFAULT_TEMP_KEY=dev.gpioths.0.temperature DEFAULT_HUMID_KEY=dev.gpioths.0.humidity DEFAULT_FAIL_KEY=dev.gpioths.0.fails DEFAULT_FAIL_LIMIT=5 -DEFAULT_LCD_VERSION=jp +DEFAULT_DATABASE_LOCATION=/var/db/rpi4-temp-humidity/db.sqlite DEFAULT_REFRESH_TIME=5000 diff --git a/src/main.c b/src/main.c index ed79904..0edd289 100644 --- a/src/main.c +++ b/src/main.c @@ -28,6 +28,7 @@ #include #include #include +#include /* * 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; } diff --git a/src/screen.c b/src/screen.c index 115611c..f6e4b3d 100644 --- a/src/screen.c +++ b/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; diff --git a/src/screen.h b/src/screen.h index 7e77c55..adb934d 100644 --- a/src/screen.h +++ b/src/screen.h @@ -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; diff --git a/src/util.c b/src/util.c index 0e68bf6..f291604 100644 --- a/src/util.c +++ b/src/util.c @@ -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) { diff --git a/src/util.h b/src/util.h index 6b24570..b1ba2e6 100644 --- a/src/util.h +++ b/src/util.h @@ -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