Skip to content

Commit 9ff53f2

Browse files
committed
Implement Matter Time Synchronization support for the Nano Matter
1 parent 387f71e commit 9ff53f2

20 files changed

Lines changed: 2163 additions & 149 deletions

File tree

boards.txt

Lines changed: 2 additions & 2 deletions
Large diffs are not rendered by default.
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/*
2+
Matter Time Synchronization example
3+
4+
The example shows how to receive UTC time (and timezone / DST offsets) from a
5+
Matter controller and read them as Unix timestamps using the Arduino Matter
6+
API.
7+
8+
The Time Synchronization cluster is enabled on the root endpoint. After the
9+
device is commissioned, a Matter hub that supports time sync (or chip-tool
10+
SetUTCTime / SetTimeZone / SetDSTOffset) can set the device clock. The sketch
11+
waits until time is available, then prints UTC and local timestamps.
12+
13+
Compatible boards:
14+
- Arduino Nano Matter
15+
16+
Author: Tamas Jozsi (Silicon Labs)
17+
*/
18+
#include <Matter.h>
19+
#include <MatterTemperature.h>
20+
#include <MatterTimeSynchronization.h>
21+
22+
MatterTemperature matter_temp_sensor;
23+
MatterTimeSynchronization matter_time;
24+
25+
void print_date_time(const char* label, uint32_t unix_time);
26+
27+
void on_time_updated()
28+
{
29+
Serial.println("Matter time updated");
30+
}
31+
32+
void on_timezone_updated()
33+
{
34+
Serial.println("Matter timezone updated");
35+
}
36+
37+
void setup()
38+
{
39+
Serial.begin(115200);
40+
Matter.begin();
41+
matter_temp_sensor.begin();
42+
matter_time.begin();
43+
matter_time.set_time_update_callback(on_time_updated);
44+
matter_time.set_timezone_update_callback(on_timezone_updated);
45+
46+
Serial.println("Matter Time Synchronization");
47+
48+
if (!Matter.isDeviceCommissioned()) {
49+
Serial.println("Matter device is not commissioned");
50+
Serial.println("Commission it to your Matter hub with the manual pairing code or QR code");
51+
Serial.printf("Manual pairing code: %s\n", Matter.getManualPairingCode().c_str());
52+
Serial.printf("QR code URL: %s\n", Matter.getOnboardingQRCodeUrl().c_str());
53+
}
54+
while (!Matter.isDeviceCommissioned()) {
55+
delay(200);
56+
}
57+
58+
Serial.println("Waiting for Thread network...");
59+
while (!Matter.isDeviceThreadConnected()) {
60+
delay(200);
61+
}
62+
Serial.println("Connected to Thread network");
63+
64+
Serial.println("Waiting for Matter device discovery...");
65+
while (!matter_temp_sensor.is_online()) {
66+
delay(200);
67+
}
68+
Serial.println("Matter device is now online");
69+
70+
matter_time.request_time();
71+
Serial.println("Waiting for Matter controller time synchronization...");
72+
uint32_t time_request_cnt = 0u;
73+
while (!matter_time.has_time()) {
74+
delay(1000);
75+
time_request_cnt++;
76+
if (time_request_cnt % 60 == 0) {
77+
Serial.println("Re-requesting time from controller...");
78+
matter_time.request_time();
79+
}
80+
}
81+
Serial.printf("Time synchronized - Unix UTC: %lu\n", matter_time.get_unix_time());
82+
print_date_time("UTC time", matter_time.get_unix_time());
83+
if (matter_time.has_timezone()) {
84+
Serial.printf("Local offset: %ld s\n", matter_time.get_local_offset_seconds());
85+
print_date_time("Local time", matter_time.get_local_unix_time());
86+
}
87+
Serial.println("-----");
88+
}
89+
90+
void loop()
91+
{
92+
float current_cpu_temp = getCPUTemp();
93+
matter_temp_sensor.set_measured_value_celsius(current_cpu_temp);
94+
95+
if (!matter_time.has_time()) {
96+
delay(1000);
97+
return;
98+
}
99+
100+
if (matter_time.has_timezone()) {
101+
Serial.printf("Local Unix time: %lu | CPU temp: %.02f C\n", matter_time.get_local_unix_time(), current_cpu_temp);
102+
print_date_time("Local time", matter_time.get_local_unix_time());
103+
} else {
104+
Serial.printf("UTC Unix time: %lu | CPU temp: %.02f C\n", matter_time.get_unix_time(), current_cpu_temp);
105+
print_date_time("UTC time", matter_time.get_unix_time());
106+
}
107+
Serial.println("-----");
108+
delay(1000);
109+
}
110+
111+
void print_date_time(const char* label, uint32_t unix_time)
112+
{
113+
time_t timestamp = static_cast<time_t>(unix_time);
114+
tm* time_info = gmtime(&timestamp);
115+
char formatted_time[32];
116+
117+
if (time_info == nullptr || strftime(formatted_time, sizeof(formatted_time), "%Y-%m-%d %H:%M:%S", time_info) == 0) {
118+
return;
119+
}
120+
121+
Serial.print(label);
122+
Serial.print(": ");
123+
Serial.println(formatted_time);
124+
}

libraries/Matter/readme.md

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,105 @@ Class for creating and controlling a Matter Power Source appliance with battery
482482

483483
```void set_system_mode(thermostat_mode_t system_mode);```
484484

