Files
IoT/doc/device-integration.md
T

4.9 KiB
Raw Blame History

Подключение устройства к 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.

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 содержит единственный раз выдаваемый пароль:

{"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:

{"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. Чтение телеметрии

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 с.