Close

Roomba's first squeaks

A project log for Controll yer Roomba 600 series with ESP8266

Another ESP8266 project. If some hardware has an interface / API to connect to, it's waiting to be assimilated into the home-hive.

Simon JansenSimon Jansen 01/22/2022 at 19:180 Comments

There are plenty of Roomba builds to find on the interwebs. This is a blessing, but can also be a bit of a curse. None are exactly what I wanted to do. Mostly in bits, but whenever you combine bits and pieces and libraries from different sources in the Arduino IDE, it all goes to.... well it doesn't go. You know it's possible, but HOW?

My particular setup and wishes:

And for the future:

Most of the projects and examples I come across do single things and use delays or while loops somewhere. This doesn't suit my wishes, so I ended up rewriting a lot of stuff.

It's all a work in progress and will be neater, I promise. For now It's all a bit of a proof of concept.

I started with the OTA stuff:

https://randomnerdtutorials.com/esp8266-ota-updates-with-arduino-ide-over-the-air/

https://arduino-esp8266.readthedocs.io/en/latest/ota_updates/readme.html

I ended up using the example from the Arduino IDE from the ESP8266 board 'add-on'.  In the IDE --> preferences --> add as extra boards:

http://arduino.esp8266.com/stable/package_esp8266com_index.json

I have to use 'Generic ESP8285 Module' as board. Check out the examples for the OTA example. 

This feature is part of the running code though. So when (not if) my software gets stuck somewhere, or it loses connection to the WiFi and isn't able to reconnect. I can't upload a fix and have to disassemble the Roomba to upload software via serial. I kind of get a glimpse in the mindset of NASA and the engineers on the JWST. 

This is where my second ESP8266 that I soldered to a DIP24 connector comes in. I have this setup on a breadboard and connected to my laptop via serial. 

I can upload software OTA to this board first to check if the connection holds and I can still send new software to it. I've been very happy with this setup several times. Especially when using callbacks, pointers to arrays, interrupt service routines and a mix of libraries from different sources. I've had several stack overflows, illegal register requests and other. Whenever this happens, the ESP8266 can spit out a stack trace and and exception cause and you get some clues as to what went wrong.

https://arduino-esp8266.readthedocs.io/en/latest/faq/a02-my-esp-crashes.html

Communication with HA via MQTT:

Next, with a stable connection, I want to get some communication via MQTT so I can send commands and receive status updates when I can't connect physically. For this, I use the PubSubClient library. 

https://github.com/knolleary/pubsubclient

Now we have two libraries / examples that both use some internet connection. They use a different library / class for the web-client though. And then it becomes a challenge to combine the two functions in one sketch. 

#include <ESP8266WiFi.h>    //For ESP8266
#include <PubSubClient.h>   //For MQTT
#include <ESP8266mDNS.h>    //For OTA
#include <WiFiUdp.h>        //For OTA
#include <ArduinoOTA.h>     //For OTA

// WIFI configuration
#define wifi_ssid "***"
#define wifi_password "***"

//OTA configuration
//const char* host = "Roomba632"; //84ed23
const char* host = "ESP8285_Testplatform"; //80e74a
const char * sketchpass = "***";

// MQTT configuration
#define mqtt_server "192.168.1.***"
#define mqtt_user "***"
#define mqtt_password "****"  
#define mqtt_sub_topic "homeassistant/device/roomba/set"
#define mqtt_pub_topic "homeassistant/device/roomba/state"
String mqtt_client_id= host;   //This text is concatenated with ChipId to get unique client_id
long lastReconnectAttempt = 0;

void callback(char* topic, byte* payload, unsigned int length) {
  // handle message arrived
  // Switch case for commands only uses first char
  switch (command) {
    case 'C': //Clean
      Serial.write(128);
      Serial.write(135);  
      break;
    case 'D': //Dock
      Serial.write(128);
      Serial.write(143);  
      break;
    case 'S': //Spot
      Serial.write(128);
      Serial.write(134); 
      break; 
    case 'M': //Music
      Serial.write(128);
      Serial.write(140);  
      Serial.write(1);  
      Serial.write(3);  
      Serial.write(48);  
      Serial.write(32);  
      Serial.write(52);  
      Serial.write(32);  
      Serial.write(55);  
      Serial.write(32);  
      Serial.write(141);
      Serial.write(1);  
      break;
    default: //Play tune
      Serial.write(128);
      Serial.write(135); 
      break;     
  }
}
// Start MQTT client
WiFiClient espClient;
PubSubClient mqtt_client(espClient);

// Necesary to make Arduino Software autodetect OTA device
//WiFiServer TelnetServer(8266);

void setup() { 
  Serial.begin(115200);
  WiFi.begin(wifi_ssid, wifi_password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
  }
  ArduinoOTA.onStart([]() { //Make sure device is in a safe mode
    //store params to EEPROM?   
  });
  ArduinoOTA.onEnd([]() { //Device will reboot

  });
  ArduinoOTA.onError([](ota_error_t error) {
    (void)error;
    ESP.restart();
  });
  ArduinoOTA.begin();
  mqtt_client.setServer(mqtt_server, 1883);
  mqtt_client.setCallback(callback);
  // Serial.println("Ready");
}
boolean attempt_reconnect() {
  if (mqtt_client.connect(mqtt_client_id.c_str(), mqtt_user, mqtt_password)) {
    // Once connected, publish autodiscover config message and current state..
    mqtt_client.publish(mqtt_pub_topic, "up");
    // ... and resubscribe
    mqtt_client.subscribe(mqtt_sub_topic);
    // Serial.println("connected");
  }
  else {
    // Serial.print("failed, rc=");
    // Serial.print(mqtt_client.state());
    // Serial.println(" try again in 5 seconds");
  }
  return mqtt_client.connected();
}
void mqtt_handler() {
  //Serial.println("MQTT-handler");
  if (!mqtt_client.connected()){
    if (millis() > (lastReconnectAttempt + 5000)) {
      lastReconnectAttempt = millis();
      // Attempt to reconnect
      // Serial.println("attempt to reconnect");
      if (attempt_reconnect()) {
        lastReconnectAttempt = 0;
      }
    }
  } 
  else {
    // Client connected
    // Serial.println("connected to MQTT broker");
    mqtt_client.loop();
  }  
}  
void loop() {
  ArduinoOTA.handle();
  mqtt_handler();
  //
}

It's not real pretty, but it works! Next up: add more stuff!

Discussions