Save temperature and humidity in sqlite3 database

This commit is contained in:
Alexander Rosenberg 2024-03-01 05:43:37 -08:00
parent 57974cc025
commit 81eb22e83a
Signed by: school-rpi4
GPG Key ID: 5CCFC80B0B47B04B
7 changed files with 85 additions and 24 deletions

View File

@ -27,7 +27,7 @@ ${OBJS}: ${.TARGET:C/^bin/src/:C/.o$/.c/}
-DDEFAULT_HUMID_KEY="\"${DEFAULT_HUMID_KEY}\""\ -DDEFAULT_HUMID_KEY="\"${DEFAULT_HUMID_KEY}\""\
-DDEFAULT_FAIL_KEY="\"${DEFAULT_FAIL_KEY}\""\ -DDEFAULT_FAIL_KEY="\"${DEFAULT_FAIL_KEY}\""\
-DDEFAULT_FAIL_LIMIT="${DEFAULT_FAIL_LIMIT}"\ -DDEFAULT_FAIL_LIMIT="${DEFAULT_FAIL_LIMIT}"\
-DDEFAULT_LCD_VERSION="\"${DEFAULT_LCD_VERSION}\""\ -DDEFAULT_DATABASE_LOCATION="\"${DEFAULT_DATABASE_LOCATION}\""\
-DDEFAULT_REFRESH_TIME="${DEFAULT_REFRESH_TIME}"\ -DDEFAULT_REFRESH_TIME="${DEFAULT_REFRESH_TIME}"\
-o ${@} ${.TARGET:C/^bin/src/:C/.o$/.c/} -o ${@} ${.TARGET:C/^bin/src/:C/.o$/.c/}

View File

@ -9,5 +9,5 @@ DEFAULT_TEMP_KEY=dev.gpioths.0.temperature
DEFAULT_HUMID_KEY=dev.gpioths.0.humidity DEFAULT_HUMID_KEY=dev.gpioths.0.humidity
DEFAULT_FAIL_KEY=dev.gpioths.0.fails DEFAULT_FAIL_KEY=dev.gpioths.0.fails
DEFAULT_FAIL_LIMIT=5 DEFAULT_FAIL_LIMIT=5
DEFAULT_LCD_VERSION=jp DEFAULT_DATABASE_LOCATION=/var/db/rpi4-temp-humidity/db.sqlite
DEFAULT_REFRESH_TIME=5000 DEFAULT_REFRESH_TIME=5000

View File

