Washing machine status widget

UPDATE: Small bugfix - Minutes < 10 are shown correctly with a leading 0

Hello there,

this time I want to present you something more playful: A widget that displays the current state of your washing machine together with the time it has been running since the start of the washing.

This is what my final result looks like:

  1. Washing 2. Washing finished and 3. Off

Code

uid: washing_machine_v2
tags: []
props:
  parameters:
    - description: Title of the card
      label: Title
      name: title
      required: false
      type: TEXT
    - description: The card footer
      label: Footer
      name: footer
      required: false
      type: TEXT
    - description: Header text above washing machine
      label: Header
      name: header
      required: false
      type: TEXT
    - description: Expression that evaluates to minutes since the begin of washing
      label: Minutes running
      name: runtime
      required: true
      type: TEXT
    - description: Expression that evaluates to OFF, RUNNING or FINISHED
      label: State
      name: state
      required: true
      type: TEXT
  parameterGroups: []
timestamp: Feb 9, 2021, 10:12:30 PM
component: f7-card
config:
  title: =(props.title)
slots:
  default:
    - component: f7-card-content
      config:
        style:
          height: 175px
      slots:
        default:
          - component: f7-row
            config:
              class:
                - display-flex
                - justify-content-center
              style:
                width: 100%
            slots:
              default:
                - component: f7-col
                  config:
                    class:
                      - display-flex
                      - flex-direction-column
                      - align-items-center
                      - justify-content-space-evenly
                    style:
                      height: 100%
                      width: 90px
                  slots:
                    default:
                      - component: f7-block-header
                        slots:
                          default:
                            - component: Label
                              config:
                                text: =props.header
                      - component: f7-block
                        config:
                          style:
                            height: 120px
                            width: 90px
                            border-radius: 9px 9px 5px 5px
                            background: rgba(255,255,255,1)
                            box-shadow: rgba(0, 0, 0, 0.25) 0px 14px 28px, rgba(0, 0, 0, 0.22) 0px 10px 10px
                          class:
                            - display-flex
                            - flex-direction-column
                            - align-items-center
                        slots:
                          default:
                            - component: f7-block
                              config:
                                class:
                                  - display-flex
                                  - justify-content-flex-end
                                  - align-items-center
                                  - flex-shrink-0
                                  - no-margin
                                style:
                                  height: 23px
                                  width: 85px
                                  border-bottom: 1pt solid lightgray
                                  padding-right: 10px
                              slots:
                                default:
                                  - component: f7-icon
                                    config:
                                      f7: circle_fill
                                      size: 10
                                      color: gray
                                  - component: f7-icon
                                    config:
                                      f7: circle_fill
                                      size: 10
                                      color: gray
                                  - component: f7-icon
                                    config:
                                      f7: '=props.state == "OFF" ? "circle_fill" : "sun_min"'
                                      size: 10
                                      style:
                                        color: '=props.state == "OFF" ? "gray" : "red"'
                            - component: f7-block
                              config:
                                class:
                                  - display-flex
                                  - justify-content-center
                                  - align-items-center
                                style:
                                  height: 60px
                                  width: 60px
                                  border: 5pt solid lightgray
                                  border-radius: 50%
                                  margin-top: 7px
                              slots:
                                default:
                                  - component: f7-preloader
                                    config:
                                      size: 50
                                      colorTheme: lightblue
                                      visible: =props.state == "RUNNING"
                                      style:
                                        position: absolute
                                  - component: f7-icon
                                    config:
                                      f7: '=props.state == "FINISHED" ? "circle_bottomthird_split" : "circle"'
                                      size: 50
                                      style:
                                        color: '=props.state == "OFF" ? "lightgray" : "lightblue"'
                                        border-radius: 50%
                                        box-shadow: 0 0 16px 8px rgba(0, 0, 0, 0.25) inset
                                  - component: f7-block-header
                                    config:
                                      visible: =props.state == "RUNNING"
                                      class:
                                        - no-margin
                                      style:
                                        position: absolute
                                    slots:
                                      default:
                                        - component: Label
                                          config:
                                            text: '=Math.floor(props.runtime / 60) + ":" + ((props.runtime % 60) < 10 ? ("0" + props.runtime % 60) : (props.runtime % 60))'
                                            style:
                                              color: black
    - component: f7-card-footer
      slots:
        default:
          - component: Label
            config:
              text: =props.footer

Configuration

Most of it is rather straightforward:

  1. Title: Sets the card title if you want one.
  2. Footer: Sets the card footer if you want one.
  3. Header: Sets the text above the machine icon.
  4. Minutes running: Sets the time inside of the machine. This should be evaluated by an expression. I haven’t implemented it myself yet but I guess this should be done via an item that gets updated regularly by a script. Let me know if you have any better ideas.
  5. State: Can be one of OFF, RUNNING or FINISHED and should be filled in via an expression as well depending on the current state of your machine.

Have fun :slight_smile:

26 Likes

It has a high WAF btw… I did two loads of wahing today just to see it spin :rofl:

In case you are struggling with the “Minutes running” parameter I can show you how I solved this:

  1. I created a Runtime Item

  2. I set this Item to ‘1’ when the washing starts in the script that determines the washing machine state:

var threshold = new QuantityType("5 W")
var zero = new QuantityType("0.1 W")
if (newState <= zero) {
  events.sendCommand('washing_machine_state', 'Aus')
} else if (newState >= threshold  && oldState < threshold) {
  events.sendCommand('washing_machine_state', 'Waschvorgang')
  events.sendCommand('washing_machine_runtime', 1)
} else if (newState < threshold  && oldState >= threshold) {
  events.sendCommand('washing_machine_state', 'Waschvorgang beendet')
}

Why do I not set it to 0? This is because I determine the state from the average consumption over the last five minutes so my “Washing” state is detected too late anyway and I’m compensating a bit for that. However it is really not important to be super precise in that case (at least in my opinion). If you have a real smart device you may be able to use a better solution, I only use a Shelly Plug S to make my dumb machine “smart”.

  1. Update the minute item with a cron rule for every minute as long as the machine state is “RUNNING” (you need mapdb or another persistance provider for that):
var logger = Java.type("org.slf4j.LoggerFactory").getLogger("scripts.washing.runtime");

var PersistenceExtensions = Java.type("org.openhab.core.persistence.extensions.PersistenceExtensions");
var ZonedDateTime = Java.type("java.time.ZonedDateTime");
var Duration = Java.type("java.time.Duration");

var lastUpdate = PersistenceExtensions.lastUpdate(ir.getItem('washing_machine_state'), "mapdb");
logger.info('Washing machine state last changed at ' + lastUpdate);
var minutesSinceLastUpdate = Duration.between(lastUpdate, ZonedDateTime.now()).toMinutes();
logger.info('Washing machine state changed ' + minutesSinceLastUpdate + ' minutes ago');

events.postUpdate('washing_machine_runtime', minutesSinceLastUpdate);

Two questions from my side:

  1. Does anyone know how the condition handling in rules is implemented? Is it just like an if condition and the rule fires anyway? I’m asking because I don’t want to use rules that fire every single minute excessively (even though they don’t really do much then).
  2. Why does MapDb know when the item has lasst changed when I haven’t configured persistence for this item? Is this:
    a) The default behaviour or
    b) Am I persisting all items states by accident/wrong configuration?
2 Likes

Hi,

this is a fantastic widget. I adapted it also for my dishwasher :smiley: And made it display the running program. Anyway there is one thing I can’t get it to do: I’d like the whole widget to be clickable and do an action when clicked (in my case: show group). Any hints how to archive this?

Thanks!

Without having it tried the f7-card has an expandable property. This should make it behave like the automatically generated cards. This would probably be the easiest option. Would you mind sharing your adapted widget code here? Since my machine is not smart I cannot show any more information in the widget but I’m pretty sure others could and might find it useful.

Well the adaptation is quite small. But give me a moment to create a post out of those changes :slight_smile:

As for the expandable property… I have no idea what you’re talking about. But that is probably my lack of understanding. Would it be possible to demonstrate something?

Ok but I would be interested in your dishwasher :wink:

I just tried that (added expandable: true under the f7-card config). It makes the card clickable and opens in a popup but it also changes the current layout. I haven’t worked with expandable cards yet so I don’t know what to do from here except of course reading the f7 documentation and searching the forums :wink:

Or maybe someone else has some more experience to share here.

Adaptation for a smart washing machine from Miele

I love the design of this widget, even beside the fact that I’m probably not able to create it myself so I adapted it for my smart washing machine which is included into OH by its own binding. I have information about the power state, running program, temperature and runtime/remaining time available. All those items were already created for a while so I could use my events.log file to tell which values I need to use.

I’ll describe the process below.

Code

