fbpixel
Développer un moniteur UDP avec Python

Développer un moniteur UDP avec Python

Dans ce projet, nous allons créer un moniteur de communication réseau UDP à l’aide de Python (PyQt). 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 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 un moniteur de communication réseau avec PyQt(PySide2).

Objectif

Pour ce projet, nous souhaitons créer une interface graphique en Python, sous Windows, se comportant comme un moniteur de communication UDP. Pour cela, il va nous falloir réaliser les fonctions suivantes

  • Zone de saisie de l’adresse IP
  • Zone de saisie du port
  • Bouton connexion
  • Zone d’écriture pour la commande
  • Bouton envoyer
  • Console affichant les données reçues

Récupérer l’adresse IP de la machine

Sur la machine de réception récupérer l’adresse IPv4 (dans notre cas 192.168.1.67)

ipconfig #sur la machine windows

ou

ip addr #sur machine linux

Application moniteur de communication UDP

Nous allons d’abord créer la fenêtre de l’application que nous appelons AcApp et qui sera la base de notre application.

#!/usr/bin/python3
# -*-coding:Utf-8 -*

import sys,os
#from PyQt5.QtWidgets import *
#from PyQt5.QtCore import *
#from PyQt5.QtGui import *
from PySide2.QtWidgets import *
from PySide2.QtCore import *
from PySide2.QtGui import *
pyqtSignal=Signal #translate pyqt to Pyside

class AcApp(QMainWindow):

    def __init__(self,title='AcApp',mainFrame=QFrame):
        super().__init__()
        self.title=title
        self.mainFrame=mainFrame()
        self.initUI()

    def initUI(self):        
        self.setCentralWidget(self.mainFrame)
       
        #connect signals
        self.mainFrame.debugSignal.connect(self.debugMsg)
       
        #General configuration
        #self.resize(self.width, self.height)
        self.setWindowTitle(self.title)
        self.setGeometry(300, 300, 850, 450)
        #self.setWindowIcon(QIcon(__icon__))
       
        #Debug bar
        self.statusBar()
        self.statusBar().showMessage('Display debug messages')
       
        self.show()

    def debugMsg(self,val):
        self.statusBar().showMessage(val)

def main():
    app = QApplication(sys.argv)
    app.setQuitOnLastWindowClosed(True)
    ex = AcApp(__title__)
    #ex = AcApp(__title__,EthernetInterface)
    app.quit()
    sys.exit(app.exec_())

       
if __name__ == '__main__':
    main()

Création des Widgets de gestion de la communication UDP

Nous allons ensuite créer une classe qui contiendra tous les Widgets dont nous avons besoin pour créer et gérer le moniteur de communication UDP (QLineEdit,QTextEdit,QButton, etc.). Ce Widget sera initialisé avec la classe qui va gérer la communication

#self.model=EthModel()

class EthernetInterface(QFrame):
    debugSignal=pyqtSignal(str) #define debug signal   

    def __init__(self,parent=None):
        super(EthernetInterface,self).__init__(parent)
        self.grid=QGridLayout()
        self.setLayout(self.grid)
        self.defineWidgets()
        self.model=None #self.model=EthModel()
        if self.model is not None: self.model.debugSignal.connect(self.read)
   
    def defineWidgets(self):
        #self.setStyleSheet("""QGroupBox{background-color:white;border: 1px solid green;border-radius: 4px;}
        #QGroupBox::title {padding:1 5px;}""")
               
        #grooupbox widget container
        self.grp=QGroupBox(self)
        self.grp.setTitle("Connection Configuration")
       
        self.fields=QGridLayout()
        self.grp.setLayout(self.fields)
        self.grid.addWidget(self.grp,0,0)
       
        #Define widget UI
        #validator
        ipRange = "(?:[0-1]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])"   # Part of the regular expression
        # Regulare expression
        ipRegex = QRegExp("^" + ipRange + "\\." + ipRange + "\\." + ipRange + "\\." + ipRange + "$")
        ipValidator = QRegExpValidator(ipRegex, self)  
       
        #label
        self.selectlbl = QLabel("IP Address:")
        self.typeBox = QLineEdit(HOST)
        #self.typeBox.setInputMask("0.0.0.0");
        self.typeBox.setValidator(ipValidator);
       
        self.baudlbl = QLabel("Port:")
        self.baudBox = QLineEdit("{}".format(PORT))
       
        #btn
        self.button = QPushButton("Connect")
        self.button.clicked.connect(self.clicked)
        self.button.clicked.connect(self.connec)
       
        sendBtn = QPushButton("send")
        sendBtn.clicked.connect(self.clicked)
        sendBtn.clicked.connect(self.send)
       
        titlelbl=  QLabel("Enter")
        self.edit = QLineEdit("")
        sentlbl=QLabel("Sent")
        self.sent = QTextEdit("")
        desclbl=QLabel("Console")
        self.desc = QTextEdit("")
           
        #row, column[, rowSpan=1[, columnSpan=1[
        self.fields.addWidget(self.selectlbl,0,0,1,1)
        self.fields.addWidget(self.typeBox,0,1,1,1)
        self.fields.addWidget(self.baudlbl,0,2,1,1)
        self.fields.addWidget(self.baudBox,0,3,1,1)
       
        self.fields.addWidget(self.button,0,4,1,1)
       
        self.fields.addWidget(titlelbl,1,0,1,1)
        self.fields.addWidget(self.edit,1,1,1,3)
        self.fields.addWidget(sendBtn,1,4,1,1)
       
        self.fields.addWidget(sentlbl,2,0,1,1,Qt.AlignTop)#Qt.AlignmentFlag.AlignTop)
        self.fields.addWidget(self.sent,2,1,1,3)  
        self.fields.addWidget(desclbl,3,0,1,1,Qt.AlignTop)#Qt.AlignmentFlag.AlignTop)
        self.fields.addWidget(self.desc,3,1,1,3)      

    def debug(self,msg):
        sender = self.sender()
        self.debugSignal.emit(sender.__class__.__name__+" : "+msg)
       
    def clicked(self):
        sender = self.sender()
        if sender.__class__.__name__=="QPushButton":
            self.debugSignal.emit(sender.text()+ " clicked")
        if sender.__class__.__name__=="QComboBox":
            self.debugSignal.emit(sender.currentText()+ " selected")

    def connec(self):
        #self.desc.setText("")
        self.desc.clear()
        if self.model is not None:
            if self.button.text() == "Connect":
                self.desc.setText(">> trying to connect to address {} on port {} ...".format(self.typeBox.text(),self.baudBox.text()))

                print("Started")
                self.button.setText("Stop")
                #self.model = EthModel()
                self.model.connec(self.typeBox.text(),int(self.baudBox.text()))
                self.model.start()
            else:
                self.model.quit_flag = True
                print("Stop sent")
                self.model.close()#self.model.wait()
                print("Stopped")
                self.button.setText("Connect")
                self.desc.setText(">> deconnect address {} on port {} ...".format(self.typeBox.text(),self.baudBox.text()))
                
   
    def read(self,msg):
        self.desc.setText(self.desc.toPlainText()+msg+"\n")
        self.desc.verticalScrollBar().setValue(self.desc.verticalScrollBar().maximum());
       
                   
    def send(self):
        if self.edit.text() != "":
            self.sent.setText(self.sent.toPlainText()+self.edit.text()+"\n")
           
            if self.model is not None:
                self.model.write(self.edit.text())

Remplacer ex = AcApp(__title__) par ex = AcApp(__title__,EthernetInterface) dans la fonction main()

Une fois nos widgets en place, nous allons pouvoir les exploiter.

Création d’un QThread de gestion de la communication UDP

Afin de ne pas bloquer l’interface graphique lors de la réception ou l’émission de paquets, nous allons créer un QThread qui va permettre la gestion de la communication UDP

#Ethernet connection
class EthModel(QThread):
    """Handle Ethernet connexion with remote device and connect to interface EthInterface"""
    debugSignal=pyqtSignal(str) #define debug signal   
    def __init__(self):
        super(EthModel, self).__init__()
        self.quit_flag = False
        self.msgToEmit=""

    def run(self):
        while True:
            if not self.quit_flag:
                self.read()
                #time.sleep(1)
            else:
                self.close()
                break

        self.quit()
        #self.wait()
                
    def connec(self,addr='192.168.1.10',port=7):
        # Create a UDP/IP socket
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) #worls with esp8266 udp client
        self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
        self.sock.bind((addr,port))
        # Listen for incoming connections
        #self.sock.listen(1) #stream
        
        print('starting up on {} port {}'.format(addr,port))
        self.debugSignal.emit('starting up on {} port {}'.format(addr,port))
        self.quit_flag = False
        self._isRunning=True
	
    def read(self):
        while self._isRunning:
            #data = self.sock.recv(1024)
            try:
                data, addr = self.sock.recvfrom(1024)
                print(data)
            except:
                print("socket closed")
                data=False

            if not data:
                print("no data > break loop")
                break
            #self.sock.sendto("received OK".encode('utf-8'),addr)
            if self.msgToEmit!="":
                self.sock.sendto(self.msgToEmit.encode('utf-8'),addr)
                self.msgToEmit="" #clear message
            self.debugSignal.emit(str(data))


    def write(self,msg):
        self.msgToEmit=msg

    def close(self):
        self._isRunning=False
        self.sock.close()

Code Python client UDP

Pour tester votre interface, vous pouvez lancer un code sur le même ordinateur en utilisant l’adresse 127.0.0.1 (adresse locale). Le code suivant va créer un socket et va envoyer la valeur d’un compteur.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import socket
import time
HOST = "127.0.0.1"
PORT = 8888
bufferSize = 1024



counter=0
while True:
	# Create a UDP socket at client side
	with socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM) as client:
		# Send to server using created UDP socket
		client.sendto(str.encode(str(counter)), (HOST,PORT))

		data,addr= client.recvfrom(bufferSize)
		msg = "Message from Server {}".format(data)

		print(msg)
	counter+=1
	time.sleep(0.1)

Code ESP8266 client UDP

Le code ESP8266 pour la communication UDP est assez simple, nous définissons la communication réseau avec le protocole UDP et nous renvoyons la valeur donnée par la fonction millis()

#include <ESP8266WiFi.h>
#include <WiFiUdp.h>

WiFiUDP udp;
char packetBuffer[256];
unsigned int localPort = 9999;
const char *ssid = "******";
const char *password = "******";

// Set your Static IP address
IPAddress local_IP(192, 168, 1, 80);
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 0, 0);

// Set destination IP address
const char *destaddr = "192.168.1.67";
unsigned int destPort = 8888;

void setup() {
  Serial.begin(115200);
  WiFi.config(local_IP, gateway, subnet);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  udp.begin(localPort);
  Serial.print(F("UDP Client : ")); Serial.println(WiFi.localIP());
}

