From a81b6960e3f1ab9057c623ef694013ca3b929c67 Mon Sep 17 00:00:00 2001 From: joeycastillo Date: Wed, 16 Oct 2024 08:20:20 -0400 Subject: [PATCH] more accelerometer testing --- movement.c | 14 +- movement_config.h | 2 +- movement_faces.h | 2 + watch-faces.mk | 2 + watch-faces/demo/accel_interrupt_count_face.c | 2 +- .../demo/accelerometer_sleep_state_face.c | 102 ++++++++++++ .../demo/accelerometer_sleep_state_face.h | 47 ++++++ watch-faces/sensor/activity_logging_face.c | 154 ++++++++++++++++++ watch-faces/sensor/activity_logging_face.h | 71 ++++++++ 9 files changed, 387 insertions(+), 9 deletions(-) create mode 100644 watch-faces/demo/accelerometer_sleep_state_face.c create mode 100644 watch-faces/demo/accelerometer_sleep_state_face.h create mode 100644 watch-faces/sensor/activity_logging_face.c create mode 100644 watch-faces/sensor/activity_logging_face.h diff --git a/movement.c b/movement.c index 1535af87..917c2881 100644 --- a/movement.c +++ b/movement.c @@ -509,8 +509,8 @@ void app_init(void) { if (date_time.reg == 0) { // at first boot, set year to 2024 date_time.unit.year = 2024 - WATCH_RTC_REFERENCE_YEAR; - date_time.unit.month = 1; - date_time.unit.day = 1; + date_time.unit.month = 10; + date_time.unit.day = 16; watch_rtc_set_date_time(date_time); } @@ -629,20 +629,20 @@ void app_setup(void) { lis2dw_set_mode(LIS2DW_MODE_LOW_POWER); // select low power (not high performance) lis2dw_set_low_power_mode(LIS2DW_LP_MODE_1); // lowest power mode, 12-bit, up this if needed lis2dw_set_low_noise_mode(true); // only marginally raises power consumption - lis2dw_enable_sleep(); // sleep at 1.6Hz, wake to 12.5Hz? - lis2dw_set_range(LIS2DW_CTRL6_VAL_RANGE_2G); // data sheet recommends 2G range + lis2dw_enable_sleep(); // enable sleep mode + lis2dw_set_range(LIS2DW_RANGE_2_G); // Application note AN5038 recommends 2g range lis2dw_set_data_rate(LIS2DW_DATA_RATE_LOWEST); // 1.6Hz in low power mode lis2dw_enable_sleep(); // allow acceleromter to sleep and wake on activity - lis2dw_configure_wakeup_threshold(24); // threshold is 1/64th of full scale, so for a FS of ±2G this is 1.5G + lis2dw_configure_wakeup_threshold(24); // g threshold to wake up: (2 * FS / 64) where FS is "full scale" of ±2g. lis2dw_configure_6d_threshold(3); // 0-3 is 80, 70, 60, or 50 degrees. 50 is least precise, hopefully most sensitive? // set up interrupts: // INT1 is on A4 which can wake from deep sleep. Wake on 6D orientation change. lis2dw_configure_int1(LIS2DW_CTRL4_INT1_6D); - watch_register_interrupt_callback(HAL_GPIO_A4_pin(), cb_motion_interrupt_1, INTERRUPT_TRIGGER_RISING); + watch_register_interrupt_callback(HAL_GPIO_A4_pin(), cb_motion_interrupt_1, INTERRUPT_TRIGGER_FALLING); // configure the accelerometer to fire INT2 when its sleep state changes. - lis2dw_configure_int2(LIS2DW_CTRL5_INT2_SLEEP_CHG); + lis2dw_configure_int2(LIS2DW_CTRL5_INT2_SLEEP_STATE | LIS2DW_CTRL5_INT2_SLEEP_CHG); // INT2 is wired to pin A3. set it up on the external interrupt controller. HAL_GPIO_A3_in(); HAL_GPIO_A3_pmuxen(HAL_GPIO_PMUX_EIC); diff --git a/movement_config.h b/movement_config.h index 49071a88..1fc7ca35 100644 --- a/movement_config.h +++ b/movement_config.h @@ -46,7 +46,7 @@ const watch_face_t watch_faces[] = { * Some folks also like to use this to hide the preferences and time set faces from the normal rotation. * If you don't want any faces to be excluded, set this to 0 and a long Mode press will have no effect. */ -#define MOVEMENT_SECONDARY_FACE_INDEX (MOVEMENT_NUM_FACES - 4) +#define MOVEMENT_SECONDARY_FACE_INDEX (MOVEMENT_NUM_FACES - 3) /* Custom hourly chime tune. Check movement_custom_signal_tunes.h for options. */ #define SIGNAL_TUNE_DEFAULT diff --git a/movement_faces.h b/movement_faces.h index a73cc9c8..eb8ff1d3 100644 --- a/movement_faces.h +++ b/movement_faces.h @@ -37,8 +37,10 @@ #include "float_demo_face.h" #include "temperature_display_face.h" #include "temperature_logging_face.h" +#include "activity_logging_face.h" #include "voltage_face.h" #include "set_time_face.h" #include "preferences_face.h" #include "light_sensor_face.h" +#include "accelerometer_sleep_state_face.h" // New includes go above this line. diff --git a/watch-faces.mk b/watch-faces.mk index f7aebaa4..55836d41 100644 --- a/watch-faces.mk +++ b/watch-faces.mk @@ -12,8 +12,10 @@ SRCS += \ ./watch-faces/demo/float_demo_face.c \ ./watch-faces/sensor/temperature_display_face.c \ ./watch-faces/sensor/temperature_logging_face.c \ + ./watch-faces/sensor/activity_logging_face.c \ ./watch-faces/sensor/voltage_face.c \ ./watch-faces/settings/set_time_face.c \ ./watch-faces/settings/preferences_face.c \ ./watch-faces/demo/light_sensor_face.c \ + ./watch-faces/demo/accelerometer_sleep_state_face.c \ # New watch faces go above this line. diff --git a/watch-faces/demo/accel_interrupt_count_face.c b/watch-faces/demo/accel_interrupt_count_face.c index 921892d6..5445a85a 100644 --- a/watch-faces/demo/accel_interrupt_count_face.c +++ b/watch-faces/demo/accel_interrupt_count_face.c @@ -75,7 +75,7 @@ bool accel_interrupt_count_face_loop(movement_event_t event, void *context) { char buf[11]; watch_display_text(WATCH_POSITION_TOP_RIGHT, " "); watch_display_text_with_fallback(WATCH_POSITION_TOP, "W_THS", "TH"); - watch_display_float_with_best_effort(state->new_threshold * 0.0625, " G"); + watch_display_float_with_best_effort(state->new_threshold * 0.03125, " G"); printf("%s\n", buf); } break; diff --git a/watch-faces/demo/accelerometer_sleep_state_face.c b/watch-faces/demo/accelerometer_sleep_state_face.c new file mode 100644 index 00000000..312b0fea --- /dev/null +++ b/watch-faces/demo/accelerometer_sleep_state_face.c @@ -0,0 +1,102 @@ +/* + * MIT License + * + * Copyright (c) 2024 Joey Castillo + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include "accelerometer_sleep_state_face.h" + +void accelerometer_sleep_state_face_setup(uint8_t watch_face_index, void ** context_ptr) { + (void) watch_face_index; + (void) context_ptr; +} + +void accelerometer_sleep_state_face_activate(void *context) { + (void) context; + movement_request_tick_frequency(8); +} + +bool accelerometer_sleep_state_face_loop(movement_event_t event, void *context) { + (void) context; + char buf[7]; + char state[4]; + + switch (event.event_type) { + case EVENT_ACTIVATE: + watch_display_text(WATCH_POSITION_TOP_LEFT, "A3"); + watch_set_colon(); + // fall through + case EVENT_TICK: + if (HAL_GPIO_A3_read()) { + strcpy(state, "SLP"); + printf("1\n"); + } else { + strcpy(state, "Act"); + printf("0\n"); + } + sprintf(buf, "ac %s", state); + // printf("%s\n", buf); + watch_display_text(WATCH_POSITION_BOTTOM, buf); + // If needed, update your display here. + break; + case EVENT_LIGHT_BUTTON_UP: + // You can use the Light button for your own purposes. Note that by default, Movement will also + // illuminate the LED in response to EVENT_LIGHT_BUTTON_DOWN; to suppress that behavior, add an + // empty case for EVENT_LIGHT_BUTTON_DOWN. + break; + case EVENT_ALARM_BUTTON_UP: + // Just in case you have need for another button. + break; + case EVENT_TIMEOUT: + // Your watch face will receive this event after a period of inactivity. If it makes sense to resign, + // you may uncomment this line to move back to the first watch face in the list: + // movement_move_to_face(0); + break; + case EVENT_LOW_ENERGY_UPDATE: + // If you did not resign in EVENT_TIMEOUT, you can use this event to update the display once a minute. + // Avoid displaying fast-updating values like seconds, since the display won't update again for 60 seconds. + // You should also consider starting the tick animation, to show the wearer that this is sleep mode: + // watch_start_sleep_animation(500); + break; + default: + // Movement's default loop handler will step in for any cases you don't handle above: + // * EVENT_LIGHT_BUTTON_DOWN lights the LED + // * EVENT_MODE_BUTTON_UP moves to the next watch face in the list + // * EVENT_MODE_LONG_PRESS returns to the first watch face (or skips to the secondary watch face, if configured) + // You can override any of these behaviors by adding a case for these events to this switch statement. + return movement_default_loop_handler(event); + } + + // return true if the watch can enter standby mode. Generally speaking, you should always return true. + // Exceptions: + // * If you are displaying a color using the low-level watch_set_led_color function, you should return false. + // * If you are sounding the buzzer using the low-level watch_set_buzzer_on function, you should return false. + // Note that if you are driving the LED or buzzer using Movement functions like movement_illuminate_led or + // movement_play_alarm, you can still return true. This guidance only applies to the low-level watch_ functions. + return true; +} + +void accelerometer_sleep_state_face_resign(void *context) { + (void) context; +} + diff --git a/watch-faces/demo/accelerometer_sleep_state_face.h b/watch-faces/demo/accelerometer_sleep_state_face.h new file mode 100644 index 00000000..008c3271 --- /dev/null +++ b/watch-faces/demo/accelerometer_sleep_state_face.h @@ -0,0 +1,47 @@ +/* + * MIT License + * + * Copyright (c) 2024 Joey Castillo + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#pragma once + +#include "movement.h" + +/* + * A DESCRIPTION OF YOUR WATCH FACE + * + * and a description of how use it + * + */ + +void accelerometer_sleep_state_face_setup(uint8_t watch_face_index, void ** context_ptr); +void accelerometer_sleep_state_face_activate(void *context); +bool accelerometer_sleep_state_face_loop(movement_event_t event, void *context); +void accelerometer_sleep_state_face_resign(void *context); + +#define accelerometer_sleep_state_face ((const watch_face_t){ \ + accelerometer_sleep_state_face_setup, \ + accelerometer_sleep_state_face_activate, \ + accelerometer_sleep_state_face_loop, \ + accelerometer_sleep_state_face_resign, \ + NULL, \ +}) diff --git a/watch-faces/sensor/activity_logging_face.c b/watch-faces/sensor/activity_logging_face.c new file mode 100644 index 00000000..b0411c2e --- /dev/null +++ b/watch-faces/sensor/activity_logging_face.c @@ -0,0 +1,154 @@ +/* + * MIT License + * + * Copyright (c) 2022 Joey Castillo + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include "activity_logging_face.h" +#include "watch.h" + +#ifdef HAS_ACCELEROMETER + +static void _activity_logging_face_log_data(activity_logging_state_t *state) { + watch_date_time_t date_time = watch_rtc_get_date_time(); + size_t pos = state->data_points % ACTIVITY_LOGGING_NUM_DATA_POINTS; + + state->data[pos].timestamp.reg = date_time.reg; + state->data[pos].active_minutes = state->active_minutes; + state->active_minutes = 0; + + state->data_points++; +} + +static void _activity_logging_face_update_display(activity_logging_state_t *state, bool clock_mode_24h) { + int8_t pos = (state->data_points - 1 - state->display_index) % ACTIVITY_LOGGING_NUM_DATA_POINTS; + char buf[7]; + + watch_clear_indicator(WATCH_INDICATOR_24H); + watch_clear_indicator(WATCH_INDICATOR_PM); + watch_clear_colon(); + + if (pos < 0) { + // no data at this index + watch_display_text_with_fallback(WATCH_POSITION_TOP, "ACT L", "AC"); + watch_display_text(WATCH_POSITION_BOTTOM, "no dat"); + sprintf(buf, "%2d", state->display_index); + watch_display_text(WATCH_POSITION_TOP_RIGHT, buf); + } else if (state->ts_ticks) { + // we are displaying the timestamp in response to a button press + watch_date_time_t date_time = state->data[pos].timestamp; + watch_set_colon(); + if (clock_mode_24h) { + watch_set_indicator(WATCH_INDICATOR_24H); + } else { + if (date_time.unit.hour > 11) watch_set_indicator(WATCH_INDICATOR_PM); + date_time.unit.hour %= 12; + if (date_time.unit.hour == 0) date_time.unit.hour = 12; + } + watch_display_text(WATCH_POSITION_TOP_LEFT, "AT"); + sprintf(buf, "%2d", date_time.unit.day); + watch_display_text(WATCH_POSITION_TOP_RIGHT, buf); + sprintf(buf, "%2d%02d%02d", date_time.unit.hour, date_time.unit.minute, date_time.unit.second); + watch_display_text(WATCH_POSITION_BOTTOM, buf); + } else { + // we are displaying the number of accelerometer wakeups + watch_display_text_with_fallback(WATCH_POSITION_TOP, "ACT L", "AC"); + sprintf(buf, "%2d", state->display_index); + watch_display_text(WATCH_POSITION_TOP_RIGHT, buf); + sprintf(buf, "%d", state->data[pos].active_minutes); + watch_display_text(WATCH_POSITION_BOTTOM, buf); + } +} + +void activity_logging_face_setup(uint8_t watch_face_index, void ** context_ptr) { + (void) watch_face_index; + if (*context_ptr == NULL) { + *context_ptr = malloc(sizeof(activity_logging_state_t)); + memset(*context_ptr, 0, sizeof(activity_logging_state_t)); + } +} + +void activity_logging_face_activate(void *context) { + activity_logging_state_t *state = (activity_logging_state_t *)context; + state->display_index = 0; + state->ts_ticks = 0; +} + +bool activity_logging_face_loop(movement_event_t event, void *context) { + activity_logging_state_t *state = (activity_logging_state_t *)context; + switch (event.event_type) { + case EVENT_TIMEOUT: + movement_move_to_face(0); + break; + case EVENT_LIGHT_LONG_PRESS: + // light button shows the timestamp, but if you need the light, long press it. + movement_illuminate_led(); + break; + case EVENT_LIGHT_BUTTON_DOWN: + state->ts_ticks = 2; + _activity_logging_face_update_display(state, movement_clock_mode_24h()); + break; + case EVENT_ALARM_BUTTON_DOWN: + state->display_index = (state->display_index + 1) % ACTIVITY_LOGGING_NUM_DATA_POINTS; + state->ts_ticks = 0; + // fall through + case EVENT_ACTIVATE: + _activity_logging_face_update_display(state, movement_clock_mode_24h()); + break; + case EVENT_TICK: + if (state->ts_ticks && --state->ts_ticks == 0) { + _activity_logging_face_update_display(state, movement_clock_mode_24h()); + } + break; + case EVENT_BACKGROUND_TASK: + _activity_logging_face_log_data(state); + break; + default: + movement_default_loop_handler(event); + break; + } + + return true; +} + +void activity_logging_face_resign(void *context) { + (void) context; +} + +movement_watch_face_advisory_t activity_logging_face_advise(void *context) { + activity_logging_state_t *state = (activity_logging_state_t *)context; + movement_watch_face_advisory_t retval = { 0 }; + + // every minute, we want to log whether the accelerometer is alseeep or awake. + if (!HAL_GPIO_A3_read()) { + state->active_minutes++; + } + + // this will get called at the top of each minute, so all we check is if we're at the top of the hour as well. + // if we are, we ask for a background task. + retval.wants_background_task = watch_rtc_get_date_time().unit.minute == 0; + + return retval; +} + +#endif diff --git a/watch-faces/sensor/activity_logging_face.h b/watch-faces/sensor/activity_logging_face.h new file mode 100644 index 00000000..2d73c4bd --- /dev/null +++ b/watch-faces/sensor/activity_logging_face.h @@ -0,0 +1,71 @@ +/* + * MIT License + * + * Copyright (c) 2022 Joey Castillo + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#pragma once + +#include "pins.h" + +#ifdef HAS_ACCELEROMETER + +/* + * ACTIVITY LOGGING + * + * This watch face works with Movement's built-in count of accelerometer + * waekeups to log activity over time. It is very much a work in progress, + * and these notes will expand as the functionality is fleshed out. + */ + +#include "movement.h" +#include "watch.h" + +#define ACTIVITY_LOGGING_NUM_DATA_POINTS (36) + +typedef struct { + watch_date_time_t timestamp; + uint8_t active_minutes; +} activity_logging_data_point_t; + +typedef struct { + uint8_t display_index; // the index we are displaying on screen + uint8_t ts_ticks; // when the user taps the LIGHT button, we show the timestamp for a few ticks. + uint8_t active_minutes; // Active minutes this hour + int32_t data_points; // the absolute number of data points logged + activity_logging_data_point_t data[ACTIVITY_LOGGING_NUM_DATA_POINTS]; +} activity_logging_state_t; + +void activity_logging_face_setup(uint8_t watch_face_index, void ** context_ptr); +void activity_logging_face_activate(void *context); +bool activity_logging_face_loop(movement_event_t event, void *context); +void activity_logging_face_resign(void *context); +movement_watch_face_advisory_t activity_logging_face_advise(void *context); + +#define activity_logging_face ((const watch_face_t){ \ + activity_logging_face_setup, \ + activity_logging_face_activate, \ + activity_logging_face_loop, \ + activity_logging_face_resign, \ + activity_logging_face_advise, \ +}) + +#endif // HAS_TEMPERATURE_SENSOR