Broadlink smart home devices complete protocol hack

Ipsum Domus
Smart Home DIY
Published in
15 min readMar 6, 2017

--

Finally we got into the stage when we can actually add smart devices to DIY smart home hub. We will start with one of most famous and cost effective products from Broadlink (@broadlinkdna).

Broadlink SP products

Those smart plugs have WiFi connectivity and can be managed by Broadlink RM device and/or e-Control application for iPhone or Android. We are not going to use either of those, but only our own code.

We will use Broadlink Smart Socket as an example hardware, but it works the same for any one of Broadlink wifi managed devices.

In this article we will cover only theoretical part of the broadlink protocol. Next article will cover entire source code running on Node JS.

Step 1 : Setup for configuration and pairing

First of all, we need to configure smart socket. To do that:

  1. Press and hold button until blue light start blinking quick (about 5 seconds) — This is reset command
  2. Press and hold button again until blue light start blinking with pauses of about 1 seconds — This is pair command

Now you will find new wifi network named BroadlinkProv. This is your configuration access point.

Step 2 : Hacking Broadlink protocol

Broadlink uses QUIC UDP protocol to communicate over network. The same protocol works when you are connected to the configuration access point and to you network. There are two options to work over QUIC — public and not public. The differentiation is second bit of the first byte of the message.

All messages can be broadcasted or send to the specific IP. In both cases, Broadlink uses port 80 for communication.

Broadlink message structure

When public bit set to low (0), message structure will be following:

|        0         |         1          |      2       |     …     |
|------------------|--------------------|--------------|-----------|
| Public Flags | Packet Number | Payload(...) |
  • Where public flag is 0x00
  • Packet number is 0x00

So we have “static” first 2 bytes header, and then Payload from byte 2 and beyond.

When public bit set to high (1), message structure will be following:

|      0       |  1   |  2   |  …  | 8  |  -- continue -->
|--------------|------|------|-----|----|
| Public Flags | Connection ID |
| 9 | ... | 12 | 13 | 14 | ... | 17 | 18 | ... |
|---|-----|----|--------|------|-----|----|--------------|---------|
| Tag | Tag ID | Padding | Payload(...) |
  • Where public flag is 0x5a — Version:No, Reset:Yes, CID:0x2,Packet #1, Multipath:Yes
  • Connection ID is 24113000182295205 (0xa5aa555aa5aa5500)
  • Tag — 0x00
  • Tag ID — 0x00
  • and padding obviously 0x00

So we have “static” first 17 bytes header, then starting from byte 18 will come payload.

Broadlink message header comes straight after QUIC message header and has following structure

For messages with low Public flag, header actually not exists. We have only 8 bytes QUIC header (eventual mostly zeros), but some of header values should be merged into (minimum data packet length should be 40 bytes)

32 | 33 |..| 36 | 37 |    38     |39|40|...
checksum| |device id|command id | | |...

While for messages with high Public flag, header would be

32 | 33 |..| 36 | 37 |    38     |39|  40  |  41 |42|...|45|   ->
checksum| |device id|command id | |send counter| mac |
|46|47|48|...|51| 52 | 53 |54|...|56|57|58| ...
| | |target id|payload id| | | |Payload (...)
  • Total Broadlink header length is 56 bytes for Public (targeted) messages
  • bytes 32–33 (0x20–0x21) is always checksum for entire message. It being calculated after all manipulations on data (including encryption) is performed. Checksum calculation done by using Magic of 0xbeaf as following:
var _cs = (buffer) => (0xbeaf + Array.prototype.slice.call(buffer,0).reduce((p,c)=> (p+c))) & 0xffff;
  • Then on bytes 36–37 (0x24) we have device id. Ipsum Domus is using 0x7D00, Broadlink values are following:
0: 'SP1'; 
0x2711: 'SP2';
0x2719 or 0x7919 or 0x271a or 0x791a: 'Honeywell SP2';
0x2720: 'SPMini';
0x753e: 'SP3';
0x2728: 'SPMini2';
0x2733 or 0x273e: 'SPMini OEM';
0x2736: 'SPMiniPlus';
0x2712: 'RM2';
0x2737: 'RM Mini';
0x273d: 'RM Pro Phicomm';
0x2783: 'RM2 Home Plus';
0x277c: 'RM2 Home Plus GDT';
0x272a: 'RM2 Pro Plus';
0x2787: 'RM2 Pro Plus2';
0x278b: 'RM2 Pro Plus BL';
0x278f: 'RM Mini Shate';
0x2714: 'A1';
0x4EB5: 'MP1';
>= 0x7530 and <= 0x7918: 'SPMini2 OEM';
  • On byte 38 (0x26) we will have commands, based on following request IDs
