diff --git a/homeassistant/components/zwave_js/climate.py b/homeassistant/components/zwave_js/climate.py index 07d4a3f7d0f..6c0b4a0335e 100644 --- a/homeassistant/components/zwave_js/climate.py +++ b/homeassistant/components/zwave_js/climate.py @@ -140,6 +140,7 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity): THERMOSTAT_CURRENT_TEMP_PROPERTY, command_class=CommandClass.SENSOR_MULTILEVEL, add_to_watched_value_ids=True, + check_all_endpoints=True, ) self._set_modes_and_presets() diff --git a/homeassistant/components/zwave_js/entity.py b/homeassistant/components/zwave_js/entity.py index 5c64ddbc496..84870ba75f4 100644 --- a/homeassistant/components/zwave_js/entity.py +++ b/homeassistant/components/zwave_js/entity.py @@ -121,6 +121,7 @@ class ZWaveBaseEntity(Entity): endpoint: Optional[int] = None, value_property_key_name: Optional[str] = None, add_to_watched_value_ids: bool = True, + check_all_endpoints: bool = False, ) -> Optional[ZwaveValue]: """Return specific ZwaveValue on this ZwaveNode.""" # use commandclass and endpoint from primary value if omitted @@ -129,17 +130,33 @@ class ZWaveBaseEntity(Entity): command_class = self.info.primary_value.command_class if endpoint is None: endpoint = self.info.primary_value.endpoint + + # Build partial event data dictionary so we can change the endpoint later + partial_evt_data = { + "commandClass": command_class, + "property": value_property, + "propertyKeyName": value_property_key_name, + } + # lookup value by value_id value_id = get_value_id( - self.info.node, - { - "commandClass": command_class, - "endpoint": endpoint, - "property": value_property, - "propertyKeyName": value_property_key_name, - }, + self.info.node, {**partial_evt_data, "endpoint": endpoint} ) return_value = self.info.node.values.get(value_id) + + # If we haven't found a value and check_all_endpoints is True, we should + # return the first value we can find on any other endpoint + if return_value is None and check_all_endpoints: + for endpoint_ in self.info.node.endpoints: + if endpoint_.index != self.info.primary_value.endpoint: + value_id = get_value_id( + self.info.node, + {**partial_evt_data, "endpoint": endpoint_.index}, + ) + return_value = self.info.node.values.get(value_id) + if return_value: + break + # add to watched_ids list so we will be triggered when the value updates if ( return_value diff --git a/tests/components/zwave_js/conftest.py b/tests/components/zwave_js/conftest.py index 470b4c0227b..b6cbc911b6a 100644 --- a/tests/components/zwave_js/conftest.py +++ b/tests/components/zwave_js/conftest.py @@ -111,6 +111,22 @@ def climate_radio_thermostat_ct100_plus_state_fixture(): ) +@pytest.fixture( + name="climate_radio_thermostat_ct100_plus_different_endpoints_state", + scope="session", +) +def climate_radio_thermostat_ct100_plus_different_endpoints_state_fixture(): + """Load the thermostat fixture state with values on different endpoints. + + This device is a radio thermostat ct100. + """ + return json.loads( + load_fixture( + "zwave_js/climate_radio_thermostat_ct100_plus_different_endpoints_state.json" + ) + ) + + @pytest.fixture(name="nortek_thermostat_state", scope="session") def nortek_thermostat_state_fixture(): """Load the nortek thermostat node state fixture data.""" @@ -231,6 +247,16 @@ def climate_radio_thermostat_ct100_plus_fixture( return node +@pytest.fixture(name="climate_radio_thermostat_ct100_plus_different_endpoints") +def climate_radio_thermostat_ct100_plus_different_endpoints_fixture( + client, climate_radio_thermostat_ct100_plus_different_endpoints_state +): + """Mock a climate radio thermostat ct100 plus node with values on different endpoints.""" + node = Node(client, climate_radio_thermostat_ct100_plus_different_endpoints_state) + client.driver.controller.nodes[node.node_id] = node + return node + + @pytest.fixture(name="nortek_thermostat") def nortek_thermostat_fixture(client, nortek_thermostat_state): """Mock a nortek thermostat node.""" diff --git a/tests/components/zwave_js/test_climate.py b/tests/components/zwave_js/test_climate.py index aca1022f8b0..f7deefc1360 100644 --- a/tests/components/zwave_js/test_climate.py +++ b/tests/components/zwave_js/test_climate.py @@ -324,3 +324,12 @@ async def test_thermostat_v2( }, blocking=True, ) + + +async def test_thermostat_different_endpoints( + hass, client, climate_radio_thermostat_ct100_plus_different_endpoints, integration +): + """Test an entity with values on a different endpoint from the primary value.""" + state = hass.states.get(CLIMATE_RADIO_THERMOSTAT_ENTITY) + + assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 22.5 diff --git a/tests/fixtures/zwave_js/climate_radio_thermostat_ct100_plus_different_endpoints_state.json b/tests/fixtures/zwave_js/climate_radio_thermostat_ct100_plus_different_endpoints_state.json new file mode 100644 index 00000000000..ea38dfd9d6b --- /dev/null +++ b/tests/fixtures/zwave_js/climate_radio_thermostat_ct100_plus_different_endpoints_state.json @@ -0,0 +1,727 @@ +{ + "nodeId": 26, + "index": 0, + "installerIcon": 4608, + "userIcon": 4608, + "status": 4, + "ready": true, + "deviceClass": { + "basic": "Routing Slave", + "generic": "Thermostat", + "specific": "Thermostat General V2", + "mandatorySupportedCCs": [ + "Basic", + "Manufacturer Specific", + "Thermostat Mode", + "Thermostat Setpoint", + "Version" + ], + "mandatoryControlCCs": [] + }, + "isListening": true, + "isFrequentListening": false, + "isRouting": true, + "maxBaudRate": 40000, + "isSecure": false, + "version": 4, + "isBeaming": true, + "manufacturerId": 152, + "productId": 256, + "productType": 25602, + "firmwareVersion": "10.7", + "zwavePlusVersion": 1, + "nodeType": 0, + "roleType": 5, + "deviceConfig": { + "manufacturerId": 152, + "manufacturer": "Radio Thermostat Company of America (RTC)", + "label": "CT100 Plus", + "description": "Z-Wave Thermostat", + "devices": [{ "productType": "0x6402", "productId": "0x0100" }], + "firmwareVersion": { "min": "0.0", "max": "255.255" }, + "paramInformation": { "_map": {} } + }, + "label": "CT100 Plus", + "neighbors": [1, 2, 3, 4, 23], + "endpointCountIsDynamic": false, + "endpointsHaveIdenticalCapabilities": false, + "individualEndpointCount": 2, + "aggregatedEndpointCount": 0, + "interviewAttempts": 1, + "endpoints": [ + { + "nodeId": 26, + "index": 0, + "installerIcon": 4608, + "userIcon": 4608 + }, + { "nodeId": 26, "index": 1 }, + { + "nodeId": 26, + "index": 2, + "installerIcon": 3328, + "userIcon": 3333 + } + ], + "values": [ + { + "commandClassName": "Manufacturer Specific", + "commandClass": 114, + "endpoint": 0, + "property": "manufacturerId", + "propertyName": "manufacturerId", + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Manufacturer ID" + }, + "value": 152, + "ccVersion": 2 + }, + { + "commandClassName": "Manufacturer Specific", + "commandClass": 114, + "endpoint": 0, + "property": "productType", + "propertyName": "productType", + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Product type" + }, + "value": 25602, + "ccVersion": 2 + }, + { + "commandClassName": "Manufacturer Specific", + "commandClass": 114, + "endpoint": 0, + "property": "productId", + "propertyName": "productId", + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Product ID" + }, + "value": 256, + "ccVersion": 2 + }, + { + "commandClassName": "Thermostat Mode", + "commandClass": 64, + "endpoint": 0, + "property": "mode", + "propertyName": "mode", + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "min": 0, + "max": 31, + "label": "Thermostat mode", + "states": { + "0": "Off", + "1": "Heat", + "2": "Cool", + "3": "Auto", + "11": "Energy heat", + "12": "Energy cool" + } + }, + "value": 1, + "ccVersion": 2 + }, + { + "commandClassName": "Thermostat Mode", + "commandClass": 64, + "endpoint": 0, + "property": "manufacturerData", + "propertyName": "manufacturerData", + "metadata": { + "type": "any", + "readable": true, + "writeable": true + }, + "ccVersion": 2 + }, + { + "commandClassName": "Thermostat Setpoint", + "commandClass": 67, + "endpoint": 0, + "property": "setpoint", + "propertyKey": 1, + "propertyName": "setpoint", + "propertyKeyName": "Heating", + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "unit": "\u00b0F", + "ccSpecific": { "setpointType": 1 } + }, + "value": 72, + "ccVersion": 2 + }, + { + "commandClassName": "Thermostat Setpoint", + "commandClass": 67, + "endpoint": 0, + "property": "setpoint", + "propertyKey": 2, + "propertyName": "setpoint", + "propertyKeyName": "Cooling", + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "unit": "\u00b0F", + "ccSpecific": { "setpointType": 2 } + }, + "value": 73, + "ccVersion": 2 + }, + { + "commandClassName": "Thermostat Setpoint", + "commandClass": 67, + "endpoint": 0, + "property": "setpoint", + "propertyKey": 11, + "propertyName": "setpoint", + "propertyKeyName": "Energy Save Heating", + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "unit": "\u00b0F", + "ccSpecific": { "setpointType": 11 } + }, + "value": 62, + "ccVersion": 2 + }, + { + "commandClassName": "Thermostat Setpoint", + "commandClass": 67, + "endpoint": 0, + "property": "setpoint", + "propertyKey": 12, + "propertyName": "setpoint", + "propertyKeyName": "Energy Save Cooling", + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "unit": "\u00b0F", + "ccSpecific": { "setpointType": 12 } + }, + "value": 85, + "ccVersion": 2 + }, + { + "commandClassName": "Version", + "commandClass": 134, + "endpoint": 0, + "property": "libraryType", + "propertyName": "libraryType", + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Library type" + }, + "value": 3, + "ccVersion": 2 + }, + { + "commandClassName": "Version", + "commandClass": 134, + "endpoint": 0, + "property": "protocolVersion", + "propertyName": "protocolVersion", + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave protocol version" + }, + "value": "4.24", + "ccVersion": 2 + }, + { + "commandClassName": "Version", + "commandClass": 134, + "endpoint": 0, + "property": "firmwareVersions", + "propertyName": "firmwareVersions", + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave chip firmware versions" + }, + "value": ["10.7"], + "ccVersion": 2 + }, + { + "commandClassName": "Version", + "commandClass": 134, + "endpoint": 0, + "property": "hardwareVersion", + "propertyName": "hardwareVersion", + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave chip hardware version" + }, + "ccVersion": 2 + }, + { + "commandClassName": "Indicator", + "commandClass": 135, + "endpoint": 0, + "property": "value", + "propertyName": "value", + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "min": 0, + "max": 255, + "label": "Indicator value", + "ccSpecific": { "indicatorId": 0 } + }, + "value": 0, + "ccVersion": 1 + }, + { + "commandClassName": "Thermostat Operating State", + "commandClass": 66, + "endpoint": 0, + "property": "state", + "propertyName": "state", + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 255, + "label": "Operating state", + "states": { + "0": "Idle", + "1": "Heating", + "2": "Cooling", + "3": "Fan Only", + "4": "Pending Heat", + "5": "Pending Cool", + "6": "Vent/Economizer", + "7": "Aux Heating", + "8": "2nd Stage Heating", + "9": "2nd Stage Cooling", + "10": "2nd Stage Aux Heat", + "11": "3rd Stage Aux Heat" + } + }, + "value": 0, + "ccVersion": 2 + }, + { + "commandClassName": "Configuration", + "commandClass": 112, + "endpoint": 0, + "property": 1, + "propertyName": "Temperature Reporting Threshold", + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 4, + "default": 2, + "format": 0, + "allowManualEntry": false, + "states": { + "0": "Disabled", + "1": "0.5\u00b0 F", + "2": "1.0\u00b0 F", + "3": "1.5\u00b0 F", + "4": "2.0\u00b0 F" + }, + "label": "Temperature Reporting Threshold", + "description": "Reporting threshold for changes in the ambient temperature", + "isFromConfig": true + }, + "value": 2, + "ccVersion": 1 + }, + { + "commandClassName": "Configuration", + "commandClass": 112, + "endpoint": 0, + "property": 2, + "propertyName": "HVAC Settings", + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "valueSize": 4, + "min": 0, + "max": 0, + "default": 0, + "format": 0, + "allowManualEntry": true, + "label": "HVAC Settings", + "description": "Configured HVAC settings", + "isFromConfig": true + }, + "value": 17891329, + "ccVersion": 1 + }, + { + "commandClassName": "Configuration", + "commandClass": 112, + "endpoint": 0, + "property": 4, + "propertyName": "Power Status", + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "valueSize": 1, + "min": 0, + "max": 0, + "default": 0, + "format": 0, + "allowManualEntry": true, + "label": "Power Status", + "description": "C-Wire / Battery Status", + "isFromConfig": true + }, + "value": 1, + "ccVersion": 1 + }, + { + "commandClassName": "Configuration", + "commandClass": 112, + "endpoint": 0, + "property": 5, + "propertyName": "Humidity Reporting Threshold", + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 255, + "default": 0, + "format": 1, + "allowManualEntry": false, + "states": { + "0": "Disabled", + "1": "3% RH", + "2": "5% RH", + "3": "10% RH" + }, + "label": "Humidity Reporting Threshold", + "description": "Reporting threshold for changes in the relative humidity", + "isFromConfig": true + }, + "value": 2, + "ccVersion": 1 + }, + { + "commandClassName": "Configuration", + "commandClass": 112, + "endpoint": 0, + "property": 6, + "propertyName": "Auxiliary/Emergency", + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 255, + "default": 0, + "format": 1, + "allowManualEntry": false, + "states": { + "0": "Auxiliary/Emergency heat disabled", + "1": "Auxiliary/Emergency heat enabled" + }, + "label": "Auxiliary/Emergency", + "description": "Enables or disables auxiliary / emergency heating", + "isFromConfig": true + }, + "value": 0, + "ccVersion": 1 + }, + { + "commandClassName": "Configuration", + "commandClass": 112, + "endpoint": 0, + "property": 7, + "propertyName": "Thermostat Swing Temperature", + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 1, + "max": 8, + "default": 2, + "format": 0, + "allowManualEntry": false, + "states": { + "1": "0.5\u00b0 F", + "2": "1.0\u00b0 F", + "3": "1.5\u00b0 F", + "4": "2.0\u00b0 F", + "5": "2.5\u00b0 F", + "6": "3.0\u00b0 F", + "7": "3.5\u00b0 F", + "8": "4.0\u00b0 F" + }, + "label": "Thermostat Swing Temperature", + "description": "Variance allowed from setpoint to engage HVAC", + "isFromConfig": true + }, + "value": 2, + "ccVersion": 1 + }, + { + "commandClassName": "Configuration", + "commandClass": 112, + "endpoint": 0, + "property": 8, + "propertyName": "Thermostat Diff Temperature", + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 4, + "max": 12, + "default": 4, + "format": 0, + "allowManualEntry": false, + "states": { + "4": "2.0\u00b0 F", + "8": "4.0\u00b0 F", + "12": "6.0\u00b0 F" + }, + "label": "Thermostat Diff Temperature", + "description": "Configures additional stages", + "isFromConfig": true + }, + "value": 1028, + "ccVersion": 1 + }, + { + "commandClassName": "Configuration", + "commandClass": 112, + "endpoint": 0, + "property": 9, + "propertyName": "Thermostat Recovery Mode", + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 1, + "max": 2, + "default": 2, + "format": 0, + "allowManualEntry": false, + "states": { + "1": "Fast recovery mode", + "2": "Economy recovery mode" + }, + "label": "Thermostat Recovery Mode", + "description": "Fast or Economy recovery mode", + "isFromConfig": true + }, + "value": 2, + "ccVersion": 1 + }, + { + "commandClassName": "Configuration", + "commandClass": 112, + "endpoint": 0, + "property": 10, + "propertyName": "Temperature Reporting Filter", + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 4, + "min": 0, + "max": 124, + "default": 124, + "format": 0, + "allowManualEntry": true, + "label": "Temperature Reporting Filter", + "description": "Upper/Lower bounds for thermostat temperature reporting", + "isFromConfig": true + }, + "value": 32000, + "ccVersion": 1 + }, + { + "commandClassName": "Configuration", + "commandClass": 112, + "endpoint": 0, + "property": 11, + "propertyName": "Simple UI Mode", + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 1, + "default": 1, + "format": 0, + "allowManualEntry": false, + "states": { + "0": "Normal mode enabled", + "1": "Simple mode enabled" + }, + "label": "Simple UI Mode", + "description": "Simple mode enable/disable", + "isFromConfig": true + }, + "value": 1, + "ccVersion": 1 + }, + { + "commandClassName": "Configuration", + "commandClass": 112, + "endpoint": 0, + "property": 12, + "propertyName": "Multicast", + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 1, + "default": 0, + "format": 0, + "allowManualEntry": false, + "states": { + "0": "Multicast disabled", + "1": "Multicast enabled" + }, + "label": "Multicast", + "description": "Enable or disables Multicast", + "isFromConfig": true + }, + "value": 0, + "ccVersion": 1 + }, + { + "commandClassName": "Configuration", + "commandClass": 112, + "endpoint": 0, + "property": 3, + "propertyName": "Utility Lock Enable/Disable", + "metadata": { + "type": "number", + "readable": false, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 255, + "default": 0, + "format": 1, + "allowManualEntry": false, + "states": { + "0": "Utility lock disabled", + "1": "Utility lock enabled" + }, + "label": "Utility Lock Enable/Disable", + "description": "Prevents setpoint changes at thermostat", + "isFromConfig": true + }, + "ccVersion": 1 + }, + { + "commandClassName": "Battery", + "commandClass": 128, + "endpoint": 0, + "property": "level", + "propertyName": "level", + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 100, + "unit": "%", + "label": "Battery level" + }, + "value": 100, + "ccVersion": 1 + }, + { + "commandClassName": "Battery", + "commandClass": 128, + "endpoint": 0, + "property": "isLow", + "propertyName": "isLow", + "metadata": { + "type": "boolean", + "readable": true, + "writeable": false, + "label": "Low battery level" + }, + "value": false, + "ccVersion": 1 + }, + { + "commandClassName": "Multilevel Sensor", + "commandClass": 49, + "endpoint": 2, + "property": "Air temperature", + "propertyName": "Air temperature", + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "unit": "\u00b0F", + "label": "Air temperature", + "ccSpecific": { "sensorType": 1, "scale": 1 } + }, + "value": 72.5, + "ccVersion": 5 + }, + { + "commandClassName": "Multilevel Sensor", + "commandClass": 49, + "endpoint": 2, + "property": "Humidity", + "propertyName": "Humidity", + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "unit": "%", + "label": "Humidity", + "ccSpecific": { "sensorType": 5, "scale": 0 } + }, + "value": 20, + "ccVersion": 5 + } + ] +} \ No newline at end of file