]> git.earman.xyz Git - sensor-watch.git/commitdiff
add data logging, more modes to BME280 project
authorJoey Castillo <jose.castillo@gmail.com>
Wed, 18 Aug 2021 17:50:09 +0000 (13:50 -0400)
committerJoey Castillo <jose.castillo@gmail.com>
Wed, 18 Aug 2021 17:50:09 +0000 (13:50 -0400)
Sensor Watch BME280 Project/app.c
Sensor Watch BME280 Project/app.h [new file with mode: 0644]

index b34e529c6088ef72ce3a851c3a76f08c3e76cf5f..bf4969fddb1067fb283c1a2be6e068e989c9c066 100644 (file)
@@ -1,38 +1,13 @@
 #include <stdio.h>
 #include <string.h>
+#include <math.h>
 #include "watch.h"
 #include "bme280.h"
 
-typedef enum ApplicationMode {
-    MODE_TEMPERATURE = 0,
-    MODE_HUMIDITY,
-    MODE_OFF,
-} ApplicationMode;
-
-typedef struct ApplicationState {
-    ApplicationMode mode;
-    int light_ticks;
-    bool led_on;
-    bool needs_beep;
-    uint16_t dig_T1;
-    int16_t dig_T2;
-    int16_t dig_T3;
-    uint8_t dig_H1;
-    int16_t dig_H2;
-    uint8_t dig_H3;
-    int16_t dig_H4;
-    int16_t dig_H5;
-    int8_t dig_H6;
-} ApplicationState;
+#include "app.h"
 
 ApplicationState application_state;
-
-void cb_light_pressed();
-void cb_mode_pressed();
-void cb_tick();
-
-float read_temperature(int32_t *p_t_fine);
-float read_humidity(int32_t t_fine);
+char buf[16] = {0};
 
 /**
  * @brief Zeroes out the application state struct.
@@ -41,19 +16,22 @@ void app_init() {
     memset(&application_state, 0, sizeof(application_state));
 }
 
-/**
- * @todo stash the BME280's calibration values in backup memory so we don't have to ask again
- */
 void app_wake_from_deep_sleep() {
+    // This app does not support deep sleep mode.
 }
 
