Karl Entwistle

Summarized using AI

Automate your Home with Ruby

Karl Entwistle • April 03, 2019 • Minneapolis, MN

In the talk "Automate your Home with Ruby" presented by Karl Entwistle at RailsConf 2019, the focus was on leveraging Ruby to create a unified platform for controlling various smart home devices. With the increasing popularity of home automation gadgets, many users often face the challenge of having multiple applications to manage different devices. Karl shares his experience of transforming his home into a smart environment and the limitations he encountered with existing solutions.

Key points discussed in the talk include:

- Smart Home Motivation: After moving into a new house in Bristol, Karl's goal was to automate his home using smart devices like Philips Hue light bulbs and smart thermostats.

- Multiple Applications Issue: The challenge arose as each device required its own application—resulting in nine different apps with no interoperability between them.

- HomeKit as a Solution: Karl discusses Apple’s HomeKit, a software framework that enables smart home devices to work seamlessly within the Apple ecosystem. HomeKit supports voice commands via Siri for easy control.

- Limitations of HomeKit: While some devices work with HomeKit, others do not, requiring users to replace their existing gadgets—something Karl was reluctant to do.

- Creating a Ruby Library: After realizing no Ruby library existed for HomeKit, Karl took the initiative to create one, named Ruby Home. He details the technical challenges, including navigating Apple’s HomeKit Accessory Protocol (HAP) documentation.

- Technical Overview: The process involved understanding and implementing multicast DNS for service discovery, establishing a custom HTTP server to handle requests, managing unique tokens for secure communication, and the complexities of unsolicited HTTP responses for device notifications.

- Demonstration of Ruby Home: Karl provides practical examples of how to set up devices, including a fan and soundbar, using only a few lines of Ruby code. He showcases the ability to control these devices through the Home app and via Siri, demonstrating the seamless integration Ruby Home provides.

- Final Takeaways: The talk emphasizes the importance of breaking down complex problems into manageable parts, encouraging developers to be the change they wish to see in the world by creating tools that enhance user experience in home automation.

In conclusion, Karl’s talk illustrates the potential of Ruby in home automation, showcasing a practical approach to bridge the gap between various devices and ecosystem limitations, ultimately making home management simpler and more cohesive.

Automate your Home with Ruby
Karl Entwistle • April 03, 2019 • Minneapolis, MN

RailsConf 2019 - Automate your Home with Ruby by Karl Entwistle

_______________________________________________________________________________________________

Cloud 66 - Pain Free Rails Deployments
Cloud 66 for Rails acts like your in-house DevOps team to build, deploy and maintain your Rails applications on any cloud or server.

Get $100 Cloud 66 Free Credits with the code: RailsConf-19
($100 Cloud 66 Free Credits, for the new user only, valid till 31st December 2019)

Link to the website: https://cloud66.com/rails?utm_source=-&utm_medium=-&utm_campaign=RailsConf19
Link to sign up: https://app.cloud66.com/users/sign_in?utm_source=-&utm_medium=-&utm_campaign=RailsConf19
_______________________________________________________________________________________________

With the increasing number of home automation devices, our homes are getting smarter and smarter. How can Ruby help?

Instead of installing separate apps to control my many devices, I wanted to use HomeKit via Siri and the pre-installed Home app on any iOS device, for one unified experience.

This is a talk about how I created the first ever Ruby library for the HomeKit accessory protocol to bridge the gap between different platforms and just some of the exciting possibilities this could unleash.

RailsConf 2019

