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.