# Home Bus

## Purpose

The `home` bus exposes room-centric and user-facing telemetry, state, and control topics for the living environment.

Typical systems connected to this bus include:

- environmental sensors
- lighting and switches
- motion/contact/presence sensors
- thermostats and climate devices
- smart sockets for on/off control semantics

The `home` bus models living-space behavior, not electrical topology.

This document defines the `home`-specific topic grammar and semantic ownership.

Shared rules for payload profiles, metadata, time semantics, quality, and operational topics are defined in `mqtt_contract.md`.


## Scope and Ownership

The `home` bus owns room and automation semantics.

The `energy` bus owns electrical accounting semantics.

Examples:

- `vad/home/living-room/power/tv/value` is room control state
- `vad/home/living-room/power/tv/set` is room control command
- `vad/energy/load/living-room-entertainment/active_power/value` is electrical telemetry

Rule:

- publish user-oriented control/state on `home`
- publish electrical measurement and accounting on `energy`


## Normative Topic Contract (v1)

All home topics MUST follow:

`<site>/home/<location>/<capability>/<device_id>/<stream>`

Where:

- `<site>`: deployment/site id, for example `vad`
- `<location>`: normalized area identifier in kebab-case
- `<capability>`: semantic capability in snake_case
- `<device_id>`: stable device/entity identifier in kebab-case
- `<stream>`: one of `value`, `last`, `set`, `meta`, `availability`

Examples:

- `vad/home/bedroom/temperature/bedroom-sensor/value`
- `vad/home/living-room/motion/radar-south/value`
- `vad/home/kitchen/light/ceiling-switch/value`
- `vad/home/living-room/power/tv/set`


## Canonical Identity Model

The `home` bus is built around stable room semantics.

Rules:

- `<location>` MUST describe the living-space area, not the vendor room name if the two differ
- `<device_id>` MUST identify the logical endpoint exposed to consumers
- one physical device MAY publish multiple capabilities under the same `<device_id>`
- replacing a physical Zigbee sensor SHOULD keep the same canonical `<device_id>` when the logical endpoint remains the same

Examples:

- `bedroom-sensor` is a logical endpoint
- `0x00158d0008aa1111` is a vendor identity and belongs in `meta.source_ref`, not in the topic path

This keeps HomeKit, historian, and automations insulated from vendor identity churn.


## Stream Semantics

- `value`: live semantic sample, whether measurement, held state, or transition notification
- `last`: retained last-known timestamped sample for startup/bootstrap decisions
- `set`: command/request topic for controllable capabilities
- `meta`: static or slowly-changing metadata
- `availability`: online/offline state

Rules:

- adapters SHOULD emit live hot-path data only on `value`
- adapters SHOULD deduplicate repeated `value` samples when the semantic value did not change
- adapters SHOULD publish retained `last` for the latest known timestamped sample
- legacy `state` and `event` topics SHOULD be treated as compatibility-only during migration

Command safety:

- `set` MUST NOT be retained
- stateful devices SHOULD acknowledge commands via `value`
- payload profile and time semantics follow `mqtt_contract.md`


## Payload Contract

To keep Node-RED flows efficient, two payload profiles are allowed.

### Profile A: Hot Path Scalar (recommended)

For frequent updates, payload SHOULD be scalar.

Examples:

- `vad/home/bedroom/temperature/bedroom-sensor/value` -> `23.6`
- `vad/home/living-room/motion/radar-south/value` -> `true`

Metadata is published separately on retained `meta`.

Example:

- Topic: `vad/home/bedroom/temperature/bedroom-sensor/meta`
- Payload:

```json
{
  "unit": "C",
  "source": "zigbee_adapter",
  "precision": 0.1
}
```

### Profile B: Envelope JSON (optional)

For streams that need inline quality/timestamp:

```json
{
  "value": 23.6,
  "unit": "C",
  "observed_at": "2026-03-08T10:15:12Z",
  "quality": "good"
}
```

Recommendation:

- prefer Profile A on high-frequency paths
- use Profile B where source quality/time must travel with each sample
- if exact source timestamp must be preserved, use Profile B
- use `last` with Profile B and `observed_at` when control consumers need a startup sample with freshness information
- do not repeat metadata or adapter internals in `value` payloads
- keep Profile B envelopes small and canonical


## Capability Catalog (initial)

Environmental capabilities:

- `temperature`
- `humidity`
- `pressure`
- `illuminance`
- `co2`
- `voc`
- `pm25`
- `pm10`

Presence and safety capabilities:

