Washing Machine State Machine

By following this tutorial, you will make your openHAB setup aware of the current operation state of your washing machine, dishwasher, dryer, toaster, doomsday machine, you name it. This solution is purely based on a power usage reading by one of the many available modules. The solution will allow you to react on certain state transition, e.g. sending a notification to your smartphone: “Washing machine is finished!”.

Requirements

  • power consumption measuring device, e.g. Homematic HM-ES-PMSw1-Pl, Sonoff Pow, …
  • this rule was tested with openHAB 1.8 and openHAB 2.0
  • no additional addons/bindings are needed (other than the one for your module)

Before continuing, please make sure that the device is connected to openHAB and is correctly providing usage data (Watts or Amps). Please be aware, that many devices have internal settings on how often and when to send new data. You do not need to tweak these in order for this solution to work, the reaction time might however be improved by doing so.

Idea and State Details

The presented solution is build on the idea, that the power usage of most household devices will differ between their operation states. Let’s have a look at a typical washing machine. It’s easy to identify these three fundamental states: Off - Standby - Active. By taking the timely transition between these states into account, we can add one especially important state: Finished.
One could even go one step further and differentiate between washing phases. These or other additional states can only be added, if information retrieved from our device is clear enough to always decide on the right state. While thinking about your needed states, it also doesn’t hurt to remember which events are actually important to you. :wink:

First Measurements

The first thing you have to do is taking one complete measurement series. Only by knowing the specific power consumption behavior of your device, you can later parametrize your rule. Here are two examples, one showing one run of my washing machine, the other shows the consumption of my dishwasher.

As you can see, the diagrams are very different. While the dishwasher diagram follows some acceptable pattern, the washing machine seems to be very unpredictable but even this diagram seems to consist of certain phases, which one “could” analyze.

Sample Data and Diagrams

openHAB already collects all events received by registered items in the logging file events.log. Have your (washing) machine go though one full process and extract the usage data from the log. One example on how to do this on Linux:

grep "Washingmachine_Power changed" /var/log/openhab2/events.log
......
[...] Washingmachine_Power changed from 0.06 to 0.07
[...] Washingmachine_Power changed from 0.07 to 0.06
[...] Washingmachine_Power changed from 0.06 to 0.07
[...] Washingmachine_Power changed from 0.07 to 1.34
[...] Washingmachine_Power changed from 1.34 to 1.33
[...] Washingmachine_Power changed from 1.33 to 1.34
[...] Washingmachine_Power changed from 1.34 to 160.99
[...] Washingmachine_Power changed from 160.99 to 4.59
[...] Washingmachine_Power changed from 4.59 to 157.92
[...] Washingmachine_Power changed from 157.92 to 2154.860001
[...] Washingmachine_Power changed from 2154.860001 to 2257.259998
[...] Washingmachine_Power changed from 2257.259998 to 2160.779999
[...] Washingmachine_Power changed from 2160.779999 to 2158.700001
[...] Washingmachine_Power changed from 2158.700001 to 2266.509998
[...] Washingmachine_Power changed from 2266.509998 to 2155.84
......

You could now already analyze the data. It’s probably easier to do this by looking at a graph in your favorite tool (the diagrams above were created using https://plot.ly). Before you can do that, you need to extract the data to be used. You can do this on your favorite editor like Notepad++.

Converting the events log file output above on the fly into a CSV format (readable by Plot.ly or Excel) can be done with the following command:

grep "Waschmaschine_Power changed" /var/log/openhab2/events.log | sed 's/.[0-9]* \[ItemStateChangedEvent.* to /;/'
......
2016-10-22 20:13:29.095;135.17
2016-10-22 20:13:37.095;76.98
......

Finding the Transition Points

Now you need to find consumption values to decide on, in which state the washing machine might be in. Judging by the log data above, it’s pretty obvious, that my washing machine is consuming little power when turned off (0.07W), a bit more when in Standby (1.34W) and way more when active. It’s important to find power values that are only measured when switching states but not when being in one state! Always leave some space for small deviation. Finding the right values is the tricky part of this solution!

The result for the washing machine from above is illustrated in the following state machine diagram:

openHAB Implementation

The shown state machine can now be transferred to openHAB in a simple and short way. No magic here :wink:

household-devices.items

Number Washingmachine_OpState "Washingmachine State [%d]"

household-devices.rules

val Number MODE_OFF = 0
val Number MODE_STANDBY = 1
val Number MODE_ACTIVE = 2
val Number MODE_FINISHED = 3

rule "Washingmachine Consumption State Machine"
when
    Item Washingmachine_Power changed
then
    if (Washingmachine_Power.state < 0.2) Washingmachine_OpState.postUpdate(MODE_OFF)
    else if (Washingmachine_Power.state > 10) Washingmachine_OpState.postUpdate(MODE_ACTIVE)
    else if (Washingmachine_Power.state < 4.5) {
        if (Washingmachine_OpState.state == MODE_OFF) Washingmachine_OpState.postUpdate(MODE_STANDBY)
        else if (Washingmachine_OpState.state == MODE_ACTIVE) Washingmachine_OpState.postUpdate(MODE_FINISHED)
    }
end

That is basically it. You can now present the OpState item on your sitemap or react on transitions in rules. Example:

rule "Washingmachine Notifications"
when
    Item Washingmachine_OpState changed
then
    if (Washingmachine_OpState.state == MODE_FINISHED) {
        pushover("Washingmachine finished!")
    }
end

State oriented control flow

The above solution acts on the current power consumption as the main control flow element. For some a states oriented algorithm might be more intuitive or better suited. @BigMountainSki proposed a state oriented control flow in the following comment:

Bouncy States and other Obstacles

The above solution can already be used. Depending on your machine you may see bouncing states, resulting in multiple state transitions with unwanted effects (e.g. multiple notifications). In this part we will look into a few ways to handle these smaller problems.

Bouncy State Elimination using Delay

In the given washing machine example, the power reading surpasses the defined 4.5 Watts boundary a few times in the middle of the wanted active state. This can be handled by a simple time-based de-bouncing, using a lock (ReentrantLock, similar to a “semaphore”) and a delay.

household-devices.rules

import java.util.concurrent.locks.ReentrantLock

val Number MODE_OFF = 0
val Number MODE_STANDBY = 1
val Number MODE_ACTIVE = 2
val Number MODE_FINISHED = 3
var java.util.concurrent.locks.ReentrantLock finishLock  = new java.util.concurrent.locks.ReentrantLock()

rule "Washingmachine Consumption State Machine (Extended)"
when
    Item Washingmachine_Power changed
then
    if (Washingmachine_Power.state < 0.2) Washingmachine_OpState.postUpdate(MODE_OFF)
    else if (Washingmachine_Power.state > 10) Washingmachine_OpState.postUpdate(MODE_ACTIVE)
    else if (Washingmachine_Power.state < 4.5) {
        if (Washingmachine_OpState.state == MODE_OFF) Washingmachine_OpState.postUpdate(MODE_STANDBY)
        else if (Washingmachine_OpState.state == MODE_ACTIVE) {
            finishLock.lock()
            try {
                Thread::sleep(10000) // Debounce for 10 seconds
                if (Washingmachine_Power.state < 4.5) Washingmachine_OpState.postUpdate(MODE_FINISHED)
            } finally {
                finishLock.unlock()
            }
        }
    }
end

Bouncy State Elimination using Persistence

Continue reading at:

Node-RED Implementation

Outlook

An alternative solution would be an event-based instead of a delay-based rechecking. If you got such a solution working for you, please share in the comments and I’ll make sure to include it :wink:

Happy Hacking!

56 Likes

Thank you for posting – very nice!

One question: Where did you get the icons from? :slight_smile:

1 Like

A bit off topic :smiley: but sure. Here you go: https://icons8.com/web-app/548/washing-machine
You’ll receive pngs for free but have to pay for svg. You can embed png in svg as a hack…

2 Likes

Very nice write-up indeed!

This is almost exactly what I have running to monitor my washing machine, clothes dryer and dishwasher. I started out by doing capture of a few cycles of each device just like you recommend (I did this by enabling persistence on the power measurement items and used HABmin to produce graphs for analysis).

As you point out, some appliances (especially washing machines) tend to have a lot of short periods of IDLE time during the program cycle (especially at the end). My solution to determine when the “program finished” was to use a timer.

Basically I have a rule that creates a timer every time the status goes from ACTIVE to IDLE. For my machine the timer is set to 5 minutes but this may differ for different products. When the timer expires, I post an event saying “Program finished”. Then I have another rule that cancels the timer whenever the status changes from IDLE (to either ACTIVE or OFF). The result of this is that if the machine is in IDLE state for 5 minutes, then this means that the program has finished.

This has been working well for me for quite some time (even across replacing my washing machine with a new model, :-)).

