Merge branch 'main' of git.zander.im:Zander671/rpi4b-temp-humidity

This commit is contained in:
Alexander Rosenberg 2024-04-10 01:15:08 -07:00
commit 8c638f00a5
Signed by: Zander671
GPG Key ID: 5FD0394ADBD72730
15 changed files with 878 additions and 11 deletions

View File

@ -9,7 +9,7 @@ SRCS=src/main.c src/util.c src/lcd.c src/ths.c src/button.c src/ui/screen.c\
src/ui/datesel.c src/ui/statsby.c src/ui/datapoints.c src/ui/timesel.c\ src/ui/datesel.c src/ui/statsby.c src/ui/datapoints.c src/ui/timesel.c\
src/ui/statrange.c src/config.c src/ui/blankscreen.c src/drive.c\ src/ui/statrange.c src/config.c src/ui/blankscreen.c src/drive.c\
src/ui/exportscreen.c src/ui/setdatescreen.c src/ui/viewdatescreen.c\ src/ui/exportscreen.c src/ui/setdatescreen.c src/ui/viewdatescreen.c\
src/ui/powerscreen.c src/ui/powerscreen.c src/ui/settzscreen.c src/ui/cleardatascreen.c
PROG=rpi4b-temp-humidity PROG=rpi4b-temp-humidity
all: config.conf bin/${PROG} all: config.conf bin/${PROG}
@ -24,16 +24,19 @@ bin/main.o bin/util.o bin/lcd.o bin/ths.o bin/button.o: src/util.h
bin/ui/screen.o bin/ui/statsby.o bin/ui/datesel.o: src/util.h bin/ui/screen.o bin/ui/statsby.o bin/ui/datesel.o: src/util.h
bin/ui/datapoints.o bin/ui/timesel.o bin/ui/statrange.o: src/util.h bin/ui/datapoints.o bin/ui/timesel.o bin/ui/statrange.o: src/util.h
bin/ui/viewdatescreen.o bin/ui/setdatescreen.o bin/ui/powerscreen.o: src/util.h bin/ui/viewdatescreen.o bin/ui/setdatescreen.o bin/ui/powerscreen.o: src/util.h
bin/ui/settzscreen.o bin/ui/cleardatascreen.o: src/util.h
bin/main.o bin/lcd.o bin/screen.o bin/ui/datesel.o: src/lcd.h bin/main.o bin/lcd.o bin/screen.o bin/ui/datesel.o: src/lcd.h
bin/ui/statsby.o bin/ui/timesel.o bin/ui/statrange.o: src/lcd.h bin/ui/statsby.o bin/ui/timesel.o bin/ui/statrange.o: src/lcd.h
bin/ui/datapoints.o bin/ui/viewdatescreen.o: src/lcd.h bin/ui/datapoints.o bin/ui/viewdatescreen.o: src/lcd.h
bin/ui/setdatescreen.o bin/ui/powerscreen.o: src/lcd.h bin/ui/setdatescreen.o bin/ui/powerscreen.o: src/lcd.h
bin/ui/settzscreen.o bin/ui/cleardatascreen.o: src/lcd.h
bin/main.o bin/ui/screen.o bin/ui/statsby.o: src/ui/screen.h bin/main.o bin/ui/screen.o bin/ui/statsby.o: src/ui/screen.h
bin/ui/datesel.o bin/ui/datapoints.o bin/ui/timesel.o: src/ui/screen.h bin/ui/datesel.o bin/ui/datapoints.o bin/ui/timesel.o: src/ui/screen.h
bin/ui/statrange.o bin/ui/setdatescreen.o: src/ui/screen.h bin/ui/statrange.o bin/ui/setdatescreen.o: src/ui/screen.h
bin/ui/viewdatescreen.o bin/ui/powerscreen.o: src/ui/screen.h bin/ui/viewdatescreen.o bin/ui/powerscreen.o: src/ui/screen.h
bin/ui/settzscreen.o bin/ui/cleardatascreen.o: src/ui/screen.h
bin/main.o bin/ui/datesel.o bin/ui/statsby.o: src/ui/datesel.h bin/main.o bin/ui/datesel.o bin/ui/statsby.o: src/ui/datesel.h
bin/ui/datapoints.o bin/ui/timesel.o bin/ui/statrange.o: src/ui/datesel.h bin/ui/datapoints.o bin/ui/timesel.o bin/ui/statrange.o: src/ui/datesel.h
@ -53,6 +56,8 @@ bin/main.o bin/ui/exportscreen.o: src/ui/exportscreen.h
bin/main.o bin/ui/setdatescreen.o: src/ui/setdatescreen.h bin/main.o bin/ui/setdatescreen.o: src/ui/setdatescreen.h
bin/main.o bin/ui/viewdatescreen.o: src/ui/viewdatescreen.h bin/main.o bin/ui/viewdatescreen.o: src/ui/viewdatescreen.h
bin/main.o bin/ui/powerscreen.o: src/ui/powerscreen.h bin/main.o bin/ui/powerscreen.o: src/ui/powerscreen.h
bin/main.o bin/ui/settzscreen.o: src/ui/settzscreen.h
bin/main.o bin/ui/cleardatascreen.o: src/ui/cleardatascreen.h
bin/main.o bin/drive.o bin/ui/exportscreen.o: src/drive.h bin/main.o bin/drive.o bin/ui/exportscreen.o: src/drive.h
.if "${DEFAULT_TEMP_UNIT}" != "F" && "${DEFAULT_TEMP_UNIT}" != "C" &&\ .if "${DEFAULT_TEMP_UNIT}" != "F" && "${DEFAULT_TEMP_UNIT}" != "C" &&\
@ -71,6 +76,7 @@ DEFINES:=\
-DDEFAULT_REFRESH_TIME="${DEFAULT_REFRESH_TIME}"\ -DDEFAULT_REFRESH_TIME="${DEFAULT_REFRESH_TIME}"\
-DDEFAULT_TEMP_UNIT="${DEFAULT_TEMP_UNIT}"\ -DDEFAULT_TEMP_UNIT="${DEFAULT_TEMP_UNIT}"\
-DDEFAULT_EXPORT_FILE_NAME="${DEFAULT_EXPORT_FILE_NAME}"\ -DDEFAULT_EXPORT_FILE_NAME="${DEFAULT_EXPORT_FILE_NAME}"\
-DDEFAULT_TIMEZONE="${DEFAULT_TIMEZONE}"\
${OBJS}: ${.TARGET:C/^bin/src/:C/.o$/.c/} ${OBJS}: ${.TARGET:C/^bin/src/:C/.o$/.c/}
@mkdir -p ${.TARGET:H} @mkdir -p ${.TARGET:H}

