-
-
Notifications
You must be signed in to change notification settings - Fork 37.4k
Add SIA Alarm systems #36625
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
Merged
Merged
Add SIA Alarm systems #36625
Changes from all commits
Commits
Show all changes
51 commits
Select commit
Hold shift + click to select a range
84f8cc1
initial commit of SIA integration
eavanvalkenburg 219f0e7
translations
eavanvalkenburg 92fbe28
moved reactions to file, typed everything
eavanvalkenburg eceff78
fixed no-else-return 3 times
eavanvalkenburg 78b10c4
refactored config and fix coverage of test
eavanvalkenburg 88cc15e
fix requirements_test
eavanvalkenburg e29cce6
elimated another platform
eavanvalkenburg e6d1cc4
forgot some mentions of sensor
eavanvalkenburg acd8059
updated config flow steps, fixed restore and small edits
eavanvalkenburg 6c936df
fixed pylint
eavanvalkenburg 346ee55
updated config_flow with better schema, small fixes from review
eavanvalkenburg f0ca2aa
final comment and small legibility enhancements
eavanvalkenburg 12bfc99
small fix for pylint
eavanvalkenburg 11819d5
fixed init
eavanvalkenburg 0dd74c2
fixes for botched rebase
eavanvalkenburg 09b7fae
fixed port string
eavanvalkenburg 4f87cb1
updated common strings
eavanvalkenburg 3381d77
rebuild component with eventbus
eavanvalkenburg e25a97c
fixed pylint and tests
eavanvalkenburg e9a18df
updates based on review by @bdraco
eavanvalkenburg 7c90ba5
updates based on new version of package and reviews
eavanvalkenburg b677307
small updates with latest package
eavanvalkenburg f0ec788
added raise from
eavanvalkenburg 66d064d
deleted async_setup from test
eavanvalkenburg 2a0ab12
fixed tests
eavanvalkenburg cc01c83
removed unused code from addititional account step
eavanvalkenburg da28c10
fixed typo in strings
eavanvalkenburg e8e6532
clarification and update to update_data func
eavanvalkenburg cf20bac
added iot_class to manifest
eavanvalkenburg 81fd57e
fixed entity and unique id setup
eavanvalkenburg 3914ce4
small fix in tests
eavanvalkenburg d007d12
improved unique_id semantics and load/unload functions
eavanvalkenburg fd71c49
added typing in order to fix mypy
eavanvalkenburg 4541dc2
further fixes for typing
eavanvalkenburg 40c77a8
final fixes for mypy
eavanvalkenburg 409fc1a
adding None return types
eavanvalkenburg 6673927
fix hub DR identifier
eavanvalkenburg def09c8
rebased, added DeviceInfo
eavanvalkenburg 2838699
rewrite to clean up and make it easier to read
eavanvalkenburg e16459d
replaced functions with format for id and name
eavanvalkenburg 9b6ed50
renamed tracker remover small fix in state.setter
eavanvalkenburg d2638ce
improved readibility of state.setter
eavanvalkenburg 42d1fd0
no more state.setter and small updates
eavanvalkenburg 8bc7c29
mypy fix
eavanvalkenburg 99b3e5c
fixed and improved config flow
eavanvalkenburg 0800bac
added fixtures to test and other cleaner test code
eavanvalkenburg 74aa738
removed timeband from config, will reintro in a options flow
eavanvalkenburg ac66f04
removed timeband from tests
eavanvalkenburg 10e7b26
added options flow for zones and timestamps
eavanvalkenburg 818be2e
removed type ignore
eavanvalkenburg f3e4c2c
replaced mapping with collections.abc
eavanvalkenburg File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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
This file contains hidden or 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
This file contains hidden or 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
This file contains hidden or 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,34 @@ | ||
| """The sia integration.""" | ||
| from homeassistant.config_entries import ConfigEntry | ||
| from homeassistant.const import CONF_PORT | ||
| from homeassistant.core import HomeAssistant | ||
| from homeassistant.exceptions import ConfigEntryNotReady | ||
|
|
||
| from .const import DOMAIN, PLATFORMS | ||
| from .hub import SIAHub | ||
|
|
||
|
|
||
| async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: | ||
| """Set up sia from a config entry.""" | ||
| hub: SIAHub = SIAHub(hass, entry) | ||
| await hub.async_setup_hub() | ||
|
|
||
| hass.data.setdefault(DOMAIN, {}) | ||
| hass.data[DOMAIN][entry.entry_id] = hub | ||
| try: | ||
| await hub.sia_client.start(reuse_port=True) | ||
| except OSError as exc: | ||
| raise ConfigEntryNotReady( | ||
| f"SIA Server at port {entry.data[CONF_PORT]} could not start." | ||
| ) from exc | ||
| hass.config_entries.async_setup_platforms(entry, PLATFORMS) | ||
| return True | ||
|
|
||
|
|
||
| async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: | ||
| """Unload a config entry.""" | ||
| unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) | ||
| if unload_ok: | ||
| hub: SIAHub = hass.data[DOMAIN].pop(entry.entry_id) | ||
| await hub.async_shutdown() | ||
| return unload_ok |
This file contains hidden or 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,253 @@ | ||
| """Module for SIA Alarm Control Panels.""" | ||
| from __future__ import annotations | ||
|
|
||
| import logging | ||
| from typing import Any, Callable | ||
|
|
||
| from pysiaalarm import SIAEvent | ||
|
|
||
| from homeassistant.components.alarm_control_panel import ( | ||
| ENTITY_ID_FORMAT as ALARM_ENTITY_ID_FORMAT, | ||
| AlarmControlPanelEntity, | ||
| ) | ||
| from homeassistant.config_entries import ConfigEntry | ||
| from homeassistant.const import ( | ||
| CONF_PORT, | ||
| CONF_ZONE, | ||
| STATE_ALARM_ARMED_AWAY, | ||
| STATE_ALARM_ARMED_CUSTOM_BYPASS, | ||
| STATE_ALARM_ARMED_NIGHT, | ||
| STATE_ALARM_DISARMED, | ||
| STATE_ALARM_TRIGGERED, | ||
| STATE_UNAVAILABLE, | ||
| ) | ||
| from homeassistant.core import CALLBACK_TYPE, Event, HomeAssistant, callback | ||
| from homeassistant.helpers.entity import DeviceInfo | ||
| from homeassistant.helpers.event import async_call_later | ||
| from homeassistant.helpers.restore_state import RestoreEntity | ||
| from homeassistant.helpers.typing import StateType | ||
|
|
||
| from .const import ( | ||
| CONF_ACCOUNT, | ||
| CONF_ACCOUNTS, | ||
| CONF_PING_INTERVAL, | ||
| CONF_ZONES, | ||
| DOMAIN, | ||
| SIA_ENTITY_ID_FORMAT, | ||
| SIA_EVENT, | ||
| SIA_NAME_FORMAT, | ||
| SIA_UNIQUE_ID_FORMAT_ALARM, | ||
| ) | ||
| from .utils import get_attr_from_sia_event, get_unavailability_interval | ||
|
|
||
| _LOGGER = logging.getLogger(__name__) | ||
|
|
||
| DEVICE_CLASS_ALARM = "alarm" | ||
| PREVIOUS_STATE = "previous_state" | ||
|
|
||
| CODE_CONSEQUENCES: dict[str, StateType] = { | ||
| "PA": STATE_ALARM_TRIGGERED, | ||
| "JA": STATE_ALARM_TRIGGERED, | ||
| "TA": STATE_ALARM_TRIGGERED, | ||
| "BA": STATE_ALARM_TRIGGERED, | ||
| "CA": STATE_ALARM_ARMED_AWAY, | ||
| "CB": STATE_ALARM_ARMED_AWAY, | ||
| "CG": STATE_ALARM_ARMED_AWAY, | ||
| "CL": STATE_ALARM_ARMED_AWAY, | ||
| "CP": STATE_ALARM_ARMED_AWAY, | ||
| "CQ": STATE_ALARM_ARMED_AWAY, | ||
| "CS": STATE_ALARM_ARMED_AWAY, | ||
| "CF": STATE_ALARM_ARMED_CUSTOM_BYPASS, | ||
| "OA": STATE_ALARM_DISARMED, | ||
| "OB": STATE_ALARM_DISARMED, | ||
| "OG": STATE_ALARM_DISARMED, | ||
| "OP": STATE_ALARM_DISARMED, | ||
| "OQ": STATE_ALARM_DISARMED, | ||
| "OR": STATE_ALARM_DISARMED, | ||
| "OS": STATE_ALARM_DISARMED, | ||
| "NC": STATE_ALARM_ARMED_NIGHT, | ||
| "NL": STATE_ALARM_ARMED_NIGHT, | ||
| "BR": PREVIOUS_STATE, | ||
| "NP": PREVIOUS_STATE, | ||
| "NO": PREVIOUS_STATE, | ||
| } | ||
|
|
||
|
|
||
| async def async_setup_entry( | ||
| hass: HomeAssistant, | ||
| entry: ConfigEntry, | ||
| async_add_entities: Callable[..., None], | ||
| ) -> bool: | ||
|
eavanvalkenburg marked this conversation as resolved.
|
||
| """Set up SIA alarm_control_panel(s) from a config entry.""" | ||
| async_add_entities( | ||
| [ | ||
|
eavanvalkenburg marked this conversation as resolved.
|
||
| SIAAlarmControlPanel(entry, account_data, zone) | ||
| for account_data in entry.data[CONF_ACCOUNTS] | ||
| for zone in range( | ||
| 1, | ||
| entry.options[CONF_ACCOUNTS][account_data[CONF_ACCOUNT]][CONF_ZONES] | ||
| + 1, | ||
| ) | ||
| ] | ||
| ) | ||
| return True | ||
|
eavanvalkenburg marked this conversation as resolved.
|
||
|
|
||
|
|
||
| class SIAAlarmControlPanel(AlarmControlPanelEntity, RestoreEntity): | ||
| """Class for SIA Alarm Control Panels.""" | ||
|
|
||
| def __init__( | ||
| self, | ||
| entry: ConfigEntry, | ||
| account_data: dict[str, Any], | ||
| zone: int, | ||
| ): | ||
| """Create SIAAlarmControlPanel object.""" | ||
| self._entry: ConfigEntry = entry | ||
| self._account_data: dict[str, Any] = account_data | ||
| self._zone: int = zone | ||
|
|
||
| self._port: int = self._entry.data[CONF_PORT] | ||
| self._account: str = self._account_data[CONF_ACCOUNT] | ||
| self._ping_interval: int = self._account_data[CONF_PING_INTERVAL] | ||
|
|
||
| self.entity_id: str = ALARM_ENTITY_ID_FORMAT.format( | ||
|
eavanvalkenburg marked this conversation as resolved.
|
||
| SIA_ENTITY_ID_FORMAT.format( | ||
| self._port, self._account, self._zone, DEVICE_CLASS_ALARM | ||
| ) | ||
| ) | ||
|
|
||
| self._attr: dict[str, Any] = { | ||
| CONF_PORT: self._port, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't use |
||
| CONF_ACCOUNT: self._account, | ||
| CONF_ZONE: self._zone, | ||
| CONF_PING_INTERVAL: f"{self._ping_interval} minute(s)", | ||
|
eavanvalkenburg marked this conversation as resolved.
|
||
| } | ||
|
|
||
| self._available: bool = True | ||
| self._state: StateType = None | ||
| self._old_state: StateType = None | ||
| self._cancel_availability_cb: CALLBACK_TYPE | None = None | ||
|
|
||
| async def async_added_to_hass(self) -> None: | ||
| """Run when entity about to be added to hass. | ||
|
|
||
| Overridden from Entity. | ||
|
|
||
| 1. start the event listener and add the callback to on_remove | ||
| 2. get previous state from storage | ||
| 3. if previous state: restore | ||
| 4. if previous state is unavailable: set _available to False and return | ||
| 5. if available: create availability cb | ||
| """ | ||
| self.async_on_remove( | ||
| self.hass.bus.async_listen( | ||
|
eavanvalkenburg marked this conversation as resolved.
|
||
| event_type=SIA_EVENT.format(self._port, self._account), | ||
| listener=self.async_handle_event, | ||
| ) | ||
| ) | ||
| last_state = await self.async_get_last_state() | ||
| if last_state is not None: | ||
| self._state = last_state.state | ||
| if self.state == STATE_UNAVAILABLE: | ||
| self._available = False | ||
| return | ||
| self._cancel_availability_cb = self.async_create_availability_cb() | ||
|
|
||
| async def async_will_remove_from_hass(self) -> None: | ||
| """Run when entity will be removed from hass. | ||
|
|
||
| Overridden from Entity. | ||
| """ | ||
| if self._cancel_availability_cb: | ||
| self._cancel_availability_cb() | ||
|
|
||
| async def async_handle_event(self, event: Event) -> None: | ||
| """Listen to events for this port and account and update state and attributes. | ||
|
|
||
| If the port and account combo receives any message it means it is online and can therefore be set to available. | ||
| """ | ||
| sia_event: SIAEvent = SIAEvent.from_dict( # pylint: disable=no-member | ||
| event.data | ||
| ) | ||
| _LOGGER.debug("Received event: %s", sia_event) | ||
| if int(sia_event.ri) == self._zone: | ||
| self._attr.update(get_attr_from_sia_event(sia_event)) | ||
| new_state = CODE_CONSEQUENCES.get(sia_event.code, None) | ||
| if new_state is not None: | ||
| if new_state == PREVIOUS_STATE: | ||
| new_state = self._old_state | ||
| self._state, self._old_state = new_state, self._state | ||
| self._available = True | ||
| self.async_write_ha_state() | ||
| self.async_reset_availability_cb() | ||
|
|
||
| @callback | ||
| def async_reset_availability_cb(self) -> None: | ||
| """Reset availability cb by cancelling the current and creating a new one.""" | ||
| if self._cancel_availability_cb: | ||
| self._cancel_availability_cb() | ||
| self._cancel_availability_cb = self.async_create_availability_cb() | ||
|
|
||
| @callback | ||
| def async_create_availability_cb(self) -> CALLBACK_TYPE: | ||
| """Create a availability cb and return the callback.""" | ||
| return async_call_later( | ||
| self.hass, | ||
| get_unavailability_interval(self._ping_interval), | ||
| self.async_set_unavailable, | ||
| ) | ||
|
|
||
| @callback | ||
| def async_set_unavailable(self, _) -> None: | ||
| """Set unavailable.""" | ||
| self._available = False | ||
| self.async_write_ha_state() | ||
|
|
||
| @property | ||
| def state(self) -> StateType: | ||
| """Get state.""" | ||
| return self._state | ||
|
|
||
| @property | ||
| def name(self) -> str: | ||
| """Get Name.""" | ||
| return SIA_NAME_FORMAT.format( | ||
| self._port, self._account, self._zone, DEVICE_CLASS_ALARM | ||
| ) | ||
|
|
||
| @property | ||
| def unique_id(self) -> str: | ||
| """Get unique_id.""" | ||
| return SIA_UNIQUE_ID_FORMAT_ALARM.format( | ||
| self._entry.entry_id, self._account, self._zone | ||
| ) | ||
|
|
||
| @property | ||
| def available(self) -> bool: | ||
| """Get availability.""" | ||
| return self._available | ||
|
|
||
| @property | ||
| def extra_state_attributes(self) -> dict[str, Any]: | ||
| """Return device attributes.""" | ||
| return self._attr | ||
|
|
||
| @property | ||
| def should_poll(self) -> bool: | ||
| """Return False if entity pushes its state to HA.""" | ||
| return False | ||
|
|
||
| @property | ||
| def supported_features(self) -> int: | ||
| """Flag supported features.""" | ||
| return 0 | ||
|
|
||
| @property | ||
| def device_info(self) -> DeviceInfo: | ||
| """Return the device_info.""" | ||
| return { | ||
| "identifiers": {(DOMAIN, self.unique_id)}, | ||
| "name": self.name, | ||
| "via_device": (DOMAIN, f"{self._port}_{self._account}"), | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.