esphome: name: uart-mock-modbus-shared host: api: logger: level: VERBOSE external_components: - source: type: local path: EXTERNAL_COMPONENT_PATH # Dummy uart entry to satisfy modbus's DEPENDENCIES = ["uart"] # The actual UART bus used is the uart_mock component below uart: baud_rate: 115200 port: /dev/null uart_mock: - id: virtual_uart_dev baud_rate: 9600 rx_full_threshold: 120 rx_timeout: 2 auto_start: false debug: responses: # Three sensors, one frame. At 0x9001 a U_WORD (1 register) and a U_DWORD (2 registers) share the # start address but cannot merge, so the range widens to count 2. A third sensor at 0x9002 falls # inside the widened range and must read its slice of the same response rather than splitting into # a second overlapping poll. The single expect_tx pins the "one frame on the wire" contract - any # duplicate or overlapping range would put an extra frame on the bus and fail to match. - expect_tx: [0x01, 0x03, 0x90, 0x01, 0x00, 0x02, 0xB8, 0xCB] # Read holding 0x9001 count 2 on device 1 inject_rx: [0x01, 0x03, 0x04, 0x03, 0x97, 0x02, 0x91, 0x8B, 0x57] # 0x9001=0x0397, 0x9002=0x0291 # A sensor at 0x30 with the deprecated force_new_range (migrates to reuse_previous_range: false) # and a plain sensor at 0x10. The two must poll as separate ranges: the 0x10 sensor must not be # absorbed into the isolated 0x30 range. - expect_tx: [0x01, 0x03, 0x00, 0x30, 0x00, 0x01, 0x84, 0x05] # Read holding 0x30 count 1 (forced range) inject_rx: [0x01, 0x03, 0x02, 0x01, 0x11, 0x79, 0xD8] # 0x30 = 0x0111 = 273 - expect_tx: [0x01, 0x03, 0x00, 0x10, 0x00, 0x01, 0x85, 0xCF] # Read holding 0x10 count 1 (own range) inject_rx: [0x01, 0x03, 0x02, 0x02, 0x22, 0x39, 0x3D] # 0x10 = 0x0222 = 546 # A wide sensor (U_QWORD at 0x100, 4 registers) followed by plain sensors at 0x101 and 0x103. # None of them merge, so all three poll separately - exactly as before the range refactor. The # 0x103 sensor sits at the wide range's tail address, so it must not anchor a re-use join on a # mid-range predecessor and inherit its byte offset. - expect_tx: [0x01, 0x03, 0x01, 0x00, 0x00, 0x04, 0x45, 0xF5] # Read holding 0x100 count 4 inject_rx: [0x01, 0x03, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x94, 0x3C] # = 100 - expect_tx: [0x01, 0x03, 0x01, 0x01, 0x00, 0x01, 0xD4, 0x36] # Read holding 0x101 count 1 inject_rx: [0x01, 0x03, 0x02, 0x01, 0x41, 0x79, 0xE4] # 0x101 = 0x0141 = 321 - expect_tx: [0x01, 0x03, 0x01, 0x03, 0x00, 0x01, 0x75, 0xF6] # Read holding 0x103 count 1 inject_rx: [0x01, 0x03, 0x02, 0x01, 0xA5, 0x79, 0xAF] # 0x103 = 0x01A5 = 421 # Two sensors sharing start address 0x200 (a word and a dword) widen the range to 2 registers # and both decode from the single response. - expect_tx: [0x01, 0x03, 0x02, 0x00, 0x00, 0x02, 0xC5, 0xB3] # Read holding 0x200 count 2 inject_rx: [0x01, 0x03, 0x04, 0x01, 0x41, 0x00, 0x02, 0x2A, 0x1A] # 0x200=0x0141, 0x201=0x0002 modbus: uart_id: virtual_uart_dev send_wait_time: 200ms turnaround_time: 10ms modbus_controller: - address: 1 id: modbus_controller_ok max_cmd_retries: 2 update_interval: never sensor: # Word sensor at 0x9001 (1 register) - platform: modbus_controller name: "shared_word" address: 0x9001 register_type: holding value_type: U_WORD modbus_controller_id: modbus_controller_ok # Dword sensor at the SAME address 0x9001 (2 registers) - non-mergeable, shares the range start - platform: modbus_controller name: "shared_dword" address: 0x9001 register_type: holding value_type: U_DWORD modbus_controller_id: modbus_controller_ok # Word sensor at 0x9002 - inside the widened range, reads bytes 2-3 of the same response - platform: modbus_controller name: "covered_word" address: 0x9002 register_type: holding value_type: U_WORD modbus_controller_id: modbus_controller_ok # Isolated sensor (deprecated spelling, migrates to reuse_previous_range: false): own range - platform: modbus_controller name: "forced_high" address: 0x30 register_type: holding value_type: U_WORD force_new_range: true modbus_controller_id: modbus_controller_ok # Plain sensor at a lower address: must get its own range, never absorbed into the isolated one - platform: modbus_controller name: "plain_low" address: 0x10 register_type: holding value_type: U_WORD modbus_controller_id: modbus_controller_ok # Wide sensor spanning 0x100-0x103; the two sensors below sit inside its span but do not merge - platform: modbus_controller name: "wide_qword" address: 0x100 register_type: holding value_type: U_QWORD modbus_controller_id: modbus_controller_ok - platform: modbus_controller name: "inside_wide" address: 0x101 register_type: holding value_type: U_WORD modbus_controller_id: modbus_controller_ok # At the wide range's tail address: must decode its own poll, not inherit a mid-range byte offset - platform: modbus_controller name: "tail_of_wide" address: 0x103 register_type: holding value_type: U_WORD modbus_controller_id: modbus_controller_ok # Shared address 0x200: the dword widens the range the word opened (or vice versa) - platform: modbus_controller name: "widen_word" address: 0x200 register_type: holding value_type: U_WORD modbus_controller_id: modbus_controller_ok - platform: modbus_controller name: "widen_dword" address: 0x200 register_type: holding value_type: U_DWORD modbus_controller_id: modbus_controller_ok button: - platform: template name: "Start Scenario" id: start_scenario_btn on_press: - lambda: |- id(virtual_uart_dev).start_scenario(); id(modbus_controller_ok).set_update_interval(1000); id(modbus_controller_ok).start_poller();