Modbus客户端示例¶
该示例充当Modbus客户端,分别通过串行线路和TCP发送Modbus请求。显示的对话框允许定义标准请求并显示传入的响应。
该示例必须与Modbus服务器示例或通过TCP或串行端口连接的其他Modbus设备一起使用。
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations
"""PySide6 port of the examples/serialbus/modbus/client example from Qt v6.x"""
from argparse import ArgumentParser, RawDescriptionHelpFormatter
import sys
from PySide6.QtCore import QCoreApplication, QLoggingCategory
from PySide6.QtWidgets import QApplication
from mainwindow import MainWindow
if __name__ == "__main__":
parser = ArgumentParser(prog="Modbus Client Example",
formatter_class=RawDescriptionHelpFormatter)
parser.add_argument("-v", "--verbose", action="store_true",
help="Generate more output")
options = parser.parse_args()
if options.verbose:
QLoggingCategory.setFilterRules("qt.modbus* = true")
a = QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(QCoreApplication.exec())
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations
from enum import IntEnum
from PySide6.QtCore import QUrl, Slot
from PySide6.QtGui import QStandardItemModel, QStandardItem
from PySide6.QtWidgets import QMainWindow
from PySide6.QtSerialBus import (QModbusDataUnit, QModbusDevice,
QModbusRtuSerialClient, QModbusTcpClient)
from ui_mainwindow import Ui_MainWindow
from settingsdialog import SettingsDialog
from writeregistermodel import WriteRegisterModel
class ModbusConnection(IntEnum):
SERIAL = 0
TCP = 1
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self._modbus_device = None
self._settings_dialog = SettingsDialog(self)
self.init_actions()
self._write_model = WriteRegisterModel(self)
self._write_model.set_start_address(self.ui.writeAddress.value())
self._write_model.set_number_of_values(self.ui.writeSize.currentText())
self.ui.writeValueTable.setModel(self._write_model)
self.ui.writeValueTable.hideColumn(2)
vp = self.ui.writeValueTable.viewport()
self._write_model.update_viewport.connect(vp.update)
self.ui.writeTable.addItem("Coils", QModbusDataUnit.Coils)
self.ui.writeTable.addItem("Discrete Inputs", QModbusDataUnit.DiscreteInputs)
self.ui.writeTable.addItem("Input Registers", QModbusDataUnit.InputRegisters)
self.ui.writeTable.addItem("Holding Registers", QModbusDataUnit.HoldingRegisters)
self.ui.connectType.setCurrentIndex(0)
self.onConnectTypeChanged(0)
self._write_size_model = QStandardItemModel(0, 1, self)
for i in range(1, 11):
self._write_size_model.appendRow(QStandardItem(f"{i}"))
self.ui.writeSize.setModel(self._write_size_model)
self.ui.writeSize.setCurrentText("10")
self.ui.writeSize.currentTextChanged.connect(self._write_model.set_number_of_values)
self.ui.writeAddress.valueChanged.connect(self._write_model.set_start_address)
self.ui.writeAddress.valueChanged.connect(self._writeAddress)
@Slot(int)
def _writeAddress(self, i):
last_possible_index = 0
currentIndex = self.ui.writeSize.currentIndex()
for ii in range(0, 10):
if ii < (10 - i):
last_possible_index = ii
self._write_size_model.item(ii).setEnabled(True)
else:
self._write_size_model.item(ii).setEnabled(False)
if currentIndex > last_possible_index:
self.ui.writeSize.setCurrentIndex(last_possible_index)
def _close_device(self):
if self._modbus_device:
self._modbus_device.disconnectDevice()
del self._modbus_device
self._modbus_device = None
def closeEvent(self, event):
self._close_device()
event.accept()
def init_actions(self):
self.ui.actionConnect.setEnabled(True)
self.ui.actionDisconnect.setEnabled(False)
self.ui.actionExit.setEnabled(True)
self.ui.actionOptions.setEnabled(True)
self.ui.connectButton.clicked.connect(self.onConnectButtonClicked)
self.ui.actionConnect.triggered.connect(self.onConnectButtonClicked)
self.ui.actionDisconnect.triggered.connect(self.onConnectButtonClicked)
self.ui.readButton.clicked.connect(self.onReadButtonClicked)
self.ui.writeButton.clicked.connect(self.onWriteButtonClicked)
self.ui.readWriteButton.clicked.connect(self.onReadWriteButtonClicked)
self.ui.connectType.currentIndexChanged.connect(self.onConnectTypeChanged)
self.ui.writeTable.currentIndexChanged.connect(self.onWriteTableChanged)
self.ui.actionExit.triggered.connect(self.close)
self.ui.actionOptions.triggered.connect(self._settings_dialog.show)
@Slot(int)
def onConnectTypeChanged(self, index):
self._close_device()
if index == ModbusConnection.SERIAL:
self._modbus_device = QModbusRtuSerialClient(self)
elif index == ModbusConnection.TCP:
self._modbus_device = QModbusTcpClient(self)
if not self.ui.portEdit.text():
self.ui.portEdit.setText("127.0.0.1:50200")
self._modbus_device.errorOccurred.connect(self._show_device_errorstring)
if not self._modbus_device:
self.ui.connectButton.setDisabled(True)
message = "Could not create Modbus client."
self.statusBar().showMessage(message, 5000)
else:
self._modbus_device.stateChanged.connect(self.onModbusStateChanged)
@Slot()
def _show_device_errorstring(self):
self.statusBar().showMessage(self._modbus_device.errorString(), 5000)
@Slot()
def onConnectButtonClicked(self):
if not self._modbus_device:
return
self.statusBar().clearMessage()
md = self._modbus_device
if md.state() != QModbusDevice.ConnectedState:
settings = self._settings_dialog.settings()
if self.ui.connectType.currentIndex() == ModbusConnection.SERIAL:
md.setConnectionParameter(QModbusDevice.SerialPortNameParameter,
self.ui.portEdit.text())
md.setConnectionParameter(QModbusDevice.SerialParityParameter,
settings.parity)
md.setConnectionParameter(QModbusDevice.SerialBaudRateParameter,
settings.baud)
md.setConnectionParameter(QModbusDevice.SerialDataBitsParameter,
settings.data_bits)
md.setConnectionParameter(QModbusDevice.SerialStopBitsParameter,
settings.stop_bits)
else:
url = QUrl.fromUserInput(self.ui.portEdit.text())
md.setConnectionParameter(QModbusDevice.NetworkPortParameter,
url.port())
md.setConnectionParameter(QModbusDevice.NetworkAddressParameter,
url.host())
md.setTimeout(settings.response_time)
md.setNumberOfRetries(settings.number_of_retries)
if not md.connectDevice():
message = "Connect failed: " + md.errorString()
self.statusBar().showMessage(message, 5000)
else:
self.ui.actionConnect.setEnabled(False)
self.ui.actionDisconnect.setEnabled(True)
else:
md.disconnectDevice()
self.ui.actionConnect.setEnabled(True)
self.ui.actionDisconnect.setEnabled(False)
@Slot(int)
def onModbusStateChanged(self, state):
connected = (state != QModbusDevice.UnconnectedState)
self.ui.actionConnect.setEnabled(not connected)
self.ui.actionDisconnect.setEnabled(connected)
if state == QModbusDevice.UnconnectedState:
self.ui.connectButton.setText("Connect")
elif state == QModbusDevice.ConnectedState:
self.ui.connectButton.setText("Disconnect")
@Slot()
def onReadButtonClicked(self):
if not self._modbus_device:
return
self.ui.readValue.clear()
self.statusBar().clearMessage()
reply = self._modbus_device.sendReadRequest(self.read_request(),
self.ui.serverEdit.value())
if reply:
if not reply.isFinished():
reply.finished.connect(self.onReadReady)
else:
del reply # broadcast replies return immediately
else:
message = "Read error: " + self._modbus_device.errorString()
self.statusBar().showMessage(message, 5000)
@Slot()
def onReadReady(self):
reply = self.sender()
if not reply:
return
if reply.error() == QModbusDevice.NoError:
unit = reply.result()
total = unit.valueCount()
for i in range(0, total):
addr = unit.startAddress() + i
value = unit.value(i)
if unit.registerType().value <= QModbusDataUnit.Coils.value:
entry = f"Address: {addr}, Value: {value}"
else:
entry = f"Address: {addr}, Value: {value:x}"
self.ui.readValue.addItem(entry)
elif reply.error() == QModbusDevice.ProtocolError:
e = reply.errorString()
ex = reply.rawResult().exceptionCode()
message = f"Read response error: {e} (Modbus exception: 0x{ex:x})"
self.statusBar().showMessage(message, 5000)
else:
e = reply.errorString()
code = int(reply.error())
message = f"Read response error: {e} (code: 0x{code:x})"
self.statusBar().showMessage(message, 5000)
reply.deleteLater()
@Slot()
def onWriteButtonClicked(self):
if not self._modbus_device:
return
self.statusBar().clearMessage()
write_unit = self.write_request()
total = write_unit.valueCount()
table = write_unit.registerType()
for i in range(0, total):
addr = i + write_unit.startAddress()
if table == QModbusDataUnit.Coils:
write_unit.setValue(i, self._write_model.m_coils[addr])
else:
write_unit.setValue(i, self._write_model.m_holdingRegisters[addr])
reply = self._modbus_device.sendWriteRequest(write_unit,
self.ui.serverEdit.value())
if reply:
if reply.isFinished():
# broadcast replies return immediately
reply.deleteLater()
else:
reply.finished.connect(self._write_finished)
else:
message = "Write error: " + self._modbus_device.errorString()
self.statusBar().showMessage(message, 5000)
@Slot()
def _write_finished(self):
reply = self.sender()
if not reply:
return
error = reply.error()
if error == QModbusDevice.ProtocolError:
e = reply.errorString()
ex = reply.rawResult().exceptionCode()
message = f"Write response error: {e} (Modbus exception: 0x{ex:x}"
self.statusBar().showMessage(message, 5000)
elif error != QModbusDevice.NoError:
e = reply.errorString()
message = f"Write response error: {e} (code: 0x{error:x})"
self.statusBar().showMessage(message, 5000)
reply.deleteLater()
@Slot()
def onReadWriteButtonClicked(self):
if not self._modbus_device:
return
self.ui.readValue.clear()
self.statusBar().clearMessage()
write_unit = self.write_request()
table = write_unit.registerType()
total = write_unit.valueCount()
for i in range(0, total):
addr = i + write_unit.startAddress()
if table == QModbusDataUnit.Coils:
write_unit.setValue(i, self._write_model.m_coils[addr])
else:
write_unit.setValue(i, self._write_model.m_holdingRegisters[addr])
reply = self._modbus_device.sendReadWriteRequest(self.read_request(),
write_unit,
self.ui.serverEdit.value())
if reply:
if not reply.isFinished():
reply.finished.connect(self.onReadReady)
else:
del reply # broadcast replies return immediately
else:
message = "Read error: " + self._modbus_device.errorString()
self.statusBar().showMessage(message, 5000)
@Slot(int)
def onWriteTableChanged(self, index):
coils_or_holding = index == 0 or index == 3
if coils_or_holding:
self.ui.writeValueTable.setColumnHidden(1, index != 0)
self.ui.writeValueTable.setColumnHidden(2, index != 3)
self.ui.writeValueTable.resizeColumnToContents(0)
self.ui.readWriteButton.setEnabled(index == 3)
self.ui.writeButton.setEnabled(coils_or_holding)
self.ui.writeGroupBox.setEnabled(coils_or_holding)
def read_request(self):
table = self.ui.writeTable.currentData()
start_address = self.ui.readAddress.value()
assert start_address >= 0 and start_address < 10
# do not go beyond 10 entries
number_of_entries = min(int(self.ui.readSize.currentText()),
10 - start_address)
return QModbusDataUnit(table, start_address, number_of_entries)
def write_request(self):
table = self.ui.writeTable.currentData()
start_address = self.ui.writeAddress.value()
assert start_address >= 0 and start_address < 10
# do not go beyond 10 entries
number_of_entries = min(int(self.ui.writeSize.currentText()),
10 - start_address)
return QModbusDataUnit(table, start_address, number_of_entries)
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>601</width>
<height>378</height>
</rect>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>1000</height>
</size>
</property>
<property name="windowTitle">
<string>Modbus Client Example</string>
</property>
<widget class="QWidget" name="centralWidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="5">
<widget class="QLabel" name="label_27">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Server Address:</string>
</property>
</widget>
</item>
<item row="0" column="7">
<widget class="QPushButton" name="connectButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Connect</string>
</property>
<property name="checkable">
<bool>false</bool>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
<property name="default">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="4">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="6">
<widget class="QSpinBox" name="serverEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>247</number>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="connectType">
<item>
<property name="text">
<string>Serial</string>
</property>
</item>
<item>
<property name="text">
<string>TCP</string>
</property>
</item>
</widget>
</item>
<item row="0" column="2">
<widget class="QLabel" name="label_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Port:</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Connection type:</string>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QLineEdit" name="portEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="minimumSize">
<size>
<width>250</width>
<height>0</height>
</size>
</property>
<property name="title">
<string>Read</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Start address:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="readAddress">
<property name="maximum">
<number>9</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Number of values:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="readSize">
<property name="currentIndex">
<number>9</number>
</property>
<item>
<property name="text">
<string>1</string>
</property>
</item>
<item>
<property name="text">
<string>2</string>
</property>
</item>
<item>
<property name="text">
<string>3</string>
</property>
</item>
<item>
<property name="text">
<string>4</string>
</property>
</item>
<item>
<property name="text">
<string>5</string>
</property>
</item>
<item>
<property name="text">
<string>6</string>
</property>
</item>
<item>
<property name="text">
<string>7</string>
</property>
</item>
<item>
<property name="text">
<string>8</string>
</property>
</item>
<item>
<property name="text">
<string>9</string>
</property>
</item>
<item>
<property name="text">
<string>10</string>
</property>
</item>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Result:</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QListWidget" name="readValue">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="writeGroupBox">
<property name="minimumSize">
<size>
<width>225</width>
<height>0</height>
</size>
</property>
<property name="title">
<string>Write</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Start address:</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QTreeView" name="writeValueTable">
<property name="showDropIndicator" stdset="0">
<bool>true</bool>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="rootIsDecorated">
<bool>false</bool>
</property>
<property name="uniformRowHeights">
<bool>true</bool>
</property>
<property name="itemsExpandable">
<bool>false</bool>
</property>
<property name="expandsOnDoubleClick">
<bool>false</bool>
</property>
<attribute name="headerVisible">
<bool>true</bool>
</attribute>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="writeAddress">
<property name="maximum">
<number>9</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Number of values:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="writeSize">
<property name="currentIndex">
<number>9</number>
</property>
<item>
<property name="text">
<string>1</string>
</property>
</item>
<item>
<property name="text">
<string>2</string>
</property>
</item>
<item>
<property name="text">
<string>3</string>
</property>
</item>
<item>
<property name="text">
<string>4</string>
</property>
</item>
<item>
<property name="text">
<string>5</string>
</property>
</item>
<item>
<property name="text">
<string>6</string>
</property>
</item>
<item>
<property name="text">
<string>7</string>
</property>
</item>
<item>
<property name="text">
<string>8</string>
</property>
</item>
<item>
<property name="text">
<string>9</string>
</property>
</item>
<item>
<property name="text">
<string>10</string>
</property>
</item>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label_6">
<property name="text">
<string>Table:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="writeTable"/>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>13</width>
<height>17</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="readButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Read</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="writeButton">
<property name="text">
<string>Write</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="readWriteButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Read-Write</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QStatusBar" name="statusBar"/>
<widget class="QMenuBar" name="menuBar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>601</width>
<height>26</height>
</rect>
</property>
<widget class="QMenu" name="menuDevice">
<property name="title">
<string>&Device</string>
</property>
<addaction name="actionConnect"/>
<addaction name="actionDisconnect"/>
<addaction name="separator"/>
<addaction name="actionExit"/>
</widget>
<widget class="QMenu" name="menuToo_ls">
<property name="title">
<string>Too&ls</string>
</property>
<addaction name="actionOptions"/>
</widget>
<addaction name="menuDevice"/>
<addaction name="menuToo_ls"/>
</widget>
<action name="actionConnect">
<property name="icon">
<iconset resource="modbusclient.qrc">
<normaloff>:/images/connect.png</normaloff>:/images/connect.png</iconset>
</property>
<property name="text">
<string>&Connect</string>
</property>
</action>
<action name="actionDisconnect">
<property name="icon">
<iconset resource="modbusclient.qrc">
<normaloff>:/images/disconnect.png</normaloff>:/images/disconnect.png</iconset>
</property>
<property name="text">
<string>&Disconnect</string>
</property>
</action>
<action name="actionExit">
<property name="icon">
<iconset resource="modbusclient.qrc">
<normaloff>:/images/application-exit.png</normaloff>:/images/application-exit.png</iconset>
</property>
<property name="text">
<string>&Quit</string>
</property>
</action>
<action name="actionOptions">
<property name="icon">
<iconset resource="modbusclient.qrc">
<normaloff>:/images/settings.png</normaloff>:/images/settings.png</iconset>
</property>
<property name="text">
<string>&Options</string>
</property>
</action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<tabstops>
<tabstop>connectType</tabstop>
<tabstop>portEdit</tabstop>
<tabstop>serverEdit</tabstop>
<tabstop>connectButton</tabstop>
<tabstop>readAddress</tabstop>
<tabstop>readSize</tabstop>
<tabstop>readValue</tabstop>
<tabstop>writeAddress</tabstop>
<tabstop>writeSize</tabstop>
<tabstop>writeValueTable</tabstop>
<tabstop>writeTable</tabstop>
<tabstop>readButton</tabstop>
<tabstop>writeButton</tabstop>
<tabstop>readWriteButton</tabstop>
</tabstops>
<resources>
<include location="modbusclient.qrc"/>
</resources>
<connections/>
</ui>
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations
from PySide6.QtCore import Slot
from PySide6.QtWidgets import QDialog
from PySide6.QtSerialPort import QSerialPort
from ui_settingsdialog import Ui_SettingsDialog
class Settings:
def __init__(self):
self.parity = QSerialPort.EvenParity
self.baud = QSerialPort.Baud19200
self.data_bits = QSerialPort.Data8
self.stop_bits = QSerialPort.OneStop
self.response_time = 1000
self.number_of_retries = 3
class SettingsDialog(QDialog):
def __init__(self, parent):
super().__init__(parent)
self.m_settings = Settings()
self.ui = Ui_SettingsDialog()
self.ui.setupUi(self)
self.ui.parityCombo.setCurrentIndex(1)
self.ui.baudCombo.setCurrentText(f"{self.m_settings.baud}")
self.ui.dataBitsCombo.setCurrentText(f"{self.m_settings.data_bits}")
self.ui.stopBitsCombo.setCurrentText(f"{self.m_settings.stop_bits}")
self.ui.timeoutSpinner.setValue(self.m_settings.response_time)
self.ui.retriesSpinner.setValue(self.m_settings.number_of_retries)
self.ui.applyButton.clicked.connect(self._apply)
@Slot()
def _apply(self):
self.m_settings.parity = self.ui.parityCombo.currentIndex()
if self.m_settings.parity > 0:
self.m_settings.parity = self.m_settings.parity + 1
self.m_settings.baud = int(self.ui.baudCombo.currentText())
self.m_settings.data_bits = int(self.ui.dataBitsCombo.currentText())
self.m_settings.stop_bits = int(self.ui.stopBitsCombo.currentText())
self.m_settings.response_time = self.ui.timeoutSpinner.value()
self.m_settings.number_of_retries = self.ui.retriesSpinner.value()
self.hide()
def settings(self):
return self.m_settings
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SettingsDialog</class>
<widget class="QDialog" name="SettingsDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>259</width>
<height>321</height>
</rect>
</property>
<property name="windowTitle">
<string>Modbus Settings</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="3" column="1">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>43</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="timeoutSpinner">
<property name="accelerated">
<bool>true</bool>
</property>
<property name="suffix">
<string> ms</string>
</property>
<property name="minimum">
<number>-1</number>
</property>
<property name="maximum">
<number>5000</number>
</property>
<property name="singleStep">
<number>20</number>
</property>
<property name="value">
<number>200</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Response Timeout:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QPushButton" name="applyButton">
<property name="text">
<string>Apply</string>
</property>
</widget>
</item>
<item row="0" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Serial Parameters</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Parity:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="parityCombo">
<item>
<property name="text">
<string>No</string>
</property>
</item>
<item>
<property name="text">
<string>Even</string>
</property>
</item>
<item>
<property name="text">
<string>Odd</string>
</property>
</item>
<item>
<property name="text">
<string>Space</string>
</property>
</item>
<item>
<property name="text">
<string>Mark</string>
</property>
</item>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Baud Rate:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="baudCombo">
<item>
<property name="text">
<string>1200</string>
</property>
</item>
<item>
<property name="text">
<string>2400</string>
</property>
</item>
<item>
<property name="text">
<string>4800</string>
</property>
</item>
<item>
<property name="text">
<string>9600</string>
</property>
</item>
<item>
<property name="text">
<string>19200</string>
</property>
</item>
<item>
<property name="text">
<string>38400</string>
</property>
</item>
<item>
<property name="text">
<string>57600</string>
</property>
</item>
<item>
<property name="text">
<string>115200</string>
</property>
</item>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Data Bits:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="dataBitsCombo">
<item>
<property name="text">
<string>5</string>
</property>
</item>
<item>
<property name="text">
<string>6</string>
</property>
</item>
<item>
<property name="text">
<string>7</string>
</property>
</item>
<item>
<property name="text">
<string>8</string>
</property>
</item>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Stop Bits:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="stopBitsCombo">
<item>
<property name="text">
<string>1</string>
</property>
</item>
<item>
<property name="text">
<string>3</string>
</property>
</item>
<item>
<property name="text">
<string>2</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Number of retries:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QSpinBox" name="retriesSpinner">
<property name="value">
<number>3</number>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations
from enum import IntEnum
from PySide6.QtCore import QAbstractTableModel, QBitArray, Qt, Signal, Slot
class Column(IntEnum):
NUM_COLUMN = 0
COILS_COLUMN = 1
HOLDING_COLUMN = 2
COLUMN_COUNT = 3
ROW_COUNT = 10
class WriteRegisterModel(QAbstractTableModel):
update_viewport = Signal()
def __init__(self, parent=None):
super().__init__(parent)
self.m_coils = QBitArray(Column.ROW_COUNT, False)
self.m_number = 0
self.m_address = 0
self.m_holdingRegisters = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
def rowCount(self, parent):
return Column.ROW_COUNT
def columnCount(self, parent):
return Column.COLUMN_COUNT
def data(self, index, role):
row = index.row()
column = index.column()
if not index.isValid() or row >= Column.ROW_COUNT or column >= Column.COLUMN_COUNT:
return None
assert self.m_coils.size() == Column.ROW_COUNT
assert len(self.m_holdingRegisters) == Column.ROW_COUNT
if column == Column.NUM_COLUMN and role == Qt.ItemDataRole.DisplayRole:
return f"{row}"
if column == Column.COILS_COLUMN and role == Qt.ItemDataRole.CheckStateRole: # coils
return Qt.Checked if self.m_coils[row] else Qt.Unchecked
# holding registers
if column == Column.HOLDING_COLUMN and role == Qt.ItemDataRole.DisplayRole:
reg = self.m_holdingRegisters[row]
return f"0x{reg:x}"
return None
def headerData(self, section, orientation, role):
if role != Qt.ItemDataRole.DisplayRole:
return None
if orientation == Qt.Orientation.Horizontal:
if section == Column.NUM_COLUMN:
return "#"
if section == Column.COILS_COLUMN:
return "Coils "
if section == Column.HOLDING_COLUMN:
return "Holding Registers"
return None
def setData(self, index, value, role):
row = index.row()
column = index.column()
if not index.isValid() or row >= Column.ROW_COUNT or column >= Column.COLUMN_COUNT:
return False
assert self.m_coils.size() == Column.ROW_COUNT
assert len(self.m_holdingRegisters) == Column.ROW_COUNT
if column == Column.COILS_COLUMN and role == Qt.ItemDataRole.CheckStateRole: # coils
s = Qt.CheckState(int(value))
if s == Qt.Checked:
self.m_coils.setBit(row)
else:
self.m_coils.clearBit(row)
self.dataChanged.emit(index, index)
return True
if column == Column.HOLDING_COLUMN and role == Qt.ItemDataRole.EditRole:
# holding registers
base = 16 if value.startswith("0x") else 10
self.m_holdingRegisters[row] = int(value, base=base)
self.dataChanged.emit(index, index)
return True
return False
def flags(self, index):
row = index.row()
column = index.column()
flags = super().flags(index)
if not index.isValid() or row >= Column.ROW_COUNT or column >= Column.COLUMN_COUNT:
return flags
if row < self.m_address or row >= (self.m_address + self.m_number):
flags &= ~Qt.ItemIsEnabled
if column == Column.COILS_COLUMN: # coils
return flags | Qt.ItemIsUserCheckable
if column == Column.HOLDING_COLUMN: # holding registers
return flags | Qt.ItemIsEditable
return flags
@Slot(int)
def set_start_address(self, address):
self.m_address = address
self.update_viewport.emit()
@Slot(str)
def set_number_of_values(self, number):
self.m_number = int(number)
self.update_viewport.emit()
<RCC>
<qresource prefix="/">
<file>images/application-exit.png</file>
<file>images/connect.png</file>
<file>images/disconnect.png</file>
<file>images/settings.png</file>
</qresource>
</RCC>