Add date set/view and power screen

This commit is contained in:
Alexander Rosenberg 2024-04-05 04:16:42 -07:00
parent 317b3ee5b7
commit d05f4c39ec
Signed by: school-rpi4
GPG Key ID: 5CCFC80B0B47B04B
15 changed files with 468 additions and 67 deletions

View File

@ -8,7 +8,8 @@ LDFLAGS=-lgpio -lfigpar -lpthread ${SQLITE3_LDFLAGS}
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/statrange.c src/config.c src/ui/blankscreen.c src/drive.c\
src/ui/exportscreen.c
src/ui/exportscreen.c src/ui/setdatescreen.c src/ui/viewdatescreen.c\
src/ui/powerscreen.c
PROG=rpi4b-temp-humidity
all: config.conf bin/${PROG}
@ -22,20 +23,24 @@ bin/main.o bin/config.o: config.mk
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/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/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/datapoints.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/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/statrange.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/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/setdatescreen.o: src/ui/datesel.h
bin/main.o bin/ui/timesel.o bin/ui/datapoints.o: src/ui/timesel.h
bin/ui/statrange.o: src/ui/timesel.h
bin/ui/statrange.o bin/ui/setdatescreen.o: src/ui/timesel.h
bin/main.o bin/ths.o: src/ths.h
bin/main.o bin/config.o: src/config.h
@ -44,7 +49,10 @@ bin/main.o bin/ui/statsby.o: src/ui/statsby.h
bin/main.o bin/ui/datapoints.o: src/ui/datapoints.h
bin/main.o bin/ui/blankscreen.o: src/ui/blankscreen.h
bin/main.o bin/ui/statrange.o: src/ui/statrange.h
vin/main.o bin/ui/exportscreen.o: src/ui/exportscreen.h
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/viewdatescreen.o: src/ui/viewdatescreen.h
bin/main.o bin/ui/powerscreen.o: src/ui/powerscreen.h
bin/main.o bin/drive.o bin/ui/exportscreen.o: src/drive.h
.if "${DEFAULT_TEMP_UNIT}" != "F" && "${DEFAULT_TEMP_UNIT}" != "C" &&\

View File

