Add config files and service file

This commit is contained in:
Alexander Rosenberg 2024-04-05 01:31:05 -07:00
parent 338d9360a6
commit 7ad8ea25a6
Signed by: school-rpi4
GPG Key ID: 5CCFC80B0B47B04B
9 changed files with 233 additions and 46 deletions

2
.gitignore vendored
View File

@ -1,5 +1,5 @@
bin/ bin/
*.core *.core
rpi4b-temp-humidity
compile_commands.json compile_commands.json
.cache/ .cache/
config.conf

View File

@ -11,10 +11,14 @@ SRCS=src/main.c src/util.c src/lcd.c src/ths.c src/button.c src/ui/screen.c\
src/ui/exportscreen.c src/ui/exportscreen.c
PROG=rpi4b-temp-humidity PROG=rpi4b-temp-humidity
all: config.conf bin/${PROG}
OBJS=${SRCS:C/^src/bin/:C/.c$/.o/} OBJS=${SRCS:C/^src/bin/:C/.c$/.o/}
bin/${PROG}: ${OBJS} bin/${PROG}: ${OBJS}
${LD} ${LDFLAGS} -o ${@} ${OBJS} ${LD} ${LDFLAGS} -o ${@} ${OBJS}
bin/main.o bin/config.o: config.mk
bin/main.o bin/util.o bin/lcd.o bin/ths.o bin/button.o: src/util.h bin/main.o bin/util.o bin/lcd.o bin/ths.o bin/button.o: src/util.h
bin/ui/screen.o bin/ui/statsby.o bin/ui/datesel.o: src/util.h bin/ui/screen.o bin/ui/statsby.o bin/ui/datesel.o: src/util.h
bin/ui/datapoints.o bin/ui/timesel.o bin/ui/statrange.o: src/util.h bin/ui/datapoints.o bin/ui/timesel.o bin/ui/statrange.o: src/util.h
@ -30,7 +34,6 @@ bin/ui/statrange.o: src/ui/screen.h
bin/main.o bin/ui/datesel.o bin/ui/statsby.o: src/ui/datesel.h bin/main.o bin/ui/datesel.o bin/ui/statsby.o: src/ui/datesel.h
bin/ui/datapoints.o bin/ui/timesel.o bin/ui/statrange.o: src/ui/datesel.h bin/ui/datapoints.o bin/ui/timesel.o bin/ui/statrange.o: src/ui/datesel.h
bin/main.o bin/ui/timesel.o bin/ui/datapoints.o: src/ui/timesel.h bin/main.o bin/ui/timesel.o bin/ui/datapoints.o: src/ui/timesel.h
bin/ui/statrange.o: src/ui/timesel.h bin/ui/statrange.o: src/ui/timesel.h
@ -49,23 +52,34 @@ bin/main.o bin/drive.o bin/ui/exportscreen.o: src/drive.h
.error Invalid temperature unit "${DEFAULT_TEMP_UNIT}" .error Invalid temperature unit "${DEFAULT_TEMP_UNIT}"
.endif .endif
DEFINES:=\
-DDEFAULT_CONFIG_PATH="${DEFAULT_CONFIG_PATH}"\
-DDEFAULT_GPIO_DEVICE="${DEFAULT_GPIO_DEVICE}"\
-DDEFAULT_TEMP_KEY="${DEFAULT_TEMP_KEY}"\
-DDEFAULT_HUMID_KEY="${DEFAULT_HUMID_KEY}"\
-DDEFAULT_FAIL_KEY="${DEFAULT_FAIL_KEY}"\
-DDEFAULT_FAIL_LIMIT="${DEFAULT_FAIL_LIMIT}"\
-DDEFAULT_DATABASE_LOCATION="${DEFAULT_DATABASE_LOCATION}"\
-DDEFAULT_REFRESH_TIME="${DEFAULT_REFRESH_TIME}"\
-DDEFAULT_TEMP_UNIT="${DEFAULT_TEMP_UNIT}"\
-DDEFAULT_EXPORT_FILE_NAME="${DEFAULT_EXPORT_FILE_NAME}"\
${OBJS}: ${.TARGET:C/^bin/src/:C/.o$/.c/} ${OBJS}: ${.TARGET:C/^bin/src/:C/.o$/.c/}
@mkdir -p ${.TARGET:H} @mkdir -p ${.TARGET:H}
${CC} ${CFLAGS} -c\ ${CC} ${CFLAGS} -c ${DEFINES} -o ${@} ${.TARGET:C/^bin/src/:C/.o$/.c/}
-DDEFAULT_CONFIG_PATH="\"${DEFAULT_CONFIG_PATH}\""\
-DDEFAULT_GPIO_DEVICE="\"${DEFAULT_GPIO_DEVICE}\""\ config.conf: config.conf.m4
-DDEFAULT_TEMP_KEY="\"${DEFAULT_TEMP_KEY}\""\ m4 ${DEFINES} config.conf.m4 >config.conf
-DDEFAULT_HUMID_KEY="\"${DEFAULT_HUMID_KEY}\""\
-DDEFAULT_FAIL_KEY="\"${DEFAULT_FAIL_KEY}\""\ install: all
-DDEFAULT_FAIL_LIMIT="${DEFAULT_FAIL_LIMIT}"\ mkdir -p "${PREFIX}/bin"
-DDEFAULT_DATABASE_LOCATION="\"${DEFAULT_DATABASE_LOCATION}\""\ install -o root -g wheel "bin/${PROG}" "${PREFIX}/bin"
-DDEFAULT_REFRESH_TIME="${DEFAULT_REFRESH_TIME}"\ mkdir -p "${PREFIX}/etc/rpi4b-temp-humidity"
-DDEFAULT_TEMP_UNIT="'${DEFAULT_TEMP_UNIT}'"\ install -b -o root -g wheel -m 0644 config.conf "${PREFIX}/etc/rpi4b-temp-humidity"
-DDEFAULT_EXPORT_FILE_NAME="\"${DEFAULT_EXPORT_FILE_NAME}\""\ install -o root -g wheel -m 0555 rpi4b-temp-humidity "${PREFIX}/etc/rc.d"
-o ${@} ${.TARGET:C/^bin/src/:C/.o$/.c/}
clean: clean:
rm -rf bin/ rm -rf config.conf bin/
.SUFFIXES: .c .o .SUFFIXES: .c .o
.PHONY: clean .PHONY: all install clean

