UNPKG

blynk-tools

Version:

Tools for working with Blynk IoT Platform

239 lines (183 loc) 7.7 kB
/** * Copyright 2017 Volodymyr Shymanskyy **/ 'use strict'; const util = require('util') const stream = require('stream') const crc = require('crc') const debug = require('debug')('BLE:lbb') const dump = require('debug')('BLE:dump') const { hexdump } = require('../utils.js') var commands = { MSG_ID_SERIAL_DATA : Buffer.from([0x00, 0x00]), MSG_ID_BT_SET_ADV : Buffer.from([0x05, 0x00]), MSG_ID_BT_SET_CONN : Buffer.from([0x05, 0x02]), MSG_ID_BT_SET_LOCAL_NAME : Buffer.from([0x05, 0x04]), MSG_ID_BT_SET_PIN : Buffer.from([0x05, 0x06]), MSG_ID_BT_SET_TX_PWR : Buffer.from([0x05, 0x08]), MSG_ID_BT_GET_CONFIG : Buffer.from([0x05, 0x10]), MSG_ID_BT_ADV_ONOFF : Buffer.from([0x05, 0x12]), MSG_ID_BT_SET_SCRATCH : Buffer.from([0x05, 0x14]), MSG_ID_BT_GET_SCRATCH : Buffer.from([0x05, 0x15]), MSG_ID_BT_RESTART : Buffer.from([0x05, 0x20]), MSG_ID_GATING : Buffer.from([0x05, 0x50]), MSG_ID_BL_CMD : Buffer.from([0x10, 0x00]), MSG_ID_BL_FW_BLOCK : Buffer.from([0x10, 0x01]), MSG_ID_BL_STATUS : Buffer.from([0x10, 0x02]), MSG_ID_CC_LED_WRITE : Buffer.from([0x20, 0x00]), MSG_ID_CC_LED_WRITE_ALL : Buffer.from([0x20, 0x01]), MSG_ID_CC_LED_READ_ALL : Buffer.from([0x20, 0x02]), MSG_ID_CC_ACCEL_READ : Buffer.from([0x20, 0x10]), MSG_ID_CC_TEMP_READ : Buffer.from([0x20, 0x11]), MSG_ID_AR_SET_POWER : Buffer.from([0x30, 0x00]), MSG_ID_AR_GET_CONFIG : Buffer.from([0x30, 0x06]), MSG_ID_DB_LOOPBACK : Buffer.from([0xFE, 0x00]), MSG_ID_DB_COUNTER : Buffer.from([0xFE, 0x01]), }; function BleBeanSerial(peripheral, opts, options) { stream.Duplex.call(this, options); this.peripheral = peripheral; this.uuid_svc = opts.uuid_svc; this.uuid_rxtx = opts.uuid_rxtx; this.delay_send = opts.delay_send || 30; this.max_chunk = opts.max_chunk || 20; this.buff_tx = []; this.timer_tx = -1; this.count = 0; this.gst = new Buffer(0); } util.inherits(BleBeanSerial, stream.Duplex); BleBeanSerial.prototype._onRead = function(gt){ //see https://github.com/PunchThrough/bean-documentation/blob/master/serial_message_protocol.md //Received a single GT packet var start = (gt[0] & 0x80); //Set to 1 for the first packet of each App Message, 0 for every other packet var messageCount = (gt[0] & 0x60); //Increments and rolls over on each new GT Message (0, 1, 2, 3, 0, ...) var packetCount = (gt[0] & 0x1F); //Represents the number of packets remaining in the GST message //first packet, reset data buffer if (start) { this.gst = new Buffer(0); } //TODO probably only if messageCount is in order this.gst = Buffer.concat( [this.gst, gt.slice(1)] ); //last packet, process and emit if(packetCount === 0){ var length = this.gst[0]; //size of thse cmd and payload //crc only the size, cmd and payload var crcString = crc.crc16ccitt(this.gst.slice(0,this.gst.length-2)); //messy buffer equality because we have to swap bytes and can't use string equality because tostring drops leading zeros //debug('CRC: ' , typeof crcString); var crc16 = new Buffer(2); crc16.writeUInt16BE(crcString, 0); var valid = (crc16[0]===this.gst[this.gst.length-1] && crc16[1]===this.gst[this.gst.length-2]); var command = ( (this.gst[2] << 8) + this.gst[3] ) & ~(0x80) ; //this.emit('raw', this.gst.slice(2,this.gst.length-2), length, valid, command); if(valid){ //ideally some better way to do lookup if(command === (commands.MSG_ID_CC_ACCEL_READ[0] << 8 ) + commands.MSG_ID_CC_ACCEL_READ[1]) { var x = (((this.gst[5] << 24) >> 16) | this.gst[4]) * 0.00391; var y = (((this.gst[7] << 24) >> 16) | this.gst[6]) * 0.00391; var z = (((this.gst[9] << 24) >> 16) | this.gst[8]) * 0.00391; this.emit('accell', x.toFixed(5), y.toFixed(5), z.toFixed(5), valid); } else if(this.gst[2] === commands.MSG_ID_SERIAL_DATA[0] && this.gst[3] === commands.MSG_ID_SERIAL_DATA[1]){ var data = this.gst.slice(4,this.gst.length-2); if (!this.push(data)) { //this._source.readStop(); } } else if(command === (commands.MSG_ID_CC_TEMP_READ[0] << 8 ) + commands.MSG_ID_CC_TEMP_READ[1]){ this.emit('temp', this.gst[4], valid); } else{ this.emit('invalid', this.gst.slice(2,this.gst.length-2), length, valid, command); } } } }; BleBeanSerial.prototype.sendCmd = function(cmdBuffer,payloadBuffer,done) { var self = this; var sizeBuffer = new Buffer(2); sizeBuffer.writeUInt8(cmdBuffer.length + payloadBuffer.length,0); sizeBuffer.writeUInt8(0,1); //GST contains sizeBuffer, cmdBuffer, and payloadBuffer var gst = Buffer.concat([sizeBuffer,cmdBuffer,payloadBuffer]); var crcString = crc.crc16ccitt(gst); var crc16Buffer = new Buffer(2); crc16Buffer.writeUInt16LE(crcString, 0); gst = Buffer.concat([sizeBuffer,cmdBuffer,payloadBuffer,crc16Buffer]); var gt_qty = Math.floor(gst.length/19); if (gst.length % 19 != 0) { gt_qty += 1; } var optimal_packet_size = 19; for (var ch=0; ch<gt_qty; ch++) { var data = gst.slice(ch*optimal_packet_size, (ch+1)*optimal_packet_size); var gt = (self.count * 0x20); // << 5 if (ch == 0) { gt |= 0x80; } gt |= gt_qty - ch - 1; gt = Buffer.concat([new Buffer([ gt ]), data]); self.buff_tx.push(gt); } if (self.timer_tx == -1) { //debug('send start'); self.timer_tx = setInterval(function() { var chunk = self.buff_tx[0]; self.buff_tx = self.buff_tx.slice(1); dump('<', hexdump(chunk)); self.char_rxtx.write(chunk, true); if (!self.buff_tx.length) { clearInterval(self.timer_tx); self.timer_tx = -1; //debug('send stop'); if (typeof(done) == 'function') done(); } }, self.delay_send); } self.count = (self.count + 1) % 4; }; BleBeanSerial.prototype.connect = function (callback) { var self = this; self.peripheral.discoverServices([self.uuid_svc], function(err, services) { if (err) throw err; services.forEach(function(service) { service.discoverCharacteristics([self.uuid_rxtx], function(err, characteristics) { if (err) throw err; //debug("CHARS:", characteristics); self.char_rxtx = characteristics[0]; self.char_rxtx.notify(true, function(err) { if (err) throw err; }); self.char_rxtx.on('read', function(data, isNotification) { dump('>', hexdump(data)); self._onRead(data); }); self.unGate(); callback(); }); }); }); }; BleBeanSerial.prototype._read = function (size) { //this._source.readStart(); }; BleBeanSerial.prototype._write = function (data, enc, done) { var i = 0; for (; i+64<data.length; i+=64) { this.sendCmd(commands.MSG_ID_SERIAL_DATA, data.slice(i, i+64)); } this.sendCmd(commands.MSG_ID_SERIAL_DATA, data.slice(i, i+64), done); }; BleBeanSerial.prototype.unGate = function(done) { this.sendCmd(commands.MSG_ID_GATING, Buffer.from([]), done); } BleBeanSerial.prototype.setColor = function(color,done) { this.sendCmd(commands.MSG_ID_CC_LED_WRITE_ALL, color, done); }; BleBeanSerial.prototype.requestAccell = function(done) { this.sendCmd(commands.MSG_ID_CC_ACCEL_READ, Buffer.from([]), done); }; BleBeanSerial.prototype.requestTemp = function(done) { this.sendCmd(commands.MSG_ID_CC_TEMP_READ, Buffer.from([]), done); }; module.exports = BleBeanSerial;