void loop() {
  int packetSize = udp.parsePacket();
  Serial.print(" Received packet from : "); Serial.println(udp.remoteIP());
  Serial.print(" Size : "); Serial.println(packetSize);
  Serial.print(" Data : ");
  packetBuffer[0] = '0'; //reset buffer
  if (packetSize) {
    int len = udp.read(packetBuffer, 256);
    Serial.print(packetBuffer);
    
    udp.flush();
  }
  Serial.println("\n");
  delay(500);
  Serial.print("[Client Connected] ");
  Serial.println(WiFi.localIP());
  udp.beginPacket(destaddr, destPort);
  udp.write("Send millis: ");
  char buf[20];
  unsigned long testID = millis();
  sprintf(buf, "%lu", testID);
  Serial.print(" Sent : "); Serial.println(buf);
  udp.write(buf);
  udp.write("\r\n");
  udp.endPacket();
}

N.B.: Pour ce projet, nous utilisons un esp8266 mais vous pouvez adapter le code pour n’importe quel appareil utilisant le protocole UDP

Résultats

Pour utiliser le code client Python, entrez l’adresse IP 127.0.0.1

Pour tester la communication avec l’ESP8266, utilisez l’adresse IP de votre ordinateur (ici, 192.168.1.67)

Nous avons créer un moniteur de communication UDP à l’aide de Python qui peut permettre de s’interfacer avec des appareils distants comme ESP8266, ESP32, Raspberry Pi ou autres ordinateurs. Vous pouvez à présent améliorer l’interface pour qu’elle corresponde à votre besoin

Code complet

#!/usr/bin/python3
# -*-coding:Utf-8 -*
"""
Created on Thu Nov 17 16:59:13 2022

@author: X.Wiedmer

AC windows
Define the application window
"""
import sys,os
#from PyQt5.QtWidgets import *
#from PyQt5.QtCore import *
#from PyQt5.QtGui import *
from PySide2.QtWidgets import *
from PySide2.QtCore import *
from PySide2.QtGui import *
pyqtSignal=Signal #translate pyqt to Pyside

import socket
import time

"""
App configuration
"""
__title__="ACTerminal"
__version__="v0.1"

HOST = '192.168.1.67'
PORT = 8888

#Ethernet connection
class EthModel(QThread):
    """Handle Ethernet connexion with remote device and connect to interface EthInterface"""
    debugSignal=pyqtSignal(str) #define debug signal   
    def __init__(self):
        super(EthModel, self).__init__()
        self.quit_flag = False
        self.msgToEmit=""

    def run(self):
        while True:
            if not self.quit_flag:
                self.read()
                #time.sleep(1)
            else:
                self.close()
                break

        self.quit()
        self.exit()
        #self.wait()
                
    def connec(self,addr='192.168.1.10',port=7):
        # Create a UDP/IP socket
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) #worls with esp8266 udp client
        self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
        self.sock.bind((addr,port))
        # Listen for incoming connections
        #self.sock.listen(1) #stream
        
        print('starting up on {} port {}'.format(addr,port))
        self.debugSignal.emit('starting up on {} port {}'.format(addr,port))
        self.quit_flag = False
        self._isRunning=True
	
    def read(self):
        while self._isRunning:
            #data = self.sock.recv(1024)
            try:
                data, addr = self.sock.recvfrom(1024)
                print(data)
            except:
                print("socket closed")
                data=False

            if not data:
                print("no data > break loop")
                break
            #self.sock.sendto("received OK".encode('utf-8'),addr)
            if self.msgToEmit!="":
                self.sock.sendto(self.msgToEmit.encode('utf-8'),addr)
                self.msgToEmit="" #clear message
            self.debugSignal.emit(str(data))


    def write(self,msg):
        self.msgToEmit=msg

    def close(self):
        self._isRunning=False
        self.sock.close()

