Skip to main content

使用 Amazon Sidewalk 和 LoRaWAN 网络实现无缝资产跟踪

介绍

通过这个双设备演示体验无缝网络集成。该设置包含多功能的 Seeed Studio Wio Tracker 开发板和强大的 Seeed Studio SenseCAP T1000-S 跟踪器设备,两者都展示了在 LoRaWAN 和 Sidewalk 网络之间的无缝切换,以实现最佳覆盖。

  • 双网络支持:只需按一下按钮,即可在 LoRaWAN 和 Sidewalk 之间切换,保持持续连接。
  • 云端连接:实时观察数据传输到 AWS IoT Core,通过笔记本电脑显示器/监视器上的 AWS Web 应用程序进行可视化。
  • 电池供电效率:设备确保持续运行,无需电线束缚,实现真正的移动体验。

该过程分为以下主要步骤:

先决条件

安装 nRF Connect SDK

有多种方式安装 nRF Connect SDK,取决于您首选的开发环境和工具链管理工具

  • 使用 Visual Studio Code 和 nRF Connect for VS Code 扩展(推荐)

  • 使用命令行和 nRF Util

步骤 1:更新操作系统

在开始设置工具链之前,请为您的操作系统安装可用更新。有关支持的操作系统信息,请参阅要求

步骤 2:安装先决条件

步骤 3:安装 nRF Connect SDK 工具链

  • 通过点击 Activity Bar 中的图标在 Visual Studio Code 中打开 nRF Connect 扩展。

  • 在扩展的 Welcome View 中,点击 Install Toolchain

  • 选择要安装的工具链版本。工具链版本应与您将要使用的 nRF Connect SDK 版本匹配。我们使用 V2.5.0(推荐)。

安装工具链后,您可以通过点击 Manage toolchains 来访问 Install Toolchain 选项。

步骤 4:获取 nRF Connect SDK 代码

要克隆 nRF Connect SDK 代码,请完成以下步骤:

  • 通过点击 Activity Bar 中的图标在 Visual Studio Code 中打开 nRF Connect 扩展。

  • 在扩展的 Welcome View 中,点击 Manage SDKs。操作列表将出现在 Visual Studio Code 的快速选择中。

  • 点击 Install SDK。可用的 SDK 版本列表将出现在 Visual Studio Code 的快速选择中。

  • 选择要安装的 SDK 版本,我们使用 V2.5.0

SDK 安装开始,可能需要几分钟时间。

设置 Sidewalk 环境

按照以下步骤为 nRF Connect SDK 下载 Sidewalk 应用程序:

  • 打开终端窗口。您的目录结构应如下所示:
.
|___ .west
|___ bootloader
|___ modules
|___ nrf
|___ nrfxlib
|___ zephyr
|___ ...
  • 确保清单路径指向 nrf 目录内的 west.yml:
west manifest --path
/path-to-ncs-folder/nrf/west.yml

如果你的清单路径指向不同的文件,请使用以下命令:

west config manifest.path nrf
  • 为西部启用人行道组过滤器。
west config manifest.group-filter "+sidewalk"

检查西边是否存在人行道:

west list sidewalk
sidewalk sidewalk <sidewalk_revision> https://github.com/nrfconnect/sdk-sidewalk
  • Update all repositories:
west update

根据您的网络连接情况,更新可能需要一些时间。

  • 安装 Sidewalk 的 Python 依赖项。
pip install -r sidewalk/requirements.txt

将 LR11xx 添加到 nRF Connect SDK Sidewalk 扩展

此仓库包含软件驱动程序,使 LR11xx 系列 芯片能够在与 Nordic nRF52840 MCU 和 nRF Connect SDK 配对时支持 Sidewalk 协议。该驱动程序以二进制形式提供,作为静态库实现支持 LoRa 或 FSK 连接所需的"平台抽象层"接口。静态库内部包含 Semtech SWDR001(LR11xx 驱动程序)的完整实现,可用于访问 LR11xx 芯片的其他功能,如 WIFI 和 GNSS 扫描及测距。

  • 下载 SWDM016

  • 在克隆的 nordic 仓库的工作目录中,位于顶级目录,即 ~/ncs/<version>/sidewalk

