Washing machine status widget

washing

This widget displays the current state of your washing machine together with the time it has been running since the start of the washing.

Configuration is pretty straightforward (see screenshot below):

  1. Title: Sets the card title if you want one. Leave empty for no title.
  2. Footer: Sets the card footer if you want one. Leave emtpy for no footer.
  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 (e.g. =items.washing_machine_runtime.state).
  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.
  6. Dryer Mode (not in the screenshot below): Put the string ā€œONā€ in here to get a red/orange color scheme that can be used for a dryer.

Have fun :slight_smile:

Screenshots

Dryer Mode:
dryer

Configuration:

Changelog

Version 0.4

  • fixed: Replaced standard preloader component with the replacement as proposed by @Nico_R for my dishwasher widget. This way the look of the washing machine will be the same on iOS and android devices

Version 0.3

  • added: dryer mode with a red/orange color scheme

Version 0.2

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

Version 0.1

  • initial release

Resources

uid: washing_machine_v4
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
    - description: "Dryer mode: Set this to ON for a red dryer color scheme"
      label: Dryer Mode
      name: dryer
      required: false
      type: TEXT
  parameterGroups: []
timestamp: Oct 2, 2021, 9:24:59 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: 2px 2px 4px 4px
                            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-block
                                    config:
                                      colorTheme: '=props.dryer == "ON" ? "red" : "lightblue"'
                                      class: custom-preloader-container-dummy
                                      style:
                                        display: '=props.state == "RUNNING" ? "" : "none"'
                                        margin: 0
                                        padding: 0
                                        --f7-preloader-size: 50px
                                        --f7-preloader-color: var(--f7-theme-color)
                                        position: absolute
                                        left: 50%
                                        top: 50%
                                        transform: translate(-50%, -50%)
                                    slots:
                                      default:
                                        - component: f7-block
                                          config:
                                            class: custom-preloader-dummy
                                            style:
                                              margin: 0
                                              padding: 0
                                              width: var(--f7-preloader-size)
                                              height: var(--f7-preloader-size)
                                          slots:
                                            default:
                                              - component: f7-block
                                                config:
                                                  class: custom-preloader-inner-dummy
                                                  style:
                                                    margin: 0
                                                    padding: 0
                                                    position: absolute
                                                    left: 0
                                                    top: 0
                                                    width: 100%
                                                    height: 100%
                                                slots:
                                                  default:
                                                    - component: f7-block
                                                      config:
                                                        class: custom-preloader-inner-circle-dummy
                                                        style:
                                                          margin: 0
                                                          padding: 0
                                                          border-radius: 50%
                                                          border: calc(var(--f7-preloader-size)/8) solid var(--f7-preloader-color)
                                                          border-top-color: transparent
                                                          box-sizing: border-box
                                                          animation: aurora-preloader-rotate 1s linear infinite
                                                          position: absolute
                                                          left: 0
                                                          top: 0
                                                          width: 100%
                                                          height: 100%
                                  - component: f7-icon
                                    config:
                                      f7: '=props.state == "FINISHED" ? "circle_bottomthird_split" : "circle"'
                                      size: 50
                                      style:
                                        color: '=props.state == "OFF" ? "lightgray" : (props.dryer == "ON" ? "orange" : "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
32 Likes

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

4 Likes

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?

1 Like

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.