docs: интеграция устройства — MQTT/ACL, требование ts/seq в payload (дедуп по содержимому), лимиты

This commit is contained in:
Repinoid
2026-10-11 20:26:30 +03:00
parent 6b9f397746
commit 52a93da95d
+88
View File
@@ -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 с.