Add basic Supla cover support (#22133)
* Added basic Supla (https://www.supla.org) support (covers) * PySupla upgrade, minor spelling corrections and .coveragerc update * Linter errors cleanup * More linter cleanups. * Documentation link removal and import sorting * Docstring formatting * PR suggestions * Styling and linting * PySupla version update * Removal of ALLOW_EXTRA in SERVER_CONFIG * Return False on failed connection validation, function order cleanup * Component manifest * Missing return None and different way of setting unique_id * CODEOWNERS update * CircleCI nudge
This commit is contained in:
parent
8a4dd093f8
commit
7251e29e60
6 changed files with 236 additions and 0 deletions
|
@ -691,6 +691,7 @@ omit =
|
|||
homeassistant/components/zigbee/*
|
||||
homeassistant/components/ziggo_mediabox_xl/media_player.py
|
||||
homeassistant/components/zoneminder/*
|
||||
homeassistant/components/supla/*
|
||||
homeassistant/components/zwave/util.py
|
||||
|
||||
[report]
|
||||
|
|
|
@ -198,6 +198,7 @@ homeassistant/components/sql/* @dgomes
|
|||
homeassistant/components/statistics/* @fabaff
|
||||
homeassistant/components/stiebel_eltron/* @fucm
|
||||
homeassistant/components/sun/* @home-assistant/core
|
||||
homeassistant/components/supla/* @mwegrzynek
|
||||
homeassistant/components/swiss_hydrological_data/* @fabaff
|
||||
homeassistant/components/swiss_public_transport/* @fabaff
|
||||
homeassistant/components/switchbot/* @danielhiversen
|
||||
|
|
162
homeassistant/components/supla/__init__.py
Normal file
162
homeassistant/components/supla/__init__.py
Normal file
|
@ -0,0 +1,162 @@
|
|||
"""Support for Supla devices."""
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import CONF_ACCESS_TOKEN
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.discovery import load_platform
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
REQUIREMENTS = ['pysupla==0.0.3']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
DOMAIN = 'supla'
|
||||
|
||||
CONF_SERVER = 'server'
|
||||
CONF_SERVERS = 'servers'
|
||||
|
||||
SUPLA_FUNCTION_HA_CMP_MAP = {
|
||||
'CONTROLLINGTHEROLLERSHUTTER': 'cover'
|
||||
}
|
||||
SUPLA_CHANNELS = 'supla_channels'
|
||||
SUPLA_SERVERS = 'supla_servers'
|
||||
|
||||
SERVER_CONFIG = vol.Schema({
|
||||
vol.Required(CONF_SERVER): cv.string,
|
||||
vol.Required(CONF_ACCESS_TOKEN): cv.string
|
||||
})
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
DOMAIN: vol.Schema({
|
||||
vol.Required(CONF_SERVERS):
|
||||
vol.All(cv.ensure_list, [SERVER_CONFIG])
|
||||
})
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
|
||||
def setup(hass, base_config):
|
||||
"""Set up the Supla component."""
|
||||
from pysupla import SuplaAPI
|
||||
|
||||
server_confs = base_config[DOMAIN][CONF_SERVERS]
|
||||
|
||||
hass.data[SUPLA_SERVERS] = {}
|
||||
hass.data[SUPLA_CHANNELS] = {}
|
||||
|
||||
for server_conf in server_confs:
|
||||
|
||||
server_address = server_conf[CONF_SERVER]
|
||||
|
||||
server = SuplaAPI(
|
||||
server_address,
|
||||
server_conf[CONF_ACCESS_TOKEN]
|
||||
)
|
||||
|
||||
# Test connection
|
||||
try:
|
||||
srv_info = server.get_server_info()
|
||||
if srv_info.get('authenticated'):
|
||||
hass.data[SUPLA_SERVERS][server_conf[CONF_SERVER]] = server
|
||||
else:
|
||||
_LOGGER.error(
|
||||
'Server: %s not configured. API call returned: %s',
|
||||
server_address,
|
||||
srv_info
|
||||
)
|
||||
return False
|
||||
except IOError:
|
||||
_LOGGER.exception(
|
||||
'Server: %s not configured. Error on Supla API access: ',
|
||||
server_address
|
||||
)
|
||||
return False
|
||||
|
||||
discover_devices(hass, base_config)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def discover_devices(hass, hass_config):
|
||||
"""
|
||||
Run periodically to discover new devices.
|
||||
|
||||
Currently it's only run at startup.
|
||||
"""
|
||||
component_configs = {}
|
||||
|
||||
for server_name, server in hass.data[SUPLA_SERVERS].items():
|
||||
|
||||
for channel in server.get_channels(include=['iodevice']):
|
||||
channel_function = channel['function']['name']
|
||||
component_name = SUPLA_FUNCTION_HA_CMP_MAP.get(channel_function)
|
||||
|
||||
if component_name is None:
|
||||
_LOGGER.warning(
|
||||
'Unsupported function: %s, channel id: %s',
|
||||
channel_function, channel['id']
|
||||
)
|
||||
continue
|
||||
|
||||
channel['server_name'] = server_name
|
||||
component_configs.setdefault(component_name, []).append(channel)
|
||||
|
||||
# Load discovered devices
|
||||
for component_name, channel in component_configs.items():
|
||||
load_platform(
|
||||
hass,
|
||||
component_name,
|
||||
'supla',
|
||||
channel,
|
||||
hass_config
|
||||
)
|
||||
|
||||
|
||||
class SuplaChannel(Entity):
|
||||
"""Base class of a Supla Channel (an equivalent of HA's Entity)."""
|
||||
|
||||
def __init__(self, channel_data):
|
||||
"""Channel data -- raw channel information from PySupla."""
|
||||
self.server_name = channel_data['server_name']
|
||||
self.channel_data = channel_data
|
||||
|
||||
@property
|
||||
def server(self):
|
||||
"""Return PySupla's server component associated with entity."""
|
||||
return self.hass.data[SUPLA_SERVERS][self.server_name]
|
||||
|
||||
@property
|
||||
def unique_id(self) -> str:
|
||||
"""Return a unique ID."""
|
||||
return 'supla-{}-{}'.format(
|
||||
self.channel_data['iodevice']['gUIDString'].lower(),
|
||||
self.channel_data['channelNumber']
|
||||
)
|
||||
|
||||
@property
|
||||
def name(self) -> Optional[str]:
|
||||
"""Return the name of the device."""
|
||||
return self.channel_data['caption']
|
||||
|
||||
def action(self, action, **add_pars):
|
||||
"""
|
||||
Run server action.
|
||||
|
||||
Actions are currently hardcoded in components.
|
||||
Supla's API enables autodiscovery
|
||||
"""
|
||||
_LOGGER.debug(
|
||||
'Executing action %s on channel %d, params: %s',
|
||||
action,
|
||||
self.channel_data['id'],
|
||||
add_pars
|
||||
)
|
||||
self.server.execute_action(self.channel_data['id'], action, **add_pars)
|
||||
|
||||
def update(self):
|
||||
"""Call to update state."""
|
||||
self.channel_data = self.server.get_channel(
|
||||
self.channel_data['id'],
|
||||
include=['connected', 'state']
|
||||
)
|
57
homeassistant/components/supla/cover.py
Normal file
57
homeassistant/components/supla/cover.py
Normal file
|
@ -0,0 +1,57 @@
|
|||
"""Support for Supla cover - curtains, rollershutters etc."""
|
||||
import logging
|
||||
from pprint import pformat
|
||||
|
||||
from homeassistant.components.cover import ATTR_POSITION, CoverDevice
|
||||
from homeassistant.components.supla import SuplaChannel
|
||||
|
||||
DEPENDENCIES = ['supla']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up the Supla covers."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
|
||||
_LOGGER.debug('Discovery: %s', pformat(discovery_info))
|
||||
|
||||
add_entities([
|
||||
SuplaCover(device) for device in discovery_info
|
||||
])
|
||||
|
||||
|
||||
class SuplaCover(SuplaChannel, CoverDevice):
|
||||
"""Representation of a Supla Cover."""
|
||||
|
||||
@property
|
||||
def current_cover_position(self):
|
||||
"""Return current position of cover. 0 is closed, 100 is open."""
|
||||
state = self.channel_data.get('state')
|
||||
if state:
|
||||
return 100 - state['shut']
|
||||
return None
|
||||
|
||||
def set_cover_position(self, **kwargs):
|
||||
"""Move the cover to a specific position."""
|
||||
self.action('REVEAL', percentage=kwargs.get(ATTR_POSITION))
|
||||
|
||||
@property
|
||||
def is_closed(self):
|
||||
"""Return if the cover is closed."""
|
||||
if self.current_cover_position is None:
|
||||
return None
|
||||
return self.current_cover_position == 0
|
||||
|
||||
def open_cover(self, **kwargs):
|
||||
"""Open the cover."""
|
||||
self.action('REVEAL')
|
||||
|
||||
def close_cover(self, **kwargs):
|
||||
"""Close the cover."""
|
||||
self.action('SHUT')
|
||||
|
||||
def stop_cover(self, **kwargs):
|
||||
"""Stop the cover."""
|
||||
self.action('STOP')
|
12
homeassistant/components/supla/manifest.json
Normal file
12
homeassistant/components/supla/manifest.json
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"domain": "supla",
|
||||
"name": "Supla",
|
||||
"documentation": "https://www.home-assistant.io/components/supla",
|
||||
"requirements": [
|
||||
"pysupla==0.0.3"
|
||||
],
|
||||
"dependencies": [],
|
||||
"codeowners": [
|
||||
"@mwegrzynek"
|
||||
]
|
||||
}
|
|
@ -1287,6 +1287,9 @@ pystiebeleltron==0.0.1.dev2
|
|||
# homeassistant.components.stride
|
||||
pystride==0.1.7
|
||||
|
||||
# homeassistant.components.supla
|
||||
pysupla==0.0.3
|
||||
|
||||
# homeassistant.components.syncthru
|
||||
pysyncthru==0.3.1
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue