dohertj2 dohertj2
  • Joined on 2026-02-20
dohertj2 created branch phase-3-pr48-dl205-cdab-float in dohertj2/lmxopcua 2026-04-18 22:00:37 -04:00
dohertj2 pushed to phase-3-pr48-dl205-cdab-float at dohertj2/lmxopcua 2026-04-18 22:00:37 -04:00
463c5a4320 Phase 3 PR 48 -- DL205 CDAB word order for Float32 end-to-end test. The driver has supported ModbusByteOrder.WordSwap (CDAB) since PR 24 for all multi-register types -- the underlying word-swap code path was already there. PR 48 closes the loop with an integration test that validates it end-to-end against the dl205 pymodbus profile: HR[1056..1057] stores IEEE-754 1.5f with the low word at the lower address (0x0000 at HR[1056], 0x3FC0 at HR[1057]). Reading with WordSwap returns 1.5f; reading with BigEndian returns a tiny denormal (~5.74e-41) -- a silent "value is 0" bug that typically surfaces in the field only when an operator notices a setpoint readout stuck at 0 while the PLC display shows the real value. Test asserts both: WordSwap==1.5f AND BigEndian!=1.5f, proving the flag is not a no-op. No driver code changes -- the word-swap normalization at NormalizeWordOrder() has handled Float32/Int32/UInt32 correctly since PR 24 and the unit test suite already covers it (Int32_WordSwap_decodes_CDAB_layout + Float32 equivalent). This PR exists primarily to lock in the integration-level validation so future refactors of the codec don't silently break DL205/DL260 floats. 6/6 DL205 integration tests pass with MODBUS_SIM_PROFILE=dl205.
dohertj2 created branch phase-3-pr47-dl205-vmemory in dohertj2/lmxopcua 2026-04-18 22:00:33 -04:00
dohertj2 pushed to phase-3-pr47-dl205-vmemory at dohertj2/lmxopcua 2026-04-18 22:00:33 -04:00
2b5222f5db Phase 3 PR 47 -- DL205 V-memory octal-address helper. Adds DirectLogicAddress static class with two entry points: UserVMemoryToPdu(string) parses a DirectLOGIC V-address (V-prefixed or bare, whitespace tolerated) as OCTAL and returns the 0-based Modbus PDU address. V2000 octal = decimal 1024 = PDU 0x0400, which is the canonical start of the user V-memory bank on DL205/DL260. SystemVMemoryBasePdu + SystemVMemoryToPdu(ushort offset) handle the system bank (V40400 and up) which does NOT follow the simple octal-to-decimal formula -- the CPU relocates the system bank to PDU 0x2100 in H2-ECOM100 absolute mode. A naive caller converting 40400 octal would land at PDU 0x4100 (decimal 16640) and miss the system registers entirely; the helper routes the correct 0x2100 base. Why this matters: DirectLOGIC operators think in OCTAL (the ladder-logic editor, the Productivity/Do-more UI, every AutomationDirect manual addresses V-memory octally) while the Modbus wire is DECIMAL. Integrators routinely copy V-addresses from the PLC documentation into client configs and read garbage because they treated V2000 as decimal 2000 (HR[2000] = 0 in the dl205 sim, zero in most PLCs). The helper makes the translation explicit per the D2-USER-M appendix + H2-ECOM-M \u00A76.5 references cited in docs/v2/dl205.md. Unit tests: UserVMemoryToPdu_converts_octal_V_prefix (V0, V1, V7, V10, V2000, V7777, V10000, V17777 -- the exact sweep documented in dl205.md), UserVMemoryToPdu_accepts_bare_or_prefixed_or_padded (case + whitespace tolerance), UserVMemoryToPdu_rejects_non_octal_digits (V8/V19/V2009 must throw ArgumentException with 'octal' in the message -- .NET has no base-8 int.Parse so we hand-walk digits to catch 8/9 instead of silently accepting them), UserVMemoryToPdu_rejects_empty_input, UserVMemoryToPdu_overflow_rejected (200000 octal = 0x10000 overflows ushort), SystemVMemoryBasePdu_is_0x2100_for_V40400, SystemVMemoryToPdu_offsets_within_bank, SystemVMemoryToPdu_rejects_overflow. 23/23 Modbus.Tests pass. Integration tests against dl205.json pymodbus profile: DL205_V2000_user_memory_resolves_to_PDU_0x0400_marker (reads HR[0x0400]=0x2000), DL205_V40400_system_memory_resolves_to_PDU_0x2100_marker (reads HR[0x2100]=0x4040). 5/5 DL205 integration tests pass. Caller opts into the helper per tag by calling DirectLogicAddress.UserVMemoryToPdu("V2000") as the ModbusTagDefinition Address -- no driver-wide "DL205 mode" flag needed, because users mix DL and non-DL tags in a single driver instance all the time.
dohertj2 created branch phase-3-pr46-dl205-bcd in dohertj2/lmxopcua 2026-04-18 22:00:32 -04:00
dohertj2 pushed to phase-3-pr46-dl205-bcd at dohertj2/lmxopcua 2026-04-18 22:00:32 -04:00
8248b126ce Phase 3 PR 46 -- DL205 BCD decoder (binary-coded-decimal numeric encoding). Adds ModbusDataType.Bcd16 and Bcd32 to the driver. Bcd16 is 1 register wide, Bcd32 is 2 registers wide; Bcd32 respects ModbusByteOrder (BigEndian/WordSwap) the same way Int32 does so the CDAB-style families (including DL205/DL260 themselves) can be configured. DecodeRegister uses the new internal DecodeBcd helper: walks each nibble from MSB to LSB, multiplies the running result by 10, adds the nibble as a decimal digit. Explicitly rejects nibbles > 9 with InvalidDataException -- hardware sometimes produces garbage during write-in-progress transitions and silently returning wrong numeric values would quietly corrupt the caller's data. EncodeRegister's new EncodeBcd inverts the operation (mod/div by 10 nibble-by-nibble) with an up-front overflow check against 10^nibbles-1. Why this matters for DL205/DL260: AutomationDirect DirectLOGIC uses BCD as the default numeric encoding for timers, counters, and operator-display numerics (not binary). A plain Int16 read of register 0x1234 returns 4660; the BCD path returns 1234. The two differ enough that silently defaulting to Int16 would give wildly wrong HMI values -- the caller must opt in to Bcd16/Bcd32 per tag. Unit tests: DecodeBcd (theory: 0,1,9,10,1234,9999), DecodeBcd_rejects_nibbles_above_nine, EncodeBcd (theory), Bcd16_decodes_DL205_register_1234_as_decimal_1234 (control: same bytes as Int16 decode to 4660), Bcd16_encode_round_trips_with_decode, Bcd16_encode_rejects_out_of_range_values, Bcd32_decodes_8_digits_big_endian, Bcd32_word_swap_handles_CDAB_layout, Bcd32_encode_round_trips_with_decode, Bcd_RegisterCount_matches_underlying_width. 66/66 Modbus.Tests pass. Integration test: DL205BcdQuirkTests.DL205_BCD16_decodes_HR1072_as_decimal_1234 against dl205.json pymodbus profile (HR[1072]=0x1234). Asserts Bcd16 decode=1234 AND Int16 decode=0x1234 on the same wire bytes to prove the paths are distinct. 3/3 DL205 integration tests pass with MODBUS_SIM_PROFILE=dl205.
dohertj2 created branch phase-3-pr45-dl205-string-byte-order in dohertj2/lmxopcua 2026-04-18 22:00:29 -04:00
dohertj2 pushed to phase-3-pr45-dl205-string-byte-order at dohertj2/lmxopcua 2026-04-18 22:00:29 -04:00
cd19022d19 Phase 3 PR 45 -- DL205 string byte-order quirk (low-byte-first ASCII packing). Adds ModbusStringByteOrder enum {HighByteFirst, LowByteFirst} + StringByteOrder field on ModbusTagDefinition (default HighByteFirst, the standard Modbus convention). DecodeRegister + EncodeRegister String branches now respect per-tag byte order. Under LowByteFirst each register packs the first char in the low byte instead of the high byte -- the AutomationDirect DirectLOGIC DL205/DL260/DL350 family's headline string quirk. Without the flag the driver decodes 'eHllo' garbage from HR[1040..1042] even though wire bytes are identical. Unit tests: String_LowByteFirst_decodes_DL205_packed_Hello (5 chars across 3 regs with nul pad), String_LowByteFirst_decode_truncates_at_first_nul, String_LowByteFirst_encode_round_trips_with_decode (asserts exact DL205-documented byte sequence {0x65,0x48,0x6C,0x6C,0x00,0x6F} + symmetric encode->decode), String_HighByteFirst_and_LowByteFirst_differ_on_same_wire (control: same wire, different flag => different decode). 56/56 Modbus.Tests pass. Integration test: DL205StringQuirkTests.DL205_string_low_byte_first_decodes_Hello_from_HR1040 against the dl205.json pymodbus profile; reads HR[1040..1042] with both flags on the same tag map and asserts LowByteFirst='Hello' + HighByteFirst!='Hello'. Gated on MODBUS_SIM_PROFILE=dl205 since the standard profile doesn't seed HR[1040..1042]. Verified 2/2 integration tests pass against running pymodbus dl205 simulator. Baseline for PR 46 (BCD decoder), PR 47 (V-memory octal helper), PR 48 (CDAB float order), PR 49 (FC03/FC16 per-device caps) -- each lands its own DL205_<behavior> test class in tests/.../DL205/.
dohertj2 pushed to v2 at dohertj2/lmxopcua 2026-04-18 21:39:27 -04:00
5ee9acb255 Merge pull request 'Phase 3 PR 44 -- pymodbus validation + IPv4-explicit transport bugfix' (#43) from phase-3-pr44-pymodbus-validation-fixes into v2
02fccbc762 Phase 3 PR 43 — followup commit: validate pymodbus simulator end-to-end + fix three real bugs surfaced by running it. winget-installed Python 3.12.10 + pip-installed pymodbus[simulator]==3.13.0 on the dev box; both profiles boot cleanly, the integration-suite smoke test passes against either profile.
Compare 2 commits »
dohertj2 merged pull request dohertj2/lmxopcua#43 2026-04-18 21:39:25 -04:00
Phase 3 PR 44 -- pymodbus validation + IPv4-explicit transport bugfix
dohertj2 created pull request dohertj2/lmxopcua#43 2026-04-18 21:19:58 -04:00
Phase 3 PR 44 -- pymodbus validation + IPv4-explicit transport bugfix
dohertj2 created branch phase-3-pr44-pymodbus-validation-fixes in dohertj2/lmxopcua 2026-04-18 21:18:07 -04:00
dohertj2 pushed to phase-3-pr44-pymodbus-validation-fixes at dohertj2/lmxopcua 2026-04-18 21:18:07 -04:00
02fccbc762 Phase 3 PR 43 — followup commit: validate pymodbus simulator end-to-end + fix three real bugs surfaced by running it. winget-installed Python 3.12.10 + pip-installed pymodbus[simulator]==3.13.0 on the dev box; both profiles boot cleanly, the integration-suite smoke test passes against either profile.
dohertj2 pushed to v2 at dohertj2/lmxopcua 2026-04-18 20:52:50 -04:00
faeab34541 Merge pull request 'Phase 3 PR 43 — Swap ModbusPal to pymodbus for the integration-test simulator' (#42) from phase-3-pr43-pymodbus-swap into v2
a05b84858d Phase 3 PR 43 — Swap ModbusPal to pymodbus for the integration-test simulator. Replaces the .xmpp profiles shipped in PR 42 with pymodbus 3.13.0 ModbusSimulatorServer JSON configs in tests/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests/Pymodbus/. Substantive reasons for the swap (rationale block in the test-plan doc): ModbusPal 1.6b is abandoned (last release ~2019), Java GUI-only with no headless mode in the official JAR, and only exposes 2 of the 4 standard Modbus tables (holding_registers + coils — no input_registers, no discrete_inputs). pymodbus is current stable, pure Python CLI (pip install pymodbus[simulator]==3.13.0), exposes all four tables, has built-in declarative actions (increment / random / timestamp / uptime) for dynamic registers, supports custom Python actions for anything more complex, and ships an optional aiohttp-based web UI / REST API for live inspection. Pip-installable on Windows; sidesteps the privileged-port admin requirement by defaulting to TCP 5020.
Compare 2 commits »
dohertj2 merged pull request dohertj2/lmxopcua#42 2026-04-18 20:52:48 -04:00
Phase 3 PR 43 — Swap ModbusPal to pymodbus for the integration-test simulator
dohertj2 created pull request dohertj2/lmxopcua#42 2026-04-18 20:52:45 -04:00
Phase 3 PR 43 — Swap ModbusPal to pymodbus for the integration-test simulator
dohertj2 created branch phase-3-pr43-pymodbus-swap in dohertj2/lmxopcua 2026-04-18 20:37:04 -04:00
dohertj2 pushed to phase-3-pr43-pymodbus-swap at dohertj2/lmxopcua 2026-04-18 20:37:04 -04:00
a05b84858d Phase 3 PR 43 — Swap ModbusPal to pymodbus for the integration-test simulator. Replaces the .xmpp profiles shipped in PR 42 with pymodbus 3.13.0 ModbusSimulatorServer JSON configs in tests/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests/Pymodbus/. Substantive reasons for the swap (rationale block in the test-plan doc): ModbusPal 1.6b is abandoned (last release ~2019), Java GUI-only with no headless mode in the official JAR, and only exposes 2 of the 4 standard Modbus tables (holding_registers + coils — no input_registers, no discrete_inputs). pymodbus is current stable, pure Python CLI (pip install pymodbus[simulator]==3.13.0), exposes all four tables, has built-in declarative actions (increment / random / timestamp / uptime) for dynamic registers, supports custom Python actions for anything more complex, and ships an optional aiohttp-based web UI / REST API for live inspection. Pip-installable on Windows; sidesteps the privileged-port admin requirement by defaulting to TCP 5020.
dohertj2 pushed to v2 at dohertj2/lmxopcua 2026-04-18 20:12:42 -04:00
c59ac9e52d Merge pull request 'Phase 3 PR 42 — ModbusPal simulator profiles for Standard + DL205/DL260' (#41) from phase-3-pr42-modbuspal-profiles into v2
02a0e8efd1 Phase 3 PR 42 — ModbusPal simulator profiles for Standard Modbus + DL205/DL260 quirks. Two hand-authored .xmpp profiles in tests/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests/ModbusPal/ that integration tests load via the GUI to drive the suite without a real PLC. Both well-formed XML (verified via PowerShell [xml] cast); both copied to test-output as PreserveNewest content per the existing csproj rule.
Compare 2 commits »
dohertj2 merged pull request dohertj2/lmxopcua#41 2026-04-18 20:12:41 -04:00
Phase 3 PR 42 — ModbusPal simulator profiles for Standard + DL205/DL260