Merge remote-tracking branch 'origin/main' into claude/device-support-issue-77-freezer

# Conflicts:
#	custom_components/localthings/registry/by_type/__init__.py
#	tests/test_by_type.py
This commit is contained in:
Marc Billow committed 2026-07-27 04:03:38 +00:00
commit f31cee206b
28 files changed
+2585 -14

No files matched your search

+523
View File
@@ -0,0 +1,523 @@
{
"device0": [
{
"rt": [
"x.com.samsung.devcol",
"oic.wk.col"
],
"if": [
"oic.if.baseline",
"oic.if.ll",
"oic.if.b"
]
},
{
"href": "/aisleep/vs/0",
"rep": {
"x.com.samsung.da.displayNightMode": "Off",
"x.com.samsung.da.elapsedTime": "0",
"x.com.samsung.da.requestFeedback": "Off",
"x.com.samsung.da.resultFeedback": "0",
"x.com.samsung.da.statusFeedback": "Idle",
"x.com.samsung.da.sleepTime": "14002200"
}
},
{
"href": "/alarms/vs/0",
"rep": {
"x.com.samsung.da.items": [
{
"x.com.samsung.da.id": "0",
"x.com.samsung.da.description": "Alarm",
"x.com.samsung.da.alarmType": "Device",
"x.com.samsung.da.code": "ErrorCode_OFF",
"x.com.samsung.da.triggeredTime": "2026-07-26T06:26:40",
"x.com.samsung.da.state": "Deleted"
},
{
"x.com.samsung.da.id": "1",
"x.com.samsung.da.description": "Alarm",
"x.com.samsung.da.alarmType": "Device",
"x.com.samsung.da.code": "FilterAlarm_OFF",
"x.com.samsung.da.triggeredTime": "2026-07-26T06:26:40",
"x.com.samsung.da.state": "Deleted"
}
]
}
},
{
"href": "/availablecontrolsets/vs/0",
"rep": {
"x.com.samsung.da.sets": "8D0D00B4012C3F21064804000000",
"x.com.samsung.da.id": "RAC",
"x.com.samsung.da.version": "1.0"
}
},
{
"href": "/configuration/vs/0",
"rep": {
"x.com.samsung.da.region": "2647000000",
"x.com.samsung.da.airconOptionList": [
"SingleCommand_1",
"DR",
"HOMECARE_WIZARD_V2",
"AI_RAC_KOREA_COOLONLY_3.0",
"AI_3.0",
"Auto_To_AI"
]
}
},
{
"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": "/drlc/0",
"rep": {
"DRLevel": 0,
"start": "1970-01-01T00:00:00Z",
"duration": 0,
"override": false
}
},
{
"href": "/drlc/vs/0",
"rep": {
"x.com.samsung.da.drlcLevel": "0",
"x.com.samsung.da.duration": "00:00:00",
"x.com.samsung.da.drlcStartTime": "1970-01-01T00:00:00Z",
"x.com.samsung.da.override": "Off",
"x.com.samsung.da.realSaving": "Off"
}
},
{
"href": "/energy/consumption/0",
"rep": {
"power": 244.0
}
},
{
"href": "/energy/consumption/vs/0",
"rep": {
"x.com.samsung.da.instantaneousPower": "244.000000",
"x.com.samsung.da.cumulativePower": "11001",
"x.com.samsung.da.cumulativeSavedPower": "0",
"x.com.samsung.da.cumulativeUnit": "Wh",
"x.com.samsung.da.instantaneousPowerUnit": "W",
"x.com.samsung.da.cumulativePowerType": "total"
}
},
{
"href": "/file/information/vs/0",
"rep": {
"x.com.samsung.timeoffset": "+09:00",
"x.com.samsung.supprtedtype": 1
}
},
{
"href": "/filter/airdustfilter/vs/0",
"rep": {
"x.com.samsung.da.filterUsage": "20",
"x.com.samsung.da.filterUsageResolution": "1",
"x.com.samsung.da.filterDesiredUsage": "500",
"x.com.samsung.da.filterStatus": "normal",
"x.com.samsung.da.filterCapacity": "500",
"x.com.samsung.da.filterCapacityUnit": "Hour",
"x.com.samsung.da.filterResetType": [
"replaceable",
"washable"
]
}
},
{
"href": "/humidity/0",
"rep": {
"humidity": 0
}
},
{
"href": "/humidity/vs/0",
"rep": {
"x.com.samsung.da.humidity": "0",
"x.com.samsung.da.fivepercentHumidity": "60"
}
},
{
"href": "/information/vs/0",
"rep": {
"x.com.samsung.da.modelNum": "TP1X_DA_AC_WAC_01001_0000|10254341|60050000001711014E00402000002000",
"x.com.samsung.da.description": "TP1X_DA_AC_WAC_01001_0000",
"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": "AW0",
"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": "Version",
"x.com.samsung.da.type": "Software",
"x.com.samsung.da.number": "02545A260601",
"x.com.samsung.da.newVersionAvailable": "0"
},
{
"x.com.samsung.da.id": "1",
"x.com.samsung.da.description": "Version",
"x.com.samsung.da.type": "Firmware",
"x.com.samsung.da.number": "02543A24061800,FFFFFFFFFFFFFF",
"x.com.samsung.da.newVersionAvailable": "0"
},
{
"x.com.samsung.da.id": "2",
"x.com.samsung.da.description": "Version",
"x.com.samsung.da.type": "Outdoor",
"x.com.samsung.da.number": "02580A10000100,FFFFFFFFFFFFFF"
},
{
"x.com.samsung.da.id": "3",
"x.com.samsung.da.description": "Version",
"x.com.samsung.da.type": "Touch IC",
"x.com.samsung.da.number": "02553A23031600,FFFFFFFFFFFFFF"
}
]
}
},
{
"href": "/keepnormalstate/vs/0",
"rep": {
"x.com.samsung.da.keepnormal": 0
}
},
{
"href": "/light/vs/0",
"rep": {
"mode": "On",
"supportedModes": [
"On",
"Off"
]
}
},
{
"href": "/mode/convenient/vs/0",
"rep": {
"x.com.samsung.da.modes": "Off",
"x.com.samsung.da.supportedModes": [
"Off",
"Quiet",
"Nano",
"NanoSleep"
]
}
},
{
"href": "/mode/vs/0",
"rep": {
"x.com.samsung.da.supportedModes": [
"AIComfort",
"Cool",
"Dry",
"Fan"
],
"x.com.samsung.da.modes": [
"Cool"
],
"x.com.samsung.da.options": [
"OnTimer_0",
"OffTimer_0",
"ArtificialWorking_Off",
"ComfortAICooling_Off",
"AiTempChanged_Off",
"AiTemp_230",
"Volume_Mute",
"OutdoorTemp_97",
"CoolCapa_0",
"StopAutoClean_Idle",
"Autoclean_Off",
"OptionCode_51320",
"ExtendOptionCode_230093",
"RacInfo_None",
"UpdateAllow_NotAllowed",
"DurationOn_93",
"WelcomeCoolingState_Off"
]
}
},
{
"href": "/option/autoclean/vs/0",
"rep": {
"x.com.samsung.da.status": "Stop",
"x.com.samsung.da.settingStatus": "Off",
"x.com.samsung.da.progress": "0",
"x.com.samsung.da.supportedStatus": [
"Start",
"Stop"
],
"x.com.samsung.da.supportedSettingStatus": [
"On",
"Off"
]
}
},
{
"href": "/option/muteonce/vs/0",
"rep": {
"muteonce": "Off"
}
},
{
"href": "/otninformation/vs/0",
"rep": {
"x.com.samsung.da.target": "",
"x.com.samsung.da.newVersionAvailable": "false",
"x.com.samsung.da.newVersionNo": "00000000",
"x.com.samsung.da.currentVersionInfo": "00000000",
"otnStatus": "None",
"flashingProgress": "",
"otnTarget": "main",
"otnCompleteDate": "noHistory",
"otnList": [
{
"type": "WIFI",
"modelId": "AWA-KR-TP1-23-AW7000",
"versions": [
"15260601"
],
"visVersion": "260601"
},
{
"type": "Micom",
"modelId": "042010254341FFFFFFFF",
"versions": [
"24061800",
"FFFFFFFF"
],
"visVersion": "240618"
},
{
"type": "Micom",
"modelId": "042010258041FFFFFFFF",
"versions": [
"10000100",
"FFFFFFFF"
],
"visVersion": "100001"
},
{
"type": "Micom",
"modelId": "042010255341FFFFFFFF",
"versions": [
"23031600",
"FFFFFFFF"
],
"visVersion": "230316"
}
]
}
},
{
"href": "/personality/presence/vs/0",
"rep": {
"x.com.samsung.da.items": [
{
"x.com.samsung.da.id": "",
"x.com.samsung.da.deviceId": "**REDACTED**",
"x.com.samsung.da.value": ""
}
]
}
},
{
"href": "/power/0",
"rep": {
"value": true
}
},
{
"href": "/power/vs/0",
"rep": {
"x.com.samsung.da.power": "On",
"causeSource": "SMTS"
}
},
{
"href": "/quickcontrol/info/vs/0",
"rep": {
"supportedVersion": "1.0"
}
},
{
"href": "/realtimenotiforclient/vs/0",
"rep": {
"x.com.samsung.da.timeforshortnoti": "0",
"x.com.samsung.da.longnotisubscription": "true",
"x.com.samsung.da.periodicnotisubscription": "true"
}
},
{
"href": "/reserverulesets/vs/0",
"rep": {
"x.com.samsung.da.sets": "8DFFFFFFFF121E121E0000FFFFFFFFFFFF01000000011F0000000000000000001F8C00",
"x.com.samsung.da.id": "WAC",
"x.com.samsung.da.version": "1.0"
}
},
{
"href": "/selfcheck/vs/0",
"rep": {
"x.com.samsung.da.start": "Cancel",
"x.com.samsung.da.status": "Ready",
"x.com.samsung.da.progress": "0",
"x.com.samsung.da.result": "NA",
"x.com.samsung.da.supportedActions": [
"Start",
"Cancel"
],
"x.com.samsung.da.error": [
"DA_ERROR_NONE"
]
}
},
{
"href": "/sensors/vs/0",
"rep": {}
},
{
"href": "/temperature/control/vs/0",
"rep": {
"x.com.samsung.da.increment": "1"
}
},
{
"href": "/temperature/current/0",
"rep": {
"range": [
16,
30
],
"units": "C",
"temperature": 30.0
}
},
{
"href": "/temperature/desired/0",
"rep": {
"range": [
16,
30
],
"units": "C",
"temperature": 30.0
}
},
{
"href": "/temperatures/vs/0",
"rep": {
"x.com.samsung.da.items": [
{
"x.com.samsung.da.id": "0",
"x.com.samsung.da.description": "Temperature",
"x.com.samsung.da.desired": "30.0",
"x.com.samsung.da.current": "30.0",
"x.com.samsung.da.maximum": "30",
"x.com.samsung.da.minimum": "18",
"x.com.samsung.da.increment": "1.0",
"x.com.samsung.da.unit": "Celsius"
}
]
}
},
{
"href": "/timezone/vs/0",
"rep": {
"timezoneid": "Asia/Seoul",
"offset": "+09:00",
"DST": "OFF"
}
},
{
"href": "/welcome/temperature/vs/0",
"rep": {
"operatingStatus": "None",
"requestId": "0000"
}
},
{
"href": "/wind/direction/vs/0",
"rep": {
"x.com.samsung.da.modes": "Fix",
"x.com.samsung.da.supportedModes": [
"Fix",
"Left_And_Right"
]
}
},
{
"href": "/wind/strength/vs/0",
"rep": {
"x.com.samsung.da.modes": "1",
"x.com.samsung.da.supportedModes": [
"0",
"1",
"2",
"3",
"4",
"5"
],
"x.com.samsung.da.modesName": [
"Auto",
"1",
"2",
"3",
"4",
"5"
]
}
},
{
"href": "/wirelessinfo/vs/0",
"rep": {
"macaddressWiFi": "**REDACTED**",
"macaddressBLE": "**REDACTED**",
"connectedApSsid": "**REDACTED**"
}
}
]
}
+386
View File
@@ -0,0 +1,386 @@
{
"device0": [
{
"rt": [
"x.com.samsung.devcol",
"oic.wk.col"
],
"if": [
"oic.if.baseline",
"oic.if.ll",
"oic.if.b"
]
},
{
"href": "/personality/presence/vs/0",
"rep": {
"x.com.samsung.da.items": [
{
"x.com.samsung.da.id": "",
"x.com.samsung.da.deviceId": "**REDACTED**",
"x.com.samsung.da.value": ""
}
]
}
},
{
"href": "/realtimenotiforclient/vs/0",
"rep": {
"x.com.samsung.da.timeforshortnoti": "0",
"x.com.samsung.da.longnotisubscription": "false",
"x.com.samsung.da.periodicnotisubscription": "true"
}
},
{
"href": "/filter/airdustfilter/vs/0",
"rep": {
"x.com.samsung.da.filterUsage": "100",
"x.com.samsung.da.filterUsageResolution": "1",
"x.com.samsung.da.filterDesiredUsage": "224",
"x.com.samsung.da.filterStatus": "wash",
"x.com.samsung.da.filterCapacity": "224",
"x.com.samsung.da.filterCapacityUnit": "Hour",
"x.com.samsung.da.filterResetType": [
"washable"
],
"x.com.samsung.da.supportedFilterDesiredUsage": [
"224",
"300",
"500",
"700"
]
}
},
{
"href": "/mode/convenient/vs/0",
"rep": {
"x.com.samsung.da.supportedModes": [
"Off",
"Sleep"
]
}
},
{
"href": "/option/autoclean/vs/0",
"rep": {
"x.com.samsung.da.status": "Stop",
"x.com.samsung.da.settingStatus": "On",
"x.com.samsung.da.progress": "100",
"cleaningTime": "30",
"minCleaningTime": "10",
"maxCleaningTime": "60",
"cleaningTimeUnit": "10",
"x.com.samsung.da.supportedStatus": [
"Start",
"TimedClean",
"Stop"
],
"x.com.samsung.da.supportedSettingStatus": [
"On",
"TimedClean",
"Off"
]
}
},
{
"href": "/alarms/vs/0",
"rep": {
"x.com.samsung.da.items": [
{
"x.com.samsung.da.id": "0",
"x.com.samsung.da.description": "Alarm",
"x.com.samsung.da.alarmType": "Device",
"x.com.samsung.da.code": "ErrorCode_OFF",
"x.com.samsung.da.triggeredTime": "2026-07-26T10:35:53",
"x.com.samsung.da.state": "Deleted"
},
{
"x.com.samsung.da.id": "2",
"x.com.samsung.da.description": "Alarm",
"x.com.samsung.da.alarmType": "Device",
"x.com.samsung.da.code": "WaterTankFull_OFF",
"x.com.samsung.da.triggeredTime": "2026-07-26T10:35:53",
"x.com.samsung.da.state": "Deleted"
}
]
}
},
{
"href": "/energy/consumption/vs/0",
"rep": {
"x.com.samsung.da.instantaneousPower": "259.000000",
"x.com.samsung.da.cumulativePower": "183099",
"x.com.samsung.da.cumulativeSavedPower": "0",
"x.com.samsung.da.cumulativeUnit": "Wh",
"x.com.samsung.da.instantaneousPowerUnit": "W",
"x.com.samsung.da.cumulativePowerType": "total"
}
},
{
"href": "/mode/vs/0",
"rep": {
"x.com.samsung.da.supportedModes": [
"Smart",
"Max",
"High",
"Medium",
"Quiet",
"ClothesDrying"
],
"x.com.samsung.da.modes": [
"Smart"
],
"x.com.samsung.da.options": [
"Volume_Mute",
"RacInfo_None",
"UpdateAllow_NotAllowed",
"WelcomeCoolingState_Off"
]
}
},
{
"href": "/power/vs/0",
"rep": {
"x.com.samsung.da.power": "On",
"causeSource": "SMTS"
}
},
{
"href": "/sensors/vs/0",
"rep": {}
},
{
"href": "/information/vs/0",
"rep": {
"x.com.samsung.da.modelNum": "TP1X_DA_AC_DHM_01001_0000|10253841|77000000001700000A00000000000000",
"x.com.samsung.da.description": "TP1X_DA_AC_DHM_01001_0000",
"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": "AY0",
"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": "Version",
"x.com.samsung.da.type": "Software",
"x.com.samsung.da.number": "02544A260401",
"x.com.samsung.da.newVersionAvailable": "0"
},
{
"x.com.samsung.da.id": "1",
"x.com.samsung.da.description": "Version",
"x.com.samsung.da.type": "Firmware",
"x.com.samsung.da.number": "02538A24032700,FFFFFFFFFFFFFF",
"x.com.samsung.da.newVersionAvailable": "0"
},
{
"x.com.samsung.da.id": "2",
"x.com.samsung.da.description": "Version",
"x.com.samsung.da.type": "Outdoor",
"x.com.samsung.da.number": "02590A10000100,FFFFFFFFFFFFFF"
},
{
"x.com.samsung.da.id": "4",
"x.com.samsung.da.description": "Version",
"x.com.samsung.da.type": "Touch IC",
"x.com.samsung.da.number": "02552A23020200,FFFFFFFFFFFFFF"
}
]
}
},
{
"href": "/file/information/vs/0",
"rep": {
"x.com.samsung.timeoffset": "+09:00",
"x.com.samsung.supprtedtype": 1
}
},
{
"href": "/configuration/vs/0",
"rep": {
"x.com.samsung.da.region": "0000000000",
"x.com.samsung.da.airconOptionList": [
"SingleCommand_1",
"DR",
"HOMECARE_WIZARD_V2",
"PRODUCT_GLOBAL",
"AI_RAC_GLOBAL_2.0",
"AI_Heat"
]
}
},
{
"href": "/humidity/vs/0",
"rep": {
"x.com.samsung.da.humidity": "47",
"x.com.samsung.da.desiredHumidity": "50",
"increment": "5",
"x.com.samsung.da.fivepercentHumidity": "46"
}
},
{
"href": "/drlc/vs/0",
"rep": {
"x.com.samsung.da.drlcLevel": "0",
"x.com.samsung.da.durationminutes": "0",
"x.com.samsung.da.start": "1970-01-01T00:00:00Z",
"x.com.samsung.da.override": "Off",
"x.com.samsung.da.realSaving": "Off"
}
},
{
"href": "/availablecontrolsets/vs/0",
"rep": {
"x.com.samsung.da.sets": "20001E46070A5A00000000000000",
"x.com.samsung.da.id": "DHM",
"x.com.samsung.da.version": "1.0"
}
},
{
"href": "/da/softreset/vs/0",
"rep": {
"x.com.samsung.da.softwarereset": "false"
}
},
{
"href": "/keepnormalstate/vs/0",
"rep": {
"x.com.samsung.da.keepnormal": 0
}
},
{
"href": "/otninformation/vs/0",
"rep": {
"x.com.samsung.da.target": "",
"x.com.samsung.da.newVersionAvailable": "false",
"x.com.samsung.da.newVersionNo": "00000000",
"x.com.samsung.da.currentVersionInfo": "00000000",
"otnStatus": "None",
"flashingProgress": "",
"otnTarget": "main",
"otnCompleteDate": "noHistory",
"otnList": [
{
"type": "WIFI",
"modelId": "ADH-WW-TP1-23-AY7100",
"versions": [
"18260401"
],
"visVersion": "260401"
},
{
"type": "Micom",
"modelId": "042110253841FFFFFFFF",
"versions": [
"24032700",
"FFFFFFFF"
],
"visVersion": "240327"
},
{
"type": "Micom",
"modelId": "042110259041FFFFFFFF",
"versions": [
"10000100",
"FFFFFFFF"
],
"visVersion": "100001"
},
{
"type": "Micom",
"modelId": "042110255241FFFFFFFF",
"versions": [
"23020200",
"FFFFFFFF"
],
"visVersion": "230202"
}
]
}
},
{
"href": "/timezone/vs/0",
"rep": {
"timezoneid": "Asia/Seoul",
"offset": "+09:00",
"DST": "OFF"
}
},
{
"href": "/option/muteonce/vs/0",
"rep": {
"muteonce": "Off"
}
},
{
"href": "/reserverulesets/vs/0",
"rep": {
"x.com.samsung.da.sets": "3F1E461E461E46FFFFFFFF1E4600",
"x.com.samsung.da.id": "DHM",
"x.com.samsung.da.version": "1.0"
}
},
{
"href": "/connectionconfig/vs/0",
"rep": {
"autoReconnectionMinVersion": "1.0",
"autoReconnection": "true",
"autoReconnectionProtocolType": [
"helper_hotspot",
"ble_ocf"
],
"supportedWiFiAuthType": [
"OPEN",
"WEP",
"WPA-PSK",
"WPA2-PSK",
"SAE"
],
"supportedWiFiCryptoType": [
"TKIP",
"AES",
"WEP-64",
"WEP-128"
],
"supportedWiFiFreq": [
"2.4G"
],
"calmConnectionCare": {
"version": "1.0",
"role": [
"things"
]
}
}
},
{
"href": "/welcome/humidity/vs/0",
"rep": {
"requestId": "0000",
"operatingStatus": "None"
}
},
{
"href": "/wirelessinfo/vs/0",
"rep": {
"macaddressWiFi": "**REDACTED**",
"macaddressBLE": "**REDACTED**"
}
},
{
"href": "/quickcontrol/info/vs/0",
"rep": {
"supportedVersion": "1.0"
}
}
]
}
+391
View File
@@ -0,0 +1,391 @@
{
"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": "/configuration/vs/0",
"rep": {
"x.com.samsung.da.region": "0000000000",
"x.com.samsung.da.countryCode": "US"
}
},
{
"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": "/course/vs/0",
"rep": {
"x.com.samsung.da.supportedModes": [
"HOMECARE_WIZARD_V2"
],
"x.com.samsung.da.options": [
"DeviceType_0152",
"UpdateAllow_NotAllowed",
"Course_01",
"AiOption_On",
"MixedLoadBell_Disable",
"MixedLoadBellNoti_Nothing",
"LaundryOutTime_0",
"SeamlessControl_Enable",
"KidsLockBypass_On",
"DetergentOnce_1",
"DetergentLeft_0",
"DetergentBase_5",
"DetergentAlarm_Off",
"DetergentType_3",
"DetergentTotal_0",
"SpecialFunction_20",
"AvailableDelayTime_39",
"LaundryPlannerUserSetTime_0",
"ProgressTimeSet_B20870B400B4",
"WrinklePreventRunning_Off",
"DryTime_0",
"SendToDevice_Off",
"GMT_F6",
"DryTimeSet_0A00000000500A005A00000000000000001E0A005A00000000000000001414005A000000000000000000000000000000000000000000000000000000000000000000000000140A005A280A005A",
"EnergyLevelSet_0504040504040205020204040305020203030103",
"MostUsed_068A3ED000",
"WrinklePreventSet_0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F",
"MixedLoadBellSet_02FF02FFFF02FFFFFF0202FF02FFFF02FFFFFF",
"GeoFenceAlarm",
"UsagesDB_ok",
"EnergyKW_396",
"DrumCleanLog_Empty",
"TimeSync_NotSupported"
],
"x.com.samsung.da.supportedOptions": [
"2018410D33E06833ED0002F8520D33E178520D3083E8520D000078102D33E338520D520358520D000348520D0000E8520D33E058520D33E308410D308318410D33E098410D3080F8204D308328308D33E2E8102D308368000D0000D8102D000"
]
}
},
{
"href": "/cycleinterface/vs/0",
"rep": {
"x.com.samsung.da.cycleInterfaceEnabled": "On"
}
},
{
"href": "/diagnosis/vs/0",
"rep": {
"x.com.samsung.da.diagnosisStart": "Ready"
}
},
{
"href": "/energy/consumption/0",
"rep": {}
},
{
"href": "/energy/consumption/vs/0",
"rep": {
"x.com.samsung.da.instantaneousPower": "-500",
"x.com.samsung.da.instantaneousPowerUnit": "W",
"x.com.samsung.da.cumulativePower": "2323800",
"x.com.samsung.da.cumulativeUnit": "Wh",
"x.com.samsung.da.cumulativeDate": "1784991600",
"x.com.samsung.da.cumulativeDateUTC": "1785009600"
}
},
{
"href": "/file/information/vs/0",
"rep": {
"x.com.samsung.timeoffset": "-05:00"
}
},
{
"href": "/information/vs/0",
"rep": {
"x.com.samsung.da.modelNum": "DA_WM_TP1_21_COMMON|20286441|300000010015110002A3031700000000",
"x.com.samsung.da.description": "DA_WM_TP1_21_COMMON_DVE50A8800_8600/DC92-02835A_0080",
"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": "310",
"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": "DA_WM_TP1_21_COMMON|20286441|300000010015110002A3031700000000",
"x.com.samsung.da.type": "Software",
"x.com.samsung.da.number": "02986A260118(A182)",
"x.com.samsung.da.newVersionAvailable": "0"
},
{
"x.com.samsung.da.id": "1",
"x.com.samsung.da.description": "Firmware_1_DB_20286441210514150FFFFF202835412105131904FFFF(01522028644120283541_30000000)(FileDown:0)(Type:0)",
"x.com.samsung.da.type": "Firmware",
"x.com.samsung.da.number": "02864A21051415,02835A21051319",
"x.com.samsung.da.newVersionAvailable": "0"
}
]
}
},
{
"href": "/kidslock/0",
"rep": {
"value": false
}
},
{
"href": "/kidslock/vs/0",
"rep": {
"x.com.samsung.da.kidsLock": "Ready"
}
},
{
"href": "/operational/state/0",
"rep": {
"currentMachineState": "**REDACTED**",
"machineStates": "**REDACTED**",
"jobStates": [
"None",
"Drying",
"Cooling",
"Finish"
],
"currentJobState": "None",
"remainingTime": "01:15:00",
"progressPercentage": "1"
}
},
{
"href": "/operational/state/vs/0",
"rep": {
"x.com.samsung.da.state": "Ready",
"x.com.samsung.da.remainingTime": "01:15:00",
"x.com.samsung.da.progressPercentage": "1",
"x.com.samsung.da.progress": "None",
"x.com.samsung.da.delayEndTime": "00:00:00",
"x.com.samsung.da.supportedProgress": [
"None",
"Drying",
"Cooling",
"Finish"
]
}
},
{
"href": "/otninformation/vs/0",
"rep": {
"x.com.samsung.da.target": "",
"x.com.samsung.da.newVersionAvailable": "false",
"x.com.samsung.da.newVersionNo": "00000000",
"x.com.samsung.da.currentVersionInfo": "00000000",
"otnStatus": "None",
"flashingProgress": "",
"otnTarget": "main",
"otnCompleteDate": "2026-03-04",
"otnList": [
{
"type": "WIFI",
"modelId": "DA_WM_TP1_21_COMMON",
"versions": [
"30260118"
],
"visVersion": "260118"
},
{
"type": "Micom",
"modelId": "01522028644120283541",
"versions": [
"21051415",
"21051319"
],
"visVersion": "210514"
}
]
}
},
{
"href": "/power/0",
"rep": {
"value": false
}
},
{
"href": "/power/vs/0",
"rep": {
"x.com.samsung.da.power": "Off"
}
},
{
"href": "/quickcontrol/info/vs/0",
"rep": {
"supportedVersion": "1.0"
}
},
{
"href": "/realtimenotiforclient/vs/0",
"rep": {
"x.com.samsung.da.timeforshortnoti": "10",
"x.com.samsung.da.periodicnotisubscription": "true"
}
},
{
"href": "/remotectrl/0",
"rep": {
"value": false
}
},
{
"href": "/remotectrl/vs/0",
"rep": {
"x.com.samsung.da.remoteControlEnabled": "false"
}
},
{
"href": "/setting/vs/0",
"rep": {
"x.com.samsung.da.supportedSetLanguage": [
"en_US",
"ko_KR",
"fr_CA",
"es_MX",
"pt_BR"
],
"x.com.samsung.da.setLanguage": "en_US"
}
},
{
"href": "/st/dryercourse/vs/0",
"rep": {
"x.com.samsung.da.st.dryerMode": "Table_03_Course_01",
"x.com.samsung.da.st.courseTable": "Table_03"
}
},
{
"href": "/timezone/vs/0",
"rep": {
"timezoneid": "America/Chicago",
"offset": "-05:00",
"DST": "ON"
}
},
{
"href": "/washer/vs/0",
"rep": {
"x.com.samsung.da.wrinklePrevent": "Off",
"x.com.samsung.da.waterTemperature": "Medium",
"x.com.samsung.da.supportedWaterTemperature": [
"None",
"ExtraLow",
"Low",
"MediumLow",
"Medium",
"High"
],
"x.com.samsung.da.dryLevel": "Normal",
"x.com.samsung.da.supportedDryLevel": [
"None",
"Damp",
"Less",
"Normal",
"More",
"Very"
],
"x.com.samsung.da.dryerType": "Electricity"
}
},
{
"href": "/wirelessinfo/vs/0",
"rep": {
"macaddressWiFi": "**REDACTED**",
"macaddressBLE": "**REDACTED**"
}
},
{
"href": "/wm/editcourse/vs/0",
"rep": {
"x.com.samsung.da.editCourseList": "EditCourseList_0106",
"x.com.samsung.da.fixedCourseList": "FixedCourseList_0101"
}
},
{
"href": "/wm/jobbeginingstatus/vs/0",
"rep": {
"x.com.samsung.da.currentStatus": "None"
}
},
{
"href": "/wm/personalcourse/vs/0",
"rep": {
"x.com.samsung.da.courses": [
"F1_00",
"F2_00",
"F3_00",
"F4_00",
"F5_00",
"F6_00",
"F7_00",
"F8_00",
"F9_00",
"FA_00"
],
"x.com.samsung.da.maxCourseNum": "10"
}
},
{
"href": "/wm/setinfo/vs/0",
"rep": {
"x.com.samsung.da.isModelSettingWithoutSC": "true",
"x.com.samsung.da.aiCourse": "false",
"x.com.samsung.da.isModelSettingPowerOnOff": "false",
"x.com.samsung.da.modelCode": "M(None),W(DVE50A8600V/A3)"
}
},
{
"href": "/wm/welcomemsg/vs/0",
"rep": {}
}
]
}
+20
View File
@@ -0,0 +1,20 @@
{
"state_keys": [
"air_filter_status",
"air_filter_usage",
"alarm_code",
"auto_clean",
"climate",
"current_temperature_c",
"display_light",
"energy_kwh",
"energy_saved_kwh",
"firmware_update",
"humidity",
"mute_once",
"power_watts",
"selfcheck_error",
"selfcheck_result",
"selfcheck_status"
]
}
+17
View File
@@ -0,0 +1,17 @@
{
"state_keys": [
"air_filter_status",
"air_filter_usage",
"alarm_code",
"auto_clean",
"energy_kwh",
"energy_saved_kwh",
"firmware_update",
"humidity",
"mute_once",
"operating_mode",
"power_switch",
"power_watts",
"target_humidity"
]
}
+24
View File
@@ -0,0 +1,24 @@
{
"state_keys": [
"alarm_code",
"child_lock",
"completion_minutes",
"cycle",
"cycle_active",
"delay_start_hours",
"diagnosis",
"dry_level",
"dry_time",
"dryer_type",
"energy_kwh",
"finish_time",
"firmware_update",
"job_beginning_status",
"machine_state",
"power_switch",
"progress",
"progress_percentage",
"remote_control",
"wrinkle_prevent"
]
}
+27
View File
@@ -0,0 +1,27 @@
{
"state_keys": [
"alarm_code",
"buzz_lock",
"coldwater_lock",
"dispense_capacity",
"dispense_type",
"favorite_capacity",
"favorite_capacity_enabled",
"filter_clean_remain_time",
"filter_door_status",
"filter_status",
"filter_usage",
"firmware_update",
"hot_water_temperature",
"hotwater_lock",
"pouring",
"selfcheck_error",
"selfcheck_result",
"selfcheck_status",
"sterilize_last_time",
"sterilize_period",
"sterilize_plan_time",
"sterilize_run_time",
"waterpurifier_status"
]
}
+234
View File
@@ -0,0 +1,234 @@
{
"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": "/mode/vs/0",
"rep": {
"x.com.samsung.da.supportedModes": [
"HOMECARE_WIZARD_V2"
],
"x.com.samsung.da.modes": [
"WATERFILTER_DISABLE"
]
}
},
{
"href": "/mode/0",
"rep": {
"supportedModes": [
"HOMECARE_WIZARD_V2"
],
"modes": [
"WATERFILTER_DISABLE"
]
}
},
{
"href": "/realtimenotiforclient/vs/0",
"rep": {}
},
{
"href": "/information/vs/0",
"rep": {
"x.com.samsung.da.modelNum": "TP2X_WATERPURIFIER_20K|00132341|900000000215130001060F0000020000",
"x.com.samsung.da.description": "TP2X_WATERPURIFIER_20K",
"x.com.samsung.da.serialNum": "**REDACTED**",
"x.com.samsung.da.otnDUID": "**REDACTED**",
"x.com.samsung.da.diagProtocolType": "WIFI_HTTPS",
"x.com.samsung.da.diagLogType": [
"errCode",
"dump"
],
"x.com.samsung.da.diagDumpType": "file",
"x.com.samsung.da.diagEndPoint": "SSM",
"x.com.samsung.da.diagMnid": "0AJT",
"x.com.samsung.da.diagSetupid": "540",
"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": "02144A240615",
"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": "24061111, 24091218, 21072807, 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": "/otninformation/vs/0",
"rep": {
"x.com.samsung.da.target": "",
"x.com.samsung.da.newVersionAvailable": "false",
"otnStatus": "None",
"flashingProgress": "",
"otnList": [
{
"type": "WIFI",
"modelId": "A-WPWW-TP2-20-COMMON",
"versions": [
"20240615"
],
"visVersion": "240615"
},
{
"type": "Micom",
"modelId": "03800013234100132441",
"versions": [
"24061111",
"24091218"
],
"visVersion": "240912"
},
{
"type": "Micom",
"modelId": "038000132541FFFFFFFF",
"versions": [
"21072807",
"FFFFFFFF"
],
"visVersion": "210728"
}
]
}
},
{
"href": "/filter/waterfilter/vs/0",
"rep": {
"x.com.samsung.da.filterUsage": "28",
"x.com.samsung.da.filterUsageResolution": "1",
"x.com.samsung.da.filterStatus": "normal",
"x.com.samsung.da.lastResetDate": "2026-04-14T14:33:15",
"x.com.samsung.da.filterResetType": [
"replaceable"
]
}
},
{
"href": "/status/lock/vs/0",
"rep": {
"x.com.samsung.da.hotwaterLock": "Unlocked",
"x.com.samsung.da.coldwaterLock": "Unlocked",
"x.com.samsung.da.buzzLock": "Unlocked"
}
},
{
"href": "/setting/waterpurifier/vs/0",
"rep": {
"x.com.samsung.da.desiredType": "coldwater",
"x.com.samsung.da.tempDesiredHotWater": "85",
"x.com.samsung.da.tempUnit": "C",
"x.com.samsung.da.desiredCapacity": "100",
"x.com.samsung.da.capacityUnit": "C",
"x.com.samsung.da.capacityResolution": "10",
"x.com.samsung.da.desiredCapacityRange": [
"50",
"2000"
],
"x.com.samsung.da.supportedTypes": [
"ambientwater",
"coldwater",
"hotwater"
],
"x.com.samsung.da.supportedHotTemperatures": [
"40",
"75",
"85"
],
"x.com.samsung.da.triggerTime": "1785063153",
"x.com.samsung.da.pourStatus": "Off"
}
},
{
"href": "/status/waterpurifier/vs/0",
"rep": {
"x.com.samsung.da.status": "Ready",
"x.com.samsung.da.filterDoorStatus": "Close",
"x.com.samsung.da.sterilizeRunTime": "0",
"x.com.samsung.da.sterilizeStartTime": "2026-07-27T18:00:00",
"x.com.samsung.da.sterilizePeriod": "3",
"x.com.samsung.da.sterilizePlanTime": "2026-07-27T18:00:00",
"x.com.samsung.da.sterilizeLastTime": "2026-07-24T18:09:25",
"x.com.samsung.da.sterilizeMaxTime": "10",
"x.com.samsung.da.filterCleanRemainTime": "0",
"x.com.samsung.da.filterCleanMaxTime": "15"
}
},
{
"href": "/favorite/capacity/vs/0",
"rep": {
"x.com.samsung.da.switchCapacity": "On",
"x.com.samsung.da.defaultCapacity": "100",
"x.com.samsung.da.maxCapacity": "6",
"x.com.samsung.da.capacityList": [
"100",
"150",
"200",
"250",
"550",
"9999"
]
}
},
{
"href": "/automation/waterpurifier/vs/0",
"rep": {
"automation.supported.modes": [
"0"
],
"automation.supported.options": [
"capacity"
]
}
},
{
"href": "/timezone/vs/0",
"rep": {
"timezoneid": "Asia/Seoul",
"offset": "+09:00",
"DST": "OFF"
}
}
]
}
+16
View File
@@ -508,3 +508,19 @@ async def test_options_flow_finish_preserves_existing_options(
assert result['type'] == FlowResultType.CREATE_ENTRY
assert entry.options[CONF_BYPASS_REMOTE_CONTROL] is True
def test_is_placeholder_serial_catches_nothing_svc():
"""Issue #83: the ARTIK051_DONGLE_REF firmware family reports the
literal string 'Nothing(SVC)' as serialNum on every unit -- non-empty,
so it must be caught by name, not by the plain `if not serial` check."""
from custom_components.localthings.config_flow import _is_placeholder_serial
assert _is_placeholder_serial('Nothing(SVC)') is True
assert _is_placeholder_serial('nothing(svc)') is True
assert _is_placeholder_serial(' Nothing(SVC) ') is True
def test_is_placeholder_serial_accepts_real_serials():
from custom_components.localthings.config_flow import _is_placeholder_serial
assert _is_placeholder_serial('0A1B2C3D4E5F') is False
assert _is_placeholder_serial('') is False
+24 -1
View File
@@ -137,6 +137,29 @@ def test_run_discovery_detects_washer_via_model_fallback(
assert coordinator.device_type_name == 'washer'
def test_run_discovery_falls_back_to_host_for_placeholder_serial(
hass: HomeAssistant, mock_entry
) -> None:
"""Issue #83: the ARTIK051_DONGLE_REF firmware family reports the
literal string 'Nothing(SVC)' as serialNum on every unit. Left as-is,
two such units get the same device_serial (which feeds both the HA
device-registry identifier and every entity's unique_id), so the
second one's entities silently collide and get dropped. It must be
treated the same as an empty serial and fall back to the host."""
resources = {
'/information/vs/0': {
'x.com.samsung.da.modelNum':
'ARTIK051_DONGLE_REF|00127641|00080020001430300100000000000000',
'x.com.samsung.da.description': 'ARTIK_REF_17K',
'x.com.samsung.da.serialNum': 'Nothing(SVC)',
},
'/otninformation/vs/0': {},
}
coordinator = LocalThingsCoordinator(hass, mock_entry)
coordinator._run_discovery(resources)
assert coordinator.device_serial == mock_entry.data[CONF_HOST]
def test_run_discovery_detects_cooktop_via_resource_signature(
hass: HomeAssistant, mock_entry
) -> None:
@@ -146,7 +169,7 @@ def test_run_discovery_detects_cooktop_via_resource_signature(
coordinator = LocalThingsCoordinator(hass, mock_entry)
coordinator._run_discovery(_load_device('cooktop'))
assert coordinator.device_type_name == 'cooktop'
assert coordinator.device_type_name == 'gas_cooktop'
assert coordinator._unbound_hrefs == []
+80 -3
View File
@@ -42,9 +42,12 @@ class TestForDevice:
assert registry.name == 'refrigerator'
def test_for_device_returns_cooktop_registry(self):
"""The registry's own .name is 'gas_cooktop' (disambiguated from
induction_cooktop), but the lookup key devices route through stays
'cooktop' -- oneUiVersion "Cooktop" still resolves here."""
registry = for_device('7.0 Cooktop')
assert registry is not None
assert registry.name == 'cooktop'
assert registry.name == 'gas_cooktop'
def test_for_device_returns_range_hood_registry(self):
registry = for_device('7.0 Range Hood')
@@ -130,6 +133,27 @@ class TestModelNumSegments:
assert _model_num_segments(None) == ['']
class TestConsumerModelKey:
def test_finds_key_in_last_segment(self):
from custom_components.localthings.registry.by_type import _consumer_model_key
assert _consumer_model_key('DA_WM_TP1_21_COMMON_WW5000C') == 'washer'
def test_finds_key_before_a_trailing_unrecognized_segment(self):
"""Issue #79: 'DVE50A8800_8600' pairs two model numbers -- the real
consumer token is the second-to-last segment, not the last."""
from custom_components.localthings.registry.by_type import _consumer_model_key
assert _consumer_model_key(
'DA_WM_TP1_21_COMMON_DVE50A8800_8600/DC92-02835A_0080') == 'dryer'
def test_ignores_everything_after_first_slash(self):
from custom_components.localthings.registry.by_type import _consumer_model_key
assert _consumer_model_key('DA_WM_TP1_21_COMMON_WW5000C/DW9000_board') == 'washer'
def test_none_when_no_segment_matches(self):
from custom_components.localthings.registry.by_type import _consumer_model_key
assert _consumer_model_key('ARTIK051_DONGLE_REF') is None
class TestForDeviceByModel:
"""Fallback device-type detection for hardware without oneUiVersion."""
@@ -161,6 +185,20 @@ class TestForDeviceByModel:
assert reg is not None
assert reg.name == 'dryer'
def test_dryer_dve50a8600_paired_model_numbers_in_description(self):
"""Issue #79: description pairs two model numbers
('..._DVE50A8800_8600/DC92-...'), so the 'DV' consumer token is one
segment before the literal last segment ('8600', which has no
recognizable prefix on its own). The old last-segment-only check
fell through to 'unknown' here."""
from custom_components.localthings.registry.by_type import for_device_by_model
reg = for_device_by_model(
'DA_WM_TP1_21_COMMON|20286441|300000010015110002A3031700000000',
'DA_WM_TP1_21_COMMON_DVE50A8800_8600/DC92-02835A_0080',
)
assert reg is not None
assert reg.name == 'dryer'
def test_dishwasher_not_misdetected_as_washer(self):
"""Dishwasher's modelNum contains the substring 'WW' -- must not
be misrouted by a naive substring match."""
@@ -223,6 +261,45 @@ class TestForDeviceByModel:
assert reg is not None
assert reg.name == 'airconditioner'
def test_dehumidifier_via_dhm_token(self):
"""Issue #88: a dehumidifier (AY18CG7500GED) shares the DA_AC_ board
family with the room-AC models but reports no oneUiVersion and
carries the '_DHM_' (DeHuMidifier) token instead of
'_RAC_'/'_PRAC_'/'_WAC_'; falls back to the '_DHM_' token in
modelNum."""
from custom_components.localthings.registry.by_type import for_device_by_model
reg = for_device_by_model(
'TP1X_DA_AC_DHM_01001_0000|10253841|77000000001700000A00000000000000',
'TP1X_DA_AC_DHM_01001_0000',
)
assert reg is not None
assert reg.name == 'dehumidifier'
def test_water_purifier_via_waterpurifier_token(self):
"""Issue #90: a water purifier (TP2X_WATERPURIFIER_20K) reports no
oneUiVersion and no consumer-prefix match; falls back to the
'WATERPURIFIER' token shared by modelNum and description."""
from custom_components.localthings.registry.by_type import for_device_by_model
reg = for_device_by_model(
'TP2X_WATERPURIFIER_20K|00132341|900000000215130001060F0000020000',
'TP2X_WATERPURIFIER_20K',
)
assert reg is not None
assert reg.name == 'water_purifier'
def test_airconditioner_via_wac_token(self):
"""Issue #87: a Bespoke Window AC (AW06C7155EWAZ) reports no
oneUiVersion and a modelNum carrying the '_WAC_' (Window Air
Conditioner) token instead of '_RAC_'/'_PRAC_'; falls back to the
'_WAC_' token in modelNum."""
from custom_components.localthings.registry.by_type import for_device_by_model
reg = for_device_by_model(
'TP1X_DA_AC_WAC_01001_0000|40460041|50030018001611020A00000000000000',
'AW06C7155EWAZ',
)
assert reg is not None
assert reg.name == 'airconditioner'
def test_cooktop_via_legacy_model_description(self):
"""Older cooktops identify themselves as ARTIK051_GLOBAL_COOKTOP."""
from custom_components.localthings.registry.by_type import for_device_by_model
@@ -231,7 +308,7 @@ class TestForDeviceByModel:
'ARTIK051_GLOBAL_COOKTOP',
)
assert reg is not None
assert reg.name == 'cooktop'
assert reg.name == 'gas_cooktop'
def test_range_hood_via_ahd_model(self):
from custom_components.localthings.registry.by_type import for_device_by_model
@@ -292,7 +369,7 @@ class TestForDeviceByResources:
reg = for_device_by_resources(_load_device('cooktop'))
assert reg is not None
assert reg.name == 'cooktop'
assert reg.name == 'gas_cooktop'
def test_unrelated_mode_options_are_not_cooktop(self):
from custom_components.localthings.registry.by_type import for_device_by_resources
+118
View File
@@ -0,0 +1,118 @@
"""Tests for Samsung dehumidifier support (issue #88, AY18CG7500GED).
HA-free like the rest of the suite: exercises the registry, discovery/
flatten, and the target-humidity/operating-mode write contracts.
"""
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.discovery import discover
from custom_components.localthings.registry.entities import NumberDesc, SelectDesc
from tests.conftest import _load_device
def _dehumidifier():
resources = _load_device('dehumidifier')
info = resources['/information/vs/0']
reg = for_device_by_model(
info['x.com.samsung.da.modelNum'], info['x.com.samsung.da.description'],
)
return reg, resources
def _bound():
reg, resources = _dehumidifier()
return discover(resources, reg.capabilities, reg.pattern_capabilities), resources
def _state():
bound, resources = _bound()
return flatten(bound, resources)
def _desc(key):
bound, _ = _bound()
return next(b.desc for b in bound if b.desc.key == key)
def test_model_resolves_to_dehumidifier_registry():
reg, _ = _dehumidifier()
assert reg is not None and reg.name == 'dehumidifier'
def test_no_unbound_hrefs():
"""Every resource in the issue #88 dump binds or is covered -- clears
the coverage-gap repair."""
reg, resources = _dehumidifier()
unbound = []
discover(resources, reg.capabilities, reg.pattern_capabilities, log=unbound.append)
assert unbound == []
def test_expected_state_keys_present():
state = _state()
for key in ('humidity', 'target_humidity', 'operating_mode', 'power_switch',
'auto_clean', 'air_filter_status', 'mute_once'):
assert key in state, key
def test_humidity_sensor_reads_current_value():
state = _state()
assert state['humidity'] == 47
def test_target_humidity_number_reads_desired_value():
state = _state()
assert state['target_humidity'] == 50
def test_target_humidity_write_contract():
desc = _desc('target_humidity')
assert isinstance(desc, NumberDesc)
path, body = desc.write_fn('55', {})
assert path == ['humidity', 'vs', '0']
assert body == {'x.com.samsung.da.desiredHumidity': '55'}
def test_target_humidity_step_reads_live_increment():
"""Step comes from the device's own `increment` field rather than a
hardcoded constant -- see the adding-device-support skill's 'never
hard-code the one dump's values' section."""
desc = _desc('target_humidity')
assert desc.step_fn({'increment': '5'}) == 5
assert desc.step_fn({'increment': '10'}) == 10
# No live field: falls back to a sane default rather than raising.
assert desc.step_fn({}) == 1
def test_target_humidity_has_no_hardcoded_bounds():
"""No range field is present in any dump seen so far -- native_min/max
are deliberately left unset (falls back to HA's own 0-100 default for a
percentage field) rather than a bound guessed from a spec sheet."""
desc = _desc('target_humidity')
assert desc.native_min is None
assert desc.native_max is None
assert desc.native_min_fn is None
assert desc.native_max_fn is None
def test_operating_mode_select_options_come_from_live_supported_modes():
"""Options are read live from x.com.samsung.da.supportedModes, not a
hardcoded tuple -- so a future device with a different mode set is
handled automatically."""
desc = _desc('operating_mode')
assert isinstance(desc, SelectDesc)
assert desc.options_field == 'x.com.samsung.da.supportedModes'
assert desc.options == ()
def test_operating_mode_write_contract():
desc = _desc('operating_mode')
path, body = desc.write_fn('Quiet', {})
assert path == ['mode', 'vs', '0']
assert body == {'x.com.samsung.da.modes': ['Quiet']}
def test_operating_mode_reads_first_mode():
state = _state()
assert state['operating_mode'] == 'Smart'
+67
View File
@@ -73,6 +73,23 @@ def test_registry_reproduces_golden_state_keys_for_dryer():
)
def test_registry_reproduces_golden_state_keys_for_dryer_dve50a8600():
"""DVE50A8600V/A3 (issue #79) -- description pairs two model numbers
('..._DVE50A8800_8600/...'), so the true 'DV' consumer-model token sits
one segment before the actual last segment ('8600'). The old
last-segment-only check missed it and fell back to 'unknown'; resolved
via _consumer_model_key scanning segments from the end."""
from tests.conftest import _load_device
resources = _load_device('dryer_dve50a8600')
golden = json.loads((GOLDEN / 'dryer_dve50a8600.json').read_text())
state_keys = _new_state_keys('dryer_dve50a8600', 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_airconditioner():
from tests.conftest import _load_device
resources = _load_device('airconditioner')
@@ -85,6 +102,39 @@ def test_registry_reproduces_golden_state_keys_for_airconditioner():
)
def test_registry_reproduces_golden_state_keys_for_dehumidifier():
"""TP1X_DA_AC_DHM_01001_0000 (issue #88, AY18CG7500GED) shares the DA_AC_
board family with the room-AC models but carries the '_DHM_' token;
resolved via the '_DHM_' modelNum fallback in for_device_by_model into a
dedicated dehumidifier registry (target humidity, operating mode, reused
AC filter/auto-clean/mute-once capabilities)."""
from tests.conftest import _load_device
resources = _load_device('dehumidifier')
golden = json.loads((GOLDEN / 'dehumidifier.json').read_text())
state_keys = _new_state_keys('dehumidifier', 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_water_purifier():
"""TP2X_WATERPURIFIER_20K (issue #90) reports no oneUiVersion; resolved
via the 'WATERPURIFIER' modelNum/description fallback into a dedicated
water_purifier registry (dispense settings, sterilize/filter status,
favorite capacity, and the three lock switches)."""
from tests.conftest import _load_device
resources = _load_device('water_purifier')
golden = json.loads((GOLDEN / 'water_purifier.json').read_text())
state_keys = _new_state_keys('water_purifier', resources)
assert set(state_keys) == set(golden['state_keys']), (
f"state_keys mismatch:\n"
f" extra: {sorted(set(state_keys) - set(golden['state_keys']))}\n"
f" missing: {sorted(set(golden['state_keys']) - set(state_keys))}"
)
def test_registry_reproduces_golden_state_keys_for_cooktop():
from tests.conftest import _load_device
resources = _load_device('cooktop')
@@ -270,6 +320,23 @@ def test_registry_reproduces_golden_state_keys_for_caww_tp2():
)
def test_registry_reproduces_golden_state_keys_for_window_ac():
"""TP1X_DA_AC_WAC_01001_0000 (issue #87, Bespoke Window AC AW06C7155EWAZ)
reports no oneUiVersion and carries the '_WAC_' (Window Air Conditioner)
token instead of '_RAC_'/'_PRAC_'; resolved via the '_WAC_' modelNum
fallback in for_device_by_model. Otherwise binds cleanly against the
existing airconditioner registry with zero unbound hrefs."""
from tests.conftest import _load_device
resources = _load_device('airconditioner_window_ac')
golden = json.loads((GOLDEN / 'airconditioner_window_ac.json').read_text())
state_keys = _new_state_keys('airconditioner_window_ac', resources)
assert set(state_keys) == set(golden['state_keys']), (
f"state_keys mismatch:\n"
f" extra: {sorted(set(state_keys) - set(golden['state_keys']))}\n"
f" missing: {sorted(set(golden['state_keys']) - set(state_keys))}"
)
def test_registry_reproduces_golden_state_keys_for_tp1x_rac():
"""TP1X_DA-AC-RAC-01001_0000 (issue #38) -- fuller RAC board with display
light, self-check, mute-once, and a current-limit setting."""
+1
View File
@@ -168,3 +168,4 @@ def test_all_entity_state_translation_keys_are_lowercase():
for translation in platform.values():
for state_key in translation.get("state", {}):
assert state_key == state_key.lower()
+140
View File
@@ -0,0 +1,140 @@
"""Tests for Samsung water-purifier support (issue #90, TP2X_WATERPURIFIER_20K).
HA-free like the rest of the suite: exercises the registry, discovery/
flatten, and the write contracts.
"""
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.discovery import discover
from custom_components.localthings.registry.entities import NumberDesc
from tests.conftest import _load_device
def _water_purifier():
resources = _load_device('water_purifier')
info = resources['/information/vs/0']
reg = for_device_by_model(
info['x.com.samsung.da.modelNum'], info['x.com.samsung.da.description'],
)
return reg, resources
def _bound():
reg, resources = _water_purifier()
return discover(resources, reg.capabilities, reg.pattern_capabilities), resources
def _state():
bound, resources = _bound()
return flatten(bound, resources)
def _desc(key):
bound, _ = _bound()
return next(b.desc for b in bound if b.desc.key == key)
def test_model_resolves_to_water_purifier_registry():
reg, _ = _water_purifier()
assert reg is not None and reg.name == 'water_purifier'
def test_no_unbound_hrefs():
"""Every resource in the issue #90 dump binds or is covered -- clears
the coverage-gap repair."""
reg, resources = _water_purifier()
unbound = []
discover(resources, reg.capabilities, reg.pattern_capabilities, log=unbound.append)
assert unbound == []
def test_expected_state_keys_present():
state = _state()
for key in ('dispense_type', 'hot_water_temperature', 'dispense_capacity',
'pouring', 'waterpurifier_status', 'filter_usage', 'filter_status',
'hotwater_lock', 'coldwater_lock', 'buzz_lock'):
assert key in state, key
def test_dispense_type_options_come_from_live_supported_types():
desc = _desc('dispense_type')
assert desc.options_field == 'x.com.samsung.da.supportedTypes'
def test_dispense_type_write_contract():
desc = _desc('dispense_type')
path, body = desc.write_fn('hotwater', {})
assert path == ['setting', 'waterpurifier', 'vs', '0']
assert body == {'x.com.samsung.da.desiredType': 'hotwater'}
def test_hot_water_temperature_is_a_select_not_a_number():
"""Only a handful of discrete temperatures are selectable (not a
continuous range) -- confirmed by supportedHotTemperatures being a short
enumerated list, not a [min, max] range field."""
desc = _desc('hot_water_temperature')
assert desc.options_field == 'x.com.samsung.da.supportedHotTemperatures'
def test_dispense_capacity_bounds_come_live_not_hardcoded():
"""Bounds and step come from the device's own desiredCapacityRange/
capacityResolution fields, not a hardcoded constant -- see the
adding-device-support skill's 'never hard-code the one dump's values'
section."""
desc = _desc('dispense_capacity')
assert isinstance(desc, NumberDesc)
assert desc.native_min is None
assert desc.native_max is None
assert desc.range_field == 'x.com.samsung.da.desiredCapacityRange'
rep = {'x.com.samsung.da.desiredCapacityRange': ['50', '2000'],
'x.com.samsung.da.capacityResolution': '10'}
assert desc.step_fn(rep) == 10
def test_dispense_capacity_write_contract():
desc = _desc('dispense_capacity')
path, body = desc.write_fn('550', {})
assert path == ['setting', 'waterpurifier', 'vs', '0']
assert body == {'x.com.samsung.da.desiredCapacity': '550'}
def test_lock_switches_read_unlocked_as_off():
state = _state()
assert state['hotwater_lock'] is False
assert state['coldwater_lock'] is False
assert state['buzz_lock'] is False
def test_lock_switch_write_contracts():
hot = _desc('hotwater_lock')
cold = _desc('coldwater_lock')
buzz = _desc('buzz_lock')
assert hot.write_fn('On', {}) == (
['status', 'lock', 'vs', '0'], {'x.com.samsung.da.hotwaterLock': 'Locked'})
assert cold.write_fn('Off', {}) == (
['status', 'lock', 'vs', '0'], {'x.com.samsung.da.coldwaterLock': 'Unlocked'})
assert buzz.write_fn('On', {}) == (
['status', 'lock', 'vs', '0'], {'x.com.samsung.da.buzzLock': 'Locked'})
def test_favorite_capacity_options_come_from_live_capacity_list():
desc = _desc('favorite_capacity')
assert desc.options_field == 'x.com.samsung.da.capacityList'
def test_sterilize_timestamps_parsed_as_utc():
state = _state()
assert state['sterilize_last_time'].tzinfo is not None
assert state['sterilize_plan_time'].tzinfo is not None
def test_mode_hrefs_are_ignored_not_guessed():
"""/mode/vs/0's supportedModes carries a single opaque wizard token and
modes reports an unrelated value not even in supportedModes -- internal
plumbing, left unmodeled per the 'don't guess' rule rather than exposed
as a nonsensical select."""
from custom_components.localthings.registry.capabilities import water_purifier
ignored_hrefs = {cap.href for cap in water_purifier.COVERAGE}
assert '/mode/vs/0' in ignored_hrefs
assert '/automation/waterpurifier/vs/0' in ignored_hrefs