Tags: , ,

When developing a project with Arduino, Raspberry Pi or any microcontroller you will certainly have to create a graphical interface like, a serial monitor, for the management of the system (debugging, observing measurements, launching actions, etc.). There are a lot of tools to create graphical interfaces. In this project, we will create an interface with Python using the QT(PySide2) framework.

Objectif

For this project, we want to create a graphical interface in Python, under Windows, behaving similarly to the serial monitor of the Arduino IDE. For this, we will need to perform the following functions

  • USB port selection area
  • Connection button
  • Writing area for the order
  • Send button
  • Console displaying the data sent by Arduino

Code Arduino

The Arduino code for serial communication is quite simple, we define the serial communication and listen on the serial port at each loop. If a piece of information appears, the algorithm returns what it has received.

/*---------------------------------------------------------------------- 
* Project : Test 
* Author : AranaCorp 
* Version : V01 
* Date : 7/4/2020 
* 
* Summary : 
* Hardware : 
   - Arduino UNO x1 
   - usbserial x1 
   - usbserialIn x1     https://www.aranacorp.com/fr/communiquez-avec-votre-arduino/ 
* 
* www.aranacorp.com 
----------------------------------------------------------------------*/ 
//Variables 
String msg ; 
 
void setup(){ 
//Init Serial USB 
Serial.begin(115200); 
delay(500);
Serial.println(F(">> Initialize System")); 
} 
 
void loop(){ 
readSerialPort(); 
} 
 
void readSerialPort(){/* function readSerialPort */ 
////Write something into the serial monitor to test serial communication 
while (Serial.available()) { 
   delay(10); 
   if (Serial.available() >0) { 
     char c = Serial.read();  //gets one byte from serial buffer 
     msg += c; //makes the string readString 
   } 
 } 
 if (msg.length() >0){ 
    Serial.println(">> "+msg); 
    msg=""; 
 } 
} 
 
 

For this project we used the Arduino UNO, but this code can be applied to all microcontrollers that can be programmed using the Arduino IDE.

Recovering connected USB devices

To retrieve the used USB ports, there is the serial.tools.list_ports library.

def find_USB_device():
	myports = [tuple(p) for p in list(serial.tools.list_ports.comports())]
	print(myports)
	usb_port_list = [p[0] for p in myports]
	
	return usb_port_list

Under linux, it is possible to use subprocess with the lsusb control.

Python program

First we will create the application window we call Serial Interface, which will behave like the serial monitor.

#!/usr/bin/python3
# -*-coding:Utf-8 -*

import sys,os, time
import platform
from random import randint
import serial,serial.tools.list_ports

#interface import
from PySide2.QtWidgets import QApplication, QMainWindow,QDesktopWidget, QTextEdit, QLineEdit, QPushButton, QMessageBox, QWidget, QGridLayout, QTextEdit, QGroupBox, QVBoxLayout,QHBoxLayout, QComboBox, QLabel
from PySide2.QtGui import QIcon, QScreen

class SerialInterface(QMainWindow):
	def __init__(self, parent=None):
		super().__init__(parent)
		self.width=650
		self.height=350
		
		self.resize(self.width, self.height)
		self.setWindowIcon(QIcon('./resources/logo-100.png'))
		self.setWindowTitle( 'Serial Monitor')
		
		#center window on screen
		qr = self.frameGeometry()
		cp = QScreen().availableGeometry().center()
		qr.moveCenter(cp)
		
		
		#init layout
		centralwidget = QWidget(self)
		centralLayout=QHBoxLayout(centralwidget)
		self.setCentralWidget(centralwidget)
		
		#add connect group
		#self.connectgrp=GroupClass(self)
		#centralLayout.addWidget(self.connectgrp)

if __name__ == "__main__":
	app = QApplication(sys.argv)
	frame = SerialInterface()
	frame.show()
	sys.exit(app.exec_())
	

We will then create a class that will contain all the Widgets we need to create the Python Serial Monitor (QLineEdit,QTextEdit,QButton).

class GroupClass(QGroupBox):
	def __init__(self,widget,title="Connection Configuration"):
		super().__init__(widget)
		self.widget=widget
		self.title=title
		self.sep="-"
		self.id=-1
		self.name=''
		self.items=find_USB_device()
		self.serial=None
		self.init()
		
	def init(self):
		self.setTitle(self.title)
		
		self.selectlbl = QLabel("Select port:")
		#label
		self.typeBox=QComboBox()
		self.typeBox.addItems(self.items)#database getMotionType()
		self.typeBox.setCurrentIndex(self.typeBox.count()-1)
		
		#btn
		button = QPushButton("Connect")
		button.clicked.connect(self.connect)
		sendBtn = QPushButton("send")
		sendBtn.clicked.connect(self.sendData)
		
		titlelbl=  QLabel("Enter")
		self.title = QLineEdit("")
		desclbl=QLabel("Console")
		self.desc = QTextEdit("")
			
		self.fields=QGridLayout()
		self.fields.addWidget(self.selectlbl,0,0,1,1)
		self.fields.addWidget(self.typeBox,0,1,1,1)
		self.fields.addWidget(button,0,2,1,1)
		
		self.fields.addWidget(titlelbl,1,0,1,1)
		self.fields.addWidget(self.title,1,1,1,1)
		self.fields.addWidget(sendBtn,1,2,1,1)
		self.fields.addWidget(desclbl,2,0,1,1)
		self.fields.addWidget(self.desc,3,1,1,1)
		self.setLayout(self.fields)

Uncomment these two lines to add it to the main window:

#self.connectgrp=GroupClass(self)
#centralLayout.addWidget(self.connectgrp)

Once our widgets are in place, we will be able to exploit them. First of all, we’re going to take care of the connection. To do this, we will link the “Connect” button to the function in the following way:

button.clicked.connect(self.connect)

Then we will define the corresponding function that will check if the serial port is not already open to avoid errors. Then create and open the serial port, in order to read the data sent by Arduino.

	def connect(self):
		self.desc.setText("")
		self.desc.setText(">> trying to connect to port %s ..." % self.typeBox.currentText())
		if self.serial is None:
			self.serial=serial.Serial(self.typeBox.currentText(), 115200, timeout=1)
			time.sleep(0.05)
			#self.serial.write(b'hello')
			answer=self.readData()
			if answer!="":
				self.desc.setText(self.desc.toPlainText()+"\n>> Connected!\n"+answer)
		else:
			self.desc.setText(">> {} already Opened!\n".format(self.typeBox.currentText()))

To recover and put the data sent by Arduino in the right format, we will create another self.readData function. As we can see, it will store the data sent by Arduino as long as there is data available in the buffer.

	def readData(self):
		self.serial.flush() # it is buffering. required to get the data out *now*
		answer=""
		while  self.serial.inWaiting()>0: #self.serial.readable() and
			answer += "\n"+str(self.serial.readline()).replace("\r","").replace("\n","").replace("'","").replace("b","")
		return answer	

Once the connection and playback is operational, we will create the sendData command to be able to send commands to the Arduino via the Python serial monitor. The function retrieves the text from the corresponding QLineEdit and sends it to the serial port. We then use the readData function to recover the response from the Arduino.

	def sendData(self):
		if self.serial.isOpen():
			if self.title.text() != "":
				self.serial.write(self.title.text().encode())
				answer=self.readData()
				self.desc.setText(self.desc.toPlainText()+"\n"+answer)

Complete code

#!/usr/bin/python3
# -*-coding:Utf-8 -*

import sys,os, time
import platform
from random import randint
import serial,serial.tools.list_ports

#interface import
import PySide2


from PySide2.QtWidgets import QApplication, QMainWindow,QDesktopWidget, QTextEdit, QLineEdit, QPushButton, QMessageBox, QWidget, QGridLayout, QTextEdit, QGroupBox, QVBoxLayout,QHBoxLayout, QComboBox, QLabel

from PySide2.QtGui import QIcon, QScreen


__prgm__ = 'Serial Monitor'
__version__ = '0.0.2'

def find_USB_device(USB_DEV_NAME=None):
    myports = [tuple(p) for p in list(serial.tools.list_ports.comports())]
	print(myports)
	usb_port_list = [p[0] for p in myports]
		usb_device_list = [p[1] for p in myports]
		print(usb_device_list)

		if USB_DEV_NAME is None:
			return myports
		else:
			USB_DEV_NAME=str(USB_DEV_NAME).replace("'","").replace("b","")
			for device in usb_device_list:
				print("{} -> {}".format(USB_DEV_NAME,device))
				print(USB_DEV_NAME in device)
				if USB_DEV_NAME in device:
					print(device)
					usb_id = device[device.index("COM"):device.index("COM")+4]
			
					print("{} port is {}".format(USB_DEV_NAME,usb_id))
					return usb_id
				
