Skip to content

Commit

Permalink
feat: add support for VACATION mode (#34)
Browse files Browse the repository at this point in the history
  • Loading branch information
palazzem authored Sep 8, 2023
1 parent 6179f17 commit 6249eba
Show file tree
Hide file tree
Showing 7 changed files with 78 additions and 16 deletions.
13 changes: 12 additions & 1 deletion custom_components/econnect_alarm/alarm_control_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@
SUPPORT_ALARM_ARM_AWAY,
SUPPORT_ALARM_ARM_HOME,
SUPPORT_ALARM_ARM_NIGHT,
SUPPORT_ALARM_ARM_VACATION,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_NIGHT,
STATE_ALARM_ARMED_VACATION,
STATE_ALARM_ARMING,
STATE_ALARM_DISARMED,
STATE_ALARM_DISARMING,
Expand Down Expand Up @@ -83,7 +85,7 @@ def code_format(self):
@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_AWAY | SUPPORT_ALARM_ARM_NIGHT
return SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_AWAY | SUPPORT_ALARM_ARM_NIGHT | SUPPORT_ALARM_ARM_VACATION

@set_device_state(STATE_ALARM_DISARMED, STATE_ALARM_DISARMING)
async def async_alarm_disarm(self, code=None):
Expand Down Expand Up @@ -112,3 +114,12 @@ async def async_alarm_arm_night(self, code=None):
return

await self.hass.async_add_executor_job(self._device.arm, code, self._device._sectors_night)

@set_device_state(STATE_ALARM_ARMED_VACATION, STATE_ALARM_ARMING)
async def async_alarm_arm_vacation(self, code=None):
"""Send arm vacation command."""
if not self._device._sectors_vacation:
_LOGGER.warning("Triggering ARM VACATION without configuration. Use integration Options to configure it.")
return

await self.hass.async_add_executor_job(self._device.arm, code, self._device._sectors_vacation)
10 changes: 10 additions & 0 deletions custom_components/econnect_alarm/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from .const import (
CONF_AREAS_ARM_HOME,
CONF_AREAS_ARM_NIGHT,
CONF_AREAS_ARM_VACATION,
CONF_DOMAIN,
CONF_SYSTEM_URL,
DOMAIN,
Expand Down Expand Up @@ -95,6 +96,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
Available options are:
* Areas armed in Arm Away state
* Areas armed in Arm Night state
* Areas armed in Arm Vacation state
"""

def __init__(self, config_entry: config_entries.ConfigEntry) -> None:
Expand All @@ -108,6 +110,7 @@ async def async_step_init(self, user_input=None):
try:
parse_areas_config(user_input.get(CONF_AREAS_ARM_HOME), raises=True)
parse_areas_config(user_input.get(CONF_AREAS_ARM_NIGHT), raises=True)
parse_areas_config(user_input.get(CONF_AREAS_ARM_VACATION), raises=True)
except InvalidAreas:
errors["base"] = "invalid_areas"
except Exception as err: # pylint: disable=broad-except
Expand All @@ -120,6 +123,9 @@ async def async_step_init(self, user_input=None):
user_input = user_input or {}
suggest_arm_home = user_input.get(CONF_AREAS_ARM_HOME) or self.config_entry.options.get(CONF_AREAS_ARM_HOME)
suggest_arm_night = user_input.get(CONF_AREAS_ARM_NIGHT) or self.config_entry.options.get(CONF_AREAS_ARM_NIGHT)
suggest_arm_vacation = user_input.get(CONF_AREAS_ARM_VACATION) or self.config_entry.options.get(
CONF_AREAS_ARM_VACATION
)
return self.async_show_form(
step_id="init",
data_schema=vol.Schema(
Expand All @@ -132,6 +138,10 @@ async def async_step_init(self, user_input=None):
CONF_AREAS_ARM_NIGHT,
description={"suggested_value": suggest_arm_night},
): str,
vol.Optional(
CONF_AREAS_ARM_VACATION,
description={"suggested_value": suggest_arm_vacation},
): str,
}
),
errors=errors,
Expand Down
1 change: 1 addition & 0 deletions custom_components/econnect_alarm/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
CONF_SYSTEM_URL = "system_base_url"
CONF_AREAS_ARM_HOME = "areas_arm_home"
CONF_AREAS_ARM_NIGHT = "areas_arm_night"
CONF_AREAS_ARM_VACATION = "areas_arm_vacation"
DOMAIN = "econnect_alarm"
KEY_DEVICE = "device"
KEY_COORDINATOR = "coordinator"
Expand Down
8 changes: 7 additions & 1 deletion custom_components/econnect_alarm/devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@
STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_NIGHT,
STATE_ALARM_ARMED_VACATION,
STATE_ALARM_DISARMED,
STATE_UNAVAILABLE,
)
from requests.exceptions import HTTPError

from .const import CONF_AREAS_ARM_HOME, CONF_AREAS_ARM_NIGHT
from .const import CONF_AREAS_ARM_HOME, CONF_AREAS_ARM_NIGHT, CONF_AREAS_ARM_VACATION
from .helpers import parse_areas_config

_LOGGER = logging.getLogger(__name__)
Expand All @@ -38,6 +39,7 @@ def __init__(self, connection, config=None):
self._connection = connection
self._sectors_home = []
self._sectors_night = []
self._sectors_vacation = []
self._lastIds = {
q.SECTORS: 0,
q.INPUTS: 0,
Expand All @@ -47,6 +49,7 @@ def __init__(self, connection, config=None):
if config is not None:
self._sectors_home = parse_areas_config(config.get(CONF_AREAS_ARM_HOME))
self._sectors_night = parse_areas_config(config.get(CONF_AREAS_ARM_NIGHT))
self._sectors_vacation = parse_areas_config(config.get(CONF_AREAS_ARM_VACATION))

# Alarm state
self.state = STATE_UNAVAILABLE
Expand Down Expand Up @@ -115,6 +118,9 @@ def get_state(self):
if sectors_armed_sorted == sorted(self._sectors_night):
return STATE_ALARM_ARMED_NIGHT

if sectors_armed_sorted == sorted(self._sectors_vacation):
return STATE_ALARM_ARMED_VACATION

return STATE_ALARM_ARMED_AWAY

def update(self):
Expand Down
11 changes: 6 additions & 5 deletions custom_components/econnect_alarm/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"password": "[%key:common::config_flow::data::password%]"
},
"description": "Insert your username and password to gain access. You can configure the system selecting \"Options\" after installing the integration.",
"title": "Configure your e-connect system"
"title": "Configure your e-Connect/IESS system"
}
},
"error": {
Expand All @@ -23,17 +23,18 @@
},
"options": {
"error": {
"invalid_areas": "Digited areas (home or night) are invalid",
"invalid_areas": "Selected sectors are invalid",
"unknown": "Unexpected error: check your logs"
},
"step": {
"init": {
"data": {
"areas_arm_home": "Armed areas while at home (e.g 3,4 - optional)",
"areas_arm_night": "Armed areas at night (e.g. 3,4 - optional)"
"areas_arm_night": "Armed areas at night (e.g. 3,4 - optional)",
"areas_arm_vacation": "Armed areas when you are on vacation (e.g. 3,4 - optional)"
},
"description": "Define areas that are armed when you use Arm Away or Arm Night modes.",
"title": "Configure your e-connect system"
"description": "Define sectors you want to arm in different modes.",
"title": "Configure your e-Connect/IESS system"
}
}
}
Expand Down
19 changes: 10 additions & 9 deletions custom_components/econnect_alarm/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"error": {
"cannot_connect": "Failed to connect",
"invalid_auth": "Invalid authentication",
"invalid_areas": "Digited areas (home or night) are invalid",
"invalid_areas": "Selected sectors are invalid",
"unknown": "Unexpected error: check your logs"
},
"step": {
Expand All @@ -17,23 +17,24 @@
"password": "Password"
},
"description": "Insert your username and password to gain access. You can configure the system selecting \"Options\" after installing the integration.",
"title": "Configure your e-connect system"
"title": "Configure your e-Connect/IESS system"
}
}
},
"options": {
"error": {
"invalid_areas": "Digited areas (home or night) are invalid",
"invalid_areas": "Selected sectors are invalid",
"unknown": "Unexpected error: check your logs"
},
"step": {
"init": {
"data": {
"areas_arm_home": "Armed areas while at home (e.g 3,4 - optional)",
"areas_arm_night": "Armed areas at night (e.g. 3,4 - optional)"
},
"description": "Define areas that are armed when you use Arm Away or Arm Night modes.",
"title": "Configure your e-connect system"
"data": {
"areas_arm_home": "Armed areas while at home (e.g 3,4 - optional)",
"areas_arm_night": "Armed areas at night (e.g. 3,4 - optional)",
"areas_arm_vacation": "Armed areas when you are on vacation (e.g. 3,4 - optional)"
},
"description": "Define sectors you want to arm in different modes.",
"title": "Configure your e-Connect/IESS system"
}
}
}
Expand Down
32 changes: 32 additions & 0 deletions tests/test_devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_NIGHT,
STATE_ALARM_ARMED_VACATION,
STATE_ALARM_DISARMED,
STATE_UNAVAILABLE,
)
Expand All @@ -13,6 +14,7 @@
from custom_components.econnect_alarm.const import (
CONF_AREAS_ARM_HOME,
CONF_AREAS_ARM_NIGHT,
CONF_AREAS_ARM_VACATION,
)
from custom_components.econnect_alarm.devices import AlarmDevice

Expand All @@ -25,6 +27,7 @@ def test_device_constructor(client):
assert device._lastIds == {q.SECTORS: 0, q.INPUTS: 0}
assert device._sectors_home == []
assert device._sectors_night == []
assert device._sectors_vacation == []
assert device.state == STATE_UNAVAILABLE
assert device.sectors_armed == {}
assert device.sectors_disarmed == {}
Expand All @@ -37,13 +40,15 @@ def test_device_constructor_with_config(client):
config = {
CONF_AREAS_ARM_HOME: "3, 4",
CONF_AREAS_ARM_NIGHT: "1, 2, 3",
CONF_AREAS_ARM_VACATION: "5, 3",
}
device = AlarmDevice(client, config=config)
# Test
assert device._connection == client
assert device._lastIds == {q.SECTORS: 0, q.INPUTS: 0}
assert device._sectors_home == [3, 4]
assert device._sectors_night == [1, 2, 3]
assert device._sectors_vacation == [5, 3]
assert device.state == STATE_UNAVAILABLE
assert device.sectors_armed == {}
assert device.sectors_disarmed == {}
Expand Down Expand Up @@ -425,11 +430,38 @@ def test_get_state_armed_night_out_of_order(client):
assert device.get_state() == STATE_ALARM_ARMED_NIGHT


def test_get_state_armed_vacation(client):
"""Test when sectors are armed for vacation."""
device = AlarmDevice(client)
device._sectors_vacation = [4, 5, 6]
device.sectors_armed = {
0: {"id": 1, "index": 0, "element": 4, "excluded": False, "status": True, "name": "S1 Living Room"},
1: {"id": 2, "index": 1, "element": 5, "excluded": False, "status": True, "name": "S2 Bedroom"},
2: {"id": 3, "index": 2, "element": 6, "excluded": False, "status": True, "name": "S3 Outdoor"},
}
# Test (out of order keys to test sorting)
assert device.get_state() == STATE_ALARM_ARMED_VACATION


def test_get_state_armed_vacation_out_of_order(client):
"""Test when sectors are armed for vacation (out of order)."""
device = AlarmDevice(client)
device._sectors_vacation = [5, 6, 4]
device.sectors_armed = {
0: {"id": 1, "index": 0, "element": 6, "excluded": False, "status": True, "name": "S1 Living Room"},
1: {"id": 2, "index": 1, "element": 4, "excluded": False, "status": True, "name": "S2 Bedroom"},
2: {"id": 3, "index": 2, "element": 5, "excluded": False, "status": True, "name": "S3 Outdoor"},
}
# Test
assert device.get_state() == STATE_ALARM_ARMED_VACATION


def test_get_state_armed_away(client):
"""Test when sectors are armed but don't match home or night."""
device = AlarmDevice(client)
device._sectors_home = [1, 2, 3]
device._sectors_night = [4, 5, 6]
device._sectors_vacation = [4, 2]
device.sectors_armed = {
0: {"id": 1, "index": 0, "element": 1, "excluded": False, "status": True, "name": "S1 Living Room"},
1: {"id": 2, "index": 1, "element": 2, "excluded": False, "status": True, "name": "S2 Bedroom"},
Expand Down

0 comments on commit 6249eba

Please sign in to comment.