1 Like

Thanks Kjetil,
I started by deliberately capturing a few cycles just like you said but then realized that it’s actually easier to just take what’s already there :slight_smile: Nothing against persistence or HABmin - using plotly was quite easy as I am already quite fond of it and you can do quick analysis there, see the minimal or maximum value, …

Regarding the timer solution: I have just the same setup with my automatic bathroom light activated for 30 minutes upon signal from the door contact.
Did you see the solution based on classic debouncing I just added as an update? I think it has the same effect but is a bit smaller.

Yes, I did see it, and I certainly agree this is one way to do it. My washing machine, however, tends to have quite long periods of IDLE time followed by ACTIVE time before it is actually finished. The periods of IDLE time are in the range of several minutes, and I don’t like to have a rule sleep that long, :slight_smile: That is why I ended up with the timer approach, but as the saying goes; “There are many ways to skin a cat”…

1 Like

Right you are :slight_smile:
Would you post a minimal working example as an alternative to the one above?

I tried to use a similar approach.
In my case the both the washing machine and the dryer have to use the same power plug.
Both machines do not have a fixed cycle. The cycles depending on the washing program, the dirt and the wetness of the clothes.
Simple state machine did not work.
I turned out I had to program a coloration to determine the state which at the end never worked well and I finally gave up.

Hopefully your cables are thick enough and you have some spare fuses :sunglasses: :grin:

Hi!

Very good with this tutorial. I was searching for this a month ago to get inspiration.
I ended up with something very similar.

Some things about my design (if anyone else is implementing and want inspiration)

  • I used the same state as mentioned by Thom.
  • I’m using a timer when it is running to determine when it’s finished but only to allow it to be idle for ~10 mins
    if the state is running/active. The timer for me was basically used to know when to cut the power rather than
    prevention of state jumping
  • I permitted the state machine to change state from active to idle while running, just because I think it’s interesting to see when it is idle during the program,
  • I only pay attention to transitions of states, i.e if the state is the same like idle-idle or active-active i don’t do anything.
  • I’m using locks just to be sure I don’t get two concurrent requests
  • I turn the power off after the dishwasher/washingmachine as completed in order to save power, this can be tricky since you want to be able to start the with a timer. Sometimes I set my washingmachine to start in 10 hours.
  • I Send reports with Start time, end time, running time, number of kwhs used, cost for the particular wash/drying-session in html format via email. This is done by checking the accumulated kwh:s when it is started and when it is finished, and multiply that with my current energy cost. (Fibaro has support for this). I only send reports if the running time is long than 20 mins (in order to avoid testing runs etc)

When developing and debugging I used MQTT to be able to post power updates like this:

rule "DebugDishWasher"
when 
    Item MqttDishWasherPower received update
then
    logInfo("MqttDishWasherPower", "Power: " + MqttDishWasherPower.state.toString)
    ZwaveDishWasherSensorPower.postUpdate(MqttDishWasherPower.state)
