Add support for real-time data from SimpliSafe (#31424)
* Add support for real-time data from SimpliSafe * Updated requirements * Linting * Ensure dispatcher topic contains the domain * Don't bother with a partial * Websovket dataclass and other code review * Ensure initial_event_to_use works with error * Don't inline methods * Don't abuse loop variable * Simplify initial event retrieval * Add connection lost and restored events * Revert "Add connection lost and restored events" This reverts commite7ffe05938
. * Make _on_disconnect a static method * Code review comments * Allow entities to opt out of REST and/or websocket API updates * Revert "Allow entities to opt out of REST and/or websocket API updates" This reverts commit1989f2e00e
. * Code review comments * Fix issues with events not triggering correct entities * Bug fixes
This commit is contained in:
parent
9e7185c676
commit
f091e0412f
7 changed files with 451 additions and 137 deletions
|
@ -2,9 +2,21 @@
|
|||
import logging
|
||||
import re
|
||||
|
||||
from simplipy.entity import EntityTypes
|
||||
from simplipy.errors import SimplipyError
|
||||
from simplipy.system import SystemStates
|
||||
from simplipy.system.v3 import VOLUME_HIGH, VOLUME_LOW, VOLUME_MEDIUM, VOLUME_OFF
|
||||
from simplipy.websocket import (
|
||||
EVENT_ALARM_CANCELED,
|
||||
EVENT_ALARM_TRIGGERED,
|
||||
EVENT_ARMED_AWAY,
|
||||
EVENT_ARMED_AWAY_BY_KEYPAD,
|
||||
EVENT_ARMED_AWAY_BY_REMOTE,
|
||||
EVENT_ARMED_HOME,
|
||||
EVENT_AWAY_EXIT_DELAY_BY_KEYPAD,
|
||||
EVENT_AWAY_EXIT_DELAY_BY_REMOTE,
|
||||
EVENT_DISARMED_BY_MASTER_PIN,
|
||||
EVENT_DISARMED_BY_REMOTE,
|
||||
EVENT_HOME_EXIT_DELAY,
|
||||
)
|
||||
|
||||
from homeassistant.components.alarm_control_panel import (
|
||||
FORMAT_NUMBER,
|
||||
|
@ -23,40 +35,33 @@ from homeassistant.const import (
|
|||
STATE_ALARM_DISARMED,
|
||||
STATE_ALARM_TRIGGERED,
|
||||
)
|
||||
from homeassistant.util.dt import utc_from_timestamp
|
||||
from homeassistant.core import callback
|
||||
|
||||
from . import SimpliSafeEntity
|
||||
from .const import DATA_CLIENT, DOMAIN
|
||||
from .const import (
|
||||
ATTR_ALARM_DURATION,
|
||||
ATTR_ALARM_VOLUME,
|
||||
ATTR_CHIME_VOLUME,
|
||||
ATTR_ENTRY_DELAY_AWAY,
|
||||
ATTR_ENTRY_DELAY_HOME,
|
||||
ATTR_EXIT_DELAY_AWAY,
|
||||
ATTR_EXIT_DELAY_HOME,
|
||||
ATTR_LIGHT,
|
||||
ATTR_VOICE_PROMPT_VOLUME,
|
||||
DATA_CLIENT,
|
||||
DOMAIN,
|
||||
VOLUME_STRING_MAP,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
ATTR_ALARM_DURATION = "alarm_duration"
|
||||
ATTR_ALARM_VOLUME = "alarm_volume"
|
||||
ATTR_BATTERY_BACKUP_POWER_LEVEL = "battery_backup_power_level"
|
||||
ATTR_CHIME_VOLUME = "chime_volume"
|
||||
ATTR_ENTRY_DELAY_AWAY = "entry_delay_away"
|
||||
ATTR_ENTRY_DELAY_HOME = "entry_delay_home"
|
||||
ATTR_EXIT_DELAY_AWAY = "exit_delay_away"
|
||||
ATTR_EXIT_DELAY_HOME = "exit_delay_home"
|
||||
ATTR_GSM_STRENGTH = "gsm_strength"
|
||||
ATTR_LAST_EVENT_INFO = "last_event_info"
|
||||
ATTR_LAST_EVENT_SENSOR_NAME = "last_event_sensor_name"
|
||||
ATTR_LAST_EVENT_SENSOR_TYPE = "last_event_sensor_type"
|
||||
ATTR_LAST_EVENT_TIMESTAMP = "last_event_timestamp"
|
||||
ATTR_LAST_EVENT_TYPE = "last_event_type"
|
||||
ATTR_LIGHT = "light"
|
||||
ATTR_PIN_NAME = "pin_name"
|
||||
ATTR_RF_JAMMING = "rf_jamming"
|
||||
ATTR_VOICE_PROMPT_VOLUME = "voice_prompt_volume"
|
||||
ATTR_WALL_POWER_LEVEL = "wall_power_level"
|
||||
ATTR_WIFI_STRENGTH = "wifi_strength"
|
||||
|
||||
VOLUME_STRING_MAP = {
|
||||
VOLUME_HIGH: "high",
|
||||
VOLUME_LOW: "low",
|
||||
VOLUME_MEDIUM: "medium",
|
||||
VOLUME_OFF: "off",
|
||||
}
|
||||
|
||||
|
||||
async def async_setup_entry(hass, entry, async_add_entities):
|
||||
"""Set up a SimpliSafe alarm control panel based on a config entry."""
|
||||
|
@ -75,33 +80,42 @@ class SimpliSafeAlarm(SimpliSafeEntity, AlarmControlPanel):
|
|||
|
||||
def __init__(self, simplisafe, system, code):
|
||||
"""Initialize the SimpliSafe alarm."""
|
||||
super().__init__(system, "Alarm Control Panel")
|
||||
super().__init__(simplisafe, system, "Alarm Control Panel")
|
||||
self._changed_by = None
|
||||
self._code = code
|
||||
self._simplisafe = simplisafe
|
||||
self._state = None
|
||||
self._last_event = None
|
||||
|
||||
if self._system.version == 3:
|
||||
self._attrs.update(
|
||||
{
|
||||
ATTR_ALARM_DURATION: self._system.alarm_duration,
|
||||
ATTR_ALARM_VOLUME: VOLUME_STRING_MAP[self._system.alarm_volume],
|
||||
ATTR_BATTERY_BACKUP_POWER_LEVEL: self._system.battery_backup_power_level,
|
||||
ATTR_CHIME_VOLUME: VOLUME_STRING_MAP[self._system.chime_volume],
|
||||
ATTR_ENTRY_DELAY_AWAY: self._system.entry_delay_away,
|
||||
ATTR_ENTRY_DELAY_HOME: self._system.entry_delay_home,
|
||||
ATTR_EXIT_DELAY_AWAY: self._system.exit_delay_away,
|
||||
ATTR_EXIT_DELAY_HOME: self._system.exit_delay_home,
|
||||
ATTR_GSM_STRENGTH: self._system.gsm_strength,
|
||||
ATTR_LIGHT: self._system.light,
|
||||
ATTR_RF_JAMMING: self._system.rf_jamming,
|
||||
ATTR_VOICE_PROMPT_VOLUME: VOLUME_STRING_MAP[
|
||||
self._system.voice_prompt_volume
|
||||
],
|
||||
ATTR_WALL_POWER_LEVEL: self._system.wall_power_level,
|
||||
ATTR_WIFI_STRENGTH: self._system.wifi_strength,
|
||||
}
|
||||
)
|
||||
if system.alarm_going_off:
|
||||
self._state = STATE_ALARM_TRIGGERED
|
||||
elif system.state == SystemStates.away:
|
||||
self._state = STATE_ALARM_ARMED_AWAY
|
||||
elif system.state in (
|
||||
SystemStates.away_count,
|
||||
SystemStates.exit_delay,
|
||||
SystemStates.home_count,
|
||||
):
|
||||
self._state = STATE_ALARM_ARMING
|
||||
elif system.state == SystemStates.home:
|
||||
self._state = STATE_ALARM_ARMED_HOME
|
||||
elif system.state == SystemStates.off:
|
||||
self._state = STATE_ALARM_DISARMED
|
||||
else:
|
||||
self._state = None
|
||||
|
||||
for event_type in (
|
||||
EVENT_ALARM_CANCELED,
|
||||
EVENT_ALARM_TRIGGERED,
|
||||
EVENT_ARMED_AWAY,
|
||||
EVENT_ARMED_AWAY_BY_KEYPAD,
|
||||
EVENT_ARMED_AWAY_BY_REMOTE,
|
||||
EVENT_ARMED_HOME,
|
||||
EVENT_AWAY_EXIT_DELAY_BY_KEYPAD,
|
||||
EVENT_AWAY_EXIT_DELAY_BY_REMOTE,
|
||||
EVENT_DISARMED_BY_MASTER_PIN,
|
||||
EVENT_DISARMED_BY_REMOTE,
|
||||
EVENT_HOME_EXIT_DELAY,
|
||||
):
|
||||
self.websocket_events_to_listen_for.append(event_type)
|
||||
|
||||
@property
|
||||
def changed_by(self):
|
||||
|
@ -139,71 +153,96 @@ class SimpliSafeAlarm(SimpliSafeEntity, AlarmControlPanel):
|
|||
if not self._validate_code(code, "disarming"):
|
||||
return
|
||||
|
||||
await self._system.set_off()
|
||||
try:
|
||||
await self._system.set_off()
|
||||
except SimplipyError as err:
|
||||
_LOGGER.error('Error while disarming "%s": %s', self._system.name, err)
|
||||
return
|
||||
|
||||
self._state = STATE_ALARM_DISARMED
|
||||
|
||||
async def async_alarm_arm_home(self, code=None):
|
||||
"""Send arm home command."""
|
||||
if not self._validate_code(code, "arming home"):
|
||||
return
|
||||
|
||||
await self._system.set_home()
|
||||
try:
|
||||
await self._system.set_home()
|
||||
except SimplipyError as err:
|
||||
_LOGGER.error('Error while arming "%s" (home): %s', self._system.name, err)
|
||||
return
|
||||
|
||||
self._state = STATE_ALARM_ARMED_HOME
|
||||
|
||||
async def async_alarm_arm_away(self, code=None):
|
||||
"""Send arm away command."""
|
||||
if not self._validate_code(code, "arming away"):
|
||||
return
|
||||
|
||||
await self._system.set_away()
|
||||
try:
|
||||
await self._system.set_away()
|
||||
except SimplipyError as err:
|
||||
_LOGGER.error('Error while arming "%s" (away): %s', self._system.name, err)
|
||||
return
|
||||
|
||||
async def async_update(self):
|
||||
"""Update alarm status."""
|
||||
last_event = self._simplisafe.last_event_data[self._system.system_id]
|
||||
|
||||
if last_event.get("pinName"):
|
||||
self._changed_by = last_event["pinName"]
|
||||
self._state = STATE_ALARM_ARMING
|
||||
|
||||
@callback
|
||||
def async_update_from_rest_api(self):
|
||||
"""Update the entity with the provided REST API data."""
|
||||
if self._system.state == SystemStates.error:
|
||||
self._online = False
|
||||
return
|
||||
|
||||
self._online = True
|
||||
|
||||
if self._system.alarm_going_off:
|
||||
if self._system.version == 3:
|
||||
self._attrs.update(
|
||||
{
|
||||
ATTR_ALARM_DURATION: self._system.alarm_duration,
|
||||
ATTR_ALARM_VOLUME: VOLUME_STRING_MAP[self._system.alarm_volume],
|
||||
ATTR_BATTERY_BACKUP_POWER_LEVEL: self._system.battery_backup_power_level,
|
||||
ATTR_CHIME_VOLUME: VOLUME_STRING_MAP[self._system.chime_volume],
|
||||
ATTR_ENTRY_DELAY_AWAY: self._system.entry_delay_away,
|
||||
ATTR_ENTRY_DELAY_HOME: self._system.entry_delay_home,
|
||||
ATTR_EXIT_DELAY_AWAY: self._system.exit_delay_away,
|
||||
ATTR_EXIT_DELAY_HOME: self._system.exit_delay_home,
|
||||
ATTR_GSM_STRENGTH: self._system.gsm_strength,
|
||||
ATTR_LIGHT: self._system.light,
|
||||
ATTR_RF_JAMMING: self._system.rf_jamming,
|
||||
ATTR_VOICE_PROMPT_VOLUME: VOLUME_STRING_MAP[
|
||||
self._system.voice_prompt_volume
|
||||
],
|
||||
ATTR_WALL_POWER_LEVEL: self._system.wall_power_level,
|
||||
ATTR_WIFI_STRENGTH: self._system.wifi_strength,
|
||||
}
|
||||
)
|
||||
|
||||
@callback
|
||||
def async_update_from_websocket_event(self, event):
|
||||
"""Update the entity with the provided websocket API event data."""
|
||||
if event.event_type in (
|
||||
EVENT_ALARM_CANCELED,
|
||||
EVENT_DISARMED_BY_MASTER_PIN,
|
||||
EVENT_DISARMED_BY_REMOTE,
|
||||
):
|
||||
self._state = STATE_ALARM_DISARMED
|
||||
elif event.event_type == EVENT_ALARM_TRIGGERED:
|
||||
self._state = STATE_ALARM_TRIGGERED
|
||||
elif self._system.state == SystemStates.away:
|
||||
elif event.event_type in (
|
||||
EVENT_ARMED_AWAY,
|
||||
EVENT_ARMED_AWAY_BY_KEYPAD,
|
||||
EVENT_ARMED_AWAY_BY_REMOTE,
|
||||
):
|
||||
self._state = STATE_ALARM_ARMED_AWAY
|
||||
elif self._system.state in (
|
||||
SystemStates.away_count,
|
||||
SystemStates.exit_delay,
|
||||
SystemStates.home_count,
|
||||
elif event.event_type == EVENT_ARMED_HOME:
|
||||
self._state = STATE_ALARM_ARMED_HOME
|
||||
elif event.event_type in (
|
||||
EVENT_AWAY_EXIT_DELAY_BY_KEYPAD,
|
||||
EVENT_AWAY_EXIT_DELAY_BY_REMOTE,
|
||||
EVENT_HOME_EXIT_DELAY,
|
||||
):
|
||||
self._state = STATE_ALARM_ARMING
|
||||
elif self._system.state == SystemStates.home:
|
||||
self._state = STATE_ALARM_ARMED_HOME
|
||||
elif self._system.state == SystemStates.off:
|
||||
self._state = STATE_ALARM_DISARMED
|
||||
else:
|
||||
self._state = None
|
||||
|
||||
try:
|
||||
last_event_sensor_type = EntityTypes(last_event["sensorType"]).name
|
||||
except ValueError:
|
||||
_LOGGER.warning(
|
||||
'Encountered unknown entity type: %s ("%s"). Please report it at'
|
||||
"https://github.com/home-assistant/home-assistant/issues.",
|
||||
last_event["sensorType"],
|
||||
last_event["sensorName"],
|
||||
)
|
||||
last_event_sensor_type = None
|
||||
|
||||
self._attrs.update(
|
||||
{
|
||||
ATTR_LAST_EVENT_INFO: last_event["info"],
|
||||
ATTR_LAST_EVENT_SENSOR_NAME: last_event["sensorName"],
|
||||
ATTR_LAST_EVENT_SENSOR_TYPE: last_event_sensor_type,
|
||||
ATTR_LAST_EVENT_TIMESTAMP: utc_from_timestamp(
|
||||
last_event["eventTimestamp"]
|
||||
),
|
||||
ATTR_LAST_EVENT_TYPE: last_event["eventType"],
|
||||
}
|
||||
)
|
||||
self._changed_by = event.changed_by
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue