Washing Machine State Machine

Sorry…
This one I have bought 5 years ago… still working properly using a USB stick and 2 ‘plugs’
There is also a binding available. you can measure and switch on/off.
https://www.plugwise.com/en_US/products/plug

I read somewhere this also can be done by sonoff (Chinese stuff).

I like that tutorial a lot, unfortunately I’m not getting the expected results. I’m new at this which is why I suppose I’m making a beginners mistake. The only output I receive in my GUI is a “-”.

washingmachine.items:

String Washingmachine_OpState “Washingmachine State [MAP(washingmachine.map):%s]”

washingmachine.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 avmfritz_FRITZ_DECT_200_1_087610044241_power changed
then
if (avmfritz_FRITZ_DECT_200_1_087610044241_power.state < 0.2) Washingmachine_OpState.postUpdate(MODE_OFF)
else if (avmfritz_FRITZ_DECT_200_1_087610044241_power.state > 10) Washingmachine_OpState.postUpdate(MODE_ACTIVE)
else if (avmfritz_FRITZ_DECT_200_1_087610044241_power.state < 3) {
if (Washingmachine_OpState.state == MODE_OFF) Washingmachine_OpState.postUpdate(MODE_STANDBY)
else if (Washingmachine_OpState.state == MODE_ACTIVE) Washingmachine_OpState.postUpdate(MODE_FINISHED)
}
end

default.sitemap entry, embedded in a frame:

Text item=Washingmachine_OpState label=“Waschmaschine [MAP(washingmachine.map):%d]” icon=“washingmachine_2”

washingmachine.map:

0=Aus
1=Standby
2=Aktiv
3=Fertig
NULL=?
-=-

Some example lines from my measurements:

2019-02-10 15:58:39.185 [vent.ItemStateChangedEvent] - avmfritz_FRITZ_DECT_200_1_087610044241_power changed from 394.390 W to 389.310 W
2019-02-10 15:58:54.815 [vent.ItemStateChangedEvent] - avmfritz_FRITZ_DECT_200_1_087610044241_power changed from 389.310 W to 15.300 W
2019-02-10 15:59:09.967 [vent.ItemStateChangedEvent] - avmfritz_FRITZ_DECT_200_1_087610044241_power changed from 15.300 W to 15.230 W
2019-02-10 15:59:24.975 [vent.ItemStateChangedEvent] - avmfritz_FRITZ_DECT_200_1_087610044241_power changed from 15.230 W to 15.300 W
2019-02-10 15:59:39.805 [vent.ItemStateChangedEvent] - avmfritz_FRITZ_DECT_200_1_087610044241_power changed from 15.300 W to 15.230 W
2019-02-10 15:59:54.821 [vent.ItemStateChangedEvent] - avmfritz_FRITZ_DECT_200_1_087610044241_power changed from 15.230 W to 15.660 W
2019-02-10 16:00:10.474 [vent.ItemStateChangedEvent] - avmfritz_FRITZ_DECT_200_1_087610044241_power changed from 15.660 W to 3.140 W
2019-02-10 16:00:24.803 [vent.ItemStateChangedEvent] - avmfritz_FRITZ_DECT_200_1_087610044241_power changed from 3.140 W to 3.360 W
2019-02-10 16:00:54.825 [vent.ItemStateChangedEvent] - avmfritz_FRITZ_DECT_200_1_087610044241_power changed from 3.360 W to 3.070 W
2019-02-10 16:01:25.005 [vent.ItemStateChangedEvent] - avmfritz_FRITZ_DECT_200_1_087610044241_power changed from 3.070 W to 3.360 W
2019-02-10 16:01:39.833 [vent.ItemStateChangedEvent] - avmfritz_FRITZ_DECT_200_1_087610044241_power changed from 3.360 W to 3.140 W
2019-02-10 16:01:54.927 [vent.ItemStateChangedEvent] - avmfritz_FRITZ_DECT_200_1_087610044241_power changed from 3.140 W to 14.730 W
2019-02-10 16:02:10.041 [vent.ItemStateChangedEvent] - avmfritz_FRITZ_DECT_200_1_087610044241_power changed from 14.730 W to 3.430 W
2019-02-10 16:02:24.927 [vent.ItemStateChangedEvent] - avmfritz_FRITZ_DECT_200_1_087610044241_power changed from 3.430 W to 3.070 W
2019-02-10 16:02:54.887 [vent.ItemStateChangedEvent] - avmfritz_FRITZ_DECT_200_1_087610044241_power changed from 3.070 W to 3.360 W
2019-02-10 16:03:24.864 [vent.ItemStateChangedEvent] - avmfritz_FRITZ_DECT_200_1_087610044241_power changed from 3.360 W to 3.070 W
2019-02-10 16:03:54.877 [vent.ItemStateChangedEvent] - avmfritz_FRITZ_DECT_200_1_087610044241_power changed from 3.070 W to 2.930 W
2019-02-10 16:04:09.840 [vent.ItemStateChangedEvent] - avmfritz_FRITZ_DECT_200_1_087610044241_power changed from 2.930 W to 2.640 W
2019-02-10 16:04:39.825 [vent.ItemStateChangedEvent] - avmfritz_FRITZ_DECT_200_1_087610044241_power changed from 2.640 W to 2.930 W
2019-02-10 16:04:55.208 [vent.ItemStateChangedEvent] - avmfritz_FRITZ_DECT_200_1_087610044241_power changed from 2.930 W to 2.640 W
2019-02-10 16:05:24.834 [vent.ItemStateChangedEvent] - avmfritz_FRITZ_DECT_200_1_087610044241_power changed from 2.640 W to 2.930 W

Basic UI Output:
BasicUI-Output

May somebody please help me to find the problem?

You are defining the mapping in both your .items and your .sitemap. Also, in your .sitemap you are using a %d which is for a number, you have the correct %s in your .items definition.

1 Like

Thank you very much. It’s working fine now, but I still get the following warning in the logs:

17:13:41.396 [WARN ] [.rest.core.item.EnrichedItemDTOMapper] - Failed transforming the state ‘0’ on item ‘Washingmachine_OpState’ with pattern ‘MAP(AVM.map):%d’: Cannot format state ‘0’ to format ‘%d’

It says “Cannot format state”, but still the “0” gets transformed into “Aus” as expected and described in my AVM.map file:

0=Aus
1=Standby
2=Active
3=Fertig
-=-
NULL=-

The result in the Basic UI is as expected:
image

For completeness, this is how my item looks like:

Number Washingmachine_OpState “Washingmachine State [MAP(AVM.map):%d]”

What am I doing wrong and how to avoid that warning?

You have to use %s instead.

Number Washingmachine_OpState “Washingmachine State [MAP(AVM.map):%s]”

trying to get this working here, but im not getting the MODE_FINISHED part.
It goes from off to on to off, but never to finished.

Hi,
could you share .items,.map and script?
i’ve tried this method but it hangs at STATE_DEBOUNCE and changing to STATE_FINISHED
this thread is very big and for someone new trying to find a decent working method it’s a mission.
the first method presented and extended one jumps from Active to Off and never Finished. i’m cursed :slight_smile:

Hello,
I’ve tried every solution given in this tutorial but for some reason i had no success. This could also be due to cheap “Tuya SP23” that loves to report 0W between the cycles when power drop from 1000w to 1w.
It may be me that didn’t have success with InfluxDb but in Grafana i can see everything.
Long story short…I took simplified version of the tutorial and i added timers to confirm if the power is constant or was just a spike, if it was a spike then the timer gets cancelled and is waiting for the next power drop.
the two variables on top represents the gaps between cycles.
this it works for me and i would like to share with others if helpful but i would like an opinion from someone with more experience if can be polished or it’s bad way of doing
I’ve left logInfo to help debug until timings are identified and working as intended
Washingmachine.rules

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

val Washingmachine_Timer_1_Delay_sec  = 15
val Washingmachine_Timer_2_Delay_sec  = 21 //time off between cycles

var Timer Washingmachine_Timer_1       // timer < 0.5 W
var Timer Washingmachine_Timer_2       // timer < 3.5 W

rule "Washingmachine Consumption State Machine"
when
    Item Power4 changed
