feat(ble): add BLE UART daemon notify API

This commit is contained in:
Zhou Xiao
2026-04-28 11:18:21 +08:00
committed by zhiweijian
parent 044339c29c
commit 11447dd9ad
8 changed files with 151 additions and 9 deletions
@@ -114,6 +114,28 @@ Do not send requests to a daemon bound to a shared network interface unless that
The CLI prints only the response payload. If the device returns a JSON object, the CLI prints it as JSON.
## Send a notification from the CLI
Use `daemon-notify` for fire-and-forget operations where the caller only needs the daemon to write to the BLE device and does not need a protocol response:
```bash
python main.py daemon-notify --op set_led --json '{"state": true}'
```
Send a raw string notification with the default operation name `raw`:
```bash
python main.py daemon-notify "hello"
```
Use a non-default daemon address:
```bash
python main.py daemon-notify --host 127.0.0.1 --port 8899 --op set_led --json '{"state": true}'
```
`daemon-notify` returns after the local BLE write completes. It does not wait for the device to send a JSONL response.
## HTTP API
Daemon mode exposes a local HTTP API.
@@ -135,7 +157,7 @@ Response fields:
| `is_connected` | Whether the BLE client is currently connected |
| `pending_requests` | Number of pending request futures |
| `single_flight` | Whether the daemon serializes requests |
| `max_request_data_bytes` | Maximum JSON-encoded `data` size accepted by `/request` |
| `max_request_data_bytes` | Maximum JSON-encoded `data` size accepted by `/request` and `/notify` |
| `protocol` | Wire protocol name and version |
### `POST /request`
@@ -189,6 +211,54 @@ HTTP error behavior:
| `502` | Device returned a protocol error or invalid response |
| `504` | Timed out waiting for the device response |
### `POST /notify`
Sends one notification to the BLE device and returns without waiting for a protocol response:
```bash
curl -X POST http://127.0.0.1:8888/notify \
-H 'Content-Type: application/json' \
-d '{"op":"set_led","data":{"state":true}}'
```
Request body:
```json
{
"op": "set_led",
"data": {
"state": true
}
}
```
Fields:
| Field | Required | Meaning |
| --- | --- | --- |
| `op` | No | Operation name. Defaults to `raw`. |
| `data` | Yes | Notification payload. Can be a string, number, boolean, array, object, or null. |
Limits:
- `op` must be 1 to 64 characters.
- JSON-encoded `data` must not exceed 4096 bytes.
Successful response:
```json
{
"ok": true
}
```
HTTP error behavior:
| HTTP status | Meaning |
| --- | --- |
| `413` | Request data exceeds the daemon payload limit |
| `500` | Failed to send data to the BLE device |
## BLE JSONL RPC protocol
The daemon communicates with the BLE device using newline-delimited JSON. Every message is one JSON object followed by `\n`.
@@ -226,7 +296,7 @@ Fields:
| Field | Meaning |
| --- | --- |
| `v` | Protocol version. Current value is `1`. |
| `id` | Request ID generated by the daemon. The device must echo this in the response. |
| `id` | Request ID generated by the daemon. The device must echo this in `/request` responses. `/notify` uses an empty string because no response is expected. |
| `op` | Operation name selected by the client. |
| `data` | Request payload. |
@@ -295,7 +365,8 @@ On the BLE device, implement this loop conceptually:
3. Parse each line as JSON.
4. Read `id`, `op`, and `data`.
5. Execute the requested operation.
6. Send a JSON response with the same `id` and a final `\n`.
6. If `id` is non-empty, send a JSON response with the same `id` and a final `\n`.
7. If `id` is empty, treat the message as fire-and-forget and normally do not send a response.
For example, an `echo` operation can return the same data:
@@ -303,6 +374,14 @@ For example, an `echo` operation can return the same data:
{"v":1,"id":"6f8f...","ok":true,"data":"hello"}
```
For notifications sent through `/notify`, the daemon uses an empty `id`:
```json
{"v":1,"id":"","op":"set_led","data":{"state":true}}
```
Firmware can execute the operation without responding. If it does respond with `id: ""`, the daemon will log the message as unsolicited because no pending request is waiting for that ID.
## Single-flight behavior
The daemon currently processes one `/request` at a time. This is exposed as: