Auto: abcip-2.5 — online tag-DB refresh trigger
Add IDriverControl capability interface in Core.Abstractions with a RebrowseAsync(IAddressSpaceBuilder, CancellationToken) hook so operators can force a controller-side @tags re-walk without restarting the driver. AbCipDriver now implements IDriverControl. RebrowseAsync clears the UDT template cache (so stale shapes from a pre-download program don't survive) then runs the same enumerator + builder fan-out as DiscoverAsync, serialised against concurrent discovery / rebrowse via a new SemaphoreSlim. Driver.AbCip.Cli ships a `rebrowse` subcommand mirroring the existing probe / read shape: connects to a single gateway, runs RebrowseAsync against an in-memory builder, and prints discovered tag names so operators can sanity-check the controller's symbol table from a shell. Tests cover: two consecutive RebrowseAsync calls bump the enumerator's Create / Enumerate counters once each, discovered tags reach the supplied builder, the template cache is dropped on rebrowse, and the driver exposes IDriverControl. 313 AbCip unit tests + 17 CLI tests + 37 Core.Abstractions tests pass. Closes #233
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
using CliFx.Attributes;
|
||||
using CliFx.Infrastructure;
|
||||
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
using ZB.MOM.WW.OtOpcUa.Driver.Cli.Common;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip.Cli.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// Force a controller-side @tags re-walk on a live AbCip driver instance. Issue #233 —
|
||||
/// online tag-DB refresh trigger. The CLI variant builds a transient driver against the
|
||||
/// supplied gateway, runs <see cref="AbCipDriver.RebrowseAsync"/>, and prints the freshly
|
||||
/// discovered tag names. In-server (Tier-A) operators wire this same call to an Admin UI
|
||||
/// button so a controller program-download is reflected in the address space without a
|
||||
/// driver restart.
|
||||
/// </summary>
|
||||
[Command("rebrowse", Description =
|
||||
"Re-walk the AB CIP controller symbol table (force @tags refresh) and print discovered tags.")]
|
||||
public sealed class RebrowseCommand : AbCipCommandBase
|
||||
{
|
||||
public override async ValueTask ExecuteAsync(IConsole console)
|
||||
{
|
||||
ConfigureLogging();
|
||||
var ct = console.RegisterCancellationHandler();
|
||||
|
||||
// EnableControllerBrowse must be true for the @tags walk to happen; the CLI baseline
|
||||
// (BuildOptions in AbCipCommandBase) leaves it off for one-shot probes, so we flip it
|
||||
// here without touching the base helper.
|
||||
var baseOpts = BuildOptions(tags: []);
|
||||
var options = new AbCipDriverOptions
|
||||
{
|
||||
Devices = baseOpts.Devices,
|
||||
Tags = baseOpts.Tags,
|
||||
Timeout = baseOpts.Timeout,
|
||||
Probe = baseOpts.Probe,
|
||||
EnableControllerBrowse = true,
|
||||
EnableAlarmProjection = false,
|
||||
};
|
||||
|
||||
await using var driver = new AbCipDriver(options, DriverInstanceId);
|
||||
try
|
||||
{
|
||||
await driver.InitializeAsync("{}", ct);
|
||||
|
||||
var builder = new ConsoleAddressSpaceBuilder();
|
||||
await driver.RebrowseAsync(builder, ct);
|
||||
|
||||
await console.Output.WriteLineAsync($"Gateway: {Gateway}");
|
||||
await console.Output.WriteLineAsync($"Family: {Family}");
|
||||
await console.Output.WriteLineAsync($"Variables: {builder.VariableCount}");
|
||||
await console.Output.WriteLineAsync();
|
||||
foreach (var line in builder.Lines)
|
||||
await console.Output.WriteLineAsync(line);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await driver.ShutdownAsync(CancellationToken.None);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Minimal in-memory <see cref="IAddressSpaceBuilder"/> that flattens the tree to one
|
||||
/// line per variable for CLI display. Folder nesting is captured in the prefix so the
|
||||
/// operator can see the same shape the in-server builder would receive.
|
||||
/// </summary>
|
||||
private sealed class ConsoleAddressSpaceBuilder : IAddressSpaceBuilder
|
||||
{
|
||||
private readonly string _prefix;
|
||||
private readonly Counter _counter;
|
||||
public List<string> Lines { get; }
|
||||
public int VariableCount => _counter.Count;
|
||||
|
||||
public ConsoleAddressSpaceBuilder() : this("", new List<string>(), new Counter()) { }
|
||||
private ConsoleAddressSpaceBuilder(string prefix, List<string> sharedLines, Counter counter)
|
||||
{
|
||||
_prefix = prefix;
|
||||
Lines = sharedLines;
|
||||
_counter = counter;
|
||||
}
|
||||
|
||||
public IAddressSpaceBuilder Folder(string browseName, string displayName)
|
||||
{
|
||||
var newPrefix = string.IsNullOrEmpty(_prefix) ? browseName : $"{_prefix}/{browseName}";
|
||||
return new ConsoleAddressSpaceBuilder(newPrefix, Lines, _counter);
|
||||
}
|
||||
|
||||
public IVariableHandle Variable(string browseName, string displayName, DriverAttributeInfo info)
|
||||
{
|
||||
_counter.Count++;
|
||||
Lines.Add($" {_prefix}/{browseName} ({info.DriverDataType}, {info.SecurityClass})");
|
||||
return new Handle(info.FullName);
|
||||
}
|
||||
|
||||
public void AddProperty(string browseName, DriverDataType dataType, object? value) { }
|
||||
|
||||
private sealed class Counter { public int Count; }
|
||||
|
||||
private sealed class Handle(string fullRef) : IVariableHandle
|
||||
{
|
||||
public string FullReference => fullRef;
|
||||
public IAlarmConditionSink MarkAsAlarmCondition(AlarmConditionInfo info) => new NullSink();
|
||||
}
|
||||
private sealed class NullSink : IAlarmConditionSink
|
||||
{
|
||||
public void OnTransition(AlarmEventArgs args) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user