Using PyPortal to Display Air Quality Data and Interact with Home Assistant

I got AdaBox 011 in March of 2019 and wrote this about the PyPortal (buy at Adafruit):

I’ll likely turn this into something that interfaces with my Home Assistant server to control different devices around my house.

The PyPortal has been sitting on a shelf ever since. Way back in February, it caught my eye, and I picked it up, not remembering what it’s capabilities were. Then I started upgrading IKEA air quality monitors and even made my own. Since I’m at the desk in my office a large portion of the week I thought I would make that 2019 prediction come true.

I could show a bunch of data on the screen and the PyPortal has a touchscreen, so I could display buttons for triggering things around the house. The device also has connectors for doing GPIO, so I got the idea of adding an LED strip, which I could use for notifications. I even had a meter long strip of Adafruit Mini Skinny NeoPixels I had bought in 2017 and never touched that would be perfect. I needed to buy a 2.0mm JST PH Connector kit in order to make a wire that would connect to the pack of the PyPortal. I ended up using a piece of Cat6 cable, even though I only needed 3 of the 8 wires inside.

I used light strip clips to mount the LEDs to the back of my desk.

I also mounted a power strip under the desk and cleaned up all of the cables.

The code I’m running on the PyPortal was heavily inspired by Adafruit’s PyPortal MQTT Sensor Node/Control Pad for Home Assistant and PyPortal NeoPixel Color Picker. It’s done with CircuitPython and I’ve added my code to pyportal-home-assistant-display on GitHub.

All of this was done back in March. I quickly began having issues with the ethernet cable and the small JST connectors, so I put this post on pause. Figured it was time to finally fix this before the end of the year. While testing, I determined the LED strip got fried up at some point. It was probably some kind of short from the janky wire.

Here’s what my display looks like.

My favorite aspect of the project and code is being able to publish MQTT messages from Home Assistant, which the PyPortal listens for and reacts to. I can send various commands, such as fill:blue, which turns all of the LEDs blue, or whatever color I set. I have commands to chase a color from one side to the other, bounce a color from left to right and back to the left, pulse the entire strip, animate a rainbow, or set the brightness. Since I don’t have another strip of Neopixels, in order to create a demo video, I wired up a 24 LED circle. You’ll have to imagine the effects on the back of my desk, lighting up the wall.

I can manually send these MQTT messages as shown in the demo, but the real power comes from automations. For example, the LEDs automatically pulse blue when the washing machine is done and pink when the dryer is done.

With the different effects and color combinations, the possibilities are endless. What kind of automations would you run?

Update: I got a new LED strip, made a new demo video, and improved a bunch of stuff. See Updates to my PyPortal Home Assistant Display Project.

Building a Shop Air Filter with Box Fans

Over the years, I’ve seen many versions of a shop air filter, made from box fans and 20×20 inch furnace filters. A few years ago I picked up some old box fans on Facebook Marketplace and bought a pack of filters from Sam’s Club. They’ve been stacked in the corner.

It was finally time to build my air filter. I removed the back covers, feet, handles, and knobs from the fans. I got my first look at the switches inside, which are nearly identical.

I’d easily be able to wire the fans together, so I removed the switches and power cords.

I put together a frame from OSB, cut slots to feed the wires through, and screwed the box fans in.

Then I grabbed wood that had been salvaged from a pallet to construct a door.

On the back side, I used glue and brad nails to attach plywood rails. I also made tabs to hold the filters secure.

I attached the door with a couple hinges and made some notched tabs to hold the door shut.

Then it was time to work on the wiring and electronics. I had recently watched a YouTube video showing how to make an old fan smart and his code with ESPHome and Home Assistant gave my a great start. I bought a 4 channel relay board to use with a an ESP8266 development board, a button, and some LEDs. I tore open an old USB power plug and was originally going to tie in the 120 volt line, but decided against it. First, I tested out the circuit and code on a breadboard and then soldering things up more permanently.

A plastic screw container was a good side, so I used hot glue to secure the boards and then wired up all of the fan connections.

I’m not sure if I’ll ever use the button, but it allows me to cycle between the three speeds and turn it off. The three LEDs show which speed is currently running. The only thing I got wrong was reversing the low and high speeds, which was a quick fix in the ESPHome code. Speaking of the code, here’s mine.

