跳到主要内容

1. Modbus转Mqtt网关技术案例

本网关提供了程序iot_client,为用户提供便捷的数据采集、数据上云的功能,设备端支持ModbusRtuModbusTcp工业协议,云端支持EMQX、阿里云。

1.1. 参数配置

iot_client的所有配置均通过JSON配置文件来实现,所有配置文件存放在/usr/share/iot_client目录中。

1.1.1. 1 全局配置

全局配置用于设置网关向上连接的云服务器,和向下连接的连接器。

全局配置文件的路径为:/usr/share/iot_client/general_config.json,例子如下:

{
"cloud": {
"type": "emqx",
"configuration": "emqx.json"
},
"connectors": [{
"name": "modbus_serial_ttysWK0",
"type": "modbus",
"configuration": "modbus_serial_ttysWK0.json"
}]
}

1.1.1.1. 1.1 子节点cloud

该节点描述云端的类型和云端的配置文件,目前只支持阿里云和EMQX。

参数默认值描述和说明
type云服务器类型,取值"emqx"、"aliyun"
configuration云服务器配置文件,存放在/usr/share/iot_client目录

上面配置案例表明网关使用EMQX作为云服务器,配置文件为emqx.json

1.1.1.2. 1.2 子节点connectors

该节点描述网关所连接的连接器,连接器可以是modbus连接器,CAN总线连接器等等,目前只支持modbus连接器,它是对协议操作方法的描述。一个网关可以连接0个或多个连接器,一个连接器上可以连接多个使用相同协议的外设。

参数默认值描述和说明
name连接器名称,字段不要有空格,由字母、数字、下划线组成,且必须唯一
type连接器类型,目前只能取值"modbus"
configuration连接器配置文件,存放在/usr/share/iot_client目录

上面配置案例表明网关连接了一个名为modbus_serial_ttysWK0modbus连接器,该连接器的配置文件为modbus_serial_ttysWK0.json

1.1.1.2.1. 1.2.1 添加多个连接器

connectors子节点是一个json数组,可以添加多个连接器,eg:

{
"cloud": {
"type": "emqx",
"configuration": "emqx.json"
},
"connectors": [{
"name": "modbus_serial_ttysWK0",
"type": "modbus",
"configuration": "modbus_serial_ttysWK0.json"
},{
"name": "modbus_serial_ttysWK1",
"type": "modbus",
"configuration": "modbus_serial_ttysWK1.json"
}]
}
1.1.1.2.2. 1.2.2 不添加任何连接器

如果不连接任何外设,直接取空即可。

{
"cloud": {
"type": "emqx",
"configuration": "emqx.json"
},
"connectors": []
}

1.1.2. 2 云配置

网关只能连接一个云平台,只能设置连接EMQX或者阿里云平台。

1.1.2.1. 2.1 EMQX云配置

例子:[/usr/share/iot_client/emqx.json]

{
"host": "127.0.0.1",
"port": 8883,
"ssl": true,
"qos": 1,
"keepAlive": 60,
"username": "firefly",
"password": "",
"clientId": ""
}

参数配置说明如下表格:

参数缺省默认值描述和说明
host云服务器IP地址
port端口号(根据使用tcp还是ssl填写相应端口号)
sslfalse是否使用ssl连接
qos1qos=1设置mqtt至少发送一次
keepAlive60保活时间
username用户名
password密码
clientId由iot_client自动生成客户端ID

注意:若使用ssl连接,需配置用户自己的ca证书文件。ca证书文件固定路径为:/usr/share/iot_client/cacert.pem,用户将文件重命名覆盖即可。

1.1.2.2. 2.2 阿里云配置

如果配置了EMQX云配置,请跳过当前章节。

例子1:阿里云一机一码配置:[/usr/share/iot_client/aliyun_normal.json]

{
"region": "cn-shanghai",
"is_public_instance" : true,
"is_dynreg" : false,
"is_data_mode" : true,
"product_key" : "a1lrT6i2bBF",
"device_name" : "rk3308_1",
"device_secret" : "8a313b03c4ad7c13fc4a3ed8304b7f7d"
}

