joeycastillo / Sensor-Watch

A board replacement for the classic Casio F-91W wristwatch
Other
1.03k stars 210 forks source link

Watch face for tracking deadlines. #266

Open rieck opened 11 months ago

rieck commented 11 months ago

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.

The user can navigate in running mode using the following buttons:

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.

matheusmoreira commented 4 months ago

Am now running this code on real hardware. Tracking two deadlines right now. No issues.

matheusmoreira commented 4 months ago

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?

rieck commented 4 months ago

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:

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.

matheusmoreira commented 4 months ago

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.

rieck commented 4 months ago

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.

matheusmoreira commented 4 months ago

At least, I don't remember seeing this behavior when the watch face was first developed.

Which commit was it originally based on?

rieck commented 4 months ago

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).

rieck commented 4 months ago

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.

matheusmoreira commented 4 months ago

(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 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.

rieck commented 4 months ago

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.

matheusmoreira commented 4 months ago

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:

https://github.com/joeycastillo/Sensor-Watch/blob/af18673e1aa53091880d829a6fa4d7e23a6b4381/movement/movement.c#L160-L170

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:

https://github.com/joeycastillo/Sensor-Watch/blob/af18673e1aa53091880d829a6fa4d7e23a6b4381/movement/movement.c#L399-L403

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...

rieck commented 4 months ago

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.