I’ve wanted to years to be able to let a guest/employee access some resources in our home automation system. I finally figured out one solution.
A big thanks to @tmjpugh who helped me out over on this thread. All of the original structure / idea is here because of their work.
There are a few features:
- Guest website available when connected to local Wi-Fi - You could use a QR code to let them connect to your Guest Wi-Fi, and to link to the URL.
- Logs who the last person to successfully enter a pin / OTP.
- Logs the last person to request access via the “doorbell”.
- Prompts a phone call via an actionable notification (may need tweaking for iOS).
Screenshots
Things I’d love to make better, but don’t know how. Can you help me?
There are a few things I’d like to be better:
- Easier process to add/remove users (something that would pass HAF)
- Currently the process requires adding a OTP sensor or
input_number
and modifying the automation.
- Currently the process requires adding a OTP sensor or
- Currently there is an
input_number.set_value
toNA
then adelay
of 1 second so that a value is logged if it’s the same as the last entry.- It would be great to know if there’s a better way to log repetitive entries of the same value.
- Include a photo from a security camera in the notification.
SECURITY NOTE / REQUEST
The webhook string is visible to anyone visiting the website and inspecting the headers. I don’t believe this is a security issues since they don’t trigger anything critical without a condition that checks for a pin or OTP. However, anyone with a strength in security, please take a look to see if you see any weaknesses.
Requirements
We have a few things going here to make this work:
- nginx for webserver and reverse proxy
- Local DNS, eg. PiHole
- HA - automation
- HA - helpers
Lets start with getting a website up:
Copy and paste the following index.html
into a text editor. You can find and replace the following:
- your home
- your address
- your_GUEST_HA_domain.com
You may also want to place a logo.png
or similar for the background in the same directory as the index.html
file.
index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Welcome to your home</title>
<style>
body {
color: #000000;
/*text-shadow: 10px 10px 10px #000000;*/
background-image: url("logo.png");
width: 50%;
min-height: 100vh;
background-repeat: no-repeat;
background-position: 55% center;
background-attachment: fixed;
-webkit-background-size: 50%;
-moz-background-size: 50%;
-o-background-size: 50%;
background-size: 50%;
}
.btn {
background: #3498db;
background-image: -webkit-linear-gradient(top, #3498db, #2980b9);
background-image: -moz-linear-gradient(top, #3498db, #2980b9);
background-image: -ms-linear-gradient(top, #3498db, #2980b9);
background-image: -o-linear-gradient(top, #3498db, #2980b9);
background-image: linear-gradient(to bottom, #3498db, #2980b9);
-webkit-border-radius: 33;
-moz-border-radius: 33;
border-radius: 33px;
-webkit-box-shadow: 4px 5px 3px #697cdb;
-moz-box-shadow: 4px 5px 3px #697cdb;
box-shadow: 4px 5px 3px #697cdb;
font-family: Arial;
color: #f5ebf5;
font-size: 20px;
padding: 10px 20px 10px 20px;
text-decoration: none;
margin: 12px;
}
.btn:hover {
background: #29dde3;
background-image: -webkit-linear-gradient(top, #29dde3, #3498db);
background-image: -moz-linear-gradient(top, #29dde3, #3498db);
background-image: -ms-linear-gradient(top, #29dde3, #3498db);
background-image: -o-linear-gradient(top, #29dde3, #3498db);
background-image: linear-gradient(to bottom, #29dde3, #3498db);
text-decoration: none;
}
</style>
</head>
<body>
<input type="text" id="currentDateTime" />
<br />
<h1>your home</h1>
<h2>your address</h2>
<br />
<button class="btn" onclick="Doorbell()">Ring Doorbell</button>
<button class="btn" onclick="OpenGate()">Open Garage</button>
<script>
function Doorbell() {
let phonenum = prompt("Please Enter Your Phone Number", "");
var request = new XMLHttpRequest();
request.open("POST", "https://your_GUEST_HA_domain.com/doorbell");
request.setRequestHeader("Content-type", "application/json");
request.send(JSON.stringify(phonenum));
}
</script>
<script>
function OpenGate() {
let code = prompt("Enter Code", "");
if (code != null) {
var request = new XMLHttpRequest();
request.open("POST", "https://your_GUEST_HA_domain.com/garage");
request.setRequestHeader("Content-type", "application/json");
request.send(JSON.stringify(code));
}
}
</script>
<script>
var today = new Date();
var date = today.getFullYear() + "-" + (today.getMonth() + 1) + "-" + today.getDate();
var time = today.getHours() + ":" + today.getMinutes() + ":" + today.getSeconds();
var dateTime = date + " " + time;
document.getElementById("currentDateTime").value = dateTime;
</script>
</body>
</html>
That gets saved in your /data/guest_server_npm
directory within nginx, or wherever you are hosting website that nginx can serve from.
OTP Integration
For each person that you want to have a unique OTP, create a OTP token and give it to them.
Alternatively you could use an input_number
helper to store a pin.
Helpers
You’ll need a couple of helpers to log everything:
input_text.last_garage_code_or_person
input_text.last_doorbell_phone_number
Automation
The following need to be changed. If you create new webhooks via the GUI it will generate a unique webhook ID. You would have to click the trigger menu and assign the respective Trigger ID.
- -your_doorbell_webhook
- -your_garage_webhook
Find and replace:
sensor.person_1_otp_sensor
with the OTP sensor ORinput_number
that stores the pinPerson 1
with the name of Person 1
Repeat that for each person, and delete or add as needed.
If you wanted additional conditions you could add them for each user and use an and:
eg:
- time based access (use a scheduler to turn an
input_boolean
on and off that is checked)
If you’re using iOS, you may need to update the notification so that your Dial Number
intent works.
Automation YAML
alias: Guest Website
description: ""
triggers:
- allowed_methods:
- POST
- PUT
local_only: true
webhook_id: "-your_garage_webhook"
id: garage
trigger: webhook
- allowed_methods:
- POST
- PUT
local_only: true
webhook_id: "-your_doorbell_webhook"
id: doorbell
trigger: webhook
conditions: []
actions:
if:
- condition: trigger
id:
- code
- condition: or
conditions:
- condition: template
value_template: "{{ (trigger.json) == (states.sensor.person_1_otp_sensor.state) }}"
- condition: template
value_template: "{{ (trigger.json) == (states.sensor.person_2_otp_sensor.state) }}"
- condition: template
value_template: "{{ (trigger.json) == (states.sensor.person_3_otp_sensor.state) }}"
then:
- action: notify.mobile_app_yours
data:
message: >-
The Garage Door was Activated By {% if (trigger.json) ==
(states.sensor.person_1_otp_sensor.state) %} Person 1 {% elif
(trigger.json) == (states.sensor.person_2_otp_sensor.state) %} Person 2
{% elif (trigger.json) == (states.sensor.person_3_otp_sensor.state)
%} Person 3 {% else %} Error {% endif %}
data:
importance: high
color: red
tag: garage_doors
channel: house_quiet_alerts
visibility: public
notification_icon: mdi:garage-alert
title: Garage Door Alert
- action: input_text.set_value
metadata: {}
data:
value: NA
target:
entity_id: input_text.last_garage_code_or_person
- delay:
hours: 0
minutes: 0
seconds: 1
milliseconds: 0
- action: input_text.set_value
metadata: {}
data:
value: >-
Garage Activated By{% if (trigger.json) ==
(states.sensor.person_1_otp_sensor.state) %} Person 1 {% elif
(trigger.json) == (states.sensor.Person_2_otp_sensor.state) %} Person 2 {%
elif (trigger.json) == (states.sensor.person_3_otp_sensor.state) %}
Person 3 {% else %} NA {% endif %}
target:
entity_id: input_text.last_garage_code_or_person
- if:
- condition: trigger
id:
- doorbell
then:
- action: notify.mobile_app_yours
data:
message: Access Requested by {{trigger.json}}
data:
importance: high
color: yellow
tag: garage_doors
channel: house_quiet_alerts
visibility: public
notification_icon: mdi:doorbell
actions:
- action: URI
title: Call Back {{trigger.json}}
uri: >-
intent://#Intent;scheme=tel:{{trigger.json}};action=android.intent.action.DIAL;end
title: Doorbell!
- action: input_text.set_value
metadata: {}
data:
value: NA
target:
entity_id: input_text.last_doorbell_phone_number
- delay:
hours: 0
minutes: 0
seconds: 1
milliseconds: 0
- action: input_text.set_value
metadata: {}
data:
value: "{{trigger.json}}"
target:
entity_id: input_text.last_doorbell_phone_number
mode: single
Nginx
I’m using nginx proxy manager. Create a new proxy host with the following:
- Domain Name =
your_GUEST_HA_domain.com
- Scheme = http
- Forward Hostname / IP = 127.0.0.1
- Forward Port = 80
In the Advanced tab place the following snippit with changes to:
- -your_doorbell_webhook
- -your_garage_webhook
- your_HA_domain.com
nginx configuration
location / {
root /data/guest_server_npm;
}
location /doorbell {
return 308 https://your_HA_domain.com/api/webhook/-your_doorbell_webhook;
}
location /garage {
return 308 https://your_HA_domain.com/api/webhook/-your_garage_webhook;
}
Dashboard Cards
You can add this to your dashboard if you want a log of who activated the gate/garage door.
Dashboard Card
type: custom:logbook-card
entity: input_text.last_garage_code_or_person
hidden_state:
- Exit*
show:
state: true
duration: false
start_date: true
end_date: false
icon: false
separator: true
entity_name: true
title: Garage Pin History
max_items: 10
collapse: 2
custom_logs: false
view_layout:
position: main
I think that’s everything, please let me know if something is missing or doesn’t make sense.
Again, this is all made possible by @tmjpugh who got me started. Thank you!
I’m very interested to hear any tips that could make this better, and certainly if there are any security concerns.