From e9eb38740d942a05d07a2bc1f9bcca1c48cd881e Mon Sep 17 00:00:00 2001 From: Marc Billow Date: Mon, 27 Jul 2026 14:43:02 +0000 Subject: [PATCH] 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. --- README.md | 3 ++- .../registry/capabilities/common.py | 18 ++++++++++++++++++ .../registry/capabilities/vacuum_station.py | 16 +--------------- .../registry/capabilities/water_purifier.py | 16 +--------------- 4 files changed, 22 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 0c9c89e..e9881b3 100644 --- a/README.md +++ b/README.md @@ -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` | | Dehumidifier | `by_type/dehumidifier.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` | | Range hood | `by_type/range_hood.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` | | Washer | `by_type/washer.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. diff --git a/custom_components/localthings/registry/capabilities/common.py b/custom_components/localthings/registry/capabilities/common.py index 9d519ff..cd03a91 100644 --- a/custom_components/localthings/registry/capabilities/common.py +++ b/custom_components/localthings/registry/capabilities/common.py @@ -10,6 +10,8 @@ against live device dumps: /water/consumption/vs/0 -> x.com.samsung.da.cumulativeWater /filter/waterfilter/vs/0 -> x.com.samsung.da.filterUsage / filterStatus """ +from datetime import datetime, timezone + from ..capability import Capability from ..entities import ( 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 +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): """Filter usage as a percentage of rated capacity. Several families (AC, air purifier) report `filterUsage` as a raw count in diff --git a/custom_components/localthings/registry/capabilities/vacuum_station.py b/custom_components/localthings/registry/capabilities/vacuum_station.py index d4b5287..090ed88 100644 --- a/custom_components/localthings/registry/capabilities/vacuum_station.py +++ b/custom_components/localthings/registry/capabilities/vacuum_station.py @@ -15,23 +15,9 @@ docstring for that rule). Resources verified against the issue #131 diagnostics dump. """ -from datetime import datetime, timezone - from ..capability import Capability from ..entities import BinarySensorDesc, SelectDesc, SensorDesc, SwitchDesc -from .common import int_or_none - - -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 +from .common import int_or_none, parse_iso_utc as _parse_iso_utc DUSTBAG = Capability( diff --git a/custom_components/localthings/registry/capabilities/water_purifier.py b/custom_components/localthings/registry/capabilities/water_purifier.py index ef287cb..953cd3d 100644 --- a/custom_components/localthings/registry/capabilities/water_purifier.py +++ b/custom_components/localthings/registry/capabilities/water_purifier.py @@ -3,23 +3,9 @@ issue #90, model TP2X_WATERPURIFIER_20K). Resources verified against the issue #90 diagnostics dump. """ -from datetime import datetime, timezone - from ..capability import Capability from ..entities import BinarySensorDesc, NumberDesc, SelectDesc, SensorDesc, SwitchDesc -from .common import int_or_none - - -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 +from .common import int_or_none, parse_iso_utc as _parse_iso_utc DISPENSE = Capability(