Devices¶
cosalette device handlers — the bridge between domain logic and the MQTT framework.
Gas Counter¶
gas2mqtt.devices.gas_counter
¶
Gas counter telemetry — stateful trigger detection and counting.
Uses shared GasCounterState instance created by the @app.state provider in main.py and injected via DI. Telemetry handler is called periodically (poll_interval). Command handler processes MQTT consumption updates.
Scheduled runs: read magnetometer, detect trigger edges, count ticks. Command runs: set consumption value from MQTT command payload.
State persistence
GasCounterState wraps a DeviceStore. stage_state() explicitly calls store.save() to persist counter and consumption on changes. Counter and consumption survive restarts.
MQTT state payload
{"counter": 42, "trigger": "CLOSED"} or with consumption:
MQTT command payload (on gas2mqtt/consumption/set):
GasCounterReading
dataclass
¶
GasCounterReading(
counter: Annotated[
int,
Field(
json_schema_extra=consumer(
display_name="Gas Pulse Counter",
state_class="total_increasing",
icon="mdi:counter",
)
),
],
trigger: str,
consumption_m3: Annotated[
float | None,
Field(
json_schema_extra=consumer(
display_name="Gas Consumption",
device_class="gas",
unit="m³",
state_class="total_increasing",
)
),
] = None,
)
Published gas-counter telemetry payload.
Typed state_model for the gas_counter telemetry channel so
cosalette schema init emits typed properties carrying
x-cosalette-consumer metadata for Home Assistant discovery. The
metadata rides on each field via :func:pydantic.Field's
json_schema_extra, so it survives task gas2mqtt:schema:generate
(TypeAdapter(model).json_schema() preserves it) — no post-generation
hand-application. Mirrors the dict returned by
:meth:GasCounterState.build_state.
Attributes:
| Name | Type | Description |
|---|---|---|
counter |
Annotated[int, Field(json_schema_extra=consumer(display_name='Gas Pulse Counter', state_class='total_increasing', icon='mdi:counter'))]
|
Raw pulse count (wraps at |
trigger |
str
|
Schmitt-trigger edge state — |
consumption_m3 |
Annotated[float | None, Field(json_schema_extra=consumer(display_name='Gas Consumption', device_class='gas', unit='m³', state_class='total_increasing'))]
|
Accumulated gas volume in m³, or |
GasCounterState
¶
GasCounterState(
trigger: SchmittTrigger,
counter: int,
consumption: ConsumptionTracker | None,
store: DeviceStore,
)
Mutable state container for the gas counter.
Created once by make_gas_counter() called from the @app.state provider, persists across all scheduled and triggered telemetry runs.
Source code in packages/src/gas2mqtt/devices/gas_counter.py
make_gas_counter
¶
make_gas_counter(
settings: Gas2MqttSettings,
store: DeviceStore,
logger: Logger,
) -> GasCounterState
Factory — creates stateful domain objects.
Called once by the @app.state provider before the telemetry loop. The returned GasCounterState is injected into gas_counter() on every scheduled and triggered run.
Source code in packages/src/gas2mqtt/devices/gas_counter.py
gas_counter
async
¶
gas_counter(
state: GasCounterState,
magnetometer: MagnetometerPort,
logger: Logger,
) -> dict[str, object] | None
Gas counter telemetry handler (read-only).
Called periodically to read magnetometer and count ticks. Returns dict to publish, or None to skip.
Source code in packages/src/gas2mqtt/devices/gas_counter.py
update_consumption
async
¶
update_consumption(
payload: str, state: GasCounterState, logger: Logger
) -> dict[str, object] | None
Handle inbound MQTT command to set consumption value.
Expects JSON payload: {"consumption_m3":
Source code in packages/src/gas2mqtt/devices/gas_counter.py
Temperature¶
gas2mqtt.devices.temperature
¶
Temperature telemetry — PT1-filtered, calibrated sensor readings.
Reads the raw temperature from the magnetometer's built-in sensor,
applies a linear calibration (temp_scale * raw + temp_offset),
then smooths via an exponentially-weighted PT1 (first-order lag) filter.
The OnChange publish strategy ensures readings are only published
when the filtered value shifts by more than 0.05 °C, avoiding MQTT
chatter for sensor noise.
MQTT state payload
{"temperature": 22.5}
TemperatureReading
dataclass
¶
TemperatureReading(
temperature: Annotated[
float,
Field(
json_schema_extra=consumer(
device_class="temperature",
unit="°C",
state_class="measurement",
)
),
],
)
Published temperature telemetry payload.
Typed state_model for the temperature telemetry channel so
cosalette schema init emits a typed property carrying
x-cosalette-consumer metadata for Home Assistant discovery. The
metadata rides on the field via :func:pydantic.Field's
json_schema_extra, so it survives task gas2mqtt:schema:generate
(TypeAdapter(model).json_schema() preserves it) — no post-generation
hand-application.
Attributes:
| Name | Type | Description |
|---|---|---|
temperature |
Annotated[float, Field(json_schema_extra=consumer(device_class='temperature', unit='°C', state_class='measurement'))]
|
PT1-filtered temperature in degrees Celsius. |
make_pt1
¶
make_pt1(settings: Gas2MqttSettings) -> Pt1Filter
Create PT1 filter from settings for temperature smoothing.
This is the init= factory: cosalette calls it once before the
handler loop and injects the returned Pt1Filter by type.
Source code in packages/src/gas2mqtt/devices/temperature.py
temperature
async
¶
temperature(
magnetometer: MagnetometerPort,
settings: Gas2MqttSettings,
pt1: Pt1Filter,
) -> dict[str, object]
Read temperature, calibrate, filter, and return state dict.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
magnetometer
|
MagnetometerPort
|
Sensor adapter (injected by cosalette DI). |
required |
settings
|
Gas2MqttSettings
|
Application settings with calibration coefficients. |
required |
pt1
|
Pt1Filter
|
PT1 filter instance (created by :func: |
required |
Returns:
| Type | Description |
|---|---|
dict[str, object]
|
|
dict[str, object]
|
the framework when the |
Source code in packages/src/gas2mqtt/devices/temperature.py
Magnetometer¶
gas2mqtt.devices.magnetometer
¶
Magnetometer debug telemetry — raw 3-axis magnetic field readings.
Publishes raw Bx, By, Bz values from the QMC5883L sensor at the gas
counter's poll interval. Disabled by default — enable via the
enable_debug_device setting for calibration and troubleshooting.
MQTT state payload
{"bx": -1234, "by": 567, "bz": -4567}
magnetometer
async
¶
magnetometer(
magnetometer: MagnetometerPort,
) -> dict[str, object]
Read and return raw magnetic field values.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
magnetometer
|
MagnetometerPort
|
Sensor adapter (injected by cosalette DI). |
required |
Returns:
| Type | Description |
|---|---|
dict[str, object]
|
|
dict[str, object]
|
on every poll cycle. |