esphome:
  name: shop-air-filter
  friendly_name: Shop Air Filter

esp8266:
  board: d1_mini

logger:
  level: WARN

api:
  encryption:
    key: "input_yours"

ota:
  - platform: esphome
    password: "input_yours"

wifi:
  min_auth_mode: WPA2
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  manual_ip:
    static_ip: 192.168.1.2
    gateway: 192.168.1.1
    subnet: 255.255.255.0

time:
  - platform: homeassistant
    id: home_time

binary_sensor:
  - platform: gpio
    pin: {number: D5, mode: INPUT_PULLUP, inverted: true}
    name: "Speed Button"
    on_press:
      then:
        - script.execute: cycle_fan_speed

  - platform: template
    id: active
    lambda: 'return id( fan_speed ).state > 0;'

switch:
  # Relays
  - platform: gpio
    pin: D3
    id: speed_1
    inverted: true
    interlock: &fan_interlock [speed_1, speed_2, speed_3]
    internal: true
  - platform: gpio
    pin: D2
    id: speed_2
    inverted: true
    interlock: *fan_interlock
    internal: true
  - platform: gpio
    pin: D1
    id: speed_3
    inverted: true
    interlock: *fan_interlock
    internal: true

  # LEDs
  - platform: gpio
    pin: D6
    id: led_1
    internal: true
  - platform: gpio
    pin: D7
    id: led_2
    internal: true
  - platform: gpio
    pin: D8
    id: led_3
    internal: true

number:
  - platform: template
    name: "Fan Speed"
    id: fan_speed
    min_value: 0
    max_value: 3
    step: 1
    optimistic: true
    restore_value: true
    on_value:
      then:
        - script.execute: set_shop_filter_speed

text_sensor:
  - platform: template
    name: "Current State"
    id: current_state

sensor:
  - platform: duty_time
    name: "Filter Runtime"
    id: shop_filter_usage
    sensor: active
    restore: true
    unit_of_measurement: h
    accuracy_decimals: 1
    filters:
      - multiply: 0.000277778 # Convert seconds to hours

button:
  - platform: template
    name: "Reset Filter Timer"
    icon: "mdi:timer-off"
    on_press:
      then:
        - sensor.duty_time.reset: shop_filter_usage

script:
  - id: set_shop_filter_speed
    mode: restart
    then:
      - switch.turn_off: speed_1
      - switch.turn_off: speed_2
      - switch.turn_off: speed_3
      - switch.turn_off: led_1 
      - switch.turn_off: led_2
      - switch.turn_off: led_3
      - delay: 300ms 
      - lambda: |-
          if ( id( fan_speed ).state == 0 ) {
            id( current_state ).publish_state( "Off" );
          } else if ( id( fan_speed ).state == 1 ) {
            id( speed_1 ).turn_on();
            id( led_1 ).turn_on();
            id( current_state ).publish_state( "Low" );
          } else if ( id( fan_speed ).state == 2 ) {
            id( speed_2 ).turn_on();
            id( led_1 ).turn_on(); id( led_2 ).turn_on();
            id( current_state ).publish_state( "Medium" );
          } else if ( id( fan_speed ).state == 3 ) {
            id( speed_3 ).turn_on();
            id( led_1 ).turn_on(); id( led_2 ).turn_on(); id( led_3 ).turn_on();
            id( current_state ).publish_state( "High" );
          }
  - id: cycle_fan_speed
    then:
      - lambda: |-
          int next_speed = id( fan_speed ).state + 1;
          if ( next_speed > 3 ) next_speed = 0;
          id( fan_speed ).publish_state( next_speed );

I used Google Gemini to help and it had a great suggestion to track the run time and add a maintenance reminder when it was time to replace the filters.

In Home Assistant I created some automations. My dust collector uses a smart plug, so when it draws electricity, the air filter automatically turns on at high speed. When the dust collector turns off, the air filter continues to run for 15 minutes before turning off. If I had to remember to turn on the air filter all the time, it would rarely happen, so this is amazing.

I’m still on lifting restrictions for several weeks so Brandi helped me install the air filter on the ceiling.

I wish I hadn’t waited so long to build this!

DIY Shop Towel Bar

With my big shop table done and a lot of storage space to fill, I finally started to tackle the boxes and piles of things that have been sitting in the corner for nearly a year and a half. First up was towels, rags, cleaning supplies, and first aid. We had an unused plastic shelving unit, so I put it to use. It went between my Ryobi cordless tool wall and the utility sink.

Finally trying to pick things up!

I’ve been draping a hand towel over the edge of the sink since moving in and now I had a place to mount a towel bar. I quickly cut some 2×4 to size, so it would fit between the edges on the side of the top shelf.

Truss head screws, so they’re less likely to pull through the plastic.

Then I drilled four holes in the edge of that shelf and mounted the piece of wood. I drilled two more holes in a piece of PVC and mounted it to the wood.

Ignore the sand marks.

This was a quick project and it’s extremely useful. I love simple shop builds where functionality is the most important requirement.

Several years ago my aunt made me Lions and Red Wings towels that I use in the shop.

If you’re looking for a much nicer towel bar/rack, check out the rustic ones with hooks I made for Brandi’s old house or the combo of black walnut and railroad ties for our old previous house.

Playing Card Racks

Mom sent this photo and asked me to make a couple racks/holders for playing cards.

Sounded like a fun little project to put my spin on. At first I assumed the slots would need to be angled away from the player. The more I thought about it, the width of the slot should provide enough of a lean. I gave it a test.

I was right, no angle needed. My next question was if you could see the cards in multiple rows, so I cut slots at several distances apart.

Not great. I had a hunch that differing heights would help and this made me confident. So I cut one slot deeper and tried it as the front row.

Yep! I grabbed a couple pieces from my lumber rack and marked slot spacing that could get me two racks, each with three slots.

I laminated a couple of boards to give me enough height for the different slot depths.

Then I squared up the edges, cut to length, and sliced down the middle. The dimensions are 12 x 2-5/8 x 1-1/2 inches.

At my fancy new router station, I chamfered the edges.

Finally, I cut the slots. The deck of cards I have in the shop are a bad example, since the numbers on most of the cards are lower than they should be. The slots are 1/4, 5/8, and 1″ deep, with 5/8″ of space in between.

I sanded with 80, 120, and 180 grits.

Something was bugging me and it hit me when I watched TWW’s video on making Greeting Card Holders. While his build was a bit different, this gave me an idea.

All the chamfers on my ends and the slot depths being visible was a bad look. So I cut out the middle section of each end, made black walnut fillers, glued them in, and trimmed them at a 10° angle to spice up the visual. I also added my NM stamp on the bottoms.

Much better! As a bonus this adds strength and will keep the cards from sliding out if the holder is picked up. I applied two coats of Bumblechutes All Natural Wood Finish and then Bumblechutes Bee’Nooba Wax. To finish them off I added felt pads to the bottom.

They turned out great and Mom is happy!

Outfeed Assembly Table – Part 3

Time to finish this table. While part 1 and part 2 were quite involved, the rest was all about drawers. I needed to make use of the rest of the space inside the table.

First, I got to work on a cabinet next to the router station, on the front of the table. I wanted to use five sets of cheap drawer slides I salvaged from an old dresser. I had enough room for four deeper drawers, using 22″ slides. I took measurements and sketched out a plan. I had to design around the vice, which hung down below the frame of the table.

I cut a full bottom panel and some 2x4s for extra bracing. I wanted to make good use of space and have the most room for drawers on the right side of the table, so the vertical supports were made in the odd L shape again. The one on the left was cut to match up with the one from the router station.

In the middle, I needed something up top to connect to, so I cut a piece of 2×4 and would secure it in place after determining the exact width of the left drawer column. The vertical on the right needed some cut away around the vice.

Then I was able to cut all of the drawer sides, with two different depths for the different types of slides I was using. Or so I thought.

As I was going over some of my notes I realized I didn’t cut the back part of the middle and right verticals tall enough. To fix the mistake, I glued and pocket screwed on some patch pieces. I also had to recut longer sides for two drawers. From there I figured out the width of the left drawers based on the foam router bit storage tray I was using. Then I cut the fronts, backs, and bottoms for the five left drawers.

