📜 ⬆️ ⬇️

Breaking the nooLite wireless light control

image

Hello!

In our previous article, we showed how our home automation controller works with NooLite system devices without using native USB dongles for transmitting and receiving a signal. To make this possible, we carried out reverse engineering of the NooLite protocol.
')
Actually, this article contains a description of the protocol and tells how we analyzed it.


Introduction


About the NooLite system on Habré a lot has been written before. The system consists of wireless consoles and executive devices that operate at a frequency of 433.92 MHz.

Communication in the system is one-way - the console sends data by radio, and the actuators receive and process it. There is no return channel, and consequently, there is neither acknowledgment of receipt of the message, nor control of the status of executive devices.

Special attention is given to the communication scheme of consoles and actuators in Noolite. In the system, each console has one or several addresses. These addresses are sewn into consoles during production and are different for each console. Each executive unit can be tied to one or several control panels (more precisely, addresses), while the unit stores in memory a list of allowed addresses from which control signals can be received.
The binding is carried out as follows: first, on the executive unit, it is necessary to press a special (single) button, after which the light bulb on the unit starts flashing rapidly, indicating that it has entered the programming mode. Then in the programming mode, again using a special button, you need to translate the remote control that you want to bind. After that, you need to click on any button on the remote that will send a special binding signal. The executive unit receives this signal, after which it is necessary to confirm its entry into the memory by another pressing of the programming button.
Uncoupling the console from the unit is done in a similar way.

The interesting thing about this scheme is that the unique identifiers in the system have not execution units, but consoles. Accordingly, the commands from the console have the form "I have a remote control with the address NNN, I send the command XXX". All blocks configured to “listen” to the given NNN address execute command XXX after receiving such a message.

Getting started



Of course, we started by dismantling Noolite consoles.
imageimage
(before and after)

(various other photos of disassembled and assembled devices can be found in the above article )

What is visible inside?

Inside the board you can see three large touch pads that implement the touch buttons.
An antenna at 433MHz is drawn on the motherboard in the upper right quarter.
The big round thing on the left is a lithium battery. To her minus we soldered for convenience the wire.

The scheme on the right implements all the logic and the transmitting part. Microcircuit in the case with 8 legs - PIC12F microcontroller.
The button to the right of the microcontroller is visible, and even to the right is the entire analog part.

The analog part is a transmitter that transmits data in called OOK modulation. The transmitter is fully analog and assembled on discrete components. The carrier frequency at 433.92 MHz is set by the SAW resonator , which is indicated in the diagram by ZQ1.

Transmission scheme

In such devices, in the overwhelming majority of cases, the so-called OOK modulation is used . OOK stands for "on-off keyring", i.e. such a subtype of amplitude modulation, in which the modulation is actually carried out on-off transmitter. Thus, the presence of a signal at a certain time instant at a frequency of 433.92 MHz means a logical unit, and the absence means a logical zero.

As stated above, the radio transmitter circuit is very simple and completely analog. A digital signal (a sequence of zeros and ones that is sent on the radio) comes from the leg marked in red in the photo. Accordingly, the presence of voltage on the leg turns on the transmitter, the absence turns it off.

We remove the data



We decided not to take the data using a radio receiver, but simply by recording the signal from the digital output of the chip. This approach is noticeably simpler, since you can work directly with the digital signal and it is free from any kind of interference.

Remove the signal will be a logical analyzer. Roughly speaking, a logic analyzer is an oscilloscope that has a one-bit ADC, i.e. can distinguish only the presence and absence of voltage in the channel. Logic analyzers are used, oddly enough, to analyze digital signals.

In our work, we use a logical analyzer. Open Bench Logic Sniffer is an open project with open firmware and open software. It costs only $ 50 and has a decent capture speed of 50 million samples per second. The used client application OLS is written in java and cross-platform.

In principle, for the purposes of this article, much cheaper logic analyzers on the Cypress CY7C68013A chip (search by the keywords “saleae clone” or “usbee clone”), which cost the Chinese something like $ 7, would be suitable.

image
(Layout with the actual logical analyzer, arduinka and RFM69H radio module used in Wiren Board Smart Home )

Dumps



So, we brought the signal with the marked red legs to the first channel of the logic analyzer, connected the ground and set up a trigger in the analyzer client. This is what happened:
image

