# Virtual Device Emulator

Эмуляторы устройств умного дома на Python + Flask. Полностью повторяют REST-контракт реальных ESP8266/ESP32 устройств и позволяют разрабатывать и тестировать серверную логику без железа.

## Зачем нужен

- Разрабатывать automation-скрипты (`ControlScripts`) без реальных устройств
- Тестировать серверные API: `/api/v1/devices/setup/new-device`, `/devices/action`, `/events/new`
- Проверять end-to-end цепочку: виртуальная кнопка → событие на сервер → скрипт → виртуальное реле
- Отлаживать Vue-клиент: устройства отображаются в UI как настоящие

## Установка

```bash
cd tools/virtual_devices
python3 -m venv .venv
source .venv/bin/activate  # .venv\Scripts\activate на Windows
pip install -r requirements.txt
```

## Быстрый старт

```bash
# 1. Создать реле и кнопки
python cli.py create --type relay --alias virt_relay --name "Виртуальное реле" --port 9001 --server-url http://smart-home-serv.local
python cli.py create --type button --alias virt_btn --name "Виртуальные кнопки" --port 9002 --server-url http://smart-home-serv.local

# 2. Запустить
python cli.py start --alias virt_relay
python cli.py start --alias virt_btn

# 3. Зарегистрировать в сервере (они должны быть в режиме setup)
python cli.py register --alias virt_relay --server-url http://smart-home-serv.local
python cli.py register --alias virt_btn --server-url http://smart-home-serv.local

# 4. Посмотреть список
python cli.py list

# 5. Открыть Web UI
# http://127.0.0.1:9001/  — реле
# http://127.0.0.1:9002/  — кнопки
```

## CLI

### `create`
Создаёт файл состояния устройства в `devices/<alias>.json`.

```bash
python cli.py create \
  --type relay|button \
  --alias virt_relay \
  --name "Виртуальное реле" \
  --channels 4 \
  --port 9001 \
  --server-url http://smart-home-serv.local
```

### `start`
Запускает Flask-эмулятор как background-процесс.

```bash
python cli.py start --alias virt_relay [--host 0.0.0.0] [--port 9001]
```

### `stop`
Убивает процесс по PID-файлу.

```bash
python cli.py stop --alias virt_relay
```

### `list`
Показывает все виртуальные устройства и статус (`running` / `stopped`).

```bash
python cli.py list
```

### `status`
Выводит JSON-состояние устройства (каналы, токен, режим и т.д.).

```bash
python cli.py status --alias virt_relay
```

### `click`
Симулирует нажатие кнопки (для `button`-типа). Шлёт `POST /simulate-event` на эмулятор.

```bash
python cli.py click --alias virt_btn --channel 0
```

### `register`
Регистрирует устройство в PHP-сервере через стандартный API `POST /api/v1/devices/setup/new-device`.

```bash
python cli.py register --alias virt_relay --server-url http://smart-home-serv.local
```

Устройство должно быть в режиме `setup`. Команда:
1. Проверяет `/about`
2. Отправляет `POST /api/v1/devices/setup/new-device` на сервер
3. Сервер сам устанавливает токен (`/set_token`) и переводит устройство в `normal`

### `remove`
Удаляет файл состояния. Устройство должно быть остановлено.

```bash
python cli.py remove --alias virt_relay
```

## Web UI

Каждый эмулятор отдаёт UI на корневом URL (`GET /`).

### Relay
- Тумблеры **Turn ON / Turn OFF** для каждого канала
- Зелёная подсветка — канал `on`
- Автообновление статуса каждые 2 секунды

### Button
- Кнопки **Click** для каждого канала
- Последнее событие и время
- Автообновление статуса каждые 2 секунды

## Ручное управление через curl

```bash
# /about — всегда без авторизации
curl http://127.0.0.1:9001/about | python3 -m json.tool

# Установить токен (в режиме setup)
curl -X POST http://127.0.0.1:9001/set_token \
  -H "Content-Type: application/json" \
  -d '{"token":"debug123"}'

# Получить статус (normal — требует Bearer)
curl http://127.0.0.1:9001/status \
  -H "Authorization: Bearer debug123"

# Переключить реле
curl -X POST http://127.0.0.1:9001/action \
  -H "Authorization: Bearer debug123" \
  -H "Content-Type: application/json" \
  -d '{"action":"set_state","params":{"channel":0,"state":"on"}}'

# Нажать кнопку
curl -X POST http://127.0.0.1:9002/simulate-event \
  -H "Content-Type: application/json" \
  -d '{"event_name":"click","channel":0}'

# Сбросить в setup
curl -X POST http://127.0.0.1:9001/reset \
  -H "Authorization: Bearer debug123"
```

## Полный список эндпоинтов эмулятора

| Method | Path | Auth | Описание |
|--------|------|------|----------|
| GET | `/` | — | Web UI |
| GET | `/about` | — | Информация об устройстве |
| GET | `/status` | Bearer | Состояние каналов |
| POST | `/action` | Bearer | Управление (`set_state`, `simulate_click`) |
| POST | `/set_token` | — (setup) / Bearer (normal) | Установка токена |
| POST | `/reset` | Bearer | Сброс в `setup`, очистка токена |
| POST | `/reboot` | Bearer | Stub — заглушка |
| POST | `/set_device_name` | Bearer | Смена имени |
| GET | `/channels_schema` | — (setup) / Bearer (normal) | Схема 8 каналов |
| POST | `/set_channels_schema` | — (setup) / Bearer (normal) | Задать схему |
| POST | `/simulate-event` | — | Debug: ручной триггер события |

## Структура

```
virtual_devices/
├── cli.py              # Click CLI — управление устройствами
├── emulator.py         # Flask-приложение одного эмулятора
├── state.py            # JSON-хранилище DeviceState
├── device/
│   ├── __init__.py
│   ├── base.py         # Базовый класс: auth, channels_schema, reset
│   ├── relay.py        # 4 канала, set_state(on|off)
│   └── button.py       # 4 канала, trigger_click → POST /events/new
├── requirements.txt    # Flask, requests, click
├── devices/            # *.json — состояния (в .gitignore)
├── pids/               # *.pid — PID файлы (в .gitignore)
└── .gitignore          # .venv/, __pycache__/, *.pid
```

## End-to-end пример: нажатие кнопки → сервер → реле

```bash
# 1. Создать устройства
python cli.py create --type relay --alias relay_lamp --port 9001 --server-url http://smart-home-serv.local
python cli.py create --type button --alias btn_living --port 9002 --server-url http://smart-home-serv.local

# 2. Запустить
python cli.py start --alias relay_lamp
python cli.py start --alias btn_living

# 3. Зарегистрировать в сервере
python cli.py register --alias relay_lamp --server-url http://smart-home-serv.local
python cli.py register --alias btn_living --server-url http://smart-home-serv.local

# 4. Нажать кнопку
python cli.py click --alias btn_living --channel 0

# 5. Проверить, что реле переключилось (если скрипт настроен)
curl -s http://127.0.0.1:9001/status -H "Authorization: Bearer debug123" | python3 -m json.tool
```

## Ограничения

- Нет реальной Wi-Fi конфигурации (`/setup` — заглушка)
- `/reboot` — заглушка, процесс не перезапускается
- События от button — best-effort (`requests.post` с `timeout=3`, игнорируются ошибки)
- Состояние хранится в JSON-файле, без шифрования (только для локальной разработки)
- Нет OTA-обновлений прошивки
- Не эмулирует реальные задержки сети и потери пакетов
