Skip to content

Package: Unifi Access

Version: 1.1.0
Description: Unified management for G4 Doorbell Pro Fingerprint & NFC.

Package Diagram

Executive Summary

The Unifi Access package implements a unified access control layer on top of the Ubiquiti G4 Doorbell Pro. It abstracts distinct hardware events (NFC, Fingerprint) into a single logical "Unified User" entity backed by MQTT discovery. The system provides granular, per-credential access management (switches), a centralized Guest Schedule (input_helpers), and "Fail-Closed" authorization logic. State persistence is handled via MQTT Retain to ensure reliability across reboots.

Process Description (Non-Technical)

  1. Scan: A user scans their fingerprint or NFC tag at the doorbell.
  2. Identify: The system instantly recognizes the person (e.g., "Dad" or "Guest").
  3. Check Permissions:
    • Family: Checks if their specific access (Fingerprint or Card) is currently switched ON.
    • Guests: Checks if they are allowed today AND if it is currently within the "Guest Hours".
  4. Unlock: If valid, the door unlocks and a snapshot is sent to your phone. If denied, a security alert is triggered.

Dashboard Connections

This package powers the following dashboard views:

  • Home Access Center: The Home Access Center serves as the central administrative hub for managing physical security and entry credentials. Its primary function is to maintain a unified user registry where distinct hardware identifiers—such as Fingerprints and NFC tags—are mapped to specific individuals. This abstraction allows homeowners to easily enroll new keys, assign them to family members or guests, and instantly revoke permissions if a credential is lost or compromised. (Uses 8 entities)
  • Front Door: This view is the central security hub for the Front Door. It features live feeds and event clips from Frigate cameras (Doorbell and Porch). Users can control the smart lock, arm/disarm the alarm system, and manage the front entry lights. It also monitors door status and presence, with settings for occupancy-based automations. (Uses 1 entities)
  • Home: The Home dashboard serves as the central information hub. It features a large clock and family calendars, alongside detailed weather forecasts. Key home stats are highlighted, including real-time energy prices, power usage, and the status of major appliances like the dishwasher and washing machine. The view also provides a high-level overview of the entire house, displaying camera feeds and status summaries for all key rooms (Sauna, Bathroom, Bedroom, etc.) using 'Streamline' area cards. (Uses 1 entities)
  • Mud Room: This dashboard manages the Mud Room. It provides control for the ceiling light and visualizes occupancy status via motion sensors. The view includes standard settings for occupancy automations and light scheduling. (Uses 1 entities)

Architecture Diagram

The sequence starts when the G4 Doorbell Pro broadcasts a hardware event (fingerprint or NFC) to Home Assistant. The automation logic intercepts this event and resolves the raw ID to a "Unified User" using a mapped MQTT lookup. Once the user is identified, the system performs a multi-stage authorization check: first verifying the specific credential type is enabled (e.g., is NFC allowed for this user?), and then—if the user is a Guest—verifying the request against the global Guest Schedule. A "Pass" decision triggers the Z-Wave lock and logs the entry; a "Fail" decision prevents access and logs a security warning.

sequenceDiagram
    participant User
    participant G4 as G4 Doorbell Pro
    participant HA as Home Assistant
    participant Logic as Access Logic
    participant MQTT as MQTT Broker
    participant Lock as Yale Lock
    participant Notify as Notification Svc

    User->>G4: Scans Fingerprint / NFC
    G4->>HA: Event (ulp_id)
    HA->>Logic: Trigger Automation

    rect rgb(240, 240, 240)
        Note over Logic: Identity Resolution
        Logic->>MQTT: Query User for ulp_id
        MQTT-->>Logic: Returns "Unified User"
    end

    rect rgb(230, 245, 255)
        Note over Logic: Authorization Check
        alt is Unknown ID
            Logic->>Logic: Auto-Learn (Create "Unknown")
            Logic->>Notify: Alert "New ID Found"
        else is Known User
            Logic->>Logic: Check Access Switch (FP/NFC)
            opt is Guest
                Logic->>Logic: Validate Schedule (Time/Day)
            end

            alt Access Granted
                Logic->>Lock: Unlock Command
                Logic->>Notify: Send "Unlocked" Snapshot
            else Access Denied
                Logic->>Notify: Send Critical Alert
            end
        end
    end

Configuration (Source Code)

