0%

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 设备

CPU

Intel® Core™ i5-7267U CPU @ 3.10GHz 单CPU双核四线程

Mem

7.7Gi

Disk

110GB

NIC

1000Mb/s

OS

Ubuntu 20.04.2 LTS (Focal Fossa)

kernel

5.8.0-43-generic

ARM64 设备

CPU

AArch64 Processor rev 12 (aarch64) Qualcomm Technologies, Inc SDA845双CPU八核八线程

Mem

3.5G

Disk

64GB

NIC

1000Mb/s

OS

Ubuntu 16.04.4 LTS (Xenial Xerus)

kernel

4.9.103

基础软件环境准备

  1. 安装docker
    a. 参考:https://docs.docker.com/engine/install/ubuntu/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

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"

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

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/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19



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






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)
1
2
3
4
5
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

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. 配置环境变量

1
2
3
4
5
6
7
8
9


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. 配置国内依赖下载资源

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

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

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

EdgeX 编译源码

  1. 下载源码
1
2
sudo apt install -y git
git clone https://github.com/edgexfoundry/edgex-go.git
  1. 编译
1
2
3
4
5
6
7
cd edgex-go
go get github.com/rjeczalik/pkgconfig/cmd/pkg-config
go get github.com/edgexfoundry/edgex-go
make build


sudo make docker

部署EdgeX

部署方式

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

native binaries

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

1
make run

docker-compose

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

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

  1. 下载配置文件
1
git clone https://github.com/edgexfoundry/developer-scripts.git
  1. 选择对应的release版本
1
2
3
4
5
6
7
8
9
10
11
12

cd developer-scripts/releases/hanoi/compose-files/


sudo make pull
sudo make run

sudo docker-compose -pedgex -f docker-compose-hanoi.yml ps

sudo make pull arm64
sudo make run arm64
sudo docker-compose -pedgex -f docker-compose-hanoi-arm64.yml ps

部署成功
3. UI

1
2
3
4
 
sudo make run-ui

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

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

  1. 安装
1
sudo snap install edgex-cli

CLI

Modbus 设备

部署Modbus设备服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
vim add-device-modbus.yml

"""
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

设备调试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#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;
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];

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;
}

编译运行

1
2
gcc rst_5900_test.c -o rst_5900_test
./rst_5900_test

在这里插入图片描述

创建 profile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

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的介绍:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
关于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
1
2
edgex-cli profile add -f rst_5900_profile.yaml

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

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




获取数据

  1. 查看设备
1
2
3
edgex-cli device list
edgex-cli device list --name RST5900
edgex-cli device list --name RST5900 -v

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

1
2

curl -X GET http://localhost:48082/api/v1/device/name/RST5900/command/Data

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

1
2
3
4
5
6

edgex-cli reading list -d RST5900


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
2
3
4
5
1. 读取单个LED/蜂鸣器的状态
1. 请求数据
1. 起始地址表示从哪一位开始查
2. 起始地址 0~4 对应 LED1~5
2. 响应数据

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

调试设备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
#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;
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

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;

request[8]=0x00;

request[9]=0x00;

request[10]=0x00;

request[11]=0x06;

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

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;
request[8]=0x00;



request[9]=0x02;



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

编译运行

1
2
gcc patlite_la6_test.c -o patlite_la6_test
./patlite_la6_test

创建profile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
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
1
2
edgex-cli profile add -f patlite_la6_led_profile.yaml

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

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. 查看设备
1
2
3
edgex-cli device list
edgex-cli device list --name patlite
edgex-cli device list --name patlite -v

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

1
2
3
4
5




curl -X PUT "http://localhost:48082/api/v1/device/name/patlite/command/LED1" -d '{"LED1":"257"}'

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

1
2
3


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. 获取设备状态

1
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
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"
"""


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65






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))

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))

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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
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
1
2
edgex-cli profile add -f mqtt_device_profile.yaml

  1. 添加设备
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

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

启动模拟的设备程序

1
2
pip3 install paho.mqtt
python3 edgex_mqtt_device.py

查看主动上报的数据

1
edgex-cli reading list -d mqtt_device

在这里插入图片描述

get

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

在这里插入图片描述

set

1
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为例

1
2
3
4
5

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的限制

1
2
3
4
5
6
7
8
9
10
11
vim docker-compose-hanoi.yml

"""例如
ports:
# - 127.0.0.1:48100:48100/tcp
- 48100:48100/tcp
"""

sudo make down

sudo make run

在这里插入图片描述

删除device service的旧记录

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

1
2
3
4



curl --request DELETE http://127.0.0.1:8500/v1/kv/edgex/devices/1.0/edgex-device-modbus

在其他节点重新启动device service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
需要修改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问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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

设置定时任务 —— Scheduler & event

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

1
2
3
4
5

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格式表示。空意味着没有频率
1
2
3
4
5
6
7
8
9
10
11
12
13
14

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
1
2
3

curl -X GET http://localhost:48085/api/v1/interval|jq .
curl -X GET http://localhost:48085/api/v1/intervalaction |jq .

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

RulesEngine

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39


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会亮灯
在这里插入图片描述