HTTP API портала: привязка устройства (Link ↔ iDryer Portal)¶
Разработчик продукта: вы реализуете клиента этого API в своей прошивке моста (или используете слой из библиотеки), а не свой портал — вводный текст 00-for-product-developers.md.
Документ описывает контракт публичного backend iDryer Portal для прошивки LINK (и совместимых мостов): provision, PIN, claim, проверка статуса. Сверено с кодом backend (devices.controller.ts, devices.service.ts, mqtt-auth.service.ts) и прокси nginx (location /api/ → корень Nest).
Расширенные сценарии (матрица unlink/remove, угрозы provision): в репозитории портала docs/development/DEVICE_CLAIMING_PROTOCOL.md, docs/development/LINK_CLAIM_SCENARIOS.md.
Базовый URL¶
| Окружение | API (HTTPS) |
|---|---|
| Продакшен | https://portal.idryer.org/api |
| Локальная разработка (Nest без nginx) | http://localhost:3000 (пути без префикса /api) |
Пути ниже указаны относительно базы: например, POST /devices/provision → прод: https://portal.idryer.org/api/devices/provision.
Заголовок: Content-Type: application/json для POST.
Идентификатор serialNumber¶
Используется в provision, как username для MQTT и в префиксе топиков idryer/{serialNumber}/….
Формат (валидация ProvisionDeviceDto):
DEVICE_{mac}илиDEVICE_{mac}_{suffix}(типичный ESP32 Link),- либо ровно 16 hex-символов (
0-9A-Fa-f) — RP2040mcuSerial.
Строка должна совпадать с той, что мост объявляет облаку (и кладёт в MQTT client id / username).
1. POST /devices/provision¶
Auth: не требуется. Rate limit: 10 запросов / 60 с (throttle на контроллере).
Тело:
Ответ 200/201 (новый или существующий Link UNCLAIMED):
{
"deviceToken": "<секрет, хранить в NVS>",
"serialNumber": "<тот же serial>",
"isNew": true,
"isClaimed": false
}
provision для того же serial в состоянии UNCLAIMED возвращает тот же deviceToken, isNew: false.
Уже привязанное устройство (Link CLAIMED / BOUND или legacy Device с тем же serial): токен не выдаётся (безопасность):
LINK_CLAIM_SCENARIOS.md.
2. POST /devices/register¶
Auth: не требуется. Rate limit: 10 / 60 с.
Тело:
{
"token": "<deviceToken из provision>",
"serialNumber": "<опционально; обязателен, если записи Link по token ещё нет>"
}
Ответ A — нужен PIN на экране (200):
register в пределах TTL возвращает тот же PIN (обновляется lastSeen).
Ответ B — устройство уже привязано (recovery, 200):
check-claim по согласованной с прошивкой логике.
Ошибки: 400 — нет Link по token и не передан serialNumber (нужен сначала provision или serial в теле).
3. POST /devices/claim¶
Auth: обязателен — Authorization: Bearer <user JWT> (веб/приложение пользователя, не токен устройства).
Тело:
Успех (201):
Ошибки (типичные):
- 404 — неверный PIN,
- 400 — PIN истёк или устройство уже привязано,
- 401 — нет/невалидный пользовательский JWT.
После успешного claim запись Device создаётся в состоянии ожидания первого MQTT info (CLAIMED_PENDING_BIND), затем переходит в активное после bind — см. портал LINK_CLAIM_SCENARIOS.md.
4. GET /devices/check-claim/:token¶
Auth: не требуется. :token — Link.token (тот же deviceToken из NVS).
- Пока пользователь не завершил claim:
404и тело{ "claimed": false }. - После claim (Link
CLAIMED/BOUNDсactiveDryerId):200и{ "claimed": true, "deviceId": "<uuid>" }.
5. POST /devices/cleanup-expired¶
Служебная очистка просроченных UNCLAIMED Link (и legacy-таблицы). Auth: JWT пользователя с ролью ADMIN или SUPERUSER (не публичный endpoint).
Ответ: { "deleted": <number> }.
Связь с UART (MCU ↔ LINK)¶
На стороне MCU/LINK последовательность согласуется с Основные потоки: ClaimStart → HTTP provision/register → ClaimStatus (PIN) → пользователь вводит PIN в портале → опрос check-claim → ClaimComplete.
См. также¶
- MQTT API — аутентификация на брокере (username / password)
- Основные потоки — claiming в контексте UART
- Репозиторий портала:
docs/development/DEVICE_CLAIMING_PROTOCOL.md,docs/development/LINK_CLAIM_SCENARIOS.md,docs/development/PROVISION_SECURITY_DESIGN.md