patch -p1 < ../nRF52840_LR11xx_driver_v010000.diff

父目录路径 .. 假设您将差异文件放在那里,否则您可以指定其位置的完整路径。

  • 将无线电驱动程序库 lib*.a 复制到 sidewalk 项目的 ~/ncs/<version>/sidewalk/lib/lora_fsk/ 目录中
    提供了两个库,一个启用了 LOG_RUNTIME_FILTERING,另一个没有启用。

  • ~/template_lbm_wio_tracker/boards/arm/wio_tracker_1110 文件夹复制到 ~/ncs/v2.5.0/zephyr/boards/arm

·
├─── .west/
├─── modules/
├─── nrf/
├─── ...
└─── zephyr/
└─── Boards/
└─── arm/
└─── wio_tracker_1110/

创建资源

步骤 1:部署 Cloud9 环境

在本节中,您将创建开始之前所需的所有资源。首先,您将创建一个 Cloud9 工作空间,用于构建和部署其他资源。然后,您将部署一个包含资产跟踪器应用程序所有后端资源的 CDK 堆栈。最后,您将安装所有前端依赖项并配置应用程序。

  • 除了 实例类型 外,其他所有设置保持默认。选择 m5.large

步骤 2:配置先决条件

  • 打开 Cloud9 IDE。
  • 在您的 Cloud9 环境终端中输入以下命令来克隆 github 仓库:
git clone --recurse-submodules https://github.com/aws-samples/aws-iot-asset-tracker-demo.git /home/ec2-user/environment/asset-tracking-workshop
  • 导航到示例应用程序目录:
cd ~/environment/asset-tracking-workshop
  • 调整底层 EC2 实例的 EBS 卷大小。
npm run utils:resizeC9EBS
  • Install the project's dependencies:
npm ci
  • Deploy the backend infrastructure:
# Prepare the AWS account for CDK
npm run infra:bootstrap
# Deploy the backend resources
npm run infra:deploy
  • Create a config file:
npm run utils:createConfig

LoRaWAN 配置

在 AWS 上添加 LoRaWAN 网关

查看此入门指南,将 SenseCAP M2 多平台网关添加到 AWS IoT Core。

在 AWS 上添加 LoRaWAN 设备

步骤 1:定义密钥

src/lorawan_v4/example_options.h 中定义 DevEUI/JoinEUI/APPkeyREGION

tip

JoinEUI 也称为 AppEUI

步骤 2:创建配置文件

登录到 AWS IoT 控制台,导航到 Devices,点击 Profiles

  • 设备配置文件

设备配置文件定义了网络服务器用于设置 LoRaWAN 无线接入服务的设备功能和启动参数。它包括 LoRa 频段、LoRa 区域参数版本和设备 MAC 版本等参数的选择。

要了解不同的频段,请参阅为您的网关和设备连接考虑选择 LoRa 频段

  • 服务配置文件

我们建议您保持 AddGWMetaData 设置启用,这样您将收到每个有效载荷的额外网关元数据,例如数据传输的 RSSI 和 SNR。

步骤 3:添加设备

导航到 LPWAN devices > Devices,点击 Add wireless device

Wireless device specification:OTAAv1.0x

pir

选择您在上一步中创建的设备配置文件和目标。

pir

导航到设备页面并选择您之前添加的设备。

Sidewalk 配置

设置 Sidewalk 网关(可选)

您可以设置 Sidewalk 网关,配置它,并将您的网关与您的 Amazon 账户关联。您的 Sidewalk 端点在注册到 Amazon Sidewalk 后将连接到 Sidewalk 网关并与其通信。

查看设置 Sidewalk 网关了解更多详情。

设置您的 Sidewalk 设备

添加您的 Sidewalk 设备

步骤 1:添加您的设备配置文件和 Sidewalk 终端设备

在创建无线设备之前,首先创建设备配置文件。

