Compare commits
35
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1493467c63 | ||
|
|
61ee2972dc | ||
|
|
3d5263d792 | ||
|
|
d6639c99d4 | ||
|
|
5d9b470d7e | ||
|
|
9800cd0aa8 | ||
|
|
3469f57080 | ||
|
|
f54daf3608 | ||
|
|
e49b61e01e | ||
|
|
dbe70ffc5b | ||
|
|
7d011bfe89 | ||
|
|
9199fb7711 | ||
|
|
6611f85958 | ||
|
|
8470853fb6 | ||
|
|
e942c822a6 | ||
|
|
4a376a98aa | ||
|
|
bc9d4f4cf5 | ||
|
|
10f2aac0bd | ||
|
|
0cc9d8e511 | ||
|
|
b586277031 | ||
|
|
2a0a9f32ee | ||
|
|
8dadea6796 | ||
|
|
2cd93619a4 | ||
|
|
5a9a002a7f | ||
|
|
0715eb0829 | ||
|
|
43542b146f | ||
|
|
9a899080fa | ||
|
|
88e630b54f | ||
|
|
70a7f861c3 | ||
|
|
ff034905a2 | ||
|
|
ae8bb0b621 | ||
|
|
3688cf24a0 | ||
|
|
d917d757e9 | ||
|
|
0503bda0f3 | ||
|
|
6f53b3043b |
@@ -41,6 +41,7 @@ from .registry.capabilities.airconditioner import (
|
||||
HREF_TEMP_CURRENT as TEMP_CURRENT_HREF,
|
||||
HREF_TEMP_DESIRED as TEMP_DESIRED_HREF,
|
||||
HREF_TEMP_CONTROL as TEMP_CONTROL_HREF,
|
||||
HREF_TEMPS_VS as TEMPS_VS_HREF,
|
||||
HREF_WIND_STRENGTH as WIND_STRENGTH_HREF,
|
||||
HREF_WIND_DIRECTION as WIND_DIRECTION_HREF,
|
||||
HREF_CONVENIENT as CONVENIENT_HREF,
|
||||
@@ -125,6 +126,22 @@ def _num(value):
|
||||
return None
|
||||
|
||||
|
||||
def _temps_vs_item(rep: dict) -> dict:
|
||||
"""First item of the vendor `/temperatures/vs/0` items[] array.
|
||||
|
||||
Newer AC firmware (Tizen Lite, oneUiVersion "7.0 Air conditioner", e.g.
|
||||
model TP1X_DA-AC-RAC-01011) does NOT expose the OCF-standard
|
||||
/temperature/current/0 + /temperature/desired/0 pair; it reports current
|
||||
and target under a single `/temperatures/vs/0` resource whose
|
||||
`x.com.samsung.da.items[0]` carries current/desired/minimum/maximum/
|
||||
increment/unit. Returns {} when absent, so callers fall through cleanly.
|
||||
"""
|
||||
items = rep.get('x.com.samsung.da.items')
|
||||
if isinstance(items, (list, tuple)) and items and isinstance(items[0], dict):
|
||||
return items[0]
|
||||
return {}
|
||||
|
||||
|
||||
class LocalThingsClimate(LocalThingsEntity, ClimateEntity):
|
||||
"""Composite climate entity for a Samsung air conditioner."""
|
||||
|
||||
@@ -172,24 +189,41 @@ class LocalThingsClimate(LocalThingsEntity, ClimateEntity):
|
||||
|
||||
# -- temperature --------------------------------------------------------
|
||||
|
||||
def _temps_vs(self) -> dict:
|
||||
"""Vendor `/temperatures/vs/0` items[0] (empty {} when absent)."""
|
||||
return _temps_vs_item(self._rep(TEMPS_VS_HREF))
|
||||
|
||||
@property
|
||||
def temperature_unit(self) -> str:
|
||||
raw = self._rep(TEMP_DESIRED_HREF).get('units')
|
||||
if raw is None:
|
||||
raw = self._temps_vs().get('x.com.samsung.da.unit')
|
||||
return (UnitOfTemperature.FAHRENHEIT
|
||||
if normalize_temp_unit(raw, '°C') == '°F'
|
||||
else UnitOfTemperature.CELSIUS)
|
||||
|
||||
@property
|
||||
def current_temperature(self):
|
||||
return _num(self._rep(TEMP_CURRENT_HREF).get('temperature'))
|
||||
v = _num(self._rep(TEMP_CURRENT_HREF).get('temperature'))
|
||||
if v is None:
|
||||
v = _num(self._temps_vs().get('x.com.samsung.da.current'))
|
||||
return v
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
return _num(self._rep(TEMP_DESIRED_HREF).get('temperature'))
|
||||
v = _num(self._rep(TEMP_DESIRED_HREF).get('temperature'))
|
||||
if v is None:
|
||||
v = _num(self._temps_vs().get('x.com.samsung.da.desired'))
|
||||
return v
|
||||
|
||||
def _range(self) -> list | None:
|
||||
r = self._rep(TEMP_DESIRED_HREF).get('range')
|
||||
return r if (isinstance(r, (list, tuple)) and len(r) == 2) else None
|
||||
if isinstance(r, (list, tuple)) and len(r) == 2:
|
||||
return r
|
||||
item = self._temps_vs()
|
||||
lo = _num(item.get('x.com.samsung.da.minimum'))
|
||||
hi = _num(item.get('x.com.samsung.da.maximum'))
|
||||
return [lo, hi] if (lo is not None and hi is not None) else None
|
||||
|
||||
@property
|
||||
def min_temp(self) -> float:
|
||||
@@ -203,7 +237,10 @@ class LocalThingsClimate(LocalThingsEntity, ClimateEntity):
|
||||
|
||||
@property
|
||||
def target_temperature_step(self) -> float:
|
||||
return _num(self._rep(TEMP_CONTROL_HREF).get('increment')) or 1.0
|
||||
return (_num(self._rep(TEMP_CONTROL_HREF).get('increment'))
|
||||
or _num(self._rep(TEMP_CONTROL_HREF).get('x.com.samsung.da.increment'))
|
||||
or _num(self._temps_vs().get('x.com.samsung.da.increment'))
|
||||
or 1.0)
|
||||
|
||||
# -- hvac mode ----------------------------------------------------------
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import cbor2
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import ServiceValidationError
|
||||
from homeassistant.helpers import issue_registry as ir
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
@@ -509,13 +510,23 @@ class LocalThingsCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
|
||||
async def async_send_command(self, bound_entity: BoundEntity,
|
||||
payload: Any) -> None:
|
||||
"""Write a value to the device. Fire-and-forget style."""
|
||||
"""Write a value to the device. Fire-and-forget style.
|
||||
|
||||
A description-level validate_fn (currently SwitchDesc only) runs
|
||||
here rather than per-platform, so rejecting a write with a
|
||||
user-facing message -- as opposed to write_fn's silent no-op below
|
||||
-- is available to every platform for free."""
|
||||
desc = bound_entity.desc
|
||||
write_fn = getattr(desc, 'write_fn', None)
|
||||
if write_fn is None:
|
||||
return
|
||||
href = bound_entity.href
|
||||
rep = self._cache.get(href or '') or {}
|
||||
validate_fn = getattr(desc, 'validate_fn', None)
|
||||
if validate_fn is not None:
|
||||
error = validate_fn(payload, rep, self._cache.snapshot())
|
||||
if error:
|
||||
raise ServiceValidationError(error)
|
||||
result = write_fn(payload, rep, href)
|
||||
if result is None:
|
||||
self._log.warning("write_fn rejected payload %r for %s", payload, href)
|
||||
|
||||
@@ -12,5 +12,5 @@
|
||||
"pyOpenSSL>=23.0",
|
||||
"smartthings-local>=0.1.0"
|
||||
],
|
||||
"version": "0.6.0"
|
||||
"version": "0.9.0"
|
||||
}
|
||||
|
||||
@@ -68,6 +68,8 @@ def for_device(one_ui_version: str) -> Optional[DeviceRegistry]:
|
||||
_CONSUMER_PREFIX_TO_KEY: dict[str, str] = {
|
||||
'WW': 'washer',
|
||||
'WD': 'washer',
|
||||
'WF': 'washer',
|
||||
'WV': 'washer', # FlexWash twin units (e.g. WV55M9600AW) -- issue #19
|
||||
'DV': 'dryer',
|
||||
'DW': 'dishwasher',
|
||||
}
|
||||
@@ -94,4 +96,9 @@ def for_device_by_model(model_num: str, description: str) -> Optional[DeviceRegi
|
||||
# a modelNum carrying the '_PRAC_' (Package Room Air Conditioner) token.
|
||||
if key is None and '_PRAC_' in (model_num or ''):
|
||||
key = 'airconditioner'
|
||||
# Older/simpler RAC boards (e.g. TP2X_RAC_20K, issue #37) use the plain
|
||||
# '_RAC_' token instead -- distinct from '_PRAC_' above (no overlap: the
|
||||
# 'P' sits between the underscore and 'RAC' in that token).
|
||||
if key is None and '_RAC_' in (model_num or ''):
|
||||
key = 'airconditioner'
|
||||
return _REGISTRY_BY_KEY.get(key) if key else None
|
||||
|
||||
@@ -3,27 +3,28 @@
|
||||
The first device whose core controls surface as a single composite HA `climate`
|
||||
entity (see capabilities/airconditioner.py and climate.py). Power/mode/temp/wind
|
||||
are consumed by that entity rather than exposed as separate switches/selects, so
|
||||
this registry deliberately does NOT include the common POWER caps -- on/off is
|
||||
the climate entity's HVACMode.OFF / TURN_ON/OFF.
|
||||
this registry includes *common.UNIVERSAL but deliberately NOT common.POWER --
|
||||
on/off is the climate entity's HVACMode.OFF / TURN_ON/OFF. See common.POWER's
|
||||
own comment in capabilities/common.py for why it's excluded.
|
||||
|
||||
Reuses common.ALARMS + common.ENERGY_METER, fridge.FIRMWARE_UPDATE (as every
|
||||
registry does), and dishwasher.DIAGNOSIS for /diagnosis/vs/0.
|
||||
Reuses dishwasher.DIAGNOSIS for /diagnosis/vs/0.
|
||||
"""
|
||||
from ..capabilities import airconditioner, common, dishwasher, fridge, ignored
|
||||
from ..capabilities import airconditioner, common, dishwasher, ignored
|
||||
from ._base import DeviceRegistry, _build
|
||||
|
||||
REGISTRY = DeviceRegistry(
|
||||
name='airconditioner',
|
||||
capabilities=_build([
|
||||
*ignored.IGNORED,
|
||||
common.ALARMS,
|
||||
common.ENERGY_METER,
|
||||
fridge.FIRMWARE_UPDATE,
|
||||
*common.UNIVERSAL,
|
||||
dishwasher.DIAGNOSIS,
|
||||
airconditioner.CLIMATE,
|
||||
airconditioner.AIR_PURIFY,
|
||||
airconditioner.AUTO_CLEAN,
|
||||
airconditioner.AIR_FILTER,
|
||||
airconditioner.DISPLAY_LIGHT,
|
||||
airconditioner.MUTE_ONCE,
|
||||
airconditioner.CURRENT_LIMIT,
|
||||
*airconditioner.COVERAGE,
|
||||
]),
|
||||
)
|
||||
|
||||
@@ -1,19 +1,13 @@
|
||||
"""Dishwasher device registry."""
|
||||
from ..capabilities import common, dishwasher, fridge, ignored, laundry, operational
|
||||
from ..capabilities import common, dishwasher, ignored, laundry, operational
|
||||
from ._base import DeviceRegistry, _build
|
||||
|
||||
REGISTRY = DeviceRegistry(
|
||||
name='dishwasher',
|
||||
capabilities=_build([
|
||||
*ignored.IGNORED,
|
||||
common.POWER_GENERIC,
|
||||
common.POWER_VS_FALLBACK,
|
||||
common.KIDS_LOCK_GENERIC,
|
||||
common.KIDS_LOCK_VS_FALLBACK,
|
||||
common.REMOTE_CONTROL_GENERIC,
|
||||
common.REMOTE_CONTROL_VS_FALLBACK,
|
||||
common.ALARMS,
|
||||
common.ENERGY_METER,
|
||||
*common.UNIVERSAL,
|
||||
*common.POWER,
|
||||
common.WATER_METER,
|
||||
common.WATER_FILTER,
|
||||
operational.OPERATIONAL_STATE,
|
||||
@@ -25,6 +19,5 @@ REGISTRY = DeviceRegistry(
|
||||
laundry.DOOR_LED,
|
||||
laundry.SOUND_MODE,
|
||||
laundry.SOUND_VOLUME,
|
||||
fridge.FIRMWARE_UPDATE,
|
||||
]),
|
||||
)
|
||||
|
||||
@@ -9,21 +9,15 @@ objects the washer registry uses -- washer and dryer expose the same
|
||||
DA_WM_-family surface, so they stay consistent instead of each carrying a
|
||||
bespoke variant.
|
||||
"""
|
||||
from ..capabilities import common, dryer, fridge, ignored, laundry, operational
|
||||
from ..capabilities import common, dryer, ignored, laundry, operational
|
||||
from ._base import DeviceRegistry, _build
|
||||
|
||||
REGISTRY = DeviceRegistry(
|
||||
name='dryer',
|
||||
capabilities=_build([
|
||||
*ignored.IGNORED,
|
||||
common.POWER_GENERIC,
|
||||
common.POWER_VS_FALLBACK,
|
||||
common.KIDS_LOCK_GENERIC,
|
||||
common.KIDS_LOCK_VS_FALLBACK,
|
||||
common.REMOTE_CONTROL_GENERIC,
|
||||
common.REMOTE_CONTROL_VS_FALLBACK,
|
||||
common.ALARMS,
|
||||
common.ENERGY_METER,
|
||||
*common.UNIVERSAL,
|
||||
*common.POWER,
|
||||
operational.OPERATIONAL_STATE,
|
||||
laundry.DOOR_LED,
|
||||
laundry.SOUND_MODE,
|
||||
@@ -32,6 +26,5 @@ REGISTRY = DeviceRegistry(
|
||||
dryer.DRYER_SETTINGS,
|
||||
dryer.DRYER_COURSE,
|
||||
dryer.DRYER_DIAGNOSIS,
|
||||
fridge.FIRMWARE_UPDATE,
|
||||
]),
|
||||
)
|
||||
|
||||
@@ -1,24 +1,18 @@
|
||||
"""Oven device registry."""
|
||||
from ..capabilities import common, fridge, ignored, oven
|
||||
from ..capabilities import common, ignored, oven
|
||||
from ._base import DeviceRegistry, _build
|
||||
|
||||
REGISTRY = DeviceRegistry(
|
||||
name='oven',
|
||||
capabilities=_build([
|
||||
*ignored.IGNORED,
|
||||
common.POWER_GENERIC,
|
||||
common.POWER_VS_FALLBACK,
|
||||
common.KIDS_LOCK_GENERIC,
|
||||
common.KIDS_LOCK_VS_FALLBACK,
|
||||
common.REMOTE_CONTROL_GENERIC,
|
||||
common.REMOTE_CONTROL_VS_FALLBACK,
|
||||
common.ALARMS,
|
||||
*common.UNIVERSAL,
|
||||
*common.POWER,
|
||||
oven.OVEN_CAVITY,
|
||||
oven.OVEN_SETPOINT,
|
||||
oven.OVEN_MODE,
|
||||
oven.OVEN_OPERATIONAL_STATE,
|
||||
oven.OVEN_DOOR,
|
||||
oven.OVEN_CONNECTED,
|
||||
fridge.FIRMWARE_UPDATE,
|
||||
]),
|
||||
)
|
||||
|
||||
@@ -1,22 +1,17 @@
|
||||
"""Refrigerator device registry."""
|
||||
from ..capabilities import common, fridge, ignored
|
||||
from ..capabilities import common, dishwasher, fridge, ignored
|
||||
from ._base import DeviceRegistry, _build
|
||||
|
||||
REGISTRY = DeviceRegistry(
|
||||
name='refrigerator',
|
||||
capabilities=_build([
|
||||
*ignored.IGNORED,
|
||||
common.POWER_GENERIC,
|
||||
common.POWER_VS_FALLBACK,
|
||||
*common.UNIVERSAL,
|
||||
*common.POWER,
|
||||
fridge.STATUS_LOCK,
|
||||
fridge.DOOR_ALERT,
|
||||
common.KIDS_LOCK_GENERIC,
|
||||
common.KIDS_LOCK_VS_FALLBACK,
|
||||
common.REMOTE_CONTROL_GENERIC,
|
||||
common.REMOTE_CONTROL_VS_FALLBACK,
|
||||
common.ALARMS,
|
||||
common.ENERGY_METER,
|
||||
common.WATER_FILTER,
|
||||
dishwasher.DIAGNOSIS,
|
||||
fridge.ICEMAKER_NIGHTTIME,
|
||||
fridge.FLEX_ZONE,
|
||||
fridge.REFRIGERATION,
|
||||
@@ -26,13 +21,14 @@ REGISTRY = DeviceRegistry(
|
||||
fridge.CABINET_LIGHT_ENHANCED,
|
||||
fridge.SABBATH,
|
||||
fridge.BEVERAGE_ZONE,
|
||||
fridge.FIRMWARE_UPDATE,
|
||||
fridge.PANTRY_ZONE,
|
||||
fridge.DEFROST_DELAY,
|
||||
fridge.DEFROST_DELAY_NATIVE_DUPLICATE,
|
||||
fridge.DEFROST_BLOCK_STATUS,
|
||||
fridge.SELF_CHECK,
|
||||
fridge.DOORS_FALLBACK,
|
||||
fridge.TEMPERATURES_FALLBACK,
|
||||
fridge.ICEMAKER_STATUS_FALLBACK,
|
||||
fridge.ICEMAKER_STATUS_NATIVE_DUPLICATE,
|
||||
fridge.REFRIGERATION_FALLBACK,
|
||||
]),
|
||||
pattern_capabilities=[
|
||||
|
||||
@@ -1,26 +1,19 @@
|
||||
"""Washer device registry."""
|
||||
from ..capabilities import common, dishwasher, fridge, ignored, laundry, operational, washer
|
||||
from ..capabilities import common, dishwasher, ignored, laundry, operational, washer
|
||||
from ._base import DeviceRegistry, _build
|
||||
|
||||
REGISTRY = DeviceRegistry(
|
||||
name='washer',
|
||||
capabilities=_build([
|
||||
*ignored.IGNORED,
|
||||
common.POWER_GENERIC,
|
||||
common.POWER_VS_FALLBACK,
|
||||
common.KIDS_LOCK_GENERIC,
|
||||
common.KIDS_LOCK_VS_FALLBACK,
|
||||
common.REMOTE_CONTROL_GENERIC,
|
||||
common.REMOTE_CONTROL_VS_FALLBACK,
|
||||
*common.UNIVERSAL,
|
||||
*common.POWER,
|
||||
washer.WASHER_SETTINGS,
|
||||
washer.WASHER_COURSE,
|
||||
laundry.BUZZER_SOUND,
|
||||
laundry.JOB_BEGINNING_STATUS,
|
||||
common.ALARMS,
|
||||
common.ENERGY_METER,
|
||||
common.WATER_METER,
|
||||
operational.OPERATIONAL_STATE,
|
||||
dishwasher.DIAGNOSIS,
|
||||
fridge.FIRMWARE_UPDATE,
|
||||
]),
|
||||
)
|
||||
|
||||
@@ -15,7 +15,7 @@ different schema (see capabilities/__init__.py). They live only in the AC
|
||||
by_type registry.
|
||||
"""
|
||||
from ..capability import Capability
|
||||
from ..entities import ClimateDesc, SensorDesc, SwitchDesc
|
||||
from ..entities import BinarySensorDesc, ClimateDesc, SensorDesc, SwitchDesc
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Canonical AC resource hrefs. The climate entity (climate.py) binds the
|
||||
@@ -34,10 +34,12 @@ HREF_TEMP_CONTROL = '/temperature/control/vs/0' # target_temperature_step
|
||||
HREF_WIND_STRENGTH = '/wind/strength/vs/0' # fan_mode
|
||||
HREF_WIND_DIRECTION = '/wind/direction/vs/0' # swing_mode
|
||||
HREF_CONVENIENT = '/mode/convenient/vs/0' # preset_mode
|
||||
HREF_TEMPS_VS = '/temperatures/vs/0' # vendor temp fallback (items[] array)
|
||||
|
||||
CLIMATE_CONSUMED_HREFS = [
|
||||
HREF_POWER, HREF_POWER_VS, HREF_TEMP_CURRENT, HREF_TEMP_DESIRED,
|
||||
HREF_TEMP_CONTROL, HREF_WIND_STRENGTH, HREF_WIND_DIRECTION, HREF_CONVENIENT,
|
||||
HREF_TEMP_CONTROL, HREF_TEMPS_VS, HREF_WIND_STRENGTH, HREF_WIND_DIRECTION,
|
||||
HREF_CONVENIENT,
|
||||
]
|
||||
|
||||
|
||||
@@ -146,6 +148,57 @@ AIR_FILTER = Capability(
|
||||
),
|
||||
)
|
||||
|
||||
DISPLAY_LIGHT = Capability(
|
||||
href='/light/vs/0',
|
||||
poll_tier='cold',
|
||||
entities=(
|
||||
SwitchDesc(key='display_light', field='mode',
|
||||
name='Display light', icon='mdi:led-on',
|
||||
entity_category='config',
|
||||
value_fn=lambda v: v == 'On',
|
||||
write_fn=lambda p, rep, href=None: (
|
||||
['light', 'vs', '0'],
|
||||
{'mode': 'On' if p == 'On' else 'Off'})),
|
||||
),
|
||||
)
|
||||
|
||||
# Confirmed against issue #38's dump (TP1X_DA-AC-RAC-01001_0000): a single
|
||||
# boolean field, no vendor prefix, mirroring the On/Off convention used
|
||||
# throughout the rest of this API.
|
||||
MUTE_ONCE = Capability(
|
||||
href='/option/muteonce/vs/0',
|
||||
poll_tier='warm',
|
||||
entities=(
|
||||
SwitchDesc(key='mute_once', field='muteonce',
|
||||
name='Mute once', icon='mdi:volume-mute',
|
||||
entity_category='config',
|
||||
value_fn=lambda v: v == 'On',
|
||||
write_fn=lambda p, rep, href=None: (
|
||||
['option', 'muteonce', 'vs', '0'],
|
||||
{'muteonce': 'On' if p == 'On' else 'Off'})),
|
||||
),
|
||||
)
|
||||
|
||||
# Circuit-breaker current-limit setting (issue #38, TP1X board): `operation`
|
||||
# toggles the limiter and `modes` picks a level out of `supportedModes`
|
||||
# (seen as '3'..'9'). No vendor field-name prefix and no unit/label in the
|
||||
# dump to confirm what the levels mean (amps vs. an abstract tier) --
|
||||
# exposed read-only per the 'don't guess' rule rather than risking an
|
||||
# unverified write to live HVAC hardware.
|
||||
CURRENT_LIMIT = Capability(
|
||||
href='/electriccurrent/vs/0',
|
||||
poll_tier='cold',
|
||||
entities=(
|
||||
BinarySensorDesc(key='current_limit_enabled', field='operation',
|
||||
name='Current limit enabled', icon='mdi:current-ac',
|
||||
entity_category='diagnostic',
|
||||
value_fn=lambda v: v == 'On'),
|
||||
SensorDesc(key='current_limit_level', field='modes',
|
||||
name='Current limit level', icon='mdi:current-ac',
|
||||
entity_category='diagnostic'),
|
||||
),
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# AC-scoped coverage: the CLIMATE_CONSUMED_HREFS above (read by the climate
|
||||
# entity) plus vendor duplicates / all-zero-ambiguous / plumbing resources.
|
||||
@@ -155,8 +208,6 @@ AIR_FILTER = Capability(
|
||||
# bound so discover() reports no coverage gap.
|
||||
# ---------------------------------------------------------------------------
|
||||
_AC_IGNORED = [
|
||||
# Vendor superset that duplicates the OCF /temperature/current+desired pair.
|
||||
'/temperatures/vs/0',
|
||||
# All-zero and ambiguously encoded on this model (2-value arrays); the
|
||||
# 'don't guess' rule -- leave unmodeled rather than invent entities.
|
||||
'/sensors/vs/0',
|
||||
@@ -164,6 +215,24 @@ _AC_IGNORED = [
|
||||
'/humidity/vs/0',
|
||||
# Presence-personalization plumbing (empty item list here).
|
||||
'/personality/presence/vs/0',
|
||||
# --- TP1X/TP2X-class housekeeping / opaque blobs. These carry no
|
||||
# user-actionable state or no documented write contract, so per the
|
||||
# 'don't guess' rule they are ignored rather than modeled.
|
||||
# /option/muteonce/vs/0 and /selfcheck/vs/0 are deliberately NOT here --
|
||||
# see MUTE_ONCE above and common.SELF_CHECK (via common.UNIVERSAL) in
|
||||
# the by_type registry, both of which have a confirmed, cleanly
|
||||
# modelable contract.
|
||||
'/airlevelcheck/vs/0', # periodic air-quality sensing scheduler plumbing
|
||||
'/aisleep/vs/0', # AI-sleep feedback state (no actionable control)
|
||||
'/availablecontrolsets/vs/0', # opaque hex-encoded control-set bitmap
|
||||
'/da/softreset/vs/0', # soft-reset trigger plumbing
|
||||
'/keepnormalstate/vs/0', # internal keep-normal flag
|
||||
'/mds/absencemonitoring/vs/0', # motion-detection sensor plumbing (empty here)
|
||||
'/mds/absencestate/vs/0', # motion-detection state (empty here)
|
||||
'/remotedatacontrol/vs/0', # remote data-control session status
|
||||
'/remotetemperature/vs/0', # external temp-sensor feed (unset on this unit)
|
||||
'/reserverulesets/vs/0', # opaque hex-encoded schedule reservation blob
|
||||
'/welcome/temperature/vs/0', # welcome-cooling plumbing
|
||||
]
|
||||
|
||||
# Built as bare no-entity caps; folded into the AC registry (not global).
|
||||
|
||||
@@ -11,7 +11,9 @@ against live device dumps:
|
||||
/filter/waterfilter/vs/0 -> x.com.samsung.da.filterUsage / filterStatus
|
||||
"""
|
||||
from ..capability import Capability
|
||||
from ..entities import BinarySensorDesc, SensorDesc, SwitchDesc
|
||||
from ..entities import (
|
||||
BinarySensorDesc, ButtonDesc, SelectDesc, SensorDesc, SwitchDesc,
|
||||
)
|
||||
|
||||
|
||||
def _num(v):
|
||||
@@ -175,6 +177,38 @@ ENERGY_METER = Capability(
|
||||
state_class='total_increasing', unit='kWh', value_fn=wh_to_kwh,
|
||||
exists_fn=lambda rep, resources: (
|
||||
not rep or 'x.com.samsung.da.cumulativePower' in rep)),
|
||||
# cumulativeConsumption is a second, independently-varying running
|
||||
# total alongside cumulativePower -- some fridges (issue #26) report
|
||||
# both. Self-gates off where only cumulativePower is present. `not
|
||||
# rep or` keeps the same empty-{} stub carve-out as power_watts/
|
||||
# energy_kwh above -- without it, an exists_fn permanently drops the
|
||||
# entity if setup happens to land on a not-yet-fetched stub.
|
||||
SensorDesc(key='power_energy_kwh', field='x.com.samsung.da.cumulativeConsumption',
|
||||
name='Power energy', device_class='energy',
|
||||
state_class='total_increasing', unit='kWh', value_fn=wh_to_kwh,
|
||||
exists_fn=lambda rep, resources: (
|
||||
not rep or 'x.com.samsung.da.cumulativeConsumption' in rep)),
|
||||
# AI Energy Mode's lifetime savings estimate vs. an unoptimized
|
||||
# baseline -- present on some models (e.g. TP1X_REF_21K, issue #21/
|
||||
# #27) and absent on others (issue #20/#26), unlike cumulativePower.
|
||||
SensorDesc(key='energy_saved_kwh', field='x.com.samsung.da.cumulativeSavedPower',
|
||||
name='Energy saved', device_class='energy',
|
||||
state_class='total_increasing', unit='kWh', value_fn=wh_to_kwh,
|
||||
exists_fn=lambda rep, resources: (
|
||||
not rep or 'x.com.samsung.da.cumulativeSavedPower' in rep)),
|
||||
# Monthly billing-cycle totals -- the completed prior month and the
|
||||
# in-progress current month. Not ever-increasing (each resets at
|
||||
# month boundary), so no state_class.
|
||||
SensorDesc(key='energy_last_month_kwh', field='x.com.samsung.da.monthlyConsumption',
|
||||
name='Energy (last month)', device_class='energy',
|
||||
unit='kWh', value_fn=wh_to_kwh,
|
||||
exists_fn=lambda rep, resources: (
|
||||
not rep or 'x.com.samsung.da.monthlyConsumption' in rep)),
|
||||
SensorDesc(key='energy_this_month_kwh', field='x.com.samsung.da.thismonthlyConsumption',
|
||||
name='Energy (this month)', device_class='energy',
|
||||
unit='kWh', value_fn=wh_to_kwh,
|
||||
exists_fn=lambda rep, resources: (
|
||||
not rep or 'x.com.samsung.da.thismonthlyConsumption' in rep)),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -199,3 +233,162 @@ WATER_FILTER = Capability(
|
||||
name='Filter status', icon='mdi:filter-check'),
|
||||
),
|
||||
)
|
||||
|
||||
# AI energy-saving level -- '0' is off, and supportedAiLevel lists the
|
||||
# additional level(s) the device offers ('1' meaning just "on" on most
|
||||
# hardware, but multi-level boards have been reported). Verified cross-family:
|
||||
# fridge (issue #21) and washer (issue #40) both expose this href.
|
||||
#
|
||||
# supportedAiLevel is a single-entry list on most captured hardware, where a
|
||||
# select would offer only one real choice against an implicit "off" -- shown
|
||||
# as a switch instead. '0' itself is never in supportedAiLevel but has been
|
||||
# observed live as the off value of aiLevel, so the select synthesizes it
|
||||
# back in as an explicit option rather than leaving no way to turn off.
|
||||
#
|
||||
# No translation_key: aiLevel's values are plain digit strings, and
|
||||
# select.py's _display() already renders an untranslated numeric string
|
||||
# as-is -- there's nothing a strings.json entry adds that's worth maintaining
|
||||
# against an unknown, growing number of future levels.
|
||||
|
||||
|
||||
def _ai_energy_supported_levels(rep):
|
||||
"""supportedAiLevel as a list -- a stray scalar (e.g. a string) must not
|
||||
be len()-checked as if it were a list."""
|
||||
sl = rep.get('supportedAiLevel')
|
||||
return list(sl) if isinstance(sl, (list, tuple)) else []
|
||||
|
||||
|
||||
def _ai_energy_level_options(resources):
|
||||
rep = resources.get('/energy/ailevel/vs/0') or {}
|
||||
return ['0', *_ai_energy_supported_levels(rep)]
|
||||
|
||||
|
||||
def _ai_energy_level_write(p, rep, href=None):
|
||||
return ['energy', 'ailevel', 'vs', '0'], {'aiLevel': p}
|
||||
|
||||
|
||||
def _ai_energy_level_switch_write(p, rep, href=None):
|
||||
levels = _ai_energy_supported_levels(rep)
|
||||
on_level = levels[0] if levels else '1'
|
||||
return ['energy', 'ailevel', 'vs', '0'], {'aiLevel': on_level if p == 'On' else '0'}
|
||||
|
||||
|
||||
AI_ENERGY_LEVEL = Capability(
|
||||
href='/energy/ailevel/vs/0',
|
||||
poll_tier='cold',
|
||||
entities=(
|
||||
# No `not rep` stub carve-out on either side, unlike most exists_fn
|
||||
# gates in this file -- entity creation only ever runs once, against
|
||||
# whichever snapshot happens to be current the moment platforms are
|
||||
# set up (see entity._is_included / __init__.py's
|
||||
# async_config_entry_first_refresh-before-forward-entry-setups
|
||||
# ordering), while flatten() re-evaluates exists_fn every poll
|
||||
# against live data. Both descriptors share key='ai_energy_level',
|
||||
# so if a stub carve-out let one of them win at setup time while the
|
||||
# other wins once real data lands, flatten() would feed the
|
||||
# instantiated entity a value shaped for the other platform (e.g. a
|
||||
# bool into a Select). Requiring real, populated data on both sides
|
||||
# keeps the entity-creation decision and the live-value decision in
|
||||
# permanent agreement -- the cost is this entity doesn't appear
|
||||
# until a reload if the device's very first poll stubs this
|
||||
# cold-tier href, the same reload already required to fix which
|
||||
# platform got picked in that case.
|
||||
SwitchDesc(key='ai_energy_level', field='aiLevel',
|
||||
name='AI energy level', icon='mdi:leaf',
|
||||
entity_category='config',
|
||||
value_fn=lambda v: v != '0',
|
||||
exists_fn=lambda rep, resources: (
|
||||
len(_ai_energy_supported_levels(rep)) == 1),
|
||||
write_fn=_ai_energy_level_switch_write),
|
||||
SelectDesc(key='ai_energy_level', field='aiLevel',
|
||||
name='AI energy level', icon='mdi:leaf',
|
||||
entity_category='config',
|
||||
options=_ai_energy_level_options,
|
||||
exists_fn=lambda rep, resources: (
|
||||
len(_ai_energy_supported_levels(rep)) > 1),
|
||||
write_fn=_ai_energy_level_write),
|
||||
),
|
||||
)
|
||||
|
||||
FIRMWARE_UPDATE = Capability(
|
||||
href='/otninformation/vs/0',
|
||||
poll_tier='cold',
|
||||
entities=(
|
||||
BinarySensorDesc(
|
||||
key='firmware_update',
|
||||
field='x.com.samsung.da.newVersionAvailable',
|
||||
name='Firmware update available',
|
||||
device_class='update',
|
||||
entity_category='diagnostic',
|
||||
value_fn=lambda v: str(v).lower() == 'true' if v is not None else None,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
SELF_CHECK = Capability(
|
||||
href='/selfcheck/vs/0',
|
||||
poll_tier='cold',
|
||||
entities=(
|
||||
SensorDesc(key='selfcheck_status', field='x.com.samsung.da.status',
|
||||
name='Self-check status', icon='mdi:stethoscope',
|
||||
entity_category='diagnostic'),
|
||||
SensorDesc(key='selfcheck_result', field='x.com.samsung.da.result',
|
||||
name='Self-check result', icon='mdi:clipboard-check-outline',
|
||||
entity_category='diagnostic'),
|
||||
# List of error codes from the last self-check; joined for display.
|
||||
# Not every fridge reports the field, hence the exists_fn.
|
||||
SensorDesc(key='selfcheck_error', field='x.com.samsung.da.error',
|
||||
name='Self-check error', icon='mdi:alert-circle-outline',
|
||||
entity_category='diagnostic',
|
||||
exists_fn=lambda rep, resources: (
|
||||
not rep or 'x.com.samsung.da.error' in rep),
|
||||
value_fn=lambda v: (', '.join(v) if v else None) if isinstance(v, list) else v),
|
||||
ButtonDesc(key='selfcheck_start', field='', name='Start self-check',
|
||||
payload='Start', icon='mdi:play-circle-outline',
|
||||
entity_category='diagnostic',
|
||||
write_fn=lambda p, rep, href=None: (
|
||||
['selfcheck', 'vs', '0'], {'x.com.samsung.da.status': p})),
|
||||
),
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Cross-family bundles, unpacked into every by_type registry's _build([...])
|
||||
# call the same way ignored.IGNORED is (*common.UNIVERSAL / *common.POWER).
|
||||
# discover() only binds a capability whose href is actually present in a
|
||||
# given device's resource dump, so listing one here for a family that
|
||||
# doesn't expose the href is a no-op, not a phantom entity -- see the
|
||||
# adding-device-support skill's coverage-discipline section.
|
||||
#
|
||||
# UNIVERSAL holds every capability with no known family that both (a) has
|
||||
# the href and (b) needs to model it some other way -- broadening one of
|
||||
# these to a new family is a safe, harmless guess (issue #40's AI energy
|
||||
# level: 2 of 6 families confirmed, blanket-added everywhere else).
|
||||
#
|
||||
# POWER is kept separate -- airconditioner is the one family that opts out
|
||||
# of it. Canonical reason (see by_type/airconditioner.py and its test for
|
||||
# pointers back here, not restatements): AC's climate entity already owns
|
||||
# /power/0 and /power/vs/0 via bare, no-entity Capability objects
|
||||
# (airconditioner.COVERAGE), and a second, real POWER_GENERIC/
|
||||
# POWER_VS_FALLBACK cap on the same href would make _build() raise (a href
|
||||
# with >1 cap must have every cap discriminated by rt_filter/match_fn, and
|
||||
# the bare COVERAGE cap has neither). Kids-lock/remote-control don't have
|
||||
# this conflict -- no AC dump has ever reported those hrefs -- so they stay
|
||||
# in UNIVERSAL.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
UNIVERSAL = (
|
||||
ALARMS,
|
||||
ENERGY_METER,
|
||||
FIRMWARE_UPDATE,
|
||||
SELF_CHECK,
|
||||
AI_ENERGY_LEVEL,
|
||||
KIDS_LOCK_GENERIC,
|
||||
KIDS_LOCK_VS_FALLBACK,
|
||||
REMOTE_CONTROL_GENERIC,
|
||||
REMOTE_CONTROL_VS_FALLBACK,
|
||||
)
|
||||
|
||||
POWER = (
|
||||
POWER_GENERIC,
|
||||
POWER_VS_FALLBACK,
|
||||
)
|
||||
|
||||
@@ -8,7 +8,7 @@ wash, auto release dry) are read locally here.
|
||||
"""
|
||||
from ..capability import Capability
|
||||
from ..entities import ButtonDesc, SelectDesc, SensorDesc, SwitchDesc
|
||||
from .laundry import cycle_select, option_value, replace_in_options
|
||||
from .laundry import bool_option_switch, cycle_select
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# /dishwasher/vs/0 — cycle wash/dry settings
|
||||
@@ -35,49 +35,20 @@ DISHWASHER_SETTINGS = Capability(
|
||||
# ---------------------------------------------------------------------------
|
||||
# /course/vs/0 — cycle selection (shared laundry.cycle_select) plus the
|
||||
# dishwasher-only StormWashZone / AutoDoorRelease toggles that ride in the
|
||||
# same options array. Course display names live in translations under
|
||||
# same options array (shared laundry.bool_option_switch, same options[]
|
||||
# boolean-toggle contract washer's bubble-soak/pre-wash/intensive switches
|
||||
# use). Course display names live in translations under
|
||||
# entity.select.dishwasher_cycle (see laundry.cycle_select).
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _storm_wash_write(p, rep, href=None):
|
||||
if p not in ('On', 'Off'):
|
||||
return None
|
||||
opts = list(rep.get('x.com.samsung.da.options') or [])
|
||||
if not opts:
|
||||
return None
|
||||
return ['course', 'vs', '0'], {
|
||||
'x.com.samsung.da.options': replace_in_options(opts, 'StormWashZone', p),
|
||||
}
|
||||
|
||||
|
||||
def _auto_release_write(p, rep, href=None):
|
||||
if p not in ('On', 'Off'):
|
||||
return None
|
||||
opts = list(rep.get('x.com.samsung.da.options') or [])
|
||||
if not opts:
|
||||
return None
|
||||
return ['course', 'vs', '0'], {
|
||||
'x.com.samsung.da.options': replace_in_options(opts, 'AutoDoorRelease', p),
|
||||
}
|
||||
|
||||
|
||||
CYCLE_OPTIONS = Capability(
|
||||
href='/course/vs/0',
|
||||
entities=(
|
||||
cycle_select(translation_key='dishwasher_cycle', icon='mdi:dishwasher'),
|
||||
SwitchDesc(key='storm_wash', name='Storm Wash+', icon='mdi:weather-lightning-rainy',
|
||||
rep_fn=lambda rep: option_value(
|
||||
rep.get('x.com.samsung.da.options'), 'StormWashZone') == 'On',
|
||||
write_fn=_storm_wash_write),
|
||||
SwitchDesc(key='auto_release_dry', name='Auto release dry', icon='mdi:door-open',
|
||||
exists_fn=lambda rep, resources: any(
|
||||
isinstance(o, str) and o.startswith('AutoDoorRelease_')
|
||||
for o in (rep.get('x.com.samsung.da.options') or [])
|
||||
),
|
||||
rep_fn=lambda rep: option_value(
|
||||
rep.get('x.com.samsung.da.options'), 'AutoDoorRelease') == 'On',
|
||||
write_fn=_auto_release_write),
|
||||
bool_option_switch('storm_wash', 'Storm Wash+', 'mdi:weather-lightning-rainy',
|
||||
'StormWashZone'),
|
||||
bool_option_switch('auto_release_dry', 'Auto release dry', 'mdi:door-open',
|
||||
'AutoDoorRelease', gate_on_presence=True),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ import datetime
|
||||
|
||||
from ..capability import Capability
|
||||
from ..entities import (
|
||||
BinarySensorDesc, ButtonDesc, NumberDesc, SelectDesc, SensorDesc,
|
||||
BinarySensorDesc, NumberDesc, SelectDesc, SensorDesc,
|
||||
SwitchDesc, TimeDesc,
|
||||
)
|
||||
from .common import normalize_temp_unit
|
||||
@@ -209,6 +209,14 @@ DEFROST_DELAY = Capability(
|
||||
),
|
||||
)
|
||||
|
||||
# OCF-native boolean mirror of DEFROST_DELAY. The captured TP1X_REF_21K
|
||||
# firmware publishes the same state on both hrefs, but only the vendor resource
|
||||
# above has a confirmed write contract. Bind the native mirror without another
|
||||
# entity so discovery records it as an intentional duplicate.
|
||||
DEFROST_DELAY_NATIVE_DUPLICATE = Capability(
|
||||
href='/defrost/delay/0',
|
||||
)
|
||||
|
||||
DEFROST_BLOCK_STATUS = Capability(
|
||||
href='/defrost/block/vs/0',
|
||||
poll_tier='warm',
|
||||
@@ -220,28 +228,6 @@ DEFROST_BLOCK_STATUS = Capability(
|
||||
),
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Self-check diagnostic
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
SELF_CHECK = Capability(
|
||||
href='/selfcheck/vs/0',
|
||||
poll_tier='cold',
|
||||
entities=(
|
||||
SensorDesc(key='selfcheck_status', field='x.com.samsung.da.status',
|
||||
name='Self-check status', icon='mdi:stethoscope',
|
||||
entity_category='diagnostic'),
|
||||
SensorDesc(key='selfcheck_result', field='x.com.samsung.da.result',
|
||||
name='Self-check result', icon='mdi:clipboard-check-outline',
|
||||
entity_category='diagnostic'),
|
||||
ButtonDesc(key='selfcheck_start', field='', name='Start self-check',
|
||||
payload='Start', icon='mdi:play-circle-outline',
|
||||
entity_category='diagnostic',
|
||||
write_fn=lambda p, rep, href=None: (
|
||||
['selfcheck', 'vs', '0'], {'x.com.samsung.da.status': p})),
|
||||
),
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Refrigeration modes (rapid cooling)
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -494,17 +480,64 @@ BEVERAGE_ZONE = Capability(
|
||||
),
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Pantry / Cool Select Zone -- a convertible compartment toggled between
|
||||
# wine/deli/drinks temperature presets (issue #20). Same shape as
|
||||
# BEVERAGE_ZONE (a controllable named sub-zone with a mode + supported-modes
|
||||
# list) but a distinct resource/field set -- x.com.samsung.da.mode /
|
||||
# x.com.samsung.da.supportedOptions on /status/pantry/one/vs/0, rather than
|
||||
# roomDesiredMode/roomSupportedModes on /specialzone/one/vs/0. Only a "one"
|
||||
# instance has been seen; not generalized to a pattern cap until a second
|
||||
# instance turns up.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _pantry_write(p, rep, href=None):
|
||||
return ['status', 'pantry', 'one', 'vs', '0'], {'x.com.samsung.da.mode': p}
|
||||
|
||||
|
||||
PANTRY_ZONE = Capability(
|
||||
href='/status/pantry/one/vs/0',
|
||||
poll_tier='warm',
|
||||
entities=(
|
||||
SelectDesc(key='pantry_zone_mode', field='x.com.samsung.da.mode',
|
||||
name='Pantry zone mode', icon='mdi:glass-wine',
|
||||
translation_key='pantry_zone_mode',
|
||||
entity_category='config',
|
||||
options_field='x.com.samsung.da.supportedOptions',
|
||||
write_fn=_pantry_write),
|
||||
),
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Flex zone (convertible drawer — /mode/vs/0 on RF9000-class fridges)
|
||||
#
|
||||
# x.com.samsung.da.modes holds multiple orthogonal flags in one list.
|
||||
# The flex zone entry is identified by the CV_TTYPE_RF9000A_ prefix.
|
||||
# x.com.samsung.da.modes holds multiple orthogonal flags in one list; the
|
||||
# flex-zone entry is whichever item is also a member of supportedOptions --
|
||||
# the other flags (WATERFILTER_*, DEFROST_BLOCK_*, the CVN_*_ZONE marker)
|
||||
# never appear there. The prefix on that item varies by fridge family
|
||||
# (CV_TTYPE_RF9000A_ on RF9000-class, CV_FDR_ on Bespoke-class -- issue #27 /
|
||||
# #26, where the old CV_TTYPE_RF9000A_-only match left this entity bound but
|
||||
# stuck on None), so match by list membership instead of a hardcoded prefix.
|
||||
# Write replaces only that item; other flags are preserved.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _flex_zone_supported(rep):
|
||||
return set(rep.get('x.com.samsung.da.supportedOptions') or ())
|
||||
|
||||
|
||||
def _flex_zone_current(rep):
|
||||
# Every dump seen has at most one modes/supportedOptions overlap, so
|
||||
# "first match" and "strip all matches" (in the write below) agree. If a
|
||||
# future device ever reports two, this reads the first and the write
|
||||
# would drop both -- revisit if that turns up.
|
||||
modes = rep.get('x.com.samsung.da.modes') or []
|
||||
supported = _flex_zone_supported(rep)
|
||||
return next((m for m in modes if m in supported), None)
|
||||
|
||||
|
||||
def _flex_zone_write(p, rep, href=None):
|
||||
modes = list(rep.get('x.com.samsung.da.modes') or [])
|
||||
modes = [m for m in modes if not m.startswith('CV_TTYPE_RF9000A_')]
|
||||
supported = _flex_zone_supported(rep)
|
||||
modes = [m for m in (rep.get('x.com.samsung.da.modes') or []) if m not in supported]
|
||||
modes.append(p)
|
||||
return ['mode', 'vs', '0'], {'x.com.samsung.da.modes': modes}
|
||||
|
||||
@@ -514,39 +547,17 @@ FLEX_ZONE = Capability(
|
||||
poll_tier='warm',
|
||||
entities=(
|
||||
SelectDesc(key='flex_zone_mode',
|
||||
field='x.com.samsung.da.modes',
|
||||
name='Flex zone mode', icon='mdi:thermostat',
|
||||
translation_key='flex_zone_mode',
|
||||
entity_category='config',
|
||||
options_field='x.com.samsung.da.supportedOptions',
|
||||
exists_fn=lambda rep, resources: bool(
|
||||
rep.get('x.com.samsung.da.supportedOptions')),
|
||||
value_fn=lambda modes: next(
|
||||
(m for m in (modes or [])
|
||||
if m.startswith('CV_TTYPE_RF9000A_')), None),
|
||||
rep_fn=_flex_zone_current,
|
||||
write_fn=_flex_zone_write),
|
||||
),
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Firmware update
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
FIRMWARE_UPDATE = Capability(
|
||||
href='/otninformation/vs/0',
|
||||
poll_tier='cold',
|
||||
entities=(
|
||||
BinarySensorDesc(
|
||||
key='firmware_update',
|
||||
field='x.com.samsung.da.newVersionAvailable',
|
||||
name='Firmware update available',
|
||||
device_class='update',
|
||||
entity_category='diagnostic',
|
||||
value_fn=lambda v: str(v).lower() == 'true' if v is not None else None,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Generic door pattern capability (href=None — use as pattern_cap only)
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -655,6 +666,14 @@ ICEMAKER_STATUS_FALLBACK = Capability(
|
||||
),
|
||||
)
|
||||
|
||||
# OCF-native aggregate mirror of ICEMAKER_STATUS_FALLBACK. On the captured
|
||||
# TP1X_REF_21K it duplicates both the vendor aggregate and the richer per-unit
|
||||
# /icemaker/one|two/vs/0 resources. Its write contract is not advertised, so
|
||||
# keep the proven per-unit/vendor controls and bind this as a duplicate only.
|
||||
ICEMAKER_STATUS_NATIVE_DUPLICATE = Capability(
|
||||
href='/icemaker/status/0',
|
||||
)
|
||||
|
||||
# OCF-native /refrigeration/0 (issue #7's unbound_hrefs) -- the odd one out
|
||||
# in this section: its three fields duplicate two *different* richer
|
||||
# hrefs (REFRIGERATION's rapidFridge/rapidFreezing and
|
||||
|
||||
@@ -85,14 +85,9 @@ IGNORED: list[Capability] = [
|
||||
# Warranty/service-plan enrollment status — every field reads "Unknown"
|
||||
# on hardware not enrolled in a Samsung Care+ style program.
|
||||
Capability(href='/dginformation/vs/0'),
|
||||
# AI energy-saving level; only one value ('1') is ever in
|
||||
# supportedAiLevel on hardware seen so far, so there's no real choice
|
||||
# to expose. Revisit if a device surfaces more than one supported level.
|
||||
Capability(href='/energy/ailevel/vs/0'),
|
||||
# OCF-native vacation-mode flag (fridge). Only one value ('RVACATION_OFF')
|
||||
# has ever been seen in `modes` (issue #7's dump), same "no real choice
|
||||
# to expose yet" situation as /energy/ailevel/vs/0 above. Revisit if a
|
||||
# device surfaces it toggled on.
|
||||
# has ever been seen in `modes` (issue #7's dump) -- no real choice to
|
||||
# expose yet. Revisit if a device surfaces it toggled on.
|
||||
Capability(href='/mode/0'),
|
||||
# Opaque integer with no supportedModes/options list to interpret it
|
||||
# against — meaning unclear from the raw resource alone.
|
||||
|
||||
@@ -217,6 +217,59 @@ def cycle_select(*, translation_key, icon):
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Plain boolean toggles over /course/vs/0's options[] array: a
|
||||
# '<prefix>_On'/'<prefix>_Off' token, read-modify-written the same way as
|
||||
# the 'Course' token above. Shared by washer (bubble soak, pre-wash,
|
||||
# intensive -- issue #22) and dishwasher (storm wash, auto release dry) --
|
||||
# both families ride this exact contract, just with different prefixes and
|
||||
# different presence/validation needs on top.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def bool_option_write(prefix):
|
||||
def write(p, rep, href=None):
|
||||
if p not in ('On', 'Off'):
|
||||
return None
|
||||
opts = list(rep.get('x.com.samsung.da.options') or [])
|
||||
if not opts:
|
||||
return None
|
||||
return ['course', 'vs', '0'], {
|
||||
'x.com.samsung.da.options': replace_in_options(opts, prefix, p),
|
||||
}
|
||||
return write
|
||||
|
||||
|
||||
def bool_option_value(prefix):
|
||||
return lambda rep: option_value(rep.get('x.com.samsung.da.options'), prefix) == 'On'
|
||||
|
||||
|
||||
def bool_option_exists(prefix):
|
||||
return lambda rep, resources: option_value(
|
||||
rep.get('x.com.samsung.da.options'), prefix) is not None
|
||||
|
||||
|
||||
def bool_option_switch(key, name, icon, prefix, *, entity_category=None,
|
||||
gate_on_presence=False, validate_fn=None):
|
||||
"""A SwitchDesc over a '<prefix>_On'/'<prefix>_Off' options[] token.
|
||||
|
||||
gate_on_presence self-gates the entity off on models that never report
|
||||
the token at all (washer's bubble soak/pre-wash/intensive); leave False
|
||||
for a toggle every device in the family reports (dishwasher's storm
|
||||
wash). validate_fn is passed straight through to SwitchDesc for callers
|
||||
that need to reject a write against live device state (e.g. washer's
|
||||
per-course availability check) -- this factory has no opinion on it and
|
||||
building one, if needed, is the caller's job.
|
||||
"""
|
||||
return SwitchDesc(
|
||||
key=key, name=name, icon=icon, entity_category=entity_category,
|
||||
exists_fn=bool_option_exists(prefix) if gate_on_presence else None,
|
||||
rep_fn=bool_option_value(prefix),
|
||||
write_fn=bool_option_write(prefix),
|
||||
validate_fn=validate_fn,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# /wm/jobbeginingstatus/vs/0 -- the "why did the cycle not start" reason
|
||||
# (e.g. door open, no water). The vendor field is x.com.samsung.da.currentStatus
|
||||
|
||||
@@ -17,10 +17,13 @@ from datetime import datetime, timezone
|
||||
|
||||
from ..capability import Capability
|
||||
from ..entities import BinarySensorDesc, SelectDesc, SensorDesc
|
||||
from .laundry import cycle_select, hex_pairs, option_value, replace_in_options
|
||||
from .laundry import (
|
||||
bool_option_exists, bool_option_switch, cycle_options, cycle_select, hex_pairs, option_value,
|
||||
replace_in_options,
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Course_XX hex codes. The 23 codes named in strings.json/translations
|
||||
# Course_XX hex codes. 23 of the codes named in strings.json/translations
|
||||
# under entity.select.washer_cycle.state.<id, lowercased> were captured
|
||||
# from a live WW90DG6U25LEU4's x.com.samsung.da.editCourseList
|
||||
# (EditCourseList_1C1D211B1E29243328262722202325322F2E30662D8F96), matched
|
||||
@@ -35,6 +38,13 @@ from .laundry import cycle_select, hex_pairs, option_value, replace_in_options
|
||||
# you'd expect to be pinned (default cycle + maintenance cycle),
|
||||
# corroborating the positional match.
|
||||
#
|
||||
# A further 5 codes -- '36' Wash+Dry, '37' Air Wash, '38' Cotton Dry,
|
||||
# '39' Synthetics Dry, and a second, distinct '1F' Intense Cold (not the
|
||||
# same code as '8F' above) -- came from a WD90T654DBN/S1 washer/dryer
|
||||
# combo's editCourseList and were named from that user's app screenshot
|
||||
# (issue #22). Combo units carry their own course set, so these codes
|
||||
# don't imply anything about '1F' on a plain washer.
|
||||
#
|
||||
# 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
|
||||
@@ -82,6 +92,19 @@ WASHER_SETTINGS = Capability(
|
||||
options_field='x.com.samsung.da.supportedRinseCycles',
|
||||
write_fn=lambda p, rep, href=None: (
|
||||
['washer', 'vs', '0'], {'x.com.samsung.da.rinseCycles': p})),
|
||||
# Washer/dryer combo units carry a dryLevel field on the wash
|
||||
# resource itself (no separate dryer device/course) -- see issue
|
||||
# #22. Self-gates off on plain washers, which never report
|
||||
# supportedDryLevel.
|
||||
SelectDesc(key='dry_level', field='x.com.samsung.da.dryLevel',
|
||||
name='Dry level', icon='mdi:tumble-dryer',
|
||||
entity_category='config',
|
||||
translation_key='washer_dry_level',
|
||||
options_field='x.com.samsung.da.supportedDryLevel',
|
||||
exists_fn=lambda rep, resources: bool(
|
||||
rep.get('x.com.samsung.da.supportedDryLevel')),
|
||||
write_fn=lambda p, rep, href=None: (
|
||||
['washer', 'vs', '0'], {'x.com.samsung.da.dryLevel': p})),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -213,9 +236,58 @@ def _dosing_low(prefix):
|
||||
rep.get('x.com.samsung.da.options'), prefix) not in (None, 'Off')
|
||||
|
||||
|
||||
def _dosing_alarm_exists(prefix):
|
||||
return lambda rep, resources: option_value(
|
||||
rep.get('x.com.samsung.da.options'), prefix) is not None
|
||||
# Bubble soak / pre-wash / intensive-wash toggles, from the same options[]
|
||||
# array (issue #22 follow-up on a WD90T654DBN/S1 combo). Each rides as a
|
||||
# plain '<Prefix>_On'/'<Prefix>_Off' token, confirmed by a dump taken with
|
||||
# Bubble Soak switched on in the app (BubbleSoak_On) -- the same On/Off shape
|
||||
# already used by AiOption and KidsLockBypass in this same array, so
|
||||
# PreWashSetting/IntensiveSetting are assumed to follow suit.
|
||||
#
|
||||
# Each also has a differently-named hex-pair availability field that lines up
|
||||
# positionally with editCourseList: BubbleSoakSet, PreWashAvailableSet,
|
||||
# IntensiveAvailableSet. On the reporter's dump (course '30' at position 1 of
|
||||
# 24), all three read 'F0' at that position and the toggle was writable --
|
||||
# and the same dump's earlier state (course '1C' at position 0, 'BubbleSoak
|
||||
# Off') decodes to '00' for that course, matching the app graying the
|
||||
# control out there. 'F0'/'00' is treated as available/unavailable on that
|
||||
# evidence. exists_fn (device-level presence) still only runs once, against
|
||||
# the setup-time snapshot, so it isn't a fit for this per-course check --
|
||||
# validate_fn runs on every write attempt instead (dispatched from
|
||||
# coordinator.async_send_command, ahead of write_fn), rejecting an on-write
|
||||
# for a course whose byte isn't 'F0' with a user-facing error rather than
|
||||
# silently no-opping against the device. The read/write/presence machinery
|
||||
# itself is laundry.bool_option_switch, shared with dishwasher's storm-wash/
|
||||
# auto-release-dry toggles -- only this per-course gating is washer-only, so
|
||||
# it stays here rather than in laundry.py (see laundry.bool_option_switch's
|
||||
# docstring: it takes a prebuilt validate_fn and has no opinion on it).
|
||||
def _bool_option_switch(key, name, icon, prefix, availability_field):
|
||||
def validate(p, rep, resources):
|
||||
"""Reject turning on when the selected course's byte in
|
||||
`availability_field` isn't 'F0'. Turning off is never blocked. Falls
|
||||
back to allowing the write whenever the availability data can't be
|
||||
resolved (unrecognized course, missing/mismatched-length bitmap)
|
||||
rather than guessing -- a false rejection is worse than an
|
||||
occasional no-op write."""
|
||||
if p != 'On':
|
||||
return None
|
||||
opts = rep.get('x.com.samsung.da.options') or []
|
||||
current = option_value(opts, 'Course')
|
||||
courses = cycle_options(resources)
|
||||
if not current or current not in courses:
|
||||
return None
|
||||
raw = option_value(opts, availability_field)
|
||||
if raw is None:
|
||||
return None
|
||||
pairs = hex_pairs(raw)
|
||||
if len(pairs) != len(courses):
|
||||
return None
|
||||
if pairs[courses.index(current)] != 'F0':
|
||||
return f"{name} isn't available on the selected cycle."
|
||||
return None
|
||||
|
||||
return bool_option_switch(
|
||||
key, name, icon, prefix,
|
||||
entity_category='config', gate_on_presence=True, validate_fn=validate)
|
||||
|
||||
|
||||
WASHER_COURSE = Capability(
|
||||
@@ -268,11 +340,17 @@ WASHER_COURSE = Capability(
|
||||
write_fn=_level_write('SoftenerLevel2Ctrl')),
|
||||
BinarySensorDesc(key='detergent_low', name='Detergent low',
|
||||
icon='mdi:alert-circle-outline', device_class='problem',
|
||||
exists_fn=_dosing_alarm_exists('DetergentAlarm'),
|
||||
exists_fn=bool_option_exists('DetergentAlarm'),
|
||||
rep_fn=_dosing_low('DetergentAlarm')),
|
||||
BinarySensorDesc(key='softener_low', name='Softener low',
|
||||
icon='mdi:alert-circle-outline', device_class='problem',
|
||||
exists_fn=_dosing_alarm_exists('SoftenerAlarm'),
|
||||
exists_fn=bool_option_exists('SoftenerAlarm'),
|
||||
rep_fn=_dosing_low('SoftenerAlarm')),
|
||||
_bool_option_switch('bubble_soak', 'Bubble soak', 'mdi:chart-bubble',
|
||||
'BubbleSoak', 'BubbleSoakSet'),
|
||||
_bool_option_switch('pre_wash', 'Pre wash', 'mdi:washing-machine',
|
||||
'PreWashSetting', 'PreWashAvailableSet'),
|
||||
_bool_option_switch('intensive', 'Intensive', 'mdi:washing-machine',
|
||||
'IntensiveSetting', 'IntensiveAvailableSet'),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
Frozen dataclasses so the future native HA component can consume them as
|
||||
EntityDescription subclasses unchanged. Read transforms live in value_fn;
|
||||
presence gating in exists_fn; write logic in write_fn on command platforms.
|
||||
presence gating in exists_fn; write logic in write_fn on command platforms;
|
||||
pre-write rejection (surfaced to the user, not just logged) in validate_fn
|
||||
where a description declares one.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -10,6 +12,11 @@ from dataclasses import dataclass, field
|
||||
from typing import Any, Callable, Optional
|
||||
|
||||
WriteFn = Optional[Callable[[Any, dict], "tuple[list[str], dict] | None"]]
|
||||
# (payload, rep, resources) -> a human-readable rejection message, or None to
|
||||
# allow the write. resources is the coordinator's full href->rep snapshot, for
|
||||
# the same cross-resource lookups exists_fn needs (e.g. reading a sibling
|
||||
# href's live option list).
|
||||
ValidateFn = Optional[Callable[[Any, dict, dict], "str | None"]]
|
||||
|
||||
|
||||
def _identity(v: Any) -> Any:
|
||||
@@ -61,6 +68,7 @@ class SelectDesc(SamsungEntityDescription):
|
||||
class SwitchDesc(SamsungEntityDescription):
|
||||
device_class: Optional[str] = None
|
||||
write_fn: WriteFn = None
|
||||
validate_fn: ValidateFn = None
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
|
||||
@@ -30,13 +30,25 @@
|
||||
"sp_ttype_wine_dessert": "Wine and Dessert"
|
||||
}
|
||||
},
|
||||
"pantry_zone_mode": {
|
||||
"state": {
|
||||
"fdr_wine": "Wine",
|
||||
"fdr_deli": "Deli",
|
||||
"fdr_drinks": "Drinks"
|
||||
}
|
||||
},
|
||||
"flex_zone_mode": {
|
||||
"state": {
|
||||
"cv_ttype_rf9000a_freeze": "Freeze",
|
||||
"cv_ttype_rf9000a_softfreeze": "Soft Freeze",
|
||||
"cv_ttype_rf9000a_meat_fish": "Meat/Fish",
|
||||
"cv_ttype_rf9000a_fruit_veggies": "Fruit & Veggies",
|
||||
"cv_ttype_rf9000a_beverage": "Beverage"
|
||||
"cv_ttype_rf9000a_beverage": "Beverage",
|
||||
"cv_fdr_wine": "Wine",
|
||||
"cv_fdr_deli": "Deli",
|
||||
"cv_fdr_beverage": "Beverage",
|
||||
"cv_fdr_meat": "Meat",
|
||||
"cv_fdr_soft_freezer": "Soft Freezer"
|
||||
}
|
||||
},
|
||||
"dishwasher_cycle": {
|
||||
@@ -77,7 +89,12 @@
|
||||
"66": "Denim",
|
||||
"2d": "Silent Wash",
|
||||
"8f": "Intense Cold",
|
||||
"96": "Less Microfiber"
|
||||
"96": "Less Microfiber",
|
||||
"36": "Wash+Dry",
|
||||
"37": "Air Wash",
|
||||
"38": "Cotton Dry",
|
||||
"39": "Synthetics Dry",
|
||||
"1f": "Intense Cold"
|
||||
}
|
||||
},
|
||||
"dryer_cycle": {
|
||||
@@ -119,6 +136,18 @@
|
||||
"02": "2x",
|
||||
"03": "3x"
|
||||
}
|
||||
},
|
||||
"washer_dry_level": {
|
||||
"state": {
|
||||
"none": "Off",
|
||||
"cupboard": "Cupboard",
|
||||
"30": "30 min",
|
||||
"60": "1 hr",
|
||||
"90": "1 hr 30",
|
||||
"120": "2 hr",
|
||||
"180": "3 hr",
|
||||
"240": "4 hr"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
|
||||
@@ -30,13 +30,25 @@
|
||||
"sp_ttype_wine_dessert": "Wine and Dessert"
|
||||
}
|
||||
},
|
||||
"pantry_zone_mode": {
|
||||
"state": {
|
||||
"fdr_wine": "Wine",
|
||||
"fdr_deli": "Deli",
|
||||
"fdr_drinks": "Drinks"
|
||||
}
|
||||
},
|
||||
"flex_zone_mode": {
|
||||
"state": {
|
||||
"cv_ttype_rf9000a_freeze": "Freeze",
|
||||
"cv_ttype_rf9000a_softfreeze": "Soft Freeze",
|
||||
"cv_ttype_rf9000a_meat_fish": "Meat/Fish",
|
||||
"cv_ttype_rf9000a_fruit_veggies": "Fruit & Veggies",
|
||||
"cv_ttype_rf9000a_beverage": "Beverage"
|
||||
"cv_ttype_rf9000a_beverage": "Beverage",
|
||||
"cv_fdr_wine": "Wine",
|
||||
"cv_fdr_deli": "Deli",
|
||||
"cv_fdr_beverage": "Beverage",
|
||||
"cv_fdr_meat": "Meat",
|
||||
"cv_fdr_soft_freezer": "Soft Freezer"
|
||||
}
|
||||
},
|
||||
"dishwasher_cycle": {
|
||||
@@ -77,7 +89,12 @@
|
||||
"66": "Denim",
|
||||
"2d": "Silent Wash",
|
||||
"8f": "Intense Cold",
|
||||
"96": "Less Microfiber"
|
||||
"96": "Less Microfiber",
|
||||
"36": "Wash+Dry",
|
||||
"37": "Air Wash",
|
||||
"38": "Cotton Dry",
|
||||
"39": "Synthetics Dry",
|
||||
"1f": "Intense Cold"
|
||||
}
|
||||
},
|
||||
"dryer_cycle": {
|
||||
@@ -119,6 +136,18 @@
|
||||
"02": "2x",
|
||||
"03": "3x"
|
||||
}
|
||||
},
|
||||
"washer_dry_level": {
|
||||
"state": {
|
||||
"none": "Off",
|
||||
"cupboard": "Cupboard",
|
||||
"30": "30 min",
|
||||
"60": "1 hr",
|
||||
"90": "1 hr 30",
|
||||
"120": "2 hr",
|
||||
"180": "3 hr",
|
||||
"240": "4 hr"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
|
||||
@@ -0,0 +1,595 @@
|
||||
{
|
||||
"meta": {
|
||||
"note": "Samsung Room A/C, model TP1X_DA-AC-RAC-01011 (oneUiVersion '7.0 Air conditioner', Tizen Lite). Scrubbed real /device/0 dump. Adds temperature via /temperatures/vs/0 + display light. Issue #17 (newer model class).",
|
||||
"issue": 17,
|
||||
"model": "TP1X_DA-AC-RAC-01011",
|
||||
"oneUiVersion": "7.0 Air conditioner"
|
||||
},
|
||||
"device0": [
|
||||
{
|
||||
"rt": [
|
||||
"x.com.samsung.devcol",
|
||||
"oic.wk.col"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.ll",
|
||||
"oic.if.b"
|
||||
]
|
||||
},
|
||||
{
|
||||
"href": "/airlevelcheck/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.periodicSensingInterval": "1800",
|
||||
"x.com.samsung.da.sensingState": "NonProcessing",
|
||||
"x.com.samsung.da.lastSensingTime": "1784508715",
|
||||
"x.com.samsung.da.lastSensingLevel": "Kr1",
|
||||
"x.com.samsung.da.periodicSensingSkipStatus": "Off",
|
||||
"x.com.samsung.da.periodicSensingSkipTime": "00000000",
|
||||
"x.com.samsung.da.periodicSensingActivationState": "On",
|
||||
"x.com.samsung.da.autoExeState": "Alarm",
|
||||
"x.com.samsung.da.supportedAutoExeState": [
|
||||
"Airpurify",
|
||||
"Alarm",
|
||||
"Sensing"
|
||||
],
|
||||
"x.com.samsung.da.startSensingOnce": "Off",
|
||||
"x.com.samsung.da.autoExeSetting": "Off",
|
||||
"x.com.samsung.da.supportedAutoExeSetting": [
|
||||
"On",
|
||||
"Off"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"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": {
|
||||
"rt": [
|
||||
"x.com.samsung.da.alarms"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.a"
|
||||
],
|
||||
"x.com.samsung.da.items": [
|
||||
{
|
||||
"x.com.samsung.da.id": "0",
|
||||
"x.com.samsung.da.alarmType": "Device",
|
||||
"x.com.samsung.da.code": "ErrorCode_OFF",
|
||||
"x.com.samsung.da.triggeredTime": "2026-07-22T07:13:19",
|
||||
"x.com.samsung.da.state": "Deleted"
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "1",
|
||||
"x.com.samsung.da.alarmType": "Device",
|
||||
"x.com.samsung.da.code": "FilterAlarm_OFF",
|
||||
"x.com.samsung.da.triggeredTime": "2026-07-22T07:13:19",
|
||||
"x.com.samsung.da.state": "Deleted"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/availablecontrolsets/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.sets": "000000A0012C016102490F0F0000",
|
||||
"x.com.samsung.da.id": "RAC",
|
||||
"x.com.samsung.da.version": "1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/configuration/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.region": "0000000000",
|
||||
"x.com.samsung.da.airconOptionList": [
|
||||
"PRODUCT_GLOBAL",
|
||||
"AI_GLOBAL_HEATPUMP_4.0",
|
||||
"AI_3.0",
|
||||
"Auto_To_AI",
|
||||
"AI_Heat",
|
||||
"ENERGY_2.0",
|
||||
"HOMECARE_WIZARD_V2",
|
||||
"DR",
|
||||
"SingleCommand_1"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/da/softreset/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.softwarereset": "false"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/drlc/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.start": "1970-01-01T00:00:00Z",
|
||||
"x.com.samsung.da.override": "Off",
|
||||
"x.com.samsung.da.durationminutes": "0",
|
||||
"x.com.samsung.da.drlcLevel": "0",
|
||||
"x.com.samsung.da.realSaving": "Off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/energy/consumption/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.cumulativeDate": "1784704371",
|
||||
"x.com.samsung.da.cumulativeSavedPower": "-1",
|
||||
"x.com.samsung.da.cumulativePower": "75025",
|
||||
"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": "+02:00",
|
||||
"x.com.samsung.supprtedtype": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/filter/airdustfilter/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.filterUsageResolution": "1",
|
||||
"x.com.samsung.da.filterCapacity": "500",
|
||||
"x.com.samsung.da.filterCapacityUnit": "Hour",
|
||||
"x.com.samsung.da.filterResetType": [
|
||||
"replaceable",
|
||||
"washable"
|
||||
],
|
||||
"x.com.samsung.da.filterStatus": "normal",
|
||||
"x.com.samsung.da.filterUsage": "42",
|
||||
"x.com.samsung.da.filterDesiredUsage": "500",
|
||||
"x.com.samsung.da.supportedFilterDesiredUsage": [
|
||||
"180",
|
||||
"300",
|
||||
"500",
|
||||
"700"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/humidity/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.humidity": "0",
|
||||
"x.com.samsung.da.fivepercentHumidity": "53"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/information/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.otnDUID": "**REDACTED**",
|
||||
"x.com.samsung.da.modelNum": "TP1X_DA-AC-RAC-01011_0000|10275643|60010523001911F58E0049320092E900",
|
||||
"x.com.samsung.da.description": "TP1X_DA-AC-RAC-01011_0000",
|
||||
"x.com.samsung.da.serialNum": "**REDACTED**",
|
||||
"x.com.samsung.da.serialNumOption": "**REDACTED**",
|
||||
"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": "02762A260401",
|
||||
"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.newVersionAvailable": "0",
|
||||
"x.com.samsung.da.number": "02756C25082500,FFFFFFFFFFFFFF"
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "2",
|
||||
"x.com.samsung.da.description": "Version",
|
||||
"x.com.samsung.da.type": "Firmware",
|
||||
"x.com.samsung.da.newVersionAvailable": "0",
|
||||
"x.com.samsung.da.number": "02669A24092600,02636A10001200"
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "3",
|
||||
"x.com.samsung.da.description": "Version",
|
||||
"x.com.samsung.da.type": "Firmware",
|
||||
"x.com.samsung.da.newVersionAvailable": "0",
|
||||
"x.com.samsung.da.number": "02672A10001000,FFFFFFFFFFFFFF"
|
||||
}
|
||||
],
|
||||
"x.com.samsung.da.diagDumpType": "file",
|
||||
"x.com.samsung.da.diagEndPoint": "SSM",
|
||||
"x.com.samsung.da.diagLogType": [
|
||||
"errCode",
|
||||
"dump"
|
||||
],
|
||||
"x.com.samsung.da.diagMnid": "0AJT",
|
||||
"x.com.samsung.da.diagSetupid": "AR4",
|
||||
"x.com.samsung.da.diagProtocolType": "BLE_OCF",
|
||||
"x.com.samsung.da.diagMinVersion": "3.0",
|
||||
"x.com.samsung.da.diagTsId": "DA01"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/keepnormalstate/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.keepnormal": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/light/vs/0",
|
||||
"rep": {
|
||||
"mode": "On",
|
||||
"supportedModes": [
|
||||
"On",
|
||||
"Off"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/mds/absencemonitoring/vs/0",
|
||||
"rep": {}
|
||||
},
|
||||
{
|
||||
"href": "/mds/absencestate/vs/0",
|
||||
"rep": {}
|
||||
},
|
||||
{
|
||||
"href": "/mode/convenient/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.modes": "Off",
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"Off",
|
||||
"Sleep",
|
||||
"Quiet",
|
||||
"Smart",
|
||||
"Speed",
|
||||
"MotionIndirect",
|
||||
"Nano",
|
||||
"NanoSleep",
|
||||
"DryComfort"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/mode/vs/0",
|
||||
"rep": {
|
||||
"rt": [
|
||||
"x.com.samsung.da.mode"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.a"
|
||||
],
|
||||
"x.com.samsung.da.modes": [
|
||||
"Heat"
|
||||
],
|
||||
"workingMode": "Heat",
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"Auto",
|
||||
"Cool",
|
||||
"Dry",
|
||||
"Heat",
|
||||
"Wind"
|
||||
],
|
||||
"x.com.samsung.da.options": [
|
||||
"OptionCode_56378",
|
||||
"Sleep_6",
|
||||
"ArtificialWorking_Off",
|
||||
"Off",
|
||||
"OutdoorTemp_68",
|
||||
"OutdoorConnection_Connected"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/option/airpurify/vs/0",
|
||||
"rep": {
|
||||
"rt": [
|
||||
"x.com.samsung.da.option.airpurify"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.a"
|
||||
],
|
||||
"x.com.samsung.da.modes": "Off",
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"On",
|
||||
"Off"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/option/autoclean/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.status": "Stop",
|
||||
"x.com.samsung.da.supportedStatus": [
|
||||
"Start",
|
||||
"Stop"
|
||||
],
|
||||
"x.com.samsung.da.progress": "0",
|
||||
"x.com.samsung.da.settingStatus": "On",
|
||||
"x.com.samsung.da.supportedSettingStatus": [
|
||||
"On",
|
||||
"Off"
|
||||
],
|
||||
"defaultSettingStatus": "On"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/option/muteonce/vs/0",
|
||||
"rep": {
|
||||
"muteonce": "Off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/personality/presence/vs/0",
|
||||
"rep": {}
|
||||
},
|
||||
{
|
||||
"href": "/power/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.power": "Off",
|
||||
"operationNumber": "39",
|
||||
"displayCondition": "Enable"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/realtimenotiforclient/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.timeforshortnoti": "0",
|
||||
"x.com.samsung.da.longnotisubscription": "true",
|
||||
"x.com.samsung.da.periodicnotisubscription": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/remotedatacontrol/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.status": "Off",
|
||||
"x.com.samsung.da.connectionStatus": "Disconnected"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/remotetemperature/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.temperature": "",
|
||||
"x.com.samsung.da.unit": "",
|
||||
"x.com.samsung.da.error": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/reserverulesets/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.sets": "3EFFFFFFFF101E121EFFFF101EFFFF000001001F0001001F001F0000009CACFFFF0000",
|
||||
"x.com.samsung.da.id": "RAC",
|
||||
"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": {
|
||||
"x.com.samsung.da.cleanLevel": "1",
|
||||
"x.com.samsung.da.items": [
|
||||
{
|
||||
"x.com.samsung.da.id": "0",
|
||||
"x.com.samsung.da.description": "Sensor for CleanLevel",
|
||||
"x.com.samsung.da.type": "CleanLevel",
|
||||
"x.com.samsung.da.value": [
|
||||
"1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "2",
|
||||
"x.com.samsung.da.description": "Sensor for Dust",
|
||||
"x.com.samsung.da.type": "Dust",
|
||||
"x.com.samsung.da.value": [
|
||||
"0",
|
||||
"0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "3",
|
||||
"x.com.samsung.da.description": "Sensor for FineDust",
|
||||
"x.com.samsung.da.type": "FineDust",
|
||||
"x.com.samsung.da.value": [
|
||||
"0",
|
||||
"0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "4",
|
||||
"x.com.samsung.da.description": "Sensor for SuperFineDust",
|
||||
"x.com.samsung.da.type": "SuperFineDust",
|
||||
"x.com.samsung.da.value": [
|
||||
"0",
|
||||
"0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/temperature/control/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.increment": "1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"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.current": "17.0",
|
||||
"x.com.samsung.da.desired": "25.0",
|
||||
"x.com.samsung.da.minimum": "16",
|
||||
"x.com.samsung.da.maximum": "30",
|
||||
"x.com.samsung.da.increment": "1",
|
||||
"x.com.samsung.da.unit": "Celsius"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"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",
|
||||
"Up_And_Low",
|
||||
"Left_And_Right",
|
||||
"All"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/wind/strength/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.modes": "0",
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"0",
|
||||
"1",
|
||||
"2",
|
||||
"3",
|
||||
"4"
|
||||
],
|
||||
"x.com.samsung.da.modesName": [
|
||||
"Auto",
|
||||
"Low",
|
||||
"Mid",
|
||||
"High",
|
||||
"Turbo"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/otninformation/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.target": "",
|
||||
"x.com.samsung.da.newVersionAvailable": "false",
|
||||
"otnStatus": "None",
|
||||
"flashingProgress": "0",
|
||||
"otnCompleteDate": "noHistory",
|
||||
"scheduledTime": "None",
|
||||
"swVersionInfo": {
|
||||
"platform": "Tizen Lite",
|
||||
"oneUiVersion": "7.0 Air conditioner",
|
||||
"osVersion": "4.0"
|
||||
},
|
||||
"otnList": [
|
||||
{
|
||||
"type": "WIFI",
|
||||
"modelId": "ARA-KR-TP1-25-ARXX00",
|
||||
"versions": [
|
||||
"11260401"
|
||||
],
|
||||
"visVersion": "260401"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/timezone/vs/0",
|
||||
"rep": {
|
||||
"timezoneid": "Africa/Johannesburg",
|
||||
"offset": "+02:00",
|
||||
"DST": "OFF"
|
||||
}
|
||||
},
|
||||
{
|
||||
"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": "/wirelessinfo/vs/0",
|
||||
"rep": {
|
||||
"macaddressWiFi": "**REDACTED**",
|
||||
"macaddressBLE": "**REDACTED**",
|
||||
"connectedApSsid": "**REDACTED**"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/quickcontrol/info/vs/0",
|
||||
"rep": {
|
||||
"supportedVersion": "1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/dginformation/vs/0",
|
||||
"rep": {
|
||||
"enrolmentstatus": "Unknown",
|
||||
"devicestate": "Unknown",
|
||||
"lockstatus": "Normal",
|
||||
"nextduedate": "",
|
||||
"workingminutes": 0,
|
||||
"paymentinfo": {
|
||||
"emiplan": "Unknown",
|
||||
"currency": "Unknown",
|
||||
"totalemi": 0,
|
||||
"totalemipaid": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
+591
@@ -0,0 +1,591 @@
|
||||
{
|
||||
"meta": {
|
||||
"model": "TP1X_DA-AC-RAC-01001_0000",
|
||||
"device_type": "airconditioner",
|
||||
"source": "issue #38 diagnostics (scrubbed)",
|
||||
"note": "Newer TP1X RAC board (oneUiVersion '7.0 Air conditioner') with a fuller resource set than the ARTIK051/TP2X boards: display light, self-check, mute-once, and a circuit-breaker current-limit setting."
|
||||
},
|
||||
"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": {
|
||||
"rt": [
|
||||
"x.com.samsung.da.alarms"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.a"
|
||||
],
|
||||
"x.com.samsung.da.items": [
|
||||
{
|
||||
"x.com.samsung.da.id": "0",
|
||||
"x.com.samsung.da.alarmType": "Device",
|
||||
"x.com.samsung.da.code": "ErrorCode_OFF",
|
||||
"x.com.samsung.da.triggeredTime": "2026-07-22T12:58:00",
|
||||
"x.com.samsung.da.state": "Deleted"
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "1",
|
||||
"x.com.samsung.da.alarmType": "Device",
|
||||
"x.com.samsung.da.code": "FilterAlarm_OFF",
|
||||
"x.com.samsung.da.triggeredTime": "2026-07-22T12:58:00",
|
||||
"x.com.samsung.da.state": "Deleted"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/availablecontrolsets/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.sets": "AEB700A0012C01610248040F0000",
|
||||
"x.com.samsung.da.id": "RAC",
|
||||
"x.com.samsung.da.version": "1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/configuration/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.region": "0000000000",
|
||||
"x.com.samsung.da.airconOptionList": [
|
||||
"PRODUCT_GLOBAL",
|
||||
"AI_GLOBAL_HEATPUMP_4.0",
|
||||
"AI_3.0",
|
||||
"Auto_To_AI",
|
||||
"AI_Heat",
|
||||
"ENERGY_2.0",
|
||||
"HOMECARE_WIZARD_V2",
|
||||
"DR",
|
||||
"SingleCommand_1"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"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": "/da/softreset/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.softwarereset": "false"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/dginformation/vs/0",
|
||||
"rep": {
|
||||
"enrolmentstatus": "Unknown",
|
||||
"devicestate": "Unknown",
|
||||
"lockstatus": "Unknown",
|
||||
"nextduedate": "",
|
||||
"workingminutes": 0,
|
||||
"paymentinfo": {
|
||||
"emiplan": "Unknown",
|
||||
"currency": "Unknown",
|
||||
"totalemi": 0,
|
||||
"totalemipaid": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/drlc/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.start": "2026-07-22T10:59:14Z",
|
||||
"x.com.samsung.da.override": "Off",
|
||||
"x.com.samsung.da.durationminutes": "1148",
|
||||
"x.com.samsung.da.drlcLevel": "1",
|
||||
"x.com.samsung.da.realSaving": "Off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/electriccurrent/vs/0",
|
||||
"rep": {
|
||||
"operation": "Off",
|
||||
"modes": "3",
|
||||
"supportedModes": [
|
||||
"3",
|
||||
"4",
|
||||
"5",
|
||||
"6",
|
||||
"7",
|
||||
"8",
|
||||
"9"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/energy/consumption/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.cumulativeDate": "1784725078",
|
||||
"x.com.samsung.da.cumulativeSavedPower": "-1",
|
||||
"x.com.samsung.da.cumulativePower": "160922",
|
||||
"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": "+03:00",
|
||||
"x.com.samsung.supprtedtype": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/filter/airdustfilter/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.filterUsageResolution": "1",
|
||||
"x.com.samsung.da.filterCapacity": "500",
|
||||
"x.com.samsung.da.filterCapacityUnit": "Hour",
|
||||
"x.com.samsung.da.filterResetType": [
|
||||
"replaceable",
|
||||
"washable"
|
||||
],
|
||||
"x.com.samsung.da.filterStatus": "normal",
|
||||
"x.com.samsung.da.filterUsage": "30",
|
||||
"x.com.samsung.da.filterDesiredUsage": "500",
|
||||
"x.com.samsung.da.supportedFilterDesiredUsage": [
|
||||
"180",
|
||||
"300",
|
||||
"500",
|
||||
"700"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/information/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.otnDUID": "**REDACTED**",
|
||||
"x.com.samsung.da.modelNum": "TP1X_DA-AC-RAC-01001_0000|10267841|60010532001911000E0041220090A000",
|
||||
"x.com.samsung.da.description": "TP1X_DA-AC-RAC-01001_0000",
|
||||
"x.com.samsung.da.serialNum": "**REDACTED**",
|
||||
"x.com.samsung.da.serialNumOption": "**REDACTED**",
|
||||
"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": "02762A260401",
|
||||
"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.newVersionAvailable": "0",
|
||||
"x.com.samsung.da.number": "02678A24092500,FFFFFFFFFFFFFF"
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "2",
|
||||
"x.com.samsung.da.description": "Version",
|
||||
"x.com.samsung.da.type": "Firmware",
|
||||
"x.com.samsung.da.newVersionAvailable": "0",
|
||||
"x.com.samsung.da.number": "02669A24092600,02636A10001200"
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "3",
|
||||
"x.com.samsung.da.description": "Version",
|
||||
"x.com.samsung.da.type": "Firmware",
|
||||
"x.com.samsung.da.newVersionAvailable": "0",
|
||||
"x.com.samsung.da.number": "02672A10001000,FFFFFFFFFFFFFF"
|
||||
}
|
||||
],
|
||||
"x.com.samsung.da.diagDumpType": "file",
|
||||
"x.com.samsung.da.diagEndPoint": "SSM",
|
||||
"x.com.samsung.da.diagLogType": [
|
||||
"errCode",
|
||||
"dump"
|
||||
],
|
||||
"x.com.samsung.da.diagMnid": "0AJT",
|
||||
"x.com.samsung.da.diagSetupid": "AR2",
|
||||
"x.com.samsung.da.diagProtocolType": "BLE_OCF",
|
||||
"x.com.samsung.da.diagMinVersion": "3.0",
|
||||
"x.com.samsung.da.diagTsId": "DA01"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/keepnormalstate/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.keepnormal": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/light/vs/0",
|
||||
"rep": {
|
||||
"mode": "On",
|
||||
"supportedModes": [
|
||||
"On",
|
||||
"Off"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/mds/absencemonitoring/vs/0",
|
||||
"rep": {}
|
||||
},
|
||||
{
|
||||
"href": "/mds/absencestate/vs/0",
|
||||
"rep": {}
|
||||
},
|
||||
{
|
||||
"href": "/mode/convenient/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.modes": "Off",
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"Off",
|
||||
"Sleep",
|
||||
"Quiet",
|
||||
"Smart",
|
||||
"Speed"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/mode/vs/0",
|
||||
"rep": {
|
||||
"rt": [
|
||||
"x.com.samsung.da.mode"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.a"
|
||||
],
|
||||
"x.com.samsung.da.modes": [
|
||||
"Cool"
|
||||
],
|
||||
"workingMode": "Cool",
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"Auto",
|
||||
"Cool",
|
||||
"Dry",
|
||||
"Fan",
|
||||
"Heat"
|
||||
],
|
||||
"x.com.samsung.da.options": [
|
||||
"OptionCode_54328",
|
||||
"Sleep_0",
|
||||
"ArtificialWorking_Off",
|
||||
"ComfortAICooling_Off",
|
||||
"AiTempChanged_Off",
|
||||
"AiTemp_240",
|
||||
"Volume_100",
|
||||
"StopAutoClean_Idle",
|
||||
"DiagnosisAI_Off",
|
||||
"ProgressDiagnosisAI_0",
|
||||
"ResultDiagnosisAI_Normal",
|
||||
"ExtendOptionCode_230024",
|
||||
"UpdateAllow_NotAllowed",
|
||||
"DurationOn_120",
|
||||
"OutdoorTemp_92",
|
||||
"CoolCapa_50",
|
||||
"WarmCapa_60",
|
||||
"Autoclean_Off",
|
||||
"WelcomeCoolingState_Off",
|
||||
"WelcomeHeatingState_Off",
|
||||
"OutdoorConnection_Connected"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/option/airpurify/vs/0",
|
||||
"rep": {
|
||||
"rt": [
|
||||
"x.com.samsung.da.option.airpurify"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.a"
|
||||
],
|
||||
"x.com.samsung.da.modes": "Off",
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"On",
|
||||
"Off"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/option/autoclean/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.status": "Stop",
|
||||
"x.com.samsung.da.supportedStatus": [
|
||||
"Start",
|
||||
"Stop"
|
||||
],
|
||||
"x.com.samsung.da.progress": "0",
|
||||
"x.com.samsung.da.settingStatus": "Off",
|
||||
"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",
|
||||
"otnStatus": "None",
|
||||
"flashingProgress": "0",
|
||||
"otnCompleteDate": "noHistory",
|
||||
"scheduledTime": "None",
|
||||
"swVersionInfo": {
|
||||
"platform": "Tizen Lite",
|
||||
"oneUiVersion": "7.0 Air conditioner",
|
||||
"osVersion": "4.0"
|
||||
},
|
||||
"otnList": [
|
||||
{
|
||||
"type": "WIFI",
|
||||
"modelId": "ARA-KR-TP1-25-ARXX00",
|
||||
"versions": [
|
||||
"11260401"
|
||||
],
|
||||
"visVersion": "260401"
|
||||
},
|
||||
{
|
||||
"type": "Micom",
|
||||
"modelId": "845210267841FFFFFFFF",
|
||||
"versions": [
|
||||
"24092500",
|
||||
"FFFFFFFF"
|
||||
],
|
||||
"visVersion": "240925"
|
||||
},
|
||||
{
|
||||
"type": "Micom",
|
||||
"modelId": "045210267841FFFFFFFF",
|
||||
"versions": [
|
||||
"24092500",
|
||||
"FFFFFFFF"
|
||||
],
|
||||
"visVersion": "240925"
|
||||
},
|
||||
{
|
||||
"type": "Micom",
|
||||
"modelId": "04521026694110263641",
|
||||
"versions": [
|
||||
"24092600",
|
||||
"10001200"
|
||||
],
|
||||
"visVersion": "240926"
|
||||
},
|
||||
{
|
||||
"type": "Micom",
|
||||
"modelId": "FGCAN3",
|
||||
"versions": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"visVersion": "999999"
|
||||
},
|
||||
{
|
||||
"type": "Micom",
|
||||
"modelId": "845210266941FFFFFFFF",
|
||||
"versions": [
|
||||
"24092600",
|
||||
"FFFFFFFF"
|
||||
],
|
||||
"visVersion": "240926"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/personality/presence/vs/0",
|
||||
"rep": {}
|
||||
},
|
||||
{
|
||||
"href": "/power/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.power": "On",
|
||||
"operationNumber": "9"
|
||||
}
|
||||
},
|
||||
{
|
||||
"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": "false",
|
||||
"x.com.samsung.da.periodicnotisubscription": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/remotedatacontrol/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.status": "Off",
|
||||
"x.com.samsung.da.connectionStatus": "Disconnected"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/remotetemperature/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.temperature": "",
|
||||
"x.com.samsung.da.unit": "",
|
||||
"x.com.samsung.da.error": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/reserverulesets/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.sets": "AEFFFFFFFF101E121EFFFF101EFFFF000001001F00010000001F0000000000FFFF1E00",
|
||||
"x.com.samsung.da.id": "RAC",
|
||||
"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": "/temperature/control/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.increment": "1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"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.current": "26.0",
|
||||
"x.com.samsung.da.desired": "22.0",
|
||||
"x.com.samsung.da.minimum": "16",
|
||||
"x.com.samsung.da.maximum": "30",
|
||||
"x.com.samsung.da.increment": "1",
|
||||
"x.com.samsung.da.unit": "Celsius"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/timezone/vs/0",
|
||||
"rep": {
|
||||
"timezoneid": "Asia/Amman",
|
||||
"offset": "+03:00",
|
||||
"DST": "OFF"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/welcome/temperature/vs/0",
|
||||
"rep": {
|
||||
"operatingStatus": "None",
|
||||
"requestId": "0000"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/wind/direction/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.modes": "All",
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"Fix",
|
||||
"Up_And_Low",
|
||||
"Left_And_Right",
|
||||
"All"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/wind/strength/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.modes": "1",
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"0",
|
||||
"1",
|
||||
"2",
|
||||
"3",
|
||||
"4"
|
||||
],
|
||||
"x.com.samsung.da.modesName": [
|
||||
"Auto",
|
||||
"Low",
|
||||
"Mid",
|
||||
"High",
|
||||
"Turbo"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/wirelessinfo/vs/0",
|
||||
"rep": {
|
||||
"macaddressWiFi": "**REDACTED**",
|
||||
"macaddressBLE": "**REDACTED**",
|
||||
"connectedApSsid": "**REDACTED**"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,380 @@
|
||||
{
|
||||
"meta": {
|
||||
"model": "TP2X_RAC_20K",
|
||||
"device_type": "airconditioner",
|
||||
"source": "issue #37 diagnostics (scrubbed)",
|
||||
"note": "Older/simpler RAC board reporting no oneUiVersion -- resolved via the '_RAC_' modelNum token fallback in for_device_by_model (unlike issue #38's TP1X board, which self-reports oneUiVersion)."
|
||||
},
|
||||
"device0": [
|
||||
{
|
||||
"rt": [
|
||||
"x.com.samsung.devcol",
|
||||
"oic.wk.col"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.ll",
|
||||
"oic.if.b"
|
||||
]
|
||||
},
|
||||
{
|
||||
"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-22T12:42:46",
|
||||
"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-22T12:42:46",
|
||||
"x.com.samsung.da.state": "Deleted"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/availablecontrolsets/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.sets": "000000A0012C01610249040F0000",
|
||||
"x.com.samsung.da.id": "RAC",
|
||||
"x.com.samsung.da.version": "1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/configuration/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.region": "0000000000",
|
||||
"x.com.samsung.da.airconOptionList": [
|
||||
"SingleCommand_1",
|
||||
"HOMECARE_WIZARD_V2",
|
||||
"PRODUCT_GLOBAL",
|
||||
"AI_RAC_GLOBAL_HEATPUMP_3.0",
|
||||
"AI_3.0",
|
||||
"Auto_To_AI",
|
||||
"AI_Heat"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/energy/consumption/0",
|
||||
"rep": {
|
||||
"power": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/energy/consumption/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.instantaneousPower": "0.000000",
|
||||
"x.com.samsung.da.cumulativePower": "717060",
|
||||
"x.com.samsung.da.cumulativeUnit": "Wh",
|
||||
"x.com.samsung.da.cumulativePowerType": "total",
|
||||
"x.com.samsung.da.instantaneousPowerUnit": "W"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/file/information/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.timeoffset": "+03:00",
|
||||
"x.com.samsung.supprtedtype": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/filter/airdustfilter/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.filterUsage": "39",
|
||||
"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/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.humidity": "0",
|
||||
"x.com.samsung.da.fivepercentHumidity": "43"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/information/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.modelNum": "TP2X_RAC_20K|10229641|60010523001511014600003200800000",
|
||||
"x.com.samsung.da.description": "TP2X_RAC_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": "010",
|
||||
"x.com.samsung.da.diagMinVersion": "1.0",
|
||||
"x.com.samsung.da.serialNumOption": "**REDACTED**",
|
||||
"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": "02273A260324",
|
||||
"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": "102296A23012700",
|
||||
"x.com.samsung.da.newVersionAvailable": "0"
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "2",
|
||||
"x.com.samsung.da.description": "Version",
|
||||
"x.com.samsung.da.type": "Firmware",
|
||||
"x.com.samsung.da.newVersionAvailable": "0"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/keepnormalstate/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.keepnormal": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/mode/convenient/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.modes": "Off",
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"Off",
|
||||
"Sleep",
|
||||
"Quiet",
|
||||
"Smart",
|
||||
"Speed",
|
||||
"MotionIndirect",
|
||||
"MotionDirect",
|
||||
"Nano",
|
||||
"NanoSleep"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/mode/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"Auto",
|
||||
"Cool",
|
||||
"Dry",
|
||||
"Wind",
|
||||
"Heat",
|
||||
"HOMECARE_WIZARD_V2"
|
||||
],
|
||||
"x.com.samsung.da.modes": [
|
||||
"Cool"
|
||||
],
|
||||
"x.com.samsung.da.options": [
|
||||
"Sleep_16",
|
||||
"ArtificialWorking_Off",
|
||||
"ComfortAICooling_Off",
|
||||
"MotionState_Normal",
|
||||
"AiTempChanged_Off",
|
||||
"AiTemp_240",
|
||||
"OutdoorTemp_85",
|
||||
"CoolCapa_35",
|
||||
"WarmCapa_35",
|
||||
"Light_Off",
|
||||
"Volume_100",
|
||||
"StopAutoClean_Idle",
|
||||
"Autoclean_Off",
|
||||
"DiagnosisAI_Off",
|
||||
"ProgressDiagnosisAI_1",
|
||||
"ResultDiagnosisAI_Normal",
|
||||
"MotionMode_PowerSave",
|
||||
"OptionCode_56376",
|
||||
"ExtendOptionCode_230157",
|
||||
"RacInfo_None",
|
||||
"UpdateAllow_NotAllowed",
|
||||
"DurationOn_50",
|
||||
"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": "TP2X_RAC_20K",
|
||||
"versions": [
|
||||
"11260324"
|
||||
],
|
||||
"visVersion": "260324"
|
||||
},
|
||||
{
|
||||
"type": "Micom",
|
||||
"modelId": "045210229641FFFFFFFF",
|
||||
"versions": [
|
||||
"23012700",
|
||||
"FFFFFFFF"
|
||||
],
|
||||
"visVersion": "230127"
|
||||
},
|
||||
{
|
||||
"type": "Micom",
|
||||
"modelId": "04521022954110229941",
|
||||
"versions": [
|
||||
"21092700",
|
||||
"10000300"
|
||||
],
|
||||
"visVersion": "210927"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"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": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/power/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.power": "Off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/realtimenotiforclient/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.timeforshortnoti": "0",
|
||||
"x.com.samsung.da.longnotisubscription": "false",
|
||||
"x.com.samsung.da.periodicnotisubscription": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/sensors/vs/0",
|
||||
"rep": {}
|
||||
},
|
||||
{
|
||||
"href": "/temperature/control/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.increment": "1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"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": "22.0",
|
||||
"x.com.samsung.da.current": "26.0",
|
||||
"x.com.samsung.da.maximum": "30",
|
||||
"x.com.samsung.da.minimum": "16",
|
||||
"x.com.samsung.da.increment": "1.0",
|
||||
"x.com.samsung.da.unit": "Celsius"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/timezone/vs/0",
|
||||
"rep": {
|
||||
"timezoneid": "Asia/Amman",
|
||||
"offset": "+03:00",
|
||||
"DST": "OFF"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/wind/direction/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.modes": "Left_And_Right",
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"Fix",
|
||||
"All",
|
||||
"Up_And_Low",
|
||||
"Left_And_Right"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/wind/strength/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.modes": "2",
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"0",
|
||||
"1",
|
||||
"2",
|
||||
"3",
|
||||
"4"
|
||||
],
|
||||
"x.com.samsung.da.modesName": [
|
||||
"Auto",
|
||||
"Low",
|
||||
"Mid",
|
||||
"High",
|
||||
"Turbo"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
+1
@@ -8,6 +8,7 @@
|
||||
"climate",
|
||||
"diagnosis_status",
|
||||
"energy_kwh",
|
||||
"energy_saved_kwh",
|
||||
"power_watts"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"state_keys": [
|
||||
"air_filter_status",
|
||||
"air_filter_usage",
|
||||
"air_purify",
|
||||
"alarm_code",
|
||||
"auto_clean",
|
||||
"climate",
|
||||
"display_light",
|
||||
"energy_kwh",
|
||||
"energy_saved_kwh",
|
||||
"firmware_update",
|
||||
"mute_once",
|
||||
"selfcheck_error",
|
||||
"selfcheck_result",
|
||||
"selfcheck_status"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"state_keys": [
|
||||
"air_filter_status",
|
||||
"air_filter_usage",
|
||||
"air_purify",
|
||||
"alarm_code",
|
||||
"auto_clean",
|
||||
"climate",
|
||||
"current_limit_enabled",
|
||||
"current_limit_level",
|
||||
"display_light",
|
||||
"energy_kwh",
|
||||
"energy_saved_kwh",
|
||||
"firmware_update",
|
||||
"mute_once",
|
||||
"selfcheck_error",
|
||||
"selfcheck_result",
|
||||
"selfcheck_status"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"state_keys": [
|
||||
"air_filter_status",
|
||||
"air_filter_usage",
|
||||
"alarm_code",
|
||||
"auto_clean",
|
||||
"climate",
|
||||
"energy_kwh",
|
||||
"firmware_update",
|
||||
"mute_once",
|
||||
"power_watts"
|
||||
]
|
||||
}
|
||||
+2
@@ -8,6 +8,7 @@
|
||||
"delay_start_hours",
|
||||
"diagnosis_status",
|
||||
"energy_kwh",
|
||||
"energy_saved_kwh",
|
||||
"finish_time",
|
||||
"firmware_update",
|
||||
"heated_dry",
|
||||
@@ -39,6 +40,7 @@
|
||||
"samsung_dishwasher_diagnosis_start",
|
||||
"samsung_dishwasher_diagnosis_status",
|
||||
"samsung_dishwasher_energy_kwh",
|
||||
"samsung_dishwasher_energy_saved_kwh",
|
||||
"samsung_dishwasher_finish_time",
|
||||
"samsung_dishwasher_firmware_update",
|
||||
"samsung_dishwasher_heated_dry",
|
||||
|
||||
+12
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"state_keys": [
|
||||
"ai_energy_level",
|
||||
"alarm_code",
|
||||
"auto_door_opener",
|
||||
"autofill",
|
||||
@@ -16,6 +17,9 @@
|
||||
"door_cooler_open",
|
||||
"door_freezer_open",
|
||||
"energy_kwh",
|
||||
"energy_last_month_kwh",
|
||||
"energy_saved_kwh",
|
||||
"energy_this_month_kwh",
|
||||
"filter_status",
|
||||
"filter_usage",
|
||||
"firmware_update",
|
||||
@@ -31,15 +35,18 @@
|
||||
"icemaker_two_type",
|
||||
"night_end",
|
||||
"night_start",
|
||||
"power_energy_kwh",
|
||||
"power_watts",
|
||||
"rapid_freezing",
|
||||
"rapid_fridge",
|
||||
"sabbath_mode",
|
||||
"selfcheck_error",
|
||||
"selfcheck_result",
|
||||
"selfcheck_status",
|
||||
"welcome_lighting"
|
||||
],
|
||||
"discovery_unique_ids": [
|
||||
"samsung_refrigerator_ai_energy_level",
|
||||
"samsung_refrigerator_alarm_code",
|
||||
"samsung_refrigerator_auto_door_opener",
|
||||
"samsung_refrigerator_autofill",
|
||||
@@ -56,6 +63,9 @@
|
||||
"samsung_refrigerator_door_cooler_open",
|
||||
"samsung_refrigerator_door_freezer_open",
|
||||
"samsung_refrigerator_energy_kwh",
|
||||
"samsung_refrigerator_energy_last_month_kwh",
|
||||
"samsung_refrigerator_energy_saved_kwh",
|
||||
"samsung_refrigerator_energy_this_month_kwh",
|
||||
"samsung_refrigerator_filter_status",
|
||||
"samsung_refrigerator_filter_usage",
|
||||
"samsung_refrigerator_firmware_update",
|
||||
@@ -71,10 +81,12 @@
|
||||
"samsung_refrigerator_icemaker_two_type",
|
||||
"samsung_refrigerator_night_end",
|
||||
"samsung_refrigerator_night_start",
|
||||
"samsung_refrigerator_power_energy_kwh",
|
||||
"samsung_refrigerator_power_watts",
|
||||
"samsung_refrigerator_rapid_freezing",
|
||||
"samsung_refrigerator_rapid_fridge",
|
||||
"samsung_refrigerator_sabbath_mode",
|
||||
"samsung_refrigerator_selfcheck_error",
|
||||
"samsung_refrigerator_selfcheck_result",
|
||||
"samsung_refrigerator_selfcheck_start",
|
||||
"samsung_refrigerator_selfcheck_status",
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"state_keys": [
|
||||
"alarm_code",
|
||||
"autofill",
|
||||
"cooler_setpoint",
|
||||
"cooler_temperature",
|
||||
"defrost_active",
|
||||
"defrost_delay",
|
||||
"diagnosis_status",
|
||||
"door_cooler_open",
|
||||
"door_freezer_open",
|
||||
"energy_last_month_kwh",
|
||||
"energy_this_month_kwh",
|
||||
"filter_status",
|
||||
"filter_usage",
|
||||
"firmware_update",
|
||||
"freezer_setpoint",
|
||||
"freezer_temperature",
|
||||
"icemaker_one_enabled",
|
||||
"icemaker_one_making_status",
|
||||
"pantry_zone_mode",
|
||||
"power_energy_kwh",
|
||||
"power_watts",
|
||||
"rapid_freezing",
|
||||
"rapid_fridge",
|
||||
"sabbath_mode"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"state_keys": [
|
||||
"ai_energy_level",
|
||||
"alarm_code",
|
||||
"autofill",
|
||||
"cooler_setpoint",
|
||||
"cooler_temperature",
|
||||
"defrost_active",
|
||||
"defrost_delay",
|
||||
"door_cooler_open",
|
||||
"door_cvroom_open",
|
||||
"door_freezer_open",
|
||||
"energy_kwh",
|
||||
"energy_last_month_kwh",
|
||||
"energy_saved_kwh",
|
||||
"energy_this_month_kwh",
|
||||
"filter_status",
|
||||
"filter_usage",
|
||||
"firmware_update",
|
||||
"flex_zone_mode",
|
||||
"freezer_setpoint",
|
||||
"freezer_temperature",
|
||||
"icemaker_one_enabled",
|
||||
"icemaker_one_making_status",
|
||||
"icemaker_two_enabled",
|
||||
"icemaker_two_making_status",
|
||||
"power_energy_kwh",
|
||||
"power_watts",
|
||||
"rapid_freezing",
|
||||
"rapid_fridge",
|
||||
"sabbath_mode",
|
||||
"selfcheck_error",
|
||||
"selfcheck_result",
|
||||
"selfcheck_status"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"state_keys": [
|
||||
"alarm_code",
|
||||
"cooler_setpoint",
|
||||
"cooler_temperature",
|
||||
"defrost_active",
|
||||
"defrost_delay",
|
||||
"diagnosis_status",
|
||||
"door_cooler_open",
|
||||
"door_cvroom_open",
|
||||
"door_freezer_open",
|
||||
"energy_kwh",
|
||||
"energy_last_month_kwh",
|
||||
"energy_this_month_kwh",
|
||||
"filter_status",
|
||||
"filter_usage",
|
||||
"firmware_update",
|
||||
"flex_zone_mode",
|
||||
"freezer_setpoint",
|
||||
"freezer_temperature",
|
||||
"icemaker_one_enabled",
|
||||
"icemaker_one_making_status",
|
||||
"power_energy_kwh",
|
||||
"power_watts",
|
||||
"rapid_freezing",
|
||||
"rapid_fridge",
|
||||
"sabbath_mode"
|
||||
]
|
||||
}
|
||||
Vendored
+5
@@ -1,6 +1,8 @@
|
||||
{
|
||||
"state_keys": [
|
||||
"ai_energy_level",
|
||||
"alarm_code",
|
||||
"bubble_soak",
|
||||
"buzzer_sound",
|
||||
"child_lock",
|
||||
"cycle",
|
||||
@@ -11,11 +13,14 @@
|
||||
"drum_clean_cycles_remaining",
|
||||
"drum_clean_last_cleaned",
|
||||
"energy_kwh",
|
||||
"energy_saved_kwh",
|
||||
"finish_time",
|
||||
"firmware_update",
|
||||
"intensive",
|
||||
"job_beginning_status",
|
||||
"machine_state",
|
||||
"power_switch",
|
||||
"pre_wash",
|
||||
"progress",
|
||||
"progress_percentage",
|
||||
"remote_control",
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"state_keys": [
|
||||
"alarm_code",
|
||||
"bubble_soak",
|
||||
"child_lock",
|
||||
"cycle",
|
||||
"cycle_active",
|
||||
"delay_start_hours",
|
||||
"detergent_low",
|
||||
"diagnosis_status",
|
||||
"drum_clean_cycles_remaining",
|
||||
"dry_level",
|
||||
"energy_kwh",
|
||||
"finish_time",
|
||||
"firmware_update",
|
||||
"intensive",
|
||||
"job_beginning_status",
|
||||
"machine_state",
|
||||
"power_switch",
|
||||
"pre_wash",
|
||||
"progress",
|
||||
"progress_percentage",
|
||||
"remote_control",
|
||||
"rinse_cycles",
|
||||
"softener_low",
|
||||
"spin_speed",
|
||||
"wash_temperature"
|
||||
]
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"state_keys": [
|
||||
"alarm_code",
|
||||
"child_lock",
|
||||
"cycle_active",
|
||||
"delay_start_hours",
|
||||
"detergent_low",
|
||||
"diagnosis_status",
|
||||
"drum_clean_cycles_remaining",
|
||||
"energy_kwh",
|
||||
"finish_time",
|
||||
"job_beginning_status",
|
||||
"machine_state",
|
||||
"power_switch",
|
||||
"progress",
|
||||
"progress_percentage",
|
||||
"remote_control",
|
||||
"rinse_cycles",
|
||||
"spin_speed",
|
||||
"wash_temperature"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,351 @@
|
||||
{
|
||||
"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": "/temperatures/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.items": [
|
||||
{
|
||||
"x.com.samsung.da.id": "0",
|
||||
"x.com.samsung.da.description": "Freezer",
|
||||
"x.com.samsung.da.desired": "-21",
|
||||
"x.com.samsung.da.current": "-21",
|
||||
"x.com.samsung.da.maximum": "-15",
|
||||
"x.com.samsung.da.minimum": "-23",
|
||||
"x.com.samsung.da.unit": "Celsius"
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "1",
|
||||
"x.com.samsung.da.description": "Fridge",
|
||||
"x.com.samsung.da.desired": "2",
|
||||
"x.com.samsung.da.current": "2",
|
||||
"x.com.samsung.da.maximum": "7",
|
||||
"x.com.samsung.da.minimum": "1",
|
||||
"x.com.samsung.da.unit": "Celsius"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/temperature/current/freezer/0",
|
||||
"rep": {
|
||||
"range": [
|
||||
-23.0,
|
||||
-15.0
|
||||
],
|
||||
"units": "C",
|
||||
"temperature": -21.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/temperature/desired/freezer/0",
|
||||
"rep": {
|
||||
"range": [
|
||||
-23.0,
|
||||
-15.0
|
||||
],
|
||||
"units": "C",
|
||||
"temperature": -21.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/temperature/current/cooler/0",
|
||||
"rep": {
|
||||
"range": [
|
||||
1.0,
|
||||
7.0
|
||||
],
|
||||
"units": "C",
|
||||
"temperature": 2.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/temperature/desired/cooler/0",
|
||||
"rep": {
|
||||
"range": [
|
||||
1.0,
|
||||
7.0
|
||||
],
|
||||
"units": "C",
|
||||
"temperature": 2.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/diagnosis/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.diagnosisStart": "Ready"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/energy/consumption/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.cumulativeConsumption": "0",
|
||||
"x.com.samsung.da.cumulativeUnit": "Wh",
|
||||
"x.com.samsung.da.instantaneousPower": "0",
|
||||
"x.com.samsung.da.instantaneousPowerUnit": "W",
|
||||
"x.com.samsung.da.monthlyConsumption": "86000",
|
||||
"x.com.samsung.da.thismonthlyConsumption": "64026"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/mode/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"HOMECARE_WIZARD_V2",
|
||||
"ENERGY_REPORT_MODEL",
|
||||
"18K_REF_OUTDOOR_CONTROL_V2",
|
||||
"SUPPORT_SABBATH_CONTROL"
|
||||
],
|
||||
"x.com.samsung.da.modes": [
|
||||
"WATERFILTER_ENABLE"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/mode/0",
|
||||
"rep": {
|
||||
"supportedModes": [
|
||||
"HOMECARE_WIZARD_V2",
|
||||
"ENERGY_REPORT_MODEL",
|
||||
"18K_REF_OUTDOOR_CONTROL_V2",
|
||||
"SUPPORT_SABBATH_CONTROL"
|
||||
],
|
||||
"modes": [
|
||||
"WATERFILTER_ENABLE"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/defrost/block/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"DEFROST_BLOCK_ON",
|
||||
"DEFROST_BLOCK_OFF"
|
||||
],
|
||||
"x.com.samsung.da.modes": [
|
||||
"DEFROST_BLOCK_OFF"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/sabbath/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.sabbathMode": "Off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/realtimenotiforclient/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.timeforshortnoti": "0",
|
||||
"x.com.samsung.da.periodicnotisubscription": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/information/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.modelNum": "ARTIK051_REF_17K|00117141|0002074E051311200103000010000000",
|
||||
"x.com.samsung.da.description": "ARTIK051_REF_17K",
|
||||
"x.com.samsung.da.serialNum": "REDACTED",
|
||||
"x.com.samsung.da.otnDUID": "REDACTED",
|
||||
"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": "02144A201124",
|
||||
"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": "2003030C,FFFFFFFF",
|
||||
"x.com.samsung.da.newVersionAvailable": "0"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/file/information/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.timeoffset": "-04:00",
|
||||
"x.com.samsung.supprtedtype": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/doors/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.items": [
|
||||
{
|
||||
"x.com.samsung.da.id": "0",
|
||||
"x.com.samsung.da.description": "Door",
|
||||
"x.com.samsung.da.openState": "Close"
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "1",
|
||||
"x.com.samsung.da.description": "Door",
|
||||
"x.com.samsung.da.openState": "Close"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/door/freezer/0",
|
||||
"rep": {
|
||||
"openState": "Close"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/door/cooler/0",
|
||||
"rep": {
|
||||
"openState": "Close"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/configuration/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.countryCode": "US",
|
||||
"x.com.samsung.da.region": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/defrost/delay/0",
|
||||
"rep": {
|
||||
"value": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/defrost/delay/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.delayDefrost": "Off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/defrost/reservation/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.items": [
|
||||
{
|
||||
"x.com.samsung.da.id": "0",
|
||||
"x.com.samsung.da.description": "Summer Season",
|
||||
"x.com.samsung.da.startTime": "0000-05-01T15:00:00",
|
||||
"x.com.samsung.da.period": "04:00:00",
|
||||
"x.com.samsung.da.endTime": "0000-10-31T00:00:00"
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "1",
|
||||
"x.com.samsung.da.description": "Winter Season",
|
||||
"x.com.samsung.da.startTime": "0000-11-01T06:00:00",
|
||||
"x.com.samsung.da.period": "04:00:00",
|
||||
"x.com.samsung.da.endTime": "0000-04-30T00:00:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/icemaker/status/0",
|
||||
"rep": {
|
||||
"status": "On"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/icemaker/status/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.iceMaker": "On"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/refrigeration/0",
|
||||
"rep": {
|
||||
"defrost": false,
|
||||
"rapidFreeze": false,
|
||||
"rapidCool": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/refrigeration/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.rapidFridge": "Off",
|
||||
"x.com.samsung.da.rapidFreezing": "Off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/drlc/0",
|
||||
"rep": {
|
||||
"DRLevel": 0,
|
||||
"start": "00:00:00",
|
||||
"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": "00:00:00",
|
||||
"x.com.samsung.da.override": "Off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/otninformation/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.target": "Micom",
|
||||
"x.com.samsung.da.newVersionAvailable": "false"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/filter/waterfilter/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.filterUsage": "100",
|
||||
"x.com.samsung.da.filterUsageResolution": "1",
|
||||
"x.com.samsung.da.filterStatus": "replace",
|
||||
"x.com.samsung.da.filterResetType": [
|
||||
"replaceable"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/autofill/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.autofill": "Off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/icemaker/one/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.iceMaker.name": "ICE_MAKER",
|
||||
"x.com.samsung.da.iceMaker.state": "On",
|
||||
"x.com.samsung.da.iceMaker.type": "toggle",
|
||||
"x.com.samsung.da.iceMaker.iceMakingStatus": "ICESTATUS_STOP",
|
||||
"x.com.samsung.da.iceType.desired": "NORMAL"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/status/pantry/one/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.controllable": "true",
|
||||
"x.com.samsung.da.name": "COOL_SELECT_ZONE",
|
||||
"x.com.samsung.da.mode": "FDR_DRINKS",
|
||||
"x.com.samsung.da.supportedOptions": [
|
||||
"FDR_WINE",
|
||||
"FDR_DELI",
|
||||
"FDR_DRINKS"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
+2
-1
@@ -279,7 +279,8 @@
|
||||
"rep": {
|
||||
"aiLevel": "1",
|
||||
"supportedAiLevel": [
|
||||
"1"
|
||||
"1",
|
||||
"2"
|
||||
],
|
||||
"href": "/energy/ailevel/vs/0"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,253 @@
|
||||
{
|
||||
"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": "/temperatures/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.items": [
|
||||
{
|
||||
"x.com.samsung.da.id": "0",
|
||||
"x.com.samsung.da.description": "Freezer",
|
||||
"x.com.samsung.da.desired": "0",
|
||||
"x.com.samsung.da.current": "0",
|
||||
"x.com.samsung.da.maximum": "5",
|
||||
"x.com.samsung.da.minimum": "-8",
|
||||
"x.com.samsung.da.unit": "Fahrenheit"
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "1",
|
||||
"x.com.samsung.da.description": "Fridge",
|
||||
"x.com.samsung.da.desired": "39",
|
||||
"x.com.samsung.da.current": "39",
|
||||
"x.com.samsung.da.maximum": "44",
|
||||
"x.com.samsung.da.minimum": "34",
|
||||
"x.com.samsung.da.unit": "Fahrenheit"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{"href": "/temperature/current/freezer/0", "rep": {"range": [-8.0, 5.0], "units": "F", "temperature": 0.0}},
|
||||
{"href": "/temperature/desired/freezer/0", "rep": {"range": [-8.0, 5.0], "units": "F", "temperature": 0.0}},
|
||||
{"href": "/temperature/current/cooler/0", "rep": {"range": [34.0, 44.0], "units": "F", "temperature": 39.0}},
|
||||
{"href": "/temperature/desired/cooler/0", "rep": {"range": [34.0, 44.0], "units": "F", "temperature": 39.0}},
|
||||
{
|
||||
"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": "/energy/consumption/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.cumulativeConsumption": "63",
|
||||
"x.com.samsung.da.cumulativePower": "1831899",
|
||||
"x.com.samsung.da.cumulativeUnit": "Wh",
|
||||
"x.com.samsung.da.instantaneousPower": "3",
|
||||
"x.com.samsung.da.instantaneousPowerUnit": "W",
|
||||
"x.com.samsung.da.monthlyConsumption": "52000",
|
||||
"x.com.samsung.da.thismonthlyConsumption": "38900",
|
||||
"x.com.samsung.da.cumulativeSavedPower": "0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/mode/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"HOMECARE_WIZARD_V2",
|
||||
"ENERGY_REPORT_MODEL",
|
||||
"18K_REF_OUTDOOR_CONTROL_V2",
|
||||
"SUPPORT_SABBATH_CONTROL"
|
||||
],
|
||||
"x.com.samsung.da.modes": ["CVN_CONVERTIBLE_ZONE", "CV_FDR_WINE", "WATERFILTER_DISABLE"],
|
||||
"x.com.samsung.da.supportedOptions": [
|
||||
"CV_FDR_WINE",
|
||||
"CV_FDR_DELI",
|
||||
"CV_FDR_BEVERAGE",
|
||||
"CV_FDR_MEAT",
|
||||
"CV_FDR_SOFT_FREEZER"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/mode/0",
|
||||
"rep": {
|
||||
"supportedModes": [
|
||||
"HOMECARE_WIZARD_V2",
|
||||
"ENERGY_REPORT_MODEL",
|
||||
"18K_REF_OUTDOOR_CONTROL_V2",
|
||||
"SUPPORT_SABBATH_CONTROL"
|
||||
],
|
||||
"modes": ["CVN_CONVERTIBLE_ZONE", "CV_FDR_WINE", "WATERFILTER_DISABLE"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/defrost/block/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.supportedModes": ["DEFROST_BLOCK_ON", "DEFROST_BLOCK_OFF"],
|
||||
"x.com.samsung.da.modes": ["DEFROST_BLOCK_OFF"]
|
||||
}
|
||||
},
|
||||
{"href": "/sabbath/vs/0", "rep": {"x.com.samsung.da.sabbathMode": "Off"}},
|
||||
{
|
||||
"href": "/realtimenotiforclient/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.timeforshortnoti": "0",
|
||||
"x.com.samsung.da.periodicnotisubscription": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/information/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.modelNum": "TP1X_REF_21K|00148241|00020850031611204103000021010000",
|
||||
"x.com.samsung.da.description": "TP1X_REF_21K",
|
||||
"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": "512",
|
||||
"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": "250420",
|
||||
"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": "23071923, FFFFFFFF",
|
||||
"x.com.samsung.da.newVersionAvailable": "0"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{"href": "/file/information/vs/0", "rep": {"x.com.samsung.timeoffset": "-05:00", "x.com.samsung.supprtedtype": 1}},
|
||||
{
|
||||
"href": "/doors/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.items": [
|
||||
{"x.com.samsung.da.id": "0", "x.com.samsung.da.description": "Door", "x.com.samsung.da.openState": "Close"},
|
||||
{"x.com.samsung.da.id": "1", "x.com.samsung.da.description": "Door", "x.com.samsung.da.openState": "Close"},
|
||||
{"x.com.samsung.da.id": "2", "x.com.samsung.da.description": "Door", "x.com.samsung.da.openState": "Close"}
|
||||
]
|
||||
}
|
||||
},
|
||||
{"href": "/door/freezer/0", "rep": {"openState": "Close"}},
|
||||
{"href": "/door/cooler/0", "rep": {"openState": "Close"}},
|
||||
{"href": "/door/cvroom/0", "rep": {"openState": "Close"}},
|
||||
{"href": "/configuration/vs/0", "rep": {"x.com.samsung.da.countryCode": "", "x.com.samsung.da.region": ""}},
|
||||
{"href": "/defrost/delay/0", "rep": {"value": false}},
|
||||
{"href": "/defrost/delay/vs/0", "rep": {"x.com.samsung.da.delayDefrost": "Off"}},
|
||||
{
|
||||
"href": "/defrost/reservation/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.items": [
|
||||
{
|
||||
"x.com.samsung.da.id": "0",
|
||||
"x.com.samsung.da.description": "Summer Season",
|
||||
"x.com.samsung.da.startTime": "0000-05-01T15:00:00",
|
||||
"x.com.samsung.da.period": "04:00:00",
|
||||
"x.com.samsung.da.endTime": "0000-10-31T00:00:00"
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "1",
|
||||
"x.com.samsung.da.description": "Winter Season",
|
||||
"x.com.samsung.da.startTime": "0000-11-01T06:00:00",
|
||||
"x.com.samsung.da.period": "04:00:00",
|
||||
"x.com.samsung.da.endTime": "0000-04-30T00:00:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{"href": "/icemaker/status/0", "rep": {"status": "On"}},
|
||||
{"href": "/icemaker/status/vs/0", "rep": {"x.com.samsung.da.iceMaker": "On"}},
|
||||
{"href": "/refrigeration/0", "rep": {"defrost": false, "rapidFreeze": false, "rapidCool": false}},
|
||||
{
|
||||
"href": "/refrigeration/vs/0",
|
||||
"rep": {"x.com.samsung.da.rapidFridge": "Off", "x.com.samsung.da.rapidFreezing": "Off"}
|
||||
},
|
||||
{"href": "/drlc/0", "rep": {"DRLevel": 0, "override": false}},
|
||||
{
|
||||
"href": "/drlc/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.drlcLevel": "0",
|
||||
"x.com.samsung.da.override": "Off",
|
||||
"x.com.samsung.da.durationminutes": "0",
|
||||
"x.com.samsung.da.realSaving": "Off"
|
||||
}
|
||||
},
|
||||
{"href": "/bespoke/vs/0", "rep": {"x.com.samsung.da.BespokeProduct": "On"}},
|
||||
{
|
||||
"href": "/otninformation/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.target": "",
|
||||
"x.com.samsung.da.newVersionAvailable": "false",
|
||||
"otnStatus": "None",
|
||||
"flashingProgress": "",
|
||||
"otnList": [
|
||||
{"type": "WIFI", "modelId": "A-RFWW-TP1-22-REV1", "versions": ["20250420"], "visVersion": "250420"},
|
||||
{"type": "Micom", "modelId": "024100148241FFFFFFFF", "versions": ["23071923", "FFFFFFFF"], "visVersion": "230719"}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/filter/waterfilter/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.filterUsage": "31",
|
||||
"x.com.samsung.da.filterUsageResolution": "1",
|
||||
"x.com.samsung.da.filterStatus": "normal",
|
||||
"x.com.samsung.da.filterResetType": ["replaceable"]
|
||||
}
|
||||
},
|
||||
{"href": "/autofill/vs/0", "rep": {"x.com.samsung.da.autofill": "Off"}},
|
||||
{
|
||||
"href": "/icemaker/one/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.iceMaker.name": "CUBED_ICE",
|
||||
"x.com.samsung.da.iceMaker.state": "On",
|
||||
"x.com.samsung.da.iceMaker.type": "toggle",
|
||||
"x.com.samsung.da.iceMaker.iceMakingStatus": "ICESTATUS_STOP",
|
||||
"x.com.samsung.da.iceType.desired": "NORMAL"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/icemaker/two/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.iceMaker.name": "ICE_BITES",
|
||||
"x.com.samsung.da.iceMaker.state": "On",
|
||||
"x.com.samsung.da.iceMaker.type": "toggle",
|
||||
"x.com.samsung.da.iceMaker.iceMakingStatus": "ICESTATUS_STOP",
|
||||
"x.com.samsung.da.iceType.desired": "On"
|
||||
}
|
||||
},
|
||||
{"href": "/energy/ailevel/vs/0", "rep": {"aiLevel": "1", "supportedAiLevel": ["1"]}},
|
||||
{
|
||||
"href": "/connectionconfig/vs/0",
|
||||
"rep": {
|
||||
"autoReconnectionMinVersion": "1.0",
|
||||
"autoReconnection": "true",
|
||||
"autoReconnectionProtocolType": ["ble_ocf", null],
|
||||
"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": "/timezone/vs/0", "rep": {"timezoneid": "America/Chicago", "offset": "-05:00", "DST": "ON"}},
|
||||
{"href": "/wirelessinfo/vs/0", "rep": {"macaddressWiFi": "REDACTED", "macaddressBLE": "REDACTED"}},
|
||||
{"href": "/quickcontrol/info/vs/0", "rep": {"supportedVersion": "1.0"}}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,351 @@
|
||||
{
|
||||
"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": "/temperatures/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.items": [
|
||||
{
|
||||
"x.com.samsung.da.id": "0",
|
||||
"x.com.samsung.da.description": "Freezer",
|
||||
"x.com.samsung.da.desired": "0",
|
||||
"x.com.samsung.da.current": "0",
|
||||
"x.com.samsung.da.maximum": "5",
|
||||
"x.com.samsung.da.minimum": "-8",
|
||||
"x.com.samsung.da.unit": "Fahrenheit"
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "1",
|
||||
"x.com.samsung.da.description": "Fridge",
|
||||
"x.com.samsung.da.desired": "37",
|
||||
"x.com.samsung.da.current": "37",
|
||||
"x.com.samsung.da.maximum": "44",
|
||||
"x.com.samsung.da.minimum": "34",
|
||||
"x.com.samsung.da.unit": "Fahrenheit"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/temperature/current/freezer/0",
|
||||
"rep": {
|
||||
"range": [
|
||||
-8.0,
|
||||
5.0
|
||||
],
|
||||
"units": "F",
|
||||
"temperature": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/temperature/desired/freezer/0",
|
||||
"rep": {
|
||||
"range": [
|
||||
-8.0,
|
||||
5.0
|
||||
],
|
||||
"units": "F",
|
||||
"temperature": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/temperature/current/cooler/0",
|
||||
"rep": {
|
||||
"range": [
|
||||
34.0,
|
||||
44.0
|
||||
],
|
||||
"units": "F",
|
||||
"temperature": 37.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/temperature/desired/cooler/0",
|
||||
"rep": {
|
||||
"range": [
|
||||
34.0,
|
||||
44.0
|
||||
],
|
||||
"units": "F",
|
||||
"temperature": 37.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/diagnosis/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.diagnosisStart": "Ready"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/energy/consumption/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.cumulativeConsumption": "288048",
|
||||
"x.com.samsung.da.cumulativePower": "1797027",
|
||||
"x.com.samsung.da.cumulativeUnit": "Wh",
|
||||
"x.com.samsung.da.instantaneousPower": "115",
|
||||
"x.com.samsung.da.instantaneousPowerUnit": "W",
|
||||
"x.com.samsung.da.monthlyConsumption": "55400",
|
||||
"x.com.samsung.da.thismonthlyConsumption": "39737"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/mode/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"HOMECARE_WIZARD_V2",
|
||||
"ENERGY_REPORT_MODEL",
|
||||
"18K_REF_OUTDOOR_CONTROL_V2",
|
||||
"SUPPORT_SABBATH_CONTROL"
|
||||
],
|
||||
"x.com.samsung.da.modes": [
|
||||
"CVN_CONVERTIBLE_ZONE",
|
||||
"CV_FDR_BEVERAGE",
|
||||
"WATERFILTER_DISABLE"
|
||||
],
|
||||
"x.com.samsung.da.supportedOptions": [
|
||||
"CV_FDR_WINE",
|
||||
"CV_FDR_DELI",
|
||||
"CV_FDR_BEVERAGE",
|
||||
"CV_FDR_MEAT"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/mode/0",
|
||||
"rep": {
|
||||
"supportedModes": [
|
||||
"HOMECARE_WIZARD_V2",
|
||||
"ENERGY_REPORT_MODEL",
|
||||
"18K_REF_OUTDOOR_CONTROL_V2",
|
||||
"SUPPORT_SABBATH_CONTROL"
|
||||
],
|
||||
"modes": [
|
||||
"CVN_CONVERTIBLE_ZONE",
|
||||
"CV_FDR_BEVERAGE",
|
||||
"WATERFILTER_DISABLE"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/defrost/block/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"DEFROST_BLOCK_ON",
|
||||
"DEFROST_BLOCK_OFF"
|
||||
],
|
||||
"x.com.samsung.da.modes": [
|
||||
"DEFROST_BLOCK_OFF"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/sabbath/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.sabbathMode": "Off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/realtimenotiforclient/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.timeforshortnoti": "0",
|
||||
"x.com.samsung.da.periodicnotisubscription": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/information/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.modelNum": "TP2X_REF_20K|00113141|0002033F051311200103000010000000",
|
||||
"x.com.samsung.da.description": "TP2X_REF_20K",
|
||||
"x.com.samsung.da.serialNum": "REDACTED",
|
||||
"x.com.samsung.da.otnDUID": "REDACTED",
|
||||
"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": "02144A220110",
|
||||
"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": "2011030E,FFFFFFFF",
|
||||
"x.com.samsung.da.newVersionAvailable": "0"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/file/information/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.timeoffset": "+00:00",
|
||||
"x.com.samsung.supprtedtype": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/doors/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.items": [
|
||||
{
|
||||
"x.com.samsung.da.id": "0",
|
||||
"x.com.samsung.da.description": "Door",
|
||||
"x.com.samsung.da.openState": "Close"
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "1",
|
||||
"x.com.samsung.da.description": "Door",
|
||||
"x.com.samsung.da.openState": "Close"
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "2",
|
||||
"x.com.samsung.da.description": "Door",
|
||||
"x.com.samsung.da.openState": "Close"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/door/freezer/0",
|
||||
"rep": {
|
||||
"openState": "Close"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/door/cooler/0",
|
||||
"rep": {
|
||||
"openState": "Close"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/door/cvroom/0",
|
||||
"rep": {
|
||||
"openState": "Close"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/configuration/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.countryCode": "US",
|
||||
"x.com.samsung.da.region": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/defrost/delay/0",
|
||||
"rep": {
|
||||
"value": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/defrost/delay/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.delayDefrost": "Off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/defrost/reservation/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.items": [
|
||||
{
|
||||
"x.com.samsung.da.id": "0",
|
||||
"x.com.samsung.da.description": "Summer Season",
|
||||
"x.com.samsung.da.startTime": "0000-05-01T15:00:00",
|
||||
"x.com.samsung.da.period": "04:00:00",
|
||||
"x.com.samsung.da.endTime": "0000-10-31T00:00:00"
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "1",
|
||||
"x.com.samsung.da.description": "Winter Season",
|
||||
"x.com.samsung.da.startTime": "0000-11-01T06:00:00",
|
||||
"x.com.samsung.da.period": "04:00:00",
|
||||
"x.com.samsung.da.endTime": "0000-04-30T00:00:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/icemaker/status/0",
|
||||
"rep": {
|
||||
"status": "On"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/icemaker/status/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.iceMaker": "On"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/refrigeration/0",
|
||||
"rep": {
|
||||
"defrost": false,
|
||||
"rapidFreeze": false,
|
||||
"rapidCool": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/refrigeration/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.rapidFridge": "Off",
|
||||
"x.com.samsung.da.rapidFreezing": "Off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/drlc/0",
|
||||
"rep": {
|
||||
"DRLevel": 0,
|
||||
"override": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/drlc/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.drlcLevel": "0",
|
||||
"x.com.samsung.da.override": "Off",
|
||||
"x.com.samsung.da.durationminutes": "0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/otninformation/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.target": "Micom",
|
||||
"x.com.samsung.da.newVersionAvailable": "false"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/filter/waterfilter/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.filterUsage": "0",
|
||||
"x.com.samsung.da.filterUsageResolution": "1",
|
||||
"x.com.samsung.da.filterStatus": "normal",
|
||||
"x.com.samsung.da.filterResetType": [
|
||||
"replaceable"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/icemaker/one/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.iceMaker.name": "ICE_MAKER",
|
||||
"x.com.samsung.da.iceMaker.state": "On",
|
||||
"x.com.samsung.da.iceMaker.type": "toggle",
|
||||
"x.com.samsung.da.iceMaker.iceMakingStatus": "ICESTATUS_STOP",
|
||||
"x.com.samsung.da.iceType.desired": "NORMAL"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
Vendored
+15
@@ -44,6 +44,21 @@
|
||||
"href": "/energy/consumption/0",
|
||||
"rep": {}
|
||||
},
|
||||
{
|
||||
"href": "/energy/ailevel/vs/0",
|
||||
"rep": {
|
||||
"aiLevel": "0",
|
||||
"supportedAiLevel": [
|
||||
"1"
|
||||
],
|
||||
"energySavingInfo": [
|
||||
{
|
||||
"aiLevel": "1",
|
||||
"maxEnergySavingRate": "70"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/course/vs/0",
|
||||
"rep": {
|
||||
|
||||
+325
@@ -0,0 +1,325 @@
|
||||
{
|
||||
"device0": [
|
||||
{
|
||||
"rt": [
|
||||
"x.com.samsung.devcol",
|
||||
"oic.wk.col"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.ll",
|
||||
"oic.if.b"
|
||||
]
|
||||
},
|
||||
{
|
||||
"href": "/realtimenotiforclient/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.timeforshortnoti": "0",
|
||||
"x.com.samsung.da.periodicnotisubscription": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/alarms/vs/0",
|
||||
"rep": {}
|
||||
},
|
||||
{
|
||||
"href": "/diagnosis/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.diagnosisStart": "Ready"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/energy/consumption/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.instantaneousPower": "-500",
|
||||
"x.com.samsung.da.instantaneousPowerUnit": "W",
|
||||
"x.com.samsung.da.cumulativePower": "1210200",
|
||||
"x.com.samsung.da.cumulativeUnit": "Wh",
|
||||
"x.com.samsung.da.cumulativeDate": "1784671200",
|
||||
"x.com.samsung.da.cumulativeDateUTC": "1784667600"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/energy/consumption/0",
|
||||
"rep": {}
|
||||
},
|
||||
{
|
||||
"href": "/course/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"HOMECARE_WIZARD_V2"
|
||||
],
|
||||
"x.com.samsung.da.options": [
|
||||
"DeviceType_0167",
|
||||
"UpdateAllow_NotAllowed",
|
||||
"Course_1C",
|
||||
"AiOption_On",
|
||||
"LaundryOutTime_0",
|
||||
"AddWashSet_0",
|
||||
"AddWashAvailable_7",
|
||||
"AddWashIndicator_On",
|
||||
"SeamlessControl_Enable",
|
||||
"KidsLockBypass_On",
|
||||
"WashingTimes_25",
|
||||
"DrumCleanProposal_40",
|
||||
"DrumCleanLog_2024-07-10T22:36:33|2024-09-12T21:37:17|2024-12-06T02:13:28|2025-03-04T20:40:47|2025-05-25T11:02:07|2025-08-17T19:48:10|2025-10-30T20:20:13|2026-01-08T21:44:02|2026-04-02T21:17:40|2026-06-08T19:51:07",
|
||||
"DetergentOnce_1",
|
||||
"DetergentLeft_0",
|
||||
"DetergentBase_5",
|
||||
"DetergentAlarm_Off",
|
||||
"DetergentType_2",
|
||||
"DetergentTotal_49",
|
||||
"SoftenerOnce_0",
|
||||
"SoftenerLeft_0",
|
||||
"SoftenerBase_0",
|
||||
"SoftenerAlarm_Off",
|
||||
"SoftenerType_0",
|
||||
"SoftenerTotal_0",
|
||||
"SpecialFunction_4",
|
||||
"AvailableDelayTime_159",
|
||||
"BubbleSoak_Off",
|
||||
"LaundryPlannerUserSetTime_0",
|
||||
"ProgressTimeSet_420BF48204D4A204F5",
|
||||
"SendToDevice_Off",
|
||||
"GMT_02",
|
||||
"LaundryPlannerMicomSetTime_1704",
|
||||
"PreWashSetting_Off",
|
||||
"IntensiveSetting_Off",
|
||||
"BubbleSoakSet_00F000F000F000F0F0F0F00000F000F0F00000F000000000",
|
||||
"EnergyLevelSet_05030401040104020404020503020202030301010505050405",
|
||||
"MostUsed_1C8410923FA67FB01F0000000000",
|
||||
"PreWashAvailableSet_F0F000F0F0F000F0F0F0F00000F0F0F0F00000F000000000",
|
||||
"IntensiveAvailableSet_F0F000F0F0F000F0F0F0F00000F0F0F0F00000F000000000",
|
||||
"GeoFenceAlarm",
|
||||
"UsagesDB_ok",
|
||||
"EnergyKW_396",
|
||||
"DrumCleanLog_2024-07-10T22:36:33|2024-09-12T21:37:17|2024-12-06T02:13:28|2025-03-04T20:40:47|2025-05-25T11:02:07|2025-08-17T19:48:10|2025-10-30T20:20:13|2026-01-08T21:44:02|2026-04-02T21:17:40|2026-06-08T19:51:07",
|
||||
"TimeSync_NotSupported"
|
||||
],
|
||||
"x.com.samsung.da.supportedOptions": [
|
||||
"41C8410923FA67FB01F1B847E923FA67FB01F1E811E933FA53FB01F1D841E923FA67FB01F1F8102923FA57FB01F25843E923FA57FB01F26841E920FA207B01F33857E933FA67FB00024841E930FA30FB00032833E923FA37FB00020857E943FA67FB01F22841E920FA30FB00023831E930FA57FB01F2F831E933FA30FB00021831E943FA57FB0002D841E923FA30FB00030843E923FA67FB000278000913FA67FB01F2880009000A67EB01F368410923FA640B11E3880009000A000B11E3980009000A000B11E3780009000A000B0002985209204A520B000"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/power/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.power": "Off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/power/0",
|
||||
"rep": {
|
||||
"value": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/cycleinterface/vs/0",
|
||||
"rep": {}
|
||||
},
|
||||
{
|
||||
"href": "/kidslock/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.kidsLock": "Ready"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/kidslock/0",
|
||||
"rep": {
|
||||
"value": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/operational/state/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.state": "Ready",
|
||||
"x.com.samsung.da.remainingTime": "02:39: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",
|
||||
"Weightsensing",
|
||||
"Wash",
|
||||
"Rinse",
|
||||
"Spin",
|
||||
"Finish"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/operational/state/0",
|
||||
"rep": {
|
||||
"currentMachineState": "**REDACTED**",
|
||||
"machineStates": "**REDACTED**",
|
||||
"jobStates": [
|
||||
"None",
|
||||
"Weightsensing",
|
||||
"Wash",
|
||||
"Rinse",
|
||||
"Spin",
|
||||
"Finish"
|
||||
],
|
||||
"currentJobState": "None",
|
||||
"remainingTime": "02:39:00",
|
||||
"progressPercentage": "1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/information/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.modelNum": "DA_WM_TP2_20_COMMON|20260041|20010102011411B402A3020700000000",
|
||||
"x.com.samsung.da.description": "DA_WM_TP2_20_COMMON_WW6600R/DC92-02617A_5049",
|
||||
"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": "210",
|
||||
"x.com.samsung.da.diagMinVersion": "1.0",
|
||||
"x.com.samsung.da.items": [
|
||||
{
|
||||
"x.com.samsung.da.id": "0",
|
||||
"x.com.samsung.da.description": "DA_WM_TP2_20_COMMON|20260041|20010102011411B402A3020700000000",
|
||||
"x.com.samsung.da.type": "Software",
|
||||
"x.com.samsung.da.number": "02674A250414(F546)",
|
||||
"x.com.samsung.da.newVersionAvailable": "0"
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "1",
|
||||
"x.com.samsung.da.description": "Firmware_1_DB_20260041200911390FFFFF202617412101203104FFFF(01672026004120261741_30000000)(FileDown:0)(Type:0)",
|
||||
"x.com.samsung.da.type": "Firmware",
|
||||
"x.com.samsung.da.number": "20091139,21012031",
|
||||
"x.com.samsung.da.newVersionAvailable": "0"
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "2",
|
||||
"x.com.samsung.da.description": "Firmware_2_DB_2025984120072205042FFFFFFFFFFFFFFFFFFFFFFFFE(016720259841FFFFFFFF_30000000)(FileDown:0)(Type:0)",
|
||||
"x.com.samsung.da.type": "Firmware",
|
||||
"x.com.samsung.da.number": "20072205,FFFFFFFF"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/file/information/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.timeoffset": "+01:00"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/washer/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.waterTemperature": "40",
|
||||
"x.com.samsung.da.supportedWaterTemperature": [
|
||||
"None",
|
||||
"Cold",
|
||||
"20",
|
||||
"30",
|
||||
"40",
|
||||
"60",
|
||||
"90"
|
||||
],
|
||||
"x.com.samsung.da.spinLevel": "1400",
|
||||
"x.com.samsung.da.supportedSpinLevel": [
|
||||
"RinseHold",
|
||||
"NoSpin",
|
||||
"400",
|
||||
"800",
|
||||
"1000",
|
||||
"1200",
|
||||
"1400"
|
||||
],
|
||||
"x.com.samsung.da.dryLevel": "None",
|
||||
"x.com.samsung.da.supportedDryLevel": [
|
||||
"None",
|
||||
"Cupboard",
|
||||
"30",
|
||||
"60",
|
||||
"90",
|
||||
"120",
|
||||
"180",
|
||||
"240"
|
||||
],
|
||||
"x.com.samsung.da.rinseCycles": "2",
|
||||
"x.com.samsung.da.supportedRinseCycles": [
|
||||
"0",
|
||||
"1",
|
||||
"2",
|
||||
"3",
|
||||
"4",
|
||||
"5"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/st/washercourse/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.st.washerMode": "Table_02_Course_1C",
|
||||
"x.com.samsung.da.st.courseTable": "Table_02"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/setting/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.supportedSetLanguage": [
|
||||
"ko_KR",
|
||||
"en_US"
|
||||
],
|
||||
"x.com.samsung.da.setLanguage": "en_GB"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/wm/editcourse/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.editCourseList": "EditCourseList_1C3026241E293633281D27211B202F2223323825392D1F37",
|
||||
"x.com.samsung.da.fixedCourseList": "FixedCourseList_1C353A29"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/wm/setinfo/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.isModelSettingWithoutSC": "true",
|
||||
"x.com.samsung.da.aiCourse": "true_formobile",
|
||||
"x.com.samsung.da.isModelSettingPowerOnOff": "false"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/wm/jobbeginingstatus/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.currentStatus": "None"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/otninformation/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.target": "Micom",
|
||||
"x.com.samsung.da.newVersionAvailable": "false"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/remotectrl/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.remoteControlEnabled": "false"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/remotectrl/0",
|
||||
"rep": {
|
||||
"value": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/configuration/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.region": "0000000000",
|
||||
"x.com.samsung.da.countryCode": "GB"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
+253
@@ -0,0 +1,253 @@
|
||||
{
|
||||
"device0": [
|
||||
{
|
||||
"rt": [
|
||||
"x.com.samsung.devcol",
|
||||
"oic.wk.col"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.ll",
|
||||
"oic.if.b"
|
||||
]
|
||||
},
|
||||
{
|
||||
"href": "/realtimenotiforclient/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.timeforshortnoti": "0",
|
||||
"x.com.samsung.da.periodicnotisubscription": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/alarms/vs/0",
|
||||
"rep": {}
|
||||
},
|
||||
{
|
||||
"href": "/diagnosis/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.diagnosisStart": "Ready"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/energy/consumption/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.instantaneousPowerUnit": "W",
|
||||
"x.com.samsung.da.instantaneousPower": "-500",
|
||||
"x.com.samsung.da.cumulativePower": "199200",
|
||||
"x.com.samsung.da.cumulativeUnit": "Wh",
|
||||
"x.com.samsung.da.cumulativeDate": "1784631600",
|
||||
"x.com.samsung.da.cumulativeDateUTC": "1784646000"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/energy/consumption/0",
|
||||
"rep": {}
|
||||
},
|
||||
{
|
||||
"href": "/course/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"HOMECARE_WIZARD_V2"
|
||||
],
|
||||
"x.com.samsung.da.options": [
|
||||
"DeviceType_0146",
|
||||
"UpdateAllow_NotAllowed",
|
||||
"Course_A5",
|
||||
"LaundryOutTime_0",
|
||||
"AddGarmentIndicator_Off",
|
||||
"SteamWash_Enable",
|
||||
"SeamlessControl_Disable",
|
||||
"KidsLockBypass_On",
|
||||
"WashingTimes_14",
|
||||
"DrumCleanProposal_40",
|
||||
"DetergentOnce_0",
|
||||
"DetergentLeft_0",
|
||||
"DetergentBase_0",
|
||||
"DetergentAlarm_Off",
|
||||
"DetergentType_0",
|
||||
"DetergentTotal_0",
|
||||
"SpecialFunction_4",
|
||||
"AvailableDelayTime_124",
|
||||
"LaundryPlannerUserSetTime_0",
|
||||
"ProgressTimeSet_4210E08209F6A2021C",
|
||||
"EnergyLevelSet_0502040502030302030105",
|
||||
"UsagesDB_ok",
|
||||
"EnergyKW_396",
|
||||
"DrumCleanLog_2023-08-11T19:17:11|2023-11-02T03:23:13|2023-12-22T22:35:54|2024-04-08T01:27:32|2024-04-22T01:07:26|2024-10-02T19:16:45|2025-02-03T02:01:33|2025-06-16T17:36:13|2026-01-04T18:34:46|2026-05-26T02:33:16",
|
||||
"TimeSync_NotSupported"
|
||||
],
|
||||
"x.com.samsung.da.supportedOptions": [
|
||||
"301833EA41BC20E9C843EA41FC30EA98520A41FC20EA2810EA207C20EAA831EA41FC20EA5830EA20FC20E9F831EA41FC10E9E830EA21FC20E9D8000A41FC000B18520A410C000"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/power/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.power": "On"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/power/0",
|
||||
"rep": {
|
||||
"value": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/cycleinterface/vs/0",
|
||||
"rep": {}
|
||||
},
|
||||
{
|
||||
"href": "/kidslock/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.kidsLock": "Ready"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/kidslock/0",
|
||||
"rep": {
|
||||
"value": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/operational/state/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.state": "Ready",
|
||||
"x.com.samsung.da.remainingTime": "02:04: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",
|
||||
"Wash",
|
||||
"Rinse",
|
||||
"Spin",
|
||||
"Finish"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/operational/state/0",
|
||||
"rep": {
|
||||
"currentMachineState": "**REDACTED**",
|
||||
"machineStates": "**REDACTED**",
|
||||
"jobStates": [
|
||||
"None",
|
||||
"Wash",
|
||||
"Rinse",
|
||||
"Spin",
|
||||
"Finish"
|
||||
],
|
||||
"currentJobState": "None",
|
||||
"remainingTime": "02:04:00",
|
||||
"progressPercentage": "1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/information/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.modelNum": "DA_WM_A51_20_COMMON|20198042|20020001001111400203000000000000",
|
||||
"x.com.samsung.da.description": "DA_WM_A51_20_COMMON_WV9600M/DC92-01980B_0014",
|
||||
"x.com.samsung.da.serialNum": "REDACTED",
|
||||
"x.com.samsung.da.otnDUID": "REDACTED",
|
||||
"x.com.samsung.da.items": [
|
||||
{
|
||||
"x.com.samsung.da.id": "0",
|
||||
"x.com.samsung.da.description": "DA_WM_A51_20_COMMON|20198042|20020001001111400203000000000000",
|
||||
"x.com.samsung.da.type": "Software",
|
||||
"x.com.samsung.da.number": "02198A230708(E257)",
|
||||
"x.com.samsung.da.newVersionAvailable": "0"
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "1",
|
||||
"x.com.samsung.da.description": "DA_WM_A51_20_COMMON",
|
||||
"x.com.samsung.da.type": "Firmware",
|
||||
"x.com.samsung.da.number": "18030902,18090603",
|
||||
"x.com.samsung.da.newVersionAvailable": "0"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/file/information/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.timeoffset": "-04:00"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/washer/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.waterTemperature": "Warm",
|
||||
"x.com.samsung.da.supportedWaterTemperature": [
|
||||
"None",
|
||||
"Cold",
|
||||
"Cool",
|
||||
"Warm",
|
||||
"Hot",
|
||||
"ExtraHot"
|
||||
],
|
||||
"x.com.samsung.da.spinLevel": "Low",
|
||||
"x.com.samsung.da.supportedSpinLevel": [
|
||||
"RinseHold",
|
||||
"NoSpin",
|
||||
"Low",
|
||||
"Medium",
|
||||
"High"
|
||||
],
|
||||
"x.com.samsung.da.soilLevel": "Heavy",
|
||||
"x.com.samsung.da.supportedSoilLevel": [
|
||||
"None",
|
||||
"Light",
|
||||
"Normal",
|
||||
"Heavy"
|
||||
],
|
||||
"x.com.samsung.da.rinseCycles": "3"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/st/washercourse/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.st.washerMode": "Table_00_Course_A5",
|
||||
"x.com.samsung.da.st.courseTable": "Table_00"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/setting/vs/0",
|
||||
"rep": {}
|
||||
},
|
||||
{
|
||||
"href": "/wm/editcourse/vs/0",
|
||||
"rep": {}
|
||||
},
|
||||
{
|
||||
"href": "/wm/setinfo/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.isModelSettingWithoutSC": "false",
|
||||
"x.com.samsung.da.isModelSettingPowerOnOff": "false"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/wm/jobbeginingstatus/vs/0",
|
||||
"rep": {}
|
||||
},
|
||||
{
|
||||
"href": "/remotectrl/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.remoteControlEnabled": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/remotectrl/0",
|
||||
"rep": {
|
||||
"value": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/configuration/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.region": "0000000000",
|
||||
"x.com.samsung.da.countryCode": "US"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -3,8 +3,10 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from unittest.mock import patch
|
||||
|
||||
import cbor2
|
||||
import pytest
|
||||
from pytest_homeassistant_custom_component.common import MockConfigEntry
|
||||
|
||||
@@ -150,12 +152,26 @@ class FakeObserveSession:
|
||||
self.subscribed: list[str] = []
|
||||
self.fail_hrefs: set[str] = set()
|
||||
self.closed = False
|
||||
# When set to a rep dict, subscribe() immediately delivers that rep
|
||||
# as an OBSERVE notification for the href — what a real device does
|
||||
# when it answers a subscription with the current representation.
|
||||
# `try_enter_observe_mode` clears its notified set before it
|
||||
# subscribes, so this is the only way to deliver a notify that
|
||||
# reliably counts: it lands synchronously via on_notification,
|
||||
# after that clear and before the post-sleep fraction check — a
|
||||
# notify raced in from another thread can be wiped by the clear
|
||||
# (or arrive after the check) depending on scheduling. Set to
|
||||
# None to model a device that answers subscriptions but never
|
||||
# notifies.
|
||||
self.notify_on_subscribe: dict[str, Any] | None = None
|
||||
|
||||
def subscribe(self, path_segs):
|
||||
href = '/' + '/'.join(path_segs)
|
||||
if href in self.fail_hrefs:
|
||||
raise ConnectionError("subscribe failed")
|
||||
self.subscribed.append(href)
|
||||
if self.notify_on_subscribe is not None and self.on_notification is not None:
|
||||
self.on_notification(href, cbor2.dumps(self.notify_on_subscribe))
|
||||
return b'\x01'
|
||||
|
||||
def refresh_observes(self, paths):
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
"""Tests for the LocalThingsCoordinator."""
|
||||
from __future__ import annotations
|
||||
|
||||
import threading
|
||||
import time
|
||||
from datetime import timedelta
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
@@ -260,21 +258,18 @@ async def test_enters_observe_mode_when_hot_warm_hrefs_notify(
|
||||
coordinator: LocalThingsCoordinator = hass.data[DOMAIN][mock_entry.entry_id]
|
||||
hrefs = coordinator._hot_hrefs + coordinator._warm_hrefs
|
||||
|
||||
# try_enter_observe_mode clears prior notifications as soon as it
|
||||
# starts, so notifies must land *during* its grace-period sleep (same
|
||||
# pattern test_observe.py uses), not before the call.
|
||||
def _notify_during_grace_period():
|
||||
time.sleep(0.005)
|
||||
for href in hrefs:
|
||||
fake.on_notification(href, cbor2.dumps({'notified': True}))
|
||||
|
||||
notifier = threading.Thread(target=_notify_during_grace_period, daemon=True)
|
||||
notifier.start()
|
||||
# try_enter_observe_mode clears prior notifications before it
|
||||
# subscribes, then checks the notified fraction after the grace-period
|
||||
# sleep — a notify counts as long as it arrives in that window, not
|
||||
# specifically during the sleep itself. The fake session delivers one
|
||||
# synchronously from subscribe() for every href it subscribes to (see
|
||||
# FakeObserveSession.notify_on_subscribe in conftest.py), which lands
|
||||
# it in that window by construction rather than by timing.
|
||||
fake.notify_on_subscribe = {'notified': True}
|
||||
entered = await hass.async_add_executor_job(
|
||||
coordinator._observe.try_enter_observe_mode,
|
||||
fake, hrefs, 0.02, 0.8,
|
||||
)
|
||||
notifier.join()
|
||||
|
||||
assert entered is True
|
||||
assert coordinator._observe.mode == MODE_OBSERVE
|
||||
@@ -300,18 +295,11 @@ async def test_reconnect_while_observe_mode_downgrades_to_poll(
|
||||
|
||||
# Get the coordinator into observe mode the same way
|
||||
# test_enters_observe_mode_when_hot_warm_hrefs_notify does.
|
||||
def _notify_during_grace_period():
|
||||
time.sleep(0.005)
|
||||
for href in hrefs:
|
||||
fake.on_notification(href, cbor2.dumps({'notified': True}))
|
||||
|
||||
notifier = threading.Thread(target=_notify_during_grace_period, daemon=True)
|
||||
notifier.start()
|
||||
fake.notify_on_subscribe = {'notified': True}
|
||||
entered = await hass.async_add_executor_job(
|
||||
coordinator._observe.try_enter_observe_mode,
|
||||
fake, hrefs, 0.02, 0.8,
|
||||
)
|
||||
notifier.join()
|
||||
assert entered is True
|
||||
assert coordinator.observe_mode == MODE_OBSERVE
|
||||
|
||||
@@ -324,12 +312,12 @@ async def test_reconnect_while_observe_mode_downgrades_to_poll(
|
||||
|
||||
# Simulate the existing "poll failed, reconnecting" branch: _poll_once
|
||||
# fails once (triggering the reconnect/backoff path), then succeeds.
|
||||
# No fresh notifies are supplied for the immediate resubscribe attempt
|
||||
# this now triggers, so its grace period (shortened for tests by the
|
||||
# `_fast_coordinator_timers` autouse fixture — see conftest.py) times
|
||||
# out and mode stays 'poll' — this test only asserts the tear-down half of
|
||||
# the fix; test_reconnect_from_observe_mode_resubscribes_immediately
|
||||
# covers the successful-immediate-resubscribe half.
|
||||
# The device stops notifying on subscribe, so the immediate resubscribe
|
||||
# attempt this now triggers gets nothing back and mode stays 'poll' —
|
||||
# this test only asserts the tear-down half of the fix;
|
||||
# test_reconnect_from_observe_mode_resubscribes_immediately covers the
|
||||
# successful-immediate-resubscribe half.
|
||||
fake.notify_on_subscribe = None
|
||||
with (
|
||||
patch(
|
||||
'custom_components.localthings.coordinator.LocalThingsCoordinator._poll_once',
|
||||
@@ -362,18 +350,11 @@ async def test_poll_timeout_skips_reconnect_when_push_is_healthy(
|
||||
coordinator: LocalThingsCoordinator = hass.data[DOMAIN][mock_entry.entry_id]
|
||||
hrefs = coordinator._hot_hrefs + coordinator._warm_hrefs
|
||||
|
||||
def _notify_during_grace_period():
|
||||
time.sleep(0.005)
|
||||
for href in hrefs:
|
||||
fake.on_notification(href, cbor2.dumps({'notified': True}))
|
||||
|
||||
notifier = threading.Thread(target=_notify_during_grace_period, daemon=True)
|
||||
notifier.start()
|
||||
fake.notify_on_subscribe = {'notified': True}
|
||||
entered = await hass.async_add_executor_job(
|
||||
coordinator._observe.try_enter_observe_mode,
|
||||
fake, hrefs, 0.02, 0.8,
|
||||
)
|
||||
notifier.join()
|
||||
assert entered is True
|
||||
assert coordinator.observe_mode == MODE_OBSERVE
|
||||
|
||||
@@ -408,18 +389,11 @@ async def test_poll_timeout_reconnects_after_consecutive_limit_even_with_push(
|
||||
coordinator: LocalThingsCoordinator = hass.data[DOMAIN][mock_entry.entry_id]
|
||||
hrefs = coordinator._hot_hrefs + coordinator._warm_hrefs
|
||||
|
||||
def _notify_during_grace_period():
|
||||
time.sleep(0.005)
|
||||
for href in hrefs:
|
||||
fake.on_notification(href, cbor2.dumps({'notified': True}))
|
||||
|
||||
notifier = threading.Thread(target=_notify_during_grace_period, daemon=True)
|
||||
notifier.start()
|
||||
fake.notify_on_subscribe = {'notified': True}
|
||||
entered = await hass.async_add_executor_job(
|
||||
coordinator._observe.try_enter_observe_mode,
|
||||
fake, hrefs, 0.02, 0.8,
|
||||
)
|
||||
notifier.join()
|
||||
assert entered is True
|
||||
assert coordinator.observe_mode == MODE_OBSERVE
|
||||
|
||||
@@ -441,7 +415,10 @@ async def test_poll_timeout_reconnects_after_consecutive_limit_even_with_push(
|
||||
assert coordinator.observe_mode == MODE_OBSERVE
|
||||
|
||||
# The final consecutive timeout crosses the limit and triggers the
|
||||
# existing reconnect path, which then succeeds and downgrades.
|
||||
# existing reconnect path, which then succeeds and downgrades. The
|
||||
# device stops notifying on subscribe, so the resubscribe attempt that
|
||||
# follows the reconnect finds nothing and mode stays 'poll'.
|
||||
fake.notify_on_subscribe = None
|
||||
with (
|
||||
patch(
|
||||
'custom_components.localthings.coordinator.LocalThingsCoordinator._poll_once',
|
||||
@@ -473,18 +450,11 @@ async def test_poll_timeout_counter_resets_when_push_is_healthy_again(
|
||||
coordinator: LocalThingsCoordinator = hass.data[DOMAIN][mock_entry.entry_id]
|
||||
hrefs = coordinator._hot_hrefs + coordinator._warm_hrefs
|
||||
|
||||
def _notify_during_grace_period():
|
||||
time.sleep(0.005)
|
||||
for href in hrefs:
|
||||
fake.on_notification(href, cbor2.dumps({'notified': True}))
|
||||
|
||||
notifier = threading.Thread(target=_notify_during_grace_period, daemon=True)
|
||||
notifier.start()
|
||||
fake.notify_on_subscribe = {'notified': True}
|
||||
entered = await hass.async_add_executor_job(
|
||||
coordinator._observe.try_enter_observe_mode,
|
||||
fake, hrefs, 0.02, 0.8,
|
||||
)
|
||||
notifier.join()
|
||||
assert entered is True
|
||||
assert coordinator.observe_mode == MODE_OBSERVE
|
||||
|
||||
@@ -526,21 +496,11 @@ async def test_reconnect_from_observe_mode_resubscribes_immediately(
|
||||
coordinator: LocalThingsCoordinator = hass.data[DOMAIN][mock_entry.entry_id]
|
||||
hrefs = coordinator._hot_hrefs + coordinator._warm_hrefs
|
||||
|
||||
def _notify(delay: float = 0.005) -> threading.Thread:
|
||||
def _run():
|
||||
time.sleep(delay)
|
||||
for href in hrefs:
|
||||
fake.on_notification(href, cbor2.dumps({'notified': True}))
|
||||
t = threading.Thread(target=_run, daemon=True)
|
||||
t.start()
|
||||
return t
|
||||
|
||||
notifier = _notify()
|
||||
fake.notify_on_subscribe = {'notified': True}
|
||||
entered = await hass.async_add_executor_job(
|
||||
coordinator._observe.try_enter_observe_mode,
|
||||
fake, hrefs, 0.02, 0.8,
|
||||
)
|
||||
notifier.join()
|
||||
assert entered is True
|
||||
assert coordinator.observe_mode == MODE_OBSERVE
|
||||
|
||||
@@ -563,10 +523,8 @@ async def test_reconnect_from_observe_mode_resubscribes_immediately(
|
||||
new=AsyncMock(),
|
||||
),
|
||||
):
|
||||
notifier = _notify()
|
||||
await coordinator.async_request_refresh()
|
||||
await hass.async_block_till_done()
|
||||
notifier.join()
|
||||
|
||||
assert coordinator.observe_mode == MODE_OBSERVE
|
||||
|
||||
@@ -587,21 +545,11 @@ async def test_sweep_mismatch_never_downgrades_a_live_observe_session(
|
||||
hrefs = coordinator._hot_hrefs + coordinator._warm_hrefs
|
||||
assert len(hrefs) >= 2
|
||||
|
||||
def _notify(delay: float = 0.005) -> threading.Thread:
|
||||
def _run():
|
||||
time.sleep(delay)
|
||||
for href in hrefs:
|
||||
fake.on_notification(href, cbor2.dumps({'notified': True}))
|
||||
t = threading.Thread(target=_run, daemon=True)
|
||||
t.start()
|
||||
return t
|
||||
|
||||
notifier = _notify()
|
||||
fake.notify_on_subscribe = {'notified': True}
|
||||
entered = await hass.async_add_executor_job(
|
||||
coordinator._observe.try_enter_observe_mode,
|
||||
fake, hrefs, 0.02, 0.8,
|
||||
)
|
||||
notifier.join()
|
||||
assert entered is True
|
||||
assert coordinator.observe_mode == MODE_OBSERVE
|
||||
|
||||
@@ -637,21 +585,11 @@ async def test_sweep_mismatch_forces_subpolls_on_a_live_observe_session(
|
||||
hrefs = coordinator._hot_hrefs + coordinator._warm_hrefs
|
||||
assert len(hrefs) >= 2
|
||||
|
||||
def _notify(delay: float = 0.005) -> threading.Thread:
|
||||
def _run():
|
||||
time.sleep(delay)
|
||||
for href in hrefs:
|
||||
fake.on_notification(href, cbor2.dumps({'notified': True}))
|
||||
t = threading.Thread(target=_run, daemon=True)
|
||||
t.start()
|
||||
return t
|
||||
|
||||
notifier = _notify()
|
||||
fake.notify_on_subscribe = {'notified': True}
|
||||
entered = await hass.async_add_executor_job(
|
||||
coordinator._observe.try_enter_observe_mode,
|
||||
fake, hrefs, 0.02, 0.8,
|
||||
)
|
||||
notifier.join()
|
||||
assert entered is True
|
||||
assert coordinator.observe_mode == MODE_OBSERVE
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ climate entity itself lives in climate.py (imports homeassistant) and is not
|
||||
importable here -- consistent with how the other HA platform files are untested.
|
||||
"""
|
||||
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.by_type import for_device, for_device_by_model
|
||||
from custom_components.localthings.registry.capabilities import airconditioner
|
||||
from custom_components.localthings.registry.discovery import discover
|
||||
from custom_components.localthings.registry.entities import ClimateDesc
|
||||
@@ -23,6 +23,22 @@ def _ac():
|
||||
return reg, resources
|
||||
|
||||
|
||||
def _resolve(name):
|
||||
"""Mirror the coordinator's detection order: oneUiVersion first, modelNum
|
||||
fallback second (needed for issue #37's board, which reports neither
|
||||
oneUiVersion nor a '_PRAC_' modelNum token)."""
|
||||
resources = _load_device(name)
|
||||
otn = resources.get('/otninformation/vs/0', {})
|
||||
one_ui = otn.get('swVersionInfo', {}).get('oneUiVersion', '')
|
||||
info = resources['/information/vs/0']
|
||||
reg = for_device(one_ui) if one_ui else None
|
||||
if reg is None:
|
||||
reg = for_device_by_model(
|
||||
info['x.com.samsung.da.modelNum'], info['x.com.samsung.da.description'],
|
||||
)
|
||||
return reg, resources
|
||||
|
||||
|
||||
def _bound():
|
||||
reg, resources = _ac()
|
||||
return discover(resources, reg.capabilities, reg.pattern_capabilities), resources
|
||||
@@ -106,3 +122,114 @@ def test_climate_consumed_hrefs_declared_as_coverage():
|
||||
caps = reg.capabilities.get(href)
|
||||
assert caps, href
|
||||
assert all(c.entities == () for c in caps), href
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# TP1X_DA-AC-RAC-01011 (oneUiVersion "7.0 Air conditioner", Tizen Lite) -- a
|
||||
# newer model class than the ARTIK051_PRAC dump above. It has no OCF-standard
|
||||
# /temperature/current+desired pair (temperature lives on the vendor
|
||||
# /temperatures/vs/0 items[] resource), exposes a /light/vs/0 display light, and
|
||||
# carries extra vendor housekeeping hrefs. Issue #17 for this class (PR #36).
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _ac_tp1x():
|
||||
resources = _load_device('airconditioner_tp1x_da_ac_rac_01011')
|
||||
one_ui = resources['/otninformation/vs/0']['swVersionInfo']['oneUiVersion']
|
||||
return for_device(one_ui), resources
|
||||
|
||||
|
||||
def test_tp1x_resolves_to_airconditioner_registry():
|
||||
reg, _ = _ac_tp1x()
|
||||
assert reg is not None and reg.name == 'airconditioner'
|
||||
|
||||
|
||||
def test_tp1x_no_unbound_hrefs():
|
||||
"""Every resource in the TP1X dump binds or is covered -- including
|
||||
/temperatures/vs/0, /light/vs/0 and the housekeeping hrefs absent from
|
||||
the ARTIK051 dump. Clears the coverage-gap repair."""
|
||||
reg, resources = _ac_tp1x()
|
||||
unbound = []
|
||||
discover(resources, reg.capabilities, reg.pattern_capabilities, log=unbound.append)
|
||||
assert unbound == []
|
||||
|
||||
|
||||
def test_tp1x_display_light_switch_present():
|
||||
"""/light/vs/0 (mode On/Off) surfaces as the display-light switch."""
|
||||
reg, resources = _ac_tp1x()
|
||||
state = flatten(
|
||||
discover(resources, reg.capabilities, reg.pattern_capabilities), resources)
|
||||
assert state.get('display_light') is True # device reports mode == 'On'
|
||||
|
||||
|
||||
def test_tp1x_vendor_temperature_and_light_covered():
|
||||
"""The vendor temperature resource (read by the climate entity) and the
|
||||
display-light resource both resolve in the registry -- no gap."""
|
||||
reg, _ = _ac_tp1x()
|
||||
assert reg.capabilities.get('/temperatures/vs/0'), '/temperatures/vs/0'
|
||||
assert reg.capabilities.get('/light/vs/0'), '/light/vs/0'
|
||||
|
||||
|
||||
def test_tp1x_climate_entity_is_bound():
|
||||
"""The composite climate entity still binds the primary /mode/vs/0."""
|
||||
reg, resources = _ac_tp1x()
|
||||
bound = discover(resources, reg.capabilities, reg.pattern_capabilities)
|
||||
climate = [b for b in bound if isinstance(b.desc, ClimateDesc)]
|
||||
assert len(climate) == 1 and climate[0].href == '/mode/vs/0'
|
||||
|
||||
|
||||
def test_tp2x_rac_20k_model_resolves_via_model_fallback():
|
||||
"""TP2X_RAC_20K (issue #37) reports no oneUiVersion and no '_PRAC_' token
|
||||
-- resolved via the '_RAC_' modelNum fallback added for this device."""
|
||||
reg, _ = _resolve('airconditioner_tp2x_rac_20k')
|
||||
assert reg is not None and reg.name == 'airconditioner'
|
||||
|
||||
|
||||
def test_tp2x_rac_20k_no_unbound_hrefs():
|
||||
reg, resources = _resolve('airconditioner_tp2x_rac_20k')
|
||||
unbound = []
|
||||
discover(resources, reg.capabilities, reg.pattern_capabilities, log=unbound.append)
|
||||
assert unbound == []
|
||||
|
||||
|
||||
def test_tp1x_rac_model_resolves_via_one_ui_version():
|
||||
"""TP1X_DA-AC-RAC-01001_0000 (issue #38) self-reports oneUiVersion
|
||||
'7.0 Air conditioner' -- resolved via for_device(), not the modelNum
|
||||
fallback."""
|
||||
reg, _ = _resolve('airconditioner_tp1x_rac')
|
||||
assert reg is not None and reg.name == 'airconditioner'
|
||||
|
||||
|
||||
def test_tp1x_rac_no_unbound_hrefs():
|
||||
reg, resources = _resolve('airconditioner_tp1x_rac')
|
||||
unbound = []
|
||||
discover(resources, reg.capabilities, reg.pattern_capabilities, log=unbound.append)
|
||||
assert unbound == []
|
||||
|
||||
|
||||
def test_tp1x_rac_expected_state_keys_present():
|
||||
reg, resources = _resolve('airconditioner_tp1x_rac')
|
||||
bound = discover(resources, reg.capabilities, reg.pattern_capabilities)
|
||||
state = flatten(bound, resources)
|
||||
for key in ('climate', 'display_light', 'mute_once', 'selfcheck_status',
|
||||
'selfcheck_result', 'current_limit_enabled', 'current_limit_level'):
|
||||
assert key in state, key
|
||||
|
||||
|
||||
def test_mute_once_write_target():
|
||||
write = airconditioner.MUTE_ONCE.entities[0].write_fn
|
||||
assert write('On', {}) == (['option', 'muteonce', 'vs', '0'], {'muteonce': 'On'})
|
||||
assert write('Off', {}) == (['option', 'muteonce', 'vs', '0'], {'muteonce': 'Off'})
|
||||
|
||||
|
||||
def test_display_light_write_target():
|
||||
write = airconditioner.DISPLAY_LIGHT.entities[0].write_fn
|
||||
assert write('On', {}) == (['light', 'vs', '0'], {'mode': 'On'})
|
||||
assert write('Off', {}) == (['light', 'vs', '0'], {'mode': 'Off'})
|
||||
|
||||
|
||||
def test_current_limit_is_read_only():
|
||||
"""Meaning/write contract for the current-limit levels isn't confirmed
|
||||
from the dump alone -- exposed as read-only diagnostic sensors rather
|
||||
than a guessed writable control."""
|
||||
for desc in airconditioner.CURRENT_LIMIT.entities:
|
||||
assert getattr(desc, 'write_fn', None) is None
|
||||
|
||||
@@ -180,6 +180,26 @@ class TestForDeviceByModel:
|
||||
assert reg is not None
|
||||
assert reg.name == 'airconditioner'
|
||||
|
||||
def test_washer_wf_prefix(self):
|
||||
"""US front-load washers use the WF consumer-model prefix."""
|
||||
from custom_components.localthings.registry.by_type import for_device_by_model
|
||||
reg = for_device_by_model(
|
||||
'DA_WM_TP1_21_COMMON|20313741|20010001001611244AA3021700000000',
|
||||
'DA_WM_TP1_21_COMMON_WF8900B/DC92-03129A_A0AE',
|
||||
)
|
||||
assert reg is not None
|
||||
assert reg.name == 'washer'
|
||||
|
||||
def test_washer_wv_prefix(self):
|
||||
"""FlexWash twin washers use the WV consumer-model prefix -- issue #19."""
|
||||
from custom_components.localthings.registry.by_type import for_device_by_model
|
||||
reg = for_device_by_model(
|
||||
'DA_WM_A51_20_COMMON|20198042|20020001001111400203000000000000',
|
||||
'DA_WM_A51_20_COMMON_WV9600M/DC92-01980B_0014',
|
||||
)
|
||||
assert reg is not None
|
||||
assert reg.name == 'washer'
|
||||
|
||||
def test_unknown_model_returns_none(self):
|
||||
from custom_components.localthings.registry.by_type import for_device_by_model
|
||||
reg = for_device_by_model('SOME-UNKNOWN-BOARD', 'SOME-UNKNOWN-BOARD')
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
"""Tests for the /temperatures/vs/0 fallback helper in climate.py (PR #36).
|
||||
|
||||
`_temps_vs_item` is a pure module-level function (plain dict in, plain dict
|
||||
out) with no coordinator/entity dependency, so -- like select.py's `_display`
|
||||
in test_select_display.py -- it's testable directly even though the
|
||||
LocalThingsClimate entity itself needs a coordinator/hass fixture to
|
||||
exercise. This covers the actual fallback-selection logic that the
|
||||
registry-level "no unbound hrefs" tests in test_airconditioner_capabilities.py
|
||||
don't reach: TP1X-class firmware has no OCF-standard
|
||||
/temperature/current+desired pair, so current_temperature/target_temperature
|
||||
would silently be None without this fallback.
|
||||
"""
|
||||
from custom_components.localthings.climate import _temps_vs_item
|
||||
|
||||
|
||||
def test_temps_vs_item_returns_first_item():
|
||||
rep = {
|
||||
'x.com.samsung.da.items': [
|
||||
{'x.com.samsung.da.current': '17.0', 'x.com.samsung.da.desired': '25.0'},
|
||||
],
|
||||
}
|
||||
assert _temps_vs_item(rep) == {
|
||||
'x.com.samsung.da.current': '17.0', 'x.com.samsung.da.desired': '25.0',
|
||||
}
|
||||
|
||||
|
||||
def test_temps_vs_item_empty_when_items_missing():
|
||||
"""OCF-pair devices don't populate /temperatures/vs/0's items at all --
|
||||
callers must fall through to the OCF hrefs cleanly, not raise."""
|
||||
assert _temps_vs_item({}) == {}
|
||||
|
||||
|
||||
def test_temps_vs_item_empty_when_items_is_empty_list():
|
||||
assert _temps_vs_item({'x.com.samsung.da.items': []}) == {}
|
||||
|
||||
|
||||
def test_temps_vs_item_empty_when_items_entry_is_not_a_dict():
|
||||
assert _temps_vs_item({'x.com.samsung.da.items': ['not-a-dict']}) == {}
|
||||
@@ -130,3 +130,202 @@ class TestEnergyMeter:
|
||||
phantom power sensor (the exists_fn replaces the field-presence gate)."""
|
||||
pw = next(e for e in common.ENERGY_METER.entities if e.key == 'power_watts')
|
||||
assert pw.exists_fn({'x.com.samsung.da.cumulativePower': '5'}, {}) is False
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# AI energy-saving level. '0' is off; supportedAiLevel lists the additional
|
||||
# level(s) on offer. A single-entry list (issue #21 fridge, issue #40 washer)
|
||||
# is really a binary toggle, so it's exposed as a switch instead of a
|
||||
# one-choice select; multiple entries get a select with '0' synthesized back
|
||||
# in as the explicit off option (supportedAiLevel never lists '0' itself, but
|
||||
# it's a real, observed value of aiLevel).
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _ai_energy_level_desc(cls_name):
|
||||
return next(e for e in common.AI_ENERGY_LEVEL.entities
|
||||
if e.__class__.__name__ == cls_name)
|
||||
|
||||
|
||||
class TestAiEnergyLevelSwitch:
|
||||
def _desc(self):
|
||||
return _ai_energy_level_desc('SwitchDesc')
|
||||
|
||||
def test_href(self):
|
||||
assert common.AI_ENERGY_LEVEL.href == '/energy/ailevel/vs/0'
|
||||
|
||||
def test_shown_only_with_single_supported_level(self):
|
||||
desc = self._desc()
|
||||
assert desc.exists_fn({'aiLevel': '1', 'supportedAiLevel': ['1']}, {}) is True
|
||||
assert desc.exists_fn({'aiLevel': '1', 'supportedAiLevel': ['1', '2']}, {}) is False
|
||||
|
||||
def test_hidden_when_supported_level_is_non_list_scalar(self):
|
||||
"""A stray scalar (e.g. a string) must not be len()-checked as if it
|
||||
were a list -- a 2-char string would otherwise wrongly pass `== 1`
|
||||
style checks."""
|
||||
desc = self._desc()
|
||||
assert desc.exists_fn({'aiLevel': '1', 'supportedAiLevel': '1'}, {}) is False
|
||||
|
||||
def test_hidden_when_supported_level_missing(self):
|
||||
desc = self._desc()
|
||||
assert desc.exists_fn({'aiLevel': '1'}, {}) is False
|
||||
|
||||
def test_hidden_when_supported_level_empty_list(self):
|
||||
desc = self._desc()
|
||||
assert desc.exists_fn({'aiLevel': '0', 'supportedAiLevel': []}, {}) is False
|
||||
|
||||
def test_hidden_on_empty_stub_rep(self):
|
||||
desc = self._desc()
|
||||
assert desc.exists_fn({}, {}) is False
|
||||
|
||||
def test_value_fn(self):
|
||||
desc = self._desc()
|
||||
assert desc.value_fn('0') is False
|
||||
assert desc.value_fn('1') is True
|
||||
|
||||
def test_write_on_uses_the_single_supported_level(self):
|
||||
"""The on-value is whatever the device calls its one level, not a
|
||||
hardcoded '1'."""
|
||||
desc = self._desc()
|
||||
path, body = desc.write_fn('On', {'supportedAiLevel': ['2']})
|
||||
assert path == ['energy', 'ailevel', 'vs', '0']
|
||||
assert body == {'aiLevel': '2'}
|
||||
|
||||
def test_write_off(self):
|
||||
desc = self._desc()
|
||||
path, body = desc.write_fn('Off', {'supportedAiLevel': ['1']})
|
||||
assert body == {'aiLevel': '0'}
|
||||
|
||||
|
||||
class TestAiEnergyLevelSelect:
|
||||
def _desc(self):
|
||||
return _ai_energy_level_desc('SelectDesc')
|
||||
|
||||
def test_shown_only_with_multiple_supported_levels(self):
|
||||
desc = self._desc()
|
||||
assert desc.exists_fn({'aiLevel': '1', 'supportedAiLevel': ['1', '2']}, {}) is True
|
||||
assert desc.exists_fn({'aiLevel': '1', 'supportedAiLevel': ['1']}, {}) is False
|
||||
|
||||
def test_hidden_when_supported_level_is_non_list_scalar(self):
|
||||
desc = self._desc()
|
||||
assert desc.exists_fn({'aiLevel': '1', 'supportedAiLevel': '12'}, {}) is False
|
||||
|
||||
def test_hidden_when_supported_level_missing(self):
|
||||
desc = self._desc()
|
||||
assert desc.exists_fn({'aiLevel': '1'}, {}) is False
|
||||
|
||||
def test_hidden_when_supported_level_empty_list(self):
|
||||
desc = self._desc()
|
||||
assert desc.exists_fn({'aiLevel': '0', 'supportedAiLevel': []}, {}) is False
|
||||
|
||||
def test_no_translation_key(self):
|
||||
"""aiLevel's values are plain digits that render fine untranslated
|
||||
(select.py's _display()) -- no strings.json entry to maintain
|
||||
against an unknown number of future levels."""
|
||||
desc = self._desc()
|
||||
assert desc.translation_key is None
|
||||
|
||||
def test_options_synthesize_off(self):
|
||||
"""'0' is never in supportedAiLevel but is a real, observed aiLevel
|
||||
value -- synthesized back in as the explicit off option."""
|
||||
desc = self._desc()
|
||||
resources = {'/energy/ailevel/vs/0': {'supportedAiLevel': ['1', '2']}}
|
||||
assert desc.options(resources) == ['0', '1', '2']
|
||||
|
||||
def test_options_empty_when_resource_missing(self):
|
||||
desc = self._desc()
|
||||
assert desc.options({}) == ['0']
|
||||
|
||||
def test_write(self):
|
||||
desc = self._desc()
|
||||
path, body = desc.write_fn('2', {})
|
||||
assert path == ['energy', 'ailevel', 'vs', '0']
|
||||
assert body == {'aiLevel': '2'}
|
||||
|
||||
|
||||
class TestAiEnergyLevelStubDoesNotDecideThePlatform:
|
||||
"""Issue found in review: entity *creation* runs once, against whatever
|
||||
snapshot is current when platforms are set up (see
|
||||
__init__.py's async_config_entry_first_refresh-before-forward-entry-setups
|
||||
ordering), while flatten() re-evaluates exists_fn every poll against live
|
||||
data. Both descriptors share key='ai_energy_level' (see adapter._key), so
|
||||
if a stub carve-out let one of them win at setup time while the other
|
||||
wins once real data lands, flatten() would feed the already-instantiated
|
||||
entity a value shaped for the other platform (e.g. a bool into a Select
|
||||
expecting a string option). Neither side gets a `not rep` carve-out, so
|
||||
an unfetched stub can never win entity creation for either platform --
|
||||
the entity simply doesn't appear until a reload happens with real data,
|
||||
same as any other exists_fn-gated entity in this codebase that's unlucky
|
||||
on first-poll timing, instead of appearing as the wrong widget type."""
|
||||
|
||||
def test_neither_widget_exists_on_empty_stub_rep(self):
|
||||
switch = _ai_energy_level_desc('SwitchDesc')
|
||||
select = _ai_energy_level_desc('SelectDesc')
|
||||
assert switch.exists_fn({}, {}) is False
|
||||
assert select.exists_fn({}, {}) is False
|
||||
|
||||
|
||||
class TestSelfCheckError:
|
||||
"""Self-check diagnostic error list -- surfaced on hardware that reports
|
||||
x.com.samsung.da.error, joined into a single display string."""
|
||||
|
||||
def _desc(self):
|
||||
return next(e for e in common.SELF_CHECK.entities if e.key == 'selfcheck_error')
|
||||
|
||||
def test_exists_when_field_present(self):
|
||||
desc = self._desc()
|
||||
assert desc.exists_fn({'x.com.samsung.da.error': ['DA_ERROR_NONE']}, {}) is True
|
||||
|
||||
def test_does_not_exist_when_field_absent(self):
|
||||
desc = self._desc()
|
||||
assert desc.exists_fn({'x.com.samsung.da.status': 'Ready'}, {}) is False
|
||||
|
||||
def test_exists_for_empty_stub_rep(self):
|
||||
"""An empty {} rep is /device/0's not-yet-fetched-stub carve-out --
|
||||
must be included-for-now, same as ENERGY_METER's fields."""
|
||||
desc = self._desc()
|
||||
assert desc.exists_fn({}, {}) is True
|
||||
|
||||
def test_value_joins_list(self):
|
||||
desc = self._desc()
|
||||
assert desc.value_fn(['E1', 'E2']) == 'E1, E2'
|
||||
|
||||
def test_value_passes_through_scalar(self):
|
||||
desc = self._desc()
|
||||
assert desc.value_fn('DA_ERROR_NONE') == 'DA_ERROR_NONE'
|
||||
|
||||
def test_value_none_for_empty_list(self):
|
||||
"""An empty error list means no value to show -- None (unknown),
|
||||
not an empty string."""
|
||||
desc = self._desc()
|
||||
assert desc.value_fn([]) is None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Cross-family bundles (UNIVERSAL / POWER) -- unpacked into every by_type
|
||||
# registry's _build([...]) call in place of the hand-duplicated capability
|
||||
# lists that used to live there.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestUniversalAndPowerBundles:
|
||||
def test_universal_contains_the_no_conflict_capabilities(self):
|
||||
assert set(common.UNIVERSAL) == {
|
||||
common.ALARMS, common.ENERGY_METER, common.FIRMWARE_UPDATE,
|
||||
common.SELF_CHECK, common.AI_ENERGY_LEVEL,
|
||||
common.KIDS_LOCK_GENERIC, common.KIDS_LOCK_VS_FALLBACK,
|
||||
common.REMOTE_CONTROL_GENERIC, common.REMOTE_CONTROL_VS_FALLBACK,
|
||||
}
|
||||
|
||||
def test_power_kept_separate_for_airconditioners_sake(self):
|
||||
"""See common.POWER's own comment for why airconditioner opts out."""
|
||||
assert set(common.POWER) == {common.POWER_GENERIC, common.POWER_VS_FALLBACK}
|
||||
|
||||
def test_no_overlap_between_the_two_bundles(self):
|
||||
assert not (set(common.UNIVERSAL) & set(common.POWER))
|
||||
|
||||
def test_airconditioner_registry_does_not_include_power(self):
|
||||
from custom_components.localthings.registry.by_type import airconditioner
|
||||
bound_caps = {c for caps in airconditioner.REGISTRY.capabilities.values() for c in caps}
|
||||
assert common.POWER_GENERIC not in bound_caps
|
||||
assert common.POWER_VS_FALLBACK not in bound_caps
|
||||
|
||||
@@ -102,3 +102,159 @@ class TestRefrigerationFallback:
|
||||
for key in ('rapid_fridge', 'rapid_freezing'):
|
||||
desc = next(e for e in fridge.REFRIGERATION_FALLBACK.entities if e.key == key)
|
||||
assert desc.exists_fn({}, {'/refrigeration/0': {}}) is True
|
||||
|
||||
|
||||
class TestFlexZone:
|
||||
"""Issue #32 (surfaced by #26/#27's TP2X/Bespoke dumps): the flex-zone
|
||||
entry's prefix varies by fridge family (CV_TTYPE_RF9000A_ on RF9000-class,
|
||||
CV_FDR_ on TP1X/Bespoke-class). The old hardcoded-prefix match left
|
||||
CV_FDR_-family fridges with an always-unknown select, and a write would
|
||||
have appended a duplicate flag instead of replacing the existing one."""
|
||||
|
||||
def test_reads_rf9000_prefix(self):
|
||||
rep = {
|
||||
'x.com.samsung.da.modes': ['CV_TTYPE_RF9000A_BEVERAGE', 'WATERFILTER_DISABLE'],
|
||||
'x.com.samsung.da.supportedOptions': [
|
||||
'CV_TTYPE_RF9000A_FREEZE', 'CV_TTYPE_RF9000A_BEVERAGE'],
|
||||
}
|
||||
assert fridge._flex_zone_current(rep) == 'CV_TTYPE_RF9000A_BEVERAGE'
|
||||
|
||||
def test_reads_cv_fdr_prefix(self):
|
||||
rep = {
|
||||
'x.com.samsung.da.modes': ['CVN_CONVERTIBLE_ZONE', 'CV_FDR_MEAT', 'WATERFILTER_ENABLE'],
|
||||
'x.com.samsung.da.supportedOptions': [
|
||||
'CV_FDR_WINE', 'CV_FDR_DELI', 'CV_FDR_BEVERAGE', 'CV_FDR_MEAT'],
|
||||
}
|
||||
assert fridge._flex_zone_current(rep) == 'CV_FDR_MEAT'
|
||||
|
||||
def test_write_replaces_rather_than_duplicates(self):
|
||||
rep = {
|
||||
'x.com.samsung.da.modes': ['CVN_CONVERTIBLE_ZONE', 'CV_FDR_MEAT', 'WATERFILTER_ENABLE'],
|
||||
'x.com.samsung.da.supportedOptions': [
|
||||
'CV_FDR_WINE', 'CV_FDR_DELI', 'CV_FDR_BEVERAGE', 'CV_FDR_MEAT'],
|
||||
}
|
||||
path, payload = fridge._flex_zone_write('CV_FDR_WINE', rep)
|
||||
assert path == ['mode', 'vs', '0']
|
||||
modes = payload['x.com.samsung.da.modes']
|
||||
assert modes.count('CV_FDR_WINE') == 1
|
||||
assert 'CV_FDR_MEAT' not in modes
|
||||
assert 'CVN_CONVERTIBLE_ZONE' in modes and 'WATERFILTER_ENABLE' in modes
|
||||
|
||||
|
||||
class TestTp1xNativeDuplicateResources:
|
||||
"""The US TP1X_REF_21K publishes two native mirrors in addition to the
|
||||
richer vendor resources. They must count as covered without producing
|
||||
duplicate entities or guessing unverified write contracts."""
|
||||
|
||||
def test_duplicate_capabilities_have_no_entities(self):
|
||||
assert fridge.DEFROST_DELAY_NATIVE_DUPLICATE.entities == ()
|
||||
assert fridge.ICEMAKER_STATUS_NATIVE_DUPLICATE.entities == ()
|
||||
|
||||
def test_us_fixture_has_complete_coverage(self):
|
||||
from custom_components.localthings.registry.adapter import flatten
|
||||
from custom_components.localthings.registry.by_type import refrigerator
|
||||
from custom_components.localthings.registry.discovery import discover
|
||||
from tests.conftest import _load_device
|
||||
|
||||
resources = _load_device('refrigerator_tp1x_ref_21k_us')
|
||||
unbound = []
|
||||
bound = discover(
|
||||
resources,
|
||||
refrigerator.REGISTRY.capabilities,
|
||||
refrigerator.REGISTRY.pattern_capabilities,
|
||||
log=unbound.append,
|
||||
)
|
||||
|
||||
assert unbound == []
|
||||
state = flatten(bound, resources)
|
||||
assert state['defrost_delay'] is False
|
||||
assert 'ice_maker_enabled' not in state
|
||||
assert state['icemaker_one_enabled'] is True
|
||||
assert state['icemaker_two_enabled'] is True
|
||||
assert state['selfcheck_error'] == 'DA_ERROR_NONE'
|
||||
|
||||
|
||||
class TestPantryZone:
|
||||
"""Cool Select Zone pantry compartment on ARTIK051_REF_17K -- issue #20."""
|
||||
|
||||
def test_href(self):
|
||||
assert fridge.PANTRY_ZONE.href == '/status/pantry/one/vs/0'
|
||||
|
||||
def test_write(self):
|
||||
desc = fridge.PANTRY_ZONE.entities[0]
|
||||
path, body = desc.write_fn('FDR_WINE', {})
|
||||
assert path == ['status', 'pantry', 'one', 'vs', '0']
|
||||
assert body == {'x.com.samsung.da.mode': 'FDR_WINE'}
|
||||
|
||||
|
||||
class TestArtik051AndTp2xFixturesHaveCompleteCoverage:
|
||||
"""issue #20 (ARTIK051_REF_17K) and #26 (TP2X_REF_20K) both triggered
|
||||
the incomplete-capability-coverage repair; both must resolve to zero
|
||||
unbound hrefs now that /diagnosis/vs/0 (dishwasher.DIAGNOSIS, reused for
|
||||
fridges), /status/pantry/one/vs/0 (PANTRY_ZONE), and the OCF-native
|
||||
defrost/icemaker mirrors are covered."""
|
||||
|
||||
def test_artik051_ref_17k(self):
|
||||
from custom_components.localthings.registry.adapter import flatten
|
||||
from custom_components.localthings.registry.by_type import refrigerator
|
||||
from custom_components.localthings.registry.discovery import discover
|
||||
from tests.conftest import _load_device
|
||||
|
||||
resources = _load_device('refrigerator_artik051_ref_17k')
|
||||
unbound = []
|
||||
bound = discover(
|
||||
resources,
|
||||
refrigerator.REGISTRY.capabilities,
|
||||
refrigerator.REGISTRY.pattern_capabilities,
|
||||
log=unbound.append,
|
||||
)
|
||||
assert unbound == []
|
||||
state = flatten(bound, resources)
|
||||
assert state['pantry_zone_mode'] == 'FDR_DRINKS'
|
||||
|
||||
def test_tp2x_ref_20k(self):
|
||||
from custom_components.localthings.registry.adapter import flatten
|
||||
from custom_components.localthings.registry.by_type import refrigerator
|
||||
from custom_components.localthings.registry.discovery import discover
|
||||
from tests.conftest import _load_device
|
||||
|
||||
resources = _load_device('refrigerator_tp2x_ref_20k')
|
||||
unbound = []
|
||||
bound = discover(
|
||||
resources,
|
||||
refrigerator.REGISTRY.capabilities,
|
||||
refrigerator.REGISTRY.pattern_capabilities,
|
||||
log=unbound.append,
|
||||
)
|
||||
assert unbound == []
|
||||
state = flatten(bound, resources)
|
||||
assert state['flex_zone_mode'] == 'CV_FDR_BEVERAGE'
|
||||
|
||||
|
||||
class TestRefrigeratorAiEnergyLevelFixtureCoverage:
|
||||
"""AI energy-saving level (common.AI_ENERGY_LEVEL, see
|
||||
test_common_capabilities.py) is exercised end-to-end here against the
|
||||
refrigerator fixture/registry.
|
||||
|
||||
refrigerator_device.json's supportedAiLevel was extended to two entries
|
||||
(['1', '2']) specifically to exercise the select branch -- the real
|
||||
captured TP1X_REF_21K_US dump only ever reports one entry (switch
|
||||
branch), so the select is synthetic-fixture-only for now."""
|
||||
|
||||
def test_synthetic_fixture_has_complete_coverage(self):
|
||||
from custom_components.localthings.registry.adapter import flatten
|
||||
from custom_components.localthings.registry.by_type import refrigerator
|
||||
from custom_components.localthings.registry.discovery import discover
|
||||
from tests.conftest import _load_device
|
||||
|
||||
resources = _load_device('refrigerator')
|
||||
unbound = []
|
||||
bound = discover(
|
||||
resources,
|
||||
refrigerator.REGISTRY.capabilities,
|
||||
refrigerator.REGISTRY.pattern_capabilities,
|
||||
log=unbound.append,
|
||||
)
|
||||
assert unbound == []
|
||||
state = flatten(bound, resources)
|
||||
assert state['ai_energy_level'] == '1'
|
||||
|
||||
@@ -81,6 +81,124 @@ def test_registry_reproduces_golden_state_keys_for_airconditioner():
|
||||
)
|
||||
|
||||
|
||||
def test_registry_reproduces_golden_state_keys_for_tp1x_ref_21k_us():
|
||||
from tests.conftest import _load_device
|
||||
resources = _load_device('refrigerator_tp1x_ref_21k_us')
|
||||
golden = json.loads(
|
||||
(GOLDEN / 'refrigerator_tp1x_ref_21k_us.json').read_text()
|
||||
)
|
||||
state_keys = _new_state_keys('refrigerator_tp1x_ref_21k_us', 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_washer_flexwash():
|
||||
"""FlexWash twin washers (WV-prefix consumer model, e.g. WV55M9600AW)
|
||||
report no oneUiVersion and previously fell through for_device_by_model's
|
||||
consumer-prefix map entirely -- issue #19."""
|
||||
from tests.conftest import _load_device
|
||||
resources = _load_device('washer_flexwash')
|
||||
golden = json.loads((GOLDEN / 'washer_flexwash.json').read_text())
|
||||
state_keys = _new_state_keys('washer_flexwash', 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_washer_dryer_combo():
|
||||
"""Washer/dryer combo units carry a writable dryLevel field on
|
||||
/washer/vs/0 itself, with no separate dryer resource -- issue #22."""
|
||||
from tests.conftest import _load_device
|
||||
resources = _load_device('washer_dryer_combo')
|
||||
golden = json.loads((GOLDEN / 'washer_dryer_combo.json').read_text())
|
||||
state_keys = _new_state_keys('washer_dryer_combo', 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_artik051_ref_17k():
|
||||
"""ARTIK051_REF_17K's Cool Select Zone pantry compartment
|
||||
(/status/pantry/one/vs/0) -- issue #20."""
|
||||
from tests.conftest import _load_device
|
||||
resources = _load_device('refrigerator_artik051_ref_17k')
|
||||
golden = json.loads((GOLDEN / 'refrigerator_artik051_ref_17k.json').read_text())
|
||||
state_keys = _new_state_keys('refrigerator_artik051_ref_17k', 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_tp2x_ref_20k():
|
||||
"""TP2X_REF_20K -- CV_FDR_-prefixed flex zone (issue #32) plus the extra
|
||||
energy fields (cumulativeConsumption/monthlyConsumption/
|
||||
thismonthlyConsumption) surfaced by issue #26."""
|
||||
from tests.conftest import _load_device
|
||||
resources = _load_device('refrigerator_tp2x_ref_20k')
|
||||
golden = json.loads((GOLDEN / 'refrigerator_tp2x_ref_20k.json').read_text())
|
||||
state_keys = _new_state_keys('refrigerator_tp2x_ref_20k', 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_ac_tp1x_da_ac_rac_01011():
|
||||
"""Newer AC firmware (Tizen Lite, oneUiVersion "7.0 Air conditioner"; model
|
||||
TP1X_DA-AC-RAC-01011) reports temperature via the vendor /temperatures/vs/0
|
||||
items[] resource and adds a /light/vs/0 display light, with extra vendor
|
||||
housekeeping hrefs -- issue #17 for this model class (PR #36)."""
|
||||
from tests.conftest import _load_device
|
||||
resources = _load_device('airconditioner_tp1x_da_ac_rac_01011')
|
||||
golden = json.loads(
|
||||
(GOLDEN / 'airconditioner_tp1x_da_ac_rac_01011.json').read_text()
|
||||
)
|
||||
state_keys = _new_state_keys('airconditioner_tp1x_da_ac_rac_01011', 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_tp2x_rac_20k():
|
||||
"""TP2X_RAC_20K (issue #37) -- reports no oneUiVersion; resolved via the
|
||||
'_RAC_' modelNum token fallback in for_device_by_model."""
|
||||
from tests.conftest import _load_device
|
||||
resources = _load_device('airconditioner_tp2x_rac_20k')
|
||||
golden = json.loads((GOLDEN / 'airconditioner_tp2x_rac_20k.json').read_text())
|
||||
state_keys = _new_state_keys('airconditioner_tp2x_rac_20k', 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."""
|
||||
from tests.conftest import _load_device
|
||||
resources = _load_device('airconditioner_tp1x_rac')
|
||||
golden = json.loads((GOLDEN / 'airconditioner_tp1x_rac.json').read_text())
|
||||
state_keys = _new_state_keys('airconditioner_tp1x_rac', 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_resources_from_batch_preferred_over_flat():
|
||||
from tests.conftest import _resources_from_dump
|
||||
dump = {
|
||||
|
||||
@@ -40,6 +40,22 @@ class TestWasherSettings:
|
||||
assert body == {'x.com.samsung.da.rinseCycles': '3'}
|
||||
|
||||
|
||||
class TestDryLevel:
|
||||
"""Washer/dryer combo units carry a writable dryLevel field on
|
||||
/washer/vs/0 itself, self-gated off on plain washers -- issue #22."""
|
||||
|
||||
def test_exists_only_when_supported_dry_level_present(self):
|
||||
desc = next(e for e in washer.WASHER_SETTINGS.entities if e.key == 'dry_level')
|
||||
assert desc.exists_fn({'x.com.samsung.da.supportedDryLevel': ['None', '30']}, {}) is True
|
||||
assert desc.exists_fn({}, {}) is False
|
||||
|
||||
def test_write(self):
|
||||
desc = next(e for e in washer.WASHER_SETTINGS.entities if e.key == 'dry_level')
|
||||
path, body = desc.write_fn('Cupboard', {})
|
||||
assert path == ['washer', 'vs', '0']
|
||||
assert body == {'x.com.samsung.da.dryLevel': 'Cupboard'}
|
||||
|
||||
|
||||
class TestWasherCourse:
|
||||
def test_href(self):
|
||||
assert washer.WASHER_COURSE.href == '/course/vs/0'
|
||||
@@ -233,3 +249,201 @@ class TestDetergentSoftenerDosing:
|
||||
assert self._desc('detergent_low').exists_fn({'x.com.samsung.da.options': []}, {}) is False
|
||||
rep = {'x.com.samsung.da.options': _DOSING_OPTIONS}
|
||||
assert self._desc('detergent_low').exists_fn(rep, {}) is True
|
||||
|
||||
|
||||
class TestWashOptionToggles:
|
||||
"""Bubble soak / pre-wash / intensive-wash switches, from the same
|
||||
options[] array as the cycle select (issue #22 follow-up). Confirmed
|
||||
On/Off shape from a dump with Bubble Soak toggled on in the app."""
|
||||
|
||||
@staticmethod
|
||||
def _desc(key):
|
||||
return next(e for e in washer.WASHER_COURSE.entities if e.key == key)
|
||||
|
||||
@staticmethod
|
||||
def _keys():
|
||||
return ('bubble_soak', 'pre_wash', 'intensive')
|
||||
|
||||
@staticmethod
|
||||
def _prefix(key):
|
||||
return {'bubble_soak': 'BubbleSoak',
|
||||
'pre_wash': 'PreWashSetting',
|
||||
'intensive': 'IntensiveSetting'}[key]
|
||||
|
||||
def test_exists_only_when_field_present(self):
|
||||
for key in self._keys():
|
||||
desc = self._desc(key)
|
||||
assert desc.exists_fn({'x.com.samsung.da.options': []}, {}) is False
|
||||
prefix = self._prefix(key)
|
||||
rep = {'x.com.samsung.da.options': [f'{prefix}_Off']}
|
||||
assert desc.exists_fn(rep, {}) is True
|
||||
|
||||
def test_reads_off(self):
|
||||
for key in self._keys():
|
||||
prefix = self._prefix(key)
|
||||
rep = {'x.com.samsung.da.options': [f'{prefix}_Off']}
|
||||
assert self._desc(key).rep_fn(rep) is False
|
||||
|
||||
def test_reads_on(self):
|
||||
for key in self._keys():
|
||||
prefix = self._prefix(key)
|
||||
rep = {'x.com.samsung.da.options': [f'{prefix}_On']}
|
||||
assert self._desc(key).rep_fn(rep) is True
|
||||
|
||||
def test_write_on_and_off(self):
|
||||
"""write_fn receives the same 'On'/'Off' string switch.py sends
|
||||
(not a bool) -- covers a bug where an earlier `'On' if p else 'Off'`
|
||||
implementation always wrote 'On', since any non-empty string
|
||||
(including 'Off') is truthy."""
|
||||
for key in self._keys():
|
||||
prefix = self._prefix(key)
|
||||
rep = {'x.com.samsung.da.options': [f'{prefix}_Off', 'GMT_02']}
|
||||
path, body = self._desc(key).write_fn('On', rep)
|
||||
assert path == ['course', 'vs', '0']
|
||||
assert f'{prefix}_On' in body['x.com.samsung.da.options']
|
||||
assert 'GMT_02' in body['x.com.samsung.da.options']
|
||||
|
||||
rep = {'x.com.samsung.da.options': [f'{prefix}_On']}
|
||||
path, body = self._desc(key).write_fn('Off', rep)
|
||||
assert f'{prefix}_Off' in body['x.com.samsung.da.options']
|
||||
assert f'{prefix}_On' not in body['x.com.samsung.da.options']
|
||||
|
||||
def test_write_rejects_non_on_off_payload(self):
|
||||
for key in self._keys():
|
||||
prefix = self._prefix(key)
|
||||
rep = {'x.com.samsung.da.options': [f'{prefix}_Off']}
|
||||
assert self._desc(key).write_fn('bogus', rep) is None
|
||||
|
||||
|
||||
# editCourseList and availability bitmaps from the reporter's issue #22
|
||||
# follow-up dump (WD90T654DBN/S1, course '30' selected, Bubble Soak just
|
||||
# turned on in the app): 24 courses, course '30' at position 1 reads 'F0'
|
||||
# (available) on all three bitmaps; course '1C' at position 0 reads '00' on
|
||||
# BubbleSoakSet (matching the app graying that control out for Eco 40-60).
|
||||
_EDIT_COURSE_RESOURCES = {
|
||||
'/wm/editcourse/vs/0': {
|
||||
'x.com.samsung.da.editCourseList':
|
||||
'EditCourseList_1C301E26361B1D1F253324322022232F212D272838393729',
|
||||
},
|
||||
}
|
||||
_BUBBLE_SOAK_SET = 'BubbleSoakSet_00F000F000F000F0F0F0F00000F000F0F00000F000000000'
|
||||
_PRE_WASH_AVAILABLE_SET = 'PreWashAvailableSet_F0F000F0F0F000F0F0F0F00000F0F0F0F00000F000000000'
|
||||
_INTENSIVE_AVAILABLE_SET = 'IntensiveAvailableSet_F0F000F0F0F000F0F0F0F00000F0F0F0F00000F000000000'
|
||||
|
||||
|
||||
class TestWashOptionToggleValidation:
|
||||
"""validate_fn rejects turning a toggle on for a course whose byte in
|
||||
its availability bitmap isn't 'F0', with a user-facing message switch.py
|
||||
raises as ServiceValidationError -- distinct from write_fn's silent
|
||||
no-op for a malformed payload."""
|
||||
|
||||
@staticmethod
|
||||
def _desc(key):
|
||||
return next(e for e in washer.WASHER_COURSE.entities if e.key == key)
|
||||
|
||||
def test_allowed_on_a_supported_course(self):
|
||||
rep = {'x.com.samsung.da.options': ['Course_30', _BUBBLE_SOAK_SET]}
|
||||
assert self._desc('bubble_soak').validate_fn(
|
||||
'On', rep, _EDIT_COURSE_RESOURCES) is None
|
||||
|
||||
def test_rejected_on_an_unsupported_course(self):
|
||||
rep = {'x.com.samsung.da.options': ['Course_1C', _BUBBLE_SOAK_SET]}
|
||||
msg = self._desc('bubble_soak').validate_fn('On', rep, _EDIT_COURSE_RESOURCES)
|
||||
assert msg == "Bubble soak isn't available on the selected cycle."
|
||||
|
||||
def test_pre_wash_and_intensive_use_their_own_availableset_field(self):
|
||||
rep = {'x.com.samsung.da.options': ['Course_30', _PRE_WASH_AVAILABLE_SET]}
|
||||
assert self._desc('pre_wash').validate_fn(
|
||||
'On', rep, _EDIT_COURSE_RESOURCES) is None
|
||||
rep = {'x.com.samsung.da.options': ['Course_30', _INTENSIVE_AVAILABLE_SET]}
|
||||
assert self._desc('intensive').validate_fn(
|
||||
'On', rep, _EDIT_COURSE_RESOURCES) is None
|
||||
|
||||
def test_turning_off_is_never_blocked(self):
|
||||
rep = {'x.com.samsung.da.options': ['Course_1C', _BUBBLE_SOAK_SET]}
|
||||
assert self._desc('bubble_soak').validate_fn(
|
||||
'Off', rep, _EDIT_COURSE_RESOURCES) is None
|
||||
|
||||
def test_allows_write_when_course_unresolvable(self):
|
||||
"""No editCourseList, no Course_ token, or a bitmap whose length
|
||||
doesn't match editCourseList -- in every case, fail open rather than
|
||||
block a write we can't actually verify."""
|
||||
desc = self._desc('bubble_soak')
|
||||
rep = {'x.com.samsung.da.options': ['Course_1C', _BUBBLE_SOAK_SET]}
|
||||
assert desc.validate_fn('On', rep, {}) is None
|
||||
|
||||
rep = {'x.com.samsung.da.options': [_BUBBLE_SOAK_SET]}
|
||||
assert desc.validate_fn('On', rep, _EDIT_COURSE_RESOURCES) is None
|
||||
|
||||
rep = {'x.com.samsung.da.options': ['Course_1C', 'BubbleSoakSet_00F0']}
|
||||
assert desc.validate_fn('On', rep, _EDIT_COURSE_RESOURCES) is None
|
||||
|
||||
|
||||
class TestAiEnergyLevel:
|
||||
"""Issue #40 -- /energy/ailevel/vs/0 was unbound on a plain washer.
|
||||
|
||||
The capability itself (common.AI_ENERGY_LEVEL) is tested in
|
||||
test_common_capabilities.py; this just confirms it's wired into the
|
||||
washer registry and that the fixture's single-entry supportedAiLevel
|
||||
(['1'], matching the issue's dump) surfaces as a switch, not a select."""
|
||||
|
||||
def test_fixture_has_complete_coverage(self):
|
||||
from custom_components.localthings.registry.adapter import flatten
|
||||
from custom_components.localthings.registry.by_type import washer as washer_registry
|
||||
from custom_components.localthings.registry.discovery import discover
|
||||
from tests.conftest import _load_device
|
||||
|
||||
resources = _load_device('washer')
|
||||
unbound = []
|
||||
bound = discover(
|
||||
resources,
|
||||
washer_registry.REGISTRY.capabilities,
|
||||
washer_registry.REGISTRY.pattern_capabilities,
|
||||
log=unbound.append,
|
||||
)
|
||||
assert unbound == []
|
||||
state = flatten(bound, resources)
|
||||
assert state['ai_energy_level'] is False # aiLevel '0' -- off
|
||||
|
||||
|
||||
class TestFlexWashAndComboFixturesHaveCompleteCoverage:
|
||||
"""FlexWash (issue #19, previously unrecognized entirely) and
|
||||
washer/dryer combo (issue #22, dry_level) dumps must both resolve to
|
||||
zero unbound hrefs."""
|
||||
|
||||
def test_flexwash(self):
|
||||
from custom_components.localthings.registry.adapter import flatten
|
||||
from custom_components.localthings.registry.by_type import washer as washer_registry
|
||||
from custom_components.localthings.registry.discovery import discover
|
||||
from tests.conftest import _load_device
|
||||
|
||||
resources = _load_device('washer_flexwash')
|
||||
unbound = []
|
||||
bound = discover(
|
||||
resources,
|
||||
washer_registry.REGISTRY.capabilities,
|
||||
washer_registry.REGISTRY.pattern_capabilities,
|
||||
log=unbound.append,
|
||||
)
|
||||
assert unbound == []
|
||||
state = flatten(bound, resources)
|
||||
assert 'dry_level' not in state # plain washer -- no supportedDryLevel field
|
||||
|
||||
def test_dryer_combo(self):
|
||||
from custom_components.localthings.registry.adapter import flatten
|
||||
from custom_components.localthings.registry.by_type import washer as washer_registry
|
||||
from custom_components.localthings.registry.discovery import discover
|
||||
from tests.conftest import _load_device
|
||||
|
||||
resources = _load_device('washer_dryer_combo')
|
||||
unbound = []
|
||||
bound = discover(
|
||||
resources,
|
||||
washer_registry.REGISTRY.capabilities,
|
||||
washer_registry.REGISTRY.pattern_capabilities,
|
||||
log=unbound.append,
|
||||
)
|
||||
assert unbound == []
|
||||
state = flatten(bound, resources)
|
||||
# the device's own "off" sentinel is the literal string 'None', not absence
|
||||
assert state['dry_level'] == 'None'
|
||||
|
||||
Reference in New Issue
Block a user