Mi-Light/Limitless LED are a type of RGB(W) LED lamps with standard light bulb sockets and a proprietary 2.4GHz radio control protocol
Okay, I've created some github projects and pushed my source so far. This is only what I have now, but I intend to make it usable.
First up: https://github.com/henryk/milight-reverse-engineering simply contains all the little tools and some of the notes I developed while doing the actual reverse engineering. I don't believe that it's useful to anyone, but you never know. This is totally undocumented and unsupported, and in some cases has commented-out functionality from different phases of the process. YMMV.
Then there's my currently most stable code in https://github.com/henryk/openbeacon-ng/tree/openmili (in firmware/nRF51/pca10031-openmili). This is actually the openbeacon-ng project by my friend Milosch Meriac, but it does have a build system for the nRF51 that is not as full-on retarded as the official Nordic SDK, and also allows for easier code sharing. At some point this should somehow move into the actual project described next.
Finally, there's the OpenMili project: https://github.com/henryk/openmili. This is supposed to be the actual project that people want to use. I have a grand mental plan, but there's only the Arduino proof-of-concept code in there as of now.
The idea is to have proper layers of abstraction and build upon them, allowing mix-and-match as needed. Some of the comments on this http://hackaday.io project indicated that they were/are working along similiar lines, but do have a real PL1167 (or compatible). So the middle layer will be a virtual PL1167 abstraction, that can either shell out to a real PL1167 (I may need help here, since I don't have one to test on) or an emulation on a Nordic chip. As I've said before I want support for both the nRF5122 and nRF24L01(+) chips on the layer below that. Above that would be a thin shell for Mi-Light 2.4GHz frames so that a simple serial protocol will allow to make useful use of the hardware from a host application. Then the first primary goal is to have a host application that fully replaces a Mi-Light Wi-Fi gateway, possibly with slightly better timing, and, most importantly, configurable IDs. (First idea: open multiple UDP ports, each port will send with a different ID; Second idea: also open TCP ports for reliable communication.)
Then, far off in the future I see the possibility of a more advanced firmware implementation with vastly improved timing and features: interleaving multiple commands to different bulbs when sending, maybe performing color animations autonomously, receiving and reporting commands sent by other remotes when idle.
When entering a short test sequence on my remote this is what my receiver displays (a dot means an exact repetition of the previous packet):
All on: 07 B0 F2 EA 35 90 01 B9 AC F9 .......................... 07 B0 F2 EA 35 90 00 B9 74 E0 .. Color red: 07 B0 F2 EA 04 90 0F BA 6E 01 07 B0 F2 EA 04 90 0F BB E7 10 07 B0 F2 EA 04 90 0F BC 58 64 07 B0 F2 EA 04 90 0F BD D1 75 07 B0 F2 EA 04 90 0F BE 4A 47 07 B0 F2 EA 04 90 0F BF C3 56 07 B0 F2 EA 04 90 0F C0 B3 DD 07 B0 F2 EA 04 90 0F C1 3A CC 07 B0 F2 EA 04 90 0F C2 A1 FE 07 B0 F2 EA 04 90 0F C3 28 EF 07 B0 F2 EA 04 90 0F C4 97 9B 07 B0 F2 EA 04 90 0F C5 1E 8A 07 B0 F2 EA 04 90 0F C6 85 B8 07 B0 F2 EA 04 90 0F C7 0C A9 07 B0 F2 EA 04 90 0F C8 FB 51 07 B0 F2 EA 04 90 0F C9 72 40 07 B0 F2 EA 04 90 0F CA E9 72 07 B0 F2 EA 04 90 0F CB 60 63 07 B0 F2 EA 04 90 0F CC DF 17 07 B0 F2 EA 04 90 0F CD 56 06 07 B0 F2 EA 04 90 0F CE CD 34 07 B0 F2 EA 04 90 0F CF 44 25 07 B0 F2 EA 04 90 00 CF 8C A6 ............................ Group 1 on: 07 B0 F2 EA 04 91 03 D0 4E 3E ............................. 07 B0 F2 EA 04 91 03 D1 C7 2F ............................. Group 1 white: 07 B0 F2 EA 04 91 13 D2 CD 88 ............................. 07 B0 F2 EA 04 91 13 D3 44 99 ............................. Color green: 07 B0 F2 EA 7D 91 0F D4 EC 72 07 B0 F2 EA 7D 91 0F D5 65 63 07 B0 F2 EA 7D 91 0F D6 FE 51 07 B0 F2 EA 7D 91 0F D7 77 40 07 B0 F2 EA 7D 91 0F D8 80 B8 07 B0 F2 EA 7D 91 0F D9 09 A9 07 B0 F2 EA 7D 91 0F DA 92 9B 07 B0 F2 EA 7D 91 0F DB 1B 8A 07 B0 F2 EA 7D 91 0F DC A4 FE 07 B0 F2 EA 7D 91 0F DD 2D EF 07 B0 F2 EA 7D 91 0F DE B6 DD 07 B0 F2 EA 7D 91 0F DF 3F CC 07 B0 F2 EA 7D 91 0F E0 4B 05 07 B0 F2 EA 7D 91 0F E1 C2 14 07 B0 F2 EA 7D 91 0F E2 59 26 07 B0 F2 EA 7D 91 0F E3 D0 37 07 B0 F2 EA 7D 91 0F E4 6F 43 07 B0 F2 EA 7D 91 0F E5 E6 52 07 B0 F2 EA 7D 91 0F E6 7D 60 07 B0 F2 EA 7D 91 0F E7 F4 71 07 B0 F2 EA 7D 91 0F E8 03 89 07 B0 F2 EA 7D 91 0F E9 8A 98 07 B0 F2 EA 7D 91 00 E9 42 1B ............................ Group 1 off: 07 B0 F2 EA 7D 91 04 EA B9 4E .............................
I already know that the first byte is length, the last two bytes are CRC and the one before that is a packet ID that is incremented for each distinct packet (but kept the same for resends). That leaves 6 bytes in the middle: In my first preliminary assessment they seem to be:
This is not a proper "command" set. This is just the current state of the remote control, transmitted in the most unimaginative way possible. By looking at the lower nibble of the CMD byte I can map it to the buttons on the remote as follows:
Now you know why they can't have more channels or additional features: There simply aren't any button codes left. Two more rules seem to apply:
The value of the sliders are as follows:
Based on what I knew so far I could a) describe the Mi-Light frame format (if not the contents) and b) try to map this to the nRF51 or nRF24 frame format. Way back in 2011 the neighbourly Travis Goodspeed described how to achieve promiscuous sniffing with the nRF24L01+. Even though I didn't use this technique here (having done both SDR and logic analyzer sniffing instead), you should read his post, since it contains some basic knowledge that also applies to the PL1167 format.
Below is the framing I deduced for both nRF51422 (in little endian mode) and nRF24L01+ (which seems to operate in big endian), illustrated using an example from my received data:
Read more »
I also wanted to sniff the communication between the micro controller and radio IC, but couldn't get the case of the remote control open. At the next opportunity I brought the Mi-Light Wi-Fi controller with me and got that open more or less easily:
Inside wasn't much: a main board and, as I'd read before in the openhab group, a Wi-Fi daughter board connected to that, both with simple wire antennas.
I knew that the connection between the boards wasn't interesting, since it only contains an UART with the command set I already know. I also know that that command set isn't related (in any obvious way) to the on-air protocol. That leaves me with a conclusion: The unmarked chip in the top left must be a microcontroller that implements the actual protocol and transmits commands to the PL1167 chip to the right of it. The PL1167 chip's leads seemed a little small to try to connect a logic analyzer, so instead I connected it to the microcontroller. I didn't know the pinout (except that center right is ground), so I just connected probes at random, hoping to catch something interesting (the probe at the bottom of the image is connected to ground).
Counting counter clockwise from the marking on the microcontroller I got probes onto 1, 2, 3, 4, 8, 10, 13, and 14. I was using a Saleae Logic with their beta software for Linux (for some reason the 'stable' version would crash rather reliably). I also had the RF receiving set up, though it turned out that I didn't really need it:
And yep, sure enough, I got all four wires required for SPI among my sniffed traces, and can see some action when the device boots:
I had to play with the SPI settings to get this right (CPOL=0, CPHA=1), but it matches up with what the data sheet told me. Here you can see it writing the "Register Optimum Values" from the user manuals right after boot:
I exported the data from the Logic software as a CSV file and wrote a small python tool to decode and interpret the data, this is what happens on boot:
1.17814875: Write reg 0 -> 6F E0 1.17824850: Write reg 1 -> 56 81 1.17834825: Write reg 2 -> 66 17 1.17844775: Write reg 4 -> 9C C9 1.17854750: Write reg 5 -> 66 37 1.17864725: Write reg 7 -> 00 4C channel 76 (2478)MHz 1.17874700: Write reg 8 -> 6C 90 1.17884675: Write reg 9 -> 48 00 1.17894650: Write reg A -> 7F FD 1.17904625: Write reg B -> 00 08 1.17914575: Write reg C -> 00 00 1.17924550: Write reg D -> 48 BD 1.17934525: Write reg 16 -> 00 FF 1.17944500: Write reg 17 -> 80 05 1.17954475: Write reg 18 -> 00 67 1.17964450: Write reg 19 -> 16 59 1.17974425: Write reg 1A -> 19 E0 1.17984375: Write reg 1B -> 13 00 1.17994350: Write reg 1C -> 18 00 1.18004325: Write reg 20 -> 48 00 preamble length: 3 bytes, syncword length: 32 bits, trailer length: 4 bits, data type: NRZ, FEC: None 1.18014300: Write reg 21 -> 3F C7 1.18024275: Write reg 22 -> 20 00 1.18034250: Write reg 23 -> 03 00 1.18044225: Write reg 28 -> 44 02 1.18054175: Write reg 29 -> B0 00 CRC on, first byte is length, FW_TERM_TX, initial CRC data: 00 1.18064150: Write reg 2A -> FD B0 1.18074125: Write reg 2B -> 00 0F 1.39462075: Read reg 0 -> 6F E0 1.39473375: Read reg 1 -> 56 81 1.39484675: Read reg 2 -> 66 17 1.39495975: Read reg 4 -> 9C C9 1.39507300: Read reg 5 -> 66 37 1.39518600: Read reg 7 -> 00 4C channel 76 (2478)MHz 1.39529900: Read reg 8 -> 6C 90 1.39541200: Read reg 9 -> 48 00 1.39552525: Read reg A -> 7F FD 1.39563825: Read reg B -> 00 08 1.39575125: Read reg C -> 00 00 1.39586425: Read reg D -> 48 BD 1.39597725: Read reg 16 -> 00 FF 1.39609050: Read reg 17 -> 80 05 1.39620350: Read reg 18 -> 00 67 1.39631650: Read reg 19 -> 16 59 1.39642950: Read reg 1A -> 19 E0 1.39654250: Read reg 1B -> 13 00 1.39665575: Read reg 1C -> 18 00 1.39676875: Read reg 20 -> 48 00 preamble...Read more »
The first step in reverse-engineering any radio protocol is to listen to it on the air. The advent of software-defined radio makes this much, much, easier than before. For this project I've used a nuand bladeRF board and the GNURadio software stack, specifically gnuradio-companion.
The first task I set out to achieve was finding the frequency the devices communicate on. From the data sheet I knew that there are 128 channels in 1MHz spacing starting at 2.402GHz. In GRC I connected the bladeRF source to a FFT display with peak hold mode and then pressed buttons on the Mi-Light remote, positioned directly next to the antenna, while switching through receiving frequencies. I found that something would happen on 2.411GHz every time I touched the remote.
Next I wanted to look at the recorded signal more closely in the swiss army knife of signal analyzing: baudline, but for reasons that are not entirely clear to me I couldn't get the raw recording to load in baudline (or it would always crash immediately). Well, OK then, on to some speculative signal processing in GRC: I assumed amplitude modulation, because that's what all my previous RFID projects used, and so I connected a Frequency Xlating band pass filter and a simple complex-to-mag block in between the signal source and a scope. If I made the band pass narrow enough, I would get something akin to a data signal in the scope, but it didn't look pretty.
I re-checked the PL1167 data sheet, which I should have done before, and there it was spelled out rather clearly: The chip uses GFSK, a type of frequency modulation. Yes, if you put a narrow band pass on one of the frequency bands used by FSK you will get something similar to the original signal. But that's not the proper way to decode FSK. Truth be told, I don't know the proper way to decode GFSK, but I've seen someone on the internets use a quadrature demod block for the purpose, and sure enough, it seemed to work.
Now that I had a demodulated signal, I simply saved that as a .wav file and could look at it in baudline at my leisure. I identified a 10101010… preamble used to prep the receiver and could deduce the bit timing (4 samples per bit, or, at my sample rate of 4MHz, 1 Mbit/s – I could have gotten that from the data sheet).
I wrote Python code that consumes the recorded samples, looks for a strong signal and the preamble and then decodes the signal into bits that are written to stdout:
So that looks quite good already: Aligning on the first 111 tells me that most of the received data is identical, except for some changing bits at the end. Visually I identified a counter at the end, followed by 16 bytes that seemed to change at random (most likely a CRC). Aligning it so that the counter wasn't split between bytes, decoding as little endian (I got that from the counter), gave me a list of bytes transmitted:
47 B1 58 52 07 B0 F2 EA 76 10 01 8A 22 C5 FF 03 00 00 00 00 47 B1 58 52 07 B0 F2 EA 76 10 01 8A 22 C5 FF 03 00 00 00 47 B1 58 52 07 B0 F2 EA 76 10 01 8A 22 C5 FF 03 00 00 00 00 47 B1 58 52 07 B0 F2 EA 03 10 0F 8B A9 7A FF 03 00 00 00 47 B1 58 52 07 B0 F2 EA 03 10 0F 8C 16 0E FF 03 00 00 00 00 47 B1 58 52 07 B0 F2 EA 03 10 0F 8D 9F 1F FF 07 00 00 00 47 B1 58 52 07 B0 F2 EA 03 10 0F 8E 04 2D FF 03 00 00 00I already could tell: Most commands are sent as ~30 copies (with same counter value). Except for color/brightness changing, that seems to be streamed (with a new counter value per transmission) as long as the finger is on the wheel, and then when taking the finger off the wheel the end is repeated (with same counter value) again ~30 times.
I have bought a set from Amazon: 4 Mi-Light bulbs (E27, 6W), remote control, and Wi-Fi gateway. The components perform as advertised: using the remote control is reliable and immediate, lamp brightness is ok, colors are great. There's a big disappointment though: These aren't RGBW lamps! These are, to coin a phrase, HV⊕V lamps: You can have them either in color or in warm white mode, and in color mode you can only set hue and value (brightness), while in warm white mode you can only set the brightness. You cannot change the saturation and you cannot combine white and colored LEDs.
As documented elsewhere, the Wi-Fi gateway creates its own password-less Wi-Fi network to which you can connect and has a web interface with username/password admin/admin through which you can join it to your home Wi-Fi network. (Actually, my access point lets me create multiple virtual SSIDs that can be mapped to VLANs and I did so here: The Mi-Light Wi-Fi gateway is connected to a special separated network and does not have internet connectivity.) I've never used the accompanying phone app.
Instead I'm using the Wi-Fi gateway with the WifiLight module for FHEM (a home automation framework I use). This works mostly reliably, but the delay/refresh rate is awful. I'm using the same module with an LD382 Wi-Fi LED strip controller which can easily sustain one command every 150ms. With the Mi-Light gateway it's more like one command per second.
LimitlessLED (one of the names under which Mi-Light bulbs are sold) publishes a lengthy and not well organized developer page with lots of information and something they call OpenSource API. What it is though is a listing of all the commands that you can send to the Wi-Fi gateway, and that's already a big help. Besides the FHEM module mentioned above, there already is a lot of code in all the programming languages of the world (including PHP und bash) to send commands to the gateway.
In the openhab (which is another home automation framework) Google Group there is a thread from someone who has opened their bridge, discovered that it consists essentially of two boards (a Wi-Fi to UART side, and an UART to proprietary 2.4GHz side) and replaced the Wi-Fi connection with a wired LAN connection for better reliability. This thread contains a lot of information: It lists the 2.4GHz chip for the proprietary RF system as a PL1167 and even has data sheets for the PL1167. The protocol on the UART connection is simply the same protocol as on the Wi-Fi: the Wi-Fi→UART converter is a dumb standard component that is not Mi-Light specific.
Some of this information was previously collected and linked to from https://wikidevi.com/wiki/MiLight, there's also a link to a Mi-Fi bulb teardown.