目录
前言
Modbus协议是一种已广泛应用于当今工业控制领域的通用通讯协议。通过此协议,控制器相互之间、或控制器经由网络(如以太网)可以和其它设备之间进行通信。Modbus协议使用的是主从通讯技术,即由主设备主动查询和操作从设备。一般将主控设备方所使用的协议称为Modbus Master,从设备方使用的协议称为Modbus Slave。典型的主设备包括工控机和工业控制器等;典型的从设备如PLC可编程控制器等。
Modbus TCP/IP协议,去掉了Modbus协议本身的CRC校验,增加了MBAP 报文头。TCP/IP上的Modbus的请求/响应。

驱动目的
将该类型的控制器的相关监测数据对接至IOTOS的线上中台
适用范围
Wheelers的控制器
使用示例
- 连接设备
将设备的电源插上,听到嘀嘀嘀的声音后表面设备正常启动,然后将网线一端插入到设备的网口,一端插入到电脑的网口

- 访问设备
打开电脑的更改适配器选项

找到刚才插入的网口的网卡 ,右键属性,更改其IP地址,使得与控制器在一个网段且IP不一样。

这里该控制器的IP为192.168.11.111,所以将自己电脑的IP改为11网段的即可

更改完IP后打开cmd,ping控制器的IP如果可以ping则说明 可以访问到控制器

- 中台操作
这里只说明了大致的操作流程,具体细节可以参考中台中台文档手册
首先在IOTOS中台创建网关,再打开的界面填写名称后点击确认即可创建成功

网关创建成功后去[我的模板]进行模板的创建

模板的内容如下:

创建完成后再去创建设备实例

选择刚刚创建的网关和模板,点击确认即可创建成功

最后去创建数据点,删除自带的四个数据点然后点击创建数据点,填写名称,选择类型。

然后点击高级配置,填写需要的参数
这样批量读取后,读取几个后面的数据点就要创建几个。

然后其他十二个的配置为

也可以进行单个量的读取,修改其配置即可

- SDK运行驱动
配置完后,在SDK目录下的_driver路径下新建一个名为dr_modbus_tcp的py文件,若路径下已有则不用再创建,其文件内容如下一节源码内容,然后在_example创建一个运行脚本.bat文件,内容如下:

创建完成后双击运行即可看到数据已经上报

