fbpixel
Comunicación BLE con ESP32

Comunicación BLE con ESP32

En este tutorial, vamos a aprender a activar y gestionar Bluetooth Low Energy (BLE) en un ESP32 utilizando el lenguaje de programación Arduino.

Bluetooth Low Energy es una versión de baja energía de Bluetooth que permite enviar pequeños paquetes de datos a intervalos regulares.

Equipamiento

  • Un módulo ESP32 (Bluetooth+Wifi a bordo)
  • Un ordenador con Python instalado o un smartphone
  • Cable USB para conexión ESP32-ordenador

Configuración del entorno y del IDE

Para programar tu ESP32 con el IDE de Arduino, puedes seguir este tutorial anterior.

Communication Série via BLE

La comunicación BLE debe configurarse con un cierto número de direcciones (UIIDs) que son como registros de memoria en los que podemos leer y escribir.

//https://github.com/espressif/arduino-esp32/tree/master/libraries/BLE

#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>

// See the following for generating UUIDs:
// https://www.uuidgenerator.net/

#define SERVICE_UUID        "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"


class MyCallbacks: public BLECharacteristicCallbacks {
    void onWrite(BLECharacteristic *pCharacteristic) {
      std::string value = pCharacteristic->getValue();

      if (value.length() > 0) {
        Serial.println("*********");
        Serial.print("New value: ");
        for (int i = 0; i < value.length(); i++)
          Serial.print(value[i]);

        Serial.println();
        Serial.println("*********");
      }
    }
};

void setup() {
  Serial.begin(115200);

  Serial.println("1- Download and install an BLE Terminal FREE");
  Serial.println("2- Scan for BLE devices in the app");
  Serial.println("3- Connect to ESP32BLE");
  Serial.println("4- Go to CUSTOM CHARACTERISTIC in CUSTOM SERVICE and write something");

  BLEDevice::init("ESP32BLE");
  BLEServer *pServer = BLEDevice::createServer();

  BLEService *pService = pServer->createService(SERVICE_UUID);

  BLECharacteristic *pCharacteristic = pService->createCharacteristic(
                                         CHARACTERISTIC_UUID,
                                         BLECharacteristic::PROPERTY_READ |
                                         BLECharacteristic::PROPERTY_WRITE
                                       );

  pCharacteristic->setCallbacks(new MyCallbacks());

  pCharacteristic->setValue("Hello World");
  pService->start();

  BLEAdvertising *pAdvertising = pServer->getAdvertising();
  pAdvertising->start();

  Serial.print("Server address:");
  Serial.println(BLEDevice::getAddress().toString().c_str());
}

void loop() {
  // put your main code here, to run repeatedly:
  delay(2000);
}

N.B.: es posible recuperar la dirección MAC del ESP32 utilizando la función BLEDevice::getAddress().toString().c_str().

Emparejamiento

Una vez configurado el módulo como desee, puede emparejar el ESP32 con el sistema de su elección como cualquier otro dispositivo Bluetooth. Seleccione el nombre de la lista de dispositivos detectados (nombre ESP32BLE)

Prueba de la comunicación BLE con el terminal BLE

Vamos a probar la comunicación BLE utilizando la aplicación BLE Terminal.

El mensaje se intercambia entre el teléfono y el ESP32 a través de Bluetooth LE

Comunicación bidireccional entre el dispositivo y el ESP32BLE

Este es el código utilizado para modificar el valor de la característica utilizando el monitor serie. El dispositivo conectado y el monitor serie modifican el valor de la característica.

#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>

// See the following for generating UUIDs:
// https://www.uuidgenerator.net/

#define SERVICE_UUID        "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"

BLECharacteristic *pCharacteristic = NULL;

std::string msg;

class MyCallbacks: public BLECharacteristicCallbacks {
    void onWrite(BLECharacteristic *pCharacteristic) {
      std::string value = pCharacteristic->getValue();

      if (value.length() > 0) {
        Serial.println("*********");
        Serial.print("New value: ");
        for (int i = 0; i < value.length(); i++)
          Serial.print(value[i]);

        Serial.println();
        Serial.println("*********");
      }
    }
};