class GroupClass(QGroupBox):
	def __init__(self,widget,title="Connection Configuration"):
		super().__init__(widget)
		self.widget=widget
		self.title=title
		self.sep="-"
		self.id=-1
		self.name=''
		self.portlist=find_USB_device()
		self.items=[p[0] for p in self.portlist]#["COM1","COM2"]
		self.serial=None
		#self.motionDict={"POSITION BASED":" Describe motion based on position","VELOCITY BASED":" Describe motion based on velocity", "LOOP":" Describe loop motion", "PINGPONG":" Describe pingpong motion", "INTERACTIF":" Describe interactive motion"}
		self.init()
		
	def init(self):
		self.setTitle(self.title)
		
		self.selectlbl = QLabel("Select port:")
		#label
		self.typeBox=QComboBox()
		self.typeBox.addItems(self.items)#database getMotionType()
		self.typeBox.setCurrentIndex(self.typeBox.count()-1)
		
		#btn
		button = QPushButton("Connect")
		button.clicked.connect(self.connect)
		#hbox.addWidget(button)
		sendBtn = QPushButton("send")
		sendBtn.clicked.connect(self.sendData)
		#hbox.addWidget(button)
		
		titlelbl=  QLabel("Enter")
		self.title = QLineEdit("")
		desclbl=QLabel("Console")
		self.desc = QTextEdit("")
		
		#self.add=QPushButton("Ajouter/Modifier")
		#self.add.clicked.connect(self.addItem)
		#self.rem=QPushButton("Supprimer")
		#self.rem.clicked.connect(self.remItem)
			
		self.fields=QGridLayout()
		self.fields.addWidget(self.selectlbl,0,0,1,1)
		self.fields.addWidget(self.typeBox,0,1,1,1)
		self.fields.addWidget(button,0,2,1,1)
		
		self.fields.addWidget(titlelbl,1,0,1,1)
		self.fields.addWidget(self.title,1,1,1,1)
		self.fields.addWidget(sendBtn,1,2,1,1)
		self.fields.addWidget(desclbl,2,0,1,1)
		self.fields.addWidget(self.desc,3,1,1,1)
		#self.fields.addWidget(self.add,2,2,1,1)
		#self.fields.addWidget(self.rem,3,2,1,1)
		self.setLayout(self.fields)
	
	def connect(self):
		
		self.desc.setText("")
		self.desc.setText(">> trying to connect to port %s ..." % self.typeBox.currentText())
		#with serial.Serial(self.typeBox.currentText(), 115200, timeout=1) as self.serial:
		if self.serial is None:
			self.serial=serial.Serial(self.typeBox.currentText(), 115200, timeout=1)
			time.sleep(0.05)
			#self.serial.write(b'hello')
			answer=self.readData()
			if answer!="":
				self.desc.setText(self.desc.toPlainText()+"\n>> Connected!\n"+answer)
		else:
			self.desc.setText(">> {} already Opened!\n".format(self.typeBox.currentText()))
			
	def sendData(self):
		if self.serial.isOpen():
			if self.title.text() != "":
				self.serial.write(self.title.text().encode())
				answer=self.readData()
				if(self.title.text().encode()=="scan"):
					print("scanning results -> "+answer.find("0x"))
				else:
					print(answer.find("0x"))
				self.desc.setText(self.desc.toPlainText()+"\n"+answer)
					
	def readData(self):
		#self.serial.flush() # it is buffering. required to get the data out *now*
		answer=""
		while  self.serial.inWaiting()>0: #self.serial.readable() and
			
			print(self.serial.inWaiting())
			answer += "\n"+str(self.serial.readline()).replace("\r","").replace("\n","").replace("'","").replace("b","")
			#print(self.serial.inWaiting())
		#self.desc.setText(self.desc.toPlainText()+"\n"+answer)
		return answer	
	
			
class SerialInterface(QMainWindow):
	def __init__(self, parent=None):
		super().__init__(parent)
		self.width=650
		self.height=350
		
		self.resize(self.width, self.height)
		self.setWindowIcon(QIcon('./resources/logo-100.png'))
		self.setWindowTitle(__prgm__)
		
		#center window on screen
		qr = self.frameGeometry()
		cp = QScreen().availableGeometry().center()
		qr.moveCenter(cp)
		
		
		#init layout
		centralwidget = QWidget(self)
		centralLayout=QHBoxLayout(centralwidget)
		self.setCentralWidget(centralwidget)
		
		#add connect group
		self.connectgrp=GroupClass(self)
		centralLayout.addWidget(self.connectgrp)
		
			

if __name__ == "__main__":
	app = QApplication(sys.argv)
	frame = SerialInterface()
	frame.show()
	sys.exit(app.exec_())
	

Sources