Skip to content

Commit

Permalink
Initial
Browse files Browse the repository at this point in the history
  • Loading branch information
slydiman committed Dec 17, 2020
0 parents commit 2451295
Show file tree
Hide file tree
Showing 28 changed files with 1,183 additions and 0 deletions.
198 changes: 198 additions & 0 deletions __init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
"""Support for HLK-SW16 (old) relay switches."""
import logging

from .protocol import create_hlk_sw16_old_connection
import voluptuous as vol

from homeassistant.config_entries import SOURCE_IMPORT
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT, CONF_SWITCHES
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import (
async_dispatcher_connect,
async_dispatcher_send,
)
from homeassistant.helpers.entity import Entity

from .const import (
CONNECTION_TIMEOUT,
DEFAULT_KEEP_ALIVE_INTERVAL,
DEFAULT_PORT,
DEFAULT_RECONNECT_INTERVAL,
DOMAIN,
)

_LOGGER = logging.getLogger(__name__)

DATA_DEVICE_REGISTER = "hlk_sw16_old_device_register"
DATA_DEVICE_LISTENER = "hlk_sw16_old_device_listener"

SWITCH_SCHEMA = vol.Schema({vol.Optional(CONF_NAME): cv.string})

RELAY_ID = vol.All(
vol.Any(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "a", "b", "c", "d", "e", "f"), vol.Coerce(str)
)

CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: vol.Schema(
{
cv.string: vol.Schema(
{
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Required(CONF_SWITCHES): vol.Schema(
{RELAY_ID: SWITCH_SCHEMA}
),
}
)
}
)
},
extra=vol.ALLOW_EXTRA,
)


async def async_setup(hass, config):
"""Component setup, do nothing."""
if DOMAIN not in config:
return True

for device_id in config[DOMAIN]:
conf = config[DOMAIN][device_id]
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_IMPORT},
data={CONF_HOST: conf[CONF_HOST], CONF_PORT: conf[CONF_PORT]},
)
)
return True


async def async_setup_entry(hass, entry):
"""Set up the HLK-SW16 (old) switch."""
hass.data.setdefault(DOMAIN, {})
host = entry.data[CONF_HOST]
port = entry.data[CONF_PORT]
address = f"{host}:{port}"

hass.data[DOMAIN][entry.entry_id] = {}

@callback
def disconnected():
"""Schedule reconnect after connection has been lost."""
_LOGGER.warning("HLK-SW16 (old) %s disconnected", address)
async_dispatcher_send(
hass, f"hlk_sw16_old_device_available_{entry.entry_id}", False
)

@callback
def reconnected():
"""Schedule reconnect after connection has been lost."""
_LOGGER.warning("HLK-SW16 (old) %s connected", address)
async_dispatcher_send(hass, f"hlk_sw16_old_device_available_{entry.entry_id}", True)

async def connect():
"""Set up connection and hook it into HA for reconnect/shutdown."""
_LOGGER.info("Initiating HLK-SW16 (old) connection to %s", address)

client = await create_hlk_sw16_old_connection(
host=host,
port=port,
disconnect_callback=disconnected,
reconnect_callback=reconnected,
loop=hass.loop,
timeout=CONNECTION_TIMEOUT,
reconnect_interval=DEFAULT_RECONNECT_INTERVAL,
keep_alive_interval=DEFAULT_KEEP_ALIVE_INTERVAL,
)

hass.data[DOMAIN][entry.entry_id][DATA_DEVICE_REGISTER] = client

# Load entities
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, "switch")
)

_LOGGER.info("Connected to HLK-SW16 (old) device: %s", address)

hass.loop.create_task(connect())

return True


async def async_unload_entry(hass, entry):
"""Unload a config entry."""
client = hass.data[DOMAIN][entry.entry_id].pop(DATA_DEVICE_REGISTER)
client.stop()
unload_ok = await hass.config_entries.async_forward_entry_unload(entry, "switch")

if unload_ok:
if hass.data[DOMAIN][entry.entry_id]:
hass.data[DOMAIN].pop(entry.entry_id)
if not hass.data[DOMAIN]:
hass.data.pop(DOMAIN)
return unload_ok


class SW16OldDevice(Entity):
"""Representation of a HLK-SW16 (old) device.
Contains the common logic for HLK-SW16 (old) entities.
"""

def __init__(self, device_relay, entry_id, client):
"""Initialize the device."""
# HLK-SW16 (old) specific attributes for every component type
self._entry_id = entry_id
self._device_relay = device_relay
self._is_on = None
self._client = client
self._name = device_relay

@property
def unique_id(self):
"""Return a unique ID."""
return f"{self._entry_id}_{self._device_relay}"

