基于pyside2(pyqt5)和pymodbus实现的modbus协议通信界面程序

项目源码全开源。

本次项目python版本环境: python3.10.9

所需要的库: PySide2、pymodbus、serial、threading、time

提示:项目文件要在同一个目录下。本案例只有modbus-rtu模式,就是通过串行口进行通讯的,如果需要使用modbus-tcp模式的,只需要改部分代码就可以了,大家可以自行探索一下。

环境配置

1.命令行输入(下载慢的话可使用国内镜像源下载,之前的文章我讲过)

  • pip install PySide2 -i https://pypi.tuna.tsinghua.edu.cn/simple/
  • pip install pymodbus
  • pip install pyserial

2.安装modbus仿真工具,modbus Poll和modbus slave。

两个软件都只有几兆,去网上随便一搜就有很多,所以这两个软件还请大家自行下载。

3.安装Configure Virtual Serial Port Driver,用来创建虚拟串口。

下文讲解完代码再讲述如何使用这几个软件。

运行效果

首先运行程序后会自动检测电脑上的串行端口,包括虚拟端口,然后在复选框选中某个串行口的时候,后面会显示出相应串行口的描述。

接下来填写的是波特率,波特率是必须要填写的,一般填9600就可以,因为那两个仿真软件是默认就是9600的波特率,波特率是必须一样才能正常连接以及通信的。然后选择模式,模式有两种,一种是模拟modbus poll,一种是模拟modbus slave。modbus poll是用来和modbus slave通信的,modbus slave是用来和modbus poll通信的。仿真的时候要选择正确。

连接之后可以读取modbus从站设备保持型寄存器中的值,实时显示在下方表格中。

在表格里也可以实时进行更改从站设备中保持型寄存器的值。

这里只是一个案例用来参考学习,所以没有进行多个从设备的仿真。有需要可以自己参考这个做一下,或者有问题的话可以评论告诉我。

代码修改:

麻烦大家把这行注释掉,然后加上一个print打印的。后来使用的时候发现在线程中不可以使用QMessageBox,这里应该要使用Qt中的QThread或者QProcess。后面有时间再给大家改吧。

代码解释

class MODBUS:
    def __init__(self, app1):
        self.app1: QApplication = app1
        file = QFile('./modbus.ui')
        file.open(QFile.ReadOnly)
        file.close()
        self.window = QUiLoader().load(file)
        self.port_dict = self.initial()
        self.window.port_box.setEditable(True)
        self.window.port_box.addItems(list(self.port_dict.keys()))
        self.window.port_box.currentIndexChanged.connect(self.box_change)
        self.window.description.setText(self.port_dict[list(self.port_dict.keys())[0]])
        self.window.data_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        self.table_init()
        self.window.add_button.clicked.connect(self.add_data)
        self.window.drop_button.clicked.connect(self.drop_data)
        self.window.conn_button.clicked.connect(self.connect_modbus)
        self.window.close_button.clicked.connect(self.close_modbus)
        self.window.data_table.cellChanged.connect(self.write_registers)
        self.client = None
        self.store = ModbusSlaveContext(di=ModbusSequentialDataBlock.create(), co=ModbusSequentialDataBlock.create(),
                                        hr=ModbusSequentialDataBlock.create(), ir=ModbusSequentialDataBlock.create())
        self.context = ModbusServerContext(slaves=self.store, single=True)

这段代码定义了一个MODBUS类对象,通过__init__函数初始化,初始化函数中首先加载了QT的ui文件,然后调用类里面的initial()函数获取电脑的串行端口,并且将电脑的所有串行端口显示在界面的COMBOX控件中,下面让这个串行端口复选框绑定了一个函数,在复选框选项改变的时候会执行这个函数,函数作用是在另一个控件LineEdit中显示这个串行端口的详细描述。下面是一系列给按钮绑定点击事件函数的代码。最后面三行代码,倒数第三行定义了一个客户端对象,倒数第二行定义了一个Modbusslave的存储器对象,用来存储数据,最后一行是使用上面的存储器对象来生成一个上下文对象。

    def box_change(self):
        port = self.window.port_box.currentText()
        self.window.description.setText(self.port_dict[port])

    def initial(self):
        # 获取所有串行端口
        ports = serial.tools.list_ports.comports()
        # 打印所有串行端口信息
        port_dict = {}
        for port, desc, hwid in sorted(ports):
            # print(f"Port: {port} | Description: {desc} | Hardware ID: {hwid}")
            port_dict[port] = desc
        return port_dict

