Add menu system, fix Makefile
This commit is contained in:
parent
900243fb1c
commit
57974cc025
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,4 +1,5 @@
|
|||||||
*.o
|
bin/
|
||||||
|
*.core
|
||||||
rpi4b-temp-humidity
|
rpi4b-temp-humidity
|
||||||
compile_commands.json
|
compile_commands.json
|
||||||
.cache/
|
.cache/
|
26
Makefile
26
Makefile
@ -1,20 +1,22 @@
|
|||||||
CC=clang
|
# -*- mode: makefile -*-
|
||||||
CFLAGS=-std=c99 -Wall
|
|
||||||
LD=clang
|
|
||||||
LDFLAGS=-lgpio -lfigpar
|
|
||||||
SRCS=src/main.c src/util.c src/lcd.c src/ths.c src/button.c
|
|
||||||
PROG=rpi4b-temp-humidity
|
|
||||||
|
|
||||||
.include "config.mk"
|
.include "config.mk"
|
||||||
|
|
||||||
|
CC=clang
|
||||||
|
CFLAGS=-std=c11 -Wall ${SQLITE3_CFLAGS}
|
||||||
|
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/screen.c
|
||||||
|
PROG=rpi4b-temp-humidity
|
||||||
|
|
||||||
OBJS=${SRCS:C/^src/bin/:C/.c$/.o/}
|
OBJS=${SRCS:C/^src/bin/:C/.c$/.o/}
|
||||||
bin/${PROG}: ${OBJS}
|
bin/${PROG}: ${OBJS}
|
||||||
${LD} ${LDFLAGS} -o ${@} ${OBJS}
|
${LD} ${LDFLAGS} -o ${@} ${OBJS}
|
||||||
|
|
||||||
main.o util.o lcd.o ths.o button.o: util.h
|
bin/main.o bin/util.o bin/lcd.o bin/ths.o bin/button.o bin/screen.o: src/util.h
|
||||||
main.o lcd.o: lcd.h
|
bin/main.o bin/lcd.o bin/screen.o: src/lcd.h
|
||||||
main.o ths.o: ths.h
|
bin/main.o bin/ths.o bin/screen.o: src/ths.h
|
||||||
main.o button.o: button.h
|
bin/main.o bin/button.o bin/screen.o: src/button.h
|
||||||
|
bin/main.o bin/screen.o: src/screen.h
|
||||||
|
|
||||||
${OBJS}: ${.TARGET:C/^bin/src/:C/.o$/.c/}
|
${OBJS}: ${.TARGET:C/^bin/src/:C/.o$/.c/}
|
||||||
@mkdir -p bin/
|
@mkdir -p bin/
|
||||||
@ -27,7 +29,7 @@ ${OBJS}: ${.TARGET:C/^bin/src/:C/.o$/.c/}
|
|||||||
-DDEFAULT_FAIL_LIMIT="${DEFAULT_FAIL_LIMIT}"\
|
-DDEFAULT_FAIL_LIMIT="${DEFAULT_FAIL_LIMIT}"\
|
||||||
-DDEFAULT_LCD_VERSION="\"${DEFAULT_LCD_VERSION}\""\
|
-DDEFAULT_LCD_VERSION="\"${DEFAULT_LCD_VERSION}\""\
|
||||||
-DDEFAULT_REFRESH_TIME="${DEFAULT_REFRESH_TIME}"\
|
-DDEFAULT_REFRESH_TIME="${DEFAULT_REFRESH_TIME}"\
|
||||||
-o ${@} ${.ALLSRC}
|
-o ${@} ${.TARGET:C/^bin/src/:C/.o$/.c/}
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf bin/
|
rm -rf bin/
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
|
# Library and compile options
|
||||||
|
SQLITE3_LDFLAGS=-L/usr/local/lib -lsqlite3
|
||||||
|
SQLITE3_CFLAGS=-I/usr/local/include
|
||||||
|
|
||||||
|
# Default option values, empty means NULL (for strings)
|
||||||
DEFAULT_CONFIG_PATH=config.conf
|
DEFAULT_CONFIG_PATH=config.conf
|
||||||
DEFAULT_GPIO_DEVICE=/dev/gpioc0
|
DEFAULT_GPIO_DEVICE=/dev/gpioc0
|
||||||
DEFAULT_TEMP_KEY=dev.gpioths.0.temperature
|
DEFAULT_TEMP_KEY=dev.gpioths.0.temperature
|
||||||
|
13
src/button.c
13
src/button.c
@ -17,8 +17,7 @@ static const struct timespec DEBOUNCE_TIME = {
|
|||||||
.tv_nsec = 10 * 1000 * 1000, // 10ms
|
.tv_nsec = 10 * 1000 * 1000, // 10ms
|
||||||
};
|
};
|
||||||
|
|
||||||
Button *button_new(gpio_handle_t handle, gpio_pin_t pin, ButtonAction action,
|
Button *button_new(gpio_handle_t handle, gpio_pin_t pin) {
|
||||||
void *user_data) {
|
|
||||||
Button *button = malloc_checked(sizeof(Button));
|
Button *button = malloc_checked(sizeof(Button));
|
||||||
button->handle = handle;
|
button->handle = handle;
|
||||||
button->pin = pin;
|
button->pin = pin;
|
||||||
@ -27,8 +26,6 @@ Button *button_new(gpio_handle_t handle, gpio_pin_t pin, ButtonAction action,
|
|||||||
.g_flags = GPIO_PIN_INPUT | GPIO_PIN_PULLUP,
|
.g_flags = GPIO_PIN_INPUT | GPIO_PIN_PULLUP,
|
||||||
};
|
};
|
||||||
gpio_pin_set_flags(handle, &cfg);
|
gpio_pin_set_flags(handle, &cfg);
|
||||||
button->action = action;
|
|
||||||
button->user_data = user_data;
|
|
||||||
button->is_down = false;
|
button->is_down = false;
|
||||||
timespecclear(&button->last_detect);
|
timespecclear(&button->last_detect);
|
||||||
return button;
|
return button;
|
||||||
@ -38,7 +35,7 @@ void button_delete(Button *button) {
|
|||||||
free(button);
|
free(button);
|
||||||
}
|
}
|
||||||
|
|
||||||
int button_dispatch(Button *button) {
|
bool button_pressed(Button *button) {
|
||||||
struct timespec cur_time, diff_time;
|
struct timespec cur_time, diff_time;
|
||||||
clock_gettime(CLOCK_UPTIME, &cur_time);
|
clock_gettime(CLOCK_UPTIME, &cur_time);
|
||||||
timespecsub(&cur_time, &button->last_detect, &diff_time);
|
timespecsub(&cur_time, &button->last_detect, &diff_time);
|
||||||
@ -47,13 +44,11 @@ int button_dispatch(Button *button) {
|
|||||||
if (!button->is_down && status == GPIO_PIN_LOW) {
|
if (!button->is_down && status == GPIO_PIN_LOW) {
|
||||||
button->is_down = true;
|
button->is_down = true;
|
||||||
button->last_detect= cur_time;
|
button->last_detect= cur_time;
|
||||||
if (button->action) {
|
return true;
|
||||||
return button->action(button->user_data);
|
|
||||||
}
|
|
||||||
} else if (button->is_down && status == GPIO_PIN_HIGH) {
|
} else if (button->is_down && status == GPIO_PIN_HIGH) {
|
||||||
button->last_detect = cur_time;
|
button->last_detect = cur_time;
|
||||||
button->is_down = false;
|
button->is_down = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 0;
|
return false;
|
||||||
}
|
}
|
||||||
|
18
src/button.h
18
src/button.h
@ -14,13 +14,9 @@
|
|||||||
#include <libgpio.h>
|
#include <libgpio.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
typedef int(*ButtonAction)(void *);
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
gpio_handle_t handle;
|
gpio_handle_t handle;
|
||||||
gpio_pin_t pin;
|
gpio_pin_t pin;
|
||||||
ButtonAction action;
|
|
||||||
void *user_data;
|
|
||||||
|
|
||||||
struct timespec last_detect;
|
struct timespec last_detect;
|
||||||
bool is_down;
|
bool is_down;
|
||||||
@ -28,24 +24,20 @@ typedef struct {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* Create a Button object with the given HANDLE, PIN and ACTION. USER_DATA will
|
* Create a Button object with the given HANDLE, PIN and ACTION. USER_DATA will
|
||||||
* be passed to ACTION every time it is run. It is up to the caller to free
|
* be passed to ACTION every time it is run.
|
||||||
* USER_DATA when the Button is freed.
|
|
||||||
* Return: the created Button.
|
* Return: the created Button.
|
||||||
*/
|
*/
|
||||||
Button *button_new(gpio_handle_t uandle, gpio_pin_t pin, ButtonAction action,
|
Button *button_new(gpio_handle_t handle, gpio_pin_t pin);
|
||||||
void *user_data);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Delete BUTTON.
|
* Delete BUTTON.
|
||||||
* NOTE: This does NOT free USER_DATA.
|
|
||||||
*/
|
*/
|
||||||
void button_delete(Button *button);
|
void button_delete(Button *button);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Check if the pin for this button is HIGH, if it is, call ACTION and prevent
|
* Return: true if BUTTON is down
|
||||||
* it from being called again until button becomes LOW.
|
|
||||||
* Return: 0 if nothing is done, otherwise, the return value of ACTION.
|
|
||||||
*/
|
*/
|
||||||
int button_dispatch(Button *button);
|
bool button_pressed(Button *button);
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -68,7 +68,7 @@ LCD *lcd_open(gpio_handle_t handle, gpio_pin_t rs, gpio_pin_t rw, gpio_pin_t en,
|
|||||||
lcd_call(lcd, 0, 0, 0, 0, 1, 1, 1, 0, 0); // 5x8 font, 2 lines, 8bit
|
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_entry_mode(lcd, LCD_INCREMENT, LCD_CURSOR_MOVE);
|
||||||
lcd_display_control(lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF, LCD_DISPLAY_ON);
|
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,"
|
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);
|
"%d, %d, %d}\n", rs, rw, en, d0, d1, d2, d3, d4, d5, d6, d7);
|
||||||
return lcd;
|
return lcd;
|
||||||
}
|
}
|
||||||
|
216
src/main.c
216
src/main.c
@ -11,6 +11,8 @@
|
|||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "ths.h"
|
#include "ths.h"
|
||||||
#include "button.h"
|
#include "button.h"
|
||||||
|
#include "menu.h"
|
||||||
|
#include "screen.h"
|
||||||
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <err.h>
|
#include <err.h>
|
||||||
@ -23,6 +25,9 @@
|
|||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <stdatomic.h>
|
||||||
|
#include <signal.h>
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Print help message to standard output.
|
* Print help message to standard output.
|
||||||
@ -36,6 +41,33 @@ void parse_arguments(int argc, char *const *argv);
|
|||||||
* Parse config file PATH.
|
* Parse config file PATH.
|
||||||
*/
|
*/
|
||||||
void parse_config_file(const char *path);
|
void parse_config_file(const char *path);
|
||||||
|
/*
|
||||||
|
* Read the temp. and humid., store it in the configured database, and output it
|
||||||
|
* to the given uint32_t pointers.
|
||||||
|
*/
|
||||||
|
void update_stats(THS *ths, struct timespec *cur_time);
|
||||||
|
/*
|
||||||
|
* Setup signals used to ensure clean termination.
|
||||||
|
*/
|
||||||
|
void setup_signals(void);
|
||||||
|
/*
|
||||||
|
* Start the thread used to update the temp. and humid. and record them.
|
||||||
|
*/
|
||||||
|
void start_update_thread(pthread_t *thread);
|
||||||
|
|
||||||
|
// cross thread variables
|
||||||
|
pthread_mutex_t STAT_MUTEX;
|
||||||
|
uint32_t LAST_TEMP, LAST_HUMID;
|
||||||
|
time_t LAST_READ_SEC;
|
||||||
|
_Atomic bool RUNNING = true;
|
||||||
|
/*
|
||||||
|
* Lock the cross thread variables above.
|
||||||
|
*/
|
||||||
|
void lock_stat_globals(void);
|
||||||
|
/*
|
||||||
|
* Unlock the cross thread variables above.
|
||||||
|
*/
|
||||||
|
#define unlock_stat_globals() pthread_mutex_unlock(&STAT_MUTEX)
|
||||||
|
|
||||||
int main(int argc, char *const *argv) {
|
int main(int argc, char *const *argv) {
|
||||||
parse_arguments(argc, argv);
|
parse_arguments(argc, argv);
|
||||||
@ -52,54 +84,38 @@ int main(int argc, char *const *argv) {
|
|||||||
GLOBAL_OPTS.data_pins[3], GLOBAL_OPTS.data_pins[4],
|
GLOBAL_OPTS.data_pins[3], GLOBAL_OPTS.data_pins[4],
|
||||||
GLOBAL_OPTS.data_pins[5], GLOBAL_OPTS.data_pins[6],
|
GLOBAL_OPTS.data_pins[5], GLOBAL_OPTS.data_pins[6],
|
||||||
GLOBAL_OPTS.data_pins[7]);
|
GLOBAL_OPTS.data_pins[7]);
|
||||||
lcd_display_control(lcd, LCD_CURSOR_BLINK, LCD_CURSOR_ON, LCD_DISPLAY_ON);
|
setup_signals();
|
||||||
THS *ths = ths_open(GLOBAL_OPTS.temp_key, GLOBAL_OPTS.humid_key,
|
pthread_t bg_update;
|
||||||
GLOBAL_OPTS.fail_key);
|
start_update_thread(&bg_update);
|
||||||
if (!GLOBAL_OPTS.fail_key) {
|
ScreenManager *screen_manager = screen_manager_new(lcd, handle,
|
||||||
warnx("it's probably a bad idea to not set fail_key");
|
GLOBAL_OPTS.back_pin,
|
||||||
}
|
GLOBAL_OPTS.up_pin,
|
||||||
struct timespec last_update, cur_time, diff_time;
|
GLOBAL_OPTS.down_pin,
|
||||||
timespecclear(&last_update);
|
GLOBAL_OPTS.sel_pin);
|
||||||
struct timespec refresh_time = {
|
screen_manager_add(screen_manager, (Screen *) stats_screen_new());
|
||||||
.tv_sec = GLOBAL_OPTS.refresh_time / 1000,
|
while (RUNNING) {
|
||||||
.tv_nsec = (GLOBAL_OPTS.refresh_time % 1000) * 1000 * 1000,
|
lock_stat_globals();
|
||||||
};
|
uint32_t temp = LAST_TEMP;
|
||||||
Button *btn = button_new(handle, 23, (ButtonAction) printf, "Button Pressed!\n");
|
uint32_t humid = LAST_HUMID;
|
||||||
while (true) {
|
unlock_stat_globals();
|
||||||
clock_gettime(CLOCK_UPTIME, &cur_time);
|
screen_manager_dispatch(screen_manager, temp, humid);
|
||||||
timespecsub(&cur_time, &last_update, &diff_time);
|
|
||||||
if (timespeccmp(&diff_time, &refresh_time, >=)) {
|
|
||||||
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));
|
|
||||||
struct tm lt;
|
|
||||||
localtime_r(&cur_time.tv_sec, <);
|
|
||||||
strftime(buff + cur_len, sizeof(buff) - cur_len,
|
|
||||||
"%H:%M", <);
|
|
||||||
lcd_move_to(lcd, 1, 0);
|
|
||||||
lcd_write_string(lcd, buff);
|
|
||||||
LOG_VERBOSE("Temp. and humid. display updated\n");
|
|
||||||
last_update = cur_time;
|
|
||||||
}
|
|
||||||
button_dispatch(btn);
|
|
||||||
// 10ms is probably faster than anyone will press a button
|
// 10ms is probably faster than anyone will press a button
|
||||||
usleep(10 * 1000);
|
usleep(10 * 1000);
|
||||||
}
|
}
|
||||||
button_delete(btn);
|
screen_manager_delete(screen_manager);
|
||||||
ths_close(ths);
|
|
||||||
lcd_close(lcd);
|
lcd_close(lcd);
|
||||||
|
if (pthread_join(bg_update, NULL) != 0) {
|
||||||
|
err(1, "join of background thread failed");
|
||||||
|
}
|
||||||
|
// this needs to be done after bg_update exits
|
||||||
|
pthread_mutex_destroy(&STAT_MUTEX);
|
||||||
gpio_close(handle);
|
gpio_close(handle);
|
||||||
cleanup_options(&GLOBAL_OPTS);
|
cleanup_options(&GLOBAL_OPTS);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void print_help(const char *exec_name) {
|
void print_help(const char *exec_name) {
|
||||||
printf("usage: %s [-h] [-v] [-s] [-f CONFIG_PATH]\n", exec_name);
|
printf("usage: %s [-h] [-vs] [-f CONFIG_PATH]\n", exec_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
void parse_arguments(int argc, char *const *argv) {
|
void parse_arguments(int argc, char *const *argv) {
|
||||||
@ -269,6 +285,25 @@ static void set_options_from_entries(struct figpar_config *entries,
|
|||||||
LOG_VERBOSE("Using lcd_version: \"%s\"\n", GLOBAL_OPTS.lcd_version);
|
LOG_VERBOSE("Using lcd_version: \"%s\"\n", GLOBAL_OPTS.lcd_version);
|
||||||
|
|
||||||
GLOBAL_OPTS.refresh_time = entries[10].value.u_num;
|
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) {
|
void parse_config_file(const char *path) {
|
||||||
@ -278,7 +313,7 @@ void parse_config_file(const char *path) {
|
|||||||
.directive = "gpio_file",
|
.directive = "gpio_file",
|
||||||
.type = FIGPAR_TYPE_STR,
|
.type = FIGPAR_TYPE_STR,
|
||||||
.action = parse_str_callback,
|
.action = parse_str_callback,
|
||||||
.value = {.str = strdup_checked(DEFAULT_GPIO_DEVICE)},
|
.value = {.str = strdup_default_opt(DEFAULT_GPIO_DEVICE)},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.directive = "rs_pin",
|
.directive = "rs_pin",
|
||||||
@ -304,19 +339,19 @@ void parse_config_file(const char *path) {
|
|||||||
.directive = "temp_key",
|
.directive = "temp_key",
|
||||||
.type = FIGPAR_TYPE_STR,
|
.type = FIGPAR_TYPE_STR,
|
||||||
.action = parse_str_callback,
|
.action = parse_str_callback,
|
||||||
.value = {.str = strdup_checked(DEFAULT_TEMP_KEY)},
|
.value = {.str = strdup_default_opt(DEFAULT_TEMP_KEY)},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.directive = "humid_key",
|
.directive = "humid_key",
|
||||||
.type = FIGPAR_TYPE_STR,
|
.type = FIGPAR_TYPE_STR,
|
||||||
.action = parse_str_callback,
|
.action = parse_str_callback,
|
||||||
.value = {.str = strdup_checked(DEFAULT_HUMID_KEY)},
|
.value = {.str = strdup_default_opt(DEFAULT_HUMID_KEY)},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.directive = "fail_key",
|
.directive = "fail_key",
|
||||||
.type = FIGPAR_TYPE_STR,
|
.type = FIGPAR_TYPE_STR,
|
||||||
.action = parse_str_callback,
|
.action = parse_str_callback,
|
||||||
.value = {.str = strdup_checked(DEFAULT_FAIL_KEY)},
|
.value = {.str = strdup_default_opt(DEFAULT_FAIL_KEY)},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.directive = "fail_limit",
|
.directive = "fail_limit",
|
||||||
@ -328,7 +363,7 @@ void parse_config_file(const char *path) {
|
|||||||
.directive = "lcd_version",
|
.directive = "lcd_version",
|
||||||
.type = FIGPAR_TYPE_STR,
|
.type = FIGPAR_TYPE_STR,
|
||||||
.action = parse_str_callback,
|
.action = parse_str_callback,
|
||||||
.value = {.str = strdup_checked(DEFAULT_LCD_VERSION)},
|
.value = {.str = strdup_default_opt(DEFAULT_LCD_VERSION)},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.directive = "refresh_time",
|
.directive = "refresh_time",
|
||||||
@ -336,6 +371,26 @@ void parse_config_file(const char *path) {
|
|||||||
.action = parse_uint_callback,
|
.action = parse_uint_callback,
|
||||||
.value = {.u_num = DEFAULT_REFRESH_TIME},
|
.value = {.u_num = DEFAULT_REFRESH_TIME},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
.directive = "sel_pin",
|
||||||
|
.type = FIGPAR_TYPE_NONE,
|
||||||
|
.action = parse_uint_callback,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.directive = "down_pin",
|
||||||
|
.type = FIGPAR_TYPE_NONE,
|
||||||
|
.action = parse_uint_callback,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.directive = "up_pin",
|
||||||
|
.type = FIGPAR_TYPE_NONE,
|
||||||
|
.action = parse_uint_callback,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.directive = "back_pin",
|
||||||
|
.type = FIGPAR_TYPE_NONE,
|
||||||
|
.action = parse_uint_callback,
|
||||||
|
},
|
||||||
{ .directive = NULL },
|
{ .directive = NULL },
|
||||||
};
|
};
|
||||||
size_t entry_count = sizeof(entries) / sizeof(struct figpar_config);
|
size_t entry_count = sizeof(entries) / sizeof(struct figpar_config);
|
||||||
@ -362,3 +417,78 @@ void parse_config_file(const char *path) {
|
|||||||
}
|
}
|
||||||
LOG_VERBOSE("Finished loading config file\n");
|
LOG_VERBOSE("Finished loading config file\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void exit_signal_callback(int sig) {
|
||||||
|
LOG_VERBOSE("Caught signal %d. Exiting...\n", sig);
|
||||||
|
RUNNING = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define SIGNAL_SETUP_CHECKED(sig) \
|
||||||
|
if (signal(sig, exit_signal_callback) == SIG_ERR) {\
|
||||||
|
err(1, "failed to setup signal %s", #sig);\
|
||||||
|
}
|
||||||
|
void setup_signals() {
|
||||||
|
SIGNAL_SETUP_CHECKED(SIGTERM);
|
||||||
|
SIGNAL_SETUP_CHECKED(SIGINT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void update_stats(THS *ths, struct timespec *cur_time) {
|
||||||
|
if (GLOBAL_OPTS.fail_key && ths_read_fails(ths) > GLOBAL_OPTS.fail_limit) {
|
||||||
|
errx(1, "THS fail limit reached");
|
||||||
|
}
|
||||||
|
uint32_t temp = ths_read_temp(ths);
|
||||||
|
uint32_t humid = ths_read_humid(ths);
|
||||||
|
LOG_VERBOSE("Temp. and humid. read\n");
|
||||||
|
// TODO store in database
|
||||||
|
|
||||||
|
lock_stat_globals();
|
||||||
|
LAST_TEMP = temp;
|
||||||
|
LAST_HUMID = humid;
|
||||||
|
LAST_READ_SEC = cur_time->tv_sec;
|
||||||
|
unlock_stat_globals();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *update_thread_action(void *_ignored) {
|
||||||
|
sigset_t to_block;
|
||||||
|
sigemptyset(&to_block);
|
||||||
|
sigaddset(&to_block, SIGTERM);
|
||||||
|
sigaddset(&to_block, SIGINT);
|
||||||
|
if (pthread_sigmask(SIG_BLOCK, &to_block, NULL) != 0) {
|
||||||
|
err(1, "failed to set background thread signal mask");
|
||||||
|
}
|
||||||
|
THS *ths = ths_open(GLOBAL_OPTS.temp_key, GLOBAL_OPTS.humid_key,
|
||||||
|
GLOBAL_OPTS.fail_key);
|
||||||
|
struct timespec last_update, cur_time, diff_time;
|
||||||
|
timespecclear(&last_update);
|
||||||
|
struct timespec refresh_time = {
|
||||||
|
.tv_sec = GLOBAL_OPTS.refresh_time / 1000,
|
||||||
|
.tv_nsec = (GLOBAL_OPTS.refresh_time % 1000) * 1000 * 1000,
|
||||||
|
};
|
||||||
|
while (RUNNING) {
|
||||||
|
clock_gettime(CLOCK_UPTIME, &cur_time);
|
||||||
|
timespecsub(&cur_time, &last_update, &diff_time);
|
||||||
|
if (timespeccmp(&diff_time, &refresh_time, >=)) {
|
||||||
|
update_stats(ths, &cur_time);
|
||||||
|
last_update = cur_time;
|
||||||
|
}
|
||||||
|
usleep(10 * 1000); // 10ms, a reasonable delay
|
||||||
|
}
|
||||||
|
ths_close(ths);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void start_update_thread(pthread_t *thread) {
|
||||||
|
if (pthread_mutex_init(&STAT_MUTEX, NULL) != 0) {
|
||||||
|
err(1, "could not create mutex");
|
||||||
|
}
|
||||||
|
if (pthread_create(thread, NULL, &update_thread_action, NULL) != 0) {
|
||||||
|
err(1, "could not create backgroup update thread");
|
||||||
|
}
|
||||||
|
LOG_VERBOSE("Created background update thread\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void lock_stat_globals() {
|
||||||
|
if (pthread_mutex_lock(&STAT_MUTEX) != 0) {
|
||||||
|
err(1, "trying to lock mutex reported an error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
170
src/screen.c
Normal file
170
src/screen.c
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
/*
|
||||||
|
* screen.c - Simple menu system
|
||||||
|
* 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 "screen.h"
|
||||||
|
#include "util.h"
|
||||||
|
#include "ths.h"
|
||||||
|
|
||||||
|
#include <err.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
|
|
||||||
|
void screen_init(Screen *screen, const char *name,
|
||||||
|
ScreenDispatchFunc dispatch_func,
|
||||||
|
ScreenCleanupFunc cleanup_func) {
|
||||||
|
screen->dispatch_func = dispatch_func;
|
||||||
|
screen->cleanup_func = cleanup_func;
|
||||||
|
screen->name = strdup_checked(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
void screen_delete(Screen *screen) {
|
||||||
|
FREE_CHECKED(screen->name);
|
||||||
|
if (screen->cleanup_func) {
|
||||||
|
screen->cleanup_func(screen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ScreenManager *screen_manager_new(LCD *lcd, gpio_handle_t handle,
|
||||||
|
gpio_pin_t back_pin, gpio_pin_t up_pin,
|
||||||
|
gpio_pin_t down_pin, gpio_pin_t sel_pin) {
|
||||||
|
ScreenManager *sm = malloc_checked(sizeof(ScreenManager));
|
||||||
|
sm->lcd = lcd;
|
||||||
|
sm->screens = NULL;
|
||||||
|
sm->screen_count = 0;
|
||||||
|
sm->cur_scren = -1;
|
||||||
|
sm->up_btn = button_new(handle, up_pin);
|
||||||
|
sm->down_btn = button_new(handle, down_pin);
|
||||||
|
sm->back_btn = button_new(handle, back_pin);
|
||||||
|
sm->sel_btn = button_new(handle, sel_pin);
|
||||||
|
sm->cur_menu_line = 0;
|
||||||
|
sm->last_drawn_menu_line = 0;
|
||||||
|
sm->force_draw = true;
|
||||||
|
return sm;
|
||||||
|
}
|
||||||
|
|
||||||
|
void screen_manager_delete(ScreenManager *sm) {
|
||||||
|
for (size_t i = 0; i < sm->screen_count; ++i) {
|
||||||
|
screen_delete(sm->screens[i]);
|
||||||
|
}
|
||||||
|
FREE_CHECKED(sm->screens);
|
||||||
|
button_delete(sm->up_btn);
|
||||||
|
button_delete(sm->down_btn);
|
||||||
|
button_delete(sm->back_btn);
|
||||||
|
button_delete(sm->sel_btn);
|
||||||
|
free(sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
void screen_manager_add(ScreenManager *sm, Screen *screen) {
|
||||||
|
sm->screens = realloc_checked(sm->screens,
|
||||||
|
sizeof(Screen *) * ++sm->screen_count);
|
||||||
|
sm->screens[sm->screen_count - 1] = screen;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void screen_manager_dispatch_select_menu(ScreenManager *sm) {
|
||||||
|
if (button_pressed(sm->up_btn) && sm->cur_menu_line != 0) {
|
||||||
|
--sm->cur_menu_line;
|
||||||
|
}
|
||||||
|
if (button_pressed(sm->down_btn) && sm->cur_menu_line < sm->screen_count - 1) {
|
||||||
|
++sm->cur_menu_line;
|
||||||
|
}
|
||||||
|
// poll the button, but don't do anything with the state
|
||||||
|
button_pressed(sm->back_btn);
|
||||||
|
if (sm->cur_menu_line != sm->last_drawn_menu_line || sm->force_draw) {
|
||||||
|
sm->force_draw = false;
|
||||||
|
sm->last_drawn_menu_line = sm->cur_menu_line;
|
||||||
|
lcd_clear(sm->lcd);
|
||||||
|
lcd_move_to(sm->lcd, 0, 0);
|
||||||
|
if (sm->screen_count == 1) {
|
||||||
|
lcd_write_char(sm->lcd, '*');
|
||||||
|
lcd_write_string(sm->lcd, sm->screens[0]->name);
|
||||||
|
} else if (sm->cur_menu_line >= sm->screen_count - 1) {
|
||||||
|
lcd_write_char(sm->lcd, ' ');
|
||||||
|
lcd_write_string(sm->lcd, sm->screens[sm->screen_count - 2]->name);
|
||||||
|
lcd_move_to(sm->lcd, 1, 0);
|
||||||
|
lcd_write_char(sm->lcd, '*');
|
||||||
|
lcd_write_string(sm->lcd, sm->screens[sm->screen_count - 1]->name);
|
||||||
|
} else {
|
||||||
|
lcd_write_char(sm->lcd, '*');
|
||||||
|
lcd_write_string(sm->lcd, sm->screens[sm->cur_menu_line]->name);
|
||||||
|
lcd_move_to(sm->lcd, 1, 0);
|
||||||
|
lcd_write_char(sm->lcd, ' ');
|
||||||
|
lcd_write_string(sm->lcd, sm->screens[sm->cur_menu_line + 1]->name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (button_pressed(sm->sel_btn)) {
|
||||||
|
sm->cur_scren = sm->cur_menu_line;
|
||||||
|
sm->force_draw = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void screen_manager_dispatch(ScreenManager *sm, uint32_t temp, uint32_t humid) {
|
||||||
|
if (!sm->screen_count) {
|
||||||
|
errx(1, "attempt to display empty screen manager");
|
||||||
|
}
|
||||||
|
if (sm->cur_scren < 0) {
|
||||||
|
screen_manager_dispatch_select_menu(sm);
|
||||||
|
} else {
|
||||||
|
Screen *cs = sm->screens[sm->cur_scren];
|
||||||
|
if (cs->dispatch_func) {
|
||||||
|
SensorState state = {
|
||||||
|
.lcd = sm->lcd,
|
||||||
|
.temp = temp,
|
||||||
|
.humid = humid,
|
||||||
|
.back_down = button_pressed(sm->back_btn),
|
||||||
|
.up_down = button_pressed(sm->up_btn),
|
||||||
|
.down_down = button_pressed(sm->down_btn),
|
||||||
|
.sel_down = button_pressed(sm->sel_btn),
|
||||||
|
.force_draw = sm->force_draw,
|
||||||
|
};
|
||||||
|
if (cs->dispatch_func(cs, &state)) {
|
||||||
|
// if this is true, it means return to the main menu
|
||||||
|
sm->cur_scren = -1;
|
||||||
|
sm->force_draw = true;
|
||||||
|
} else {
|
||||||
|
sm->force_draw = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool stats_screen_dispatch(StatsScreen *screen, SensorState *state) {
|
||||||
|
if (state->back_down) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
time_t cur_time = time(NULL);
|
||||||
|
if (state->force_draw || state->temp != screen->last_temp
|
||||||
|
|| state->humid != screen->last_humid
|
||||||
|
|| screen->last_min != cur_time / 60) {
|
||||||
|
screen->last_temp = state->temp;
|
||||||
|
screen->last_humid = state->humid;
|
||||||
|
screen->last_min = cur_time / 60;
|
||||||
|
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);
|
||||||
|
struct tm lt;
|
||||||
|
localtime_r(&cur_time, <);
|
||||||
|
strftime(buff + cur_len, sizeof(buff) - cur_len,
|
||||||
|
"%H:%M", <);
|
||||||
|
lcd_move_to(state->lcd, 1, 0);
|
||||||
|
lcd_write_string(state->lcd, buff);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
StatsScreen *stats_screen_new() {
|
||||||
|
StatsScreen *s = malloc_checked(sizeof(StatsScreen));
|
||||||
|
screen_init(&s->parent, "Current Stats",
|
||||||
|
(ScreenDispatchFunc) stats_screen_dispatch,
|
||||||
|
(ScreenCleanupFunc) free);
|
||||||
|
s->last_humid = 0;
|
||||||
|
s->last_temp = 0;
|
||||||
|
return s;
|
||||||
|
}
|
104
src/screen.h
Normal file
104
src/screen.h
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
/*
|
||||||
|
* screen.h - Simple menu system
|
||||||
|
* 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_SCREEN_H
|
||||||
|
#define INCLUDED_SCREEN_H
|
||||||
|
|
||||||
|
#include "button.h"
|
||||||
|
#include "lcd.h"
|
||||||
|
|
||||||
|
#include <time.h>
|
||||||
|
#include <libgpio.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
LCD *lcd;
|
||||||
|
uint32_t temp;
|
||||||
|
uint32_t humid;
|
||||||
|
bool back_down;
|
||||||
|
bool up_down;
|
||||||
|
bool down_down;
|
||||||
|
bool sel_down;
|
||||||
|
bool force_draw;
|
||||||
|
} SensorState;
|
||||||
|
|
||||||
|
typedef struct _Screen Screen;
|
||||||
|
// should return true if we are to go back
|
||||||
|
typedef bool (*ScreenDispatchFunc)(Screen *, SensorState *);
|
||||||
|
typedef void (*ScreenCleanupFunc)(Screen *);
|
||||||
|
|
||||||
|
struct _Screen {
|
||||||
|
char *name;
|
||||||
|
ScreenDispatchFunc dispatch_func;
|
||||||
|
ScreenCleanupFunc cleanup_func;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Init Screen with NAME and the given callbacks.
|
||||||
|
* Return: the new Screen
|
||||||
|
*/
|
||||||
|
void screen_init(Screen *screen, const char *name,
|
||||||
|
ScreenDispatchFunc dispatch_func,
|
||||||
|
ScreenCleanupFunc cleanup_func);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Cleanup screen by calling its cleanup func and then freeing its name
|
||||||
|
*/
|
||||||
|
void screen_delete(Screen *screen);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
LCD *lcd;
|
||||||
|
Button *back_btn;
|
||||||
|
Button *up_btn;
|
||||||
|
Button *down_btn;
|
||||||
|
Button *sel_btn;
|
||||||
|
Screen **screens;
|
||||||
|
size_t screen_count;
|
||||||
|
|
||||||
|
// internal
|
||||||
|
ssize_t cur_scren;
|
||||||
|
size_t cur_menu_line;
|
||||||
|
size_t last_drawn_menu_line;
|
||||||
|
bool force_draw;
|
||||||
|
} ScreenManager;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Crate a new ScreenManager with the given LCD and buttons
|
||||||
|
* Return: the new ScreenManager
|
||||||
|
*/
|
||||||
|
ScreenManager *screen_manager_new(LCD *lcd, gpio_handle_t handle,
|
||||||
|
gpio_pin_t back_pin, gpio_pin_t up_pin,
|
||||||
|
gpio_pin_t down_pin, gpio_pin_t sel_pin);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Delete ScreenManager and the buttons it manages. This does NOT delete it's
|
||||||
|
* LCD.
|
||||||
|
*/
|
||||||
|
void screen_manager_delete(ScreenManager *sm);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Add SCREEN to SM.
|
||||||
|
*/
|
||||||
|
void screen_manager_add(ScreenManager *sm, Screen *screen);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Process input and, if needed, redraw the screen for ScreenManager. TEMP and
|
||||||
|
* HUMID should be the last values read by the connected THS.
|
||||||
|
*/
|
||||||
|
void screen_manager_dispatch(ScreenManager *sm, uint32_t temp, uint32_t humid);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
Screen parent;
|
||||||
|
uint32_t last_temp;
|
||||||
|
uint32_t last_humid;
|
||||||
|
time_t last_min;
|
||||||
|
} StatsScreen;
|
||||||
|
|
||||||
|
StatsScreen *stats_screen_new(void);
|
||||||
|
|
||||||
|
#endif
|
@ -36,6 +36,9 @@ void *realloc_checked(void *old_ptr, size_t n) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void *strdup_checked(const char *str) {
|
void *strdup_checked(const char *str) {
|
||||||
|
if (!str) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
char *new_str = strdup(str);
|
char *new_str = strdup(str);
|
||||||
if (!new_str) {
|
if (!new_str) {
|
||||||
errx(1, "out of memory!");
|
errx(1, "out of memory!");
|
||||||
|
@ -34,6 +34,12 @@ typedef struct {
|
|||||||
char *fail_key; // sysctl key for number of fails
|
char *fail_key; // sysctl key for number of fails
|
||||||
uint32_t fail_limit; // limit for number of failures before exit
|
uint32_t fail_limit; // limit for number of failures before exit
|
||||||
uint32_t refresh_time; // time between temp and humid refresh
|
uint32_t refresh_time; // time between temp and humid refresh
|
||||||
|
|
||||||
|
// menu navigation pins
|
||||||
|
gpio_pin_t up_pin;
|
||||||
|
gpio_pin_t down_pin;
|
||||||
|
gpio_pin_t back_pin;
|
||||||
|
gpio_pin_t sel_pin;
|
||||||
} Options;
|
} Options;
|
||||||
extern Options GLOBAL_OPTS;
|
extern Options GLOBAL_OPTS;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user