Files
wiredsensor/examples/esphome/wiredsensor.yaml
T
Oliver Walter c49fff9faf Add ESPHome example configurations
The README carried a fragment, which is enough to show the shape but not
enough to flash. These are complete configs:

  wiredsensor.yaml      one node, every measurement and diagnostic register
  wiredsensor-node.yaml one node as a reusable package, parameterised by address
  bus-of-nodes.yaml     three nodes on one segment, via that package

All three validate against ESPHome 2026.7, and the expanded config was
checked register by register rather than only for schema validity — a
wrong address or value_type validates perfectly and then reports a
plausible but wrong temperature, which is the failure mode worth
guarding against.

Two things the fragment in the README was missing and a real config
cannot be:

flow_control_pin on the modbus component. Without it the ESP32 never
asserts DE, so nothing reaches the segment and the node looks dead. The
generated fragment now carries it too.

The read/holding split for diagnostics. ESPHome merges adjacent registers
of one register_type into a single command, and a read overlapping
0x0000..0x0004 is refused when the sensor has never produced a reading —
so diagnostics merged into the measurement command go unavailable exactly
when they are needed. Asking for them as `holding` puts them in their own
command.

Serials are text_sensors with raw_encode: HEXBYTES rather than numeric
sensors, because Home Assistant stores states as float32 and 24 bits of
mantissa cannot hold a 32-bit serial. Uptime has the same limit and is
left numeric with a note; quantising past 194 days does not matter for
spotting a reboot.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 17:28:08 +02:00

295 lines
8.8 KiB
YAML