49
config.conf.m4 Normal file
View File

@ -0,0 +1,49 @@
dnl The following line enables macro expansion in # comments
changecom(`//')
# These options do not have default value and MUST be set before running
# LCD pins (the ones below match up with the diagram in README.md)
# Register select
rs_pin = 4
# Read-write
rw_pin = 17
# Enable
en_pin = 27
# Optional backlight pin, set to -1 to disable
bl_pin = 26
# d0-d7
data_pins = 22 10 9 11 5 6 13 19
# Button pins
sel_pin = 14
back_pin = 15
up_pin = 18
down_pin = 23
# Options below this comment have compile-time defaults (which are show below)
# GPIO device file
#gpio_file = DEFAULT_GPIO_DEVICE
# sysctl(8) keys for the temperature sensor
#temp_key = DEFAULT_TEMP_KEY
#humid_key = DEFAULT_HUMID_KEY
# Set to empty to disable
#fail_key = DEFAULT_FAIL_KEY
# Number of temperature sensor fails before exit
#fail_limit = DEFAULT_FAIL_LIMIT
# Database location
#database_location = DEFAULT_DATABASE_LOCATION
# Time between data points, note that this is probably limited by the kernel's
# gpioths driver (5 seconds)
#refresh_time = DEFAULT_REFRESH_TIME
# Temperature unit F for Fahrenheit, C for Celsius
#temp_unit = DEFAULT_TEMP_UNIT
# The base name for export, .sqlite3 will be appended for SQLite exports and
# .csv will be appended for CSV exports
#export_file_name = DEFAULT_EXPORT_FILE_NAME

View File

@ -2,14 +2,35 @@
SQLITE3_LDFLAGS=-L/usr/local/lib -lsqlite3 SQLITE3_LDFLAGS=-L/usr/local/lib -lsqlite3
SQLITE3_CFLAGS=-I/usr/local/include SQLITE3_CFLAGS=-I/usr/local/include
# Install prefix
PREFIX=/usr/local
# Default option values, empty means NULL (for strings) # Default option values, empty means NULL (for strings)
DEFAULT_CONFIG_PATH=config.conf # Config file path
DEFAULT_CONFIG_PATH=/usr/local/etc/rpi4b-temp-humidity/config.conf
# GPIO device
DEFAULT_GPIO_DEVICE=/dev/gpioc0 DEFAULT_GPIO_DEVICE=/dev/gpioc0
# sysctl(8) keys for the temperature sensor
DEFAULT_TEMP_KEY=dev.gpioths.0.temperature DEFAULT_TEMP_KEY=dev.gpioths.0.temperature
DEFAULT_HUMID_KEY=dev.gpioths.0.humidity DEFAULT_HUMID_KEY=dev.gpioths.0.humidity
# Set this to empty to disable checking failures
DEFAULT_FAIL_KEY=dev.gpioths.0.fails DEFAULT_FAIL_KEY=dev.gpioths.0.fails
# Max failures of the temperature sensor before exit
DEFAULT_FAIL_LIMIT=5 DEFAULT_FAIL_LIMIT=5
DEFAULT_DATABASE_LOCATION=/var/db/rpi4-temp-humidity/db.sqlite
# Database location
DEFAULT_DATABASE_LOCATION=/var/db/rpi4b-temp-humidity/db.sqlite
# Time between data points, note that this is probably limited by the kernel's
# gpioths driver (5 seconds)
DEFAULT_REFRESH_TIME=5000 DEFAULT_REFRESH_TIME=5000
# F for Fahrenheit, C for Celsius
DEFAULT_TEMP_UNIT=F DEFAULT_TEMP_UNIT=F
# The base name for export, .sqlite3 will be appended for SQLite exports and
# .csv will be appended for CSV exports
DEFAULT_EXPORT_FILE_NAME=env_data DEFAULT_EXPORT_FILE_NAME=env_data

44
rpi4b-temp-humidity Normal file
View File

@ -0,0 +1,44 @@
#!/bin/sh
. /etc/rc.subr
name=rpi4b_temp_humidity
rcvar=rpi4b_temp_humidity_enable
command="/usr/sbin/daemon"
procname="/usr/local/bin/rpi4b-temp-humidity"
start_precmd="${name}_prestart"
load_rc_config ${name}
: ${rpi4b_temp_humidity_enable:=NO}
: ${rpi4b_temp_humidity_config_file:=""}
: ${rpi4b_temp_humidity_strict_config:=NO}
: ${rpi4b_temp_humidity_verbose:=YES}
: ${rpi4b_temp_humidity_log_file:="/var/log/rpi4b-temp-humidity.log"}
rpi4b_temp_humidity_prestart()
{
if checkyesno rpi4b_temp_humidity_strict_config; then
rc_flags="-s ${rc_flags}"
fi
if ! [ -z "${rpi4b_temp_humidity_config_file}" ]; then
rc_flags="-f \"${rpi4b_temp_humidity_config_file}\" ${rc_flags}"
fi
if checkyesno rpi4b_temp_humidity_verbose; then
rc_flags="-v ${rc_flags}"
fi
if ! [ -z "${rpi4b_temp_humidity_log_file}" ]; then
# simple log rotation
mv "${rpi4b_temp_humidity_log_file}" "${rpi4b_temp_humidity_log_file}.old"
rpi4b_temp_humidity_daemon_flags="-o \"${rpi4b_temp_humidity_log_file}\""
if ! mkdir -p "$(dirname "${rpi4b_temp_humidity_log_file}")"; then
return 1
fi
else
rpi4b_temp_humidity_daemon_flags="-f"
fi
rc_flags="${rpi4b_temp_humidity_daemon_flags} ${procname} ${rc_flags}"
}
run_rc_command "$1"

View File

@ -221,7 +221,7 @@ void parse_config_file(const char *path) {
.directive = "gpio_file", .directive = "gpio_file",
.type = FIGPAR_TYPE_STR, .type = FIGPAR_TYPE_STR,
.action = parse_str_callback, .action = parse_str_callback,
.value = {.str = strdup_default_opt(DEFAULT_GPIO_DEVICE)}, .value = {.str = strdup_default_opt(STRINGIFY(DEFAULT_GPIO_DEVICE))},
}, },
{ {
.directive = "rs_pin", .directive = "rs_pin",
@ -247,19 +247,19 @@ void parse_config_file(const char *path) {
.directive = "temp_key", .directive = "temp_key",
.type = FIGPAR_TYPE_STR, .type = FIGPAR_TYPE_STR,
.action = parse_str_callback, .action = parse_str_callback,
.value = {.str = strdup_default_opt(DEFAULT_TEMP_KEY)}, .value = {.str = strdup_default_opt(STRINGIFY(DEFAULT_TEMP_KEY))},
}, },
{ {
.directive = "humid_key", .directive = "humid_key",
.type = FIGPAR_TYPE_STR, .type = FIGPAR_TYPE_STR,
.action = parse_str_callback, .action = parse_str_callback,
.value = {.str = strdup_default_opt(DEFAULT_HUMID_KEY)}, .value = {.str = strdup_default_opt(STRINGIFY(DEFAULT_HUMID_KEY))},
}, },
{ {
.directive = "fail_key", .directive = "fail_key",
.type = FIGPAR_TYPE_STR, .type = FIGPAR_TYPE_STR,
.action = parse_str_callback, .action = parse_str_callback,
.value = {.str = strdup_default_opt(DEFAULT_FAIL_KEY)}, .value = {.str = strdup_default_opt(STRINGIFY(DEFAULT_FAIL_KEY))},
}, },
{ {
.directive = "fail_limit", .directive = "fail_limit",
@ -271,7 +271,7 @@ void parse_config_file(const char *path) {
.directive = "database_location", .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_DATABASE_LOCATION)}, .value = {.str = strdup_default_opt(STRINGIFY(DEFAULT_DATABASE_LOCATION))},
}, },
{ {
.directive = "refresh_time", .directive = "refresh_time",
@ -303,7 +303,7 @@ void parse_config_file(const char *path) {
.directive = "temp_unit", .directive = "temp_unit",
.type = FIGPAR_TYPE_INT, .type = FIGPAR_TYPE_INT,
.action = parse_temp_unit_callback, .action = parse_temp_unit_callback,
.value = {.num = DEFAULT_TEMP_UNIT}, .value = {.num = STRINGIFY(DEFAULT_TEMP_UNIT)[0]},
}, },
{ {
.directive = "bl_pin", .directive = "bl_pin",
@ -315,7 +315,7 @@ void parse_config_file(const char *path) {
.directive = "export_file_name", .directive = "export_file_name",
.type = FIGPAR_TYPE_STR, .type = FIGPAR_TYPE_STR,
.action = parse_str_callback, .action = parse_str_callback,
.value = {.str = strdup_default_opt(DEFAULT_EXPORT_FILE_NAME)}, .value = {.str = strdup_default_opt(STRINGIFY(DEFAULT_EXPORT_FILE_NAME))},
}, },
{ .directive = NULL }, { .directive = NULL },
}; };

View File

@ -35,6 +35,7 @@
#include <stdatomic.h> #include <stdatomic.h>
#include <signal.h> #include <signal.h>
#include <sqlite3.h> #include <sqlite3.h>
#include <libgen.h>
/* /*
* Print help message to standard output. * Print help message to standard output.
@ -49,9 +50,13 @@ void parse_arguments(int argc, char *const *argv);
*/ */
void setup_signals(void); void setup_signals(void);
/* /*
* Open an sqlite3 database connection. * Open the sqlite3 database connection.
*/ */
void open_database(void); void open_database(void);
/*
* Create the env_data table in DATABASE
*/
void create_db_table(void);
/* /*
* Read the temp. and humid., store it in the configured database, and output it * Read the temp. and humid., store it in the configured database, and output it
* to the given uint32_t pointers. * to the given uint32_t pointers.
@ -93,6 +98,7 @@ int main(int argc, char *const *argv) {
GLOBAL_OPTS.data_pins[7], GLOBAL_OPTS.bl_pin); GLOBAL_OPTS.data_pins[7], GLOBAL_OPTS.bl_pin);
setup_signals(); setup_signals();
open_database(); open_database();
create_db_table();
initialize_util_queries(DATABASE); initialize_util_queries(DATABASE);
pthread_t bg_update; pthread_t bg_update;
start_update_thread(&bg_update); start_update_thread(&bg_update);
@ -167,7 +173,7 @@ void parse_arguments(int argc, char *const *argv) {
} }
} }
if (!GLOBAL_OPTS.config_path) { if (!GLOBAL_OPTS.config_path) {
GLOBAL_OPTS.config_path = strdup_checked(DEFAULT_CONFIG_PATH); GLOBAL_OPTS.config_path = strdup_checked(STRINGIFY(DEFAULT_CONFIG_PATH));
LOG_VERBOSE("Config file path set: \"%s\"\n", GLOBAL_OPTS.config_path); LOG_VERBOSE("Config file path set: \"%s\"\n", GLOBAL_OPTS.config_path);
} }
} }
@ -190,18 +196,43 @@ void setup_signals() {
SIGNAL_SETUP_CHECKED(SIGUSR1, SIG_IGN); // used to kill export operations SIGNAL_SETUP_CHECKED(SIGUSR1, SIG_IGN); // used to kill export operations
} }
static const char *CREATE_DB_TABLE_QUERY =
"CREATE TABLE IF NOT EXISTS env_data("
"time INTEGER PRIMARY KEY,"
"temp INTEGER,"
"humid INTEGER"
");";
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");
}
void open_database() { void open_database() {
int status = sqlite3_config(SQLITE_CONFIG_SERIALIZED); int status = sqlite3_config(SQLITE_CONFIG_SERIALIZED);
if (status != SQLITE_OK) { if (status != SQLITE_OK) {
errx(1, "failed to enable multi-thread mode for sqlite: %s", errx(1, "failed to enable multi-thread mode for sqlite: %s",
sqlite3_errstr(status)); sqlite3_errstr(status));
} }
char *to_free = strdup_checked(GLOBAL_OPTS.database_location);
char *db_dir = dirname(to_free);
if (!mkdirs(db_dir, 0755)) {
errx(1, "failed to create database directory: %s", db_dir);
}
free(to_free);
status = sqlite3_open(GLOBAL_OPTS.database_location, &DATABASE); status = sqlite3_open(GLOBAL_OPTS.database_location, &DATABASE);
if (status != SQLITE_OK) { if (status != SQLITE_OK) {
sqlite3_close(DATABASE); sqlite3_close(DATABASE);
errx(1, "failed to open database: %s", errx(1, "failed to open database: %s",
sqlite3_errstr(status)); sqlite3_errstr(status));
} }
LOG_VERBOSE("Successfully opened database at \"%s\"\n",
GLOBAL_OPTS.database_location);
} }
void update_stats(THS *ths, sqlite3_stmt *insert_statement) { void update_stats(THS *ths, sqlite3_stmt *insert_statement) {
@ -229,28 +260,10 @@ void update_stats(THS *ths, sqlite3_stmt *insert_statement) {
} }
} }
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_STATS_QUERY = static const char *UPDATE_STATS_QUERY =
"INSERT INTO env_data (time, temp, humid) " "INSERT INTO env_data (time, temp, humid) "
"VALUES(?, ?, ?);"; "VALUES(?, ?, ?);";
static void *update_thread_action(void *_ignored) { static void *update_thread_action(void *_ignored) {
create_db_table();
sqlite3_stmt *insert_statement = NULL; sqlite3_stmt *insert_statement = NULL;
int status = sqlite3_prepare_v2(DATABASE, UPDATE_STATS_QUERY, -1, int status = sqlite3_prepare_v2(DATABASE, UPDATE_STATS_QUERY, -1,
&insert_statement, NULL); &insert_statement, NULL);

View File

@ -18,6 +18,7 @@
#include <unistd.h> #include <unistd.h>
#include <fcntl.h> #include <fcntl.h>
#include <pthread.h> #include <pthread.h>
#include <errno.h>
const char *PERIOD_LABELS[] = { const char *PERIOD_LABELS[] = {
"HOUR", "HOUR",
@ -164,7 +165,7 @@ static const char *AVG_FOR_PERIOD_QUERY_STR =
"min(humid) AS minhumid\n" "min(humid) AS minhumid\n"
"FROM env_data\n" "FROM env_data\n"
"WHERE time >= stime\n" "WHERE time >= stime\n"
"AND time <= etime);\n"; "AND time <= etime);";
static sqlite3_stmt *AVG_FOR_PERIOD_QUERY; static sqlite3_stmt *AVG_FOR_PERIOD_QUERY;
static const char *DATA_POINT_QUERY_STR = static const char *DATA_POINT_QUERY_STR =
"SELECT max(time) IS NULL, max(time), temp, humid FROM env_data WHERE time < ?1\n" "SELECT max(time) IS NULL, max(time), temp, humid FROM env_data WHERE time < ?1\n"
@ -528,3 +529,39 @@ bool export_database_as_csv(sqlite3 *db, const char *dest) {
pthread_cleanup_pop(true); // close file pthread_cleanup_pop(true); // close file
return status; return status;
} }
static bool ensure_dir_exists(const char *path, mode_t mode) {
struct stat statbuf;
errno = 0;
if (mkdir(path, mode) < 0) {
if (errno != EEXIST) {
warn("mkdir failed: %s", path);
return false;
}
if (stat(path, &statbuf) < 0) {
warn("mkdir and stat failed: %s", path);
return false;
} else if (!S_ISDIR(statbuf.st_mode)) {
warn("not a directory: %s", path);
return false;
}
}
return true;
}
bool mkdirs(const char *path, mode_t mode) {
LOG_VERBOSE("Creating directory: \"%s\"\n", path)
char *copy = strdup_checked(path);
char *work_str = copy, *token;
while ((token = strsep(&work_str, "/"))) {
if (token != copy) {
token[-1] = '/';
}
if (*copy && *token && !ensure_dir_exists(copy, mode)) {
free(copy);
return false;
}
}
free(copy);
return true;
}

View File

@ -19,6 +19,9 @@
#include <libgpio.h> #include <libgpio.h>
#include <sqlite3.h> #include <sqlite3.h>
#define _STRINGIFY_LIT(s) (#s)
#define STRINGIFY(v) _STRINGIFY_LIT(v)
typedef enum { typedef enum {
TEMP_UNIT_F = 'F', TEMP_UNIT_F = 'F',
TEMP_UNIT_C = 'C' TEMP_UNIT_C = 'C'
@ -239,4 +242,10 @@ bool export_database(sqlite3 *db, const char *dest);
*/ */
bool export_database_as_csv(sqlite3 *db, const char *dest); bool export_database_as_csv(sqlite3 *db, const char *dest);
/*
* Create all the missing directories along path.
* Return: true on success, false otherwise
*/
bool mkdirs(const char *path, mode_t mode);
#endif #endif