#define GUI    
class EthernetInterface(QFrame):
    debugSignal=pyqtSignal(str) #define debug signal   

    def __init__(self,parent=None):
        super(EthernetInterface,self).__init__(parent)
        self.grid=QGridLayout()
        self.setLayout(self.grid)
        self.defineWidgets()
        self.model=EthModel()#self.model=None
        self.model.debugSignal.connect(self.read)
   
    def defineWidgets(self):
        #self.setStyleSheet("""QGroupBox{background-color:white;border: 1px solid green;border-radius: 4px;}
        #QGroupBox::title {padding:1 5px;}""")
               
        #grooupbox widget container
        self.grp=QGroupBox(self)
        self.grp.setTitle("Connection Configuration")
       
        self.fields=QGridLayout()
        self.grp.setLayout(self.fields)
        self.grid.addWidget(self.grp,0,0)
       
        #Define widget UI
        #validator
        ipRange = "(?:[0-1]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])"   # Part of the regular expression
        # Regulare expression
        ipRegex = QRegExp("^" + ipRange + "\\." + ipRange + "\\." + ipRange + "\\." + ipRange + "$")
        ipValidator = QRegExpValidator(ipRegex, self)  
       
        #label
        self.selectlbl = QLabel("IP Address:")
        self.typeBox = QLineEdit(HOST)
        #self.typeBox.setInputMask("0.0.0.0");
        self.typeBox.setValidator(ipValidator);
       
        self.baudlbl = QLabel("Port:")
        self.baudBox = QLineEdit("{}".format(PORT))
       
        #btn
        self.button = QPushButton("Connect")
        self.button.clicked.connect(self.clicked)
        self.button.clicked.connect(self.connec)
       
        sendBtn = QPushButton("send")
        sendBtn.clicked.connect(self.clicked)
        sendBtn.clicked.connect(self.send)
       
        titlelbl=  QLabel("Enter")
        self.edit = QLineEdit("")
        sentlbl=QLabel("Sent")
        self.sent = QTextEdit("")
        desclbl=QLabel("Console")
        self.desc = QTextEdit("")
           
        #row, column[, rowSpan=1[, columnSpan=1[
        self.fields.addWidget(self.selectlbl,0,0,1,1)
        self.fields.addWidget(self.typeBox,0,1,1,1)
        self.fields.addWidget(self.baudlbl,0,2,1,1)
        self.fields.addWidget(self.baudBox,0,3,1,1)
       
        self.fields.addWidget(self.button,0,4,1,1)
       
        self.fields.addWidget(titlelbl,1,0,1,1)
        self.fields.addWidget(self.edit,1,1,1,3)
        self.fields.addWidget(sendBtn,1,4,1,1)
       
        self.fields.addWidget(sentlbl,2,0,1,1,Qt.AlignTop)#Qt.AlignmentFlag.AlignTop)
        self.fields.addWidget(self.sent,2,1,1,3)  
        self.fields.addWidget(desclbl,3,0,1,1,Qt.AlignTop)#Qt.AlignmentFlag.AlignTop)
        self.fields.addWidget(self.desc,3,1,1,3)      

    def debug(self,msg):
        sender = self.sender()
        self.debugSignal.emit(sender.__class__.__name__+" : "+msg)
       
    def clicked(self):
        sender = self.sender()
        if sender.__class__.__name__=="QPushButton":
            self.debugSignal.emit(sender.text()+ " clicked")
        if sender.__class__.__name__=="QComboBox":
            self.debugSignal.emit(sender.currentText()+ " selected")

    def connec(self):
        #self.desc.setText("")
        self.desc.clear()
        if self.model is not None:
            if self.button.text() == "Connect":
                self.desc.setText(">> trying to connect to address {} on port {} ...\n".format(self.typeBox.text(),self.baudBox.text()))

                print("Started")
                self.button.setText("Stop")
                self.model.connec(self.typeBox.text(),int(self.baudBox.text()))
                self.model.start()
            else:
                self.model.quit_flag = True
                print("Stop sent")
                self.model.close()#self.model.wait()
                print("Stopped")
                self.button.setText("Connect")
                self.desc.setText(">> deconnect address {} on port {} ...".format(self.typeBox.text(),self.baudBox.text()))
                
   
    def read(self,msg):
        self.desc.setText(self.desc.toPlainText()+msg+"\n")
        self.desc.verticalScrollBar().setValue(self.desc.verticalScrollBar().maximum());
       
                   
    def send(self):
        if self.edit.text() != "":
            self.sent.setText(self.sent.toPlainText()+self.edit.text()+"\n")
           
            if self.model is not None:
                self.model.write(self.edit.text())

# Generic app container     
class AcApp(QMainWindow):

    def __init__(self,title='AcApp',mainFrame=QFrame):
        super().__init__()
        self.title=title
        self.mainFrame=mainFrame()
        self.initUI()

    def initUI(self):        
        self.setCentralWidget(self.mainFrame)
       
        #connect signals
        self.mainFrame.debugSignal.connect(self.debugMsg)
       
        #General configuration
        #self.resize(self.width, self.height)
        self.setWindowTitle(self.title)
        self.setGeometry(300, 300, 850, 450)
        #self.setWindowIcon(QIcon(__icon__))
       
        #Debug bar
        self.statusBar()
        self.statusBar().showMessage('Display debug messages')
       
        self.show()

    def debugMsg(self,val):
        self.statusBar().showMessage(val)
   


       
def main():
    app = QApplication(sys.argv)
    app.setQuitOnLastWindowClosed(True)
    #app.setStyleSheet(open("style.txt").read()); #set style according to file 
    #ex = AcApp(__title__)
    ex = AcApp(__title__,EthernetInterface)
    app.quit()
    sys.exit(app.exec_())

       
if __name__ == '__main__':
    main()

Sources

Installer PlatformIO IDE sur Visual Studio Code

Installer PlatformIO IDE sur Visual Studio Code

Dans ce tutoriel nous allons configurer Visual Studio Code pour pouvoir lancer des scripts C/C++ avec PlatformIO IDE, qui est très utile pour programmer des microcontrôleurs tels que Arduino ou ESP32.

Matériel

  • Ordinateur
  • Carte Arduino UNO ou autre
  • Câble USB pour connecter la carte Arduino au PC

Installation de Visual Studio Code et de PlatformIO IDE

Suivez les étapes d’installation en téléchargeant .Net 6.0

Ouvrez ensuite Visual Studio Code.

Cherchez PlatformIO IDE dans l’onglet “Extensions” sur la colonne à gauche de Visual Studio Code et cliquez sur “installer”.

Une fois installé, fermez puis relancez Visual Studio Code pour que le logiciel soit bien paramétré avec PlatformIO IDE. Vous devriez alors arrivez sur la page d’accueil de l’IDE.

Démarrer votre premier Project en C avec PlatformIO IDE

Pour cela, cliquez sur ” + New Project ” et remplissez la fenêtre qui apparaît.

Ici, en terme d’exemple, j’ai choisi une carte Arduino Uno et le cadre sera aussi de type Arduino, c’est à dire que l’on va retrouver les fonctions setup() et loop() dans le fichier principal main. La création de projet peut prendre un peu de temps.