View File

@ -47,3 +47,8 @@ down_pin = 23
# The base name for export, .sqlite3 will be appended for SQLite exports and # The base name for export, .sqlite3 will be appended for SQLite exports and
# .csv will be appended for CSV exports # .csv will be appended for CSV exports
#export_file_name = DEFAULT_EXPORT_FILE_NAME #export_file_name = DEFAULT_EXPORT_FILE_NAME
# The timezone used. This is overridden by the environment variable TZ. See the
# tzset(3) manual page for more information about the format of this. Leave
# blank for system default
#timezone = DEFAULT_TIMEZONE

View File

@ -34,3 +34,8 @@ DEFAULT_TEMP_UNIT=F
# The base name for export, .sqlite3 will be appended for SQLite exports and # The base name for export, .sqlite3 will be appended for SQLite exports and
# .csv will be appended for CSV exports # .csv will be appended for CSV exports
DEFAULT_EXPORT_FILE_NAME=env_data DEFAULT_EXPORT_FILE_NAME=env_data
# The timezone used. This is overridden by the environment variable TZ. See the
# tzset(3) manual page for more information about the format of this. Leave
# blank for system default
DEFAULT_TIMEZONE=

View File

@ -205,6 +205,10 @@ static void set_options_from_entries(struct figpar_config *entries,
GLOBAL_OPTS.export_file_name = steal_opt_if_set(entries[17].value.str); GLOBAL_OPTS.export_file_name = steal_opt_if_set(entries[17].value.str);
LOG_VERBOSE("Using export_file_name: \"%s\"\n", LOG_VERBOSE("Using export_file_name: \"%s\"\n",
GLOBAL_OPTS.export_file_name); GLOBAL_OPTS.export_file_name);
entries[18].type = FIGPAR_TYPE_NONE;
GLOBAL_OPTS.timezone = steal_opt_if_set(entries[18].value.str);
LOG_VERBOSE("Using timezone: \"%s\"\n", GLOBAL_OPTS.timezone);
} }
static char *strdup_default_opt(const char *def) { static char *strdup_default_opt(const char *def) {
@ -317,6 +321,12 @@ void parse_config_file(const char *path) {
.action = parse_str_callback, .action = parse_str_callback,
.value = {.str = strdup_default_opt(STRINGIFY(DEFAULT_EXPORT_FILE_NAME))}, .value = {.str = strdup_default_opt(STRINGIFY(DEFAULT_EXPORT_FILE_NAME))},
}, },
{
.directive = "timezone",
.type = FIGPAR_TYPE_STR,
.action = parse_str_callback,
.value = {.str = strdup_default_opt(STRINGIFY(DEFAULT_TIMEZONE))},
},
{ .directive = NULL }, { .directive = NULL },
}; };
size_t entry_count = sizeof(entries) / sizeof(struct figpar_config); size_t entry_count = sizeof(entries) / sizeof(struct figpar_config);

View File