# A complete ESPHome configuration for one wiredsensor node on an RS485
# segment. Flashable as-is once the substitutions below and your secrets are
# filled in:
#
# esphome run wiredsensor.yaml
#
# Nothing here is wiredsensor-specific beyond the register addresses — the node
# is an ordinary Modbus RTU server, so `modbus_controller` reads it with no
# custom component and no external library.
#
# For several nodes on one bus, see bus-of-nodes.yaml.
substitutions:
name: wiredsensor-gateway
friendly_name: "Wiredsensor 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"
# RS485 transceiver wiring. GPIO16/17 are UART2's defaults on the ESP32, but
# they are wired to PSRAM on WROVER modules — pick different pins there.
tx_pin: GPIO17
rx_pin: GPIO16
# Driver enable, wired to DE and /RE together. Delete `flow_control_pin`
# below if your transceiver handles direction itself (MAX13487 and most
# "automatic" breakout modules).
de_pin: GPIO4
esphome:
name: ${name}
friendly_name: ${friendly_name}
esp32:
board: esp32dev
framework:
type: esp-idf
logger:
# The RS485 port is UART2, so the log can stay on UART0. Set `baud_rate: 0`
# if you ever move RS485 onto UART0.
level: INFO
api:
encryption:
key: !secret api_encryption_key
ota:
- platform: esphome
password: !secret ota_password
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
ap: {}
captive_portal:
# ---------------------------------------------------------------- the bus ---
uart:
id: rs485
tx_pin: ${tx_pin}
rx_pin: ${rx_pin}
baud_rate: ${baud_rate}
data_bits: 8
parity: NONE
stop_bits: 1
modbus:
id: rs485_bus
uart_id: rs485
# Asserted while transmitting, released afterwards. The node holds its own DE
# until the final stop bit has left the shift register; yours must too, which
# is what this option does.
flow_control_pin: ${de_pin}
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. 30s is plenty for room temperature.
update_interval: 30s
# ------------------------------------------------------------------ sensors ---
#
# Two groups, on two different function codes, and the split is deliberate.
#
# ESPHome merges adjacent registers of the same register_type into one command.
# A read overlapping 0x0000..0x0004 is refused outright when the sensor has
# never produced a reading, so anything merged into the measurement command goes
# unavailable along with it — including, without this split, the very
# diagnostics that would tell you why. The node serves both 0x03 and 0x04 from
# one register table precisely so there is a way out: asking for the
# diagnostics as `holding` puts them in their own command, where a dead sensor
# cannot take them down.
sensor:
# ---- measurements, function code 0x04 (Read Input Registers) -------------
#
# Temperature and humidity are 32-bit signed milli-units spanning two
# registers each, high word first — ESPHome's S_DWORD. Using S_WORD here
# would read half the value and report a plausible but wrong number rather
# than failing, so this is the one field worth double-checking.
- 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
# How old the cached reading was when the node answered. The node reports this
# rather than enforcing a freshness policy of its own, so the decision about
# what staleness matters is yours.
- platform: modbus_controller
modbus_controller_id: wiredsensor
name: "Reading age"
register_type: read
address: 0x0004
value_type: U_WORD
unit_of_measurement: "ms"
state_class: measurement
entity_category: diagnostic
# ---- diagnostics, function code 0x03 (Read Holding Registers) ------------
#
# All counters are monotonic since power-up, so `total_increasing` lets Home
# Assistant show a rate. A segment with a termination or biasing problem shows
# up here as a steadily climbing CRC error count long before any reading is
# actually lost — which is the whole reason to graph them.
- platform: modbus_controller
modbus_controller_id: wiredsensor
name: "I2C errors"
register_type: holding
address: 0x0007
value_type: U_WORD
state_class: total_increasing
entity_category: diagnostic
- platform: modbus_controller
modbus_controller_id: wiredsensor
name: "Sensor CRC errors"
register_type: holding
address: 0x0008
value_type: U_WORD
state_class: total_increasing
entity_category: diagnostic
- platform: modbus_controller
modbus_controller_id: wiredsensor
name: "Bus frame errors"
register_type: holding
address: 0x0009
value_type: U_WORD
state_class: total_increasing
entity_category: diagnostic
- 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 low byte of 0x000C. `bitmask` on a sensor masks and then shifts down by
# the mask's trailing zeros, so this yields the protocol version on its own.
# Worth surfacing: it is the one value that tells you a node is running
# firmware older than the register map this config assumes.
- platform: modbus_controller
modbus_controller_id: wiredsensor
name: "Protocol version"
register_type: holding
address: 0x000C
value_type: U_WORD
bitmask: 0x00FF
entity_category: diagnostic
skip_updates: 60
# Saturating 32-bit seconds. Home Assistant stores sensor states as float32,
# which carries 24 bits of mantissa, so this quantises past ~194 days of
# uptime. Fine for spotting an unexpected reboot, which is what it is for.
- platform: modbus_controller
modbus_controller_id: wiredsensor
name: "Uptime"
register_type: holding
address: 0x0011
value_type: U_DWORD
unit_of_measurement: "s"
device_class: duration
state_class: total_increasing
entity_category: diagnostic
binary_sensor:
# The flag bits of register 0x0005, on `holding` for the reason above.
- platform: modbus_controller
modbus_controller_id: wiredsensor
name: "Sensor OK"
register_type: holding
address: 0x0005
bitmask: 0x01
entity_category: diagnostic
- platform: modbus_controller
modbus_controller_id: wiredsensor
name: "Data stale"
register_type: holding
address: 0x0005
bitmask: 0x02
entity_category: diagnostic
- 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: "Bus line error"
register_type: holding
address: 0x0005
bitmask: 0x08
device_class: problem
entity_category: diagnostic
- platform: modbus_controller
modbus_controller_id: wiredsensor
name: "Sensor reset seen"
register_type: holding
address: 0x0005
bitmask: 0x20
entity_category: diagnostic
- platform: modbus_controller
modbus_controller_id: wiredsensor
name: "Heater on"
register_type: holding
address: 0x0005
bitmask: 0x40
entity_category: diagnostic
text_sensor:
# 32-bit serials as hex. A plain sensor would store these as a float and lose
# the low bits, for the same 24-bit mantissa reason as uptime above.
- platform: modbus_controller
modbus_controller_id: wiredsensor
name: "Board serial"
register_type: holding
address: 0x000D
register_count: 2
raw_encode: HEXBYTES
entity_category: diagnostic
disabled_by_default: true
skip_updates: 60
- platform: modbus_controller
modbus_controller_id: wiredsensor
name: "SHT31 serial"
register_type: holding
address: 0x000F
register_count: 2
raw_encode: HEXBYTES
entity_category: diagnostic
disabled_by_default: true
skip_updates: 60