void setup() {
  Serial.begin(115200);

  Serial.println("1- Download and install an BLE Terminal Free");
  Serial.println("2- Scan for BLE devices in the app");
  Serial.println("3- Connect to ESP32BLE");
  Serial.println("4- Go to CUSTOM CHARACTERISTIC in CUSTOM SERVICE and write something");

  BLEDevice::init("ESP32BLE");
  BLEServer *pServer = BLEDevice::createServer();

  BLEService *pService = pServer->createService(SERVICE_UUID);

  pCharacteristic = pService->createCharacteristic(
                                         CHARACTERISTIC_UUID,
                                         BLECharacteristic::PROPERTY_READ |
                                         BLECharacteristic::PROPERTY_WRITE
                                       );

  pCharacteristic->setCallbacks(new MyCallbacks());

  pCharacteristic->setValue("Hello World");
  pService->start();

  BLEAdvertising *pAdvertising = pServer->getAdvertising();
  pAdvertising->addServiceUUID(SERVICE_UUID);
  pAdvertising->setScanResponse(true);
  pAdvertising->setMinPreferred(0x06);  // functions that help with iPhone connections issue
  pAdvertising->setMinPreferred(0x12);
  pAdvertising->start();

  Serial.print("Server address:");
  Serial.println(BLEDevice::getAddress().toString().c_str());
}

void loop() {
  readSerialPort();

  //Send data to slave
  if(msg!=""){
    pCharacteristic->setValue(msg);
    msg="";
  }
  
  delay(2000);
}

void readSerialPort(){
 while (Serial.available()) {
   delay(10);  
   if (Serial.available() >0) {
     char c = Serial.read();  //gets one byte from serial buffer
     msg += c; //add to String
   }
 }
 Serial.flush(); //clean buffer
}

En la práctica, será preferible utilizar un servicio para escribir y otro para leer.

Comunicación entre ESP32 y Python a través de BLE

Puedes gestionar la comunicación Bluetooth Low Energy desde tu PC.

Para ello, instale el paquete Bleak

python -m pip install bleak

Detectar dispositivos bluetooth

import asyncio
from bleak import BleakScanner

async def main():
	target_name = "ESP32BLE"
	target_address = None

	devices = await BleakScanner.discover()
	for d in devices:
		print(d)
		if target_name == d.name:
			target_address = d.address
			print("found target {} bluetooth device with address {} ".format(target_name,target_address))
			break
asyncio.run(main())

Salida

C1:2E:C6:8E:47:E8: None
3C:61:05:31:5F:12: ESP32BLE
found target ESP32BLE bluetooth device with address 3C:61:05:31:5F:12

Conexión y comunicación con el ESP32

Aquí tienes un script en Python para conectarte automáticamente al dispositivo ESP32 BLE desde un PC. Para comunicarse con el dispositivo BLE, es importante conocer el UUID de los diferentes servicios. Definimos los UUID como los definidos en el código del ESP32

import asyncio
from bleak import BleakScanner
from bleak import BleakClient

async def main():
	target_name = "ESP32BLE"
	target_address = None

	SERVICE_UUID=        "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
	CHARACTERISTIC_UUID= "beb5483e-36e1-4688-b7f5-ea07361b26a8"

	devices = await BleakScanner.discover()
	for d in devices:
		print(d)
		if target_name == d.name:
			target_address = d.address
			print("found target {} bluetooth device with address {} ".format(target_name,target_address))
			break

	if target_address is not None:		
		async with BleakClient(target_address) as client:
			print(f"Connected: {client.is_connected}")
				
			while 1:
				text = input()
				if text == "quit":
					break

				await client.write_gatt_char(CHARACTERISTIC_UUID, bytes(text, 'UTF-8'), response=True)
				
				try:
					data = await client.read_gatt_char(CHARACTERISTIC_UUID)
					data = data.decode('utf-8') #convert byte to str
					print("data: {}".format(data))
				except Exception:
					pass
				
			
	else:
		print("could not find target bluetooth device nearby")


asyncio.run(main())

Aplicación

Fuentes

Descripción general del microcontrolador NodeMCU ESP32

Descripción general del microcontrolador NodeMCU ESP32

NodeMCU ESP32 es un microcontrolador con módulos WiFi y Bluetooth integrados. Es muy fácil de usar, ligero y tiene mayor memoria y capacidad de cálculo que un Arduino. Esto la convierte en una placa ideal para aprender a programar y desarrollar objetos o servidores conectados.

Características del microcontrolador

