Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Decouple Konnected entity setup from discovery #16146

Merged
merged 2 commits into from
Aug 24, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 83 additions & 68 deletions homeassistant/components/konnected.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
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,
HTTP_BAD_REQUEST, HTTP_NOT_FOUND, 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_ENTITY_ID, ATTR_STATE)
Expand Down Expand Up @@ -74,7 +74,7 @@
vol.Required(CONF_ACCESS_TOKEN): cv.string,
vol.Optional(CONF_API_HOST): vol.Url(),
vol.Required(CONF_DEVICES): [{
vol.Required(CONF_ID): cv.string,
vol.Required(CONF_ID): cv.matches_regex("[0-9a-f]{12}"),
vol.Optional(CONF_BINARY_SENSORS): vol.All(
cv.ensure_list, [_BINARY_SENSOR_SCHEMA]),
vol.Optional(CONF_SWITCHES): vol.All(
Expand Down Expand Up @@ -107,12 +107,18 @@ async def async_setup(hass, config):

def 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)
discovered = DiscoveredDevice(hass, host, port)
if discovered.is_configured:
discovered.setup()
else:
_LOGGER.warning("Konnected device %s was discovered on the network"
" but not specified in configuration.yaml",
discovered.device_id)

device = KonnectedDevice(hass, host, port, cfg)
device.setup()
for device in cfg.get(CONF_DEVICES):
ConfiguredDevice(hass, device).save_data()

discovery.async_listen(
hass,
Expand All @@ -124,98 +130,51 @@ def device_discovered(service, info):
return True


class KonnectedDevice:
"""A representation of a single Konnected device."""
class ConfiguredDevice:
"""A representation of a configured Konnected device."""

def __init__(self, hass, host, port, config):
def __init__(self, hass, 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)

def 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()
self.sync_device_config()
discovery.load_platform(
self.hass, 'binary_sensor',
DOMAIN, {'device_id': self.device_id})
discovery.load_platform(
self.hass, 'switch', DOMAIN,
{'device_id': self.device_id})
self.config = config

@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)
return self.config.get(CONF_ID)

def save_data(self):
"""Save the device configuration to `hass.data`."""
sensors = {}
for entity in self.config().get(CONF_BINARY_SENSORS) or []:
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
ATTR_STATE: None
}
_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 []:
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

act = {
CONF_PIN: pin,
CONF_NAME: entity.get(
CONF_NAME, 'Konnected {} Actuator {}'.format(
self.device_id[6:], PIN_TO_ZONE[pin])),
ATTR_STATE: initial_state,
ATTR_STATE: None,
CONF_ACTIVATION: entity[CONF_ACTIVATION],
CONF_MOMENTARY: entity.get(CONF_MOMENTARY),
CONF_PAUSE: entity.get(CONF_PAUSE),
Expand All @@ -224,23 +183,67 @@ def save_data(self):
_LOGGER.debug('Set up actuator %s', act)

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)
_LOGGER.debug('Storing data in hass.data[%s][%s][%s]: %s',
DOMAIN, CONF_DEVICES, self.device_id, device_data)
self.hass.data[DOMAIN][CONF_DEVICES][self.device_id] = device_data

discovery.load_platform(
self.hass, 'binary_sensor',
DOMAIN, {'device_id': self.device_id})
discovery.load_platform(
self.hass, 'switch', DOMAIN,
{'device_id': self.device_id})


class DiscoveredDevice:
"""A representation of a discovered Konnected device."""

def __init__(self, hass, host, port):
"""Initialize the Konnected device."""
self.hass = hass
self.host = host
self.port = port

import konnected
self.client = konnected.Client(host, str(port))
self.status = self.client.get_status()

def setup(self):
"""Set up a newly discovered Konnected device."""
_LOGGER.info('Discovered Konnected device %s. Open http://%s:%s in a '
'web browser to view device status.',
self.device_id, self.host, self.port)
self.save_data()
self.update_initial_states()
self.sync_device_config()

def save_data(self):
"""Save the discovery information to `hass.data`."""
self.stored_configuration['client'] = self.client
self.stored_configuration['host'] = self.host
self.stored_configuration['port'] = self.port

@property
def device_id(self):
"""Device id is the MAC address as string with punctuation removed."""
return self.status['mac'].replace(':', '')

@property
def is_configured(self):
"""Return true if device_id is specified in the configuration."""
return bool(self.hass.data[DOMAIN][CONF_DEVICES].get(self.device_id))

@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]
return self.hass.data[DOMAIN][CONF_DEVICES].get(self.device_id)

