Merge pull request #334 from mbillow/claude/issue-triage-d7hz7u
Issue triage: filterUsage percentage fix, TP1X_REF_21K auto-door + winecellar, dual-cavity range routing (#330, #328, #324)
This commit is contained in:
@@ -12,5 +12,5 @@
|
||||
"pyOpenSSL>=23.0",
|
||||
"smartthings-local>=0.1.2"
|
||||
],
|
||||
"version": "0.20.0"
|
||||
"version": "0.21.0"
|
||||
}
|
||||
|
||||
@@ -225,13 +225,16 @@ _OIC_TYPE_TO_KEY: dict[str, str] = {
|
||||
"oic.d.dishwasher": "dishwasher",
|
||||
"oic.d.dryer": "dryer",
|
||||
"oic.d.oven": "oven",
|
||||
"oic.d.range": "range", # issue #324 -- oven+cooktop combo, no /information/vs/0
|
||||
"oic.d.refrigerator": "refrigerator",
|
||||
"oic.d.krefrigerator": "refrigerator", # issue #328 -- kimchi refrigerator
|
||||
"oic.d.washer": "washer",
|
||||
"x.com.st.d.airqualitysensor": "air_monitor",
|
||||
"x.com.st.d.dehumidifier": "dehumidifier",
|
||||
"x.com.st.d.hood": "range_hood", # AHD-WW-TP1-22-COMMON
|
||||
"x.com.st.d.stickcleaner": "vacuum_station",
|
||||
"x.com.st.d.steamcloset": "air_dresser",
|
||||
"x.com.st.d.winecellar": "refrigerator", # issue #328 -- same TP1X_REF_21K board
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -14,6 +14,10 @@ REGISTRY = DeviceRegistry(
|
||||
fridge.DOOR_ALERT,
|
||||
common.WATER_FILTER,
|
||||
fridge.AIR_FILTER,
|
||||
fridge.DEODOR_FILTER,
|
||||
fridge.AUTO_DOOR_TIMER,
|
||||
fridge.WINECELLAR_PANTRY_ZONE,
|
||||
fridge.WINECELLAR_INFO,
|
||||
dishwasher.DIAGNOSIS,
|
||||
fridge.ICEMAKER_NIGHTTIME,
|
||||
fridge.FLEX_ZONE,
|
||||
@@ -44,5 +48,6 @@ REGISTRY = DeviceRegistry(
|
||||
fridge.DOOR_GENERIC,
|
||||
fridge.KIMCHI_ZONE,
|
||||
fridge.KIMCHI_DOOR_GENERIC,
|
||||
fridge.AUTO_DOOR_VARIANT,
|
||||
],
|
||||
)
|
||||
|
||||
@@ -22,7 +22,7 @@ from ..entities import (
|
||||
SwitchDesc,
|
||||
)
|
||||
from . import common
|
||||
from .common import filter_usage_percent, normalize_temp_unit
|
||||
from .common import filter_usage_hours, filter_usage_percent, normalize_temp_unit
|
||||
from .laundry import option_write
|
||||
|
||||
|
||||
@@ -854,16 +854,17 @@ AIR_FILTER = Capability(
|
||||
icon="mdi:air-filter",
|
||||
entity_category="diagnostic",
|
||||
),
|
||||
# Lifetime hour counter, resets only on filter replacement.
|
||||
# Lifetime hour counter, resets only on filter replacement. Derived
|
||||
# from the percentage and filterCapacity (issue #330) -- filterUsage
|
||||
# itself is not an hour count.
|
||||
SensorDesc(
|
||||
key="air_filter_usage_hours",
|
||||
field="x.com.samsung.da.filterUsage",
|
||||
rep_fn=filter_usage_hours,
|
||||
device_class="duration",
|
||||
state_class="total_increasing",
|
||||
unit_fn=_filter_unit,
|
||||
icon="mdi:air-filter",
|
||||
entity_category="diagnostic",
|
||||
value_fn=_int,
|
||||
),
|
||||
# Locally writable alarm threshold (see _threshold_write); only
|
||||
# surfaces where supportedFilterDesiredUsage is advertised.
|
||||
@@ -925,13 +926,12 @@ AIR_FILTER_PM1 = Capability(
|
||||
),
|
||||
SensorDesc(
|
||||
key="air_filter_pm1_usage_hours",
|
||||
field="x.com.samsung.da.filterUsage",
|
||||
rep_fn=filter_usage_hours,
|
||||
device_class="duration",
|
||||
state_class="total_increasing",
|
||||
unit_fn=_filter_unit,
|
||||
icon="mdi:air-filter",
|
||||
entity_category="diagnostic",
|
||||
value_fn=_int,
|
||||
exists_fn=_has_filter_field("x.com.samsung.da.filterUsage"),
|
||||
),
|
||||
SelectDesc(
|
||||
|
||||
@@ -72,15 +72,25 @@ def epoch_to_utc(value):
|
||||
|
||||
|
||||
def filter_usage_percent(rep):
|
||||
"""Filter usage as a percentage of rated capacity. Several families
|
||||
report `filterUsage` as a raw count in `filterCapacityUnit` (e.g. 100 of
|
||||
a 500-hour capacity), so a plain value with a '%' unit would be wrong.
|
||||
Returns None when capacity is missing/zero."""
|
||||
used = _num(rep.get("x.com.samsung.da.filterUsage"))
|
||||
"""Filter usage as a percentage. `filterUsage` is already 0-100 on every
|
||||
family confirmed so far, including ARTIK051_PRAC (issue #330): its own
|
||||
fixture and three live heads all show `filterStatus == 'wash'` at
|
||||
`filterUsage == '100'` regardless of `filterCapacity` (60/224/500 across
|
||||
other families' fixtures), which only holds if `filterUsage` is already
|
||||
a percent -- dividing by capacity again would read that filter as fresh
|
||||
at 20%."""
|
||||
return int_or_none(rep.get("x.com.samsung.da.filterUsage"))
|
||||
|
||||
|
||||
def filter_usage_hours(rep):
|
||||
"""Elapsed filter hours, derived from the percentage and capacity rather
|
||||
than read off `filterUsage` directly -- `filterUsage` is a percent, not
|
||||
an hour count (issue #330). Returns None when capacity is missing/zero."""
|
||||
pct = filter_usage_percent(rep)
|
||||
cap = _num(rep.get("x.com.samsung.da.filterCapacity"))
|
||||
if used is None or not cap:
|
||||
if pct is None or not cap:
|
||||
return None
|
||||
return round(used / cap * 100)
|
||||
return round(pct / 100 * cap, 1)
|
||||
|
||||
|
||||
def normalize_temp_unit(raw, default="°F"):
|
||||
|
||||
@@ -326,9 +326,154 @@ STATUS_LOCK = Capability(
|
||||
value_fn=lambda v: v == "On",
|
||||
write_fn=_status_lock_write("x.com.samsung.da.device.sound"),
|
||||
),
|
||||
# Auto Door Open's own voice/sound feedback toggles (issue #328,
|
||||
# TP1X_REF_21K family) -- siblings of auto_door_opener above, not
|
||||
# duplicates of fridge_sound (device.sound is the general appliance
|
||||
# beep, these two gate ado's own prompts). Only seen on the
|
||||
# auto-door-equipped variants (single/kimchi/winecellar), not the
|
||||
# earlier TP1X_REF_21K dumps that predate that feature -- gated on
|
||||
# each field's own presence rather than assumed universal.
|
||||
SwitchDesc(
|
||||
key="auto_door_voice_control",
|
||||
field="x.com.samsung.da.ado.voicecontrol",
|
||||
icon="mdi:microphone",
|
||||
entity_category="config",
|
||||
value_fn=lambda v: v == "On",
|
||||
write_fn=_status_lock_write("x.com.samsung.da.ado.voicecontrol"),
|
||||
exists_fn=lambda rep, resources: "x.com.samsung.da.ado.voicecontrol" in rep,
|
||||
),
|
||||
SwitchDesc(
|
||||
key="auto_door_sound_control",
|
||||
field="x.com.samsung.da.ado.soundcontrol",
|
||||
icon="mdi:volume-medium",
|
||||
entity_category="config",
|
||||
value_fn=lambda v: v == "On",
|
||||
write_fn=_status_lock_write("x.com.samsung.da.ado.soundcontrol"),
|
||||
exists_fn=lambda rep, resources: "x.com.samsung.da.ado.soundcontrol" in rep,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
# Auto Door Open's paired delay setting (issue #328, TP1X_REF_21K family):
|
||||
# how long the door stays held open before it re-closes, on/off itself is
|
||||
# STATUS_LOCK.auto_door_opener above. Same discrete-options-select shape as
|
||||
# DEFINITE_TEMPERATURE_COOLER/FREEZER. Unit unconfirmed -- no supportedList
|
||||
# field states it and the report carried no app screenshot -- so this is a
|
||||
# raw-code select rather than a guessed seconds/minutes NumberDesc.
|
||||
|
||||
|
||||
def _auto_door_timer_write(p, rep, href=None):
|
||||
return ["autodoor", "timer", "vs", "0"], {"x.com.samsung.da.time.desired": p}
|
||||
|
||||
|
||||
AUTO_DOOR_TIMER = Capability(
|
||||
href="/autodoor/timer/vs/0",
|
||||
poll_tier="warm",
|
||||
entities=(
|
||||
SelectDesc(
|
||||
key="auto_door_timer",
|
||||
field="x.com.samsung.da.time.desired",
|
||||
icon="mdi:timer-outline",
|
||||
translation_key="auto_door_timer",
|
||||
entity_category="config",
|
||||
options_field="x.com.samsung.da.time.supportedOptions",
|
||||
write_fn=_auto_door_timer_write,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
# /autodoor/<variant>/vs/0 -- one per fridge sub-type sharing the Auto Door
|
||||
# Open feature (single-door, kimchi, winecellar seen so far; issue #328).
|
||||
# Each reports only x.com.samsung.da.ado.openOptions, declaring which open
|
||||
# styles that variant supports -- every dump seen so far carries exactly
|
||||
# one option ('Single') with no paired desired/current field to make a
|
||||
# choice against, the same "no real choice to expose yet" shape as
|
||||
# ignored.py's /mode/0. Bound with no entities to record coverage; revisit
|
||||
# if a device ever reports more than one option.
|
||||
#
|
||||
# A pattern cap rather than one entry per variant: match_fn (not just the
|
||||
# prefix) is what actually gates this, so a future variant href needs no
|
||||
# code change to stay covered, and /autodoor/timer/vs/0's own exact-href
|
||||
# AUTO_DOOR_TIMER above always wins for that href regardless (discover()
|
||||
# only falls through to pattern caps when no exact cap matched). This is
|
||||
# registry-scoped, not global -- unlike ignored.IGNORED, the unknown-
|
||||
# device-type fallback never reaches it, so the prefix caveat in
|
||||
# ignored.py's own docstring doesn't apply here.
|
||||
AUTO_DOOR_VARIANT = Capability(
|
||||
href=None,
|
||||
href_prefix="/autodoor/",
|
||||
match_fn=lambda rep, resources: "x.com.samsung.da.ado.openOptions" in rep,
|
||||
)
|
||||
|
||||
# Wine-cellar variant (x.com.st.d.winecellar, issue #328) of the same
|
||||
# deodorizing filter AIR_FILTER models -- filterUsage/filterStatus at a
|
||||
# different href, same 0-100-percentage-already shape (see AIR_FILTER's own
|
||||
# comment). filterUsage reads '-1' on the only dump seen (filterStatus
|
||||
# 'normal'), relayed as-is rather than special-cased -- no second dump to
|
||||
# confirm whether that's a real sentinel or this unit just not tracking it.
|
||||
# Own 'deodor_'-prefixed keys rather than reusing AIR_FILTER.entities
|
||||
# verbatim -- same collision AIR_FILTER's own 'air_' prefix was chosen to
|
||||
# avoid against WATER_FILTER, and both filters are plausible on one unit
|
||||
# (this device's own board reports an internal air filter on other
|
||||
# TP1X_REF_21K variants).
|
||||
DEODOR_FILTER = Capability(
|
||||
href="/filter/deodorfilter/vs/0",
|
||||
poll_tier="cold",
|
||||
entities=(
|
||||
SensorDesc(
|
||||
key="deodor_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="deodor_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,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
# Wine-cellar multi-compartment pantry select (issue #328): same
|
||||
# mode/supportedOptions shape as PANTRY_ZONE, at its own href with a 5-way
|
||||
# option set (Processed_Meat/Cheese/Nuts/Fruit/Wine) instead of PANTRY_ZONE's.
|
||||
# Only a "one" instance seen -- not generalized to a pattern cap, same
|
||||
# discipline as PANTRY_ZONE itself.
|
||||
|
||||
|
||||
def _winecellar_pantry_write(p, rep, href=None):
|
||||
return ["status", "winecellar", "pantry", "one", "vs", "0"], {"x.com.samsung.da.mode": p}
|
||||
|
||||
|
||||
WINECELLAR_PANTRY_ZONE = Capability(
|
||||
href="/status/winecellar/pantry/one/vs/0",
|
||||
poll_tier="warm",
|
||||
entities=(
|
||||
SelectDesc(
|
||||
key="winecellar_pantry_zone_mode",
|
||||
field="x.com.samsung.da.mode",
|
||||
icon="mdi:glass-wine",
|
||||
translation_key="winecellar_pantry_zone_mode",
|
||||
entity_category="config",
|
||||
options_field="x.com.samsung.da.supportedOptions",
|
||||
write_fn=_winecellar_pantry_write,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
# Wine-cellar internal table-revision marker (issue #328) -- versioning
|
||||
# metadata, not appliance state. Same treatment as ignored.py's
|
||||
# /wm/setinfo/vs/0.
|
||||
WINECELLAR_INFO = Capability(href="/information/winecellar/vs/0")
|
||||
|
||||
# /defrost/delay/vs/0 is the writable toggle to postpone a scheduled
|
||||
# defrost. /defrost/block/vs/0 is unrelated: despite the "block" naming,
|
||||
# live dumps confirm DEFROST_BLOCK_ON means the defrost cycle is actively
|
||||
|
||||
@@ -225,6 +225,9 @@
|
||||
"air_filter_threshold": {
|
||||
"name": "Práh upozornění na filtr"
|
||||
},
|
||||
"auto_door_timer": {
|
||||
"name": "Časovač automatického otevírání dvířek"
|
||||
},
|
||||
"beverage_zone_mode": {
|
||||
"name": "Režim zóny nápojů",
|
||||
"state": {
|
||||
@@ -472,9 +475,11 @@
|
||||
"kimchi_storage_crunfch": "Křupavé kimči",
|
||||
"kimchi_storage_buy": "Kupované kimči",
|
||||
"storage_fridge_normal": "Chlazení",
|
||||
"storage_fridge": "Chlazení",
|
||||
"storage_fridge_cold": "Chlazení, silné",
|
||||
"storage_fridge_warm": "Chlazení, slabé",
|
||||
"storage_freezer_normal": "Mražení",
|
||||
"storage_freezer": "Mražení",
|
||||
"storage_freezer_cold": "Mražení, silné",
|
||||
"storage_freezer_warm": "Mražení, slabé",
|
||||
"kimchi_ripe_low_temp": "Zrání kimči, nízká teplota",
|
||||
@@ -487,7 +492,8 @@
|
||||
"storage_fresh_cereal": "Obiloviny",
|
||||
"storage_fridge_drink": "Nápoje",
|
||||
"storage_fresh_wine": "Víno",
|
||||
"storage_fresh_potato_banana": "Brambory a banány"
|
||||
"storage_fresh_potato_banana": "Brambory a banány",
|
||||
"newmode_kimchi_0000": "Nový režim"
|
||||
}
|
||||
},
|
||||
"pantry_zone_mode": {
|
||||
@@ -498,6 +504,16 @@
|
||||
"fdr_drinks": "Nápoje"
|
||||
}
|
||||
},
|
||||
"winecellar_pantry_zone_mode": {
|
||||
"name": "Režim spíže",
|
||||
"state": {
|
||||
"processed_meat": "Zpracované maso",
|
||||
"cheese": "Sýr",
|
||||
"nuts": "Ořechy",
|
||||
"fruit": "Ovoce",
|
||||
"wine": "Víno"
|
||||
}
|
||||
},
|
||||
"range_burner_power_level": {
|
||||
"name": "Výkon hořáku {number}",
|
||||
"state": {
|
||||
@@ -745,6 +761,9 @@
|
||||
"air_filter_usage_hours": {
|
||||
"name": "Hodiny využití filtru"
|
||||
},
|
||||
"deodor_filter_usage": {
|
||||
"name": "Využití filtru"
|
||||
},
|
||||
"air_quality_standard": {
|
||||
"name": "Norma kvality vzduchu"
|
||||
},
|
||||
@@ -1147,6 +1166,12 @@
|
||||
"auto_door_opener": {
|
||||
"name": "Automatické otevírání dvířek"
|
||||
},
|
||||
"auto_door_sound_control": {
|
||||
"name": "Zvuk automatického otevírání dvířek"
|
||||
},
|
||||
"auto_door_voice_control": {
|
||||
"name": "Hlasové ovládání automatického otevírání dvířek"
|
||||
},
|
||||
"auto_release_dry": {
|
||||
"name": "Automatické uvolnění při sušení"
|
||||
},
|
||||
|
||||
@@ -225,6 +225,9 @@
|
||||
"air_filter_threshold": {
|
||||
"name": "Filteralarm-Schwellenwert"
|
||||
},
|
||||
"auto_door_timer": {
|
||||
"name": "Timer für automatischen Türöffner"
|
||||
},
|
||||
"beverage_zone_mode": {
|
||||
"name": "Getränkezonen-Modus",
|
||||
"state": {
|
||||
@@ -467,6 +470,16 @@
|
||||
"fdr_drinks": "Getränke"
|
||||
}
|
||||
},
|
||||
"winecellar_pantry_zone_mode": {
|
||||
"name": "Vorratszonen-Modus",
|
||||
"state": {
|
||||
"processed_meat": "Wurst/Aufschnitt",
|
||||
"cheese": "Käse",
|
||||
"nuts": "Nüsse",
|
||||
"fruit": "Obst",
|
||||
"wine": "Wein"
|
||||
}
|
||||
},
|
||||
"range_burner_power_level": {
|
||||
"name": "Brenner {number} Leistungsstufe",
|
||||
"state": {
|
||||
@@ -678,9 +691,11 @@
|
||||
"kimchi_storage_crunfch": "Knackiges Kimchi",
|
||||
"kimchi_storage_buy": "Gekauftes Kimchi",
|
||||
"storage_fridge_normal": "Kühlen",
|
||||
"storage_fridge": "Kühlen",
|
||||
"storage_fridge_cold": "Kühlen, stark",
|
||||
"storage_fridge_warm": "Kühlen, schwach",
|
||||
"storage_freezer_normal": "Gefrieren",
|
||||
"storage_freezer": "Gefrieren",
|
||||
"storage_freezer_cold": "Gefrieren, stark",
|
||||
"storage_freezer_warm": "Gefrieren, schwach",
|
||||
"kimchi_ripe_low_temp": "Kimchi-Reifung, niedrige Temperatur",
|
||||
@@ -693,7 +708,8 @@
|
||||
"storage_fresh_cereal": "Getreide",
|
||||
"storage_fridge_drink": "Getränke",
|
||||
"storage_fresh_wine": "Wein",
|
||||
"storage_fresh_potato_banana": "Kartoffeln und Bananen"
|
||||
"storage_fresh_potato_banana": "Kartoffeln und Bananen",
|
||||
"newmode_kimchi_0000": "Neuer Modus"
|
||||
}
|
||||
},
|
||||
"energy_saving_mode": {
|
||||
@@ -745,6 +761,9 @@
|
||||
"air_filter_usage_hours": {
|
||||
"name": "Filternutzungsstunden"
|
||||
},
|
||||
"deodor_filter_usage": {
|
||||
"name": "Filternutzung"
|
||||
},
|
||||
"air_quality_standard": {
|
||||
"name": "Luftqualitätsstandard"
|
||||
},
|
||||
@@ -1144,6 +1163,12 @@
|
||||
"auto_door_opener": {
|
||||
"name": "Automatischer Türöffner"
|
||||
},
|
||||
"auto_door_sound_control": {
|
||||
"name": "Ton des automatischen Türöffners"
|
||||
},
|
||||
"auto_door_voice_control": {
|
||||
"name": "Sprachsteuerung des automatischen Türöffners"
|
||||
},
|
||||
"auto_release_dry": {
|
||||
"name": "Automatische Trocknungsfreigabe"
|
||||
},
|
||||
|
||||
@@ -225,6 +225,9 @@
|
||||
"air_filter_threshold": {
|
||||
"name": "Filter alarm threshold"
|
||||
},
|
||||
"auto_door_timer": {
|
||||
"name": "Auto door open timer"
|
||||
},
|
||||
"beverage_zone_mode": {
|
||||
"name": "Beverage zone mode",
|
||||
"state": {
|
||||
@@ -474,9 +477,11 @@
|
||||
"storage_fridge_normal": "Fridge",
|
||||
"storage_fridge_cold": "Fridge, strong",
|
||||
"storage_fridge_warm": "Fridge, weak",
|
||||
"storage_fridge": "Fridge",
|
||||
"storage_freezer_normal": "Freezer",
|
||||
"storage_freezer_cold": "Freezer, strong",
|
||||
"storage_freezer_warm": "Freezer, weak",
|
||||
"storage_freezer": "Freezer",
|
||||
"kimchi_ripe_low_temp": "Kimchi ripening, low temperature",
|
||||
"kimchi_ripe_normal_temp": "Kimchi ripening, room temperature",
|
||||
"kimchi_ripe_kkakdugi": "Kkakdugi ripening",
|
||||
@@ -487,7 +492,8 @@
|
||||
"storage_fresh_cereal": "Grains",
|
||||
"storage_fridge_drink": "Beverages",
|
||||
"storage_fresh_wine": "Wine",
|
||||
"storage_fresh_potato_banana": "Potato & banana"
|
||||
"storage_fresh_potato_banana": "Potato & banana",
|
||||
"newmode_kimchi_0000": "New mode"
|
||||
}
|
||||
},
|
||||
"pantry_zone_mode": {
|
||||
@@ -498,6 +504,16 @@
|
||||
"fdr_drinks": "Drinks"
|
||||
}
|
||||
},
|
||||
"winecellar_pantry_zone_mode": {
|
||||
"name": "Pantry zone mode",
|
||||
"state": {
|
||||
"processed_meat": "Processed meat",
|
||||
"cheese": "Cheese",
|
||||
"nuts": "Nuts",
|
||||
"fruit": "Fruit",
|
||||
"wine": "Wine"
|
||||
}
|
||||
},
|
||||
"range_burner_power_level": {
|
||||
"name": "Burner {number} power level",
|
||||
"state": {
|
||||
@@ -745,6 +761,9 @@
|
||||
"air_filter_usage_hours": {
|
||||
"name": "Filter usage hours"
|
||||
},
|
||||
"deodor_filter_usage": {
|
||||
"name": "Filter usage"
|
||||
},
|
||||
"air_quality_standard": {
|
||||
"name": "Air quality standard"
|
||||
},
|
||||
@@ -1147,6 +1166,12 @@
|
||||
"auto_door_opener": {
|
||||
"name": "Auto door opener"
|
||||
},
|
||||
"auto_door_sound_control": {
|
||||
"name": "Auto door sound"
|
||||
},
|
||||
"auto_door_voice_control": {
|
||||
"name": "Auto door voice control"
|
||||
},
|
||||
"auto_release_dry": {
|
||||
"name": "Auto release dry"
|
||||
},
|
||||
|
||||
@@ -374,6 +374,9 @@
|
||||
"air_filter_threshold": {
|
||||
"name": "Umbral de alarma del filtro"
|
||||
},
|
||||
"auto_door_timer": {
|
||||
"name": "Temporizador de apertura automática de puerta"
|
||||
},
|
||||
"beverage_zone_mode": {
|
||||
"name": "Modo de zona de bebidas",
|
||||
"state": {
|
||||
@@ -621,9 +624,11 @@
|
||||
"kimchi_storage_crunfch": "Kimchi crujiente",
|
||||
"kimchi_storage_buy": "Kimchi comprado",
|
||||
"storage_fridge_normal": "Frigorífico",
|
||||
"storage_fridge": "Frigorífico",
|
||||
"storage_fridge_cold": "Frigorífico, fuerte",
|
||||
"storage_fridge_warm": "Frigorífico, suave",
|
||||
"storage_freezer_normal": "Congelador",
|
||||
"storage_freezer": "Congelador",
|
||||
"storage_freezer_cold": "Congelador, fuerte",
|
||||
"storage_freezer_warm": "Congelador, suave",
|
||||
"kimchi_ripe_low_temp": "Maduración kimchi, baja temperatura",
|
||||
@@ -636,7 +641,8 @@
|
||||
"storage_fresh_cereal": "Cereales",
|
||||
"storage_fridge_drink": "Bebidas",
|
||||
"storage_fresh_wine": "Vino",
|
||||
"storage_fresh_potato_banana": "Patata y plátano"
|
||||
"storage_fresh_potato_banana": "Patata y plátano",
|
||||
"newmode_kimchi_0000": "Modo nuevo"
|
||||
}
|
||||
},
|
||||
"pantry_zone_mode": {
|
||||
@@ -647,6 +653,16 @@
|
||||
"fdr_drinks": "Bebidas"
|
||||
}
|
||||
},
|
||||
"winecellar_pantry_zone_mode": {
|
||||
"name": "Modo de zona de despensa",
|
||||
"state": {
|
||||
"processed_meat": "Embutidos",
|
||||
"cheese": "Queso",
|
||||
"nuts": "Frutos secos",
|
||||
"fruit": "Fruta",
|
||||
"wine": "Vino"
|
||||
}
|
||||
},
|
||||
"range_burner_power_level": {
|
||||
"name": "Nivel de potencia del fuego {number}",
|
||||
"state": {
|
||||
@@ -891,6 +907,9 @@
|
||||
"air_filter_usage_hours": {
|
||||
"name": "Horas de uso del filtro"
|
||||
},
|
||||
"deodor_filter_usage": {
|
||||
"name": "Uso del filtro"
|
||||
},
|
||||
"air_quality_standard": {
|
||||
"name": "Estándar de calidad del aire"
|
||||
},
|
||||
@@ -1296,6 +1315,12 @@
|
||||
"auto_door_opener": {
|
||||
"name": "Apertura automática de puerta"
|
||||
},
|
||||
"auto_door_sound_control": {
|
||||
"name": "Sonido de apertura automática de puerta"
|
||||
},
|
||||
"auto_door_voice_control": {
|
||||
"name": "Control por voz de apertura automática de puerta"
|
||||
},
|
||||
"auto_release_dry": {
|
||||
"name": "Liberación automática de secado"
|
||||
},
|
||||
|
||||
@@ -225,6 +225,9 @@
|
||||
"air_filter_threshold": {
|
||||
"name": "Soglia allarme filtro"
|
||||
},
|
||||
"auto_door_timer": {
|
||||
"name": "Timer apertura automatica sportello"
|
||||
},
|
||||
"beverage_zone_mode": {
|
||||
"name": "Modalità zona bevande",
|
||||
"state": {
|
||||
@@ -472,9 +475,11 @@
|
||||
"kimchi_storage_crunfch": "Kimchi croccante",
|
||||
"kimchi_storage_buy": "Kimchi acquistato",
|
||||
"storage_fridge_normal": "Frigorifero",
|
||||
"storage_fridge": "Frigorifero",
|
||||
"storage_fridge_cold": "Frigorifero, forte",
|
||||
"storage_fridge_warm": "Frigorifero, debole",
|
||||
"storage_freezer_normal": "Congelatore",
|
||||
"storage_freezer": "Congelatore",
|
||||
"storage_freezer_cold": "Congelatore, forte",
|
||||
"storage_freezer_warm": "Congelatore, debole",
|
||||
"kimchi_ripe_low_temp": "Maturazione kimchi, bassa temperatura",
|
||||
@@ -487,7 +492,8 @@
|
||||
"storage_fresh_cereal": "Cereali",
|
||||
"storage_fridge_drink": "Bevande",
|
||||
"storage_fresh_wine": "Vino",
|
||||
"storage_fresh_potato_banana": "Patate e banane"
|
||||
"storage_fresh_potato_banana": "Patate e banane",
|
||||
"newmode_kimchi_0000": "Nuova modalità"
|
||||
}
|
||||
},
|
||||
"pantry_zone_mode": {
|
||||
@@ -498,6 +504,16 @@
|
||||
"fdr_drinks": "Bevande"
|
||||
}
|
||||
},
|
||||
"winecellar_pantry_zone_mode": {
|
||||
"name": "Modalità zona dispensa",
|
||||
"state": {
|
||||
"processed_meat": "Salumi",
|
||||
"cheese": "Formaggio",
|
||||
"nuts": "Frutta secca",
|
||||
"fruit": "Frutta",
|
||||
"wine": "Vino"
|
||||
}
|
||||
},
|
||||
"range_burner_power_level": {
|
||||
"name": "Livello potenza fornello {number}",
|
||||
"state": {
|
||||
@@ -745,6 +761,9 @@
|
||||
"air_filter_usage_hours": {
|
||||
"name": "Ore di utilizzo filtro"
|
||||
},
|
||||
"deodor_filter_usage": {
|
||||
"name": "Utilizzo filtro"
|
||||
},
|
||||
"air_quality_standard": {
|
||||
"name": "Standard qualità aria"
|
||||
},
|
||||
@@ -1147,6 +1166,12 @@
|
||||
"auto_door_opener": {
|
||||
"name": "Apertura automatica sportello"
|
||||
},
|
||||
"auto_door_sound_control": {
|
||||
"name": "Suono apertura automatica sportello"
|
||||
},
|
||||
"auto_door_voice_control": {
|
||||
"name": "Controllo vocale apertura automatica sportello"
|
||||
},
|
||||
"auto_release_dry": {
|
||||
"name": "Apertura automatica per asciugatura"
|
||||
},
|
||||
|
||||
@@ -225,6 +225,9 @@
|
||||
"air_filter_threshold": {
|
||||
"name": "필터 알림 기준"
|
||||
},
|
||||
"auto_door_timer": {
|
||||
"name": "오토 오픈 도어 타이머"
|
||||
},
|
||||
"beverage_zone_mode": {
|
||||
"name": "음료 보관 모드",
|
||||
"state": {
|
||||
@@ -472,9 +475,11 @@
|
||||
"kimchi_storage_crunfch": "아삭 김치",
|
||||
"kimchi_storage_buy": "구입 김치",
|
||||
"storage_fridge_normal": "냉장",
|
||||
"storage_fridge": "냉장",
|
||||
"storage_fridge_cold": "냉장 강",
|
||||
"storage_fridge_warm": "냉장 약",
|
||||
"storage_freezer_normal": "냉동",
|
||||
"storage_freezer": "냉동",
|
||||
"storage_freezer_cold": "냉동 강",
|
||||
"storage_freezer_warm": "냉동 약",
|
||||
"kimchi_ripe_low_temp": "김치 저온 숙성",
|
||||
@@ -487,7 +492,8 @@
|
||||
"storage_fresh_cereal": "곡류",
|
||||
"storage_fridge_drink": "음료",
|
||||
"storage_fresh_wine": "와인",
|
||||
"storage_fresh_potato_banana": "감자 및 바나나"
|
||||
"storage_fresh_potato_banana": "감자 및 바나나",
|
||||
"newmode_kimchi_0000": "새 모드"
|
||||
}
|
||||
},
|
||||
"pantry_zone_mode": {
|
||||
@@ -498,6 +504,16 @@
|
||||
"fdr_drinks": "음료"
|
||||
}
|
||||
},
|
||||
"winecellar_pantry_zone_mode": {
|
||||
"name": "팬트리실 모드",
|
||||
"state": {
|
||||
"processed_meat": "가공육",
|
||||
"cheese": "치즈",
|
||||
"nuts": "견과류",
|
||||
"fruit": "과일",
|
||||
"wine": "와인"
|
||||
}
|
||||
},
|
||||
"range_burner_power_level": {
|
||||
"name": "{number}번 화구 화력",
|
||||
"state": {
|
||||
@@ -745,6 +761,9 @@
|
||||
"air_filter_usage_hours": {
|
||||
"name": "필터 사용 시간"
|
||||
},
|
||||
"deodor_filter_usage": {
|
||||
"name": "필터 사용량"
|
||||
},
|
||||
"air_quality_standard": {
|
||||
"name": "공기질 기준"
|
||||
},
|
||||
@@ -1147,6 +1166,12 @@
|
||||
"auto_door_opener": {
|
||||
"name": "오토 오픈 도어"
|
||||
},
|
||||
"auto_door_sound_control": {
|
||||
"name": "오토 오픈 도어 소리"
|
||||
},
|
||||
"auto_door_voice_control": {
|
||||
"name": "오토 오픈 도어 음성 안내"
|
||||
},
|
||||
"auto_release_dry": {
|
||||
"name": "자동 문 열림 건조"
|
||||
},
|
||||
|
||||
@@ -225,6 +225,9 @@
|
||||
"air_filter_threshold": {
|
||||
"name": "Filteralarmdrempel"
|
||||
},
|
||||
"auto_door_timer": {
|
||||
"name": "Timer automatische deuropener"
|
||||
},
|
||||
"beverage_zone_mode": {
|
||||
"name": "Modus drankenzone",
|
||||
"state": {
|
||||
@@ -472,9 +475,11 @@
|
||||
"kimchi_storage_crunfch": "Knapperige kimchi",
|
||||
"kimchi_storage_buy": "Gekochte kimchi",
|
||||
"storage_fridge_normal": "Koelkast",
|
||||
"storage_fridge": "Koelkast",
|
||||
"storage_fridge_cold": "Koelkast, sterk",
|
||||
"storage_fridge_warm": "Koelkast, zwak",
|
||||
"storage_freezer_normal": "Vriezer",
|
||||
"storage_freezer": "Vriezer",
|
||||
"storage_freezer_cold": "Vriezer, sterk",
|
||||
"storage_freezer_warm": "Vriezer, zwak",
|
||||
"kimchi_ripe_low_temp": "Kimchi rijpen, lage temperatuur",
|
||||
@@ -487,7 +492,8 @@
|
||||
"storage_fresh_cereal": "Granen",
|
||||
"storage_fridge_drink": "Dranken",
|
||||
"storage_fresh_wine": "Wijn",
|
||||
"storage_fresh_potato_banana": "Aardappelen en bananen"
|
||||
"storage_fresh_potato_banana": "Aardappelen en bananen",
|
||||
"newmode_kimchi_0000": "Nieuwe modus"
|
||||
}
|
||||
},
|
||||
"pantry_zone_mode": {
|
||||
@@ -498,6 +504,16 @@
|
||||
"fdr_drinks": "Dranken"
|
||||
}
|
||||
},
|
||||
"winecellar_pantry_zone_mode": {
|
||||
"name": "Modus voorraadzone",
|
||||
"state": {
|
||||
"processed_meat": "Vleeswaren",
|
||||
"cheese": "Kaas",
|
||||
"nuts": "Noten",
|
||||
"fruit": "Fruit",
|
||||
"wine": "Wijn"
|
||||
}
|
||||
},
|
||||
"range_burner_power_level": {
|
||||
"name": "Vermogensniveau brander {number}",
|
||||
"state": {
|
||||
@@ -745,6 +761,9 @@
|
||||
"air_filter_usage_hours": {
|
||||
"name": "Filterverbruik (uren)"
|
||||
},
|
||||
"deodor_filter_usage": {
|
||||
"name": "Filterverbruik"
|
||||
},
|
||||
"air_quality_standard": {
|
||||
"name": "Luchtkwaliteitsnorm"
|
||||
},
|
||||
@@ -1147,6 +1166,12 @@
|
||||
"auto_door_opener": {
|
||||
"name": "Automatische deuropener"
|
||||
},
|
||||
"auto_door_sound_control": {
|
||||
"name": "Geluid automatische deuropener"
|
||||
},
|
||||
"auto_door_voice_control": {
|
||||
"name": "Spraakbediening automatische deuropener"
|
||||
},
|
||||
"auto_release_dry": {
|
||||
"name": "Auto Release Dry"
|
||||
},
|
||||
|
||||
+8
-1
@@ -70,7 +70,7 @@ class FakeCoapSession:
|
||||
pass
|
||||
|
||||
|
||||
def _discover_full(resources: dict[str, dict], oic_res, seeds: dict[str, list]):
|
||||
def _discover_full(resources: dict[str, dict], oic_res, seeds: dict[str, list], device_types=()):
|
||||
"""Run the *whole* subdevice-aware discovery pipeline against fixture
|
||||
data, HA-free -- mirrors exactly what LocalThingsCoordinator does across
|
||||
_enumerate_subdevices_blocking + _run_discovery (issue #177), so a test
|
||||
@@ -78,6 +78,12 @@ def _discover_full(resources: dict[str, dict], oic_res, seeds: dict[str, list]):
|
||||
it. See the adding-device-support skill's section 2 for the plain
|
||||
(non-subdevice) equivalent this extends.
|
||||
|
||||
`device_types` is the master's own /oic/d `rt` (see
|
||||
discover_partitioned's `oic_device_types` param) -- only needed for a
|
||||
board with no /information/vs/0 at all to route from (issue #324's
|
||||
range, whose modelNum-based fallback has nothing to read), so it
|
||||
defaults to () for every fixture that resolves by board token instead.
|
||||
|
||||
Returns `(bound, materialized, skipped, full_resources, device_type_name)`:
|
||||
- `bound`: every BoundEntity, main + every materialized subdevice.
|
||||
- `materialized`/`skipped`: Subdevice / SkippedSubdevice lists straight from
|
||||
@@ -101,6 +107,7 @@ def _discover_full(resources: dict[str, dict], oic_res, seeds: dict[str, list]):
|
||||
candidates,
|
||||
resolve,
|
||||
CAPABILITIES,
|
||||
oic_device_types=device_types,
|
||||
)
|
||||
return bound, materialized, skipped, full_resources, device_type_name
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"state_keys": [
|
||||
"alarm_code",
|
||||
"child_lock",
|
||||
"cloud_connected",
|
||||
"cook_time",
|
||||
"cooktop_on_alert",
|
||||
"cooktop_running_state",
|
||||
"current_temp_c",
|
||||
"cycle_active",
|
||||
"door_open",
|
||||
"energy_saving",
|
||||
"finish_time",
|
||||
"firmware_update",
|
||||
"lamp",
|
||||
"machine_state",
|
||||
"operation_time_minutes",
|
||||
"oven_mode",
|
||||
"oven_setpoint",
|
||||
"oven_state",
|
||||
"power_switch",
|
||||
"progress_percentage",
|
||||
"remote_control",
|
||||
"sound",
|
||||
"subdevice1_cloud_connected",
|
||||
"subdevice1_cook_time",
|
||||
"subdevice1_current_temp_c",
|
||||
"subdevice1_cycle_active",
|
||||
"subdevice1_finish_time",
|
||||
"subdevice1_machine_state",
|
||||
"subdevice1_operation_time_minutes",
|
||||
"subdevice1_oven_mode",
|
||||
"subdevice1_oven_setpoint",
|
||||
"subdevice1_oven_state",
|
||||
"subdevice1_progress_percentage",
|
||||
"subdevice1_sound",
|
||||
"warming_center_state"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"state_keys": [
|
||||
"ai_energy_level",
|
||||
"alarm_code",
|
||||
"auto_door_opener",
|
||||
"auto_door_timer",
|
||||
"auto_door_voice_control",
|
||||
"defrost_active",
|
||||
"door_onedoorfreezer_open",
|
||||
"energy_kwh",
|
||||
"energy_saved_kwh",
|
||||
"firmware_update",
|
||||
"freezer_setpoint",
|
||||
"freezer_temperature",
|
||||
"fridge_sound",
|
||||
"power_energy_kwh",
|
||||
"power_watts",
|
||||
"rapid_freezing",
|
||||
"rapid_fridge",
|
||||
"selfcheck_error",
|
||||
"selfcheck_result",
|
||||
"selfcheck_status"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"state_keys": [
|
||||
"alarm_code",
|
||||
"auto_door_opener",
|
||||
"auto_door_timer",
|
||||
"auto_door_voice_control",
|
||||
"door_onedoorkimchi_open",
|
||||
"energy_kwh",
|
||||
"energy_saved_kwh",
|
||||
"firmware_update",
|
||||
"fridge_sound",
|
||||
"onedoor_mode",
|
||||
"onedoor_rack_count",
|
||||
"onedoor_ripening_remaining",
|
||||
"onedoor_ripening_status",
|
||||
"power_energy_kwh",
|
||||
"power_watts",
|
||||
"selfcheck_error",
|
||||
"selfcheck_result",
|
||||
"selfcheck_status"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"state_keys": [
|
||||
"alarm_code",
|
||||
"auto_door_opener",
|
||||
"auto_door_sound_control",
|
||||
"auto_door_timer",
|
||||
"auto_door_voice_control",
|
||||
"cabinet_light_dim",
|
||||
"cabinet_light_switch",
|
||||
"deodor_filter_status",
|
||||
"deodor_filter_usage",
|
||||
"door_winecellar_open",
|
||||
"energy_kwh",
|
||||
"energy_saved_kwh",
|
||||
"firmware_update",
|
||||
"fridge_sound",
|
||||
"power_energy_kwh",
|
||||
"power_watts",
|
||||
"selfcheck_error",
|
||||
"selfcheck_result",
|
||||
"selfcheck_status",
|
||||
"winecellar_bottom_setpoint",
|
||||
"winecellar_pantry_zone_mode",
|
||||
"winecellar_top_setpoint"
|
||||
]
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,343 @@
|
||||
{
|
||||
"device0": [
|
||||
{
|
||||
"rt": [
|
||||
"x.com.samsung.devcol",
|
||||
"oic.wk.col"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.ll",
|
||||
"oic.if.b"
|
||||
]
|
||||
},
|
||||
{
|
||||
"href": "/alarms/vs/0",
|
||||
"rep": {}
|
||||
},
|
||||
{
|
||||
"href": "/temperatures/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.items": [
|
||||
{
|
||||
"x.com.samsung.da.id": "0",
|
||||
"x.com.samsung.da.description": "Freezer",
|
||||
"x.com.samsung.da.desired": "-19",
|
||||
"x.com.samsung.da.current": "-19",
|
||||
"x.com.samsung.da.maximum": "-17",
|
||||
"x.com.samsung.da.minimum": "-23",
|
||||
"x.com.samsung.da.unit": "Celsius"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/temperature/current/freezer/0",
|
||||
"rep": {
|
||||
"range": [
|
||||
-23.0,
|
||||
-17.0
|
||||
],
|
||||
"units": "C",
|
||||
"temperature": -19.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/temperature/desired/freezer/0",
|
||||
"rep": {
|
||||
"range": [
|
||||
-23.0,
|
||||
-17.0
|
||||
],
|
||||
"units": "C",
|
||||
"temperature": -19.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/selfcheck/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.status": "Ready",
|
||||
"x.com.samsung.da.result": "Success",
|
||||
"x.com.samsung.da.error": [
|
||||
"DA_ERROR_NONE"
|
||||
],
|
||||
"x.com.samsung.da.supportedActions": [
|
||||
"Start"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/energy/consumption/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.cumulativeConsumption": "41042",
|
||||
"x.com.samsung.da.cumulativePower": "466708",
|
||||
"x.com.samsung.da.cumulativeUnit": "Wh",
|
||||
"x.com.samsung.da.instantaneousPower": "4",
|
||||
"x.com.samsung.da.instantaneousPowerUnit": "W",
|
||||
"x.com.samsung.da.cumulativeSavedPower": "15759"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/mode/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"HOMECARE_WIZARD_V2",
|
||||
"ENERGY_REPORT_MODEL",
|
||||
"18K_REF_OUTDOOR_CONTROL_V2"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/mode/0",
|
||||
"rep": {
|
||||
"supportedModes": [
|
||||
"HOMECARE_WIZARD_V2",
|
||||
"ENERGY_REPORT_MODEL",
|
||||
"18K_REF_OUTDOOR_CONTROL_V2"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/realtimenotiforclient/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.timeforshortnoti": "0",
|
||||
"x.com.samsung.da.periodicnotisubscription": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/information/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.modelNum": "TP1X_REF_21K|00168541|00080023001713104100000041010000",
|
||||
"x.com.samsung.da.description": "TP1X_REF_21K",
|
||||
"x.com.samsung.da.serialNum": "**REDACTED**",
|
||||
"x.com.samsung.da.otnDUID": "**REDACTED**",
|
||||
"x.com.samsung.da.diagProtocolType": "BLE_OCF",
|
||||
"x.com.samsung.da.diagLogType": [
|
||||
"errCode",
|
||||
"dump"
|
||||
],
|
||||
"x.com.samsung.da.diagDumpType": "file",
|
||||
"x.com.samsung.da.diagEndPoint": "SSM",
|
||||
"x.com.samsung.da.diagMnid": "0AJT",
|
||||
"x.com.samsung.da.diagSetupid": "RO6",
|
||||
"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": "250422",
|
||||
"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": "24101709, 23031302, 24062000, FFFFFFFF",
|
||||
"x.com.samsung.da.newVersionAvailable": "0"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/file/information/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.timeoffset": "+09:00",
|
||||
"x.com.samsung.supprtedtype": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/doors/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.items": [
|
||||
{
|
||||
"x.com.samsung.da.id": "3",
|
||||
"x.com.samsung.da.description": "Door",
|
||||
"x.com.samsung.da.openState": "Close"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/door/onedoorfreezer/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.openState": "Close"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/configuration/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.countryCode": "",
|
||||
"x.com.samsung.da.region": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/refrigeration/0",
|
||||
"rep": {
|
||||
"defrost": false,
|
||||
"rapidFreeze": false,
|
||||
"rapidCool": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/refrigeration/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.rapidFreezing": "Off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/drlc/0",
|
||||
"rep": {
|
||||
"DRLevel": 2,
|
||||
"override": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"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-06T00:34:47Z",
|
||||
"x.com.samsung.da.realSaving": "On"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/bespoke/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.BespokeProduct": "On"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/otninformation/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.target": "",
|
||||
"x.com.samsung.da.newVersionAvailable": "false",
|
||||
"otnStatus": "None",
|
||||
"flashingProgress": "",
|
||||
"otnList": [
|
||||
{
|
||||
"type": "WIFI",
|
||||
"modelId": "A-RFWW-TP1-23-COMMON",
|
||||
"versions": [
|
||||
"20250422"
|
||||
],
|
||||
"visVersion": "250422"
|
||||
},
|
||||
{
|
||||
"type": "Micom",
|
||||
"modelId": "02800016854100168441",
|
||||
"versions": [
|
||||
"24101709",
|
||||
"23031302"
|
||||
],
|
||||
"visVersion": "241017"
|
||||
},
|
||||
{
|
||||
"type": "Micom",
|
||||
"modelId": "028070655341FFFFFFFF",
|
||||
"versions": [
|
||||
"24062000",
|
||||
"FFFFFFFF"
|
||||
],
|
||||
"visVersion": "240620"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/status/lock/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.ado.voicecontrol": "Off",
|
||||
"x.com.samsung.da.device.sound": "On"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/energy/ailevel/vs/0",
|
||||
"rep": {
|
||||
"aiLevel": "1",
|
||||
"supportedAiLevel": [
|
||||
"1",
|
||||
"2"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/autodoor/timer/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.time.desired": "1",
|
||||
"x.com.samsung.da.time.supportedOptions": [
|
||||
"1",
|
||||
"2",
|
||||
"3",
|
||||
"4",
|
||||
"5",
|
||||
"6"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/autodoor/single/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.ado.openOptions": [
|
||||
"Single"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/connectionconfig/vs/0",
|
||||
"rep": {
|
||||
"autoReconnectionMinVersion": "1.0",
|
||||
"autoReconnection": "true",
|
||||
"autoReconnectionProtocolType": [
|
||||
"ble_ocf",
|
||||
null
|
||||
],
|
||||
"supportedWiFiAuthType": [
|
||||
"OPEN",
|
||||
"WEP",
|
||||
"WPA-PSK",
|
||||
"WPA2-PSK",
|
||||
"SAE"
|
||||
],
|
||||
"supportedWiFiCryptoType": [
|
||||
"TKIP",
|
||||
"AES",
|
||||
"WEP-64",
|
||||
"WEP-128"
|
||||
],
|
||||
"supportedWiFiFreq": [
|
||||
"2.4G"
|
||||
],
|
||||
"calmConnectionCare": {
|
||||
"version": "1.0",
|
||||
"role": [
|
||||
"things"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/timezone/vs/0",
|
||||
"rep": {
|
||||
"timezoneid": "Asia/Seoul",
|
||||
"offset": "+09:00",
|
||||
"DST": "OFF"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/wirelessinfo/vs/0",
|
||||
"rep": {
|
||||
"macaddressWiFi": "**REDACTED**",
|
||||
"macaddressBLE": "**REDACTED**"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/quickcontrol/info/vs/0",
|
||||
"rep": {
|
||||
"supportedVersion": "1.0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,332 @@
|
||||
{
|
||||
"device0": [
|
||||
{
|
||||
"rt": [
|
||||
"x.com.samsung.devcol",
|
||||
"oic.wk.col"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.ll",
|
||||
"oic.if.b"
|
||||
]
|
||||
},
|
||||
{
|
||||
"href": "/alarms/vs/0",
|
||||
"rep": {}
|
||||
},
|
||||
{
|
||||
"href": "/selfcheck/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.status": "Ready",
|
||||
"x.com.samsung.da.result": "Success",
|
||||
"x.com.samsung.da.error": [
|
||||
"DA_ERROR_NONE"
|
||||
],
|
||||
"x.com.samsung.da.supportedActions": [
|
||||
"Start"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/energy/consumption/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.cumulativeConsumption": "53539",
|
||||
"x.com.samsung.da.cumulativePower": "174590",
|
||||
"x.com.samsung.da.cumulativeUnit": "Wh",
|
||||
"x.com.samsung.da.instantaneousPower": "4",
|
||||
"x.com.samsung.da.instantaneousPowerUnit": "W",
|
||||
"x.com.samsung.da.cumulativeSavedPower": "0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/mode/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"HOMECARE_WIZARD_V2",
|
||||
"ENERGY_REPORT_MODEL",
|
||||
"18K_KIMCHI_OUTDOOR_CONTROL"
|
||||
],
|
||||
"x.com.samsung.da.modes": [
|
||||
"KIMCHI_KIMCHI_STORAGE_NORMAL",
|
||||
"KIMCHI_RIPE_REMAIN_[0]:[0]",
|
||||
"KIMCHIT_BOX_COUNT_[0]",
|
||||
"KIMCHIM_BOX_COUNT_[0]",
|
||||
"KIMCHIB_BOX_COUNT_[0]",
|
||||
"KIMCHI_BOX_COUNT_[8]"
|
||||
],
|
||||
"x.com.samsung.da.supportedOptions": [
|
||||
"KIMCHI_KIMCHI_STORAGE_NORMAL_[0]:[0]",
|
||||
"KIMCHI_KIMCHI_STORAGE_COLD_[0]:[0]",
|
||||
"KIMCHI_KIMCHI_STORAGE_WARM_[0]:[0]",
|
||||
"KIMCHI_KIMCHI_STORAGE_CRUNFCH_[0]:[0]",
|
||||
"KIMCHI_KIMCHI_STORAGE_BUY_[0]:[0]",
|
||||
"KIMCHI_STORAGE_FRIDGE_[0]:[0]",
|
||||
"KIMCHI_STORAGE_FREEZER_[0]:[0]",
|
||||
"KIMCHI_KIMCHI_RIPE_NORMAL_TEMP_[2]:[12]",
|
||||
"KIMCHI_KIMCHI_RIPE_LOW_TEMP_[5]:[17]"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/mode/0",
|
||||
"rep": {
|
||||
"supportedModes": [
|
||||
"HOMECARE_WIZARD_V2",
|
||||
"ENERGY_REPORT_MODEL",
|
||||
"18K_KIMCHI_OUTDOOR_CONTROL"
|
||||
],
|
||||
"modes": [
|
||||
"KIMCHI_KIMCHI_STORAGE_NORMAL",
|
||||
"KIMCHI_RIPE_REMAIN_[0]:[0]",
|
||||
"KIMCHIT_BOX_COUNT_[0]",
|
||||
"KIMCHIM_BOX_COUNT_[0]",
|
||||
"KIMCHIB_BOX_COUNT_[0]",
|
||||
"KIMCHI_BOX_COUNT_[8]"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/realtimenotiforclient/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.timeforshortnoti": "0",
|
||||
"x.com.samsung.da.periodicnotisubscription": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/information/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.modelNum": "TP1X_REF_21K|00168041|10010022011713004101800031010000",
|
||||
"x.com.samsung.da.description": "TP1X_REF_21K",
|
||||
"x.com.samsung.da.serialNum": "**REDACTED**",
|
||||
"x.com.samsung.da.otnDUID": "**REDACTED**",
|
||||
"x.com.samsung.da.diagProtocolType": "BLE_OCF",
|
||||
"x.com.samsung.da.diagLogType": [
|
||||
"errCode",
|
||||
"dump"
|
||||
],
|
||||
"x.com.samsung.da.diagDumpType": "file",
|
||||
"x.com.samsung.da.diagEndPoint": "SSM",
|
||||
"x.com.samsung.da.diagMnid": "0AJT",
|
||||
"x.com.samsung.da.diagSetupid": "CO0",
|
||||
"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": "250422",
|
||||
"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": "24101006, 23031302, 24102200, FFFFFFFF",
|
||||
"x.com.samsung.da.newVersionAvailable": "0"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/file/information/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.timeoffset": "+00:00",
|
||||
"x.com.samsung.supprtedtype": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/doors/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.items": [
|
||||
{
|
||||
"x.com.samsung.da.id": "8",
|
||||
"x.com.samsung.da.description": "Door",
|
||||
"x.com.samsung.da.openState": "Close"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/door/onedoorkimchi/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.openState": "Close"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/configuration/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.countryCode": "",
|
||||
"x.com.samsung.da.region": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/drlc/0",
|
||||
"rep": {
|
||||
"DRLevel": 2,
|
||||
"override": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"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-06T00:34:47Z",
|
||||
"x.com.samsung.da.realSaving": "Off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/bespoke/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.BespokeProduct": "On"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/otninformation/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.target": "",
|
||||
"x.com.samsung.da.newVersionAvailable": "false",
|
||||
"otnStatus": "None",
|
||||
"flashingProgress": "",
|
||||
"otnList": [
|
||||
{
|
||||
"type": "WIFI",
|
||||
"modelId": "A-RFWW-TP1-23-COMMON",
|
||||
"versions": [
|
||||
"20250422"
|
||||
],
|
||||
"visVersion": "250422"
|
||||
},
|
||||
{
|
||||
"type": "Micom",
|
||||
"modelId": "02120016804100168441",
|
||||
"versions": [
|
||||
"24101006",
|
||||
"23031302"
|
||||
],
|
||||
"visVersion": "241010"
|
||||
},
|
||||
{
|
||||
"type": "Micom",
|
||||
"modelId": "021270659241FFFFFFFF",
|
||||
"versions": [
|
||||
"24102200",
|
||||
"FFFFFFFF"
|
||||
],
|
||||
"visVersion": "241022"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/status/lock/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.ado.voicecontrol": "Off",
|
||||
"x.com.samsung.da.device.sound": "On"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/status/kimchi/onedoor/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.currentMode": "KIMCHI_STORAGE_NORMAL",
|
||||
"x.com.samsung.da.ripeStatus": "Off",
|
||||
"x.com.samsung.da.ripeRemaintime": "0",
|
||||
"x.com.samsung.da.rackCount": "8",
|
||||
"x.com.samsung.da.ripeTotaltime": "0",
|
||||
"x.com.samsung.da.supportMode": [
|
||||
"KIMCHI_STORAGE_NORMAL",
|
||||
"KIMCHI_STORAGE_COLD",
|
||||
"KIMCHI_STORAGE_WARM",
|
||||
"KIMCHI_STORAGE_CRUNFCH",
|
||||
"KIMCHI_STORAGE_BUY",
|
||||
"STORAGE_FRIDGE",
|
||||
"STORAGE_FREEZER",
|
||||
"KIMCHI_RIPE_NORMAL_TEMP",
|
||||
"KIMCHI_RIPE_LOW_TEMP",
|
||||
"NEWMODE_KIMCHI_0000",
|
||||
"NEWMODE_KIMCHI_0000",
|
||||
"NEWMODE_KIMCHI_0000"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/autodoor/timer/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.time.desired": "1",
|
||||
"x.com.samsung.da.time.supportedOptions": [
|
||||
"1",
|
||||
"2",
|
||||
"3",
|
||||
"4",
|
||||
"5",
|
||||
"6"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/autodoor/kimchi/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.ado.openOptions": [
|
||||
"Single"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/connectionconfig/vs/0",
|
||||
"rep": {
|
||||
"autoReconnectionMinVersion": "1.0",
|
||||
"autoReconnection": "true",
|
||||
"autoReconnectionProtocolType": [
|
||||
"ble_ocf",
|
||||
null
|
||||
],
|
||||
"supportedWiFiAuthType": [
|
||||
"OPEN",
|
||||
"WEP",
|
||||
"WPA-PSK",
|
||||
"WPA2-PSK",
|
||||
"SAE"
|
||||
],
|
||||
"supportedWiFiCryptoType": [
|
||||
"TKIP",
|
||||
"AES",
|
||||
"WEP-64",
|
||||
"WEP-128"
|
||||
],
|
||||
"supportedWiFiFreq": [
|
||||
"2.4G"
|
||||
],
|
||||
"calmConnectionCare": {
|
||||
"version": "1.0",
|
||||
"role": [
|
||||
"things"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/timezone/vs/0",
|
||||
"rep": {
|
||||
"timezoneid": "Asia/Seoul",
|
||||
"offset": "+09:00",
|
||||
"DST": "OFF"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/wirelessinfo/vs/0",
|
||||
"rep": {
|
||||
"macaddressWiFi": "**REDACTED**",
|
||||
"macaddressBLE": "**REDACTED**"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/quickcontrol/info/vs/0",
|
||||
"rep": {
|
||||
"supportedVersion": "1.0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
+466
@@ -0,0 +1,466 @@
|
||||
{
|
||||
"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": "/autodoor/winecellar/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.ado.openOptions": [
|
||||
"Single"
|
||||
],
|
||||
"href": "/autodoor/winecellar/vs/0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/autodoor/timer/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.time.desired": "1",
|
||||
"x.com.samsung.da.time.supportedOptions": [
|
||||
"1",
|
||||
"2",
|
||||
"3",
|
||||
"4",
|
||||
"5",
|
||||
"6"
|
||||
],
|
||||
"href": "/autodoor/timer/vs/0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/bespoke/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.BespokeProduct": "On",
|
||||
"href": "/bespoke/vs/0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/cabinet/light/total/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.lightLevel": "100",
|
||||
"x.com.samsung.da.lightResolution": "1",
|
||||
"x.com.samsung.da.lightControl.off.include": "Off",
|
||||
"x.com.samsung.da.lightControl": "Off",
|
||||
"x.com.samsung.da.timeout.desired": "0",
|
||||
"x.com.samsung.da.timeout.supportedList": [
|
||||
"0",
|
||||
"15",
|
||||
"30",
|
||||
"60"
|
||||
],
|
||||
"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": "/door/winecellar/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.openState": "Close",
|
||||
"href": "/door/winecellar/vs/0",
|
||||
"rt": [
|
||||
"x.com.samsung.da.doorwinecellar"
|
||||
],
|
||||
"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": "9",
|
||||
"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": "1345",
|
||||
"x.com.samsung.da.start": "2026-08-06T02:08:48Z",
|
||||
"x.com.samsung.da.realSaving": "On",
|
||||
"href": "/drlc/vs/0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/energy/consumption/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.cumulativeConsumption": "19073",
|
||||
"x.com.samsung.da.instantaneousPower": "5",
|
||||
"x.com.samsung.da.cumulativePower": "210483",
|
||||
"x.com.samsung.da.cumulativeSavedPower": "15696",
|
||||
"x.com.samsung.da.cumulativeUnit": "Wh",
|
||||
"x.com.samsung.da.instantaneousPowerUnit": "W",
|
||||
"href": "/energy/consumption/vs/0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/file/information/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.timeoffset": "+00:00",
|
||||
"x.com.samsung.supprtedtype": 1,
|
||||
"href": "/file/information/vs/0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/filter/deodorfilter/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.filterUsage": "-1",
|
||||
"x.com.samsung.da.filterUsageResolution": "1",
|
||||
"x.com.samsung.da.filterResetType": [
|
||||
"replaceable"
|
||||
],
|
||||
"x.com.samsung.da.filterStatus": "normal",
|
||||
"href": "/filter/deodorfilter/vs/0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/information/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.modelNum": "TP1X_REF_21K|00146141|000B0020001513924100000011000000",
|
||||
"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": "531",
|
||||
"x.com.samsung.da.diagProtocolType": "WIFI_HTTPS",
|
||||
"x.com.samsung.da.diagMinVersion": "1.0",
|
||||
"x.com.samsung.da.items": [
|
||||
{
|
||||
"x.com.samsung.da.id": "0",
|
||||
"x.com.samsung.da.description": "WiFi Module",
|
||||
"x.com.samsung.da.type": "Software",
|
||||
"x.com.samsung.da.number": "260619",
|
||||
"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": "24102313, 2201030E",
|
||||
"x.com.samsung.da.newVersionAvailable": "0"
|
||||
}
|
||||
],
|
||||
"href": "/information/vs/0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/status/lock/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.ado.voicecontrol": "Off",
|
||||
"x.com.samsung.da.ado.soundcontrol": "On",
|
||||
"href": "/status/lock/vs/0",
|
||||
"rt": [
|
||||
"x.com.samsung.da.lockstatus"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.a"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/mode/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.modes": [
|
||||
"AIRFILTER_DISABLE"
|
||||
],
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"HOMECARE_WIZARD_V2",
|
||||
"ENERGY_REPORT_MODEL",
|
||||
"WINE_T_TOTRACK_[6]",
|
||||
"WINE_T_BOTTLE_RACK_[7]",
|
||||
"WINE_B_TOTRACK_[4]",
|
||||
"WINE_B_BOTTLE_RACK_[7]",
|
||||
"WINE_PRESENTATION_[6]",
|
||||
"WINE_BTM_BOTTLE_[20]",
|
||||
"WINE_PANTRY_BOTTLE_[5]"
|
||||
],
|
||||
"href": "/mode/vs/0",
|
||||
"rt": [
|
||||
"x.com.samsung.da.mode"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.a"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/realtimenotiforclient/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.timeforshortnoti": "0",
|
||||
"x.com.samsung.da.periodicnotisubscription": "true",
|
||||
"href": "/realtimenotiforclient/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": "/temperature/desired/winecellar/top/vs/0",
|
||||
"rep": {
|
||||
"temperature": 13.0,
|
||||
"range": [
|
||||
4.0,
|
||||
18.0
|
||||
],
|
||||
"units": "C",
|
||||
"href": "/temperature/desired/winecellar/top/vs/0",
|
||||
"rt": [
|
||||
"oic.r.temperature"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.a"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/temperature/desired/winecellar/bottom/vs/0",
|
||||
"rep": {
|
||||
"temperature": 7.0,
|
||||
"range": [
|
||||
4.0,
|
||||
18.0
|
||||
],
|
||||
"units": "C",
|
||||
"href": "/temperature/desired/winecellar/bottom/vs/0",
|
||||
"rt": [
|
||||
"oic.r.temperature"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.a"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/temperatures/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.items": [
|
||||
{
|
||||
"x.com.samsung.da.id": "2",
|
||||
"x.com.samsung.da.description": "WineCellar-top",
|
||||
"x.com.samsung.da.desired": "13",
|
||||
"x.com.samsung.da.current": "13",
|
||||
"x.com.samsung.da.maximum": "18",
|
||||
"x.com.samsung.da.minimum": "4",
|
||||
"x.com.samsung.da.unit": "Celsius"
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "3",
|
||||
"x.com.samsung.da.description": "WineCellar-bottom",
|
||||
"x.com.samsung.da.desired": "7",
|
||||
"x.com.samsung.da.current": "7",
|
||||
"x.com.samsung.da.maximum": "18",
|
||||
"x.com.samsung.da.minimum": "4",
|
||||
"x.com.samsung.da.unit": "Celsius"
|
||||
}
|
||||
],
|
||||
"href": "/temperatures/vs/0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/information/winecellar/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.tbl.revision": "1",
|
||||
"href": "/information/winecellar/vs/0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/status/winecellar/pantry/one/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.mode": "Fruit",
|
||||
"x.com.samsung.range": [
|
||||
"4",
|
||||
"13"
|
||||
],
|
||||
"x.com.samsung.da.room": "0x10",
|
||||
"x.com.samsung.da.name": "MULTI_PANTRY",
|
||||
"x.com.samsung.da.unit": "Celsius",
|
||||
"x.com.samsung.da.supportedOptions": [
|
||||
"Processed_Meat",
|
||||
"Cheese",
|
||||
"Nuts",
|
||||
"Fruit",
|
||||
"Wine"
|
||||
],
|
||||
"href": "/status/winecellar/pantry/one/vs/0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/otninformation/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.target": "",
|
||||
"x.com.samsung.da.newVersionAvailable": "false",
|
||||
"otnStatus": "None",
|
||||
"flashingProgress": "0",
|
||||
"otnCompleteDate": "noHistory",
|
||||
"scheduledTime": "None",
|
||||
"swVersionInfo": {
|
||||
"platform": "Tizen Lite",
|
||||
"oneUiVersion": "7.0 Refrigerator",
|
||||
"osVersion": "4.0"
|
||||
},
|
||||
"otnList": [
|
||||
{
|
||||
"type": "WIFI",
|
||||
"modelId": "A-RFWW-TP1-24-T4-RE2",
|
||||
"versions": [
|
||||
"20260619"
|
||||
],
|
||||
"visVersion": "260619"
|
||||
},
|
||||
{
|
||||
"type": "Micom",
|
||||
"modelId": "02900014614100146441",
|
||||
"versions": [
|
||||
"24102313",
|
||||
"2201030E"
|
||||
],
|
||||
"visVersion": "241023"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/timezone/vs/0",
|
||||
"rep": {
|
||||
"timezoneid": "Asia/Seoul",
|
||||
"offset": "+09:00",
|
||||
"DST": "OFF"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/connectionconfig/vs/0",
|
||||
"rep": {
|
||||
"autoReconnectionMinVersion": "1.0",
|
||||
"autoReconnection": "true",
|
||||
"autoReconnectionProtocolType": [
|
||||
"helper_hotspot",
|
||||
"ble_ocf"
|
||||
],
|
||||
"supportedWiFiAuthType": [
|
||||
"OPEN",
|
||||
"WEP",
|
||||
"WPA-PSK",
|
||||
"WPA2-PSK",
|
||||
"SAE"
|
||||
],
|
||||
"supportedWiFiCryptoType": [
|
||||
"TKIP",
|
||||
"AES",
|
||||
"WEP-64",
|
||||
"WEP-128"
|
||||
],
|
||||
"supportedWiFiFreq": [
|
||||
"2.4G"
|
||||
],
|
||||
"calmConnectionCare": {
|
||||
"version": "1.0",
|
||||
"role": [
|
||||
"things"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/wirelessinfo/vs/0",
|
||||
"rep": {
|
||||
"macaddressWiFi": "**REDACTED**",
|
||||
"macaddressBLE": "**REDACTED**",
|
||||
"connectedApSsid": "Home_IoT"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/quickcontrol/info/vs/0",
|
||||
"rep": {
|
||||
"supportedVersion": "1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/dginformation/vs/0",
|
||||
"rep": {
|
||||
"enrolmentstatus": "Unknown",
|
||||
"devicestate": "Unknown",
|
||||
"lockstatus": "Unknown",
|
||||
"nextduedate": "",
|
||||
"workingminutes": 0,
|
||||
"paymentinfo": {
|
||||
"emiplan": "Unknown",
|
||||
"currency": "Unknown",
|
||||
"totalemi": 0,
|
||||
"totalemipaid": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -59,5 +59,5 @@ def test_air_quality_and_filter_sensors_present():
|
||||
assert state["dust"] == 10
|
||||
assert state["fine_dust"] == 9
|
||||
assert state["odor"] == 1
|
||||
assert state["hepa_filter_usage"] == 0
|
||||
assert state["hepa_filter_usage"] == 30
|
||||
assert state["hepa_filter_status"] == "normal"
|
||||
|
||||
@@ -95,10 +95,13 @@ def test_power_and_convenient_folded_into_climate():
|
||||
assert "convenient_mode" not in state
|
||||
|
||||
|
||||
def test_air_filter_usage_is_percentage_of_capacity():
|
||||
"""filterUsage is a raw count in the capacity unit (100 of 500), surfaced as
|
||||
a percentage rather than the misleading raw value."""
|
||||
assert _state()["air_filter_usage"] == 20
|
||||
def test_air_filter_usage_is_already_a_percentage():
|
||||
"""filterUsage is already 0-100, not a raw count to divide by
|
||||
filterCapacity (issue #330): this fixture (ARTIK051_PRAC_20K) reports
|
||||
filterStatus == 'wash' at filterUsage == '100', which only holds if
|
||||
filterUsage is already a percent -- dividing by the 500 capacity again
|
||||
would read a filter due for washing as 20% fresh."""
|
||||
assert _state()["air_filter_usage"] == 100
|
||||
|
||||
|
||||
def test_climate_write_targets():
|
||||
@@ -771,19 +774,34 @@ def test_tropical_night_state_levels_across_fixtures():
|
||||
assert level("airconditioner_tp2x_rac_20k") == 16
|
||||
|
||||
|
||||
def test_air_filter_usage_hours_reads_raw_count():
|
||||
"""filterUsage is a lifetime hour counter (41 of 500) that resets on
|
||||
filter replacement -- total_increasing, not measurement. Unit comes from
|
||||
def test_air_filter_usage_hours_derived_from_percent_and_capacity():
|
||||
"""filterUsage is a percent, not an hour count (issue #330): the lifetime
|
||||
hour reading is derived from percent * capacity rather than read off
|
||||
filterUsage directly. total_increasing, not measurement; unit comes from
|
||||
filterCapacityUnit via unit_fn, not a hardcoded 'h'."""
|
||||
desc = next(
|
||||
e
|
||||
for e in airconditioner.AIR_FILTER.entities
|
||||
if e.key == "air_filter_usage_hours" and isinstance(e, SensorDesc)
|
||||
)
|
||||
assert desc.value_fn("41") == 41
|
||||
assert desc.value_fn(41) == 41
|
||||
assert desc.value_fn(None) is None
|
||||
assert desc.value_fn("not-a-number") is None
|
||||
assert desc.rep_fn is not None
|
||||
assert (
|
||||
desc.rep_fn(
|
||||
{"x.com.samsung.da.filterUsage": "91", "x.com.samsung.da.filterCapacity": "500"}
|
||||
)
|
||||
== 455.0
|
||||
)
|
||||
assert (
|
||||
desc.rep_fn(
|
||||
{
|
||||
"x.com.samsung.da.filterUsage": "not-a-number",
|
||||
"x.com.samsung.da.filterCapacity": "500",
|
||||
}
|
||||
)
|
||||
is None
|
||||
)
|
||||
assert desc.rep_fn({"x.com.samsung.da.filterCapacity": "500"}) is None
|
||||
assert desc.rep_fn({"x.com.samsung.da.filterUsage": "91"}) is None
|
||||
assert desc.device_class == "duration"
|
||||
assert desc.state_class == "total_increasing"
|
||||
assert desc.unit_fn is not None
|
||||
@@ -830,8 +848,8 @@ def test_air_filter_threshold_absent_without_supported_enum():
|
||||
reg, resources = _ac_windfree()
|
||||
state = flatten(discover(resources, reg.capabilities, reg.pattern_capabilities), resources)
|
||||
assert "air_filter_threshold" not in state
|
||||
assert state["air_filter_usage_hours"] == 41
|
||||
assert state["air_filter_usage"] == 8 # 41/500 -> 8%
|
||||
assert state["air_filter_usage"] == 41
|
||||
assert state["air_filter_usage_hours"] == 205.0 # 41% of 500
|
||||
|
||||
|
||||
def test_air_filter_threshold_binds_on_enum_board():
|
||||
|
||||
@@ -1132,12 +1132,16 @@ def test_registry_reproduces_golden_state_keys_for_airconditioner_fac_bora():
|
||||
)
|
||||
|
||||
|
||||
def _new_subdevice_aware_state_keys(name):
|
||||
def _new_subdevice_aware_state_keys(name, device_types=()):
|
||||
"""Like _new_state_keys, but runs the full subdevice-aware pipeline
|
||||
(enumerate_subdevices + discover_partitioned, issue #177) instead of a
|
||||
single discover() call, so the golden for a composite-device fixture
|
||||
captures every materialized subdevice's keys
|
||||
(subdevice1_-/subdevice_<uuid>_-prefixed), not just the master's."""
|
||||
(subdevice1_-/subdevice_<uuid>_-prefixed), not just the master's.
|
||||
|
||||
`device_types` threads through to discover_partitioned's own
|
||||
`oic_device_types` -- needed for a board with no /information/vs/0 at
|
||||
all to route from (issue #324's range)."""
|
||||
from custom_components.localthings.registry.adapter import flatten
|
||||
from tests.conftest import _discover_full, _load_device_full
|
||||
|
||||
@@ -1146,6 +1150,7 @@ def _new_subdevice_aware_state_keys(name):
|
||||
resources,
|
||||
oic_res,
|
||||
seeds,
|
||||
device_types,
|
||||
)
|
||||
state = flatten(bound, full_resources)
|
||||
return sorted(state.keys())
|
||||
@@ -1407,6 +1412,78 @@ def test_registry_reproduces_golden_state_keys_for_refrigerator_tp1x_ref_21k_def
|
||||
)
|
||||
|
||||
|
||||
def test_registry_reproduces_golden_state_keys_for_refrigerator_tp1x_ref_21k_autodoor():
|
||||
"""TP1X_REF_21K, single-freezer-door Auto Door Open variant (issue
|
||||
#328): /autodoor/single/vs/0 + /autodoor/timer/vs/0, plus
|
||||
STATUS_LOCK's ado.voicecontrol sibling toggle."""
|
||||
from tests.conftest import _load_device
|
||||
|
||||
resources = _load_device("refrigerator_tp1x_ref_21k_autodoor")
|
||||
golden = json.loads((GOLDEN / "refrigerator_tp1x_ref_21k_autodoor.json").read_text())
|
||||
state_keys = _new_state_keys("refrigerator_tp1x_ref_21k_autodoor", 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_refrigerator_tp1x_ref_21k_kimchi():
|
||||
"""Kimchi-refrigerator Auto Door Open variant (issue #328) -- resolves
|
||||
via /oic/d's oic.d.krefrigerator, not modelNum (the board is the same
|
||||
TP1X_REF_21K as the regular fridge, so only the OCF type tells them
|
||||
apart before checking hrefs)."""
|
||||
from tests.conftest import _load_device
|
||||
|
||||
resources = _load_device("refrigerator_tp1x_ref_21k_kimchi")
|
||||
golden = json.loads((GOLDEN / "refrigerator_tp1x_ref_21k_kimchi.json").read_text())
|
||||
state_keys = _new_state_keys(
|
||||
"refrigerator_tp1x_ref_21k_kimchi",
|
||||
resources,
|
||||
device_types=("oic.wk.d", "oic.d.krefrigerator"),
|
||||
)
|
||||
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_refrigerator_winecellar():
|
||||
"""Wine-cellar refrigerator variant (issue #328) -- resolves via /oic/d's
|
||||
x.com.st.d.winecellar, same TP1X_REF_21K board as the other two. Adds
|
||||
the deodorizing filter at its own href, the multi-compartment pantry
|
||||
select, and the table-revision info resource."""
|
||||
from tests.conftest import _load_device
|
||||
|
||||
resources = _load_device("refrigerator_winecellar")
|
||||
golden = json.loads((GOLDEN / "refrigerator_winecellar.json").read_text())
|
||||
state_keys = _new_state_keys(
|
||||
"refrigerator_winecellar",
|
||||
resources,
|
||||
device_types=("oic.wk.d", "x.com.st.d.winecellar"),
|
||||
)
|
||||
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_tp1x_da_ks_range_0101x():
|
||||
"""Dual-cavity range (issue #324): no /information/vs/0 at all, so
|
||||
routing depends entirely on /oic/d's oic.d.range -- and the second
|
||||
cavity is a genuine Pattern A indexed subdevice at /device/1."""
|
||||
name = "range_tp1x_da_ks_range_0101x"
|
||||
golden = json.loads((GOLDEN / f"{name}.json").read_text())
|
||||
state_keys = _new_subdevice_aware_state_keys(name, device_types=("oic.wk.d", "oic.d.range"))
|
||||
assert set(state_keys) == set(golden["state_keys"]), (
|
||||
f"state_keys mismatch:\n"
|
||||
f" extra: {sorted(set(state_keys) - set(golden['state_keys']))}\n"
|
||||
f" missing: {sorted(set(golden['state_keys']) - set(state_keys))}"
|
||||
)
|
||||
|
||||
|
||||
def test_resources_from_batch_preferred_over_flat():
|
||||
from tests.conftest import _resources_from_dump
|
||||
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
"""Dual-cavity range (TP1X_DA-KS-RANGE-0101X, NE63T8751SG/AA-class, issue
|
||||
#324): no /information/vs/0 at all, so the modelNum-based fallback has
|
||||
nothing to read -- routing depends entirely on /oic/d's own device type
|
||||
(`oic.d.range`), read separately from the /device/0 batch (see
|
||||
registry/identity.py and by_type/__init__.py's `_OIC_TYPE_TO_KEY`).
|
||||
|
||||
The second oven cavity answers as an indexed sibling at /device/1 (Pattern
|
||||
A, same mechanism as the AC family's issue #177 fixtures) -- the master's
|
||||
/mode/vs/0 (defaultMode 'UpperConvectionBake') is the upper cavity, the
|
||||
subdevice's canonical /mode/vs/0 (defaultMode 'LowerConvectionBake') is the
|
||||
lower one.
|
||||
"""
|
||||
|
||||
from custom_components.localthings.registry.adapter import flatten
|
||||
from custom_components.localthings.registry.by_type import for_device_by_oic_type, resolve
|
||||
from tests.conftest import _discover_full, _load_device_full
|
||||
|
||||
FIXTURE = "range_tp1x_da_ks_range_0101x"
|
||||
DEVICE_TYPES = ("oic.wk.d", "oic.d.range")
|
||||
|
||||
|
||||
def _discover():
|
||||
resources, oic_res, seeds = _load_device_full(FIXTURE)
|
||||
return _discover_full(resources, oic_res, seeds, DEVICE_TYPES)
|
||||
|
||||
|
||||
def test_resolves_via_oic_type():
|
||||
reg = for_device_by_oic_type(DEVICE_TYPES)
|
||||
assert reg is not None and reg.name == "range"
|
||||
|
||||
|
||||
def test_resolves_via_the_full_resolve_entrypoint():
|
||||
resources, _oic_res, _seeds = _load_device_full(FIXTURE)
|
||||
reg = resolve(resources, device_types=DEVICE_TYPES)
|
||||
assert reg is not None and reg.name == "range"
|
||||
|
||||
|
||||
def test_no_unbound_hrefs():
|
||||
"""Every resource in the issue #324 dump binds or is ignored, on both
|
||||
the master (upper cavity) and the materialized second cavity -- clears
|
||||
the coverage-gap repair."""
|
||||
resources, oic_res, seeds = _load_device_full(FIXTURE)
|
||||
unbound = []
|
||||
from custom_components.localthings.registry.registry import CAPABILITIES
|
||||
from custom_components.localthings.registry.subdevices import (
|
||||
discover_partitioned,
|
||||
enumerate_subdevices,
|
||||
)
|
||||
from tests.conftest import FakeCoapSession
|
||||
|
||||
sess = FakeCoapSession(seeds)
|
||||
candidates, extra = enumerate_subdevices(sess, resources, oic_res)
|
||||
full_resources = {**resources, **extra}
|
||||
discover_partitioned(
|
||||
full_resources,
|
||||
candidates,
|
||||
resolve,
|
||||
CAPABILITIES,
|
||||
log=unbound.append,
|
||||
oic_device_types=DEVICE_TYPES,
|
||||
)
|
||||
assert unbound == []
|
||||
|
||||
|
||||
def test_second_cavity_materializes_as_an_indexed_subdevice():
|
||||
_bound, materialized, skipped, _full_resources, device_type_name = _discover()
|
||||
assert device_type_name == "range"
|
||||
assert skipped == []
|
||||
assert [(s.kind, s.key) for s in materialized] == [("indexed", "1")]
|
||||
assert materialized[0].seed_path == ("device", "1")
|
||||
|
||||
|
||||
def test_both_cavities_expose_distinct_oven_state():
|
||||
bound, _materialized, _skipped, full_resources, _name = _discover()
|
||||
state = flatten(bound, full_resources)
|
||||
for key in ("oven_mode", "oven_setpoint", "oven_state", "machine_state", "door_open"):
|
||||
assert key in state, key
|
||||
# Second cavity's entities carry the subdevice1_ prefix (adapter._key) --
|
||||
# the master's own keys stay unprefixed, so both cavities get distinct
|
||||
# unique_ids rather than colliding on the same entity key.
|
||||
assert "subdevice1_oven_mode" in state
|
||||
assert "subdevice1_oven_setpoint" in state
|
||||
|
||||
|
||||
def test_cooktop_monitoring_present_on_the_master_only():
|
||||
"""The cooktop half belongs to the appliance as a whole, not either oven
|
||||
cavity -- /cooktopmonitoring/vs/0 has no per-cavity index."""
|
||||
bound, _materialized, _skipped, full_resources, _name = _discover()
|
||||
state = flatten(bound, full_resources)
|
||||
assert "cooktop_running_state" in state
|
||||
assert "warming_center_state" in state
|
||||
assert "subdevice1_cooktop_running_state" not in state
|
||||
@@ -0,0 +1,80 @@
|
||||
"""Auto Door Open (issue #328, TP1X_REF_21K single-freezer-door variant).
|
||||
|
||||
`/status/lock/vs/0`'s ado.devicecontrol switch was already modeled
|
||||
(STATUS_LOCK.auto_door_opener); this dump adds the paired voice-feedback
|
||||
toggle, the door-hold timer, and the per-variant capability declaration
|
||||
href -- see fridge.py's AUTO_DOOR_TIMER/AUTO_DOOR_VARIANT comments.
|
||||
"""
|
||||
|
||||
from custom_components.localthings.registry.adapter import flatten
|
||||
from custom_components.localthings.registry.by_type import for_device_by_model
|
||||
from custom_components.localthings.registry.capabilities import fridge
|
||||
from custom_components.localthings.registry.discovery import discover
|
||||
from custom_components.localthings.registry.entities import SelectDesc
|
||||
from tests.conftest import _load_device
|
||||
|
||||
FIXTURE = "refrigerator_tp1x_ref_21k_autodoor"
|
||||
|
||||
|
||||
def _fridge():
|
||||
resources = _load_device(FIXTURE)
|
||||
info = resources["/information/vs/0"]
|
||||
reg = for_device_by_model(
|
||||
info["x.com.samsung.da.modelNum"], info["x.com.samsung.da.description"]
|
||||
)
|
||||
return reg, resources
|
||||
|
||||
|
||||
def _state():
|
||||
reg, resources = _fridge()
|
||||
bound = discover(resources, reg.capabilities, reg.pattern_capabilities)
|
||||
return flatten(bound, resources)
|
||||
|
||||
|
||||
def test_resolves_to_refrigerator_registry():
|
||||
reg, _ = _fridge()
|
||||
assert reg is not None and reg.name == "refrigerator"
|
||||
|
||||
|
||||
def test_no_unbound_hrefs():
|
||||
reg, resources = _fridge()
|
||||
unbound = []
|
||||
discover(resources, reg.capabilities, reg.pattern_capabilities, log=unbound.append)
|
||||
assert unbound == []
|
||||
|
||||
|
||||
def test_auto_door_voice_control_reads_status_lock():
|
||||
state = _state()
|
||||
assert state["auto_door_opener"] is False
|
||||
assert state["auto_door_voice_control"] is False
|
||||
# This variant's dump has no ado.soundcontrol field -- gated absent.
|
||||
assert "auto_door_sound_control" not in state
|
||||
|
||||
|
||||
def test_auto_door_timer_reads_its_own_options():
|
||||
desc = next(
|
||||
e
|
||||
for e in fridge.AUTO_DOOR_TIMER.entities
|
||||
if e.key == "auto_door_timer" and isinstance(e, SelectDesc)
|
||||
)
|
||||
rep = {
|
||||
"x.com.samsung.da.time.desired": "1",
|
||||
"x.com.samsung.da.time.supportedOptions": ["1", "2", "3", "4", "5", "6"],
|
||||
}
|
||||
assert desc.value_fn(rep["x.com.samsung.da.time.desired"]) == "1"
|
||||
assert desc.options_field == "x.com.samsung.da.time.supportedOptions"
|
||||
assert desc.write_fn is not None
|
||||
assert desc.write_fn("3", rep) == (
|
||||
["autodoor", "timer", "vs", "0"],
|
||||
{"x.com.samsung.da.time.desired": "3"},
|
||||
)
|
||||
|
||||
|
||||
def test_auto_door_single_variant_href_bound_with_no_entities():
|
||||
"""/autodoor/single/vs/0 only ever declares openOptions=['Single'] --
|
||||
no paired current/desired field to expose, so it's coverage-only via
|
||||
the shared AUTO_DOOR_VARIANT pattern cap, not an entry of its own."""
|
||||
assert fridge.AUTO_DOOR_VARIANT.entities == ()
|
||||
assert fridge.AUTO_DOOR_VARIANT.match_fn is not None
|
||||
_reg, resources = _fridge()
|
||||
assert fridge.AUTO_DOOR_VARIANT.match_fn(resources["/autodoor/single/vs/0"], resources)
|
||||
@@ -0,0 +1,57 @@
|
||||
"""Kimchi-refrigerator Auto Door Open variant (issue #328,
|
||||
oic.d.krefrigerator device type -- TP1X_REF_21K board, single kimchi
|
||||
compartment). Same STATUS_LOCK/AUTO_DOOR_TIMER shape as the regular
|
||||
TP1X_REF_21K, at the kimchi-specific /autodoor/kimchi/vs/0 declaration
|
||||
href.
|
||||
"""
|
||||
|
||||
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.capabilities import fridge
|
||||
from custom_components.localthings.registry.discovery import discover
|
||||
from tests.conftest import _load_device
|
||||
|
||||
FIXTURE = "refrigerator_tp1x_ref_21k_kimchi"
|
||||
DEVICE_TYPES = ("oic.wk.d", "oic.d.krefrigerator")
|
||||
|
||||
|
||||
def _fridge():
|
||||
resources = _load_device(FIXTURE)
|
||||
reg = resolve(resources, device_types=DEVICE_TYPES)
|
||||
return reg, resources
|
||||
|
||||
|
||||
def _state():
|
||||
reg, resources = _fridge()
|
||||
bound = discover(resources, reg.capabilities, reg.pattern_capabilities)
|
||||
return flatten(bound, resources)
|
||||
|
||||
|
||||
def test_resolves_via_oic_type():
|
||||
reg = for_device_by_oic_type(DEVICE_TYPES)
|
||||
assert reg is not None and reg.name == "refrigerator"
|
||||
|
||||
|
||||
def test_resolves_to_refrigerator_registry():
|
||||
reg, _ = _fridge()
|
||||
assert reg is not None and reg.name == "refrigerator"
|
||||
|
||||
|
||||
def test_no_unbound_hrefs():
|
||||
reg, resources = _fridge()
|
||||
unbound = []
|
||||
discover(resources, reg.capabilities, reg.pattern_capabilities, log=unbound.append)
|
||||
assert unbound == []
|
||||
|
||||
|
||||
def test_auto_door_opener_present():
|
||||
state = _state()
|
||||
assert "auto_door_opener" in state
|
||||
assert "auto_door_timer" in state
|
||||
|
||||
|
||||
def test_auto_door_kimchi_variant_href_bound_with_no_entities():
|
||||
assert fridge.AUTO_DOOR_VARIANT.entities == ()
|
||||
assert fridge.AUTO_DOOR_VARIANT.match_fn is not None
|
||||
_reg, resources = _fridge()
|
||||
assert fridge.AUTO_DOOR_VARIANT.match_fn(resources["/autodoor/kimchi/vs/0"], resources)
|
||||
@@ -0,0 +1,90 @@
|
||||
"""Wine-cellar refrigerator variant (issue #328, x.com.st.d.winecellar
|
||||
device type -- RW33C99B1TFG-class, TP1X_REF_21K board). Auto Door Open's
|
||||
winecellar-specific declaration href, a deodorizing filter reported at a
|
||||
different href than the regular AIR_FILTER, a multi-compartment pantry
|
||||
select, and a table-revision info resource -- all new coverage this dump
|
||||
surfaced.
|
||||
"""
|
||||
|
||||
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.capabilities import fridge
|
||||
from custom_components.localthings.registry.discovery import discover
|
||||
from custom_components.localthings.registry.entities import SelectDesc
|
||||
from tests.conftest import _load_device
|
||||
|
||||
FIXTURE = "refrigerator_winecellar"
|
||||
DEVICE_TYPES = ("oic.wk.d", "x.com.st.d.winecellar")
|
||||
|
||||
|
||||
def _fridge():
|
||||
resources = _load_device(FIXTURE)
|
||||
reg = resolve(resources, device_types=DEVICE_TYPES)
|
||||
return reg, resources
|
||||
|
||||
|
||||
def _state():
|
||||
reg, resources = _fridge()
|
||||
bound = discover(resources, reg.capabilities, reg.pattern_capabilities)
|
||||
return flatten(bound, resources)
|
||||
|
||||
|
||||
def test_resolves_via_oic_type():
|
||||
reg = for_device_by_oic_type(DEVICE_TYPES)
|
||||
assert reg is not None and reg.name == "refrigerator"
|
||||
|
||||
|
||||
def test_no_unbound_hrefs():
|
||||
reg, resources = _fridge()
|
||||
unbound = []
|
||||
discover(resources, reg.capabilities, reg.pattern_capabilities, log=unbound.append)
|
||||
assert unbound == []
|
||||
|
||||
|
||||
def test_auto_door_sound_control_present_here_only():
|
||||
"""This variant's dump is the only one that carries
|
||||
ado.soundcontrol -- the other TP1X_REF_21K auto-door fixtures don't."""
|
||||
state = _state()
|
||||
assert state["auto_door_sound_control"] is True
|
||||
assert state["auto_door_voice_control"] is False
|
||||
|
||||
|
||||
def test_deodor_filter_has_its_own_keys_not_air_filters():
|
||||
"""Same shape/reasoning as fridge.AIR_FILTER (percentage-not-raw-count,
|
||||
see its own comment) but its own 'deodor_'-prefixed keys, not a shared
|
||||
tuple -- AIR_FILTER's own docstring picked 'air_' specifically to avoid
|
||||
colliding with WATER_FILTER's filter_usage/filter_status, and reusing
|
||||
AIR_FILTER.entities verbatim here would silently recreate that same
|
||||
collision if a unit ever reports both hrefs."""
|
||||
assert fridge.DEODOR_FILTER.entities != fridge.AIR_FILTER.entities
|
||||
state = _state()
|
||||
assert state["deodor_filter_status"] == "normal"
|
||||
# The device's own sentinel, relayed as-is -- not a real 0-100 reading
|
||||
# on this unit, and no second dump to say what else -1 could mean.
|
||||
assert state["deodor_filter_usage"] == -1
|
||||
assert "air_filter_usage" not in state
|
||||
assert "air_filter_status" not in state
|
||||
|
||||
|
||||
def test_winecellar_pantry_zone_mode_options_and_write():
|
||||
desc = next(
|
||||
e
|
||||
for e in fridge.WINECELLAR_PANTRY_ZONE.entities
|
||||
if e.key == "winecellar_pantry_zone_mode" and isinstance(e, SelectDesc)
|
||||
)
|
||||
assert desc.options_field == "x.com.samsung.da.supportedOptions"
|
||||
assert desc.write_fn is not None
|
||||
assert desc.write_fn("Cheese", {}) == (
|
||||
["status", "winecellar", "pantry", "one", "vs", "0"],
|
||||
{"x.com.samsung.da.mode": "Cheese"},
|
||||
)
|
||||
state = _state()
|
||||
assert state["winecellar_pantry_zone_mode"] == "Fruit"
|
||||
|
||||
|
||||
def test_winecellar_variant_and_info_hrefs_bound_with_no_entities():
|
||||
assert fridge.AUTO_DOOR_VARIANT.entities == ()
|
||||
assert fridge.WINECELLAR_INFO.entities == ()
|
||||
assert fridge.AUTO_DOOR_VARIANT.match_fn is not None
|
||||
_reg, resources = _fridge()
|
||||
assert fridge.AUTO_DOOR_VARIANT.match_fn(resources["/autodoor/winecellar/vs/0"], resources)
|
||||
Reference in New Issue
Block a user