Skip to main content

A platform-agnostic home topology kernel for modeling spaces and attaching behavior modules

Project description

home-topology

CI License: MIT Python 3.12+ Code style: black

A platform-agnostic home topology kernel for modeling spaces (Locations), attaching behavior (Modules), and wiring everything together with a location-aware Event Bus.

home-topology is the structural backbone for smart homes:

  • It models where things are (rooms, floors, zones, virtual spaces).
  • It lets you attach modules like Occupancy, Automation, Ambient, Comfort, Energy.
  • It routes events through this topology so modules can react cleanly.
  • It stays independent of Home Assistant or any specific platform.

Think of it as a tiny "operating system" for your home's spatial model.
Occupancy, automations, energy logic, etc. are apps running on top.


Why Home Topology?

If every room had a perfect presence sensor, occupancy detection would be trivial. But reality is different:

  • Sensors are expensive – $30-50 per room adds up fast for a whole house
  • Batteries die – Motion sensors need constant maintenance
  • Devices go offline – More devices = more failure points
  • Coverage gaps – Even good sensors miss corners and edges

Home Topology lets you use what you already have:

Device Occupancy Signal
💡 Light switch turned on Someone's in the room
🚪 Door opened Someone entered
📺 TV playing Living room is occupied
🌡️ Thermostat adjusted Someone's home
🔊 Speaker volume changed Activity detected

Combine signals from motion sensors, switches, doors, media players, and more into reliable occupancy detection – without buying more hardware.

Your storage room doesn't need a motion sensor. Turn on the light → room is occupied → lights turn off after timeout. Simple.


Features

  • 🧱 Location graph (topology)
    Structured model of your home: house → floors → rooms → zones, with optional links to Home Assistant Areas.

  • 🧠 Modules as plug-ins
    Occupancy, Automation, Ambient, Comfort, Energy, etc. are independent modules that attach to Locations and react to events.

  • 🔁 Location-aware Event Bus
    Simple, synchronous event pipeline with filters for type / location / ancestors / descendants. Location mutations can publish core events (location.created, location.renamed, location.parent_changed, location.deleted, location.reordered) when LocationManager is attached to an event bus.

  • 🧩 Schema-driven configuration
    Each module exposes a config schema; UIs can render dynamic forms per location without custom frontend code.

  • 🧪 Platform agnostic
    Core library has no dependency on Home Assistant. HA support is a thin adapter layer.

  • 💾 Config & state evolution
    Modules can version and migrate their configs, and optionally dump/restore runtime state via the host platform.


Installation

pip install home-topology

Or pin a specific version:

pip install home-topology==1.0.1

Install from source (development):

git clone https://github.com/mjcumming/home-topology.git
cd home-topology
pip install -e ".[dev]"

Used by Topomation

The current Topomation Home Assistant integration uses these home-topology surfaces at runtime:

  • Core: LocationManager, EventBus, Event, EventFilter
  • Modules: OccupancyModule, AutomationModule, AmbientLightModule

Topomation relies on:

  • Location tree and entity-to-location mapping
  • Topology and semantic event routing (occupancy.signal, occupancy.changed, etc.)
  • Occupancy runtime commands/state APIs (trigger/clear/vacate/lock family + timeout/state queries)
  • Per-location automation module config/state persistence
  • Ambient light lookups for location-level light state entities

Core Concepts

Location

A Location is a logical space: a room, floor, area, or virtual zone.

from dataclasses import dataclass
from typing import Optional, Dict, List

@dataclass
class Location:
    id: str
    name: str
    parent_id: Optional[str]
    ha_area_id: Optional[str]           # optional link to a HA Area
    entity_ids: List[str]               # platform entity IDs mapped here
    modules: Dict[str, Dict]            # per-module config blobs

Locations form a hierarchy (e.g. house → main_floor → kitchen → kitchen_table_zone).

LocationManager

LocationManager owns the topology and config, not the behavior.

