mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 19:27:45 +00:00
Fix HomematicIP Cloud Alarm Control Panel support for basic mode (#28778)
This commit is contained in:
parent
b4ccc0202a
commit
60e7440ec1
@ -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
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user