星纵网关和节点连接LinkWAN
目录
概述
本文主要介绍如何将星纵物联LoRaWAN®网关和终端节点连接到LinkWAN平台。目前平台只支持CN470频段,如下以星纵物联UG65网关和EM300传感器(LinkWAN版本)为例。
准备
- 星纵物联LoRaWAN®网关
- LoRaWAN®终端节点(LinkWAN版)
步骤
1. 网关配置
1.1 获取密钥文件
- 用户提供网关序列号给设备厂商,厂商使用网关密钥管理工具生成配置文件;
- 用户获取配置压缩文件:
(该文件包含了GwEUI和PIN Code两个参数,该参数在平台添加网关时需要使用)。

1.2 网关启用LinkWAN
进入网关配置页面“Packet Forwarder>常规”,根据下图,配置LinkWAN连接,需要在“导入配置文件”位置导入上一步生成的配置文件。


2. LinkWAN平台配置
登录LinkWAN物联网络管理平台:https://signin.aliyun.com。(可自助申请登录账号)
- 登录控制台,选择“物联网络管理平台>网络管理>网关管理>添加网关”。

- 输入配置文件中的GwEUI和PIN Code参数及对应信息,如下图所示。

- “物联网络管理平台>网络管理>网关管理”,查看网关状态为在线。

- 点击查看,可以看到该网关的上下行数据,以及无效数据。(当无法收到节点数据时,可以查看无效数据。一般节点的JoinEUI或者DevEUI不合法等错误会打印在无效数据中。)

LinkWAN相关程序说明如下:


3. 传感器连接LinkWAN
3.1 创建凭证和密钥
登录物联网络管理平台,先创建凭证和申请密钥。凭证用于后续创建产品使用,密钥用于设备烧录和添加设备时使用。
- 进入“网络管理>入网开通>添加专用凭证”授权给自己。


- 进入“密钥管理>申请密钥>申请购买密钥”。注意:一个密钥配置文件1元。



3.2 新建产品
切换到物联网平台,先创建一个产品,如果没有凭证需要先创建凭证,并授权(选择授权给自己)。

3.3 添加设备
- 进入“设备管理>设备>添加设备”,输入节点的DevEUI和PIN Code,这两个参数在平台成功购买密钥后,可以从下载的密钥文件EXCEL表格中获取。

- 查看设备启用状态,刚添加完设备为未激活状态,设备成功上线后变为在线。

3.4 创建模型
- 进入物联网平台“设备管理>产品>产品详情”,选择对应产品,为设备创建物理模型。

- 添加自定义功能。
注意:标识符和数据类型应该遵循Decoder内的定义进行填写,否则会解析不成功。
A、EM300设备自定义功能参考如下:

B、UC1152设备自定义功能参考如下:

3.5 发布上线

3.6 设置数据解析
- 选择数据解析,将数据解析代码拷贝进去,具体代码见4.7(EM300)或4.8(UC1152)。

- 到设备物理模型下,可以查看设备的上报数据。


