View  Edit  Attributes  History  Attach  Print  Search

PYQTLAB

PyQt Lab' : Port Série : en réception : Réception d'une valeur numérique sur le port série, dans un QwtThermo, le widget d'affichage analogique « vu-mètre », conversion mesure/unité et affichage dans des widgets LCD.

Par X. HINAULT - Juin 2013

Ce que l'on va faire ici

  • Dans ce code PyQt, une interface permettant la réception d'une valeur numérique sur le port série, dans un QwtThermo, le widget d'affichage analogique « vu-mètre », conversion mesure/unité et affichage dans des widgets LCD.

Pré-requis

  • python 2.7
  • pyqt4.x
  • modules :
    • python-serial
    • python-qwt5-qt4

Téléchargement :

  • L'archive des codes est disponible ici : ..

Le fichier d'interface *.py

  • Fichier obtenu automatiquement avec l'utilitaire pyuic4 à partir du fichier *.ui créé avec QtDesigner :
# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file
#
# Created: Sun Feb  3 11:24:41 2013
#      by: PyQt4 UI code generator 4.9.1
#
# WARNING! All changes made in this file will be lost!

from PyQt4 import QtCore, QtGui
from PyQt4.Qwt5 import * # module Qwt

try:
    _fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
    _fromUtf8 = lambda s: s

