Files
IoT/doc/device-integration.md
T

89 lines
4.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Подключение устройства к 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 с.