Skip to content

Commit

Permalink
Fix HomematicIP Cloud Alarm Control Panel support for basic mode (hom…
Browse files Browse the repository at this point in the history
  • Loading branch information
SukramJ authored and MartinHjelmare committed Nov 15, 2019
1 parent b4ccc02 commit 60e7440
Show file tree
Hide file tree
Showing 4 changed files with 31 additions and 113 deletions.
67 changes: 11 additions & 56 deletions homeassistant/components/homematicip_cloud/alarm_control_panel.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
"""Support for HomematicIP Cloud alarm control panel."""
import logging

from homematicip.aio.group import AsyncSecurityZoneGroup
from homematicip.base.enums import WindowState
from homematicip.functionalHomes import SecurityAndAlarmHome

from homeassistant.components.alarm_control_panel import AlarmControlPanel
from homeassistant.config_entries import ConfigEntry
Expand Down Expand Up @@ -32,34 +31,15 @@ async def async_setup_entry(
) -> None:
"""Set up the HomematicIP alrm control panel from a config entry."""
hap = hass.data[HMIPC_DOMAIN][config_entry.data[HMIPC_HAPID]]
devices = []
security_zones = []
for group in hap.home.groups:
if isinstance(group, AsyncSecurityZoneGroup):
security_zones.append(group)

if security_zones:
devices.append(HomematicipAlarmControlPanel(hap, security_zones))

if devices:
async_add_entities(devices)
async_add_entities([HomematicipAlarmControlPanel(hap)])


class HomematicipAlarmControlPanel(AlarmControlPanel):
"""Representation of an alarm control panel."""

def __init__(self, hap: HomematicipHAP, security_zones) -> None:
def __init__(self, hap: HomematicipHAP) -> None:
"""Initialize the alarm control panel."""
self._home = hap.home
self.alarm_state = STATE_ALARM_DISARMED
self._internal_alarm_zone = None
self._external_alarm_zone = None

for security_zone in security_zones:
if security_zone.label == "INTERNAL":
self._internal_alarm_zone = security_zone
elif security_zone.label == "EXTERNAL":
self._external_alarm_zone = security_zone

@property
def device_info(self):
Expand All @@ -75,28 +55,23 @@ def device_info(self):
@property
def state(self) -> str:
"""Return the state of the device."""
# check for triggered alarm
if self._security_and_alarm.alarmActive:
return STATE_ALARM_TRIGGERED

activation_state = self._home.get_security_zones_activation()
# check arm_away
if activation_state == (True, True):
if self._internal_alarm_zone_state or self._external_alarm_zone_state:
return STATE_ALARM_TRIGGERED
return STATE_ALARM_ARMED_AWAY
# check arm_home
if activation_state == (False, True):
if self._external_alarm_zone_state:
return STATE_ALARM_TRIGGERED
return STATE_ALARM_ARMED_HOME

return STATE_ALARM_DISARMED

@property
def _internal_alarm_zone_state(self) -> bool:
return _get_zone_alarm_state(self._internal_alarm_zone)

@property
def _external_alarm_zone_state(self) -> bool:
"""Return the state of the device."""
return _get_zone_alarm_state(self._external_alarm_zone)
def _security_and_alarm(self):
return self._home.get_functionalHome(SecurityAndAlarmHome)

async def async_alarm_disarm(self, code=None):
"""Send disarm command."""
Expand All @@ -112,10 +87,7 @@ async def async_alarm_arm_away(self, code=None):

async def async_added_to_hass(self):
"""Register callbacks."""
if self._internal_alarm_zone:
self._internal_alarm_zone.on_update(self._async_device_changed)
if self._external_alarm_zone:
self._external_alarm_zone.on_update(self._async_device_changed)
self._home.on_update(self._async_device_changed)

def _async_device_changed(self, *args, **kwargs):
"""Handle device state changes."""
Expand All @@ -138,26 +110,9 @@ def should_poll(self) -> bool:
@property
def available(self) -> bool:
"""Device available."""
return (
not self._internal_alarm_zone.unreach
or not self._external_alarm_zone.unreach
)
return self._home.connected

@property
def unique_id(self) -> str:
"""Return a unique ID."""
return f"{self.__class__.__name__}_{self._home.id}"


def _get_zone_alarm_state(security_zone) -> bool:
if security_zone and security_zone.active:
if (
security_zone.sabotage
or security_zone.motionDetected
or security_zone.presenceDetected
or security_zone.windowState == WindowState.OPEN
or security_zone.windowState == WindowState.TILTED
):
return True

return False
72 changes: 17 additions & 55 deletions tests/components/homematicip_cloud/test_alarm_control_panel.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
"""Tests for HomematicIP Cloud alarm control panel."""
from homematicip.base.enums import WindowState
from homematicip.group import SecurityZoneGroup

from homeassistant.components.alarm_control_panel import (
DOMAIN as ALARM_CONTROL_PANEL_DOMAIN,
)
Expand All @@ -17,29 +14,24 @@
from .helper import get_and_check_entity_basics


def _get_security_zones(groups): # pylint: disable=W0221
"""Get the security zones."""
for group in groups:
if isinstance(group, SecurityZoneGroup):
if group.label == "EXTERNAL":
external = group
elif group.label == "INTERNAL":
internal = group
return internal, external


async def _async_manipulate_security_zones(
hass, home, internal_active, external_active, window_state
hass, home, internal_active=False, external_active=False, alarm_triggered=False
):
"""Set new values on hmip security zones."""
internal_zone, external_zone = _get_security_zones(home.groups)
json = home._rawJSONData # pylint: disable=W0212
json["functionalHomes"]["SECURITY_AND_ALARM"]["alarmActive"] = alarm_triggered
external_zone_id = json["functionalHomes"]["SECURITY_AND_ALARM"]["securityZones"][
"EXTERNAL"
]
internal_zone_id = json["functionalHomes"]["SECURITY_AND_ALARM"]["securityZones"][
"INTERNAL"
]
external_zone = home.search_group_by_id(external_zone_id)
external_zone.active = external_active
external_zone.windowState = window_state
internal_zone = home.search_group_by_id(internal_zone_id)
internal_zone.active = internal_active

# Just one call to a security zone is required to refresh the ACP.
internal_zone.fire_update_event()

home.fire_update_event(json)
await hass.async_block_till_done()


Expand Down Expand Up @@ -70,79 +62,49 @@ async def test_hmip_alarm_control_panel(hass, default_mock_hap):
assert not hmip_device

home = default_mock_hap.home
service_call_counter = len(home.mock_calls)

await hass.services.async_call(
"alarm_control_panel", "alarm_arm_away", {"entity_id": entity_id}, blocking=True
)
assert len(home.mock_calls) == service_call_counter + 1
assert home.mock_calls[-1][0] == "set_security_zones_activation"
assert home.mock_calls[-1][1] == (True, True)
await _async_manipulate_security_zones(
hass,
home,
internal_active=True,
external_active=True,
window_state=WindowState.CLOSED,
hass, home, internal_active=True, external_active=True
)
assert hass.states.get(entity_id).state is STATE_ALARM_ARMED_AWAY

await hass.services.async_call(
"alarm_control_panel", "alarm_arm_home", {"entity_id": entity_id}, blocking=True
)
assert len(home.mock_calls) == service_call_counter + 3
assert home.mock_calls[-1][0] == "set_security_zones_activation"
assert home.mock_calls[-1][1] == (False, True)
await _async_manipulate_security_zones(
hass,
home,
internal_active=False,
external_active=True,
window_state=WindowState.CLOSED,
)
await _async_manipulate_security_zones(hass, home, external_active=True)
assert hass.states.get(entity_id).state is STATE_ALARM_ARMED_HOME

await hass.services.async_call(
"alarm_control_panel", "alarm_disarm", {"entity_id": entity_id}, blocking=True
)
assert len(home.mock_calls) == service_call_counter + 5
assert home.mock_calls[-1][0] == "set_security_zones_activation"
assert home.mock_calls[-1][1] == (False, False)
await _async_manipulate_security_zones(
hass,
home,
internal_active=False,
external_active=False,
window_state=WindowState.CLOSED,
)
await _async_manipulate_security_zones(hass, home)
assert hass.states.get(entity_id).state is STATE_ALARM_DISARMED

await hass.services.async_call(
"alarm_control_panel", "alarm_arm_away", {"entity_id": entity_id}, blocking=True
)
assert len(home.mock_calls) == service_call_counter + 7
assert home.mock_calls[-1][0] == "set_security_zones_activation"
assert home.mock_calls[-1][1] == (True, True)
await _async_manipulate_security_zones(
hass,
home,
internal_active=True,
external_active=True,
window_state=WindowState.OPEN,
hass, home, internal_active=True, external_active=True, alarm_triggered=True
)
assert hass.states.get(entity_id).state is STATE_ALARM_TRIGGERED

await hass.services.async_call(
"alarm_control_panel", "alarm_arm_home", {"entity_id": entity_id}, blocking=True
)
assert len(home.mock_calls) == service_call_counter + 9
assert home.mock_calls[-1][0] == "set_security_zones_activation"
assert home.mock_calls[-1][1] == (False, True)
await _async_manipulate_security_zones(
hass,
home,
internal_active=False,
external_active=True,
window_state=WindowState.OPEN,
hass, home, external_active=True, alarm_triggered=True
)
assert hass.states.get(entity_id).state is STATE_ALARM_TRIGGERED
1 change: 1 addition & 0 deletions tests/components/homematicip_cloud/test_climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,7 @@ async def test_hmip_heating_group_heat_with_switch(hass, default_mock_hap):
hass, default_mock_hap, entity_id, entity_name, device_model
)

assert hmip_device
assert ha_state.state == HVAC_MODE_AUTO
assert ha_state.attributes["current_temperature"] == 24.7
assert ha_state.attributes["min_temp"] == 5.0
Expand Down
4 changes: 2 additions & 2 deletions tests/components/homematicip_cloud/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,5 +161,5 @@ async def test_hmip_dump_hap_config_services(hass, mock_hap_with_service):
)
home = mock_hap_with_service.home
assert home.mock_calls[-1][0] == "download_configuration"
assert len(home.mock_calls) == 8 # pylint: disable=W0212
assert len(write_mock.mock_calls) > 0
assert home.mock_calls
assert write_mock.mock_calls

0 comments on commit 60e7440

Please sign in to comment.