Merge b626-ablegacy-focas (batch #626+)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
@@ -388,6 +388,21 @@ public sealed class AbLegacyDriver : IDriver, IReadable, IWritable, ITagDiscover
|
||||
|
||||
try
|
||||
{
|
||||
// Indirect addresses (N7:[N7:0]) parse but are not resolved at runtime (#247) — answer the
|
||||
// refusal before a runtime is created for a name neither transport can encode.
|
||||
var preParsed = AbLegacyAddress.TryParse(def.Address, device.Options.PlcFamily);
|
||||
if (preParsed is null)
|
||||
{
|
||||
WarnMalformedOnce(device, def);
|
||||
results[i] = new DataValueSnapshot(null, AbLegacyStatusMapper.BadNodeIdUnknown, null, now);
|
||||
continue;
|
||||
}
|
||||
if (preParsed.IsIndirect)
|
||||
{
|
||||
results[i] = new DataValueSnapshot(null, AbLegacyStatusMapper.BadNotSupported, null, now);
|
||||
continue;
|
||||
}
|
||||
|
||||
var runtime = await EnsureTagRuntimeAsync(device, def, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
int status;
|
||||
@@ -401,7 +416,7 @@ public sealed class AbLegacyDriver : IDriver, IReadable, IWritable, ITagDiscover
|
||||
{
|
||||
await runtime.ReadAsync(cancellationToken).ConfigureAwait(false);
|
||||
status = runtime.GetStatus();
|
||||
var parsed = AbLegacyAddress.TryParse(def.Address);
|
||||
var parsed = AbLegacyAddress.TryParse(def.Address, device.Options.PlcFamily);
|
||||
if (status != 0)
|
||||
value = null;
|
||||
else if (IsArrayTag(def))
|
||||
@@ -488,7 +503,22 @@ public sealed class AbLegacyDriver : IDriver, IReadable, IWritable, ITagDiscover
|
||||
|
||||
try
|
||||
{
|
||||
var parsed = AbLegacyAddress.TryParse(def.Address);
|
||||
var parsed = AbLegacyAddress.TryParse(def.Address, device.Options.PlcFamily);
|
||||
|
||||
// A malformed address is a config error, not a comms fault (BadNodeIdUnknown, warned once per tag).
|
||||
if (parsed is null)
|
||||
{
|
||||
WarnMalformedOnce(device, def);
|
||||
results[i] = new WriteResult(AbLegacyStatusMapper.BadNodeIdUnknown);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Indirect addressing is parse-only (#247): refuse before any runtime or RMW read is created.
|
||||
if (parsed.IsIndirect)
|
||||
{
|
||||
results[i] = new WriteResult(AbLegacyStatusMapper.BadNotSupported);
|
||||
continue;
|
||||
}
|
||||
|
||||
// A write the transport refuses outright (native: a T/C/R status-bit sub-element such as T4:0.DN,
|
||||
// or any PLC-5 write) is answered BEFORE a runtime is created. Creating one first costs an init
|
||||
@@ -643,7 +673,10 @@ public sealed class AbLegacyDriver : IDriver, IReadable, IWritable, ITagDiscover
|
||||
Port: state.ParsedAddress.Port,
|
||||
CipPath: state.EffectiveCipPath,
|
||||
LibplctagPlcAttribute: state.Profile.LibplctagPlcAttribute,
|
||||
TagName: _options.Probe.ProbeAddress!,
|
||||
// Normalised exactly like tag paths (PLC-5 octal I/O -> decimal); an unparseable probe address is passed through
|
||||
// so the transport reports the failure as it always did.
|
||||
TagName: AbLegacyAddress.TryParse(_options.Probe.ProbeAddress, state.Options.PlcFamily)?.ToLibplctagName()
|
||||
?? _options.Probe.ProbeAddress!,
|
||||
Timeout: _options.Probe.Timeout);
|
||||
|
||||
IAbLegacyTagRuntime? probeRuntime = null;
|
||||
@@ -891,6 +924,19 @@ public sealed class AbLegacyDriver : IDriver, IReadable, IWritable, ITagDiscover
|
||||
keep();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A tag whose address does not parse for its device's PLC family (e.g. a PLC-5 octal I/O digit 8/9, a
|
||||
/// function-file letter off MicroLogix) is a configuration error: answered <c>BadNodeIdUnknown</c> with the
|
||||
/// handle/health untouched, and warned once per tag so a polled tag does not flood the log.
|
||||
/// </summary>
|
||||
private void WarnMalformedOnce(DeviceState device, AbLegacyTagDefinition def)
|
||||
{
|
||||
if (!device.MalformedAddressLogged.TryAdd(def.Name, 0)) return;
|
||||
_logger.LogWarning(
|
||||
"AbLegacy tag address is not valid for the device's PLC family. Driver={DriverInstanceId} Device={DeviceHostAddress} Reference={Reference} Address={Address} Family={Family}",
|
||||
_driverInstanceId, def.DeviceHostAddress, def.Name, def.Address, device.Options.PlcFamily);
|
||||
}
|
||||
|
||||
/// <summary>The create parameters for a tag's own runtime — shared by creation and the write-refusal check, so
|
||||
/// the refusal is asked about exactly the runtime a write would otherwise create.</summary>
|
||||
private AbLegacyTagCreateParams CreateParamsFor(DeviceState device, AbLegacyTagDefinition def, AbLegacyAddress parsed) =>
|
||||
@@ -916,9 +962,9 @@ public sealed class AbLegacyDriver : IDriver, IReadable, IWritable, ITagDiscover
|
||||
{
|
||||
if (device.Runtimes.TryGetValue(def.Name, out existing)) return existing;
|
||||
|
||||
var parsed = AbLegacyAddress.TryParse(def.Address)
|
||||
var parsed = AbLegacyAddress.TryParse(def.Address, device.Options.PlcFamily)
|
||||
?? throw new InvalidOperationException(
|
||||
$"AbLegacy tag '{def.Name}' has malformed Address '{def.Address}'.");
|
||||
$"AbLegacy tag '{def.Name}' has malformed Address '{def.Address}' for PLC family {device.Options.PlcFamily}.");
|
||||
|
||||
var runtime = _tagFactory.Create(CreateParamsFor(device, def, parsed));
|
||||
await InitializeOrKeepRefusedAsync(runtime, () => device.Runtimes[def.Name] = runtime, ct)
|
||||
@@ -1123,6 +1169,9 @@ public sealed class AbLegacyDriver : IDriver, IReadable, IWritable, ITagDiscover
|
||||
/// </summary>
|
||||
public bool FirstNonZeroStatusLogged { get; set; }
|
||||
|
||||
/// <summary>Tags already warned about for a malformed address (once-per-tag log).</summary>
|
||||
public System.Collections.Concurrent.ConcurrentDictionary<string, byte> MalformedAddressLogged { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Disposes all cached tag runtimes.
|
||||
/// </summary>
|
||||
|
||||
Reference in New Issue
Block a user