rename: prefix gateway projects/namespaces with ZB.MOM.WW + sln→slnx
Apply the ZB.MOM.WW. prefix to all gateway-side projects, folders,
.csproj/.sln contents, C# namespaces, using directives, generated proto
C# (csharp_namespace + checked-in generated files), InternalsVisibleTo
attributes, project-name string literals (LoadProject, .sln lookups,
worker exe paths, staticwebassets manifest), and the install/script/doc
references that point at any of the above. Migrate the solution from
.sln to .slnx via `dotnet sln migrate` and delete the old file.
External-runtime identifiers are intentionally NOT prefixed so external
configuration keeps working:
- GatewayMetrics.cs MeterName ("MxGateway.Server")
- DashboardAuthenticationDefaults Scheme/Policy ("MxGateway.Dashboard")
- GatewayRequestLoggingMiddleware logger category ("MxGateway.Request")
- StaRuntime thread name ("MxGateway.Worker.STA")
- appsettings.json root section "MxGateway" + env-var prefix
MxGateway__... and secret-name MxGateway:ApiKeyPepper
- C:\ProgramData\MxGateway\ data dir paths
Also fixes two tests that were not rename-related but became visible
while validating the rename:
- WorkerLiveMxAccessSmokeTests.ShutDownAsync: cancellation that the
gateway service correctly maps to RpcException(Cancelled) per gRPC
convention was being misclassified as a stream fault. Added a sibling
catch on RpcException with StatusCode.Cancelled.
- IntegrationTestEnvironment.ResolveRepositoryRoot: extracted IsRepositoryRoot
and made it accept either a .git marker OR a .sln/.slnx next to src/
so the worker-exe walker works in non-git working copies.
clients/proto/proto-inputs.json's protoRoot updated to point at
src/ZB.MOM.WW.MxGateway.Contracts/Protos.
Verified by `dotnet build` and a full `dotnet test` of the .slnx with
MXGATEWAY_RUN_LIVE_{MXACCESS,LDAP,GALAXY}_TESTS=1:
Tests: 472/472 pass
Worker.Tests: 280/280 pass (4 dev-rig [Fact(Skip=...)] skipped)
IntegrationTests: 18/18 pass
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,873 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using ZB.MOM.WW.MxGateway.Contracts.Proto;
|
||||
using ZB.MOM.WW.MxGateway.Worker.Conversion;
|
||||
using ZB.MOM.WW.MxGateway.Worker.Sta;
|
||||
|
||||
namespace ZB.MOM.WW.MxGateway.Worker.MxAccess;
|
||||
|
||||
/// <summary>
|
||||
/// Executes MXAccess commands on an STA session.
|
||||
/// </summary>
|
||||
public sealed class MxAccessCommandExecutor : IStaCommandExecutor
|
||||
{
|
||||
/// <summary>Default per-tag timeout used when <c>ReadBulkCommand.timeout_ms</c> is zero.</summary>
|
||||
internal static readonly TimeSpan DefaultReadBulkTimeout = TimeSpan.FromMilliseconds(1000);
|
||||
|
||||
private readonly MxAccessSession session;
|
||||
private readonly VariantConverter variantConverter;
|
||||
private readonly IAlarmCommandHandler? alarmCommandHandler;
|
||||
private readonly Action pumpStep;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a command executor with an MXAccess session.
|
||||
/// </summary>
|
||||
/// <param name="session">MXAccess session on the STA thread.</param>
|
||||
public MxAccessCommandExecutor(MxAccessSession session)
|
||||
: this(session, new VariantConverter(), alarmCommandHandler: null, pumpStep: null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a command executor with an MXAccess session and a variant converter.
|
||||
/// </summary>
|
||||
/// <param name="session">MXAccess session on the STA thread.</param>
|
||||
/// <param name="variantConverter">Converter for MXAccess variant values to MxValue protobuf messages.</param>
|
||||
public MxAccessCommandExecutor(
|
||||
MxAccessSession session,
|
||||
VariantConverter variantConverter)
|
||||
: this(session, variantConverter, alarmCommandHandler: null, pumpStep: null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a command executor with an MXAccess session, variant
|
||||
/// converter, and an alarm command handler. The alarm handler is
|
||||
/// optional — when null, alarm-side commands return an
|
||||
/// "alarm consumer not configured" diagnostic.
|
||||
/// </summary>
|
||||
public MxAccessCommandExecutor(
|
||||
MxAccessSession session,
|
||||
VariantConverter variantConverter,
|
||||
IAlarmCommandHandler? alarmCommandHandler)
|
||||
: this(session, variantConverter, alarmCommandHandler, pumpStep: null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a command executor with an MXAccess session, variant
|
||||
/// converter, alarm command handler, and a Windows-message pump action.
|
||||
/// The pump action is invoked from inside <c>ReadBulk</c>'s wait loop so
|
||||
/// MXAccess COM events queued for this STA can be dispatched while the
|
||||
/// executor is still holding the thread. Pass <c>null</c> in tests where
|
||||
/// ReadBulk is exercised against a fake worker that pre-populates the
|
||||
/// value cache — the executor falls back to a no-op pump step.
|
||||
/// </summary>
|
||||
public MxAccessCommandExecutor(
|
||||
MxAccessSession session,
|
||||
VariantConverter variantConverter,
|
||||
IAlarmCommandHandler? alarmCommandHandler,
|
||||
Action? pumpStep)
|
||||
{
|
||||
this.session = session ?? throw new ArgumentNullException(nameof(session));
|
||||
this.variantConverter = variantConverter ?? throw new ArgumentNullException(nameof(variantConverter));
|
||||
this.alarmCommandHandler = alarmCommandHandler;
|
||||
this.pumpStep = pumpStep ?? (static () => { });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes an MXAccess command and returns the reply.
|
||||
/// </summary>
|
||||
/// <param name="command">STA command to execute.</param>
|
||||
/// <returns>Command reply with result or error details.</returns>
|
||||
public MxCommandReply Execute(StaCommand command)
|
||||
{
|
||||
if (command is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(command));
|
||||
}
|
||||
|
||||
return command.Kind switch
|
||||
{
|
||||
MxCommandKind.Register => ExecuteRegister(command),
|
||||
MxCommandKind.Unregister => ExecuteUnregister(command),
|
||||
MxCommandKind.AddItem => ExecuteAddItem(command),
|
||||
MxCommandKind.AddItem2 => ExecuteAddItem2(command),
|
||||
MxCommandKind.RemoveItem => ExecuteRemoveItem(command),
|
||||
MxCommandKind.Advise => ExecuteAdvise(command),
|
||||
MxCommandKind.UnAdvise => ExecuteUnAdvise(command),
|
||||
MxCommandKind.AdviseSupervisory => ExecuteAdviseSupervisory(command),
|
||||
MxCommandKind.Write => ExecuteWrite(command),
|
||||
MxCommandKind.Write2 => ExecuteWrite2(command),
|
||||
MxCommandKind.WriteSecured => ExecuteWriteSecured(command),
|
||||
MxCommandKind.WriteSecured2 => ExecuteWriteSecured2(command),
|
||||
MxCommandKind.AddItemBulk => ExecuteAddItemBulk(command),
|
||||
MxCommandKind.AdviseItemBulk => ExecuteAdviseItemBulk(command),
|
||||
MxCommandKind.RemoveItemBulk => ExecuteRemoveItemBulk(command),
|
||||
MxCommandKind.UnAdviseItemBulk => ExecuteUnAdviseItemBulk(command),
|
||||
MxCommandKind.SubscribeBulk => ExecuteSubscribeBulk(command),
|
||||
MxCommandKind.UnsubscribeBulk => ExecuteUnsubscribeBulk(command),
|
||||
MxCommandKind.WriteBulk => ExecuteWriteBulk(command),
|
||||
MxCommandKind.Write2Bulk => ExecuteWrite2Bulk(command),
|
||||
MxCommandKind.WriteSecuredBulk => ExecuteWriteSecuredBulk(command),
|
||||
MxCommandKind.WriteSecured2Bulk => ExecuteWriteSecured2Bulk(command),
|
||||
MxCommandKind.ReadBulk => ExecuteReadBulk(command),
|
||||
MxCommandKind.SubscribeAlarms => ExecuteSubscribeAlarms(command),
|
||||
MxCommandKind.UnsubscribeAlarms => ExecuteUnsubscribeAlarms(command),
|
||||
MxCommandKind.AcknowledgeAlarm => ExecuteAcknowledgeAlarm(command),
|
||||
MxCommandKind.AcknowledgeAlarmByName => ExecuteAcknowledgeAlarmByName(command),
|
||||
MxCommandKind.QueryActiveAlarms => ExecuteQueryActiveAlarms(command),
|
||||
_ => CreateInvalidRequestReply(command, $"Unsupported MXAccess command kind {command.Kind}."),
|
||||
};
|
||||
}
|
||||
|
||||
private MxCommandReply ExecuteRegister(StaCommand command)
|
||||
{
|
||||
if (command.Command.PayloadCase != MxCommand.PayloadOneofCase.Register)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "Register command payload is required.");
|
||||
}
|
||||
|
||||
int serverHandle = session.Register(command.Command.Register.ClientName);
|
||||
MxCommandReply reply = CreateOkReply(command);
|
||||
reply.ReturnValue = variantConverter.Convert(serverHandle);
|
||||
reply.Register = new RegisterReply
|
||||
{
|
||||
ServerHandle = serverHandle,
|
||||
};
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
private MxCommandReply ExecuteUnregister(StaCommand command)
|
||||
{
|
||||
if (command.Command.PayloadCase != MxCommand.PayloadOneofCase.Unregister)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "Unregister command payload is required.");
|
||||
}
|
||||
|
||||
session.Unregister(command.Command.Unregister.ServerHandle);
|
||||
return CreateOkReply(command);
|
||||
}
|
||||
|
||||
private MxCommandReply ExecuteAddItem(StaCommand command)
|
||||
{
|
||||
if (command.Command.PayloadCase != MxCommand.PayloadOneofCase.AddItem)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "AddItem command payload is required.");
|
||||
}
|
||||
|
||||
AddItemCommand addItemCommand = command.Command.AddItem;
|
||||
int itemHandle = session.AddItem(
|
||||
addItemCommand.ServerHandle,
|
||||
addItemCommand.ItemDefinition);
|
||||
|
||||
MxCommandReply reply = CreateOkReply(command);
|
||||
reply.ReturnValue = variantConverter.Convert(itemHandle);
|
||||
reply.AddItem = new AddItemReply
|
||||
{
|
||||
ItemHandle = itemHandle,
|
||||
};
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
private MxCommandReply ExecuteAddItem2(StaCommand command)
|
||||
{
|
||||
if (command.Command.PayloadCase != MxCommand.PayloadOneofCase.AddItem2)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "AddItem2 command payload is required.");
|
||||
}
|
||||
|
||||
AddItem2Command addItem2Command = command.Command.AddItem2;
|
||||
int itemHandle = session.AddItem2(
|
||||
addItem2Command.ServerHandle,
|
||||
addItem2Command.ItemDefinition,
|
||||
addItem2Command.ItemContext);
|
||||
|
||||
MxCommandReply reply = CreateOkReply(command);
|
||||
reply.ReturnValue = variantConverter.Convert(itemHandle);
|
||||
reply.AddItem2 = new AddItem2Reply
|
||||
{
|
||||
ItemHandle = itemHandle,
|
||||
};
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
private MxCommandReply ExecuteRemoveItem(StaCommand command)
|
||||
{
|
||||
if (command.Command.PayloadCase != MxCommand.PayloadOneofCase.RemoveItem)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "RemoveItem command payload is required.");
|
||||
}
|
||||
|
||||
RemoveItemCommand removeItemCommand = command.Command.RemoveItem;
|
||||
session.RemoveItem(
|
||||
removeItemCommand.ServerHandle,
|
||||
removeItemCommand.ItemHandle);
|
||||
|
||||
return CreateOkReply(command);
|
||||
}
|
||||
|
||||
private MxCommandReply ExecuteAdvise(StaCommand command)
|
||||
{
|
||||
if (command.Command.PayloadCase != MxCommand.PayloadOneofCase.Advise)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "Advise command payload is required.");
|
||||
}
|
||||
|
||||
AdviseCommand adviseCommand = command.Command.Advise;
|
||||
session.Advise(
|
||||
adviseCommand.ServerHandle,
|
||||
adviseCommand.ItemHandle);
|
||||
|
||||
return CreateOkReply(command);
|
||||
}
|
||||
|
||||
private MxCommandReply ExecuteUnAdvise(StaCommand command)
|
||||
{
|
||||
if (command.Command.PayloadCase != MxCommand.PayloadOneofCase.UnAdvise)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "UnAdvise command payload is required.");
|
||||
}
|
||||
|
||||
UnAdviseCommand unAdviseCommand = command.Command.UnAdvise;
|
||||
session.UnAdvise(
|
||||
unAdviseCommand.ServerHandle,
|
||||
unAdviseCommand.ItemHandle);
|
||||
|
||||
return CreateOkReply(command);
|
||||
}
|
||||
|
||||
private MxCommandReply ExecuteAdviseSupervisory(StaCommand command)
|
||||
{
|
||||
if (command.Command.PayloadCase != MxCommand.PayloadOneofCase.AdviseSupervisory)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "AdviseSupervisory command payload is required.");
|
||||
}
|
||||
|
||||
AdviseSupervisoryCommand adviseSupervisoryCommand = command.Command.AdviseSupervisory;
|
||||
session.AdviseSupervisory(
|
||||
adviseSupervisoryCommand.ServerHandle,
|
||||
adviseSupervisoryCommand.ItemHandle);
|
||||
|
||||
return CreateOkReply(command);
|
||||
}
|
||||
|
||||
private MxCommandReply ExecuteWrite(StaCommand command)
|
||||
{
|
||||
if (command.Command.PayloadCase != MxCommand.PayloadOneofCase.Write)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "Write command payload is required.");
|
||||
}
|
||||
|
||||
WriteCommand writeCommand = command.Command.Write;
|
||||
if (writeCommand.Value is null)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "Write command value is required.");
|
||||
}
|
||||
|
||||
session.Write(
|
||||
writeCommand.ServerHandle,
|
||||
writeCommand.ItemHandle,
|
||||
variantConverter.ConvertToComValue(writeCommand.Value),
|
||||
writeCommand.UserId);
|
||||
|
||||
return CreateOkReply(command);
|
||||
}
|
||||
|
||||
private MxCommandReply ExecuteWrite2(StaCommand command)
|
||||
{
|
||||
if (command.Command.PayloadCase != MxCommand.PayloadOneofCase.Write2)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "Write2 command payload is required.");
|
||||
}
|
||||
|
||||
Write2Command write2Command = command.Command.Write2;
|
||||
if (write2Command.Value is null)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "Write2 command value is required.");
|
||||
}
|
||||
|
||||
if (write2Command.TimestampValue is null)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "Write2 command timestamp value is required.");
|
||||
}
|
||||
|
||||
session.Write2(
|
||||
write2Command.ServerHandle,
|
||||
write2Command.ItemHandle,
|
||||
variantConverter.ConvertToComValue(write2Command.Value),
|
||||
variantConverter.ConvertToComValue(write2Command.TimestampValue),
|
||||
write2Command.UserId);
|
||||
|
||||
return CreateOkReply(command);
|
||||
}
|
||||
|
||||
private MxCommandReply ExecuteWriteSecured(StaCommand command)
|
||||
{
|
||||
if (command.Command.PayloadCase != MxCommand.PayloadOneofCase.WriteSecured)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "WriteSecured command payload is required.");
|
||||
}
|
||||
|
||||
WriteSecuredCommand writeSecuredCommand = command.Command.WriteSecured;
|
||||
if (writeSecuredCommand.Value is null)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "WriteSecured command value is required.");
|
||||
}
|
||||
|
||||
session.WriteSecured(
|
||||
writeSecuredCommand.ServerHandle,
|
||||
writeSecuredCommand.ItemHandle,
|
||||
writeSecuredCommand.CurrentUserId,
|
||||
writeSecuredCommand.VerifierUserId,
|
||||
variantConverter.ConvertToComValue(writeSecuredCommand.Value));
|
||||
|
||||
return CreateOkReply(command);
|
||||
}
|
||||
|
||||
private MxCommandReply ExecuteWriteSecured2(StaCommand command)
|
||||
{
|
||||
if (command.Command.PayloadCase != MxCommand.PayloadOneofCase.WriteSecured2)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "WriteSecured2 command payload is required.");
|
||||
}
|
||||
|
||||
WriteSecured2Command writeSecured2Command = command.Command.WriteSecured2;
|
||||
if (writeSecured2Command.Value is null)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "WriteSecured2 command value is required.");
|
||||
}
|
||||
|
||||
if (writeSecured2Command.TimestampValue is null)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "WriteSecured2 command timestamp value is required.");
|
||||
}
|
||||
|
||||
session.WriteSecured2(
|
||||
writeSecured2Command.ServerHandle,
|
||||
writeSecured2Command.ItemHandle,
|
||||
writeSecured2Command.CurrentUserId,
|
||||
writeSecured2Command.VerifierUserId,
|
||||
variantConverter.ConvertToComValue(writeSecured2Command.Value),
|
||||
variantConverter.ConvertToComValue(writeSecured2Command.TimestampValue));
|
||||
|
||||
return CreateOkReply(command);
|
||||
}
|
||||
|
||||
private MxCommandReply ExecuteAddItemBulk(StaCommand command)
|
||||
{
|
||||
if (command.Command.PayloadCase != MxCommand.PayloadOneofCase.AddItemBulk)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "AddItemBulk command payload is required.");
|
||||
}
|
||||
|
||||
AddItemBulkCommand addItemBulkCommand = command.Command.AddItemBulk;
|
||||
return CreateBulkReply(
|
||||
command,
|
||||
session.AddItemBulk(addItemBulkCommand.ServerHandle, addItemBulkCommand.TagAddresses));
|
||||
}
|
||||
|
||||
private MxCommandReply ExecuteAdviseItemBulk(StaCommand command)
|
||||
{
|
||||
if (command.Command.PayloadCase != MxCommand.PayloadOneofCase.AdviseItemBulk)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "AdviseItemBulk command payload is required.");
|
||||
}
|
||||
|
||||
AdviseItemBulkCommand adviseItemBulkCommand = command.Command.AdviseItemBulk;
|
||||
return CreateBulkReply(
|
||||
command,
|
||||
session.AdviseItemBulk(adviseItemBulkCommand.ServerHandle, adviseItemBulkCommand.ItemHandles));
|
||||
}
|
||||
|
||||
private MxCommandReply ExecuteRemoveItemBulk(StaCommand command)
|
||||
{
|
||||
if (command.Command.PayloadCase != MxCommand.PayloadOneofCase.RemoveItemBulk)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "RemoveItemBulk command payload is required.");
|
||||
}
|
||||
|
||||
RemoveItemBulkCommand removeItemBulkCommand = command.Command.RemoveItemBulk;
|
||||
return CreateBulkReply(
|
||||
command,
|
||||
session.RemoveItemBulk(removeItemBulkCommand.ServerHandle, removeItemBulkCommand.ItemHandles));
|
||||
}
|
||||
|
||||
private MxCommandReply ExecuteUnAdviseItemBulk(StaCommand command)
|
||||
{
|
||||
if (command.Command.PayloadCase != MxCommand.PayloadOneofCase.UnAdviseItemBulk)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "UnAdviseItemBulk command payload is required.");
|
||||
}
|
||||
|
||||
UnAdviseItemBulkCommand unAdviseItemBulkCommand = command.Command.UnAdviseItemBulk;
|
||||
return CreateBulkReply(
|
||||
command,
|
||||
session.UnAdviseItemBulk(unAdviseItemBulkCommand.ServerHandle, unAdviseItemBulkCommand.ItemHandles));
|
||||
}
|
||||
|
||||
private MxCommandReply ExecuteSubscribeBulk(StaCommand command)
|
||||
{
|
||||
if (command.Command.PayloadCase != MxCommand.PayloadOneofCase.SubscribeBulk)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "SubscribeBulk command payload is required.");
|
||||
}
|
||||
|
||||
SubscribeBulkCommand subscribeBulkCommand = command.Command.SubscribeBulk;
|
||||
return CreateBulkReply(
|
||||
command,
|
||||
session.SubscribeBulk(subscribeBulkCommand.ServerHandle, subscribeBulkCommand.TagAddresses));
|
||||
}
|
||||
|
||||
private MxCommandReply ExecuteUnsubscribeBulk(StaCommand command)
|
||||
{
|
||||
if (command.Command.PayloadCase != MxCommand.PayloadOneofCase.UnsubscribeBulk)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "UnsubscribeBulk command payload is required.");
|
||||
}
|
||||
|
||||
UnsubscribeBulkCommand unsubscribeBulkCommand = command.Command.UnsubscribeBulk;
|
||||
return CreateBulkReply(
|
||||
command,
|
||||
session.UnsubscribeBulk(unsubscribeBulkCommand.ServerHandle, unsubscribeBulkCommand.ItemHandles));
|
||||
}
|
||||
|
||||
private MxCommandReply ExecuteWriteBulk(StaCommand command)
|
||||
{
|
||||
if (command.Command.PayloadCase != MxCommand.PayloadOneofCase.WriteBulk)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "WriteBulk command payload is required.");
|
||||
}
|
||||
|
||||
WriteBulkCommand writeBulkCommand = command.Command.WriteBulk;
|
||||
foreach (WriteBulkEntry entry in writeBulkCommand.Entries)
|
||||
{
|
||||
if (entry.Value is null)
|
||||
{
|
||||
return CreateInvalidRequestReply(
|
||||
command,
|
||||
$"WriteBulk entry for item handle {entry.ItemHandle} is missing its value.");
|
||||
}
|
||||
}
|
||||
|
||||
return CreateBulkWriteReply(
|
||||
command,
|
||||
session.WriteBulk(
|
||||
writeBulkCommand.ServerHandle,
|
||||
writeBulkCommand.Entries,
|
||||
value => variantConverter.ConvertToComValue(value)));
|
||||
}
|
||||
|
||||
private MxCommandReply ExecuteWrite2Bulk(StaCommand command)
|
||||
{
|
||||
if (command.Command.PayloadCase != MxCommand.PayloadOneofCase.Write2Bulk)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "Write2Bulk command payload is required.");
|
||||
}
|
||||
|
||||
Write2BulkCommand write2BulkCommand = command.Command.Write2Bulk;
|
||||
foreach (Write2BulkEntry entry in write2BulkCommand.Entries)
|
||||
{
|
||||
if (entry.Value is null)
|
||||
{
|
||||
return CreateInvalidRequestReply(
|
||||
command,
|
||||
$"Write2Bulk entry for item handle {entry.ItemHandle} is missing its value.");
|
||||
}
|
||||
|
||||
if (entry.TimestampValue is null)
|
||||
{
|
||||
return CreateInvalidRequestReply(
|
||||
command,
|
||||
$"Write2Bulk entry for item handle {entry.ItemHandle} is missing its timestamp value.");
|
||||
}
|
||||
}
|
||||
|
||||
return CreateBulkWriteReply(
|
||||
command,
|
||||
session.Write2Bulk(
|
||||
write2BulkCommand.ServerHandle,
|
||||
write2BulkCommand.Entries,
|
||||
value => variantConverter.ConvertToComValue(value)));
|
||||
}
|
||||
|
||||
private MxCommandReply ExecuteWriteSecuredBulk(StaCommand command)
|
||||
{
|
||||
if (command.Command.PayloadCase != MxCommand.PayloadOneofCase.WriteSecuredBulk)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "WriteSecuredBulk command payload is required.");
|
||||
}
|
||||
|
||||
WriteSecuredBulkCommand writeSecuredBulkCommand = command.Command.WriteSecuredBulk;
|
||||
foreach (WriteSecuredBulkEntry entry in writeSecuredBulkCommand.Entries)
|
||||
{
|
||||
if (entry.Value is null)
|
||||
{
|
||||
return CreateInvalidRequestReply(
|
||||
command,
|
||||
$"WriteSecuredBulk entry for item handle {entry.ItemHandle} is missing its value.");
|
||||
}
|
||||
}
|
||||
|
||||
return CreateBulkWriteReply(
|
||||
command,
|
||||
session.WriteSecuredBulk(
|
||||
writeSecuredBulkCommand.ServerHandle,
|
||||
writeSecuredBulkCommand.Entries,
|
||||
value => variantConverter.ConvertToComValue(value)));
|
||||
}
|
||||
|
||||
private MxCommandReply ExecuteWriteSecured2Bulk(StaCommand command)
|
||||
{
|
||||
if (command.Command.PayloadCase != MxCommand.PayloadOneofCase.WriteSecured2Bulk)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "WriteSecured2Bulk command payload is required.");
|
||||
}
|
||||
|
||||
WriteSecured2BulkCommand writeSecured2BulkCommand = command.Command.WriteSecured2Bulk;
|
||||
foreach (WriteSecured2BulkEntry entry in writeSecured2BulkCommand.Entries)
|
||||
{
|
||||
if (entry.Value is null)
|
||||
{
|
||||
return CreateInvalidRequestReply(
|
||||
command,
|
||||
$"WriteSecured2Bulk entry for item handle {entry.ItemHandle} is missing its value.");
|
||||
}
|
||||
|
||||
if (entry.TimestampValue is null)
|
||||
{
|
||||
return CreateInvalidRequestReply(
|
||||
command,
|
||||
$"WriteSecured2Bulk entry for item handle {entry.ItemHandle} is missing its timestamp value.");
|
||||
}
|
||||
}
|
||||
|
||||
return CreateBulkWriteReply(
|
||||
command,
|
||||
session.WriteSecured2Bulk(
|
||||
writeSecured2BulkCommand.ServerHandle,
|
||||
writeSecured2BulkCommand.Entries,
|
||||
value => variantConverter.ConvertToComValue(value)));
|
||||
}
|
||||
|
||||
private MxCommandReply ExecuteReadBulk(StaCommand command)
|
||||
{
|
||||
if (command.Command.PayloadCase != MxCommand.PayloadOneofCase.ReadBulk)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "ReadBulk command payload is required.");
|
||||
}
|
||||
|
||||
ReadBulkCommand readBulkCommand = command.Command.ReadBulk;
|
||||
TimeSpan timeout = readBulkCommand.TimeoutMs == 0
|
||||
? DefaultReadBulkTimeout
|
||||
: TimeSpan.FromMilliseconds(readBulkCommand.TimeoutMs);
|
||||
|
||||
IReadOnlyList<BulkReadResult> results = session.ReadBulk(
|
||||
readBulkCommand.ServerHandle,
|
||||
readBulkCommand.TagAddresses,
|
||||
timeout,
|
||||
pumpStep);
|
||||
|
||||
MxCommandReply reply = CreateOkReply(command);
|
||||
BulkReadReply bulkReply = new();
|
||||
bulkReply.Results.Add(results);
|
||||
reply.ReadBulk = bulkReply;
|
||||
return reply;
|
||||
}
|
||||
|
||||
private MxCommandReply ExecuteSubscribeAlarms(StaCommand command)
|
||||
{
|
||||
if (command.Command.PayloadCase != MxCommand.PayloadOneofCase.SubscribeAlarms)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "SubscribeAlarms command payload is required.");
|
||||
}
|
||||
if (alarmCommandHandler is null)
|
||||
{
|
||||
return CreateInvalidRequestReply(
|
||||
command,
|
||||
"SubscribeAlarms requires an alarm command handler; the worker was constructed without one.");
|
||||
}
|
||||
|
||||
string subscription = command.Command.SubscribeAlarms.SubscriptionExpression ?? string.Empty;
|
||||
if (string.IsNullOrWhiteSpace(subscription))
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "SubscribeAlarms.subscription_expression is required.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
alarmCommandHandler.Subscribe(subscription, command.SessionId);
|
||||
return CreateOkReply(command);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return CreateAlarmFailureReply(command, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private MxCommandReply ExecuteUnsubscribeAlarms(StaCommand command)
|
||||
{
|
||||
if (command.Command.PayloadCase != MxCommand.PayloadOneofCase.UnsubscribeAlarms)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "UnsubscribeAlarms command payload is required.");
|
||||
}
|
||||
if (alarmCommandHandler is null)
|
||||
{
|
||||
// No handler configured — Unsubscribe is a no-op in that case;
|
||||
// it can't be in a subscribed state to begin with.
|
||||
return CreateOkReply(command);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
alarmCommandHandler.Unsubscribe();
|
||||
return CreateOkReply(command);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return CreateAlarmFailureReply(command, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private MxCommandReply ExecuteAcknowledgeAlarm(StaCommand command)
|
||||
{
|
||||
if (command.Command.PayloadCase != MxCommand.PayloadOneofCase.AcknowledgeAlarmCommand)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "AcknowledgeAlarm command payload is required.");
|
||||
}
|
||||
if (alarmCommandHandler is null)
|
||||
{
|
||||
return CreateInvalidRequestReply(
|
||||
command,
|
||||
"AcknowledgeAlarm requires an alarm command handler; the worker was constructed without one.");
|
||||
}
|
||||
|
||||
AcknowledgeAlarmCommand payload = command.Command.AcknowledgeAlarmCommand;
|
||||
if (!Guid.TryParse(payload.AlarmGuid, out Guid alarmGuid))
|
||||
{
|
||||
return CreateInvalidRequestReply(
|
||||
command,
|
||||
$"AcknowledgeAlarm.alarm_guid is not a valid canonical GUID: '{payload.AlarmGuid}'.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
int rc = alarmCommandHandler.Acknowledge(
|
||||
alarmGuid,
|
||||
payload.Comment,
|
||||
payload.OperatorUser,
|
||||
payload.OperatorNode,
|
||||
payload.OperatorDomain,
|
||||
payload.OperatorFullName);
|
||||
MxCommandReply reply = CreateOkReply(command);
|
||||
reply.Hresult = rc;
|
||||
reply.AcknowledgeAlarm = new AcknowledgeAlarmReplyPayload
|
||||
{
|
||||
NativeStatus = rc,
|
||||
};
|
||||
if (rc != 0)
|
||||
{
|
||||
reply.DiagnosticMessage = $"AVEVA AlarmAckByGUID returned non-zero status {rc}.";
|
||||
}
|
||||
return reply;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return CreateAlarmFailureReply(command, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private MxCommandReply ExecuteAcknowledgeAlarmByName(StaCommand command)
|
||||
{
|
||||
if (command.Command.PayloadCase != MxCommand.PayloadOneofCase.AcknowledgeAlarmByNameCommand)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "AcknowledgeAlarmByName command payload is required.");
|
||||
}
|
||||
if (alarmCommandHandler is null)
|
||||
{
|
||||
return CreateInvalidRequestReply(
|
||||
command,
|
||||
"AcknowledgeAlarmByName requires an alarm command handler; the worker was constructed without one.");
|
||||
}
|
||||
|
||||
AcknowledgeAlarmByNameCommand payload = command.Command.AcknowledgeAlarmByNameCommand;
|
||||
if (string.IsNullOrWhiteSpace(payload.AlarmName))
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "AcknowledgeAlarmByName.alarm_name is required.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
int rc = alarmCommandHandler.AcknowledgeByName(
|
||||
payload.AlarmName,
|
||||
payload.ProviderName,
|
||||
payload.GroupName,
|
||||
payload.Comment,
|
||||
payload.OperatorUser,
|
||||
payload.OperatorNode,
|
||||
payload.OperatorDomain,
|
||||
payload.OperatorFullName);
|
||||
MxCommandReply reply = CreateOkReply(command);
|
||||
reply.Hresult = rc;
|
||||
reply.AcknowledgeAlarm = new AcknowledgeAlarmReplyPayload
|
||||
{
|
||||
NativeStatus = rc,
|
||||
};
|
||||
if (rc != 0)
|
||||
{
|
||||
reply.DiagnosticMessage = $"AVEVA AlarmAckByName returned non-zero status {rc}.";
|
||||
}
|
||||
return reply;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return CreateAlarmFailureReply(command, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private MxCommandReply ExecuteQueryActiveAlarms(StaCommand command)
|
||||
{
|
||||
if (command.Command.PayloadCase != MxCommand.PayloadOneofCase.QueryActiveAlarmsCommand)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "QueryActiveAlarms command payload is required.");
|
||||
}
|
||||
if (alarmCommandHandler is null)
|
||||
{
|
||||
return CreateInvalidRequestReply(
|
||||
command,
|
||||
"QueryActiveAlarms requires an alarm command handler; the worker was constructed without one.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
IReadOnlyList<ActiveAlarmSnapshot> snapshots = alarmCommandHandler.QueryActive(
|
||||
command.Command.QueryActiveAlarmsCommand.AlarmFilterPrefix);
|
||||
QueryActiveAlarmsReplyPayload payload = new QueryActiveAlarmsReplyPayload();
|
||||
payload.Snapshots.AddRange(snapshots);
|
||||
MxCommandReply reply = CreateOkReply(command);
|
||||
reply.QueryActiveAlarms = payload;
|
||||
return reply;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return CreateAlarmFailureReply(command, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static MxCommandReply CreateAlarmFailureReply(StaCommand command, Exception exception)
|
||||
{
|
||||
return new MxCommandReply
|
||||
{
|
||||
SessionId = command.SessionId,
|
||||
CorrelationId = command.CorrelationId,
|
||||
Kind = command.Kind,
|
||||
ProtocolStatus = new ProtocolStatus
|
||||
{
|
||||
Code = ProtocolStatusCode.MxaccessFailure,
|
||||
Message = exception.Message,
|
||||
},
|
||||
DiagnosticMessage = $"{exception.GetType().FullName}: {exception.Message}",
|
||||
};
|
||||
}
|
||||
|
||||
private static MxCommandReply CreateOkReply(StaCommand command)
|
||||
{
|
||||
return new MxCommandReply
|
||||
{
|
||||
SessionId = command.SessionId,
|
||||
CorrelationId = command.CorrelationId,
|
||||
Kind = command.Kind,
|
||||
Hresult = 0,
|
||||
ProtocolStatus = new ProtocolStatus
|
||||
{
|
||||
Code = ProtocolStatusCode.Ok,
|
||||
Message = "OK",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private static MxCommandReply CreateBulkReply(
|
||||
StaCommand command,
|
||||
IEnumerable<SubscribeResult> results)
|
||||
{
|
||||
MxCommandReply reply = CreateOkReply(command);
|
||||
BulkSubscribeReply bulkReply = new();
|
||||
bulkReply.Results.Add(results);
|
||||
|
||||
switch (command.Kind)
|
||||
{
|
||||
case MxCommandKind.AddItemBulk:
|
||||
reply.AddItemBulk = bulkReply;
|
||||
break;
|
||||
case MxCommandKind.AdviseItemBulk:
|
||||
reply.AdviseItemBulk = bulkReply;
|
||||
break;
|
||||
case MxCommandKind.RemoveItemBulk:
|
||||
reply.RemoveItemBulk = bulkReply;
|
||||
break;
|
||||
case MxCommandKind.UnAdviseItemBulk:
|
||||
reply.UnAdviseItemBulk = bulkReply;
|
||||
break;
|
||||
case MxCommandKind.SubscribeBulk:
|
||||
reply.SubscribeBulk = bulkReply;
|
||||
break;
|
||||
case MxCommandKind.UnsubscribeBulk:
|
||||
reply.UnsubscribeBulk = bulkReply;
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException($"Unsupported bulk command kind {command.Kind}.");
|
||||
}
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
private static MxCommandReply CreateBulkWriteReply(
|
||||
StaCommand command,
|
||||
IEnumerable<BulkWriteResult> results)
|
||||
{
|
||||
MxCommandReply reply = CreateOkReply(command);
|
||||
BulkWriteReply bulkReply = new();
|
||||
bulkReply.Results.Add(results);
|
||||
|
||||
switch (command.Kind)
|
||||
{
|
||||
case MxCommandKind.WriteBulk:
|
||||
reply.WriteBulk = bulkReply;
|
||||
break;
|
||||
case MxCommandKind.Write2Bulk:
|
||||
reply.Write2Bulk = bulkReply;
|
||||
break;
|
||||
case MxCommandKind.WriteSecuredBulk:
|
||||
reply.WriteSecuredBulk = bulkReply;
|
||||
break;
|
||||
case MxCommandKind.WriteSecured2Bulk:
|
||||
reply.WriteSecured2Bulk = bulkReply;
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException($"Unsupported bulk write command kind {command.Kind}.");
|
||||
}
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
private static MxCommandReply CreateInvalidRequestReply(
|
||||
StaCommand command,
|
||||
string message)
|
||||
{
|
||||
return new MxCommandReply
|
||||
{
|
||||
SessionId = command.SessionId,
|
||||
CorrelationId = command.CorrelationId,
|
||||
Kind = command.Kind,
|
||||
ProtocolStatus = new ProtocolStatus
|
||||
{
|
||||
Code = ProtocolStatusCode.InvalidRequest,
|
||||
Message = message,
|
||||
},
|
||||
DiagnosticMessage = message,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user