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:
Michaël Arnauts
2017-06-28 18:04:54 +02:00
committed by GitHub
parent 6846a76c46
commit 5dde0c2201
5 changed files with 412 additions and 0 deletions

View File

@ -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

View 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)

View 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()

View 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]

View File

@ -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