@ -28,6 +28,7 @@
#include <pthread.h> #include <pthread.h>
#include <stdatomic.h> #include <stdatomic.h>
#include <signal.h> #include <signal.h>
#include <sqlite3.h>
/* /*
* Print help message to standard output. * Print help message to standard output.
@ -41,24 +42,28 @@ void parse_arguments(int argc, char *const *argv);
* Parse config file PATH. * Parse config file PATH.
*/ */
void parse_config_file(const char *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. * Setup signals used to ensure clean termination.
*/ */
void setup_signals(void); 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. * Start the thread used to update the temp. and humid. and record them.
*/ */
void start_update_thread(pthread_t *thread); void start_update_thread(pthread_t *thread);
// cross thread variables // cross thread variables
sqlite3 *DATABASE;
pthread_mutex_t STAT_MUTEX; pthread_mutex_t STAT_MUTEX;
uint32_t LAST_TEMP, LAST_HUMID; uint32_t LAST_TEMP, LAST_HUMID;
time_t LAST_READ_SEC;
_Atomic bool RUNNING = true; _Atomic bool RUNNING = true;
/* /*
* Lock the cross thread variables above. * 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[5], GLOBAL_OPTS.data_pins[6],
GLOBAL_OPTS.data_pins[7]); GLOBAL_OPTS.data_pins[7]);
setup_signals(); setup_signals();
open_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(lcd, handle,
@ -105,9 +111,11 @@ int main(int argc, char *const *argv) {
screen_manager_delete(screen_manager); screen_manager_delete(screen_manager);
lcd_close(lcd); lcd_close(lcd);
if (pthread_join(bg_update, NULL) != 0) { if (pthread_join(bg_update, NULL) != 0) {
sqlite3_close(DATABASE); // hopefully prevent data loss
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
sqlite3_close(DATABASE);
pthread_mutex_destroy(&STAT_MUTEX); pthread_mutex_destroy(&STAT_MUTEX);
gpio_close(handle); gpio_close(handle);
cleanup_options(&GLOBAL_OPTS); 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; GLOBAL_OPTS.fail_limit = entries[8].value.u_num;
entries[9].type = FIGPAR_TYPE_NONE; entries[9].type = FIGPAR_TYPE_NONE;
GLOBAL_OPTS.lcd_version = entries[9].value.str; GLOBAL_OPTS.database_location = entries[9].value.str;
LOG_VERBOSE("Using lcd_version: \"%s\"\n", GLOBAL_OPTS.lcd_version); LOG_VERBOSE("Using database_location: \"%s\"\n",
GLOBAL_OPTS.database_location);
GLOBAL_OPTS.refresh_time = entries[10].value.u_num; 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}, .value = {.u_num = DEFAULT_FAIL_LIMIT},
}, },
{ {
.directive = "lcd_version", .directive = "database_location",
.type = FIGPAR_TYPE_STR, .type = FIGPAR_TYPE_STR,
.action = parse_str_callback, .action = parse_str_callback,
.value = {.str = strdup_default_opt(DEFAULT_LCD_VERSION)}, .value = {.str = strdup_default_opt(DEFAULT_DATABASE_LOCATION)},
}, },
{ {
.directive = "refresh_time", .directive = "refresh_time",
@ -432,23 +441,74 @@ void setup_signals() {
SIGNAL_SETUP_CHECKED(SIGINT); 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) { if (GLOBAL_OPTS.fail_key && ths_read_fails(ths) > GLOBAL_OPTS.fail_limit) {
errx(1, "THS fail limit reached"); errx(1, "THS fail limit reached");
} }
uint32_t temp = ths_read_temp(ths); uint32_t temp = ths_read_temp(ths);
uint32_t humid = ths_read_humid(ths); uint32_t humid = ths_read_humid(ths);
time_t read_time = time(NULL);
LOG_VERBOSE("Temp. and humid. read\n"); LOG_VERBOSE("Temp. and humid. read\n");
// TODO store in database
lock_stat_globals(); lock_stat_globals();
LAST_TEMP = temp; LAST_TEMP = temp;
LAST_HUMID = humid; LAST_HUMID = humid;
LAST_READ_SEC = cur_time->tv_sec;
unlock_stat_globals(); 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) { 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; sigset_t to_block;
sigemptyset(&to_block); sigemptyset(&to_block);
sigaddset(&to_block, SIGTERM); sigaddset(&to_block, SIGTERM);
@ -468,11 +528,12 @@ static void *update_thread_action(void *_ignored) {
clock_gettime(CLOCK_UPTIME, &cur_time); clock_gettime(CLOCK_UPTIME, &cur_time);
timespecsub(&cur_time, &last_update, &diff_time); timespecsub(&cur_time, &last_update, &diff_time);
if (timespeccmp(&diff_time, &refresh_time, >=)) { if (timespeccmp(&diff_time, &refresh_time, >=)) {
update_stats(ths, &cur_time); update_stats(ths, insert_statement);
last_update = cur_time; last_update = cur_time;
} }
usleep(10 * 1000); // 10ms, a reasonable delay usleep(10 * 1000); // 10ms, a reasonable delay
} }
sqlite3_finalize(insert_statement);
ths_close(ths); ths_close(ths);
return NULL; return NULL;
} }

View File

@ -37,7 +37,7 @@ ScreenManager *screen_manager_new(LCD *lcd, gpio_handle_t handle,
sm->lcd = lcd; sm->lcd = lcd;
sm->screens = NULL; sm->screens = NULL;
sm->screen_count = 0; sm->screen_count = 0;
sm->cur_scren = -1; sm->cur_screen = 0;
sm->up_btn = button_new(handle, up_pin); sm->up_btn = button_new(handle, up_pin);
sm->down_btn = button_new(handle, down_pin); sm->down_btn = button_new(handle, down_pin);
sm->back_btn = button_new(handle, back_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)) { if (button_pressed(sm->sel_btn)) {
sm->cur_scren = sm->cur_menu_line; sm->cur_screen = sm->cur_menu_line;
sm->force_draw = true; sm->force_draw = true;
} }
} }
@ -107,10 +107,10 @@ void screen_manager_dispatch(ScreenManager *sm, uint32_t temp, uint32_t humid) {
if (!sm->screen_count) { if (!sm->screen_count) {
errx(1, "attempt to display empty screen manager"); errx(1, "attempt to display empty screen manager");
} }
if (sm->cur_scren < 0) { if (sm->cur_screen < 0) {
screen_manager_dispatch_select_menu(sm); screen_manager_dispatch_select_menu(sm);
} else { } else {
Screen *cs = sm->screens[sm->cur_scren]; Screen *cs = sm->screens[sm->cur_screen];
if (cs->dispatch_func) { if (cs->dispatch_func) {
SensorState state = { SensorState state = {
.lcd = sm->lcd, .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 (cs->dispatch_func(cs, &state)) {
// if this is true, it means return to the main menu // if this is true, it means return to the main menu
sm->cur_scren = -1; sm->cur_screen = -1;
sm->force_draw = true; sm->force_draw = true;
} else { } else {
sm->force_draw = false; sm->force_draw = false;

View File

@ -61,7 +61,7 @@ typedef struct {
size_t screen_count; size_t screen_count;
// internal // internal
ssize_t cur_scren; ssize_t cur_screen;
size_t cur_menu_line; size_t cur_menu_line;
size_t last_drawn_menu_line; size_t last_drawn_menu_line;
bool force_draw; bool force_draw;

View File

@ -20,7 +20,7 @@ void cleanup_options(Options *opts) {
FREE_CHECKED(opts->temp_key); FREE_CHECKED(opts->temp_key);
FREE_CHECKED(opts->humid_key); FREE_CHECKED(opts->humid_key);
FREE_CHECKED(opts->fail_key); FREE_CHECKED(opts->fail_key);
FREE_CHECKED(opts->lcd_version); FREE_CHECKED(opts->database_location);
} }
void *malloc_checked(size_t n) { void *malloc_checked(size_t n) {

View File

@ -28,7 +28,7 @@ typedef struct {
gpio_pin_t rs_pin; gpio_pin_t rs_pin;
gpio_pin_t rw_pin; gpio_pin_t rw_pin;
gpio_pin_t data_pins[8]; 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 *temp_key; // sysctl key for temperature
char *humid_key; // sysctl key for humidity char *humid_key; // sysctl key for humidity
char *fail_key; // sysctl key for number of fails char *fail_key; // sysctl key for number of fails