Deduplicate ISO-timestamp parsing and document the new device type (review follow-up, issue #131)

- vacuum_station._parse_iso_utc was a verbatim copy of
  water_purifier._parse_iso_utc; promoted to common.parse_iso_utc and
  pointed both families at it. Also made it tzinfo-aware rather than
  unconditionally overwriting with UTC -- harmless today since every
  dump seen is a bare or Z-suffixed UTC timestamp, but a board that
  ever emits a real offset would otherwise have it silently clobbered.
- Added the new vacuum_station type to the README's supported-appliance
  table, and noted that combi microwaves route through the oven
  registry.
This commit is contained in:
Marc Billow
2026-07-27 14:43:02 +00:00
parent f37c9ec4ef
commit e9eb38740d
4 changed files with 22 additions and 31 deletions
+2 -1
View File
@@ -25,7 +25,7 @@ Your state stays on your LAN: HA talks to the appliance over a direct DTLS sessi
| Air purifier | `by_type/air_purifier.py` | | Air purifier | `by_type/air_purifier.py` |
| Dehumidifier | `by_type/dehumidifier.py` | | Dehumidifier | `by_type/dehumidifier.py` |
| Dryer | `by_type/dryer.py` | | Dryer | `by_type/dryer.py` |
| Oven | `by_type/oven.py` | | Oven (including combi microwaves) | `by_type/oven.py` |
| Gas cooktop (read-only burner status) | `by_type/cooktop.py` | | Gas cooktop (read-only burner status) | `by_type/cooktop.py` |
| Range hood | `by_type/range_hood.py` | | Range hood | `by_type/range_hood.py` |
| Range | `by_type/range.py` | | Range | `by_type/range.py` |
@@ -33,6 +33,7 @@ Your state stays on your LAN: HA talks to the appliance over a direct DTLS sessi
| Refrigerator | `by_type/refrigerator.py` | | Refrigerator | `by_type/refrigerator.py` |
| Washer | `by_type/washer.py` | | Washer | `by_type/washer.py` |
| Water purifier | `by_type/water_purifier.py` | | Water purifier | `by_type/water_purifier.py` |
| Vacuum clean/auto-empty station | `by_type/vacuum_station.py` |
Each registry composes shared and family-specific `Capability` objects from `registry/capabilities/`; those modules document the individual resources/entities in more depth than a README table can stay current with. Each registry composes shared and family-specific `Capability` objects from `registry/capabilities/`; those modules document the individual resources/entities in more depth than a README table can stay current with.
@@ -10,6 +10,8 @@ against live device dumps:
/water/consumption/vs/0 -> x.com.samsung.da.cumulativeWater /water/consumption/vs/0 -> x.com.samsung.da.cumulativeWater
/filter/waterfilter/vs/0 -> x.com.samsung.da.filterUsage / filterStatus /filter/waterfilter/vs/0 -> x.com.samsung.da.filterUsage / filterStatus
""" """
from datetime import datetime, timezone
from ..capability import Capability from ..capability import Capability
from ..entities import ( from ..entities import (
BinarySensorDesc, ButtonDesc, SelectDesc, SensorDesc, SwitchDesc, BinarySensorDesc, ButtonDesc, SelectDesc, SensorDesc, SwitchDesc,
@@ -40,6 +42,22 @@ def wh_to_kwh(v):
return round(n / 1000.0, 2) if n is not None else None return round(n / 1000.0, 2) if n is not None else None
def parse_iso_utc(raw):
"""ISO datetime defaulting to UTC when the string carries no timezone
of its own (this integration's convention for other bare ISO datetime
fields -- see washer.py's drum-clean-log comment). A few boards do
ship a 'Z'/offset suffix (fromisoformat parses that natively since
Python 3.11) -- only fill in UTC when parsing left the result naive,
rather than unconditionally overwriting whatever offset was parsed."""
if not raw:
return None
try:
dt = datetime.fromisoformat(raw)
except ValueError:
return None
return dt if dt.tzinfo is not None else dt.replace(tzinfo=timezone.utc)
def filter_usage_percent(rep): def filter_usage_percent(rep):
"""Filter usage as a percentage of rated capacity. Several families """Filter usage as a percentage of rated capacity. Several families
(AC, air purifier) report `filterUsage` as a raw count in (AC, air purifier) report `filterUsage` as a raw count in
@@ -15,23 +15,9 @@ docstring for that rule).
Resources verified against the issue #131 diagnostics dump. Resources verified against the issue #131 diagnostics dump.
""" """
from datetime import datetime, timezone
from ..capability import Capability from ..capability import Capability
from ..entities import BinarySensorDesc, SelectDesc, SensorDesc, SwitchDesc from ..entities import BinarySensorDesc, SelectDesc, SensorDesc, SwitchDesc
from .common import int_or_none from .common import int_or_none, parse_iso_utc as _parse_iso_utc
def _parse_iso_utc(raw):
"""Bare ISO datetime with no timezone field alongside it -- treated as
UTC, matching this integration's convention for other bare ISO datetime
fields (see water_purifier.py's own _parse_iso_utc)."""
if not raw:
return None
try:
return datetime.fromisoformat(raw).replace(tzinfo=timezone.utc)
except ValueError:
return None
DUSTBAG = Capability( DUSTBAG = Capability(
@@ -3,23 +3,9 @@ issue #90, model TP2X_WATERPURIFIER_20K).
Resources verified against the issue #90 diagnostics dump. Resources verified against the issue #90 diagnostics dump.
""" """
from datetime import datetime, timezone
from ..capability import Capability from ..capability import Capability
from ..entities import BinarySensorDesc, NumberDesc, SelectDesc, SensorDesc, SwitchDesc from ..entities import BinarySensorDesc, NumberDesc, SelectDesc, SensorDesc, SwitchDesc
from .common import int_or_none from .common import int_or_none, parse_iso_utc as _parse_iso_utc
def _parse_iso_utc(raw):
"""Bare ISO datetime with no timezone field alongside it -- treated as
UTC, matching this integration's convention for other bare ISO datetime
fields (see washer.py's drum-clean-log comment)."""
if not raw:
return None
try:
return datetime.fromisoformat(raw).replace(tzinfo=timezone.utc)
except ValueError:
return None
DISPENSE = Capability( DISPENSE = Capability(