Sonoff S31 Outlet with ESPHome - Example YAML

Below I am sharing the current version of the YAML I am using for the S31 outlets. It has not changed too much from the original posted above but has been adjusted to work with the latest changed in ESPHome.

@nosar7 - I modified the code to add the regular energy sensor and a couple others that I had missed or maybe were added after I finished working on the YAML. Either way you should now have what you seek.

If you want to adjust it in any way, here are a couple pages that might help:

@MatthewCMason - Unfortunately I do not know of any way to add the restore state configuration to the UI. If anyone does, please let me know!

One of the sensor I added, based on the cse7766 page referenced above is the Power Factor which prior I was calculating with a lambda. They both gave the same number so I just removed my template in favor of the baked in support… anyhow now I see an error in the logs:

[09:00:33][D][cse7766:186]: Impossible power factor: 1.0110 not in interval [0, 1]

Not a big deal but I wonder why…

Also, another minor issue is that the switch entity will show in the UI without a name. This is due to how I set up the YAML to force the naming of the entities (both internal and UI facing) to I want them to be. In other words, if the device is called “Office Equipment Outlet”, I want the switch entity to have the same name and I want the internal entity to also reflect that as in switch.office_equipment_outlet. Without my workaround it would end up being something like switch.office_equipment_outlet_office_equipment_outlet. Without the workaround the fix is just to rename the entity id, and with the workaround, it is simply to copy the device name and paste it into the entity name. This issue started once friendly_name was introduced eliminating the need to prepend it to each entity. Hopefully this makes sense…

Device specific YAML:

office-equipment-outlet.yaml

substitutions:
  devicename: "office-equipment-outlet"
  friendly_devicename: "Office Equipment Outlet"
  device_description: "Office Equipment Outlet - s31-outlet-1"
  voltage_cal: "0.5"
  restore_mode_setting: ALWAYS_ON
  #restore_mode: Control how the relay attempts to restore state on bootup.
  #RESTORE_DEFAULT_OFF          - Attempt to restore state and default to OFF if not possible to restore.
  #RESTORE_DEFAULT_ON           - Attempt to restore state and default to ON if not possible to restore.
  #RESTORE_INVERTED_DEFAULT_OFF - Attempt to restore state inverted from the previous state and default to OFF.
  #RESTORE_INVERTED_DEFAULT_ON  - Attempt to restore state inverted from the previous state and default to ON.
  #ALWAYS_OFF                   - Always initialize the pin as OFF on bootup.
  #ALWAYS_ON                    - Always initialize the pin as ON on bootup.
  # Interval of how often the wifi info is updated
  update_interval_wifi: "120s"
  cse7766_throttle_interval: "60s"

wifi:
  ssid: !secret iot_wifi_ssid
  password: !secret iot_wifi_password
  # Default 15min, 0s will disable. Disabling because w/HA down the relays cycle on/off every 15 minutes.
  reboot_timeout: 0s
  
  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "${devicename}"
    password: !secret iot_wifi_password

#Faster than DHCP. Also use if can't reach because of name change
  manual_ip:
    static_ip: 10.1.3.170
    gateway: 10.1.2.1
    subnet: 255.255.254.0
    dns1: 10.1.0.50
    dns2: 10.1.0.51

#Manually override what address to use to connect to the ESP.
#Defaults to auto-generated value. Example, if you have changed your
#static IP and want to flash OTA to the previously configured IP address.
  use_address: 10.1.3.170

<<: !include template-s31-outlet.yaml

Template YAML common to all S31 outlets you have:

