EdgeX 部署实战

相关文章

EdgeX 相关概念

概述

本文将介绍以下内容:

  1. EdgeX 编译和EdgeX docker image 编译
  2. EdgeX 部署的基本环境准备
  3. EdgeX CLI
  4. EdgeX 连接 Modbus设备
  5. EdgeX 连接MQTT设备
  6. 分布式部署Device Service
  7. 如何设定定时任务
  8. 基于Release1.3.0(Hanoi)版本

实验设备

本文将同时基于x86-64架构设备和ARM64(AARCH64)设备进行实验,下面是硬件信息表

x86-64 设备

CPUIntel® Core™ i5-7267U CPU @ 3.10GHz 单CPU双核四线程
Mem7.7Gi
Disk110GB
NIC1000Mb/s
OSUbuntu 20.04.2 LTS (Focal Fossa)
kernel5.8.0-43-generic

ARM64 设备

CPUAArch64 Processor rev 12 (aarch64) Qualcomm Technologies, Inc SDA845双CPU八核八线程
Mem3.5G
Disk64GB
NIC1000Mb/s
OSUbuntu 16.04.4 LTS (Xenial Xerus)
kernel4.9.103

基本安装

基础软件环境准备

  1. 安装docker
    a. 参考:https://docs.docker.com/engine/install/ubuntu/
# 最简单的安装方式
sudo apt install -y docker.io
# 安装指定版本 或 最新版本
sudo apt remove docker docker-engine docker.io containerd runc
sudo apt update
sudo apt install -y\
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg-agent \
    software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo apt-key fingerprint 0EBFCD88
sudo add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
   $(lsb_release -cs) \
   stable"
# ARM64架构为
sudo add-apt-repository \
   "deb [arch=arm64] https://download.docker.com/linux/ubuntu \
   $(lsb_release -cs) \
   stable"
sudo apt update
apt-cache madison docker-ce
## 安装最新
sudo apt install docker-ce docker-ce-cli containerd.io
## 安装指定版本
sudo apt install docker-ce=5:20.10.0~3-0~ubuntu-focal \
    docker-ce-cli=5:20.10.0~3-0~ubuntu-focal \
    containerd.io
## ARM64设备由于是Ubuntu16.04系统,软件包名存在区别
sudo apt install docker-ce=5:20.10.0~3-0~ubuntu-xenial \
    docker-ce-cli=5:20.10.0~3-0~ubuntu-xenial \
    containerd.io
  1. 安装docker-compose
    a. 参考:https://docs.docker.com/compose/install/
# x86_64设备
# 可以在https://github.com/docker/compose/releases/查看最新的release版本
# 并替换下面的版本号(1.28.4)
sudo curl -L "https://github.com/docker/compose/releases/download/1.28.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose

# ARM64设备
# 官方并不提供arm64版本的docker-compose包,有以下途径获取
##@1 apt install docker-compose (可能版本过低无法使用)
##@2 源码编译
##@3 pip install 
export LC_ALL=C
sudo apt install -y libssl-dev libffi-dev build-essential python3-dev \
    protobuf-compiler libprotoc-dev zlib1g-dev gcc g++ make \
    libxml2-dev libxslt1-dev 
sudo apt install python3-pip 
sudo pip3 install --upgrade pip
sudo pip3 install docker-compose
  1. 安装go语言环境(如果需要编译源码)
    a. 参考:https://golang.org/dl/
    b. 下载&安装合适版本golang安装包(当前需要 >=1.15.x)
wget https://golang.org/dl/go1.15.8.linux-amd64.tar.gz
sudo tar -C /usr/local -xzf ./go1.15.8.linux-amd64.tar.gz
# ARM64 为
wget https://golang.org/dl/go1.15.8.linux-arm64.tar.gz
sudo tar -C /usr/local -xzf ./go1.15.8.linux-arm64.tar.gz

c. 配置环境变量

# 需要替换{your gopath}为一用户指定的gopath的路径
# GOPATH  作为编译后二进制的存放目的地和import包时的搜索路径,包含bin、pkg、src三个目录
cat >> ~/.bashrc << EOF
export PATH=\$PATH:/usr/local/go/bin 
export GOROOT=/usr/local/go
export GOPATH=/{your gopath}/gopath
EOF
source ~/.bashrc
go version

d. 配置国内依赖下载资源

go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.io,direct

e. 其他依赖(如果需要编译源码)

sudo apt install -y libtool pkg-config build-essential \
    autoconf automake uuid-dev
sudo apt install -y libzmq3-dev libzmq5

EdgeX 编译源码

  1. 下载源码
sudo apt install -y git
git clone https://github.com/edgexfoundry/edgex-go.git
  1. 编译
cd edgex-go
go get github.com/rjeczalik/pkgconfig/cmd/pkg-config
go get github.com/edgexfoundry/edgex-go
make build
# 编译好的二进制在./cmd/下
# 编译docker镜像
sudo make docker

部署EdgeX

部署方式

  1. snap
  2. native binaries
  3. docker-compose
  4. kubernetes

native binaries

在上述编译源码完成的基础上,在edgex-go/目录下:(注意需要提前配置好数据库)

make run

docker-compose

docker-compose 部署需要edgex各微服务的容器镜像,在上述源码编译中最后一步(make docker)可以生成docker 镜像,也可以使用 Docker Hub上有已经编译好的docker 镜像。

可以使用github上提供的edgex部署脚本中的docker-compose的yaml文件进行部署,方法最为简便,方法如下:

  1. 下载配置文件