On peut remarquer à gauche dans l’espace de travail notre projet qu’on retrouve différent sous dossier. C’est le dossier ” src ” qu’on va retrouver les scriptes sources du projet, dont le fichier principal “main.cpp ” dans lequel on a va écrire notre scripte pour programmer notre carte Arduino Uno. Dans ce cadre préconstruit Arduino, on peut remarquer que dans l’entête dans du programme la ligne de commande ” #include “. Cette ligne de commande est très importante car elle permet à votre projet d’utiliser toutes les librairies et les fonctions principales que l’ont peut retrouver dans Arduino IDE.

Débuter avec un programme en C sur PlatformIO IDE

Pour tester votre premier projet en C sur PlatformIO IDE, vous allez téléverser un petit programme pour faire clignoter une LED sur la carte Arduino Uno et avoir un retour via liaison série sur l’état de la Led sur le terminal de Visual Studio Code.

// Ici dans l'entête du programme, on inclut les librairies et prototypes de fonctions nécessaires
#include <Arduino.h>

// Ici on met nos initialisations
void setup() 
{ 
    Serial.begin(9600); //ouverture pour communiquer via le port série
    pinMode(13, OUTPUT); //On initialise le pin 13 qui est associé à la LED en sortie pour pouvoir l'allumer et l'éteindre
} //Fin de la fonction setup()

// Ici dans la fonction loop on vient placer le script qui se répètera en boucle dans l'Arduino
void loop() 
{
  digitalWrite(13,HIGH); //Place le pin digital 13 à l'état HAUT (5V) -> Led allumée
  Serial.println("Led allumée");//Nous renvoie par la liaison série l'état de la Led Allumé
  delay(500); //Met en pause le programme pendant la valeur de 500 en ms

  digitalWrite(13,LOW); //Place le pin digital 13 à l'état BAS (0V) -> Led éteinte
   Serial.println("Led éteinte");//Nous renvoie par la liaison série l'état de la Led éteinte
  delay(500); //Met en pause le programme pendant la valeur de 500 en ms
} // Fin de la fonction

Une fois votre programme copier collé, vous pouvez le téléverser. Assurez vous que l’Arduino soit bien branché à un des ports USB de votre ordinateur.
Pour téléverser le programme, il existe des raccourcis qu’on peut activer en cliquant dessus tout en bas de Studio Visual Code :

  1. compiler le programme ;
  2. compiler, nettoyer et de téléverser le programme dans la carte Arduino ( la détection du PORT USB utilisé se fait automatiquement ) ;
  3. nettoyer le Terminal ainsi que la carte microcontrôleur branché ( de supprimer le script enregistré dessus ) ;
  4. tester le programme ;
  5. ouvrir un moniteur pour la Liaison Série et recevoir des envoies de la carte ( ou d’en envoyer ). Lorsque ce moniteur appelé “Serial Monitor” est ouvert, le téléversement d’un programme est impossible. Alors avant de téléverser un nouveau programme, il faut fermer le moniteur en cliquant une fois sur le terminal, puis en appuyant sur les touches Ctrl + C ;
  6. Permet d’ouvrir un Terminal.

Une fois que vous avez cliqué sur téléversement, vous devriez avoir un retour dans un terminal qui confirme que la compilation et le téléversement a été un succès qui ressemble à ceci :

Ensuite, cliquez sur la commande pour ouvrir un Moniteur et établir la liaison série.

Vous pouvez alors observer le retour de l’état de la Led en direct sur le moniteur.

Comment installer un librairie externe sur Visual Studio Code pour la PlatformIO IDE

Pour cela rien de plus. Télécharger d’abord votre librairie externe. Une fois que vous aurez un fichier .zipp, vous devez l’extraire ( ou copiez collez de dossier non compressé de la librairie ) dans ce dossier là ( ici la librairie qui nous servira d’exemple sera ServoLib, qui sert à faciliter la commande de servo-moteur ) :

On pourra retrouver l’accès ce dossier ” lib ” de votre projet fait pour accueillir les libraires externes via Visual Studio Code.

Une fois une librairie installé dans votre projet, vous devez l’inclure dans le programme dans l’entête tel que :

Voilà, maintenant vous avez toutes les bases pour débuter sur PlatformIO IDE pour programmer en C/C++ !

Utilisation d’un écran Nextion avec Arduino

Utilisation d’un écran Nextion avec Arduino

L’écran Nextion est une des meilleures solutions pour créer une interface graphique afin de piloter votre projet Arduino. L’interface la plus connue pour interagir avec un Arduino est l’écran LCD avec quelques boutons et potentiomètres au prix des entrées-sorties et d’une surcharge du code Arduino. Nous allons voir dans ce tutoriel comment configurer un écran Nextion et comment l’utiliser avec Arduino

Matériel

  • Arduino (ou autre carte avec port UART)
  • Écran Nextion
  • Adaptateur USB-TTL 5V
  • 4x Dupont wire avec connecteur JST

Présentation de la carte Nextion

L’interface de base pour piloter un Arduino, et présent dans tous les kits, est l’écran LCD avec quelques boutons et potentiomètres qui existe sous forme de Shield LCD. Il existe aussi des Shield muni d’écran tactile mais, ces derniers, utilisent toutes les entrées-sorties de l’Arduino et surcharge le code Arduino. Une solution à cela est d’utiliser un écran Nextion qui contient son propre programme et communique avec n’importe quelle microcontrôleur avec le port série.

N.B.: Il est possible de créer une interface graphique sur PC ou créer une interface web pour piloter le projet en ajoutant un module de communication sans fil.

Installation et présentation de l’éditeur Nextion

Téléchargez et installez l’éditeur Nextion

