Run a Dishwasher on Solar Surplus and Off-Peak Power

Run a Dishwasher on Solar Surplus and Off-Peak Power

My 15-year-old dishwasher now waits for sun or cheap night power – no bus commands. Home Assistant pauses it with a Shelly and resumes later.

Advertising: the video contains a paid partnership with remind.me (a German energy plan switching service).

This summer I made my more than 15-year-old Siemens dishwasher smart: a small ESP32 board now sits on its internal data bus and tells Home Assistant which program is running, how long it will take and whether the door is open. But at the end of the last article I also said: I’m only listening in. I can’t start or control the machine that way.

That hasn’t changed. Still, Home Assistant now decides when it runs: on sun from the roof, or at night when electricity is cheaper for me. All it takes is a Shelly and three automations.

YouTube Video
To load the video, please click the image. Please note that by doing so, data will be transmitted to YouTube.

Why I don’t send commands to the bus

The bus inside the dishwasher is basically one long cable that all modules hang on: power module, control panel with display, sensors. My board only listens – it never sends, so it doesn’t disturb anyone.

Talking would be different. Anyone may send at any time; if two talk at once, the messages collide. The machine handles that with acknowledgements and random back-off times. To join in, you’d have to follow those rules perfectly and know the right commands. In the reference project the relevant commands are listed under the heading “Pure guesswork”. With a device full of water and a two-kilowatt heater, I’d rather not experiment.

So I don’t talk. I cut the power to the whole group and turn it back on later.

The Shelly is the controller

There already is a Shelly in front of the machine – now a Shelly Plus 1PM rated 16 A (it used to be a Shelly Plug S, more on that below). The board on the bus only supplies the knowledge of when the right moment is; switching happens purely via power.

The tip from the community – and a failed attempt

Under my last video, imho4556 wrote (paraphrased): With our BSH machines, you just use a deliberate power cut. Once started, the machine continues where the power failed. Thanks for that!

My first idea was to go even simpler: cut the power just before the delayed start runs out, when no program is running yet. Result: after switching back on, the display didn’t show the remaining time but 30 minutes – every time (tested with 7 and with 5 minutes left). So the machine doesn’t remember the delay, at least not shortly before it ends.

A running program, however, it remembers reliably. So exactly as the comment said: let the delay run out, the program starts, and only one minute later the Shelly switches off. During that minute the machine draws only about 20 W; nothing is heated yet. After pauses from two up to twelve hours it always resumed immediately, with the remaining time continuing from the first minute.

Important: I tested this on my Siemens SX66T096EU. Other Bosch/Siemens machines probably behave similarly – but test yours before you enable the automation (see below).

The delayed start becomes a button

I didn’t want an extra dashboard or button. Operation stays at the machine – via the delayed start:

The delayed start becomes a button

  • 1 h delay → start as soon as there’s enough solar surplus (4 pm at the latest)
  • 2 h delay, set in the evening between 6 pm and midnight → wash from midnight
  • any other delay → runs normally

One or two hours are quick to set, and I practically never use them as a real delay.

How Home Assistant thinks

From Home Assistant’s point of view the machine is always in exactly one state:

States with a 1 h delay

  1. Normal: power on, nothing scheduled.
  2. Waiting: 1 h delay set; a helper (input_boolean.spulmaschine_wartet_auf_pv) remembers “wait for the sun”.
  3. Start-up: delay over, program started. After 60 s Home Assistant checks the surplus.
  4. Paused: not enough solar → Shelly off.
  5. Running: back on as soon as the surplus stays above 2000 W for 5 minutes, at 4 pm at the latest (clean dishes by evening) – or when someone switches the Shelly on by hand.

What you need to create first

