diff --git a/custom_components/securitas/__init__.py b/custom_components/securitas/__init__.py index ed6bce95..e462767f 100644 --- a/custom_components/securitas/__init__.py +++ b/custom_components/securitas/__init__.py @@ -46,6 +46,7 @@ DOMAIN = "securitas" CONF_COUNTRY = "country" +CONF_CODE_ARM_REQUIRED = "code_arm_required" CONF_CHECK_ALARM_PANEL = "check_alarm_panel" CONF_USE_2FA = "use_2FA" CONF_PERI_ALARM = "PERI_alarm" @@ -60,11 +61,13 @@ DEFAULT_USE_2FA = True DEFAULT_SCAN_INTERVAL = 120 +DEFAULT_CODE_ARM_REQUIRED = True DEFAULT_CHECK_ALARM_PANEL = True DEFAULT_DELAY_CHECK_OPERATION = 2 -DEFAULT_CODE = "" DEFAULT_PERI_ALARM = False +DEFAULT_COUNTRY = "ES" +EMPTY_CODE = "" PLATFORMS = [Platform.ALARM_CONTROL_PANEL, Platform.SENSOR, Platform.LOCK] HUB = None @@ -77,9 +80,10 @@ vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str, vol.Optional(CONF_USE_2FA, default=DEFAULT_USE_2FA): bool, - vol.Optional(CONF_COUNTRY, default="ES"): str, - vol.Optional(CONF_CODE, default=DEFAULT_CODE): str, + vol.Optional(CONF_COUNTRY, default=DEFAULT_COUNTRY): str, + vol.Optional(CONF_CODE, default=EMPTY_CODE): str, vol.Optional(CONF_PERI_ALARM, default=DEFAULT_PERI_ALARM): bool, + vol.Optional(CONF_CODE_ARM_REQUIRED, default=DEFAULT_CODE_ARM_REQUIRED): bool, vol.Optional( CONF_CHECK_ALARM_PANEL, default=DEFAULT_CHECK_ALARM_PANEL ): bool, @@ -111,6 +115,7 @@ async def async_update_options(hass: HomeAssistant, entry: ConfigEntry) -> None: entry.data.get(attrib) != entry.options.get(attrib) for attrib in ( CONF_CODE, + CONF_CODE_ARM_REQUIRED, CONF_SCAN_INTERVAL, CONF_CHECK_ALARM_PANEL, CONF_PERI_ALARM, @@ -136,8 +141,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: config[CONF_PASSWORD] = entry.data[CONF_PASSWORD] config[CONF_USE_2FA] = entry.data.get(CONF_USE_2FA, DEFAULT_USE_2FA) config[CONF_COUNTRY] = entry.data.get(CONF_COUNTRY, None) - config[CONF_CODE] = entry.data.get(CONF_CODE, DEFAULT_CODE) + config[CONF_CODE] = entry.data.get(CONF_CODE, None) config[CONF_PERI_ALARM] = entry.data.get(CONF_PERI_ALARM, DEFAULT_PERI_ALARM) + config[CONF_CODE_ARM_REQUIRED] = entry.data.get( + CONF_CODE_ARM_REQUIRED, DEFAULT_CODE_ARM_REQUIRED + ) config[CONF_CHECK_ALARM_PANEL] = entry.data.get( CONF_CHECK_ALARM_PANEL, DEFAULT_CHECK_ALARM_PANEL ) diff --git a/custom_components/securitas/alarm_control_panel.py b/custom_components/securitas/alarm_control_panel.py index 01b2d3a7..d855c8f4 100644 --- a/custom_components/securitas/alarm_control_panel.py +++ b/custom_components/securitas/alarm_control_panel.py @@ -18,10 +18,12 @@ from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.event import async_track_time_interval +from homeassistant.exceptions import ServiceValidationError from . import ( CONF_INSTALLATION_KEY, DEFAULT_SCAN_INTERVAL, + CONF_CODE_ARM_REQUIRED, DOMAIN, SecuritasDirectDevice, SecuritasHub, @@ -87,7 +89,6 @@ def __init__( """Initialize the Securitas alarm panel.""" self._state: str = AlarmControlPanelState.DISARMED self._last_status: str = AlarmControlPanelState.DISARMED - self._changed_by: str = "" self._device: str = installation.address self.entity_id: str = f"securitas_direct.{installation.number}" self._attr_unique_id: str = f"securitas_direct.{installation.number}" @@ -121,6 +122,13 @@ def __init__( self._update_unsub = async_track_time_interval( hass, self.async_update_status, self._update_interval ) + + self._code: str | None = client.config.get(CONF_CODE, None) + self._attr_code_format: CodeFormat | None = None + if self._code: + self._attr_code_format = CodeFormat.NUMBER if self._code.isdigit() else CodeFormat.TEXT + + self._attr_code_arm_required: bool = client.config.get(CONF_CODE_ARM_REQUIRED, True) if self._code else False self._attr_device_info: DeviceInfo = DeviceInfo( identifiers={(DOMAIN, self._attr_unique_id)}, @@ -155,21 +163,6 @@ def name(self) -> str: """Return the name of the device.""" return self.installation.alias - @property - def code_format(self) -> CodeFormat: - """Return one or more digits/characters.""" - return CodeFormat.NUMBER - - @property - def code_arm_required(self) -> bool: - """Whether the code is required for arm actions.""" - return False - - @property - def changed_by(self) -> str: - """Return the last change triggered by.""" - return self._changed_by - async def get_arm_state(self) -> CheckAlarmStatus: """Get alarm state.""" reference_id: str = await self.client.session.check_alarm(self.installation) @@ -233,25 +226,28 @@ def update_status_alarm(self, status: CheckAlarmStatus | None = None) -> None: f"Securitas Direct integration options.", ) - def check_code(self, code=None) -> bool: - """Check that the code entered in the panel matches the code in the config.""" - - result: bool = False - - if ( - self.client.config.get(CONF_CODE, "") == "" - or str(self.client.config.get(CONF_CODE, "")) == str(code) - or self.client.config.get(CONF_CODE, None) is None - ): - result = True - else: - _LOGGER.info("PIN doesn't match") + def _check_code_for_arm_if_required(self, code: str | None) -> bool: + """Check that the code entered in the panel matches the code in the config only if arm require code is enable.""" + if not self.code_arm_required: + return True + return self._check_code(code) + def _check_code(self, code: str | None) -> bool: + """Check that the code entered in the panel matches the code in the config.""" + result: bool = not self._code or self._code == code + if not result: + raise ServiceValidationError( + translation_domain=DOMAIN, + translation_key="invalid_pin_code", + translation_placeholders={ + "entity_id": self.entity_id, + }, + ) return result async def async_alarm_disarm(self, code: str | None = None) -> None: """Send disarm command.""" - if self.check_code(code): + if self._check_code(code): self.__force_state(AlarmControlPanelState.DISARMING) disarm_status: DisarmStatus = DisarmStatus() try: @@ -324,25 +320,25 @@ async def set_arm_state(self, mode: str) -> None: async def async_alarm_arm_home(self, code: str | None = None): """Send arm home command.""" - if self.check_code(code): + if self._check_code_for_arm_if_required(code): self.__force_state(AlarmControlPanelState.ARMING) await self.set_arm_state(AlarmControlPanelState.ARMED_HOME) async def async_alarm_arm_away(self, code: str | None = None): """Send arm away command.""" - if self.check_code(code): + if self._check_code_for_arm_if_required(code): self.__force_state(AlarmControlPanelState.ARMING) await self.set_arm_state(AlarmControlPanelState.ARMED_AWAY) async def async_alarm_arm_night(self, code: str | None = None): """Send arm home command.""" - if self.check_code(code): + if self._check_code_for_arm_if_required(code): self.__force_state(AlarmControlPanelState.ARMING) await self.set_arm_state(AlarmControlPanelState.ARMED_NIGHT) async def async_alarm_arm_custom_bypass(self, code: str | None = None): """Send arm perimeter command.""" - if self.check_code(code): + if self._check_code_for_arm_if_required(code): self.__force_state(AlarmControlPanelState.ARMING) await self.set_arm_state(AlarmControlPanelState.ARMED_CUSTOM_BYPASS) diff --git a/custom_components/securitas/config_flow.py b/custom_components/securitas/config_flow.py index 520886d3..f0071abe 100644 --- a/custom_components/securitas/config_flow.py +++ b/custom_components/securitas/config_flow.py @@ -25,6 +25,7 @@ from homeassistant.helpers.selector import selector from . import ( + CONF_CODE_ARM_REQUIRED, CONF_CHECK_ALARM_PANEL, CONF_COUNTRY, CONF_DELAY_CHECK_OPERATION, @@ -37,10 +38,12 @@ CONF_PERI_ALARM, CONF_USE_2FA, CONFIG_SCHEMA, + DEFAULT_CODE_ARM_REQUIRED, DEFAULT_CHECK_ALARM_PANEL, DEFAULT_DELAY_CHECK_OPERATION, DEFAULT_PERI_ALARM, DEFAULT_SCAN_INTERVAL, + EMPTY_CODE, DOMAIN, SecuritasDirectDevice, SecuritasHub, @@ -216,6 +219,7 @@ async def async_step_import(self, user_input: dict): self.config[CONF_PASSWORD] = user_input[CONF_PASSWORD] self.config[CONF_COUNTRY] = user_input[CONF_COUNTRY] self.config[CONF_CODE] = user_input[CONF_CODE] + self.config[CONF_CODE_ARM_REQUIRED] = user_input.get(CONF_CODE_ARM_REQUIRED, DEFAULT_CODE_ARM_REQUIRED) self.config[CONF_CHECK_ALARM_PANEL] = user_input[CONF_CHECK_ALARM_PANEL] self.config[CONF_SCAN_INTERVAL] = user_input[CONF_SCAN_INTERVAL] self.config[CONF_DELAY_CHECK_OPERATION] = user_input[CONF_DELAY_CHECK_OPERATION] @@ -263,10 +267,12 @@ async def async_step_init( return await self.async_step_mappings() scan_interval = self._get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL) - code = "" delay_check_operation = self._get( CONF_DELAY_CHECK_OPERATION, DEFAULT_DELAY_CHECK_OPERATION ) + code_arm_required = self._get( + CONF_CODE_ARM_REQUIRED, DEFAULT_CODE_ARM_REQUIRED + ) check_alarm_panel = self._get( CONF_CHECK_ALARM_PANEL, DEFAULT_CHECK_ALARM_PANEL ) @@ -274,7 +280,8 @@ async def async_step_init( schema = vol.Schema( { - vol.Optional(CONF_CODE, default=code): str, + vol.Optional(CONF_CODE, default=self._get(CONF_CODE, EMPTY_CODE)): str, + vol.Optional(CONF_CODE_ARM_REQUIRED, default=code_arm_required): bool, vol.Optional(CONF_PERI_ALARM, default=peri_alarm): bool, vol.Optional( CONF_CHECK_ALARM_PANEL, default=check_alarm_panel diff --git a/custom_components/securitas/translations/en.json b/custom_components/securitas/translations/en.json index 9da929cb..98af262b 100644 --- a/custom_components/securitas/translations/en.json +++ b/custom_components/securitas/translations/en.json @@ -11,6 +11,7 @@ "use_2FA": "Use 2FA", "country": "Country Code", "code": "PIN Code (leave empty for no PIN)", + "code_arm_required": "Require PIN code to arm?", "PERI_alarm": "Is there a Perimetral alarm?", "check_alarm_panel": "Check alarm panel's status in every request", "scan_interval": "Update scan interval (sec)", @@ -29,11 +30,17 @@ } } }, + "exceptions": { + "invalid_pin_code": { + "message": "The PIN code doesn't match the configured one" + } + }, "options": { "step": { "init": { "data": { "code": "PIN Code (leave empty for no PIN)", + "code_arm_required": "Require PIN code to arm?", "PERI_alarm": "Is there a Perimetral alarm?", "check_alarm_panel": "Check alarm panel's status in every request (not recommended)", "scan_interval": "Update scan interval (sec)",