Icono del sitio AranaCorp

Desarrollar un monitor en serie con Python

Cuando se desarrolla un proyecto con Arduino, Raspberry Pi o cualquier microcontrolador, sin duda habrá que crear una interfaz gráfica como, un monitor en serie, para la gestión del sistema (depuración, observación de las mediciones, lanzamiento de acciones, etc.). Hay muchas herramientas para crear interfaces gráficas. En este proyecto, crearemos una interfaz con Python usando el marco QT(PySide2).

Objetivo

Para este proyecto, queremos crear una interfaz gráfica en Python, bajo Windows, que se comporte de forma similar al monitor de serie del IDE de Arduino. Para ello, tendremos que realizar las siguientes funciones

Código Arduino

El código de Arduino para la comunicación en serie es bastante simple, definimos la comunicación en serie y escuchamos en el puerto serie en cada bucle. Si aparece una información, el algoritmo devuelve lo que ha recibido.

/*---------------------------------------------------------------------- 
* 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 proyecto usamos el Arduino UNO, pero este código puede ser aplicado a todos los microcontroladores que pueden ser programados usando el Arduino IDE.

Recuperar los dispositivos USB conectados

Para recuperar los puertos USB usados, está la 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

Bajo linux, es posible usar un subproceso con el comando lsusb.

Código Python

Primero crearemos la ventana de la aplicación que llamamos Serial Interface que se comportará como el monitor en serie.

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

Luego crearemos una clase que contendrá todos los Widgets que necesitamos para crear el Monitor Serial 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)

Descomponga estas dos líneas para añadirlo a la ventana principal:

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

Una vez que nuestros widgets estén en su lugar, seremos capaces de explotarlos. Primero, nos encargaremos de la conexión. Para ello, enlazaremos el botón «Conectar» con la función de la siguiente manera:

button.clicked.connect(self.connect)

Luego definiremos la función correspondiente que comprobará si el puerto serie no está ya abierto para evitar errores. Entonces crea y abre el puerto serie, para leer los datos enviados por 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 y poner los datos enviados por Arduino en el formato correcto, crearemos otra función self.readData. Como podemos ver, almacenará los datos enviados por Arduino mientras haya datos disponibles en el 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	

Una vez que la conexión y la reproducción sea operacional, crearemos el comando sendData para poder enviar comandos al Arduino a través del monitor serial Python. La función recupera el texto del QLineEdit correspondiente y lo envía al puerto serie. Luego usamos la función readData para recuperar la respuesta del 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_())

Fuentes

Salir de la versión móvil