例子2:阿里云一型一密(白名单)配置:[/usr/share/iot_client/aliyun_whitelist.json]

{
"region": "cn-shanghai",
"is_public_instance": true,
"is_dynreg": true,
"is_data_mode" : true,
"product_key": "a1lrT6i2bBF",
"device_name": "rk3308_5",
"is_white_list": true,
"product_secret": "KFgItEca9W14uCja"
}

例子3:阿里云一型一密(免白名单)配置:[/usr/share/iot_client/aliyun_no_whitelist.json]

{
"region": "cn-shanghai",
"is_public_instance": true,
"is_dynreg": true,
"is_data_mode" : true,
"product_key": "a1lrT6i2bBF",
"device_name": "rk3308_6",
"is_white_list": false,
"product_secret": "KFgItEca9W14uCja"
}

参数配置说明如下表格:

参数缺省默认值描述和说明
region区域
is_public_instance是否是共用实例
is_dynreg是否是动态注册
is_data_mode缺省为true是否采用物模型
product_key产品key
product_secret产品秘钥,在is_dynreg为true时必须提供该字段
device_name设备名
device_secret设备秘钥,在is_dynreg为false时必须提供该字段
is_white_list是否是白名单模式,在is_dynreg为true时必须提供该字段

1.1.3. 3 连接器配置

1.1.3.1. 3.1 modbus连接器配置

eg: [/usr/share/iot_client/modbus_serial_ttysWK0.json](这是一个ModbusRtu协议的配置文件)

{
"name": "modbus_serial_ttysWK0",
"type": "rtu",
"port": "/dev/ttysWK0",
"stopbits": 1,
"bytesize": 8,
"parity": "N",
"baudrate": 4800,
"devices": [{
"unitId": 1,
"deviceName": "RS_WS_N01",
"uploadInterval": 1000,
"cycleInterval": 1000,
"sendDataOnlyOnChange": false,
"order": "dcba",
"attributes": [{
"tag": "humidity",
"type": "int16",
"rw_access": "r",
"variableRangeConversion": 0.1,
"doubleDigit": 2,
"address": 40001
}, {
"tag": "temperature",
"type": "int16",
"variableRangeConversion": 0.1,
"doubleDigit": 2,
"rw_access": "r",
"address": 40002
}]
}, {
"unitId": 2,
"deviceName": "RS_WS_N02",
"uploadInterval": 1000,
"cycleInterval": 1000,
"sendDataOnlyOnChange": false,
"order": 18,
"attributes": [{
"tag": "humidity",
"type": "int16",
"rw_access": "r",
"variableRangeConversion": 0.1,
"doubleDigit": 2,
"address": 40001
}, {
"tag": "temperature",
"type": "int16",
"variableRangeConversion": 0.1,
"doubleDigit": 2,
"rw_access": "r",
"address": 40002
}]
}]
}
1.1.3.1.1. 3.1.1 ModbusRtu配置

一个最简单的ModbusRtu配置文件如下

{
"name": "modbus_serial_ttysWK0",
"type": "rtu",
"port": "/dev/ttysWK0",
"stopbits": 1,
"bytesize": 8,
"parity": "N",
"baudrate": 4800,
"devices": []
}
参数默认值描述和说明
name连接器名称,字段不要有空格,由字母、数字、下划线组成
type类型,取值rtu/tcp
port串口设备文件
stopbits停止位,取值1,2
bytesize数据位,取值5,6,7,8
parity奇偶校验位,N:无校验,E:偶校验,O:奇校验
baudrate波特率,取值300,1200,2400,4800,9600,19200,38400,56000,115200,187500,230400
devices参考3.1.3节
1.1.3.1.2. 3.1.2 ModbusTcp配置

一个最简单的ModbusTcp配置文件如下,

{
"name": "modbus_tcp",
"type": "tcp",
"host": "168.168.101.95",
"port": 502,
"devices": []
}
参数默认值描述和说明
name连接器名称,字段不要有空格,由字母、数字、下划线组成
type类型,取值rtu/tcp
hostIP地址
port端口号
devices参考3.1.3节
1.1.3.1.3. 3.1.3 节点devices:设备配置

