From 3d5b007c6bc63be50a4aa5bb8e45e9366bdbd201 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Micha=C3=ABl=20Arnauts?= <michael.arnauts@gmail.com>
Date: Mon, 18 Nov 2019 00:39:49 +0100
Subject: [PATCH] Implement more Comfoconnect sensors (#28817)

* Rework Comfoconnect sensor platform

* Sort ATTRS and fix icon

* Add unique_id to fan

* Use a different signal per sensor type

* Add more logging

* Swap to be sure.

* Remove -fan suffix from unique_id
---
 .../components/comfoconnect/__init__.py       |  32 +-
 homeassistant/components/comfoconnect/fan.py  |  24 +-
 .../components/comfoconnect/sensor.py         | 291 +++++++++++++-----
 3 files changed, 242 insertions(+), 105 deletions(-)

diff --git a/homeassistant/components/comfoconnect/__init__.py b/homeassistant/components/comfoconnect/__init__.py
index efdbf020f1a..f1fd67cc4bb 100644
--- a/homeassistant/components/comfoconnect/__init__.py
+++ b/homeassistant/components/comfoconnect/__init__.py
@@ -1,12 +1,7 @@
 """Support to control a Zehnder ComfoAir Q350/450/600 ventilation unit."""
 import logging
 
-from pycomfoconnect import (
-    SENSOR_TEMPERATURE_EXTRACT,
-    SENSOR_TEMPERATURE_OUTDOOR,
-    Bridge,
-    ComfoConnect,
-)
+from pycomfoconnect import Bridge, ComfoConnect
 import voluptuous as vol
 
 from homeassistant.const import (
@@ -24,14 +19,7 @@ _LOGGER = logging.getLogger(__name__)
 
 DOMAIN = "comfoconnect"
 
-SIGNAL_COMFOCONNECT_UPDATE_RECEIVED = "comfoconnect_update_received"
-
-ATTR_CURRENT_TEMPERATURE = "current_temperature"
-ATTR_CURRENT_HUMIDITY = "current_humidity"
-ATTR_OUTSIDE_TEMPERATURE = "outside_temperature"
-ATTR_OUTSIDE_HUMIDITY = "outside_humidity"
-ATTR_AIR_FLOW_SUPPLY = "air_flow_supply"
-ATTR_AIR_FLOW_EXHAUST = "air_flow_exhaust"
+SIGNAL_COMFOCONNECT_UPDATE_RECEIVED = "comfoconnect_update_received_{}"
 
 CONF_USER_AGENT = "user_agent"
 
@@ -105,6 +93,7 @@ class ComfoConnectBridge:
         self.data = {}
         self.name = name
         self.hass = hass
+        self.unique_id = bridge.uuid.hex()
 
         self.comfoconnect = ComfoConnect(
             bridge=bridge,
@@ -125,13 +114,8 @@ class ComfoConnectBridge:
         self.comfoconnect.disconnect()
 
     def sensor_callback(self, var, value):
-        """Call function for sensor updates."""
-        _LOGGER.debug("Got value from bridge: %d = %d", var, value)
-
-        if var in [SENSOR_TEMPERATURE_EXTRACT, SENSOR_TEMPERATURE_OUTDOOR]:
-            self.data[var] = value / 10
-        else:
-            self.data[var] = value
-
-        # Notify listeners that we have received an update
-        dispatcher_send(self.hass, SIGNAL_COMFOCONNECT_UPDATE_RECEIVED, var)
+        """Notify listeners that we have received an update."""
+        _LOGGER.debug("Received update for %s: %s", var, value)
+        dispatcher_send(
+            self.hass, SIGNAL_COMFOCONNECT_UPDATE_RECEIVED.format(var), value
+        )
diff --git a/homeassistant/components/comfoconnect/fan.py b/homeassistant/components/comfoconnect/fan.py
index 34e784d61eb..432b25ac602 100644
--- a/homeassistant/components/comfoconnect/fan.py
+++ b/homeassistant/components/comfoconnect/fan.py
@@ -43,24 +43,34 @@ class ComfoConnectFan(FanEntity):
 
     async def async_added_to_hass(self):
         """Register for sensor updates."""
+        _LOGGER.debug("Registering for fan speed")
+        async_dispatcher_connect(
+            self.hass,
+            SIGNAL_COMFOCONNECT_UPDATE_RECEIVED.format(SENSOR_FAN_SPEED_MODE),
+            self._handle_update,
+        )
         await self.hass.async_add_executor_job(
             self._ccb.comfoconnect.register_sensor, SENSOR_FAN_SPEED_MODE
         )
-        async_dispatcher_connect(
-            self.hass, SIGNAL_COMFOCONNECT_UPDATE_RECEIVED, self._handle_update
-        )
 
-    def _handle_update(self, var):
+    def _handle_update(self, value):
         """Handle update callbacks."""
-        if var == SENSOR_FAN_SPEED_MODE:
-            _LOGGER.debug("Received update for %s", var)
-            self.schedule_update_ha_state()
+        _LOGGER.debug(
+            "Handle update for fan speed (%d): %s", SENSOR_FAN_SPEED_MODE, value
+        )
+        self._ccb.data[SENSOR_FAN_SPEED_MODE] = value
+        self.schedule_update_ha_state()
 
     @property
     def should_poll(self) -> bool:
         """Do not poll."""
         return False
 
+    @property
+    def unique_id(self):
+        """Return a unique_id for this entity."""
+        return self._ccb.unique_id
+
     @property
     def name(self):
         """Return the name of the fan."""
diff --git a/homeassistant/components/comfoconnect/sensor.py b/homeassistant/components/comfoconnect/sensor.py
index a1f16ed9631..3e3507ea48d 100644
--- a/homeassistant/components/comfoconnect/sensor.py
+++ b/homeassistant/components/comfoconnect/sensor.py
@@ -2,93 +2,214 @@
 import logging
 
 from pycomfoconnect import (
+    SENSOR_BYPASS_STATE,
+    SENSOR_DAYS_TO_REPLACE_FILTER,
+    SENSOR_FAN_EXHAUST_DUTY,
     SENSOR_FAN_EXHAUST_FLOW,
+    SENSOR_FAN_EXHAUST_SPEED,
+    SENSOR_FAN_SUPPLY_DUTY,
     SENSOR_FAN_SUPPLY_FLOW,
+    SENSOR_FAN_SUPPLY_SPEED,
+    SENSOR_HUMIDITY_EXHAUST,
     SENSOR_HUMIDITY_EXTRACT,
     SENSOR_HUMIDITY_OUTDOOR,
+    SENSOR_HUMIDITY_SUPPLY,
+    SENSOR_POWER_CURRENT,
+    SENSOR_TEMPERATURE_EXHAUST,
     SENSOR_TEMPERATURE_EXTRACT,
     SENSOR_TEMPERATURE_OUTDOOR,
+    SENSOR_TEMPERATURE_SUPPLY,
 )
+import voluptuous as vol
 
-from homeassistant.const import CONF_RESOURCES, TEMP_CELSIUS
+from homeassistant.components.sensor import PLATFORM_SCHEMA
+from homeassistant.const import (
+    ATTR_DEVICE_CLASS,
+    CONF_RESOURCES,
+    DEVICE_CLASS_HUMIDITY,
+    DEVICE_CLASS_POWER,
+    DEVICE_CLASS_TEMPERATURE,
+    POWER_WATT,
+    TEMP_CELSIUS,
+)
+import homeassistant.helpers.config_validation as cv
 from homeassistant.helpers.dispatcher import async_dispatcher_connect
 from homeassistant.helpers.entity import Entity
 
-from . import (
-    ATTR_AIR_FLOW_EXHAUST,
-    ATTR_AIR_FLOW_SUPPLY,
-    ATTR_CURRENT_HUMIDITY,
-    ATTR_CURRENT_TEMPERATURE,
-    ATTR_OUTSIDE_HUMIDITY,
-    ATTR_OUTSIDE_TEMPERATURE,
-    DOMAIN,
-    SIGNAL_COMFOCONNECT_UPDATE_RECEIVED,
-    ComfoConnectBridge,
-)
+from . import DOMAIN, SIGNAL_COMFOCONNECT_UPDATE_RECEIVED, ComfoConnectBridge
 
-_LOGGER = logging.getLogger(__name__)
+ATTR_AIR_FLOW_EXHAUST = "air_flow_exhaust"
+ATTR_AIR_FLOW_SUPPLY = "air_flow_supply"
+ATTR_BYPASS_STATE = "bypass_state"
+ATTR_CURRENT_HUMIDITY = "current_humidity"
+ATTR_CURRENT_TEMPERATURE = "current_temperature"
+ATTR_DAYS_TO_REPLACE_FILTER = "days_to_replace_filter"
+ATTR_EXHAUST_FAN_DUTY = "exhaust_fan_duty"
+ATTR_EXHAUST_FAN_SPEED = "exhaust_fan_speed"
+ATTR_EXHAUST_HUMIDITY = "exhaust_humidity"
+ATTR_EXHAUST_TEMPERATURE = "exhaust_temperature"
+ATTR_OUTSIDE_HUMIDITY = "outside_humidity"
+ATTR_OUTSIDE_TEMPERATURE = "outside_temperature"
+ATTR_POWER_CURRENT = "power_usage"
+ATTR_SUPPLY_FAN_DUTY = "supply_fan_duty"
+ATTR_SUPPLY_FAN_SPEED = "supply_fan_speed"
+ATTR_SUPPLY_HUMIDITY = "supply_humidity"
+ATTR_SUPPLY_TEMPERATURE = "supply_temperature"
 
-SENSOR_TYPES = {}
+_LOGGER = logging.getLogger(__name__)
 
+ATTR_ICON = "icon"
+ATTR_ID = "id"
+ATTR_LABEL = "label"
+ATTR_MULTIPLIER = "multiplier"
+ATTR_UNIT = "unit"
 
-def setup_platform(hass, config, add_entities, discovery_info=None):
-    """Set up the ComfoConnect fan platform."""
+SENSOR_TYPES = {
+    ATTR_CURRENT_TEMPERATURE: {
+        ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
+        ATTR_LABEL: "Inside Temperature",
+        ATTR_UNIT: TEMP_CELSIUS,
+        ATTR_ICON: "mdi:thermometer",
+        ATTR_ID: SENSOR_TEMPERATURE_EXTRACT,
+        ATTR_MULTIPLIER: 0.1,
+    },
+    ATTR_CURRENT_HUMIDITY: {
+        ATTR_DEVICE_CLASS: DEVICE_CLASS_HUMIDITY,
+        ATTR_LABEL: "Inside Humidity",
+        ATTR_UNIT: "%",
+        ATTR_ICON: "mdi:water-percent",
+        ATTR_ID: SENSOR_HUMIDITY_EXTRACT,
+    },
+    ATTR_OUTSIDE_TEMPERATURE: {
+        ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
+        ATTR_LABEL: "Outside Temperature",
+        ATTR_UNIT: TEMP_CELSIUS,
+        ATTR_ICON: "mdi:thermometer",
+        ATTR_ID: SENSOR_TEMPERATURE_OUTDOOR,
+        ATTR_MULTIPLIER: 0.1,
+    },
+    ATTR_OUTSIDE_HUMIDITY: {
+        ATTR_DEVICE_CLASS: DEVICE_CLASS_HUMIDITY,
+        ATTR_LABEL: "Outside Humidity",
+        ATTR_UNIT: "%",
+        ATTR_ICON: "mdi:water-percent",
+        ATTR_ID: SENSOR_HUMIDITY_OUTDOOR,
+    },
+    ATTR_SUPPLY_TEMPERATURE: {
+        ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
+        ATTR_LABEL: "Supply Temperature",
+        ATTR_UNIT: TEMP_CELSIUS,
+        ATTR_ICON: "mdi:thermometer",
+        ATTR_ID: SENSOR_TEMPERATURE_SUPPLY,
+        ATTR_MULTIPLIER: 0.1,
+    },
+    ATTR_SUPPLY_HUMIDITY: {
+        ATTR_DEVICE_CLASS: DEVICE_CLASS_HUMIDITY,
+        ATTR_LABEL: "Supply Humidity",
+        ATTR_UNIT: "%",
+        ATTR_ICON: "mdi:water-percent",
+        ATTR_ID: SENSOR_HUMIDITY_SUPPLY,
+    },
+    ATTR_SUPPLY_FAN_SPEED: {
+        ATTR_DEVICE_CLASS: None,
+        ATTR_LABEL: "Supply Fan Speed",
+        ATTR_UNIT: "rpm",
+        ATTR_ICON: "mdi:fan",
+        ATTR_ID: SENSOR_FAN_SUPPLY_SPEED,
+    },
+    ATTR_SUPPLY_FAN_DUTY: {
+        ATTR_DEVICE_CLASS: None,
+        ATTR_LABEL: "Supply Fan Duty",
+        ATTR_UNIT: "%",
+        ATTR_ICON: "mdi:fan",
+        ATTR_ID: SENSOR_FAN_SUPPLY_DUTY,
+    },
+    ATTR_EXHAUST_FAN_SPEED: {
+        ATTR_DEVICE_CLASS: None,
+        ATTR_LABEL: "Exhaust Fan Speed",
+        ATTR_UNIT: "rpm",
+        ATTR_ICON: "mdi:fan",
+        ATTR_ID: SENSOR_FAN_EXHAUST_SPEED,
+    },
+    ATTR_EXHAUST_FAN_DUTY: {
+        ATTR_DEVICE_CLASS: None,
+        ATTR_LABEL: "Exhaust Fan Duty",
+        ATTR_UNIT: "%",
+        ATTR_ICON: "mdi:fan",
+        ATTR_ID: SENSOR_FAN_EXHAUST_DUTY,
+    },
+    ATTR_EXHAUST_TEMPERATURE: {
+        ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
+        ATTR_LABEL: "Exhaust Temperature",
+        ATTR_UNIT: TEMP_CELSIUS,
+        ATTR_ICON: "mdi:thermometer",
+        ATTR_ID: SENSOR_TEMPERATURE_EXHAUST,
+        ATTR_MULTIPLIER: 0.1,
+    },
+    ATTR_EXHAUST_HUMIDITY: {
+        ATTR_DEVICE_CLASS: DEVICE_CLASS_HUMIDITY,
+        ATTR_LABEL: "Exhaust Humidity",
+        ATTR_UNIT: "%",
+        ATTR_ICON: "mdi:water-percent",
+        ATTR_ID: SENSOR_HUMIDITY_EXHAUST,
+    },
+    ATTR_AIR_FLOW_SUPPLY: {
+        ATTR_DEVICE_CLASS: None,
+        ATTR_LABEL: "Supply airflow",
+        ATTR_UNIT: "m³/h",
+        ATTR_ICON: "mdi:fan",
+        ATTR_ID: SENSOR_FAN_SUPPLY_FLOW,
+    },
+    ATTR_AIR_FLOW_EXHAUST: {
+        ATTR_DEVICE_CLASS: None,
+        ATTR_LABEL: "Exhaust airflow",
+        ATTR_UNIT: "m³/h",
+        ATTR_ICON: "mdi:fan",
+        ATTR_ID: SENSOR_FAN_EXHAUST_FLOW,
+    },
+    ATTR_BYPASS_STATE: {
+        ATTR_DEVICE_CLASS: None,
+        ATTR_LABEL: "Bypass State",
+        ATTR_UNIT: "%",
+        ATTR_ICON: "mdi:camera-iris",
+        ATTR_ID: SENSOR_BYPASS_STATE,
+    },
+    ATTR_DAYS_TO_REPLACE_FILTER: {
+        ATTR_DEVICE_CLASS: None,
+        ATTR_LABEL: "Days to replace filter",
+        ATTR_UNIT: "days",
+        ATTR_ICON: "mdi:calendar",
+        ATTR_ID: SENSOR_DAYS_TO_REPLACE_FILTER,
+    },
+    ATTR_POWER_CURRENT: {
+        ATTR_DEVICE_CLASS: DEVICE_CLASS_POWER,
+        ATTR_LABEL: "Power usage",
+        ATTR_UNIT: POWER_WATT,
+        ATTR_ICON: "mdi:flash",
+        ATTR_ID: SENSOR_POWER_CURRENT,
+    },
+}
 
-    global SENSOR_TYPES
-    SENSOR_TYPES = {
-        ATTR_CURRENT_TEMPERATURE: [
-            "Inside Temperature",
-            TEMP_CELSIUS,
-            "mdi:thermometer",
-            SENSOR_TEMPERATURE_EXTRACT,
-        ],
-        ATTR_CURRENT_HUMIDITY: [
-            "Inside Humidity",
-            "%",
-            "mdi:water-percent",
-            SENSOR_HUMIDITY_EXTRACT,
-        ],
-        ATTR_OUTSIDE_TEMPERATURE: [
-            "Outside Temperature",
-            TEMP_CELSIUS,
-            "mdi:thermometer",
-            SENSOR_TEMPERATURE_OUTDOOR,
-        ],
-        ATTR_OUTSIDE_HUMIDITY: [
-            "Outside Humidity",
-            "%",
-            "mdi:water-percent",
-            SENSOR_HUMIDITY_OUTDOOR,
-        ],
-        ATTR_AIR_FLOW_SUPPLY: [
-            "Supply airflow",
-            "m³/h",
-            "mdi:air-conditioner",
-            SENSOR_FAN_SUPPLY_FLOW,
-        ],
-        ATTR_AIR_FLOW_EXHAUST: [
-            "Exhaust airflow",
-            "m³/h",
-            "mdi:air-conditioner",
-            SENSOR_FAN_EXHAUST_FLOW,
-        ],
+PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
+    {
+        vol.Optional(CONF_RESOURCES, default=[]): vol.All(
+            cv.ensure_list, [vol.In(SENSOR_TYPES)]
+        ),
     }
+)
+
 
+def setup_platform(hass, config, add_entities, discovery_info=None):
+    """Set up the ComfoConnect fan platform."""
     ccb = hass.data[DOMAIN]
 
     sensors = []
     for resource in config[CONF_RESOURCES]:
-        sensor_type = resource.lower()
-
-        if sensor_type not in SENSOR_TYPES:
-            _LOGGER.warning("Sensor type: %s is not a valid sensor", sensor_type)
-            continue
-
         sensors.append(
             ComfoConnectSensor(
-                name=f"{ccb.name} {SENSOR_TYPES[sensor_type][0]}",
+                name=f"{ccb.name} {SENSOR_TYPES[resource][ATTR_LABEL]}",
                 ccb=ccb,
-                sensor_type=sensor_type,
+                sensor_type=resource,
             )
         )
 
@@ -102,23 +223,35 @@ class ComfoConnectSensor(Entity):
         """Initialize the ComfoConnect sensor."""
         self._ccb = ccb
         self._sensor_type = sensor_type
-        self._sensor_id = SENSOR_TYPES[self._sensor_type][3]
+        self._sensor_id = SENSOR_TYPES[self._sensor_type][ATTR_ID]
         self._name = name
 
     async def async_added_to_hass(self):
         """Register for sensor updates."""
-        await self.hass.async_add_executor_job(
-            self._ccb.comfoconnect.register_sensor, self._sensor_id
+        _LOGGER.debug(
+            "Registering for sensor %s (%d)", self._sensor_type, self._sensor_id,
         )
         async_dispatcher_connect(
-            self.hass, SIGNAL_COMFOCONNECT_UPDATE_RECEIVED, self._handle_update
+            self.hass,
+            SIGNAL_COMFOCONNECT_UPDATE_RECEIVED.format(self._sensor_id),
+            self._handle_update,
+        )
+        await self.hass.async_add_executor_job(
+            self._ccb.comfoconnect.register_sensor, self._sensor_id
         )
 
-    def _handle_update(self, var):
+    def _handle_update(self, value):
         """Handle update callbacks."""
-        if var == self._sensor_id:
-            _LOGGER.debug("Received update for %s", var)
-            self.schedule_update_ha_state()
+        _LOGGER.debug(
+            "Handle update for sensor %s (%d): %s",
+            self._sensor_type,
+            self._sensor_id,
+            value,
+        )
+        self._ccb.data[self._sensor_id] = round(
+            value * SENSOR_TYPES[self._sensor_type].get(ATTR_MULTIPLIER, 1), 2
+        )
+        self.schedule_update_ha_state()
 
     @property
     def state(self):
@@ -133,6 +266,11 @@ class ComfoConnectSensor(Entity):
         """Do not poll."""
         return False
 
+    @property
+    def unique_id(self):
+        """Return a unique_id for this entity."""
+        return f"{self._ccb.unique_id}-{self._sensor_type}"
+
     @property
     def name(self):
         """Return the name of the sensor."""
@@ -140,10 +278,15 @@ class ComfoConnectSensor(Entity):
 
     @property
     def icon(self):
-        """Return the icon to use in the frontend, if any."""
-        return SENSOR_TYPES[self._sensor_type][2]
+        """Return the icon to use in the frontend."""
+        return SENSOR_TYPES[self._sensor_type][ATTR_ICON]
 
     @property
     def unit_of_measurement(self):
-        """Return the unit of measurement of this entity, if any."""
-        return SENSOR_TYPES[self._sensor_type][1]
+        """Return the unit of measurement of this entity."""
+        return SENSOR_TYPES[self._sensor_type][ATTR_UNIT]
+
+    @property
+    def device_class(self):
+        """Return the device_class."""
+        return SENSOR_TYPES[self._sensor_type][ATTR_DEVICE_CLASS]
-- 
GitLab