@callback
def handle_event_callback(self, event):
"""Propagate changes through ha."""
_LOGGER.debug("Relay %s new state callback: %r", self.unique_id, event)
self._is_on = event
self.async_write_ha_state()

@property
def should_poll(self):
"""No polling needed."""
return False

@property
def name(self):
"""Return a name for the device."""
return self._name

@property
def available(self):
"""Return True if entity is available."""
return bool(self._client.is_connected)

@callback
def _availability_callback(self, availability):
"""Update availability state."""
self.async_write_ha_state()

async def async_added_to_hass(self):
"""Register update callback."""
self._client.register_status_callback(
self.handle_event_callback, self._device_relay
)
self._is_on = await self._client.status(self._device_relay)
self.async_on_remove(
async_dispatcher_connect(
self.hass,
f"hlk_sw16_old_device_available_{self._entry_id}",
self._availability_callback,
)
)
96 changes: 96 additions & 0 deletions config_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
"""Config flow for HLK-SW16 (old)."""
import asyncio

from .protocol import create_hlk_sw16_old_connection
import voluptuous as vol

from homeassistant import config_entries
from homeassistant.const import CONF_HOST, CONF_PORT
from homeassistant.core import HomeAssistant

from .const import (
CONNECTION_TIMEOUT,
DEFAULT_KEEP_ALIVE_INTERVAL,
DEFAULT_PORT,
DEFAULT_RECONNECT_INTERVAL,
DOMAIN,
)
from .errors import AlreadyConfigured, CannotConnect

DATA_SCHEMA = vol.Schema(
{
vol.Required(CONF_HOST): str,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): vol.Coerce(int),
}
)


async def connect_client(hass, user_input):
"""Connect the HLK-SW16 (old) client."""
client_aw = create_hlk_sw16_old_connection(
host=user_input[CONF_HOST],
port=user_input[CONF_PORT],
loop=hass.loop,
timeout=CONNECTION_TIMEOUT,
reconnect_interval=DEFAULT_RECONNECT_INTERVAL,
keep_alive_interval=DEFAULT_KEEP_ALIVE_INTERVAL,
)
return await asyncio.wait_for(client_aw, timeout=CONNECTION_TIMEOUT)


async def validate_input(hass: HomeAssistant, user_input):
"""Validate the user input allows us to connect."""
for entry in hass.config_entries.async_entries(DOMAIN):
if (
entry.data[CONF_HOST] == user_input[CONF_HOST]
and entry.data[CONF_PORT] == user_input[CONF_PORT]
):
raise AlreadyConfigured

try:
client = await connect_client(hass, user_input)
except asyncio.TimeoutError as err:
raise CannotConnect from err
try:

def disconnect_callback():
if client.in_transaction:
client.active_transaction.set_exception(CannotConnect)

client.disconnect_callback = disconnect_callback
await client.status()
except CannotConnect:
client.disconnect_callback = None
client.stop()
raise
else:
client.disconnect_callback = None
client.stop()


class SW16FlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a HLK-SW16 config flow."""

VERSION = 1
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH

async def async_step_import(self, user_input):
"""Handle import."""
return await self.async_step_user(user_input)

async def async_step_user(self, user_input=None):
"""Handle the initial step."""
errors = {}
if user_input is not None:
try:
await validate_input(self.hass, user_input)
address = f"{user_input[CONF_HOST]}:{user_input[CONF_PORT]}"
return self.async_create_entry(title=address, data=user_input)
except AlreadyConfigured:
errors["base"] = "already_configured"
except CannotConnect:
errors["base"] = "cannot_connect"

return self.async_show_form(
step_id="user", data_schema=DATA_SCHEMA, errors=errors
)
9 changes: 9 additions & 0 deletions const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"""Constants for HLK-SW16 (old) component."""

DOMAIN = "hlk_sw16_old"

DEFAULT_NAME = "HLK-SW16 (old)"
DEFAULT_PORT = 8080
DEFAULT_RECONNECT_INTERVAL = 10
DEFAULT_KEEP_ALIVE_INTERVAL = 3
CONNECTION_TIMEOUT = 10
14 changes: 14 additions & 0 deletions errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"""Errors for the HLK-SW16 (old) component."""
from homeassistant.exceptions import HomeAssistantError


class SW16Exception(HomeAssistantError):
"""Base class for HLK-SW16 (old) exceptions."""


class AlreadyConfigured(SW16Exception):
"""HLK-SW16 (old) is already configured."""


class CannotConnect(SW16Exception):
"""Unable to connect to the HLK-SW16 (old)."""
Loading

0 comments on commit 2451295

Please sign in to comment.