设备配置包含以下信息:

{
"unitId": 1,
"deviceName": "Test Device A1",
"uploadInterval": 1000,
"cycleInterval": 1000,
"sendDataOnlyOnChange": true,
"order": "dcba",
"attributes": []
}
参数默认值描述和说明
unitIdslave id
deviceName设备名,同一连接器的不同设备名,必须唯一
uploadInterval上传间隔,单位ms
cycleInterval轮循间隔,单位ms
sendDataOnlyOnChange是否只在数据发生改变才提交数据
order字节序,取值"abcd","cdab","dcba","badc"
attributes参考3.1.4节

这里补充一下order属性

modbus中,除了操作线圈寄存器和离散寄存器的大小是以1个字节为单位外,其他寄存器(保持寄存器,输入寄存器)每次操作的单位都是2个字节。而外设的体系结构不同,导致数据有不同的大小端字节序。

大端:高字节存放在低地址,低字节存放在高地址,比如:

char buf[4] = 0x12345678;(左边是数据的高位)
低地址 -----------------> 高地址
0x12 | 0x34 | 0x56 | 0x78

小端:高字节存放在高地址,低字节存放在低地址,比如:

char buf[4] = 0x12345678;(左边是数据的高位)
低地址 ------------------> 高地址
0x78 | 0x56 | 0x34 | 0x12

如果一个属性是由2个属性构成的,2个寄存器保存的值哪个在前, 就需要设置寄存器高低位在前。只由一个寄存器保存的变量是不需要设置寄存器高低位在前的。比如:

有一个属性,由2个连续寄存器组成,在内存中某一个时刻其数据为:0x1234(左边是数据的高位)
高位在前时,读取出来为:12 34
低位在前时,读取出来为:34 12

根据大小端,和高低位,数据在modbus的存储格式就有4种:"abcd","cdab","dcba","badc"

"abcd":大端模式 + 高位在前
"cdab":大端模式 + 低位在前
"badc":小端模式 + 高位在前
"dcba":小端模式 + 低位在前
1.1.3.1.4. 3.1.4 节点attributes:属性配置

一个完整的属性配置为:

{
"tag": "bit_coil_rw",
"type": "bit",
"rw_access": "rw",
"address": 4,
"variableRangeConversion": 1,
"bitDigit": 0,
"doubleDigit": 0
}
参数默认值描述和说明
tag属性名称,字段不要有空格,由字母、数字、下划线组成。若是同一设备的不同属性名,必须唯一
type属性类型,取值bool,bit,int16,uint16,int32,uint32,int64,uint64,float,double
rw_access读写权限,rw(可读可写),r(只读),w(只写)
addressPLC地址
variableRangeConversion省略默认值1量程转换,取值1,(0.1)^N,(10)^N (N为正整数)
bitDigit省略默认值0寄存器的第几位,取值0-15
doubleDigit省略默认值0浮点数小数点保留位数

补充:

前面介绍过,modbus寄存器分为线圈寄存器,离散寄存器,输入寄存器和保持寄存器。

①其中,离散寄存器和输入寄存器是只读的,线圈寄存器和保持寄存器可读可写的。

②不同的寄存器类型,人为的将PLC地址规定为不同的地址类型

地址类型寄存器类型PLC地址范围
0X线圈寄存器地址1~10000
1X离散寄存器地址1000120000,110001165535
3X输入寄存器地址3000140000,310001365535
4X保持寄存器地址4000150000,410001465535

type:属性类型

目前支持的属性类型为bool,bit,int16,uint16,int32,uint32,int64,uint64,float,double

对于线圈寄存器或离散寄存器,只支持boolbit类型

对于输入寄存器或保持寄存器,支持所有类型

rw_access:读写

对于不同的寄存器类型,能够选择的rw_access就会有所区别,比如线圈寄存器和保持寄存器可取值为"r","w","rw",离散寄存器和保持寄存器只能取值"r"

addressPLC地址

modbus实际上并非使用PLC地址作为读写地址,而是使用modbus地址。由于modbus地址是从0开始的,其换算规则如下:

PLC地址范围modbus地址换算
1~10000=PLC地址-1
1000120000,110001165535=PLC地址-10001 =PLC地址-110001
3000140000,310001365535=PLC地址-30001 =PLC地址-310001
4000150000,410001465535=PLC地址-40001 =PLC地址-410001

比如某个属性的地址为411234,则实际上modbus读写的地址为411234-410001=1233

variableRangeConversion:量程转换

某些从modbus读取到的数值并非实际数值,需要经过数值的缩小或放大,比如某PLC手册:

寄存器地址 内容 操作
40001H 湿度(单位 0.1%RH) 只读
40002H 温度(单位 0.1℃) 只读

温度:
当温度低于 0℃时以补码形式上传
FF9B H(十六进制)=-101=>温度=-10.1℃
湿度:
292 H(十六进制)=658=>湿度=65.8%RH

可见,数值需要输小10倍,才是真实的数值,variableRangeConversion此时取值为0.1

doubleDigit:小数点保留位数

同样以上面为例,doubleDigit需要取值为1,小数点后保留1位有效数字

bitDigit:寄存器的第几位

如果某个属性的数值,来源于某个寄存器的第几位,就需要设置这个参数了。注意类型为bool或bit,这个字段才有用。eg:

{
"tag": "rw_bool",
"type": "bool",
"rw_access": "rw",
"bitDigit": 13,
"address": 40004
}

1.2. 案例:使用阿里云服务器

本案例网关使用阿里云作为云服务器,外设使用温湿度传感器,该温湿度传感器使用标准ModbusRtu协议。

  • 登录阿里云平台,控制台->物联网平台

_images/into_aliyun_1.png

_images/into_aliyun_2.png

  • 公共实例->产品->创建产品

_images/into_aliyun_3.png

_images/start.png

  • 新建产品,输入自定义产品名称,设置为网关设备,联网采用蜂窝网络

_images/add_product.png

  • 配置产品功能

点击刚刚创建的产品

_images/product_config_1.png

点击:功能定义->编辑草稿

_images/product_config_2.png

点击:添加模块

_images/product_config_3.png

添加模块:创建温湿度传感器1

_images/product_config_4.png

添加模块:创建温湿度传感器2

_images/product_config_5.png

点击:温湿度传感器1->添加自定义功能

_images/product_config_6.png

添加属性:温度检测

_images/module_config_1.png

添加属性:湿度检测

_images/module_config_2.png

同理,给温度传感器2添加同样的属性,这里略

最后,发布上线即可。

_images/module_config_3.png

  • 新建设备

_images/add_device_1.png

_images/add_device_2.png

_images/add_device_3.png

  • 复制产品ProductKey,设备名DeviceName,设备秘钥DeviceSecret,后面网关需要用到。

_images/set_device_config.png

  • 使用USB TYPE C线接入网关,另一端接入电脑

网关所有的配置文件都在/usr/share/iot_client目录下,进入/usr/share/iot_client

cd /usr/share/iot_client

编辑网关全局配置文件general_config.json

vi general_config.json

更改文件的内容如下:

{
"cloud": {
"type": "aliyun",
"configuration": "aliyun_normal.json"
},
"connectors": [{
"name": "modbus_serial",
"type": "modbus",
"configuration": "modbus_serial.json"
}]
}

上述文件的意义:

①配置云服务器类型为aliyun,配置文件为aliyun_normal.json

②配置连接器类型为modbus,配置文件为modbus_serial.json

  • 配置云服务器文件aliyun_normal.json

配置网关以非动态注册的方式连接阿里云,以物模型数据将网关的数据采集上报

vi aliyun_normal.json

更改product_key,device_name,device_secret的值为上面复制的值,其他保留不变。

{
"region": "cn-shanghai",
"is_public_instance" : true,
"is_dynreg" : false,
"is_data_mode" : true,
"product_key" : "你自己产品的ProductKey",
"device_name" : "你自己设备的DeviceName",
"device_secret" : "你自己设备的DeviceSecret"
}

比如,例子中的配置文件如下:

{
"region": "cn-shanghai",
"is_public_instance" : true,
"is_dynreg" : false,
"is_data_mode" : true,
"product_key" : "a17pmHuRtr4",
"device_name" : "ihc-3308Y-device1",
"device_secret" : "3d0ad657f020fe601d0c80a3f880832b"
}
  • 配置连接器modbus配置文件modbus_serial.json

根据PLC连接的串口,以及PLC手册配置串口的基本信息

{
"name": "modbus_serial",
"type": "rtu",
"port": "/dev/ttysWK0",
"stopbits": 1,
"bytesize": 8,
"parity": "N",
"baudrate": 4800,
"devices": []
}

由于我们连接了2个PLC在”/dev/ttysWK0”即RS485_1上,查看PLC手册的信息,将这个2个PLC的具体信息加入上面的”devices”,

{
"name": "modbus_serial",
"type": "rtu",
"port": "/dev/ttysWK0",
"stopbits": 1,
"bytesize": 8,
"parity": "N",
"baudrate": 4800,
"devices": [{
"unitId": 1,
"deviceName": "DEV1",
"uploadInterval": 1000,
"cycleInterval": 1000,
"sendDataOnlyOnChange": true,
"order": "dcba",
"attributes": [{
"tag": "humidity",
"type": "int16",
"rw_access": "r",
"variableRangeConversion": 0.1,
"doubleDigit": 2,
"address": 40001
}, {
"tag": "temperature",
"type": "int16",
"variableRangeConversion": 0.1,
"doubleDigit": 2,
"rw_access": "r",
"address": 40002
}]
}, {
"unitId": 2,
"deviceName": "DEV2",
"uploadInterval": 1000,
"cycleInterval": 1000,
"sendDataOnlyOnChange": false,
"order": "dcba",
"attributes": [{
"tag": "humidity",
"type": "int16",
"rw_access": "r",
"variableRangeConversion": 0.1,
"doubleDigit": 2,
"address": 40001
}, {
"tag": "temperature",
"type": "int16",
"variableRangeConversion": 0.1,
"doubleDigit": 2,
"rw_access": "r",
"address": 40002
}]
}]
}

之后,运行iot_client,将PLC采集到的数据发送到阿里云,打印日志如下:

~ # iot_client
establish mbedtls connection with server(host='a17pmHuRtr4.iot-as-mqtt.cn-shanghai.aliyuncs.com', port=[443])
success to establish tcp, fd=4
success to establish mbedtls connection, fd = 4(cost 45110 bytes in total, max used 47786 bytes)
[INFO ] "2021-03-02 16:57:49" (iot_aliyun_core.c:60) - Aliyun connect success!
[INFO ] "2021-03-02 16:57:49" (iot_client.c:140) - Iot client started!
[INFO ] "2021-03-02 16:57:49" (iot_aliyun_core.c:632) - Subscribing to topic /a17pmHuRtr4/ihc-3308Y-device1/user/sys_request success!
[INFO ] "2021-03-02 16:57:49" (iot_aliyun_core.c:632) - Subscribing to topic /a17pmHuRtr4/ihc-3308Y-device1/user/data_request success!
[INFO ] "2021-03-02 16:57:49" (iot_aliyun_core.c:632) - Subscribing to topic /a17pmHuRtr4/ihc-3308Y-device1/user/cloud_response success!
Opening /dev/ttysWK0 at 4800 bauds (N, 8, 1)
[INFO ] "2021-03-02 16:57:50" (iot_modbus_core.c:577) - Connecting to modbus(type:rtu,slaveId:1) success!
[01][03][00][00][00][01][84][0A]
Waiting for a confirmation...
<01><03><02><00><DE><38><1C>
[01][03][00][01][00][01][D5][CA]
Waiting for a confirmation...
<01><03><02><00><64><B9><AF>
[INFO ] "2021-03-02 16:57:51" (iot_aliyun_core.c:395) - Publishing payload: &#123;"DEV1:humidity":22.2,"DEV1:temperature":10&#125;
...

进入阿里云,点击设备->物模型数据->温湿度传感器,即可查看到从本地上传的物模型数据。

