Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 152 additions & 0 deletions src/docs/devices/Sonoff-THR320/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
---
title: Sonoff THR320
date-published: 2026-02-19
type: relay
standard: global
board: esp32
difficulty: 3
---

## Bootloop Workaround

Some people experience a boot loop when trying to flash esphome directly.
Here's a workaround:
[https://community.home-assistant.io/t/bootloop-workaround-for-flashing-sonoff-th-elite-thr316d-thr320d-and-maybe-others-with-esphome-for-the-first-time/498868](https://community.home-assistant.io/t/bootloop-workaround-for-flashing-sonoff-th-elite-thr316d-thr320d-and-maybe-others-with-esphome-for-the-first-time/498868)

## GPIO Pinout

(Source: [https://templates.blakadder.com/sonoff_THR320D.html](https://templates.blakadder.com/sonoff_THR320D.html))
Some GPIO are active-low, meaning they're "on" when they're pulled low. In ESPHome that's often called "inverted".
The relays GPIO are active-high.

The main relay is bistable/latching, meaning a pulse on pin 1 switches the
relay ON, and a pulse on pin 2 switches the relay OFF.
These two pins should never be active at the same time, or the device will become dangerously hot in a few minutes.

Note that until Feb 2026 there was an error in this page causing a safety issue:
The code was considering the relays GPIO as being active-low, when they are actually active-high. So the two main relay
pins were stay simultaneously active most of the time, making the device dangerously hot.
If you copied the old version of the code from here, please update your devices as soon as possible.

| Pin | Function |
| ------ | -------------------------------------------------------- |
| GPIO0 | Push Button (HIGH = off, LOW = on) |
| GPIO19 | Large/Main Relay pin 1, pull high briefly for relay OFF |
| GPIO22 | Large/Main Relay pin 2, pull high briefly for relay ON |
| GPIO16 | Left LED (Red) |
| GPIO15 | Middle LED (Blue) |
| GPIO13 | Right LED (Green) |

## Basic Configuration

Internal momentary switches are used to pulse the ON/OFF pins on the main relay.
A template switch is used to hide the complexity of controlling the two internal
momentary switches.

One shortcoming here is we don't have any way to confirm the true state of the
main relay, and so there is a possibility that our main relay switch could get out
of sync with the true state of the relay. It is advised to force the relay to a
known state on power up, rather than leave it in an unknown state until some
switching operation is performed.

```yaml

esp32:
board: nodemcu-32s
framework:
type: esp-idf

binary_sensor:
# single button that also puts device into flash mode when held on boot
- platform: gpio
pin:
number: GPIO0
mode: INPUT_PULLUP
inverted: True
ignore_strapping_warning: true
id: button_
filters:
- delayed_on_off: 50ms

switch:
- platform: hbridge
id: main_relay
on_pin:
number: GPIO22
off_pin:
number: GPIO19
pulse_length: 100ms
wait_time: 100ms

output:
- platform: ledc
id: red_led_output
pin:
number: GPIO16
inverted: true

- platform: ledc
id: green_led_output
pin:
number: GPIO13
inverted: true

# This is needed to power the external sensor.
# It receives 3v3 from this pin, which should be activated appropriately.
- platform: gpio
pin: GPIO27
id: sensor_power

# The middle (blue) LED is used as wifi status indicator.
status_led:
pin:
number: GPIO15
inverted: true
ignore_strapping_warning: true

light:
# Leftmost (red) LED that's used to indicate the relay being on/off
- platform: binary
id: red_led
output: red_led_output

# Rightmost (green) LED
- platform: binary
id: green_led
output: green_led_output

```

The THR320 can be used with either a 1-wire bus, or else using a
uart-based sensor like the WTS01.

1-wire:

```yaml

one_wire:
platform: gpio
pin: GPIO25

```

Then you can add `sensor: platform: dallas_temp` entities as appropriate, or whatever other 1-wire devices you choose.

WTS01:

```yaml

# You need to have a UART bus setup in your configuration
uart:
- id: sensor_uart
rx_pin: GPIO25
baud_rate: 9600

# Then you can add the WTS01 sensor
sensor:
- platform: wts01
id: wts01_sensor
uart_id: sensor_uart

```

241 changes: 0 additions & 241 deletions src/docs/devices/Sonoff-THR320D/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,244 +234,3 @@ text_sensor:
name: "${friendly_name} IP Address"
disabled_by_default: true
```

Here is an alternative configuration, set up to control a geyser, with an
ATTiny85 acting as a DS18B20 1-wire probe, using OneWireHub. The intent is
to use excess solar power to heat the geyser in Boost mode, revert to Eco
overnight, and default to Home in case there is no external controller.

```yaml
substitutions:
name: "geyser"
friendly_name: "Geyser Thermostat"
project_name: "thermostats"
project_version: "1.0"

packages:
# contains basic setup, WiFi, etc
common: !include .common.yaml

esphome:
name: "${name}"
friendly_name: "${friendly_name}"
on_boot:
- priority: 90
then:
# supply the external sensor with 3v power by pulling this GPIO high
- output.turn_on: sensor_power
# make sure the relay is in a known state at startup
- switch.turn_off: main_relay
# Default to running the geyser in Home mode
- climate.control:
id: geyser_climate
preset: "Home"

esp32:
variant: esp32

logger:
# It's in the ceiling, nobody is listening to the UART
baud_rate: 0
level: DEBUG

web_server:
port: 80

captive_portal:

binary_sensor:
# single main button that also puts device into flash mode when held on boot
# For someone in the ceiling, this can be used to turn the climate control
# into OFF or HEAT modes. It does NOT directly control the relay.
- platform: gpio
pin:
number: GPIO0
mode: INPUT_PULLUP
inverted: True
id: button0
filters:
- delayed_on_off: 50ms
on_press:
then:
- if:
condition:
lambda: |-
return id(geyser_climate).mode != CLIMATE_MODE_OFF;
then:
- logger.log: "Button deactivates climate control"
- climate.control:
id: geyser_climate
mode: "OFF"
else:
- logger.log: "Button activates climate control"
- climate.control:
id: geyser_climate
mode: "HEAT"

switch:
# template switch to represent the main relay
# this is synchronised with the RED LED
# Note: this is controlled by the climate entity, and is not exposed
# for direct manipulation, otherwise it could be left on permanently
- platform: template
id: main_relay
turn_on_action:
- button.press: main_relay_on
- light.turn_on: onoff_led
turn_off_action:
- button.press: main_relay_off
- light.turn_off: onoff_led
assumed_state: True
optimistic: True
restore_state: True

output:
# Ideally, these two relay GPIOs should be interlocked to prevent
# simultaneous operation. ESPHome currently does not support
# interlocks at an output: level, or even at a button: level
# BE CAREFUL!
- platform: gpio
id: main_relay_on_output
pin:
number: GPIO19

- platform: gpio
id: main_relay_off_output
pin:
number: GPIO22

- platform: ledc
id: red_led_output
pin:
number: GPIO16
inverted: true

- platform: ledc
id: green_led_output
pin:
number: GPIO13
inverted: true

# This is needed to power the external sensor.
# It receives 3v3 from this pin, which is pulled up on boot.
- platform: gpio
pin: GPIO27
id: sensor_power

button:
# See note above about interlocks!
- platform: output
id: main_relay_on
output: main_relay_on_output
duration: 100ms

- platform: output
id: main_relay_off
output: main_relay_off_output
duration: 100ms

# The middle (blue) LED is used as wifi status indicator.
status_led:
pin:
number: GPIO15
inverted: true

light:
# Leftmost (red) LED that's used to indicate the relay being on/off
- platform: binary
id: onoff_led
output: red_led_output
internal: true

# Rightmost (green) LED used to indicate climate control being active
- platform: binary
id: auto_led
output: green_led_output
internal: true

sensor:
# Geyser temperature
# Has some failsafes to disable climate control if the temperature
# being reported is unreasonable. Below 10C suggests that the ATTiny85
# is either not connected to the thermistor, or is otherwise reporting
# incorrect values, and should be investigated.
#
# NOTE: This can be overridden, but care should be taken when doing so
# because these only apply when the temperature ENTERS these ranges
# If it REMAINS in the range, and climate is turned on manually, these
# failsafes will not apply!
- platform: dallas_temp
address: 0x1e11223344550028
id: temp
name: "Temperature"
on_value_range:
- below: 10.0
then:
- logger.log: "Temperature too low, disabling climate!"
- climate.control:
id: geyser_climate
mode: "OFF"
- above: 70.0
then:
- logger.log: "Temperature too high, disabling climate!"
- climate.control:
id: geyser_climate
mode: "OFF"

# The THR320 appears to run quite hot, let's just keep an eye on it
- platform: internal_temperature
name: "Internal Temperature"

climate:
- platform: thermostat
id: geyser_climate
name: "Climate"
sensor: temp
visual:
min_temperature: 45C
max_temperature: 70C
temperature_step:
target_temperature: 1
current_temperature: 1
default_preset: Home
preset:
- name: Home
default_target_temperature_low: 55C
mode: heat
- name: Boost
default_target_temperature_low: 65C
mode: heat
- name: Eco
default_target_temperature_low: 45C
mode: heat
min_heating_off_time: 0s
min_heating_run_time: 60s
min_idle_time: 30s
heat_action:
- switch.turn_on: main_relay
idle_action:
- switch.turn_off: main_relay
heat_deadband: 2 # how many degrees can we go under the temp before starting to heat
heat_overrun: 0.5 # how many degrees can we go over the temp before stopping
off_mode:
- switch.turn_off: main_relay
on_state:
- if:
condition:
lambda: |-
return id(geyser_climate).mode == CLIMATE_MODE_OFF;
then:
- logger.log: "Climate control OFF"
- light.turn_off: auto_led
- if:
condition:
lambda: |-
return id(geyser_climate).mode == CLIMATE_MODE_HEAT;
then:
- logger.log: "Climate control ON"
- light.turn_on: auto_led

one_wire:
pin: GPIO25
update_interval: 10s
```