@ -22,6 +22,8 @@
#include "ui/viewdatescreen.h" #include "ui/viewdatescreen.h"
#include "ui/setdatescreen.h" #include "ui/setdatescreen.h"
#include "ui/powerscreen.h" #include "ui/powerscreen.h"
#include "ui/settzscreen.h"
#include "ui/cleardatascreen.h"
#include <unistd.h> #include <unistd.h>
#include <err.h> #include <err.h>
@ -84,9 +86,22 @@ void lock_stat_globals(void);
*/ */
#define unlock_stat_globals() pthread_mutex_unlock(&STAT_MUTEX) #define unlock_stat_globals() pthread_mutex_unlock(&STAT_MUTEX)
// should we restart instead of exiting
bool SHOULD_RESTART = false;
bool NEED_CLEAR_TZ = false;
int main(int argc, char *const *argv) { int main(int argc, char *const *argv) {
parse_arguments(argc, argv); parse_arguments(argc, argv);
parse_config_file(GLOBAL_OPTS.config_path); parse_config_file(GLOBAL_OPTS.config_path);
if (GLOBAL_OPTS.timezone) {
if (!getenv("TZ")) {
setenv("TZ", GLOBAL_OPTS.timezone, true);
NEED_CLEAR_TZ = true;
} else {
LOG_VERBOSE("Config timezone option shadowed by local environment variable\n");
}
}
tzset();
gpio_handle_t handle = gpio_open_device(GLOBAL_OPTS.gpio_path); gpio_handle_t handle = gpio_open_device(GLOBAL_OPTS.gpio_path);
if (handle == GPIO_INVALID_HANDLE) { if (handle == GPIO_INVALID_HANDLE) {
@ -121,9 +136,11 @@ int main(int argc, char *const *argv) {
if (GLOBAL_OPTS.bl_pin >= 0) { if (GLOBAL_OPTS.bl_pin >= 0) {
screen_manager_add(screen_manager, blank_screen_new()); screen_manager_add(screen_manager, blank_screen_new());
} }
screen_manager_add(screen_manager, (Screen *) power_screen_new());
screen_manager_add(screen_manager, (Screen *) view_date_screen_new()); screen_manager_add(screen_manager, (Screen *) view_date_screen_new());
screen_manager_add(screen_manager, (Screen *) set_date_screen_new()); screen_manager_add(screen_manager, (Screen *) set_date_screen_new());
screen_manager_add(screen_manager, (Screen *) power_screen_new()); screen_manager_add(screen_manager, (Screen *) set_tz_screen_new());
screen_manager_add(screen_manager, (Screen *) clear_data_screen_new());
while (RUNNING) { while (RUNNING) {
lock_stat_globals(); lock_stat_globals();
uint32_t temp = LAST_TEMP; uint32_t temp = LAST_TEMP;
@ -133,8 +150,6 @@ int main(int argc, char *const *argv) {
// 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);
} }
// this is mostly about LCD and DB cleanup, not freeing memory, this leaks a
// lot when caused from something other than the main screen
screen_manager_delete(screen_manager); screen_manager_delete(screen_manager);
lcd_close(lcd); lcd_close(lcd);
if (pthread_join(bg_update, NULL) != 0) { if (pthread_join(bg_update, NULL) != 0) {
@ -147,6 +162,14 @@ int main(int argc, char *const *argv) {
pthread_mutex_destroy(&STAT_MUTEX); pthread_mutex_destroy(&STAT_MUTEX);
gpio_close(handle); gpio_close(handle);
cleanup_options(&GLOBAL_OPTS); cleanup_options(&GLOBAL_OPTS);
if (SHOULD_RESTART) {
LOG_VERBOSE("Re-invoking...\n");
if (NEED_CLEAR_TZ) {
unsetenv("TZ");
}
execv(argv[0], argv);
err(1, "re-invoke with execv(2) failed");
}
return 0; return 0;
} }
@ -263,8 +286,13 @@ void update_stats(THS *ths, sqlite3_stmt *insert_statement) {
sqlite3_bind_int(insert_statement, 3, humid); sqlite3_bind_int(insert_statement, 3, humid);
int status = sqlite3_step(insert_statement); int status = sqlite3_step(insert_statement);
if (status != SQLITE_DONE) { if (status != SQLITE_DONE) {
errx(1, "failed to insert temp. and humid. data into database: %s", warnx("failed to insert temp. and humid. data into database: %s",
sqlite3_errstr(status)); sqlite3_errstr(status));
lock_stat_globals();
// this means error
LAST_TEMP = UINT32_MAX;
LAST_HUMID = UINT32_MAX;
unlock_stat_globals();
} }
} }

72
src/ui/cleardatascreen.c Normal file
View File

@ -0,0 +1,72 @@
/*
* cleardatascreen.c - Screen for rebooting or halting
* 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 "cleardatascreen.h"
#include <err.h>
static bool clear_data_screen_display(ClearDataScreen *screen,
SensorState *state) {
if (state->back_down || (!screen->choice && state->sel_down) ||
(screen->done && (state->sel_down || state->up_down ||
state->down_down))) {
screen->done = false;
screen->error = false;
lcd_display_control(state->lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF,
LCD_DISPLAY_ON);
return true;
}
if (state->sel_down) {
// TODO clear data
char *errmsg;
sqlite3_exec(state->db, "DELETE FROM env_data;", NULL, NULL, &errmsg);
if (errmsg) {
warnx("sqlite3 error when clearing database: \"%s\"\n", errmsg);
sqlite3_free(errmsg);
screen->error = true;
} else {
LOG_VERBOSE("Database cleared!\n");
}
screen->need_redraw = true;
screen->done = true;
return false;
}
if (screen->need_redraw || state->force_draw || state->up_down ||
state->down_down) {
screen->need_redraw = false;
screen->choice = abs((screen->choice + state->up_down -
state->down_down) % 2);
lcd_clear(state->lcd);
lcd_move_to(state->lcd, 0, 0);
if (screen->done) {
lcd_display_control(state->lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF,
LCD_DISPLAY_ON);
lcd_write_string(state->lcd, screen->error ? "Error!" : "Done!");
} else {
lcd_write_string(state->lcd, "Really clear?");
lcd_move_to(state->lcd, 1, 0);
lcd_write_string(state->lcd, ">No >Yes");
lcd_move_to(state->lcd, 1, 4 * screen->choice);
lcd_display_control(state->lcd, LCD_CURSOR_BLINK, LCD_CURSOR_ON,
LCD_DISPLAY_ON);
}
}
return false;
}
ClearDataScreen *clear_data_screen_new() {
ClearDataScreen *s = malloc_checked(sizeof(ClearDataScreen));
screen_init(&s->parent, "Clear data",
(ScreenDispatchFunc) clear_data_screen_display,
(ScreenCleanupFunc) free);
s->choice = 0;
s->need_redraw = false;
s->done = false;
return s;
}

29
src/ui/cleardatascreen.h Normal file
View File

@ -0,0 +1,29 @@
/*
* cleardatascreen.c - Screen for rebooting or halting
* 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_CLEARDATASCREEN_H
#define INCLUDED_CLEARDATASCREEN_H
#include "screen.h"
typedef struct {
Screen parent;
int choice;
bool done;
bool need_redraw;
bool error;
} ClearDataScreen;
/*
* Create a new ClearDataScreen that will allow the user to wipe data collected
* by the device.
*/
ClearDataScreen *clear_data_screen_new(void);
#endif

View File

@ -375,7 +375,9 @@ static bool export_screen_dispatch(ExportScreen *screen,
if (confirm_status == EXPORT_CONFIRM_YES) { if (confirm_status == EXPORT_CONFIRM_YES) {
kill(0, SIGUSR1); kill(0, SIGUSR1);
pthread_cancel(screen->backgroud_oper); pthread_cancel(screen->backgroud_oper);
pthread_detach(screen->backgroud_oper);
drive_unmount(cur_drive, true); drive_unmount(cur_drive, true);
screen->bg_inited = false;
screen->stage = EXPORT_SCREEN_CANCELED; screen->stage = EXPORT_SCREEN_CANCELED;
screen->need_redraw = true; screen->need_redraw = true;
} else if (confirm_status == EXPORT_CONFIRM_NO) { } else if (confirm_status == EXPORT_CONFIRM_NO) {
@ -395,11 +397,18 @@ static bool export_screen_dispatch(ExportScreen *screen,
return false; return false;
} }
static void export_screen_cleanup(ExportScreen *screen) {
if (screen->bg_inited) {
pthread_cancel(screen->backgroud_oper);
pthread_detach(screen->backgroud_oper);
}
}
ExportScreen *export_screen_new() { ExportScreen *export_screen_new() {
ExportScreen *s = malloc_checked(sizeof(ExportScreen)); ExportScreen *s = malloc_checked(sizeof(ExportScreen));
screen_init(&s->parent, "Export", screen_init(&s->parent, "Export",
(ScreenDispatchFunc) export_screen_dispatch, (ScreenDispatchFunc) export_screen_dispatch,
(ScreenCleanupFunc) free); (ScreenCleanupFunc) export_screen_cleanup);
s->read_drives = true; s->read_drives = true;
s->stage = EXPORT_SCREEN_FORMAT; s->stage = EXPORT_SCREEN_FORMAT;
s->format = EXPORT_FORMAT_SQLITE; s->format = EXPORT_FORMAT_SQLITE;

View File

@ -18,11 +18,14 @@ static bool power_screen_dispatch(PowerScreen *screen,
if (state->back_down) { if (state->back_down) {
lcd_display_control(state->lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF, lcd_display_control(state->lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF,
LCD_DISPLAY_ON); LCD_DISPLAY_ON);
screen->choice = POWER_SCREEN_REBOOT; screen->choice = POWER_SCREEN_RESTART_PROGRAM;
return true; return true;
} }
if (state->sel_down) { if (state->sel_down) {
switch (screen->choice) { switch (screen->choice) {
case POWER_SCREEN_RESTART_PROGRAM:
request_restart();
break;
case POWER_SCREEN_REBOOT: case POWER_SCREEN_REBOOT:
LOG_VERBOSE("Rebooting..."); LOG_VERBOSE("Rebooting...");
reboot(RB_AUTOBOOT); reboot(RB_AUTOBOOT);
@ -51,6 +54,9 @@ static bool power_screen_dispatch(PowerScreen *screen,
lcd_move_to(state->lcd, 1, 0); lcd_move_to(state->lcd, 1, 0);
lcd_write_char(state->lcd, '>'); lcd_write_char(state->lcd, '>');
switch (screen->choice) { switch (screen->choice) {
case POWER_SCREEN_RESTART_PROGRAM:
lcd_write_string(state->lcd, "REINIT");
break;
case POWER_SCREEN_REBOOT: case POWER_SCREEN_REBOOT:
lcd_write_string(state->lcd, "REBOOT"); lcd_write_string(state->lcd, "REBOOT");
break; break;
@ -70,6 +76,6 @@ PowerScreen *power_screen_new(void) {
screen_init(&s->parent, "Power options", screen_init(&s->parent, "Power options",
(ScreenDispatchFunc) power_screen_dispatch, (ScreenDispatchFunc) power_screen_dispatch,
(ScreenCleanupFunc) free); (ScreenCleanupFunc) free);
s->choice = POWER_SCREEN_REBOOT; s->choice = POWER_SCREEN_RESTART_PROGRAM;
return s; return s;
} }

View File

@ -15,7 +15,8 @@
typedef struct { typedef struct {
Screen parent; Screen parent;
enum { enum {
POWER_SCREEN_REBOOT = 0, POWER_SCREEN_RESTART_PROGRAM = 0,
POWER_SCREEN_REBOOT,
POWER_SCREEN_POWEROFF, POWER_SCREEN_POWEROFF,
POWER_SCREEN_NCHOICE, POWER_SCREEN_NCHOICE,
}; };

View File

@ -149,9 +149,14 @@ static bool stats_screen_dispatch(StatsScreen *screen, SensorState *state) {
lcd_clear(state->lcd); lcd_clear(state->lcd);
lcd_write_string(state->lcd, "temp humi time"); lcd_write_string(state->lcd, "temp humi time");
char buff[17]; char buff[17];
int cur_len = snprintf(buff, sizeof(buff), "%-4.1f%c %3" PRIu32 "%% ", int cur_len;
if (state->temp == UINT32_MAX || state->humid == UINT32_MAX) {
cur_len = snprintf(buff, sizeof(buff), "ERROR! ");
} else {
cur_len = snprintf(buff, sizeof(buff), "%-4.1f%c %3" PRIu32 "%% ",
convert_temperature(state->temp), convert_temperature(state->temp),
GLOBAL_OPTS.temp_unit, state->humid); GLOBAL_OPTS.temp_unit, state->humid);
}
struct tm lt; struct tm lt;
localtime_r(&cur_time, &lt); localtime_r(&cur_time, &lt);
strftime(buff + cur_len, sizeof(buff) - cur_len, strftime(buff + cur_len, sizeof(buff) - cur_len,

585
src/ui/settzscreen.c Normal file
View File

@ -0,0 +1,585 @@
/*
* settzscreen.c - Screen for setting the timezone
* 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 "settzscreen.h"
#include <spawn.h>
#include <err.h>
#include <sys/wait.h>
extern char **environ;
static void set_tz_screen_reset(SetTZScreen *screen, SensorState *state) {
screen->has_dst = false;
screen->stage = SET_TZ_OFFSET;
screen->off_sel_stage = TZ_OFFSET_SEL_HOUR;
screen->bound_sel_stage = DST_BOUND_SEL_TYPE;
tz_offset_reset(&screen->offset);
tz_offset_reset(&screen->dst_offset);
screen->did_set = false;
if (state) {
lcd_display_control(state->lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF,
LCD_DISPLAY_ON);
}
}
static void set_tz_offset_normalize(TZOffset *offset, bool allow_negative) {
if (offset->second < 0) {
offset->second = 59;
--offset->minute;
} else if (offset->second > 59) {
offset->second = 0;
++offset->minute;
}
if (offset->minute < 0) {
offset->minute = 59;
--offset->hour;
} else if (offset->minute > 59) {
offset->minute = 0;
++offset->hour;
}
if (allow_negative) {
if (offset->hour < -24) {
offset->hour = 24;
} else if (offset->hour > 24) {
offset->hour = -24;
}
} else {
if (offset->hour < 0) {
offset->hour = 24;
} else if (offset->hour > 24) {
offset->hour = 0;
}
}
}
static SetTZScreenAction set_tz_dispatch_select_offset(TZOffsetSelectStage *stage,
SensorState *state,
TZOffset *target,
const char *label,
bool force_redraw,
bool allow_negative) {
if (state->back_down) {
if (*stage == TZ_OFFSET_SEL_HOUR) {
*stage = TZ_OFFSET_SEL_SECOND;
return SET_TZ_BACK;
}
--*stage;
force_redraw = true;
}
if (state->up_down || state->down_down) {
force_redraw = true;
int change = state->up_down - state->down_down;
switch (*stage) {
case TZ_OFFSET_SEL_HOUR:
target->hour += change;
break;
case TZ_OFFSET_SEL_MINUTE:
target->minute += change;
break;
case TZ_OFFSET_SEL_SECOND:
target->second += change;
break;
}
set_tz_offset_normalize(target, allow_negative);
}
if (state->sel_down) {
if (*stage == TZ_OFFSET_SEL_SECOND) {
*stage = TZ_OFFSET_SEL_HOUR;
return SET_TZ_NEXT;
}
++*stage;
force_redraw = true;
}
if (force_redraw) {
const char *format;
int cursor_pos;
switch (*stage) {
case TZ_OFFSET_SEL_HOUR:
format = ">%0*d:%02d:%02d";
cursor_pos = 0;
break;
case TZ_OFFSET_SEL_MINUTE:
format = "%0*d:>%02d:%02d";
cursor_pos = target->hour < 0 ? 4 : 3;
break;
case TZ_OFFSET_SEL_SECOND:
format = "%0*d:%02d:>%02d";
cursor_pos = target->hour < 0 ? 7 : 6;
break;
default:
format = "";
cursor_pos = 0;
break;
}
char time_str[17];
snprintf(time_str, 17, format, target->hour < 0 ? 3 : 2, target->hour,
target->minute, target->second);
lcd_clear(state->lcd);
lcd_move_to(state->lcd, 0, 0);
lcd_write_string(state->lcd, label);
lcd_move_to(state->lcd, 1, 0);
lcd_write_string(state->lcd, time_str);
lcd_move_to(state->lcd, 1, cursor_pos);
lcd_display_control(state->lcd, LCD_CURSOR_BLINK, LCD_CURSOR_ON,
LCD_DISPLAY_ON);
}
return SET_TZ_CONTINUE;
}
static void set_tz_prompt_has_dst(SetTZScreen *screen,
SensorState *state) {
if (state->back_down) {
--screen->stage;
screen->need_redraw = true;
screen->off_sel_stage = TZ_OFFSET_SEL_SECOND;
return;
}
if (state->sel_down) {
if (screen->has_dst) {
screen->stage = SET_TZ_DST_OFFSET;
} else {
screen->stage = SET_TZ_FINISH;
}
screen->need_redraw = true;
screen->off_sel_stage = TZ_OFFSET_SEL_HOUR;
return;
}
if (state->up_down || state->down_down) {
screen->has_dst = abs((((int) screen->has_dst) + state->up_down -
state->down_down) % 2);
screen->need_redraw = true;
}
if (screen->need_redraw) {
screen->need_redraw = false;
lcd_clear(state->lcd);
lcd_move_to(state->lcd, 0, 0);
lcd_write_string(state->lcd, "Has DST?");
lcd_move_to(state->lcd, 1, 0);
lcd_write_string(state->lcd, ">No >Yes");
lcd_move_to(state->lcd, 1, screen->has_dst ? 4 : 0);
}
}
static const char *DST_BOUND_TYPE_LABELS[] = {
"Exact Julian",
"Leap Julian",
"Month Day Week"
};
static SetTZScreenAction set_tz_bound_select_type(SetTZScreen *screen,
SensorState *state,
DSTBound *target,
const char *label) {
if (state->back_down) {
screen->off_sel_stage = TZ_OFFSET_SEL_SECOND;
screen->bound_sel_stage = DST_BOUND_SEL_TIME;
return SET_TZ_BACK;
}
if (state->sel_down) {
switch (target->type) {
case DST_BOUND_EXACT_JULIAN:
screen->bound_sel_stage = DST_BOUND_SEL_JULIAN;
target->julian_day = 1;
break;
case DST_BOUND_LEAP_JULAIN:
screen->bound_sel_stage = DST_BOUND_SEL_JULIAN;
target->julian_day = 0;
break;
case DST_BOUND_MND:
screen->bound_sel_stage = DST_BOUND_SEL_MONTH;
target->month = 1;
target->week = 1;
target->day = 0;
break;
}
target->time.hour = 2;
target->time.minute = 0;
target->time.second = 0;
screen->julian_didgit = 3;
screen->need_redraw = true;
return SET_TZ_CONTINUE;
}
if (state->up_down || state->down_down) {
int new_type = (((int) target->type) + state->up_down - state->down_down) % 3;
if (new_type < 0) {
target->type = 2;
} else {
target->type = new_type;
}
screen->need_redraw = true;
}
if (screen->need_redraw) {
screen->need_redraw = false;
lcd_clear(state->lcd);
lcd_move_to(state->lcd, 0, 0);
lcd_write_string(state->lcd, label);
lcd_write_string(state->lcd, " Type:");
lcd_move_to(state->lcd, 1, 0);
lcd_write_char(state->lcd, '>');
lcd_write_string(state->lcd, DST_BOUND_TYPE_LABELS[target->type]);
lcd_move_to(state->lcd, 1, 0);
}
return SET_TZ_CONTINUE;
}
static const char *MONTH_ABBREVS[] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov",
"Dec",
};
static const char *WEEKDAY_ABBREVS[] = {
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat",
};
static SetTZScreenAction set_tz_bound_select_mnd(SetTZScreen *screen,
SensorState *state,
DSTBound *target,
const char *label) {
if (state->back_down) {
--screen->bound_sel_stage;
screen->need_redraw = true;
return SET_TZ_CONTINUE;
}
if (state->sel_down) {
screen->need_redraw = true;
if (screen->bound_sel_stage == DST_BOUND_SEL_DAY) {
screen->off_sel_stage = TZ_OFFSET_SEL_HOUR;
screen->bound_sel_stage = DST_BOUND_SEL_TIME;
return SET_TZ_CONTINUE;
}
++screen->bound_sel_stage;
}
if (state->up_down || state->down_down) {
int change = state->up_down - state->down_down;
switch (screen->bound_sel_stage) {
case DST_BOUND_SEL_MONTH:
target->month += change;
if (target->month < 1) {
target->month = 1;
} else if (target->month > 12) {
target->month = 12;
}
break;
case DST_BOUND_SEL_WEEK:
target->week += change;
if (target->week < 1) {
target->week = 1;
} else if (target->week > 5) {
target->week = 5;
}
break;
case DST_BOUND_SEL_DAY:
target->day += change;
if (target->day < 0) {
target->day = 0;
} else if (target->day > 6) {
target->day = 6;
}
break;
default:
break; // should not happen
}
screen->need_redraw = true;
}
if (screen->need_redraw) {
screen->need_redraw = false;
const char *format;
int cursor_pos;
switch(screen->bound_sel_stage) {
case DST_BOUND_SEL_MONTH:
format = ">%s-%d-%s";
cursor_pos = 0;
break;
case DST_BOUND_SEL_WEEK:
format = "%s->%d-%s";
cursor_pos = 4;
break;
case DST_BOUND_SEL_DAY:
format = "%s-%d->%s";
cursor_pos = 7;
break;
default:
// should not happen
format = "";
cursor_pos = 0;
break;
}
char sel_str[17];
snprintf(sel_str, 17, format, MONTH_ABBREVS[target->month - 1],
target->week, WEEKDAY_ABBREVS[target->day]);
lcd_clear(state->lcd);
lcd_move_to(state->lcd, 0, 0);
lcd_write_string(state->lcd, "Mon-Week-Day");
lcd_move_to(state->lcd, 1, 0);
lcd_write_string(state->lcd, sel_str);
lcd_move_to(state->lcd, 1, cursor_pos);
}
return SET_TZ_CONTINUE;
}
static int set_tz_normalize_julian(int julian, bool allow_zero) {
if (julian <= 0) {
return allow_zero ? 0 : 1;
} else if (julian > 365) {
return 365;
}
return julian;
}
static SetTZScreenAction set_tz_dispatch_select_julian(SetTZScreen *screen,
SensorState *state,
DSTBound *target,
const char *label) {
if (state->back_down) {
screen->need_redraw = true;
if (screen->julian_didgit == 3) {
screen->bound_sel_stage = DST_BOUND_SEL_TYPE;
return SET_TZ_CONTINUE;
}
++screen->julian_didgit;
}
if (state->sel_down) {
screen->need_redraw = true;
if (screen->julian_didgit == 1) {
screen->off_sel_stage = TZ_OFFSET_SEL_HOUR;
screen->bound_sel_stage = DST_BOUND_SEL_TIME;
return SET_TZ_CONTINUE;
}
--screen->julian_didgit;
}
if (state->up_down || state->down_down) {
screen->need_redraw = true;
int change = state->up_down - state->down_down;
int scale = 0;
switch (screen->julian_didgit) {
case 1: // ones
scale = 1;
break;
case 2: // tens
scale = 10;
break;
case 3: // hundreds
scale = 100;
break;
}
bool allow_zero = screen->off_sel_stage == DST_BOUND_LEAP_JULAIN;
int new_day = target->julian_day + scale * change;
target->julian_day = set_tz_normalize_julian(new_day, allow_zero);
}
if (screen->need_redraw) {
screen->need_redraw = false;
char jul_str[4];
snprintf(jul_str, 4, "%03d", target->julian_day);
lcd_clear(state->lcd);
lcd_move_to(state->lcd, 0, 0);
lcd_write_string(state->lcd, "Julian Day");
lcd_move_to(state->lcd, 1, 0);
lcd_write_string(state->lcd, jul_str);
lcd_move_to(state->lcd, 1, 3 - screen->julian_didgit);
}
return SET_TZ_CONTINUE;
}
static SetTZScreenAction set_tz_dispatch_select_bound(SetTZScreen *screen,
SensorState *state,
DSTBound *target,
const char *label) {
SetTZScreenAction action;
switch (screen->bound_sel_stage) {
case DST_BOUND_SEL_TYPE:
return set_tz_bound_select_type(screen, state, target, label);
case DST_BOUND_SEL_MONTH:
case DST_BOUND_SEL_WEEK:
case DST_BOUND_SEL_DAY:
return set_tz_bound_select_mnd(screen, state, target, label);
case DST_BOUND_SEL_JULIAN:
return set_tz_dispatch_select_julian(screen, state, target, label);
case DST_BOUND_SEL_TIME:
action = set_tz_dispatch_select_offset(&screen->off_sel_stage, state,
&target->time, "Effect Time:",
screen->need_redraw, false);
switch (action) {
case SET_TZ_NEXT:
screen->bound_sel_stage = DST_BOUND_SEL_TYPE;
case SET_TZ_CONTINUE:
screen->need_redraw = false;
return action;
case SET_TZ_BACK:
if (target->type == DST_BOUND_MND) {
screen->bound_sel_stage = DST_BOUND_SEL_DAY;
} else {
screen->julian_didgit = 1;
screen->bound_sel_stage = DST_BOUND_SEL_JULIAN;
}
screen->need_redraw = true;
return SET_TZ_CONTINUE;
}
default:
return SET_TZ_BACK;
}
}
static void set_tz_write_dst_bound(FILE *tz, DSTBound *bound) {
fputc(',', tz);
switch (bound->type) {
case DST_BOUND_EXACT_JULIAN:
fprintf(tz, "J%d", bound->julian_day);
break;
case DST_BOUND_LEAP_JULAIN:
fprintf(tz, "%d", bound->julian_day);
break;
case DST_BOUND_MND:
fprintf(tz, "M%d.%d.%d", bound->month, bound->week, bound->day);
break;
}
fprintf(tz, "/%02d:%02d:%02d", bound->time.hour, bound->time.minute,
bound->time.second);
}
static bool set_tz_screen_finish(SetTZScreen *screen,
SensorState *state) {
if (!screen->did_set) {
screen->did_set = true;
size_t len;
char *buf;
FILE *tz = open_memstream(&buf, &len);
TZOffset *off = &screen->offset;
fprintf(tz, "timezone=CTZ%02d:%02d:%02d", off->hour, off->minute, off->second);
if (screen->has_dst) {
TZOffset *dst_off = &screen->dst_offset;
fprintf(tz, "CDST%02d:%02d:%02d", dst_off->hour, dst_off->minute,
dst_off->second);
set_tz_write_dst_bound(tz, &screen->start_bound);
set_tz_write_dst_bound(tz, &screen->end_bound);
}
fclose(tz);
char *const args[] = {
"sysrc",
"-f",
GLOBAL_OPTS.config_path,
buf,
NULL
};
pid_t pid;
int exit_status;
if (posix_spawnp(&pid, "sysrc", NULL, NULL, args, environ) != 0) {
warnx("failed to start sysrc(8)");
} else if (waitpid(pid, &exit_status, 0) < 0) {
warn("waitpid(2) for sysrc(8) failed");
} else if (exit_status != 0) {
warnx("sysrc(8) failed to set timezone");
} else {
LOG_VERBOSE("Set timezone to: \"%s\"\n", buf);
}
free(buf);
request_restart();
}
if (state->up_down || state->back_down ||
state->sel_down || state->down_down) {
set_tz_screen_reset(screen, state);
return true;
}
if (screen->need_redraw) {
screen->need_redraw = false;
lcd_display_control(state->lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF,
LCD_DISPLAY_ON);
lcd_clear(state->lcd);
lcd_move_to(state->lcd, 0, 0);
lcd_write_string(state->lcd, "Done!");
}
return false;
}
static bool set_tz_screen_dispatch(SetTZScreen *screen,
SensorState *state) {
if (state->force_draw) {
screen->need_redraw = true;
}
SetTZScreenAction action;
switch (screen->stage) {
case SET_TZ_OFFSET:
action = set_tz_dispatch_select_offset(&screen->off_sel_stage, state,
&screen->offset, "Offset",
screen->need_redraw, true);
screen->need_redraw = false;
switch (action) {
case SET_TZ_BACK:
set_tz_screen_reset(screen, state);
return true;
case SET_TZ_CONTINUE:
break; // ignore
case SET_TZ_NEXT:
screen->need_redraw = true;
++screen->stage;
break;
}
break;
case SET_TZ_HAS_DST:
set_tz_prompt_has_dst(screen, state);
break;
case SET_TZ_DST_OFFSET:
action = set_tz_dispatch_select_offset(&screen->off_sel_stage, state,
&screen->dst_offset,
"DST Offset",
screen->need_redraw, true);
screen->need_redraw = false;
switch (action) {
case SET_TZ_BACK:
--screen->stage;
screen->need_redraw = true;
case SET_TZ_CONTINUE:
break; // ignore
case SET_TZ_NEXT:
screen->need_redraw = true;
++screen->stage;
break;
}
break;
case SET_TZ_DST_START:
action = set_tz_dispatch_select_bound(screen, state,
&screen->start_bound, "Start");
switch (action) {
case SET_TZ_BACK:
--screen->stage;
screen->need_redraw = true;
case SET_TZ_CONTINUE:
break; // ignore
case SET_TZ_NEXT:
screen->need_redraw = true;
++screen->stage;
break;
}
break;
case SET_TZ_DST_END:
action = set_tz_dispatch_select_bound(screen, state,
&screen->end_bound, "End");
switch (action) {
case SET_TZ_BACK:
--screen->stage;
screen->need_redraw = true;
case SET_TZ_CONTINUE:
break; // ignore
case SET_TZ_NEXT:
screen->need_redraw = true;
++screen->stage;
break;
}
break;
case SET_TZ_FINISH:
return set_tz_screen_finish(screen, state);
}
return false;
}
SetTZScreen *set_tz_screen_new() {
SetTZScreen *s = malloc_checked(sizeof(SetTZScreen));
screen_init(&s->parent, "Set timezone",
(ScreenDispatchFunc) set_tz_screen_dispatch,
(ScreenCleanupFunc) free);
set_tz_screen_reset(s, NULL);
return s;
}

88
src/ui/settzscreen.h Normal file
View File

@ -0,0 +1,88 @@
/*
* settzscreen.h - Screen for setting the timezone
* 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_SETTZSCREEN_H
#define INCLUDED_SETTZSCREEN_H
#include "screen.h"
typedef struct {
int hour;
int minute;
int second;
} TZOffset;
#define tz_offset_reset(o) ((o)->hour = (o)->minute = (o)->second = 0)
typedef struct {
enum {
DST_BOUND_EXACT_JULIAN = 0,
DST_BOUND_LEAP_JULAIN,
DST_BOUND_MND,
} type;
union {
struct {
int month;
int week;
int day;
};
int julian_day;
};
TZOffset time;
} DSTBound;
typedef enum {
TZ_OFFSET_SEL_HOUR,
TZ_OFFSET_SEL_MINUTE,
TZ_OFFSET_SEL_SECOND,
} TZOffsetSelectStage;
typedef enum {
DST_BOUND_SEL_TYPE,
DST_BOUND_SEL_MONTH,
DST_BOUND_SEL_WEEK,
DST_BOUND_SEL_DAY,
DST_BOUND_SEL_JULIAN,
DST_BOUND_SEL_TIME,
} DSTBoundSelectStage;
typedef enum {
SET_TZ_BACK,
SET_TZ_CONTINUE,
SET_TZ_NEXT,
} SetTZScreenAction;
typedef struct {
Screen parent;
enum {
SET_TZ_OFFSET,
SET_TZ_HAS_DST,
SET_TZ_DST_OFFSET,
SET_TZ_DST_START,
SET_TZ_DST_END,
SET_TZ_FINISH,
} stage;
bool need_redraw;
TZOffsetSelectStage off_sel_stage;
DSTBoundSelectStage bound_sel_stage;
int julian_didgit;
TZOffset offset;
bool has_dst;
TZOffset dst_offset;
DSTBound start_bound;
DSTBound end_bound;
bool did_set;
} SetTZScreen;
/*
* Create a new SetTZScreen that will allow the user the set the time zone.
*/
SetTZScreen *set_tz_screen_new(void);
#endif

View File

@ -19,6 +19,7 @@
#include <fcntl.h> #include <fcntl.h>
#include <pthread.h> #include <pthread.h>
#include <errno.h> #include <errno.h>
#include <stdatomic.h>
const char *PERIOD_LABELS[] = { const char *PERIOD_LABELS[] = {
"HOUR", "HOUR",
@ -29,6 +30,10 @@ const char *PERIOD_LABELS[] = {
}; };
const size_t NPERIOD = sizeof(PERIOD_LABELS) / sizeof(char *); const size_t NPERIOD = sizeof(PERIOD_LABELS) / sizeof(char *);
// these are defined in main.c
extern _Atomic bool RUNNING;
extern bool SHOULD_RESTART;
Options GLOBAL_OPTS; Options GLOBAL_OPTS;
void cleanup_options(Options *opts) { void cleanup_options(Options *opts) {
@ -39,6 +44,7 @@ void cleanup_options(Options *opts) {
free(opts->fail_key); free(opts->fail_key);
free(opts->database_location); free(opts->database_location);
free(opts->export_file_name); free(opts->export_file_name);
free(opts->timezone);
} }
void *malloc_checked(size_t n) { void *malloc_checked(size_t n) {
@ -565,3 +571,8 @@ bool mkdirs(const char *path, mode_t mode) {
free(copy); free(copy);
return true; return true;
} }
void request_restart() {
RUNNING = false;
SHOULD_RESTART = true;
}

View File

@ -54,6 +54,7 @@ typedef struct {
TemperatureUnit temp_unit; TemperatureUnit temp_unit;
char *export_file_name; // file to export to char *export_file_name; // file to export to
char *timezone; // default timezone
} Options; } Options;
extern Options GLOBAL_OPTS; extern Options GLOBAL_OPTS;
@ -248,4 +249,10 @@ bool export_database_as_csv(sqlite3 *db, const char *dest);
*/ */
bool mkdirs(const char *path, mode_t mode); bool mkdirs(const char *path, mode_t mode);
/*
* Request that this program re-invoke itself with the same arguments. Used to
* re-initialize configuration values.
*/
void request_restart(void);
#endif #endif