IOTOS物联中台Modbus_Tcp驱动对接Wheelers控制器设备

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

目录

前言

驱动目的

适用范围

使用示例

驱动源码

驱动解析


前言

 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': ''})

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IOTOS

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值