Responsibilities:

  • Store the location tree.

  • Provide graph queries: parent_of, children_of, ancestors_of, descendants_of.

  • Maintain canonical sibling ordering (Location.order) and indexed reorder operations.

  • Maintain entity → location mappings.

  • Store per-location module config:

    location.modules["occupancy"]  # config for the Occupancy module on this location
    

It does not implement occupancy, energy, or automation logic.

Event Bus

The Event Bus is a simple, synchronous dispatcher for domain events:

from dataclasses import dataclass
from datetime import datetime
from typing import Optional, Dict, Any

@dataclass
class Event:
    type: str                  # "sensor.state_changed", "occupancy.changed", ...
    source: str                # "ha", "occupancy", "automation", ...
    location_id: Optional[str]
    entity_id: Optional[str]
    payload: Dict[str, Any]
    timestamp: datetime
  • publish(event) synchronously delivers events to subscribers.
  • Handlers are wrapped in try/except so one bad module cannot crash the kernel.
  • Modules treat handlers as fast and CPU-bound.
  • For I/O-heavy work, the host integration should offload asynchronously.

Modules

Modules are plug-ins that add behavior to the topology:

  • OccupancyModule – computes binary occupied / vacant per Location.
  • AutomationModule – runs automations in response to semantic events.
  • AmbientLightModule – resolves ambient light using local/ancestor sensors with fallback strategies.
  • ComfortModule (future) – room comfort metrics.
  • EnergyModule (future) – room-level energy and power.

A module:

  • Receives events from the Event Bus.
  • Uses the LocationManager to understand hierarchy.
  • Maintains its own runtime state.
  • Emits semantic events that other modules can consume.

Example interface (simplified):

class LocationModule:
    id: str
    CURRENT_CONFIG_VERSION: int

    def attach(self, bus, loc_manager) -> None:
        """Register event subscriptions and capture references."""

    def default_config(self) -> dict:
        """Default per-location config."""

    def location_config_schema(self) -> dict:
        """JSON-schema-like definition for UI configuration."""

    def migrate_config(self, config: dict) -> dict:
        """Upgrade older config versions to CURRENT_CONFIG_VERSION."""

    def on_location_config_changed(self, location_id: str, config: dict) -> None:
        """React to config updates for a given location."""

    def dump_state(self) -> dict:
        """Optional: serialize runtime state (host is responsible for storage)."""

    def restore_state(self, state: dict) -> None:
        """Optional: restore runtime state from serialized form."""

Quick Example

Note: This is illustrative, not a final API.

from datetime import UTC, datetime
from home_topology import Event, EventBus, LocationManager
from home_topology.modules.occupancy import OccupancyModule

# 1. Kernel components
loc_mgr = LocationManager()
bus = EventBus()
bus.set_location_manager(loc_mgr)
loc_mgr.set_event_bus(bus)

# 2. Create a simple topology
loc_mgr.create_location(
    id="main_floor",
    name="Main Floor",
    parent_id=None,
)

kitchen = loc_mgr.create_location(
    id="kitchen",
    name="Kitchen",
    parent_id="main_floor",
    ha_area_id="area.kitchen",
)

# Map a motion sensor entity to the kitchen
loc_mgr.add_entity_to_location("binary_sensor.kitchen_motion", "kitchen")

# 3. Attach the Occupancy module
occupancy = OccupancyModule()
occupancy.attach(bus, loc_mgr)

# Optionally override per-location config
loc_mgr.set_module_config(
    location_id="kitchen",
    module_id="occupancy",
    config={
        "version": occupancy.CURRENT_CONFIG_VERSION,
        "default_timeout": 300,
        "default_trailing_timeout": 120,
        "occupancy_strategy": "independent",
        "contributes_to_parent": True,
    },
)