Hello       : [0x6]
Discover : [0x1a]
Join : [0x14]
Auth : [0x65]
Command : [0x6a]
  • For those we will have correlated response IDs (will arrive as part of response
Hello       : [0x7]
Discover : [0x1b]
Join : [0x15]
Auth : [0x3e9]
Command : [0x3ee]
  • For messages with public high, following additional data fields
  • On byte 40 (0x28) — two bytes send counter. This values is used to correlate request attempt to the response
  • On byte 42 (0x2a) we will have device (source) MAC address in “Little-Endian” format as following
var m = message.target.mac.split(':').reverse();
var offset = 42;
for(var i=0;i<m.length;i++){
packet.writeUInt8(parseInt(m[i],16),offset++);
}
  • On byte 48 (0x30) we place four bytes target device id (it is set to 0x0 at the beginning. You will get valid target device id as response to Auth command. Then you should start using it.
  • On byte 52 (0x34) we place payload checksum. Checksum calculation is the same as for entire message. The only difference is that you should calculate it for payload (before encryption) only.
  • Then you encrypt payload if required and append it at the end of the custom header (from byte 57 in case of public high).

Data encryption should be done by using AES-128 CBC algorithm with no padding. Initial values will be as following

Initial key: [0x09, 0x76, 0x28, 0x34, 0x3f, 0xe9, 0x9e, 0x23, 0x76, 0x5c, 0x15, 0x13, 0xac, 0xcf, 0x8b, 0x02]
Initial vector: [0x56, 0x2e, 0x17, 0x99, 0x6d, 0x09, 0x3d, 0x28, 0xdd, 0xb3, 0xba, 0x69, 0x5a, 0x2e, 0x6f, 0x58]

Here is the full JavaScript source code of message formatter for Broadlink protocol

BLK.getPacket = (message, deviceId = 0x7D00 ,currentCID = [0xa5,0xaa,0x55,0x5a,0xa5,0xaa,0x55,0x0]) =>{
if(!message.payload) message.payload = Buffer.alloc(0);
var isBroadcast = !message.target || !message.target.ip || message.target.ip == '255.255.255.255' || message.target.ip == '224.0.0.251';
//QUIC header
if(isBroadcast || message.isPublic){
var packet = Buffer.alloc(8); //0x8 padding, Public flag = 0x0
//multicast - PKN:0
//merge payload right away
if(message.payload.length < 40){ // minimum payload length
var filler = Buffer.alloc(40 - message.payload.length);
message.payload = Buffer.concat([message.payload,filler]);
}
packet = Buffer.concat([packet,message.payload]);
} else {
var packet = Buffer.alloc(56); //0x38 total custom header length
packet.writeUInt8(0x5a,0); //Version:0, Reset:Yes, CID:0x2,Packet #1, Multipath:Yes
var cid = Buffer.from(message.target.CID || currentCID);
cid.copy(packet,1);
//tag:0x0, tag #:0x0, padding: 0
}
packet.writeUInt16LE(deviceId,36); // 0x24 : Device ID
packet.writeUInt8(message.command[0],38); // 0x26 : Command

if(!isBroadcast && !message.isPublic) {
BLK.mgs = BLK.mgs || 0;
packet.writeUInt16LE(BLK.mgs++ & 0xFFFF, 40); // 0x28 : Send Counter

if(message.target.mac) {
var m = message.target.mac.split(':').reverse();
var offset = 42; // 0x2a : MAC
for(var i=0;i<m.length;i++){
packet.writeUInt8(parseInt(m[i],16),offset++);
}
}
if(message.target.id){
packet.writeUInt32LE(48); // 0x30 : Device ID
}
if(message.payload.length > 0) {
var cs = _cs(message.payload);
packet.writeUInt16LE(cs,52); // 0x34 : Header Checksum
}
if(message.isEncrypted){

BLK.key = Buffer.from([0x09, 0x76, 0x28, 0x34, 0x3f, 0xe9, 0x9e, 0x23, 0x76, 0x5c, 0x15, 0x13, 0xac, 0xcf, 0x8b, 0x02]);
BLK.iv = Buffer.from([0x56, 0x2e, 0x17, 0x99, 0x6d, 0x09, 0x3d, 0x28, 0xdd, 0xb3, 0xba, 0x69, 0x5a, 0x2e, 0x6f, 0x58]);
var key = message.target.key || BLK.key;
var cipher = crypto.createCipheriv('aes-128-cbc', key, BLK.iv);
cipher.setAutoPadding(false);
message.payload = Buffer.concat([cipher.update(message.payload),cipher.final()]);
}

packet = Buffer.concat([packet,message.payload]);
}
var cs = _cs(packet);
packet.writeUInt16LE(cs, 32); // 0x20 : Full checksum
return packet;
}

Reading Broadlink response

Protocol response messages are in the same format like requests, so to read it, start from header (minimum 48 bytes), then read and validate checksum on byte 32

var cs = buffer.readUInt16LE(32);
buffer.writeUInt16LE(0x0,32);
if(_cs(buffer) != cs){
console.log('ERR | Wrong incoming message format : ',JSON.stringify(buffer));
return null;
}

Read command ID, device type and source MAC from correlated locations

var command = buffer.readUInt16LE(38);
var device = _readDeviceType(buffer);
var srs = _readMac(buffer,42);

All the rest is payload. Now we can process to learn and understand the protocol flow

Broadlink protocol flow

In order to detect Broadlink product on your network, you should start from broadcasting Hello (0x6, public low) message on port 80 to 255.255.255.255 and 224.0.0.251 (if multicast is not supported). As response, you should expect to get message 0x7.

The payload for this message (payload size is 40) is very straight forward:

  • 4 bytes of timezone offset
  • 2 bytes of full year
  • Byte of Seconds
  • Byte of Minutes
  • Byte of Hours
  • Byte of Day
  • Byte of week day (0–7)
  • Byte of Month
  • 4 bytes of your host IP address
  • 2 bytes of source port
var buffer = Buffer.alloc(40);var d = new Date();
var os = d.getTimezoneOffset()/60 * -1;
buffer.writeUInt32LE(os,0);
buffer.writeUInt16LE(d.getFullYear(),4);
buffer.writeUInt8(d.getSeconds(),6);
buffer.writeUInt8(d.getMinutes(),7);
buffer.writeUInt8(d.getHours(),8);
buffer.writeUInt8(d.getDay(),9);
buffer.writeUInt8(d.getDate(),10);
buffer.writeUInt8(d.getMonth(),11);
buffer.writeInt32LE(_parseIp(ip), 16);
buffer.writeUInt16LE(port, 20);

As response you will get message which will have

  • device IP address on byte 54
  • device MAC address on byte 58
  • Null terminated device title on byte 64
var res = {};res.ip = _readIp(buffer, 54);
res.mac = _readMac(buffer,58);
res.kind = _readKind(buffer,64);

Now you know device ip address and it kind, so we can process with configuration. Step two is to get all wifi networks, which this device sees join it to one of those.

!!! IMPORTANT (PRIVACY and SECURITY): All Broadlink devices have internet facing channel. It works over TCP and web sockets with the similar protocol like it does on your local network. We kindly recommend you NOT TO CONNECT those devices to your network and NOT TO EXPOSE it to Internet due to privacy and security concerns.

REMEMBER: You do not need internet connectivity to make those devices work!

Discover message is a message with no payload and code 0x1a. It used to esquire device about networks it sees and detect the security required for the configuration. You can skip this message, if you sure that the target device “sees” you network and know it security requirements.

Response to this message (code 0x1b) has a list of networks smart device “sees”. Payload of the message has following structure

  • On byte 48 number of visible networks
  • On byte 52 — list of networks where each entry (64 bytes) has following structure
  • Offset 0 — Name (SSID) of the network (32 bytes max, ASCII)
  • Offset 4 — Byte Number of characters in SSID
  • Offset 16 — Byte Key length
  • Offset 28 — Byte network encryption, where 0 — not encrypted, 1 — WEP ,2 — WPA1, 3 — WPA2, 4 — WPA2 personal(CCMP), 6 — WPA2 personal(TKIP). Those are bit flags, so first bit — encrypted or not, second — support for WEP, third — WPA2

Here how you can read the response

res.networks = [];
var offset = 48;
var c = buffer.readUInt8(offset);
offset += 4;
for(;offset<=c*64;offset+=64){
var l = buffer.readUInt8(offset + 32 + 4);
var n = {
ssid : buffer.toString('ascii',offset, offset + l),
key : buffer.readUInt8(offset + 32 + 4 + 12),
encryption : buffer.readUInt8(offset + 32 + 4 + 24)
};
res.networks.push(n);
}

Network discovery for the unit is very bad, so you can retry the request, until you will see your target network or just use following command to Join the target network

Join message (request ID 0x14). This message has 128 bytes payload which includes target SSID, target Password,wifi Authentication and Encryption types. This message might (as well as previous one) might be either Public low or high and sent on the configuration access point network.

  • On byte 60 — SSID of the target network in ascii encoding
  • On byte 92 — Password of the target network in ascii encoding
  • On byte 124 — Byte of length of SSID
  • On byte 125 — Byte of length of Password
  • On byte 126 — Target network security (value you got from Discovery message or generated 0-none, 1-wep, 2-wpa1,3-wpa2,4-wpa1/2 CCMP,6-wpa1/2 TKIP
var buffer = Buffer.alloc(128);
buffer.writeUInt8(ssid.length,124);
buffer.writeUInt8(pwd.length,125);
buffer.write(ssid,60,'ascii');
buffer.write(pwd,92,'ascii');
var s = security || ((!pwd || pwd=='')?0:4); //(0 = none, 1 = WEP, 2 = WPA1, 3 = WPA2, 4 = WPA1/2)
buffer.writeUInt8(s,126);

As response you will get message 0x15 with no payload. At the same time the device will remove configuration access point and connect to your network.

If connection succeeded, blue light will be turned off, if device is not able to connect, blue light will blink slowly (about once a second). To reset it, long press the button (blue light will start blink fast and start over)

Now our device is connected to the network, our next step will be Authentication of the device within your smart home network.

Auth message (message id 0x65), is public high message and should be sent to the target device. Payload length is 80 bytes and intended to provide broadlink device with your encryption public key to be used for your connection. As result, the device should provide device public key and this how you can security communicate. Unfortunately, there is a famous joke

The S in IoT stands for security

All you need to do is to put your public 16 bytes key to the payload of this message and … forget about it. Because most probably, device will send you back 16 bytes of 0x0 and ignore any of your attempts to use your key. You will have to encrypt all subsequent messages with silly key of 0x0.

var _decryptPayload = (buffer, key) => {
var data = Buffer.alloc(buffer.length - 56); // 0x38 : Encrypted payload
buffer.copy(data,0, 56, buffer.length);

var decipher = crypto.createDecipheriv('aes-128-cbc',key,BLK.iv);
decipher.setAutoPadding(false);
return Buffer.concat([decipher.update(data) , decipher.final()]);
};

Nevertheless, the payload for this particular message should be encrypted with predefined Broadlink key and IV (see above). For all other messages, you will use 0x0 (or if you lucky to get some other key from the device, whatever come there)

So, payload is simple

  • From byte 4–16 bytes of your key
  • Delimiter of 0x1 at byte 45
  • Null terminated name of your system at byte 48 in ascii.
var buffer = Buffer.alloc(80);
var key = crypto.randomBytes(16);//target.key || BLK.key;
key.copy(buffer,4); // 0x4 : Shared key (16 bytes)

buffer.writeUInt8(0x1,45); // 0x2d : 0x1
buffer.write('Home alone',48, 'ascii'); // 0x30 : Device name

Response with code 0x3e9 will bring you after decryption with default key device encryption key (bytes 4–20) and device id (at byte 0)

We done, and ready to go with management of your device. As example, we will provide SP1, SP2, SP2mini and SP3 commands for turn it on and off and get the switch status

Command (16 bytes message id 0x6a payload) always has Get (byte 0x1) or Set (byte 0x2) at byte 0 and state (On — 0x1 and Off — 0x0) at byte 4. So coding is very simple.

var buffer = Buffer.alloc(16);
buffer.writeUInt8((state != null)?2:1,0); // 0x0 : toggle->value=2, check->value=1
buffer.writeUInt8(state?1:0,4); // 0x4 : 1: on, 2: off

Response (message id 0x3ee) should be encrypted by using key provided by the device and will contain one byte state (0x1-on, 0x0-off on byte 4). But before, you should read two bytes response code on byte 34 and if it is 0x0- you are good, else, it was an error while reading or executing your command.

On Get command there is additional configuration information which is provided by the device, however, it is not interesting in terms of understanding how your device works.

We done with Broadlink. You can purchase their products from AliExpress

Here the full source code of NodeJS Broadlink module

'use strict'
const _blEvnt = 'brl:';
const crypto = require('crypto');
var BLK = {};BLK.Commands = {
Hello : [0x6,0x7],
Discover : [0x1a,0x1b],
Join : [0x14,0x15],
Auth : [0x65,0x3e9],
TogglePower : [0x6a,0x3ee]
};
BLK.Hello = {
Request : (ip, port) => {
var res = {
command : BLK.Commands.Hello,
};

if(ip && port) {
var buffer = Buffer.alloc(40);
var d = new Date();
var os = d.getTimezoneOffset()/60 * -1;
buffer.writeUInt32LE(os,0);
buffer.writeUInt16LE(d.getFullYear(),4);
buffer.writeUInt8(d.getSeconds(),6);
buffer.writeUInt8(d.getMinutes(),7);
buffer.writeUInt8(d.getHours(),8);
buffer.writeUInt8(d.getDay(),9);
buffer.writeUInt8(d.getDate(),10);
buffer.writeUInt8(d.getMonth(),11);
buffer.writeInt32LE(_parseIp(ip), 16);
buffer.writeUInt16LE(port, 20);
res.payload = buffer;
}
return res;
},
Response : (buffer) =>{
var res = {};
res.ip = _readIp(buffer, 54);
res.mac = _readMac(buffer,58);
res.type = _readType(buffer,64);

return res;
}
};
BLK.Discover = {
Request : (target) => {
var res = {
command : BLK.Commands.Discover,
target : target
};
return res;
},
Response : (buffer) =>{
var res = { };

res.networks = [];
var offset = 48;
var c = buffer.readUInt8(offset);
offset += 4;
for(;offset<=c*64;offset+=64){
var l = buffer.readUInt8(offset + 32 + 4);
var n = {
ssid : buffer.toString('ascii',offset, offset + l),
x1 : buffer.readUInt8(offset + 32 + 4 + 12),
encryption : buffer.readUInt8(offset + 32 + 4 + 24)
};
res.networks.push(n);
}

return res;
}
};
BLK.Join = {
Request : (target, ssid, pwd, security) => {
var res = {
isPublic: true,
command : BLK.Commands.Join,
target : target
};
if(ssid && pwd) {
var buffer = Buffer.alloc(128);
buffer.writeUInt8(ssid.length,124);
buffer.writeUInt8(pwd.length,125);
buffer.write(ssid,60,'ascii');
buffer.write(pwd,92,'ascii');
var s = security || ((!pwd || pwd=='')?0:4); //(0 = none, 1 = WEP, 2 = WPA1, 3 = WPA2, 4 = WPA1/2)
buffer.writeUInt8(s,126);
res.payload = buffer;
}

return res;
},
Response : (buffer) => {
var res = { };
//nothing here - just ACK
return res;
}
};
BLK.Auth = {
Request : (target) => {
var res = {
command : BLK.Commands.Auth,
target : target,
isEncrypted : true
};
if(!target) return res;var buffer = Buffer.alloc(80);
var key = crypto.randomBytes(16);//target.key || BLK.key;
key.copy(buffer,4); // 0x4 : Shared key (16 bytes)

//buffer.writeUInt8(0x1,30); // 0x1e : 0x1
buffer.writeUInt8(0x1,45); // 0x2d : 0x1
buffer.write('Khone alone',48, 'ascii'); // 0x30 : Device name
res.payload = buffer;

return res;
},
Response : (buffer, target) => {
var res = { };
var data = _decryptPayload(buffer, BLK.key);
var key = Buffer.alloc(16);
data.copy(key,0,4,20); // 0x4 : key in payload
var id = data.readInt32LE(0); // 0x0 : device id in payloadres.key = key;
res.id = id;
res.target = target; //TODO do not need to return res, return target itself
console.log('INFO | %s (#%s) key is %s',target.kind, res.id, res.key.toString('hex'));
return res;
}
};
BLK.TogglePower = {
Request : (target, state) => {
var res = {
command : BLK.Commands.TogglePower,
target : target,
isEncrypted : true
};
if(target && target.id && target.key){
var buffer = Buffer.alloc(16);
buffer.writeUInt8((state != null)?2:1,0); // 0x0 : toggle->value=2, check->value=1
buffer.writeUInt8(state?1:0,4); // 0x4 : 1: on, 2: off
res.payload = buffer;
}
return res;
},
Response : (buffer, target) => {
var res = {
target : target
};
var err = buffer.readUInt16LE(34); // 0x22 : Error
if(err === 0) {
var data = _decryptPayload(buffer,target.key);
res.state = data.readUInt8(4)?'ON':'OFF';// 0x4 : State
if(data.length > 16) {
//this is info message
//TODO: parse and learn
console.log('==>',data.toString('hex'));
}
} else {
console.log('ERR | Error %s getting device %s power state', err, target.id);
}
return res;
}
};
BLK.getPacket = (message, deviceId = 0x7D00 ,currentCID = [0xa5,0xaa,0x55,0x5a,0xa5,0xaa,0x55,0x0]) =>{
if(!message.payload) message.payload = Buffer.alloc(0);
var isBroadcast = !message.target || !message.target.ip || message.target.ip == '255.255.255.255' || message.target.ip == '224.0.0.251';
//QUIC header
if(isBroadcast || message.isPublic){
var packet = Buffer.alloc(8); //0x8 padding, Public flag = 0x0
//multicast - PKN:0
//merge payload right away
if(message.payload.length < 40){ // minimum payload length
var filler = Buffer.alloc(40 - message.payload.length);
message.payload = Buffer.concat([message.payload,filler]);
}
packet = Buffer.concat([packet,message.payload]);
} else {
var packet = Buffer.alloc(56); //0x38 total custom header length
packet.writeUInt8(0x5a,0); //Version:0, Reset:Yes, CID:0x2,Packet #1, Multipath:Yes
var cid = Buffer.from(message.target.CID || currentCID);
cid.copy(packet,1);
//tag:0x0, tag #:0x0, padding: 0
}
packet.writeUInt16LE(deviceId,36); // 0x24 : Device ID
packet.writeUInt8(message.command[0],38); // 0x26 : Command

if(!isBroadcast && !message.isPublic) {
BLK.mgs = BLK.mgs || 0;
packet.writeUInt16LE(BLK.mgs++ & 0xFFFF, 40); // 0x28 : Send Counter

if(message.target.mac) {
var m = message.target.mac.split(':').reverse();
var offset = 42; // 0x2a : MAC
for(var i=0;i<m.length;i++){
packet.writeUInt8(parseInt(m[i],16),offset++);
}
}
if(message.target.id){
packet.writeUInt32LE(48); // 0x30 : Device ID
}
if(message.payload.length > 0) {
var cs = _cs(message.payload);
packet.writeUInt16LE(cs,52); // 0x34 : Header Checksum
}
if(message.isEncrypted){

BLK.key = Buffer.from([0x09, 0x76, 0x28, 0x34, 0x3f, 0xe9, 0x9e, 0x23, 0x76, 0x5c, 0x15, 0x13, 0xac, 0xcf, 0x8b, 0x02]);
BLK.iv = Buffer.from([0x56, 0x2e, 0x17, 0x99, 0x6d, 0x09, 0x3d, 0x28, 0xdd, 0xb3, 0xba, 0x69, 0x5a, 0x2e, 0x6f, 0x58]);
var key = message.target.key || BLK.key;
var cipher = crypto.createCipheriv('aes-128-cbc', key, BLK.iv);
cipher.setAutoPadding(false);
message.payload = Buffer.concat([cipher.update(message.payload),cipher.final()]);
}

packet = Buffer.concat([packet,message.payload]);
}
var cs = _cs(packet);
packet.writeUInt16LE(cs, 32); // 0x20 : Full checksum
return packet;
}
var _decryptPayload = (buffer, key) => {
var data = Buffer.alloc(buffer.length - 56); // 0x38 : Encrypted payload
buffer.copy(data,0, 56, buffer.length);

var decipher = crypto.createDecipheriv('aes-128-cbc',key,BLK.iv);
decipher.setAutoPadding(false);
return Buffer.concat([decipher.update(data) , decipher.final()]);
};
var _parseIp = (ip) => {
var o = ip.split('.');
var res = 0;
for (var i = 0; i < o.length; ++i) {
var p = parseInt(o[i], 10);
res |= p << ((o.length - i) * 8);
}
return res;
};
var _readIp = (buffer,start) => {
var ip = buffer.readInt32LE(start);
var p = [];
for(var s = 0; s <= 24; s+=8 ){
p.push(((ip >> s) & 0xFF).toString());
}
return p.join('.');
};
var _readMac = (buffer,start) => {
var mac = [];
for(var i=start;i<start+6;i++){
mac.push(buffer.readUInt8(i).toString(16));
}
return mac.reverse().join(':');
};
var _cs = (buffer) => (0xbeaf + Array.prototype.slice.call(buffer,0).reduce((p,c)=> (p+c))) & 0xffff;//TODO: Maybe i will call it translate? :)
var _readType = (buffer,start) => {
var type = buffer.toString('utf8',start,buffer.length);
if(type.match('智能插座').length > 0) return 'SMART SOCKET';
else return 'UNDEFINED';
};
var _readDeviceType = (buffer) => {
var type = buffer.readUInt16LE(36);
switch(type){
case 0: return 'SP1'; break;
case 0x2711: return 'SP2'; break;
case 0x2719:
case 0x7919:
case 0x271a:
case 0x791a: return 'Honeywell SP2'; break;
case 0x2720: return 'SPMini'; break;
case 0x753e: return 'SP3'; break;
case 0x2728: return 'SPMini2'; break;
case 0x2733:
case 0x273e: return 'SPMini OEM'; break;
case 0x2736: return 'SPMiniPlus'; break;
case 0x2712: return 'RM2'; break;
case 0x2737: return 'RM Mini'; break;
case 0x273d: return 'RM Pro Phicomm'; break;
case 0x2783: return 'RM2 Home Plus'; break;
case 0x277c: return 'RM2 Home Plus GDT'; break;
case 0x272a: return 'RM2 Pro Plus'; break;
case 0x2787: return 'RM2 Pro Plus2'; break;
case 0x278b: return 'RM2 Pro Plus BL'; break;
case 0x278f: return 'RM Mini Shate'; break;
case 0x2714: return 'A1'; break;
case 0x4EB5: return 'MP1'; break;
default:
if(type >= 0x7530 & type <= 0x7918) return 'SPMini2 OEM';
else return 'Unknown';
break;
}
}
BLK.getName = function(value) {
return Object.keys(BLK.Commands).find(key => Array.isArray(value) ? BLK.Commands[key] === value : BLK.Commands[key].includes(value));
};
BLK.get = function(value) {
var m = BLK.getName(value);
if(!m) return null;
return this[m];
};
BLK.getTrigger = function(msg) {
var m = BLK.get(msg.command);
if(m){
var n = BLK.getName(msg.command);
return _blEvnt+n;
}
return null;
};
BLK.parse = function(buffer, targets){
if(buffer.length < 48){
console.log('ERR | Response message is too short (%d bytes)',buffer.length);
return null;
}
var cs = buffer.readUInt16LE(32);
buffer.writeUInt16LE(0x0,32);
if(_cs(buffer) != cs){
console.log('ERR | Wrong incoming message format : ',JSON.stringify(buffer));
return null;
}
//header
/*if(buffer.readUInt8(0) & 2){ //this is public reset
//hack to workout JS bug!
var cid = [];
for(var i=1;i<=8;i++) {
cid.push(buffer[i]);
}

//attach it to device
}*/
var command = buffer.readUInt16LE(38);
var device = _readDeviceType(buffer);
var srs = _readMac(buffer,42);
var msg = BLK.get(command);
if(!msg){
console.log('TODO | Unknown incoming message 0x%s',command.toString(16));
return null;
}
var evt = BLK.getTrigger(msg.Request());
var target = targets.find(t=>t.id === evt)
var res = msg.Response(buffer, target?target.target:null);
res.event = evt;
res.name = BLK.getName(command);
res.srs = srs;
res.kind = device;
return res;
};
module.exports = BLK;

--

--