Lorsque vous ouvrez un nouveau projet ou fichier, le logiciel vous demande le modèle de l’écran (dans notre cas NX4832K035_011). Vous pouvez modifier la selection dans le menu Device>Settings

Puis choisissez l’orientation de l’écran et l’encodage.

Création d’une interface graphique

Nous allons utiliser l’éditeur graphique Nextion pour créer notre interface graphique. Dans cet exemple, nous allons ajouter:

  • Une image
  • Quelques textes pour le titre ou pour afficher des données
  • Un bouton qui modifie l’interface
  • Un bouton qui envoie une commande sur le port série
  • Un timer qui rafraichit l’interface
  • Une variable qui stocke une donnée reçue du port série

Pour ajouter un objet cliquez sur l’objet désiré dans la fenêtre Toolbox et l’objet sera inséré automatiquement dans la fenêtre Display. Vous pouvez ensuite configurer l’objet dans la fenêtre Attributes

Vous pouvez télécharger le fichier IHM à importer dans Nextion Editor. Avec cet exemple, vous serez en mesure de créer des interfaces bien plus complexes.

Ajouter une image

Pour ajouter une image, il faut d’abord importer une image dans le logiciel à l’aide de la touche (+) de la fenêtre “Picture”

Vous pouvez ensuite insérer un objet Picture dans la fenêtre Display puis sélectionnez l’image en appuyant sur l’attribut pic > browse…

N.B.: Ajouter une image aux dimensions désirées

Ajouter du texte

Avant de rajouter un objet text, il vous faut générer une police de caractère. Ceci peut se faire dans Tools> Generate Font

Une fois la police générée, vous pouvez la sélectionnez dans l’attribut font de l’objet Text. Vous pouvez ensuite modifier le texte dans l’attribut txt (Attention au nombre de caractère maximum txt_maxl).

Nous ajoutons quatre objets texte:

  • Le label titre
  • l’état de la LED
  • le label “Analog Val”
  • la valeur analogique reçue du micrcontrôleur

Ajouter un bouton

Une fois l’objet bouton ajouté à l’interface, vous pouvez régler dans les attributs:

  • le texte affiché
  • la couleur lorsque le bouton est pressé
  • la couleur lorsque la couleur est relâché

Dans la fenêtre Events, il est possible de programmer ce que l’écran va faire lorsque le bouton est pressé ou relâché. Il y a un certain nombre d’instructions que vous pouvez utiliser dans ces fenêtres. Notamment:

  • Modifier des éléments de l’interface (Ex: t0.txt=”Pressed”)
  • Envoyer l’identifiant du bouton via le port série
  • Envoyer une autre commande avec prints
  • Bouton b0
  • Bouton b1

Dans l’onglet Touch Release Event , nous écrivons la commande suivante qui est une commande prédéfinie de la librairie Nextion et correspondant à trigger1()

printh 23 02 54 01

  • Bouton b2

Pour le bouton b2, nous utilisons la même commande mais pour trigger2()

printh 23 02 54 02

Ajouter un timer

L’objet Timer permet d’exécuter un code régulièrement. Particulièrement utile pour récupérer des données provenant du port série et mettre à jour l’interface.

Dans l’onglet Timer event, nous utilisons la fonctions covx pour convertir la valeur de la variable en string et l’écrire dans le texte t4

covx analog0.val,t4.txt,0,0

Charger le programme sur l’écran Nextion

Brancher la carte Nextion au convertisseur USB-TTL

Pour charger l’interface, appuyez sur Compile puis Upload

Connexion de l’écran Nextion au microcontrôleur Arduino

Pour téléverser le code Arduino sur la carte, les pin RX et TX doivent être déconnectées de l’écran.

Utilisation de la librairie Nextion.h

Il existe différentes librairies que vous pouvez utiliser pour gérer un écran Nextion

Nous utilisons EasyNextion

Dans Nextion Editor, nous définissons un event release “printh 23 02 54 01” pour le bouton ON et un event release “printh 23 02 54 02” pour le bouton Off.

A l’aide de la fonction NextionListen(), ces commandes vont activer les fonctions trigger1 et trigger2.

#include "EasyNextionLibrary.h"

#define baudrate 9600

EasyNex myNex(Serial);

//Variables
uint16_t analog;
bool ledstate;

const int REFRESH_TIME = 100;
unsigned long refresh_timer = millis();

void setup() {
  myNex.begin(baudrate);

  pinMode(A0, INPUT);
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  myNex.NextionListen();

  if ((millis() - refresh_timer) > REFRESH_TIME) {
    analog = analogRead(A0);
    myNex.writeNum("analog0.val", analog);

    refresh_timer = millis();
  }
}

//`printh 23 02 54 XX` , where `XX` the id for the triggerXX() in HEX.
void trigger1() {
  digitalWrite(LED_BUILTIN, HIGH);
  myNex.writeStr("t2.txt", "LED ON");
}

void trigger2() {
  digitalWrite(LED_BUILTIN, LOW);
  myNex.writeStr("t2.txt", "LED OFF");
}

Résultats

Bonus: Nextion Editor Simulator

Nextion propose un simulateur permettant de tester les interactions entre l’écran et microcontrôleur et l’interface graphique. Ce simulateur est accessible en appuyant sur “Debug”

Sources

Programmer un ESP32/ESP8266 avec MicroPython et VS Code

Programmer un ESP32/ESP8266 avec MicroPython et VS Code