# ------------------------------------------------------------------------------
# Package: Unifi Unified Access Control
# Version: 1.1.0
# Description: Unified management for G4 Doorbell Pro Fingerprint & NFC.
#              - Shared Identity: 'select.unifi_user_ID'
#              - Separate Access: 'switch.fp_access_USER' & 'switch.nfc_access_USER'
#              - Unified Schedule: 'input_datetime.guest_access_...'
# ------------------------------------------------------------------------------

# ------------------------------------------------------------------------------
# 1. HELPERS & SCHEDULES
# ------------------------------------------------------------------------------
input_select:
  unifi_delete_id:
    name: "Select ID to Delete"
    icon: mdi:delete-empty
    options: ["-select-"]

# --- UNIFIED GUEST SCHEDULE ---
input_datetime:
  guest_access_start:
    name: "Guest Access Start"
    has_date: false
    has_time: true
    icon: mdi:clock-start
    initial: "09:00"
  guest_access_end:
    name: "Guest Access End"
    has_date: false
    has_time: true
    icon: mdi:clock-end
    initial: "21:00"

input_boolean:
  guest_access_mon:
    name: "Mon"
    icon: mdi:calendar
  guest_access_tue:
    name: "Tue"
    icon: mdi:calendar
  guest_access_wed:
    name: "Wed"
    icon: mdi:calendar
  guest_access_thu:
    name: "Thu"
    icon: mdi:calendar
  guest_access_fri:
    name: "Fri"
    icon: mdi:calendar
  guest_access_sat:
    name: "Sat"
    icon: mdi:calendar
  guest_access_sun:
    name: "Sun"
    icon: mdi:calendar