In the picture there is a package dump, which forwards the NooLite remote by radio when a button is pressed.
What can you say on this picture? Actually quite a lot:

The minimum length of ones and zeros is the same and is 500us. Data is transmitted at the physical level at a frequency of 2000 bit / s.

At the beginning of the transmission package, a large piece of a rectangular meander is visible. This is the so-called preamble, which helps the receiver to adjust the frequency of data transmission (bit rate) for a specific receiver. Both the receiver and the transmitter use the built-in RC generators to clock the microcontroller, so the “2000 baud” of the receiver and transmitter can differ by 10 percent.

After the pre-memory the data begins. The data follows a very recognizable Manchester coding pattern: in the code there are only pairs of long zero-long units and short zero-short units. “Long” sections with a constant level are exactly twice as long as short ones, i.e. represent two "physical" bits. There are no sequences of zeros or ones longer than 2 bits in the package.
Such a pattern is obtained if the source data is encoded as follows: transmit two bits, “01” for each unit and “10” for each zero. What is the Manchester code is described in detail in Wikipedia, but if very briefly, it is used for two things: firstly, it allows you to get rid of the constant component of the signal, and secondly, does not require the frequency of the receiver and transmitter, allowing you to restore the frequency from the received signal.

Another observation regarding data: in the packet, the same data block is transmitted twice. This is done apparently to reduce the chances of packet damage due to interference and interference (recall, Noolite does not confirm message delivery)

image
(two identical data packages, larger)

We digitize data



Unfortunately, in the software for the logic analyzer there are quite few possibilities for analyzing arbitrary protocols. We did not manage to make Manchester coding decoder work in it, so we will write our own.

To begin with, we export data in the form of csv. The time of each change of our signal will be recorded in the file.
They look like this
The first column is time, the last column is the state of our channel.
"timestamp (abs)","timestamp (rel)","sample rate (Hz)","Channel-7","Channel-6"," Channel-5","Channel-4","Channel-3","Channel-2","Channel-1","Channel-0" 0,-2454,100000,0,0,0,0,0,0,0,0 2456,2,100000,0,0,0,0,0,0,0,1 2505,51,100000,0,0,0,0,0,0,0,0 2555,101,100000,0,0,0,0,0,0,0,1 2605,151,100000,0,0,0,0,0,0,0,0 2655,201,100000,0,0,0,0,0,0,0,1 2704,250,100000,0,0,0,0,0,0,0,0 2755,301,100000,0,0,0,0,0,0,0,1 2804,350,100000,0,0,0,0,0,0,0,0 2854,400,100000,0,0,0,0,0,0,0,1 2904,450,100000,0,0,0,0,0,0,0,0 2954,500,100000,0,0,0,0,0,0,0,1 3003,549,100000,0,0,0,0,0,0,0,0 3054,600,100000,0,0,0,0,0,0,0,1 3103,649,100000,0,0,0,0,0,0,0,0 3153,699,100000,0,0,0,0,0,0,0,1 3203,749,100000,0,0,0,0,0,0,0,0 



image
(picture from wikipedia)

The decoding logic of the Manchester code is very simple: you can see that the long gap between the edges encodes a data bit that differs from the previous one. If the next data bit does not differ from the previous one, it will be encoded by two short intervals between the edges.

Relevant Python Code
 #coding: utf-8 import csv import sys, time reader = csv.DictReader(open(sys.argv[1])) #~ reader.next() channel = 'Channel-0' delays = [] freq = None prev_ts = None state = False out_short = True def print_packet(packet): checksum = False line = '' for bit in packet: line+= '1' if bit else '0' line+= ',' checksum ^= bit #~ print line, "=", checksum print line packet = [] packets = [] prev_val = None for row in reader: freq = int(row['sample rate (Hz)']) ts = int(row['timestamp (abs)']) val = int(row[channel]) #~ print row if val == prev_val: continue prev_val = val if prev_ts: delay = 1.0/freq * (ts-prev_ts) if delay > 1000E-6 * 1.3: #~ print "pause" state = False packets.append(packet) packet = [] out_short = True elif delay > 500E-6 * 1.3: #~ print "long" state = not state packet.append(state) out_short = True else: #~ print "short" # short out_short = not out_short if out_short: packet.append(state) prev_ts = ts #~ for packet in packets: #~ print_packet(packet) print sys.argv[1],",", #~ assert packets[0] == [0,]*37 #~ assert (packets[1] == packets[2]) #~ print packets print_packet(packets[1]) print_packet(packets[2]) # 5       .     #         



