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
Metadata
Release files for appdaemon-lighting 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| appdaemon_lighting-0.2.0.tar.gz | 31.1 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| appdaemon_lighting-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 54.8 kB
Release files / appdaemon_lighting-0.2.0.tar.gz
| Download URL | appdaemon_lighting-0.2.0.tar.gz |
|---|---|
| Size | 31.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
28c5333a3236d38f51b37d46116a0679481c8ef7436ebc4e8d4bb9e1c87f893c
|
|
BLAKE2b-256 checksum How to use checksums |
840743ddb07f9f0e6d1bbe8923edf9621ef5f105af7ab5b85e3ad5613b927eed
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Feb 13, 2026.
Transparency logRelease files / appdaemon_lighting-0.2.0-py3-none-any.whl
| Download URL | appdaemon_lighting-0.2.0-py3-none-any.whl |
|---|---|
| Size | 23.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d19c1c088644f7f91768022b34a9fa0734c4e71fb60d8dea9f6487e60057da98
|
|
BLAKE2b-256 checksum How to use checksums |
2e4cc7d59411e8e1bb8d59015cc0ff458f90b2de5ba2b86b867cbed4f85b2793
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Feb 13, 2026.
Transparency logSecurity for appdaemon-lighting 0.2.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for appdaemon_lighting-0.2.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Feb 13, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.