/* Washing Machine Alarm - https://steve.fi/Hardware/ This script is designed to alert me/us to the finish of a washing-machine cycle. We're using a state-machine to keep track of where things are. We're always in one of three states: QUIET RUNNING FINISHED To determine which state we're inside we read the vibration sensor every 30 seconds, for five minutes. If we receive three motion-events we decide we've got movement, otherwse we do not. This allows the state to be updated appropriately. If you use this you'll want to change the WiFi details (`SCOTLAND` + `highlander1`), as well as the reporting URL in the function `log_state()` - that triggers real notification. Annoyingly the D1 doesn't really support HTTPS or I'd do that directly. */ #include // HTTP #include #define VIB_PIN D1 // // WiFi details. // const char* ssid = "SCOTLAND"; const char* password = "highlander1"; // // We work out our state by sampling ten times, once every 30 seconds. // bool history_records [10]; int history_num = 0; // // We'll use an enum to keep track of states // enum state_t { quiet, noisy, finished }; // // The current state. // state_t state = quiet; // // We'll be running a HTTP-server on port 80. // WiFiServer server(80); // // Called once to set things up. // void setup() { // Setup serial-output Serial.begin(115200); delay(10); // Connect to WiFi network WiFi.mode(WIFI_STA); WiFi.hostname("washing-monitor"); WiFi.begin(ssid, password); // Wait until we're connected. Serial.println(" WiFi connecting .."); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println("WiFi connected"); // Start the (HTTP)-server server.begin(); Serial.println("Server started"); // Print the IP address Serial.print("Use this URL to connect: "); Serial.print("http://"); Serial.print(WiFi.localIP()); Serial.println("/"); } // // Called constantly to respond to things. // void loop() { // Last time we checked the vibration-state. static long last_update = 0; // The last time we finished a laundry cycle. static long finished_time = 0; // Check if a web-client has connected WiFiClient client = server.available(); // If they have then we're good. if (client) { // Wait until the client sends some data while (client.connected() && !client.available()) delay(1); // // Read the first line of the request // // We just log this, but useful to see. String request = client.readStringUntil('\r'); Serial.println(request); client.flush(); // Return the response: header. client.println("HTTP/1.1 200 OK"); client.println("Content-Type: text/html"); client.println(""); // Return the response: body. client.println(""); client.println("Washing Machine"); if (state == quiet) client.println("

Washing Machine

Current state is QUIET.

"); if (state == noisy) client.println("

Washing Machine

Current state is RUNNING.

"); if (state == finished) client.println("

Washing Machine

Current state is FINISHED.

"); // // Show the state of the samples. // client.println("

History

"); client.println(""); client.println(""); for (int i = 0; i < 11; i++) { client.print(""); client.print(""); } client.println("
SampleVibration?
"); client.print(i); // Current reading? if (history_num == i) client.print(" * "); client.print(""); if (i < history_num) { if (history_records[i]) client.print("true"); else client.print("false"); } else { client.print("?"); } client.println("
"); client.print("

Next sample in "); long next = 30 - ((millis() - last_update) / 1000); client.print(next); client.println(" seconds.

"); client.println(""); delay(1); Serial.println("Client disconnected"); } // // Should we update our sensor state? // // We do so if a) we've never updated (i.e. just booted), or // b) it was more than 30 seconds since our last update. // long now = millis(); if ((last_update == 0) || (abs(now - last_update) > 1000 * 30)) { // Read the sensor history_records[ history_num ] = digitalRead(VIB_PIN) ; // Show on the serial console Serial.print("Sampling for reading: "); Serial.print(history_num); Serial.print(" read: "); Serial.println(history_records[ history_num ]); // Bump our count history_num ++; // Record when we read this value last_update = now; // // If we've now read 10 entries we've finished our // testing-phase, so we want to see if our state has // changed. // if (history_num > 10) { // // Work out what state-transition to make, if any. // // The state depends on the *CURRENT* state, as well // as the last five minutes of updates. // switch (state) { case quiet: { if (is_quiet()) { // still quiet - no change Serial.println("Old state: Quiet - New State: Quiet"); } else { // went from quiet to noisy? state = noisy; Serial.println("Old state: Quiet - New State: Noisy"); } break; } case noisy: { // if we're noisy that means the machine is running if (is_quiet()) { state = finished; Serial.println("Old state: Noisy - New State: Finished"); finished_time = now; } else { state = noisy ; // no change Serial.println("Old state: Noisy - New State: Noisy"); } break; } case finished: { Serial.println("Old state: Finished - New State: Finished"); // // If we finished a long time ago then we can reset // // Here we say that after 30 minutes of finishing we // go back to the quiet time, awaiting a new load of // laundry. // if ((now - finished_time) > (1000 * 60 * 30)) { state = quiet; } break; } // Now log the state - after the end of the five minute sampling // period. log_state(state); } // And now we reset our monitoring state. history_num = 0; } } } // // Is the vibration-sensor 100% quiet? // bool is_quiet() { int sum = 0; for (int i = 0; i < 10; i++) sum += history_records[i]; return (sum == 0); } // // Is motion detected? We use a threshold of 3/10 periods. // bool is_motion() { int sum = 0; for (int i = 0; i < 10; i++) sum += history_records[i]; return (sum >= 3); } // // Log the state to a remote HTTP-server // void log_state(state_t s) { char url[128]; HTTPClient client; if (s == quiet) sprintf(url, "http://192.168.10.64/cgi-bin/washing.cgi?state=quiet"); else if (s == noisy) sprintf(url, "http://192.168.10.64/cgi-bin/washing.cgi?state=noisy"); else if (s == finished) sprintf(url, "http://192.168.10.64/cgi-bin/washing.cgi?state=finished"); else sprintf(url, "http://192.168.10.64/cgi-bin/washing.cgi?state=UNKNOWN"); client.begin(url); // httpCode will be negative on error int httpCode = client.GET(); Serial.printf("[HTTP] GET resulted in code: %d\n", httpCode); if (httpCode > 0) { // file found at server if (httpCode == HTTP_CODE_OK) { // Get the body of the response. // This uses some hellish String object. String payload = client.getString(); Serial.println(payload); } } else { Serial.printf("[HTTP] GET... failed, error: %s\n", client.errorToString(httpCode).c_str()); } client.end(); }