Fix HomematicIP Cloud Alarm Control Panel support for basic mode (#28778)

This commit is contained in:
SukramJ 2019-11-15 09:55:40 +01:00 committed by Martin Hjelmare
parent b4ccc0202a
commit 60e7440ec1
4 changed files with 31 additions and 113 deletions

View File

@ -1,8 +1,7 @@
"""Support for HomematicIP Cloud alarm control panel.""" """Support for HomematicIP Cloud alarm control panel."""
import logging import logging
from homematicip.aio.group import AsyncSecurityZoneGroup from homematicip.functionalHomes import SecurityAndAlarmHome
from homematicip.base.enums import WindowState
from homeassistant.components.alarm_control_panel import AlarmControlPanel from homeassistant.components.alarm_control_panel import AlarmControlPanel
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
@ -32,34 +31,15 @@ async def async_setup_entry(
) -> None: ) -> None:
"""Set up the HomematicIP alrm control panel from a config entry.""" """Set up the HomematicIP alrm control panel from a config entry."""
hap = hass.data[HMIPC_DOMAIN][config_entry.data[HMIPC_HAPID]] hap = hass.data[HMIPC_DOMAIN][config_entry.data[HMIPC_HAPID]]
devices = [] async_add_entities([HomematicipAlarmControlPanel(hap)])
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)
class HomematicipAlarmControlPanel(AlarmControlPanel): class HomematicipAlarmControlPanel(AlarmControlPanel):
"""Representation of an alarm control panel.""" """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.""" """Initialize the alarm control panel."""
self._home = hap.home 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 @property
def device_info(self): def device_info(self):
@ -75,28 +55,23 @@ class HomematicipAlarmControlPanel(AlarmControlPanel):
@property @property
def state(self) -> str: def state(self) -> str:
"""Return the state of the device.""" """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() activation_state = self._home.get_security_zones_activation()
# check arm_away # check arm_away
if activation_state == (True, True): 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 return STATE_ALARM_ARMED_AWAY
# check arm_home # check arm_home
if activation_state == (False, True): if activation_state == (False, True):
if self._external_alarm_zone_state:
return STATE_ALARM_TRIGGERED
return STATE_ALARM_ARMED_HOME return STATE_ALARM_ARMED_HOME
return STATE_ALARM_DISARMED return STATE_ALARM_DISARMED
@property @property
def _internal_alarm_zone_state(self) -> bool: def _security_and_alarm(self):
return _get_zone_alarm_state(self._internal_alarm_zone) return self._home.get_functionalHome(SecurityAndAlarmHome)
@property
def _external_alarm_zone_state(self) -> bool:
"""Return the state of the device."""
return _get_zone_alarm_state(self._external_alarm_zone)
async def async_alarm_disarm(self, code=None): async def async_alarm_disarm(self, code=None):
"""Send disarm command.""" """Send disarm command."""
@ -112,10 +87,7 @@ class HomematicipAlarmControlPanel(AlarmControlPanel):
async def async_added_to_hass(self): async def async_added_to_hass(self):
"""Register callbacks.""" """Register callbacks."""
if self._internal_alarm_zone: self._home.on_update(self._async_device_changed)
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)
def _async_device_changed(self, *args, **kwargs): def _async_device_changed(self, *args, **kwargs):
"""Handle device state changes.""" """Handle device state changes."""
@ -138,26 +110,9 @@ class HomematicipAlarmControlPanel(AlarmControlPanel):
@property @property
def available(self) -> bool: def available(self) -> bool:
"""Device available.""" """Device available."""
return ( return self._home.connected
not self._internal_alarm_zone.unreach
or not self._external_alarm_zone.unreach
)
@property @property
def unique_id(self) -> str: def unique_id(self) -> str:
"""Return a unique ID.""" """Return a unique ID."""
return f"{self.__class__.__name__}_{self._home.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

View File

@ -1,7 +1,4 @@
"""Tests for HomematicIP Cloud alarm control panel.""" """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 ( from homeassistant.components.alarm_control_panel import (
DOMAIN as ALARM_CONTROL_PANEL_DOMAIN, DOMAIN as ALARM_CONTROL_PANEL_DOMAIN,
) )
@ -17,29 +14,24 @@ from homeassistant.setup import async_setup_component
from .helper import get_and_check_entity_basics 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( 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.""" """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.active = external_active
external_zone.windowState = window_state internal_zone = home.search_group_by_id(internal_zone_id)
internal_zone.active = internal_active internal_zone.active = internal_active
# Just one call to a security zone is required to refresh the ACP. home.fire_update_event(json)
internal_zone.fire_update_event()
await hass.async_block_till_done() await hass.async_block_till_done()
@ -70,79 +62,49 @@ async def test_hmip_alarm_control_panel(hass, default_mock_hap):
assert not hmip_device assert not hmip_device
home = default_mock_hap.home home = default_mock_hap.home
service_call_counter = len(home.mock_calls)
await hass.services.async_call( await hass.services.async_call(
"alarm_control_panel", "alarm_arm_away", {"entity_id": entity_id}, blocking=True "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][0] == "set_security_zones_activation"
assert home.mock_calls[-1][1] == (True, True) assert home.mock_calls[-1][1] == (True, True)
await _async_manipulate_security_zones( await _async_manipulate_security_zones(
hass, hass, home, internal_active=True, external_active=True
home,
internal_active=True,
external_active=True,
window_state=WindowState.CLOSED,
) )
assert hass.states.get(entity_id).state is STATE_ALARM_ARMED_AWAY assert hass.states.get(entity_id).state is STATE_ALARM_ARMED_AWAY
await hass.services.async_call( await hass.services.async_call(
"alarm_control_panel", "alarm_arm_home", {"entity_id": entity_id}, blocking=True "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][0] == "set_security_zones_activation"
assert home.mock_calls[-1][1] == (False, True) assert home.mock_calls[-1][1] == (False, True)
await _async_manipulate_security_zones( await _async_manipulate_security_zones(hass, home, external_active=True)
hass,
home,
internal_active=False,
external_active=True,
window_state=WindowState.CLOSED,
)
assert hass.states.get(entity_id).state is STATE_ALARM_ARMED_HOME assert hass.states.get(entity_id).state is STATE_ALARM_ARMED_HOME
await hass.services.async_call( await hass.services.async_call(
"alarm_control_panel", "alarm_disarm", {"entity_id": entity_id}, blocking=True "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][0] == "set_security_zones_activation"
assert home.mock_calls[-1][1] == (False, False) assert home.mock_calls[-1][1] == (False, False)
await _async_manipulate_security_zones( await _async_manipulate_security_zones(hass, home)
hass,
home,
internal_active=False,
external_active=False,
window_state=WindowState.CLOSED,
)
assert hass.states.get(entity_id).state is STATE_ALARM_DISARMED assert hass.states.get(entity_id).state is STATE_ALARM_DISARMED
await hass.services.async_call( await hass.services.async_call(
"alarm_control_panel", "alarm_arm_away", {"entity_id": entity_id}, blocking=True "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][0] == "set_security_zones_activation"
assert home.mock_calls[-1][1] == (True, True) assert home.mock_calls[-1][1] == (True, True)
await _async_manipulate_security_zones( await _async_manipulate_security_zones(
hass, hass, home, internal_active=True, external_active=True, alarm_triggered=True
home,
internal_active=True,
external_active=True,
window_state=WindowState.OPEN,
) )
assert hass.states.get(entity_id).state is STATE_ALARM_TRIGGERED assert hass.states.get(entity_id).state is STATE_ALARM_TRIGGERED
await hass.services.async_call( await hass.services.async_call(
"alarm_control_panel", "alarm_arm_home", {"entity_id": entity_id}, blocking=True "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][0] == "set_security_zones_activation"
assert home.mock_calls[-1][1] == (False, True) assert home.mock_calls[-1][1] == (False, True)
await _async_manipulate_security_zones( await _async_manipulate_security_zones(
hass, hass, home, external_active=True, alarm_triggered=True
home,
internal_active=False,
external_active=True,
window_state=WindowState.OPEN,
) )
assert hass.states.get(entity_id).state is STATE_ALARM_TRIGGERED assert hass.states.get(entity_id).state is STATE_ALARM_TRIGGERED

View File

@ -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 hass, default_mock_hap, entity_id, entity_name, device_model
) )
assert hmip_device
assert ha_state.state == HVAC_MODE_AUTO assert ha_state.state == HVAC_MODE_AUTO
assert ha_state.attributes["current_temperature"] == 24.7 assert ha_state.attributes["current_temperature"] == 24.7
assert ha_state.attributes["min_temp"] == 5.0 assert ha_state.attributes["min_temp"] == 5.0

View File

@ -161,5 +161,5 @@ async def test_hmip_dump_hap_config_services(hass, mock_hap_with_service):
) )
home = mock_hap_with_service.home home = mock_hap_with_service.home
assert home.mock_calls[-1][0] == "download_configuration" assert home.mock_calls[-1][0] == "download_configuration"
assert len(home.mock_calls) == 8 # pylint: disable=W0212 assert home.mock_calls
assert len(write_mock.mock_calls) > 0 assert write_mock.mock_calls