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

Support Konnected Pro alarm panel, embrace async, leverage latest HA features/architecture #30894

Merged
merged 13 commits into from
Feb 11, 2020
Prev Previous commit
Next Next commit
migrate config flow to use options
  • Loading branch information
kit-klein committed Feb 6, 2020
commit fe4fcdbadc524be1b8b559a366ab72148869ad07
77 changes: 47 additions & 30 deletions homeassistant/components/konnected/.translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,51 @@
"already_in_progress": "Config flow for device is already in progress.",
"cannot_connect": "Unable to connect to the Konnected Panel",
"not_konn_panel": "Not a recognized Konnected.io device",
"unknown": "Unknown error occurred"
"unknown": "Unknown error occurred",
"wrong_device": "MAC address of device at host does not match config entry"
},
"error": {
"linking": "Unknown linking error occurred.",
"register_failed": "Failed to register, please try again"
"cannot_connect": "Unable to connect to a Konnected Panel at {host}:{port}"
},
"step": {
"io": {
"confirm": {
"description": "Model: {model}\nHost: {host}\nPort: {port}\n\nUse config options to configure the IO and panel behavior.",
"title": "Konnected Device Ready"
},
"user": {
"data": {
"host": "Konnected device IP address",
"port": "Konnected device port"
},
"description": "Please enter the host information for your Konnected Panel.",
"title": "Discover Konnected Device"
}
},
"title": "Konnected.io"
},
"options": {
"abort": {},
"error": {},
"step": {
"options_binary": {
"data": {
"inverse": "Invert the open/close state",
"name": "Name (optional)",
"type": "Binary Sensor Type"
},
"description": "Please select the options for the binary sensor attached to {zone}",
"title": "Configure Binary Sensor"
},
"options_digital": {
"data": {
"name": "Name (optional)",
"poll_interval": "Poll Interval (minutes) (optional)",
"type": "Sensor Type"
},
"description": "Please select the options for the digital sensor attached to {zone}",
"title": "Configure Digital Sensor"
},
"options_io": {
"data": {
"1": "Zone 1",
"2": "Zone 2",
Expand All @@ -26,7 +63,7 @@
"description": "Discovered a {model} at {host}. Select the base configuration of each I/O below - depending on the I/O it may allow for binary sensors (open/close contacts), digital sensors (dht and ds18b20), or switchable outputs. You'll be able to configure detailed options in the next steps.",
"title": "Configure I/O"
},
"io_ext": {
"options_io_ext": {
"data": {
"10": "Zone 10",
"11": "Zone 11",
Expand All @@ -40,23 +77,12 @@
"description": "Select the configuration of the remaining I/O below. You'll be able to configure detailed options in the next steps.",
"title": "Configure Extended I/O"
},
"options_binary": {
"options_misc": {
"data": {
"inverse": "Invert the open/close state",
"name": "Name (optional)",
"type": "Binary Sensor Type"
"blink": "Blink panel LED on when sending state change"
},
"description": "Please select the options for the binary sensor attached to {zone}",
"title": "Configure Binary Sensor"
},
"options_digital": {
"data": {
"name": "Name (optional)",
"poll_interval": "Poll Interval (minutes) (optional)",
"type": "Sensor Type"
},
"description": "Please select the options for the digital sensor attached to {zone}",
"title": "Configure Digital Sensor"
"description": "Please select the desired behavior for your panel",
"title": "Configure Misc"
},
"options_switch": {
"data": {
Expand All @@ -68,17 +94,8 @@
},
"description": "Please select the output options for {zone}",
"title": "Configure Switchable Output"
},
"user": {
"data": {
"host": "Konnected device IP address",
"inverse": "Invert the open/close state",
"port": "Konnected device port"
},
"description": "Please enter the host information for your Konnected Panel.",
"title": "Discover Konnected Device"
}
},
"title": "Konnected.io"
"title": "Konnected Alarm Panel Options"
}
}
60 changes: 26 additions & 34 deletions homeassistant/components/konnected/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
import hmac
import json
import logging
import random
import string

from aiohttp.hdrs import AUTHORIZATION
from aiohttp.web import Request, Response
Expand All @@ -30,11 +28,10 @@
STATE_ON,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv, storage
from homeassistant.helpers import config_validation as cv

from .config_flow import ( # Loading the config flow file will register the flow
DEVICE_SCHEMA_YAML,
configured_devices,
)
from .const import (
CONF_ACTIVATION,
Expand All @@ -51,9 +48,6 @@

_LOGGER = logging.getLogger(__name__)

STORAGE_VERSION = 1
STORAGE_KEY = DOMAIN

# pylint: disable=no-value-for-parameter
CONFIG_SCHEMA = vol.Schema(
{
Expand All @@ -78,44 +72,23 @@ async def async_setup(hass: HomeAssistant, config: dict):
if cfg is None:
cfg = {}

store = storage.Store(hass, STORAGE_VERSION, STORAGE_KEY)
data_store = await store.async_load()
if data_store is None:
data_store = {}

# create a token if none in yaml or storage
access_token = (
cfg.get(CONF_ACCESS_TOKEN)
or data_store.get(CONF_ACCESS_TOKEN)
or "".join(random.choices(f"{string.ascii_uppercase}{string.digits}", k=20))
)
if DOMAIN not in hass.data:
hass.data[DOMAIN] = {
CONF_ACCESS_TOKEN: access_token,
CONF_ACCESS_TOKEN: cfg.get(CONF_ACCESS_TOKEN),
CONF_API_HOST: cfg.get(CONF_API_HOST),
CONF_DEVICES: {},
"config_data": {},
}

# save off the access token
await store.async_save({CONF_ACCESS_TOKEN: access_token})

hass.http.register_view(KonnectedView(access_token))
hass.http.register_view(KonnectedView)

# Check if they have yaml configured devices
if CONF_DEVICES not in cfg:
return True

configured = configured_devices(hass)
devices = cfg[CONF_DEVICES]

if devices:
MartinHjelmare marked this conversation as resolved.
Show resolved Hide resolved
for device in devices:
# If configured, the panel is already imported and needs to be managed via config entries
if device["id"] in configured:
continue

# No existing config entry found, try importing it. Use
# Attempt to importing the cfg. Use
# hass.async_add_job to avoid a deadlock.
hass.async_create_task(
hass.config_entries.flow.async_init(
Expand Down Expand Up @@ -147,6 +120,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
# this will trigger a retry in the future
raise config_entries.ConfigEntryNotReady

entry.add_update_listener(async_entry_updated)
return True


Expand All @@ -166,16 +140,20 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
return unload_ok


async def async_entry_updated(hass: HomeAssistant, entry: ConfigEntry):
"""Reload the config entry when options change."""
await hass.config_entries.async_reload(entry.entry_id)


class KonnectedView(HomeAssistantView):
"""View creates an endpoint to receive push updates from the device."""

url = UPDATE_ENDPOINT
name = "api:konnected"
requires_auth = False # Uses access token from configuration

def __init__(self, auth_token):
def __init__(self):
"""Initialize the view."""
self.auth_token = auth_token

@staticmethod
def binary_value(state, activation):
Expand All @@ -190,7 +168,21 @@ async def update_sensor(self, request: Request, device_id) -> Response:
data = hass.data[DOMAIN]

auth = request.headers.get(AUTHORIZATION, None)
if auth is None or not hmac.compare_digest(f"Bearer {self.auth_token}", auth):
tokens = (
[hass.data[DOMAIN][CONF_ACCESS_TOKEN]]
if hass.data[DOMAIN].get(CONF_ACCESS_TOKEN)
MartinHjelmare marked this conversation as resolved.
Show resolved Hide resolved
else []
)
tokens.extend(
[
entry.data[CONF_ACCESS_TOKEN]
for entry in hass.config_entries.async_entries(DOMAIN)
]
)
if auth is None or not next(
(True for token in tokens if hmac.compare_digest(f"Bearer {token}", auth)),
False,
):
return self.json_message("unauthorized", status_code=HTTP_UNAUTHORIZED)

try: # Konnected 2.2.0 and above supports JSON payloads
Expand Down
Loading