WIP: accelerometer activity tracking

This commit is contained in:
Joey Castillo 2025-04-20 13:47:04 -04:00
parent 4b490bed4c
commit e435969b51
9 changed files with 41 additions and 21 deletions

View File

@ -78,6 +78,7 @@ void cb_tick(void);
void cb_accelerometer_event(void);
void cb_accelerometer_wake(void);
uint8_t stationary_minutes = 0;
uint8_t active_minutes = 0;
#endif
#if __EMSCRIPTEN__
@ -155,23 +156,30 @@ static inline void _movement_disable_fast_tick_if_possible(void) {
static void _movement_handle_top_of_minute(void) {
watch_date_time_t date_time = watch_rtc_get_date_time();
static const uint8_t stationary_minutes_for_sleep = 2;
#ifdef HAS_ACCELEROMETER
if (stationary_minutes < 5) {
// if the watch has been stationary for fewer than 5 minutes, find out if it's still stationary.
if (HAL_GPIO_A4_read()) stationary_minutes++;
bool accelerometer_is_alseep = HAL_GPIO_A4_read();
if (!accelerometer_is_alseep) active_minutes++;
printf("Active minutes: %d\n", active_minutes);
if (stationary_minutes < 2) {
// if the watch has been stationary for fewer minutes than the cutoff, find out if it's still stationary.
if (accelerometer_is_alseep) stationary_minutes++;
printf("Stationary minutes: %d\n", stationary_minutes);
// does this mark five stationary minutes? and are we not already asleep?
if (stationary_minutes == 5 && movement_state.le_mode_ticks != -1) {
// should we go to sleep? and are we not already asleep?
if (stationary_minutes >= stationary_minutes_for_sleep && movement_state.le_mode_ticks != -1) {
// if so, enter low energy mode.
printf("Entering low energy mode due to inactivity.\n");
movement_request_sleep();
}
}
// log data every five minutes, and reset the active_minutes count.
if ((date_time.unit.minute % 5) == 0) {
_movement_log_data();
active_minutes = 0;
}
#endif
@ -718,7 +726,7 @@ void app_setup(void) {
lis2dw_enable_stationary_motion_detection(); // stationary/motion detection mode keeps the data rate at 1.6 Hz even in sleep
lis2dw_set_range(LIS2DW_RANGE_2_G); // Application note AN5038 recommends 2g range
lis2dw_enable_sleep(); // allow acceleromter to sleep and wake on activity
lis2dw_configure_wakeup_threshold(8); // g threshold to wake up: (2 * FS / 64) where FS is "full scale" of ±2g.
lis2dw_configure_wakeup_threshold(32); // g threshold to wake up: (THS * 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:

View File

@ -34,16 +34,16 @@ movement_activity_data_point movement_activity_log[MOVEMENT_NUM_DATA_POINTS] = {
// the absolute number of data points logged
uint32_t data_points = 0;
// hacky: we're just tapping into Movement's global state for stationary detection.
// hacky: we're just tapping into Movement's global state for activity detection.
// do we need better API for this? i'm less bothered now that it's all in Movement.
extern uint8_t stationary_minutes;
extern uint8_t active_minutes;
void _movement_log_data(void) {
size_t pos = data_points % MOVEMENT_NUM_DATA_POINTS;
movement_activity_data_point data_point = {0};
// Movement tracks stationary minutes when deciding whether to sleep.
data_point.bit.stationary_minutes = stationary_minutes;
// Movement tracks active minutes when deciding whether to sleep.
data_point.bit.active_minutes = active_minutes;
// orientation changes are counted in TC2. stash them in the data point...
data_point.bit.orientation_changes = tc_count16_get_count(2);

View File

@ -33,7 +33,7 @@
typedef union {
struct {
uint32_t stationary_minutes: 3;
uint32_t active_minutes: 3;
uint32_t orientation_changes: 9;
uint32_t measured_temperature: 10;
uint32_t measured_light: 10;

View File

@ -37,6 +37,7 @@ const watch_face_t watch_faces[] = {
activity_logging_face,
voltage_face,
days_since_face,
accel_interrupt_count_face,
preferences_face,
set_time_face,
};
@ -49,7 +50,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 - 5)
/* Custom hourly chime tune. Check movement_custom_signal_tunes.h for options. */
#define SIGNAL_TUNE_DEFAULT

View File

@ -33,7 +33,7 @@
// hacky: we're just tapping into Movement's global state.
// we should make better API for this.
extern uint8_t stationary_minutes;
extern uint8_t active_minutes;
static void _accel_interrupt_count_face_update_display(accel_interrupt_count_state_t *state) {
(void) state;
@ -48,7 +48,7 @@ static void _accel_interrupt_count_face_update_display(accel_interrupt_count_sta
// Orientation changes / active minutes
uint16_t orientation_changes = tc_count16_get_count(2);
sprintf(buf, "%-3u/%2d", orientation_changes > 999 ? 999 : orientation_changes, stationary_minutes);
sprintf(buf, "%-3u/%2d", orientation_changes > 999 ? 999 : orientation_changes, active_minutes);
watch_display_text(WATCH_POSITION_BOTTOM, buf);
}
@ -57,9 +57,6 @@ void accel_interrupt_count_face_setup(uint8_t watch_face_index, void ** context_
if (*context_ptr == NULL) {
*context_ptr = malloc(sizeof(accel_interrupt_count_state_t));
memset(*context_ptr, 0, sizeof(accel_interrupt_count_state_t));
accel_interrupt_count_state_t *state = (accel_interrupt_count_state_t *)*context_ptr;
/// TODO: hook up to movement methods for tracking threshold
state->threshold = 8;
}
}
@ -71,6 +68,9 @@ void accel_interrupt_count_face_activate(void *context) {
// update more quickly to catch changes, also to blink setting
movement_request_tick_frequency(4);
// fetch current threshold from accelerometer
state->threshold = lis2dw_get_wakeup_threshold();
}
bool accel_interrupt_count_face_loop(movement_event_t event, void *context) {
@ -88,7 +88,7 @@ bool accel_interrupt_count_face_loop(movement_event_t event, void *context) {
watch_display_text(WATCH_POSITION_BOTTOM, " ");
} else {
watch_display_text(WATCH_POSITION_TOP_RIGHT, " ");
watch_display_text_with_fallback(WATCH_POSITION_TOP, "W_THS", "TH");
watch_display_text_with_fallback(WATCH_POSITION_TOP, "WAKth", "TH");
watch_display_float_with_best_effort(state->new_threshold * 0.03125, " G");
printf("%s\n", buf);
}

View File

@ -76,7 +76,7 @@ static void _activity_logging_face_update_display(activity_logging_state_t *stat
watch_display_text_with_fallback(WATCH_POSITION_TOP_LEFT, "LOG", "AC");
sprintf(buf, "%2d", state->display_index);
watch_display_text(WATCH_POSITION_TOP_RIGHT, buf);
sprintf(buf, "%-3u/%2d", data_points[pos].bit.orientation_changes > 999 ? 999 : data_points[pos].bit.orientation_changes, data_points[pos].bit.stationary_minutes);
sprintf(buf, "%-3u/%2d", data_points[pos].bit.orientation_changes > 999 ? 999 : data_points[pos].bit.orientation_changes, data_points[pos].bit.active_minutes);
watch_display_text(WATCH_POSITION_BOTTOM, buf);
}
}
@ -140,7 +140,7 @@ bool activity_logging_face_loop(movement_event_t event, void *context) {
sprintf(buf, "%3d%3d", data_points[pos].bit.measured_temperature - 300, data_points[pos].bit.orientation_changes > 999 ? 999 : data_points[pos].bit.orientation_changes);
buf[6] = 0;
watch_display_text(WATCH_POSITION_BOTTOM, buf);
sprintf(buf, "%2d", data_points[pos].bit.stationary_minutes);
sprintf(buf, "%2d", data_points[pos].bit.active_minutes);
watch_display_text(WATCH_POSITION_TOP_RIGHT, buf);
state->data_dump_idx++;

View File

@ -129,6 +129,8 @@ static void _watch_disable_all_pins_except_rtc(void) {
uint32_t config = RTC->MODE0.TAMPCTRL.reg;
uint32_t portb_pins_to_disable = 0xFFFFFFFF;
/// FIXME: Watch library shouldn't be responsible for this, but ovement uses PB03 for orientation tracking. Keep it on.
portb_pins_to_disable &= 0xFFFFFFF7;
// if there's an action set on RTC/IN[0], leave PB00 configured
if (config & RTC_TAMPCTRL_IN0ACT_Msk) portb_pins_to_disable &= 0xFFFFFFFE;
// same with RTC/IN[1] and PB02
@ -150,7 +152,10 @@ static void _watch_disable_all_pins_except_rtc(void) {
static void _watch_disable_all_peripherals_except_slcd(void) {
_watch_disable_tcc();
watch_disable_adc();
watch_disable_external_interrupts();
/// FIXME: I just disabled this next line since we need the EIC's event system connection to count orientation changes.
// The TODO item: need to power profile the impact of keeping EIC enabled, as well as the UI implications.
// watch_disable_external_interrupts();
/// TODO: Actually disable all these peripherals! #SecondMovement
// watch_disable_i2c();
// SERCOM3->USART.CTRLA.reg &= ~SERCOM_USART_CTRLA_ENABLE;

View File

@ -288,3 +288,7 @@ lis2dw_wakeup_source_t lis2dw_get_wakeup_source() {
lis2dw_interrupt_source_t lis2dw_get_interrupt_source(void) {
return (lis2dw_interrupt_source_t) watch_i2c_read8(LIS2DW_ADDRESS, LIS2DW_REG_ALL_INT_SRC);
}
uint8_t lis2dw_get_wakeup_threshold(void) {
return watch_i2c_read8(LIS2DW_ADDRESS, LIS2DW_REG_WAKE_UP_THS) & 0b00111111;
}

View File

@ -375,4 +375,6 @@ lis2dw_interrupt_source_t lis2dw_get_interrupt_source(void);
lis2dw_wakeup_source_t lis2dw_get_wakeup_source(void);
uint8_t lis2dw_get_wakeup_threshold(void);
#endif // LIS2DW_H