Help with ATS diesel generator

Can you clarify what is the difference between:
switch_start_stop_generator
and
switch_generator
?

In addition to the above question, does this set of states accurately describe what actions you’d want taken under the varying conditions?

# State Tables
# These will tell us if we are going to be operating the system properly
# by guiding the condition-testing code we write below
# There are only 4 situations in these tables where we need to take action
# I used two separate tables in order to be sure we don't accidentally introduce
# side-effects from the combination of delays between turning the relay off versus turning the generator off.
# The logic could certainly be made more visually efficient, but that introduces risk of side-effects, and
# in this case I think you would prefer it to be provably correct, than pretty.

#### ==== Generator State Table

# UtilOn & GenOff
#   - quiescent state, take no action

# UtilOff & GenOff
#   >>> turn generator on

# UtilOn & GenOn
#   - has util been on > 5 minutes?
#     >>> turn generator off

# UtilOff & GenOn
#   - steady-state, operationally active, take no action

#### ==== Relay State Table

# UtilOn & RelayOff?
#   - quiescent state, take no action

# UtilOff & RelayOff?
#   >>> turn relay on

# UtilOn * RelayOn?
#   - has util been on > 10 seconds?
#     >>> turn relay off

# UtilOff & RelayOn?
#   - steady-state, operationally active, take no action

#### =================

Switch_start_stop_generator = the switch that starts and stops the generator engine.
Switch_generator = the switch that switches the utilities (electrical installation of the house) to the voltage from the generator

I am sending you a diagram to see logically.

I think I get it now, thanks. The name had me a bit confused.
I have logic for this in YAML that compiles, but I have yet to fully desk-check if I got it right.
Will post in a moment.

Oh, my. Two separate relays. (scared me a bit)
I had assumed the interlock was a mechanical one within the ‘retea’ device, and that it had one input signal that caused it to transfer the load from mains to generator, and it would drop back to mains when deselected.

Yes, I can see now why there are two logical ‘switches’ and why they must be exclusive of one another.

I’ll revise my YAML to reflect this.

In the next reply will be some YAML that compiles. Note: That doesn’t mean it’s correct. :wink:
(standard disclaimer applies, no warranty, etc.)

Spend some time walking through the 4 relay-generator-state tests. Be sure that for each condition it’s taking the proper actions. Remember that it’s a loop, and the series of tests will be run every second.

You’ll notice that I don’t do both relay changes in the test block where it starts the generator.
I did that on purpose. It will let the generator start in one pass, then do the load transfer (changing the relays) in the subsequent interval pass. That gives the generator about 1 second (one loop interval) to stabilize before the loop repeats and (this time) transfers the load because the generator is now running.

# There are only 4 situations in which we need to take action.
# I used separate logic blocks in order to be sure we don't accidentally introduce
# a weird combination of delays between turning the relay off versus turning the generator off.
# The logic could certainly be made more visually efficient, but that introduces risk of side-effects.

binary_sensor:

  - platform: gpio
    id: sensor_retea
    pin:
      number: GPIO5
      inverted: true
    name: “Sensor Retea”

switch:

  - platform: gpio
    id: switch_retea
    pin:
      number: GPIO14
      inverted: true
    name: “Switch Retea”
    interlock: switch_generator

  - platform: gpio
    id: switch_generator
    pin:
      number: GPIO4
      inverted: true
    name: “Switch Generator”
    interlock: switch_retea

  - platform: gpio
    id: switch_start_stop_generator
    pin:
      number: GPIO16
      inverted: true
    name: “Switch Start/Stop Generator”

globals:

   - id: sensor_retea_last_change_time
     type: long unsigned
     restore_value: no
     initial_value: '0'

   - id: sensor_retea_last_state
     type: boolean
     restore_value: no
     initial_value: 'false'