then
    if (Power4.state < 0.5){ 
        if (Washingmachine_Timer_1 === null) {
            Washingmachine_Timer_1 = createTimer(now.plusSeconds(Washingmachine_Timer_1_Delay_sec), [ |
                logInfo("Washingmachine Timer1", "Started < 0.5 W")
                if (Power4.state < 0.5){ 
                        Washingmachine_OpState.postUpdate(MODE_OFF) 
                        Washingmachine_Timer_1 = null  // Cancels the timer 1 when run out
                        logInfo("Washingmachine Timer1", "MODE_OFF < 0.5 W")
                }
                else if (Power4.state > 0.5){
                    if (Washingmachine_Timer_1 !== null) {  //// Cancels the timer 2 when Power4 > 3.5 W
                    Washingmachine_Timer_1.cancel()
                    Washingmachine_Timer_1 = null
                    logInfo("Washingmachine Timer1", "Canceled >0.5 W")
                    }
                }
            ])
            
        }
    }        
        
    else if (Power4.state > 3.5) {
                Washingmachine_OpState.postUpdate(MODE_ACTIVE)
                if (Washingmachine_Timer_1 !== null) {  //// Cancels the timer 1 when Power4 > 3.5 W
                    Washingmachine_Timer_1.cancel()
                    Washingmachine_Timer_1 = null
                    logInfo("Washingmachine Timer1", "Canceled > 3.5 W")
                }
                else if (Washingmachine_Timer_2 !== null) {  //// Cancels the timer 2 when Power4 > 3.5 W
                    Washingmachine_Timer_2.cancel()
                    Washingmachine_Timer_2 = null
                    logInfo("Washingmachine Timer2", "Canceled > 3.5 W")
                }

    }
    else if (Power4.state < 3.5) {
        if (Washingmachine_Timer_2 === null) {
            Washingmachine_Timer_2 = createTimer(now.plusSeconds(Washingmachine_Timer_2_Delay_sec), [ |
                if (Power4.state < 3.5) { 
                        logInfo("Washingmachine Timer2", "Started < 3.5 W")
                        if (Washingmachine_OpState.state == MODE_OFF){
                            Washingmachine_OpState.postUpdate(MODE_STANDBY)
                            Washingmachine_Timer_2 = null  // Cancels the timer 2 when run out
                            logInfo("Washingmachine Timer2", "Standby after MODE_OFF ")
                            }
                        else if (Washingmachine_OpState.state == MODE_ACTIVE){
                            Washingmachine_OpState.postUpdate(MODE_FINISHED) 
                            Washingmachine_Timer_2 = null  // Cancels the timer 2 when run out
                            logInfo("Washingmachine Timer2", "Finished after MODE_FINISHED ")
                            }
                    }
                else if (Power4.state > 3.5){
                    if (Washingmachine_Timer_2 !== null) {  //// Cancels the timer 2 when Power4 > 3.5 W
                    Washingmachine_Timer_2.cancel()
                    Washingmachine_Timer_2 = null
                    logInfo("Washingmachine Timer2", "Canceled >3.5 W")
                    }
                    else if (Washingmachine_Timer_1 !== null) {  //// Cancels the timer 1 when Power4 > 3.5 W
                    Washingmachine_Timer_1.cancel()
                    Washingmachine_Timer_1 = null
                    logInfo("Washingmachine Timer1", "Canceled > 3.5 W")
                    }  
                }
            ])
                
        }
    }
end

I have adapted the code to my hardware specifc (load levels and wait time to turn off if load is below 3 for two measurements in a row) but sometimes MODE_FINISHED is triggered for no apparent reason. Maybe you can figure this out.
Additional info: at other times during operation, the value dropped to 2 as well for a single measurement and did not cause the fals triggering of MODE_FINISHED.

Rule:

rule "Washingmachine Consumption State Machine (Extended)"
when
    Item SD_Jis1_Waschmaschine_Powerload changed
then
    if (SD_Jis1_Waschmaschine_Powerload.state > 20) SD_Jis1_Waschmaschine_Status.postUpdate(MODE_ACTIVE)
    else if (SD_Jis1_Waschmaschine_Powerload.state < 3) {
        if (SD_Jis1_Waschmaschine_Status.state == MODE_ACTIVE) {
            finishLock.lock()
            try {
                Thread::sleep(61000) // Debounce for 61 seconds
                if (SD_Jis1_Waschmaschine_Powerload.state < 3) {
                    SD_Jis1_Waschmaschine_Status.postUpdate(MODE_FINISHED)
                    SD_Jis1_Waschmaschine_Status.postUpdate(MODE_OFF)
                }
            } finally {
                finishLock.unlock()
            }
        }
    }
end

State values with wrong behaviour at 12:48:

2019-12-19 12:21:56.453 [vent.ItemStateChangedEvent] - SD_Jis1_Waschmaschine_Status changed from 0 to 1
2019-12-19 12:48:57.438 [vent.ItemStateChangedEvent] - SD_Jis1_Waschmaschine_Status changed from 1 to 2
2019-12-19 12:48:57.453 [vent.ItemStateChangedEvent] - SD_Jis1_Waschmaschine_Status changed from 2 to 0
2019-12-19 12:48:57.500 [vent.ItemStateChangedEvent] - SD_Jis1_Waschmaschine_Status changed from 0 to 1
2019-12-19 13:44:57.511 [vent.ItemStateChangedEvent] - SD_Jis1_Waschmaschine_Status changed from 1 to 2
2019-12-19 13:44:57.523 [vent.ItemStateChangedEvent] - SD_Jis1_Waschmaschine_Status changed from 2 to 0

