Implement entity_descriptions in SIA (#63130)
* moved sia to entity_descriptions * cleanup * moved logger to const * redid entity description to static * small fix * moved entity description classes to proper file * improved entity def's and undid logger * further cleanup * redid naming logic * Clean up Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
parent
8459a28489
commit
b66fd820ff
6 changed files with 296 additions and 222 deletions
|
@ -1,14 +1,18 @@
|
||||||
"""Module for SIA Alarm Control Panels."""
|
"""Module for SIA Alarm Control Panels."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
import logging
|
import logging
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from pysiaalarm import SIAEvent
|
from pysiaalarm import SIAEvent
|
||||||
|
|
||||||
from homeassistant.components.alarm_control_panel import AlarmControlPanelEntity
|
from homeassistant.components.alarm_control_panel import (
|
||||||
|
AlarmControlPanelEntity,
|
||||||
|
AlarmControlPanelEntityDescription,
|
||||||
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
|
CONF_PORT,
|
||||||
STATE_ALARM_ARMED_AWAY,
|
STATE_ALARM_ARMED_AWAY,
|
||||||
STATE_ALARM_ARMED_CUSTOM_BYPASS,
|
STATE_ALARM_ARMED_CUSTOM_BYPASS,
|
||||||
STATE_ALARM_ARMED_NIGHT,
|
STATE_ALARM_ARMED_NIGHT,
|
||||||
|
@ -20,40 +24,58 @@ from homeassistant.core import HomeAssistant, State
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.typing import StateType
|
from homeassistant.helpers.typing import StateType
|
||||||
|
|
||||||
from .const import CONF_ACCOUNT, CONF_ACCOUNTS, CONF_ZONES, SIA_UNIQUE_ID_FORMAT_ALARM
|
from .const import (
|
||||||
from .sia_entity_base import SIABaseEntity
|
CONF_ACCOUNT,
|
||||||
|
CONF_ACCOUNTS,
|
||||||
|
CONF_PING_INTERVAL,
|
||||||
|
CONF_ZONES,
|
||||||
|
KEY_ALARM,
|
||||||
|
PREVIOUS_STATE,
|
||||||
|
SIA_NAME_FORMAT,
|
||||||
|
SIA_UNIQUE_ID_FORMAT_ALARM,
|
||||||
|
)
|
||||||
|
from .sia_entity_base import SIABaseEntity, SIAEntityDescription
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
DEVICE_CLASS_ALARM = "alarm"
|
|
||||||
PREVIOUS_STATE = "previous_state"
|
|
||||||
|
|
||||||
CODE_CONSEQUENCES: dict[str, StateType] = {
|
@dataclass
|
||||||
"PA": STATE_ALARM_TRIGGERED,
|
class SIAAlarmControlPanelEntityDescription(
|
||||||
"JA": STATE_ALARM_TRIGGERED,
|
AlarmControlPanelEntityDescription,
|
||||||
"TA": STATE_ALARM_TRIGGERED,
|
SIAEntityDescription,
|
||||||
"BA": STATE_ALARM_TRIGGERED,
|
):
|
||||||
"CA": STATE_ALARM_ARMED_AWAY,
|
"""Describes SIA alarm control panel entity."""
|
||||||
"CB": STATE_ALARM_ARMED_AWAY,
|
|
||||||
"CG": STATE_ALARM_ARMED_AWAY,
|
|
||||||
"CL": STATE_ALARM_ARMED_AWAY,
|
ENTITY_DESCRIPTION_ALARM = SIAAlarmControlPanelEntityDescription(
|
||||||
"CP": STATE_ALARM_ARMED_AWAY,
|
key=KEY_ALARM,
|
||||||
"CQ": STATE_ALARM_ARMED_AWAY,
|
code_consequences={
|
||||||
"CS": STATE_ALARM_ARMED_AWAY,
|
"PA": STATE_ALARM_TRIGGERED,
|
||||||
"CF": STATE_ALARM_ARMED_CUSTOM_BYPASS,
|
"JA": STATE_ALARM_TRIGGERED,
|
||||||
"OA": STATE_ALARM_DISARMED,
|
"TA": STATE_ALARM_TRIGGERED,
|
||||||
"OB": STATE_ALARM_DISARMED,
|
"BA": STATE_ALARM_TRIGGERED,
|
||||||
"OG": STATE_ALARM_DISARMED,
|
"CA": STATE_ALARM_ARMED_AWAY,
|
||||||
"OP": STATE_ALARM_DISARMED,
|
"CB": STATE_ALARM_ARMED_AWAY,
|
||||||
"OQ": STATE_ALARM_DISARMED,
|
"CG": STATE_ALARM_ARMED_AWAY,
|
||||||
"OR": STATE_ALARM_DISARMED,
|
"CL": STATE_ALARM_ARMED_AWAY,
|
||||||
"OS": STATE_ALARM_DISARMED,
|
"CP": STATE_ALARM_ARMED_AWAY,
|
||||||
"NC": STATE_ALARM_ARMED_NIGHT,
|
"CQ": STATE_ALARM_ARMED_AWAY,
|
||||||
"NL": STATE_ALARM_ARMED_NIGHT,
|
"CS": STATE_ALARM_ARMED_AWAY,
|
||||||
"BR": PREVIOUS_STATE,
|
"CF": STATE_ALARM_ARMED_CUSTOM_BYPASS,
|
||||||
"NP": PREVIOUS_STATE,
|
"OA": STATE_ALARM_DISARMED,
|
||||||
"NO": PREVIOUS_STATE,
|
"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(
|
async def async_setup_entry(
|
||||||
|
@ -63,7 +85,19 @@ async def async_setup_entry(
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up SIA alarm_control_panel(s) from a config entry."""
|
"""Set up SIA alarm_control_panel(s) from a config entry."""
|
||||||
async_add_entities(
|
async_add_entities(
|
||||||
SIAAlarmControlPanel(entry, account_data, zone)
|
SIAAlarmControlPanel(
|
||||||
|
port=entry.data[CONF_PORT],
|
||||||
|
account=account_data[CONF_ACCOUNT],
|
||||||
|
zone=zone,
|
||||||
|
ping_interval=account_data[CONF_PING_INTERVAL],
|
||||||
|
entity_description=ENTITY_DESCRIPTION_ALARM,
|
||||||
|
unique_id=SIA_UNIQUE_ID_FORMAT_ALARM.format(
|
||||||
|
entry.entry_id, account_data[CONF_ACCOUNT], zone
|
||||||
|
),
|
||||||
|
name=SIA_NAME_FORMAT.format(
|
||||||
|
entry.data[CONF_PORT], account_data[CONF_ACCOUNT], zone, "alarm"
|
||||||
|
),
|
||||||
|
)
|
||||||
for account_data in entry.data[CONF_ACCOUNTS]
|
for account_data in entry.data[CONF_ACCOUNTS]
|
||||||
for zone in range(
|
for zone in range(
|
||||||
1,
|
1,
|
||||||
|
@ -75,29 +109,32 @@ async def async_setup_entry(
|
||||||
class SIAAlarmControlPanel(SIABaseEntity, AlarmControlPanelEntity):
|
class SIAAlarmControlPanel(SIABaseEntity, AlarmControlPanelEntity):
|
||||||
"""Class for SIA Alarm Control Panels."""
|
"""Class for SIA Alarm Control Panels."""
|
||||||
|
|
||||||
|
entity_description: SIAAlarmControlPanelEntityDescription
|
||||||
|
_attr_supported_features = 0
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
entry: ConfigEntry,
|
port: int,
|
||||||
account_data: dict[str, Any],
|
account: str,
|
||||||
zone: int,
|
zone: int | None,
|
||||||
|
ping_interval: int,
|
||||||
|
entity_description: SIAAlarmControlPanelEntityDescription,
|
||||||
|
unique_id: str,
|
||||||
|
name: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Create SIAAlarmControlPanel object."""
|
"""Create SIAAlarmControlPanel object."""
|
||||||
super().__init__(entry, account_data, zone, DEVICE_CLASS_ALARM)
|
super().__init__(
|
||||||
self._attr_state: StateType = None
|
port,
|
||||||
self._old_state: StateType = None
|
account,
|
||||||
|
zone,
|
||||||
self._attr_unique_id = SIA_UNIQUE_ID_FORMAT_ALARM.format(
|
ping_interval,
|
||||||
self._entry.entry_id, self._account, self._zone
|
entity_description,
|
||||||
|
unique_id,
|
||||||
|
name,
|
||||||
)
|
)
|
||||||
|
|
||||||
def update_state(self, sia_event: SIAEvent) -> None:
|
self._attr_state: StateType = None
|
||||||
"""Update the state of the alarm control panel."""
|
self._old_state: StateType = None
|
||||||
new_state = CODE_CONSEQUENCES.get(sia_event.code, None)
|
|
||||||
if new_state is not None:
|
|
||||||
_LOGGER.debug("New state will be %s", new_state)
|
|
||||||
if new_state == PREVIOUS_STATE:
|
|
||||||
new_state = self._old_state
|
|
||||||
self._attr_state, self._old_state = new_state, self._attr_state
|
|
||||||
|
|
||||||
def handle_last_state(self, last_state: State | None) -> None:
|
def handle_last_state(self, last_state: State | None) -> None:
|
||||||
"""Handle the last state."""
|
"""Handle the last state."""
|
||||||
|
@ -106,7 +143,13 @@ class SIAAlarmControlPanel(SIABaseEntity, AlarmControlPanelEntity):
|
||||||
if self.state == STATE_UNAVAILABLE:
|
if self.state == STATE_UNAVAILABLE:
|
||||||
self._attr_available = False
|
self._attr_available = False
|
||||||
|
|
||||||
@property
|
def update_state(self, sia_event: SIAEvent) -> bool:
|
||||||
def supported_features(self) -> int:
|
"""Update the state of the alarm control panel."""
|
||||||
"""Return the list of supported features."""
|
new_state = self.entity_description.code_consequences.get(sia_event.code)
|
||||||
return 0
|
if new_state is None:
|
||||||
|
return False
|
||||||
|
_LOGGER.debug("New state will be %s", new_state)
|
||||||
|
if new_state == PREVIOUS_STATE:
|
||||||
|
new_state = self._old_state
|
||||||
|
self._attr_state, self._old_state = new_state, self._attr_state
|
||||||
|
return True
|
||||||
|
|
|
@ -2,64 +2,148 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Iterable
|
from collections.abc import Iterable
|
||||||
|
from dataclasses import dataclass
|
||||||
import logging
|
import logging
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from pysiaalarm import SIAEvent
|
from pysiaalarm import SIAEvent
|
||||||
|
|
||||||
from homeassistant.components.binary_sensor import (
|
from homeassistant.components.binary_sensor import (
|
||||||
BinarySensorDeviceClass,
|
BinarySensorDeviceClass,
|
||||||
BinarySensorEntity,
|
BinarySensorEntity,
|
||||||
|
BinarySensorEntityDescription,
|
||||||
)
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE
|
from homeassistant.const import CONF_PORT, STATE_OFF, STATE_ON, STATE_UNAVAILABLE
|
||||||
from homeassistant.core import HomeAssistant, State
|
from homeassistant.core import HomeAssistant, State
|
||||||
|
from homeassistant.helpers.entity import EntityCategory
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
CONF_ACCOUNT,
|
CONF_ACCOUNT,
|
||||||
CONF_ACCOUNTS,
|
CONF_ACCOUNTS,
|
||||||
|
CONF_PING_INTERVAL,
|
||||||
CONF_ZONES,
|
CONF_ZONES,
|
||||||
|
KEY_MOISTURE,
|
||||||
|
KEY_POWER,
|
||||||
|
KEY_SMOKE,
|
||||||
SIA_HUB_ZONE,
|
SIA_HUB_ZONE,
|
||||||
|
SIA_NAME_FORMAT,
|
||||||
|
SIA_NAME_FORMAT_HUB,
|
||||||
SIA_UNIQUE_ID_FORMAT_BINARY,
|
SIA_UNIQUE_ID_FORMAT_BINARY,
|
||||||
)
|
)
|
||||||
from .sia_entity_base import SIABaseEntity
|
from .sia_entity_base import SIABaseEntity, SIAEntityDescription
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
POWER_CODE_CONSEQUENCES: dict[str, bool] = {
|
@dataclass
|
||||||
"AT": False,
|
class SIABinarySensorEntityDescription(
|
||||||
"AR": True,
|
BinarySensorEntityDescription,
|
||||||
}
|
SIAEntityDescription,
|
||||||
|
):
|
||||||
SMOKE_CODE_CONSEQUENCES: dict[str, bool] = {
|
"""Describes SIA sensor entity."""
|
||||||
"GA": True,
|
|
||||||
"GH": False,
|
|
||||||
"FA": True,
|
|
||||||
"FH": False,
|
|
||||||
"KA": True,
|
|
||||||
"KH": False,
|
|
||||||
}
|
|
||||||
|
|
||||||
MOISTURE_CODE_CONSEQUENCES: dict[str, bool] = {
|
|
||||||
"WA": True,
|
|
||||||
"WH": False,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def generate_binary_sensors(entry) -> Iterable[SIABinarySensorBase]:
|
ENTITY_DESCRIPTION_POWER = SIABinarySensorEntityDescription(
|
||||||
|
key=KEY_POWER,
|
||||||
|
device_class=BinarySensorDeviceClass.POWER,
|
||||||
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
|
code_consequences={
|
||||||
|
"AT": False,
|
||||||
|
"AR": True,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
ENTITY_DESCRIPTION_SMOKE = SIABinarySensorEntityDescription(
|
||||||
|
key=KEY_SMOKE,
|
||||||
|
device_class=BinarySensorDeviceClass.SMOKE,
|
||||||
|
code_consequences={
|
||||||
|
"GA": True,
|
||||||
|
"GH": False,
|
||||||
|
"FA": True,
|
||||||
|
"FH": False,
|
||||||
|
"KA": True,
|
||||||
|
"KH": False,
|
||||||
|
},
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
ENTITY_DESCRIPTION_MOISTURE = SIABinarySensorEntityDescription(
|
||||||
|
key=KEY_MOISTURE,
|
||||||
|
device_class=BinarySensorDeviceClass.MOISTURE,
|
||||||
|
code_consequences={
|
||||||
|
"WA": True,
|
||||||
|
"WH": False,
|
||||||
|
},
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_binary_sensors(entry) -> Iterable[SIABinarySensor]:
|
||||||
"""Generate binary sensors.
|
"""Generate binary sensors.
|
||||||
|
|
||||||
For each Account there is one power sensor with zone == 0.
|
For each Account there is one power sensor with zone == 0.
|
||||||
For each Zone in each Account there is one smoke and one moisture sensor.
|
For each Zone in each Account there is one smoke and one moisture sensor.
|
||||||
"""
|
"""
|
||||||
for account in entry.data[CONF_ACCOUNTS]:
|
for account_data in entry.data[CONF_ACCOUNTS]:
|
||||||
yield SIABinarySensorPower(entry, account)
|
yield SIABinarySensor(
|
||||||
zones = entry.options[CONF_ACCOUNTS][account[CONF_ACCOUNT]][CONF_ZONES]
|
port=entry.data[CONF_PORT],
|
||||||
|
account=account_data[CONF_ACCOUNT],
|
||||||
|
zone=SIA_HUB_ZONE,
|
||||||
|
ping_interval=account_data[CONF_PING_INTERVAL],
|
||||||
|
entity_description=ENTITY_DESCRIPTION_POWER,
|
||||||
|
unique_id=SIA_UNIQUE_ID_FORMAT_BINARY.format(
|
||||||
|
entry.entry_id,
|
||||||
|
account_data[CONF_ACCOUNT],
|
||||||
|
SIA_HUB_ZONE,
|
||||||
|
ENTITY_DESCRIPTION_POWER.device_class,
|
||||||
|
),
|
||||||
|
name=SIA_NAME_FORMAT_HUB.format(
|
||||||
|
entry.data[CONF_PORT],
|
||||||
|
account_data[CONF_ACCOUNT],
|
||||||
|
ENTITY_DESCRIPTION_POWER.device_class,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
zones = entry.options[CONF_ACCOUNTS][account_data[CONF_ACCOUNT]][CONF_ZONES]
|
||||||
for zone in range(1, zones + 1):
|
for zone in range(1, zones + 1):
|
||||||
yield SIABinarySensorSmoke(entry, account, zone)
|
yield SIABinarySensor(
|
||||||
yield SIABinarySensorMoisture(entry, account, zone)
|
port=entry.data[CONF_PORT],
|
||||||
|
account=account_data[CONF_ACCOUNT],
|
||||||
|
zone=zone,
|
||||||
|
ping_interval=account_data[CONF_PING_INTERVAL],
|
||||||
|
entity_description=ENTITY_DESCRIPTION_SMOKE,
|
||||||
|
unique_id=SIA_UNIQUE_ID_FORMAT_BINARY.format(
|
||||||
|
entry.entry_id,
|
||||||
|
account_data[CONF_ACCOUNT],
|
||||||
|
zone,
|
||||||
|
ENTITY_DESCRIPTION_SMOKE.device_class,
|
||||||
|
),
|
||||||
|
name=SIA_NAME_FORMAT.format(
|
||||||
|
entry.data[CONF_PORT],
|
||||||
|
account_data[CONF_ACCOUNT],
|
||||||
|
zone,
|
||||||
|
ENTITY_DESCRIPTION_SMOKE.device_class,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
yield SIABinarySensor(
|
||||||
|
port=entry.data[CONF_PORT],
|
||||||
|
account=account_data[CONF_ACCOUNT],
|
||||||
|
zone=zone,
|
||||||
|
ping_interval=account_data[CONF_PING_INTERVAL],
|
||||||
|
entity_description=ENTITY_DESCRIPTION_MOISTURE,
|
||||||
|
unique_id=SIA_UNIQUE_ID_FORMAT_BINARY.format(
|
||||||
|
entry.entry_id,
|
||||||
|
account_data[CONF_ACCOUNT],
|
||||||
|
zone,
|
||||||
|
ENTITY_DESCRIPTION_MOISTURE.device_class,
|
||||||
|
),
|
||||||
|
name=SIA_NAME_FORMAT.format(
|
||||||
|
entry.data[CONF_PORT],
|
||||||
|
account_data[CONF_ACCOUNT],
|
||||||
|
zone,
|
||||||
|
ENTITY_DESCRIPTION_MOISTURE.device_class,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
|
@ -71,22 +155,10 @@ async def async_setup_entry(
|
||||||
async_add_entities(generate_binary_sensors(entry))
|
async_add_entities(generate_binary_sensors(entry))
|
||||||
|
|
||||||
|
|
||||||
class SIABinarySensorBase(SIABaseEntity, BinarySensorEntity):
|
class SIABinarySensor(SIABaseEntity, BinarySensorEntity):
|
||||||
"""Class for SIA Binary Sensors."""
|
"""Class for SIA Binary Sensors."""
|
||||||
|
|
||||||
def __init__(
|
entity_description: SIABinarySensorEntityDescription
|
||||||
self,
|
|
||||||
entry: ConfigEntry,
|
|
||||||
account_data: dict[str, Any],
|
|
||||||
zone: int,
|
|
||||||
device_class: BinarySensorDeviceClass,
|
|
||||||
) -> None:
|
|
||||||
"""Initialize a base binary sensor."""
|
|
||||||
super().__init__(entry, account_data, zone)
|
|
||||||
self._attr_device_class = device_class
|
|
||||||
self._attr_unique_id = SIA_UNIQUE_ID_FORMAT_BINARY.format(
|
|
||||||
self._entry.entry_id, self._account, self._zone, self._attr_device_class
|
|
||||||
)
|
|
||||||
|
|
||||||
def handle_last_state(self, last_state: State | None) -> None:
|
def handle_last_state(self, last_state: State | None) -> None:
|
||||||
"""Handle the last state."""
|
"""Handle the last state."""
|
||||||
|
@ -98,66 +170,11 @@ class SIABinarySensorBase(SIABaseEntity, BinarySensorEntity):
|
||||||
elif last_state.state == STATE_UNAVAILABLE:
|
elif last_state.state == STATE_UNAVAILABLE:
|
||||||
self._attr_available = False
|
self._attr_available = False
|
||||||
|
|
||||||
|
def update_state(self, sia_event: SIAEvent) -> bool:
|
||||||
class SIABinarySensorMoisture(SIABinarySensorBase):
|
|
||||||
"""Class for Moisture Binary Sensors."""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
entry: ConfigEntry,
|
|
||||||
account_data: dict[str, Any],
|
|
||||||
zone: int,
|
|
||||||
) -> None:
|
|
||||||
"""Initialize a Moisture binary sensor."""
|
|
||||||
super().__init__(entry, account_data, zone, BinarySensorDeviceClass.MOISTURE)
|
|
||||||
self._attr_entity_registry_enabled_default = False
|
|
||||||
|
|
||||||
def update_state(self, sia_event: SIAEvent) -> None:
|
|
||||||
"""Update the state of the binary sensor."""
|
"""Update the state of the binary sensor."""
|
||||||
new_state = MOISTURE_CODE_CONSEQUENCES.get(sia_event.code, None)
|
new_state = self.entity_description.code_consequences.get(sia_event.code)
|
||||||
if new_state is not None:
|
if new_state is None:
|
||||||
_LOGGER.debug("New state will be %s", new_state)
|
return False
|
||||||
self._attr_is_on = new_state
|
_LOGGER.debug("New state will be %s", new_state)
|
||||||
|
self._attr_is_on = bool(new_state)
|
||||||
|
return True
|
||||||
class SIABinarySensorSmoke(SIABinarySensorBase):
|
|
||||||
"""Class for Smoke Binary Sensors."""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
entry: ConfigEntry,
|
|
||||||
account_data: dict[str, Any],
|
|
||||||
zone: int,
|
|
||||||
) -> None:
|
|
||||||
"""Initialize a Smoke binary sensor."""
|
|
||||||
super().__init__(entry, account_data, zone, BinarySensorDeviceClass.SMOKE)
|
|
||||||
self._attr_entity_registry_enabled_default = False
|
|
||||||
|
|
||||||
def update_state(self, sia_event: SIAEvent) -> None:
|
|
||||||
"""Update the state of the binary sensor."""
|
|
||||||
new_state = SMOKE_CODE_CONSEQUENCES.get(sia_event.code, None)
|
|
||||||
if new_state is not None:
|
|
||||||
_LOGGER.debug("New state will be %s", new_state)
|
|
||||||
self._attr_is_on = new_state
|
|
||||||
|
|
||||||
|
|
||||||
class SIABinarySensorPower(SIABinarySensorBase):
|
|
||||||
"""Class for Power Binary Sensors."""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
entry: ConfigEntry,
|
|
||||||
account_data: dict[str, Any],
|
|
||||||
) -> None:
|
|
||||||
"""Initialize a Power binary sensor."""
|
|
||||||
super().__init__(
|
|
||||||
entry, account_data, SIA_HUB_ZONE, BinarySensorDeviceClass.POWER
|
|
||||||
)
|
|
||||||
self._attr_entity_registry_enabled_default = True
|
|
||||||
|
|
||||||
def update_state(self, sia_event: SIAEvent) -> None:
|
|
||||||
"""Update the state of the binary sensor."""
|
|
||||||
new_state = POWER_CODE_CONSEQUENCES.get(sia_event.code, None)
|
|
||||||
if new_state is not None:
|
|
||||||
_LOGGER.debug("New state will be %s", new_state)
|
|
||||||
self._attr_is_on = new_state
|
|
||||||
|
|
|
@ -35,7 +35,6 @@ from .hub import SIAHub
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
HUB_SCHEMA = vol.Schema(
|
HUB_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Required(CONF_PORT): int,
|
vol.Required(CONF_PORT): int,
|
||||||
|
|
|
@ -1,28 +1,40 @@
|
||||||
"""Constants for the sia integration."""
|
"""Constants for the sia integration."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Final
|
||||||
|
|
||||||
from homeassistant.const import Platform
|
from homeassistant.const import Platform
|
||||||
|
|
||||||
PLATFORMS = [Platform.ALARM_CONTROL_PANEL, Platform.BINARY_SENSOR]
|
PLATFORMS: Final = [Platform.ALARM_CONTROL_PANEL, Platform.BINARY_SENSOR]
|
||||||
|
|
||||||
DOMAIN = "sia"
|
DOMAIN: Final = "sia"
|
||||||
|
|
||||||
ATTR_CODE = "last_code"
|
ATTR_CODE: Final = "last_code"
|
||||||
ATTR_ZONE = "last_zone"
|
ATTR_ZONE: Final = "last_zone"
|
||||||
ATTR_MESSAGE = "last_message"
|
ATTR_MESSAGE: Final = "last_message"
|
||||||
ATTR_ID = "last_id"
|
ATTR_ID: Final = "last_id"
|
||||||
ATTR_TIMESTAMP = "last_timestamp"
|
ATTR_TIMESTAMP: Final = "last_timestamp"
|
||||||
|
|
||||||
TITLE = "SIA Alarm on port {}"
|
TITLE: Final = "SIA Alarm on port {}"
|
||||||
CONF_ACCOUNT = "account"
|
CONF_ACCOUNT: Final = "account"
|
||||||
CONF_ACCOUNTS = "accounts"
|
CONF_ACCOUNTS: Final = "accounts"
|
||||||
CONF_ADDITIONAL_ACCOUNTS = "additional_account"
|
CONF_ADDITIONAL_ACCOUNTS: Final = "additional_account"
|
||||||
CONF_ENCRYPTION_KEY = "encryption_key"
|
CONF_ENCRYPTION_KEY: Final = "encryption_key"
|
||||||
CONF_IGNORE_TIMESTAMPS = "ignore_timestamps"
|
CONF_IGNORE_TIMESTAMPS: Final = "ignore_timestamps"
|
||||||
CONF_PING_INTERVAL = "ping_interval"
|
CONF_PING_INTERVAL: Final = "ping_interval"
|
||||||
CONF_ZONES = "zones"
|
CONF_ZONES: Final = "zones"
|
||||||
|
|
||||||
SIA_NAME_FORMAT = "{} - {} - zone {} - {}"
|
SIA_NAME_FORMAT: Final = "{} - {} - zone {} - {}"
|
||||||
SIA_UNIQUE_ID_FORMAT_ALARM = "{}_{}_{}"
|
SIA_NAME_FORMAT_HUB: Final = "{} - {} - {}"
|
||||||
SIA_UNIQUE_ID_FORMAT_BINARY = "{}_{}_{}_{}"
|
SIA_UNIQUE_ID_FORMAT_ALARM: Final = "{}_{}_{}"
|
||||||
SIA_HUB_ZONE = 0
|
SIA_UNIQUE_ID_FORMAT_BINARY: Final = "{}_{}_{}_{}"
|
||||||
|
SIA_UNIQUE_ID_FORMAT_HUB: Final = "{}_{}_{}"
|
||||||
|
SIA_HUB_ZONE: Final = 0
|
||||||
|
SIA_EVENT: Final = "sia_event_{}_{}"
|
||||||
|
|
||||||
SIA_EVENT = "sia_event_{}_{}"
|
KEY_ALARM: Final = "alarm_control_panel"
|
||||||
|
KEY_SMOKE: Final = "smoke"
|
||||||
|
KEY_MOISTURE: Final = "moisture"
|
||||||
|
KEY_POWER: Final = "power"
|
||||||
|
|
||||||
|
PREVIOUS_STATE: Final = "previous_state"
|
||||||
|
|
|
@ -27,7 +27,6 @@ from .utils import get_event_data_from_sia_event
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_TIMEBAND = (80, 40)
|
DEFAULT_TIMEBAND = (80, 40)
|
||||||
IGNORED_TIMEBAND = (3600, 1800)
|
IGNORED_TIMEBAND = (3600, 1800)
|
||||||
|
|
||||||
|
|
|
@ -2,52 +2,68 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from abc import abstractmethod
|
from abc import abstractmethod
|
||||||
|
from dataclasses import dataclass
|
||||||
import logging
|
import logging
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from pysiaalarm import SIAEvent
|
from pysiaalarm import SIAEvent
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
|
||||||
from homeassistant.const import CONF_PORT
|
|
||||||
from homeassistant.core import CALLBACK_TYPE, State, callback
|
from homeassistant.core import CALLBACK_TYPE, State, callback
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
from homeassistant.helpers.entity import DeviceInfo
|
from homeassistant.helpers.entity import DeviceInfo, EntityDescription
|
||||||
from homeassistant.helpers.event import async_call_later
|
from homeassistant.helpers.event import async_call_later
|
||||||
from homeassistant.helpers.restore_state import RestoreEntity
|
from homeassistant.helpers.restore_state import RestoreEntity
|
||||||
|
from homeassistant.helpers.typing import StateType
|
||||||
|
|
||||||
from .const import CONF_ACCOUNT, CONF_PING_INTERVAL, DOMAIN, SIA_EVENT, SIA_NAME_FORMAT
|
from .const import DOMAIN, SIA_EVENT, SIA_HUB_ZONE
|
||||||
from .utils import get_attr_from_sia_event, get_unavailability_interval
|
from .utils import get_attr_from_sia_event, get_unavailability_interval
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class SIARequiredKeysMixin:
|
||||||
|
"""Required keys for SIA entities."""
|
||||||
|
|
||||||
|
code_consequences: dict[str, StateType | bool]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class SIAEntityDescription(EntityDescription, SIARequiredKeysMixin):
|
||||||
|
"""Entity Description for SIA entities."""
|
||||||
|
|
||||||
|
|
||||||
class SIABaseEntity(RestoreEntity):
|
class SIABaseEntity(RestoreEntity):
|
||||||
"""Base class for SIA entities."""
|
"""Base class for SIA entities."""
|
||||||
|
|
||||||
|
entity_description: SIAEntityDescription
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
entry: ConfigEntry,
|
port: int,
|
||||||
account_data: dict[str, Any],
|
account: str,
|
||||||
zone: int,
|
zone: int | None,
|
||||||
device_class: str | None = None,
|
ping_interval: int,
|
||||||
|
entity_description: SIAEntityDescription,
|
||||||
|
unique_id: str,
|
||||||
|
name: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Create SIABaseEntity object."""
|
"""Create SIABaseEntity object."""
|
||||||
self._entry: ConfigEntry = entry
|
self.port = port
|
||||||
self._account_data: dict[str, Any] = account_data
|
self.account = account
|
||||||
self._zone: int = zone
|
self.zone = zone
|
||||||
self._attr_device_class = device_class
|
self.ping_interval = ping_interval
|
||||||
|
self.entity_description = entity_description
|
||||||
self._port: int = self._entry.data[CONF_PORT]
|
self._attr_unique_id = unique_id
|
||||||
self._account: str = self._account_data[CONF_ACCOUNT]
|
self._attr_name = name
|
||||||
self._ping_interval: int = self._account_data[CONF_PING_INTERVAL]
|
self._attr_device_info = DeviceInfo(
|
||||||
|
name=name,
|
||||||
|
identifiers={(DOMAIN, unique_id)},
|
||||||
|
via_device=(DOMAIN, f"{port}_{account}"),
|
||||||
|
)
|
||||||
|
|
||||||
self._cancel_availability_cb: CALLBACK_TYPE | None = None
|
self._cancel_availability_cb: CALLBACK_TYPE | None = None
|
||||||
|
|
||||||
self._attr_extra_state_attributes = {}
|
self._attr_extra_state_attributes = {}
|
||||||
self._attr_should_poll = False
|
self._attr_should_poll = False
|
||||||
self._attr_name = SIA_NAME_FORMAT.format(
|
|
||||||
self._port, self._account, self._zone, self._attr_device_class
|
|
||||||
)
|
|
||||||
|
|
||||||
async def async_added_to_hass(self) -> None:
|
async def async_added_to_hass(self) -> None:
|
||||||
"""Run when entity about to be added to hass.
|
"""Run when entity about to be added to hass.
|
||||||
|
@ -61,7 +77,7 @@ class SIABaseEntity(RestoreEntity):
|
||||||
self.async_on_remove(
|
self.async_on_remove(
|
||||||
async_dispatcher_connect(
|
async_dispatcher_connect(
|
||||||
self.hass,
|
self.hass,
|
||||||
SIA_EVENT.format(self._port, self._account),
|
SIA_EVENT.format(self.port, self.account),
|
||||||
self.async_handle_event,
|
self.async_handle_event,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -83,19 +99,18 @@ class SIABaseEntity(RestoreEntity):
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_handle_event(self, sia_event: SIAEvent) -> None:
|
def async_handle_event(self, sia_event: SIAEvent) -> None:
|
||||||
"""Listen to dispatcher events for this port and account and update state and attributes.
|
"""Listen to dispatcher 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.
|
|
||||||
"""
|
|
||||||
_LOGGER.debug("Received event: %s", sia_event)
|
_LOGGER.debug("Received event: %s", sia_event)
|
||||||
if int(sia_event.ri) == self._zone:
|
if int(sia_event.ri) not in (self.zone, SIA_HUB_ZONE):
|
||||||
self._attr_extra_state_attributes.update(get_attr_from_sia_event(sia_event))
|
return
|
||||||
self.update_state(sia_event)
|
self._attr_extra_state_attributes.update(get_attr_from_sia_event(sia_event))
|
||||||
self.async_reset_availability_cb()
|
state_changed = self.update_state(sia_event)
|
||||||
|
if state_changed:
|
||||||
|
self.async_reset_availability_cb()
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def update_state(self, sia_event: SIAEvent) -> None:
|
def update_state(self, sia_event: SIAEvent) -> bool:
|
||||||
"""Do the entity specific state updates."""
|
"""Do the entity specific state updates."""
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
|
@ -110,7 +125,7 @@ class SIABaseEntity(RestoreEntity):
|
||||||
"""Create a availability cb and return the callback."""
|
"""Create a availability cb and return the callback."""
|
||||||
self._cancel_availability_cb = async_call_later(
|
self._cancel_availability_cb = async_call_later(
|
||||||
self.hass,
|
self.hass,
|
||||||
get_unavailability_interval(self._ping_interval),
|
get_unavailability_interval(self.ping_interval),
|
||||||
self.async_set_unavailable,
|
self.async_set_unavailable,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -119,14 +134,3 @@ class SIABaseEntity(RestoreEntity):
|
||||||
"""Set unavailable."""
|
"""Set unavailable."""
|
||||||
self._attr_available = False
|
self._attr_available = False
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
@property
|
|
||||||
def device_info(self) -> DeviceInfo:
|
|
||||||
"""Return the device_info."""
|
|
||||||
assert self._attr_name is not None
|
|
||||||
assert self.unique_id is not None
|
|
||||||
return DeviceInfo(
|
|
||||||
name=self._attr_name,
|
|
||||||
identifiers={(DOMAIN, self.unique_id)},
|
|
||||||
via_device=(DOMAIN, f"{self._port}_{self._account}"),
|
|
||||||
)
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue