Adding a unit to a sensor that recorded long-term statistics without one is not cosmetic: Home Assistant raises a units_changed repair and then *suppresses statistics generation* for that entity until a human resolves it (sensor/recorder.py's compile path hits `continue`). Shipping the PM device classes on their own would therefore have silently frozen the very history the labels were meant to describe. A v2 -> v3 config-entry migration relabels the statistics metadata first. It rewrites only the metadata row, never the recorded values -- these readings were always µg/m³ and only the label was missing, so nothing needs converting, which is why this uses async_update_statistics_metadata and not change_statistics_unit. It reads entity_ids back off the entity registry rather than rebuilding them from descriptor keys, since a renamed entity's statistic_id no longer follows from its key, and it is scoped by device family: range_hood and airconditioner still declare no unit for their identically-named sensors, so relabelling theirs would create the exact mismatch this exists to prevent. With the migration in place there is no longer a reason to hold the labels back on air_monitor, so it takes them too. That board is one of the two whose fixtures pin the grade bands the mapping rests on -- typing the purifier and not the monitor was an inconsistency, not caution. It still declines the shared state_class column, unchanged. Recorder coupling is guarded: after_dependencies pulls it in when configured, the import is local to the migration, and a setup without it is a no-op. Freshly created entries mint v3 directly, having no history to relabel. Tested at both levels. The unit-level tests patch the recorder and assert which entities are relabelled with which arguments, covering the renamed entity, subdevice-prefixed and instanced keys, the near-miss keys (dustbag_/dustbin_), the skipped families and the recorder-absent path. Because a mock can only prove the call is made, not that it does what the migration needs, a second suite drives a real in-memory recorder end to end: statistics seeded unitless, migration run, metadata confirmed to read µg/m³ / concentration, recorded means confirmed byte-identical, and the units_changed issue confirmed present before and absent after.
18 lines
448 B
JSON
18 lines
448 B
JSON
{
|
|
"domain": "localthings",
|
|
"name": "LocalThings",
|
|
"after_dependencies": ["recorder"],
|
|
"codeowners": ["@mbillow"],
|
|
"config_flow": true,
|
|
"dependencies": [],
|
|
"documentation": "https://github.com/mbillow/localthings",
|
|
"iot_class": "local_push",
|
|
"issue_tracker": "https://github.com/mbillow/localthings/issues",
|
|
"requirements": [
|
|
"cbor2>=5.4.6",
|
|
"pyOpenSSL>=23.0",
|
|
"smartthings-local>=0.1.6"
|
|
],
|
|
"version": "0.21.2"
|
|
}
|