# 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