🚧 Change module numbering
This commit is contained in:
@@ -88,13 +88,15 @@ class TestAliases:
|
||||
class TestModuleAddressing:
|
||||
"""Module type + module number + channel number addressing (DI0.1).
|
||||
|
||||
The pump node fixture modules: [DI_8, DI_16, DO_8, DO_16, AI_2, AO_2]
|
||||
(indices 0..5). Module addressing uses 0-based module positions and
|
||||
0-based channel numbers on the module.
|
||||
The pump node fixture modules: [DI_8, DI_16, DO_8, DO_16, AI_2, AO_2].
|
||||
Module addressing (FR-4.3.11 v1.5) numbers modules 0-based WITHIN
|
||||
THEIR TYPE: DI0=DI_8, DI1=DI_16, DO0=DO_8, DO1=DO_16, AI0=AI_2,
|
||||
AO0=AO_2 — regardless of physical slot interleaving. Channels are
|
||||
0-based on the module.
|
||||
"""
|
||||
|
||||
def test_module_addressing_di(self, node, datastore):
|
||||
# DI0.1: module slot 0 (DI_8), channel 1 (0-based) = 2nd DI
|
||||
# DI0.1: DI module 0 (DI_8), channel 1 (0-based) = 2nd DI
|
||||
scenario = _scenario()
|
||||
scenario["inputs"]["second_di"] = {"type": "di", "module": 0, "channel": 1}
|
||||
engine = ScenarioEngine(node, scenario)
|
||||
@@ -104,35 +106,42 @@ class TestModuleAddressing:
|
||||
assert engine.alias_value("second_di") is True
|
||||
|
||||
def test_module_addressing_across_modules(self, node, datastore):
|
||||
# DI1.0: module slot 1 (DI_16), channel 0 = 9th DI globally
|
||||
# (8 channels on module 0 come first)
|
||||
# DI1.0: DI module 1 (DI_16, physical slot 1), channel 0 = 9th DI
|
||||
scenario = _scenario()
|
||||
scenario["inputs"]["di16_first"] = {"type": "di", "module": 1, "channel": 0}
|
||||
engine = ScenarioEngine(node, scenario)
|
||||
engine.set_alias("di16_first", True)
|
||||
assert datastore.read_bits(8, 1) == [True]
|
||||
|
||||
def test_module_addressing_do(self, node, datastore):
|
||||
# DO2.2: module slot 2 (DO_8), channel 2 = 3rd DO globally
|
||||
def test_module_addressing_do_interleaved(self, node, datastore):
|
||||
# DO0.2: DO module 0 is the DO_8 at PHYSICAL SLOT 2 — per-type
|
||||
# numbering means module 0, not slot 2
|
||||
scenario = _scenario()
|
||||
scenario["outputs"]["third_do"] = {"type": "do", "module": 2, "channel": 2}
|
||||
scenario["outputs"]["third_do"] = {"type": "do", "module": 0, "channel": 2}
|
||||
engine = ScenarioEngine(node, scenario)
|
||||
bit = engine.alias_location("third_do").bit
|
||||
assert bit == 2
|
||||
assert datastore.write_bits(bit, [True]) is None
|
||||
assert engine.alias_value("third_do") is True
|
||||
|
||||
def test_module_addressing_ai_ao(self, node, datastore):
|
||||
# AI4.1: module slot 4 (AI_2), channel 1 = 2nd AI word
|
||||
def test_do_module_1_is_do16(self, node):
|
||||
# DO1.0: DO module 1 is the DO_16 at physical slot 3
|
||||
scenario = _scenario()
|
||||
scenario["inputs"]["ai2"] = {"type": "ai", "module": 4, "channel": 1, "scale": 1.0}
|
||||
scenario["outputs"]["x"] = {"type": "do", "module": 1, "channel": 0}
|
||||
engine = ScenarioEngine(node, scenario)
|
||||
assert engine.alias_location("x").bit == 8 # after DO_8's 8 channels
|
||||
|
||||
def test_module_addressing_ai_ao(self, node, datastore):
|
||||
# AI0.1: AI module 0 (AI_2 at physical slot 4), channel 1 = 2nd AI word
|
||||
scenario = _scenario()
|
||||
scenario["inputs"]["ai2"] = {"type": "ai", "module": 0, "channel": 1, "scale": 1.0}
|
||||
engine = ScenarioEngine(node, scenario)
|
||||
engine.set_alias("ai2", 0x1234)
|
||||
loc = node.channel("ai", 2) # global 2nd AI
|
||||
assert datastore.get_input_word(loc.word) == 0x1234
|
||||
|
||||
# AO5.1: module slot 5 (AO_2), channel 1 = 2nd AO word
|
||||
scenario["outputs"] = {"ao2": {"type": "ao", "module": 5, "channel": 1}}
|
||||
# AO0.1: AO module 0 (AO_2 at physical slot 5), channel 1 = 2nd AO word
|
||||
scenario["outputs"] = {"ao2": {"type": "ao", "module": 0, "channel": 1}}
|
||||
engine2 = ScenarioEngine(node, scenario)
|
||||
bit_word = node.channel("ao", 2).word
|
||||
assert datastore.write_words(bit_word, [0xBEEF]) is None
|
||||
@@ -149,31 +158,34 @@ class TestModuleAddressing:
|
||||
|
||||
def test_module_out_of_range_rejected(self, node):
|
||||
scenario = _scenario()
|
||||
scenario["inputs"]["bad"] = {"type": "di", "module": 99, "channel": 0}
|
||||
with pytest.raises(IndexError, match="module 99"):
|
||||
# the node has only 2 DI modules
|
||||
scenario["inputs"]["bad"] = {"type": "di", "module": 2, "channel": 0}
|
||||
with pytest.raises(IndexError, match="DI module 2"):
|
||||
ScenarioEngine(node, scenario)
|
||||
|
||||
def test_channel_out_of_range_rejected(self, node):
|
||||
# module 0 is a DI_8 -> channels 0-7 only
|
||||
# DI module 0 is a DI_8 -> channels 0-7 only
|
||||
scenario = _scenario()
|
||||
scenario["inputs"]["bad"] = {"type": "di", "module": 0, "channel": 8}
|
||||
with pytest.raises(IndexError, match="channel 8"):
|
||||
ScenarioEngine(node, scenario)
|
||||
|
||||
def test_kind_mismatch_rejected(self, node):
|
||||
# module 0 is DI-only -> DO addressing must fail
|
||||
def test_module_kind_routes_to_own_type(self, node):
|
||||
"""Per-type numbering (v1.5): 'do' module numbers address the DO
|
||||
modules regardless of physical slot — DO0 is the DO_8 at slot 2.
|
||||
A 'do' alias on module 1 (the DO_16) is valid and distinct."""
|
||||
scenario = _scenario()
|
||||
scenario["outputs"]["bad"] = {"type": "do", "module": 0, "channel": 0}
|
||||
from wago_sim.modules import ChannelMismatch
|
||||
with pytest.raises(ChannelMismatch):
|
||||
ScenarioEngine(node, scenario)
|
||||
scenario["outputs"]["do8"] = {"type": "do", "module": 0, "channel": 0}
|
||||
scenario["outputs"]["do16"] = {"type": "do", "module": 1, "channel": 0}
|
||||
engine = ScenarioEngine(node, scenario)
|
||||
assert engine.alias_location("do8").bit == 0
|
||||
assert engine.alias_location("do16").bit == 8
|
||||
|
||||
def test_analog_kind_on_digital_module_rejected(self, node):
|
||||
# module 0 is DI-only -> AI addressing must fail
|
||||
# per-type routing: 'ai' module 9 is out of range (only 1 AI module)
|
||||
scenario = _scenario()
|
||||
scenario["inputs"]["bad"] = {"type": "ai", "module": 0, "channel": 0}
|
||||
from wago_sim.modules import ChannelMismatch
|
||||
with pytest.raises(ChannelMismatch):
|
||||
scenario["inputs"]["bad"] = {"type": "ai", "module": 9, "channel": 0}
|
||||
with pytest.raises(IndexError):
|
||||
ScenarioEngine(node, scenario)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user