Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Notify user if arming or disarming totalconnect alarm fails #36085

Merged
merged 8 commits into from
Jun 3, 2020
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions homeassistant/components/totalconnect/alarm_control_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
STATE_ALARM_DISARMING,
STATE_ALARM_TRIGGERED,
)
from homeassistant.exceptions import HomeAssistantError

from .const import DOMAIN

Expand All @@ -31,19 +32,20 @@ async def async_setup_entry(hass, entry, async_add_entities) -> None:

for location_id, location in client.locations.items():
location_name = location.location_name
alarms.append(TotalConnectAlarm(location_name, location_id, client))
alarms.append(TotalConnectAlarm(location_name, location_id, client, hass))

async_add_entities(alarms, True)


class TotalConnectAlarm(alarm.AlarmControlPanelEntity):
"""Represent an TotalConnect status."""

def __init__(self, name, location_id, client):
def __init__(self, name, location_id, client, hass):
austinmroczek marked this conversation as resolved.
Show resolved Hide resolved
"""Initialize the TotalConnect status."""
self._name = name
self._location_id = location_id
self._client = client
self._hass = hass
austinmroczek marked this conversation as resolved.
Show resolved Hide resolved
self._state = None
self._device_state_attributes = {}

Expand Down Expand Up @@ -114,16 +116,20 @@ def update(self):

def alarm_disarm(self, code=None):
"""Send disarm command."""
self._client.disarm(self._location_id)
if self._client.disarm(self._location_id) is not True:
raise HomeAssistantError(f"TotalConnect failed to disarm {self._name}.")

def alarm_arm_home(self, code=None):
"""Send arm home command."""
self._client.arm_stay(self._location_id)
if self._client.arm_stay(self._location_id) is not True:
raise HomeAssistantError(f"TotalConnect failed to arm home {self._name}.")

def alarm_arm_away(self, code=None):
"""Send arm away command."""
self._client.arm_away(self._location_id)
if self._client.arm_away(self._location_id) is not True:
raise HomeAssistantError(f"TotalConnect failed to arm away {self._name}.")

def alarm_arm_night(self, code=None):
"""Send arm night command."""
self._client.arm_stay_night(self._location_id)
if self._client.arm_stay_night(self._location_id) is not True:
raise HomeAssistantError(f"TotalConnect failed to arm night {self._name}.")
2 changes: 1 addition & 1 deletion homeassistant/components/totalconnect/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"domain": "totalconnect",
"name": "Honeywell Total Connect Alarm",
"documentation": "https://www.home-assistant.io/integrations/totalconnect",
"requirements": ["total_connect_client==0.54.1"],
"requirements": ["total_connect_client==0.55"],
"dependencies": [],
"codeowners": ["@austinmroczek"],
"config_flow": true
Expand Down
2 changes: 1 addition & 1 deletion requirements_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2078,7 +2078,7 @@ todoist-python==8.0.0
toonapilib==3.2.4

# homeassistant.components.totalconnect
total_connect_client==0.54.1
total_connect_client==0.55

# homeassistant.components.tplink_lte
tp-connected==0.0.4
Expand Down
2 changes: 1 addition & 1 deletion requirements_test_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -811,7 +811,7 @@ teslajsonpy==0.8.0
toonapilib==3.2.4

# homeassistant.components.totalconnect
total_connect_client==0.54.1
total_connect_client==0.55

# homeassistant.components.transmission
transmissionrpc==0.11
Expand Down
136 changes: 136 additions & 0 deletions tests/components/totalconnect/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
"""Common methods used across tests for TotalConnect."""
from total_connect_client import TotalConnectClient

from homeassistant.components.totalconnect import DOMAIN
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.setup import async_setup_component

from tests.async_mock import patch
from tests.common import MockConfigEntry

LOCATION_INFO_BASIC_NORMAL = {
"LocationID": "123456",
"LocationName": "test",
"SecurityDeviceID": "987654",
"PhotoURL": "http://www.example.com/some/path/to/file.jpg",
"LocationModuleFlags": "Security=1,Video=0,Automation=0,GPS=0,VideoPIR=0",
"DeviceList": None,
}

LOCATIONS = {"LocationInfoBasic": [LOCATION_INFO_BASIC_NORMAL]}

MODULE_FLAGS = "Some=0,Fake=1,Flags=2"

USER = {
"UserID": "1234567",
"Username": "username",
"UserFeatureList": "Master=0,User Administration=0,Configuration Administration=0",
}

RESPONSE_AUTHENTICATE = {
"ResultCode": 0,
"SessionID": 1,
"Locations": LOCATIONS,
"ModuleFlags": MODULE_FLAGS,
"UserInfo": USER,
}

PARTITION_DISARMED = {
"PartitionID": "1",
"ArmingState": TotalConnectClient.TotalConnectLocation.DISARMED,
}

PARTITION_ARMED_STAY = {
"PartitionID": "1",
"ArmingState": TotalConnectClient.TotalConnectLocation.ARMED_STAY,
}