git clone https://github.com/edgexfoundry/developer-scripts.git
  1. 选择对应的release版本
# 本次选用的是1.3.1(Hanoi)当前最新版本
cd developer-scripts/releases/hanoi/compose-files/
# 当前版本提供了Makefile更简便的操作,之前的版本直接使用docker-compose 命令可以启动服务
# Makefile的脚本实际也是调用docker-compose
sudo make pull
sudo make run 
# 查看状态 
sudo docker-compose -pedgex -f docker-compose-hanoi.yml ps
# ARM64设备
sudo make pull arm64
sudo make run arm64
sudo docker-compose -pedgex -f docker-compose-hanoi-arm64.yml ps

部署成功
3. UI

 # 如果需要开启UI
sudo make run-ui
 # ARM64设备额
sudo make run-ui arm64
  • 当前版本的UI只是一个开发测试版本,并不是商业版本
  • 此时可通过浏览器访问本机4000端口访问到UI页面(可以尝试用github上最新的ui代码编译镜像)
    ui

EdgeX CLI

edgex-cli 是用以和edgex微服务交互的命令行接口(工具)。取代以往使用curl命令的方式访问edgex内部的微服务接口。

  1. 安装
sudo snap install edgex-cli

CLI

设备连接 —— Device Service

Modbus 设备

部署Modbus设备服务

vim add-device-modbus.yml
# ARM64注意使用不同镜像
"""
version: '3.7'
networks:
  edgex-network:
    external: true
    name: edgex_edgex-network
services:
  device-modbus:
    container_name: edgex-device-modbus
    environment:
      SERVICE_HOST: edgex-device-modbus
      CLIENTS_COMMAND_HOST: edgex-core-command
      CLIENTS_COREDATA_HOST: edgex-core-data
      CLIENTS_DATA_HOST: edgex-core-data
      CLIENTS_METADATA_HOST: edgex-core-metadata
      CLIENTS_NOTIFICATIONS_HOST: edgex-support-notifications
      CLIENTS_RULESENGINE_HOST: edgex-kuiper
      CLIENTS_SCHEDULER_HOST: edgex-support-scheduler
      CLIENTS_VIRTUALDEVICE_HOST: edgex-device-random
      DATABASES_PRIMARY_HOST: edgex-redis
      EDGEX_SECURITY_SECRET_STORE: "false"
      REGISTRY_HOST: edgex-core-consul
    hostname: edgex-device-modbus
    image: edgexfoundry/docker-device-modbus-go:1.3.1
# For ARM64
#    image: edgexfoundry/docker-device-modbus-go-arm64:1.3.1
    networks:
      edgex-network: {}
    ports:
      - "127.0.0.1:49991:49991"
"""
sudo docker-compose -p edgex -f add-device-modbus.yml up -d
# 查看已启动的设备服务
edgex-cli deviceservice list

获取数据的例子(GET)—— RST5900

设备介绍

RST5900 是一个温湿度一体的传感器,有温度值和湿度值两个数据源。具体信息如下:

  1. 协议和连接信息
    rst5900-1述
  2. Modbus通讯协议功能代码
    rst5900-2
  3. 获取温湿度请求报文
    rst5900-3
  4. 传感器应答数据
    rst5900-4
设备调试
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
    int client_socket,ct;
    unsigned int SERVER_ADDR=0x0a0014c8;//设备IP 10.0.20.200 的十六进制
    struct sockaddr_in server_addr;
    int err;
    client_socket=socket(AF_INET,SOCK_STREAM,0);
    if(client_socket<0)
    {
        printf("socket error\n");
        return -1;
    }
    bzero(&server_addr,sizeof(server_addr));
    server_addr.sin_family=AF_INET;
    server_addr.sin_addr.s_addr=htonl(SERVER_ADDR);
    server_addr.sin_port=htons(502);  // 设备端口 
    ct=connect(client_socket,(struct sockaddr *)&server_addr,sizeof(struct sockaddr)); // 连接
    if(ct<0)
    {
        printf("connect error\n");
        return -1;
    }
    modbusTCP_request(client_socket);  // 发送请求
    close(client_socket);
    return 0;
}

int modbusTCP_request(int client_socket)
{
    int recv,send;
    unsigned char buff[13];
    char request[12];
//	memset(request, 0x00, 12);
    request[5]=0x06;
    request[7]=0x04;
    request[11]=0x02; 
    while(1)
    {
        send=write(client_socket,request,sizeof(request));
		if (send >= 0){
			printf("send:   ");
			for (int i=0; i<send; i++){
				printf("%02x\t",request[i]);
			} 
			printf("\n");
		}
		else {
            printf("send error\n");
            return -1;
        }
        recv=read(client_socket,buff,13);
        if(recv>0)
        {
			printf("recive: ");
            for (int i=0; i<recv; i++)
            {
                printf("%02x\t",buff[i]);
            }
            printf("\n--------------------------------\n");
        }
        request[0]++;
		sleep(1);
    }
    return 0;
}

编译运行

gcc rst_5900_test.c -o rst_5900_test
./rst_5900_test 

在这里插入图片描述

创建 profile
# Copyright 2019 TS Inc. All rights reserved.
name: "RST5900"
manufacturer: "TS Beijing"
model: "RST5900"
description: "Temperature and Humidity Senser."
labels: 
  - "modbus"
  - "senser"
deviceResources: 
    -
        name: "Temperature"
        description: "Sensor Temperature"
        attributes:
            { primaryTable: "INPUT_REGISTERS", startingAddress: "1", rawType: "INT16"}
        properties:
            value:
                { type: "FLOAT32", readWrite: "R",scale: "0.1", floatEncoding: "eNotation"}
            units:
                { type: "String", readWrite: "R", defaultValue: "°C"}
    -
        name: "Humidity"
        description: "Sensor Relative Humidity %"
        attributes:
            { primaryTable: "INPUT_REGISTERS", startingAddress: "2", rawType: "INT16" }
        properties:
            value:
                { type: "FLOAT32", readWrite: "R",scale: "0.1", floatEncoding: "eNotation"}
            units:
                { type: "String", readWrite: "R", defaultValue: "%RH"}
deviceCommands:
    -
        name: "Data"
        get:
            - { index: "1", operation: "get", object: "Temperature", parameter: "Temperature" }
            - { index: "2", operation: "get", object: "Humidity", parameter: "Humidity" }
coreCommands:
    -
        name: "Data"
        get: 
            path: "/api/v1/device/{deviceId}/Data"
            responses: 
                - 
                    code: "200"
                    description: "Get the Data"
                    expectedValues: ["Temperature","Humidity"]
                -
                    code: "503"
                    description: "service unavailable"
                    expectedValues: []
关于profile的介绍:
关于profile的介绍:
1. name:profile标识。添加设备时,设备需要绑定一个profile名称以明确所支持的指令和数据源。
2. deviceResources:一个设备提供的数据源(如温湿度传感器的温度、湿度为两个resouce)
  1. name:数据源的名称标识
  2. attributes:数据源的特征值。在modbus device service中用来标识功能寄存器类型、起始地址等
    1. primaryTable:寄存器类型
      1. HOLDING_REGISTERS
      2. INPUT_REGISTERS
      3. COILS
      4. DISCRETES_INPUT
    2. startingAddress:寄存器起始地址(这里是从1开始,通常对应寄存器手册的0位)
    3. rawType:(可选)解析二进制数据的原始数据类型。如果没有设置,默认使用properties-value-type
       [ 避免精度丢失:如果一个精度为0.01的温度值存储在INT16类型数据中,如果实际温度 26.53,
       则读取值为2653。但是,在转换之后,值是26]
    4. properties:定义数据源的数据类型、精度值、单位等
3. deviceCommands:定义一个命令将数据源组合
  1. name:命令名称
  2. get:get方法,组合可以get的数据源
  3. set:set(post)方法,组合可以控制的数据源
4. coreCommands:定义外部调用的API接口
  • (引)For example, a Modbus device stores the temperature and humidity in an INT16 data type with a float scale of 0.01. If the temperature is 26.53, the read value is 2653. However, following transformation, the value is 26.
设备接入
  1. 上传profile
edgex-cli profile add -f rst_5900_profile.yaml
# 或 curl -X POST http://localhost:48081/api/v1/deviceprofile/uploadfile -F "file=@rst_5900_profile.yaml"

在这里插入图片描述
2. 添加设备

# 使用CLI没有成功,报错“Error: fork/exec /usr/bin/vi: permission denied”
edgex-cli device add --description "Temperature & Humidity Senser " \
    --profileName RST5900 --operatingStatus ENABLED -i
    
 # 直接调接口   
 curl -X POST -d \
'{
  "description": "Temperature & Humidity Senser ",
  "name": "RST5900",
  "adminState": "unlocked",
  "operatingState": "enabled",
  "protocols":{
    "modbus-tcp":{
      "name":"senser",
      "Address":"10.0.20.200",
      "Port":"502",
      "UnitID":"1"
    }
  },
  "labels": [
  ],
  "location": null,
  "service": {
    "name": "edgex-device-modbus"
  },
  "profile": {
    "name": "RST5900"
  }
}' \
http://localhost:48081/api/v1/device

## "Address":"10.0.20.200" ——> 设备的IP
## "Port":"502"           ——>modbus tcp 端口
## profile->name->RST5900 ——> 设备配置文件的名称
获取数据
  1. 查看设备
edgex-cli device list
edgex-cli device list --name RST5900
edgex-cli device list --name RST5900 -v

在这里插入图片描述
2. 获取数据

# Data是profile中定义的CMD
curl -X GET http://localhost:48082/api/v1/device/name/RST5900/command/Data

在这里插入图片描述
在这里插入图片描述
3. 获取历史数据

# 通过CLI
edgex-cli reading list -d RST5900 
#  通过API 
## 参考:https://app.swaggerhub.com/apis-docs/EdgeXFoundry1/core-data
curl -X GET "http://localhost:48080/api/v1/reading/device/RST5900/10" \
    -H  "accept: */*"|jq .

在这里插入图片描述

下发控制的例子(SET)—— Patlite LA6

设备介绍

Patlite LA6 是一组有5个LED灯和一个蜂鸣器的报警器。LED灯和蜂鸣器可以单独控制或群组控制,具体操作需要查看说明书的寄存器手册。本例只使用5个LED的单独控制作为例子,演示对Modbus设备的控制。

  1. 协议和连接信息
    在这里插入图片描述
  2. Modbus通讯协议
    根据说明书寄存器手册可知Holding 寄存器管理单个LED/蜂鸣器,功能码03为读,06为写。
  1. 读取单个LED/蜂鸣器的状态
    1. 请求数据
      1. 起始地址表示从哪一位开始查
      2. 起始地址 0~4 对应 LED1~5 
    2. 响应数据

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

