Skip to content

Commit 9e7230a

Browse files
committed
Add initial implementation of Time Driver HAL with NTP management and event handling
1 parent 4252b7a commit 9e7230a

File tree

1 file changed

+108
-0
lines changed

1 file changed

+108
-0
lines changed

docs/specs/hal/time_driver.md

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# Time Driver (HAL)
2+
3+
## Description
4+
5+
The time driver is a HAL component that manages the system NTP daemon (`sysntpd`) and exposes a time source capability to the rest of the system. It is responsible for all direct OS interaction related to time synchronisation: restarting the daemon, monitoring kernel hotplug NTP events via ubus, and publishing the current sync state and accuracy metadata as a capability on the bus.
6+
7+
The time driver acts as both a driver and a time manager — it owns the full lifecycle of `sysntpd` within the running session.
8+
9+
## Dependencies
10+
11+
- **ubus capability** (`{'cap', 'ubus', '1', ...}`): required to listen for `hotplug.ntp` kernel events. The driver waits for the ubus capability to become available before starting NTP.
12+
13+
## Initialisation
14+
15+
On startup (once the ubus capability is available):
16+
17+
1. Restart `sysntpd` via `exec.command("/etc/init.d/sysntpd", "restart"):run()`.
18+
2. Register a ubus listener for `hotplug.ntp` events.
19+
3. Publish initial capability `meta` and `state` (initially `synced = false`).
20+
21+
## Capability
22+
23+
The driver publishes a single time source capability. The capability id is a UUID generated at startup. In the future, additional time source capabilities may exist alongside this one.
24+
25+
### Meta (retained)
26+
27+
Topic: `{'cap', 'time', <uuid>, 'meta'}`
28+
29+
```lua
30+
{
31+
provider = 'hal',
32+
source = 'ntp', -- time source type
33+
version = 1, -- interface version
34+
accuracy_seconds = <number|nil>, -- estimated absolute error in seconds
35+
}
36+
```
37+
38+
`accuracy_seconds` is a coarse estimate of absolute clock error and is derived from NTP stratum. Lower values are better. It is `nil` when unsynced (stratum >= 16) or before first sync-quality data is available.
39+
40+
### State (retained)
41+
42+
Topic: `{'cap', 'time', <uuid>, 'state'}`
43+
44+
```lua
45+
{
46+
synced = true | false,
47+
stratum = <number>, -- last reported stratum, nil before first event
48+
accuracy_seconds = <number|nil>,
49+
}
50+
```
51+
52+
Published on every sync/unsync transition. Retained so new subscribers immediately get the current state.
53+
54+
### Events (non-retained)
55+
56+
#### synced
57+
58+
Topic: `{'cap', 'time', <uuid>, 'event', 'synced'}`
59+
60+
Fired when the NTP daemon transitions from unsynced to synced (stratum < 16). Payload:
61+
62+
```lua
63+
{ stratum = <number>, accuracy_seconds = <number|nil> }
64+
```
65+
66+
#### unsynced
67+
68+
Topic: `{'cap', 'time', <uuid>, 'event', 'unsynced'}`
69+
70+
Fired when the NTP daemon transitions from synced to unsynced (stratum == 16 or daemon restarts). Payload:
71+
72+
```lua
73+
{ stratum = 16, accuracy_seconds = nil }
74+
```
75+
76+
These events are non-retained — consumers that need current state should read `{'cap', 'time', '1', 'state'}` first, then subscribe to events for transitions.
77+
78+
## Service Flow
79+
80+
```mermaid
81+
flowchart TD
82+
St[Start] --> A(Install alarm handler)
83+
A --> B(Wait for ubus capability)
84+
B -->|ubus cap available| C(Restart sysntpd)
85+
C --> D{sysntpd restart ok?}
86+
D -->|error| E[Log error and stop]
87+
D -->|ok| F(Register ubus listener for hotplug.ntp)
88+
F --> G{Listener registered ok?}
89+
G -->|error| H[Log error and stop]
90+
G -->|ok| I(Publish meta + initial state: synced=false)
91+
I --> J{Wait for hotplug.ntp event or stream closed or context done}
92+
J -->|hotplug.ntp event| K{stratum < 16?}
93+
K -->|yes, was unsynced| L(Publish state synced=true + event/synced)
94+
K -->|no, was synced| M(Publish state synced=false + event/unsynced)
95+
L --> J
96+
M --> J
97+
K -->|no change| J
98+
J -->|stream closed| N[Log warning, stop]
99+
J -->|context done| O(Send stop_stream to ubus)
100+
```
101+
102+
## Architecture
103+
104+
- The driver runs a single main fiber that handles the full lifecycle. No child scope is needed.
105+
- Sync and unsync events are only published on **transitions** — the driver tracks the previous sync state and only emits an event when it changes.
106+
- The retained `state` topic is always updated on any hotplug event regardless of transition, to refresh the stratum value.
107+
- A `finally` block logs the reason for shutdown and performs cleanup (stop_stream if still active).
108+
- The ubus `stream_id` must be stopped cleanly on context cancellation to avoid leaking listener registrations in the ubus driver.

0 commit comments

Comments
 (0)