Powerload values at relevant time:

2019-12-19 12:44:56.436 [vent.ItemStateChangedEvent] - SD_Jis1_Waschmaschine_Powerload changed from 115 to 2196
2019-12-19 12:45:56.419 [vent.ItemStateChangedEvent] - SD_Jis1_Waschmaschine_Powerload changed from 2196 to 114
2019-12-19 12:46:56.435 [vent.ItemStateChangedEvent] - SD_Jis1_Waschmaschine_Powerload changed from 114 to 2275
2019-12-19 12:47:56.401 [vent.ItemStateChangedEvent] - SD_Jis1_Waschmaschine_Powerload changed from 2275 to 2
2019-12-19 12:48:57.479 [vent.ItemStateChangedEvent] - SD_Jis1_Waschmaschine_Powerload changed from 2 to 102
2019-12-19 12:49:56.432 [vent.ItemStateChangedEvent] - SD_Jis1_Waschmaschine_Powerload changed from 102 to 95

Seems to be the expected behaviour; powerload was < 3 for more than 6100mS

But is this delay in value change somehow caused by the 61-second-sleep in the rule? For all other value changes the interval is exactly 60 seconds (and right after this it’s 59 seconds, so jumping back in the old rythm).

This isn’t a realtime system. Looking for a delay that you know will be around sixty seconds - and sometimes is known to vary to 59 seconds - using a 61 second threshold is dumb.

Yes, the long sleep in the rule may be contributing. It ties up system resources for the duration, so potentially could delay the processing of external events. There’d have to be other contributions as well - other rules with sleeps, lots of I/O activity, etc. - but not impossible.

1 Like

Do it without a Thread::Sleep and use your Persistence :slight_smile:

val Number MODE_OFF = 0
val Number MODE_STANDBY = 1
val Number MODE_ACTIVE = 2
val Number MODE_FINISHED = 3
rule "Waschmaschine Status"
when
    Item Steckdose_Waschmaschine_Power changed
then
    if (Steckdose_Waschmaschine_Power.state < 0.4) Waschmaschine_Status.postUpdate(MODE_OFF)
    else if (Steckdose_Waschmaschine_Power.state > 30) Waschmaschine_Status.postUpdate(MODE_ACTIVE)
    else if (Steckdose_Waschmaschine_Power.averageSince(now.minusMinutes(3))< 5){
      if (Waschmaschine_Status.state == MODE_OFF) Waschmaschine_Status.postUpdate(MODE_STANDBY)
      else if (Waschmaschine_Status.state == MODE_ACTIVE) Waschmaschine_Status.postUpdate(MODE_FINISHED)
    }
end
1 Like

Still have to get into the persistence topic as I didn’t need it so far.
I only use mapdb to store the last item state.

I use mapdb for restore at startup and influxdb for use cases like the washing machine.

rule "Waschmaschine Status"
when
    Item Steckdose_Waschmaschine_Power changed
then
    if (Steckdose_Waschmaschine_Power.state < 0.4) Waschmaschine_Status.postUpdate(MODE_OFF)
    else if (Steckdose_Waschmaschine_Power.state > 30) Waschmaschine_Status.postUpdate(MODE_ACTIVE)
    else if (Steckdose_Waschmaschine_Power.averageSince(now.minusMinutes(3), "influxdb")< 5){
      if (Waschmaschine_Status.state == MODE_OFF) Waschmaschine_Status.postUpdate(MODE_STANDBY)
      else if (Waschmaschine_Status.state == MODE_ACTIVE) Waschmaschine_Status.postUpdate(MODE_FINISHED)
    }
end
1 Like

I had difficulties to make the script work with a tplink 110 added in openhab2 through simple mode. The issue was the Unit “W” (defined as Number:Power tplinksmarthome_hs110_A8E90C_power “Power [%.1f %unit%]”).
The workaround was to change the checks as follow, adding "| “W”:
if (tplinksmarthome_hs110_A8E90C_power.state < 1 | “W” )
I got this solution through this post

hope this can help.
J

Hi all together,

during my search i found this thread.
Maybe it can be approach for my project.
I have rule that starts and stop my Ventingsystem dependent on humidity

