Close
0%
0%

Laundry monitoring

A collection of notes and pointers for the usual laundry monitoring project.

Public Chat
Similar projects worth following
920 views
0 followers
As a project, this isn't much. So many people have implemented monitoring for their laundry machines (washer and dryer) that I fully expect to do everything using pieces of other folks' work that I find in blogs and projects and source repositories. I'll use the project log for keeping track of things.

It's not a very original thought that we sometimes forget that we have something in the washer or dryer or both, and it's not a very original idea to use automation to try to prod the household into remembering and reacting.

A few years ago, I cooked up my own solution with current sensors inside a power strip. That was kind of interesting (educational for me) and worked reasonably well for detecting when the machines were running. Although I was never completely happy with the aesthetics, it was electronically good enough. I originally designed it in an online tool (https://upverter.com/) that disappointed me with its bugs (at that time ... I haven't use it for years, so maybe it's fantastic now). I was collecting the data in another online system (Cayenne) for building dashboards of IoT data (I haven't used that for years, either).

In the intervening years, multiple things happened:

  • I got familiar with using Kicad for schematic and layout design.
  • I evaluated home automation frameworks and have now consolidated everything into a Home Assistant server that I run locally.
  • I developed an even larger bias for local control instead of the cloud for automation projects.
  • Manufacturers provide many brands of low-cost smart outlets that include power monitoring.
  • Helpful hobbyists and other helpful people have figured all of this out and left documentation all over the place.

Time to re-do this project with today's technology and infrastructure.

  • Washer and Dryer restore state

    WJCarpenter04/03/2024 at 23:06 0 comments

    I originally defined the dropdown helpers for the washer and dryer states to have the state "Unknown" at Home Assistant start-up. I rarely restart Home Assistant because of things like power failures or hardware maintenance, but I frequently restart it for software updates. In those scenarios, it's more useful to remember the state instead of resetting it to "Unknown". Making that change was just a matter of deleting the "initial:" configuration from those input_select definitions. There's no UI for that, so the edit has to happen manually in config/.storage/input_select.

  • A bug in the dryer state transitions

    WJCarpenter08/30/2023 at 21:16 0 comments

    A logic error quickly revealed itself in the state transitions I described earlier. I had said, for both the washer and the dryer, that the power turning off must lead to the STOPPED state no matter what. For the dryer, I used a measure of 8 watts above or below for a little while. That's to account for the interior light that comes on when the door is opened. The trigger for power off was "power less than 8 watts for 1 minute".

    The problem with that trigger is that opening the door, unloading the dryer, and then closing the door triggers "power off" and the dryer goes back into the STOPPED state.

    I kept the same power on and off triggers for the dryer, but I modified the condition guarding the action resulting from the dryer power off trigger. It is now "triggered by dryer power off, and current dryer state is RUNNING". I don't really need that extra logic for the washer, which has no interior light, but I'm adding it just for consistency.

    The YAML for that looks like this:

    condition: and
    conditions:
      - condition: trigger
        id: power_off_dryer
      - condition: template
        value_template: "{{ dryer_before|lower == 'running' }}"
    

  • Laundry in the wall panel

    WJCarpenter08/28/2023 at 01:56 0 comments

    I have a Home Assistant wall panel in my kitchen. It's a 10 inch tablet mounted in landscape orientation. As the first step in my notification plan, I wanted to put some kind of visual notification on that wall panel. I'm starting off simple and might do something more elaborate later. For now, I want some kind of notification that either the washer or dryer is in the stopped state, and I want the notification to go away when someone gives them attention. In my scheme, anyone opening either door will cause that machine to move on from the stopped state.

    I defined a binary sensor in a triggered template. It's triggered by any state or attribute change in the washer or dryer. The state of the template sensor is on if either the washer or dryer is stopped; otherwise, the template sensor is off.

    - trigger:
        - platform: state
          entity_id:
            - input_select.washer_state
            - input_select.dryer_state
      binary_sensor:
        - name: Laundry needs attention
          state: >-
            {% if states('input_select.washer_state')|lower == 'stopped' or states('input_select.dryer_state')|lower == 'stopped' %}
              on
            {% else %}
              off
            {% endif %}
    

    In the wall panel main screen, I use a custom button-card with a tap action that displays the laundry machine states. I directly followed the example from the documentation of blinking the card based on an entity state. Here, the entity is the triggered template sensor defined just above.

    type: custom:button-card
    name: Laundry
    entity: binary_sensor.laundry_needs_attention
    color_type: label-card
    color: rgba(7, 103, 215, 0.5)
    icon: mdi:tumble-dryer
    styles:
      card:
        - width: 80px
        - height: 72px
    state:
      - value: 'on'
        color: yellow
        styles:
          card:
            - animation: blink 2s ease infinite
      - operator: default
        color: rgba(7, 103, 215, 0.5)
    tap_action:
      action: navigate
      navigation_path: /wjc-subpanels/laundry?wp_enabled=true
    

     The button is normally a steady blue, but it slowly pulses yellow if either of the machines is stopped. When either machine changes state, the triggered template sensor will change state, and the button will go back to steady blue.

    I had some difficulty creating the triggered template sensor. Even though Home Assistant developer tools has a button for reloading template entities, I found that they didn't completely pick up changes until I restarted Home Assistant. Most of the time, it worked as expected, so it's either some edge case or I was doing something wrong.

  • Before and after

    WJCarpenter08/19/2023 at 18:01 0 comments

    I set up an action to send a notice to the Home Assistant app on my phone when the laundry automation runs. The message reports the washer and dryer states. I noticed right away that I was getting some notifications when neither of those states changed. I'm not completely sure, but I think that happens because the contact sensors on the washer and dryer doors periodically wake up and send their states, and I theorize that that's so they can report their battery levels occasionally even if they haven't been triggered by opening or closing.

    To get rid of these spurious notifications, I wanted to define some variables in the automation: washer_before, washer_after, dryer_before, and dryer_after. I could then use a condition to only do notifications if either of the pairs showed a state change. I had a dickens of a time figuring out the right syntax for this. An ambitious person would deeply study the Home Assistant documentation, along with callouts to various YAML and Jinja template documents. Instead, I used the tried and true method of googling around and trial and error. The Home Assistant automation trace UI is very helpful for that kind of experimental approach.

    Recall that the state changes are done in a massive "choose" action. I set the "before" variables just before that and the "after" variables just after that. Here's the before action; the after action is similar in the obvious way. I also do a logbook entry for diagnostic purposes.

    - variables:
        washer_before: "{{ states('input_select.washer_state') }}"
        dryer_before: "{{ states('input_select.dryer_state') }}"
    - service: logbook.log
      data:
        name: Laundry
        message: BEFORE washer {{ washer_before }}; dryer {{ dryer_before }}
    

     The condition and notification actions then looks like this:

    - condition: template
      value_template: "{{ washer_before != washer_after or dryer_before != dryer_after }}"
    - service: notify.mobile_app_sm_g981u
      data:
        title: Laundry status
        message: >-
          washer {{ washer_before }} --> {{ washer_after }}; dryer {{ dryer_before
          }} --> {{ dryer_after }}
    

    In an automation, a condition evaluating to false stops the sequence of action, in which case the  notification action that follows is not executed. If I needed more elaborate logic, I'd use a "choose" action instead of a condition.

  • Revisiting state transitions

    WJCarpenter07/14/2023 at 03:13 0 comments

    This is another look at the states and event transitions that I described in an earlier project log. I'm now ready to implement it in a Home Assistant automation definition. It will actually be two automations, but the automation for the dryer is just a clone of the automation for the washer with the names of devices and entities adjusted.

    As a reminder, there are 4 event inputs for each machine: door open, door closed, power on, and power off. Those are events, not states, so "power on" means "power is turned on". I'm configuring the automations as "queued", so the events can never happen simultaneously, no matter how it happens in real life. There are some impossible or nonsense transitions, and there are also some fairly simple considerations.

    • If the power turns on, the machine must be transitioned to the RUNNING state, no matter what we thought its previous state was.
    • If the power turns off, the machine must be transitioned to the STOPPED state, no matter what we thought its previous state was.
    • If the door becomes open, the machine cannot be in the RUNNING state.
    • The door cannot become open without being closed, and vice versa.
    Current
    state
    Door
    open
    Door
    closed
    Power
    on
    Power
    off
    UnloadedLoading-RunningStopped
    Loading-LoadedRunningStopped
    LoadedLoading-RunningStopped
    Running--RunningStopped
    StoppedUnloading-RunningStopped
    Unloading-UnloadedRunningStopped
    Unknown--RunningStopped

    This leads to fairly simple logic. Power events don't need to consider anything else. Door events only need to consider the current state to decide what the next state should be. There are some cases where things are not perfect, but we can tolerate them. For example, when in the STOPPED state and the door is opened, we transition to the unloading state. It will be very common for the machine to be re-loaded without the door being closed in the meantime. When the door is closed, we will think we are in the UNLOADED state when we are actually in the LOADED state. In the normal case, the power will soon turn on and transition us to the RUNNING state. We don't care that we missed the LOADING and LOADED states, except that we don't necessarily have the opportunity to alert if the machine is loaded but not powered on for some amount of time. That's not much different from someone opening the door, not loading the machine, and then closing the door. We would need some additional sensors of some kind to distinguish those cases, and it's not worth it (to me).

    For the automation, I am using just one automation per machine, and the trigger can be any of the four event types. Even though it might seem simpler to have separate automations for each of the possible triggers, or at least for the possible trigger types, I'm combining them all into a single automation so that I can configure it as "queued" and not have to worry about concurrency edge cases.

    I mentioned in an earlier project log that I am using templated sensors for the door contact sensors. That avoids having to deal with unavailable or unknown values. I'm using the templated forms for the automation triggers. Incidentally, I had to be careful to use entity state changes as the triggers. I originally used the contact sensor device, but that triggers on any attribute change. The devices phone home every few hours even if the door does not open or close. I think that's to report their battery level as an attribute.

    For the power triggers, I used a numeric value comparison. Power above a threshold value for a certain period of time is considered on. Power below that threshold for a certain period of time is considered off. At the moment, I'm using 8 watts for the threshold and 1.5 minutes for the time period. I may have to tweak those things later, depending on what I observe in real life.

    The action in the automation is just changing the state of the applicable dropdown helper entity. It consists of a nested choose action that implements the transition table. The default action is to do nothing, so we only...

    Read more »

  • Power consumption graphs

    WJCarpenter07/13/2023 at 16:32 0 comments

    These graphs show the power (in watts) and current (in amps) drawn by the washer (blue) and dryer (purple) that happened to be both running at roughly the same time. The bars at the top of the screenshot show the state of the door sensors for the machines. Click the image to get a larger view of it.

    Observations....

    • When the washer is off, it is completely off with zero energy consumption. That can't be literally true because it has to be able to sense the soft switch to be able to be turned on. It's zero within the precision of the smart socket measuring it.
    • The same is true of the dryer, with one important extra factor. There is a light in the dryer that comes on when the door is open. If you look closely, you can see a small blip in the top graph for the dryer between 9:36 and 9:40. It's directly under the yellow "open" patch for the door sensor. I opened the door for a little while specifically to see what the energy consumption would be. It's 3.7 watts. The current graph does not show a similar draw. It stays at zero, which I think is probably due to a difference in the measuring sensors in the smart outlet.
    • The very end of the cycle for the washer shows a small power draw (4-6 watts) that lasts about a minute. I don't know what the machine is thinking about during that time, but it always does it before it finally switches completely off.
    • There are other low spots in the washer's cycle when it is switching between phases. The durations are all exactly 30 seconds, but that is probably the granularity of how often the smart socket is reporting things. The lowest of those dips is 11.9 watts for this particular cycle. There are other types of cycles that could be different in that respect.
    • Since I want to use the power consumption to know when the machines start or stop running, I think I can reliably do that for the washer by saying "zero watts for at least some time period", which is an easy thing to do in Home Assistant triggers. For the dryer, I can't use zero watts because of the light. I will have to use a small value of perhaps 5 watts. But in both cases, I can reasonably assume that the machines are not running if their door is open. I'll have to tinker with this a little, but I'm pretty sure I will be able to work out triggers and conditions that can be the same for both machines. (Using the same triggers and conditions is not a hard requirement for me. It's just for simplicity.)

  • Somebody did the homework

    WJCarpenter07/05/2023 at 01:06 0 comments

    This person already worked out quite a bit of the mechanics of integrating all this with Home Assistant, so I will probably copy a lot of their work. I haven't yet analyze all the machine state stuff yet, so I don't know if I agree or disagree with what they decided.

    (some time passes)

    I'm definitely going to take advantage of that person's mechanics in  how they set this up. My notion about the machine states is a little different. It's more detailed, but it will probably turn out that I don't need most of the states, but I'm going to start with all of them and see how it goes. I also expect to organize my automations differently, but that is mostly a style matter rather than a fundamental difference.

    First up, the dropdown helper. I had to consult Home Assistant documentation to find it in the UI because it has moved since that write-up. It's now at Settings > Devices & Services > Helpers. I originally planned to use the same states for both the washer and dryer, but it seems the helper is really an individual entity rather than something that can be associated with some other kind of object.

    Here's the helper for the washer. I have an essentially identical one for the dryer. There is probably a smarter way to do this that does allow sharing, but the duplication is OK for this simple project.

    In addition to the states mentioned in a previous project log, I also defined an "Unknown" state. That will be used when Home Assistant starts up since any number of events may have been missed. It will also incidentally be used the first time I start all these things going.

    The UI doesn't have the usual option to view or edit the YAML. I found it afterwards in file config/.storage/input_select, which is referenced from config/.storage/core.entity_registry.

    {
      "version": 1,
      "minor_version": 2,
      "key": "input_select",
      "data": {
        "items": [
          {
            "id": "washer_state",
            "name": "Washer state",
            "icon": "mdi:washing-machine",
            "options": [
              "Unloaded",
              "Loading",
              "Loaded",
              "Running",
              "Stopped",
              "Unloading",
              "Unknown"
            ]
          },
          {
            "id": "dryer_state",
            "name": "Dryer state",
            "icon": "mdi:tumble-dryer",
            "options": [
              "Unloaded",
              "Loading",
              "Loaded",
              "Running",
              "Stopped",
              "Unloading",
              "Unknown"
            ]
          }
        ]
      }
    }

  • The state of machine state machines

    WJCarpenter07/02/2023 at 01:24 0 comments

    Both the washer and the dryer have these possible states:

    1. UNLOADED (ub). The machine is not running and has no laundry in it.
    2. LOADING (la). Laundry is being loaded into the machine.
    3. LOADED (lb). There is laundry in the machine, but it is not yet running.
    4. RUNNING (r). The machine is running.
    5. STOPPED (s). The machine is stopped at the end of its cycle.
    6. UNLOADING (ua). Laundry is being unloaded from the machine.

    We'll only have two sensors for each machine: the power monitor, as a simple binary sensor (on or off) and the door sensor (open or closed). 

    With 6 states for each machine, there are 36 possible state combinations. With two sensors per machine there are 8 possible sensor inputs and 1 or more "some time has passed" inputs. We make the simplifying assumption that sensor inputs never occur at exactly the same time, so they can be treated independently. The states for separate machines can obviously overlap.

    For a while, I thought about making a big state transition matrix, with 36 states and 8 or 9 types of event transitions. That would be a very sparse matrix, with most of the transitions being either impossible or no-ops. Instead, let's look at the states for an individual machine. 

    • The machine starts in a default state of UNLOADED.
    • When the door in open for more than some period of time, we assume it's LOADING.
    • When the door closes, the we assume the machine is LOADED.
    • When the power monitor shows non-zero, the machine is RUNNING.
    • When the power monitor returns to zero, the machine is STOPPED.
    • When the door in open for more than some period of time, we assume it's UNLOADING.
    • When the door closes, the machine is UNLOADED.

    The above stages are the normal progression of things for each machine. Only a couple of combinations are interesting for human attention:

    1. Dryer is STOPPED, washer is in any state.
    2. Washer is STOPPED, dryer is UNLOADED (or STOPPED, from the previous rule). We also include UNLOADING in case someone left the door open after unloading.

    Assuming we have some kind of notification out for attention, we can automatically cancel the notification for any of these events happening:

    • Someone explicitly acknowledges the notification. 
    • Either door opens or closes.
    • Either machine moves to the RUNNING state.

    For the doors and power monitor, we can considering having separate notifications and cancellations . We can also consider having some kind of reminder loop if someone acknowledges the notification but we don't see the expected attention to the laundry.

    There are a lot of things that could go wrong with this cold algorithmic plan. For example, someone could open a machine door and not actually unload the machine. Best effort here is probably good enough for now.

  • Blink service

    WJCarpenter07/02/2023 at 00:06 0 comments

    I haven't decided yet what kind of notifications I'm going to do when the laundry needs attention, but among the possibilities is blinking the LED on or both of the Sonoff S31 switches.

    Doing the classic perpetual LED blink in ESPHome is simplicity itself. You just set up actions on the state changes to delay and toggle back and forth. This example uses explicit turning on and off, but it could also have use toggle actions.

    switch:
      - platform: gpio
        id: me
        pin:
          number: 15
        on_turn_on:
        - delay: 100ms
        - switch.turn_off: me
        on_turn_off:
        - delay: 1000ms
        - switch.turn_on: me
    

     For this project, I want to be able to turn blinking on and off as a service call from Home Assistant. Further, I want Home Assistant to decide what the on and off periods are so it can do slow blinking, fast blinking, or some kind of asymmetric on/off cadence. So, the start_blinking service takes two parameters for those durations. The stop_blinking service has one additional job, besides just stopping the blinking. It has to restore the LED to the state it was in (on or off) before the blinking was started. Of course, that status was recorded when the start_blinking service was called. If start_blinking is called multiple times without an intervening stop_blinking call, the new cadence is set but the pre-blinking status of the LED is unaffected.

    Here's the code (in two files) as it stands today. It has several other small changes since the previous project log.

    substitutions:
      name: s31yellow
      friendly_name: Unknown Yellow Spirit
      alias_icon: mdi:cloud-question
      power_on_boot: 'true'
      use_address: ${name}.carpenter.org
    
    <<: !include { file: s31.yaml.inc}
    

    and

    # This is an adaptation of the ESPHome configuration for Sonoff S31
    # smart plug described here: https://www.esphome-devices.com/devices/Sonoff-S31/
    
    # Pin     Function
    # GPIO0   Push Button (HIGH = off, LOW = on)
    # GPIO12  Relay and its status LED
    # GPIO13  Blue LED (HIGH = off, LOW = on)
    # GPIO1   RX pin (for external sensors)
    # GPIO3   TX pin (for external sensors)
    
    # This file is intended to be included from another file that defines
    # some configuration choices via substitution variables:
    #   name:          the name of this device, appears various places
    #   alias_icon:    front-end icon for the alias sensor
    #   power_on_boot: boolean, whether to turn on the relay at boot time
    
    esphome:
      name: ${name}
      friendly_name: ${friendly_name}
      platform: ESP8266
      board: esp01_1m
      on_boot:
        then:
          - text_sensor.template.publish:
              id: i_alias
              state: ${name}
          - if:
              condition:
                lambda: "return ${power_on_boot};"
              then:
                - switch.turn_on: relay
              else:
                - switch.turn_off: relay
          - switch.turn_off: i_blue_status_led
    
    globals:
      - id: IS_BLINKING
        type: "bool"
        initial_value: "false"
      - id: BLINK_OFF_PERIOD
        type: int
      - id: BLINK_ON_PERIOD
        type: int
      - id: PREBLINK_STATE
        type: "bool"
    
    wifi:
      ssid: !secret wifi_ssid
      password: !secret wifi_password
      use_address: ${use_address}
    
    logger:
      level: DEBUG
      baud_rate: 0 # (UART logging interferes with cse7766)
    
    ota:
      password: !secret ota_password
    
    api:
      services:
    
        - service: start_blinking
          variables: {on_period: int, off_period: int}
          then:
            - globals.set:
                id: BLINK_ON_PERIOD
                value: !lambda "return on_period;"
            - globals.set:
                id: BLINK_OFF_PERIOD
                value: !lambda "return off_period;"
            - if:
                condition:
                  lambda: "return !id(IS_BLINKING);"
                then:
                  - globals.set:
                      id: PREBLINK_STATE
                      value: !lambda "return id(i_blue_status_led).state;"
                  - globals.set:
                      id: IS_BLINKING
                      value: !lambda "return true;"
                  - switch.toggle: i_blue_status_led
    
        - service: stop_blinking
          then:
            - if:
                condition:
                  lambda: "return id(IS_BLINKING);"
                then:
                  - globals.set:
                      id: IS_BLINKING
                      value: !lambda "return false;"
                  # The blinking doesn't actually stop until one or the other of these delays expires
                  # so wait it out (plus a little slop) before setting the LED on or off.
                  - delay: !lambda "return 20 + max(id(BLINK_ON_PERIOD), id(BLINK_OFF_PERIOD));"
                  - if:
                      condition:
                        lambda: "return id(PREBLINK_STATE);"
                      then:
     -...
    Read more »

  • Machine door sensors

    WJCarpenter07/01/2023 at 18:08 0 comments

    To keep track of when the laundry is moved around by someone, I put contact sensors on the door of the washer and dryer. I know the smart money thinks you should use Zigbee sensors for battery-powered devices like this, but I have not had a good experience with Zigbee sensors in the past. Maybe I just need to work harder at it and understand more things. For now, I am using these battery-powered wifi devices.

    These are widely available with no-name branding for US$10-12 each. They are powered by 2 AAA batteries. Certainly there is a few seconds lag in getting updates from them, but that's not important in this application. Places that sell them claim half a year battery life under some unknown conditions. We shall see about that.

    I integrated these with Home Assistant using localtuya. I've already got several other devices integrated that way, so I'm comfortable in getting it to work. The only tricky part is more of an annoyance than a trick. To conserve power, the devices are only connected to the wifi for a few seconds after an open or close event. So, to set them up via the localtuya integration, you have to keep triggering them by moving the magnet near and away. It's not too bad once you know what's going on and get used to what you need to do. The localtuya integration does not make the battery level available, but the cloud-based Tuya integration does. I disabled the entity for the open/close sensor in the Tuya integration but left the battery level sensor available.

    The other quirk that you have to deal with in this kind of sensor is that it shows "unavailable" most of the time in Home Assistant. Of course, that's also due to dropping off of wifi as soon as it can. The answer to that is a somewhat advanced feature of Home Assistant called templating. It's advanced in the sense that you have to directly edit templates in files; there's no simple GUI for it. The idea of templates for these sensors is to define a new sensor that remembers the value of the last real sensor. Home Assistant's templating mechanism will let you trigger the update of the templated sensor based on a state change of the real sensor. So, when the real washer door sensor signals "open", it triggers an update of the templated sensor to that value. Likewise when it signals "closed". For this purpose, the template explicitly ignore state changes to "unavailable".

    Here's what the resulting triggered templates look like:

      - trigger:
          - platform: state
            entity_id:
              - binary_sensor.lt_contact_sensor_washer
        binary_sensor:
          - name: Contact Sensor Washer Triggered
            device_class: door
            state: >-
              {% if states('binary_sensor.lt_contact_sensor_washer') != 'unavailable' %}
                {{ states('binary_sensor.lt_contact_sensor_washer') }}
              {% else %}
                {{ states('binary_sensor.contact_sensor_washer_triggered') }}
              {% endif %}
    
      - trigger:
          - platform: state
            entity_id:
              - binary_sensor.lt_contact_sensor_dryer
        binary_sensor:
          - name: Contact Sensor Dryer Triggered
            device_class: door
            state: >-
              {% if states('binary_sensor.lt_contact_sensor_dryer') != 'unavailable' %}
                {{ states('binary_sensor.lt_contact_sensor_dryer') }}
              {% else %}
                {{ states('binary_sensor.contact_sensor_dryer_triggered') }}
              {% endif %}
    

     (I first learned about this template sensor trick from another Home Assistant user for a different sensor.)

View all 14 project logs

Enjoy this project?

Share

Discussions

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates