Terminal Example¶
The Terminal Example shows how to create a terminal for a simple serial interface by using Qt Serial Port.
It demonstrates the main features of the QSerialPort class, like configuration, I/O implementation and so forth. Also, the class QSerialPortInfo is invoked to display information about the serial ports available in the system.
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations
import sys
from PySide6.QtWidgets import QApplication
from mainwindow import MainWindow
"""PySide6 port of the serialport/terminal example from Qt v6.x"""
if __name__ == "__main__":
a = QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(a.exec())
# 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 Qt, Signal, Slot
from PySide6.QtGui import QPalette
from PySide6.QtWidgets import QPlainTextEdit
UNHANDLED_KEYS = [Qt.Key.Key_Backspace, Qt.Key.Key_Left, Qt.Key.Key_Right, Qt.Key.Key_Up,
Qt.Key.Key_Down]
class Console(QPlainTextEdit):
get_data = Signal(bytearray)
def __init__(self, parent=None):
super().__init__(parent)
self.m_localEchoEnabled = False
self.document().setMaximumBlockCount(100)
p = self.palette()
p.setColor(QPalette.Base, Qt.black)
p.setColor(QPalette.Text, Qt.green)
self.setPalette(p)
@Slot(bytearray)
def put_data(self, data):
self.insertPlainText(data.decode("utf8"))
bar = self.verticalScrollBar()
bar.setValue(bar.maximum())
def set_local_echo_enabled(self, e):
self.m_localEchoEnabled = e
def keyPressEvent(self, e):
key = e.key()
if key not in UNHANDLED_KEYS:
if self.m_localEchoEnabled:
super().keyPressEvent(e)
self.get_data.emit(e.text().encode())
def mousePressEvent(self, e):
self.setFocus()
def mouseDoubleClickEvent(self, e):
pass
def contextMenuEvent(self, e):
pass
# 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 QIODeviceBase, Slot
from PySide6.QtWidgets import QLabel, QMainWindow, QMessageBox
from PySide6.QtSerialPort import QSerialPort
from ui_mainwindow import Ui_MainWindow
from console import Console
from settingsdialog import SettingsDialog
HELP = """The <b>Simple Terminal</b> example demonstrates how to
use the Qt Serial Port module in modern GUI applications
using Qt, with a menu bar, toolbars, and a status bar."""
def description(s):
return (f"Connected to {s.name} : {s.string_baud_rate}, "
f"{s.string_data_bits}, {s.string_parity}, {s.string_stop_bits}, "
f"{s.string_flow_control}")
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.m_ui = Ui_MainWindow()
self.m_status = QLabel()
self.m_console = Console()
self.m_settings = SettingsDialog(self)
self.m_serial = QSerialPort(self)
self.m_ui.setupUi(self)
self.m_console.setEnabled(False)
self.setCentralWidget(self.m_console)
self.m_ui.actionConnect.setEnabled(True)
self.m_ui.actionDisconnect.setEnabled(False)
self.m_ui.actionQuit.setEnabled(True)
self.m_ui.actionConfigure.setEnabled(True)
self.m_ui.statusBar.addWidget(self.m_status)
self.m_ui.actionConnect.triggered.connect(self.open_serial_port)
self.m_ui.actionDisconnect.triggered.connect(self.close_serial_port)
self.m_ui.actionQuit.triggered.connect(self.close)
self.m_ui.actionConfigure.triggered.connect(self.m_settings.show)
self.m_ui.actionClear.triggered.connect(self.m_console.clear)
self.m_ui.actionAbout.triggered.connect(self.about)
self.m_ui.actionAboutQt.triggered.connect(qApp.aboutQt) # noqa: F821
self.m_serial.errorOccurred.connect(self.handle_error)
self.m_serial.readyRead.connect(self.read_data)
self.m_console.get_data.connect(self.write_data)
@Slot()
def open_serial_port(self):
s = self.m_settings.settings()
self.m_serial.setPortName(s.name)
self.m_serial.setBaudRate(s.baud_rate)
self.m_serial.setDataBits(s.data_bits)
self.m_serial.setParity(s.parity)
self.m_serial.setStopBits(s.stop_bits)
self.m_serial.setFlowControl(s.flow_control)
if self.m_serial.open(QIODeviceBase.ReadWrite):
self.m_console.setEnabled(True)
self.m_console.set_local_echo_enabled(s.local_echo_enabled)
self.m_ui.actionConnect.setEnabled(False)
self.m_ui.actionDisconnect.setEnabled(True)
self.m_ui.actionConfigure.setEnabled(False)
self.show_status_message(description(s))
else:
QMessageBox.critical(self, "Error", self.m_serial.errorString())
self.show_status_message("Open error")
@Slot()
def close_serial_port(self):
if self.m_serial.isOpen():
self.m_serial.close()
self.m_console.setEnabled(False)
self.m_ui.actionConnect.setEnabled(True)
self.m_ui.actionDisconnect.setEnabled(False)
self.m_ui.actionConfigure.setEnabled(True)
self.show_status_message("Disconnected")
@Slot()
def about(self):
QMessageBox.about(self, "About Simple Terminal", HELP)
@Slot(bytearray)
def write_data(self, data):
self.m_serial.write(data)
@Slot()
def read_data(self):
data = self.m_serial.readAll()
self.m_console.put_data(data.data())
@Slot(QSerialPort.SerialPortError)
def handle_error(self, error):
if error == QSerialPort.ResourceError:
QMessageBox.critical(self, "Critical Error",
self.m_serial.errorString())
self.close_serial_port()
@Slot(str)
def show_status_message(self, message):
self.m_status.setText(message)
<?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>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Simple Terminal</string>
</property>
<widget class="QWidget" name="centralWidget">
<layout class="QVBoxLayout" name="verticalLayout"/>
</widget>
<widget class="QMenuBar" name="menuBar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>26</height>
</rect>
</property>
<widget class="QMenu" name="menuCalls">
<property name="title">
<string>Calls</string>
</property>
<addaction name="actionConnect"/>
<addaction name="actionDisconnect"/>
<addaction name="separator"/>
<addaction name="actionQuit"/>
</widget>
<widget class="QMenu" name="menuTools">
<property name="title">
<string>Tools</string>
</property>
<addaction name="actionConfigure"/>
<addaction name="actionClear"/>
</widget>
<widget class="QMenu" name="menuHelp">
<property name="title">
<string>Help</string>
</property>
<addaction name="actionAbout"/>
<addaction name="actionAboutQt"/>
</widget>
<addaction name="menuCalls"/>
<addaction name="menuTools"/>
<addaction name="menuHelp"/>
</widget>
<widget class="QToolBar" name="mainToolBar">
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
<addaction name="actionConnect"/>
<addaction name="actionDisconnect"/>
<addaction name="actionConfigure"/>
<addaction name="actionClear"/>
</widget>
<widget class="QStatusBar" name="statusBar"/>
<action name="actionAbout">
<property name="text">
<string>&About</string>
</property>
<property name="toolTip">
<string>About program</string>
</property>
<property name="shortcut">
<string>Alt+A</string>
</property>
</action>
<action name="actionAboutQt">
<property name="text">
<string>About Qt</string>
</property>
</action>
<action name="actionConnect">
<property name="icon">
<iconset resource="terminal.qrc">
<normaloff>:/images/connect.png</normaloff>:/images/connect.png</iconset>
</property>
<property name="text">
<string>C&onnect</string>
</property>
<property name="toolTip">
<string>Connect to serial port</string>
</property>
<property name="shortcut">
<string>Ctrl+O</string>
</property>
</action>
<action name="actionDisconnect">
<property name="icon">
<iconset resource="terminal.qrc">
<normaloff>:/images/disconnect.png</normaloff>:/images/disconnect.png</iconset>
</property>
<property name="text">
<string>&Disconnect</string>
</property>
<property name="toolTip">
<string>Disconnect from serial port</string>
</property>
<property name="shortcut">
<string>Ctrl+D</string>
</property>
</action>
<action name="actionConfigure">
<property name="icon">
<iconset resource="terminal.qrc">
<normaloff>:/images/settings.png</normaloff>:/images/settings.png</iconset>
</property>
<property name="text">
<string>&Configure</string>
</property>
<property name="toolTip">
<string>Configure serial port</string>
</property>
<property name="shortcut">
<string>Alt+C</string>
</property>
</action>
<action name="actionClear">
<property name="icon">
<iconset resource="terminal.qrc">
<normaloff>:/images/clear.png</normaloff>:/images/clear.png</iconset>
</property>
<property name="text">
<string>C&lear</string>
</property>
<property name="toolTip">
<string>Clear data</string>
</property>
<property name="shortcut">
<string>Alt+L</string>
</property>
</action>
<action name="actionQuit">
<property name="icon">
<iconset resource="terminal.qrc">
<normaloff>:/images/application-exit.png</normaloff>:/images/application-exit.png</iconset>
</property>
<property name="text">
<string>&Quit</string>
</property>
<property name="shortcut">
<string>Ctrl+Q</string>
</property>
</action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<resources>
<include location="terminal.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
import sys
from PySide6.QtCore import Slot
from PySide6.QtGui import QIntValidator
from PySide6.QtWidgets import QComboBox
from PySide6.QtSerialPort import QSerialPort, QSerialPortInfo
from PySide6.QtWidgets import QDialog
from ui_settingsdialog import Ui_SettingsDialog
BLANK_STRING = "N/A"
CUSTOM_BAUDRATE_INDEX = 4
class Settings():
def __init__(self):
self.name = ""
self.baud_rate = 0
self.string_baud_rate = ""
self.data_bits = QSerialPort.Data8
self.string_data_bits = ""
self.parity = QSerialPort.NoParity
self.string_parity = ""
self.stop_bits = QSerialPort.OneStop
self.string_stop_bits = ""
self.flow_control = QSerialPort.SoftwareControl
self.string_flow_control = ""
self.local_echo_enabled = False
class SettingsDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.m_ui = Ui_SettingsDialog()
self._custom_port_index = -1
self.m_ui.setupUi(self)
self.m_currentSettings = Settings()
self.m_intValidator = QIntValidator(0, 4000000, self)
self.m_ui.baudRateBox.setInsertPolicy(QComboBox.NoInsert)
self.m_ui.applyButton.clicked.connect(self.apply)
self.m_ui.serialPortInfoListBox.currentIndexChanged.connect(self.show_port_info)
self.m_ui.baudRateBox.currentIndexChanged.connect(self.check_custom_baud_rate_policy)
self.m_ui.serialPortInfoListBox.currentIndexChanged.connect(
self.check_custom_device_path_policy)
self.fill_ports_parameters()
self.fill_ports_info()
self.update_settings()
def settings(self):
return self.m_currentSettings
@Slot(int)
def show_port_info(self, idx):
if idx == -1:
return
list = self.m_ui.serialPortInfoListBox.itemData(idx)
count = len(list) if list else 0
description = list[1] if count > 1 else BLANK_STRING
self.m_ui.descriptionLabel.setText(f"Description: {description}")
manufacturer = list[2] if count > 2 else BLANK_STRING
self.m_ui.manufacturerLabel.setText(f"Manufacturer: {manufacturer}")
serialno = list[3] if count > 3 else BLANK_STRING
self.m_ui.serialNumberLabel.setText(f"Serial number: {serialno}")
location = list[4] if count > 4 else BLANK_STRING
self.m_ui.locationLabel.setText(f"Location: {location}")
vendor = list[5] if count > 5 else BLANK_STRING
self.m_ui.vidLabel.setText(f"Vendor Identifier: {vendor}")
id = list[6] if count > 6 else BLANK_STRING
self.m_ui.pidLabel.setText(f"Product Identifier: {id}")
@Slot()
def apply(self):
self.update_settings()
self.hide()
@Slot(int)
def check_custom_baud_rate_policy(self, idx):
is_custom_baud_rate = idx == CUSTOM_BAUDRATE_INDEX
self.m_ui.baudRateBox.setEditable(is_custom_baud_rate)
if is_custom_baud_rate:
self.m_ui.baudRateBox.clearEditText()
edit = self.m_ui.baudRateBox.lineEdit()
edit.setValidator(self.m_intValidator)
@Slot(int)
def check_custom_device_path_policy(self, idx):
is_custom_path = idx == self._custom_port_index
self.m_ui.serialPortInfoListBox.setEditable(is_custom_path)
if is_custom_path:
self.m_ui.serialPortInfoListBox.clearEditText()
def fill_ports_parameters(self):
self.m_ui.baudRateBox.addItem("9600", QSerialPort.Baud9600)
self.m_ui.baudRateBox.addItem("19200", QSerialPort.Baud19200)
self.m_ui.baudRateBox.addItem("38400", QSerialPort.Baud38400)
self.m_ui.baudRateBox.addItem("115200", QSerialPort.Baud115200)
self.m_ui.baudRateBox.addItem("Custom")
self.m_ui.dataBitsBox.addItem("5", QSerialPort.Data5)
self.m_ui.dataBitsBox.addItem("6", QSerialPort.Data6)
self.m_ui.dataBitsBox.addItem("7", QSerialPort.Data7)
self.m_ui.dataBitsBox.addItem("8", QSerialPort.Data8)
self.m_ui.dataBitsBox.setCurrentIndex(3)
self.m_ui.parityBox.addItem("None", QSerialPort.NoParity)
self.m_ui.parityBox.addItem("Even", QSerialPort.EvenParity)
self.m_ui.parityBox.addItem("Odd", QSerialPort.OddParity)
self.m_ui.parityBox.addItem("Mark", QSerialPort.MarkParity)
self.m_ui.parityBox.addItem("Space", QSerialPort.SpaceParity)
self.m_ui.stopBitsBox.addItem("1", QSerialPort.OneStop)
if sys.platform == "win32":
self.m_ui.stopBitsBox.addItem("1.5", QSerialPort.OneAndHalfStop)
self.m_ui.stopBitsBox.addItem("2", QSerialPort.TwoStop)
self.m_ui.flowControlBox.addItem("None", QSerialPort.NoFlowControl)
self.m_ui.flowControlBox.addItem("RTS/CTS", QSerialPort.HardwareControl)
self.m_ui.flowControlBox.addItem("XON/XOFF", QSerialPort.SoftwareControl)
def fill_ports_info(self):
self.m_ui.serialPortInfoListBox.clear()
for info in QSerialPortInfo.availablePorts():
list = []
description = info.description()
manufacturer = info.manufacturer()
serial_number = info.serialNumber()
list.append(info.portName())
list.append(description if description else BLANK_STRING)
list.append(manufacturer if manufacturer else BLANK_STRING)
list.append(serial_number if serial_number else BLANK_STRING)
list.append(info.systemLocation())
vid = info.vendorIdentifier()
list.append(f"{vid:x}" if vid else BLANK_STRING)
pid = info.productIdentifier()
list.append(f"{pid:x}" if pid else BLANK_STRING)
self.m_ui.serialPortInfoListBox.addItem(list[0], list)
self._custom_port_index = self.m_ui.serialPortInfoListBox.count()
self.m_ui.serialPortInfoListBox.addItem("Custom")
def update_settings(self):
self.m_currentSettings.name = self.m_ui.serialPortInfoListBox.currentText()
baud_index = self.m_ui.baudRateBox.currentIndex()
if baud_index == CUSTOM_BAUDRATE_INDEX:
text = self.m_ui.baudRateBox.currentText()
self.m_currentSettings.baud_rate = int(text)
else:
self.m_currentSettings.baud_rate = self.m_ui.baudRateBox.currentData()
self.m_currentSettings.string_baud_rate = f"{self.m_currentSettings.baud_rate}"
self.m_currentSettings.data_bits = self.m_ui.dataBitsBox.currentData()
self.m_currentSettings.string_data_bits = self.m_ui.dataBitsBox.currentText()
self.m_currentSettings.parity = self.m_ui.parityBox.currentData()
self.m_currentSettings.string_parity = self.m_ui.parityBox.currentText()
self.m_currentSettings.stop_bits = self.m_ui.stopBitsBox.currentData()
self.m_currentSettings.string_stop_bits = self.m_ui.stopBitsBox.currentText()
self.m_currentSettings.flow_control = self.m_ui.flowControlBox.currentData()
self.m_currentSettings.string_flow_control = self.m_ui.flowControlBox.currentText()
self.m_currentSettings.local_echo_enabled = self.m_ui.localEchoCheckBox.isChecked()
<?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>392</width>
<height>386</height>
</rect>
</property>
<property name="windowTitle">
<string>Settings</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="1">
<widget class="QGroupBox" name="parametersBox">
<property name="title">
<string>Select Parameters</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="baudRateLabel">
<property name="text">
<string>BaudRate:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="baudRateBox"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="dataBitsLabel">
<property name="text">
<string>Data bits:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="dataBitsBox"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="parityLabel">
<property name="text">
<string>Parity:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="parityBox"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="stopBitsLabel">
<property name="text">
<string>Stop bits:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="stopBitsBox"/>
</item>
<item row="4" column="0">
<widget class="QLabel" name="flowControlLabel">
<property name="text">
<string>Flow control:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QComboBox" name="flowControlBox"/>
</item>
</layout>
</widget>
</item>
<item row="0" column="0">
<widget class="QGroupBox" name="selectBox">
<property name="title">
<string>Select Serial Port</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QComboBox" name="serialPortInfoListBox"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="descriptionLabel">
<property name="text">
<string>Description:</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="manufacturerLabel">
<property name="text">
<string>Manufacturer:</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="serialNumberLabel">
<property name="text">
<string>Serial number:</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="locationLabel">
<property name="text">
<string>Location:</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="vidLabel">
<property name="text">
<string>Vendor ID:</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="pidLabel">
<property name="text">
<string>Product ID:</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>96</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="applyButton">
<property name="text">
<string>Apply</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0" colspan="2">
<widget class="QGroupBox" name="additionalOptionsGroupBox">
<property name="title">
<string>Additional options</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QCheckBox" name="localEchoCheckBox">
<property name="text">
<string>Local echo</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
<RCC>
<qresource prefix="/">
<file>images/connect.png</file>
<file>images/disconnect.png</file>
<file>images/application-exit.png</file>
<file>images/settings.png</file>
<file>images/clear.png</file>
</qresource>
</RCC>