这两个函数就是上面用到的两个函数,一个用来获取电脑串行端口号,一个用来更新lineedit控件的文本。

    def table_init(self):
        for row in range(10):
            item = QTableWidgetItem()
            item.setText(f'{row}')
            self.window.data_table.setItem(row, 0, item)

    def add_data(self):
        rowcount = self.window.data_table.rowCount()
        # print(rowcount)
        self.window.data_table.insertRow(rowcount)
        item = QTableWidgetItem()
        item.setText(f'{rowcount}')
        self.window.data_table.setItem(rowcount, 0, item)

    def drop_data(self):
        rowcount = self.window.data_table.rowCount()
        # print(rowcount)
        self.window.data_table.removeRow(rowcount - 1)

这三个函数,第一个是用来给表格初始化,让表格的第一列显示出0到9的数字,代表保持型寄存器的地址。第二个函数用来给表格控件增加一行,第三个则用来给表格控件删除一行。

    def run_modbus_server(self, port):
        try:
            server = StartSerialServer(context=self.context, port=port, framer=ModbusRtuFramer)
            server.serve_forever()
        except Exception as e:
            print(e)

    def close_modbus(self):
        self.client.close()

这两个函数,第一个函数是用来运行一个modbus-rtu模式的server服务器。这个在多线程运行比较好,所以单独定义了一个函数。第二个函数很明显是用来关闭客户端的。

    def connect_modbus(self):
        if self.window.method_type.currentText() == "client/poll":
            self.window.status.setText("连接中...")
            baud = self.window.baud.text()
            port = self.window.port_box.currentText()
            self.client = ModbusClient(method='rtu', port=port, baudrate=int(baud))
            if self.client.connect():
                self.window.status.setText("连接成功!")
                table_change = threading.Thread(target=self.read_registers, args=tuple())
                table_change.start()
        elif self.window.method_type.currentText() == "server/slave":
            self.window.status.setText("连接中...")
            port = self.window.port_box.currentText()
            modbus_thread = threading.Thread(target=self.run_modbus_server, args=(port,))
            modbus_thread.start()
            self.window.status.setText("连接成功!")
            table_change = threading.Thread(target=self.read_registers, args=tuple())
            table_change.start()

这个函数用来连接modbus的服务器或者客户端。如果使用的是服务器模式的话,这个函数的主要作用是打开一个modbus-rtu服务器,并且开一个线程用来持续读取数据。如果用的是客户端模式的话,这个函数用来连接服务器,并且也会打开一个多线程用来读取数据。

    def read_registers(self):
        if self.window.method_type.currentText() == "client/poll":
            while True:
                row_num = self.window.data_table.rowCount()
                response = self.client.read_holding_registers(address=0, count=row_num, slave=1)
                if not response.isError():
                    for i in range(len(response.registers)):
                        try:
                            if str(response.registers[i]) != self.window.data_table.item(i, 2).text():
                                item = QTableWidgetItem()
                                item.setText(f'{response.registers[i]}')
                                self.window.data_table.setItem(i, 2, item)
                            else:
                                pass
                        except:
                            item = QTableWidgetItem()
                            item.setText(f'{response.registers[i]}')
                            self.window.data_table.setItem(i, 2, item)
                else:
                    # QMessageBox.information(self.window, "提示", "寄存器数量小于正在读取的数据数量。")
                    print("寄存器数量小于正在读取的数据数量。")
                    break
                time.sleep(2)
        elif self.window.method_type.currentText() == "server/slave":
            while True:
                try:
                    row_num = self.window.data_table.rowCount()
                    self.store.setValues(3, row_num - 1, [0])
                    response = self.context[0].getValues(3, 0, count=row_num)
                    # print(response)
                    for i in range(len(response)):
                        try:
                            if str(response[i]) != self.window.data_table.item(i, 2).text():
                                item = QTableWidgetItem()
                                item.setText(f'{response[i]}')
                                self.window.data_table.setItem(i, 2, item)
                        except:
                            item = QTableWidgetItem()
                            item.setText(f'{response[i]}')
                            self.window.data_table.setItem(i, 2, item)
                except Exception as e:
                    print(e)
                time.sleep(2)

这个代码用来读取保持型寄存器的数据,同样分为服务器模式和客户端模式,读取之后会把有改变的数据更新在表格中,读取频率的话我设置的是2s读取一次。

    def write_registers(self, row, column):
        if self.window.method_type.currentText() == "client/poll":
            data = self.window.data_table.item(row, column).text()
            value = int(data)
            self.client.write_register(address=row, value=value, slave=1)
        elif self.window.method_type.currentText() == "server/slave":
            data = self.window.data_table.item(row, column).text()
            value = int(data)
            self.store.setValues(3, row, [value])

这个函数用来向保持型寄存器写入数据,通过在表格中修改值来改变modbus保持型寄存器的数据。

项目源代码

链接:https://pan.baidu.com/s/1BPPb_qvIZTRQYKfUpwAuAA?pwd=wwww 
提取码:wwww

  • 18
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

会三十六变的猫

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

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

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

打赏作者

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

抵扣说明:

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

余额充值