component: widget:washing_machine_v2
config:
title: Waschmaschine
state: =items.waschmaschine_OperationState.state
runtime: =(items.waschmaschine_ProgramRemainingTime.state)/60
header: ‘=(items.waschmaschine_ProgramPhase.state != “UNDEF” ) ? items.waschmaschine_ProgramPhase.state : “”’
footer: ‘=(items.waschmaschine_ActiveProgram.state != “UNDEF” ? items.waschmaschine_ActiveProgram.state : “”) + (items.waschmaschine_TargetTemperature.state != “UNDEF” ? ", " + items.waschmaschine_TargetTemperature.state : “”)’

Step 1: state
I searched my logfile for the different states the machine goes through. In my case they were called a little differently and are translated to German: “Aus”, “In Betrieb” and “Ende”

Consequently I changed all occurences of OFF, RUNNING and FINISHED to those strings-

Step 2: runtime
I’d prefer seeing the remaining time. Checking the logfile I noticed that my machine is reporting the remaining time in seconds. So this is easy to do when you just add a /60 to your formula.

Step 3: header / Program step
I used the space above the machine to display the state of the program. What I worked around is that the Binding will report ‘UNDEF’ if no program is running. In that case nothing is displayed.

Step 4: footer / Program name and temp
More or less like Step 3. Only a little bit more complicated in working out what to display. In this case it checks if the program name (AcitveProgram) is something else then UNDEF and if yes displays it. Followed by a comma and the set temperature if that one is something else then UNDEF.

Other appliances
As it happens I use the same binding not only for my washing machine but also my dishwasher and sometime soon (whenever they deliver it) my dryer. So adapting the same process for a dishwasher and later the dryer is as easy as changing out the item names. Most of the program data is more or less the same. One change is that the dishwasher has one more ‘state’ (which I just ignore) and does not report a target temperature (so I don’t show it).

Only downside is that in the UI my dishwasher now looks like a washing machine :wink:

Future improvements
What I’m going to implement is a notification when the program was recently finished (and the machine is already off). Because that way you can tell from a quick glance if one of the appliances finished without you noticing.

The three appliances I’m working with all have a ‘FINISHED’ switch in the binding. I’ll create a rule which toggles a virtual ‘recently_finished’ switch whenever FINISHED toggles ON. That recently_finished switch gets an expiration timer of maybe 2-3h. Also when the according appliance is toggled on the recently_finished switch is toggled off. After that I can for example change the background color of the footer or display some text in the widget when recently_finished is ON.

Thanks. I guess I’ll look into it :wink:

Thank you for sharing details of your adaption. I would also prefer to show the remaining time but this is simply not possible with my dumb machine… But I use a notification as well which is more important anyway. The widget was just made to play around a bit :wink:

Oh and btw: I will probably be adding my dishwasher soon as well so I will probably create another widget for that :wink:

it is pretty simple to add an action to show a group in case of clicking the widget. Add this to your widget and define the group to show in the props.

  >  - component: oh-link
>       config:
>         action: group
>         actionGroupPopupItem: =[props.itemGroup]
>         style:
>           position: absolute
>           z-index: 1
>           width: 100%
>           height: 100%
>           top: 0
>           left: 0

I hope this is what you were searching for.

I was thinking about something like this as well but haven’t tried it. Looks good and very simple! Thank you for sharing

I’d love to have the dishwasher not looking like a washing machine :smiley: But in terms of designing something out of simple elements I’m quite… well bad.

Thanks @muelli1967! Works like a charm. I still don’t really understand where to put what, but it worked out :smiley:

Here you go:

:smiley:

1 Like

the widget is working nicely here. Thanks Thomas for your efforts and the clear explanations!

2 Likes

Can anyone tell me how to get the status of your washing machine in openHab ? Does anyone measures the used Ampere of the machine ? which kind of hardware ? thanks

Stefan mentioned that he owns a smart Miele machine. I own a dumb Miele machine and use a Shelly plug S that can measure the energy consumption of the attached device (I’m sure however there are lots of alternatives on the market). By analysing the typical energy usage during and after the washing you can figure out thresholds to determine when the machine is finished. In contrast to Stefan I can’t do any more than that but that’s in fact all I need.

Connecting the washing machine and dryer to a KNX actuator which can measure the current was one of the oversights in my installation. Now I am using switchable plugs from AVM, which are connected via Dect, this works fine.

To get the state I am using a openHAB rule with a simple state machine, based on the current. This needed some trying, to get the limits correct but now it is very reliable.