Assembly was quick with glue and brad nails.

I modified the old drawer slides with an angle grinder to remove tabs that were in the way for my use case.

I mounted the slides to the verticals for both columns of drawers. Then I was able to secure the left and right verticals to the table frame, attach slides to the left drawers, and put them in to guide the placement of the middle vertical. It got screwed in, along with the upper 2×4.

With the left column of drawers in, I figured out how wide the remaining parts needed to be for the right column of drawers and got them done. Then it was time for some drawer fronts and a false front, since the vice prevented a drawer from being installed there. This gave me a place for a recessed power strip with USB. I didn’t have enough matching handles, so I used two different styles I’ve bought at estate sales.

I filled in a few drawers.

I will never combine different drawer slides in a cabinet like this again. It was a major pain in the ass and too much to keep track of with the different mounting methods, widths, depths, and clearances. I had to adjust the placement of the old slides many times and alter some drawers. Wasn’t worth it.

The drawers for the right side of the table was much of the same and went together a lot faster. The table frame is slightly out of square, so the bottom drawer was a touch too tight and the top too loose. I had to route a recess on one and add some spacer material on the other. The handles were cut out of a test piece from the nightstands I made.

I cut a piece of shiplap paneling and closed off the back of the shelf. This leaves a little unused area in the middle back of the table. If I took out the drawers I could hide something back there. Shhh!

The saw outfeed and the huge work area are already amazing to have. This is really going to improve my processes in the shop. Here are some final photos.

Pet Waste Bag Dispenser

We use those small pet waste bags when cleaning out the cat litter and just throw the roll on a cabinet.

I’ve thought about making a holder/dispenser for quite awhile and decided to do it this weekend. I picked a piece of oak from my scrap bin to cut all of the pieces from. It was a quick, crude build. I snipped the ends off a nail for the pin and glued a couple of magnets in the bottom, since the cabinet is metal.

Does the job!

Storage for a Ryobi Framing Nailer and Narrow Crown Stapler

Quick build this weekend to expand my Ryobi tool storage wall (which moved to the new workshop) with spots for the framing nailer I got last year to build the shop wall and a narrow crown stapler I recently bought on sale.

As I’m writing this and seeing the pictures, I butt jointed the back and bottom incorrectly, which is why it’s too tall and not deep enough to match up with the old spots. Oh well!

It’s great to be back in the shop again and getting it more organized.

Mountain Dew: Dark Berry Bash

I’ve never heard of this one and that’s because it’s an Applebee’s franchise exclusive fountain soda. Rightly so, because they shouldn’t be trying to sell this anywhere. Maybe it was a bad mix at this particular restaurant, because the aftertaste really stood out; it was really strong and very artificial flavoring. A search says it’s “blending blue raspberry and blackberry with Mountain Dew’s citrus base, similar to Voltage but with darker berry notes.” Voltage is one of the rare flavors I’ll buy at the store and Dark Berry Bash tastes nothing like it. I can only give it a 2/10 and it was so bad I’m not sure I’d try it again to find out if I got a bad batch.

Favorite Buys of 2025

Here are my favorite purchases of the year, in no particular order.

I saw the EDJY fingernail cutter in an ad and had to give it a try. It is soooo good. Previously I would cut my left hand nails with a pair of nail scissors and my right hand nails with a typical nail clipper. Then I had to use a nail file to knock down the sharp edges on every finger. This EDJY cutter is extremely quick, catches the cutoffs, and I no longer need to use a nail file. I’m patiently waiting for their toenail cutter, which was supposed to release this year.

A couple of hobby electronics items are the Pinecil soldering iron and a silicon soldering mat. The Pinecil is much smaller, lighter, and easier to use than my HAKKO and it’s cool I can upgrade and modify it. It heats up so quick. The mat protects my desk from solder splatter and keeps everything organized during a project.

I posted about these next two items earlier this year and they are the Putting Thing from WHYGOLF and my L.A.B. Golf DF3 Putter. I love the feel of this club and when I was practicing daily I was putting great. Then I stopped setting aside the time and my putting inside 10 feet turned terrible. I’m committed to more consistent practice next year.

