Skip to content

Commit

Permalink
revise blog post
Browse files Browse the repository at this point in the history
  • Loading branch information
AndrewDTR committed Oct 13, 2024
1 parent 1924bfe commit a93e314
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 23 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,7 @@ pnpm-debug.log*
.idea/

# obsidian
.obsidian/
.obsidian/

# until i finish
ics-schedule-generator.md
57 changes: 35 additions & 22 deletions src/pages/blog/upl-people-counter.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
---
layout: "../../layouts/Blog.astro"
title: "Revamping the UPL's people counter"
date: "2024-09-28"
date: "2024-10-14"
subtitle: "who knew figuring out whether the lab's open would be so much work...?"
tags: ["python", "opencv", "zigbee", "upl"]
tags: ["projects", "python", "opencv", "zigbee", "upl"]
author: "Andrew"
---

Expand Down Expand Up @@ -36,43 +36,47 @@ Well, myself, in collaboration with other UPL members, decided to fix this issue

## People counting

<!-- TODO -->

TODO

## Switching to door sensing

This worked perfectly for a while -- people would check the Discord channel name and see an estimated count of the number of people in the room. If it said "zero people", they could infer that the UPL wasn't open.
This worked perfectly for a while -- people would check the Discord channel name and see the estimated count of the number of people in the room. If it said "zero people", they could infer that the UPL wasn't open.

However, this solution started presenting issues. For one, having people in the room didn't necessarily indicate that the UPL was open -- there could be a meeting, or a separate gathering where the doors were closed and people weren't allowed inside. This was confusing to people who might have seen "8 people inside the UPL", only to arrive at the building to see coords having a meeting.
However, this solution started presenting issues. For one, having people in the room didn't necessarily indicate that the UPL was open. There could be a meeting, or a separate gathering where the doors were closed and people weren't allowed inside. This was confusing to people who might have seen "8 people inside the UPL", only to arrive at the building to see coords having a meeting.

There was also the issue of the model sometimes interpreting the chair in the corner as a person:
There was also the issue of the model sometimes interpreting the chair in the corner as a person[^1]:

<div style="display: flex; justify-content: center; gap: 10px; max-width: 100%; flex-wrap: wrap; margin-bottom">
<img src="/images/upl-pc/upl_empty.png" alt="An image of the empty lab. An annotation on a brown armchair reads 'Person 0', despite no human on the chair." style="max-height: 250px; width: auto; max-width: 100%;" />
</div>

<i style="display: flex; justify-content: center; margin-top: 10px; font-size: 0.95em;">Either the model is too sensitive or the UPL has a friendly ghost</i>

