Sowel -- Data Model¶
Version: 1.0 -- 2026-02-19
This document is the reference for Sowel's core data model. It describes the three-layer architecture (Topology -> Functional -> Physical), all entities, their relationships, and the aggregation rules.
1. Three-Layer Architecture¶
Sowel separates concerns into three distinct layers:
+-------------------------------------------------------------+
| LAYER 1 -- TOPOLOGY (Zones) |
| Spatial structure of the home |
| Hierarchical: Home -> Floor -> Room |
| Aggregates data automatically from child Equipments |
+-------------------------------------------------------------+
| LAYER 2 -- FUNCTIONAL (Equipments + Groups) |
| What the user sees and controls |
| Placed IN a Zone, optionally IN a Group |
| Binds to one or more physical Devices |
+-------------------------------------------------------------+
| LAYER 3 -- PHYSICAL (Devices) |
| Hardware discovered from integrations |
| Auto-discovered, raw data and commands |
| Never directly manipulated by the end user |
+-------------------------------------------------------------+
Guiding principle: A Device is what's on the network. An Equipment is what's in the room.
2. Entity Relationship Diagram¶
Zone (hierarchy: parent -> children)
|
+-- EquipmentGroup (optional functional grouping)
| +-- Equipment*
|
+-- Equipment (functional unit, user-facing)
+-- DataBinding --> DeviceData (on a Device)
+-- OrderBinding --> DeviceOrder (on a Device)
+-- [V0.5] ComputedData (expression-based virtual data)
Device (physical, auto-discovered)
+-- DeviceData (readable properties: temperature, state, brightness...)
+-- DeviceOrder (writable commands: set brightness, turn on...)
3. Zone¶
A Zone represents a spatial area in the home. Zones form a tree hierarchy.
3.1 Interface¶
interface Zone {
id: string; // UUID v4
name: string; // "Salon", "Etage 1", "Maison"
parentId: string | null; // null = root zone
icon?: string; // Lucide icon name: "home", "sofa", "bed"
description?: string; // "Piece principale, 35m2"
displayOrder: number; // Sort order among siblings (0-based)
createdAt: string; // ISO 8601
updatedAt: string; // ISO 8601
}
3.2 Hierarchy¶
Zones are nestable to any depth. Typical structure:
Maison (root, parentId: null)
+-- RDC (floor, parentId: Maison)
| +-- Salon (room, parentId: RDC)
| +-- Cuisine (room, parentId: RDC)
| +-- Entree (room, parentId: RDC)
| +-- WC (room, parentId: RDC)
+-- Etage (floor, parentId: Maison)
| +-- Chambre Parentale (room, parentId: Etage)
| +-- Chambre Enfant (room, parentId: Etage)
| +-- Salle de Bain (room, parentId: Etage)
| +-- Couloir (room, parentId: Etage)
+-- Exterieur (area, parentId: Maison)
+-- Jardin (area, parentId: Exterieur)
+-- Garage (area, parentId: Exterieur)
3.3 Zone Aggregated Data¶
The engine automatically computes aggregated data for each Zone based on the Equipments it contains. No manual configuration required -- this is a core feature.
Aggregation is recursive: a parent Zone aggregates its own Equipments plus all children Zones.
| Attribute | Type | Aggregation Rule | Source (DataCategory) | Description |
|---|---|---|---|---|
temperature |
number | null | AVG | temperature |
Average temperature in the zone |
humidity |
number | null | AVG | humidity |
Average humidity |
pressure |
number | null | AVG | pressure |
Average atmospheric pressure |
luminosity |
number | null | AVG | luminosity |
Average luminosity (lux) |
co2 |
number | null | AVG | co2 |
Average CO2 level (ppm) |
voc |
number | null | AVG | voc |
Average VOC level (ppb) |
motion |
boolean | OR | motion |
true if ANY motion sensor detects movement |
presence |
boolean | OR + timeout | motion |
true if motion detected within configurable timeout |
openDoors |
number | COUNT (open) | contact_door |
Number of open door contacts |
openWindows |
number | COUNT (open) | contact_window |
Number of open window contacts |
waterLeak |
boolean | OR | water_leak |
true if ANY water leak sensor triggers |
smoke |
boolean | OR | smoke |
true if ANY smoke detector triggers |
lightsOn |
number | COUNT (on) | light_state |
Number of lights turned on |
lightsTotal |
number | COUNT (all) | light_state |
Total number of light equipments |
averageBrightness |
number | null | AVG (on only) | light_brightness |
Average brightness of lights that are on |
shuttersOpen |
number | COUNT (open) | shutter_position |
Number of open shutters |
shuttersTotal |
number | COUNT (all) | shutter_position |
Total number of shutters |
averageShutterPosition |
number | null | AVG | shutter_position |
Average shutter position (%) |
totalPower |
number | SUM | power |
Total instantaneous power (W) |
totalEnergy |
number | SUM | energy |
Total energy consumption (kWh) |
heatingActive |
boolean | OR | thermostat equipments | true if any thermostat is actively heating |
targetTemperature |
number | null | AVG | thermostat equipments | Average target temperature setpoint |
Note: This list will evolve. New aggregated attributes can be added as new Equipment types and DataCategories are introduced.
3.4 Zone Auto-Orders¶
Zones expose bulk commands that act on all Equipments within the zone (and child zones recursively):
| Order | Effect |
|---|---|
allOff |
Turn off ALL controllable Equipments in the Zone |
allLightsOff |
Turn off all light-type Equipments |
allLightsOn |
Turn on all light-type Equipments |
allShuttersOpen |
Open all shutters |
allShuttersClose |
Close all shutters |
3.5 SQLite Schema¶
CREATE TABLE zones (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
parent_id TEXT REFERENCES zones(id) ON DELETE SET NULL,
icon TEXT,
description TEXT,
display_order INTEGER DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
4. Equipment Group¶
An EquipmentGroup is a functional grouping of Equipments within a Zone. It allows controlling and monitoring a subset of equipments together.
4.1 Interface¶
interface EquipmentGroup {
id: string; // UUID v4
name: string; // "Volets Sud", "Eclairage Ambiance"
zoneId: string; // FK -> Zone (a group belongs to exactly one zone)
icon?: string; // Lucide icon name
description?: string;
displayOrder: number; // Sort order within the zone
createdAt: string; // ISO 8601
updatedAt: string; // ISO 8601
}
4.2 Purpose & Examples¶
Salon (Zone)
+-- Group "Volets Sud"
| +-- Volet Baie Vitree (Equipment: shutter)
| +-- Volet Porte Fenetre (Equipment: shutter)
+-- Group "Volets Nord"
| +-- Volet Fenetre Nord (Equipment: shutter)
+-- Group "Eclairage Ambiance"
| +-- Spots Plafond (Equipment: dimmer)
| +-- Lampe Canape (Equipment: dimmer)
+-- Detection Salon (Equipment: motion_sensor, no group)
+-- Temperature Salon (Equipment: sensor, no group)
Key behaviors:
- An Equipment belongs to at most one Group (optional, via
groupId) - A Group belongs to exactly one Zone
- Groups have their own aggregated data (same rules as Zones, scoped to group members)
- Groups can receive bulk orders (e.g., "close all shutters in Volets Sud")
4.3 Group Aggregated Data¶
Groups compute aggregated data using the same rules as Zones (section 3.3), but scoped to the group's member Equipments only. This allows:
- "Average shutter position of Volets Sud" vs "Average shutter position of Volets Nord"
- "Number of lights on in Eclairage Ambiance" vs zone-wide count
4.4 SQLite Schema¶
CREATE TABLE equipment_groups (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
zone_id TEXT NOT NULL REFERENCES zones(id) ON DELETE CASCADE,
icon TEXT,
description TEXT,
display_order INTEGER DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
5. Equipment¶
An Equipment is the user-facing functional unit. It's the primary entity users interact with in the UI, scenarios, and voice commands.
5.1 Interface¶
type EquipmentType =
| "light" // on/off light
| "dimmer" // dimmable light
| "color_light" // color-capable light
| "shutter" // cover, blind, shutter
| "thermostat" // heating/cooling control
| "lock" // door lock
| "alarm" // alarm system
| "sensor" // generic sensor (temp, humidity...)
| "motion_sensor" // motion detector
| "contact_sensor" // door/window contact
| "media_player" // media device
| "camera" // surveillance camera
| "switch" // on/off switch or plug
| "generic"; // anything else
interface Equipment {
id: string; // UUID v4
name: string; // "Spots Salon", "Volet Baie Vitree"
zoneId: string; // FK -> Zone (where the equipment functions)
groupId: string | null; // FK -> EquipmentGroup (optional)
type: EquipmentType; // Semantic type, drives UI rendering & aggregation
icon?: string; // Lucide icon name (overrides type default)
description?: string;
enabled: boolean; // Disabled equipments are ignored by the engine
createdAt: string; // ISO 8601
updatedAt: string; // ISO 8601
}
5.2 Equipment vs Device¶
| Device | Equipment | |
|---|---|---|
| Nature | Physical hardware | Functional abstraction |
| Discovery | Auto-discovered from integrations | Manually created by user |
| Identity | Integration-specific ID | User-chosen name |
| Location | Where physically installed | Where functionally used |
| Cardinality | 1 Device -> N Equipments possible | 1 Equipment -> N Devices possible |
| User interaction | Never (technical layer) | Always (primary interface) |
Examples:
- 1 Device -> 1 Equipment: Aqara temperature sensor -> "Temperature Salon"
- 1 Device -> N Equipments: Double relay module -> "Lumiere Cuisine" + "Lumiere Cellier"
- N Devices -> 1 Equipment: 3 PIR sensors -> "Detection Salon" (via computed data, V0.5)
6. Data Binding¶
A DataBinding maps a Device Data property to an Equipment-level alias.
6.1 Interface¶
interface DataBinding {
id: string; // UUID v4
equipmentId: string; // FK -> Equipment
deviceDataId: string; // FK -> DeviceData
alias: string; // Equipment-level name: "state", "brightness", "temperature"
}
6.2 How It Works¶
Device "Variateur #1"
+-- DeviceData: key="state", value="ON" <--+
+-- DeviceData: key="brightness", value=180 <---+-- DataBinding
+-- DeviceData: key="linkquality", value=85 |
|
Equipment "Spots Salon" |
+-- Data alias "state" ----------------------------+ (bound to DeviceData "state")
+-- Data alias "brightness" ----------------------- (bound to DeviceData "brightness")
+-- [not bound to linkquality -- it's a technical metric, not user-facing]
6.3 Constraints¶
UNIQUE(equipment_id, alias)-- each alias is unique per Equipment- When DeviceData changes, the Equipment's bound alias reflects the new value immediately
- The alias is used in expressions, UI display, Zone aggregation, and Scenario conditions
6.4 SQLite Schema¶
CREATE TABLE data_bindings (
id TEXT PRIMARY KEY,
equipment_id TEXT NOT NULL REFERENCES equipments(id) ON DELETE CASCADE,
device_data_id TEXT NOT NULL REFERENCES device_data(id) ON DELETE CASCADE,
alias TEXT NOT NULL,
UNIQUE(equipment_id, alias)
);
7. Order Binding¶
An OrderBinding maps a Device Order to an Equipment-level command alias.
7.1 Interface¶
interface OrderBinding {
id: string; // UUID v4
equipmentId: string; // FK -> Equipment
deviceOrderId: string; // FK -> DeviceOrder
alias: string; // Equipment-level command: "turn_on", "set_brightness"
}
7.2 How It Works¶
When an Equipment Order is executed:
User clicks "Turn On" on Equipment "Spots Salon"
-> API: POST /equipments/:id/orders/turn_on { value: true }
-> Equipment Manager finds OrderBinding alias="turn_on"
-> Resolves to DeviceOrder
-> Integration Plugin dispatches command to device
7.3 Multi-Device Dispatch¶
An Equipment can have multiple OrderBindings with the same alias pointing to different Devices. This enables controlling multiple devices with a single command:
Equipment "Eclairage Cuisine"
+-- OrderBinding: alias="turn_on" -> DeviceOrder on Relais #1
+-- OrderBinding: alias="turn_on" -> DeviceOrder on Relais #2
Executing turn_on dispatches to BOTH relays in parallel.
Schema constraint note: For multi-device dispatch, the UNIQUE constraint is on (equipment_id, alias, device_order_id) -- not just (equipment_id, alias).
7.4 SQLite Schema¶
CREATE TABLE order_bindings (
id TEXT PRIMARY KEY,
equipment_id TEXT NOT NULL REFERENCES equipments(id) ON DELETE CASCADE,
device_order_id TEXT NOT NULL REFERENCES device_orders(id) ON DELETE CASCADE,
alias TEXT NOT NULL,
UNIQUE(equipment_id, alias, device_order_id)
);
8. Device (Layer 3 -- Physical)¶
Devices are auto-discovered from configured integrations (Zigbee2MQTT, Panasonic CC, MCZ Maestro, Netatmo HC, etc.). They are documented here for completeness.
8.1 Interface¶
interface Device {
id: string; // UUID v4
mqttBaseTopic: string; // Integration-specific topic or identifier
mqttName: string; // Integration-specific name or ID
name: string; // User-editable display name
manufacturer?: string; // "Aqara", "IKEA", "Panasonic", "MCZ"
model?: string; // "MCCGQ11LM", "CS-Z25VKEW", etc.
ieeeAddress?: string; // Hardware address (Zigbee IEEE, serial, etc.)
source: DeviceSource; // "zigbee2mqtt" | "panasonic_cc" | "mcz_maestro" | "netatmo_hc" | ...
status: DeviceStatus; // "online" | "offline" | "unknown"
lastSeen: string | null; // ISO 8601
rawExpose?: unknown; // Raw integration-specific metadata
createdAt: string;
updatedAt: string;
}
8.2 DeviceData¶
interface DeviceData {
id: string;
deviceId: string; // FK -> Device
key: string; // Property name: "temperature", "state", "brightness"
type: DataType; // "boolean" | "number" | "enum" | "text" | "json"
category: DataCategory; // Semantic category for aggregation rules
value: unknown; // Current value
unit?: string; // "C", "%", "lx", "W"
lastUpdated: string | null;
}
8.3 DeviceOrder¶
interface DeviceOrder {
id: string;
deviceId: string; // FK -> Device
key: string; // "state", "brightness", "position"
type: DataType;
mqttSetTopic: string; // MQTT topic to publish to
payloadKey: string; // Key in the JSON payload
min?: number; // For numeric: minimum value
max?: number; // For numeric: maximum value
enumValues?: string[]; // For enum: allowed values
unit?: string;
}
9. Computed Data (V0.5)¶
Deferred to V0.5 -- documented here as part of the complete data model.
A ComputedData is a virtual data point on an Equipment whose value is derived from an expression over other data sources.
9.1 Interface¶
interface ComputedData {
id: string; // UUID v4
equipmentId: string; // FK -> Equipment
key: string; // "state", "average_temperature", "motion"
type: DataType;
category: DataCategory; // Used by Zone aggregation
expression: string; // Computation expression
value: unknown; // Current computed value
}
9.2 Expression Language¶
// Boolean
OR(binding.motion_1, binding.motion_2)
AND(binding.door, binding.window)
NOT(binding.occupancy)
// Numeric
AVG(binding.temp_1, binding.temp_2)
MIN(binding.temp_1, binding.temp_2)
MAX(binding.temp_1, binding.temp_2)
SUM(binding.power_1, binding.power_2)
// Conditional
IF(binding.brightness > 0, "on", "off")
THRESHOLD(binding.temperature, 19, "cold", "ok")
// References
binding.<alias> -> DataBinding on the same Equipment
equipment.<equipmentId>.<alias> -> Data on another Equipment
zone.<zoneId>.<key> -> Zone aggregated Data
10. Reactive Data Flow¶
The complete event-driven pipeline:
Integration event (MQTT message, cloud API poll, etc.)
|
v
Integration Plugin (receives + parses)
|
v
Device Manager (updates DeviceData)
|
+--> Event: "device.data.updated"
|
v
Equipment Manager
+-- Updates bound Equipment Data (via DataBindings)
+-- [V0.5] Re-evaluates ComputedData expressions
|
+--> Event: "equipment.data.changed"
|
v
Zone Aggregator
+-- Re-computes Zone aggregated data
+-- Re-computes Group aggregated data
+-- Propagates up the zone hierarchy (recursive)
|
+--> Event: "zone.data.changed"
|
v
Scenario Engine (V0.7)
+-- Evaluates triggers
+-- Checks conditions
+-- Executes actions
|
+--> Actions may dispatch Equipment/Zone Orders -> Integration Plugin -> device
|
v
WebSocket Server
+-- Broadcasts all events to connected UI clients
11. Event Bus Events¶
All events are typed via TypeScript discriminated unions.
System Events¶
| Event | Payload | When |
|---|---|---|
system.started |
-- | Engine boot complete |
system.mqtt.connected |
-- | MQTT broker connected |
system.mqtt.disconnected |
-- | MQTT broker lost |
system.error |
error: string |
Unrecoverable error |
Device Events (V0.1)¶
| Event | Payload | When |
|---|---|---|
device.discovered |
device: Device |
New device found |
device.removed |
deviceId, deviceName |
Device deleted |
device.status_changed |
deviceId, deviceName, status |
Online/offline |
device.data.updated |
deviceId, deviceName, dataId, key, value, previous |
Property change |
Equipment Events (V0.3)¶
| Event | Payload | When |
|---|---|---|
equipment.data.changed |
equipmentId, key, value, previous |
Bound data changed |
equipment.order.executed |
equipmentId, orderAlias, value |
Order dispatched |
Zone Events (V0.3+)¶
| Event | Payload | When |
|---|---|---|
zone.data.changed |
zoneId, key, value, previous |
Aggregated data changed |
12. Complete SQLite Schema¶
-- ============================================================
-- ZONES (V0.2)
-- ============================================================
CREATE TABLE zones (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
parent_id TEXT REFERENCES zones(id) ON DELETE SET NULL,
icon TEXT,
description TEXT,
display_order INTEGER DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- ============================================================
-- EQUIPMENT GROUPS (V0.2)
-- ============================================================
CREATE TABLE equipment_groups (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
zone_id TEXT NOT NULL REFERENCES zones(id) ON DELETE CASCADE,
icon TEXT,
description TEXT,
display_order INTEGER DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- ============================================================
-- DEVICES (V0.1 -- existing)
-- ============================================================
CREATE TABLE devices (
id TEXT PRIMARY KEY,
mqtt_base_topic TEXT NOT NULL UNIQUE,
mqtt_name TEXT NOT NULL,
name TEXT NOT NULL,
manufacturer TEXT,
model TEXT,
ieee_address TEXT,
source TEXT NOT NULL, -- integration source: 'zigbee2mqtt', 'panasonic_cc', 'mcz_maestro', 'netatmo_hc', ...
status TEXT NOT NULL DEFAULT 'unknown',
last_seen DATETIME,
raw_expose JSON,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE device_data (
id TEXT PRIMARY KEY,
device_id TEXT NOT NULL REFERENCES devices(id) ON DELETE CASCADE,
key TEXT NOT NULL,
type TEXT NOT NULL,
category TEXT NOT NULL DEFAULT 'generic',
value TEXT,
unit TEXT,
last_updated DATETIME,
UNIQUE(device_id, key)
);
CREATE TABLE device_orders (
id TEXT PRIMARY KEY,
device_id TEXT NOT NULL REFERENCES devices(id) ON DELETE CASCADE,
key TEXT NOT NULL,
type TEXT NOT NULL,
mqtt_set_topic TEXT NOT NULL,
payload_key TEXT NOT NULL,
min_value REAL,
max_value REAL,
enum_values JSON,
unit TEXT,
UNIQUE(device_id, key)
);
-- ============================================================
-- EQUIPMENTS (V0.3)
-- ============================================================
CREATE TABLE equipments (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
zone_id TEXT NOT NULL REFERENCES zones(id) ON DELETE CASCADE,
group_id TEXT REFERENCES equipment_groups(id) ON DELETE SET NULL,
type TEXT NOT NULL DEFAULT 'generic',
icon TEXT,
description TEXT,
enabled INTEGER DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE data_bindings (
id TEXT PRIMARY KEY,
equipment_id TEXT NOT NULL REFERENCES equipments(id) ON DELETE CASCADE,
device_data_id TEXT NOT NULL REFERENCES device_data(id) ON DELETE CASCADE,
alias TEXT NOT NULL,
UNIQUE(equipment_id, alias)
);
CREATE TABLE order_bindings (
id TEXT PRIMARY KEY,
equipment_id TEXT NOT NULL REFERENCES equipments(id) ON DELETE CASCADE,
device_order_id TEXT NOT NULL REFERENCES device_orders(id) ON DELETE CASCADE,
alias TEXT NOT NULL,
UNIQUE(equipment_id, alias, device_order_id)
);
-- ============================================================
-- COMPUTED DATA (V0.5)
-- ============================================================
CREATE TABLE computed_data (
id TEXT PRIMARY KEY,
equipment_id TEXT NOT NULL REFERENCES equipments(id) ON DELETE CASCADE,
key TEXT NOT NULL,
type TEXT NOT NULL,
category TEXT NOT NULL DEFAULT 'generic',
expression TEXT NOT NULL,
value TEXT,
UNIQUE(equipment_id, key)
);
-- ============================================================
-- INTERNAL RULES (V0.5)
-- ============================================================
CREATE TABLE internal_rules (
id TEXT PRIMARY KEY,
equipment_id TEXT NOT NULL REFERENCES equipments(id) ON DELETE CASCADE,
name TEXT NOT NULL,
condition_expr TEXT NOT NULL,
action_expr TEXT NOT NULL,
enabled INTEGER DEFAULT 1
);
-- ============================================================
-- RECIPES (V0.8)
-- ============================================================
CREATE TABLE recipe_instances (
id TEXT PRIMARY KEY,
recipe_id TEXT NOT NULL,
params JSON NOT NULL DEFAULT '{}',
enabled INTEGER DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE recipe_state (
instance_id TEXT NOT NULL REFERENCES recipe_instances(id) ON DELETE CASCADE,
key TEXT NOT NULL,
value TEXT,
PRIMARY KEY (instance_id, key)
);
CREATE TABLE recipe_log (
id TEXT PRIMARY KEY,
instance_id TEXT NOT NULL REFERENCES recipe_instances(id) ON DELETE CASCADE,
level TEXT NOT NULL DEFAULT 'info',
message TEXT NOT NULL,
data JSON,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- ============================================================
-- USERS & AUTH
-- ============================================================
CREATE TABLE users (
id TEXT PRIMARY KEY,
username TEXT UNIQUE NOT NULL,
display_name TEXT NOT NULL,
password_hash TEXT NOT NULL,
role TEXT NOT NULL DEFAULT 'standard',
preferences JSON DEFAULT '{}',
enabled INTEGER DEFAULT 1,
last_login_at DATETIME,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE api_tokens (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
name TEXT NOT NULL,
token_hash TEXT NOT NULL,
last_used_at DATETIME,
expires_at DATETIME,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE refresh_tokens (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
token_hash TEXT NOT NULL,
expires_at DATETIME NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- ============================================================
-- SETTINGS (key-value store for integration config)
-- ============================================================
CREATE TABLE settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
13. API Endpoints Summary¶
Zones (V0.2)¶
| Method | Route | Description |
|---|---|---|
| GET | /api/v1/zones |
List all zones (tree structure) |
| GET | /api/v1/zones/:id |
Get zone with aggregated data |
| POST | /api/v1/zones |
Create zone |
| PUT | /api/v1/zones/:id |
Update zone |
| DELETE | /api/v1/zones/:id |
Delete zone (must have no children/equipments) |
Equipment Groups (V0.2)¶
| Method | Route | Description |
|---|---|---|
| GET | /api/v1/zones/:zoneId/groups |
List groups in a zone |
| POST | /api/v1/zones/:zoneId/groups |
Create group in a zone |
| PUT | /api/v1/groups/:id |
Update group |
| DELETE | /api/v1/groups/:id |
Delete group |
Equipments (V0.3)¶
| Method | Route | Description |
|---|---|---|
| GET | /api/v1/equipments |
List all equipments |
| GET | /api/v1/equipments/:id |
Get equipment with bindings and current data |
| POST | /api/v1/equipments |
Create equipment |
| PUT | /api/v1/equipments/:id |
Update equipment |
| DELETE | /api/v1/equipments/:id |
Delete equipment |
| POST | /api/v1/equipments/:id/orders/:alias |
Execute an equipment order |
Zone Orders (V0.3+)¶
| Method | Route | Description |
|---|---|---|
| POST | /api/v1/zones/:id/orders/:orderKey |
Execute zone auto-order (allOff, allLightsOff...) |
| POST | /api/v1/groups/:id/orders/:orderKey |
Execute group order |
For the complete API reference, see API Reference.
14. Implementation Roadmap¶
| Version | Entities Implemented |
|---|---|
| V0.1 | Device, DeviceData, DeviceOrder |
| V0.2 | Zone, EquipmentGroup (CRUD + UI) |
| V0.3 | Equipment, DataBinding, OrderBinding (CRUD + Order execution + UI) |
| V0.5 | ComputedData, InternalRule |
| V0.3+ | Zone aggregation engine, Zone auto-orders |