end

And can it that manner easily create scripts or use command prompt to test the state machine.
Like:

mosquitto_pub -h SERVER -t openhab/debug/washingmachinepower -m "44.2"

In order to capture the cycles and states, I didn’t do anything sophisticated, I just printed the powerconsumption as debug info and then turned the washer on and off as well as started it, which was enough to determine the states.
I’m using the same rules in for both the dryer, washingmachine and dishwasher in order to be able to reuse code for instance for the html reports.

My reports look like this:

Dishwasher
Started at: 2016-10-23 19:50:09
Finished at: 2016-10-23 21:53:45
RunningTime: 2h 3m 35s
Consumption: 0.92 kWh 1.012 SEK
Monthly: 24.38 kWh 26.818 SEK

Regards, S

2 Likes

Anyone else using GEMs and seeing 0s where you should see data?

2016-10-22 15:04:48.590 [INFO ] [runtime.busevents             ] - Circuit_C02 state updated to 0.0
2016-10-22 15:05:04.591 [INFO ] [runtime.busevents             ] - Circuit_C02 state updated to 0.0
2016-10-22 15:05:20.581 [INFO ] [runtime.busevents             ] - Circuit_C02 state updated to 2573.75
2016-10-22 15:05:36.582 [INFO ] [runtime.busevents             ] - Circuit_C02 state updated to 756.938
2016-10-22 15:06:15.584 [INFO ] [runtime.busevents             ] - Circuit_C02 state updated to 1220.462
2016-10-22 15:06:31.585 [INFO ] [runtime.busevents             ] - Circuit_C02 state updated to 1034.438
2016-10-22 15:06:47.586 [INFO ] [runtime.busevents             ] - Circuit_C02 state updated to 722.875
2016-10-22 15:07:03.587 [INFO ] [runtime.busevents             ] - Circuit_C02 state updated to 1022.625
2016-10-22 15:07:19.588 [INFO ] [runtime.busevents             ] - Circuit_C02 state updated to 241.0
2016-10-22 15:07:35.588 [INFO ] [runtime.busevents             ] - Circuit_C02 state updated to 1449.438
2016-10-22 15:07:51.589 [INFO ] [runtime.busevents             ] - Circuit_C02 state updated to 0.0
2016-10-22 15:08:07.590 [INFO ] [runtime.busevents             ] - Circuit_C02 state updated to 1683.625
2016-10-22 15:08:23.581 [INFO ] [runtime.busevents             ] - Circuit_C02 state updated to 0.0
2016-10-22 15:08:39.582 [INFO ] [runtime.busevents             ] - Circuit_C02 state updated to 1634.563
2016-10-22 15:08:55.582 [INFO ] [runtime.busevents             ] - Circuit_C02 state updated to 114.313
2016-10-22 15:09:11.583 [INFO ] [runtime.busevents             ] - Circuit_C02 state updated to 0.0
2016-10-22 15:09:27.584 [INFO ] [runtime.busevents             ] - Circuit_C02 state updated to 1767.75
2016-10-22 15:09:43.585 [INFO ] [runtime.busevents             ] - Circuit_C02 state updated to 0.0
2016-10-22 15:09:59.586 [INFO ] [runtime.busevents             ] - Circuit_C02 state updated to 474.188
2016-10-22 15:10:15.587 [INFO ] [runtime.busevents             ] - Circuit_C02 state updated to 1008.375
2016-10-22 15:10:31.588 [INFO ] [runtime.busevents             ] - Circuit_C02 state updated to 0.0
2016-10-22 15:10:47.588 [INFO ] [runtime.busevents             ] - Circuit_C02 state updated to 1422.375
2016-10-22 15:11:03.589 [INFO ] [runtime.busevents             ] - Circuit_C02 state updated to 38.688
2016-10-22 15:11:19.589 [INFO ] [runtime.busevents             ] - Circuit_C02 state updated to 0.0
2016-10-22 15:11:35.591 [INFO ] [runtime.busevents             ] - Circuit_C02 state updated to 1703.438
2016-10-22 15:11:51.581 [INFO ] [runtime.busevents             ] - Circuit_C02 state updated to 2126.313
2016-10-22 15:12:07.582 [INFO ] [runtime.busevents             ] - Circuit_C02 state updated to 2120.125
2016-10-22 15:12:23.583 [INFO ] [runtime.busevents             ] - Circuit_C02 state updated to 2121.938
2016-10-22 15:12:39.584 [INFO ] [runtime.busevents             ] - Circuit_C02 state updated to 2119.813
2016-10-22 15:12:55.584 [INFO ] [runtime.busevents             ] - Circuit_C02 state updated to 2114.25
2016-10-22 15:13:11.585 [INFO ] [runtime.busevents             ] - Circuit_C02 state updated to 2111.813
2016-10-22 15:13:27.586 [INFO ] [runtime.busevents             ] - Circuit_C02 state updated to 2112.688
2016-10-22 15:13:43.587 [INFO ] [runtime.busevents             ] - Circuit_C02 state updated to 2103.5
2016-10-22 15:13:59.588 [INFO ] [runtime.busevents             ] - Circuit_C02 state updated to 922.625
2016-10-22 15:14:15.589 [INFO ] [runtime.busevents             ] - Circuit_C02 state updated to 0.0
2016-10-22 15:14:31.589 [INFO ] [runtime.busevents             ] - Circuit_C02 state updated to 0.0

