task-galaxy-e2e branch — non-FOCAS work-in-progress snapshot
Catch-all commit for pending work on the task-galaxy-e2e branch that
wasn't part of the FOCAS migration. Grouping by topic so future per-topic
commits can be cherry-picked if needed.
TwinCAT
- src/.../Driver.TwinCAT/AdsTwinCATClient.cs + TwinCATDriverFactoryExtensions.cs:
factory-registration extensions + ADS client refinements.
- src/.../Driver.TwinCAT.Cli/Commands/BrowseCommand.cs: new browse command
for the TwinCAT test-client CLI.
- tests/.../Driver.TwinCAT.IntegrationTests/TwinCAT3SmokeTests.cs + TwinCatProject/:
fixture scaffold with a minimal POU + README pointing at the TCBSD/ESXi
VM for e2e.
- docs/Driver.TwinCAT.Cli.md + docs/drivers/TwinCAT-Test-Fixture.md:
documentation for the above.
- docs/v3/twincat-backlog.md: forward-looking backlog seed.
Admin UI + fleet status
- src/.../Admin/Components/Pages/Clusters/DriversTab.razor + Hosts.razor:
UI refresh for fleet-status rendering.
- src/.../Admin/Hubs/FleetStatusHub.cs + FleetStatusPoller.cs +
Admin/Program.cs: SignalR hub + poller plumbing for live fleet data.
- tests/.../Admin.Tests/FleetStatusPollerTests.cs: poller coverage.
Server + redundancy runtime (Phase 6.3 follow-ups)
- src/.../Server/Hosting/RedundancyPublisherHostedService.cs: HostedService
that owns the RedundancyStatePublisher lifecycle + wires peer reachability.
- src/.../Server/Redundancy/ServerRedundancyNodeWriter.cs: OPC UA
variable-node writer binding ServiceLevel + ServerUriArray to the
publisher's events.
- src/.../Server/Program.cs + Server.csproj: hosted-service registration.
- tests/.../Server.Tests/ServerRedundancyNodeWriterTests.cs +
Server.Tests.csproj: coverage for the above.
Configuration
- src/.../Configuration/Validation/DraftValidator.cs +
tests/.../Configuration.Tests/DraftValidatorTests.cs: draft-validation
refinements.
E2E scripts (shared infrastructure)
- scripts/e2e/README.md + _common.ps1 + test-all.ps1: shared helpers + the
all-drivers test-all runner.
- scripts/e2e/test-opcuaclient.ps1: OPC UA Client e2e runner.
Docs
- docs/v2/implementation/phase-6-{1,2,3,4}*.md + exit-gate-phase-{3,7}.md:
phase-gate + implementation doc updates.
- docs/v2/plan.md: top-level plan refresh.
- docs/v2/redundancy-interop-playbook.md: client interop playbook for the
Phase 6.3 redundancy-runtime work.
Two orphan FOCAS docs remain on disk but deliberately unstaged —
docs/v2/focas-deployment.md and docs/v2/implementation/focas-simulator-plan.md
describe the now-retired Tier-C topology and should either be rewritten
or deleted in a follow-up.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
using CliFx.Attributes;
|
||||
using CliFx.Infrastructure;
|
||||
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.Cli.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// Walk the target's symbol table (ADS <c>SymbolLoaderFactory</c>, flat mode) and print every
|
||||
/// symbol the driver's atomic-type mapper recognizes. Same path <c>DiscoverAsync</c> takes
|
||||
/// when <c>EnableControllerBrowse = true</c> — structured UDTs / function-block instances
|
||||
/// won't appear because the driver filters to the supported primitive surface.
|
||||
/// </summary>
|
||||
[Command("browse", Description = "Enumerate controller symbols via the driver's DiscoverAsync walk.")]
|
||||
public sealed class BrowseCommand : TwinCATCommandBase
|
||||
{
|
||||
[CommandOption("prefix", Description =
|
||||
"Case-sensitive instance-path prefix to filter on (e.g. 'GVL_Fixture' or " +
|
||||
"'MAIN.'). Empty (default) prints everything.")]
|
||||
public string? Prefix { get; init; }
|
||||
|
||||
[CommandOption("max", Description =
|
||||
"Maximum number of symbols to print. 0 = unbounded (default 500 for large " +
|
||||
"controllers — flat-mode symbol counts easily top 10k).")]
|
||||
public int Max { get; init; } = 500;
|
||||
|
||||
public override async ValueTask ExecuteAsync(IConsole console)
|
||||
{
|
||||
ConfigureLogging();
|
||||
var ct = console.RegisterCancellationHandler();
|
||||
|
||||
// Browse-only — no declared tags. EnableControllerBrowse=true flips DiscoverAsync's
|
||||
// symbol-walk on so every recognized primitive surfaces through the builder.
|
||||
var options = new TwinCATDriverOptions
|
||||
{
|
||||
Devices = [new TwinCATDeviceOptions(Gateway, $"cli-{AmsNetId}:{AmsPort}")],
|
||||
Tags = [],
|
||||
Timeout = Timeout,
|
||||
Probe = new TwinCATProbeOptions { Enabled = false },
|
||||
UseNativeNotifications = !PollOnly,
|
||||
EnableControllerBrowse = true,
|
||||
};
|
||||
|
||||
await using var driver = new TwinCATDriver(options, DriverInstanceId);
|
||||
var builder = new CollectingAddressSpaceBuilder();
|
||||
try
|
||||
{
|
||||
await driver.InitializeAsync("{}", ct);
|
||||
await driver.DiscoverAsync(builder, ct);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await driver.ShutdownAsync(CancellationToken.None);
|
||||
}
|
||||
|
||||
var matched = builder.Variables
|
||||
.Where(v => string.IsNullOrEmpty(Prefix) || v.BrowseName.StartsWith(Prefix, StringComparison.Ordinal))
|
||||
.ToList();
|
||||
var printLimit = Max <= 0 ? matched.Count : Math.Min(Max, matched.Count);
|
||||
|
||||
await console.Output.WriteLineAsync($"AMS: {AmsNetId}:{AmsPort}");
|
||||
await console.Output.WriteLineAsync(
|
||||
$"Symbols: {matched.Count} matched ({builder.Variables.Count} total), showing {printLimit}");
|
||||
await console.Output.WriteLineAsync();
|
||||
|
||||
foreach (var v in matched.Take(printLimit))
|
||||
{
|
||||
var access = v.Info.SecurityClass == SecurityClassification.ViewOnly ? "RO" : "RW";
|
||||
await console.Output.WriteLineAsync($" [{access}] {v.Info.DriverDataType,-8} {v.BrowseName}");
|
||||
}
|
||||
|
||||
if (matched.Count > printLimit)
|
||||
await console.Output.WriteLineAsync(
|
||||
$" … {matched.Count - printLimit} more — raise --max or tighten --prefix");
|
||||
}
|
||||
|
||||
private sealed class CollectingAddressSpaceBuilder : IAddressSpaceBuilder
|
||||
{
|
||||
public List<(string BrowseName, DriverAttributeInfo Info)> Variables { get; } = [];
|
||||
|
||||
public IAddressSpaceBuilder Folder(string browseName, string displayName) => this;
|
||||
|
||||
public IVariableHandle Variable(string browseName, string displayName, DriverAttributeInfo info)
|
||||
{
|
||||
Variables.Add((browseName, info));
|
||||
return new Handle(info.FullName);
|
||||
}
|
||||
|
||||
public void AddProperty(string name, DriverDataType type, object? value) { }
|
||||
|
||||
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