Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2cd93619a4 | ||
|
|
5a9a002a7f | ||
|
|
43542b146f | ||
|
|
9a899080fa | ||
|
|
88e630b54f | ||
|
|
70a7f861c3 | ||
|
|
ff034905a2 | ||
|
|
ae8bb0b621 | ||
|
|
3688cf24a0 | ||
|
|
d917d757e9 | ||
|
|
f4b4a04628 | ||
|
|
c0298d57d9 | ||
|
|
0503bda0f3 | ||
|
|
6f53b3043b | ||
|
|
bedc662b47 | ||
|
|
296535d0f2 |
No files matched your search
@@ -16,6 +16,7 @@ Your state stays on your LAN: HA talks to the appliance over a direct DTLS sessi
|
|||||||
|
|
||||||
| Type | Registry |
|
| Type | Registry |
|
||||||
|---|---|
|
|---|---|
|
||||||
|
| Air conditioner | `by_type/airconditioner.py` |
|
||||||
| Dryer | `by_type/dryer.py` |
|
| Dryer | `by_type/dryer.py` |
|
||||||
| Oven | `by_type/oven.py` |
|
| Oven | `by_type/oven.py` |
|
||||||
| Dishwasher | `by_type/dishwasher.py` |
|
| Dishwasher | `by_type/dishwasher.py` |
|
||||||
@@ -99,7 +100,7 @@ custom_components/localthings/
|
|||||||
diagnostics.py Redacted diagnostics download (device state + coverage metadata)
|
diagnostics.py Redacted diagnostics download (device state + coverage metadata)
|
||||||
const.py Domain, config keys, probe ports
|
const.py Domain, config keys, probe ports
|
||||||
entity.py Base entity wiring capability registry -> HA entity
|
entity.py Base entity wiring capability registry -> HA entity
|
||||||
sensor.py / binary_sensor.py / switch.py / number.py / select.py / button.py / time.py
|
sensor.py / binary_sensor.py / switch.py / number.py / select.py / button.py / time.py / climate.py
|
||||||
One module per HA platform
|
One module per HA platform
|
||||||
strings.json / translations/ Config-flow copy + entity state translations
|
strings.json / translations/ Config-flow copy + entity state translations
|
||||||
registry/
|
registry/
|
||||||
@@ -110,8 +111,8 @@ custom_components/localthings/
|
|||||||
adapter.py Flattens bound entities into HA-ready state
|
adapter.py Flattens bound entities into HA-ready state
|
||||||
identity.py Reads device identity for type detection
|
identity.py Reads device identity for type detection
|
||||||
redact.py Strips account/identity data before diagnostics leave HA
|
redact.py Strips account/identity data before diagnostics leave HA
|
||||||
capabilities/ Shared + per-family Capability definitions (common, dryer, oven,
|
capabilities/ Shared + per-family Capability definitions (common, airconditioner,
|
||||||
dishwasher, fridge, washer, laundry, operational, ignored)
|
dryer, oven, dishwasher, fridge, washer, laundry, operational, ignored)
|
||||||
by_type/ One DeviceRegistry per appliance type, composed from capabilities/
|
by_type/ One DeviceRegistry per appliance type, composed from capabilities/
|
||||||
tests/ Registry composition, discovery, entity descriptors, coordinator/observe
|
tests/ Registry composition, discovery, entity descriptors, coordinator/observe
|
||||||
behavior, and golden-file regression against captured device dumps
|
behavior, and golden-file regression against captured device dumps
|
||||||
|
|||||||
@@ -0,0 +1,289 @@
|
|||||||
|
"""Climate platform for Local Things.
|
||||||
|
|
||||||
|
The first composite entity in this integration: a single HA climate card that
|
||||||
|
unifies several OCF resources of a Samsung air conditioner. Unlike every other
|
||||||
|
platform here (one descriptor -> one resource field), a climate entity reads
|
||||||
|
power, HVAC mode, current/target temperature, fan (wind) strength, swing (wind
|
||||||
|
direction) and the convenient-mode preset from *different* resources.
|
||||||
|
|
||||||
|
It binds one primary `BoundEntity` (the `/mode/vs/0` capability) so the registry
|
||||||
|
still tracks it, and reads the sibling resources straight from the coordinator
|
||||||
|
snapshot via `coordinator.resource(href)` -- the same cross-resource read that
|
||||||
|
`number.py` (live range/unit) and `select.py` (options callable) already do.
|
||||||
|
|
||||||
|
Writes go through `coordinator.async_send_command(bound, (kind, value))`: the
|
||||||
|
CLIMATE capability's `write_fn` maps each `(kind, value)` payload to the right
|
||||||
|
`(path_segs, body)`, and `async_send_command` POSTs to those path_segs (the
|
||||||
|
bound href is only used for logging), so one descriptor drives writes to power,
|
||||||
|
mode, temperature and wind resources.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from homeassistant.components.climate import (
|
||||||
|
ClimateEntity,
|
||||||
|
ClimateEntityFeature,
|
||||||
|
HVACMode,
|
||||||
|
)
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import UnitOfTemperature
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
|
from .registry.entities import ClimateDesc
|
||||||
|
# The AC's canonical resource hrefs live in the capability module (the single
|
||||||
|
# source of truth shared with its COVERAGE caps); power prefers the OCF-standard
|
||||||
|
# href, falling back to the vendor one, mirroring common.POWER_GENERIC /
|
||||||
|
# POWER_VS_FALLBACK.
|
||||||
|
from .registry.capabilities.airconditioner import (
|
||||||
|
HREF_MODE as MODE_HREF,
|
||||||
|
HREF_POWER as POWER_HREF,
|
||||||
|
HREF_POWER_VS as POWER_VS_HREF,
|
||||||
|
HREF_TEMP_CURRENT as TEMP_CURRENT_HREF,
|
||||||
|
HREF_TEMP_DESIRED as TEMP_DESIRED_HREF,
|
||||||
|
HREF_TEMP_CONTROL as TEMP_CONTROL_HREF,
|
||||||
|
HREF_WIND_STRENGTH as WIND_STRENGTH_HREF,
|
||||||
|
HREF_WIND_DIRECTION as WIND_DIRECTION_HREF,
|
||||||
|
HREF_CONVENIENT as CONVENIENT_HREF,
|
||||||
|
)
|
||||||
|
from .registry.capabilities.common import normalize_temp_unit
|
||||||
|
|
||||||
|
from .const import DOMAIN
|
||||||
|
from .coordinator import LocalThingsCoordinator
|
||||||
|
from .entity import LocalThingsEntity, _is_included
|
||||||
|
|
||||||
|
_MODES_FIELD = 'x.com.samsung.da.modes'
|
||||||
|
_SUPPORTED_FIELD = 'x.com.samsung.da.supportedModes'
|
||||||
|
|
||||||
|
# --- device code <-> HA value maps -----------------------------------------
|
||||||
|
# HVAC mode: Samsung /mode/vs/0 modes <-> HA HVACMode (excluding OFF, which is
|
||||||
|
# driven by the power resource).
|
||||||
|
_DEVICE_TO_HVAC: dict[str, HVACMode] = {
|
||||||
|
'Cool': HVACMode.COOL,
|
||||||
|
'Dry': HVACMode.DRY,
|
||||||
|
'Wind': HVACMode.FAN_ONLY,
|
||||||
|
'Auto': HVACMode.HEAT_COOL,
|
||||||
|
'Heat': HVACMode.HEAT,
|
||||||
|
}
|
||||||
|
_HVAC_TO_DEVICE = {v: k for k, v in _DEVICE_TO_HVAC.items()}
|
||||||
|
|
||||||
|
# Fan (wind strength): device codes "0".."4" -> HA standard fan constants where
|
||||||
|
# a clean match exists so they auto-localize; "turbo" is custom (translated).
|
||||||
|
_DEVICE_TO_FAN: dict[str, str] = {
|
||||||
|
'0': 'auto',
|
||||||
|
'1': 'low',
|
||||||
|
'2': 'medium',
|
||||||
|
'3': 'high',
|
||||||
|
'4': 'turbo',
|
||||||
|
}
|
||||||
|
_FAN_TO_DEVICE = {v: k for k, v in _DEVICE_TO_FAN.items()}
|
||||||
|
|
||||||
|
# Swing (wind direction): all map onto HA standard swing constants (auto-localize).
|
||||||
|
_DEVICE_TO_SWING: dict[str, str] = {
|
||||||
|
'Fix': 'off',
|
||||||
|
'All': 'both',
|
||||||
|
'Up_And_Low': 'vertical',
|
||||||
|
}
|
||||||
|
_SWING_TO_DEVICE = {v: k for k, v in _DEVICE_TO_SWING.items()}
|
||||||
|
|
||||||
|
# Preset (convenient mode): Off/Sleep map onto HA standard presets; Quiet/Smart/
|
||||||
|
# Speed are custom (translated).
|
||||||
|
_DEVICE_TO_PRESET: dict[str, str] = {
|
||||||
|
'Off': 'none',
|
||||||
|
'Sleep': 'sleep',
|
||||||
|
'Quiet': 'quiet',
|
||||||
|
'Smart': 'smart',
|
||||||
|
'Speed': 'speed',
|
||||||
|
}
|
||||||
|
_PRESET_TO_DEVICE = {v: k for k, v in _DEVICE_TO_PRESET.items()}
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entry: ConfigEntry,
|
||||||
|
async_add_entities: AddEntitiesCallback,
|
||||||
|
) -> None:
|
||||||
|
coordinator: LocalThingsCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||||
|
async_add_entities(
|
||||||
|
LocalThingsClimate(coordinator, b)
|
||||||
|
for b in coordinator.bound
|
||||||
|
if isinstance(b.desc, ClimateDesc) and _is_included(b, coordinator)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _first(value):
|
||||||
|
"""Samsung `modes` is a single-element list on some resources, a scalar on
|
||||||
|
others. Return the first element of a list, else the value itself."""
|
||||||
|
if isinstance(value, (list, tuple)):
|
||||||
|
return value[0] if value else None
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def _num(value):
|
||||||
|
try:
|
||||||
|
return float(value)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class LocalThingsClimate(LocalThingsEntity, ClimateEntity):
|
||||||
|
"""Composite climate entity for a Samsung air conditioner."""
|
||||||
|
|
||||||
|
# translation_key comes from the ClimateDesc (base __init__ sets
|
||||||
|
# _attr_translation_key from bound.desc), resolving the state_attributes
|
||||||
|
# translations under entity.climate.airconditioner.
|
||||||
|
# Modern climate entities opt out of the deprecated auto-added TURN_ON/OFF.
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
|
def __init__(self, coordinator: LocalThingsCoordinator, bound) -> None:
|
||||||
|
super().__init__(coordinator, bound)
|
||||||
|
# Primary/main entity for the device: no name suffix, just the device name.
|
||||||
|
self._attr_name = None
|
||||||
|
self._attr_supported_features = (
|
||||||
|
ClimateEntityFeature.TARGET_TEMPERATURE
|
||||||
|
| ClimateEntityFeature.FAN_MODE
|
||||||
|
| ClimateEntityFeature.SWING_MODE
|
||||||
|
| ClimateEntityFeature.PRESET_MODE
|
||||||
|
| ClimateEntityFeature.TURN_ON
|
||||||
|
| ClimateEntityFeature.TURN_OFF
|
||||||
|
)
|
||||||
|
|
||||||
|
# -- resource helpers ---------------------------------------------------
|
||||||
|
|
||||||
|
def _rep(self, href: str) -> dict:
|
||||||
|
return self.coordinator.resource(href) or {}
|
||||||
|
|
||||||
|
def _is_on(self) -> bool:
|
||||||
|
rep = self._rep(POWER_HREF)
|
||||||
|
if 'value' in rep:
|
||||||
|
return bool(rep.get('value'))
|
||||||
|
vs = self._rep(POWER_VS_HREF)
|
||||||
|
return str(vs.get('x.com.samsung.da.power', '')).lower() == 'on'
|
||||||
|
|
||||||
|
def _supported(self, href: str) -> list[str]:
|
||||||
|
return list(self._rep(href).get(_SUPPORTED_FIELD) or [])
|
||||||
|
|
||||||
|
def _read_mode(self, href: str, mapping: dict):
|
||||||
|
"""Current mode of a wind/convenient resource, mapped to its HA value."""
|
||||||
|
return mapping.get(_first(self._rep(href).get(_MODES_FIELD)))
|
||||||
|
|
||||||
|
def _read_modes(self, href: str, mapping: dict) -> list[str]:
|
||||||
|
"""Supported modes of a resource, mapped to HA values (unknowns dropped)."""
|
||||||
|
return [mapping[c] for c in self._supported(href) if c in mapping]
|
||||||
|
|
||||||
|
# -- temperature --------------------------------------------------------
|
||||||
|
|
||||||
|
@property
|
||||||
|
def temperature_unit(self) -> str:
|
||||||
|
raw = self._rep(TEMP_DESIRED_HREF).get('units')
|
||||||
|
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'))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def target_temperature(self):
|
||||||
|
return _num(self._rep(TEMP_DESIRED_HREF).get('temperature'))
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
@property
|
||||||
|
def min_temp(self) -> float:
|
||||||
|
r = self._range()
|
||||||
|
return float(r[0]) if r else super().min_temp
|
||||||
|
|
||||||
|
@property
|
||||||
|
def max_temp(self) -> float:
|
||||||
|
r = self._range()
|
||||||
|
return float(r[1]) if r else super().max_temp
|
||||||
|
|
||||||
|
@property
|
||||||
|
def target_temperature_step(self) -> float:
|
||||||
|
return _num(self._rep(TEMP_CONTROL_HREF).get('increment')) or 1.0
|
||||||
|
|
||||||
|
# -- hvac mode ----------------------------------------------------------
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hvac_mode(self) -> HVACMode:
|
||||||
|
if not self._is_on():
|
||||||
|
return HVACMode.OFF
|
||||||
|
device = _first(self._rep(MODE_HREF).get(_MODES_FIELD))
|
||||||
|
return _DEVICE_TO_HVAC.get(device, HVACMode.AUTO)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hvac_modes(self) -> list[HVACMode]:
|
||||||
|
modes = [HVACMode.OFF]
|
||||||
|
for m in self._supported(MODE_HREF):
|
||||||
|
mapped = _DEVICE_TO_HVAC.get(m)
|
||||||
|
if mapped is not None and mapped not in modes:
|
||||||
|
modes.append(mapped)
|
||||||
|
return modes
|
||||||
|
|
||||||
|
# -- fan / swing / preset ----------------------------------------------
|
||||||
|
|
||||||
|
@property
|
||||||
|
def fan_mode(self):
|
||||||
|
return self._read_mode(WIND_STRENGTH_HREF, _DEVICE_TO_FAN)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def fan_modes(self) -> list[str]:
|
||||||
|
return self._read_modes(WIND_STRENGTH_HREF, _DEVICE_TO_FAN)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def swing_mode(self):
|
||||||
|
return self._read_mode(WIND_DIRECTION_HREF, _DEVICE_TO_SWING)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def swing_modes(self) -> list[str]:
|
||||||
|
return self._read_modes(WIND_DIRECTION_HREF, _DEVICE_TO_SWING)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def preset_mode(self):
|
||||||
|
return self._read_mode(CONVENIENT_HREF, _DEVICE_TO_PRESET)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def preset_modes(self) -> list[str]:
|
||||||
|
return self._read_modes(CONVENIENT_HREF, _DEVICE_TO_PRESET)
|
||||||
|
|
||||||
|
# -- writes -------------------------------------------------------------
|
||||||
|
|
||||||
|
async def async_set_temperature(self, **kwargs) -> None:
|
||||||
|
temp = kwargs.get('temperature')
|
||||||
|
if temp is not None:
|
||||||
|
await self.coordinator.async_send_command(self._bound, ('temperature', temp))
|
||||||
|
|
||||||
|
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
|
||||||
|
if hvac_mode == HVACMode.OFF:
|
||||||
|
await self.coordinator.async_send_command(self._bound, ('power', False))
|
||||||
|
return
|
||||||
|
device = _HVAC_TO_DEVICE.get(hvac_mode)
|
||||||
|
if device is None:
|
||||||
|
return
|
||||||
|
if not self._is_on():
|
||||||
|
await self.coordinator.async_send_command(self._bound, ('power', True))
|
||||||
|
await self.coordinator.async_send_command(self._bound, ('mode', device))
|
||||||
|
|
||||||
|
async def async_turn_on(self) -> None:
|
||||||
|
await self.coordinator.async_send_command(self._bound, ('power', True))
|
||||||
|
|
||||||
|
async def async_turn_off(self) -> None:
|
||||||
|
await self.coordinator.async_send_command(self._bound, ('power', False))
|
||||||
|
|
||||||
|
async def _set_mapped(self, kind: str, mapping: dict, value: str) -> None:
|
||||||
|
"""Map an HA fan/swing/preset value back to its device code and write it."""
|
||||||
|
device = mapping.get(value)
|
||||||
|
if device is not None:
|
||||||
|
await self.coordinator.async_send_command(self._bound, (kind, device))
|
||||||
|
|
||||||
|
async def async_set_fan_mode(self, fan_mode: str) -> None:
|
||||||
|
await self._set_mapped('fan', _FAN_TO_DEVICE, fan_mode)
|
||||||
|
|
||||||
|
async def async_set_swing_mode(self, swing_mode: str) -> None:
|
||||||
|
await self._set_mapped('swing', _SWING_TO_DEVICE, swing_mode)
|
||||||
|
|
||||||
|
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
||||||
|
await self._set_mapped('preset', _PRESET_TO_DEVICE, preset_mode)
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
DOMAIN = "localthings"
|
DOMAIN = "localthings"
|
||||||
|
|
||||||
PLATFORMS = ["sensor", "binary_sensor", "switch", "number", "select", "button", "time"]
|
PLATFORMS = ["sensor", "binary_sensor", "switch", "number", "select", "button", "time", "climate"]
|
||||||
|
|
||||||
CONF_HOST = "host"
|
CONF_HOST = "host"
|
||||||
CONF_PORT = "port"
|
CONF_PORT = "port"
|
||||||
|
|||||||
@@ -26,13 +26,18 @@ async def async_get_config_entry_diagnostics(
|
|||||||
coordinator: LocalThingsCoordinator = hass.data[DOMAIN][entry.entry_id]
|
coordinator: LocalThingsCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||||
integration = await async_get_integration(hass, DOMAIN)
|
integration = await async_get_integration(hass, DOMAIN)
|
||||||
|
|
||||||
|
# importlib.metadata.version() reads the installed package's metadata off
|
||||||
|
# disk (listdir + open + read_text), which trips HA's event-loop blocking
|
||||||
|
# detector when called inline here. Offload it to the executor.
|
||||||
|
stl_version = await hass.async_add_executor_job(pkg_version, "smartthings-local")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"device_type": coordinator.device_type_name or "unknown",
|
"device_type": coordinator.device_type_name or "unknown",
|
||||||
"one_ui_version": coordinator.one_ui_version,
|
"one_ui_version": coordinator.one_ui_version,
|
||||||
"unbound_hrefs": sorted(coordinator._unbound_hrefs),
|
"unbound_hrefs": sorted(coordinator._unbound_hrefs),
|
||||||
"resources": redact_resources(coordinator.last_resources),
|
"resources": redact_resources(coordinator.last_resources),
|
||||||
"integration_version": integration.version,
|
"integration_version": integration.version,
|
||||||
"smartthings_local_version": pkg_version("smartthings-local"),
|
"smartthings_local_version": stl_version,
|
||||||
"observe_mode": coordinator.observe_mode,
|
"observe_mode": coordinator.observe_mode,
|
||||||
"observe_subscribed_hrefs": sorted(coordinator._observe.subscribed_hrefs),
|
"observe_subscribed_hrefs": sorted(coordinator._observe.subscribed_hrefs),
|
||||||
"observe_fallback_hrefs": sorted(coordinator._observe.fallback_hrefs),
|
"observe_fallback_hrefs": sorted(coordinator._observe.fallback_hrefs),
|
||||||
|
|||||||
@@ -12,5 +12,5 @@
|
|||||||
"pyOpenSSL>=23.0",
|
"pyOpenSSL>=23.0",
|
||||||
"smartthings-local>=0.1.0"
|
"smartthings-local>=0.1.0"
|
||||||
],
|
],
|
||||||
"version": "0.6.0"
|
"version": "0.8.0"
|
||||||
}
|
}
|
||||||
@@ -2,12 +2,14 @@
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from ._base import DeviceRegistry
|
from ._base import DeviceRegistry
|
||||||
from . import dishwasher, dryer, oven, refrigerator, washer
|
from . import airconditioner, dishwasher, dryer, oven, refrigerator, washer
|
||||||
|
|
||||||
__all__ = ['DeviceRegistry', '_type_key', 'for_device', 'for_device_by_model']
|
__all__ = ['DeviceRegistry', '_type_key', 'for_device', 'for_device_by_model']
|
||||||
|
|
||||||
|
|
||||||
_REGISTRY_BY_KEY: dict[str, DeviceRegistry] = {
|
_REGISTRY_BY_KEY: dict[str, DeviceRegistry] = {
|
||||||
|
'airconditioner': airconditioner.REGISTRY,
|
||||||
|
'air_conditioner': airconditioner.REGISTRY,
|
||||||
'dishwasher': dishwasher.REGISTRY,
|
'dishwasher': dishwasher.REGISTRY,
|
||||||
'dryer': dryer.REGISTRY,
|
'dryer': dryer.REGISTRY,
|
||||||
'oven': oven.REGISTRY,
|
'oven': oven.REGISTRY,
|
||||||
@@ -66,6 +68,8 @@ def for_device(one_ui_version: str) -> Optional[DeviceRegistry]:
|
|||||||
_CONSUMER_PREFIX_TO_KEY: dict[str, str] = {
|
_CONSUMER_PREFIX_TO_KEY: dict[str, str] = {
|
||||||
'WW': 'washer',
|
'WW': 'washer',
|
||||||
'WD': 'washer',
|
'WD': 'washer',
|
||||||
|
'WF': 'washer',
|
||||||
|
'WV': 'washer', # FlexWash twin units (e.g. WV55M9600AW) -- issue #19
|
||||||
'DV': 'dryer',
|
'DV': 'dryer',
|
||||||
'DW': 'dishwasher',
|
'DW': 'dishwasher',
|
||||||
}
|
}
|
||||||
@@ -88,4 +92,8 @@ def for_device_by_model(model_num: str, description: str) -> Optional[DeviceRegi
|
|||||||
key = _CONSUMER_PREFIX_TO_KEY.get(token[:2].upper())
|
key = _CONSUMER_PREFIX_TO_KEY.get(token[:2].upper())
|
||||||
if key is None and '_REF_' in (model_num or ''):
|
if key is None and '_REF_' in (model_num or ''):
|
||||||
key = 'refrigerator'
|
key = 'refrigerator'
|
||||||
|
# Room air conditioners (e.g. ARTIK051_PRAC_20K) report no oneUiVersion and
|
||||||
|
# a modelNum carrying the '_PRAC_' (Package Room Air Conditioner) token.
|
||||||
|
if key is None and '_PRAC_' in (model_num or ''):
|
||||||
|
key = 'airconditioner'
|
||||||
return _REGISTRY_BY_KEY.get(key) if key else None
|
return _REGISTRY_BY_KEY.get(key) if key else None
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
"""Air-conditioner device registry (Samsung ARTIK051_PRAC-class, issue #17).
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Reuses common.ALARMS + common.ENERGY_METER, fridge.FIRMWARE_UPDATE (as every
|
||||||
|
registry does), and dishwasher.DIAGNOSIS for /diagnosis/vs/0.
|
||||||
|
"""
|
||||||
|
from ..capabilities import airconditioner, common, dishwasher, fridge, ignored
|
||||||
|
from ._base import DeviceRegistry, _build
|
||||||
|
|
||||||
|
REGISTRY = DeviceRegistry(
|
||||||
|
name='airconditioner',
|
||||||
|
capabilities=_build([
|
||||||
|
*ignored.IGNORED,
|
||||||
|
common.ALARMS,
|
||||||
|
common.ENERGY_METER,
|
||||||
|
fridge.FIRMWARE_UPDATE,
|
||||||
|
dishwasher.DIAGNOSIS,
|
||||||
|
airconditioner.CLIMATE,
|
||||||
|
airconditioner.AIR_PURIFY,
|
||||||
|
airconditioner.AUTO_CLEAN,
|
||||||
|
airconditioner.AIR_FILTER,
|
||||||
|
*airconditioner.COVERAGE,
|
||||||
|
]),
|
||||||
|
)
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
"""Refrigerator device registry."""
|
"""Refrigerator device registry."""
|
||||||
from ..capabilities import common, fridge, ignored
|
from ..capabilities import common, dishwasher, fridge, ignored
|
||||||
from ._base import DeviceRegistry, _build
|
from ._base import DeviceRegistry, _build
|
||||||
|
|
||||||
REGISTRY = DeviceRegistry(
|
REGISTRY = DeviceRegistry(
|
||||||
@@ -17,6 +17,7 @@ REGISTRY = DeviceRegistry(
|
|||||||
common.ALARMS,
|
common.ALARMS,
|
||||||
common.ENERGY_METER,
|
common.ENERGY_METER,
|
||||||
common.WATER_FILTER,
|
common.WATER_FILTER,
|
||||||
|
dishwasher.DIAGNOSIS,
|
||||||
fridge.ICEMAKER_NIGHTTIME,
|
fridge.ICEMAKER_NIGHTTIME,
|
||||||
fridge.FLEX_ZONE,
|
fridge.FLEX_ZONE,
|
||||||
fridge.REFRIGERATION,
|
fridge.REFRIGERATION,
|
||||||
@@ -26,13 +27,16 @@ REGISTRY = DeviceRegistry(
|
|||||||
fridge.CABINET_LIGHT_ENHANCED,
|
fridge.CABINET_LIGHT_ENHANCED,
|
||||||
fridge.SABBATH,
|
fridge.SABBATH,
|
||||||
fridge.BEVERAGE_ZONE,
|
fridge.BEVERAGE_ZONE,
|
||||||
|
fridge.PANTRY_ZONE,
|
||||||
fridge.FIRMWARE_UPDATE,
|
fridge.FIRMWARE_UPDATE,
|
||||||
fridge.DEFROST_DELAY,
|
fridge.DEFROST_DELAY,
|
||||||
|
fridge.DEFROST_DELAY_NATIVE_DUPLICATE,
|
||||||
fridge.DEFROST_BLOCK_STATUS,
|
fridge.DEFROST_BLOCK_STATUS,
|
||||||
fridge.SELF_CHECK,
|
fridge.SELF_CHECK,
|
||||||
fridge.DOORS_FALLBACK,
|
fridge.DOORS_FALLBACK,
|
||||||
fridge.TEMPERATURES_FALLBACK,
|
fridge.TEMPERATURES_FALLBACK,
|
||||||
fridge.ICEMAKER_STATUS_FALLBACK,
|
fridge.ICEMAKER_STATUS_FALLBACK,
|
||||||
|
fridge.ICEMAKER_STATUS_NATIVE_DUPLICATE,
|
||||||
fridge.REFRIGERATION_FALLBACK,
|
fridge.REFRIGERATION_FALLBACK,
|
||||||
]),
|
]),
|
||||||
pattern_capabilities=[
|
pattern_capabilities=[
|
||||||
|
|||||||
@@ -0,0 +1,170 @@
|
|||||||
|
"""Capabilities for the Samsung air-conditioner family (ARTIK051_PRAC-class).
|
||||||
|
|
||||||
|
Resources verified against the issue #17 diagnostics dump (model
|
||||||
|
ARTIK051_PRAC_20K). This is the first family whose core controls surface as a
|
||||||
|
single composite HA `climate` entity rather than a scatter of switches/selects:
|
||||||
|
power (on/off), HVAC mode, current/target temperature, fan (wind) strength,
|
||||||
|
swing (wind direction), and the convenient-mode preset all live on one climate
|
||||||
|
card. The climate platform (climate.py) reads those sibling resources from the
|
||||||
|
coordinator snapshot; here we bind the primary `/mode/vs/0` resource to the
|
||||||
|
`ClimateDesc` and mark the consumed siblings as covered.
|
||||||
|
|
||||||
|
None of these caps may go into the global `ALL`/`CAPABILITIES`: `/mode/vs/0`,
|
||||||
|
`/temperatures/vs/0`, `/humidity/*` collide with fridge/oven hrefs of a
|
||||||
|
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
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Canonical AC resource hrefs. The climate entity (climate.py) binds the
|
||||||
|
# primary HREF_MODE via CLIMATE below and reads the CLIMATE_CONSUMED_HREFS
|
||||||
|
# siblings off the coordinator snapshot; those siblings are marked covered
|
||||||
|
# (no-entity caps) so discover() reports no gap. Declared once here and
|
||||||
|
# imported by climate.py, so a new sibling read can't drift out of sync with
|
||||||
|
# its coverage entry.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
HREF_MODE = '/mode/vs/0' # primary (bound by CLIMATE)
|
||||||
|
HREF_POWER = '/power/0' # on/off -> HVACMode.OFF / TURN_ON/OFF
|
||||||
|
HREF_POWER_VS = '/power/vs/0' # vendor fallback for on/off
|
||||||
|
HREF_TEMP_CURRENT = '/temperature/current/0' # current_temperature
|
||||||
|
HREF_TEMP_DESIRED = '/temperature/desired/0' # target_temperature (write target)
|
||||||
|
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
|
||||||
|
|
||||||
|
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,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _num(v):
|
||||||
|
try:
|
||||||
|
return float(v)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _filter_usage_percent(rep):
|
||||||
|
"""Filter usage as a percentage of rated capacity. The device reports
|
||||||
|
`filterUsage` as a raw count in `filterCapacityUnit` (Hours here, e.g.
|
||||||
|
100 of a 500 capacity), so a plain value with a '%' unit would be wrong --
|
||||||
|
normalize to used/capacity. Returns None when capacity is missing/zero."""
|
||||||
|
used = _num(rep.get('x.com.samsung.da.filterUsage'))
|
||||||
|
cap = _num(rep.get('x.com.samsung.da.filterCapacity'))
|
||||||
|
if used is None or not cap:
|
||||||
|
return None
|
||||||
|
return round(used / cap * 100)
|
||||||
|
|
||||||
|
|
||||||
|
def _first_mode(rep):
|
||||||
|
"""Representative scalar for the climate entity in the flattened state
|
||||||
|
(golden/regression). The real entity computes hvac_mode from power + mode."""
|
||||||
|
modes = rep.get('x.com.samsung.da.modes')
|
||||||
|
if isinstance(modes, (list, tuple)):
|
||||||
|
return modes[0] if modes else None
|
||||||
|
return modes
|
||||||
|
|
||||||
|
|
||||||
|
def _climate_write(payload, rep, href=None):
|
||||||
|
"""Map a (kind, value) command from the climate platform to the
|
||||||
|
(path_segs, body) for that one sub-write. `value` is already the raw device
|
||||||
|
code (the platform maps HA<->device). async_send_command POSTs to path_segs,
|
||||||
|
so a single desc drives writes across power/mode/temperature/wind resources.
|
||||||
|
Read-modify-write safe: each write sends only its own field, leaving the
|
||||||
|
resource's other fields (e.g. /mode/vs/0's opaque `options` blob) untouched.
|
||||||
|
"""
|
||||||
|
kind, value = payload
|
||||||
|
if kind == 'power':
|
||||||
|
return (['power', '0'], {'value': bool(value)})
|
||||||
|
if kind == 'mode':
|
||||||
|
return (['mode', 'vs', '0'], {'x.com.samsung.da.modes': [value]})
|
||||||
|
if kind == 'temperature':
|
||||||
|
return (['temperature', 'desired', '0'], {'temperature': int(round(float(value)))})
|
||||||
|
if kind == 'fan':
|
||||||
|
return (['wind', 'strength', 'vs', '0'], {'x.com.samsung.da.modes': value})
|
||||||
|
if kind == 'swing':
|
||||||
|
return (['wind', 'direction', 'vs', '0'], {'x.com.samsung.da.modes': value})
|
||||||
|
if kind == 'preset':
|
||||||
|
return (['mode', 'convenient', 'vs', '0'], {'x.com.samsung.da.modes': value})
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
CLIMATE = Capability(
|
||||||
|
href=HREF_MODE,
|
||||||
|
poll_tier='warm',
|
||||||
|
entities=(
|
||||||
|
ClimateDesc(key='climate', translation_key='airconditioner',
|
||||||
|
rep_fn=_first_mode, write_fn=_climate_write),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
AIR_PURIFY = Capability(
|
||||||
|
href='/option/airpurify/vs/0',
|
||||||
|
poll_tier='warm',
|
||||||
|
entities=(
|
||||||
|
SwitchDesc(key='air_purify', field='x.com.samsung.da.modes',
|
||||||
|
name='Air purification', icon='mdi:air-purifier',
|
||||||
|
entity_category='config',
|
||||||
|
value_fn=lambda v: v == 'On',
|
||||||
|
write_fn=lambda p, rep, href=None: (
|
||||||
|
['option', 'airpurify', 'vs', '0'],
|
||||||
|
{'x.com.samsung.da.modes': 'On' if p == 'On' else 'Off'})),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
AUTO_CLEAN = Capability(
|
||||||
|
href='/option/autoclean/vs/0',
|
||||||
|
poll_tier='cold',
|
||||||
|
entities=(
|
||||||
|
SwitchDesc(key='auto_clean', field='x.com.samsung.da.settingStatus',
|
||||||
|
name='Auto clean', icon='mdi:spray-bottle',
|
||||||
|
entity_category='config',
|
||||||
|
value_fn=lambda v: v == 'On',
|
||||||
|
write_fn=lambda p, rep, href=None: (
|
||||||
|
['option', 'autoclean', 'vs', '0'],
|
||||||
|
{'x.com.samsung.da.settingStatus': 'On' if p == 'On' else 'Off'})),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
AIR_FILTER = Capability(
|
||||||
|
href='/filter/airdustfilter/vs/0',
|
||||||
|
poll_tier='cold',
|
||||||
|
entities=(
|
||||||
|
SensorDesc(key='air_filter_usage', rep_fn=_filter_usage_percent,
|
||||||
|
name='Filter usage', unit='%', state_class='measurement',
|
||||||
|
icon='mdi:air-filter', entity_category='diagnostic'),
|
||||||
|
SensorDesc(key='air_filter_status', field='x.com.samsung.da.filterStatus',
|
||||||
|
name='Filter status', device_class='enum',
|
||||||
|
options=('normal', 'wash', 'replace'),
|
||||||
|
translation_key='air_filter_status',
|
||||||
|
icon='mdi:air-filter', entity_category='diagnostic',
|
||||||
|
value_fn=lambda v: v.lower() if isinstance(v, str) else v),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# AC-scoped coverage: the CLIMATE_CONSUMED_HREFS above (read by the climate
|
||||||
|
# entity) plus vendor duplicates / all-zero-ambiguous / plumbing resources.
|
||||||
|
# These are NOT in the global ignored.IGNORED because several of them
|
||||||
|
# (/mode/vs/0 handled above, /temperatures/vs/0, /humidity/*) collide with
|
||||||
|
# other families' schemas. A no-entity Capability still marks the href as
|
||||||
|
# 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',
|
||||||
|
'/humidity/0',
|
||||||
|
'/humidity/vs/0',
|
||||||
|
# Presence-personalization plumbing (empty item list here).
|
||||||
|
'/personality/presence/vs/0',
|
||||||
|
]
|
||||||
|
|
||||||
|
# Built as bare no-entity caps; folded into the AC registry (not global).
|
||||||
|
COVERAGE = [Capability(href=h) for h in (CLIMATE_CONSUMED_HREFS + _AC_IGNORED)]
|
||||||
@@ -175,6 +175,38 @@ ENERGY_METER = Capability(
|
|||||||
state_class='total_increasing', unit='kWh', value_fn=wh_to_kwh,
|
state_class='total_increasing', unit='kWh', value_fn=wh_to_kwh,
|
||||||
exists_fn=lambda rep, resources: (
|
exists_fn=lambda rep, resources: (
|
||||||
not rep or 'x.com.samsung.da.cumulativePower' in rep)),
|
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)),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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(
|
DEFROST_BLOCK_STATUS = Capability(
|
||||||
href='/defrost/block/vs/0',
|
href='/defrost/block/vs/0',
|
||||||
poll_tier='warm',
|
poll_tier='warm',
|
||||||
@@ -494,17 +502,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)
|
# Flex zone (convertible drawer — /mode/vs/0 on RF9000-class fridges)
|
||||||
#
|
#
|
||||||
# x.com.samsung.da.modes holds multiple orthogonal flags in one list.
|
# x.com.samsung.da.modes holds multiple orthogonal flags in one list; the
|
||||||
# The flex zone entry is identified by the CV_TTYPE_RF9000A_ prefix.
|
# 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.
|
# 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):
|
def _flex_zone_write(p, rep, href=None):
|
||||||
modes = list(rep.get('x.com.samsung.da.modes') or [])
|
supported = _flex_zone_supported(rep)
|
||||||
modes = [m for m in modes if not m.startswith('CV_TTYPE_RF9000A_')]
|
modes = [m for m in (rep.get('x.com.samsung.da.modes') or []) if m not in supported]
|
||||||
modes.append(p)
|
modes.append(p)
|
||||||
return ['mode', 'vs', '0'], {'x.com.samsung.da.modes': modes}
|
return ['mode', 'vs', '0'], {'x.com.samsung.da.modes': modes}
|
||||||
|
|
||||||
@@ -514,16 +569,13 @@ FLEX_ZONE = Capability(
|
|||||||
poll_tier='warm',
|
poll_tier='warm',
|
||||||
entities=(
|
entities=(
|
||||||
SelectDesc(key='flex_zone_mode',
|
SelectDesc(key='flex_zone_mode',
|
||||||
field='x.com.samsung.da.modes',
|
|
||||||
name='Flex zone mode', icon='mdi:thermostat',
|
name='Flex zone mode', icon='mdi:thermostat',
|
||||||
translation_key='flex_zone_mode',
|
translation_key='flex_zone_mode',
|
||||||
entity_category='config',
|
entity_category='config',
|
||||||
options_field='x.com.samsung.da.supportedOptions',
|
options_field='x.com.samsung.da.supportedOptions',
|
||||||
exists_fn=lambda rep, resources: bool(
|
exists_fn=lambda rep, resources: bool(
|
||||||
rep.get('x.com.samsung.da.supportedOptions')),
|
rep.get('x.com.samsung.da.supportedOptions')),
|
||||||
value_fn=lambda modes: next(
|
rep_fn=_flex_zone_current,
|
||||||
(m for m in (modes or [])
|
|
||||||
if m.startswith('CV_TTYPE_RF9000A_')), None),
|
|
||||||
write_fn=_flex_zone_write),
|
write_fn=_flex_zone_write),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -655,6 +707,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
|
# OCF-native /refrigeration/0 (issue #7's unbound_hrefs) -- the odd one out
|
||||||
# in this section: its three fields duplicate two *different* richer
|
# in this section: its three fields duplicate two *different* richer
|
||||||
# hrefs (REFRIGERATION's rapidFridge/rapidFreezing and
|
# hrefs (REFRIGERATION's rapidFridge/rapidFreezing and
|
||||||
|
|||||||
@@ -82,6 +82,19 @@ WASHER_SETTINGS = Capability(
|
|||||||
options_field='x.com.samsung.da.supportedRinseCycles',
|
options_field='x.com.samsung.da.supportedRinseCycles',
|
||||||
write_fn=lambda p, rep, href=None: (
|
write_fn=lambda p, rep, href=None: (
|
||||||
['washer', 'vs', '0'], {'x.com.samsung.da.rinseCycles': p})),
|
['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})),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -160,13 +173,50 @@ def _level_options(prefix):
|
|||||||
return lambda resources: _supported_level_options(resources, prefix)
|
return lambda resources: _supported_level_options(resources, prefix)
|
||||||
|
|
||||||
|
|
||||||
|
def _dosing_level(prefix):
|
||||||
|
"""Current dose code, normalized to the `Supported<prefix>` code format.
|
||||||
|
|
||||||
|
The device reports the selected level as `<prefix>_<code>` with the code
|
||||||
|
un-padded (e.g. '3'), but the valid codes -- which are also this select's
|
||||||
|
options and its translation keys -- come from `Supported<prefix>_<hexpairs>`
|
||||||
|
as zero-padded hex pairs (e.g. '03'). Left as '3', the current value sits
|
||||||
|
outside the select's own option list, so HA renders it 'unknown' (issue #9).
|
||||||
|
Resolve it to the supported code with the same integer value so
|
||||||
|
current_option matches an option (and its translation)."""
|
||||||
|
def fn(rep):
|
||||||
|
opts = rep.get('x.com.samsung.da.options')
|
||||||
|
raw = option_value(opts, prefix)
|
||||||
|
if raw is None:
|
||||||
|
return None
|
||||||
|
supported_raw = option_value(opts, f'Supported{prefix}')
|
||||||
|
try:
|
||||||
|
target = int(raw, 16)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return raw
|
||||||
|
for code in hex_pairs(supported_raw) if supported_raw else []:
|
||||||
|
try:
|
||||||
|
if int(code, 16) == target:
|
||||||
|
return code
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
continue
|
||||||
|
return raw
|
||||||
|
return fn
|
||||||
|
|
||||||
|
|
||||||
def _level_write(prefix):
|
def _level_write(prefix):
|
||||||
def write(p, rep, href=None):
|
def write(p, rep, href=None):
|
||||||
opts = list(rep.get('x.com.samsung.da.options') or [])
|
opts = list(rep.get('x.com.samsung.da.options') or [])
|
||||||
if not opts:
|
if not opts:
|
||||||
return None
|
return None
|
||||||
|
# `p` is the zero-padded supported code the UI selected (e.g. '03');
|
||||||
|
# the device stores the level un-padded (e.g. '3'), matching how it
|
||||||
|
# reports it, so write it back in that native shape.
|
||||||
|
try:
|
||||||
|
native = format(int(p, 16), 'X')
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
native = p
|
||||||
return ['course', 'vs', '0'], {
|
return ['course', 'vs', '0'], {
|
||||||
'x.com.samsung.da.options': replace_in_options(opts, prefix, p),
|
'x.com.samsung.da.options': replace_in_options(opts, prefix, native),
|
||||||
}
|
}
|
||||||
return write
|
return write
|
||||||
|
|
||||||
@@ -201,8 +251,7 @@ WASHER_COURSE = Capability(
|
|||||||
options=_level_options('DetergentLevelCtrl'),
|
options=_level_options('DetergentLevelCtrl'),
|
||||||
exists_fn=lambda rep, resources: bool(
|
exists_fn=lambda rep, resources: bool(
|
||||||
_level_options('DetergentLevelCtrl')(resources)),
|
_level_options('DetergentLevelCtrl')(resources)),
|
||||||
rep_fn=lambda rep: option_value(
|
rep_fn=_dosing_level('DetergentLevelCtrl'),
|
||||||
rep.get('x.com.samsung.da.options'), 'DetergentLevelCtrl'),
|
|
||||||
write_fn=_level_write('DetergentLevelCtrl')),
|
write_fn=_level_write('DetergentLevelCtrl')),
|
||||||
SelectDesc(key='detergent_water_hardness', name='Detergent water hardness',
|
SelectDesc(key='detergent_water_hardness', name='Detergent water hardness',
|
||||||
icon='mdi:water-opacity',
|
icon='mdi:water-opacity',
|
||||||
@@ -211,8 +260,7 @@ WASHER_COURSE = Capability(
|
|||||||
options=_level_options('DetergentLevel2Ctrl'),
|
options=_level_options('DetergentLevel2Ctrl'),
|
||||||
exists_fn=lambda rep, resources: bool(
|
exists_fn=lambda rep, resources: bool(
|
||||||
_level_options('DetergentLevel2Ctrl')(resources)),
|
_level_options('DetergentLevel2Ctrl')(resources)),
|
||||||
rep_fn=lambda rep: option_value(
|
rep_fn=_dosing_level('DetergentLevel2Ctrl'),
|
||||||
rep.get('x.com.samsung.da.options'), 'DetergentLevel2Ctrl'),
|
|
||||||
write_fn=_level_write('DetergentLevel2Ctrl')),
|
write_fn=_level_write('DetergentLevel2Ctrl')),
|
||||||
SelectDesc(key='softener_quantity', name='Softener quantity', icon='mdi:flask-outline',
|
SelectDesc(key='softener_quantity', name='Softener quantity', icon='mdi:flask-outline',
|
||||||
translation_key='washer_dosing_quantity',
|
translation_key='washer_dosing_quantity',
|
||||||
@@ -220,8 +268,7 @@ WASHER_COURSE = Capability(
|
|||||||
options=_level_options('SoftenerLevelCtrl'),
|
options=_level_options('SoftenerLevelCtrl'),
|
||||||
exists_fn=lambda rep, resources: bool(
|
exists_fn=lambda rep, resources: bool(
|
||||||
_level_options('SoftenerLevelCtrl')(resources)),
|
_level_options('SoftenerLevelCtrl')(resources)),
|
||||||
rep_fn=lambda rep: option_value(
|
rep_fn=_dosing_level('SoftenerLevelCtrl'),
|
||||||
rep.get('x.com.samsung.da.options'), 'SoftenerLevelCtrl'),
|
|
||||||
write_fn=_level_write('SoftenerLevelCtrl')),
|
write_fn=_level_write('SoftenerLevelCtrl')),
|
||||||
SelectDesc(key='softener_concentration', name='Softener concentration',
|
SelectDesc(key='softener_concentration', name='Softener concentration',
|
||||||
icon='mdi:flask-plus-outline',
|
icon='mdi:flask-plus-outline',
|
||||||
@@ -230,8 +277,7 @@ WASHER_COURSE = Capability(
|
|||||||
options=_level_options('SoftenerLevel2Ctrl'),
|
options=_level_options('SoftenerLevel2Ctrl'),
|
||||||
exists_fn=lambda rep, resources: bool(
|
exists_fn=lambda rep, resources: bool(
|
||||||
_level_options('SoftenerLevel2Ctrl')(resources)),
|
_level_options('SoftenerLevel2Ctrl')(resources)),
|
||||||
rep_fn=lambda rep: option_value(
|
rep_fn=_dosing_level('SoftenerLevel2Ctrl'),
|
||||||
rep.get('x.com.samsung.da.options'), 'SoftenerLevel2Ctrl'),
|
|
||||||
write_fn=_level_write('SoftenerLevel2Ctrl')),
|
write_fn=_level_write('SoftenerLevel2Ctrl')),
|
||||||
BinarySensorDesc(key='detergent_low', name='Detergent low',
|
BinarySensorDesc(key='detergent_low', name='Detergent low',
|
||||||
icon='mdi:alert-circle-outline', device_class='problem',
|
icon='mdi:alert-circle-outline', device_class='problem',
|
||||||
|
|||||||
@@ -86,6 +86,16 @@ class TimeDesc(SamsungEntityDescription):
|
|||||||
write_fn: WriteFn = None
|
write_fn: WriteFn = None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, kw_only=True)
|
||||||
|
class ClimateDesc(SamsungEntityDescription):
|
||||||
|
# A composite entity: it binds one *primary* resource (its href) but the
|
||||||
|
# climate platform reads sibling resources (power, temperature, wind) from
|
||||||
|
# the coordinator snapshot and writes to several of them. write_fn takes a
|
||||||
|
# (kind, value) payload from the platform and returns the (path_segs, body)
|
||||||
|
# for that one sub-write, so a single desc drives multi-resource writes.
|
||||||
|
write_fn: WriteFn = None
|
||||||
|
|
||||||
|
|
||||||
PLATFORM_OF: dict[type, str] = {
|
PLATFORM_OF: dict[type, str] = {
|
||||||
SensorDesc: 'sensor',
|
SensorDesc: 'sensor',
|
||||||
BinarySensorDesc: 'binary_sensor',
|
BinarySensorDesc: 'binary_sensor',
|
||||||
@@ -94,4 +104,5 @@ PLATFORM_OF: dict[type, str] = {
|
|||||||
ButtonDesc: 'button',
|
ButtonDesc: 'button',
|
||||||
NumberDesc: 'number',
|
NumberDesc: 'number',
|
||||||
TimeDesc: 'time',
|
TimeDesc: 'time',
|
||||||
|
ClimateDesc: 'climate',
|
||||||
}
|
}
|
||||||
@@ -30,13 +30,25 @@
|
|||||||
"sp_ttype_wine_dessert": "Wine and Dessert"
|
"sp_ttype_wine_dessert": "Wine and Dessert"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"pantry_zone_mode": {
|
||||||
|
"state": {
|
||||||
|
"fdr_wine": "Wine",
|
||||||
|
"fdr_deli": "Deli",
|
||||||
|
"fdr_drinks": "Drinks"
|
||||||
|
}
|
||||||
|
},
|
||||||
"flex_zone_mode": {
|
"flex_zone_mode": {
|
||||||
"state": {
|
"state": {
|
||||||
"cv_ttype_rf9000a_freeze": "Freeze",
|
"cv_ttype_rf9000a_freeze": "Freeze",
|
||||||
"cv_ttype_rf9000a_softfreeze": "Soft Freeze",
|
"cv_ttype_rf9000a_softfreeze": "Soft Freeze",
|
||||||
"cv_ttype_rf9000a_meat_fish": "Meat/Fish",
|
"cv_ttype_rf9000a_meat_fish": "Meat/Fish",
|
||||||
"cv_ttype_rf9000a_fruit_veggies": "Fruit & Veggies",
|
"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": {
|
"dishwasher_cycle": {
|
||||||
@@ -119,6 +131,18 @@
|
|||||||
"02": "2x",
|
"02": "2x",
|
||||||
"03": "3x"
|
"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": {
|
"sensor": {
|
||||||
@@ -140,6 +164,44 @@
|
|||||||
"active": "Active",
|
"active": "Active",
|
||||||
"pause": "Paused"
|
"pause": "Paused"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"air_filter_status": {
|
||||||
|
"state": {
|
||||||
|
"normal": "Normal",
|
||||||
|
"wash": "Wash",
|
||||||
|
"replace": "Replace"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"climate": {
|
||||||
|
"airconditioner": {
|
||||||
|
"state_attributes": {
|
||||||
|
"fan_mode": {
|
||||||
|
"state": {
|
||||||
|
"auto": "Auto",
|
||||||
|
"low": "Low",
|
||||||
|
"medium": "Medium",
|
||||||
|
"high": "High",
|
||||||
|
"turbo": "Turbo"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"swing_mode": {
|
||||||
|
"state": {
|
||||||
|
"off": "Fixed",
|
||||||
|
"both": "All directions",
|
||||||
|
"vertical": "Up and down"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"preset_mode": {
|
||||||
|
"state": {
|
||||||
|
"none": "Off",
|
||||||
|
"sleep": "Sleep",
|
||||||
|
"quiet": "Quiet",
|
||||||
|
"smart": "Smart",
|
||||||
|
"speed": "Speed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -30,13 +30,25 @@
|
|||||||
"sp_ttype_wine_dessert": "Wine and Dessert"
|
"sp_ttype_wine_dessert": "Wine and Dessert"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"pantry_zone_mode": {
|
||||||
|
"state": {
|
||||||
|
"fdr_wine": "Wine",
|
||||||
|
"fdr_deli": "Deli",
|
||||||
|
"fdr_drinks": "Drinks"
|
||||||
|
}
|
||||||
|
},
|
||||||
"flex_zone_mode": {
|
"flex_zone_mode": {
|
||||||
"state": {
|
"state": {
|
||||||
"cv_ttype_rf9000a_freeze": "Freeze",
|
"cv_ttype_rf9000a_freeze": "Freeze",
|
||||||
"cv_ttype_rf9000a_softfreeze": "Soft Freeze",
|
"cv_ttype_rf9000a_softfreeze": "Soft Freeze",
|
||||||
"cv_ttype_rf9000a_meat_fish": "Meat/Fish",
|
"cv_ttype_rf9000a_meat_fish": "Meat/Fish",
|
||||||
"cv_ttype_rf9000a_fruit_veggies": "Fruit & Veggies",
|
"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": {
|
"dishwasher_cycle": {
|
||||||
@@ -119,6 +131,18 @@
|
|||||||
"02": "2x",
|
"02": "2x",
|
||||||
"03": "3x"
|
"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": {
|
"sensor": {
|
||||||
@@ -140,6 +164,44 @@
|
|||||||
"active": "Active",
|
"active": "Active",
|
||||||
"pause": "Paused"
|
"pause": "Paused"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"air_filter_status": {
|
||||||
|
"state": {
|
||||||
|
"normal": "Normal",
|
||||||
|
"wash": "Wash",
|
||||||
|
"replace": "Replace"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"climate": {
|
||||||
|
"airconditioner": {
|
||||||
|
"state_attributes": {
|
||||||
|
"fan_mode": {
|
||||||
|
"state": {
|
||||||
|
"auto": "Auto",
|
||||||
|
"low": "Low",
|
||||||
|
"medium": "Medium",
|
||||||
|
"high": "High",
|
||||||
|
"turbo": "Turbo"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"swing_mode": {
|
||||||
|
"state": {
|
||||||
|
"off": "Fixed",
|
||||||
|
"both": "All directions",
|
||||||
|
"vertical": "Up and down"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"preset_mode": {
|
||||||
|
"state": {
|
||||||
|
"none": "Off",
|
||||||
|
"sleep": "Sleep",
|
||||||
|
"quiet": "Quiet",
|
||||||
|
"smart": "Smart",
|
||||||
|
"speed": "Speed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
+400
@@ -0,0 +1,400 @@
|
|||||||
|
{
|
||||||
|
"meta": {
|
||||||
|
"model": "ARTIK051_PRAC_20K",
|
||||||
|
"device_type": "airconditioner",
|
||||||
|
"source": "issue #17 diagnostics (scrubbed)",
|
||||||
|
"note": "First air-conditioner /device/0 dump; first composite HA climate entity."
|
||||||
|
},
|
||||||
|
"device0": [
|
||||||
|
{
|
||||||
|
"rt": [
|
||||||
|
"x.com.samsung.devcol",
|
||||||
|
"oic.wk.col"
|
||||||
|
],
|
||||||
|
"if": [
|
||||||
|
"oic.if.baseline",
|
||||||
|
"oic.if.ll",
|
||||||
|
"oic.if.b"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"href": "/personality/presence/vs/0",
|
||||||
|
"rep": {
|
||||||
|
"x.com.samsung.da.items": [
|
||||||
|
{
|
||||||
|
"x.com.samsung.da.id": "",
|
||||||
|
"x.com.samsung.da.deviceId": "**REDACTED**",
|
||||||
|
"x.com.samsung.da.value": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"href": "/realtimenotiforclient/vs/0",
|
||||||
|
"rep": {
|
||||||
|
"x.com.samsung.da.timeforshortnoti": "0",
|
||||||
|
"x.com.samsung.da.longnotisubscription": "false",
|
||||||
|
"x.com.samsung.da.periodicnotisubscription": "true"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"href": "/filter/airdustfilter/vs/0",
|
||||||
|
"rep": {
|
||||||
|
"x.com.samsung.da.filterUsage": "100",
|
||||||
|
"x.com.samsung.da.filterUsageResolution": "1",
|
||||||
|
"x.com.samsung.da.filterDesiredUsage": "500",
|
||||||
|
"x.com.samsung.da.filterStatus": "wash",
|
||||||
|
"x.com.samsung.da.filterCapacity": "500",
|
||||||
|
"x.com.samsung.da.filterCapacityUnit": "Hour",
|
||||||
|
"x.com.samsung.da.filterResetType": [
|
||||||
|
"replaceable",
|
||||||
|
"washable"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"href": "/temperature/control/vs/0",
|
||||||
|
"rep": {
|
||||||
|
"x.com.samsung.da.increment": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"href": "/mode/convenient/vs/0",
|
||||||
|
"rep": {
|
||||||
|
"x.com.samsung.da.modes": "Off",
|
||||||
|
"x.com.samsung.da.supportedModes": [
|
||||||
|
"Off",
|
||||||
|
"Sleep",
|
||||||
|
"Quiet",
|
||||||
|
"Smart",
|
||||||
|
"Speed"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"href": "/option/airpurify/vs/0",
|
||||||
|
"rep": {
|
||||||
|
"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.settingStatus": "Off",
|
||||||
|
"x.com.samsung.da.progress": "0",
|
||||||
|
"x.com.samsung.da.supportedStatus": [
|
||||||
|
"Start",
|
||||||
|
"Stop"
|
||||||
|
],
|
||||||
|
"x.com.samsung.da.supportedSettingStatus": [
|
||||||
|
"On",
|
||||||
|
"Off"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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": "/wind/direction/vs/0",
|
||||||
|
"rep": {
|
||||||
|
"x.com.samsung.da.modes": "Fix",
|
||||||
|
"x.com.samsung.da.supportedModes": [
|
||||||
|
"Fix",
|
||||||
|
"All",
|
||||||
|
"Up_And_Low"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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-21T07:20:30"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x.com.samsung.da.id": "1",
|
||||||
|
"x.com.samsung.da.description": "Alarm",
|
||||||
|
"x.com.samsung.da.alarmType": "Device",
|
||||||
|
"x.com.samsung.da.code": "FilterAlarm",
|
||||||
|
"x.com.samsung.da.triggeredTime": "2026-07-21T07:20:30",
|
||||||
|
"x.com.samsung.da.state": "Created"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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": "24.0",
|
||||||
|
"x.com.samsung.da.current": "27.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": "/temperature/current/0",
|
||||||
|
"rep": {
|
||||||
|
"range": [
|
||||||
|
16.0,
|
||||||
|
30.0
|
||||||
|
],
|
||||||
|
"units": "C",
|
||||||
|
"temperature": 27.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"href": "/temperature/desired/0",
|
||||||
|
"rep": {
|
||||||
|
"range": [
|
||||||
|
16.0,
|
||||||
|
30.0
|
||||||
|
],
|
||||||
|
"units": "C",
|
||||||
|
"temperature": 24.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"href": "/diagnosis/vs/0",
|
||||||
|
"rep": {
|
||||||
|
"x.com.samsung.da.diagnosisStart": "Ready"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"href": "/energy/consumption/vs/0",
|
||||||
|
"rep": {
|
||||||
|
"x.com.samsung.da.instantaneousPower": "0",
|
||||||
|
"x.com.samsung.da.cumulativePower": "1686632",
|
||||||
|
"x.com.samsung.da.cumulativeSavedPower": "0",
|
||||||
|
"x.com.samsung.da.cumulativeUnit": "Wh",
|
||||||
|
"x.com.samsung.da.instantaneousPowerUnit": "W"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"href": "/energy/consumption/0",
|
||||||
|
"rep": {
|
||||||
|
"power": 0.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"href": "/mode/vs/0",
|
||||||
|
"rep": {
|
||||||
|
"x.com.samsung.da.supportedModes": [
|
||||||
|
"Cool",
|
||||||
|
"Dry",
|
||||||
|
"Wind",
|
||||||
|
"Auto",
|
||||||
|
"Heat"
|
||||||
|
],
|
||||||
|
"x.com.samsung.da.modes": [
|
||||||
|
"Cool"
|
||||||
|
],
|
||||||
|
"x.com.samsung.da.options": [
|
||||||
|
"Sleep_0",
|
||||||
|
"ArtificialWorking_Off",
|
||||||
|
"ComfortAICooling_Off",
|
||||||
|
"AiTempChanged_Off",
|
||||||
|
"AiTemp_240",
|
||||||
|
"OutdoorTemp_79",
|
||||||
|
"CoolCapa_50",
|
||||||
|
"WarmCapa_60",
|
||||||
|
"Light_Off",
|
||||||
|
"Volume_100",
|
||||||
|
"OptionCode_53304",
|
||||||
|
"ExtendOptionCode_197256",
|
||||||
|
"RacInfo_None",
|
||||||
|
"UpdateAllow_NotAllowed",
|
||||||
|
"DurationOn_0",
|
||||||
|
"WelcomeCoolingState_Off"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"href": "/power/vs/0",
|
||||||
|
"rep": {
|
||||||
|
"x.com.samsung.da.power": "Off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"href": "/power/0",
|
||||||
|
"rep": {
|
||||||
|
"value": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"href": "/sensors/vs/0",
|
||||||
|
"rep": {
|
||||||
|
"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": [
|
||||||
|
"0"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x.com.samsung.da.id": "1",
|
||||||
|
"x.com.samsung.da.description": "Sensor for Odor",
|
||||||
|
"x.com.samsung.da.type": "Odor",
|
||||||
|
"x.com.samsung.da.value": [
|
||||||
|
"0"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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": "/information/vs/0",
|
||||||
|
"rep": {
|
||||||
|
"x.com.samsung.da.modelNum": "ARTIK051_PRAC_20K|10217841|60010532001411004200003000000000",
|
||||||
|
"x.com.samsung.da.description": "ARTIK051_PRAC_20K",
|
||||||
|
"x.com.samsung.da.serialNum": "**REDACTED**",
|
||||||
|
"x.com.samsung.da.otnDUID": "**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": "02181A230313",
|
||||||
|
"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": "22112400,FFFFFFFF",
|
||||||
|
"x.com.samsung.da.newVersionAvailable": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x.com.samsung.da.id": "2",
|
||||||
|
"x.com.samsung.da.description": "Version",
|
||||||
|
"x.com.samsung.da.type": "Outdoor",
|
||||||
|
"x.com.samsung.da.number": "0000,0000"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"href": "/file/information/vs/0",
|
||||||
|
"rep": {
|
||||||
|
"x.com.samsung.timeoffset": "+00:00",
|
||||||
|
"x.com.samsung.supprtedtype": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"href": "/configuration/vs/0",
|
||||||
|
"rep": {
|
||||||
|
"x.com.samsung.da.region": "0000000000",
|
||||||
|
"x.com.samsung.da.airconOptionList": [
|
||||||
|
"SingleCommand_1",
|
||||||
|
"DR",
|
||||||
|
"HOMECARE_WIZARD_V2",
|
||||||
|
"PRODUCT_GLOBAL",
|
||||||
|
"AI_RAC_GLOBAL_HEATPUMP_3.0",
|
||||||
|
"AI_3.0",
|
||||||
|
"Auto_To_AI",
|
||||||
|
"AI_Heat"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"href": "/humidity/0",
|
||||||
|
"rep": {
|
||||||
|
"humidity": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"href": "/humidity/vs/0",
|
||||||
|
"rep": {
|
||||||
|
"x.com.samsung.da.humidity": "0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"href": "/drlc/0",
|
||||||
|
"rep": {
|
||||||
|
"DRLevel": 0,
|
||||||
|
"start": "1970-01-01T00:00:00Z",
|
||||||
|
"duration": 0,
|
||||||
|
"override": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"href": "/drlc/vs/0",
|
||||||
|
"rep": {
|
||||||
|
"x.com.samsung.da.drlcLevel": "0",
|
||||||
|
"x.com.samsung.da.duration": "00:00:00",
|
||||||
|
"x.com.samsung.da.drlcStartTime": "1970-01-01T00:00:00Z",
|
||||||
|
"x.com.samsung.da.override": "Off",
|
||||||
|
"x.com.samsung.da.realSaving": "Off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"href": "/timezone/vs/0",
|
||||||
|
"rep": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"state_keys": [
|
||||||
|
"air_filter_status",
|
||||||
|
"air_filter_usage",
|
||||||
|
"air_purify",
|
||||||
|
"alarm_code",
|
||||||
|
"auto_clean",
|
||||||
|
"climate",
|
||||||
|
"diagnosis_status",
|
||||||
|
"energy_kwh",
|
||||||
|
"energy_saved_kwh",
|
||||||
|
"power_watts"
|
||||||
|
]
|
||||||
|
}
|
||||||
+2
@@ -8,6 +8,7 @@
|
|||||||
"delay_start_hours",
|
"delay_start_hours",
|
||||||
"diagnosis_status",
|
"diagnosis_status",
|
||||||
"energy_kwh",
|
"energy_kwh",
|
||||||
|
"energy_saved_kwh",
|
||||||
"finish_time",
|
"finish_time",
|
||||||
"firmware_update",
|
"firmware_update",
|
||||||
"heated_dry",
|
"heated_dry",
|
||||||
@@ -39,6 +40,7 @@
|
|||||||
"samsung_dishwasher_diagnosis_start",
|
"samsung_dishwasher_diagnosis_start",
|
||||||
"samsung_dishwasher_diagnosis_status",
|
"samsung_dishwasher_diagnosis_status",
|
||||||
"samsung_dishwasher_energy_kwh",
|
"samsung_dishwasher_energy_kwh",
|
||||||
|
"samsung_dishwasher_energy_saved_kwh",
|
||||||
"samsung_dishwasher_finish_time",
|
"samsung_dishwasher_finish_time",
|
||||||
"samsung_dishwasher_firmware_update",
|
"samsung_dishwasher_firmware_update",
|
||||||
"samsung_dishwasher_heated_dry",
|
"samsung_dishwasher_heated_dry",
|
||||||
|
|||||||
+8
@@ -16,6 +16,9 @@
|
|||||||
"door_cooler_open",
|
"door_cooler_open",
|
||||||
"door_freezer_open",
|
"door_freezer_open",
|
||||||
"energy_kwh",
|
"energy_kwh",
|
||||||
|
"energy_last_month_kwh",
|
||||||
|
"energy_saved_kwh",
|
||||||
|
"energy_this_month_kwh",
|
||||||
"filter_status",
|
"filter_status",
|
||||||
"filter_usage",
|
"filter_usage",
|
||||||
"firmware_update",
|
"firmware_update",
|
||||||
@@ -31,6 +34,7 @@
|
|||||||
"icemaker_two_type",
|
"icemaker_two_type",
|
||||||
"night_end",
|
"night_end",
|
||||||
"night_start",
|
"night_start",
|
||||||
|
"power_energy_kwh",
|
||||||
"power_watts",
|
"power_watts",
|
||||||
"rapid_freezing",
|
"rapid_freezing",
|
||||||
"rapid_fridge",
|
"rapid_fridge",
|
||||||
@@ -56,6 +60,9 @@
|
|||||||
"samsung_refrigerator_door_cooler_open",
|
"samsung_refrigerator_door_cooler_open",
|
||||||
"samsung_refrigerator_door_freezer_open",
|
"samsung_refrigerator_door_freezer_open",
|
||||||
"samsung_refrigerator_energy_kwh",
|
"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_status",
|
||||||
"samsung_refrigerator_filter_usage",
|
"samsung_refrigerator_filter_usage",
|
||||||
"samsung_refrigerator_firmware_update",
|
"samsung_refrigerator_firmware_update",
|
||||||
@@ -71,6 +78,7 @@
|
|||||||
"samsung_refrigerator_icemaker_two_type",
|
"samsung_refrigerator_icemaker_two_type",
|
||||||
"samsung_refrigerator_night_end",
|
"samsung_refrigerator_night_end",
|
||||||
"samsung_refrigerator_night_start",
|
"samsung_refrigerator_night_start",
|
||||||
|
"samsung_refrigerator_power_energy_kwh",
|
||||||
"samsung_refrigerator_power_watts",
|
"samsung_refrigerator_power_watts",
|
||||||
"samsung_refrigerator_rapid_freezing",
|
"samsung_refrigerator_rapid_freezing",
|
||||||
"samsung_refrigerator_rapid_fridge",
|
"samsung_refrigerator_rapid_fridge",
|
||||||
|
|||||||
@@ -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,34 @@
|
|||||||
|
{
|
||||||
|
"state_keys": [
|
||||||
|
"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_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
+1
@@ -11,6 +11,7 @@
|
|||||||
"drum_clean_cycles_remaining",
|
"drum_clean_cycles_remaining",
|
||||||
"drum_clean_last_cleaned",
|
"drum_clean_last_cleaned",
|
||||||
"energy_kwh",
|
"energy_kwh",
|
||||||
|
"energy_saved_kwh",
|
||||||
"finish_time",
|
"finish_time",
|
||||||
"firmware_update",
|
"firmware_update",
|
||||||
"job_beginning_status",
|
"job_beginning_status",
|
||||||
|
|||||||
+26
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"state_keys": [
|
||||||
|
"alarm_code",
|
||||||
|
"child_lock",
|
||||||
|
"cycle",
|
||||||
|
"cycle_active",
|
||||||
|
"delay_start_hours",
|
||||||
|
"detergent_low",
|
||||||
|
"diagnosis_status",
|
||||||
|
"drum_clean_cycles_remaining",
|
||||||
|
"dry_level",
|
||||||
|
"energy_kwh",
|
||||||
|
"finish_time",
|
||||||
|
"firmware_update",
|
||||||
|
"job_beginning_status",
|
||||||
|
"machine_state",
|
||||||
|
"power_switch",
|
||||||
|
"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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
+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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -2,10 +2,12 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
import threading
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
|
from custom_components.localthings import diagnostics as diagnostics_mod
|
||||||
from custom_components.localthings.const import DOMAIN
|
from custom_components.localthings.const import DOMAIN
|
||||||
from custom_components.localthings.diagnostics import async_get_config_entry_diagnostics
|
from custom_components.localthings.diagnostics import async_get_config_entry_diagnostics
|
||||||
from custom_components.localthings.registry.redact import REDACTED
|
from custom_components.localthings.registry.redact import REDACTED
|
||||||
@@ -49,3 +51,27 @@ async def test_diagnostics_include_observe_mode_fields(
|
|||||||
assert diag['observe_fallback_hrefs'] == []
|
assert diag['observe_fallback_hrefs'] == []
|
||||||
assert 'observe_last_mode_change' in diag
|
assert 'observe_last_mode_change' in diag
|
||||||
assert diag['observe_href_freshness_s'] == {}
|
assert diag['observe_href_freshness_s'] == {}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_dependency_version_read_off_the_event_loop(
|
||||||
|
hass: HomeAssistant, mock_entry, mock_coordinator_session, monkeypatch
|
||||||
|
) -> None:
|
||||||
|
"""importlib.metadata.version() does blocking disk I/O, so it must run in
|
||||||
|
the executor, not on the event loop (issue #9's logs flagged it)."""
|
||||||
|
await hass.config_entries.async_setup(mock_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
loop_thread_id = threading.get_ident() # this coroutine runs on the loop
|
||||||
|
seen: dict[str, int] = {}
|
||||||
|
real_pkg_version = diagnostics_mod.pkg_version
|
||||||
|
|
||||||
|
def _spy(name: str) -> str:
|
||||||
|
seen['thread_id'] = threading.get_ident()
|
||||||
|
return real_pkg_version(name)
|
||||||
|
|
||||||
|
monkeypatch.setattr(diagnostics_mod, 'pkg_version', _spy)
|
||||||
|
|
||||||
|
diag = await async_get_config_entry_diagnostics(hass, mock_entry)
|
||||||
|
|
||||||
|
assert diag['smartthings_local_version']
|
||||||
|
assert seen['thread_id'] != loop_thread_id
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
"""Tests for Samsung air-conditioner support (issue #17).
|
||||||
|
|
||||||
|
These stay HA-free like the rest of the suite: they exercise the registry,
|
||||||
|
discovery/flatten, and the CLIMATE capability's write contract. The composite
|
||||||
|
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.capabilities import airconditioner
|
||||||
|
from custom_components.localthings.registry.discovery import discover
|
||||||
|
from custom_components.localthings.registry.entities import ClimateDesc
|
||||||
|
|
||||||
|
from tests.conftest import _load_device
|
||||||
|
|
||||||
|
|
||||||
|
def _ac():
|
||||||
|
resources = _load_device('airconditioner')
|
||||||
|
info = resources['/information/vs/0']
|
||||||
|
reg = for_device_by_model(
|
||||||
|
info['x.com.samsung.da.modelNum'], info['x.com.samsung.da.description'],
|
||||||
|
)
|
||||||
|
return reg, resources
|
||||||
|
|
||||||
|
|
||||||
|
def _bound():
|
||||||
|
reg, resources = _ac()
|
||||||
|
return discover(resources, reg.capabilities, reg.pattern_capabilities), resources
|
||||||
|
|
||||||
|
|
||||||
|
def _state():
|
||||||
|
bound, resources = _bound()
|
||||||
|
return flatten(bound, resources)
|
||||||
|
|
||||||
|
|
||||||
|
def test_ac_model_resolves_to_airconditioner_registry():
|
||||||
|
reg, _ = _ac()
|
||||||
|
assert reg is not None and reg.name == 'airconditioner'
|
||||||
|
|
||||||
|
|
||||||
|
def test_no_unbound_hrefs():
|
||||||
|
"""Every resource in the issue #17 dump binds or is covered -- clears the
|
||||||
|
coverage-gap repair."""
|
||||||
|
reg, resources = _ac()
|
||||||
|
unbound = []
|
||||||
|
discover(resources, reg.capabilities, reg.pattern_capabilities, log=unbound.append)
|
||||||
|
assert unbound == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_climate_entity_is_bound():
|
||||||
|
"""The composite climate entity binds the primary /mode/vs/0 resource."""
|
||||||
|
bound, _ = _bound()
|
||||||
|
climate = [b for b in bound if isinstance(b.desc, ClimateDesc)]
|
||||||
|
assert len(climate) == 1
|
||||||
|
assert climate[0].href == '/mode/vs/0'
|
||||||
|
|
||||||
|
|
||||||
|
def test_expected_state_keys_present():
|
||||||
|
state = _state()
|
||||||
|
for key in ('climate', 'air_purify', 'auto_clean', 'air_filter_status',
|
||||||
|
'air_filter_usage', 'diagnosis_status', 'alarm_code', 'energy_kwh'):
|
||||||
|
assert key in state, key
|
||||||
|
|
||||||
|
|
||||||
|
def test_power_and_convenient_folded_into_climate():
|
||||||
|
"""On/off is the climate entity's HVACMode.OFF and convenient mode is its
|
||||||
|
preset_mode -- neither surfaces as a standalone switch/select."""
|
||||||
|
state = _state()
|
||||||
|
assert 'power_switch' not in state
|
||||||
|
assert 'convenient_mode' not in state
|
||||||
|
|
||||||
|
|
||||||
|
def test_air_filter_usage_is_percentage_of_capacity():
|
||||||
|
"""filterUsage is a raw count in the capacity unit (100 of 500), surfaced as
|
||||||
|
a percentage rather than the misleading raw value."""
|
||||||
|
assert _state()['air_filter_usage'] == 20
|
||||||
|
|
||||||
|
|
||||||
|
def test_climate_write_targets():
|
||||||
|
"""The CLIMATE write_fn maps each (kind, value) command to the right OCF
|
||||||
|
POST target and body. `value` is already the raw device code."""
|
||||||
|
write = airconditioner.CLIMATE.entities[0].write_fn
|
||||||
|
assert write(('power', True), {}) == (['power', '0'], {'value': True})
|
||||||
|
assert write(('power', False), {}) == (['power', '0'], {'value': False})
|
||||||
|
assert write(('mode', 'Heat'), {}) == (
|
||||||
|
['mode', 'vs', '0'], {'x.com.samsung.da.modes': ['Heat']})
|
||||||
|
assert write(('temperature', 23.6), {}) == (
|
||||||
|
['temperature', 'desired', '0'], {'temperature': 24})
|
||||||
|
assert write(('fan', '2'), {}) == (
|
||||||
|
['wind', 'strength', 'vs', '0'], {'x.com.samsung.da.modes': '2'})
|
||||||
|
assert write(('swing', 'All'), {}) == (
|
||||||
|
['wind', 'direction', 'vs', '0'], {'x.com.samsung.da.modes': 'All'})
|
||||||
|
assert write(('preset', 'Sleep'), {}) == (
|
||||||
|
['mode', 'convenient', 'vs', '0'], {'x.com.samsung.da.modes': 'Sleep'})
|
||||||
|
assert write(('bogus', 1), {}) is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_climate_consumed_hrefs_declared_as_coverage():
|
||||||
|
"""The climate-consumed and ambiguous hrefs are declared in the AC registry
|
||||||
|
(as no-entity coverage caps) so they don't leak as gaps -- but produce no
|
||||||
|
standalone entities."""
|
||||||
|
reg, _ = _ac()
|
||||||
|
for href in ('/power/0', '/power/vs/0', '/temperature/desired/0',
|
||||||
|
'/wind/strength/vs/0', '/mode/convenient/vs/0',
|
||||||
|
'/temperatures/vs/0', '/sensors/vs/0', '/humidity/0'):
|
||||||
|
caps = reg.capabilities.get(href)
|
||||||
|
assert caps, href
|
||||||
|
assert all(c.entities == () for c in caps), href
|
||||||
@@ -168,6 +168,38 @@ class TestForDeviceByModel:
|
|||||||
assert reg is not None
|
assert reg is not None
|
||||||
assert reg.name == 'refrigerator'
|
assert reg.name == 'refrigerator'
|
||||||
|
|
||||||
|
def test_airconditioner_via_prac_token(self):
|
||||||
|
"""Issue #17: a room AC (ARTIK051_PRAC_20K) reports no oneUiVersion and
|
||||||
|
an unrecognized consumer token ('20K'); it falls back to the '_PRAC_'
|
||||||
|
(Package Room Air Conditioner) token in modelNum."""
|
||||||
|
from custom_components.localthings.registry.by_type import for_device_by_model
|
||||||
|
reg = for_device_by_model(
|
||||||
|
'ARTIK051_PRAC_20K|10217841|60010532001411004200003000000000',
|
||||||
|
'ARTIK051_PRAC_20K',
|
||||||
|
)
|
||||||
|
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):
|
def test_unknown_model_returns_none(self):
|
||||||
from custom_components.localthings.registry.by_type import for_device_by_model
|
from custom_components.localthings.registry.by_type import for_device_by_model
|
||||||
reg = for_device_by_model('SOME-UNKNOWN-BOARD', 'SOME-UNKNOWN-BOARD')
|
reg = for_device_by_model('SOME-UNKNOWN-BOARD', 'SOME-UNKNOWN-BOARD')
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from custom_components.localthings.registry.entities import (
|
from custom_components.localthings.registry.entities import (
|
||||||
SensorDesc, BinarySensorDesc, SelectDesc, SwitchDesc, ButtonDesc,
|
SensorDesc, BinarySensorDesc, SelectDesc, SwitchDesc, ButtonDesc,
|
||||||
NumberDesc, PLATFORM_OF,
|
NumberDesc, TimeDesc, ClimateDesc, PLATFORM_OF,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -26,6 +26,8 @@ def test_platform_mapping_covers_all_subclasses():
|
|||||||
assert PLATFORM_OF[SwitchDesc] == 'switch'
|
assert PLATFORM_OF[SwitchDesc] == 'switch'
|
||||||
assert PLATFORM_OF[ButtonDesc] == 'button'
|
assert PLATFORM_OF[ButtonDesc] == 'button'
|
||||||
assert PLATFORM_OF[NumberDesc] == 'number'
|
assert PLATFORM_OF[NumberDesc] == 'number'
|
||||||
|
assert PLATFORM_OF[TimeDesc] == 'time'
|
||||||
|
assert PLATFORM_OF[ClimateDesc] == 'climate'
|
||||||
|
|
||||||
|
|
||||||
def test_select_carries_options_and_write_fn():
|
def test_select_carries_options_and_write_fn():
|
||||||
|
|||||||
@@ -102,3 +102,129 @@ class TestRefrigerationFallback:
|
|||||||
for key in ('rapid_fridge', 'rapid_freezing'):
|
for key in ('rapid_fridge', 'rapid_freezing'):
|
||||||
desc = next(e for e in fridge.REFRIGERATION_FALLBACK.entities if e.key == key)
|
desc = next(e for e in fridge.REFRIGERATION_FALLBACK.entities if e.key == key)
|
||||||
assert desc.exists_fn({}, {'/refrigeration/0': {}}) is True
|
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
|
||||||
|
|
||||||
|
|
||||||
|
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'
|
||||||
@@ -69,6 +69,90 @@ def test_registry_reproduces_golden_state_keys_for_dryer():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_registry_reproduces_golden_state_keys_for_airconditioner():
|
||||||
|
from tests.conftest import _load_device
|
||||||
|
resources = _load_device('airconditioner')
|
||||||
|
golden = json.loads((GOLDEN / 'airconditioner.json').read_text())
|
||||||
|
state_keys = _new_state_keys('airconditioner', 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_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_resources_from_batch_preferred_over_flat():
|
def test_resources_from_batch_preferred_over_flat():
|
||||||
from tests.conftest import _resources_from_dump
|
from tests.conftest import _resources_from_dump
|
||||||
dump = {
|
dump = {
|
||||||
|
|||||||
@@ -40,6 +40,22 @@ class TestWasherSettings:
|
|||||||
assert body == {'x.com.samsung.da.rinseCycles': '3'}
|
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:
|
class TestWasherCourse:
|
||||||
def test_href(self):
|
def test_href(self):
|
||||||
assert washer.WASHER_COURSE.href == '/course/vs/0'
|
assert washer.WASHER_COURSE.href == '/course/vs/0'
|
||||||
@@ -151,11 +167,32 @@ class TestDetergentSoftenerDosing:
|
|||||||
return next(e for e in washer.WASHER_COURSE.entities if e.key == key)
|
return next(e for e in washer.WASHER_COURSE.entities if e.key == key)
|
||||||
|
|
||||||
def test_quantity_and_hardness_read(self):
|
def test_quantity_and_hardness_read(self):
|
||||||
|
"""The device reports the level un-padded ('3'), but the options and
|
||||||
|
translation keys are zero-padded supported codes ('03'); rep_fn
|
||||||
|
normalizes to the supported code so the value is a valid option
|
||||||
|
(issue #9 -- otherwise HA renders the select 'unknown')."""
|
||||||
rep = {'x.com.samsung.da.options': _DOSING_OPTIONS}
|
rep = {'x.com.samsung.da.options': _DOSING_OPTIONS}
|
||||||
assert self._desc('detergent_quantity').rep_fn(rep) == '3'
|
assert self._desc('detergent_quantity').rep_fn(rep) == '03'
|
||||||
assert self._desc('detergent_water_hardness').rep_fn(rep) == '2'
|
assert self._desc('detergent_water_hardness').rep_fn(rep) == '02'
|
||||||
assert self._desc('softener_quantity').rep_fn(rep) == '3'
|
assert self._desc('softener_quantity').rep_fn(rep) == '03'
|
||||||
assert self._desc('softener_concentration').rep_fn(rep) == '2'
|
assert self._desc('softener_concentration').rep_fn(rep) == '02'
|
||||||
|
|
||||||
|
def test_current_value_is_a_valid_option_for_every_dosing_select(self):
|
||||||
|
"""The core regression: HA shows a select 'unknown' when current_option
|
||||||
|
is not in options. Each dosing select's value must be one of its own
|
||||||
|
options."""
|
||||||
|
rep = {'x.com.samsung.da.options': _DOSING_OPTIONS}
|
||||||
|
for key in ('detergent_quantity', 'detergent_water_hardness',
|
||||||
|
'softener_quantity', 'softener_concentration'):
|
||||||
|
desc = self._desc(key)
|
||||||
|
assert desc.rep_fn(rep) in desc.options(_DOSING_RESOURCES), key
|
||||||
|
|
||||||
|
def test_read_passes_through_when_no_supported_match(self):
|
||||||
|
"""A value with no matching supported code is returned as-is rather than
|
||||||
|
dropped, so an unexpected device stays visible instead of blank."""
|
||||||
|
rep = {'x.com.samsung.da.options': ['DetergentLevelCtrl_7',
|
||||||
|
'SupportedDetergentLevelCtrl_00010203']}
|
||||||
|
assert self._desc('detergent_quantity').rep_fn(rep) == '7'
|
||||||
|
|
||||||
def test_translation_keys(self):
|
def test_translation_keys(self):
|
||||||
"""detergent_quantity and softener_quantity share one translation_key
|
"""detergent_quantity and softener_quantity share one translation_key
|
||||||
@@ -181,17 +218,21 @@ class TestDetergentSoftenerDosing:
|
|||||||
assert desc.exists_fn({}, _DOSING_RESOURCES) is True
|
assert desc.exists_fn({}, _DOSING_RESOURCES) is True
|
||||||
|
|
||||||
def test_quantity_write(self):
|
def test_quantity_write(self):
|
||||||
|
"""The UI selects a padded supported code ('01'); the write posts the
|
||||||
|
un-padded device code ('1'), mirroring how the device reports it."""
|
||||||
rep = {'x.com.samsung.da.options': list(_DOSING_OPTIONS)}
|
rep = {'x.com.samsung.da.options': list(_DOSING_OPTIONS)}
|
||||||
path, body = self._desc('detergent_quantity').write_fn('01', rep)
|
path, body = self._desc('detergent_quantity').write_fn('01', rep)
|
||||||
assert path == ['course', 'vs', '0']
|
assert path == ['course', 'vs', '0']
|
||||||
assert 'DetergentLevelCtrl_01' in body['x.com.samsung.da.options']
|
assert 'DetergentLevelCtrl_1' in body['x.com.samsung.da.options']
|
||||||
assert 'DetergentLevelCtrl_3' not in body['x.com.samsung.da.options']
|
assert 'DetergentLevelCtrl_3' not in body['x.com.samsung.da.options']
|
||||||
|
# untouched siblings survive the read-modify-write
|
||||||
|
assert 'SoftenerLevelCtrl_3' in body['x.com.samsung.da.options']
|
||||||
|
|
||||||
def test_hardness_write(self):
|
def test_hardness_write(self):
|
||||||
rep = {'x.com.samsung.da.options': list(_DOSING_OPTIONS)}
|
rep = {'x.com.samsung.da.options': list(_DOSING_OPTIONS)}
|
||||||
path, body = self._desc('softener_concentration').write_fn('03', rep)
|
path, body = self._desc('softener_concentration').write_fn('03', rep)
|
||||||
assert path == ['course', 'vs', '0']
|
assert path == ['course', 'vs', '0']
|
||||||
assert 'SoftenerLevel2Ctrl_03' in body['x.com.samsung.da.options']
|
assert 'SoftenerLevel2Ctrl_3' in body['x.com.samsung.da.options']
|
||||||
|
|
||||||
def test_low_reservoir_off_when_alarm_off(self):
|
def test_low_reservoir_off_when_alarm_off(self):
|
||||||
rep = {'x.com.samsung.da.options': _DOSING_OPTIONS}
|
rep = {'x.com.samsung.da.options': _DOSING_OPTIONS}
|
||||||
@@ -208,3 +249,46 @@ class TestDetergentSoftenerDosing:
|
|||||||
assert self._desc('detergent_low').exists_fn({'x.com.samsung.da.options': []}, {}) is False
|
assert self._desc('detergent_low').exists_fn({'x.com.samsung.da.options': []}, {}) is False
|
||||||
rep = {'x.com.samsung.da.options': _DOSING_OPTIONS}
|
rep = {'x.com.samsung.da.options': _DOSING_OPTIONS}
|
||||||
assert self._desc('detergent_low').exists_fn(rep, {}) is True
|
assert self._desc('detergent_low').exists_fn(rep, {}) is True
|
||||||
|
|
||||||
|
|
||||||
|
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