diff --git a/Makefile b/Makefile index 58a2505..70ab191 100644 --- a/Makefile +++ b/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: diff --git a/config.mk b/config.mk index f245740..e860b82 100644 --- a/config.mk +++ b/config.mk @@ -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 diff --git a/src/config.c b/src/config.c new file mode 100644 index 0000000..78b88ee --- /dev/null +++ b/src/config.c @@ -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 +#include +#include +#include +#include +#include + +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"); +} diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..de345c8 --- /dev/null +++ b/src/config.h @@ -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 diff --git a/src/main.c b/src/main.c index 367e537..4631a0a 100644 --- a/src/main.c +++ b/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; diff --git a/src/ths.h b/src/ths.h index 216f380..cfa5d6c 100644 --- a/src/ths.h +++ b/src/ths.h @@ -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. diff --git a/src/ui/datapoints.c b/src/ui/datapoints.c index a63d59e..8dc02fb 100644 --- a/src/ui/datapoints.c +++ b/src/ui/datapoints.c @@ -8,7 +8,6 @@ * version. See the LICENSE file for more information. */ #include "datapoints.h" -#include "../ths.h" #include #include @@ -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); } diff --git a/src/ui/screen.c b/src/ui/screen.c index 3c48f51..91aaf3e 100644 --- a/src/ui/screen.c +++ b/src/ui/screen.c @@ -8,7 +8,6 @@ * version. See the LICENSE file for more information. */ #include "screen.h" -#include "../ths.h" #include #include @@ -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, diff --git a/src/ui/statrange.c b/src/ui/statrange.c index 0a02666..4eb0010 100644 --- a/src/ui/statrange.c +++ b/src/ui/statrange.c @@ -8,7 +8,6 @@ * version. See the LICENSE file for more information. */ #include "statrange.h" -#include "../ths.h" #include #include @@ -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!"); diff --git a/src/ui/statsby.c b/src/ui/statsby.c index 026b406..1fe6109 100644 --- a/src/ui/statsby.c +++ b/src/ui/statsby.c @@ -8,7 +8,6 @@ * version. See the LICENSE file for more information. */ #include "statsby.h" -#include "../ths.h" #include @@ -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!"); diff --git a/src/util.c b/src/util.c index f758ff7..b88769d 100644 --- a/src/util.c +++ b/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; + } +} diff --git a/src/util.h b/src/util.h index bf8d38f..64f501d 100644 --- a/src/util.h +++ b/src/util.h @@ -19,6 +19,11 @@ #include #include +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