- `motion`
- `presence`
- `contact`
- `water_leak`
- `smoke`
- `gas`
- `tamper`

Control and user-facing capabilities:

- `light`
- `power`
- `lock`
- `cover_position`
- `thermostat_mode`
- `target_temperature`
- `fan_mode`
- `button`

Device health capabilities that are still user-relevant:

- `battery`
- `battery_low`

Rules:

- use `power` on `home` only for control semantics
- use electrical telemetry such as `active_power` and cumulative counters such as `energy_total` on `energy`
- radio diagnostics such as `linkquality` SHOULD NOT go on `home` by default
- `presence` SHOULD normally be a higher-level derived state, not a raw single-device detection
- raw mmWave detection, including Zigbee `presence` with `fading_time=0`, SHOULD be published as `motion`


## MQTT Delivery Policy

Default policy:

- `value`: QoS 1, retain false
- `last`: QoS 1, retain true
- `set`: QoS 1, retain false
- `meta`: QoS 1, retain true
- `availability`: QoS 1, retain true (LWT preferred)

Cold-start rule:

- control consumers such as thermostats SHOULD subscribe to retained `last` to obtain the latest known sample at startup
- `last` SHOULD include `observed_at` so staleness can be evaluated immediately after subscribe
- consumers MAY unsubscribe from `last` after bootstrap and continue consuming lightweight live updates on `value`
- consumers that depend on deterministic retained bootstrap SHOULD use a dedicated MQTT client session instead of sharing the same broker config with unrelated subscribers

If uncertain, choose QoS 1.


## Node-RED Implementation Optimizations

Translation is done in Node-RED, so the contract is optimized for low processing overhead.

Guidelines:

- keep topic shape stable to simplify wildcard routing and switch nodes
- avoid unnecessary JSON parse/build in high-rate pipelines
- publish device metadata on retained `meta` to avoid payload repetition
- publish canonical MQTT-ready messages as early as possible after normalization
- emit MQTT-ready messages only at the publish boundary
- do not carry adapter-internal normalization structures on forwarded `msg` objects
- discard temporary normalization fields before publish
- keep `value` streams extremely lightweight
- centralize mapping rules (location, capability, device_id) in reusable subflows
- keep one ingress subflow per protocol and one normalization subflow shared by all
- avoid broad `#` subscriptions in hot paths

Suggested Node-RED subscriptions:

- `+/home/+/+/+/value`
- `+/home/+/+/+/last`
- `+/home/+/+/+/set`


## Interaction with Other Buses

Cross-bus correlations are implemented by consumers, not by changing bus semantics.

Examples:

- combine `home` presence with `energy` load telemetry for occupancy-aware energy decisions
- combine `home` climate data with `network` device state for diagnostics


## Historian Relationship

The `home` bus is a major historian source.

For ingestion worker compatibility, each message should map to:

- `metric_name`
- `device_id`
- `value`
- `observed_at`

`device_id` recommendation:

- `<location>.<device_id>`

Historian defaults:

- `value` streams SHOULD be ingested by default
- `last` streams SHOULD NOT be ingested as normal telemetry samples
- `meta.historian.mode` SHOULD describe whether a `value` stream represents `sample`, `state`, or `event` semantics
- when Profile A scalar is used, `observed_at` will usually fall back to ingestion time
- string enum states are semantically valid, but the current PostgreSQL ingestion API directly supports only numeric and boolean samples

Example:

- Topic: `vad/home/bedroom/temperature/bedroom-sensor/value`
- `metric_name = temperature`
- `device_id = bedroom.bedroom-sensor`

Zigbee2MQTT projection examples:

- `zigbee2mqtt/bedroom_sensor.temperature` -> `vad/home/bedroom/temperature/bedroom-sensor/value`
- `zigbee2mqtt/front_door_contact.contact` -> `vad/home/entrance/contact/front-door/value`
- `zigbee2mqtt/bedside_remote.action` -> `vad/home/bedroom/button/bedside-remote/value`


## Relationship with HomeKit

The `home` bus is a primary source for HomeKit adapters.

HomeKit integration remains a consumer layer concern:

- Device -> Protocol Adapter -> MQTT Bus -> HomeKit Adapter -> HomeKit

The `home` bus contract SHOULD remain independent from HomeKit-specific modeling constraints.


## Design Principles

- keep room-centric semantics explicit and stable
- separate control semantics (`home`) from electrical accounting (`energy`)
- optimize for deterministic, low-cost Node-RED translation
- keep topic grammar strict and payload overhead low
- keep canonical IDs independent from vendor identifiers
