diff options
-rw-r--r-- | movement/movement.c | 4 | ||||
-rw-r--r-- | movement/movement.h | 93 | ||||
-rw-r--r-- | movement/watch_faces/clock/simple_clock_face.c | 8 | ||||
-rw-r--r-- | movement/watch_faces/clock/simple_clock_face.h | 1 | ||||
-rw-r--r-- | movement/watch_faces/complications/pulseometer_face.h | 1 | ||||
-rw-r--r-- | movement/watch_faces/settings/preferences_face.h | 1 | ||||
-rw-r--r-- | movement/watch_faces/settings/set_time_face.h | 1 |
7 files changed, 102 insertions, 7 deletions
diff --git a/movement/movement.c b/movement/movement.c index 03ddb244..d43285e1 100644 --- a/movement/movement.c +++ b/movement/movement.c @@ -144,7 +144,7 @@ bool app_loop() { // this is a little mini-runloop. // as long as le_mode_ticks is -1 (i.e. we are in low energy mode), we wake up here, update the screen, and go right back to sleep. while (movement_state.le_mode_ticks == -1) { - event.event_type = EVENT_LOW_POWER_TICK; + event.event_type = EVENT_LOW_ENERGY_UPDATE; watch_faces[movement_state.current_watch_face].loop(event, &movement_state.settings, watch_face_contexts[movement_state.current_watch_face]); watch_enter_shallow_sleep(true); } @@ -201,7 +201,7 @@ void cb_alarm_btn_extwake() { } void cb_alarm_fired() { - event.event_type = EVENT_LOW_POWER_TICK; + event.event_type = EVENT_LOW_ENERGY_UPDATE; } void cb_tick() { diff --git a/movement/movement.h b/movement/movement.h index 0ae0cc13..3f3f0365 100644 --- a/movement/movement.h +++ b/movement/movement.h @@ -21,7 +21,8 @@ typedef enum { EVENT_NONE = 0, // There is no event to report. EVENT_ACTIVATE, // Your watch face is entering the foreground. EVENT_TICK, // Most common event type. Your watch face is being called from the tick callback. - EVENT_LOW_POWER_TICK, // The watch is in low energy mode, and you are getting the once-per-minute tick callback. + EVENT_LOW_ENERGY_UPDATE, // If the watch is in low energy mode and you are in the foreground, you will get a chance to update the display once per minute. + EVENT_BACKGROUND_TASK, // Your watch face is being invoked to perform a background task. Don't update the display here; you may not be in the foreground. EVENT_LIGHT_BUTTON_DOWN, // The light button has been pressed, but not yet released. EVENT_LIGHT_BUTTON_UP, // The light button was pressed and released. EVENT_LIGHT_LONG_PRESS, // The light button was held for >2 seconds, and released. @@ -38,16 +39,106 @@ typedef struct { uint8_t subsecond; } movement_event_t; +/** @brief Perform setup for your watch face. + * @details It's tempting to say this is 'one-time' setup, but technically this function is called more than + * once. When the watch first boots, this function is called with a NULL context_ptr, indicating + * that it is the first run. At this time you should set context_ptr to something non-NULL if you + * need to keep track of any state in your watch face. If your watch face requires any other setup, + * like configuring a pin mode or a peripheral, you may want to do that here too. + * This function will be called again after waking from sleep mode, since sleep mode disables all + * of the device's pins and peripherals. + * @param settings A pointer to the global Movement settings. You can use this to inform how you present your + * display to the user (i.e. taking into account whether they have silenced the buttons, or if + * they prefer 12 or 24-hour mode). You can also change these settings if you like. + * @param context_ptr A pointer to a pointer; at first invocation, this value will be NULL, and you can set it + * to any value you like. Subsequent invocations will pass in whatever value you previously + * set. You may want to check if this is NULL and if so, allocate some space to store any + * data required for your watch face. + * + */ typedef void (*watch_face_setup)(movement_settings_t *settings, void ** context_ptr); + +/** @brief Prepare to go on-screen. + * @details This function is called just before your watch enters the foreground. If your watch face has any + * segments or text that is always displayed, you may want to set that here. In addition, if your + * watch face depends on data from a peripheral (like an I2C sensor), you will likely want to enable + * that peripheral here. In addition, if your watch face requires an update frequncy other than 1 Hz, + * you may want to request that here using the movement_request_tick_frequency function. + * @param settings A pointer to the global Movement settings. @see watch_face_setup. + * @param context A pointer to your watch face's context. @see watch_face_setup. + * + */ typedef void (*watch_face_activate)(movement_settings_t *settings, void *context); + +/** @brief Handle events and update the display. + * @details This function is called in response to an event. You should set up a switch statement that handles, + * at the very least, the EVENT_TICK and EVENT_MODE_BUTTON_UP event types. The tick event happens once + * per second (or more frequently if you asked for a faster tick with movement_request_tick_frequency). + * The mode button up event occurs when the user presses the MODE button. **Your loop function SHOULD + * call the movement_move_to_next_face function in response to this event.** If you have a good reason + * to override this behavior (e.g. your user interface requires all three buttons), your watch face MUST + * call the movement_move_to_next_face function in response to the EVENT_MODE_LONG_PRESS event. If you + * fail to do this, the user will become stuck on your watch face. + * @param event A struct containing information about the event, including its type. @see movement_event_type_t + * for a list of all possible event types. + * @param settings A pointer to the global Movement settings. @see watch_face_setup. + * @param context A pointer to your application's context. @see watch_face_setup. + * @return true if Movement can enter STANDBY mode; false to keep it awake. You should almost always return true. + * @note There are two event types that require some extra thought: + The EVENT_LOW_ENERGY_UPDATE event type is a special case. If you are in the foreground when the watch + goes into low energy mode, you will receive this tick once a minute (at the top of the minute) so that + you can update the screen. Great! But! When you receive this event, all pins and peripherals other than + the RTC will have been disabled to save energy. If your display is clock or calendar oriented, this is + fine. But if your display requires polling an I2C sensor or reading a value with the ADC, you won't be + able to do this. You should either display the name of the watch face in response to the low power tick, + or ensure that you resign before low power mode triggers, (e.g. by calling movement_move_to_face(0)). + **Your watch face MUST NOT wake up peripherals in response to a low power tick.** The purpose of this + mode is to consume as little energy as possible during the (potentially long) intervals when it's + unlikely the user is wearing or looking at the watch. + EVENT_BACKGROUND_TASK is also a special case. @see watch_face_wants_background_task for details. + */ typedef bool (*watch_face_loop)(movement_event_t event, movement_settings_t *settings, void *context); + +/** @brief Prepare to go off-screen. + * @details This function is called before your watch face enters the background. If you requested a tick + * frequency other than the standard 1 Hz, **you must call movement_request_tick_frequency(1) here** + * to reset to 1 Hz. You should also disable any peripherals you enabled when you entered the foreground. + * @param settings A pointer to the global Movement settings. @see watch_face_setup. + * @param context A pointer to your application's context. @see watch_face_setup. + */ typedef void (*watch_face_resign)(movement_settings_t *settings, void *context); +/** @brief OPTIONAL. Request an opportunity to run a background task. + * @warning NOT YET IMPLEMENTED. + * @details Most apps will not need this function, but if you provide it, Movement will call it once per minute in + * both active and low power modes, regardless of whether your app is in the foreground. You can check the + * current time to determine whether you require a background task. If you return true here, Movement will + * immediately call your loop function with an EVENT_BACKGROUND_TASK event. Note that it will not call your + * activate or deactivate functions, since you are not going on screen. + * + * Examples of background tasks: + * - Wake and play a sound when an alarm or timer has been triggered. + * - Check the state of an RTC interrupt pin or the timestamp of an RTC interrupt event. + * - Log a data point from a sensor, and then return to sleep mode. + * + * Guidelines for background tasks: + * - Assume all peripherals and pins other than the RTC will be disabled when you get an EVENT_BACKGROUND_TASK. + * - Even if your background task involves only the RTC peripheral, try to request background tasks sparingly. + * - If your background task involves an external pin or peripheral, request background tasks no more than once per hour. + * - If you need to enable a pin or a peripheral to perform your task, return it to its original state afterwards. + * + * @param settings A pointer to the global Movement settings. @see watch_face_setup. + * @param context A pointer to your application's context. @see watch_face_setup. + * @return true to request a background task; false otherwise. + */ +typedef bool (*watch_face_wants_background_task)(movement_settings_t *settings, void *context); + typedef struct { watch_face_setup setup; watch_face_activate activate; watch_face_loop loop; watch_face_resign resign; + watch_face_wants_background_task wants_background_task; } watch_face_t; typedef struct { diff --git a/movement/watch_faces/clock/simple_clock_face.c b/movement/watch_faces/clock/simple_clock_face.c index 6e58313a..351c7ebc 100644 --- a/movement/watch_faces/clock/simple_clock_face.c +++ b/movement/watch_faces/clock/simple_clock_face.c @@ -28,16 +28,16 @@ bool simple_clock_face_loop(movement_event_t event, movement_settings_t *setting switch (event.event_type) { case EVENT_ACTIVATE: case EVENT_TICK: - case EVENT_LOW_POWER_TICK: + case EVENT_LOW_ENERGY_UPDATE: date_time = watch_rtc_get_date_time(); previous_date_time = *((uint32_t *)context); *((uint32_t *)context) = date_time.reg; - if (date_time.reg >> 6 == previous_date_time >> 6 && event.event_type != EVENT_LOW_POWER_TICK) { + if (date_time.reg >> 6 == previous_date_time >> 6 && event.event_type != EVENT_LOW_ENERGY_UPDATE) { // everything before seconds is the same, don't waste cycles setting those segments. pos = 8; sprintf(buf, "%02d", date_time.unit.second); - } else if (date_time.reg >> 12 == previous_date_time >> 12 && event.event_type != EVENT_LOW_POWER_TICK) { + } else if (date_time.reg >> 12 == previous_date_time >> 12 && event.event_type != EVENT_LOW_ENERGY_UPDATE) { // everything before minutes is the same. pos = 6; sprintf(buf, "%02d%02d", date_time.unit.minute, date_time.unit.second); @@ -54,7 +54,7 @@ bool simple_clock_face_loop(movement_event_t event, movement_settings_t *setting if (date_time.unit.hour == 0) date_time.unit.hour = 12; } pos = 0; - if (event.event_type == EVENT_LOW_POWER_TICK) { + if (event.event_type == EVENT_LOW_ENERGY_UPDATE) { sprintf(buf, "%s%2d%2d%02d ", weekdays[simple_clock_face_get_weekday(date_time.unit.year, date_time.unit.month, date_time.unit.day)], date_time.unit.day, date_time.unit.hour, date_time.unit.minute); } else { sprintf(buf, "%s%2d%2d%02d%02d", weekdays[simple_clock_face_get_weekday(date_time.unit.year, date_time.unit.month, date_time.unit.day)], date_time.unit.day, date_time.unit.hour, date_time.unit.minute, date_time.unit.second); diff --git a/movement/watch_faces/clock/simple_clock_face.h b/movement/watch_faces/clock/simple_clock_face.h index 3cdc09e8..7daaba3e 100644 --- a/movement/watch_faces/clock/simple_clock_face.h +++ b/movement/watch_faces/clock/simple_clock_face.h @@ -15,6 +15,7 @@ uint8_t simple_clock_face_get_weekday(uint16_t day, uint16_t month, uint16_t yea simple_clock_face_activate, \ simple_clock_face_loop, \ simple_clock_face_resign, \ + NULL, \ } #endif // FAKE_FACE_H_
\ No newline at end of file diff --git a/movement/watch_faces/complications/pulseometer_face.h b/movement/watch_faces/complications/pulseometer_face.h index f8e69d27..5a9c66b0 100644 --- a/movement/watch_faces/complications/pulseometer_face.h +++ b/movement/watch_faces/complications/pulseometer_face.h @@ -19,6 +19,7 @@ void pulseometer_face_resign(movement_settings_t *settings, void *context); pulseometer_face_activate, \ pulseometer_face_loop, \ pulseometer_face_resign, \ + NULL, \ } #endif // PULSEOMETER_FACE_H_
\ No newline at end of file diff --git a/movement/watch_faces/settings/preferences_face.h b/movement/watch_faces/settings/preferences_face.h index c8f1a14e..5b735db9 100644 --- a/movement/watch_faces/settings/preferences_face.h +++ b/movement/watch_faces/settings/preferences_face.h @@ -13,6 +13,7 @@ void preferences_face_resign(movement_settings_t *settings, void *context); preferences_face_activate, \ preferences_face_loop, \ preferences_face_resign, \ + NULL, \ } #endif // PREFERENCES_FACE_H_
\ No newline at end of file diff --git a/movement/watch_faces/settings/set_time_face.h b/movement/watch_faces/settings/set_time_face.h index b330d852..06dbe2ce 100644 --- a/movement/watch_faces/settings/set_time_face.h +++ b/movement/watch_faces/settings/set_time_face.h @@ -13,6 +13,7 @@ void set_time_face_resign(movement_settings_t *settings, void *context); set_time_face_activate, \ set_time_face_loop, \ set_time_face_resign, \ + NULL, \ } #endif // SET_TIME_FACE_H_ |