Add kimchi-refrigerator compartment coverage (issue #26)
TP2X_REF_20K-class 3-compartment kimchi refrigerators report each compartment's storage mode and ripening status/timer on /status/kimchi/<slot>/vs/0, plus a top-compartment door sensor on /kimchidoors/top/vs/0 -- all previously unbound. Bind them as pattern capabilities (fridge.KIMCHI_ZONE, fridge.KIMCHI_DOOR_GENERIC), deriving the per-compartment entity key and display name from the href's top/middle/bottom segment, the same way DOOR_GENERIC/TEMP_CURRENT_GENERIC already do. Storage-mode option labels were translated directly from the reporter's own SmartThings app screenshots rather than guessed from the raw device codes or their English paraphrase, confirming the on-screen option order matches supportMode's array order (including the freezer triplet's -19/-21/-17°C -> Standard/Strong/Weak mapping). Also tighten FLEX_ZONE's exists_fn: this device's /mode/vs/0 also populates modes/supportedOptions, but with a token shape that never overlaps (a "_[n]:[n]" suffix supportedOptions carries that modes never repeats), so the existing "supportedOptions is nonempty" check let the entity bind anyway and get stuck permanently on "unknown". Requiring an actual resolvable value keeps it working for the RF9000/Bespoke-class fridges it was built for while leaving it absent here.
This commit is contained in:
@@ -36,5 +36,7 @@ REGISTRY = DeviceRegistry(
|
||||
fridge.TEMP_SETPOINT,
|
||||
fridge.ICEMAKER_GENERIC,
|
||||
fridge.DOOR_GENERIC,
|
||||
fridge.KIMCHI_ZONE,
|
||||
fridge.KIMCHI_DOOR_GENERIC,
|
||||
],
|
||||
)
|
||||
|
||||
@@ -590,8 +590,17 @@ FLEX_ZONE = Capability(
|
||||
translation_key='flex_zone_mode',
|
||||
entity_category='config',
|
||||
options_field='x.com.samsung.da.supportedOptions',
|
||||
exists_fn=lambda rep, resources: bool(
|
||||
rep.get('x.com.samsung.da.supportedOptions')),
|
||||
# A nonempty supportedOptions alone isn't sufficient: the
|
||||
# kimchi-refrigerator family (issue #26) also populates
|
||||
# /mode/vs/0's modes/supportedOptions with real data, but
|
||||
# its tokens carry a "_[n]:[n]" parameter suffix on
|
||||
# supportedOptions that modes never repeats, so no item
|
||||
# ever overlaps -- the RF9000/Bespoke-class overlap this
|
||||
# capability was built for never happens there. Require an
|
||||
# actual resolvable value instead of just a populated
|
||||
# list, so this stays absent on that family rather than
|
||||
# showing a select permanently stuck on "unknown".
|
||||
exists_fn=lambda rep, resources: _flex_zone_current(rep) is not None,
|
||||
rep_fn=_flex_zone_current,
|
||||
write_fn=_flex_zone_write),
|
||||
),
|
||||
@@ -625,6 +634,99 @@ DOOR_GENERIC = Capability(
|
||||
),
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Kimchi refrigerator compartments (TP2X_REF_20K-class 3-compartment kimchi
|
||||
# units, issue #26) -- top/middle/bottom each report their own storage mode
|
||||
# plus a ripening status/timer on /status/kimchi/<slot>/vs/0, all three in
|
||||
# an identical shape; modeled as a pattern capability the same way
|
||||
# DOOR_GENERIC/TEMP_CURRENT_GENERIC above are, deriving the per-compartment
|
||||
# key and {instance_name} from the href's top/middle/bottom segment. Only
|
||||
# the top compartment's door has been seen reported separately (kimchidoors);
|
||||
# middle/bottom apparently have no contact switch of their own, so that's
|
||||
# its own narrower pattern cap rather than assumed universal.
|
||||
#
|
||||
# The same state is also mirrored -- packed into single tokens like
|
||||
# "KIMCHIT_KIMCHI_STORAGE_NORMAL" (T/M/B prefix per compartment) with
|
||||
# bracketed parameters -- on /mode/vs/0, the same resource FLEX_ZONE reads
|
||||
# for RF9000-class fridges. /status/kimchi/<slot>/vs/0's plain currentMode/
|
||||
# supportMode fields are unpacked and self-describing, so that's what this
|
||||
# binds to instead.
|
||||
#
|
||||
# Write path is unconfirmed (no live write against a real unit) -- same
|
||||
# "write the same field back to the entity's own href" convention as
|
||||
# PANTRY_ZONE/BEVERAGE_ZONE above, first real-world write is also the test.
|
||||
#
|
||||
# translations/en.json's kimchi_zone_mode state labels were translated
|
||||
# directly from the reporter's own (Korean-language) SmartThings app
|
||||
# screenshots, not guessed from the codes or from their English paraphrase.
|
||||
# Cross-checking the screenshots against supportMode confirms the on-screen
|
||||
# option order matches the array order everywhere it's verifiable: the top
|
||||
# compartment's freezer triplet (표준/강냉/약냉 = Standard/Strong/Weak, at
|
||||
# -19/-21/-17°C) lines up 1:1 with STORAGE_FREEZER_NORMAL/COLD/WARM, and the
|
||||
# middle/bottom compartments' full 8-entry kimchi-storage list, 2-entry
|
||||
# ripening list, and 4-entry custom-storage list each line up 1:1 with their
|
||||
# supportMode order too -- so COLD/WARM consistently means Strong/Weak (a
|
||||
# colder or warmer preset around the NORMAL setpoint) everywhere that suffix
|
||||
# appears, including on STORAGE_FRIDGE_* and the low-salt kimchi variants,
|
||||
# which weren't directly screenshotted but share the same NORMAL/COLD/WARM
|
||||
# vocabulary as the two confirmed triplets. CRUNFCH (아삭, "crisp/crunchy")
|
||||
# and BUY (구입, "purchased") are also confirmed exact matches, not
|
||||
# abbreviation guesses.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _kimchi_mode_write(p, rep, href=None):
|
||||
if not href:
|
||||
return None
|
||||
return [s for s in href.strip('/').split('/') if s], {
|
||||
'x.com.samsung.da.currentMode': p,
|
||||
}
|
||||
|
||||
|
||||
KIMCHI_ZONE = Capability(
|
||||
href=None,
|
||||
href_prefix='/status/kimchi/',
|
||||
strip_prefix_in_key=True,
|
||||
poll_tier='warm',
|
||||
entities=(
|
||||
SelectDesc(key='mode', field='x.com.samsung.da.currentMode',
|
||||
use_instance_name=True, icon='mdi:fridge-outline',
|
||||
translation_key='kimchi_zone_mode',
|
||||
entity_category='config',
|
||||
options_field='x.com.samsung.da.supportMode',
|
||||
write_fn=_kimchi_mode_write),
|
||||
SensorDesc(key='ripening_status', field='x.com.samsung.da.ripeStatus',
|
||||
use_instance_name=True, icon='mdi:progress-clock',
|
||||
translation_key='kimchi_ripening_status',
|
||||
entity_category='diagnostic',
|
||||
value_fn=lambda v: v.lower() if isinstance(v, str) else v),
|
||||
SensorDesc(key='ripening_remaining', field='x.com.samsung.da.ripeRemaintime',
|
||||
use_instance_name=True, icon='mdi:timer-sand',
|
||||
translation_key='kimchi_ripening_remaining',
|
||||
entity_category='diagnostic',
|
||||
# No dump has this nonzero (ripeStatus is always "Off" so
|
||||
# far) -- device-reported unit unconfirmed, so this stays
|
||||
# a bare number rather than asserting minutes or hours.
|
||||
value_fn=_int),
|
||||
SensorDesc(key='rack_count', field='x.com.samsung.da.rackCount',
|
||||
use_instance_name=True, icon='mdi:tray-full',
|
||||
translation_key='kimchi_rack_count',
|
||||
entity_category='diagnostic', enabled_default=False,
|
||||
value_fn=_int),
|
||||
),
|
||||
)
|
||||
|
||||
KIMCHI_DOOR_GENERIC = Capability(
|
||||
href=None,
|
||||
href_prefix='/kimchidoors/',
|
||||
strip_prefix_in_key=True,
|
||||
poll_tier='hot',
|
||||
entities=(
|
||||
BinarySensorDesc(key='open', rep_fn=_door_open_state,
|
||||
translation_key='instance_open',
|
||||
use_instance_name=True, device_class='door'),
|
||||
),
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Aggregate-resource fallbacks
|
||||
#
|
||||
|
||||
@@ -381,6 +381,37 @@
|
||||
"operating_mode": {
|
||||
"name": "Operating mode"
|
||||
},
|
||||
"kimchi_zone_mode": {
|
||||
"name": "{instance_name} storage mode",
|
||||
"state": {
|
||||
"off": "Off",
|
||||
"kimchi_storage_normal": "Kimchi",
|
||||
"kimchi_storage_cold": "Kimchi, strong",
|
||||
"kimchi_storage_warm": "Kimchi, weak",
|
||||
"kimchi_storage_low_salt_normal": "Low-salt kimchi",
|
||||
"kimchi_storage_low_salt_cold": "Low-salt kimchi, strong",
|
||||
"kimchi_storage_low_salt_warm": "Low-salt kimchi, weak",
|
||||
"kimchi_storage_crunfch": "Crisp kimchi",
|
||||
"kimchi_storage_buy": "Purchased kimchi",
|
||||
"storage_fridge_normal": "Fridge",
|
||||
"storage_fridge_cold": "Fridge, strong",
|
||||
"storage_fridge_warm": "Fridge, weak",
|
||||
"storage_freezer_normal": "Freezer",
|
||||
"storage_freezer_cold": "Freezer, strong",
|
||||
"storage_freezer_warm": "Freezer, weak",
|
||||
"kimchi_ripe_low_temp": "Kimchi ripening, low temperature",
|
||||
"kimchi_ripe_normal_temp": "Kimchi ripening, room temperature",
|
||||
"kimchi_ripe_kkakdugi": "Kkakdugi ripening",
|
||||
"kimchi_ripe_dongchimi": "Dongchimi ripening",
|
||||
"meat_ripe_normal": "Meat ripening",
|
||||
"storage_meat": "Meat & fish",
|
||||
"storage_fridge_vegetables_fruit": "Fruit & vegetables",
|
||||
"storage_fresh_cereal": "Grains",
|
||||
"storage_fridge_drink": "Beverages",
|
||||
"storage_fresh_wine": "Wine",
|
||||
"storage_fresh_potato_banana": "Potato & banana"
|
||||
}
|
||||
},
|
||||
"pantry_zone_mode": {
|
||||
"name": "Pantry zone mode",
|
||||
"state": {
|
||||
@@ -678,6 +709,15 @@
|
||||
"fridge_temperature": {
|
||||
"name": "Fridge temperature"
|
||||
},
|
||||
"kimchi_ripening_status": {
|
||||
"name": "{instance_name} ripening status"
|
||||
},
|
||||
"kimchi_ripening_remaining": {
|
||||
"name": "{instance_name} ripening time remaining"
|
||||
},
|
||||
"kimchi_rack_count": {
|
||||
"name": "{instance_name} rack count"
|
||||
},
|
||||
"hood_filter_capacity": {
|
||||
"name": "Filter capacity"
|
||||
},
|
||||
|
||||
@@ -381,6 +381,37 @@
|
||||
"operating_mode": {
|
||||
"name": "Bedrijfsmodus"
|
||||
},
|
||||
"kimchi_zone_mode": {
|
||||
"name": "{instance_name} opslagmodus",
|
||||
"state": {
|
||||
"off": "Uit",
|
||||
"kimchi_storage_normal": "Kimchi",
|
||||
"kimchi_storage_cold": "Kimchi, sterk",
|
||||
"kimchi_storage_warm": "Kimchi, zwak",
|
||||
"kimchi_storage_low_salt_normal": "Kimchi met weinig zout",
|
||||
"kimchi_storage_low_salt_cold": "Kimchi met weinig zout, sterk",
|
||||
"kimchi_storage_low_salt_warm": "Kimchi met weinig zout, zwak",
|
||||
"kimchi_storage_crunfch": "Knapperige kimchi",
|
||||
"kimchi_storage_buy": "Gekochte kimchi",
|
||||
"storage_fridge_normal": "Koelkast",
|
||||
"storage_fridge_cold": "Koelkast, sterk",
|
||||
"storage_fridge_warm": "Koelkast, zwak",
|
||||
"storage_freezer_normal": "Vriezer",
|
||||
"storage_freezer_cold": "Vriezer, sterk",
|
||||
"storage_freezer_warm": "Vriezer, zwak",
|
||||
"kimchi_ripe_low_temp": "Kimchi rijpen, lage temperatuur",
|
||||
"kimchi_ripe_normal_temp": "Kimchi rijpen, kamertemperatuur",
|
||||
"kimchi_ripe_kkakdugi": "Kkakdugi rijpen",
|
||||
"kimchi_ripe_dongchimi": "Dongchimi rijpen",
|
||||
"meat_ripe_normal": "Vlees rijpen",
|
||||
"storage_meat": "Vlees en vis",
|
||||
"storage_fridge_vegetables_fruit": "Groenten en fruit",
|
||||
"storage_fresh_cereal": "Granen",
|
||||
"storage_fridge_drink": "Dranken",
|
||||
"storage_fresh_wine": "Wijn",
|
||||
"storage_fresh_potato_banana": "Aardappelen en bananen"
|
||||
}
|
||||
},
|
||||
"pantry_zone_mode": {
|
||||
"name": "Modus voorraadzone",
|
||||
"state": {
|
||||
@@ -678,6 +709,15 @@
|
||||
"fridge_temperature": {
|
||||
"name": "Koelkasttemperatuur"
|
||||
},
|
||||
"kimchi_ripening_status": {
|
||||
"name": "{instance_name} rijpingsstatus"
|
||||
},
|
||||
"kimchi_ripening_remaining": {
|
||||
"name": "{instance_name} resterende rijptijd"
|
||||
},
|
||||
"kimchi_rack_count": {
|
||||
"name": "{instance_name} aantal rekken"
|
||||
},
|
||||
"hood_filter_capacity": {
|
||||
"name": "Filtercapaciteit"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"state_keys": [
|
||||
"alarm_code",
|
||||
"bottom_mode",
|
||||
"bottom_rack_count",
|
||||
"bottom_ripening_remaining",
|
||||
"bottom_ripening_status",
|
||||
"diagnosis_status",
|
||||
"door_open",
|
||||
"energy_kwh",
|
||||
"firmware_update",
|
||||
"middle_mode",
|
||||
"middle_rack_count",
|
||||
"middle_ripening_remaining",
|
||||
"middle_ripening_status",
|
||||
"power_energy_kwh",
|
||||
"power_watts",
|
||||
"top_mode",
|
||||
"top_open",
|
||||
"top_rack_count",
|
||||
"top_ripening_remaining",
|
||||
"top_ripening_status"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,274 @@
|
||||
{
|
||||
"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": "/diagnosis/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.diagnosisStart": "Ready"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/energy/consumption/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.cumulativeConsumption": "5",
|
||||
"x.com.samsung.da.cumulativePower": "1733313",
|
||||
"x.com.samsung.da.cumulativeUnit": "Wh",
|
||||
"x.com.samsung.da.instantaneousPower": "61",
|
||||
"x.com.samsung.da.instantaneousPowerUnit": "W"
|
||||
}
|
||||
},
|
||||
{
|
||||
"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": [
|
||||
"KIMCHIT_STORAGE_FREEZER_NORMAL",
|
||||
"KIMCHIT_RIPE_REMAIN_[0]:[0]",
|
||||
"KIMCHIM_KIMCHI_STORAGE_NORMAL",
|
||||
"KIMCHIM_RIPE_REMAIN_[0]:[0]",
|
||||
"KIMCHIB_KIMCHI_STORAGE_NORMAL",
|
||||
"KIMCHIB_RIPE_REMAIN_[0]:[0]",
|
||||
"KIMCHIT_BOX_COUNT_[6]",
|
||||
"KIMCHIM_BOX_COUNT_[4]",
|
||||
"KIMCHIB_BOX_COUNT_[2]",
|
||||
"KIMCHI_BOX_COUNT_[0]"
|
||||
],
|
||||
"x.com.samsung.da.supportedOptions": [
|
||||
"KIMCHIT_KIMCHI_STORAGE_NORMAL_[0]:[0]",
|
||||
"KIMCHIT_KIMCHI_STORAGE_COLD_[0]:[0]",
|
||||
"KIMCHIT_KIMCHI_STORAGE_WARM_[0]:[0]",
|
||||
"KIMCHIT_KIMCHI_STORAGE_LOW_SALT_NORMAL_[0]:[0]",
|
||||
"KIMCHIT_KIMCHI_STORAGE_LOW_SALT_COLD_[0]:[0]",
|
||||
"KIMCHIT_KIMCHI_STORAGE_LOW_SALT_WARM_[0]:[0]",
|
||||
"KIMCHIT_STORAGE_FRIDGE_NORMAL_[0]:[0]",
|
||||
"KIMCHIT_STORAGE_FRIDGE_COLD_[0]:[0]",
|
||||
"KIMCHIT_STORAGE_FRIDGE_WARM_[0]:[0]",
|
||||
"KIMCHIT_STORAGE_FREEZER_NORMAL_[0]:[0]",
|
||||
"KIMCHIT_STORAGE_FREEZER_COLD_[0]:[0]",
|
||||
"KIMCHIT_STORAGE_FREEZER_WARM_[0]:[0]",
|
||||
"KIMCHIT_OFF_[0]:[0]",
|
||||
"KIMCHIT_KIMCHI_RIPE_LOW_TEMP_[5]:[17]",
|
||||
"KIMCHIT_KIMCHI_RIPE_NORMAL_TEMP_[2]:[12]",
|
||||
"KIMCHIM_KIMCHI_STORAGE_NORMAL_[0]:[0]",
|
||||
"KIMCHIM_KIMCHI_STORAGE_COLD_[0]:[0]",
|
||||
"KIMCHIM_KIMCHI_STORAGE_WARM_[0]:[0]",
|
||||
"KIMCHIM_KIMCHI_STORAGE_LOW_SALT_NORMAL_[0]:[0]",
|
||||
"KIMCHIM_KIMCHI_STORAGE_LOW_SALT_COLD_[0]:[0]",
|
||||
"KIMCHIM_KIMCHI_STORAGE_LOW_SALT_WARM_[0]:[0]",
|
||||
"KIMCHIM_KIMCHI_STORAGE_CRUNFCH_[0]:[0]",
|
||||
"KIMCHIM_KIMCHI_STORAGE_BUY_[0]:[0]",
|
||||
"KIMCHIM_MEAT_RIPE_NORMAL_[3]:[0]",
|
||||
"KIMCHIM_STORAGE_MEAT_[0]:[0]",
|
||||
"KIMCHIM_STORAGE_FRIDGE_VEGETABLES_FRUIT_[0]:[0]",
|
||||
"KIMCHIM_STORAGE_FRESH_CEREAL_[0]:[0]",
|
||||
"KIMCHIM_OFF_[0]:[0]",
|
||||
"KIMCHIM_KIMCHI_RIPE_LOW_TEMP_[5]:[17]",
|
||||
"KIMCHIM_KIMCHI_RIPE_NORMAL_TEMP_[2]:[12]",
|
||||
"KIMCHIB_KIMCHI_STORAGE_NORMAL_[0]:[0]",
|
||||
"KIMCHIB_KIMCHI_STORAGE_COLD_[0]:[0]",
|
||||
"KIMCHIB_KIMCHI_STORAGE_WARM_[0]:[0]",
|
||||
"KIMCHIB_KIMCHI_STORAGE_LOW_SALT_NORMAL_[0]:[0]",
|
||||
"KIMCHIB_KIMCHI_STORAGE_LOW_SALT_COLD_[0]:[0]",
|
||||
"KIMCHIB_KIMCHI_STORAGE_LOW_SALT_WARM_[0]:[0]",
|
||||
"KIMCHIB_KIMCHI_STORAGE_CRUNFCH_[0]:[0]",
|
||||
"KIMCHIB_KIMCHI_STORAGE_BUY_[0]:[0]",
|
||||
"KIMCHIB_STORAGE_FRIDGE_VEGETABLES_FRUIT_[0]:[0]",
|
||||
"KIMCHIB_STORAGE_FRIDGE_DRINK_[0]:[0]",
|
||||
"KIMCHIB_STORAGE_FRESH_WINE_[0]:[0]",
|
||||
"KIMCHIB_STORAGE_FRESH_POTATO_BANANA_[0]:[0]",
|
||||
"KIMCHIB_OFF_[0]:[0]",
|
||||
"KIMCHIB_KIMCHI_RIPE_KKAKDUGI_[4]:[5]",
|
||||
"KIMCHIB_KIMCHI_RIPE_DONGCHIMI_[7]:[6]"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/mode/0",
|
||||
"rep": {
|
||||
"supportedModes": [
|
||||
"HOMECARE_WIZARD_V2",
|
||||
"ENERGY_REPORT_MODEL",
|
||||
"18K_KIMCHI_OUTDOOR_CONTROL"
|
||||
],
|
||||
"modes": [
|
||||
"KIMCHIT_STORAGE_FREEZER_NORMAL",
|
||||
"KIMCHIT_RIPE_REMAIN_[0]:[0]",
|
||||
"KIMCHIM_KIMCHI_STORAGE_NORMAL",
|
||||
"KIMCHIM_RIPE_REMAIN_[0]:[0]",
|
||||
"KIMCHIB_KIMCHI_STORAGE_NORMAL",
|
||||
"KIMCHIB_RIPE_REMAIN_[0]:[0]",
|
||||
"KIMCHIT_BOX_COUNT_[6]",
|
||||
"KIMCHIM_BOX_COUNT_[4]",
|
||||
"KIMCHIB_BOX_COUNT_[2]",
|
||||
"KIMCHI_BOX_COUNT_[0]"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/realtimenotiforclient/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.timeforshortnoti": "0",
|
||||
"x.com.samsung.da.periodicnotisubscription": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/information/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.modelNum": "TP2X_REF_20K|00135941|10010221011411010103642021000000",
|
||||
"x.com.samsung.da.description": "TP2X_REF_20K",
|
||||
"x.com.samsung.da.serialNum": "**REDACTED**",
|
||||
"x.com.samsung.da.otnDUID": "**REDACTED**",
|
||||
"x.com.samsung.da.items": [
|
||||
{
|
||||
"x.com.samsung.da.id": "0",
|
||||
"x.com.samsung.da.description": "WiFi Module",
|
||||
"x.com.samsung.da.type": "Software",
|
||||
"x.com.samsung.da.number": "02144A220110",
|
||||
"x.com.samsung.da.newVersionAvailable": "0"
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "1",
|
||||
"x.com.samsung.da.description": "Micom",
|
||||
"x.com.samsung.da.type": "Firmware",
|
||||
"x.com.samsung.da.number": "20082109,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": "4",
|
||||
"x.com.samsung.da.description": "Door",
|
||||
"x.com.samsung.da.openState": "Close"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/kimchidoors/top/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": "/bespoke/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.BespokeProduct": "On"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/otninformation/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.target": "Micom",
|
||||
"x.com.samsung.da.newVersionAvailable": "false"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/status/kimchi/top/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.currentMode": "STORAGE_FREEZER_NORMAL",
|
||||
"x.com.samsung.da.ripeStatus": "Off",
|
||||
"x.com.samsung.da.ripeRemaintime": "0",
|
||||
"x.com.samsung.da.rackCount": "6",
|
||||
"x.com.samsung.da.supportMode": [
|
||||
"KIMCHI_STORAGE_NORMAL",
|
||||
"KIMCHI_STORAGE_COLD",
|
||||
"KIMCHI_STORAGE_WARM",
|
||||
"KIMCHI_STORAGE_LOW_SALT_NORMAL",
|
||||
"KIMCHI_STORAGE_LOW_SALT_COLD",
|
||||
"KIMCHI_STORAGE_LOW_SALT_WARM",
|
||||
"STORAGE_FRIDGE_NORMAL",
|
||||
"STORAGE_FRIDGE_COLD",
|
||||
"STORAGE_FRIDGE_WARM",
|
||||
"STORAGE_FREEZER_NORMAL",
|
||||
"STORAGE_FREEZER_COLD",
|
||||
"STORAGE_FREEZER_WARM",
|
||||
"OFF",
|
||||
"KIMCHI_RIPE_LOW_TEMP",
|
||||
"KIMCHI_RIPE_NORMAL_TEMP"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/status/kimchi/middle/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": "4",
|
||||
"x.com.samsung.da.supportMode": [
|
||||
"KIMCHI_STORAGE_NORMAL",
|
||||
"KIMCHI_STORAGE_COLD",
|
||||
"KIMCHI_STORAGE_WARM",
|
||||
"KIMCHI_STORAGE_LOW_SALT_NORMAL",
|
||||
"KIMCHI_STORAGE_LOW_SALT_COLD",
|
||||
"KIMCHI_STORAGE_LOW_SALT_WARM",
|
||||
"KIMCHI_STORAGE_CRUNFCH",
|
||||
"KIMCHI_STORAGE_BUY",
|
||||
"MEAT_RIPE_NORMAL",
|
||||
"STORAGE_MEAT",
|
||||
"STORAGE_FRIDGE_VEGETABLES_FRUIT",
|
||||
"STORAGE_FRESH_CEREAL",
|
||||
"OFF",
|
||||
"KIMCHI_RIPE_LOW_TEMP",
|
||||
"KIMCHI_RIPE_NORMAL_TEMP"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/status/kimchi/bottom/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": "2",
|
||||
"x.com.samsung.da.supportMode": [
|
||||
"KIMCHI_STORAGE_NORMAL",
|
||||
"KIMCHI_STORAGE_COLD",
|
||||
"KIMCHI_STORAGE_WARM",
|
||||
"KIMCHI_STORAGE_LOW_SALT_NORMAL",
|
||||
"KIMCHI_STORAGE_LOW_SALT_COLD",
|
||||
"KIMCHI_STORAGE_LOW_SALT_WARM",
|
||||
"KIMCHI_STORAGE_CRUNFCH",
|
||||
"KIMCHI_STORAGE_BUY",
|
||||
"STORAGE_FRIDGE_VEGETABLES_FRUIT",
|
||||
"STORAGE_FRIDGE_DRINK",
|
||||
"STORAGE_FRESH_WINE",
|
||||
"STORAGE_FRESH_POTATO_BANANA",
|
||||
"OFF",
|
||||
"KIMCHI_RIPE_KKAKDUGI",
|
||||
"KIMCHI_RIPE_DONGCHIMI"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -163,6 +163,26 @@ class TestFlexZone:
|
||||
assert 'CV_FDR_MEAT' not in modes
|
||||
assert 'CVN_CONVERTIBLE_ZONE' in modes and 'WATERFILTER_ENABLE' in modes
|
||||
|
||||
def test_exists_only_when_a_current_value_resolves(self):
|
||||
"""Issue #26's kimchi-refrigerator family also populates /mode/vs/0's
|
||||
modes/supportedOptions, but its supportedOptions tokens carry a
|
||||
"_[n]:[n]" suffix modes never repeats, so no item ever overlaps --
|
||||
the entity used to bind anyway (supportedOptions is nonempty) and
|
||||
get stuck on "unknown" forever."""
|
||||
no_overlap_rep = {
|
||||
'x.com.samsung.da.modes': ['KIMCHIT_STORAGE_FREEZER_NORMAL'],
|
||||
'x.com.samsung.da.supportedOptions': [
|
||||
'KIMCHIT_STORAGE_FREEZER_NORMAL_[0]:[0]'],
|
||||
}
|
||||
desc = fridge.FLEX_ZONE.entities[0]
|
||||
assert desc.exists_fn(no_overlap_rep, {}) is False
|
||||
|
||||
overlap_rep = {
|
||||
'x.com.samsung.da.modes': ['CV_FDR_MEAT'],
|
||||
'x.com.samsung.da.supportedOptions': ['CV_FDR_WINE', 'CV_FDR_MEAT'],
|
||||
}
|
||||
assert desc.exists_fn(overlap_rep, {}) is True
|
||||
|
||||
|
||||
class TestTp1xNativeDuplicateResources:
|
||||
"""The US TP1X_REF_21K publishes two native mirrors in addition to the
|
||||
@@ -210,6 +230,37 @@ class TestPantryZone:
|
||||
assert body == {'x.com.samsung.da.mode': 'FDR_WINE'}
|
||||
|
||||
|
||||
class TestKimchiZone:
|
||||
"""Kimchi-refrigerator compartments (TP2X_REF_20K-class, issue #26) --
|
||||
top/middle/bottom each report an identically-shaped currentMode/
|
||||
supportMode resource under /status/kimchi/<slot>/vs/0."""
|
||||
|
||||
def test_href_prefix(self):
|
||||
assert fridge.KIMCHI_ZONE.href_prefix == '/status/kimchi/'
|
||||
assert fridge.KIMCHI_DOOR_GENERIC.href_prefix == '/kimchidoors/'
|
||||
|
||||
def test_write_derives_path_from_href(self):
|
||||
desc = fridge.KIMCHI_ZONE.entities[0]
|
||||
path, body = desc.write_fn(
|
||||
'KIMCHI_STORAGE_COLD', {}, href='/status/kimchi/middle/vs/0')
|
||||
assert path == ['status', 'kimchi', 'middle', 'vs', '0']
|
||||
assert body == {'x.com.samsung.da.currentMode': 'KIMCHI_STORAGE_COLD'}
|
||||
|
||||
def test_write_without_href_is_rejected(self):
|
||||
desc = fridge.KIMCHI_ZONE.entities[0]
|
||||
assert desc.write_fn('KIMCHI_STORAGE_COLD', {}) is None
|
||||
|
||||
def test_ripening_status_is_lowercased(self):
|
||||
desc = next(e for e in fridge.KIMCHI_ZONE.entities if e.key == 'ripening_status')
|
||||
assert desc.value_fn('Off') == 'off'
|
||||
assert desc.value_fn(None) is None
|
||||
|
||||
def test_door_reuses_open_state_helper(self):
|
||||
desc = fridge.KIMCHI_DOOR_GENERIC.entities[0]
|
||||
assert desc.rep_fn({'x.com.samsung.da.openState': 'Open'}) is True
|
||||
assert desc.rep_fn({'x.com.samsung.da.openState': 'Close'}) is False
|
||||
|
||||
|
||||
class TestArtik051AndTp2xFixturesHaveCompleteCoverage:
|
||||
"""issue #20 (ARTIK051_REF_17K) and #26 (TP2X_REF_20K) both triggered
|
||||
the incomplete-capability-coverage repair; both must resolve to zero
|
||||
@@ -253,6 +304,33 @@ class TestArtik051AndTp2xFixturesHaveCompleteCoverage:
|
||||
state = flatten(bound, resources)
|
||||
assert state['flex_zone_mode'] == 'CV_FDR_BEVERAGE'
|
||||
|
||||
def test_tp2x_ref_20k_kimchi(self):
|
||||
"""A different physical unit sharing the same modelNum string (issue
|
||||
#26, second reporter) -- a 3-compartment kimchi refrigerator instead
|
||||
of the flex-zone fridge above. /kimchidoors/top/vs/0 and
|
||||
/status/kimchi/{top,middle,bottom}/vs/0 were previously unbound."""
|
||||
from custom_components.localthings.registry.adapter import flatten
|
||||
from custom_components.localthings.registry.by_type import refrigerator
|
||||
from custom_components.localthings.registry.discovery import discover
|
||||
from tests.conftest import _load_device
|
||||
|
||||
resources = _load_device('refrigerator_tp2x_ref_20k_kimchi')
|
||||
unbound = []
|
||||
bound = discover(
|
||||
resources,
|
||||
refrigerator.REGISTRY.capabilities,
|
||||
refrigerator.REGISTRY.pattern_capabilities,
|
||||
log=unbound.append,
|
||||
)
|
||||
assert unbound == []
|
||||
state = flatten(bound, resources)
|
||||
assert state['top_mode'] == 'STORAGE_FREEZER_NORMAL'
|
||||
assert state['middle_mode'] == 'KIMCHI_STORAGE_NORMAL'
|
||||
assert state['bottom_mode'] == 'KIMCHI_STORAGE_NORMAL'
|
||||
assert state['top_open'] is False
|
||||
assert 'middle_open' not in state # no /kimchidoors/middle/vs/0 reported
|
||||
assert 'flex_zone_mode' not in state # no resolvable overlap on this family
|
||||
|
||||
|
||||
class TestArtik051DongleRefFixtureCoverage:
|
||||
"""Issues #77/#83: the ARTIK051_DONGLE_REF standalone freezer resolves
|
||||
|
||||
@@ -300,6 +300,23 @@ def test_registry_reproduces_golden_state_keys_for_tp2x_ref_20k():
|
||||
)
|
||||
|
||||
|
||||
def test_registry_reproduces_golden_state_keys_for_tp2x_ref_20k_kimchi():
|
||||
"""A different physical unit reporting the same "TP2X_REF_20K" modelNum
|
||||
string as the fixture above (issue #26's second reporter) -- a
|
||||
3-compartment kimchi refrigerator with no flex zone, doors/icemaker, or
|
||||
freezer/cooler split, but its own /status/kimchi/<slot>/vs/0 and
|
||||
/kimchidoors/top/vs/0 resources (fridge.KIMCHI_ZONE/KIMCHI_DOOR_GENERIC)."""
|
||||
from tests.conftest import _load_device
|
||||
resources = _load_device('refrigerator_tp2x_ref_20k_kimchi')
|
||||
golden = json.loads((GOLDEN / 'refrigerator_tp2x_ref_20k_kimchi.json').read_text())
|
||||
state_keys = _new_state_keys('refrigerator_tp2x_ref_20k_kimchi', resources)
|
||||
assert set(state_keys) == set(golden['state_keys']), (
|
||||
f"state_keys mismatch:\n"
|
||||
f" extra: {sorted(set(state_keys) - set(golden['state_keys']))}\n"
|
||||
f" missing: {sorted(set(golden['state_keys']) - set(state_keys))}"
|
||||
)
|
||||
|
||||
|
||||
def test_registry_reproduces_golden_state_keys_for_ac_tp1x_da_ac_rac_01011():
|
||||
"""Newer AC firmware (Tizen Lite, oneUiVersion "7.0 Air conditioner"; model
|
||||
TP1X_DA-AC-RAC-01011) reports temperature via the vendor /temperatures/vs/0
|
||||
|
||||
Reference in New Issue
Block a user