EntityTypePurpose
input_boolean.spulmaschine_wartet_auf_pvHelper: toggleflag “waiting for sun”
input_boolean.spulmaschine_wartet_auf_nachtstromHelper: toggleflag “waiting for midnight”
input_number.spulmaschine_pv_schwelleHelper: numberthreshold in W, 2000 for me
input_datetime.spulmaschine_spatester_startHelper: date/time (time only)latest start, 16:00 for me
sensor.pv_uberschussTemplate sensorsolar production minus house load in W
sensor.geschirrspuler_zeitvorwahlfrom the board (ESPHome)delay in hours
binary_sensor.geschirrspuler_statusfrom the board (ESPHome)program running
switch.shelly_dishwasher_switch_0Shelly Plus 1PMthe machine’s power

notify.std_information is a notification group in my setup – replace it with your own notify target. The notification texts are German; translate them as you like.

Automation 1: postpone a 1 h delay to solar

It reacts to three events: the delay jumps to exactly one hour (flag on), the program has been running for 60 s (keep running or pause), or a human switches the Shelly on (cancel the postponement).

Two traps I fell into:

  • The manual switch: without the third branch, I once switched the paused machine on by hand – a minute later it was off again, because the automation took it for a new program start. That’s why the branch checks context.user_id: only a human switch cancels.
  • Counting down: with a 3 h delay, the machine eventually counts through “1 h” as well. So only a jump upwards (new value larger than the old one) counts, not counting down from 61 to 60 minutes.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
alias: 'Haushalt: Spülmaschine – 1h-Vorwahl auf PV verschieben'
triggers:
- trigger: state
  entity_id: sensor.geschirrspuler_zeitvorwahl
- trigger: state
  entity_id: binary_sensor.geschirrspuler_status
  to: 'on'
  for:
    seconds: 60
- trigger: state
  entity_id: switch.shelly_dishwasher_switch_0
  from: 'off'
  to: 'on'
conditions: []
actions:
- choose:
  - alias: 1h-Vorwahl gesetzt -> scharf schalten
    conditions:
    - condition: trigger
    - condition: numeric_state
      entity_id: sensor.geschirrspuler_zeitvorwahl
      above: 0.95
      below: 1.001
    - condition: template
      value_template: '{{ trigger.from_state is none or not is_number(trigger.from_state.state) or trigger.from_state.state|float < trigger.to_state.state|float }}'
    sequence:
    - action: input_boolean.turn_on
      target:
        entity_id: input_boolean.spulmaschine_wartet_auf_pv
    - action: notify.std_information
      data:
        title: Geschirrspüler
        message: Start wird auf PV-Überschuss verschoben.
        data:
          tag: geschirrspueler_notify
  - alias: Vorwahl auf >1h geändert oder Maschine ausgeschaltet -> entschärfen
    conditions:
    - condition: trigger
    - condition: state
      entity_id: input_boolean.spulmaschine_wartet_auf_pv
      state: 'on'
    - condition: state
      entity_id: switch.shelly_dishwasher_switch_0
      state: 'on'
    - condition: or
      conditions:
      - condition: numeric_state
        entity_id: sensor.geschirrspuler_zeitvorwahl
        above: 1.001
      - condition: state
        entity_id: sensor.geschirrspuler_zeitvorwahl
        state:
        - unavailable
        - unknown
    sequence:
    - action: input_boolean.turn_off
      target:
        entity_id: input_boolean.spulmaschine_wartet_auf_pv
      data: {}
  - alias: 60 s nach Programmstart -> Shelly aus (außer PV reicht schon)
    conditions:
    - condition: trigger
    - condition: state
      entity_id: input_boolean.spulmaschine_wartet_auf_pv
      state: 'on'
    - condition: state
      entity_id: switch.shelly_dishwasher_switch_0
      state: 'on'
    sequence:
    - if:
      - condition: numeric_state
        entity_id: sensor.pv_uberschuss
        above: input_number.spulmaschine_pv_schwelle
      then:
      - action: input_boolean.turn_off
        target:
          entity_id: input_boolean.spulmaschine_wartet_auf_pv
      - action: notify.std_information
        data:
          title: Geschirrspüler
          message: Programm angelaufen – PV-Überschuss reicht schon ({{ states('sensor.pv_uberschuss') }} W), läuft direkt weiter.
          data:
            tag: geschirrspueler_notify
      else:
      - action: switch.turn_off
        target:
          entity_id: switch.shelly_dishwasher_switch_0
      - action: notify.std_information
        data:
          title: Geschirrspüler
          message: Programm angelaufen und pausiert – weiter ab {{ states('input_number.spulmaschine_pv_schwelle')|int }} W PV-Überschuss, spätestens {{ states('input_datetime.spulmaschine_spatester_start')[:5]
            }} Uhr.
          data:
            tag: geschirrspueler_notify
  - alias: Shelly von Hand eingeschaltet -> entschärfen
    conditions:
    - condition: trigger
    - condition: state
      entity_id: input_boolean.spulmaschine_wartet_auf_pv
      state: 'on'
    - condition: template
      value_template: '{{ trigger.to_state.context.user_id is not none }}'
    sequence:
    - action: input_boolean.turn_off
      target:
        entity_id: input_boolean.spulmaschine_wartet_auf_pv
    - action: notify.std_information
      data:
        title: Geschirrspüler
        message: PV-Verschiebung aufgehoben.
        data:
          tag: geschirrspueler_notify
