mirror of
https://github.com/home-assistant/core.git
synced 2025-06-25 01:21:51 +02:00
Comfoconnect fan component (#8073)
* Comfoconnect fan component. * Fix linter. Don't store hass object when not needed. * More code style. * Rebase to dev and add to coverage ignore list. * Use published package from pypi.
This commit is contained in:
@ -35,6 +35,9 @@ omit =
|
||||
homeassistant/components/bloomsky.py
|
||||
homeassistant/components/*/bloomsky.py
|
||||
|
||||
homeassistant/components/comfoconnect.py
|
||||
homeassistant/components/*/comfoconnect.py
|
||||
|
||||
homeassistant/components/digital_ocean.py
|
||||
homeassistant/components/*/digital_ocean.py
|
||||
|
||||
|
137
homeassistant/components/comfoconnect.py
Normal file
137
homeassistant/components/comfoconnect.py
Normal file
@ -0,0 +1,137 @@
|
||||
"""
|
||||
Platform to control a Zehnder ComfoAir Q350/450/600 ventilation unit.
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/comfoconnect/
|
||||
"""
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.const import (
|
||||
CONF_HOST, CONF_NAME, CONF_TOKEN, CONF_PIN,
|
||||
EVENT_HOMEASSISTANT_STOP)
|
||||
from homeassistant.helpers import (discovery)
|
||||
from homeassistant.helpers.dispatcher import (dispatcher_send)
|
||||
|
||||
REQUIREMENTS = ['pycomfoconnect==0.3']
|
||||
|
||||
_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'
|
||||
|
||||
CONF_USER_AGENT = 'user_agent'
|
||||
|
||||
DEFAULT_PIN = 0
|
||||
DEFAULT_TOKEN = '00000000000000000000000000000001'
|
||||
DEFAULT_NAME = 'ComfoAirQ'
|
||||
DEFAULT_USER_AGENT = 'Home Assistant'
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
DOMAIN: vol.Schema({
|
||||
vol.Required(CONF_HOST): cv.string,
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_TOKEN, default=DEFAULT_TOKEN):
|
||||
vol.Length(min=32, max=32, msg='invalid token'),
|
||||
vol.Optional(CONF_USER_AGENT, default=DEFAULT_USER_AGENT):
|
||||
cv.string,
|
||||
vol.Optional(CONF_PIN, default=DEFAULT_PIN): cv.positive_int,
|
||||
}),
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
DEVICE = None
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
"""Setup the ComfoConnect bridge."""
|
||||
from pycomfoconnect import (Bridge)
|
||||
|
||||
conf = config[DOMAIN]
|
||||
host = conf.get(CONF_HOST)
|
||||
name = conf.get(CONF_NAME)
|
||||
token = conf.get(CONF_TOKEN)
|
||||
user_agent = conf.get(CONF_USER_AGENT)
|
||||
pin = conf.get(CONF_PIN)
|
||||
|
||||
# Run discovery on the configured ip
|
||||
bridges = Bridge.discover(host)
|
||||
if not bridges:
|
||||
_LOGGER.error('Could not connect to ComfoConnect bridge on %s', host)
|
||||
return False
|
||||
bridge = bridges[0]
|
||||
_LOGGER.info('Bridge found: %s (%s)', bridge.uuid.hex(), bridge.host)
|
||||
|
||||
# Setup ComfoConnect Bridge
|
||||
ccb = ComfoConnectBridge(hass, bridge, name, token, user_agent, pin)
|
||||
hass.data[DOMAIN] = ccb
|
||||
|
||||
# Start connection with bridge
|
||||
ccb.connect()
|
||||
|
||||
# Schedule disconnect on shutdown
|
||||
def _shutdown(_event):
|
||||
ccb.disconnect()
|
||||
|
||||
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, _shutdown)
|
||||
|
||||
# Load platforms
|
||||
discovery.load_platform(hass, 'fan', DOMAIN, {}, config)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class ComfoConnectBridge(object):
|
||||
"""Representation of a ComfoConnect bridge."""
|
||||
|
||||
def __init__(self, hass, bridge, name, token, friendly_name, pin):
|
||||
"""Initialize the ComfoConnect bridge."""
|
||||
from pycomfoconnect import (ComfoConnect)
|
||||
|
||||
self.data = {}
|
||||
self.name = name
|
||||
self.hass = hass
|
||||
|
||||
self.comfoconnect = ComfoConnect(bridge=bridge,
|
||||
local_uuid=bytes.fromhex(token),
|
||||
local_devicename=friendly_name,
|
||||
pin=pin)
|
||||
self.comfoconnect.callback_sensor = self.sensor_callback
|
||||
|
||||
def connect(self):
|
||||
"""Connect with the bridge."""
|
||||
_LOGGER.debug('Connecting with bridge.')
|
||||
self.comfoconnect.connect(True)
|
||||
|
||||
def disconnect(self):
|
||||
"""Disconnect from the bridge."""
|
||||
_LOGGER.debug('Disconnecting from bridge.')
|
||||
self.comfoconnect.disconnect()
|
||||
|
||||
def sensor_callback(self, var, value):
|
||||
"""Callback function for sensor updates."""
|
||||
_LOGGER.debug('Got value from bridge: %d = %d', var, value)
|
||||
|
||||
from pycomfoconnect import (
|
||||
SENSOR_TEMPERATURE_EXTRACT, SENSOR_TEMPERATURE_OUTDOOR)
|
||||
|
||||
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)
|
||||
|
||||
def subscribe_sensor(self, sensor_id):
|
||||
"""Subscribe for the specified sensor."""
|
||||
self.comfoconnect.register_sensor(sensor_id)
|
128
homeassistant/components/fan/comfoconnect.py
Normal file
128
homeassistant/components/fan/comfoconnect.py
Normal file
@ -0,0 +1,128 @@
|
||||
"""
|
||||
Platform to control a Zehnder ComfoAir Q350/450/600 ventilation unit.
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/fan.comfoconnect/
|
||||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.components.comfoconnect import (
|
||||
DOMAIN, ComfoConnectBridge, SIGNAL_COMFOCONNECT_UPDATE_RECEIVED)
|
||||
from homeassistant.components.fan import (
|
||||
FanEntity, SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH,
|
||||
SUPPORT_SET_SPEED)
|
||||
from homeassistant.const import STATE_UNKNOWN
|
||||
from homeassistant.helpers.dispatcher import (dispatcher_connect)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEPENDENCIES = ['comfoconnect']
|
||||
|
||||
SPEED_MAPPING = {
|
||||
0: SPEED_OFF,
|
||||
1: SPEED_LOW,
|
||||
2: SPEED_MEDIUM,
|
||||
3: SPEED_HIGH
|
||||
}
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the ComfoConnect fan platform."""
|
||||
ccb = hass.data[DOMAIN]
|
||||
|
||||
add_devices([
|
||||
ComfoConnectFan(
|
||||
hass,
|
||||
name=ccb.name,
|
||||
ccb=ccb
|
||||
)
|
||||
], True)
|
||||
|
||||
return
|
||||
|
||||
|
||||
class ComfoConnectFan(FanEntity):
|
||||
"""Representation of the fan platform."""
|
||||
|
||||
def __init__(self, hass, name, ccb: ComfoConnectBridge):
|
||||
"""Initialize the ComfoConnect fan."""
|
||||
from pycomfoconnect import (
|
||||
SENSOR_FAN_SPEED_MODE
|
||||
)
|
||||
|
||||
self._ccb = ccb
|
||||
self._name = name
|
||||
|
||||
# Ask the bridge to keep us updated
|
||||
self._ccb.comfoconnect.register_sensor(SENSOR_FAN_SPEED_MODE)
|
||||
|
||||
def _handle_update(var):
|
||||
if var == SENSOR_FAN_SPEED_MODE:
|
||||
_LOGGER.debug('Dispatcher update for %s.', var)
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
# Register for dispatcher updates
|
||||
dispatcher_connect(
|
||||
hass, SIGNAL_COMFOCONNECT_UPDATE_RECEIVED, _handle_update)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the fan."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Return the icon to use in the frontend."""
|
||||
return 'mdi:air-conditioner'
|
||||
|
||||
@property
|
||||
def supported_features(self) -> int:
|
||||
"""Flag supported features."""
|
||||
return SUPPORT_SET_SPEED
|
||||
|
||||
@property
|
||||
def speed(self):
|
||||
"""Return the current fan mode."""
|
||||
from pycomfoconnect import (SENSOR_FAN_SPEED_MODE)
|
||||
|
||||
try:
|
||||
speed = self._ccb.data[SENSOR_FAN_SPEED_MODE]
|
||||
return SPEED_MAPPING[speed]
|
||||
except KeyError:
|
||||
return STATE_UNKNOWN
|
||||
|
||||
@property
|
||||
def speed_list(self):
|
||||
"""List of available fan modes."""
|
||||
return [SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
|
||||
|
||||
def turn_on(self, speed: str=None, **kwargs) -> None:
|
||||
"""Turn on the fan."""
|
||||
if speed is None:
|
||||
speed = SPEED_LOW
|
||||
self.set_speed(speed)
|
||||
|
||||
def turn_off(self) -> None:
|
||||
"""Turn off the fan (to away)."""
|
||||
self.set_speed(SPEED_OFF)
|
||||
|
||||
def set_speed(self, mode):
|
||||
"""Set fan speed."""
|
||||
_LOGGER.debug('Changing fan mode to %s.', mode)
|
||||
|
||||
from pycomfoconnect import (
|
||||
CMD_FAN_MODE_AWAY, CMD_FAN_MODE_LOW, CMD_FAN_MODE_MEDIUM,
|
||||
CMD_FAN_MODE_HIGH
|
||||
)
|
||||
|
||||
if mode == SPEED_OFF:
|
||||
self._ccb.comfoconnect.cmd_rmi_request(CMD_FAN_MODE_AWAY)
|
||||
elif mode == SPEED_LOW:
|
||||
self._ccb.comfoconnect.cmd_rmi_request(CMD_FAN_MODE_LOW)
|
||||
elif mode == SPEED_MEDIUM:
|
||||
self._ccb.comfoconnect.cmd_rmi_request(CMD_FAN_MODE_MEDIUM)
|
||||
elif mode == SPEED_HIGH:
|
||||
self._ccb.comfoconnect.cmd_rmi_request(CMD_FAN_MODE_HIGH)
|
||||
|
||||
# Update current mode
|
||||
self.schedule_update_ha_state()
|
141
homeassistant/components/sensor/comfoconnect.py
Normal file
141
homeassistant/components/sensor/comfoconnect.py
Normal file
@ -0,0 +1,141 @@
|
||||
"""
|
||||
Platform to control a Zehnder ComfoAir Q350/450/600 ventilation unit.
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/sensor.comfoconnect/
|
||||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.components.comfoconnect import (
|
||||
DOMAIN, ComfoConnectBridge, ATTR_CURRENT_TEMPERATURE,
|
||||
ATTR_CURRENT_HUMIDITY, ATTR_OUTSIDE_TEMPERATURE,
|
||||
ATTR_OUTSIDE_HUMIDITY, ATTR_AIR_FLOW_SUPPLY,
|
||||
ATTR_AIR_FLOW_EXHAUST, SIGNAL_COMFOCONNECT_UPDATE_RECEIVED)
|
||||
from homeassistant.const import (
|
||||
CONF_RESOURCES, TEMP_CELSIUS, STATE_UNKNOWN)
|
||||
from homeassistant.helpers.dispatcher import dispatcher_connect
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEPENDENCIES = ['comfoconnect']
|
||||
|
||||
SENSOR_TYPES = {}
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the ComfoConnect fan platform."""
|
||||
from pycomfoconnect import (
|
||||
SENSOR_TEMPERATURE_EXTRACT, SENSOR_HUMIDITY_EXTRACT,
|
||||
SENSOR_TEMPERATURE_OUTDOOR, SENSOR_HUMIDITY_OUTDOOR,
|
||||
SENSOR_FAN_SUPPLY_FLOW, SENSOR_FAN_EXHAUST_FLOW)
|
||||
|
||||
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
|
||||
],
|
||||
}
|
||||
|
||||
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(
|
||||
hass,
|
||||
name="%s %s" % (ccb.name, SENSOR_TYPES[sensor_type][0]),
|
||||
ccb=ccb,
|
||||
sensor_type=sensor_type
|
||||
)
|
||||
)
|
||||
|
||||
add_devices(sensors, True)
|
||||
|
||||
return
|
||||
|
||||
|
||||
class ComfoConnectSensor(Entity):
|
||||
"""Representation of a ComfoConnect sensor."""
|
||||
|
||||
def __init__(self, hass, name, ccb: ComfoConnectBridge, sensor_type):
|
||||
"""Initialize the ComfoConnect sensor."""
|
||||
self._ccb = ccb
|
||||
self._sensor_type = sensor_type
|
||||
self._sensor_id = SENSOR_TYPES[self._sensor_type][3]
|
||||
self._name = name
|
||||
|
||||
# Register the requested sensor
|
||||
self._ccb.comfoconnect.register_sensor(self._sensor_id)
|
||||
|
||||
def _handle_update(var):
|
||||
if var == self._sensor_id:
|
||||
_LOGGER.debug('Dispatcher update for %s.', var)
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
# Register for dispatcher updates
|
||||
dispatcher_connect(
|
||||
hass, SIGNAL_COMFOCONNECT_UPDATE_RECEIVED, _handle_update)
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the state of the entity."""
|
||||
try:
|
||||
return self._ccb.data[self._sensor_id]
|
||||
except KeyError:
|
||||
return STATE_UNKNOWN
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the sensor."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Return the icon to use in the frontend, if any."""
|
||||
return SENSOR_TYPES[self._sensor_type][2]
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
"""Return the unit of measurement of this entity, if any."""
|
||||
return SENSOR_TYPES[self._sensor_type][1]
|
@ -533,6 +533,9 @@ pychromecast==0.8.1
|
||||
# homeassistant.components.media_player.cmus
|
||||
pycmus==0.1.0
|
||||
|
||||
# homeassistant.components.comfoconnect
|
||||
pycomfoconnect==0.3
|
||||
|
||||
# homeassistant.components.sensor.cups
|
||||
# pycups==1.9.73
|
||||
|
||||
|
Reference in New Issue
Block a user