Home on the web ~ Dan Van Fleet

Information on SoftPro ProForm with some general computer tips and techniques, with a bit of me.

Home on the web ~ Dan Van Fleet - Information on SoftPro ProForm with some general computer tips and techniques, with a bit of me.

From Laundry to ifttt then text done

Our laundry machines are in the basement, when we remodeled our 1932 two story home, the staircases were sound insulated.  The cute chirping of our front loading machines doesn't stand a chance of cutting through.  My wife mentioned that our machines have some sort of phone connection for servicing, why can't they tell her when the cycle is complete.

That started the build.

I had a couple Wii nunchucks from a broken Wii, they have motion sensors in them.  A quick web search found an instructable http://www.instructables.com/id/Washer-Dryer-Laundry-Alarm-using-Arudino-SMS-Text-/?ALLSTEPS using a Arduino Yun, it was quite helpful, I had an Uno with an Ethernet card.

What about that way

Before going too far, let's consider different methods of grabbing an alert an Arduino could respond to.  Vibration, current, and sound were all possibilities. I liked a current sensing method best, it however requires dealing with line voltages, even with an inductive sensor.  The NEC is going to require the sensor be in a junction box. In my case both the washer and dryer are on independent circuits, so the main panel would be a fine place for the sensors.  Since I designed and wired the whole lot, that method fits me quite well, for this project a blog post was one of the goals.

The main electrical panel method would limit the audience.  So as MakerBee considered, a noninvasive solution fits the bill better.  Vibration seemed better than sound to me, the variety of beeps and boops from today's electronic equipment seemed more difficult to decrypt.

Nunchuck and more requirements

A nunchuck is a pretty inexpensive method to get a motion controller wired up ready to go.  A little desoldering yields a nice starting point. Most of my laundry experience is with top load washers. They vibrate to beat the band, the front loader we have now, doesn't move very much.  Of course when it spins, that all changes, but most of the time it just smoothly goes about its business.  The nunchuck sensor is impressive, it easily picks up the slightest movement.  Tapping one's fingers lightly on a sturdy desk, a couple feet away is discernable.

Another requirement was an auto start function.  I want the device to be hands off, it should notice the machine is running, monitor it until complete and fire off a text message. The dryer monitor shouldn't auto start during a washer cycle, and vice versa.  Lastly the device should indicate what's it is doing through a couple LED's.

The build, thanks to the nunchuck library is pretty easy.

Parts needed for one unit
1 - Arduino Uno
1 - Ethernet Shield
2 - Momentary contact buttons
4 - LED's different colors.  (Red, Green, Blue, Yellow)
2 - about 2K Resistors
1 - Wii Nunchuck, maybe with a nunchuck breakout board.
1 - Case
1 - Neodymium magnet (hard drive salvage)
1 - Arduino nunchuck library
Misc other standard building things, wire, breadboard

You may need another resistor. The red and blue LED's needed them, for some reason the yellow did not, and the green is typically held down by the program. I'm not an EE, but that side of me says both yellow and green should have a series resistor, maybe smaller on green.  The buttons are not shown on the diagram, extend them from the nunchuck circuit board.  The button module has 3 wires, a common and one for each button.  The nunchuck is a 3.3 volt device.  Read all about the nunchuck at https://create.arduino.cc/projecthub/infusion/using-a-wii-nunchuk-with-arduino-597254

I started by cutting the plug off the nunchuck and installing DuPont connectors onto  the wires.

Those were plugged into the Ethernet Shield sitting on top of the Uno.  That is really all that is needed for this project, the LED's are fluff or as a friend says, everyone sells steak, that's the sizzle.  When using an Ethernet Shield, don’t use pins 10 through 13, also sometimes pin 8 gets involved in network connections.

You should know, I'm quite new to Arduino, electronic design and Arduino code, I can solder, so take my notes like you read it on the internet.

The LED sequence and usage are.

  • Blue, comes on during POST then off when running.
  • Blue turns on consider starting, turns back off it's done considering for now. Turns on at the half way point.
  • Yellow, the system automatically started.
  • Green, the system is running.
  • Red, consider stopping, turns off it's done for now. turns on at 10% of the way there.
  • Red turns off when network transmission to ifttt starts.
  • Yellow turns off when network transmission to ifttt ends.
  • Green turns off when cycle complete.

