Arduino-Bastelei: Die smarte(re) Waschmaschine

Es wäre gelogen zu sagen, dass es bei unserer Waschmaschine dringenden Optimierungsbedarf gegeben hätte. Aber als Bastler sucht man ja immer nach einer möglichst sinnvollen Kanalisation des Basteltriebs. Daher hier die im Nachhinein zusammengelogene Motivation für das aktuelle Projekt:

Unsere Waschmaschine steht im Keller. Außerdem ist sie alt, fast 18 Jahre. Damals gab es noch kein „Ich bin fertig!“-Gepiepse. Also muss man nach dem Einschalten der Maschine in etwa abschätzen, wann sie fertig sein wird und sich am besten eine Erinnerung am Handy einstellen. Sonst könnte sich eine der folgenden dramatischen Szenen abspielen:

  • Die Maschine ist fertig und niemand räumt sie aus.
  • Die Maschine ist fertig und man räumt sie viel zu spät aus.
  • Die Maschine ist fertig und man merkt es zu spät, wodurch sich die nächste Maschine verzögert, deren Ende man wieder zu spät merkt, wodurch sich die näcshte Maschine… (das Prinzip ist klar).
  • Die Maschine ist fertig und irgend etwas anderes ganz schlimmes.

Wemos D1 mini

Da hat man als Bastler jetzt also einen Wemos D1 mini herumliegen (also einem Arduino-ähnlichen Mikrocontroller mit einem ESP8266-WLAN-Modul) und fragt sich, was man – bezüglich des oben geschilderten „Problems – sinnvolles damit machen könnte. Natürlich 1) irgendwie das Ende des Waschvorgangs detektieren und dann 2) irgendwie eine Nachricht absetzen, die dann wiederum irgend jemanden zum Ausräumen der Waschmaschine animieren soll.

Das Ende des Waschvorgangs

Es gibt im Netz einige Ansätze, wie man das machen könnte:

    • Wenn die Maschmaschine eine LED hat, die leuchtet, sobald der Waschvorgang beendet ist, kann man deren Licht mit einer Photodiode abgreifen und ist fertig.

 

    • Man könnte auch direkt die Spannung der LED verwenden und auslesen.

 

    • Man könnte den Stromverbrauch messen und hoffen, daraus Rückschlüsse über das Ende des Waschvorgangs ziehen zu können. Vielleicht nicht trivial, weil die Heizung ja auch viel Strom zieht, nicht nur das finale Schleudern.

 

    • Man könnte die Vibrationen des finalen Schleudergangs zu detektieren versuchen.

 

Vibrations-Sensor

Es gibt sicher noch mehr Möglichkeiten, ich habe mich für die letzt genannte entschieden, den Vibrations-Sensor. Die Idee war also, mit diesem Sensor ein zeitliches Profil der Vibrationen der Waschmaschine zu messen, die beim Schleudern entstehen. Und kurz nach dem letzten Schleudern sollte die Maschine dann fertig sein und die Benachrichtigung kann abgesetzt werden. Soweit die Idee. Ich hab also den Vibrationssensor außen an das Gehäuse der Waschmaschine geklebt und angefangen, damit Vibrationen zu messen, und zwar für alle drei Raumrichtungen. Leider hat sich nach Auswertung der Profile einiger Waschgänge herausgestellt, dass die Schwingungsdämpfung der Waschmaschine zu gut ist, um die Schleudergänge eindeutig zu identifieren. Gut für die Maschine, schlecht für die ihr zugedachte Smartheit. Daher wurde der Sensor im zweiten Anlauf mit Heißkleber innen in der Maschine angebracht, direkt an der (ungedämpften) Trommel. Danach wurden die drei Kabel für Ground, +5V und Data nach außen gelegt und der D1 mini mit Klebeband am Gehäuse befestigt:

Der Aufbau funktioniert einwandfrei, die Vibrationen beim Schleudern sind signifikant und reproduzierbar:

Vibrationsprofil eines Waschvorgangs über die Zeit. Zu sehen sind drei Schleudergänge. Der letzte ist im Vergleich zu den ersten beiden etwas höher, kosntanter und vor allem länger (etwas über 3 Minuten).

Der letzte Schleudergang ist im Vergleich zu den ersten beiden etwas höher, konstanter und vor allem länger (etwas über 3 Minuten). Findet man also den letzten Schleudergang hat man damit auch das Ende des Waschvorgangsgefunden (evtl. mit einem kleinen Delay für’s anschließende Abpumpen). Mit dieser Erkenntnis kann also der Code gestrickt werden. Noch eine Randbemerkung zur Konzeption: Es ist natürlich wichtig, dass es am Standort der Waschmaschine WLAN gibt, sonst hilft die ganze Intelligenz nichts. Aber das ist ja auch irgendwie klar.

Der Code

Der Wemos D1 mini kann mit der ganz normalen Arduino-IDE programmiert werden. Es muss lediglich das Board noch eingebunden werden, dazu findet man aber überall Anleitungen im Netz. Mein finaler aktueller Code sieht jetzt so aus:

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


// PIN config:
#define PinVibration A0


// timing varaibles:
unsigned long delay_between_measurements = 50;    // ms
unsigned long averaging_time             = 5000;  // ms
unsigned long time_between_data_points   = 6000;  // ms   => 10 measurement points per minute
unsigned long notification_delay         = 3;     // min

// other variables:
double        vibration            = 0.0;     // vibration value
double        vibration_log_thres  = 150.0;    // if vibration is higher than this value a log entry is created
double        vibration_thres      = 500.0;   // if vibration is higher than this value the last vibration_last[] entry is set to 1
bool          vibration_last         [28];    // => array over the last 2.8 minutes (depends on variable time_between_data_points)
unsigned long means                = 0;       // number of means per measurement

// WIFI credentials:
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(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();
  }

  // send motification if end of long vibration is detected:
  if (sum_over_thres() == sizeof(vibration_last)) {
    send_notification();
  }

  // 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 %i 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(delay_between_measurements); } // 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.");
  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.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);
}


Die Vibrationsprofile (wie das oben gezeigte) mussten ja aber auch irgendwie aufgenommen werden. Im Code oben sieht man, dass der D1 mini Vibrationswerte per HTTP an einen Server schickt. Dazu verwende ich meinen Raspberry PI, auf dem ich dann noch ein kleines PHP-Skript habe, welches die übermittelten Werte in ein Logfile schreibt:

<?

// print_r($_SERVER);
// print_r($_GET);


$log = "";
$log .= time();
$log .= "\t";
$log .= date("Y-m-d H:i:s");
$log .= "\t";
$log .= $_GET['mac'];
$log .= "\t";
$log .= $_GET['hostname'];
$log .= "\t";
$log .= $_GET['ip'];
$log .= "\t";
$log .= $_GET['vibration_log_thres'];
$log .= "\t";
$log .= $_GET['vibration_thres'];
$log .= "\t";
$log .= $_GET['vibration'];
$log .= "\t";
$log .= $_GET['vibration_last'];
$log .= "\t";
$log .= $_GET['sum_over_thres']."/".strlen($_GET['vibration_last']);
$log .= "\n";


if (file_put_contents("vibration.log", $log, FILE_APPEND)) 	echo "[RaspberryPI] writing values to log file: ".$log;
else 														echo "[RaspberryPI] writing values to log file: **error**";

Fertig ist die Laube. Wenn die Maschine jetzt nicht läuft misst der D1 mini nur Rauschwerte, die unterhalb einer definierten Schwelle liegen und macht weiter nichts. Liefert die Messung aber Werte oberhalb der Schwelle werden diese über den Raspberry Pi mitprotokolliert. Und wird jetzt noch das Ende des Waschvorgangs detektiert wird eine Meldung abgesetzt und weiter gemessen.

Die Benachrichtigungen

Die Meldungen über das Ende des Waschvorgangs werden über das hier schon einmal vorgestellte „If This Than That“ (IFTTT) realisiert. Dazu nuzt man einfach den auf solche Anwendungen ausgeleten Maker-Kanal des Dienstes.  IFTTT funktioniert ja immer so, dass ein gewisses Ereignis eine Aktion auslöst. In diesem Fall ist das Ereignis der Aufruf einer speziellen IFTTT-URL, die irgendwie diese Form hat:

http://maker.ifttt.com/trigger/waschmaschinefertig/with/key/MEINPERSÖNLICHERKEY

Das Ereignis, welches beim Aufruf dieser URL ausgelöst wird, ist dann eine Benachrichtigung mittels der auf dem Smartphone laufenden IFTTT-App.

Also: Der D1 mini ruft eine URL auf, und im nächsten Moment erscheint auf dem Smartphone eine Meldung. Oder eben auf zwei Smartphones, wenn der D1 mini zwei URLs aufruft.

ToDo

Bisher läuft das Teil ganz ordentlich, aber zwei, drei Dinge könnten oder müssten vielleicht noch getan werden:

  1. Manchmal funktioniert die Erkennung des letzten Schleuderganges noch nicht hundertprozentig, sondern es werden auch falsche Schleudergänge erkannt. Aktuell optimiere ich das noch durch Anpassen des Schwellwertes, vielleicht wäre es aber besser, das Erkennungsmuster an sich zu optimieren. Falls du den Quellcode gelesen hast: Wenn 2x hintereinander keine Vibration über dem Schwellwert erkannt wird fängt die Erkennung von vorne an (das boolsche Array wird zurückgesetzt).
  2. Aktualisierung des Codes per WLAN. Es wäre schon elegant, den D1 mini nicht an den Rechner stecken zu müssen, um neuen Code aufzuspielen. Wenn das Ding schon WLAN hat könnte man es ja auch darüber machen. Und man kann auch, wie hier mein Lieblings-Grieche erklärt.
  3. Ein Case bauen. Aktuell liegt das kleine Teil noch recht nackt neben der Waschmaschine. Schön ist das nicht, und wenn das Gerät sich noch etwas bewährt hat bekommt es vielleicht auch mal ein kleines Case.

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

  1. micky

    Hallo, bekomme beim veröffentlichten Sketch die Meldung: “

    E:\Downloads\Arduino\arduino-nightly\Sketches\Waschmaschine\Waschmaschine.ino: In function ‚void setup()‘:

    Waschmaschine:46: error: ‚connect2Wifi‘ was not declared in this scope

    connect2Wifi();

    ^

    E:\Downloads\Arduino\arduino-nightly\Sketches\Waschmaschine\Waschmaschine.ino: In function ‚void loop()‘:

    Waschmaschine:57: error: ’sum_over_thres‘ was not declared in this scope

    if (vibration > vibration_log_thres || sum_over_thres() >= 1) {

    ^

    Waschmaschine:58: error: ’send_log‘ was not declared in this scope

    send_log();

    ^

    Waschmaschine:62: error: ’sum_over_thres‘ was not declared in this scope

    if (sum_over_thres() == sizeof(vibration_last)) {

    ^

    E:\Downloads\Arduino\arduino-nightly\Sketches\Waschmaschine\Waschmaschine.ino: In function ‚void measure_vibration()‘:

    Waschmaschine:86: error: ’shift_vibration_array‘ was not declared in this scope

    shift_vibration_array();

    ^

    Waschmaschine:96: error: ‚gen_vibration_string‘ was not declared in this scope

    Serial.println(„[Vibration] vibration_last = “ + gen_vibration_string());

    ^

    Waschmaschine:97: error: ’sum_over_thres‘ was not declared in this scope

    Serial.println(„[Vibration] progress = “ + (String)sum_over_thres() + „/“ + (String)sizeof(vibration_last));

    ^

    E:\Downloads\Arduino\arduino-nightly\Sketches\Waschmaschine\Waschmaschine.ino: In function ‚void send_notification()‘:

    Waschmaschine:112: error: ’send_HTTP_request‘ was not declared in this scope

    send_HTTP_request(URL_notification[i]);

    ^

    E:\Downloads\Arduino\arduino-nightly\Sketches\Waschmaschine\Waschmaschine.ino: At global scope:

    Waschmaschine:117: error: ‚Serial‘ does not name a type

    Serial.printf(„[HTTP] Return code: %d\n“, httpCode);

    ^

    Waschmaschine:119: error: expected unqualified-id before ‚if‘

    if (httpCode == HTTP_CODE_OK) { Serial.println(http.getString()); }

    ^

    Waschmaschine:120: error: expected declaration before ‚}‘ token

    }

    ^

    Mehrere Bibliotheken wurden für „ESP8266WiFi.h“ gefunden
    Benutzt: C:\Users\Packy\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.3.0\libraries\ESP8266WiFi
    Nicht benutzt: E:\Downloads\Arduino\arduino-nightly\libraries\ESP8266WiFi
    exit status 1
    ‚connect2Wifi‘ was not declared in this scope

    Antworten
  2. micky

    E:\Downloads\Arduino\arduino-nightly\Sketches\Waschmaschine\Waschmaschine.ino: In function ‚void setup()‘:

    Waschmaschine:46: error: ‚connect2Wifi‘ was not declared in this scope

    connect2Wifi();

    ^

    E:\Downloads\Arduino\arduino-nightly\Sketches\Waschmaschine\Waschmaschine.ino: In function ‚void loop()‘:

    Waschmaschine:57: error: ’sum_over_thres‘ was not declared in this scope

    if (vibration > vibration_log_thres || sum_over_thres() >= 1) {

    ^

    Waschmaschine:58: error: ’send_log‘ was not declared in this scope

    send_log();

    ^

    Waschmaschine:62: error: ’sum_over_thres‘ was not declared in this scope

    if (sum_over_thres() == sizeof(vibration_last)) {

    ^

    E:\Downloads\Arduino\arduino-nightly\Sketches\Waschmaschine\Waschmaschine.ino: In function ‚void measure_vibration()‘:

    Waschmaschine:86: error: ’shift_vibration_array‘ was not declared in this scope

    shift_vibration_array();

    ^

    Waschmaschine:96: error: ‚gen_vibration_string‘ was not declared in this scope

    Serial.println(„[Vibration] vibration_last = “ + gen_vibration_string());

    ^

    Waschmaschine:97: error: ’sum_over_thres‘ was not declared in this scope

    Serial.println(„[Vibration] progress = “ + (String)sum_over_thres() + „/“ + (String)sizeof(vibration_last));

    ^

    E:\Downloads\Arduino\arduino-nightly\Sketches\Waschmaschine\Waschmaschine.ino: In function ‚void send_notification()‘:

    Waschmaschine:112: error: ’send_HTTP_request‘ was not declared in this scope

    send_HTTP_request(URL_notification[i]);

    ^

    E:\Downloads\Arduino\arduino-nightly\Sketches\Waschmaschine\Waschmaschine.ino: At global scope:

    Waschmaschine:117: error: ‚Serial‘ does not name a type

    Serial.printf(„[HTTP] Return code: %d\n“, httpCode);

    ^

    Waschmaschine:119: error: expected unqualified-id before ‚if‘

    if (httpCode == HTTP_CODE_OK) { Serial.println(http.getString()); }

    ^

    Waschmaschine:120: error: expected declaration before ‚}‘ token

    }

    ^

    Mehrere Bibliotheken wurden für „ESP8266WiFi.h“ gefunden
    Benutzt: C:\Users\Packy\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.3.0\libraries\ESP8266WiFi
    Nicht benutzt: E:\Downloads\Arduino\arduino-nightly\libraries\ESP8266WiFi
    exit status 1
    ‚connect2Wifi‘ was not declared in this scope

    Antworten
  3. micky

    Hallo, ich bekomme folgende Fehlermeldungen:

    Waschmaschine:46: error: ‚connect2Wifi‘ was not declared in this scope
    connect2Wifi();

    Waschmaschine:57: error: ’sum_over_thres‘ was not declared in this scope

    Waschmaschine:58: error: ’send_log‘ was not declared in this scope

    Waschmaschine:62: error: ’sum_over_thres‘ was not declared in this scope

    Waschmaschine:86: error: ’shift_vibration_array‘ was not declared in this scope

    Waschmaschine:96: error: ‚gen_vibration_string‘ was not declared in this scope

    Waschmaschine:97: error: ’sum_over_thres‘ was not declared in this scope

    Waschmaschine:112: error: ’send_HTTP_request‘ was not declared in this scope

    Waschmaschine:117: error: ‚Serial‘ does not name a type

    Waschmaschine:119: error: expected unqualified-id before ‚if‘

    Waschmaschine:120: error: expected declaration before ‚}‘ token

    ‚connect2Wifi‘ was not declared in this scope

    Antworten
    1. dasaweb Beitragsautor

      Hi Micky,
      sorry, ich bin gerade auf dem Sprung und die nächsten 2 Wochen nicht da, daher nur ein kurzer Hinweis: Das ist wohl kein Problem meines Sketches, sondern ein generelles. Hatte ich auch schon. Google mal einfach nach „arduino was not declared in this scope“ und probier ein bisschen rum. Kann alles sein zwischen Umlaute in Pfadnamen, IDE hängt irgendwie, Code nochmal in ne frische Datei kopieren usw.
      Viel Erfolg! Und wenn ich wieder zurück bin und du immer noch hängst schau ich nochmal genauer.

      Antworten

Schreibe einen Kommentar

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