Создать серверный каталог прошивок, API для управления ими, и механизм push-OTA на устройства.
server/SHServ/config.php)firmwares_dir — путь к директории с ZIP-архивами прошивок.__DIR__ . '/../../firmwares'.FirmwareCatalog::__construct).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 встроенный).exec('unzip -o ...') во временную директорию (sys_get_temp_dir() . '/shserv_firmwares/...').server/SHServ/Tools/DeviceAPI/Base.php)Добавить метод:
public function updateFirmware(string $binPath): array
POST /update на устройство через multipart/form-data (CURLFile).['http_code' => int, 'raw' => string, 'error' => string|null].server/SHServ/Routes/FirmwareRESTAPI_v1.php)Новый trait с endpoint'ами:
URI routes:
GET /api/v1/firmwares → FirmwareRESTAPIController@firmwares_listGET /api/v1/firmwares/id/$firmware_id → FirmwareRESTAPIController@firmware_detailGET /api/v1/firmwares/id/$firmware_id/download → FirmwareRESTAPIController@firmware_downloadGET /api/v1/devices/id/$device_id/firmware-compatibility → FirmwareRESTAPIController@device_firmware_compatibilityPOST routes:
POST /api/v1/firmwares/refresh (без body) → FirmwareRESTAPIController@firmware_refreshPOST /api/v1/devices/update-firmware (device_id, firmware_id) → FirmwareRESTAPIController@device_update_firmwareserver/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 ошибка.server/SHServ/Routes.php)use \SHServ\Routes\FirmwareRESTAPI_v1;.routes_init() вызвать $this->firmware_restapi_uri_routes(), $this->firmware_restapi_post_routes().server/SHServ/text-msgs.php)Добавить алиасы:
firmware_not_found → "Прошивка не найдена"firmware_not_compatible → "Прошивка не совместима с устройством"ota_failed → "Не удалось обновить прошивку на устройстве"composer dump-autoload в server/.config.php — добавить firmwares_dir.FirmwareCatalog.php — создать класс.Base.php — добавить updateFirmware().FirmwareRESTAPI_v1.php — trait с маршрутами.FirmwareRESTAPIController.php — контроллер.Routes.php — подключить trait.text-msgs.php — добавить сообщения об ошибках.composer dump-autoload.device_update_firmware и firmware_download нужно не забыть удалить распакованный .bin. Будем использовать finally или inline cleanup.