From 95a97d99dd30aa9295294855258e1c9c685c43d2 Mon Sep 17 00:00:00 2001 From: RDFurman Date: Mon, 4 Apr 2022 12:26:33 -0600 Subject: [PATCH] Honeywell outdoor sensor (#65347) --- .../components/honeywell/__init__.py | 2 +- homeassistant/components/honeywell/const.py | 2 + homeassistant/components/honeywell/sensor.py | 96 +++++++++++++++++++ tests/components/honeywell/conftest.py | 23 +++++ tests/components/honeywell/test_sensor.py | 29 ++++++ 5 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/honeywell/sensor.py create mode 100644 tests/components/honeywell/test_sensor.py diff --git a/homeassistant/components/honeywell/__init__.py b/homeassistant/components/honeywell/__init__.py index d141822e2ea..0e0bfb902e8 100644 --- a/homeassistant/components/honeywell/__init__.py +++ b/homeassistant/components/honeywell/__init__.py @@ -21,7 +21,7 @@ from .const import ( UPDATE_LOOP_SLEEP_TIME = 5 MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=300) -PLATFORMS = [Platform.CLIMATE] +PLATFORMS = [Platform.CLIMATE, Platform.SENSOR] MIGRATE_OPTIONS_KEYS = {CONF_COOL_AWAY_TEMPERATURE, CONF_HEAT_AWAY_TEMPERATURE} diff --git a/homeassistant/components/honeywell/const.py b/homeassistant/components/honeywell/const.py index 08e7c3fc14e..94455d569cb 100644 --- a/homeassistant/components/honeywell/const.py +++ b/homeassistant/components/honeywell/const.py @@ -9,5 +9,7 @@ DEFAULT_COOL_AWAY_TEMPERATURE = 88 DEFAULT_HEAT_AWAY_TEMPERATURE = 61 CONF_DEV_ID = "thermostat" CONF_LOC_ID = "location" +TEMPERATURE_STATUS_KEY = "outdoor_temperature" +HUMIDITY_STATUS_KEY = "outdoor_humidity" _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/honeywell/sensor.py b/homeassistant/components/honeywell/sensor.py new file mode 100644 index 00000000000..151ad2ada1f --- /dev/null +++ b/homeassistant/components/honeywell/sensor.py @@ -0,0 +1,96 @@ +"""Support for Honeywell (US) Total Connect Comfort sensors.""" +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import dataclass +from typing import Any + +from somecomfort import Device + +from homeassistant.components.sensor import ( + SensorEntity, + SensorEntityDescription, + SensorStateClass, +) +from homeassistant.const import ( + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_TEMPERATURE, + PERCENTAGE, + TEMP_CELSIUS, + TEMP_FAHRENHEIT, +) +from homeassistant.helpers.typing import StateType + +from .const import DOMAIN, HUMIDITY_STATUS_KEY, TEMPERATURE_STATUS_KEY + + +def _get_temperature_sensor_unit(device: Device) -> str: + """Get the correct temperature unit for the device.""" + return TEMP_CELSIUS if device.temperature_unit == "C" else TEMP_FAHRENHEIT + + +@dataclass +class HoneywellSensorEntityDescriptionMixin: + """Mixin for required keys.""" + + value_fn: Callable[[Device], Any] + unit_fn: Callable[[Device], Any] + + +@dataclass +class HoneywellSensorEntityDescription( + SensorEntityDescription, HoneywellSensorEntityDescriptionMixin +): + """Describes a Honeywell sensor entity.""" + + +SENSOR_TYPES: tuple[HoneywellSensorEntityDescription, ...] = ( + HoneywellSensorEntityDescription( + key=TEMPERATURE_STATUS_KEY, + name="Temperature", + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, + value_fn=lambda device: device.outdoor_temperature, + unit_fn=_get_temperature_sensor_unit, + ), + HoneywellSensorEntityDescription( + key=HUMIDITY_STATUS_KEY, + name="Humidity", + device_class=DEVICE_CLASS_HUMIDITY, + state_class=SensorStateClass.MEASUREMENT, + value_fn=lambda device: device.outdoor_humidity, + unit_fn=lambda device: PERCENTAGE, + ), +) + + +async def async_setup_entry(hass, config, async_add_entities, discovery_info=None): + """Set up the Honeywell thermostat.""" + data = hass.data[DOMAIN][config.entry_id] + sensors = [] + + for device in data.devices.values(): + for description in SENSOR_TYPES: + if getattr(device, description.key) is not None: + sensors.append(HoneywellSensor(device, description)) + + async_add_entities(sensors) + + +class HoneywellSensor(SensorEntity): + """Representation of a Honeywell US Outdoor Temperature Sensor.""" + + entity_description: HoneywellSensorEntityDescription + + def __init__(self, device, description): + """Initialize the outdoor temperature sensor.""" + self._device = device + self.entity_description = description + self._attr_unique_id = f"{device.deviceid}_outdoor_{description.device_class}" + self._attr_name = f"{device.name} outdoor {description.device_class}" + self._attr_native_unit_of_measurement = description.unit_fn(device) + + @property + def native_value(self) -> StateType: + """Return the state.""" + return self.entity_description.value_fn(self._device) diff --git a/tests/components/honeywell/conftest.py b/tests/components/honeywell/conftest.py index ff5ec57e27c..dcb8edb4015 100644 --- a/tests/components/honeywell/conftest.py +++ b/tests/components/honeywell/conftest.py @@ -40,6 +40,27 @@ def device(): mock_device.name = "device1" mock_device.current_temperature = 20 mock_device.mac_address = "macaddress1" + mock_device.outdoor_temperature = None + mock_device.outdoor_humidity = None + return mock_device + + +@pytest.fixture +def device_with_outdoor_sensor(): + """Mock a somecomfort.Device.""" + mock_device = create_autospec(somecomfort.Device, instance=True) + mock_device.deviceid = 1234567 + mock_device._data = { + "canControlHumidification": False, + "hasFan": False, + } + mock_device.system_mode = "off" + mock_device.name = "device1" + mock_device.current_temperature = 20 + mock_device.mac_address = "macaddress1" + mock_device.temperature_unit = "C" + mock_device.outdoor_temperature = 5 + mock_device.outdoor_humidity = 25 return mock_device @@ -56,6 +77,8 @@ def another_device(): mock_device.name = "device2" mock_device.current_temperature = 20 mock_device.mac_address = "macaddress1" + mock_device.outdoor_temperature = None + mock_device.outdoor_humidity = None return mock_device diff --git a/tests/components/honeywell/test_sensor.py b/tests/components/honeywell/test_sensor.py new file mode 100644 index 00000000000..6a5b5636745 --- /dev/null +++ b/tests/components/honeywell/test_sensor.py @@ -0,0 +1,29 @@ +"""Test honeywell sensor.""" +from somecomfort import Device, Location + +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry + + +async def test_outdoor_sensor( + hass: HomeAssistant, + config_entry: MockConfigEntry, + location: Location, + device_with_outdoor_sensor: Device, +): + """Test outdoor temperature sensor.""" + location.devices_by_id[ + device_with_outdoor_sensor.deviceid + ] = device_with_outdoor_sensor + config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + temperature_state = hass.states.get("sensor.device1_outdoor_temperature") + humidity_state = hass.states.get("sensor.device1_outdoor_humidity") + + assert temperature_state + assert humidity_state + assert temperature_state.state == "5" + assert humidity_state.state == "25"