The 0.0s in the middle of the set are false since I know the pump is still on.

It would be nice to add SMS notification when finished, in my family more the one person does the laundry so you would need to have the UI ask for a selection of a person to SMS when finished that work work just for the current cycle.

I’m sending email notifications to a selection of people. I’m using mqtt to send notifications and then have mqttwarn sending the actual mail.
For me email is just as good as sms in terms of being a notifier. I know there are plenty of options in mqttwarn, not sure about sms though.

I guess a intelligent choice could be made depending on presence if you have that.

Regards s

I have a system working, but I would love some help on how I could clean it up.

system.items

String    Notify_Message
String    Notify_Person

notify.rules

rule "Notify"
when
  Item Notify_Person received update
then  
  logInfo("Notify", "Notify: " + Notify_Person.state + " - " + Notify_Message.state
  if (Notify_Person.state == "nathan") {
    sendMail("832330xxxx@mms.att.net", "OpenHab", "" + Notify_Message.state)
  } else if (Notify_Person.state == "beth") {
    sendMail("832215xxxx@mms.att.net", "OpenHab", "" + Notify_Message.state)
  } else if (Notify_Person.sate == "joshua") {
    sendMail("304579xxxx@mms.att.net", "OpenHab", "" + Notify_Message.state)
end

Is this a good way of dealing with notifications? I figured that it puts it all in one place and lets all rules set Notify_Message and then Notify_Person. On a update it pushes the message out to the right person.

appliances.items

Switch    First_Washer                       "1st Floor Washer"                                       <washer>        (First_Laundry) 
String    First_Washer_Notify
Switch    First_Dryer                        "1st Floor Dryer"                                        <dryer>         (First_Laundry) 
String    First_Dryer_Notify

Switch    Second_Washer                      "2nd Floor Washer"                                       <washer>        (Second_Laundry) 
String    Second_Washer_Notify
Switch    Second_Dryer                       "2nd Floor Dryer"                                        <dryer>         (Second_Laundry) 
String    Second_Dryer_Notify

appliances.rules

rule "First Washer State Machine"
when
  Item Circuit_C14 changed
then
  if (First_Washer.state == OFF && Circuit_C14.state > 10) {
    sendCommand(First_Washer, ON)
  } else if (First_Washer.state == ON && Circuit_C14.state < 10) {
    sendCommand(First_Washer, OFF)
    if (First_Washer_Notify.state != "null") {
      // Notify person the Washer is finished
      postUpdate(Notify_Message, "1st Floor Washer Finished")
      postUpdate(Notify_Person, First_Washer_Notify.state)
    }
    // Clear Notify
    postUpdate(First_Washer_Notify, "null")
  }
end

rule "First Dryer State Machine"
when
  Item Circuit_C13 changed
then
  if (First_Dryer.state == OFF && Circuit_C13.state > 10) {
    sendCommand(First_Dryer, ON)
  } else if (First_Dryer.state == ON && Circuit_C13.state < 10) {
    sendCommand(First_Dryer, OFF)
    if (First_Dryer_Notify.state != "null") {
      // Notify person the Dryer is finished
      postUpdate(Notify_Message, "1st Floor Dryer Finished")
      postUpdate(Notify_Person, First_Dryer_Notify.state)
    }
    // Clear Notify
    postUpdate(First_Dryer_Notify, "null")
  }
end

rule "Second Washer State Machine"
when
  Item Circuit_C15 changed
then
  if (Second_Washer.state == OFF && Circuit_C15.state > 10) {
    sendCommand(Second_Washer, ON)
  } else if (Second_Washer.state == ON && Circuit_C15.state < 10) {
    sendCommand(Second_Washer, OFF)
    if (Second_Washer_Notify.state != "null") {
      // Notify person the Washer is finished
      postUpdate(Notify_Message, "2nd Floor Washer Finished")
      postUpdate(Notify_Person, Second_Washer_Notify.state)
    }
    // Clear Notify
    postUpdate(Second_Washer_Notify, "null")
  }
end

rule "Second Dryer State Machine"
when
  Item Circuit_C17 changed
then
  if (Second_Dryer.state == OFF && Circuit_C17.state > 10) {
    sendCommand(Second_Dryer, ON)
  } else if (Second_Dryer.state == ON && Circuit_C17.state < 10) {
    sendCommand(Second_Dryer, OFF)
    if (Second_Dryer_Notify.state != "null") {
      // Notify person the Dryer is finished
      postUpdate(Notify_Message, "2nd Floor Dryer Finished")
      postUpdate(Notify_Person, Second_Dryer_Notify.state)
    }
    // Clear Notify
    postUpdate(Second_Dryer_Notify, "null")
  }
end

I don’t really like the appliances.rules, lots of duplication, but the way it works is if and only if someone wants to be notified they go to the sitemap and select the person to notify for each device. As long as more then one event does not hit the notify.rules at once things should be ok.

Thanks for the nice post!
You mentioned the Sonoff Pow Wifi Switch in the beginning. How can this be integrated with openhab? Is there a special binding available?
Thank you!

1 Like

I used the rule from the first post (the second one with delay), but i always get this error:

2016-10-25 12:48:45.971 [ERROR] [o.o.c.s.ScriptExecutionThread ] - Error during the execution of rule 'Washingmachine Consumption State Machine (Extended)': The name 'MODE_OFF' cannot be resolved to an item or type.

Do you know whats wrong?

I added the Washingmachine_OpState to my items-file like you described in your example and corrected the rule to my own item-names. Also i copied the text of the rule-file including all “import” “val” and “var” to my rule-file.

Is waschinmachine_opstate a string- or number-item? Is number-item right?

Thanks

Ok, i found out what was wrong:

“Number” was missing on top of the rule-file. It should be this way:

val Number MODE_OFF = 0
val Number MODE_STANDBY = 1
val Number MODE_ACTIVE = 2
val Number MODE_FINISHED = 3

Please can you show me your rule-file?

I have to add some long timer also. Sometimes washing mashine needs delay for 5-10 minutes. And it would be better to do this with a timer instead of simple pause the rule.

Because if i stay in front of the washing machine when it´s finished very quick - rule will still be paused until delay is over. When i do it with timer, rule will see the restart of the washing machine for the next run immediately.