docs: интеграция устройства — MQTT/ACL, требование ts/seq в payload (дедуп по содержимому), лимиты
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
# Подключение устройства к IoT (Nubes, тест-стенд)
|
||||
|
||||
Проверено end-to-end 2026-10-11 на развёрнутом стенде
|
||||
(`https://iot-api.containerk8s.dev.nubes.ru`, `wss://exqx-iot.containerk8s.dev.nubes.ru/mqtt`).
|
||||
|
||||
## 1. Регистрация устройства (HTTP API, JWT HS256)
|
||||
|
||||
JWT подписывается `JWT_HMAC_SECRET` (HS256), claim `sub` = ваш namespace,
|
||||
`exp` обязателен; `namespace` в пути **обязан совпадать** с `sub`, иначе `403`.
|
||||
|
||||
```bash
|
||||
curl -X POST "$API/v1/namespaces/$NS/iot/devices" \
|
||||
-H "Authorization: Bearer $JWT" -H 'Content-Type: application/json' \
|
||||
-d '{"name":"dev1","device_id":"d-01"}'
|
||||
```
|
||||
|
||||
Ответ `201` содержит **единственный раз** выдаваемый пароль:
|
||||
|
||||
```json
|
||||
{"name":"dev1","namespace":"e2e-nubes","device_id":"d-01","enabled":true,
|
||||
"mqtt_username":"e2e-nubes_d-01","mqtt_password":"<hex>",
|
||||
"telemetry_topic":"e2e-nubes/telemetry/d-01"}
|
||||
```
|
||||
|
||||
`GET …/devices/{name}` пароль **не** возвращает. Ограничения идентификаторов:
|
||||
`^[a-zA-Z0-9][a-zA-Z0-9-]*$`, namespace ≤ 56, name/device_id ≤ 64 символов
|
||||
(иначе `400`). Дубликаты name/device_id → `409`.
|
||||
|
||||
## 2. Подключение по MQTT
|
||||
|
||||
- URL: `wss://exqx-iot.containerk8s.dev.nubes.ru/mqtt` (WebSocket, subprotocol `mqtt`);
|
||||
внутри кластера — `ws://containerk8s.<uuid-инстанса EMQX>.svc.cluster.local:8083/mqtt`.
|
||||
- username = `namespace_device_id`, пароль — из ответа на создание.
|
||||
- Публикация: топик `namespace/telemetry/device_id` (`telemetry_topic`).
|
||||
|
||||
ACL: устройству разрешён **только свой** топик. Публикация/подписка куда-либо ещё
|
||||
(чужой namespace, другое устройство, wildcard `+`/`#`) → `deny_action=disconnect`,
|
||||
соединение рвётся. Также запрещены: retained-публикации
|
||||
(`retain_available=false`), топики глубже 10 уровней, subscribe на
|
||||
`+/telemetry/+` (это привилегия бриджа).
|
||||
|
||||
Коды CONNACK: `0` — успех, `4` — неверный username/пароль, `5` — не авторизован
|
||||
(в т.ч. устройство выключено через `PATCH {"enabled": false}`).
|
||||
|
||||
## 3. Требование к payload — ВАЖНО
|
||||
|
||||
Дедупликация выполняется по **содержимому сообщения**:
|
||||
`message_id = SHA256(namespace | device_id | topic | payload)`
|
||||
(`internal/service/bridge/handler.go`). Поэтому:
|
||||
|
||||
- **два побайтово одинаковых payload подряд считаются дубликатом и сохраняются
|
||||
один раз** (это защита от повторной доставки SQS at-least-once);
|
||||
- ⇒ в payload обязателен **timestamp или счётчик**, иначе легитимные повторные
|
||||
одинаковые показания (например, `{"t":21.0}` каждую минуту) будут отброшены.
|
||||
|
||||
Пример корректного payload:
|
||||
|
||||
```json
|
||||
{"temp": 21.5, "ts": 1791737028, "seq": 4211}
|
||||
```
|
||||
|
||||
Payload валидный JSON сохраняется как JSON; не-JSON (текст, бинарь) оборачивается
|
||||
в строку. Лимиты: MQTT-пакет ≤ 1 МБ (`max_packet_size`), envelope для SQS ≤ 250 КБ
|
||||
(`maxSQSBodyBytes`) — сообщения больше отбрасываются мостом.
|
||||
|
||||
QoS: `QoS0` — «выстрелил и забыл»; для гарантии доставки используйте `QoS1`
|
||||
(дождитесь PUBACK перед закрытием соединения).
|
||||
|
||||
## 4. Чтение телеметрии
|
||||
|
||||
```bash
|
||||
curl "$API/v1/namespaces/$NS/iot/telemetry?device_id=d-01&limit=50&offset=0" \
|
||||
-H "Authorization: Bearer $JWT"
|
||||
```
|
||||
|
||||
`limit` — дефолт 50, потолок 1000. Ответ:
|
||||
`{"count":N,"items":[{"id":…,"device_id":…,"ts":…,"payload":…}]}`.
|
||||
|
||||
⚠️ Чтение отдаёт payload целиком, поэтому `limit=1000` при крупных payload'ах
|
||||
дорого для клиента и сервиса — для больших объёмов берите меньше и
|
||||
используйте `offset`.
|
||||
|
||||
## 5. Наблюдаемое поведение (e2e, 2026-10-11)
|
||||
|
||||
Задержка «MQTT → видно в API» недетерминирована и доходит до ~20 с: потребитель
|
||||
SQS работает long-poll окном (`SQS_LONG_POLL_SECONDS=20`), батч пишется в PG и
|
||||
только потом удаляется из очереди. Для тестов/интеграции — опрашивать с
|
||||
таймаутом ≥ 30 с, а не фиксированной паузой 1–5 с.
|
||||
Reference in New Issue
Block a user