diff --git a/.gitignore b/.gitignore index 401433f..a4c519d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ *.o -rpi4b-temp-humidity \ No newline at end of file +rpi4b-temp-humidity +compile_commands.json +.cache/ \ No newline at end of file diff --git a/Makefile b/Makefile index fc4bfac..58bb4f5 100644 --- a/Makefile +++ b/Makefile @@ -1,18 +1,29 @@ CC=clang -CFLAGS=-std=c99 +CFLAGS=-std=c99 -g LD=clang -LDFLAGS=-lgpio -OBJS=main.o util.o lcd.o +LDFLAGS=-lgpio -lfigpar +OBJS=main.o util.o lcd.o ths.o OUTFILE=rpi4b-temp-humidity +.include "config.mk" + ${OUTFILE}: ${OBJS} ${LD} ${LDFLAGS} -o $@ ${OBJS} +main.o util.o lcd.o ths.o: util.h main.o lcd.o: lcd.h -main.o util.o lcd.o: util.h +main.o ths.o: ths.h .c.o: - ${CC} ${CFLAGS} -c $< + ${CC} ${CFLAGS} -c\ +-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_LCD_VERSION="\"${DEFAULT_LCD_VERSION}\""\ +$< clean: rm -f ${OUTFILE} ${OBJS} diff --git a/config.mk b/config.mk new file mode 100644 index 0000000..d77f527 --- /dev/null +++ b/config.mk @@ -0,0 +1,7 @@ +DEFAULT_CONFIG_PATH=config.conf +DEFAULT_GPIO_DEVICE=/dev/gpioc0 +DEFAULT_TEMP_KEY=dev.gpioths.0.temperature +DEFAULT_HUMID_KEY=dev.gpioths.0.humidity +DEFAULT_FAIL_KEY=dev.gpioths.0.fails +DEFAULT_FAIL_LIMIT=5 +DEFAULT_LCD_VERSION=jp diff --git a/lcd.c b/lcd.c index 599a133..94ea69b 100644 --- a/lcd.c +++ b/lcd.c @@ -45,8 +45,9 @@ static void lcd_pulse_enable(LCD *lcd) { gpio_pin_low(lcd->handle, lcd->en); } -LCD *lcd_open(gpio_handle_t handle, int rs, int rw, int en, int d0, int d1, - int d2, int d3, int d4, int d5, int d6, int d7) { +LCD *lcd_open(gpio_handle_t handle, gpio_pin_t rs, gpio_pin_t rw, gpio_pin_t en, + gpio_pin_t d0, gpio_pin_t d1, gpio_pin_t d2, gpio_pin_t d3, + gpio_pin_t d4, gpio_pin_t d5, gpio_pin_t d6, gpio_pin_t d7) { LCD *lcd = malloc_checked(sizeof(LCD)); lcd->handle = handle; lcd->rs = rs; @@ -60,20 +61,24 @@ LCD *lcd_open(gpio_handle_t handle, int rs, int rw, int en, int d0, int d1, lcd->d5 = d5; lcd->d6 = d6; lcd->d7 = d7; - lcd_set_read(lcd, false, true); + lcd_set_read(lcd, true, true); lcd_clear(lcd); lcd_call(lcd, 0, 0, 0, 0, 1, 1, 1, 0, 0); // 5x8 font, 2 lines, 8bit lcd_entry_mode(lcd, LCD_INCREMENT, LCD_CURSOR_MOVE); lcd_display_control(lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF, LCD_DISPLAY_ON); + LOG_VERBOSE("Initializing LCD: rs=%d, rw=%d, en=%d, d={%d, %d, %d, %d, %d," + "%d, %d, %d}\n", rs, rw, en, d0, d1, d2, d3, d4, d5, d6, d7); return lcd; } void lcd_close(LCD *lcd) { + LOG_VERBOSE("Closing LCD\n"); free(lcd); } -void lcd_call(LCD *lcd, int rs, int d0, int d1, int d2, int d3, int d4, int d5, - int d6, int d7) { +void lcd_call(LCD *lcd, gpio_value_t rs, gpio_value_t d0, gpio_value_t d1, + gpio_value_t d2, gpio_value_t d3, gpio_value_t d4, + gpio_value_t d5, gpio_value_t d6, gpio_value_t d7) { while(lcd_is_busy(lcd)) { usleep(5); } @@ -105,7 +110,7 @@ size_t lcd_write_string(LCD *lcd, const char *str) { return count; } -void lcd_move_to(LCD *lcd, int line, int col) { +void lcd_move_to(LCD *lcd, gpio_value_t line, gpio_value_t col) { lcd_call(lcd, 0, col & 1, (col >> 1) & 1, (col >> 2) & 1, (col >> 3) & 1, (col >> 4) & 1, (col >> 5) & 1, line, 1); } @@ -114,15 +119,15 @@ void lcd_clear(LCD *lcd) { lcd_call(lcd, 0, 1, 0, 0, 0, 0, 0, 0, 0); } -void lcd_display_control(LCD *lcd, int b, int c, int d) { +void lcd_display_control(LCD *lcd, gpio_value_t b, gpio_value_t c, gpio_value_t d) { lcd_call(lcd, 0, b, c, d, 1, 0, 0, 0, 0); } -void lcd_entry_mode(LCD *lcd, int id, int s) { +void lcd_entry_mode(LCD *lcd, gpio_value_t id, gpio_value_t s) { lcd_call(lcd, 0, s, id, 1, 0, 0, 0, 0, 0); } -void lcd_cursor_shift(LCD *lcd, int sc, int rl) { +void lcd_cursor_shift(LCD *lcd, gpio_value_t sc, gpio_value_t rl) { lcd_call(lcd, 0, 0, 0, rl, sc, 1, 0, 0, 0); } diff --git a/lcd.h b/lcd.h index 03fd977..f853fa5 100644 --- a/lcd.h +++ b/lcd.h @@ -17,31 +17,32 @@ typedef struct { gpio_handle_t handle; // pin numbers - int rs; // register select - int rw; // read-write - int en; // enable - int d0; - int d1; - int d2; - int d3; - int d4; - int d5; - int d6; - int d7; + gpio_pin_t rs; // register select + gpio_pin_t rw; // read-write + gpio_pin_t en; // enable + gpio_pin_t d0; + gpio_pin_t d1; + gpio_pin_t d2; + gpio_pin_t d3; + gpio_pin_t d4; + gpio_pin_t d5; + gpio_pin_t d6; + gpio_pin_t d7; - // internal use + // gpio_value_ternal use bool is_read; } LCD; /* - * Allocate and initialize a new LCD object and return a pointer to it. HANDLE + * Allocate and initialize a new LCD object and return a pogpio_value_ter to it. HANDLE * is a gpio_handle_t, each of the other arguments corresponds to the GPIO pin * number that the given controller pin is connected to: "register select", * "read-write", "enable", and data pins 0-7. * Return: the newly created object. */ -LCD *lcd_open(gpio_handle_t handle, int rs, int rw, int en, int d0, int d1, - int d2, int d3, int d4, int d5, int d6, int d7); +LCD *lcd_open(gpio_handle_t handle, gpio_pin_t rs, gpio_pin_t rw, gpio_pin_t en, + gpio_pin_t d0, gpio_pin_t d1, gpio_pin_t d2, gpio_pin_t d3, + gpio_pin_t d4, gpio_pin_t d5, gpio_pin_t d6, gpio_pin_t d7); /* * Close LCD by freeing any resources associated with it. */ @@ -49,8 +50,9 @@ void lcd_close(LCD *lcd); /* * Call a single instruction on LCD. Each argument is the value of that pin. */ -void lcd_call(LCD *lcd, int rs, int d0, int d1, int d2, int d3, int d4, int d5, - int d6, int d7); +void lcd_call(LCD *lcd, gpio_value_t rs, gpio_value_t d0, gpio_value_t d1, + gpio_value_t d2, gpio_value_t d3, gpio_value_t d4, + gpio_value_t d5, gpio_value_t d6, gpio_value_t d7); /* * Write character C to LCD. */ @@ -65,7 +67,7 @@ size_t lcd_write_string(LCD *lcd, const char *str); /* * Move the cursor to COL on LINE, which both start from 0. */ -void lcd_move_to(LCD *lcd, int line, int col); +void lcd_move_to(LCD *lcd, gpio_value_t line, gpio_value_t col); /* * Clear LCD and return the cursor to the top right. @@ -81,7 +83,7 @@ void lcd_clear(LCD *lcd); /* * Control B, cursor blink, C, cursor visibility, and D, display power for LCD. */ -void lcd_display_control(LCD *lcd, int b, int c, int d); +void lcd_display_control(LCD *lcd, gpio_value_t b, gpio_value_t c, gpio_value_t d); #define LCD_INCREMENT 1 #define LCD_DECREMENT 0 @@ -92,12 +94,12 @@ void lcd_display_control(LCD *lcd, int b, int c, int d); /* * Control shift and increment for LCD. */ -void lcd_entry_mode(LCD *lcd, int id, int s); +void lcd_entry_mode(LCD *lcd, gpio_value_t id, gpio_value_t s); /* * Control direction and shift/move for cursor for LCD. */ -void lcd_cursor_shift(LCD *lcd, int sc, int rl); +void lcd_cursor_shift(LCD *lcd, gpio_value_t sc, gpio_value_t rl); /* * Return: weather the busy flag is set for LCD. diff --git a/main.c b/main.c index 48791a7..38ce2e5 100644 --- a/main.c +++ b/main.c @@ -1,30 +1,327 @@ +/* + * 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 #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include -#define RS 2 -#define RW 3 -#define EN 4 -#define D0 17 -#define D1 27 -#define D2 22 -#define D3 10 -#define D4 9 -#define D5 14 -#define D6 15 -#define D7 18 +/* + * 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, const char **argv) { - gpio_handle_t handle = gpio_open(0); +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) { - errx(1, "count not open GPIO 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 *lcd = lcd_open(handle, RS, RW, EN, D0, D1, D2, D3, D4, D5, D6, D7); - lcd_write_string(lcd, "This is a test"); - lcd_move_to(lcd, 1, 0); - lcd_write_string(lcd, "FreeBSD!"); 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"); +} diff --git a/ths.c b/ths.c new file mode 100644 index 0000000..57b1314 --- /dev/null +++ b/ths.c @@ -0,0 +1,107 @@ +/* + * ths.c - Utilities for reading sysctl based temp. and humidity sensors + * 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 "ths.h" +#include "util.h" + +#include +#include +#include + +THS *ths_open(const char *temp_key, const char *humid_key, const char *fail_key) { + THS *ths = malloc_checked(sizeof(THS)); + if (temp_key) { + ths->temp_mib_len = THS_MIB_BUF_SIZE; + if (sysctlnametomib(temp_key, ths->temp_mib, &ths->temp_mib_len) < 0) { + warn("could not look up temp. key name: \"%s\"", temp_key); + free(ths); + return NULL; + } + } else { + ths->temp_mib_len = 0; + } + if (humid_key) { + ths->humid_mib_len = THS_MIB_BUF_SIZE; + if (sysctlnametomib(humid_key, ths->humid_mib, &ths->humid_mib_len) < 0) { + warn("could not look up humid. key name: \"%s\"", humid_key); + free(ths); + return NULL; + } + } else { + ths->humid_mib_len = 0; + } + if (fail_key) { + ths->fail_mib_len = THS_MIB_BUF_SIZE; + if (sysctlnametomib(fail_key, ths->fail_mib, &ths->fail_mib_len) < 0) { + warn("could not look up fail key name: \"%s\"", fail_key); + free(ths); + return NULL; + } + } else { + ths->fail_mib_len = 0; + } + LOG_VERBOSE("Opened THS with temp. key: \"%s\"\n" + " humid. key: \"%s\"\n" + " fail key: \"%s\"\n", + temp_key, humid_key, fail_key); + return ths; +} + +void ths_close(THS *ths) { + free(ths); +} + +int32_t ths_read_fails(const THS *ths) { + if (!ths->fail_mib_len) { + warnx("THS sensor does not support reading failure count"); + return -1; + } + uint32_t ret_buf; + size_t ret_buf_size = sizeof(uint32_t); + if (sysctl(ths->fail_mib, ths->fail_mib_len, &ret_buf, + &ret_buf_size, NULL, 0) < 0) { + warn("could not read THS fail count"); + return -1; + } + if (ret_buf > 0) { + LOG_VERBOSE("THS sensor reported: %" PRIu32 " fails\n", ret_buf) + } + return ret_buf; +} + +int32_t ths_read_temp(const THS *ths) { + if (!ths->temp_mib_len) { + warnx("THS sensor does not support reading temperature"); + return -1; + } + uint32_t ret_buf; + size_t ret_buf_size = sizeof(uint32_t); + if (sysctl(ths->temp_mib, ths->temp_mib_len, &ret_buf, + &ret_buf_size, NULL, 0) < 0) { + warn("could not read THS temperature"); + return -1; + } + return ret_buf; +} + +int32_t ths_read_humid(const THS *ths) { + if (!ths->humid_mib_len) { + warnx("THS sensor does not support reading humidity"); + return -1; + } + uint32_t ret_buf; + size_t ret_buf_size = sizeof(uint32_t); + if (sysctl(ths->humid_mib, ths->humid_mib_len, &ret_buf, + &ret_buf_size, NULL, 0) < 0) { + warn("could not read THS humidity"); + return -1; + } + return ret_buf; + +} diff --git a/ths.h b/ths.h new file mode 100644 index 0000000..216f380 --- /dev/null +++ b/ths.h @@ -0,0 +1,65 @@ +/* + * ths.h - Utilities for reading sysctl based temp. and humidity sensors + * 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_THS_H +#define INCLUDED_THS_H + +#include +#include + +#define THS_MIB_BUF_SIZE 8 + +typedef struct { + size_t temp_mib_len; + int temp_mib[THS_MIB_BUF_SIZE]; + size_t humid_mib_len; + int humid_mib[THS_MIB_BUF_SIZE]; + size_t fail_mib_len; + 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. + */ +THS *ths_open(const char *temp_key, const char *humid_key, const char *fail_key); + +/* + * Close THS. + */ +void ths_close(THS *ths); + +/* + * Read the fail cound of THS. + * Return: the value read, or -1 on error + */ +int32_t ths_read_fails(const THS *ths); + +/* + * Read the temperature of THS in deciKelvin. + * Return: the value read, or -1 on error + */ +int32_t ths_read_temp(const THS *ths); + +/* + * Read the relative humidity of THS as an integer percentage. + * Return: the value read, or -1 on error + */ +int32_t ths_read_humid(const THS *ths); + +#endif diff --git a/util.c b/util.c index 8994062..add739a 100644 --- a/util.c +++ b/util.c @@ -10,11 +10,14 @@ #include "util.h" #include +#include -struct GlobalFlags GLOBAL_FLAGS = { - .config_path = NULL, - .gpio_path = NULL, -}; +Options GLOBAL_OPTS; + +void cleanup_options(Options *opts) { + FREE_CHECKED(opts->config_path); + FREE_CHECKED(opts->gpio_path); +} void *malloc_checked(size_t n) { return realloc_checked(NULL, n); @@ -27,3 +30,11 @@ void *realloc_checked(void *old_ptr, size_t n) { } return ptr; } + +void *strdup_checked(const char *str) { + char *new_str = strdup(str); + if (!new_str) { + errx(1, "out of memory!"); + } + return new_str; +} diff --git a/util.h b/util.h index d0989ba..42827e1 100644 --- a/util.h +++ b/util.h @@ -11,12 +11,35 @@ #define INCLUDED_UTIL_H #include +#include +#include +#include +#include +#include +#include -struct GlobalFlags { - const char *config_path; // path to config file - const char *gpio_path; // path of GPIO device file to use -}; -extern struct GlobalFlags GLOBAL_FLAGS; +typedef struct { + char *config_path; // path to config file + bool strict_config; // exit if unknown config options is found + bool verbose; // be more verbose + + char *gpio_path; // path of GPIO device file to use + gpio_pin_t en_pin; + gpio_pin_t rs_pin; + gpio_pin_t rw_pin; + gpio_pin_t data_pins[8]; + char *lcd_version; // Is LCD jp or eu + char *temp_key; // sysctl key for temperature + char *humid_key; // sysctl key for humidity + char *fail_key; // sysctl key for number of fails + uint32_t fail_limit; // limit for number of failures before exit +} Options; +extern Options GLOBAL_OPTS; + +/* + * Cleanup an Options struct. + */ +void cleanup_options(Options *opts); /* * Like malloc(3), except that if allocation fails, an error will be written to @@ -30,4 +53,20 @@ void *malloc_checked(size_t n); */ void *realloc_checked(void *old_ptr, size_t n); +/* + * Like strdup(3), except that if allocation fails, an error will be written to + * standard error and abort(3) called + */ +void *strdup_checked(const char *str); + +/* + * Like free(3), but do nothing if P is NULL. + */ +#define FREE_CHECKED(p) if (p) {free(p);} + +/* + * Call fprintf(3) to stderr only if verbose mode was enabled. + */ +#define LOG_VERBOSE(...) if (GLOBAL_OPTS.verbose) {fprintf(stderr, __VA_ARGS__);} + #endif