Mit MQTT einen Roboter steuern - [Teil 3]

In the first and second part of this blog series, you learned the basics of MQTT and how it can be used with various micro controllers. In this blog post, the aforementioned (roll) robot will see the light of day and will be controlled with a simple remote control.

An MQTT broker is necessary for this project to succeed. How you can easily implement this with a Raspberry Pi is explained in detail in Part 1 of this blog series.

Hardware requirement

For this blog post you need the following components, see Table 1.

Pos Number Component
1 1 Raspberry Pi (necessary)
2 1 Matching power supply
3 2

ESP32 NodeMCU Module WLAN WiFi Development Board (necessary)

4 2 Potentiometer(necessary)
5 1 H-bridge L298N
6 1 Breadboard
7 1 Rolling robot kit
8 1 Jumper wires Female to Female
9 1 Powerbank for cell phone

Table 1: Required Hardware

With the Raspberry Pi, remember that in addition to the hardware mentioned above, you also need a MicroSD card and power supply. To do this, you should copy the Raspberry Pi OS (formerly known as Raspbian) as an image onto the card and install an MQTT broker according to the instructions from Part 1 of this series.

Software requirement

You need the following software for the blog post:

How you can install libraries via the library management is under https://www.az-delivery.de/blogs/azdelivery-blog-fur-arduino-und-raspberry-pi/arduino-ide-programmieren-fuer-einsteiger-teil-1 Section Library Management, described in more detail.

Basic requirement

Please first check whether the MQTT broker was started when the Raspberry Pi started up. To do this, issue the command Code 1 into the terminal.

sudo service mosquitto status

code 1: Query in the termina whether mosquitto has started

The output should be a active (running) show see illustration 1, no further commands are necessary.

Illustration 1: Status of mosquitto broker in the terminal

If the output differs, please check again whether mosquitto has been installed correctly and the service has also been correctly integrated into the autostart. The exact instructions can be found in Part 1.

The remote control

A remote control is required so that our rolling robot can also be controlled later. This has to fulfill two tasks:

  1. Detect the speed for forward, backward, left and right
  2. Send the data to the MQTT broker

In this case, the direction of movement is implemented using two potentiometers, see Figure 2.

Illustration 2: Remote control wiring

An ESP32 NodeMCU Module WLAN WiFi Development Board is used as the micro controller. Because this micro controller has more than one analog input and has built the WLAN directly on the controller. In comparison to other micro controllers, this saves the additional wiring of a WiFi board.

Code 2 shows the complete source code of the remote control. In order for the code to work for you, you must enter your “WiFi Credentials” in the lines with the comments “Enter Wifi-Name”, “Enter Passkey” and “Name of the mqtt broker" between the quotation marks “”.


//-----------------------------------------------------
// ESP-NodeMCU remote controller
// mqtt broker and mapping analog input
// Author: Joern Weise
// License: GNU GPl 3.0
// Created: Jan 20, 2021
// Update: Jan 20, 2021
//-----------------------------------------------------
#include
#include // 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 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);
  }
}

Code 2: source code remote control

The core function is hidden in the loop () function. Here, the current value of the potentiometer is read in cyclically on the two analog inputs and compared with the last known value. If there is a change, this value is mapped and sent to the MQTT broker. The basic principle is the same as the potentiometer from part 2, only that the analog value is mapped beforehand and only then transmitted to the broker.

So that the remote control can be used anywhere later, it is operated with a power bank. This delivers 5V to a USB output and thus supplies the microcontroller with power.

The rolling robot

The implementation of the rolling robot should be just as easy as the remote control. In principle, two tasks have to be taken over by the microcontroller of the rolling robot:

  1. Receiving the values ​​from the remote control via the MQTT broker
  2. Convert the received values ​​so that the motors rotate correctly

An H-bridge L298N is used here so that point 2 can be fulfilled. What exactly an H-bridge is will be explained again in a later blog; For now, it is enough to know that this electrical component is responsible for controlling the motors. At the same time, the H-bridge saves a second voltage source, since with a voltage of up to 12V the H-bridge L298N supplies 5V to a separate output. In theory, you can also use two separate voltage sources for the microcontroller and H-bridge. It is important that both are raised to the same potential; both GND connections must be interconnected for this.

Figure 3 shows the wiring of the rolling robot with only one voltage source.

Illustration 3: Wiring rolling robot

Please ensure that the 5V voltage is connected to the specified pins of the ESP32 NodeMCU Module WLAN WiFi Development Board. Do not use the GND pin directly next to V5, otherwise the Micro Controller will not boot. With the H-bridge L298N, the jumper for 5V must also be inserted, otherwise you will not receive a 5V voltage from the module, see Figure 4 red border. With voltages above 5V you must remove the jumper, otherwise components on the module will be damaged.

Illustration 4: 5V jumper on L298N

 

You can see the source code for the rolling robot in Code 3. Enter the settings of your network in the lines with the comments "Enter Wifi-Name", "Enter Passkey" and "Name of the mqtt broker".

 //-----------------------------------------------------
// ESP-NodeMCU robot
// mqtt broker and mapping analog input
// Author: Joern Weise
// License: GNU GPl 3.0
// Created: Jan 20, 2021
// Update: Jan 29, 2021
//-----------------------------------------------------
#include
#include // 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"; // Client name 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;
// engine 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 // belongs to the previous line
ledcSetup (motor2channel, freq, resolution); // Configurate PWM for motor 2 // belongs to the previous line
ledcAttachPin (enable1Pin, motor1channel); // Attach channel 1 to motor 1 // belongs to the previous line
ledcAttachPin (enable2Pin, motor2channel); // Attach channel 2 to motor 2 // belongs to the previous line

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 // belongs to the previous line
  {
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; // belongs to the previous line
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; // belongs to the previous line
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;
}

Code 3: source code of the rolling robot

The basic principle of the rolling robot is almost analogous to the Arduino with LCD display from part 2. The rolling robot initializes and connects to the WLAN. In the next step, the rolling robot connects to the MQTT broker and subscribes to the topics relevant to it. If a value of the topics changes, the callback function is called and the new value is accepted.

In the case of the rolling robot, which logs on to the MQTT broker as AZbot, the value for the forward and sideways movement is still debounced with the "DebounceStraight" and "DebounceCross" functions. However, if we receive rapid signal jumps from the remote control, the code does not react to the change with every cycle.

Finally, the corresponding value from the remote control is adopted in the loop function and the corresponding PWM signals are set.

 

Illustration 5: The little AzBot is allowed to drive

The rolling robot shown, including remote control, is simple. This basic structure can be modified in several places. A joystick module, for example, would be ideal for remote control. The source code can also be optimized a lot, but is kept simple for beginners.

A wide variety of modifications are also conceivable for the rolling robot. In this example, the change in direction to the left or right is implemented by reducing the speed of a motor. Here you could consider connecting the front wheel to a servo motor so that you can steer better to the left or right. The robot becomes more child-friendly, for example, if two OLED displays with eyes are installed and, if necessary, a buzzer as a horn.

As you can see, the beginning of a much larger project is done, now you decide how and what your rolling robot should be able to do.

 

This and other projects can be found on GitHub at https://github.com/M3taKn1ght/Blog-Repo

Leave a comment

All comments are moderated before being published