328 lines
11 KiB
C
328 lines
11 KiB
C
/*
|
|
* main.c - Program entry and argument handling
|
|
* 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 "lcd.h"
|
|
#include "util.h"
|
|
#include "ths.h"
|
|
|
|
#include <unistd.h>
|
|
#include <err.h>
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <sys/sysctl.h>
|
|
#include <string.h>
|
|
#include <figpar.h>
|
|
#include <inttypes.h>
|
|
#include <ctype.h>
|
|
#include <sys/sysctl.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
/*
|
|
* Print help message to standard output.
|
|
*/
|
|
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);
|
|
|
|
int main(int argc, char *const *argv) {
|
|
parse_arguments(argc, argv);
|
|
parse_config_file(GLOBAL_OPTS.config_path);
|
|
gpio_handle_t handle = gpio_open_device(GLOBAL_OPTS.gpio_path);
|
|
if (handle == GPIO_INVALID_HANDLE) {
|
|
err(1, "could not open GPIO device \"%s\"", GLOBAL_OPTS.gpio_path);
|
|
}
|
|
LOG_VERBOSE("Opened GPIO device: \"%s\"\n", GLOBAL_OPTS.gpio_path);
|
|
LCD *lcd = lcd_open(handle, GLOBAL_OPTS.rs_pin, GLOBAL_OPTS.rw_pin,
|
|
GLOBAL_OPTS.en_pin, GLOBAL_OPTS.data_pins[0],
|
|
GLOBAL_OPTS.data_pins[1], GLOBAL_OPTS.data_pins[2],
|
|
GLOBAL_OPTS.data_pins[3], GLOBAL_OPTS.data_pins[4],
|
|
GLOBAL_OPTS.data_pins[5], GLOBAL_OPTS.data_pins[6],
|
|
GLOBAL_OPTS.data_pins[7]);
|
|
lcd_display_control(lcd, LCD_CURSOR_BLINK, LCD_CURSOR_ON, LCD_DISPLAY_ON);
|
|
THS *ths = ths_open(GLOBAL_OPTS.temp_key, GLOBAL_OPTS.humid_key,
|
|
GLOBAL_OPTS.fail_key);
|
|
while (true) {
|
|
if (ths_read_fails(ths) > GLOBAL_OPTS.fail_limit) {
|
|
errx(1, "THS fail limit reached");
|
|
}
|
|
lcd_clear(lcd);
|
|
lcd_write_string(lcd, "temp humi time");
|
|
char buff[17];
|
|
int cur_len = snprintf(buff, sizeof(buff), "%4.1fF %3" PRIu32 "%% ",
|
|
DK_TO_F(ths_read_temp(ths)), ths_read_humid(ths));
|
|
time_t it = time(NULL);
|
|
struct tm lt;
|
|
localtime_r(&it, <);
|
|
strftime(buff + cur_len, sizeof(buff) - cur_len,
|
|
"%H:%M", <);
|
|
lcd_move_to(lcd, 1, 0);
|
|
lcd_write_string(lcd, buff);
|
|
usleep(1000 * 1000 * 5);
|
|
}
|
|
lcd_close(lcd);
|
|
gpio_close(handle);
|
|
cleanup_options(&GLOBAL_OPTS);
|
|
return 0;
|
|
}
|
|
|
|
void print_help(const char *exec_name) {
|
|
printf("usage: %s [-h] [-v] [-s] [-f CONFIG_PATH]", exec_name);
|
|
}
|
|
|
|
void parse_arguments(int argc, char *const *argv) {
|
|
int c;
|
|
while ((c = getopt(argc, argv, "hf:vs")) != -1) {
|
|
switch (c) {
|
|
case 'h':
|
|
print_help(argv[0]);
|
|
exit(0);
|
|
case 'f':
|
|
FREE_CHECKED(GLOBAL_OPTS.config_path);
|
|
GLOBAL_OPTS.config_path = optarg;
|
|
LOG_VERBOSE("Config file path set: \"%s\"\n", optarg);
|
|
break;
|
|
case 's':
|
|
GLOBAL_OPTS.strict_config = true;
|
|
LOG_VERBOSE("Strict config mode enabled\n");
|
|
break;
|
|
case 'v':
|
|
GLOBAL_OPTS.verbose = true;
|
|
LOG_VERBOSE("Verbose mode enabled\n");
|
|
break;
|
|
case '?':
|
|
default:
|
|
print_help(argv[0]);
|
|
exit(1);
|
|
}
|
|
}
|
|
if (!GLOBAL_OPTS.config_path) {
|
|
GLOBAL_OPTS.config_path = strdup_checked(DEFAULT_CONFIG_PATH);
|
|
LOG_VERBOSE("Config file path set: \"%s\"\n", GLOBAL_OPTS.config_path);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
#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 = 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 = 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 = 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.lcd_version = entries[9].value.str;
|
|
LOG_VERBOSE("Using lcd_version: \"%s\"\n", GLOBAL_OPTS.lcd_version);
|
|
}
|
|
|
|
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_checked(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_checked(DEFAULT_TEMP_KEY)},
|
|
},
|
|
{
|
|
.directive = "humid_key",
|
|
.type = FIGPAR_TYPE_STR,
|
|
.action = parse_str_callback,
|
|
.value = {.str = strdup_checked(DEFAULT_HUMID_KEY)},
|
|
},
|
|
{
|
|
.directive = "fail_key",
|
|
.type = FIGPAR_TYPE_STR,
|
|
.action = parse_str_callback,
|
|
.value = {.str = strdup_checked(DEFAULT_FAIL_KEY)},
|
|
},
|
|
{
|
|
.directive = "fail_limit",
|
|
.type = FIGPAR_TYPE_UINT,
|
|
.action = parse_uint_callback,
|
|
.value = {.u_num = DEFAULT_FAIL_LIMIT},
|
|
},
|
|
{
|
|
.directive = "lcd_version",
|
|
.type = FIGPAR_TYPE_STR,
|
|
.action = parse_str_callback,
|
|
.value = {.str = strdup_checked(DEFAULT_LCD_VERSION)},
|
|
},
|
|
{ .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");
|
|
}
|