ADR-001: Migrate to cosalette Framework¶
Status¶
Accepted Date: 2026-02-22
Context¶
The gas2mqtt application originated as a single 185-line Python script (hmc5883.py)
using direct smbus, paho-mqtt, and argparse calls. While functional, this
monolithic design carried significant maintenance and reliability risks:
- No test coverage — the script was untestable because I2C hardware access, MQTT publishing, and business logic were tightly coupled in a single loop.
- Hardcoded configuration — MQTT credentials and sensor parameters were embedded in source. Changing anything required editing and redeploying the script.
- No error isolation — an exception in the polling loop would crash the entire process with no notification.
- No health reporting — no heartbeats, no Last Will and Testament (LWT), no per-device availability. Silent failures were common.
- Global mutable state — the I2C bus handle, MQTT client, and counter state were all module-level globals, making the code fragile and order-dependent.
- Single sensor assumption — adding a second sensor would require duplicating most of the script.
Decision¶
Migrate gas2mqtt to the cosalette IoT-to-MQTT framework (v0.1.0+).
cosalette provides a declarative application model for IoT bridges:
@app.device()for full-lifecycle coroutines with shutdown awareness@app.telemetry()for periodic polling devices@app.command()for inbound MQTT command handlers- Automatic MQTT connection management with reconnect
- Built-in health reporting: heartbeats, per-device availability, LWT
- Automatic error isolation and error topics
- Pydantic-based settings with env /
.env/ CLI layering - Dependency injection via type annotations
- Adapter registration with dry-run alternatives
Decision Drivers¶
- Testability first — the primary goal was achieving meaningful test coverage. cosalette's DI system and adapter pattern make every component independently testable.
- Operational visibility — heartbeats, availability, and error topics are table-stakes for unattended IoT deployments.
- Configuration flexibility — moving to Pydantic settings eliminates hardcoded
values and supports Docker-native
.envfiles. - Reduced boilerplate — cosalette handles MQTT lifecycle, signal handling, and graceful shutdown, eliminating ~50 lines of manual plumbing.
Considered Options¶
- cosalette framework — purpose-built for IoT-to-MQTT bridges.
- Manual refactor — restructure the script into modules without a framework.
- Home Assistant add-on — rewrite as an HA integration.
Decision Matrix¶
| Criterion | cosalette | Manual Refactor | HA Add-on |
|---|---|---|---|
| Testability | 5 | 4 | 3 |
| Operational visibility | 5 | 2 | 4 |
| Migration effort | 4 | 3 | 2 |
| Deployment flexibility | 5 | 5 | 2 |
| Maintenance burden | 5 | 3 | 3 |
Scale: 1 (poor) to 5 (excellent)
Consequences¶
Positive¶
- 101 tests (94 unit + 7 integration) covering all domain logic, devices, adapters, and app wiring — up from zero.
- Ports-and-adapters architecture — domain logic (
schmitt.py,ewma.py,consumption.py) has zero I/O dependencies. - 12 focused modules replacing one monolithic script, each with a single responsibility.
- Declarative device model —
@app.device()coroutines withDeviceContextinjection replace manual MQTT plumbing. - Automatic health reporting — heartbeats, per-device availability, and LWT come free from cosalette.
- Docker-ready deployment — Pydantic settings +
.envfiles work naturally withdocker compose. - Dry-run mode —
FakeMagnetometeradapter enables running without hardware for development and CI.
Negative¶
- Framework dependency — the application now depends on cosalette's lifecycle and conventions. If cosalette's API changes, migration work is needed.
- Python 3.14+ requirement — cosalette requires Python 3.14+, limiting deployment to systems with recent Python.
- Learning curve — contributors need to understand cosalette's device model and DI system in addition to the domain logic.
2026-02-22