El microcontrolador NodeMCU ESP32 utiliza el microprocesador ESP-WROOM-32 (Tensilica Xtensa LX6). Este procesador funciona a una frecuencia de reloj de 240 MHz y dispone de 520 kB de RAM, 448 kB de EEPROM y 4000 kB de memoria Flash (para programación y registro de datos).

  • CPU ESP-WROOM-32 (Tensilica Xtensa LX6)
  • Voltage : 3.3V
  • Flash : 4000 kB
  • RAM : 520 kB
  • EEPROM : 448 kB
  • Clock speed : 240MHz
  • WiFi : Yes
  • Bluetooth : Yes
  • SD : No

El microcontrolador tiene un chip WiFi que le permite conectarse a la red local, crear un servidor o crear su propia red para que otros dispositivos puedan conectarse a ella. El microcontrolador tiene un chip Bluetooth que le permite interactuar con otros dispositivos.

Alimentación

El microcontrolador NodeMCU ESP32 funciona en un rango de tensión de 7-12V gracias a su regulador de tensión integrado, mientras que el microprocesador funciona a una tensión de 3,3V. En funcionamiento normal, el microcontrolador consume hasta 45mA (si no recibe alimentación) y puede aceptar una corriente máxima de 40mA en cada uno de sus pines IO.

Pinout

  • Analog I/O : 15 (2, 4, 12, 13, 14, 15, 25, 26, 27, 32, 33, 34, 35, 36, 39)
  • Digital I/O : 4 (all except 6 to 11)
  • Pines PWM: 16 (2, 4, 5, 6, 12, 13, 14, 15, 18, 19, 21, 25, 26, 27, 32, 33)
  • Comunicación Serial: 9 (1, 2, 3, 4, 5, 12, 13, 14, 15)
  • Comunicación I2C: 1 ((’21’, ’22’))
  • Comunicación SPI: 1 ((‘5′, ’18’, ’19’, ’23’))
  • Comunicación I2S: 1 ((’26’, ’25’, ’33’))
  • Toque: 9 (2, 4, 12, 13, 14, 15, 27, 32, 33)
  • Interrupción: 26 (0, 1, 2, 4, 5, 7, 12, 13, 14, 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 27, 32, 33, 34, 35, 36, 39)

Código básico e identificación de pines

const int analogPin=34; // 2, 4, 12, 13, 14, 15, 25, 26, 27, 32, 33, 34, 35, 36, 39
const int digitalInPin=2; // all except 6 to 11
const int digitalOutPin=4; // all except 6 to 11 and 34, 35, 36 and 39 inputs only
const int pwmPin=3; // broches 2, 4, 5, 6, 12, 13, 14, 15, 18, 19, 21, 25, 26, 27, 32, 33

int analogVal=0;
int digitalState=LOW;
int pwmVal=250;

// setting PWM properties
const int freq = 5000;
const int ledChannel = 0;
const int resolution = 8;

void setup() {
  Serial.begin(115200); 
  
  pinMode(analogPin,INPUT); // broches 0-13 et 14-19, Argument OUTPUT, INPUT
  pinMode(digitalInPin,INPUT);
  pinMode(digitalOutPin,OUTPUT);

  ledcSetup(ledChannel, freq, resolution);
  ledcAttachPin(pwmPin, ledChannel);
}

void loop() {
 analogVal=analogRead(analogPin); //  return int
 digitalState=digitalRead(digitalInPin); //  return boolean
 digitalWrite(digitalOutPin,HIGH); // valeur LOW(0) ou HIGH(1)
 ledcWrite(ledChannel, pwmVal);// valeur 0-255
}

Para obtener más información sobre el uso de los pines, visite el sitio ESP32 Pinout Reference.

Resumen de características

Microcontrôleur
Nom: ESP32
Marque: Espressif
Caractéristiques
CPU: ESP-WROOM-32 (Tensilica Xtensa LX6)
Tension d’alimentation : 7-12V
Tension logic: 3.3V
E/S digitales: 14
Entrées analogiques: 6
Flash: 4000kB
SRAM: 520kB
EEPROM: 448kB
Fréquence d’horloge: 240 MHz
Wifi: Yes
Bluetooth: Yes
SD card: No
Touch: Yes
UART/SPI/I2C/I2S: Yes/Yes/Yes/Yes

Cómo empezar

Desarrollo de un monitor UDP con Python

Desarrollo de un monitor UDP con Python

En este proyecto, vamos a crear un monitor de comunicación de red UDP usando Python (PyQt). Cuando desarrolles un proyecto con Arduino, Raspberry Pi o cualquier otro microcontrolador, seguramente necesitarás crear una interfaz gráfica para gestionar el sistema (depurar, observar medidas, lanzar acciones, etc.). Hay muchas herramientas disponibles para crear interfaces gráficas. En este proyecto, vamos a crear un monitor de comunicación de red utilizando PyQt(PySide2).

