Work on the second stats screen
This commit is contained in:
parent
81eb22e83a
commit
da83874d11
2
Makefile
2
Makefile
@ -2,7 +2,7 @@
|
|||||||
.include "config.mk"
|
.include "config.mk"
|
||||||
|
|
||||||
CC=clang
|
CC=clang
|
||||||
CFLAGS=-std=c11 -Wall ${SQLITE3_CFLAGS}
|
CFLAGS=-g -std=c11 -Wall ${SQLITE3_CFLAGS}
|
||||||
LD=clang
|
LD=clang
|
||||||
LDFLAGS=-lgpio -lfigpar -lpthread ${SQLITE3_LDFLAGS}
|
LDFLAGS=-lgpio -lfigpar -lpthread ${SQLITE3_LDFLAGS}
|
||||||
SRCS=src/main.c src/util.c src/lcd.c src/ths.c src/button.c src/screen.c
|
SRCS=src/main.c src/util.c src/lcd.c src/ths.c src/button.c src/screen.c
|
||||||
|
17
src/main.c
17
src/main.c
@ -91,14 +91,17 @@ int main(int argc, char *const *argv) {
|
|||||||
GLOBAL_OPTS.data_pins[7]);
|
GLOBAL_OPTS.data_pins[7]);
|
||||||
setup_signals();
|
setup_signals();
|
||||||
open_database();
|
open_database();
|
||||||
|
initialize_util_queries(DATABASE);
|
||||||
pthread_t bg_update;
|
pthread_t bg_update;
|
||||||
start_update_thread(&bg_update);
|
start_update_thread(&bg_update);
|
||||||
ScreenManager *screen_manager = screen_manager_new(lcd, handle,
|
ScreenManager *screen_manager = screen_manager_new(DATABASE,
|
||||||
|
lcd, handle,
|
||||||
GLOBAL_OPTS.back_pin,
|
GLOBAL_OPTS.back_pin,
|
||||||
GLOBAL_OPTS.up_pin,
|
GLOBAL_OPTS.up_pin,
|
||||||
GLOBAL_OPTS.down_pin,
|
GLOBAL_OPTS.down_pin,
|
||||||
GLOBAL_OPTS.sel_pin);
|
GLOBAL_OPTS.sel_pin);
|
||||||
screen_manager_add(screen_manager, (Screen *) stats_screen_new());
|
screen_manager_add(screen_manager, (Screen *) stats_screen_new());
|
||||||
|
screen_manager_add(screen_manager, (Screen *) stats_by_screen_new());
|
||||||
while (RUNNING) {
|
while (RUNNING) {
|
||||||
lock_stat_globals();
|
lock_stat_globals();
|
||||||
uint32_t temp = LAST_TEMP;
|
uint32_t temp = LAST_TEMP;
|
||||||
@ -115,6 +118,7 @@ int main(int argc, char *const *argv) {
|
|||||||
err(1, "join of background thread failed");
|
err(1, "join of background thread failed");
|
||||||
}
|
}
|
||||||
// this needs to be done after bg_update exits
|
// this needs to be done after bg_update exits
|
||||||
|
cleanup_util_queries();
|
||||||
sqlite3_close(DATABASE);
|
sqlite3_close(DATABASE);
|
||||||
pthread_mutex_destroy(&STAT_MUTEX);
|
pthread_mutex_destroy(&STAT_MUTEX);
|
||||||
gpio_close(handle);
|
gpio_close(handle);
|
||||||
@ -386,12 +390,12 @@ void parse_config_file(const char *path) {
|
|||||||
.action = parse_uint_callback,
|
.action = parse_uint_callback,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.directive = "down_pin",
|
.directive = "up_pin",
|
||||||
.type = FIGPAR_TYPE_NONE,
|
.type = FIGPAR_TYPE_NONE,
|
||||||
.action = parse_uint_callback,
|
.action = parse_uint_callback,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.directive = "up_pin",
|
.directive = "down_pin",
|
||||||
.type = FIGPAR_TYPE_NONE,
|
.type = FIGPAR_TYPE_NONE,
|
||||||
.action = parse_uint_callback,
|
.action = parse_uint_callback,
|
||||||
},
|
},
|
||||||
@ -430,6 +434,9 @@ void parse_config_file(const char *path) {
|
|||||||
static void exit_signal_callback(int sig) {
|
static void exit_signal_callback(int sig) {
|
||||||
LOG_VERBOSE("Caught signal %d. Exiting...\n", sig);
|
LOG_VERBOSE("Caught signal %d. Exiting...\n", sig);
|
||||||
RUNNING = false;
|
RUNNING = false;
|
||||||
|
if (signal(sig, SIG_DFL) == SIG_ERR) {
|
||||||
|
err(1, "resetting signal handler failed");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#define SIGNAL_SETUP_CHECKED(sig) \
|
#define SIGNAL_SETUP_CHECKED(sig) \
|
||||||
@ -497,13 +504,13 @@ static void create_db_table() {
|
|||||||
LOG_VERBOSE("Ensured env_data table existance\n");
|
LOG_VERBOSE("Ensured env_data table existance\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char *UPDATE_STATES_QUERY =
|
static const char *UPDATE_STATS_QUERY =
|
||||||
"INSERT INTO env_data (time, temp, humid) "
|
"INSERT INTO env_data (time, temp, humid) "
|
||||||
"VALUES(?, ?, ?);";
|
"VALUES(?, ?, ?);";
|
||||||
static void *update_thread_action(void *_ignored) {
|
static void *update_thread_action(void *_ignored) {
|
||||||
create_db_table();
|
create_db_table();
|
||||||
sqlite3_stmt *insert_statement = NULL;
|
sqlite3_stmt *insert_statement = NULL;
|
||||||
int status = sqlite3_prepare_v2(DATABASE, UPDATE_STATES_QUERY, -1,
|
int status = sqlite3_prepare_v2(DATABASE, UPDATE_STATS_QUERY, -1,
|
||||||
&insert_statement, NULL);
|
&insert_statement, NULL);
|
||||||
if (status != SQLITE_OK) {
|
if (status != SQLITE_OK) {
|
||||||
errx(1, "could not compile SQL query. sqlite3 error follows:\n%s",
|
errx(1, "could not compile SQL query. sqlite3 error follows:\n%s",
|
||||||
|
297
src/screen.c
297
src/screen.c
@ -8,11 +8,12 @@
|
|||||||
* version. See the LICENSE file for more information.
|
* version. See the LICENSE file for more information.
|
||||||
*/
|
*/
|
||||||
#include "screen.h"
|
#include "screen.h"
|
||||||
#include "util.h"
|
|
||||||
#include "ths.h"
|
#include "ths.h"
|
||||||
|
|
||||||
#include <err.h>
|
#include <err.h>
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
|
||||||
void screen_init(Screen *screen, const char *name,
|
void screen_init(Screen *screen, const char *name,
|
||||||
@ -30,10 +31,11 @@ void screen_delete(Screen *screen) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ScreenManager *screen_manager_new(LCD *lcd, gpio_handle_t handle,
|
ScreenManager *screen_manager_new(sqlite3 *db, LCD *lcd, gpio_handle_t handle,
|
||||||
gpio_pin_t back_pin, gpio_pin_t up_pin,
|
gpio_pin_t back_pin, gpio_pin_t up_pin,
|
||||||
gpio_pin_t down_pin, gpio_pin_t sel_pin) {
|
gpio_pin_t down_pin, gpio_pin_t sel_pin) {
|
||||||
ScreenManager *sm = malloc_checked(sizeof(ScreenManager));
|
ScreenManager *sm = malloc_checked(sizeof(ScreenManager));
|
||||||
|
sm->db = db;
|
||||||
sm->lcd = lcd;
|
sm->lcd = lcd;
|
||||||
sm->screens = NULL;
|
sm->screens = NULL;
|
||||||
sm->screen_count = 0;
|
sm->screen_count = 0;
|
||||||
@ -133,7 +135,7 @@ void screen_manager_dispatch(ScreenManager *sm, uint32_t temp, uint32_t humid) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool stats_screen_dispatch(StatsScreen *screen, SensorState *state) {
|
static bool stats_screen_dispatch(StatsScreen *screen, SensorState *state) {
|
||||||
if (state->back_down) {
|
if (state->back_down) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -168,3 +170,292 @@ StatsScreen *stats_screen_new() {
|
|||||||
s->last_temp = 0;
|
s->last_temp = 0;
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const char *PERIOD_LABELS[] = {
|
||||||
|
"HOUR",
|
||||||
|
"DAY",
|
||||||
|
"WEEK",
|
||||||
|
"MONTH",
|
||||||
|
"YEAR",
|
||||||
|
};
|
||||||
|
static const size_t NPERIOD = sizeof(PERIOD_LABELS) / sizeof(char *);
|
||||||
|
|
||||||
|
static void stats_by_select_period(StatsByScreen *screen, SensorState *state) {
|
||||||
|
if (state->up_down) {
|
||||||
|
screen->period = (screen->period + 1) % NPERIOD;
|
||||||
|
screen->need_redraw = true;
|
||||||
|
}
|
||||||
|
if (state->down_down) {
|
||||||
|
if (--screen->period < 0) {
|
||||||
|
screen->period = NPERIOD - 1;
|
||||||
|
}
|
||||||
|
screen->need_redraw = true;
|
||||||
|
}
|
||||||
|
if (screen->need_redraw) {
|
||||||
|
lcd_clear(state->lcd);
|
||||||
|
lcd_move_to(state->lcd, 0, 0);
|
||||||
|
lcd_write_string(state->lcd, "Period:");
|
||||||
|
lcd_move_to(state->lcd, 1, 0);
|
||||||
|
lcd_write_string(state->lcd, ">");
|
||||||
|
lcd_write_string(state->lcd, PERIOD_LABELS[screen->period]);
|
||||||
|
lcd_move_to(state->lcd, 1, 0);
|
||||||
|
lcd_display_control(state->lcd, LCD_CURSOR_BLINK, LCD_CURSOR_ON,
|
||||||
|
LCD_DISPLAY_ON);
|
||||||
|
screen->need_redraw = false;
|
||||||
|
}
|
||||||
|
if (state->sel_down) {
|
||||||
|
++screen->stage;
|
||||||
|
switch (screen->period) {
|
||||||
|
case 0:
|
||||||
|
case 1:
|
||||||
|
case 2:
|
||||||
|
screen->ds.max_stage = DATE_SEL_DAY;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
screen->ds.max_stage = DATE_SEL_MONTH;
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
screen->ds.max_stage = DATE_SEL_YEAR;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
screen->need_redraw = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void date_sel_init(DateSelection *ds, int start_line, int start_col,
|
||||||
|
DateSelectionStage max_stage) {
|
||||||
|
ds->max_stage = max_stage;
|
||||||
|
ds->start_line = start_line;
|
||||||
|
ds->start_col = start_col;
|
||||||
|
memset(&ds->start_time, 0, sizeof(UtilDate)); // min date
|
||||||
|
memset(&ds->end_time, 0xff, sizeof(UtilDate)); // max date
|
||||||
|
date_sel_reset(ds);
|
||||||
|
}
|
||||||
|
|
||||||
|
void date_sel_reset(DateSelection *ds) {
|
||||||
|
ds->year = -1;
|
||||||
|
ds->stage = DATE_SEL_YEAR;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void date_sel_clamp_time(DateSelection *ds) {
|
||||||
|
if (ds->year > ds->end_time.local_year) {
|
||||||
|
ds->year = ds->end_time.local_year;
|
||||||
|
}
|
||||||
|
if (ds->year == ds->end_time.local_year &&
|
||||||
|
ds->month > ds->end_time.local_month) {
|
||||||
|
ds->month = ds->end_time.local_month;
|
||||||
|
}
|
||||||
|
if (ds->month == ds->end_time.local_year &&
|
||||||
|
ds->month == ds->end_time.local_month &&
|
||||||
|
ds->day > ds->end_time.local_day) {
|
||||||
|
printf("Clamp Day High!\n");
|
||||||
|
ds->day = ds->end_time.local_day;
|
||||||
|
}
|
||||||
|
if (ds->year < ds->start_time.local_year) {
|
||||||
|
ds->year = ds->start_time.local_year;
|
||||||
|
}
|
||||||
|
if (ds->year == ds->start_time.local_year &&
|
||||||
|
ds->month < ds->start_time.local_month) {
|
||||||
|
ds->month = ds->start_time.local_month;
|
||||||
|
}
|
||||||
|
if (ds->year == ds->start_time.local_year &&
|
||||||
|
ds->month == ds->start_time.local_month &&
|
||||||
|
ds->day < ds->start_time.local_day) {
|
||||||
|
printf("Clamp Day Low!\n");
|
||||||
|
ds->day = ds->start_time.local_day;
|
||||||
|
}
|
||||||
|
printf("Max: %d %d %d\n", ds->end_time.local_year, ds->end_time.local_month, ds->end_time.local_day);
|
||||||
|
printf("Min: %d %d %d\n", ds->start_time.local_year, ds->start_time.local_month, ds->start_time.local_day);
|
||||||
|
printf("Cur: %d %d %d\n", ds->year, ds->month, ds->day);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void date_sel_cleanup(DateSelection *ds) {
|
||||||
|
if (ds->month < 1) {
|
||||||
|
ds->month = 12 - ds->month;
|
||||||
|
--ds->year;
|
||||||
|
} else if (ds->month > 12) {
|
||||||
|
ds->month = (ds->month % 13) + 1;
|
||||||
|
++ds->year;
|
||||||
|
}
|
||||||
|
int ndays = days_in_month(ds->month, ds->year);
|
||||||
|
if (ds->day == 33) {
|
||||||
|
ds->day = ndays;
|
||||||
|
} else if (ds->day < 1) {
|
||||||
|
ds->day = 33; // this means last day of month
|
||||||
|
--ds->month;
|
||||||
|
date_sel_cleanup(ds);
|
||||||
|
} else if (ds->day > ndays) {
|
||||||
|
ds->day = 1;
|
||||||
|
++ds->month;
|
||||||
|
date_sel_cleanup(ds);
|
||||||
|
}
|
||||||
|
date_sel_clamp_time(ds);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void date_sel_add_units(DateSelection *ds, int n) {
|
||||||
|
switch (ds->stage) {
|
||||||
|
case DATE_SEL_YEAR:
|
||||||
|
ds->year += n;
|
||||||
|
break;
|
||||||
|
case DATE_SEL_MONTH:
|
||||||
|
ds->month += n;
|
||||||
|
break;
|
||||||
|
case DATE_SEL_DAY:
|
||||||
|
ds->day += n;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
date_sel_cleanup(ds);
|
||||||
|
}
|
||||||
|
|
||||||
|
DateSelectionState date_sel_dispatch(DateSelection *ds, SensorState *state) {
|
||||||
|
if (ds->year == -1) {
|
||||||
|
time_t utc_time = time(NULL);
|
||||||
|
struct tm local_time;
|
||||||
|
localtime_r(&utc_time, &local_time);
|
||||||
|
ds->year = local_time.tm_year + 1900;
|
||||||
|
if (ds->max_stage > DATE_SEL_YEAR) {
|
||||||
|
ds->month = local_time.tm_mon + 1;
|
||||||
|
}
|
||||||
|
if (ds->max_stage > DATE_SEL_MONTH) {
|
||||||
|
ds->day = local_time.tm_mday;
|
||||||
|
}
|
||||||
|
date_sel_cleanup(ds);
|
||||||
|
}
|
||||||
|
if (ds->max_stage < DATE_SEL_MONTH) {
|
||||||
|
ds->month = 1;
|
||||||
|
}
|
||||||
|
if (ds->max_stage < DATE_SEL_DAY) {
|
||||||
|
ds->day = 1;
|
||||||
|
}
|
||||||
|
if (state->back_down) {
|
||||||
|
if (ds->stage == DATE_SEL_YEAR) {
|
||||||
|
return DATE_SEL_BACK;
|
||||||
|
}
|
||||||
|
--ds->stage;
|
||||||
|
}
|
||||||
|
if (state->up_down || state->down_down) {
|
||||||
|
date_sel_add_units(ds, state->up_down - state->down_down);
|
||||||
|
}
|
||||||
|
if (state->sel_down) {
|
||||||
|
if (ds->stage == ds->max_stage) {
|
||||||
|
return DATE_SEL_DONE;
|
||||||
|
}
|
||||||
|
++ds->stage;
|
||||||
|
}
|
||||||
|
int buf_size = 17 - ds->start_col;
|
||||||
|
char buff[buf_size];
|
||||||
|
int cursor_pos;
|
||||||
|
const char *format;
|
||||||
|
switch (ds->stage) {
|
||||||
|
case DATE_SEL_YEAR:
|
||||||
|
cursor_pos = 0;
|
||||||
|
format = ">%04d/%02d/%02d";
|
||||||
|
break;
|
||||||
|
case DATE_SEL_MONTH:
|
||||||
|
cursor_pos = 5;
|
||||||
|
format = "%04d/>%02d/%02d";
|
||||||
|
break;
|
||||||
|
case DATE_SEL_DAY:
|
||||||
|
cursor_pos = 8;
|
||||||
|
format = "%04d/%02d/>%02d";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOG_VERBOSE("Date selector tried to select bad field\n");
|
||||||
|
return DATE_SEL_BACK;
|
||||||
|
}
|
||||||
|
snprintf(buff, buf_size, format, ds->year, ds->month, ds->day);
|
||||||
|
cursor_pos += ds->start_col;
|
||||||
|
lcd_move_to(state->lcd, ds->start_line, ds->start_col);
|
||||||
|
lcd_write_string(state->lcd, buff);
|
||||||
|
lcd_display_control(state->lcd, LCD_CURSOR_BLINK, LCD_CURSOR_ON,
|
||||||
|
LCD_DISPLAY_ON);
|
||||||
|
lcd_move_to(state->lcd, ds->start_line, cursor_pos);
|
||||||
|
return DATE_SEL_CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void stats_by_select_start(StatsByScreen *screen, SensorState *state) {
|
||||||
|
if (state->up_down || state->down_down || state->sel_down || state->back_down) {
|
||||||
|
screen->need_redraw = true;
|
||||||
|
UtilDate start, end;
|
||||||
|
if (!get_database_limits(state->db, PERIOD_LABELS[screen->period],
|
||||||
|
&start, &end)) {
|
||||||
|
warnx("failed to query database limits");
|
||||||
|
--screen->stage;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
screen->ds.start_time = start;
|
||||||
|
screen->ds.end_time = end;
|
||||||
|
}
|
||||||
|
if (screen->need_redraw) {
|
||||||
|
lcd_clear(state->lcd);
|
||||||
|
lcd_move_to(state->lcd, 0, 0);
|
||||||
|
lcd_write_string(state->lcd, "Start: ");
|
||||||
|
lcd_move_to(state->lcd, 1, 0);
|
||||||
|
screen->need_redraw = false;
|
||||||
|
DateSelectionState dss = date_sel_dispatch(&screen->ds, state);
|
||||||
|
switch (dss) {
|
||||||
|
case DATE_SEL_BACK:
|
||||||
|
screen->need_redraw = true;
|
||||||
|
lcd_display_control(state->lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF,
|
||||||
|
LCD_DISPLAY_ON);
|
||||||
|
--screen->stage;
|
||||||
|
return;
|
||||||
|
case DATE_SEL_CONTINUE:
|
||||||
|
break; // ignore
|
||||||
|
case DATE_SEL_DONE:
|
||||||
|
++screen->stage;
|
||||||
|
screen->need_redraw = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool stats_by_screen_dispatch(StatsByScreen *screen,
|
||||||
|
SensorState *state) {
|
||||||
|
if (state->force_draw) {
|
||||||
|
screen->need_redraw = true;
|
||||||
|
}
|
||||||
|
if (state->back_down) {
|
||||||
|
if (screen->stage == STATS_BY_SELECT_PERIOD) {
|
||||||
|
screen->need_redraw = true;
|
||||||
|
lcd_display_control(state->lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF,
|
||||||
|
LCD_DISPLAY_ON);
|
||||||
|
date_sel_reset(&screen->ds);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch (screen->stage) {
|
||||||
|
case STATS_BY_SELECT_PERIOD:
|
||||||
|
stats_by_select_period(screen, state);
|
||||||
|
break;
|
||||||
|
case STATS_BY_SELECT_START:
|
||||||
|
stats_by_select_start(screen, state);
|
||||||
|
break;
|
||||||
|
case STATS_BY_SHOWING:
|
||||||
|
if (state->back_down) {
|
||||||
|
screen->stage = STATS_BY_SELECT_PERIOD;
|
||||||
|
screen->need_redraw = true;
|
||||||
|
screen->ds.stage = DATE_SEL_YEAR;
|
||||||
|
} else if (screen->need_redraw) {
|
||||||
|
lcd_clear(state->lcd);
|
||||||
|
lcd_move_to(state->lcd, 0, 0);
|
||||||
|
lcd_write_string(state->lcd, "Some data!");
|
||||||
|
screen->need_redraw = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOG_VERBOSE("Attempt to show bad stats by screen\n");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
StatsByScreen *stats_by_screen_new() {
|
||||||
|
StatsByScreen *s = malloc_checked(sizeof(StatsByScreen));
|
||||||
|
screen_init(&s->parent, "Stats by...",
|
||||||
|
(ScreenDispatchFunc) stats_by_screen_dispatch,
|
||||||
|
(ScreenCleanupFunc) free);
|
||||||
|
s->need_redraw = true;
|
||||||
|
date_sel_init(&s->ds, 1, 0, DATE_SEL_DAY);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
74
src/screen.h
74
src/screen.h
@ -12,11 +12,14 @@
|
|||||||
|
|
||||||
#include "button.h"
|
#include "button.h"
|
||||||
#include "lcd.h"
|
#include "lcd.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <libgpio.h>
|
#include <libgpio.h>
|
||||||
|
#include <sqlite3.h>
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
sqlite3 *db;
|
||||||
LCD *lcd;
|
LCD *lcd;
|
||||||
uint32_t temp;
|
uint32_t temp;
|
||||||
uint32_t humid;
|
uint32_t humid;
|
||||||
@ -52,6 +55,7 @@ void screen_init(Screen *screen, const char *name,
|
|||||||
void screen_delete(Screen *screen);
|
void screen_delete(Screen *screen);
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
sqlite3 *db;
|
||||||
LCD *lcd;
|
LCD *lcd;
|
||||||
Button *back_btn;
|
Button *back_btn;
|
||||||
Button *up_btn;
|
Button *up_btn;
|
||||||
@ -68,10 +72,11 @@ typedef struct {
|
|||||||
} ScreenManager;
|
} ScreenManager;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Crate a new ScreenManager with the given LCD and buttons
|
* Crate a new ScreenManager with the given DB, LCD and buttons. DB and LCD
|
||||||
|
* still owned by the caller!
|
||||||
* Return: the new ScreenManager
|
* Return: the new ScreenManager
|
||||||
*/
|
*/
|
||||||
ScreenManager *screen_manager_new(LCD *lcd, gpio_handle_t handle,
|
ScreenManager *screen_manager_new(sqlite3 *db, LCD *lcd, gpio_handle_t handle,
|
||||||
gpio_pin_t back_pin, gpio_pin_t up_pin,
|
gpio_pin_t back_pin, gpio_pin_t up_pin,
|
||||||
gpio_pin_t down_pin, gpio_pin_t sel_pin);
|
gpio_pin_t down_pin, gpio_pin_t sel_pin);
|
||||||
|
|
||||||
@ -99,6 +104,71 @@ typedef struct {
|
|||||||
time_t last_min;
|
time_t last_min;
|
||||||
} StatsScreen;
|
} StatsScreen;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create a new stats screen. This screen will display the current temp. and
|
||||||
|
* humidity data from a THS sensor.
|
||||||
|
*/
|
||||||
StatsScreen *stats_screen_new(void);
|
StatsScreen *stats_screen_new(void);
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
DATE_SEL_YEAR,
|
||||||
|
DATE_SEL_MONTH,
|
||||||
|
DATE_SEL_DAY,
|
||||||
|
} DateSelectionStage;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int year;
|
||||||
|
int month;
|
||||||
|
int day;
|
||||||
|
DateSelectionStage stage;
|
||||||
|
DateSelectionStage max_stage;
|
||||||
|
int start_line;
|
||||||
|
int start_col;
|
||||||
|
UtilDate start_time;
|
||||||
|
UtilDate end_time;
|
||||||
|
} DateSelection;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
DATE_SEL_BACK,
|
||||||
|
DATE_SEL_CONTINUE,
|
||||||
|
DATE_SEL_DONE
|
||||||
|
} DateSelectionState;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Initialize a DateSelection. START_LINE and START_COL are the first line and
|
||||||
|
* column where it should draw the widget. MAX_STAGE is the most specific field
|
||||||
|
* to select.
|
||||||
|
*/
|
||||||
|
void date_sel_init(DateSelection *ds, int start_line, int start_col,
|
||||||
|
DateSelectionStage max_stage);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Reset the date on DS.
|
||||||
|
*/
|
||||||
|
void date_sel_reset(DateSelection *ds);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Dispatch a DateSelection by processing STATE to continue the selection.
|
||||||
|
* Return: weather the user backed out, is still working, or is done
|
||||||
|
*/
|
||||||
|
DateSelectionState date_sel_dispatch(DateSelection *ds, SensorState *state);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
Screen parent;
|
||||||
|
enum {
|
||||||
|
STATS_BY_SELECT_PERIOD = 0,
|
||||||
|
STATS_BY_SELECT_START,
|
||||||
|
STATS_BY_SHOWING,
|
||||||
|
} stage;
|
||||||
|
bool need_redraw;
|
||||||
|
int period;
|
||||||
|
DateSelection ds;
|
||||||
|
} StatsByScreen;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create a new stats by screen. This screen will display data from the database
|
||||||
|
* by hour, day, week, month, and year.
|
||||||
|
*/
|
||||||
|
StatsByScreen *stats_by_screen_new(void);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
100
src/util.c
100
src/util.c
@ -45,3 +45,103 @@ void *strdup_checked(const char *str) {
|
|||||||
}
|
}
|
||||||
return new_str;
|
return new_str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int days_in_month(int m, int y) {
|
||||||
|
switch (m) {
|
||||||
|
case 2:
|
||||||
|
if (y % 4 == 0 && (y % 100 != 0 || y % 400 == 0)) {
|
||||||
|
return 29;
|
||||||
|
} else {
|
||||||
|
return 28;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
case 3:
|
||||||
|
case 5:
|
||||||
|
case 7:
|
||||||
|
case 8:
|
||||||
|
case 10:
|
||||||
|
case 12:
|
||||||
|
return 31;
|
||||||
|
default:
|
||||||
|
return 30;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// is this even a C program anymore?
|
||||||
|
static const char *DB_LIMITS_QUERY_STR =
|
||||||
|
"SELECT\n"
|
||||||
|
"MinUTC,\n"
|
||||||
|
// labels below are for debugging
|
||||||
|
"strftime('%Y', MinUTC, 'unixepoch') as MinUTCYear,\n"
|
||||||
|
"strftime('%m', MinUTC, 'unixepoch') as MinUTCMonth,\n"
|
||||||
|
"strftime('%e', MinUTC, 'unixepoch') as MinUTCDay,\n"
|
||||||
|
"MinLocal,\n"
|
||||||
|
"strftime('%Y', MinLocal, 'unixepoch') as MinLocalYear,\n"
|
||||||
|
"strftime('%m', MinLocal, 'unixepoch') as MinLocalMonth,\n"
|
||||||
|
"strftime('%e', MinLocal, 'unixepoch') as MinLocalDay,\n"
|
||||||
|
"MaxUTC,\n"
|
||||||
|
"strftime('%Y', MaxUTC, 'unixepoch') as MaxUTCYear,\n"
|
||||||
|
"strftime('%m', MaxUTC, 'unixepoch') as MaxUTCMonth,\n"
|
||||||
|
"strftime('%e', MaxUTC, 'unixepoch') as MaxUTCDay,\n"
|
||||||
|
"MaxLocal,\n"
|
||||||
|
"strftime('%Y', MaxLocal, 'unixepoch') as MaxLocalYear,\n"
|
||||||
|
"strftime('%m', MaxLocal, 'unixepoch') as MaxLocalMonth,\n"
|
||||||
|
"strftime('%e', MaxLocal, 'unixepoch') as MaxLocalDay\n"
|
||||||
|
"FROM\n"
|
||||||
|
"(SELECT\n"
|
||||||
|
"unixepoch(min(time), 'unixepoch', 'start of ' || ?1) as MinUTC,\n"
|
||||||
|
"unixepoch(max(time), 'unixepoch', '+1 ' || ?1, 'start of ' || ?1, '-1 second') as MaxUTC,\n"
|
||||||
|
"unixepoch(min(time), 'unixepoch', 'localtime', 'start of ' || ?1) as MinLocal,\n"
|
||||||
|
"unixepoch(max(time), 'unixepoch', 'localtime', '+1 ' || ?1, 'start of ' || ?1, '-1 second') as MaxLocal\n"
|
||||||
|
"FROM env_data);";
|
||||||
|
static sqlite3_stmt *DB_LIMITS_QUERY;
|
||||||
|
void initialize_util_queries(sqlite3 *db) {
|
||||||
|
int status = sqlite3_prepare_v2(db, DB_LIMITS_QUERY_STR, -1,
|
||||||
|
&DB_LIMITS_QUERY, NULL);
|
||||||
|
if (status != SQLITE_OK) {
|
||||||
|
errx(1, "failed to compile limits query: %s", sqlite3_errstr(status));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void cleanup_util_queries() {
|
||||||
|
sqlite3_finalize(DB_LIMITS_QUERY);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get_database_limits(sqlite3 *db, const char *period, UtilDate *start,
|
||||||
|
UtilDate *end) {
|
||||||
|
if (strcasecmp(period, "week") == 0 || strcasecmp(period, "hour") == 0) {
|
||||||
|
period = "day";
|
||||||
|
}
|
||||||
|
bool success = true;
|
||||||
|
sqlite3_bind_text(DB_LIMITS_QUERY, 1, period, -1, SQLITE_TRANSIENT);
|
||||||
|
int status = sqlite3_step(DB_LIMITS_QUERY);
|
||||||
|
if (status == SQLITE_ROW) {
|
||||||
|
if (start) {
|
||||||
|
start->utc = sqlite3_column_int64(DB_LIMITS_QUERY, 0);
|
||||||
|
start->utc_year = sqlite3_column_int64(DB_LIMITS_QUERY, 1);
|
||||||
|
start->utc_month = sqlite3_column_int64(DB_LIMITS_QUERY, 2);
|
||||||
|
start->utc_day = sqlite3_column_int64(DB_LIMITS_QUERY, 3);
|
||||||
|
start->local = sqlite3_column_int64(DB_LIMITS_QUERY, 4);
|
||||||
|
start->local_year = sqlite3_column_int64(DB_LIMITS_QUERY, 5);
|
||||||
|
start->local_month = sqlite3_column_int64(DB_LIMITS_QUERY, 6);
|
||||||
|
start->local_day = sqlite3_column_int64(DB_LIMITS_QUERY, 7);
|
||||||
|
}
|
||||||
|
if (end) {
|
||||||
|
end->utc = sqlite3_column_int64(DB_LIMITS_QUERY, 8);
|
||||||
|
end->utc_year = sqlite3_column_int64(DB_LIMITS_QUERY, 9);
|
||||||
|
end->utc_month = sqlite3_column_int64(DB_LIMITS_QUERY, 10);
|
||||||
|
end->utc_day = sqlite3_column_int64(DB_LIMITS_QUERY, 11);
|
||||||
|
end->local = sqlite3_column_int64(DB_LIMITS_QUERY, 12);
|
||||||
|
end->local_year = sqlite3_column_int64(DB_LIMITS_QUERY, 13);
|
||||||
|
end->local_month = sqlite3_column_int64(DB_LIMITS_QUERY, 14);
|
||||||
|
end->local_day = sqlite3_column_int64(DB_LIMITS_QUERY, 15);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
// unbind the string so it can be freed by the caller
|
||||||
|
sqlite3_bind_null(DB_LIMITS_QUERY, 1);
|
||||||
|
sqlite3_reset(DB_LIMITS_QUERY);
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
34
src/util.h
34
src/util.h
@ -17,6 +17,7 @@
|
|||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <libgpio.h>
|
#include <libgpio.h>
|
||||||
|
#include <sqlite3.h>
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char *config_path; // path to config file
|
char *config_path; // path to config file
|
||||||
@ -76,4 +77,37 @@ void *strdup_checked(const char *str);
|
|||||||
*/
|
*/
|
||||||
#define LOG_VERBOSE(...) if (GLOBAL_OPTS.verbose) {fprintf(stderr, __VA_ARGS__);}
|
#define LOG_VERBOSE(...) if (GLOBAL_OPTS.verbose) {fprintf(stderr, __VA_ARGS__);}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return: the number of days in month M. 1 is January. Y is the year.
|
||||||
|
*/
|
||||||
|
int days_in_month(int m, int y);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Initialize SQL queries used by this file.
|
||||||
|
*/
|
||||||
|
void initialize_util_queries(sqlite3 *db);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Cleanup SQL queries used by this file.
|
||||||
|
*/
|
||||||
|
void cleanup_util_queries(void);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int64_t utc;
|
||||||
|
int utc_year;
|
||||||
|
int utc_month;
|
||||||
|
int utc_day;
|
||||||
|
int64_t local;
|
||||||
|
int local_year;
|
||||||
|
int local_month;
|
||||||
|
int local_day;
|
||||||
|
} UtilDate;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return the START of the first and END of the last PERIOD (ex. week) of DB.
|
||||||
|
* Return: false if an error occurred, true otherwise.
|
||||||
|
*/
|
||||||
|
bool get_database_limits(sqlite3 *db, const char *period, UtilDate *start,
|
||||||
|
UtilDate *end);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
Loading…
Reference in New Issue
Block a user