Analysis


Press different buttons on different consoles, write down what happens. In the process, we noticed that sending commands for dimming is done in an original way: when you start pressing the button, the remote sends one command (“start increasing brightness”), when released, sends another one (“finish increasing brightness”).

We collect statistics in the tablet:
image

Some help is given by the documentation on the original Noolite modules for the computer.

First, take the command codes from the documentation and try to find them in the bit stream. The command code is found in 4 bits, starting with the second. The code is written under the LSB scheme, i.e. the first significant digit backwards.

In addition to the command, two bytes of the address are easily distinguished (they remain unchanged for any commands of one console) and filled with zeros of the “argument”. At repeated sending of the same command between the two possible values, the value of the first bit and the last 8 bits changes.

All parcels have constant parity, i.e. The xor of all bits gives the same value. This behavior hints at having a checksum.

Check sum


The first idea was that parity is used as a checksum, which is written to the first bit of the parcel. This version was supported by the parity of the parcel that was preserved and that the first bit was set apart from the other bits in the packet.
However, attempts to figure out the value of the last byte did not lead to anything good.

The next idea, which turned out to be correct, was that the checksum is the last 8 bits. The first bit is used so that the receiver can distinguish two consecutive button presses from one (as we said, with one press, the package is repeated several times for reliability).

The first candidate for the checksum is of course the CRC . However, the CRC-8 algorithm generally has three 8-bit parameters, and the enumeration of a pair of frequently used combinations did not lead to success.

We send commands


It was decided to try to generate Noolite packets and check their “validity” with the help of an execution unit that flashes when receiving a valid packet.

To do this, we put a simple sketch for arduino, which was connected to the input of the analog part of the disassembled console but no olight.