调试设备
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#define PORT 502

int main(int argc, char *argv[])
{
	int client_socket,ct;
	unsigned int SERVER_ADDR=0x0a0014c9;//10.0.20.201
	struct sockaddr_in server_addr;
	int err;
	client_socket=socket(AF_INET,SOCK_STREAM,0);
	if(client_socket<0)
	{
		printf("socket error\n");
		return -1;
	}
	bzero(&server_addr,sizeof(server_addr));
	server_addr.sin_family=AF_INET;
	server_addr.sin_addr.s_addr=htonl(SERVER_ADDR);
	server_addr.sin_port=htons(PORT);
	ct=connect(client_socket,(struct sockaddr *)&server_addr,sizeof(struct sockaddr));
	if(ct<0)
	{
		printf("connect error\n");
		return -1;
	}
	modbusTCP_request_read(client_socket);
	modbusTCP_request_write(client_socket);
	modbusTCP_request_read(client_socket);
	close(client_socket);
	return 0;
}


#if 1
// Read Holding Registers
int modbusTCP_request_read(int client_socket)
{
	int recv,send;
	char request[12];
	int BUFFERSIZE = 21;
	unsigned char buff[BUFFERSIZE];
	request[0]=0x00;
	request[1]=0x01;
	request[2]=0x00;
	request[3]=0x00;
	request[4]=0x00;
	request[5]=0x06;
	request[6]=0x01;
	request[7]=0x03;   // function:0x03 ——> 读取保持寄存器,读取信号塔和蜂鸣器对应寄存器 的当前状态
	/* request[8]~[9]: 寄存器起始地址
	   00~04 —— LED1~5;
	   05    —— 蜂鸣器 
	*/
	request[8]=0x00;
//	request[9]=0x04;  //从第五位 LED5开始查
	request[9]=0x00;  //从第一位 LED1开始查
	// 向后查询几位
	request[10]=0x00;
//	request[11]=0x01; // 查询1个寄存器
	request[11]=0x06; // 查询6个寄存器

	send=write(client_socket,request,sizeof(request));
	if (send >= 0){
		printf("Send:%d\n",send);
		for (int i=0; i<send; i++){
			printf("%02x\t",request[i]);
		}
	}else{
		printf("send error\n");
		return -1;
	}
    recv=read(client_socket,buff,BUFFERSIZE);
    if(recv>0)
	{
		printf("\nRecv:%d\n",recv);
		for (int i=0; i<recv; i++)
		{
			printf("%02x\t",buff[i]);
		}
		printf("\n--------------------------------\n");
	}
	return 0;
}
#endif

#if 1
// Write Single Register (06H)
int modbusTCP_request_write(int client_socket)
{
	int recv,send;
	char request[12];
	int BUFFERSIZE = 12;
	unsigned char buff[BUFFERSIZE];
	request[0]=0x00;
	request[1]=0x03;
	request[2]=0x00;
	request[3]=0x00;
	request[4]=0x00;
	request[5]=0x06;
	request[6]=0xFF;
	request[7]=0x06;   // function:0x06 ——> 写保持寄存器,控制某一位寄存器
	request[8]=0x00;
	/*
	 第10位,request[9]:
	   0x00~0x04 LED1~LED5
       0x05      蜂鸣器
	   0x06      智能模式
	   0x07      clear
	 */
//	request[9]=0x00;  //LED1
//	request[9]=0x01;  //LED2
	request[9]=0x02;  //LED3
//	request[9]=0x03;  //LED4
//	request[9]=0x04;  //LED5
	/*
	LED1~LED5:
		request[10] —— 0x00:不控制; 0x01 控制
		request[11] —— 00:OFF; 01:ON; 02:闪烁 
	蜂鸣器:
		request[10] —— 0x00:不控制; 0x01 控制
		request[11] —— 0x01~0x03 // 读取值为0x00~0x0B
    智能模式: 
		request[10] —— 0x00:不控制; 0x01 控制
		request[11] —— 0x01~0x1F 
    clear   
		request[10] —— 0x00
		request[11] —— 0x01
	 */
	request[10]=0x01;  
	request[11]=0x01;
	send=write(client_socket,request,sizeof(request));
	if (send >= 0){
		printf("Send:%d\n",send);
		for (int i=0; i<send; i++){
			printf("%02x\t",request[i]);
		}
	}else{
		printf("send error\n");
		return -1;
	}
    recv=read(client_socket,buff,BUFFERSIZE);
    if(recv>0)
	{
		printf("\nrecv:%d\n",recv);
		for (int i=0; i<BUFFERSIZE; i++)
		{
			printf("%02x\t",buff[i]);
		}
		printf("\n--------------------------------\n");
	}
	return 0;
}
#endif

编译运行

gcc patlite_la6_test.c -o patlite_la6_test
./patlite_la6_test 
创建profile
name: "LA6-POE"
manufacturer: "PATLATE Japan(Profile by TS Beijing)"
model: "LA6-POE"
description: "Signal Tower LA6-POE."
labels: 
  - "modbus"
  - "led"
