Newer
Older
smart-home-server / .claude / plans / ota-phase1.md

План реализации Phase 1: Серверная часть OTA обновлений прошивки

Цель

Создать серверный каталог прошивок, API для управления ими, и механизм push-OTA на устройства.

Изменения

1. Конфигурация (server/SHServ/config.php)

  • Добавить поле firmwares_dir — путь к директории с ZIP-архивами прошивок.
  • Значение по умолчанию: __DIR__ . '/../../firmwares'.
  • Директория должна автоматически создаваться при старте (в FirmwareCatalog::__construct).

2. Каталог прошивок (server/SHServ/Tools/FirmwareCatalog.php)

Новый класс, отвечающий за сканирование, кэширование и поиск прошивок.

Формат манифеста (manifest.json внутри ZIP):

{
  "id": "relay-1.3.0-esp8266-x8",
  "device_type": "relay",
  "platform": "esp8266",
  "channels": 8,
  "version": "1.3.0",
  "core_version": "1.5.0",
  "bin_filename": "relay.ino.bin",
  "description": "...",
  "changelog": "..."
}
  • id, device_type, version, bin_filename — обязательные.
  • platform, channels — опциональные; если указаны, участвуют в matching.

API класса:

  • scan(): array — сканирует firmwares_dir/*.zip, распаковывает каждый ZIP (временно), читает manifest.json, валидирует, сохраняет в статический кэш. Возвращает кэш.
  • getAll(): array — возвращает все записи каталога (массив манифестов).
  • getById(string $id): ?array — одна запись каталога по ID.
  • getBinPath(string $id): ?string — распаковывает .bin из ZIP во временную директорию, возвращает абсолютный путь к файлу. Cleanup — по желанию вызывающего.
  • findCompatible(array $deviceAbout): array — фильтрует кэш по device_type, platform, channels, и возвращает только те прошивки, чья version > текущей версии устройства. Сравнение версий — компонентное X.Y.Z.
  • clearCache(): void — сброс кэша.

Распаковка ZIP:

  • Первичный метод: ZipArchive (PHP встроенный).
  • Fallback: exec('unzip -o ...') во временную директорию (sys_get_temp_dir() . '/shserv_firmwares/...').
  • После чтения манифеста — временная директория удаляется.

3. DeviceAPI (server/SHServ/Tools/DeviceAPI/Base.php)

Добавить метод:

public function updateFirmware(string $binPath): array
  • Отправляет POST /update на устройство через multipart/form-data (CURLFile).
  • Таймаут 60 секунд (OTA длительная операция).
  • Отправляет Bearer-токен, если есть.
  • Возвращает массив ['http_code' => int, 'raw' => string, 'error' => string|null].
  • Не использует retry-логику (OTA не идемпотентна).

4. Routes (server/SHServ/Routes/FirmwareRESTAPI_v1.php)

Новый trait с endpoint'ами:

URI routes:

  • GET /api/v1/firmwaresFirmwareRESTAPIController@firmwares_list
  • GET /api/v1/firmwares/id/$firmware_idFirmwareRESTAPIController@firmware_detail
  • GET /api/v1/firmwares/id/$firmware_id/downloadFirmwareRESTAPIController@firmware_download
  • GET /api/v1/devices/id/$device_id/firmware-compatibilityFirmwareRESTAPIController@device_firmware_compatibility

POST routes:

  • POST /api/v1/firmwares/refresh (без body) → FirmwareRESTAPIController@firmware_refresh
  • POST /api/v1/devices/update-firmware (device_id, firmware_id) → FirmwareRESTAPIController@device_update_firmware

5. Controller (server/SHServ/Controllers/FirmwareRESTAPIController.php)

Новый контроллер, расширяет \SHServ\Middleware\Controller.

Методы:

  • firmwares_list() — возвращает массив всех манифестов.
  • firmware_detail($firmware_id) — один манифест по ID.
  • firmware_download($firmware_id) — распаковывает .bin, отдаёт Content-Type: application/octet-stream через readfile(), затем cleanup и exit.
  • firmware_refresh() — вызывает $catalog->scan(), возвращает success.
  • device_firmware_compatibility($device_id) — берёт устройство, запрашивает /about, передаёт в findCompatible().
  • device_update_firmware($device_id, $firmware_id) — валидация, проверка совместимости, вызов updateFirmware(), cleanup temp bin, возврат результата.

Обработка ошибок:

  • firmware_not_found — прошивка не найдена в каталоге.
  • firmware_not_compatible — прошивка не подходит для устройства.
  • ota_failed — устройство вернуло HTTP != 200 или cURL ошибка.

6. Интеграция маршрутов (server/SHServ/Routes.php)

  • Добавить use \SHServ\Routes\FirmwareRESTAPI_v1;.
  • В routes_init() вызвать $this->firmware_restapi_uri_routes(), $this->firmware_restapi_post_routes().

7. Локализация (server/SHServ/text-msgs.php)

Добавить алиасы:

  • firmware_not_found → "Прошивка не найдена"
  • firmware_not_compatible → "Прошивка не совместима с устройством"
  • ota_failed → "Не удалось обновить прошивку на устройстве"

8. Autoload

  • После создания новых PHP-классов запустить composer dump-autoload в server/.

9. База данных

  • Изменений в схему БД не требуется. Каталог полностью in-memory, на основе файловой системы.

Последовательность реализации

  1. config.php — добавить firmwares_dir.
  2. FirmwareCatalog.php — создать класс.
  3. Base.php — добавить updateFirmware().
  4. FirmwareRESTAPI_v1.php — trait с маршрутами.
  5. FirmwareRESTAPIController.php — контроллер.
  6. Routes.php — подключить trait.
  7. text-msgs.php — добавить сообщения об ошибках.
  8. composer dump-autoload.
  9. Тестовый ZIP-архив (вручную) для проверки.

Риски / Открытые вопросы

  • Таймаут OTA: 60 секунд достаточно для ESP8266/ESP32 по Wi-Fi? Если нет, увеличим до 120.
  • Cleanup temp файлов: при ошибках в device_update_firmware и firmware_download нужно не забыть удалить распакованный .bin. Будем использовать finally или inline cleanup.
  • Права доступа: endpoint'ы пока без middleware-авторизации (как и остальные API), позже подключим.