mode: queued

Automation 2: start on solar surplus

Switches the paused machine back on once the surplus has been above the threshold for 5 minutes, at 4 pm at the latest. The third trigger (“Shelly off for 5 min”) is needed because a threshold trigger only fires at the moment of crossing: if the sun was already there before the pause, there’s no new crossing.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
alias: 'Haushalt: Spülmaschine – bei PV-Überschuss starten'
triggers:
- trigger: numeric_state
  entity_id: sensor.pv_uberschuss
  above: input_number.spulmaschine_pv_schwelle
  for:
    minutes: 5
- trigger: time
  at: input_datetime.spulmaschine_spatester_start
- trigger: state
  entity_id: switch.shelly_dishwasher_switch_0
  to: 'off'
  for:
    minutes: 5
conditions:
- condition: state
  entity_id: input_boolean.spulmaschine_wartet_auf_pv
  state: 'on'
- condition: state
  entity_id: switch.shelly_dishwasher_switch_0
  state: 'off'
- condition: or
  conditions:
  - condition: trigger
  - condition: numeric_state
    entity_id: sensor.pv_uberschuss
    above: input_number.spulmaschine_pv_schwelle
actions:
- action: switch.turn_on
  target:
    entity_id: switch.shelly_dishwasher_switch_0
- action: input_boolean.turn_off
  target:
    entity_id: input_boolean.spulmaschine_wartet_auf_pv
- action: notify.std_information
  data:
    title: Geschirrspüler
    message: '{{ ''Läuft weiter mit PV-Überschuss ('' ~ states(''sensor.pv_uberschuss'') ~ '' W).'' if trigger.id == ''pv'' else ''Läuft weiter – kein ausreichender PV-Überschuss bis '' ~ states(''input_datetime.spulmaschine_spatester_start'')[:5]
      ~ '' Uhr.'' }}'
    data:
      tag: geschirrspueler_notify
mode: single

Why exactly 2000 watts?

The Shelly measures what the machine draws, and every run (Auto 45–65°) looks almost the same: about half an hour of pre-rinse at around 30 W, then 7–8 minutes of main heating at 2.2–2.3 kW, three short heating blocks during the final rinse, and drying at the end with next to no consumption. More than 90 % of the energy goes into heating – so the threshold is based on the heating element. With 2000 W surplus, the heating comes almost entirely from the sun.

Does the sun then have to last two hours? No. The demand is almost entirely at the beginning:

The demand is front-loaded

