diff --git a/custom_components/sia/__init__.py b/custom_components/sia/__init__.py index 94c861c..1b28334 100644 --- a/custom_components/sia/__init__.py +++ b/custom_components/sia/__init__.py @@ -1,5 +1,6 @@ """The sia integration.""" import asyncio +import logging from homeassistant.components.alarm_control_panel import ( DOMAIN as ALARM_CONTROL_PANEL_DOMAIN, @@ -7,7 +8,7 @@ from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_PORT +from homeassistant.const import CONF_PORT, CONF_PROTOCOL from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady @@ -16,6 +17,8 @@ PLATFORMS = [ALARM_CONTROL_PANEL_DOMAIN, BINARY_SENSOR_DOMAIN, SENSOR_DOMAIN] +_LOGGER = logging.getLogger(__name__) + async def async_setup(hass: HomeAssistant, config: dict): """Set up the sia component.""" @@ -57,3 +60,17 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): hub: SIAHub = hass.data[DOMAIN].pop(entry.entry_id) await hub.async_shutdown() return unload_ok + + +async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry): + """Migrate old entry.""" + _LOGGER.debug("Migrating from version %s", config_entry.version) + + if config_entry.version == 1: + data = config_entry.data.copy() + data[CONF_PROTOCOL] = "TCP" + config_entry.version = 2 + hass.config_entries.async_update_entry(config_entry, data=data) + + _LOGGER.info("Migration to version %s successful", config_entry.version) + return True diff --git a/custom_components/sia/config_flow.py b/custom_components/sia/config_flow.py index d9cd870..442c324 100644 --- a/custom_components/sia/config_flow.py +++ b/custom_components/sia/config_flow.py @@ -1,10 +1,6 @@ """Config flow for sia integration.""" import logging -import voluptuous as vol -from homeassistant import config_entries, exceptions -from homeassistant.const import CONF_PORT -from homeassistant.data_entry_flow import AbortFlow from pysiaalarm import ( InvalidAccountFormatError, InvalidAccountLengthError, @@ -12,12 +8,18 @@ InvalidKeyLengthError, SIAAccount, ) +import voluptuous as vol + +from homeassistant import config_entries, exceptions +from homeassistant.const import CONF_PORT, CONF_PROTOCOL +from homeassistant.data_entry_flow import AbortFlow from .const import ( CONF_ACCOUNT, CONF_ACCOUNTS, CONF_ADDITIONAL_ACCOUNTS, CONF_ENCRYPTION_KEY, + CONF_IGNORE_TIMESTAMPS, CONF_PING_INTERVAL, CONF_ZONES, DOMAIN, @@ -29,10 +31,12 @@ HUB_SCHEMA = vol.Schema( { vol.Required(CONF_PORT): int, + vol.Optional(CONF_PROTOCOL, default="TCP"): vol.In(["TCP", "UDP"]), vol.Required(CONF_ACCOUNT): str, vol.Optional(CONF_ENCRYPTION_KEY): str, vol.Required(CONF_PING_INTERVAL, default=1): int, vol.Required(CONF_ZONES, default=1): int, + vol.Required(CONF_IGNORE_TIMESTAMPS, default=False): bool, vol.Optional(CONF_ADDITIONAL_ACCOUNTS, default=False): bool, } ) @@ -43,6 +47,7 @@ vol.Optional(CONF_ENCRYPTION_KEY): str, vol.Required(CONF_PING_INTERVAL, default=1): int, vol.Required(CONF_ZONES, default=1): int, + vol.Required(CONF_IGNORE_TIMESTAMPS, default=False): bool, vol.Optional(CONF_ADDITIONAL_ACCOUNTS, default=False): bool, } ) @@ -55,105 +60,95 @@ def validate_input(data: dict): try: ping = int(data[CONF_PING_INTERVAL]) assert 1 <= ping <= 1440 - except AssertionError: - raise InvalidPing + except AssertionError as invalid_ping: + raise InvalidPing from invalid_ping try: zones = int(data[CONF_ZONES]) assert zones > 0 - except AssertionError: - raise InvalidZones + except AssertionError as invalid_zone: + raise InvalidZones from invalid_zone class SIAConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Handle a config flow for sia.""" - VERSION = 1 - CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH - data = None + VERSION = 2 + + def __init__(self): + """Initialize the config flow.""" + self._data = {} async def async_step_add_account(self, user_input: dict = None): """Handle the additional accounts steps.""" - errors = {} - if user_input is not None: - try: - validate_input(user_input) - self.update_data(user_input) - if user_input[CONF_ADDITIONAL_ACCOUNTS]: - return await self.async_step_add_account() - except InvalidKeyFormatError: - errors["base"] = "invalid_key_format" - except InvalidKeyLengthError: - errors["base"] = "invalid_key_length" - except InvalidAccountFormatError: - errors["base"] = "invalid_account_format" - except InvalidAccountLengthError: - errors["base"] = "invalid_account_length" - except InvalidPing: - errors["base"] = "invalid_ping" - except InvalidZones: - errors["base"] = "invalid_zones" - - return self.async_show_form( - step_id="user", - data_schema=ACCOUNT_SCHEMA, - errors=errors, - ) + if user_input is None: + return self.async_show_form( + step_id="user", + data_schema=ACCOUNT_SCHEMA, + errors={}, + ) async def async_step_user(self, user_input: dict = None): """Handle the initial step.""" + if user_input is None: + return self.async_show_form( + step_id="user", data_schema=HUB_SCHEMA, errors={} + ) errors = {} - if user_input is not None: - try: - validate_input(user_input) - await self.async_set_unique_id(f"{DOMAIN}_{self.data[CONF_PORT]}") - self._abort_if_unique_id_configured() - self.update_data(user_input) - if not user_input[CONF_ADDITIONAL_ACCOUNTS]: - return self.async_create_entry( - title=f"SIA Alarm on port {self.data[CONF_PORT]}", - data=self.data, - ) - return await self.async_step_add_account() - except InvalidKeyFormatError: - errors["base"] = "invalid_key_format" - except InvalidKeyLengthError: - errors["base"] = "invalid_key_length" - except InvalidAccountFormatError: - errors["base"] = "invalid_account_format" - except InvalidAccountLengthError: - errors["base"] = "invalid_account_length" - except InvalidPing: - errors["base"] = "invalid_ping" - except InvalidZones: - errors["base"] = "invalid_zones" - except AbortFlow: - return self.async_abort(reason="already_configured") - except Exception: # pylint: disable=broad-except - _LOGGER.exception("Unexpected exception") - errors["base"] = "unknown" - - return self.async_show_form( - step_id="user", data_schema=HUB_SCHEMA, errors=errors + try: + validate_input(user_input) + except InvalidKeyFormatError: + errors["base"] = "invalid_key_format" + except InvalidKeyLengthError: + errors["base"] = "invalid_key_length" + except InvalidAccountFormatError: + errors["base"] = "invalid_account_format" + except InvalidAccountLengthError: + errors["base"] = "invalid_account_length" + except InvalidPing: + errors["base"] = "invalid_ping" + except InvalidZones: + errors["base"] = "invalid_zones" + except Exception: # pylint: disable=broad-except + _LOGGER.exception("Unexpected exception") + errors["base"] = "unknown" + if errors: + return self.async_show_form( + step_id="user", data_schema=HUB_SCHEMA, errors=errors + ) + self.update_data(user_input) + await self.async_set_unique_id(f"{DOMAIN}_{self._data[CONF_PORT]}") + try: + self._abort_if_unique_id_configured() + except AbortFlow: + return self.async_abort(reason="already_configured") + + if user_input[CONF_ADDITIONAL_ACCOUNTS]: + return await self.async_step_add_account() + return self.async_create_entry( + title=f"SIA Alarm on port {self._data[CONF_PORT]}", + data=self._data, ) def update_data(self, user_input): """Parse the user_input and store in self.data.""" - if not self.data: - self.data = { + if not self._data: + self._data = { CONF_PORT: user_input[CONF_PORT], + CONF_PROTOCOL: user_input[CONF_PROTOCOL], CONF_ACCOUNTS: [ { CONF_ACCOUNT: user_input[CONF_ACCOUNT], CONF_ENCRYPTION_KEY: user_input.get(CONF_ENCRYPTION_KEY), CONF_PING_INTERVAL: user_input[CONF_PING_INTERVAL], CONF_ZONES: user_input[CONF_ZONES], + CONF_IGNORE_TIMESTAMPS: user_input[CONF_IGNORE_TIMESTAMPS], } ], } else: add_data = user_input.copy() add_data.pop(CONF_ADDITIONAL_ACCOUNTS) - self.data[CONF_ACCOUNTS].append(add_data) + self._data[CONF_ACCOUNTS].append(add_data) class InvalidPing(exceptions.HomeAssistantError): diff --git a/custom_components/sia/const.py b/custom_components/sia/const.py index 531afc0..512fb5a 100644 --- a/custom_components/sia/const.py +++ b/custom_components/sia/const.py @@ -8,6 +8,7 @@ CONF_PING_INTERVAL = "ping_interval" CONF_ENCRYPTION_KEY = "encryption_key" CONF_ZONES = "zones" +CONF_IGNORE_TIMESTAMPS = "ignore_timestamps" DOMAIN = "sia" DATA_UPDATED = f"{DOMAIN}_data_updated" diff --git a/custom_components/sia/manifest.json b/custom_components/sia/manifest.json index 8285981..8268c82 100644 --- a/custom_components/sia/manifest.json +++ b/custom_components/sia/manifest.json @@ -4,10 +4,11 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/sia", "requirements": [ - "pysiaalarm==3.0.0b6" + "pysiaalarm==3.0.0b12" ], "codeowners": [ "@eavanvalkenburg" ], - "version": "0.5.0b5" + "version": "0.5.0b6", + "iot_class": "local_push" } diff --git a/custom_components/sia/strings.json b/custom_components/sia/strings.json index 1eed82a..1d7aeba 100644 --- a/custom_components/sia/strings.json +++ b/custom_components/sia/strings.json @@ -4,15 +4,27 @@ "step": { "user": { "data": { - "name": "Name", - "port": "Port", - "account": "Account", + "port": "[%key:common::config_flow::data::port%]", + "protocol": "Protocol", + "account": "Account ID", "encryption_key": "Encryption Key", "ping_interval": "Ping Interval (min)", "zones": "Number of zones for the account", - "additional_account": "Add more accounts?" + "ignore_timestamps": "Ignore the timestamp check", + "additional_account": "Additional accounts" }, - "title": "Create a connection for SIA DC-09 based alarm systems." + "title": "Create a connection for SIA based alarm systems." + }, + "additional_account": { + "data": { + "account": "[%key:component::sia::config::step::user::data::account%]", + "encryption_key": "[%key:component::sia::config::step::user::data::encryption_key%]", + "ping_interval": "[%key:component::sia::config::step::user::data::ping_interval%]", + "zones": "[%key:component::sia::config::step::user::data::zones%]", + "ignore_timestamps": "[%key:component::sia::config::step::user::data::ignore_timestamps%]", + "additional_account": "[%key:component::sia::config::step::user::data::additional_account%]" + }, + "title": "Add another account to the current port." } }, "error": { @@ -22,10 +34,10 @@ "invalid_account_length": "The account is not the right length, it has to be between 3 and 16 characters.", "invalid_ping": "The ping interval needs to be between 1 and 1440 minutes.", "invalid_zones": "There needs to be at least 1 zone.", - "unknown": "Unexpected error" + "unknown": "[%key:common::config_flow::error::unknown%]" }, "abort": { - "already_configured": "This SIA Port is already used, please select another or recreate the existing with an extra account." + "already_configured": "[%key:common::config_flow::abort::already_configured_service%]" } } } diff --git a/custom_components/sia/translations/en.json b/custom_components/sia/translations/en.json index 9cc5202..c46bc9e 100644 --- a/custom_components/sia/translations/en.json +++ b/custom_components/sia/translations/en.json @@ -21,9 +21,21 @@ "encryption_key": "Encryption Key", "ping_interval": "Ping Interval (min)", "zones": "Number of zones for the account", + "ignore_timestamps": "Ignore the timestamp check", "additional_account": "Add more accounts?" }, "title": "Create a connection for SIA DC-09 based alarm systems." + }, + "additional_account": { + "data": { + "account": "Account", + "encryption_key": "Encryption Key", + "ping_interval": "Ping Interval (min)", + "zones": "Number of zones for the account", + "ignore_timestamps": "Ignore the timestamp check", + "additional_account": "Add more accounts?" + }, + "title": "Add another account to the current port." } } },