产品定价 立即试用
云平台
欧洲地区
文档 > 集成 > ChirpStack
入门
指南 API 常见问题
目录

ChirpStack集成

文档信息图标
ThingsBoard PE 功能

专业版支持Platform Integrations功能。
请使用ThingsBoard Cloud自行安装平台实例。

ChirpStack开源LoRaWAN网络服务器栈为LoRaWAN网络提供开源组件。将ChirpStack与ThingsBoard集成后,可在ThingsBoard IoT平台中连接、通信、处理和可视化设备数据。

前置条件

要接收数据,需具备已配置的ChirpStack网络服务器栈实例。本指南使用通过Docker Compose安装的本地实例。
点击此处了解如何使用Docker Compose安装ChirpStack网络服务器

此外,需将设备连接到网络。可在ChirpStack官方设备连接指南中找到详细说明。

创建ChirpStack集成

您需要拥有ThingsBoard专业版的访问权限。最简单的方式是使用ThingsBoard Cloud服务器。 另一种方式是参照安装指南安装ThingsBoard。


接下来,我们开始设置ThingsBoard平台与ChirpStack之间的集成。

1. 基本设置

  • 登录您的ThingsBoard账户。
  • 导航到”Integrations center“部分下的”Integrations“页面,点击”plus“按钮添加新集成。
  • 从列表中选择集成类型”ChirpStack“。
  • 如果您希望监控事件并进行故障排查,请启用调试模式

调试模式

启用调试模式可追踪与integrations执行相关的事件、状态及潜在错误,便于开发和排障。

文档信息图标

注意:调试模式可能迅速增加磁盘占用,因为所有调试事件都会存入数据库。 自ThingsBoard 3.9起,平台仅在integrations创建后的前15分钟内存储完整调试事件,之后仅保留错误事件。

调试模式设置可组合使用或完全关闭。

  • 点击”Next“。

image


2. 上行数据转换器

上行转换器用于将设备传入的数据转换为ThingsBoard所需的显示格式。

ThingsBoard 4.0开始,我们简化了为Loriot集成编写转换器的流程。现在您可以轻松选择集成消息字段的去向(属性或遥测),无需在解码函数中手动定义。

注意:在ThingsBoard 4.0发布之前创建的转换器仍然可用,并将继续正常运行。

  • 输入转换器名称,名称必须唯一。
  • 如需查看事件,请启用调试模式
  • 在”Main decoding configuration“部分:
    • 选择集成结果将创建的实体类型(DeviceAsset),并指定实体名称。$eui模式将从Loriot消息中动态获取设备的唯一标识符。
    • 使用现有脚本解析和转换数据,或提供您自己的自定义脚本。
文档信息图标

注意:以下所示的转换器仅适用于ThingsBoard 3.9及更早版本

可使用 TBEL(TBEL)或 JavaScript 开发用户自定义函数。 建议使用 TBEL,其在ThingsBoard 中的执行效率远高于 JS。

示例中使用的脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
var data = decodeToJson(payload);
var deviceName = data.deviceInfo.deviceName;
var deviceType = data.deviceInfo.deviceProfileName;
var groupName = 'IAQ devices';
// var customerName = 'Customer A';
// use assetName and assetType instead of deviceName and deviceType
// to automatically create assets instead of devices.
// var assetName = 'Asset A';
// var assetType = 'building';

// If you want to parse incoming data somehow, you can add your code to this function.
// input: bytes
// expected output:
//  {
//    "attributes": {"attributeKey": "attributeValue"},
//    "telemetry": {"telemetryKey": "telemetryValue"}
//  }
//
// In the example - bytes will be saved as HEX string and also parsed as light level, battery level and PIR sensor value.
//