After 38 minutes half the energy is used, after 81 minutes 90 %. The last 24 minutes of drying need 3 Wh in total, because my machine dries with residual heat and zeolite – a mineral that stores heat while heating and releases it during drying. So the sun only needs to last about an hour.

The proof: October 14

Solar production and dishwasher on October 14

Shortly before seven I set a one-hour delay. Shortly before eight the program starts; a minute later the Shelly switches off – almost nothing comes from the roof at that time. At 10:38 the surplus has been above 2000 W for five minutes, the Shelly switches on and the machine keeps washing. Half an hour later it heats at 2.3 kW while the roof delivers a good 3 kW. A few clouds passed during the final rinse; overall the sun supplied 92 % of the electricity for the cycle that day, and all of the main heating.

One detail: during the pause, Home Assistant shows the machine as “unavailable”. The board is powered by the machine – when the Shelly cuts the power, the board is off too. That’s fine.

Night-time power with time-variable grid fees

In autumn and winter the sun often isn’t enough. I have time-variable grid fees (Germany: § 14a EnWG, module 3):

Time-variable grid fees

Between midnight and 2 am I pay a good 6 ct/kWh less than during the day and about 7.6 ct less than in the evening between 7 and 10 pm. Times and prices are set by each grid operator – check what applies to you. With a dynamic tariff, the same principle works with any fixed time.

That’s what the third automation is for: if I set exactly 2 hours in the evening between 6 pm and midnight, the same principle applies – delay runs out, program starts, Shelly off, back on at midnight. If the program starts after midnight anyway, it isn’t paused.

Automation 3: night-time power

Before, I did that by hand: a 3-hour delay at exactly 9 pm so it starts at midnight. With the 2-hour rule, the time doesn’t matter as long as it’s evening.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
alias: 'Haushalt: Spülmaschine – 2h-Vorwahl auf Mitternacht verschieben'
triggers:
- trigger: state
  entity_id: sensor.geschirrspuler_zeitvorwahl
- trigger: state
  entity_id: binary_sensor.geschirrspuler_status
  to: 'on'
  for:
    seconds: 60
- trigger: time
  at: 00:00:00
- trigger: state
  entity_id: switch.shelly_dishwasher_switch_0
  from: 'off'
  to: 'on'
