Arduino-Bastelei: Die smarte(re) Waschmaschine (Update)

Es gibt ein Update zum meinem Waschmaschinenprojekt. Zur Erinnerung: Es ging darum, bei einer recht alten Waschmaschine eine Benachrichtigung am Handy zu bekommen, sobald der Waschvorgang beendet ist.

Die Spielerei hat anfangs recht gut funktioniert, im Laufe der Zeit jedoch immer schlechter. Meine Erklärung: Der Vibrationssensor hat der Belastung nicht standgehalten. Der erste Sensor ging wirklich kaputt, ein zweiter wurde auch laufend schlechter. Schlechter bedeutet, dass die Werte immer mehr streuten und daher das zeitliche Vibrationsprofil, über das der letzte Schleudervorgang und damit das Ende des Waschvorgangs detektiert wurde, immer verrauschter und uneindeutiger wurde. Es gab Fehlalarme.

3-Achsen-Beschleunigungssensor GY-61

Über ein anderes Bastelprojekt bin ich dann auf einen einfachen 3-Achsen-Beschleunigungssensor gestoßen, den GY-61. Der gibt an drei analogen Ausgängen einfach die Beschleunigung für drei orthogonale Achsen (x, y, z) aus. Im Prinzip so ein Teil, welches heute in jedem Smartphone z.B. die Bildschirmorientierung steuert. Gravitation ist ja auch nichts anderes als eine Beschleunigung; dreht man das Handy, dann wirkt diese Beschleunigung (zumindest anteilig) in eine andere Richtung.

Ich habe nun den alten Vibrationssensor durch den neuen Beschleunigungssensor ersetzt. Von den drei Achsen kann ich allerdings nur ein einzige nutzen, weil der Wemos D1 mini nur einen analogen Eingang bietet. Hier ein paar Bilder vom Umbau:

Die Werte, die der Sensor in den drei Schleuderphasen der Waschmaschine liefert sind um Welten besser als das, was der Vibrationssensor jemals geliefert hat. Hier der Plot eines typischen Waschvorgangs:

Die Werte werden für mich zur Kontrolle an meinen Raspberry Pi geschickt und mit Hilfe der Bibliothek dygraphs geplottet. Blau: Beschleunigung. Rot: Der Schwellwert. Gelb: Dauer über dem Schwellwert. Rot: Auslösen der Benachrichtigung.

Man sieht hier schön, dass der letzte Schleudergang stärker und vor allem länger ist als die ersten beiden. Seit dem Einbau des neuen Sensors wurde damit das Ende des Waschvorgangs hundertprozentig richtig erkannt.

Ach ja, und hier noch der aktualisierte Code, falls der jemanden interessiert:

#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>


// PIN config:
#define PinVibration A0
// #define PinVibration D3
#define PinReset D0


// timing varaibles:
unsigned long delay_between_measurements = 50;    // ms
unsigned long averaging_time             = 4500;  // ms
unsigned long time_between_data_points   = 5000;  // ms   => 12 measurement points per minute
unsigned long notification_delay         = 4;     // min

// other variables:
double        vibration                 = 0.0;     // vibration value
double        vibration_log_thres       = 20.0;     // if vibration is higher than this value a log entry is created
double        vibration_thres           = 100.0;   // if vibration is higher than this value the last vibration_last[] entry is set to 1
                                          // Erfahrungswerte:
                                          //
                                          // 120 ist zu hoch, das liefert z.B. bei Bettwäsche schon mal keinen Wert, die läuft sehr rund
                                          // 80 ist zu niedrig, das löst ggf. gerade so 2x aus.
bool          vibration_last            [28];      // => array over the last 2.xx minutes (depends on variable time_between_data_points)
unsigned long means                     = 0;       // number of means per measurement
unsigned long notification_delay_start  = 0;       //start time of notification delay (milliseconds)

// WIFI credentials:
const char* wifi_host     = "Waschmaschine";
const char* wifi_ssid     = "MY_SSID";
const char* wifi_password = "MY_WIFI_PASSWORD";

// define URLs (BTW: use HTTP instead of HTTPS!):
String URL_log            = "http://MY_URL/waschmaschine/";
String URL_notification[] = {
                            "http://maker.ifttt.com/trigger/waschmaschinefertig/with/key/MY_KEY_1",
                            "http://maker.ifttt.com/trigger/waschmaschinefertig/with/key/MY_KEY_2",
                            };
