From 5085ce8ab10cff4733961e47d7ac72f3b1710987 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Mon, 3 Jun 2019 21:40:16 +0200 Subject: [PATCH] Add temperature sensor support to google smarthome thermostat device (#24264) * Add temperature sensor support to google smarthome thermostat device * fix lint for trait_test * Reset temperature unit in tests * Address comment --- .../components/google_assistant/const.py | 2 + .../components/google_assistant/trait.py | 158 +++++++++++------- .../components/google_assistant/test_trait.py | 33 ++++ 3 files changed, 133 insertions(+), 60 deletions(-) diff --git a/homeassistant/components/google_assistant/const.py b/homeassistant/components/google_assistant/const.py index 92afe90a5ac..ebded79447e 100644 --- a/homeassistant/components/google_assistant/const.py +++ b/homeassistant/components/google_assistant/const.py @@ -12,6 +12,7 @@ from homeassistant.components import ( media_player, scene, script, + sensor, switch, vacuum, ) @@ -108,6 +109,7 @@ DEVICE_CLASS_TO_GOOGLE_TYPES = { (binary_sensor.DOMAIN, binary_sensor.DEVICE_CLASS_WINDOW): TYPE_SENSOR, (media_player.DOMAIN, media_player.DEVICE_CLASS_TV): TYPE_TV, (media_player.DOMAIN, media_player.DEVICE_CLASS_SPEAKER): TYPE_SPEAKER, + (sensor.DOMAIN, sensor.DEVICE_CLASS_TEMPERATURE): TYPE_SENSOR, } CHALLENGE_ACK_NEEDED = 'ackNeeded' diff --git a/homeassistant/components/google_assistant/trait.py b/homeassistant/components/google_assistant/trait.py index f9590a07b95..7776daf65c9 100644 --- a/homeassistant/components/google_assistant/trait.py +++ b/homeassistant/components/google_assistant/trait.py @@ -13,6 +13,7 @@ from homeassistant.components import ( lock, scene, script, + sensor, switch, vacuum, ) @@ -550,89 +551,126 @@ class TemperatureSettingTrait(_Trait): @staticmethod def supported(domain, features, device_class): """Test if state is supported.""" - if domain != climate.DOMAIN: - return False + if domain == climate.DOMAIN: + return features & climate.SUPPORT_OPERATION_MODE - return features & climate.SUPPORT_OPERATION_MODE + return (domain == sensor.DOMAIN + and device_class == sensor.DEVICE_CLASS_TEMPERATURE) def sync_attributes(self): """Return temperature point and modes attributes for a sync request.""" - modes = [] - supported = self.state.attributes.get(ATTR_SUPPORTED_FEATURES) + response = {} + attrs = self.state.attributes + domain = self.state.domain + response['thermostatTemperatureUnit'] = _google_temp_unit( + self.hass.config.units.temperature_unit) - if supported & climate.SUPPORT_ON_OFF != 0: - modes.append(STATE_OFF) - modes.append(STATE_ON) + if domain == sensor.DOMAIN: + device_class = attrs.get(ATTR_DEVICE_CLASS) + if device_class == sensor.DEVICE_CLASS_TEMPERATURE: + response["queryOnlyTemperatureSetting"] = True - if supported & climate.SUPPORT_OPERATION_MODE != 0: - for mode in self.state.attributes.get(climate.ATTR_OPERATION_LIST, - []): - google_mode = self.hass_to_google.get(mode) - if google_mode and google_mode not in modes: - modes.append(google_mode) + elif domain == climate.DOMAIN: + modes = [] + supported = attrs.get(ATTR_SUPPORTED_FEATURES) - return { - 'availableThermostatModes': ','.join(modes), - 'thermostatTemperatureUnit': _google_temp_unit( - self.hass.config.units.temperature_unit) - } + if supported & climate.SUPPORT_ON_OFF != 0: + modes.append(STATE_OFF) + modes.append(STATE_ON) + + if supported & climate.SUPPORT_OPERATION_MODE != 0: + for mode in attrs.get(climate.ATTR_OPERATION_LIST, []): + google_mode = self.hass_to_google.get(mode) + if google_mode and google_mode not in modes: + modes.append(google_mode) + response['availableThermostatModes'] = ','.join(modes) + + return response def query_attributes(self): """Return temperature point and modes query attributes.""" - attrs = self.state.attributes response = {} - - operation = attrs.get(climate.ATTR_OPERATION_MODE) - supported = self.state.attributes.get(ATTR_SUPPORTED_FEATURES) - - if (supported & climate.SUPPORT_ON_OFF - and self.state.state == STATE_OFF): - response['thermostatMode'] = 'off' - elif (supported & climate.SUPPORT_OPERATION_MODE and - operation in self.hass_to_google): - response['thermostatMode'] = self.hass_to_google[operation] - elif supported & climate.SUPPORT_ON_OFF: - response['thermostatMode'] = 'on' - + attrs = self.state.attributes + domain = self.state.domain unit = self.hass.config.units.temperature_unit + if domain == sensor.DOMAIN: + device_class = attrs.get(ATTR_DEVICE_CLASS) + if device_class == sensor.DEVICE_CLASS_TEMPERATURE: + current_temp = self.state.state + if current_temp is not None: + response['thermostatTemperatureAmbient'] = \ + round(temp_util.convert( + float(current_temp), + unit, + TEMP_CELSIUS + ), 1) - current_temp = attrs.get(climate.ATTR_CURRENT_TEMPERATURE) - if current_temp is not None: - response['thermostatTemperatureAmbient'] = \ - round(temp_util.convert(current_temp, unit, TEMP_CELSIUS), 1) + elif domain == climate.DOMAIN: + operation = attrs.get(climate.ATTR_OPERATION_MODE) + supported = attrs.get(ATTR_SUPPORTED_FEATURES) - current_humidity = attrs.get(climate.ATTR_CURRENT_HUMIDITY) - if current_humidity is not None: - response['thermostatHumidityAmbient'] = current_humidity + if (supported & climate.SUPPORT_ON_OFF + and self.state.state == STATE_OFF): + response['thermostatMode'] = 'off' + elif (supported & climate.SUPPORT_OPERATION_MODE + and operation in self.hass_to_google): + response['thermostatMode'] = self.hass_to_google[operation] + elif supported & climate.SUPPORT_ON_OFF: + response['thermostatMode'] = 'on' - if operation == climate.STATE_AUTO: - if (supported & climate.SUPPORT_TARGET_TEMPERATURE_HIGH and - supported & climate.SUPPORT_TARGET_TEMPERATURE_LOW): - response['thermostatTemperatureSetpointHigh'] = \ + current_temp = attrs.get(climate.ATTR_CURRENT_TEMPERATURE) + if current_temp is not None: + response['thermostatTemperatureAmbient'] = \ round(temp_util.convert( - attrs[climate.ATTR_TARGET_TEMP_HIGH], - unit, TEMP_CELSIUS), 1) - response['thermostatTemperatureSetpointLow'] = \ - round(temp_util.convert( - attrs[climate.ATTR_TARGET_TEMP_LOW], - unit, TEMP_CELSIUS), 1) + current_temp, + unit, + TEMP_CELSIUS + ), 1) + + current_humidity = attrs.get(climate.ATTR_CURRENT_HUMIDITY) + if current_humidity is not None: + response['thermostatHumidityAmbient'] = current_humidity + + if operation == climate.STATE_AUTO: + if (supported & climate.SUPPORT_TARGET_TEMPERATURE_HIGH and + supported & climate.SUPPORT_TARGET_TEMPERATURE_LOW): + response['thermostatTemperatureSetpointHigh'] = \ + round(temp_util.convert( + attrs[climate.ATTR_TARGET_TEMP_HIGH], + unit, TEMP_CELSIUS), 1) + response['thermostatTemperatureSetpointLow'] = \ + round(temp_util.convert( + attrs[climate.ATTR_TARGET_TEMP_LOW], + unit, TEMP_CELSIUS), 1) + else: + target_temp = attrs.get(ATTR_TEMPERATURE) + if target_temp is not None: + target_temp = round( + temp_util.convert( + target_temp, + unit, + TEMP_CELSIUS + ), 1) + response['thermostatTemperatureSetpointHigh'] = \ + target_temp + response['thermostatTemperatureSetpointLow'] = \ + target_temp else: target_temp = attrs.get(ATTR_TEMPERATURE) if target_temp is not None: - target_temp = round( + response['thermostatTemperatureSetpoint'] = round( temp_util.convert(target_temp, unit, TEMP_CELSIUS), 1) - response['thermostatTemperatureSetpointHigh'] = target_temp - response['thermostatTemperatureSetpointLow'] = target_temp - else: - target_temp = attrs.get(ATTR_TEMPERATURE) - if target_temp is not None: - response['thermostatTemperatureSetpoint'] = round( - temp_util.convert(target_temp, unit, TEMP_CELSIUS), 1) return response async def execute(self, command, data, params, challenge): """Execute a temperature point or mode command.""" + domain = self.state.domain + if domain == sensor.DOMAIN: + raise SmartHomeError( + ERR_NOT_SUPPORTED, + 'Execute is not supported by sensor') + # All sent in temperatures are always in Celsius unit = self.hass.config.units.temperature_unit min_temp = self.state.attributes[climate.ATTR_MIN_TEMP] @@ -687,8 +725,8 @@ class TemperatureSettingTrait(_Trait): ATTR_ENTITY_ID: self.state.entity_id, } - if(supported & climate.SUPPORT_TARGET_TEMPERATURE_HIGH and - supported & climate.SUPPORT_TARGET_TEMPERATURE_LOW): + if(supported & climate.SUPPORT_TARGET_TEMPERATURE_HIGH + and supported & climate.SUPPORT_TARGET_TEMPERATURE_LOW): svc_data[climate.ATTR_TARGET_TEMP_HIGH] = temp_high svc_data[climate.ATTR_TARGET_TEMP_LOW] = temp_low else: diff --git a/tests/components/google_assistant/test_trait.py b/tests/components/google_assistant/test_trait.py index 28cab008201..6b1b6a7c9f4 100644 --- a/tests/components/google_assistant/test_trait.py +++ b/tests/components/google_assistant/test_trait.py @@ -14,6 +14,7 @@ from homeassistant.components import ( media_player, scene, script, + sensor, switch, vacuum, group, @@ -1380,3 +1381,35 @@ async def test_volume_media_player_relative(hass): ATTR_ENTITY_ID: 'media_player.bla', media_player.ATTR_MEDIA_VOLUME_LEVEL: .5 } + + +async def test_temperature_setting_sensor(hass): + """Test TemperatureSetting trait support for temperature sensor.""" + assert helpers.get_google_type(sensor.DOMAIN, + sensor.DEVICE_CLASS_TEMPERATURE) is not None + assert not trait.TemperatureSettingTrait.supported( + sensor.DOMAIN, + 0, + sensor.DEVICE_CLASS_HUMIDITY + ) + assert trait.TemperatureSettingTrait.supported( + sensor.DOMAIN, + 0, + sensor.DEVICE_CLASS_TEMPERATURE + ) + + hass.config.units.temperature_unit = TEMP_FAHRENHEIT + + trt = trait.TemperatureSettingTrait(hass, State('sensor.test', "70", { + ATTR_DEVICE_CLASS: sensor.DEVICE_CLASS_TEMPERATURE, + }), BASIC_CONFIG) + + assert trt.sync_attributes() == { + 'queryOnlyTemperatureSetting': True, + 'thermostatTemperatureUnit': 'F', + } + + assert trt.query_attributes() == { + 'thermostatTemperatureAmbient': 21.1 + } + hass.config.units.temperature_unit = TEMP_CELSIUS