It was around this time that I stumbled upon the homepage of [MITERS](https://miters.mit.edu/), a makerspace at MIT. On their website, they broadcast whether the door to the space is open using a reed switch attached to a Raspberry Pi. Reed switches are small components that are able to detect a magnetic field. I was able to find [a writeup](https://andrewbirkel.com/projects/MITERS_Door.html) by a former member of the space on their implementation, but I can't guarantee that it's accurate to how it's set up there currently.
It was around this time that I stumbled upon the homepage of [MITERS](https://miters.mit.edu/), a makerspace at MIT. On their website, they broadcast whether the door to the space is open using a reed switch attached to a Raspberry Pi. Reed switches are small, physical components that are able to detect a magnetic field. If you put one on a doorframe, and then attach a tiny magnet to the door itself, you have an effective way of detecting whether a door is open or closed! I was able to find [a writeup](https://andrewbirkel.com/projects/MITERS_Door.html) by a former member of the space on their implementation, but I can't guarantee that it's accurate to how it's set up there currently.

I considered using similar components for a door status checker for the UPL -- it wouldn't have been too much effort to buy WiFi enabled ESP32 modules and off-the-shelf door-mountable reed switches. Then, I would have the chips simply send a POST request with their status every time the door was opened or closed.

I decided against this approach for a few reasons:

- The UPL doesn't really have the equipment to maintain such a system. I don't know how to solder, and mounting a breadboard to the walls doesn't seem like the most future-proof or aesthetically pleasing solution.
- The UPL doesn't really have the equipment to maintain such a system. I don't know how to solder, and mounting breadboards to the walls doesn't seem like the most future-proof or aesthetically pleasing solution.

- If the system were to spontaneously break after I left, it would be difficult to find somebody to fix it. The UPL is mainly a software oriented lab!

- The WiFi ran by the university (UWNet) requires you to log in with a captive portal to register your device to connect to the network. Without intervention, it will occasionally require you to sign back in to renew your ability to connect. While there _are_ some ways to forge the specific requests a typical browser uses to authenticate with your NetID, it would be a ton of recurring effort (and the login would have to be changed as people graduated)!
- The WiFi ran by the university (UWNet) requires you to log in with a [captive portal](https://en.wikipedia.org/wiki/Captive_portal) to register your device to connect to the network. Without intervention, it will occasionally require you to sign back in to renew your ability to connect[^2]. While there _are_ some ways to emulate the specific requests a typical browser would use to authenticate with your NetID, it would be a ton of recurring effort (and the login it used would have to be changed as people graduated)!

So, I decided that the sensors themselves would have to act autonomously and simply relay their state to a device elsewhere in the room. Luckily, the Raspberry Pi that ran the code for the people counter was easily repurposed. I changed its operating system to [Home Assistant](https://www.home-assistant.io/), an open source platform for interfacing with various network connected devices.
So, I decided that the sensors themselves would have to act autonomously and simply relay their state to a device elsewhere in the room. Luckily, the Raspberry Pi that ran the code for the people counter was easily repurposed. I installed [Home Assistant](https://www.home-assistant.io/), an open source platform for interfacing with various network connected devices.

There are plenty of devices that track the status of doors, made by companies like Ring and ADT for home security. However, they usually require proprietary hubs to check the status, and don't offer easily usable APIs to interface with the device. Luckily, there was a better solution!
There are plenty of devices that track the status of doors, made by companies like Ring and ADT for home security. However, they usually require proprietary hubs to check their status, and don't offer easily accessible APIs to interface with the device. Luckily, there was a better solution!

## Zigbee!

Enter Zigbee. It's a low-rate mesh wireless protocol that allows for smart devices to communicate over a personal area network it creates. A benefit of this is that you're able to use one hub to communicate with a variety of devices, even those made by different manufacturers. Instead of searching for a particular brand for the door contact sensors, I would just have to find ones that support the Zigbee protocol. Then I would be able to see their status through Home Assistant's dashboard.
Enter Zigbee. It's a low-rate mesh wireless protocol that allows for smart devices to communicate over a personal area network. A benefit of this is that you're able to use one hub to communicate with a variety of devices, even those made by different manufacturers. Instead of searching for a particular brand for the door contact sensors, I would just have to find ones that support the Zigbee protocol. Then I would be able to view their status through Home Assistant's dashboard.

It's important to note that Zigbee radios are separate from WiFi or Bluetooth antennas. If you want to interface with them, you'll have to pick up a special receiver that can support the protocol. For this project, I picked up [this one](https://www.amazon.com/SONOFF-Universal-Assistant-Zigbee2MQTT-Wireless/dp/B0B6P22YJC/) made by SONOFF. Home Assistant's Zigbee integration is called [Zigbee Home Automation](https://www.home-assistant.io/integrations/zha/), and it supports a variety of Zigbee coordinators (the USB dongles that allow for connections). When you use this integration, Home Assistant automatically creates a Zigbee network that the devices can join.
It's important to note that Zigbee radios operate independently from WiFi or Bluetooth antennas. If you want to interface with Zigbee devices, you'll have to pick up a special receiver that can support the protocol. For this project, I picked up [this one](https://www.amazon.com/SONOFF-Universal-Assistant-Zigbee2MQTT-Wireless/dp/B0B6P22YJC/) made by SONOFF. Home Assistant's Zigbee integration is called [Zigbee Home Automation](https://www.home-assistant.io/integrations/zha/), and it supports a variety of Zigbee coordinators (the USB dongles that allow for connections). When you use this integration, Home Assistant automatically creates a Zigbee network that the devices can join.

I decided to use these [Aqara door and window sensors](https://www.amazon.com/Aqara-MCCGQ11LM-Window-Sensor-White/dp/B07D37VDM3/) for this project. They had the best reviews out of all of the Zigbee door sensors I looked at, and have a battery life of two years (with an easily replaceable CR1632 cell).
I decided to use [these Aqara door and window sensors](https://www.amazon.com/Aqara-MCCGQ11LM-Window-Sensor-White/dp/B07D37VDM3/) for this project. They had the best reviews out of all of the Zigbee door sensors I looked at, and have a battery life of two years (with an easily replaceable CR1632 cell).

Once the coordinator and sensors arrived, I created a Home Assistant login and installed the ZHA integration. Pairing simply required holding the "reset" button on the sensors until Home Assistant recognized them and added the corresponding entities in the dashboard.

Expand All @@ -82,7 +86,7 @@ Once the coordinator and sensors arrived, I created a Home Assistant login and i

Once this was all configured, I had the live statuses of the doors through the Home Assistant dashboard! I'm not going to lie, it was really fun opening and closing the doors repeatedly and seeing the dashboard change in real-time (even if the rest of the UPL members probably thought I was crazy).

An important thing to note here is that UWNet provides total access point isolation. None of the devices on the network can see any of the others (for good reason, as it would be a huge security vulnerability for any devices with open ports).
An important thing to note here is that UWNet provides total access point isolation. None of the devices on the network can see any of the others (for good reason, as it would be a huge security vulnerability for any devices with open ports). If this wasn't a limitation, I would just have the website directly query the rpi.

My first intuition was to use Home Assistant's [RESTful Command](https://www.home-assistant.io/integrations/rest_command/) integration to send a POST request to my webserver whenever the status of the doors changed. These require you to setup each command ahead of time, in HA's `configuration.yml`:

Expand Down Expand Up @@ -121,17 +125,15 @@ rest_command:
content_type: "application/json; charset=utf-8"
```
...but I very quickly realized that this solution wasn't the best. For one, when I published [the source code](https://github.com/UW-UPL/people-counter-v2/blob/main/home-assistant/configuration.yaml) onto GitHub, some very funny students decided that they would manually simulate the POST requests and change the status of the doors to be inaccurate. That's what I get for leaving the endpoint unsecured!
And, before you try, these endpoints aren't in use anymore. :P
...but I very quickly realized that this solution wasn't the best. For one, when I published [the source code](https://github.com/UW-UPL/people-counter-v2/blob/main/home-assistant/configuration.yaml) onto GitHub, some very funny students decided that they would manually simulate the POST requests and change the status of the doors to be inaccurate. That's what I get for leaving the endpoint unsecured![^3]
I eventually learned that Home Assistant provides a [RESTful API](https://developers.home-assistant.io/docs/api/rest/) directly alongside the web dashboard. All it took was appending an `/api/` route to the HA URL. I could just use that!

The API has all of its routes authenticated with a bearer token (to most likely mirror the permissions of the frontend, which requires a user login before seeing any information). I didn't want to ship the bearer token with the website, as any crafty user could take it and access any other route on Home Assistant. Given the level of information and control available on HA instances, this could be disastrous.
The API has all of its routes authenticated with a bearer token (to most likely mirror the permissions of the frontend, which requires a user login before showing any data). Given that I wanted to display the door status on the UPL's page, I realized the potential danger in shipping the bearer token with the site. Any crafty user could take it and access any other route on Home Assistant's API. Given the level of information and control available on HA instances, this could be disastrous.

I made a quick webserver using Express that proxies the request with bearer token and only returns the relevant door information. Because it serves this separately, the user has no way of seeing or manipulating anything beyond this.
I made a quick webserver using Express that proxies the request with the bearer token and only serves the relevant door information. Because it displays this separately, the user has no way of seeing or manipulating anything beyond this.

```js title="server.js" collapse={1-7, 43-51}
```js title="server.js" collapse={1-7, 43-53}
const express = require("express");
const axios = require("axios");
const cors = require("cors");
Expand Down Expand Up @@ -212,7 +214,6 @@ Now, the server will query the Home Assistant's API on your behalf, with the pro

<i style="display: flex; justify-content: center; margin-top: 10px; font-size: 0.95em;">The UPL website uses a header component which fetches the door status every 15 seconds</i>


<div style="display: flex; justify-content: center; gap: 10px; max-width: 100%; flex-wrap: wrap; margin-bottom">
<img src="/images/upl-pc/upl_discord.png" alt="A channel in the UPL Discord reads 'UPL doors open'." style="max-width: 350px; width: auto;" />
</div>
Expand All @@ -221,4 +222,16 @@ Now, the server will query the Home Assistant's API on your behalf, with the pro

## Conclusion

I'm pretty happy with how this project turned out. It's been really fun developing something that I actually use every day, and I find it pretty special that every time I check if the UPL's open or not, I'm doing it via something that I made myself.
I'm pretty happy with how this project turned out. It's been really fun developing something that I actually use every day, and I find it pretty special that every time I check if the UPL's open or not, I'm doing it via something that I made myself.

<br>

---

<br>

[^1]: I'm sure that you could apply various transformations to the image to mask out that area from detection. But people occasionally sit in it!

[^2]: If you've ever lived in the UW dorms, you'll know all too well what I'm talking about. Every device without browser access needs to have its MAC address whitelisted by the network system. This authorization expires in six months, so you'll lose internet access and have to renew.

[^3]: Before you try, these endpoints aren't in use anymore. :P

0 comments on commit a93e314

Please sign in to comment.