Making Our Dumb Dishwasher Smarter

Our dishwasher is about as simple as it's possible for an appliance to be: it's got a wheel to select a program and a push button that you hit to start the cycle.

I'm not actually sure how many years it's been in use (it came with the house), but it's still going strong and does what's needed of it.

The one small gripe that I've ever really had with it is the inability to turn it on remotely: it doesn't matter how cheap electricity is at 3am, I'm am not getting up just to turn the dishwasher on.

Nowadays, there are a ton of "smart" dishwashers available on the market, each offering some part of a huge range of features including the ability to be switched on and off remotely.

The one thing that they all have in common, though, is increasing your exposure to the manufacturer:

  • If the manufacturer decides to discontinue their IoT platform (or applies non-backwards compatible updates), those coveted smart features will stop working.
  • If the manufacturer changes their terms and conditions to something you don't find acceptable, your smart features will stop working.
  • If the manufacturer starts charging for access to their platform, you'll need to pay or your smart features will stop working.
  • The appliance (and the network it's connected to) may well also be exposed to the risk of compromise (as SmartThinkQ owners found out)

Those feel like some fairly serious trade-offs considering that, amongst those offered by dishwashers, the only "smart" feature that I've really felt any need for is remote-start.

This post talks about implementing the ability to remote-start our existing dumb dishwasher (though it'll also work for any appliance with physical push buttons).


The Options

If you look around on the net, there are a number of options that people seem to consider and/or pursue for this:

  1. Use a smart-socket to turn the dishwasher on/off
  2. Install a smart implant (something like the Fibaro)

Option 1 is only viable if your appliance has a latching power/start button (i.e. one that clicks into place), rather than the simple push button that seems to be sported by most modern appliances.

Option 2 would mean opening the dishwasher up to solder something to the control board - not the most appetising option for most (plus, in line with my whinging here, I've probably not got around to buying any more solder).

Recently, though, I was bemoaning the lack of options in a thread on social media and someone (I'd love to credit you but couldn't re-find the message) asked whether a Switchbot might do what's needed (seriously, talking in most of the threads that @neil starts almost always leads to something useful).

In my case, the Switchbot (specifically) wouldn't have been a particularly good choice:

  • The fascia of the dishwasher is slightly curved, Switchbot is about 4cm long so probably wouldn't have stuck very well
  • It uses Bluetooth Low Energy (BLE) for connectivity, so I'd have needed to get an additional hub

However, the idea was a particularly good one and Switchbot is just one (reasonably) well-known brand of fingerbot. With a quick bit of searching, I managed to find and order a Zigbee fingerbot that's about 1cm smaller than the Switchbot (and, a reasonable chunk cheaper to boot).


Energy Monitoring

As smart technology goes, most fingerbots are quite dumb: you send them a signal and they poke. They've got no understanding of the state of the thing they've poked and simply poke on demand.

Technically, you could try and track state at the thing sending the signals (I know I sent you a signal, so now it's on, another, it's off), but this would be extremely prone to going of sync.

It makes far more sense to use the appliance's power consumption as an indicator of state: if it's drawing lots of energy it must have been switched on (whether via the bot, or because a meatbag jabbed the button with a finger).

Having previously connected a smart-plug in order to see how much more efficient Eco mode is, I was already mostly set up for this.


A Note on Plug Choice

If you are looking for a smart plug to use, I've tended to opt for either a TP-Link Kasa KP115 or a TP-Link Tapo P110.

The Tapo's are (slightly) cheaper, but having had some for a while, I would no longer recommend choosing them (and, in fact, plan on phasing them out myself).

Unfortunately, they can be quite flakey: if they experience issues connecting to Wifi (or NTP), they quite quickly give up trying and so will not reconnect when connectivity becomes reliable again.

They also seem to be quite susceptible to interference, increasing the likelihood that they'll decide your network is unreachable and sulk off into a disconnected state until someone turns the wall socket off and on again.

IT Crowd Meme: Have you tried turning it off and on again?

The Kasa's are connected to the same network and continue to be rock solid (though, for both Tapo and Kasa you do also have to watch out for firmware updates breaking local access).

Crawling around reaching behind appliances to unplug and replug because a smart-socket lacks an effective retry mechanism is not quite the automation utopia that I've been aspiring to. So, although I'm in no huge hurry, over time the Tapo's are going to get replaced by either Kasa's or Zigbee sockets.

I use a python script to poll the plugs and write readings into InfluxDB. Energy consumption information can also be read directly from Kasa plugs by HomeAssistant.


Installing the Finger-Bot

Installing the finger bot was simple:

  • I removed the tab on the battery
  • Told HomeAssistant's ZHA integration to find the new Zigbee device
  • Set a name in Homeassistant
  • Used a double-sided pad to stick the bot in place

A quick toggle of the switch in HomeAssistant and the fingerbot sprung into action:

A gif of the finger-bot in operation. It is a white cube stuck to the front of the dishwasher next to the dishwashers start button. The bot has a little protruding arm which slides forward to press the button

For IRL users, there's also a button on the front of the bot that can be used to trigger it (although there's also enough room to just push the dishwasher start button manually).

As you can see, the bot does stick out a little way, so this may not be workable if your dishwasher's position means that people will often be brushing past (ours is in a corner, so this isn't a concern for us).


Reading Dishwasher Usage

The bot is simple to trigger - it just needs the relevant switch to be toggled within HomeAssistant, something that's easily achievable from within automations and dashboards.

However, this leaves quite a bit of scope for mistakes: The bot responds to a simple toggle and has no concept of on or off, so if we accidentally toggle it whilst the dishwasher is running, the bot will end up pausing the cycle.

In order to guard against this (because noone wants to start their day by finding the dishwasher paused and full of cold dirty water) I decided to have HomeAssistant first check how much power the dishwasher is consuming (hence the smart socket).

The only problem with this was that this consumption information didn't exist in HomeAssistant.

However, that was easily addressed because HomeAssistant supports creating sensors based on the result of InfluxDB queries (I actually already had a few of these sensors defined, pulling in information about current solar panel output):

influxdb:
   api_version: 1
   host: 192.168.3.84
   port: 8086
   precision: s
sensor:
    - platform: influxdb
      host: 192.168.3.84
      port: 8086
      api_version: 1
      queries:
        - name: "dishwasherConsumption"
          unit_of_measurement: "W"
          database: "Systemstats"
          field: "consumption"
          measurement: "power_watts"
          where: '"host"=''dishwasher'' and time > now() - 5m GROUP BY time(1m) fill(0)'
          group_function: "max"

This block

  • creates a sensor called dishwasherConsumption
  • builds the InfluxQL query SELECT max(consumption) FROM Systemstats..power_watts WHERE "host"='dishwasher' and time > now() - 5m GROUP BY time(1m) fill(0)
  • Populates the state of the sensor using the result

By default, HomeAssistant runs the query once a minute and once the query completes, the sensor's value will be the maximum consumption reading.


Initial Implementation

With the dishwasher's usage now available to us, it was just a case of wrapping the switch toggle in a script:

alias: Start Dishwasher
sequence:
  - if:
      - condition: numeric_state
        entity_id: sensor.dishwasherconsumption
        above: 30
    then:
      - stop: Dishwasher already on
    enabled: true
  - service: switch.toggle
    data: {}
    target:
      device_id: fd36b3a03ec120023d80d3f4746b65b2
mode: single

The script first checks whether the dishwasher is consuming more than 30 watts (a threshold chosen based on my previous experimentation ), and, if it is, refuses to proceed.

Note, though that this script does rely a little on an implementation detail of HomeAssistant: If, for some reason, the InfluxQL query fails to return data, the sensor value will be set to Unknown.

In the script's logical check, HomeAssistant treats Unknown as being False and so concludes that the condition does not match. That's exactly what we want, but does mean that if HomeAssistant ever moved to throwing an error on Unknown (or assigning a low numerical value to it), the script would break.


Debouncing

The initial script implementation works quite well to ensure that the fingerbot cannot be re-activated midway through the dishwasher's cycle.

However, it only offers protection once the InfluxQL based sensor's state has updated, which happens once a minute. This means that, if the script were re-triggered within 60 seconds we'd still accidentally pause the dishwasher.

That could be addressed by making the InfluxQL sensor update more regularly (by adding a scan_interval attribute), but having HomeAssistant query InfluxDB every second "just in case" felt like quite an inefficient option.

Instead, I thought I'd add a templated condition to the script to check whether it had been run in the last two minutes:

{{ ( as_timestamp(now()) - as_timestamp(state_attr('script.start_dishwasher','last_triggered')) |int(0) ) < 120 }}

However, this ran into something unexpected.

The script's last_triggered attribute is actually updated when the script first starts, rather than after actions have been fired, so it's not possible to use the attribute to check when the currently running script last ran.

In an automation the previous last_triggered value is preserved whilst applying conditions (and then updated once the actions start), but a script doesn't have a condition section (the conditionals that you can add are all added to the sequence section and so are classed as actions).

The workaround, for scripts, is to set the mode to single (so that only a single instance of the script can ever run) and then add a delay action to make the script pause for the desired interval:

alias: My script
mode: single
sequence:
  - service: do.something
    foo: bar
  # Pause for 2 minutes to prevent re-runs of the script
  - delay: '00:02:00'

I.... I don't like this.

I'm sure it's fine, but preventing re-runs by effectively doing a time.sleep() in a script just feels dirty (what if I wanted to prevent re-runs for days? Or have it survive reboots?).

Instead, I switched to using an automation so that I could take advantage of the condition block:

alias: Trigger Dishwasher
description: ""
trigger:
  - platform: event
    event_type: do_the_dishes
condition:
  - condition: and
    conditions:
      - condition: or
        conditions:
          - condition: numeric_state
            entity_id: sensor.dishwasherconsumption
            below: 30
          - condition: state
            entity_id: sensor.dishwasherconsumption
            state: unknown
      - condition: template
        value_template: >-
          {{ ( as_timestamp(now()) -
          as_timestamp(state_attr('automation.trigger_dishwasher','last_triggered'))
          |int(0) ) > 120 }}
action:
  - service: switch.toggle
    data: {}
    target:
      device_id: fd36b3a03ec120023d80d3f4746b65b2
mode: single

The event name (do_the_dishes) is completely made up, I only added it so that there would be a trigger type defined: My intention had been to run this automation by calling automation.trigger within my other scheduling automations.

Except, it turned out that automation.trigger skips the condition section, so it quickly became clear that that was something of a non-starter.

Instead, I updated my scheduling automation so that it would fire the custom event (do_the_dishes) that the main automation was listening for:

alias: Start Dishwasher at time
description: ""
trigger:
  - platform: time
    at: "02:00:00"
condition: []
action:
  - event: do_the_dishes
    event_data:
      msg: scrub-a-dub-dub
  - service: notify.mobile_app_fp4
    data:
      message: Dishwasher Started
  # Turn yourself off to prevent retriggering tomorrow
  - service: automation.turn_off
    data:
      stop_actions: false
    enabled: true
    target:
      entity_id: automation.start_dishwasher
mode: single

When the event fires, Trigger Dishwasher runs and (assuming that all conditions are met), the fingerbot does it's pokey little dance.


First Scheduled Run

Knowing that I might well wake up the next morning to find the dishes hadn't been done, I loaded the machine and enabled the automation.

Sure enough, though, the bot did its job:

Graph of the dishwashers power consumption - it kicked in a little after 2am as scheduled

As a result, the next morning, I came down to exactly the sight that I'd been hoping for:

Photo of the front of the dishwasher, the End light is illuminated because the cycle ran and completed


Future Improvements

The trigger automation & bot seem to work quite reliably - I've also since used them to help start the dishwasher at the start of an Octopus Powerup.

However, my prior experience with battery-backed Zigbee devices has been a little mixed: I have previously found that they sometimes go unreachable for periods of time.

So, at some point, I might look at having HomeAssistant check whether consumption went up (say) 5 minutes after the automation ran so that it can (potentially) be retried.


Conclusion

Our years-old dishwasher now has all of the features that I'd really want from a smart replacement.

Not that it was ever a real consideration, but replacing the dishwasher with a new "smart" appliance would have generated unnecessary waste (by ditching a working appliance) as well as exposing us to the whims and competence of the new appliance's manufacturer.

And that's before we mention the cost: we'd be spending hundreds of pounds on a new appliance to get functionality that can be achieved with a £20 fingerbot.

It took about 5 minutes from opening the box to get the basic setup (i.e. a bot that pokes on demand). Rolling in the additional checks (and associated experimentation) probably brings it up to an hour or two.

The average household, of course, probably isn't going to be running HomeAssistant or have an energy monitoring setup. However, the basic functionality can also be achieved using a fingerbot which talks to a mobile app (and Switchbot is a fantastic example of that).

The next step for me, is probably to see whether we can do the same with our washing machine - it has capacitive rather than press buttons but, in theory, a little bit of copper tape on the end of the bots finger should allow it to work (the real concern being that the button's actually fairly unreliable with a human finger, steady march of progress eh?).