deviceResources: 
    -
        name: "LED1"
        description: "First LED Light RED, (0-ignore, 1-ctrl | 0-off, 1-on, 02-flashing, 09-no-chang)"
        attributes:
            { primaryTable: "HOLDING_REGISTERS", startingAddress: "1" }
        properties:
            value:
                { type: "INT16", readWrite: "RW", scale: "1", minimum: "0", maximum: "65535", defaultValue: "0"}
            units:
                { type: "String", readWrite: "R", defaultValue: "Operation Mode"}
    -
        name: "LED2"
        description: "Second LED Light YELLOW, (0-ignore, 1-ctrl | 0-off, 1-on, 02-flashing, 09-no-chang)"
        attributes:
            { primaryTable: "HOLDING_REGISTERS", startingAddress: "2" }
        properties:
            value:
                { type: "INT16", readWrite: "RW", scale: "1", minimum: "0", maximum: "65535", defaultValue: "0"}
            units:
                { type: "String", readWrite: "R", defaultValue: "Operation Mode"}
    -
        name: "LED3"
        description: "3rd LED Light RED, (0-ignore, 1-ctrl | 0-off, 1-on, 02-flashing, 09-no-chang)"
        attributes:
            { primaryTable: "HOLDING_REGISTERS", startingAddress: "3" }
        properties:
            value:
                { type: "INT16", readWrite: "RW", scale: "1", minimum: "0", maximum: "65535", defaultValue: "0"}
            units:
                { type: "String", readWrite: "R", defaultValue: "Operation Mode"}
    -
        name: "LED4"
        description: "4th LED Light RED, (0-ignore, 1-ctrl | 0-off, 1-on, 02-flashing, 09-no-chang)"
        attributes:
            { primaryTable: "HOLDING_REGISTERS", startingAddress: "4" }
        properties:
            value:
                { type: "INT16", readWrite: "RW", scale: "1", minimum: "0", maximum: "65535", defaultValue: "0"}
            units:
                { type: "String", readWrite: "R", defaultValue: "Operation Mode"}
    -
        name: "LED5"
        description: "5th LED Light RED, (0-ignore, 1-ctrl | 0-off, 1-on, 02-flashing, 09-no-chang)"
        attributes:
            { primaryTable: "HOLDING_REGISTERS", startingAddress: "5" }
        properties:
            value:
                { type: "INT16", readWrite: "RW", scale: "1", minimum: "0", maximum: "65535", defaultValue: "0"}
            units:
                { type: "String", readWrite: "R", defaultValue: "Operation Mode"}
deviceCommands:
    -
        name: "CUSTOM"
        set:
            - { index: "1", operation: "set", object: "LED1", parameter: "LED1" }
            - { index: "2", operation: "set", object: "LED2", parameter: "LED2" }
            - { index: "3", operation: "set", object: "LED3", parameter: "LED3" }
            - { index: "4", operation: "set", object: "LED4", parameter: "LED4" }
            - { index: "5", operation: "set", object: "LED5", parameter: "LED5" }
        get:
            - { index: "1", operation: "get", object: "LED1", parameter: "LED1" }
            - { index: "2", operation: "get", object: "LED2", parameter: "LED2" }
            - { index: "3", operation: "get", object: "LED3", parameter: "LED3" }
            - { index: "4", operation: "get", object: "LED4", parameter: "LED4" }
            - { index: "5", operation: "get", object: "LED5", parameter: "LED5" }
coreCommands:
    -
        name: "CUSTOM"
        get: 
            path: "/api/v1/device/{deviceId}/CUSTOM"
            responses: 
                - 
                    code: "200"
                    description: "Get the Configuration"
                    expectedValues: ["LED1","LED2","LED3","LED4","LED5"]
                -
                    code: "503"
                    description: "service unavailable"
                    expectedValues: []
        put:
            path: "/api/v1/device/{deviceId}/CUSTOM"
            parameterNames: ["LED1","LED2","LED3","LED4","LED5" ]
            responses:
                -
                    code: "204"
                    description: "Set the Configuration"
                    expectedValues: []
                -
                    code: "503"
                    description: "service unavailable"
                    expectedValues: []
    -
        name: "LED1"
        get: 
            path: "/api/v1/device/{deviceId}/LED1"
            responses: 
                - 
                    code: "200"
                    description: "Get the Configuration"
                    expectedValues: ["LED1"]
                -
                    code: "503"
                    description: "service unavailable"
                    expectedValues: []
        put:
            path: "/api/v1/device/{deviceId}/LED1"
            parameterNames: ["LED1" ]
            responses:
                -
                    code: "204"
                    description: "Set the Configuration"
                    expectedValues: []
                -
                    code: "503"
                    description: "service unavailable"
                    expectedValues: []
设备接入
  1. 上传profile
edgex-cli profile add -f patlite_la6_led_profile.yaml
# 或 curl -X POST http://localhost:48081/api/v1/deviceprofile/uploadfile -F "file=@patlite_la6_led_profile.yaml"

在这里插入图片描述
2. 添加设备

# 直接调用接口
curl -X POST -d \
'{
  "description": "Patlite LA6 POE",
  "name": "patlite",
  "adminState": "unlocked",
  "operatingState": "enabled",
  "protocols":{
    "modbus-tcp":{
      "name":"patlite_la6",
      "Address":"10.0.20.201",
      "Port":"502",
      "UnitID":"255"
    }
  },
  "labels": [
  ],
  "location": null,
  "service": {
    "name": "edgex-device-modbus"
  },
  "profile": {
    "name": "LA6-POE"
  }
}' \
http://localhost:48081/api/v1/device
通过edgex控制
  1. 查看设备
edgex-cli device list
edgex-cli device list --name patlite
edgex-cli device list --name patlite -v

在这里插入图片描述
2. 控制设备
通过coreCommands——deviceResource(LED1)