interval:
  - interval: 1s
    # There are 6 separate tests here. Their order matters.
    # the first 2 manage the load-transfer relays.
    # the next 2 manage the generator-running conrtol.
    # the next 2 tests are there to remember state and change time of mains power.
    then:

      # UtilOff, Generator running, SwitchGeneratorOff? >>> transfer load to generator
      - if:
          condition:
            and:
              - binary_sensor.is_off: sensor_retea
              - switch.is_on: switch_start_stop_generator
              - switch.is_off: switch_generator
          then:
            - switch.turn_off: switch_retea
            - switch.turn_on: switch_generator

      # UtilOn for > 5 seconds & SwitchGeneratorOn? >>> transter load to mains
        # the 5 second delay is to prevent transferring the load when 'blips' of brief power on the utility line happen while it's being restored
        # you want the utility power to be on and stable for at least a few seconds before beginning to transition load back to it.
      - if:
          condition:
            and:
              - binary_sensor.is_on: sensor_retea
              - switch.is_on: switch_generator
          then:
            - if:
                condition:
                  lambda: |-
                    // this math is immune to millis() rollover because it's all unsigned
                    return ((unsigned long)(millis() - id(sensor_retea_last_change_time)) > 5000); // (5 seconds)
                then:
                  - switch.turn_off: switch_generator
                  - switch.turn_on: switch_retea

      # UtilOff & GenOff? >>> start generator
      - if:
          condition:
            and:
              - binary_sensor.is_off: sensor_retea
              - switch.is_off: switch_start_stop_generator
          then:
            - switch.turn_on: switch_start_stop_generator

      # UtilOn for >5 minutes & GenOn? >>> turn generator off
      - if:
          condition:
            and:
              - binary_sensor.is_on: sensor_retea
              - switch.is_on: switch_start_stop_generator
          then:
            - if:
                condition:
                  lambda: |-
                    // this math is immune to millis() rollover because it's all unsigned
                    return ((unsigned long)(millis() - id(sensor_retea_last_change_time)) > 300000); // (5 minutes)
                then:
                  - switch.turn_off: switch_start_stop_generator

      # this is to track the state and timing of changes on the mains on/off sensor.
        # the purpose of this is to know how long ago the mains utility changed state
        # which is used in the relay-control logic to delay the load transfer by a few seconds
        # and to ensure that mains power has been stable for 5 minutes before stopping the generator
      - if:
          # util is on, but last_state is false
          condition:
            and:
              - binary_sensor.is_on: sensor_retea
              - lambda: |-
                  return id(sensor_retea_last_state) == false;
          then:
            - globals.set:
                id: sensor_retea_last_state
                value: 'true'
            - globals.set:
                id: sensor_retea_last_change_time
                value: !lambda |-
                        return millis();

      - if:
          # util is off, but last_state is true
          condition:
            and:
              - binary_sensor.is_off: sensor_retea
              - lambda: |-
                  return id(sensor_retea_last_state) == true;
          then:
            - globals.set:
                id: sensor_retea_last_state
                value: 'false'
            - globals.set:
                id: sensor_retea_last_change_time
                value: !lambda |-
                        return millis();

# during ESP boot, set the conditions in the global variables to the current observed conditions
# e.g. sensor_retea, and the last_change_time values to current time in millis().
esphome:
  name: generator-control
  on_boot:
    priority: 700.0
    then:
      - if:
          condition:
            - binary_sensor.is_off: sensor_retea
          then:
            - globals.set:
                id: sensor_retea_last_state
                value: 'false'
          else:
            - globals.set:
                id: sensor_retea_last_state
                value: 'true'
      - globals.set:
          id: sensor_retea_last_change_time
          value: !lambda |-
                  return millis();


esp8266:
  board: esp01_1m
logger:
  level: DEBUG
web_server:
  port: 80
  include_internal: true
captive_portal:
api:
  reboot_timeout: 0s  # default 15
ota:
wifi:
  networks:
  - ssid: your_ssid
    password: your_wifi_password

Hey @glyndon, I was wondering if you could please help with my project as well if it’s not too much trouble for you.

@aquiveal This seems like something an electrician should advise you on. There may be switchover issues that neither of us are aware of, so the risk of getting things wrong could be pretty high.
I’d suggest asking your electrician to recommend an automatic-switchover device, rather than build it by hand.
For safety’s sake.

bisscableguy, I’m curious:
What will be powering your ESP when the utility goes off and the generator isn’t yet running?

A battery pack can easily power the ESP and other tools for hours.

There aren’t is why I have to build on myself. And I only asked for some advice on my code as this project is not very different from mine.

I still haven’t had time to test the code properly, I’ll do it tomorrow. The esp is powered by the generator battery. In the future, I will also set an automation to start the generator from time to time to charge the battery. From what I have tested, at leisure, the yaml code seems ok. I will return tomorrow with conclusions. Anyway, you are good! Thank you so much!

1 Like

I’ll help you tomorrow if I can, you don’t necessarily need an esp, it only works with power relays.

You might find some value in following the time-triggered model (versus delay-based) in the solution I offered for this case.
While the delay-based model may feel more intuitive to you and me, it’s not good for machines.
It feels natural to us, perhaps because we are usually able to pay attention to - and change our actions because of - unexpected changes that occur while we’re waiting for something else. Machines don’t have that intuition.