导航到设备中心的 Sidewalk 选项卡,选择 Provision device,然后执行以下步骤。

步骤 2:获取设备 JSON 文件

要获取用于配置您的 Sidewalk 设备的 JSON 文件:

  • 转到 Sidewalk 设备中心

  • 选择您添加到 AWS IoT Core for Amazon Sidewalk 的设备以查看其详细信息。

  • 通过在您添加的设备详细信息页面中选择 Download device JSON file 来获取 JSON 文件。

将下载一个包含配置您的终端设备所需信息的 certificate.json 文件。

pir

步骤 3:配置您的 Sidewalk 端点

生成二进制镜像

  • 安装需求文件

转到 Sidewalk SDK 文件夹 $[Amazon Sidewalk repository]/tools/scripts/public/provision/,然后运行以下命令来安装 requirements 文件。

pip3 install -r requirements.txt
  • 生成制造二进制镜像

运行 provision.py 脚本来生成制造二进制镜像文件,该文件将用于配置您用作 Sidewalk 端点的开发板。

  • 如果您使用的是从 AWS IoT 控制台获得的组合设备 JSON 文件,请在运行配置脚本时使用 certificate_json 参数来指定此文件作为输入。
python3 provision.py aws --output_bin mfg.bin --certificate_json certificate.json \ 
--config config/[device_vendor]/[device]_dk/config.yaml

如果您使用的是从 GetDeviceProfile 和 GetWirelessDevice API 操作响应中获得的单独设备 JSON 文件,请在运行配置脚本时使用 wireless_device_json 和 device_profile_json 参数来指定这些文件作为输入。

python3 provision.py aws --output_bin mfg.bin \  
--wireless_device_json wireless_device.json \
--device_profile_json device_profile.json \
--config config/[device_vendor]/[device]_dk/config.yaml

您应该看到以下输出:

  • 烧录 mfg.hex 文件

您的配置文件通常位于 EdgeDeviceProvisioning 目录中。

要烧录二进制镜像,请使用地址 0xFD000 在 Nordic Semiconductor HDK 上加载二进制镜像。有关烧录二进制镜像的信息,请参阅 Nordic Semiconductor 文档。

步骤 4:构建并烧录演示程序

  • 打开终端窗口。

  • 进入 template_lbm_wio_tracker 目录。

例如:

cd /opt/nordic/ncs/v2.5.0/sidewalk/samples/template_lbm_wio_tracker
  • 使用以下west命令构建应用程序:
west build --board wio_tracker_1110 -- -DRADIO=LR1110_SRC

或使用预编译的无线电驱动程序库:

west build --board wio_tracker_1110 -- -DRADIO=LR1110
  • 使用以下 west 命令刷写应用程序:
west flash

Sidewalk 注册

在您配置了 Sidewalk 端点后,必须注册该端点,以便它能够通过 Sidewalk 网络进行通信。

要注册您的 Sidewalk 端点,可以使用 Sidewalk 无忧网络 (FFN) 的自动无接触注册,或者使用运行注册脚本的 Mac 或原生 Ubuntu 机器手动注册您的设备。

标准 自动注册(使用 Sidewalk FFN)手动注册
用户和端点关联此注册方法不需要 Sidewalk 端点与用户之间的任何关联。端点可以加入 Sidewalk 网络而无需与任何用户关联。此注册方法需要 Sidewalk 端点与用户的 Amazon 账户之间的关联。
LWA(使用 Amazon 登录)不需要 LWA。需要 LWA 来链接用户的 Amazon 账户和 Sidewalk 端点开发者使用的 AWS 账户。

使用 Sidewalk FFN 执行注册:

  • 您的 Sidewalk 网关和端点必须已开机。
  • 您的网关必须已选择加入 Sidewalk,并且在端点的近距离范围内。我们建议您将设备保持在彼此 10 米范围内。

有关 手动 Sidewalk 注册 和其他详细信息,请查看 这里

网络切换

默认为 LoRaWAN 网络,点击 用户按钮 切换网络。

查看消息

添加目标

IoT Core 控制台 中,从左侧菜单选择 LPWAN devices,然后选择 Destinations

选择 Edit 并选择 Publish to AWS IoT Core message broker。在主题文本框中,输入 assets 作为 MQTT 主题。

Permissions 下选择 Create a new service role 并将 Role name 留空。

  • ExpressionType: MqttTopic
  • Expression: EmbeddedWorldTrackerDemo

添加解码器规则

导航到 Message routing 选项卡 → Rules,然后点击 Create Rule 按钮。

为您的规则命名并提交。

从 IoT Core 规则中,选择 Lambda 函数。然后点击 Create a Lambda function

从头开始创建
Function name: 为您的函数命名。
Runtime: Node.js 14.x
Architexture: x86_64

点击 Create function 按钮创建新函数

在以下函数配置页面上,删除所有代码并用以下脚本替换,然后点击 Deploy 按钮。

Lambda 代码
const {IoTDataPlaneClient, PublishCommand} = require("@aws-sdk/client-iot-data-plane");
const {IoTWirelessClient, GetWirelessDeviceCommand} = require("@aws-sdk/client-iot-wireless");
const client = new IoTDataPlaneClient({
"region": "us-east-1"
});
const wireless_client = new IoTWirelessClient({
"region": "us-east-1"
});

function decodeUplink(input) {
const originMessage = input.toLocaleUpperCase()
const decoded = {
valid: true,
err: 0,
payload: input,
messages: []
}
let measurement = messageAnalyzed(originMessage)
if (measurement.length === 0) {
decoded.valid = false
return {data: decoded}
}

for (let message of measurement) {
if (message.length === 0) {
continue
}
let elements = []
for (let element of message) {
if (element.errorCode) {
decoded.err = element.errorCode
decoded.errMessage = element.error
} else {
elements.push(element)
}
}
if (elements.length > 0) {
decoded.messages.push(elements)
}
}
return {data: decoded}
}

function messageAnalyzed(messageValue) {
try {
let frames = unpack(messageValue)
let measurementResultArray = []
for (let i = 0; i < frames.length; i++) {
let item = frames[i]
let dataId = item.dataId
let dataValue = item.dataValue
let measurementArray = deserialize(dataId, dataValue)
measurementResultArray.push(measurementArray)
}
return measurementResultArray
} catch (e) {
return e.toString()
}
}

function unpack(messageValue) {
return [{dataId: 0, dataValue: messageValue}]
}

function deserialize(dataId, dataValue) {
let measurementArray = null
measurementArray = [
{
measurementId: '4198',
type: 'Latitude',
measurementValue: parseFloat(getSensorValue(dataValue.substring(0, 8), 1000000))
},
{
measurementId: '4197',
type: 'Longitude',
measurementValue: parseFloat(getSensorValue(dataValue.substring(8, 16), 1000000))
},
{
measurementId: '4097',
type: 'Air Temperature',
measurementValue: getSensorValue(dataValue.substring(16, 20), 10)
},
{
measurementId: '4098',
type: 'Air Humidity',
measurementValue: getSensorValue(dataValue.substring(20, 22))
}
]
return measurementArray
}

function getSensorValue(str, dig) {
if (str === '8000') {
return null
} else {
return loraWANV2DataFormat(str, dig)
}
}

function bytes2HexString(arrBytes) {
var str = ''
for (var i = 0; i < arrBytes.length; i++) {
var tmp
var num = arrBytes[i]
if (num < 0) {
tmp = (255 + num + 1).toString(16)
} else {
tmp = num.toString(16)
}
if (tmp.length === 1) {
tmp = '0' + tmp
}
str += tmp
}
return str
}

function loraWANV2DataFormat(str, divisor = 1) {
let strReverse = bigEndianTransform(str)
let str2 = toBinary(strReverse)
if (str2.substring(0, 1) === '1') {
let arr = str2.split('')
let reverseArr = arr.map((item) => {
if (parseInt(item) === 1) {
return 0
} else {
return 1
}
})
str2 = parseInt(reverseArr.join(''), 2) + 1
return '-' + str2 / divisor
}
return parseInt(str2, 2) / divisor
}

function bigEndianTransform(data) {
let dataArray = []
for (let i = 0; i < data.length; i += 2) {
dataArray.push(data.substring(i, i + 2))
}
return dataArray
}

function toBinary(arr) {
let binaryData = arr.map((item) => {
let data = parseInt(item, 16)
.toString(2)
let dataLength = data.length
if (data.length !== 8) {
for (let i = 0; i < 8 - dataLength; i++) {
data = `0` + data
}
}
return data
})
return binaryData.toString().replace(/,/g, '')
}

exports.handler = async (event) => {
try {
let device_id = event['WirelessDeviceId'];
let lorawan_info = null;
let sidewalk_info = null;
let payload = null
let timestamp = null

let queryDeviceRequest = {
Identifier: device_id,
IdentifierType: "WirelessDeviceId"
}
let deviceInfo = await wireless_client.send(new GetWirelessDeviceCommand(queryDeviceRequest))
console.log("device_info:" + JSON.stringify(deviceInfo))
if (!deviceInfo || deviceInfo.name) {
return {
statusCode: 500,
body: 'can not find this wirelessDeviceId: ' + device_id
};
}
let device_name = deviceInfo.Name

if (event["WirelessMetadata"]["LoRaWAN"]) {
lorawan_info = event["WirelessMetadata"]["LoRaWAN"]
timestamp = lorawan_info["Timestamp"]
let bytes = Buffer.from(event["PayloadData"], 'base64');
payload = bytes2HexString(bytes)
} else if (event["WirelessMetadata"]["Sidewalk"]) {
timestamp = new Date().getTime()
let origin = new Buffer(event["PayloadData"], 'base64')
payload = origin.toString('utf8')
}

console.log(`event.PayloadData: ${payload}`)
const resolved_data = decodeUplink(payload);

// publish all measurement data
const input = { // PublishRequest
topic: `tracker/EmbeddedWorldTrackerDemo/sensor/${device_id}`,
qos: 0,
retain: false,
payload: JSON.stringify({
DeviceName: "assettracker",
timestamp: timestamp,
data: resolved_data.data,
WirelessDeviceId: device_id,
PayloadData: event['PayloadData'],
WirelessMetadata: event["WirelessMetadata"]
})
};

const command = new PublishCommand(input);
const response = await client.send(command);
console.log("response: " + JSON.stringify(response));
return {
statusCode: 200,
body: 'Message published successfully' + JSON.stringify(event)
};
} catch (error) {
console.error('Error publishing message:', error);

return {
statusCode: 500,
body: 'Error publishing message'
};
}
};

现在回到 Device Destination,选择 Enter a rule name 并输入我们刚刚创建的名称。

导航到 AWS IoT Core Console 并选择 MQTT Test Client 并订阅该主题。

添加跟踪器规则

重复上述步骤创建一个新规则,并复制以下 Lambda 代码:

Lambda 代码
const {IoTDataPlaneClient, PublishCommand} = require("@aws-sdk/client-iot-data-plane");

const {LocationClient, BatchUpdateDevicePositionCommand} = require("@aws-sdk/client-location")

const {IoTWirelessClient, UpdateResourcePositionCommand } = require("@aws-sdk/client-iot-wireless");
const client = new IoTDataPlaneClient({
"region": "us-east-1"
});
const wireless_client = new IoTWirelessClient({
"region": "us-east-1"
});