conditions: []
actions:
- choose:
  - alias: 2h-Vorwahl abends gesetzt -> scharf schalten
    conditions:
    - condition: trigger
    - condition: numeric_state
      entity_id: sensor.geschirrspuler_zeitvorwahl
      above: 1.95
      below: 2.001
    - condition: time
      after: '18:00:00'
      before: '23:59:59'
    - condition: template
      value_template: '{{ trigger.from_state is none or not is_number(trigger.from_state.state) or trigger.from_state.state|float < trigger.to_state.state|float }}'
    sequence:
    - action: input_boolean.turn_on
      target:
        entity_id: input_boolean.spulmaschine_wartet_auf_nachtstrom
    - action: notify.std_information
      data:
        title: Geschirrspüler
        message: Start wird auf 0 Uhr (Nachtstrom) verschoben.
        data:
          tag: geschirrspueler_notify
  - alias: Vorwahl geändert oder Maschine aus -> entschärfen
    conditions:
    - condition: trigger
    - condition: state
      entity_id: input_boolean.spulmaschine_wartet_auf_nachtstrom
      state: 'on'
    - condition: state
      entity_id: switch.shelly_dishwasher_switch_0
      state: 'on'
    - condition: or
      conditions:
      - condition: numeric_state
        entity_id: sensor.geschirrspuler_zeitvorwahl
        above: 2.001
      - condition: state
        entity_id: sensor.geschirrspuler_zeitvorwahl
        state:
        - unavailable
        - unknown
    sequence:
    - action: input_boolean.turn_off
      target:
        entity_id: input_boolean.spulmaschine_wartet_auf_nachtstrom
  - alias: 60 s nach Programmstart -> Shelly aus (außer schon 0–2 Uhr)
    conditions:
    - condition: trigger
    - condition: state
      entity_id: input_boolean.spulmaschine_wartet_auf_nachtstrom
      state: 'on'
    - condition: state
      entity_id: switch.shelly_dishwasher_switch_0
      state: 'on'
    sequence:
    - if:
      - condition: time
        after: 00:00:00
        before: 02:00:00
      then:
      - action: input_boolean.turn_off
        target:
          entity_id: input_boolean.spulmaschine_wartet_auf_nachtstrom
      else:
      - action: switch.turn_off
        target:
          entity_id: switch.shelly_dishwasher_switch_0
      - action: notify.std_information
        data:
          title: Geschirrspüler
          message: Programm angelaufen und pausiert – weiter um 0 Uhr.
          data:
            tag: geschirrspueler_notify
  - alias: Mitternacht -> Shelly ein
    conditions:
    - condition: trigger
    - condition: state
      entity_id: input_boolean.spulmaschine_wartet_auf_nachtstrom
      state: 'on'
    sequence:
    - action: input_boolean.turn_off
      target:
        entity_id: input_boolean.spulmaschine_wartet_auf_nachtstrom
    - action: switch.turn_on
      target:
        entity_id: switch.shelly_dishwasher_switch_0
    - action: notify.std_information
      data:
        title: Geschirrspüler
        message: Läuft weiter mit Nachtstrom.
        data:
          tag: geschirrspueler_notify
  - alias: Shelly von Hand eingeschaltet -> entschärfen
    conditions:
    - condition: trigger
    - condition: state
      entity_id: input_boolean.spulmaschine_wartet_auf_nachtstrom
      state: 'on'
    - condition: template
      value_template: '{{ trigger.to_state.context.user_id is not none }}'
    sequence:
    - action: input_boolean.turn_off
      target:
        entity_id: input_boolean.spulmaschine_wartet_auf_nachtstrom
    - action: notify.std_information
      data:
        title: Geschirrspüler
        message: Nachtstrom-Verschiebung aufgehoben.
        data:
          tag: geschirrspueler_notify
mode: queued

Is it worth it? The honest calculation

Savings per wash cycle

A wash cycle needs about 0.55 kWh for me. With night-time power I save 3–4 ct per run, with solar about 11 ct, because I’d otherwise feed the power into the grid for 7.7 ct. Over a year that’s up to about 30 €, depending on the variant. You won’t get rich – but it costs nothing, and I press the same buttons as before.

Before you build it

  1. Test your machine: start a program, wait a minute, cut the power, switch it back on after five minutes. If it continues, you’re fine. If it starts over or shows an error, this approach won’t work for you.
  2. Does it harm the machine? What about Aquastop? Siemens itself describes in the manual how to interrupt a program by switching off – it stays saved. The Shelly switches exactly when the machine is drawing hardly anything. The Aquastop valve is closed without power, so no water can run in during the pause. I wouldn’t hard-switch it off in the middle of heating.
  3. The Shelly must handle the load – that of the fuse in front of it, not just the machine. The Shelly Plug S is only rated for 10 A; one viewer’s broke on their dishwasher. I use a Shelly Plus 1PM rated 16 A, matching the dishwasher’s 16 A circuit. Mains wiring only by a qualified electrician.
  4. It also works without the board: you can detect the program start via the Shelly’s power metering (here’s how). You lose the delay as a button, though, and everything gets a bit less precise.

Conclusion

“Only listening in” sounded like a dead end in the last video. Today my 15-year-old dishwasher runs on sun from the roof or on night-time power without sending a single command to its bus: the board listens, Home Assistant decides, the Shelly switches. Thanks to imho4556 and cmakas – as you can see, many an idea from the comments ends up in the next project.

Adapt the threshold, times and tariff to your setup. And let me know under the video: do you do something similar?

Built with Hugo
Theme Stack designed by Jimmy
Build: 2026-11-05 11:12 UTC