diff --git a/HISTORY/2026-08-16-session-log.md b/HISTORY/2026-08-16-session-log.md index cf77ea5..7a276f7 100644 --- a/HISTORY/2026-08-16-session-log.md +++ b/HISTORY/2026-08-16-session-log.md @@ -1203,3 +1203,21 @@ paho connect() rc=0 даже при CONNACK≠0 (on_connect обязателен подставляется через {{VERSION}} (без лишней "v"). - v0.1.8 (digest db648725dfe6) задеплоен; проверка: "/" 200, favicon/logo 200 (image/svg+xml), chip v0.1.8. + +--- + +## 34. Лендинг переписан — «понятно, без воды» (v0.1.9) (19:30 GMT+03) + +- Пользователь: «слишком сухо, ничего не понял, воду лить не надо, но чтобы + было ясно». +- Переработано содержимое landing.html (дизайн/структура сохранены): + - hero: «Это база телеметрии. Устройства шлют показания по MQTT...»; + - «Как это работает»: цепочка простыми словами + «Три понятия» + (namespace / device_id / топик на примере myhome и boiler-1); + - «Быстрый старт — 4 шага» с ГОТОВЫМИ копипаст-командами: 1) токен + (одна python-команда, объяснено что подпись не проверяется), + 2) регистрация устройства (два curl, объяснено ГДЕ пароль), + 3) отправка показания (полный paho-пример), 4) просмотр результата; + - «MQTT endpoint (exqx)»: почему 400 в браузере и что делать. +- v0.1.9 (digest fb488a8666ab) задеплоен, страница проверена в браузере + (структура отрисована корректно). diff --git a/Makefile b/Makefile index 3729c49..33fb9f1 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # Makefile — монолит iot-service (образ naeel/iot-service). # Старые k8s-цели — в legacy/Makefile.old. -VERSION ?= v0.1.8 +VERSION ?= v0.1.9 IMAGE ?= naeel/iot-service LDFLAGS = -X main.version=$(VERSION) diff --git a/internal/service/api/ui/landing.html b/internal/service/api/ui/landing.html index bc0480a..46dd76f 100644 --- a/internal/service/api/ui/landing.html +++ b/internal/service/api/ui/landing.html @@ -161,50 +161,52 @@ pre code { background: none; border: none; padding: 0; } nubes

IoT тестирование

-

Приём и хранение телеметрии устройств: MQTT → шина SQS → - PostgreSQL → REST API и консоль. Устройства изолированы по namespace, - каждому namespace — отдельная база данных.

+

Это база телеметрии. Устройства шлют сюда показания по MQTT — + показания сохраняются и доступны через API и консоль. Ниже — как отправить + первое показание за 2 минуты.

-
Что это и зачем
+
Как это работает
-

Платформа принимает телеметрию от устройств по протоколу MQTT - (WebSocket с TLS) и доставляет её в базу данных по цепочке:

+

Устройство (датчик, бойлер, счётчик) отправляет показания по протоколу + MQTT. Дальше показания проходят по цепочке и попадают в базу данных:

+
+ устройство → MQTT (wss) → очередь iot-telemetry + → PostgreSQL → API / консоль +

- устройство → wss://…/mqtt (EMQX) → очередь - iot-telemetry (shared-sqs) → потребитель → PostgreSQL → - REST API / консоль. + У каждого устройства — свои логин и пароль, поэтому чужое устройство не + сможет писать в ваш топик. Данные разных проектов (namespace) лежат в + разных базах и не смешиваются.

+

Три понятия:

- Устройства регистрируются через REST API и получают собственный - пароль. Каждое устройство публикует только в свой топик - {namespace}/telemetry/{device_id} — чужие топики - отклоняются брокером. + namespace — имя вашего проекта/группы устройств (например + myhome);
+ device_id — идентификатор конкретного устройства (например + boiler-1);
+ топик — адрес, в который устройство отправляет показания: + namespace/telemetry/device_id.

-
Порядок действий — 3 шага
+
Быстрый старт — 4 шага
1
-
Регистрация устройства
-

Устройство создаётся через REST API с Bearer-JWT (структура - sub + exp). В ответе — mqtt_username; - пароль возвращается только запросом GET по имени устройства.

+
Получить токен доступа к API
+

Все команды API требуют заголовок Authorization: Bearer + <токен>. Для тестов подойдёт самодельный токен: сервис + проверяет только два поля (кто и до какого времени), подпись не + проверяется. Одна команда — токен готов:

- TOKEN="…jwt…"
- curl -X POST -H "Authorization: Bearer $TOKEN" \ - -H "Content-Type: application/json" \ - -d '{"name":"dev1","device_id":"dev-001"}' \ - https://iot.containerk8s.dev.nubes.ru/v1/namespaces/test/iot/devices
- curl -H "Authorization: Bearer $TOKEN" \ - https://iot.containerk8s.dev.nubes.ru/v1/namespaces/test/iot/devices/dev1 + TOKEN=$(python3 -c "import base64,json,time;h=base64.urlsafe_b64encode(b'{\"alg\":\"none\",\"typ\":\"JWT\"}').rstrip(b'=').decode();p=base64.urlsafe_b64encode(json.dumps({'sub':'admin','exp':int(time.time())+86400}).encode()).rstrip(b'=').decode();print(h+'.'+p+'.sig')")
@@ -212,18 +214,19 @@ pre code { background: none; border: none; padding: 0; }
2
-
Подключение устройства к MQTT
-

Endpoint wss://exqx.containerk8s.dev.nubes.ru/mqtt, - подпротокол WebSocket — mqtt; username - {namespace}_{device_id}, пароль из шага 1; публикация в - топик {namespace}/telemetry/{device_id}.

+
Зарегистрировать устройство
+

Одна команда — и устройство boiler-1 появляется в + проекте myhome. В ответе будет логин для MQTT + (myhome_boiler-1). Пароль в ответе НЕ показывается — + его отдаёт второй запрос.

- import paho.mqtt.client as mqtt
- c = mqtt.Client(transport="websockets")
- c.ws_set_options(path="/mqtt")
- c.username_pw_set("test_dev-001", "<пароль>")
- c.connect("exqx.containerk8s.dev.nubes.ru", 443)
- c.publish("test/telemetry/dev-001", '{"temp":23.5}') + curl -X POST -H "Authorization: Bearer $TOKEN" \
+ -H "Content-Type: application/json" \
+ -d '{"name":"boiler","device_id":"boiler-1"}' \
+ https://iot.containerk8s.dev.nubes.ru/v1/namespaces/myhome/iot/devices

+
# пароль — во втором запросе (поле mqtt_password):
+ curl -H "Authorization: Bearer $TOKEN" \
+ https://iot.containerk8s.dev.nubes.ru/v1/namespaces/myhome/iot/devices/boiler
@@ -231,11 +234,31 @@ pre code { background: none; border: none; padding: 0; }
3
-
Чтение телеметрии
-

История сообщений — REST API (Bearer-JWT):

+
Отправить показание
+

Подключение к MQTT — по адресу + wss://exqx.containerk8s.dev.nubes.ru/mqtt. Логин — + myhome_boiler-1, пароль — из шага 2. Показание + отправляется в топик myhome/telemetry/boiler-1.

- curl -H "Authorization: Bearer $TOKEN" \ - "https://iot.containerk8s.dev.nubes.ru/v1/namespaces/test/iot/telemetry?limit=50" + import paho.mqtt.client as mqtt, json
+ c = mqtt.Client(transport="websockets")
+ c.ws_set_options(path="/mqtt")
+ c.username_pw_set("myhome_boiler-1", "<пароль из шага 2>")
+ c.connect("exqx.containerk8s.dev.nubes.ru", 443)
+ c.publish("myhome/telemetry/boiler-1", json.dumps({"temp": 58.3})) +
+
+
+ +
+
4
+
+
Увидеть результат
+

Показание доходит в базу за ~1 секунду. Посмотреть — запросом или + в консоли:

+
+ curl -H "Authorization: Bearer $TOKEN" \
+ "https://iot.containerk8s.dev.nubes.ru/v1/namespaces/myhome/iot/telemetry?limit=50"
Открыть консоль →
@@ -272,17 +295,17 @@ pre code { background: none; border: none; padding: 0; }
MQTT endpoint (exqx)

- https://exqx.containerk8s.dev.nubes.ru — это MQTT-брокер - (EMQX), а не веб-страница. Точка подключения — только - /mqtt через WebSocket. Открытие этого адреса в браузере - или HTTP-запрос вернёт 400 — это штатное поведение - (нет WebSocket-подпротокола mqtt). + exqx.containerk8s.dev.nubes.ru — это MQTT-брокер, а не + веб-страница. Точка подключения — только /mqtt через + WebSocket. Если открыть этот адрес в браузере, вернётся + 400 — так и должно быть: браузер не использует + WebSocket-подпротокол mqtt. Подключаться нужно MQTT- + клиентом (paho-mqtt, mqtt.js и т.п.), как в шаге 3.

- Ограничения: размер сообщения — до ~250 КБ (лимит SQS 256 КБ); - платформа может разрывать внешние WebSocket-соединения примерно раз - в 150 секунд — устройства должны автоматически переподключаться - (для QoS 1 повторы выполняются клиентом). + Ограничения: сообщение — до ~250 КБ; платформа может разрывать + внешние WebSocket-соединения примерно раз в 150 секунд — клиент + должен уметь переподключаться (в paho это делается автоматически).