-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 2451295
Showing
28 changed files
with
1,183 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
) | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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).""" |
Oops, something went wrong.