IoT · Automation · White Paper

Sensor to Alert

IoT environment monitoring on a self-hosted stack. How a bearded dragon's enclosure became a proof-of-work for closed-loop automation, Prometheus observability, and Infrastructure-as-Code discipline — all deployed with a single make command.

Home Assistant Zigbee2MQTT Matter MQTT Proxmox VE Prometheus Ansible Discord
Executive Summary

Gutgrinda Skullkrumpa da Choppy is a bearded dragon. She requires careful temperature management: basking temps between 95–110°F during the day, ambient heat above 70°F at night, and full-spectrum lighting on a seasonal schedule. Before automation, this was entirely manual — check a thermometer, toggle a plug, hope nothing goes wrong overnight. The system built here uses a Zigbee sensor, three Matter smart plugs, and 236 lines of Home Assistant YAML to handle every edge case: day/night transitions, seasonal light schedules, failure alerting, and runtime threshold adjustment — all deployed from a single make command, all producing Prometheus metrics that feed into the same Grafana dashboards used for the rest of the homelab.

01The Problem with Manual Anything

Bearded dragons are ectotherms. They cannot regulate their own body temperature, which means the enclosure is their entire climate system. The thermal requirements are specific:

Basking zone
95–110°F
Daytime only
Ambient (cool side)
80–85°F
Daytime
Nighttime floor
>65–70°F
Supplemental heat if needed

These are not suggestions. Sustained temperatures outside these ranges cause illness. A lamp left on all night disrupts sleep cycles. A dead bulb on a cold night is an emergency.

Before automation, management was entirely manual: check the thermometer, toggle a smart plug app, remember to turn things off before bed. The cognitive overhead was low on any given day but accumulated across weeks and months. More importantly, it was failure-prone — there is no reliable human process for "check the thermometer at 2am."

The goal was simple: zero manual intervention under normal conditions, immediate notification when something is abnormal.

02Hardware Layer

The physical layer consists of four devices, all running open protocols with no proprietary cloud dependencies.

DeviceModelProtocolRole
Temp/humidity sensor ZG-227Z Zigbee → Z2M → MQTT Ground truth for all automation logic
Basking lamp plug Linkind LC09003256 Matter (WiFi) Primary daytime heat source
Ambient light plug Linkind LC09003256 Matter (WiFi) Full-spectrum UVB light, seasonal schedule
Ceramic heater plug Linkind LC09003256 Matter (WiFi) Nighttime supplemental heat

Why Zigbee for the sensor? Battery-powered Zigbee sensors are cheap, reliable, and integrate directly with the Zigbee2MQTT instance running on the homelab. The ZG-227Z reports temperature and humidity every few minutes on a CR2032 battery with months of life.

Why Matter for the plugs? Matter over WiFi gives local-network control without a proprietary cloud. The Linkind plugs commission directly into Home Assistant, are controllable offline, and expose on/off state back to HA for monitoring. No proprietary hubs. No cloud dependencies. Everything talks to infrastructure I control.

03Integration Layer

The data path from hardware to automation engine runs through three stages:

ZG-227Z Zigbee sensor
Reports temperature + humidity every few minutes via Zigbee radio
Zigbee2MQTT — LXC 192.168.86.36
Handles Zigbee coordinator, publishes device state to Mosquitto MQTT broker on same LXC
Home Assistant OS — VM 192.168.86.41
Subscribes to MQTT topics, surfaces sensor as sensor.* entities, runs automation engine
Switch entities
Basking lamp, ambient light, ceramic heater — controlled via Matter over LAN
Discord webhook
Alert notifications for out-of-range temps and sensor offline events

Home Assistant OS runs as a VM on the Proxmox cluster. HAOS restricts direct SSH access (a protected addon limitation), so file deployment goes through the Proxmox qm guest exec API rather than direct SSH — handled transparently by the Makefile. Matter commissioning is done once via the HA Companion app; after initial pairing, plugs are persistent switch.* entities.

04Automation Logic

