Add AC UV LED, ventilation alarm, and PM1 filter support (issue #270)
TP1X_FAC_TIME_23K reports three previously unbound hrefs: a UV-C sterilization LED and a ventilation-reminder alarm (both plain On/Off toggles), and a second PM1-rated dust filter with no live usage/status fields on this particular dump. The PM1 filter capability gates each entity on its own field's presence rather than a blanket ignore, since the TP1X_DA-AC-CAC-01001_0000 cassette AC (issue #191) reports the same href with full live data -- this also closes two of that device's ten documented coverage gaps (UV LED and the PM1 filter) as a side effect.
This commit is contained in:
@@ -33,8 +33,11 @@ REGISTRY = DeviceRegistry(
|
||||
airconditioner.AIR_PURIFY,
|
||||
airconditioner.AUTO_CLEAN,
|
||||
airconditioner.AIR_FILTER,
|
||||
airconditioner.AIR_FILTER_PM1,
|
||||
airconditioner.AIR_QUALITY,
|
||||
airconditioner.DISPLAY_LIGHT,
|
||||
airconditioner.UV_LED,
|
||||
airconditioner.VENTILATION_ALARM,
|
||||
airconditioner.MUTE_ONCE,
|
||||
airconditioner.CURRENT_LIMIT,
|
||||
airconditioner.ANOMALY_LOAD,
|
||||
|
||||
@@ -909,6 +909,80 @@ AIR_FILTER = Capability(
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _pm1_threshold_write(payload, rep, href=None):
|
||||
"""Same contract as _threshold_write, against this filter's own href --
|
||||
not yet confirmed live (no dump seen advertises
|
||||
supportedFilterDesiredUsage for this href), so the Select this backs
|
||||
stays gated behind that field's presence, same as AIR_FILTER's."""
|
||||
return ["filter", "airdustPM1filter", "vs", "0"], {
|
||||
"x.com.samsung.da.filterDesiredUsage": payload,
|
||||
}
|
||||
|
||||
|
||||
def _has_filter_field(field):
|
||||
return lambda rep, resources: rep.get(field) is not None
|
||||
|
||||
|
||||
# Second, PM1-rated dust filter some TP1X_FAC-class boards report alongside
|
||||
# AIR_FILTER's /filter/airdustfilter/vs/0. TP1X_FAC_TIME_23K (issue #270)
|
||||
# reports only filterCapacity/filterCapacityUnit/filterResetType
|
||||
# ('notresetable') on this href, with no live filterUsage/filterStatus at
|
||||
# all -- but the TP1X_DA-AC-CAC-01001_0000 cassette AC (issue #191) reports
|
||||
# the identical href with full live fields. So this stays a real capability
|
||||
# rather than a blanket ignore, with every entity individually gated on its
|
||||
# own field's presence per the 'don't guess' rule: no entity on a board that
|
||||
# has nothing behind it, real readings on one that does.
|
||||
AIR_FILTER_PM1 = Capability(
|
||||
href="/filter/airdustPM1filter/vs/0",
|
||||
poll_tier="cold",
|
||||
entities=(
|
||||
SensorDesc(
|
||||
key="air_filter_pm1_usage",
|
||||
rep_fn=filter_usage_percent,
|
||||
unit="%",
|
||||
state_class="measurement",
|
||||
icon="mdi:air-filter",
|
||||
entity_category="diagnostic",
|
||||
exists_fn=_has_filter_field("x.com.samsung.da.filterUsage"),
|
||||
),
|
||||
SensorDesc(
|
||||
key="air_filter_pm1_usage_hours",
|
||||
field="x.com.samsung.da.filterUsage",
|
||||
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(
|
||||
key="air_filter_pm1_threshold",
|
||||
field="x.com.samsung.da.filterDesiredUsage",
|
||||
options_field="x.com.samsung.da.supportedFilterDesiredUsage",
|
||||
exists_fn=lambda rep, res: bool(
|
||||
rep.get("x.com.samsung.da.supportedFilterDesiredUsage")
|
||||
),
|
||||
icon="mdi:alarm",
|
||||
entity_category="config",
|
||||
write_fn=_pm1_threshold_write,
|
||||
value_fn=lambda v: str(v) if v is not None else None,
|
||||
),
|
||||
SensorDesc(
|
||||
key="air_filter_pm1_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,
|
||||
exists_fn=_has_filter_field("x.com.samsung.da.filterStatus"),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
DISPLAY_LIGHT = Capability(
|
||||
href="/light/vs/0",
|
||||
poll_tier="cold",
|
||||
@@ -927,6 +1001,51 @@ DISPLAY_LIGHT = Capability(
|
||||
),
|
||||
)
|
||||
|
||||
# UV-C sterilization LED (issue #270, TP1X_FAC_TIME_23K). Same On/Off
|
||||
# convention as everything else on this board, and (unlike VENTILATION_ALARM
|
||||
# below) a real supportedModes list confirms the value set.
|
||||
UV_LED = Capability(
|
||||
href="/uvled/vs/0",
|
||||
poll_tier="cold",
|
||||
entities=(
|
||||
SwitchDesc(
|
||||
key="uv_led",
|
||||
field="x.com.samsung.da.modes",
|
||||
icon="mdi:lightbulb-fluorescent-tube",
|
||||
entity_category="config",
|
||||
value_fn=lambda v: v == "On",
|
||||
write_fn=lambda p, rep, href=None: (
|
||||
["uvled", "vs", "0"],
|
||||
{"x.com.samsung.da.modes": "On" if p == "On" else "Off"},
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
# Ventilation-reminder alarm toggle (issue #270). Only a bare `alarm` field
|
||||
# is present -- no supportedModes list to confirm the value set against,
|
||||
# unlike UV_LED above. Modeled as a switch on the strength of the same
|
||||
# On/Off convention used everywhere else in this API; a rejected write is
|
||||
# the worst case for a wrong guess (see the adding-device-support skill),
|
||||
# but this hasn't been round-trip confirmed on real hardware.
|
||||
VENTILATION_ALARM = Capability(
|
||||
href="/ventilation/setting/vs/0",
|
||||
poll_tier="cold",
|
||||
entities=(
|
||||
SwitchDesc(
|
||||
key="ventilation_alarm",
|
||||
field="alarm",
|
||||
icon="mdi:bell-alert",
|
||||
entity_category="config",
|
||||
value_fn=lambda v: v == "On",
|
||||
write_fn=lambda p, rep, href=None: (
|
||||
["ventilation", "setting", "vs", "0"],
|
||||
{"alarm": "On" if p == "On" else "Off"},
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
# Confirmed against issue #38's dump (TP1X_DA-AC-RAC-01001_0000): a single
|
||||
# boolean field, no vendor prefix, mirroring the On/Off convention used
|
||||
# throughout the rest of this API.
|
||||
|
||||
@@ -637,6 +637,9 @@
|
||||
},
|
||||
"watertank_light_brightness": {
|
||||
"name": "Jas osvětlení nádržky na vodu"
|
||||
},
|
||||
"air_filter_pm1_threshold": {
|
||||
"name": "Práh alarmu PM1 filtru"
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
@@ -988,6 +991,12 @@
|
||||
},
|
||||
"watertank_full_alarm_status": {
|
||||
"name": "Stav alarmu plné nádržky na vodu"
|
||||
},
|
||||
"air_filter_pm1_usage": {
|
||||
"name": "Využití PM1 filtru"
|
||||
},
|
||||
"air_filter_pm1_usage_hours": {
|
||||
"name": "Hodiny využití PM1 filtru"
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
@@ -1158,6 +1167,12 @@
|
||||
},
|
||||
"watertank_light": {
|
||||
"name": "Osvětlení nádržky na vodu"
|
||||
},
|
||||
"uv_led": {
|
||||
"name": "UV LED"
|
||||
},
|
||||
"ventilation_alarm": {
|
||||
"name": "Alarm větrání"
|
||||
}
|
||||
},
|
||||
"time": {
|
||||
|
||||
@@ -637,6 +637,9 @@
|
||||
},
|
||||
"watertank_light_brightness": {
|
||||
"name": "Water tank light brightness"
|
||||
},
|
||||
"air_filter_pm1_threshold": {
|
||||
"name": "PM1 filter alarm threshold"
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
@@ -988,6 +991,12 @@
|
||||
},
|
||||
"watertank_full_alarm_status": {
|
||||
"name": "Water tank full alarm status"
|
||||
},
|
||||
"air_filter_pm1_usage": {
|
||||
"name": "PM1 filter usage"
|
||||
},
|
||||
"air_filter_pm1_usage_hours": {
|
||||
"name": "PM1 filter usage hours"
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
@@ -1158,6 +1167,12 @@
|
||||
},
|
||||
"watertank_light": {
|
||||
"name": "Water tank light"
|
||||
},
|
||||
"uv_led": {
|
||||
"name": "UV LED"
|
||||
},
|
||||
"ventilation_alarm": {
|
||||
"name": "Ventilation alarm"
|
||||
}
|
||||
},
|
||||
"time": {
|
||||
|
||||
@@ -751,6 +751,9 @@
|
||||
},
|
||||
"watertank_light_brightness": {
|
||||
"name": "Brillo de la luz del depósito de agua"
|
||||
},
|
||||
"air_filter_pm1_threshold": {
|
||||
"name": "Umbral de alarma del filtro PM1"
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
@@ -1102,6 +1105,12 @@
|
||||
},
|
||||
"watertank_full_alarm_status": {
|
||||
"name": "Estado de alarma de depósito de agua lleno"
|
||||
},
|
||||
"air_filter_pm1_usage": {
|
||||
"name": "Uso del filtro PM1"
|
||||
},
|
||||
"air_filter_pm1_usage_hours": {
|
||||
"name": "Horas de uso del filtro PM1"
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
@@ -1272,6 +1281,12 @@
|
||||
},
|
||||
"watertank_light": {
|
||||
"name": "Luz del depósito de agua"
|
||||
},
|
||||
"uv_led": {
|
||||
"name": "LED UV"
|
||||
},
|
||||
"ventilation_alarm": {
|
||||
"name": "Alarma de ventilación"
|
||||
}
|
||||
},
|
||||
"time": {
|
||||
|
||||
@@ -637,6 +637,9 @@
|
||||
},
|
||||
"watertank_light_brightness": {
|
||||
"name": "Luminosità della luce del serbatoio dell'acqua"
|
||||
},
|
||||
"air_filter_pm1_threshold": {
|
||||
"name": "Soglia di allarme del filtro PM1"
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
@@ -988,6 +991,12 @@
|
||||
},
|
||||
"watertank_full_alarm_status": {
|
||||
"name": "Stato allarme serbatoio d'acqua pieno"
|
||||
},
|
||||
"air_filter_pm1_usage": {
|
||||
"name": "Utilizzo del filtro PM1"
|
||||
},
|
||||
"air_filter_pm1_usage_hours": {
|
||||
"name": "Ore di utilizzo del filtro PM1"
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
@@ -1158,6 +1167,12 @@
|
||||
},
|
||||
"watertank_light": {
|
||||
"name": "Luce del serbatoio dell'acqua"
|
||||
},
|
||||
"uv_led": {
|
||||
"name": "LED UV"
|
||||
},
|
||||
"ventilation_alarm": {
|
||||
"name": "Allarme ventilazione"
|
||||
}
|
||||
},
|
||||
"time": {
|
||||
|
||||
@@ -637,6 +637,9 @@
|
||||
},
|
||||
"watertank_light_brightness": {
|
||||
"name": "Helderheid van het waterreservoirlicht"
|
||||
},
|
||||
"air_filter_pm1_threshold": {
|
||||
"name": "PM1-filteralarmdrempel"
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
@@ -988,6 +991,12 @@
|
||||
},
|
||||
"watertank_full_alarm_status": {
|
||||
"name": "Status alarm waterreservoir vol"
|
||||
},
|
||||
"air_filter_pm1_usage": {
|
||||
"name": "PM1-filtergebruik"
|
||||
},
|
||||
"air_filter_pm1_usage_hours": {
|
||||
"name": "PM1-filtergebruik in uren"
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
@@ -1158,6 +1167,12 @@
|
||||
},
|
||||
"watertank_light": {
|
||||
"name": "Waterreservoirlicht"
|
||||
},
|
||||
"uv_led": {
|
||||
"name": "Uv-led"
|
||||
},
|
||||
"ventilation_alarm": {
|
||||
"name": "Ventilatiealarm"
|
||||
}
|
||||
},
|
||||
"time": {
|
||||
|
||||
@@ -0,0 +1,641 @@
|
||||
{
|
||||
"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": "true",
|
||||
"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": [
|
||||
"112",
|
||||
"224",
|
||||
"336",
|
||||
"448"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/filter/airdustPM1filter/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.filterCapacity": "3",
|
||||
"x.com.samsung.da.filterCapacityUnit": "Month",
|
||||
"x.com.samsung.da.filterResetType": [
|
||||
"notresetable"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/temperature/control/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.increment": "0.5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/mode/convenient/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.modes": "Off",
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"Off",
|
||||
"Sleep",
|
||||
"Nano",
|
||||
"NanoSleep"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/option/airpurify/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.modes": "Off",
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"On",
|
||||
"Off"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/option/autoclean/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.status": "Stop",
|
||||
"x.com.samsung.da.settingStatus": "On",
|
||||
"x.com.samsung.da.progress": "0",
|
||||
"x.com.samsung.da.supportedStatus": [
|
||||
"Start",
|
||||
"SpeedClean",
|
||||
"QuietClean",
|
||||
"Stop"
|
||||
],
|
||||
"x.com.samsung.da.supportedSettingStatus": [
|
||||
"On",
|
||||
"SpeedClean",
|
||||
"QuietClean",
|
||||
"Off"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/wind/strength/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.modes": "12",
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"0",
|
||||
"11",
|
||||
"12",
|
||||
"13",
|
||||
"14",
|
||||
"15",
|
||||
"16"
|
||||
],
|
||||
"x.com.samsung.da.modesName": [
|
||||
"Auto",
|
||||
"1",
|
||||
"2",
|
||||
"3",
|
||||
"4",
|
||||
"5",
|
||||
"Max"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/wind/direction/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.modes": "NotSupported",
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"NotSupported"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/subdevices/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.subdeviceIdList": "**REDACTED**"
|
||||
}
|
||||
},
|
||||
{
|
||||
"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-08-03T14:14:25"
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "2",
|
||||
"x.com.samsung.da.description": "Alarm",
|
||||
"x.com.samsung.da.alarmType": "Device",
|
||||
"x.com.samsung.da.code": "AC_V_0002_OFF",
|
||||
"x.com.samsung.da.triggeredTime": "2026-08-03T14:14:25"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"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": "27.0",
|
||||
"x.com.samsung.da.current": "28.0",
|
||||
"x.com.samsung.da.maximum": "30",
|
||||
"x.com.samsung.da.minimum": "18",
|
||||
"x.com.samsung.da.increment": "0.5",
|
||||
"x.com.samsung.da.unit": "Celsius"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/temperature/current/0",
|
||||
"rep": {
|
||||
"range": [
|
||||
18.0,
|
||||
30.0
|
||||
],
|
||||
"units": "C",
|
||||
"temperature": 28.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/temperature/desired/0",
|
||||
"rep": {
|
||||
"range": [
|
||||
18.0,
|
||||
30.0
|
||||
],
|
||||
"units": "C",
|
||||
"temperature": 27.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/diagnosis/vs/0",
|
||||
"rep": {}
|
||||
},
|
||||
{
|
||||
"href": "/energy/consumption/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.cumulativeConsumption": "400.000000",
|
||||
"x.com.samsung.da.instantaneousPower": "14.000000",
|
||||
"x.com.samsung.da.cumulativePower": "882861",
|
||||
"x.com.samsung.da.cumulativeSavedPower": "0",
|
||||
"x.com.samsung.da.cumulativeDate": "1785798000",
|
||||
"x.com.samsung.da.cumulativeUnit": "Wh",
|
||||
"x.com.samsung.da.instantaneousPowerUnit": "W",
|
||||
"x.com.samsung.da.cumulativePowerType": "individual"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/energy/consumption/0",
|
||||
"rep": {
|
||||
"energy": 400.0,
|
||||
"power": 14.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/mode/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"AIComfort",
|
||||
"Cool",
|
||||
"Dry",
|
||||
"Wind"
|
||||
],
|
||||
"x.com.samsung.da.modes": [
|
||||
"Cool"
|
||||
],
|
||||
"x.com.samsung.da.options": [
|
||||
"ArtificialWorking_Off",
|
||||
"welcomecare_Off",
|
||||
"Volume_Mute",
|
||||
"Operation_Family",
|
||||
"ComfortAICooling_Off",
|
||||
"OnTimer_0",
|
||||
"OffTimer_0",
|
||||
"Sleep_0",
|
||||
"Booster_Off",
|
||||
"OutdoorSilence_Off",
|
||||
"AiTempChanged_Off",
|
||||
"AiTemp_250",
|
||||
"StopAutoClean_Idle",
|
||||
"Weather_Off",
|
||||
"Service_Off",
|
||||
"SmartCoolClean_Off",
|
||||
"ProgressSmartClean_0",
|
||||
"OutDoorVentil_Off",
|
||||
"InDoorVentil_Off",
|
||||
"FreezeAlarmSetting_Off",
|
||||
"DesiredFreezeAlarm_240",
|
||||
"ProtectOperation_Off",
|
||||
"EnergySaveIcon_Off",
|
||||
"ExtendOptionCode_16975",
|
||||
"OptionCode_2589",
|
||||
"RacInfo_First",
|
||||
"ModelInfo_16K_BORA_VENT2",
|
||||
"RacInfo_None_Second",
|
||||
"OutdoorConnection_Connected",
|
||||
"DurationOn_0",
|
||||
"UpdateAllow_NotAllowed",
|
||||
"welcomecareElapsedTime_0",
|
||||
"welcomecareThresholdTemp_0",
|
||||
"welcomecareStartDate_0000",
|
||||
"welcomecareEndDate_0000",
|
||||
"welcomecareSeason_None"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/power/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.power": "Off",
|
||||
"causeSource": "SMTS"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/power/0",
|
||||
"rep": {
|
||||
"value": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/information/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.modelNum": "TP1X_FAC_TIME_23K|10250841|600007110017112BCE00CE2200937000",
|
||||
"x.com.samsung.da.description": "TP1X_FAC_TIME_23K",
|
||||
"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": "AF2",
|
||||
"x.com.samsung.da.diagMinVersion": "3.0",
|
||||
"x.com.samsung.da.diagTsId": "DA01",
|
||||
"x.com.samsung.da.serialNumOption": "**REDACTED**",
|
||||
"x.com.samsung.da.items": [
|
||||
{
|
||||
"x.com.samsung.da.id": "0",
|
||||
"x.com.samsung.da.description": "Version",
|
||||
"x.com.samsung.da.type": "Software",
|
||||
"x.com.samsung.da.number": "02517A260330",
|
||||
"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": "02508A23020200,02509A24030701",
|
||||
"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": "02502A23011300,02299A10000300"
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "3",
|
||||
"x.com.samsung.da.description": "Version",
|
||||
"x.com.samsung.da.type": "LED",
|
||||
"x.com.samsung.da.number": "02510A22121900,FFFFFFFFFFFFFF"
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "4",
|
||||
"x.com.samsung.da.description": "Version",
|
||||
"x.com.samsung.da.type": "BLERemoteController",
|
||||
"x.com.samsung.da.number": "22021760"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"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": "1138000000",
|
||||
"x.com.samsung.da.airconOptionList": [
|
||||
"HOMECARE_WIZARD_V2",
|
||||
"ENERGY_2.0",
|
||||
"AI_2.0",
|
||||
"DeviceTypeMaster",
|
||||
"CLEAN_OPTION_SUPPORT",
|
||||
"DR",
|
||||
"SingleCommand_1"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/humidity/0",
|
||||
"rep": {
|
||||
"humidity": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/humidity/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.humidity": "0.000000",
|
||||
"x.com.samsung.da.fivepercentHumidity": "71"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/drlc/0",
|
||||
"rep": {
|
||||
"DRLevel": 0,
|
||||
"start": "1970-01-01T00:00:00Z",
|
||||
"durationminutes": 0,
|
||||
"override": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"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": "/runn/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.runningMode": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/availablecontrolsets/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.sets": "000000B4012C0000000D04000000",
|
||||
"x.com.samsung.da.id": "FAC",
|
||||
"x.com.samsung.da.version": "1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/keepnormalstate/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.keepnormal": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/uvled/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.modes": "On",
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"On",
|
||||
"Off"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/remotedatacontrol/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.status": "Off",
|
||||
"x.com.samsung.da.connectionStatus": "Disconnected"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/remotetemperature/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.temperature": "",
|
||||
"x.com.samsung.da.unit": "",
|
||||
"x.com.samsung.da.error": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"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": "/remotedeviceinfo/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.didList": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"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": "AFA-WW-TP1-23-AFXX00",
|
||||
"versions": [
|
||||
"10260330"
|
||||
],
|
||||
"visVersion": "260330"
|
||||
},
|
||||
{
|
||||
"type": "Micom",
|
||||
"modelId": "845110250241FFFFFFFF",
|
||||
"versions": [
|
||||
"23011300",
|
||||
"FFFFFFFF"
|
||||
],
|
||||
"visVersion": "230113"
|
||||
},
|
||||
{
|
||||
"type": "Micom",
|
||||
"modelId": "04511025084110250941",
|
||||
"versions": [
|
||||
"23020200",
|
||||
"24030701"
|
||||
],
|
||||
"visVersion": "240307"
|
||||
},
|
||||
{
|
||||
"type": "Micom",
|
||||
"modelId": "04511025024110229941",
|
||||
"versions": [
|
||||
"23011300",
|
||||
"10000300"
|
||||
],
|
||||
"visVersion": "230113"
|
||||
},
|
||||
{
|
||||
"type": "Micom",
|
||||
"modelId": "045110240441FFFFFFFF",
|
||||
"versions": [
|
||||
"23121900",
|
||||
"FFFFFFFF"
|
||||
],
|
||||
"visVersion": "231219"
|
||||
},
|
||||
{
|
||||
"type": "Micom",
|
||||
"modelId": "045110251041FFFFFFFF",
|
||||
"versions": [
|
||||
"22121900",
|
||||
"FFFFFFFF"
|
||||
],
|
||||
"visVersion": "221219"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/option/muteonce/vs/0",
|
||||
"rep": {
|
||||
"muteonce": "Off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/selfcheck/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.start": "Cancel",
|
||||
"x.com.samsung.da.status": "Ready",
|
||||
"x.com.samsung.da.progress": "1",
|
||||
"x.com.samsung.da.result": "NA",
|
||||
"x.com.samsung.da.supportedActions": [
|
||||
"Start",
|
||||
"Cancel"
|
||||
],
|
||||
"x.com.samsung.da.error": [
|
||||
"DA_ERROR_NONE"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/reserverulesets/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.sets": "1DFFFFFFFF121E121EFFFFFFFFFFFF01000000013F0100013F000000001C0C",
|
||||
"x.com.samsung.da.id": "FAC",
|
||||
"x.com.samsung.da.version": "1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/ventilation/setting/vs/0",
|
||||
"rep": {
|
||||
"alarm": "Off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/light/vs/0",
|
||||
"rep": {
|
||||
"mode": "Off",
|
||||
"supportedModes": [
|
||||
"On",
|
||||
"Off"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"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": "**REDACTED**"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/quickcontrol/info/vs/0",
|
||||
"rep": {
|
||||
"supportedVersion": "1.0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
+5
-1
@@ -2,6 +2,9 @@
|
||||
"state_keys": [
|
||||
"absence_power_saving_active",
|
||||
"absence_power_saving_mode",
|
||||
"air_filter_pm1_status",
|
||||
"air_filter_pm1_usage",
|
||||
"air_filter_pm1_usage_hours",
|
||||
"air_filter_status",
|
||||
"air_filter_usage",
|
||||
"air_filter_usage_hours",
|
||||
@@ -29,6 +32,7 @@
|
||||
"selfcheck_result",
|
||||
"selfcheck_status",
|
||||
"super_fine_dust",
|
||||
"tropical_night_mode"
|
||||
"tropical_night_mode",
|
||||
"uv_led"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"state_keys": [
|
||||
"air_filter_status",
|
||||
"air_filter_threshold",
|
||||
"air_filter_usage",
|
||||
"air_filter_usage_hours",
|
||||
"air_purify",
|
||||
"alarm_code",
|
||||
"auto_clean",
|
||||
"auto_clean_progress",
|
||||
"auto_clean_running",
|
||||
"beep",
|
||||
"climate",
|
||||
"current_temperature_c",
|
||||
"diagnosis_status",
|
||||
"display_light",
|
||||
"energy_kwh",
|
||||
"energy_saved_kwh",
|
||||
"firmware_update",
|
||||
"humidity",
|
||||
"mute_once",
|
||||
"odor_controller_active",
|
||||
"odor_controller_progress",
|
||||
"power_energy_kwh",
|
||||
"power_watts",
|
||||
"selfcheck_error",
|
||||
"selfcheck_result",
|
||||
"selfcheck_status",
|
||||
"tropical_night_mode",
|
||||
"uv_led",
|
||||
"ventilation_alarm"
|
||||
]
|
||||
}
|
||||
@@ -6,12 +6,18 @@ token -- this board was the one exception (its oneUiVersion self-reports
|
||||
"7.0 Air conditioner", but 'CAC' had never been added to the board-token
|
||||
table), so it silently fell back to common caps and lost its climate entity.
|
||||
|
||||
This dump is NOT fully covered yet -- ten hrefs remain unbound (edge
|
||||
lighting, PM1 filter, a second stateful light resource, absence-clean, four
|
||||
`/settings/sound/*` resources, smart-sensing-cooling, UV LED), all genuinely
|
||||
new to this board generation. That's a real device-support gap, left
|
||||
documented here rather than guessed at, per the 'don't guess' rule -- fixing
|
||||
the routing regression was the scope of #191.
|
||||
This dump is NOT fully covered yet -- eight hrefs remain unbound (edge
|
||||
lighting, a second stateful light resource, absence-clean, four
|
||||
`/settings/sound/*` resources, smart-sensing-cooling), all genuinely new to
|
||||
this board generation. That's a real device-support gap, left documented
|
||||
here rather than guessed at, per the 'don't guess' rule -- fixing the
|
||||
routing regression was the scope of #191.
|
||||
|
||||
/uvled/vs/0 and /filter/airdustPM1filter/vs/0 used to be on this list too,
|
||||
until issue #270 (TP1X_FAC_TIME_23K) added real capabilities for both --
|
||||
this board's own live filterUsage/filterStatus data on the PM1 filter binds
|
||||
through the same exists_fn-gated entities #270's dump (which has neither
|
||||
field) leaves empty.
|
||||
"""
|
||||
|
||||
from custom_components.localthings.registry.adapter import flatten
|
||||
@@ -22,7 +28,6 @@ from tests.conftest import _load_device
|
||||
_STILL_UNBOUND = frozenset(
|
||||
{
|
||||
"/edgelighting/vs/0",
|
||||
"/filter/airdustPM1filter/vs/0",
|
||||
"/light/stateful/vs/0",
|
||||
"/mds/absenceclean/vs/0",
|
||||
"/settings/sound/mode/vs/0",
|
||||
@@ -30,7 +35,6 @@ _STILL_UNBOUND = frozenset(
|
||||
"/settings/sound/output/vs/0",
|
||||
"/settings/sound/volume/vs/0",
|
||||
"/smartsensingcooling/vs/0",
|
||||
"/uvled/vs/0",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
"""Tests for the TP1X_FAC_TIME_23K air conditioner (issue #270).
|
||||
|
||||
A 2-in-1 system (x.com.samsung.da.numofsubdevice='2') whose UUID-prefixed
|
||||
sibling materializes via the flat fallback (issue #205) -- unrelated to the
|
||||
three capability gaps this fixture actually locks in (UV LED, ventilation
|
||||
alarm, and the unmodeled PM1 filter), which live entirely on canonical
|
||||
hrefs already exercised by the master's own resources.
|
||||
"""
|
||||
|
||||
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 SwitchDesc
|
||||
from tests.conftest import _load_device
|
||||
|
||||
|
||||
def _airconditioner():
|
||||
resources = _load_device("airconditioner_tp1x_fac_time_23k")
|
||||
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 = _airconditioner()
|
||||
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_airconditioner_registry():
|
||||
reg, _ = _airconditioner()
|
||||
assert reg is not None and reg.name == "airconditioner"
|
||||
|
||||
|
||||
def test_no_unbound_hrefs():
|
||||
reg, resources = _airconditioner()
|
||||
unbound = []
|
||||
discover(resources, reg.capabilities, reg.pattern_capabilities, log=unbound.append)
|
||||
assert unbound == []
|
||||
|
||||
|
||||
def test_uv_led_switch_reads_on_and_has_write_contract():
|
||||
state = _state()
|
||||
assert state["uv_led"] is True
|
||||
desc = _desc("uv_led")
|
||||
assert isinstance(desc, SwitchDesc)
|
||||
path, body = desc.write_fn("Off", {})
|
||||
assert path == ["uvled", "vs", "0"]
|
||||
assert body == {"x.com.samsung.da.modes": "Off"}
|
||||
|
||||
|
||||
def test_ventilation_alarm_switch_reads_off_and_has_write_contract():
|
||||
state = _state()
|
||||
assert state["ventilation_alarm"] is False
|
||||
desc = _desc("ventilation_alarm")
|
||||
assert isinstance(desc, SwitchDesc)
|
||||
path, body = desc.write_fn("On", {})
|
||||
assert path == ["ventilation", "setting", "vs", "0"]
|
||||
assert body == {"alarm": "On"}
|
||||
|
||||
|
||||
def test_pm1_filter_has_no_live_fields_so_no_state_is_reported():
|
||||
"""This dump's /filter/airdustPM1filter/vs/0 has only
|
||||
filterCapacity/filterCapacityUnit/filterResetType -- no filterUsage or
|
||||
filterStatus -- so every AIR_FILTER_PM1 entity's exists_fn gates it out.
|
||||
The href still covers (see test_no_unbound_hrefs above) without
|
||||
fabricating a reading from nothing (the 'don't guess' rule); contrast
|
||||
with test_airconditioner_cac.py, whose dump has live data on the same
|
||||
href and does get real air_filter_pm1_* state."""
|
||||
state = _state()
|
||||
assert not any(k.startswith("air_filter_pm1_") for k in state)
|
||||
@@ -1243,6 +1243,26 @@ def test_registry_reproduces_golden_state_keys_for_dehumidifier_tp1x_dhm01001():
|
||||
)
|
||||
|
||||
|
||||
def test_registry_reproduces_golden_state_keys_for_airconditioner_tp1x_fac_time_23k():
|
||||
"""TP1X_FAC_TIME_23K (issue #270) reports a UV-C LED, a ventilation-alarm
|
||||
toggle, and a second (PM1-rated, no live usage field) dust filter, none
|
||||
previously modeled. Also a 2-in-1 system on this dump (numofsubdevice=2,
|
||||
a UUID-prefixed sibling materializes via the flat fallback) -- the three
|
||||
new/ignored hrefs are fixed on their canonical form so the sibling picks
|
||||
them up too, per the adding-device-support skill's 'never index or
|
||||
prefix a registry href' rule."""
|
||||
from tests.conftest import _load_device
|
||||
|
||||
resources = _load_device("airconditioner_tp1x_fac_time_23k")
|
||||
golden = json.loads((GOLDEN / "airconditioner_tp1x_fac_time_23k.json").read_text())
|
||||
state_keys = _new_state_keys("airconditioner_tp1x_fac_time_23k", resources)
|
||||
assert set(state_keys) == set(golden["state_keys"]), (
|
||||
f"state_keys mismatch:\n"
|
||||
f" extra: {sorted(set(state_keys) - set(golden['state_keys']))}\n"
|
||||
f" missing: {sorted(set(golden['state_keys']) - set(state_keys))}"
|
||||
)
|
||||
|
||||
|
||||
def test_resources_from_batch_preferred_over_flat():
|
||||
from tests.conftest import _resources_from_dump
|
||||
|
||||
|
||||
Reference in New Issue
Block a user