PARTITION_ARMED_AWAY = {
"PartitionID": "1",
"ArmingState": TotalConnectClient.TotalConnectLocation.ARMED_AWAY,
}

PARTITION_INFO_DISARMED = {}
PARTITION_INFO_DISARMED[0] = PARTITION_DISARMED
austinmroczek marked this conversation as resolved.
Show resolved Hide resolved

PARTITION_INFO_ARMED_STAY = {}
PARTITION_INFO_ARMED_STAY[0] = PARTITION_ARMED_STAY

PARTITION_INFO_ARMED_AWAY = {}
PARTITION_INFO_ARMED_AWAY[0] = PARTITION_ARMED_AWAY


PARTITIONS_DISARMED = {"PartitionInfo": PARTITION_INFO_DISARMED}
PARTITIONS_ARMED_STAY = {"PartitionInfo": PARTITION_INFO_ARMED_STAY}
PARTITIONS_ARMED_AWAY = {"PartitionInfo": PARTITION_INFO_ARMED_AWAY}

ZONE_NORMAL = {
"ZoneID": "1",
"ZoneDescription": "Normal",
"ZoneStatus": TotalConnectClient.ZONE_STATUS_NORMAL,
"PartitionId": "1",
}

ZONE_INFO = []
ZONE_INFO.append(ZONE_NORMAL)
ZONES = {"ZoneInfo": ZONE_INFO}

METADATA_DISARMED = {
"Partitions": PARTITIONS_DISARMED,
"Zones": ZONES,
"PromptForImportSecuritySettings": False,
"IsInACLoss": False,
"IsCoverTampered": False,
"Bell1SupervisionFailure": False,
"Bell2SupervisionFailure": False,
"IsInLowBattery": False,
}

METADATA_ARMED_STAY = METADATA_DISARMED.copy()
METADATA_ARMED_STAY["Partitions"] = PARTITIONS_ARMED_STAY

METADATA_ARMED_AWAY = METADATA_DISARMED.copy()
METADATA_ARMED_AWAY["Partitions"] = PARTITIONS_ARMED_AWAY

RESPONSE_DISARMED = {"ResultCode": 0, "PanelMetadataAndStatus": METADATA_DISARMED}
RESPONSE_ARMED_STAY = {"ResultCode": 0, "PanelMetadataAndStatus": METADATA_ARMED_STAY}
RESPONSE_ARMED_AWAY = {"ResultCode": 0, "PanelMetadataAndStatus": METADATA_ARMED_AWAY}

RESPONSE_ARM_SUCCESS = {"ResultCode": TotalConnectClient.TotalConnectClient.ARM_SUCCESS}
RESPONSE_ARM_FAILURE = {
"ResultCode": TotalConnectClient.TotalConnectClient.COMMAND_FAILED
}
RESPONSE_DISARM_SUCCESS = {
"ResultCode": TotalConnectClient.TotalConnectClient.DISARM_SUCCESS
}
RESPONSE_DISARM_FAILURE = {
"ResultCode": TotalConnectClient.TotalConnectClient.COMMAND_FAILED,
"ResultData": "Command Failed",
}


async def setup_platform(hass, platform):
"""Set up the TotalConnect platform."""
# first set up a config entry and add it to hass
mock_entry = MockConfigEntry(
domain=DOMAIN,
data={CONF_USERNAME: "user@email.com", CONF_PASSWORD: "password"},
)
mock_entry.add_to_hass(hass)

RESPONSES = [RESPONSE_AUTHENTICATE, RESPONSE_DISARMED]
austinmroczek marked this conversation as resolved.
Show resolved Hide resolved

with patch("homeassistant.components.totalconnect.PLATFORMS", [platform]), patch(
"zeep.Client", autospec=True
), patch(
"homeassistant.components.totalconnect.TotalConnectClient.TotalConnectClient.request",
side_effect=RESPONSES,
) as mock_request, patch(
"homeassistant.components.totalconnect.TotalConnectClient.TotalConnectClient.get_zone_details",
return_value=True,
):
assert await async_setup_component(hass, DOMAIN, {})
assert mock_request.call_count == 2
await hass.async_block_till_done()

return mock_entry
136 changes: 136 additions & 0 deletions tests/components/totalconnect/test_alarm_control_panel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
"""Tests for the TotalConnect alarm control panel device."""
import pytest

from homeassistant.components.alarm_control_panel import DOMAIN as ALARM_DOMAIN
from homeassistant.const import (
ATTR_FRIENDLY_NAME,
STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_HOME,
STATE_ALARM_DISARMED,
)

from .common import (
RESPONSE_ARM_FAILURE,
RESPONSE_ARM_SUCCESS,
RESPONSE_ARMED_AWAY,
RESPONSE_ARMED_STAY,
RESPONSE_DISARM_FAILURE,
RESPONSE_DISARM_SUCCESS,
RESPONSE_DISARMED,
setup_platform,
)

from tests.async_mock import patch
from tests.components.alarm_control_panel import common

ENTITY_ID = "alarm_control_panel.test"
CODE = "-1"


async def test_attributes(hass):
"""Test the alarm control panel attributes are correct."""
with patch(
"homeassistant.components.totalconnect.TotalConnectClient.TotalConnectClient.request",
return_value=RESPONSE_DISARMED,
) as mock_request:
await setup_platform(hass, ALARM_DOMAIN)
state = hass.states.get(ENTITY_ID)
assert state.state == STATE_ALARM_DISARMED
mock_request.assert_called_once()
assert state.attributes.get(ATTR_FRIENDLY_NAME) == "test"


async def test_arm_home_success(hass):
"""Test arm home method success."""
RESPONSES = [RESPONSE_DISARMED, RESPONSE_ARM_SUCCESS, RESPONSE_ARMED_STAY]
with patch(
"homeassistant.components.totalconnect.TotalConnectClient.TotalConnectClient.request",
side_effect=RESPONSES,
):
await setup_platform(hass, ALARM_DOMAIN)
assert STATE_ALARM_DISARMED == hass.states.get(ENTITY_ID).state

await common.async_alarm_arm_home(hass)
austinmroczek marked this conversation as resolved.
Show resolved Hide resolved
await hass.async_block_till_done()
assert STATE_ALARM_ARMED_HOME == hass.states.get(ENTITY_ID).state


async def test_arm_home_failure(hass):
"""Test arm home method failure."""
RESPONSES = [RESPONSE_DISARMED, RESPONSE_ARM_FAILURE, RESPONSE_DISARMED]
with patch(
"homeassistant.components.totalconnect.TotalConnectClient.TotalConnectClient.request",
side_effect=RESPONSES,
):
await setup_platform(hass, ALARM_DOMAIN)
assert STATE_ALARM_DISARMED == hass.states.get(ENTITY_ID).state

with pytest.raises(Exception) as e:
await common.async_alarm_arm_home(hass)
await hass.async_block_till_done()
assert f"{e.value}" == "TotalConnect failed to arm home test."
assert STATE_ALARM_DISARMED == hass.states.get(ENTITY_ID).state


async def test_arm_away_success(hass):
"""Test arm away method success."""
RESPONSES = [RESPONSE_DISARMED, RESPONSE_ARM_SUCCESS, RESPONSE_ARMED_AWAY]
with patch(
"homeassistant.components.totalconnect.TotalConnectClient.TotalConnectClient.request",
side_effect=RESPONSES,
):
await setup_platform(hass, ALARM_DOMAIN)
assert STATE_ALARM_DISARMED == hass.states.get(ENTITY_ID).state

await common.async_alarm_arm_away(hass)
await hass.async_block_till_done()
assert STATE_ALARM_ARMED_AWAY == hass.states.get(ENTITY_ID).state


async def test_arm_away_failure(hass):
"""Test arm away method failure."""
RESPONSES = [RESPONSE_DISARMED, RESPONSE_ARM_FAILURE, RESPONSE_DISARMED]
with patch(
"homeassistant.components.totalconnect.TotalConnectClient.TotalConnectClient.request",
side_effect=RESPONSES,
):
await setup_platform(hass, ALARM_DOMAIN)
assert STATE_ALARM_DISARMED == hass.states.get(ENTITY_ID).state

with pytest.raises(Exception) as e:
await common.async_alarm_arm_away(hass)
await hass.async_block_till_done()
assert f"{e.value}" == "TotalConnect failed to arm away test."
assert STATE_ALARM_DISARMED == hass.states.get(ENTITY_ID).state


async def test_disarm_success(hass):
"""Test disarm method success."""
RESPONSES = [RESPONSE_ARMED_AWAY, RESPONSE_DISARM_SUCCESS, RESPONSE_DISARMED]
with patch(
"homeassistant.components.totalconnect.TotalConnectClient.TotalConnectClient.request",
side_effect=RESPONSES,
):
await setup_platform(hass, ALARM_DOMAIN)
assert STATE_ALARM_ARMED_AWAY == hass.states.get(ENTITY_ID).state

await common.async_alarm_disarm(hass)
await hass.async_block_till_done()
assert STATE_ALARM_DISARMED == hass.states.get(ENTITY_ID).state


async def test_disarm_failure(hass):
"""Test disarm method failure."""
RESPONSES = [RESPONSE_ARMED_AWAY, RESPONSE_DISARM_FAILURE, RESPONSE_ARMED_AWAY]
with patch(
"homeassistant.components.totalconnect.TotalConnectClient.TotalConnectClient.request",
side_effect=RESPONSES,
):
await setup_platform(hass, ALARM_DOMAIN)
assert STATE_ALARM_ARMED_AWAY == hass.states.get(ENTITY_ID).state

with pytest.raises(Exception) as e:
await common.async_alarm_disarm(hass)
await hass.async_block_till_done()
assert f"{e.value}" == "TotalConnect failed to disarm test."
assert STATE_ALARM_ARMED_AWAY == hass.states.get(ENTITY_ID).state