Skip to content

Conversation

benpicco
Copy link
Contributor

@benpicco benpicco commented Apr 1, 2025

Contribution description

Currently applications use the periph_rtc API directly if they want to track wall clock time.
This leads to problems when the time is not provided by periph_rtc but some external RTC or when we want to perform actions e.g. on setting the time - then this has to be hooked up with every periph_rtc implementation.

Instead, provide a wrapper API that applications can use to obtain the current system time without accessing the RTC API directly.

This allows to write applications that are agnostic to the availability of periph_rtc and still can provide some proper timestamps.

Testing procedure

Enable the walltime module.
This will provide a walltime command to get / set the time and display the time since last boot.

Issues/PRs references

Alternative to #17416, #21366

@github-actions github-actions bot added the Area: sys Area: System label Apr 1, 2025
@maribu maribu self-requested a review April 1, 2025 17:25
Copy link
Contributor

@crasbe crasbe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this addition a lot, some small comments and observations from testing the PR below.

Edit: For reference: I tested this with an nRF52840DK with USEMODULE+=walltime BOARD=nrf52840dk make -C tests/sys/shell flash term.

* @brief Common functions to access the wall-clock / real time clock
* @{
* @file
*
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to have some documentation about what this module does and how to use it. More or less what the PR description says for example.

Currently it's a bit sparse and if you don't know what it does, it's a bit hard to get into it (IMO):
image

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also since there is no documentation for struct tm (I think time.h is part of the standard library?), it might be good to include a link to a reference for it.

This is the link used by drivers/periph/rtc.h: https://pubs.opengroup.org/onlinepubs/7908799/xsh/time.h.html

* @note
* The values used for setting and getting the time/alarm should
* conform to the `struct tm` specification.
* Compare: http://pubs.opengroup.org/onlinepubs/7908799/xsh/time.h.html

@crasbe crasbe added Type: new feature The issue requests / The PR implemements a new feature for RIOT CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR labels Apr 1, 2025
@riot-ci
Copy link

riot-ci commented Apr 1, 2025

Murdock results

✔️ PASSED

90fc5e4 tests/walltime: add test for the walltime module

Success Failures Total Runtime
10316 0 10317 11m:02s

Artifacts

Copy link
Contributor

@mguetschow mguetschow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, I like this! Some first thoughts on it below.

Comment on lines +58 to +64
struct tm now;
walltime_get(&now, ms);
return mktime(&now);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you are providing a ZTIMER_FALLBACK above, what would happen here if periph_rtc and rtt_rtc are not available?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The system time will always start at RIOT_EPOCH at boot, but still can by synced later on e.g. via NTP - it's just not persistent across reboots.

#endif

static uint32_t _boottime;
#ifdef BACKUP_RAM
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that macro documented somewhere?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this count?

$ rg BACKUP_RAM -B 5 features.yaml
417-- title: RAM Related Features
418-  help: These features indicate presence of special RAM regions or features
419-  features:
420-  - name: backup_ram
421-    help: A special portion of RAM is retained during deep sleep.
422:          Variables can be placed there by annotating them with the `BACKUP_RAM`

See https://doc.riot-os.org/feature-list.html#autotoc_md2404 for the output

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hum, that's indeed something, but probably nothing doxygen would find when using @ref BACKUP_RAM

uint32_t msec = 0;
#ifdef ZTIMER_FALLBACK
rtc_localtime(walltime_get_riot(ms), time);
#elif defined(MODULE_PERIPH_RTC_MS)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it on purpose that you check for MODULE_PERIPH_RTC above for ZTIMER_FALLBACK?

static uint32_t _boottime_bkup;
#endif
#ifdef ZTIMER_FALLBACK
uint32_t ztimer_offset;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

even though i rarely run my devboards for 50 days or longer there should be a "mitigation for the 49day overflow"
eg a overflow counter

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah here: uint32_t now = ztimer_now(ZTIMER_MSEC); you mean.


void walltime_set(struct tm *time)
{
int32_t diff = rtc_mktime(time) - walltime_get_riot(NULL);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the int32_t could lead to unexpected results. For example the extreme case 0 - 4294967295 becomes 1.
I would expect -4294967295
You could figure out the sign and compute the number as uint32_t higher - lower.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

diff --git a/sys/walltime/walltime.c b/sys/walltime/walltime.c
index 903e856a16..5171cb0770 100644
--- a/sys/walltime/walltime.c
+++ b/sys/walltime/walltime.c
@@ -17,6 +17,8 @@
  *
  * @}
  */
+#include <stdio.h>
+
 #include "auto_init.h"
 #include "auto_init_utils.h"
 #include "auto_init_priorities.h"
@@ -96,7 +98,10 @@ time_t walltime_get_unix(uint16_t *ms)
 
 void walltime_set(struct tm *time)
 {
-    int32_t diff = rtc_mktime(time) - walltime_get_riot(NULL);
+    uint32_t t;
+    int32_t w;
+    int32_t diff = (t = rtc_mktime(time)) - (w = walltime_get_riot(NULL));
+    printf("t %"PRIu32" - w %"PRId32" = diff %"PRId32"\n", t, w, diff);
     _boottime += diff;
 #ifdef BACKUP_RAM
     _boottime_bkup += diff;
> 2025-05-06 15:29:20,168 # main(): This is RIOT! (Version: 2025.04-devel-522-g90fc5e-walltime)
> walltime set 4000-01-01 00:00:00
2025-05-06 15:29:23,464 # walltime set 4000-01-01 00:00:00
2025-05-06 15:29:23,480 # t 2353209856 - w 3 = diff -1941757443
2025-05-06 15:29:23,481 # time changed by -1941757443 sec, 0 ms

So when going very very very far in the future, the diff becomes negative.
I know that no one actually cares about the year 4000, but consider some user input or malformed network packet ... whatever.

*
* @}
*/
#include "auto_init.h"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe guard with IS_USED(MODULE_AUTO_INIT)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But why?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because when it would be used with unittest it would fail to compile, or when someone does not want auto_init.
I would say if the auto init stuff is not guarded, USEMODULE += auto_init would belong in Makefile.dep.
But auto_init is not necessarily required.

Too much worries or do you agree that any auto init stuff should be guarded?

return now - _boottime;
}

