mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 13:17:32 +00:00
Add Konnected component with support for discovery, binary sensor and switch (#13670)
* Add Konnected component with support for discovery, binary sensor, and switch Co-authored-by: Eitan Mosenkis <eitan@mosenkis.net> * Use more built-in constants from const.py * Fix switch actuation with low-level trigger * Quiet logging; Improve schema validation. * Execute sync request outside of event loop * Whitespace cleanup * Cleanup config validation; async device setup * Update API endpoint for Konnected 2.2.0 changes * Update async coroutines via @OttoWinter * Make backwards compatible with Konnected < 2.2.0 * Add constants suggested by @syssi * Add to CODEOWNERS * Remove TODO comment
This commit is contained in:
parent
612a37b2dd
commit
de50d5d9c1
@ -153,6 +153,9 @@ omit =
|
|||||||
homeassistant/components/knx.py
|
homeassistant/components/knx.py
|
||||||
homeassistant/components/*/knx.py
|
homeassistant/components/*/knx.py
|
||||||
|
|
||||||
|
homeassistant/components/konnected.py
|
||||||
|
homeassistant/components/*/konnected.py
|
||||||
|
|
||||||
homeassistant/components/lametric.py
|
homeassistant/components/lametric.py
|
||||||
homeassistant/components/*/lametric.py
|
homeassistant/components/*/lametric.py
|
||||||
|
|
||||||
|
@ -94,6 +94,8 @@ homeassistant/components/*/hive.py @Rendili @KJonline
|
|||||||
homeassistant/components/homekit/* @cdce8p
|
homeassistant/components/homekit/* @cdce8p
|
||||||
homeassistant/components/knx.py @Julius2342
|
homeassistant/components/knx.py @Julius2342
|
||||||
homeassistant/components/*/knx.py @Julius2342
|
homeassistant/components/*/knx.py @Julius2342
|
||||||
|
homeassistant/components/konnected.py @heythisisnate
|
||||||
|
homeassistant/components/*/konnected.py @heythisisnate
|
||||||
homeassistant/components/matrix.py @tinloaf
|
homeassistant/components/matrix.py @tinloaf
|
||||||
homeassistant/components/*/matrix.py @tinloaf
|
homeassistant/components/*/matrix.py @tinloaf
|
||||||
homeassistant/components/qwikswitch.py @kellerza
|
homeassistant/components/qwikswitch.py @kellerza
|
||||||
|
74
homeassistant/components/binary_sensor/konnected.py
Normal file
74
homeassistant/components/binary_sensor/konnected.py
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
"""
|
||||||
|
Support for wired binary sensors attached to a Konnected device.
|
||||||
|
|
||||||
|
For more details about this platform, please refer to the documentation at
|
||||||
|
https://home-assistant.io/components/binary_sensor.konnected/
|
||||||
|
"""
|
||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||||
|
from homeassistant.components.konnected import (DOMAIN, PIN_TO_ZONE)
|
||||||
|
from homeassistant.const import (
|
||||||
|
CONF_DEVICES, CONF_TYPE, CONF_NAME, CONF_BINARY_SENSORS, ATTR_STATE)
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
DEPENDENCIES = ['konnected']
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_platform(hass, config, async_add_devices,
|
||||||
|
discovery_info=None):
|
||||||
|
"""Set up binary sensors attached to a Konnected device."""
|
||||||
|
if discovery_info is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
data = hass.data[DOMAIN]
|
||||||
|
device_id = discovery_info['device_id']
|
||||||
|
sensors = [KonnectedBinarySensor(device_id, pin_num, pin_data)
|
||||||
|
for pin_num, pin_data in
|
||||||
|
data[CONF_DEVICES][device_id][CONF_BINARY_SENSORS].items()]
|
||||||
|
async_add_devices(sensors)
|
||||||
|
|
||||||
|
|
||||||
|
class KonnectedBinarySensor(BinarySensorDevice):
|
||||||
|
"""Representation of a Konnected binary sensor."""
|
||||||
|
|
||||||
|
def __init__(self, device_id, pin_num, data):
|
||||||
|
"""Initialize the binary sensor."""
|
||||||
|
self._data = data
|
||||||
|
self._device_id = device_id
|
||||||
|
self._pin_num = pin_num
|
||||||
|
self._state = self._data.get(ATTR_STATE)
|
||||||
|
self._device_class = self._data.get(CONF_TYPE)
|
||||||
|
self._name = self._data.get(CONF_NAME, 'Konnected {} Zone {}'.format(
|
||||||
|
device_id, PIN_TO_ZONE[pin_num]))
|
||||||
|
self._data['entity'] = self
|
||||||
|
_LOGGER.debug('Created new Konnected sensor: %s', self._name)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
"""Return the name of the sensor."""
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_on(self):
|
||||||
|
"""Return the state of the sensor."""
|
||||||
|
return self._state
|
||||||
|
|
||||||
|
@property
|
||||||
|
def should_poll(self):
|
||||||
|
"""No polling needed."""
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_class(self):
|
||||||
|
"""Return the device class."""
|
||||||
|
return self._device_class
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def async_set_state(self, state):
|
||||||
|
"""Update the sensor's state."""
|
||||||
|
self._state = state
|
||||||
|
self._data[ATTR_STATE] = state
|
||||||
|
self.async_schedule_update_ha_state()
|
@ -37,6 +37,7 @@ SERVICE_WINK = 'wink'
|
|||||||
SERVICE_XIAOMI_GW = 'xiaomi_gw'
|
SERVICE_XIAOMI_GW = 'xiaomi_gw'
|
||||||
SERVICE_TELLDUSLIVE = 'tellstick'
|
SERVICE_TELLDUSLIVE = 'tellstick'
|
||||||
SERVICE_HUE = 'philips_hue'
|
SERVICE_HUE = 'philips_hue'
|
||||||
|
SERVICE_KONNECTED = 'konnected'
|
||||||
SERVICE_DECONZ = 'deconz'
|
SERVICE_DECONZ = 'deconz'
|
||||||
SERVICE_DAIKIN = 'daikin'
|
SERVICE_DAIKIN = 'daikin'
|
||||||
SERVICE_SABNZBD = 'sabnzbd'
|
SERVICE_SABNZBD = 'sabnzbd'
|
||||||
@ -62,6 +63,7 @@ SERVICE_HANDLERS = {
|
|||||||
SERVICE_DAIKIN: ('daikin', None),
|
SERVICE_DAIKIN: ('daikin', None),
|
||||||
SERVICE_SABNZBD: ('sabnzbd', None),
|
SERVICE_SABNZBD: ('sabnzbd', None),
|
||||||
SERVICE_SAMSUNG_PRINTER: ('sensor', 'syncthru'),
|
SERVICE_SAMSUNG_PRINTER: ('sensor', 'syncthru'),
|
||||||
|
SERVICE_KONNECTED: ('konnected', None),
|
||||||
'google_cast': ('media_player', 'cast'),
|
'google_cast': ('media_player', 'cast'),
|
||||||
'panasonic_viera': ('media_player', 'panasonic_viera'),
|
'panasonic_viera': ('media_player', 'panasonic_viera'),
|
||||||
'plex_mediaserver': ('media_player', 'plex'),
|
'plex_mediaserver': ('media_player', 'plex'),
|
||||||
@ -191,6 +193,7 @@ def _discover(netdisco):
|
|||||||
for disc in netdisco.discover():
|
for disc in netdisco.discover():
|
||||||
for service in netdisco.get_info(disc):
|
for service in netdisco.get_info(disc):
|
||||||
results.append((disc, service))
|
results.append((disc, service))
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
netdisco.stop()
|
netdisco.stop()
|
||||||
|
|
||||||
|
315
homeassistant/components/konnected.py
Normal file
315
homeassistant/components/konnected.py
Normal file
@ -0,0 +1,315 @@
|
|||||||
|
"""
|
||||||
|
Support for Konnected devices.
|
||||||
|
|
||||||
|
For more details about this component, please refer to the documentation at
|
||||||
|
https://home-assistant.io/components/konnected/
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
import hmac
|
||||||
|
import json
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from aiohttp.hdrs import AUTHORIZATION
|
||||||
|
from aiohttp.web import Request, Response # NOQA
|
||||||
|
|
||||||
|
from homeassistant.components.binary_sensor import DEVICE_CLASSES_SCHEMA
|
||||||
|
from homeassistant.components.discovery import SERVICE_KONNECTED
|
||||||
|
from homeassistant.components.http import HomeAssistantView
|
||||||
|
from homeassistant.const import (
|
||||||
|
HTTP_BAD_REQUEST, HTTP_INTERNAL_SERVER_ERROR, HTTP_UNAUTHORIZED,
|
||||||
|
CONF_DEVICES, CONF_BINARY_SENSORS, CONF_SWITCHES, CONF_HOST, CONF_PORT,
|
||||||
|
CONF_ID, CONF_NAME, CONF_TYPE, CONF_PIN, CONF_ZONE, CONF_ACCESS_TOKEN,
|
||||||
|
ATTR_STATE)
|
||||||
|
from homeassistant.helpers import discovery
|
||||||
|
from homeassistant.helpers import config_validation as cv
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
REQUIREMENTS = ['konnected==0.1.2']
|
||||||
|
|
||||||
|
DOMAIN = 'konnected'
|
||||||
|
|
||||||
|
CONF_ACTIVATION = 'activation'
|
||||||
|
STATE_LOW = 'low'
|
||||||
|
STATE_HIGH = 'high'
|
||||||
|
|
||||||
|
PIN_TO_ZONE = {1: 1, 2: 2, 5: 3, 6: 4, 7: 5, 8: 'out', 9: 6}
|
||||||
|
ZONE_TO_PIN = {zone: pin for pin, zone in PIN_TO_ZONE.items()}
|
||||||
|
|
||||||
|
_BINARY_SENSOR_SCHEMA = vol.All(
|
||||||
|
vol.Schema({
|
||||||
|
vol.Exclusive(CONF_PIN, 's_pin'): vol.Any(*PIN_TO_ZONE),
|
||||||
|
vol.Exclusive(CONF_ZONE, 's_pin'): vol.Any(*ZONE_TO_PIN),
|
||||||
|
vol.Required(CONF_TYPE): DEVICE_CLASSES_SCHEMA,
|
||||||
|
vol.Optional(CONF_NAME): cv.string,
|
||||||
|
}), cv.has_at_least_one_key(CONF_PIN, CONF_ZONE)
|
||||||
|
)
|
||||||
|
|
||||||
|
_SWITCH_SCHEMA = vol.All(
|
||||||
|
vol.Schema({
|
||||||
|
vol.Exclusive(CONF_PIN, 'a_pin'): vol.Any(*PIN_TO_ZONE),
|
||||||
|
vol.Exclusive(CONF_ZONE, 'a_pin'): vol.Any(*ZONE_TO_PIN),
|
||||||
|
vol.Optional(CONF_NAME): cv.string,
|
||||||
|
vol.Optional(CONF_ACTIVATION, default=STATE_HIGH):
|
||||||
|
vol.All(vol.Lower, vol.Any(STATE_HIGH, STATE_LOW))
|
||||||
|
}), cv.has_at_least_one_key(CONF_PIN, CONF_ZONE)
|
||||||
|
)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = vol.Schema(
|
||||||
|
{
|
||||||
|
DOMAIN: vol.Schema({
|
||||||
|
vol.Required(CONF_ACCESS_TOKEN): cv.string,
|
||||||
|
vol.Required(CONF_DEVICES): [{
|
||||||
|
vol.Required(CONF_ID): cv.string,
|
||||||
|
vol.Optional(CONF_BINARY_SENSORS): vol.All(
|
||||||
|
cv.ensure_list, [_BINARY_SENSOR_SCHEMA]),
|
||||||
|
vol.Optional(CONF_SWITCHES): vol.All(
|
||||||
|
cv.ensure_list, [_SWITCH_SCHEMA]),
|
||||||
|
}],
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
extra=vol.ALLOW_EXTRA,
|
||||||
|
)
|
||||||
|
|
||||||
|
DEPENDENCIES = ['http', 'discovery']
|
||||||
|
|
||||||
|
ENDPOINT_ROOT = '/api/konnected'
|
||||||
|
UPDATE_ENDPOINT = (ENDPOINT_ROOT + r'/device/{device_id:[a-zA-Z0-9]+}')
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup(hass, config):
|
||||||
|
"""Set up the Konnected platform."""
|
||||||
|
cfg = config.get(DOMAIN)
|
||||||
|
if cfg is None:
|
||||||
|
cfg = {}
|
||||||
|
|
||||||
|
access_token = cfg.get(CONF_ACCESS_TOKEN)
|
||||||
|
if DOMAIN not in hass.data:
|
||||||
|
hass.data[DOMAIN] = {CONF_ACCESS_TOKEN: access_token}
|
||||||
|
|
||||||
|
async def async_device_discovered(service, info):
|
||||||
|
"""Call when a Konnected device has been discovered."""
|
||||||
|
_LOGGER.debug("Discovered a new Konnected device: %s", info)
|
||||||
|
host = info.get(CONF_HOST)
|
||||||
|
port = info.get(CONF_PORT)
|
||||||
|
|
||||||
|
device = KonnectedDevice(hass, host, port, cfg)
|
||||||
|
await device.async_setup()
|
||||||
|
|
||||||
|
discovery.async_listen(
|
||||||
|
hass,
|
||||||
|
SERVICE_KONNECTED,
|
||||||
|
async_device_discovered)
|
||||||
|
|
||||||
|
hass.http.register_view(KonnectedView(access_token))
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class KonnectedDevice(object):
|
||||||
|
"""A representation of a single Konnected device."""
|
||||||
|
|
||||||
|
def __init__(self, hass, host, port, config):
|
||||||
|
"""Initialize the Konnected device."""
|
||||||
|
self.hass = hass
|
||||||
|
self.host = host
|
||||||
|
self.port = port
|
||||||
|
self.user_config = config
|
||||||
|
|
||||||
|
import konnected
|
||||||
|
self.client = konnected.Client(host, str(port))
|
||||||
|
self.status = self.client.get_status()
|
||||||
|
_LOGGER.info('Initialized Konnected device %s', self.device_id)
|
||||||
|
|
||||||
|
async def async_setup(self):
|
||||||
|
"""Set up a newly discovered Konnected device."""
|
||||||
|
user_config = self.config()
|
||||||
|
if user_config:
|
||||||
|
_LOGGER.debug('Configuring Konnected device %s', self.device_id)
|
||||||
|
self.save_data()
|
||||||
|
await self.async_sync_device_config()
|
||||||
|
await discovery.async_load_platform(
|
||||||
|
self.hass, 'binary_sensor',
|
||||||
|
DOMAIN, {'device_id': self.device_id})
|
||||||
|
await discovery.async_load_platform(
|
||||||
|
self.hass, 'switch', DOMAIN,
|
||||||
|
{'device_id': self.device_id})
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_id(self):
|
||||||
|
"""Device id is the MAC address as string with punctuation removed."""
|
||||||
|
return self.status['mac'].replace(':', '')
|
||||||
|
|
||||||
|
def config(self):
|
||||||
|
"""Return an object representing the user defined configuration."""
|
||||||
|
device_id = self.device_id
|
||||||
|
valid_keys = [device_id, device_id.upper(),
|
||||||
|
device_id[6:], device_id.upper()[6:]]
|
||||||
|
configured_devices = self.user_config[CONF_DEVICES]
|
||||||
|
return next((device for device in
|
||||||
|
configured_devices if device[CONF_ID] in valid_keys),
|
||||||
|
None)
|
||||||
|
|
||||||
|
def save_data(self):
|
||||||
|
"""Save the device configuration to `hass.data`."""
|
||||||
|
sensors = {}
|
||||||
|
for entity in self.config().get(CONF_BINARY_SENSORS) or []:
|
||||||
|
if CONF_ZONE in entity:
|
||||||
|
pin = ZONE_TO_PIN[entity[CONF_ZONE]]
|
||||||
|
else:
|
||||||
|
pin = entity[CONF_PIN]
|
||||||
|
|
||||||
|
sensor_status = next((sensor for sensor in
|
||||||
|
self.status.get('sensors') if
|
||||||
|
sensor.get(CONF_PIN) == pin), {})
|
||||||
|
if sensor_status.get(ATTR_STATE):
|
||||||
|
initial_state = bool(int(sensor_status.get(ATTR_STATE)))
|
||||||
|
else:
|
||||||
|
initial_state = None
|
||||||
|
|
||||||
|
sensors[pin] = {
|
||||||
|
CONF_TYPE: entity[CONF_TYPE],
|
||||||
|
CONF_NAME: entity.get(CONF_NAME, 'Konnected {} Zone {}'.format(
|
||||||
|
self.device_id[6:], PIN_TO_ZONE[pin])),
|
||||||
|
ATTR_STATE: initial_state
|
||||||
|
}
|
||||||
|
_LOGGER.debug('Set up sensor %s (initial state: %s)',
|
||||||
|
sensors[pin].get('name'),
|
||||||
|
sensors[pin].get(ATTR_STATE))
|
||||||
|
|
||||||
|
actuators = {}
|
||||||
|
for entity in self.config().get(CONF_SWITCHES) or []:
|
||||||
|
if 'zone' in entity:
|
||||||
|
pin = ZONE_TO_PIN[entity['zone']]
|
||||||
|
else:
|
||||||
|
pin = entity['pin']
|
||||||
|
|
||||||
|
actuator_status = next((actuator for actuator in
|
||||||
|
self.status.get('actuators') if
|
||||||
|
actuator.get('pin') == pin), {})
|
||||||
|
if actuator_status.get(ATTR_STATE):
|
||||||
|
initial_state = bool(int(actuator_status.get(ATTR_STATE)))
|
||||||
|
else:
|
||||||
|
initial_state = None
|
||||||
|
|
||||||
|
actuators[pin] = {
|
||||||
|
CONF_NAME: entity.get(
|
||||||
|
CONF_NAME, 'Konnected {} Actuator {}'.format(
|
||||||
|
self.device_id[6:], PIN_TO_ZONE[pin])),
|
||||||
|
ATTR_STATE: initial_state,
|
||||||
|
CONF_ACTIVATION: entity[CONF_ACTIVATION],
|
||||||
|
}
|
||||||
|
_LOGGER.debug('Set up actuator %s (initial state: %s)',
|
||||||
|
actuators[pin].get(CONF_NAME),
|
||||||
|
actuators[pin].get(ATTR_STATE))
|
||||||
|
|
||||||
|
device_data = {
|
||||||
|
'client': self.client,
|
||||||
|
CONF_BINARY_SENSORS: sensors,
|
||||||
|
CONF_SWITCHES: actuators,
|
||||||
|
CONF_HOST: self.host,
|
||||||
|
CONF_PORT: self.port,
|
||||||
|
}
|
||||||
|
|
||||||
|
if CONF_DEVICES not in self.hass.data[DOMAIN]:
|
||||||
|
self.hass.data[DOMAIN][CONF_DEVICES] = {}
|
||||||
|
|
||||||
|
_LOGGER.debug('Storing data in hass.data[konnected]: %s', device_data)
|
||||||
|
self.hass.data[DOMAIN][CONF_DEVICES][self.device_id] = device_data
|
||||||
|
|
||||||
|
@property
|
||||||
|
def stored_configuration(self):
|
||||||
|
"""Return the configuration stored in `hass.data` for this device."""
|
||||||
|
return self.hass.data[DOMAIN][CONF_DEVICES][self.device_id]
|
||||||
|
|
||||||
|
def sensor_configuration(self):
|
||||||
|
"""Return the configuration map for syncing sensors."""
|
||||||
|
return [{'pin': p} for p in
|
||||||
|
self.stored_configuration[CONF_BINARY_SENSORS].keys()]
|
||||||
|
|
||||||
|
def actuator_configuration(self):
|
||||||
|
"""Return the configuration map for syncing actuators."""
|
||||||
|
return [{'pin': p,
|
||||||
|
'trigger': (0 if data.get(CONF_ACTIVATION) in [0, STATE_LOW]
|
||||||
|
else 1)}
|
||||||
|
for p, data in
|
||||||
|
self.stored_configuration[CONF_SWITCHES].items()]
|
||||||
|
|
||||||
|
async def async_sync_device_config(self):
|
||||||
|
"""Sync the new pin configuration to the Konnected device."""
|
||||||
|
desired_sensor_configuration = self.sensor_configuration()
|
||||||
|
current_sensor_configuration = [
|
||||||
|
{'pin': s[CONF_PIN]} for s in self.status.get('sensors')]
|
||||||
|
_LOGGER.debug('%s: desired sensor config: %s', self.device_id,
|
||||||
|
desired_sensor_configuration)
|
||||||
|
_LOGGER.debug('%s: current sensor config: %s', self.device_id,
|
||||||
|
current_sensor_configuration)
|
||||||
|
|
||||||
|
desired_actuator_config = self.actuator_configuration()
|
||||||
|
current_actuator_config = self.status.get('actuators')
|
||||||
|
_LOGGER.debug('%s: desired actuator config: %s', self.device_id,
|
||||||
|
desired_actuator_config)
|
||||||
|
_LOGGER.debug('%s: current actuator config: %s', self.device_id,
|
||||||
|
current_actuator_config)
|
||||||
|
|
||||||
|
if (desired_sensor_configuration != current_sensor_configuration) or \
|
||||||
|
(current_actuator_config != desired_actuator_config):
|
||||||
|
_LOGGER.debug('pushing settings to device %s', self.device_id)
|
||||||
|
self.client.put_settings(
|
||||||
|
desired_sensor_configuration,
|
||||||
|
desired_actuator_config,
|
||||||
|
self.hass.data[DOMAIN].get(CONF_ACCESS_TOKEN),
|
||||||
|
self.hass.config.api.base_url + ENDPOINT_ROOT
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class KonnectedView(HomeAssistantView):
|
||||||
|
"""View creates an endpoint to receive push updates from the device."""
|
||||||
|
|
||||||
|
url = UPDATE_ENDPOINT
|
||||||
|
extra_urls = [UPDATE_ENDPOINT + '/{pin_num}/{state}']
|
||||||
|
name = 'api:konnected'
|
||||||
|
requires_auth = False # Uses access token from configuration
|
||||||
|
|
||||||
|
def __init__(self, auth_token):
|
||||||
|
"""Initialize the view."""
|
||||||
|
self.auth_token = auth_token
|
||||||
|
|
||||||
|
async def put(self, request: Request, device_id,
|
||||||
|
pin_num=None, state=None) -> Response:
|
||||||
|
"""Receive a sensor update via PUT request and async set state."""
|
||||||
|
hass = request.app['hass']
|
||||||
|
data = hass.data[DOMAIN]
|
||||||
|
|
||||||
|
try: # Konnected 2.2.0 and above supports JSON payloads
|
||||||
|
payload = await request.json()
|
||||||
|
pin_num = payload['pin']
|
||||||
|
state = payload['state']
|
||||||
|
except json.decoder.JSONDecodeError:
|
||||||
|
_LOGGER.warning(("Your Konnected device software may be out of "
|
||||||
|
"date. Visit https://help.konnected.io for "
|
||||||
|
"updating instructions."))
|
||||||
|
|
||||||
|
auth = request.headers.get(AUTHORIZATION, None)
|
||||||
|
if not hmac.compare_digest('Bearer {}'.format(self.auth_token), auth):
|
||||||
|
return self.json_message(
|
||||||
|
"unauthorized", status_code=HTTP_UNAUTHORIZED)
|
||||||
|
pin_num = int(pin_num)
|
||||||
|
state = bool(int(state))
|
||||||
|
device = data[CONF_DEVICES].get(device_id)
|
||||||
|
if device is None:
|
||||||
|
return self.json_message('unregistered device',
|
||||||
|
status_code=HTTP_BAD_REQUEST)
|
||||||
|
pin_data = device[CONF_BINARY_SENSORS].get(pin_num) or \
|
||||||
|
device[CONF_SWITCHES].get(pin_num)
|
||||||
|
|
||||||
|
if pin_data is None:
|
||||||
|
return self.json_message('unregistered sensor/actuator',
|
||||||
|
status_code=HTTP_BAD_REQUEST)
|
||||||
|
entity = pin_data.get('entity')
|
||||||
|
if entity is None:
|
||||||
|
return self.json_message('uninitialized sensor/actuator',
|
||||||
|
status_code=HTTP_INTERNAL_SERVER_ERROR)
|
||||||
|
|
||||||
|
await entity.async_set_state(state)
|
||||||
|
return self.json_message('ok')
|
87
homeassistant/components/switch/konnected.py
Normal file
87
homeassistant/components/switch/konnected.py
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
"""
|
||||||
|
Support for wired switches attached to a Konnected device.
|
||||||
|
|
||||||
|
For more details about this platform, please refer to the documentation at
|
||||||
|
https://home-assistant.io/components/switch.konnected/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from homeassistant.components.konnected import (
|
||||||
|
DOMAIN, PIN_TO_ZONE, CONF_ACTIVATION, STATE_LOW, STATE_HIGH)
|
||||||
|
from homeassistant.helpers.entity import ToggleEntity
|
||||||
|
from homeassistant.const import (CONF_DEVICES, CONF_SWITCHES, ATTR_STATE)
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
DEPENDENCIES = ['konnected']
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_platform(hass, config, async_add_devices,
|
||||||
|
discovery_info=None):
|
||||||
|
"""Set switches attached to a Konnected device."""
|
||||||
|
if discovery_info is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
data = hass.data[DOMAIN]
|
||||||
|
device_id = discovery_info['device_id']
|
||||||
|
client = data[CONF_DEVICES][device_id]['client']
|
||||||
|
switches = [KonnectedSwitch(device_id, pin_num, pin_data, client)
|
||||||
|
for pin_num, pin_data in
|
||||||
|
data[CONF_DEVICES][device_id][CONF_SWITCHES].items()]
|
||||||
|
async_add_devices(switches)
|
||||||
|
|
||||||
|
|
||||||
|
class KonnectedSwitch(ToggleEntity):
|
||||||
|
"""Representation of a Konnected switch."""
|
||||||
|
|
||||||
|
def __init__(self, device_id, pin_num, data, client):
|
||||||
|
"""Initialize the switch."""
|
||||||
|
self._data = data
|
||||||
|
self._device_id = device_id
|
||||||
|
self._pin_num = pin_num
|
||||||
|
self._state = self._data.get(ATTR_STATE)
|
||||||
|
self._activation = self._data.get(CONF_ACTIVATION, STATE_HIGH)
|
||||||
|
self._name = self._data.get(
|
||||||
|
'name', 'Konnected {} Actuator {}'.format(
|
||||||
|
device_id, PIN_TO_ZONE[pin_num]))
|
||||||
|
self._data['entity'] = self
|
||||||
|
self._client = client
|
||||||
|
_LOGGER.debug('Created new switch: %s', self._name)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
"""Return the name of the switch."""
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_on(self):
|
||||||
|
"""Return the status of the sensor."""
|
||||||
|
return self._state
|
||||||
|
|
||||||
|
def turn_on(self, **kwargs):
|
||||||
|
"""Send a command to turn on the switch."""
|
||||||
|
self._client.put_device(self._pin_num,
|
||||||
|
int(self._activation == STATE_HIGH))
|
||||||
|
self._set_state(True)
|
||||||
|
|
||||||
|
def turn_off(self, **kwargs):
|
||||||
|
"""Send a command to turn off the switch."""
|
||||||
|
self._client.put_device(self._pin_num,
|
||||||
|
int(self._activation == STATE_LOW))
|
||||||
|
self._set_state(False)
|
||||||
|
|
||||||
|
def _set_state(self, state):
|
||||||
|
self._state = state
|
||||||
|
self._data[ATTR_STATE] = state
|
||||||
|
self.schedule_update_ha_state()
|
||||||
|
_LOGGER.debug('Setting status of %s actuator pin %s to %s',
|
||||||
|
self._device_id, self.name, state)
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def async_set_state(self, state):
|
||||||
|
"""Update the switch's state."""
|
||||||
|
self._state = state
|
||||||
|
self._data[ATTR_STATE] = state
|
||||||
|
self.async_schedule_update_ha_state()
|
@ -464,6 +464,9 @@ keyring==12.2.0
|
|||||||
# homeassistant.scripts.keyring
|
# homeassistant.scripts.keyring
|
||||||
keyrings.alt==3.1
|
keyrings.alt==3.1
|
||||||
|
|
||||||
|
# homeassistant.components.konnected
|
||||||
|
konnected==0.1.2
|
||||||
|
|
||||||
# homeassistant.components.eufy
|
# homeassistant.components.eufy
|
||||||
lakeside==0.6
|
lakeside==0.6
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user