Objetivo

Para este proyecto, queremos crear una interfaz gráfica en Python para Windows que se comporte como un monitor de comunicaciones UDP. Para ello, necesitaremos realizar las siguientes funciones

  • Casilla de introducción de la dirección IP
  • Zona portuaria de entrada
  • Botón de conexión
  • Zona de escritura por encargo
  • Botón Enviar
  • Consola que muestra los datos recibidos

Recuperar la dirección IP de la máquina

En la máquina receptora, recupere la dirección IPv4 (en nuestro caso 192.168.1.67)

ipconfig #sur la machine windows

o

ip addr #sur machine linux

Aplicación de monitorización de comunicaciones UDP

En primer lugar, vamos a crear la ventana de la aplicación, que llamaremos AcApp y que será la base de nuestra aplicación.

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

Creación de widgets de gestión de comunicaciones UDP

Crearemos entonces una clase que contenga todos los Widgets que necesitamos para crear y gestionar el monitor de comunicación UDP (QLineEdit,QTextEdit,QButton, etc.). Este Widget se inicializará con la clase que gestionará la comunicación

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

    def __init__(self,parent=None):
        super(EthernetInterface,self).__init__(parent)
        self.grid=QGridLayot()
        self.setLayot(self.grid)
        self.defineWidgets()
        self.model=None 
        if self.model is not None: self.model.debugSignal.connect(self.read)
   
    def defineWidgets(self):
        #self.setStyleSheet("""QGropBox{backgrond-color:white;border: 1px solid green;border-radius: 4px;}
        #QGropBox::title {padding:1 5px;}""")
               
        #groopbox widget container
        self.grp=QGropBox(self)
        self.grp.setTitle("Connection Configuration")
       
        self.fields=QGridLayot()
        self.grp.setLayot(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("&gt;&gt; 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("&gt;&gt; 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())

Sustituya ex = AcApp(__title__) por ex = AcApp(__title__,EthernetInterface) en la función main()

Una vez colocados nuestros widgets, vamos a empezar a utilizarlos.

Creación de un QThread para gestionar la comunicación UDP

Para no bloquear la interfaz gráfica cuando se reciben o envían paquetes, vamos a crear un QThread para gestionar la comunicación 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 &gt; 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()

Cliente UDP Código Python

Para probar su interfaz, puede ejecutar un código en el mismo ordenador utilizando la dirección 127.0.0.1 (dirección local). El siguiente código creará un socket y enviará el valor de un contador.

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



conter=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(conter)), (HOST,PORT))

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

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

Código ESP8266 Cliente UDP

El código del ESP8266 para la comunicación UDP es bastante sencillo. Definimos la comunicación de red utilizando el protocolo UDP y devolvemos el valor dado por la función millis().

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

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

// Set yor 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.: Para este proyecto, estamos utilizando un esp8266, pero puedes adaptar el código a cualquier dispositivo que utilice el protocolo UDP.

Resultados

Para utilizar el código de cliente Python, introduzca la dirección IP 127.0.0.1

Para probar la comunicación con el ESP8266, utilice la dirección IP de su ordenador (aquí, 192.168.1.67).

Hemos creado un monitor de comunicación UDP usando Python que puede interactuar con dispositivos remotos como ESP8266, ESP32, Raspberry Pi u otros ordenadores. Ahora puedes mejorar la interfaz para adaptarla a tus necesidades.

Código completo

