ef68016c7a
Wire the config operations that previously only worked over WCF onto RemoteGrpc, reusing the proven 2020 byte serializers verbatim inside the protobuf bytes fields (keyed by the Open2 session handle). Live-verified against a real 2023 R2 server where noted. Read ops (live-verified): - GetRuntimeParameterAsync -> StatusService.GetRuntimeParameter (GETRP serializer) - GetTagExtendedPropertiesAsync -> RetrievalService.GetTagExtendedPropertiesFromName (GetTepByNm serializer + sequence paging; page-0 FillBufferFromVector is the benign no-data terminator, matched to the WCF break-and-return-empty semantics) Server-walled (bounded with captured evidence): - ExecuteSqlCommandAsync -> RetrievalService.ExecuteSqlCommand. The request rides the RPC but the server-side CSrvDbConnection.ExecuteSqlCommand faults (IndexOutOfRange / native err 38) on a DB-connection precondition the pure managed gRPC session doesn't establish (same class as OpenStorageConnection). Surfaced as ProtocolEvidenceMissingException. Write ops (tooled + routed, sandbox-gated — not run destructively live): - EnsureTagAsync / DeleteTagAsync / RenameTagsAsync / AddTagExtendedPropertiesAsync via HistoryService.EnsureTags / DeleteTags / StartJob / AddTagExtendedProperties on a write-enabled (0x401) session, reusing the WCF golden serializers. The WCF priming discovery-dance is omitted (the M3 gRPC write probe worked without it); add it first if a live sandbox run is rejected. Routed in Historian2020ProtocolDialect / HistorianClient on the RemoteGrpc branch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B6mcaT2PjRFKcogzp9UkfC
133 lines
6.5 KiB
C#
133 lines
6.5 KiB
C#
using AVEVA.Historian.Client.Grpc;
|
|
using AVEVA.Historian.Client.Models;
|
|
using AVEVA.Historian.Client.Wcf;
|
|
|
|
namespace AVEVA.Historian.Client.Protocol;
|
|
|
|
internal sealed class Historian2020ProtocolDialect
|
|
{
|
|
private readonly HistorianClientOptions _options;
|
|
|
|
public Historian2020ProtocolDialect(HistorianClientOptions options)
|
|
{
|
|
_options = options ?? throw new ArgumentNullException(nameof(options));
|
|
}
|
|
|
|
private bool UseGrpc => _options.Transport == HistorianTransport.RemoteGrpc;
|
|
|
|
public IAsyncEnumerable<HistorianSample> ReadRawAsync(string tag, DateTime startUtc, DateTime endUtc, int maxValues, CancellationToken cancellationToken)
|
|
{
|
|
return UseGrpc
|
|
? new HistorianGrpcReadOrchestrator(_options).ReadRawAsync(tag, startUtc, endUtc, maxValues, cancellationToken)
|
|
: new HistorianWcfReadOrchestrator(_options).ReadRawAsync(tag, startUtc, endUtc, maxValues, cancellationToken);
|
|
}
|
|
|
|
public IAsyncEnumerable<HistorianAggregateSample> ReadAggregateAsync(string tag, DateTime startUtc, DateTime endUtc, RetrievalMode mode, TimeSpan interval, CancellationToken cancellationToken)
|
|
{
|
|
return UseGrpc
|
|
? new HistorianGrpcReadOrchestrator(_options).ReadAggregateAsync(tag, startUtc, endUtc, mode, interval, cancellationToken)
|
|
: new HistorianWcfReadOrchestrator(_options).ReadAggregateAsync(tag, startUtc, endUtc, mode, interval, cancellationToken);
|
|
}
|
|
|
|
public Task<IReadOnlyList<HistorianSample>> ReadAtTimeAsync(string tag, IReadOnlyList<DateTime> timestampsUtc, CancellationToken cancellationToken)
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
return UseGrpc
|
|
? new HistorianGrpcReadOrchestrator(_options).ReadAtTimeAsync(tag, timestampsUtc, cancellationToken)
|
|
: new HistorianWcfReadOrchestrator(_options).ReadAtTimeAsync(tag, timestampsUtc, cancellationToken);
|
|
}
|
|
|
|
public IAsyncEnumerable<HistorianBlock> ReadBlocksAsync(string tag, DateTime startUtc, DateTime endUtc, CancellationToken cancellationToken)
|
|
{
|
|
return Missing<HistorianBlock>("StartBlockRetrievalQuery", cancellationToken);
|
|
}
|
|
|
|
public IAsyncEnumerable<HistorianEvent> ReadEventsAsync(DateTime startUtc, DateTime endUtc, HistorianEventFilter? filter, CancellationToken cancellationToken)
|
|
{
|
|
HistorianWcfEventOrchestrator orchestrator = new(_options);
|
|
return orchestrator.ReadEventsAsync(startUtc, endUtc, filter, cancellationToken);
|
|
}
|
|
|
|
public Task<HistorianConnectionStatus> GetConnectionStatusAsync(CancellationToken cancellationToken)
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
return Wcf.HistorianWcfStatusClient.GetConnectionStatusAsync(_options, cancellationToken);
|
|
}
|
|
|
|
public Task<HistorianStoreForwardStatus> GetStoreForwardStatusAsync(CancellationToken cancellationToken)
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
|
|
// Over gRPC (2023 R2) we return a MEASURED idle-state: the client actually contacts the server
|
|
// (GetHistorianConsoleStatus) and reports ErrorOccurred when unreachable. The active-SF buffer
|
|
// magnitude lives behind the D2 storage-engine console wall and stays false. Non-gRPC transports
|
|
// keep the synthesized all-false (no SF sidecar to probe). See R4.3 §9.7.
|
|
return UseGrpc
|
|
? HistorianGrpcStatusClient.GetStoreForwardStatusAsync(_options, cancellationToken)
|
|
: Wcf.HistorianWcfStatusClient.GetStoreForwardStatusAsync(_options, cancellationToken);
|
|
}
|
|
|
|
public Task<string?> GetSystemParameterAsync(string name, CancellationToken cancellationToken)
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
ArgumentException.ThrowIfNullOrWhiteSpace(name);
|
|
return UseGrpc
|
|
? HistorianGrpcStatusClient.GetSystemParameterAsync(_options, name, cancellationToken)
|
|
: Wcf.HistorianWcfStatusClient.GetSystemParameterAsync(_options, name, cancellationToken);
|
|
}
|
|
|
|
public Task<string?> GetServerTimeZoneAsync(CancellationToken cancellationToken)
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
|
|
// 2023 R2 gRPC returns the real server time-zone name; the 2020 WCF
|
|
// GetSystemTimeZoneName is a client-side stub (empty value), so there is no evidence-backed
|
|
// value to return on that transport — fail closed rather than hand back an empty string.
|
|
if (!UseGrpc)
|
|
{
|
|
throw new ProtocolEvidenceMissingException("GetSystemTimeZoneName (2020 WCF stub — gRPC/2023R2 only)");
|
|
}
|
|
|
|
return HistorianGrpcStatusClient.GetSystemTimeZoneNameAsync(_options, cancellationToken);
|
|
}
|
|
|
|
public Task<string?> GetRuntimeParameterAsync(string name, CancellationToken cancellationToken)
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
ArgumentException.ThrowIfNullOrWhiteSpace(name);
|
|
return UseGrpc
|
|
? HistorianGrpcStatusClient.GetRuntimeParameterAsync(_options, name, cancellationToken)
|
|
: Wcf.HistorianWcfStatusClient.GetRuntimeParameterAsync(_options, name, cancellationToken);
|
|
}
|
|
|
|
public Task<IReadOnlyList<Models.HistorianTagExtendedProperty>> GetTagExtendedPropertiesAsync(string tag, CancellationToken cancellationToken)
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
ArgumentException.ThrowIfNullOrWhiteSpace(tag);
|
|
return UseGrpc
|
|
? Grpc.HistorianGrpcTagClient.GetTagExtendedPropertiesAsync(_options, tag, cancellationToken)
|
|
: Wcf.HistorianWcfTagExtendedPropertyClient.GetTagExtendedPropertiesAsync(_options, tag, cancellationToken);
|
|
}
|
|
|
|
public Task<HistorianSqlResult> ExecuteSqlCommandAsync(string command, HistorianSqlExecuteOption option, CancellationToken cancellationToken)
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
ArgumentException.ThrowIfNullOrWhiteSpace(command);
|
|
return UseGrpc
|
|
? Grpc.HistorianGrpcSqlClient.ExecuteSqlCommandAsync(_options, command, option, cancellationToken)
|
|
: Wcf.HistorianWcfSqlClient.ExecuteSqlCommandAsync(_options, command, option, cancellationToken);
|
|
}
|
|
|
|
private static async IAsyncEnumerable<T> Missing<T>(
|
|
string operation,
|
|
[System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken)
|
|
{
|
|
await Task.Yield();
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
throw new ProtocolEvidenceMissingException(operation);
|
|
#pragma warning disable CS0162
|
|
yield break;
|
|
#pragma warning restore CS0162
|
|
}
|
|
}
|