function decodePayload(input) {
var output = { attributes:{}, telemetry: {} };
// --- Decoding code --- //

    output.telemetry.HEX_bytes = bytesToHex(input);

    // If the length of the input byte array is odd - we cannot parse it using the example below
    if (input.length > 0) {
        for (var i = 0; i < input.length; ) {
            var channel_id = input[i++];
            if (i < input.length) {
                var channel_type = input[i++];
                // BATTERY
                if (channel_id === 0x01 && channel_type === 0x75) {
                    output.telemetry.battery = input[i];
                    i += 1;
                }
                // PIR
                else if (channel_id === 0x03 && channel_type === 0x00) {
                    output.telemetry.pir = input[i] === 0 ? "normal" : "trigger";
                    i += 1;
                }
                // DAYLIGHT
                else if (channel_id === 0x04 && channel_type === 0x00) {
                    output.telemetry.daylight = input[i] === 0 ? "dark" : "light";
                    i += 1;
                }
            }
        }
    }

    // --- Decoding code --- //
    return output;
}

// --- attributes and telemetry objects ---
var telemetry = {};
var attributes = {};
// --- attributes and telemetry objects ---

// --- Timestamp parsing
var dateString = data.time;
var timestamp = -1;
if (dateString != null) {
  timestamp = new Date(dateString).getTime();
  if (timestamp == -1) {
    var secondsSeparatorIndex = dateString.lastIndexOf('.') + 1;
    var millisecondsEndIndex = dateString.lastIndexOf('+');
    if (millisecondsEndIndex == -1) {
      millisecondsEndIndex = dateString.lastIndexOf('Z');
    }
    if (millisecondsEndIndex == -1) {
      millisecondsEndIndex = dateString.lastIndexOf('-');
    }
    if (millisecondsEndIndex == -1) {
      if (dateString.length >= secondsSeparatorIndex + 3) {
        dateString = dateString.substring(0, secondsSeparatorIndex + 3);
      }
    } else {
      dateString = dateString.substring(0, secondsSeparatorIndex + 3) +
        dateString.substring(millisecondsEndIndex, dateString.length);
    }
    timestamp = new Date(dateString).getTime();
  }
}
// If we cannot parse timestamp - we will use the current timestamp
if (timestamp == -1) {
timestamp = Date.now();
}
// --- Timestamp parsing

// You can add some keys manually to attributes or telemetry
attributes.deduplicationId = data.deduplicationId;

// You can exclude some keys from the result
var excludeFromAttributesList = ["deviceName", "rxInfo", "confirmed", "data", "deduplicationId","time", "adr", "dr", "fCnt"];
var excludeFromTelemetryList = ["data", "deviceInfo", "txInfo", "devAddr", "adr", "time", "fPort", "region_common_name", "region_config_id", "deduplicationId"];

// Message parsing
// To avoid paths in the decoded objects we passing false value to function as "pathInKey" argument.
// Warning: pathInKey can cause already found fields to be overwritten with the last value found.

var telemetryData = toFlatMap(data, excludeFromTelemetryList, false);
var attributesData = toFlatMap(data, excludeFromAttributesList, false);

var uplinkDataList = [];

// Passing incoming bytes to decodePayload function, to get custom decoding
var customDecoding = decodePayload(base64ToBytes(data.data));

// Collecting data to result
if (customDecoding.?telemetry.size() > 0) {
  telemetry.putAll(customDecoding.telemetry);
}

if (customDecoding.?attributes.size() > 0) {
  attributes.putAll(customDecoding.attributes);
}

telemetry.putAll(telemetryData);
attributes.putAll(attributesData);

var result = {
    deviceName: deviceName,
    deviceType: deviceType,
    //  assetName: assetName,
    //  assetType: assetType,
    //  customerName: customerName,
    groupName: groupName,
    attributes: attributes,
    telemetry: {
    ts: timestamp,
    values: telemetry
    }
};

return result;

示例中使用的脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// Decode an uplink message from a buffer
// payload - array of bytes
// metadata - key/value object

/** Decoder **/

// decode payload to string
var payloadStr = decodeToString(payload);

// decode payload to JSON
// var data = decodeToJson(payload);

var deviceName = 'Device A';
var deviceType = 'thermostat';
var customerName = 'Customer C';
var groupName = 'thermostat devices';
var manufacturer = 'Example corporation';
// use assetName and assetType instead of deviceName and deviceType
// to automatically create assets instead of devices.
// var assetName = 'Asset A';
// var assetType = 'building';