Sketch looks like this
 #define UINT8 unsigned char // Pin 13 has an LED connected on most Arduino boards. // give it a name: int gpio = 12; // the setup routine runs once when you press reset: void setup() { // initialize the digital pin as an output. pinMode(gpio, OUTPUT); Serial.begin(115200); } const unsigned int PERIOD = 500; //usec void send_sequence(int count, unsigned char * sequence) { char clock = 1; for (int i = 0; i < count; ++i) { char data = sequence[i]; clock = !clock; digitalWrite(gpio, clock ^ (!data)); delayMicroseconds(PERIOD); clock = !clock; digitalWrite(gpio, clock ^ (!data)); delayMicroseconds(PERIOD); } } // Automatically generated CRC function // polynomial: 0x131, bit reverse algorithm UINT8 crc8_maxim(UINT8 *data, int len, UINT8 crc) { static const UINT8 table[256] = { 0x00U,0x5EU,0xBCU,0xE2U,0x61U,0x3FU,0xDDU,0x83U, 0xC2U,0x9CU,0x7EU,0x20U,0xA3U,0xFDU,0x1FU,0x41U, 0x9DU,0xC3U,0x21U,0x7FU,0xFCU,0xA2U,0x40U,0x1EU, 0x5FU,0x01U,0xE3U,0xBDU,0x3EU,0x60U,0x82U,0xDCU, 0x23U,0x7DU,0x9FU,0xC1U,0x42U,0x1CU,0xFEU,0xA0U, 0xE1U,0xBFU,0x5DU,0x03U,0x80U,0xDEU,0x3CU,0x62U, 0xBEU,0xE0U,0x02U,0x5CU,0xDFU,0x81U,0x63U,0x3DU, 0x7CU,0x22U,0xC0U,0x9EU,0x1DU,0x43U,0xA1U,0xFFU, 0x46U,0x18U,0xFAU,0xA4U,0x27U,0x79U,0x9BU,0xC5U, 0x84U,0xDAU,0x38U,0x66U,0xE5U,0xBBU,0x59U,0x07U, 0xDBU,0x85U,0x67U,0x39U,0xBAU,0xE4U,0x06U,0x58U, 0x19U,0x47U,0xA5U,0xFBU,0x78U,0x26U,0xC4U,0x9AU, 0x65U,0x3BU,0xD9U,0x87U,0x04U,0x5AU,0xB8U,0xE6U, 0xA7U,0xF9U,0x1BU,0x45U,0xC6U,0x98U,0x7AU,0x24U, 0xF8U,0xA6U,0x44U,0x1AU,0x99U,0xC7U,0x25U,0x7BU, 0x3AU,0x64U,0x86U,0xD8U,0x5BU,0x05U,0xE7U,0xB9U, 0x8CU,0xD2U,0x30U,0x6EU,0xEDU,0xB3U,0x51U,0x0FU, 0x4EU,0x10U,0xF2U,0xACU,0x2FU,0x71U,0x93U,0xCDU, 0x11U,0x4FU,0xADU,0xF3U,0x70U,0x2EU,0xCCU,0x92U, 0xD3U,0x8DU,0x6FU,0x31U,0xB2U,0xECU,0x0EU,0x50U, 0xAFU,0xF1U,0x13U,0x4DU,0xCEU,0x90U,0x72U,0x2CU, 0x6DU,0x33U,0xD1U,0x8FU,0x0CU,0x52U,0xB0U,0xEEU, 0x32U,0x6CU,0x8EU,0xD0U,0x53U,0x0DU,0xEFU,0xB1U, 0xF0U,0xAEU,0x4CU,0x12U,0x91U,0xCFU,0x2DU,0x73U, 0xCAU,0x94U,0x76U,0x28U,0xABU,0xF5U,0x17U,0x49U, 0x08U,0x56U,0xB4U,0xEAU,0x69U,0x37U,0xD5U,0x8BU, 0x57U,0x09U,0xEBU,0xB5U,0x36U,0x68U,0x8AU,0xD4U, 0x95U,0xCBU,0x29U,0x77U,0xF4U,0xAAU,0x48U,0x16U, 0xE9U,0xB7U,0x55U,0x0BU,0x88U,0xD6U,0x34U,0x6AU, 0x2BU,0x75U,0x97U,0xC9U,0x4AU,0x14U,0xF6U,0xA8U, 0x74U,0x2AU,0xC8U,0x96U,0x15U,0x4BU,0xA9U,0xF7U, 0xB6U,0xE8U,0x0AU,0x54U,0xD7U,0x89U,0x6BU,0x35U, }; while (len > 0) { crc = table[*data ^ (UINT8)crc]; data++; len--; } return crc; } void convert_to_buf(unsigned char val, unsigned char* buf) { unsigned char mask = 1; for (int i = 0; i < 8; ++ i) { if (val & mask) { buf[i] = 1; } else { buf[i] = 0; } mask = mask << 1; } } unsigned char calc_checksum(int count, unsigned char * sequence) { unsigned char data[] = {0,0,0,0}; unsigned char mask ; // first byte from 1 to 5 bit (0-based) for (int i=1; i < 6; ++i) { if (sequence[i]) { //bit 1 to 2**3 mask mask = 1 << (i + 2); data[0] |= mask; } } for (int byte_n=0; byte_n < 3; ++byte_n) { // [] = 6 + byte_n * 8 + i for (int i=0; i < 8; ++i) { if (sequence[6 + byte_n * 8 + i]) { mask = 1 << i; data[byte_n + 1] |= mask; } } } return crc8_maxim(data, 4, 0); } unsigned char preamble[] = {1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1}; unsigned char command[] = {1, 0, 1,0,0,1, 1,0,0,1,0,1,1,0, 0,0,1,0,0,0,0,1, /*1,1,1,1,1,0,0,0, 0,0,0,0,0,0,0,0,*/ 0,0,0,0,0,0,0,0, }; // the loop routine runs over and over again forever: void loop() { unsigned char checksum[] = {0,0,0,0,0,0,0,0}; for (unsigned int addr_lo = 0; addr_lo < 256; ++addr_lo) { for (unsigned int addr_hi = 0; addr_hi < 256; ++addr_hi) { Serial.println(addr_hi); Serial.println(addr_lo); convert_to_buf(addr_hi, command + 6); convert_to_buf(addr_lo, command + 6 + 8); unsigned char checksum_val = calc_checksum(sizeof(command), command); convert_to_buf(checksum_val, checksum); // Serial.println(checksum_val); // if (checksum_val==198) continue; delay(10); send_sequence(sizeof(preamble), preamble); digitalWrite(gpio, LOW); delayMicroseconds(500 * 3); send_sequence(sizeof(command), command); send_sequence(sizeof(checksum), checksum); digitalWrite(gpio, LOW); delayMicroseconds(500 * 3); send_sequence(sizeof(command), command); send_sequence(sizeof(checksum), checksum); digitalWrite(gpio, LOW); delayMicroseconds(500 * 3); digitalWrite(gpio, LOW); } } // command[1] = !command[1]; // Serial.println(command[1]); // delay(4000); while (1) {}; } 



