Sonoff S31 Outlet with ESPHome - Example YAML

EDIT: The YAML has changed over time as I make improvements and corrections. I am not rewriting this post as for the most part it is still correct (if not let me know) but keep in mind the YAML at the bottom of the post has evolved a bit compared to what you see in this first post.

I found a basic version of the code below for the Sonoff S31 and made a number of improvements, at least for my use scenario. I rely a lot on people sharing so I wanted to share back in case it helps others. I had started off with Tasmota and really liked the fact the outlet had a web interface and how much information was available for each device. What I did not like was the complexity of setting it all up due to MQTT being in the middle. It was not difficult but it felt unnecessarily complicated when ESPHome has a more direct integration. Anyway this thread is not meant o be a debate between ESPHome and Tasmota so back to the YAML:

This is the entire code needed:

substitutions:
  devicename: s31-outlet-1
  friendly_devicename: "Office Equipment Outlet"
  
# Basic Config
esphome:
  name: ${devicename}
  platform: ESP8266
  board: esp01_1m

The substitutions section allows to define variables so that you don’t have to edit the YAML in many places each time you create a new device using this YAML or simply make a change.

The devicename needs to be short and a unique identifier. I would set it, and leave it, named as you named the configuration in ESPHome when you created the device. For some reason I run into issues (device not found, etc.) if I mess with it. This is also how this device will be named on the Integration page under ESPHome (but you can rename it there if you really want to), and will be the default device name in Home Assistant (which we will change).

The friendly_devicename is what you will call the device. This device is an outlet so I would recommend naming it with the name of the device attached to the outlet, or blah blah outlet in the event the outlet is powering has multiple devices. This is important as it will determine what all the other entities will be named and will save you lots of entity editing later.

wifi:
  ssid: !secret iot_wifi_ssid
  password: !secret iot_wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "${devicename} Hotspot"
    password: !secret iot_wifi_password

When you see !secret it is referring to a variable in your secrets.yaml file which can be edited directly from within ESPHome (top right of window). Just add the following editing in your details:

# Your IOT Wi-Fi SSID and password
iot_wifi_ssid: "IoT_ SSID_ here"
iot_wifi_password: "password_here"

I really wanted to see some device information when loading up the IP of the device in a web browser like Tasmota. While not as fancy, a lot can be shown on ESPDevices just by adding a few lines of YAML. I don’t know what include_internal: true actually does as I don’t believe I saw a difference with or without it, but it doesn’t seem to be causing issues so I left it.

web_server:
  port: 80
  include_internal: true

Whether needed or not, I wanted to see all connectivity variables as entities for easier troubleshooting. When you start having lots of devices this is handy. Here we see the ${friendly_devicename} variable which will give the entities a name that includes the device name.

text_sensor:
  - platform: wifi_info
    ip_address:
      name: "${friendly_devicename}: IP"
      icon: "mdi:ip-outline"
    ssid:
      name: "${friendly_devicename}: SSID"
      icon: "mdi:wifi-settings"
    bssid:
      name: "${friendly_devicename}: BSSID"
      icon: "mdi:wifi-settings"
    mac_address:
      name: "${friendly_devicename}: MAC"
      icon: "mdi:network-outline"
    scan_results:
      name: "${friendly_devicename}: Wifi Scan"
      icon: "mdi:wifi-refresh"
      disabled_by_default: true

This is how it will look in HA:

In the example above, only IP will appear on the device page but on a dashboard it will sow as Office Equipment Outlet: IP.

Same thing for the Controls and Sensors of the device. In the sensor section I changed the Energy over time from W (which I believe was showing with 3 decimals which was a bit silly) to kW with 3 decimals. I would argue that seeing anything under 1W is really meaningless and probably not even very accurate for this kind of device. If you set it to kW the numbers will be smaller and you will still see down to the 1W granularity. For those wanting to figure out the cost of the power used, this makes it a tiny bit simpler since power is sold by the kWh. Before this change, 2.345kW was showing as 2,345.678W. I would argue that the 1 decimal accuracy for voltage and power are also pointless but I left them since almost all other devices show that or more.