// Result object with device/asset attributes/telemetry data
var result = {
// Use deviceName and deviceType or assetName and assetType, but not both.
  deviceName: deviceName,
  deviceType: deviceType,
// assetName: assetName,
// assetType: assetType,
// customerName: customerName,
  groupName: groupName,
  attributes: {
    model: 'Model A',
    serialNumber: 'SN111',
    integrationName: metadata['integrationName'],
    manufacturer: manufacturer
  },
  telemetry: {
    temperature: 42,
    humidity: 80,
    rawData: payloadStr
  }
};

/** Helper functions **/

function decodeToString(payload) {
  return String.fromCharCode.apply(String, payload);
}

function decodeToJson(payload) {
  // covert payload to string.
  var str = decodeToString(payload);

  // parse string to JSON
  var data = JSON.parse(str);
  return data;
}

return result;

image

  • Advanced decoding parameters“部分:
    • Device profileDevice labelCustomer nameDevice group name字段不是必填项,您也可以使用$模式动态填充它们。
    • AttributesTelemetry部分,分别指定应解释为属性和遥测的键。
    • Update only keys list部分,定义仅在值与上一条传入消息相比发生变化时才保存到数据库的键。这适用于属性和遥测,有助于优化数据存储。
  • 上行转换器设置完成后,点击”Next“。

image


3. 下行数据转换器

在添加下行转换器的步骤中,您也可以选择之前创建的转换器或创建新的下行转换器。但目前先将”Downlink data converter”字段留空,点击”Skip“;

image


4. 连接

要完成集成的添加,您需要:

  • 指定您的”Base URL”;
  • 记下”HTTP endpoint URL”,我们稍后会用到此值;
  • 指定”Application server URL”——应用服务器或REST API服务的地址。通常在标准安装中,只需将端口更改为8090;
  • 指定”Application server API Token”——从应用服务器获取。获取方法:打开ChirpStack应用服务器界面,从左上角菜单导航到”API keys”页面,创建新的API密钥。

最后,点击”Add”按钮完成ChirpStack集成的添加。

image

在ChirpStack应用中配置集成

为了将数据从ChirpStack传输到ThingsBoard,您需要在ChirpStack应用中配置集成。

要在ChirpStack网络服务器栈上创建集成,需执行以下步骤:

  • 在ChirpStack网络服务器界面的左侧菜单中进入”Applications”页面,点击”Add application”按钮;
  • 为其命名并点击”Submit”按钮;
  • 应用创建完成。现在导航到”Integrations”选项卡;
  • 点击”+”图标找到并添加HTTP集成;
  • 在字段中填入之前从ThingsBoard中ChirpStack集成复制的”HTTP endpoint URL”,然后点击”Submit”按钮。

HTTP集成创建完成。

处理上行消息

当您的设备发送上行消息时,ThingsBoard用户界面中将出现一个新设备。

您将在ChirpStack集成中收到一个上行事件。

接收到的数据可在上行转换器中查看,位于”Events”选项卡的”In”和”Out”区块中:


使用仪表板处理数据。仪表板是一种用于收集和可视化数据集的现代化格式,通过多种部件实现数据展示的可视性。 ThingsBoard提供了多种类型的仪表板示例供您使用。在此处了解更多关于解决方案模板的信息。

高级用法:下行

要从ThingsBoard向设备发送下行消息,我们需要定义一个下行转换器。您可以根据自己的配置自定义下行消息。

添加下行转换器

让我们看一个发送属性更新消息的示例。

可使用 TBEL(TBEL)或 JavaScript 开发用户自定义函数。 建议使用 TBEL,其在ThingsBoard 中的执行效率远高于 JS。

可使用我们的 downlink converter 示例,或根据您的配置自行编写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Encode downlink data from incoming Rule Engine message

