View  Edit  Attributes  History  Attach  Print  Search

PYQTLAB

PyQt Lab' : Graphiques Math : Pyqtgraph + Pyserial : Afficher une courbe à partir de valeurs reçues sur le port série, avec widgets de paramétrage des axes

Par X. HINAULT - Juin 2013

Ce que l'on va faire ici

  • Cette interface PyQt permet d'afficher une courbe à partir de valeurs reçues sur le port série, avec widgets de paramétrage des axes.

Pré-requis

  • python 2.7
  • pyqt4.x
  • pyqtgraph
  • python-pyserial

Téléchargement :

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: Fri Jun  7 15:48:24 2013
#      by: PyQt4 UI code generator 4.9.1
#
# WARNING! All changes made in this file will be lost!

from PyQt4 import QtCore, QtGui

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(691, 410)
        self.graph = PlotWidget(Form)
        self.graph.setGeometry(QtCore.QRect(200, 5, 480, 360))
        self.graph.setObjectName(_fromUtf8("graph"))
        self.labelReception = QtGui.QLabel(Form)
        self.labelReception.setGeometry(QtCore.QRect(10, 290, 171, 16))
        self.labelReception.setObjectName(_fromUtf8("labelReception"))
        self.comboBoxFinLigne = QtGui.QComboBox(Form)
        self.comboBoxFinLigne.setGeometry(QtCore.QRect(10, 175, 121, 24))
        self.comboBoxFinLigne.setObjectName(_fromUtf8("comboBoxFinLigne"))
        self.comboBoxFinLigne.addItem(_fromUtf8(""))
        self.comboBoxFinLigne.addItem(_fromUtf8(""))
        self.comboBoxFinLigne.addItem(_fromUtf8(""))
        self.comboBoxFinLigne.addItem(_fromUtf8(""))
        self.labelPort = QtGui.QLabel(Form)
        self.labelPort.setGeometry(QtCore.QRect(10, 10, 101, 16))
        self.labelPort.setObjectName(_fromUtf8("labelPort"))
        self.lineEditChaineEnvoi = QtGui.QLineEdit(Form)
        self.lineEditChaineEnvoi.setGeometry(QtCore.QRect(10, 135, 166, 23))
        self.lineEditChaineEnvoi.setText(_fromUtf8(""))
        self.lineEditChaineEnvoi.setObjectName(_fromUtf8("lineEditChaineEnvoi"))
        self.textEditTraceEnvoiSerie = QtGui.QTextEdit(Form)
        self.textEditTraceEnvoiSerie.setGeometry(QtCore.QRect(10, 220, 171, 71))
        self.textEditTraceEnvoiSerie.setStyleSheet(_fromUtf8("color: rgb(0, 0, 255);\n"
"background-color: rgb(170, 255, 255);"))
        self.textEditTraceEnvoiSerie.setLineWrapMode(QtGui.QTextEdit.NoWrap)
        self.textEditTraceEnvoiSerie.setObjectName(_fromUtf8("textEditTraceEnvoiSerie"))
        self.line_3 = QtGui.QFrame(Form)
        self.line_3.setGeometry(QtCore.QRect(180, 10, 16, 371))
        self.line_3.setFrameShape(QtGui.QFrame.VLine)
        self.line_3.setFrameShadow(QtGui.QFrame.Sunken)
        self.line_3.setObjectName(_fromUtf8("line_3"))
        self.labelDelaiRecept = QtGui.QLabel(Form)
        self.labelDelaiRecept.setGeometry(QtCore.QRect(10, 95, 106, 16))
        self.labelDelaiRecept.setObjectName(_fromUtf8("labelDelaiRecept"))
        self.labelChaineEnvoi = QtGui.QLabel(Form)
        self.labelChaineEnvoi.setGeometry(QtCore.QRect(10, 120, 121, 16))
        self.labelChaineEnvoi.setObjectName(_fromUtf8("labelChaineEnvoi"))
        self.pushButtonEnvoi = QtGui.QPushButton(Form)
        self.pushButtonEnvoi.setGeometry(QtCore.QRect(135, 160, 41, 41))
        self.pushButtonEnvoi.setObjectName(_fromUtf8("pushButtonEnvoi"))
        self.spinBoxDelaiReception = QtGui.QSpinBox(Form)
        self.spinBoxDelaiReception.setGeometry(QtCore.QRect(115, 90, 71, 23))
        self.spinBoxDelaiReception.setMaximum(10000)
        self.spinBoxDelaiReception.setProperty("value", 20)
        self.spinBoxDelaiReception.setObjectName(_fromUtf8("spinBoxDelaiReception"))
        self.line = QtGui.QFrame(Form)
        self.line.setGeometry(QtCore.QRect(10, 110, 166, 16))
        self.line.setFrameShape(QtGui.QFrame.HLine)
        self.line.setFrameShadow(QtGui.QFrame.Sunken)
        self.line.setObjectName(_fromUtf8("line"))
        self.labelTraceEnvoiSerie = QtGui.QLabel(Form)
        self.labelTraceEnvoiSerie.setGeometry(QtCore.QRect(10, 205, 191, 16))
        self.labelTraceEnvoiSerie.setObjectName(_fromUtf8("labelTraceEnvoiSerie"))
        self.pushButtonStop = QtGui.QPushButton(Form)
        self.pushButtonStop.setGeometry(QtCore.QRect(135, 55, 41, 31))
        self.pushButtonStop.setObjectName(_fromUtf8("pushButtonStop"))
        self.comboBoxDebit = QtGui.QComboBox(Form)
        self.comboBoxDebit.setGeometry(QtCore.QRect(10, 65, 121, 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.labelFinLigne = QtGui.QLabel(Form)
        self.labelFinLigne.setGeometry(QtCore.QRect(10, 160, 71, 16))
        self.labelFinLigne.setObjectName(_fromUtf8("labelFinLigne"))
        self.comboBoxPort = QtGui.QComboBox(Form)
        self.comboBoxPort.setGeometry(QtCore.QRect(10, 25, 121, 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.labelDebit = QtGui.QLabel(Form)
        self.labelDebit.setGeometry(QtCore.QRect(10, 50, 111, 16))
        self.labelDebit.setObjectName(_fromUtf8("labelDebit"))
        self.pushButtonInitSerial = QtGui.QPushButton(Form)
        self.pushButtonInitSerial.setGeometry(QtCore.QRect(135, 10, 41, 41))
        self.pushButtonInitSerial.setObjectName(_fromUtf8("pushButtonInitSerial"))
        self.line_2 = QtGui.QFrame(Form)
        self.line_2.setGeometry(QtCore.QRect(10, 195, 166, 16))
        self.line_2.setFrameShape(QtGui.QFrame.HLine)
        self.line_2.setFrameShadow(QtGui.QFrame.Sunken)
        self.line_2.setObjectName(_fromUtf8("line_2"))
        self.textEditReception = QtGui.QTextEdit(Form)
        self.textEditReception.setGeometry(QtCore.QRect(10, 305, 171, 76))
        self.textEditReception.setStyleSheet(_fromUtf8("background-color: rgb(244, 255, 190);"))
        self.textEditReception.setObjectName(_fromUtf8("textEditReception"))
        self.checkBoxAxisBottom = QtGui.QCheckBox(Form)
        self.checkBoxAxisBottom.setGeometry(QtCore.QRect(290, 370, 82, 19))
        self.checkBoxAxisBottom.setChecked(True)
        self.checkBoxAxisBottom.setObjectName(_fromUtf8("checkBoxAxisBottom"))
        self.checkBoxAxisLeft = QtGui.QCheckBox(Form)
        self.checkBoxAxisLeft.setGeometry(QtCore.QRect(390, 370, 82, 19))
        self.checkBoxAxisLeft.setChecked(True)
        self.checkBoxAxisLeft.setObjectName(_fromUtf8("checkBoxAxisLeft"))
        self.pushButtonInit = QtGui.QPushButton(Form)
        self.pushButtonInit.setGeometry(QtCore.QRect(200, 375, 85, 27))
        self.pushButtonInit.setObjectName(_fromUtf8("pushButtonInit"))
        self.checkBoxLabelAxisLeft = QtGui.QCheckBox(Form)
        self.checkBoxLabelAxisLeft.setGeometry(QtCore.QRect(390, 390, 96, 19))
        self.checkBoxLabelAxisLeft.setChecked(True)
        self.checkBoxLabelAxisLeft.setObjectName(_fromUtf8("checkBoxLabelAxisLeft"))
        self.checkBoxLabelAxisBottom = QtGui.QCheckBox(Form)
        self.checkBoxLabelAxisBottom.setGeometry(QtCore.QRect(290, 390, 96, 19))
        self.checkBoxLabelAxisBottom.setChecked(True)
        self.checkBoxLabelAxisBottom.setObjectName(_fromUtf8("checkBoxLabelAxisBottom"))

        self.retranslateUi(Form)
        self.comboBoxFinLigne.setCurrentIndex(1)
        QtCore.QMetaObject.connectSlotsByName(Form)

    def retranslateUi(self, Form):
        Form.setWindowTitle(QtGui.QApplication.translate("Form", "PyQt + PySerial + pyqtgraph : Oscillo simple", None, QtGui.QApplication.UnicodeUTF8))
        self.labelReception.setText(QtGui.QApplication.translate("Form", "Réception sur le port série : ", None, QtGui.QApplication.UnicodeUTF8))
        self.comboBoxFinLigne.setItemText(0, QtGui.QApplication.translate("Form", "Rien", None, QtGui.QApplication.UnicodeUTF8))
        self.comboBoxFinLigne.setItemText(1, QtGui.QApplication.translate("Form", "Saut de ligne (\\n = LF)", None, QtGui.QApplication.UnicodeUTF8))
        self.comboBoxFinLigne.setItemText(2, QtGui.QApplication.translate("Form", "Retour Chariot (\\r = CR)", None, QtGui.QApplication.UnicodeUTF8))
        self.comboBoxFinLigne.setItemText(3, QtGui.QApplication.translate("Form", "Les 2 (LF + CR)", None, QtGui.QApplication.UnicodeUTF8))
        self.labelPort.setText(QtGui.QApplication.translate("Form", "Port Série :", None, QtGui.QApplication.UnicodeUTF8))
        self.labelDelaiRecept.setText(QtGui.QApplication.translate("Form", "Délai Récept. (ms)", None, QtGui.QApplication.UnicodeUTF8))
        self.labelChaineEnvoi.setText(QtGui.QApplication.translate("Form", "Chaîne à envoyer :", None, QtGui.QApplication.UnicodeUTF8))
        self.pushButtonEnvoi.setText(QtGui.QApplication.translate("Form", "Envoi", None, QtGui.QApplication.UnicodeUTF8))
        self.labelTraceEnvoiSerie.setText(QtGui.QApplication.translate("Form", "Envoi sur le port série : ", None, QtGui.QApplication.UnicodeUTF8))
        self.pushButtonStop.setText(QtGui.QApplication.translate("Form", "Stop", 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.labelFinLigne.setText(QtGui.QApplication.translate("Form", "Fin de ligne :", 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.labelDebit.setText(QtGui.QApplication.translate("Form", "Débit Série (bauds) :", None, QtGui.QApplication.UnicodeUTF8))
        self.pushButtonInitSerial.setText(QtGui.QApplication.translate("Form", "Init", None, QtGui.QApplication.UnicodeUTF8))
        self.checkBoxAxisBottom.setText(QtGui.QApplication.translate("Form", "Axe X", None, QtGui.QApplication.UnicodeUTF8))
        self.checkBoxAxisLeft.setText(QtGui.QApplication.translate("Form", "Axe Y", None, QtGui.QApplication.UnicodeUTF8))
        self.pushButtonInit.setText(QtGui.QApplication.translate("Form", "Initialiser", None, QtGui.QApplication.UnicodeUTF8))
        self.checkBoxLabelAxisLeft.setText(QtGui.QApplication.translate("Form", "Label Axe Y", None, QtGui.QApplication.UnicodeUTF8))
        self.checkBoxLabelAxisBottom.setText(QtGui.QApplication.translate("Form", "Label Axe X", None, QtGui.QApplication.UnicodeUTF8))

from pyqtgraph import PlotWidget

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 - Mai 2013 - Tous droits réservés
# GPLv3 - www.mon-club-elec.fr

# modules a importer
from PyQt4.QtGui import *
from PyQt4.QtCore import *  # inclut QTimer..
import os,sys

import pyqtgraph as pg # pour accès à certaines constantes pyqtgraph, widget, etc...

import numpy as np # math et tableaux

import serial # communication serie

from tuto_pyqt_pyserial_pyqtgraph_oscillo_param_axes import * # fichier obtenu à partir QtDesigner et pyuic4

# +/- variables et objets globaux

class myApp(QWidget, Ui_Form): # la classe reçoit le Qwidget principal ET la classe définie dans test.py obtenu avec pyuic4
        def __init__(self, parent=None):
                QWidget.__init__(self) # initialise le Qwidget principal
                self.setupUi(parent) # Obligatoire

                # --- Variables de classe
                self.points=None # déclaration initiale
                self.compt=0 # variable comptage
                self.nombreValeurs=250
                self.framerate=20 # nombre d'image par secondes (rafraîchissement de l'affichage)              


                # --- Paramétrage des widgets de l'interface GUI si nécessaire ---

                # --- Connexions entre signaux des widgets et fonctions
                # connecte chaque signal utilisé des objets à l'appel de la fonction voulue

                #--- port série ---
                self.connect(self.pushButtonInitSerial, SIGNAL("clicked()"), self.pushButtonInitSerialClicked)
                self.connect(self.pushButtonEnvoi, SIGNAL("clicked()"), self.pushButtonEnvoiClicked)
                self.connect(self.pushButtonStop, SIGNAL("clicked()"), self.pushButtonStopClicked)

                # -- signaux widgets de paramétrage du graphique pyqtgraph --
                self.connect(self.pushButtonInit, SIGNAL("clicked()"), self.pushButtonInitClicked)
                self.connect(self.checkBoxAxisBottom, SIGNAL("clicked()"), self.checkBoxAxisBottomClicked) # connecte le signal Clicked de l'objet checkBox à l'appel de la fonction voulue
                self.connect(self.checkBoxAxisLeft, SIGNAL("clicked()"), self.checkBoxAxisLeftClicked) # connecte le signal Clicked de l'objet checkBox à l'appel de la fonction voulue
                self.connect(self.checkBoxLabelAxisBottom, SIGNAL("clicked()"), self.checkBoxLabelAxisBottomClicked) # connecte le signal Clicked de l'objet checkBox à l'appel de la fonction voulue
                self.connect(self.checkBoxLabelAxisLeft, SIGNAL("clicked()"), self.checkBoxLabelAxisLeftClicked) # connecte le signal Clicked de l'objet checkBox à l'appel de la fonction voulue

                # --- Code actif initial  ---

                #-- initialise le graphique pyqtgraph --
                # l'objet self.graph correspond au plotWidget créé dans QtDesigner

                # aspect fond /axes
                #self.graph.hideAxis('left') # masque axes - ‘left’, ‘bottom’, ‘right’, or ‘top’               
                self.graph.setBackgroundBrush(QBrush(QColor(Qt.white))) # la classe PlotWidget est un GraphicsWidget qui est un QGraphics View
                self.graph.showGrid(x=True, y=True)  # affiche la grille
                self.graph.getAxis('bottom').setPen(pg.mkPen(0,0,255)) # couleur de l'axe + grille
                self.graph.getAxis('left').setPen(pg.mkPen(255,0,0)) # couleur de l'axe + grille

                # légende des axes
                labelStyle = {'color': '#00F', 'font-size': '10pt'} # propriétés CSS à utiliser pour le label
                self.graph.getAxis('bottom').setLabel('Mesure', units='unit', **labelStyle) # label de l'axe
                self.graph.getAxis('left').setLabel('Valeur brute', units='unit', **labelStyle) # label de l'axe

                # adaptation échelle axes
                # axe X et Y sont autoscale par défaut
                self.graph.enableAutoRange(axis=pg.ViewBox.YAxis, enable=False) # fonction plotItem : désactive autoscale Y

                self.minY=0
                self.maxY=1023 
                self.graph.setYRange(self.minY,self.maxY) # fonction plotItem : fixe échelle des Y

                self.minX=0
                self.maxX=self.nombreValeurs
                self.graph.setXRange(self.minX,self.maxX) # fonction plotItem : fixe échelle des X

                # interactivité
                #self.graph.setInteractive(False) # fonction QGraphics View : pour inactiver interaction souris
                self.graph.getViewBox().setMouseMode(pg.ViewBox.RectMode)  # fonction ViewBox pas accessible depuis PlotWidget : fixe selection par zone
                self.graph.setMouseEnabled(x=False, y=True) # désactive interactivité axe X

                #-- initialise données --
                #-- définition des x
                self.x = np.arange(0.0, self.nombreValeurs+1, 1.0) # crée un vecteur de n valeurs à intervalle régulier pour les x
                print(self.x) # debug - affiche les valeurs x

                #-- calcul des y : courbe y=f(x)
                self.y=np.zeros(self.nombreValeurs+1)# crée un tableau de valeur y basé sur x - courbe y=sin(x)
                #self.y= np.random.normal(0,1,size=self.nombreValeurs) # génére une série de 1000 valeurs aléatoires

                print(self.y) # debug - affiche les valeurs y

                #-- affichage de la courbe --
                self.courbe=self.graph.plot(self.x,self.y, pen=(0,0,255)) # avec couleur

        # -- Activation d'un Timer pour MAJ graphique - fixe le fps d'affichage en fait --
                self.timerRefreshPlot=QTimer() # déclare un timer Qt
                self.connect(self.timerRefreshPlot, SIGNAL("timeout()"), self.refreshPlot) # connecte le signal timeOut de l'objet timer à l'appel de la fonction voulue
                self.timerRefreshPlot.start(int(1000/self.framerate)) # lance le timer - mettre délai assez court pour fps élevé

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

                #--- déclaration utiles ---
                self.serialPort=None # déclaration initiale

        # --- les fonctions appelées, utilisées par les signaux des widgets ---

        # -- widgets config Axes Pyqtgraph --
        def pushButtonInitClicked(self):
                print("Bouton Init cliqué")
                self.graph.setYRange(self.minY,self.maxY) # fonction plotItem : fixe échelle des Y
                self.graph.setXRange(0,self.nombreValeurs) # fonction plotItem : fixe échelle des Y

        def checkBoxAxisBottomClicked(self):
                print("Checkbox axe X cliqué")

                if self.checkBoxAxisBottom.isChecked(): # si checkBox sélectionné
                        print("Etat checkBox = " + str(self.checkBoxAxisBottom.isChecked()))
                        self.graph.showAxis('bottom')
                        # self.graph.showAxis('bottom', True) # équivalent
                        if self.checkBoxLabelAxisBottom.isChecked(): self.graph.showLabel('bottom',True)        
                else : # si checkBox pas sélectionné
                        print("Etat checkBox = " + str(self.checkBoxAxisBottom.isChecked()))
                        self.graph.hideAxis('bottom')
                        # self.graph.showAxis('bottom', False) # équivalent
                        self.graph.showLabel('bottom',False)

        def checkBoxAxisLeftClicked(self):
                print("Checkbox axe Y cliqué")

                if self.checkBoxAxisLeft.isChecked(): # si checkBox sélectionné
                        print("Etat checkBox = " + str(self.checkBoxAxisLeft.isChecked()))
                        self.graph.showAxis('left')
                        # self.graph.showAxis('left', True) # équivalent
                        if self.checkBoxLabelAxisLeft.isChecked():self.graph.showLabel('left',True)                    
                else : # si checkBox pas sélectionné
                        print("Etat checkBox = " + str(self.checkBoxAxisLeft.isChecked()))
                        self.graph.hideAxis('left')
                        self.graph.showLabel('left',False)

        def checkBoxLabelAxisBottomClicked(self):
                print("Checkbox Label axe X cliqué")

                if self.checkBoxLabelAxisBottom.isChecked(): # si checkBox sélectionné
                        print("Etat checkBox = " + str(self.checkBoxLabelAxisBottom.isChecked()))
                        self.graph.showLabel('bottom',True)    
                else : # si checkBox pas sélectionné
                        print("Etat checkBox = " + str(self.checkBoxLabelAxisBottom.isChecked()))
                        self.graph.showLabel('bottom',False)

        def checkBoxLabelAxisLeftClicked(self):
                print("Checkbox Label axe Y cliqué")

                if self.checkBoxLabelAxisLeft.isChecked(): # si checkBox sélectionné
                        print("Etat checkBox = " + str(self.checkBoxLabelAxisLeft.isChecked()))
                        self.graph.showLabel('left',True)                      
                else : # si checkBox pas sélectionné
                        print("Etat checkBox = " + str(self.checkBoxLabelAxisLeft.isChecked()))
                        self.graph.showLabel('left',False)             

        #----- les fonctions des signaux des boutons du Terminal série ----                            
        def pushButtonInitSerialClicked(self): # lors appui bouton initialisation série
                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 ms
                        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("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("PB"))  # change titre bouton

                #self.timerSerial.start(20) # lance le timer avec délai en ms - 10 pour réception rapide
                self.timerSerial.start(self.spinBoxDelaiReception.value()) # lance le timer avec délai en ms avec valeur spinbox

        def pushButtonEnvoiClicked(self): # lors appui bouton envoi série du champ du Terminal Série
                print("Bouton ENVOI appuyé")
                self.envoiChaineSerie(str(self.lineEditChaineEnvoi.text())) # envoi le contenu du champ texte sur le port série

        #-- fonction gestion clicked pushButton Stop
        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.timerSerial.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("Off"))  # change titre bouton

        #-- fin fonction gestion clicked pushButton Stop


        # --- les fonctions appelées, utilisées par les signaux hors widgets ---

        #----- fonction de gestion du signal timeout du QTimer
        def timerSerialEvent(self): # fonction appelée lors de la survenue d'un évènement Timer - nom fonction indiférrent
                #-- variables de réception --
                self.chaineIn="";
                self.char="";

                # lecture des données reçues           
                if self.serialPort: # seulement si le port série existe
                        self.timerSerial.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
                                        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                                  

                        if len(self.chaineIn)>0: # ... 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 (saut de ligne)                               

                                if self.chaineIn[:1].isdigit(): # si la chaine est un chiffre
                                        self.draw(int(self.chaineIn)) # // ajoute la valeur au graphique

                        self.timerSerial.start(self.spinBoxDelaiReception.value()) # lance le timer avec délai en ms avec valeur spinbox
                        #self.timerSerial.start() # redémarre le timer
                        # ne pas stopper le timerSerial permet plus grande vitesse réception... Il faut que Arduino envoie à même fréquence également

        #---- fin timerEvent Serial

        #----- fonction de gestion du signal timeout du QTimer
        def refreshPlot(self): # fonction appelée lors de la survenue d'un évènement Timer - nom fonction indiférrent
                print ("MAJ tracé")
                self.courbe.setData(self.x,self.y) # actualisation valeurs courbe


        # --- fonctions de classes autres---    

        #----- fonction de classe commune d'envoi d'une chaîne sur le port série ----
        def envoiChaineSerie(self, chaineIn): # la fonction reçoit un objet chaîne Str

                if self.serialPort: # seulement si le port série existe - n'existe pas (=None) tant que pas initialisé

                        self.timerSerial.stop() # stoppe le timer le temps d'envoyer message sur le port série         

                        # combobox avec index 0 = rien, 1=saut de ligne (LF), 2=retour chariot (CR), 3= les 2 LF+CR
                        if self.comboBoxFinLigne.currentIndex()==0: # si rien sélectionné
                                # self.serialPort.write(str(self.lineEditChaineEnvoi.text())+'\n'  ) # envoie la chaine sur le port serie              
                                self.serialPort.write(chaineIn)  # envoie la chaine sur le port serie   - variante ascii       
                                print("Envoi Série : " + chaineIn )
                                self.textEditTraceEnvoiSerie.append(chaineIn) # ajoute texteEdit de visualisation

                        if self.comboBoxFinLigne.currentIndex()==1: # si saut de ligne sélectionné
                                self.serialPort.write(chaineIn +chr(10) ) # envoie la chaine sur le port serie  - variante ascii       
                                print("Envoi Série : " + chaineIn + '\n')
                                self.textEditTraceEnvoiSerie.append(chaineIn) # ajoute texteEdit de visualisation

                        if self.comboBoxFinLigne.currentIndex()==2: # si retour chariot sélectionné
                                self.serialPort.write(chaineIn+chr(13)  ) # envoie la chaine sur le port serie  - variante ascii       
                                print("Envoi Série : " + chaineIn + '\r')
                                self.textEditTraceEnvoiSerie.append(chaineIn) # ajoute texteEdit de visualisation

                        if self.comboBoxFinLigne.currentIndex()==3: # si saut de ligne + retour chariot sélectionné
                                self.serialPort.write(chaineIn+chr(10)+chr(13)  ) # envoie la chaine sur le port serie  - variante ascii
                                print("Envoi Série : " + chaineIn + '\n'+'\r')
                                self.textEditTraceEnvoiSerie.append(chaineIn) # ajoute texteEdit de visualisation

                        self.timerSerial.start(self.spinBoxDelaiReception.value()) # lance le timer avec délai en ms avec valeur spinbox       
                        #self.timerSerial.start() # redémarre le timer - laisse délai pour réception en réinitialisation Timer à 0
                        # car sinon l'appui survient n'importe quand et si survient peu de temps avant fin délai
                        # la réception est hachée


        #--- fin envoiChaineSerie

        #-- fonction tracé appelée lors événement TimerSérie
        def draw(self, value): # fonction appelée lors de la survenue d'un évènement Timer - nom de la fonction indiférrent

                if self.compt==0: # premier passage
                        self.points= np.array([[self.compt,value]]) # tableau à 2 dimensions - ici 1er points
                        self.x=self.points[:,0] # la première colonne = les x
                        self.y=self.points[:,1] # la deuxième colonne = les y          
                        self.compt=self.compt+1 # incrémente compt
                elif self.compt<=self.nombreValeurs: # on remplit le tableau de point une première fois
                        newY=value
                        #newY=self.compt # x=y - debug
                        self.points=np.append(self.points,[[self.compt,newY]],axis=0)# ajouter un point au tableau
                        self.x=self.points[:,0] # la première colonne = les x
                        self.y=self.points[:,1] # la deuxième colonne = les y          
                        self.compt=self.compt+1 # incrémente compt
                else:
                        #self.points=roll(self.points,1, axis=1) # décale les éléments y de 1 - fonctin numpy
                        #self.x=self.points[:,0] # la première colonne = les x - existe déjà
                        #self.y=self.points[:,1] # la deuxième colonne = les y - existe déjà

                        #self.y=roll(self.y,1) # décale les éléments y de 1 - fonctin numpy - les x ne bougent pas..
                        self.y=np.roll(self.y,-1) # décale les éléments y de 1 - fonctin numpy - les x ne bougent pas..

                        #self.y[self.Xmax]=self.y[self.Xmax-1]/2 # nouvelle valeur en dernière position
                        self.y[self.nombreValeurs]=value # nouvelle valeur en dernière position

                        # important : pour le tracé, ce n'est pas l'ordre qui est important, mais la coordonnée x,y dans notre cas...

                # remarquer la simplicité avec laquelle il es possible d'extraire le tableau des x ou des y à partir du tableau de points


                #if self.compt==self.Xmax : self.compt=0 # RAZ compt

                # debug
                #print self.points
                #print self.x  
                #print self.y

                #-- actualise la courbe --
                #self.courbe.setData(self.x,self.y) # actualisation valeurs courbe
                # voir refrehPlot

# -- Autres Classes utiles --

# -- Classe principale (lancement)  --
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

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
  • Initialiser la comunication série, paramétrer le délai avec le même que celui utilisé côté Arduino : la courbe s'affiche.