Mit MQTT einen Roboter steuern - [Teil 3] - AZ-Delivery

En la primera y segunda publicación, como parte de esta serie de blogs, ya aprendió los conceptos básicos de MQTT y cómo se puede utilizarlo con varios microcontroladores. En esta publicación de blog, el robot (roll) antes mencionado, se lo va a emplear y será controlado con un simple control remoto.

Es necesario un corredor de MQTT para que este proyecto se realice de la mejor manera. Dado a que puede implementar esto fácilmente con un Raspberry Pi, se explica en detalle en la Parte 1 de esta serie de blogs.

Requisitos de hardware

Para esta publicación de blog, necesita los siguientes componentes, consulte tabla 1.

Pos Number Component
1 1 Raspberry Pi (requerido)
2 1 Fuente de alimentación a juego
3 2

Placa de desarrollo WiFi ESP32 NodeMCU Module WLAN
(requerido)

4 2 Potenciómetro (requerido)
5 1 Un puente en H L298N
6 1 Una placa de pruebas
7 1 Un kit de robot rodante
8 1 Algunos puentes conectan hembra a hembra
9 1 Banco de energía para el teléfono móvil

Mesa 1: Hardware requerido

Con el Raspberry Pi, recuerde que además del hardware mencionado anteriormente, también necesita una tarjeta MicroSD y una fuente de alimentación. Para realizar esto, debe copiar el sistema operativo Raspberry Pi (anteriormente Raspbian) como una imagen en la tarjeta e instalar un agente MQTT de acuerdo con las instrucciones de la Parte 1 de esta serie.

Requisitos de software

Necesita el siguiente software para la publicación del blog:

Cómo puede instalar bibliotecas a través de la gestión de bibliotecas se encuentra en https://www.az-delivery.de/blogs/azdelivery-blog-fur-arduino-und-raspberry-pi/arduino-ide-programmieren-fuer-einsteiger-teil-1 Sección Gestión de bibliotecas, descrita con más detalle.

Requisitos básicos

Primero verifique si el broker MQTT se inició cuando se inició Raspberry Pi. Para hacer esto, emita el comando Codigo 1 en la terminal.

sudo service mosquitto status

Código 1: Consulta en el terminal si mosquitto ha comenzado

La salida debe ser un activo (corriendo) como se observa en la ilustración 1. No se necesitan más comandos.

Ilustración 1: Estado del broker mosquitto en la terminal

Si la salida es diferente, verifique nuevamente si mosquitto se ha instalado correctamente y el servicio también se ha integrado correctamente en el inicio automático. Las instrucciones exactas se pueden encontrar en la Parte 1.

El control remoto

Se requiere un control remoto para que nuestro robot rodante se pueda controlar más tarde. Este módulo debe cumplir dos tareas:

  1. Detecta la velocidad de avance, retroceso, izquierda y derecha.
  2. Envíe los datos al corredor de MQTT

En este caso, la dirección del movimiento se implementa mediante dos potenciómetros, ver Figura 2.

Ilustración 2: Cableado de control remoto

Como microcontrolador se utiliza una placa de desarrollo WLAN WiFi del módulo ESP32 NodeMCU. Este microcontrolador tiene más de una entrada analógica y ha construido la WLAN directamente en el controlador. En comparación con otros microcontroladores, éste ahorra el cableado adicional de una placa WiFi.

 

El código 2 muestra el código fuente completo del mando a distancia. Para que el código funcione correctamente, debe ingresar su “WiFi” en las líneas con los comentarios “Ingrese Wifi-Nombre”, “Ingrese clave de acceso” y “Nombre del corredor mqtt”. Cartas credenciales Inserte “” y el nombre del corredor de MQTT entre las comillas “”.


//-----------------------------------------------------
// ESP-NodeMCU remote controller
// mqtt-broker and mapping analog input
// Autor: Joern Weise
// License: GNU GPl 3.0
// Created: 20. Jan 2021
// Update: 20. Jan 2021
//-----------------------------------------------------
#include <WiFi.h>
#include <PubSubClient.h> //Lib for MQTT Pub and Sub

