Merge remote-tracking branch 'origin/main' into claude/device-support-issue-77-freezer
# Conflicts: # custom_components/localthings/registry/by_type/__init__.py # tests/test_by_type.py
This commit is contained in:
@@ -23,14 +23,16 @@ Your state stays on your LAN: HA talks to the appliance over a direct DTLS sessi
|
||||
|---|---|
|
||||
| Air conditioner | `by_type/airconditioner.py` |
|
||||
| Air purifier | `by_type/air_purifier.py` |
|
||||
| Dehumidifier | `by_type/dehumidifier.py` |
|
||||
| Dryer | `by_type/dryer.py` |
|
||||
| Oven | `by_type/oven.py` |
|
||||
| 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 | `by_type/range.py` |
|
||||
| Dishwasher | `by_type/dishwasher.py` |
|
||||
| Refrigerator | `by_type/refrigerator.py` |
|
||||
| Washer | `by_type/washer.py` |
|
||||
| Water purifier | `by_type/water_purifier.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.
|
||||
|
||||
|
||||
@@ -197,6 +197,18 @@ def _find_live_ports(host: str, ports: list[int], timeout: float) -> list[int]:
|
||||
return _order_candidates(live)
|
||||
|
||||
|
||||
def _is_placeholder_serial(serial: str) -> bool:
|
||||
"""True for a non-empty serialNum that isn't actually a real identity.
|
||||
|
||||
The ARTIK051_DONGLE_REF firmware family reports the literal string
|
||||
'Nothing(SVC)' for every unit -- non-empty, so the plain `if not
|
||||
serial` check here (and the equivalent one in coordinator.py's
|
||||
`_run_discovery`) doesn't catch it, and two such units get the same
|
||||
config-entry unique_id / entity unique_ids and collide (issue #83).
|
||||
"""
|
||||
return serial.strip().lower().startswith('nothing')
|
||||
|
||||
|
||||
def _probe_and_validate(host: str, ca_cert_pem: str, ca_key_pem: str) -> dict:
|
||||
"""Fetch UUID, mint leaf cert, probe each port. Returns config entry data dict."""
|
||||
import cbor2
|
||||
@@ -261,7 +273,7 @@ def _probe_and_validate(host: str, ca_cert_pem: str, ca_key_pem: str) -> dict:
|
||||
.get('/information/vs/0', {})
|
||||
.get('x.com.samsung.da.serialNum', '')
|
||||
)
|
||||
if not serial:
|
||||
if not serial or _is_placeholder_serial(serial):
|
||||
serial = f"{host}:{port}"
|
||||
one_ui_version = (
|
||||
resources
|
||||
|
||||
@@ -53,6 +53,22 @@ class _NoOpDescriptor:
|
||||
_RECOVERY_RETRY_S = 600.0 # re-attempt observe mode this often while polling
|
||||
|
||||
|
||||
def _is_placeholder_serial(serial: str) -> bool:
|
||||
"""True for a non-empty serialNum that isn't actually a real identity.
|
||||
|
||||
The ARTIK051_DONGLE_REF firmware family reports the literal string
|
||||
'Nothing(SVC)' for every unit -- non-empty, so the plain `if not
|
||||
serial` check below doesn't catch it, and `device_serial` feeds both
|
||||
the HA device-registry identifier and every entity's unique_id
|
||||
(entity.py), so two such units on the same install silently collide
|
||||
and the second one's entities get dropped (issue #83). Mirrors the
|
||||
identical helper in config_flow.py's `_probe_and_validate` -- kept
|
||||
separate rather than imported to avoid pulling the config-flow module
|
||||
into the runtime coordinator's import graph for a two-line check.
|
||||
"""
|
||||
return serial.strip().lower().startswith('nothing')
|
||||
|
||||
|
||||
class LocalThingsCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
"""Manages one Samsung appliance: session, discovery, polling."""
|
||||
|
||||
@@ -316,7 +332,7 @@ class LocalThingsCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
self._unbound_hrefs = unbound
|
||||
|
||||
serial = info.get('x.com.samsung.da.serialNum', '')
|
||||
if not serial:
|
||||
if not serial or _is_placeholder_serial(serial):
|
||||
serial = self._entry.data[CONF_HOST]
|
||||
self.device_serial = serial
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ from typing import Optional
|
||||
|
||||
from ._base import DeviceRegistry
|
||||
from . import (
|
||||
air_purifier, airconditioner, cooktop, dishwasher, dryer, oven,
|
||||
range as _range, range_hood, refrigerator, washer,
|
||||
air_purifier, airconditioner, cooktop, dehumidifier, dishwasher, dryer,
|
||||
oven, range as _range, range_hood, refrigerator, washer, water_purifier,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
@@ -19,6 +19,7 @@ _REGISTRY_BY_KEY: dict[str, DeviceRegistry] = {
|
||||
'airconditioner': airconditioner.REGISTRY,
|
||||
'air_conditioner': airconditioner.REGISTRY,
|
||||
'cooktop': cooktop.REGISTRY,
|
||||
'dehumidifier': dehumidifier.REGISTRY,
|
||||
'dishwasher': dishwasher.REGISTRY,
|
||||
'dryer': dryer.REGISTRY,
|
||||
'oven': oven.REGISTRY,
|
||||
@@ -27,6 +28,7 @@ _REGISTRY_BY_KEY: dict[str, DeviceRegistry] = {
|
||||
'range_hood': range_hood.REGISTRY,
|
||||
'refrigerator': refrigerator.REGISTRY,
|
||||
'washer': washer.REGISTRY,
|
||||
'water_purifier': water_purifier.REGISTRY,
|
||||
}
|
||||
|
||||
|
||||
@@ -103,6 +105,26 @@ def _model_num_segments(model_num: str) -> list[str]:
|
||||
return prefix.split('_')
|
||||
|
||||
|
||||
def _consumer_model_key(description: str) -> Optional[str]:
|
||||
"""Registry key from the consumer-model token in `description`, or None.
|
||||
|
||||
Usually that token is the last '_'-delimited segment before any
|
||||
'/board-info' suffix (e.g. '..._WW90DG6U25LEU4' -> 'WW90DG6U25LEU4').
|
||||
But issue #79's dryer pairs two model numbers in one description --
|
||||
'..._DVE50A8800_8600/DC92-...' -- so the true consumer token
|
||||
('DVE50A8800') sits one segment *before* the actual last segment
|
||||
('8600', a bare second model number with no recognizable prefix). Scan
|
||||
segments from the end and take the first one that resolves, rather
|
||||
than assuming the last segment is always it.
|
||||
"""
|
||||
segments = (description or '').split('/', 1)[0].split('_')
|
||||
for segment in reversed(segments):
|
||||
key = _CONSUMER_PREFIX_TO_KEY.get(segment[:2].upper())
|
||||
if key is not None:
|
||||
return key
|
||||
return None
|
||||
|
||||
|
||||
def for_device_by_model(model_num: str, description: str) -> Optional[DeviceRegistry]:
|
||||
"""Fallback device-type detection for hardware that never reports
|
||||
oneUiVersion (confirmed for washers -- their /otninformation/vs/0 has
|
||||
@@ -116,8 +138,7 @@ def for_device_by_model(model_num: str, description: str) -> Optional[DeviceRegi
|
||||
DeviceRegistry if the consumer-model code or modelNum resolves to a
|
||||
known type, None otherwise.
|
||||
"""
|
||||
token = (description or '').split('/', 1)[0].rsplit('_', 1)[-1]
|
||||
key = _CONSUMER_PREFIX_TO_KEY.get(token[:2].upper())
|
||||
key = _consumer_model_key(description)
|
||||
if key is None and 'REF' in _model_num_segments(model_num):
|
||||
key = 'refrigerator'
|
||||
# Room air conditioners (e.g. ARTIK051_PRAC_20K) report no oneUiVersion and
|
||||
@@ -138,6 +159,22 @@ def for_device_by_model(model_num: str, description: str) -> Optional[DeviceRegi
|
||||
# resource (see airconditioner.py's _AC_IGNORED).
|
||||
if key is None and '-CAWW-' in (model_num or '').upper():
|
||||
key = 'airconditioner'
|
||||
# Dehumidifiers (e.g. TP1X_DA_AC_DHM_01001_0000, issue #88) share the
|
||||
# DA_AC_ board family with the room-AC models above but carry the
|
||||
# '_DHM_' (DeHuMidifier) token instead of '_RAC_'/'_PRAC_'. Distinct
|
||||
# registry: target humidity, not temperature, is the primary control,
|
||||
# and there's no climate composite.
|
||||
if key is None and '_DHM_' in (model_num or ''):
|
||||
key = 'dehumidifier'
|
||||
# Window air conditioners (e.g. TP1X_DA_AC_WAC_01001_0000, issue #87)
|
||||
# report no oneUiVersion and carry the '_WAC_' (Window Air Conditioner)
|
||||
# token instead of '_RAC_'/'_PRAC_'. Same TP1X-class resource surface
|
||||
# as the room-AC models above (mode/convenient/wind/temperature/power/
|
||||
# filter/humidity all confirmed against the issue #87 dump binding
|
||||
# cleanly against the existing airconditioner registry once routed
|
||||
# here) -- no WAC-specific resources needed.
|
||||
if key is None and '_WAC_' in (model_num or ''):
|
||||
key = 'airconditioner'
|
||||
# Air purifiers (e.g. ARTIK051_TVTL_18K, issue #56) report no
|
||||
# oneUiVersion either, and carry the '_TVTL_' board-family token.
|
||||
if key is None and '_TVTL_' in (model_num or ''):
|
||||
@@ -145,6 +182,10 @@ def for_device_by_model(model_num: str, description: str) -> Optional[DeviceRegi
|
||||
model_identity = f'{model_num} {description}'.upper()
|
||||
if key is None and ('_COOKTOP' in model_identity or '_GB_CT_' in model_identity):
|
||||
key = 'cooktop'
|
||||
# Water purifiers (e.g. TP2X_WATERPURIFIER_20K, issue #90) report no
|
||||
# oneUiVersion and carry the 'WATERPURIFIER' board-family token.
|
||||
if key is None and 'WATERPURIFIER' in model_identity:
|
||||
key = 'water_purifier'
|
||||
if key is None and model_identity.startswith('AHD-'):
|
||||
key = 'range_hood'
|
||||
# Range/cooktop-oven combos (e.g. TP1X_DA-KS-RANGE-0102X, issue #44) --
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
"""Cooktop device registry."""
|
||||
"""Gas cooktop device registry (NA9300K-class, PR #23).
|
||||
|
||||
Named 'gas_cooktop' (not 'cooktop') so its diagnostics/device-info label
|
||||
doesn't collide with the unrelated induction_cooktop family (issue #86,
|
||||
by_type/induction_cooktop.py) -- two different OCF surfaces that happen to
|
||||
share the English word "cooktop". The `_REGISTRY_BY_KEY['cooktop']` lookup
|
||||
key is unchanged: it's relied on by real devices reporting
|
||||
oneUiVersion "Cooktop" (for_device), the legacy ARTIK051 modelNum rule
|
||||
(for_device_by_model), and the resource-signature fallback
|
||||
(for_device_by_resources) alike.
|
||||
"""
|
||||
|
||||
from ..capabilities import common, cooktop, ignored
|
||||
from ._base import DeviceRegistry, _build
|
||||
|
||||
|
||||
REGISTRY = DeviceRegistry(
|
||||
name='cooktop',
|
||||
name='gas_cooktop',
|
||||
capabilities=_build([
|
||||
*ignored.IGNORED,
|
||||
cooktop.COOKTOP_POWER,
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
"""Dehumidifier device registry (Samsung TP1X_DA_AC_DHM-class, issue #88).
|
||||
|
||||
Shares the DA_AC_ board family with airconditioner.py (power, air filter,
|
||||
auto-clean, mute-once all use the identical resource shapes), so those three
|
||||
Capability objects are reused directly rather than duplicated.
|
||||
"""
|
||||
from ..capabilities import airconditioner, common, dehumidifier, ignored
|
||||
from ._base import DeviceRegistry, _build
|
||||
|
||||
REGISTRY = DeviceRegistry(
|
||||
name='dehumidifier',
|
||||
capabilities=_build([
|
||||
*ignored.IGNORED,
|
||||
*common.UNIVERSAL,
|
||||
*common.POWER,
|
||||
dehumidifier.MODE,
|
||||
dehumidifier.HUMIDITY,
|
||||
airconditioner.AUTO_CLEAN,
|
||||
airconditioner.AIR_FILTER,
|
||||
airconditioner.MUTE_ONCE,
|
||||
*dehumidifier.COVERAGE,
|
||||
]),
|
||||
)
|
||||
@@ -0,0 +1,18 @@
|
||||
"""Water-purifier device registry (Samsung TP2X_WATERPURIFIER-class, issue #90).
|
||||
"""
|
||||
from ..capabilities import common, ignored, water_purifier
|
||||
from ._base import DeviceRegistry, _build
|
||||
|
||||
REGISTRY = DeviceRegistry(
|
||||
name='water_purifier',
|
||||
capabilities=_build([
|
||||
*ignored.IGNORED,
|
||||
*common.UNIVERSAL,
|
||||
common.WATER_FILTER,
|
||||
water_purifier.DISPENSE,
|
||||
water_purifier.STATUS,
|
||||
water_purifier.FAVORITE_CAPACITY,
|
||||
water_purifier.LOCK,
|
||||
*water_purifier.COVERAGE,
|
||||
]),
|
||||
)
|
||||
@@ -0,0 +1,85 @@
|
||||
"""Capabilities for the Samsung dehumidifier family (TP1X_DA_AC_DHM-class,
|
||||
issue #88, model AY18CG7500GED).
|
||||
|
||||
Same DA_AC_ board family as the room-AC models in airconditioner.py (shared
|
||||
power/energy/filter/auto-clean/mute-once resource shapes), but target
|
||||
humidity -- not temperature -- is this device's primary control, and there
|
||||
is no climate composite: power, mode, and humidity are exposed as separate
|
||||
entities rather than folded into one card.
|
||||
"""
|
||||
from ..capability import Capability
|
||||
from ..entities import NumberDesc, SelectDesc, SensorDesc
|
||||
from .common import int_or_none
|
||||
|
||||
|
||||
def _first_mode(rep):
|
||||
"""Representative scalar for the operating-mode select. `modes` is a
|
||||
single-element list on every dump seen so far, mirroring
|
||||
airconditioner._first_mode's handling of the same field shape."""
|
||||
modes = rep.get('x.com.samsung.da.modes')
|
||||
if isinstance(modes, (list, tuple)):
|
||||
return modes[0] if modes else None
|
||||
return modes
|
||||
|
||||
|
||||
MODE = Capability(
|
||||
href='/mode/vs/0',
|
||||
poll_tier='warm',
|
||||
entities=(
|
||||
SelectDesc(key='operating_mode', rep_fn=_first_mode,
|
||||
icon='mdi:tune-variant',
|
||||
options_field='x.com.samsung.da.supportedModes',
|
||||
write_fn=lambda p, rep, href=None: (
|
||||
['mode', 'vs', '0'], {'x.com.samsung.da.modes': [p]})),
|
||||
),
|
||||
)
|
||||
|
||||
# Target humidity is this device's primary control (the issue-#88 dump's
|
||||
# equivalent of a thermostat setpoint). No min/max range field is present in
|
||||
# any dump seen so far -- native_min/native_max are deliberately left unset
|
||||
# so the number entity falls back to HA's own 0-100 default, the natural
|
||||
# bound for a percentage field, rather than a bound guessed from one unit's
|
||||
# spec sheet (see the adding-device-support skill's "never hard-code the one
|
||||
# dump's values" section). Step comes live from the device's own `increment`
|
||||
# field.
|
||||
HUMIDITY = Capability(
|
||||
href='/humidity/vs/0',
|
||||
poll_tier='warm',
|
||||
entities=(
|
||||
SensorDesc(key='humidity', field='x.com.samsung.da.humidity',
|
||||
device_class='humidity', unit='%', state_class='measurement',
|
||||
value_fn=int_or_none),
|
||||
NumberDesc(key='target_humidity', field='x.com.samsung.da.desiredHumidity',
|
||||
device_class='humidity', unit='%',
|
||||
icon='mdi:water-percent',
|
||||
entity_category='config',
|
||||
value_fn=int_or_none,
|
||||
step_fn=lambda rep: int_or_none(rep.get('increment')) or 1,
|
||||
write_fn=lambda p, rep, href=None: (
|
||||
['humidity', 'vs', '0'],
|
||||
{'x.com.samsung.da.desiredHumidity': str(int(round(float(p))))})),
|
||||
),
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Dehumidifier-scoped coverage: vendor plumbing with no user-actionable state
|
||||
# or no documented write contract, following the same 'don't guess' rule as
|
||||
# airconditioner._AC_IGNORED (this is the same DA_AC_ board family). Not in
|
||||
# the global ignored.IGNORED since some of these hrefs collide with other
|
||||
# families' schemas.
|
||||
# ---------------------------------------------------------------------------
|
||||
_DHM_IGNORED = [
|
||||
'/availablecontrolsets/vs/0', # opaque hex-encoded control-set bitmap (id: DHM)
|
||||
'/da/softreset/vs/0', # soft-reset trigger plumbing
|
||||
'/keepnormalstate/vs/0', # internal keep-normal flag
|
||||
'/personality/presence/vs/0', # presence-personalization plumbing (empty item value)
|
||||
'/reserverulesets/vs/0', # opaque hex-encoded schedule reservation blob
|
||||
'/sensors/vs/0', # empty {} on this dump
|
||||
'/welcome/humidity/vs/0', # welcome-mode plumbing (requestId/operatingStatus, inert)
|
||||
# Only supportedModes ([Off, Sleep]) is present -- no live "current
|
||||
# value" field on this dump to confirm the read/write contract, so per
|
||||
# the 'don't guess' rule this is left unmodeled rather than assumed.
|
||||
'/mode/convenient/vs/0',
|
||||
]
|
||||
|
||||
COVERAGE = [Capability(href=h) for h in _DHM_IGNORED]
|
||||
@@ -42,7 +42,10 @@ DRYER_SETTINGS = Capability(
|
||||
# under entity.select.dryer_cycle (Table_03, DV5000-class, captured
|
||||
# 2026-05-29). Codes 0x21 and 0x4C appear in the issue #14 DV90BB5245AES1
|
||||
# editCourseList but aren't identified yet -- they render as the raw code
|
||||
# until named. The /st/dryercourse/vs/0 resource re-encodes the same selected
|
||||
# until named. Codes '01' Normal and '06' Time dry were confirmed on a
|
||||
# DVE50A8600V/A3 (also Table_03) by selecting each cycle on the physical
|
||||
# appliance and reading back the raw code from the entity's state (issue
|
||||
# #80). The /st/dryercourse/vs/0 resource re-encodes the same selected
|
||||
# course and is ignored (ignored.py) -- the mirror of how /st/washercourse/vs/0
|
||||
# is ignored for washers.
|
||||
DRYER_COURSE = Capability(
|
||||
|
||||
@@ -45,6 +45,14 @@ from .laundry import (
|
||||
# (issue #22). Combo units carry their own course set, so these codes
|
||||
# don't imply anything about '1F' on a plain washer.
|
||||
#
|
||||
# Three more -- '52' Eco Cold, '54' Towels, '60' Self Clean+ -- came from a
|
||||
# WF50A8600AV/US, verified directly rather than by inference: the reporter
|
||||
# selected each cycle on the physical appliance and read back the resulting
|
||||
# raw code from the cycle_select entity's state (issue #80). '54' shares a
|
||||
# display name with the existing '24' Towels -- a different code on a
|
||||
# different course table legitimately landing on the same label, not a typo
|
||||
# (same pattern as '21'/'65' Colors and '27'/'5E' Rinse+Spin above).
|
||||
#
|
||||
# No static fallback list of those codes is kept here, deliberately: other
|
||||
# washer models have a different actual course set (a second dump's active
|
||||
# course, '65', isn't even in the list above; models with 'AI Wash'/'Mixed
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
"""Capabilities for the Samsung water-purifier family (TP2X_WATERPURIFIER-class,
|
||||
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
|
||||
|
||||
|
||||
DISPENSE = Capability(
|
||||
href='/setting/waterpurifier/vs/0',
|
||||
poll_tier='warm',
|
||||
entities=(
|
||||
SelectDesc(key='dispense_type', field='x.com.samsung.da.desiredType',
|
||||
icon='mdi:cup-water',
|
||||
options_field='x.com.samsung.da.supportedTypes',
|
||||
write_fn=lambda p, rep, href=None: (
|
||||
['setting', 'waterpurifier', 'vs', '0'],
|
||||
{'x.com.samsung.da.desiredType': p})),
|
||||
# Only a handful of discrete temperatures are selectable (not a
|
||||
# continuous range) -- a select over the live-reported set, not a
|
||||
# number with invented bounds.
|
||||
SelectDesc(key='hot_water_temperature', field='x.com.samsung.da.tempDesiredHotWater',
|
||||
icon='mdi:thermometer',
|
||||
entity_category='config',
|
||||
options_field='x.com.samsung.da.supportedHotTemperatures',
|
||||
write_fn=lambda p, rep, href=None: (
|
||||
['setting', 'waterpurifier', 'vs', '0'],
|
||||
{'x.com.samsung.da.tempDesiredHotWater': p})),
|
||||
# Bounds and step come live from the device's own
|
||||
# desiredCapacityRange/capacityResolution fields, not a hardcoded
|
||||
# constant -- see the adding-device-support skill's "never hard-code
|
||||
# the one dump's values" section. No unit is set: capacityUnit reads
|
||||
# "C" on this dump, which can't be right for a volume field, so per
|
||||
# the 'don't guess' rule the unit is left unset rather than assumed
|
||||
# to be mL.
|
||||
NumberDesc(key='dispense_capacity', field='x.com.samsung.da.desiredCapacity',
|
||||
icon='mdi:cup-water',
|
||||
value_fn=int_or_none,
|
||||
range_field='x.com.samsung.da.desiredCapacityRange',
|
||||
step_fn=lambda rep: int_or_none(
|
||||
rep.get('x.com.samsung.da.capacityResolution')) or 1,
|
||||
write_fn=lambda p, rep, href=None: (
|
||||
['setting', 'waterpurifier', 'vs', '0'],
|
||||
{'x.com.samsung.da.desiredCapacity': str(int(round(float(p))))})),
|
||||
BinarySensorDesc(key='pouring', field='x.com.samsung.da.pourStatus',
|
||||
icon='mdi:cup-water',
|
||||
value_fn=lambda v: v == 'On'),
|
||||
),
|
||||
)
|
||||
|
||||
STATUS = Capability(
|
||||
href='/status/waterpurifier/vs/0',
|
||||
poll_tier='warm',
|
||||
entities=(
|
||||
SensorDesc(key='waterpurifier_status', field='x.com.samsung.da.status',
|
||||
icon='mdi:water-pump',
|
||||
entity_category='diagnostic'),
|
||||
BinarySensorDesc(key='filter_door_status', field='x.com.samsung.da.filterDoorStatus',
|
||||
device_class='door',
|
||||
entity_category='diagnostic',
|
||||
value_fn=lambda v: v == 'Open'),
|
||||
SensorDesc(key='sterilize_period', field='x.com.samsung.da.sterilizePeriod',
|
||||
icon='mdi:calendar-sync',
|
||||
entity_category='diagnostic'),
|
||||
SensorDesc(key='sterilize_run_time', field='x.com.samsung.da.sterilizeRunTime',
|
||||
icon='mdi:timer-outline',
|
||||
entity_category='diagnostic'),
|
||||
SensorDesc(key='sterilize_last_time', device_class='timestamp',
|
||||
entity_category='diagnostic',
|
||||
rep_fn=lambda rep: _parse_iso_utc(
|
||||
rep.get('x.com.samsung.da.sterilizeLastTime'))),
|
||||
SensorDesc(key='sterilize_plan_time', device_class='timestamp',
|
||||
entity_category='diagnostic',
|
||||
rep_fn=lambda rep: _parse_iso_utc(
|
||||
rep.get('x.com.samsung.da.sterilizePlanTime'))),
|
||||
SensorDesc(key='filter_clean_remain_time', field='x.com.samsung.da.filterCleanRemainTime',
|
||||
icon='mdi:timer-sand',
|
||||
entity_category='diagnostic'),
|
||||
),
|
||||
)
|
||||
|
||||
FAVORITE_CAPACITY = Capability(
|
||||
href='/favorite/capacity/vs/0',
|
||||
poll_tier='cold',
|
||||
entities=(
|
||||
SwitchDesc(key='favorite_capacity_enabled', field='x.com.samsung.da.switchCapacity',
|
||||
icon='mdi:star-outline',
|
||||
entity_category='config',
|
||||
value_fn=lambda v: v == 'On',
|
||||
write_fn=lambda p, rep, href=None: (
|
||||
['favorite', 'capacity', 'vs', '0'],
|
||||
{'x.com.samsung.da.switchCapacity': 'On' if p == 'On' else 'Off'})),
|
||||
SelectDesc(key='favorite_capacity', field='x.com.samsung.da.defaultCapacity',
|
||||
icon='mdi:cup-water',
|
||||
entity_category='config',
|
||||
options_field='x.com.samsung.da.capacityList',
|
||||
write_fn=lambda p, rep, href=None: (
|
||||
['favorite', 'capacity', 'vs', '0'],
|
||||
{'x.com.samsung.da.defaultCapacity': p})),
|
||||
),
|
||||
)
|
||||
|
||||
LOCK = Capability(
|
||||
href='/status/lock/vs/0',
|
||||
poll_tier='warm',
|
||||
entities=(
|
||||
SwitchDesc(key='hotwater_lock', field='x.com.samsung.da.hotwaterLock',
|
||||
device_class='lock',
|
||||
entity_category='config',
|
||||
value_fn=lambda v: v != 'Unlocked',
|
||||
write_fn=lambda p, rep, href=None: (
|
||||
['status', 'lock', 'vs', '0'],
|
||||
{'x.com.samsung.da.hotwaterLock': 'Locked' if p == 'On' else 'Unlocked'})),
|
||||
SwitchDesc(key='coldwater_lock', field='x.com.samsung.da.coldwaterLock',
|
||||
device_class='lock',
|
||||
entity_category='config',
|
||||
value_fn=lambda v: v != 'Unlocked',
|
||||
write_fn=lambda p, rep, href=None: (
|
||||
['status', 'lock', 'vs', '0'],
|
||||
{'x.com.samsung.da.coldwaterLock': 'Locked' if p == 'On' else 'Unlocked'})),
|
||||
SwitchDesc(key='buzz_lock', field='x.com.samsung.da.buzzLock',
|
||||
device_class='lock',
|
||||
entity_category='config',
|
||||
value_fn=lambda v: v != 'Unlocked',
|
||||
write_fn=lambda p, rep, href=None: (
|
||||
['status', 'lock', 'vs', '0'],
|
||||
{'x.com.samsung.da.buzzLock': 'Locked' if p == 'On' else 'Unlocked'})),
|
||||
),
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Water-purifier-scoped coverage: hrefs with no user-actionable state or no
|
||||
# confirmed contract, following the 'don't guess' rule.
|
||||
# ---------------------------------------------------------------------------
|
||||
_WP_IGNORED = [
|
||||
# supportedModes carries a single opaque wizard-workflow token
|
||||
# ('HOMECARE_WIZARD_V2') and modes reports a completely different,
|
||||
# unrelated value ('WATERFILTER_DISABLE') not even present in
|
||||
# supportedModes -- internal plumbing, not a real user-facing mode
|
||||
# select. OCF-standard /mode/0 mirrors the same vendor resource but is
|
||||
# already covered by the global ignored.IGNORED (fridge's OCF-native
|
||||
# vacation-mode flag shares that href).
|
||||
'/mode/vs/0',
|
||||
# Static support-flags blob (automation.supported.modes/options) -- no
|
||||
# live "current automation setting" field to expose.
|
||||
'/automation/waterpurifier/vs/0',
|
||||
]
|
||||
|
||||
COVERAGE = [Capability(href=h) for h in _WP_IGNORED]
|
||||
@@ -34,6 +34,9 @@
|
||||
"door_open": {
|
||||
"name": "Door"
|
||||
},
|
||||
"filter_door_status": {
|
||||
"name": "Filter door"
|
||||
},
|
||||
"firmware_update": {
|
||||
"name": "Firmware update available"
|
||||
},
|
||||
@@ -52,6 +55,9 @@
|
||||
"periodic_air_sensing": {
|
||||
"name": "Periodic air sensing"
|
||||
},
|
||||
"pouring": {
|
||||
"name": "Pouring"
|
||||
},
|
||||
"power_state": {
|
||||
"name": "Power state"
|
||||
},
|
||||
@@ -114,6 +120,9 @@
|
||||
"delay_start_hours": {
|
||||
"name": "Delay start"
|
||||
},
|
||||
"dispense_capacity": {
|
||||
"name": "Dispense capacity"
|
||||
},
|
||||
"instance_setpoint": {
|
||||
"name": "{instance_name} setpoint"
|
||||
},
|
||||
@@ -122,6 +131,9 @@
|
||||
},
|
||||
"sound_volume": {
|
||||
"name": "Sound volume"
|
||||
},
|
||||
"target_humidity": {
|
||||
"name": "Target humidity"
|
||||
}
|
||||
},
|
||||
"select": {
|
||||
@@ -193,6 +205,9 @@
|
||||
"8f": "Baby Care"
|
||||
}
|
||||
},
|
||||
"dispense_type": {
|
||||
"name": "Dispense type"
|
||||
},
|
||||
"door_alert": {
|
||||
"name": "Door alarm",
|
||||
"state": {
|
||||
@@ -205,6 +220,8 @@
|
||||
"dryer_cycle_table_03": {
|
||||
"name": "Cycle",
|
||||
"state": {
|
||||
"01": "Normal",
|
||||
"06": "Time dry",
|
||||
"16": "Cotton",
|
||||
"18": "Synthetics",
|
||||
"19": "Delicates",
|
||||
@@ -221,6 +238,9 @@
|
||||
"1f": "Mixed load"
|
||||
}
|
||||
},
|
||||
"favorite_capacity": {
|
||||
"name": "Favorite capacity"
|
||||
},
|
||||
"finish_sound": {
|
||||
"name": "Finish sound",
|
||||
"state": {
|
||||
@@ -252,6 +272,9 @@
|
||||
"extra_high": "Extra high"
|
||||
}
|
||||
},
|
||||
"hot_water_temperature": {
|
||||
"name": "Hot water temperature"
|
||||
},
|
||||
"ice_type": {
|
||||
"name": "{instance_name} type",
|
||||
"state": {
|
||||
@@ -290,6 +313,9 @@
|
||||
"air_fry": "Air fry"
|
||||
}
|
||||
},
|
||||
"operating_mode": {
|
||||
"name": "Operating mode"
|
||||
},
|
||||
"pantry_zone_mode": {
|
||||
"name": "Pantry zone mode",
|
||||
"state": {
|
||||
@@ -397,10 +423,13 @@
|
||||
"38": "Cotton Dry",
|
||||
"39": "Synthetics Dry",
|
||||
"3a": "Drum Clean",
|
||||
"52": "Eco Cold",
|
||||
"53": "Heavy Duty",
|
||||
"54": "Towels",
|
||||
"55": "Activewear",
|
||||
"57": "Delicate",
|
||||
"5e": "Rinse+Spin",
|
||||
"60": "Self Clean+",
|
||||
"65": "Colors",
|
||||
"66": "Denim",
|
||||
"7c": "Whites",
|
||||
@@ -513,6 +542,9 @@
|
||||
"fan_speed_level": {
|
||||
"name": "Fan speed level"
|
||||
},
|
||||
"filter_clean_remain_time": {
|
||||
"name": "Filter clean remaining time"
|
||||
},
|
||||
"filter_progress": {
|
||||
"name": "Filter progress"
|
||||
},
|
||||
@@ -537,6 +569,9 @@
|
||||
"hood_filter_capacity": {
|
||||
"name": "Filter capacity"
|
||||
},
|
||||
"humidity": {
|
||||
"name": "Humidity"
|
||||
},
|
||||
"hood_filter_usage": {
|
||||
"name": "Filter usage"
|
||||
},
|
||||
@@ -606,6 +641,18 @@
|
||||
"selfcheck_status": {
|
||||
"name": "Self-check status"
|
||||
},
|
||||
"sterilize_last_time": {
|
||||
"name": "Sterilize last time"
|
||||
},
|
||||
"sterilize_period": {
|
||||
"name": "Sterilize period"
|
||||
},
|
||||
"sterilize_plan_time": {
|
||||
"name": "Sterilize plan time"
|
||||
},
|
||||
"sterilize_run_time": {
|
||||
"name": "Sterilize run time"
|
||||
},
|
||||
"super_fine_dust": {
|
||||
"name": "Super fine dust"
|
||||
},
|
||||
@@ -615,6 +662,9 @@
|
||||
"water_liters": {
|
||||
"name": "Water consumption"
|
||||
},
|
||||
"waterpurifier_status": {
|
||||
"name": "Status"
|
||||
},
|
||||
"machine_state": {
|
||||
"name": "Machine state",
|
||||
"state": {
|
||||
@@ -661,6 +711,9 @@
|
||||
"bubble_soak": {
|
||||
"name": "Bubble soak"
|
||||
},
|
||||
"buzz_lock": {
|
||||
"name": "Buzzer lock"
|
||||
},
|
||||
"cabinet_light_dim": {
|
||||
"name": "Brighten gradually"
|
||||
},
|
||||
@@ -670,6 +723,9 @@
|
||||
"child_lock": {
|
||||
"name": "Child lock"
|
||||
},
|
||||
"coldwater_lock": {
|
||||
"name": "Cold water lock"
|
||||
},
|
||||
"defrost_delay": {
|
||||
"name": "Defrost delay"
|
||||
},
|
||||
@@ -679,9 +735,15 @@
|
||||
"fast_preheat": {
|
||||
"name": "Fast preheat"
|
||||
},
|
||||
"favorite_capacity_enabled": {
|
||||
"name": "Favorite capacity"
|
||||
},
|
||||
"fridge_sound": {
|
||||
"name": "Sound"
|
||||
},
|
||||
"hotwater_lock": {
|
||||
"name": "Hot water lock"
|
||||
},
|
||||
"ice_maker_enabled": {
|
||||
"name": "Ice maker"
|
||||
},
|
||||
|
||||
@@ -34,6 +34,9 @@
|
||||
"door_open": {
|
||||
"name": "Deur"
|
||||
},
|
||||
"filter_door_status": {
|
||||
"name": "Filterdeur"
|
||||
},
|
||||
"firmware_update": {
|
||||
"name": "Firmware-update beschikbaar"
|
||||
},
|
||||
@@ -52,6 +55,9 @@
|
||||
"periodic_air_sensing": {
|
||||
"name": "Periodieke luchtmeting"
|
||||
},
|
||||
"pouring": {
|
||||
"name": "Schenken"
|
||||
},
|
||||
"power_state": {
|
||||
"name": "Voedingsstatus"
|
||||
},
|
||||
@@ -114,6 +120,9 @@
|
||||
"delay_start_hours": {
|
||||
"name": "Uitgestelde start"
|
||||
},
|
||||
"dispense_capacity": {
|
||||
"name": "Schenkhoeveelheid"
|
||||
},
|
||||
"instance_setpoint": {
|
||||
"name": "Instelpunt {instance_name}"
|
||||
},
|
||||
@@ -122,6 +131,9 @@
|
||||
},
|
||||
"sound_volume": {
|
||||
"name": "Geluidsvolume"
|
||||
},
|
||||
"target_humidity": {
|
||||
"name": "Doelvochtigheid"
|
||||
}
|
||||
},
|
||||
"select": {
|
||||
@@ -193,6 +205,9 @@
|
||||
"8f": "Babyverzorging"
|
||||
}
|
||||
},
|
||||
"dispense_type": {
|
||||
"name": "Type dispenser"
|
||||
},
|
||||
"door_alert": {
|
||||
"name": "Deuralarm",
|
||||
"state": {
|
||||
@@ -205,6 +220,8 @@
|
||||
"dryer_cycle_table_03": {
|
||||
"name": "Programma",
|
||||
"state": {
|
||||
"01": "Normaal",
|
||||
"06": "Tijdprogramma",
|
||||
"16": "Katoen",
|
||||
"18": "Synthetisch",
|
||||
"19": "Fijne was",
|
||||
@@ -221,6 +238,9 @@
|
||||
"1f": "Gemengde was"
|
||||
}
|
||||
},
|
||||
"favorite_capacity": {
|
||||
"name": "Favoriete capaciteit"
|
||||
},
|
||||
"finish_sound": {
|
||||
"name": "Eindgeluid",
|
||||
"state": {
|
||||
@@ -252,6 +272,9 @@
|
||||
"extra_high": "Extra hoog"
|
||||
}
|
||||
},
|
||||
"hot_water_temperature": {
|
||||
"name": "Temperatuur warm water"
|
||||
},
|
||||
"ice_type": {
|
||||
"name": "Type {instance_name}",
|
||||
"state": {
|
||||
@@ -290,6 +313,9 @@
|
||||
"air_fry": "Airfryen"
|
||||
}
|
||||
},
|
||||
"operating_mode": {
|
||||
"name": "Bedrijfsmodus"
|
||||
},
|
||||
"pantry_zone_mode": {
|
||||
"name": "Modus voorraadzone",
|
||||
"state": {
|
||||
@@ -397,10 +423,13 @@
|
||||
"38": "Katoen drogen",
|
||||
"39": "Synthetisch drogen",
|
||||
"3a": "Trommel reinigen",
|
||||
"52": "Eco koud",
|
||||
"53": "Intensief",
|
||||
"54": "Handdoeken",
|
||||
"55": "Sportkleding",
|
||||
"57": "Fijn",
|
||||
"5e": "Spoelen+centrifugeren",
|
||||
"60": "Zelfreinigend+",
|
||||
"65": "Bonte was",
|
||||
"66": "Spijkergoed",
|
||||
"7c": "Witte was",
|
||||
@@ -513,6 +542,9 @@
|
||||
"fan_speed_level": {
|
||||
"name": "Ventilatorniveau"
|
||||
},
|
||||
"filter_clean_remain_time": {
|
||||
"name": "Resterende tijd filterreiniging"
|
||||
},
|
||||
"filter_progress": {
|
||||
"name": "Filterverbruik"
|
||||
},
|
||||
@@ -537,6 +569,9 @@
|
||||
"hood_filter_capacity": {
|
||||
"name": "Filtercapaciteit"
|
||||
},
|
||||
"humidity": {
|
||||
"name": "Luchtvochtigheid"
|
||||
},
|
||||
"hood_filter_usage": {
|
||||
"name": "Filterverbruik"
|
||||
},
|
||||
@@ -606,6 +641,18 @@
|
||||
"selfcheck_status": {
|
||||
"name": "Status zelfcontrole"
|
||||
},
|
||||
"sterilize_last_time": {
|
||||
"name": "Laatste sterilisatie"
|
||||
},
|
||||
"sterilize_period": {
|
||||
"name": "Sterilisatie-interval"
|
||||
},
|
||||
"sterilize_plan_time": {
|
||||
"name": "Geplande sterilisatie"
|
||||
},
|
||||
"sterilize_run_time": {
|
||||
"name": "Sterilisatieduur"
|
||||
},
|
||||
"super_fine_dust": {
|
||||
"name": "Ultrafijnstof"
|
||||
},
|
||||
@@ -615,6 +662,9 @@
|
||||
"water_liters": {
|
||||
"name": "Waterverbruik"
|
||||
},
|
||||
"waterpurifier_status": {
|
||||
"name": "Status"
|
||||
},
|
||||
"machine_state": {
|
||||
"name": "Machinestatus",
|
||||
"state": {
|
||||
@@ -661,6 +711,9 @@
|
||||
"bubble_soak": {
|
||||
"name": "Bubble Soak"
|
||||
},
|
||||
"buzz_lock": {
|
||||
"name": "Zoemerslot"
|
||||
},
|
||||
"cabinet_light_dim": {
|
||||
"name": "Geleidelijk feller"
|
||||
},
|
||||
@@ -670,6 +723,9 @@
|
||||
"child_lock": {
|
||||
"name": "Kinderslot"
|
||||
},
|
||||
"coldwater_lock": {
|
||||
"name": "Koudwaterslot"
|
||||
},
|
||||
"defrost_delay": {
|
||||
"name": "Ontdooien uitstellen"
|
||||
},
|
||||
@@ -679,9 +735,15 @@
|
||||
"fast_preheat": {
|
||||
"name": "Snel voorverwarmen"
|
||||
},
|
||||
"favorite_capacity_enabled": {
|
||||
"name": "Favoriete capaciteit"
|
||||
},
|
||||
"fridge_sound": {
|
||||
"name": "Geluid"
|
||||
},
|
||||
"hotwater_lock": {
|
||||
"name": "Heetwaterslot"
|
||||
},
|
||||
"ice_maker_enabled": {
|
||||
"name": "IJsmaker"
|
||||
},
|
||||
|
||||
+523
@@ -0,0 +1,523 @@
|
||||
{
|
||||
"device0": [
|
||||
{
|
||||
"rt": [
|
||||
"x.com.samsung.devcol",
|
||||
"oic.wk.col"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.ll",
|
||||
"oic.if.b"
|
||||
]
|
||||
},
|
||||
{
|
||||
"href": "/aisleep/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.displayNightMode": "Off",
|
||||
"x.com.samsung.da.elapsedTime": "0",
|
||||
"x.com.samsung.da.requestFeedback": "Off",
|
||||
"x.com.samsung.da.resultFeedback": "0",
|
||||
"x.com.samsung.da.statusFeedback": "Idle",
|
||||
"x.com.samsung.da.sleepTime": "14002200"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/alarms/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.items": [
|
||||
{
|
||||
"x.com.samsung.da.id": "0",
|
||||
"x.com.samsung.da.description": "Alarm",
|
||||
"x.com.samsung.da.alarmType": "Device",
|
||||
"x.com.samsung.da.code": "ErrorCode_OFF",
|
||||
"x.com.samsung.da.triggeredTime": "2026-07-26T06:26:40",
|
||||
"x.com.samsung.da.state": "Deleted"
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "1",
|
||||
"x.com.samsung.da.description": "Alarm",
|
||||
"x.com.samsung.da.alarmType": "Device",
|
||||
"x.com.samsung.da.code": "FilterAlarm_OFF",
|
||||
"x.com.samsung.da.triggeredTime": "2026-07-26T06:26:40",
|
||||
"x.com.samsung.da.state": "Deleted"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/availablecontrolsets/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.sets": "8D0D00B4012C3F21064804000000",
|
||||
"x.com.samsung.da.id": "RAC",
|
||||
"x.com.samsung.da.version": "1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/configuration/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.region": "2647000000",
|
||||
"x.com.samsung.da.airconOptionList": [
|
||||
"SingleCommand_1",
|
||||
"DR",
|
||||
"HOMECARE_WIZARD_V2",
|
||||
"AI_RAC_KOREA_COOLONLY_3.0",
|
||||
"AI_3.0",
|
||||
"Auto_To_AI"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/connectionconfig/vs/0",
|
||||
"rep": {
|
||||
"autoReconnectionMinVersion": "1.0",
|
||||
"autoReconnection": "true",
|
||||
"autoReconnectionProtocolType": [
|
||||
"helper_hotspot",
|
||||
"ble_ocf"
|
||||
],
|
||||
"supportedWiFiAuthType": [
|
||||
"OPEN",
|
||||
"WEP",
|
||||
"WPA-PSK",
|
||||
"WPA2-PSK",
|
||||
"SAE"
|
||||
],
|
||||
"supportedWiFiCryptoType": [
|
||||
"TKIP",
|
||||
"AES",
|
||||
"WEP-64",
|
||||
"WEP-128"
|
||||
],
|
||||
"supportedWiFiFreq": [
|
||||
"2.4G"
|
||||
],
|
||||
"calmConnectionCare": {
|
||||
"version": "1.0",
|
||||
"role": [
|
||||
"things"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/drlc/0",
|
||||
"rep": {
|
||||
"DRLevel": 0,
|
||||
"start": "1970-01-01T00:00:00Z",
|
||||
"duration": 0,
|
||||
"override": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/drlc/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.drlcLevel": "0",
|
||||
"x.com.samsung.da.duration": "00:00:00",
|
||||
"x.com.samsung.da.drlcStartTime": "1970-01-01T00:00:00Z",
|
||||
"x.com.samsung.da.override": "Off",
|
||||
"x.com.samsung.da.realSaving": "Off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/energy/consumption/0",
|
||||
"rep": {
|
||||
"power": 244.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/energy/consumption/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.instantaneousPower": "244.000000",
|
||||
"x.com.samsung.da.cumulativePower": "11001",
|
||||
"x.com.samsung.da.cumulativeSavedPower": "0",
|
||||
"x.com.samsung.da.cumulativeUnit": "Wh",
|
||||
"x.com.samsung.da.instantaneousPowerUnit": "W",
|
||||
"x.com.samsung.da.cumulativePowerType": "total"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/file/information/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.timeoffset": "+09:00",
|
||||
"x.com.samsung.supprtedtype": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/filter/airdustfilter/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.filterUsage": "20",
|
||||
"x.com.samsung.da.filterUsageResolution": "1",
|
||||
"x.com.samsung.da.filterDesiredUsage": "500",
|
||||
"x.com.samsung.da.filterStatus": "normal",
|
||||
"x.com.samsung.da.filterCapacity": "500",
|
||||
"x.com.samsung.da.filterCapacityUnit": "Hour",
|
||||
"x.com.samsung.da.filterResetType": [
|
||||
"replaceable",
|
||||
"washable"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/humidity/0",
|
||||
"rep": {
|
||||
"humidity": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/humidity/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.humidity": "0",
|
||||
"x.com.samsung.da.fivepercentHumidity": "60"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/information/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.modelNum": "TP1X_DA_AC_WAC_01001_0000|10254341|60050000001711014E00402000002000",
|
||||
"x.com.samsung.da.description": "TP1X_DA_AC_WAC_01001_0000",
|
||||
"x.com.samsung.da.serialNum": "**REDACTED**",
|
||||
"x.com.samsung.da.otnDUID": "**REDACTED**",
|
||||
"x.com.samsung.da.diagProtocolType": "BLE_OCF",
|
||||
"x.com.samsung.da.diagLogType": [
|
||||
"errCode",
|
||||
"dump"
|
||||
],
|
||||
"x.com.samsung.da.diagDumpType": "file",
|
||||
"x.com.samsung.da.diagEndPoint": "SSM",
|
||||
"x.com.samsung.da.diagMnid": "0AJT",
|
||||
"x.com.samsung.da.diagSetupid": "AW0",
|
||||
"x.com.samsung.da.diagMinVersion": "3.0",
|
||||
"x.com.samsung.da.diagTsId": "DA01",
|
||||
"x.com.samsung.da.items": [
|
||||
{
|
||||
"x.com.samsung.da.id": "0",
|
||||
"x.com.samsung.da.description": "Version",
|
||||
"x.com.samsung.da.type": "Software",
|
||||
"x.com.samsung.da.number": "02545A260601",
|
||||
"x.com.samsung.da.newVersionAvailable": "0"
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "1",
|
||||
"x.com.samsung.da.description": "Version",
|
||||
"x.com.samsung.da.type": "Firmware",
|
||||
"x.com.samsung.da.number": "02543A24061800,FFFFFFFFFFFFFF",
|
||||
"x.com.samsung.da.newVersionAvailable": "0"
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "2",
|
||||
"x.com.samsung.da.description": "Version",
|
||||
"x.com.samsung.da.type": "Outdoor",
|
||||
"x.com.samsung.da.number": "02580A10000100,FFFFFFFFFFFFFF"
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "3",
|
||||
"x.com.samsung.da.description": "Version",
|
||||
"x.com.samsung.da.type": "Touch IC",
|
||||
"x.com.samsung.da.number": "02553A23031600,FFFFFFFFFFFFFF"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/keepnormalstate/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.keepnormal": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/light/vs/0",
|
||||
"rep": {
|
||||
"mode": "On",
|
||||
"supportedModes": [
|
||||
"On",
|
||||
"Off"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/mode/convenient/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.modes": "Off",
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"Off",
|
||||
"Quiet",
|
||||
"Nano",
|
||||
"NanoSleep"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/mode/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"AIComfort",
|
||||
"Cool",
|
||||
"Dry",
|
||||
"Fan"
|
||||
],
|
||||
"x.com.samsung.da.modes": [
|
||||
"Cool"
|
||||
],
|
||||
"x.com.samsung.da.options": [
|
||||
"OnTimer_0",
|
||||
"OffTimer_0",
|
||||
"ArtificialWorking_Off",
|
||||
"ComfortAICooling_Off",
|
||||
"AiTempChanged_Off",
|
||||
"AiTemp_230",
|
||||
"Volume_Mute",
|
||||
"OutdoorTemp_97",
|
||||
"CoolCapa_0",
|
||||
"StopAutoClean_Idle",
|
||||
"Autoclean_Off",
|
||||
"OptionCode_51320",
|
||||
"ExtendOptionCode_230093",
|
||||
"RacInfo_None",
|
||||
"UpdateAllow_NotAllowed",
|
||||
"DurationOn_93",
|
||||
"WelcomeCoolingState_Off"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/option/autoclean/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.status": "Stop",
|
||||
"x.com.samsung.da.settingStatus": "Off",
|
||||
"x.com.samsung.da.progress": "0",
|
||||
"x.com.samsung.da.supportedStatus": [
|
||||
"Start",
|
||||
"Stop"
|
||||
],
|
||||
"x.com.samsung.da.supportedSettingStatus": [
|
||||
"On",
|
||||
"Off"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/option/muteonce/vs/0",
|
||||
"rep": {
|
||||
"muteonce": "Off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/otninformation/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.target": "",
|
||||
"x.com.samsung.da.newVersionAvailable": "false",
|
||||
"x.com.samsung.da.newVersionNo": "00000000",
|
||||
"x.com.samsung.da.currentVersionInfo": "00000000",
|
||||
"otnStatus": "None",
|
||||
"flashingProgress": "",
|
||||
"otnTarget": "main",
|
||||
"otnCompleteDate": "noHistory",
|
||||
"otnList": [
|
||||
{
|
||||
"type": "WIFI",
|
||||
"modelId": "AWA-KR-TP1-23-AW7000",
|
||||
"versions": [
|
||||
"15260601"
|
||||
],
|
||||
"visVersion": "260601"
|
||||
},
|
||||
{
|
||||
"type": "Micom",
|
||||
"modelId": "042010254341FFFFFFFF",
|
||||
"versions": [
|
||||
"24061800",
|
||||
"FFFFFFFF"
|
||||
],
|
||||
"visVersion": "240618"
|
||||
},
|
||||
{
|
||||
"type": "Micom",
|
||||
"modelId": "042010258041FFFFFFFF",
|
||||
"versions": [
|
||||
"10000100",
|
||||
"FFFFFFFF"
|
||||
],
|
||||
"visVersion": "100001"
|
||||
},
|
||||
{
|
||||
"type": "Micom",
|
||||
"modelId": "042010255341FFFFFFFF",
|
||||
"versions": [
|
||||
"23031600",
|
||||
"FFFFFFFF"
|
||||
],
|
||||
"visVersion": "230316"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/personality/presence/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.items": [
|
||||
{
|
||||
"x.com.samsung.da.id": "",
|
||||
"x.com.samsung.da.deviceId": "**REDACTED**",
|
||||
"x.com.samsung.da.value": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/power/0",
|
||||
"rep": {
|
||||
"value": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/power/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.power": "On",
|
||||
"causeSource": "SMTS"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/quickcontrol/info/vs/0",
|
||||
"rep": {
|
||||
"supportedVersion": "1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/realtimenotiforclient/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.timeforshortnoti": "0",
|
||||
"x.com.samsung.da.longnotisubscription": "true",
|
||||
"x.com.samsung.da.periodicnotisubscription": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/reserverulesets/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.sets": "8DFFFFFFFF121E121E0000FFFFFFFFFFFF01000000011F0000000000000000001F8C00",
|
||||
"x.com.samsung.da.id": "WAC",
|
||||
"x.com.samsung.da.version": "1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/selfcheck/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.start": "Cancel",
|
||||
"x.com.samsung.da.status": "Ready",
|
||||
"x.com.samsung.da.progress": "0",
|
||||
"x.com.samsung.da.result": "NA",
|
||||
"x.com.samsung.da.supportedActions": [
|
||||
"Start",
|
||||
"Cancel"
|
||||
],
|
||||
"x.com.samsung.da.error": [
|
||||
"DA_ERROR_NONE"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/sensors/vs/0",
|
||||
"rep": {}
|
||||
},
|
||||
{
|
||||
"href": "/temperature/control/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.increment": "1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/temperature/current/0",
|
||||
"rep": {
|
||||
"range": [
|
||||
16,
|
||||
30
|
||||
],
|
||||
"units": "C",
|
||||
"temperature": 30.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/temperature/desired/0",
|
||||
"rep": {
|
||||
"range": [
|
||||
16,
|
||||
30
|
||||
],
|
||||
"units": "C",
|
||||
"temperature": 30.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/temperatures/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.items": [
|
||||
{
|
||||
"x.com.samsung.da.id": "0",
|
||||
"x.com.samsung.da.description": "Temperature",
|
||||
"x.com.samsung.da.desired": "30.0",
|
||||
"x.com.samsung.da.current": "30.0",
|
||||
"x.com.samsung.da.maximum": "30",
|
||||
"x.com.samsung.da.minimum": "18",
|
||||
"x.com.samsung.da.increment": "1.0",
|
||||
"x.com.samsung.da.unit": "Celsius"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/timezone/vs/0",
|
||||
"rep": {
|
||||
"timezoneid": "Asia/Seoul",
|
||||
"offset": "+09:00",
|
||||
"DST": "OFF"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/welcome/temperature/vs/0",
|
||||
"rep": {
|
||||
"operatingStatus": "None",
|
||||
"requestId": "0000"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/wind/direction/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.modes": "Fix",
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"Fix",
|
||||
"Left_And_Right"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/wind/strength/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.modes": "1",
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"0",
|
||||
"1",
|
||||
"2",
|
||||
"3",
|
||||
"4",
|
||||
"5"
|
||||
],
|
||||
"x.com.samsung.da.modesName": [
|
||||
"Auto",
|
||||
"1",
|
||||
"2",
|
||||
"3",
|
||||
"4",
|
||||
"5"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/wirelessinfo/vs/0",
|
||||
"rep": {
|
||||
"macaddressWiFi": "**REDACTED**",
|
||||
"macaddressBLE": "**REDACTED**",
|
||||
"connectedApSsid": "**REDACTED**"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
+386
@@ -0,0 +1,386 @@
|
||||
{
|
||||
"device0": [
|
||||
{
|
||||
"rt": [
|
||||
"x.com.samsung.devcol",
|
||||
"oic.wk.col"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.ll",
|
||||
"oic.if.b"
|
||||
]
|
||||
},
|
||||
{
|
||||
"href": "/personality/presence/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.items": [
|
||||
{
|
||||
"x.com.samsung.da.id": "",
|
||||
"x.com.samsung.da.deviceId": "**REDACTED**",
|
||||
"x.com.samsung.da.value": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/realtimenotiforclient/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.timeforshortnoti": "0",
|
||||
"x.com.samsung.da.longnotisubscription": "false",
|
||||
"x.com.samsung.da.periodicnotisubscription": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/filter/airdustfilter/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.filterUsage": "100",
|
||||
"x.com.samsung.da.filterUsageResolution": "1",
|
||||
"x.com.samsung.da.filterDesiredUsage": "224",
|
||||
"x.com.samsung.da.filterStatus": "wash",
|
||||
"x.com.samsung.da.filterCapacity": "224",
|
||||
"x.com.samsung.da.filterCapacityUnit": "Hour",
|
||||
"x.com.samsung.da.filterResetType": [
|
||||
"washable"
|
||||
],
|
||||
"x.com.samsung.da.supportedFilterDesiredUsage": [
|
||||
"224",
|
||||
"300",
|
||||
"500",
|
||||
"700"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/mode/convenient/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"Off",
|
||||
"Sleep"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/option/autoclean/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.status": "Stop",
|
||||
"x.com.samsung.da.settingStatus": "On",
|
||||
"x.com.samsung.da.progress": "100",
|
||||
"cleaningTime": "30",
|
||||
"minCleaningTime": "10",
|
||||
"maxCleaningTime": "60",
|
||||
"cleaningTimeUnit": "10",
|
||||
"x.com.samsung.da.supportedStatus": [
|
||||
"Start",
|
||||
"TimedClean",
|
||||
"Stop"
|
||||
],
|
||||
"x.com.samsung.da.supportedSettingStatus": [
|
||||
"On",
|
||||
"TimedClean",
|
||||
"Off"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/alarms/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.items": [
|
||||
{
|
||||
"x.com.samsung.da.id": "0",
|
||||
"x.com.samsung.da.description": "Alarm",
|
||||
"x.com.samsung.da.alarmType": "Device",
|
||||
"x.com.samsung.da.code": "ErrorCode_OFF",
|
||||
"x.com.samsung.da.triggeredTime": "2026-07-26T10:35:53",
|
||||
"x.com.samsung.da.state": "Deleted"
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "2",
|
||||
"x.com.samsung.da.description": "Alarm",
|
||||
"x.com.samsung.da.alarmType": "Device",
|
||||
"x.com.samsung.da.code": "WaterTankFull_OFF",
|
||||
"x.com.samsung.da.triggeredTime": "2026-07-26T10:35:53",
|
||||
"x.com.samsung.da.state": "Deleted"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/energy/consumption/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.instantaneousPower": "259.000000",
|
||||
"x.com.samsung.da.cumulativePower": "183099",
|
||||
"x.com.samsung.da.cumulativeSavedPower": "0",
|
||||
"x.com.samsung.da.cumulativeUnit": "Wh",
|
||||
"x.com.samsung.da.instantaneousPowerUnit": "W",
|
||||
"x.com.samsung.da.cumulativePowerType": "total"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/mode/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"Smart",
|
||||
"Max",
|
||||
"High",
|
||||
"Medium",
|
||||
"Quiet",
|
||||
"ClothesDrying"
|
||||
],
|
||||
"x.com.samsung.da.modes": [
|
||||
"Smart"
|
||||
],
|
||||
"x.com.samsung.da.options": [
|
||||
"Volume_Mute",
|
||||
"RacInfo_None",
|
||||
"UpdateAllow_NotAllowed",
|
||||
"WelcomeCoolingState_Off"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/power/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.power": "On",
|
||||
"causeSource": "SMTS"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/sensors/vs/0",
|
||||
"rep": {}
|
||||
},
|
||||
{
|
||||
"href": "/information/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.modelNum": "TP1X_DA_AC_DHM_01001_0000|10253841|77000000001700000A00000000000000",
|
||||
"x.com.samsung.da.description": "TP1X_DA_AC_DHM_01001_0000",
|
||||
"x.com.samsung.da.serialNum": "**REDACTED**",
|
||||
"x.com.samsung.da.otnDUID": "**REDACTED**",
|
||||
"x.com.samsung.da.diagProtocolType": "BLE_OCF",
|
||||
"x.com.samsung.da.diagLogType": [
|
||||
"errCode",
|
||||
"dump"
|
||||
],
|
||||
"x.com.samsung.da.diagDumpType": "file",
|
||||
"x.com.samsung.da.diagEndPoint": "SSM",
|
||||
"x.com.samsung.da.diagMnid": "0AJT",
|
||||
"x.com.samsung.da.diagSetupid": "AY0",
|
||||
"x.com.samsung.da.diagMinVersion": "3.0",
|
||||
"x.com.samsung.da.diagTsId": "DA01",
|
||||
"x.com.samsung.da.items": [
|
||||
{
|
||||
"x.com.samsung.da.id": "0",
|
||||
"x.com.samsung.da.description": "Version",
|
||||
"x.com.samsung.da.type": "Software",
|
||||
"x.com.samsung.da.number": "02544A260401",
|
||||
"x.com.samsung.da.newVersionAvailable": "0"
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "1",
|
||||
"x.com.samsung.da.description": "Version",
|
||||
"x.com.samsung.da.type": "Firmware",
|
||||
"x.com.samsung.da.number": "02538A24032700,FFFFFFFFFFFFFF",
|
||||
"x.com.samsung.da.newVersionAvailable": "0"
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "2",
|
||||
"x.com.samsung.da.description": "Version",
|
||||
"x.com.samsung.da.type": "Outdoor",
|
||||
"x.com.samsung.da.number": "02590A10000100,FFFFFFFFFFFFFF"
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "4",
|
||||
"x.com.samsung.da.description": "Version",
|
||||
"x.com.samsung.da.type": "Touch IC",
|
||||
"x.com.samsung.da.number": "02552A23020200,FFFFFFFFFFFFFF"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/file/information/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.timeoffset": "+09:00",
|
||||
"x.com.samsung.supprtedtype": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/configuration/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.region": "0000000000",
|
||||
"x.com.samsung.da.airconOptionList": [
|
||||
"SingleCommand_1",
|
||||
"DR",
|
||||
"HOMECARE_WIZARD_V2",
|
||||
"PRODUCT_GLOBAL",
|
||||
"AI_RAC_GLOBAL_2.0",
|
||||
"AI_Heat"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/humidity/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.humidity": "47",
|
||||
"x.com.samsung.da.desiredHumidity": "50",
|
||||
"increment": "5",
|
||||
"x.com.samsung.da.fivepercentHumidity": "46"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/drlc/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.drlcLevel": "0",
|
||||
"x.com.samsung.da.durationminutes": "0",
|
||||
"x.com.samsung.da.start": "1970-01-01T00:00:00Z",
|
||||
"x.com.samsung.da.override": "Off",
|
||||
"x.com.samsung.da.realSaving": "Off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/availablecontrolsets/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.sets": "20001E46070A5A00000000000000",
|
||||
"x.com.samsung.da.id": "DHM",
|
||||
"x.com.samsung.da.version": "1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/da/softreset/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.softwarereset": "false"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/keepnormalstate/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.keepnormal": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/otninformation/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.target": "",
|
||||
"x.com.samsung.da.newVersionAvailable": "false",
|
||||
"x.com.samsung.da.newVersionNo": "00000000",
|
||||
"x.com.samsung.da.currentVersionInfo": "00000000",
|
||||
"otnStatus": "None",
|
||||
"flashingProgress": "",
|
||||
"otnTarget": "main",
|
||||
"otnCompleteDate": "noHistory",
|
||||
"otnList": [
|
||||
{
|
||||
"type": "WIFI",
|
||||
"modelId": "ADH-WW-TP1-23-AY7100",
|
||||
"versions": [
|
||||
"18260401"
|
||||
],
|
||||
"visVersion": "260401"
|
||||
},
|
||||
{
|
||||
"type": "Micom",
|
||||
"modelId": "042110253841FFFFFFFF",
|
||||
"versions": [
|
||||
"24032700",
|
||||
"FFFFFFFF"
|
||||
],
|
||||
"visVersion": "240327"
|
||||
},
|
||||
{
|
||||
"type": "Micom",
|
||||
"modelId": "042110259041FFFFFFFF",
|
||||
"versions": [
|
||||
"10000100",
|
||||
"FFFFFFFF"
|
||||
],
|
||||
"visVersion": "100001"
|
||||
},
|
||||
{
|
||||
"type": "Micom",
|
||||
"modelId": "042110255241FFFFFFFF",
|
||||
"versions": [
|
||||
"23020200",
|
||||
"FFFFFFFF"
|
||||
],
|
||||
"visVersion": "230202"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/timezone/vs/0",
|
||||
"rep": {
|
||||
"timezoneid": "Asia/Seoul",
|
||||
"offset": "+09:00",
|
||||
"DST": "OFF"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/option/muteonce/vs/0",
|
||||
"rep": {
|
||||
"muteonce": "Off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/reserverulesets/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.sets": "3F1E461E461E46FFFFFFFF1E4600",
|
||||
"x.com.samsung.da.id": "DHM",
|
||||
"x.com.samsung.da.version": "1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/connectionconfig/vs/0",
|
||||
"rep": {
|
||||
"autoReconnectionMinVersion": "1.0",
|
||||
"autoReconnection": "true",
|
||||
"autoReconnectionProtocolType": [
|
||||
"helper_hotspot",
|
||||
"ble_ocf"
|
||||
],
|
||||
"supportedWiFiAuthType": [
|
||||
"OPEN",
|
||||
"WEP",
|
||||
"WPA-PSK",
|
||||
"WPA2-PSK",
|
||||
"SAE"
|
||||
],
|
||||
"supportedWiFiCryptoType": [
|
||||
"TKIP",
|
||||
"AES",
|
||||
"WEP-64",
|
||||
"WEP-128"
|
||||
],
|
||||
"supportedWiFiFreq": [
|
||||
"2.4G"
|
||||
],
|
||||
"calmConnectionCare": {
|
||||
"version": "1.0",
|
||||
"role": [
|
||||
"things"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/welcome/humidity/vs/0",
|
||||
"rep": {
|
||||
"requestId": "0000",
|
||||
"operatingStatus": "None"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/wirelessinfo/vs/0",
|
||||
"rep": {
|
||||
"macaddressWiFi": "**REDACTED**",
|
||||
"macaddressBLE": "**REDACTED**"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/quickcontrol/info/vs/0",
|
||||
"rep": {
|
||||
"supportedVersion": "1.0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
+391
@@ -0,0 +1,391 @@
|
||||
{
|
||||
"device0": [
|
||||
{
|
||||
"rt": [
|
||||
"x.com.samsung.devcol",
|
||||
"oic.wk.col"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.ll",
|
||||
"oic.if.b"
|
||||
]
|
||||
},
|
||||
{
|
||||
"href": "/alarms/vs/0",
|
||||
"rep": {}
|
||||
},
|
||||
{
|
||||
"href": "/configuration/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.region": "0000000000",
|
||||
"x.com.samsung.da.countryCode": "US"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/connectionconfig/vs/0",
|
||||
"rep": {
|
||||
"autoReconnectionMinVersion": "1.0",
|
||||
"autoReconnection": "true",
|
||||
"autoReconnectionProtocolType": [
|
||||
"helper_hotspot",
|
||||
"ble_ocf"
|
||||
],
|
||||
"supportedWiFiAuthType": [
|
||||
"OPEN",
|
||||
"WEP",
|
||||
"WPA-PSK",
|
||||
"WPA2-PSK",
|
||||
"SAE"
|
||||
],
|
||||
"supportedWiFiCryptoType": [
|
||||
"TKIP",
|
||||
"AES",
|
||||
"WEP-64",
|
||||
"WEP-128"
|
||||
],
|
||||
"supportedWiFiFreq": [
|
||||
"2.4G"
|
||||
],
|
||||
"calmConnectionCare": {
|
||||
"version": "1.0",
|
||||
"role": [
|
||||
"things"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/course/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"HOMECARE_WIZARD_V2"
|
||||
],
|
||||
"x.com.samsung.da.options": [
|
||||
"DeviceType_0152",
|
||||
"UpdateAllow_NotAllowed",
|
||||
"Course_01",
|
||||
"AiOption_On",
|
||||
"MixedLoadBell_Disable",
|
||||
"MixedLoadBellNoti_Nothing",
|
||||
"LaundryOutTime_0",
|
||||
"SeamlessControl_Enable",
|
||||
"KidsLockBypass_On",
|
||||
"DetergentOnce_1",
|
||||
"DetergentLeft_0",
|
||||
"DetergentBase_5",
|
||||
"DetergentAlarm_Off",
|
||||
"DetergentType_3",
|
||||
"DetergentTotal_0",
|
||||
"SpecialFunction_20",
|
||||
"AvailableDelayTime_39",
|
||||
"LaundryPlannerUserSetTime_0",
|
||||
"ProgressTimeSet_B20870B400B4",
|
||||
"WrinklePreventRunning_Off",
|
||||
"DryTime_0",
|
||||
"SendToDevice_Off",
|
||||
"GMT_F6",
|
||||
"DryTimeSet_0A00000000500A005A00000000000000001E0A005A00000000000000001414005A000000000000000000000000000000000000000000000000000000000000000000000000140A005A280A005A",
|
||||
"EnergyLevelSet_0504040504040205020204040305020203030103",
|
||||
"MostUsed_068A3ED000",
|
||||
"WrinklePreventSet_0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F",
|
||||
"MixedLoadBellSet_02FF02FFFF02FFFFFF0202FF02FFFF02FFFFFF",
|
||||
"GeoFenceAlarm",
|
||||
"UsagesDB_ok",
|
||||
"EnergyKW_396",
|
||||
"DrumCleanLog_Empty",
|
||||
"TimeSync_NotSupported"
|
||||
],
|
||||
"x.com.samsung.da.supportedOptions": [
|
||||
"2018410D33E06833ED0002F8520D33E178520D3083E8520D000078102D33E338520D520358520D000348520D0000E8520D33E058520D33E308410D308318410D33E098410D3080F8204D308328308D33E2E8102D308368000D0000D8102D000"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/cycleinterface/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.cycleInterfaceEnabled": "On"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/diagnosis/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.diagnosisStart": "Ready"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/energy/consumption/0",
|
||||
"rep": {}
|
||||
},
|
||||
{
|
||||
"href": "/energy/consumption/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.instantaneousPower": "-500",
|
||||
"x.com.samsung.da.instantaneousPowerUnit": "W",
|
||||
"x.com.samsung.da.cumulativePower": "2323800",
|
||||
"x.com.samsung.da.cumulativeUnit": "Wh",
|
||||
"x.com.samsung.da.cumulativeDate": "1784991600",
|
||||
"x.com.samsung.da.cumulativeDateUTC": "1785009600"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/file/information/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.timeoffset": "-05:00"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/information/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.modelNum": "DA_WM_TP1_21_COMMON|20286441|300000010015110002A3031700000000",
|
||||
"x.com.samsung.da.description": "DA_WM_TP1_21_COMMON_DVE50A8800_8600/DC92-02835A_0080",
|
||||
"x.com.samsung.da.serialNum": "**REDACTED**",
|
||||
"x.com.samsung.da.otnDUID": "**REDACTED**",
|
||||
"x.com.samsung.da.diagProtocolType": "BLE_OCF",
|
||||
"x.com.samsung.da.diagLogType": [
|
||||
"errCode",
|
||||
"dump"
|
||||
],
|
||||
"x.com.samsung.da.diagDumpType": "file",
|
||||
"x.com.samsung.da.diagEndPoint": "SSM",
|
||||
"x.com.samsung.da.diagMnid": "0AJT",
|
||||
"x.com.samsung.da.diagSetupid": "310",
|
||||
"x.com.samsung.da.diagMinVersion": "3.0",
|
||||
"x.com.samsung.da.diagTsId": "DA01",
|
||||
"x.com.samsung.da.items": [
|
||||
{
|
||||
"x.com.samsung.da.id": "0",
|
||||
"x.com.samsung.da.description": "DA_WM_TP1_21_COMMON|20286441|300000010015110002A3031700000000",
|
||||
"x.com.samsung.da.type": "Software",
|
||||
"x.com.samsung.da.number": "02986A260118(A182)",
|
||||
"x.com.samsung.da.newVersionAvailable": "0"
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "1",
|
||||
"x.com.samsung.da.description": "Firmware_1_DB_20286441210514150FFFFF202835412105131904FFFF(01522028644120283541_30000000)(FileDown:0)(Type:0)",
|
||||
"x.com.samsung.da.type": "Firmware",
|
||||
"x.com.samsung.da.number": "02864A21051415,02835A21051319",
|
||||
"x.com.samsung.da.newVersionAvailable": "0"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/kidslock/0",
|
||||
"rep": {
|
||||
"value": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/kidslock/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.kidsLock": "Ready"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/operational/state/0",
|
||||
"rep": {
|
||||
"currentMachineState": "**REDACTED**",
|
||||
"machineStates": "**REDACTED**",
|
||||
"jobStates": [
|
||||
"None",
|
||||
"Drying",
|
||||
"Cooling",
|
||||
"Finish"
|
||||
],
|
||||
"currentJobState": "None",
|
||||
"remainingTime": "01:15:00",
|
||||
"progressPercentage": "1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/operational/state/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.state": "Ready",
|
||||
"x.com.samsung.da.remainingTime": "01:15:00",
|
||||
"x.com.samsung.da.progressPercentage": "1",
|
||||
"x.com.samsung.da.progress": "None",
|
||||
"x.com.samsung.da.delayEndTime": "00:00:00",
|
||||
"x.com.samsung.da.supportedProgress": [
|
||||
"None",
|
||||
"Drying",
|
||||
"Cooling",
|
||||
"Finish"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/otninformation/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.target": "",
|
||||
"x.com.samsung.da.newVersionAvailable": "false",
|
||||
"x.com.samsung.da.newVersionNo": "00000000",
|
||||
"x.com.samsung.da.currentVersionInfo": "00000000",
|
||||
"otnStatus": "None",
|
||||
"flashingProgress": "",
|
||||
"otnTarget": "main",
|
||||
"otnCompleteDate": "2026-03-04",
|
||||
"otnList": [
|
||||
{
|
||||
"type": "WIFI",
|
||||
"modelId": "DA_WM_TP1_21_COMMON",
|
||||
"versions": [
|
||||
"30260118"
|
||||
],
|
||||
"visVersion": "260118"
|
||||
},
|
||||
{
|
||||
"type": "Micom",
|
||||
"modelId": "01522028644120283541",
|
||||
"versions": [
|
||||
"21051415",
|
||||
"21051319"
|
||||
],
|
||||
"visVersion": "210514"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/power/0",
|
||||
"rep": {
|
||||
"value": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/power/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.power": "Off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/quickcontrol/info/vs/0",
|
||||
"rep": {
|
||||
"supportedVersion": "1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/realtimenotiforclient/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.timeforshortnoti": "10",
|
||||
"x.com.samsung.da.periodicnotisubscription": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/remotectrl/0",
|
||||
"rep": {
|
||||
"value": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/remotectrl/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.remoteControlEnabled": "false"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/setting/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.supportedSetLanguage": [
|
||||
"en_US",
|
||||
"ko_KR",
|
||||
"fr_CA",
|
||||
"es_MX",
|
||||
"pt_BR"
|
||||
],
|
||||
"x.com.samsung.da.setLanguage": "en_US"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/st/dryercourse/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.st.dryerMode": "Table_03_Course_01",
|
||||
"x.com.samsung.da.st.courseTable": "Table_03"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/timezone/vs/0",
|
||||
"rep": {
|
||||
"timezoneid": "America/Chicago",
|
||||
"offset": "-05:00",
|
||||
"DST": "ON"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/washer/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.wrinklePrevent": "Off",
|
||||
"x.com.samsung.da.waterTemperature": "Medium",
|
||||
"x.com.samsung.da.supportedWaterTemperature": [
|
||||
"None",
|
||||
"ExtraLow",
|
||||
"Low",
|
||||
"MediumLow",
|
||||
"Medium",
|
||||
"High"
|
||||
],
|
||||
"x.com.samsung.da.dryLevel": "Normal",
|
||||
"x.com.samsung.da.supportedDryLevel": [
|
||||
"None",
|
||||
"Damp",
|
||||
"Less",
|
||||
"Normal",
|
||||
"More",
|
||||
"Very"
|
||||
],
|
||||
"x.com.samsung.da.dryerType": "Electricity"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/wirelessinfo/vs/0",
|
||||
"rep": {
|
||||
"macaddressWiFi": "**REDACTED**",
|
||||
"macaddressBLE": "**REDACTED**"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/wm/editcourse/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.editCourseList": "EditCourseList_0106",
|
||||
"x.com.samsung.da.fixedCourseList": "FixedCourseList_0101"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/wm/jobbeginingstatus/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.currentStatus": "None"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/wm/personalcourse/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.courses": [
|
||||
"F1_00",
|
||||
"F2_00",
|
||||
"F3_00",
|
||||
"F4_00",
|
||||
"F5_00",
|
||||
"F6_00",
|
||||
"F7_00",
|
||||
"F8_00",
|
||||
"F9_00",
|
||||
"FA_00"
|
||||
],
|
||||
"x.com.samsung.da.maxCourseNum": "10"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/wm/setinfo/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.isModelSettingWithoutSC": "true",
|
||||
"x.com.samsung.da.aiCourse": "false",
|
||||
"x.com.samsung.da.isModelSettingPowerOnOff": "false",
|
||||
"x.com.samsung.da.modelCode": "M(None),W(DVE50A8600V/A3)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/wm/welcomemsg/vs/0",
|
||||
"rep": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"state_keys": [
|
||||
"air_filter_status",
|
||||
"air_filter_usage",
|
||||
"alarm_code",
|
||||
"auto_clean",
|
||||
"climate",
|
||||
"current_temperature_c",
|
||||
"display_light",
|
||||
"energy_kwh",
|
||||
"energy_saved_kwh",
|
||||
"firmware_update",
|
||||
"humidity",
|
||||
"mute_once",
|
||||
"power_watts",
|
||||
"selfcheck_error",
|
||||
"selfcheck_result",
|
||||
"selfcheck_status"
|
||||
]
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"state_keys": [
|
||||
"air_filter_status",
|
||||
"air_filter_usage",
|
||||
"alarm_code",
|
||||
"auto_clean",
|
||||
"energy_kwh",
|
||||
"energy_saved_kwh",
|
||||
"firmware_update",
|
||||
"humidity",
|
||||
"mute_once",
|
||||
"operating_mode",
|
||||
"power_switch",
|
||||
"power_watts",
|
||||
"target_humidity"
|
||||
]
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"state_keys": [
|
||||
"alarm_code",
|
||||
"child_lock",
|
||||
"completion_minutes",
|
||||
"cycle",
|
||||
"cycle_active",
|
||||
"delay_start_hours",
|
||||
"diagnosis",
|
||||
"dry_level",
|
||||
"dry_time",
|
||||
"dryer_type",
|
||||
"energy_kwh",
|
||||
"finish_time",
|
||||
"firmware_update",
|
||||
"job_beginning_status",
|
||||
"machine_state",
|
||||
"power_switch",
|
||||
"progress",
|
||||
"progress_percentage",
|
||||
"remote_control",
|
||||
"wrinkle_prevent"
|
||||
]
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"state_keys": [
|
||||
"alarm_code",
|
||||
"buzz_lock",
|
||||
"coldwater_lock",
|
||||
"dispense_capacity",
|
||||
"dispense_type",
|
||||
"favorite_capacity",
|
||||
"favorite_capacity_enabled",
|
||||
"filter_clean_remain_time",
|
||||
"filter_door_status",
|
||||
"filter_status",
|
||||
"filter_usage",
|
||||
"firmware_update",
|
||||
"hot_water_temperature",
|
||||
"hotwater_lock",
|
||||
"pouring",
|
||||
"selfcheck_error",
|
||||
"selfcheck_result",
|
||||
"selfcheck_status",
|
||||
"sterilize_last_time",
|
||||
"sterilize_period",
|
||||
"sterilize_plan_time",
|
||||
"sterilize_run_time",
|
||||
"waterpurifier_status"
|
||||
]
|
||||
}
|
||||
+234
@@ -0,0 +1,234 @@
|
||||
{
|
||||
"device0": [
|
||||
{
|
||||
"rt": [
|
||||
"x.com.samsung.devcol",
|
||||
"oic.wk.col"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.ll",
|
||||
"oic.if.b"
|
||||
]
|
||||
},
|
||||
{
|
||||
"href": "/alarms/vs/0",
|
||||
"rep": {}
|
||||
},
|
||||
{
|
||||
"href": "/selfcheck/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.status": "Ready",
|
||||
"x.com.samsung.da.result": "Success",
|
||||
"x.com.samsung.da.error": [
|
||||
"DA_ERROR_NONE"
|
||||
],
|
||||
"x.com.samsung.da.supportedActions": [
|
||||
"Start"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/mode/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"HOMECARE_WIZARD_V2"
|
||||
],
|
||||
"x.com.samsung.da.modes": [
|
||||
"WATERFILTER_DISABLE"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/mode/0",
|
||||
"rep": {
|
||||
"supportedModes": [
|
||||
"HOMECARE_WIZARD_V2"
|
||||
],
|
||||
"modes": [
|
||||
"WATERFILTER_DISABLE"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/realtimenotiforclient/vs/0",
|
||||
"rep": {}
|
||||
},
|
||||
{
|
||||
"href": "/information/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.modelNum": "TP2X_WATERPURIFIER_20K|00132341|900000000215130001060F0000020000",
|
||||
"x.com.samsung.da.description": "TP2X_WATERPURIFIER_20K",
|
||||
"x.com.samsung.da.serialNum": "**REDACTED**",
|
||||
"x.com.samsung.da.otnDUID": "**REDACTED**",
|
||||
"x.com.samsung.da.diagProtocolType": "WIFI_HTTPS",
|
||||
"x.com.samsung.da.diagLogType": [
|
||||
"errCode",
|
||||
"dump"
|
||||
],
|
||||
"x.com.samsung.da.diagDumpType": "file",
|
||||
"x.com.samsung.da.diagEndPoint": "SSM",
|
||||
"x.com.samsung.da.diagMnid": "0AJT",
|
||||
"x.com.samsung.da.diagSetupid": "540",
|
||||
"x.com.samsung.da.diagMinVersion": "1.0",
|
||||
"x.com.samsung.da.items": [
|
||||
{
|
||||
"x.com.samsung.da.id": "0",
|
||||
"x.com.samsung.da.description": "WiFi Module",
|
||||
"x.com.samsung.da.type": "Software",
|
||||
"x.com.samsung.da.number": "02144A240615",
|
||||
"x.com.samsung.da.newVersionAvailable": "0"
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "1",
|
||||
"x.com.samsung.da.description": "Micom",
|
||||
"x.com.samsung.da.type": "Firmware",
|
||||
"x.com.samsung.da.number": "24061111, 24091218, 21072807, FFFFFFFF",
|
||||
"x.com.samsung.da.newVersionAvailable": "0"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/file/information/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.timeoffset": "+09:00",
|
||||
"x.com.samsung.supprtedtype": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/otninformation/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.target": "",
|
||||
"x.com.samsung.da.newVersionAvailable": "false",
|
||||
"otnStatus": "None",
|
||||
"flashingProgress": "",
|
||||
"otnList": [
|
||||
{
|
||||
"type": "WIFI",
|
||||
"modelId": "A-WPWW-TP2-20-COMMON",
|
||||
"versions": [
|
||||
"20240615"
|
||||
],
|
||||
"visVersion": "240615"
|
||||
},
|
||||
{
|
||||
"type": "Micom",
|
||||
"modelId": "03800013234100132441",
|
||||
"versions": [
|
||||
"24061111",
|
||||
"24091218"
|
||||
],
|
||||
"visVersion": "240912"
|
||||
},
|
||||
{
|
||||
"type": "Micom",
|
||||
"modelId": "038000132541FFFFFFFF",
|
||||
"versions": [
|
||||
"21072807",
|
||||
"FFFFFFFF"
|
||||
],
|
||||
"visVersion": "210728"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/filter/waterfilter/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.filterUsage": "28",
|
||||
"x.com.samsung.da.filterUsageResolution": "1",
|
||||
"x.com.samsung.da.filterStatus": "normal",
|
||||
"x.com.samsung.da.lastResetDate": "2026-04-14T14:33:15",
|
||||
"x.com.samsung.da.filterResetType": [
|
||||
"replaceable"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/status/lock/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.hotwaterLock": "Unlocked",
|
||||
"x.com.samsung.da.coldwaterLock": "Unlocked",
|
||||
"x.com.samsung.da.buzzLock": "Unlocked"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/setting/waterpurifier/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.desiredType": "coldwater",
|
||||
"x.com.samsung.da.tempDesiredHotWater": "85",
|
||||
"x.com.samsung.da.tempUnit": "C",
|
||||
"x.com.samsung.da.desiredCapacity": "100",
|
||||
"x.com.samsung.da.capacityUnit": "C",
|
||||
"x.com.samsung.da.capacityResolution": "10",
|
||||
"x.com.samsung.da.desiredCapacityRange": [
|
||||
"50",
|
||||
"2000"
|
||||
],
|
||||
"x.com.samsung.da.supportedTypes": [
|
||||
"ambientwater",
|
||||
"coldwater",
|
||||
"hotwater"
|
||||
],
|
||||
"x.com.samsung.da.supportedHotTemperatures": [
|
||||
"40",
|
||||
"75",
|
||||
"85"
|
||||
],
|
||||
"x.com.samsung.da.triggerTime": "1785063153",
|
||||
"x.com.samsung.da.pourStatus": "Off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/status/waterpurifier/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.status": "Ready",
|
||||
"x.com.samsung.da.filterDoorStatus": "Close",
|
||||
"x.com.samsung.da.sterilizeRunTime": "0",
|
||||
"x.com.samsung.da.sterilizeStartTime": "2026-07-27T18:00:00",
|
||||
"x.com.samsung.da.sterilizePeriod": "3",
|
||||
"x.com.samsung.da.sterilizePlanTime": "2026-07-27T18:00:00",
|
||||
"x.com.samsung.da.sterilizeLastTime": "2026-07-24T18:09:25",
|
||||
"x.com.samsung.da.sterilizeMaxTime": "10",
|
||||
"x.com.samsung.da.filterCleanRemainTime": "0",
|
||||
"x.com.samsung.da.filterCleanMaxTime": "15"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/favorite/capacity/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.switchCapacity": "On",
|
||||
"x.com.samsung.da.defaultCapacity": "100",
|
||||
"x.com.samsung.da.maxCapacity": "6",
|
||||
"x.com.samsung.da.capacityList": [
|
||||
"100",
|
||||
"150",
|
||||
"200",
|
||||
"250",
|
||||
"550",
|
||||
"9999"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/automation/waterpurifier/vs/0",
|
||||
"rep": {
|
||||
"automation.supported.modes": [
|
||||
"0"
|
||||
],
|
||||
"automation.supported.options": [
|
||||
"capacity"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/timezone/vs/0",
|
||||
"rep": {
|
||||
"timezoneid": "Asia/Seoul",
|
||||
"offset": "+09:00",
|
||||
"DST": "OFF"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -508,3 +508,19 @@ async def test_options_flow_finish_preserves_existing_options(
|
||||
|
||||
assert result['type'] == FlowResultType.CREATE_ENTRY
|
||||
assert entry.options[CONF_BYPASS_REMOTE_CONTROL] is True
|
||||
|
||||
|
||||
def test_is_placeholder_serial_catches_nothing_svc():
|
||||
"""Issue #83: the ARTIK051_DONGLE_REF firmware family reports the
|
||||
literal string 'Nothing(SVC)' as serialNum on every unit -- non-empty,
|
||||
so it must be caught by name, not by the plain `if not serial` check."""
|
||||
from custom_components.localthings.config_flow import _is_placeholder_serial
|
||||
assert _is_placeholder_serial('Nothing(SVC)') is True
|
||||
assert _is_placeholder_serial('nothing(svc)') is True
|
||||
assert _is_placeholder_serial(' Nothing(SVC) ') is True
|
||||
|
||||
|
||||
def test_is_placeholder_serial_accepts_real_serials():
|
||||
from custom_components.localthings.config_flow import _is_placeholder_serial
|
||||
assert _is_placeholder_serial('0A1B2C3D4E5F') is False
|
||||
assert _is_placeholder_serial('') is False
|
||||
|
||||
@@ -137,6 +137,29 @@ def test_run_discovery_detects_washer_via_model_fallback(
|
||||
assert coordinator.device_type_name == 'washer'
|
||||
|
||||
|
||||
def test_run_discovery_falls_back_to_host_for_placeholder_serial(
|
||||
hass: HomeAssistant, mock_entry
|
||||
) -> None:
|
||||
"""Issue #83: the ARTIK051_DONGLE_REF firmware family reports the
|
||||
literal string 'Nothing(SVC)' as serialNum on every unit. Left as-is,
|
||||
two such units get the same device_serial (which feeds both the HA
|
||||
device-registry identifier and every entity's unique_id), so the
|
||||
second one's entities silently collide and get dropped. It must be
|
||||
treated the same as an empty serial and fall back to the host."""
|
||||
resources = {
|
||||
'/information/vs/0': {
|
||||
'x.com.samsung.da.modelNum':
|
||||
'ARTIK051_DONGLE_REF|00127641|00080020001430300100000000000000',
|
||||
'x.com.samsung.da.description': 'ARTIK_REF_17K',
|
||||
'x.com.samsung.da.serialNum': 'Nothing(SVC)',
|
||||
},
|
||||
'/otninformation/vs/0': {},
|
||||
}
|
||||
coordinator = LocalThingsCoordinator(hass, mock_entry)
|
||||
coordinator._run_discovery(resources)
|
||||
assert coordinator.device_serial == mock_entry.data[CONF_HOST]
|
||||
|
||||
|
||||
def test_run_discovery_detects_cooktop_via_resource_signature(
|
||||
hass: HomeAssistant, mock_entry
|
||||
) -> None:
|
||||
@@ -146,7 +169,7 @@ def test_run_discovery_detects_cooktop_via_resource_signature(
|
||||
coordinator = LocalThingsCoordinator(hass, mock_entry)
|
||||
coordinator._run_discovery(_load_device('cooktop'))
|
||||
|
||||
assert coordinator.device_type_name == 'cooktop'
|
||||
assert coordinator.device_type_name == 'gas_cooktop'
|
||||
assert coordinator._unbound_hrefs == []
|
||||
|
||||
|
||||
|
||||
+80
-3
@@ -42,9 +42,12 @@ class TestForDevice:
|
||||
assert registry.name == 'refrigerator'
|
||||
|
||||
def test_for_device_returns_cooktop_registry(self):
|
||||
"""The registry's own .name is 'gas_cooktop' (disambiguated from
|
||||
induction_cooktop), but the lookup key devices route through stays
|
||||
'cooktop' -- oneUiVersion "Cooktop" still resolves here."""
|
||||
registry = for_device('7.0 Cooktop')
|
||||
assert registry is not None
|
||||
assert registry.name == 'cooktop'
|
||||
assert registry.name == 'gas_cooktop'
|
||||
|
||||
def test_for_device_returns_range_hood_registry(self):
|
||||
registry = for_device('7.0 Range Hood')
|
||||
@@ -130,6 +133,27 @@ class TestModelNumSegments:
|
||||
assert _model_num_segments(None) == ['']
|
||||
|
||||
|
||||
class TestConsumerModelKey:
|
||||
def test_finds_key_in_last_segment(self):
|
||||
from custom_components.localthings.registry.by_type import _consumer_model_key
|
||||
assert _consumer_model_key('DA_WM_TP1_21_COMMON_WW5000C') == 'washer'
|
||||
|
||||
def test_finds_key_before_a_trailing_unrecognized_segment(self):
|
||||
"""Issue #79: 'DVE50A8800_8600' pairs two model numbers -- the real
|
||||
consumer token is the second-to-last segment, not the last."""
|
||||
from custom_components.localthings.registry.by_type import _consumer_model_key
|
||||
assert _consumer_model_key(
|
||||
'DA_WM_TP1_21_COMMON_DVE50A8800_8600/DC92-02835A_0080') == 'dryer'
|
||||
|
||||
def test_ignores_everything_after_first_slash(self):
|
||||
from custom_components.localthings.registry.by_type import _consumer_model_key
|
||||
assert _consumer_model_key('DA_WM_TP1_21_COMMON_WW5000C/DW9000_board') == 'washer'
|
||||
|
||||
def test_none_when_no_segment_matches(self):
|
||||
from custom_components.localthings.registry.by_type import _consumer_model_key
|
||||
assert _consumer_model_key('ARTIK051_DONGLE_REF') is None
|
||||
|
||||
|
||||
class TestForDeviceByModel:
|
||||
"""Fallback device-type detection for hardware without oneUiVersion."""
|
||||
|
||||
@@ -161,6 +185,20 @@ class TestForDeviceByModel:
|
||||
assert reg is not None
|
||||
assert reg.name == 'dryer'
|
||||
|
||||
def test_dryer_dve50a8600_paired_model_numbers_in_description(self):
|
||||
"""Issue #79: description pairs two model numbers
|
||||
('..._DVE50A8800_8600/DC92-...'), so the 'DV' consumer token is one
|
||||
segment before the literal last segment ('8600', which has no
|
||||
recognizable prefix on its own). The old last-segment-only check
|
||||
fell through to 'unknown' here."""
|
||||
from custom_components.localthings.registry.by_type import for_device_by_model
|
||||
reg = for_device_by_model(
|
||||
'DA_WM_TP1_21_COMMON|20286441|300000010015110002A3031700000000',
|
||||
'DA_WM_TP1_21_COMMON_DVE50A8800_8600/DC92-02835A_0080',
|
||||
)
|
||||
assert reg is not None
|
||||
assert reg.name == 'dryer'
|
||||
|
||||
def test_dishwasher_not_misdetected_as_washer(self):
|
||||
"""Dishwasher's modelNum contains the substring 'WW' -- must not
|
||||
be misrouted by a naive substring match."""
|
||||
@@ -223,6 +261,45 @@ class TestForDeviceByModel:
|
||||
assert reg is not None
|
||||
assert reg.name == 'airconditioner'
|
||||
|
||||
def test_dehumidifier_via_dhm_token(self):
|
||||
"""Issue #88: a dehumidifier (AY18CG7500GED) shares the DA_AC_ board
|
||||
family with the room-AC models but reports no oneUiVersion and
|
||||
carries the '_DHM_' (DeHuMidifier) token instead of
|
||||
'_RAC_'/'_PRAC_'/'_WAC_'; falls back to the '_DHM_' token in
|
||||
modelNum."""
|
||||
from custom_components.localthings.registry.by_type import for_device_by_model
|
||||
reg = for_device_by_model(
|
||||
'TP1X_DA_AC_DHM_01001_0000|10253841|77000000001700000A00000000000000',
|
||||
'TP1X_DA_AC_DHM_01001_0000',
|
||||
)
|
||||
assert reg is not None
|
||||
assert reg.name == 'dehumidifier'
|
||||
|
||||
def test_water_purifier_via_waterpurifier_token(self):
|
||||
"""Issue #90: a water purifier (TP2X_WATERPURIFIER_20K) reports no
|
||||
oneUiVersion and no consumer-prefix match; falls back to the
|
||||
'WATERPURIFIER' token shared by modelNum and description."""
|
||||
from custom_components.localthings.registry.by_type import for_device_by_model
|
||||
reg = for_device_by_model(
|
||||
'TP2X_WATERPURIFIER_20K|00132341|900000000215130001060F0000020000',
|
||||
'TP2X_WATERPURIFIER_20K',
|
||||
)
|
||||
assert reg is not None
|
||||
assert reg.name == 'water_purifier'
|
||||
|
||||
def test_airconditioner_via_wac_token(self):
|
||||
"""Issue #87: a Bespoke Window AC (AW06C7155EWAZ) reports no
|
||||
oneUiVersion and a modelNum carrying the '_WAC_' (Window Air
|
||||
Conditioner) token instead of '_RAC_'/'_PRAC_'; falls back to the
|
||||
'_WAC_' token in modelNum."""
|
||||
from custom_components.localthings.registry.by_type import for_device_by_model
|
||||
reg = for_device_by_model(
|
||||
'TP1X_DA_AC_WAC_01001_0000|40460041|50030018001611020A00000000000000',
|
||||
'AW06C7155EWAZ',
|
||||
)
|
||||
assert reg is not None
|
||||
assert reg.name == 'airconditioner'
|
||||
|
||||
def test_cooktop_via_legacy_model_description(self):
|
||||
"""Older cooktops identify themselves as ARTIK051_GLOBAL_COOKTOP."""
|
||||
from custom_components.localthings.registry.by_type import for_device_by_model
|
||||
@@ -231,7 +308,7 @@ class TestForDeviceByModel:
|
||||
'ARTIK051_GLOBAL_COOKTOP',
|
||||
)
|
||||
assert reg is not None
|
||||
assert reg.name == 'cooktop'
|
||||
assert reg.name == 'gas_cooktop'
|
||||
|
||||
def test_range_hood_via_ahd_model(self):
|
||||
from custom_components.localthings.registry.by_type import for_device_by_model
|
||||
@@ -292,7 +369,7 @@ class TestForDeviceByResources:
|
||||
reg = for_device_by_resources(_load_device('cooktop'))
|
||||
|
||||
assert reg is not None
|
||||
assert reg.name == 'cooktop'
|
||||
assert reg.name == 'gas_cooktop'
|
||||
|
||||
def test_unrelated_mode_options_are_not_cooktop(self):
|
||||
from custom_components.localthings.registry.by_type import for_device_by_resources
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
"""Tests for Samsung dehumidifier support (issue #88, AY18CG7500GED).
|
||||
|
||||
HA-free like the rest of the suite: exercises the registry, discovery/
|
||||
flatten, and the target-humidity/operating-mode write contracts.
|
||||
"""
|
||||
from custom_components.localthings.registry.adapter import flatten
|
||||
from custom_components.localthings.registry.by_type import for_device_by_model
|
||||
from custom_components.localthings.registry.discovery import discover
|
||||
from custom_components.localthings.registry.entities import NumberDesc, SelectDesc
|
||||
|
||||
from tests.conftest import _load_device
|
||||
|
||||
|
||||
def _dehumidifier():
|
||||
resources = _load_device('dehumidifier')
|
||||
info = resources['/information/vs/0']
|
||||
reg = for_device_by_model(
|
||||
info['x.com.samsung.da.modelNum'], info['x.com.samsung.da.description'],
|
||||
)
|
||||
return reg, resources
|
||||
|
||||
|
||||
def _bound():
|
||||
reg, resources = _dehumidifier()
|
||||
return discover(resources, reg.capabilities, reg.pattern_capabilities), resources
|
||||
|
||||
|
||||
def _state():
|
||||
bound, resources = _bound()
|
||||
return flatten(bound, resources)
|
||||
|
||||
|
||||
def _desc(key):
|
||||
bound, _ = _bound()
|
||||
return next(b.desc for b in bound if b.desc.key == key)
|
||||
|
||||
|
||||
def test_model_resolves_to_dehumidifier_registry():
|
||||
reg, _ = _dehumidifier()
|
||||
assert reg is not None and reg.name == 'dehumidifier'
|
||||
|
||||
|
||||
def test_no_unbound_hrefs():
|
||||
"""Every resource in the issue #88 dump binds or is covered -- clears
|
||||
the coverage-gap repair."""
|
||||
reg, resources = _dehumidifier()
|
||||
unbound = []
|
||||
discover(resources, reg.capabilities, reg.pattern_capabilities, log=unbound.append)
|
||||
assert unbound == []
|
||||
|
||||
|
||||
def test_expected_state_keys_present():
|
||||
state = _state()
|
||||
for key in ('humidity', 'target_humidity', 'operating_mode', 'power_switch',
|
||||
'auto_clean', 'air_filter_status', 'mute_once'):
|
||||
assert key in state, key
|
||||
|
||||
|
||||
def test_humidity_sensor_reads_current_value():
|
||||
state = _state()
|
||||
assert state['humidity'] == 47
|
||||
|
||||
|
||||
def test_target_humidity_number_reads_desired_value():
|
||||
state = _state()
|
||||
assert state['target_humidity'] == 50
|
||||
|
||||
|
||||
def test_target_humidity_write_contract():
|
||||
desc = _desc('target_humidity')
|
||||
assert isinstance(desc, NumberDesc)
|
||||
path, body = desc.write_fn('55', {})
|
||||
assert path == ['humidity', 'vs', '0']
|
||||
assert body == {'x.com.samsung.da.desiredHumidity': '55'}
|
||||
|
||||
|
||||
def test_target_humidity_step_reads_live_increment():
|
||||
"""Step comes from the device's own `increment` field rather than a
|
||||
hardcoded constant -- see the adding-device-support skill's 'never
|
||||
hard-code the one dump's values' section."""
|
||||
desc = _desc('target_humidity')
|
||||
assert desc.step_fn({'increment': '5'}) == 5
|
||||
assert desc.step_fn({'increment': '10'}) == 10
|
||||
# No live field: falls back to a sane default rather than raising.
|
||||
assert desc.step_fn({}) == 1
|
||||
|
||||
|
||||
def test_target_humidity_has_no_hardcoded_bounds():
|
||||
"""No range field is present in any dump seen so far -- native_min/max
|
||||
are deliberately left unset (falls back to HA's own 0-100 default for a
|
||||
percentage field) rather than a bound guessed from a spec sheet."""
|
||||
desc = _desc('target_humidity')
|
||||
assert desc.native_min is None
|
||||
assert desc.native_max is None
|
||||
assert desc.native_min_fn is None
|
||||
assert desc.native_max_fn is None
|
||||
|
||||
|
||||
def test_operating_mode_select_options_come_from_live_supported_modes():
|
||||
"""Options are read live from x.com.samsung.da.supportedModes, not a
|
||||
hardcoded tuple -- so a future device with a different mode set is
|
||||
handled automatically."""
|
||||
desc = _desc('operating_mode')
|
||||
assert isinstance(desc, SelectDesc)
|
||||
assert desc.options_field == 'x.com.samsung.da.supportedModes'
|
||||
assert desc.options == ()
|
||||
|
||||
|
||||
def test_operating_mode_write_contract():
|
||||
desc = _desc('operating_mode')
|
||||
path, body = desc.write_fn('Quiet', {})
|
||||
assert path == ['mode', 'vs', '0']
|
||||
assert body == {'x.com.samsung.da.modes': ['Quiet']}
|
||||
|
||||
|
||||
def test_operating_mode_reads_first_mode():
|
||||
state = _state()
|
||||
assert state['operating_mode'] == 'Smart'
|
||||
@@ -73,6 +73,23 @@ def test_registry_reproduces_golden_state_keys_for_dryer():
|
||||
)
|
||||
|
||||
|
||||
def test_registry_reproduces_golden_state_keys_for_dryer_dve50a8600():
|
||||
"""DVE50A8600V/A3 (issue #79) -- description pairs two model numbers
|
||||
('..._DVE50A8800_8600/...'), so the true 'DV' consumer-model token sits
|
||||
one segment before the actual last segment ('8600'). The old
|
||||
last-segment-only check missed it and fell back to 'unknown'; resolved
|
||||
via _consumer_model_key scanning segments from the end."""
|
||||
from tests.conftest import _load_device
|
||||
resources = _load_device('dryer_dve50a8600')
|
||||
golden = json.loads((GOLDEN / 'dryer_dve50a8600.json').read_text())
|
||||
state_keys = _new_state_keys('dryer_dve50a8600', resources)
|
||||
assert set(state_keys) == set(golden['state_keys']), (
|
||||
f"state_keys mismatch:\n"
|
||||
f" extra: {sorted(set(state_keys) - set(golden['state_keys']))}\n"
|
||||
f" missing: {sorted(set(golden['state_keys']) - set(state_keys))}"
|
||||
)
|
||||
|
||||
|
||||
def test_registry_reproduces_golden_state_keys_for_airconditioner():
|
||||
from tests.conftest import _load_device
|
||||
resources = _load_device('airconditioner')
|
||||
@@ -85,6 +102,39 @@ def test_registry_reproduces_golden_state_keys_for_airconditioner():
|
||||
)
|
||||
|
||||
|
||||
def test_registry_reproduces_golden_state_keys_for_dehumidifier():
|
||||
"""TP1X_DA_AC_DHM_01001_0000 (issue #88, AY18CG7500GED) shares the DA_AC_
|
||||
board family with the room-AC models but carries the '_DHM_' token;
|
||||
resolved via the '_DHM_' modelNum fallback in for_device_by_model into a
|
||||
dedicated dehumidifier registry (target humidity, operating mode, reused
|
||||
AC filter/auto-clean/mute-once capabilities)."""
|
||||
from tests.conftest import _load_device
|
||||
resources = _load_device('dehumidifier')
|
||||
golden = json.loads((GOLDEN / 'dehumidifier.json').read_text())
|
||||
state_keys = _new_state_keys('dehumidifier', resources)
|
||||
assert set(state_keys) == set(golden['state_keys']), (
|
||||
f"state_keys mismatch:\n"
|
||||
f" extra: {sorted(set(state_keys) - set(golden['state_keys']))}\n"
|
||||
f" missing: {sorted(set(golden['state_keys']) - set(state_keys))}"
|
||||
)
|
||||
|
||||
|
||||
def test_registry_reproduces_golden_state_keys_for_water_purifier():
|
||||
"""TP2X_WATERPURIFIER_20K (issue #90) reports no oneUiVersion; resolved
|
||||
via the 'WATERPURIFIER' modelNum/description fallback into a dedicated
|
||||
water_purifier registry (dispense settings, sterilize/filter status,
|
||||
favorite capacity, and the three lock switches)."""
|
||||
from tests.conftest import _load_device
|
||||
resources = _load_device('water_purifier')
|
||||
golden = json.loads((GOLDEN / 'water_purifier.json').read_text())
|
||||
state_keys = _new_state_keys('water_purifier', resources)
|
||||
assert set(state_keys) == set(golden['state_keys']), (
|
||||
f"state_keys mismatch:\n"
|
||||
f" extra: {sorted(set(state_keys) - set(golden['state_keys']))}\n"
|
||||
f" missing: {sorted(set(golden['state_keys']) - set(state_keys))}"
|
||||
)
|
||||
|
||||
|
||||
def test_registry_reproduces_golden_state_keys_for_cooktop():
|
||||
from tests.conftest import _load_device
|
||||
resources = _load_device('cooktop')
|
||||
@@ -270,6 +320,23 @@ def test_registry_reproduces_golden_state_keys_for_caww_tp2():
|
||||
)
|
||||
|
||||
|
||||
def test_registry_reproduces_golden_state_keys_for_window_ac():
|
||||
"""TP1X_DA_AC_WAC_01001_0000 (issue #87, Bespoke Window AC AW06C7155EWAZ)
|
||||
reports no oneUiVersion and carries the '_WAC_' (Window Air Conditioner)
|
||||
token instead of '_RAC_'/'_PRAC_'; resolved via the '_WAC_' modelNum
|
||||
fallback in for_device_by_model. Otherwise binds cleanly against the
|
||||
existing airconditioner registry with zero unbound hrefs."""
|
||||
from tests.conftest import _load_device
|
||||
resources = _load_device('airconditioner_window_ac')
|
||||
golden = json.loads((GOLDEN / 'airconditioner_window_ac.json').read_text())
|
||||
state_keys = _new_state_keys('airconditioner_window_ac', resources)
|
||||
assert set(state_keys) == set(golden['state_keys']), (
|
||||
f"state_keys mismatch:\n"
|
||||
f" extra: {sorted(set(state_keys) - set(golden['state_keys']))}\n"
|
||||
f" missing: {sorted(set(golden['state_keys']) - set(state_keys))}"
|
||||
)
|
||||
|
||||
|
||||
def test_registry_reproduces_golden_state_keys_for_tp1x_rac():
|
||||
"""TP1X_DA-AC-RAC-01001_0000 (issue #38) -- fuller RAC board with display
|
||||
light, self-check, mute-once, and a current-limit setting."""
|
||||
|
||||
@@ -168,3 +168,4 @@ def test_all_entity_state_translation_keys_are_lowercase():
|
||||
for translation in platform.values():
|
||||
for state_key in translation.get("state", {}):
|
||||
assert state_key == state_key.lower()
|
||||
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
"""Tests for Samsung water-purifier support (issue #90, TP2X_WATERPURIFIER_20K).
|
||||
|
||||
HA-free like the rest of the suite: exercises the registry, discovery/
|
||||
flatten, and the write contracts.
|
||||
"""
|
||||
from custom_components.localthings.registry.adapter import flatten
|
||||
from custom_components.localthings.registry.by_type import for_device_by_model
|
||||
from custom_components.localthings.registry.discovery import discover
|
||||
from custom_components.localthings.registry.entities import NumberDesc
|
||||
|
||||
from tests.conftest import _load_device
|
||||
|
||||
|
||||
def _water_purifier():
|
||||
resources = _load_device('water_purifier')
|
||||
info = resources['/information/vs/0']
|
||||
reg = for_device_by_model(
|
||||
info['x.com.samsung.da.modelNum'], info['x.com.samsung.da.description'],
|
||||
)
|
||||
return reg, resources
|
||||
|
||||
|
||||
def _bound():
|
||||
reg, resources = _water_purifier()
|
||||
return discover(resources, reg.capabilities, reg.pattern_capabilities), resources
|
||||
|
||||
|
||||
def _state():
|
||||
bound, resources = _bound()
|
||||
return flatten(bound, resources)
|
||||
|
||||
|
||||
def _desc(key):
|
||||
bound, _ = _bound()
|
||||
return next(b.desc for b in bound if b.desc.key == key)
|
||||
|
||||
|
||||
def test_model_resolves_to_water_purifier_registry():
|
||||
reg, _ = _water_purifier()
|
||||
assert reg is not None and reg.name == 'water_purifier'
|
||||
|
||||
|
||||
def test_no_unbound_hrefs():
|
||||
"""Every resource in the issue #90 dump binds or is covered -- clears
|
||||
the coverage-gap repair."""
|
||||
reg, resources = _water_purifier()
|
||||
unbound = []
|
||||
discover(resources, reg.capabilities, reg.pattern_capabilities, log=unbound.append)
|
||||
assert unbound == []
|
||||
|
||||
|
||||
def test_expected_state_keys_present():
|
||||
state = _state()
|
||||
for key in ('dispense_type', 'hot_water_temperature', 'dispense_capacity',
|
||||
'pouring', 'waterpurifier_status', 'filter_usage', 'filter_status',
|
||||
'hotwater_lock', 'coldwater_lock', 'buzz_lock'):
|
||||
assert key in state, key
|
||||
|
||||
|
||||
def test_dispense_type_options_come_from_live_supported_types():
|
||||
desc = _desc('dispense_type')
|
||||
assert desc.options_field == 'x.com.samsung.da.supportedTypes'
|
||||
|
||||
|
||||
def test_dispense_type_write_contract():
|
||||
desc = _desc('dispense_type')
|
||||
path, body = desc.write_fn('hotwater', {})
|
||||
assert path == ['setting', 'waterpurifier', 'vs', '0']
|
||||
assert body == {'x.com.samsung.da.desiredType': 'hotwater'}
|
||||
|
||||
|
||||
def test_hot_water_temperature_is_a_select_not_a_number():
|
||||
"""Only a handful of discrete temperatures are selectable (not a
|
||||
continuous range) -- confirmed by supportedHotTemperatures being a short
|
||||
enumerated list, not a [min, max] range field."""
|
||||
desc = _desc('hot_water_temperature')
|
||||
assert desc.options_field == 'x.com.samsung.da.supportedHotTemperatures'
|
||||
|
||||
|
||||
def test_dispense_capacity_bounds_come_live_not_hardcoded():
|
||||
"""Bounds and step come from the device's own desiredCapacityRange/
|
||||
capacityResolution fields, not a hardcoded constant -- see the
|
||||
adding-device-support skill's 'never hard-code the one dump's values'
|
||||
section."""
|
||||
desc = _desc('dispense_capacity')
|
||||
assert isinstance(desc, NumberDesc)
|
||||
assert desc.native_min is None
|
||||
assert desc.native_max is None
|
||||
assert desc.range_field == 'x.com.samsung.da.desiredCapacityRange'
|
||||
rep = {'x.com.samsung.da.desiredCapacityRange': ['50', '2000'],
|
||||
'x.com.samsung.da.capacityResolution': '10'}
|
||||
assert desc.step_fn(rep) == 10
|
||||
|
||||
|
||||
def test_dispense_capacity_write_contract():
|
||||
desc = _desc('dispense_capacity')
|
||||
path, body = desc.write_fn('550', {})
|
||||
assert path == ['setting', 'waterpurifier', 'vs', '0']
|
||||
assert body == {'x.com.samsung.da.desiredCapacity': '550'}
|
||||
|
||||
|
||||
def test_lock_switches_read_unlocked_as_off():
|
||||
state = _state()
|
||||
assert state['hotwater_lock'] is False
|
||||
assert state['coldwater_lock'] is False
|
||||
assert state['buzz_lock'] is False
|
||||
|
||||
|
||||
def test_lock_switch_write_contracts():
|
||||
hot = _desc('hotwater_lock')
|
||||
cold = _desc('coldwater_lock')
|
||||
buzz = _desc('buzz_lock')
|
||||
assert hot.write_fn('On', {}) == (
|
||||
['status', 'lock', 'vs', '0'], {'x.com.samsung.da.hotwaterLock': 'Locked'})
|
||||
assert cold.write_fn('Off', {}) == (
|
||||
['status', 'lock', 'vs', '0'], {'x.com.samsung.da.coldwaterLock': 'Unlocked'})
|
||||
assert buzz.write_fn('On', {}) == (
|
||||
['status', 'lock', 'vs', '0'], {'x.com.samsung.da.buzzLock': 'Locked'})
|
||||
|
||||
|
||||
def test_favorite_capacity_options_come_from_live_capacity_list():
|
||||
desc = _desc('favorite_capacity')
|
||||
assert desc.options_field == 'x.com.samsung.da.capacityList'
|
||||
|
||||
|
||||
def test_sterilize_timestamps_parsed_as_utc():
|
||||
state = _state()
|
||||
assert state['sterilize_last_time'].tzinfo is not None
|
||||
assert state['sterilize_plan_time'].tzinfo is not None
|
||||
|
||||
|
||||
def test_mode_hrefs_are_ignored_not_guessed():
|
||||
"""/mode/vs/0's supportedModes carries a single opaque wizard token and
|
||||
modes reports an unrelated value not even in supportedModes -- internal
|
||||
plumbing, left unmodeled per the 'don't guess' rule rather than exposed
|
||||
as a nonsensical select."""
|
||||
from custom_components.localthings.registry.capabilities import water_purifier
|
||||
ignored_hrefs = {cap.href for cap in water_purifier.COVERAGE}
|
||||
assert '/mode/vs/0' in ignored_hrefs
|
||||
assert '/automation/waterpurifier/vs/0' in ignored_hrefs
|
||||
Reference in New Issue
Block a user