Here we are simply issuing edicles and zeroes to the line using digitalWrite (). The duration of the pulses is adjustable delay.

After we checked that we can reproduce one of the recorded packets, and the noolight block takes it as its own, we began further experiments.

Checksum 2

As it was written above, it was necessary to understand the algorithm for generating the checksum.
The CRC algorithm has a good property that you can check: this checksum is linear in the arguments.

image

Thus, if you take two well-known packages, add them up bitwise (that is, make xor), then the resulting package will have the correct checksum!

Having chosen two suitable packages, we check this assumption and confirm that the check sum is linear in the arguments.

Checksum 3

Our next action was brutfors checksums. Using a binary search for a given sequence of bits, the checksum is selected in a few minutes. This is done in an obvious way: run the sketch, sorting the checksums from 1 to 128 and look at the receiver. If during the work of the sketch he blinked (received a valid package), then we know that the required checksum is somewhere from 1 to 128. And so on.

Now that we can determine the checksums of an arbitrary package, we can try to restore the function. Since CRC is linear in the arguments, then, knowing how the cheksum changes as each bit changes, you can restore the function.

To restore the function, it is necessary to select the checksums to the number of packets equal to the number of bits in the packet, i.e. to 29 packets.
image

This is done all slowly and had enough time to get bored. So, having passed more than half of the bits, we decided to quickly try to sort out the cheksumma offline.

Checksum 4

As stated above, the CRC has several input parameters: a polynomial (8-bit), an initial value (8-bit), a value that is added at the end (8-bit). In addition, 1 bit - sets whether the algorithm is inverted or not.

In addition to these parameters, it is possible to prepare bytes in different ways with which the CRC algorithm operates. In the noolite package, 29 bits, i.e. non-integer number of bytes. The question arises, how to form the first byte. In addition, each byte can be inverted when calculating the CRC. Moreover, theoretically, not only bits in a byte, but also bytes in pairs (words) can turn over.

Let's go through all this with brute force. For brute force, we used Python and the crcmod library.

