Auto: abcip-3.1 — configurable CIP connection size per device
Closes #235
This commit is contained in:
@@ -83,7 +83,8 @@ public sealed class AbCipDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
CipPath: device.ParsedAddress.CipPath,
|
||||
LibplctagPlcAttribute: device.Profile.LibplctagPlcAttribute,
|
||||
TagName: $"@udt/{templateInstanceId}",
|
||||
Timeout: _options.Timeout);
|
||||
Timeout: _options.Timeout,
|
||||
ConnectionSize: device.ConnectionSize);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -121,6 +122,31 @@ public sealed class AbCipDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
?? throw new InvalidOperationException(
|
||||
$"AbCip device has invalid HostAddress '{device.HostAddress}' — expected 'ab://gateway[:port]/cip-path'.");
|
||||
var profile = AbCipPlcFamilyProfile.ForFamily(device.PlcFamily);
|
||||
// PR abcip-3.1 — validate the optional ConnectionSize override before stamping
|
||||
// the device. The Kepware-supported range [500..4002] is what the libplctag
|
||||
// ControlLogix driver supports; anything outside that fails the Forward Open at
|
||||
// runtime, so we reject it loudly at config time instead.
|
||||
if (device.ConnectionSize is int explicitSize)
|
||||
{
|
||||
if (explicitSize < AbCipConnectionSize.Min || explicitSize > AbCipConnectionSize.Max)
|
||||
throw new InvalidOperationException(
|
||||
$"AbCip device '{device.HostAddress}' has ConnectionSize {explicitSize} outside the supported range " +
|
||||
$"[{AbCipConnectionSize.Min}..{AbCipConnectionSize.Max}].");
|
||||
// Legacy-firmware warning: families whose profile default is 504 (CompactLogix
|
||||
// narrow cap, also where v19-and-earlier ControlLogix lives) can't actually
|
||||
// raise their CIP buffer above 511 bytes — the controller rejects the Forward
|
||||
// Open. Ship the override anyway so newer firmware can use it, but flag the
|
||||
// mismatch so operators see it in the warning sink.
|
||||
if (explicitSize > AbCipConnectionSize.LegacyFirmwareCap
|
||||
&& profile.DefaultConnectionSize <= AbCipConnectionSize.LegacyFirmwareCap)
|
||||
{
|
||||
_options.OnWarning?.Invoke(
|
||||
$"AbCip device '{device.HostAddress}' family '{device.PlcFamily}' uses a narrow-buffer profile " +
|
||||
$"(default ConnectionSize {profile.DefaultConnectionSize}); the configured ConnectionSize {explicitSize} " +
|
||||
$"exceeds the {AbCipConnectionSize.LegacyFirmwareCap}-byte legacy-firmware cap and will fail the " +
|
||||
"Forward Open on v19-and-earlier ControlLogix or 5069-L1/L2/L3 CompactLogix firmware.");
|
||||
}
|
||||
}
|
||||
_devices[device.HostAddress] = new DeviceState(addr, device, profile);
|
||||
}
|
||||
// Pre-declared tags first; L5K imports fill in only the names not already covered
|
||||
@@ -356,7 +382,8 @@ public sealed class AbCipDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
CipPath: state.ParsedAddress.CipPath,
|
||||
LibplctagPlcAttribute: state.Profile.LibplctagPlcAttribute,
|
||||
TagName: _options.Probe.ProbeTagPath!,
|
||||
Timeout: _options.Probe.Timeout);
|
||||
Timeout: _options.Probe.Timeout,
|
||||
ConnectionSize: state.ConnectionSize);
|
||||
|
||||
IAbCipTagRuntime? probeRuntime = null;
|
||||
while (!ct.IsCancellationRequested)
|
||||
@@ -533,7 +560,8 @@ public sealed class AbCipDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
CipPath: device.ParsedAddress.CipPath,
|
||||
LibplctagPlcAttribute: device.Profile.LibplctagPlcAttribute,
|
||||
TagName: parsedPath.ToLibplctagName(),
|
||||
Timeout: _options.Timeout);
|
||||
Timeout: _options.Timeout,
|
||||
ConnectionSize: device.ConnectionSize);
|
||||
|
||||
var plan = AbCipArrayReadPlanner.TryBuild(def, parsedPath, baseParams);
|
||||
if (plan is null)
|
||||
@@ -892,7 +920,8 @@ public sealed class AbCipDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
CipPath: device.ParsedAddress.CipPath,
|
||||
LibplctagPlcAttribute: device.Profile.LibplctagPlcAttribute,
|
||||
TagName: parentTagName,
|
||||
Timeout: _options.Timeout));
|
||||
Timeout: _options.Timeout,
|
||||
ConnectionSize: device.ConnectionSize));
|
||||
try
|
||||
{
|
||||
await runtime.InitializeAsync(ct).ConfigureAwait(false);
|
||||
@@ -927,7 +956,8 @@ public sealed class AbCipDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
LibplctagPlcAttribute: device.Profile.LibplctagPlcAttribute,
|
||||
TagName: parsed.ToLibplctagName(),
|
||||
Timeout: _options.Timeout,
|
||||
StringMaxCapacity: def.DataType == AbCipDataType.String ? def.StringLength : null));
|
||||
StringMaxCapacity: def.DataType == AbCipDataType.String ? def.StringLength : null,
|
||||
ConnectionSize: device.ConnectionSize));
|
||||
try
|
||||
{
|
||||
await runtime.InitializeAsync(ct).ConfigureAwait(false);
|
||||
@@ -1081,7 +1111,8 @@ public sealed class AbCipDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
CipPath: state.ParsedAddress.CipPath,
|
||||
LibplctagPlcAttribute: state.Profile.LibplctagPlcAttribute,
|
||||
TagName: "@tags",
|
||||
Timeout: _options.Timeout);
|
||||
Timeout: _options.Timeout,
|
||||
ConnectionSize: state.ConnectionSize);
|
||||
|
||||
IAddressSpaceBuilder? discoveredFolder = null;
|
||||
await foreach (var discovered in enumerator.EnumerateAsync(deviceParams, cancellationToken)
|
||||
@@ -1152,6 +1183,16 @@ public sealed class AbCipDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
public AbCipDeviceOptions Options { get; } = options;
|
||||
public AbCipPlcFamilyProfile Profile { get; } = profile;
|
||||
|
||||
/// <summary>
|
||||
/// PR abcip-3.1 — effective CIP connection size for this device. Per-device
|
||||
/// <see cref="AbCipDeviceOptions.ConnectionSize"/> override wins; otherwise the
|
||||
/// family profile's <see cref="AbCipPlcFamilyProfile.DefaultConnectionSize"/>
|
||||
/// (4002 / 504 / 488 depending on family). Threaded through every
|
||||
/// <see cref="AbCipTagCreateParams"/> the driver builds so libplctag receives a
|
||||
/// consistent buffer-size hint across read / write / probe / discovery handles.
|
||||
/// </summary>
|
||||
public int ConnectionSize { get; } = options.ConnectionSize ?? profile.DefaultConnectionSize;
|
||||
|
||||
public object ProbeLock { get; } = new();
|
||||
public HostState HostState { get; set; } = HostState.Unknown;
|
||||
public DateTime HostStateChangedUtc { get; set; } = DateTime.UtcNow;
|
||||
|
||||
Reference in New Issue
Block a user