LANSITEC - Indoor Bluetooth Gateway

Sensor

Codec Description

Codec for LANSITEC - Indoor-Bluetooth-Gateway (v1.0.0).

Codec Preview

// Decode decodes an array of bytes into an object. // - fPort contains the LoRaWAN fPort number // - bytes is an array of bytes, e.g. [225, 230, 255, 0] // - variables contains the device variables e.g. {"calibration": "3.5"} (both the key / value are of type string) // The function must return an object, e.g. {"temperature": 22.5} // gateway上行数据解析代码 function Decode(fPort, bytes) { // 根据消息开头的第一个字节解析数据 var type = ((bytes[0] >> 4) & 0x0F); if(type == 0x01){ var beaconBytes = bytes.slice(2, 5); // console.log(beaconBytes); // 校验过 return registerUplinkMessage("", bytes); } if (type == 0x02){ // 校验过 return heartbeatUplinkMessage("", bytes); } if (type == 0x06) { // 校验过 return alarmUplinkMessage("", bytes); } if (type == 0x08) { // 校验过 return beaconUplinkMessage("", bytes); } if (type == 0x09) { // 校验 return braceletUplinkMessage("", bytes); } if (type == 0x0A) { // 校验过 - 格式有待完善 return lightPerceptionUplinkMessage("", bytes); } if (type == 0x0B) { // 校验过 - 格式有待完善 return gSensorUplinkMessage("", bytes); } if (type == 0x0D) { // 校验 return beaconListUplinkMessage("", bytes); } if (type == 0x0F) { // 校验过 return acknowledgeUplinkMessage("", bytes) } } // 4.4.1 Register, 0x01设备注册消息 function registerUplinkMessage(prefix, bytes) { var data = {}; data[prefix+"type"] = "Register"; data[prefix+"adr"] = ((bytes[0] >> 3) & 0x01) === 0? "OFF" : "ON"; data[prefix+"mode"] = (bytes[0] & 0x07); switch (data[prefix+"mode"]) { case 0x01: data[prefix+"mode"] = "AU920"; break; case 0x02: data[prefix+"mode"] = "CLAA"; break; case 0x03: data[prefix+"mode"] = "CN470"; break; case 0x04: data[prefix+"mode"] = "AS923"; break; case 0x05: data[prefix+"mode"] = "EU433"; break; case 0x06: data[prefix+"mode"] = "EU868"; break; case 0x07: data[prefix+"mode"] = "US915"; break; } data[prefix+"smode"] = bytes[1]; switch (data[prefix+"smode"]) { case 0x01: data[prefix+"smode"] = "AU920"; break; case 0x02: data[prefix+"smode"] = "CLAA"; break; case 0x04: data[prefix+"smode"] = "CN470"; break; case 0x08: data[prefix+"smode"] = "AS923"; break; case 0x10: data[prefix+"smode"] = "EU433"; break; case 0x20: data[prefix+"smode"] = "EU868"; break; case 0x40: data[prefix+"smode"] = "US915"; break; } data[prefix+"power"] = { value: ((bytes[2] >> 3) & 0x1F), unit: "dBm" }; if(data[prefix+"mode"] === "CLAA") { switch(bytes[2] & 0x07) { case 0x01: data[prefix+"frequencysweepmode"] = "A mode"; break; case 0x02: data[prefix+"frequencysweepmode"] = "B mode"; break; case 0x03: data[prefix+"frequencysweepmode"] = "C mode"; break; case 0x04: data[prefix+"frequencysweepmode"] = "D mode"; break; case 0x05: data[prefix+"frequencysweepmode"] = "E mode"; break; case 0x06: data[prefix+"frequencysweepmode"] = "All frequency sweep"; break; } } //data[prefix+"reserved"] = (bytes[2] & 0x07); data[prefix+"dr"] = `DR${((bytes[3] >> 4) & 0x0F)}`; // data[prefix+"cfgReserved"] = 0; data[prefix+"pos"] = { value: ((((bytes[4] << 8) & 0xff00) | (bytes[5] & 0xff)) * 5), unit: "sec" }; data[prefix+"hb"] = { value: bytes[6] * 30, unit: "sec" }; data[prefix+"crc"] = (((bytes[7] << 8) & 0xff00) | (bytes[8] & 0xff)); // - 65536; return data; } // 4.4.2 Heartbeat, 0x2设备心跳消息 function heartbeatUplinkMessage(prefix, bytes) { var data = {}; data[prefix+"type"] = "Heartbeat"; var bVersion = (bytes[0] & 0x0F) data[prefix+"version"] = bVersion; data[prefix+"vol"] = { value: bytes[1], unit: "%" }; data[prefix+"rssi"] = { value: bytes[2] * (-1), unit: "dBm" }; var snr = (((bytes[3] << 8) & 0xff00) | (bytes[4] & 0xff)); snr = snr > 0x7fff ? snr - 0x10000 : snr; data[prefix+"snr"] = snr * 0.01; data[prefix+"revision"] = bytes[5]; data[prefix+"chgstat"] = bytes[6]; switch(data[prefix+"chgstat"]) { case 0x00: data[prefix+"chgstat"] = "Not charging"; break; case 0x05: data[prefix+"chgstat"] = "Charging"; break; case 0x06: data[prefix+"chgstat"] = "Charge completed"; break; default: break; } data[prefix+"crc"] = (((bytes[7] << 8) & 0xff00) | (bytes[8] & 0xff)); // 打印 //document.getElementById("demo").innerHTML = JSON.stringify(data); //console.log(data); return data; } // 4.3.8 Alarm, 0x6报警消息 function alarmUplinkMessage(prefix, bytes) { var data = {}; data[prefix+"type"] = "Alarm"; data[prefix+"alarm"] = (bytes[0] & 0x0F); data[prefix+"msgid"] = bytes[1]; // document.getElementById("demo").innerHTML = JSON.stringify(data); return data; } // 4.4.3 Beacon, 0x8信标消息 function beaconUplinkMessage(prefix, bytes) { //console.log(bytes); var data = {}; data[prefix+"type"] = "Beacon"; var length = (bytes[0] & 0X0F); data[prefix+"length"] = length; if(length === 0) { return data; } for(var idx = 0; idx < length; idx++){ //console.log(bytes.slice(1 + 5 * idx, 6 + 5 * idx)); var tmp1 = idx + 1; var major = ((bytes[1 + 5 * idx] & 0xFF) << 8 | (bytes[2 + 5 * idx] & 0xFF)); var minor = ((bytes[3 + 5 * idx] & 0xFF) << 8 | (bytes[4 + 5 * idx] & 0xFF)); var rssi = (bytes[5 + 5 * idx] & 0xFF) * (-1); data[prefix+"dev"+tmp1+"major"] = major; data[prefix+"dev"+tmp1+"minor"] = minor; data[prefix+"dev"+tmp1+"rssi"] = { value: rssi, unit: "dBm" }; } const begin = 6 + 5 * (length - 1); const n = Math.floor((length + 1) / 2); console.log(n); for(var i = 0; i < n; ++i) { var tmp = 2*i + 1; data[prefix+"dev"+tmp+"tmoff"] = { value: (bytes[begin + i] >> 4) & 0x0F, unit: "sec" }; if (tmp + 1 <= length) { data[prefix+"dev"+(tmp+1)+"tmoff"] = { value: (bytes[begin + i] & 0x0F), unit: "sec" }; } } return data; } // 4.4.4 Bracelet, 0x9手环消息 function braceletUplinkMessage(prefix, bytes) { var data = {}; data[prefix+"type"] = "Bracelet"; var length = (bytes[0] & 0x0F); data[prefix+"length"] = length; var pos = 0; for(var i = 0; i < length; ++i) { var tmp1 = i+1; data[prefix+"dev"+tmp1+"mac"] = `${bytes[pos+1].toString(16).padStart(2, "0").toUpperCase()}${bytes[pos+2].toString(16).padStart(2, "0").toUpperCase()}${bytes[pos+3].toString(16).padStart(2, "0").toUpperCase()}${bytes[pos+4].toString(16).padStart(2, "0").toUpperCase()}${bytes[pos+5].toString(16).padStart(2, "0").toUpperCase()}${bytes[pos+6].toString(16).padStart(2, "0").toUpperCase()}`;//(((bytes[pos+1] & 0xff) << 40) | ((bytes[pos+2] & 0xff) << 32) | ((bytes[pos+3] & 0xff) << 24) | ((bytes[pos+4] & 0xff) << 16) | ((bytes[pos+5] & 0xff) << 8) | ((bytes[pos+6] & 0xff))).toString(16); data[prefix+"dev"+tmp1+"hb"] = (bytes[pos+7] & 0xff); data[prefix+"dev"+tmp1+"steps"] = (((bytes[pos+8] << 8) & 0xff00) | (bytes[pos+9] & 0xff)); data[prefix+"dev"+tmp1+"bat"] = { value: (bytes[pos+10] & 0xff), unit: "%" }; data[prefix+"dev"+tmp1+"sysp"] = (bytes[pos+11] & 0xff); data[prefix+"dev"+tmp1+"diap"] = (bytes[pos+12] & 0xff); data[prefix+"dev"+tmp1+"calo"] = { value: (((bytes[pos+13] << 8) & 0xff00) | (bytes[pos+14] & 0xff)), unit: "Calorie" }; data[prefix+"dev"+tmp1+"rssi"] = { value: (bytes[pos+15] & 0xff) * (-1), unit: "dBm" }; data[prefix+"dev"+tmp1+"sos"] = bytes[pos+16]; pos += 16; } const begin = pos + 1; const n = Math.floor((length + 1) / 2); for(var i = 0; i < n; ++i) { var tmp = i+1; data[prefix+"dev"+tmp+"tmoff"] = { value: (bytes[begin + i] >> 4) & 0x0F, unit: "sec" }; if (tmp + 1 <= length) { data[prefix+"dev"+(tmp+1)+"tmoff"] = { value: (bytes[begin + i] & 0x0F), unit: "sec" }; } } return data; } // 4.3.5 Light perception, 0xA光感消息 function lightPerceptionUplinkMessage(prefix, bytes) { var data = {}; data[prefix+"type"] = "LightPerception"; var length = (bytes[0] & 0x0F); data[prefix+"length"] = length; var pos = 0; for(var i = 0; i < length; ++i) { var tmp1 = i+1; data[prefix+"dev"+tmp1+"mac"] = `${bytes[pos+1].toString(16).padStart(2, "0").toUpperCase()}${bytes[pos+2].toString(16).padStart(2, "0").toUpperCase()}${bytes[pos+3].toString(16).padStart(2, "0").toUpperCase()}${bytes[pos+4].toString(16).padStart(2, "0").toUpperCase()}${bytes[pos+5].toString(16).padStart(2, "0").toUpperCase()}${bytes[pos+6].toString(16).padStart(2, "0").toUpperCase()}`;//(((bytes[pos+1] & 0xff) << 40) | ((bytes[pos+2] & 0xff) << 32) | ((bytes[pos+3] & 0xff) << 24) | ((bytes[pos+4] & 0xff) << 16) | ((bytes[pos+5] & 0xff) << 8) | ((bytes[pos+6] & 0xff))).toString(16); data[prefix+"dev"+tmp1+"light"] = { value: ((bytes[pos+7] & 0xff) << 8 | (bytes[pos+8] & 0xff)), unit: "Lux" }; data[prefix+"dev"+tmp1+"bat"] = { value: (bytes[pos+9] & 0xff), unit: "%" }; data[prefix+"dev"+tmp1+"rssi"] = { value: bytes[pos+10] * (-1), unit: "dBm" }; pos += 10; } const begin = pos + 1; const n = Math.floor((length + 1) / 2); for(var i = 0; i < n; ++i) { var tmp = i+1; data[prefix+"dev"+tmp+"tmoff"] = { value: (bytes[begin + i] >> 4) & 0x0F, unit: "sec" }; if (tmp + 1 <= length) { data[prefix+"dev"+(tmp+1)+"tmoff"] = { value: (bytes[begin + i] & 0x0F), unit: "sec" }; } } return data; } // 4.3.6 G-Sensor, 0xB重力加速度传感器消息 function gSensorUplinkMessage(prefix, bytes) { var data = {}; data[prefix+"type"] = "G-Sensor"; var length = (bytes[0] & 0x0F); data[prefix+"length"] = length; var pos = 0; for(var i = 0; i < length; ++i) { var tmp1 = i+1; data[prefix+"dev"+tmp1+"mac"] = `${bytes[pos+1].toString(16).padStart(2, "0").toUpperCase()}${bytes[pos+2].toString(16).padStart(2, "0").toUpperCase()}${bytes[pos+3].toString(16).padStart(2, "0").toUpperCase()}${bytes[pos+4].toString(16).padStart(2, "0").toUpperCase()}${bytes[pos+5].toString(16).padStart(2, "0").toUpperCase()}${bytes[pos+6].toString(16).padStart(2, "0").toUpperCase()}`;//(((bytes[pos+1] & 0xff) << 40) | ((bytes[pos+2] & 0xff) << 32) | ((bytes[pos+3] & 0xff) << 24) | ((bytes[pos+4] & 0xff) << 16) | ((bytes[pos+5] & 0xff) << 8) | ((bytes[pos+6] & 0xff))).toString(16); var x = ((bytes[pos+7] & 0xff) << 8 | (bytes[pos+8] & 0xff)); x = x > 0x7fff? x - 0x10000 : x; data[prefix+"dev"+tmp1+"x"] = { value: x, unit: "g" }; var y = ((bytes[pos+9] & 0xff) << 8 | (bytes[pos+10] & 0xff)); y = y > 0x7fff? y - 0x10000 : y; data[prefix+"dev"+tmp1+"y"] = { value: y, unit: "g" }; var z = ((bytes[pos+11] & 0xff) << 8 | (bytes[pos+12] & 0xff)); z = z > 0x7fff? z - 0x10000 : z; data[prefix+"dev"+tmp1+"z"] = { value: z, unit: "g" }; data[prefix+"dev"+tmp1+"bat"] = { value: (bytes[pos+13] & 0xff), unit: "%" }; data[prefix+"dev"+tmp1+"rssi"] = { value: (bytes[pos+14] & 0xff) * (-1), unit: "dBm" }; pos += 14; } const begin = pos + 1; const n = Math.floor((length + 1) / 2); for(var i = 0; i < n; ++i) { var tmp = i+1; data[prefix+"dev"+tmp+"tmoff"] = { value: (bytes[begin + i] >> 4) & 0x0F, unit: "sec" }; if (tmp + 1 <= length) { data[prefix+"dev"+(tmp+1)+"tmoff"] = { value: (bytes[begin + i] & 0x0F), unit: "sec" }; } } return data; } // 4.3.7 Beacon List, 0xD包含4.4.3 Beacon, 4.4.4 Bracelet, 4.3.5 Light perception, 4.3.6 G-Sensor, 四种类型消息至少两种消息以上 function beaconListUplinkMessage(prefix, bytes) { var data = {}; data["type"] = "BeaconList"; // 接收到几种信标类型 var length = (bytes[0] & 0x0F); data["length"] = length; // 定义四种信标数据类型 var beaconList = {}; var braceletList = {}; var lightPerceptionList = {} ; var gSensorList = {}; //解析数据 var index = 0 for (let i = 0; i < length; i++) { var tmp1 = i+1; // 判断属于哪种信标类型 var msgType = ((bytes[index+1] & 0xf0) >> 4); /*switch(msgType){ case 0x01: data["msgType"+tmp1] = "Beacon"; break; case 0x02: data["msgType"+tmp1] = "Bracelet"; break; case 0x03: data["msgType"+tmp1] = "Light perception"; break; case 0x04: data["msgType"+tmp1] = "G-Sensor"; break; default: break; }*/ //data["msgType"] = msgType; // 这种类型信标数量 var msgLength = (bytes[index+1] & 0x0f); // msgLengthType, js 1/2 = 0.5, java 1/2 = 0 var msgLengthType = msgLength; if (msgLength % 2 === 1) { msgLengthType = msgLength + 1; } //data["msgLength"+tmp1] = msgLength; // 定义变量用来标识数据解析到哪个数组下标 var msgTotalLength = 0; index += 1; if(msgType == 1){ // Beacon msgTotalLength = 1 + msgLength * 5 + ((msgLengthType)/2); if(index + msgTotalLength <= bytes.length){ // 解析数据Beacon var beaconBytes = bytes.slice(index, index + msgTotalLength); beaconList = beaconUplinkMessage("msg"+tmp1, beaconBytes); index += msgLength * 5 + (msgLengthType)/2; continue; } } else if(msgType == 2){ // Bracelet msgTotalLength = 1 + msgLength*16+((msgLengthType)/2); if(index + msgTotalLength <= bytes.length){ // 解析手环 var braceletListBytes = bytes.slice(index, index + msgTotalLength); braceletList = braceletUplinkMessage("msg"+tmp1, braceletListBytes); index += msgLength * 16 + ((msgLengthType)/2); continue; } } else if(msgType == 3){ // Light perception msgTotalLength = 1 + msgLength*10+((msgLengthType)/2); if( index + msgTotalLength <= bytes.length){ // 解析光感 var lightPerceptionBytes = bytes.slice(index, index + 1 + msgLength * 10 + ((msgLengthType)/2)); lightPerceptionList = lightPerceptionUplinkMessage("msg"+tmp1, lightPerceptionBytes); index += msgLength * 10 + ((msgLengthType)/2); continue; } } else if(msgType == 4){ // G-Sensor msgTotalLength = 1 + msgLength*14+((msgLengthType)/2); if(index + msgTotalLength <= bytes.length){ // 解析G-Sensor var lightPerceptionBytes = bytes.slice(index, index + 1 + msgLength*14 + ((msgLengthType)/2)); gSensorList = gSensorUplinkMessage("msg"+tmp1, lightPerceptionBytes); index += msgLength * 14 + ((msgLengthType)/2); continue; } } } data = { ...data, ...beaconList, ...braceletList, ...lightPerceptionList, ...gSensorList }; /*data["beaconList"] = beaconList; data["braceletList"] = braceletList; data["lightPerceptionList"] = lightPerceptionList; data["gSensorList"] = gSensorList; document.getElementById("demo").innerHTML = JSON.stringify(data);*/ return data; } // 4.3.9 Acknowledge, 0xF确认消息 function acknowledgeUplinkMessage(prefix, bytes) { var data = {}; data[prefix+"type"] = "Acknowledge"; data[prefix+"result"] = (bytes[0] & 0x0F); switch(data[prefix+"result"]){ case 0x00: data[prefix+"result"] = "success"; break; case 0x01: data[prefix+"result"] = "failure"; break; } data[prefix+"msgid"] = bytes[1];; return data; } const ignore_vars = []; function toTagoFormat(object_item, serie, prefix = '') { const result = []; for (const key in object_item) { if (ignore_vars.includes(key)) continue; if (typeof object_item[key] === 'object') { result.push({ variable: object_item[key].variable || `${prefix}${key}`, value: object_item[key].value, serie: object_item[key].serie || serie, metadata: object_item[key].metadata, location: object_item[key].location, unit: object_item[key].unit, }); } else { result.push({ variable: `${prefix}${key}`, value: object_item[key], serie, }); } } return result; } // let payload = [{ variable: "payload", value: "19017020000a040000"}]; // Register // let payload = [{ variable: "payload", value: "215f0409c401060000"}]; // Heartbeat // let payload = [{ variable: "payload", value: "81000500030400"}]; // Beacon // let payload = [{ variable: "payload", value: "91abcdef0102035d03e75432320032040020"}]; // Bracelet // let payload = [{ variable: "payload", value: "a1abcdef0102030072600430"}]; // light perception // let payload = [{ variable: "payload", value: "b1abcdef01020300fdfffcffef460710"}]; // g-sensor // let payload = [{ variable: "payload", value: "d111000500030400"}]; // beacon list - beacon // let payload = [{ variable: "payload", value: "6005"}]; // alarm // let payload = [{ variable: "payload", value: "f001"}]; // acknowledge const data = payload.find((x) => x.variable === "payload_raw" || x.variable === "payload" || x.variable === "data"); const port = payload.find((x) => x.variable === "fport" || x.variable === "port"); if (data) { const buffer = Buffer.from(data.value, "hex"); const serie = data.serie || new Date().getTime(); payload = payload.map((x) => ({ ...x, serie })); payload = payload.concat(toTagoFormat(Decode(port, buffer), serie)); } // console.log(payload); 

This codec is sourced from TagoIO Github. All rights belong to TagoIO Github.

This codec is licensed under the GNU General Public License v3 (GPL v3). Modifications, if any, are clearly marked. You are free to use, modify, and distribute the codec under the terms of GPL v3.

Community Feedback