# 4. Feed a normalized occupancy signal into the kernel
bus.publish(
    Event(
        type="occupancy.signal",
        source="ha_adapter",
        location_id="kitchen",
        entity_id="binary_sensor.kitchen_motion",
        payload={
            "event_type": "trigger",
            "source_id": "binary_sensor.kitchen_motion",
            "timeout": 300,
        },
        timestamp=datetime.now(UTC),
    )
)

# 5. Query occupancy state (implementation-dependent)
state = occupancy.get_location_state("kitchen")
print(state["occupied"] if state else None)

In a Home Assistant integration, you'd:

  • Translate HA state changes → Events.
  • Expose module state back as HA entities.
  • Optionally provide a UI to configure modules per location.

See the Integration Guide for a complete, production-ready Home Assistant integration example.


Relationship to Home Assistant

home-topology is not a Home Assistant custom component. It's a pure Python library that can back a HA integration (and other platforms).

A typical HA setup would add a custom integration (for example custom_components/topomation/) that:

  • Builds a location graph from HA Areas / devices / entities.
  • Feeds HA events into the Event Bus.
  • Exposes module state (e.g., occupancy sensors) back to HA.
  • Provides a UI for Locations and their modules (with an "Unassigned/Inbox" view for entities).

Building an integration? See the complete Integration Guide for step-by-step instructions, patterns, and a full Home Assistant example.


Project Status

This is a work-in-progress architecture focused on:

  • Clean separation between topology, events, and behavior.
  • Extensibility via modules.
  • Strong testability in pure Python (without spinning up HA).

Expect breaking changes while the core stabilizes.


Development

Quick Start

# Clone and setup
git clone https://github.com/mjcumming/home-topology.git
cd home-topology
python3 -m venv .venv
source .venv/bin/activate

# Install in development mode
make dev-install

# Run tests
make test

# Run example
make example

# Run all checks
make check

Documentation

📖 Start Here:

🔌 Integration:

📊 Project:

🏗️ Architecture:

📦 Modules:

🧪 Testing:

📚 Reference:

Development Commands

make help          # Show all available commands
make test-cov      # Run tests with coverage
make format        # Format code with black
make lint          # Run ruff linter
make typecheck     # Run mypy type checker
make check         # Run all quality checks

Contributing

Contributions are welcome! Please read CONTRIBUTING.md before submitting PRs.

Key guidelines:


License

MIT License - see LICENSE for details.


Links

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

home_topology-1.0.1.tar.gz (225.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

home_topology-1.0.1-py3-none-any.whl (66.6 kB view details)

Uploaded Python 3

File details

Details for the file home_topology-1.0.1.tar.gz.

File metadata

  • Download URL: home_topology-1.0.1.tar.gz
  • Upload date:
  • Size: 225.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for home_topology-1.0.1.tar.gz
Algorithm Hash digest
SHA256 3c8f48e39fc52335f38976095ab7e249f05dac08b3343e34fe5e01d407bdbfd3
MD5 9dfb45fbc6e75c4218a9cdc495fc9f67
BLAKE2b-256 1e6ff781ae79ce1b701c9ff09901d478756640d18df3de7bad4f2b91bd9cc99c

See more details on using hashes here.

Provenance

The following attestation bundles were made for home_topology-1.0.1.tar.gz:

Publisher: release.yml on mjcumming/home-topology

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file home_topology-1.0.1-py3-none-any.whl.

File metadata

  • Download URL: home_topology-1.0.1-py3-none-any.whl
  • Upload date:
  • Size: 66.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for home_topology-1.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 777823b04bb2ca4095fcc92bfe734d8bdb8495972be05b89b2c83ad09c287204
MD5 3ce55b7d5aace90491b8e35a49143ffd
BLAKE2b-256 d691158913b1a30c3dca0b8cc0d7f6e75a0b008e47fff4902e25e8d26eef179c

See more details on using hashes here.

Provenance

The following attestation bundles were made for home_topology-1.0.1-py3-none-any.whl:

Publisher: release.yml on mjcumming/home-topology

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page