Havoc Rover Base is the movable bottom half of the Havoc Sentry Drone Unit. I initially wanted to build one to carry a camera. DSLR perhaps is too heavy for this unit but a GoPro would fit just fine. The base unit has a 1/4 inch mount screw for attaching the top module. The sentry unit is powered by a camera battery. Which makes it easily rechargeable and many photographers might have extra ones laying around since it is a very popular battery module for a wide range of Sony DSLR cameras in the market. I will make another post about the top mounting module with the stabilizer gimbal unit later. Let’s get right to the electronics.
The Micro Controller Chip
I’m using a ESP32 chip for the sentry unit. I will refer to the ESP32 as the motherboard. The ESP32 is capable of WiFi, BLE and a proprietary communication protocol named ESP-NOW. In current configuration, the ESP32 starts a wifi access point which can be found on the phone screen. Once connected to the wifi access point, login credentials of the home router can be configured on the localhost interface of the chip access point. Using these credentials, the chip can then connect to the wifi router directly and become accessible through the mobile app interface. The authentication data is saved on the chip so this process only requires to run at first time startup.
The rover is driven by two powerful dc motors (Image #2). They are more than capable of pulling the rover with high torque at quite low amperage of power supply.
The Rover Chassis
The chassis is a plastic frame with 6 wheels on each side. These wheels pull a belt for rough terrain movement of the rover. Wheels on the ground also have shock absorbent springs to reduce vibration during operation. Two DC motors are used to drive the wheels on two sides. These are standard 7.4v DC motors.
Measurements & Weights
The Havoc sentry rover is a ground unit. I plan to use it as a payload from lightweight carrier drones. Keeping the weight down would be very helpful in those cases. The unit carries a 3000 mAH battery on it’s back. NPF-970 batteries are widely used for Sony DSLR cameras but they are heavy. Below are some images of the rover base, with and without the battery cover frame. I can reduce some of the weight by using a lighter composite for the battery frame, which is metal right now.
Hardware Assembly
The rover base has two 7.4v DC motors. One for each side of the rover belt. I have connected the motors with two DC power transistors that connect the motors directly to the battery source. The transistors can be controlled by the motherboard through a digital input signal. Signal input for the transistors are connected to 14(L) and 12(R) pin of the motherboard.
The motherboard takes power from a micro usb port. I had an extra micro usb wire that I converted into a power supply connector for the regulator. Instead of being a usb connection, now the wire serves as a power line for the motherboard.
First Build
The first build turned out to be quite bulky. I added a power regulator to step down the 7.2v battery to match the motherboard’s power. The power regulator unit was difficult to fit inside the base. Since the DC motors are rated close to the battery voltage, no regulator is needed for the main drive.
Coding the ESP32
After setting up the wifi access on the motherboard, the device becomes available on the home router wifi network via UDP protocols and listens to port 8888 for command inputs. Let’s get our hands dirty with some coding. I’m updating the wifi listener periodically to keep track of all the input commands coming in through the UDP port. ProcessCommand() takes the input strings and decodes them for appropriate use. DriveWheelAll() takes a pulse input signal to switch the DC motors on and off. This is a very effective way to control speed without dealing with the current supply or voltage regulation. DriveWheelSingle() rotates only one targeted motor where DriveWheelAll controls both motors simultaneously.
#include <WiFi.h>
//needed for library
#include <DNSServer.h>
#include <WebServer.h>
#include <WiFiManager.h>
#include <WiFiUdp.h>
#define UDP_TX_PACKET_MAX_SIZE 1024 //increase UDP size
extern bool WIFI_CONNECTED = false;
String uniqueID = “RxHv1000”;
//Wheel motor drive
int leftWheelPin = 14;
int rightWheelPin = 12;// temporary variabled for speed control
int rwSpeed;
int lwSpeed;
String roverID;int ind1;
int ind2;
int ind3;//The udp library class
WiFiUDP udp;// UDP variables
unsigned int localPort = 8888;
boolean udpConnected = false;//buffer to hold incoming packet,
uint8_t packetBuffer[UDP_TX_PACKET_MAX_SIZE];
// a string to send back
uint8_t ReplyBuffer[] = “acknowledged”;// Driving variables
bool isDriving = false;
int leftWheelSpeed = 100;
int rightWheelSpeed = 100;
void configModeCallback (WiFiManager *myWiFiManager) {
Serial.println(“Entered config mode”);
Serial.println(WiFi.softAPIP());
//if you used auto generated SSID, print it
Serial.println(myWiFiManager->getConfigPortalSSID());
}//wifi event handler
void WiFiEvent(WiFiEvent_t event){
switch(event) {
case SYSTEM_EVENT_STA_GOT_IP:
//When connected set
Serial.print(“WiFi connected! IP address: “);
Serial.println(WiFi.localIP());
WIFI_CONNECTED = true;
break;
case SYSTEM_EVENT_STA_DISCONNECTED:
Serial.println(“WiFi lost connection”);
WIFI_CONNECTED = false;
break;
}
}// connect to UDP – returns true if successful or false if not
boolean connectUDP()
{
boolean state = false;Serial.println(“”);
Serial.println(“Connecting to UDP”);if(udp.begin(localPort) == 1){
Serial.println(“UDP Connection successful”);
state = true;
}
else{
Serial.println(“UDP Connection failed”);
}return state;
}
//RxHv1000 command & control
void ProcessCommands(char* commandMessage)
{
String commandString = ((String)commandMessage);//Command is one line of message string that contains movement info
//Ex: LW:100,RW:100
//Left wheel speed, Right wheel speed
//Actual data: RxHv1000,100,100//Lets parse the input data
ind1 = commandString.indexOf(‘,’); //finds location of first ,
roverID = commandString.substring(0, ind1); //captures first data String
ind2 = commandString.indexOf(‘,’, ind1+1 ); //finds location of second ,
lwSpeed = commandString.substring(ind1+1, ind2+1).toInt(); //captures second data String
ind3 = commandString.indexOf(‘,’, ind2+1 );
rwSpeed = commandString.substring(ind2+1, ind3+1).toInt();if(uniqueID==”RxHv1000″) //Input message matched for this unique rover
{if(lwSpeed>0 || rwSpeed>0)
{
isDriving = true;
//Sync speed data
leftWheelSpeed = lwSpeed;
rightWheelSpeed = rwSpeed;Serial.println(“Left wheel: “+ String(leftWheelSpeed) +” Right wheel: ” + String(rightWheelSpeed));
}else
{
Serial.println(“Driving power off”);
isDriving = false;
}
}
}void ReadUDP()
{
if(udpConnected)
{
// if there’s data available, read a packet
int packetSize = udp.parsePacket();if(packetSize)
{
Serial.println(“”);
Serial.print(“Received packet of size “);
Serial.println(packetSize);
Serial.print(“From “);
IPAddress remote = udp.remoteIP();for (int i =0; i < 4; i++)
{
Serial.print(remote[i], DEC);
if (i < 3)
{
Serial.print(“.”);
}
}
Serial.print(“, port “);
Serial.println(udp.remotePort());memset(packetBuffer, 0, UDP_TX_PACKET_MAX_SIZE); // read the packet into packetBufffer
if(udp.read(packetBuffer,UDP_TX_PACKET_MAX_SIZE)>0)
{
char* udpMessage = (char*)packetBuffer;
Serial.println(“Contents:”);
Serial.println(udpMessage);//Take actions based on command inputs
ProcessCommands(udpMessage);
}
}
// send a reply, to the IP address and port that sent us the packet we received
// udp.beginPacket(udp.remoteIP(), udp.remotePort());//Keep broadcasting
udp.beginPacket();
//udp.write(ReplyBuffer);
udp.printf(“G-Rex: Seconds since boot: %u”, millis()/1000);// print time log to console
//Serial.printf(“G-Rex UDP: Seconds since boot: %u \n”, millis()/1000);
//Serial.println(WiFi.localIP());udp.endPacket();
}
}void InitWiFi()
{
//WiFiManager
//Local intialization. Once its business is done, there is no need to keep it around
WiFiManager wifiManager;
//reset settings – for testing
//wifiManager.resetSettings();//set callback that gets called when connecting to previous WiFi fails, and enters Access Point mode
wifiManager.setAPCallback(configModeCallback);//fetches ssid and pass and tries to connect
//if it does not connect it starts an access point with the specified name
//here “AutoConnectAP”
//and goes into a blocking loop awaiting configurationif(!wifiManager.autoConnect(“T-Rex Robot RxHv0a1”))
{
Serial.println(“failed to connect and hit timeout”);
//reset and try again, or maybe put it to deep sleep
ESP.restart();
delay(1000);
}
else
{
//if you get here you have connected to the WiFi
Serial.println(“WiFi Connected…”);
Serial.println(“Ready!”);
//register event handler
WiFi.onEvent(WiFiEvent);
//WiFi.disconnect(true); // Erases SSID/password on ESP32
//Serial.println(“WiFi disconnected…”);WIFI_CONNECTED = true;
//Start connecting to UDP
udpConnected = connectUDP();
if (udpConnected)
{
// initialise pins
pinMode(leftWheelPin,OUTPUT);
pinMode(rightWheelPin,OUTPUT);}
}
}void DriveWheelAll(int speedPulse)
{
digitalWrite(leftWheelPin,HIGH); //left wheel
digitalWrite(rightWheelPin,HIGH); //right wheel
delay(speedPulse);
digitalWrite(leftWheelPin,LOW); //left wheel
digitalWrite(rightWheelPin,LOW); //right wheel
}
void DriveWheelSingle(int SpeedPulse, int wheelPin)
{
digitalWrite(wheelPin,HIGH); //wheel on
delay(SpeedPulse);
digitalWrite(wheelPin,LOW); //wheel off
}void UpdateWiFiLoop(){
//only send data when connected
if(WIFI_CONNECTED)
{
ReadUDP();
}if(isDriving)
{
//DriveWheel(roverSpeed);
DriveWheelSingle(leftWheelSpeed, leftWheelPin);
DriveWheelSingle(rightWheelSpeed, rightWheelPin);
}
}
After burning the code to the motherboard, I used a simple Android network diagnostic app to send signals to the rover. Not practical to control the rover this way but good for testing. I will have to make a better user interface with precise controls. Which means it is time for some graphics programming.
Controller Interface on a Mobile App
Now that I have a functioning rover base, it’s time to make the controller app for the phone. I’m going to use Unity3D for it’s excellent multi-platform compatibility. Let’s start up Unity3D and establish a UDP connection with the rover. Unity’s networking features are rich enough to handle everything the rover needs. I want to make a mobile interface with tactile touch controllers on my Android phone.
Final Build
After a good amount of trial and error, I have finally nailed the rover controls. The touch controller rotates a cube in the interface to show the direction of input. The buttons are for increasing/decreasing the pulse frequency of the motor input which in turn changes the speed of the rover. The red blinking light on the motherboard indicates that rover command input has been received over UDP networking connection.
Extra Features
I would like to make the rover compatible with game controllers, such as Xbox One controller and iOS MFi controllers. I will write about that is a future post. Implementation of controllers to an android phone is quite easy but I don’t have one at my disposal right now. On a second thought, it might be better to use a base station to control multiple connected drones in formation instead of using one single phone to control one drone at a time.