Skip to content

Commit

Permalink
Merge pull request #38 from rlippmann/0.3.2
Browse files Browse the repository at this point in the history
0.3.2
  • Loading branch information
rlippmann authored Oct 8, 2023
2 parents 793e327 + fde9b53 commit a4363c0
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 14 deletions.
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
## 0.3.2 (2023-10-08)

Alarm control panel updates:
* update alarm capabilities based upon existing state of alarm
* disable setting alarm to existing state
* add arming/disarming icons
* add assumed state
* remove site id from attributes
* raise HomeAssistant exception on alarm set failure
* write ha state even if alarm action fails

## 0.3.1 (2023-10-07)

* fix typo in manifest preventing install

## 0.3.0 (2023-10-07)

* bump pyadtpulse to 1.1.2
Expand Down
13 changes: 13 additions & 0 deletions custom_components/adtpulse/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,11 @@
ADT_DEFAULT_KEEPALIVE_INTERVAL,
ADT_DEFAULT_POLL_INTERVAL,
ADT_DEFAULT_RELOGIN_INTERVAL,
STATE_OK,
STATE_ONLINE,
)
from pyadtpulse.site import ADTPulseSite
from pyadtpulse.zones import ADTPulseZoneData

from .const import (
ADTPULSE_DOMAIN,
Expand All @@ -56,6 +59,16 @@ def get_alarm_unique_id(site: ADTPulseSite) -> str:
return f"adt_pulse_alarm_{site.id}"


def zone_open(zone: ADTPulseZoneData) -> bool:
"""Determine if a zone is opened."""
return not zone.state == STATE_OK


def zone_trouble(zone: ADTPulseZoneData) -> bool:
"""Determine if a zone is in trouble state."""
return not zone.status == STATE_ONLINE


@callback
def migrate_entity_name(
hass: HomeAssistant, site: ADTPulseSite, platform_name: str, entity_uid: str
Expand Down
59 changes: 50 additions & 9 deletions custom_components/adtpulse/alarm_control_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
STATE_UNAVAILABLE,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
Expand All @@ -33,7 +34,13 @@
)
from pyadtpulse.site import ADTPulseSite

from . import get_alarm_unique_id, get_gateway_unique_id, migrate_entity_name
from . import (
get_alarm_unique_id,
get_gateway_unique_id,
migrate_entity_name,
zone_open,
zone_trouble,
)
from .const import ADTPULSE_DATA_ATTRIBUTION, ADTPULSE_DOMAIN
from .coordinator import ADTPulseDataUpdateCoordinator

Expand All @@ -45,11 +52,13 @@
ADT_ALARM_DISARMING: STATE_ALARM_DISARMING,
ADT_ALARM_HOME: STATE_ALARM_ARMED_HOME,
ADT_ALARM_OFF: STATE_ALARM_DISARMED,
ADT_ALARM_UNKNOWN: None,
ADT_ALARM_UNKNOWN: STATE_UNAVAILABLE,
}

ALARM_ICON_MAP = {
ADT_ALARM_ARMING: "mdi:shield-refresh",
ADT_ALARM_AWAY: "mdi:shield-lock",
ADT_ALARM_DISARMING: "mdi-shield-sync",
ADT_ALARM_HOME: "mdi:shield-home",
ADT_ALARM_OFF: "mdi:shield-off",
ADT_ALARM_UNKNOWN: "mdi:shield-bug",
Expand Down Expand Up @@ -89,6 +98,7 @@ def __init__(self, coordinator: ADTPulseDataUpdateCoordinator, site: ADTPulseSit
self._name = f"ADT Alarm Panel - Site {site.id}"
self._site = site
self._alarm = site.alarm_control_panel
self._assumed_state: str | None = None
super().__init__(coordinator, self._name)

@property
Expand All @@ -98,10 +108,16 @@ def state(self) -> str:
Returns:
str: the status
"""
if self._assumed_state is not None:
return self._assumed_state
if self._alarm.status in ALARM_MAP:
return ALARM_MAP[self._alarm.status]
return STATE_UNAVAILABLE

@property
def assumed_state(self) -> bool:
return self._assumed_state is None

@property
def attribution(self) -> str | None:
"""Return API data attribution."""
Expand All @@ -115,8 +131,16 @@ def icon(self) -> str:
return ALARM_ICON_MAP[self._alarm.status]

@property
def supported_features(self) -> AlarmControlPanelEntityFeature:
def supported_features(self) -> AlarmControlPanelEntityFeature | None:
"""Return the list of supported features."""
if self.state != STATE_ALARM_DISARMED:
return None
retval = AlarmControlPanelEntityFeature.ARM_CUSTOM_BYPASS
if self._site.zones_as_dict is None:
return retval
for zone in self._site.zones_as_dict.values():
if zone_open(zone) or zone_trouble(zone):
return retval
return (
AlarmControlPanelEntityFeature.ARM_AWAY
| AlarmControlPanelEntityFeature.ARM_CUSTOM_BYPASS
Expand All @@ -140,23 +164,41 @@ def device_info(self) -> DeviceInfo:
async def _perform_alarm_action(
self, arm_disarm_func: Coroutine[bool | None, None, bool], action: str
) -> None:
result = True
LOG.debug("%s: Setting Alarm to %s", ADTPULSE_DOMAIN, action)
if await arm_disarm_func:
self.async_write_ha_state()
if self.state == action:
LOG.warning("Attempting to set alarm to same state, ignoring")
return
if action == STATE_ALARM_DISARMED:
self._assumed_state = STATE_ALARM_DISARMING
else:
self._assumed_state = STATE_ALARM_ARMING
self.async_write_ha_state()
result = await arm_disarm_func
if not result:
LOG.warning("Could not %s ADT Pulse alarm", action)
self._assumed_state = None
self.async_write_ha_state()
if not result:
raise HomeAssistantError(f"Could not set alarm status to {action}")

async def async_alarm_disarm(self, code: str | None = None) -> None:
"""Send disarm command."""
await self._perform_alarm_action(self._site.async_disarm(), "disarm")
await self._perform_alarm_action(
self._site.async_disarm(), STATE_ALARM_DISARMED
)

async def async_alarm_arm_home(self, code: str | None = None) -> None:
"""Send arm home command."""
await self._perform_alarm_action(self._site.async_arm_home(), "arm home")
await self._perform_alarm_action(
self._site.async_arm_home(), STATE_ALARM_ARMED_HOME
)

async def async_alarm_arm_away(self, code: str | None = None) -> None:
"""Send arm away command."""
await self._perform_alarm_action(self._site.async_arm_away(), "arm away")
await self._perform_alarm_action(
self._site.async_arm_away(), STATE_ALARM_ARMED_AWAY
)

# Pulse can arm away or home with bypass
async def async_alarm_arm_custom_bypass(self, code: str | None = None) -> None:
Expand All @@ -178,7 +220,6 @@ def has_entity_name(self) -> bool:
def extra_state_attributes(self) -> dict:
"""Return the state attributes."""
return {
"site_id": self._site.id,
"last_update_time": as_local(
datetime.fromtimestamp(self._alarm.last_update)
),
Expand Down
13 changes: 9 additions & 4 deletions custom_components/adtpulse/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,16 @@
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.util import as_local
from pyadtpulse.const import STATE_OK, STATE_ONLINE
from pyadtpulse.site import ADTPulseSite
from pyadtpulse.zones import ADTPulseZoneData

from . import get_alarm_unique_id, get_gateway_unique_id, migrate_entity_name
from . import (
get_alarm_unique_id,
get_gateway_unique_id,
migrate_entity_name,
zone_open,
zone_trouble,
)
from .const import ADTPULSE_DATA_ATTRIBUTION, ADTPULSE_DOMAIN
from .coordinator import ADTPulseDataUpdateCoordinator

Expand Down Expand Up @@ -214,8 +219,8 @@ def is_on(self) -> bool:
"""Return True if the binary sensor is on."""
# sensor is considered tripped if the state is anything but OK
if self._is_trouble_indicator:
return not self._my_zone.status == STATE_ONLINE
return not self._my_zone.state == STATE_OK
return zone_trouble(self._my_zone)
return zone_open(self._my_zone)

@property
def device_class(self) -> BinarySensorDeviceClass:
Expand Down
2 changes: 1 addition & 1 deletion custom_components/adtpulse/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@
"requirements": [
"pyadtpulse>=1.1.2"
],
"version": "0.3.0"
"version": "0.3.2"
}

0 comments on commit a4363c0

Please sign in to comment.