I tested and found the following.
If the main voltage from the supplier (sensor_network: off) is missing for more than 5 minutes when the main voltage comes from the supplier (sensor_network: on), it switches directly to it and stops the generator (switch_start/stop: off), that is, the generator it doesn’t work for another 5 minutes to cool down.
I added a delay to the transition from the main line to the generator.
So far that’s all I’ve noticed.


  - platform: gpio
    id: sensor_retea
    pin:
      number: GPIO5
      inverted: true
    name: “Sensor Retea”

switch:

  - platform: gpio
    id: switch_retea
    pin:
      number: GPIO14
      inverted: true
    name: “Switch Retea”
    interlock: switch_generator

  - platform: gpio
    id: switch_generator
    pin:
      number: GPIO4
      inverted: true
    name: “Switch Generator”
    interlock: switch_retea

  - platform: gpio
    id: switch_start_stop_generator
    pin:
      number: GPIO16
      inverted: true
    name: “Switch Start/Stop Generator”

globals:

   - id: sensor_retea_last_change_time
     type: long unsigned
     restore_value: no
     initial_value: '0'

   - id: sensor_retea_last_state
     type: boolean
     restore_value: no
     initial_value: 'false'

interval:
  - interval: 1s
    # There are 6 separate tests here. Their order matters.
    # the first 2 manage the load-transfer relays.
    # the next 2 manage the generator-running conrtol.
    # the next 2 tests are there to remember state and change time of mains power.
    then:

      # UtilOff, Generator running, SwitchGeneratorOff? >>> transfer load to generator
      - if:
          condition:
            and:
              - binary_sensor.is_off: sensor_retea
              - switch.is_on: switch_start_stop_generator
              - switch.is_off: switch_generator
          then:
            - if:
                condition:
                  lambda: |-
                    return millis() - id(sensor_retea_last_change_time) > 30000; // (30 seconds)
                then:
                  - switch.turn_off: switch_retea
                  - switch.turn_on: switch_generator

      # UtilOn for > 5 seconds & SwitchGeneratorOn? >>> transter load to mains
        # the 5 second delay is to prevent transferring the load when 'blips' of brief power on the utility line happen while it's being restored
        # you want the utility power to be on and stable for at least a few seconds before beginning to transition load back to it.
      - if:
          condition:
            and:
              - binary_sensor.is_on: sensor_retea
              - switch.is_on: switch_generator
          then:
            - if:
                condition:
                  lambda: |-
                    return millis() - id(sensor_retea_last_change_time) > 30000; // (30 seconds)
                then:
                  - switch.turn_off: switch_generator
                  - switch.turn_on: switch_retea

      # UtilOff & GenOff? >>> start generator
      - if:
          condition:
            and:
              - binary_sensor.is_off: sensor_retea
              - switch.is_off: switch_start_stop_generator
          then:
            - switch.turn_on: switch_start_stop_generator

      # UtilOn for >5 minutes & GenOn? >>> turn generator off
      - if:
          condition:
            and:
              - binary_sensor.is_on: sensor_retea
              - switch.is_on: switch_start_stop_generator
          then:
            - if:
                condition:
                  lambda: |-
                    return millis() - id(sensor_retea_last_change_time) > 300000; // (5 minutes)
                then:
                  - switch.turn_off: switch_start_stop_generator

      # this is to track the state and timing of changes on the mains on/off sensor.
        # the purpose of this is to know how long ago the mains utility changed state
        # which is used in the relay-control logic to delay the load transfer by a few seconds
        # and to ensure that mains power has been stable for 5 minutes before stopping the generator
      - if:
          # util is on, but last_state is false
          condition:
            and:
              - binary_sensor.is_on: sensor_retea
              - lambda: |-
                  return id(sensor_retea_last_state) == false;
          then:
            - globals.set:
                id: sensor_retea_last_state
                value: 'true'
            - globals.set:
                id: sensor_retea_last_change_time
                value: !lambda |-
                        return millis();

      - if:
          # util is off, but last_state is true
          condition:
            and:
              - binary_sensor.is_off: sensor_retea
              - lambda: |-
                  return id(sensor_retea_last_state) == true;
          then:
            - globals.set:
                id: sensor_retea_last_state
                value: 'false'
            - globals.set:
                id: sensor_retea_last_change_time
                value: !lambda |-
                        return millis();


# during ESP boot, set the conditions in the global variables to the current observed conditions
# e.g. sensor_retea, and the last_change_time values to current time in millis().
esphome:
  name: generator-control
  on_boot:
    priority: 700.0
    then:
      - if:
          condition:
            - binary_sensor.is_off: sensor_retea
          then:
            - globals.set:
                id: sensor_retea_last_state
                value: 'false'
          else:
            - globals.set:
                id: sensor_retea_last_state
                value: 'true'
      - globals.set:
          id: sensor_retea_last_change_time
          value: !lambda |-
                  return millis();
1 Like