Home Lighting Programmer
Ecosystem-agnostic smart home automation using the Matter protocol. Replaces physical switches with sensor-driven control of lights (circadian-aware brightness + color temperature schedules) and air conditioners (temperature-driven hysteresis with occupancy gating).
Note: For domestic use only. Not hardened for production/commercial deployment.
How It Works
The system runs a 1Hz loop that:
- Reads real-time sensor data (motion/presence) via Server-Sent Events
- Interpolates the configured schedule to determine target brightness and color temperature
- Sends commands only when the target state changes
Three lighting modes are supported:
- Decoration - Static color and intensity
- Utility - Sensor-triggered, low-latency response
- Ambient - Time-based color temperature and brightness that follows natural daylight
Requirements
- Python 3
- A running
matter-web-controllerinstance - Matter-compatible lights and sensors
Installation
pip install light-programmer
Quick Start
# Step 1: Auto-generate config from your hardware
light-genconfig --ip 192.168.1.220 --port 8080 --out config.json
# Step 2: Edit config.json to customize schedules and sensor logic
# Step 3: Run
light-programmer --server 192.168.1.220:8080 --config config.json
Configuration
Each device entry in the config JSON has:
{
"id": "dev_kitchen_sink", // Matter node ID
"note": "Sink area light", // Human-readable description
"schedule": [ // Time-based control points
{ "time": "06:30", "level": 50, "kelvin": 4000 },
{ "time": "12:00", "level": 100, "kelvin": 4000 },
{ "time": "21:30", "level": 100, "kelvin": 2700 }
],
"sensor": [ // Simple sensor trigger
{ "id": "kitchen_motion", "timeout": 5 }
]
}
level: Brightness 0-100%kelvin: Color temperature 2700-6500K (omit for non-color lights)timeout: Seconds to keep light on after sensor clears- Values between schedule points are linearly interpolated
See sample.json for a full working example.
Advanced Sensor Logic
For complex scenarios, use sensor_condition instead of sensor. It supports a tree of boolean operators:
| Node Type | Description |
|---|---|
sensor |
true if occupied or within timeout |
time_window |
true if current time is between start and end (cross-midnight supported) |
AND |
All operands must be true |
OR |
At least one operand must be true |
NOT |
Inverts its operand |
Example: Light on only when at desk AND not in bed
{
"sensor_condition": {
"operator": "AND",
"operands": [
{ "type": "sensor", "id": "desk_presence", "timeout": 15 },
{
"operator": "NOT",
"operands": [
{ "type": "sensor", "id": "bed_presence", "timeout": 5 }
]
}
]
}
}
Example: Follow schedule during day, sensor-only at night
{
"sensor_condition": {
"operator": "OR",
"operands": [
{ "type": "time_window", "start": "06:00", "end": "22:00" },
{ "type": "sensor", "id": "room_motion", "timeout": 5 }
]
}
}
During 06:00-22:00 the light follows its schedule regardless of sensors. Outside that window, it only turns on when the sensor detects motion.
Air Conditioner Control
AC entries are climate-driven (no time schedule). They use a Matter thermostat (/api/ac) and read ambient state via /api/climate — either from one or more standalone climate sensors, or directly from the AC's own thermostat (local_temperature) when no sensor is configured.
{
"id": "dev_ac_livingroom",
"type": "ac", // marks this entry as an AC
"climate_sensor": "dev_temp_livingroom",
"mode": "cool", // cool / heat / dry / fan / auto
"setpoint": 26.0, // °C sent to the thermostat
"on_above": 29.0, // turn on when ambient ≥ 29 °C
"off_below": 26.5, // turn off when ambient ≤ 26.5 °C
"on_delay_minutes": 5, // require continuous occupancy ≥ 5 min before turning on
"active_window": {"start": "10:00", "end": "23:30"},
"sensor": [
{"id": "dev_occ_livingroom", "timeout": 15}
]
}
Bring-up rule (off → on): climate trigger AND occupancy continuously satisfied for on_delay_minutes AND time within active_window.
Bring-down rule (on → off): climate trigger no longer met, OR occupancy fails (after each sensor's timeout hold), OR outside active_window.
The climate trigger is the OR of two independent hysteresis loops — temperature (on_above / off_below) and humidity (humidity_above / humidity_below). The AC turns on when either dimension says on, and off only when every configured dimension says off. In the dead band of every dimension, it holds the previous state. Use climate_sensors (list) to aggregate readings across multiple sensors with any-trigger semantics (max temp / max humidity). For mode: "heat", use on_below / off_above instead.
The same sensor / sensor_condition AST used by lights applies — combine multiple occupancy sensors with AND/OR/NOT as needed. If a climate reading is unavailable, that dimension holds its last decision rather than flapping.
MCP Server (AI Agent Configuration)
An MCP server is bundled so AI agents (Claude Desktop, Claude Code, etc.) can discover devices and edit the config for you.
pip install light-programmer[mcp]
light-programmer-mcp # stdio transport
Example Claude Desktop config (~/Library/Application Support/Claude/claude_desktop_config.json):
{
"mcpServers": {
"light-programmer": {
"command": "light-programmer-mcp",
"env": { "MATTER_SRV_KEY": "your-api-key" }
}
}
}
Tools exposed:
| Tool | Purpose |
|---|---|
list_devices |
Discover lights / sensors / climate / AC from /api/metadata |
read_climate, read_ac_state, read_status |
Live readings |
read_config, write_config, validate_config |
Whole-file CRUD with schema validation |
upsert_entry, remove_entry |
Per-entry edits keyed by device id |
set_light, set_ac |
Direct device control for quick tests |
config_schema (prompt) |
Schema reference an agent can pull when authoring entries |
Validation rejects writes with bad time formats, out-of-range levels/Kelvin, missing thresholds, or hysteresis bands where off_below ≥ on_above (cool) / off_above ≤ on_below (heat).
Project Structure
| File | Purpose |
|---|---|
light_programmer/programmer.py |
Main automation controller — runs the 1Hz loop for lights and ACs |
light_programmer/matter_lib.py |
Device abstractions: LightDevice, SensorDevice, ClimateSensorDevice, ACDevice |
light_programmer/genconfig.py |
Generates config JSON from hardware discovery |
light_programmer/mcp_server.py |
MCP server exposing discovery + config CRUD tools to AI agents |
sample.json |
Example configuration with 11 devices |
pyproject.toml |
Package configuration and CLI entry points |
License
MIT
Metadata
Release files for light-programmer 0.6.2
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 | Actions |
|---|---|---|---|---|
| light_programmer-0.6.2.tar.gz | 27.4 kB | With provenance | Details for light_programmer-0.6.2.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| light_programmer-0.6.2-py3-none-any.whl | Python 3 | none | any | Details for light_programmer-0.6.2-py3-none-any.whl |
Total release size: 55.1 kB
Release files / light_programmer-0.6.2.tar.gz
| Download URL | light_programmer-0.6.2.tar.gz |
|---|---|
| Size | 27.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8311fe03d8e3b0e51513c4c751d0233771869c942b38ce187d6e197f71fd692c
|
|
BLAKE2b-256 checksum How to use checksums |
952851e7af2cce77ed339f8fc48545dbd3dd25b2bf6d2c148e5193ada6dc5b8c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
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 May 4, 2026.
Transparency logRelease files / light_programmer-0.6.2-py3-none-any.whl
| Download URL | light_programmer-0.6.2-py3-none-any.whl |
|---|---|
| Size | 27.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
fa81cc37369c881f6cddd4f1df1db8276cedaf352ff4c5ada71ec3a0be91dd18
|
|
BLAKE2b-256 checksum How to use checksums |
061379a3631b37ff6e1faead98e6d59af41166b3bc82ea6327fb40040826ceee
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
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 May 4, 2026.
Transparency logSecurity for light-programmer 0.6.2
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 light_programmer-0.6.2.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 May 4, 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.
To learn more about protecting yourself from malicious actors, visit our package security documentation.