Skip to main content

Composable lighting building blocks for AppDaemon + Home Assistant

Project description

appdaemon-lighting

Composable lighting building blocks for AppDaemon + Home Assistant.

What Is This?

If you've written AppDaemon lighting automations, you've probably solved the same problems more than once: rate-limiting service calls so you don't flood Zigbee meshes, tolerating small brightness differences so lights don't flicker, detecting when a bulb has drifted from its target after a manual override or a power blip, and restoring state after a temporary scene. This library extracts those solutions into a small set of typed, composable Python building blocks.

The core idea is a LightTarget — a frozen dataclass that says "this entity should be on at this brightness and colour temperature". Your app computes a list of targets from whatever signals it cares about (motion, occupancy, time of day, switches). The library then handles the messy part: signature-based deduplication, per-light and global rate limiting, deadband tolerance to avoid writing trivially similar states, and drift detection that repairs lights the user (or a reboot) has nudged away from the target. An optional overlay manager snapshots and restores light state for temporary modes, and a reconciler watches for entities that go unavailable and re-applies the correct state when they come back online.

Everything talks through Python Protocols — there is no dependency on AppDaemon at runtime. You write a thin adapter (a few one-line methods wrapping self.call_service and self.get_state), and the rest is plain Python that you can unit-test without a running HA instance. The configuration models use Pydantic, which means your YAML gets validated at app startup rather than silently misbehaving at 2am.

This is not a framework. It doesn't manage your app lifecycle, impose a file layout, or decide how your layers combine. You wire together exactly the pieces you need. If all you want is the actuator, import that. If you want the full zone/layer/period registry with overlay and reconciliation, that works too. It came out of a real home with ~40 lights across five rooms, so the defaults are practical — but it's a library for people who want to understand and control their own automation logic, not a black box.

Install

pip install appdaemon-lighting

Components

graph BT
    %% ── Inputs (signals from HA) ─────────────────────────────────
    subgraph Inputs["HA Signals (your app reads these)"]
        sensors["sensor.*  binary_sensor.*\nmotion · lux · occupancy · switches"]
        period["Period\n'breakfast' | 'evening' | …"]
    end

    %% ── Configuration layer (registry.py) ────────────────────────
    subgraph Registry["registry · Pydantic config models"]
        zones["ZoneConfig\nentities: light.a, light.b"]
        profiles["PeriodProfile\nct_mired · transition_s"]
        layers["LayerConfig × N\nname · enabled_entity\n┗ LayerZoneConfig per zone\n   bri_pct · ct_mired\n   bri_pct_by_period"]
    end

    %% ── Target computation (your app logic) ──────────────────────
    targets["LightTarget[]\nentity_id · on · brightness\nct_mired · transition · zone · layer"]

    %% ── Signature gating (signatures.py) ─────────────────────────
    sig["stable_signature()\ndeterministic JSON hash\nskip apply if unchanged"]

    %% ── Actuator (actuator.py) ───────────────────────────────────
    subgraph Act["Actuator"]
        config["ActuatorConfig\nrate limits · deadband tols"]
        actuator["Actuator.apply(targets)\n→ global rate gate\n→ per-light rate gate\n→ deadband match\n→ drift detection"]
        result["ApplyResult\napplied · suppressed_match\nsuppressed_rate · sig_unchanged"]
    end

    %% ── Overlay (overlay.py) — parallel path ────────────────────
    subgraph Ovr["OverlayManager (optional)"]
        snapshot["LightSnapshot\ncaptures current HA state"]
        overlay["enter(mode, entities)\nsnapshot → override → exit() restores"]
    end

    %% ── Reconciler (reconcile.py) — background repair ───────────
    subgraph Rec["Reconciler (optional)"]
        watcher["register_watchers()\nlisten_state on controlled entities"]
        recon["unavailable → available\nschedule settle → on_reconcile()"]
    end

    %% ── Output ───────────────────────────────────────────────────
    ha["HAService Protocol\nturn_on · turn_off · get_state\n(adapter around hass.Hass)"]
    lights["💡 Physical lights in HA"]

    %% ── Edges ────────────────────────────────────────────────────
    sensors --> targets
    period --> profiles
    profiles --> targets
    zones --> targets
    layers --> targets

    targets --> sig --> actuator
    config --> actuator
    actuator --> result
    actuator --> ha

    overlay --> ha
    snapshot -.->|"exit: restore"| ha

    watcher --> recon
    recon -.->|"triggers re-apply"| targets

    ha --> lights

    %% ── Styles ───────────────────────────────────────────────────
    style Inputs fill:#e8f4f8,stroke:#5ba3c9,color:#333
    style Registry fill:#fef9e7,stroke:#d4a017,color:#333
    style Act fill:#fdebd0,stroke:#ca6f1e,color:#333
    style Ovr fill:#ebdef0,stroke:#8e44ad,color:#333
    style Rec fill:#e8f8f5,stroke:#1abc9c,color:#333
    style targets fill:#fff,stroke:#2c3e50,color:#333,stroke-width:2px
    style sig fill:#f0f0f0,stroke:#666,color:#333
    style ha fill:#d5f5e3,stroke:#27ae60,color:#333
    style lights fill:#fffacd,stroke:#daa520,color:#333