Nous allons voir comment programmer une carte ESP32 ou ESP8266 en MicroPython avec Visual Studio Code qui est un IDE de plus en plus utilisé pour la programmation dans différent langage. Il est possible de programmer la carte ESP32/ESP8266 avec VS Code et l’extension Platform.IO en C/C++. Il peut être intéressant d’utiliser le même outil pour programmer le microcontrôleur avec MicroPython

Présentation de Visual Studio Code

Visual Studio Code est un éditeur de code extensible et léger développé par Microsoft. VsCode apporte de nombreuses fonctionnalités comparé à l’IDE Arduino:

  • Complétion automatique
  • Mise en évidence de la syntaxe
  • Fonctionnalité de débogage
  • Programmation dans plusieurs langages (C++, C#, Java, Python, PHP, etc.)
  • Gestion de projet
  • Gestion de dépôt Git
  • Etc.

Il est en source libre et disponible sur les plateformes Windows, Linux et MacOS.

Installation du Firmware MicroPython sur la carte ESP

Pour installer le firmware MicroPyhton sur la carte ESP32 ou ESP8266, vous pouvez utiliser un des deux tutoriels suivants:

Installation de Visual Studio Code

Allez sur la page de téléchargement de Visual Studio Code et téléchargez la version correspondante à votre OS.

Lancez l’installateur et suivez la procédure

Installation de Pymakr

Pour pouvoir programmer en Python, il faut installer l’extension correspondante.

Cliquez sur l’icône “Manage” (roue dentée) en bas à gauche et sélectionnez Extensions (ou Ctrl+Shift+X)

Recherchez ensuite et sélectionnez “Pymakr”. Puis installez, l’extension.

Cliquez sur l’icône Pymakr et sélectionner “create project”

Cliquez ensuite ADD DEVICE puis sélectionnez l’ESP connecté à l’ordinateur.

Une fois l’appareil sélectionné, vous pouvez le connecter en appuyant sur l’éclair

Installation de Node.js

Il se peut que sur l’étape précédente, l’étape de création de projet en cliquant “create project”, ne marche pas correctement.
Dans ce cas là, pour régler ce souci il suffit d’installer un autre logiciel tiers nommé Node.js qui permettra le fonctionnement correcte de l’extension Pymakr. Rendez vous sur le site https://nodejs.org/en/ pour télécharger la version Windows (x64). Une fois téléchargé, vous pouvez exécuter l’installateur de l’application.

On clique 2 fois sur “next” en acceptant les conditions.

On choisi l’emplacement de l’application ( on peut laisser l’emplacement par défaut dans le dossier “Program Files” de votre ordinateur” ) puis on clique sur “Next”.

Ici, il n’est pas nécessaire de changer quoi que ce soit donc on clique sur “Next”.

Ici, il n’est pas nécessaire d’installer des outils supplémentaires tel que Chocolatey comme suggéré, alors on clique sur “Next”.

On finit par cliquer sur “Install”, en autorisant l’installation avec les droits d’administrateur si l’ordinateur vous le demande.

L’installation de Node.js est terminé, on peut cliquer sur “Finish”.

On peut passer à la partie programmation en MicroPython sur Visual Studio Code. Cependant, assurez d’avoir bien fermé et redémarré Visual Studio Code pour qu’il prenne en compte Node.js.

Programmer la carte ESP32/ESP8266 en MicroPython

Une fois le projet configuré, vous pouvez lancer un terminal Pymakr dans l’onglet TERMINAL, cliquez sur la flèche à droite du “+” pour sélectionnez la console Pymakr

Appuyer sur “Entrée” pour établir la connexion “select/COM8”

Vous pouvez à présent programmer en MicroPython directement sur l’ESP

Lancer un script Python sur votre ESP32/ESP8266

Il y a deux fichiers Python dans le projet:

  • boot.py qui s’exécute au démarrage du microcontrôleur
  • main.py qui contient le code principal et s’exécute juste après boot.py

Ouvrez le fichier main.py du projet et copier le code suivant

import sys
import time

def main():
    print("MicroPython program is running ")
    time.sleep(0.5)

if __name__=="__main__":
  print("{} initialized".format(sys.platform))
  while(1):
    try:
      main()
    except KeyboardInterrupt:
      print("Program stopped")
      sys.exit(0)

Vous pouvez ensuite téléverser le code sur la carte en utilisant le bouton “Sync project to device”

Après un reset de la carte avec le bouton EN, vous pouvez voir que le code s’exécute correctement

Sources

Communication UDP entre Raspberry Pi et ESP32

Communication UDP entre Raspberry Pi et ESP32

Nous allons voir dans ce tutoriel comment faire communiquer un Raspberry Pi et un ESP32 avec le protocole UDP. Lorsque des appareils sont connectés au même réseau Wifi, il peuvent communiquer très simplement en échangeant des paquets de données à l’aide du protocole UDP. Le Raspberry Pi et l’ESP32 ayant tous deux une connectivité Wifi embarqué, il est assez simple de mettre en place un protocole UDP entre les deux.

Dans cet exemple, le Raspberry Pi aura le rôle de serveur et l’ESP32 de client.

N.B.: Ce tutoriel est facilement transposable pour un ESP8266 (code client en bonus à la fin de l’article)

Matériel

  • Ordinateur
  • Raspberry Pi
  • NodeMCU ESP32 x1 ou plus (ou ESP8266)
  • Câble USB A Mâle/Micro B Mâle

Code Raspberry Pi Server

Pour communiquer à l’aide du protocole UDP, nous allons simplement créer le serveur udp qui pourra lire et écrire sur le port localPort. La librairie socket permet d”ouvrir une connexion entre deux appareils.

Le constructeur socket attend deux paramètres, la famille d’adresse (ici adresses internet) et le type de socket (ici UDP)

sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) ## Internet,UDP

