Projects / recap
Personal/Session recap

Bin Collection automation — four sensors and a projection layer

Trivial-sounding, surprisingly intricate. The story of how a 'just remind me on Tuesday night' task became a four-sensor Home Assistant state machine with bell-curve schedule-expiry alerts.

Across multiple iac sessions
home-assistant
ansible
mqtt
ios
council-data
Architecture
4 command-line sensors

check_bins (dashboard) · check_tomorrow (notification trigger) · check_schedule_expiry (bell curve) · check_address.

Resilience
90-day projection

Past confirmed_until, rotation continues forward, prefixed (PROJECTED).

Delivery
IaC-deployed

Files on iaccn01, Ansible playbook copies to HA container mount.

The setup

A bin reminder shouldn't be this interesting

It also shouldn't false-positive six days a week, or silently lapse the day the council issues a new calendar.

North Lanarkshire’s calendar is a PDF — three-week green / blue / black rotation, brown bin every other week, Wednesday collection. Standard council format. The naive solution is a weekly time-triggered automation; the right solution is a small data model with sensors derived from it, so adding new automations (notifications, dashboard cards, holiday adjustments) doesn’t require touching the bin logic.

The shape that landed was a schedule.json with metadata and a date array, four command-line sensors driving different consumers, an Ansible playbook to deploy it, and a documented “paste this prompt into Claude” workflow for regenerating the JSON when the council issues a new calendar.

Data model

schedule.json, the single source of truth

schedule.json (excerpt)
json
{
"metadata": {
  "address": "<redacted>",
  "confirmed_from": "2026-04-15",
  "confirmed_until": "2027-03-31",
  "collection_day": "Wednesday",
  "rotation": ["green", "blue", "black"],
  "brown_alternates": true,
  "notes": "Council holidays (Christmas/New Year) may shift dates — verify against printed calendar."
},
"schedule": [
  {"date": "2026-04-15", "bins": ["blue"]},
  {"date": "2026-04-22", "bins": ["green"]},
  {"date": "2026-04-29", "bins": ["general"]},
  {"date": "2026-05-06", "bins": ["blue", "brown"]},
  {"date": "2026-05-13", "bins": ["green"]}
]
}
The four sensors

One JSON, four consumers

Each sensor has one job. The split is what eliminates the false-positive problem.

check_bins.sh
dashboard

always shows next collection + countdown

check_tomorrow.sh
trigger

returns "none" 6 days/wk; only fires on collection eve

check_schedule_expiry.sh
bell-curve

days remaining until confirmed_until lapses

check_address.sh
dashboard

address tag for dashboard card

Issues hit

The bugs that turned a one-day build into a five-session arc

Notification fired every single day

home-assistantautomations Major
Symptom

Set up a time trigger at 18:00 with a condition sensor.bin_collection_tomorrow != 'none'. Got reminded “Bins out tonight” every evening — including Mondays, Thursdays, Fridays. Useless.

Root cause

The dashboard sensor always has a value — it’s the next collection with a countdown (“Next: Green in 4 days”), which is what makes the dashboard card useful. The condition != 'none' is therefore always true.

Resolution

Split the dashboard sensor from the notification sensor. sensor.bin_collection_alert (new) returns 'none' six days a week and only returns bin names when tomorrow is collection day. Automation triggers off the alert sensor; dashboard keeps using the always-populated one. The pattern generalises: any sensor used both for display and notification will produce false positives in one of the two contexts.

Schedule silently expires when council issues new calendar

data-freshness Moderate
Symptom

Initial design: hardcoded confirmed dates only. Past the last date, every sensor returns “none” — the dashboard says nothing, no notifications fire, no warning. The system silently degrades to non-functional.

Root cause

No notion of schedule freshness, no projection past the last known date, no expiry alerts.

Resolution

Added metadata.confirmed_from / confirmed_until, a projection layer in check_bins.sh that continues the rotation rules forward up to 90 days (prefixed (PROJECTED) so the source-of-truth distinction is preserved), and a check_schedule_expiry.sh sensor that returns integer days remaining. Bell-curve automation fires reminders at ±28, ±14, ±7, ±3, and 0 days relative to expiry so the schedule never silently lapses.

Schedule JSON regeneration was a chore

workflow Minor
Symptom

Hand-typing 52 dates plus bin types from a council PDF, every year, is the kind of toil that means it doesn’t get done.

Root cause

No tooling around the data refresh.

Resolution

Documented a “paste this prompt into Claude / ChatGPT” workflow with the exact JSON schema and the rotation rules pre-stated. Operator pastes the council’s plain-text dates in any format; the model returns a single-line JSON ready to paste into the cat > schedule.json line on iaccn01. Refresh time: ~2 minutes.

What works well

Two patterns worth keeping

  • pattern·Single data file, multiple sensors. Adding a new consumer (e.g. a Lovelace card on a different dashboard, a daily morning summary, a holiday-week override) is a new .sh script and an HA sensor entry — no changes to the underlying data.
  • pattern·Confirmed data + projection layer with clear labelling. The system never fails closed when input data goes stale; it degrades to projected output that’s clearly marked, while a separate sensor measures and alerts on the staleness itself.
  • pattern·Display sensor ≠ notification sensor. Whenever a single sensor is used for both, false positives happen in one or the other. Splitting them early is cheaper than debugging the symptom later.
Stack

What it took to ship

Home Assistantcommand_line sensorsBash + PythonAnsibleiOS Companion appMosquitto MQTT