mirror of
https://github.com/esphome/esphome.git
synced 2026-09-21 20:18:43 +00:00
Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: J. Nick Koston <nick@koston.org>
228 lines
8.3 KiB
YAML
228 lines
8.3 KiB
YAML
esphome:
|
|
name: uart-mock-modbus-ranges-test
|
|
|
|
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
|
|
|
|
# Each expect_tx below pins the exact read request the controller's range builder emits, so this
|
|
# fixture is a wire-level test of reuse_previous_range (auto/yes/no), gap joins, same-register reuse,
|
|
# response_size surplus accounting, and RAW/text block reads.
|
|
uart_mock:
|
|
- id: virtual_uart_dev
|
|
baud_rate: 9600
|
|
rx_full_threshold: 120
|
|
rx_timeout: 2
|
|
# auto_start must be false to avoid races: the test presses the
|
|
# "Start Scenario" button only after subscribing to states.
|
|
auto_start: false
|
|
debug:
|
|
responses:
|
|
- expect_tx: [0x01, 0x03, 0x00, 0x00, 0x00, 0x03, 0x05, 0xCB] # auto adjacency: one read covers 0x00-0x02
|
|
inject_rx: [0x01, 0x03, 0x06, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0xFD, 0x74]
|
|
- expect_tx: [0x01, 0x03, 0x00, 0x10, 0x00, 0x01, 0x85, 0xCF] # auto gap: 0x10 alone
|
|
inject_rx: [0x01, 0x03, 0x02, 0x00, 0x04, 0xB9, 0x87]
|
|
- expect_tx: [0x01, 0x03, 0x00, 0x13, 0x00, 0x01, 0x75, 0xCF] # auto gap: 0x13 alone
|
|
inject_rx: [0x01, 0x03, 0x02, 0x00, 0x05, 0x78, 0x47]
|
|
- expect_tx: [0x01, 0x03, 0x00, 0x20, 0x00, 0x04, 0x45, 0xC3] # yes across gap: one read 0x20-0x23, gap registers ignored
|
|
inject_rx: [0x01, 0x03, 0x08, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x33, 0xD1]
|
|
- expect_tx: [0x01, 0x03, 0x00, 0x30, 0x00, 0x01, 0x84, 0x05] # no isolation: 0x30 alone despite adjacency
|
|
inject_rx: [0x01, 0x03, 0x02, 0x00, 0x0A, 0x38, 0x43]
|
|
- expect_tx: [0x01, 0x03, 0x00, 0x31, 0x00, 0x01, 0xD5, 0xC5] # no isolation: 0x31 alone
|
|
inject_rx: [0x01, 0x03, 0x02, 0x00, 0x0B, 0xF9, 0x83]
|
|
- expect_tx: [0x01, 0x03, 0x00, 0x3F, 0x00, 0x01, 0xB4, 0x06] # open NEVER: 0x3F alone (the reuse:false item split off)
|
|
inject_rx: [0x01, 0x03, 0x02, 0x00, 0x0C, 0xB8, 0x41]
|
|
- expect_tx: [0x01, 0x03, 0x00, 0x40, 0x00, 0x02, 0xC5, 0xDF] # open NEVER: 0x40 (reuse: false) still extended by the auto item at 0x41
|
|
inject_rx: [0x01, 0x03, 0x04, 0x00, 0x0D, 0x00, 0x0E, 0xEA, 0x34]
|
|
- expect_tx: [0x01, 0x03, 0x00, 0x50, 0x00, 0x01, 0x84, 0x1B] # same-address reuse: one read, two sensors on 0x50
|
|
inject_rx: [0x01, 0x03, 0x02, 0x12, 0x34, 0xB5, 0x33]
|
|
- expect_tx: [0x01, 0x03, 0x00, 0x60, 0x00, 0x04, 0x44, 0x17] # text block + adjacent word: one read 0x60-0x63
|
|
inject_rx: [0x01, 0x03, 0x08, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x00, 0x0F, 0x78, 0x0E]
|
|
- expect_tx: [0x01, 0x03, 0x00, 0x70, 0x00, 0x02, 0xC5, 0xD0] # response_size surplus: 0x70 answers 4 bytes, reuse:true word at 0x71 shifted along
|
|
inject_rx: [0x01, 0x03, 0x06, 0x00, 0x10, 0xAA, 0xBB, 0x00, 0x11, 0x71, 0x47]
|
|
- expect_tx: [0x01, 0x03, 0x00, 0x90, 0x00, 0x01, 0x84, 0x27] # auto after surplus: 0x90 alone (auto never joins past response_size)
|
|
inject_rx: [0x01, 0x03, 0x04, 0x00, 0x18, 0xCC, 0xDD, 0xEF, 0x6D]
|
|
- expect_tx: [0x01, 0x03, 0x00, 0x91, 0x00, 0x01, 0xD5, 0xE7] # auto after surplus: 0x91 alone
|
|
inject_rx: [0x01, 0x03, 0x02, 0x00, 0x19, 0x79, 0x8E]
|
|
- expect_tx: [0x01, 0x03, 0x00, 0x80, 0x00, 0x04, 0x45, 0xE1] # RAW block via response_size: 8 bytes = 4 registers in one read
|
|
inject_rx: [0x01, 0x03, 0x08, 0x00, 0x14, 0x00, 0x15, 0x00, 0x16, 0x00, 0x17, 0x6D, 0xDF]
|
|
|
|
modbus:
|
|
uart_id: virtual_uart_dev
|
|
send_wait_time: 200ms
|
|
turnaround_time: 10ms
|
|
|
|
modbus_controller:
|
|
- address: 1
|
|
id: ranges_controller
|
|
max_cmd_retries: 0
|
|
# The test triggers a single poll by pressing the "Start Scenario" button
|
|
update_interval: never
|
|
|
|
sensor:
|
|
# Case 1: three adjacent registers merge into one read (auto default)
|
|
- platform: modbus_controller
|
|
modbus_controller_id: ranges_controller
|
|
name: "adjacent_a"
|
|
register_type: holding
|
|
address: 0x00
|
|
- platform: modbus_controller
|
|
modbus_controller_id: ranges_controller
|
|
name: "adjacent_b"
|
|
register_type: holding
|
|
address: 0x01
|
|
- platform: modbus_controller
|
|
modbus_controller_id: ranges_controller
|
|
name: "adjacent_c"
|
|
register_type: holding
|
|
address: 0x02
|
|
|
|
# Case 2: a gap keeps auto items apart
|
|
- platform: modbus_controller
|
|
modbus_controller_id: ranges_controller
|
|
name: "gap_a"
|
|
register_type: holding
|
|
address: 0x10
|
|
- platform: modbus_controller
|
|
modbus_controller_id: ranges_controller
|
|
name: "gap_b"
|
|
register_type: holding
|
|
address: 0x13
|
|
|
|
# Case 3: reuse_previous_range: true bridges the gap into one read
|
|
- platform: modbus_controller
|
|
modbus_controller_id: ranges_controller
|
|
name: "bridge_a"
|
|
register_type: holding
|
|
address: 0x20
|
|
- platform: modbus_controller
|
|
modbus_controller_id: ranges_controller
|
|
name: "bridge_b"
|
|
register_type: holding
|
|
address: 0x23
|
|
reuse_previous_range: true
|
|
|
|
# Case 4: reuse_previous_range: false splits adjacent registers
|
|
- platform: modbus_controller
|
|
modbus_controller_id: ranges_controller
|
|
name: "split_a"
|
|
register_type: holding
|
|
address: 0x30
|
|
- platform: modbus_controller
|
|
modbus_controller_id: ranges_controller
|
|
name: "split_b"
|
|
register_type: holding
|
|
address: 0x31
|
|
reuse_previous_range: false
|
|
|
|
# Case 5: a reuse:false item starts its own range but stays open for later auto items
|
|
- platform: modbus_controller
|
|
modbus_controller_id: ranges_controller
|
|
name: "open_prev"
|
|
register_type: holding
|
|
address: 0x3F
|
|
- platform: modbus_controller
|
|
modbus_controller_id: ranges_controller
|
|
name: "open_never"
|
|
register_type: holding
|
|
address: 0x40
|
|
reuse_previous_range: false
|
|
- platform: modbus_controller
|
|
modbus_controller_id: ranges_controller
|
|
name: "open_tagalong"
|
|
register_type: holding
|
|
address: 0x41
|
|
|
|
# Case 6: two sensors on the same register share one read
|
|
- platform: modbus_controller
|
|
modbus_controller_id: ranges_controller
|
|
name: "shared_lo"
|
|
register_type: holding
|
|
address: 0x50
|
|
bitmask: 0x00FF
|
|
- platform: modbus_controller
|
|
modbus_controller_id: ranges_controller
|
|
name: "shared_hi"
|
|
register_type: holding
|
|
address: 0x50
|
|
bitmask: 0xFF00
|
|
|
|
# Case 10 (text block, see text_sensor below) shares the range with this word at 0x63
|
|
- platform: modbus_controller
|
|
modbus_controller_id: ranges_controller
|
|
name: "after_text"
|
|
register_type: holding
|
|
address: 0x63
|
|
|
|
# Case 11: response_size surplus — the device answers 4 bytes for this single register, so the
|
|
# following sensor's data sits 2 bytes later than its address alone implies. Joining past a
|
|
# non-standard response_size takes an explicit reuse_previous_range: true.
|
|
- platform: modbus_controller
|
|
modbus_controller_id: ranges_controller
|
|
name: "surplus"
|
|
register_type: holding
|
|
address: 0x70
|
|
response_size: 4
|
|
- platform: modbus_controller
|
|
modbus_controller_id: ranges_controller
|
|
name: "after_surplus"
|
|
register_type: holding
|
|
address: 0x71
|
|
reuse_previous_range: true
|
|
|
|
# Case 13: auto never joins past a response_size register — despite adjacency these poll separately
|
|
- platform: modbus_controller
|
|
modbus_controller_id: ranges_controller
|
|
name: "surplus_split"
|
|
register_type: holding
|
|
address: 0x90
|
|
response_size: 4
|
|
- platform: modbus_controller
|
|
modbus_controller_id: ranges_controller
|
|
name: "after_surplus_split"
|
|
register_type: holding
|
|
address: 0x91
|
|
|
|
# Case 12: RAW + response_size reads a block of ceil(8/2) = 4 registers
|
|
- platform: modbus_controller
|
|
modbus_controller_id: ranges_controller
|
|
name: "raw_block"
|
|
register_type: holding
|
|
address: 0x80
|
|
value_type: RAW
|
|
response_size: 8
|
|
lambda: |-
|
|
return (float) data.size();
|
|
|
|
text_sensor:
|
|
# Case 10: text sensor reads 3 registers (response_size 6)
|
|
- platform: modbus_controller
|
|
modbus_controller_id: ranges_controller
|
|
name: "text_block"
|
|
register_type: holding
|
|
address: 0x60
|
|
response_size: 6
|
|
raw_encode: NONE
|
|
|
|
button:
|
|
- platform: template
|
|
name: "Start Scenario"
|
|
id: start_scenario_btn
|
|
on_press:
|
|
- lambda: |-
|
|
id(virtual_uart_dev).start_scenario();
|
|
id(ranges_controller).set_update_interval(1000);
|
|
id(ranges_controller).start_poller();
|