early work on background tasks, documentation

This commit is contained in:
Joey Castillo 2021-10-16 16:03:27 -04:00
parent 458ebf6987
commit 0cfb37c671
7 changed files with 102 additions and 7 deletions

View file

@ -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() {

View file

@ -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 {

View file

@ -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);

View file

@ -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_

View file

@ -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_

View file

@ -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_

View file

@ -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_