PMC-D726X是深圳中电电力技术股份有限公司生产的一款电表,它的通信协议是基于MODBUS协议基础上进行二次开发的通信。
本文的目的,在于说明如何针对这类基于协议框架的基础上,开发针对的JS解码器。
上面的协议文档引用自www.shwanqiao.com ,如果该文档失效,请自行去百度该设备的协议文档。
考虑到很多开发者手头上并没有MODBUS物理设备,可以先使用MODBUS设备模拟器进行新手上路。
等你能够对MODBUS设备模拟器进行操作之后,你可以将MODBUS模拟器替换为你手头上的MODBUS物理设备,就可以直接通信了。
推荐工具:
ModBus设备模拟器:ModbusSlave
ModBus客户端工具:ModbusPoll
你可以在下载和安装这两个工具之后,使用ModbusSlave模拟一个TCP的MODBUS设备,然后使用ModbusPoll工具使用TCP方式去连接这ModbusSlave。
这时候,你会看到ModbusPoll会循环读取ModbusSlave的表格数据,你在ModbusPoll上修改表格数据,它也会被配置到ModbusSlave中去。
很多开发者一开始没有接触过MODBUS,对MODBUS缺失概念,然后会摸不着头脑。
在MODBUS中,是一种类似内存地址的通信协议,它的数据放在连续的内存地址当中,只不过它的内存地址,在MODBUS中叫做寄存器。
我们的计算机每一个内存地址存放的是8位的数据,而MODBUS寄存器存放的是一个16位的数据。
设备厂商,通常会把MODBUS设备的数据放到一个个的寄存器中,你想对MODBUS设备进行操作的时候,就是读取寄存器数据和写入寄存器数据。
你对MODBUS设备的操作,就是读取和写入寄存器。
那么跟着对应的就是,每一个MODBUS设备的生产厂商,他们都会提供一个地址表,告诉你它在哪个寄存器上是如何存储内存数据的。
你对MODBUS设备的操作,就是根据设备厂商提供给你的那一份表,按整数、浮点数、定点数、布尔值,读取和写入数据到寄存器。
灵狐已经提供了ModBus通信协议的框架代码,你可以在Fox-Edge的 组件仓库>动态解码 页面中搜索 MODBUS ,就可以看到该代码。
可以将Modbus解码器中的 foxLibs 和 modbusCoreLibs 两个库函数复制出来,作为ModBus的协议框架的代码。
然后,按照Fox-Edge动态解码器的规范,自定义 问答操作 的方法,在编码函数和解码函数中,调用 foxLibs 和 modbusCoreLibs 的函数。
即可实现自己的解码器PMC-D726X。
在Fox-Edge的 组件仓库>动态解码 页面中搜索 MODBUS ,就可以看到该代码。
可以将Modbus解码器中的 foxLibs 和 modbusCoreLibs 两个库函数复制出来,作为ModBus的协议框架的代码。
待会将这两段代码,复制到自己的 PMC-D726X 解码器中。
在Fox-Edge的 组件仓库>动态解码 页面中新建一个 PMC-D726X 解码器。
在这个解码器中,新建 foxLibs 和 modbusCoreLibs 两个库函数,它们的内容直接从ModBus解码器中复制过来。
然后,为 PMC-D726X 新建一个 cetLibs 库函数,新建一个 读取实时测量数据 问答操作。
先根据 PMC-D726X装置 Modbus通信协议 (V1.0) 协议文档的指导, 通过MODBUS串口测试工具,发送读取数据报文给电表设备,获得一段发送报文和测试报文,作为后面开发调试用的素材。
发送报文:读取77号设备,从寄存器地址0开始的10个寄存器
4D 03 00 00 00 0A CB C1
返回报文:从77号设备,返回了从寄存器地址0开始的10个寄存器数据
4D 03 14 00 00 5B 9A 00 00 00 00 00 00 00 00 00 00 1E 89 00 00 5B 9A 8E 83
在IDEA开发工具中,新建一个PMC-D726X.JS文件,作为开发调试环境。
然后,将刚才在Fox-Edge中复制的 foxLibs 和 modbusCoreLibs 两个库函数,复制代码到IDEA中的PMC-D726X.JS文件中。
然后,编写编码函数,内容如下:
/**
* 编码器的入口函数
* 全局参数:
* fox_edge_param:json string格式的设备参数的合并对象
* 返回值:
* 提供给通道的发送数据。根据不同的通道服务,它可能是HEX结构的文本,也可能是JSON结构的对象,请自行根据选定的通道服务进行确认
*/
function encode() {
var param = JSON.parse(fox_edge_param);
return encodeReadHoldingRegisterRequest(param);
}
/**
* 数据编码
*
* @param param
* @returns {string}
*/
function encodeReadHoldingRegisterRequest(param) {
// 检查输入参数
testEmpty(param.devAddr, "输入参数不能为空: devAddr");
testEmpty(param.regAddr, "输入参数不能为空: regAddr");
testEmpty(param.regCnt, "输入参数不能为空: regCnt");
testEmpty(param.modbusMode, "输入参数不能为空: modbusMode");
// 使用灵狐的modbus库进行编码
var pdu = encodeReadRegistersRequest(0x03, param);
return arrayToHex(pdu);
}
三部分代码在写入后,在IDEA中进行测试和执行
var fox_edge_param = "{\n" + " \"devAddr\": 77,\n" + " \"modbusMode\": \"RTU\",\n" + " \"regAddr\": 0,\n" + " \"regCnt\": 10\n" + "}";
var result = encode()
那么最终你会看到,编码出下列报文,证明验证成功。
4D 03 00 00 00 0A CB C1
编写解码函数,内容如下:
/**
* 解码器的入口函数
* 全局参数:
* fox_edge_data:json string格式 或者 hex string格式的接收数据
* fox_edge_param:json string格式的设备参数的合并对象
* 返回值:
* 提供给通道的发送数据。根据不同的通道服务,它可能是HEX结构的文本,也可能是JSON结构的对象,请自行根据选定的通道服务进行确认
*/
function decode() {
var param = JSON.parse(fox_edge_param);
var result = decodeReadHoldingRegisterRespond(fox_edge_data, param);
return JSON.stringify(result);
}
/**
* 解码数据
*
* @param hexRecv
* @param param
* @returns {{}}
*/
function decodeReadHoldingRegisterRespond(hexRecv, param) {
// 检查输入参数
testEmpty(param.devAddr, "输入参数不能为空: devAddr");
testEmpty(param.regAddr, "输入参数不能为空: regAddr");
testEmpty(param.regCnt, "输入参数不能为空: regCnt");
testEmpty(param.modbusMode, "输入参数不能为空: modbusMode");
// 使用灵狐的modbus库进行解码
var pdu = hexToArray(hexRecv);
var registers = decodeReadRegistersRespond(pdu, param);
// 使用中电的库进行解码
var result = decodeCetReadHoldingRegisters(param, registers)
return result
}
/**
* 用户定义的字典
*
* @returns {*[]}
*/
function getObjectDict() {
var list = [];
list.push({addr: 0, name: "A相电压", format: "UINT32", unit: "x100,V"})
list.push({addr: 2, name: "B相电压", format: "UINT32", unit: "x100,V"})
list.push({addr: 4, name: "C相电压", format: "UINT32", unit: "x100,V"})
list.push({addr: 6, name: "平均相电压", format: "UINT32", unit: "x100,V"})
list.push({addr: 8, name: "AB线电压", format: "UINT32", unit: "x100,V"})
list.push({addr: 10, name: "BC线电压", format: "UINT32", unit: "x100,V"})
list.push({addr: 12, name: "CA线电压", format: "UINT32", unit: "x100,V"})
list.push({addr: 14, name: "平均线电压", format: "UINT32", unit: "x100,V"})
return list
}
function findObjectInfo(objDict, regAddr) {
for (var index = 0; index < objDict.length; index++) {
var obj = objDict[index];
if (obj.addr == regAddr) {
return obj
}
}
return null
}
function decodeObjectInfo(objInfo, reg0, reg1, reg2, reg3) {
if (objInfo.format == "UINT32") {
var value = (reg0 << 8) + (reg1 << 0)
if (objInfo.unit.startsWith("x100,")) {
value = value / 100.0;
}
return value
}
return null
}
function decodeCetReadHoldingRegisters(param, registers) {
var result = {};
var objectDict = getObjectDict();
for (var index = 0; index < registers.length; index++) {
var regAddr = param.regAddr + index;
// 查找定义信息
var objInfo = findObjectInfo(objectDict, regAddr)
if (objInfo == null) {
continue
}
var value = decodeObjectInfo(objInfo, registers[index], registers[index + 1], registers[index + 2], registers[index + 3])
if (value == null) {
continue
}
result[objInfo.name] = value;
}
return result;
}
三部分代码在写入后,在IDEA中进行测试和执行
var fox_edge_param = "{\n" + " \"devAddr\": 77,\n" + " \"modbusMode\": \"RTU\",\n" + " \"regAddr\": 0,\n" + " \"regCnt\": 10\n" + "}";
var fox_edge_data = "4D 03 14 00 00 5B 9A 00 00 00 00 00 00 00 00 00 00 1E 89 00 00 5B 9A 8E 83"
var result = decode()
那么最终你会看到,编码出下列报文,证明验证成功。
{
"A相电压": 234.5,
"B相电压": 0,
"C相电压": 0,
"平均相电压": 78.17,
"AB线电压": 234.5
}
编写解码函数,相关的一些说明
function encode() 函数是Fox-Edge约定的编码函数的入口函数,Fox-Edge会在发送数据前调用该函数,进行数据的编码。
其中,fox_edge_param 是Fox-Edge传递给解码器的设备参数和操作参数,你可以从这里获得自己需要的设备和操作信息。
该信息的是 JSON 格式,其具体参数格式,由你定义。在开发完成之后,记得写一份文档告知其他人如何配置你需要的参数。
function decode() 函数是Fox-Edge约定的解码函数的入口函数,Fox-Edge会在接收数据前调用该函数,进行数据的解码。
解码器实际上是一个JavaScript的工程,所有的库和操作定义的函数,都会被加载到 device服务 的JavaScript引擎之中进行执行。
各函数的调用关系,JavaScript引擎会自动分析。
用户的命名自己的库函数时,要避免引用的modbus库中的函数名称重复的冲突。
复制IDEA开发的代码到Fox-Edge的 PMC-D726X 解码器中,其中可复用的部分函数,放到 cetLibs 库函数中。
那么,一个解码器基本上就在本地开发完成了。
在Fox-Edge上的 组件仓库>服务模块 选择安装TCP或者Serial通道服务。
在Fox-Edge上的 组件仓库>动态界面 选择安装PMC-D726X解码器
在Fox-Edge上的 通道管理>通道列表 添加一个TCP或者Serial的通道
如果是TCP参数,它的格式范例如下:
{
"host": "192.168.1.8",
"port": 502
}
其中,host/port是你的PMC-D726X设备的服务地址,目前是ModbusSlave这个设备模拟器的服务地址,由于ModbusSlave安装在你的计算机上,那么它就是你笔记本的IP。
如果是Serial参数,它的格式范例如下:
{
"baudRate": 9600,
"databits": 8,
"parity": "N",
"serialName": "ttyUSB2",
"stopbits": 1
}
如果你安装了 本地组件/设备模板 ,那么你可以直接生成默认的通道配置参数
在Fox-Edge上的 通道管理>设备列表 添加一个PMC-D726X设备,指明该设备使用的是前面创建的通道
并在参数配置中,填写PMC-D726X的地址和协议模式
如果是TCP参数,它的格式范例如下:
{
"devAddr": 77,
"modbusMode": "TCP"
}
同样,如果你是串口,那么你的工作模式应该填为RTU
{
"devAddr": 77,
"modbusMode": "RTU"
}
如果你安装了 本地组件/设备模板 ,那么你可以直接生成默认的设备配置参数
在Fox-Edge上的 任务管理>通道操作任务 添加一个连通性测试任务,用于测试跟MODBUS设备是否连接上了。
你可以在ModbusSlave或者ModbusPoll的通信日志页面,取一条有效的通信报文,作为验证报文。
例如你是TCP通信,你在ModbusSlave的通信日志页面,复制一条通信数据内容如下:
"4D 03 00 00 00 0A CB C1"
这时候,你可以把该内容填写到模板参数中
这时候,你选择该任务后,点击页面中的发送按钮,你可以看到ModbusSlave给你返回了一段报文:
{
"type": "serialport",
"uuid": "25364b96-06a5-49d0-bbe2-534e0abc6dd4",
"name": "测试通道-MODBUS",
"mode": "exchange",
"send": "4D 03 00 00 00 0A CB C1",
"recv": "4D 03 14 00 00 5B 9A 00 00 00 00 00 00 00 00 00 00 1E 89 00 00 5B 9A 8E 83",
"timeout": 2000,
"msg": "",
"code": 200
}
recv有数据,这时候说明你的通道连接,跟ModbusSlave是正常的,它能够应答你的Fox-Edge上的HEX报文请求。
在Fox-Edge上的 任务管理>设备操作任务 PMC-D726X设备操作任务,比如 读取实时测量数据 的操作任务。
参数说明:
{
"devAddr": 77,---------------------------设备地址
"modbusMode": "RTU",--------------------MODBUS的工作模式:TCP/RTU
"regAddr": 0,--------------------------告知解码器,从0号寄存器开始,连续读取10个寄存器
"regCnt": 10
}
范例:
{
"devAddr": 77,
"modbusMode": "RTU",
"regAddr": 0,
"regCnt": 10
}
这时候,你选择该任务后,点击页面中的发送按钮,你可以看到ModbusSlave给你返回了一段报文:
[
{
"uuid": "6c6d24bf-fad6-49c8-b0d7-495c0667c21d",
"operateMode": "exchange",
"operateName": "读取实时测量数据",
"manufacturer": "深圳中电电力技术股份有限公司",
"deviceType": "PMC-D726X",
"deviceName": "中电电表(PMC-D726X)",
"param": {
"devAddr": 77,
"modbusMode": "RTU",
"regAddr": 0,
"regCnt": 10
},
"timeout": 2000,
"record": true,
"data": {
"commStatus": {
"commFailedCount": 0,
"commFailedTime": 0,
"commSuccessTime": 1753968065552
},
"value": {
"status": {-----------------------------这是解析出来的数据
"AB线电压": 234.5,
"A相电压": 234.5,
"B相电压": 0,
"C相电压": 0,
"平均相电压": 78.17
}
}
},
"msg": "",
"code": 200
}
]
上面的返回,说明你的PMC-D726X设备已经正常访问了
考虑到用户的解码器,会在很多工程项目中使用,你可以将开发的解码器代码,发布到Fox-Edge的中央仓库中, 那么你和其他Fox-Edge的用户的各个项目就都可以使用了。