// we need this line to walk through the string loop using "ArraySize()":
template< typename T, size_t N > size_t ArraySize (T (&) [N]){ return N; }    // see http://forum.arduino.cc/index.php?topic=157398.0


void setup() {
  
  // set PINs and PIN modes:
  pinMode(PinVibration, INPUT);
  pinMode(PinReset, OUTPUT);
  digitalWrite(PinReset, HIGH);
  pinMode(BUILTIN_LED, OUTPUT);

  // start serial output:
  Serial.begin(115200);

  // connect to WiFi network:
  connect2Wifi();
}



void loop() {
  
  // measure a vibration value:
  measure_vibration();

  // log the vibration value:
  if (vibration > vibration_log_thres || sum_over_thres() >= 1) {
    send_log();
  }

  // start notification delay if end of long vibration is detected:
  if (notification_delay_start == 0 && sum_over_thres() == sizeof(vibration_last)) {
    notification_delay_start = millis();
    Serial.println("[Notification] End of spinning detected!");
    Serial.println("[Notification] Start sending a notification in " + (String)notification_delay + " minutes.");
  
  }

  // send notification if end of notification delay is reached:
  if (notification_delay_start > 0 && millis() - notification_delay_start >= notification_delay * 60 * 1000) {
    notification_delay_start = 0;
    send_notification();

    // Sometimes the board does no longer respond, maybe due to an overflow of the millis-varaibles.
    // Try to reset the board 10s after sending the notifications:
    // RESET the board:
    delay(10*1000);
    digitalWrite(PinReset, LOW);

  }

  // wait rest of the time:
  delay(time_between_data_points - averaging_time);

}





///////////////////// function: measure the vibration value 
void measure_vibration() {
  Serial.println("=================================================================================");
  Serial.printf("[Vibration] Starting vibration measurement for %.1f seconds", averaging_time / 1000.);

  vibration = 0;
  means = 0;
  unsigned long starttime = millis();
  /*while (millis() - starttime < averaging_time) {
    means ++;
    if (means % 10 == 0) Serial.print(".");
    vibration = vibration + (analogRead(PinVibration) - vibration) / means;
    // delay(10);
    // vibration = vibration + (pulseIn(PinVibration, HIGH) - vibration) / means;    // see http://www.theorycircuit.com/sw-420-vibration-sensor-arduino-interface/
    delay(delay_between_measurements-10);
  }*/
  int x = 0;
  int xlast = 0;
  int xdiff = 0;
  while (millis() - starttime < averaging_time) {
    means ++;
    if (means % 10 == 0) Serial.print(".");
    x = analogRead(PinVibration);
    xdiff = abs(x - xlast);
    vibration = vibration + (xdiff - vibration) / means;
    xlast = x;
    delay(delay_between_measurements-10);
  }

  // explanation:
  // we use an bool array that contains zeros.
  // with every measurement the values of the bool array are shifted one step to the end
  // if a vibration measurement gives a value larger than the threshold => write a 1 to the first position of the bool array.
  // if the washing machine is spinning the array is filled with ones.
  // if the sum over the array values exceeds a defined value the notifications are sent
  shift_vibration_array();
  if (vibration >= vibration_thres) {
    vibration_last[0] = 1;
  }

  Serial.println();
  Serial.print(  "[Vibration] vibration           = "); Serial.print(vibration); Serial.println();
  Serial.print(  "[Vibration] vibration_log_thres = "); Serial.print(vibration_log_thres); Serial.println();
  Serial.print(  "[Vibration] vibration_thres     = "); Serial.print(vibration_thres); Serial.println();
  Serial.printf( "[Vibration] means               = %i\n", means);
  Serial.println("[Vibration] vibration_last      = " + gen_vibration_string());
  Serial.println("[Vibration] progress             = " + (String)sum_over_thres() + "/" + (String)sizeof(vibration_last));

}


///////////////////// function: send notification(s) 
void send_notification() {

  // Serial.println("[Notification] End of spinning detected!");
  // Serial.println("[Notification] Start sending a notification in " + (String)notification_delay + " minutes.");
  Serial.println("[Notification] Sending the notification(s).");
  //delay(notification_delay * 60 * 1000);

  // send notifications:
  for (int i = 0; i < ArraySize(URL_notification); i++) {
    Serial.println("[Notification] Sending notification #" + (String)(i+1) + ":");
    send_HTTP_request(URL_notification[i]);
  }

  // reset the vibration array (to avoid multiple alarms):
  for (int i = 0; i < sizeof(vibration_last); i++) { vibration_last[i] = 0; }
}


///////////////////// function: send log values to server 
void send_log() {

  send_HTTP_request(URL_log + 
                              "?means=" + (String)means +
                              "&vibration=" + (String)vibration + 
                              "&vibration_log_thres=" + (String)vibration_log_thres + 
                              "&vibration_thres=" + (String)vibration_thres + 
                              "&vibration_last=" + gen_vibration_string() + 
                              "&sum_over_thres=" + (String)sum_over_thres() +
                              "&mac=" + WiFi.macAddress() + 
                              "&hostname=" + WiFi.hostname() + 
                              "&ip=" + WiFi.localIP().toString() + 
                   "");
}


///////////////////// function: send a http request 
void send_HTTP_request(String url) {

  HTTPClient http;

  Serial.println("[HTTP] Request URL: " + url);
  http.begin(url);

  // start connection and send HTTP header
  int httpCode = http.GET();

  if (httpCode > 0) { // httpCode will be negative on error
    Serial.printf("[HTTP] Return code: %d\n", httpCode);

    if (httpCode == HTTP_CODE_OK) { Serial.println(http.getString()); }
  }
  else {
    Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
  }

  http.end();
}


///////////////////// function: shift the values of the vobration array
void shift_vibration_array() {
  // move all array elements one step to the end
  // the first element is set to zero
  for (int i = sizeof(vibration_last) - 1; i > 0; i--) {
    vibration_last[i] = vibration_last[i - 1];
  }
  vibration_last[0] = 0;
}


///////////////////// function: calculate the sum of the vibration array
int sum_over_thres() {
  // calculate the sum of values 1 in the vibration array
  int vibration_sum = 0;
  for (int i = 0; i < sizeof(vibration_last); i++) {
    vibration_sum += vibration_last[i];
  }
  return vibration_sum;
}

///////////////////// function: return the values of the vibration array as string 
String gen_vibration_string() {
  
  String vibration_string = "";
  for (int i = 0; i < sizeof(vibration_last); i++) {
    vibration_string = vibration_string + (String)vibration_last[i];
  }
  return vibration_string;
}


///////////////////// function: establish the WiFi connection 
void connect2Wifi() {
  Serial.println();
  Serial.printf("\n[WiFi] Connecting to SSID '%s' ", wifi_ssid);

  WiFi.mode(WIFI_STA);
  WiFi.hostname(wifi_host);
  WiFi.begin(wifi_ssid, wifi_password);

  while (WiFi.status() != WL_CONNECTED) {
    digitalWrite(BUILTIN_LED, LOW);
    delay(100);
    Serial.print(".");
    digitalWrite(BUILTIN_LED, HIGH);
    delay(100);
  }
  Serial.println(" Connected!");
  Serial.println("[WiFi] Hostname:  " + WiFi.hostname());
  Serial.println("[WiFi] MAC:       " + WiFi.macAddress());
  Serial.println("[WiFi] IP:        " + WiFi.localIP().toString());
  Serial.println();
  
  digitalWrite(BUILTIN_LED, LOW);
}

