Creating a smart thermostat using ESP32+openHAB+Mosquitto+Apache+letsencrypt
I wanted a smart thermostat for my village home, so that we can turn off the heating when we leave and turn it on a few hours before we’re due to arrive. Unfortunately this came with a lot of restrictions, which basically excluded almost all choices in the market currently:
- Encryption
- No
admin
/admin
vulnerabillities - Something that respects the GDPR
- Something ideally open source, or at least that respects the GPL
- Open protocol, so I don’t need to pollute my phone with yet another fishy app
- Something that doesn’t depend on third party servers, otherwise I risk ending up with an expensive paperweight at a random company’s whim
The excellent folks at https://hestiapi.com/ have a product that look like it’s checking all my boxes. Plus, it’s apparently a company from Athens! However, I eventually decided on a DIY solution based on one of my pre-existing servers. I’d install OpenHAB and MQTT on my server, and have an ESP32 on-site as the controller. The advantage of using OpenHAB and MQTT on a pre-existing server, as opposed to a RaspberryPi on-site, is that I don’t need to try to speak with a real person on my ISP’s tech support in order to convince them to give me a real IP address.
This blog post will cover the installation of openHAB and MQTT on an existing Apache web server using letsencrypt.
For the following instructions, assume a root shell on the server.
First of all, I installed mosquitto on my Debian server:
apt install mosquitto mosquitto-clients
Then I edited /etc/mosquitto/mosquitto.conf
to make it work with a username/password and also my existing letsencrypt certificates, by adding these lines at the bottom:
tls_version tlsv1.2
listener 8883
allow_anonymous false
password_file /etc/mosquitto/users
certfile /etc/mosquitto/certs/fullchain.pem
keyfile /etc/mosquitto/certs/privkey.pem
cafile /etc/ssl/certs/DST_Root_CA_X3.pem
Now it’s referencing some files that don’t exist yet. First of all, we need to remove the existing /etc/mosquitto/certs
directory and symlink it to our /etc/letsencrypt/live/example.com
directory. We also need to give the mosquitto
user access to the certificates by adding it to the ssl-cert
group. Feel free to ignore the README that says that the directory must be readable only by the mosquitto
user – having it part of the ssl-cert
group works just fine.
We also need to create the /etc/mosquitto/users
file. We initially edit it by adding a list of usernames and passwords, one username per line, with a colon between usernames and passwords. Example:
jimmy:password
admin:letmein
We then encrypt the file using this command:
mosquitto_passwd -U /etc/mosquitto/users
Restart the mosquitto service:
/etc/init.d/mosquitto restart
And this part is ready. Next, we install openHAB. I installed the testing distribution:
wget -qO - 'https://openhab.jfrog.io/artifactory/api/gpg/key/public' | apt-key add -
echo 'deb https://openhab.jfrog.io/artifactory/openhab-linuxpkg testing main' | tee /etc/apt/sources.list.d/openhab.list
apt update
apt install openhab openhab-addons openjdk-11-jre
Now, openHAB runs its own web server on port 8080, and 8443 for SSL using self-signed certificates. We do not want to expose port 8080 to the public. Also, for SSL it’s using certificates in a different format than letsencrypt’s default, so we would theoretically need to convert the certificates every two months and restart the openHAB server. It’s easier to configure Apache to do a reverse proxy on a different port than the default 443, which we use for our own stuff. The example that I had found online uses port 444 instead, but Firefox complains that this address is restricted. So let’s use port 1443 instead:
<VirtualHost *:1443> ServerName example.com SSLEngine on SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem Header set Set-Cookie "X-OPENHAB-AUTH-HEADER=1" ProxyPreserveHost On ProxyPass / http://127.0.0.1:8080/ ProxyPassReverse http://127.0.0.1:8080/ / RequestHeader set X-Forwarded-Proto "https" env=HTTPS Header add Authorization "" RequestHeader unset Authorization ErrorLog ${APACHE_LOG_DIR}/openhab_error.log CustomLog ${APACHE_LOG_DIR}/openhab_access.log combined <Location /> AuthType Basic AuthName "example.com 1443 " AuthUserFile /etc/openhab/.passwd Require valid-user </Location> </VirtualHost>
Add the necessary NameVirtualHost *:1443
and Listen 1443
to /etc/apache2/ports.conf
and you’re ready.
You will also notice that we’re password-protecting the webpage. We’ll explain the reason in a while. For now, just create the file in question:
htpasswd -c /etc/openhab/.passwd jimmy
and enter the password in the prompt.
After this is done, restart Apache, point your browser towards https://example.com:1443
and create the administrator’s username and password. You will also be prompted to install the MQTT module.
After logging in as administrator, go to Settings -> (under System Services) API Security, click “Show advanced”, and enable “Allow Basic Authentication” in order for the Android app to work. (I’m not 100% sure that this step is necessary, in fact)
Note: DO NOT disable “Implicit User Role”, as the Android app will break. It does ask for a username and password, but I think those are used for Apache’s authentication instead. I had initially tried to disable Apache’s authentication and also disable “Implicit User Role”, thinking that already gives me proper access control. The Android app failed spectacularly.
Now, let’s add a dummy thermostat. Go to Settings -> Things and click the Plus button to create a new Thing. From MQTT Binding, select MQTT Broker. Add your example.com
hostname (ideally not 127.0.0.1
otherwise certificate verification will fail), port 8883
even though it’s the default, provide the username and password you configured for mosquitto, and enable Secure Connection. Your broker should show up as Online. In order to prevent it from breaking at every letsencrypt update, disable Certificate Pinning and Public Key Pinning, and clear their hashes.
For now, let’s add a dummy On/Off switch. Go back to Things and add a new Generic MQTT Thing. Give it a name, select the MQTT Broker you added earlier, and then go to Channels. Add a Channel of On/Off Switch type. Give it a name and select an MQTT State Topic, for instance thermostat/status
. Leave the Command Topic empty for now, it can be a read-only switch. Its Custom On value can be 1 and its Custom Off value can be 0. It should also show up as Online.
Go back to Settings and click Items. Add an item for the switch you just added and select its channel. Let’s send a dummy command to turn it on:
mosquitto_pub --insecure -u jimmy -d -h example.com -p 8883 -t thermostat/status -m 1 -P password
It should show up as ON on the openHAB GUI. Change 1 to 0 to turn it off.
If you want to check if the command arrived to the Mosquitto server itself,
you can run a listener:
mosquitto_sub --insecure -u jimmy -d -h example.com -p 8883 -t thermostat/status -P password
While it’s running, it should show you any updates that it catches.
Note that I used the --insecure
switch in both commands. I couldn’t get certificate verification to work here, but it doesn’t matter because it’s running on the host itself.
You can also install the openHAB Android client and configure it with the https://example.com:1443
remote server with your configured username and password. It will show an empty layout, but we haven’t configured our smart home’s layout yet. It will be explained in Part 2, together with the actual thermostat’s ESP32 implementation.