Shelly Plus Plug S - ESPHOME

I added BLE to my second device, and it looks like that broke it :frowning: that one I flashed using serial with ESPFlasher. Let’s hope I can restore it using serial.

Sadly it looks dead, can’t flash it or get the logs from it :frowning:

Edit: After checking all my connections and trying again, its alive, but I don’t dare flash it with BT again.

Here you have my config (But its just the base with how long it has been running and some debug information)

substitutions:
  device_name: thrudr
  friendly_name: Thrudr
  channel_1: Relay
  max_power: "1500" # 2000
  max_temp: "65.0" # 70.0
  # Higher value gives lower watt readout.
  current_res: "0.00105"
  # Lower value gives lower voltage readout.
  voltage_div: "1830"


esphome:
  name: ${device_name}
  friendly_name: ${friendly_name}
  on_boot:
    - delay: 10s
    - lambda: !lambda |-
        id(rgb_ready) = true;
    - script.execute: set_rgb

esp32:
  board: esp32doit-devkit-v1
  framework:
    type: esp-idf

# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: "...."

ota:
  password: "...."

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Enable fallback hotspot in case wifi connection fails
  ap:
    ssid: ${device_name}
    password: "..."

esp32_ble_tracker:

bluetooth_proxy:

debug:
  update_interval: 5s

time:
  - platform: homeassistant
    id: my_time

globals:
  - id: rgb_ready
    type: bool
    restore_value: false
    initial_value: 'false'
  - id: total_energy
    type: float
    restore_value: true
    initial_value: '0.0'

    #### only needed for RGB LED to set up a while after boot. Not available with esp-idf framework #####

script:
  - id: set_rgb
    mode: queued
    then:
      - if:
          condition:
            lambda: 'return id(rgb_ready);'
          then:
            - if:
                condition:
                  lambda: 'return id(relay).state;'
                then:
                  - if:
                      condition:
                        lambda: 'return id(ring_on).remote_values.is_on();'
                      then:
                        - light.turn_on:
                            id: rgb_light1
                            brightness: !lambda |-
                              return id(ring_on).remote_values.get_brightness();
                            red: !lambda |-
                              return id(ring_on).remote_values.get_red();
                            green: !lambda |-
                              return id(ring_on).remote_values.get_green();
                            blue: !lambda |-
                              return id(ring_on).remote_values.get_blue();
                        - light.turn_on:
                            id: rgb_light2
                            brightness: !lambda |-
                              return id(ring_on).remote_values.get_brightness();
                            red: !lambda |-
                              return id(ring_on).remote_values.get_red();
                            green: !lambda |-
                              return id(ring_on).remote_values.get_green();
                            blue: !lambda |-
                              return id(ring_on).remote_values.get_blue();
                      else:
                        - light.turn_off: rgb_light1
                        - light.turn_off: rgb_light2
                else:
                  - if:
                      condition:
                        lambda: 'return id(ring_off).remote_values.is_on();'
                      then:
                        - light.turn_on:
                            id: rgb_light1
                            brightness: !lambda |-
                              return id(ring_off).remote_values.get_brightness();
                            red: !lambda |-
                              return id(ring_off).remote_values.get_red();
                            green: !lambda |-
                              return id(ring_off).remote_values.get_green();
                            blue: !lambda |-
                              return id(ring_off).remote_values.get_blue();
                        - light.turn_on:
                            id: rgb_light2
                            brightness: !lambda |-
                              return id(ring_off).remote_values.get_brightness();
                            red: !lambda |-
                              return id(ring_off).remote_values.get_red();
                            green: !lambda |-
                              return id(ring_off).remote_values.get_green();
                            blue: !lambda |-
                              return id(ring_off).remote_values.get_blue();
                      else:
                        - light.turn_off: rgb_light1
                        - light.turn_off: rgb_light2

output:
  - platform: template
    id: r_out_on
    type: float
    write_action:
      - lambda: |-
  - platform: template
    id: g_out_on
    type: float
    write_action:
      - lambda: |-
  - platform: template
    id: b_out_on
    type: float
    write_action:
      - lambda: |-
  - platform: template
    id: r_out_off
    type: float
    write_action:
      - lambda: |-
  - platform: template
    id: g_out_off
    type: float
    write_action:
      - lambda: |-
  - platform: template
    id: b_out_off
    type: float
    write_action:
      - lambda: |-