static void auto_init_uptime(void)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe guard with IS_USED(MODULE_AUTO_INIT_WALLTIME/UPTIME)

@fabian18
Copy link
Contributor

fabian18 commented Apr 7, 2025

This allows to write applications that are independent of the RTC
peripheral and allows the implementation of on-time change callbacks.

How would I implement an on-time change callback?
You expect that the backend for walltime implements the RTC API, don't you?
This limits to only only one RTC API. If I want to implement my application rtc_set_time around the RTC rtc_set_time, it does not work. I would have gone with function pointers for walltime_backend->rtc_set_time and walltime_backend->rtc_get_time

@benpicco
Copy link
Contributor Author

benpicco commented Apr 7, 2025

How would I implement an on-time change callback?

We can add that to the walltime module as a 2nd step, then we would only need to add it in one place instead of adding it to every RTC implementation.

I wanted to get the core API in first, but might as well already add this here.

If I want to implement my application rtc_set_time around the RTC rtc_set_time, it does not work.

Yea applications must not use the periph_rtc API if the walltime module is used to access the RTC instead - but that should be a simple change.

I would have gone with function pointers for walltime_backend->rtc_set_time and walltime_backend->rtc_get_time

There only is a single wall time/system time. I'm happy to hook up different backends like ds1307 or even dcf77 or gnss. The nice thing about the high level API is that the internals can be as complex as needed, to the user it's still just a simple get time / set time API.

@benpicco benpicco requested a review from kaspar030 as a code owner April 11, 2025 17:37
@github-actions github-actions bot added the Area: tests Area: tests and testing framework label Apr 24, 2025
*
* @param[in] time The current data / time to set
*/
void walltime_set(struct tm *time);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If rtc_set_time returns an int, then walltime_set also should be capable to return an error.
There could be an external I2C RTC which could report a communication error.

* @param[out] ms current milliseconds output, may be NULL
* always returns 0 if the backend supports no millisecond resolution
*/
void walltime_get(struct tm *time, uint16_t *ms);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also here I would vote for an int return type

return 8;
}


Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

double empty line

@fabian18
Copy link
Contributor

fabian18 commented Jun 19, 2025

The walltime is calling rtc API.

Consider the case of an external RTC.

There is the option to wrap the driver RTC API in our periph RTC API.

  • Then periph_rtc must get substituted with the external RTC.

If the walltime would not use the RTC API but use a driver struct with function callbacks, then the walltime could be instanciated with either the periph RTC or the external RTC driver. Then periph RTC features could still be used.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area: sys Area: System Area: tests Area: tests and testing framework CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR Type: new feature The issue requests / The PR implemements a new feature for RIOT
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants