From 4b4a8f04898ebad5b972b1ea4ebd71016b77a537 Mon Sep 17 00:00:00 2001 From: Clinton Gormley Date: Thu, 26 Feb 2026 08:59:03 +0000 Subject: [PATCH 1/6] fix: validate mapping defaults against available options in config flow When peri_alarm changes between step 1 and step 2 of the options flow, saved mapping values (e.g. PERI_ONLY) may not be in the new options list (STD_OPTIONS). Reset to the appropriate default when this happens. Co-Authored-By: Claude Opus 4.6 --- custom_components/securitas/config_flow.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/custom_components/securitas/config_flow.py b/custom_components/securitas/config_flow.py index 520886d3..9ff6128b 100644 --- a/custom_components/securitas/config_flow.py +++ b/custom_components/securitas/config_flow.py @@ -299,13 +299,20 @@ async def async_step_mappings( # Determine defaults for mapping dropdowns defaults = PERI_DEFAULTS if peri_alarm else STD_DEFAULTS - map_home = self._get(CONF_MAP_HOME, defaults[CONF_MAP_HOME]) - map_away = self._get(CONF_MAP_AWAY, defaults[CONF_MAP_AWAY]) - map_night = self._get(CONF_MAP_NIGHT, defaults[CONF_MAP_NIGHT]) - map_custom = self._get(CONF_MAP_CUSTOM, defaults[CONF_MAP_CUSTOM]) - - # Build dropdown options based on perimeter setting options = PERI_OPTIONS if peri_alarm else STD_OPTIONS + valid_values = {state.value for state in options} + + def _valid_map(key: str) -> str: + """Return saved mapping if valid for current options, else default.""" + val = self._get(key, defaults[key]) + return val if val in valid_values else defaults[key] + + map_home = _valid_map(CONF_MAP_HOME) + map_away = _valid_map(CONF_MAP_AWAY) + map_night = _valid_map(CONF_MAP_NIGHT) + map_custom = _valid_map(CONF_MAP_CUSTOM) + + # Build dropdown options select_options = [ {"value": state.value, "label": STATE_LABELS[state]} for state in options From 85e651052f83c71acf0eab8e95f75977cd8ac335 Mon Sep 17 00:00:00 2001 From: Clinton Gormley Date: Thu, 26 Feb 2026 09:05:55 +0000 Subject: [PATCH 2/6] fix: persist migrated mapping values to entry.data The migration logic derived mapping defaults from the PERI_alarm checkbox but only set them in the runtime config dict. Without calling async_update_entry, the migrated values were lost on restart, causing the migration to re-run every time. Co-Authored-By: Claude Opus 4.6 --- custom_components/securitas/__init__.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/custom_components/securitas/__init__.py b/custom_components/securitas/__init__.py index ed6bce95..ae97d24e 100644 --- a/custom_components/securitas/__init__.py +++ b/custom_components/securitas/__init__.py @@ -164,6 +164,16 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: config[CONF_MAP_AWAY] = defaults[CONF_MAP_AWAY] config[CONF_MAP_NIGHT] = defaults[CONF_MAP_NIGHT] config[CONF_MAP_CUSTOM] = defaults[CONF_MAP_CUSTOM] + hass.config_entries.async_update_entry( + entry, + data={ + **entry.data, + CONF_MAP_HOME: config[CONF_MAP_HOME], + CONF_MAP_AWAY: config[CONF_MAP_AWAY], + CONF_MAP_NIGHT: config[CONF_MAP_NIGHT], + CONF_MAP_CUSTOM: config[CONF_MAP_CUSTOM], + }, + ) if CONF_DEVICE_ID in entry.data: config[CONF_DEVICE_ID] = entry.data[CONF_DEVICE_ID] From b821b8ddfffaf2dd2fd9dc50d3f993f7aaa73bca Mon Sep 17 00:00:00 2001 From: Clinton Gormley Date: Thu, 26 Feb 2026 09:09:42 +0000 Subject: [PATCH 3/6] fix: use string values in mapping defaults for selector compatibility The config_flow selector expects string values, but STD_DEFAULTS and PERI_DEFAULTS contained SecuritasState enum objects. This caused mismatches when comparing defaults against valid_values (which are strings from state.value). Use .value to store strings directly. Co-Authored-By: Claude Opus 4.6 --- .../securitas_direct_new_api/const.py | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/custom_components/securitas/securitas_direct_new_api/const.py b/custom_components/securitas/securitas_direct_new_api/const.py index 7cc394e4..d5be8198 100644 --- a/custom_components/securitas/securitas_direct_new_api/const.py +++ b/custom_components/securitas/securitas_direct_new_api/const.py @@ -71,16 +71,16 @@ class SecuritasState(StrEnum): ] # Default mappings matching current behavior (keyed by HA button name) -STD_DEFAULTS: dict[str, SecuritasState] = { - "map_home": SecuritasState.PARTIAL, - "map_away": SecuritasState.TOTAL, - "map_night": SecuritasState.PARTIAL, - "map_custom": SecuritasState.NOT_USED, +STD_DEFAULTS: dict[str, str] = { + "map_home": SecuritasState.PARTIAL.value, + "map_away": SecuritasState.TOTAL.value, + "map_night": SecuritasState.PARTIAL.value, + "map_custom": SecuritasState.NOT_USED.value, } -PERI_DEFAULTS: dict[str, SecuritasState] = { - "map_home": SecuritasState.PARTIAL, - "map_away": SecuritasState.TOTAL_PERI, - "map_night": SecuritasState.PARTIAL_PERI, - "map_custom": SecuritasState.PERI_ONLY, +PERI_DEFAULTS: dict[str, str] = { + "map_home": SecuritasState.PARTIAL.value, + "map_away": SecuritasState.TOTAL_PERI.value, + "map_night": SecuritasState.PARTIAL_PERI.value, + "map_custom": SecuritasState.PERI_ONLY.value, } From 408031a3b413b9fc135c612e1742454ec368bb94 Mon Sep 17 00:00:00 2001 From: Clinton Gormley Date: Thu, 26 Feb 2026 09:17:02 +0000 Subject: [PATCH 4/6] chore: add strings.json and translate mappings to Spanish Create strings.json as the source of truth for translations, synced with en.json. Add missing mappings section to es.json with Spanish translations for the alarm state mapping options step. Co-Authored-By: Claude Opus 4.6 --- custom_components/securitas/strings.json | 55 +++++++++++++++++++ .../securitas/translations/es.json | 10 ++++ 2 files changed, 65 insertions(+) create mode 100644 custom_components/securitas/strings.json diff --git a/custom_components/securitas/strings.json b/custom_components/securitas/strings.json new file mode 100644 index 00000000..9da929cb --- /dev/null +++ b/custom_components/securitas/strings.json @@ -0,0 +1,55 @@ +{ + "config": { + "abort": { + "already_configured": "Service is already configured" + }, + "step": { + "user": { + "data": { + "username": "Username", + "password": "Password", + "use_2FA": "Use 2FA", + "country": "Country Code", + "code": "PIN Code (leave empty for no PIN)", + "PERI_alarm": "Is there a Perimetral alarm?", + "check_alarm_panel": "Check alarm panel's status in every request", + "scan_interval": "Update scan interval (sec)", + "delay_check_operation": "Delay to check the arming and disarming operations (sec)" + }, + "description": "Login into Securitas Direct.", + "title": "Login" + }, + "phone_list": { + "description": "Please select the phone to send the 2FA code", + "title": "Securitas Direct 2FA" + }, + "otp_challenge": { + "description": "Enter the code you received on your phone", + "title": "Securitas Direct 2FA" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "code": "PIN Code (leave empty for no PIN)", + "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)", + "delay_check_operation": "Delay to check the arming and disarming operation (sec)" + } + }, + "mappings": { + "title": "Alarm State Mappings", + "description": "Map each Home Assistant alarm action to a Securitas alarm mode. Set to 'Not used' to disable a button.", + "data": { + "map_home": "Home", + "map_away": "Away", + "map_night": "Night", + "map_custom": "Custom" + } + } + } + } +} diff --git a/custom_components/securitas/translations/es.json b/custom_components/securitas/translations/es.json index 2f591f57..b392d621 100644 --- a/custom_components/securitas/translations/es.json +++ b/custom_components/securitas/translations/es.json @@ -39,6 +39,16 @@ "scan_interval": "Intervalo de actualización (seg)", "delay_check_operation": "Retardo para comprobar las operaciones de armado y desarmado (seg)" } + }, + "mappings": { + "title": "Asignación de estados de alarma", + "description": "Asigna cada acción de alarma de Home Assistant a un modo de alarma Securitas. Selecciona 'No usado' para desactivar un botón.", + "data": { + "map_home": "Casa", + "map_away": "Fuera", + "map_night": "Noche", + "map_custom": "Personalizado" + } } } } From 774e6bd9982c6e0b51cb8686fb181e3c788039b9 Mon Sep 17 00:00:00 2001 From: Clinton Gormley Date: Thu, 26 Feb 2026 11:51:24 +0000 Subject: [PATCH 5/6] feat: add Partial Night state and rename Partial to Partial Day MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Partial Night (ARMNIGHT1 / protomResponse Q) exists in some countries. Rename PARTIAL → PARTIAL_DAY, PARTIAL_PERI → PARTIAL_DAY_PERI, and add PARTIAL_NIGHT and PARTIAL_NIGHT_PERI with their commands and mappings. - Update STD_OPTIONS/PERI_OPTIONS to include the new states - Default Night button to Partial Night (STD) / Partial Night + Peri (PERI) - Use .value strings in defaults for selector compatibility - Create strings.json, sync es.json with mappings and new fields - Update plan doc to correct Q/ARMNIGHT1 documentation Co-Authored-By: Claude Opus 4.6 --- .../securitas_direct_new_api/const.py | 40 ++++++++++------- ...02-12-configurable-alarm-mapping-design.md | 44 ++++++++++--------- 2 files changed, 48 insertions(+), 36 deletions(-) diff --git a/custom_components/securitas/securitas_direct_new_api/const.py b/custom_components/securitas/securitas_direct_new_api/const.py index d5be8198..9a33ac71 100644 --- a/custom_components/securitas/securitas_direct_new_api/const.py +++ b/custom_components/securitas/securitas_direct_new_api/const.py @@ -13,20 +13,24 @@ class SecuritasState(StrEnum): """Verisure alarm states - combinations of interior mode and perimeter.""" NOT_USED = "not_used" DISARMED = "disarmed" - PARTIAL = "partial" + PARTIAL_DAY = "partial_day" + PARTIAL_NIGHT = "partial_night" TOTAL = "total" PERI_ONLY = "peri_only" - PARTIAL_PERI = "partial_peri" + PARTIAL_DAY_PERI = "partial_day_peri" + PARTIAL_NIGHT_PERI = "partial_night_peri" TOTAL_PERI = "total_peri" # Map SecuritasState -> API arm command string STATE_TO_COMMAND: dict[SecuritasState, str] = { SecuritasState.DISARMED: "DARM1DARMPERI", - SecuritasState.PARTIAL: "ARMDAY1", + SecuritasState.PARTIAL_DAY: "ARMDAY1", + SecuritasState.PARTIAL_NIGHT: "ARMNIGHT1", SecuritasState.TOTAL: "ARM1", SecuritasState.PERI_ONLY: "PERI1", - SecuritasState.PARTIAL_PERI: "ARMDAY1PERI1", + SecuritasState.PARTIAL_DAY_PERI: "ARMDAY1PERI1", + SecuritasState.PARTIAL_NIGHT_PERI: "ARMNIGHT1PERI1", SecuritasState.TOTAL_PERI: "ARM1PERI1", } @@ -34,8 +38,9 @@ class SecuritasState(StrEnum): PROTO_TO_STATE: dict[str, SecuritasState] = { "D": SecuritasState.DISARMED, "E": SecuritasState.PERI_ONLY, - "P": SecuritasState.PARTIAL, - "B": SecuritasState.PARTIAL_PERI, + "P": SecuritasState.PARTIAL_DAY, + "Q": SecuritasState.PARTIAL_NIGHT, + "B": SecuritasState.PARTIAL_DAY_PERI, "T": SecuritasState.TOTAL, "A": SecuritasState.TOTAL_PERI, } @@ -44,10 +49,12 @@ class SecuritasState(StrEnum): STATE_LABELS: dict[SecuritasState, str] = { SecuritasState.NOT_USED: "Not used", SecuritasState.DISARMED: "Disarmed", - SecuritasState.PARTIAL: "Partial", + SecuritasState.PARTIAL_DAY: "Partial Day", + SecuritasState.PARTIAL_NIGHT: "Partial Night", SecuritasState.TOTAL: "Total", SecuritasState.PERI_ONLY: "Perimeter only", - SecuritasState.PARTIAL_PERI: "Partial + Perimeter", + SecuritasState.PARTIAL_DAY_PERI: "Partial Day + Perimeter", + SecuritasState.PARTIAL_NIGHT_PERI: "Partial Night + Perimeter", SecuritasState.TOTAL_PERI: "Total + Perimeter", } @@ -55,7 +62,8 @@ class SecuritasState(StrEnum): STD_OPTIONS: list[SecuritasState] = [ SecuritasState.NOT_USED, SecuritasState.DISARMED, - SecuritasState.PARTIAL, + SecuritasState.PARTIAL_DAY, + SecuritasState.PARTIAL_NIGHT, SecuritasState.TOTAL, ] @@ -63,24 +71,26 @@ class SecuritasState(StrEnum): PERI_OPTIONS: list[SecuritasState] = [ SecuritasState.NOT_USED, SecuritasState.DISARMED, - SecuritasState.PARTIAL, + SecuritasState.PARTIAL_DAY, + SecuritasState.PARTIAL_NIGHT, SecuritasState.TOTAL, SecuritasState.PERI_ONLY, - SecuritasState.PARTIAL_PERI, + SecuritasState.PARTIAL_DAY_PERI, + SecuritasState.PARTIAL_NIGHT_PERI, SecuritasState.TOTAL_PERI, ] # Default mappings matching current behavior (keyed by HA button name) STD_DEFAULTS: dict[str, str] = { - "map_home": SecuritasState.PARTIAL.value, + "map_home": SecuritasState.PARTIAL_DAY.value, "map_away": SecuritasState.TOTAL.value, - "map_night": SecuritasState.PARTIAL.value, + "map_night": SecuritasState.PARTIAL_NIGHT.value, "map_custom": SecuritasState.NOT_USED.value, } PERI_DEFAULTS: dict[str, str] = { - "map_home": SecuritasState.PARTIAL.value, + "map_home": SecuritasState.PARTIAL_DAY.value, "map_away": SecuritasState.TOTAL_PERI.value, - "map_night": SecuritasState.PARTIAL_PERI.value, + "map_night": SecuritasState.PARTIAL_NIGHT_PERI.value, "map_custom": SecuritasState.PERI_ONLY.value, } diff --git a/docs/plans/2026-02-12-configurable-alarm-mapping-design.md b/docs/plans/2026-02-12-configurable-alarm-mapping-design.md index e2333009..99a3b84e 100644 --- a/docs/plans/2026-02-12-configurable-alarm-mapping-design.md +++ b/docs/plans/2026-02-12-configurable-alarm-mapping-design.md @@ -8,18 +8,20 @@ The integration also repurposes HA buttons for different Securitas states depend ## Verisure Alarm States -Verisure provides 6 alarm states, which are combinations of interior mode (Disarmed/Partial/Total) and perimeter (on/off): +Verisure provides 8 alarm states, which are combinations of interior mode (Disarmed/Partial Day/Partial Night/Total) and perimeter (on/off): -| Securitas State | `protomResponse` | API Command | `msg` key | -|-----------------------|------------------|-----------------|-------------------------------------------------| -| Disarmed | `D` | `DARM1DARMPERI` | `alarm-manager.inactive_alarm` | -| Perimeter only | `E` | `PERI1` | `alarm-manager.status_panel.active_perimetral_alarm_msg` | -| Partial | `P` | `ARMDAY1` | `alarm-manager.status_panel.armed_partial` | -| Partial + Perimeter | `B` | `ARMDAY1PERI1` | `alarm-manager.status_panel.armed_partial_plus_perimeter` | -| Total | `T` | `ARM1` | `alarm-manager.status.active_alarm_msg` | -| Total + Perimeter | `A` | `ARM1PERI1` | `alarm-manager.active_perimeter_plus_alarm` | +| Securitas State | `protomResponse` | API Command | `msg` key | +|----------------------------|------------------|------------------|-------------------------------------------------| +| Disarmed | `D` | `DARM1DARMPERI` | `alarm-manager.inactive_alarm` | +| Perimeter only | `E` | `PERI1` | `alarm-manager.status_panel.active_perimetral_alarm_msg` | +| Partial Day | `P` | `ARMDAY1` | `alarm-manager.status_panel.armed_partial` | +| Partial Night | `Q` | `ARMNIGHT1` | `alarm-manager.status_panel.armed_night` | +| Partial Day + Perimeter | `B` | `ARMDAY1PERI1` | `alarm-manager.status_panel.armed_partial_plus_perimeter` | +| Partial Night + Perimeter | — | `ARMNIGHT1PERI1` | — | +| Total | `T` | `ARM1` | `alarm-manager.status.active_alarm_msg` | +| Total + Perimeter | `A` | `ARM1PERI1` | `alarm-manager.active_perimeter_plus_alarm` | -Note: There is no separate "Night" concept in Verisure. The existing `Q` code / `ARMNIGHT1` command from the original integration is not a real Verisure mode. +Note: Partial Night (`Q` / `ARMNIGHT1`) exists in some countries but not all. The `protomResponse` code and `msg` key for Partial Night + Perimeter are not yet confirmed. ## Design @@ -34,20 +36,20 @@ The options flow (Configure button on integration page) presents: 1. **Perimeter checkbox** (same as today): "My system has perimeter sensors" 2. **Per-button dropdowns** for Home, Away, Night, Custom: - - If **no perimeter**, each dropdown offers: Not used, Disarmed, Partial, Total - - If **perimeter**, each dropdown offers: Not used, Disarmed, Partial, Total, Perimeter only, Partial + Perimeter, Total + Perimeter + - If **no perimeter**, each dropdown offers: Not used, Disarmed, Partial Day, Partial Night, Total + - If **perimeter**, each dropdown offers: Not used, Disarmed, Partial Day, Partial Night, Total, Perimeter only, Partial Day + Perimeter, Partial Night + Perimeter, Total + Perimeter ### Default Mappings Defaults match current integration behavior to avoid breaking changes: -| HA State | STD default (no perimeter) | PERI default (with perimeter) | -|------------|---------------------------|-------------------------------| -| Disarmed | Disarmed (`D`) | Disarmed (`D`) | -| Home | Partial (`P`) | Partial (`P`) | -| Away | Total (`T`) | Total + Perimeter (`A`) | -| Night | Partial (`P`) | Partial + Perimeter (`B`) | -| Custom | Not used | Perimeter only (`E`) | +| HA State | STD default (no perimeter) | PERI default (with perimeter) | +|------------|-----------------------------|-------------------------------------| +| Disarmed | Disarmed (`D`) | Disarmed (`D`) | +| Home | Partial Day (`P`) | Partial Day (`P`) | +| Away | Total (`T`) | Total + Perimeter (`A`) | +| Night | Partial Night (`Q`) | Partial Night + Perimeter | +| Custom | Not used | Perimeter only (`E`) | ### Bidirectional Mapping @@ -55,7 +57,7 @@ The stored config drives both directions: **Outgoing (HA button press -> Securitas command):** Each Securitas state maps to a known API command (see table above). When the user presses "Away" and it's configured as "Total + Perimeter", send `ARM1PERI1`. Disarm always sends `DARM1DARMPERI` (safe: disarms everything regardless of current state). -**Incoming (status poll -> HA state):** Reverse lookup from the user's config. When `protomResponse` is `B`, find which HA button is configured as "Partial + Perimeter" and set that HA state. If a response code comes back that isn't mapped to any configured button, set the HA state to `ARMED_CUSTOM_BYPASS`. +**Incoming (status poll -> HA state):** Reverse lookup from the user's config. When `protomResponse` is `B`, find which HA button is configured as "Partial Day + Perimeter" and set that HA state. If a response code comes back that isn't mapped to any configured button, set the HA state to `ARMED_CUSTOM_BYPASS`. ### Supported Features @@ -67,7 +69,7 @@ The `supported_features` property on the alarm entity should be dynamic based on Replace the `SecDirAlarmState` enum and `STD_COMMANDS_MAP`/`PERI_COMMANDS_MAP` with a simpler model: -- Define a `SecuritasState` enum with values: `NOT_USED`, `DISARMED`, `PARTIAL`, `TOTAL`, `PERI_ONLY`, `PARTIAL_PERI`, `TOTAL_PERI` +- Define a `SecuritasState` enum with values: `NOT_USED`, `DISARMED`, `PARTIAL_DAY`, `PARTIAL_NIGHT`, `TOTAL`, `PERI_ONLY`, `PARTIAL_DAY_PERI`, `PARTIAL_NIGHT_PERI`, `TOTAL_PERI` - Each value maps to a command string and a `protomResponse` code - Define the two default mapping presets (STD and PERI) From c40ef895cca691e884c87c0a4ec0e1ec13e5a92f Mon Sep 17 00:00:00 2001 From: Clinton Gormley Date: Thu, 26 Feb 2026 12:45:07 +0000 Subject: [PATCH 6/6] docs: add alarm state mapping section to README Explains how Securitas modes map to HA alarm buttons, default mappings for standard and perimeter installations, and the disarm-before-rearm behavior. Notes that Partial Night + Perimeter status code is unknown. Co-Authored-By: Claude Opus 4.6 --- README.md | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index dcf614a7..c03de598 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # securitas-direct-new-api + This repository contains the new securitas direct API that can be integrated in Home Assistant. ## Features @@ -25,12 +26,13 @@ _or_ 4. Select that entry and click the download button. ⬇️ ## Setup + ![Options](./docs/images/setup.png) This integration supports config flow, so go to the list of integrations and click on add Securitas from there. - Enter the username and password for your Securitas account. -- Use 2FA (default: yes). Uncheck this box if you want to skip the 2FA. +- Use 2FA (default: yes). Uncheck this box if you want to skip the 2FA. - Country Code. One of BR (Brasil), CL (Chile), ES (Spain), FR (France), GB (Great Britain), IE (Ireland), IT (Italy) and AR (Argentine). If you are outside of those countries, try entering "default" and if that doesn't work open an issue to see if we can expand. - PIN code (optional). If you set a PIN here, you will need to enter it to arm or disarm the alarm using the Home Assistant panel. This PIN is independent of Securitas. It is never sent to Securitas and it has nothing to do with your account with them. - Perimetral alarm (default: no). If you have sensors outside of your home, check the box. Otherwise, leave the box unchecked. This will ensure that the integration sends the correct commands to arm the alarm. @@ -38,13 +40,63 @@ This integration supports config flow, so go to the list of integrations and cli - Update scan interval (default: 120). How often the integration checks the status of the alarm. ## Options + If you need to change some of the options, you can configure the integration (in HA, go to Settings -> Integrations -> Securitas Direct -> Configure) ![Options](./docs/images/options.png) +## Alarm State Mapping + +Securitas Direct supports several alarm modes, but Home Assistant's alarm panel only has four buttons: **Home**, **Away**, **Night**, and **Custom Bypass**. This integration lets you choose which Securitas mode each button activates. + +### Securitas Alarm Modes + +| Mode | Description | +| ------------------------- | ------------------------------------- | +| Disarmed | Alarm is off | +| Partial Day | Interior sensors armed (daytime) | +| Partial Night | Interior sensors armed (nighttime) | +| Total | All interior sensors armed | +| Perimeter Only | External/outdoor sensors only | +| Partial Day + Perimeter | Daytime interior + external sensors | +| Partial Night + Perimeter | Nighttime interior + external sensors | +| Total + Perimeter | All interior + external sensors | + +### How It Works + +Each of the four HA alarm buttons can be mapped to any Securitas mode in the integration options (Settings -> Integrations -> Securitas Direct -> Configure). If you set a button to "Not Used", it will be hidden from the alarm panel. + +When the integration checks the alarm status, it translates the Securitas response back to the correct HA state using the same mapping. For example, if you mapped the **Away** button to "Total + Perimeter", then when Securitas reports "Total + Perimeter" the alarm panel will show "Armed Away". + +When switching between armed modes (e.g. from "Armed Home" to "Armed Away"), the integration automatically disarms the alarm first and then arms with the new mode. This is necessary because the Securitas API treats interior and perimeter as independent axes -- sending a new interior mode without disarming first could leave the perimeter in an unexpected state. + +### Default Mappings + +**Standard installations** (no perimeter sensors): + +| HA Button | Securitas Mode | +| --------- | ----------------- | +| Home | Partial Day | +| Away | Total | +| Night | Partial Night | +| Custom | Not Used (hidden) | + +**Perimeter installations** (external sensors enabled): + +| HA Button | Securitas Mode | +| --------- | ------------------------- | +| Home | Partial Day | +| Away | Total + Perimeter | +| Night | Partial Night + Perimeter | +| Custom | Perimeter Only | + +### Known Limitations + +The status code for **Partial Night + Perimeter** is currently unknown. If your alarm is in this state, it will show as "Custom Bypass" in Home Assistant. If you have this situation, please [open an issue](https://github.com/guerrerotook/securitas-direct-new-api/issues) so we can identify the correct status code and add proper support. + ## New Features -Added a button to update the status of your alamr using the API. Thanks to @edwin-anne. +Added a button to update the status of your alarm using the API. Thanks to @edwin-anne. ## Breaking changes