7 Reaktionen auf “Arduino-Bastelei: Die smarte(re) Waschmaschine (Update)

    1. dasaweb Beitragsautor

      Du meinst an die Schaltung, die die Tür freigibt, damit man diese öffnen kann? Wäre ne Option gewesen. Allerdings wollte ich möglichst „nicht-invasiv“ bleiben. Die ursprüngliche Idee war, das einfach außen an der Maschmaschine ohne Eingriff hinzubekommen. Das ging dann mit dem zuerste eingesetzten Vibrationssensor nicht wirklich, mit dem Beschleunigungssensor könnte es jetzt wieder gehen.

      Antworten
  1. ernschd

    Hallo,

    beim Kompilieren habe ich gesehen, dass der Code in Zeile 131 irgendwie durcheinander gekommen ist. Z.B. steht die Methode send_log() auskommentiert in dieser Zeile.
    Dies war wohl auch schon bei der vorherigen Variante so.

    Ansonsten finde ich, dass das ein cooles Projekt ist. Ich freue mich schon darauf, es auszuprobieren.

    Antworten
  2. leo_poldX

    Moin,

    vielen vielen dank für die tolle Idee mit dem GY-61 Sensor an der Waschmaschine, hab ich bei mir jetzt auch, meine Frau freut es (tatsächlich!)!

    Da ich bei mir alle ESP32 / ESP8266 Geräte via esphome.io verwalte habe ich einen anderen Weg eingeschlagen (ich versuche halt immer zu vermeiden Business-Logik auf den ESPs zu haben, die sollen eigentlich nur Werte auslesen und irgendwo hin schicken).

    Falls jemand das ein wenig Zukunftssicherer umsetzen möchte hier mein vorgehen:

    in der yaml von esphome für den einen eingesetzten ESP8266 eine der Achsen von GY-61 einfach wie folgt konfiguriert:

    „`yaml
    sensor:
    – platform: adc
    pin: A0
    name: „basement_washer“
    update_interval: 5s
    „`

    in der yaml ist natürlich noch ein mqtt Broker definiert (ich verwende kein Home Assistant!)

    Im sowieso vorhandenen mqtt Broker kommt dann der aktuelle float Wert alle 5 Sekunden ganz von allein an.

    Über node-RED (war ebenfalls schon vorhanden) schicke ich ALLE eingehenden Sensor Daten im MQTT an eine InfluxDB (ich habe diverse ESP Geräte mit unterschiedlichen Sensoren im ganzen Haus verteilt, deswegen auch esphome.io wegen der Verwaltung dieser).

    Die InfluxDB werte ich dann einfach über Grafana für die einzelnen Sensoren aus.
    Über Grafana hab ich dadurch auch die Benachrichtigungen entsprechend fein justieren können (ohne jedes mal den Code neu kompilieren zu müssen) und kann so auch nur bestimmte Alerts zusätzlich an das Smartphone über meiner Frau schicken (bei uns über Telegram).

    Alle genannten Services laufen für sich gekapselt in Docker Containern welche wiederum via Portainer verwaltet werden.

    Docker Container:
    – portainer/portainer(GUI für die Container Verwaltung)
    – esphome/esphome(GUI für die ESP Verwaltung -> OTA!)
    – influxdb(DB für Zeitreihen)
    – grafana/grafana(Auswertung der InfluxDB)
    – nodered/node-red-docker(u.a. für mqtt zu influxdb)
    – eclipse-mosquitto:latest (mqtt broker)

    Worauf ich mit meinem Kommentar hinaus möchte: wenn man dieses Grundsetup der oben genannten Container durchführt, oder sogar einen Teil schon hat, dann hat man sich das definieren von Regeln für gewisse Zustände einfacher gemacht.

    Ich für meinen Teil kann jetzt Abends auf der Couch am Tablet über Grafana neue Views bauen oder Alerts einrichten – oder aber mit node-RED neue Flows auf Basis der eh vorliegende Werte definieren.

    Grüße,

    jemand von der Ostsee 🙂

    Antworten
    1. dasaweb Beitragsautor

      Hallo jemand von der Ostsee!

      Vielen Dank für den ausführlichen Kommentar, das klingt sehr geil. Allein wenn du die Begriffe Docker, InfluxDB, Grafana und NodeRed fallen lässt hast du mich schon 😉
      Dass du keine Business-Logik auf den Dingern haben willst ist verständlich und nur sinnvoll, wenn du mehrere Sensoren laufen hast. Ich habe da bisher wenig, der Schritt hin zur Zentralisierung fehlt mir noch (gibt gerade andere Baustellen). In meinen Augen haben solche Dinger auch dann einen besonderen Reiz, wenn sie als Standalone laufen, also keine weitere Infrastruktur brauchen. Dann sind aber halt auch weder Interaktionen noch komplexere Regeln möglich.

      esphome.io kannte ich bisher nicht, werde ich mir mal ansehen.

      Beste Grüße,
      Daniel

      PS: Deiner Stadt werden wir im Sommer mal einen Kurzbesuch abstatten, nur eine Nacht. Tipps?

      Antworten

    Mentions

  • 💬 Johannes Weber ????

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert