Save temperature and humidity in sqlite3 database
This commit is contained in:
parent
57974cc025
commit
81eb22e83a
2
Makefile
2
Makefile
@ -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/}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
89
src/main.c
89
src/main.c
@ -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;
|
||||||
}
|
}
|
||||||
|
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->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;
|
||||||
|
@ -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;
|
||||||
|
@ -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) {
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user