Add cleaer screen, timezone, and some other additions
This commit is contained in:
		
							
								
								
									
										10
									
								
								src/config.c
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								src/config.c
									
									
									
									
									
								
							| @ -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); | ||||
|     LOG_VERBOSE("Using export_file_name: \"%s\"\n", | ||||
|                 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) { | ||||
| @ -317,6 +321,12 @@ void parse_config_file(const char *path) { | ||||
|             .action = parse_str_callback, | ||||
|             .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 }, | ||||
|     }; | ||||
|     size_t entry_count = sizeof(entries) / sizeof(struct figpar_config); | ||||
|  | ||||
							
								
								
									
										38
									
								
								src/main.c
									
									
									
									
									
								
							
							
						
						
									
										38
									
								
								src/main.c
									
									
									
									
									
								
							| @ -22,6 +22,8 @@ | ||||
| #include "ui/viewdatescreen.h" | ||||
| #include "ui/setdatescreen.h" | ||||
| #include "ui/powerscreen.h" | ||||
| #include "ui/settzscreen.h" | ||||
| #include "ui/cleardatascreen.h" | ||||
|  | ||||
| #include <unistd.h> | ||||
| #include <err.h> | ||||
| @ -84,9 +86,22 @@ void lock_stat_globals(void); | ||||
|  */ | ||||
| #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) { | ||||
|     parse_arguments(argc, argv); | ||||
|     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); | ||||
|     if (handle == GPIO_INVALID_HANDLE) { | ||||
| @ -121,9 +136,11 @@ int main(int argc, char *const *argv) { | ||||
|     if (GLOBAL_OPTS.bl_pin >= 0) { | ||||
|         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 *) 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) { | ||||
|         lock_stat_globals(); | ||||
|         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 | ||||
|         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); | ||||
|     lcd_close(lcd); | ||||
|     if (pthread_join(bg_update, NULL) != 0) { | ||||
| @ -147,6 +162,14 @@ int main(int argc, char *const *argv) { | ||||
|     pthread_mutex_destroy(&STAT_MUTEX); | ||||
|     gpio_close(handle); | ||||
|     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; | ||||
| } | ||||
|  | ||||
| @ -263,8 +286,13 @@ void update_stats(THS *ths, sqlite3_stmt *insert_statement) { | ||||
|     sqlite3_bind_int(insert_statement, 3, humid); | ||||
|     int status = sqlite3_step(insert_statement); | ||||
|     if (status != SQLITE_DONE) { | ||||
|         errx(1, "failed to insert temp. and humid. data into database: %s", | ||||
|              sqlite3_errstr(status)); | ||||
|         warnx("failed to insert temp. and humid. data into database: %s", | ||||
|               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
									
								
							
							
						
						
									
										72
									
								
								src/ui/cleardatascreen.c
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										29
									
								
								src/ui/cleardatascreen.h
									
									
									
									
									
										Normal 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 | ||||
| @ -375,7 +375,9 @@ static bool export_screen_dispatch(ExportScreen *screen, | ||||
|         if (confirm_status == EXPORT_CONFIRM_YES) { | ||||
|             kill(0, SIGUSR1); | ||||
|             pthread_cancel(screen->backgroud_oper); | ||||
|             pthread_detach(screen->backgroud_oper); | ||||
|             drive_unmount(cur_drive, true); | ||||
|             screen->bg_inited = false; | ||||
|             screen->stage = EXPORT_SCREEN_CANCELED; | ||||
|             screen->need_redraw = true; | ||||
|         } else if (confirm_status == EXPORT_CONFIRM_NO) { | ||||
| @ -395,11 +397,18 @@ static bool export_screen_dispatch(ExportScreen *screen, | ||||
|     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 *s = malloc_checked(sizeof(ExportScreen)); | ||||
|     screen_init(&s->parent, "Export", | ||||
|                 (ScreenDispatchFunc) export_screen_dispatch, | ||||
|                 (ScreenCleanupFunc) free); | ||||
|                 (ScreenCleanupFunc) export_screen_cleanup); | ||||
|     s->read_drives = true; | ||||
|     s->stage = EXPORT_SCREEN_FORMAT; | ||||
|     s->format = EXPORT_FORMAT_SQLITE; | ||||
|  | ||||
| @ -18,11 +18,14 @@ static bool power_screen_dispatch(PowerScreen *screen, | ||||
|     if (state->back_down) { | ||||
|         lcd_display_control(state->lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF, | ||||
|                             LCD_DISPLAY_ON); | ||||
|         screen->choice = POWER_SCREEN_REBOOT; | ||||
|         screen->choice = POWER_SCREEN_RESTART_PROGRAM; | ||||
|         return true; | ||||
|     } | ||||
|     if (state->sel_down) { | ||||
|         switch (screen->choice) { | ||||
|         case POWER_SCREEN_RESTART_PROGRAM: | ||||
|             request_restart(); | ||||
|             break; | ||||
|         case POWER_SCREEN_REBOOT: | ||||
|             LOG_VERBOSE("Rebooting..."); | ||||
|             reboot(RB_AUTOBOOT); | ||||
| @ -51,6 +54,9 @@ static bool power_screen_dispatch(PowerScreen *screen, | ||||
|         lcd_move_to(state->lcd, 1, 0); | ||||
|         lcd_write_char(state->lcd, '>'); | ||||
|         switch (screen->choice) { | ||||
|         case POWER_SCREEN_RESTART_PROGRAM: | ||||
|             lcd_write_string(state->lcd, "REINIT"); | ||||
|             break; | ||||
|         case POWER_SCREEN_REBOOT: | ||||
|             lcd_write_string(state->lcd, "REBOOT"); | ||||
|             break; | ||||
| @ -70,6 +76,6 @@ PowerScreen *power_screen_new(void) { | ||||
|     screen_init(&s->parent, "Power options", | ||||
|                 (ScreenDispatchFunc) power_screen_dispatch, | ||||
|                 (ScreenCleanupFunc) free); | ||||
|     s->choice = POWER_SCREEN_REBOOT; | ||||
|     s->choice = POWER_SCREEN_RESTART_PROGRAM; | ||||
|     return s; | ||||
| } | ||||
|  | ||||
| @ -15,7 +15,8 @@ | ||||
| typedef struct { | ||||
|     Screen parent; | ||||
|     enum { | ||||
|         POWER_SCREEN_REBOOT = 0, | ||||
|         POWER_SCREEN_RESTART_PROGRAM = 0, | ||||
|         POWER_SCREEN_REBOOT, | ||||
|         POWER_SCREEN_POWEROFF, | ||||
|         POWER_SCREEN_NCHOICE, | ||||
|     }; | ||||
|  | ||||
| @ -149,9 +149,14 @@ static bool stats_screen_dispatch(StatsScreen *screen, SensorState *state) { | ||||
|         lcd_clear(state->lcd); | ||||
|         lcd_write_string(state->lcd, "temp  humi time"); | ||||
|         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), | ||||
|                                GLOBAL_OPTS.temp_unit, state->humid); | ||||
|         } | ||||
|         struct tm lt; | ||||
|         localtime_r(&cur_time, <); | ||||
|         strftime(buff + cur_len, sizeof(buff) - cur_len, | ||||
|  | ||||
							
								
								
									
										585
									
								
								src/ui/settzscreen.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										585
									
								
								src/ui/settzscreen.c
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										88
									
								
								src/ui/settzscreen.h
									
									
									
									
									
										Normal 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 | ||||
							
								
								
									
										11
									
								
								src/util.c
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								src/util.c
									
									
									
									
									
								
							| @ -19,6 +19,7 @@ | ||||
| #include <fcntl.h> | ||||
| #include <pthread.h> | ||||
| #include <errno.h> | ||||
| #include <stdatomic.h> | ||||
|  | ||||
| const char *PERIOD_LABELS[] = { | ||||
|     "HOUR", | ||||
| @ -29,6 +30,10 @@ const char *PERIOD_LABELS[] = { | ||||
| }; | ||||
| 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; | ||||
|  | ||||
| void cleanup_options(Options *opts) { | ||||
| @ -39,6 +44,7 @@ void cleanup_options(Options *opts) { | ||||
|     free(opts->fail_key); | ||||
|     free(opts->database_location); | ||||
|     free(opts->export_file_name); | ||||
|     free(opts->timezone); | ||||
| } | ||||
|  | ||||
| void *malloc_checked(size_t n) { | ||||
| @ -565,3 +571,8 @@ bool mkdirs(const char *path, mode_t mode) { | ||||
|     free(copy); | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| void request_restart() { | ||||
|     RUNNING = false; | ||||
|     SHOULD_RESTART = true; | ||||
| } | ||||
|  | ||||
| @ -54,6 +54,7 @@ typedef struct { | ||||
|     TemperatureUnit temp_unit; | ||||
|  | ||||
|     char *export_file_name; // file to export to | ||||
|     char *timezone; // default timezone | ||||
| } Options; | ||||
| 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); | ||||
|  | ||||
| /* | ||||
|  * Request that this program re-invoke itself with the same arguments. Used to | ||||
|  * re-initialize configuration values. | ||||
|  */ | ||||
| void request_restart(void); | ||||
|  | ||||
| #endif | ||||
|  | ||||
		Reference in New Issue
	
	Block a user