So, I ran into the character limit on the original post, so I’m continuing to add to it in a reply, and I’ll just have them hyperlinked to each other.
Setting up the offline Wifi network
To round this out, the last step will be to set up a wifi network from the Raspberry Pi, which does not connect to the internet, to isolate some of our… less trusted IoT devices from the world, and keep them safe, while still conveniently keeping control of them through Home Assistant.
Straight up, this part only works if your Raspberry Pi is connected to the rest of your network via ethernet, or if you are using a USB wifi adapter as a second network interface.
When I was running Home Assistant on top of Raspberry Pi OS, I used hostapd
to broadcast a network, dchpd
to map IP addresses to devices, so they wouldn’t change over time.
A while ago, I found David Ramos had written a custom Hassio add-on that would run hostapd
within Home Assistant. Nothing to fancy, just choose your network name, password and IP range and away you go. You could combine it with the built in DHCP add-on to assign IP addresses and have it working like a champ.
For me, there were only two things missing: MAC address filtering, and choosing a specific network interface (once I got to writing this guide, anyway, I’m using the built in interface, but other’s doing similar may need to choose a USB one).
It also required making some changes with the SSH add-on, with protection mode enabled. The NetworkManager software used by Home Assistant to manage the network interfaces, as well as communication between the docker containers that make up Home Assistant, has a default setting for wifi interfaces that, while good for privacy when connecting TO wifi networks, breaks the ability to create a network for others to join.
In short, every 3-5 minutes, NetworkManager changes the advertised MAC address of your wifi interface. It keeps itself properly identified to the router running the network so it doesn’t get booted by MAC address filtering itself, but all other devices on the network see it as a new device. It’s a pretty standard thing to do now (iPhones and Android also do this by default), but when setting up to broadcast a new network, NetworkManager doesn’t realise what we’re doing and keeps changing the MAC address. All the devices on the network then disconnect because they use the MAC address (as a BSSID) to make sure they’re talking to the right network.
I had to solve this originally too, by running:
nmcli dev set wlan0 managed no
The problem with this is, occasionally updates to Home Assistant reset this. I got around that by including the above command in the systemd
unit that started hostapd
, so on ever boot the setting would be explicitly set, but we can’t do that in Home Assistant OS, and the Add-on requires us to run it manually, so you would have to remember this every time your IoT wifi stopped working.
I was looking through how it was set up and the issues section, so I could fork it and make it work around these issues, when I found, in grand open-source fashion, someone else had also run into this, and had already addressed it all in their own fork. They also expanded it to include a DHCP host all in one Add-on. (And like good Open Source citizens, they submitted a PR for their fixes around hostapd
that were relevant to the original Add-on, although they are yet to be merged).
Meet the Hass.io Access Point Add-on, by mattlongman.
Here we get a wifi network from either the built in wifi chip or an external one, a DHCP server (dnsmasq
), MAC address filtering and we can even choose whether to hide the SSID (name) of our wifi network so it does or does not show up in the viewable list of wifi networks.
And it runs the above command for NetworkManager every time it starts, to make sure the settings are right! Nailed it!
Installing the Add-on
So first, we need to install the Add-on. It isn’t in the community repository, so we will need to manually add it. Fortunately, it’s pretty straightforward.
Go to the Supervisor in your Home Assistant, and then go to the “Add-on Store” tab. Head up to the 3 vertical dots in the top right corner, and choose “Repositories”.
By default you’ll see Franck’s Community Add-ons repository, and a text field below that to allow us to add a new repository. Paste in the link to the GitHub Repo:
https://github.com/mattlongman/hassio-access-point
and then click “Add”. It will get slotted onto the list of repositories, and you’ll see it appear in the background as well. Click “Close” and then select the “Hass.io Access Point” entry in the Store, and install it.
Now there is a bit of set up to get it going. We need to know what we’re calling the network, and come up with a good password, as a lot of the devices we will be adding to it are likely to be a pain to change their wifi settings, especially as they get older and their respective configuration app get out of date.
We’ll need to choose an IP range as well. It shouldn’t be the same range as the main network, as that will be a tad confusing to Home Assistant (if there is a 192.168.1.5 on both networks, how will it know which you want to talk to?).
Finally, if you want to have MAC address filtering on, you’ll need to collect the MAC addresses of the devices you intend to connect to the network.
I should note, by the way, MAC address filtering isn’t a super strong security setting on it’s own, which is why a strong password would still be a must. It just is an added layer to help deter mischievous locals who are bored and poke around to see what they can mess with (I’m looking at you, unit 66. I know it’s you!). It basically just means they have to work out your password AND a valid MAC address, so setting something brute forcing its way through passwords will take significantly longer as it has to try each password with an array of MAC addresses as well. Still, it won’t help if your wifi password is “abc123” or something of that ilk.
Configuring your network
Now I know most people will diverge a bit here to choose their own settings for their own situations, so I’ll outline what I have and why, and it’s hopefully enough information that you can make your own choices that work.
First of all, there is a bunch of info we’ll be entering here that actually may end up being entered in multiple other parts of our configuration as well. So I kinda cheat on this, and make gratuitous use of YAML Secrets.
While it is meant to be used to keep your passwords out of your config files, so you can safely share them when asking for help or showing off your setup, it also works as a great “Source of Truth” for other info as well. I keep IP addresses, usernames, MAC addresses, just about anything that I might need to enter in multiple places through my config, and don’t want to have to remember where those places are. Update one spot, reboot, and everything is still working nicely, no forgotten corner of my config.
So let’s jump into our secrets.yaml
file in a text editor. It’s in the same folder as your configuration.yaml
. First add some comment lines, to break up what the secrets are logically. We’ll also set our wifi network’s name (SSID), password, and the broadcast address for the network (trust me, useful):
# Use this file to store secrets like usernames and passwords.
# Learn more at https://www.home-assistant.io/docs/configuration/secrets/
# Integration Passwords/Keys
...
#IOT Wifi Config
IOT-Wifi-SSID: "A Network Name"
IOT-Wifi-Key: "A c0mp1ex p@55word"
IOT-Wifi-Broadcast-Addr: "something.something.something.255"
# MAC Addresses
See where I’m going with this?
I use ESPHome, and so whenever I need to put in the wifi credentials for a new node, I can just reference the secrets, so if they change I don’t have to go and edit EVERY SINGLE NODE
You’ll also see a entry in Home Assistant’s log file every time an Add-on accesses a secret, for your piece of mind, so you can check that nothing fishy is going on.
Head back to the Supervisor, and go to the Hass.io Access Point, then the Configuration Tab, and we’ll add in those secrets to get started:
ssid: '!secret IOT-Wifi-SSID'
wpa_passphrase: '!secret IOT-Wifi-Key'
channel: '6'
address: 192.168.66.1
netmask: 255.255.255.0
broadcast: '!secret IOT-Wifi-Broadcast-Addr'
interface: wlan0
hide_ssid: '0'
dhcp: '0'
dhcp_start_addr: 192.168.66.10
dhcp_end_addr: 192.168.66.20
allow_mac_addresses: []
deny_mac_addresses: []
debug: 0
hostapd_config_override: []
Click save. The Add-on’s configuration screen doesn’t autosave so if you navigate away, even just to the log or info tabs, you lose your changes.
Lets unpack this a little.
The SSID and passphrase are our Network name and password. You’ll notice that to call the secret, it’s a little different than in the rest of our config. When you are calling a secret in configuration.yaml
(or any .yaml
file in your /config folder and it’s subfolders), you do so with:
option: !secret nameOfSecret
But when you are requesting one in the configuration of an Add-on, we need to surround them in single-quotes:
option: '!secret nameOfSecret'
Subtle difference, but you’ll be told your yaml is invalid if you don’t throw them in quotes.
Back to wifi: “channel
” is the wifi channel your network is on. If you live in a freestanding house, you are unlikely to need to change this from the default, but there are plenty of apps out there that will like you scan the wifi environment and list what channels the networks around you are using. If there are a lot, try find an unused channel to jump in. At least, use a channel different to your primary home wifi network, to minimise the interference.
The address
is the IP address your wifi chip will have, so pretty much always ending in “.1”, although you can choose any number you like really. The start of the number can be any private IP range for example, “192.168.”, “10.”, or, to be wild, anything from “172.16.” to “172.31.”
There is a nice table in that wikipedia article, and it also tells you what the next option, “netmask
”, should be for each IP range (subnet mask, in the 4th column). You want to make sure that your “IOT-Wifi-Broadcast-Addr” secret is correct for the range you choose. It should be the last possible address for that range.
I stuck with the 192.168 range, as the Pi isn’t powerful enough to host more than 30-50 devices, depending on the load each adds, so I don’t need a really large range.
The “broadcast
” address if used to send messages to every device on a network at once. For example, Wake On LAN messages get sent there, specifying a MAC address, and whatever device has the matching MAC address wakes up.
Interface
is the ID of the wifi chip we will be using. The one built in to the Raspberry Pi should always be wlan0
. An easy way to find them from here is to use the “Glances” Supervisor Add-on. It lists all network interfaces on the left side, so if you are using a USB wifi card, look at Glances before plugging in, and then plug the card in and restart the Add-on (or possibly the whole Raspberry Pi, if nothing seems to change) to see what network interface is added.
(All the interfaces starting with an underscore are used by Docker, and they may come and go, so just ignore them)
hide_ssid
does what it says on the tin. If you enter 0, the name of the network will show up in “Join Wifi Network” lists, if you enter 1, it won’t. Wifi scanning apps will still show it, but without a name.
Its worth noting, some “smart” devices don’t have the option to join a network with a hidden SSID, so you may need to leave this as 0.
dhcp
enables or disables the built in DHCP server, which assigns and keeps track of the IP addresses being handed out to devices connecting to the network. Why would we leave it disabled? Well, at this time, the DHCP server in this Add-on only does dynamic assignment: you can’t choose what IP address a particular device gets. And if one is disconnected for a while, it may get a different address when it next connects.
Depending on the devices you connect to this network, that might not be a problem, in which case you can set this to 1 and away you go!
However, a fair number of integrations either ask for the device’s IP address in YAML, or see a changed address as a different copy of that device. My TV’s integration for example, gets a login token and stores it paired with the IP address of the TV for whenever it needs to connect and send a command. If my TV’s address changes, Home Assistant can no longer connect. Additionally, my sound system’s IP needs to be entered in the YAML file to tell Home Assistant where to connect to.
On the other hand, my Air Conditioner’s integration is identified by it’s MAC address, and ESPHome devices use unique IDs, so their IP addresses changing wouldn’t matter.
So if you devices can’t afford to change IP addresses to work with Home Assistant, you’ll need to use the DHCP Server add-on from the Community Repository in addition to the Access Point add-on. Fortunately, they work well together, and we’ll get into that later.
Advice
When deciding what approach you are going to take, its worth keeping in mind that you can’t run both DHCP options at the same time, so you might just choose to use the other add-on, and just not specify any addresses to start with, so it’s easier to change if you need to for a new device later down the track.
If you aren’t using the separate DHCP add-on, set “dhcp
” to “1”, and then you can set “dhcp_start_addr
” and “dhcp_end_addr
”. Make sure it is the same IP range, and then enter any number from .2 upwards for the start address and any below .255 for the end address. Your Raspberry Pi will only give out addresses in-between the two numbers you specify (inclusive).
Next, we have “allow_mac_addresses
” and “deny_mac_addresses
”. Both just ask for a list of MAC addresses, pretty simple. You can only use one or the other, however, if you specify “allow” addresses, hostapd
will ignore the deny addresses (since the allow list will be more restrictive than the deny list, and there is no point having an address in both).
debug
sets what level of detail to get in the logs. You can see the GitHub page for the add-on for more info on that, but its basically to help track down bugs, or if something is wrong with your config. in normal use, you should leave this as 0 so you don’t waste disk space on large log files.
Lastly, hostapd_config_override
allows you to pass through a list of hostapd
settings you want to specify that aren’t explicitly exposed here. Look up hostapd
's Man file for more info if you think you need to add something here, like specific encryption settings, but generally you can leave this alone.
The add-on also sets more options than are exposed to Home Assistant for configuration, so if you are looking at this, check the “hostapd.conf
” file in the add-on’s source on GitHub. I think the defaults are sane, but you may have different needs.
MAC address filtering
So do we really need MAC address filtering? For me, yes, cause I live in an apartment building and have cheeky and bored neighbours, and during lockdown, they seem to have been getting more tech-literate
So this section is optional, but useful anyway.
Now, a MAC address is just 6 pairs of characters, that can be 0 to 9 or “A” to “F”, so not very descriptive. And when you save the configuration for a Add-on, it gets rid of any comments (lines starting with #) you leave, so entering in a list of addresses could be tricky if you replace any devices in the future, what with having to work out which one to remove and all.
So again, secrets to the rescue. Lets chuck the MAC addresses into the secrets file with short but descriptive names!
# MAC Addresses
Daikin-MAC: "AA:BB:CC:DD:EE:FF"
SoundSys-MAC: "CC:DD:EE:FF:00:11"
TV-MAC: "EE:FF:00:11:22:33"
Phone-MAC: "00:11:22:33:44:55"
Huh, wait, why did I add my phone?
Some IoT devices can only have their settings changed from mobile apps or via a web browser pointed at their IP address. So allowing my phone to connect means I can jump in and do that if needed (like if Unit 66 works out the wifi password again, and I need to change it). My phone’s settings allows me to turn of Auto-join on a per network level, so that makes it nice and easy.
Back to the Add-on configuration.
allow_mac_addresses:
- '!secret Daikin-MAC'
- '!secret SoundSys-MAC'
- '!secret TV-MAC'
- '!secret Phone-MAC'
That’s muuuuuch more readable! If we replace the TV or phone, we can just update the secrets file, restart the Access Point Add-on and bam, everything keeps running smoothly!
(hit Save. I totally didn’t repeatedly forget this and lose config after config, not at all)
It net-worked!
At this point, if you are using the built in DHCP, you can start the add-on, and it will begin broadcasting the network. The “Log” tab will display the basics of the network such as when devices connect (and if MAC address filtering blocked them), as well as what IP gets provided for each connected device (identified by MAC address in the logs). Now just start joining your devices to the wifi network and configuring them in Home Assistant as normal.
Custom DHCP
If you are setting up the second add-on, you CAN start the access point add-on now, but you might have interesting behaviour if you start connecting devices. Lets get our DHCP config set up.
Head back to Supervisor’s Add-on Store, and select “DHCP server” in the “Official add-ons” section, and then hit install.
Let’s jump straight into the config:
default_lease: 86400
max_lease: 172800
domain: iot.local
dns:
- 192.168.66.1
networks:
- subnet: 192.168.66.0
netmask: 255.255.255.0
range_start: 192.168.66.2
range_end: 192.168.66.250
broadcast: 192.168.66.255
gateway: 192.168.66.1
interface: wlan0
hosts: []
Yet another breakdown:
The two “_lease
” options are how long in seconds a IP address is held for a device before the network makes it available to different devices again if that device isn’t still connected to renew the lease at the end of that time frame.
“domain
” is not really used, but its a required entry, so I just called it “iot.local”. Any devices that set their own hostname on this network would potentially be accessible using .iot.local instead can an IP address from Home Assistant, but I’ve not really tried this out.
“dns
” is, like domain, not really used in this set up, but its a required field. I set it to the Pi’s address so devices on the network that do try to get to the internet will get a very quick “nope” from the Pi, rather than endlessly searching for nameservers.
“networks
” is very similar to our Access Point settings, so I’ll just cover the own ones, since these will all otherwise be the same. You can, however define multiple networks, so if you want to assign IP addresses for your main network as well, you can make a second list entry for that. Remember to point your router to the Pi’s main network IP address for DHCP.
“subnet
” is new, its just the network portion of the IP address followed by 0. 192.168 addresses will just have the last number as zero, whereas 10. addresses will have the last 3 numbers as 0.0.0
The wikipedia article above will help here if you are stuck.
“gateway
” is the IP address of the device that gets you from your network out to the internet, usually your modem or router. But in this case we don’t want them to have internet access. Really, this being set to the Pi’s IP address is just a way to get the devices trying to phone home over the internet a quick “nope”, like with the DNS option above. While it is technically possible to bridge this network to your main network through the Pi, the Access Point Add-on doesn’t do it.
“interface
” will be eth0 by default, which we do NOT want for these settings, as it will make the Pi start trying to give IP addresses to your main network, which could cause a little havoc if you don’t mean to do so. Make sure this is set to the same interface as in the Hass.io Access Point Add-on.
“hosts
” is where we can specify IP addresses for specific devices, so only those devices get these specific addresses. You don’t have to specify addresses for every device connected to the network, just the ones you want to always get a specific address. Fill this section out as follows, if needed:
hosts:
- name: Daikin
mac: '!secret Daikin-MAC'
ip: 192.168.66.10
- name: TV
mac: '!secret TV-MAC'
ip: '!secret TV-IP'
- name: SoundSys
mac: '!secret SoundSys-MAC'
ip: 192.168.66.12
Those secrets are coming in handy. If we need to change them later, there is one place for the info in both Add-ons!
Note that the “name
” entry can’t have spaces. Even if you add quotes, they will be removed when saving (btw, let’s Save!) and you’ll be presented with an error. Underscores and dashes work fine.
You’ll notice you can make the IP Address a secret too, which is useful if you need that IP address elsewhere in your main configuration files or other Add-ons.
(I didn’t do this for my sound system as it needs the IP address with a port number on it, and I have no idea how, or if it’s possible, to concatenate secrets. If it can be done and you know how, I’d love to know!)
It’s over!!!
And now, with all that, we can start the Hass.io Access Point Add-on, and then follow up with the DHCP server Add-on. (starting them the other way round may give you an error in the DHCP one, since the wifi interface may not be accessible).
Once you add your devices, they’ll start showing up in Home Assistant and voila, nice and neat control without having them on the internet, or using up connections on your main router (which might be a real problem if you are using a ISP provided one).
You may want to enable “Watchdog” on both Add-ons so that if they quit for any reason, they’ll get restarted automatically, so you aren’t cut off from your devices until you get the chance to log in and restart them yourself.
coming soon: Backups!