Module reference

Module What it does
types (LightTarget) Frozen dataclass describing the desired state of a single light — entity, on/off, brightness, CT, transition, plus zone/layer metadata.
registry (ZoneConfig, LayerConfig, PeriodProfile) Pydantic models that declare which lights belong to which zones, what each layer does to each zone, and what the default CT/transition is per time-of-day period.
signatures (stable_signature) Produces a deterministic JSON hash from a list of LightTargets so you can skip redundant apply cycles when nothing changed.
actuator (Actuator) Takes a list of LightTargets and writes them to HA, gated by global and per-light rate limits, brightness/CT deadband tolerance, and drift detection that repairs manual overrides.
overlay (OverlayManager) Snapshots the current state of lights on entry, lets a temporary mode take over, then restores the original state on exit.
reconcile (Reconciler) Watches controlled entities for unavailable→available transitions and automatically re-applies the correct lighting state after the bulb settles.
utils Pure helper functions — clamp, lerp, smoothstep, linmap, safe_float, as_bool, brightness↔percent conversions.

Quick Example

import appdaemon.plugins.hass.hassapi as hass
from appdaemon_lighting import Actuator, ActuatorConfig, LightTarget

class MyLights(hass.Hass):
    def initialize(self):
        self.actuator = Actuator(
            config=ActuatorConfig(rate_limit_s=0.5),
            call_service=self.call_service,
            get_state=self.get_state,
            now_fn=self.datetime,
            log_fn=self.log,
        )

    def apply_scene(self):
        targets = [
            LightTarget(
                entity_id="light.ceiling",
                brightness=200,
                color_temp=350,
                transition=2,
            ),
        ]
        self.actuator.apply(targets)

Design Principles

  • Composable — each component works standalone
  • No magic — you wire things together explicitly
  • Typed — full type hints, Protocols for extension
  • Testable — all logic works without AppDaemon runtime
  • Zero AppDaemon dependency — Protocols only

License

MIT

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

appdaemon_lighting-0.2.0.tar.gz (31.1 kB view details)

Uploaded Source

Built Distribution

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

appdaemon_lighting-0.2.0-py3-none-any.whl (23.7 kB view details)

Uploaded Python 3

File details

Details for the file appdaemon_lighting-0.2.0.tar.gz.

File metadata

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

File hashes

Hashes for appdaemon_lighting-0.2.0.tar.gz
Algorithm Hash digest
SHA256 28c5333a3236d38f51b37d46116a0679481c8ef7436ebc4e8d4bb9e1c87f893c
MD5 6b94259cc79149805744ce233b99ed2e
BLAKE2b-256 840743ddb07f9f0e6d1bbe8923edf9621ef5f105af7ab5b85e3ad5613b927eed

See more details on using hashes here.

Provenance

The following attestation bundles were made for appdaemon_lighting-0.2.0.tar.gz:

Publisher: publish-lighting.yml on rsr5/ha-appdaemon

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

File details

Details for the file appdaemon_lighting-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for appdaemon_lighting-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d19c1c088644f7f91768022b34a9fa0734c4e71fb60d8dea9f6487e60057da98
MD5 6ef043e3f689ac2dc064bdd265871bf0
BLAKE2b-256 2e4cc7d59411e8e1bb8d59015cc0ff458f90b2de5ba2b86b867cbed4f85b2793

See more details on using hashes here.

Provenance

The following attestation bundles were made for appdaemon_lighting-0.2.0-py3-none-any.whl:

Publisher: publish-lighting.yml on rsr5/ha-appdaemon

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