sample code
 import crcmod samples = [ #~ ['\x00\x00\x01' + chr(0b11110000), chr(0b11011010)], #~ ['\x00\x00\x03' + chr(0b11110000), chr(0b10010101)], [chr(0b11110000) + '\x01\x00\x00', chr(0b11011010)], [chr(0b11110000) + '\x03\x00\x00', chr(0b10010101)], [chr(0b11110000) + chr(0b1) + chr(0b1) + '\x00', chr(0b00011110)], [chr(0b11111000) + chr(0b1) + chr(0b1) + '\x00', chr(0b00000010)], #~ [chr(0b11111000) + chr(0b1) + chr(0b1) + '\x00', chr(0b00000010)], ] #~ predef = crcmod.predefined.mkPredefinedCrcFun('crc-8-maxim') predef = crcmod.Crc(256 + 0x31,initCrc=0x00,rev=True) for data, checksum in samples: print "="*10 for poly in xrange(255): for init_crc in (0, 0xff): for rev in (True, False): digest = crcmod.Crc(256 + poly,initCrc=init_crc,rev=rev).new(data).digest() if digest == checksum: print poly, init_crc, rev for data, checksum in samples: print "expected: ", hex(ord(checksum)) print predef.new(data).hexdigest() import sys print predef.generateCode("crc8_maxim", sys.stdout) 



Function found! This is the “crc8_maxim” scheme, the first 5 bits are first achieved with zeros to the left. Then all bytes are written to the LSB, i.e. roll over.

Python Algorithm Code
 crc8_table = [ 0x00,0x5E,0xBC,0xE2,0x61,0x3F,0xDD,0x83, 0xC2,0x9C,0x7E,0x20,0xA3,0xFD,0x1F,0x41, 0x9D,0xC3,0x21,0x7F,0xFC,0xA2,0x40,0x1E, 0x5F,0x01,0xE3,0xBD,0x3E,0x60,0x82,0xDC, 0x23,0x7D,0x9F,0xC1,0x42,0x1C,0xFE,0xA0, 0xE1,0xBF,0x5D,0x03,0x80,0xDE,0x3C,0x62, 0xBE,0xE0,0x02,0x5C,0xDF,0x81,0x63,0x3D, 0x7C,0x22,0xC0,0x9E,0x1D,0x43,0xA1,0xFF, 0x46,0x18,0xFA,0xA4,0x27,0x79,0x9B,0xC5, 0x84,0xDA,0x38,0x66,0xE5,0xBB,0x59,0x07, 0xDB,0x85,0x67,0x39,0xBA,0xE4,0x06,0x58, 0x19,0x47,0xA5,0xFB,0x78,0x26,0xC4,0x9A, 0x65,0x3B,0xD9,0x87,0x04,0x5A,0xB8,0xE6, 0xA7,0xF9,0x1B,0x45,0xC6,0x98,0x7A,0x24, 0xF8,0xA6,0x44,0x1A,0x99,0xC7,0x25,0x7B, 0x3A,0x64,0x86,0xD8,0x5B,0x05,0xE7,0xB9, 0x8C,0xD2,0x30,0x6E,0xED,0xB3,0x51,0x0F, 0x4E,0x10,0xF2,0xAC,0x2F,0x71,0x93,0xCD, 0x11,0x4F,0xAD,0xF3,0x70,0x2E,0xCC,0x92, 0xD3,0x8D,0x6F,0x31,0xB2,0xEC,0x0E,0x50, 0xAF,0xF1,0x13,0x4D,0xCE,0x90,0x72,0x2C, 0x6D,0x33,0xD1,0x8F,0x0C,0x52,0xB0,0xEE, 0x32,0x6C,0x8E,0xD0,0x53,0x0D,0xEF,0xB1, 0xF0,0xAE,0x4C,0x12,0x91,0xCF,0x2D,0x73, 0xCA,0x94,0x76,0x28,0xAB,0xF5,0x17,0x49, 0x08,0x56,0xB4,0xEA,0x69,0x37,0xD5,0x8B, 0x57,0x09,0xEB,0xB5,0x36,0x68,0x8A,0xD4, 0x95,0xCB,0x29,0x77,0xF4,0xAA,0x48,0x16, 0xE9,0xB7,0x55,0x0B,0x88,0xD6,0x34,0x6A, 0x2B,0x75,0x97,0xC9,0x4A,0x14,0xF6,0xA8, 0x74,0x2A,0xC8,0x96,0x15,0x4B,0xA9,0xF7, 0xB6,0xE8,0x0A,0x54,0xD7,0x89,0x6B,0x35, ] def crc8_maxim(data): crc = 0 for i, ch in enumerate(data): crc = crc8_table[ord(ch) ^ crc] return crc 



Subtotal


Now we know almost everything about the protocol and can generate arbitrary on, off, start brightness adjustment and end brightness adjustment commands with arbitrary address values, emulating arbitrary Noolite consoles.

This, however, is not quite enough. The fact is that among these commands there is no “set brightness to level X” command, which is very inconvenient when used with the smart home system. It is rather strange to control the brightness with the help of the delay between two commands, as it is done in conventional Noolite consoles.

At the same time, the documentation for the NooLite computer modules shows that such commands exist. For example, the documentation for the command with code 6 says " value = 6 - set the brightness specified in" Data Byte 0 ", set the brightness specified in Data Byte 0, 1, 2 ".
The natural contender for "Data Byte 0" is the penultimate byte in the packet, which in our experiments was always zero. However, attempts to send a command in which this non-zero byte was unsuccessful. Apparently, the format of the parcel when sending commands with arguments is different.

Extended Commands


To deal with the commands with arguments and put an end to the parsing of the Noolite protocol, you need native NooLite modules.

(I would immediately like to thank the thinking-home.ru store and the dima117 habrovich person for the promptly provided devices for this)

If you have a native module, you can send commands, for example, using this program .

In addition to the command to set the specified brightness, there are also commands that control the mode of switching the brightness and color of RGB blocks NooLite, as well as setting the value of color (RGB).

This time, we will intercept commands using the RFM69H packet radio, which is installed in Wiren Board Smart Home using our protocol parsing code .

What happened:

image

It can be seen that the last byte, which was zero in our experiments, is in fact the choice of format. We observed fmt = 0, except that values ​​of 1, 3 and 4 are possible.

Format 1 is used for the brightness setting command, with one byte with a brightness value added to the beginning of the packet.
Format 3 is used for the color setting command, 4 bytes are added to the beginning of the packet. The first three set the color components, the fourth is always zero, its value is unclear (apparently reserved).
Format 4 is used for mode switching commands. In this format, 4 bits of the argument are added to the beginning of the packet after the command. The entire packet is shifted to calculate the checksum, i.e. bytes are counted from the left border, the remaining bits are padded with zeros to a byte.

Total



Total, we completely understood how the Noolite protocol works. What can you say about the protocol:

  1. Preamble and Manchester coding are used, this is good practice
  2. Only 16 bits of the address are used. This is certainly better than 4-8 bits in very cheap Chinese radio sets, but still quite a bit. For example, new Chinese chips for radio controls have a code length of 21 bits. 16 bits are only 65,536 possible combinations, which allows you to sort them out pretty quickly. If you have a powerful transmitter and a good antenna, for example, you can start sending a command to turn off the light in a loop with all possible addresses. Such a thing will sort out all the addresses in a couple of hours and is guaranteed to turn off the light of all your neighbors. And since the connection is one-way, the number of neighbors by which you can turn off the light is limited only by the power of your transmitter.
  3. The redundancy in the protocol is implemented by re-sending the packet and monitoring its integrity. Availability of integrity control is an absolute advantage of the protocol. On the other hand, redundant coding could be implemented in a more compact way, although in the case of the transmission of short packets by radio, this may not be an advantage
  4. The protocol completely lacks any protection. In this case, it would be quite possible to use the code type KeeLoq - this is the code, which, roughly speaking, is based on the synchronization of the counters in the transmitter and the receiver, is used in car key rings. You can even implement something similar on hardware, which is now used in NooLite
  5. A rather strange package structure is used. The choice of format (that is, including the length of the package) is encoded by the last byte of the packet. Thus, it is necessary to separate packages at the physical level. More logical (and often used) is the encoding of the packet length at the very beginning of this packet. It is also not very clear why the packet is not trimmed at the byte boundary. Quite a strange saving on three bits, given that as much as 8 bits are allocated for the format code.


Although this is not related to the protocol itself, we found the results of the opening of pocket radio consoles interesting:
autopsy revealed
Actually radio control (pictures from the article ):
image
He, disassembled:
image
But the board on the other hand:
image

The fee is not only that in the traditional Chinese version (i.e. it is fouled with flux), it also says “livolo.com” on it.
Needless to say, the livolo consoles look identical ? At the same time they cost less than $ 5 .

Apparently, the comrades NooLite developers buy ready-made livolo consoles and reflash them with their firmware, since the console is made on the PIC16 mic, and the connector for programming it is conveniently displayed on the board. I must say, a very smart decision!


PS


NooLite manufacturers are going to release the MT1132 module with a UART interface, which is intended for use with Arduino, etc. or in their own devices.

We heard rumors that it would be more adequate to cost than native USB charms .

Uncovered advertising



Actually, as you probably already understood, the NooLite protocol was analyzed in order to add support to Wiren Board Smart Home - our home automation controller with Linux on board and great opportunities for connecting peripherals, which, in particular, has a universal transceiver at 433MHz.

We send the order for the first batch to the plant this Wednesday, so we decided to extend the pre-order for three days until Tuesday, March 18, inclusive . You can buy a controller (shipping at the end of April - early May) in our store .

Source: https://habr.com/ru/post/216023/


All Articles