Add stats-by menu

This commit is contained in:
Alexander Rosenberg 2024-04-01 21:14:47 -07:00
parent 375f8494f4
commit be0040bb2f
Signed by: school-rpi4
GPG Key ID: 5CCFC80B0B47B04B
11 changed files with 546 additions and 70 deletions

View File

@ -4,9 +4,9 @@
CC=clang CC=clang
CFLAGS=-g -std=c11 -Wall ${SQLITE3_CFLAGS} CFLAGS=-g -std=c11 -Wall ${SQLITE3_CFLAGS}
LD=clang LD=clang
LDFLAGS=-lgpio -lfigpar -lgeom -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/ui/screen.c\ 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/datesel.c src/ui/statsby.c src/ui/datapoints.c src/ui/timesel.c
PROG=rpi4b-temp-humidity PROG=rpi4b-temp-humidity
OBJS=${SRCS:C/^src/bin/:C/.c$/.o/} OBJS=${SRCS:C/^src/bin/:C/.c$/.o/}
@ -15,15 +15,24 @@ bin/${PROG}: ${OBJS}
bin/main.o bin/util.o bin/lcd.o bin/ths.o bin/button.o: src/util.h 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: 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: src/lcd.h bin/ui/statsby.o bin/ui/timesel.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/main.o bin/ui/datesel.o bin/ui/statsby.o: src/ui/datesel.h
bin/ui/datapoints.o bin/ui/timesel.o: src/ui/datesel.h
bin/main.o bin/ths.o bin/ui/screen.o bin/ui/statsby.o: src/ths.h bin/main.o bin/ths.o bin/ui/screen.o bin/ui/statsby.o: src/ths.h
bin/ui/timesel.o: src/ths.h
bin/main.o bin/ui/timesel.o bin/ui/datapoints.o: src/ui/timesel.h
bin/main.o bin/button.o bin/ui/screen.o: src/button.h bin/main.o bin/button.o bin/ui/screen.o: src/button.h
bin/main.o bin/ui/screen.o bin/ui/statsby.o bin/ui/datesel.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/statsby.o: src/ui/statsby.h bin/main.o bin/ui/statsby.o: src/ui/statsby.h
bin/main.o bin/ui/datapoints.o: src/ui/datapoints.h
${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

@ -14,6 +14,7 @@
#include "menu.h" #include "menu.h"
#include "ui/screen.h" #include "ui/screen.h"
#include "ui/statsby.h" #include "ui/statsby.h"
#include "ui/datapoints.h"
#include <unistd.h> #include <unistd.h>
#include <err.h> #include <err.h>
@ -103,6 +104,7 @@ int main(int argc, char *const *argv) {
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()); screen_manager_add(screen_manager, (Screen *) stats_by_screen_new());
screen_manager_add(screen_manager, (Screen *) data_points_screen_new());
while (RUNNING) { while (RUNNING) {
lock_stat_globals(); lock_stat_globals();
uint32_t temp = LAST_TEMP; uint32_t temp = LAST_TEMP;

164
src/ui/datapoints.c Normal file
View File

@ -0,0 +1,164 @@
/*
* datapoints.c - Screen for viewing individual data points
* 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 "datapoints.h"
#include "../ths.h"
#include <inttypes.h>
#include <time.h>
#include <err.h>
static bool data_points_select_date(DataPointsScreen *screen,
SensorState *state) {
if (state->up_down || state->down_down || state->sel_down || state->back_down) {
screen->need_redraw = true;
}
if (screen->need_redraw) {
screen->need_redraw = false;
DateSelectionState dss = date_sel_dispatch(&screen->ds, state,
PERIOD_DAY, "Date:");
switch (dss) {
case DATE_SEL_CONTINUE:
break; // ignore
case DATE_SEL_DONE:
++screen->stage;
screen->need_redraw = true;
break;
case DATE_SEL_BACK:
case DATE_SEL_ERROR:
screen->need_redraw = true;
return true;
}
}
return false;
}
static void data_points_calculate_initial_time(DataPointsScreen *screen) {
struct tm broken_time = {
.tm_sec = 0,
.tm_min = screen->ts.minute,
.tm_hour = screen->ts.hour,
.tm_mday = screen->ds.day,
.tm_mon = screen->ds.month - 1,
.tm_year = screen->ds.year - 1900,
.tm_isdst = -1,
};
time_t it = mktime(&broken_time);
if (it == -1) {
warnx("could not convert selected date and time");
screen->cur_point = 0;
} else {
screen->cur_point = it;
}
}
static void data_points_select_time(DataPointsScreen *screen,
SensorState *state) {
if (state->up_down || state->down_down || state->sel_down || state->back_down) {
screen->need_redraw = true;
}
if (screen->need_redraw) {
screen->need_redraw = false;
TimeSelState tss = time_sel_dispatch(&screen->ts, state);
switch (tss) {
case TIME_SEL_CONTINUE:
break; // ignore
case TIME_SEL_DONE:
++screen->stage;
screen->need_redraw = true;
data_points_calculate_initial_time(screen);
break;
case TIME_SEL_BACK:
case TIME_SEL_ERROR:
screen->need_redraw = true;
--screen->stage;
}
}
}
static void data_points_show(DataPointsScreen *screen, SensorState *state) {
if (state->back_down) {
screen->stage = DP_STAGE_DATE;
screen->need_redraw = true;
return;
}
if (state->up_down || state->down_down) {
screen->need_redraw = true;
}
if (screen->need_redraw) {
screen->need_redraw = false;
UtilDataPointInfo info;
if (!get_data_point_info(state->db, screen->cur_point, &info)) {
warnx("could not get info for data point: %" PRIi64 "", screen->cur_point);
screen->stage = DP_STAGE_DATE;
screen->need_redraw = true;
return;
}
screen->cur_point = info.time;
int temp = info.temp;
int humid = info.humid;
if (state->up_down && info.has_next) {
screen->cur_point = info.next_time;
temp = info.next_temp;
humid = info.next_humid;
} else if (state->down_down && info.has_prev) {
screen->cur_point = info.prev_time;
temp = info.prev_temp;
humid = info.prev_humid;
}
struct tm broken_time;
localtime_r(&screen->cur_point, &broken_time);
char str_time[17];
if (!strftime(str_time, 17, "%y/%m/%d %H:%M", &broken_time)) {
warnx("could not format time: %" PRIi64 "", screen->cur_point);
screen->stage = DP_STAGE_DATE;
screen->need_redraw = true;
return;
}
lcd_clear(state->lcd);
lcd_move_to(state->lcd, 0, 0);
lcd_write_string(state->lcd, str_time);
char data_line[17];
snprintf(data_line, 17, "%02ds %.1fF %d%%", broken_time.tm_sec,
DK_TO_F(temp), humid);
lcd_move_to(state->lcd, 1, 0);
lcd_write_string(state->lcd, data_line);
}
}
static bool data_points_screen_dispatch(DataPointsScreen *screen, SensorState *state) {
if (state->force_draw) {
screen->need_redraw = true;
}
switch (screen->stage) {
case DP_STAGE_DATE:
return data_points_select_date(screen, state);
case DP_STAGE_TIME:
data_points_select_time(screen, state);
return false;
case DP_STAGE_SHOW:
data_points_show(screen, state);
return false;
default:
warnx("Attempt to show bad data points screen stage");
return true;
}
}
DataPointsScreen *data_points_screen_new() {
DataPointsScreen *s = malloc_checked(sizeof(DataPointsScreen));
screen_init(&s->parent, "Data Points",
(ScreenDispatchFunc) data_points_screen_dispatch,
(ScreenCleanupFunc) free);
s->need_redraw = true;
s->stage = DP_STAGE_DATE;
date_sel_init(&s->ds, DATE_SEL_DAY);
time_sel_init(&s->ts, &s->ds);
return s;
}

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

@ -0,0 +1,35 @@
/*
* datapoints.h - Screen for viewing individual data points
* 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_DATAPOINTS_H
#define INCLUDED_DATAPOINTS_H
#include "datesel.h"
#include "timesel.h"
typedef struct {
Screen parent;
bool need_redraw;
enum {
DP_STAGE_DATE = 0,
DP_STAGE_TIME,
DP_STAGE_SHOW,
} stage;
DateSelection ds;
TimeSelection ts;
int64_t cur_point;
} DataPointsScreen;
/*
* Create a new data points screen. This screen will display individual
* temperature and humidity data points.
*/
DataPointsScreen *data_points_screen_new(void);
#endif

View File

@ -1,5 +1,5 @@
/* /*
* datesel.h - Date selector * datesel.c - Date selector
* Copyright (C) 2024 Alexander Rosenberg * Copyright (C) 2024 Alexander Rosenberg
* *
* This program is free software: you can redistribute it and/or modify it under * This program is free software: you can redistribute it and/or modify it under
@ -10,12 +10,10 @@
#include "datesel.h" #include "datesel.h"
#include <string.h> #include <string.h>
#include <err.h>
void date_sel_init(DateSelection *ds, int start_line, int start_col, void date_sel_init(DateSelection *ds, DateSelectionStage max_stage) {
DateSelectionStage max_stage) {
ds->max_stage = 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->start_time, 0, sizeof(UtilDate)); // min date
memset(&ds->end_time, 0xff, sizeof(UtilDate)); // max date memset(&ds->end_time, 0xff, sizeof(UtilDate)); // max date
date_sel_reset(ds); date_sel_reset(ds);
@ -99,7 +97,23 @@ static void date_sel_add_units(DateSelection *ds, int n) {
date_sel_cleanup(ds); date_sel_cleanup(ds);
} }
DateSelectionState date_sel_dispatch(DateSelection *ds, SensorState *state) { DateSelectionState date_sel_dispatch(DateSelection *ds,
SensorState *state,
UtilPeriod limit_period,
const char *label) {
UtilDate start, end;
if (!get_database_limits(state->db, limit_period, &start, &end)) {
warnx("failed to query database limits");
lcd_display_control(state->lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF,
LCD_DISPLAY_ON);
return DATE_SEL_ERROR;
}
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);
lcd_move_to(state->lcd, 1, 0);
if (ds->year == -1) { if (ds->year == -1) {
time_t utc_time = time(NULL); time_t utc_time = time(NULL);
struct tm local_time; struct tm local_time;
@ -121,6 +135,8 @@ DateSelectionState date_sel_dispatch(DateSelection *ds, SensorState *state) {
} }
if (state->back_down) { if (state->back_down) {
if (ds->stage == DATE_SEL_YEAR) { if (ds->stage == DATE_SEL_YEAR) {
lcd_display_control(state->lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF,
LCD_DISPLAY_ON);
return DATE_SEL_BACK; return DATE_SEL_BACK;
} }
--ds->stage; --ds->stage;
@ -130,11 +146,13 @@ DateSelectionState date_sel_dispatch(DateSelection *ds, SensorState *state) {
} }
if (state->sel_down) { if (state->sel_down) {
if (ds->stage == ds->max_stage) { if (ds->stage == ds->max_stage) {
lcd_display_control(state->lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF,
LCD_DISPLAY_ON);
return DATE_SEL_DONE; return DATE_SEL_DONE;
} }
++ds->stage; ++ds->stage;
} }
int buf_size = 17 - ds->start_col; int buf_size = 17;
char buff[buf_size]; char buff[buf_size];
int cursor_pos; int cursor_pos;
const char *format; const char *format;
@ -153,14 +171,15 @@ DateSelectionState date_sel_dispatch(DateSelection *ds, SensorState *state) {
break; break;
default: default:
LOG_VERBOSE("Date selector tried to select bad field\n"); LOG_VERBOSE("Date selector tried to select bad field\n");
lcd_display_control(state->lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF,
LCD_DISPLAY_ON);
return DATE_SEL_BACK; return DATE_SEL_BACK;
} }
snprintf(buff, buf_size, format, ds->year, ds->month, ds->day); snprintf(buff, buf_size, format, ds->year, ds->month, ds->day);
cursor_pos += ds->start_col; lcd_move_to(state->lcd, 1, 0);
lcd_move_to(state->lcd, ds->start_line, ds->start_col);
lcd_write_string(state->lcd, buff); lcd_write_string(state->lcd, buff);
lcd_display_control(state->lcd, LCD_CURSOR_BLINK, LCD_CURSOR_ON, lcd_display_control(state->lcd, LCD_CURSOR_BLINK, LCD_CURSOR_ON,
LCD_DISPLAY_ON); LCD_DISPLAY_ON);
lcd_move_to(state->lcd, ds->start_line, cursor_pos); lcd_move_to(state->lcd, 1, cursor_pos);
return DATE_SEL_CONTINUE; return DATE_SEL_CONTINUE;
} }

View File

@ -25,8 +25,6 @@ typedef struct {
int day; int day;
DateSelectionStage stage; DateSelectionStage stage;
DateSelectionStage max_stage; DateSelectionStage max_stage;
int start_line;
int start_col;
UtilDate start_time; UtilDate start_time;
UtilDate end_time; UtilDate end_time;
} DateSelection; } DateSelection;
@ -34,16 +32,15 @@ typedef struct {
typedef enum { typedef enum {
DATE_SEL_BACK, DATE_SEL_BACK,
DATE_SEL_CONTINUE, DATE_SEL_CONTINUE,
DATE_SEL_DONE DATE_SEL_DONE,
DATE_SEL_ERROR
} DateSelectionState; } DateSelectionState;
/* /*
* Initialize a DateSelection. START_LINE and START_COL are the first line and * Initialize a DateSelection. MAX_STAGE is the most specific field
* column where it should draw the widget. MAX_STAGE is the most specific field
* to select. * to select.
*/ */
void date_sel_init(DateSelection *ds, int start_line, int start_col, void date_sel_init(DateSelection *ds, DateSelectionStage max_stage);
DateSelectionStage max_stage);
/* /*
* Reset the date on DS. * Reset the date on DS.
@ -54,6 +51,9 @@ void date_sel_reset(DateSelection *ds);
* Dispatch a DateSelection by processing STATE to continue the selection. * Dispatch a DateSelection by processing STATE to continue the selection.
* Return: weather the user backed out, is still working, or is done * Return: weather the user backed out, is still working, or is done
*/ */
DateSelectionState date_sel_dispatch(DateSelection *ds, SensorState *state); DateSelectionState date_sel_dispatch(DateSelection *ds,
SensorState *state,
UtilPeriod limit_period,
const char *label);
#endif #endif

View File

@ -1,5 +1,5 @@
/* /*
* statsby.h - Screen for view stats by a specified period (day, week, etc.) * statsby.c - Screen for view stats by a specified period (day, week, etc.)
* Copyright (C) 2024 Alexander Rosenberg * Copyright (C) 2024 Alexander Rosenberg
* *
* This program is free software: you can redistribute it and/or modify it under * This program is free software: you can redistribute it and/or modify it under
@ -59,36 +59,24 @@ static void stats_by_select_start(StatsByScreen *screen, SensorState *state) {
screen->need_redraw = true; screen->need_redraw = true;
} }
if (screen->need_redraw) { if (screen->need_redraw) {
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;
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; screen->need_redraw = false;
DateSelectionState dss = date_sel_dispatch(&screen->ds, state); DateSelectionState dss = date_sel_dispatch(&screen->ds, state,
screen->period, "Start:");
switch (dss) { 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: case DATE_SEL_CONTINUE:
break; // ignore break; // ignore
case DATE_SEL_DONE: case DATE_SEL_DONE:
++screen->stage; ++screen->stage;
screen->offset_scale = 0; screen->offset_scale = 0;
screen->need_redraw = true; screen->need_redraw = true;
screen->blimit_reached = false;
screen->ulimit_reached = false; screen->ulimit_reached = false;
break; break;
case DATE_SEL_BACK:
case DATE_SEL_ERROR:
--screen->stage;
screen->need_redraw = true;
break;
} }
} }
} }
@ -200,6 +188,8 @@ StatsByScreen *stats_by_screen_new() {
(ScreenDispatchFunc) stats_by_screen_dispatch, (ScreenDispatchFunc) stats_by_screen_dispatch,
(ScreenCleanupFunc) free); (ScreenCleanupFunc) free);
s->need_redraw = true; s->need_redraw = true;
date_sel_init(&s->ds, 1, 0, DATE_SEL_DAY); s->stage = STATS_BY_SELECT_PERIOD;
s->period = PERIOD_HOUR;
date_sel_init(&s->ds, DATE_SEL_DAY);
return s; return s;
} }

118
src/ui/timesel.c Normal file
View File

@ -0,0 +1,118 @@
/*
* timesel.c - Time selector
* 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 "timesel.h"
#include <err.h>
void time_sel_init(TimeSelection *ts, DateSelection *ds) {
ts->hour = 0;
ts->minute = 0;
ts->hour_set = false;
ts->ds = ds;
}
static void time_sel_clamp(TimeSelection *ts) {
if (ts->ds) {
if (ts->ds->end_time.local_year == ts->ds->year &&
ts->ds->end_time.local_month == ts->ds->month &&
ts->ds->end_time.local_day == ts->ds->day) {
if (ts->hour > ts->ds->end_time.local_hour) {
ts->hour = ts->ds->end_time.local_hour;
}
if (ts->hour == ts->ds->end_time.local_hour &&
ts->minute > ts->ds->end_time.local_minute) {
ts->minute = ts->ds->end_time.local_minute;
}
} else if (ts->ds->start_time.local_year == ts->ds->year &&
ts->ds->start_time.local_month == ts->ds->month &&
ts->ds->start_time.local_day == ts->ds->day) {
if (ts->hour < ts->ds->start_time.local_hour) {
ts->hour = ts->ds->start_time.local_hour;
}
if (ts->hour == ts->ds->start_time.local_hour &&
ts->minute < ts->ds->start_time.local_minute) {
ts->minute = ts->ds->start_time.local_minute;
}
}
}
}
static void time_sel_add(TimeSelection *ts, int amount) {
if (!ts->hour_set) {
ts->hour = (ts->hour + amount) % 24;
} else {
if (ts->minute + amount >= 60) {
ts->hour = (ts->hour + 1) % 24;
} else if (ts->minute + amount < 0) {
ts->hour = (ts->hour - 1) % 24;
}
ts->minute = (ts->minute + amount) % 60;
}
if (ts->hour < 0) {
ts->hour = 24 + ts->hour;
}
if (ts->minute < 0) {
ts->minute = 60 + ts->minute;
}
time_sel_clamp(ts);
}
TimeSelState time_sel_dispatch(TimeSelection *ts, SensorState *state) {
if (state->back_down) {
if (ts->hour_set) {
ts->hour_set = false;
} else {
lcd_display_control(state->lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF,
LCD_DISPLAY_ON);
return TIME_SEL_BACK;
}
}
if (state->sel_down) {
if (!ts->hour_set) {
ts->hour_set = true;
} else {
lcd_display_control(state->lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF,
LCD_DISPLAY_ON);
return TIME_SEL_DONE;
}
}
if (ts->ds) {
UtilDate start, end;
if (!get_database_limits(state->db, PERIOD_DAY, &start, &end)) {
warnx("failed to query database limits");
lcd_display_control(state->lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF,
LCD_DISPLAY_ON);
return TIME_SEL_ERROR;
}
ts->ds->start_time = start;
ts->ds->end_time = end;
}
time_sel_add(ts, state->up_down - state->down_down);
lcd_clear(state->lcd);
lcd_move_to(state->lcd, 0, 0);
lcd_write_string(state->lcd, "Time:");
const char *format;
int cursor_pos;
if (!ts->hour_set) {
format = ">%02d:%02d";
cursor_pos = 0;
} else {
format = "%02d:>%02d";
cursor_pos = 3;
}
char time_line[17];
snprintf(time_line, 17, format, ts->hour, ts->minute);
lcd_move_to(state->lcd, 1, 0);
lcd_write_string(state->lcd, time_line);
lcd_move_to(state->lcd, 1, cursor_pos);
lcd_display_control(state->lcd, LCD_CURSOR_BLINK, LCD_CURSOR_ON,
LCD_DISPLAY_ON);
return TIME_SEL_CONTINUE;
}

43
src/ui/timesel.h Normal file
View File

@ -0,0 +1,43 @@
/*
* timesel.h - Time selector
* 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_TIMESEL_H
#define INCLUDED_TIMESEL_H
#include "../util.h"
#include "screen.h"
#include "datesel.h"
typedef struct {
DateSelection *ds;
int hour;
bool hour_set;
int minute;
} TimeSelection;
typedef enum {
TIME_SEL_BACK,
TIME_SEL_CONTINUE,
TIME_SEL_DONE,
TIME_SEL_ERROR,
} TimeSelState;
/*
* Initialize the TimeSelection TS. Optionally, get the year, month, and day,
* and limits from DS.
*/
void time_sel_init(TimeSelection *ts, DateSelection *ds);
/*
* Dispatch (process input and draw) the TimeSelection TS according to STATE.
* Return: the current state of the selection (back, continue, done).
*/
TimeSelState time_sel_dispatch(TimeSelection *ts, SensorState *state);
#endif

View File

@ -11,6 +11,7 @@
#include <err.h> #include <err.h>
#include <string.h> #include <string.h>
#include <stdlib.h>
const char *PERIOD_LABELS[] = { const char *PERIOD_LABELS[] = {
"HOUR", "HOUR",
@ -86,21 +87,25 @@ static const char *DB_LIMITS_QUERY_STR =
"strftime('%m', MinUTC, 'unixepoch') as MinUTCMonth,\n" "strftime('%m', MinUTC, 'unixepoch') as MinUTCMonth,\n"
"strftime('%e', MinUTC, 'unixepoch') as MinUTCDay,\n" "strftime('%e', MinUTC, 'unixepoch') as MinUTCDay,\n"
"strftime('%H', RealMin, 'unixepoch') as MinUTCHour,\n" "strftime('%H', RealMin, 'unixepoch') as MinUTCHour,\n"
"strftime('%M', RealMin, 'unixepoch') as MinUTCMin,\n"
"MinLocal,\n" "MinLocal,\n"
"strftime('%Y', MinLocal, 'unixepoch') as MinLocalYear,\n" "strftime('%Y', MinLocal, 'unixepoch') as MinLocalYear,\n"
"strftime('%m', MinLocal, 'unixepoch') as MinLocalMonth,\n" "strftime('%m', MinLocal, 'unixepoch') as MinLocalMonth,\n"
"strftime('%e', MinLocal, 'unixepoch') as MinLocalDay,\n" "strftime('%e', MinLocal, 'unixepoch') as MinLocalDay,\n"
"strftime('%H', RealMin, 'unixepoch', 'localtime') as MinLocalHour,\n" "strftime('%H', RealMin, 'unixepoch', 'localtime') as MinLocalHour,\n"
"strftime('%M', RealMin, 'unixepoch', 'localtime') as MinLocalMin,\n"
"MaxUTC,\n" "MaxUTC,\n"
"strftime('%Y', MaxUTC, 'unixepoch') as MaxUTCYear,\n" "strftime('%Y', MaxUTC, 'unixepoch') as MaxUTCYear,\n"
"strftime('%m', MaxUTC, 'unixepoch') as MaxUTCMonth,\n" "strftime('%m', MaxUTC, 'unixepoch') as MaxUTCMonth,\n"
"strftime('%e', MaxUTC, 'unixepoch') as MaxUTCDay,\n" "strftime('%e', MaxUTC, 'unixepoch') as MaxUTCDay,\n"
"strftime('%H', RealMax, 'unixepoch') as MaxUTCHour,\n" "strftime('%H', RealMax, 'unixepoch') as MaxUTCHour,\n"
"strftime('%M', RealMax, 'unixepoch') as MaxUTCMin,\n"
"MaxLocal,\n" "MaxLocal,\n"
"strftime('%Y', MaxLocal, 'unixepoch') as MaxLocalYear,\n" "strftime('%Y', MaxLocal, 'unixepoch') as MaxLocalYear,\n"
"strftime('%m', MaxLocal, 'unixepoch') as MaxLocalMonth,\n" "strftime('%m', MaxLocal, 'unixepoch') as MaxLocalMonth,\n"
"strftime('%e', MaxLocal, 'unixepoch') as MaxLocalDay,\n" "strftime('%e', MaxLocal, 'unixepoch') as MaxLocalDay,\n"
"strftime('%H', RealMax, 'unixepoch', 'localtime') as MaxLocalHour\n" "strftime('%H', RealMax, 'unixepoch', 'localtime') as MaxLocalHour,\n"
"strftime('%M', RealMax, 'unixepoch', 'localtime') as MaxLocalMin\n"
"FROM\n" "FROM\n"
"(SELECT\n" "(SELECT\n"
"RealMax, RealMin,\n" "RealMax, RealMin,\n"
@ -110,7 +115,7 @@ static const char *DB_LIMITS_QUERY_STR =
"unixepoch(RealMax, 'unixepoch', 'localtime', '+1 ' || ?1, 'start of ' || ?1, '-1 second') as MaxLocal\n" "unixepoch(RealMax, 'unixepoch', 'localtime', '+1 ' || ?1, 'start of ' || ?1, '-1 second') as MaxLocal\n"
"FROM (SELECT max(time) as RealMax, min(time) as RealMin FROM env_data));"; "FROM (SELECT max(time) as RealMax, min(time) as RealMin FROM env_data));";
static sqlite3_stmt *DB_LIMITS_QUERY; static sqlite3_stmt *DB_LIMITS_QUERY;
const char *AVG_FOR_RANGE_QUERY_STR = static const char *AVG_FOR_RANGE_QUERY_STR =
"SELECT\n" "SELECT\n"
"strftime('%Y', stime, 'unixepoch', 'localtime') as y,\n" "strftime('%Y', stime, 'unixepoch', 'localtime') as y,\n"
"strftime('%m', stime, 'unixepoch', 'localtime') as m,\n" "strftime('%m', stime, 'unixepoch', 'localtime') as m,\n"
@ -131,6 +136,13 @@ const char *AVG_FOR_RANGE_QUERY_STR =
"WHERE time > stime\n" "WHERE time > stime\n"
"AND time < etime);\n"; "AND time < etime);\n";
static sqlite3_stmt *AVG_FOR_RANGE_QUERY; static sqlite3_stmt *AVG_FOR_RANGE_QUERY;
static const char *DATA_POINT_QUERY_STR =
"SELECT max(time) IS NULL, max(time), temp, humid FROM env_data WHERE time < ?1\n"
"UNION ALL\n"
"SELECT min(time) IS NULL, min(time), temp, humid FROM env_data WHERE time > ?1\n"
"UNION ALL\n"
"SELECT false, * FROM env_data WHERE time == ?1;";
static sqlite3_stmt *DATA_POINT_QUERY;
void initialize_util_queries(sqlite3 *db) { void initialize_util_queries(sqlite3 *db) {
int status = sqlite3_prepare_v2(db, DB_LIMITS_QUERY_STR, -1, int status = sqlite3_prepare_v2(db, DB_LIMITS_QUERY_STR, -1,
&DB_LIMITS_QUERY, NULL); &DB_LIMITS_QUERY, NULL);
@ -142,21 +154,27 @@ void initialize_util_queries(sqlite3 *db) {
if (status != SQLITE_OK) { if (status != SQLITE_OK) {
errx(1, "failed to compile range average query: %s", sqlite3_errstr(status)); errx(1, "failed to compile range average query: %s", sqlite3_errstr(status));
} }
status = sqlite3_prepare_v2(db, DATA_POINT_QUERY_STR, -1,
&DATA_POINT_QUERY, NULL);
if (status != SQLITE_OK) {
errx(1, "failed to compile data point query: %s", sqlite3_errstr(status));
}
} }
void cleanup_util_queries() { void cleanup_util_queries() {
sqlite3_finalize(DB_LIMITS_QUERY); sqlite3_finalize(DB_LIMITS_QUERY);
sqlite3_finalize(AVG_FOR_RANGE_QUERY); sqlite3_finalize(AVG_FOR_RANGE_QUERY);
sqlite3_finalize(DATA_POINT_QUERY);
} }
bool get_database_limits(sqlite3 *db, UtilPeriod period, UtilDate *start,
bool get_database_limits(sqlite3 *db, const char *period, UtilDate *start,
UtilDate *end) { UtilDate *end) {
if (strcasecmp(period, "week") == 0 || strcasecmp(period, "hour") == 0) { if (period == PERIOD_WEEK || period == PERIOD_HOUR) {
period = "day"; period = PERIOD_DAY;
} }
bool success = true; bool success = true;
sqlite3_bind_text(DB_LIMITS_QUERY, 1, period, -1, SQLITE_STATIC); sqlite3_bind_text(DB_LIMITS_QUERY, 1, PERIOD_LABELS[period], -1,
SQLITE_STATIC);
int status = sqlite3_step(DB_LIMITS_QUERY); int status = sqlite3_step(DB_LIMITS_QUERY);
if (status == SQLITE_ROW) { if (status == SQLITE_ROW) {
if (start) { if (start) {
@ -165,29 +183,31 @@ bool get_database_limits(sqlite3 *db, const char *period, UtilDate *start,
start->utc_month = sqlite3_column_int64(DB_LIMITS_QUERY, 2); start->utc_month = sqlite3_column_int64(DB_LIMITS_QUERY, 2);
start->utc_day = sqlite3_column_int64(DB_LIMITS_QUERY, 3); start->utc_day = sqlite3_column_int64(DB_LIMITS_QUERY, 3);
start->utc_hour = sqlite3_column_int64(DB_LIMITS_QUERY, 4); start->utc_hour = sqlite3_column_int64(DB_LIMITS_QUERY, 4);
start->local = sqlite3_column_int64(DB_LIMITS_QUERY, 5); start->utc_minute = sqlite3_column_int64(DB_LIMITS_QUERY, 5);
start->local_year = sqlite3_column_int64(DB_LIMITS_QUERY, 6); start->local = sqlite3_column_int64(DB_LIMITS_QUERY, 6);
start->local_month = sqlite3_column_int64(DB_LIMITS_QUERY, 7); start->local_year = sqlite3_column_int64(DB_LIMITS_QUERY, 7);
start->local_day = sqlite3_column_int64(DB_LIMITS_QUERY, 8); start->local_month = sqlite3_column_int64(DB_LIMITS_QUERY, 8);
start->local_hour = sqlite3_column_int64(DB_LIMITS_QUERY, 9); start->local_day = sqlite3_column_int64(DB_LIMITS_QUERY, 9);
start->local_hour = sqlite3_column_int64(DB_LIMITS_QUERY, 10);
start->local_minute = sqlite3_column_int64(DB_LIMITS_QUERY, 11);
} }
if (end) { if (end) {
end->utc = sqlite3_column_int64(DB_LIMITS_QUERY, 10); end->utc = sqlite3_column_int64(DB_LIMITS_QUERY, 12);
end->utc_year = sqlite3_column_int64(DB_LIMITS_QUERY, 11); end->utc_year = sqlite3_column_int64(DB_LIMITS_QUERY, 13);
end->utc_month = sqlite3_column_int64(DB_LIMITS_QUERY, 12); end->utc_month = sqlite3_column_int64(DB_LIMITS_QUERY, 14);
end->utc_day = sqlite3_column_int64(DB_LIMITS_QUERY, 13); end->utc_day = sqlite3_column_int64(DB_LIMITS_QUERY, 15);
end->utc_hour = sqlite3_column_int64(DB_LIMITS_QUERY, 14); end->utc_hour = sqlite3_column_int64(DB_LIMITS_QUERY, 16);
end->local = sqlite3_column_int64(DB_LIMITS_QUERY, 15); end->utc_minute = sqlite3_column_int64(DB_LIMITS_QUERY, 17);
end->local_year = sqlite3_column_int64(DB_LIMITS_QUERY, 16); end->local = sqlite3_column_int64(DB_LIMITS_QUERY, 18);
end->local_month = sqlite3_column_int64(DB_LIMITS_QUERY, 17); end->local_year = sqlite3_column_int64(DB_LIMITS_QUERY, 19);
end->local_day = sqlite3_column_int64(DB_LIMITS_QUERY, 18); end->local_month = sqlite3_column_int64(DB_LIMITS_QUERY, 20);
end->local_hour = sqlite3_column_int64(DB_LIMITS_QUERY, 19); end->local_day = sqlite3_column_int64(DB_LIMITS_QUERY, 21);
end->local_hour = sqlite3_column_int64(DB_LIMITS_QUERY, 22);
end->local_minute = sqlite3_column_int64(DB_LIMITS_QUERY, 23);
} }
} else { } else {
success = false; 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); sqlite3_reset(DB_LIMITS_QUERY);
return success; return success;
} }
@ -227,3 +247,55 @@ bool get_average_for_range(sqlite3 *db, int year, int month, int day,
sqlite3_reset(AVG_FOR_RANGE_QUERY); sqlite3_reset(AVG_FOR_RANGE_QUERY);
return success; return success;
} }
bool get_data_point_info(sqlite3 *db, int64_t time, UtilDataPointInfo *info) {
sqlite3_bind_int64(DATA_POINT_QUERY, 1, time);
if (sqlite3_step(DATA_POINT_QUERY) != SQLITE_ROW) {
sqlite3_reset(DATA_POINT_QUERY);
return false;
}
info->has_prev = !sqlite3_column_int(DATA_POINT_QUERY, 0);
info->prev_time = sqlite3_column_int64(DATA_POINT_QUERY, 1);
info->prev_temp = sqlite3_column_int64(DATA_POINT_QUERY, 2);
info->prev_humid = sqlite3_column_int64(DATA_POINT_QUERY, 3);
if (sqlite3_step(DATA_POINT_QUERY) != SQLITE_ROW) {
sqlite3_reset(DATA_POINT_QUERY);
return false;
}
info->has_next = !sqlite3_column_int(DATA_POINT_QUERY, 0);
info->next_time = sqlite3_column_int64(DATA_POINT_QUERY, 1);
info->next_temp = sqlite3_column_int64(DATA_POINT_QUERY, 2);
info->next_humid = sqlite3_column_int64(DATA_POINT_QUERY, 3);
int status = sqlite3_step(DATA_POINT_QUERY);
if (status == SQLITE_DONE) {
// we need to select the closes data point
int64_t closest_time;
if (!info->has_next && !info->has_prev) {
// no data points
return false;
} else if (info->has_next && !info->has_prev) {
closest_time = info->next_time;
} else if (info->has_prev && !info->has_next) {
closest_time = info->prev_time;
} else {
int64_t next_dist = labs(time - info->next_time);
int64_t prev_dist = labs(time - info->prev_time);
if (next_dist < prev_dist) {
closest_time = info->next_time;
} else {
closest_time = info->prev_time;
}
}
sqlite3_reset(DATA_POINT_QUERY);
return get_data_point_info(db, closest_time, info);
} else if (status == SQLITE_ROW) {
info->time = sqlite3_column_int64(DATA_POINT_QUERY, 1);
info->temp = sqlite3_column_int64(DATA_POINT_QUERY, 2);
info->humid = sqlite3_column_int64(DATA_POINT_QUERY, 3);
sqlite3_reset(DATA_POINT_QUERY);
return true;
} else {
sqlite3_reset(DATA_POINT_QUERY);
return false;
}
}

View File

@ -98,11 +98,13 @@ typedef struct {
int utc_month; int utc_month;
int utc_day; int utc_day;
int utc_hour; int utc_hour;
int utc_minute;
int64_t local; int64_t local;
int local_year; int local_year;
int local_month; int local_month;
int local_day; int local_day;
int local_hour; int local_hour;
int local_minute;
} UtilDate; } UtilDate;
enum { enum {
@ -118,9 +120,10 @@ extern const size_t NPERIOD;
/* /*
* Return the START of the first and END of the last PERIOD (ex. week) of DB. * Return the START of the first and END of the last PERIOD (ex. week) of DB.
* Also get the hour and minute limits for the first and last day.
* Return: false if an error occurred, true otherwise. * Return: false if an error occurred, true otherwise.
*/ */
bool get_database_limits(sqlite3 *db, const char *period, UtilDate *start, bool get_database_limits(sqlite3 *db, UtilPeriod period, UtilDate *start,
UtilDate *end); UtilDate *end);
typedef struct { typedef struct {
@ -144,4 +147,25 @@ bool get_average_for_range(sqlite3 *db, int year, int month, int day,
int64_t count, UtilPeriod period, int64_t count, UtilPeriod period,
UtilAverageRange *data); UtilAverageRange *data);
typedef struct {
int64_t time;
int temp;
int humid;
bool has_next;
int64_t next_time;
int next_temp;
int next_humid;
bool has_prev;
int64_t prev_time;
int prev_temp;
int prev_humid;
} UtilDataPointInfo;
/*
* Get the data, next point, and previous point for data point TIME. INFO must
* not be NULL.
* Return: false if an error occurred, true otherwise.
*/
bool get_data_point_info(sqlite3 *db, int64_t time, UtilDataPointInfo *info);
#endif #endif