// msg - JSON message payload downlink message json
// msgType - type of message, for ex. 'ATTRIBUTES_UPDATED', 'POST_TELEMETRY_REQUEST', etc.
// metadata - list of key-value pairs with additional data about the message
// integrationMetadata - list of key-value pairs with additional data defined in Integration executing this converter

/** Encoder **/

// Result object with encoded downlink payload
var result = {

    // downlink data content type: JSON, TEXT or BINARY (base64 format)
    contentType: "TEXT",

    // downlink data
    data: btoa(msg.downlink),

    // Optional metadata object presented in key/value format
    metadata: {
            DevEUI: metadata.cs_devEui,
            fPort: metadata.cs_fPort
    }

};

return result;

按以下步骤向 integration 添加 downlink converter:

  • Go to the “Integrations” page, click ChirpStack integration to open its details, and enter integration editing mode by clicking the “pencil” icon;

  • Enter a name for the downlink data converter and click “Create new converter”;

  • Paste the script to the encoder function section, and click “Add”;

  • Apply changes.

可使用我们的 downlink converter 示例,或根据您的配置自行编写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Encode downlink data from incoming Rule Engine message

// msg - JSON message payload downlink message json
// msgType - type of message, for ex. 'ATTRIBUTES_UPDATED', 'POST_TELEMETRY_REQUEST', etc.
// metadata - list of key-value pairs with additional data about the message
// integrationMetadata - list of key-value pairs with additional data defined in Integration executing this converter

/** Encoder **/

// Result object with encoded downlink payload
var result = {

    // downlink data content type: JSON, TEXT or BINARY (base64 format)
    contentType: "TEXT",

    // downlink data
    data: btoa(msg.downlink),

    // Optional metadata object presented in key/value format
    metadata: {
            DevEUI: metadata.cs_devEui,
            fPort: metadata.cs_fPort
    }

};

return result;

按以下步骤向 integration 添加 downlink converter:

  • Go to the “Integrations” page, click ChirpStack integration to open its details, and enter integration editing mode by clicking the “pencil” icon;

  • Enter a name for the downlink data converter and click “Create new converter”;

  • Paste the script to the encoder function section, and click “Add”;

  • Apply changes.

您可以在创建或编辑集成时添加下行转换器。

修改根规则链

为了发送下行消息,我们将使用规则链来处理共享属性更新。让我们导入此规则链:

  • 下载downlink_to_chirpstack.json文件;
  • 前往”Rule Chains”页面。要导入此JSON文件,点击屏幕右上角的”+”图标并选择”Import rule chain”;
  • 将下载的JSON文件拖入导入规则链窗口,点击”Import”;
  • “Downlink to Chirpstack”规则链将打开。双击”integration downlink”节点,在”Integration”字段中指定ChirpStack集成并保存更改;
  • 点击对勾保存规则链。

现在您需要配置根规则链

  • Open the "Root Rule Chain", and find a "check relation presence" node;
  • Drag it to the rule chain. Name it "Check relation to ChirpStack integration", select the direction - "To originator", specify "ManagedByOriginator" relation type. Specify ChirpStack integration and click "Add";
  • Tap on a right grey circle of "message type switch" node and drag this circle to the left side of "check relation presence" node. Here, add the "Attributes Updated" link, and click "Add";
  • Find a "rule chain" node;
  • Drag it to the rule chain. Name it "Downlink to Chirpstack", specify "Downlink to Chirpstack" rule chain, and click "Add";
  • Tap on the right grey circle of the "check relation presence" node and drag this circle to left side of “rule chain” node. Here, select the "True" link, and click "Add". Finally, save Root Rule Chain.

测试下行

当添加或修改属性时,下行消息将被发送到集成。

通过示例来演示:前往”Devices”页面,选择您的设备并导航到”Attributes”选项卡。 选择”Shared attributes”并点击”plus”图标添加新属性。然后输入属性名称及其值(例如,键名为”downlink”,值为”01040203”),点击”Add”。

接收到的数据和已发送的数据可在下行转换器中查看。在”Events”选项卡的”In”区块中可以看到输入的数据,”Out”字段则显示发送到设备的消息:

下一步