//Define WiFi-Settings
#ifndef STASSID
#define STASSID "" //Enter Wfi-Name
#define STAPSK "" //Enter Passkey
#endif

#define ADVANCEDIAG 1

#define ADC_STRAIGHT 36
#define ADC_CROSS 39

const char* MQTT_BROKER = ""; //Name of the mqtt broker
const char* PubTopicStraight = "/RemoteControl/Straight"; //Topic first temp
const char* PubTopicCross = "/RemoteControl/Cross"; //Topic second temp
String clientID = "RemoteController"; //Clientname for MQTT-Broker

int iLastStraight, iLastCross;
//Create objects for mqtt
WiFiClient espClient;
PubSubClient mqttClient(espClient);

#define MSG_BUFFER_SIZE (50)
char msg[MSG_BUFFER_SIZE];


void setup()
{
Serial.begin(115200);
Serial.println("Remote control started");
writeAdvanceDiag("Init WiFi", true);
setupWifi();
writeAdvanceDiag("Init Wifi - DONE", true);
writeAdvanceDiag("Set MQTT-Server", true);
mqttClient.setServer(MQTT_BROKER,1883);
iLastStraight = -7;
iLastCross = -7;
writeAdvanceDiag("Finish setup()-Function", true);
}

void loop() {
if(!mqttClient.connected())
reconnectMQTT();

mqttClient.loop();
//Read value from analog input and map value
int iMappedStraight = map(analogRead(ADC_STRAIGHT),4095,0,-2,2);
if(iMappedStraight != iLastStraight)
{
snprintf(msg,MSG_BUFFER_SIZE, "%1d",iMappedStraight); //Convert message to char
mqttClient.publish(PubTopicStraight,msg,true); //Send to broker
writeAdvanceDiag("Send Straight: " + String(iMappedStraight), true); // gehört zur vorherigen Zeile
iLastStraight = iMappedStraight;
}
//Read value from analog input and map value
int iMappedCross = map(analogRead(ADC_CROSS),4095,0,-2,2);
if(iMappedCross != iLastCross)
{
snprintf(msg,MSG_BUFFER_SIZE, "%1d",iMappedCross); //Convert message to char
mqttClient.publish(PubTopicCross,msg,true); //Send to broker
writeAdvanceDiag("Send Cross: " + String(iMappedCross), true);
iLastCross = iMappedCross;
}
}