-/**
- * Enables the MODE button, the display and the sensor, and grabs calibration data.
- */
 void app_setup() {
+    struct calendar_date_time date_time;
+    watch_get_date_time(&date_time);
+    if (date_time.date.year < 2020) {
+        date_time.date.year = 2020;
+        watch_set_date_time(date_time);
+    }
+
     watch_enable_buttons();
     watch_register_button_callback(BTN_MODE, cb_mode_pressed);
     watch_register_button_callback(BTN_LIGHT, cb_light_pressed);
+    watch_register_button_callback(BTN_ALARM, cb_alarm_pressed);
 
     watch_enable_buzzer();
     watch_enable_led(false);
@@ -79,7 +57,7 @@ void app_setup() {
                                 (watch_i2c_read8(BME280_ADDRESS, BME280_REGISTER_DIG_H5) >> 4);
     application_state.dig_H6 = (int8_t)watch_i2c_read8(BME280_ADDRESS, BME280_REGISTER_DIG_H6);
 
-    watch_i2c_write8(BME280_ADDRESS, BME280_REGISTER_CONTROL_HUMID, BME280_CONTROL_HUMID_SAMPLING_X16);
+    watch_i2c_write8(BME280_ADDRESS, BME280_REGISTER_CONTROL_HUMID, BME280_CONTROL_HUMID_SAMPLING_NONE);
     watch_i2c_write8(BME280_ADDRESS, BME280_REGISTER_CONTROL, BME280_CONTROL_TEMPERATURE_SAMPLING_X16 |
                                                               BME280_CONTROL_PRESSURE_SAMPLING_NONE |
                                                               BME280_CONTROL_MODE_FORCED);
@@ -105,22 +83,28 @@ void app_wake_from_sleep() {
  * Displays the temperature and humidity on screen, or a string indicating no measurements are being taken.
  */
 bool app_loop() {
-    int32_t t_fine;
-    float temperature;
-    float humidity;
-    char buf[11] = {0};
+    // play a beep if the mode has changed in response to a user's press of the MODE button
+    if (application_state.mode_changed) {
+        // low note for nonzero case, high note for return to clock
+        watch_buzzer_play_note(application_state.mode ? BUZZER_NOTE_C7 : BUZZER_NOTE_C8, 100);
+        application_state.mode_changed = false;
+    }
 
-    if (application_state.needs_beep) {
-        watch_buzzer_play_note(BUZZER_NOTE_A6, 100);
-        application_state.needs_beep = false;
+    // If the user is not in clock mode and the mode timeout has expired, return them to clock mode
+    if (application_state.mode != MODE_CLOCK && application_state.mode_ticks == 0) {
+        application_state.mode = MODE_CLOCK;
+        application_state.mode_changed = true;
     }
 
+    // If the LED is off and should be on, turn it on
     if (application_state.light_ticks > 0 && !application_state.led_on) {
-        watch_set_led_yellow();
+        watch_set_led_green();
         application_state.led_on = true;
     }
 
+    // if the LED is on and should be off, turn it off
     if (application_state.led_on && application_state.light_ticks == 0) {
+        // unless the user is holding down the LIGHT button, in which case, give them more time.
         if (watch_get_pin_level(BTN_LIGHT)) {
             application_state.light_ticks = 3;
         } else {
@@ -130,34 +114,31 @@ bool app_loop() {
     }
 
     switch (application_state.mode) {
-        case MODE_TEMPERATURE:
-            // take one reading
-            watch_i2c_write8(BME280_ADDRESS, BME280_REGISTER_CONTROL, BME280_CONTROL_TEMPERATURE_SAMPLING_X16 |
-                                                                      BME280_CONTROL_MODE_FORCED);
-            // wait for reading to finish
-            while(watch_i2c_read8(BME280_ADDRESS, BME280_REGISTER_STATUS) & BME280_STATUS_UPDATING_MASK);
-            temperature = read_temperature(NULL);
-            sprintf(buf, "TE  %4.1f#C", temperature);
-            watch_display_string(buf, 0);
-            watch_clear_colon();
-            break;
-        case MODE_HUMIDITY:
-            // take one reading
-            watch_i2c_write8(BME280_ADDRESS, BME280_REGISTER_CONTROL, BME280_CONTROL_TEMPERATURE_SAMPLING_X16 |
-                                                                      BME280_CONTROL_MODE_FORCED);
-            // wait for reading to finish
-            while(watch_i2c_read8(BME280_ADDRESS, BME280_REGISTER_STATUS) & BME280_STATUS_UPDATING_MASK);
-            temperature = read_temperature(&t_fine);
-            humidity = read_humidity(t_fine);
-            sprintf(buf, "HU  rH %3d", (int)humidity);
-            watch_display_string(buf, 0);
-            watch_set_colon();
-            break;
-        case MODE_OFF:
-            watch_display_string("    Sleep ", 0);
-            watch_clear_pixel(1, 16);
+        case MODE_CLOCK:
+            do_clock_mode();
+            break;
+        case MODE_TEMP:
+            do_temp_mode();
+            break;
+        case MODE_LOG:
+            do_log_mode();
+            break;
+        case MODE_PREFS:
+            do_prefs_mode();
+            break;
+        case MODE_SET:
+            do_set_time_mode();
+            break;
+        case NUM_MODES:
+            // dummy case, just silences a warning
+            break;
     }
 
+    application_state.mode_changed = false;
+
+    delay_ms(250);
+    application_state.debounce_wait = false;
+
     return true;
 }
 
@@ -183,7 +164,11 @@ float read_temperature(int32_t *p_t_fine) {
     // if we got a pointer to a t_fine, return it by reference (for humidity calculation).
     if (p_t_fine != NULL) *p_t_fine = t_fine;
 
-    return ((t_fine * 5 + 128) >> 8) / 100.0;
+    if (application_state.is_fahrenheit) {
+        return (((t_fine * 5 + 128) >> 8) / 100.0) * 1.8 + 32;
+    } else {
+        return ((t_fine * 5 + 128) >> 8) / 100.0;
+    }
 }
 
 /**
@@ -208,17 +193,212 @@ float read_humidity(int32_t t_fine) {
     return h / 1024.0;
 }
 
+void log_data() {
+    struct calendar_date_time date_time;
+    watch_get_date_time(&date_time);
+    uint8_t hour = date_time.time.sec;
+    int8_t temperature = read_temperature(NULL);
+
+    for(int i = 0; i < MAX_DATA_POINTS - 1; i++) {
+        application_state.logged_data[i] = application_state.logged_data[i + 1];
+    }
+    application_state.logged_data[MAX_DATA_POINTS - 1].is_valid = true;
+    application_state.logged_data[MAX_DATA_POINTS - 1].hour = hour;
+    application_state.logged_data[MAX_DATA_POINTS - 1].temperature = temperature;
+}
+
+void do_clock_mode() {
+    struct calendar_date_time date_time;
+    const char months[12][3] = {"JA", "FE", "MR", "AR", "MA", "JN", "JL", "AU", "SE", "OC", "NO", "dE"};
+
+    watch_get_date_time(&date_time);
+    watch_display_string((char *)months[date_time.date.month - 1], 0);
+    sprintf(buf, "%2d%2d%02d%02d", date_time.date.day, date_time.time.hour, date_time.time.min, date_time.time.sec);
+    watch_display_string(buf, 2);
+    watch_set_colon();
+}
+
+void do_temp_mode() {
+    int32_t t_fine;
+    float temperature;
+    float humidity;
+
+    // take one reading
+    watch_i2c_write8(BME280_ADDRESS, BME280_REGISTER_CONTROL, BME280_CONTROL_TEMPERATURE_SAMPLING_X16 |
+                                                              BME280_CONTROL_MODE_FORCED);
+    // wait for reading to finish
+    while(watch_i2c_read8(BME280_ADDRESS, BME280_REGISTER_STATUS) & BME280_STATUS_UPDATING_MASK);
+    temperature = read_temperature(&t_fine);
+    humidity = read_humidity(t_fine);
+    if (application_state.show_humidity) {
+        sprintf(buf, "TE%2d%4.1f#%c", (int)(humidity / 10), temperature), application_state.is_fahrenheit ? 'F' : 'C';
+    } else {
+        sprintf(buf, "TE  %4.1f#%c", temperature, application_state.is_fahrenheit ? 'F' : 'C');
+    }
+    watch_display_string(buf, 0);
+    watch_clear_colon();
+}
+
+void do_log_mode() {
+    bool is_valid = (uint8_t)(application_state.logged_data[MAX_DATA_POINTS - 1 - application_state.page].is_valid);
+    uint8_t hour = (uint8_t)(application_state.logged_data[MAX_DATA_POINTS - 1 - application_state.page].hour);
+    int8_t temperature = (int8_t)(application_state.logged_data[MAX_DATA_POINTS - 1 - application_state.page].temperature);
+    if (!is_valid) {
+        sprintf(buf, "LO%2d------", application_state.page);
+        watch_clear_colon();
+    } else {
+        sprintf(buf, "LO%2d%2d%4d", application_state.page, hour, temperature);
+        watch_set_colon();
+    }
+    watch_display_string(buf, 0);
+}
+
+void log_mode_handle_primary_button() {
+    application_state.page++;
+    if (application_state.page == MAX_DATA_POINTS) application_state.page = 0;
+}
+
+void do_prefs_mode() {
+    sprintf(buf, "PR  CorF %c", application_state.is_fahrenheit ? 'F' : 'C');
+    watch_display_string(buf, 0);
+    watch_clear_colon();
+}
+
+void prefs_mode_handle_primary_button() {
+    // TODO: add rest of preferences (12/24, humidity, LED color, etc.)
+    // for now only one, C or F
+}
+
+void prefs_mode_handle_secondary_button() {
+    application_state.is_fahrenheit = !application_state.is_fahrenheit;
+}
+
+void do_set_time_mode() {
+    struct calendar_date_time date_time;
+
+    watch_get_date_time(&date_time);
+    watch_display_string("          ", 0);
+    switch (application_state.page) {
+        case 0: // hour
+            sprintf(buf, "ST t%2d", date_time.time.hour);
+            break;
+        case 1: // minute
+            sprintf(buf, "ST t  %02d", date_time.time.min);
+            break;
+        case 2: // second
+            sprintf(buf, "ST t    %02d", date_time.time.sec);
+            break;
+        case 3: // year
+            sprintf(buf, "ST d%2d", date_time.date.year - 2000);
+            break;
+        case 4: // month
+            sprintf(buf, "ST d  %02d", date_time.date.month);
+            break;
+        case 5: // day
+            sprintf(buf, "ST d    %02d", date_time.date.day);
+            break;
+    }
+    watch_display_string(buf, 0);
+    watch_set_pixel(1, 12); // required for T in position 1
+}
+
+void set_time_mode_handle_primary_button() {
+    application_state.page++;
+    if (application_state.page == 6) application_state.page = 0;
+}
+
+void set_time_mode_handle_secondary_button() {
+    struct calendar_date_time date_time;
+    watch_get_date_time(&date_time);
+    const uint8_t days_in_month[12] = {31, 28, 31, 30, 31, 30, 30, 31, 30, 31, 30, 31};
+
+    switch (application_state.page) {
+        case 0: // hour
+            date_time.time.hour = (date_time.time.hour + 1) % 24;
+            break;
+        case 1: // minute
+            date_time.time.min = (date_time.time.min + 1) % 60;
+            break;
+        case 2: // second
+            date_time.time.sec = 0;
+            break;
+        case 3: // year
+            // only allow 2021-2030. fix this sometime next decade
+            date_time.date.year = ((date_time.date.year % 10) + 1) + 2020;
+            break;
+        case 4: // month
+            date_time.date.month = ((date_time.date.month + 1) % 12);
+            break;
+        case 5: // day
+            date_time.date.day = date_time.date.day + 1;
+            // can't set to the 29th on a leap year. if it's february 29, set to 11:59 on the 28th.
+            // and it should roll over.
+            if (date_time.date.day > days_in_month[date_time.date.month - 1]) {
+                date_time.date.day = 1;
+            }
+            break;
+    }
+    watch_set_date_time(date_time);
+}
+
 void cb_mode_pressed() {
-    application_state.mode = (application_state.mode + 1) % 3;
-    application_state.needs_beep = true;
+    if (application_state.debounce_wait) return;
+    application_state.debounce_wait = true;
+
+    application_state.mode = (application_state.mode + 1) % NUM_MODES;
+    application_state.mode_changed = true;
+    application_state.mode_ticks = 300;
+    application_state.page = 0;
+}
+
+void cb_light_pressed() {
+    if (application_state.debounce_wait) return;
+    application_state.debounce_wait = true;
+
+    switch (application_state.mode) {
+        case MODE_PREFS:
+            prefs_mode_handle_secondary_button();
+            break;
+        case MODE_SET:
+            set_time_mode_handle_secondary_button();
+            break;
+        default:
+            application_state.light_ticks = 3;
+            break;
+    }
+}
+
+void cb_alarm_pressed() {
+    if (application_state.debounce_wait) return;
+    application_state.debounce_wait = true;
+
+    switch (application_state.mode) {
+        case MODE_LOG:
+            log_mode_handle_primary_button();
+            break;
+        case MODE_PREFS:
+            prefs_mode_handle_primary_button();
+            break;
+        case MODE_SET:
+            set_time_mode_handle_primary_button();
+            break;
+        default:
+            break;
+    }
 }
 
 void cb_tick() {
+    // TODO: use alarm interrupt to trigger data acquisition.
+    struct calendar_date_time date_time;
+    watch_get_date_time(&date_time);
+    if (date_time.time.min == 0 && date_time.time.sec == 0) {
+        log_data();
+    }
+
     if (application_state.light_ticks > 0) {
         application_state.light_ticks--;
     }
-}
-
-void cb_light_pressed() {
-    application_state.light_ticks = 3;
+    if (application_state.mode_ticks > 0) {
+        application_state.mode_ticks--;
+    }
 }
diff --git a/Sensor Watch BME280 Project/app.h b/Sensor Watch BME280 Project/app.h
new file mode 100644 (file)
index 0000000..8507669
--- /dev/null
@@ -0,0 +1,77 @@
+// Sensor Watch: Hiking Log Demo App
+// This app displays a clock and temperature data from a BME280 temperature and humidiity sensor.
+// It also logs up to 36 hours of temperature data for playback.
+// You can use this app on backcountry treks: take the watch off at night and place it outside your tent.
+// It will log overnight low temperatures for review in the morning and optional transfer to your notepad.
+
+#define MAX_DATA_POINTS 36
+
+typedef enum ApplicationMode {
+    MODE_CLOCK = 0, // Displays month, day and current time.
+    MODE_TEMP,      // (TE) Displays temperature and an optional humidity reading (0-10 representing 0-100%)
+    MODE_LOG,       // (LO) Plays back temperature data (temperature in seconds slot)
+    MODE_PREFS,     // (PR) Allows setting options for the application
+    MODE_SET,       // (ST) Set time and date
+    NUM_MODES       // Last item in the enum, it's the number of cases.
+} ApplicationMode;
+
+typedef struct SensorReading {
+    bool is_valid;
+    uint8_t hour;
+    int8_t temperature;
+} SensorReading;
+
+typedef struct ApplicationState {
+    // Internal application state
+    ApplicationMode mode;   // Current mode
+    bool mode_changed;      // Lets us perform one-time setup for a given mode
+    uint16_t mode_ticks;    // Timeout for the mode (returns to clock after timeout expires)
+    uint8_t light_ticks;    // Timeout for the light
+    bool led_on;            // Indicates that the LED is on
+    uint8_t page;           // Tracks the current page in log, prefs or settings.
+    bool is_fahrenheit;     // User preference, C or F
+    bool debounce_wait;     // For debouncing button inputs
+
+    // Data logging
+    SensorReading logged_data[MAX_DATA_POINTS];
+
+    // User preference
+    bool show_humidity;     // Indicates that the LED is on
+
+    // BME280 calibration values
+    uint16_t dig_T1;
+    int16_t dig_T2;
+    int16_t dig_T3;
+    uint8_t dig_H1;
+    int16_t dig_H2;
+    uint8_t dig_H3;
+    int16_t dig_H4;
+    int16_t dig_H5;
+    int8_t dig_H6;
+} ApplicationState;
+
+float read_temperature(int32_t *p_t_fine);
+float read_humidity(int32_t t_fine);
+
+void log_data();
+
+void do_clock_mode();
+
+void do_temp_mode();
+void temp_mode_handle_primary_button();
+
+void do_log_mode();
+void log_mode_handle_primary_button();
+
+void do_prefs_mode();
+void prefs_mode_handle_primary_button();
+void prefs_mode_handle_secondary_button();
+
+void do_set_time_mode();
+void set_time_mode_handle_primary_button();
+void set_time_mode_handle_secondary_button();
+
+void cb_light_pressed();
+void cb_mode_pressed();
+void cb_alarm_pressed();
+void cb_tick();