[{"content":"Intro # This post will go over the hardware, software, and networking I use in my enterprise-esque home network.\nThe network diagram below provides a visual representation of my network\u0026rsquo;s topology. Following the diagram is a detailed write-up on almost everything pictured, so read on if anything catches your eye.\nThis is a lengthy post, so feel free to skip around using the table of contents.\nNetwork Diagram # Making of # This was created using online diagramming app draw.io.\nYou can download the importable XML diagram file here.\nSVG Asset packs used:\nloganmarchione/homelab-svg-assets jgraph/drawio-libs Material Design Icons svgrepo.com vectorlogo.zone SVG Assets I created using Inkscape:\nDell PowerEdge R530 Server TP-Link TL-SG1016PE Switch Spectrum ET2251 Modem Ubiquiti G3 Flex Camera Inspiration from:\naathsopaach\u0026rsquo;s Network Diagram on Reddit TechGeek01\u0026rsquo;s Network Diagram on Reddit Hardware # Dell PowerEdge R530 # This is my primary (and currently only) physical server. It\u0026rsquo;s a 2U server running Proxmox and several virtual machines, both of which are covered further in the Software section.\nAfter buying this server used, I upgraded and added several parts, leading to its current specs:\n2x Intel Xeon E5-2640 v4 (10c/20t, 2.4/3.4GHz) CPUs 64GB (8x8GB) Samsung DDR4 2133MHz Registered ECC RAM 2x 2TB Samsung SATA SSDs (ZFS mirror with proxmox install and VM disks) Dell PERC H330 Mini RAID Controller (Connected to 8 drive backplane; Flashed to HBA330 firmware and passed through to TrueNAS VM) 2x 14TB Shucked Western Digital Elements HDDs (ZFS mirror in TrueNAS) 2x 500w OEM PSUs (redundant) Mini PCIe Google Coral TPU (passed through to Ubuntu Server VM, used by Frigate) With redundant PSUs and drives, this machine is built to survive common hardware failures. Additionally, it is plugged into an Uninterruptible Power Supply for protection during brownouts and power outages.\nTP-Link Switch # My primary switch is a TP-Link TL-SG1016PE.\nThis is a 16 port layer 2 gigabit switch supporting several important features:\n802.1Q VLANs 8 Power over Ethernet (PoE) Ports supporting 802.3af/at (15W/30W per-port output) with a 150W total power budget Link Aggregation Groups (LAG) Supporting VLANs is standard for any \u0026ldquo;managed\u0026rdquo; switch, but it still seems pertinent to mention it. Virtual network segmentation means you don\u0026rsquo;t have to use a different cable, switch, and port for each subnet you want to keep separate, rendering it essential in all but the simplest networks.\nPoE is another switch feature that\u0026rsquo;s almost essential in my network. Providing power to devices over their already required network connection is much less messy cable-wise, and for some devices, required. In my current setup my Ubiquiti APs, IP cameras, and Zigbee coordinator all get power over ethernet from this switch. While I don\u0026rsquo;t have a need for it currently, I do wish my switch supported 802.3bt PoE. This newer PoE standard allows ports to supply a maximum of 60-90W of power by sending power over all four twisted pairs. The higher power output could be useful for providing power to another switch, which then distributes it to several other PoE devices.\nThe last feature on my list, LAGs, is one that I haven\u0026rsquo;t used yet, but plan to someday. Being able to logically group multiple ports to act as a single interface would allow for a higher bandwidth connection from my switch to my server, which could be useful since my switch has only gigabit ports. Since almost all other devices connected to my switch are typically sending their packets to my server, the current single gigabit uplink is a potential bottleneck. That bottleneck hasn\u0026rsquo;t been a problem thus far, but if I switch to a different storage setup on my NAS with higher r/w speeds, I plan to implement a LAG between my switch and server to ensure file transfers don\u0026rsquo;t use up all my network bandwidth.\nBesides a basic implementation of QoS, the features above are about all my switch can do. It gets the job done, but since I\u0026rsquo;ve studied for and gotten a Cisco networking certification (CCNA) since buying this switch, it now feels a bit limited. If it weren\u0026rsquo;t for the high price (or power usage, in the case of used enterprise gear with a lower up-front cost) of Cisco gear, I\u0026rsquo;d probably want one of their switches for my home network in the future.\nCabling # Since my house doesn\u0026rsquo;t have existing network infrastructure, I typically have to run my own cables when connecting devices to my network. I use solid copper Cat 5e ethernet cable, which I terminate to the T568B standard. Since the majority of devices on my network support a maximum of 1000BASE-T (1Gbps), Cat 5e cabling meets my needs. It also supports 2.5GBASE-T (2.5Gbps), which I may use in the future as a cheaper alternative to 10 gigabit networking.\nWireless Access Points # For Wi-Fi in my house, I primarily use two UniFi U6-LRs from Ubiquiti. This pair of Wi-Fi 6 APs are connected to my UniFi Controller docker container, which allows for easy management and troubleshooting of my WLAN.\nWhile I like these APs, they\u0026rsquo;re not free of problems. Despite having \u0026ldquo;Long Range\u0026rdquo; (LR) in the name, these APs have a less-than-impressive range. For this reason, I ended up having to use an old consumer router in AP mode to fix a Wi-Fi dead zone between the two APs, which you can read more about here.\nClient Devices # When I\u0026rsquo;m connecting to my network, it\u0026rsquo;s usually via one of 3 devices: My Windows desktop, Linux laptop, or Android phone.\nWindows Desktop # My desktop is a Small Form Factor (SFF) PC in a sub-10 liter case - the FormD T1. I use it primarily for working on projects like this one, as well as playing online games with my friends. Here\u0026rsquo;s the full parts list for my PC and peripherals, as well as a picture inside the case:\nFrom this machine I normally use PuTTY to SSH into my Linux or FreeBSD VMs, where I\u0026rsquo;m typically editing config files or writing scripts/YAML/Dockerfiles with Vim. For projects with lots of files I need to swap between, like this website, I use VSCode over SSH (with a Vim extension, of course).\nLinux Laptop # My laptop is an HP Elitebook 840 G8 with an i5-1135g7 and 16 gigs of RAM. Since it\u0026rsquo;s a business laptop, it\u0026rsquo;s built to last (unlike many thin-and-lights), and fortunately also has great repairability.\nOn this laptop I run Xubuntu, an Ubuntu-based linux distro with the Xfce desktop environment. This is actually the OS I used on my very first computer when I was 9, so I\u0026rsquo;m quite comfortable with it. While using linux on a laptop does come with a few problems, I still find it preferable to Windows.\nSince this machine acts as my workstation away from home, it\u0026rsquo;s almost always connected to my VPN, Wireguard. While I still use VSCode, I typically use Eternal Terminal instead of plain SSH on this device. ET is a remote shell that provides resilience against spotty connections and network changes. Unlike SSH, ET can quickly reconnect to the same session after a disconnect, which is great for a frequently roaming client like my laptop.\nAndroid Phone # My phone is a Samsung Galaxy S20+. Since I love high refresh rates, I bought this phone for its 120hz screen, a feature very few competitors had at the time.\nSince this is a phone, it acts more as a consumer of my network\u0026rsquo;s services than a device I use for building upon it. Nonetheless, apps like Termux, a linux terminal emulator, and WiFiman, a Wi-Fi diagnostic tool, are often useful for troubleshooting.\nSoftware # This section will cover the software I have running on each of my servers/VMs. This includes their OS and any relevant programs, scripts, or containers.\nProxmox # Proxmox Virtual Environment is an open-source type-1 hypervisor I run on my R530. Underneath Proxmox are several virtual machines (VMs) which run most of the services on my network.\nThe Proxmox web GUI Running my servers as virtual machines under a hypervisor lends many advantages over using multiple physical servers:\nCost: Running fewer physical servers is cheaper both up front and in the long term when considering the cost of power. Flexibility: It\u0026rsquo;s easy to spin up VMs to try/deploy different OSs like Linux, FreeBSD, and Windows. Templates \u0026amp; Clones: By using a template or cloning an existing VM, a new VM can be up and running within seconds. Resource Sharing: A large shared resource pool means each VM can leverage more/less resources as needed. Snapshots \u0026amp; Backups: Easily saving or restoring a VM\u0026rsquo;s state (running or not) is great for testing and backups. There are even more benefits, but this list gets the general idea across.\nThe remaining sub-sections of Software will cover services running on VMs under Proxmox.\nOPNsense # OPNsense is an open-source FreeBSD-based firewall and router. Besides acting as the default gateway for each of my subnets, it also provides DHCP, DNS, and VPN services to my network.\nSensible auto-creation of routes, NAT rules, and firewall rules give OPNsense functionality similar to a consumer router out of the gate. Despite its ease of use, OPNsense\u0026rsquo;s feature-set isn\u0026rsquo;t lacking, and is even further bolstered by its library of community plugins.\nOPNsense is highly configurable via both its web interface and REST API. If all else fails, advanced users can SSH into OPNsense\u0026rsquo;s FreeBSD shell to manage things under the hood.\nEditing firewall rules in the OPNsense web GUI Unbound DNS # Unbound DNS is a validating, recursive, caching DNS resolver. Through its integration in OPNsense many features can easily be enabled/configured. For finer control, you can add your own Unbound configuration files alongside those generated from OPNsense\u0026rsquo;s config.\nAside from resolving DNS requests for other domains, Unbound also serves local DNS entries for my domain. These include both manually created overrides, and ones which OPNsense automatically creates for each DHCP client that supplies a hostname.\nI also use Unbound for DNS ad blocking. By returning an answer of 0.0.0.0 for domains that serve ads, Unbound stops client devices from being able to find/load the ads. I have 3 lists of domains configured in OPNsense for which DNS requests are \u0026ldquo;black holed\u0026rdquo;:\nSteven Black Hosts AdGuard List oisd big List By blocking the domains on these lists I see very few ads. When combined with the uBlock Origin browser extension, the internet is nearly ad-free.\nWireGuard VPN # WireGuard is a lightweight, performant, and easy-to-use VPN. Installed as an OPNsense plugin, it enables remote access for trusted client devices which leave my house.\nBy using a VPN when away from home, I\u0026rsquo;m able to use internal services without completely exposing them to the internet. Since WireGuard is extremely secure, allowing remote access in this way introduces minimal security risk.\nBesides allowing clients remote access, I\u0026rsquo;m also using WireGuard as a site-to-site VPN. With the remote site configured just like any other peer, WireGuard logically links the two networks as if they were connected by a point-to-point link.\nTrueNAS # TrueNAS Core is an open-source FreeBSD-based storage server. It uses OpenZFS for data security and reliability, and supports multiple well-known protocols for network file sharing.\nThe TrueNAS Core web GUI Since I virtualize TrueNAS, my storage drives are connected to an HBA passed through to the TrueNAS VM to give TrueNAS direct access to them. Currently I have two 14TB drives mirrored in pool \u0026ldquo;shucks\u0026rdquo;, under which datasets like \u0026ldquo;media\u0026rdquo; and \u0026ldquo;data\u0026rdquo; store my files.\nFor sharing datasets to other devices on my network, TrueNAS hosts several NFS and SMB shares. Access to these shares is limited using TrueNAS accounts and/or IP whitelists.\nSnapshots # One of the most powerful features of ZFS is snapshots. Enabled by ZFS\u0026rsquo;s copy-on-write architecture, snapshots efficiently save a dataset\u0026rsquo;s state at a point in time. Practically, scheduled snapshots protect against ransomware and accidental data deletion with minimal overhead.\nWith TrueNAS it\u0026rsquo;s easy to schedule automated snapshot creation and removal through a cron frontend. Using this system, I have TrueNAS create snapshots hourly, daily, and weekly (kept for 2 days, 2 weeks, and 2 months, respectively).\nUbuntu Server # Ubuntu Server is the headless server version of linux\u0026rsquo;s most well known distro. Having limited linux knowledge when creating my first general-purpose server VM, Ubuntu seemed like a good place to start.\nThis instance of Ubuntu Server (creatively named \u0026ldquo;ubuntuserver\u0026rdquo;) runs several services in Docker containers, as well as some scheduled cronjobs. Besides that, it\u0026rsquo;s also where I work on most of my projects (over SSH). Typically this entails writing/editing Dockerfiles, scripts, YAML, and more with Vim, or running commands from my shell of choice, Bash.\nCronjobs # I have a few tasks scheduled with cron on ubuntuserver, mostly running scripts I\u0026rsquo;ve written.\nMy Porkbun API bash script gets run every 6 hours to ensure the DNS A record pointing to my house is up to date with my public IP. It also runs once a week to keep my wildcard SSL cert up to date.\nMy Google API IPs python script runs daily to keep an OPNsense alias up to date. You can read more about how I use this script to connect Home Assistant to Google Assistant here.\nDocker # Docker is a containerization platform/runtime through which I deploy many of the services in my network. The Dockerfile and docker-compose.yml files I use to deploy these containers are available at the github repository below. corey-braun/docker-containers The Dockerfile and Docker Compose files defining my Docker containers null 0 0 Emby # Emby is a media server which automatically fetches metadata for your shows and movies. Running Emby\u0026rsquo;s official Docker image, I can browse and stream my media library through a user-friendly web interface or app (available on most major platforms - iOS, Android, Apple TV, etc).\nUsing an HDHomeRun TV Tuner, my Emby container also acts as a DVR, which my family uses to record/watch TV. Emby doesn\u0026rsquo;t have Live TV commercial detection or skipping built in, but with this plugin it can read .edl files generated by Comskip, automatically skipping segments marked as commercials.\nUsing this Docker image I created, I built a static Comskip binary, which I bind mount in my Emby container. By setting Comskip as the \u0026ldquo;Recording Post Processing Application\u0026rdquo; it is automatically run on new TV recordings. Along with the aforementioned plugin, this fully automates commercial detection and skipping in Emby.\nUniFi # The UniFi Network Application allows management of Ubiquiti\u0026rsquo;s UniFi devices through a webapp. With linuxserver/unifi-network-application alongside a MongoDB container I host it in Docker to manage my UniFi APs.\nHome Assistant # My Home Assistant Docker stack consists of three containers:\nHome Assistant, a smart home control and automation platform. Frigate, an NVR with AI image detection. Eclipse Mosquitto, an MQTT broker. The latter two containers are integrated with Home Assistant to expose devices connected through them. Even more devices are connected via Zigbee, which I documented setting up in this post.\nFrigate\u0026rsquo;s AI image detection can be run on any CPU, but I use a more performant Google Coral TPU, which is made available to my virtualized docker host with PCIe passthrough, then bind mounted in the container.\nYou can see all of my Home Assistant-related posts here.\nTraefik # Traefik is a reverse proxy and load balancer which integrates with docker for automatic/dynamic configuration. With a trusted SSL cert, which it can optionally generate for you, clients open an encrypted connection to Traefik. From here Traefik can route the connection to different endpoints based on many factors.\nIn my internal network I have multiple DNS entries pointing to Traefik. Based on the hostname clients use to access it, Traefik routes to different services. This means I can open a secure connection to Traefik, which serves a valid cert and routes traffic to my end destinations, removing the need to install a cert on every endpoint.\nIn the case of other containers, Traefik\u0026rsquo;s backend connection to these services is done over plain HTTP. Even though this final hop from Traefik to the service isn\u0026rsquo;t encrypted, it\u0026rsquo;s done over docker\u0026rsquo;s networking, where it isn\u0026rsquo;t subject to attacks or interception by outside attackers.\nWhen routing to services on other VMs, like OPNsense or TrueNAS, Traefik opens an HTTPS connection to them, accepting the self-signed default cert they provide. Technically this isn\u0026rsquo;t much more secure than just opening an HTTPS connection to them directly on my client, but it\u0026rsquo;s nice not having to click through the \u0026ldquo;invalid cert\u0026rdquo; warning in the browser.\nWordPress # I also host a WordPress website in Docker. Since it has higher uptime requirements than my other containers, this stack is hosted on a separate linux VM. It consists of three containers:\nTraefik, which proxies connections to WordPress and handles SSL cert requests/updates through Let\u0026rsquo;s Encrypt (This is a different Traefik instance than the one in my main docker stack). WordPress, an easy-to-use site builder. MariaDB, an open-source database used by the WordPress container. This WordPress website is used by my sister, Megan. She is the artist behind the beautiful backgrounds used on this site; If you like what you\u0026rsquo;ve seen so far, go check out more at meganbraunart.com!\nNetworking # This section will cover the architecture and policies of my network at layers 2 through 4. The earlier section on OPNsense, my router/firewall, covers application layer network services it provides, like DNS and DHCP.\nSubnets/VLANs # The subnets I use are as follows:\nSubnet Name Network Address VLAN Access Clients LAN 10.1.3.0/24 1 (Native) Full PCs, Phones, Servers, Misc. WireGuard 172.30.30.0/24 N/A (VPN) Full Remote Laptops, Phones, Etc. Guest 192.168.10.0/24 10 Internet Guests\u0026rsquo; Devices DMZ 192.168.20.0/24 20 Internet Public-Facing Services IoT 192.168.30.0/24 30 None Smart Home Devices As you can see, I use primarily IPv4 with a subnet mask of /24. While I\u0026rsquo;ll never have 254 hosts on any subnet, there\u0026rsquo;s little reason to designate any fewer host bits either, as the private IPv4 space can more than accommodate any number of /24s I may want.\nAnother thing to note is that wireless clients on the Guest network aren\u0026rsquo;t allowed to communicate even with devices on their subnet, a policy enforced by my UniFi APs. Also, outbound NAT is completely disabled on the IoT network to further ensure these devices cannot talk to the internet.\nIn terms of incoming access, the DMZ network has servers with port forwards (inbound NAT mappings) pointing at them. These ports have wildcard allow rules pointing to them, allowing any host access them.\nSubnet Numbering # When looking at addressing of each subnet, you can see the first two subnets, LAN and WireGuard, are in the 10.0.0.0/8 and 172.16.0.0/12 space, respectively, while the last three are in the 192.168.0.0/16 space, with their third octet corresponding to their VLAN tag.\nHaving my subnets set up this way helps me more easily parse logs and packet sniffers at a glance. Just from the first few digits of an address I can see if a client is \u0026ldquo;trusted\u0026rdquo; (i.e. not on a VLAN), and whether it is coming from a VPN or local connection. Additionally, the third octet of each 192.168 subnet matching the interface\u0026rsquo;s VLAN tag provides additional clarification as to which VLAN they\u0026rsquo;re on.\nISP # My internet connection is supplied by Spectrum, from which I get one IPv4 address and a /56 IPv6 prefix. Both of these are dynamically allocated, and therefore subject to change at any time. This means I have to use dynamic DNS for any public-facing services to remain consistently accessible, more on that in the Ubuntu Server Cronjobs section.\nIn terms of bandwidth, I get 300 Mbps download and 10 Mbps upload. These low upload speeds make most things I make externally accessible very slow, and due to the lack of competition in my area I don\u0026rsquo;t see them improving any time soon.\nIPv6 # As you may have noticed, I haven\u0026rsquo;t talked about IPv6 much thus far. Since I have a dynamically assigned IPv6 prefix, I have been hesitant to implement a fully dual-stack network. IPv6 has a lot of benefits over IPv4, but when your assigned prefix can change at any time, many of those benefits are negated, or worse, turned into problems.\nOne issue would be updating AAAA DNS records. Since all of my local IPv4 addresses get NAT\u0026rsquo;d to my public IP when routing to the internet, I only have to keep one public DNS record up to date. With the benefit of IPv6 giving me multiple internet-routable IPs would come the problem of having to update multiple DNS records when my prefix changes. I could use NAT66 to make all my services accessible from a single IPv6 address, leaving only one AAAA record to update, but at that point there\u0026rsquo;s little benefit to using IPv6 at all.\nAnother problem comes from my VPN, WireGuard. With WireGuard I have to specify which destination IPs should be routed over the VPN in each client\u0026rsquo;s config (to be added to their routing tables). Since I want to route only traffic destined to devices in my home network over the VPN, I would have to manually change this config on every client when my prefix changes. I could resolve this using IPv6\u0026rsquo;s Unique Local Addresses (ULAs) to have a consistent IPv6 space similar to the private IPv4 space, but again, this becomes more work than it\u0026rsquo;s worth, and makes using IPv6 an additional hassle rather than a benefit.\nDue to these issues, I currently have a very basic/incomplete implementation of IPv6 in my network: OPNsense is configured to request a /56 prefix from my ISP, but IPv6 is only enabled on my LAN network, using subnet X:X:X:XX01::/64. With this bare minimum config, clients can use Neighbor Discovery Protocol (NDP) to discover the subnet\u0026rsquo;s prefix, then use stateless address autoconfiguration (SLAAC) to generate their own IPs. Since I don\u0026rsquo;t have any static IPv6 addresses or AAAA records, these IPv6 addresses are only useful for accessing the internet, not local services.\nI wish there was a way to have a static IPv6 prefix through my ISP. If my assigned prefix didn\u0026rsquo;t change implementing IPv6 in my network would be useful and hassle-free. Perhaps I will navigate the pitfalls of implementing IPv6 with a non-static prefix someday, but for now my half-hearted IPv6 adoption is serving me well enough.\nFuture Plans # There\u0026rsquo;s always more to learn, so my network and servers remain a perpetual work in progress. To end off this post, here\u0026rsquo;s a few more things I\u0026rsquo;m planning to implement in the future:\nScheduled encrypted offsite backups Automatic shutdowns during power outages using Network UPS Tools 2-3 Host Proxmox High Availability Cluster Deployment and Provisioning of VMs with IaC (Terraform/Ansible) Windows Server(s) + Active Directory Some form of Single Sign-On More Cloud and Hybrid-Cloud projects (including an email setup I\u0026rsquo;m currently working on with AWS and docker) ","date":"November 1, 2023","permalink":"/posts/my-home-network/","section":"Posts","summary":"Intro # This post will go over the hardware, software, and networking I use in my enterprise-esque home network.","title":"Everything in my Home Network"},{"content":"","date":"November 1, 2023","permalink":"/tags/networking/","section":"Tags","summary":"","title":"Networking"},{"content":"","date":"November 1, 2023","permalink":"/tags/","section":"Tags","summary":"","title":"Tags"},{"content":" Hi, I\u0026rsquo;m Corey Braun!\nEver since my dad helped me build my first computer at 9 years old, I\u0026rsquo;ve loved technology. I\u0026rsquo;m now looking to transition this lifelong interest into a career in IT.\nOn this site I upload blog-style posts documenting the projects I work on in my overcomplicated home network. Besides their posts here on my website, many of my projects also have open-source repositories on my github for anyone to use and build upon.\nThe awesome background art on this website was created by my sister, Megan. Go check out more of her artwork at meganbraunart.com!\nCertifications # Cisco Certified Network Associate (August 2023) ","date":"September 2, 2023","permalink":"/about-me/","section":"","summary":"Hi, I\u0026rsquo;m Corey Braun!","title":"About Me"},{"content":"","date":"July 26, 2023","permalink":"/tags/coding/","section":"Tags","summary":"","title":"Coding"},{"content":"","date":"July 26, 2023","permalink":"/tags/home-assistant/","section":"Tags","summary":"","title":"Home Assistant"},{"content":" This project has an associated GitHub repository. Intro # One of my favorite things about Home Assistant is that it lets me keep control of my IoT devices off the internet. I was therefore disappointed when I found that connecting HA to Google Assistant requires allowing external access. I\u0026rsquo;d like to control my smart home devices with my phone\u0026rsquo;s assistant, but I\u0026rsquo;m not willing to completely open my HA instance to the world.\nI found several others\u0026rsquo; solutions to this problem on Home Assistant\u0026rsquo;s forums, but I ended up solving the issue in a different (and in my opinion better) way than the other solutions I found, so I\u0026rsquo;m making this post to document my process and hopefully help others who might have a similar goal.\nChoosing an approach # The most active thread regarding this topic on the Home Assistant community forums had several solutions suggested by users.\nSome users had firewall rules only allowing a few CIDR blocks from Google\u0026rsquo;s SPF record to access their Home Assistant instance. While these users did seem to have success connecting Google Assistant to their HA instance, other users reported Google bots attempting to connect from IPs outside of these ranges. Using a manual whitelist like this is simply too unreliable. There\u0026rsquo;s no telling when the IPs Google uses to connect might change, and no way of knowing whether your list is actually up to date.\nOther users were limiting access to their Home Assistant instance to any IPs associated with Google\u0026rsquo;s ASN. Where the previous user didn\u0026rsquo;t allow enough IPs, this solution cast the net too wide. Whitelisting the entire ASN allows Google\u0026rsquo;s APIs access, but also allows any Google Cloud Platform customers access, a very thin barrier to entry.\nNeither of these solutions seemed to fit the bill for me, so I did some research on what IPs Google uses for their APIs, and found this Google support page. The page links to two lists of CIDR-notation IP blocks in JSON format:\nA list of all of Google\u0026rsquo;s public IP blocks A list of IP blocks assigned to GCP customers Per the article, the list of IPs hosting Google APIs and services is the first list minus the second list. Using this information, I can make a firewall rule that only allows these IPs, thus limiting access to my Home Assistant instance as much as possible while still allowing it to connect to Google Assistant.\nCreating a firewall rule # Since the two lists of Google IPs are very long and updated frequently, updating this firewall rule would have to be automated. This means I\u0026rsquo;d need to write a script to not only get the IPs, but also add them to an alias which would be used as the allowed source IPs in a firewall rule. Fortunately, I use OPNsense as my firewall, which has an API through which I can update an alias with the contents of this IP list.\nFor actually getting the alias content, there is a Python module called netaddr which can subtract these two large lists of IPv4 and IPv6 CIDR-notation network blocks. Starting this project I knew almost no Python, but since I\u0026rsquo;ve been wanting to start learning it, this served as a great opportunity!\nIf you\u0026rsquo;d like to use the script I wrote, it is available at the github repository linked below. Even if you aren\u0026rsquo;t using OPNsense, it can write the list of IPs to a file, or print them to stdout for use however else you\u0026rsquo;d like. corey-braun/google-api-ips Python script to create/update an OPNsense alias with the IPs used for Google\u0026rsquo;s APIs Python 0 0 Since the alias needs to be kept up to date, I created a cronjob to run this script daily on my linux server.\nWith the alias created and set to update, I next created a port forward on OPNsense which allows packets with a source matching the alias to reach my reverse proxy, Traefik. Traefik serves a valid SSL cert for my domain (a requirement for connecting to Google Assistant) and routes traffic from the forwarded port to my Home Assistant docker container.\nConnecting with Google Assistant # With the networking done, I followed Home Assistant\u0026rsquo;s documentation on connecting to Google Assistant. Within my Home Assistant configuration for the google_assistant integration I chose to only manually allow the few devices I wanted to control with my voice to connect to Google Assistant, further limiting access in the event that a connected Google account was compromised.\nOnce I was finished following the integration\u0026rsquo;s instructions in the docs I attempted to connect to Home Assistant in Google Home. On my first attempt I got an error saying Google \u0026ldquo;Couldn\u0026rsquo;t reach\u0026rdquo; Home Assistant. Within the Home Assistant logs, however, I found a login attempt with \u0026ldquo;invalid authentication\u0026rdquo; from one of the Google IPs in my OPNsense alias, which at least confirmed my networking was working, unlike what the Google Home error initially lead me to believe.\nWith the information gleaned from these nonspecific error messages, I eventually found my problem: The Home Assistant account I\u0026rsquo;d configured Google Home to use was set to only allow logins from the local network. After correcting this I was able to connect HA to Google Home successfully. With everything finally set up, I\u0026rsquo;m now able to control my devices with my voice via Google Assistant, all without making Home Assistant publicly accessible on the internet.\n","date":"July 26, 2023","permalink":"/posts/google-assistant-home-assistant/","section":"Posts","summary":"This project has an associated GitHub repository.","title":"Minimizing External Access while connecting Google Assistant to Home Assistant"},{"content":"","date":"July 26, 2023","permalink":"/tags/security/","section":"Tags","summary":"","title":"Security"},{"content":"Intro # Zigbee is a wireless mesh connectivity standard for smart home devices. It\u0026rsquo;s great for small, low-power devices, like sensors and buttons. Compared to Wi-Fi, Zigbee devices are much easier to set up and typically offer longer battery life.\nChoosing a Zigbee Coordinator # Zigbee devices all connect back to one central hub, a Zigbee coordinator. While there\u0026rsquo;s many options for coordinators, deciding on one was actually a pretty easy choice for me.\nAlmost all Zigbee coordinators use USB to connect directly to your smart home control platform. Since I use Home Assistant running on a server in my basement, this would put my coordinator in a pretty bad location for a wireless hub.\nFortunately, TubesZB Zigbee Coordinators connect to your network via ethernet. This allows me to place my coordinator in a central location rather than being tethered to another computer. These coordinators also come highly recommended on the Home Assistant Forums.\nThe specific device I ended up choosing was the EFR32 MGM21 PoE Coordinator. At the time of purchase, it had the most recent hardware of the TubesZB coordinators. Power over Ethernet is also a plus since my switch can power the device over its already required network connection.\nMy TubesZB Coordinator Setting up the coordinator # I\u0026rsquo;m going to be putting this zigbee coordinator on my IoT VLAN, which is accessible locally only and has NAT disabled to prevent access to the internet. Upon plugging the coordinator into a PoE port on the correct VLAN, it got a DHCP lease on the IoT subnet, which I then changed to a static mapping in my router. This ensures the coordinator will get the same IP every time, allowing me to connect it to home assistant via its IP.\nSpeaking of which, there are two options for connecting zigbee coordinators to Home Assistant: The Zigbee Home Automation (ZHA) Home Assistant integration, or using Zigbee2MQTT (which can be run as a docker container) and controlling zigbee devices through home assistant\u0026rsquo;s connection to your MQTT broker. Since the TubesZB github page lists EFR32 coordinators\u0026rsquo; Zigbee2MQTT support as \u0026ldquo;experimental\u0026rdquo; I\u0026rsquo;ve just gone with ZHA for now, which seems to be the simpler option to implement anyways.\nOn the TubesZB github page the coordinator\u0026rsquo;s creator has a few lines he suggests adding to your Home Assistant configuration.yaml when using an EFR32 device (which I am). Using this configuration as a base I edited a few things to my liking, using the ZHA integration documentation as a reference. Here\u0026rsquo;s the config I ended up with:\nzha: custom_quirks_path: /config/zha_custom_quirks zigpy_config: network: channel: 25 ota: otau_directory: /config/zigpy_ota source_routing: true handle_unknown_devices: true ezsp_config: CONFIG_APS_UNICAST_MESSAGE_COUNT: 30 CONFIG_TX_POWER_MODE: 3 CONFIG_MAX_END_DEVICE_CHILDREN: 32 CONFIG_BROADCAST_TABLE_SIZE: 60 CONFIG_SOURCE_ROUTE_TABLE_SIZE: 200 CONFIG_ROUTE_TABLE_SIZE: 32 CONFIG_ADDRESS_TABLE_SIZE: 32 CONFIG_PACKET_BUFFER_COUNT: 255 CONFIG_BINDING_TABLE_SIZE: 32 CONFIG_NEIGHBOR_TABLE_SIZE: 16 CONFIG_MTORR_FLOW_CONTROL: 1 CONFIG_MAX_HOPS: 5 The things I changed from the TubesZB suggested config were the OTA \u0026amp; custom quirks folder paths (which I set to be subdirectories in /config), as well as the Zigbee channel for my network to use. I set the channel to 25, which according to a diagram in this article won\u0026rsquo;t interfere with 2.4GHz Wi-Fi channels 1 and 6, which I use on my UniFi APs. Since Zigbee uses lower power signals than Wi-Fi it is important to ensure they aren\u0026rsquo;t operating on the same frequencies, as that could disrupt the Zigbee network\u0026rsquo;s operation.\nAfter restarting home assistant to apply my configuration changes, I set up my coordinator with the ZHA integration by following the TubesZB github page\u0026rsquo;s instructions.\nMigrating Philips Hue devices # With my coordinator set up, I was now ready to move over my Philips Hue zigbee devices, which include 3 color bulbs and a dimmer switch. These were previously paired to a \u0026ldquo;Hue Hub\u0026rdquo; (Philips Hue\u0026rsquo;s proprietary zigbee coordinator), so I\u0026rsquo;d have to remove them via the hue app before pairing them to my new coordinator. Before un-pairing them I clicked \u0026ldquo;Add device\u0026rdquo; under the ZHA integration, which makes it search for new zigbee devices to adopt. After removing the devices from the hub app, I expected them to enter pairing mode and get picked up by ZHA.\nAs soon as I clicked \u0026ldquo;Remove Device\u0026rdquo; in the hue app, bulb 1 and 2 entered pairing mode and were picked up by ZHA. Unfortunately, bulb 3 and my dimmer switch did not get picked up. The dimmer switch has a reset button, so I wasn\u0026rsquo;t as worried about it as the light. I tried flicking the light switch off then on again a couple times, but the first bulb still wasn\u0026rsquo;t getting picked up.\nSearching online, I found that hue bulbs can be reset using a hue dimmer switch by pressing and holding the off and on switch for 10 seconds in close proximity to a bulb. When I tried this, however, it only caused bulb 2 to blink and reset; I guess I should have unscrewed the other 2 bulbs first\u0026hellip; After this reset of bulb 2 it was now in the same state as bulb 3, with neither of them wanting to pair to my coordinator.\nTurning to the infinite wisdom of reddit, I found a comment claiming that hue bulbs could be reset (thereby entering pairing mode) by following this procedure:\nStart with bulb off for at least 5 seconds Turn lights on for 8 seconds Turn lights off for 2 seconds Repeat steps 2 and 3 three more times Turn on lights, they should blink for 5 seconds if the reset was successful Since my bulbs were all in a Bond Light fixture which is also connected to Home Assistant, I used the controls in HA to follow this on/off procedure, again making sure ZHA was searching for new devices first. Fortunately, following this procedure did end up with my lights all flashing for 5 seconds, then being picked up by ZHA. Thanks reddit!\nFor the dimmer switch I used a paperclip to press the \u0026ldquo;reset\u0026rdquo; button on the back for ~15 seconds, after which the front LED flashed orange and green a few times, then the device was picked up by ZHA.\nAdding other devices # Update from 2023-11-05:\nAfter using my Zigbee network for a couple months, I\u0026rsquo;ve been very happy with it. After several power outages, devices have reconnected automatically, and controlling Zigbee devices continues to be reliable and snappy.\nUnlike my initial experience migrating old devices, pairing new devices has been extremely painless. I\u0026rsquo;ve added several sensors and smart plugs now, and connecting each one has been as simple as pressing their pairing button while ZHA is searching for new devices.\n","date":"July 24, 2023","permalink":"/posts/zigbee-home-assistant/","section":"Posts","summary":"Intro # Zigbee is a wireless mesh connectivity standard for smart home devices.","title":"Setting up a Zigbee Network with Home Assistant"},{"content":"","date":"July 24, 2023","permalink":"/tags/wireless/","section":"Tags","summary":"","title":"Wireless"},{"content":"Intro # A year or two back I switched from a traditional all-in-one home router, the ASUS RT-AC88U, to a more \u0026ldquo;prosumer\u0026rdquo; setup, using a separate router, switch, and Access Points (APs). Unfortunately, when planning my Wi-Fi design I overestimated the range of the two UniFi U6-LRs I purchased. This left one room, which was located on the edge of my house, directly between my APs, with poor Wi-Fi signal. For a while the deadzone wasn\u0026rsquo;t too large an issue, but when my sister decided to use this room as an office, doing frequent video calls as well as using her laptop, phone, and tablet, all connected to Wi-Fi, the problem became much more pronounced. Today I\u0026rsquo;m going to patching up this weak point in my wireless network by using my old consumer router as an AP, configuring it with the same SSID as my other APs for seamless client roaming.\nHow this will work? # This problem has been ongoing for a little while, and honestly I didn\u0026rsquo;t consider this solution until recently stictly due to me misunderstanding how Wi-Fi roaming works. I thought the process of a wireless client switching from one AP to another (roaming) was in some way handled by the APs communicating with one another to hand off the client. Because of this misconception I thought that I couldn\u0026rsquo;t have seamless client roaming across the same SSID when using APs from differing brands. In reality, it is the client that makes the decisions regarding when to roam from one AP to another. When a Wi-Fi client\u0026rsquo;s connection to its current BSSID (the service set an individual AP provides) becomes too weak it will search for other BSSIDs (other APs) that are part of the same Extended Service Set (ESS - a term encompassing all APs on the same network advertising an identical SSID \u0026amp; authentication). If the client finds new candidate BSSIDs it will authenticate \u0026amp; associate with the one it has the best connection to, then end the connection with the previous AP.\nIf that somewhat technical explanation was confusing, here\u0026rsquo;s the short and sweet version: If multiple APs on the same network are configured with the same SSID name and authentication, Wi-Fi clients can switch between them seamlessly. The important thing here is that wireless clients\u0026rsquo; experience will be the same roaming between my UniFi APs and my old router in AP mode.\nConfiguring the router # The first step to configuring my router as an access point was getting it connected to my current network. Since this project was still in a proof-of-concept phase at this point, I just ran a spare ethernet cable from my main switch across the floor to the office. If I do decide to keep this as a more permanent solution I\u0026rsquo;ll find a way to run a cable more nicely later. From here I connected the router to power and plugged in the ethernet cable to its \u0026ldquo;WAN\u0026rdquo; port. I used the WAN port since the router was currently configured to act as an all-in-one consumer router, and I didn\u0026rsquo;t want it to start fighting OPNsense as the main router on my main network (making DHCP offers and such). I actually found an ASUS article that says to plug into a LAN port when using one of their routers in AP mode, but I never switched it and it didn\u0026rsquo;t cause any trouble.\nAfter getting the soon-to-be AP plugged in, it recieved a DHCP lease from my router, which I set to a static mapping so it wouldn\u0026rsquo;t change, and so Unbound (my DNS server running on OPNsense) would automatically create a DNS registry for it. From here I was able to access its web interface to configure it.\nFirmware Update # The first thing I did was update the router\u0026rsquo;s firmware. I didn\u0026rsquo;t see anything too intriguing when skimming the release notes for the firmware updates between the one I had and the latest, just lots of security patches, good to have nonetheless.\nGetting to AP Mode # The next thing to do was switch the router to AP mode. I did this by going to Administration \u0026gt; Operation Mode \u0026gt; Access Point Mode. After doing this and configuring the SSID to the same settings as my UniFi APs (under Wireless \u0026gt; General) my wireless clients were roaming to and from it when entering and exiting the office.\nWi-Fi bands and channels # Even though my AP is now working, I\u0026rsquo;m not done configuring it yet. With this AP being nestled between my two other APs the overlap in service area is going to be even greater than before. Because of this, if I have APs on conflicting channels they could definitely cause issues.\nOn the 2.4GHz band I have my 2 UniFi APs running on channels 1 and 6. Since 2.4GHz channels 1, 6, and 11 are non-overlapping I could just stick this new AP on channel 11 and avoid any Wi-Fi interference. The only problem with this is that I\u0026rsquo;m planning on implementing a zigbee network soon, which will also operate in the same 2.4GHz space as Wi-Fi. Because of this I want to leave the top of the 2.4GHz space available for my zigbee network to ensure it doesn\u0026rsquo;t deal with any interference, eliminating Wi-Fi channel 11 as an option.\nWhile I could change the channels for my UniFi APs, alternating between channels 1 and 6 for the 3 APs, the solution that fits my needs here is actually just not using the 2.4GHz band on this newly introduced AP. Since this AP is older than my UniFi APs, it doesn\u0026rsquo;t support Wi-Fi 6, so I want to steer clients away from using it unless it\u0026rsquo;s absolutely necessary. Because of this, the shorter range of the 5GHz band will actually be a benefit rather than a detriment.\nWithin the asus web interface I was able to choose my 5GHz channel under Wireless \u0026gt; General. I chose band 161 with a 40MHz channel width as this wouldn\u0026rsquo;t interfere with my existing APs\u0026rsquo; 5GHz channels. Under Wireless \u0026gt; Professional I was able to disable the 2.4GHz band, as well as configure a couple useful settings on the 5GHz band to support my goal of limiting the usage of this AP.\nFirst I enabled \u0026ldquo;Roaming assistant\u0026rdquo;, which drops wireless clients when their RSSI (signal strength) falls below a configurable threshold, forcing them to roam to another AP. This is useful to stop wireless clients from staying with this AP for too long when better options are availible, and as I\u0026rsquo;ve already established that\u0026rsquo;s pretty much anywhere in the house except the room this AP will be in. Because of that I\u0026rsquo;m starting with this set at -55 dBm, but I\u0026rsquo;ll update it as needed (normally clients won\u0026rsquo;t try to roam until their RSSI is around -70dBm or lower).\nThe final setting I changed was \u0026ldquo;Tx power adjustment\u0026rdquo;, a slider which I set to its lowest value, \u0026ldquo;Power Saving\u0026rdquo;. This setting just reduces the strength at which the AP transmits its 5GHz signals. Lowering this setting causes a quicker drop in RSSI as clients move away from the AP, thus promoting roaming sooner.\nConclusion # Overall I\u0026rsquo;m pretty happy with the solution I\u0026rsquo;ve gotten to here. So far it\u0026rsquo;s working great, clients are using the AP in the office when needed, but are still prioritizing my UniFi APs most of the time. The only real downsides come from worsening the utility of the UniFi controller application I run in docker. Since wireless client information and configuration editing/viewing for the ASUS AP aren\u0026rsquo;t availible in UniFi controller, it becomes less reliable and less useful as a central tool for managing my wireless network. I knew this would be a consequence when I decided on this solution, though, and for me it\u0026rsquo;s worth saving the pretty penny another UniFi AP would\u0026rsquo;ve cost, their stuff is not cheap!\n","date":"July 22, 2023","permalink":"/posts/consumer-router-ap/","section":"Posts","summary":"Intro # A year or two back I switched from a traditional all-in-one home router, the ASUS RT-AC88U, to a more \u0026ldquo;prosumer\u0026rdquo; setup, using a separate router, switch, and Access Points (APs).","title":"Using a Consumer Router as an AP to fix a Wi-Fi Dead Zone"},{"content":"Intro # In an ideal scenario all IoT/Smart Home devices should be blocked from accessing the internet. This ensures that no bad actors can tamper with them, and stops them from sending data back to their manufacturers, something that can be a major privacy concern, especially with devices like cameras.\nMost IoT device manufacturers intend their products to be linked to an app made by them. Unfortunately, for many of these apps this requires allowing your device to have internet access, and connects it to the manufacturer\u0026rsquo;s cloud. The device manufacturer typically markets this as a feature, advertising the capability to control your device from anywhere. While this can be a benefit, it also comes with some major drawbacks, including the privacy concerns noted above, as well as giving full control of your smart devices to that company, something that can go very badly.\nFor these reasons, I opt to use home assistant to control my smart home locally, thereby leaving me in charge of my devices. When I added my IoT/Smart Home devices to home assistant, however, migrating from controlling them via their manufacturers\u0026rsquo; apps to using only home assistant was a gradual process. This wasn\u0026rsquo;t a quick switch for me, mostly because at the time I started out with home assistant I was having some hardware issues with my server. These issues resulted in a fair amount of downtime, which made it even harder to fully commit to using just home assistant. I\u0026rsquo;ve since resolved my server\u0026rsquo;s hardware issues, and now that I can reliably have home assistant running, it\u0026rsquo;s finally time to secure my IoT devices by limiting their network access and ditching their manufacturer-proprietary apps.\nNetworking Configuration # Starting on this endeavor I had 3 IoT devices still on my main LAN and using cloud-connected apps:\nA Bond Home fan/light fixture A Midea Window Air Conditioner A Hubspace fan/light fixture In order to limit the access of these devices, as well as future IoT devices, I\u0026rsquo;m going to be using two different VLANs to isolate them from my trusted subnets as well as the internet.\nVLAN 30 will be a new VLAN I create for IoT devices only. This VLAN will be on subnet 192.168.30.0/24, and my router will have firewall rules denying all traffic from this subnet to any destination, meaning these devices cannot send packets to any other subnets, including the internet. This is where I will put any ethernet connected IoT devices, such as cameras and hubs.\nI already use VLAN 10 as my home\u0026rsquo;s guest network, with it being the only other SSID advertised by my UniFi access points. IoT devices that connect via Wi-Fi will be put on this network, with firewall rules set to deny these specific IoT devices access to the internet (the Guest network already denies all devices on it access to my other local subnets). While having my IoT devices on the same subnet as guest users could normally be a security concern, my APs have \u0026ldquo;Client Device Isolation\u0026rdquo; enabled on the Guest SSID in the my UniFi controller, meaning the APs don\u0026rsquo;t allow communication between wireless clients on this SSID, despite them being on the same subnet. One thing that is an issue with this setup, though, is that rogue IoT devices could manually set their IP to a different IP on this subnet that isn\u0026rsquo;t blocked, making these per-host internet-blocking firewall rules an imperfect solution. This scenario is pretty unlikely, though, and this setup still offers better security than having these devices on my main LAN. Since I don\u0026rsquo;t want another SSID adding additional wireless interference this will have to do for my few IoT devices that need a Wi-Fi connection.\nThere are a couple more things to note about both of these VLANs. In order to allow home assistant and MQTT to communicate directly with these IoT devices, my linux host which is running them in docker will have an IP on both of these new subnets. I\u0026rsquo;ll be relying on docker\u0026rsquo;s default behavior of binding bridge network port mappings to all host interfaces here. Additionally, the IoT devices on each VLAN will be set with static DHCP mappings, so they\u0026rsquo;ll recieve their IP via DHCP, but will get the same IP leased to them every time based on their MAC address, allowing me to configure integrations for them in home assistant via their IP without having to worry about it changing. Once I set all this networking up, it was time to move on to configuring each device and connecting them to home assistant.\nConfiguring and Connecting the Devices # Bond Fan # First up was my Bond Home fan, which connects to the network via Wi-Fi. I first removed it from the bond app so it wouldn\u0026rsquo;t be searching for an unreachable device after I blocked internet access. Next, I accessed the fan\u0026rsquo;s web interface at its current IP on my main LAN. Here I was able to change its network configuration, moving it over to the guest network where it could obtain an IP via DHCP. Once it got its IP, I set the lease to a static mapping, ensuring it would get the same IP every time it connected to the network, then set firewall rules to deny any packets from the fan, restricting it to only the subnet it\u0026rsquo;s on. After this I just had to change the configuration for the fan to its new IP in home assistant. From here I could control the fan via home assistant, but it was otherwise completely isolated, unable to access the internet or other subnets.\nUnfortunately, the fan really did not like being denied internet access. On my firewall I was constantly seeing blocked dns requests from it every second, with it trying both google and cloudflare\u0026rsquo;s DNS at 8.8.8.8 and 1.1.1.1 respectively. Additionally, the fan was running its configuration AP constantly, advertising its SSID for anybody within range to take control of it, as well as restarting frequently.\nI was able to find a couple forum posts from people with similar issues with their bond devices running on a network with no internet connection. Per the conversations there as well as the Bond Local API Docs, I was able to find that disabling MQTT (which is how the fan communicates with the cloud) on the fan via the local API would stop it from running its AP and restarting. Technically I could also connect the fan to my local MQTT instance instead of disabling MQTT, but controlling the fan via the homeassistant plugin (which uses calls to this local API) is working fine. Here are the commands I ran from linux to disable MQTT on the fan:\ncurl -iH \u0026#34;BOND-Token: \u0026lt;token\u0026gt;\u0026#34; -H \u0026#34;Content-Type: application/json\u0026#34; -X PATCH -d \u0026#39;{ \u0026#34;enabled\u0026#34;: false }\u0026#39; http://\u0026lt;ip\u0026gt;/v2/api/mqtt curl -iX PUT -d \u0026#39;{\u0026#34;_token\u0026#34;: \u0026#34;\u0026lt;token\u0026gt;\u0026#34;}\u0026#39; http://\u0026lt;ip\u0026gt;/v2/sys/reboot While this did stop the AP running and the fan rebooting, it was still making tons of DNS requests. In the forum posts I linked above there was talk by the API devs of implementing a local only mode which would stop the fan rebooting/running the AP as well as stopping these needless DNS requests, but it appears the commands I just ran were as far as they got with that plan. After scouring the docs I found one more thing to try, disabling the \u0026ldquo;Bond Cloud watchdog\u0026rdquo;:\ncurl -iH \u0026#34;BOND-Token: \u0026lt;token\u0026gt;\u0026#34; -H \u0026#34;Content-Type: application/json\u0026#34; -X PATCH -d \u0026#39;{ \u0026#34;rwdg_disable\u0026#34;: true }\u0026#39; http://\u0026lt;ip\u0026gt;/v2/sys/wifi/watchdog curl -iX PUT -d \u0026#39;{\u0026#34;_token\u0026#34;: \u0026#34;\u0026lt;token\u0026gt;\u0026#34;}\u0026#39; http://\u0026lt;ip\u0026gt;/v2/sys/reboot Unfortunately this did not help, and per the docs it only disables the rebooting behavior, which I already stopped by disabling MQTT.\nI also tried manually configuring the fan\u0026rsquo;s network settings, setting its DNS server to 127.0.0.1 (loopback address) in hopes of stopping the DNS requests from leaving the device. I do think this decreased the amount of DNS messages being sent to my router, but it didn\u0026rsquo;t outright stop them. At the end of the day a few DNS packets getting chucked at my firewall isn\u0026rsquo;t that big a deal, so without much of a solution in sight I decided to accept it and move on.\nMidea Air Conditioner # Home assistant doesn\u0026rsquo;t have a direct integration for Midea products, so I had to turn to community-created plugins. While searching I found four github repos for Midea integrations:\nNeoAcheron/midea-ac-py andersonshatch/midea-ac-lib mac-zhou/midea-msmart georgezhao2010/midea_ac_lan These options all seemed to be building on code from each previous repo in the list. The last two seemed to be the only ones still active, with both claiming to allow control via the local network rather than hooking into the existing cloud integration, which is good since I am hoping to remove the cloud integration altogether. I started out by trying the last integration on my list since it was updated most recently and had the most downloads, installing version 0.3.16 via the Home Assistant Community Store (HACS).\nOnce I installed the integration I added the Air Conditioner through the integration, first clicking \u0026ldquo;Add Integration\u0026rdquo;, then finding \u0026ldquo;Midea AC LAN\u0026rdquo;. The first step the configuration has is how it should find your device, with four options: \u0026ldquo;Auto\u0026rdquo;, \u0026ldquo;By IP\u0026rdquo;, \u0026ldquo;Manual\u0026rdquo;, and \u0026ldquo;Just list appliances\u0026rdquo;. I don\u0026rsquo;t know the point of the last option, but both it and \u0026ldquo;Auto\u0026rdquo; gave an error. This probably isn\u0026rsquo;t the integration\u0026rsquo;s fault as Home Assistant auto discovery doesn\u0026rsquo;t work when running it in docker using bridge network mode as I am.\nSelecting \u0026ldquo;Manual\u0026rdquo; asked for a ton of information I didn\u0026rsquo;t know, and I couldn\u0026rsquo;t find all of it in the Midea App. Luckily, once I chose \u0026ldquo;By IP\u0026rdquo; and entered the A/C\u0026rsquo;s IP, it autopopulated almost all of these fields, including \u0026ldquo;Token\u0026rdquo; and \u0026ldquo;Key\u0026rdquo;, though I still had to select the device\u0026rsquo;s type of \u0026ldquo;Air Conditioner\u0026rdquo;. From here I was able to control the device via home assistant.\nI find it a bit concerning that this integration could find all of the details it needed to control the device just by having network access to it, but it isn\u0026rsquo;t too surprising considering the general lack of security with most IoT devices. Since I couldn\u0026rsquo;t find any documentation suggesting the A/C was intended to be controllable within your home network, I\u0026rsquo;m assuming this home assistant addon is just pretending to be the midea cloud when it\u0026rsquo;s sending commands to the air conditioner.\nSince the device was now working in home assistant I set up my firewall rules to block its access to the internet, which fortunately didn\u0026rsquo;t impact home assistant\u0026rsquo;s ability to control it. After rebooting the device it started spamming DNS requests every second, just like my fan. At this point I guess this is just the cost of using Wi-Fi IoT devices without allowing them to phone home.\nHubspace Fan # Like the air conditioner, home assistant has no native integration for Hubspace. Turning to the community again I found this thread, where somebody has created an integration to control Hubspace products through home assistant by connecting to the Hubspace cloud. When starting this process I wanted to avoid having devices connect to the internet if possible, but it seems this is the only integration that home assistant users have created so far. The only other option I could come up with to control the fan locally would be using RF, mimicing the controls the fan\u0026rsquo;s remote uses and controlling this via home assistant. Since I not in the mood to go out and buy hardware to do this, I\u0026rsquo;m going to be using the aforementioned integration, though I may change this in the future, and will leave an update here if I do.\nI added the integration to home assistant, using HACS to add it as a custom repository per the instructions on the integration\u0026rsquo;s github page. After downloading v1.93 and adding the required config to my configuration.yaml the entities for the light and fan were available in home assistant.\nThough this integration works for controlling the fan \u0026amp; lights, sending commands is painfully slow. Despite the fan being right next to the phone using home assistant, packets have a long path to take in order to reach the fan due to the cloud-only control. Sometimes a command takes up to 10 seconds to register a change on the fan, significantly slower than the \u0026lt;1 second response time of most locally controlled IoT devices. While I\u0026rsquo;m glad somebody made an integration enabling control of the fan through home assistant, I will likely look to a different solution in the future.\nConclusion # While I have been able to at least limit the access of my IoT devices, I am still disappointed with how difficult, and sometimes even impossible, it is to control many IoT devices locally. Of the devices I worked on today, I have to give kudos to Bond for having a locally accessable REST API, though I would still like a local-only mode where it would stop spamming DNS requests.\nFrom now on I plan to be much more selective with the IoT devices I buy for my home, ensuring future devices are locally controllable \u0026amp; do not require internet. In my future plans is a zigbee network, on which none of the devices will have/need internet access - an aspect of the zigbee protocol that is a major draw for me.\n","date":"July 14, 2023","permalink":"/posts/iot-security-privacy/","section":"Posts","summary":"Intro # In an ideal scenario all IoT/Smart Home devices should be blocked from accessing the internet.","title":"Isolated IoT Devices with Local-Only Control"},{"content":"","date":"July 4, 2023","permalink":"/posts/","section":"Posts","summary":"","title":"Posts"},{"content":"","date":"July 2, 2023","permalink":"/","section":"","summary":"","title":""},{"content":" This project has an associated GitHub repository. Intro # The best part about building your own keyboard is being able to customize anything and everything to your liking. On keyboards that support QMK firmware that customization doesn\u0026rsquo;t stop at just the physical keyboard.\nWith QMK you can write your own custom firmware using C code, tweaking keyboard functions and adding features like custom keymaps, RGB lighting, macros, and more. Since this is firmware-level customization it requires no software on your computer, and stays with your keyboard wherever you take it.\nIn this post I\u0026rsquo;ll be using QMK to make my own firmware for my GMMK Pro and flashing it onto my keyboard.\nSetting up a QMK environment # Since I\u0026rsquo;m using Windows, I first installed QMK MSYS. If you\u0026rsquo;re using a different OS, the QMK docs have instructions for setting up a build environment on MacOS, Linux, and FreeBSD.\nNext I forked the qmk_firmware github repository and cloned my new repository with git clone --recurse-submodules. After that it just took a few basic commands to get QMK ready:\nqmk setup qmk list-keyboards | grep gmmk qmk config user.keyboard=gmmk/pro/rev1/ansi qmk config user.keymap=coreybraun The first command performs initial setup of QMK, finding or creating your qmk home folder with the QMK file structure. By default QMK will search for/create the qmk folder at qmk_firmware/ within your user\u0026rsquo;s home, but if you want to use a different location one can be specified with -H \u0026lt;path\u0026gt;. The second command lists all QMK keyboards, which I pipe to grep to find the name of my keyboard, \u0026ldquo;gmmk/pro/rev1/ansi\u0026rdquo;, which is also the path to its keymaps in qmk_firmware/keyboards/. The last two commands set some defaults that will save me having to specify a keyboard and keymap name with -kb \u0026lt;keyboard\u0026gt; and -km \u0026lt;name\u0026gt; arguments every time when I create, compile, and flash my firmware.\nCreating my keymap # To start off I can create my own keymap using qmk new-keymap, which creates my new keymap at qmk_firmware/keyboards/\u0026lt;my keyboard\u0026gt;/keymaps/coreybraun, copying the default keymap for my keyboard (.../keymaps/default) to use as a base.\nWithin this folder there should be three files:\nkeymap.c, which contains the main C code for your keyboard config.h, a C header file which can be used to set keyboard variables rules.mk, a makefile used to define other variables, most notably enabling (or disabling) QMK features at the cost of increased firmware size If either of the latter two files are missing you can create them; Since they are optional configuration files they may not be included in the default keymap. In my case I had to create my own config.h, as well as include it with a line near the top of my keymap.c:\n#include \u0026#34;config.h\u0026#34; By editing these three files you can customize your keyboard to your liking. The files are pretty empty to start, relying on QMK\u0026rsquo;s defaults for most settings and functions, as well as default files for your specific keyboard model. Thanks to QMK\u0026rsquo;s focus on customization it is usually pretty easy to add the features you want by using existing functions or changing configuration variables within your personal keymap.\nChanging the Keyboard Layout # Near the top of keymap.c is a definition of one or more (up to 16) matrices which set the mapping of keys on each layer of the keyboard, typically with accompanying comment lines to give an idea of what the default keymap translates to on the keyboard. The keycodes within these matrices can be changed to any of the valid QMK Keycodes, but it is important to have QK_BOOT accessible somewhere, as without it you cannot put the keyboard in bootloader mode to flash it again (unless the keyboard has a hardware bootloader button). Here is the final keymap I ended up deciding on:\n[0] = LAYOUT( KC_ESC, KC_F1, KC_F2, KC_F3, KC_F4, KC_F5, KC_F6, KC_F7, KC_F8, KC_F9, KC_F10, KC_F11, KC_F12, KC_DEL, KC_MPLY, KC_GRV, KC_1, KC_2, KC_3, KC_4, KC_5, KC_6, KC_7, KC_8, KC_9, KC_0, KC_MINS, KC_EQL, KC_BSPC, KC_HOME, KC_TAB, KC_Q, KC_W, KC_E, KC_R, KC_T, KC_Y, KC_U, KC_I, KC_O, KC_P, KC_LBRC, KC_RBRC, KC_BSLS, KC_PGUP, KC_CAPS, KC_A, KC_S, KC_D, KC_F, KC_G, KC_H, KC_J, KC_K, KC_L, KC_SCLN, KC_QUOT, KC_ENT, KC_PGDN, KC_LSFT, KC_Z, KC_X, KC_C, KC_V, KC_B, KC_N, KC_M, KC_COMM, KC_DOT, KC_SLSH, KC_RSFT, KC_UP, KC_END, KC_LCTL, KC_LGUI, KC_LALT, KC_SPC, KC_RALT, MO(1), KC_RCTL, KC_LEFT, KC_DOWN, KC_RGHT ), [1] = LAYOUT( _______, UNSAFE, _______, _______, SPAM_M1, _______, _______, RGB_MOD, RGB_VAD, KC_VOLD, KC_VOLU, KC_MPRV, KC_MNXT, KC_PSCR, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, KC_INS, _______, _______, _______, _______, RGB_TOG, _______, _______, _______, _______, _______, _______, _______, _______, QK_BOOT, _______, QK_LOCK, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, NK_TOGG, _______, _______, _______, _______, TG(2), KC_PGUP, _______, _______, _______, _______, _______, _______, _______, _______, KC_HOME, KC_PGDN, KC_END ), [2] = LAYOUT( _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, KC_WH_U, QK_LOCK, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, KC_WH_D, _______, _______, _______, _______, _______, _______, _______, _______, KC_BTN1, KC_BTN2, KC_BTN3, TG(2), KC_MS_U, KC_BTN2, _______, _______, _______, _______, _______, _______, KC_BTN1, KC_MS_L, KC_MS_D, KC_MS_R ), For the most part I put normal keycodes on layer 0, then my lesser-used keys like insert and printscreen on layer 1 along with some special QMK keycodes, custom macros, and controls for RGB and media. Layer 2, which can be toggled on/off via right shift on layer 1 or 2, is primarily used for mouse controls.\nOther Keyboard Functions # Key Lock # Key Lock is a niche but useful feature in QMK, setting the next keycode you input after key lock to stay in a down state until pressed again, essentially holding down a key for you. To use key lock you first have to enable it by adding a line with KEY_LOCK_ENABLE = yes in rules.mk, then you can set a key in your keymap to QK_LOCK, the key lock keycode. In my case I set the caps lock key to QK_LOCK on layer 1 and 2.\nMouse Keys # Mouse Keys allow you to control your cursor as well as send mouse button presses from your keyboard. After adding MOUSEKEY_ENABLE = yes to rules.mk, you can use mouse control keycodes in your keymap. For my keymap I use layer 2 for mouse keys, with arrow keys mapped to moving the cursor, page up and page down for scroll wheel, and mouse button presses mapped in two locations for the option of controlling the mouse one-handed or two-handed. I also set the layer to be toggled on and off so I don\u0026rsquo;t have to hold a button to keep the layer active while I\u0026rsquo;m controlling the mouse via my keyboard.\nThere are also several configuration values you can define in to override the defaults. These are mostly down to personal preference, but these are the lines I ended up adding to my config.h:\n#define MOUSEKEY_INTERVAL 7 #define MOUSEKEY_DELAY 0 #define MOUSEKEY_TIME_TO_MAX 60 #define MOUSEKEY_MAX_SPEED 8 #define MOUSEKEY_MOVE_DELTA 1 #define MOUSEKEY_WHEEL_DELAY 0 #define MOUSEKEY_WHEEL_INTERVAL 76 RGB Indicators # One of the most important goals I had was setting certain RGB LEDs in my keyboard to a different colors to indicate caps lock status or which layer is currently active. Fortunately QMK defines functions to use for setting your own RGB indicators, with these functions running after other RGB lighting effects to ensure your indicators take precedence. Within these functions you can set LEDs\u0026rsquo; colors with RGB_MATRIX_INDICATOR_SET_COLOR(index, red, green, blue), where the first value is the index number of the LED and the latter 3 are 8-bit values for the brightness of each color.\nI started out pretty simple, using the indicator function in my keymap.c to set one LED to red when caps lock was on, green while layer 1 was active, and blue while layer 2 was active. The variables I use for the RGB colors here are defined in a QMK C header file.\nbool rgb_matrix_indicators_advanced_user(uint8_t led_min, uint8_t led_max) { if (host_keyboard_led_state().caps_lock) RGB_MATRIX_INDICATOR_SET_COLOR(3, RGB_RED); switch(get_highest_layer(layer_state|default_layer_state)) { case 1: RGB_MATRIX_INDICATOR_SET_COLOR(3, RGB_GREEN); break; case 2: RGB_MATRIX_INDICATOR_SET_COLOR(3, RGB_BLUE); break; } return false; } After this first basic iteration worked I wanted to change the lighting to be on the left and right lightbars as well as the top row of the keyboard. Since I wanted to set the color of about 30 LEDs to indicate caps lock or different layers I created a function to set the color of all these LEDs at once by iterating over an array of the LEDs\u0026rsquo; index numbers:\nuint8_t outer_led_array[] = {0, 6, 12, 18, 23, 28, 34, 39, 44, 50, 56, 61, 66, 69, 67, 68, 70, 71, 73, 74, 76, 77, 80, 81, 83, 84, 87, 88, 91, 92}; void set_outer_leds(uint8_t redValue, uint8_t greenValue, uint8_t blueValue) { for (uint8_t i=0; i \u0026lt; (sizeof(outer_led_array) / sizeof(outer_led_array[0])); i++) { RGB_MATRIX_INDICATOR_SET_COLOR((outer_led_array[i]), redValue, greenValue, blueValue); } }; After that I just had to change the caps lock and layer conditionals to call this function instead, passing it the desired color values.\nMacros # There are a couple macros I want on my keyboard. The first should just type \u0026ldquo;thisisunsafe\u0026rdquo; when pressed. Typing this string into chromium browsers bypasses the warning for an invalid SSL cert, a frequently-seen warning when using HTTPS to access web interfaces for internal services that use a self-signed cert. For the second macro I want pressing it to toggle spam pressing mouse1. I also want to be able to easily disable spamming mouse1 at any time by pressing escape or pressing the key again.\nI started by defining keycodes for these two macros, as well as a boolean value for whether mouse1 should be getting spam pressed, with a default of false. These definitions are in my keymap.c, located above my keyboard layout since they need to be defined before referencing them in the keymap arrays.\nenum custom_keycodes { UNSAFE = SAFE_RANGE, SPAM_M1, }; bool spamming_m1 = false; Assigning the first keycode to SAFE_RANGE ensures the keycodes will get a unique ID without requiring me to manually specify one.\nNext I defined what should happen on key presses:\nbool process_record_user(uint16_t keycode, keyrecord_t *record) { switch (keycode) { case UNSAFE: if (record-\u0026gt;event.pressed) SEND_STRING(\u0026#34;thisisunsafe\u0026#34;); break; case SPAM_M1: if (record-\u0026gt;event.pressed) spamming_m1 = !spamming_m1; break; case KC_ESC: if (record-\u0026gt;event.pressed) if (spamming_m1) spamming_m1 = false; break; } return true; }; process_record_user is a QMK function called any time a key is pressed or released, by using it here we can modify key behaviors. Using if (record-\u0026gt;event.pressed) causes these actions to happen on the key press and not release. For the first macro we just send a string, and for the second we toggle the state of the boolean value we created earlier. We also want escape to set the boolean to false if it\u0026rsquo;s true, so we can do that here as well. Since this returns true it doesn\u0026rsquo;t interupt the key\u0026rsquo;s normal function.\nFinally I needed to make the boolean for spamming mouse1 actually do something:\nvoid matrix_scan_user(void) { if (spamming_m1) { tap_code(KC_BTN1); } }; matrix_scan_user is a function that gets run every matrix scan, so by setting it to tap (send down then up) mouse1 every cycle when the boolean is true it achieves my goal of spam pressing mouse1. The QMK docs say the function will get run as often as your keyboard\u0026rsquo;s processor can handle, and with my keyboard I was getting around 90 clicks per second.\nCompiling and Flashing firmware # A keymap can be compiled by running:\nqmk compile Assuming the firmware compiles without errors you can put your keyboard into bootloader mode and flash your firmware onto it.\nThe method to enter bootloader mode varies, but for keyboards already running QMK you need to input the key mapped to keycode QK_BOOT (default Fn+\\), or, if enabled, hold the bootmagic lite key (default escape) while plugging in the keyboard. Using the stock Glorious firmware on my keyboard I had to plug in the keyboard while holding space and B to enter bootloader mode.\nOnce in bootloader mode your keyboard will no longer work until it is either flashed and reboots or is rebooted by unplugging then plugging it back in. It should go without saying, but don\u0026rsquo;t unplug the keyboard while firmware is being written to it.\nWhile in bootloader mode, you can flash firmware onto your keyboard by running:\nqmk flash Alteratively, you can run the command and then enter bootloader mode as it may be difficult to type the command without a usable keyboard.\n","date":"June 8, 2023","permalink":"/posts/qmk-keyboard-firmware/","section":"Posts","summary":"This project has an associated GitHub repository.","title":"Writing Custom Keyboard Firmware with QMK"},{"content":"","date":"January 1, 0001","permalink":"/authors/","section":"Authors","summary":"","title":"Authors"},{"content":"","date":"January 1, 0001","permalink":"/categories/","section":"Categories","summary":"","title":"Categories"},{"content":"","date":"January 1, 0001","permalink":"/series/","section":"Series","summary":"","title":"Series"}]