LoRaWAN设备与物联网平台的通信数据格式为透传/自定义,因此需要使用消息解析脚本,解析上下行数据。本文以LoRaWAN温湿度传感器为例,介绍LoRaWAN设备消息解析脚本的编辑和调试方法。

步骤一:编辑脚本

  1. 物联网平台控制台,创建连网方式为LoRaWAN的产品。
  2. 为该产品定义物模型。功能定义具体方法,请参见单个添加物模型

    本示例中,定义了以下属性、事件和服务:

    表 1. 属性
    标识符数据类型取值范围读写类型
    Temperatureint32-40~55读写
    Humidityint321~100读写
    表 2. 服务
    标识符调用方式输入参数
    SetTempHumiThreshold异步四个输入参数,数据类型均为int32:
    • 温度过高告警阈值(标识符:MaxTemp)
    • 温度过低告警阈值(标识符:MinTemp)
    • 湿度过高告警阈值(标识符:MaxHumi)
    • 湿度过低告警阈值(标识符:MinHumi)
    表 3. 事件
    标识符事件类型输入参数
    TempError告警温度Temperature
    HumiError告警湿度Humidity
  3. 编写脚本。

    在物联网平台控制台,产品详情页面的消息解析页签下,选择脚本语言,编写脚本。

    支持的脚本语言设备自定义数据格式转Alink JSON格式数据的函数(上行通信)Alink JSON格式数据转为设备自定义数据格式的函数(下行通信)
    JavaScript(ECMAScript 5)rawDataToProtocolprotocolToRawData
    Python 2.7raw_data_to_protocolprotocol_to_raw_data
    PHP 7.2rawDataToProtocolprotocolToRawData

    本文示例的语言为JavaScript(ECMAScript 5)。

    脚本中,解析下行数据的函数protocolToRawData中,需设定输出结果的起始三个字节,用于指定下行端口号和下行消息类型。否则,系统会丢掉下行帧。 设备实际接收的数据中不会包含这三个字节。

    表 4. 下行数据解析输出结果起始三字节
    LoRa Downlink字节数说明
    DFlag1固定为0x5D
    FPort1下行端口号。
    DHDR1可选:
    • 0:表示 “Unconfirmed Data Down”数据帧。
    • 1:表示 “Confirmed Data Down”数据帧。

    例如,0x5D 0x0A 0x00表示下行帧端口号为10,数据帧为“Unconfirmed Data Down”。

    完整的示例脚本Demo,请参见本文附录:示例脚本。

步骤二: 在线测试脚本

在数据解析编辑器中,使用模拟数据测试脚本。

  • 设备上报数据模拟解析 。

    模拟输入框中,选择模拟类型为设备上报数据,输入模拟数据000102,单击运行

    运行结果为:

    {
      "method": "thing.event.property.post",
      "id": "12345",
      "params": {
        "Temperature": 1,
        "Humidity": 2
      },
      "version": "1.1"
    }
  • 设备接收数据模拟解析 。

    选择模拟类型为设备接收数据,输入以下JSON格式的下行模拟数据,单击运行

    {
        "method": "thing.service.SetTempHumiThreshold",
        "id": "12345",
        "version": "1.1",
        "params": {
            "MaxTemp": 50,
            "MinTemp": 8,
            "MaxHumi": 90,
            "MinHumi": 10
        }
    }

    运行结果为:

    0x5d0a000332085a0a

步骤三:提交脚本

确认脚本可以正确解析数据后,单击提交,将该脚本提交到物联网平台系统,以供数据上下行时,物联网平台调用该脚本解析数据。

说明 仅提交后的脚本才能被物联网平台调用;草稿状态的脚本不能被调用。

步骤四:使用真实设备调试

脚本提交后,正式使用之前,请使用真实设备进行测试。LoRaWAN节点设备如何发送和接收数据,请参见模组厂商的相关手册。

  • 测试LoRaWAN设备上报温湿度属性。
    1. 使用设备端发送数据,例如000102
    2. 在该设备的设备详情物模型数据 > 运行状态页签下,查看设备上报的属性数据。
  • 测试LoRaWAN设备上报事件。
    1. 使用设备端发送事件数据,如,温度告警数据0102,或湿度告警数据0202
    2. 在该设备的设备详情物模型数据 > 事件管理页签下,查看设备上报的事件数据。
  • 测试调用LoRaWAN设备服务。
    1. 在物联网平台控制台,选择监控运维 > 在线调试
    2. 选择要调试的产品和设备,并选择调试真实设备,功能选择为温度湿度阈值(SetTempHumiThreshold),输入以下数据后,单击发送指令
      {
          "MaxTemp": 50,
          "MinTemp": 8,
          "MaxHumi": 90,
          "MinHumi": 10
      }
    3. 检查设备端是否接收到服务调用命令。
    4. 在该设备的设备详情物模型数据 > 服务调用页签下,查看设备服务调用数据。

附录:示例脚本

根据以上产品和其功能定义,编辑的示例脚本如下:

var ALINK_ID = "12345";
var ALINK_VERSION = "1.1";
var ALINK_PROP_POST_METHOD     = 'thing.event.property.post';
var ALINK_EVENT_TEMPERR_METHOD = 'thing.event.TempError.post';
var ALINK_EVENT_HUMIERR_METHOD = 'thing.event.HumiError.post';
var ALINK_PROP_SET_METHOD      = 'thing.service.property.set';
var ALINK_SERVICE_THSET_METHOD = 'thing.service.SetTempHumiThreshold';
/*
 * 示例数据:
 *  传入参数:
 *      000102 // 共3个字节
 *  输出结果:
 *      {"method":"thing.event.property.post", "id":"12345", "params":{"Temperature":1,"Humidity":2}, "version":"1.1"}
 *  传入参数:
 *      0102 // 共2个字节
 *  输出结果:
 *      {"method":"thing.event.TempError.post","id":"12345","params":{"Temperature":2},"version":"1.1"}
 *  传入参数:
 *      0202 // 共2个字节
 *  输出结果:
 *     {"method":"thing.event.HumiError.post","id":"12345","params":{"Humidity":2},"version":"1.1"}
 */
function rawDataToProtocol(bytes)
{
    var uint8Array = new Uint8Array(bytes.length);
    for (var i = 0; i < bytes.length; i++)
    {
        uint8Array[i] = bytes[i] & 0xff;
    }
    var params = {};
    var jsonMap = {};
    var dataView = new DataView(uint8Array.buffer, 0);
    var cmd = uint8Array[0]; // command
    if (cmd === 0x00)
    {
        params['Temperature']  = dataView.getInt8(1);
        params['Humidity']     = dataView.getInt8(2);
        jsonMap['method']  = ALINK_PROP_POST_METHOD;
    }
    else if (cmd == 0x01)
    {
        params['Temperature']  = dataView.getInt8(1);
        jsonMap['method']  = ALINK_EVENT_TEMPERR_METHOD;
    }
    else if (cmd == 0x02)
    {
        params['Humidity']  = dataView.getInt8(1);
        jsonMap['method']  = ALINK_EVENT_HUMIERR_METHOD;
    }
    else
    {
        return null;
    }
    jsonMap['version'] = ALINK_VERSION;
    jsonMap['id']      = ALINK_ID;
    jsonMap['params']  = params;
    return jsonMap;
}
/*
 * 示例数据:
 *  传入参数:
 *      {"method":"thing.service.SetTempHumiThreshold", "id":"12345", "version":"1.1", "params":{"MaxTemp":50, "MinTemp":8, "MaxHumi":90, "MinHumi":10}}
 *  输出结果:
 *      0x5d0a000332085a0a
 */
function protocolToRawData(json)
{
    var id  = json['id'];
    var method  = json['method'];
    var version = json['version'];
    var payloadArray = [];
    // 追加下行帧头部。
    payloadArray = payloadArray.concat(0x5d);
    payloadArray = payloadArray.concat(0x0a);
    payloadArray = payloadArray.concat(0x00);
    if (method == ALINK_SERVICE_THSET_METHOD)
    {
        var params  = json['params'];
        var maxtemp = params['MaxTemp'];
        var mintemp = params['MinTemp'];
        var maxhumi = params['MaxHumi'];
        var minhumi = params['MinHumi'];
        payloadArray = payloadArray.concat(0x03);
        if (maxtemp !== null)
        {
            payloadArray = payloadArray.concat(maxtemp);
        }
        if (mintemp !== null)
        {
            payloadArray = payloadArray.concat(mintemp);
        }
        if (maxhumi !== null)
        {
            payloadArray = payloadArray.concat(maxhumi);
        }
        if (minhumi !== null)
        {
            payloadArray = payloadArray.concat(minhumi);
        }
    }
    return payloadArray;
}

/**
 * 将设备自定义Topic数据转换为JSON格式数据,设备上报数据到物联网平台时调用。
 * 入参:topic,字符串,设备上报消息的Topic。    
 * 入参:rawData,byte[]数组,不能为空。
 * 出参:jsonObj,对象,不能为空。
 */
function transformPayload(topic, rawData) {
    var jsonObj = {}
    return jsonObj;
}

// 以下是部分辅助函数。
function buffer_uint8(value)
{
    var uint8Array = new Uint8Array(1);
    var dv = new DataView(uint8Array.buffer, 0);
    dv.setUint8(0, value);
    return [].slice.call(uint8Array);
}
function buffer_int16(value)
{
    var uint8Array = new Uint8Array(2);
    var dv = new DataView(uint8Array.buffer, 0);
    dv.setInt16(0, value);
    return [].slice.call(uint8Array);
}
function buffer_int32(value)
{
    var uint8Array = new Uint8Array(4);
    var dv = new DataView(uint8Array.buffer, 0);
    dv.setInt32(0, value);
    return [].slice.call(uint8Array);
}
function buffer_float32(value)
{
    var uint8Array = new Uint8Array(4);
    var dv = new DataView(uint8Array.buffer, 0);
    dv.setFloat32(0, value);
    return [].slice.call(uint8Array);
}

相关文档