Promotes DirectLogicAddress + MelsecAddress from "utility helpers an engineer
calls manually" to "first-class branch of ModbusAddressParser." Users can now
paste DL205-native (V2000, Y0, C100, X17, SP10) and MELSEC-native (D100, M50,
X20 hex/octal, Y0) addresses directly into TagConfig and the parser handles
the PLC-native → Modbus PDU translation.
Changes:
- Both helper files moved into the shared Driver.Modbus.Addressing assembly
(same namespace, zero-churn for callers). Required because the parser
needs to call them and the dependency direction is parser→helpers, not
the other way.
- New ModbusFamily enum (Generic / DL205 / MELSEC) on
ModbusDriverOptions.Family. Generic preserves pre-#144 behaviour exactly.
- ModbusDriverOptions.MelsecSubFamily picks the X/Y notation (Q_L_iQR hex
vs F_iQF octal). Default Q_L_iQR.
- ModbusAddressParser.Parse now takes optional family + sub-family hints.
When non-Generic, family-native parsing runs FIRST; on miss falls back to
Modicon / mnemonic. Cross-family ambiguity (C100 = Modicon coil under
Generic, DL205 control relay under DL205) is unambiguous within one
driver instance.
- Suffix grammar composes with native addresses: V2000:F:CDAB:5 parses
end-to-end as DL205 V-memory at PDU 1024 + Float32 + word-swap + array of 5.
- Bit suffix composes too: V2000.7 parses as bit 7 of HR[1024].
- Factory DTO fields Family / MelsecSubFamily flow through to BuildTag so
the JSON binding can drive everything per-driver.
Tests: 16 new ModbusFamilyParserTests covering DL205 V/Y/C/X/SP, MELSEC
D/M/X/Y, sub-family hex-vs-octal disambiguation, cross-family C100 ambiguity,
fallback to Modicon when native misses, and grammar composition with bit/
byte-order/array modifiers. Existing 91 parser tests still green; 220 driver
tests still green.
Caveat: bank-base offsets for MELSEC X/Y/M default to 0 in the grammar
string. Sites with non-zero "Modbus Device Assignment Parameter" bases must
use the structured tag form to override — addressed in the docs refresh
(#138).
Adds the full Wonderware/Kepware/Ignition-style address suffix grammar so
users paste tag spreadsheets without per-tag manual translation:
<region><offset>[.<bit>][:<type>[<len>]][:<order>][:<count>]
Examples that now parse end-to-end:
40001 HoldingRegisters[0], Int16
400001 same, 6-digit form
40001.5 bit 5 of HR[0]
40001:F Float32 (HR[0..1])
40001:F:CDAB word-swapped Float32
40001:STR20 20-char ASCII string
HR1:DI Int32 via mnemonic region
C100 Coils[99] (mnemonic)
40001:F:5 Float32[5] array (3-field shorthand)
40001:I:CDAB:10 Int16[10] word-swapped (4-field strict)
Driver-side plumbing:
- ModbusAddressParser + ParsedModbusAddress in the shared Addressing
assembly. 91 parser tests (every grammar variant + malformed shapes).
- ModbusDataType / ModbusByteOrder moved to shared (with the same namespace
so callers compile unchanged). ModbusByteOrder gains ByteSwap (BADC) and
FullReverse (DCBA) alongside the existing BigEndian (ABCD) and WordSwap
(CDAB).
- NormalizeWordOrder extended to honor all four orders for both 4-byte and
8-byte values. Old WordSwap behavior preserved bit-for-bit.
- ModbusTagDefinition gains optional ArrayCount.
- ReadOneAsync / WriteOneAsync handle array fan-out: one FC03/04 read covers
N consecutive register-typed elements, decoded into a typed array (short[],
float[], etc.). Coil arrays use FC01 reads + FC15 writes (FakeTransport
in tests gains FC15 support to match).
- DriverAttributeInfo IsArray / ArrayDim flow from ArrayCount so the OPC UA
address space surfaces ValueRank=1 + ArrayDimensions to clients.
- ModbusDriverFactoryExtensions gains AddressString DTO field. When
present, the parser drives Region/Address/DataType/ByteOrder/Bit/
StringLength/ArrayCount; structured fields (Writable, WriteIdempotent,
StringByteOrder) still come from the DTO. Existing structured tag rows
keep working unchanged.
Tests: 91 parser unit tests (Driver.Modbus.Addressing.Tests, all green) +
204 driver tests including new ModbusByteOrderTests (BADC/DCBA roundtrips
across Int32/Float32/Float64) and ModbusArrayTests (Int16[5], Float32[3]
CDAB, Coil[10], length-mismatch error, IsArray/ArrayDim discovery).
Solution-wide build clean.
Caveat: grammar names (type codes, byte-order mnemonics, the :count
shorthand) were synthesized from training-era vendor docs. Verify against
current Kepware Modbus Ethernet Driver Help and Ignition Modbus Addressing
manuals before freezing for production deployments — naming may need a
back-compat layer if vendor wording has shifted.
Foundation for the Modbus addressing-grammar work tracked in #137-#145. Adds
ModbusModiconAddress.Parse / TryParse that turns classic Modicon strings
(40001 / 400001 / 30001 / 00001 / 10001) into (Region, ushort PduOffset).
Also extracts ModbusRegion to a new Driver.Modbus.Addressing assembly so the
Admin UI (#145) can reference the addressing surface without taking a dep on
the wire driver. The new assembly intentionally extends the same
ZB.MOM.WW.OtOpcUa.Driver.Modbus namespace as the driver — callers see the
type as if it lived in one place; only the project layout changes. No
existing call site needed editing (zero-churn move).
Behaviour:
- Single leading digit selects region (0=Coils, 1=DiscreteInputs,
3=InputRegisters, 4=HoldingRegisters).
- 5-digit form: trailing 4 digits are 1-based register, supports 1..9999.
- 6-digit form: trailing 5 digits are 1-based register, supports 1..65536
(full PDU address space).
- Strict 5-or-6 length check; whitespace trimmed; clear FormatException
diagnostics for every malformed shape (wrong length, non-digit body,
illegal leading digit, register zero, register overflow).
29/29 new unit tests pass. Full Driver.Modbus suite (182 tests) and the
solution-wide build still green after the ModbusRegion move.