Skip to content

Commit c0edf84

Browse files
committed
docs: new gateway IPC documentation, improved python docstrings
1 parent 5c7e0ba commit c0edf84

2 files changed

Lines changed: 157 additions & 14 deletions

File tree

clients/python/bm_sbc_gateway/__init__.py

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,25 @@
11
"""Fire-and-forget client for the bm_sbc_gateway IPC socket.
22
33
The gateway binds a Unix-domain `SOCK_DGRAM` listener at
4-
`/run/bm_sbc/gateway_ipc.sock` and accepts CBOR-encoded datagrams matching
5-
the v1 schema documented in the gateway app. This module wraps each supported
6-
message type as a single function call.
4+
`/run/bm_sbc/gateway_ipc.sock` and accepts CBOR-encoded datagrams
5+
matching the v1 schema documented in `docs/gateway-ipc.md`.
6+
This module wraps each supported message type as a single function call.
77
88
Example::
99
10-
from bm_sbc_gateway import sensor_data, spotter_tx, spotter_log
10+
from bm_sbc_gateway import (
11+
config_set,
12+
replay_caught_up,
13+
sensor_data,
14+
spotter_log,
15+
spotter_tx,
16+
)
1117
1218
sensor_data("temperature", cbor2.dumps({"t_c": 21.4}))
1319
spotter_tx(payload_bytes, iridium_fallback=True)
1420
spotter_log("boot complete", file_name="system.log", print_timestamp=True)
21+
config_set("wifi_ssid", "mynet")
22+
replay_caught_up()
1523
"""
1624

1725
from __future__ import annotations
@@ -40,15 +48,17 @@
4048
class Client:
4149
"""Reusable client holding a connected `SOCK_DGRAM` socket.
4250
43-
Prefer a single `Client` instance over the module-level helpers when
44-
sending many messages — it avoids re-opening the socket every call.
51+
Prefer a single `Client` instance over the module-level helpers
52+
when sending many messages —
53+
it avoids re-opening the socket every call.
4554
"""
4655

4756
def __init__(self, socket_path: str = DEFAULT_SOCKET_PATH) -> None:
4857
self._path = socket_path
4958
self._sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
5059

5160
def close(self) -> None:
61+
"""Close the underlying socket."""
5262
self._sock.close()
5363

5464
def __enter__(self) -> "Client":
@@ -62,6 +72,7 @@ def _send(self, message: dict[str, Any]) -> None:
6272
self._sock.sendto(cbor2.dumps(message), self._path)
6373

6474
def replay_caught_up(self) -> None:
75+
"""Signal that the upstream replay has caught up."""
6576
self._send({"type": "replay_caught_up"})
6677

6778
def spotter_log(
@@ -70,6 +81,11 @@ def spotter_log(
7081
file_name: Optional[str] = None,
7182
print_timestamp: Optional[bool] = None,
7283
) -> None:
84+
"""Append a line to a Spotter log, either SD card file or console.
85+
86+
`data` is bounded by the gateway at 1024 bytes.
87+
`file_name` is bounded at 63 bytes; omit it for the console log.
88+
"""
7389
msg: dict[str, Any] = {"type": "spotter_log", "data": data}
7490
if file_name is not None:
7591
msg["file_name"] = file_name
@@ -82,22 +98,38 @@ def spotter_tx(
8298
data: bytes,
8399
iridium_fallback: Optional[bool] = None,
84100
) -> None:
101+
"""Transmit a payload over the Spotter cell/satellite link.
102+
103+
`iridium_fallback=True` enables Iridium fallback on top of cellular;
104+
omit (or `False`) for cellular-only.
105+
"""
85106
msg: dict[str, Any] = {"type": "spotter_tx", "data": data}
86107
if iridium_fallback is not None:
87108
msg["iridium_fallback"] = iridium_fallback
88109
self._send(msg)
89110

90111
def config_set(self, config_key: str, config_value: Any) -> None:
91-
# The gateway dispatches on the CBOR wire type of config_value
92-
# (text → STR, uint → UINT32, neg int → INT32, float → FLOAT),
93-
# so just pass the native Python value.
94-
self._send({
95-
"type": "config_set",
96-
"config_key": config_key,
97-
"config_value": config_value,
98-
})
112+
"""Write a key-value pair into the local system config partition.
113+
114+
The gateway infers the stored type from the CBOR wire type of `config_value`:
115+
text → STR, unsigned int → UINT32, negative int → INT32, float/double → FLOAT.
116+
Strings are capped at 48 bytes by the gateway.
117+
"""
118+
self._send(
119+
{
120+
"type": "config_set",
121+
"config_key": config_key,
122+
"config_value": config_value,
123+
}
124+
)
99125

100126
def sensor_data(self, topic_suffix: str, data: bytes) -> None:
127+
"""Publish sensor data on the Bristlemouth pub-sub network.
128+
129+
Published topic is `sensor/<node_id_hex16>/<topic_suffix>`.
130+
`topic_suffix` must not begin with `/`;
131+
the gateway inserts the separator.
132+
"""
101133
if topic_suffix.startswith("/"):
102134
raise ValueError(
103135
"topic_suffix must not begin with '/'; the gateway inserts "

docs/gateway-ipc.md

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# Gateway IPC
2+
3+
Local clients (Hydrotwin, tools) send commands to a running `bm_sbc_gateway`
4+
over a Unix-domain `SOCK_DGRAM` socket.
5+
Each datagram is one self-contained CBOR map.
6+
There are no replies — the socket is one-way client → gateway.
7+
8+
## Python client
9+
10+
A reference client lives in `clients/python/bm_sbc_gateway/`,
11+
with one helper per message type
12+
(`config_set`, `replay_caught_up`, `sensor_data`, `spotter_log`, `spotter_tx`)
13+
and a `Client` class for callers that want to keep one socket open.
14+
The helpers handle CBOR encoding and the `v=1` envelope.
15+
16+
## Transport
17+
18+
- **Socket path:** `/run/bm_sbc/gateway_ipc.sock`
19+
(override with `BM_SBC_GATEWAY_IPC` for tests).
20+
- **Type:** `AF_UNIX` / `SOCK_DGRAM`, non-blocking, world-writable (`0666`).
21+
Access control is expected at the directory level.
22+
- **Encoding:** CBOR (definite-length maps).
23+
- **Max datagram:** 4096 bytes.
24+
25+
## Envelope
26+
27+
Every message is a CBOR map with two required keys plus message-specific
28+
fields:
29+
30+
| key | type | value |
31+
| ------ | ------- | ------------------------------ |
32+
| `v` | integer | Schema version. Currently `1`. |
33+
| `type` | text | Message type (see below). |
34+
35+
Unknown `type` values are logged and dropped.
36+
Malformed datagrams (bad CBOR, missing `v`, wrong schema version) are dropped.
37+
38+
## Messages
39+
40+
### `replay_caught_up`
41+
42+
Signal that processing of replayed audio has caught up to realtime.
43+
The gateway will signal `cobs_to_shm` to stop.
44+
Then Hydrotwin or other clients should exit cleanly upon ingesting EOF.
45+
The gateway will request a poweroff service ack from the mote,
46+
then run `systemctl poweroff` once acknowledged.
47+
48+
No additional fields.
49+
50+
### `spotter_log`
51+
52+
Append a line to the Spotter on-board log.
53+
54+
| key | type | required | notes |
55+
| ----------------- | ------- | -------- | ------------------------------ |
56+
| `data` | text | yes | Log line. Max 1024 bytes. |
57+
| `file_name` | text | no | Target log file. Max 63 bytes. |
58+
| `print_timestamp` | boolean | no | Default `false`. |
59+
60+
### `spotter_tx`
61+
62+
Transmit a payload over the Spotter satellite link.
63+
64+
| key | type | required | notes |
65+
| ------------------ | ------- | -------- | ---------------------------------------- |
66+
| `data` | bytes | yes | Payload to transmit. |
67+
| `iridium_fallback` | boolean | no | `true` ⇒ cellular with Iridium fallback. |
68+
69+
### `sensor_data`
70+
71+
Publish sensor data on the Bristlemouth pub/sub network.
72+
Published topic is `sensor/<node_id_hex16>/<topic_suffix>`.
73+
74+
| key | type | required | notes |
75+
| -------------- | ----- | -------- | ------------------------------------------------ |
76+
| `topic_suffix` | text | yes | Must not begin with `/`. Bounded by total ≤ 255. |
77+
| `data` | bytes | yes | Payload bytes. |
78+
79+
### `config_set`
80+
81+
Write a key/value into the local system config partition
82+
(`BM_CFG_PARTITION_SYSTEM`) and persist to disk.
83+
84+
| key | type | required | notes |
85+
| -------------- | ------------------------- | -------- | ------------------------ |
86+
| `config_key` | text | yes | Max 32 bytes, non-empty. |
87+
| `config_value` | text / uint / int / float | yes | Type inferred from CBOR. |
88+
89+
Type mapping is taken directly from the CBOR wire type:
90+
91+
| CBOR type | Stored as |
92+
| ---------------- | --------- |
93+
| text string | `STR` |
94+
| unsigned integer | `UINT32` |
95+
| negative integer | `INT32` |
96+
| float / double | `FLOAT` |
97+
98+
Limits:
99+
100+
- Strings ≤ 48 bytes
101+
(the backing CBOR value buffer is 50 bytes and length-prefix takes 1–2).
102+
- `UINT32` values must fit in `uint32_t`; `INT32` in `int32_t`.
103+
- Floats are stored as `float` regardless of source precision.
104+
105+
Successful writes are persisted via `save_config(BM_CFG_PARTITION_SYSTEM)`
106+
before the handler returns.
107+
108+
## Testing
109+
110+
`apps/ipc_test` runs only the IPC listener (no UART, no mote)
111+
and is what `scripts/gateway_ipc_test.sh` exercises against a Python client.

0 commit comments

Comments
 (0)