Add humidity/temperature sensors and horizontal swing for AC (issue #75)
Three gaps reported against an ARTIK051_PRAC_20K WindFree unit vs. the SmartThings integration: 1. Missing WindFree/motion convenient-mode presets -- left alone here. PR #91 replaces climate.py's static _DEVICE_TO_PRESET table with a generic resolver that reads any preset code straight off the unit's own supportedModes, which already covers this (and more generically than a per-model dict would) -- adding one here would just conflict. 2. No horizontal oscillation: /wind/direction/vs/0's supportedModes includes Left_And_Right, which _DEVICE_TO_SWING had no mapping for. Add it to HA's standard 'horizontal' swing constant. 3. No standalone humidity/current-temperature sensors: the climate card already reads both internally, but nothing exposed them as entities for history/automations. Add CURRENT_TEMPERATURE (OCF /temperature/current/0) with a CURRENT_TEMPERATURE_VS vendor fallback (same match_fn-gated pair shape as common.py's POWER_GENERIC/ POWER_VS_FALLBACK), and a HUMIDITY sensor reading /humidity/vs/0's fivepercentHumidity field -- the only one of the three humidity-shaped fields across /humidity/0 and /humidity/vs/0 that isn't permanently stuck at 0 on every dump seen. Regenerates the five existing AC goldens (all pick up current_temperature_c; most pick up humidity) and adds a dedicated fixture from the issue's WindFree dump, whose /humidity/vs/0 actually has live fivepercentHumidity data.
This commit is contained in:
@@ -84,6 +84,7 @@ _DEVICE_TO_SWING: dict[str, str] = {
|
||||
'Fix': 'off',
|
||||
'All': 'both',
|
||||
'Up_And_Low': 'vertical',
|
||||
'Left_And_Right': 'horizontal', # issue #75
|
||||
}
|
||||
_SWING_TO_DEVICE = {v: k for k, v in _DEVICE_TO_SWING.items()}
|
||||
|
||||
|
||||
@@ -25,6 +25,9 @@ REGISTRY = DeviceRegistry(
|
||||
airconditioner.DISPLAY_LIGHT,
|
||||
airconditioner.MUTE_ONCE,
|
||||
airconditioner.CURRENT_LIMIT,
|
||||
airconditioner.CURRENT_TEMPERATURE,
|
||||
airconditioner.CURRENT_TEMPERATURE_VS,
|
||||
airconditioner.HUMIDITY,
|
||||
*airconditioner.COVERAGE,
|
||||
]),
|
||||
)
|
||||
|
||||
@@ -16,6 +16,7 @@ by_type registry.
|
||||
"""
|
||||
from ..capability import Capability
|
||||
from ..entities import BinarySensorDesc, ClimateDesc, SensorDesc, SwitchDesc
|
||||
from .common import normalize_temp_unit
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Canonical AC resource hrefs. The climate entity (climate.py) binds the
|
||||
@@ -50,6 +51,25 @@ def _num(v):
|
||||
return None
|
||||
|
||||
|
||||
def _temps_vs_item(rep):
|
||||
"""First item of the vendor `/temperatures/vs/0` items[] array -- the
|
||||
Tizen Lite board's only current-temperature source (see climate.py's
|
||||
identical helper; duplicated rather than imported to avoid a
|
||||
capabilities<->platform import cycle)."""
|
||||
items = rep.get('x.com.samsung.da.items')
|
||||
if isinstance(items, (list, tuple)) and items and isinstance(items[0], dict):
|
||||
return items[0]
|
||||
return {}
|
||||
|
||||
|
||||
def _temps_vs_current(rep):
|
||||
return _num(_temps_vs_item(rep).get('x.com.samsung.da.current'))
|
||||
|
||||
|
||||
def _temps_vs_unit(rep):
|
||||
return normalize_temp_unit(_temps_vs_item(rep).get('x.com.samsung.da.unit'), '°C')
|
||||
|
||||
|
||||
def _filter_usage_percent(rep):
|
||||
"""Filter usage as a percentage of rated capacity. The device reports
|
||||
`filterUsage` as a raw count in `filterCapacityUnit` (Hours here, e.g.
|
||||
@@ -199,6 +219,48 @@ CURRENT_LIMIT = Capability(
|
||||
),
|
||||
)
|
||||
|
||||
# The climate entity already surfaces current_temperature as a card
|
||||
# attribute, but that's not enough for history graphs/automations/
|
||||
# statistics -- issue #75 asked for a standalone sensor. Same OCF-standard-
|
||||
# with-vendor-fallback shape as common.py's POWER_GENERIC/POWER_VS_FALLBACK
|
||||
# pair: both share key='current_temperature_c' so only one ever binds
|
||||
# (match_fn gates the vendor one off when the OCF resource is present).
|
||||
CURRENT_TEMPERATURE = Capability(
|
||||
href=HREF_TEMP_CURRENT,
|
||||
poll_tier='warm',
|
||||
entities=(
|
||||
SensorDesc(key='current_temperature_c', field='temperature',
|
||||
device_class='temperature', state_class='measurement',
|
||||
unit_fn=lambda rep: normalize_temp_unit(rep.get('units'), '°C')),
|
||||
),
|
||||
)
|
||||
|
||||
CURRENT_TEMPERATURE_VS = Capability(
|
||||
href=HREF_TEMPS_VS,
|
||||
poll_tier='warm',
|
||||
match_fn=lambda rep, resources: HREF_TEMP_CURRENT not in resources,
|
||||
entities=(
|
||||
SensorDesc(key='current_temperature_c', rep_fn=_temps_vs_current,
|
||||
device_class='temperature', state_class='measurement',
|
||||
unit_fn=_temps_vs_unit),
|
||||
),
|
||||
)
|
||||
|
||||
# Only the vendor resource's `fivepercentHumidity` (current reading, rounded
|
||||
# to the nearest 5%) has live data on the issue #75 dump -- its `humidity`
|
||||
# field, and the OCF-standard /humidity/0 resource entirely, both read a
|
||||
# stuck "0" there and stay ignored per the 'don't guess' rule (see
|
||||
# _AC_IGNORED below).
|
||||
HUMIDITY = Capability(
|
||||
href='/humidity/vs/0',
|
||||
poll_tier='warm',
|
||||
entities=(
|
||||
SensorDesc(key='humidity', field='x.com.samsung.da.fivepercentHumidity',
|
||||
device_class='humidity', state_class='measurement', unit='%',
|
||||
value_fn=_num),
|
||||
),
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# AC-scoped coverage: the CLIMATE_CONSUMED_HREFS above (read by the climate
|
||||
# entity) plus vendor duplicates / all-zero-ambiguous / plumbing resources.
|
||||
@@ -221,8 +283,10 @@ _AC_IGNORED = [
|
||||
# All-zero and ambiguously encoded on this model (2-value arrays); the
|
||||
# 'don't guess' rule -- leave unmodeled rather than invent entities.
|
||||
'/sensors/vs/0',
|
||||
# Stuck at "0" on every dump seen -- HUMIDITY above reads the vendor
|
||||
# resource's usable fivepercentHumidity field instead; this OCF-standard
|
||||
# one has no corresponding live value confirmed yet.
|
||||
'/humidity/0',
|
||||
'/humidity/vs/0',
|
||||
# Presence-personalization plumbing (empty item list here).
|
||||
'/personality/presence/vs/0',
|
||||
# --- TP1X/TP2X-class housekeeping / opaque blobs. These carry no
|
||||
@@ -251,8 +315,11 @@ _AC_IGNORED = [
|
||||
]
|
||||
|
||||
# Built as bare no-entity caps; folded into the AC registry (not global).
|
||||
# HREF_TEMP_CURRENT and HREF_TEMPS_VS are excluded here -- CURRENT_TEMPERATURE
|
||||
# / CURRENT_TEMPERATURE_VS above already cover those two with real entities.
|
||||
COVERAGE = [
|
||||
Capability(href=h, poll_tier='warm') for h in CLIMATE_CONSUMED_HREFS
|
||||
if h not in (HREF_TEMP_CURRENT, HREF_TEMPS_VS)
|
||||
] + [
|
||||
Capability(href=h) for h in _AC_IGNORED
|
||||
]
|
||||
|
||||
@@ -457,6 +457,9 @@
|
||||
"current_temp_c": {
|
||||
"name": "Temperature"
|
||||
},
|
||||
"current_temperature_c": {
|
||||
"name": "Temperature"
|
||||
},
|
||||
"diagnosis": {
|
||||
"name": "Diagnosis"
|
||||
},
|
||||
@@ -526,6 +529,9 @@
|
||||
"hood_filter_usage": {
|
||||
"name": "Filter usage"
|
||||
},
|
||||
"humidity": {
|
||||
"name": "Humidity"
|
||||
},
|
||||
"instance_temperature": {
|
||||
"name": "{instance_name} temperature"
|
||||
},
|
||||
|
||||
@@ -457,6 +457,9 @@
|
||||
"current_temp_c": {
|
||||
"name": "Temperatuur"
|
||||
},
|
||||
"current_temperature_c": {
|
||||
"name": "Temperatuur"
|
||||
},
|
||||
"diagnosis": {
|
||||
"name": "Diagnose"
|
||||
},
|
||||
@@ -526,6 +529,9 @@
|
||||
"hood_filter_usage": {
|
||||
"name": "Filterverbruik"
|
||||
},
|
||||
"humidity": {
|
||||
"name": "Luchtvochtigheid"
|
||||
},
|
||||
"instance_temperature": {
|
||||
"name": "Temperatuur {instance_name}"
|
||||
},
|
||||
|
||||
+380
@@ -0,0 +1,380 @@
|
||||
{
|
||||
"device0": [
|
||||
{
|
||||
"rt": [
|
||||
"x.com.samsung.devcol",
|
||||
"oic.wk.col"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.ll",
|
||||
"oic.if.b"
|
||||
]
|
||||
},
|
||||
{
|
||||
"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-25T08:19:03"
|
||||
},
|
||||
{
|
||||
"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-25T08:19:03"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/configuration/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.region": "0000000000",
|
||||
"x.com.samsung.da.airconOptionList": [
|
||||
"SingleCommand_1",
|
||||
"HOMECARE_WIZARD_V2",
|
||||
"PRODUCT_GLOBAL",
|
||||
"AI_RAC_GLOBAL_2.0",
|
||||
"AI_2.0",
|
||||
"Auto_To_AI",
|
||||
"AI_Heat"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/diagnosis/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.diagnosisStart": "Ready"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/energy/consumption/0",
|
||||
"rep": {
|
||||
"power": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/energy/consumption/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.instantaneousPower": "0",
|
||||
"x.com.samsung.da.cumulativePower": "320466",
|
||||
"x.com.samsung.da.cumulativeUnit": "Wh",
|
||||
"x.com.samsung.da.instantaneousPowerUnit": "W"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/file/information/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.timeoffset": "+00:00",
|
||||
"x.com.samsung.supprtedtype": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/filter/airdustfilter/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.filterUsage": "41",
|
||||
"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": "42"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/information/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.modelNum": "ARTIK051_PRAC_20K|10217841|60010519001411010200001000000000",
|
||||
"x.com.samsung.da.description": "ARTIK051_PRAC_20K",
|
||||
"x.com.samsung.da.serialNum": "**REDACTED**",
|
||||
"x.com.samsung.da.otnDUID": "**REDACTED**",
|
||||
"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": "02181A230313",
|
||||
"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": "20082000,FFFFFFFF",
|
||||
"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": "20091600,10000400"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/mode/convenient/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.modes": "Off",
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"Off",
|
||||
"Sleep",
|
||||
"Quiet",
|
||||
"Smart",
|
||||
"Speed",
|
||||
"MotionIndirect",
|
||||
"MotionDirect",
|
||||
"Nano",
|
||||
"NanoSleep"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/mode/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"Cool",
|
||||
"Dry",
|
||||
"Wind",
|
||||
"Auto",
|
||||
"Heat"
|
||||
],
|
||||
"x.com.samsung.da.modes": [
|
||||
"Dry"
|
||||
],
|
||||
"x.com.samsung.da.options": [
|
||||
"Sleep_0",
|
||||
"ArtificialWorking_Off",
|
||||
"ComfortAICooling_Off",
|
||||
"MotionState_Normal",
|
||||
"AiTempChanged_Off",
|
||||
"AiTemp_240",
|
||||
"OutdoorTemp_87",
|
||||
"CoolCapa_25",
|
||||
"WarmCapa_32",
|
||||
"Light_Off",
|
||||
"Volume_100",
|
||||
"MotionMode_Default",
|
||||
"OptionCode_56376",
|
||||
"ExtendOptionCode_781",
|
||||
"RacInfo_None",
|
||||
"UpdateAllow_NotAllowed",
|
||||
"DurationOn_0",
|
||||
"WelcomeCoolingState_Off"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"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",
|
||||
"Stop"
|
||||
],
|
||||
"x.com.samsung.da.supportedSettingStatus": [
|
||||
"On",
|
||||
"Off"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"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": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/power/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.power": "Off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/realtimenotiforclient/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.timeforshortnoti": "0",
|
||||
"x.com.samsung.da.longnotisubscription": "false",
|
||||
"x.com.samsung.da.periodicnotisubscription": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/sensors/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.items": [
|
||||
{
|
||||
"x.com.samsung.da.id": "0",
|
||||
"x.com.samsung.da.description": "Sensor for CleanLevel",
|
||||
"x.com.samsung.da.type": "CleanLevel",
|
||||
"x.com.samsung.da.value": [
|
||||
"0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "1",
|
||||
"x.com.samsung.da.description": "Sensor for Odor",
|
||||
"x.com.samsung.da.type": "Odor",
|
||||
"x.com.samsung.da.value": [
|
||||
"0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "2",
|
||||
"x.com.samsung.da.description": "Sensor for Dust",
|
||||
"x.com.samsung.da.type": "Dust",
|
||||
"x.com.samsung.da.value": [
|
||||
"0",
|
||||
"0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "3",
|
||||
"x.com.samsung.da.description": "Sensor for FineDust",
|
||||
"x.com.samsung.da.type": "FineDust",
|
||||
"x.com.samsung.da.value": [
|
||||
"0",
|
||||
"0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "4",
|
||||
"x.com.samsung.da.description": "Sensor for SuperFineDust",
|
||||
"x.com.samsung.da.type": "SuperFineDust",
|
||||
"x.com.samsung.da.value": [
|
||||
"0",
|
||||
"0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/temperature/control/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.increment": "1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/temperature/current/0",
|
||||
"rep": {
|
||||
"range": [
|
||||
16.0,
|
||||
30.0
|
||||
],
|
||||
"units": "C",
|
||||
"temperature": 27.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/temperature/desired/0",
|
||||
"rep": {
|
||||
"range": [
|
||||
16.0,
|
||||
30.0
|
||||
],
|
||||
"units": "C",
|
||||
"temperature": 26.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": "26.0",
|
||||
"x.com.samsung.da.current": "27.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": {}
|
||||
},
|
||||
{
|
||||
"href": "/wind/direction/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.modes": "Fix",
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"Fix",
|
||||
"All",
|
||||
"Up_And_Low",
|
||||
"Left_And_Right"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/wind/strength/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.modes": "0",
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"0",
|
||||
"1",
|
||||
"2",
|
||||
"3",
|
||||
"4"
|
||||
],
|
||||
"x.com.samsung.da.modesName": [
|
||||
"Auto",
|
||||
"Low",
|
||||
"Mid",
|
||||
"High",
|
||||
"Turbo"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
+2
@@ -6,9 +6,11 @@
|
||||
"alarm_code",
|
||||
"auto_clean",
|
||||
"climate",
|
||||
"current_temperature_c",
|
||||
"diagnosis_status",
|
||||
"energy_kwh",
|
||||
"energy_saved_kwh",
|
||||
"humidity",
|
||||
"power_watts"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -5,10 +5,12 @@
|
||||
"alarm_code",
|
||||
"auto_clean",
|
||||
"climate",
|
||||
"current_temperature_c",
|
||||
"diagnosis_status",
|
||||
"energy_kwh",
|
||||
"energy_saved_kwh",
|
||||
"firmware_update",
|
||||
"humidity",
|
||||
"mute_once",
|
||||
"power_watts"
|
||||
]
|
||||
|
||||
@@ -6,10 +6,12 @@
|
||||
"alarm_code",
|
||||
"auto_clean",
|
||||
"climate",
|
||||
"current_temperature_c",
|
||||
"display_light",
|
||||
"energy_kwh",
|
||||
"energy_saved_kwh",
|
||||
"firmware_update",
|
||||
"humidity",
|
||||
"mute_once",
|
||||
"selfcheck_error",
|
||||
"selfcheck_result",
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
"climate",
|
||||
"current_limit_enabled",
|
||||
"current_limit_level",
|
||||
"current_temperature_c",
|
||||
"display_light",
|
||||
"energy_kwh",
|
||||
"energy_saved_kwh",
|
||||
|
||||
@@ -5,8 +5,10 @@
|
||||
"alarm_code",
|
||||
"auto_clean",
|
||||
"climate",
|
||||
"current_temperature_c",
|
||||
"energy_kwh",
|
||||
"firmware_update",
|
||||
"humidity",
|
||||
"mute_once",
|
||||
"power_watts"
|
||||
]
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"state_keys": [
|
||||
"air_filter_status",
|
||||
"air_filter_usage",
|
||||
"air_purify",
|
||||
"alarm_code",
|
||||
"auto_clean",
|
||||
"climate",
|
||||
"current_temperature_c",
|
||||
"diagnosis_status",
|
||||
"energy_kwh",
|
||||
"humidity",
|
||||
"power_watts"
|
||||
]
|
||||
}
|
||||
@@ -114,11 +114,13 @@ def test_climate_write_targets():
|
||||
def test_climate_consumed_hrefs_declared_as_coverage():
|
||||
"""The climate-consumed and ambiguous hrefs are declared in the AC registry
|
||||
(as no-entity coverage caps) so they don't leak as gaps -- but produce no
|
||||
standalone entities."""
|
||||
standalone entities. /temperature/current/0 and /temperatures/vs/0 are
|
||||
NOT in this list -- CURRENT_TEMPERATURE / CURRENT_TEMPERATURE_VS give
|
||||
those two real sensor entities (issue #75)."""
|
||||
reg, _ = _ac()
|
||||
for href in ('/power/0', '/power/vs/0', '/temperature/desired/0',
|
||||
'/wind/strength/vs/0', '/mode/convenient/vs/0',
|
||||
'/temperatures/vs/0', '/sensors/vs/0', '/humidity/0'):
|
||||
'/sensors/vs/0', '/humidity/0'):
|
||||
caps = reg.capabilities.get(href)
|
||||
assert caps, href
|
||||
assert all(c.entities == () for c in caps), href
|
||||
@@ -257,3 +259,50 @@ def test_current_limit_is_read_only():
|
||||
than a guessed writable control."""
|
||||
for desc in airconditioner.CURRENT_LIMIT.entities:
|
||||
assert getattr(desc, 'write_fn', None) is None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# WindFree unit (issue #75): same ARTIK051_PRAC_20K modelNum family as the
|
||||
# original issue #17 fixture, but its /mode/convenient/vs/0 additionally
|
||||
# supports Nano/NanoSleep/MotionDirect/MotionIndirect, /wind/direction/vs/0
|
||||
# additionally supports Left_And_Right, and /humidity/vs/0's
|
||||
# fivepercentHumidity is populated (unlike the all-zero original dump).
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _ac_windfree():
|
||||
resources = _load_device('airconditioner_windfree')
|
||||
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 test_windfree_no_unbound_hrefs():
|
||||
reg, resources = _ac_windfree()
|
||||
unbound = []
|
||||
discover(resources, reg.capabilities, reg.pattern_capabilities, log=unbound.append)
|
||||
assert unbound == []
|
||||
|
||||
|
||||
def test_windfree_humidity_and_temperature_sensors_present():
|
||||
reg, resources = _ac_windfree()
|
||||
bound = discover(resources, reg.capabilities, reg.pattern_capabilities)
|
||||
state = flatten(bound, resources)
|
||||
assert state['humidity'] == 42.0 # fivepercentHumidity, not the stuck humidity=0 field
|
||||
assert state['current_temperature_c'] == 27.0
|
||||
|
||||
|
||||
def test_current_temperature_vs_only_binds_when_ocf_href_absent():
|
||||
"""CURRENT_TEMPERATURE_VS's match_fn must not double-bind alongside
|
||||
CURRENT_TEMPERATURE when a device (like this one) reports both
|
||||
/temperature/current/0 and /temperatures/vs/0."""
|
||||
match = airconditioner.CURRENT_TEMPERATURE_VS.match_fn
|
||||
assert match({}, {'/temperature/current/0': {}}) is False
|
||||
assert match({}, {}) is True
|
||||
|
||||
|
||||
def test_humidity_reads_five_percent_field_not_stuck_humidity_field():
|
||||
desc = airconditioner.HUMIDITY.entities[0]
|
||||
rep = {'x.com.samsung.da.humidity': '0', 'x.com.samsung.da.fivepercentHumidity': '42'}
|
||||
assert desc.value_fn(rep.get(desc.field)) == 42.0
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
"""Tests for the horizontal-swing mapping added to climate.py for issue #75
|
||||
-- pure dict lookups, testable without a coordinator/entity fixture (see
|
||||
test_climate_temperature_fallback.py).
|
||||
|
||||
The preset side of issue #75 (WindFree/motion convenient modes not
|
||||
surfacing) is intentionally not addressed here: PR #91 replaces
|
||||
climate.py's static _DEVICE_TO_PRESET table with a generic resolver that
|
||||
reads any device preset code straight off the unit's own supportedModes,
|
||||
which covers WindFree/motion generically instead of a per-model dict --
|
||||
duplicating that here would just conflict with it.
|
||||
"""
|
||||
from custom_components.localthings.climate import _DEVICE_TO_SWING, _SWING_TO_DEVICE
|
||||
|
||||
|
||||
def test_horizontal_swing_maps_both_directions():
|
||||
assert _DEVICE_TO_SWING['Left_And_Right'] == 'horizontal'
|
||||
assert _SWING_TO_DEVICE['horizontal'] == 'Left_And_Right'
|
||||
|
||||
|
||||
def test_existing_swing_modes_unchanged():
|
||||
assert _DEVICE_TO_SWING['Fix'] == 'off'
|
||||
assert _DEVICE_TO_SWING['All'] == 'both'
|
||||
assert _DEVICE_TO_SWING['Up_And_Low'] == 'vertical'
|
||||
@@ -245,6 +245,23 @@ def test_registry_reproduces_golden_state_keys_for_tp1x_rac():
|
||||
)
|
||||
|
||||
|
||||
def test_registry_reproduces_golden_state_keys_for_airconditioner_windfree():
|
||||
"""ARTIK051_PRAC_20K, WindFree-capable unit (issue #75) -- same modelNum
|
||||
family as the original issue #17 fixture, but its /mode/convenient/vs/0
|
||||
additionally reports Nano/NanoSleep/MotionDirect/MotionIndirect, its
|
||||
/wind/direction/vs/0 reports Left_And_Right, and /humidity/vs/0's
|
||||
fivepercentHumidity is actually populated."""
|
||||
from tests.conftest import _load_device
|
||||
resources = _load_device('airconditioner_windfree')
|
||||
golden = json.loads((GOLDEN / 'airconditioner_windfree.json').read_text())
|
||||
state_keys = _new_state_keys('airconditioner_windfree', resources)
|
||||
assert set(state_keys) == set(golden['state_keys']), (
|
||||
f"state_keys mismatch:\n"
|
||||
f" extra: {sorted(set(state_keys) - set(golden['state_keys']))}\n"
|
||||
f" missing: {sorted(set(golden['state_keys']) - set(state_keys))}"
|
||||
)
|
||||
|
||||
|
||||
def test_registry_reproduces_golden_state_keys_for_range():
|
||||
"""Range/cooktop-oven combo (model TP1X_DA-KS-RANGE-0102X, issue #44) --
|
||||
reports no oneUiVersion; resolved via the '-RANGE-' modelNum token
|
||||
|
||||
Reference in New Issue
Block a user