Имплементация поддержки любых лампочек #16

Open
opened 2026-05-21 22:56:40 +07:00 by artem.kokos · 0 comments
Owner

Multi-Provider Architecture

Цель: развить ignis-core из WiZ-only сервера в ядро, которое умеет работать и с локальными устройствами, и с внешними платформами вроде Яндекса и Tuya, не превращаясь в свалку условностей.

Базовый принцип

Нужно разделить:

  • core — единая доменная модель, API, группы, расписания, аудит, web UI;
  • providers — конкретные источники устройств и команд;
  • transport/protocol — низкоуровневые детали WiZ, Yandex API, Tuya API и т.д.

Ключевая идея: не все устройства являются "лампочкой в локальной сети".
WiZ — локальный провайдер.
Yandex Smart Home и Tuya — облачные провайдеры.

Чего делать не надо

  • не пытаться засунуть Яндекс и Tuya в текущий WizDriver;
  • не делать вид, что discovery одинаково работает для LAN и cloud;
  • не смешивать device ID локальной лампы, provider device ID и UI/group ID в одну сущность;
  • не делать “магический if provider == x” по всему проекту.

Целевая модель

Нужны три уровня сущностей.

1. Provider

Источник устройств и команд.

Примеры:

  • wiz_local
  • yandex_cloud
  • tuya_cloud

Provider отвечает за:

  • перечисление доступных устройств;
  • получение состояния устройства;
  • применение команды;
  • health/auth status;
  • опционально: провайдерские группы, сцены, capabilities.

2. Provider Device

Сырая сущность провайдера.

Пример полей:

  • provider_name
  • provider_device_id
  • display_name
  • room
  • capabilities
  • online
  • raw_payload

Это не обязательно то, что напрямую видит пользователь в API.

3. Managed Device

Нормализованная сущность внутри ignis-core, с которой работают:

  • группы;
  • расписания;
  • аудит;
  • web UI.

Пример полей:

  • id — внутренний стабильный ID, например wiz:192.168.0.25 или yandex:abc123;
  • provider
  • provider_device_id
  • name
  • room
  • device_type
  • supported_features
  • connectivity_kindlocal / cloud
  • online
  • state_snapshot

Контракт провайдера

Нужен единый интерфейс уровня Python-кода.

Примерно такой:

  • provider_name() -> str
  • kind() -> "local" | "cloud"
  • list_devices() -> list[ProviderDevice]
  • get_device_state(provider_device_id) -> DeviceState
  • apply_command(provider_device_id, command) -> CommandResult
  • blink(provider_device_id) -> CommandResult | Unsupported
  • list_scenes(provider_device_id) -> list[Scene] | Unsupported
  • healthcheck() -> ProviderHealth

Опционально:

  • list_provider_groups()
  • apply_group_command()

Но groups лучше держать в ядре, а не размазывать по провайдерам, если нет жёсткой необходимости.

Командная модель

Вместо текущей привязки к WiZ-командам ядро должно оперировать нормализованной моделью.

Пример:

  • state: bool | None
  • brightness: int | None
  • temp: int | None
  • rgb: {r,g,b} | None
  • scene: str | None

Дальше каждый провайдер сам маппит это:

  • WiZ -> UDP payload;
  • Tuya -> DP/functions;
  • Yandex -> capabilities/actions API.

Discovery и синхронизация

Нужно отказаться от предположения, что discovery = сетевой скан.

Правильно так:

  • для wiz_local: активный LAN discovery;
  • для yandex_cloud: sync/import devices;
  • для tuya_cloud: sync/import devices.

То есть вместо одного discovery_service в будущем должен появиться общий orchestration слой:

  • provider sync manager

Он запускает:

  • LAN refresh для локальных провайдеров;
  • poll/sync для облачных провайдеров.

Группы

Группы лучше оставить собственными, на уровне ignis-core.

Почему:

  • одинаковая логика для всех провайдеров;
  • единые расписания;
  • единый UI;
  • не надо зависеть от того, есть ли у конкретной платформы "родные группы".

При этом допустимо потом добавить:

  • provider_group_id как optional mapping,

но не делать это основой дизайна.

Аудит и расписания

Аудит и расписания должны работать только через нормализованный слой.

То есть:

  • API получает команду;
  • ядро валидирует её;
  • находит ManagedDevice или группу;
  • разруливает по провайдерам;
  • пишет audit log уже с внутренними device_id и group_id.

Так мы не привязываем event log к деталям Tuya или Яндекса.

Конфиг и секреты

Нужно вводить provider-level конфиг.

