msgbartop
This is a story which is based on a true story, which is based on a lie
msgbarbottom

25 ago 20 Adaptación a IoT de un robot de limpieza Dirt Devil e integración en Home Assistant

Estos días he estado juegueteando un poco con un viejo robot de limpieza doméstico del Lidl, un Dirt Devil. Es un robot de limpieza bastante básico, con algo ya de tiempo a sus espaldas. No dispone de ninguna capacidad de integración con sistemas de domótica, ni nada que se le parezca. Es más, ni siquiera tiene WiFi. Pero tiene algo interesante: su funcionamiento se basa en el uso de un chip etiquetado como RV285R, que controla toda una placa de circuitos integrados para realizar las funciones del robot: movimiento de los cepillos, ventilador de succión, detección de golpes, sensores de vacío para que no se caiga por escaleras… El caso es que buscando un poco por Internet, encontré un par de páginas sumamente interesantes sobre cómo reemplazar este chip por un sistema Arduino. Así que aprovechando que sigo de vacaciones, no podía menos que dedicar algo de tiempo al asunto.

De acuerdo a la información compartida por Paijmans, el chip RV285R trabaja a 5 voltios, por lo que es posible hacerlo funcionar directamente en dispositivos Arduino. Con un trabajo de ingeniería inversa, fue capaz de obtener la información de a qué se dedican los pines del chip:

rv285r-pinout

Yo he podido refinarlo un poco más, distinguiendo las funcionalidades de los pines:

  • P50: Led azul, o de encendido
  • P67: Detector de colisión (bumper)
  • P66: Zumbador interno
  • VDD: 5 voltios
  • P65: Nada
  • P64: Ventilador
  • P63: Nada (Etiquetado como Reset por el fabricante)
  • P51: Led rojo, o de indicación de batería baja
  • P52: Motor de rueda izquierda, funcionamiento hacia delante
  • P63: Motor de rueda izquierda, funcionamiento hacia atrás
  • VSS: Tierra
  • P60: Nada (aunque sospecho que es detector de depósito lleno del robot)
  • P61: Motor de rueda derecha, funcionamiento hacia delante
  • P62: Motor de rueda derecha, funcionamiento hacia atrás

Una vez identificados los elementos del chip, era hora de abrir el robot. Se puede apreciar el chip en la parte central de la placa:

IMG_20200824_103439438

Aunque en Paijmans indican que es posible puentear cada una de las patas del chip y conectarlas directamente al arduino (ya que éste tiene más fuerza en las señales que las que pasa el chip), en mi caso he optado por desoldarlo, y conectarle cables de prototipado. Aunque en un principio parecía una buena idea, al poner de nuevo las carcasas del robot, en mi caso saltaron un par de soldaduras de sus pistas, por lo que tuve que hacer trabajo extra de soldadura. Recomiendo soldar directamente al cable:

IMG_20200824_185905184

Una vez soldados los cables, el siguiente paso es el dispositivo Arduino. He optado por hacer uso de un Wemos D1 Mini Pro, de la familia de los ESP8266, ya que proporciona capacidades WiFi, y viene sin los bornes para los cables de prototipado, por lo que podía soldar directamente los cables para ahorrar espacio. Para la lógica del mismo me he basado en la creada por Conrad Vassallo (Converting a cheap Vacuum Robot into IoT) en su proyecto de domotización, ya que está preparado para integrar directamente en un sistema de domótica Home Assistant.

IMG_20200824_185913934_HDR

Algunos comentarios:

  • He tenido que hacer algunas modificaciones al trabajo de Conrad, ya que en el código que comparte faltaba la parte de conectar a la red WiFi el ESP8266. También he eliminado algunas librerías de las que no se hacía uso (EEPROM, Wire). Además he tenido que adaptar los valores de detección de batería baja (más en el siguiente punto) para que se amoldara a mi caso.
  • Conrad ha añadido al proyecto base una idea interesante: un detector de voltaje de la batería de 16v del robot. Usa para ello el pin A0 del ESP8266, que tiene capacidad para medir voltajes. Sin embargo, es necesario utilizar un conversor de voltaje para no quemar el disposito. Un simple divisor de voltaje con dos resistencias funcionará bien, y esta información estará disponible para Home Assistant.
  • Carga el código antes de soldar los pines provenientes de la placa. Una vez soldados, ya no es posible cargar código al Wemos a través del puerto microUSB, ya que el pin D4 mete ruido. En mi caso, tuve que desoldar. Para evitar este problema sería interesante añadir actualización OTA, pero me temo que eso quedará para una nueva versión.
