Home Automation Armageddon!
I have a friend who is new to programming and has asked me how I do it. It made me reflect for a minute to think about the patterns I take while learning something new or even completely broken and then get it working. Curious myself now, I decided to document a simple project from start to finish.
I found some relatively inexpensive “WiFi” LED light bulbs online that have an open developer community and documented network protocol. I grabbed a set of bulbs and the WiFI controller off of the internet and then setup the whole getup using the web instructions. After playing with the provided app, I started coming up with harebrained ideas about tricks I could pull using the API. So I started hacking.
Some background on the Mi-Light WiFi
The way the whole system works is a combination of (a cloud service maybe?), the WiFi connector, and radio control of the various lights in your house. Pairing is easiest through the app and flipping lights on and off (with the switch) seems to help when pairing. Once the lights are paired with the WiFi connector, it uses radio to control them.
Network messages to the router are extremely simple, UDP packets sent to the listener on the router are handled and the commands are transmitted over radio to the lights. Btw, I like this protocol, it’s obviously simple and does specifically what you say. It’s no surprise to me that as much code exists out there working against the system.
Here’s the rub. As far as I could tell, the protocol changed between the time that most of the demo apps were made and when I had received my lights. When I tried the code I could find, I encountered issues and needed to diagnose so I started coding my own tests and reverse engineering.
Testing the Network Protocol for the Mi-Light
You need a way to monitor UDP packets and to send UDP packets. My home machine happens to be Windows so I got things rocking with a little WinPCap, Packet Sender, and SmartSniff. The plan is to use the sender to test the network API and then use the sniffer to monitor the responses. This way, I can clear out any potential network / protocol bug issues.
To kick things off, I tested the WiFi discovery query, which is a broadcast (255.255.255.255) UDP send to port 48899 over the network that the WiFi connector is paired with. I tracked the broadcast packets using SmartSniff and the output looks something like this:
The packet of interest is the response from the Mi-Light that includes its IP and MAC addresses. This response contains a tuple with the IP and MAC address of the WiFi connector. At this point I gave myself a little high-five because I now had a relatively simple discovery protocol that could be worked with and I had verified the light system was operational on my network.
Time to try some more fun stuff. A short UDP blast to my WiFi connector on port 8899 was definitely in order. Packets out were:
00000000 42 00 55 B.U
And my living room lights turned on, along with all my other lights, soo awesome. Time to try turning them off, I sent the following packets to the server on 192.168.1.174, port 8899:
00000000 41 00 55 B.U
Some decoding of the commands:
The first byte (41 or 42) is the command from the documentation, the rest is a packet suffix. For any of the commands, you just need to check the op code table:
Hexidecimal (byte) Decimal (integer) RGBW COLOR LED ALL OFF 0x41 65 RGBW COLOR LED ALL ON 0x42 66 DISCO SPEED SLOWER 0x43 67 DISCO SPEED FASTER 0x44 68 GROUP 1 ALL ON 0x45 69 (SYNC/PAIR RGB+W Bulb within 2 seconds of Wall Switch Power being turned ON) GROUP 1 ALL OFF 0x46 70 GROUP 2 ALL ON 0x47 71 (SYNC/PAIR RGB+W Bulb within 2 seconds of Wall Switch Power being turned ON) GROUP 2 ALL OFF 0x48 72 GROUP 3 ALL ON 0x49 73 (SYNC/PAIR RGB+W Bulb within 2 seconds of Wall Switch Power being turned ON) GROUP 3 ALL OFF 0x4A 74 GROUP 4 ALL ON 0x4B 75 (SYNC/PAIR RGB+W Bulb within 2 seconds of Wall Switch Power being turned ON) GROUP 4 ALL OFF 0x4C 76 DISCO MODE 0x4D 77 SET COLOR TO WHITE (GROUP ALL) 0x42 100ms followed by: 0xC2 SET COLOR TO WHITE (GROUP 1) 0x45 100ms followed by: 0xC5 SET COLOR TO WHITE (GROUP 2) 0x47 100ms followed by: 0xC7 SET COLOR TO WHITE (GROUP 3) 0x49 100ms followed by: 0xC9 SET COLOR TO WHITE (GROUP 4) 0x4B 100ms followed by: 0xCB
Darn those set color to white commands!!! At this point, sending the packets in a network tool won’t suffice – but wifi light developer don’t care, this is the right problem to have. At this point, we’ve confirmed the network API works and are ready to code.
The code at this point is a super simple Visual Studio project that is definitely worth keeping around for testing lights during initial setup. I might add a setup command (send group [1..4] n times at y frequency) because this phase for lights is a trick or you can fork it and do that for me.
Crawl, walk, run…
After I have a minimal test with as few software variables as possible (sigh, crawl), I tend to start coding with a reusable template (walk). In this case, sending the messages / receiving broadcast responses is the clear point of interest. After wrapping making API calls, it then makes sense to create meaningful wrappers around API calling. In this way, you can easily refactor the API calls independently from calling the API.
To accomplish this design, I created a second project to be used while build out my utility class / library that will eventually take care of message queuing and performing API calls – all the bits relevant for integrating the lights with software.
The intermediate project used for testing the API wrapper looks something like this:
It’s a boring project, I know. But, this is how it’s supposed to be. The intermediate library is most importantly a bootstrap for a more exciting project that will take full advantage of the functioning library. While developing in an environment where I can’t be sure if anything even works, I try to minimize the variables that can interfere with determining whether the client application code is the issue or it’s the state of the system – a WiFi router and radio rig in this case.
I filled out the methods on the Utility class. These just are bootstrapped by the original starter project that I created. The API calls are abstracted out, the constants for the various supported commands are made internal, and then refactoring happens if the class is getting bloated.
A first pass over the API yields the following structure:
I also whipped together a command prompt and added console colors for flair:
At this point I have a pretty good understanding of how well my library works. Here is the GitHub commit for this change that also has a few fun tests for checking the potential of the API. The following video shows it off:
Making it more robust
Using a RESTful API is an easy way to connect more services to the existing UDP service and should make it easier for me to extend to mobile apps or shell scripts. I haven’t started it yet but the final phase (run) is to create a Web API endpoints project with endpoints corresponding to the API calls the controllers will make use of the library I created.
But what about testing? Where is your Test Driven Development hat?
These projects are still throwaway prototypes at this point so I haven’t yet integrated a test framework such as NUnit. Additionally, behavioral testing becomes pretty tough when you’re flipping on and off lights in the house using a fire and forget protocol. Finally, the intermediate project which exercizes the library functions as a full system test. However, physically testing all the commands can make refactoring much less of a chore to put together tests, proper, though so while things are working it’s time to take a step back and protect your code.
If the prototype needs to be iterated, the first thing that I do is refactor based on what I’ve learned while using the library. Before or during the refactor, tests are extremely helpful – if you don’t do this, things get out of control again in terms of variables (are the lights broken or is it my code?) to debug.
Testing starts with the utility library that is moved to a separate project and tested as a separate component. All further development on the library becomes TDD. Subsequent projects that use the library become TDD. Test all the things.
Thoughts on my prototype coding process
I can think of a number of projects that I have worked on where I followed exactly this pattern and I’m wondering at this point if everybody does this when working on similar projects. When I was working on the early Google+ samples, the process was:
- Create Quickstart app that tests a single API call – leave as test to confirm configuration
- Write code that takes full advantage of the API (create graphs using connections, use app activities, integrate best practices, and add tests)
Note that this process has worked for me on small projects but on larger projects and even more experimental projects, the process is entirely different. Perhaps worth another post in the future. Testing similar projects could also be worth a few words.