esphome: name: uart-mock-modbus-srv-injected 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 # Shared server-role fixture (see the shared_yaml markers in the test file); # the injections concatenate and each test waits only on its own sensors. uart_mock: - id: virtual_uart_dev baud_rate: 9600 rx_full_threshold: 120 rx_timeout: 2 auto_start: false debug: # Each burst-case reply from device 1 (single-register FC 0x03, told apart by its register # value) fires its own sensor when it reaches the wire. One sensor per reply, because the API # merges updates to the same entity that land within its batching window. on_tx: - then: - lambda: |- if (data.size() != 7 || data[0] != 0x01 || data[1] != 0x03 || data[3] != 0x00) return; switch (data[4]) { case 0xA1: id(burst_tx_a).publish_state(1); break; case 0xB2: id(burst_tx_b).publish_state(1); break; case 0xC3: id(burst_tx_before_peer).publish_state(1); break; case 0xD4: id(burst_tx_probe).publish_state(1); break; } injections: - delay: 100ms inject_rx: [0x01, 0x03, 0x00, 0x03, 0x00, 0x01, 0x74, 0x0A] # Read holding register 3 on device 1 (basic_read) - delay: 100ms # Read holding register 7 on device 2, its reply, then read holding # register 5 on device 1 (read_after_peer_response) inject_rx: [0x02, 0x03, 0x00, 0x07, 0x00, 0x01, 0x35, 0xF8, 0x02, 0x03, 0x02, 0x00, 0xF0, 0xFC, 0x00, 0x01, 0x03, 0x00, 0x05, 0x00, 0x01, 0x94, 0x0B] - delay: 100ms inject_rx: [0x02, 0x03, 0x00, 0x07, 0x00, 0x01, 0x35, 0xF8] # Read holding register 7 on device 2, with no response - delay: 100ms # Read holding register 7 on device 2 with no response, then read # holding register A on device 1 (read_after_peer_timeout) inject_rx: [0x02, 0x03, 0x00, 0x07, 0x00, 0x01, 0x35, 0xF8, 0x01, 0x03, 0x00, 0x0A, 0x00, 0x01, 0xA4, 0x08] # FC 0x17 on device 1: write reg 0x0001 = 0x1234 then read 0x0001..0x0002; # per Modbus 6.17 the write runs first, so 0x0001 must read back 0x1234. - delay: 100ms inject_rx: [0x01, 0x17, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x01, 0x02, 0x12, 0x34, 0x49, 0xD8] # FC 0x17: write reg 0x0006 = 0x5678 (qty 1), then read reg 0x0006 (qty 1) - # a write and read targeting a different register block. - delay: 100ms inject_rx: [0x01, 0x17, 0x00, 0x06, 0x00, 0x01, 0x00, 0x06, 0x00, 0x01, 0x02, 0x56, 0x78, 0x8B, 0x55] # Two reads of device 1 (regs 0x0B then 0x0C) in one injection so both land in the rx buffer # together. The reply to 0x0B is deferred because 0x0C is still queued behind it, and must be # dropped once 0x0C is parsed: only the reply to 0x0C may reach the wire (burst_read_a/b). - delay: 100ms inject_rx: [0x01, 0x03, 0x00, 0x0B, 0x00, 0x01, 0xF5, 0xC8, 0x01, 0x03, 0x00, 0x0C, 0x00, 0x01, 0x44, 0x09] # Read of device 1 (reg 0x0D) followed in the same injection by a read of device 2. The client # has moved on to another device, so the deferred reply to 0x0D must never be sent # (burst_read_before_peer). - delay: 100ms inject_rx: [0x01, 0x03, 0x00, 0x0D, 0x00, 0x01, 0x15, 0xC9, 0x02, 0x03, 0x00, 0x07, 0x00, 0x01, 0x35, 0xF8] # Plain read of device 1 (reg 0x0E) whose reply on the wire marks the burst cases as settled. - delay: 100ms inject_rx: [0x01, 0x03, 0x00, 0x0E, 0x00, 0x01, 0xE5, 0xC9] globals: - id: stored_1 type: uint16_t initial_value: "0" - id: stored_3 type: uint16_t initial_value: "0" modbus: uart_id: virtual_uart_dev role: server modbus_server: - address: 1 registers: # Writable + readable register backed by a global. The read publishes what it # returns so the test can confirm the write half ran before the read half. - address: 0x01 value_type: U_WORD read_lambda: |- id(rw_read_1).publish_state(id(stored_1)); return id(stored_1); write_lambda: |- id(stored_1) = x; id(rw_write_1).publish_state(x); return true; # Read-only register, read together with 0x01 by the first request's 2-register read. - address: 0x02 value_type: U_WORD read_lambda: |- id(rw_read_2).publish_state(0x00AA); return 0x00AA; - address: 0x03 value_type: U_WORD read_lambda: |- id(basic_read).publish_state(1); return 1; - address: 0x05 value_type: U_WORD read_lambda: |- id(read_after_peer_response).publish_state(1); return 1; # Second writable + readable register, targeted by the second FC 0x17 request. - address: 0x06 value_type: U_WORD read_lambda: |- id(rw_read_3).publish_state(id(stored_3)); return id(stored_3); write_lambda: |- id(stored_3) = x; id(rw_write_3).publish_state(x); return true; - address: 0x0A value_type: U_WORD read_lambda: |- id(read_after_peer_timeout).publish_state(1); return 1; - address: 0x0B value_type: U_WORD read_lambda: |- id(burst_read_a).publish_state(1); return 0xA1; - address: 0x0C value_type: U_WORD read_lambda: |- id(burst_read_b).publish_state(1); return 0xB2; - address: 0x0D value_type: U_WORD read_lambda: |- id(burst_read_before_peer).publish_state(1); return 0xC3; - address: 0x0E value_type: U_WORD read_lambda: return 0xD4; sensor: - platform: template name: "basic_read" id: basic_read - platform: template name: "read_after_peer_response" id: read_after_peer_response - platform: template name: "read_after_peer_timeout" id: read_after_peer_timeout - platform: template name: "rw_write_1" id: rw_write_1 - platform: template name: "rw_read_1" id: rw_read_1 - platform: template name: "rw_read_2" id: rw_read_2 - platform: template name: "rw_write_3" id: rw_write_3 - platform: template name: "rw_read_3" id: rw_read_3 - platform: template name: "burst_read_a" id: burst_read_a - platform: template name: "burst_read_b" id: burst_read_b - platform: template name: "burst_read_before_peer" id: burst_read_before_peer - platform: template name: "burst_tx_a" id: burst_tx_a - platform: template name: "burst_tx_b" id: burst_tx_b - platform: template name: "burst_tx_before_peer" id: burst_tx_before_peer - platform: template name: "burst_tx_probe" id: burst_tx_probe button: - platform: template name: "Start Scenario" id: start_scenario_btn on_press: - lambda: "id(virtual_uart_dev).start_scenario();"