# 参数值
## 0x01 0x00 —— 关闭 —— 256
## 0x01 0x01 —— 常亮 —— 257
## 0x01 0x02 —— 闪烁 —— 258
curl -X PUT "http://localhost:48082/api/v1/device/name/patlite/command/LED1" -d '{"LED1":"257"}'

在这里插入图片描述
3. 通过coreCommands——deviceCommands——deviceResources组合(LED1~LED5)

##LED2 —— 常亮
## LED4 —— 闪烁
curl -X PUT "http://localhost:48082/api/v1/device/name/patlite/command/CUSTOM" -d '{"LED1":"256",  "LED2":"257",  "LED3":"256",  "LED4":"258",  "LED5":"256"}'

在这里插入图片描述
4. 获取设备状态

curl -X GET "http://localhost:48082/api/v1/device/name/patlite/command/CUSTOM"|jq .

在这里插入图片描述

MQTT设备

部署MQTT设备服务

  1. Broker
    MQTT Device Service依赖MQTT Broker, 使用时可以单独部署一个MQTT Broker或使用外部的MQTT Broker。在我们本次的实验中,将MQTT Device Service和Broker放在一个yaml文件中部署使用。
  2. docker-compose.yaml
sudo vim add-device-mqtt.yml
"""
version: '3.7'
networks:
  edgex-network:
    external: true
    name: edgex_edgex-network

services:
  mqtt-broker:
    container_name: edgex-mqtt-broker
    hostname: edgex-mqtt-broker
    image: "eclipse-mosquitto"
    networks:
      edgex-network: {}
    ports:
      - "1883:1883"

  device-mqtt:
    container_name: edgex-device-mqtt
    environment:
      CLIENTS_COMMAND_HOST: edgex-core-command
      CLIENTS_COREDATA_HOST: edgex-core-data
      CLIENTS_DATA_HOST: edgex-core-data
      CLIENTS_METADATA_HOST: edgex-core-metadata
      CLIENTS_NOTIFICATIONS_HOST: edgex-support-notifications
      CLIENTS_RULESENGINE_HOST: edgex-kuiper
      CLIENTS_SCHEDULER_HOST: edgex-support-scheduler
      CLIENTS_VIRTUALDEVICE_HOST: edgex-device-random
      DATABASES_PRIMARY_HOST: edgex-redis
      EDGEX_SECURITY_SECRET_STORE: "false"
      REGISTRY_HOST: edgex-core-consul
      SERVICE_HOST: edgex-device-mqtt
      DRIVER_INCOMINGHOST: edgex-mqtt-broker
      DRIVER_INCOMINGTOPIC: MyDataTopic
      DRIVER_RESPONSEHOST: edgex-mqtt-broker
      DRIVER_RESPONSETOPIC: MyResponseTopic
    depends_on:
      - mqtt-broker
    hostname: edgex-device-mqtt
    image: edgexfoundry/docker-device-mqtt-go:1.3.1
#    image: edgexfoundry/docker-device-mqtt-go-arm64:1.3.1
    networks:
      edgex-network: {}
    ports:
      - "127.0.0.1:49982:49982"
"""
## eclipse-mosquitto 版本用的1.6.9
## 通过环境变量设置了broker,topic等
sudo docker-compose -p edgex -f add-device-mqtt.yml up -d
# 查看已启动的设备服务
edgex-cli deviceservice list --name edgex-device-mqtt

MQTT Device Service 的几种模式

在这里插入图片描述

设备主动上报数据

在这里插入图片描述

设备服务请求

在这里插入图片描述

虚拟一个EdgeX MQTT设备

模拟设备程序

该设备有一个resource : time; cmd为:localtime;支持get / set

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# when       who           what, where, why
# --------   ---           ----------------------------------------------
# 19-9-27    zhanglu       init
#=========================================================================
import paho.mqtt.client as mqtt
import time
import json

def on_connect(client, userdata, flags, rc):
  print("Connected with result code " + str(rc))
  client.subscribe(topic="MyCommandTopic") 
  '''
    MyCommandTopic 是该设备订阅的CommandTopic
  '''

def on_message(client, userdata, msg):
  print("topic:",msg.topic , "\nmessage:" + str(msg.payload))
  '''
  '''
  try:
    rcv_data = json.loads(msg.payload.decode('utf-8'))
  except:
    print("error")
  ret_data = rcv_data
  '''
    返回信息中需要:uuid、cmd、method等信息
  '''
  print("method:",rcv_data.get("method",None))
  ## 处理get
  if rcv_data.get("method",None) == "get":
    print ("cmd:",rcv_data.get("cmd",None))
    if rcv_data.get("cmd",None) == "time":
      ret_data["time"] = "{}".format(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))  
      print ("MyResponseTopic publish:",json.dumps(ret_data))
  ## 处理set
  elif rcv_data.get("method",None) == "set":
    print ("cmd:",rcv_data.get("cmd",None))
    print ("set resource value:",rcv_data.get(rcv_data.get("cmd",None)))
  client.publish(topic="MyResponseTopic", payload=json.dumps(ret_data), qos=0)


def mqtt_connect(client, username,password,broker_ip,broker_port=1883,keepalive=60):
  ret = 0
  try:
    client.on_connect = on_connect
    client.on_message = on_message
    client.username_pw_set(username, password)
    ret = client.connect(broker_ip, broker_port, keepalive)
    client.loop_start()
  except:
    ret = -1
  return ret

