Phase 3 PR 59 -- MelsecAddress helper with family selector (hex vs octal X/Y) #58

Merged
dohertj2 merged 1 commits from phase-3-pr59-melsec-address-helper into v2 2026-04-18 23:10:30 -04:00
Owner

Summary

Adds MelsecAddress static helper + MelsecFamily enum {Q_L_iQR, F_iQF}.

The headline MELSEC trap: X20 on a Q-series PLC means DI 32 (hex 0x20), but on an FX3U it means DI 16 (octal 0o20). Same string, different value — the #1 MELSEC driver bug source. The helper forces the caller to name the family per tag; no sensible default.

Helpers:

  • XInputToDiscrete(x, family, xBankBase=0) — hex on Q/L/iQ-R, octal on FX/iQ-F
  • YOutputToCoil(y, family, yBankBase=0) — same rule
  • MRelayToCoil(m, mBankBase=0) — decimal on all families
  • DRegisterToHolding(d, dBankBase=0) — decimal on all families

Bank-base argument handles per-site Modbus Device Assignment offsets (QJ71MT91 default places X at DI 8192+, etc.).

Validation

  • 34 new unit tests: hex/octal sweeps, family-trap assertions (X20 → 32 vs 16), non-hex/non-octal rejection, bank-base adjustment, overflow guards
  • 176/176 Modbus.Tests pass (143 prior + 34 Melsec)
  • No integration regression
## Summary Adds `MelsecAddress` static helper + `MelsecFamily` enum `{Q_L_iQR, F_iQF}`. The headline MELSEC trap: `X20` on a Q-series PLC means DI **32** (hex `0x20`), but on an FX3U it means DI **16** (octal `0o20`). Same string, different value — the #1 MELSEC driver bug source. The helper forces the caller to name the family per tag; no sensible default. Helpers: - `XInputToDiscrete(x, family, xBankBase=0)` — hex on Q/L/iQ-R, octal on FX/iQ-F - `YOutputToCoil(y, family, yBankBase=0)` — same rule - `MRelayToCoil(m, mBankBase=0)` — decimal on all families - `DRegisterToHolding(d, dBankBase=0)` — decimal on all families Bank-base argument handles per-site Modbus Device Assignment offsets (QJ71MT91 default places X at DI 8192+, etc.). ## Validation - 34 new unit tests: hex/octal sweeps, family-trap assertions (`X20` → 32 vs 16), non-hex/non-octal rejection, bank-base adjustment, overflow guards - 176/176 Modbus.Tests pass (143 prior + 34 Melsec) - No integration regression
dohertj2 added 1 commit 2026-04-18 23:10:26 -04:00
Phase 3 PR 59 -- MelsecAddress helper for MELSEC X/Y hex-vs-octal family trap + D/M bank bases. Adds MelsecAddress static class with XInputToDiscrete, YOutputToCoil, MRelayToCoil, DRegisterToHolding helpers and a MelsecFamily enum {Q_L_iQR, F_iQF} that drives whether X/Y addresses are parsed as hex (Q-series convention) or octal (FX-series convention). This is the #1 MELSEC driver bug source per docs/v2/mitsubishi.md: the string 'X20' on a MELSEC-Q means DI 32 (hex 0x20) while the same string on an FX3U means DI 16 (octal 0o20). The helper forces the caller to name the family explicitly; no 'sensible default' because wrong defaults just move the bug. Key design decisions: (1) Family is an enum argument, not a helper-level static-selector, because real deployments have BOTH Q-series and FX-series PLCs on the same gateway -- one driver instance per device means family must be per-tag, not per-driver. (2) Bank base is a ushort argument defaulting to 0. Real QJ71MT91/LJ71MT91 assignment blocks commonly place X at DI 8192+, Y at coil 8192+, etc. to leave the low-address range for D-registers; the helper takes the site's configured base as runtime config rather than a compile-time constant. Matches the 'driver opt-in per tag' pattern DirectLogicAddress established for DL260. (3) M-relay and D-register are DECIMAL on every MELSEC family -- docs explicitly; the MELSEC confusion is only about X/Y, not about data registers or internal relays. Helpers reject non-numeric M/D addresses and honor bank bases the same way. (4) Parser walks digits manually for both hex and octal (instead of int.Parse with NumberStyles) so non-hex / non-octal characters give a clear ArgumentException with the offending char + family name. Prevents a subtle class of bugs where int.Parse('X20', Hex) silently returns 32 even for F_iQF callers. Unit tests (MelsecAddressTests, 34 facts): XInputToDiscrete_QLiQR_parses_hex theory (X0, X9, XA, XF, X10, X20, X1FF + lowercase); XInputToDiscrete_FiQF_parses_octal theory (X0, X7, X10, X20, X777); YOutputToCoil equivalents; Same_address_string_decodes_differently_between_families (the headline trap, X20 => 32 on Q vs 16 on FX); reject-non-octal / reject-non-hex / reject-empty / overflow facts; honors-bank-base for X and M and D. 176/176 Modbus.Tests pass (143 prior + 34 new Melsec). No driver core changes -- this is purely a new helper class in the Driver.Modbus project. PR 60 wires it into integration tests against the mitsubishi pymodbus profile. d4c1873998
dohertj2 merged commit 65de2b4a09 into v2 2026-04-18 23:10:30 -04:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: dohertj2/lmxopcua#58