All automation lives in a single HA package file (beardie.yaml) deployed to the HAOS config directory. The logic divides into three clean domains: daytime heat management, nighttime heat management, and the seasonal lighting schedule. Sun state comes from HA's built-in sun.sun entity, which calculates sunrise/sunset from the configured latitude/longitude — the schedule adapts to seasonal day length automatically, no cron jobs required.

Daytime Heat — Basking Lamp

The basking lamp runs closed-loop temperature control during daylight hours:

temp < 95°F AND sun above horizon
basking lamp ON
temp > 110°F (anytime)
basking lamp OFF
sunset (event)
basking lamp OFF failsafe

The sunset failsafe is intentional redundancy. The too-hot cutoff would trigger if the lamp ran at night and overheated, but explicitly turning it off at sunset eliminates the scenario entirely.

Nighttime Heat — Ceramic Heater

The ceramic heater runs the same closed-loop logic, constrained to nighttime hours:

temp < 70°F AND sun below horizon
ceramic heater ON
temp > 80°F AND sun below horizon
ceramic heater OFF
sunrise (event)
ceramic heater OFF handoff

The sunrise handoff is the cleanest part of the design. The ceramic heater and basking lamp never run simultaneously — the heater shuts off at sunrise, and the basking lamp's own automation takes over within minutes as the enclosure warms up.

Lighting Schedule — Ambient Light

UVB lighting follows fixed offsets from sunrise and sunset. The 30-minute buffers mimic a natural dusk/dawn transition and give the basking lamp a head start before UVB comes on each morning.

sunrise + 30 minutes
ambient light ON
sunset − 30 minutes
ambient light OFF

Runtime Threshold Adjustment

All temperature thresholds are exposed as input_number helpers in the HA dashboard. Changing a threshold is a slider adjustment — no YAML edit, no redeploy. This matters for seasonal tuning: summer ambient temps mean the enclosure holds heat differently than winter.

ThresholdDefaultAdjustable Range
Basking lamp ON95°F70–120°F
Basking lamp OFF110°F70–130°F
Ceramic heater ON70°F50–85°F
Ceramic heater OFF80°F55–90°F
Alert: too hot115°F80–140°F
Alert: too cold70°F50–90°F

05Alerting

Three conditions trigger Discord notifications. Hold timers before alerting are intentional — a momentary temperature spike from opening the enclosure door should not fire a page. A sustained anomaly should.

Too hot >115°F sustained for 5 minutes
WAAAGH! Gutgrinda iz too HOT! Da warboss iz roastin' at 118°F — above da 115°F limit. TURN IT OFF!
Too cold <70°F sustained for 10 minutes
Gutgrinda iz FREEZIN'! Da warboss iz shiverin' at 67°F — below da 70°F limit. GET DA BURNA!
Sensor offline unavailable for 15 minutes
Da Gubbinz iz Broken! Gutgrinda'z temperature sensor iz offline — check da Zigbee2MQTT at 192.168.86.36:8080, ya grot!

The Discord webhook URL is stored in HAOS secrets.yaml and never committed to the repository. The Makefile accepts it as a deploy-time parameter and writes it to the secrets file — the value lives only in the running system and in the operator's secure storage.

06Infrastructure-as-Code

The full deployment runs from a single make target:

bash
make beardie GUTGRINDA_DISCORD_WEBHOOK=<webhook_url> HA_TOKEN=<token>

The Makefile target deploys beardie.yaml to /mnt/data/supervisor/homeassistant/packages/ on the HAOS VM via qm guest exec (Proxmox host API), then calls the HA API to reload automations. If the Discord webhook has not changed, the parameter can be omitted — the existing value in secrets.yaml is preserved.

Reproducible

No configuration exists outside of the repository and the running system. Wipe the HAOS VM and make beardie restores the full automation stack in under two minutes.

Secrets-safe

Webhook URLs and API tokens are passed as parameters at deploy time. Nothing sensitive is committed to the repository. Secrets live only in the running system.

Zero runtime configuration

