Round robin activation of Shelly Pro 3 Outlets

Hi there,

I have connected my water heating (3 phases) to a Shelly Pro 3 (no EM). Depending on the PV production I want to activate 1/2 or all 3 phases of this heater. Seems pretty easy you might say, just say above x kW production activate phase 1, above y kW activate phase 2, above y kW activate phase 3, but here comes the complication: I don’t want to have always phase 1 being the first one to fire up, otherwise this part of my heater will quit service because of over utilization…

Any expert tipps for a newbie are higly appreciated!

what do you want? random? track last? ??
do you have code you’ve already written for this? post what you have and easier for us to mod and fit into what you’ve got.

if you’re just asking for general approach, then one way is to create a counter or helper that tracks which was the last one run, or which was the last one started, and increment that so that you’re either starting from where you left off, or starting from a new.

or if you want random
{{ range(1, 3) | random }}
will give you a random number between 1 and 3

Here’s a tested (with surrogate helpers in place of your entities) automation that operates the phases randomly to spread the usage.

You’ll need to either paste into the UI automation editor (without the id line) or include in your YAML under the automation: heading (directly or via an !include: into another file).

You’ll need to substitute in the PV (two occurrences) and Shelly entity IDs, threshold values x (two occurrences), y (two occurrences) and z; and adjust the entity_id filter in s_on and s_off declarations to ensure it picks up your three Shelly entities correctly. Use Developer Tools / Template to check this.

alias: "Water heater manager"
description: >
  Turns individual heater phases on or off in line with PV production.
  Heater switches chosen randomly to balance load.
id: 8faab0ac-e916-4348-8463-c325895dce1b
trigger:
  - platform: state
    entity_id:
      - sensor.PV
      - switch.shelly_1
      - switch.shelly_2
      - switch.shelly_3
action:
  - variables:
      s_on:  "{{ states['switch']|selectattr('entity_id','contains','shelly')|selectattr('state','==','on')|map(attribute='entity_id')|list }}"
      s_off: "{{ states['switch']|selectattr('entity_id','contains','shelly')|selectattr('state','==','off')|map(attribute='entity_id')|list }}"
      want: >
        {% set pv = states('sensor.PV')|float(0) %}
        {{ { pv < x: 0, x <= pv < y: 1, y <= pv < z: 2 }.get(true, 3) }}
  - if:
      - "{{ s_on|count != want }}"
    then:
      - service: switch.turn_{{ 'off' if s_on|count > want else 'on' }}
        target:
          entity_id: "{{ (s_on if s_on|count > want else s_off)|random }}"
mode: queued

The want variable represents the number of phases that should be on. s_on|count is the number that are on.

Every time a new PV reading comes in, the want variable is updated with the number of desired phases. If this is different from the current switch count, the action either turns on a random off switch, or turns off a random on switch.

I’ve included the switch entities in the trigger to force a re-evaluation to catch the situation where the PV jumps more than one threshold, and mode: queued is critical for this to work.

For example:

  • a new much-higher reading comes in and want goes from 0 to 2, it’ll switch on one random switch
  • that switch change will fire a new trigger, want will be re-evaluated as 2 (as the PV reading hasn’t changed by this point), and a second switch will turn on.
  • that switch change will fire another trigger, but this time want is the same as the number of on switches and it’ll not do anything until the next reading comes in.

The id is just a random UUID, but is required if you want to see automation traces for debugging.

My test dashboard:

image

Randomly spreading the load is not the best either.

I would make 3 number helpers and 3 date helpers, one for each phase
When a phase is triggered on the date helper is stored with the now() value.
When a phase is triggered off the date helper is subtracted from the then now() value and the result is stored in the number helper.
Now you have helpers with a number for how much time they have been used each and can always just select the one with the lowest value.

Just remember that values in computer codes are not unlimited. At some point it will reach the limit and start at zero or a negative number. To prevent this you just at intervals take the lowest number of the 3 number helpers and subtract that from all 3 number helpers.

Depends how good your random number generator is. In the long term my implementation above should be equivalent to your proposal provided total runtime is your only concern (as opposed to cycle time or other metrics), and doesn’t require six extra entities to manage.

It would also be possible to amend my implementation so that instead of

{{ s_on|random }}

you pick the least-recently operated switch from that list. Diminishing returns and no practical benefit, though.

You might be right and I might too, but without a proper statistic over how the heater is used it is impossible to tell, so both options stands and Op can choose what he thinks is best for him. :slight_smile:

1 Like

Thanks a lot folks, I see there is plenty of possibilities.

I’ll give @Troon 's proposal a try first. stats will show whether I get equal distribution on the three phases, over time I would expect it.

As a backup I’ll take @WallyR and compare hours run distribution after a couple of weeks.

As said, thanks a lot for your inputs!

Cheers!

1 Like