pinout-wemos-mini-d1

Esquema de conexiones del D1 Mini Pro. Atención al uso del pin D4 para el envío de datos como puerto serie desde el Arduino IDE

Por último, queda la parte de Home Assistant. Con añadir el siguiente código a la configuración, el sistema estará preparado para interactuar con el robot:

vacuum:
- platform: mqtt
command_topic: "vacuum/command"
battery_level_topic: "vacuum/state"
battery_level_template: "{{ value_json.battery_level }}"
charging_topic: "vacuum/state"
charging_template: "{{ value_json.charging }}"
cleaning_topic: "vacuum/state"
cleaning_template: "{{ value_json.cleaning }}"
docked_topic: "vacuum/state"
docked_template: "{{ value_json.docked }}"
error_topic: "vacuum/state"
error_template: "{{ value_json.error }}"

El intercambio de información entre el robot y Home Assistant se realiza mediante MQTT. A continuación se puede ver una captura de los datos intercambiados:

mqtt-capture

¿Y el resultado? Espectacularmente bueno. Dejo un par de vídeos. El primero, con el robot aún abierto, y con una Victorinox haciendo de contrapeso, ya que si no, los sensores de vacío entran en funcionamiento:

…y el segundo, ya con las carcasas colocadas, y probando algunas funcionalidades adicionales, como la limpieza específica en un punto concreto:

Fuentes:

VN:F [1.9.20_1166]
Rating: 10.0/10 (1 vote cast)
Adaptación a IoT de un robot de limpieza Dirt Devil e integración en Home Assistant, 10.0 out of 10 based on 1 rating
Comparte este artículo:
  • Twitter
  • Facebook
  • email
  • StumbleUpon
  • Delicious
  • Google Reader
  • LinkedIn
  • BlinkList

Etiquetas: , , , , , , , , ,