00:00:20.779 Hi everyone, so this talk is entitled 'Automate Your Home with Ruby'. My name is Karl Entwistle, and you can tweet me at CollinWhistle. I work at Cookpad, and they sponsored me to come to RailsConf and give this talk.
00:00:33.420 You can probably tell from my accent that I'm not from America; I'm actually from the UK. This is my first time in America and also my first time at RailsConf. It's quite an experience, and my brain has associated America with the video game Grand Theft Auto. So, while I'm walking around in the downtown area, I feel like I'm in Grand Theft Auto and everyone else is like an NPC walking past. It's a bit confusing!
00:01:18.270 As I said, I'm from Bristol in the UK, which is a city in the southwest of England. I used to live in London, but my wife and I wanted to buy a house. So, we moved to Bristol, where property prices are much more affordable than in London. We bought a modest-sized home.
00:01:30.690 After moving into our house, I decided that I wanted it to be a smart home, like a smart home from the future. The first thing I bought was some Philips Hue light bulbs. I installed them in all the rooms, and we had to download and install the application onto our phones to control them.
00:01:44.930 With the lighting situation under control, I decided the next thing we needed was to manage temperature with a smart thermostat. Since England can be quite cold, it’s nice to set the heating to come on or off before I leave the office. Again, we had to download and install a separate application for controlling the thermostat. At this point, we ended up with nine different applications on our phones to control various gadgets.
00:02:21.590 There was no consistency between them, nor any interoperability, because the gadgets came from different manufacturers, creating a closed ecosystem. What I really wanted was one unified application to control all my devices.
00:02:34.650 It turns out there is such an application: if you have an iPhone, it's pre-installed, and it's called Home. Home uses a framework called HomeKit. So, what is HomeKit? Apple's website explains that HomeKit is a software framework for making smart home devices work seamlessly with iOS devices. This means you can control your smart home devices using any device in Apple's ecosystem.
00:03:39.690 Since the latest update to the operating system, it also includes Macintosh devices. HomeKit officially supports various devices like lights, power outlets, windows, and fans, among others. What I really like about HomeKit is that you can control it with Siri. You can say commands like 'turn on the living room lights,' 'turn off the lights,' or 'set the dinner scene.' My personal favorite is asking, 'What temperature is the shed?'
00:04:13.710 Normally, the answer would come in an Irish accent, saying the temperature is around 15 degrees Celsius (not Fahrenheit). In addition to Siri, there's also the visual component in the Home app. For example, I can control the brightness of my Philips Hue lights, and I also recently installed underfloor heating, which I can control through the same app.
00:04:43.000 Now, this is the end of the talk, right? You just buy things that support HomeKit? Well, not quite. There is a problem: you can only use devices with HomeKit if they officially support it. You'll know they support it if they have a sticker on the box or it’s listed on the website.
00:05:03.000 In my case, it meant that my Philips Hue light bulbs already worked with HomeKit, but my Nest thermostat and soundbar did not. I could have theoretically gone out and bought all new devices, but that would have been pretty expensive and quite wasteful. So, I had an idea: what if HomeKit could communicate with Ruby? Then I would be able to bridge the gap between HomeKit and any unsupported devices.
00:06:02.130 So, I searched Google and GitHub for a HomeKit Ruby library and was genuinely surprised that one didn't exist. I wondered why; how hard could it be to create a HomeKit library for Ruby? I remembered a quote from Gandhi: 'You must be the change you wish to see in the world.' So, I decided I was going to create the first Ruby library for HomeKit, which I ended up calling RubyHome. You can install it with 'gem install ruby_home,' and it's available on GitHub.
00:06:56.930 I'll now explain how I created the gem, and then I'll show you how you can use it to automate your homes. The first thing I did was go on Apple's developer website and download the non-commercial version of the HomeKit Accessory Protocol specification. I was happy to find out that it was a very technical 256-page document that referenced 12 RFCs.
00:07:15.350 After glancing at the specification, I saw tables of hexadecimal code, a bespoke HTTP protocol, and something about the Cha-Cha 20 Poly 1305 encryption method. At that point, I figured the most logical thing to do was to use an existing JavaScript library. However, I wanted to use Ruby, so I naively thought I could model it in just a weekend. It actually ended up taking about six months of hacking away to produce it.
00:08:08.450 Instead of reading the entire long and technical document, I decided to work through the process iteratively, as if I just bought an officially supported accessory. In the Home app, I proceeded through the workflow of pairing a new accessory. I pressed the plus icon, clicked 'add accessory,' and stated that I didn’t have a code or couldn’t scan. I knew from past experience that the accessory should be displayed there.
00:09:13.350 At this point, I scanned through the specification to figure out how to make the accessory appear. The documentation mentions that accessory servers must support multicast DNS host names, which many Mac or iPhone users are already familiar with as Bonjour. This service helps populate shared devices in the sidebar when you're on a network.
00:09:43.740 So, I found a great piece of software by a company called Tilde, called Bonjour Browser, which displays all the multicast DNS services on your local network. Going back to my Philips Hue bridge, I could see the key-value pairs it was broadcasting using the Bonjour browser. At that point, I just needed a Ruby library to broadcast similar key-value pairs, and I came across a good multicast DNS library for Ruby called DNS-SD.
00:10:50.490 I saw a familiar face associated with it—Aaron Patterson, or Tenderlove. I assumed it would work well, and it has a simple syntax. For example, I required DNS-SD, assigned an instance of a TCP server running on port 80 to a variable, and used the announce function of the DNS-SD library. When I ran that code in IRB and checked it in Bonjour Browser, I could see the service was being advertised on my network.
00:11:55.230 At this point, I began mimicking the key-value pairs that were coming from the Philips Hue bridge. After that, RubyHome appeared as an accessible service on my phone. Upon clicking the icon, I was prompted for a setup code, and I put in a simple one just to get started.
00:12:25.050 Everything up to this point seemed quite standard. However, I noticed something peculiar about the content type of the HTTP POST request, which was 'application/pairing+trv8.' This was unusual because I had primarily worked with JSON or XML APIs.
00:12:39.740 The body of the request contained hexadecimal data, indicating the start of a long and complex pairing process. I don't want to delve into too much detail about the pairing process, but the key is to understand the body of the request so I could proceed.
00:13:03.240 Again, I searched through the documentation for a piece of information related to application pairing plus trv. The documentation specified that pairing responses come as TLV data in the body of the HTTP response. So, I searched further with the new keyword 'TLV,' and I found an entire section detailing it.
00:13:30.800 To interpret the hexadecimal data sent to the server, I discovered a useful gem called BinData. BinData provides a declarative way to read structured binary data, making it easier to manage compared to Ruby's pack or unpack methods. As it turns out, the hexadecimal data was composed of two TLVs, and each TLV contained an 8-bit type followed by an 8-bit length, indicating how much of the value should be read.
00:14:52.780 After figuring out how to read the TLV data, I could proceed with the pairing process. Each device, such as my iPhone or iPad paired with RubyHome, has its own unique token. RubyHome also holds a corresponding unique token for each device. When your phone connects to the network after returning home, the pairing process recurs.
00:15:35.230 The connection holds surprisingly long, and I don’t know why the battery life doesn’t deplete quickly, but it does. When you leave your house, the connection closes, and new tokens are generated next time the device connects to the network.
00:16:16.210 The HAP specification requires a custom HTTP server. When the server and the phone have unique tokens, the connection switches to a customized HTTP protocol, where each HTTP message is split into frames no larger than 1,024 bytes. Each frame has a specific format, and if a connection failure occurs during the session, the server must immediately close the connection.
00:17:03.930 Initially, I found this to be a complicated process and wondered how to implement it. Eventually, I realized it wasn't as complex as it appeared, so let me briefly explain how it works. This is a standard HTTP 1.1 response, which includes the protocol, status code, headers, and a body.
00:17:41.840 I broke the response into three frames: the first two are 1,024 bytes, and the last frame is the remainder. Each of these frames is individually encrypted. The encryption process uses an additional piece of metadata called a nonce, which prevents replay attacks. Specifically, nonce values change with each request, so if someone tried to replay requests, they'd fail.
00:18:19.290 In terms of HTTP handling, I needed to ensure I encrypted the entire response before sending it to the device. I couldn't just encrypt the body or headers individually. Therefore, I substituted the HTTP request and HTTP response classes with my own classes that inherited from the original ones. This allowed me to override parsing logic as needed.
00:19:01.620 Now, when the request is supposed to be unencrypted, I call the original method. Still, when it's meant to be encrypted, I perform the encryption first and then send the entire package over the socket. This approach worked effectively for RubyHome. If anyone has ideas on how to make it better, please talk to me after the session.
00:19:51.510 The last thing I want to cover from the HAP specification is unsolicited HTTP. The documentation explains that accessories can deliver notifications by sending event messages through unsolicited HTTP responses. This means the HTTP server can send responses at any time because the iOS device and server have an open socket connection.
00:20:36.450 For example, if an iPhone requests a door to be opened, RubyHome will broadcast an event to the iPhone and iPad, letting them know that the door is now opened. Another instance is with the thermostat; if someone adjusts the temperature manually, that change gets intercepted through the Nest API, and RubyHome can update all associated devices to keep them in sync.
00:21:43.400 Now that we've covered a lot of technical material, let's lighten things up with some demos. This is all the code needed to create a fan in RubyHome. You can see that by requiring RubyHome and defining the devices as types of services, I've used a service factory to create the fan.
00:22:05.490 When I save and run this code, I'll open the Home app on my iPhone, click on 'add accessory,' state that I don't have a code or can't scan, and start running the program to see the multicast DNS generated code. Then, I can type that code into the phone to proceed with the pairing process.
00:22:42.760 Now, after going through the pairing process, I’ll confirm all the different services. You can see the fan appears, and I can switch it on or off. One cool thing in HomeKit is that different services have unique widgets for interaction.
00:23:16.170 What if we want to know if the fan is switched on or off? In RubyHome, a service can have many characteristics. One characteristic of a fan is the 'on' state, so we can subscribe to changes using a function that yields the updated value.
00:24:06.130 When I run this, I shut down the server and start it again so changes apply. After pairing again, I switched on the fan, and it displayed in the terminal that the fan is on. If I turn it off, it updates accordingly.
00:24:55.190 I can also control it with Siri. For instance, I say, 'Siri, switch the fan on,' and it works flawlessly. Let’s look at how we can reduce the program back to just a few lines of code. Additionally, I want to demonstrate the characteristics the fan has, such as speed and rotation direction.
00:25:50.060 These characteristics are the initial values that get applied each time the server restarts. If I apply this code and restart the server, I can see I now control both speed and rotation.
00:26:45.270 If I want more services than just the fan, I need a bridge. To create a bridge, I include another service called accessory information. If I run this with the fan and add a lightbulb, air purifier, garage door, and others, all these services magically appear upon running the program.
00:27:35.980 Furthermore, all the different characteristics can still subscribe for updates. For example, I created a demo website with a hex color code picker, which simulates a color change using HomeKit through the RubyHome library on my iPhone.
00:28:57.020 Finally, returning to the soundbar, I can switch it on or off with my iPhone and iPad, while showcasing unsolicited events as I'm checking the status from both devices.
00:29:11.380 As stated earlier, there are numerous services you can leverage with RubyHome, and I could demonstrate these for much longer! But I must wrap up somewhere and leave you all inspired. If you’re excited about what you learned today, I hope you consider using RubyHome. You've learned how to break down a large problem into manageable chunks.
00:30:02.800 That’s everything in my talk. Thank you all for listening!
Explore all talks recorded at RailsConf 2019
+102