Work on date selection
This commit is contained in:
		| @ -48,6 +48,8 @@ static void lcd_pulse_enable(LCD *lcd) { | ||||
| LCD *lcd_open(gpio_handle_t handle, gpio_pin_t rs, gpio_pin_t rw, gpio_pin_t en, | ||||
|               gpio_pin_t d0, gpio_pin_t d1, gpio_pin_t d2, gpio_pin_t d3, | ||||
|               gpio_pin_t d4, gpio_pin_t d5, gpio_pin_t d6, gpio_pin_t d7) { | ||||
|     LOG_VERBOSE("Initializing LCD: rs=%d, rw=%d, en=%d, d={%d, %d, %d, %d, %d, " | ||||
|                 "%d, %d, %d}\n", rs, rw, en, d0, d1, d2, d3, d4, d5, d6, d7); | ||||
|     LCD *lcd = malloc_checked(sizeof(LCD)); | ||||
|     lcd->handle = handle; | ||||
|     lcd->rs = rs; | ||||
| @ -68,8 +70,7 @@ LCD *lcd_open(gpio_handle_t handle, gpio_pin_t rs, gpio_pin_t rw, gpio_pin_t en, | ||||
|     lcd_call(lcd, 0, 0, 0, 0, 1, 1, 1, 0, 0); // 5x8 font, 2 lines, 8bit | ||||
|     lcd_entry_mode(lcd, LCD_INCREMENT, LCD_CURSOR_MOVE); | ||||
|     lcd_display_control(lcd, LCD_CURSOR_NO_BLINK, LCD_CURSOR_OFF, LCD_DISPLAY_ON); | ||||
|     LOG_VERBOSE("Initializing LCD: rs=%d, rw=%d, en=%d, d={%d, %d, %d, %d, %d, " | ||||
|                 "%d, %d, %d}\n", rs, rw, en, d0, d1, d2, d3, d4, d5, d6, d7); | ||||
|     LOG_VERBOSE("LCD Initialization done\n"); | ||||
|     return lcd; | ||||
| } | ||||
|  | ||||
|  | ||||
							
								
								
									
										109
									
								
								src/screen.c
									
									
									
									
									
								
							
							
						
						
									
										109
									
								
								src/screen.c
									
									
									
									
									
								
							| @ -171,15 +171,6 @@ StatsScreen *stats_screen_new() { | ||||
|     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; | ||||
| @ -206,15 +197,15 @@ static void stats_by_select_period(StatsByScreen *screen, SensorState *state) { | ||||
|     if (state->sel_down) { | ||||
|         ++screen->stage; | ||||
|         switch (screen->period) { | ||||
|         case 0: | ||||
|         case 1: | ||||
|         case 2: | ||||
|         case PERIOD_HOUR: | ||||
|         case PERIOD_DAY: | ||||
|         case PERIOD_WEEK: | ||||
|             screen->ds.max_stage = DATE_SEL_DAY; | ||||
|             break; | ||||
|         case 3: | ||||
|         case PERIOD_MONTH: | ||||
|             screen->ds.max_stage = DATE_SEL_MONTH; | ||||
|             break; | ||||
|         case 4: | ||||
|         case PERIOD_YEAR: | ||||
|             screen->ds.max_stage = DATE_SEL_YEAR; | ||||
|             break; | ||||
|         } | ||||
| @ -244,11 +235,14 @@ static void date_sel_clamp_time(DateSelection *ds) { | ||||
|     if (ds->year == ds->end_time.local_year && | ||||
|         ds->month > ds->end_time.local_month) { | ||||
|         ds->month = ds->end_time.local_month; | ||||
|         if (ds->stage == DATE_SEL_DAY) { | ||||
|             // last days of previous month | ||||
|             ds->day = days_in_month(ds->month, ds->year); | ||||
|         } | ||||
|     } | ||||
|     if (ds->month == ds->end_time.local_year && | ||||
|     if (ds->year == 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) { | ||||
| @ -257,16 +251,15 @@ static void date_sel_clamp_time(DateSelection *ds) { | ||||
|     if (ds->year == ds->start_time.local_year && | ||||
|         ds->month < ds->start_time.local_month) { | ||||
|         ds->month = ds->start_time.local_month; | ||||
|         if (ds->stage == DATE_SEL_DAY) { | ||||
|             ds->day = 1; // first day of the next 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) { | ||||
| @ -404,12 +397,77 @@ static void stats_by_select_start(StatsByScreen *screen, SensorState *state) { | ||||
|             break; // ignore | ||||
|         case DATE_SEL_DONE: | ||||
|             ++screen->stage; | ||||
|             screen->offset_scale = 0; | ||||
|             screen->need_redraw = true; | ||||
|             screen->ulimit_reached = false; | ||||
|             break; | ||||
|         } | ||||
|     }  | ||||
| } | ||||
|  | ||||
| static void stats_by_show_stats(StatsByScreen *screen, SensorState *state) { | ||||
|     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) { | ||||
|         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); | ||||
|         UtilAverageRange data; | ||||
|         if (!get_average_for_range(state->db, screen->ds.year, screen->ds.month, | ||||
|                                    screen->ds.day, screen->offset_scale, | ||||
|                                    screen->period, &data)) { | ||||
|             lcd_write_string(state->lcd, "Query failed!"); | ||||
|             warnx("failed to query average temperature and humidity"); | ||||
|             return; | ||||
|         } | ||||
|         screen->ulimit_reached = data.upper_bound; | ||||
|         screen->blimit_reached = data.lower_bound; | ||||
|         char period_string[17]; | ||||
|         switch (screen->period) { | ||||
|         case PERIOD_YEAR: | ||||
|             snprintf(period_string, 17, "Year>%d", data.year); | ||||
|             break; | ||||
|         case PERIOD_MONTH: | ||||
|             snprintf(period_string, 17, "Month>%d-%d", data.year, | ||||
|                      data.month); | ||||
|             break; | ||||
|         case PERIOD_WEEK: | ||||
|             snprintf(period_string, 17, "Week>%d-%d-%d", data.year, | ||||
|                      data.month, data.day); | ||||
|             break; | ||||
|         case PERIOD_DAY: | ||||
|             snprintf(period_string, 17, "Day>%d-%d-%d", data.year, | ||||
|                      data.month, data.day); | ||||
|             break; | ||||
|         case PERIOD_HOUR: | ||||
|             snprintf(period_string, 17, "Hour>%d-%d %d:00", data.month, | ||||
|                      data.day, data.hour); | ||||
|             break; | ||||
|         } | ||||
|         lcd_write_string(state->lcd, period_string); | ||||
|         lcd_move_to(state->lcd, 1, 0); | ||||
|         if (data.npoints) { | ||||
|             char data_string[17]; | ||||
|             snprintf(data_string, 17, "T:%.1fF H:%d%%", DK_TO_F(data.temp), data.humid); | ||||
|             lcd_write_string(state->lcd, data_string); | ||||
|         } else { | ||||
|             lcd_write_string(state->lcd, "No data!"); | ||||
|         } | ||||
|     } | ||||
|     if (!screen->ulimit_reached && state->up_down) { | ||||
|         ++screen->offset_scale; | ||||
|         screen->need_redraw = true; | ||||
|     } | ||||
|     if (!screen->blimit_reached && state->down_down) { | ||||
|         --screen->offset_scale; | ||||
|         screen->need_redraw = true; | ||||
|     } | ||||
| } | ||||
|  | ||||
| static bool stats_by_screen_dispatch(StatsByScreen *screen, | ||||
|                                      SensorState *state) { | ||||
|     if (state->force_draw) { | ||||
| @ -432,16 +490,7 @@ static bool stats_by_screen_dispatch(StatsByScreen *screen, | ||||
|         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; | ||||
|         } | ||||
|         stats_by_show_stats(screen, state); | ||||
|         break; | ||||
|     default: | ||||
|         LOG_VERBOSE("Attempt to show bad stats by screen\n"); | ||||
|  | ||||
| @ -6,8 +6,7 @@ | ||||
|  * 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_SCREEN_H | ||||
|  */ #ifndef INCLUDED_SCREEN_H | ||||
| #define INCLUDED_SCREEN_H | ||||
|  | ||||
| #include "button.h" | ||||
| @ -161,8 +160,11 @@ typedef struct { | ||||
|         STATS_BY_SHOWING, | ||||
|     } stage; | ||||
|     bool need_redraw; | ||||
|     int period; | ||||
|     UtilPeriod period; | ||||
|     DateSelection ds; | ||||
|     int offset_scale; | ||||
|     bool ulimit_reached; | ||||
|     bool blimit_reached; | ||||
| } StatsByScreen; | ||||
|  | ||||
| /* | ||||
|  | ||||
							
								
								
									
										75
									
								
								src/util.c
									
									
									
									
									
								
							
							
						
						
									
										75
									
								
								src/util.c
									
									
									
									
									
								
							| @ -12,6 +12,15 @@ | ||||
| #include <err.h> | ||||
| #include <string.h> | ||||
|  | ||||
| const char *PERIOD_LABELS[] = { | ||||
|     "HOUR", | ||||
|     "DAY", | ||||
|     "WEEK", | ||||
|     "MONTH", | ||||
|     "YEAR",  | ||||
| }; | ||||
| const size_t NPERIOD = sizeof(PERIOD_LABELS) / sizeof(char *); | ||||
|  | ||||
| Options GLOBAL_OPTS; | ||||
|  | ||||
| void cleanup_options(Options *opts) { | ||||
| @ -96,25 +105,53 @@ static const char *DB_LIMITS_QUERY_STR = | ||||
|     "unixepoch(max(time), 'unixepoch', 'localtime', '+1 ' || ?1, 'start of ' || ?1, '-1 second') as MaxLocal\n" | ||||
|     "FROM env_data);"; | ||||
| static sqlite3_stmt *DB_LIMITS_QUERY; | ||||
| const char *AVG_FOR_RANGE_QUERY_STR = | ||||
|     "SELECT\n" | ||||
|     "strftime('%Y', stime, 'unixepoch', 'localtime') as y,\n" | ||||
|     "strftime('%m', stime, 'unixepoch', 'localtime') as m,\n" | ||||
|     "strftime('%e', stime, 'unixepoch', 'localtime') as d,\n" | ||||
|     "strftime('%H', stime, 'unixepoch', 'localtime') as h,\n" | ||||
|     "ndata,\n" | ||||
|     "etime >= (select max(time) from env_data) as ulimit,\n" | ||||
|     "stime <= (select min(time) from env_data) as blimit,\n" | ||||
|     "atemp,\n" | ||||
|     "ahumid\n" | ||||
|     "FROM (SELECT\n" | ||||
|     "unixepoch(printf('%04d-%02d-%02d', ?1, ?2, ?3), 'utc', printf('%d %s', ?4 * ?6, ?5)) as stime,\n" | ||||
|     "unixepoch(printf('%04d-%02d-%02d', ?1, ?2, ?3), 'utc', printf('%d %s', (?4 + 1) * ?6, ?5)) as etime,\n" | ||||
|     "count(time) AS ndata,\n" | ||||
|     "round(avg(temp)) AS atemp,\n" | ||||
|     "round(avg(humid)) AS ahumid\n" | ||||
|     "FROM env_data\n" | ||||
|     "WHERE time > stime\n" | ||||
|     "AND time < etime);\n"; | ||||
| static sqlite3_stmt *AVG_FOR_RANGE_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)); | ||||
|     } | ||||
|     status = sqlite3_prepare_v2(db, AVG_FOR_RANGE_QUERY_STR, -1, | ||||
|                                 &AVG_FOR_RANGE_QUERY, NULL); | ||||
|     if (status != SQLITE_OK) { | ||||
|         errx(1, "failed to compile range average query: %s", sqlite3_errstr(status)); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void cleanup_util_queries() { | ||||
|     sqlite3_finalize(DB_LIMITS_QUERY); | ||||
|     sqlite3_finalize(AVG_FOR_RANGE_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); | ||||
|     sqlite3_bind_text(DB_LIMITS_QUERY, 1, period, -1, SQLITE_STATIC); | ||||
|     int status = sqlite3_step(DB_LIMITS_QUERY); | ||||
|     if (status == SQLITE_ROW) { | ||||
|         if (start) { | ||||
| @ -145,3 +182,39 @@ bool get_database_limits(sqlite3 *db, const char *period, UtilDate *start, | ||||
|     sqlite3_reset(DB_LIMITS_QUERY); | ||||
|     return success; | ||||
| } | ||||
|  | ||||
| bool get_average_for_range(sqlite3 *db, int year, int month, int day, | ||||
|                            int64_t count, UtilPeriod period, | ||||
|                            UtilAverageRange *data) { | ||||
|     bool success = true; | ||||
|     sqlite3_bind_int(AVG_FOR_RANGE_QUERY, 1, year); | ||||
|     sqlite3_bind_int(AVG_FOR_RANGE_QUERY, 2, month); | ||||
|     sqlite3_bind_int(AVG_FOR_RANGE_QUERY, 3, day); | ||||
|     sqlite3_bind_int64(AVG_FOR_RANGE_QUERY, 4, count); | ||||
|     if (period == PERIOD_WEEK) { | ||||
|         sqlite3_bind_text(AVG_FOR_RANGE_QUERY, 5, "days", -1, SQLITE_STATIC); | ||||
|         sqlite3_bind_int(AVG_FOR_RANGE_QUERY, 6, 7); | ||||
|     } else { | ||||
|         sqlite3_bind_text(AVG_FOR_RANGE_QUERY, 5, PERIOD_LABELS[period], -1, | ||||
|                           SQLITE_STATIC); | ||||
|         sqlite3_bind_int(AVG_FOR_RANGE_QUERY, 6, 1); | ||||
|     } | ||||
|     int status = sqlite3_step(AVG_FOR_RANGE_QUERY); | ||||
|     if (status == SQLITE_ROW) { | ||||
|         if (data) { | ||||
|             data->year = sqlite3_column_int(AVG_FOR_RANGE_QUERY, 0); | ||||
|             data->month = sqlite3_column_int(AVG_FOR_RANGE_QUERY, 1); | ||||
|             data->day = sqlite3_column_int(AVG_FOR_RANGE_QUERY, 2); | ||||
|             data->hour = sqlite3_column_int(AVG_FOR_RANGE_QUERY, 3); | ||||
|             data->npoints = sqlite3_column_int(AVG_FOR_RANGE_QUERY, 4); | ||||
|             data->upper_bound = sqlite3_column_int64(AVG_FOR_RANGE_QUERY, 5); | ||||
|             data->lower_bound = sqlite3_column_int64(AVG_FOR_RANGE_QUERY, 6); | ||||
|             data->temp = sqlite3_column_int(AVG_FOR_RANGE_QUERY, 7); | ||||
|             data->humid = sqlite3_column_int(AVG_FOR_RANGE_QUERY, 8); | ||||
|         } | ||||
|     } else { | ||||
|         success = false; | ||||
|     } | ||||
|     sqlite3_reset(AVG_FOR_RANGE_QUERY); | ||||
|     return success; | ||||
| } | ||||
|  | ||||
							
								
								
									
										31
									
								
								src/util.h
									
									
									
									
									
								
							
							
						
						
									
										31
									
								
								src/util.h
									
									
									
									
									
								
							| @ -103,6 +103,16 @@ typedef struct { | ||||
|     int local_day; | ||||
| } UtilDate; | ||||
|  | ||||
| typedef enum { | ||||
|     PERIOD_HOUR = 0, | ||||
|     PERIOD_DAY, | ||||
|     PERIOD_WEEK, | ||||
|     PERIOD_MONTH, | ||||
|     PERIOD_YEAR, | ||||
| } UtilPeriod; | ||||
| extern const char *PERIOD_LABELS[]; | ||||
| extern const size_t NPERIOD; | ||||
|  | ||||
| /* | ||||
|  * Return the START of the first and END of the last PERIOD (ex. week) of DB. | ||||
|  * Return: false if an error occurred, true otherwise. | ||||
| @ -110,4 +120,25 @@ typedef struct { | ||||
| bool get_database_limits(sqlite3 *db, const char *period, UtilDate *start, | ||||
|                          UtilDate *end); | ||||
|  | ||||
| typedef struct { | ||||
|     int npoints; | ||||
|     int temp; | ||||
|     int humid; | ||||
|     int year; | ||||
|     int month; | ||||
|     int day; | ||||
|     int hour; | ||||
|     bool lower_bound; | ||||
|     bool upper_bound; | ||||
| } UtilAverageRange; | ||||
|  | ||||
| /* | ||||
|  * Get the average temp. and humid. between YEAR-MONTH-DAY + COUNT * PERIOD and | ||||
|  * YEAR-MONTH-DAY + (COUNT + 1) * PERIOD. | ||||
|  * Return: false if an error occurred, true otherwise. | ||||
|  */ | ||||
| bool get_average_for_range(sqlite3 *db, int year, int month, int day, | ||||
|                            int64_t count, UtilPeriod period, | ||||
|                            UtilAverageRange *data); | ||||
|  | ||||
| #endif | ||||
|  | ||||
		Reference in New Issue
	
	Block a user