Ignis Core

ignis-core — локальный FastAPI-сервер для WiZ-ламп и домашней автоматики вокруг них.

Что есть сейчас

  • discovery ламп по локальной сети с startup, manual и background refresh;
  • управление отдельной лампой и группой;
  • реальные status-опросы и blink для идентификации;
  • группы в SQLite;
  • one-shot и cron-расписания;
  • persisted metadata расписаний поверх APScheduler;
  • роли master, admin, guest;
  • гостевые API-ключи с revoke/activate;
  • event log и простая stats summary;
  • встроенный локальный UI из static/;
  • OpenAPI-экспорт в openapi.json.

Архитектура

  • main.py — инициализация FastAPI, security headers, router wiring, startup lifecycle.
  • app/api/routes/* — HTTP-маршруты.
  • app/core/discovery.py — выбор подсетей и UDP discovery WiZ.
  • app/core/state.py — in-memory runtime-state устройств и групп.
  • app/core/scheduler.py — APScheduler, reconciliation и cleanup old events.
  • app/models/* — SQLAlchemy-модели и Pydantic-схемы.
  • static/ — встроенный web UI без внешних CDN.

Запуск локально

python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cp deploy/ignis-core.env.example .env
uvicorn main:app --host 0.0.0.0 --port 8000

UI: http://<host>:8000/

Готовые файлы для systemd: deploy/README.md

Конфигурация

Минимальный набор:

IGNIS_API_KEY=change-me
IGNIS_INSTANCE_NAME=Home
APP_TIMEZONE=Asia/Novosibirsk
LOG_LEVEL=INFO
IGNIS_DATABASE_URL=sqlite+aiosqlite:///./ignis.db
IGNIS_SYNC_DATABASE_URL=sqlite:///./ignis.db

Параметры server metadata / versioning:

IGNIS_PUBLIC_BASE_URL=https://ignis.example.local
IGNIS_BUILD_VERSION=1.0.0
IGNIS_BUILD_DATE=2026-05-21T12:00:00Z
IGNIS_GIT_SHA=abc1234def56
  • IGNIS_INSTANCE_NAME — человекочитаемое имя инстанса, которое видно в UI и GET /system/info.
  • IGNIS_PUBLIC_BASE_URL — внешний URL сервера, если он стоит за reverse proxy или доступен по доменному имени.
  • IGNIS_BUILD_VERSION, IGNIS_BUILD_DATE, IGNIS_GIT_SHA — build metadata установленного сервера для диагностики и сверки версий.

Параметры discovery:

SCAN_NETWORK=192.168.0.0/24
DISCOVERY_INTERVAL_SECONDS=600
DISCOVERY_BACKGROUND_MISSING_THRESHOLD=2
DISCOVERY_ENV_MIN_PREFIX_LEN=16
DISCOVERY_AUTO_MIN_PREFIX_LEN=24

Параметры retention:

EVENT_LOG_RETENTION_DAYS=30

Как работает discovery

  • Если SCAN_NETWORK задан, сервер сканирует только указанные подсети.
  • Если SCAN_NETWORK пуст, DiscoveryService пытается выбрать private IPv4-сегменты обычных интерфейсов и избегает docker, tun, wg, tailscale и похожих интерфейсов.
  • При старте выполняется startup_refresh().
  • POST /devices/rescan делает ручной refresh и сразу удаляет оффлайн-устройства.
  • Background refresh работает циклически и удаляет устройство только после DISCOVERY_BACKGROUND_MISSING_THRESHOLD подряд пропусков.

Для хостов с VPN, несколькими NIC или нетипичной маршрутизацией SCAN_NETWORK лучше задавать явно.

Авторизация и роли

Заголовок: X-API-Key

  • master — значение IGNIS_API_KEY, полный доступ.
  • admin — ключ из БД, доступ к группам, расписаниям, stats и rescan.
  • guest — чтение и обычное управление светом.

Сервер работает в fail-closed: если IGNIS_API_KEY не задан, защищённые маршруты отвечают 503.

HTTP API

Основные маршруты:

  • GET /auth/me
  • GET /devices
  • GET /devices/groups
  • GET /devices/scenes
  • POST /devices/groups
  • DELETE /devices/groups/{group_id}
  • POST /devices/rescan
  • POST /control/device/{device_id}
  • POST /control/group/{group_id}
  • POST /control/device/{device_id}/blink
  • GET /control/device/{device_id}/status
  • GET /control/group/{group_id}/status
  • POST /schedules/once
  • POST /schedules/cron
  • GET /schedules/tasks
  • DELETE /schedules/{job_id}
  • GET /api-keys
  • POST /api-keys
  • POST /api-keys/revoke
  • POST /api-keys/activate
  • GET /stats/summary
  • GET /stats/log
  • GET /system/info

control/* и schedules/* принимают JSON body.

Поддерживаемые параметры команды:

  • state
  • brightness
  • scene
  • temp
  • r, g, b

Валидация:

  • brightness: 10..100
  • temp: 2200..6500
  • r/g/b: 0..255
  • scene, temp и rgb взаимоисключаемы
  • r, g, b нужно передавать полной тройкой
  • для schedules/once нужно передать ровно одно из run_at или hours_from_now

Пример:

curl -X POST http://127.0.0.1:8000/control/group/bedroom \
  -H 'X-API-Key: change-me' \
  -H 'Content-Type: application/json' \
  -d '{"state":true,"brightness":60}'

Встроенный UI

  • лежит в static/index.html, static/app.js, static/ui.css;
  • использует только локальные ассеты;
  • не использует localStorage;
  • может хранить API-ключ только в sessionStorage текущей вкладки;
  • показывает build/server metadata текущего инстанса;
  • умеет базовое управление группами, расписания, API-ключи, stats/log и быстрый таймер на 4 часа.

Хранилище

SQLite-таблицы:

  • groups
  • api_keys
  • event_log
  • schedules
  • apscheduler_jobs
  • devices

Важно: реальным runtime-источником истины для онлайн-устройств остаётся in-memory state_manager.devices. Таблица devices пока не используется как полноценный persistent source of truth.

OpenAPI

Актуальная схема хранится в openapi.json.

Перегенерация:

.venv/bin/python -c 'import json, os, pathlib; os.environ.setdefault("IGNIS_API_KEY", "openapi-export"); from main import app; pathlib.Path("openapi.json").write_text(json.dumps(app.openapi(), ensure_ascii=False, indent=2) + "\n", encoding="utf-8")'

Тесты

На 2026-05-21 в tests/ лежит 29 unittest-сценариев.

Покрыто:

  • fail-closed auth и role checks;
  • lifecycle API-ключей;
  • control/status error handling и partial success;
  • validation для scene, control body и schedules body;
  • one-shot и cron-расписания;
  • reconciliation и миграция legacy jobs;
  • auto subnet selection для discovery;
  • background offline cleanup threshold;
  • manual rescan summary;
  • server metadata endpoint и отсутствие утечки секретов в нём;
  • security headers и локальные UI-ассеты;
  • stats summary без двойного счёта *_requested.

Команды:

.venv/bin/python -m compileall app tests main.py
timeout 120s .venv/bin/python -m unittest discover -s tests -v

Известные ограничения

  • discovery по-прежнему основан на переборе IP в подсетях;
  • миграций схемы БД нет, используется Base.metadata.create_all();
  • runtime-state устройств живёт в памяти процесса;
  • встроенный UI остаётся монолитным файлом без отдельной frontend-сборки;
  • stats — это audit/summary, а не полноценная аналитика.
Description
Self-hosted сервер для управления умными лампами WiZ по локальной сети
Readme MIT 664 KiB
v1.0.0 Latest
2026-05-21 22:22:47 +07:00
Languages
Python 59.3%
HTML 24%
JavaScript 14.2%
CSS 2.5%