Étiquettes : , ,

Lors de développement de projet avec Arduino, Raspberry Pi ou n’importe quel microcontrôleur vous serez certainement amené à créer une interface graphique comme, un moniteur série, pour la gestion du système (debugger, observer des mesure, lancer des actions, etc.). Ils existent beaucoup d’outils pour créer des interfaces graphiques. Dans ce projet, nous allons créer une interface avec Python en utilisant le framework Qt(PySide2).

Objectif

Pour ce projet, nous souhaitons créer une interface graphique en Python, sous Windows, se comportant de manière similaire au moniteur série de l’IDE Arduino. Pour cela, il va nous falloir réaliser les fonctions suivantes

  • Zone de sélection du port USB
  • Bouton connexion
  • Zone d’écriture pour la commande
  • Bouton envoyer
  • Console affichant les données envoyées par Arduino

Code Arduino

Le code Arduino pour la communication Série est assez simple, nous définissons la communication série et écoutons sur le port série à chaque boucle. Si une information apparait, l’algorithme renvoie ce qu’il a reçu.

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

Pour ce projet, nous avons utilisé l’Arduino UNO, mais ce code peut être appliqué à tous les microcontrôleurs pouvant être programmé à l’aide de l’IDE Arduino.

Récupérer les appareils USB connectés

Pour récupérer les ports USB utilisés, il existe la librairie 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

Sous linux, il est possible d’utiliser subprocess avec la commande lsusb.

Programme Python

Nous allons d’abord créer la fenêtre de l’application que nous appelons SerialInterface et qui se comportera comme le moniteur 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_())
	
pythonserialmonitor-window Développer un moniteur série avec Python

Nous allons ensuite créer une classe qui contiendra tous les Widgets dont nous avons besoin pour créer le moniteur 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)

Décommenter ces deux lignes pour le rajouter à la fenêtre principale:

#self.connectgrp=GroupClass(self)
#centralLayout.addWidget(self.connectgrp)
pythonserialmonitor-display Développer un moniteur série avec Python

Une fois nos widgets en place, nous allons pouvoir les exploiter. Nous allons, tout d’abord, nous occuper de la connexion. Pour cela, nous allons relier le bouton « Connect » à la fonction de la manière suivante :

button.clicked.connect(self.connect)

Puis nous allons définir la fonction correspondante qui va vérifier si le port série n’est pas déjà ouvert afin d’éviter des erreurs. Puis créer et ouvrir le port série, afin de lire les données envoyées par 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()))

Pour récupérer et mettre au bon format les données envoyé par Arduino, nous allons créer une autre fonction self.readData. Comme nous pouvons le voir, elle va stocker les données envoyées par Arduino tant qu’il y en a de disponible dans le 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	
pythonserialmonitor-connect Développer un moniteur série avec Python

Une fois la connexion et la lecture opérationnel, nous allons créer la commande sendData pour pouvoir envoyer des commande à l’Arduino via le moniteur série Python. La fonction récupère le texte du QLineEdit correspondant et l’envoie sur le port série. On utilise, ensuite, la fonction readData, pour récupérer la réponse de l’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)
pythonserialmonitor-send Développer un moniteur série avec Python

Code complet

#!/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