Open rieck opened 11 months ago
Am now running this code on real hardware. Tracking two deadlines right now. No issues.
I was having high power usage issues on my watch and although I initially suspected it might have something to do with silicon errata workaround code I eventually isolated the problem to this watch face by disassembling and resetting the watch and then testing each face until sleep mode no longer worked: it happened after a deadline was set. It reduced battery voltage from 2.97 V to 2.91 V in a span of about 18 hours.
I will review this PR again in order to try and figure out what the problem is. Do you have any ideas why this might be happening?
Oh, that's a nasty surprise. I have been running the face for over 6 months on my watch, but have not monitored the voltage. Most of the time, the face was in the background. Did you have it in the foreground when you measured the voltage drop?
Guess 1: If the face was in the foreground, I already have some ideas about where the problem is coming from and how to mitigate it. The face calculates the remaining time with different granularity when in the foreground. This can be a bit involved due to the complexity of the Gregorian calendar. I can think of two improvements:
If the time remaining is less than 28 days, we can do the calculations directly without calling conversion functions, since all months have less than 28 days. The code is already quite lean.
If the granularity of the watch face is changed to hours or days, we can cache the display buffer and update it only once per hour or day. This should be easy, if we borrow a bit of memory for the display buffer cache.
Guess 2: Before I get into the above optimizations, however, I want to rule out other issues. I could also imagine that this is a problem with the tick frequency. The calculations for time remaining should happen at 1 Hz, which might be acceptable (I don't know). However, if for some reason, the clock is still running at 4 Hz or more we would definitely waste a lot of energy with unnecessary computations.
I hope to have some time this weekend.
Most of the time, the face was in the background. Did you have it in the foreground when you measured the voltage drop?
In my case, the face was in the background. I set a deadline until my birthday and then moved on to test other faces and finetune the watch. I set it down for a bit and when I came back I noticed it was not in sleep mode: the clock face still displayed the seconds.
So I hacked in a 10 second low energy timer, reassembled the watch and tested the faces one by one by using it, moving back to the clock and waiting for the low power tick tock animation. Verified that sleep no longer engaged after setting a deadline.
I looked at the code again and saw that it was using scheduled tasks in the background. I don't think those are supposed to prevent sleep though, at least that's my impression from reading movement.c
. Your face's loops also seem to return true
on all paths.
Can you reproduce this issue? Or does your watch enter sleep mode normally? I'm asking because I flashed a branch with more features than just this PR. They might be interacting with each other.
Yes, I can reproduce the problem. 😢
The watch no longer goes into sleep mode when a deadline is set. I assume this is a bug introduced with some other changes to movement. At least, I don't remember seeing this behavior when the watch face was first developed.
Unfortunately, it's a showstopper as of now. We need to figure out what's going on and prevent entering sleep mode. I will have a look at this over the weekend.
At least, I don't remember seeing this behavior when the watch face was first developed.
Which commit was it originally based on?
That is surprisingly hard to say. I have two watches and merged in the changes from main
at irregular intervals on both. I suspect the problem was not present in August 2023 (fd2c8c20650c9d2ff332e5da55e0cdd943cf5cc3). I later had only short deadlines on the watches (a couple of days).
I spent a little time investigating this problem. We have two independent issues here: (a) the watch face does not enter sleep mode, and (b) the battery is draining badly.
(a) I did some research, and movement cannot enter sleep mode when a task is still scheduled. In this case, the main loop calls movement_handle_scheduled_tasks
on every tick. This function calls movement_reset_inactivity_countdown,
which resets the sleep timer every time.
I have slightly changed the implementation of the deadline face so that the user can now enable or disable the alarm function (long press on the light button). When the alarm function is enabled, an alarm will sound when a deadline is reached. This requires a background task, and sleep mode is not available. However, no task is scheduled if the alarm function is deactivated and sleep mode works as expected.
(b) I ran the face with a deadline set and the alarm disabled on my RED board for 8 hours and could not observe any change in voltage. I repeated the test for 8 hours with alarm enabled. Again, no voltage drop could be detected.
I suppose the face is not draining the battery, and what you have observed is a result of other patches to the deployed firmware. We didn't immediately realize there were two problems because we took the absence of sleep mode as an indication of this problem, which is not the case.
(a) I did some research, and movement cannot enter sleep mode when a task is still scheduled. In this case, the main loop calls
movement_handle_scheduled_tasks
on every tick. This function callsmovement_reset_inactivity_countdown,
which resets the sleep timer every time.
I see. Thanks, I didn't catch that. I wonder why that's the case. I thought movement set up a timer for the task. I mean the whole point of scheduling a task in the background is to be interrupted when the time is right in order to handle it. Perhaps the real solution lies in somehow making the scheduler compatible with sleep mode.
This requires a background task, and sleep mode is not available. However, no task is scheduled if the alarm function is deactivated and sleep mode works as expected.
Suggestion: use a minutely background tick when alarm is enabled but schedule a task only when the time left is less than one minute. That way the sleep mode will only be unavailable in the last 60 seconds until the deadline. What do you think?
(b) I ran the face with a deadline set and the alarm disabled on my RED board for 8 hours and could not observe any change in voltage. I repeated the test for 8 hours with alarm enabled. Again, no voltage drop could be detected.
It's possible. Although I observed a reduction in voltage while I had the deadline face flashed, I also had other code running which is a source of confounding.
I have removed the deadline face from the build and reflashed the firmware, and the voltage returned to a nominal 3 V. I also observe voltage drops of about 0.05 V when I illuminate the LED and switch to the voltage readout face. It returns to nominal immediately after the LED is turned off. I assume the same thing is happening here. Seems to be a sign the face is consuming more processor resources than anticipated. I definitely overestimated the battery drain the first time around though.
Suggestion: use a minutely background tick when alarm is enabled but schedule a task only when the time left is less than one minute. That way the sleep mode will only be unavailable in the last 60 seconds until the deadline. What do you think?
I am confused. What is a background tick? I thought the lowest frequency for faces is 1 Hz. Are you suggesting to intercept the EVENT_LOW_ENERGY_UPDATE
event? This only works if the face is in the foreground, and I don't think an alarm for a deadline should require this. You could easily miss the alert if another face is in the foreground.
I assume the same thing is happening here. Seems to be a sign the face is consuming more processor resources than anticipated.
Could you run the experiment again by flashing the deadline face back on the firmware? I have found that the voltage is not really a reliable source of information. Looking at the code of the face, I wonder if there is any computation at all when it is running in the background and no task is scheduled. I suspect something else is causing problems, including voltage being a poor indicator.
What is a background tick?
My current understanding of the source code is that there are two types of tasks:
Scheduled tasks are run when the time equals the scheduled time. It seems we just discovered that they stop the device from entering low power mode.
Background tasks execute once a minute and are handled even by the low power movement loop.
They are executed by this function:
Both the normal and low power movement loops call this function when needs_background_tasks_handled
is true
. It is set by the cb_alarm_fired
callback, which is registered to an alarm that fires every minute:
In order to use them, the watch face must provide a wants_background_task
function pointer which computes whether the background task must be executed. If it returns true
, the face's loop is run immediately with a background task event.
I suggested that a wants_background_task
function be written which checks that there is less than one minute to deadline and only then schedules an exact alarm task.
Alternatively, limit the deadline resolution to minutes and check whether the targeted minute has been reached and activate the buzzer when handling the background event. This is how the other watch faces seem to work, including the clock face's hourly chime.
Could you run the experiment again by flashing the deadline face back on the firmware?
Sure. I'll do that as soon as my branch gets merged, I'm going to be testing it over the next few days.
I should buy a second sensor watch board and a used watch for testing...
I have found that the voltage is not really a reliable source of information. Looking at the code of the face, I wonder if there is any computation at all when it is running in the background and no task is scheduled. I suspect something else is causing problems, including voltage being a poor indicator.
It's possible. I assumed it was reliable because I saw the battery endurance tests run by @joeycastillo which graphed battery voltage over time, thereby establishing that battery voltage remained above the minimum for reliable operation for over one year. It's certainly proving to be hard to pin down. Sadly I don't have those power profilers...
Now, I got it. That's a great idea. I was not aware of the background task feature. I have implemented the alarm functionality as you suggested. When the alarm is enabled (bell visible), the face checks every minute and schedules a task only at the last minute for the remaining seconds. If the alarm function is deactivated, nothing happens.
This watch face draws inspiration from other faces of the project but focuses on keeping track of deadlines. You can enter and monitor up to four different deadlines by providing their respective date and time. The face then display the remaining time at different granularity. It has two modes: running mode and settings mode.
Running Mode
When the watch face is activated, it defaults to running mode. The top right corner shows the current deadline number, and the main display presents the time left until the deadline. The format of the display varies depending on the remaining time.
When less than a day is left, the display shows the remaining hours, minutes, and seconds in the form
HH:MM:SS
.When less than a month is left, the display shows the remaining days and hours in the form
DD:HH
with the unitdy
for days.When less than a year is left, the display shows the remaining months and days in the form
MM:DD
with the unitmo
for months.When more than a year is left, the years and months are displayed in the form
YY:MM
with the unityr
for years.When a deadline has passed in the last 24 hours, the display shows
over
to indicate that the deadline has just recently been reached.When no deadline is set for a particular slot, or if a deadline has already passed by more than 24 hours,
--:--
is displayed.The user can navigate in running mode using the following buttons:
The alarm button moves the next deadline. There are currently four slots available for deadlines. When the last slot has been reached, pressing the button moves to the first slot.
A long press on the alarm button activates settings mode and enables configuring the currently selected deadline.
Settings Mode
In settings mode, the currently selected slot for a deadline can be configured by providing the date and the time. Like running mode, the top right corner of the display indicates the current deadline number. The main display shows the date and, on the next page, the time to be configured.
The user can use the following buttons in settings mode.
The light button navigates through the different date and time settings, going from year, month, day, hour, to minute. The selected position is blinking.
A long press on the light button resets the date and time to the next day at midnight. This is the default deadline.
The alarm button increments the currently selected position. A long press on the alarm button changes the value faster.
The mode button exists setting mode and returns to running mode. Here the selected deadline slot can be changed.