if __name__ == "__main__":
  client = mqtt.Client()
  ret = mqtt_connect(client,"admin","public","10.0.20.29",1883,60)
  print("mqtt_connect:",ret)
  while True:
    ## 定时上报
    ret_data = { "name": "mqtt_device", "cmd": "time", "time": "{}".format(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())) }
    print ("MyDataTopic publish:",json.dumps(ret_data))
    client.publish(topic="MyDataTopic", payload=json.dumps(ret_data), qos=0)
    time.sleep(6000)
profile
name: "Test.Device.MQTT.Profile"
manufacturer: "TS Beijing"
model: "MQTT-DEVICE"
description: "Test device profile"
labels:
  - "mqtt"
  - "test"
deviceResources:
  -
    name: time
    description: "local time"
    properties:
      value:
        { type: "String", size: "0", readWrite: "W" ,scale: "", offset: "", base: ""  }
      units:
        { type: "String", readWrite: "R", defaultValue: "" }

deviceCommands:
  -
    name: localtime
    get:
    - { index: "1", operation: "get", object: "time", parameter: "time" }
    set:
    - { index: "1", operation: "set", object: "time", parameter: "time" }

coreCommands:
  -
    name: localtime
    get:
      path: "/api/v1/device/{deviceId}/localtime"
      responses:
      -
        code: "200"
        description: "get the local time"
        expectedValues: ["time"]
      -
        code: "503"
        description: "service unavailable"
        expectedValues: []
    put:
      path: "/api/v1/device/{deviceId}/localtime"
      parameterNames: ["time"]
      responses:
      -
        code: "204"
        description: "set the local time."
        expectedValues: []
      -
        code: "503"
        description: "service unavailable"
        expectedValues: []
创建设备
  1. 上传profile
edgex-cli profile add -f mqtt_device_profile.yaml
# 或 curl -X POST http://localhost:48081/api/v1/deviceprofile/uploadfile -F "file=@mqtt_device_profile.yaml"
  1. 添加设备
# 直接调用接口
curl -X POST -d \
'{ 
  "Description": "mqtt device", 
  "Name": "mqtt_device", 
  "adminState": "unlocked", 
  "operatingState": "enabled", 
  "Protocols":{ 
    "mqtt":{ 
      "Schema": "tcp", 
      "Host": "edgex-mqtt-broker",  
      "Port": "1883", 
      "ClientId": "mqtt_device_test", 
      "User": "admin",
      "Password": "public", 
      "Topic": "MyCommandTopic" 
    } 
  }, 
  "labels": [ 
  ], 
  "location": null, 
  "service": { 
    "name": "edgex-device-mqtt" 
  }, 
  "profile": { 
    "name": "Test.Device.MQTT.Profile" 
  } 
}' \
http://localhost:48081/api/v1/device 
启动模拟的设备程序
pip3 install paho.mqtt
python3 edgex_mqtt_device.py
查看主动上报的数据
edgex-cli reading list -d mqtt_device

在这里插入图片描述

get
curl -X GET http://localhost:48082/api/v1/device/name/mqtt_device/command/localtime|jq .

在这里插入图片描述

set
curl -X PUT http://localhost:48082/api/v1/device/name/mqtt_device/command/localtime -d '{"time":"12345"}'|jq .

在这里插入图片描述

分布式部署Device Service

DeviceService的分布式部署是该版本(1.3)的一个新feature,下面进行验证。

注销已有的设备服务

以Modbus device service为例

# 关闭
sudo docker-compose -p edgex -f add-device-modbus.yml stop device-modbus
sudo docker-compose -p edgex -f add-device-modbus.yml rm device-modbus
edgex-cli deviceservice rm --name edgex-device-modbus
edgex-cli deviceservice list