Примеры env:

  • IGNIS_WIZ_ENABLED=true
  • IGNIS_YANDEX_ENABLED=false
  • IGNIS_YANDEX_CLIENT_ID=...
  • IGNIS_YANDEX_CLIENT_SECRET=...
  • IGNIS_TUYA_ENABLED=false
  • IGNIS_TUYA_ACCESS_ID=...
  • IGNIS_TUYA_ACCESS_SECRET=...
  • IGNIS_TUYA_REGION=eu

Токены лучше хранить не в .env, а в БД или отдельном secure storage, если появится OAuth/user binding.

API-слой

Снаружи API лучше оставить максимально стабильным.

Желательное поведение:

  • GET /devices по-прежнему отдаёт единый список устройств;
  • POST /control/device/{device_id} не знает о конкретном провайдере;
  • GET /devices/groups и schedules работают как сейчас.

Но стоит добавить новые служебные эндпоинты:

  • GET /providers
  • POST /providers/{provider}/sync
  • GET /providers/{provider}/health
  • POST /providers/yandex/connect
  • POST /providers/tuya/connect

Если появится OAuth или облачная авторизация, без этого будет больно.

Web UI

UI не должен знать детали протокола.

Но ему пригодятся:

  • provider badge;
  • признак local / cloud;
  • provider health;
  • экран подключения облачных интеграций;
  • ручной sync для cloud providers.

Рекомендуемая структура файлов

Файлы, которые будут затронуты крепко

  • app/core/state.py
  • app/core/discovery.py
  • app/api/routes/devices.py
  • app/api/routes/control.py
  • app/api/routes/system.py
  • app/api/schemas.py
  • app/models/device.py
  • main.py
  • static/app.js
  • static/index.html

Почему:

  • сейчас они завязаны на WiZ-first модель;
  • там зашиты предположения про локальный discovery;
  • там же будет самое большое давление от новой нормализованной модели устройств.

Файлы, которые, скорее всего, тоже придётся менять

  • app/core/server_info.py
  • app/api/routes/stats.py
  • app/core/scheduler.py
  • tests/test_p0_security_and_control.py
  • tests/test_p1_discovery.py
  • tests/test_p1_ui_security.py

Почему:

  • появятся provider-specific health/status поля;
  • discovery станет шире, чем просто LAN;
  • расписания и аудит должны корректно пережить multi-provider execution.

Новые файлы и директории, которые стоит создать

Core abstraction

  • app/core/providers/base.py
  • app/core/providers/registry.py
  • app/core/providers/models.py
  • app/core/providers/sync_manager.py

Provider implementations

  • app/providers/wiz_local/provider.py
  • app/providers/wiz_local/mapper.py
  • app/providers/yandex_cloud/provider.py
  • app/providers/yandex_cloud/client.py
  • app/providers/yandex_cloud/mapper.py
  • app/providers/tuya_cloud/provider.py
  • app/providers/tuya_cloud/client.py
  • app/providers/tuya_cloud/mapper.py

Persistence for provider accounts/config

  • app/models/provider_account.py
  • app/models/provider_state.py

API

  • app/api/routes/providers.py

Tests

  • tests/test_p0_provider_registry.py
  • tests/test_p0_yandex_provider.py
  • tests/test_p0_tuya_provider.py
  • tests/test_p1_multi_provider_control.py

Миграция по этапам

Этап 1. Внутренний abstraction layer

Сначала без новых интеграций:

  • обернуть текущий WiZ в provider interface;
  • оставить существующее API;
  • убедиться, что ничего не сломалось.

Это самый важный этап.

Этап 2. Provider registry

  • зарегистрировать wiz_local через registry;
  • перевести devices/control/system на работу через registry.

Этап 3. Cloud provider skeletons

  • добавить yandex_cloud и tuya_cloud без полноценного UI;
  • только health, sync, list devices.

Этап 4. Команды и статус

  • включить get_state и apply_command;
  • закрыть аудит и расписания для multi-provider paths.

Этап 5. UI и provider management

  • вкладка провайдеров;
  • статус интеграций;
  • ручной sync;
  • отображение источника устройств.

Главный архитектурный критерий

Если после внедрения нового провайдера приходится лезть:

  • в control.py,
  • в devices.py,
  • в UI-логику команд,

то архитектура всё ещё плохая.

Правильная цель: новый провайдер добавляется в основном через:

  • новый пакет провайдера;
  • регистрацию в registry;
  • provider-specific config;
  • provider-specific tests.

Итог

Правильная архитектура для ignis-core — это не “ещё один драйвер рядом с WiZ”, а:

  • единое ядро;
  • нормализованная модель устройств;
  • provider adapters для локальных и облачных источников;
  • отдельный orchestration слой для sync/discovery;
  • стабильное внешнее API поверх этого.

