Makefile, options, and lcd stuff
This commit is contained in:
		
							
								
								
									
										147
									
								
								src/lcd.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										147
									
								
								src/lcd.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,147 @@ | ||||
| /* | ||||
|  * lcd.c - Subroutines for communicating with HD44780U based LCD panels | ||||
|  * 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. | ||||
|  * | ||||
|  * This file was created with help from the document located here: | ||||
|  * https://www.sparkfun.com/datasheets/LCD/HD44780.pdf | ||||
|  */ | ||||
| #include "lcd.h" | ||||
| #include "util.h" | ||||
|  | ||||
| #include <unistd.h> | ||||
|  | ||||
| static void lcd_set_read(LCD *lcd, bool is_read, bool force) { | ||||
|     if (force || lcd->is_read != is_read) { | ||||
|         gpio_config_t cfg = {.g_flags = is_read ? GPIO_PIN_INPUT | ||||
|                                         : GPIO_PIN_OUTPUT}; | ||||
|         cfg.g_pin = lcd->d0; | ||||
|         gpio_pin_set_flags(lcd->handle, &cfg); | ||||
|         cfg.g_pin = lcd->d1; | ||||
|         gpio_pin_set_flags(lcd->handle, &cfg); | ||||
|         cfg.g_pin = lcd->d2; | ||||
|         gpio_pin_set_flags(lcd->handle, &cfg); | ||||
|         cfg.g_pin = lcd->d3; | ||||
|         gpio_pin_set_flags(lcd->handle, &cfg); | ||||
|         cfg.g_pin = lcd->d4; | ||||
|         gpio_pin_set_flags(lcd->handle, &cfg); | ||||
|         cfg.g_pin = lcd->d5; | ||||
|         gpio_pin_set_flags(lcd->handle, &cfg); | ||||
|         cfg.g_pin = lcd->d6; | ||||
|         gpio_pin_set_flags(lcd->handle, &cfg); | ||||
|         cfg.g_pin = lcd->d7; | ||||
|         gpio_pin_set_flags(lcd->handle, &cfg); | ||||
|         lcd->is_read = is_read; | ||||
|     } | ||||
| } | ||||
|  | ||||
| static void lcd_pulse_enable(LCD *lcd) { | ||||
|     gpio_pin_high(lcd->handle, lcd->en); | ||||
|     usleep(1); // min of 1000ns | ||||
|     gpio_pin_low(lcd->handle, lcd->en); | ||||
| } | ||||
|  | ||||
| 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; | ||||
|     lcd->rw = rw; | ||||
|     lcd->en = en; | ||||
|     lcd->d0 = d0; | ||||
|     lcd->d1 = d1; | ||||
|     lcd->d2 = d2; | ||||
|     lcd->d3 = d3; | ||||
|     lcd->d4 = d4; | ||||
|     lcd->d5 = d5; | ||||
|     lcd->d6 = d6; | ||||
|     lcd->d7 = d7; | ||||
|     gpio_pin_output(lcd->handle, lcd->rw); | ||||
|     gpio_pin_output(lcd->handle, lcd->en); | ||||
|     gpio_pin_output(lcd->handle, lcd->rs); | ||||
|     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, 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); | ||||
|     } | ||||
|     lcd_set_read(lcd, false, false); | ||||
|     gpio_pin_low(lcd->handle, lcd->en); | ||||
|     gpio_pin_low(lcd->handle, lcd->rw); | ||||
|     gpio_pin_set(lcd->handle, lcd->rs, rs); | ||||
|     gpio_pin_set(lcd->handle, lcd->d0, d0); | ||||
|     gpio_pin_set(lcd->handle, lcd->d1, d1); | ||||
|     gpio_pin_set(lcd->handle, lcd->d2, d2); | ||||
|     gpio_pin_set(lcd->handle, lcd->d3, d3); | ||||
|     gpio_pin_set(lcd->handle, lcd->d4, d4); | ||||
|     gpio_pin_set(lcd->handle, lcd->d5, d5); | ||||
|     gpio_pin_set(lcd->handle, lcd->d6, d6); | ||||
|     gpio_pin_set(lcd->handle, lcd->d7, d7); | ||||
|     lcd_pulse_enable(lcd); | ||||
| } | ||||
|  | ||||
| void lcd_write_char(LCD *lcd, char c) { | ||||
|     lcd_call(lcd, 1, c & 1, (c >> 1) & 1, (c >> 2) & 1, (c >> 3) & 1, (c >> 4) & 1, | ||||
|              (c >> 5) & 1, (c >> 6) & 1, (c >> 7) & 1); | ||||
| } | ||||
|  | ||||
| size_t lcd_write_string(LCD *lcd, const char *str) { | ||||
|     size_t count; | ||||
|     for (count = 0; *str; ++str, ++count) { | ||||
|         lcd_write_char(lcd, *str); | ||||
|     } | ||||
|     return count; | ||||
| } | ||||
|  | ||||
| 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); | ||||
| } | ||||
|  | ||||
| void lcd_clear(LCD *lcd) { | ||||
|     lcd_call(lcd, 0, 1, 0, 0, 0, 0, 0, 0, 0); | ||||
| } | ||||
|  | ||||
| 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, 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, gpio_value_t sc, gpio_value_t rl) { | ||||
|     lcd_call(lcd, 0, 0, 0, rl, sc, 1, 0, 0, 0); | ||||
| } | ||||
|  | ||||
| bool lcd_is_busy(LCD *lcd) { | ||||
|     lcd_set_read(lcd, true, false); | ||||
|     gpio_pin_low(lcd->handle, lcd->rs); | ||||
|     gpio_pin_high(lcd->handle, lcd->rw); | ||||
|     gpio_pin_high(lcd->handle, lcd->en); | ||||
|     usleep(1); | ||||
|     gpio_value_t busy = gpio_pin_get(lcd->handle, lcd->d7); | ||||
|     gpio_pin_low(lcd->handle, lcd->en); | ||||
|     usleep(1); | ||||
|     return busy; | ||||
|      | ||||
| } | ||||
							
								
								
									
										109
									
								
								src/lcd.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								src/lcd.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,109 @@ | ||||