class Ui_Form(object):
    def setupUi(self, Form):
        Form.setObjectName(_fromUtf8("Form"))
        Form.resize(610, 275)
        self.labelDebit = QtGui.QLabel(Form)
        self.labelDebit.setGeometry(QtCore.QRect(10, 45, 111, 16))
        self.labelDebit.setObjectName(_fromUtf8("labelDebit"))
        self.comboBoxPort = QtGui.QComboBox(Form)
        self.comboBoxPort.setGeometry(QtCore.QRect(5, 20, 151, 24))
        self.comboBoxPort.setEditable(True)
        self.comboBoxPort.setObjectName(_fromUtf8("comboBoxPort"))
        self.comboBoxPort.addItem(_fromUtf8(""))
        self.comboBoxPort.addItem(_fromUtf8(""))
        self.comboBoxPort.addItem(_fromUtf8(""))
        self.comboBoxPort.addItem(_fromUtf8(""))
        self.labelReception = QtGui.QLabel(Form)
        self.labelReception.setGeometry(QtCore.QRect(5, 135, 191, 16))
        self.labelReception.setObjectName(_fromUtf8("labelReception"))
        self.textEditReception = QtGui.QTextEdit(Form)
        self.textEditReception.setGeometry(QtCore.QRect(5, 155, 156, 111))
        self.textEditReception.setLineWrapMode(QtGui.QTextEdit.NoWrap)
        self.textEditReception.setObjectName(_fromUtf8("textEditReception"))
        self.pushButtonInitSerial = QtGui.QPushButton(Form)
        self.pushButtonInitSerial.setGeometry(QtCore.QRect(5, 95, 101, 31))
        self.pushButtonInitSerial.setObjectName(_fromUtf8("pushButtonInitSerial"))
        self.comboBoxDebit = QtGui.QComboBox(Form)
        self.comboBoxDebit.setGeometry(QtCore.QRect(5, 60, 111, 24))
        self.comboBoxDebit.setObjectName(_fromUtf8("comboBoxDebit"))
        self.comboBoxDebit.addItem(_fromUtf8(""))
        self.comboBoxDebit.addItem(_fromUtf8(""))
        self.comboBoxDebit.addItem(_fromUtf8(""))
        self.comboBoxDebit.addItem(_fromUtf8(""))
        self.comboBoxDebit.addItem(_fromUtf8(""))
        self.comboBoxDebit.addItem(_fromUtf8(""))
        self.comboBoxDebit.addItem(_fromUtf8(""))
        self.comboBoxDebit.addItem(_fromUtf8(""))
        self.labelPort = QtGui.QLabel(Form)
        self.labelPort.setGeometry(QtCore.QRect(10, 5, 101, 16))
        self.labelPort.setObjectName(_fromUtf8("labelPort"))
        self.lcdNumberValeurCalc = QtGui.QLCDNumber(Form)
        self.lcdNumberValeurCalc.setGeometry(QtCore.QRect(450, 140, 96, 36))
        self.lcdNumberValeurCalc.setStyleSheet(_fromUtf8("background-color: rgb(255, 255, 0);\n"
"color: rgb(255, 0, 0);"))
        self.lcdNumberValeurCalc.setSmallDecimalPoint(True)
        self.lcdNumberValeurCalc.setMode(QtGui.QLCDNumber.Dec)
        self.lcdNumberValeurCalc.setObjectName(_fromUtf8("lcdNumberValeurCalc"))
        self.lineEditValeurMax = QtGui.QLineEdit(Form)
        self.lineEditValeurMax.setGeometry(QtCore.QRect(530, 90, 66, 23))
        self.lineEditValeurMax.setObjectName(_fromUtf8("lineEditValeurMax"))
        self.labelValeurBrute = QtGui.QLabel(Form)
        self.labelValeurBrute.setGeometry(QtCore.QRect(450, 20, 81, 16))
        self.labelValeurBrute.setObjectName(_fromUtf8("labelValeurBrute"))
        self.labelValeurMin = QtGui.QLabel(Form)
        self.labelValeurMin.setGeometry(QtCore.QRect(455, 75, 71, 16))
        self.labelValeurMin.setObjectName(_fromUtf8("labelValeurMin"))
        self.labelValeurMax = QtGui.QLabel(Form)
        self.labelValeurMax.setGeometry(QtCore.QRect(530, 75, 71, 16))
        self.labelValeurMax.setObjectName(_fromUtf8("labelValeurMax"))
        self.lineEditUnite = QtGui.QLineEdit(Form)
        self.lineEditUnite.setGeometry(QtCore.QRect(550, 155, 56, 23))
        self.lineEditUnite.setObjectName(_fromUtf8("lineEditUnite"))
        self.lineEditValeurMin = QtGui.QLineEdit(Form)
        self.lineEditValeurMin.setGeometry(QtCore.QRect(455, 90, 66, 23))
        self.lineEditValeurMin.setObjectName(_fromUtf8("lineEditValeurMin"))
        self.labelValeurCalc = QtGui.QLabel(Form)
        self.labelValeurCalc.setGeometry(QtCore.QRect(455, 120, 106, 16))
        self.labelValeurCalc.setObjectName(_fromUtf8("labelValeurCalc"))
        self.labelUnite = QtGui.QLabel(Form)
        self.labelUnite.setGeometry(QtCore.QRect(550, 140, 51, 13))
        self.labelUnite.setObjectName(_fromUtf8("labelUnite"))
        self.lcdNumberValeurBrute = QtGui.QLCDNumber(Form)
        self.lcdNumberValeurBrute.setGeometry(QtCore.QRect(450, 35, 96, 36))
        self.lcdNumberValeurBrute.setStyleSheet(_fromUtf8("background-color: rgb(170, 255, 127);\n"
"color: rgb(0, 170, 0);"))
        self.lcdNumberValeurBrute.setSmallDecimalPoint(False)
        self.lcdNumberValeurBrute.setObjectName(_fromUtf8("lcdNumberValeurBrute"))
        self.line = QtGui.QFrame(Form)
        self.line.setGeometry(QtCore.QRect(160, 5, 16, 266))
        self.line.setFrameShape(QtGui.QFrame.VLine)
        self.line.setFrameShadow(QtGui.QFrame.Sunken)
        self.line.setObjectName(_fromUtf8("line"))
        self.line_2 = QtGui.QFrame(Form)
        self.line_2.setGeometry(QtCore.QRect(435, 5, 16, 266))
        self.line_2.setFrameShape(QtGui.QFrame.VLine)
        self.line_2.setFrameShadow(QtGui.QFrame.Sunken)
        self.line_2.setObjectName(_fromUtf8("line_2"))
        self.pushButtonStop = QtGui.QPushButton(Form)
        self.pushButtonStop.setGeometry(QtCore.QRect(115, 85, 41, 41))
        self.pushButtonStop.setObjectName(_fromUtf8("pushButtonStop"))
        self.qwtThermo = QwtThermo(Form)
        self.qwtThermo.setGeometry(QtCore.QRect(250, 15, 103, 244))
        self.qwtThermo.setAutoFillBackground(True)
        self.qwtThermo.setAlarmEnabled(True)
        self.qwtThermo.setAlarmLevel(512.0)
        self.qwtThermo.setMaxValue(1023.0)
        self.qwtThermo.setPipeWidth(50)
        self.qwtThermo.setProperty("value", 0.0)
        self.qwtThermo.setObjectName(_fromUtf8("qwtThermo"))

        self.retranslateUi(Form)
        QtCore.QMetaObject.connectSlotsByName(Form)

    def retranslateUi(self, Form):
        Form.setWindowTitle(QtGui.QApplication.translate("Form", "PyQt + PyQwt +PySerial : QwtThermo affiche can brute (0-1023) + conversion", None, QtGui.QApplication.UnicodeUTF8))
        self.labelDebit.setText(QtGui.QApplication.translate("Form", "Débit Série (bauds) :", None, QtGui.QApplication.UnicodeUTF8))
        self.comboBoxPort.setItemText(0, QtGui.QApplication.translate("Form", "/dev/ttyACM0", None, QtGui.QApplication.UnicodeUTF8))
        self.comboBoxPort.setItemText(1, QtGui.QApplication.translate("Form", "/dev/ttyACM1", None, QtGui.QApplication.UnicodeUTF8))
        self.comboBoxPort.setItemText(2, QtGui.QApplication.translate("Form", "/dev/ttyUSB0", None, QtGui.QApplication.UnicodeUTF8))
        self.comboBoxPort.setItemText(3, QtGui.QApplication.translate("Form", "/dev/ttyUSB1", None, QtGui.QApplication.UnicodeUTF8))
        self.labelReception.setText(QtGui.QApplication.translate("Form", "Réception sur le port série : ", None, QtGui.QApplication.UnicodeUTF8))
        self.pushButtonInitSerial.setText(QtGui.QApplication.translate("Form", "Initialiser", None, QtGui.QApplication.UnicodeUTF8))
        self.comboBoxDebit.setItemText(0, QtGui.QApplication.translate("Form", "115200", None, QtGui.QApplication.UnicodeUTF8))
        self.comboBoxDebit.setItemText(1, QtGui.QApplication.translate("Form", "57600", None, QtGui.QApplication.UnicodeUTF8))
        self.comboBoxDebit.setItemText(2, QtGui.QApplication.translate("Form", "38400", None, QtGui.QApplication.UnicodeUTF8))
        self.comboBoxDebit.setItemText(3, QtGui.QApplication.translate("Form", "28800", None, QtGui.QApplication.UnicodeUTF8))
        self.comboBoxDebit.setItemText(4, QtGui.QApplication.translate("Form", "19200", None, QtGui.QApplication.UnicodeUTF8))
        self.comboBoxDebit.setItemText(5, QtGui.QApplication.translate("Form", "14400", None, QtGui.QApplication.UnicodeUTF8))
        self.comboBoxDebit.setItemText(6, QtGui.QApplication.translate("Form", "9600", None, QtGui.QApplication.UnicodeUTF8))
        self.comboBoxDebit.setItemText(7, QtGui.QApplication.translate("Form", "4800", None, QtGui.QApplication.UnicodeUTF8))
        self.labelPort.setText(QtGui.QApplication.translate("Form", "Port Série :", None, QtGui.QApplication.UnicodeUTF8))
        self.lineEditValeurMax.setText(QtGui.QApplication.translate("Form", "5000", None, QtGui.QApplication.UnicodeUTF8))
        self.labelValeurBrute.setText(QtGui.QApplication.translate("Form", "Valeur brute :", None, QtGui.QApplication.UnicodeUTF8))
        self.labelValeurMin.setText(QtGui.QApplication.translate("Form", "Valeur mini", None, QtGui.QApplication.UnicodeUTF8))
        self.labelValeurMax.setText(QtGui.QApplication.translate("Form", "Valeur maxi", None, QtGui.QApplication.UnicodeUTF8))
        self.lineEditUnite.setText(QtGui.QApplication.translate("Form", "mV", None, QtGui.QApplication.UnicodeUTF8))
        self.lineEditValeurMin.setText(QtGui.QApplication.translate("Form", "0", None, QtGui.QApplication.UnicodeUTF8))
        self.labelValeurCalc.setText(QtGui.QApplication.translate("Form", "Valeur calculée :", None, QtGui.QApplication.UnicodeUTF8))
        self.labelUnite.setText(QtGui.QApplication.translate("Form", "Unité :", None, QtGui.QApplication.UnicodeUTF8))
        self.pushButtonStop.setText(QtGui.QApplication.translate("Form", "Stop", None, QtGui.QApplication.UnicodeUTF8))

from PyQt4.Qwt5 import * # module Qwt

if __name__ == "__main__":
    import sys
    app = QtGui.QApplication(sys.argv)
    Form = QtGui.QWidget()
    ui = Ui_Form()
    ui.setupUi(Form)
    Form.show()
    sys.exit(app.exec_())
 

Le fichier d'application *Main.py

#!/usr/bin/python
# -*- coding: utf-8 -*-

# Par X. HINAULT - Tous droits réservés - GPLv3
# Jan 2013 - www.mon-club-elec.fr

# --- importation des modules utiles ---
from PyQt4.QtGui import *
from PyQt4.QtCore import * # inclut Qtimer..

import os,sys

import serial # communication serie
from PyQt4.Qwt5 import * # module Qwt
#from PyQt4.Qwt5.anynumpy import * # pour les fonctions math..
from numpy import interp # pour équivalent map

# --- importation du fichier de description GUI ---
from tuto_pyqt_pyqwt_pyserial_qwtthermo_can_brutex1_lineedit import *

# classe principale contenant le code actif
class myApp(QWidget, Ui_Form): # la classe reçoit le Qwidget principal ET la classe définie dans test.py obtenu avec pyuic4

        # Note : ici self représente la classe

        def __init__(self, parent=None):
                QWidget.__init__(self) # initialise le Qwidget principal
                self.setupUi(parent) # Obligatoire

                # ------ code actif initial ------

                #Réalisez les connexions supplémentaires entre signaux et slots
                self.connect(self.pushButtonInitSerial, SIGNAL("clicked()"), self.pushButtonInitSerialClicked)
                self.connect(self.pushButtonStop, SIGNAL("clicked()"), self.pushButtonStopClicked)

                #===== Initialisation QwtThermo - à mettre avant QTimer ++  ========           
                self.qwtThermo.setFillColor(QColor(0,255,0)) # fixe la couleur de remplissage sous niveau alarme
                self.qwtThermo.setAlarmColor(QColor(255,0,0)) # fixe couleur remplissage au dessus niveau alarme

                #initialisation Timer
                self.timer=QTimer() # déclare un timer Qt
                self.connect(self.timer, SIGNAL("timeout()"), self.timerEvent) # connecte le signal timeOut de l'objet timer à l'appel de la fonction voulue

                #--- déclaration utiles ---
                self.serialPort=None # déclaration initiale
                self.chaineIn="" # variable pour réception



        #--- les fonctions appelées, utilisées par les signaux
        def pushButtonInitSerialClicked(self):
                print("Bouton Init cliqué")
                if self.serialPort: # si le port existe déjà
                        self.serialPort.close() # ferme le port si existe

                # -- initialise paramètres initialisation
                if self.comboBoxPort.currentText()=="" : # si le champ d'initialisation Port est vide = initialisation par défaut
                        strPortInit="/dev/ttyACM0" # port par défaut
                else :
                        strPortInit=str(self.comboBoxPort.currentText()) #sinon utilise paramètre champ texte pour le port

                strDebitInit=str(self.comboBoxDebit.currentText()) # paramètre champ texte pour debit

                #--- initialisation série avec gestion erreur ---                      
                try: # essaie d'exécuter les instructions
                        # initialise port serie avec délai attente en réception en sec
                        self.serialPort=serial.Serial(strPortInit, strDebitInit, serial.EIGHTBITS, serial.PARITY_NONE, serial.STOPBITS_ONE, 0.100)                     
                        #self.serialPort=serial.Serial(strPortInit, strDebitInit) # initialise port serie forme réduite
                        self.serialPort.flushInput() # vide la file d'attente série
                        print("Initialisation Port Série : " + strPortInit +" @ " + strDebitInit +" = OK ") # affiche debug

                        #-- change aspect bouton init
                        self.pushButtonInitSerial.setStyleSheet(QString.fromUtf8("background-color: rgb(0, 255, 0);")) # bouton en vert
                        self.pushButtonInitSerial.setText("Connexion OK")  # change titre bouton

                except: # si erreur initialisation
                        print("Erreur initialisation Série")           

                        #-- change aspect bouton init
                        self.pushButtonInitSerial.setStyleSheet(QString.fromUtf8("background-color: rgb(255, 127, 0);")) # bouton en orange
                        self.pushButtonInitSerial.setText(QString.fromUtf8("Non connecté"))  # change titre bouton

                self.timer.start(10) # lance le timer avec délai en ms - 10 pour réception rapide
                # pour optimiser la réception d'une chaîne envoyée à répétition, utiliser une valeur similaire à la valeur d'émission sur le port série

        def pushButtonStopClicked(self):
                print("Bouton Stop cliqué")

                #-- stoppe la réception série --
                if self.serialPort: # si le port existe déjà
                        self.serialPort.close() # ferme le port si existe
                        self.timer.stop() # stoppe le timer

                #-- change aspect bouton init
                self.pushButtonInitSerial.setStyleSheet(QString.fromUtf8("background-color: rgb(255, 127, 0);")) # bouton en orange
                self.pushButtonInitSerial.setText(QString.fromUtf8("Non connecté"))  # change titre bouton

        def timerEvent(self): # fonction appelée lors de la survenue d'un évènement Timer - nom fonction indiférrent
                #-- variables de réception --
                #self.chaineIn="" - pas systématique - seulement si un saut de ligne a été reçu
                self.char="";
                self.flagSautLigne=False # drapeau saut de ligne reçu

                # lecture des données reçues           
                if self.serialPort: # seulement si le port série existe
                        self.timer.stop() # stoppe le timer le temps de lire les caractères et éviter "réentrée"

                        while (self.serialPort.inWaiting()): # tant que au moins un caractère en réception
                                self.char=self.serialPort.read() # on lit le caractère
                                #self.chaineIn=self.chaineIn+self.char          # forme minimale...

                                if self.char=='\n': # si saut de ligne, on sort du while
                                        print("saut ligne reçu") # debug
                                        self.flagSautLigne=True # drapeau saut de ligne reçu
                                        break # sort du while
                                else: #tant que c'est pas le saut de ligne, on l'ajoute à la chaine
                                        self.chaineIn=self.chaineIn+self.char  

                        # fin while                            

                        # en sortie du while, on se place ici
                        print self.chaineIn
                        print self.flagSautLigne
                        # teste si chaine valide = non vide et saut de ligne = True...
                        if len(self.chaineIn)>1 and self.flagSautLigne==True: # ... pour ne pas avoir d'affichage si ""
                                print(self.chaineIn) # affiche la chaîne
                                self.textEditReception.append(self.chaineIn[:-1]) # ajoute le texte au textEdit en enlevant le dernier caractère (CR)

                                self.chaineIn=self.chaineIn[:-1] # retire dernier caractère avant extraire valeur numérique (LF)

                                if self.chaineIn.isalnum(): # si la chaine est un chiffre
                                        print("que des chiffres") # debug
                                        self.valeur=float(self.chaineIn)

                                        self.qwtThermo.setValue(self.valeur) # fixe la valeur du qwtDial à partir de la valeur reçue sur port série

                                        # actualise lcdNumber
                                        self.lcdNumberValeurBrute.display(self.valeur) # affiche la valeur

                                        # équiv map : interp(256,[1,512],[5,10]) (package numpy)
                                        self.valeurCalc=interp(self.valeur, [0,1023],[float(self.lineEditValeurMin.text()),float(self.lineEditValeurMax.text())]) # équiv map
                                        self.lcdNumberValeurCalc.display(self.valeurCalc)

                                # fin if isalnum

                                self.chaineIn=""; # dans le if : une fois chaine valide (=non vide et avec saut de ligne) traitée

                        # fin if len>0 # fin traitement chaine valide...

                        self.timer.start() # redémarre le timer

                # fin if Serial Port

        # fin def timerEvent

        #--- fonctions actives
        # ...


# fonction principale exécutant l'application Qt                       
def main(args):
        a=QApplication(args) # crée l'objet application
        f=QWidget() # crée le QWidget racine
        c=myApp(f) # appelle la classe contenant le code de l'application
        f.show() # affiche la fenêtre QWidget
        r=a.exec_() # lance l'exécution de l'application
        return r

# pour rendre le fichier *.py exécutable
if __name__=="__main__": # pour rendre le code exécutable
        main(sys.argv) # appelle la fonction main
 

Code Arduino d'exemple


// --- constantes des broches ---

const int RVar=0; //declaration constante de broche analogique

// --- Déclaration des variables globales ---
int mesureBrute=0;// Variable pour acquisition résultat brut de conversion analogique numérique

//**************** FONCTION SETUP = Code d'initialisation *****

void setup()   { // debut de la fonction setup()

Serial.begin(115200); // initialise connexion série à 115200 bauds
// IMPORTANT : régler le terminal côté PC avec la même valeur de transmission


} // fin de la fonction setup()

//***** FONCTION LOOP = Boucle sans fin *****


void loop(){ // debut de la fonction loop()

// acquisition conversion analogique-numerique (CAN) sur la voie analogique
mesureBrute=analogRead(RVar);

// affiche valeur numerique entière ou à virgule au format décimal
Serial.println(mesureBrute);

delay(500);

} // fin de la fonction loop() - le programme recommence au début de la fonction loop sans fin
 

Utilisation

  • Les 2 fichiers suivants sont à enregistrer dans un même répertoire, l'un en nom.py et l'autre en nomMain.py.
  • Puis lancer l'application depuis Geany ou équivalent, en exécutant le fichier nomMain.py
  • La valeur numérique reçue sur le port série s'affiche sous forme d'un widget analogique de type bargraph avec champ de conversion de la valeur.