485+
## class MatterTimeSynchronization
486+
487+
Receives UTC time (and optional timezone / DST offsets) from a Matter controller via the Time Synchronization cluster on the root endpoint (not a bridged appliance). Currently enabled for Arduino Nano Matter.
488+
489+
The device does not pull time by itself. A Matter controller that supports time sync must push `SetUTCTime` (and, when the TimeZone feature is advertised, `SetTimeZone` / `SetDSTOffset`). Use `request_time()` to emit a `TimeFailure` event so a supporting controller can push time again.
490+
491+
### Enabling time sync in Home Assistant
492+
493+
Requires Matter Server **1.2.0+** (Home Assistant Matter Server app **9.1.0+**).
494+
495+
#### Home Assistant OS / Supervised (Matter Server add-on)
496+
497+
1. Open **Settings → Add-ons → Matter Server → Configuration**.
498+
2. Set **`time_sync`** to one of:
499+
- `auto` (default) — enable only when the host clock is NTP synchronized
500+
- `on` — always enable (use this if sync never starts under `auto`)
501+
- `off` — disable time sync
502+
3. Ensure Home Assistant’s timezone is set correctly under **Settings → System → General** (the add-on uses the host timezone for `SetTimeZone` / DST).
503+
4. Save and **restart** the Matter Server add-on.
504+
505+
#### Standalone Docker / Docker Compose (`ghcr.io/matter-js/matterjs-server`)
506+
507+
Time sync is **off by default** in the container image. Enable it with environment variables:
508+
509+
```yaml
510+
services:
511+
matterjs-server:
512+
image: ghcr.io/matter-js/matterjs-server:stable
513+
network_mode: host
514+
restart: unless-stopped
515+
volumes:
516+
- "${HOME}/.matterjs-server:/data"
517+
environment:
518+
ENABLE_TIME_SYNC: "true"
519+
TZ: "Europe/Budapest" # IANA zone; required for correct local offsets
520+
```
521+
522+
Or with `docker run`:
523+
524+
```bash
525+
docker run -d \
526+
--name matterjs-server \
527+
--restart=unless-stopped \
528+
--network=host \
529+
-v ${HOME}/.matterjs-server:/data \
530+
-e ENABLE_TIME_SYNC=true \
531+
-e TZ=Europe/Budapest \
532+
ghcr.io/matter-js/matterjs-server:stable
533+
```
534+
535+
After changing `TZ` / `ENABLE_TIME_SYNC`, recreate the container so the environment is applied (`docker compose up -d --force-recreate`). Verify inside the container:
536+
537+
```bash
538+
docker exec matterjs-server sh -c 'echo TZ=$TZ; date; node -e "console.log(Intl.DateTimeFormat().resolvedOptions().timeZone)"'
539+
```
540+
541+
`TZ` must be non-empty and resolve to your IANA zone (not `UTC`), or timezone/DST offsets pushed to the device will be zero.
542+
543+
#### Notes
544+
545+
- After a device reflash/reboot, some Matter Server versions may skip re-sync for up to 24 hours (cooldown). Restarting the Matter Server clears that state. See [matter-js/matterjs-server#938](https://github.com/matter-js/matterjs-server/issues/938).
546+
- In Matter Server logs, a successful push looks like `timeSynchronization.setUtcTime` (and `setTimeZone` / `setDstOffset` when timezone is supported) with `status: Success`.
547+
548+
```bool request_time();```
549+
Emits a `TimeFailure` event on the root endpoint so a supporting controller can push time again. Rate-limited to once per 60 seconds. Returns `false` if skipped by the rate limit or if logging fails. Controllers may still ignore the event during their own cooldown windows.
550+
551+
```bool has_time();```
552+
Returns whether a valid wall-clock time is available.
553+
554+
```uint32_t get_unix_time();```
555+
Returns seconds since 1970-01-01 UTC, or 0 if time is not available.
556+
557+
```uint64_t get_unix_time_millis();```
558+
Returns milliseconds since 1970-01-01 UTC, or 0 if time is not available.
559+
560+
```bool has_timezone();```
561+
Returns whether the controller has set a timezone via `SetTimeZone`.
562+
563+
```int32_t get_timezone_offset_seconds();```
564+
Returns the active timezone base offset in seconds east of UTC (excluding DST), or 0 if unavailable.
565+
566+
```int32_t get_dst_offset_seconds();```
567+
Returns the currently applicable DST offset in seconds, or 0 if unavailable / not in DST.
568+
569+
```int32_t get_local_offset_seconds();```
570+
Returns timezone + DST offset in seconds east of UTC.
571+
572+
```uint32_t get_local_unix_time();```
573+
Returns local Unix time in seconds (UTC + offsets). If timezone is not set yet, returns UTC.
574+
575+
```uint64_t get_local_unix_time_millis();```
576+
Returns local Unix time in milliseconds (UTC + offsets). If timezone is not set yet, returns UTC.
577+
578+
```void set_time_update_callback(void (*cb)(void));```
579+
Sets a callback invoked when UTC time becomes available or is updated by the controller.
580+
581+
```void set_timezone_update_callback(void (*cb)(void));```
582+
Sets a callback invoked when the timezone list is updated by the controller.
583+
485584
## class MatterTVOC
486585

487586
```void set_measured_value(float value);```

0 commit comments

Comments
 (0)