EdgeX Foundry 开发设备服务的几个关键点 1.设备服务工作流 2.设备服务示例模板 2.1 SDK 相关工作流 2.2 template.c 的细节 3.删除设备的处理逻辑 4.增加设备 4.1 增加设备的方式 4.2 增加设备的处理逻辑 总结 EdgeX Foundry 开发设备服务的几个关键点 EdgeX Foundry 设备服务是与传感器/设备或物联网对象 (“物”) 交互的边缘连接器，它需要与各种设备交互，将数据传输到边缘计算系统，并根据需求执行控制命令。EdgeX Foundry 通过其模块化设计和丰富的工具集，使得设备服务的开发变得更加简单高效。我们还可以使用设备服务 SDK 来创建您自己的 EdgeX 设备服务，本文重点介绍使用 C-SDK 开发设备服务源代码的几个必须明确的关键点 。 1.设备服务工作流 以 设备服务 template.c 为例，作为一个虚拟设备服务，它可以将数据发送到核心数据微服务，用户可以通过命令和控制微服务发送命令并获得响应，虚拟设备服务的这些功能在执行功能或性能测试时不需要任何实际设备就可以使用。 注意点： 虚拟设备服务对核心数据和元数据微服务具有依赖性，因为初始化过程需要检查或注册配置文件，设备和值描述符，因此，核心数据和元数据服务必须在设备服务初始化之前完全启动。 服务启动时，服务会通过从 YAML 文件加载“值描述符”,“设备服务”,“设备描述”来初始化，并登记在元数据微服务中，YAML文件默认存在，也可以自行创建。虚拟设备服务为元数据微服务提供回调API来管理设备实例。 2.设备服务示例模板 2.1 SDK 相关工作流 EdgeX Foundry 的 C-SDK 提供了一个设备服务模板（位于 examples/template.c 强烈推荐先看一遍代码），它提供了一套规范化的开发示例，包括自定义回调函数、设备服务启动关闭、内存开辟释放等，是我们初次接触设备服务开发的良好开端。设备服务工作流在上一节有了一个大致的介绍，结合官方的指导文档 servicewritersguide.md (强烈推荐先阅读一遍)，这里进一步描述设备服务内部 SDK 相关的工作流，展示如下： 从根本上说，设备服务由多个回调组成，上图中的 SDK 内部工作流均在一系列回调函数中作处理。一个设备服务只有一个设备服务实例结构体指针 Implementation 每次调用回调时，该指针都会被传递，如初始化回调函数的第一个参数 (template_driver *) impl。 /* --- template.c ---- */ /* Initialize performs protocol-specific initialization for the device * service. */ static bool template_init ( void *impl, struct iot_logger_t *lc, const iot_data_t *config ) { template_driver *driver = (template_driver *) impl; iot_log_debug (lc, "Template Init."); return true; } 2.2 template.c 的细节 一个设备服务必须要自定义好的回调函数如下：每一个回调函数的实现正如上面提到的 template_init 那样。 /* Device Callbacks */ devsdk_callbacks *templateImpls = devsdk_callbacks_init ( template_init, template_get_handler, template_put_handler, template_stop, template_create_addr, template_free_addr, template_create_resource_attr, template_free_resource_attr ); 同时设备服务 SDK 还提供了一些非必要的回调函数，以便于开发者实现更灵活的需求，在 servicewritersguide.md 的 Optional handlers 部分有提到。举一个例子，一个设备服务实例可以实现 device_added、device_updated 和 device_removed 处理程序，然后它将收到服务管理的设备范围的更改通知。如果需要特殊的初始化/关闭操作来优化设备的使用，这可能会有所帮助。只需要将该函数实现，并注册进设备服务的回调初始化函数即可： //device_added 的具体实现 static void device_added( void *impl, const char *devname, const devsdk_protocols *protocols, const devsdk_device_resources *resources, bool adminEnabled) { openvino_driver *driver = (openvino_driver *)impl; iot_log_info(driver->lc, "New device added: %s", devname); } ... //主函数内的回调初始化 devsdk_callbacks *templateImpls = devsdk_callbacks_init ( template_init, template_get_handler, template_put_handler, template_stop, template_create_addr, template_free_addr, template_create_resource_attr, template_free_resource_attr ); //注册自定义的设备服务回调 devsdk_callbacks_set_listeners(templateImpls, device_added, NULL, NULL); 3.删除设备的处理逻辑 每删除一个设备，一定依次进入的回调函数是：template_free_resource_attr 和 template_free_addr 。 如果自定义回调 device_removed 已经初始化，则会优先进入该回调，然后再进入上面两个默认的释放内存处理回调之中。 4.增加设备 4.1 增加设备的方式 通过 EdgeX Foundry 官方 UI 界面（如下图所示）增加一个具体的设备，通过界面可视化引导，很容易完成以下步骤： SelectDeviceService SelectDeviceProfile DevicePrimary CreateAutoEvent CreateDeviceProtocol 注意点：CreateDeviceProtocol 为必须设置项，且需要跟自己的设备服务所规定的协议保持一致。 通过 API 控制命令，直接向 core-metadata 发送 POST 请求，即可一行命令生成新设备，如下所示： curl http://localhost:59881/api/v3/device -H "Content-Type: application/json" -X POST \ -d '[ { "apiVersion" : "v3", "device": { "name": "Device1", "description": "An Example Device", "adminState": "UNLOCKED", "operatingState": "UP", "serviceName": "device-template", "profileName": "template", "protocols": { "Others": { "key1": "xxxx", "key2": "yyyy", } } } } ]' 4.2 增加设备的处理逻辑 每增加一个设备，会先调用自定义回调 device_add （如果已经初始化） 然后在第一次获取设备资源，也就是 GET 请求时，会依次调用 template_create_resource_attr 、 template_create_addr 和 template_get_handler。 以后的 GET 请求只会直接调用 template_get_handler。 总结 本文深入探讨了使用 EdgeX Foundry C-SDK 开发设备服务的几个关键点，为开发者提供了一个清晰的指导框架。通过分析设备服务的工作流、模板代码的细节以及设备增删的处理逻辑，我们可以更高效地构建自定义设备服务。 首先，设备服务的工作流强调了其与核心数据和元数据微服务的依赖性，以及通过 YAML 文件初始化配置的重要性。其次，C-SDK 的模板（template.c）提供了一个规范化的开发起点，包含必备回调函数（如 init、get、put、stop）和可选回调（如 device_added），开发者可根据需求灵活扩展。再次，删除设备的逻辑涉及内存释放回调（如 template_free_addr），而增加设备则通过 UI 或 API 触发，并涉及初始化和资源获取的回调链。 总的来说，掌握这些关键点——理解工作流、熟悉模板结构、明确设备管理的回调逻辑——能够帮助开发者快速上手 EdgeX Foundry 设备服务开发，无论是构建虚拟服务还是连接真实设备。
在 ubuntu22.04 中 安装 EdgeX 并运行 device-sdk-c sample
在 ubuntu22.04 中 安装 EdgeX 并运行 device-sdk-c sample 1.克隆源码-EdgeX 2.安装工具 3.编译运行-EdgeX 4.克隆源码-SDK-C 5.添加第三方依赖库 6.编译-C SDK 7.编译运行 random 设备服务示例 总结 在 ubuntu22.04 中 安装 EdgeX 并运行 device-sdk-c sample EdgeX Foundry 作为一个开源的边缘计算框架，为开发者提供了一个灵活、可扩展的平台，用于构建和管理边缘设备及服务。然而，对于初次接触 EdgeX 的用户来说，从零开始安装并运行其相关组件（如 device-sdk-c 示例）可能会面临不少挑战。 无论是环境的配置、依赖的安装，还是编译与调试的过程，每一步都需要细致的操作和清晰的思路。本文将以 Ubuntu 22.04 为基础平台，详细记录如何从克隆 EdgeX 源码到成功运行 device-sdk-c 的 sample 示例的全过程。希望通过这篇博客，不仅能为新手提供一份实用的操作指南，也能让有经验的开发者在遇到问题时找到解决思路。 1.克隆源码-EdgeX 在开始安装 EdgeX 之前，我们需要先获取其源码，而 GitHub 是获取最新版本的最佳途径。为了让后续操作更加有条理，我们首先在用户主目录下创建一个专属的工作目录，用于存放 EdgeX 相关的文件。以下是具体操作： 打开终端，输入以下命令创建一个名为 edgex 的文件夹，并进入该目录： mkdir edgex && cd edgex 接下来，我们需要从 GitHub 上克隆 EdgeX 的 Compose 文件仓库，这是运行 EdgeX 服务的核心配置文件集合。执行以下命令： git clone https://github.com/edgexfoundry/edgex-compose.git cd edgex-compose 结果显示如下： root@ubuntu:/home/ubuntu# mkdir edgex && cd edgex root@ubuntu:/home/ubuntu/edgex# git clone https://github.com/edgexfoundry/edgex-compose.git Cloning into 'edgex-compose'... remote: Enumerating objects: 5826, done. remote: Counting objects: 100% (3563/3563), done. remote: Compressing objects: 100% (319/319), done. remote: Total 5826 (delta 3504), reused 3250 (delta 3244), pack-reused 2263 (from 3) Receiving objects: 100% (5826/5826), 1.59 MiB | 1.76 MiB/s, done. Resolving deltas: 100% (4949/4949), done. root@ubuntu:/home/ubuntu/edgex# ls edgex-compose 输出信息显示了克隆进度，最终 ls 命令确认目录中已存在 edgex-compose 文件夹，说明这一步成功完成。 2.安装工具 EdgeX 的部署离不开 Docker，因为它通过容器化技术简化了服务的管理和运行。因此，我们需要先在 Ubuntu 系统中安装 Docker。以下是详细的安装步骤： 使用官方脚本安装 Docker，并指定阿里云镜像源以加速下载： curl -fsSL https://get.docker.com -o get-docker.sh sh get-docker.sh --mirror Aliyun docker -v 第一条命令通过 curl 下载安装脚本并保存为 get-docker.sh 文件；第二条命令执行脚本，--mirror Aliyun 参数指定使用阿里云镜像源，避免默认源下载缓慢；最后通过 docker -v 检查版本，确保安装成功。安装过程的输出类似： root@ubuntu:/home/ubuntu# curl -fsSL https://get.docker.com -o get-docker.sh root@ubuntu:/home/ubuntu# sh get-docker.sh --mirror Aliyun # Executing docker install script, commit: 4c94a56999e10efcf48c5b8e3f6afea464f9108e ... Client: Docker Engine - Community Version: 28.0.1 API version: 1.48 ... root@ubuntu:/home/ubuntu# docker -v Docker version 28.0.1, build 068a01e 如果下载速度过慢，可能是镜像源的问题。这时可以调整 Ubuntu 的软件源以提高效率。首先检查系统版本： root@ubuntu:/home/ubuntu# more /etc/os-release PRETTY_NAME="Ubuntu 22.04.5 LTS" NAME="Ubuntu" VERSION_ID="22.04" VERSION="22.04.5 LTS (Jammy Jellyfish)" VERSION_CODENAME=jammy ID=ubuntu ID_LIKE=debian HOME_URL="https://www.ubuntu.com/" SUPPORT_URL="https://help.ubuntu.com/" BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/" PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-poli cy" UBUNTU_CODENAME=jammy 确认版本为 jammy 后，访问南京大学镜像站（https://mirror.nju.edu.cn/mirrorz-help/ubuntu/）, 获取对应源配置并编辑 /etc/apt/sources.list： # vim /etc/apt/sources.list deb https://mirror.nju.edu.cn/ubuntu/ jammy main restricted universe multiverse deb https://mirror.nju.edu.cn/ubuntu/ jammy-updates main restricted universe multiverse deb https://mirror.nju.edu.cn/ubuntu/ jammy-backports main restricted universe multiverse deb http://security.ubuntu.com/ubuntu/ jammy-security main restricted universe multiverse 启动 docker 并查看状态： root@ubuntu:/home/ubuntu# systemctl restart docker root@ubuntu:/home/ubuntu# systemctl status docker ● docker.service - Docker Application Container Engine Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset> Active: active (running) since Thu 2025-03-13 12:21:05 CST; 6s ago TriggeredBy: ● docker.socket Docs: https://docs.docker.com Main PID: 28525 (dockerd) Tasks: 10 Memory: 22.1M CPU: 1.228s CGroup: /system.slice/docker.service └─28525 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/con> Active: active (running) 表示 Docker 已正常运行，为后续 EdgeX 部署奠定了基础。 3.编译运行-EdgeX 有了 Docker 支持后，我们可以开始部署 EdgeX 服务。EdgeX 使用 Docker Compose 管理多个容器，因此需要进入 compose-builder 目录执行构建和运行命令： 进入目录并运行无安全模式的 EdgeX： cd /home/ubuntu/edgex/edgex-compose/compose-builder/ make run no-secty echo MQTT_VERBOSE= MQTT_VERBOSE= docker compose -p edgex -f docker-compose-base.yml -f add-postgres.yml -f add-mqtt-messagebus.yml -f add-mqtt-broker-mosquitto.yml convert --output docker-compose.yml.gen if [ "0" = "0" ]; then rm -rf ./gen_ext_compose; fi echo "# Generated with: Docker Compose version v2.33.1" > docker-compose.yml cat docker-compose.yml.gen >> docker-compose.yml rm docker-compose.yml.gen docker compose -p edgex up -d [+] Running 12/12 ✔ Container edgex-ui-go Running 0.0s ✔ Container edgex-postgres Running 0.0s ✔ Container edgex-mqtt-broker Running 0.0s ✔ Container edgex-core-keeper Running 0.0s ✔ Container edgex-core-metadata Running 0.0s ✔ Container edgex-core-common-config-bootstrapper Running 0.0s ✔ Container edgex-app-rules-engine Running 0.0s ✔ Container edgex-support-scheduler Running 0.0s ✔ Container edgex-core-data Running 0.0s ✔ Container edgex-kuiper Running 0.0s ✔ Container edgex-support-notifications Running 0.0s ✔ Container edgex-core-command Started 0.4s 这表明所有容器已启动，包括 UI、数据库和核心服务等。 查看容器运行状态，显示如下，每个容器显示 Up 状态，说明 EdgeX 已成功运行： docker compose ps NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS edgex-app-rules-engine nexus3.edgexfoundry.org:10004/app-service-configurable:latest "/app-service-config…" app-rules-engine 7 minutes ago Up 7 minutes 48095/tcp, 127.0.0.1:59701->59701/tcp edgex-core-command nexus3.edgexfoundry.org:10004/core-command:latest "/core-command --reg…" core-command 7 minutes ago Up 5 minutes 127.0.0.1:59882->59882/tcp edgex-core-common-config-bootstrapper nexus3.edgexfoundry.org:10004/core-common-config-bootstrapper:latest "entrypoint.sh /core…" core-common-config-bootstrapper 7 minutes ago Up 7 minutes edgex-core-data nexus3.edgexfoundry.org:10004/core-data:latest "/core-data --regist…" core-data 7 minutes ago Up 7 minutes 127.0.0.1:59880->59880/tcp edgex-core-keeper nexus3.edgexfoundry.org:10004/core-keeper:latest "/core-keeper" core-keeper 7 minutes ago Up 7 minutes 127.0.0.1:59890->59890/tcp edgex-core-metadata nexus3.edgexfoundry.org:10004/core-metadata:latest "/core-metadata ... 4.克隆源码-SDK-C 为了运行 device-sdk-c 的示例，我们还需要获取其源码。device-sdk-c 包含了丰富的 C 语言示例，是开发自定义设备服务的好起点： 在 /edgex 目录下克隆仓库： git clone https://github.com/edgexfoundry/device-sdk-c.git root@ubuntu:/home/ubuntu/edgex/edgex-compose/compose-builder# cd ../.. root@ubuntu:/home/ubuntu/edgex# ls edgex-compose root@ubuntu:/home/ubuntu/edgex# git clone https://github.com/edgexfoundry/device-sdk-c.git Cloning into 'device-sdk-c'... remote: Enumerating objects: 5641, done. remote: Counting objects: 100% (527/527), done. remote: Compressing objects: 100% (213/213), done. remote: Total 5641 (delta 419), reused 337 (delta 307), pack-reused 5114 (from 4) Receiving objects: 100% (5641/5641), 1.93 MiB | 1.70 MiB/s, done. Resolving deltas: 100% (3210/3210), done. 5.添加第三方依赖库 编译 device-sdk-c 需要一系列依赖库，参考官方 README（https://github.com/edgexfoundry/device-sdk-c/blob/main/README.md） A Linux build host A version of GCC supporting C11. CMake version 3 or greater and make. Development libraries and headers for: curl (version 7.56 or later) microhttpd (version 0.9) libyaml (version 0.1.6 or later) libcbor (version 0.5) paho (version 1.3.x) libuuid (from util-linux v2.x) IOTech's C utilities package (version 1.5) apt-get install libcurl4-openssl-dev libmicrohttpd-dev libyaml-dev libcbor-dev libpaho-mqtt-dev apt-get install lsb-release apt-transport-https curl gnupg curl -fsSL https://iotech.jfrog.io/artifactory/api/gpg/key/public | gpg --dearmor -o /usr/share/keyrings/iotech.gpg echo "deb [signed-by=/usr/share/keyrings/iotech.gpg] https://iotech.jfrog.io/iotech/debian-release $(lsb_release -cs) main" | tee -a /etc/apt/sources.list.d/iotech.list apt-get update apt-get install iotech-iot-1.5-dev apt install uuid-dev 6.编译-C SDK 在C SDK 顶层目录下, 运行 make，最终结果显示如下： root@ubuntu:/home/ubuntu/edgex/device-sdk-c# make ... [100%] Built target device-file 7.编译运行 random 设备服务示例 编译前的准备工作，参考https://github.com/edgexfoundry/device-sdk-c/blob/main/src/c/examples/random/README.md 此示例设备服务模拟了一个具有两个随机数生成器和一个可读取和写入状态的开关的设备。先配置环境变量，注意自己的路径： export CSDK_DIR=/home/logan/edgex/device-sdk-c/build/release/_CPack_Packages/Linux/TGZ/csdk-0.0.0 export LD_LIBRARY_PATH=$CSDK_DIR/lib:/opt/iotech/iot/1.5/lib 涉及头文件和库文件的环境变量很重要，并且运行程序的时候也需要用到。 在/random 目录下执行编译命令： gcc -I$CSDK_DIR/include -I/opt/iotech/iot/1.5/include -L$CSDK_DIR/lib -L/opt/iotech/iot/1.5/lib -o device-random device-random.c -lcsdk -liot 将编译好的可执行文件 device-random 复制到以下源文件目录，因为源文件目录提供了/res 里面包含预设的配置文件： cp device-sdk-c/build/release/c/examples/random/device-random device-sdk-c/src/c/examples/random/ cd /home/ubuntu/edgex/device-sdk-c/src/c/examples/random/ 执行以下命令：-o 表示覆盖默认配置，-cp 指定配置提供者 ./device-random -o -cp=keeper.http://localhost:59890 如果遇到报错，按以下方法解决： 安全 Token 报错：设置环境变量禁用安全服务： root@ubuntu:/home/ubuntu/edgex/device-sdk-c/src/c/examples/random# ./device-random -o -cp=keeper.http://localhost:59890 level=INFO ts=2025-03-13T06:44:41Z app=device-random msg="iot_threadpool_alloc (threads: 8 max_jobs: 0 default_priority: -1 affinity: -1)" level=INFO ts=2025-03-13T06:44:41Z app=device-random msg="iot_scheduler_alloc (priority: -1 affinity: -1)" level=ERROR ts=2025-03-13T06:44:41Z app=device-random msg="vault: Token file /tmp/edgex/secrets/device-random/secrets-token.json doesn't parse as JSON" Error: 4: Configuration is invalid 解决：需要关闭Edges安全服务： 参考https://docs.edgexfoundry.org/4.0/microservices/configuration/CommonEnvironmentVariables/#edgex_security_secret_store export EDGEX_SECURITY_SECRET_STORE="false" Curl 解析失败：编辑 /etc/hosts： level=DEBUG ts=2025-03-13T06:45:41Z app=device-random msg="mqtt: subscribing to edgex/configs/edgex/v4/core-common-config-bootstrapper/all-services/#" level=DEBUG ts=2025-03-13T06:45:41Z app=device-random msg="mqtt: subscribing to edgex/configs/edgex/v4/core-common-config-bootstrapper/device-services/#" level=ERROR ts=2025-03-13T06:45:42Z app=device-random msg="Curl failed with code 6 (Couldn't resolve host name)" level=ERROR ts=2025-03-13T06:45:43Z app=device-random msg="Curl failed with code 6 (Couldn't resolve host name)" level=ERROR ts=2025-03-13T06:45:44Z app=device-random msg="Curl failed with code 6 (Couldn't resolve host name)" level=ERROR ts=2025-03-13T06:45:45Z app=device-random msg="Curl failed with code 6 (Couldn't resolve host name)" level=ERROR ts=2025-03-13T06:45:46Z app=device-random msg="Curl failed with code 6 (Couldn't resolve host name)" level=ERROR ts=2025-03-13T06:45:47Z app=device-random msg="Curl failed with code 6 (Couldn't resolve host name)" 解决：在/etc/hosts 中添加127.0.0.1 edgex-core-metadata： /etc/hosts 127.0.0.1 localhost 127.0.0.1 edgex-core-metadata # The following lines are desirable for IPv6 capable hosts ::1 ip6-localhost ip6-loopback fe00::0 ip6-localnet ff00::0 ip6-mcastprefix ff02::1 ip6-allnodes ff02::2 ip6-allrouters ff02::3 ip6-allhosts 健康检查间隔缺失：修改 res/configuration.yml： level=DEBUG ts=2025-03-13T06:50:39Z app=device-random msg="HTTP response: 404" level=DEBUG ts=2025-03-13T06:50:39Z app=device-random msg="HTTP response: 400" level=ERROR ts=2025-03-13T06:50:39Z app=device-random msg="Register service failed: {"apiVersion":"v3","message":"AddRegistrationRequest.Registration.HealthCheck.Interval field is required","statusCode":400} level=ERROR ts=2025-03-13T06:50:39Z app=device-random msg="Unable to register service in registry" Error: 21: HTTP request failed level=DEBUG ts=2025-03-13T06:50:39Z app=device-random msg="Scheduler thread stopping" 解决：在res/configration.yml 中添加 HealthCheckInterval: 10s： Writable: LogLevel: DEBUG Service: Host: localhost Port: 59999 StartupMsg: Example random device service started HealthCheckInterval: 10s ​ 或者在/etc/hosts 中添加127.0.0.1 edgex-mqtt-broker,然后在执行命令 ./device-random -o -cp=keeper.http://localhost:59890 中去掉 -o： /etc/hosts 127.0.0.1 localhost 127.0.0.1 edgex-core-metadata 127.0.0.1 edgex-mqtt-broker # The following lines are desirable for IPv6 capable hosts ::1 ip6-localhost ip6-loopback fe00::0 ip6-localnet ff00::0 ip6-mcastprefix ff02::1 ip6-allnodes ff02::2 ip6-allrouters ff02::3 ip6-allhosts 如果程序运行成功，将显示如下： ... level=INFO ts=2025-03-13T07:36:45Z app=device-random correlation-id=c26ef1f7-755c-4952-8091-5c7c436e039b msg="AutoEvent: RandomDevice1/SensorOne" level=INFO ts=2025-03-13T07:36:50Z app=device-random correlation-id=dc222997-3113-465b-9c5c-26829ce24901 msg="AutoEvent: RandomDevice1/SensorTwo" level=INFO ts=2025-03-13T07:36:55Z app=device-random correlation-id=049d560d-896a-4e5a-8101-f3364c2767f1 msg="AutoEvent: RandomDevice1/SensorOne" 打开浏览器进入：localhost:4000 可发现一个设备服务，可成功读取随机数，如下： 总结 通过以上步骤，我们成功地在 Ubuntu 22.04 上完成了 EdgeX 的安装，并运行了 device-sdk-c 的 random 设备服务示例。从源码的克隆到 Docker 环境的搭建，再到依赖库的安装和配置文件的调整，每一个环节都环环相扣，考验着我们的耐心和解决问题的能力。 过程中可能遇到的报错，如安全性 token 问题、Curl 解析失败或健康检查间隔配置缺失，也提醒我们在操作复杂系统时，细节的重要性不容忽视。 最终，当我们在浏览器中看到 localhost:4000 界面上显示的设备服务和随机数据时，那份成就感无疑是对所有努力的最好回报。这不仅是一个技术实现的过程，更是一次对 EdgeX 架构和边缘计算理念的深入学习之旅。未来，我们还可以基于此基础，进一步扩展功能、优化性能，让边缘计算的潜力在更多场景中得以释放！
准备工作 硬件 软件 基础配置 数据库配置 Docker 数据库 PostgreSQL MySQL 部署 K3S 管理集群 主机名 K3S 核心安装 自动安装(推荐) 手动二进制文件安装(不推荐) Master / Worker 手动加入(如有必要) 安装 Rancher 集群管理工具和 Harbor Registry 仓库 准备工作 安装 Kubernetes 包管理工具 Helm 安装证书管理器 cert-manager 安装 Rancher 安装 Harbor 使用 Ceph（推荐） 使用 NFS 部署 pv 和 pvc 部署 Harbor Helm 批量上传所需 image 到 Harbor 部署 K3S 业务集群 所有的 Master 和 Worker 都加入业务集群 Rancher 中创建一个新的集群 Master 加入集群(初始化业务集群) Worker 加入业务集群 (部署/更新) Harbor hub CA 证书到所有的 Master 和 Worker (手动操作)导出 Harbor hub HTTPS 证书 (自动部署)Harbor Hub CA 证书 新建一个 Secret 新建一个 DaemonSet 基于 K3S 部署业务应用(边缘计算框架) EdgeX Foundry Base 部署辅助应用：流媒体，推理服务器，NVR 部署 OpenVINO AI 应用 总结 准备工作 硬件 主 K3S Master 节点：3台, master101, master102, master103 业务 K3S Master 节点：3台，master151, master152, master153 Worker 节点：N台，随意, worker01, worker02, ... 软件 基础配置 操作系统：Ubuntu Server 22.04 最小化安装系统，包含：openssh-server, iputils-ping, net-tools, vim PRETTY_NAME="Ubuntu 22.04.5 LTS" NAME="Ubuntu" VERSION_ID="22.04" VERSION="22.04.5 LTS (Jammy Jellyfish)" 管理集群 主机名 IP 其他 database 192.168.198.100 数据库+NFS存储，如有需要做 HA master101 192.168.198.101 管理 K3S master master102 192.168.198.102 管理 K3S master master103 192.168.198.103 管理 K3S master 业务集群 主机名 IP 其他 master151 192.168.123.151 业务 K3S master + worker master152 192.168.123.152 业务 K3S master + worker master153 192.168.123.153 业务 K3S master + worker worker161 192.168.123.161 业务 worker worker162 192.168.123.162 业务 worker 数据库配置 Docker 准备 docker 环境 curl -fsSL https://get.docker.com | sh - 验证 docker docker images REPOSITORY TAG IMAGE ID CREATED SIZE 设置一个 Registry 镜像 cat /etc/docker/daemon.json { "insecure-registries" : [ "0.0.0.0/0" ], "registry-mirrors": [ "https://xxx.mirror.swr.myhuaweicloud.com" ] } 重启 docker systemctl restart docker 数据库 PostgreSQL 创建 compose.yml services: postgres: image: postgres:alpine container_name: postgres restart: unless-stopped environment: POSTGRES_USER: k3s POSTGRES_PASSWORD: talent POSTGRES_DB: k3s ports: - "5432:5432" volumes: - ./data:/var/lib/postgresql/data - ./init-db.sh:/docker-entrypoint-initdb.d/init-db.sh 创建脚本 init-db.sh #!/bin/bash set -e psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname=postgres <<-EOSQL CREATE DATABASE harbor; EOSQL 启动 PostgreSQL 数据库容器 docker compose up -d 验证数据库 docker exec -it postgres psql -U k3s -c '\l' List of databases Name | Owner | Encoding | Locale Provider | Collate | Ctype | Locale | ICU Rules | Access privileges -----------+-------+----------+-----------------+------------+------------+--------+-----------+------------------- harbor | k3s | UTF8 | libc | en_US.utf8 | en_US.utf8 | | | k3s | k3s | UTF8 | libc | en_US.utf8 | en_US.utf8 | | | ... (5 rows) MySQL 因为 Rancher 只支持 MySQL 不支持 PostgreSQL 创建 compose.yml services: mysql: image: mysql:latest container_name: mysql restart: unless-stopped environment: MYSQL_ROOT_PASSWORD: talent MYSQL_DATABASE: rancher MYSQL_USER: rancher MYSQL_PASSWORD: talent ports: - "3306:3306" volumes: - ./data:/var/lib/mysql - ./init:/docker-entrypoint-initdb.d 创建脚本 init/permissions.sql GRANT ALL PRIVILEGES ON *.* TO 'rancher'@'%' IDENTIFIED BY 'talent'; FLUSH PRIVILEGES; 启动 MySQL 数据库容器 docker compose up -d 验证数据库 docker exec -it mysql mysql -u rancher -p'talent' -e "SHOW DATABASES;" +--------------------+ | Database | +--------------------+ | rancher | +--------------------+ 部署 K3S 管理集群 本章节内容在管理 K3S 主机 上执行 主机名 所有的 Master 配置相同的 /etc/hosts127.0.0.1 localhost 127.0.1.1 master101 # CHAMGEME 192.168.198.100 database 192.168.198.101 master101 192.168.198.102 master102 192.168.198.103 master103 K3S 核心安装 自动安装(推荐) 可以连接互联网的情况下 默认安装最新版本，这里是: v1.30.5+k3s1，注意：k3s:talent 是你自己的数据库用户名密码，使用一样的 token curl -sfL https://get.k3s.io | sh -s - server \ --datastore-endpoint="postgres://k3s:talent@database:5432/k3s?sslmode=disable" --token K10c71232f051944de58ab058871ac7a85b42cbdee5c6df94deb6bb493c79b15a92::server:3d6c2d90cec2e63db66bde0e6f24013d 验证安装 kubectl get nodes NAME STATUS ROLES AGE VERSION master101 Ready control-plane,master 43s v1.30.5+k3s1 其他 Master 安装方式一样。在任何一台 Master 上确认安装完成 kubectl get nodes NAME STATUS ROLES AGE VERSION master101 Ready control-plane,master 117s v1.30.5+k3s1 master102 Ready control-plane,master 118s v1.30.5+k3s1 master103 Ready control-plane,master 29s v1.30.5+k3s1 手动二进制文件安装(不推荐) 下载二进制文件 wget https://github.com/k3s-io/k3s/releases/download/vX.X.X/k3s -O /usr/local/bin/k3s chmod +x /usr/local/bin/k3s 初始化 k3s server --cluster-init 设置自启动脚本 所有 Master 机器上一样的配置 cat <<EOF | sudo tee /etc/systemd/system/k3s.service [Unit] Description=Lightweight Kubernetes Documentation=https://k3s.io After=network.target [Service] ExecStart=/usr/local/bin/k3s server --datastore-endpoint="postgres://k3s:talent@database:5432/k3s?sslmode=disable" --token K10c71232f051944de58ab058871ac7a85b42cbdee5c6df94deb6bb493c79b15a92::server:3d6c2d90cec2e63db66bde0e6f24013d Restart=always RestartSec=5s [Install] WantedBy=multi-user.target EOF 更新自启动设置 systemctl daemon-reload systemctl enable k3s systemctl start k3s 查看集群状态 任何一台 Master 都可以 /usr/local/bin/k3s kubectl get nodes 结果 NAME STATUS ROLES AGE VERSION master101 Ready control-plane,master 10m v1.30.4+k3s1 master102 Ready control-plane,master 10m v1.30.4+k3s1 master103 Ready control-plane,master 81s v1.30.4+k3s1 Master / Worker 手动加入(如有必要) 获取集群 token cat /var/lib/rancher/k3s/server/node-token Master 直接加入集群（不建议） k3s server --server https://master101:6443 --token <token> 或修改 /etc/systemd/system/k3s.service Worker加入（可选） 替换<node-token> 为你的token cat <<EOF | sudo tee /etc/systemd/system/k3s-agent.service [Unit] Description=Lightweight Kubernetes Documentation=https://k3s.io After=network.target [Service] ExecStart=/usr/local/bin/k3s agent --server https://master101:6443 --token <node-token> Restart=always RestartSec=5s [Install] WantedBy=multi-user.target EOF 更新自启动设置 systemctl daemon-reload systemctl enable k3s-agent systemctl start k3s-agent 不管怎么操作，基础三个主机的 K3S 集群要跑起来，保证基础工作完成。 安装 Rancher 集群管理工具和 Harbor Registry 仓库 准备工作 安装 Kubernetes 包管理工具 Helm 选择一个 Master 或全部 Master，安装 Helm curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash - 设置环境变量 加入到 /etc/profile 自启动 echo "export KUBECONFIG=/etc/rancher/k3s/k3s.yaml" >> /etc/profile && source /etc/profile 安装证书管理器 cert-manager helm 脚本安装 kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.15.3/cert-manager.crds.yaml helm repo add jetstack https://charts.jetstack.io helm repo update helm install cert-manager jetstack/cert-manager --namespace cert-manager \ --create-namespace --version v1.15.3 验证安装成功 kubectl get pods --namespace cert-manager NAME READY STATUS RESTARTS AGE cert-manager-9647b459d-9jwpj 1/1 Running 0 2m47s cert-manager-cainjector-5d8798687c-66r95 1/1 Running 0 2m47s cert-manager-webhook-c77744d75-k22h5 1/1 Running 0 2m47s 设置自签名 Issuer 创建 issuer-cert.yaml, 这里创建两个： rancher.yiqisoft.com hub.yiqisoft.com, 后面访问集群业务就是通过域名来通信 apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: selfsigned-issuer spec: selfSigned: {} --- apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: rancher-cert namespace: cattle-system spec: secretName: rancher-cert-secret issuerRef: name: selfsigned-issuer kind: ClusterIssuer commonName: rancher.yiqisoft.com duration: 315360000s renewBefore: 240h dnsNames: - rancher.yiqisoft.com --- apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: hub-cert namespace: harbor spec: secretName: hub-cert-secret issuerRef: name: selfsigned-issuer kind: ClusterIssuer commonName: hub.yiqisoft.com duration: 315360000s renewBefore: 240h dnsNames: - hub.yiqisoft.com 安装自定义 Certificate 先创建两个 namespace，再安装脚本 kubectl create namespace harbor kubectl create namespace cattle-system kubectl apply -f issuer-cert.yaml 结果输出 clusterissuer.cert-manager.io/selfsigned-issuer created certificate.cert-manager.io/rancher-cert created certificate.cert-manager.io/hub-cert created 安装 Rancher 增加 Rancher Helm 仓库 helm repo add rancher-latest https://releases.rancher.com/server-charts/latest helm repo update 安装 Rancher 这个过程比较漫长，需要从互联网下载 helm install rancher rancher-latest/rancher \ --namespace cattle-system \ --set hostname=rancher.yiqisoft.com \ --set replicas=3 \ --set extraEnv[0].name=CATTLE_DATABASE_ENDPOINT \ --set extraEnv[0].value=database:3306 \ --set extraEnv[1].name=CATTLE_DATABASE_USER \ --set extraEnv[1].value=rancher \ --set extraEnv[2].name=CATTLE_DATABASE_PASSWORD \ --set extraEnv[2].value=talent \ --set extraEnv[3].name=CATTLE_DATABASE_NAME \ --set extraEnv[3].value=rancher 验证安装成功 kubectl get pods --namespace cattle-system 结果 NAME READY STATUS RESTARTS AGE rancher-567cc5c6ff-bzx42 1/1 Running 0 65m rancher-567cc5c6ff-mgvns 1/1 Running 0 86m rancher-567cc5c6ff-rbgjn 1/1 Running 0 86m rancher-webhook-77b49b9ff9-mp2dm 1/1 Running 0 142m 安装 Harbor Harbor 作为内部 docker hub 使用 使用 Ceph（推荐） 准备一个 Ceph 集群用于存储（这里省略）。 使用 NFS 简单实用，但是效率不高，安全性一般。 在一个文件服务器上部署 NFS，这里用 database 主机apt update apt install nfs-kernel-server -y mkdir -p /opt/nfs_share chown nobody:nogroup /opt/nfs_share chmod 777 /opt/nfs_share echo "/opt/nfs_share *(rw,sync,no_subtree_check)" >> /etc/exports exportfs -ra systemctl restart nfs-kernel-server 部署 pv 和 pvc 编辑 pv-pvc.yaml apiVersion: v1 kind: PersistentVolume metadata: name: nfs-pv spec: capacity: storage: 30Gi accessModes: - ReadWriteMany nfs: path: /opt/nfs_share server: database --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: nfs-pvc spec: accessModes: - ReadWriteMany resources: requests: storage: 10Gi volumeName: nfs-pv storageClassName: "" 应用到 namespace harbor kubectl apply -f pv-pvc.yaml -n harbor 结果 persistentvolume/nfs-pv created persistentvolumeclaim/nfs-pvc created 查看结果，nfs-pvc 必须是 Bound kubectl get pvc -n harbor 结果 NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE nfs-pvc Bound nfs-pv 30Gi RWX <unset> 92s 查看 nfs-pv 必须是 Bound kubectl get pv -n harbor 结果 NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE nfs-pv 30Gi RWX Retain Bound harbor/nfs-pvc <unset> 97s 部署 Harbor Helm 安装 NFS 依赖 所有的 Master 上需要安装 nfs-common，并验证（可选），可以访问 /tmp/nfs 目录 apt install nfs-common -y mkdir /tmp/nfs mount -t nfs database:/opt/nfs_share /tmp/nfs ll /tmp/nfs 增加 Harbor Helm 仓库 helm repo add harbor https://helm.goharbor.io helm repo update 导出默认 values，修改 helm show values harbor/harbor > harbor-values.yml # 内容修改如下 expose: ingress: hosts: core: hub.yiqisoft.cn externalURL: https://hub.yiqisoft.cn persistentVolumeClaim: registry:/exter existingClaim: "nfs-pvc" storageClass: "-" database: type: external external: host: "192.168.198.100" port: "5432" username: "k3s" password: "talent" coreDatabase: "harbor" harborAdminPassword: "Harbor12345" 安装 Harbor，需要通过互联网下载所有的镜像，时间根据网络带宽决定 helm install harbor harbor/harbor -f harbor-values.yml -n harbor 确认 Harbor 是否安装成功 kubectl get pods -n harbor 都显示 READY 即可 NAME READY STATUS RESTARTS AGE harbor-core-666cf84bc6-vq8mp 1/1 Running 0 85s harbor-jobservice-c68bc669-6twbp 1/1 Running 0 85s harbor-portal-d6c99f896-cq9fx 1/1 Running 0 85s harbor-redis-0 1/1 Running 0 84s harbor-registry-5984c768c8-4xnrg 2/2 Running 0 84s harbor-trivy-0 1/1 Running 0 84s 验证 Harbor 工作状态 curl -k https://hub.yiqisoft.com/v2/ -u admin:Harbor12345 提示 {} 证明成功 {} 以下在 database 主机上运行 登录 Harbor docker login hub.yiqisoft.com -u admin -p Harbor12345 登录成功 Login Succeeded 上传已经存在的 PostgreSQL image 测试 docker tag postgres:alpine hub.yiqisoft.com/library/postgres:alpine docker push hub.yiqisoft.com/library/postgres:alpine 结果显示 The push refers to repository [hub.yiqisoft.com/library/postgres] 284f9fec9bcb: Pushed 07188499987f: Pushed ... 63ca1fbb43ae: Pushed alpine: digest: sha256:f0407919d966a86e9***c81641801e43c29b size: 2402 批量上传所需 image 到 Harbor 在准备好 docker image 的机器上运行 导出需要的 image docker images --format "{{.Repository}}:{{.Tag}}" |grep yiqisoft.com > images.txt 新建 /etc/docker/daemon.json，跳过安全证书 { "insecure-registries" : [ "0.0.0.0/0" ] } 重启 docker systemctl restart docker 新建一个脚本 push-images.sh #!/bin/bash # Check if the correct number of arguments is provided if [ $# -ne 1 ]; then echo "Usage: $0 <path_to_images_file>" exit 1 fi # Assign the first argument to the file variable file=$1 # Check if the file exists if [ ! -f "$file" ]; then echo "File not found: $file" exit 1 fi while read image; do echo "Pushing image: $image" docker push $image if [ $? -eq 0 ]; then echo "Successfully pushed $image" else echo "Failed to push $image" fi done < "$file" 登录 Harbor 并上传: push image docker login hub.yiqisoft.com -u admin -p Harbor12345 bash push-images.sh images.txt 查看 Harbor Registry 仓库结果 curl -k https://hub.yiqisoft.com/v2/_catalog -u admin:Harbor12345 |json_pp 结果显示 { "repositories" : [ "edgexfoundry/app-service-gateway", "edgexfoundry/consul", "edgexfoundry/core-command", "edgexfoundry/core-common-config-bootstrapper", "edgexfoundry/core-data", "edgexfoundry/core-metadata", "edgexfoundry/device-modbus", "edgexfoundry/device-onvif-camera", "edgexfoundry/device-openvino-face-recognition", "edgexfoundry/device-openvino-object-detection", "edgexfoundry/edgex-ui", "edgexfoundry/redis", "edgexfoundry/support-notifications", "edgexfoundry/support-scheduler", "library/hello-world", "library/postgres", "rancher/system-agent", "yiqisoft/media_server", "yiqisoft/model_server", "yiqisoft/nginx", "yiqisoft/nvr" ] } 部署 K3S 业务集群 以下操作注意：全部在新建的业务集群中操作 所有的 Master 和 Worker 都加入业务集群 Rancher 中创建一个新的集群 Rancher UI 操作（省略）, Cluster Name: yiqisoft, Kubernetes Version: 1.30.4+k3s1 Master 加入集群(初始化业务集群) 通过新集群的 Registration 加入，可以自定义一个 namespace。需要几个 Master 根据自己业务需要来定。 导入 hub CA echo | openssl s_client -showcerts -connect hub.yiqisoft.com:443 2>/dev/null | \ openssl x509 -outform PEM > /etc/ssl/certs/hub.yiqisoft.com.crt 分别在 master151-master153 主机上执行，主机名不能重复 curl --insecure -fL https://rancher.yiqisoft.com/system-agent-install.sh | \ sudo sh -s - --server https://rancher.yiqisoft.com --label 'cattle.io/os=linux' \ --token xft4fsrjgkn4rzqxrgrb4mztkcxtz4nhpwm58t84q54lvpsscdgd72 \ --ca-checksum 30d78ad0ece6355c40a4b3764ba02dfc96388a9c20f6b4e2bffb131cf7402c1f \ --etcd --controlplane --worker 查看业务集群 node kubectl get nodes 确认 Master 都在线 NAME STATUS ROLES AGE VERSION master151 Ready control-plane,etcd,master,worker 32m v1.30.4+k3s1 master152 Ready control-plane,etcd,master,worker 3h39m v1.30.4+k3s1 master153 Ready control-plane,etcd,master,worker 23m v1.30.4+k3s1 Worker 加入业务集群 首先通过 curl 直接输出 hub 证书到主机证书目录 containerd 拉取 image 时需要信任此证书 CA echo | openssl s_client -showcerts -connect hub.yiqisoft.com:443 2>/dev/null | \ openssl x509 -outform PEM > /etc/ssl/certs/hub.yiqisoft.com.crt 需要等 Master 加入成功后（至少一个成功注册），Worker 主机再次通过 Registration 加入，只需要选择 Worker 即可，需要定义一个 namespace label， 比如： namespace=worker01，以后可单独部署到独立应用 到 namespace 作为隔离使用。 在所有业务 Worker 上执行，同样注意主机名不能重复 curl --insecure -fL https://rancher.yiqisoft.com/system-agent-install.sh | \ sudo sh -s - \ --server https://rancher.yiqisoft.com \ --label 'cattle.io/os=linux' \ --token xft4fsrjgkn4rzqxrgrb4mztkcxtz4nhpwm58t84q54lvpsscdgd72 \ --ca-checksum 30d78ad0ece6355c40a4b3764ba02dfc96388a9c20f6b4e2bffb131cf7402c1f \ --worker \ --label namespace=worker161 验证 Worker 节点 kubectl get nodes -A 结果正常 NAME STATUS ROLES AGE VERSION master151 Ready control-plane,etcd,master,worker 74m v1.30.4+k3s1 master152 Ready control-plane,etcd,master,worker 4h21m v1.30.4+k3s1 master153 Ready control-plane,etcd,master,worker 65m v1.30.4+k3s1 worker161 Ready worker 25m v1.30.4+k3s1 (部署/更新) Harbor hub CA 证书到所有的 Master 和 Worker (手动操作)导出 Harbor hub HTTPS 证书 增加自签名证书到 ca-certificates 所有的 Master 和 Worker 上执行（不太可行）echo | openssl s_client -showcerts -connect hub.yiqisoft.com:443 2>/dev/null | \ openssl x509 -outform PEM > /etc/ssl/certs/hub.yiqisoft.com.crt (自动部署)Harbor Hub CA 证书 新建一个 Secret 在业务集群中，把 Harbor 证书导入集群 kube-system kubectl create secret generic hub-ca --from-file=ca.crt=/hub.yiqisoft.com.crt -n kube-system 查看 kubectl get secret -A |grep hub-ca 结果 NAMESPACE NAME TYPE DATA AGE kube-system hub-ca kubernetes.io/tls 2 101m 新建一个 DaemonSet 新建一个部署文件: ca-distributor.yaml，用于分发 hub 的证书，非常重要，否则无法从私有 Registry 拉取镜像。 apiVersion: apps/v1 kind: DaemonSet metadata: name: ca-distributor namespace: kube-system spec: selector: matchLabels: name: ca-distributor template: metadata: labels: name: ca-distributor spec: containers: - name: ca-distributor image: busybox:latest command: - /bin/sh - -c - "cp /etc/ca-certificates/tls.crt /etc/ssl/certs/hub.yiqisoft.cn.crt && echo 'Certificate Updated' && sleep 3600" volumeMounts: - name: ca-cert mountPath: /etc/ca-certificates - name: ssl-certs mountPath: /etc/ssl/certs volumes: - name: ca-cert secret: secretName: hub-ca - name: ssl-certs hostPath: path: /etc/ssl/certs type: Directory restartPolicy: Always 部署 yaml kubectl apply -f ca-distributor.yaml 验证结果,确保所有的节点都执行成功。 kubectl get pods -A |grep ca-distributor kube-system ca-distributor-ckhgh 1/1 Running 0 100s kube-system ca-distributor-hqzwt 1/1 Running 0 101s kube-system ca-distributor-lj72n. 1/1 Running 0 101s kube-system ca-distributor-mpp7m 1/1 Running 0 101s 在 Worker 上拉取镜像 image k3s ctr i pull hub.yiqisoft.com/library/hello-world:latest 验证结果 hub.yiqisoft.cn/library/hello-world:latest: resolved |++++++++++++++++++++++++++++++++++++++| manifest-sha256:d37ada95d47ad12224c205a938129df7a3e52345828b4fa27b03a98825d1e2e7: done |++++++++++++++++++++++++++++++++++++++| config-sha256:d2c94e258dcb3c5ac2798d32e1249e42ef01cba4841c2234249495f87264ac5a: done |++++++++++++++++++++++++++++++++++++++| layer-sha256:c1ec31eb59444d78df06a974d155e597c894ab4cda84f08294145e845394988e: done |++++++++++++++++++++++++++++++++++++++| elapsed: 0.6 s total: 524.0 (871.0 B/s) unpacking linux/amd64 sha256:d37ada95d47ad12224c205a938129df7a3e52345828b4fa27b03a98825d1e2e7... done: 54.873311ms 基于 K3S 部署业务应用(边缘计算框架) 以下所有部署都是通过 Helm Chart 工具进行，需要自行编写。 EdgeX Foundry Base 部署基本框架结果kubectl get pods -n worker161 NAME READY STATUS RESTARTS AGE edgex-core-command-tspw7 1/1 Running 0 5m41s edgex-core-common-config-bootstrapper-92q25 1/1 Running 0 5m25s edgex-core-consul-8lqxz 1/1 Running 0 6m4s edgex-core-data-nv26d 1/1 Running 0 6m23s edgex-core-metadata-l6c49 1/1 Running 0 5m3s edgex-device-onvif-camera-5tt4w 1/1 Running 0 3m26s edgex-mqtt-broker-j2jdr 1/1 Running 0 81s edgex-redis-rf4v7 1/1 Running 0 4m50s edgex-support-notifications-ws6l5 1/1 Running 0 4m28s edgex-support-scheduler-drx2p 1/1 Running 0 3m56s edgex-ui-v55vm 1/1 Running 0 3m45s 部署辅助应用：流媒体，推理服务器，NVR 部署辅助应用 业务 pod 之间有依赖关系，需等待其他 pod 起来才能工作，会不断的重试kubectl get pods -n worker161 |grep -v edgex 结果 NAME READY STATUS RESTARTS AGE media-server-wtjxk 1/1 Running 1 (5m29s ago) 13m model-server-9fpb2 1/1 Running 0 89s nvr-gqlst 1/1 Running 2 (5m29s ago) 11m 部署 OpenVINO AI 应用 部署基于 OpenVINO 推理框架的 AI 应用 podkubectl get pods -n worker161 |grep openvino 结果 NAME READY STATUS RESTARTS AGE edgex-device-openvino-face-recognition-bgch4 1/1 Running 0 5m43s edgex-device-openvino-object-detection-tbvwk 1/1 Running 0 7m21s 总结 通过本部署方案，可以管理基于 EdgeX 的边缘计算方案，EdgeX 具备边缘自治，即使脱离 K3S 集群，也照样能正常工作。 同时，此方案可以管理成千上万个边缘设备，之间互不干扰，适合大型项目部署。 云端配置相对较为复杂，但是边缘端就轻松很多，只需要手动注册到 K3S 集群，其他都可以通过 K8S API 进行部署和管理。 对比 K8S 而言，K3S 非常适合在边缘端工作，占用资源少，管理较为方便。 其实，最终结果就是：K3S 适合管理边缘容器分发。
规则链概述 创建规则链 规则链元素介绍 应用案例 总结与建议 ThingsBoard是一款强大的开源平台，提供了丰富的功能来管理和监控物联网设备。其中，规则链（Rule Chains）是ThingsBoard中一个关键的特性，它允许用户定义和执行自定义的业务规则，实现设备数据的处理、转换和触发特定动作。本篇博客将带你在短短五分钟内学会使用ThingsBoard规则链，让你更灵活地管理你的物联网应用。 规则链概述 规则链，即低代码的逻辑处理模块，简而言之，描述了服务器在接收数据后的处理流程 有 3 个主要组成部分： 消息： 任何传入事件。它可以是来自设备的传入数据、设备生命周期事件、REST API 事件、RPC 请求等。 规则节点： 对传入消息执行的函数。有许多不同的节点类型可以对传入消息进行过滤、转换或执行某些操作。 规则链： 节点通过关系相互连接，因此来自规则节点的出站消息被发送到下一个连接的规则节点。 创建规则链 在控制台的左侧导航栏中，找到并点击“规则链”选项。这将带你进入规则链管理页面，其中你可以查看和管理现有的规则链。 在设备配置管理页面，填写规则链的名称。保存后使用规则链。 规则链元素介绍 节点类型及其功能 在规则链中，有不同类型的节点，每个节点都有特定的功能。常见的节点类型包括： 过滤器节点： 用于筛选数据，只允许满足特定条件的数据通过。 属性集节点：用于更新传入消息的元数据 转换器节点： 用于将数据从一种格式或单位转换为另一种格式或单位。 动作节点： 用于触发特定的动作或通知。 外部节点：用于与外部系统进行交互，提供将消息及数据路由到外部。 示例规则链 在 Thingsboard UI 中，转到规则链部分并打开根规则链。这个示例可以包括一系列节点，如上传遥测、存储日志和RPC调用等。 应用案例 数据过滤和清理 考虑一个场景，设备传感器每秒钟发送一批数据，但我们只对特定阈值以上的数据感兴趣。通过使用规则链的过滤器节点，可以轻松地过滤掉那些不满足条件的数据，只保留我们关心的数据。 在本案例中，我们将配置 ThingsBoard 规则引擎来存储 -40 到 80°C 范围内的所有温度。 我们将使用此脚本进行数据验证： return msg.temperature < -40 || msg.temperature > 80 如果温度在有效范围内，脚本将返回True，否则将返回False。如果脚本返回True，传入消息将被路由到以True关系连接的下一个节点。 现在我们使用Post Telemetry关系将Message Type Switch节点与Script Filter节点连接： 接下来，我们需要使用True关系将Script Filter节点与Save Telemetry节点连接起来。因此所有有效的遥测数据都将被保存： 上传telemetry温度为200，我们将看到设备最新遥测部分中未添加遥测： { "temperature": "200" } 现在修改温度为24，再次发送请求 { "temperature": "24" } 我们将看到遥测数据已成功保存。 实时数据转换 假设设备传感器提供的数据是以摄氏度为单位，但我们更喜欢使用华氏度。通过规则链中的转换器节点，可以将摄氏度转换为华氏度，确保我们得到的数据在我们熟悉的单位中。 下面的案例中，通过配置 ThingsBoard 规则引擎区分设备A和设备B，同时根据设备属性值分别将遥测数据中的温度进行不同处理。 我们使用到属性集节点中的originator attributes 在消息元数据中添加消息发起者属性(client\shared\server)和最新的遥测值，通过isTempCalculate属性区分设备A和设备B 我们会使用脚本来验证传入的温度是否在有效范围内，并返回Success或Failure。 我们将使用此脚本进行数据验证： if (metadata.isTempCalculate && metadata.isTempCalculate === "true") { if (typeof msg.temperature !== 'undefined') { msg.temperature = precisionRound(msg.temperature / 10, 2); } }else{ } function precisionRound(number, precision) { var factor = Math.pow(10, precision); return Math.round(number * factor) / factor; } return {msg: msg, metadata: metadata, msgType: msgType}; 上传一个温度值，确保规则链正确地执行了转换脚本并将数据保存。 { "temperature": "200" } 设备A： 设备B： 设备状态监控 在这个场景中，我们想要监控设备的状态，当设备状态发生变化时触发通知。通过规则链的动作节点，我们可以设置条件，例如设备断线或异常状态，以触发相应的通知或报警动作。 本节的案例，通过配置 ThingsBoard 规则引擎，当温度不在-40～80之间时发送设备告警，并向外部MQTT中发送原始数据信息 遥测数据上传成功后，通过script脚本判断温度是否在正常范围内，并添加新增告警和清除告警节点 我们将使用此脚本进行新增告警的逻辑处理： var details = {}; details.temperature = msg.temperature; if (metadata.prevAlarmDetails) { details = JSON.parse(metadata.prevAlarmDetails); //remove prevAlarmDetails from metadata delete metadata.prevAlarmDetails; //now metadata is the same as it comes IN this rule node details.count = details.count + 1; }else { details.count = 1; } return details; 可以将规则链配置为使用转换节点链，以将正确的有效负载发送到MQTT代理。在成功发布消息的情况下，原始消息将通过Success链传递到下一个节点，否则将使用Failure链。 配置MQTT节点： 向Device发送一条带有温度 = 200 的遥测请求，查看设备的告警页面 同时MQTT接收到规则链发送的JSON { "tenantId": { "entityType": "TENANT", "id": "4d22a670-a52a-11ee-a5a6-494c5c858852" }, "customerId": { "entityType": "CUSTOMER", "id": "13814000-1dd2-11b2-8080-808080808080" }, "type": "General Alarm", "originator": { "entityType": "DEVICE", "id": "33441de0-a5e5-11ee-a3e5-494c5c858852" }, "severity": "CRITICAL", "status": "ACTIVE_UNACK", "startTs": 1703837072025, "endTs": 1703837072025, "ackTs": 0, "clearTs": 0, "details": { "temperature": 200, "count": 1 }, "propagate": false, "propagateToOwner": false, "propagateToTenant": false, "propagateRelationTypes": [], "id": { "entityType": "ALARM", "id": "e5ea5cd0-a620-11ee-948b-d78826584bf8" }, "createdTime": 1703837072157, "name": "General Alarm" } 总结与建议 通过这篇博客，你将能够快速上手使用ThingsBoard规则链，包括规则链的元素介绍、实际应用示例、总结和建议等，后续将会进一步深入介绍和展示如何在ThingsBoard中灵活使用规则链，使读者能够在短时间内掌握这一强大功能。
分享｜五分钟快速学会 ThingsBoard 对接物联网网关
如何将物联网网关与ThingsBoard集成？ 准备工作 ThingsBoard 对接物联网网关的步骤 配置ThingsBoard设备 配置ThingsBoard仪表板 与映翰通网关IG902建立连接 验证连接 小结与进一步探索 常见问题FAQ 关于我们 "亿琪软件" "联系我们--商业服务" 如何将物联网网关与ThingsBoard集成？ 准备工作 安装配置映翰通网关 连接设备 添加控制器 添加测点 ThingsBoard 对接物联网网关的步骤 配置ThingsBoard设备 在 ThingsBoard 中创建一个新设备 转到设备管理页面，创建设备并为设备分配唯一标识符和其他必要的信息。 添加凭证，获取设备的 Access Token，以便设备与平台进行通信。 连接成功后自动同步控制器与遥测数据 配置ThingsBoard仪表板 在 ThingsBoard 中创建仪表板 转到仪表板管理页面，创建一个新仪表板，为其分配一个易记的名称。 添加设备相关的控件，例如图表、指示灯等，以显示设备的实时数据。 与映翰通网关IG902建立连接 找到物联网连接或 MQTT 配置选项 进入映翰通网关 IG902 的管理界面。 输入 ThingsBoard 的 MQTT 服务器地址、端口和认证信息 添加 MQTT 消息发布 保存配置并启动网关 验证连接 确保设备状态显示为在线 返回 ThingsBoard 仪表板，检查仪表板上的控件，确保它们显示来自映翰通网关 IG902 的实时数据。 小结与进一步探索 通过本篇文章的学习，读者可以快速的了解并学会如何使用 ThingsBoard 进行与物联网网关的对接。本篇文章使用的是映翰通网关，读者可自由更换进行配置，步骤总体来说是大同小异的。 ThingsBoard平台集成网关的优势 集成的好处： 将物联网网关与 ThingsBoard 平台集成，带来了实时监控、数据可视化、分析和报告的诸多好处。这种集成不仅简化了数据管理，还提高了系统的可维护性，加强了对设备性能的洞察。 潜在的应用场景： 物联网网关与 ThingsBoard 的集成在各个行业都有广泛的应用场景。从工业自动化到智能城市，从农业到医疗保健，这种集成能够为不同领域的物联网应用提供更强大、高效的解决方案。 常见问题FAQ 物联网网关无法连接到ThingsBoard平台，应该如何排除问题？ 首先，检查物联网网关的配置是否正确，包括 MQTT 服务器地址、端口和认证信息。确保网络连接正常。 验证ThingsBoard服务器的可访问性。同时，查看ThingsBoard的日志以获取更多详细信息。 关于我们 "亿琪软件" 上海亿琪软件有限公司成立于 2016 年，专注于 5G 通信、AI 人工智能、边缘计算和大数据网络安全多项技术领域，致力于物联网领域前沿技术的创新，为用户提供全方位、智能化和安全的物联网解决方案。 2023 年，公司发布“YiFUSION ｜工业边缘智能融合一体机”产品，为工业客户提供一整套的边缘计算+AI 能力：高性能数据采集、多类型数据融合、AI 算法集成、云端业务对接。在边缘网关的基础上，集成了 IoT 平台的边缘协同能力、本地 Web SCADA 和 HMI 功能、本地数据存储、边缘 AI 视频分析、行业应用集成等。 2022 年，公司推出 “YiCLOUD ｜亿琪云”一站式物联网应用解决方案。公司的业务涵盖了智慧城市、智慧农业、智能工厂和智慧园区等多个领域，公司软硬件产品和解决方案获得华为技术认证，得到中国移动 OCP 认证，公司还是边缘计算产业联盟 ECC 成员。 "联系我们--商业服务" 网站：http://yiqisoft.cn 邮件：support@yiqisoft.cn 电话：021-68863086 手机：186-1666-9123
PLC | 西门子 Siemens S7 ISO-on-TCP 通讯协议探究——提高数据采集效率故障分析（使用边缘计算网关）
目录 需求/故障现场 分析过程 winCC 数据采集抓包 分析过程1：采集20个 real 数据字段 分析过程2：采集10个 word 数据字段 自有软件数据采集抓包 每次只是读取了1个 word 数据字段 解决方案构思 验证 读取1000个字节 下一步是继续验证分多个 item 进行数据采集 winCC 数据格式 自己代码 开发过程 大概过程描述 总结 效果展示 改进？ 解决方案 关于 亿琪软件 众所周知，我们软件读取 PLC 数据时按照 Bool/Byte/Word/Real 等类型进行通讯，有时候，需要频繁读取 PLC 多个数据字段偏移量时，就会造成传输效率低下的问题。 需求/故障现场 PLC 上有 100 个 word 需要读取，偏移量顺序排列，比如：从 0 - 200； 需要 1 秒读取 1 次数据； 转换后，数据转发到第三方系统：MQTT，SQL等； 逐个读取每个 word，两个字节，需要在一个 TCP 会话中执行 100 次请求； 每次请求，三个 TCP 数据传输，每次需要 15ms（423行-421行：3.614448-3.598902=0.015546s）； 这样的话，100次请求需要1.5546s，算上网络延迟和处理性能问题，整个过程需要2秒以上； 结果就是：完全达不到 1秒1次 采集的需求。 分析过程 winCC 数据采集抓包 分析过程1：采集20个 real 数据字段 winCC 将数据包分解成 3个 item 分析过程2：采集10个 word 数据字段 同样原理，winCC 将数据包按照每个 item 32 个字节 自有软件数据采集抓包 每次只是读取了1个 word 数据字段 解决方案构思 每次按照要求，获取所有需要的数据偏移量； 取回来后，按照测点要求，自己根据数据类型分解出来； 循环读取下一次数据； 验证 读取1000个字节 可以看到分了5次数据，222+222+222+222+112=1000 下一步是继续验证分多个 item 进行数据采集 winCC 数据格式 效率高，每次只需要 <2ms，分析原因：每次读取都是连续的地址空间，不需要PLC进行分段读取； 自己代码 效率低，每次需要20-50ms，分析原因：每个 item 里面只有一个测点（word类型），需要分段读取； 开发过程 大概过程描述 由于每次请求的数据量（按测点）叠加在一起，通过 S7DataItem 来组合，最多20个 item，打包一起发送到 PLC； 收到每次的返回数据，再解析成对象插入二维表，等待处理； 全部处理完成，统一根据请求数量，返回相应的结果集； 总结 PLC 针对于每个测点的处理时间在 1-3秒，数量越多，时间越长，是累加的； 100个测点，算上 TCP 包头和包尾，大概来回数据时间是 220ms左右，200个测点是 400ms左右； 通过这种方式，一个 PLC 建议采集点不超过 500个，虽然对 PLC 性能影响不大，但是处理时间较长，不适用1秒一次采集需求； 每个型号 PLC 的处理性能不一样，支持的 TCP 连接数也不一定一样，所以，要根据具体需求来决定。 效果展示 存入 SQL 数据库，完全控制在1秒一次采集 4 个 TCP 连接非常稳定 网关性能稳定 与 PLC 交互的网卡网络流量很少 改进？ 类似 winCC 的模式，一次性读取一段偏移量，节省 TCP 开销，也节省 PLC 处理每个测点； 针对一次读取来的一段偏移量内容进行解析，需要对字节码流处理非常熟悉； 对于新建项目，比较适合用改进的方法，毕竟 PLC 可以根据需求改动测点偏移量； 解决方案 关于 亿琪软件 上海亿琪软件有限公司成立于2016年，专注于 5G 通信、AI 人工智能、边缘计算和大数据网络安全多项技术领域，致力于物联网领域前沿技术的创新，为用户提供全方位、智能化和安全的物联网解决方案。 2023年，公司发布“YiFUSION｜工业边缘智能融合一体机”产品，为工业客户提供一整套的边缘计算+AI能力：高性能数据采集、多类型数据融合、AI算法集成、云端业务对接。在边缘网关的基础上，集成了 IoT 平台的边缘协同能力、本地Web SCADA 和 HMI 功能、本地数据存储、边缘 AI 视频分析、行业应用集成等。 2022年公司推出 “YiCLOUD｜亿琪云”一站式物联网应用解决方案。公司的业务涵盖了智慧城市、智慧农业、智能工厂和智慧园区等多个领域，公司软硬件产品和解决方案获得华为技术认证，得到中国移动OCP认证，公司还是边缘计算产业联盟ECC成员。 感知物联，畅快无限。 联系我们 网站：http://yiqisoft.cn 邮件：support@yiqisoft.cn 电话：021-68863086 手机：186-1666-9123
消息总线在 EdgeX 框架中起到了至关重要的作用，模块之间的数据交互，外部系统之间的桥梁，消息总线可以说是不可或缺的必要条件。默认情况下，redis 服务成为了 EdgeX 消息总线的初始化配置。 通用配置 EdgeX 消息总线是可以根据需求来配置和更改具体的实现引擎： Redis（默认） MQTT NATS Core NATS JetStream ZeroMQ（已过时） eKuiper 规则引擎 EdgeX MessageBus 用于内部 EdgeX 服务到服务的通信。它并不意味着作为外部服务与内部 EdgeX 服务通信的入口点。 eKuiper 规则引擎是一个例外，因为它与 EdgeX 紧密集成。 一般情况下，由于 eKuiper 跟 EdgeX 在部署时处于一个阶段，eKuiper 可以直接从 MessageBus 中直接获取数据，并遵循 MessageBus 消息格式进行解析和处理。 通过 eKuiper 最简单的创建 stream 命令，即可完成对 EdgeX MessageBus 配置：CREATE STREAM EdgeXStream () WITH ( FORMAT = "JSON", TYPE = "edgex" ) 同样的情况，eKuiper 可以简单的命令创建 数据输出到 EdgeX： { "id": "ruleRedisEvent", "sql": "SELECT temperature * 3 AS t1, humidity FROM events", "actions": [ { "edgex": { "protocol": "redis", "host": "localhost", "port": 6379, "topic": "application", "profileName": "ekuiperProfile", "deviceName": "ekuiper", "contentType": "application/json" } } ] } 提示 非专业人士，不建议直接调用 EdgeX MessageBus 进行开发和使用。
EdgeXFoundry平台的分层和服务为边缘设备/节点和云/企业应用之间提供了一个双向转换引擎。EdgeX 作为边缘计算平台是非常不错的选择。 主要服务介绍 Device Services 设备服务 设备服务是与传感器/设备或物联网对象(“物”) 交互的边缘连接器，其中包括机器、机器人、无人机、HVAC 设备、相机等。通过利用可用的连接器，可以控制设备并/或传输数据至 EdgeX或从其传输数据。您还可以使用设备服务 SDK来创建您自己的 EdgeX设备服务。 可用的设备服务包括： MQTT Modbus TCP Modbus RTU BACnet IP & MSTP REST ONVIF IP Camera OPC-UA SNMP BEL GATT Core Services 核心服务 通过这些服务可大体了解给定部署中连接了哪些设备，正在传输哪些数据以及EdgeX的配置方式。 可用的核心服务包括： Core Data 核心数据 Core Command 核心命令/控制 Core Metadata 核心元数据 Registry & Config 注册和配置 Supporting Services 支持服务 包括诸如边缘分析 (也称为“本地分析”等微服务，以及典型的软件应用功能.例如记录、计划和数据清理等。 可用的支持服务包括： Alerts & Notification 报警和通知 Rule Engine 规则引擎 Scheduling 计划服务 Application Services 应用服务 应用服务是指将感应到的数据从 EdgeX提取、处理/转换和发送到所选端点或应用的方式。这些服务可以是分析数据包、企业或本地应用，也可以是 YiCLOUD、 AzureloTHub、AWS loT或 Google loT Core 等云系统。 可用的应用服务包括： Configurable Application Services 配置化运应用服务 Application Services 应用服务 通用配置 本地配置文件 以 device-sdk-go 为例，所谓本地配置文件，就是在边缘计算设备本地磁盘上的固定配置文件，一般情况下，在 cmd/res/ 目录下。 configuration.toml 这个文件是 SDK 的主要配置文件，规定了数据输入/处理/输出，队列定义，profile 和 device 配置文件的所在位置； profiles 目录 这个目录存在 yaml 配置文件，定义了设备类型（profile）的资源和命令； devices 目录 这个目录存在 yaml 配置文件，定义了初始设备资源； 实时注入配置 除了可以在初始化服务时的本地配置，也可以通过 API 的方式对配置进行设置，这样的情况是灵活且方便。 注册 consul 作为注册中心；各种服务，都是自己模块的信息注册到 consul 中集中管理；这是一个隐藏服务，使用者不需要特别关心，此服务以微服务运行。
目录 ONVIF 协议 YiEDGE 边缘计算框架 管理 ONVIF 摄像头 同步到 Message Bus YiEVAS 系统 eKuiper 作为 pipeline 引擎 ONVIF 通告获取 stream rule-onvif-camera 拉流，抓帧，推理 拉流 抓帧 推理 结果输出 创建被管理摄像头 在边缘计算视频分析方面，ONVIF 协议实现了摄像头管理，YiEDGE 实现了拉流和图片输出，YiEVAS 模块 Pyairknn 实现推理。 ONVIF 协议 开放型网络视频接口论坛（英语：Open Network Video Interface Forum，简称ONVIF），是一个国际开放型网络视频产品标准网络接口开发论坛. IPC 摄像头必须支持 ONVIF 协议； 实现自动多摄像头并行处理性能要求； ONVIF Device Manager，用这个软件连接摄像头查看。 YiEDGE 边缘计算框架 YiEDGE 框架基于 EdgeX 框架做了深层次定制，实现了更加自动化的功能，增加了南向设备接口协议，增加了北向云平台接口协议。 管理 ONVIF 摄像头 采用 device onvif camera 设备层协议，对接各种支持 ONVIF 的摄像头设备； 定期探测网络中的 IPC； 轮训摄像头状态，获取 streamuri 地址；创建被管理摄像头 这里以 HK 摄像头为例，10s 轮训一次。 { "apiVersion": "v2", "statusCode": 200, "device": { "created": 1676646639846, "modified": 1676646710817, "id": "9a9a3276-cf19-4e01-908c-801a520ba5be", "name": "IPC-HK", "adminState": "UNLOCKED", "operatingState": "UP", "labels": [ "" ], "serviceName": "device-onvif-camera", "profileName": "onvif-camera", "autoEvents": [ { "interval": "10s", "onChange": false, "sourceName": "StreamUri" } ], "protocols": { "Onvif": { "Address": "192.168.123.183", "DeviceStatus": "UpWithAuth", "FirmwareVersion": "V5.7.3 build 220315", "FriendlyName": "HIKVISION DS-IPC-T12HV3-IA", "HardwareId": "88", "LastSeen": "Fri Feb 17 15:11:50 UTC 2023", "MACAddress": "c0:6d:ed:eb:cb:59", "Manufacturer": "HIKVISION", "Model": "DS-IPC-T12HV3-IA", "Port": "80", "SerialNumber": "DS-IPC-T12HV3-IA20220913AACHK56293791" } } } } streamUri 信息内容 [ { "id": "c88007b8-4316-451c-87b4-2dec7f1f540d", "origin": 1676646870752047000, "deviceName": "IPC-HK", "resourceName": "StreamUri", "profileName": "onvif-camera", "valueType": "Object", "value": "", "objectValue": { "MediaUri": { "InvalidAfterConnect": false, "InvalidAfterReboot": false, "Timeout": "PT60S", "Uri": "rtsp://192.168.123.183:554/Streaming/Channels/0?transportmode=unicast&profile=Profile_1" } } } ] 同步到 Message Bus 以上信息都会同步到数据总线； 可以自定义轮训时间； 筛选需要通告总线的 IPC； YiEVAS 系统 eKuiper 作为 pipeline 引擎 大概模式如下： |source输入->|rule规则引擎->|sink输出->| |:----|:----|:----| |onvif 通告获取|拉流，抓帧，推理|结果输出| ONVIF 通告获取 stream 这里，直接用 EdgeX 数据总线即可。 { "Name" : "EdgeXStream", "Options" : { "format" : "json", "type" : "edgex" }, "Statement" : null, "StreamFields" : null, "StreamType" : 0 } rule-onvif-camera 从 stream 获取信息，并解析 StreamUri 通告给拉流处理。 SELECT "true" as isRetImg, (json_path_query(StreamUri, "$")->MediaUri)->Uri AS rtspUrl, meta(deviceName) as deviceName FROM EdgeXStream where meta(profileName) = "onvif-camera" StreamUri 解析结果 { "deviceName": "IPC-HK", "isRetImg": "true", "rtspUrl": "rtsp://192.168.123.183:554/Streaming/Channels/0?transportmode=unicast&profile=Profile_1" } 拉流，抓帧，推理 拉流 通过 eKuiper video plugin：目前这个方式只能固定将 URI 写入配置文件，测试可以用，实际没有使用价值。 通过 YiEVAS application 实现：此处为商业代码和功能，有需要的可以咨询我们的热线： 18616669123 抓帧 可以通过图片帧，输出到 总线或 mqtt broker，进行按图片推理，也方便其他应用进行二次处理； 还可以直接拉流时对图片帧进行推理，效率很高，不经过总线的方式进行转发，前提是其他应用不需要视频流和图片； def get_frame(self): cap = cv2.VideoCapture(rtspUri) ret, frame = cap.read() if ret: image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) outputs = rknn_lite.inference(inputs=[image]) return outputs else: return None 推理 可以单独对图片进行推理，通过总线获取图片帧； 也可以在拉流过程中直接推理，具体看业务需要。 结果输出 此处结果与前面的系列文章结果类似，都是以输出到云平台为主。
技术｜在 RV1126 之上使用 rknn_toolkit_lite 和 eKuiper pipeline 进行图片识别推理
本文使用 Base64，OPENCV，Numpy 和 Image 组件进行数据读取转换。 图片格式 一般在边缘计算端进行推理，不会使用一张一张图片，而是采用视频流读帧的方式。 二进制文件格式 file_bytes = "binary file" // 初始化图片二进制 encoded = base64.decodebytes(file_bytes.encode("ascii")) // 转换成base64 img = Image.open(io.BytesIO(encoded)).resize((width, height)) // 打开图片 input_data = np.array(img) // 转换成 numpy image = cv2.cvtColor(input_data, cv2.COLOR_BGR2RGB) // OPENCV 读取图片 图片(二进制)-->base64-->Image-->numpy array-->opencv Base64 编码格式 如果是 Base64 格式输入，处理方法一样，但是，在输入流的地方，要注意 source 采用 json 编码。 // 代码通用 // 输入格式： {"image": "/9j/4AAQSk....X//2Q=="} 图片(json base64）-->Image-->numpy array-->opencv eKuiper source 读取源（输入输入） eKuiper 定义 source 唯一区别就是 format: binary/json，假设都是采用 mqtt broker 作为图片输入。 Binary 二进制格式 输入源定义： { "Name" : "binaryimage", "Options" : { "confKey" : "edgex_mqtt_broker", "datasource" : "image", "format" : "binary", "type" : "mqtt" }, "Statement" : null, "StreamFields" : null, "StreamType" : 0 } 输入图片示例代码, GOLANG，这里的 payload 直接是 ReadFile，二进制： package main import ( "fmt" "io/ioutil" "time" mqtt "github.com/eclipse/paho.mqtt.golang" ) func main() { const TOPIC = "binaryimage" images := []string{ "parrot.jpg", "owl.jpg", "out.jpg", // 其他你需要的图像 } opts := mqtt.NewClientOptions().AddBroker("tcp://192.168.123.184:1883") client := mqtt.NewClient(opts) if token := client.Connect(); token.Wait() && token.Error() != nil { panic(token.Error()) } for _, image := range images { fmt.Println("Publishing " + image) payload, err := ioutil.ReadFile(image) if err != nil { fmt.Println(err) continue } if token := client.Publish(TOPIC, 0, false, payload); token.Wait() && token.Error() != nil { fmt.Println(token.Error()) } else { fmt.Println("Published " + image) } time.Sleep(1 * time.Second) } client.Disconnect(0) } Json 格式 输入源定义： { "Name" : "jsonimage", "Options" : { "confKey" : "edgex_mqtt_broker", "datasource" : "image", "format" : "json", "type" : "mqtt" }, "Statement" : null, "StreamFields" : null, "StreamType" : 0 } 输入图片为 base64 编码后的结果，json 格式如下，发送到 mqtt broker 即可： image.json { "image" : "/9j/4AAQSkZJRgABAQEA8ADwAAD/7Ukit.....ucen55+KEpqqGC7pTdx3X//2Q==" } eKuiper rule 规则定义 labelImageRknn 函数是由 pyairknn 插件（portable）来实现的逻辑处理。 Json 输入格式处理 这里的 FROM jsonimage 来自上面定义的 source，其他根据自身需要修改即可。labelImageRknn(image) 为 MQTT broker 输出内容作为 rule 的输入，这个有些难以理解，多试几遍就明白了。 { "actions" : [ { "mqtt" : { "bufferLength" : 1024, "enableCache" : false, "format" : "json", "insecureSkipVerify" : false, "omitIfEmpty" : false, "protocolVersion" : "3.1.1", "qos" : 0, "resourceId" : "edgex_mqtt_broker", "runAsync" : false, "sendSingle" : true, "server" : "tcp://edgex-mqtt-broker:1883", "topic" : "label" } } ], "id" : "rule-mqtt-image", "options" : { "bufferLength" : 1024, "checkpointInterval" : 300000, "concurrency" : 1, "isEventTime" : false, "lateTolerance" : 1000, "qos" : 0, "restartStrategy" : { "attempts" : 0, "delay" : 1000, "jitter" : 0.1, "maxDelay" : 30000, "multiplier" : 2 }, "sendError" : true, "sendMetaToSink" : false }, "sql" : "SELECT labelImageRknn(image) as label FROM jsonimage" } 二进制输入格式处理 与 Json 格式处理差别只有 labelImageRknn(image) --> labelImageRknn(self)，pyairknn 逻辑并没有变化。 { "sql" : "SELECT labelImageRknn(self) as label FROM jsonimage" } pyairknn 核心处理模块 ekuiper 插件 portable 参照官方 pyai 插件，编写特定格式文件一堆，改几个字符就可以。 实现原理 主要逻辑分析如下几个方面： 初始化 rknn load model； 检查是否符合 RV1126 格式； model 载入时间，如果太长，就需要用整机来预编译model； init runtime，不出错即可。 尤其要注意，这些过程要在 5 秒内完成，因为 eKuiper 超时时间是 5 秒，目前版本还不能对开发者可见修改。这里写一个函数 load_rknn_mode() 来实现清晰的代码。 class LabelImageFunc(Function): def __init__(self): load_rknn_model() pass def validate(self, args: List[Any]): if len(args) != 1: return "invalid arg length" return "" def exec(self, args: List[Any], ctx: Context): logging.debug("executing label") return label(args[0]) def is_aggregate(self): return False 核心推理逻辑： label 函数 注意这里的 return top5 格式就是返回给 rule 里面的结果，不支持动态解析，要做好准备； 中间业务逻辑，就是前面说的载入图片到 numpy； 然后给 rknn 模块进行 inference； 最后对推理结果进行格式化处理； def label(file_bytes): logging.info('image object detecting...') encoded = base64.decodebytes(file_bytes.encode("ascii")) input_data = np.array(Image.open(io.BytesIO(encoded))) image = cv2.cvtColor(input_data, cv2.COLOR_BGR2RGB) image = cv2.resize(image, (224, 224)) t0 = time.time() outputs = rknn_lite.inference(inputs=[image]) print('rknn inference time: {:.3f}ms'.format((time.time() - t0) * 1000)) show_outputs(outputs) output = outputs[0][0] output_sorted = sorted(output, reverse=True) labels = load_labels(LABELS) top5 = [] for i in range(5): value = output_sorted[i] index = np.where(output == value) for j in range(len(index)): if (i + j) >= 5: break if value > 0: for k in range(len(index[j])): top5.append({"index": labels[index[j][k]], "value": float(value)}) else: top5.append({"index": "-1", "value": 0.0}) return top5 最终效果 eKuiper 日志打印 如果有错也会在这里出现，便于查看。可以打开 DEBUG 日志，更加详细。 time="2023-02-17 13:16:23" level=info msg="Start source mqttimage instance 0 successfully" file="node/source_node.go:138" rule=rule-mqtt-image time="2023-02-17 13:16:23" level=info msg="new subscription for topic image, reqId is rule-mqtt-image_mqttimage_0" file="mqtt/mqtt_wrapper.go:220" rule=rule-mqtt-image time="2023-02-17 13:16:23" level=info msg="Successfully subscribed to topic image." file="source/mqtt_source.go:111" rule=rule-mqtt-image I:root:image object detecting... rknn inference time: 7.212ms mobilenet_v1 -----TOP 5----- [ 9 83]: 0.2010498046875 [ 9 83]: 0.2010498046875 2023-02-17 13:16:26,316 - /kuiper/plugins/portable/pyairknn/label.py[line:68] - I: image object detecting... Mqtt 输出 测试阶段，使用 mqtt broker 可以实时看到结果，方便调试。 { "label" : [ { "index" : "84:草原鸡、草原松鸡、草原鸡", "value" : 0.2236328125 }, { "index" : "9:母鸡", "value" : 0.2010498046875 }, { "index" : "83:皱褶松鸡、鹧鸪、Bonasa umbellus", "value" : 0.2010498046875 }, { "index" : "9:母鸡", "value" : 0.2010498046875 }, { "index" : "83:皱褶松鸡、鹧鸪、Bonasa umbellus", "value" : 0.2010498046875 }, { "index" : "139:大鸨", "value" : 0.10638427734375 }, { "index" : "87:鹧鸪", "value" : 0.0626220703125 } ] } YiCLOUD IoT 平台输出 还可以通过脚本或插件方式，将结果输出到 IoT 平台，这里以 YiCLOUD 平台为例。