Docs(tasks): Add task list for spec 013
Rental Control management for Home Assistant
Rental Control is a Home Assistant integration that handles custom calendars and sensors for managing rental properties.
Table of Contents
- Features
- Installation
- Setup
- Reconfiguration
- Known issues
- Frequently Asked Questions
- Development & Testing
Features
Calendar Management
- Ingests ICS calendars from any HTTPS source that serves a text/calendar file
- Configurable refresh rate from as often as possible to once per day (default every 2 minutes)
- Define checkin/checkout times that apply to all-day calendar entries
- Ability to ignore 'Blocked' and 'Not available' events
- Calendars can have their own timezone definition that is separate from the Home Assistant instance itself. This is useful for managing properties that are in a different timezone from where Home Assistant is
- Events can have a custom prefix to help differentiate between entities if you track more than one calendar in an instance
- Submitting a configuration change forces a calendar refresh
- Calendar fetch failure resilience: if a fetch fails the integration serves the last good data; if a fetch returns an empty calendar when events existed before, it tolerates up to 2 consecutive empty responses before allowing the event list to clear
Entities Created
For a calendar named My Calendar the integration creates the following
entities. Entity IDs follow the pattern <domain>.<device_name>_<entity_name>:
- Event sensors (one per sensor count):
sensor.my_calendar_rental_control_event_0sensor.my_calendar_rental_control_event_1sensor.my_calendar_rental_control_event_2- (...)
- Calendar entity for use with calendar cards:
calendar.my_calendar_rental_control
- Check-in tracking sensor (always created):
sensor.my_calendar_check_in
- Keymaster Monitoring switch (when Keymaster lock configured):
switch.my_calendar_keymaster_monitoring
- Early Checkout Expiry switch (when Keymaster lock configured):
switch.my_calendar_early_checkout_expiry
Note: Existing installs keep their original entity IDs. Home Assistant caches entity IDs in its entity registry, so only new integrations get the updated naming pattern.
- Each event sensor has dynamically added attributes extracted from the event
description when available:
last_four-- the last 4 digits of the phone number of the booking guestnumber_of_guests-- the number of guests in the reservationguest_email-- the email address of the booking guestphone_number-- the phone number of the booking guestreservation_url-- the URL to the reservationbooking_id-- the booking / reservation identifier- Other
Field: Valuelines in the description that the above dedicated extractors do not capture become attributes with slugified keys (e.g.,Check-In Time: 3:00 PMbecomescheck_in_time)
Check-in Tracking
The integration creates a check-in tracking sensor for each configured rental that monitors guest occupancy through a four-state state machine. Configuring a Keymaster lock enables lock-related transitions and behaviors:
| State | Description |
|---|---|
no_reservation |
No relevant calendar event |
awaiting_checkin |
Event identified, waiting for guest arrival |
checked_in |
Guest has arrived |
checked_out |
Guest has departed, post-checkout linger active |
The sensor declares the enum device class so Home Assistant and other integrations know the full set of valid states.
Automatic transitions:
no_reservation→awaiting_checkin: when the coordinator picks up a new calendar eventawaiting_checkin→checked_in: at the configured check-in time (automatic) or when the guest uses their door code (requires keymaster monitoring)checked_in→checked_out: at the configured check-out time (automatic) or via the manual checkout actionchecked_out→no_reservation/awaiting_checkin: after the cleaning window expires (transitions toawaiting_checkinif a same-day follow-on reservation exists)
Sensor attributes:
checkin_state,summary,start,end,guest_namecheckin_source(automaticorkeymaster)checkout_source(automaticormanual)checkout_time,next_transition
The check-in sensor state persists across Home Assistant restarts and the integration validates stale states on startup automatically.
Keymaster event diagnostics: When enabled in the options flow, the
check-in sensor gains a keymaster_event_diagnostics attribute
containing a ring buffer of the last 10
keymaster_lock_state_changed events seen by the integration. Each
entry includes the timestamp, lock name, slot number, state, and
disposition (e.g., accepted, rejected_state, rejected_slot_zero,
rejected_out_of_range). The integration only records events from
monitored locks. This feature defaults to off; enable it in the
options flow.
Debug action: The integration provides a rental_control.set_state
service action for testing and development. It forces the check-in sensor into
any valid state (no_reservation, awaiting_checkin, checked_in,
checked_out). Use this for debugging only — it bypasses the normal state
machine transitions.
Keymaster Monitoring Switch
When you configure a Keymaster lock the integration creates a Keymaster
Monitoring switch entity. Turning it on makes Keymaster unlock events on
the configured lock trigger an immediate check-in transition (the sensor moves
from awaiting_checkin to checked_in the moment the guest uses their door
code). When off (the default), time-based automatic check-in applies.
The integration automatically discovers child locks associated with the configured Keymaster parent lock. It monitors unlock events on any child lock identically to the parent, so a guest unlocking a secondary lock (e.g., a Z-Wave deadbolt paired with a relay lock) also triggers check-in.
Early Checkout Expiry Switch
When you configure a Keymaster lock the integration also creates an Early Checkout Expiry switch entity. Turning it on and then performing a manual checkout via the checkout action shortens the lock code expiry time to the current time plus a 15-minute grace period instead of the original reservation end time. This prevents a departed guest from re-entering the property.
Manual Checkout Action
The integration provides a rental_control.checkout service action for use
in automations or the developer tools. It transitions the check-in sensor
from checked_in to checked_out. The sensor must be in the checked_in
state and the current time must fall within the active reservation window.
Home Assistant Events
The integration fires events on the Home Assistant event bus for use in automations:
| Event | Fired When |
|---|---|
rental_control_checkin |
Guest transitions to checked-in |
rental_control_checkout |
Guest transitions to checked-out |
Both events include attributes: summary, guest_name, entity_id, start,
end, and source.
Door Code Generation
- Optional code length starting at 4 digits (requires even number of digits)
- 3 door code generators are available:
- A check-in/out date based 4 digit (or greater) code using the check-in
day combined with the check-out day (default and fallback in the case
another generator fails to produce a code)
- Codes optionally regenerate when the reservation start or end dates are at least 1 day in the future
- A random 4 digit (or greater) code based on the event description
- The last 4 digits of the phone number. This works when the event description contains '(Last 4 Digits): ' or 'Last 4 Digits: ' followed by a 4 digit number. This is the most stable generator, but requires the event descriptions to have the needed data. The previous two methods can have the codes change if the event makes changes to length or to the description.
- A check-in/out date based 4 digit (or greater) code using the check-in
day combined with the check-out day (default and fallback in the case
another generator fails to produce a code)
- All events get a code associated with them. If the criteria to create the code are not fulfilled, the check-in/out date based method serves as a fallback
Keymaster Integration
- Integration with Keymaster to control door codes matched to the number of events tracked
- Automatic slot assignment with deduplication: the same guest reservation never occupies more than one lock code slot even when calendar data shifts between refreshes
- Slot reconciliation: the integration periodically verifies that all populated Keymaster slots match current calendar events. When a reservation gets cancelled or removed from the calendar, its slot clears automatically. Matching uses guest name, time-range overlap, and UID tiebreaker to handle edge cases like same-name guests with different date ranges
- Slot command retry with escalation: if a lock code set/clear command fails after 3 attempts the integration creates a persistent notification alerting the user to take manual action
Slot Name Trimming
Some locks (e.g., Schlage via Keymaster) impose a character limit for slot names. The integration can optionally trim event names to fit within a configured length on word boundaries. Enable this feature in the options flow when you configure a Keymaster lock:
trim_names(boolean, default: off) — enable slot name trimmingmax_name_length(integer, default: 16, min: 4) — character length cap for slot names
When enabled, the integration trims names on word boundaries. If a single word exceeds the cap, the integration hard-truncates it. The event prefix counts toward the limit.
Lock Code Buffer Times
Configurable pre/post buffer times control when lock codes activate and deactivate relative to the reservation schedule:
code_buffer_before(integer, default: 0) — minutes before check-in to activate the lock codecode_buffer_after(integer, default: 0) — minutes after checkout to deactivate the lock code
The buffer only affects when the physical lock code is valid on
the lock. It does not change calendar event display times, check-in
sensor timing, event override matching, or auto check-in/checkout
scheduling. If a guest arrives during the buffer window and uses
their code, the check-in sensor correctly transitions to checked_in
(when you enable Keymaster monitoring).
Keymaster Event Diagnostics
An opt-in diagnostic attribute on the check-in tracking sensor records
the last 10 keymaster_lock_state_changed events seen by the
integration. Enable it in the options flow:
enable_keymaster_event_diagnostics(boolean, default: off)
When enabled, the check-in sensor gains a
keymaster_event_diagnostics attribute. Each entry includes the
timestamp, lock name, slot number, state, and disposition. The
integration only records events from monitored locks.
- Custom calendars work as long as they provide a valid ICS file via an HTTPS
connection.
- Create rental events as all-day events (the way rental platforms provide them)
- Create maintenance style events with explicit start and end times
- The event Summary (aka event title) may contain the word Reserved.
This causes the slot name to generate in one of two ways:
- When Reserved appears followed by ' - ' and then something else, the integration uses the part after the dash
- When Reserved is not followed by ' - ' then the full summary becomes the slot name
- When the Summary contains nothing else and the Details contain
something that matches an Airbnb reservation identifier of
[A-Z][A-Z0-9]{9}that is a capital alphabet letter followed by 9 more characters that are either capital alphabet letters or numbers, then the slot will get this - If the Summary is just Reserved and there is no Airbnb code in the Description, then the integration ignores the event for purposes of managing a lock code.
- Any of the other supported platform event styles for the Summary work as long as the Summary conforms to the pattern.
- The best Summary on a manual calendar is your guest name. The entries need unique names over the sensor count worth of events or Rental Control will run into issues.
- The Description of the event can include extra details that the
integration extracts into sensor attributes.
- Phone numbers for generating door codes in one of two ways
- A line in the Description matching this regular expression:
\(?Last 4 Digits\)?:\s+(\d{4})-- This line always takes precedence for generating a door code based on last 4 digits. - A line in the Description matching this regular expression:
Phone(?: Number)?:\s+(\+?[\d\. \-\(\)]{9,})which will then have the "air" squeezed out of it to extract the last 4 digits in the number
- A line in the Description matching this regular expression:
- Number of guests
- A line in the Description that matches:
Guests:\s+(\d+)$ - The following lines also work and their values sum together:
Adults:\s+(\d+)$Children:\s+(\d+)$
- A line in the Description that matches:
- The integration extracts email addresses from the Description by
matching against:
Email:\s+(\S+@\S+) - Reservation URLs match against the first URL in the Description
- Phone numbers for generating door codes in one of two ways
Installation
To make full use of this integration, install Keymaster as this integration depends upon it.
MANUAL INSTALLATION
- Download the latest release
- Unpack the release and copy the
custom_components/rental_controldirectory into thecustom_componentsdirectory of your Home Assistant installation - Restart Home Assistant
- Follow the instructions in the setup section to finish the configuration
INSTALLATION VIA Home Assistant Community Store (HACS)
- Ensure that HACS is present
- Then press the following button
- Install the
Rental Controlintegration - Restart Home Assistant
- Follow the instructions in the setup section to finish the configuration
Setup
Set up the integration through the GUI.
-
Press the following button to install the
Rental Controlintegration -
Follow the prompts and then press
OKon the question about installingRental Control -
Enter a name for the calendar, and the calendar's
icsURL (see FAQ) -
By default the integration creates 5 sensors for the 5 next upcoming events (sensor.rental_control_<calendar_name>_event_0 ~ 4). You can adjust this to add more or fewer sensors
-
The calendar refresh rate defaults to every 2 minutes but you can set it to 0 for as often as possible (about every 30 seconds) or up to once per day (1440). This adjusts in minute increments
-
The integration considers events with a start time up to 365 days (1 year) into the future by default. You can also adjust this when adding a new calendar
-
Set your checkin and checkout times. All times use 24 hour format. These times apply to all-day calendar events. If the events come in with times already attached, the integration uses those times instead of the defaults
-
Honor event times: enable this option if your calendar source (e.g., a PMS like Guesty) provides actual check-in/check-out times in the events. When enabled, the integration always uses the calendar-provided times and pushes any time changes to Keymaster. When disabled (the default), times stored in Keymaster slots take precedence after initial assignment
-
The cleaning window (default 6 hours) controls how long the check-in sensor stays in the
checked_outstate after a guest departs before transitioning tono_reservationorawaiting_checkinfor the next guest. You can set this from 0.5 to 48 hours -
For configuration managing a Keymaster controlled lock, make sure that you have defined the lock during initial setup and that you have the starting slot set for the integration.
- Make sure you have Keymaster fully working before trying to use the slot management component of Rental Control.
- NOTE: Rental Control takes full control of the Keymaster slots defined for management. Any data in the slots gets overwritten by Rental Control when it takes over the slot unless it matches event data for the calendar.
- The following portions of a Keymaster slot will influence (that is
override) data in the calendar or event sensor (unless you enable
Honor event times, in which case the calendar always wins for
events with explicit times):
- Checkin/out TIME (not date) will update the calendar event and also the sensor tracked information. NOTE: If you use a timezone that is not the system timezone on your calendar, you will run into weird and unexpected issues as that combination has no support yet!
- Door code - by default when the integration updates the slot it uses the code that the sensor extracted / created. If you need to override the code you may do so after the slot update. This is useful if you have a non-managed slot that has the same door code (or starting code, typically first 4 digits) as the generated code and thus causes the slot to not function properly
- Slot name trimming: if your lock has a character limit for slot
names, enable
trim_namesand setmax_name_length(default 16, min 4). The integration trims names on word boundaries - Lock code buffers: set
code_buffer_beforeandcode_buffer_after(minutes) to make lock codes activate before check-in or stay active after checkout. Buffers only affect the physical lock code — not calendar times or sensor transitions - Keymaster event diagnostics: enable
enable_keymaster_event_diagnosticsto add a ring buffer of the last 10 keymaster events as an attribute on the check-in sensor. Useful for debugging lock code issues
Reconfiguration
This integration supports reconfiguration after initial setup
- Press this button
- Select the calendar and then select
Configure - Reconfigure as if you were setting it up for the first time
NOTE: Changes may not appear right away. The default update cycle checks
for updates every 2 minutes and events refresh around every 30 seconds. To
force a full update right away, select the ... menu next to Configure and
select Reload
Known issues
While the integration supports reconfiguration, some options may not fully update after a change. If you have issues with reconfigured options not taking effect, try reloading the particular integration installation or restart Home Assistant.
Frequently Asked Questions
Why does my calendar events say Reserved instead of the guest's name?
AirBnB does not include guest or booking details in the invite. What the ics
data includes varies by provider. Calendar ics URLs from some 3rd party
tools (e.g. Host Tools and Guesty) do include guest information and will show
that rather than Reserved in calendar events.
Where can I find my rental calendar's ics URL?
Each provider has slightly different instructions:
How do I use custom calendars?
Custom calendars work as long as they provide a valid ICS file via an HTTPS connection. You can structure the events on the calendar in different ways.
We recommend that the event Summary (aka event title) contain the guest's name
and not the word Reserved. We also strongly recommend that any calendar
entries across the sensor count worth of events have unique names. If the
entries are not unique, Rental Control may run into issues as the event Summary
drives the slot management.
The integration extracts the following data from the Description of the event (and the match keys):
- Phone numbers for generating door codes in one of two ways
- A line in the Description matching this regular expression:
\(?Last 4 Digits\)?:\s+(\d{4})-- This line always takes precedence for generating a door code based on last 4 digits. - A line in the Description matching this regular expression:
Phone(?: Number)?:\s+(\+?[\d\. \-\(\)]{9,})which will then have the "air" squeezed out of it to extract the last 4 digits in the number
- A line in the Description matching this regular expression:
- Number of guests
- A line in the Description that matches:
Guests:\s+(\d+)$ - The following lines also work and their values sum together:
Adults:\s+(\d+)$Children:\s+(\d+)$
- A line in the Description that matches:
- The integration extracts email addresses from the Description by matching
against:
Email:\s+(\S+@\S+) - Booking IDs match against:
Booking ID:\s*(.+)$ - Reservation URLs match against the first URL in the Description
- Dynamic attributes: other
Field: Valuelines in the description that do not match the above patterns become sensor attributes with slugified keys (lowercase, spaces and special characters replaced with underscores)
An example calendar entry with this data might look like this:
Title: John and Jane Doe
Description:
Phone: 555-555-5555
Email: jdoe@example.com
Guests: 2
Booking ID: ABC-123456
Property: Beach House
https://www.example.com/reservation/123456789
The integration extracts the following information from this event:
Slot name: John and Jane Doe
Phone number: 555-555-5555
Last four: 5555
Email: jdoe@example.com
Number of guests: 2
Booking ID: ABC-123456
Reservation URL: https://www.example.com/reservation/123456789
Dynamic attribute "property": Beach House
Automation Examples
Here are some examples of automations that work with Rental Control.
Note: The entity IDs below use the current naming pattern. If you set up your integration before the naming change, your entity IDs may follow the older
sensor.rental_control_<calendar>_event_Npattern. Check your entity registry for exact IDs.
- Manage thermostat for guests and between guests
alias: Manage Thermostat for Guests mode: single triggers: - entity_id: - sensor.my_calendar_rental_control_event_0 attribute: description trigger: state to: 'No reservation' for: hours: 1 minutes: 0 seconds: 0 id: No Reservations - entity_id: - sensor.my_calendar_rental_control_event_0 attribute: eta_days for: hours: 1 minutes: 0 seconds: 0 above: 1 id: Between Guests trigger: numeric_state - entity_id: - sensor.my_calendar_rental_control_event_0 attribute: eta_minutes below: 180 id: Guests trigger: numeric_state - entity_id: - sensor.my_calendar_rental_control_event_1 attribute: eta_minutes below: 180 id: Guests trigger: numeric_state - entity_id: - calendar.my_calendar_rental_control to: 'on' id: Guests trigger: state conditions: [] actions: - choose: - conditions: - condition: trigger id: No Reservations sequence: - service: climate.set_temperature target: entity_id: climate.thermostat data: temperature: 65 - conditions: - condition: or conditions: - condition: trigger id: Between Guests - condition: trigger id: Guests sequence: - service: climate.set_temperature target: entity_id: climate.thermostat data: temperature: 72
Development & Testing
Running Tests
This project uses pytest with pytest-homeassistant-custom-component for testing.
# Run all tests
python -m pytest tests/
# Run with coverage report
python -m pytest tests/ --cov=custom_components.rental_control --cov-report=term-missing
# Run unit tests
python -m pytest -m unit
# Run integration tests
python -m pytest -m integration
# Run a specific test file
python -m pytest tests/unit/test_config_flow.py -v
Test Structure
| Directory | Marker | Description |
|---|---|---|
tests/unit/ |
unit |
Fast isolated tests for individual components |
tests/integration/ |
integration |
Tests verifying component interactions |
tests/fixtures/ |
— | Shared test data (ICS calendars, mock entries) |
Pre-commit Hooks
All commits must pass pre-commit checks:
pre-commit install
pre-commit run --all-files