Skip to content

Commit 6b60882

Browse files
committed
Document Sonoff THR320 separately
There appear to be differences in pin assignments between the THR320 and the THR320D.
1 parent d5c6709 commit 6b60882

2 files changed

Lines changed: 152 additions & 241 deletions

File tree

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
---
2+
title: Sonoff THR320
3+
date-published: 2026-02-19
4+
type: relay
5+
standard: global
6+
board: esp32
7+
difficulty: 3
8+
---
9+
10+
## Bootloop Workaround
11+
12+
Some people experience a boot loop when trying to flash esphome directly.
13+
Here's a workaround:
14+
[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)
15+
16+
## GPIO Pinout
17+
18+
(Source: [https://templates.blakadder.com/sonoff_THR320D.html](https://templates.blakadder.com/sonoff_THR320D.html))
19+
Some GPIO are active-low, meaning they're "on" when they're pulled low. In ESPHome that's often called "inverted".
20+
The relays GPIO are active-high.
21+
22+
The main relay is bistable/latching, meaning a pulse on pin 1 switches the
23+
relay ON, and a pulse on pin 2 switches the relay OFF.
24+
These two pins should never be active at the same time, or the device will become dangerously hot in a few minutes.
25+
26+
Note that until Feb 2026 there was an error in this page causing a safety issue:
27+
The code was considering the relays GPIO as being active-low, when they are actually active-high. So the two main relay
28+
pins were stay simultaneously active most of the time, making the device dangerously hot.
29+
If you copied the old version of the code from here, please update your devices as soon as possible.
30+
31+
| Pin | Function |
32+
| ------ | -------------------------------------------------------- |
33+
| GPIO0 | Push Button (HIGH = off, LOW = on) |
34+
| GPIO19 | Large/Main Relay pin 1, pull high briefly for relay OFF |
35+
| GPIO22 | Large/Main Relay pin 2, pull high briefly for relay ON |
36+
| GPIO16 | Left LED (Red) |
37+
| GPIO15 | Middle LED (Blue) |
38+
| GPIO13 | Right LED (Green) |
39+
40+
## Basic Configuration
41+
42+
Internal momentary switches are used to pulse the ON/OFF pins on the main relay.
43+
A template switch is used to hide the complexity of controlling the two internal
44+
momentary switches.
45+
46+
One shortcoming here is we don't have any way to confirm the true state of the
47+
main relay, and so there is a possibility that our main relay switch could get out
48+
of sync with the true state of the relay. It is advised to force the relay to a
49+
known state on power up, rather than leave it in an unknown state until some
50+
switching operation is performed.
51+
52+
```yaml
53+
54+
esp32:
55+
board: nodemcu-32s
56+
framework:
57+
type: esp-idf
58+
59+
binary_sensor:
60+
# single button that also puts device into flash mode when held on boot
61+
- platform: gpio
62+
pin:
63+
number: GPIO0
64+
mode: INPUT_PULLUP
65+
inverted: True
66+
ignore_strapping_warning: true
67+
id: button_
68+
filters:
69+
- delayed_on_off: 50ms
70+
71+
switch:
72+
- platform: hbridge
73+
id: main_relay
74+
on_pin:
75+
number: GPIO22
76+
off_pin:
77+
number: GPIO19
78+
pulse_length: 100ms
79+
wait_time: 100ms
80+
81+
output:
82+
- platform: ledc
83+
id: red_led_output
84+
pin:
85+
number: GPIO16
86+
inverted: true
87+
88+
- platform: ledc
89+
id: green_led_output
90+
pin:
91+
number: GPIO13
92+
inverted: true
93+
94+
# This is needed to power the external sensor.
95+
# It receives 3v3 from this pin, which should be activated appropriately.
96+
- platform: gpio
97+
pin: GPIO27
98+
id: sensor_power
99+
100+
# The middle (blue) LED is used as wifi status indicator.
101+
status_led:
102+
pin:
103+
number: GPIO15
104+
inverted: true
105+
ignore_strapping_warning: true
106+
107+
light:
108+
# Leftmost (red) LED that's used to indicate the relay being on/off
109+
- platform: binary
110+
id: red_led
111+
output: red_led_output
112+
113+
# Rightmost (green) LED
114+
- platform: binary
115+
id: green_led
116+
output: green_led_output
117+
118+
```
119+
120+
The THR320 can be used with either a 1-wire bus, or else using a
121+
uart-based sensor like the WTS01.
122+
123+
1-wire:
124+
125+
```yaml
126+
127+
one_wire:
128+
platform: gpio
129+
pin: GPIO25
130+
131+
```
132+
133+
Then you can add `sensor: platform: dallas_temp` entities as appropriate, or whatever other 1-wire devices you choose.
134+
135+
WTS01:
136+
137+
```yaml
138+
139+
# You need to have a UART bus setup in your configuration
140+
uart:
141+
- id: sensor_uart
142+
rx_pin: GPIO25
143+
baud_rate: 9600
144+
145+
# Then you can add the WTS01 sensor
146+
sensor:
147+
- platform: wts01
148+
id: wts01_sensor
149+
uart_id: sensor_uart
150+
151+
```
152+

src/docs/devices/Sonoff-THR320D/index.md

Lines changed: 0 additions & 241 deletions
Original file line numberDiff line numberDiff line change
@@ -234,244 +234,3 @@ text_sensor:
234234
name: "${friendly_name} IP Address"
235235
disabled_by_default: true
236236
```
237-
238-
Here is an alternative configuration, set up to control a geyser, with an
239-
ATTiny85 acting as a DS18B20 1-wire probe, using OneWireHub. The intent is
240-
to use excess solar power to heat the geyser in Boost mode, revert to Eco
241-
overnight, and default to Home in case there is no external controller.
242-
243-
```yaml
244-
substitutions:
245-
name: "geyser"
246-
friendly_name: "Geyser Thermostat"
247-
project_name: "thermostats"
248-
project_version: "1.0"
249-
250-
packages:
251-
# contains basic setup, WiFi, etc
252-
common: !include .common.yaml
253-
254-
esphome:
255-
name: "${name}"
256-
friendly_name: "${friendly_name}"
257-
on_boot:
258-
- priority: 90
259-
then:
260-
# supply the external sensor with 3v power by pulling this GPIO high
261-
- output.turn_on: sensor_power
262-
# make sure the relay is in a known state at startup
263-
- switch.turn_off: main_relay
264-
# Default to running the geyser in Home mode
265-
- climate.control:
266-
id: geyser_climate
267-
preset: "Home"
268-
269-
esp32:
270-
variant: esp32
271-
272-
logger:
273-
# It's in the ceiling, nobody is listening to the UART
274-
baud_rate: 0
275-
level: DEBUG
276-
277-
web_server:
278-
port: 80
279-
280-
captive_portal:
281-
282-
binary_sensor:
283-
# single main button that also puts device into flash mode when held on boot
284-
# For someone in the ceiling, this can be used to turn the climate control
285-
# into OFF or HEAT modes. It does NOT directly control the relay.
286-
- platform: gpio
287-
pin:
288-
number: GPIO0
289-
mode: INPUT_PULLUP
290-
inverted: True
291-
id: button0
292-
filters:
293-
- delayed_on_off: 50ms
294-
on_press:
295-
then:
296-
- if:
297-
condition:
298-
lambda: |-
299-
return id(geyser_climate).mode != CLIMATE_MODE_OFF;
300-
then:
301-
- logger.log: "Button deactivates climate control"
302-
- climate.control:
303-
id: geyser_climate
304-
mode: "OFF"
305-
else:
306-
- logger.log: "Button activates climate control"
307-
- climate.control:
308-
id: geyser_climate
309-
mode: "HEAT"
310-
311-
switch:
312-
# template switch to represent the main relay
313-
# this is synchronised with the RED LED
314-
# Note: this is controlled by the climate entity, and is not exposed
315-
# for direct manipulation, otherwise it could be left on permanently
316-
- platform: template
317-
id: main_relay
318-
turn_on_action:
319-
- button.press: main_relay_on
320-
- light.turn_on: onoff_led
321-
turn_off_action:
322-
- button.press: main_relay_off
323-
- light.turn_off: onoff_led
324-
assumed_state: True
325-
optimistic: True
326-
restore_state: True
327-
328-
output:
329-
# Ideally, these two relay GPIOs should be interlocked to prevent
330-
# simultaneous operation. ESPHome currently does not support
331-
# interlocks at an output: level, or even at a button: level
332-
# BE CAREFUL!
333-
- platform: gpio
334-
id: main_relay_on_output
335-
pin:
336-
number: GPIO19
337-
338-
- platform: gpio
339-
id: main_relay_off_output
340-
pin:
341-
number: GPIO22
342-
343-
- platform: ledc
344-
id: red_led_output
345-
pin:
346-
number: GPIO16
347-
inverted: true
348-
349-
- platform: ledc
350-
id: green_led_output
351-
pin:
352-
number: GPIO13
353-
inverted: true
354-
355-
# This is needed to power the external sensor.
356-
# It receives 3v3 from this pin, which is pulled up on boot.
357-
- platform: gpio
358-
pin: GPIO27
359-
id: sensor_power
360-
361-
button:
362-
# See note above about interlocks!
363-
- platform: output
364-
id: main_relay_on
365-
output: main_relay_on_output
366-
duration: 100ms
367-
368-
- platform: output
369-
id: main_relay_off
370-
output: main_relay_off_output
371-
duration: 100ms
372-
373-
# The middle (blue) LED is used as wifi status indicator.
374-
status_led:
375-
pin:
376-
number: GPIO15
377-
inverted: true
378-
379-
light:
380-
# Leftmost (red) LED that's used to indicate the relay being on/off
381-
- platform: binary
382-
id: onoff_led
383-
output: red_led_output
384-
internal: true
385-
386-
# Rightmost (green) LED used to indicate climate control being active
387-
- platform: binary
388-
id: auto_led
389-
output: green_led_output
390-
internal: true
391-
392-
sensor:
393-
# Geyser temperature
394-
# Has some failsafes to disable climate control if the temperature
395-
# being reported is unreasonable. Below 10C suggests that the ATTiny85
396-
# is either not connected to the thermistor, or is otherwise reporting
397-
# incorrect values, and should be investigated.
398-
#
399-
# NOTE: This can be overridden, but care should be taken when doing so
400-
# because these only apply when the temperature ENTERS these ranges
401-
# If it REMAINS in the range, and climate is turned on manually, these
402-
# failsafes will not apply!
403-
- platform: dallas_temp
404-
address: 0x1e11223344550028
405-
id: temp
406-
name: "Temperature"
407-
on_value_range:
408-
- below: 10.0
409-
then:
410-
- logger.log: "Temperature too low, disabling climate!"
411-
- climate.control:
412-
id: geyser_climate
413-
mode: "OFF"
414-
- above: 70.0
415-
then:
416-
- logger.log: "Temperature too high, disabling climate!"
417-
- climate.control:
418-
id: geyser_climate
419-
mode: "OFF"
420-
421-
# The THR320 appears to run quite hot, let's just keep an eye on it
422-
- platform: internal_temperature
423-
name: "Internal Temperature"
424-
425-
climate:
426-
- platform: thermostat
427-
id: geyser_climate
428-
name: "Climate"
429-
sensor: temp
430-
visual:
431-
min_temperature: 45C
432-
max_temperature: 70C
433-
temperature_step:
434-
target_temperature: 1
435-
current_temperature: 1
436-
default_preset: Home
437-
preset:
438-
- name: Home
439-
default_target_temperature_low: 55C
440-
mode: heat
441-
- name: Boost
442-
default_target_temperature_low: 65C
443-
mode: heat
444-
- name: Eco
445-
default_target_temperature_low: 45C
446-
mode: heat
447-
min_heating_off_time: 0s
448-
min_heating_run_time: 60s
449-
min_idle_time: 30s
450-
heat_action:
451-
- switch.turn_on: main_relay
452-
idle_action:
453-
- switch.turn_off: main_relay
454-
heat_deadband: 2 # how many degrees can we go under the temp before starting to heat
455-
heat_overrun: 0.5 # how many degrees can we go over the temp before stopping
456-
off_mode:
457-
- switch.turn_off: main_relay
458-
on_state:
459-
- if:
460-
condition:
461-
lambda: |-
462-
return id(geyser_climate).mode == CLIMATE_MODE_OFF;
463-
then:
464-
- logger.log: "Climate control OFF"
465-
- light.turn_off: auto_led
466-
- if:
467-
condition:
468-
lambda: |-
469-
return id(geyser_climate).mode == CLIMATE_MODE_HEAT;
470-
then:
471-
- logger.log: "Climate control ON"
472-
- light.turn_on: auto_led
473-
474-
one_wire:
475-
pin: GPIO25
476-
update_interval: 10s
477-
```

0 commit comments

Comments
 (0)