template-s31-outlet.yaml

  esphome:
    name: ${devicename}
    comment: ${device_description}
    friendly_name: ${friendly_devicename}

  esp8266:
    board: esp01_1m
    early_pin_init: false #This prevents the on-off-on of the relay upon firmware upload

  logger:
    baud_rate: 0 # (UART logging interferes with cse7766)
    
  # Remove this line if you're not using Home Assistsant or your switch will restart every now and again
  api:
  
  ota:
    - platform: esphome
      #password: !secret ota_password
  
  web_server:
    port: 80
    include_internal: true

  # Sync time with Home Assistant
  # Used to calculate total daily energy
  time:
    - platform: homeassistant
      id: ha_time  

  uart:
    rx_pin: RX
    baud_rate: 4800
  
  text_sensor:
    - platform: wifi_info
      ip_address:
        name: "IP"
        id: device_ip
        icon: "mdi:ip-outline"
        update_interval: ${update_interval_wifi}
      dns_address:
        name: "DNS"
        id: device_dns
        icon: "mdi:dns-outline"
        update_interval: ${update_interval_wifi}
      ssid:
        name: "SSID"
        icon: "mdi:wifi-settings"
        update_interval:  ${update_interval_wifi}
      bssid:
        name: "BSSID"
        icon: "mdi:wifi-settings"
        update_interval:  ${update_interval_wifi}
      mac_address:
        name: "MAC"
        icon: "mdi:network-outline"
      scan_results:
        name: "Wifi Scan"
        icon: "mdi:wifi-refresh"
        update_interval:  ${update_interval_wifi}
        disabled_by_default: true
  
  binary_sensor:
    #Status Binary Sensor exposes the node state (if it’s connected to via MQTT/native API) for Home Assistant.
    - platform: status
      name: "Connection Status"
      id: connection_status
      entity_category: diagnostic

    - platform: gpio
      id: outlet_button
      name:  "Button"
      pin:
        number: GPIO0
        mode: INPUT_PULLUP
        inverted: True
      entity_category: ''
      on_press:
          then:
            - if:
                condition: # only toggle relay if button is enabled
                  lambda: 'return (id(select_button).state == "Enabled");'
                then:
                  switch.toggle: relay

    - platform: template
      id: threshold_status
      name: "Above Threshold"



  sensor:
    - platform: wifi_signal
      name: "WiFi Signal"
      update_interval:  ${update_interval_wifi}

    #https://esphome.io/components/sensor/cse7766.html      
    - platform: cse7766
      current:
        name: "Current"
        id: current
        state_class: measurement
        device_class: current
        unit_of_measurement: A
        accuracy_decimals: 1
        filters:
          - throttle_average: ${cse7766_throttle_interval}
      voltage:
        name: "Voltage"
        id: voltage
        state_class: measurement
        device_class: voltage
        unit_of_measurement: V
        accuracy_decimals: 1
        filters:
          - throttle_average: ${cse7766_throttle_interval}
          - offset: ${voltage_cal}
      power:
        name: "Power"
        id: power
        state_class: measurement
        device_class: power
        unit_of_measurement: W
        accuracy_decimals: 1
        filters:
          - throttle_average: ${cse7766_throttle_interval}
        on_value:   # set or clear threshold_status template binary sensor depending on whether power usage is over threshold
          - if:
              condition:
                lambda: 'return (x >= id(power_threshold).state);'
              then:
                - binary_sensor.template.publish:
                    id: threshold_status
                    state: ON
                - if:               # set or clear threshold_status template binary sensor depending on whether power usage is above threshold
                    condition:
                      lambda: 'return (id(select_led).state == "Threshold");'
                    then:
                      - switch.turn_on: blue_led
              else:
                - binary_sensor.template.publish:
                    id: threshold_status
                    state: OFF
                - if:               # set or clear threshold_status template binary sensor depending on whether power usage is above threshold
                    condition:
                      lambda: 'return (id(select_led).state == "Threshold");'
                    then:
                      - switch.turn_off: blue_led
      energy:
        name: "Energy"
        accuracy_decimals: 1
        filters:
          - throttle: ${cse7766_throttle_interval}
      apparent_power:
        name: "Apparent Power"
        accuracy_decimals: 1
        filters:
          - throttle_average: ${cse7766_throttle_interval}
      power_factor:
        name: "Power Factor"
        accuracy_decimals: 1
        filters:
          - throttle_average: ${cse7766_throttle_interval}


    - platform: total_daily_energy
      name: "Daily Energy"
      power_id: power
      filters:
        # Multiplication factor from W to kW is 0.001
        - multiply: 0.001
      unit_of_measurement: kWh
      state_class: total_increasing
      device_class: energy
      # 3 shows each W consumed, 2 every 10W, 1 every 100W
      accuracy_decimals: 1



  number:      # used as a threshold for whether the plugged-in devices is running.
    - platform: template
      name: "Power Threshold"
      min_value: 1
      max_value: 1800
      step: 1
      initial_value: 3
      id: power_threshold
      entity_category: config
      optimistic: true     # required for changing value from home assistant
      restore_value: true
      unit_of_measurement: W
      mode: box
      on_value:
        - if:
            condition:
              lambda: 'return (id(power).state >= x);'
            then:
              - binary_sensor.template.publish:
                  id: threshold_status
                  state: ON
              - if:
                  condition:
                    lambda: 'return (id(select_led).state == "Threshold");'
                  then:
                    - switch.turn_on: blue_led
            else:
              - binary_sensor.template.publish:
                  id: threshold_status
                  state: OFF  
              - if:
                  condition:
                    lambda: 'return (id(select_led).state == "Threshold");'
                  then:
                    - switch.turn_off: blue_led

  switch:
      # blue LED follows relay power state
    - platform: gpio
      id: blue_led
      pin:
        number: GPIO13
        inverted: true

      # relay output
    - platform: gpio
      id: relay
      name: ' '
      icon: "mdi:power-socket-us"
      pin: GPIO12
      entity_category: ''
      restore_mode: ${restore_mode_setting}
      # automatically make blue led equal relay state if set to "Relay"
      on_turn_on:
        - if:
            condition: # only if blue LED set to "Relay"
              lambda: 'return (id(select_led).state == "Relay");'
            then:
              switch.turn_on: blue_led
      on_turn_off:
        - if:
            condition: # only if blue LED set to "Relay"
              lambda: 'return (id(select_led).state == "Relay");'
            then:
              switch.turn_off: blue_led

  #Do not enable otherwise Blue led can't be configured
  #status_led:
  #    pin:
  #      number: GPIO13


  button:
    - platform: restart
      id: restart_button
      name: "Restart"
      entity_category: diagnostic


  select:
      # option to disable button
    - platform: template
      name: "Button"
      id: select_button
      optimistic: true
      options:
        - Enabled
        - Disabled
      initial_option: Enabled
      restore_value: true
      icon: mdi:circle-double
      entity_category: config

      # option to disable blue LED
    - platform: template
      name: Blue LED
      id: select_led
      optimistic: true
      entity_category: config
      options:
        - Relay
        - Threshold
        - Disabled
      initial_option: Relay
      restore_value: true
      icon: mdi:led-on
      on_value:
        then:
        - if:
            condition:
              lambda: 'return ( (id(select_led).state == "Relay") && id(relay).state );'
            then:
              switch.turn_on: blue_led
            else:
              switch.turn_off: blue_led