Site icon AranaCorp

Desenvolver um monitor de série com Python

Ao desenvolver um projeto com Arduino, Raspberry Pi ou qualquer outro microcontrolador, será certamente necessário criar uma interface gráfica, como um monitor série, para gerir o sistema (depurar, observar medições, lançar acções, etc.). Existem muitas ferramentas disponíveis para criar interfaces gráficas. Neste projeto, vamos criar uma interface com Python usando a estrutura Qt(PySide2).

Objetivo

Para este projeto, queremos criar uma interface gráfica baseada no Windows em Python que se comporte de forma semelhante ao monitor de série do IDE do Arduino. Para fazer isso, precisaremos executar as seguintes funções

Código Arduino

O código Arduino para a comunicação em série é bastante simples: definimos a comunicação em série e escutamos na porta série em cada ciclo. Se aparecer alguma informação, o algoritmo envia de volta o que recebeu.

/*---------------------------------------------------------------------- 
* 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=""; 
 } 
} 
 
 

Para este projeto, utilizámos o Arduino UNO, mas este código pode ser aplicado a qualquer microcontrolador que possa ser programado utilizando o Arduino IDE.

Recuperar dispositivos USB ligados

Para obter as portas USB utilizadas, existe a biblioteca serial.tools.list_ports

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

No Linux, pode utilizar o subprocesso com o comando lsusb.

Programa Python

Primeiro, vamos criar a janela da aplicação, a que chamaremos SerialInterface e que se comportará como o monitor de série.

#!/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_())
	

De seguida, criaremos uma classe que contém todos os Widgets necessários para criar o monitor série Python (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)

Descomente estas duas linhas para a adicionar à janela principal:

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

Quando os nossos widgets estiverem no sítio, podemos começar a trabalhar com eles. Antes de mais, vamos tratar da ligação. Para o fazer, vamos ligar o botão “Ligar” à função da seguinte forma:

button.clicked.connect(self.connect)

De seguida, vamos definir a função correspondente que irá verificar se a porta série ainda não está aberta para evitar erros. De seguida, criamos e abrimos a porta série, para ler os dados enviados pelo 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()))

Para recuperar e formatar os dados enviados pelo Arduino, vamos criar outra função, self.readData. Como podemos ver, ela armazenará os dados enviados pelo Arduino enquanto houver dados disponíveis no 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	

Quando a ligação e a leitura estiverem operacionais, criaremos o comando sendData para podermos enviar comandos para o Arduino através do monitor de série Python. A função recupera o texto do QLineEdit correspondente e envia-o para a porta série. Em seguida, usamos a função readData para obter a resposta do 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)

Código completo

#!/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_())

Fontes

Exit mobile version