mirror of
https://github.com/home-assistant/core.git
synced 2026-02-14 23:28:42 +00:00
Fix Z-Wave climate set preset (#162728)
This commit is contained in:
@@ -283,7 +283,7 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity):
|
||||
return UnitOfTemperature.CELSIUS
|
||||
|
||||
@property
|
||||
def hvac_mode(self) -> HVACMode:
|
||||
def hvac_mode(self) -> HVACMode | None:
|
||||
"""Return hvac operation ie. heat, cool mode."""
|
||||
if self._current_mode is None:
|
||||
# Thermostat(valve) with no support for setting
|
||||
@@ -292,7 +292,10 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity):
|
||||
if self._current_mode.value is None:
|
||||
# guard missing value
|
||||
return HVACMode.HEAT
|
||||
return ZW_HVAC_MODE_MAP.get(int(self._current_mode.value), HVACMode.HEAT_COOL)
|
||||
mode = ZW_HVAC_MODE_MAP.get(int(self._current_mode.value))
|
||||
if mode is not None and mode not in self._hvac_modes:
|
||||
return None
|
||||
return mode
|
||||
|
||||
@property
|
||||
def hvac_modes(self) -> list[HVACMode]:
|
||||
@@ -548,12 +551,17 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity):
|
||||
"""Set new target preset mode."""
|
||||
assert self._current_mode is not None
|
||||
if preset_mode == PRESET_NONE:
|
||||
# try to restore to the (translated) main hvac mode
|
||||
await self.async_set_hvac_mode(self.hvac_mode)
|
||||
# Try to restore to the (translated) main hvac mode.
|
||||
if (hvac_mode := self.hvac_mode) is None:
|
||||
# Current preset mode doesn't map to a supported HVAC mode.
|
||||
# Pick the first supported non-off mode.
|
||||
hvac_mode = next(
|
||||
mode for mode in self._hvac_modes if mode != HVACMode.OFF
|
||||
)
|
||||
await self.async_set_hvac_mode(hvac_mode)
|
||||
return
|
||||
preset_mode_value = self._hvac_presets.get(preset_mode)
|
||||
if preset_mode_value is None:
|
||||
raise ValueError(f"Received an invalid preset mode: {preset_mode}")
|
||||
|
||||
preset_mode_value = self._hvac_presets[preset_mode]
|
||||
|
||||
await self._async_set_value(self._current_mode, preset_mode_value)
|
||||
|
||||
|
||||
@@ -176,6 +176,12 @@ def climate_eurotronic_spirit_z_state_fixture() -> dict[str, Any]:
|
||||
return load_json_object_fixture("climate_eurotronic_spirit_z_state.json", DOMAIN)
|
||||
|
||||
|
||||
@pytest.fixture(name="climate_eurotronic_comet_z_state", scope="package")
|
||||
def climate_eurotronic_comet_z_state_fixture() -> dict[str, Any]:
|
||||
"""Load the climate Eurotronic Comet Z thermostat node state fixture data."""
|
||||
return load_json_object_fixture("climate_eurotronic_comet_z_state.json", DOMAIN)
|
||||
|
||||
|
||||
@pytest.fixture(name="climate_heatit_z_trm6_state", scope="package")
|
||||
def climate_heatit_z_trm6_state_fixture() -> dict[str, Any]:
|
||||
"""Load the climate HEATIT Z-TRM6 thermostat node state fixture data."""
|
||||
@@ -851,6 +857,16 @@ def climate_eurotronic_spirit_z_fixture(
|
||||
return node
|
||||
|
||||
|
||||
@pytest.fixture(name="climate_eurotronic_comet_z")
|
||||
def climate_eurotronic_comet_z_fixture(
|
||||
client: MagicMock, climate_eurotronic_comet_z_state: dict[str, Any]
|
||||
) -> Node:
|
||||
"""Mock a climate Eurotronic Comet Z node."""
|
||||
node = Node(client, copy.deepcopy(climate_eurotronic_comet_z_state))
|
||||
client.driver.controller.nodes[node.node_id] = node
|
||||
return node
|
||||
|
||||
|
||||
@pytest.fixture(name="climate_heatit_z_trm6")
|
||||
def climate_heatit_z_trm6_fixture(client, climate_heatit_z_trm6_state) -> Node:
|
||||
"""Mock a climate radio HEATIT Z-TRM6 node."""
|
||||
|
||||
@@ -0,0 +1,528 @@
|
||||
{
|
||||
"nodeId": 2,
|
||||
"index": 0,
|
||||
"installerIcon": 4608,
|
||||
"userIcon": 4608,
|
||||
"status": 4,
|
||||
"ready": true,
|
||||
"isListening": false,
|
||||
"isRouting": true,
|
||||
"isSecure": true,
|
||||
"manufacturerId": 328,
|
||||
"productId": 3,
|
||||
"productType": 4,
|
||||
"firmwareVersion": "14.1.4",
|
||||
"zwavePlusVersion": 2,
|
||||
"deviceConfig": {
|
||||
"filename": "/data/db/devices/0x0148/cometz_700.json",
|
||||
"isEmbedded": true,
|
||||
"manufacturer": "Eurotronics",
|
||||
"manufacturerId": 328,
|
||||
"label": "Comet Z",
|
||||
"description": "Radiator Thermostat",
|
||||
"devices": [
|
||||
{
|
||||
"productType": 4,
|
||||
"productId": 3
|
||||
}
|
||||
],
|
||||
"firmwareVersion": {
|
||||
"min": "0.0",
|
||||
"max": "255.255"
|
||||
},
|
||||
"preferred": false,
|
||||
"associations": {},
|
||||
"paramInformation": {
|
||||
"_map": {}
|
||||
}
|
||||
},
|
||||
"label": "Comet Z",
|
||||
"interviewAttempts": 0,
|
||||
"isFrequentListening": "1000ms",
|
||||
"maxDataRate": 100000,
|
||||
"supportedDataRates": [40000, 100000],
|
||||
"protocolVersion": 3,
|
||||
"supportsBeaming": true,
|
||||
"supportsSecurity": false,
|
||||
"nodeType": 1,
|
||||
"zwavePlusNodeType": 0,
|
||||
"zwavePlusRoleType": 7,
|
||||
"deviceClass": {
|
||||
"basic": {
|
||||
"key": 4,
|
||||
"label": "Routing End Node"
|
||||
},
|
||||
"generic": {
|
||||
"key": 8,
|
||||
"label": "Thermostat"
|
||||
},
|
||||
"specific": {
|
||||
"key": 6,
|
||||
"label": "General Thermostat V2"
|
||||
}
|
||||
},
|
||||
"interviewStage": "Complete",
|
||||
"deviceDatabaseUrl": "https://devices.zwave-js.io/?jumpTo=0x0148:0x0004:0x0003:14.1.4",
|
||||
"statistics": {
|
||||
"commandsTX": 23,
|
||||
"commandsRX": 21,
|
||||
"commandsDroppedRX": 0,
|
||||
"commandsDroppedTX": 0,
|
||||
"timeoutResponse": 3,
|
||||
"rtt": 877.4,
|
||||
"rssi": -79,
|
||||
"lwr": {
|
||||
"protocolDataRate": 2,
|
||||
"repeaters": [],
|
||||
"rssi": -78,
|
||||
"repeaterRSSI": []
|
||||
}
|
||||
},
|
||||
"highestSecurityClass": 0,
|
||||
"isControllerNode": false,
|
||||
"keepAwake": false,
|
||||
"protocol": 0,
|
||||
"sdkVersion": "7.15.4",
|
||||
"endpoints": [
|
||||
{
|
||||
"nodeId": 2,
|
||||
"index": 0,
|
||||
"installerIcon": 4608,
|
||||
"userIcon": 4608,
|
||||
"deviceClass": {
|
||||
"basic": {
|
||||
"key": 4,
|
||||
"label": "Routing End Node"
|
||||
},
|
||||
"generic": {
|
||||
"key": 8,
|
||||
"label": "Thermostat"
|
||||
},
|
||||
"specific": {
|
||||
"key": 6,
|
||||
"label": "General Thermostat V2"
|
||||
}
|
||||
},
|
||||
"commandClasses": [
|
||||
{
|
||||
"id": 49,
|
||||
"name": "Multilevel Sensor",
|
||||
"version": 11,
|
||||
"isSecure": true
|
||||
},
|
||||
{
|
||||
"id": 38,
|
||||
"name": "Multilevel Switch",
|
||||
"version": 1,
|
||||
"isSecure": true
|
||||
},
|
||||
{
|
||||
"id": 64,
|
||||
"name": "Thermostat Mode",
|
||||
"version": 3,
|
||||
"isSecure": true
|
||||
},
|
||||
{
|
||||
"id": 67,
|
||||
"name": "Thermostat Setpoint",
|
||||
"version": 3,
|
||||
"isSecure": true
|
||||
},
|
||||
{
|
||||
"id": 128,
|
||||
"name": "Battery",
|
||||
"version": 1,
|
||||
"isSecure": true
|
||||
},
|
||||
{
|
||||
"id": 112,
|
||||
"name": "Configuration",
|
||||
"version": 1,
|
||||
"isSecure": true
|
||||
},
|
||||
{
|
||||
"id": 114,
|
||||
"name": "Manufacturer Specific",
|
||||
"version": 2,
|
||||
"isSecure": true
|
||||
},
|
||||
{
|
||||
"id": 134,
|
||||
"name": "Version",
|
||||
"version": 3,
|
||||
"isSecure": true
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"values": [
|
||||
{
|
||||
"endpoint": 0,
|
||||
"commandClass": 38,
|
||||
"commandClassName": "Multilevel Switch",
|
||||
"property": "currentValue",
|
||||
"propertyName": "currentValue",
|
||||
"ccVersion": 1,
|
||||
"metadata": {
|
||||
"type": "number",
|
||||
"readable": true,
|
||||
"writeable": false,
|
||||
"label": "Current value",
|
||||
"min": 0,
|
||||
"max": 99,
|
||||
"stateful": true,
|
||||
"secret": false
|
||||
},
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"endpoint": 0,
|
||||
"commandClass": 38,
|
||||
"commandClassName": "Multilevel Switch",
|
||||
"property": "targetValue",
|
||||
"propertyName": "targetValue",
|
||||
"ccVersion": 1,
|
||||
"metadata": {
|
||||
"type": "number",
|
||||
"readable": true,
|
||||
"writeable": true,
|
||||
"label": "Target value",
|
||||
"valueChangeOptions": ["transitionDuration"],
|
||||
"min": 0,
|
||||
"max": 99,
|
||||
"stateful": true,
|
||||
"secret": false
|
||||
},
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"endpoint": 0,
|
||||
"commandClass": 38,
|
||||
"commandClassName": "Multilevel Switch",
|
||||
"property": "Up",
|
||||
"propertyName": "Up",
|
||||
"ccVersion": 1,
|
||||
"metadata": {
|
||||
"type": "boolean",
|
||||
"readable": false,
|
||||
"writeable": true,
|
||||
"label": "Perform a level change (Up)",
|
||||
"ccSpecific": {
|
||||
"switchType": 2
|
||||
},
|
||||
"valueChangeOptions": ["transitionDuration"],
|
||||
"states": {
|
||||
"true": "Start",
|
||||
"false": "Stop"
|
||||
},
|
||||
"stateful": true,
|
||||
"secret": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"endpoint": 0,
|
||||
"commandClass": 38,
|
||||
"commandClassName": "Multilevel Switch",
|
||||
"property": "Down",
|
||||
"propertyName": "Down",
|
||||
"ccVersion": 1,
|
||||
"metadata": {
|
||||
"type": "boolean",
|
||||
"readable": false,
|
||||
"writeable": true,
|
||||
"label": "Perform a level change (Down)",
|
||||
"ccSpecific": {
|
||||
"switchType": 2
|
||||
},
|
||||
"valueChangeOptions": ["transitionDuration"],
|
||||
"states": {
|
||||
"true": "Start",
|
||||
"false": "Stop"
|
||||
},
|
||||
"stateful": true,
|
||||
"secret": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"endpoint": 0,
|
||||
"commandClass": 38,
|
||||
"commandClassName": "Multilevel Switch",
|
||||
"property": "duration",
|
||||
"propertyName": "duration",
|
||||
"ccVersion": 1,
|
||||
"metadata": {
|
||||
"type": "duration",
|
||||
"readable": true,
|
||||
"writeable": false,
|
||||
"label": "Remaining duration",
|
||||
"stateful": true,
|
||||
"secret": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"endpoint": 0,
|
||||
"commandClass": 38,
|
||||
"commandClassName": "Multilevel Switch",
|
||||
"property": "restorePrevious",
|
||||
"propertyName": "restorePrevious",
|
||||
"ccVersion": 1,
|
||||
"metadata": {
|
||||
"type": "boolean",
|
||||
"readable": false,
|
||||
"writeable": true,
|
||||
"label": "Restore previous value",
|
||||
"states": {
|
||||
"true": "Restore"
|
||||
},
|
||||
"stateful": true,
|
||||
"secret": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"endpoint": 0,
|
||||
"commandClass": 49,
|
||||
"commandClassName": "Multilevel Sensor",
|
||||
"property": "Air temperature",
|
||||
"propertyName": "Air temperature",
|
||||
"ccVersion": 11,
|
||||
"metadata": {
|
||||
"type": "number",
|
||||
"readable": true,
|
||||
"writeable": false,
|
||||
"label": "Air temperature",
|
||||
"ccSpecific": {
|
||||
"sensorType": 1,
|
||||
"scale": 0
|
||||
},
|
||||
"unit": "\u00b0C",
|
||||
"stateful": true,
|
||||
"secret": false
|
||||
},
|
||||
"value": 18.5
|
||||
},
|
||||
{
|
||||
"endpoint": 0,
|
||||
"commandClass": 64,
|
||||
"commandClassName": "Thermostat Mode",
|
||||
"property": "mode",
|
||||
"propertyName": "mode",
|
||||
"ccVersion": 3,
|
||||
"metadata": {
|
||||
"type": "number",
|
||||
"readable": true,
|
||||
"writeable": true,
|
||||
"label": "Thermostat mode",
|
||||
"min": 0,
|
||||
"max": 255,
|
||||
"states": {
|
||||
"0": "Off",
|
||||
"1": "Heat",
|
||||
"11": "Energy heat",
|
||||
"15": "Full power",
|
||||
"31": "Manufacturer specific"
|
||||
},
|
||||
"stateful": true,
|
||||
"secret": false
|
||||
},
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"endpoint": 0,
|
||||
"commandClass": 64,
|
||||
"commandClassName": "Thermostat Mode",
|
||||
"property": "manufacturerData",
|
||||
"propertyName": "manufacturerData",
|
||||
"ccVersion": 3,
|
||||
"metadata": {
|
||||
"type": "buffer",
|
||||
"readable": true,
|
||||
"writeable": false,
|
||||
"label": "Manufacturer data",
|
||||
"stateful": true,
|
||||
"secret": false
|
||||
},
|
||||
"value": {
|
||||
"type": "Buffer",
|
||||
"data": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"endpoint": 0,
|
||||
"commandClass": 67,
|
||||
"commandClassName": "Thermostat Setpoint",
|
||||
"property": "setpoint",
|
||||
"propertyKey": 1,
|
||||
"propertyName": "setpoint",
|
||||
"propertyKeyName": "Heating",
|
||||
"ccVersion": 3,
|
||||
"metadata": {
|
||||
"type": "number",
|
||||
"readable": true,
|
||||
"writeable": true,
|
||||
"label": "Setpoint (Heating)",
|
||||
"ccSpecific": {
|
||||
"setpointType": 1
|
||||
},
|
||||
"min": 8,
|
||||
"max": 28,
|
||||
"unit": "\u00b0C",
|
||||
"stateful": true,
|
||||
"secret": false
|
||||
},
|
||||
"value": 21
|
||||
},
|
||||
{
|
||||
"endpoint": 0,
|
||||
"commandClass": 67,
|
||||
"commandClassName": "Thermostat Setpoint",
|
||||
"property": "setpoint",
|
||||
"propertyKey": 11,
|
||||
"propertyName": "setpoint",
|
||||
"propertyKeyName": "Energy Save Heating",
|
||||
"ccVersion": 3,
|
||||
"metadata": {
|
||||
"type": "number",
|
||||
"readable": true,
|
||||
"writeable": true,
|
||||
"label": "Setpoint (Energy Save Heating)",
|
||||
"ccSpecific": {
|
||||
"setpointType": 11
|
||||
},
|
||||
"min": 8,
|
||||
"max": 28,
|
||||
"unit": "\u00b0C",
|
||||
"stateful": true,
|
||||
"secret": false
|
||||
},
|
||||
"value": 16
|
||||
},
|
||||
{
|
||||
"endpoint": 0,
|
||||
"commandClass": 114,
|
||||
"commandClassName": "Manufacturer Specific",
|
||||
"property": "manufacturerId",
|
||||
"propertyName": "manufacturerId",
|
||||
"ccVersion": 2,
|
||||
"metadata": {
|
||||
"type": "number",
|
||||
"readable": true,
|
||||
"writeable": false,
|
||||
"label": "Manufacturer ID",
|
||||
"min": 0,
|
||||
"max": 65535,
|
||||
"stateful": true,
|
||||
"secret": false
|
||||
},
|
||||
"value": 328
|
||||
},
|
||||
{
|
||||
"endpoint": 0,
|
||||
"commandClass": 114,
|
||||
"commandClassName": "Manufacturer Specific",
|
||||
"property": "productType",
|
||||
"propertyName": "productType",
|
||||
"ccVersion": 2,
|
||||
"metadata": {
|
||||
"type": "number",
|
||||
"readable": true,
|
||||
"writeable": false,
|
||||
"label": "Product type",
|
||||
"min": 0,
|
||||
"max": 65535,
|
||||
"stateful": true,
|
||||
"secret": false
|
||||
},
|
||||
"value": 4
|
||||
},
|
||||
{
|
||||
"endpoint": 0,
|
||||
"commandClass": 114,
|
||||
"commandClassName": "Manufacturer Specific",
|
||||
"property": "productId",
|
||||
"propertyName": "productId",
|
||||
"ccVersion": 2,
|
||||
"metadata": {
|
||||
"type": "number",
|
||||
"readable": true,
|
||||
"writeable": false,
|
||||
"label": "Product ID",
|
||||
"min": 0,
|
||||
"max": 65535,
|
||||
"stateful": true,
|
||||
"secret": false
|
||||
},
|
||||
"value": 3
|
||||
},
|
||||
{
|
||||
"endpoint": 0,
|
||||
"commandClass": 128,
|
||||
"commandClassName": "Battery",
|
||||
"property": "level",
|
||||
"propertyName": "level",
|
||||
"ccVersion": 1,
|
||||
"metadata": {
|
||||
"type": "number",
|
||||
"readable": true,
|
||||
"writeable": false,
|
||||
"label": "Battery level",
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
"unit": "%",
|
||||
"stateful": true,
|
||||
"secret": false
|
||||
},
|
||||
"value": 45
|
||||
},
|
||||
{
|
||||
"endpoint": 0,
|
||||
"commandClass": 134,
|
||||
"commandClassName": "Version",
|
||||
"property": "hardwareVersion",
|
||||
"propertyName": "hardwareVersion",
|
||||
"ccVersion": 3,
|
||||
"metadata": {
|
||||
"type": "number",
|
||||
"readable": true,
|
||||
"writeable": false,
|
||||
"label": "Z-Wave chip hardware version",
|
||||
"stateful": true,
|
||||
"secret": false
|
||||
},
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"endpoint": 0,
|
||||
"commandClass": 134,
|
||||
"commandClassName": "Version",
|
||||
"property": "firmwareVersions",
|
||||
"propertyName": "firmwareVersions",
|
||||
"ccVersion": 3,
|
||||
"metadata": {
|
||||
"type": "string[]",
|
||||
"readable": true,
|
||||
"writeable": false,
|
||||
"label": "Z-Wave chip firmware versions",
|
||||
"stateful": true,
|
||||
"secret": false
|
||||
},
|
||||
"value": ["14.1", "1.6"]
|
||||
},
|
||||
{
|
||||
"endpoint": 0,
|
||||
"commandClass": 134,
|
||||
"commandClassName": "Version",
|
||||
"property": "protocolVersion",
|
||||
"propertyName": "protocolVersion",
|
||||
"ccVersion": 3,
|
||||
"metadata": {
|
||||
"type": "string",
|
||||
"readable": true,
|
||||
"writeable": false,
|
||||
"label": "Z-Wave protocol version",
|
||||
"stateful": true,
|
||||
"secret": false
|
||||
},
|
||||
"value": "7.15"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
"""Test the Z-Wave JS climate platform."""
|
||||
|
||||
import copy
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
from zwave_js_server.const import CommandClass
|
||||
@@ -56,6 +57,8 @@ from .common import (
|
||||
replace_value_of_zwave_value,
|
||||
)
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def platforms() -> list[str]:
|
||||
@@ -1001,3 +1004,329 @@ async def test_thermostat_unknown_values(
|
||||
state = hass.states.get(CLIMATE_RADIO_THERMOSTAT_ENTITY)
|
||||
|
||||
assert ATTR_HVAC_ACTION not in state.attributes
|
||||
|
||||
|
||||
async def test_set_preset_mode_manufacturer_specific(
|
||||
hass: HomeAssistant,
|
||||
client: MagicMock,
|
||||
climate_eurotronic_comet_z: Node,
|
||||
integration: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test setting preset mode to manufacturer specific.
|
||||
|
||||
This tests the Eurotronic Comet Z thermostat which has a
|
||||
"Manufacturer specific" thermostat mode (value 31) that is
|
||||
exposed as a preset mode.
|
||||
"""
|
||||
node = climate_eurotronic_comet_z
|
||||
entity_id = "climate.radiator_thermostat"
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert state.state == HVACMode.HEAT
|
||||
assert state.attributes[ATTR_TEMPERATURE] == 21
|
||||
assert state.attributes[ATTR_PRESET_MODE] == PRESET_NONE
|
||||
|
||||
# Test setting preset mode to "Manufacturer specific"
|
||||
await hass.services.async_call(
|
||||
CLIMATE_DOMAIN,
|
||||
SERVICE_SET_PRESET_MODE,
|
||||
{
|
||||
ATTR_ENTITY_ID: entity_id,
|
||||
ATTR_PRESET_MODE: "Manufacturer specific",
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert len(client.async_send_command.call_args_list) == 1
|
||||
args = client.async_send_command.call_args[0][0]
|
||||
assert args["command"] == "node.set_value"
|
||||
assert args["nodeId"] == 2
|
||||
assert args["valueId"] == {
|
||||
"commandClass": 64,
|
||||
"endpoint": 0,
|
||||
"property": "mode",
|
||||
}
|
||||
assert args["value"] == 31
|
||||
|
||||
client.async_send_command.reset_mock()
|
||||
|
||||
# Simulate the device updating to manufacturer specific mode
|
||||
event = Event(
|
||||
type="value updated",
|
||||
data={
|
||||
"source": "node",
|
||||
"event": "value updated",
|
||||
"nodeId": 2,
|
||||
"args": {
|
||||
"commandClassName": "Thermostat Mode",
|
||||
"commandClass": 64,
|
||||
"endpoint": 0,
|
||||
"property": "mode",
|
||||
"propertyName": "mode",
|
||||
"newValue": 31,
|
||||
"prevValue": 1,
|
||||
},
|
||||
},
|
||||
)
|
||||
node.receive_event(event)
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
# Mode 31 is not in ZW_HVAC_MODE_MAP, so hvac_mode is unknown.
|
||||
assert state.state == "unknown"
|
||||
assert state.attributes[ATTR_PRESET_MODE] == "Manufacturer specific"
|
||||
|
||||
# Test restoring hvac mode by setting preset to none.
|
||||
await hass.services.async_call(
|
||||
CLIMATE_DOMAIN,
|
||||
SERVICE_SET_PRESET_MODE,
|
||||
{
|
||||
ATTR_ENTITY_ID: entity_id,
|
||||
ATTR_PRESET_MODE: PRESET_NONE,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert client.async_send_command.call_count == 1
|
||||
args = client.async_send_command.call_args[0][0]
|
||||
assert args["command"] == "node.set_value"
|
||||
assert args["nodeId"] == 2
|
||||
assert args["valueId"] == {
|
||||
"commandClass": 64,
|
||||
"endpoint": 0,
|
||||
"property": "mode",
|
||||
}
|
||||
assert args["value"] == 1
|
||||
|
||||
client.async_send_command.reset_mock()
|
||||
|
||||
|
||||
async def test_preset_mode_mapped_to_unsupported_hvac_mode(
|
||||
hass: HomeAssistant,
|
||||
client: MagicMock,
|
||||
climate_eurotronic_comet_z: Node,
|
||||
integration: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test preset mapping to an HVAC mode the entity doesn't support.
|
||||
|
||||
The Away mode (13) maps to HVACMode.HEAT_COOL in ZW_HVAC_MODE_MAP,
|
||||
but the Comet Z only supports OFF and HEAT. The hvac_mode property
|
||||
should return None for this unsupported mapping.
|
||||
"""
|
||||
node = climate_eurotronic_comet_z
|
||||
entity_id = "climate.radiator_thermostat"
|
||||
|
||||
# Simulate the device being set to Away mode (13).
|
||||
event = Event(
|
||||
type="value updated",
|
||||
data={
|
||||
"source": "node",
|
||||
"event": "value updated",
|
||||
"nodeId": 2,
|
||||
"args": {
|
||||
"commandClassName": "Thermostat Mode",
|
||||
"commandClass": 64,
|
||||
"endpoint": 0,
|
||||
"property": "mode",
|
||||
"propertyName": "mode",
|
||||
"newValue": 13,
|
||||
"prevValue": 1,
|
||||
},
|
||||
},
|
||||
)
|
||||
node.receive_event(event)
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
# Away maps to HEAT_COOL which the device doesn't support,
|
||||
# so hvac_mode returns None.
|
||||
assert state.state == "unknown"
|
||||
|
||||
|
||||
async def test_set_preset_mode_mapped_preset(
|
||||
hass: HomeAssistant,
|
||||
client: MagicMock,
|
||||
climate_eurotronic_comet_z: Node,
|
||||
integration: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test that a preset mapping to a supported HVAC mode shows that mode.
|
||||
|
||||
The Eurotronic Comet Z has "Energy heat" (mode 11 = HEATING_ECON) which
|
||||
maps to HVACMode.HEAT in ZW_HVAC_MODE_MAP. Since the device supports
|
||||
heat, hvac_mode should return heat while in this preset.
|
||||
"""
|
||||
node = climate_eurotronic_comet_z
|
||||
entity_id = "climate.radiator_thermostat"
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert state.state == HVACMode.HEAT
|
||||
|
||||
# Set preset mode to "Energy heat"
|
||||
await hass.services.async_call(
|
||||
CLIMATE_DOMAIN,
|
||||
SERVICE_SET_PRESET_MODE,
|
||||
{
|
||||
ATTR_ENTITY_ID: entity_id,
|
||||
ATTR_PRESET_MODE: "Energy heat",
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert len(client.async_send_command.call_args_list) == 1
|
||||
args = client.async_send_command.call_args[0][0]
|
||||
assert args["command"] == "node.set_value"
|
||||
assert args["value"] == 11
|
||||
|
||||
client.async_send_command.reset_mock()
|
||||
|
||||
# Simulate the device updating to energy heat mode
|
||||
event = Event(
|
||||
type="value updated",
|
||||
data={
|
||||
"source": "node",
|
||||
"event": "value updated",
|
||||
"nodeId": 2,
|
||||
"args": {
|
||||
"commandClassName": "Thermostat Mode",
|
||||
"commandClass": 64,
|
||||
"endpoint": 0,
|
||||
"property": "mode",
|
||||
"propertyName": "mode",
|
||||
"newValue": 11,
|
||||
"prevValue": 1,
|
||||
},
|
||||
},
|
||||
)
|
||||
node.receive_event(event)
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
# Energy heat (HEATING_ECON) maps to HVACMode.HEAT which the device
|
||||
# supports, so hvac_mode returns heat.
|
||||
assert state.state == HVACMode.HEAT
|
||||
assert state.attributes[ATTR_PRESET_MODE] == "Energy heat"
|
||||
|
||||
# Clear preset - should restore to heat (the mapped mode).
|
||||
await hass.services.async_call(
|
||||
CLIMATE_DOMAIN,
|
||||
SERVICE_SET_PRESET_MODE,
|
||||
{
|
||||
ATTR_ENTITY_ID: entity_id,
|
||||
ATTR_PRESET_MODE: PRESET_NONE,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert client.async_send_command.call_count == 1
|
||||
args = client.async_send_command.call_args[0][0]
|
||||
assert args["command"] == "node.set_value"
|
||||
assert args["value"] == 1
|
||||
|
||||
client.async_send_command.reset_mock()
|
||||
|
||||
|
||||
async def test_set_preset_mode_none_while_in_hvac_mode(
|
||||
hass: HomeAssistant,
|
||||
client: MagicMock,
|
||||
climate_eurotronic_comet_z: Node,
|
||||
integration: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test setting preset mode to none while already in an HVAC mode."""
|
||||
entity_id = "climate.radiator_thermostat"
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert state.state == HVACMode.HEAT
|
||||
assert state.attributes[ATTR_PRESET_MODE] == PRESET_NONE
|
||||
|
||||
# Setting preset to none while already in an HVAC mode restores
|
||||
# the current hvac mode.
|
||||
await hass.services.async_call(
|
||||
CLIMATE_DOMAIN,
|
||||
SERVICE_SET_PRESET_MODE,
|
||||
{
|
||||
ATTR_ENTITY_ID: entity_id,
|
||||
ATTR_PRESET_MODE: PRESET_NONE,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert client.async_send_command.call_count == 1
|
||||
args = client.async_send_command.call_args[0][0]
|
||||
assert args["command"] == "node.set_value"
|
||||
assert args["nodeId"] == 2
|
||||
assert args["valueId"] == {
|
||||
"commandClass": 64,
|
||||
"endpoint": 0,
|
||||
"property": "mode",
|
||||
}
|
||||
assert args["value"] == 1
|
||||
|
||||
|
||||
async def test_set_preset_mode_none_unmapped_preset(
|
||||
hass: HomeAssistant,
|
||||
client: MagicMock,
|
||||
climate_eurotronic_comet_z: Node,
|
||||
integration: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test clearing an unmapped preset falls back to first supported HVAC mode.
|
||||
|
||||
When the device is in a preset mode that has no mapping in ZW_HVAC_MODE_MAP
|
||||
(e.g. "Manufacturer specific"), hvac_mode returns None. Setting preset to
|
||||
none should fall back to the first supported non-off HVAC mode.
|
||||
"""
|
||||
node = climate_eurotronic_comet_z
|
||||
entity_id = "climate.radiator_thermostat"
|
||||
|
||||
# Simulate the device being externally changed to "Manufacturer specific"
|
||||
# mode without HA having set a preset first.
|
||||
event = Event(
|
||||
type="value updated",
|
||||
data={
|
||||
"source": "node",
|
||||
"event": "value updated",
|
||||
"nodeId": 2,
|
||||
"args": {
|
||||
"commandClassName": "Thermostat Mode",
|
||||
"commandClass": 64,
|
||||
"endpoint": 0,
|
||||
"property": "mode",
|
||||
"propertyName": "mode",
|
||||
"newValue": 31,
|
||||
"prevValue": 1,
|
||||
},
|
||||
},
|
||||
)
|
||||
node.receive_event(event)
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert state.state == "unknown"
|
||||
assert state.attributes[ATTR_PRESET_MODE] == "Manufacturer specific"
|
||||
|
||||
client.async_send_command.reset_mock()
|
||||
|
||||
# Setting preset to none should default to heat since there is no
|
||||
# stored previous HVAC mode.
|
||||
await hass.services.async_call(
|
||||
CLIMATE_DOMAIN,
|
||||
SERVICE_SET_PRESET_MODE,
|
||||
{
|
||||
ATTR_ENTITY_ID: entity_id,
|
||||
ATTR_PRESET_MODE: PRESET_NONE,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert client.async_send_command.call_count == 1
|
||||
args = client.async_send_command.call_args[0][0]
|
||||
assert args["command"] == "node.set_value"
|
||||
assert args["nodeId"] == 2
|
||||
assert args["valueId"] == {
|
||||
"commandClass": 64,
|
||||
"endpoint": 0,
|
||||
"property": "mode",
|
||||
}
|
||||
assert args["value"] == 1
|
||||
|
||||
Reference in New Issue
Block a user