| /* | ||||
|  * lcd.h - Subroutines for communicating with HD44780U based LCD panels | ||||
|  * 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_LCD_H | ||||
| #define INCLUDED_LCD_H | ||||
|  | ||||
| #include <sys/types.h> | ||||
| #include <libgpio.h> | ||||
| #include <stdbool.h> | ||||
|  | ||||
| typedef struct { | ||||
|     gpio_handle_t handle; | ||||
|     // pin numbers | ||||
|     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; | ||||
|  | ||||
|     // gpio_value_ternal use | ||||
|     bool is_read; | ||||
| } LCD; | ||||
|  | ||||
| /* | ||||
|  * 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, 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. | ||||
|  */ | ||||
| void lcd_close(LCD *lcd); | ||||
| /* | ||||
|  * Call a single instruction on LCD. Each argument is the value of that pin. | ||||
|  */ | ||||
| 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. | ||||
|  */ | ||||
| void lcd_write_char(LCD *lcd, char c); | ||||
|  | ||||
| /* | ||||
|  * Write STR to LCD. | ||||
|  * Return: the number of characters written. | ||||
|  */ | ||||
| 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, gpio_value_t line, gpio_value_t col); | ||||
|  | ||||
| /* | ||||
|  * Clear LCD and return the cursor to the top right. | ||||
|  */ | ||||
| void lcd_clear(LCD *lcd); | ||||
|  | ||||
| #define LCD_DISPLAY_ON 1 | ||||
| #define LCD_DISPLAY_OFF 0 | ||||
| #define LCD_CURSOR_ON 1 | ||||
| #define LCD_CURSOR_OFF 0 | ||||
| #define LCD_CURSOR_BLINK 1 | ||||
| #define LCD_CURSOR_NO_BLINK 0 | ||||
| /* | ||||
|  * Control B, cursor blink, C, cursor visibility, and D, display power for LCD. | ||||
|  */ | ||||
| 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 | ||||
| #define LCD_DISPLAY_SHIFT 1 | ||||
| #define LCD_CURSOR_MOVE 0 | ||||
| #define LCD_SHIFT_RIGHT 1 | ||||
| #define LCD_SHIFT_LEFT 0 | ||||
| /* | ||||
|  * Control shift and increment for LCD. | ||||
|  */ | ||||
| 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, gpio_value_t sc, gpio_value_t rl); | ||||
|  | ||||
| /* | ||||
|  * Return: weather the busy flag is set for LCD. | ||||
|  */ | ||||
| bool lcd_is_busy(LCD *lcd); | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										339
									
								
								src/main.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										339
									
								
								src/main.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,339 @@ | ||||
