This custom integration controls Hartmann Controls Protector.Net and Odyssey door access systems via HTTP + a live SignalR websocket for instant updates.
If the Hartmann session cookie expired while the WebSocket was running, the connection would drop and never recover — every reconnect attempt failed silently because it kept using the stale cookie. The WebSocket client now refreshes its credentials on each reconnect attempt, re-authenticates automatically on 401 errors, and shuts down cleanly when Home Assistant stops (no more “Task was still running” warnings in the logs).
Event timestamps like “Home Assistant unlocked @ 6:39 AM” were displaying the raw UTC time from the Hartmann server instead of converting to your local timezone. Now correctly shows local time (e.g., “@ 2:39 AM” for EDT).
Restarting or reloading the integration used to create a wall of fake state-change entries in the Activity log (e.g., “Override Type changed to For Specified Time”, “Lock State changed to Locked”) even though nothing actually happened at the door. Sensors now restore silently without triggering history entries.
On certain Hartmann systems, the overview tree’s site names didn’t match the partition name, causing door discovery to silently find zero doors — only the Hub Status sensor would appear. Discovery now uses the partition’s own door list from the API (the same reliable source the websocket client already uses), with the old site-name filter as a fallback.
The Override Type select (“Until Resumed”, “For Specified Time”, etc.) used to reset to “For Specified Time” every time Home Assistant restarted, because the Hartmann API doesn’t expose the current override type. The select now persists its value across restarts.
Create and manage temporary PIN codes for doors — perfect for Airbnb, rental properties, or visitor management.
create_temp_code— Generate random or manual PINs with optional start/end timesupdate_temp_code— Extend a guest’s stay by patching the expiration directly on Hartmann. Same PIN, no downtime, no interruption to the guest.delete_temp_code/delete_temp_code_by_name— Remove codes by PIN or nameclear_all_temp_codes— Wipe all temp codes from a door- Temp Code sensor per door stores
code_name,code,user_id,start_time, andend_timeper code for automation-driven extension detection - Configurable default PIN digits (4–9) in integration setup and options
Schedule future door overrides that execute directly in the Hartmann panel — works even if Home Assistant is offline.
create_otr_schedule/delete_otr_schedule/get_otr_schedules- All times automatically converted between your local timezone and Hartmann’s UTC
- OTR Schedules sensor per door — state is the schedule count, attributes include
active_schedules,upcoming_schedules, andall_scheduleswith id, name, mode, start, and stop times - Refreshes every 5 minutes + immediately after create/delete via dispatcher signals
Pick a target date & time for timed overrides instead of calculating minutes manually — just like Hartmann’s app.
- Override Until datetime entity per door — when Override Type is “For Specified Time”, the Override switch auto-computes minutes from the datetime picker
- Falls back to Override Minutes if the datetime is empty or in the past
untilparameter onoverride_doorservice — passuntil: "2026-02-18T14:00:00"and the service auto-computes minutes. Great for automations where you know the end time, not the duration.
override_door— Single service call to override a door (no need to set mode + enable separately)resume_door— Resume normal schedule- Override Type and Override Mode selects update immediately in the UI after service calls
- Button on the Hub device +
update_panelsservice to push configuration to all connected panels immediately, so changes take effect without waiting for Hartmann’s auto-sync interval
- State now includes timestamp: “John Smith granted access @ 1:06 PM” — each event is a separate History entry
- OTR schedule activations appear as “OTR Unlock @ 10:01 PM”
- Multi-door support — All services accept multiple doors via the device picker
- Override Minutes max increased to 2,147,483,647
force_removeoption ondelete_temp_code_by_namefor cleaning stale sensor entries- Better error messages when PIN creation fails (shows Hartmann’s actual error)
- ✅ Cookie login (
ss-id) with automatic re-auth - ✅ Partition selection (imports only your chosen partition)
- ✅ Zero-polling live updates via SignalR
- ✅ Door controls: per-door override UI + Pulse Unlock (+ optional legacy buttons)
- ✅ Override Until date/time picker for timed overrides
- ✅ All Doors Lockdown switch (partition-wide)
- ✅ Temporary access codes with start/end times, extension support, and auto-cleanup
- ✅ OTR Schedules — schedule overrides that run on the panel even if HA is offline
- ✅ HA Door Log entries when you use HA buttons (e.g., “Home Assistant unlocked …”)
- ✅ All controls & options in the UI (HACS-friendly)
- ✅ Odyssey servers supported (auto-detect)
HACS (recommended):
- In HACS → Integrations, search for “Protector.Net Access Control” and install.
- Restart Home Assistant.
Manual:
- Copy
custom_components/protector_net/into your Home Assistantconfig/custom_components/. - Restart Home Assistant.
Then go to Settings → Devices & Services → Add Integration → “Protector.Net Access Control”.
- Base URL –
https://host:port - Credentials – a Protector.Net user with sufficient privileges (must be a System Administrator in Hartmann)
- Default override minutes – used for Timed Override
- Default PIN digits – 4–9 digits for generated temp codes
- Partition – select exactly one
- Action Plans – pick trigger plans to clone as System plans (so they can be executed from HA)
Revisit any time: Settings → Devices & Services → Protector.Net → Options.
- Pulse Unlock is always included (not shown in the picker).
- Choose any additional legacy buttons you want; none are pre-selected by default.
- Device:
Hub Status – <Partition> - Entity: Hub Status – (sensor)
State:
running / connecting / idle / stopped / errorAttributes:phase,connected,mapped_doors,partition_id,system_type(“Odyssey” or “ProtectorNET”) - Update Panels (button) — push configuration to all connected panels immediately
Sensors
- Lock State —
Locked/Unlocked - Overridden —
On/Off - Reader Mode — mapped from controller index:
0/8 Lockdown, 1 Card, 2 Pin, 3 Card or Pin, 4 Card and Pin, 5 Unlock, 6 First Credential In, 7 Dual Credential - Last Door Log by — highlights the last actor with timestamp (e.g., “John Smith granted access @ 1:06 PM”); attributes include the last reader/action message/time and the last door message.
- Temp Code — state is
Noneor the active code name. Attributes: list of all temp codes withcode_name,code,user_id,start_time,end_timeper entry. - OTR Schedules — state is the count of schedules for this door. Attributes:
active_schedules(currently running),upcoming_schedules(future), andall_scheduleswith id, name, mode, start, and stop times. Refreshes every 5 minutes and immediately after create/delete.
Controls
- Override (switch) — ON applies selected Type + Mode (and minutes if “For Specified Time”); OFF resumes schedule and forces Override Mode = None.
- Override Type (select) —
For Specified Time/Until Resumed/Until Next Schedule - Override Mode (select) —
None,Card,Pin,Unlock,Card and Pin,Card or Pin,First Credential In,Dual Credential,Lockdown- OFF ⇒ shows
None; ON ⇒ mirrors the panel’s current reader mode.
- OFF ⇒ shows
- Override Minutes (number) — used when type is “For Specified Time” (fallback if Override Until is not set)
- Override Until (datetime) — pick a target date & time; the switch auto-computes minutes at turn-on. Overrides the Minutes field when set to a future time.
Door Buttons
- Always: Pulse Unlock
- Optional (if selected in Options): Resume Schedule, Unlock Until Resume, Unlock Until Next Schedule, CardOrPin Until Resume, Timed Override Unlock
- Device:
Action Plans – <Partition> - Entities:
Action Plan: <Plan Name>(button) — executes cloned System-type plans.
- Device:
All Doors – <Partition> - Entity: All Doors Lockdown (switch) ON: apply Lockdown override on all doors in the partition. OFF: Resume Schedule across all doors.
| Service | Description |
|---|---|
create_temp_code |
Create a temporary PIN code with optional start/end times. Supports random or manual codes, configurable digit count (4–9). |
update_temp_code |
Update start/end time of an existing code without changing the PIN. Perfect for extending guest stays. |
delete_temp_code |
Delete a temp code by PIN value. |
delete_temp_code_by_name |
Delete a temp code by name (for calendar automations). Optional force_remove to clean stale sensor entries. |
clear_all_temp_codes |
Remove all temporary codes from a door. |
| Service | Description |
|---|---|
create_otr_schedule |
Schedule a future door override with start/stop times and mode. Stored on the Hartmann panel — runs even if HA is offline. |
delete_otr_schedule |
Delete OTR schedules by door (all) or specific schedule ID. |
get_otr_schedules |
Retrieve all OTR schedules. |
| Service | Description |
|---|---|
override_door |
Apply an override to door(s) in a single call. Supports mode, override_type, minutes, and until (datetime — auto-computes minutes). |
resume_door |
Resume normal schedule for door(s). |
update_panels |
Push current configuration to all connected panels immediately. |
All services accept multiple doors via the device picker.
A complete three-phase automation for calendar-based booking management. Handles new bookings, stay extensions, and cleanup:
alias: "Booking Code Manager"
description: "Create/extend/cleanup temp codes from calendar bookings"
triggers:
# Phase 1 & 2: Calendar event starts or changes
- trigger: calendar
entity_id: calendar.your_booking_calendar
event: start
offset: "-00:30:00" # 30 min before check-in
# Phase 3: Cleanup 24h after checkout
- trigger: calendar
entity_id: calendar.your_booking_calendar
event: end
offset: "24:00:00"
actions:
- choose:
# --- Phase 3: Cleanup ---
- conditions:
- condition: template
value_template: "{{ trigger.event == 'end' }}"
sequence:
- action: protector_net.delete_temp_code_by_name
data:
door_device_id: "YOUR_DOOR_DEVICE_ID"
code_name: "{{ trigger.calendar_event.summary }}"
force_remove: true
# --- Phase 1 & 2: Create or Extend ---
- conditions:
- condition: template
value_template: "{{ trigger.event == 'start' }}"
sequence:
- variables:
booking_name: "{{ trigger.calendar_event.summary }}"
new_end: "{{ trigger.calendar_event.end }}"
# Check if code already exists (for extension detection)
existing_codes: >-
{{ state_attr('sensor.your_door_temp_code', 'codes') or [] }}
existing_code: >-
{{ existing_codes | selectattr('code_name', 'eq', booking_name)
| list | first | default(none) }}
- choose:
# Extension: code exists but end time changed
- conditions:
- condition: template
value_template: >-
{{ existing_code is not none and
existing_code.end_time != new_end }}
sequence:
- action: protector_net.update_temp_code
data:
door_device_id: "YOUR_DOOR_DEVICE_ID"
code_name: "{{ booking_name }}"
end_time: "{{ new_end }}"
- action: protector_net.update_panels
# New booking: no existing code
- conditions:
- condition: template
value_template: "{{ existing_code is none }}"
sequence:
- action: protector_net.create_temp_code
data:
door_device_id: "YOUR_DOOR_DEVICE_ID"
code_name: "{{ booking_name }}"
random_code: true
start_time: "{{ trigger.calendar_event.start }}"
end_time: "{{ new_end }}"
response_variable: result
- action: protector_net.update_panels
- action: notify.your_notification_service
data:
message: >-
New code for {{ booking_name }}: {{ result.code }}
(valid until {{ new_end }})- State becomes the person/app with timestamp when:
- Access granted/denied events (e.g., “John Smith granted access @ 1:06 PM”)
- Action plan messages like “Home Assistant unlocked …”
- OTR activations (e.g., “OTR Unlock @ 10:01 PM”)
- Attributes are stable and minimal:
Reader Message,Reader Message Time(granted/denied or action text + timestamp)Door Message(e.g., “Door is now Locked/Unlocked”)Door ID,Partition ID
Lock/Unlock status messages don’t flip the “by” state (that’s what Lock State is for).
-
Door sensors missing (only Hub Status appears) If you see “No doors matched filters” in the logs, update to 0.2.3. Older versions relied on site-name matching which fails on some Odyssey servers. The fix uses partition-scoped door discovery instead.
-
Override Type resets to “For Specified Time” after restart Update to 0.2.3; the Override Type select now persists across restarts.
-
WebSocket disconnects and never reconnects Update to 0.2.4. Older versions captured the session cookie once at startup; if it expired, reconnects would fail forever. The WS client now re-authenticates automatically.
-
“Task was still running after final writes shutdown stage” warnings Update to 0.2.4; the WebSocket tasks now stop cleanly when Home Assistant shuts down.
-
Last Door Log shows wrong time (off by several hours) Update to 0.2.4. Timestamps from Hartmann are in UTC and were being displayed without timezone conversion.
-
Fake activity entries after every restart/reload Update to 0.2.4. Sensors now restore their previous state silently without creating history entries.
-
Sensors didn’t appear previously for “Default Partition” Update to 0.1.7; discovery now correctly loads those doors.
- Fix: WebSocket auto-reconnect after session expiry — credentials refresh on each reconnect; negotiate re-authenticates on 401
- Fix: Clean HA shutdown — WebSocket tasks stop on EVENT_HOMEASSISTANT_STOP (no more shutdown warnings)
- Fix: Last Door Log timestamps now display in local time instead of raw UTC
- Fix: No phantom activity entries on restart/reload — sensors restore silently
- Fix: Door sensors missing on some Odyssey servers — discovery now uses the partition’s API door list instead of fragile site-name matching
- Fix: Override Type select now persists across restarts via RestoreEntity
- New: Temporary access codes —
create_temp_code,update_temp_code,delete_temp_code,delete_temp_code_by_name,clear_all_temp_codes - New: Temp Code sensor per door with code details in attributes
- New: OTR Schedules —
create_otr_schedule,delete_otr_schedule,get_otr_schedules - New: OTR Schedules sensor per door with active/upcoming schedule breakdown
- New: Override Until datetime entity per door — pick a target date & time instead of calculating minutes
- New:
untilparameter onoverride_doorservice — auto-computes minutes from a target datetime - New:
override_doorandresume_doorservices for single-call door control - New: Update Panels button and service
- New: Last Door Log now includes timestamps and OTR events
- New: Multi-door support — all services accept multiple doors
- New: Override Minutes max increased to 2,147,483,647
- New:
force_removeoption ondelete_temp_code_by_name - New: Configurable default PIN digits (4–9)
- New: Odyssey servers supported (auto-detect, no config changes).
- New: Odyssey status snapshots on connect and periodically (~60s) to catch schedule flips.
- Improvement: Normalize WS types for
overriddenandtimeZonefrom Odyssey.
- Fix: Last Door Log not updating for some doors – fixed a bug where notifications coming from a reader (instead of directly from the door) were being dropped because the door ID wasn’t in the partition allowlist yet.
- Note: 0.1.9 withdrawn.
- Fix: Reader notifications (including “Reader 2” / in–out readers on the same ODM/TDM) now map cleanly to the right door because we also pull the partition-scoped AvailableReaders API (Reader → DoorId), not just the name.
- Fix: Door sensors now load correctly when the selected partition is named “Default Partition.”
- Reliability: More robust, partition-scoped discovery with safe fallback and retry.
- New: Partition-scoped devices (Hub + Action Plans + All Doors)
- New: All Doors Lockdown switch (partition-wide)
- New: 5 sensors total (Hub Status, Lock State, Overridden, Reader Mode, Last Door Log by)
- New: Per-door Override UI (Switch + Selects + Number), with instant sync
- Change: Legacy buttons refined — Pulse Unlock always; others optional via Options
- Door-action logs (“Home Assistant unlocked …”)
- Plan cloning fixes and reuse; HA Door Log plan
- Action Plans import/execute; options refresh
- Configurable door entities; options flow; base fixes
- Partition selection; session refresh; dynamic titles
- Initial release (doors & basic controls)
Author: Yoel Goldstein / Vaayer LLC