When adding LED's I found only the blue and red one's needed series resistors. The yellow one was already not too bright / about to smoke.  The green one's level is held down during the Arduino loop, then it goes overdriven when a network transmission is taking place.

Nunchuck buttons are used as optional start or stop functions.  For a couple reasons, each laundry machine got its own device which share a common code base.  Adding a couple transistors and tweaking the code would allow 2 nunchucks to live on the I2C protocol.

Configuration and Process Description

Each machine has it's own variable for each of the settings.  When updating a device use the variable IsWasherDryer to control which machine the device is for.  Set it to True for the washer and False for the dryer.

Auto Start Process

The Arduino is checking the sensor about 84 times a second.  The motion sensor in the nunchuck is quite sensitive with the ability to see a rather light finger roll a couple feet away on a heavy desk.  The movement information comes in as an integer between 0 and 254. The finger roll gives one or two of the 3 sensors a visible change.  When the machine starts shaking the device starts up.

The start movement threshold is known as AutoStartSensitivity. With such a rapid refresh rate and small movements, the sensors report no movement sometimes.  This is handled with a variable AutoStartThreashold.  That last variable defined for automatic starting is how long the device needs to be moving before it's an official start.  That is called AutoStartMovementTime.

Ideally the device would start near the begin cycle of the machine, however with really smooth front loading washers, it might be better to start at the first spin cycle.  The Dryer has a much more consistent vibration, and is easy to automatically start.  The dryer's AutoStartMovementTime should be longer than the washer's spin cycle, or at least the violent part of it.

The Serial Log can be watched to tweak these settings.  During Auto Start, it will scroll vertically if the AutoStartSensitivity is not reached.  As that level is reached the log will begin to wrap 10 loops on a line.  During a run, the log changes to showing the maximum movement received in about a second.  It is useful when adjusting the automatic start settings to manually start run mode to watch the movement.

Run Process

Once we're started, we have two variables to control the run process.  RunSensitivity, which is looking at movement like AutoStartSensitivity.  The other option is used to determine the end of the cycle and called RunWaitTime.  That is how many minutes of no movement before the ifttt post is made.   For my machines the dryer RunWaitTime is short, 30 seconds or so, the washer has a much longer delay, a few minutes.

The ifttt post, has a Maker listener with an event name set to match the machine's IftttEventName. Your personal ifttt Key goes in iftttKey.  Other than key, each of the above variables has a washer and dryer setting.

There are a couple test settings.  isTestMode is used to not fire the ifttt event, isFastMode is an integer divider on the timers for easy desktop testing. Each device has a MAC and IP address setting, they should not be duplicated.  The rest of the settings are, for what pins the LED's are connected, a timerAdjustment which can be changed to define what a minute is, and finally the baud rate to talk with the serial monitor.  The nunchuck Z button is Start while C is stop.

These are currently in operation, and from what I hear working great.  It took over 10 laundry loads to get the settings correct.  I watched a number of full cycles monitoring the serial monitor for feedback.   Load size and selected options on the machines vary quite a bit. When setting autostart it is useful to manually enter run mode to see a different output on the serial monitor.

The Disclaimer

Everything works and is highly adjustable using the various settings, there

The Code:

#include <Dhcp.h>
#include <Dns.h>
#include <Ethernet.h>
#include <EthernetClient.h>
#include <EthernetServer.h>
#include <EthernetUdp.h>
#include <Wire.h>
#include <ArduinoNunchuk.h>
/*
  Washing Machine Control
   Copyright 2016 Dan Van Fleet http://danvanfleet.com

   Project URL: None Yet
   Monitor machine for movement to automatically start, monitor cycle, send IFTTT event at end of cycle.
   Uses Wii Nunchuck for motion and buttons as manual start and cancel buttons.
*/
const bool isWasherOrDryer = false ; // true for washer, false for Dryer
const bool isTestMode = false;  // true won't fire IFTTT integration
const int isFastMode = 1; // used for testing divides timers by entered value.  0 or 1 runs normal time.