在这里插入图片描述
解除服务对localhost的限制

  • 需要修改docker-compose-*.yaml 允许从localhost之外的端口访问
  • 如果启用API Gateway 应该可以例外(参考:https://docs.edgexfoundry.org/1.3/microservices/security/Ch-APIGateway/)
vim docker-compose-hanoi.yml
# 将所有的127.0.0.1:xxxx:xxxx/tcp 改为:xxxx:xxxx/tcp
"""例如
    ports:
#    - 127.0.0.1:48100:48100/tcp
    - 48100:48100/tcp
"""
# docker-compose -p edgex -f docker-compose-hanoi.yml down
sudo make down 
# docker-compose -p edgex -f docker-compose-hanoi.yml up -d
sudo make run

在这里插入图片描述

删除device service的旧记录

Consul 作为保存了所有微服务的注册信息,如果不删除,将会继续使用旧的配置

# API参考:
# https://docs.edgexfoundry.org/1.3/api/core/Ch-APICoreServiceConfiguration/
# 也可以在http://10.0.20.29:8500/ui/dc1/kv/edgex/devices/1.0/ 页面删除
curl --request DELETE http://127.0.0.1:8500/v1/kv/edgex/devices/1.0/edgex-device-modbus

在其他节点重新启动device service

需要修改device service的docker-compose file。有几点需要注意:
1. 环境变量
  1. 环境变量中SERVICE_HOST需要设置为运行device service的主机IP
  2. 其他HOST信息需要写运行edgex其他服务的主机IP
2. 设置command 
  1. 原来镜像的dockerfile中指定了consul://edgex-core-consul 这里需要改为IP
3. 端口映射要允许除localhost外的访问
4. network_mode: "host"
  1. 原因
    1. SERVICE_HOST一方面被用为服务启动时指定的IP,另一方面要注册到consul和metadata
    2. 如果写为宿主机IP则容器内网络不同,web server will stop
    3. 如果写成localhost则其他微服务如consul,command 调用时调用不通
  2. 解决
    1. 修改程序引入其他环境变量区分
    2. 设备服务侧使用host部署/host network
    3. 使用kubernetes的部署方式,引入service概念解决集群的DNS问题
vim add-device-modbus.yml
"""
version: '3.7'
services:
  device-modbus:
    container_name: edgex-device-modbus
    environment:
      SERVICE_HOST: 10.0.20.58
      CLIENTS_COMMAND_HOST: 10.0.20.29
      CLIENTS_COREDATA_HOST: 10.0.20.29
      CLIENTS_DATA_HOST: 10.0.20.29
      CLIENTS_METADATA_HOST: 10.0.20.29
      CLIENTS_NOTIFICATIONS_HOST: 10.0.20.29
      CLIENTS_RULESENGINE_HOST: 10.0.20.29
      CLIENTS_SCHEDULER_HOST: 10.0.20.29
      CLIENTS_VIRTUALDEVICE_HOST: 10.0.20.29
      DATABASES_PRIMARY_HOST: 10.0.20.29
      EDGEX_SECURITY_SECRET_STORE: "false"
      REGISTRY_HOST: 10.0.20.29
    hostname: edgex-device-modbus
    image: edgexfoundry/docker-device-modbus-go:1.3.1
# For ARM64
#    image: edgexfoundry/docker-device-modbus-go-arm64:1.3.1
    ports:
      - "49991:49991"
    network_mode: "host"
    command: ["--cp=consul://10.0.20.29:8500", "--registry", "--confdir=/res"]
""" 
sudo docker-compose -p edgex -f add-device-modbus.yml up -d

支持服务 —— Supporting Service

设置定时任务 —— Scheduler & event

Scheduler 微服务提供了内部时钟(计时器),和事件event(触发器)以定时发送请求。下面我们创建一个每10秒读一次温湿度数据的任务。

# 创建 定时器
curl -X POST -H "Content-Type: application/json" \
    -H "Cache-Control: no-cache" \
    -d '{"name": "every_10_seconds", "start": "","frequency": "PT10S"}' \
    http://localhost:48085/api/v1/interval
  • name - 唯一名称
  • start - 生效时间,以ISO 8601 YYYYMMDD’T’hhmmss格式表示。空意味着现在。
  • end - 失效时间,同上。
  • frequency - 频度,以ISO 8601 PxYxMxD’T’xHxMxS格式表示。空意味着没有频率
# 设置一个每10秒读一次温湿度数据的任务
curl -X POST -H "Content-Type: application/json" \
    -H "Cache-Control: no-cache" -d \
    '{
        "name":"get-temp-humy-events",
        "interval":"every_10_seconds",
        "target":"core-data",
        "protocol":"http",
        "httpMethod":"GET",
        "address":"edgex-core-command",
        "port":48082, 
        "path":"/api/v1/device/name/RST5900/command/Data" 
    }' \
    http://localhost:48085/api/v1/intervalaction
  • name - 动作唯一名称
  • interval - 间隔唯一名称,与Scheduler名称对应
  • target - 间隔 操作接收者名称(ergo service or name).
  • protocol - 通讯协议 (example HTTP).
  • httpMethod - HTTP protocol verb.
  • address - IP.
  • port -端口.
  • path - url 路径.
  • parameters - (可选)参数例如 http post的 data
# 查询
curl -X GET http://localhost:48085/api/v1/interval|jq .
curl -X GET http://localhost:48085/api/v1/intervalaction  |jq .

在这里插入图片描述
在这里插入图片描述

RulesEngine

  • 参考:
    • https://github.com/emqx/kuiper/blob/master/docs/en_US/edgex/edgex_rule_engine_command.md
      规则引擎在这一版本中使用的是EMQ的kuiper,下面的简单实例是创建一个当湿度大于50%时,LED1亮的规则。
# 规则流
## 创建(默认已有)
curl -X POST \
  http://127.0.0.1:48075/streams \
  -H 'Content-Type: application/json' \
  -d '{  "sql": "create stream demo() WITH (FORMAT=\"JSON\", TYPE=\"edgex\")"}'
## 查询现有
curl -X GET http://127.0.0.1:48075/streams
curl -X GET http://127.0.0.1:48075/streams/{规则流名称}
## 删除
curl -X DELETE http://127.0.0.1:48075/streams/{规则流名称}
# 规则
## 创建 
curl -X POST \
  http://127.0.0.1:48075/rules \
  -H 'Content-Type: application/json' \
  -d '{
          "id": "rule-hum-led1",
          "sql": "SELECT uint8 FROM demo WHERE Humidity > 50.0",
          "actions": [
            {
              "rest": {
                "url": "http://edgex-core-command:48082/api/v1/device/name/patlite/command/LED1",
                "method": "put",
                "retryInterval": -1,
                "dataTemplate": "{\"LED1\":\"257\"}",
                "sendSingle": true
              }
            },
            {
              "log":{}
            }
          ]
        }'
## 查询
curl -X GET http://127.0.0.1:48075/rules
curl -X GET http://127.0.0.1:48075/rules/{规则名称}
## 删除
curl -X DELETE http://127.0.0.1:48075/rules/{规则名称}

在这里插入图片描述
配置完成后当湿度大于50%,LED1会亮灯
在这里插入图片描述

  • 9
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值