light:
  - platform: rgb
    id: ring_on
    name: "${channel_1} Ring when On"
    icon: "mdi:circle-outline"
    default_transition_length: 0s
    red: r_out_on
    green: g_out_on
    blue: b_out_on
    restore_mode: RESTORE_DEFAULT_OFF
    entity_category: config
    on_state:
      - delay: 50ms
      - script.execute: set_rgb
  - platform: rgb
    id: ring_off
    name: "${channel_1} Ring when Off"
    icon: "mdi:circle-outline"
    default_transition_length: 0s
    red: r_out_off
    green: g_out_off
    blue: b_out_off
    restore_mode: RESTORE_DEFAULT_OFF
    entity_category: config
    on_state:
      - delay: 50ms
      - script.execute: set_rgb

  - platform: esp32_rmt_led_strip
    rgb_order: GRB
    rmt_channel: 0
    chipset: ws2812
    pin: GPIO25
    num_leds: 2
    id: rgb_light1
    internal: true
    default_transition_length: 700ms
    restore_mode: ALWAYS_OFF
  - platform: esp32_rmt_led_strip
    rgb_order: GRB
    rmt_channel: 1
    chipset: ws2812
    pin: GPIO26
    num_leds: 2
    id: rgb_light2
    internal: true
    default_transition_length: 700ms
    restore_mode: ALWAYS_OFF

binary_sensor:
  - platform: gpio
    id: "push_button"
    internal: true
    pin:
      number: GPIO9
      inverted: yes
      mode:
        input: true
        pullup: true
    on_click:
      then:
        - if:
            condition:
              switch.is_off: button_lock
            then:
              - switch.toggle: relay
    filters:
      - delayed_on_off: 5ms

switch:
  - platform: gpio
    pin: GPIO4
    id: relay
    name: "${channel_1}"
    restore_mode: RESTORE_DEFAULT_OFF
    on_turn_on:
      - script.execute: set_rgb
    on_turn_off:
      - script.execute: set_rgb
  - platform: template
    entity_category: 'config'
    name: "Button lock"
    id: button_lock
    optimistic: true
    restore_mode: ALWAYS_OFF

sensor:
  - platform: uptime
    name: Uptime Sensor
  - platform: debug
    free:
      name: "Heap Free"
    block:
      name: "Heap Max Block"
    loop_time:
      name: "Loop Time"
  - platform: ntc
    sensor: temp_resistance_reading
    name: "${device_name} Temperature"
    unit_of_measurement: "°C"
    accuracy_decimals: 1
    icon: "mdi:thermometer"
    entity_category: 'diagnostic'
    calibration:
      b_constant: 3350
      reference_resistance: 10kOhm
      reference_temperature: 298.15K
    on_value_range:
      - above: ${max_temp}
        then:
          - switch.turn_off: "relay"
          - homeassistant.service:
              service: persistent_notification.create
              data:
                title: Message from ${device_name}
              data_template:
                message: Switch turned off because temperature exceeded ${max_temp} °C
  - platform: resistance
    id: temp_resistance_reading
    sensor: temp_analog_reading
    configuration: DOWNSTREAM
    resistor: 10kOhm
  - platform: adc
    id: temp_analog_reading
    pin: GPIO33
    attenuation: 11db
    update_interval: 10s

  - platform: hlw8012
    model: BL0937
    sel_pin:
      number: GPIO19
      inverted: true
    cf_pin: GPIO10
    cf1_pin: GPIO22
    current_resistor: ${current_res}
    voltage_divider: ${voltage_div}
    change_mode_every: 1
    update_interval: 5s
    current:
      id: current
      unit_of_measurement: A
      accuracy_decimals: 3
      internal: true
      name: "${channel_1} current"
    voltage:
      id: voltage
      unit_of_measurement: V
      accuracy_decimals: 1
      internal: false
      name: "${channel_1} voltage"
    power:
      name: "${channel_1} power"
      unit_of_measurement: W
      id: power
      icon: mdi:flash-outline
      force_update: true
      on_value_range:
        - above: ${max_power}
          then:
            - switch.turn_off: relay
            - homeassistant.service:
                service: persistent_notification.create
                data:
                  title: Message from ${device_name}
                data_template:
                  message: Switch turned off because power exceeded ${max_power}W

  - platform: total_daily_energy
    name: "${channel_1} energy"
    power_id: power
    state_class: total_increasing
    unit_of_measurement: kWh
    filters:
      # Multiplication factor from W to kW is 0.001
      - multiply: 0.001
      - lambda: !lambda |-
          static auto last_state = x;
          if (x < last_state) { // x was reset
            id(total_energy) += last_state;
            ESP_LOGI("main", "Energy channel 1 was reset: %f", id(total_energy));
          }
          last_state = x;
          return id(total_energy) + x;

