Merge pull request #323 from mbillow/claude/issue-triage-qgveie
Device support: A/C, fridge, cooktop, oven coverage gaps (issues #319, #318, #314, #300, #288)
This commit is contained in:
@@ -17,7 +17,7 @@ uses unconditionally.
|
||||
Reuses dishwasher.DIAGNOSIS for /diagnosis/vs/0.
|
||||
"""
|
||||
|
||||
from ..capabilities import airconditioner, common, dishwasher, ignored
|
||||
from ..capabilities import air_purifier, airconditioner, common, dishwasher, ignored
|
||||
from ._base import DeviceRegistry, _build
|
||||
|
||||
REGISTRY = DeviceRegistry(
|
||||
@@ -46,6 +46,28 @@ REGISTRY = DeviceRegistry(
|
||||
airconditioner.CURRENT_TEMPERATURE,
|
||||
airconditioner.CURRENT_TEMPERATURE_VS,
|
||||
airconditioner.HUMIDITY,
|
||||
# TP1X_DA-AC-FAC-class (issue #319): shares its /display/vs/0,
|
||||
# /settings/sound/output/vs/0 and /settings/sound/volume/vs/0
|
||||
# shape with the sibling TP1X_DA-AC-AIR board in air_purifier.py.
|
||||
air_purifier.DISPLAY,
|
||||
air_purifier.SOUND_OUTPUT,
|
||||
air_purifier.SOUND_VOLUME,
|
||||
airconditioner.SOUND_MODE,
|
||||
airconditioner.ABSENCE_CLEAN,
|
||||
airconditioner.MDS_ABSENCE_CLEAN,
|
||||
airconditioner.ENERGY_SAVING,
|
||||
airconditioner.EDGE_LIGHTING,
|
||||
airconditioner.LIGHT_STATEFUL,
|
||||
# System Fresh Air Ventilator (PR #316, ACA-KR-TP2-21-AN9000):
|
||||
# WINDFREE/WINDSLEEP are this device's own hrefs; HEPA_FILTER/
|
||||
# DEVICE_ACTIVE reuse air_purifier.py's identical shapes.
|
||||
# AIR_LEVEL_CHECK is not this-device-specific -- see its
|
||||
# removal from _AC_IGNORED above.
|
||||
airconditioner.WINDFREE,
|
||||
airconditioner.WINDSLEEP,
|
||||
air_purifier.HEPA_FILTER,
|
||||
air_purifier.DEVICE_ACTIVE,
|
||||
air_purifier.AIR_LEVEL_CHECK,
|
||||
*airconditioner.COVERAGE,
|
||||
]
|
||||
),
|
||||
|
||||
@@ -22,6 +22,12 @@ REGISTRY = DeviceRegistry(
|
||||
cooktop.COOKTOP_CONNECTED,
|
||||
cooktop.PAIRED_HOOD_STATUS,
|
||||
common.FIRMWARE_UPDATE,
|
||||
# issue #314: /alarms/vs/0 and /kidslock/vs/0 are the same
|
||||
# generic shapes common.UNIVERSAL already models elsewhere --
|
||||
# picked individually rather than pulling in all of UNIVERSAL,
|
||||
# matching this registry's existing hand-picked-common style.
|
||||
common.ALARMS,
|
||||
common.KIDS_LOCK_VS_FALLBACK,
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Oven device registry."""
|
||||
|
||||
from ..capabilities import common, ignored, oven
|
||||
from ..capabilities import common, dishwasher, ignored, oven
|
||||
from ._base import DeviceRegistry, _build
|
||||
|
||||
REGISTRY = DeviceRegistry(
|
||||
@@ -18,6 +18,9 @@ REGISTRY = DeviceRegistry(
|
||||
oven.OVEN_CONNECTED,
|
||||
oven.OVEN_SPEC,
|
||||
oven.OVEN_RECIPE_COOK,
|
||||
# issue #300: /diagnosis/vs/0 is the same diagnosisStart shape
|
||||
# dishwasher.py and airconditioner.py already reuse.
|
||||
dishwasher.DIAGNOSIS,
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
@@ -13,6 +13,7 @@ REGISTRY = DeviceRegistry(
|
||||
fridge.STATUS_LOCK,
|
||||
fridge.DOOR_ALERT,
|
||||
common.WATER_FILTER,
|
||||
fridge.AIR_FILTER,
|
||||
dishwasher.DIAGNOSIS,
|
||||
fridge.ICEMAKER_NIGHTTIME,
|
||||
fridge.FLEX_ZONE,
|
||||
|
||||
@@ -441,6 +441,10 @@ SOUND_VOLUME = Capability(
|
||||
field="level",
|
||||
icon="mdi:volume-medium",
|
||||
entity_category="config",
|
||||
# Some boards (issue #319's AC) report minLevel/resolution but no
|
||||
# maxLevel -- native_max_fn would silently collapse to 0, giving a
|
||||
# slider with no real range instead of no entity at all.
|
||||
exists_fn=lambda rep, resources: "maxLevel" in rep,
|
||||
native_min_fn=lambda rep: int_or_none(rep.get("minLevel")) or 0,
|
||||
native_max_fn=lambda rep: int_or_none(rep.get("maxLevel")) or 0,
|
||||
step_fn=lambda rep: int_or_none(rep.get("resolution")) or 1,
|
||||
|
||||
@@ -206,6 +206,28 @@ def _mode_options(rep):
|
||||
return opts if isinstance(opts, (list, tuple)) else ()
|
||||
|
||||
|
||||
# Samsung's "System Fresh Air Ventilator" (PR #316, model
|
||||
# ACA-KR-TP2-21-AN9000, vid DA-AC-DIFFUSER-01001) self-reports oic.d.
|
||||
# airconditioner and routes through this same CLIMATE capability, but its
|
||||
# /mode/vs/0 supportedModes are Purification/Ventilation/SmartVentilation --
|
||||
# none of which climate.py's HVAC-mode table knows, so hvac_mode collapses
|
||||
# to a single stuck value with no way to tell the three apart. Gated to
|
||||
# devices whose *entire* supported-mode set is this vocabulary, so it can't
|
||||
# false-positive on a real AC's Cool/Heat/Dry list.
|
||||
_VENTILATION_MODE_VALUES = frozenset(("Purification", "Ventilation", "SmartVentilation"))
|
||||
|
||||
|
||||
def _is_ventilation_mode_device(rep, resources):
|
||||
supported = rep.get("x.com.samsung.da.supportedModes")
|
||||
if not isinstance(supported, (list, tuple)) or not supported:
|
||||
return False
|
||||
return set(supported) <= _VENTILATION_MODE_VALUES
|
||||
|
||||
|
||||
def _ventilation_mode_write(payload, rep, href=None):
|
||||
return ["mode", "vs", "0"], {"x.com.samsung.da.modes": [payload]}
|
||||
|
||||
|
||||
def _has_display_light_option(rep, resources):
|
||||
"""True when the panel light lives in /mode/vs/0's `Light_*` option
|
||||
token rather than a dedicated /light/vs/0 switch -- the two encodings
|
||||
@@ -532,6 +554,17 @@ CLIMATE = Capability(
|
||||
rep_fn=_first_mode,
|
||||
write_fn=_climate_write,
|
||||
),
|
||||
# Purification/Ventilation/SmartVentilation mode select (PR #316) --
|
||||
# _is_ventilation_mode_device gates this to devices using that
|
||||
# vocabulary exclusively, so a real AC's climate card is unaffected.
|
||||
SelectDesc(
|
||||
key="ventilation_mode",
|
||||
rep_fn=_first_mode,
|
||||
exists_fn=_is_ventilation_mode_device,
|
||||
options_field="x.com.samsung.da.supportedModes",
|
||||
icon="mdi:air-filter",
|
||||
write_fn=_ventilation_mode_write,
|
||||
),
|
||||
# Panel light switch for boards that encode it in /mode/vs/0's options
|
||||
# instead of a dedicated /light/vs/0 (see _has_display_light_option).
|
||||
# Shares the switch.display_light translation key with DISPLAY_LIGHT
|
||||
@@ -1167,6 +1200,253 @@ HUMIDITY = Capability(
|
||||
),
|
||||
)
|
||||
|
||||
# TP1X_DA-AC-FAC-class additions (issue #319): most of these hrefs are the
|
||||
# same shapes air_purifier.py already models on the sibling TP1X_DA-AC-AIR
|
||||
# board (DISPLAY/SOUND_OUTPUT/SOUND_VOLUME, reused directly in the
|
||||
# registry); SOUND_MODE and the two below are genuinely new.
|
||||
|
||||
SOUND_MODE = Capability(
|
||||
href="/settings/sound/mode/vs/0",
|
||||
poll_tier="cold",
|
||||
entities=(
|
||||
# Values seen (mute/tone/voice) are exactly laundry.SOUND_MODE's
|
||||
# vocabulary, so this shares that catalog entry -- but reads the
|
||||
# live supportedModes field rather than laundry's static tuple,
|
||||
# since this resource carries one (issue #319).
|
||||
#
|
||||
# exists_fn is required, not optional here: this board's rep never
|
||||
# reports a live 'mode' value ({"supportedModes": [...]} only), and
|
||||
# entity.py's default field-presence gate would otherwise keep the
|
||||
# select from ever registering -- adapter.flatten() (what the
|
||||
# golden/tests read) has no such gate, so it would look bound while
|
||||
# silently absent from HA. Register on supportedModes' presence
|
||||
# instead; current_option reads unknown until the device reports
|
||||
# 'mode' live.
|
||||
SelectDesc(
|
||||
key="sound_mode",
|
||||
field="mode",
|
||||
icon="mdi:volume-high",
|
||||
entity_category="config",
|
||||
options_field="supportedModes",
|
||||
exists_fn=lambda rep, resources: bool(rep.get("supportedModes")),
|
||||
write_fn=lambda p, rep, href=None: (
|
||||
["settings", "sound", "mode", "vs", "0"],
|
||||
{"mode": p},
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
# Absence-detection auto air clean (issue #319) -- a plain On/Off toggle,
|
||||
# sibling feature to ABSENCE_POWER_SAVING above but on its own href.
|
||||
ABSENCE_CLEAN = Capability(
|
||||
href="/csi/absenceclean/vs/0",
|
||||
poll_tier="cold",
|
||||
entities=(
|
||||
SwitchDesc(
|
||||
key="absence_clean",
|
||||
field="mode",
|
||||
icon="mdi:broom",
|
||||
entity_category="config",
|
||||
value_fn=lambda v: v == "On",
|
||||
write_fn=lambda p, rep, href=None: (
|
||||
["csi", "absenceclean", "vs", "0"],
|
||||
{"mode": "On" if p == "On" else "Off"},
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
# The CAC-class board (issue #191) reports the identical {mode,
|
||||
# supportedModes: [On, Off]} shape under /mds/absenceclean/vs/0 instead --
|
||||
# confirmed against that board's own fixture, not guessed. Shares
|
||||
# ABSENCE_CLEAN's key/translation: no dump has ever reported both hrefs
|
||||
# together, so there's nothing for the two to collide over in
|
||||
# adapter.flatten().
|
||||
MDS_ABSENCE_CLEAN = Capability(
|
||||
href="/mds/absenceclean/vs/0",
|
||||
poll_tier="cold",
|
||||
entities=(
|
||||
SwitchDesc(
|
||||
key="absence_clean",
|
||||
field="mode",
|
||||
icon="mdi:broom",
|
||||
entity_category="config",
|
||||
value_fn=lambda v: v == "On",
|
||||
write_fn=lambda p, rep, href=None: (
|
||||
["mds", "absenceclean", "vs", "0"],
|
||||
{"mode": "On" if p == "On" else "Off"},
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
# Energy-saving schedule (issue #319). `mode` is a device-chosen preset
|
||||
# (e.g. Cooling_60/Off_180) with no confirmed unit for the trailing number
|
||||
# (minutes seen elsewhere on this board are unprefixed ints, not
|
||||
# underscore-suffixed) -- exposed as a select over the live options rather
|
||||
# than translating labels we can't confirm. `state`/`operatingStatus` stay
|
||||
# bare diagnostic passthroughs for the same reason.
|
||||
ENERGY_SAVING = Capability(
|
||||
href="/csi/energysaving/vs/0",
|
||||
poll_tier="cold",
|
||||
entities=(
|
||||
SelectDesc(
|
||||
key="energy_saving_mode",
|
||||
field="mode",
|
||||
icon="mdi:leaf",
|
||||
entity_category="config",
|
||||
options_field="supportedModes",
|
||||
write_fn=lambda p, rep, href=None: (
|
||||
["csi", "energysaving", "vs", "0"],
|
||||
{"mode": p},
|
||||
),
|
||||
),
|
||||
SensorDesc(
|
||||
key="energy_saving_state",
|
||||
field="state",
|
||||
icon="mdi:leaf",
|
||||
entity_category="diagnostic",
|
||||
),
|
||||
SensorDesc(
|
||||
key="energy_saving_operating_status",
|
||||
field="operatingStatus",
|
||||
icon="mdi:leaf",
|
||||
entity_category="diagnostic",
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
# TP1X_DA-AC-CAC-01001-class additions (issue #288, six System A/C cassette
|
||||
# units on the same board test_airconditioner_cac.py's coverage-gap test
|
||||
# documents). `convenientMode`/`operatingOption` stay unexposed -- present
|
||||
# on every dump seen but no evidence of what either actually controls.
|
||||
EDGE_LIGHTING = Capability(
|
||||
href="/edgelighting/vs/0",
|
||||
poll_tier="cold",
|
||||
entities=(
|
||||
SwitchDesc(
|
||||
key="edge_lighting",
|
||||
field="status",
|
||||
icon="mdi:led-strip",
|
||||
entity_category="config",
|
||||
value_fn=lambda v: v == "On",
|
||||
write_fn=lambda p, rep, href=None: (
|
||||
["edgelighting", "vs", "0"],
|
||||
{"status": "On" if p == "On" else "Off"},
|
||||
),
|
||||
),
|
||||
SelectDesc(
|
||||
key="edge_lighting_mode",
|
||||
field="mode",
|
||||
icon="mdi:led-strip-variant",
|
||||
entity_category="config",
|
||||
options_field="modeSupportedList",
|
||||
write_fn=lambda p, rep, href=None: (
|
||||
["edgelighting", "vs", "0"],
|
||||
{"mode": p},
|
||||
),
|
||||
),
|
||||
# Color temperature in Kelvin (3000K/4000K/6500K), not a hue -- a
|
||||
# select over the live-reported codes rather than a light color_temp
|
||||
# entity, consistent with this project's other Kelvin-coded selects.
|
||||
SelectDesc(
|
||||
key="edge_lighting_color",
|
||||
field="colorOption",
|
||||
icon="mdi:palette",
|
||||
entity_category="config",
|
||||
options_field="colorSupportedList",
|
||||
write_fn=lambda p, rep, href=None: (
|
||||
["edgelighting", "vs", "0"],
|
||||
{"colorOption": p},
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
# Second, distinct light resource on this board generation -- an
|
||||
# always-on-style indicator light with its own status/mode, not to be
|
||||
# confused with EDGE_LIGHTING (a different href/rep entirely) or
|
||||
# DISPLAY_LIGHT (/light/vs/0's ambient mood light).
|
||||
LIGHT_STATEFUL = Capability(
|
||||
href="/light/stateful/vs/0",
|
||||
poll_tier="cold",
|
||||
entities=(
|
||||
SwitchDesc(
|
||||
key="indicator_light",
|
||||
field="status",
|
||||
icon="mdi:led-on",
|
||||
entity_category="config",
|
||||
value_fn=lambda v: v == "On",
|
||||
write_fn=lambda p, rep, href=None: (
|
||||
["light", "stateful", "vs", "0"],
|
||||
{"status": "On" if p == "On" else "Off"},
|
||||
),
|
||||
),
|
||||
SelectDesc(
|
||||
key="indicator_light_mode",
|
||||
field="mode",
|
||||
icon="mdi:led-variant-on",
|
||||
entity_category="config",
|
||||
options_field="supportedModes",
|
||||
write_fn=lambda p, rep, href=None: (
|
||||
["light", "stateful", "vs", "0"],
|
||||
{"mode": p},
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
# Wind-Free / Wind-Sleep mode toggles (PR #316, ACA-KR-TP2-21-AN9000). Each
|
||||
# on its own dedicated href, so unlike ventilation_mode above these need no
|
||||
# device gating -- absent on every other family's dump. Write contract
|
||||
# extrapolated from this file's other plain On/Off options-array fields
|
||||
# (AIR_PURIFY, AUTO_CLEAN); not confirmed live.
|
||||
#
|
||||
# NOT the same WindFree already modeled elsewhere: on regular AC boards,
|
||||
# WindFree is a `Comode_Nano` token inside /mode/vs/0's options[], surfaced
|
||||
# as a climate preset (climate.py's _LEGACY_PRESET_CODES/preset_mode) with
|
||||
# real coupling to hvac_mode (disabled in Heat/AIComfort/Auto, timing rules
|
||||
# on legacy boards). This device's windfree/windsleep are bare booleans on
|
||||
# their own hrefs with no such coupling evidenced -- same feature name,
|
||||
# different wire mechanism, so plain switches rather than folding into
|
||||
# climate.py's preset machinery.
|
||||
WINDFREE = Capability(
|
||||
href="/modeoption/windfree/vs/0",
|
||||
poll_tier="warm",
|
||||
entities=(
|
||||
SwitchDesc(
|
||||
key="windfree",
|
||||
field="x.com.samsung.da.windfree",
|
||||
icon="mdi:leaf",
|
||||
entity_category="config",
|
||||
value_fn=lambda v: v == "On",
|
||||
write_fn=lambda p, rep, href=None: (
|
||||
["modeoption", "windfree", "vs", "0"],
|
||||
{"x.com.samsung.da.windfree": "On" if p == "On" else "Off"},
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
WINDSLEEP = Capability(
|
||||
href="/modeoption/windsleep/vs/0",
|
||||
poll_tier="warm",
|
||||
entities=(
|
||||
SwitchDesc(
|
||||
key="windsleep",
|
||||
field="x.com.samsung.da.windsleep",
|
||||
icon="mdi:sleep",
|
||||
entity_category="config",
|
||||
value_fn=lambda v: v == "On",
|
||||
write_fn=lambda p, rep, href=None: (
|
||||
["modeoption", "windsleep", "vs", "0"],
|
||||
{"x.com.samsung.da.windsleep": "On" if p == "On" else "Off"},
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
# /sensors/vs/0 items[] carry live air-quality readings. CleanLevel is
|
||||
# corroborated as numeric by a top-level x.com.samsung.da.cleanLevel scalar,
|
||||
# so it's a measurement; the others stay string diagnostics (see
|
||||
@@ -1203,6 +1483,27 @@ AIR_QUALITY = Capability(
|
||||
("super_fine_dust", "mdi:weather-fog", "SuperFineDust"),
|
||||
)
|
||||
),
|
||||
# CO2 (PR #316, ACA-KR-TP2-21-AN9000) -- a type this file's other AC
|
||||
# families don't report. Same field/shape air_monitor.SENSORS
|
||||
# already models with device_class='carbon_dioxide'/unit='ppm', so
|
||||
# this matches that descriptor rather than guessing fresh -- unlike
|
||||
# the pm10/pm25/pm1 mapping air_monitor.py's own docstring
|
||||
# deliberately rejects for the three dust-type keys above (Samsung's
|
||||
# two-tier PM10/PM2.5 convention doesn't confirm where a third tier
|
||||
# or PM1 fits), ppm for a field literally named CO2 isn't a guess of
|
||||
# that kind.
|
||||
SensorDesc(
|
||||
key="co2",
|
||||
field="x.com.samsung.da.items",
|
||||
icon="mdi:molecule-co2",
|
||||
entity_category="diagnostic",
|
||||
device_class="carbon_dioxide",
|
||||
state_class="measurement",
|
||||
unit="ppm",
|
||||
exists_fn=_has_sensor_type("CO2"),
|
||||
enabled_default=False,
|
||||
value_fn=lambda items: _int(_sensor_item_value(items, "CO2")),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -1222,7 +1523,12 @@ _AC_IGNORED = [
|
||||
# state or documented write contract. /option/muteonce/vs/0 and
|
||||
# /selfcheck/vs/0 are deliberately NOT here -- see MUTE_ONCE above and
|
||||
# common.SELF_CHECK, both of which have a confirmed, modelable contract.
|
||||
"/airlevelcheck/vs/0", # periodic air-quality sensing scheduler plumbing
|
||||
# /airlevelcheck/vs/0 is deliberately NOT here either (PR #316):
|
||||
# despite this list's old description of it as "scheduler plumbing",
|
||||
# both the CAC and TP1X_DA_AC_RAC_01011 fixtures already carry real,
|
||||
# populated periodicSensingActivationState/autoExeState values here --
|
||||
# the AI-Purify feature air_purifier.AIR_LEVEL_CHECK already models,
|
||||
# reused below rather than reinvented.
|
||||
"/aisleep/vs/0", # AI-sleep feedback state (no actionable control)
|
||||
"/availablecontrolsets/vs/0", # opaque hex-encoded control-set bitmap
|
||||
"/da/softreset/vs/0", # soft-reset trigger plumbing
|
||||
@@ -1250,6 +1556,24 @@ _AC_IGNORED = [
|
||||
# registry.subdevices.enumerate_subdevices, hence the entry here rather
|
||||
# than a coverage gap.
|
||||
"/multidevice/vs/0",
|
||||
# TP1X_DA-AC-FAC-class-only (issue #319) -- scoped here rather than
|
||||
# promoted to the global ignore list since it'd collide with families
|
||||
# that do bind some of these. Only /dnd/autosleep/vs/0 has a precedent
|
||||
# (air_purifier.COVERAGE ignores the same href for the same reason);
|
||||
# the rest are new, each with its own reason below.
|
||||
"/dnd/autosleep/vs/0", # every field its inert default; needs a schedule editor
|
||||
"/outdoorsharing/vs/0", # empty on this dump -- outdoor-unit sharing plumbing
|
||||
"/lifestyle/survey/vs/0", # {list: [""]} placeholder, nothing to expose
|
||||
# supportedVoices carries opaque numeric voice-pack IDs ("100"/"101")
|
||||
# with no live current-selection field and, unlike SOUND_MODE's
|
||||
# self-descriptive mute/tone/voice codes, no confirmed human-readable
|
||||
# meaning to expose them under -- don't guess.
|
||||
"/settings/sound/voice/vs/0",
|
||||
# rssi/wifiFrequency (network housekeeping); lastEnergySavingTime and
|
||||
# cleaningStartTime are inert '1900-01-00' placeholders on this dump;
|
||||
# absenceInfo is an unconfirmed 48-slot P/A history blob with no
|
||||
# documented meaning -- don't guess what it encodes.
|
||||
"/csi/information/vs/0",
|
||||
]
|
||||
|
||||
# Built as bare no-entity caps; folded into the AC registry (not global).
|
||||
|
||||
@@ -25,7 +25,7 @@ from ..entities import (
|
||||
SwitchDesc,
|
||||
TimeDesc,
|
||||
)
|
||||
from .common import normalize_temp_unit
|
||||
from .common import int_or_none, normalize_temp_unit
|
||||
|
||||
# Display names for the beverage zone, flex zone, ice type, and
|
||||
# ice-making-status enums below live in translations/en.json, keyed by the
|
||||
@@ -266,6 +266,39 @@ DOOR_ALERT = Capability(
|
||||
)
|
||||
|
||||
|
||||
# Internal deodorizing filter (issue #318, TP1X_REF_21K). Same
|
||||
# filterUsage/filterStatus field pair as common.WATER_FILTER, but
|
||||
# filterUsage here is already a 0-100 percentage with no filterCapacity to
|
||||
# divide by (confirmed by filterStatus=="wash" at filterUsage=="100") --
|
||||
# 'air_'-prefixed keys so a fridge with both a water and an air filter gets
|
||||
# two distinct entities rather than a unique_id collision.
|
||||
AIR_FILTER = Capability(
|
||||
href="/filter/airdustfilter/vs/0",
|
||||
poll_tier="cold",
|
||||
entities=(
|
||||
SensorDesc(
|
||||
key="air_filter_usage",
|
||||
field="x.com.samsung.da.filterUsage",
|
||||
unit="%",
|
||||
state_class="measurement",
|
||||
icon="mdi:air-filter",
|
||||
entity_category="diagnostic",
|
||||
value_fn=int_or_none,
|
||||
),
|
||||
SensorDesc(
|
||||
key="air_filter_status",
|
||||
field="x.com.samsung.da.filterStatus",
|
||||
device_class="enum",
|
||||
options=("normal", "wash", "replace"),
|
||||
translation_key="filter_status",
|
||||
icon="mdi:air-filter",
|
||||
entity_category="diagnostic",
|
||||
value_fn=lambda v: v.lower() if isinstance(v, str) else v,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _status_lock_write(field):
|
||||
return lambda p, rep, href=None: (
|
||||
["status", "lock", "vs", "0"],
|
||||
|
||||
@@ -401,10 +401,17 @@ OVEN_MODE = Capability(
|
||||
value_fn=lambda v: v[0] if v else None,
|
||||
write_fn=_oven_mode_write,
|
||||
),
|
||||
# No exists_fn on the NV7000BS-class board this was proven against
|
||||
# (UpperLamp_ is always in its options[]) -- but issue #300's
|
||||
# steam-oven-class WALLOVEN board's options[] has no UpperLamp_
|
||||
# token at all, so this was a phantom, always-off, write-does-
|
||||
# nothing switch there. Same fastpreheat/NaturalSteam-class gap
|
||||
# issue #183 already fixed on the other switches below.
|
||||
SwitchDesc(
|
||||
key="lamp",
|
||||
field="x.com.samsung.da.options",
|
||||
icon="mdi:track-light",
|
||||
exists_fn=_has_option("UpperLamp"),
|
||||
value_fn=lambda opts: _option_value(opts, "UpperLamp") == "On",
|
||||
write_fn=_option_switch_write("UpperLamp"),
|
||||
),
|
||||
|
||||
@@ -538,6 +538,9 @@
|
||||
"mute": "Ztlumeno"
|
||||
}
|
||||
},
|
||||
"energy_saving_mode": {
|
||||
"name": "Režim úspory energie"
|
||||
},
|
||||
"air_purifier_sound_mode": {
|
||||
"name": "Zvukový režim",
|
||||
"state": {
|
||||
@@ -665,6 +668,38 @@
|
||||
},
|
||||
"freezer_temperature_setpoint": {
|
||||
"name": "Teplota mrazicí zóny"
|
||||
},
|
||||
"edge_lighting_mode": {
|
||||
"name": "Režim okrajového osvětlení",
|
||||
"state": {
|
||||
"smart": "Chytrý",
|
||||
"high": "Vysoký",
|
||||
"low": "Nízký"
|
||||
}
|
||||
},
|
||||
"edge_lighting_color": {
|
||||
"name": "Barva okrajového osvětlení",
|
||||
"state": {
|
||||
"3000k": "3000 K",
|
||||
"4000k": "4000 K",
|
||||
"6500k": "6500 K"
|
||||
}
|
||||
},
|
||||
"indicator_light_mode": {
|
||||
"name": "Režim kontrolky",
|
||||
"state": {
|
||||
"smart": "Chytrý",
|
||||
"high": "Vysoký",
|
||||
"low": "Nízký"
|
||||
}
|
||||
},
|
||||
"ventilation_mode": {
|
||||
"name": "Režim",
|
||||
"state": {
|
||||
"purification": "Čištění",
|
||||
"ventilation": "Větrání",
|
||||
"smartventilation": "Chytré větrání"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
@@ -787,6 +822,12 @@
|
||||
"indirect": "Nepřímý"
|
||||
}
|
||||
},
|
||||
"energy_saving_state": {
|
||||
"name": "Stav úspory energie"
|
||||
},
|
||||
"energy_saving_operating_status": {
|
||||
"name": "Provozní stav úspory energie"
|
||||
},
|
||||
"current_temp_c": {
|
||||
"name": "Teplota"
|
||||
},
|
||||
@@ -1040,6 +1081,9 @@
|
||||
"absence_power_saving_active": {
|
||||
"name": "Úspora energie při nepřítomnosti aktivní"
|
||||
},
|
||||
"absence_clean": {
|
||||
"name": "Čištění při nepřítomnosti"
|
||||
},
|
||||
"motion_detect_wind_active": {
|
||||
"name": "Vyhýbání se proudu vzduchu při pohybu aktivní"
|
||||
},
|
||||
@@ -1204,6 +1248,18 @@
|
||||
},
|
||||
"ventilation_alarm": {
|
||||
"name": "Alarm větrání"
|
||||
},
|
||||
"edge_lighting": {
|
||||
"name": "Okrajové osvětlení"
|
||||
},
|
||||
"indicator_light": {
|
||||
"name": "Kontrolka"
|
||||
},
|
||||
"windfree": {
|
||||
"name": "Režim Wind-Free"
|
||||
},
|
||||
"windsleep": {
|
||||
"name": "Noční režim"
|
||||
}
|
||||
},
|
||||
"time": {
|
||||
|
||||
@@ -538,6 +538,9 @@
|
||||
"mute": "Mute"
|
||||
}
|
||||
},
|
||||
"energy_saving_mode": {
|
||||
"name": "Energy saving mode"
|
||||
},
|
||||
"air_purifier_sound_mode": {
|
||||
"name": "Sound mode",
|
||||
"state": {
|
||||
@@ -665,6 +668,38 @@
|
||||
},
|
||||
"freezer_temperature_setpoint": {
|
||||
"name": "Freezer temperature"
|
||||
},
|
||||
"edge_lighting_mode": {
|
||||
"name": "Edge lighting mode",
|
||||
"state": {
|
||||
"smart": "Smart",
|
||||
"high": "High",
|
||||
"low": "Low"
|
||||
}
|
||||
},
|
||||
"edge_lighting_color": {
|
||||
"name": "Edge lighting color",
|
||||
"state": {
|
||||
"3000k": "3000 K",
|
||||
"4000k": "4000 K",
|
||||
"6500k": "6500 K"
|
||||
}
|
||||
},
|
||||
"indicator_light_mode": {
|
||||
"name": "Indicator light mode",
|
||||
"state": {
|
||||
"smart": "Smart",
|
||||
"high": "High",
|
||||
"low": "Low"
|
||||
}
|
||||
},
|
||||
"ventilation_mode": {
|
||||
"name": "Mode",
|
||||
"state": {
|
||||
"purification": "Purification",
|
||||
"ventilation": "Ventilation",
|
||||
"smartventilation": "Smart Ventilation"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
@@ -787,6 +822,12 @@
|
||||
"indirect": "Indirect"
|
||||
}
|
||||
},
|
||||
"energy_saving_state": {
|
||||
"name": "Energy saving state"
|
||||
},
|
||||
"energy_saving_operating_status": {
|
||||
"name": "Energy saving operating status"
|
||||
},
|
||||
"current_temp_c": {
|
||||
"name": "Temperature"
|
||||
},
|
||||
@@ -1040,6 +1081,9 @@
|
||||
"absence_power_saving_active": {
|
||||
"name": "Absence power saving active"
|
||||
},
|
||||
"absence_clean": {
|
||||
"name": "Absence clean"
|
||||
},
|
||||
"motion_detect_wind_active": {
|
||||
"name": "Motion-detect wind avoidance active"
|
||||
},
|
||||
@@ -1204,6 +1248,18 @@
|
||||
},
|
||||
"ventilation_alarm": {
|
||||
"name": "Ventilation alarm"
|
||||
},
|
||||
"edge_lighting": {
|
||||
"name": "Edge lighting"
|
||||
},
|
||||
"indicator_light": {
|
||||
"name": "Indicator light"
|
||||
},
|
||||
"windfree": {
|
||||
"name": "Wind-Free mode"
|
||||
},
|
||||
"windsleep": {
|
||||
"name": "Sleep mode"
|
||||
}
|
||||
},
|
||||
"time": {
|
||||
|
||||
@@ -663,6 +663,9 @@
|
||||
"mute": "Silencio"
|
||||
}
|
||||
},
|
||||
"energy_saving_mode": {
|
||||
"name": "Modo de ahorro de energía"
|
||||
},
|
||||
"air_purifier_sound_mode": {
|
||||
"name": "Modo de sonido",
|
||||
"state": {
|
||||
@@ -790,6 +793,38 @@
|
||||
},
|
||||
"freezer_temperature_setpoint": {
|
||||
"name": "Temperatura del congelador"
|
||||
},
|
||||
"edge_lighting_mode": {
|
||||
"name": "Modo de iluminación perimetral",
|
||||
"state": {
|
||||
"smart": "Inteligente",
|
||||
"high": "Alto",
|
||||
"low": "Bajo"
|
||||
}
|
||||
},
|
||||
"edge_lighting_color": {
|
||||
"name": "Color de iluminación perimetral",
|
||||
"state": {
|
||||
"3000k": "3000 K",
|
||||
"4000k": "4000 K",
|
||||
"6500k": "6500 K"
|
||||
}
|
||||
},
|
||||
"indicator_light_mode": {
|
||||
"name": "Modo de luz indicadora",
|
||||
"state": {
|
||||
"smart": "Inteligente",
|
||||
"high": "Alto",
|
||||
"low": "Bajo"
|
||||
}
|
||||
},
|
||||
"ventilation_mode": {
|
||||
"name": "Modo",
|
||||
"state": {
|
||||
"purification": "Purificación",
|
||||
"ventilation": "Ventilación",
|
||||
"smartventilation": "Ventilación inteligente"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
@@ -909,6 +944,12 @@
|
||||
"indirect": "Indirecto"
|
||||
}
|
||||
},
|
||||
"energy_saving_state": {
|
||||
"name": "Estado de ahorro de energía"
|
||||
},
|
||||
"energy_saving_operating_status": {
|
||||
"name": "Estado de funcionamiento del ahorro de energía"
|
||||
},
|
||||
"current_temp_c": {
|
||||
"name": "Temperatura"
|
||||
},
|
||||
@@ -1165,6 +1206,9 @@
|
||||
"absence_power_saving_active": {
|
||||
"name": "Ahorro por ausencia activo"
|
||||
},
|
||||
"absence_clean": {
|
||||
"name": "Limpieza por ausencia"
|
||||
},
|
||||
"motion_detect_wind_active": {
|
||||
"name": "Evitación de viento por detección de movimiento activa"
|
||||
},
|
||||
@@ -1329,6 +1373,18 @@
|
||||
},
|
||||
"ventilation_alarm": {
|
||||
"name": "Alarma de ventilación"
|
||||
},
|
||||
"edge_lighting": {
|
||||
"name": "Iluminación perimetral"
|
||||
},
|
||||
"indicator_light": {
|
||||
"name": "Luz indicadora"
|
||||
},
|
||||
"windfree": {
|
||||
"name": "Modo Wind-Free"
|
||||
},
|
||||
"windsleep": {
|
||||
"name": "Modo nocturno"
|
||||
}
|
||||
},
|
||||
"time": {
|
||||
|
||||
@@ -538,6 +538,9 @@
|
||||
"mute": "Muto"
|
||||
}
|
||||
},
|
||||
"energy_saving_mode": {
|
||||
"name": "Modalità risparmio energetico"
|
||||
},
|
||||
"air_purifier_sound_mode": {
|
||||
"name": "Modalità audio",
|
||||
"state": {
|
||||
@@ -665,6 +668,38 @@
|
||||
},
|
||||
"freezer_temperature_setpoint": {
|
||||
"name": "Temperatura freezer"
|
||||
},
|
||||
"edge_lighting_mode": {
|
||||
"name": "Modalità illuminazione perimetrale",
|
||||
"state": {
|
||||
"smart": "Smart",
|
||||
"high": "Alta",
|
||||
"low": "Bassa"
|
||||
}
|
||||
},
|
||||
"edge_lighting_color": {
|
||||
"name": "Colore illuminazione perimetrale",
|
||||
"state": {
|
||||
"3000k": "3000 K",
|
||||
"4000k": "4000 K",
|
||||
"6500k": "6500 K"
|
||||
}
|
||||
},
|
||||
"indicator_light_mode": {
|
||||
"name": "Modalità spia luminosa",
|
||||
"state": {
|
||||
"smart": "Smart",
|
||||
"high": "Alta",
|
||||
"low": "Bassa"
|
||||
}
|
||||
},
|
||||
"ventilation_mode": {
|
||||
"name": "Modalità",
|
||||
"state": {
|
||||
"purification": "Purificazione",
|
||||
"ventilation": "Ventilazione",
|
||||
"smartventilation": "Ventilazione intelligente"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
@@ -787,6 +822,12 @@
|
||||
"indirect": "Indiretto"
|
||||
}
|
||||
},
|
||||
"energy_saving_state": {
|
||||
"name": "Stato risparmio energetico"
|
||||
},
|
||||
"energy_saving_operating_status": {
|
||||
"name": "Stato di funzionamento del risparmio energetico"
|
||||
},
|
||||
"current_temp_c": {
|
||||
"name": "Temperatura"
|
||||
},
|
||||
@@ -1040,6 +1081,9 @@
|
||||
"absence_power_saving_active": {
|
||||
"name": "Risparmio energetico in assenza attivo"
|
||||
},
|
||||
"absence_clean": {
|
||||
"name": "Pulizia in assenza"
|
||||
},
|
||||
"motion_detect_wind_active": {
|
||||
"name": "Elusione flusso d'aria a rilevamento movimento attiva"
|
||||
},
|
||||
@@ -1204,6 +1248,18 @@
|
||||
},
|
||||
"ventilation_alarm": {
|
||||
"name": "Allarme ventilazione"
|
||||
},
|
||||
"edge_lighting": {
|
||||
"name": "Illuminazione perimetrale"
|
||||
},
|
||||
"indicator_light": {
|
||||
"name": "Spia luminosa"
|
||||
},
|
||||
"windfree": {
|
||||
"name": "Modalità Wind-Free"
|
||||
},
|
||||
"windsleep": {
|
||||
"name": "Modalità notte"
|
||||
}
|
||||
},
|
||||
"time": {
|
||||
|
||||
@@ -538,6 +538,9 @@
|
||||
"mute": "음소거"
|
||||
}
|
||||
},
|
||||
"energy_saving_mode": {
|
||||
"name": "절전 모드"
|
||||
},
|
||||
"air_purifier_sound_mode": {
|
||||
"name": "소리 모드",
|
||||
"state": {
|
||||
@@ -665,6 +668,38 @@
|
||||
},
|
||||
"freezer_temperature_setpoint": {
|
||||
"name": "냉동실 온도"
|
||||
},
|
||||
"edge_lighting_mode": {
|
||||
"name": "엣지 라이팅 모드",
|
||||
"state": {
|
||||
"smart": "스마트",
|
||||
"high": "높음",
|
||||
"low": "낮음"
|
||||
}
|
||||
},
|
||||
"edge_lighting_color": {
|
||||
"name": "엣지 라이팅 색상",
|
||||
"state": {
|
||||
"3000k": "3000 K",
|
||||
"4000k": "4000 K",
|
||||
"6500k": "6500 K"
|
||||
}
|
||||
},
|
||||
"indicator_light_mode": {
|
||||
"name": "표시등 모드",
|
||||
"state": {
|
||||
"smart": "스마트",
|
||||
"high": "높음",
|
||||
"low": "낮음"
|
||||
}
|
||||
},
|
||||
"ventilation_mode": {
|
||||
"name": "모드",
|
||||
"state": {
|
||||
"purification": "청정",
|
||||
"ventilation": "환기",
|
||||
"smartventilation": "스마트환기"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
@@ -787,6 +822,12 @@
|
||||
"indirect": "간접풍"
|
||||
}
|
||||
},
|
||||
"energy_saving_state": {
|
||||
"name": "절전 상태"
|
||||
},
|
||||
"energy_saving_operating_status": {
|
||||
"name": "절전 작동 상태"
|
||||
},
|
||||
"current_temp_c": {
|
||||
"name": "온도"
|
||||
},
|
||||
@@ -1040,6 +1081,9 @@
|
||||
"absence_power_saving_active": {
|
||||
"name": "부재 절전 작동"
|
||||
},
|
||||
"absence_clean": {
|
||||
"name": "부재 청소"
|
||||
},
|
||||
"motion_detect_wind_active": {
|
||||
"name": "모션 감지 바람 회피 작동"
|
||||
},
|
||||
@@ -1204,6 +1248,18 @@
|
||||
},
|
||||
"ventilation_alarm": {
|
||||
"name": "환기 알림"
|
||||
},
|
||||
"edge_lighting": {
|
||||
"name": "엣지 라이팅"
|
||||
},
|
||||
"indicator_light": {
|
||||
"name": "표시등"
|
||||
},
|
||||
"windfree": {
|
||||
"name": "무풍 모드"
|
||||
},
|
||||
"windsleep": {
|
||||
"name": "취침 모드"
|
||||
}
|
||||
},
|
||||
"time": {
|
||||
|
||||
@@ -538,6 +538,9 @@
|
||||
"mute": "Stil"
|
||||
}
|
||||
},
|
||||
"energy_saving_mode": {
|
||||
"name": "Energiebesparingsmodus"
|
||||
},
|
||||
"air_purifier_sound_mode": {
|
||||
"name": "Geluidsmodus",
|
||||
"state": {
|
||||
@@ -665,6 +668,38 @@
|
||||
},
|
||||
"freezer_temperature_setpoint": {
|
||||
"name": "Temperatuur vriesgedeelte"
|
||||
},
|
||||
"edge_lighting_mode": {
|
||||
"name": "Randverlichtingsmodus",
|
||||
"state": {
|
||||
"smart": "Slim",
|
||||
"high": "Hoog",
|
||||
"low": "Laag"
|
||||
}
|
||||
},
|
||||
"edge_lighting_color": {
|
||||
"name": "Randverlichtingskleur",
|
||||
"state": {
|
||||
"3000k": "3000 K",
|
||||
"4000k": "4000 K",
|
||||
"6500k": "6500 K"
|
||||
}
|
||||
},
|
||||
"indicator_light_mode": {
|
||||
"name": "Indicatorlampjemodus",
|
||||
"state": {
|
||||
"smart": "Slim",
|
||||
"high": "Hoog",
|
||||
"low": "Laag"
|
||||
}
|
||||
},
|
||||
"ventilation_mode": {
|
||||
"name": "Modus",
|
||||
"state": {
|
||||
"purification": "Zuivering",
|
||||
"ventilation": "Ventilatie",
|
||||
"smartventilation": "Slimme ventilatie"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
@@ -787,6 +822,12 @@
|
||||
"indirect": "Indirect"
|
||||
}
|
||||
},
|
||||
"energy_saving_state": {
|
||||
"name": "Energiebesparing status"
|
||||
},
|
||||
"energy_saving_operating_status": {
|
||||
"name": "Bedrijfsstatus energiebesparing"
|
||||
},
|
||||
"current_temp_c": {
|
||||
"name": "Temperatuur"
|
||||
},
|
||||
@@ -1040,6 +1081,9 @@
|
||||
"absence_power_saving_active": {
|
||||
"name": "Energiebesparing bij afwezigheid actief"
|
||||
},
|
||||
"absence_clean": {
|
||||
"name": "Reiniging bij afwezigheid"
|
||||
},
|
||||
"motion_detect_wind_active": {
|
||||
"name": "Bewegingsdetectie luchtstroomvermijding actief"
|
||||
},
|
||||
@@ -1204,6 +1248,18 @@
|
||||
},
|
||||
"ventilation_alarm": {
|
||||
"name": "Ventilatiealarm"
|
||||
},
|
||||
"edge_lighting": {
|
||||
"name": "Randverlichting"
|
||||
},
|
||||
"indicator_light": {
|
||||
"name": "Indicatorlampje"
|
||||
},
|
||||
"windfree": {
|
||||
"name": "Wind-Free modus"
|
||||
},
|
||||
"windsleep": {
|
||||
"name": "Slaapmodus"
|
||||
}
|
||||
},
|
||||
"time": {
|
||||
|
||||
+1056
File diff suppressed because it is too large
Load Diff
+138
@@ -0,0 +1,138 @@
|
||||
{
|
||||
"device0": [
|
||||
{
|
||||
"rt": [
|
||||
"x.com.samsung.devcol",
|
||||
"oic.wk.col"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.ll",
|
||||
"oic.if.b"
|
||||
]
|
||||
},
|
||||
{
|
||||
"href": "/alarms/vs/0",
|
||||
"rep": {
|
||||
"rt": [
|
||||
"x.com.samsung.da.alarms"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.a"
|
||||
],
|
||||
"x.com.samsung.da.items": [
|
||||
{
|
||||
"x.com.samsung.da.id": "0",
|
||||
"x.com.samsung.da.description": "Alarm",
|
||||
"x.com.samsung.da.alarmType": "Device",
|
||||
"x.com.samsung.da.code": "CT_E",
|
||||
"x.com.samsung.da.triggeredTime": "2026-08-06T14:24:02"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/bluetooth/hood/status/vs/0",
|
||||
"rep": {
|
||||
"connectionState": "disconnected",
|
||||
"micomModelId": "",
|
||||
"firmwareVersion": "",
|
||||
"power": "off",
|
||||
"fanSpeed": 0,
|
||||
"lampState": "off",
|
||||
"timer": {},
|
||||
"rt": [
|
||||
"bluetoothHoodStatus"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.s"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/configuration/vs/0",
|
||||
"rep": {}
|
||||
},
|
||||
{
|
||||
"href": "/connected/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.connected": "On",
|
||||
"rt": [
|
||||
"x.com.samsung.da.connected"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.a"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/kidslock/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.kidsLock": "Ready"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/mode/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.options": [
|
||||
"DeviceType_NV8000T-/KO0",
|
||||
"Pause_Off",
|
||||
"SyncFlex_Off",
|
||||
"FlexCoil_0",
|
||||
"MainTimerCurrent_0",
|
||||
"MainTimerSet_0",
|
||||
"MainTimerState_Ready",
|
||||
"IndependentTimerCheck_Enable",
|
||||
"OperationState0_Ready",
|
||||
"HotSurface0_Normal",
|
||||
"PowerLevel0_0",
|
||||
"OperationState1_Ready",
|
||||
"HotSurface1_Normal",
|
||||
"PowerLevel1_0",
|
||||
"OperationState2_Ready",
|
||||
"HotSurface2_Normal",
|
||||
"PowerLevel2_0",
|
||||
"OperationState3_Ready",
|
||||
"HotSurface3_Normal",
|
||||
"PowerLevel3_0",
|
||||
"OperationState4_Ready",
|
||||
"HotSurface4_Normal",
|
||||
"PowerLevel4_0",
|
||||
"OperationState5_Ready",
|
||||
"HotSurface5_Normal",
|
||||
"PowerLevel5_0"
|
||||
],
|
||||
"rt": [
|
||||
"x.com.samsung.da.mode"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.a"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/otninformation/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.target": "",
|
||||
"x.com.samsung.da.newVersionAvailable": "false"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/power/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.power": "Off",
|
||||
"rt": [
|
||||
"x.com.samsung.da.operation"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.a"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"state_keys": [
|
||||
"absence_clean",
|
||||
"ai_energy_level",
|
||||
"air_filter_status",
|
||||
"air_filter_threshold",
|
||||
"air_filter_usage",
|
||||
"air_filter_usage_hours",
|
||||
"air_purify",
|
||||
"alarm_code",
|
||||
"auto_clean",
|
||||
"auto_clean_progress",
|
||||
"auto_clean_running",
|
||||
"beep",
|
||||
"climate",
|
||||
"current_temperature_c",
|
||||
"display",
|
||||
"energy_kwh",
|
||||
"energy_saved_kwh",
|
||||
"energy_saving_mode",
|
||||
"energy_saving_operating_status",
|
||||
"energy_saving_state",
|
||||
"firmware_update",
|
||||
"humidity",
|
||||
"mute_once",
|
||||
"odor_controller_active",
|
||||
"odor_controller_progress",
|
||||
"power_energy_kwh",
|
||||
"power_watts",
|
||||
"selfcheck_error",
|
||||
"selfcheck_result",
|
||||
"selfcheck_status",
|
||||
"sound_mode",
|
||||
"sound_output",
|
||||
"tropical_night_mode",
|
||||
"uv_led",
|
||||
"ventilation_alarm"
|
||||
]
|
||||
}
|
||||
+18
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"state_keys": [
|
||||
"absence_clean",
|
||||
"absence_power_saving_active",
|
||||
"absence_power_saving_mode",
|
||||
"air_filter_pm1_status",
|
||||
@@ -9,6 +10,7 @@
|
||||
"air_filter_usage",
|
||||
"air_filter_usage_hours",
|
||||
"air_purify",
|
||||
"air_sensing_state",
|
||||
"alarm_code",
|
||||
"auto_clean",
|
||||
"auto_clean_progress",
|
||||
@@ -17,20 +19,36 @@
|
||||
"climate",
|
||||
"current_temperature_c",
|
||||
"dust",
|
||||
"edge_lighting",
|
||||
"edge_lighting_color",
|
||||
"edge_lighting_mode",
|
||||
"energy_kwh",
|
||||
"energy_saved_kwh",
|
||||
"fine_dust",
|
||||
"firmware_update",
|
||||
"humidity",
|
||||
"indicator_light",
|
||||
"indicator_light_mode",
|
||||
"last_air_sensing_level",
|
||||
"last_air_sensing_time",
|
||||
"motion_detect_wind_active",
|
||||
"motion_detect_wind_mode",
|
||||
"mute_once",
|
||||
"odor_controller_active",
|
||||
"odor_controller_progress",
|
||||
"periodic_air_sensing",
|
||||
"periodic_sensing_skip_status",
|
||||
"power_watts",
|
||||
"selfcheck_error",
|
||||
"selfcheck_result",
|
||||
"selfcheck_status",
|
||||
"sensing_interval",
|
||||
"sensing_mode",
|
||||
"sensing_skip_end",
|
||||
"sensing_skip_start",
|
||||
"sound_mode",
|
||||
"sound_output",
|
||||
"sound_volume",
|
||||
"super_fine_dust",
|
||||
"tropical_night_mode",
|
||||
"uv_led"
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"air_filter_usage",
|
||||
"air_filter_usage_hours",
|
||||
"air_purify",
|
||||
"air_sensing_state",
|
||||
"alarm_code",
|
||||
"auto_clean",
|
||||
"auto_clean_progress",
|
||||
@@ -19,10 +20,18 @@
|
||||
"fine_dust",
|
||||
"firmware_update",
|
||||
"humidity",
|
||||
"last_air_sensing_level",
|
||||
"last_air_sensing_time",
|
||||
"mute_once",
|
||||
"periodic_air_sensing",
|
||||
"periodic_sensing_skip_status",
|
||||
"selfcheck_error",
|
||||
"selfcheck_result",
|
||||
"selfcheck_status",
|
||||
"sensing_interval",
|
||||
"sensing_mode",
|
||||
"sensing_skip_end",
|
||||
"sensing_skip_start",
|
||||
"super_fine_dust",
|
||||
"tropical_night_mode"
|
||||
]
|
||||
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"state_keys": [
|
||||
"alarm_code",
|
||||
"any_burner_active",
|
||||
"burner_0_state",
|
||||
"burner_1_state",
|
||||
"burner_2_state",
|
||||
"burner_3_state",
|
||||
"burner_4_state",
|
||||
"burner_5_state",
|
||||
"child_lock",
|
||||
"cloud_connected",
|
||||
"firmware_update",
|
||||
"main_timer_current",
|
||||
"main_timer_state",
|
||||
"paired_hood_connected",
|
||||
"paired_hood_fan_speed",
|
||||
"paired_hood_firmware",
|
||||
"paired_hood_light",
|
||||
"paired_hood_model",
|
||||
"paired_hood_power",
|
||||
"power_state"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"state_keys": [
|
||||
"alarm_code",
|
||||
"child_lock",
|
||||
"cloud_connected",
|
||||
"cook_time",
|
||||
"current_temp_c",
|
||||
"cycle_active",
|
||||
"diagnosis_status",
|
||||
"door_open",
|
||||
"energy_saving",
|
||||
"finish_time",
|
||||
"firmware_update",
|
||||
"machine_state",
|
||||
"operation_time_minutes",
|
||||
"oven_mode",
|
||||
"oven_setpoint",
|
||||
"oven_state",
|
||||
"power_switch",
|
||||
"progress_percentage",
|
||||
"remote_control",
|
||||
"sound"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"state_keys": [
|
||||
"ai_energy_level",
|
||||
"air_filter_status",
|
||||
"air_filter_usage",
|
||||
"alarm_code",
|
||||
"auto_door_opener",
|
||||
"brightness_level",
|
||||
"cabinet_light_dim",
|
||||
"cabinet_light_switch",
|
||||
"cooler_setpoint",
|
||||
"cooler_temperature",
|
||||
"day_brightness",
|
||||
"door_alert",
|
||||
"door_cooler_open",
|
||||
"door_freezer_open",
|
||||
"energy_kwh",
|
||||
"energy_saved_kwh",
|
||||
"firmware_update",
|
||||
"freezer_setpoint",
|
||||
"freezer_temperature",
|
||||
"fridge_sound",
|
||||
"ice_night_mode",
|
||||
"icemaker_one_enabled",
|
||||
"icemaker_one_making_status",
|
||||
"night_end",
|
||||
"night_start",
|
||||
"power_energy_kwh",
|
||||
"power_watts",
|
||||
"rapid_freezing",
|
||||
"rapid_fridge",
|
||||
"selfcheck_error",
|
||||
"selfcheck_result",
|
||||
"selfcheck_status"
|
||||
]
|
||||
}
|
||||
+260
@@ -0,0 +1,260 @@
|
||||
{
|
||||
"device0": [
|
||||
{
|
||||
"rt": [
|
||||
"x.com.samsung.devcol",
|
||||
"oic.wk.col"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.ll",
|
||||
"oic.if.b"
|
||||
]
|
||||
},
|
||||
{
|
||||
"href": "/alarms/vs/0",
|
||||
"rep": {
|
||||
"rt": [
|
||||
"x.com.samsung.da.alarms"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.s"
|
||||
],
|
||||
"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": "OV_E_OFF",
|
||||
"x.com.samsung.da.triggeredTime": "2026-08-05T14:20:14"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/connected/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.connected": "On",
|
||||
"rt": [
|
||||
"x.com.samsung.da.connected"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.s"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/diagnosis/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.diagnosisStart": "Ready"
|
||||
}
|
||||
},
|
||||
{
|
||||
"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.lock": "Unlock"
|
||||
}
|
||||
],
|
||||
"rt": [
|
||||
"x.com.samsung.da.doors"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.s"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/information/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.modelNum": "TP2X_DA-KS-WALLOVEN-000002|40441841|5002011E021011150100000000000000",
|
||||
"x.com.samsung.da.description": "NW9000KD/AA1",
|
||||
"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": "Version",
|
||||
"x.com.samsung.da.type": "Software",
|
||||
"x.com.samsung.da.number": "240205",
|
||||
"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": "DE92-04418A_20062500, DE92-04011A_17041300",
|
||||
"x.com.samsung.da.newVersionAvailable": "0"
|
||||
}
|
||||
],
|
||||
"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": "610",
|
||||
"x.com.samsung.da.diagMinVersion": "1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/kidslock/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.kidsLock": "Ready"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/mode/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"ConvectionBake",
|
||||
"ConvectionRoast",
|
||||
"Bake",
|
||||
"Broil",
|
||||
"SteamBake",
|
||||
"SteamRoast",
|
||||
"Easycook3",
|
||||
"Descale",
|
||||
"PyroFree",
|
||||
"Drain",
|
||||
"SelfClean",
|
||||
"NoOperation"
|
||||
],
|
||||
"x.com.samsung.da.modes": [
|
||||
"NoOperation"
|
||||
],
|
||||
"x.com.samsung.da.options": [
|
||||
"DeviceType_NW9000KD/AA1",
|
||||
"keepWarmReservation_Off",
|
||||
"meatprobe_disconnected",
|
||||
"NoPreheat_Off",
|
||||
"waterInlet_Closed",
|
||||
"steamAddLevel_0",
|
||||
"descaleAlarm_Normal",
|
||||
"descaleNewWaterAlarm_Off",
|
||||
"descaleEmptyWaterAlarm_Off",
|
||||
"steamGeneratorLevel_Empty",
|
||||
"steamUsingTime_66",
|
||||
"drainRequired_00",
|
||||
"steamState_Standby",
|
||||
"descaleState_Standby",
|
||||
"waterTankInSwitch_On",
|
||||
"pyroFreeState_Standby",
|
||||
"waterTankOutSwitch_Off",
|
||||
"Sound_On",
|
||||
"AdjustingTemp_0",
|
||||
"Sabbath_Off",
|
||||
"EnergySaving_On"
|
||||
],
|
||||
"rt": [
|
||||
"x.com.samsung.da.mode"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.a"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/operational/state/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.state": "Ready",
|
||||
"x.com.samsung.da.operationTime": "00:00:00",
|
||||
"x.com.samsung.da.remainingTime": "00:00:00",
|
||||
"x.com.samsung.da.progressPercentage": "1",
|
||||
"rt": [
|
||||
"x.com.samsung.da.operation"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.a"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/otninformation/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.target": "",
|
||||
"x.com.samsung.da.newVersionAvailable": "false"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/oven/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.state": "Ready",
|
||||
"rt": [
|
||||
"x.com.samsung.da.oven"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.s"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/power/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.power": "On",
|
||||
"rt": [
|
||||
"x.com.samsung.da.operation"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.s"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/remotectrl/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.remoteControlEnabled": "false",
|
||||
"rt": [
|
||||
"x.com.samsung.da.configuration"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.s"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"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": "0",
|
||||
"x.com.samsung.da.current": "0",
|
||||
"x.com.samsung.da.increment": "0",
|
||||
"x.com.samsung.da.unit": "Fahrenheit"
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "1",
|
||||
"x.com.samsung.da.description": "Temperature",
|
||||
"x.com.samsung.da.desired": "0",
|
||||
"x.com.samsung.da.current": "0",
|
||||
"x.com.samsung.da.increment": "0",
|
||||
"x.com.samsung.da.unit": "Fahrenheit"
|
||||
}
|
||||
],
|
||||
"rt": [
|
||||
"x.com.samsung.da.temperatures"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.a"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,623 @@
|
||||
{
|
||||
"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": "/alarms/vs/0",
|
||||
"rt": [
|
||||
"x.com.samsung.da.alarms"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.a"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/bespoke/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.BespokeProduct": "On",
|
||||
"href": "/bespoke/vs/0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/cabinet/light/enhanced/vs/0",
|
||||
"rep": {
|
||||
"light.control.status": "On",
|
||||
"level.brightness.daytime": "100",
|
||||
"level.brightness.nighttime": "33",
|
||||
"night.starttime": "2026-08-07T12:00:00",
|
||||
"night.duration.minute": "540",
|
||||
"timezone.offset": "+09:00",
|
||||
"href": "/cabinet/light/enhanced/vs/0",
|
||||
"rt": [
|
||||
"x.com.samsung.da.light.enhanced"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.a"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/cabinet/light/total/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.lightLevel": "100",
|
||||
"x.com.samsung.da.lightResolution": "3",
|
||||
"x.com.samsung.da.lightControl.off.include": "Off",
|
||||
"x.com.samsung.da.lightControl": "Off",
|
||||
"x.com.samsung.da.lightControl.hide": "true",
|
||||
"light.dimming.status": "On",
|
||||
"href": "/cabinet/light/total/vs/0",
|
||||
"rt": [
|
||||
"x.com.samsung.da.cabinetlight"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.a"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/configuration/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.region": "",
|
||||
"x.com.samsung.da.countryCode": "",
|
||||
"href": "/configuration/vs/0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/connectionconfig/vs/0",
|
||||
"rep": {
|
||||
"autoReconnectionMinVersion": "1.0",
|
||||
"autoReconnection": "true",
|
||||
"autoReconnectionProtocolType": [
|
||||
"helper_hotspot",
|
||||
"ble_ocf"
|
||||
],
|
||||
"supportedWiFiAuthType": [
|
||||
"OPEN",
|
||||
"WEP",
|
||||
"WPA-PSK",
|
||||
"WPA2-PSK",
|
||||
"SAE"
|
||||
],
|
||||
"supportedWiFiCryptoType": [
|
||||
"TKIP",
|
||||
"AES",
|
||||
"WEP-64",
|
||||
"WEP-128"
|
||||
],
|
||||
"supportedWiFiFreq": [
|
||||
"2.4G"
|
||||
],
|
||||
"calmConnectionCare": {
|
||||
"version": "1.0",
|
||||
"role": [
|
||||
"things"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/defrost/prediction/vs/0",
|
||||
"rep": {
|
||||
"ai.cooling.care": "Off",
|
||||
"href": "/defrost/prediction/vs/0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/dginformation/vs/0",
|
||||
"rep": {
|
||||
"enrolmentstatus": "Unknown",
|
||||
"devicestate": "Unknown",
|
||||
"lockstatus": "Normal",
|
||||
"nextduedate": "",
|
||||
"workingminutes": 0,
|
||||
"paymentinfo": {
|
||||
"emiplan": "Unknown",
|
||||
"currency": "Unknown",
|
||||
"totalemi": 0,
|
||||
"totalemipaid": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/door/cooler/0",
|
||||
"rep": {
|
||||
"openState": "Close",
|
||||
"href": "/door/cooler/0",
|
||||
"rt": [
|
||||
"oic.r.door"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.s"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/door/freezer/0",
|
||||
"rep": {
|
||||
"openState": "Close",
|
||||
"href": "/door/freezer/0",
|
||||
"rt": [
|
||||
"oic.r.door"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.s"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/doors/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.items": [
|
||||
{
|
||||
"x.com.samsung.da.openState": "Close",
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"href": "/doors/vs/0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/drlc/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.drlcLevel": "2",
|
||||
"x.com.samsung.da.override": "Not_Supported",
|
||||
"x.com.samsung.da.durationminutes": "1441",
|
||||
"x.com.samsung.da.start": "2026-08-07T00:00:41Z",
|
||||
"x.com.samsung.da.realSaving": "On",
|
||||
"href": "/drlc/vs/0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/energy/ailevel/vs/0",
|
||||
"rep": {
|
||||
"aiLevel": "1",
|
||||
"supportedAiLevel": [
|
||||
"1",
|
||||
"2"
|
||||
],
|
||||
"href": "/energy/ailevel/vs/0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/energy/consumption/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.cumulativeConsumption": "25344",
|
||||
"x.com.samsung.da.instantaneousPower": "48",
|
||||
"x.com.samsung.da.cumulativePower": "413385",
|
||||
"x.com.samsung.da.cumulativeSavedPower": "50694",
|
||||
"x.com.samsung.da.cumulativeUnit": "Wh",
|
||||
"x.com.samsung.da.instantaneousPowerUnit": "W",
|
||||
"href": "/energy/consumption/vs/0",
|
||||
"x.com.samsung.da.cumulativeDateUTC": "1786020360"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/file/information/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.timeoffset": "+09:00",
|
||||
"x.com.samsung.supprtedtype": 1,
|
||||
"href": "/file/information/vs/0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/filter/airdustfilter/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.filterUsage": "100",
|
||||
"x.com.samsung.da.filterUsageResolution": "1",
|
||||
"x.com.samsung.da.filterResetType": [
|
||||
"washable"
|
||||
],
|
||||
"x.com.samsung.da.filterStatus": "wash",
|
||||
"href": "/filter/airdustfilter/vs/0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/icemaker/nighttime/vs/0",
|
||||
"rep": {
|
||||
"ice.night.status": "On",
|
||||
"ice.night.starttime": "2026-08-07T12:00:00",
|
||||
"ice.night.duration": "540",
|
||||
"ice.night.timezone": "+09:00",
|
||||
"href": "/icemaker/nighttime/vs/0",
|
||||
"rt": [
|
||||
"x.com.samsung.da.ice.night"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.a"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"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.iceType.desired": "NORMAL",
|
||||
"x.com.samsung.da.iceMaker.iceMakingStatus": "ICESTATUS_STOP",
|
||||
"x.com.samsung.da.iceMaker.type": "toggle",
|
||||
"href": "/icemaker/one/vs/0",
|
||||
"rt": [
|
||||
"x.com.samsung.da.icemaker"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.a"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/icemaker/status/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.iceMaker": "On",
|
||||
"href": "/icemaker/status/vs/0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/information/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.modelNum": "TP1X_REF_21K|70664141|0000033C011913114100000041FB5F00",
|
||||
"x.com.samsung.da.description": "TP1X_REF_21K",
|
||||
"x.com.samsung.da.serialNum": "**REDACTED**",
|
||||
"x.com.samsung.da.otnDUID": "**REDACTED**",
|
||||
"x.com.samsung.da.diagDumpType": "file",
|
||||
"x.com.samsung.da.diagEndPoint": "SSM",
|
||||
"x.com.samsung.da.diagLogType": [
|
||||
"errCode",
|
||||
"dump"
|
||||
],
|
||||
"x.com.samsung.da.diagMnid": "0AJT",
|
||||
"x.com.samsung.da.diagSetupid": "RR7",
|
||||
"x.com.samsung.da.diagProtocolType": "BLE_OCF",
|
||||
"x.com.samsung.da.diagMinVersion": "3.0",
|
||||
"x.com.samsung.da.diagTsId": "DA01",
|
||||
"x.com.samsung.da.items": [
|
||||
{
|
||||
"x.com.samsung.da.id": "0",
|
||||
"x.com.samsung.da.description": "WiFi Module",
|
||||
"x.com.samsung.da.type": "Software",
|
||||
"x.com.samsung.da.number": "260618",
|
||||
"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": "2605071D, 24120906, 26040701, FFFFFFFF, 25082208, FFFFFFFF",
|
||||
"x.com.samsung.da.newVersionAvailable": "0"
|
||||
}
|
||||
],
|
||||
"href": "/information/vs/0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/mode/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"HOMECARE_WIZARD_V2",
|
||||
"ENERGY_REPORT_MODEL",
|
||||
"18K_REF_OUTDOOR_CONTROL_V2"
|
||||
],
|
||||
"href": "/mode/vs/0",
|
||||
"rt": [
|
||||
"x.com.samsung.da.mode"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.a"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/otninformation/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.target": "Micom",
|
||||
"x.com.samsung.da.newVersionAvailable": "false",
|
||||
"x.com.samsung.da.newVersionNo": "26040701",
|
||||
"x.com.samsung.da.currentVersionInfo": "10000000",
|
||||
"otnStatus": "None",
|
||||
"flashingProgress": "0",
|
||||
"otnTarget": "inverter",
|
||||
"otnCompleteDate": "noHistory",
|
||||
"scheduledTime": "None",
|
||||
"swVersionInfo": {
|
||||
"platform": "Tizen Lite",
|
||||
"oneUiVersion": "7.0 Refrigerator",
|
||||
"osVersion": "4.0"
|
||||
},
|
||||
"otnList": [
|
||||
{
|
||||
"type": "WIFI",
|
||||
"modelId": "A-RFWW-TP1-24-T4-RE1",
|
||||
"versions": [
|
||||
"20260618"
|
||||
],
|
||||
"visVersion": "260618"
|
||||
},
|
||||
{
|
||||
"type": "Micom",
|
||||
"modelId": "823070664141FFFFFFFF",
|
||||
"versions": [
|
||||
"2605071D",
|
||||
"FFFFFFFF"
|
||||
],
|
||||
"visVersion": "260507"
|
||||
},
|
||||
{
|
||||
"type": "Micom",
|
||||
"modelId": "823070664041FFFFFFFF",
|
||||
"versions": [
|
||||
"24120906",
|
||||
"FFFFFFFF"
|
||||
],
|
||||
"visVersion": "241209"
|
||||
},
|
||||
{
|
||||
"type": "Micom",
|
||||
"modelId": "02307066414170664041",
|
||||
"versions": [
|
||||
"2605071D",
|
||||
"24120906"
|
||||
],
|
||||
"visVersion": "260507"
|
||||
},
|
||||
{
|
||||
"type": "Micom",
|
||||
"modelId": "023070680641FFFFFFFF",
|
||||
"versions": [
|
||||
"26040701",
|
||||
"FFFFFFFF"
|
||||
],
|
||||
"visVersion": "260407"
|
||||
},
|
||||
{
|
||||
"type": "Micom",
|
||||
"modelId": "023070668841FFFFFFFF",
|
||||
"versions": [
|
||||
"25082208",
|
||||
"FFFFFFFF"
|
||||
],
|
||||
"visVersion": "250822"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/quickcontrol/info/vs/0",
|
||||
"rep": {
|
||||
"supportedVersion": "1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/realtimenotiforclient/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.timeforshortnoti": "0",
|
||||
"x.com.samsung.da.periodicnotisubscription": "true",
|
||||
"href": "/realtimenotiforclient/vs/0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/refrigeration/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.rapidFridge": "Off",
|
||||
"x.com.samsung.da.rapidFreezing": "Off",
|
||||
"href": "/refrigeration/vs/0",
|
||||
"rt": [
|
||||
"x.com.samsung.da.fridge"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.a"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/rm/control/vs/0",
|
||||
"rep": {
|
||||
"minPeriod": "9000",
|
||||
"href": "/rm/control/vs/0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/runningmode/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.runningMode": 0,
|
||||
"href": "/runningmode/vs/0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/selfcheck/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.supportedActions": [
|
||||
"Start"
|
||||
],
|
||||
"x.com.samsung.da.status": "Ready",
|
||||
"x.com.samsung.da.result": "Success",
|
||||
"x.com.samsung.da.error": [
|
||||
"ErrorCode_None"
|
||||
],
|
||||
"href": "/selfcheck/vs/0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/settings/sound/alert/door/vs/0",
|
||||
"rep": {
|
||||
"alert.door": "1",
|
||||
"supportedAlert.door": [
|
||||
"1",
|
||||
"2",
|
||||
"3",
|
||||
"4"
|
||||
],
|
||||
"href": "/settings/sound/alert/door/vs/0",
|
||||
"rt": [
|
||||
"x.com.samsung.alert.door"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.a"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/status/lock/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.device.sound": "On",
|
||||
"x.com.samsung.da.preciseCooling": "On",
|
||||
"x.com.samsung.da.doorAlarmSound": "On",
|
||||
"cleaning.status": "On",
|
||||
"cleaning.type": "SPI_AND_UV",
|
||||
"href": "/status/lock/vs/0",
|
||||
"rt": [
|
||||
"x.com.samsung.da.lockstatus"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.a"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/temperature/current/cooler/0",
|
||||
"rep": {
|
||||
"temperature": 2.0,
|
||||
"range": [
|
||||
1.0,
|
||||
7.0
|
||||
],
|
||||
"units": "C",
|
||||
"href": "/temperature/current/cooler/0",
|
||||
"rt": [
|
||||
"oic.r.temperature"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.a"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/temperature/current/freezer/0",
|
||||
"rep": {
|
||||
"temperature": -19.0,
|
||||
"range": [
|
||||
-23.0,
|
||||
-15.0
|
||||
],
|
||||
"units": "C",
|
||||
"href": "/temperature/current/freezer/0",
|
||||
"rt": [
|
||||
"oic.r.temperature"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.a"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/temperature/desired/cooler/0",
|
||||
"rep": {
|
||||
"temperature": 2.0,
|
||||
"range": [
|
||||
1.0,
|
||||
7.0
|
||||
],
|
||||
"units": "C",
|
||||
"href": "/temperature/desired/cooler/0",
|
||||
"rt": [
|
||||
"oic.r.temperature"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.a"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/temperature/desired/freezer/0",
|
||||
"rep": {
|
||||
"temperature": -19.0,
|
||||
"range": [
|
||||
-23.0,
|
||||
-15.0
|
||||
],
|
||||
"units": "C",
|
||||
"href": "/temperature/desired/freezer/0",
|
||||
"rt": [
|
||||
"oic.r.temperature"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.a"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/temperatures/vs/0",
|
||||
"rep": {
|
||||
"temperature.unit.control": "true",
|
||||
"x.com.samsung.da.items": [
|
||||
{
|
||||
"x.com.samsung.da.id": "0",
|
||||
"x.com.samsung.da.description": "Freezer",
|
||||
"x.com.samsung.da.desired": "-19",
|
||||
"x.com.samsung.da.current": "-19",
|
||||
"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": "/temperatures/vs/0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/timezone/vs/0",
|
||||
"rep": {
|
||||
"timezoneid": "Asia/Seoul",
|
||||
"offset": "+09:00",
|
||||
"DST": "OFF"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/wirelessinfo/vs/0",
|
||||
"rep": {
|
||||
"macaddressWiFi": "**REDACTED**",
|
||||
"macaddressBLE": "**REDACTED**",
|
||||
"connectedApSsid": "eomkim_IoT"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
"""Tests for the AILP_DA-AC-FAC-02011_0000 air conditioner (issue #319).
|
||||
|
||||
This board resolves to the airconditioner registry two independent ways --
|
||||
modelNum's 'FAC' board token (for_device_by_model) and /oic/d's
|
||||
oic.d.airconditioner type (for_device_by_oic_type) agree -- and reports
|
||||
several resources the sibling TP1X_DA-AC-CAC-01001 board (issue #191) left
|
||||
as a documented gap: /display/vs/0 and the /settings/sound/* trio (now
|
||||
shared with air_purifier.py's identical shapes), plus two genuinely new
|
||||
hrefs (/csi/absenceclean/vs/0, /csi/energysaving/vs/0).
|
||||
"""
|
||||
|
||||
from typing import cast
|
||||
|
||||
from custom_components.localthings.coordinator import LocalThingsCoordinator
|
||||
from custom_components.localthings.entity import _is_included
|
||||
from custom_components.localthings.registry.adapter import flatten
|
||||
from custom_components.localthings.registry.by_type import (
|
||||
for_device_by_model,
|
||||
for_device_by_oic_type,
|
||||
resolve,
|
||||
)
|
||||
from custom_components.localthings.registry.discovery import discover
|
||||
from tests.conftest import _load_device
|
||||
|
||||
_DEVICE_TYPES = ("oic.wk.d", "oic.d.airconditioner")
|
||||
|
||||
|
||||
class _FakeCoordinator:
|
||||
"""Minimal stand-in for entity._is_included's coordinator dependency --
|
||||
same shape as test_entity.py's own fake, kept local rather than shared
|
||||
across test modules."""
|
||||
|
||||
def __init__(self, last_resources):
|
||||
self.last_resources = last_resources
|
||||
|
||||
def canonical_resources(self, subdevice):
|
||||
return self.last_resources
|
||||
|
||||
|
||||
def _resources():
|
||||
return _load_device("airconditioner_ailp_fac")
|
||||
|
||||
|
||||
def _bound():
|
||||
resources = _resources()
|
||||
reg = resolve(resources, device_types=_DEVICE_TYPES)
|
||||
assert reg is not None
|
||||
return discover(resources, reg.capabilities, reg.pattern_capabilities), resources
|
||||
|
||||
|
||||
def test_oic_device_type_resolves_to_airconditioner_registry():
|
||||
reg = for_device_by_oic_type(_DEVICE_TYPES)
|
||||
assert reg is not None and reg.name == "airconditioner"
|
||||
|
||||
|
||||
def test_board_token_resolves_to_airconditioner_registry():
|
||||
"""'FAC' is a real _BOARD_TOKEN_TO_KEY entry -- for_device_by_model
|
||||
alone (no device_types) already resolves this board correctly, same as
|
||||
for_device_by_oic_type above; resolve()'s device_types-agreement isn't
|
||||
covering an otherwise-unreachable path."""
|
||||
resources = _resources()
|
||||
info = resources["/information/vs/0"]
|
||||
reg = for_device_by_model(
|
||||
info["x.com.samsung.da.modelNum"], info["x.com.samsung.da.description"]
|
||||
)
|
||||
assert reg is not None and reg.name == "airconditioner"
|
||||
|
||||
|
||||
def test_no_unbound_hrefs():
|
||||
resources = _resources()
|
||||
reg = resolve(resources, device_types=_DEVICE_TYPES)
|
||||
assert reg is not None
|
||||
unbound = []
|
||||
discover(resources, reg.capabilities, reg.pattern_capabilities, log=unbound.append)
|
||||
assert unbound == []
|
||||
|
||||
|
||||
def test_sound_mode_registers_despite_no_live_mode_value():
|
||||
"""This dump has no current `mode` value yet, only supportedModes
|
||||
(mute/tone/voice). entity.py's default field-presence gate would
|
||||
otherwise keep the select from ever being created in HA even though
|
||||
adapter.flatten() (checked below) has no such gate and would look
|
||||
bound regardless -- exercise the real _is_included gate, not just
|
||||
flatten(), so a regression here can't hide behind that gap again."""
|
||||
bound, resources = _bound()
|
||||
entity = next(b for b in bound if b.desc.key == "sound_mode")
|
||||
assert entity.desc.options_field == "supportedModes"
|
||||
coord = cast(LocalThingsCoordinator, _FakeCoordinator(resources))
|
||||
assert _is_included(entity, coord) is True
|
||||
state = flatten(bound, resources)
|
||||
assert "sound_mode" in state
|
||||
assert state["sound_mode"] is None
|
||||
|
||||
|
||||
def test_absence_clean_and_energy_saving_bound():
|
||||
bound, resources = _bound()
|
||||
state = flatten(bound, resources)
|
||||
assert state["absence_clean"] is True
|
||||
assert state["energy_saving_mode"] == "Off_100"
|
||||
assert state["energy_saving_state"] == "Off"
|
||||
|
||||
|
||||
def test_sound_volume_self_gates_off_without_max_level():
|
||||
"""This board's /settings/sound/volume/vs/0 reports minLevel/resolution
|
||||
but no maxLevel -- air_purifier.SOUND_VOLUME's exists_fn should keep it
|
||||
unbound-but-covered rather than shipping a min=0/max=0 slider."""
|
||||
bound, resources = _bound()
|
||||
state = flatten(bound, resources)
|
||||
assert "sound_volume" not in state
|
||||
@@ -6,18 +6,40 @@ token -- this board was the one exception (its oneUiVersion self-reports
|
||||
"7.0 Air conditioner", but 'CAC' had never been added to the board-token
|
||||
table), so it silently fell back to common caps and lost its climate entity.
|
||||
|
||||
This dump is NOT fully covered yet -- eight hrefs remain unbound (edge
|
||||
lighting, a second stateful light resource, absence-clean, four
|
||||
`/settings/sound/*` resources, smart-sensing-cooling), all genuinely new to
|
||||
this board generation. That's a real device-support gap, left documented
|
||||
here rather than guessed at, per the 'don't guess' rule -- fixing the
|
||||
routing regression was the scope of #191.
|
||||
This dump is NOT fully covered yet -- two hrefs remain unbound
|
||||
(`/settings/sound/optimization/vs/0`, smart-sensing-cooling), both
|
||||
genuinely new to this board generation. That's a real device-support gap,
|
||||
left documented here rather than guessed at, per the 'don't guess' rule --
|
||||
fixing the routing regression was the scope of #191.
|
||||
|
||||
/settings/sound/mode/vs/0, /settings/sound/output/vs/0 and
|
||||
/settings/sound/volume/vs/0 used to be on this list too, until issue #319
|
||||
(a sibling TP1X_DA-AC-FAC-class board) supplied a live dump for them --
|
||||
airconditioner.SOUND_MODE and the reused air_purifier.SOUND_OUTPUT/
|
||||
SOUND_VOLUME now cover all three here as well.
|
||||
|
||||
/edgelighting/vs/0 and /light/stateful/vs/0 used to be on this list too,
|
||||
until issue #288 (six System A/C cassette units on this same board) gave
|
||||
real dump evidence for both -- airconditioner.EDGE_LIGHTING and
|
||||
LIGHT_STATEFUL now cover them.
|
||||
|
||||
/mds/absenceclean/vs/0 used to be on this list too -- its {mode,
|
||||
supportedModes: [On, Off]} shape is byte-identical to issue #319's
|
||||
/csi/absenceclean/vs/0, confirmed on that sibling board rather than
|
||||
guessed, so airconditioner.MDS_ABSENCE_CLEAN now covers it too.
|
||||
|
||||
/uvled/vs/0 and /filter/airdustPM1filter/vs/0 used to be on this list too,
|
||||
until issue #270 (TP1X_FAC_TIME_23K) added real capabilities for both --
|
||||
this board's own live filterUsage/filterStatus data on the PM1 filter binds
|
||||
through the same exists_fn-gated entities #270's dump (which has neither
|
||||
field) leaves empty.
|
||||
|
||||
/airlevelcheck/vs/0 was never on this unbound list (this fixture's rep
|
||||
already carried a full set of periodicSensing*/autoExeState fields), but
|
||||
until PR #316 it was globally ignored by airconditioner.py's own
|
||||
_AC_IGNORED as "scheduler plumbing" -- this fixture's own populated values
|
||||
were the proof that description was wrong. air_purifier.AIR_LEVEL_CHECK
|
||||
now covers it (see test_airlevelcheck_binds_real_ai_purify_state below).
|
||||
"""
|
||||
|
||||
from custom_components.localthings.registry.adapter import flatten
|
||||
@@ -27,13 +49,7 @@ from tests.conftest import _load_device
|
||||
|
||||
_STILL_UNBOUND = frozenset(
|
||||
{
|
||||
"/edgelighting/vs/0",
|
||||
"/light/stateful/vs/0",
|
||||
"/mds/absenceclean/vs/0",
|
||||
"/settings/sound/mode/vs/0",
|
||||
"/settings/sound/optimization/vs/0",
|
||||
"/settings/sound/output/vs/0",
|
||||
"/settings/sound/volume/vs/0",
|
||||
"/smartsensingcooling/vs/0",
|
||||
}
|
||||
)
|
||||
@@ -65,6 +81,32 @@ def test_documented_coverage_gap_is_exactly_this_set():
|
||||
assert set(unbound) == _STILL_UNBOUND
|
||||
|
||||
|
||||
def test_mds_absenceclean_shares_csi_absenceclean_key():
|
||||
"""/mds/absenceclean/vs/0's mode=='Off' on this dump -- confirms
|
||||
MDS_ABSENCE_CLEAN actually binds (not just that the href stops
|
||||
reporting as unbound)."""
|
||||
resources = _resources()
|
||||
reg = _reg(resources)
|
||||
bound = discover(resources, reg.capabilities, reg.pattern_capabilities)
|
||||
state = flatten(bound, resources)
|
||||
assert state["absence_clean"] is False
|
||||
|
||||
|
||||
def test_airlevelcheck_binds_real_ai_purify_state():
|
||||
"""This fixture's /airlevelcheck/vs/0 has real, populated values --
|
||||
periodic_air_sensing on, sensing_mode 'Alarm' -- confirming
|
||||
air_purifier.AIR_LEVEL_CHECK binds real AI-Purify state here rather
|
||||
than the inert plumbing _AC_IGNORED used to describe."""
|
||||
resources = _resources()
|
||||
reg = _reg(resources)
|
||||
bound = discover(resources, reg.capabilities, reg.pattern_capabilities)
|
||||
state = flatten(bound, resources)
|
||||
assert state["periodic_air_sensing"] is True
|
||||
assert state["sensing_mode"] == "Alarm"
|
||||
assert state["sensing_interval"] == 30 # 1800s
|
||||
assert state["air_sensing_state"] == "NonProcessing"
|
||||
|
||||
|
||||
def test_non_legacy_board_uses_the_generic_energy_scale():
|
||||
"""This board reports /wind/strength/vs/0 (not /airflow/vs/0), so
|
||||
is_legacy_board() is False and it must use the plain wh_to_kwh scale,
|
||||
|
||||
@@ -221,6 +221,19 @@ def test_tp1x_climate_entity_is_bound():
|
||||
assert len(climate) == 1 and climate[0].href == "/mode/vs/0"
|
||||
|
||||
|
||||
def test_tp1x_airlevelcheck_binds_real_ai_purify_state():
|
||||
"""This fixture's /airlevelcheck/vs/0 has real, populated
|
||||
periodicSensing*/autoExeState values too (PR #316's finding that
|
||||
_AC_IGNORED's old "scheduler plumbing" description was wrong wasn't
|
||||
specific to one board) -- air_purifier.AIR_LEVEL_CHECK now covers it."""
|
||||
reg, resources = _ac_tp1x()
|
||||
state = flatten(discover(resources, reg.capabilities, reg.pattern_capabilities), resources)
|
||||
assert state["periodic_air_sensing"] is True
|
||||
assert state["sensing_mode"] == "Alarm"
|
||||
assert state["sensing_interval"] == 30 # 1800s
|
||||
assert state["air_sensing_state"] == "NonProcessing"
|
||||
|
||||
|
||||
def test_tp2x_rac_20k_model_resolves_via_model_fallback():
|
||||
"""TP2X_RAC_20K (issue #37) reports no oneUiVersion and no '_PRAC_' token
|
||||
-- resolved via the '_RAC_' modelNum fallback added for this device."""
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
"""Tests for the ventilation-mode/Wind-Free/Wind-Sleep additions extracted
|
||||
from PR #316 (Samsung "System Fresh Air Ventilator", model
|
||||
ACA-KR-TP2-21-AN9000, vid DA-AC-DIFFUSER-01001).
|
||||
|
||||
No raw diagnostics dump for this model was available -- PR #316 never
|
||||
attached one, only Korean code comments describing field shapes the
|
||||
contributor said they observed. Per this project's fixture-integrity rule
|
||||
(a fixture must record what hardware actually did, not a third party's
|
||||
prose about it), there's no `airconditioner_*_device.json` fixture for
|
||||
this model here. These tests instead exercise the gating logic directly
|
||||
against hand-built reps matching those quoted shapes, clearly distinct
|
||||
from this suite's fixture-backed tests, and check the new gate doesn't
|
||||
false-positive against every real AC fixture already in the corpus.
|
||||
|
||||
If a real diagnostics dump for this model ever surfaces (tracked as a
|
||||
follow-up device-support issue), replace this file with a proper
|
||||
fixture + golden + capability test per the usual workflow, and drop the
|
||||
disclaimer above.
|
||||
"""
|
||||
|
||||
import glob
|
||||
import json
|
||||
import os
|
||||
|
||||
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.airconditioner import (
|
||||
WINDFREE,
|
||||
WINDSLEEP,
|
||||
_is_ventilation_mode_device,
|
||||
)
|
||||
from custom_components.localthings.registry.discovery import discover
|
||||
from tests.conftest import _load_device
|
||||
|
||||
_FIXTURES_DIR = os.path.join(os.path.dirname(__file__), "fixtures")
|
||||
|
||||
|
||||
def _all_airconditioner_fixture_names():
|
||||
names = []
|
||||
for path in glob.glob(os.path.join(_FIXTURES_DIR, "airconditioner*_device.json")):
|
||||
name = os.path.basename(path)[: -len("_device.json")]
|
||||
with open(path) as f:
|
||||
info = json.load(f)
|
||||
rep = next(
|
||||
(e["rep"] for e in info.get("device0", []) if e.get("href") == "/information/vs/0"),
|
||||
None,
|
||||
)
|
||||
if rep is not None: # for_device_by_model needs /information/vs/0
|
||||
names.append(name)
|
||||
return names
|
||||
|
||||
|
||||
def test_ventilation_mode_gate_never_false_positives_on_real_ac_fixtures():
|
||||
"""None of the real air-conditioner fixtures in this corpus use the
|
||||
Purification/Ventilation/SmartVentilation vocabulary -- confirms
|
||||
_is_ventilation_mode_device can't turn a real AC's climate card into
|
||||
this select."""
|
||||
for name in _all_airconditioner_fixture_names():
|
||||
resources = _load_device(name)
|
||||
reg = for_device_by_model(
|
||||
resources["/information/vs/0"]["x.com.samsung.da.modelNum"],
|
||||
resources["/information/vs/0"]["x.com.samsung.da.description"],
|
||||
)
|
||||
if reg is None or reg.name != "airconditioner":
|
||||
continue
|
||||
mode_rep = resources.get("/mode/vs/0")
|
||||
if not mode_rep:
|
||||
continue
|
||||
assert _is_ventilation_mode_device(mode_rep, resources) is False, name
|
||||
|
||||
|
||||
def test_ventilation_mode_gate_matches_diffuser_shape():
|
||||
"""PR #316: supportedModes exactly {Purification, Ventilation,
|
||||
SmartVentilation} -- the vocabulary that makes climate.py's hvac_mode
|
||||
collapse to one stuck value with no way to tell the three apart."""
|
||||
rep = {
|
||||
"x.com.samsung.da.modes": ["Purification"],
|
||||
"x.com.samsung.da.supportedModes": ["Purification", "Ventilation", "SmartVentilation"],
|
||||
}
|
||||
assert _is_ventilation_mode_device(rep, {}) is True
|
||||
|
||||
|
||||
def test_ventilation_mode_gate_rejects_partial_overlap():
|
||||
"""A real AC reporting an unrelated mode alongside one of these three
|
||||
words (coincidence, not this device) must not gate in -- the check is
|
||||
'subset of', not 'intersects'."""
|
||||
rep = {"x.com.samsung.da.supportedModes": ["Ventilation", "Cool", "Heat"]}
|
||||
assert _is_ventilation_mode_device(rep, {}) is False
|
||||
|
||||
|
||||
def _bind(capability, href, rep):
|
||||
resources = {href: rep}
|
||||
bound = discover(resources, {href: [capability]}, [])
|
||||
return flatten(bound, resources)
|
||||
|
||||
|
||||
def test_windfree_and_windsleep_read_their_own_hrefs():
|
||||
"""PR #316's quoted rep shape: {'x.com.samsung.da.windfree': 'On'/'Off',
|
||||
'x.com.samsung.da.displaycondition': 'normal'} (displaycondition is a
|
||||
read-only UI-visibility flag, deliberately not modeled)."""
|
||||
windfree_state = _bind(
|
||||
WINDFREE,
|
||||
"/modeoption/windfree/vs/0",
|
||||
{"x.com.samsung.da.windfree": "On", "x.com.samsung.da.displaycondition": "normal"},
|
||||
)
|
||||
assert windfree_state["windfree"] is True
|
||||
|
||||
windsleep_state = _bind(
|
||||
WINDSLEEP,
|
||||
"/modeoption/windsleep/vs/0",
|
||||
{"x.com.samsung.da.windsleep": "Off", "x.com.samsung.da.displaycondition": "normal"},
|
||||
)
|
||||
assert windsleep_state["windsleep"] is False
|
||||
@@ -0,0 +1,58 @@
|
||||
"""Tests for the TP2X_DA-KS-COOKTOP-000001 gas cooktop (issue #314).
|
||||
|
||||
The reporter's actual complaint (six advertised burner slots when only
|
||||
three are physically present) is expected: the registry declares a
|
||||
generous static superset of slots and gates each on the live options array
|
||||
per cooktop.py's own comment; there's no per-device signal that
|
||||
distinguishes an advertised-but-nonexistent slot from a real one, so the
|
||||
extra entities are disabled by the user, not filtered by the integration.
|
||||
This file locks in the actual gap from that issue -- /alarms/vs/0 and
|
||||
/kidslock/vs/0 -- now bound via common.ALARMS/KIDS_LOCK_VS_FALLBACK.
|
||||
"""
|
||||
|
||||
from custom_components.localthings.registry.adapter import flatten
|
||||
from custom_components.localthings.registry.by_type import resolve
|
||||
from custom_components.localthings.registry.discovery import discover
|
||||
from tests.conftest import _load_device
|
||||
|
||||
|
||||
def _bound():
|
||||
resources = _load_device("gas_cooktop_tp2x_ks")
|
||||
reg = resolve(resources)
|
||||
assert reg is not None
|
||||
return discover(resources, reg.capabilities, reg.pattern_capabilities), resources
|
||||
|
||||
|
||||
def test_resolves_to_gas_cooktop_registry():
|
||||
resources = _load_device("gas_cooktop_tp2x_ks")
|
||||
reg = resolve(resources)
|
||||
assert reg is not None and reg.name == "gas_cooktop"
|
||||
|
||||
|
||||
def test_no_unbound_hrefs():
|
||||
_, resources = _bound()
|
||||
reg = resolve(resources)
|
||||
assert reg is not None
|
||||
unbound = []
|
||||
discover(resources, reg.capabilities, reg.pattern_capabilities, log=unbound.append)
|
||||
assert unbound == []
|
||||
|
||||
|
||||
def test_alarm_code_and_child_lock_bound():
|
||||
bound, resources = _bound()
|
||||
state = flatten(bound, resources)
|
||||
assert state["alarm_code"] == "CT_E"
|
||||
# x.com.samsung.da.kidsLock == "Ready" reads as unlocked, same polarity
|
||||
# as every other board KIDS_LOCK_VS_FALLBACK covers.
|
||||
assert state["child_lock"] is True
|
||||
|
||||
|
||||
# The reporter's original complaint -- six advertised burner slots when
|
||||
# only three are physically present -- isn't tested here beyond what the
|
||||
# golden regression test already locks in (burner_0_state..burner_5_state
|
||||
# all present). It's expected, not a bug: the registry declares a generous
|
||||
# static superset of slots per cooktop.py's own comment, and this board's
|
||||
# /mode/vs/0 rep carries no field distinguishing a real slot from an
|
||||
# advertised-but-nonexistent one for a test to assert against -- the user
|
||||
# disabling the three extra entities is the correct fix, not something the
|
||||
# integration can filter automatically.
|
||||
@@ -6,12 +6,12 @@ import pytest
|
||||
GOLDEN = Path(__file__).parent / "fixtures" / "golden"
|
||||
|
||||
|
||||
def _new_state_keys(name, resources):
|
||||
def _new_state_keys(name, resources, device_types=()):
|
||||
from custom_components.localthings.registry.adapter import flatten
|
||||
from custom_components.localthings.registry.by_type import resolve
|
||||
from custom_components.localthings.registry.discovery import discover
|
||||
|
||||
reg = resolve(resources)
|
||||
reg = resolve(resources, device_types=device_types)
|
||||
if reg is None:
|
||||
from custom_components.localthings.registry.registry import CAPABILITIES
|
||||
|
||||
@@ -134,6 +134,49 @@ def test_registry_reproduces_golden_state_keys_for_airconditioner():
|
||||
)
|
||||
|
||||
|
||||
def test_registry_reproduces_golden_state_keys_for_airconditioner_ailp_fac():
|
||||
"""AILP_DA-AC-FAC-02011_0000 (issue #319) resolves both ways --
|
||||
modelNum's 'FAC' board token and /oic/d's oic.d.airconditioner type
|
||||
independently agree on the airconditioner registry. Passes
|
||||
device_types through resolve() to exercise that agreement, not because
|
||||
the board token is absent."""
|
||||
from tests.conftest import _load_device
|
||||
|
||||
resources = _load_device("airconditioner_ailp_fac")
|
||||
golden = json.loads((GOLDEN / "airconditioner_ailp_fac.json").read_text())
|
||||
state_keys = _new_state_keys(
|
||||
"airconditioner_ailp_fac", resources, device_types=("oic.wk.d", "oic.d.airconditioner")
|
||||
)
|
||||
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_oven_tp2x_ks_walloven():
|
||||
"""TP2X_DA-KS-WALLOVEN-000002 (issue #300), a steam-oven-class board
|
||||
(water tank, descale, pyro-free) quite unlike the NV7000BS-class board
|
||||
oven.py's write comments were written against. /diagnosis/vs/0 was the
|
||||
dump's only unbound href, now covered via dishwasher.DIAGNOSIS. The
|
||||
board's options[] also has no UpperLamp_ token at all -- confirms
|
||||
LAMP's new exists_fn keeps it from registering a phantom switch here,
|
||||
same fastpreheat/NaturalSteam-class gap issue #183 fixed on its
|
||||
siblings."""
|
||||
from tests.conftest import _load_device
|
||||
|
||||
resources = _load_device("oven_tp2x_ks_walloven")
|
||||
golden = json.loads((GOLDEN / "oven_tp2x_ks_walloven.json").read_text())
|
||||
state_keys = _new_state_keys(
|
||||
"oven_tp2x_ks_walloven", resources, device_types=("oic.wk.d", "oic.d.oven")
|
||||
)
|
||||
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_dehumidifier():
|
||||
"""TP1X_DA_AC_DHM_01001_0000 (issue #88, AY18CG7500GED) shares the DA_AC_
|
||||
board family with the room-AC models but carries the '_DHM_' token;
|
||||
@@ -213,6 +256,25 @@ def test_registry_reproduces_golden_state_keys_for_water_purifier_coffee():
|
||||
)
|
||||
|
||||
|
||||
def test_registry_reproduces_golden_state_keys_for_gas_cooktop_tp2x_ks():
|
||||
"""TP2X_DA-KS-COOKTOP-000001 (issue #314) -- routes via
|
||||
for_device_by_resources' DeviceType_/OperationState signature, same as
|
||||
the original cooktop fixture. /alarms/vs/0 and /kidslock/vs/0 were the
|
||||
dump's only unbound hrefs; both are common.UNIVERSAL shapes this
|
||||
registry now picks up individually (common.ALARMS,
|
||||
common.KIDS_LOCK_VS_FALLBACK)."""
|
||||
from tests.conftest import _load_device
|
||||
|
||||
resources = _load_device("gas_cooktop_tp2x_ks")
|
||||
golden = json.loads((GOLDEN / "gas_cooktop_tp2x_ks.json").read_text())
|
||||
state_keys = _new_state_keys("gas_cooktop_tp2x_ks", resources)
|
||||
assert set(state_keys) == set(golden["state_keys"]), (
|
||||
f"state_keys mismatch:\n"
|
||||
f" extra: {sorted(set(state_keys) - set(golden['state_keys']))}\n"
|
||||
f" missing: {sorted(set(golden['state_keys']) - set(state_keys))}"
|
||||
)
|
||||
|
||||
|
||||
def test_registry_reproduces_golden_state_keys_for_cooktop():
|
||||
from tests.conftest import _load_device
|
||||
|
||||
@@ -259,6 +321,24 @@ def test_registry_reproduces_golden_state_keys_for_tp1x_ref_21k_eu():
|
||||
)
|
||||
|
||||
|
||||
def test_registry_reproduces_golden_state_keys_for_tp1x_ref_21k_airfilter():
|
||||
"""TP1X_REF_21K, air-filter-equipped variant (issue #318) -- reports
|
||||
/filter/airdustfilter/vs/0 (internal deodorizing filter), the one
|
||||
unbound href on this dump. Unlike airconditioner.AIR_FILTER's
|
||||
filterUsage/filterCapacity pair, this board's filterUsage is already a
|
||||
0-100 percentage with no filterCapacity to divide by."""
|
||||
from tests.conftest import _load_device
|
||||
|
||||
resources = _load_device("refrigerator_tp1x_ref_21k_airfilter")
|
||||
golden = json.loads((GOLDEN / "refrigerator_tp1x_ref_21k_airfilter.json").read_text())
|
||||
state_keys = _new_state_keys("refrigerator_tp1x_ref_21k_airfilter", 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_range_hood():
|
||||
from tests.conftest import _load_device
|
||||
|
||||
@@ -1095,10 +1175,9 @@ def test_registry_reproduces_golden_state_keys_for_airconditioner_cac():
|
||||
0.16.0 when oneUiVersion detection was dropped, since 'CAC' had never
|
||||
been added to the modelNum board-token table. Resolved via the new 'CAC'
|
||||
token onto the existing airconditioner registry. Not fully covered yet --
|
||||
ten hrefs remain unbound (edge lighting, PM1 filter, stateful light,
|
||||
absence-clean, four sound-settings resources, smart-sensing-cooling, UV
|
||||
LED), all genuinely new to this board generation and out of scope for
|
||||
the routing fix; see test_airconditioner_cac.py for the documented gap."""
|
||||
two hrefs remain unbound (sound-optimization, smart-sensing-cooling),
|
||||
both genuinely new to this board generation and out of scope for the
|
||||
routing fix; see test_airconditioner_cac.py for the documented gap."""
|
||||
from tests.conftest import _load_device
|
||||
|
||||
resources = _load_device("airconditioner_cac")
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
"""Tests for the TP2X_DA-KS-WALLOVEN-000002 steam oven (issue #300).
|
||||
|
||||
The reporter's "not fully supported" notification was /diagnosis/vs/0,
|
||||
now covered via dishwasher.DIAGNOSIS. Their "doesn't respond to commands"
|
||||
complaint is broader: oven.py's own module docstring already documents
|
||||
setpoint/cook-time/mode writes as unproven on this local-OCF firmware, and
|
||||
this board's options[] carries no UpperLamp_ token at all -- confirming
|
||||
`lamp` was previously a phantom, write-does-nothing switch here (same gap
|
||||
issue #183 fixed for fast_preheat/natural_steam on a different board).
|
||||
"""
|
||||
|
||||
from custom_components.localthings.registry.adapter import flatten
|
||||
from custom_components.localthings.registry.by_type import for_device_by_oic_type, resolve
|
||||
from custom_components.localthings.registry.discovery import discover
|
||||
from tests.conftest import _load_device
|
||||
|
||||
_DEVICE_TYPES = ("oic.wk.d", "oic.d.oven")
|
||||
|
||||
|
||||
def _bound():
|
||||
resources = _load_device("oven_tp2x_ks_walloven")
|
||||
reg = resolve(resources, device_types=_DEVICE_TYPES)
|
||||
assert reg is not None
|
||||
return discover(resources, reg.capabilities, reg.pattern_capabilities), resources
|
||||
|
||||
|
||||
def test_oic_device_type_resolves_to_oven_registry():
|
||||
reg = for_device_by_oic_type(_DEVICE_TYPES)
|
||||
assert reg is not None and reg.name == "oven"
|
||||
|
||||
|
||||
def test_no_unbound_hrefs():
|
||||
resources = _load_device("oven_tp2x_ks_walloven")
|
||||
reg = resolve(resources, device_types=_DEVICE_TYPES)
|
||||
assert reg is not None
|
||||
unbound = []
|
||||
discover(resources, reg.capabilities, reg.pattern_capabilities, log=unbound.append)
|
||||
assert unbound == []
|
||||
|
||||
|
||||
def test_lamp_not_bound_without_an_upperlamp_token():
|
||||
"""This board's options[] has no UpperLamp_ entry -- confirms the new
|
||||
exists_fn keeps `lamp` from registering a switch that would always
|
||||
read Off and never actually do anything on write."""
|
||||
bound, resources = _bound()
|
||||
state = flatten(bound, resources)
|
||||
assert "lamp" not in state
|
||||
|
||||
|
||||
def test_sound_and_energy_saving_bound_from_real_options_tokens():
|
||||
"""Unlike lamp, this board's options[] does carry Sound_On and
|
||||
EnergySaving_On -- these switches should reflect real state."""
|
||||
bound, resources = _bound()
|
||||
state = flatten(bound, resources)
|
||||
assert state["sound"] is True
|
||||
assert state["energy_saving"] is True
|
||||
@@ -0,0 +1,35 @@
|
||||
"""Tests for the TP1X_REF_21K air-filter-equipped variant (issue #318).
|
||||
|
||||
The dump's only unbound href was /filter/airdustfilter/vs/0, this board's
|
||||
internal deodorizing filter -- same filterUsage/filterStatus field pair as
|
||||
common.WATER_FILTER, but filterUsage here is already a 0-100 percentage
|
||||
(no filterCapacity to divide by, unlike airconditioner.AIR_FILTER).
|
||||
"""
|
||||
|
||||
from custom_components.localthings.registry.adapter import flatten
|
||||
from custom_components.localthings.registry.by_type import resolve
|
||||
from custom_components.localthings.registry.discovery import discover
|
||||
from tests.conftest import _load_device
|
||||
|
||||
|
||||
def _bound():
|
||||
resources = _load_device("refrigerator_tp1x_ref_21k_airfilter")
|
||||
reg = resolve(resources)
|
||||
assert reg is not None
|
||||
return discover(resources, reg.capabilities, reg.pattern_capabilities), resources
|
||||
|
||||
|
||||
def test_no_unbound_hrefs():
|
||||
_, resources = _bound()
|
||||
unbound = []
|
||||
reg = resolve(resources)
|
||||
assert reg is not None
|
||||
discover(resources, reg.capabilities, reg.pattern_capabilities, log=unbound.append)
|
||||
assert unbound == []
|
||||
|
||||
|
||||
def test_air_filter_usage_and_status():
|
||||
bound, resources = _bound()
|
||||
state = flatten(bound, resources)
|
||||
assert state["air_filter_usage"] == 100
|
||||
assert state["air_filter_status"] == "wash"
|
||||
Reference in New Issue
Block a user