Le socket doit être lié un port de la machine sock.bind((hostname, localPort)). le fait de mettre un caractère vide à la place d’hostname (équivalent à 0.0.0.0) permet de lié le socket à toutes les interfaces locales.

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

#Libraries
import socket	#https://wiki.python.org/moin/UdpCommunication

#Parameters
localPort=8888
bufferSize=1024

#Objects
sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)  ## Internet,UDP

# function init 
def init():
	sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
	sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) #enable broadcasting mode
	sock.bind(('', localPort))
	print("UDP server : {}:{}".format(get_ip_address(),localPort))

# function main 
def main():
	while True:
		data, addr = sock.recvfrom(1024) # get data
		print("received message: {} from {}\n".format(data,addr))
    
		sock.sendto("RPi received OK",addr)  # write data
  

# function get_ip_address 
def get_ip_address():
	"""get host ip address"""
	ip_address = '';
	s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
	s.connect(("8.8.8.8",80))
	ip_address = s.getsockname()[0]
	s.close()
	return ip_address




if __name__ == '__main__':
	init()
	main()

N.B.: La fonction get_ip_address() implémente une méthode pour obtenir l’adresse IP du Raspberry Pi en ouvrant un socket temporairement. Il est tout à fait possible d’utiliser la librairie os avec la commande ifconfig.

Notez l’adresse IP et le port du Raspberry Pi pour les copier dans le code client:

  • Adresse IP : 192.168.1.46
  • Port : 8888

Code ESP32 UDP Client

Dans le code client, nous allons nous connecter au serveur en utilisant l’adresse IP et le port précédemment notée (ici 192.168.1.46:8888)

N.B.: N’oubliez pas de modifier les valeurs de ssid et password afin que l’ESP32 se connecte au même réseau que le Raspberry Pi

#include <WiFi.h>
#include <WiFiUdp.h>
WiFiUDP udp;

char packetBuffer[255];
unsigned int localPort = 9999;
char *serverip = "192.168.1.46";
unsigned int serverport = 8888;

const char *ssid = "******";
const char *password = "********";

void setup() {
  Serial.begin(115200);
  // Connect to Wifi network.
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500); Serial.print(F("."));
  }
  udp.begin(localPort);
  Serial.printf("UDP Client : %s:%i \n", WiFi.localIP().toString().c_str(), localPort);
}

void loop() {
  int packetSize = udp.parsePacket();
  if (packetSize) {
    Serial.print(" Received packet from : "); Serial.println(udp.remoteIP());
    int len = udp.read(packetBuffer, 255);
    Serial.printf("Data : %s\n", packetBuffer);
    Serial.println();
  }
  delay(500);
  Serial.print("[Client Connected] "); Serial.println(WiFi.localIP());
  udp.beginPacket(serverip, serverport);
  char buf[30];
  unsigned long testID = millis();
  sprintf(buf, "ESP32 send millis: %lu", testID);
  udp.printf(buf);
  udp.endPacket();
}

Résultat

Lorsque les deux cartes se connectent au Wifi, nous pouvons voir que des informations s’échangent entre les deux cartes. Il est alors possible de piloter un appareil connecté au client à partir du serveur ou inversement.

N.B.: Généralement, l’affectation des adresses IP se fait automatiquement. Comme les adresses sont définies en “dur” dans le code, il est préférable de configurer vos appareils afin qu’ils aient une adresse IP statique et non dynamique. Cela vous évitera d’avoir à modifier le code à chaque mise sous tension.

Bonus: Communication UDP entre Raspberry Pi et deux clients ESP32/ESP8266

Une fois la communication établie, il est possible de rajouter plusieurs clients. Dans cet exemple, nous rajoutons un client ESP8266

Code client UDP ESP8266

Il est facile de transformer le code pour ESP32 pour l’adapter à l’ESP8266 en modifiant le nom de la librairie Wifi. Nous modifions également la valeur du port 9696

N.B.: Lorsque vous rajoutez un client, veillez à ce qu’il ait un couple adresse/port unique.

#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
WiFiUDP udp;

char packetBuffer[255];
unsigned int localPort = 9696;
char *serverip = "192.168.1.46";
unsigned int serverport = 8888;

const char *ssid = "******";
const char *password = "******";


void setup() {
  Serial.begin(115200);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500); Serial.print(F("."));
  }
  udp.begin(localPort);
  Serial.printf("UDP Client : %s:%i \n", WiFi.localIP().toString().c_str(), localPort);
}


void loop() {
  int packetSize = udp.parsePacket();
  if (packetSize) {
    Serial.print(" Received packet from : "); Serial.println(udp.remoteIP());
    int len = udp.read(packetBuffer, 255);
    Serial.printf("Data : %s\n", packetBuffer);
    Serial.println();
  }
  delay(500);
  Serial.print("[Client Connected] "); Serial.println(WiFi.localIP());
  udp.beginPacket(serverip, serverport);
  char buf[30];
  unsigned long testID = millis();
  sprintf(buf, "ESP8266 send millis: %lu", testID);
  udp.printf(buf);
  udp.endPacket();
}

Résultats

Nous pouvons voir que les adresses et les valeurs échangées par l’ESP32 et l’ESP8266 sont différentes.

Applications

  • Créer un réseau d’ordinateurs et/ou de microcontrôleurs pour gérer différents appareils ou capteurs

Sources