rule "WACLuftfeuchtigkeit"
when
    Member of Durchschnitt_Feuchte_Haus changed // Durchschnitt Feuchte durch Lacrosse Sensoren
then
    if (WAC350_Durchschnitt_Feuchte_Haus.state != ON) { // wenn Schalter aus(nicht an) , keine Aktionen
        logInfo("Feuchtesteuerung:","Steuerung deaktiviert!")
    return;
    }
    // bestimme die durchschnittliche und maximale Luftfeuchte
    var int lfSumme=0
    var int lfMax=0
    var int lfCnt=0
    for (item: Durchschnitt_Feuchte_Haus.members) {
        if (item.state instanceof Number) {
            lfCnt+=1
            val int lf=(item.state as Number).intValue()
            lfSumme+=lf
            if (lf>lfMax) lfMax=lf
        }
    }
    // nur bei mindestens 2 Werten ist es sinnvoll, die Werte zu betrachten
    var int lfAvg
    if (lfCnt>=2) {
        lfAvg=lfSumme/lfCnt
    }
    else {
        lfAvg=0
        lfMax=0
    }
    // Bei mehr als max 60% pro Sensor oder avg 55% Luftfeuchtigkeit lüfte.
    if ((lfMax>=60 || lfAvg>=55) && FanStandby_Switch.state != OFF) {  // Standby ist ON und nicht OFF = Lüftung läuft nicht
        FanStandby_Switch.sendCommand(OFF)                             // Schalte Lüftung EIN
        logInfo("Feuchtesteuerung:","Lüftung EIN") 
        sendBroadcastNotification("Feuchtesteuerung: Lüftung EIN")     //Pushnachricht
    }
    else {
    // fällt die Luftfeuchte aller Sensoren unter 55%, wurde genug gelüftet. Lüftung Stop
    if ((lfMax<55) && FanStandby_Switch.state != ON) {                 // Standby ist OFF und nicht ON = Lüftung läuft
        FanStandby_Switch.sendCommand(ON)                              // Schalte Lüftung Standby
        logInfo("Feuchtesteuerung:","Lüftung Standby")
        sendBroadcastNotification("Feuchtesteuerung: Lüftung Standby") //Pushnachricht
        }
    }
end

This works very well.
But now i want to start the Venting also if in the kitchen the recirculation hood is swichted on.
I can detect this by taking the energy consumption in watts by a Shelly Plug S.
So at the end i have a number item with the watts value in it.
If the hood is switched on the watts value is minimum 50Watts.
How can i put this into my exsiting rule?
Basically the use of the hood should my have more priority like the humidiy.
So i want to say:
If the venting is off and the hood is in use, switch on the venting for 60minutes no matter what the humindiy is.

Or is it more easy to do this with time cron ?
Ususally the Kitchen is in use between 11.30 and 12.30 every day.
So can i say: If the time is in between 11.30 and 12.30 every day, switch on the venting if the venting is not on, but dont allow the humidity value to switch of the venting during that period of time

Or is it more usefull to have a second rule that send a postUpdate to the “WAC350_Durchschnitt_Feuchte_Haus” (it´s activating or deactivating the humidity rule)

Is this possible ?

Br Peter

This is really not the topic of this tutorial. Start a new thread.

You seem to have a good idea of what you need to do, it’s not really clear what difficulty you are having with doing it.

There are some Zigbee Devices: https://forum.iobroker.net/topic/20234/steckdose-mit-zigbee-und-verbrauchsmessung/9

i’ve adjusted an example from above and get an “error” on the “<” sign in VSCode

rule "Spülmaschine Status"
when
    Item BW1_Power changed
then
    if (BW1_Power.state < 0.4) Spuelmaschine_Status.postUpdate(MODE_OFF)
    else if (BW1_Power.state > 30) Spuelmaschine_Status.postUpdate(MODE_ACTIVE)
    else if (BW1_Power.averageSince(now.minusMinutes(3), "influxdb") < 5){
      if (Spuelmaschine_Status.state == MODE_OFF) Spuelmaschine_Status.postUpdate(MODE_STANDBY)
      else if (Spuelmaschine_Status.state == MODE_ACTIVE) Spuelmaschine_Status.postUpdate(MODE_FINISHED)
    }
end

popup from VSCode:

Ambiguous binary operation.
The operator declarations
operator_lessThan(Number, Number) in NumberExtensions and
operator_lessThan(Type, Number) in NumberExtensions
both match.(org.eclipse.xtext.xbase.validation.IssueCodes.ambiguous_feature_call)

What is there to do?