docs(landing): rewrite as clear how-to (4 steps with copy-paste commands); v0.1.9

This commit is contained in:
“Naeel”
2026-08-16 18:51:59 +04:00
parent bd6bc8777e
commit 6f609eca4b
3 changed files with 91 additions and 50 deletions
+18
View File
@@ -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) задеплоен, страница проверена в браузере
(структура отрисована корректно).
+1 -1
View File
@@ -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)
+72 -49
View File
@@ -161,50 +161,52 @@ pre code { background: none; border: none; padding: 0; }
<img src="/static/logo.svg" height="28" alt="nubes">
<div>
<h1>IoT <span class="badge">тестирование</span></h1>
<p class="sub">Приём и хранение телеметрии устройств: MQTT → шина SQS →
PostgreSQL → REST API и консоль. Устройства изолированы по namespace,
каждому namespace — отдельная база данных.</p>
<p class="sub">Это база телеметрии. Устройства шлют сюда показания по MQTT —
показания сохраняются и доступны через API и консоль. Ниже — как отправить
первое показание за 2 минуты.</p>
</div>
</div>
<div class="card">
<div class="card-header">Что это и зачем</div>
<div class="card-header">Как это работает</div>
<div class="card-body">
<p>Платформа принимает телеметрию от устройств по протоколу MQTT
(WebSocket с TLS) и доставляет её в базу данных по цепочке:</p>
<p>Устройство (датчик, бойлер, счётчик) отправляет показания по протоколу
MQTT. Дальше показания проходят по цепочке и попадают в базу данных:</p>
<div class="hint-box">
устройство → <code>MQTT (wss)</code> → очередь <code>iot-telemetry</code>
<b>PostgreSQL</b> → API / консоль
</div>
<p style="color:#374151">
устройство → <code>wss://…/mqtt</code> (EMQX) → очередь
<code>iot-telemetry</code> (shared-sqs) → потребитель → PostgreSQL →
REST API / консоль.
У каждого устройства — свои логин и пароль, поэтому чужое устройство не
сможет писать в ваш топик. Данные разных проектов (namespace) лежат в
разных базах и не смешиваются.
</p>
<p><b>Три понятия:</b></p>
<p style="color:#374151">
Устройства регистрируются через REST API и получают собственный
пароль. Каждое устройство публикует только в свой топик
<code>{namespace}/telemetry/{device_id}</code>чужие топики
отклоняются брокером.
<b>namespace</b> — имя вашего проекта/группы устройств (например
<code>myhome</code>);<br>
<b>device_id</b>идентификатор конкретного устройства (например
<code>boiler-1</code>);<br>
<b>топик</b> — адрес, в который устройство отправляет показания:
<code>namespace/telemetry/device_id</code>.
</p>
</div>
</div>
<div class="card">
<div class="card-header">Порядок действий3 шага</div>
<div class="card-header">Быстрый старт4 шага</div>
<div class="card-body">
<div class="step">
<div class="step-num">1</div>
<div class="step-body">
<div class="step-title">Регистрация устройства</div>
<p>Устройство создаётся через REST API с Bearer-JWT (структура
<code>sub</code> + <code>exp</code>). В ответе — <code>mqtt_username</code>;
пароль возвращается только запросом GET по имени устройства.</p>
<div class="step-title">Получить токен доступа к API</div>
<p>Все команды API требуют заголовок <code>Authorization: Bearer
&lt;токен&gt;</code>. Для тестов подойдёт самодельный токен: сервис
проверяет только два поля (кто и до какого времени), подпись не
проверяется. Одна команда — токен готов:</p>
<div class="hint-box">
<code>TOKEN="…jwt…"</code><br>
<code>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</code><br>
<code>curl -H "Authorization: Bearer $TOKEN" \
https://iot.containerk8s.dev.nubes.ru/v1/namespaces/test/iot/devices/dev1</code>
<code>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')")</code>
</div>
</div>
</div>
@@ -212,18 +214,19 @@ pre code { background: none; border: none; padding: 0; }
<div class="step">
<div class="step-num">2</div>
<div class="step-body">
<div class="step-title">Подключение устройства к MQTT</div>
<p>Endpoint <code>wss://exqx.containerk8s.dev.nubes.ru/mqtt</code>,
подпротокол WebSocket — <code>mqtt</code>; username
<code>{namespace}_{device_id}</code>, пароль из шага 1; публикация в
топик <code>{namespace}/telemetry/{device_id}</code>.</p>
<div class="step-title">Зарегистрировать устройство</div>
<p>Одна команда — и устройство <code>boiler-1</code> появляется в
проекте <code>myhome</code>. В ответе будет логин для MQTT
(<code>myhome_boiler-1</code>). Пароль в ответе НЕ показывается
его отдаёт второй запрос.</p>
<div class="hint-box">
<code>import paho.mqtt.client as mqtt</code><br>
<code>c = mqtt.Client(transport="websockets")</code><br>
<code>c.ws_set_options(path="/mqtt")</code><br>
<code>c.username_pw_set("test_dev-001", "&lt;пароль&gt;")</code><br>
<code>c.connect("exqx.containerk8s.dev.nubes.ru", 443)</code><br>
<code>c.publish("test/telemetry/dev-001", '{"temp":23.5}')</code>
<code>curl -X POST -H "Authorization: Bearer $TOKEN" \<br>
-H "Content-Type: application/json" \<br>
-d '{"name":"boiler","device_id":"boiler-1"}' \<br>
https://iot.containerk8s.dev.nubes.ru/v1/namespaces/myhome/iot/devices</code><br>
<br><code># пароль — во втором запросе (поле mqtt_password):</code><br>
<code>curl -H "Authorization: Bearer $TOKEN" \<br>
https://iot.containerk8s.dev.nubes.ru/v1/namespaces/myhome/iot/devices/boiler</code>
</div>
</div>
</div>
@@ -231,11 +234,31 @@ pre code { background: none; border: none; padding: 0; }
<div class="step">
<div class="step-num">3</div>
<div class="step-body">
<div class="step-title">Чтение телеметрии</div>
<p>История сообщений — REST API (Bearer-JWT):</p>
<div class="step-title">Отправить показание</div>
<p>Подключение к MQTT — по адресу
<code>wss://exqx.containerk8s.dev.nubes.ru/mqtt</code>. Логин —
<code>myhome_boiler-1</code>, пароль — из шага 2. Показание
отправляется в топик <code>myhome/telemetry/boiler-1</code>.</p>
<div class="hint-box">
<code>curl -H "Authorization: Bearer $TOKEN" \
"https://iot.containerk8s.dev.nubes.ru/v1/namespaces/test/iot/telemetry?limit=50"</code>
<code>import paho.mqtt.client as mqtt, json</code><br>
<code>c = mqtt.Client(transport="websockets")</code><br>
<code>c.ws_set_options(path="/mqtt")</code><br>
<code>c.username_pw_set("myhome_boiler-1", "&lt;пароль из шага 2&gt;")</code><br>
<code>c.connect("exqx.containerk8s.dev.nubes.ru", 443)</code><br>
<code>c.publish("myhome/telemetry/boiler-1", json.dumps({"temp": 58.3}))</code>
</div>
</div>
</div>
<div class="step">
<div class="step-num">4</div>
<div class="step-body">
<div class="step-title">Увидеть результат</div>
<p>Показание доходит в базу за ~1 секунду. Посмотреть — запросом или
в консоли:</p>
<div class="hint-box">
<code>curl -H "Authorization: Bearer $TOKEN" \<br>
"https://iot.containerk8s.dev.nubes.ru/v1/namespaces/myhome/iot/telemetry?limit=50"</code>
</div>
<a class="btn-hero" href="/console">Открыть консоль →</a>
</div>
@@ -272,17 +295,17 @@ pre code { background: none; border: none; padding: 0; }
<div class="card-header">MQTT endpoint (exqx)</div>
<div class="card-body">
<p style="color:#374151">
<code>https://exqx.containerk8s.dev.nubes.ru</code> — это MQTT-брокер
(EMQX), а не веб-страница. Точка подключения — только
<code>/mqtt</code> через WebSocket. Открытие этого адреса в браузере
или HTTP-запрос вернёт <code>400</code>это штатное поведение
(нет WebSocket-подпротокола <code>mqtt</code>).
<code>exqx.containerk8s.dev.nubes.ru</code> — это MQTT-брокер, а не
веб-страница. Точка подключения — только <code>/mqtt</code> через
WebSocket. Если открыть этот адрес в браузере, вернётся
<code>400</code>так и должно быть: браузер не использует
WebSocket-подпротокол <code>mqtt</code>. Подключаться нужно MQTT-
клиентом (paho-mqtt, mqtt.js и т.п.), как в шаге 3.
</p>
<p style="color:#374151">
Ограничения: размер сообщения — до ~250 КБ (лимит SQS 256 КБ);
платформа может разрывать внешние WebSocket-соединения примерно раз
в 150 секунд — устройства должны автоматически переподключаться
(для QoS 1 повторы выполняются клиентом).
Ограничения: сообщение — до ~250 КБ; платформа может разрывать
внешние WebSocket-соединения примерно раз в 150 секунд — клиент
должен уметь переподключаться (в paho это делается автоматически).
</p>
</div>
</div>