This code allows control of ESP32 robots or gadgets over WLAN using vigibot.
A raspberry is still required for the camera and needs to be on the same network. the serial data usually sent and received to an Arduino over UART ( https://www.robot-ma...cation-example/ ), is being sent over WLAN instead, allowing control of small robots or gadgets.
usage example: a pi with a camera watches an arena where multiple small ESP32 robots can be driven, the ESP32 bots do not have an on-board camera.
second usage example: open or close a door or toy controlled fron an ESP32.
1) insert your network info and upload the following sketch to your ESP32 from the arduino IDE using an usb cable.
Spoiler
/*
Author: firened
File: vigibot_ESP32_slave.ino
Version:
V1.0 10.2020
Description:
Vigibot Pi to ESP32 over WLAN, based on the Arduino Uart example by Mike118.
check the robot-maker post for up to date setup instructions.
pinout GPIO:
*/
#include <WiFi.h>
#include <ESPmDNS.h>
const char* ssid = "your_ssid";
const char* pass = "your_pass";
// Set your Static IP address
IPAddress local_IP(192, 168, 1, 61);
// Set your Gateway IP address
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 255, 0);
// Meta Type :
typedef struct {
union {
struct {
int16_t x;
int16_t y;
};
int16_t coordonnees[2];
uint8_t bytes[4];
};
} Point;
typedef struct {
union {
struct {
int8_t x;
int8_t y;
int8_t z;
};
uint8_t bytes[3];
};
} Vitesses;
// CONFIG
//#define PISERIAL Serial
#define NBPOSITIONS 2
#define FAILSAFE 250 // ms
// TTS
#define TTSBUFFERSIZE 255
uint8_t ttsBuffer[TTSBUFFERSIZE];
uint8_t ttsCurseur = 0;
// TX
#define TXFRAMESIZE (NBPOSITIONS * 4 + 17)
typedef struct {
union {
struct {
uint8_t sync[4]; // 4
Point positions[NBPOSITIONS]; // NBPOSITIONS * 4
uint16_t val16[2]; // 2 * 2
uint8_t choixCameras; // 1
Vitesses vitesses; // 3
uint8_t interrupteurs; // 1
uint8_t val8[4]; // 4
};
uint8_t bytes[TXFRAMESIZE];
};
} TrameTx;
// RX
#define RXFRAMESIZE (NBPOSITIONS * 4 + 9)
typedef struct {
union {
struct { // Sizes
uint8_t sync[4]; // 4
Point positions[NBPOSITIONS]; // NBPOSITIONS * 4 uint8_t choixCameras; // 1
Vitesses vitesses; // 3
uint8_t interrupteurs; // 1
};
uint8_t bytes[RXFRAMESIZE];
};
} TrameRx;
TrameTx trameTx;
TrameRx trameRx;
uint32_t lastTrameTimestamp = millis();
uint32_t lastSleepBeacon;
WiFiServer server(7070); //ESP server port
WiFiClient client;
void setup()
{
Serial.begin(115200);
// We start by connecting to a WiFi network
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.mode(WIFI_STA);
WiFi.config(local_IP, gateway, subnet);
WiFi.setHostname("Vigibot_ESP_0");
WiFi.begin(ssid, pass);
while (WiFi.waitForConnectResult() != WL_CONNECTED) {
Serial.println("Connection Failed! Rebooting..."); delay(5000);
ESP.restart();
}
server.begin();
Serial.println("WiFi connected.");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
// add all your init here
}
void loop() {
while (WiFi.waitForConnectResult() != WL_CONNECTED) {
Serial.println("WLAN Connection Failed! reconnecting...");
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, pass);
delay(10000);
}
if (readPiSerial()) {
// each time we receive a full trame run repeatedly:
// use values inside trameRx to tell your robot how to move ...
// trameRx.vitesses.x , trameRx.vitesses.y, trameRx.vitesses.z
// trameRx.positions[i].x trameRx.positions[i].y etc....
writePiSerial();
lastTrameTimestamp = millis();
}
if ( millis() - lastTrameTimestamp > FAILSAFE ) {
if ((millis() - lastSleepBeacon > 10000) ) { // every 10 seconds
writePiSerial(); // Beacon to say that the robot is alive
lastSleepBeacon = millis();
}
// Stop the robot in case the robot lost connection with the Pi
} else {
// put your main code here, to run repeatedly:
// avoid abstacle, run speed ...
}
}
bool readPiSerial() {
uint8_t current;
static uint8_t lastType = 0;
static uint8_t n = 0;
static uint8_t frame[RXFRAMESIZE];
static byte lastClientState = 0;
if (client.connected()) {
if (!lastClientState) {
lastClientState = 1;
Serial.println("New Client.");
}
while (client.available()) {
current = client.read();
//Serial.write(current); //debug
switch (n) {
case 0:
if (current == '$')
n = 1;
break;
case 1:
if (current != 'T' && lastType == 'T')
writeTtsBuffer('\n');
if (current == 'S' || current == 'T') {
lastType = current;
n = 2;
} else
n = 0;
break;
default:
frame[n++] = current;
if (n == RXFRAMESIZE) {
if (lastType == 'T') {
for (uint8_t i = 4; i < RXFRAMESIZE; i++) // Do not send the 4 sync data in tts
writeTtsBuffer(frame[i]);
} else if (lastType == 'S') {
for (uint8_t p = 0; p < RXFRAMESIZE; p++)
trameRx.bytes[p] = frame[p];
}
n = 0;
return true;
}
//break;
}
}
} else { //if current client is not actively connected anymore, disconnect and wait for new client
if (lastClientState) {
lastClientState = 0;
// close the connection:
client.stop();
Serial.println("Client Disconnected.");
}
client = server.available(); // listen for incoming clients
}
return false;
}
void writePiSerial() {
// Header, do not modify
trameTx.sync[0] = '$';
trameTx.sync[1] = 'R';
trameTx.sync[2] = ' ';
trameTx.sync[3] = ' ';
// modify the feedback according your need. By default we copy the trameRx content ...
for (uint8_t i = 0; i < NBPOSITIONS; i++) {
trameTx.positions[i].x = trameRx.positions[i].x;
trameTx.positions[i].y = trameRx.positions[i].y;
}
trameTx.val16[0] = 0; // Voltage (will be updated by Raspberry pi)
trameTx.val16[1] = 0; // Percent (will be updated by Raspberry pi)
trameTx.choixCameras = trameRx.choixCameras;
trameTx.vitesses.x = trameRx.vitesses.x;
trameTx.vitesses.y = trameRx.vitesses.y;
trameTx.vitesses.z = trameRx.vitesses.z;
trameTx.interrupteurs = trameRx.interrupteurs;
trameTx.val8[0] = 0; // CPU load (will be updated by Raspberry pi)
trameTx.val8[1] = 0; // Soc temp (will be updated by Raspberry pi)
trameTx.val8[2] = 0; // link (will be updated by Raspberry pi)
trameTx.val8[3] = 0; // RSSI (will be updated by Raspberry pi)
for ( uint8_t i = 0; i < TXFRAMESIZE; i++)
client.write(trameTx.bytes[i]);
}
void displayTtsBuffer (uint8_t * ttsBuffer, uint8_t bufferSize) {
// you can modify this function to display text on a screen depending on your hardware...
for ( uint8_t i = 0; i < bufferSize; i++) {
Serial.write(ttsBuffer[i]);
}
Serial.println("");
}
void writeTtsBuffer( uint8_t ttsChar) {
static uint8_t ttsCurseur = 0;
if ( ttsCurseur < TTSBUFFERSIZE && ttsChar != '\n') {
ttsBuffer[ttsCurseur] = ttsChar;
ttsCurseur ++;
}
if ( ttsCurseur == TTSBUFFERSIZE || ttsChar == '\n') {
displayTtsBuffer (ttsBuffer, ttsCurseur);
ttsCurseur = 0;
}
}
2) open the serial monitor to confirm a successful WLAN connection.
3) the pi needs to create a virtual serial point that's being forwarded over WLAN to the ESP32. install socat on your raspberry:
sudo apt install socat
4) create a new script:
sudo nano /usr/local/socat-esp.sh
5) paste the following code and replace your ESP's IP:
#!/bin/bash
sudo socat pty,link=/dev/ttyVigi0,rawer,shut-none pty,link=/dev/ttyEsp0,rawer,shut-none &
sleep 1
while true
do
sudo socat -T15 open:/dev/ttyEsp0,raw,nonblock tcp:192.168.1.62:7070
done
6) add permissions
sudo chmod +x /usr/local/socat-esp.sh
7) run the script
sudo /usr/local/socat-esp.sh
8) test the pi <-> ESP connection by logging in as root in a new shell (needs root) :
su -
and sending a test text frame:
echo '$T hello from cli $n' > /dev/ttyVigi0
the arduino serial monitor now says "hello from cli "
9)if it works add it to start at boot:
sudo nano /etc/rc.local
and add
/usr/local/socat-esp.sh &
above the line "exit 0".
10) add an entry on the vigibot online remote control config -> SERIALPORTS -> 3 with value: "/dev/ttyVigi0"
11) set WRITEUSERDEVICE to "3"
12) set READUSERDEVICE to "3"
restart the client and wake your robot up, if it wakes up then everything works.