Add an ESPHome gateway config for the ESP32-S3-Relay-6CH
The USB-RS485 bridge lets a PC drive a segment from tools/wiredsensor.py, which is a bench arrangement: it needs a host awake and attached. A Waveshare ESP32-S3-Relay-6CH does the same master role permanently and brings six relays of its own, so one board both reads the node and switches things in response. Every pin is fixed by the board and was read off its schematic, so they are written literally rather than as substitutions — a substitution would imply a choice that does not exist. Relays sit on GPIO1/2/41/42/45/46, the WS2812 on GPIO38, the buzzer on GPIO21 and RS485 on GPIO17/18. Two of those deserve their comments. GPIO45 and GPIO46 are strapping pins, and GPIO45 selects the flash supply voltage: the internal pulldown gives 3.3V at reset and the pin is released back to it even with the relay energised, but anything external holding it high across a reset selects 1.8V and the module will not boot at all. The strapping warnings from `esphome config` are left unsuppressed for that reason — channels 5 and 6 can also click during reset, so the warning is not crying wolf. The `modbus:` component sets no flow_control_pin, unlike the other configs here. This board derives the SP485EE's driver-enable from the TX line with a 74HC04D, a 54K/1nF RC and a diode, so there is no DE GPIO to name; stated in a comment because an absent option reads as an oversight. flash_size is 16MB, read off the chip with `esptool flash-id` rather than taken from the schematic, which names an ESP32-S3-WROOM-1U with no memory suffix. The logger goes to USB_SERIAL_JTAG because the Type-C is wired straight to native USB with no bridge chip, and the default hardware UART would log to pins this board does not break out. The node's entities are inline rather than from wiredsensor-node.yaml, which creates its own modbus_controller and would leave two of them polling address 0x01. Verified on hardware: the gateway reports 23.624 C and 43.35 %RH with zero bus CRC errors while the node's own USB diagnostics independently show 23.72 C and 43.04 %RH over the same window, drifting in step. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,2 +1,10 @@
|
||||
/target
|
||||
__pycache__/
|
||||
|
||||
# ESPHome. Unanchored so they hold anywhere in the tree, not just next to the
|
||||
# configs: secrets.yaml carries wifi credentials and the API/OTA keys, and
|
||||
# .esphome is build state (including .pioenvs). examples/esphome/.gitignore says
|
||||
# the same thing directory-locally because ESPHome regenerates it on every run;
|
||||
# these entries are the ones that matter if a config is ever built elsewhere.
|
||||
secrets.yaml
|
||||
.esphome/
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# Gitignore settings for ESPHome
|
||||
# This is an example and may include too much for your use-case.
|
||||
# You can modify this file to suit your needs.
|
||||
/.esphome/
|
||||
/secrets.yaml
|
||||
@@ -0,0 +1,287 @@
|
||||
# The Waveshare ESP32-S3-Relay-6CH as a permanent RS485 gateway: it polls the
|
||||
# wiredsensor node over Modbus RTU and exposes its own six relays, buzzer and
|
||||
# WS2812 status LED.
|
||||
#
|
||||
# esphome run relay-6ch-gateway.yaml
|
||||
#
|
||||
# This board takes the USB-RS485 bridge's place on the segment. The bridge exists
|
||||
# so a PC can drive the bus from tools/wiredsensor.py; a gateway is the same
|
||||
# master role made permanent, so the two are alternatives rather than a stack —
|
||||
# only one device may drive the segment at a time.
|
||||
#
|
||||
# Every pin below is fixed by the board and was read off its schematic
|
||||
# (files.waveshare.com/wiki/ESP32-S3-Relay-6CH/ESP32-S3-Relay-6CH.pdf). They are
|
||||
# not configurable, so they are written literally rather than as substitutions —
|
||||
# a substitution would imply a choice that does not exist.
|
||||
|
||||
substitutions:
|
||||
name: relay-6ch-gateway
|
||||
friendly_name: "Relay gateway"
|
||||
|
||||
# Must match UNIT_ADDRESS in firmware/src/board.rs. A mismatch presents as
|
||||
# total silence, indistinguishable from a wiring fault.
|
||||
node_address: "0x01"
|
||||
|
||||
# Must match BAUD_RATE in core/src/timing.rs. A mismatch is silent and shows up
|
||||
# as random CRC failures rather than as an error.
|
||||
baud_rate: "19200"
|
||||
|
||||
esphome:
|
||||
name: ${name}
|
||||
friendly_name: ${friendly_name}
|
||||
|
||||
esp32:
|
||||
board: esp32-s3-devkitc-1
|
||||
framework:
|
||||
type: esp-idf
|
||||
# Read off the chip with `esptool flash-id`, not taken from the schematic —
|
||||
# which names an ESP32-S3-WROOM-1U with no memory suffix. The part fitted here
|
||||
# is 16MB (winbond c8/4018), well above ESPHome's 4MB default; leaving the
|
||||
# default would work but would strand three quarters of the flash and cap the
|
||||
# OTA partition.
|
||||
flash_size: 16MB
|
||||
|
||||
logger:
|
||||
level: INFO
|
||||
# The Type-C connector is wired straight to the ESP32-S3's native USB (GPIO19
|
||||
# and GPIO20, the fixed D-/D+ pins), and there is no UART bridge chip. Logs
|
||||
# therefore have to go over USB-Serial-JTAG; the default hardware UART would
|
||||
# send them to pins this board does not break out, and the log would appear
|
||||
# to be silently broken.
|
||||
hardware_uart: USB_SERIAL_JTAG
|
||||
|
||||
api:
|
||||
encryption:
|
||||
key: !secret api_encryption_key
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
password: !secret ota_password
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
ap: {}
|
||||
# The WROOM-1U is the external-antenna variant: it has a U.FL connector and no
|
||||
# PCB antenna. Without an antenna fitted it will associate from a metre away
|
||||
# and nowhere else, which reads as a flaky access point rather than as missing
|
||||
# hardware.
|
||||
|
||||
captive_portal:
|
||||
|
||||
# ---------------------------------------------------------------- the bus ---
|
||||
|
||||
uart:
|
||||
id: rs485
|
||||
tx_pin: GPIO17
|
||||
rx_pin: GPIO18
|
||||
baud_rate: ${baud_rate}
|
||||
data_bits: 8
|
||||
parity: NONE
|
||||
stop_bits: 1
|
||||
|
||||
modbus:
|
||||
id: rs485_bus
|
||||
uart_id: rs485
|
||||
# Deliberately no `flow_control_pin`. This board derives the SP485EE's
|
||||
# driver-enable from the TX line itself — a 74HC04D inverter with a 54K/1nF RC
|
||||
# and a diode — so DE asserts on the first start bit and releases an RC delay
|
||||
# after the last stop bit. There is no DE GPIO to name. Stated explicitly
|
||||
# because the other configs here do set one, and its absence is the single
|
||||
# thing that differs about this board.
|
||||
|
||||
modbus_controller:
|
||||
- id: wiredsensor
|
||||
address: ${node_address}
|
||||
modbus_id: rs485_bus
|
||||
# The node caches a reading every second and reports its age, so polling
|
||||
# faster than that gains nothing.
|
||||
update_interval: 30s
|
||||
|
||||
# --------------------------------------------------------------- the node ---
|
||||
#
|
||||
# The node's entities are defined inline rather than pulled from
|
||||
# wiredsensor-node.yaml. That package creates its own `modbus_controller`, so
|
||||
# including it here would leave two controllers polling address 0x01 — hence the
|
||||
# duplication, which is the single-node tradeoff wiredsensor.yaml makes too. Use
|
||||
# the package instead of this section only if you move to several nodes, and drop
|
||||
# the `modbus_controller` above when you do.
|
||||
#
|
||||
# This is a curated subset. For the full diagnostic set — every error counter,
|
||||
# all seven flag bits, both serials — copy the `sensor:`, `binary_sensor:` and
|
||||
# `text_sensor:` blocks out of wiredsensor.yaml; the register map is identical
|
||||
# and they attach to the `wiredsensor` controller above unchanged.
|
||||
|
||||
sensor:
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: wiredsensor
|
||||
name: "Temperature"
|
||||
register_type: read
|
||||
address: 0x0000
|
||||
value_type: S_DWORD
|
||||
unit_of_measurement: "°C"
|
||||
device_class: temperature
|
||||
state_class: measurement
|
||||
accuracy_decimals: 2
|
||||
filters:
|
||||
- multiply: 0.001
|
||||
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: wiredsensor
|
||||
name: "Humidity"
|
||||
register_type: read
|
||||
address: 0x0002
|
||||
value_type: S_DWORD
|
||||
unit_of_measurement: "%"
|
||||
device_class: humidity
|
||||
state_class: measurement
|
||||
accuracy_decimals: 2
|
||||
filters:
|
||||
- multiply: 0.001
|
||||
|
||||
# Bus health. A segment with a termination or biasing problem shows up as a
|
||||
# steadily climbing CRC count long before a reading is actually lost, which is
|
||||
# the whole reason to graph it from the gateway.
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: wiredsensor
|
||||
name: "Bus CRC errors"
|
||||
register_type: holding
|
||||
address: 0x000A
|
||||
value_type: U_WORD
|
||||
state_class: total_increasing
|
||||
entity_category: diagnostic
|
||||
|
||||
# ---- the gateway's own health ----
|
||||
- platform: uptime
|
||||
name: "Gateway uptime"
|
||||
entity_category: diagnostic
|
||||
|
||||
- platform: wifi_signal
|
||||
name: "Gateway WiFi signal"
|
||||
update_interval: 60s
|
||||
entity_category: diagnostic
|
||||
|
||||
binary_sensor:
|
||||
# On `holding` (0x03) rather than `read` (0x04) deliberately. A read
|
||||
# overlapping 0x0000..0x0004 is refused outright when the sensor has never
|
||||
# produced a reading, and ESPHome merges adjacent same-type registers into one
|
||||
# command — so asking for the flags as `read` would let a dead sensor take down
|
||||
# the very diagnostic that explains it. The node serves both function codes
|
||||
# from one table precisely so there is a way out.
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: wiredsensor
|
||||
name: "Sensor fault"
|
||||
register_type: holding
|
||||
address: 0x0005
|
||||
bitmask: 0x04
|
||||
device_class: problem
|
||||
entity_category: diagnostic
|
||||
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: wiredsensor
|
||||
name: "Data stale"
|
||||
register_type: holding
|
||||
address: 0x0005
|
||||
bitmask: 0x02
|
||||
entity_category: diagnostic
|
||||
|
||||
# -------------------------------------------------------------- the relays ---
|
||||
#
|
||||
# Six independent SPDT relays, each driven from the module pin through a 0R link
|
||||
# resistor. Active high: the GPIO drives the coil transistor, so ON energises the
|
||||
# coil and closes NO.
|
||||
#
|
||||
# `restore_mode: ALWAYS_OFF` throughout rather than a restoring mode. These are
|
||||
# mains-side contacts; coming back from a power cut into whatever state preceded
|
||||
# it is a decision worth making per relay, not inheriting by default.
|
||||
|
||||
switch:
|
||||
- platform: gpio
|
||||
name: "Relay 1"
|
||||
id: relay_1
|
||||
pin: GPIO1
|
||||
restore_mode: ALWAYS_OFF
|
||||
|
||||
- platform: gpio
|
||||
name: "Relay 2"
|
||||
id: relay_2
|
||||
pin: GPIO2
|
||||
restore_mode: ALWAYS_OFF
|
||||
|
||||
- platform: gpio
|
||||
name: "Relay 3"
|
||||
id: relay_3
|
||||
pin: GPIO41
|
||||
restore_mode: ALWAYS_OFF
|
||||
|
||||
- platform: gpio
|
||||
name: "Relay 4"
|
||||
id: relay_4
|
||||
pin: GPIO42
|
||||
restore_mode: ALWAYS_OFF
|
||||
|
||||
# GPIO45 and GPIO46 are ESP32-S3 strapping pins — VDD_SPI voltage select and
|
||||
# boot mode respectively, both sampled at reset with internal pulldowns. They
|
||||
# are ordinary outputs once booted, so driving relays from them is fine, but
|
||||
# channels 5 and 6 may click during reset before the firmware takes over. Do
|
||||
# not put anything on them where a momentary close at power-up matters.
|
||||
#
|
||||
# GPIO45 is the sharper of the two: it selects the flash supply voltage, and
|
||||
# this module's flash is 3.3V. The internal pulldown gives the right answer at
|
||||
# reset (esptool confirms "flash voltage set by a strapping pin: 3.3V"), and the
|
||||
# pin is released back to it while the CPU is in reset even if the relay was
|
||||
# energised. But anything external that holds GPIO45 high across a reset — a
|
||||
# bench pullup, a scope probe, a wiring error on CH5 — selects 1.8V and the
|
||||
# module will not boot at all.
|
||||
- platform: gpio
|
||||
name: "Relay 5"
|
||||
id: relay_5
|
||||
pin: GPIO45
|
||||
restore_mode: ALWAYS_OFF
|
||||
|
||||
- platform: gpio
|
||||
name: "Relay 6"
|
||||
id: relay_6
|
||||
pin: GPIO46
|
||||
restore_mode: ALWAYS_OFF
|
||||
|
||||
# ------------------------------------------------------- indicator and buzzer ---
|
||||
|
||||
light:
|
||||
- platform: esp32_rmt_led_strip
|
||||
id: status_led
|
||||
name: "Status LED"
|
||||
pin: GPIO38
|
||||
num_leds: 1
|
||||
chipset: WS2812
|
||||
rgb_order: GRB
|
||||
default_transition_length: 0s
|
||||
restore_mode: ALWAYS_OFF
|
||||
|
||||
output:
|
||||
# Driven through LEDC because the buzzer is passive and needs a tone rather
|
||||
# than a level. If yours is an active buzzer, this still works — any non-zero
|
||||
# level sounds it — but `rtttl` melodies will all sound the same pitch.
|
||||
- platform: ledc
|
||||
id: buzzer_out
|
||||
pin: GPIO21
|
||||
|
||||
rtttl:
|
||||
id: buzzer
|
||||
output: buzzer_out
|
||||
|
||||
button:
|
||||
# Bench check for the buzzer and the LED, so a freshly flashed board can be
|
||||
# confirmed without touching the mains side or the bus.
|
||||
- platform: template
|
||||
name: "Self test"
|
||||
entity_category: diagnostic
|
||||
on_press:
|
||||
- light.turn_on:
|
||||
id: status_led
|
||||
red: 0%
|
||||
green: 100%
|
||||
blue: 0%
|
||||
- rtttl.play: "test:d=16,o=6,b=140:c,e,g"
|
||||
- delay: 1s
|
||||
- light.turn_off: status_led
|
||||
Reference in New Issue
Block a user