Allow user to select temperature unit
This commit is contained in:
parent
e1146fe006
commit
64f1cf2fe8
12
Makefile
12
Makefile
@ -7,7 +7,7 @@ LD=clang
|
||||
LDFLAGS=-lgpio -lfigpar -lpthread ${SQLITE3_LDFLAGS}
|
||||
SRCS=src/main.c src/util.c src/lcd.c src/ths.c src/button.c src/ui/screen.c\
|
||||
src/ui/datesel.c src/ui/statsby.c src/ui/datapoints.c src/ui/timesel.c\
|
||||
src/ui/statrange.c
|
||||
src/ui/statrange.c src/config.c
|
||||
PROG=rpi4b-temp-humidity
|
||||
|
||||
OBJS=${SRCS:C/^src/bin/:C/.c$/.o/}
|
||||
@ -29,17 +29,22 @@ bin/ui/statrange.o: src/ui/screen.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/main.o bin/ths.o bin/ui/screen.o bin/ui/statsby.o: src/ths.h
|
||||
bin/ui/timesel.o src/ui/statrange.o: src/ths.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/main.o bin/ths.o: src/ths.h
|
||||
bin/main.o bin/config.o: src/config.h
|
||||
bin/main.o bin/button.o bin/ui/screen.o: src/button.h
|
||||
bin/main.o bin/ui/statsby.o: src/ui/statsby.h
|
||||
bin/main.o bin/ui/datapoints.o: src/ui/datapoints.h
|
||||
bin/main.o bin/ui/statrange.o: src/ui/statrange.h
|
||||
|
||||
.if "${DEFAULT_TEMP_UNIT}" != "F" && "${DEFAULT_TEMP_UNIT}" != "C" &&\
|
||||
"${DEFAULT_TEMP_UNIT}" != "c" && "${DEFAULT_TEMP_UNIT}" != "c"
|
||||
.error Invalid temperature unit "${DEFAULT_TEMP_UNIT}"
|
||||
.endif
|
||||
|
||||
${OBJS}: ${.TARGET:C/^bin/src/:C/.o$/.c/}
|
||||
@mkdir -p ${.TARGET:H}
|
||||
${CC} ${CFLAGS} -c\
|
||||
@ -51,6 +56,7 @@ ${OBJS}: ${.TARGET:C/^bin/src/:C/.o$/.c/}
|
||||
-DDEFAULT_FAIL_LIMIT="${DEFAULT_FAIL_LIMIT}"\
|
||||
-DDEFAULT_DATABASE_LOCATION="\"${DEFAULT_DATABASE_LOCATION}\""\
|
||||
-DDEFAULT_REFRESH_TIME="${DEFAULT_REFRESH_TIME}"\
|
||||
-DDEFAULT_TEMP_UNIT="'${DEFAULT_TEMP_UNIT}'"\
|
||||
-o ${@} ${.TARGET:C/^bin/src/:C/.o$/.c/}
|
||||
|
||||
clean:
|
||||
|
@ -11,3 +11,4 @@ DEFAULT_FAIL_KEY=dev.gpioths.0.fails
|
||||
DEFAULT_FAIL_LIMIT=5
|
||||
DEFAULT_DATABASE_LOCATION=/var/db/rpi4-temp-humidity/db.sqlite
|
||||
DEFAULT_REFRESH_TIME=5000
|
||||
DEFAULT_TEMP_UNIT=F
|
||||
|
311
src/config.c
Normal file
311
src/config.c
Normal file
@ -0,0 +1,311 @@
|
||||
/*
|
||||
* config.h - Config file parsing
|
||||
* Copyright (C) 2024 Alexander Rosenberg
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, either version 3 of the License, or (at your option) any later
|
||||
* version. See the LICENSE file for more information.
|
||||
*/
|
||||
#include "config.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <figpar.h>
|
||||
#include <err.h>
|
||||
#include <inttypes.h>
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
static int unknown_key_callback(struct figpar_config *opt, uint32_t line,
|
||||
char *directive, char *value) {
|
||||
if (GLOBAL_OPTS.strict_config) {
|
||||
errx(1, "line %" PRIu32 ": unknown configuration option: \"%s\"",
|
||||
line, directive);
|
||||
} else {
|
||||
warnx("line %" PRIu32 ": unknown configuration option: \"%s\"",
|
||||
line, directive);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_uint_callback(struct figpar_config *opt, uint32_t line,
|
||||
char *directive, char *value) {
|
||||
char last_char;
|
||||
if (sscanf(value, "%" SCNu32 " %c", &opt->value.u_num, &last_char) < 1 ||
|
||||
(last_char && !isdigit(last_char))) {
|
||||
warnx("line %" PRIu32 ": not a valid number \"%s\"", line, value);
|
||||
return 1;
|
||||
}
|
||||
LOG_VERBOSE("Loaded config uint option: %s = %" PRIu32 "\n", directive,
|
||||
opt->value.u_num);
|
||||
opt->type = FIGPAR_TYPE_INT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_temp_unit_callback(struct figpar_config *opt, uint32_t line,
|
||||
char *directive, char *value) {
|
||||
if (strcasecmp(value, "c") == 0) {
|
||||
opt->value.num = TEMP_UNIT_C;
|
||||
} else if (strcasecmp(value, "f") == 0) {
|
||||
opt->value.num = TEMP_UNIT_F;
|
||||
} else {
|
||||
warnx("not a valid temperature unit: \"%s\"", value);
|
||||
return 1;
|
||||
}
|
||||
LOG_VERBOSE("Loaded temperature unit config option: %s = %c\n", directive,
|
||||
toupper(value[0]));
|
||||
opt->type = FIGPAR_TYPE_UINT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define CONFIG_UINT_ARR_TYPE FIGPAR_TYPE_DATA1
|
||||
struct UIntArr {
|
||||
uint32_t *arr;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
static int parse_uint_arr_callback(struct figpar_config *opt, uint32_t line,
|
||||
char *directive, char *value) {
|
||||
struct UIntArr *arr = malloc_checked(sizeof(struct UIntArr));
|
||||
arr->size = 0;
|
||||
arr->arr = NULL;
|
||||
uint32_t num;
|
||||
char last_char = 1;
|
||||
int jump_len;
|
||||
while (last_char && sscanf(value, "%" SCNu32 " %c%n",
|
||||
&num, &last_char, &jump_len) >= 1) {
|
||||
if (last_char && !isdigit(last_char)) {
|
||||
warnx("line %" PRIu32 ": not a valid number array \"%s\"",
|
||||
line, value);
|
||||
FREE_CHECKED(arr->arr);
|
||||
free(arr);
|
||||
return 1;
|
||||
}
|
||||
arr->arr = realloc_checked(arr->arr, sizeof(uint32_t) * ++arr->size);
|
||||
arr->arr[arr->size - 1] = num;
|
||||
value += jump_len - 1; // -1 to add back the first digit
|
||||
}
|
||||
opt->type = CONFIG_UINT_ARR_TYPE;
|
||||
opt->value.data = arr;
|
||||
if (GLOBAL_OPTS.verbose) {
|
||||
fprintf(stderr, "Loaded config uint array option: %s = ", directive);
|
||||
for (size_t i = 0; i < arr->size; ++i) {
|
||||
fprintf(stderr, "%" PRIu32, arr->arr[i]);
|
||||
if (i < arr->size - 1) {
|
||||
fputc(' ', stderr);
|
||||
}
|
||||
}
|
||||
fputc('\n', stderr);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_str_callback(struct figpar_config *opt, uint32_t line,
|
||||
char *directive, char *value) {
|
||||
FREE_CHECKED(opt->value.str);
|
||||
if (!value[0]) {
|
||||
opt->type = FIGPAR_TYPE_STR;
|
||||
opt->value.str = NULL;
|
||||
} else {
|
||||
opt->type = FIGPAR_TYPE_STR;
|
||||
opt->value.str = strdup_checked(value);
|
||||
LOG_VERBOSE("Loaded config string option: %s = %s\n", directive, value);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static char *steal_opt_if_set(char *str) {
|
||||
if (str && !*str) {
|
||||
free(str);
|
||||
return NULL;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
#define REQUIRE_KEY(ind, name) if (entries[ind].type == FIGPAR_TYPE_NONE) {\
|
||||
errx(1, "%s must be specified", # name);\
|
||||
}
|
||||
static void set_options_from_entries(struct figpar_config *entries,
|
||||
size_t nentries) {
|
||||
entries[0].type = FIGPAR_TYPE_NONE;
|
||||
GLOBAL_OPTS.gpio_path = entries[0].value.str;
|
||||
LOG_VERBOSE("Using gpio_path: \"%s\"\n", GLOBAL_OPTS.gpio_path);
|
||||
|
||||
REQUIRE_KEY(1, rs_pin);
|
||||
GLOBAL_OPTS.rs_pin = entries[1].value.u_num;
|
||||
|
||||
REQUIRE_KEY(2, rw_pin);
|
||||
GLOBAL_OPTS.rw_pin = entries[2].value.u_num;
|
||||
|
||||
REQUIRE_KEY(3, en_pin);
|
||||
GLOBAL_OPTS.en_pin = entries[3].value.u_num;
|
||||
|
||||
REQUIRE_KEY(4, data_pins);
|
||||
struct UIntArr *arr = entries[4].value.data;
|
||||
if (arr->size != 8) {
|
||||
errx(1, "data_pins must be an array of 8 uints");
|
||||
}
|
||||
memcpy(GLOBAL_OPTS.data_pins, arr->arr, sizeof(uint32_t) * 8);
|
||||
|
||||
entries[5].type = FIGPAR_TYPE_NONE;
|
||||
GLOBAL_OPTS.temp_key = steal_opt_if_set(entries[5].value.str);
|
||||
LOG_VERBOSE("Using temp_key: \"%s\"\n", GLOBAL_OPTS.temp_key);
|
||||
|
||||
entries[6].type = FIGPAR_TYPE_NONE;
|
||||
GLOBAL_OPTS.humid_key = steal_opt_if_set(entries[6].value.str);
|
||||
LOG_VERBOSE("Using humid_key: \"%s\"\n", GLOBAL_OPTS.humid_key);
|
||||
|
||||
entries[7].type = FIGPAR_TYPE_NONE;
|
||||
GLOBAL_OPTS.fail_key = steal_opt_if_set(entries[7].value.str);
|
||||
LOG_VERBOSE("Using fail_key: \"%s\"\n", GLOBAL_OPTS.fail_key);
|
||||
|
||||
GLOBAL_OPTS.fail_limit = entries[8].value.u_num;
|
||||
|
||||
entries[9].type = FIGPAR_TYPE_NONE;
|
||||
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;
|
||||
|
||||
REQUIRE_KEY(11, sel_pin);
|
||||
GLOBAL_OPTS.sel_pin = entries[11].value.u_num;
|
||||
|
||||
REQUIRE_KEY(12, up_pin);
|
||||
GLOBAL_OPTS.up_pin = entries[12].value.u_num;
|
||||
|
||||
REQUIRE_KEY(13, down_pin);
|
||||
GLOBAL_OPTS.down_pin = entries[13].value.u_num;
|
||||
|
||||
REQUIRE_KEY(14, back_pin);
|
||||
GLOBAL_OPTS.back_pin = entries[14].value.u_num;
|
||||
|
||||
GLOBAL_OPTS.temp_unit = entries[15].value.num;
|
||||
}
|
||||
|
||||
static char *strdup_default_opt(const char *def) {
|
||||
if (!*def) {
|
||||
return NULL;
|
||||
}
|
||||
return strdup_checked(def);
|
||||
}
|
||||
|
||||
void parse_config_file(const char *path) {
|
||||
LOG_VERBOSE("Loading config file: \"%s\"\n", path);
|
||||
struct figpar_config entries[] = {
|
||||
{
|
||||
.directive = "gpio_file",
|
||||
.type = FIGPAR_TYPE_STR,
|
||||
.action = parse_str_callback,
|
||||
.value = {.str = strdup_default_opt(DEFAULT_GPIO_DEVICE)},
|
||||
},
|
||||
{
|
||||
.directive = "rs_pin",
|
||||
.type = FIGPAR_TYPE_NONE,
|
||||
.action = parse_uint_callback,
|
||||
},
|
||||
{
|
||||
.directive = "rw_pin",
|
||||
.type = FIGPAR_TYPE_NONE,
|
||||
.action = parse_uint_callback,
|
||||
},
|
||||
{
|
||||
.directive = "en_pin",
|
||||
.type = FIGPAR_TYPE_NONE,
|
||||
.action = parse_uint_callback,
|
||||
},
|
||||
{
|
||||
.directive = "data_pins",
|
||||
.type = FIGPAR_TYPE_NONE,
|
||||
.action = parse_uint_arr_callback,
|
||||
},
|
||||
{
|
||||
.directive = "temp_key",
|
||||
.type = FIGPAR_TYPE_STR,
|
||||
.action = parse_str_callback,
|
||||
.value = {.str = strdup_default_opt(DEFAULT_TEMP_KEY)},
|
||||
},
|
||||
{
|
||||
.directive = "humid_key",
|
||||
.type = FIGPAR_TYPE_STR,
|
||||
.action = parse_str_callback,
|
||||
.value = {.str = strdup_default_opt(DEFAULT_HUMID_KEY)},
|
||||
},
|
||||
{
|
||||
.directive = "fail_key",
|
||||
.type = FIGPAR_TYPE_STR,
|
||||
.action = parse_str_callback,
|
||||
.value = {.str = strdup_default_opt(DEFAULT_FAIL_KEY)},
|
||||
},
|
||||
{
|
||||
.directive = "fail_limit",
|
||||
.type = FIGPAR_TYPE_UINT,
|
||||
.action = parse_uint_callback,
|
||||
.value = {.u_num = DEFAULT_FAIL_LIMIT},
|
||||
},
|
||||
{
|
||||
.directive = "database_location",
|
||||
.type = FIGPAR_TYPE_STR,
|
||||
.action = parse_str_callback,
|
||||
.value = {.str = strdup_default_opt(DEFAULT_DATABASE_LOCATION)},
|
||||
},
|
||||
{
|
||||
.directive = "refresh_time",
|
||||
.type = FIGPAR_TYPE_UINT,
|
||||
.action = parse_uint_callback,
|
||||
.value = {.u_num = DEFAULT_REFRESH_TIME},
|
||||
},
|
||||
{
|
||||
.directive = "sel_pin",
|
||||
.type = FIGPAR_TYPE_NONE,
|
||||
.action = parse_uint_callback,
|
||||
},
|
||||
{
|
||||
.directive = "up_pin",
|
||||
.type = FIGPAR_TYPE_NONE,
|
||||
.action = parse_uint_callback,
|
||||
},
|
||||
{
|
||||
.directive = "down_pin",
|
||||
.type = FIGPAR_TYPE_NONE,
|
||||
.action = parse_uint_callback,
|
||||
},
|
||||
{
|
||||
.directive = "back_pin",
|
||||
.type = FIGPAR_TYPE_NONE,
|
||||
.action = parse_uint_callback,
|
||||
},
|
||||
{
|
||||
.directive = "temp_unit",
|
||||
.type = FIGPAR_TYPE_INT,
|
||||
.action = parse_temp_unit_callback,
|
||||
.value = {.num = DEFAULT_TEMP_UNIT}
|
||||
},
|
||||
{ .directive = NULL },
|
||||
};
|
||||
size_t entry_count = sizeof(entries) / sizeof(struct figpar_config);
|
||||
errno = 0;
|
||||
int status = parse_config(entries, path, unknown_key_callback,
|
||||
FIGPAR_BREAK_ON_EQUALS);
|
||||
if (status < 0) {
|
||||
err(1, "could not parse config file: \"%s\"", path);
|
||||
} else if (status > 1) {
|
||||
errx(1, "could not parse config file: \"%s\"", path);
|
||||
}
|
||||
set_options_from_entries(entries, entry_count);
|
||||
for (size_t i = 0; i < entry_count; ++i) {
|
||||
switch (entries[i].type) {
|
||||
case FIGPAR_TYPE_STR:
|
||||
FREE_CHECKED(entries[i].value.str);
|
||||
break;
|
||||
case CONFIG_UINT_ARR_TYPE:
|
||||
FREE_CHECKED(((struct UIntArr *) entries[i].value.data)->arr);
|
||||
FREE_CHECKED(entries[i].value.data);
|
||||
break;
|
||||
default: ; // ignore
|
||||
}
|
||||
}
|
||||
LOG_VERBOSE("Finished loading config file\n");
|
||||
}
|
15
src/config.h
Normal file
15
src/config.h
Normal file
@ -0,0 +1,15 @@
|
||||
/*
|
||||
* config.h - Config file parsing
|
||||
* Copyright (C) 2024 Alexander Rosenberg
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, either version 3 of the License, or (at your option) any later
|
||||
* version. See the LICENSE file for more information.
|
||||
*/
|
||||
#ifndef INCLUDED_CONFIG_H
|
||||
#define INCLUDED_CONFIG_H
|
||||
|
||||
void parse_config_file(const char *path);
|
||||
|
||||
#endif
|
274
src/main.c
274
src/main.c
@ -12,6 +12,7 @@
|
||||
#include "ths.h"
|
||||
#include "button.h"
|
||||
#include "menu.h"
|
||||
#include "config.h"
|
||||
#include "ui/screen.h"
|
||||
#include "ui/statsby.h"
|
||||
#include "ui/datapoints.h"
|
||||
@ -41,10 +42,6 @@ void print_help(const char *exec_name);
|
||||
* Parse command line arguments and save the options to OPTS.
|
||||
*/
|
||||
void parse_arguments(int argc, char *const *argv);
|
||||
/*
|
||||
* Parse config file PATH.
|
||||
*/
|
||||
void parse_config_file(const char *path);
|
||||
/*
|
||||
* Setup signals used to ensure clean termination.
|
||||
*/
|
||||
@ -167,275 +164,6 @@ void parse_arguments(int argc, char *const *argv) {
|
||||
}
|
||||
}
|
||||
|
||||
static int unknown_key_callback(struct figpar_config *opt, uint32_t line,
|
||||
char *directive, char *value) {
|
||||
if (GLOBAL_OPTS.strict_config) {
|
||||
errx(1, "line %" PRIu32 ": unknown configuration option: \"%s\"",
|
||||
line, directive);
|
||||
} else {
|
||||
warnx("line %" PRIu32 ": unknown configuration option: \"%s\"",
|
||||
line, directive);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_uint_callback(struct figpar_config *opt, uint32_t line,
|
||||
char *directive, char *value) {
|
||||
char last_char;
|
||||
if (sscanf(value, "%" SCNu32 " %c", &opt->value.u_num, &last_char) < 1 ||
|
||||
(last_char && !isdigit(last_char))) {
|
||||
warnx("line %" PRIu32 ": not a valid number \"%s\"", line, value);
|
||||
return 1;
|
||||
}
|
||||
LOG_VERBOSE("Loaded config uint option: %s = %" PRIu32 "\n", directive,
|
||||
opt->value.u_num);
|
||||
opt->type = FIGPAR_TYPE_UINT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define CONFIG_UINT_ARR_TYPE FIGPAR_TYPE_DATA1
|
||||
struct UIntArr {
|
||||
uint32_t *arr;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
static int parse_uint_arr_callback(struct figpar_config *opt, uint32_t line,
|
||||
char *directive, char *value) {
|
||||
struct UIntArr *arr = malloc_checked(sizeof(struct UIntArr));
|
||||
arr->size = 0;
|
||||
arr->arr = NULL;
|
||||
uint32_t num;
|
||||
char last_char = 1;
|
||||
int jump_len;
|
||||
while (last_char && sscanf(value, "%" SCNu32 " %c%n",
|
||||
&num, &last_char, &jump_len) >= 1) {
|
||||
if (last_char && !isdigit(last_char)) {
|
||||
warnx("line %" PRIu32 ": not a valid number array \"%s\"",
|
||||
line, value);
|
||||
FREE_CHECKED(arr->arr);
|
||||
free(arr);
|
||||
return 1;
|
||||
}
|
||||
arr->arr = realloc_checked(arr->arr, sizeof(uint32_t) * ++arr->size);
|
||||
arr->arr[arr->size - 1] = num;
|
||||
value += jump_len - 1; // -1 to add back the first digit
|
||||
}
|
||||
opt->type = CONFIG_UINT_ARR_TYPE;
|
||||
opt->value.data = arr;
|
||||
if (GLOBAL_OPTS.verbose) {
|
||||
fprintf(stderr, "Loaded config uint array option: %s = ", directive);
|
||||
for (size_t i = 0; i < arr->size; ++i) {
|
||||
fprintf(stderr, "%" PRIu32, arr->arr[i]);
|
||||
if (i < arr->size - 1) {
|
||||
fputc(' ', stderr);
|
||||
}
|
||||
}
|
||||
fputc('\n', stderr);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_str_callback(struct figpar_config *opt, uint32_t line,
|
||||
char *directive, char *value) {
|
||||
FREE_CHECKED(opt->value.str);
|
||||
if (!value[0]) {
|
||||
opt->type = FIGPAR_TYPE_STR;
|
||||
opt->value.str = NULL;
|
||||
} else {
|
||||
opt->type = FIGPAR_TYPE_STR;
|
||||
opt->value.str = strdup_checked(value);
|
||||
LOG_VERBOSE("Loaded config string option: %s = %s\n", directive, value);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static char *steal_opt_if_set(char *str) {
|
||||
if (str && !*str) {
|
||||
free(str);
|
||||
return NULL;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
#define REQUIRE_KEY(ind, name) if (entries[ind].type == FIGPAR_TYPE_NONE) {\
|
||||
errx(1, "%s must be specified", # name);\
|
||||
}
|
||||
static void set_options_from_entries(struct figpar_config *entries,
|
||||
size_t nentries) {
|
||||
entries[0].type = FIGPAR_TYPE_NONE;
|
||||
GLOBAL_OPTS.gpio_path = entries[0].value.str;
|
||||
LOG_VERBOSE("Using gpio_path: \"%s\"\n", GLOBAL_OPTS.gpio_path);
|
||||
|
||||
REQUIRE_KEY(1, rs_pin);
|
||||
GLOBAL_OPTS.rs_pin = entries[1].value.u_num;
|
||||
|
||||
REQUIRE_KEY(2, rw_pin);
|
||||
GLOBAL_OPTS.rw_pin = entries[2].value.u_num;
|
||||
|
||||
REQUIRE_KEY(3, en_pin);
|
||||
GLOBAL_OPTS.en_pin = entries[3].value.u_num;
|
||||
|
||||
REQUIRE_KEY(4, data_pins);
|
||||
struct UIntArr *arr = entries[4].value.data;
|
||||
if (arr->size != 8) {
|
||||
errx(1, "data_pins must be an array of 8 uints");
|
||||
}
|
||||
memcpy(GLOBAL_OPTS.data_pins, arr->arr, sizeof(uint32_t) * 8);
|
||||
|
||||
entries[5].type = FIGPAR_TYPE_NONE;
|
||||
GLOBAL_OPTS.temp_key = steal_opt_if_set(entries[5].value.str);
|
||||
LOG_VERBOSE("Using temp_key: \"%s\"\n", GLOBAL_OPTS.temp_key);
|
||||
|
||||
entries[6].type = FIGPAR_TYPE_NONE;
|
||||
GLOBAL_OPTS.humid_key = steal_opt_if_set(entries[6].value.str);
|
||||
LOG_VERBOSE("Using humid_key: \"%s\"\n", GLOBAL_OPTS.humid_key);
|
||||
|
||||
entries[7].type = FIGPAR_TYPE_NONE;
|
||||
GLOBAL_OPTS.fail_key = steal_opt_if_set(entries[7].value.str);
|
||||
LOG_VERBOSE("Using fail_key: \"%s\"\n", GLOBAL_OPTS.fail_key);
|
||||
|
||||
GLOBAL_OPTS.fail_limit = entries[8].value.u_num;
|
||||
|
||||
entries[9].type = FIGPAR_TYPE_NONE;
|
||||
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;
|
||||
|
||||
REQUIRE_KEY(11, sel_pin);
|
||||
GLOBAL_OPTS.sel_pin = entries[11].value.u_num;
|
||||
|
||||
REQUIRE_KEY(12, up_pin);
|
||||
GLOBAL_OPTS.up_pin = entries[12].value.u_num;
|
||||
|
||||
REQUIRE_KEY(13, down_pin);
|
||||
GLOBAL_OPTS.down_pin = entries[13].value.u_num;
|
||||
|
||||
REQUIRE_KEY(14, back_pin);
|
||||
GLOBAL_OPTS.back_pin = entries[14].value.u_num;
|
||||
}
|
||||
|
||||
static char *strdup_default_opt(const char *def) {
|
||||
if (!*def) {
|
||||
return NULL;
|
||||
}
|
||||
return strdup_checked(def);
|
||||
}
|
||||
|
||||
void parse_config_file(const char *path) {
|
||||
LOG_VERBOSE("Loading config file: \"%s\"\n", path);
|
||||
struct figpar_config entries[] = {
|
||||
{
|
||||
.directive = "gpio_file",
|
||||
.type = FIGPAR_TYPE_STR,
|
||||
.action = parse_str_callback,
|
||||
.value = {.str = strdup_default_opt(DEFAULT_GPIO_DEVICE)},
|
||||
},
|
||||
{
|
||||
.directive = "rs_pin",
|
||||
.type = FIGPAR_TYPE_NONE,
|
||||
.action = parse_uint_callback,
|
||||
},
|
||||
{
|
||||
.directive = "rw_pin",
|
||||
.type = FIGPAR_TYPE_NONE,
|
||||
.action = parse_uint_callback,
|
||||
},
|
||||
{
|
||||
.directive = "en_pin",
|
||||
.type = FIGPAR_TYPE_NONE,
|
||||
.action = parse_uint_callback,
|
||||
},
|
||||
{
|
||||
.directive = "data_pins",
|
||||
.type = FIGPAR_TYPE_NONE,
|
||||
.action = parse_uint_arr_callback,
|
||||
},
|
||||
{
|
||||
.directive = "temp_key",
|
||||
.type = FIGPAR_TYPE_STR,
|
||||
.action = parse_str_callback,
|
||||
.value = {.str = strdup_default_opt(DEFAULT_TEMP_KEY)},
|
||||
},
|
||||
{
|
||||
.directive = "humid_key",
|
||||
.type = FIGPAR_TYPE_STR,
|
||||
.action = parse_str_callback,
|
||||
.value = {.str = strdup_default_opt(DEFAULT_HUMID_KEY)},
|
||||
},
|
||||
{
|
||||
.directive = "fail_key",
|
||||
.type = FIGPAR_TYPE_STR,
|
||||
.action = parse_str_callback,
|
||||
.value = {.str = strdup_default_opt(DEFAULT_FAIL_KEY)},
|
||||
},
|
||||
{
|
||||
.directive = "fail_limit",
|
||||
.type = FIGPAR_TYPE_UINT,
|
||||
.action = parse_uint_callback,
|
||||
.value = {.u_num = DEFAULT_FAIL_LIMIT},
|
||||
},
|
||||
{
|
||||
.directive = "database_location",
|
||||
.type = FIGPAR_TYPE_STR,
|
||||
.action = parse_str_callback,
|
||||
.value = {.str = strdup_default_opt(DEFAULT_DATABASE_LOCATION)},
|
||||
},
|
||||
{
|
||||
.directive = "refresh_time",
|
||||
.type = FIGPAR_TYPE_UINT,
|
||||
.action = parse_uint_callback,
|
||||
.value = {.u_num = DEFAULT_REFRESH_TIME},
|
||||
},
|
||||
{
|
||||
.directive = "sel_pin",
|
||||
.type = FIGPAR_TYPE_NONE,
|
||||
.action = parse_uint_callback,
|
||||
},
|
||||
{
|
||||
.directive = "up_pin",
|
||||
.type = FIGPAR_TYPE_NONE,
|
||||
.action = parse_uint_callback,
|
||||
},
|
||||
{
|
||||
.directive = "down_pin",
|
||||
.type = FIGPAR_TYPE_NONE,
|
||||
.action = parse_uint_callback,
|
||||
},
|
||||
{
|
||||
.directive = "back_pin",
|
||||
.type = FIGPAR_TYPE_NONE,
|
||||
.action = parse_uint_callback,
|
||||
},
|
||||
{ .directive = NULL },
|
||||
};
|
||||
size_t entry_count = sizeof(entries) / sizeof(struct figpar_config);
|
||||
errno = 0;
|
||||
int status = parse_config(entries, path, unknown_key_callback,
|
||||
FIGPAR_BREAK_ON_EQUALS);
|
||||
if (status < 0) {
|
||||
err(1, "could not parse config file: \"%s\"", path);
|
||||
} else if (status > 1) {
|
||||
errx(1, "could not parse config file: \"%s\"", path);
|
||||
}
|
||||
set_options_from_entries(entries, entry_count);
|
||||
for (size_t i = 0; i < entry_count; ++i) {
|
||||
switch (entries[i].type) {
|
||||
case FIGPAR_TYPE_STR:
|
||||
FREE_CHECKED(entries[i].value.str);
|
||||
break;
|
||||
case CONFIG_UINT_ARR_TYPE:
|
||||
FREE_CHECKED(((struct UIntArr *) entries[i].value.data)->arr);
|
||||
FREE_CHECKED(entries[i].value.data);
|
||||
break;
|
||||
default: ; // ignore
|
||||
}
|
||||
}
|
||||
LOG_VERBOSE("Finished loading config file\n");
|
||||
}
|
||||
|
||||
static void exit_signal_callback(int sig) {
|
||||
LOG_VERBOSE("Caught signal %d. Exiting...\n", sig);
|
||||
RUNNING = false;
|
||||
|
@ -24,15 +24,6 @@ typedef struct {
|
||||
int fail_mib[THS_MIB_BUF_SIZE];
|
||||
} THS;
|
||||
|
||||
/*
|
||||
* Convert deciKelvin to degrees Celsius
|
||||
*/
|
||||
#define DK_TO_C(k) ((k) / 10.0f - 273.15f)
|
||||
/*
|
||||
* Convert deciKelvin to degrees Fahrenheit
|
||||
*/
|
||||
#define DK_TO_F(k) (((DK_TO_C((k))) * 1.8f) + 32.0f)
|
||||
|
||||
/*
|
||||
* Create a new THS from the given sysctl keys. Any of the keys can be null.
|
||||
* Return: the new THS, or NULL if an error occurred.
|
||||
|
@ -8,7 +8,6 @@
|
||||
* version. See the LICENSE file for more information.
|
||||
*/
|
||||
#include "datapoints.h"
|
||||
#include "../ths.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <time.h>
|
||||
@ -128,8 +127,8 @@ static void data_points_show(DataPointsScreen *screen, SensorState *state) {
|
||||
lcd_move_to(state->lcd, 0, 0);
|
||||
lcd_write_string(state->lcd, str_time);
|
||||
char data_line[17];
|
||||
snprintf(data_line, 17, "%02ds %.1fF %d%%", broken_time.tm_sec,
|
||||
DK_TO_F(temp), humid);
|
||||
snprintf(data_line, 17, "%02ds %.1f%c %d%%", broken_time.tm_sec,
|
||||
convert_temperature(temp), GLOBAL_OPTS.temp_unit, humid);
|
||||
lcd_move_to(state->lcd, 1, 0);
|
||||
lcd_write_string(state->lcd, data_line);
|
||||
}
|
||||
|
@ -8,7 +8,6 @@
|
||||
* version. See the LICENSE file for more information.
|
||||
*/
|
||||
#include "screen.h"
|
||||
#include "../ths.h"
|
||||
|
||||
#include <err.h>
|
||||
#include <inttypes.h>
|
||||
@ -149,8 +148,9 @@ static bool stats_screen_dispatch(StatsScreen *screen, SensorState *state) {
|
||||
lcd_clear(state->lcd);
|
||||
lcd_write_string(state->lcd, "temp humi time");
|
||||
char buff[17];
|
||||
int cur_len = snprintf(buff, sizeof(buff), "%4.1fF %3" PRIu32 "%% ",
|
||||
DK_TO_F(state->temp), state->humid);
|
||||
int cur_len = snprintf(buff, sizeof(buff), "%.1f%c %3" PRIu32 "%% ",
|
||||
convert_temperature(state->temp),
|
||||
GLOBAL_OPTS.temp_unit, state->humid);
|
||||
struct tm lt;
|
||||
localtime_r(&cur_time, <);
|
||||
strftime(buff + cur_len, sizeof(buff) - cur_len,
|
||||
|
@ -8,7 +8,6 @@
|
||||
* version. See the LICENSE file for more information.
|
||||
*/
|
||||
#include "statrange.h"
|
||||
#include "../ths.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <time.h>
|
||||
@ -117,7 +116,9 @@ static void stat_range_show_data(StatRangeScreen *screen, SensorState *state) {
|
||||
lcd_move_to(state->lcd, 1, 0);
|
||||
if (data.npoints) {
|
||||
char data_line[17];
|
||||
snprintf(data_line, 17, "%.1fF %d%%", DK_TO_F(data.temp), data.humid);
|
||||
snprintf(data_line, 17, "%.1f%c %d%%",
|
||||
convert_temperature(data.temp),
|
||||
GLOBAL_OPTS.temp_unit, data.humid);
|
||||
lcd_write_string(state->lcd, data_line);
|
||||
} else {
|
||||
lcd_write_string(state->lcd, "No data!");
|
||||
|
@ -8,7 +8,6 @@
|
||||
* version. See the LICENSE file for more information.
|
||||
*/
|
||||
#include "statsby.h"
|
||||
#include "../ths.h"
|
||||
|
||||
#include <err.h>
|
||||
|
||||
@ -122,7 +121,9 @@ static void stats_by_show_stats(StatsByScreen *screen, SensorState *state) {
|
||||
lcd_move_to(state->lcd, 1, 0);
|
||||
if (data.npoints) {
|
||||
char data_string[17];
|
||||
snprintf(data_string, 17, "T:%.1fF H:%d%%", DK_TO_F(data.temp), data.humid);
|
||||
snprintf(data_string, 17, "T:%.1f%c H:%d%%",
|
||||
convert_temperature(data.temp), GLOBAL_OPTS.temp_unit,
|
||||
data.humid);
|
||||
lcd_write_string(state->lcd, data_string);
|
||||
} else {
|
||||
lcd_write_string(state->lcd, "No data!");
|
||||
|
21
src/util.c
21
src/util.c
@ -331,3 +331,24 @@ bool get_average_for_range(sqlite3 *db, uint64_t start, uint64_t end,
|
||||
sqlite3_reset(AVG_FOR_RANGE_QUERY);
|
||||
return success;
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert deciKelvin to degrees Celsius
|
||||
*/
|
||||
#define DK_TO_C(k) ((k) / 10.0f - 273.15f)
|
||||
/*
|
||||
* Convert deciKelvin to degrees Fahrenheit
|
||||
*/
|
||||
#define DK_TO_F(k) (((DK_TO_C((k))) * 1.8f) + 32.0f)
|
||||
|
||||
float convert_temperature(int dk) {
|
||||
switch (GLOBAL_OPTS.temp_unit) {
|
||||
case TEMP_UNIT_C:
|
||||
return DK_TO_C(dk);
|
||||
case TEMP_UNIT_F:
|
||||
return DK_TO_F(dk);
|
||||
default:
|
||||
warnx("Unknown temperature unit: 0x%x", GLOBAL_OPTS.temp_unit);
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
|
12
src/util.h
12
src/util.h
@ -19,6 +19,11 @@
|
||||
#include <libgpio.h>
|
||||
#include <sqlite3.h>
|
||||
|
||||
typedef enum {
|
||||
TEMP_UNIT_F = 'F',
|
||||
TEMP_UNIT_C = 'C'
|
||||
} TemperatureUnit;
|
||||
|
||||
typedef struct {
|
||||
char *config_path; // path to config file
|
||||
bool strict_config; // exit if unknown config options is found
|
||||
@ -41,6 +46,8 @@ typedef struct {
|
||||
gpio_pin_t down_pin;
|
||||
gpio_pin_t back_pin;
|
||||
gpio_pin_t sel_pin;
|
||||
|
||||
TemperatureUnit temp_unit;
|
||||
} Options;
|
||||
extern Options GLOBAL_OPTS;
|
||||
|
||||
@ -183,4 +190,9 @@ typedef struct {
|
||||
bool get_average_for_range(sqlite3 *db, uint64_t start, uint64_t end,
|
||||
UtilAverageRange *data);
|
||||
|
||||
/*
|
||||
* Convert the temperature in deciKelvin DK to either F or C.
|
||||
*/
|
||||
float convert_temperature(int dk);
|
||||
|
||||
#endif
|
||||
|
Loading…
Reference in New Issue
Block a user