3.7 EM300数据解析代码如下
/*** 将设备自定义topic数据转换为json格式数据, 设备上报数据到物联网平台时调用* 入参:topic string 设备上报消息的topic* 入参:rawData byte[]数组 不能为空* 出参:jsonObj JSON对象 不能为空*/function array(length) {return new Array(length).fill(null);}function fixFloat(float, fractionDigits) {fractionDigits = fractionDigits || 1;var value = typeof float === "string" ? parseFloat(float) : float;return parseFloat(value.toFixed(fractionDigits));}function readUInt8(bytes) {return (bytes & 0xFF);}function readInt8(bytes) {var ref = readUInt8(bytes);return (ref > 0x7F) ? ref - 0x100 : ref;}function readUInt16LE(bytes) {var value = (bytes[1] << 8) + bytes[0];return (value & 0xFFFF);}function readInt16LE(bytes) {var ref = readUInt16LE(bytes);return (ref > 0x7FFF) ? ref - 0x10000 : ref;}function readUInt32LE(bytes) {var value = (bytes[3] << 24) + (bytes[2] << 16) + (bytes[1] << 8) + bytes[0];return (value & 0xFFFFFFFF);}function readInt32LE(bytes) {var ref = readUInt32LE(bytes);return (ref > 0x7FFFFFFF) ? ref - 0x100000000 : ref;}function readFloatLE(bytes) {var bits = bytes[3] << 24 | bytes[2] << 16 | bytes[1] << 8 | bytes[0];var sign = (bits >>> 31 === 0) ? 1.0 : -1.0;var e = bits >>> 23 & 0xff;var m = (e === 0) ? (bits & 0x7fffff) << 1 : (bits & 0x7fffff) | 0x800000;var f = sign * m * Math.pow(2, e - 150);return f;}function BufferReader(buffer) {this.buffer = buffer;this.offset = 0;}BufferReader.prototype.getBuffer = function () {return this.buffer;}BufferReader.prototype.byteAt = function (index) {return this.buffer[index];}BufferReader.prototype.firstByte = function () {return this.buffer[0];}BufferReader.prototype.lastByte = function () {return this.buffer[this.buffer.length - 1];}BufferReader.prototype.hasNextByte = function () {return this.buffer.length > this.offset;}BufferReader.prototype.moveOffset = function (plus) {this.offset += plus;}BufferReader.prototype.setOffset = function (offset) {this.offset = offset;}BufferReader.prototype.getOffset = function () {return this.offset;}BufferReader.prototype.readBit = function (bits) {var result = [];var binary = this.readUInt8();for (var idx = 0; idx < bits; idx++) {result[idx] = (binary >> idx) & 1;}return result;}BufferReader.prototype.readUInt8 = function () {var result = readUInt8(this.buffer.slice(this.offset, this.offset + 1));this.offset += 1;return result;}BufferReader.prototype.readInt16LE = function () {var result = readInt16LE(this.buffer.slice(this.offset, this.offset + 2));this.offset += 2;return result;}BufferReader.prototype.readUInt16LE = function () {var result = readUInt16LE(this.buffer.slice(this.offset, this.offset + 2));this.offset += 2;return result;}BufferReader.prototype.readInt32LE = function () {var result = readInt32LE(this.buffer.slice(this.offset, this.offset + 4));this.offset += 4;return result;}BufferReader.prototype.readUInt32LE = function () {var result = readUInt32LE(this.buffer.slice(this.offset, this.offset + 4));this.offset += 4;return result;}BufferReader.prototype.readFloatLE = function () {var result = readFloatLE(this.buffer.slice(this.offset, this.offset + 4));this.offset += 4;return result;}BufferReader.prototype.readString = function (size) {var result = this.buffer.slice(this.offset, this.offset + size);this.offset += size;return result.toString().replace(/\0/g, "");}BufferReader.prototype.readHex = function (length) {var hex = this.buffer.slice(this.offset, this.offset + length).toString(16);this.offset += length;return hex;}BufferReader.prototype.readBuffer = function (length) {var buffer = this.buffer.slice(this.offset, this.offset + length);this.offset += length;return buffer;}var DATA_TYPE = {Int8: "Int8",UInt8: "UInt8",Int16: "Int16LE",UInt16: "UInt16LE",Int32LE: "Int32LE",UInt32LE: "UInt32LE",};var IPSO_PARSER = {0: defCommonChannelParser("digitalInput", DATA_TYPE.UInt8),1: defCommonChannelParser("digitalOutput", DATA_TYPE.UInt8),3: defCommonChannelParser("analogOutput", DATA_TYPE.Int16, undefined, function (value) { return fixFloat(value * 0.01, 2) }),101: defCommonChannelParser("illumianceSensor", DATA_TYPE.UInt16, "lux"),102: defCommonChannelParser("presenceSensor", DATA_TYPE.UInt8),103: defCommonChannelParser("temperatureSensor", DATA_TYPE.Int16, "¡ãC", function (value) { return fixFloat(value * 0.1) }),104: defCommonChannelParser("humiditySensor", DATA_TYPE.UInt8, "%", function (value) { return fixFloat(value * 0.5) }),106: defCommonChannelParser("actuation", DATA_TYPE.UInt16, ""),115: defCommonChannelParser("barometerPressure", DATA_TYPE.UInt16, "kPa", function (value) { return fixFloat(value * 0.1) }),116: defCommonChannelParser("voltage", DATA_TYPE.UInt16, "V", function (value) { return fixFloat(value * 0.01, 2) }),117: defCommonChannelParser("current", DATA_TYPE.UInt8, "%"),119: defCommonChannelParser("depth", DATA_TYPE.UInt16, "cm", function (value) { return value }),123: defCommonChannelParser("pressure", DATA_TYPE.UInt16, "", function (value) { return fixFloat(value * 0.1) }),124: defCommonChannelParser("loudness", DATA_TYPE.UInt16, "dB", function (value) { return fixFloat(value * 0.1) }),125: defCommonChannelParser("concentration", DATA_TYPE.UInt16, ""),128: defCommonChannelParser("power", DATA_TYPE.UInt16, "W", function (value) { return fixFloat(value * 0.01, 2) }),130: defCommonChannelParser("distance", DATA_TYPE.UInt16, "m", function (value) { return fixFloat(value / 1000, 3) }),146: defCommonChannelParser("par", DATA_TYPE.UInt16, "¦Ìmol¡¤m?2¡¤s?1", function (value) { return fixFloat(value * 0.1) }),200: defCommonChannelParser("counter", DATA_TYPE.UInt32LE),};// analog inputIPSO_PARSER[2] = function (channel) {return function (reader, data) {data[channel] = {channel: channel,type: "analogInput",value: {ccy: fixFloat(reader.readInt16LE() * 0.01, 2),min: fixFloat(reader.readInt16LE() * 0.01, 2),max: fixFloat(reader.readInt16LE() * 0.01, 2),avg: fixFloat(reader.readInt16LE() * 0.01, 2),},};}};var URSA_PARSER = {1: defConfigDataParser("version", DATA_TYPE.UInt8, function (value) { return value.toString() }),2: defConfigDataParser("interval", DATA_TYPE.UInt16),3: defConfigDataParser("period", DATA_TYPE.UInt16),4: defConfigDataParser("response", DATA_TYPE.UInt8),5: parseLoRaChanMask,6: skip(9, "alarmConfig"),7: defConfigDataParser("debugLevel", DATA_TYPE.UInt8),8: defSNParser(6),9: parseVersion("hardware"),10: parseVersion("firmware"),11: defConfigDataParser("powerOn", DATA_TYPE.UInt8, function (value) { return value > 0 }),12: defConfigDataParser("powerOff", DATA_TYPE.UInt8, function (value) { return value > 0 }),13: parseAlarm,14: parseChannels,15: defConfigDataParser("class", DATA_TYPE.UInt8),16: defConfigDataParser("reboot", DATA_TYPE.UInt8),17: defConfigDataParser("timestamp", DATA_TYPE.UInt16),18: parseText,19: defConfigDataParser("intelligenceReport", DATA_TYPE.UInt8),20: parseConversion,21: parseCollectFailedChannelIds,22: defSNParser(8),};function convertBufferToObject(buffer) {var reader = new BufferReader(buffer);var data = { config: {} };while (reader.hasNextByte()) {var channel = reader.readUInt8();var type = reader.readUInt8();var parser = getReadParser(channel, type);if (parser) {parser.parse(reader, data);}}return data;}function getReadParser(channel, type) {if (channel === 255) {return { parse: URSA_PARSER[type] };} else {return { parse: IPSO_PARSER[type](channel) };}}function defCommonChannelParser(type, dataType, unit, converter) {return function (channel) {return function (reader, data) {var value = reader["read" + dataType]();data[channel] = {channel: channel,type: type,unit: unit,value: (converter || function (value) { return value })(value),};}};}function defConfigDataParser(property, dataType, converter) {return function (reader, data) {var value = reader["read" + dataType]();if (!data.config) {data.config = {};}data.config[property] = (converter || function (value) { return value })(value);};}function skip(length, property) {return function (reader, data) {reader.moveOffset(length);};}function defSNParser(length) {return function (reader, data) {data.config.sn = reader.readHex(length);};}function parseLoRaChanMask(reader, data) {var id = reader.readUInt8();var value = reader.readUInt16LE();data.config.loRaChanMask = { id: id, value: value };}function parseText(reader, data) {var length = reader.readUInt8();data.config.text = reader.readString(length);}function parseAlarm(reader, data) {var MODE = ["DISABLE", "BELOW", "ABOVE", "WITHIN", "BELOW_OR_ABOVE"];var idAndMode = reader.readUInt8();var id = (idAndMode & 56) >>> 3; // & 0x00111000 >> 3var mode = MODE[idAndMode & 7]; // & 0x00000111var min = reader.readInt16LE() / 10;var max = reader.readInt16LE() / 10;var current = reader.readInt16LE() / 10;var lessThan = min;var greaterThan = max;if (mode === "WITHIN") {lessThan = max;greaterThan = min;} else if (mode === "BELOW_OR_ABOVE") {mode = current < lessThan ? "BELOW" : "ABOVE";}data.config.alarm = { id: id, mode: mode, greaterThan: greaterThan, lessThan: lessThan, current: current };}function parseConversion(reader, data) {var value = reader.readUInt8();var index = (value & 240) >>> 4; // & 11110000 >> 4var type = (value & 15) === 0 ? "mA" : "V"; // & 00001111if (!data.config.conversion) {data.config.conversion = [];}data.config.conversion.push({ index: index, type: type });}function parseVersion(property) {return function (reader, data) {var mainVer = reader.readUInt8();var subVer = reader.readUInt8();var realMainVer = mainVer % 16 + Math.floor(mainVer / 16) * 10;var realSubVer = subVer % 16 === 0 && property === "hardware"? Math.floor(subVer / 16): subVer % 16 + Math.floor(subVer / 16) * 10;if (property === "hardware") {data.config.hardware = "v" + realMainVer + "." + realSubVer;} else if (property === "firmware") {data.config.firmware = "v" + realMainVer + "." + realSubVer;}};}var CHANNEL_TYPE = ["REG_COIL", "REG_DISCRETE", "REG_INPUT", "REG_HOLD_INT16", "REG_HOLD_INT32","REG_HOLD_FLOAT", "REG_INPUT_INT32", "REG_INPUT_FLOAT","REG_INPUT_INT32_AB", "REG_INPUT_INT32_CD", "REG_HOLD_INT32_AB", "REG_HOLD_INT32_CD"];function parseChannels(reader, data) {var channelId = reader.readUInt8();var ptype = reader.readUInt8();var dataType = CHANNEL_TYPE[ptype & 7];var dataLength = ptype >> 3;var offset = reader.getOffset();var raw = "";var value = 0;switch (dataType) {case "REG_COIL":case "REG_DISCRETE":value = reader.readUInt8();reader.setOffset(offset);raw = reader.readHex(1);break;case "REG_INPUT":case "REG_HOLD_INT16":case "REG_INPUT_INT32_AB":case "REG_INPUT_INT32_CD":case "REG_HOLD_INT32_AB":case "REG_HOLD_INT32_CD":value = reader.readUInt16LE();reader.setOffset(offset);raw = reader.readHex(2);break;case "REG_HOLD_INT32":case "REG_INPUT_INT32":value = reader.readUInt32LE();reader.setOffset(offset);raw = reader.readHex(4);break;case "REG_HOLD_FLOAT":case "REG_INPUT_FLOAT":value = fixFloat(reader.readFloatLE(), 4);reader.setOffset(offset);raw = reader.readHex(4);break;}if (!data.config.channelData) {data.config.channelData = array(8);}data.config.channelData[channelId] = {channelId: channelId,dataType: dataType,dataLength: dataLength,value: value,raw: raw,};}function writeUInt8(bytes, value, index) {bytes[index] = value;}function writeUInt16LE(bytes, value, index) {var low = value & 0xFF;var high = (value & 0xFF00) >> 8;bytes[index] = low;bytes[index + 1] = high;}function safeValue(value, defaultValue) {if (value === null || value === undefined) {return defaultValue;}value = Number(value);if (value === NaN) {return defaultValue;}return value;}function writeAlarm(alarmConfig) {var MODE = { DISABLE: 0, BELOW: 1, ABOVE: 2, WITHIN: 3, BELOW_OR_ABOVE: 4 };const bytes = new Uint8Array(11);writeUInt8(bytes, 255, 0);writeUInt8(bytes, 6, 1);var idAndMode = ((alarmConfig.id || 0) << 3) + (MODE[alarmConfig.mode || "DISABLE"]);writeUInt8(bytes, idAndMode, 2);writeUInt8(bytes)var upper = safeValue(alarmConfig.upper, 9999);var lower = safeValue(alarmConfig.lower, 9999);if ((alarmConfig.mode === "BELOW_OR_ABOVE" || alarmConfig.mode === "WITHIN") && upper > lower) {// min和max都有值时min一定小于max[upper, lower] = [lower, upper];}writeUInt16LE(bytes, upper, 3); // minwriteUInt16LE(bytes, lower, 5); // maxwriteUInt16LE(bytes, 0, 7);writeUInt16LE(bytes, 0, 9);return bytes;}function parseCollectFailedChannelIds(reader, data) {var ids = data.config.collectFailedChannelIds || [];ids.push(reader.readUInt8());data.config.collectFailedChannelIds = ids;}function Decoder(bytes, port) {return convertBufferToObject(bytes);}function bufferToBytes(buffer) {var bytes = [];buffer.forEach(function (v) { bytes.push(v) });return bytes;}function rawDataToProtocol(bytes) {var data = convertBufferToObject(new Uint8Array(bytes));var result = {};// {"method":"thing.event.property.post", "id":"12345", "params":{"Temperature":1,"Humidity":2}, "version":"1.1"}result.method = "thing.event.property.post";result.version = "1.1";result.id = "1";result.params = {};if (data[3]) {result.params.temperature = data[3].value;}if (data[4]) {result.params.humiditySensor = data[4].value;}if (data[6]) {result.params.digitalInput = data[6].value;}if (data[5]) {result.params.digitalInput1 = data[5].value;}if (data[1]) {result.params.BatteryLevel = data[1].value;}return result;}function protocolToRawData(json) {// {// "method": "thing.service.SetTempHumiThreshold",// "id": "12345",// "version": "1.1",// "params": {// "MaxTemp": 50,// "MinTemp": 8,// "MaxHumi": 90,// "MinHumi": 10// }// }var header = [0x5D, 0x01, 0x00];var params = json.params;var lower = params.maxtemp;var upper = params.mintemp;var mode;var isUpperExists = !!upper || upper === 0;var isLowerExists = !!lower || lower === 0;if (isUpperExists && !isLowerExists) {mode = "BELOW";} else if (!isUpperExists && isLowerExists) {mode = "ABOVE";} else if (!isUpperExists && !isLowerExists) {return new Uint8Array([0x5d, 0x01, 0x00, 0xff, 0x06, 0x11, 0x14, 0x00, 0x32, 0x00, 0x3c, 0x00, 0x00, 0x00]);}else if (lower <= upper) {mode = "WITHIN";} else if (lower > upper) {mode = "BELOW_OR_ABOVE";}var payload = writeAlarm({id: 1, mode, upper, lower});var arr = [];payload.forEach(function (v, i) {arr[i] = v;})return new Uint8Array(header.concat(arr));}function transformPayload() {}
3.8 UC1152数据解析代码如下
/*** 将设备自定义topic数据转换为json格式数据, 设备上报数据到物联网平台时调用* 入参:topic string 设备上报消息的topic* 入参:rawData byte[]数组 不能为空* 出参:jsonObj JSON对象 不能为空*/function transformPayload(topic, rawData) {var data = Decoder(rawData, 85);var jsonObj = {};jsonObj.method=topic;jsonObj.version="1.0";jsonObj.id="1";jsonObj.params=data;return jsonObj;}/*** 将设备的自定义格式数据转换为Alink协议的数据,设备上报数据到物联网平台时调用* 入参:rawData byte[]数组 不能为空* 出参:jsonObj Alink JSON对象 不能为空*/function rawDataToProtocol(rawData) {var data = Decoder(rawData, 85);var jsonObj = {};jsonObj.method="thing.event.property.post";jsonObj.version="1.0";jsonObj.id="1";jsonObj.params=data;return jsonObj;}/*** 将Alink协议的数据转换为设备能识别的格式数据,物联网平台给设备下发数据时调用* 入参:jsonObj Alink JSON对象 不能为空* 出参:rawData byte[]数组 不能为空**/function protocolToRawData(jsonObj) {var rawdata = [];return rawdata;}function Decoder(bytes, port) {var decoded = {};for (i = 0; i < bytes.length;) {var channel_id = bytes[i++];var channel_type = bytes[i++];// Digital Input 1if (channel_id === 0x01 && channel_type !== 0xc8) {decoded.din1 = bytes[i] === 0 ? "off" : "on";i += 1;}// Digital Input 2else if (channel_id === 0x02 && channel_type !== 0xc8) {decoded.din2 = bytes[i] === 0 ? "off" : "on";i += 1;}// Pulse Counter 1else if (channel_id === 0x01 && channel_type === 0xc8) {decoded.counter1 = readUInt32LE(bytes.slice(i, i + 4));i += 4;}// Pulse Counter 1else if (channel_id === 0x02 && channel_type === 0xc8) {decoded.counter2 = readUInt32LE(bytes.slice(i, i + 4));i += 4;}// Digital Output 1else if (channel_id === 0x09) {decoded.dout1 = bytes[i] === 0 ? "off" : "on";i += 1;}// Digital Output 2else if (channel_id === 0x0a) {decoded.dout2 = bytes[i] === 0 ? "off" : "on";i += 1;}// ADC 1else if (channel_id === 0x11) {decoded.adc1 = {};decoded.adc1.cur = readInt16LE(bytes.slice(i, i + 2)) / 100;decoded.adc1.min = readInt16LE(bytes.slice(i + 2, i + 4)) / 100;decoded.adc1.max = readInt16LE(bytes.slice(i + 4, i + 6)) / 100;decoded.adc1.avg = readInt16LE(bytes.slice(i + 6, i + 8)) / 100;i += 8;continue;}// ADC 2else if (channel_id === 0x12) {decoded.adc2 = {};decoded.adc2.cur = readInt16LE(bytes.slice(i, i + 2)) / 100;decoded.adc2.min = readInt16LE(bytes.slice(i + 2, i + 4)) / 100;decoded.adc2.max = readInt16LE(bytes.slice(i + 4, i + 6)) / 100;decoded.adc2.avg = readInt16LE(bytes.slice(i + 6, i + 8)) / 100;i += 8;continue;}// MODBUSelse if (channel_id === 0xFF && channel_type === 0x0E) {var modbus_chn_id = bytes[i++];var package_type = bytes[i++];var data_type = package_type & 7;var date_length = package_type >> 3;var chn = 'chn' + modbus_chn_id;switch (data_type) {case 0:decoded[chn] = bytes[i] ? "on" : "off";i += 1;break;case 1:decoded[chn] = bytes[i];i += 1;break;case 2:case 3:decoded[chn] = readUInt16LE(bytes.slice(i, i + 2));i += 2;break;case 4:case 6:decoded[chn] = readUInt32LE(bytes.slice(i, i + 4));i += 4;break;case 5:case 7:decoded[chn] = readFloatLE(bytes.slice(i, i + 4));i += 4;break;}}}return decoded;}/* ******************************************* bytes to number********************************************/function readUInt8LE(bytes) {return (bytes & 0xFF);}function readInt8LE(bytes) {var ref = readUInt8LE(bytes);return (ref > 0x7F) ? ref - 0x100 : ref;}function readUInt16LE(bytes) {var value = (bytes[1] << 8) + bytes[0];return (value & 0xFFFF);}function readInt16LE(bytes) {var ref = readUInt16LE(bytes);return (ref > 0x7FFF) ? ref - 0x10000 : ref;}function readUInt32LE(bytes) {var value = (bytes[3] << 24) + (bytes[2] << 16) + (bytes[1] << 8) + bytes[0];return (value & 0xFFFFFFFF);}function readInt32LE(bytes) {var ref = readUInt32LE(bytes);return (ref > 0x7FFFFFFF) ? ref - 0x100000000 : ref;}function readFloatLE(bytes) {// JavaScript bitwise operators yield a 32 bits integer, not a float.// Assume LSB (least significant byte first).var bits = bytes[3] << 24 | bytes[2] << 16 | bytes[1] << 8 | bytes[0];var sign = (bits >>> 31 === 0) ? 1.0 : -1.0;var e = bits >>> 23 & 0xff;var m = (e === 0) ? (bits & 0x7fffff) << 1 : (bits & 0x7fffff) | 0x800000;var f = sign * m * Math.pow(2, e - 150);return f;}

闽公网安备 35021102001670号