text_sensor:
  - platform: debug
    reset_reason:
      name: "Reset Reason"

Looks like I’m giving up on this one. I managed to get it open, there was a screw that could be removed and then it was easily opened so I do not seem to have the newest unopenable version at least.

Got it hooked up via USB-TTL (CP2102) and using minicom I get the following output over and over again:

ELF file SHA256: c7decda0617db6a0                                               
                                                                                
Rebooting...                                                                    
ets Jul 29 2019 12:21:46                                                        
                                                                                
rst:0xc (SW_CPU_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)                          
configsip: 188777542, SPIWP:0xee                                                
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00         
mode:DIO, clock div:2                                                           
load:0x3fff0030,len:7076                                                        
load:0x40078000,len:15584                                                       
ho 0 tail 12 room 4                                                             
load:0x40080400,len:4                                                           
load:0x40080404,len:3876                                                        
entry 0x4008064c                                                                
I (31) boot: ESP-IDF 5.1.1 2nd stage bootloader                                 
I (31) boot: compile time Dec  5 2023 23:57:40                                  
I (32) boot: Multicore bootloader                                               
I (36) boot: chip revision: v3.0                                                
I (40) boot.esp32: SPI Speed      : 40MHz                                       
I (44) boot.esp32: SPI Mode       : DIO                                         
I (49) boot.esp32: SPI Flash Size : 4MB                                         
I (53) boot: Enabling RNG early entropy source...                               
I (59) boot: Partition Table:                                                   
I (62) boot: ## Label            Usage          Type ST Offset   Length         
I (70) boot:  0 otadata          OTA data         01 00 00009000 00002000       
I (77) boot:  1 phy_init         RF data          01 01 0000b000 00001000       
I (85) boot:  2 app0             OTA app          00 10 00010000 001c0000       
I (92) boot:  3 app1             OTA app          00 11 001d0000 001c0000       
I (100) boot:  4 nvs              WiFi data        01 02 00390000 0006d000      
I (107) boot: End of partition table                                            
I (112) esp_image: segment 0: paddr=001d0020 vaddr=3f400020 size=33468h (210024p
I (196) esp_image: segment 1: paddr=00203490 vaddr=3ffb0000 size=04c1ch ( 19484d
I (204) esp_image: segment 2: paddr=002080b4 vaddr=40080000 size=07f64h ( 32612d
I (218) esp_image: segment 3: paddr=00210020 vaddr=400d0020 size=f2bd4h (994260p
I (577) esp_image: segment 4: paddr=00302bfc vaddr=40087f64 size=15a04h ( 88580d
I (629) boot: Loaded app from partition at offset 0x1d0000                      
I (629) boot: Disabling RNG early entropy source...                             
I (640) cpu_start: Pro cpu up.                                                  
I (641) cpu_start: Starting app cpu, entry point is 0x4008288c                  
I (625) cpu_start: App cpu up.                                                  
I (657) cpu_start: Pro cpu start user code                                      
I (657) cpu_start: cpu freq: 160000000                                          
I (657) cpu_start: Application information:                                     
I (662) cpu_start: Project name:     shelly-esphome-frysen                      
I (668) cpu_start: App version:      2023.11.6                                  
I (673) cpu_start: Compile time:     Dec  6 2023 16:22:48                       
I (679) cpu_start: ELF file SHA256:  c7decda0617db6a0...                        
I (685) cpu_start: ESP-IDF:          4.4.5                                      
I (690) cpu_start: Min chip rev:     v0.0                                       
I (694) cpu_start: Max chip rev:     v3.99                                      
I (699) cpu_start: Chip rev:         v3.0                                       
                                                                                
assert failed: s_prepare_reserved_regions memory_layout_utils.c:100 (reserved[i)
                                                                                
                                                                                
Backtrace: 0x400831da:0x3ffe3390 0x40091d41:0x3ffe33b0 0x40097dd1:0x3ffe33d0 0xD

and it just keeps going and going. Shorting GPI0 gives varying results. Sometimes I do get into “download mode” but any flashing attempt (using esphome CLI) errors out with “A fatal error occurred: Failed to connect to ESP32: No serial data received.” or “A fatal error occurred: Failed to connect to ESP32: Invalid head of packet (0xFF): Possible serial noise or corruption.”

At some point I managed to get it to upload like 5 % but then it errored out.

Weird thing is that using minicom I can keep it running for a long time without any issues at all so I am recieving data in normal boot mode (as far a it gets anyway).

I’ve tried various baud settings but no go.

If anyone wants me to pack it up in an envelope and send it somewhere for further study, please let me know.

Anyone seen how to access the stock firmware? I can only find links to the older plugs.

Would you mind checking if you plugs are the v1 or v2 version? I beleve the v1 has a philips screw in the earthpin hole in the bottom, v2 seems to be sealed with no screw.

(I’m running v1)

Apparently mine is v1 since it has that screw. Really thought I had a v2, but look at that

I’m going to give mine one last chance of life. I’m going to head over to Kjell and get some real breadboard cables, maybe even some cables to solder if it keeps giving me problems.

My USB-TTL is one of these: CP2102 USB to TTL Konverter HW-598 for 3.3V and 5V with jumper cable

I’m probably going to give it a try on a Windows machine if my Ubuntu machine keeps failing (just to be on the safe side).

I’m looking at getting some real breadboard cables.
What are you using for grounding GPI0? I used a needle connected to ground and watched what happened in minicom as I did so.

Could you give some hints on what other equipment you have that you’re using?
And if you find the energy a step by step of your procedure so I can mimic it as much as possible.

Thank you everyone for your patience!

Just to be clear, GPIO0 needs to be grounded while you reset it. Just connecting it to ground while it’s running won’t do anything. You can then leave it grounded while flashing or release it, it doesn’t matter.

Here’s what I do:

  • Run esphome run foo.yaml. Watch it fail to connect, then take a copy of the esptool command line that it gives you, and replace the --before option with --before no_reset
  • Connect up the USB adapter with GPIO0 grounded.
  • Start minicom/miniterm
  • Disconnect and re-connect 3.3V
  • Download mode banner should appear in minicom.
  • Disconnect minicom
  • Run the modified esptool command from step 1.

It doesn’t look like that USB adapter can auto reset the device, which is why you need to reset it yourself, and then use the no_reset option.

If you can’t get it to flash a voltlog did the job for me, just connect EN to gpio0.

It’s a bit pricey but we’ll worth it if you can save your Shelly plugs.

1 Like

Answering my own question:

Plug Plus S stock fw can be found here:

https://shelly-infra1-eu.shelly.cloud/stable-firmware/PlusPlugS/fw-signed.zip

And reflashed with MOS tool (Mongoose OS Documentation)

1 Like

On the high temperature topic. I’ve now checked my plug with stock fw and it’s still at aprox 60c with no load. Very strage that you get so much lover temps.

Shelly states that 55-60c should be normal and 95c is the thermal protection limit

Silly question, but are we definitely talking about the same temperature sensor? 60°C sounds about right for the ESP32’s internal temperature sensor, whereas the plug also has a separate NTC which I’d expect to be reading much closer to room temperature. The NTC is probably a good measure of the air temperature inside the plug, so if it’s really at at 60°C I’d expect the plug to feel very hot to the touch.

My Shelly Plus UK is currently reading 60.3°C chip temperature, and 23.7°C NTC.

1 Like

After a bit (hours…) of back and forth, changing cables, tweaking the setup etc. I got it working. It’s alive!

My main mistake the other day was thinking that which ground to touch during boot would not matter. Now that I have some more cables I could test a bit more and bridged GPIO0 with the correct ground.

Moreover minicom did not display anything after I managed to get it into flash/download mode. So I took a chance in ESPHome and it worked (using the modification you mentioned). Flashed with the alternative script first, everything lit up and minicom actually did output real data luckily. The log feedback in esphome did not give anything though but after a reboot minicom displayed output.

I then did flash it again using the regular esphome command without issues. And then once again using OTA for good measure. All successful!

Thanks again for the pointers and making me rethink some of the assumptions I made.

1 Like

~~I added this conf on my last flash. The Shelly reports that Bluetooth proxy is active when I check logs using ESPHome in HA.

However HA does not pick up any BT proxies. Did you have to do any extra configuration for it to be picked up as a proxy?~~

Nvm… I misunderstood how it works. Was expecting a BT Proxy device to appear in HA. But it just works. Tested with my (very few) Plejd devices that are BT-controlled and it picked them up and was able to control the ones in range as well.

BLE Proxies doesn’t show in HA, but you should start to see the BLE devices beeing auto discovered.

Make sure that you have the ‘active’ flag set in yaml:

esp32_ble_tracker:

bluetooth_proxy:
  active: true  

Not a silly question and I doublechecked if I have been chasing the wrong sensor, but from what I can tell it’s the NTC.

If running the default esphome config for the plug (Shelly Plus Plug S | devices.esphome.io) an from what I can tell it the NTC termisistor on GPIO33 the puts out the value.

I flashed one of my plugs back to stock fw and ran the status command (http://x.x.x.x/rpc/Shelly.GetStatus) and it still outputs a high temp when idle:

“temperature”:{“tC”:63.1, “tF”:145.5}}

I guess the next question is whether it’s really running at 60°C or if there’s a problem with the sensor/configuration. Is it hot if you touch the case of the plug? If you turn it off for an hour or so, what temperature does it read immediately after turning it back on?

Not sure if you’ve got a plug that can be easily opened, but if so I’d try and find the NTC and check its resistance at room temperature, and also the resistance of the other resistor in the divider.

Also, might be interesting to know what the ESP32 temperature is. Just add:

sensor:
  - platform: internal_temperature
    name: "Internal Temperature"
1 Like

Temperature can be also higher if power consumption is big. Device measures AC current through a shunt resistor and when currents are big it can become warm/hot.
But, as said above, esphome by default has no wifi saving enabled (always runs at max), thus bigger temperature, which can be changed. I’d guess that original shelly do have some sort of wifi management…

I get link error:

Compiling .pioenvs/shelly-plus-plug-s-3/src/main.o
Linking .pioenvs/shelly-plus-plug-s-3/firmware.elf
.platformio/packages/toolchain-xtensa-esp32/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld: .pioenvs/shelly-plus-plug-s-3/src/main.o:(.literal._Z5setupv+0x2c0): undefined reference to `vtable for esphome::internal_temperature::InternalTemperatureSensor'
.platformio/packages/toolchain-xtensa-esp32/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld: .pioenvs/shelly-plus-plug-s-3/src/main.o:(.literal._Z5setupv+0x2c4): undefined reference to `vtable for esphome::internal_temperature::InternalTemperatureSensor'
collect2: error: ld returned 1 exit status

Edit: Now I am trying to compile dev branch (it compiled succesfully), but I am getting:

Failed config

ota: [source <unicode string>:34]
  password: my_ota_password
  
  [unprotected_writes] is an invalid option for [ota]. Please check the indentation.
  unprotected_writes: True

IIRC unprotected_writes was needed (there is info in this thread). I now commented it out and it compiled successfully, but will I be able to use OTA in the future after I flash this build?

I think the link error probably just needed the build files cleaning.

I am pretty certain that unprotected_writes is only needed to do the repartitioning when you first switch to esphome. Once that’s done, it’s no longer needed.

1 Like