For the last few years I’ve been carrying a Field Notes Expedition Edition (waterproof) on the golf course to keep score and track stats. Before this golf season I bought a leather cover, which fits great in my back pocket to keep the notebook and an official scorecard protected.

Other additions to my golf bag are Titleist GT2 fairway woods to replace hybrids. I bought the 16.5° (4 wood) and 21° (7 wood). The seven is my favorite and I have the confidence to hit it from anywhere. It goes through the rough like butter and launches the ball like a wedge. One of the greatest shots I’ve ever made was hitting this thing on a par 5 from over 230 yards away to 3 feet for an eagle. It came in so high and soft that it only bounced a foot from where it landed. I want to try their driver before next season.

In November we picked up the Marcy 150 lb. Stack Home Gym to replace our single cable DP Ultra Gympac. It’s already getting a lot of use and expanded the movement options we have in our home gym.

In February I got my eyes checked after some bad headaches and found out I needed glasses. Straight to progressive lenses too! We’d all prefer not to wear glasses, but seeing clearly is better. I also bought sunglasses for the golf course that only use my far prescription.

Water with Propel or Gatorade Zero electrolyte packets had been my drink on the golf course for several years and I’d often still end up with headaches on the really hot/humid days. This year I switched to SALTT, which has a lot more electrolytes and the headaches are completly gone. This stuff is great to drink all day every day. My favorite flavors are Cherry Chill and Lemon Lime Twist, while Brandi likes the Caramel Vibes.

I’ve been wanting an everyday truck for years and had a reservation for Tesla’s ugly dumpster for five years, hoping they’d redesign it. Rivian offered me great incentives and an unbelievable trade-in, so I leased a white dual motor performance R1T this summer. It’s a much better vehicle than both the Tesla Model 3 and Model Y I had. I’ve never seen anything like the automatic brights, which adjust to only point low beams at other vehicles, while still projecting high beams everywhere else in front of the vehicle. It’s magic! If you’re in the market, use my referral code (NICK13411712) and we’ll both get a reward.

I’ve tried different bags of coffee (specially ground) to make cold brew, but never cared for any of them so we resorted to buying too many bottles of premade cold brew. This year we decided to try Atlas Coffee Club (referral) and it’s been great. We’re on an automatic subscription for two bags about every two weeks (less frequently in the cold months) and make all of our own cold brew. Each delivery is a coffee blend from a different country, which keeps it interesting.

I built the outfeed/assembly table in my workshop around the JessEm Mast-R-Lift II Router Lift. Although I haven’t even used it yet, I love what it’s done to the usability of the space and how much it’s going to improve my build processes. In my old shop taking out the router table was a whole thing and not convenient at all.

A new tool I reach for all the time is a 6″ double square. Being able to use both sides means I rarely reach for a combination square anymore. I don’t think I’ve ever even used the 45° side of a combination square. I’d like to find a 10 or 12 inch as well.

I needed new belts and went with a couple different styles of ratcheting ones. Black and blue dress belts, which still go great with shorts or jeans, and more casual gray and khaki web nylon belts. I love that I’m not limited to holes in the belt for sizing and it’s so easy to loosen after a big meal.

The long sleeve hooded t-shirt from 32 Degrees was an instant favorite and I ended up getting it in three colors: artichoke space dye, heather, and black. I’d wear shorts and a hoodie year round if I could and the thinner t-shirt material is really comfortable.

At our 7 year anniversary, Automattic gifts us headphones and I’ve been traveling with the same pair of over ear headphones ever since. I’ve been with the company for over 17 years, so do the math. On a recent trip I noticed the ear pieces were falling apart and I had black stuff all over my head and clothes. So I bought a set of Apple AirPods Max and these things are incredible in terms of comfort and the listening experience.

Our resort in Mexico had Fresca in the quick snack area and I’d never had it before. We gave it a try and both loved it. It’s a grapefruit citrus flavored soda water with no caffiene and no calories. It’s delicious and we’ve been buying it here and there for the last couple of months. The Black Cherry flavor is ok too, but not as good.

Trader Joe’s Crispy Jalapeño Pieces are a delicious way to finish this list. Since trying them I never leave Trader Joe’s without several bags in the cart.

This is my fourth year making this post. Look back at 20172018, and 2024. What are some of your favorite buys?