# 11 · Устройства (BAS-IP, реле, эмулятор)

[← Home](Home.md)

Приложение управляет одним или несколькими устройствами открытия через
драйверы. Настройка — `config.device` или **Админка → Шлагбаум → Устройства**.

## Драйверы

### `basip` — панель BAS-IP Camdroid (`/api/v0`)

Официальный HTTP API панелей BAS-IP
([developers.bas-ip.com](https://developers.bas-ip.com/Camdroid-panels/)):

* **логин** → токен устройства (TTL ~1600 с, автоматическое обновление
  заранее, хранение в БД/файле/кэше на выбор — `device.token.store`);
* **открытие замка**: `access/general/lock/open/remote/accepted/{channel}`
  (endpoint и канал настраиваются, импульс `pulse_ms`);
* **статус**: SIP-регистрация, версия прошивки, время панели;
* **камера**: `security/ipcam` — snapshot (JPEG), proxy через приложение
  (скрывает панель от жителей) или прямой MJPEG-поток;
* **SIP вкл/выкл**, **перезагрузка** панели;
* таймауты/ретраи: `timeout 10 с`, `connect_timeout 4 с`, `retries 2`.

```php
'device' => [
    'driver'   => 'basip',
    'base_url' => 'http://192.168.1.20/api/v0/',
    'login'    => 'admin',
    'password' => '${secret:DEVICE_PASSWORD}',
    'lock'     => ['channel' => 1, 'channels' => [1 => 'Main gate']],
],
```

Пароль панели храните шифрованным: `bin/bas-ip secret:set DEVICE_PASSWORD`.
Для самоподписанных сертификатов — `verify_tls: false` или свой `ca_bundle`.

### `http-relay` — произвольный HTTP-контроллер

Для DIY-реле (ESP32/реле-борды), сторонних шлюзов и «умных» розеток:
настраиваются URL/метод/заголовки/тело для open/close/status, ожидаемый
ответ, таймауты. Подходит всему, что умеет принимать HTTP.

### `dryrun` — эмулятор

Все команды «выполняются», события пишутся, камера — заглушка. Режим для
разработки, демо и тестов; `doctor` предупреждает, если эмулятор включён
в production.

## Несколько устройств

`device.gates` — именованные устройства, наследующие общие настройки:

```php
'gates' => [
    'north' => ['base_url' => 'http://192.168.1.21/api/v0/', 'login' => 'admin',
                'password' => '${secret:DEVICE_NORTH_PASSWORD}'],
    'parking' => ['driver' => 'http-relay', 'open_url' => 'http://10.0.0.5/relay/2/pulse'],
],
```

В UI и API устройство выбирается параметром `device` (по умолчанию `main`);
журнал событий хранит, какое устройство сработало.

## Возможности (capabilities)

Драйвер объявляет, что умеет: `open`, `close`, `status`, `snapshot`, `sip`,
`reboot`. UI скрывает недоступные кнопки; API `/meta` публикует список.

## Камера и видеопоток

Камера **привязана к устройству (шлагбауму)** и настраивается в его карточке
(Админка → Шлагбаум → Устройства → «Камера шлагбаума и видеопоток») или
в `device.camera.*` / `device.gates.<slug>.camera.*`:

| Режим (`camera.mode`) | Как работает | Когда выбирать |
|---|---|---|
| `snapshot` (default) | приложение забирает JPEG-кадр у панели (`camera.endpoint`, у BAS-IP — `security/ipcam`) и отдаёт браузеру; JS обновляет кадр каждые `cache_ttl` сек | надёжный базовый вариант; учётные данные панели не видны браузеру |
| `proxy` | непрерывный MJPEG-поток транслируется **через сервер** (`/panel/camera/stream?device=…`): если задан `camera.stream_url` — прозрачный pass-through, иначе поток синтезируется из snapshot-кадров панели | панель/камера видны только серверу; плавное движение; занимает по одному PHP-процессу на зрителя (ограничение `?seconds=`, клиент переподключается сам) |
| `mjpeg` | браузер открывает `camera.stream_url` **напрямую** (`<img src>` с multipart/x-mixed-replace) | камера доступна клиентам по сети; нулевая нагрузка на сервер; хост потока автоматически добавляется в CSP (`img-src`/`media-src`) |
| `off` | видео выключено | — |

Потребители: **пульт жителя** `/remote` (камера выбранного шлагбаума, 30%
экрана, переключатель между воротами) и **панель оператора** `/panel`.
Список доступных камер — `GET /panel/cameras` (JSON, привязка device→камера).
Любой сбой видео деградирует мягко: кнопка открытия продолжает работать,
вместо видео — заглушка «Камера недоступна».

Снимок сохраняется в файл вручную: `bin/bas-ip gate:snapshot out.jpg`.
События журнала хранят текст (кто/когда/результат), а не фото — база
остаётся лёгкой, принцип минимизации данных соблюдается.

## Расписания и тихие часы

**Админка → Шлагбаум → Расписания**: окна (день недели, время), действие
(свободный проход / запрет гостевых / только свои), устройство. «Тихие часы»
(например 23:00–06:00) блокируют гостевые открытия; аварийное открытие
админом — всегда с явным подтверждением и отметкой `emergency` в журнале.

## Cooldown и защита от спама

`device.lock.cooldown_seconds` (3 с) — физический анти-даблтап; поверх —
rate-limit `gate_open` (12/мин на пользователя/IP). Повторное открытие
в cooldown возвращает `422 error_code=cooldown` с секундами до повтора.

## Диагностика устройства

* **UI**: карточка устройства → «Проверить» (login/status/snapshot), «Тест
  открытия».
* **CLI**: `gate:test`, `gate:status`, `gate:snapshot`, `health --deep`.
* **Журнал `device`**: каждый вызов с таймингом и результатом; ошибки сети —
  с телом ответа (секреты маскируются).

## Если панель недоступна

1. `gate:test` — что именно падает (сеть/логин/команда)?
2. Пинг/Telnet к `base_url`; панель и сервер в одной сети/VLAN?
3. Учётка панели жива? Пароль не меняли? (`secret:list`)
4. Журнал `device` — коды ответов (401 — логин, 404 — неверный endpoint
   для прошивки, timeouts — сеть).
5. Временно переключите устройство на `dryrun`, чтобы жители не остались
   без журнала, и чините сеть.

Шлагбаум при этом **не должен** зависеть от доступности приложения: держите
аппаратную кнопку/пульт у охраны — приложение добавляет учёт и удобство,
а не заменяет аварийное открытие.

---
Дальше: [12 · Безопасность](12-Security.md)