Comentarios de los lectores

  1. |

    Hola Yuri, intenté replicar el proyecto con un ESP32. Puedo conectarme y ser reconocido por el broker MTTQ, pero cuando remplazo el rv285r con esp32, el led de batería baja se enciende, el ventilator y la rueda derecha gira lentamente.
    También deshabilité la función que apaga la aspiradora cuando la batería está baja pero el resultado no cambia.
    Tienes algún consejo?
    Podrías compartir el código que subiste?
    Mil gracias :)

    VA:F [1.9.20_1166]
    Rating: 0.0/5 (0 votes cast)
    Responder a este comentario
    • |

      Hola, Daniel, gracias por escribir. En principio, con el ESP32 tendría que funcionarte igual. Me da la sensación de que el problema que estás teniendo es que tienes algunos de los pines que utilizas para batería baja, ventilador y rueda derecha incorrectamente asignado en el mapeado de pines, habría que revisarlo con calma.

      Sin problema, te pego mi código por aquí:

      #include <esp8266wifi.h>
      #include <pubsubclient.h>
      #include <arduinojson.h>

      const char *ssid = “XXXXXXXX”;
      const char *password = “XXXXXXXX”;
      const char *mqtt_server = “XXXXXXXXXX”;
      const char *device_id = “DirtDevil”;

      WiFiClient espClient;
      PubSubClient client(espClient);

      //PINS
      const byte PWR_LED_PIN = D0;
      const byte BUMPER_PIN = D1;
      const byte BUZZER_PIN = D2;
      const byte VACUUM_PIN = D5;
      const byte LOW_BATT_LED_PIN = D6;
      const byte LEFT_MOTOR_FORWARD_PIN = D7;
      const byte LEFT_MOTOR_REVERSE_PIN = D8;
      const byte RIGHT_MOTOR_FORWARD_PIN = D3;
      const byte RIGHT_MOTOR_REVERSE_PIN = D4;
      const byte VCC_PIN = A0;

      enum Directions {FORWARD, REVERSE, LEFT, RIGHT, STOP};
      enum Features {turn_on, turn_off, locate, start_pause, stop, clean_spot, return_to_base};

      Features Status = turn_off;

      char message_buff[100];

      bool cleaning = false;
      bool docked = false;
      bool charging = false;
      String error = “Ready”;

      float vout = 0.0;
      float vin = 0.0;
      int value = 0;
      int lmotor = 1024;
      int rmotor = 1024;
      bool cleaningspot = false;

      long lastMsg = 0;
      char msg[256];

      void callback(char *led_control, byte *payload, unsigned int length)
      {
      Serial.print(“Message arrived [");
      Serial.print(led_control);
      Serial.println("] “);

      int i;
      for (i = 0; i < length; i++)
      {
      message_buff[i] = payload[i];
      }
      message_buff[i] = '\0';

      String msgString = String(message_buff);
      Serial.println(msgString);
      if (strcmp(led_control, "vacuum/command") == 0)
      {
      if (msgString == "turn_off") {
      Status = turn_off;
      }

      if (msgString == "turn_on") {
      Status = turn_on;
      }

      if (msgString == "locate") {
      Status = locate;
      }

      if (msgString == "start_pause") {
      Status = start_pause;
      if (cleaning == true)
      Status = turn_off;
      else
      Status = turn_on;
      }

      if (msgString == "stop") {
      Status = stop;
      }

      if (msgString == "clean_spot") {
      Status = clean_spot;
      }

      if (msgString == "return_to_base") {
      Status = return_to_base;
      }

      }
      }

      void reconnect()
      {
      while (!client.connected())
      {
      Serial.print("Attempting MQTT connection...");
      if (client.connect(device_id))
      {
      Serial.println("connected");
      publishData();
      digitalWrite(LOW_BATT_LED_PIN, LOW);
      digitalWrite(PWR_LED_PIN, HIGH);
      client.subscribe("vacuum/command"); // write your unique ID here
      }
      else
      {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      digitalWrite(PWR_LED_PIN, LOW);
      digitalWrite(LOW_BATT_LED_PIN, HIGH);
      delay(5000);
      }
      }
      }

      /**
      * Connect to wifi
      */
      void setup_wifi() {
      delay(10);
      // We start by connecting to a WiFi network
      Serial.println();
      Serial.print("Connecting to ");
      Serial.println(ssid);
      WiFi.mode(WIFI_STA);
      WiFi.begin(ssid, password);
      while (WiFi.status() != WL_CONNECTED) {
      delay(500);
      Serial.print(".");
      }
      Serial.println("");
      Serial.println("WiFi connected");
      Serial.println("IP address: ");
      Serial.println(WiFi.localIP());
      }

      void setup()
      {
      Serial.begin(115200);
      setup_wifi();

      client.setServer(mqtt_server, 1883); // change port number
      client.setCallback(callback);

      //Set Outputs
      pinMode(PWR_LED_PIN, OUTPUT);
      pinMode(BUZZER_PIN, OUTPUT);
      pinMode(VACUUM_PIN, OUTPUT);
      pinMode(LOW_BATT_LED_PIN, OUTPUT);
      pinMode(LEFT_MOTOR_FORWARD_PIN, OUTPUT);
      pinMode(LEFT_MOTOR_REVERSE_PIN, OUTPUT);
      pinMode(RIGHT_MOTOR_FORWARD_PIN, OUTPUT);
      pinMode(RIGHT_MOTOR_REVERSE_PIN, OUTPUT);

      //Set Inputs
      pinMode(VCC_PIN , INPUT);
      pinMode(BUMPER_PIN, INPUT);

      //Initialise Pins
      digitalWrite(PWR_LED_PIN, LOW);
      digitalWrite(BUZZER_PIN, LOW);
      digitalWrite(VACUUM_PIN, LOW);
      digitalWrite(LOW_BATT_LED_PIN, LOW);
      digitalWrite(LEFT_MOTOR_FORWARD_PIN, LOW);
      digitalWrite(RIGHT_MOTOR_FORWARD_PIN, LOW);
      digitalWrite(RIGHT_MOTOR_REVERSE_PIN, LOW);

      }

      void move_robot(Directions direction)
      {
      switch (direction) {
      case FORWARD:
      analogWrite(LEFT_MOTOR_FORWARD_PIN, lmotor);
      analogWrite(LEFT_MOTOR_REVERSE_PIN, LOW);
      analogWrite(RIGHT_MOTOR_FORWARD_PIN, rmotor);
      analogWrite(RIGHT_MOTOR_REVERSE_PIN, LOW);
      break;

      case REVERSE:
      digitalWrite(LEFT_MOTOR_FORWARD_PIN, LOW);
      digitalWrite(LEFT_MOTOR_REVERSE_PIN, HIGH);
      digitalWrite(RIGHT_MOTOR_FORWARD_PIN, LOW);
      digitalWrite(RIGHT_MOTOR_REVERSE_PIN, HIGH);
      break;
      case RIGHT:
      digitalWrite(LEFT_MOTOR_FORWARD_PIN, LOW);
      digitalWrite(LEFT_MOTOR_REVERSE_PIN, HIGH);
      digitalWrite(RIGHT_MOTOR_FORWARD_PIN, HIGH);
      digitalWrite(RIGHT_MOTOR_REVERSE_PIN, LOW);
      break;
      case LEFT:
      digitalWrite(LEFT_MOTOR_FORWARD_PIN, HIGH);
      digitalWrite(LEFT_MOTOR_REVERSE_PIN, LOW);
      digitalWrite(RIGHT_MOTOR_FORWARD_PIN, LOW);
      digitalWrite(RIGHT_MOTOR_REVERSE_PIN, HIGH);
      break;
      case STOP:
      digitalWrite(LEFT_MOTOR_FORWARD_PIN, LOW);
      digitalWrite(LEFT_MOTOR_REVERSE_PIN, LOW);
      digitalWrite(RIGHT_MOTOR_FORWARD_PIN, LOW);
      digitalWrite(RIGHT_MOTOR_REVERSE_PIN, LOW);
      break;
      }
      }

      void publishData()
      {
      const size_t capacity = JSON_ARRAY_SIZE(6) + JSON_OBJECT_SIZE(7);
      DynamicJsonDocument doc(capacity);

      int value = analogRead(VCC_PIN);
      //int value = 942;
      vin = map(value, 7, 345, 0, 100);

      doc["battery_level"] = vin;
      doc["docked"] = docked;
      doc["cleaning"] = cleaning;
      doc["charging"] = charging;
      doc["error"] = error;

      serializeJson(doc, msg);
      //serializeJson(doc, Serial);
      Serial.println(String(value) + ":" + String(vin));
      client.publish("vacuum/state", msg, true);
      }

      void Buzzer()
      {
      tone(BUZZER_PIN, 1000);
      delay(1000);
      noTone(BUZZER_PIN);
      delay(1000);
      }

      void loop()
      {
      if (!client.connected())
      {
      reconnect();
      }
      client.loop();

      switch (Status)
      {
      case turn_on:
      cleaning = true;
      digitalWrite(PWR_LED_PIN, HIGH);
      digitalWrite(VACUUM_PIN, HIGH);

      if (digitalRead(BUMPER_PIN) == LOW)
      {
      move_robot(REVERSE);
      delay(250);
      move_robot(RIGHT);
      delay(random(500));
      }
      else
      {
      move_robot(FORWARD);
      }
      break;

      case turn_off:
      cleaning = false;
      digitalWrite(PWR_LED_PIN, LOW);
      move_robot(STOP);
      digitalWrite(VACUUM_PIN, LOW);
      break;

      case locate:
      Buzzer();
      break;

      case start_pause:
      if (cleaning) {
      cleaning = false;
      Status = turn_off;
      }
      else
      {
      cleaning = true;
      Status = turn_on;
      }
      break;

      case stop:
      cleaning = false;
      Status = turn_off;
      break;

      case clean_spot:
      cleaning = true;
      if (cleaningspot)
      {
      rmotor = 1024;
      cleaningspot=false;
      Serial.println("clean spot off");
      }
      else
      {
      rmotor = 512;
      cleaningspot=true;
      Serial.println("clean spot on");
      }
      Status = turn_on;
      break;

      case return_to_base:
      break;
      }

      long now = millis();
      if (now - lastMsg > 5000) {
      lastMsg = now;
      publishData();

      //if voltage reaches 14V switch off and play SOS beeps
      int value = analogRead(VCC_PIN);
      //int value = 1024;
      Serial.print(“Received voltage: “);
      Serial.println(value);
      if (value < 225 && Status != turn_off)
      {
      Serial.println("Battery low alert!");
      cleaning = false;
      digitalWrite(PWR_LED_PIN, LOW);
      move_robot(STOP);
      digitalWrite(VACUUM_PIN, LOW);
      Status = turn_off;
      error = "Low Battery";

      int i;
      int pause;
      for (i = 0; i < 5; i++)
      {
      int x;
      for (x = 1; x < 10; x++)
      {
      if (x > 3 && x < 7)
      pause = 100;
      else
      pause = 50;

      tone(BUZZER_PIN, 1000);
      delay(pause);
      noTone(BUZZER_PIN);

      if (x == 3 || x == 6)
      pause = 100;

      delay(pause);
      }
      delay(2000);
      }
      }
      }
      }

      VN:F [1.9.20_1166]
      Rating: 0.0/5 (0 votes cast)
      Responder a este comentario

Deje un comentario en yuri







6 − cinco =