sensor:
  - platform: wifi_signal
    name: "${friendly_devicename}: WiFi Signal"
    update_interval: 60s
  - platform: cse7766
    current:
      name: "${friendly_devicename}: Current"
      accuracy_decimals: 1
    voltage:
      name: "${friendly_devicename}: Voltage"
      accuracy_decimals: 1
    power:
      name: "${friendly_devicename}: Power"
      accuracy_decimals: 1
      id: my_power
  - platform: total_daily_energy
    name: "${friendly_devicename}: Daily Energy"
    power_id: my_power
    filters:
      # Multiplication factor from W to kW is 0.001
      - multiply: 0.001
    unit_of_measurement: kW

No changes here, but I believe the restore_mode: ALWAYS_ON is what makes the outlet turn on immediately after being powered up.

switch:
  - platform: gpio
    name: "${friendly_devicename}"
    icon: "mdi:power-socket-us"
    pin: GPIO12
    id: relay
    restore_mode: ALWAYS_ON

If this doesn’t work for you, the other options should be here:

I have not tested these yet, but the page above lists these as options:

* **restore_mode** (*Optional*): Control how the GPIO Switch attempts to restore state on bootup. For restoring on ESP8266s, also see `restore_from_flash` in the [esp8266 section](https://esphome.io/components/esp8266.html).
> * `RESTORE_DEFAULT_OFF` (Default) - Attempt to restore state and default to OFF if not possible to restore.
>   * `RESTORE_DEFAULT_ON` - Attempt to restore state and default to ON.
>   * `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.

I read this part is needed if you want the energy measurement kWh to be reset daily. In most cases you either need a time/date from the start of the measurement, or just reset it daily. I would like a start time/date as Tasmota offered and a way to reset it as needed. I’ll research this to see if it is possible from within the device YAML itself.

time:
  - platform: sntp
    id: my_time

:point_right::point_right::point_right: Full YAML you can try here: :point_left::point_left::point_left:

:point_right: Edit: see further down for updated full YAML

substitutions:
  devicename: s31-outlet-1
  friendly_devicename: "Office Equipment Outlet"
  
# Basic Config
esphome:
  name: ${devicename}
  platform: ESP8266
  board: esp01_1m

wifi:
  ssid: !secret iot_wifi_ssid
  password: !secret iot_wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "${devicename} Hotspot"
    password: !secret iot_wifi_password

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:

web_server:
  port: 80
  include_internal: true

# Device Specific Config

uart:
  rx_pin: RX
  baud_rate: 4800

text_sensor:
  - platform: wifi_info
    ip_address:
      name: "${friendly_devicename}: IP"
      icon: "mdi:ip-outline"
    ssid:
      name: "${friendly_devicename}: SSID"
      icon: "mdi:wifi-settings"
    bssid:
      name: "${friendly_devicename}: BSSID"
      icon: "mdi:wifi-settings"
    mac_address:
      name: "${friendly_devicename}: MAC"
      icon: "mdi:network-outline"
    scan_results:
      name: "${friendly_devicename}: Wifi Scan"
      icon: "mdi:wifi-refresh"
      disabled_by_default: true

binary_sensor:
  - platform: gpio
    pin:
      number: GPIO0
      mode: INPUT_PULLUP
      inverted: True
    name: "${friendly_devicename}: Button"
    on_press:
      - switch.toggle: relay
  - platform: status
    name: "${friendly_devicename}: Status"

sensor:
  - platform: wifi_signal
    name: "${friendly_devicename}: WiFi Signal"
    update_interval: 60s
  - platform: cse7766
    current:
      name: "${friendly_devicename}: Current"
      accuracy_decimals: 1
    voltage:
      name: "${friendly_devicename}: Voltage"
      accuracy_decimals: 1
    power:
      name: "${friendly_devicename}: Power"
      accuracy_decimals: 1
      id: my_power
  - platform: total_daily_energy
    name: "${friendly_devicename}: Daily Energy"
    power_id: my_power
    filters:
      # Multiplication factor from W to kW is 0.001
      - multiply: 0.001
    unit_of_measurement: kW

switch:
  - platform: gpio
    name: "${friendly_devicename}"
    icon: "mdi:power-socket-us"
    pin: GPIO12
    id: relay
    restore_mode: ALWAYS_ON

time:
  - platform: sntp
    id: my_time

status_led:
  pin: GPIO13

Thank you to all those who have helped me on HA stuff! I hope this counts as giving back a little :slight_smile:

Disclaimer: As mentioned above, I found most of this code online (sorry I forgot where) and just customized it a bit. Not claiming any authorship!

I would now add the device leaving the name that matches what it is called in ESPHome. Once added, go to the device to make one last change. There I would only change the device name on the top left to match what you used in the friendly_devicename variable. In the example above it is Office Equipment Outlet. Doing so will make your device page look like this without further editing:

Most of the time I end up having to edit every single entity for it to be named following what I adopted as my standard. With this YAML I don’t and it has become much easier to add/remove. In fact, if you end up with other entities under this device that you want to get rid of (I did while making changes), just delete it from the ESPHome Integration (only the integration!!!) and add it back (Add integration, select ESPHome, and enter IP).

image

The only entity that I set as disabled by default is the Wifi Scan one that shows all the APs it receives. It’s unnecessary in most cases… you may want it only if you have connectivity issues and want to see what it receives.

This is how it will show up in ESPHome… too bad one can’t add a descriptor.

Last, the web interface (default view)…

:point_right::point_right::point_right: LATEST VERSION OF THE FULL YAML :point_left::point_left::point_left:
UPDATED: 05-JUL-2022
For information on changes please refer to the posts in this thread.:

Deprecated YAML - Expand section to see it
substitutions:
  devicename: s31-outlet-1
  devicename_no_dashes: s31_outlet_1
  friendly_devicename: "Office Equipment Outlet"
  restore_mode_setting: RESTORE_DEFAULT_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.
  #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.


# Basic Config
esphome:
  name: ${devicename}
  platform: ESP8266
  board: esp01_1m

wifi:
  ssid: !secret iot_wifi_ssid
  password: !secret iot_wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "${devicename} Hotspot"
    password: !secret iot_wifi_password

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:

web_server:
  port: 80
  include_internal: true

# Device Specific Config

uart:
  rx_pin: RX
  baud_rate: 4800

text_sensor:
  - platform: wifi_info
    ip_address:
      name: "${friendly_devicename}: IP"
      icon: "mdi:ip-outline"
    ssid:
      name: "${friendly_devicename}: SSID"
      icon: "mdi:wifi-settings"
    bssid:
      name: "${friendly_devicename}: BSSID"
      icon: "mdi:wifi-settings"
    mac_address:
      name: "${friendly_devicename}: MAC"
      icon: "mdi:network-outline"
    scan_results:
      name: "${friendly_devicename}: Wifi Scan"
      icon: "mdi:wifi-refresh"
      disabled_by_default: true

binary_sensor:
  - platform: gpio
    pin:
      number: GPIO0
      mode: INPUT_PULLUP
      inverted: True
    name: "${friendly_devicename}: Button"
    on_press:
      - switch.toggle: relay
  - platform: status
    name: "${friendly_devicename}: Status"

sensor:
  - platform: wifi_signal
    name: "${friendly_devicename}: WiFi Signal"
    update_interval: 60s
  - platform: cse7766
    current:
      name: "${friendly_devicename}: Current"
      state_class: measurement
      device_class: current
      unit_of_measurement: A
      accuracy_decimals: 1    
    voltage:
      name: "${friendly_devicename}: Voltage"
      state_class: measurement
      device_class: voltage
      unit_of_measurement: V
      accuracy_decimals: 1
    power:
      name: "${friendly_devicename}: Power"
      id: "${devicename_no_dashes}_power"
      state_class: measurement
      device_class: power
      unit_of_measurement: W
      accuracy_decimals: 1
  - platform: total_daily_energy
    name: "${friendly_devicename}: Daily Energy"
    power_id: "${devicename_no_dashes}_power"
    filters:
      # Multiplication factor from W to kW is 0.001
      - multiply: 0.001
    unit_of_measurement: kW
    state_class: total_increasing
    device_class: energy
    # 3 shows each W consumed, 2 every 10W, 1 every 100W
    accuracy_decimals: 1

switch:
  - platform: gpio
    name: "${friendly_devicename}"
    icon: "mdi:power-socket-us"
    pin: GPIO12
    id: relay
    restore_mode: ${restore_mode_setting}

time:
  - platform: sntp
    id: my_time

status_led:
  pin: GPIO13

.
.
.
.
:point_right::point_right::point_right: LATEST VERSION OF THE FULL YAML :point_left::point_left::point_left:
UPDATED: 07-MAR-2024
For information on changes please refer to this post in this thread.:

template-s31-outlet.yaml
Note: placed in same folder as all other ESPHome YAML files, although there is a way to hide it, I prefer not to.

  # Basic Config
  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:
  
  web_server:
    port: 80
    include_internal: true

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


  # Device Specific Config
  uart:
    rx_pin: RX
    baud_rate: 4800
  
  text_sensor:
    - platform: wifi_info
      ip_address:
        name: "IP"
        icon: "mdi:ip-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: ${cse7766_throttle_interval}
      voltage:
        name: "Voltage"
        id: voltage
        state_class: measurement
        device_class: voltage
        unit_of_measurement: V
        accuracy_decimals: 1
        filters:
          - throttle: ${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: ${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
            
    - 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

    - platform: template
      name: "Power Factor"
      id: power_factor
      #lambda: return id(power).state / id(voltage).state / id(current).state;
      lambda: |-
        auto x = id(power).state / id(voltage).state / id(current).state;
        if (x >= 0) return x;
            return 0.0;


  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

Device specific YAML:

office-equipment-outlet.yaml

substitutions:
  devicename: "office-equipment-outlet"
  devicename_no_dashes: s31_outlet_1
  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: "60s"
  cse7766_throttle_interval: "5s"

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
5 Likes

Thanks for setting this up @aruffell. I just got a few of these outlets and I used this template to get them quickly into HA. I had some trouble getting it recognized by the Energy dashboard so I made some edits to the sensor section. All is good now. If you find it helpful feel free to include them in your template (I changed devicename substitutions variable so it would require an edit to the below).

sensor:
  - platform: wifi_signal
    name: "${friendly_devicename}: WiFi Signal"
    update_interval: 60s
  - platform: cse7766
    current:
      name: "${friendly_devicename}: Current"
      entity_category: diagnostic
      accuracy_decimals: 1
      state_class: measurement
      unit_of_measurement: A
      device_class: current
    voltage:
      name: "${friendly_devicename}: Voltage"
      entity_category: diagnostic
      accuracy_decimals: 1
      state_class: measurement
      device_class: voltage 
      unit_of_measurement: V
    power:
      name: "${friendly_devicename}: Power"
      entity_category: diagnostic
      accuracy_decimals: 1
      state_class: measurement
      device_class: energy  
      unit_of_measurement: W    
      id: "${s31_outlet_1}_power"
  - platform: total_daily_energy
    name: "${friendly_devicename}: Daily Energy"
    power_id: "${s31_outlet_1}_power"
    filters:
      - multiply: 0.001
    unit_of_measurement: kWh
    state_class: total_increasing
    device_class: energy
    accuracy_decimals: 1

@techanti Awesome! Thank you! I incorporated most of your edits and had a question about a couple.

I see that you declared Current, Voltage, Power as entity_category: diagnostic but I don’t believe that to be appropriate based on the definition here:

Is there a particular reason for doing so?

Were all the other…

state_class: measurement
device_class: energy  
unit_of_measurement: x

…needed for the sensor to be usable in the Energy dashboard?

I included your changes, albeit modified, for the power ids (id & power_id). Do they need to be unique thus prompting the change? My modification to what you did just pushes the edit to the top and will require just one edit.

This is what the full code looks like now:

substitutions:
  devicename: s31-outlet-1
  devicename_no_dashes: s31_outlet_1
  friendly_devicename: "Office Equipment Outlet"
  restore_mode_setting: RESTORE_DEFAULT_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.
  #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.


# Basic Config
esphome:
  name: ${devicename}
  platform: ESP8266
  board: esp01_1m

wifi:
  ssid: !secret iot_wifi_ssid
  password: !secret iot_wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "${devicename} Hotspot"
    password: !secret iot_wifi_password

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:

web_server:
  port: 80
  include_internal: true

# Device Specific Config

uart:
  rx_pin: RX
  baud_rate: 4800

text_sensor:
  - platform: wifi_info
    ip_address:
      name: "${friendly_devicename}: IP"
      icon: "mdi:ip-outline"
    ssid:
      name: "${friendly_devicename}: SSID"
      icon: "mdi:wifi-settings"
    bssid:
      name: "${friendly_devicename}: BSSID"
      icon: "mdi:wifi-settings"
    mac_address:
      name: "${friendly_devicename}: MAC"
      icon: "mdi:network-outline"
    scan_results:
      name: "${friendly_devicename}: Wifi Scan"
      icon: "mdi:wifi-refresh"
      disabled_by_default: true

binary_sensor:
  - platform: gpio
    pin:
      number: GPIO0
      mode: INPUT_PULLUP
      inverted: True
    name: "${friendly_devicename}: Button"
    on_press:
      - switch.toggle: relay
  - platform: status
    name: "${friendly_devicename}: Status"

sensor:
  - platform: wifi_signal
    name: "${friendly_devicename}: WiFi Signal"
    update_interval: 60s
  - platform: cse7766
    current:
      name: "${friendly_devicename}: Current"
      state_class: measurement
      device_class: current
      unit_of_measurement: A
      accuracy_decimals: 1    
    voltage:
      name: "${friendly_devicename}: Voltage"
      state_class: measurement
      device_class: voltage
      unit_of_measurement: V
      accuracy_decimals: 1
    power:
      name: "${friendly_devicename}: Power"
      id: "${devicename_no_dashes}_power"
      state_class: measurement
      device_class: energy
      unit_of_measurement: W
      accuracy_decimals: 1
  - platform: total_daily_energy
    name: "${friendly_devicename}: Daily Energy"
    power_id: "${devicename_no_dashes}_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

switch:
  - platform: gpio
    name: "${friendly_devicename}"
    icon: "mdi:power-socket-us"
    pin: GPIO12
    id: relay
    restore_mode: ${restore_mode_setting}

time:
  - platform: sntp
    id: my_time

status_led:
  pin: GPIO13

I have changed restore_mode: ALWAYS_ON to restore_mode: RESTORE_DEFAULT_ON as I believe it is what most people would likely prefer (and it is easy to change anyway). I also moved the setting up top to make it easier to spot and edit.

1 Like

As I understand things, The class and unit of measure have to be set for them be usable in the Energy dashboard. entity_category comes from the entity definition below and I usually have it on the esphome things I do while I working on them but often neglect to remove it:

https://developers.home-assistant.io/docs/core/entity/#generic-properties

Again, thanks of for this. It save me a lot time.

1 Like

@techanti - I found errors in the “Statistics” section stating that "The unit (“W”) of this entity doesn’t match a unit of device class ‘energy’.

I checked and found that the device_class for Power (W) should be “power” and not “energy” so I made the correction and updated the code in my first post (at the bottom).

1 Like

Thank you for the config example, this helped getting the base configuration running but I noticed the sensors were sending far to much spam. I refined the update intervals on the sensors to only receive useful updates - update the filter delays as you see fit.

# Device Specific Config

uart:
  rx_pin: RX
  baud_rate: 4800

text_sensor:
  - platform: wifi_info
    ip_address:
      name: "${friendly_devicename}: IP"
      icon: "mdi:ip-outline"
    ssid:
      name: "${friendly_devicename}: SSID"
      icon: "mdi:wifi-settings"
    bssid:
      name: "${friendly_devicename}: BSSID"
      icon: "mdi:wifi-settings"
    mac_address:
      name: "${friendly_devicename}: MAC"
      icon: "mdi:network-outline"
    scan_results:
      name: "${friendly_devicename}: Wifi Scan"
      icon: "mdi:wifi-refresh"
      disabled_by_default: true

binary_sensor:
  - platform: gpio
    pin:
      number: GPIO0
      mode: INPUT_PULLUP
      inverted: True
    name: "${friendly_devicename}: Button"
    on_press:
      - switch.toggle: relay
  - platform: status
    name: "${friendly_devicename}: Status"

sensor:
  - platform: wifi_signal
    name: "${friendly_devicename}: WiFi Signal"
    update_interval: 60s
  - platform: cse7766
    current:
      name: "${friendly_devicename}: Current"
      filters:
        - throttle_average: 5s
        - delta: 0.1
      state_class: measurement
      device_class: current
      unit_of_measurement: A
      accuracy_decimals: 1
    voltage:
      name: "${friendly_devicename}: Voltage"
      filters:
        - throttle: 30s
      state_class: measurement
      device_class: voltage
      unit_of_measurement: V
      accuracy_decimals: 1
    power:
      name: "${friendly_devicename}: Power"
      filters:
        - throttle: 30s
        - delta: 0.1
      id: "${devicename_no_dashes}_power"
      state_class: measurement
      device_class: energy
      unit_of_measurement: W
      accuracy_decimals: 1
  - platform: total_daily_energy
    name: "${friendly_devicename}: Daily Energy"
    power_id: "${devicename_no_dashes}_power"
    filters:
      - throttle: 60s
      # 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


switch:
  - platform: gpio
    name: "${friendly_devicename}"
    icon: "mdi:power-socket-us"
    pin: GPIO12
    id: relay
    restore_mode: ${restore_mode_setting}

time:
  - platform: sntp
    id: my_time

status_led:
  pin: GPIO13

@david-fuchs I have made several changes since I last posted the YAML, and one of them was to throttle the measurements but in a more basic way than yours. In this example it is still set at 5s which is way too often for most use cases.

Also, since I have a 10+ of these outlets and got fed up making changes to all of them, I split the YAML into a template and a device specific file.

Sharing for reference:

template-s31-outlet.yaml
Note: placed in same folder as all other ESPHome YAML files, although there is a way to hide it, I prefer not to. You don’t compile or upload this one. It gets pulled in by the device specific file. Note that there are 2 extra spaces at the beginning of each line otherwise the import won’t work.

  # Basic Config
  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:
  
  web_server:
    port: 80
    include_internal: true

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


  # Device Specific Config
  uart:
    rx_pin: RX
    baud_rate: 4800
  
  text_sensor:
    - platform: wifi_info
      ip_address:
        name: "IP"
        icon: "mdi:ip-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: ${cse7766_throttle_interval}
      voltage:
        name: "Voltage"
        id: voltage
        state_class: measurement
        device_class: voltage
        unit_of_measurement: V
        accuracy_decimals: 1
        filters:
          - throttle: ${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: ${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
            
    - 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

    - platform: template
      name: "Power Factor"
      id: power_factor
      #lambda: return id(power).state / id(voltage).state / id(current).state;
      lambda: |-
        auto x = id(power).state / id(voltage).state / id(current).state;
        if (x >= 0) return x;
            return 0.0;


  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

Device specific YAML:

office-equipment-outlet.yaml

substitutions:
  devicename: "office-equipment-outlet"
  devicename_no_dashes: s31_outlet_1
  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: "60s"
  cse7766_throttle_interval: "5s"

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

Maybe the most important modification is:

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

As it prevents the super annoying relay toggling when you upload the firmware which essentially turns the outlet off… turning off whatever is connected to it. For some devices it is not an issue, but certainly is for others…

Edit: I found the post I’d like to credit for the early_pin_init fix as it made a world of difference: Sonoff S31 Outlet running ESPHome - Any way to prevent relay cycling (on>off>on) after firmware upload? - #12 by orange-assistant

1 Like

Thank you! I’m in the same boat - have 10+ of these units also. Great idea on the template for the config.