@ -19,6 +19,9 @@
#include "ui/statrange.h"
#include "ui/blankscreen.h"
#include "ui/exportscreen.h"
#include "ui/viewdatescreen.h"
#include "ui/setdatescreen.h"
#include "ui/powerscreen.h"
#include <unistd.h>
#include <err.h>
@ -118,6 +121,9 @@ 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 *) view_date_screen_new());
screen_manager_add(screen_manager, (Screen *) set_date_screen_new());
screen_manager_add(screen_manager, (Screen *) power_screen_new());
while (RUNNING) {
lock_stat_globals();
uint32_t temp = LAST_TEMP;
@ -127,6 +133,8 @@ 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) {

View File

@ -13,8 +13,7 @@
#include <err.h>
void date_sel_init(DateSelection *ds, UtilPeriod limit_period) {
memset(&ds->start_time, 0, sizeof(UtilDate)); // min date
memset(&ds->end_time, 0xff, sizeof(UtilDate)); // max date
ds->clamp_time = true;
date_sel_set_period(ds, limit_period);
date_sel_reset(ds);
}
@ -42,6 +41,7 @@ void date_sel_set_period(DateSelection *ds, UtilPeriod limit_period) {
}
static void date_sel_clamp_time(DateSelection *ds) {
if (ds->clamp_time) {
if (ds->year > ds->end_time.local_year) {
ds->year = ds->end_time.local_year;
}
@ -74,6 +74,7 @@ static void date_sel_clamp_time(DateSelection *ds) {
ds->day = ds->start_time.local_day;
}
}
}
static void date_sel_cleanup(DateSelection *ds) {
date_sel_clamp_time(ds);
@ -96,7 +97,6 @@ static void date_sel_cleanup(DateSelection *ds) {
++ds->month;
date_sel_cleanup(ds);
}
date_sel_clamp_time(ds);
}
static void date_sel_add_units(DateSelection *ds, int n) {
@ -117,6 +117,7 @@ static void date_sel_add_units(DateSelection *ds, int n) {
DateSelectionState date_sel_dispatch(DateSelection *ds,
SensorState *state,
const char *label) {
if (ds->clamp_time) {
UtilDate start, end;
if (!get_database_limits(state->db, ds->limit_period, &start, &end)) {
warnx("failed to query database limits");
@ -126,6 +127,7 @@ DateSelectionState date_sel_dispatch(DateSelection *ds,
}
ds->start_time = start;
ds->end_time = end;
}
lcd_clear(state->lcd);
lcd_move_to(state->lcd, 0, 0);
lcd_write_string(state->lcd, label);
@ -199,3 +201,7 @@ DateSelectionState date_sel_dispatch(DateSelection *ds,
lcd_move_to(state->lcd, 1, cursor_pos);
return DATE_SEL_CONTINUE;
}
void date_sel_set_clamp_time(DateSelection *ds, bool clamp_time) {
ds->clamp_time = clamp_time;
}

View File

@ -28,6 +28,7 @@ typedef struct {
UtilPeriod limit_period;
UtilDate start_time;
UtilDate end_time;
bool clamp_time;
} DateSelection;
typedef enum {
@ -61,4 +62,9 @@ DateSelectionState date_sel_dispatch(DateSelection *ds,
SensorState *state,
const char *label);
/*
* Set weather or not DS clamps time according to CLAMP_TIME.
*/
void date_sel_set_clamp_time(DateSelection *ds, bool clamp_time);
#endif

75
src/ui/powerscreen.c Normal file
View File

@ -0,0 +1,75 @@
/*
* powerscreen.h - 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 "powerscreen.h"
#include <err.h>
#include <unistd.h>
#include <sys/reboot.h>
static bool power_screen_dispatch(PowerScreen *screen,
SensorState *state) {
if (state->back_down) {
lcd_display_control(state->lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF,
LCD_DISPLAY_ON);
screen->choice = POWER_SCREEN_REBOOT;
return true;
}
if (state->sel_down) {
switch (screen->choice) {
case POWER_SCREEN_REBOOT:
LOG_VERBOSE("Rebooting...");
reboot(RB_AUTOBOOT);
abort();
// how did we even get here???
break;
case POWER_SCREEN_POWEROFF:
LOG_VERBOSE("Powering off...");
reboot(RB_POWEROFF);
abort();
// how did we even get here???
break;
default:
warnx("invalid power screen choice");
lcd_display_control(state->lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF,
LCD_DISPLAY_ON);
return true;
}
}
screen->choice = abs((screen->choice + state->up_down - state->down_down) %
POWER_SCREEN_NCHOICE);
if (state->force_draw || state->up_down || state->down_down) {
lcd_clear(state->lcd);
lcd_move_to(state->lcd, 0, 0);
lcd_write_string(state->lcd, "Type:");
lcd_move_to(state->lcd, 1, 0);
lcd_write_char(state->lcd, '>');
switch (screen->choice) {
case POWER_SCREEN_REBOOT:
lcd_write_string(state->lcd, "REBOOT");
break;
case POWER_SCREEN_POWEROFF:
lcd_write_string(state->lcd, "POWEROFF");
break;
}
lcd_display_control(state->lcd, LCD_CURSOR_BLINK, LCD_CURSOR_ON,
LCD_DISPLAY_ON);
lcd_move_to(state->lcd, 1, 0);
}
return false;
}
PowerScreen *power_screen_new(void) {
PowerScreen *s = malloc_checked(sizeof(PowerScreen));
screen_init(&s->parent, "Power options",
(ScreenDispatchFunc) power_screen_dispatch,
(ScreenCleanupFunc) free);
s->choice = POWER_SCREEN_REBOOT;
return s;
}

31
src/ui/powerscreen.h Normal file
View File

@ -0,0 +1,31 @@
/*
* powerscreen.h - 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_POWERSCREEN_H
#define INCLUDED_POWERSCREEN_H
#include "screen.h"
typedef struct {
Screen parent;
enum {
POWER_SCREEN_REBOOT = 0,
POWER_SCREEN_POWEROFF,
POWER_SCREEN_NCHOICE,
};
int choice;
} PowerScreen;
/*
* Create a new power screen that can be used to reboot or halt the device.
*/
PowerScreen *power_screen_new(void);
#endif

157
src/ui/setdatescreen.c Normal file
View File

@ -0,0 +1,157 @@
/*
* setdatescreen.c - Screen for setting the date and time
* 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 "setdatescreen.h"
#include <err.h>
#include <unistd.h>
#include <sys/time.h>
static void set_date_reset(SetDateScreen *screen,
SensorState *state) {
date_sel_reset(&screen->ds);
time_sel_reset(&screen->ts);
screen->need_redraw = true;
screen->stage = SET_DATE_SELECT_DATE;
lcd_display_control(state->lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF,
LCD_DISPLAY_ON);
}
static bool set_date_screen_select_date(SetDateScreen *screen,
SensorState *state) {
if (state->sel_down || state->back_down || state->up_down ||
state->down_down) {
screen->need_redraw = true;
}
if (screen->need_redraw) {
screen->need_redraw = false;
DateSelectionState dss = date_sel_dispatch(&screen->ds, state, "New Date:");
switch (dss) {
case DATE_SEL_CONTINUE:
return false; // ignore
case DATE_SEL_BACK:
return true;
case DATE_SEL_DONE:
screen->stage = SET_DATE_SELECT_TIME;
break;
case DATE_SEL_ERROR:
screen->stage = SET_DATE_FAIL;
break;
}
screen->need_redraw = true;
}
return false;
}
static bool set_date_and_time(DateSelection *ds, TimeSelection *ts) {
int64_t utc_time = timedate_sel_to_uts_timestamp(ds, ts);
struct timeval new_time = {
.tv_sec = utc_time,
.tv_usec = 0, // can't set, user not fast enough (lol)
};
if (settimeofday(&new_time, NULL) < 0) {
warn("settimeofday(2) failed");
return false;
}
LOG_VERBOSE("Set system date and time to %04d/%02d/%02d %02d:%02d:%02d\n",
ds->year, ds->month, ds->day, ts->hour, ts->minute, ts->second);
return true;
}
static void set_date_screen_select_time(SetDateScreen *screen,
SensorState *state) {
if (state->sel_down || state->back_down || state->up_down ||
state->down_down) {
screen->need_redraw = true;
}
if (screen->need_redraw) {
screen->need_redraw = false;
TimeSelState tss = time_sel_dispatch(&screen->ts, state, "New Time");
switch (tss) {
case TIME_SEL_CONTINUE:
return; // ignore
case TIME_SEL_BACK:
--screen->stage;
break;
case TIME_SEL_DONE:
if (set_date_and_time(&screen->ds, &screen->ts)) {
screen->stage = SET_DATE_SUCCESS;
} else {
screen->stage = SET_DATE_FAIL;
}
break;
case TIME_SEL_ERROR:
screen->stage = SET_DATE_FAIL;
break;
}
screen->need_redraw = true;
}
}
static bool set_date_screen_message(SetDateScreen *screen,
SensorState *state,
const char *msg) {
if (state->up_down || state->down_down || state->sel_down ||
state->back_down) {
return 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, msg);
lcd_display_control(state->lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF,
LCD_DISPLAY_ON);
}
return false;
}
static bool set_date_screen_dispatch(SetDateScreen *screen,
SensorState *state) {
if (state->force_draw) {
screen->need_redraw = true;
}
switch (screen->stage) {
case SET_DATE_SELECT_DATE:
if (set_date_screen_select_date(screen, state)) {
set_date_reset(screen, state);
return true;
}
break;
case SET_DATE_SELECT_TIME:
set_date_screen_select_time(screen, state);
break;
case SET_DATE_FAIL:
if (set_date_screen_message(screen, state, "Fail!")) {
set_date_reset(screen, state);
return true;
}
break;
case SET_DATE_SUCCESS:
if (set_date_screen_message(screen, state, "Success!")) {
set_date_reset(screen, state);
return true;
}
break;
}
return false;
}
SetDateScreen *set_date_screen_new() {
SetDateScreen *s = malloc_checked(sizeof(SetDateScreen));
screen_init(&s->parent, "Set date/time",
(ScreenDispatchFunc) set_date_screen_dispatch,
(ScreenCleanupFunc) free);
date_sel_init(&s->ds, PERIOD_DAY);
date_sel_set_clamp_time(&s->ds, false);
time_sel_init(&s->ts, NULL, true);
s->need_redraw = true;
s->stage = SET_DATE_SELECT_DATE;
return s;
}

35
src/ui/setdatescreen.h Normal file
View File

@ -0,0 +1,35 @@
/*
* setdatescreen.h - Screen for setting the date and time
* 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_SETDATESCREEN_H
#define INCLUDED_SETDATESCREEN_H
#include "screen.h"
#include "timesel.h"
typedef struct {
Screen parent;
enum {
SET_DATE_SELECT_DATE,
SET_DATE_SELECT_TIME,
SET_DATE_FAIL,
SET_DATE_SUCCESS
} stage;
bool need_redraw;
DateSelection ds;
TimeSelection ts;
} SetDateScreen;
/*
* Create a new SetDateScreen. This screen will allow the user to configure the
* date and time.
*/
SetDateScreen *set_date_screen_new(void);
#endif

View File

@ -65,24 +65,6 @@ static void stat_range_handle_time(StatRangeScreen *screen, TimeSelection *ts,
}
}
static uint64_t to_uts_timestamp(DateSelection *ds, TimeSelection *ts) {
struct tm broken_time = {
.tm_sec = ts->second,
.tm_min = ts->minute,
.tm_hour = ts->hour,
.tm_mday = ds->day,
.tm_mon = ds->month - 1,
.tm_year = ds->year - 1900,
.tm_isdst = -1,
};
time_t it = mktime(&broken_time);
if (it < 0) {
warnx("failed to convert to UTC timestamp");
return 0;
}
return it;
}
static void stat_range_show_data(StatRangeScreen *screen, SensorState *state) {
if (state->back_down) {
screen->need_redraw = true;
@ -99,10 +81,10 @@ static void stat_range_show_data(StatRangeScreen *screen, SensorState *state) {
}
if (screen->need_redraw) {
screen->need_redraw = false;
uint64_t start = to_uts_timestamp(&screen->sds, &screen->sts);
uint64_t end = to_uts_timestamp(&screen->eds, &screen->ets);
int64_t start = timedate_sel_to_uts_timestamp(&screen->sds, &screen->sts);
int64_t end = timedate_sel_to_uts_timestamp(&screen->eds, &screen->ets);
if (start > end) {
uint64_t t = end;
int64_t t = end;
end = start;
start = t;
}

View File

@ -173,3 +173,21 @@ void time_sel_reset(TimeSelection *ts) {
ts->minute = 0;
ts->stage = TS_STAGE_HOUR;
}
int64_t timedate_sel_to_uts_timestamp(DateSelection *ds, TimeSelection *ts) {
struct tm broken_time = {
.tm_sec = ts->second,
.tm_min = ts->minute,
.tm_hour = ts->hour,
.tm_mday = ds->day,
.tm_mon = ds->month - 1,
.tm_year = ds->year - 1900,
.tm_isdst = -1,
};
time_t it = mktime(&broken_time);
if (it < 0) {
warnx("failed to convert to UTC timestamp");
return 0;
}
return it;
}

View File

@ -52,4 +52,9 @@ TimeSelState time_sel_dispatch(TimeSelection *ts, SensorState *state,
*/
void time_sel_reset(TimeSelection *ts);
/*
* Convert DS and TS to a UTC timestamp;
*/
int64_t timedate_sel_to_uts_timestamp(DateSelection *ds, TimeSelection *ts);
#endif

45
src/ui/viewdatescreen.c Normal file
View File

@ -0,0 +1,45 @@
/*
* viewdatescreen.c - Screen for viewing the date and time
* 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 "viewdatescreen.h"
#include <limits.h>
static bool view_date_screen_dispatch(ViewDateScreen *screen,
SensorState *state) {
time_t now = time(NULL);
if (state->back_down || state->sel_down || state->up_down ||
state->down_down) {
return true;
} else if (state->force_draw || now != screen->last_time) {
screen->last_time = now;
lcd_clear(state->lcd);
struct tm broken_time;
localtime_r(&now, &broken_time);
char buf[17];
snprintf(buf, 17, "%04d/%02d/%02d", broken_time.tm_year + 1900,
broken_time.tm_mon + 1, broken_time.tm_mday);
lcd_move_to(state->lcd, 0, 0);
lcd_write_string(state->lcd, buf);
snprintf(buf, 17, "%02d:%02d:%02d", broken_time.tm_hour,
broken_time.tm_min, broken_time.tm_sec);
lcd_move_to(state->lcd, 1, 0);
lcd_write_string(state->lcd, buf);
}
return false;
}
ViewDateScreen *view_date_screen_new() {
ViewDateScreen *s = malloc_checked(sizeof(ViewDateScreen));
screen_init(&s->parent, "View date/time",
(ScreenDispatchFunc) view_date_screen_dispatch,
(ScreenCleanupFunc) free);
s->last_time = INT64_MIN;
return s;
}

25
src/ui/viewdatescreen.h Normal file
View File

@ -0,0 +1,25 @@
/*
* viewdatescreen.h - Screen for viewing the date and time
* 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_VIEWDATESCREEN_H
#define INCLUDED_VIEWDATESCREEN_H
#include "screen.h"
typedef struct {
Screen parent;
int64_t last_time;
} ViewDateScreen;
/*
* Create a new view date screen that shows the user the current date and time.
*/
ViewDateScreen *view_date_screen_new(void);
#endif

View File

@ -357,7 +357,7 @@ bool get_data_point_info(sqlite3 *db, int64_t time, UtilDataPointInfo *info) {
}
}
bool get_average_for_range(sqlite3 *db, uint64_t start, uint64_t end,
bool get_average_for_range(sqlite3 *db, int64_t start, int64_t end,
UtilAverageRange *data) {
bool success = true;
sqlite3_bind_int64(AVG_FOR_RANGE_QUERY, 1, start);

View File

@ -203,7 +203,7 @@ typedef struct {
* Get the average DATA for the range of UTC times START to END (inclusive).
* Return: false if an error occurred, true otherwise.
*/
bool get_average_for_range(sqlite3 *db, uint64_t start, uint64_t end,
bool get_average_for_range(sqlite3 *db, int64_t start, int64_t end,
UtilAverageRange *data);
/*