# ------------------------------------------------------------------------------
# 2. AUTOMATIONS
# ------------------------------------------------------------------------------
automation:
  # --- LOGIC 1: FINGERPRINT ENTRY ---
  - alias: "Access: Fingerprint Entry Logic"
    id: access_fingerprint_entry_logic
    mode: single
    trigger:
      - platform: state
        entity_id: event.front_door_fingerprint
        not_from:
          - "unknown"
          - "unavailable"
        not_to:
          - "unknown"
          - "unavailable"
    variables:
      raw_id: >-
        {{ trigger.to_state.attributes.ulp_id | default('unknown') }}
      slug_id: "{{ raw_id | replace('-', '_') | lower }}"
      # SHARED IDENTITY ENTITY
      assigned_user: "{{ states('select.unifi_user_' ~ slug_id) }}"
      # FP ACCESS SWITCH
      user_switch: "switch.fp_access_{{ assigned_user | slugify }}"
    action:
      - target:
          entity_id: camera.g4_doorbell_pro_poe_high_resolution_channel
        data:
          filename: /config/www/front_door_unlock_snapshot.jpg
        action: camera.snapshot

      - choose:
          # ROLE 1: GUEST
          - conditions:
              - condition: template
                value_template: "{{ 'guest' in assigned_user | lower }}"
            sequence:
              - if:
                  - condition: template
                    value_template: >
                      {# FAIL CLOSED: Switch MUST be ON #}
                      {% set access_enabled = is_state(user_switch, 'on') %}
                      {# SCHEDULE CHECK (UNIFIED) #}
                      {% set current_day = now().strftime('%a')|lower %}
                      {% set is_day_allowed = is_state('input_boolean.guest_access_' ~ current_day, 'on') %}
                      {% set current_time = now().strftime('%H:%M:%S') %}
                      {% set start_time = states('input_datetime.guest_access_start') %}
                      {% set end_time = states('input_datetime.guest_access_end') %}
                      {% set is_time_allowed = (current_time >= start_time) and (current_time <= end_time) %}

                      {{ access_enabled and is_day_allowed and is_time_allowed }}
                then:
                  - action: lock.unlock
                    target:
                      entity_id: lock.front_door_lock
                  - action: script.notify_smart_master
                    data:
                      category: security
                      title: 🔓 Guest Entry (Fingerprint)
                      message: "Guest entered via Fingerprint."
                      image: /local/front_door_unlock_snapshot.jpg?t={{ now().timestamp() }}
                      tag: door_lock
                else:
                  - action: script.notify_smart_master
                    data:
                      category: security
                      title: ⛔ Access Denied (Fingerprint)
                      message: >
                        {% if not is_state(user_switch, 'on') %}
                          Guest FP access manually DISABLED.
                        {% else %}
                          Guest outside allowed schedule.
                        {% endif %}
                      critical: true
                      image: /local/front_door_unlock_snapshot.jpg?t={{ now().timestamp() }}
                      tag: door_lock_denied

          # ROLE 2: STANDARD USER
          - conditions:
              - condition: template
                value_template: >
                  {{ assigned_user not in ['unknown', 'unavailable', 'none', 'Unknown', '-Unassigned-', '-unassigned-', 'Guest'] }}
            sequence:
              - if:
                  - condition: template
                    # FAIL CLOSED: Switch MUST be ON
                    value_template: "{{ is_state(user_switch, 'on') }}"
                then:
                  - action: lock.unlock
                    target:
                      entity_id: lock.front_door_lock
                  - action: logbook.log
                    data:
                      name: "Front Door Access"
                      message: "🔓 Guest {{ assigned_user }} entered via Fingerprint."
                      entity_id: lock.front_door_lock_cloud
                  - action: logbook.log
                    data:
                      name: "Front Door Access"
                      message: "🔓 {{ assigned_user }} unlocked via Fingerprint."
                      entity_id: lock.front_door_lock_cloud
                  - action: script.notify_smart_master
                    data:
                      category: security
                      title: 🔓 Front Door Unlocked
                      message: "Unlocked by: {{ assigned_user }}"
                      image: /local/front_door_unlock_snapshot.jpg?t={{ now().timestamp() }}
                      tag: door_lock
                else:
                  - action: logbook.log
                    data:
                      name: "Front Door Access"
                      message: "⛔ Guest Access Denied (Fingerprint): {{ assigned_user }}."
                      entity_id: lock.front_door_lock_cloud
                  - action: logbook.log
                    data:
                      name: "Front Door Access"
                      message: "⛔ Access Disabled (Fingerprint): {{ assigned_user }}."
                      entity_id: lock.front_door_lock_cloud
                  - action: script.notify_smart_master
                    data:
                      category: security
                      title: ⛔ Access Disabled
                      message: "Fingerprint valid, but {{ assigned_user }}'s access is switched OFF."
                      critical: true
                      image: /local/front_door_unlock_snapshot.jpg?t={{ now().timestamp() }}
                      tag: door_lock_denied

          # ROLE 3: AUTO-LEARN
          - conditions:
              - condition: template
                value_template: "{{ raw_id != 'unknown' and raw_id != '' }}"
            sequence:
              - action: script.add_unifi_user_entity
                data:
                  ulp_id: "{{ raw_id }}"
                  current_user: Unknown
              - action: script.notify_smart_master
                data:
                  category: system
                  title: 🖐️ New Fingerprint ID
                  message: "ID: {{ raw_id }}\nAdded to User Management as 'Unknown'."
                  image: /local/front_door_unlock_snapshot.jpg?t={{ now().timestamp() }}
                  tag: door_lock_unknown
                  clickAction: /lovelace/access-control

  # --- LOGIC 2: NFC ENTRY ---
  - alias: "Access: NFC Entry Logic"
    id: access_nfc_entry_logic
    mode: single
    trigger:
      - platform: state
        entity_id: event.front_door_nfc
        not_from:
          - "unknown"
          - "unavailable"
        not_to:
          - "unknown"
          - "unavailable"
    variables:
      raw_id: >-
        {{ trigger.to_state.attributes.ulp_id | default('unknown') }}
      slug_id: "{{ raw_id | replace('-', '_') | lower }}"
      # SHARED IDENTITY ENTITY
      assigned_user: "{{ states('select.unifi_user_' ~ slug_id) }}"
      # NFC ACCESS SWITCH
      user_switch: "switch.nfc_access_{{ assigned_user | slugify }}"
    action:
      - target:
          entity_id: camera.g4_doorbell_pro_poe_high_resolution_channel
        data:
          filename: /config/www/front_door_nfc_snapshot.jpg
        action: camera.snapshot

      - choose:
          # ROLE 1: GUEST
          - conditions:
              - condition: template
                value_template: "{{ 'guest' in assigned_user | lower }}"
            sequence:
              - if:
                  - condition: template
                    value_template: >
                      {# FAIL CLOSED: Switch MUST be ON #}
                      {% set access_enabled = is_state(user_switch, 'on') %}
                      {# SCHEDULE CHECK (UNIFIED) #}
                      {% set current_day = now().strftime('%a')|lower %}
                      {% set is_day_allowed = is_state('input_boolean.guest_access_' ~ current_day, 'on') %}
                      {% set current_time = now().strftime('%H:%M:%S') %}
                      {% set start_time = states('input_datetime.guest_access_start') %}
                      {% set end_time = states('input_datetime.guest_access_end') %}
                      {% set is_time_allowed = (current_time >= start_time) and (current_time <= end_time) %}

                      {{ access_enabled and is_day_allowed and is_time_allowed }}
                then:
                  - action: lock.unlock
                    target:
                      entity_id: lock.front_door_lock
                  - action: logbook.log
                    data:
                      name: "Front Door Access"
                      message: "🔓 Guest {{ assigned_user }} entered via NFC."
                      entity_id: lock.front_door_lock_cloud
                  - action: script.notify_smart_master
                    data:
                      category: security
                      title: 🔓 Guest Entry (NFC)
                      message: "Guest entered via NFC."
                      image: /local/front_door_nfc_snapshot.jpg?t={{ now().timestamp() }}
                      tag: door_lock
                else:
                  - action: logbook.log
                    data:
                      name: "Front Door Access"
                      message: "⛔ Guest Access Denied (NFC): {{ assigned_user }}."
                      entity_id: lock.front_door_lock_cloud
                  - action: script.notify_smart_master
                    data:
                      category: security
                      title: ⛔ Access Denied (NFC)
                      message: >
                        {% if not is_state(user_switch, 'on') %}
                          Guest NFC access manually DISABLED.
                        {% else %}
                          Guest outside allowed schedule.
                        {% endif %}
                      critical: true
                      image: /local/front_door_nfc_snapshot.jpg?t={{ now().timestamp() }}
                      tag: door_lock_denied

          # ROLE 2: STANDARD USER
          - conditions:
              - condition: template
                value_template: >
                  {{ assigned_user not in ['unknown', 'unavailable', 'none', 'Unknown', '-Unassigned-', '-unassigned-', 'Guest'] }}
            sequence:
              - if:
                  - condition: template
                    # FAIL CLOSED
                    value_template: "{{ is_state(user_switch, 'on') }}"
                then:
                  - action: lock.unlock
                    target:
                      entity_id: lock.front_door_lock
                  - action: logbook.log
                    data:
                      name: "Front Door Access"
                      message: "🔓 {{ assigned_user }} unlocked via NFC."
                      entity_id: lock.front_door_lock_cloud
                  - action: script.notify_smart_master
                    data:
                      category: security
                      title: 🔓 Front Door Unlocked (NFC)
                      message: "Unlocked by: {{ assigned_user }}"
                      image: /local/front_door_nfc_snapshot.jpg?t={{ now().timestamp() }}
                      tag: door_lock
                else:
                  - action: logbook.log
                    data:
                      name: "Front Door Access"
                      message: "⛔ Access Disabled (NFC): {{ assigned_user }}."
                      entity_id: lock.front_door_lock_cloud
                  - action: script.notify_smart_master
                    data:
                      category: security
                      title: ⛔ Access Disabled
                      message: "NFC valid, but {{ assigned_user }}'s NFC access is switched OFF."
                      critical: true
                      image: /local/front_door_nfc_snapshot.jpg?t={{ now().timestamp() }}
                      tag: door_lock_denied

          # ROLE 3: AUTO-LEARN
          - conditions:
              - condition: template
                value_template: "{{ raw_id != 'unknown' and raw_id != '' }}"
            sequence:
              - action: script.add_unifi_user_entity
                data:
                  ulp_id: "{{ raw_id }}"
                  current_user: Unknown
              - action: script.notify_smart_master
                data:
                  category: system
                  title: 🏷️ New NFC ID
                  message: "ID: {{ raw_id }}\nAdded to User Management as 'Unknown'."
                  image: /local/front_door_nfc_snapshot.jpg?t={{ now().timestamp() }}
                  tag: door_lock_unknown
                  clickAction: /lovelace/access-control

  # --- SYSTEM PERSISTENCE (Universal) ---
  - alias: "System: Unifi Access MQTT Persistence"
    id: system_unifi_access_mqtt_persistence
    mode: parallel
    trigger:
      - platform: mqtt
        topic: "unifi_access/+/user/set"
      - platform: mqtt
        topic: "unifi_access/access/+/set"
      - platform: mqtt
        topic: "fingerprint/access/+/set" # Legacy/Compatibility
      - platform: mqtt
        topic: "nfc/access/+/set" # Legacy/Compatibility
    action:
      - service: mqtt.publish
        data:
          topic: "{{ trigger.topic | replace('/set', '/state') }}"
          payload: "{{ trigger.payload }}"
          retain: true

  # --- AUTO-CREATE SWITCHES ON ASSIGNMENT CHANGE ---
  - alias: "System: Unifi User Assignment Update"
    id: system_unifi_user_assignment_update
    mode: parallel
    trigger:
      - platform: mqtt
        topic: "unifi_access/+/user/set"
    action:
      - variables:
          slug: "{{ trigger.topic.split('/')[1] }}"
          entity_id: "select.unifi_user_{{ slug }}"
      - delay: "00:00:00.200"
      - service: script.add_unifi_user_entity
        data:
          ulp_id: "{{ state_attr(entity_id, 'ulp_id') | default(slug) }}"
          current_user: "{{ trigger.payload }}"
      - delay: "00:00:00.200"
      - service: script.cleanup_unused_unifi_switches

# ------------------------------------------------------------------------------
# 3. SCRIPTS
# ------------------------------------------------------------------------------
script:
  # --- ADD / UPDATE USER & CREATE BOTH SWITCHES ---
  add_unifi_user_entity:
    alias: "System: Add Unifi User"
    mode: parallel
    fields:
      ulp_id:
        description: "The Unifi ID (Member ID)"
        required: true
      current_user:
        description: "Current assigned user"
        default: "-unassigned-"
    sequence:
      - variables:
          slug: "{{ ulp_id | replace('-', '_') | lower }}"
          user_list: >-
            {% set ns = namespace(users=['-Unassigned-', 'Guest']) %}
            {% for p in states.person %}
              {% set ns.users = ns.users + [p.attributes.friendly_name] %}
            {% endfor %}
            {{ ns.users | unique | sort | list }}

          # Clean Name
          clean_current_user: >-
            {% set step1 = current_user | replace(' Notify Service', '') | replace(' Notifications', '') %}
            {% set parts = step1.split(' ') %}
            {% if parts|length > 1 and parts[0] == parts[-1] %}
               {{ parts[0] }}
            {% else %}
               {{ step1 }}
            {% endif %}

      # --- 1. PUBLISH SHARED IDENTITY SELECT ---
      - service: mqtt.publish
        data:
          retain: true
          topic: "homeassistant/select/unifi_user_{{ slug }}/config"
          payload: >-
            {
              "name": "Unifi User {{ ulp_id[:4] }}...",
              "object_id": "unifi_user_{{ slug }}",
              "unique_id": "unifi_user_{{ slug }}",
              "icon": "mdi:account-key",
              "options": {{ user_list | to_json }},
              "command_topic": "unifi_access/{{ slug }}/user/set",
              "state_topic": "unifi_access/{{ slug }}/user/state",
              "availability_topic": "unifi_access/{{ slug }}/availability",
              "payload_available": "online",
              "json_attributes_topic": "unifi_access/{{ slug }}/attributes",
              "device": {
                "identifiers": ["unifi_access_manager"],
                "name": "Unifi Access DB",
                "manufacturer": "Home Assistant",
                "model": "Unified ID DB"
              }
            }
      - service: mqtt.publish
        data:
          retain: true
          topic: "unifi_access/{{ slug }}/attributes"
          payload: >-
            { "ulp_id": "{{ ulp_id }}" }

      # --- 2. UPDATE STATE ---
      - if:
          - condition: template
            value_template: >-
              {{ states('select.unifi_user_' ~ slug) != clean_current_user }}
        then:
          - service: mqtt.publish
            data:
              retain: true
              topic: "unifi_access/{{ slug }}/user/state"
              payload: "{{ clean_current_user }}"

      # --- 3. CREATE SWITCHES (FP & NFC) ---
      - variables:
          user_slug: "{{ clean_current_user | slugify }}"

      - if:
          - condition: template
            value_template: "{{ clean_current_user not in ['-Unassigned-', 'Unknown', 'unknown'] }}"
        then:
          # --- SWITCH 1: FINGERPRINT ACCESS ---
          - service: mqtt.publish
            data:
              retain: true
              topic: "homeassistant/switch/fp_access_{{ user_slug }}/config"
              payload: >-
                {
                  "name": "FP Access: {{ clean_current_user }}",
                  "object_id": "fp_access_{{ user_slug }}",
                  "unique_id": "fp_access_switch_{{ user_slug }}",
                  "icon": "mdi:fingerprint",
                  "command_topic": "fingerprint/access/{{ user_slug }}/set",
                  "state_topic": "fingerprint/access/{{ user_slug }}/state",
                  "device": { "identifiers": ["unifi_access_manager"] }
                }
          # Init FP ON
          - if:
              - condition: template
                value_template: "{{ states('switch.fp_access_' ~ user_slug) in ['unknown', 'unavailable', 'none'] }}"
            then:
              - service: mqtt.publish
                data:
                  retain: true
                  topic: "fingerprint/access/{{ user_slug }}/state"
                  payload: "ON"

          # --- SWITCH 2: NFC ACCESS ---
          - service: mqtt.publish
            data:
              retain: true
              topic: "homeassistant/switch/nfc_access_{{ user_slug }}/config"
              payload: >-
                {
                  "name": "NFC Access: {{ clean_current_user }}",
                  "object_id": "nfc_access_{{ user_slug }}",
                  "unique_id": "nfc_access_switch_{{ user_slug }}",
                  "icon": "mdi:tag-check",
                  "command_topic": "nfc/access/{{ user_slug }}/set",
                  "state_topic": "nfc/access/{{ user_slug }}/state",
                  "device": { "identifiers": ["unifi_access_manager"] }
                }
          # Init NFC ON
          - if:
              - condition: template
                value_template: "{{ states('switch.nfc_access_' ~ user_slug) in ['unknown', 'unavailable', 'none'] }}"
            then:
              - service: mqtt.publish
                data:
                  retain: true
                  topic: "nfc/access/{{ user_slug }}/state"
                  payload: "ON"

      # --- 4. SET ONLINE ---
      - service: mqtt.publish
        data:
          retain: true
          topic: "unifi_access/{{ slug }}/availability"
          payload: "online"

  # --- REFRESH ALL USERS ---
  refresh_unifi_users:
    alias: "System: Refresh Unifi Users"
    mode: single
    sequence:
      - repeat:
          for_each: >-
            {{ states.select | selectattr('entity_id', 'search', '^select\\.unifi_user_') | list }}
          sequence:
            - service: script.add_unifi_user_entity
              data:
                ulp_id: "{{ state_attr(repeat.item.entity_id, 'ulp_id') }}"
                current_user: "{{ repeat.item.state }}"
      - service: script.cleanup_unused_unifi_switches

  # --- CLEANUP ORPHANED SWITCHES (FP & NFC) ---
  cleanup_unused_unifi_switches:
    alias: "System: Cleanup Unused Unifi Switches"
    mode: single
    sequence:
      - variables:
          active_user_slugs: >-
            {% set ns = namespace(slugs=[]) %}
            {% for s in states.select | selectattr('entity_id', 'search', '^select\\.unifi_user_') %}
               {% if s.state not in ['-Unassigned-', 'Unknown', 'unknown', 'unavailable', 'none'] %}
                 {% set ns.slugs = ns.slugs + [s.state | slugify] %}
               {% endif %}
            {% endfor %}
            {{ ns.slugs | unique | list }}

          # Find all existing switches
          fp_switches: "{{ states.switch | selectattr('entity_id', 'search', '^switch\\.fp_access_') | map(attribute='entity_id') | list }}"
          nfc_switches: "{{ states.switch | selectattr('entity_id', 'search', '^switch\\.nfc_access_') | map(attribute='entity_id') | list }}"

      # Cleanup FP Switches
      - repeat:
          for_each: "{{ fp_switches }}"
          sequence:
            - variables:
                switch_slug: "{{ repeat.item.replace('switch.fp_access_', '') }}"
            - if:
                - condition: template
                  value_template: "{{ switch_slug not in active_user_slugs }}"
              then:
                - service: mqtt.publish
                  data:
                    topic: "homeassistant/switch/fp_access_{{ switch_slug }}/config"
                    payload: ""
                    retain: true
                - service: mqtt.publish
                  data:
                    topic: "fingerprint/access/{{ switch_slug }}/state"
                    payload: ""
                    retain: true

      # Cleanup NFC Switches
      - repeat:
          for_each: "{{ nfc_switches }}"
          sequence:
            - variables:
                switch_slug: "{{ repeat.item.replace('switch.nfc_access_', '') }}"
            - if:
                - condition: template
                  value_template: "{{ switch_slug not in active_user_slugs }}"
              then:
                - service: mqtt.publish
                  data:
                    topic: "homeassistant/switch/nfc_access_{{ switch_slug }}/config"
                    payload: ""
                    retain: true
                - service: mqtt.publish
                  data:
                    topic: "nfc/access/{{ switch_slug }}/state"
                    payload: ""
                    retain: true