def sensor_configuration(self):
"""Return the configuration map for syncing sensors."""
Expand All @@ -254,6 +257,18 @@ def actuator_configuration(self):
else 1)}
for data in self.stored_configuration[CONF_SWITCHES]]

def update_initial_states(self):
"""Update the initial state of each sensor from status poll."""
for sensor in self.status.get('sensors'):
entity_id = self.stored_configuration[CONF_BINARY_SENSORS]. \
get(sensor.get(CONF_PIN), {}). \
get(ATTR_ENTITY_ID)

async_dispatcher_send(
self.hass,
SIGNAL_SENSOR_UPDATE.format(entity_id),
bool(sensor.get(ATTR_STATE)))

def sync_device_config(self):
"""Sync the new pin configuration to the Konnected device."""
desired_sensor_configuration = self.sensor_configuration()
Expand Down Expand Up @@ -285,7 +300,7 @@ def sync_device_config(self):
if (desired_sensor_configuration != current_sensor_configuration) or \
(current_actuator_config != desired_actuator_config) or \
(current_api_endpoint != desired_api_endpoint):
_LOGGER.debug('pushing settings to device %s', self.device_id)
_LOGGER.info('pushing settings to device %s', self.device_id)
self.client.put_settings(
desired_sensor_configuration,
desired_actuator_config,
Expand Down Expand Up @@ -340,7 +355,7 @@ async def put(self, request: Request, device_id,
entity_id = pin_data.get(ATTR_ENTITY_ID)
if entity_id is None:
return self.json_message('uninitialized sensor/actuator',
status_code=HTTP_INTERNAL_SERVER_ERROR)
status_code=HTTP_NOT_FOUND)

async_dispatcher_send(
hass, SIGNAL_SENSOR_UPDATE.format(entity_id), state)
Expand Down
17 changes: 11 additions & 6 deletions homeassistant/components/switch/konnected.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,16 @@ async def async_setup_platform(hass, config, async_add_devices,

data = hass.data[KONNECTED_DOMAIN]
device_id = discovery_info['device_id']
client = data[CONF_DEVICES][device_id]['client']
switches = [
KonnectedSwitch(device_id, pin_data.get(CONF_PIN), pin_data, client)
KonnectedSwitch(device_id, pin_data.get(CONF_PIN), pin_data)
for pin_data in data[CONF_DEVICES][device_id][CONF_SWITCHES]]
async_add_devices(switches)


class KonnectedSwitch(ToggleEntity):
"""Representation of a Konnected switch."""

def __init__(self, device_id, pin_num, data, client):
def __init__(self, device_id, pin_num, data):
"""Initialize the switch."""
self._data = data
self._device_id = device_id
Expand All @@ -50,7 +49,6 @@ def __init__(self, device_id, pin_num, data, client):
self._name = self._data.get(
'name', 'Konnected {} Actuator {}'.format(
device_id, PIN_TO_ZONE[pin_num]))
self._client = client
_LOGGER.debug('Created new switch: %s', self._name)

@property
Expand All @@ -63,9 +61,16 @@ def is_on(self):
"""Return the status of the sensor."""
return self._state

@property
def client(self):
"""Return the Konnected HTTP client."""
return \
self.hass.data[KONNECTED_DOMAIN][CONF_DEVICES][self._device_id].\
get('client')

def turn_on(self, **kwargs):
"""Send a command to turn on the switch."""
resp = self._client.put_device(
resp = self.client.put_device(
self._pin_num,
int(self._activation == STATE_HIGH),
self._momentary,
Expand All @@ -82,7 +87,7 @@ def turn_on(self, **kwargs):

def turn_off(self, **kwargs):
"""Send a command to turn off the switch."""
resp = self._client.put_device(
resp = self.client.put_device(
self._pin_num, int(self._activation == STATE_LOW))

if resp.get(ATTR_STATE) is not None:
Expand Down