/*
* =================================================================
* Function: setupWifi
* Returns: void
* Description: Setup wifi to connect to network
* =================================================================
*/
void setupWifi()
{
Serial.println("Connection to: " + String(STASSID));
WiFi.mode(WIFI_STA);
WiFi.begin(STASSID, STAPSK);
while (WiFi.status() != WL_CONNECTED)
{
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
}
/*
* =================================================================
* Function: reconnectMQTT
* Returns: void
* Description: If there is no connection to MQTT, this function is
* called. In addition, the desired topic is registered.
* =================================================================
*/
void reconnectMQTT()
{
while(!mqttClient.connected())
{
writeAdvanceDiag("Login to MQTT-Broker", true);
if(mqttClient.connect(clientID.c_str()))
{
Serial.println("Connected to MQTT-Broker " + String(MQTT_BROKER)); // gehört zur vorherigen Zeile
}
else
{
writeAdvanceDiag("Failed with rc=" +String(mqttClient.state()), true); // gehört zur vorherigen Zeile
Serial.println("Next MQTT-Connect in 3 sec");
delay(3000);
}
}
}

/*
* =================================================================
* Function: writeAdvanceDiag
* Returns: void
* Description: Writes advance msg to serial monitor, if
* ADVANCEDIAG >= 1
* msg: Message for the serial monitor
* newLine: Message with linebreak (true)
* =================================================================
*/
void writeAdvanceDiag(String msg, bool newLine)
{
if(bool(ADVANCEDIAG)) //Check if advance diag is enabled
{
if(newLine)
Serial.println(msg);
else
Serial.print(msg);
}
}

Código 2: Control remoto de código fuente

La función principal está oculta en la función loop (). Aquí se lee cíclicamente el valor actual de los potenciómetros de las dos entradas analógicas y se compara con el último valor conocido. Cuando se produce un cambio, este valor se mapea y se envía al broker MQTT. El principio básico es el mismo comportamiento que el del potenciómetro de la parte 2, excepto que el valor analógico se mapea primero y sólo en ese caso se transmite al corredor.

Para que el control remoto se pueda usar en cualquier lugar más adelante, se opera con un banco de energía. Esto entrega 5V a una salida USB y, por lo tanto, suministra energía al microcontrolador.

El robot rodante

La implementación del robot rodante es tan sencilla como la del control remoto. En principio, el microcontrolador del robot rodante debe desarrollar dos tareas:

  1. Recibir los valores del control remoto a través del broker MQTT
  2. Convierta los valores recibidos para que los motores giren correctamente

Aquí se utiliza un puente H L298N para que se pueda cumplir el punto 2. En una siguiente publicación se explicará lo que es un puente en H; Por ahora, solo se necesita saber que este componente eléctrico se encarga de controlar los motores. De igual forma, el puente H ahorra una segunda fuente de voltaje, ya que con un voltaje de hasta 12V el puente H L298N suministra 5V a una salida separada. En teoría, también puede usar dos fuentes de voltaje separadas para el microcontrolador y el puente H. Es importante que ambos se eleven al mismo potencial; para ello, ambas conexiones GND deben estar interconectadas.

La figura 3 presenta el cableado del robot rodante con una sola fuente de voltaje.

Ilustración 3: Robot rodante de cableado

Asegúrese de que el voltaje de 5V esté conectado a los pines especificados de la placa de desarrollo WLAN WiFi del módulo ESP32 NodeMCU. No use el pin GND directamente al lado de V5, de lo contrario el MicroController no arrancará. Con el puente H L298N, también se debe insertar el puente para 5V, de lo contrario no recibirá un voltaje de 5V del módulo, consulte la Figura 4 borde rojo. Con voltajes superiores a 5 V, debe retirar el puente; de ​​lo contrario, los componentes del módulo se dañarán.

Ilustración 4: Puente de 5V en L298N

 

Puede ver el código fuente del robot rodante en Codigo 3. Ingrese la configuración de su red en las líneas con los comentarios "Ingrese el nombre de Wifi", "Ingrese la contraseña" y "Nombre del corredor de mqtt".

//-----------------------------------------------------
// ESP-NodeMCU robot
// mqtt-broker and mapping analog input
// Autor: Joern Weise
// License: GNU GPl 3.0
// Created: 20. Jan 2021
// Update: 29. Jan 2021
//-----------------------------------------------------
#include <WiFi.h>
#include <PubSubClient.h> //Lib for MQTT Pub and Sub

//Define WiFi-Settings
#ifndef STASSID
#define STASSID "" //Enter Wifi-Name
#define STAPSK "" //Enter Passkey
#endif

#define ADVANCEDIAG 1

#define MAXSPEED 255
#define MINSPEED 155
//MQTT stuff
const char* MQTT_BROKER = ""; //Name of the mqtt broker
String clientID = "AZBot"; //Clientname for MQTT-Broker
const char* SubTopicStraight = "/RemoteControl/Straight"; //Topic first temp
const char* SubTopicCross = "/RemoteControl/Cross"; //Topic second temp

int iMQTTStraight, iMQTTCross, iMQTTStraightNew, iMQTTCrossNew, iMQTTStraightLast, iMQTTCrossLast;

//Create objects for mqtt
WiFiClient espClient;
PubSubClient mqttClient(espClient);

//Timer-vars for debounce
unsigned long ulDebounce = 10; //Debounce-time
unsigned long ulLastDebounceTimeStraight, ulLastDebounceTimeCross; //Timer to debouce button

//PWM and motor configuration
// Motor A
const int motor1Pin1 = 27;
const int motor1Pin2 = 26;
const int enable1Pin = 14;
const int motor1channel = 0;
// Motor B
const int motor2Pin1 = 17;
const int motor2Pin2 = 5;
const int enable2Pin = 16;
const int motor2channel = 1;

// Setting PWM properties
const int freq = 30000;
const int resolution = 8;

bool bUpdateMovement = false; //Will set, if there are new movements from mqtt available
/*
=================================================================
Function: setup
Returns: void
Description: Needed setup-function
=================================================================
*/
void setup()
{
// sets the pins as outputs:
pinMode(motor1Pin1, OUTPUT);
pinMode(motor1Pin2, OUTPUT);
pinMode(enable1Pin, OUTPUT);
pinMode(motor2Pin1, OUTPUT);
pinMode(motor2Pin2, OUTPUT);
pinMode(enable2Pin, OUTPUT);
Serial.begin(115200);
Serial.println("Remote control started");
iMQTTStraightNew = 0;
iMQTTCrossNew = 0;
writeAdvanceDiag("Init WiFi", true);
setupWifi();
writeAdvanceDiag("Init Wifi - DONE", true);
writeAdvanceDiag("Set MQTT-Server", true);
mqttClient.setServer(MQTT_BROKER, 1883);
writeAdvanceDiag("Set Callback-function", true);
mqttClient.setCallback(callback);
writeAdvanceDiag("Set PWM-Channels", true);
ledcSetup(motor1channel, freq, resolution); //Configurate PWM for motor 1 // gehört zur vorherigen Zeile
ledcSetup(motor2channel, freq, resolution); //Configurate PWM for motor 2 // gehört zur vorherigen Zeile
ledcAttachPin(enable1Pin, motor1channel); //Attach channel 1 to motor 1 // gehört zur vorherigen Zeile
ledcAttachPin(enable2Pin, motor2channel); //Attach channel 2 to motor 2 // gehört zur vorherigen Zeile

writeAdvanceDiag("Finish setup()-Function", true);
}
/*
=================================================================
Function: loop
Returns: void
Description: Needed loop-function
=================================================================
*/
void loop()
{
if (!mqttClient.connected())
reconnectMQTT();

mqttClient.loop();
DebounceStraight();
DebounceCross();
int iSpeedMotor1, iSpeedMotor2;
if (bUpdateMovement) //Check if there is a new movement available from mqtt // gehört zur vorherigen Zeile
{
Serial.println("Current value straight: " + String(iMQTTStraight));
Serial.println("Current value cross: " + String(iMQTTCross));
if (iMQTTStraight != 0)
{
if (iMQTTStraight < 0)
{
digitalWrite(motor1Pin1, LOW);
digitalWrite(motor1Pin2, HIGH);
digitalWrite(motor2Pin1, LOW);
digitalWrite(motor2Pin2, HIGH);
}
else
{
digitalWrite(motor1Pin1, HIGH);
digitalWrite(motor1Pin2, LOW);
digitalWrite(motor2Pin1, HIGH);
digitalWrite(motor2Pin2, LOW);
}
if(abs(iMQTTStraight) == 1)
{
iSpeedMotor1 = MAXSPEED - (MAXSPEED - MINSPEED)/2;
iSpeedMotor2 = MAXSPEED - (MAXSPEED - MINSPEED)/2;
}
else
{
iSpeedMotor1 = MAXSPEED;
iSpeedMotor2 = MAXSPEED;
}
}
else
{
iSpeedMotor1 = 0;
iSpeedMotor2 = 0;
}

if (iMQTTCross != 0)
{
if (iMQTTCross < 0)
{
if(iSpeedMotor1 == MAXSPEED)
{
if(abs(iMQTTCross) == 1)
iSpeedMotor1 = MAXSPEED - (MAXSPEED - MINSPEED)/2; // gehört zur vorherigen Zeile
else
iSpeedMotor1 = MINSPEED;
}
else
{
if(abs(iMQTTCross) == 1)
iSpeedMotor1 = MINSPEED;
else
iSpeedMotor1 = 0;
}
Serial.println("New Speed motor 1: " + String(iSpeedMotor1));
}
else
{
if(iSpeedMotor2 == MAXSPEED)
{
if(abs(iMQTTCross) == 1)
iSpeedMotor2 = MAXSPEED - (MAXSPEED - MINSPEED)/2; // gehört zur vorherigen Zeile
else
iSpeedMotor2 = MINSPEED;
}
else
{
if(abs(iMQTTCross) == 1)
iSpeedMotor2 = MINSPEED;
else
iSpeedMotor2 = 0;
}
Serial.println("New Speed motor 2: " + String(iSpeedMotor2));
}
}
//Write speed to motor pwm
ledcWrite(motor1channel, iSpeedMotor1);
ledcWrite(motor2channel, iSpeedMotor2);
bUpdateMovement = false; //New movement set
}
}

/*
=================================================================
Function: setupWifi
Returns: void
Description: Setup wifi to connect to network
=================================================================
*/
void setupWifi()
{
Serial.println("Connection to: " + String(STASSID));
WiFi.mode(WIFI_STA);
WiFi.begin(STASSID, STAPSK);
while (WiFi.status() != WL_CONNECTED)
{
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
}

/*
=================================================================
Function: callback
Returns: void
Description: Will automatical called, if a subscribed topic
has a new message
topic: Returns the topic, from where a new msg comes from
payload: The message from the topic
length: Length of the msg, important to get conntent
=================================================================
*/
void callback(char* topic, byte* payload, unsigned int length)
{
String strMessage = "";
writeAdvanceDiag("Message arrived from topic: " + String(topic), true); // gehört zur vorherigen Zeile
writeAdvanceDiag("Message length: " + String(length), true);
for (int i = 0; i < length; i++)
strMessage += String((char)payload[i]);
writeAdvanceDiag("Message is: " + strMessage, true);
if (String(topic) == String(SubTopicStraight))
{
iMQTTStraightNew = strMessage.toInt();
}
else if (String(topic) == String(SubTopicCross))
{
iMQTTCrossNew = strMessage.toInt();
}
}

/*
=================================================================
Function: reconnectMQTT
Returns: void
Description: If there is no connection to MQTT, this function is
called. In addition, the desired topic is registered
=================================================================
*/
void reconnectMQTT()
{
while (!mqttClient.connected())
{
writeAdvanceDiag("Login to MQTT-Broker", true);
if (mqttClient.connect(clientID.c_str()))
{
Serial.println("Connected to MQTT-Broker " + String(MQTT_BROKER)); // gehört zur vorherigen Zeile
writeAdvanceDiag("Subscribe topic '" + String(SubTopicStraight) + "'", true); // gehört zur vorherigen Zeile
mqttClient.subscribe(SubTopicStraight, 1); //Subscibe topic "SubTopicStraight" // gehört zur vorherigen Zeile
writeAdvanceDiag("Subscribe topic '" + String(SubTopicCross) + "'", true); // gehört zur vorherigen Zeile
mqttClient.subscribe(SubTopicCross, 1); //Subscibe topic "SubTopicCross" // gehört zur vorherigen Zeile
}
else
{
writeAdvanceDiag("Failed with rc=" + String(mqttClient.state()), true); // gehört zur vorherigen Zeile
Serial.println("Next MQTT-Connect in 3 sec");
delay(3000);
}
}
}


/*
=================================================================
Function: writeAdvanceDiag
Returns: void
Description: Writes advance msg to serial monitor, if
ADVANCEDIAG >= 1
msg: Message for the serial monitor
newLine: Message with linebreak (true)
=================================================================
*/
void writeAdvanceDiag(String msg, bool newLine)
{
if (bool(ADVANCEDIAG)) //Check if advance diag is enabled
{
if (newLine)
Serial.println(msg);
else
Serial.print(msg);
}
}

/*
=================================================================
Function: DebounceStraight
Returns: void
Description: Set new value, if debouce is over
If there is a new valid bUpdateMovement
will set true
=================================================================
*/
void DebounceStraight()
{
if (iMQTTStraightNew != iMQTTStraightLast)
ulLastDebounceTimeStraight = millis();

if ((millis() - ulLastDebounceTimeStraight) > ulDebounce)
{

if (iMQTTStraightNew != iMQTTStraight)
{
iMQTTStraight = iMQTTStraightNew;
writeAdvanceDiag("New straight value " + String(iMQTTStraight), true); // gehört zur vorherigen Zeile
bUpdateMovement = true;
}
}
iMQTTStraightLast = iMQTTStraightNew;

}

/*
=================================================================
Function: DebounceCross
Returns: void
Description: Set new value, if debouce is over
If there is a new valid bUpdateMovement
will set true
=================================================================
*/
void DebounceCross()
{
if (iMQTTCrossNew != iMQTTCrossLast)
ulLastDebounceTimeCross = millis();

if ((millis() - ulLastDebounceTimeCross) > ulDebounce)
{
if (iMQTTCrossNew != iMQTTCross)
{
iMQTTCross = iMQTTCrossNew;
writeAdvanceDiag("New cross value " + String(iMQTTCross), true); // gehört zur vorherigen Zeile
bUpdateMovement = true;
}
}
iMQTTCrossLast = iMQTTCrossNew;
}

Código 3: Código fuente del robot rodante


El principio básico del robot rodante es un Arduino analógico rápido con pantalla LCD desde la parte 2. El robot rodante se inicia y se conecta al WLAN. En el siguiente paso, el robot rodante se conecta al broker MQTT y se suscribe a los Topics que le corresponden. Si un valor de los temas cambia, se llama a la función callback y se acepta el nuevo valor.

En el caso del robot rodante, que inicia sesión en el bróker MQTT como AZbot, el valor para el movimiento hacia adelante y hacia los lados todavía se elimina con las funciones "DebounceStraight" y "DebounceCross". Sin embargo, si recibimos saltos de señal rápidos desde el control remoto, el código no reacciona al cambio con cada ciclo.

Finalmente, el valor correspondiente del control remoto se adopta en la función de bucle y se configuran las señales PWM correspondientes.

 

Ilustración 5: El pequeño AzBot puede conducir

El robot rodante que se presenta en la ilustración 5, incluido el control remoto, es simple. Esta estructura básica se puede modificar en varios lugares. Un módulo de joystick, por ejemplo, sería ideal para el control remoto. El código fuente también se puede optimizar más, pero se mantiene simple para los principiantes.

También son posibles una amplia variedad de modificaciones para el robot rodante. En este ejemplo, el cambio de dirección hacia la izquierda o hacia la derecha se implementa reduciendo la velocidad de un motor. Aquí podría considerar conectar la rueda delantera a un servomotor para poder dirigir mejor hacia la izquierda o hacia la derecha. El robot se vuelve más amigable para los niños, por ejemplo, si se instalan dos pantallas OLED con ojos y, si es necesario, un timbre como bocina.

Como puede ver, ha comenzado un proyecto mucho más grande, ahora usted decide cómo y qué puede realizar su robot rodante.

 

Este y otros proyectos se pueden encontrar en GitHub en https://github.com/M3taKn1ght/Blog-Repo

Esp-32Raspberry pi

Deja un comentario

Todos los comentarios son moderados antes de ser publicados