my blog

Automated Blinds Part 5: HomeBridge Integration & Firmware Updates

HomeBridge Integration

All of my smart home devices are routed through Apple Home, which uses an API called HomeKit to control smart devices through a hub. When I started this project I knew I wanted it to feed into the Home app so I could control it using the same workflows as my other devices and add it to my existing automations. Unfortunately, Apple has no native 3rd party support for random devices like my blinds but luckily some better coders than me already solved this problem. HomeBridge is an open source, 3rd party connector for any device that can connect to the internet. You can feed it any sort of 3rd party device control in JSON and it will handle the connection and executing on the HomeKit side. In my case, I’m using an HTTP control library for HomeKit called homebridge-minimal-http-blinds which is, lucky for me, designed for blinds that expose HTTP APIs.

HomeBridge desktop UI

HomeBridge desktop UI

I got my instance of HomeBridge setup on a Raspberry Pi Zero which was super straight forward (great documentation). The interface (right) is fantastic and adding accessories is super simple. I really have to hand it to the people who developed this and made it entirely open source; it’s better than a lot of paid apps I use.

Using the minimal blinds library, I setup three exposed HTTP APIs for the system:

"get_current_position_url": "http://192.168.1.218/get/current_position/",
"set_target_position_url": "http://192.168.1.218/set/%position%",
"get_current_state_url": "http://192.168.1.218/get/current_state/",
Adin hard at work

Adin hard at work

The names are fairly self explanatory. Get position and get state receive information from the Arduino on the current position and whether it’s opening, closing, or idle. The set target position hands a position value to the blinds that’s captured in the %position% field at the end of the link. I actually ran into a ton of issues around the request handling and responses and was really lucky to have my friend from college, Adin Vashi (who is an actual coding whizkid), visiting. He helped me smooth out some of the server and request and response handling logic and helped me parse the %position% value into the firmware.

We ended up spending quite a few days working through a particular bug with the set_target_position API that cause HomeBridge to timeout. It ended up being caused by a super innocuous inversion of the HomeBridge position and actual blind position that made the system hangup. Always feels good to change one line of code and fix a weeks worth of debugging. Once that was ironed out, I whipped up a protoboard to condense the electronics (see below).

Breadboard - messy!

Breadboard - messy!

Protoboard final product

Protoboard final product

After all that, I had 2 functional blinds (currently driven & controlled identically other than during homing) that could be run via voice commands on my HomePod and through the Home app on my iPhone. They can be controlled in full open & close cycles as well as any position mapped from 0%-100% in the open-closed range.

The full integration in HomeKit means they can also be controlled from the Home UI on my iPhone & mac and be added to all my existing home automation routines.

Firmware Updates

The firmware also went through quite a few changes at this stage. The key change was making the motor running non-blocking to the rest of the loop. Previously, when a new target position came in, the computer spent all of its cycle time running the motors until they got to their new target. This took the form of a loop like:

  while (stepperA.currentPosition() != target || stepperB.currentPosition() != target){
    stepperA.run();
    stepperB.run();
  }

where the computer would run either stepper until the current position was equal to the target position (the run() command takes a single step only if accelStepper decides a step is due so it must be repeated until the move is complete). This became an issue because the Arduino would stop responding to the HomeBridge server and the connection would timeout while it focused solely on running the motors. The fix requires that the run() commands be added to the main loop. I think I have a general misconception of how quickly these processors tear through code which is what kept me from doing this in the first place. To run the motor at ~25rpm with 256 discrete micro steps, the motor needs to take a step about every 8 milliseconds, about 20 times faster than a blink of an eye. This means if the step command is in the main loop like so:

void loop() {    
  stepperA.run();
  stepperB.run();
  client = server.available();
  if (client) {
    runServer();
  }
}

the Arduino needs to check if a step is due (and step if required) then cycle through an entire client read loop and provide any information to the client should it be requested (like state or current position). My brain tells me this would without a doubt take longer than 8 milliseconds, but no, the Arduino absolutely rips through these functions and in fact, only takes a step once every 3 cycles of the loop, meaning it does this with time to spare. It’s pretty interesting to really understand how fast microprocessors deal with tasks, even through what I would think are bottlenecks, like WiFi. Now since this works, the Arduino can effectively run the motors and continue to communicate with the HomeBridge server which avoids any timeouts.

The project is mostly wrapped up at this point. I’m planning on making a few mechanical updates at which point I’ll make a separate post about the mechanical design and create the final page with the complete build tutorial and code!

Nathaniel Berman2 Comments