fix(driver-ablegacy): resolve Medium code-review finding (Driver.AbLegacy-012)
Consume previously-dead AbLegacyPlcFamilyProfile fields: - DeviceState.EffectiveCipPath applies DefaultCipPath when the parsed host address has an empty CIP path (SLC 500 / PLC-5 misconfigured without /1,0 now gets the profile-supplied default route). All three tag/parent/probe Create() callers updated. - InitializeAsync validates each tag's DataType against SupportsLongFile / SupportsStringFile and throws InvalidOperationException at init time so a MicroLogix Long tag or similar fails early rather than at runtime with an opaque comms error. - MaxTagBytes tracked as a follow-up (string/array chunking requires broader design work). Tests added for CipPath fallback and Long/String type validation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -59,6 +59,24 @@ public sealed class AbLegacyDriver : IDriver, IReadable, IWritable, ITagDiscover
|
||||
}
|
||||
foreach (var tag in _options.Tags) _tagsByName[tag.Name] = tag;
|
||||
|
||||
// Validate tag types against their device's family profile. Long (32-bit integer)
|
||||
// and String (ST-file) are not supported by all PCCC families; reject them early
|
||||
// so a misconfigured tag fails at init time with a clear message rather than
|
||||
// surfacing an opaque comms error at runtime.
|
||||
foreach (var tag in _options.Tags)
|
||||
{
|
||||
if (!_devices.TryGetValue(tag.DeviceHostAddress, out var deviceForTag)) continue;
|
||||
var profile = deviceForTag.Profile;
|
||||
if (tag.DataType == AbLegacyDataType.Long && !profile.SupportsLongFile)
|
||||
throw new InvalidOperationException(
|
||||
$"Tag '{tag.Name}' is typed as Long but device '{tag.DeviceHostAddress}' " +
|
||||
$"(family {deviceForTag.Options.PlcFamily}) does not support L-files.");
|
||||
if (tag.DataType == AbLegacyDataType.String && !profile.SupportsStringFile)
|
||||
throw new InvalidOperationException(
|
||||
$"Tag '{tag.Name}' is typed as String but device '{tag.DeviceHostAddress}' " +
|
||||
$"(family {deviceForTag.Options.PlcFamily}) does not support ST-files.");
|
||||
}
|
||||
|
||||
// Probe loops — one per device when enabled + probe address configured.
|
||||
if (_options.Probe.Enabled && !string.IsNullOrWhiteSpace(_options.Probe.ProbeAddress))
|
||||
{
|
||||
@@ -333,7 +351,7 @@ public sealed class AbLegacyDriver : IDriver, IReadable, IWritable, ITagDiscover
|
||||
var probeParams = new AbLegacyTagCreateParams(
|
||||
Gateway: state.ParsedAddress.Gateway,
|
||||
Port: state.ParsedAddress.Port,
|
||||
CipPath: state.ParsedAddress.CipPath,
|
||||
CipPath: state.EffectiveCipPath,
|
||||
LibplctagPlcAttribute: state.Profile.LibplctagPlcAttribute,
|
||||
TagName: _options.Probe.ProbeAddress!,
|
||||
Timeout: _options.Probe.Timeout);
|
||||
@@ -466,7 +484,7 @@ public sealed class AbLegacyDriver : IDriver, IReadable, IWritable, ITagDiscover
|
||||
var runtime = _tagFactory.Create(new AbLegacyTagCreateParams(
|
||||
Gateway: device.ParsedAddress.Gateway,
|
||||
Port: device.ParsedAddress.Port,
|
||||
CipPath: device.ParsedAddress.CipPath,
|
||||
CipPath: device.EffectiveCipPath,
|
||||
LibplctagPlcAttribute: device.Profile.LibplctagPlcAttribute,
|
||||
TagName: parentName,
|
||||
Timeout: _options.Timeout));
|
||||
@@ -509,7 +527,7 @@ public sealed class AbLegacyDriver : IDriver, IReadable, IWritable, ITagDiscover
|
||||
var runtime = _tagFactory.Create(new AbLegacyTagCreateParams(
|
||||
Gateway: device.ParsedAddress.Gateway,
|
||||
Port: device.ParsedAddress.Port,
|
||||
CipPath: device.ParsedAddress.CipPath,
|
||||
CipPath: device.EffectiveCipPath,
|
||||
LibplctagPlcAttribute: device.Profile.LibplctagPlcAttribute,
|
||||
TagName: parsed.ToLibplctagName(),
|
||||
Timeout: _options.Timeout));
|
||||
@@ -543,6 +561,16 @@ public sealed class AbLegacyDriver : IDriver, IReadable, IWritable, ITagDiscover
|
||||
public AbLegacyDeviceOptions Options { get; } = options;
|
||||
public AbLegacyPlcFamilyProfile Profile { get; } = profile;
|
||||
|
||||
/// <summary>
|
||||
/// The CIP path to pass to libplctag. When the parsed host address has an empty CIP
|
||||
/// path (e.g. <c>ab://10.0.0.5/</c>), the profile-supplied default is used instead so
|
||||
/// that a SLC 500 misconfigured without an explicit path still gets the required
|
||||
/// <c>1,0</c> backplane route. MicroLogix has an empty default by design (direct EIP).
|
||||
/// </summary>
|
||||
public string EffectiveCipPath => parsedAddress.CipPath.Length > 0
|
||||
? parsedAddress.CipPath
|
||||
: profile.DefaultCipPath;
|
||||
|
||||
/// <summary>
|
||||
/// Per-tag cached runtimes. <see cref="System.Collections.Concurrent.ConcurrentDictionary{TKey,TValue}"/>
|
||||
/// avoids the check-then-act race present on a plain <c>Dictionary</c>: two concurrent
|
||||
|
||||
Reference in New Issue
Block a user