Только так поддержка Яндекса, Tuya и следующих платформ не превратится в набор if/else по всему проекту.

# Multi-Provider Architecture Цель: развить `ignis-core` из WiZ-only сервера в ядро, которое умеет работать и с локальными устройствами, и с внешними платформами вроде Яндекса и Tuya, не превращаясь в свалку условностей. ## Базовый принцип Нужно разделить: - `core` — единая доменная модель, API, группы, расписания, аудит, web UI; - `providers` — конкретные источники устройств и команд; - `transport/protocol` — низкоуровневые детали WiZ, Yandex API, Tuya API и т.д. Ключевая идея: **не все устройства являются "лампочкой в локальной сети"**. WiZ — локальный провайдер. Yandex Smart Home и Tuya — облачные провайдеры. ## Чего делать не надо - не пытаться засунуть Яндекс и Tuya в текущий `WizDriver`; - не делать вид, что `discovery` одинаково работает для LAN и cloud; - не смешивать device ID локальной лампы, provider device ID и UI/group ID в одну сущность; - не делать “магический if provider == x” по всему проекту. ## Целевая модель Нужны три уровня сущностей. ### 1. Provider Источник устройств и команд. Примеры: - `wiz_local` - `yandex_cloud` - `tuya_cloud` Provider отвечает за: - перечисление доступных устройств; - получение состояния устройства; - применение команды; - health/auth status; - опционально: провайдерские группы, сцены, capabilities. ### 2. Provider Device Сырая сущность провайдера. Пример полей: - `provider_name` - `provider_device_id` - `display_name` - `room` - `capabilities` - `online` - `raw_payload` Это не обязательно то, что напрямую видит пользователь в API. ### 3. Managed Device Нормализованная сущность внутри `ignis-core`, с которой работают: - группы; - расписания; - аудит; - web UI. Пример полей: - `id` — внутренний стабильный ID, например `wiz:192.168.0.25` или `yandex:abc123`; - `provider` - `provider_device_id` - `name` - `room` - `device_type` - `supported_features` - `connectivity_kind` — `local` / `cloud` - `online` - `state_snapshot` ## Контракт провайдера Нужен единый интерфейс уровня Python-кода. Примерно такой: - `provider_name() -> str` - `kind() -> "local" | "cloud"` - `list_devices() -> list[ProviderDevice]` - `get_device_state(provider_device_id) -> DeviceState` - `apply_command(provider_device_id, command) -> CommandResult` - `blink(provider_device_id) -> CommandResult | Unsupported` - `list_scenes(provider_device_id) -> list[Scene] | Unsupported` - `healthcheck() -> ProviderHealth` Опционально: - `list_provider_groups()` - `apply_group_command()` Но groups лучше держать в ядре, а не размазывать по провайдерам, если нет жёсткой необходимости. ## Командная модель Вместо текущей привязки к WiZ-командам ядро должно оперировать нормализованной моделью. Пример: - `state: bool | None` - `brightness: int | None` - `temp: int | None` - `rgb: {r,g,b} | None` - `scene: str | None` Дальше каждый провайдер сам маппит это: - WiZ -> UDP payload; - Tuya -> DP/functions; - Yandex -> capabilities/actions API. ## Discovery и синхронизация Нужно отказаться от предположения, что discovery = сетевой скан. Правильно так: - для `wiz_local`: активный LAN discovery; - для `yandex_cloud`: `sync/import devices`; - для `tuya_cloud`: `sync/import devices`. То есть вместо одного `discovery_service` в будущем должен появиться общий orchestration слой: - `provider sync manager` Он запускает: - LAN refresh для локальных провайдеров; - poll/sync для облачных провайдеров. ## Группы Группы лучше оставить собственными, на уровне `ignis-core`. Почему: - одинаковая логика для всех провайдеров; - единые расписания; - единый UI; - не надо зависеть от того, есть ли у конкретной платформы "родные группы". При этом допустимо потом добавить: - `provider_group_id` как optional mapping, но не делать это основой дизайна. ## Аудит и расписания Аудит и расписания должны работать только через нормализованный слой. То есть: - API получает команду; - ядро валидирует её; - находит `ManagedDevice` или группу; - разруливает по провайдерам; - пишет audit log уже с внутренними `device_id` и `group_id`. Так мы не привязываем event log к деталям Tuya или Яндекса. ## Конфиг и секреты Нужно вводить provider-level конфиг. Примеры env: - `IGNIS_WIZ_ENABLED=true` - `IGNIS_YANDEX_ENABLED=false` - `IGNIS_YANDEX_CLIENT_ID=...` - `IGNIS_YANDEX_CLIENT_SECRET=...` - `IGNIS_TUYA_ENABLED=false` - `IGNIS_TUYA_ACCESS_ID=...` - `IGNIS_TUYA_ACCESS_SECRET=...` - `IGNIS_TUYA_REGION=eu` Токены лучше хранить не в `.env`, а в БД или отдельном secure storage, если появится OAuth/user binding. ## API-слой Снаружи API лучше оставить максимально стабильным. Желательное поведение: - `GET /devices` по-прежнему отдаёт единый список устройств; - `POST /control/device/{device_id}` не знает о конкретном провайдере; - `GET /devices/groups` и schedules работают как сейчас. Но стоит добавить новые служебные эндпоинты: - `GET /providers` - `POST /providers/{provider}/sync` - `GET /providers/{provider}/health` - `POST /providers/yandex/connect` - `POST /providers/tuya/connect` Если появится OAuth или облачная авторизация, без этого будет больно. ## Web UI UI не должен знать детали протокола. Но ему пригодятся: - provider badge; - признак `local` / `cloud`; - provider health; - экран подключения облачных интеграций; - ручной `sync` для cloud providers. ## Рекомендуемая структура файлов ### Файлы, которые будут затронуты крепко - `app/core/state.py` - `app/core/discovery.py` - `app/api/routes/devices.py` - `app/api/routes/control.py` - `app/api/routes/system.py` - `app/api/schemas.py` - `app/models/device.py` - `main.py` - `static/app.js` - `static/index.html` Почему: - сейчас они завязаны на WiZ-first модель; - там зашиты предположения про локальный discovery; - там же будет самое большое давление от новой нормализованной модели устройств. ### Файлы, которые, скорее всего, тоже придётся менять - `app/core/server_info.py` - `app/api/routes/stats.py` - `app/core/scheduler.py` - `tests/test_p0_security_and_control.py` - `tests/test_p1_discovery.py` - `tests/test_p1_ui_security.py` Почему: - появятся provider-specific health/status поля; - discovery станет шире, чем просто LAN; - расписания и аудит должны корректно пережить multi-provider execution. ### Новые файлы и директории, которые стоит создать #### Core abstraction - `app/core/providers/base.py` - `app/core/providers/registry.py` - `app/core/providers/models.py` - `app/core/providers/sync_manager.py` #### Provider implementations - `app/providers/wiz_local/provider.py` - `app/providers/wiz_local/mapper.py` - `app/providers/yandex_cloud/provider.py` - `app/providers/yandex_cloud/client.py` - `app/providers/yandex_cloud/mapper.py` - `app/providers/tuya_cloud/provider.py` - `app/providers/tuya_cloud/client.py` - `app/providers/tuya_cloud/mapper.py` #### Persistence for provider accounts/config - `app/models/provider_account.py` - `app/models/provider_state.py` #### API - `app/api/routes/providers.py` #### Tests - `tests/test_p0_provider_registry.py` - `tests/test_p0_yandex_provider.py` - `tests/test_p0_tuya_provider.py` - `tests/test_p1_multi_provider_control.py` ## Миграция по этапам ### Этап 1. Внутренний abstraction layer Сначала без новых интеграций: - обернуть текущий WiZ в provider interface; - оставить существующее API; - убедиться, что ничего не сломалось. Это самый важный этап. ### Этап 2. Provider registry - зарегистрировать `wiz_local` через registry; - перевести `devices/control/system` на работу через registry. ### Этап 3. Cloud provider skeletons - добавить `yandex_cloud` и `tuya_cloud` без полноценного UI; - только health, sync, list devices. ### Этап 4. Команды и статус - включить `get_state` и `apply_command`; - закрыть аудит и расписания для multi-provider paths. ### Этап 5. UI и provider management - вкладка провайдеров; - статус интеграций; - ручной sync; - отображение источника устройств. ## Главный архитектурный критерий Если после внедрения нового провайдера приходится лезть: - в `control.py`, - в `devices.py`, - в UI-логику команд, то архитектура всё ещё плохая. Правильная цель: новый провайдер добавляется в основном через: - новый пакет провайдера; - регистрацию в registry; - provider-specific config; - provider-specific tests. ## Итог Правильная архитектура для `ignis-core` — это не “ещё один драйвер рядом с WiZ”, а: - единое ядро; - нормализованная модель устройств; - provider adapters для локальных и облачных источников; - отдельный orchestration слой для sync/discovery; - стабильное внешнее API поверх этого. Только так поддержка Яндекса, Tuya и следующих платформ не превратится в набор `if/else` по всему проекту.
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: artem.kokos/ignis-core#16