#!/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 &gt; 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=QGridLayot()
        self.setLayot(self.grid)
        self.defineWidgets()
        self.model=EthModel()#self.model=None
        self.model.debugSignal.connect(self.read)
   
    def defineWidgets(self):
        #self.setStyleSheet("""QGropBox{backgrond-color:white;border: 1px solid green;border-radius: 4px;}
        #QGropBox::title {padding:1 5px;}""")
               
        #groopbox widget container
        self.grp=QGropBox(self)
        self.grp.setTitle("Connection Configuration")
       
        self.fields=QGridLayot()
        self.grp.setLayot(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("&gt;&gt; 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("&gt;&gt; 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()

Fuentes

Instalar PlatformIO IDE en Visual Studio Code

Instalar PlatformIO IDE en Visual Studio Code

En este tutorial vamos a configurar Visual Studio Code para que pueda ejecutar C

Hardware

  • Ordenador
  • Placa Arduino UNO u otra
  • Cable USB para conectar la placa Arduino al PC

Instalación de Visual Studio Code y PlatformIO IDE

Siga los pasos de instalación para descargar .Net 6.0

A continuación, abra Visual Studio Code.

Busque PlatformIO IDE en la pestaña “Extensiones” de la columna izquierda de Visual Studio Code y haga clic en “Instalar”.

Una vez instalado, cierre y vuelva a iniciar Visual Studio Code para que el software se configure correctamente con PlatformIO IDE. A continuación, aparecerá la página de inicio del IDE.

Empezar su primer proyecto en C con PlatformIO IDE

Para ello, haga clic en “+ Nuevo proyecto” y rellene la ventana que aparece.

Aquí, como ejemplo, he elegido una placa Arduino Uno y el framework también será del tipo Arduino, lo que significa que las funciones setup() y loop() se encontrarán en el archivo principal main. Crear un proyecto puede llevar un poco de tiempo.

En la parte izquierda del espacio de trabajo de nuestro proyecto, verás una serie de subcarpetas. La carpeta “src” contiene los scripts fuente del proyecto, incluyendo el archivo principal “main.cpp”, en el que escribiremos nuestro script para programar nuestra placa Arduino Uno. En este framework pre-construido de Arduino, notarás que en la cabecera del programa la línea de comandos “

Empezar con un programa C en PlatformIO IDE

Para probar tu primer proyecto en C en PlatformIO IDE, cargarás un pequeño programa para hacer parpadear un LED en la placa Arduino Uno y obtener información sobre el estado del LED a través del enlace serie en el 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

Una vez que hayas copiado y pegado tu programa, puedes subirlo. Para subir el programa, hay accesos directos que puedes activar haciendo clic sobre ellos en la parte inferior de Studio Visual Code :

  1. compilar el programa ;
  2. compilar, limpiar y descargar el programa en la placa Arduino (el PUERTO USB utilizado se detecta automáticamente);
  3. limpiar el Terminal y la tarjeta microcontroladora conectada (borrar el script grabado en ella);
  4. probar el programa ;
  5. abrir un monitor para el Serial Link y recibir (o enviar) datos de la tarjeta. Cuando este “Monitor Serial” está abierto, es imposible cargar un programa. Por lo tanto, antes de cargar un nuevo programa, es necesario cerrar el monitor haciendo clic una vez en el terminal y, a continuación, pulsando Ctrl + C ;
  6. Abre un Terminal.

Una vez que haya hecho clic en cargar, debería obtener un retorno en un terminal confirmando que la compilación y la carga se han realizado correctamente, con el siguiente aspecto:

A continuación, haga clic en el comando para abrir un Monitor para establecer el enlace serie.

A continuación, puede observar el estado de los LED en directo en el monitor.

Cómo instalar una biblioteca externa en Visual Studio Code para el IDE PlatformIO

Eso es todo. Primero descarga tu biblioteca externa. Una vez que tengas el archivo .zipp, tienes que extraerlo (o copiar y pegar desde la carpeta de la librería descomprimida) en esa carpeta (en este caso, la librería que usaremos como ejemplo es ServoLib, que se usa para facilitar el control de servomotores):

Puede acceder a la carpeta “lib” de su proyecto, diseñada para alojar bibliotecas externas, a través de Visual Studio Code.

Una vez instalada una biblioteca en su proyecto, deberá incluirla en la cabecera del programa, como :

Ahora ya tienes todo lo básico que necesitas para empezar a utilizar el IDE PlatformIO para programar en C.

Uso de una pantalla Nextion con Arduino

Uso de una pantalla Nextion con Arduino

La pantalla Nextion es una de las mejores soluciones para crear una interfaz gráfica para controlar tu proyecto Arduino. La interfaz más conocida para interactuar con un Arduino es la pantalla LCD con unos pocos botones y potenciómetros a costa de E/S y sobrecarga de código Arduino. En este tutorial, veremos cómo configurar una pantalla Nextion y cómo utilizarla con Arduino.

Hardware

  • Arduino (u otra placa con puerto UART)
  • Pantalla Nextion
  • Adaptador USB-TTL 5V
  • 4x cable Dupont con conector JST

Presentación de la tarjeta Nextion

La interfaz básica para controlar un Arduino, y presente en todos los kits, es la pantalla LCD con unos pocos botones y potenciómetros, que existe en forma de LCD Shield. También existen Shields con pantallas táctiles, pero éstas utilizan todas las E/S del Arduino y sobrecargan el código del Arduino. Una solución es utilizar una pantalla Nextion, que contiene su propio programa y se comunica con cualquier microcontrolador a través del puerto serie.

N.B.: Es posible crear una interfaz gráfica en un PC o crear una interfaz web para controlar el proyecto añadiendo un módulo de comunicación inalámbrica.

Instalación y presentación del editor Nextion

Descargar e instalar el editor Nextion

Al abrir un nuevo proyecto o archivo, el software le pide el modelo de pantalla (en nuestro caso NX4832K035_011). Puede cambiar la selección en el menú Dispositivo>Configuración.

A continuación, elige la orientación de la pantalla y la codificación.

Crear una interfaz gráfica

Vamos a utilizar el editor gráfico Nextion para crear nuestra interfaz gráfica. En este ejemplo, vamos a añadir:

  • Una imagen
  • Algunos textos para el título o para mostrar datos
  • Un botón que cambia la interfaz
  • Un botón que envía un comando al puerto serie
  • Un temporizador que refresca la interfaz
  • Una variable que almacena los datos recibidos del puerto serie

Para añadir un objeto, haga clic en el objeto deseado en la ventana Caja de herramientas y el objeto se insertará automáticamente en la ventana Visualización. A continuación, puede configurar el objeto en la ventana Atributos

Puede descargar el archivo GUI para importarlo a Nextion Editor. Con este ejemplo, podrás crear interfaces mucho más complejas.

Añadir una imagen

Para añadir una imagen, impórtela primero en el programa con el botón (+) de la ventana “Imagen”.

A continuación, puede insertar un objeto Imagen en la ventana Visualización y seleccionar la imagen pulsando el atributo pic > examinar….

N.B.: Añadir una imagenaux dimensions désirées

Añadir texto

Antes de añadir un objeto de texto, es necesario generar una fuente. Esto puede hacerse en Herramientas> Generar fuente

Una vez generada la fuente, puede seleccionarla en el atributo font del objeto Texto. A continuación, puede modificar el texto en el atributo txt (tenga en cuenta el número máximo de caracteres txt_maxl).

Vamos a añadir cuatro objetos de texto:

  • La etiqueta del título
  • el estado del LED
  • la etiqueta “Analog Val
  • el valor analógico recibido del microcontrolador

Añadir un botón

Una vez añadido el objeto botón a la interfaz, puede ajustar sus atributos:

  • el texto mostrado
  • el color al pulsar el botón
  • el color cuando se libera

En la ventana Eventos, puede programar lo que hará la pantalla cuando se pulse o suelte el botón. Hay una serie de instrucciones que puede utilizar en estas ventanas. Entre ellas se incluyen

  • Modificar elementos de la interfaz (por ejemplo, t0.txt=”Pulsado”)
  • Enviar identificador de botón por puerto serie
  • Envoyer une autre commande avec prints
  • Botón b0
  • Botón b1

En la pestaña Touch Release Event, escribimos el siguiente comando, que es un comando predefinido de la librería Nextion y corresponde a trigger1()

printh 23 02 54 01
  • Botón b2

Para el botón b2, utilizamos el mismo comando pero para trigger2()

printh 23 02 54 02

Añadir un temporizador

El objeto Temporizador puede utilizarse para ejecutar código de forma regular. Especialmente útil para recuperar datos del puerto serie y actualizar la interfaz.

En la pestaña de eventos del temporizador, utilizamos la función covx para convertir el valor de la variable en una cadena y escribirla en el texto t4

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

Cargar el programa en la pantalla Nextion

Conectar la tarjeta Nextion al conversor USB-TTL

Para cargar la interfaz, pulse Compilar y luego Cargar

Conexión de la pantalla Nextion al microcontrolador Arduino

Para descargar el código Arduino a la placa, los pines RX y TX deben estar desconectados de la pantalla.

Uso de la biblioteca Nextion.h

Hay varias bibliotecas que puede utilizar para gestionar una pantalla Nextion

Utilizamos EasyNextion

En el Editor Nextion, definimos un evento “printh 23 02 54 01” para el botón ON y un evento “printh 23 02 54 02” para el botón Off.

Utilizando la función NextionListen(), estos comandos activarán las funciones trigger1 y 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");
}

Resultados

Bonificación: Simulador de editor Nextion

Nextion ofrece un simulador para probar las interacciones entre la pantalla, el microcontrolador y la interfaz gráfica. Se puede acceder a este simulador haciendo clic en “Depurar”.

Fuentes