const String washerIftttEventName = "WasherDone";
const String dryerIftttEventName = "DryerDone";
const String iftttKey = "PutYourIFtttKeyHere";
const char server[] = "maker.ifttt.com";

byte dryerMac[] = { 0xDA, 0xAD, 0xBE, 0xEF, 0xFE, 0xEE };
byte washerMac[] = { 0xDA, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
const IPAddress dryerIP(192, 168, 16, 177);  // Set the static IP address to use if the DHCP fails to assign
const IPAddress washerIP(192, 168, 16, 178);  // Set the static IP address to use if the DHCP fails to assign

/*

  These settings are for a Front Loading Kenmore Elite washer and dryer sitting on a drawer on concrete. 
  The dryer is rather a consistent vibration, and significantly larger than the washer other than spin cycle.
  The washer stops for a fairly long period of time and has a high RunWaitTime in comparison.
  DryerAutostartMovement time should be longer than Washer Spin cycle.
  Washer AutoStart could be set fairly high to startup during the wash spin cycle rather than close to initial startup.
*/

const int washerRunSensitivity = 4; // movement sensitivity, lower more sensitive 1 - 255
const float washerRunWaitTime = 4.1;  // minutes set to maximum no movement time during a run, at the end the event is fired.

const int washerAutoStartSensitivity = 3;  //movement sensitivity for automatic start, can be set big to get caught by first spin cycle
const float washerAutoStartMovementTime = 60; // Seconds movement required before auto start
const int washerAutoStartThreshold = 250; // skips # of not breaking sensitivity allowed during autostart

const int dryerRunSensitivity = 6; // movement sensitivity, lower more sensitive 1 - 255
const float dryerRunWaitTime = 0.5;  // minutes set to maximum no movement time during a run, at the end the event is fired.

const int dryerAutoStartSensitivity = 4;  //movement sensitivity for automatic start
const float dryerAutoStartMovementTime = 480; // Seconds movement required before auto start, needs to be longer than washer's fast spin cycle
const int dryerAutoStartThreshold = 85; // skips # of loops reading lower than sensitivity allowed during autostart

const int pinRun = 4;  // Pin Run LED is connected to Green.  Illumiate 50% during run, 100% during ifttt send.
const int pinAutoStartCheck = 7; // Pin AutoStart in progress LED Blue.  Illuminates at 50% of AutoStartMovementTime. Flashes at initial power up, after post.
const int pinEndCycleCheck = 6;  // Pin End Cycle check LED Red. Illuminates at 10% of RunWaitTime.
const int pinAutoStarted = 5;  //Pin cycle was autostarted LED Yellow. Illuminates when autostart detected.

const int timerAdjustment = 5000; // loops per minute
const int BAUDRATE = 19200;
// End Setup items

ArduinoNunchuk nunchuk = ArduinoNunchuk();
bool isRunning = false;  // program contol
float lastAccelX = 0;  // stores last positions for delta determinations.
float lastAccelY = 0;  // ''
float lastAccelZ = 0;  // ''
int counter = 0;  // stores counter position for run loop counter
float startMoveCounter = 0;  // stores counter position for auto start cycle loops
int lastChangeCount = 0;  // stores counter position of last seen change
// these come from washer or dryer settings.
int runSensitivity = 0; // stores minimum movement value to continuing running.
float runWaitTime = 0; // stores maximum no movement time in seconds before run ends
int autoStartSensitivity = 0; // stores minimum value to consider starting
float autoStartMovementTime = 0; // stores seconds of start movement before auto start
int autoStartThreshold = 0; // stores maximum no movement loops to ignore during auto start check.
String iftttEventName = ""; // stores ifttt event name to run
byte mac[]  = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; // placeholder for mac address
IPAddress ip(192, 168, 16, 175);  // place holder for IP address //end washer or dryer settings
int logX = 0; // Used for Display
int logY = 0;
int logZ = 0;
int countWrap = 0;
int displayWrap = 0; // end Display
EthernetClient client; // used to talk

void setup()
{
   pinMode(pinRun, OUTPUT);
  pinMode(pinAutoStartCheck, OUTPUT);
  pinMode(pinEndCycleCheck, OUTPUT);
  pinMode(pinAutoStarted, OUTPUT);
  digitalWrite(pinRun, LOW);
  digitalWrite(pinAutoStartCheck, HIGH);
  digitalWrite(pinEndCycleCheck, LOW);
  digitalWrite(pinAutoStarted, LOW);
  Serial.begin(BAUDRATE);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }
  nunchuk.init();
  
  if (isWasherOrDryer) // toggle in variables
  {
    runSensitivity = washerRunSensitivity;
    runWaitTime = washerRunWaitTime;
    autoStartSensitivity = washerAutoStartSensitivity;
    autoStartMovementTime = washerAutoStartMovementTime;
    autoStartThreshold = washerAutoStartThreshold;
    iftttEventName = washerIftttEventName;
    memcpy(mac,washerMac,  6);
    memcpy(ip, washerIP, 4);
  }
  else
  {
    runSensitivity = dryerRunSensitivity;
    runWaitTime = dryerRunWaitTime;
    autoStartSensitivity = dryerAutoStartSensitivity;
    autoStartMovementTime = dryerAutoStartMovementTime;
    autoStartThreshold = dryerAutoStartThreshold;
    iftttEventName = dryerIftttEventName;
    memcpy(mac, dryerMac, 6);
    memcpy(ip, dryerIP, 4);    
  }
  if (isFastMode > 1) {
    runWaitTime = runWaitTime / isFastMode;
    autoStartMovementTime = autoStartMovementTime / isFastMode;
  }
}
void loop()
{
  // if there are incoming bytes available
  // from the ifttt server, read and print them:
  if (client.available()) {
    digitalWrite(pinAutoStartCheck, LOW);
    char c = client.read();
    Serial.print(c);
  }
  else {
    nunchuk.update();
    CheckButtons();
    if (isRunning == true)
    {
      digitalWrite(pinAutoStartCheck, LOW);
      digitalWrite(pinRun, HIGH);
      if (counter < (runWaitTime * timerAdjustment)) { RunMovementInspector(); digitalWrite(pinRun, LOW); // keeps LED dimmer so that when ifttt call is being setup and made it gets brighter. } else { isRunning = false; digitalWrite(pinEndCycleCheck, LOW); if (!isTestMode) { SendAlert(); } else { Serial.println("IFTTT request simulated"); } PostRunCleanup(); } } else { // Not running should we? StartupCheck(); } } // if the server's disconnected, stop the client: if (!client.connected()) { //Serial.println(); //Serial.println("disconnecting."); client.stop(); } } void PostRunCleanup() { Serial.println("Done"); digitalWrite(pinAutoStarted, LOW); delay(5000); counter = 0; startMoveCounter = 0; lastChangeCount = 0; digitalWrite(pinRun, LOW); } void CheckButtons() { if (nunchuk.zButton == 1) { isRunning = true; digitalWrite(pinRun, HIGH); digitalWrite(pinEndCycleCheck, LOW); digitalWrite(pinAutoStarted, LOW); } if (nunchuk.cButton == 1) { // cancel button turn off the lights and stop isRunning = false; digitalWrite(pinAutoStartCheck, LOW); digitalWrite(pinEndCycleCheck, LOW); digitalWrite(pinAutoStarted, LOW); digitalWrite(pinRun, LOW); counter = 0; startMoveCounter = 0; } } void RunMovementInspector() { float changeX, changeY, changeZ; changeX = GetChangeX(); // abs(lastAccelX - accelX); changeY = GetChangeY(); //abs(lastAccelY - accelY); changeZ = GetChangeZ(); //abs(lastAccelZ - accelZ); // Take out Z if ( (changeX >= runSensitivity)  ||  (changeY >= runSensitivity) || (changeZ >= runSensitivity ))
  {
    // Movement detected
    counter = 0;
    digitalWrite(pinEndCycleCheck, LOW);
  }
  else
  {
    //Not moving, should we get out?
    counter++;
    if (counter > runWaitTime * timerAdjustment / 10)
    { // Light the considering stop light at
      digitalWrite(pinEndCycleCheck, HIGH);
    }
  }  
  PrintLog(counter, changeX, changeY, changeZ);
 
}

int GetChangeX(){
  float accel = nunchuk.accelX;
  float change = lastAccelX - accel;
  lastAccelX = accel;
  return abs(change);
}
int GetChangeY(){
  float accel = nunchuk.accelY;
  float change = lastAccelY - accel;
  lastAccelY = accel;
  return abs(change);
}
int GetChangeZ(){
  float accel = nunchuk.accelZ;
  float change = lastAccelZ - accel;
  lastAccelZ = accel;
  return abs(change);
}
void StartupCheck()
{
  float changeX, changeY, changeZ;
  changeX = GetChangeX();
  changeY = GetChangeY();
  changeZ = GetChangeZ();
  if ( changeX > autoStartSensitivity ||  abs(changeY) > autoStartSensitivity || abs(changeZ) > autoStartSensitivity )
  { // Something  moved more than it should have, count it.
    startMoveCounter++;
    lastChangeCount = startMoveCounter;
    StartupLog(startMoveCounter, changeX, changeY, changeZ);  
    if (startMoveCounter > (autoStartMovementTime * timerAdjustment / 120)) {
      digitalWrite(pinAutoStartCheck, HIGH); // if we hit half way point light the led. 
    }
  }
  else
  {
    if (startMoveCounter - lastChangeCount > autoStartThreshold) {
      //too many counts since change detected, reset counters
      startMoveCounter = 0;
      lastChangeCount = 0;
      digitalWrite(pinAutoStartCheck, LOW);
      Serial.println("Reset Counters");
    } else
    {
      startMoveCounter++;
      Serial.println(startMoveCounter, 0);  // we didn't move, print a line
    }
  }
  if (startMoveCounter > (autoStartMovementTime * timerAdjustment / 60 ))  //autoStartMovementTime is in Seconds.
  {
    isRunning = true;
    digitalWrite(pinRun, HIGH);
    digitalWrite(pinAutoStarted, HIGH);
    startMoveCounter = 0;
  }
}
void SendAlert()
{
  // start the Ethernet connection:
  Serial.println("starting");
  if (Ethernet.begin(mac) == 0) {
    Serial.println("Failed to configure Ethernet using DHCP");
    // try to congifure using IP address instead of DHCP:
    Ethernet.begin(mac, ip);
  }
  // give the Ethernet shield a second to initialize:
  delay(1000);

  Serial.println("connecting...");
  if (client.connect(server, 80)) {
    Serial.println("connected");
    //Make a HTTP request:
    client.println("GET /trigger/" + iftttEventName + "/with/key/" + iftttKey + " HTTP/1.1");
    client.println("Host: maker.ifttt.com");
    client.println("Connection: close");
    client.println();
  }
  else //client.connect
  {
    // if you didn't get a connection to the server:
    Serial.println("connection failed");
  }
}
void PrintLog(int counter, int changeX, int changeY, int changeZ)
{
  int summary = 99;  // 83 is closer to a second 100 is easier to read
  displayWrap++;
  // Display largest change over summary movements
  if (countWrap < summary) {
    if (logX < changeX) {
      logX = changeX;
    }
    if (logY < changeY) {
      logY += changeY;
    }
    if (logZ < changeZ) { logZ += changeZ; } countWrap++; } else { Serial.print(counter); Serial.print("-"); Serial.print(logX); Serial.print(" "); Serial.print(logY); Serial.print(" "); if (displayWrap > (summary * 10)) {
      Serial.println(logZ);
      displayWrap = 0;
    }
    else
    {
      Serial.print(logZ);
      Serial.print("   ");
    }
    countWrap = 0;
    logX = 0;
    logY = 0;
    logZ = 0;
  }
}
void StartupLog(int startMoveCounter, int x,int y,int z){
  //we are moving, wrap every 15.
    displayWrap++;
    Serial.print(startMoveCounter);      
    //Serial.print("-");
    //Serial.print(x);
    //Serial.print(" ");
    //Serial.print(y);
    //Serial.print(" ");
    //Serial.print(z);
    //Serial.print(" ");
    if (displayWrap > 15) {
      Serial.println(" ");      
      displayWrap = 0;
    }
    else {
      Serial.print(" ");
    }
}

Category: General

Your email address will not be published. Required fields are marked *

*

css.php