| /* | ||||
|  * 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); | ||||
|     if (!GLOBAL_OPTS.fail_key) { | ||||
|         warnx("it's probably a bad idea to not set fail_key"); | ||||
|     } | ||||
|     while (true) { | ||||
|         if (GLOBAL_OPTS.fail_key && 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]\n", 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; | ||||
| } | ||||
|  | ||||
| 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.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"); | ||||
| } | ||||
							
								
								
									
										107
									
								
								src/ths.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								src/ths.c
									
									
									
									
									
										Normal file
									
								
							| @ -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 <err.h> | ||||
| #include <sys/sysctl.h> | ||||
| #include <inttypes.h> | ||||
|  | ||||
| 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; | ||||
|      | ||||
| } | ||||
							
								
								
									
										65
									
								
								src/ths.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								src/ths.h
									
									
									
									
									
										Normal file
									
								
							| @ -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 <stdint.h> | ||||
| #include <stddef.h> | ||||
|  | ||||
| #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 | ||||
							
								
								
									
										40
									
								
								src/util.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								src/util.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,40 @@ | ||||
| /* | ||||
|  * util.c - Utility functions | ||||
|  * 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 "util.h" | ||||
|  | ||||
| #include <err.h> | ||||
| #include <string.h> | ||||
|  | ||||
| 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); | ||||
| } | ||||
|  | ||||
| void *realloc_checked(void *old_ptr, size_t n) { | ||||
|     void *ptr = realloc(old_ptr, n); | ||||
|     if (n && !ptr) { | ||||
|         errx(1, "out of memory!"); | ||||
|     } | ||||
|     return ptr; | ||||
| } | ||||
|  | ||||
| void *strdup_checked(const char *str) { | ||||
|     char *new_str = strdup(str); | ||||
|     if (!new_str) { | ||||
|         errx(1, "out of memory!"); | ||||
|     } | ||||
|     return new_str; | ||||
| } | ||||
							
								
								
									
										72
									
								
								src/util.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								src/util.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,72 @@ | ||||
| /* | ||||
|  * util.h - Utility functions | ||||
|  * 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_UTIL_H | ||||
| #define INCLUDED_UTIL_H | ||||
|  | ||||
| #include <stdlib.h> | ||||
| #include <stdint.h> | ||||
| #include <stdio.h> | ||||
| #include <time.h> | ||||
| #include <stdbool.h> | ||||
| #include <sys/types.h> | ||||
| #include <libgpio.h> | ||||
|  | ||||
| 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 | ||||
|  * standard error and abort(3) called | ||||
|  */ | ||||
| void *malloc_checked(size_t n); | ||||
|  | ||||
| /* | ||||
|  * Like realloc(3), except that if allocation fails, an error will be written to | ||||
|  * standard error and abort(3) called | ||||
|  */ | ||||
| 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 | ||||
		Reference in New Issue
	
	Block a user