-
-
Notifications
You must be signed in to change notification settings - Fork 2
en Architecture
- Overview
- High-Level Architecture
- Boot Sequence
- Module System
- EventBus
- Module Bus
- Intent System
- Device Registry
- API Layer
- Cloud Sync
- Configuration
- Internationalization
- Integrity Agent
- Deployment
- Shutdown Sequence
- Further Reading
SelenaCore is a local-first smart home hub built on FastAPI, designed to run on low-power hardware such as Raspberry Pi. All automation logic, device management, and voice processing happen on the local machine. Cloud connectivity is optional and limited to heartbeat sync and remote command reception.
The entire stack runs as a single FastAPI process on port 80 that serves the REST API, the WebSocket endpoints, and the React SPA. HTTPS on port 443 is handled by a lightweight asyncio TLS proxy (~5 MB RAM). Total runtime footprint on a Raspberry Pi 4 is ~1.5 GB RAM for the entire stack.
There are 24 built-in SYSTEM modules, including the recently added climate, lights_switches, and update_manager.
Core technology stack:
| Component | Technology |
|---|---|
| Web framework | FastAPI (port 80) |
| Database | SQLite via SQLAlchemy 2.0 async |
| Async driver | aiosqlite |
| Event loop | Single asyncio loop |
| Entry point |
core/main.py (FastAPI lifespan) |
| Language | Python 3.11 |
+------------------------------------------------------------------+
| SelenaCore (single FastAPI :80) |
| |
| +------------------+ +------------------+ +--------------+ |
| | 24 SYSTEM | | EventBus | | Device | |
| | modules |<->| (asyncio.Queue) |<->| Registry | |
| | (in-process) | | | | (SQLite) | |
| +------------------+ +--------+---------+ +--------------+ |
| | |
| +--------+---------+ |
| | Module Bus | |
| | (WebSocket) | |
| +--------+---------+ |
| | |
| SyncManager (UI WebSocket /api/ui/sync) |
| Static SPA + PWA served from same process |
+----------------------------------+-------------------------------+
|
+--------------+--------------+
| | |
+----+----+ +----+----+ +------+------+
| Docker | | Docker | | Docker |
| Module | | Module | | Module |
| (user) | | (user) | | (user) |
+---------+ +---------+ +-------------+
HTTPS :443 ---> TLS proxy (asyncio, ~5 MB RAM) ---> :80
Separate process:
+----------------------------+
| Integrity Agent |
| SHA256 hash check / 30s |
| Safe mode enforcement |
+----------------------------+
The startup procedure is defined in the FastAPI lifespan handler in core/main.py. Steps execute in strict order:
1. _setup_logging()
| Read logging.yaml or fall back to basic config
v
2. Create SQLAlchemy async engine + tables
| SQLite database initialized
v
3. Inject session factory into sandbox
| System modules gain database access
v
4. EventBus.start()
| asyncio.Queue consumer begins
v
5. Publish core.startup event
| Listeners notified
v
6. CloudSync.start()
| Heartbeat loop begins (optional)
v
7. Scan system_modules/ -> load in-process -> mount routers
| 24 built-in modules activated
v
8. Scan modules/ -> start user modules
| Docker containers launched, bus connections accepted
v
9. SPA static files mounted, SyncManager initialized,
single process ready on :80
SelenaCore supports two distinct module types that share the same EventBus but differ fundamentally in how they run.
| Property | Value |
|---|---|
| Count | 24 built-in |
| Base class |
SystemModule (core/module_loader/system_module.py) |
| Execution | In-process via Python importlib
|
| Isolation | None (shared process) |
| RAM overhead | ~0 MB (no container) |
| EventBus access | Direct async callbacks (DirectSubscription) |
| Database access | Direct SQLAlchemy session |
| API surface | Optional FastAPI router at /api/ui/modules/{name}/
|
| Location |
system_modules/ directory |
Built-in system modules (24):
voice_core llm_engine ui_core
user_manager automation_engine scheduler
device_watchdog protocol_bridge notification_router
media_player presence_detection hw_monitor
backup_manager remote_access network_scanner
device_control energy_monitor update_manager
notify_push secrets_vault weather_service
climate lights_switches clock
| Module | Purpose |
|---|---|
voice_core |
STT (Vosk / Whisper), TTS (Piper), wake-word, speaker ID |
llm_engine |
Ollama local LLM, FastMatcher, 5-tier intent router with registry-aware prompt, cloud fallback |
ui_core |
React SPA + PWA, served from the unified Core process |
device_control |
Pluggable provider system (Tuya / Gree / Hue / ESPHome / MQTT) |
climate |
A/C and thermostat control panel grouped by room |
lights_switches |
Lights, switches, outlets — on/off, brightness, RGB |
energy_monitor |
Per-device power and kWh tracking with auto-routing |
automation_engine |
YAML rule engine: triggers → actions |
update_manager |
OTA updates from GitHub Releases with SHA256 verification |
scheduler |
Cron / interval / sunrise / sunset triggers |
user_manager |
Profiles, PIN, Face ID, audit log |
secrets_vault |
AES-256-GCM token and credential storage |
hw_monitor |
CPU temperature, RAM, disk, uptime polling |
media_player |
Internet radio, USB, SMB, Internet Archive |
protocol_bridge |
MQTT / Zigbee / Z-Wave / HTTP gateway |
weather_service |
Open-Meteo current and forecast (no API key) |
presence_detection |
ARP / Bluetooth / Wi-Fi MAC presence tracking |
device_watchdog |
Device availability monitoring (ICMP, MQTT/Zigbee heartbeat) |
notification_router |
Routes notifications to TTS / Telegram / Web Push / webhooks |
notify_push |
Web Push (VAPID) |
network_scanner |
ARP / mDNS / SSDP / Zigbee discovery |
clock |
Alarms, timers, reminders, world clock, stopwatch |
backup_manager |
Local USB / SD and E2E cloud backup |
remote_access |
Tailscale-based remote access |
| Property | Value |
|---|---|
| Base class |
SmartHomeModule (sdk/base_module.py) |
| Execution | Individual Docker containers |
| Communication | WebSocket Module Bus |
| Bus endpoint | ws://core/api/v1/bus?token=TOKEN |
| Individual ports | None -- all traffic through the single bus |
User module types:
| Type | Purpose |
|---|---|
| UI | Custom user interface panels |
| INTEGRATION | Third-party service connectors |
| DRIVER | Hardware/protocol device drivers |
| AUTOMATION | Custom automation logic |
| IMPORT_SOURCE | External data importers |
[Discovered]
|
v
[Installed] --module.installed-->
|
v
[Started] --module.started---> (EventBus subscription active)
|
v
[Running] <-- normal operation -->
|
v
[Stopped] --module.stopped--->
|
v
[Removed] --module.removed--->
Source: core/eventbus/bus.py
The EventBus is the central nervous system of SelenaCore. It is an asyncio.Queue-based publish/subscribe system with a maximum queue size of 10,000 messages and a drop-oldest overflow policy.
Publisher
|
v
+---+-----------+
| EventBus |
| (Queue: 10K) |
+---+-------+---+
| |
v v
Direct Module Bus
Subscr. WebSocket
(system) (user modules)
- DirectSubscription -- in-process async callbacks used by system modules. Zero serialization cost, microsecond delivery.
- Module Bus WebSocket -- events serialized to JSON and delivered over the WebSocket connection to user modules running in Docker containers.
All event types are defined in core/eventbus/types.py:
| Namespace | Events |
|---|---|
core.* |
startup, shutdown, integrity_violation, safe_mode_entered, safe_mode_exited |
device.* |
state_changed, registered, removed, offline, online, discovered |
module.* |
installed, started, stopped, error, removed |
sync.* |
command_received, command_ack, connection_lost, connection_restored |
voice.* |
wake_word, recognized, intent, response, privacy_on, privacy_off |
Protection rule: Events in the core.* namespace can only be published by the core process itself. Modules cannot emit core events.
Source: core/module_bus.py
The Module Bus is a CAN-bus-inspired communication layer that multiplexes all user module traffic through a single WebSocket endpoint.
- Core is the master node. Modules connect TO core, never the reverse.
-
Single endpoint:
/api/v1/bus-- no per-module ports. - Dual message queues per connection to separate critical and best-effort traffic.
| Message | Direction | Purpose |
|---|---|---|
announce |
module -> core | Module registers on connect |
re_announce |
module -> core | Module re-registers after reconnect |
announce_ack |
core -> module | Registration confirmed |
intent |
core -> module | Intent routed to handler |
intent_response |
module -> core | Handler returns result |
event |
bidirectional | EventBus event forwarding |
ping / pong
|
bidirectional | Keepalive |
api_request |
module -> core | Module calls core API |
api_response |
core -> module | Core returns API result |
shutdown |
core -> module | Graceful shutdown signal |
Each WebSocket connection maintains two independent queues:
Module Connection
+---------------------------------------+
| |
| Critical Queue (backpressure) |
| - Max size: 100 |
| - Used for: intent, api_request, |
| api_response, intent_response |
| - Blocks sender when full |
| |
| Event Queue (drop-oldest) |
| - Max size: 1000 |
| - Used for: event messages |
| - Drops oldest when full |
| |
+---------------------------------------+
This design ensures that a flood of non-critical events never blocks intent processing or API calls.
If a module fails to respond within 30 seconds, the bus activates a circuit breaker for that module. The module is temporarily excluded from intent routing until it recovers.
Each module type has a predefined set of allowed message types and event subscriptions. The bus enforces these permissions on every message.
Source: core/api/sync_manager.py, core/api/routes/ui.py
Real-time synchronization of UI state (theme, language, widget layout) across all connected clients via WebSocket /api/ui/sync.
| Property | Value |
|---|---|
| Endpoint | ws://host/api/ui/sync?v=<version> |
| Protocol | JSON messages with monotonic versioning |
| State | Settings (theme, language) + widget layout |
| On connect | Full snapshot (hello) or delta replay (replay) |
| Health check | Server ping every 5s, client pong required within 15s |
| Backend |
SyncManager singleton with deque(256) event log |
| Frontend | Zustand store connectSyncStream() with exponential backoff reconnect |
| Kiosk safety |
useConnectionHealth hook — force reload after 60s of silence |
The SPA (React) and all API endpoints are served from a single process on port 80. HTTPS on port 443 is handled by a lightweight TLS proxy (~5 MB RAM).
See UI Sync Architecture for full protocol details and migration notes.
Source: system_modules/llm_engine/intent_router.py
The intent router uses a 5-tier cascade. Each tier is tried in order; the first match wins. The whole pipeline operates on English internally — non-English input is translated by Tier 3 (LLM) into an English intent + English params. There are no Ukrainian / Russian / German FastMatcher patterns by design.
User utterance (any language)
│
▼
┌──────────────────────────────────────────────────────────────┐
│ Tier 1 FastMatcher (DB regex, English-only) ~0 ms │
│ Tier 2 Module Bus (user modules, WebSocket) ~ms │
│ Cache IntentCache (SQLite, prev LLM hits) ~10 ms │
│ Tier 3 Local LLM (Ollama, single call) 300-800 │
│ Tier 4 Cloud LLM (OpenAI-compatible, optional) 1-3 sec │
│ Fallback "not understood" (i18n) │
└──────────────────────────────────────────────────────────────┘
│
▼ EventBus: voice.intent { intent, params, source }
Module owning the intent executes
│
▼
Response (LLM rephrase for variety) → TTS
Tier details:
| Tier | Source | Latency | Mechanism | Notes |
|---|---|---|---|---|
| 1 | FastMatcher (IntentCompiler) |
~0 ms | DB regex with priority + specificity sorting + verb-bucket pre-filter | English-only |
| 2 | Module Bus | ~ms | WebSocket round-trip to user-installed modules | Per-module circuit breaker |
| Cache |
IntentCache (SQLite) |
~10 ms | Lookup by (text, lang) key for previous LLM hits |
All languages |
| 3 | Local LLM (Ollama) | 300-800 ms | Single classification call with dynamic registry-aware prompt | Requires ~3-5 GB RAM |
| 4 | Cloud LLM | 1-3 sec | OpenAI / Anthropic / Groq classification | Optional |
| — | Fallback | ~0 ms | i18n "not understood" message | — |
Hard intents come from modules, not seed scripts. Each system module declares its OWNED_INTENTS and _OWNED_INTENT_META and inserts/claims rows in intent_definitions on start() via _claim_intent_ownership(). There is no central seed file.
Composite device patterns scale O(1) in DB rows. PatternGenerator.rebuild_composite_device_patterns() produces at most 5 rows for the entire device registry — one per verb (device.on, device.off, device.set_temperature, device.lock, device.unlock) — each with a (?P<name>...) alternation of all known device names. The captured name is resolved to a device_id via an in-memory index in O(1).
Verb-bucket pre-filter brings the typical FastMatcher scan length down from O(all-patterns) to ~3-15 candidates. The _VERB_BUCKETS map routes the input's first word (turn, set, play, what, lock, …) to a small set of candidate intents.
Dynamic LLM prompt with registry context. The Tier 3 prompt is rebuilt on every device CRUD and contains: registered intents (with descriptions), connected modules with their intents, devices grouped by meta.location_en, and a list of known indoor rooms. Two constants cap the size: _DEVICES_PER_ROOM_LIMIT=10 and _ROOMS_LIMIT=30. This is what lets the LLM disambiguate "what is the temperature in the living room" (→ device.query_temperature) from "what is the temperature outside" (→ weather.temperature) without any hardcoded mapping.
IntentCache promotion. Hot phrases that hit the cache >=5 times are promoted to FastMatcher patterns once per hour from the core/main.py lifespan. Promoted rows use source='auto_learned' namespaced separately from auto_entity. English-only by design.
LLM Response Rephrase: After a module executes a voice command, voice-core sends the structured action context to the rephrase LLM (temperature=0.9). The rephrase LLM produces English text (since v0.4 the core operates in English internally), which then goes through OutputTranslator to the user's language before Piper TTS.
Translation at the edges. For non-English users, Argos Translate sits at two points in the voice pipeline:
-
Input: after Vosk STT, before IntentRouter —
uk→en(~200 ms warm) -
Output: after IntentRouter / rephrase LLM, before Piper TTS —
en→uk
This makes the entire LLM stack (intent classification, response rephrase, prompt examples) English-only, which dramatically improves reliability of small local models (qwen2.5:3b, phi3:mini) and shrinks the intent prompt from ~1700 tokens to ~300.
For full implementation details — pattern specificity scoring, composite resolver, ambiguous-name disambiguation, prompt structure, scaling envelope — see intent-routing.md.
Source: core/registry/
The device registry is the persistent store for all known devices, their current state, and historical data.
Device table:
| Column | Type | Description |
|---|---|---|
| device_id | UUID | Primary key, auto-generated |
| name | String | Human-readable device name |
| type | String | Device category (light, sensor, etc.) |
| protocol | String | Communication protocol (zigbee, mqtt...) |
| state | JSON | Current device state blob |
| capabilities | JSON | Supported features and value ranges |
| last_seen | DateTime | Last communication timestamp |
| module_id | String | Owning module identifier |
| meta | JSON | Arbitrary metadata |
StateHistory table:
- Records the last 1,000 state changes per device.
- Each entry stores the previous state, new state, and timestamp.
- Older entries are pruned automatically.
AuditLog table:
- Stores up to 10,000 records with automatic rotation.
- Logs administrative actions: device registration, removal, configuration changes.
Requests pass through middleware in the following order:
Incoming request
|
v
RequestIdMiddleware -- Assigns unique X-Request-ID header
|
v
RateLimitMiddleware -- 120 requests per 60 seconds
|
v
CORSMiddleware -- Cross-origin policy
|
v
Route handler
- Bearer token authentication for module and external API access.
- Tokens stored in
/secure/module_tokens/. - UI routes (
/api/ui/*) require no auth but are restricted to localhost only.
Core API (/api/v1/*) -- authenticated:
| Route | Purpose |
|---|---|
/system |
System info, health, status |
/devices |
Device CRUD and state queries |
/events |
EventBus inspection and publishing |
/integrity |
Integrity check status and reports |
/modules |
Module lifecycle management |
/secrets |
Secrets vault access |
/intents |
Intent routing and testing |
/bus |
Module Bus WebSocket endpoint |
UI API (/api/ui/*) -- localhost only, no auth:
| Route | Purpose |
|---|---|
/ui |
UI panel serving |
/setup |
First-run setup wizard |
/voice_engines |
Voice engine configuration |
/sync |
UI state sync (WebSocket, versioned) |
/stream |
Legacy SSE stream (backward compat) |
Available at /docs only when DEBUG=true in the environment.
Source: core/cloud_sync/sync.py
Cloud connectivity is optional and designed to be minimal. The core never depends on cloud availability for local operation.
| Parameter | Value |
|---|---|
| Remote server | selenehome.tech |
| Heartbeat interval | 60 seconds |
| Request signing | HMAC-SHA256 |
| Command poll timeout | 55 seconds (long-poll) |
| Backoff (initial) | 5 seconds |
| Backoff (maximum) | 300 seconds |
| Backoff strategy | Exponential |
SelenaCore selenehome.tech
| |
|--- heartbeat (HMAC-SHA256) --------->|
|<-- 200 OK ---------------------------|
| |
|--- long-poll /commands ------------->|
| (55s timeout) |
|<-- command payload ------------------|
| |
|--- command ack --------------------->|
| |
On network failure, the sync client backs off exponentially from 5 seconds up to 300 seconds before retrying.
SelenaCore uses a dual-source configuration model.
Managed by Pydantic BaseSettings in core/config.py via the CoreSettings class. All fields are typed and validated at startup.
Key settings:
| Variable | Default | Description |
|---|---|---|
CORE_PORT |
80 | FastAPI listening port |
CORE_DATA_DIR |
/var/lib/selena | Persistent data directory |
CORE_SECURE_DIR |
/secure | Tokens and secrets storage |
DEBUG |
false | Enable debug mode and /docs |
Used for structured configuration that does not fit well into flat environment variables (module settings, logging presets, automation rules).
Precedence: Environment variables override YAML values where both sources define the same setting.
Frontend: src/i18n/locales/{en,uk}.ts via i18next + react-i18next.
Voice responses: Generated by LLM in real-time via _generate_via_llm() in VoiceCoreModule. Voice handlers return structured action context dicts; LLM produces natural-language TTS text in the configured TTS language. No pre-written translations or caching — every response is freshly generated.
HTML widgets: Built-in var L = {en:{…}, uk:{…}} dictionaries with data-i18n attributes.
Source: agent/
The Integrity Agent runs as a separate process alongside the core. It is the watchdog that ensures the core codebase has not been tampered with.
every 30 seconds:
|
v
Compute SHA256 hashes of core files
|
v
Compare against known-good manifest
|
+-- match -----> OK, sleep 30s
|
+-- mismatch --> VIOLATION DETECTED
|
v
Stop all modules
|
v
Notify (core.integrity_violation event)
|
v
Attempt rollback
|
v
Enter SAFE MODE
|
v
Publish core.safe_mode_entered
In Safe Mode, only essential core functions remain active. All user modules are stopped and cannot be restarted until the integrity issue is resolved.
docker-compose.yml
+--------------------------------------------------+
| |
| +------------------+ +-------------------+ |
| | core | | agent | |
| | Dockerfile.core | | Integrity Agent | |
| | Host networking | | Separate process | |
| | Privileged mode | | | |
| +------------------+ +-------------------+ |
| | | |
| v v |
| +-------------+ +------------------+ |
| | selena_data | | selena_secure | |
| | (volume) | | (volume) | |
| +-------------+ +------------------+ |
| |
+--------------------------------------------------+
| Property | Value |
|---|---|
| Base image | python:3.11-slim |
| Network mode | host |
| Privileges | privileged (hardware access) |
| System packages | ffmpeg, portaudio, VLC, ALSA libs, PulseAudio |
| Runtime RAM | ~1.5 GB total (down from ~3 GB pre-rework) |
Host networking and privileged mode are required for:
- Direct access to audio hardware (microphone, speakers) for voice processing.
- Access to USB devices and GPIO pins for protocol bridges (Zigbee, Z-Wave, Thread dongles).
- Multicast/broadcast for device discovery protocols (mDNS, SSDP, Matter, Thread).
- Bluetooth Low Energy radio for Matter commissioning (see matter-thread.md).
| Volume | Purpose |
|---|---|
| selena_data | Database, module data, logs, backups |
| selena_secure | Tokens, secrets, certificates |
Graceful shutdown mirrors the boot sequence in reverse, ensuring no data loss:
1. CloudSync.stop()
| Stop heartbeat and command polling
v
2. Publish core.shutdown event
| All listeners notified
v
3. Module Bus shutdown_all(drain_ms=5000)
| Send shutdown to all user modules
| Wait up to 5 seconds for queues to drain
v
4. Shutdown in-process system modules
| Each system module's stop() called
v
5. EventBus.stop()
| Queue consumer halted
v
6. Database engine dispose
| All connections closed, WAL checkpoint
v
Process exit
The 5-second drain window for the Module Bus ensures that user modules have time to persist their state and acknowledge the shutdown before their WebSocket connections are terminated.
| Topic | Document |
|---|---|
| User authentication and QR flow | user-manager-auth.md |
| Module protocol (tokens, HMAC, webhooks) | module-core-protocol.md |
| Module Bus wire protocol | module-bus-protocol.md |
| Module development (SDK, manifest) | module-development.md |
| Widget development (widget.html, i18n) | widget-development.md |
| Configuration reference | configuration.md |
| Deployment and systemd | deployment.md |
🤖 This wiki is auto-synced from docs/ in the main repo. Hand-edits on the wiki UI get overwritten on the next push. Open a PR against the main repo instead.
MIT License · Sponsor · Ko-fi
SelenaCore
🇬🇧 English
Getting started
Architecture
Voice & translation
Hardware integration
Development
- Modules overview
- Module development
- System module development
- Module API guide
- Module bus protocol
- Widget development
- User manager / auth
Reference
🇺🇦 Українська
Початок
Архітектура
Голос і переклад
Інтеграція заліза
Розробка
- Розробка модулів
- Розробка системних модулів
- Module API
- Module bus
- Widget development
- User manager / auth
Довідник