Every threshold is runtime-adjustable via the HA dashboard. Changing a value does not require a redeploy — the YAML reacts to input_number state in real time.

This is the same IaC discipline applied across the rest of the homelab — every service is a make target, every secret is a parameter, nothing is manually configured. See the Homelab Architecture paper for the full stack this runs on.

07Observability

The package exposes all enclosure entities to Prometheus via the HA Prometheus integration:

yaml
prometheus:
  namespace: homeassistant
  filter:
    include_entity_globs:
      - "sensor.0xa4c13874d0343902_*"   # temp + humidity
      - "switch.gutgrinda_*"             # lamp/heater state
      - "input_number.gutgrinda_*"       # thresholds

Temperature history, device on/off state, and threshold values all appear in Grafana alongside the rest of the infrastructure metrics. A single dashboard can show server CPU load next to enclosure temperature — not because those metrics are related, but because the same observability stack covers everything.

What Prometheus reveals that thresholds don't

A basking lamp that cycles on and off every 90 seconds is technically within range — temps are between 95°F and 110°F — but the duty cycle is abnormal. It may indicate a failing bulb, a drafty enclosure location, or unusual ambient temps. Prometheus duty-cycle data makes this visible; raw threshold logic never would.

08Where AI Enters the Picture

The current system is entirely rule-based. It works well for the defined conditions, but rule-based systems have a known failure mode: they handle the cases you thought of, not the ones you didn't. AI augmentation can address three gaps without replacing the automation layer.

Anomaly detection

A model trained on normal duty-cycle patterns could flag abnormal lamp cycling before temperatures actually go out of range — catching a failing bulb or unusual ambient condition earlier than any fixed threshold.

Seasonal threshold recommendations

Prometheus has months of temperature and duty-cycle history. An LLM with access to that data and the current date could recommend threshold adjustments based on how the enclosure behaves differently across seasons.

Natural language health summaries

Instead of raw Grafana panels, a daily summary generated from Prometheus data: average temps, heater activation count, days since last alert. No new sensors — just a Claude API call against data already collected.

None of this requires replacing the rule-based system. The automations run regardless. AI adds a layer of interpretation on top of data that's already being collected — the same pattern that makes AlertMind useful for infrastructure triage.

09The Pattern Is Not About Bearded Dragons

The full stack in use here is the same architecture pattern found in industrial monitoring, building management systems, and server room environmental control — implemented on commodity hardware with open-source software.

EnvironmentSensor"Basking Lamp"Alert trigger
Server room Temp/humidity sensor CRAC unit relay Rack ambient >85°F
Greenhouse Temp + soil moisture Misting relay + grow light Frost risk or drought threshold
Brewery fermentation Thermocouple probe Heating pad or glycol chiller Out-of-range fermentation temp
3D printing farm Enclosure temp + humidity Heater relay, exhaust fan ABS warp risk, filament moisture

In every case: sensor → MQTT → Home Assistant → smart relay → alert. The YAML changes. The stack does not. The same Proxmox homelab that runs this enclosure automation also runs Traefik, Authentik, a Kubernetes cluster, Grafana, and AlertMind — all managed by a single operator from a single Makefile.

10Conclusion

What started as "I don't want to manually manage smart plugs" became a fully observable, IaC-driven, alert-enabled environment monitoring system running on the same infrastructure stack used for production-grade services.

The implementation took one afternoon. The ongoing maintenance cost is near zero — the system has operated for months without a config change. The alerts have fired twice: once when a bulb started failing and the lamp couldn't reach target temp, and once when the Zigbee sensor battery died. Both times, Discord delivered the notification before the temperature reached a dangerous level.

The actual outcome

Not the technology. Not the architecture diagram. The outcome is that an enclosure is safer and more consistent than it was before, managed by a system that runs without human intervention and pages when something is wrong. That outcome is achievable for any environment where temperature, humidity, or device state matters — at the cost of a few smart plugs, an open-source automation platform, and an afternoon of YAML.