ESPHome on_turn_on not triggered if switch is already on

Hello. I have been converting my devices to ESPHome from Tasmota. However, for the life of me I could not replicate Tasmota’s pulsetime functionality, mainly because:

If the switch receives a turn_on request while it is already on then the on_turn_on action is not triggered. It is only triggered if switching from off to on.

Consider this snippet to replicate pulsetime:

switch:
  - platform: gpio
    name: "Relay1"
    pin: GPIO12
    id: relay_1
    icon: ${device_icon}
    on_turn_on:
      - script.execute: pulsetime
    on_turn_off:
      - script.stop: pulsetime

script:
  - id: pulsetime
    mode: restart
    then:
      - delay: 10min
      - switch.turn_off: relay_1

Once turned on, the switch turns off after 10min – fine.
However, what if a PIR action sends a turn_on request to the device whilst it is already on?

In Tasmota the remaining pulsetime is reset.
In the definition above, the script could reset itself and restart counting

BUT

The on_turn_on action is never triggered!

See what happens when I manually switch off then on the device

08:39:10	[D]	[switch:012] 'Relay1' Turning ON.
08:36:39	[D]	[script:077] Script 'pulsetime' restarting (mode: restart)

But if the device is already on and I send a turn_on request, I only get:

08:41:11	[D]	[switch:012] 'Relay1' Turning ON.

No on_turn_on (and therefore script) is executed!

Can anybody help with this?

Answer is to use template switch, which have turn_on_action & turn_off_action.

on_turn_on is designed to trigger on state change, unfortunately or not.

Thank you very much for the quick response!

Yes, it works. The solution is a bit convoluted. If someone can simplify it, that would be perfect.

Based on your suggestion, this is what I did:

switch:
  - platform: template
    name: "Relay1"
    id: relay_1
    lambda: |-
      if (id(int_relay_1).state) {
        return true;
      } else {
        return false;
      }
    turn_on_action:
      - switch.turn_on: int_relay_1
      - script.execute: pulsetime
    turn_off_action:
      - switch.turn_off: int_relay_1  
      - script.stop: pulsetime

  - platform: gpio
    name: "Internal Relay1"
    pin: GPIO12
    id: int_relay_1
    internal: true

script:
  - id: pulsetime
    mode: restart
    then:
      - delay: 10min
      - switch.turn_off: relay_1

Basically, the template switch is now a “shadow switch” for the real internal switch.

And now when the switch is turned on, this is what happens:

11:25:23	[D]	[sensor:093] 'Uptime': Sending state 47.52600 s with 0 decimals of accuracy
11:25:24	[D]	[switch:012] 'Relay1' Turning ON.
11:25:24	[D]	[switch:012] 'Internal Relay1' Turning ON.
11:25:24	[D]	[switch:055] 'Internal Relay1': Sending state ON
11:25:24	[D]	[switch:055] 'Relay1': Sending state ON

And when the switch is turned on and it is already on, this is what happens:

11:29:55	[D]	[switch:012] 'Relay1' Turning ON.
11:29:55	[D]	[switch:012] 'Internal Relay1' Turning ON.
11:29:55	[D]	[script:077] Script 'pulsetime' restarting (mode: restart)

Just as desired.

It is a little convoluted and you could improve a lot. First off, Switch is the wrong platform to use if your attempting to make an “internal relay”. You need to be using a binary sensor which is much more appropriate for this situation. Even when you are using a physical button/switch you’ll use binary_sensor most of the time, not Switch.

This simply gives reads the state of a gpio and when turned ON or Pressed it sends a command to execute your script which will then restart because you set

 mode: restart
binary_sensor:
  - platform: gpio
    pin: gpio12
    name: "Relay1"
    id: relay_1
    on_press:
      - script.execute: pulsetime

script:
  - id: pulsetime
    mode: restart
    then:
      - delay: 10min
      - switch.turn_off: relay_1

If you elaborate on what the 10min delay is for, im sure we could stream line it even more.