|
|
@@ -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
|