至此控制器已经对接完成
驱动源码
# -*- coding:utf-8 -*-
# author : jiji time 12/10/2021
# Modbus tcp server
import sys
import modbus_tk.modbus_tcp as modbus_tcp
from modbus_tk.exceptions import ModbusInvalidResponseError
reload(sys)
sys.setdefaultencoding('utf8')
sys.path.append("..")
from driver import *
# 补码转为负数
def Complement2Negative(int_data):
data = '0b'
bin_data = bin(int_data).split('0b')[1]
print bin(int_data)
if len(bin_data) < 16:
return int_data
else:
for i in bin_data:
if i == '1':
data += '0'
if i == '0':
data += '1'
return (int(data,2)+1)*-1
class ModbusTCPDriver(IOTOSDriverI):
#1、通信初始化
def InitComm(self,attrs):
self._HOST = self.sysAttrs['config']['param']['HOST']
self._PORT = self.sysAttrs['config']['param']['PORT']
self._master = modbus_tcp.TcpMaster(host=self._HOST, port=self._PORT)
self._master.set_timeout(5.0)
self.online(True)
self.setPauseCollect(False)
self.setCollectingOneCircle(False)
# #2、采集引擎回调,可也可以开启,也可以直接注释掉(对于主动上报,不存在遍历采集的情况)
def Collecting(self, dataId):
try:
cfgtmp = self.data2attrs[dataId]['config']
# 过滤非modbus tcp配置的点
if not cfgtmp.has_key('param') or not cfgtmp.has_key('proxy'):
return ()
# 当是新一组功能号时;当没有proxy.pointer,或者有,但是值为null时,就进行采集!否则(有pointer且值不为null,表明设置了采集代理,那么自己自然就被略过了,因为被代理了)当前数据点遍历轮询会被略过!
if 'pointer' not in cfgtmp['proxy'] or cfgtmp['proxy']['pointer'] == None or cfgtmp['proxy']['pointer'] == '':
# 某些过滤掉不采集,因为有的地址的设备不在线,只要在proxy下面配置disabled:true,这样就不会轮训到它!
if 'disabled' in cfgtmp['proxy'] and cfgtmp['proxy']['disabled'] == True:
return ()
else:
self.warn(self.name(dataId))
# 过滤非modbus rtu配置的点
if not cfgtmp['param'].has_key('funid'):
return ()
# 功能码
funid = cfgtmp['param']['funid']
# 设备地址
devid = cfgtmp['param']['devid']
# 寄存器地址
regad = cfgtmp['param']['regad']
# 格式
format = cfgtmp['param']['format']
# 长度
quantity = re.findall(r"\d+\.?\d*", format)
if len(quantity):
quantity = int(quantity[0])
else:
quantity = 1
if format.lower().find('i') != -1: # I、i类型数据为4个字节,所以n个数据,就是4n字节,除一般应对modbus标准协议的2字节一个数据的个数单位!
quantity *= 4 / 2
elif format.lower().find('h') != -1:
quantity *= 2 / 2
elif format.lower().find('b') != -1:
quantity *= 1 / 2
elif format.find('d') != -1:
quantity *= 8 / 2
elif format.find('f') != -1:
quantity *= 4 / 2
elif format.find(
'?') != -1: # 对于功能号1、2的开关量读,开关个数,对于这种bool开关型,个数就不是返回字节数的两倍了!返回的字节个数是动态的,要字节数对应的位数总和,能覆盖传入的个数数值!
quantity *= 1
format = '' # 实践发现,对于bool开关型,传入开关量个数就行,format保留为空!如果format设置为 "?"或"8?"、">?"等,都会解析不正确!!
self.debug(
'>>>>>>' + '(PORT-' + str(self._PORT) + ')' + str(devid) + ' ' + str(funid) + ' ' + str(regad) + ' ' + str(
quantity) + ' ' + str(format))
rtu_ret = self._master.execute(devid, funid, regad, quantity, data_format=format)
self.debug(rtu_ret)
# 私有modbus解析
if cfgtmp['param'].has_key('private'):
# 温湿度传感器
if cfgtmp['param']['private'] == 'Temp&Hum':
data_list = []
for i in rtu_ret:
data_list.append(Complement2Negative(i)*0.1)
rtu_ret = tuple(data_list)
if cfgtmp['param']['private'] == 'time':
# wheelers表的时间的解析
rtu_ret = str(rtu_ret[0]) + "年" + str(rtu_ret[1]) + "月" + str(rtu_ret[3]) + "日" + str(rtu_ret[4]) + "时" + str(rtu_ret[5]) + "分" + str(rtu_ret[6]) +"秒"
return (rtu_ret,)
return rtu_ret
except ModbusInvalidResponseError, e:
self.error(u'MODBUS响应超时, ' + e.message)
return None
except Exception, e:
traceback.print_exc(e.message)
self.error(u'采集解析参数错误:' + e.message)
return None
def Event_setData(self, dataId, value):
# self.setValue(self.name(dataId),value)
return json.dumps({'code': 0, 'msg': '', 'data': ''})
驱动解析
- 导入相关的包,包括Modbus Tcp连接所需要的包和iotos运行驱动的依赖包
# -*- coding:utf-8 -*-
# Modbus tcp server
import sys
import modbus_tk.modbus_tcp as modbus_tcp
from modbus_tk.exceptions import ModbusInvalidResponseError
reload(sys)
sys.setdefaultencoding('utf8')
sys.path.append("..")
from driver import *
- 通讯初始化,定义驱动需要运行的类并将其初始化,获取中台配置的IP和端口并进行TCP的连接
class ModbusTCPDriver(IOTOSDriverI):
#1、通信初始化
def InitComm(self,attrs):
self._HOST = self.sysAttrs['config']['param']['HOST']
self._PORT = self.sysAttrs['config']['param']['PORT']
self._master = modbus_tcp.TcpMaster(host=self._HOST, port=self._PORT)
self._master.set_timeout(5.0)
self.online(True)
self.setPauseCollect(False)
self.setCollectingOneCircle(False)
- 编写采集函数。获取中台数据点的配置,然后通过已连接的Tcp用数据点里面的配置向控制器发生命令,得到返回值后进行数据点的赋值
# #2、采集引擎回调,可也可以开启,也可以直接注释掉(对于主动上报,不存在遍历采集的情况)
def Collecting(self, dataId):
try:
cfgtmp = self.data2attrs[dataId]['config']
# 过滤非modbus tcp配置的点
if not cfgtmp.has_key('param') or not cfgtmp.has_key('proxy'):
return ()
# 当是新一组功能号时;当没有proxy.pointer,或者有,但是值为null时,就进行采集!否则(有pointer且值不为null,表明设置了采集代理,那么自己自然就被略过了,因为被代理了)当前数据点遍历轮询会被略过!
if 'pointer' not in cfgtmp['proxy'] or cfgtmp['proxy']['pointer'] == None or cfgtmp['proxy']['pointer'] == '':
# 某些过滤掉不采集,因为有的地址的设备不在线,只要在proxy下面配置disabled:true,这样就不会轮训到它!
if 'disabled' in cfgtmp['proxy'] and cfgtmp['proxy']['disabled'] == True:
return ()
else:
self.warn(self.name(dataId))
# 过滤非modbus rtu配置的点
if not cfgtmp['param'].has_key('funid'):
return ()
# 功能码
funid = cfgtmp['param']['funid']
# 设备地址
devid = cfgtmp['param']['devid']
# 寄存器地址
regad = cfgtmp['param']['regad']
# 格式
format = cfgtmp['param']['format']
# 长度
quantity = re.findall(r"\d+\.?\d*", format)
if len(quantity):
quantity = int(quantity[0])
else:
quantity = 1
if format.lower().find('i') != -1: # I、i类型数据为4个字节,所以n个数据,就是4n字节,除一般应对modbus标准协议的2字节一个数据的个数单位!
quantity *= 4 / 2
elif format.lower().find('h') != -1:
quantity *= 2 / 2
elif format.lower().find('b') != -1:
quantity *= 1 / 2
elif format.find('d') != -1:
quantity *= 8 / 2
elif format.find('f') != -1:
quantity *= 4 / 2
elif format.find(
'?') != -1: # 对于功能号1、2的开关量读,开关个数,对于这种bool开关型,个数就不是返回字节数的两倍了!返回的字节个数是动态的,要字节数对应的位数总和,能覆盖传入的个数数值!
quantity *= 1
format = '' # 实践发现,对于bool开关型,传入开关量个数就行,format保留为空!如果format设置为 "?"或"8?"、">?"等,都会解析不正确!!
self.debug(
'>>>>>>' + '(PORT-' + str(self._PORT) + ')' + str(devid) + ' ' + str(funid) + ' ' + str(regad) + ' ' + str(
quantity) + ' ' + str(format))
rtu_ret = self._master.execute(devid, funid, regad, quantity, data_format=format)
self.debug(rtu_ret)
# 私有modbus解析
if cfgtmp['param'].has_key('private'):
# 温湿度传感器
if cfgtmp['param']['private'] == 'Temp&Hum':
data_list = []
for i in rtu_ret:
data_list.append(Complement2Negative(i)*0.1)
rtu_ret = tuple(data_list)
if cfgtmp['param']['private'] == 'time':
# wheelers表的时间的解析
rtu_ret = str(rtu_ret[0]) + "年" + str(rtu_ret[1]) + "月" + str(rtu_ret[3]) + "日" + str(rtu_ret[4]) + "时" + str(rtu_ret[5]) + "分" + str(rtu_ret[6]) +"秒"
return (rtu_ret,)
return rtu_ret
except ModbusInvalidResponseError, e:
self.error(u'MODBUS响应超时, ' + e.message)
return None
except Exception, e:
traceback.print_exc(e.message)
self.error(u'采集解析参数错误:' + e.message)
return None
- 编写下发函数。也可以不定义,定义后可以用于中台数据点的下发
def Event_setData(self, dataId, value):
# self.setValue(self.name(dataId),value)
return json.dumps({'code': 0, 'msg': '', 'data': ''})
ModbusTCP驱动教程:对接Wheelers控制器

本文档详细介绍了如何使用ModbusTCP驱动对接Wheelers控制器,包括驱动的目的、适用范围、使用示例及驱动源码解析。首先确保设备连接和网络配置正确,然后在IOTOS中台创建网关和设备模板,配置数据点,最后编写并运行SDK驱动脚本,实现数据上报。
870

被折叠的 条评论
为什么被折叠?



