docs(xmldoc): fill missing XML docs + strip tracking-ID comments across src
v2-ci / build (push) Failing after 41s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
v2-ci / build (push) Failing after 41s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
Adds <summary>/<param>/<returns>/<inheritdoc> where missing and removes project bookkeeping IDs (task/tracking refs) from shipped code comments, so the docs read cleanly and CommentChecker is quiet except for known false positives (PLC/protocol terms, event/IEqualityComparer inheritdoc). Doc/comment-only; no logic changed; solution builds clean.
This commit is contained in:
@@ -27,6 +27,7 @@ public static class DirectLogicAddress
|
||||
/// V-address that may fall in either bank, call <see cref="VMemoryToPdu"/>, which routes
|
||||
/// system-bank addresses through <see cref="SystemVMemoryToPdu"/> instead.
|
||||
/// </remarks>
|
||||
/// <returns>The 0-based Modbus PDU address for the given user V-memory address.</returns>
|
||||
/// <exception cref="ArgumentException">Input is null / empty / contains non-octal digits (8,9).</exception>
|
||||
/// <exception cref="OverflowException">Parsed value exceeds ushort.MaxValue (0xFFFF).</exception>
|
||||
public static ushort UserVMemoryToPdu(string vAddress)
|
||||
@@ -80,11 +81,16 @@ public static class DirectLogicAddress
|
||||
/// <remarks>Octal 40400 == decimal 16640 (0x4100).</remarks>
|
||||
public const ushort SystemVMemoryOctalBase = 0x4100; // octal 40400 decoded
|
||||
|
||||
/// <summary>
|
||||
/// Convert a 0-based offset within the system V-memory bank to its absolute Modbus PDU
|
||||
/// address, relocated to the fixed <see cref="SystemVMemoryBasePdu"/> base.
|
||||
/// </summary>
|
||||
/// <param name="offsetWithinSystemBank">
|
||||
/// 0-based register offset within the system bank. Pass 0 for V40400 itself; pass 1 for
|
||||
/// V40401 (octal), and so on. NOT an octal-decoded value — the system bank lives at
|
||||
/// consecutive PDU addresses, so the offset is plain decimal.
|
||||
/// </param>
|
||||
/// <returns>The 0-based Modbus PDU address for the given system-bank offset.</returns>
|
||||
public static ushort SystemVMemoryToPdu(ushort offsetWithinSystemBank)
|
||||
{
|
||||
var pdu = SystemVMemoryBasePdu + offsetWithinSystemBank;
|
||||
@@ -107,6 +113,7 @@ public static class DirectLogicAddress
|
||||
/// octal-decoded value itself: V40400 → PDU 0x2100, V40401 → 0x2101, and so on.
|
||||
/// See <c>docs/v2/dl205.md</c> §V-Memory Addressing.
|
||||
/// </remarks>
|
||||
/// <returns>The 0-based Modbus PDU address for the given V-memory address.</returns>
|
||||
/// <exception cref="ArgumentException">Input is null / empty / contains non-octal digits.</exception>
|
||||
/// <exception cref="OverflowException">The result exceeds the 16-bit Modbus PDU range.</exception>
|
||||
public static ushort VMemoryToPdu(string vAddress)
|
||||
@@ -116,11 +123,11 @@ public static class DirectLogicAddress
|
||||
return (ushort)octalValue;
|
||||
|
||||
// System bank: the registers are contiguous from V40400, so the offset within the bank
|
||||
// is the plain decimal distance from the octal base, not another octal decode.
|
||||
// Driver.Modbus.Addressing-011: the subtraction result is provably <= 0xBEFF because
|
||||
// DecodeOctalVAddress already caps octalValue at 0xFFFF, so no overflow guard is needed
|
||||
// here. The real overflow guard (pdu > ushort.MaxValue) lives in SystemVMemoryToPdu and
|
||||
// is reachable only when that helper is called directly with a large explicit offset.
|
||||
// is the plain decimal distance from the octal base, not another octal decode. The
|
||||
// subtraction result is provably <= 0xBEFF because DecodeOctalVAddress already caps
|
||||
// octalValue at 0xFFFF, so no overflow guard is needed here. The real overflow guard
|
||||
// (pdu > ushort.MaxValue) lives in SystemVMemoryToPdu and is reachable only when that
|
||||
// helper is called directly with a large explicit offset.
|
||||
var offsetWithinBank = (ushort)(octalValue - SystemVMemoryOctalBase);
|
||||
return SystemVMemoryToPdu(offsetWithinBank);
|
||||
}
|
||||
@@ -156,6 +163,7 @@ public static class DirectLogicAddress
|
||||
/// ladder-logic editor's notation.
|
||||
/// </summary>
|
||||
/// <param name="yAddress">The Y-output address (octal, with optional Y prefix).</param>
|
||||
/// <returns>The 0-based Modbus coil address.</returns>
|
||||
public static ushort YOutputToCoil(string yAddress) =>
|
||||
AddOctalOffset(YOutputBaseCoil, StripPrefix(yAddress, 'Y'));
|
||||
|
||||
@@ -164,6 +172,7 @@ public static class DirectLogicAddress
|
||||
/// 0-based Modbus coil address.
|
||||
/// </summary>
|
||||
/// <param name="cAddress">The C-relay address (octal, with optional C prefix).</param>
|
||||
/// <returns>The 0-based Modbus coil address.</returns>
|
||||
public static ushort CRelayToCoil(string cAddress) =>
|
||||
AddOctalOffset(CRelayBaseCoil, StripPrefix(cAddress, 'C'));
|
||||
|
||||
@@ -173,6 +182,7 @@ public static class DirectLogicAddress
|
||||
/// exception — the CPU sizes the table to configured I/O, not installed modules.
|
||||
/// </summary>
|
||||
/// <param name="xAddress">The X-input address (octal, with optional X prefix).</param>
|
||||
/// <returns>The 0-based Modbus discrete-input address.</returns>
|
||||
public static ushort XInputToDiscrete(string xAddress) =>
|
||||
AddOctalOffset(XInputBaseDiscrete, StripPrefix(xAddress, 'X'));
|
||||
|
||||
@@ -181,6 +191,7 @@ public static class DirectLogicAddress
|
||||
/// Modbus discrete-input address. Accepts <c>"SP"</c> prefix case-insensitively.
|
||||
/// </summary>
|
||||
/// <param name="spAddress">The SP special-relay address (octal, with optional SP prefix).</param>
|
||||
/// <returns>The 0-based Modbus discrete-input address.</returns>
|
||||
public static ushort SpecialToDiscrete(string spAddress)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(spAddress))
|
||||
|
||||
@@ -53,6 +53,7 @@ public static class MelsecAddress
|
||||
/// 0-based Modbus DI address the assignment-block has configured X0 to land at.
|
||||
/// Typical default on QJ71MT91 sample projects: 0. Pass the site-specific value.
|
||||
/// </param>
|
||||
/// <returns>The 0-based Modbus discrete-input address.</returns>
|
||||
public static ushort XInputToDiscrete(string xAddress, MelsecFamily family, ushort xBankBase = 0) =>
|
||||
AddFamilyOffset(xBankBase, StripPrefix(xAddress, 'X'), family);
|
||||
|
||||
@@ -63,6 +64,7 @@ public static class MelsecAddress
|
||||
/// <param name="yAddress">MELSEC Y address. <c>Y</c> prefix optional, case-insensitive.</param>
|
||||
/// <param name="family">The PLC family — determines whether the trailing digits are hex or octal.</param>
|
||||
/// <param name="yBankBase">0-based Modbus coil address the assignment-block has configured Y0 to land at.</param>
|
||||
/// <returns>The 0-based Modbus coil address.</returns>
|
||||
public static ushort YOutputToCoil(string yAddress, MelsecFamily family, ushort yBankBase = 0) =>
|
||||
AddFamilyOffset(yBankBase, StripPrefix(yAddress, 'Y'), family);
|
||||
|
||||
@@ -73,6 +75,7 @@ public static class MelsecAddress
|
||||
/// </summary>
|
||||
/// <param name="mAddress">MELSEC M address. <c>M</c> prefix optional, case-insensitive.</param>
|
||||
/// <param name="mBankBase">0-based Modbus coil address the assignment-block has configured M0 to land at.</param>
|
||||
/// <returns>The 0-based Modbus coil address.</returns>
|
||||
public static ushort MRelayToCoil(string mAddress, ushort mBankBase = 0)
|
||||
{
|
||||
var digits = StripPrefix(mAddress, 'M');
|
||||
@@ -93,6 +96,7 @@ public static class MelsecAddress
|
||||
/// </summary>
|
||||
/// <param name="dAddress">MELSEC D address. <c>D</c> prefix optional, case-insensitive.</param>
|
||||
/// <param name="dBankBase">0-based Modbus holding register address the assignment-block has configured D0 to land at.</param>
|
||||
/// <returns>The 0-based Modbus holding register address.</returns>
|
||||
public static ushort DRegisterToHolding(string dAddress, ushort dBankBase = 0)
|
||||
{
|
||||
var digits = StripPrefix(dAddress, 'D');
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Modbus;
|
||||
/// spreadsheets from any of those tools without per-tag manual translation.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Examples (post-#146 type codes — verified against Wonderware DASMBTCP + Ignition):
|
||||
/// Examples (type codes — verified against Wonderware DASMBTCP + Ignition):
|
||||
/// <list type="bullet">
|
||||
/// <item><c>40001</c> — HoldingRegisters[0], Int16 (default).</item>
|
||||
/// <item><c>400001</c> — HoldingRegisters[0], Int16 (6-digit form).</item>
|
||||
@@ -30,7 +30,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Modbus;
|
||||
/// </list>
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <b>Grammar scope — out of band (Driver.Modbus.Addressing-007):</b> per-string byte
|
||||
/// <b>Grammar scope — out of band:</b> per-string byte
|
||||
/// order (<see cref="ModbusStringByteOrder"/>) is NOT expressible through this grammar.
|
||||
/// The DL205 low-byte-first string-packing knob is configurable only via the structured
|
||||
/// tag form (the driver's <c>ModbusTagDefinition.StringByteOrder</c> field). The 3rd
|
||||
@@ -46,7 +46,7 @@ public static class ModbusAddressParser
|
||||
/// <returns>The parsed Modbus address.</returns>
|
||||
public static ParsedModbusAddress Parse(string address) => Parse(address, ModbusFamily.Generic, MelsecFamily.Q_L_iQR);
|
||||
|
||||
/// <summary>Parse with a family hint (#144 family-native branch).</summary>
|
||||
/// <summary>Parse with a family hint (family-native branch).</summary>
|
||||
/// <param name="address">The address string to parse.</param>
|
||||
/// <param name="family">The Modbus family hint.</param>
|
||||
/// <param name="melsecSubFamily">The MELSEC subfamily hint.</param>
|
||||
@@ -113,7 +113,7 @@ public static class ModbusAddressParser
|
||||
// else surfaces a clear error in whichever slot it lands.
|
||||
if (parts.Length == 3)
|
||||
{
|
||||
// Driver.Modbus.Addressing-002: reject an empty 3rd field (e.g. "40001:F:") rather
|
||||
// Reject an empty 3rd field (e.g. "40001:F:") rather
|
||||
// than silently dropping it. Enumerable.All returns true for an empty sequence, so
|
||||
// without this guard the empty string would be classified as a valid array count and
|
||||
// then quietly ignored, leaving the user with no diagnostic for a typo'd trailing colon.
|
||||
@@ -126,7 +126,7 @@ public static class ModbusAddressParser
|
||||
else if (parts[2].All(char.IsDigit)) countPart = parts[2];
|
||||
else
|
||||
{
|
||||
// Driver.Modbus.Addressing-003: when TryParseByteOrder would fail on a 4-letter
|
||||
// When TryParseByteOrder would fail on a 4-letter
|
||||
// token that looks like a type code (e.g. BOOL), improve the diagnostic so the
|
||||
// user knows field 3 is a byte order and field 2 is the type.
|
||||
var mightBeTypeCode = parts[2].Length == 4 && parts[2].All(char.IsLetterOrDigit);
|
||||
@@ -223,7 +223,7 @@ public static class ModbusAddressParser
|
||||
}
|
||||
|
||||
// Optional bit suffix: '.N' at the end, N in 0..15. Strip before parsing region/offset.
|
||||
// Driver.Modbus.Addressing-004: use LastIndexOf so a multi-dot input like "40001.5.3"
|
||||
// Use LastIndexOf so a multi-dot input like "40001.5.3"
|
||||
// produces a descriptive error ("bit index '5.3' must be 0..15") rather than silently
|
||||
// parsing "5" as the bit and leaving ".3" as part of the address text. Also validate
|
||||
// the address segment is non-empty (a leading dot like ".5" is not a valid Modbus addr).
|
||||
@@ -252,7 +252,7 @@ public static class ModbusAddressParser
|
||||
bit = bitVal;
|
||||
}
|
||||
|
||||
// Family-native branch (#144) — when a non-Generic family is configured, try its native
|
||||
// Family-native branch — when a non-Generic family is configured, try its native
|
||||
// syntax first. Successful native parse wins; failure falls through to Modicon / mnemonic.
|
||||
// The order matters for cross-family ambiguity: DL205 'C100' is a control relay, not a
|
||||
// Modicon coil, when the user has explicitly selected DL205.
|
||||
@@ -271,11 +271,11 @@ public static class ModbusAddressParser
|
||||
if (TryParseMnemonicAddress(addrText, out region, out offset, out error))
|
||||
return true;
|
||||
|
||||
// Fall back to Modicon (5/6-digit). Reuses #136's parser.
|
||||
// Fall back to Modicon (5/6-digit). Reuses the shared Modicon parser.
|
||||
if (ModbusModiconAddress.TryParse(addrText, out region, out offset, out error))
|
||||
return true;
|
||||
|
||||
// Driver.Modbus.Addressing-005: when a non-Generic family was configured and the
|
||||
// When a non-Generic family was configured and the
|
||||
// family-native parser set a specific error (meaning the address matched a recognised
|
||||
// family prefix but the value was invalid, e.g. "contains non-octal digit"), prefer
|
||||
// that error over the generic Modicon fallback diagnostic, which otherwise says
|
||||
@@ -371,7 +371,7 @@ public static class ModbusAddressParser
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Driver.Modbus.Addressing-006: a try-parse method must never throw, so any helper
|
||||
// A try-parse method must never throw, so any helper
|
||||
// exception is converted to a structured error. The current helpers throw only
|
||||
// ArgumentException (incl. ArgumentOutOfRangeException) and OverflowException, but
|
||||
// catching narrowly would silently break the TryParse contract if a helper ever
|
||||
@@ -447,17 +447,17 @@ public static class ModbusAddressParser
|
||||
return true;
|
||||
}
|
||||
|
||||
// #146 — codes aligned with Wonderware DASMBTCP + Ignition Modbus driver after the
|
||||
// Codes aligned with Wonderware DASMBTCP + Ignition Modbus driver after the
|
||||
// 2026-04-25 vendor-doc verification:
|
||||
// - `:I` is Int32 (Wonderware: "letter 'I' follow ... 32-bit signed quantity, two
|
||||
// consecutive registers"). Ignition's HRI is also Int32. The pre-#146 mapping
|
||||
// consecutive registers"). Ignition's HRI is also Int32. The previous mapping
|
||||
// `:I` = Int16 silently produced wrong-typed data + offset-shifted neighbours when
|
||||
// a tag spreadsheet was pasted from another vendor.
|
||||
// - `:S` is the explicit Int16 code (Wonderware: "letter 'S' ... 16-bit signed").
|
||||
// - `:US` is UInt16 (Ignition: HRUS = "Unsigned Short").
|
||||
// - `:UI` is UInt32 (parallel to `:I` shape; matches Ignition HRUI).
|
||||
// - `:I_64` / `:UI_64` for 64-bit (Ignition HRI_64 / HRUI_64 underscore-N convention).
|
||||
// - `:BCD_32` for 32-bit BCD (Ignition HRBCD_32). The pre-#146 `:LBCD` is dropped.
|
||||
// - `:BCD_32` for 32-bit BCD (Ignition HRBCD_32). The previous `:LBCD` is dropped.
|
||||
// - HR/IR with no explicit type still default to Int16 (matches Ignition `HR`).
|
||||
type = text.ToUpperInvariant() switch
|
||||
{
|
||||
@@ -503,13 +503,12 @@ public static class ModbusAddressParser
|
||||
|
||||
if ((int)order == -1)
|
||||
{
|
||||
// Driver.Modbus.Addressing-003: if the unknown token is a VALID type code in this
|
||||
// parser, direct the user to put it in field 2 instead. Only BOOL (4-letter) and
|
||||
// STR<n> are valid type codes that could appear here. "REAL", "DINT", "UINT" look
|
||||
// like type names from other tools (STEP 7 / RSLogix) but are NOT valid in this
|
||||
// parser's type table (Driver.Modbus.Addressing-010) — listing them here would give
|
||||
// advice that leads to a second error ("Unknown type code 'REAL'").
|
||||
var upper = text.ToUpperInvariant();
|
||||
// If the unknown token is a VALID type code in this parser, direct the user to put
|
||||
// it in field 2 instead. Only BOOL (4-letter) and STR<n> are valid type codes that
|
||||
// could appear here. "REAL", "DINT", "UINT" look like type names from other tools
|
||||
// (STEP 7 / RSLogix) but are NOT valid in this parser's type table — listing them
|
||||
// here would give advice that leads to a second error ("Unknown type code 'REAL'").
|
||||
var isActualTypeCode = upper is "BOOL"
|
||||
|| (text.Length > 3 && text.StartsWith("STR", StringComparison.OrdinalIgnoreCase)
|
||||
&& text[3..].All(char.IsDigit));
|
||||
|
||||
@@ -90,7 +90,7 @@ public enum ModbusByteOrder
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// <b>Grammar scope (Driver.Modbus.Addressing-007):</b> this enum is intentionally NOT
|
||||
/// <b>Grammar scope:</b> this enum is intentionally NOT
|
||||
/// expressible through the <see cref="ModbusAddressParser"/> grammar string. The grammar
|
||||
/// has no token form for it, and <see cref="ParsedModbusAddress"/> has no field for it —
|
||||
/// a DL205 string tag parsed from the grammar always carries the driver's default order.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
namespace ZB.MOM.WW.OtOpcUa.Driver.Modbus;
|
||||
|
||||
/// <summary>
|
||||
/// PLC family selector that drives the parser's family-native branch (#144). When the
|
||||
/// PLC family selector that drives the parser's family-native branch. When the
|
||||
/// driver is configured for a specific family, address strings using that family's native
|
||||
/// notation (DirectLOGIC <c>V2000</c> octal, MELSEC <c>X20</c> hex/octal, etc.) are
|
||||
/// translated to <see cref="ModbusRegion"/> + PDU offset directly — without forcing
|
||||
@@ -10,7 +10,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Modbus;
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// When set to <see cref="Generic"/> (the default), the parser only accepts Modicon and
|
||||
/// mnemonic forms — preserves pre-#144 behaviour exactly. Setting a non-Generic family
|
||||
/// mnemonic forms — preserves the previous behaviour exactly. Setting a non-Generic family
|
||||
/// is the only way to enable family-native parsing.
|
||||
/// </para>
|
||||
/// <para>
|
||||
|
||||
@@ -45,6 +45,7 @@ public static class ModbusModiconAddress
|
||||
/// <param name="region">The parsed Modbus region, or default if parsing fails.</param>
|
||||
/// <param name="offset">The zero-based PDU offset, or 0 if parsing fails.</param>
|
||||
/// <param name="error">The error message if parsing fails, or null on success.</param>
|
||||
/// <returns><c>true</c> if the address parsed successfully; otherwise <c>false</c>.</returns>
|
||||
public static bool TryParse(string? address, out ModbusRegion region, out ushort offset, out string? error)
|
||||
{
|
||||
region = default;
|
||||
|
||||
Reference in New Issue
Block a user