exports.handler = async (event) => {
console.log(`message received: ${JSON.stringify(event)}`)
let device_id = event['WirelessDeviceId']
let device_name = event['DeviceName']
let measurements = event['data']['messages']
let resolver_time = event['timestamp']
let network = 1; // 1: lorawan 2: sidewalk
if (event["WirelessMetadata"] && event["WirelessMetadata"]["Sidewalk"]) {
network = 2
}

let longitude;
let latitude;
let gps_data = null
let sensor_map = {}
if (measurements && measurements.length > 0) {
for (let i = 0; i < measurements.length; i++) {
for (let j = 0; j < measurements[i].length; j++) {
if (measurements[i][j].measurementId === "4097") {
sensor_map["Temperature"] = measurements[i][j].measurementValue;
}
if (measurements[i][j].measurementId === "4098") {
sensor_map["Humidity"] = measurements[i][j].measurementValue;
}
if (measurements[i][j].measurementId === "4197") {
longitude = measurements[i][j]["measurementValue"];
}
if (measurements[i][j].measurementId === "4198") {
latitude = measurements[i][j]["measurementValue"];
}

if (latitude && longitude) {
try {
gps_data = {
"type": "Point",
"coordinates": [longitude, latitude]
// "coordinates": [33.3318, -22.2155, 13.123]
}
} catch (e) {
console.log(`===>error`, e)
}
}
}
}
}

if (gps_data) {
console.log(`update device location : ${JSON.stringify(gps_data)}`)
await updateDevicePosition(gps_data, device_id);
const input = { // PublishRequest
topic: `tracker/EmbeddedWorldTrackerDemo/location/${device_id}`,
qos: 0,
retain: false,
payload: JSON.stringify({
timestamp: resolver_time,
deviceId: device_id,
deviceName: device_name,
latitude: gps_data.coordinates[1],
longitude: gps_data.coordinates[0],
positionProperties: {'batteryLevel': 90, "sensor:": 60}
})
};
const command = new PublishCommand(input);
const response = await client.send(command);
console.log("mqtt push response: " + JSON.stringify(response));

let locationClient = new LocationClient()
let location_info = {
TrackerName: 'AssetTracker',
Updates: [
{
DeviceId: 'assettracker',
SampleTime: new Date(resolver_time),
Position: [
gps_data.coordinates[0], gps_data.coordinates[1]
],
Accuracy: {
Horizontal: 1,
},
PositionProperties: {
"context": JSON.stringify({net: network}),
"sensor": JSON.stringify(sensor_map)
}
}
]
}
let loc_response = await locationClient.send(new BatchUpdateDevicePositionCommand(location_info))
console.log("loc update response: " + JSON.stringify(loc_response));

}
}

async function updateDevicePosition(gps_data, device_id) {
const input = { // UpdateResourcePositionRequest
ResourceIdentifier: device_id, // required
ResourceType: "WirelessDevice", // required
GeoJsonPayload: JSON.stringify(gps_data),
};
const command = new UpdateResourcePositionCommand(input);
const wireless_response = await wireless_client.send(command);
console.log(wireless_response)
}

构建 Web 应用

我们将部署必要的 Amazon Location Service 资源,以便在地图上显示我们的设备。

创建地图

首先,您需要创建一个新的 Amazon Location Service 地图资源。您将使用 AWS 控制台来完成此操作。

  • 打开 Amazon Location Service 控制台

  • 然后展开屏幕左侧的导航栏,选择地图。

  • 在此屏幕中,创建一个新地图:

  • 输入地图名称并选择 HERE Explore 地图样式,然后点击 创建地图

创建路由计算器

继续选择 HERE 作为数据提供商,点击 创建路由计算器 按钮。

创建跟踪器

导航到 跟踪器 -> 创建跟踪器

输入跟踪器名称,并在位置过滤下选择 基于时间的过滤

然后向下滚动,在 EventBridge 配置下标记 启用 EventBridge 事件 设置,然后点击 创建跟踪器

创建地理围栏集合

导航到 地理围栏集合,点击 创建地理围栏集合

显示 Web 应用

将应用部署到 Cloudfront

  • 在您的 Cloud9 终端中,导航到 /home/ec2-user/environment/asset-tracking-workshop
cd /home/ec2-user/environment/asset-tracking-workshop
  • Run the following command:
npm run frontend:publish
  • 完成后,您将收到网站URL。
  • 在浏览器中导航到此URL以查看您的跟踪应用程序。

资源

SWDM016

template_lbm_wio_tracker

Loading Comments...