XS1 component (#19115)

* added xs1 main component
added implementations for switch, sensor, climate and binary_sensor

* updated code
fixed styling
added comments
removed binary_sensor (wasn't working)

* ran "gen_requirements_all.py" script

* fixed linting issues

* added config options for port and ssl
small fixes

* use already defined config constants instead of defining new ones

* avoid passing in hass to the entity

* use async keyword and proper asyncio calls
limit updates with a global lock to prevent overwhelming the gateway with concurrent requests
change info logger calls to debug

* update dependency

* removed unneeded constant

* fix lint issues

* updated requirements

* removed unused imports

* fixed some flake8 errors

* fixed some flake8 errors

* changed imports to absolute paths

* fixed some lint errors

* fixed some lint errors

* fix update of attached sensor

* reordered imports
added config defaults
check if platform is available
changed docstring

* lint fix

* review fixes

* isort

* import fix

* review fix

* review fix

* review fix

* removed unused imports

* lint fix

* lint fix

* climate fix
exclude sensors that will be used in climate component from default sensor category

* .coveragerc fix

* lint fix

* moved platform to it's own package
pull/20993/head
Markus Ressel 2019-02-07 23:21:41 +01:00 committed by Martin Hjelmare
parent a9672b0d52
commit 542f024356
6 changed files with 341 additions and 0 deletions

View File

@ -655,6 +655,7 @@ omit =
homeassistant/components/wirelesstag/*
homeassistant/components/xiaomi_aqara/*
homeassistant/components/xiaomi_miio/*
homeassistant/components/xs1/*
homeassistant/components/zabbix/*
homeassistant/components/zeroconf/*
homeassistant/components/zha/__init__.py

View File

@ -0,0 +1,119 @@
"""
Support for the EZcontrol XS1 gateway.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/xs1/
"""
import asyncio
from functools import partial
import logging
import voluptuous as vol
from homeassistant.const import (
CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_SSL, CONF_USERNAME)
from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
REQUIREMENTS = ['xs1-api-client==2.3.5']
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'xs1'
ACTUATORS = 'actuators'
SENSORS = 'sensors'
# define configuration parameters
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_PORT, default=80): cv.string,
vol.Optional(CONF_SSL, default=False): cv.boolean,
vol.Optional(CONF_USERNAME): cv.string,
vol.Optional(CONF_PASSWORD): cv.string
}),
}, extra=vol.ALLOW_EXTRA)
XS1_COMPONENTS = [
'switch',
'sensor',
'climate'
]
# Lock used to limit the amount of concurrent update requests
# as the XS1 Gateway can only handle a very
# small amount of concurrent requests
UPDATE_LOCK = asyncio.Lock()
def _create_controller_api(host, port, ssl, user, password):
"""Create an api instance to use for communication."""
import xs1_api_client
try:
return xs1_api_client.XS1(
host=host,
port=port,
ssl=ssl,
user=user,
password=password)
except ConnectionError as error:
_LOGGER.error("Failed to create XS1 api client "
"because of a connection error: %s", error)
return None
async def async_setup(hass, config):
"""Set up XS1 Component."""
_LOGGER.debug("Initializing XS1")
host = config[DOMAIN][CONF_HOST]
port = config[DOMAIN][CONF_PORT]
ssl = config[DOMAIN][CONF_SSL]
user = config[DOMAIN].get(CONF_USERNAME)
password = config[DOMAIN].get(CONF_PASSWORD)
# initialize XS1 API
xs1 = await hass.async_add_executor_job(
partial(_create_controller_api,
host, port, ssl, user, password))
if xs1 is None:
return False
_LOGGER.debug(
"Establishing connection to XS1 gateway and retrieving data...")
hass.data[DOMAIN] = {}
actuators = await hass.async_add_executor_job(
partial(xs1.get_all_actuators, enabled=True))
sensors = await hass.async_add_executor_job(
partial(xs1.get_all_sensors, enabled=True))
hass.data[DOMAIN][ACTUATORS] = actuators
hass.data[DOMAIN][SENSORS] = sensors
_LOGGER.debug("Loading components for XS1 platform...")
# load components for supported devices
for component in XS1_COMPONENTS:
hass.async_create_task(
discovery.async_load_platform(
hass, component, DOMAIN, {}, config))
return True
class XS1DeviceEntity(Entity):
"""Representation of a base XS1 device."""
def __init__(self, device):
"""Initialize the XS1 device."""
self.device = device
async def async_update(self):
"""Retrieve latest device state."""
async with UPDATE_LOCK:
await self.hass.async_add_executor_job(
partial(self.device.update))

View File

@ -0,0 +1,109 @@
"""
Support for XS1 climate devices.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.xs1/
"""
from functools import partial
import logging
from homeassistant.components.climate import (
ATTR_TEMPERATURE, ClimateDevice, SUPPORT_TARGET_TEMPERATURE)
from homeassistant.components.xs1 import (
ACTUATORS, DOMAIN as COMPONENT_DOMAIN, SENSORS, XS1DeviceEntity)
DEPENDENCIES = ['xs1']
_LOGGER = logging.getLogger(__name__)
MIN_TEMP = 8
MAX_TEMP = 25
async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
"""Set up the XS1 thermostat platform."""
from xs1_api_client.api_constants import ActuatorType
actuators = hass.data[COMPONENT_DOMAIN][ACTUATORS]
sensors = hass.data[COMPONENT_DOMAIN][SENSORS]
thermostat_entities = []
for actuator in actuators:
if actuator.type() == ActuatorType.TEMPERATURE:
# Search for a matching sensor (by name)
actuator_name = actuator.name()
matching_sensor = None
for sensor in sensors:
if actuator_name in sensor.name():
matching_sensor = sensor
break
thermostat_entities.append(
XS1ThermostatEntity(actuator, matching_sensor))
async_add_entities(thermostat_entities)
class XS1ThermostatEntity(XS1DeviceEntity, ClimateDevice):
"""Representation of a XS1 thermostat."""
def __init__(self, device, sensor):
"""Initialize the actuator."""
super().__init__(device)
self.sensor = sensor
@property
def name(self):
"""Return the name of the device if any."""
return self.device.name()
@property
def supported_features(self):
"""Flag supported features."""
return SUPPORT_TARGET_TEMPERATURE
@property
def current_temperature(self):
"""Return the current temperature."""
if self.sensor is None:
return None
return self.sensor.value()
@property
def temperature_unit(self):
"""Return the unit of measurement used by the platform."""
return self.device.unit()
@property
def target_temperature(self):
"""Return the current target temperature."""
return self.device.new_value()
@property
def min_temp(self):
"""Return the minimum temperature."""
return MIN_TEMP
@property
def max_temp(self):
"""Return the maximum temperature."""
return MAX_TEMP
def set_temperature(self, **kwargs):
"""Set new target temperature."""
temp = kwargs.get(ATTR_TEMPERATURE)
self.device.set_value(temp)
if self.sensor is not None:
self.schedule_update_ha_state()
async def async_update(self):
"""Also update the sensor when available."""
await super().async_update()
if self.sensor is not None:
await self.hass.async_add_executor_job(
partial(self.sensor.update))

View File

@ -0,0 +1,57 @@
"""
Support for XS1 sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.xs1/
"""
import logging
from homeassistant.components.xs1 import (
ACTUATORS, DOMAIN as COMPONENT_DOMAIN, SENSORS, XS1DeviceEntity)
from homeassistant.helpers.entity import Entity
DEPENDENCIES = ['xs1']
_LOGGER = logging.getLogger(__name__)
async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
"""Set up the XS1 sensor platform."""
from xs1_api_client.api_constants import ActuatorType
sensors = hass.data[COMPONENT_DOMAIN][SENSORS]
actuators = hass.data[COMPONENT_DOMAIN][ACTUATORS]
sensor_entities = []
for sensor in sensors:
belongs_to_climate_actuator = False
for actuator in actuators:
if actuator.type() == ActuatorType.TEMPERATURE and \
actuator.name() in sensor.name():
belongs_to_climate_actuator = True
break
if not belongs_to_climate_actuator:
sensor_entities.append(XS1Sensor(sensor))
async_add_entities(sensor_entities)
class XS1Sensor(XS1DeviceEntity, Entity):
"""Representation of a Sensor."""
@property
def name(self):
"""Return the name of the sensor."""
return self.device.name()
@property
def state(self):
"""Return the state of the sensor."""
return self.device.value()
@property
def unit_of_measurement(self):
"""Return the unit of measurement."""
return self.device.unit()

View File

@ -0,0 +1,52 @@
"""
Support for XS1 switches.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/switch.xs1/
"""
import logging
from homeassistant.components.xs1 import (
ACTUATORS, DOMAIN as COMPONENT_DOMAIN, XS1DeviceEntity)
from homeassistant.helpers.entity import ToggleEntity
DEPENDENCIES = ['xs1']
_LOGGER = logging.getLogger(__name__)
async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
"""Set up the XS1 switch platform."""
from xs1_api_client.api_constants import ActuatorType
actuators = hass.data[COMPONENT_DOMAIN][ACTUATORS]
switch_entities = []
for actuator in actuators:
if (actuator.type() == ActuatorType.SWITCH) or \
(actuator.type() == ActuatorType.DIMMER):
switch_entities.append(XS1SwitchEntity(actuator))
async_add_entities(switch_entities)
class XS1SwitchEntity(XS1DeviceEntity, ToggleEntity):
"""Representation of a XS1 switch actuator."""
@property
def name(self):
"""Return the name of the device if any."""
return self.device.name()
@property
def is_on(self):
"""Return true if switch is on."""
return self.device.value() == 100
def turn_on(self, **kwargs):
"""Turn the device on."""
self.device.turn_on()
def turn_off(self, **kwargs):
"""Turn the device off."""
self.device.turn_off()

View File

@ -1754,6 +1754,9 @@ xknx==0.9.3
# homeassistant.components.sensor.zestimate
xmltodict==0.11.0
# homeassistant.components.xs1
xs1-api-client==2.3.5
# homeassistant.components.sensor.yweather
# homeassistant.components.weather.yweather
yahooweather==0.10