_images/aliyun_data.png

1.3. 案例:使用EMQX服务器

本案例网关使用EMQX作为云服务器,外设使用温湿度传感器,该温湿度传感器使用标准ModbusRtu协议。

设置全局配置文件,设置云服务器类型为emqx,配置文件为emqx.json,连接器类型为modbus,配置文件为modbus_serial_ttysWK0.json

vi /usr/share/iot_client/general_config.json

&#123;
"cloud": &#123;
"type": "emqx",
"configuration": "emqx.json"
&#125;,
"connectors": [&#123;
"name": "modbus_serial_ttysWK0",
"type": "modbus",
"configuration": "modbus_serial_ttysWK0.json"
&#125;]
&#125;

设置EMQX服务器信息emqx.json。由于使用tcp连接,ssl字段取值为false。

vi /usr/share/iot_client/emqx.json

&#123;
"host": "168.168.100.172",
"port": 1883,
"ssl": false,
"qos": 1,
"keepAlive": 60,
"username": "firefly",
"password": "",
"clientId": ""
&#125;

设置连接器modbus_serial_ttysWK0.json

vi /usr/share/iot_client/modbus_serial_ttysWK0.json

&#123;
"name": "modbus_serial_ttysWK0",
"type": "rtu",
"port": "/dev/ttysWK0",
"stopbits": 1,
"bytesize": 8,
"parity": "N",
"baudrate": 4800,
"devices": [&#123;
"unitId": 1,
"deviceName": "RS_WS_N01",
"uploadInterval": 1000,
"cycleInterval": 1000,
"sendDataOnlyOnChange": false,
"order": "dcba",
"attributes": [&#123;
"tag": "humidity",
"type": "int16",
"rw_access": "r",
"variableRangeConversion": 0.1,
"doubleDigit": 2,
"address": 40001
&#125;, &#123;
"tag": "temperature",
"type": "int16",
"variableRangeConversion": 0.1,
"doubleDigit": 2,
"rw_access": "r",
"address": 40002
&#125;]
&#125;]
&#125;

运行iot_client,打印日志如下:

/usr/share/iot_client # iot_client
[INFO ] "2021-03-02 14:34:43" (iot_emqx_core.c:311) - ClientId:mqtt-gateway-5c07c88b92014d87925c73718fab7231
[INFO ] "2021-03-02 14:34:43" (iot_emqx_core.c:312) - Connect to tcp://168.168.100.172:1883 success!
[INFO ] "2021-03-02 14:34:43" (iot_client.c:140) - Iot client started!
[INFO ] "2021-03-02 14:34:43" (iot_emqx_core.c:372) - Subscribing to topic v1/gateway/mqtt-gateway-5c07c88b92014d87925c73718fab7231/sys_request success!
[INFO ] "2021-03-02 14:34:43" (iot_emqx_core.c:372) - Subscribing to topic v1/gateway/mqtt-gateway-5c07c88b92014d87925c73718fab7231/data_request success!
[INFO ] "2021-03-02 14:34:43" (iot_emqx_core.c:372) - Subscribing to topic v1/gateway/mqtt-gateway-5c07c88b92014d87925c73718fab7231/cloud_response success!
Opening /dev/ttysWK0 at 4800 bauds (N, 8, 1)
[INFO ] "2021-03-02 14:34:43" (iot_modbus_core.c:577) - Connecting to modbus(type:rtu,slaveId:1) success!
[01][03][00][00][00][01][84][0A]
Waiting for a confirmation...
<01><03><02><00><00><B8><44>
[01][03][00][01][00][01][D5][CA]
Waiting for a confirmation...
<01><03><02><00><00><B8><44>
[INFO ] "2021-03-02 14:34:44" (iot_emqx_core.c:355) - Publishing to topic v1/cloud/mqtt-gateway-5c07c88b92014d87925c73718fab7231 : &#123;"request":"attribute_send","packet_id":1,"timestamp":1614666884,"code":0,"msg":"","data":&#123;"device_name":"RS_WS_N01","attribute":&#123;"humidity":0,"temperature":0&#125;&#125;&#125;