fix(modbus): review r1 - resolved address keys win only when the editor pinned them
N1: the m2 precedence trusted region/address whenever the keys were present,
but the AdminUI model stamps defaulted HoldingRegisters/0 on every save, so a
string-only tag (CSV import, or any editor toggle) silently became HR[0] -
reads AND writes. The resolved keys are now authoritative only when the editor
marked them ("addressResolved": true, written by Resolve); an unmarked
addressString is always resolved through the parser with the driver's family.
The model no longer stamps address fields for a string-only tag, CSV export
leaves its address cells blank, and the editor hint distinguishes string-only
from pinned tags. Tests: CSV import and editor save of a string-only tag, a
stamped-default blob, and the pinned positive control across families.
n1: docs/v2/modbus-addressing.md updated (FC23 opt-in, maxReadGap semantics,
coalescing scope, addressString/family rules, coil coercion, refusals).
n2: form round-trip tests for UseFC23ForMultiRegisterWrites /
DisableReadCoalescing. n4: the editor keeps unresolved address text when the
parent echoes back its own emit. n3: noted, no change.
Refs #741
Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
This commit is contained in:
+29
-6
@@ -35,10 +35,11 @@ public static class ModbusTagDefinitionFactory
|
||||
=> FromTagConfig(tagConfig, rawPath, ModbusFamily.Generic, MelsecFamily.Q_L_iQR, out def);
|
||||
|
||||
/// <summary>
|
||||
/// Family-aware overload. The persisted <c>region</c> + <c>address</c> keys are AUTHORITATIVE whenever
|
||||
/// present — the editor resolves an <c>addressString</c> with the driver's family and persists the
|
||||
/// result, so a later Family / MelsecSubFamily change can never silently remap a live tag. A blob that
|
||||
/// carries ONLY an <c>addressString</c> (CSV / API authoring, the Modicon / mnemonic / family-native
|
||||
/// Family-aware overload. The persisted <c>region</c> + <c>address</c> keys are AUTHORITATIVE when they
|
||||
/// were actually authored — a classic tag with no <c>addressString</c>, or one the editor resolved and
|
||||
/// marked (<see cref="AddressResolvedKey"/>) — so a later Family / MelsecSubFamily change can never
|
||||
/// silently remap such a tag. A blob whose <c>addressString</c> is unmarked (CSV / API authoring, even if
|
||||
/// a model round-trip stamped default region/address beside it; the Modicon / mnemonic / family-native
|
||||
/// grammar of <see cref="ModbusAddressParser"/> — <c>40001:F:CDAB</c>, DL205 <c>V2000</c>, MELSEC
|
||||
/// <c>D100</c>) is resolved here with the DRIVER's <paramref name="family"/> /
|
||||
/// <paramref name="melsecSubFamily"/>. Without an <c>addressString</c> this is exactly the 3-argument
|
||||
@@ -69,7 +70,7 @@ public static class ModbusTagDefinitionFactory
|
||||
ushort stringLength;
|
||||
int? arrayCount;
|
||||
var addressText = ReadAddressString(root, out var addressStringInvalid);
|
||||
var hasResolvedKeys = root.TryGetProperty("region", out _) && root.TryGetProperty("address", out _);
|
||||
var hasResolvedKeys = ResolvedKeysAuthoritative(root, addressText);
|
||||
if (!hasResolvedKeys && addressStringInvalid) return false;
|
||||
if (!hasResolvedKeys && addressText is not null)
|
||||
{
|
||||
@@ -174,6 +175,28 @@ public static class ModbusTagDefinitionFactory
|
||||
(ModbusFamily.MELSEC, MelsecFamily.F_iQF),
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// The key the tag editor writes beside the resolved <c>region</c> / <c>address</c> when it resolved an
|
||||
/// <c>addressString</c> with the driver's family (<c>"addressResolved": true</c>). Its presence — not the
|
||||
/// mere presence of <c>region</c> / <c>address</c> — is what makes the resolved keys authoritative over
|
||||
/// the string: the AdminUI model stamps DEFAULTED <c>region</c>/<c>address</c> (HoldingRegisters / 0) on
|
||||
/// every save, so key presence alone turned a string-only CSV/API tag into HR[0] on its first edit.
|
||||
/// </summary>
|
||||
public const string AddressResolvedKey = "addressResolved";
|
||||
|
||||
/// <summary>
|
||||
/// Whether the persisted <c>region</c> / <c>address</c> are the tag's real address: they are present AND
|
||||
/// either there is no <c>addressString</c> (a classic region + address tag) or the editor marked them as
|
||||
/// resolved from it (<see cref="AddressResolvedKey"/>). A blob carrying an <c>addressString</c> without
|
||||
/// the marker resolves the string through the parser, never the (possibly defaulted) keys.
|
||||
/// </summary>
|
||||
private static bool ResolvedKeysAuthoritative(JsonElement root, string? addressText)
|
||||
{
|
||||
if (!root.TryGetProperty("region", out _) || !root.TryGetProperty("address", out _)) return false;
|
||||
if (addressText is null) return true;
|
||||
return root.TryGetProperty(AddressResolvedKey, out var marker) && marker.ValueKind == JsonValueKind.True;
|
||||
}
|
||||
|
||||
/// <summary>Reads the optional <c>addressString</c>: null when absent or blank; flags a non-string value.</summary>
|
||||
private static string? ReadAddressString(JsonElement root, out bool invalid)
|
||||
{
|
||||
@@ -320,7 +343,7 @@ public static class ModbusTagDefinitionFactory
|
||||
private static void DescribeRejection(JsonElement root, List<string> warnings)
|
||||
{
|
||||
var addressText = ReadAddressString(root, out var addressStringInvalid);
|
||||
var hasResolvedKeys = root.TryGetProperty("region", out _) && root.TryGetProperty("address", out _);
|
||||
var hasResolvedKeys = ResolvedKeysAuthoritative(root, addressText);
|
||||
if (!hasResolvedKeys && addressStringInvalid)
|
||||
{
|
||||
warnings.Add("Modbus TagConfig 'addressString' is not a string — the tag will not resolve (BadNodeIdUnknown).");
|
||||
|
||||
Reference in New Issue
Block a user