feat(grpc): HistorianSessionKind + …OnSession seams (read/status/tag-write)
This commit is contained in:
@@ -126,28 +126,51 @@ internal sealed class HistorianGrpcReadOrchestrator
|
|||||||
return RunRawQueryOnSession(connection, clientHandle, tag, startUtc, endUtc, maxValues, cancellationToken);
|
return RunRawQueryOnSession(connection, clientHandle, tag, startUtc, endUtc, maxValues, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Spike/Phase-1 seam (pending.md A1): run an aggregate query against an EXTERNALLY-supplied,
|
||||||
|
// already-authenticated connection + client handle — i.e. NO Create()/handshake here.
|
||||||
|
// RunAggregateChain delegates to this so the per-call path and the reuse path share one query
|
||||||
|
// implementation (DRY).
|
||||||
|
internal List<HistorianAggregateSample> RunAggregateQueryOnSession(
|
||||||
|
HistorianGrpcConnection connection,
|
||||||
|
uint clientHandle,
|
||||||
|
string tag,
|
||||||
|
DateTime startUtc,
|
||||||
|
DateTime endUtc,
|
||||||
|
RetrievalMode mode,
|
||||||
|
TimeSpan interval,
|
||||||
|
CancellationToken ct)
|
||||||
|
{
|
||||||
|
return RunAggregateQuery(connection, clientHandle, tag, startUtc, endUtc, mode, interval, ct);
|
||||||
|
}
|
||||||
|
|
||||||
private List<HistorianAggregateSample> RunAggregateChain(
|
private List<HistorianAggregateSample> RunAggregateChain(
|
||||||
string tag, DateTime startUtc, DateTime endUtc, RetrievalMode mode, TimeSpan interval, CancellationToken cancellationToken)
|
string tag, DateTime startUtc, DateTime endUtc, RetrievalMode mode, TimeSpan interval, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
using HistorianGrpcConnection connection = HistorianGrpcChannelFactory.Create(_options);
|
using HistorianGrpcConnection connection = HistorianGrpcChannelFactory.Create(_options);
|
||||||
uint clientHandle = OpenAuthenticatedConnection(connection, cancellationToken);
|
uint clientHandle = OpenAuthenticatedConnection(connection, cancellationToken);
|
||||||
return RunAggregateQuery(connection, clientHandle, tag, startUtc, endUtc, mode, interval, cancellationToken);
|
return RunAggregateQueryOnSession(connection, clientHandle, tag, startUtc, endUtc, mode, interval, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<HistorianSample> RunAtTimeChain(string tag, IReadOnlyList<DateTime> timestampsUtc, CancellationToken cancellationToken)
|
// Spike/Phase-1 seam (pending.md A1): run an at-time query against an EXTERNALLY-supplied,
|
||||||
|
// already-authenticated connection + client handle — i.e. NO Create()/handshake here.
|
||||||
|
// RunAtTimeChain delegates to this so the per-call path and the reuse path share one
|
||||||
|
// implementation (DRY).
|
||||||
|
internal List<HistorianSample> RunAtTimeOnSession(
|
||||||
|
HistorianGrpcConnection connection,
|
||||||
|
uint clientHandle,
|
||||||
|
string tag,
|
||||||
|
IReadOnlyList<DateTime> timestampsUtc,
|
||||||
|
CancellationToken ct)
|
||||||
{
|
{
|
||||||
if (timestampsUtc.Count == 0)
|
if (timestampsUtc.Count == 0)
|
||||||
{
|
{
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
using HistorianGrpcConnection connection = HistorianGrpcChannelFactory.Create(_options);
|
|
||||||
uint clientHandle = OpenAuthenticatedConnection(connection, cancellationToken);
|
|
||||||
|
|
||||||
List<HistorianSample> results = new(timestampsUtc.Count);
|
List<HistorianSample> results = new(timestampsUtc.Count);
|
||||||
foreach (DateTime ts in timestampsUtc)
|
foreach (DateTime ts in timestampsUtc)
|
||||||
{
|
{
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
ct.ThrowIfCancellationRequested();
|
||||||
DateTime tsUtc = ts.ToUniversalTime();
|
DateTime tsUtc = ts.ToUniversalTime();
|
||||||
List<HistorianAggregateSample> aggregates = RunAggregateQuery(
|
List<HistorianAggregateSample> aggregates = RunAggregateQuery(
|
||||||
connection,
|
connection,
|
||||||
@@ -157,7 +180,7 @@ internal sealed class HistorianGrpcReadOrchestrator
|
|||||||
tsUtc + TimeSpan.FromTicks(1),
|
tsUtc + TimeSpan.FromTicks(1),
|
||||||
RetrievalMode.Interpolated,
|
RetrievalMode.Interpolated,
|
||||||
TimeSpan.FromTicks(2),
|
TimeSpan.FromTicks(2),
|
||||||
cancellationToken);
|
ct);
|
||||||
|
|
||||||
if (aggregates.Count == 0)
|
if (aggregates.Count == 0)
|
||||||
{
|
{
|
||||||
@@ -179,6 +202,18 @@ internal sealed class HistorianGrpcReadOrchestrator
|
|||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<HistorianSample> RunAtTimeChain(string tag, IReadOnlyList<DateTime> timestampsUtc, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (timestampsUtc.Count == 0)
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
using HistorianGrpcConnection connection = HistorianGrpcChannelFactory.Create(_options);
|
||||||
|
uint clientHandle = OpenAuthenticatedConnection(connection, cancellationToken);
|
||||||
|
return RunAtTimeOnSession(connection, clientHandle, tag, timestampsUtc, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
private uint OpenAuthenticatedConnection(HistorianGrpcConnection connection, CancellationToken cancellationToken)
|
private uint OpenAuthenticatedConnection(HistorianGrpcConnection connection, CancellationToken cancellationToken)
|
||||||
=> HistorianGrpcHandshake.OpenAuthenticatedConnection(connection, _options, cancellationToken);
|
=> HistorianGrpcHandshake.OpenAuthenticatedConnection(connection, _options, cancellationToken);
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,19 @@ internal static class HistorianGrpcStatusClient
|
|||||||
{
|
{
|
||||||
using HistorianGrpcConnection connection = HistorianGrpcChannelFactory.Create(options);
|
using HistorianGrpcConnection connection = HistorianGrpcChannelFactory.Create(options);
|
||||||
uint clientHandle = HistorianGrpcHandshake.OpenAuthenticatedConnection(connection, options, cancellationToken);
|
uint clientHandle = HistorianGrpcHandshake.OpenAuthenticatedConnection(connection, options, cancellationToken);
|
||||||
|
return GetSystemParameterOnSession(connection, clientHandle, options, parameterName, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spike/Phase-1 seam (pending.md A1): run GetSystemParameter against an EXTERNALLY-supplied,
|
||||||
|
// already-authenticated connection + client handle — NO Create()/handshake here. GetSystemParameter
|
||||||
|
// delegates so the per-call path and the reuse path share one RPC implementation (DRY).
|
||||||
|
internal static string? GetSystemParameterOnSession(
|
||||||
|
HistorianGrpcConnection connection,
|
||||||
|
uint clientHandle,
|
||||||
|
HistorianClientOptions options,
|
||||||
|
string parameterName,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
var statusClient = new GrpcStatus.StatusService.StatusServiceClient(connection.Channel);
|
var statusClient = new GrpcStatus.StatusService.StatusServiceClient(connection.Channel);
|
||||||
GrpcStatus.GetSystemParameterResponse response = statusClient.GetSystemParameter(
|
GrpcStatus.GetSystemParameterResponse response = statusClient.GetSystemParameter(
|
||||||
new GrpcStatus.GetSystemParameterRequest { UiHandle = clientHandle, StrParameterName = parameterName },
|
new GrpcStatus.GetSystemParameterRequest { UiHandle = clientHandle, StrParameterName = parameterName },
|
||||||
@@ -82,23 +94,7 @@ internal static class HistorianGrpcStatusClient
|
|||||||
{
|
{
|
||||||
using HistorianGrpcConnection connection = HistorianGrpcChannelFactory.Create(options);
|
using HistorianGrpcConnection connection = HistorianGrpcChannelFactory.Create(options);
|
||||||
HistorianGrpcHandshake.Session session = HistorianGrpcHandshake.OpenSession(connection, options, cancellationToken);
|
HistorianGrpcHandshake.Session session = HistorianGrpcHandshake.OpenSession(connection, options, cancellationToken);
|
||||||
|
return GetStoreForwardStatusOnSession(connection, session.StringHandle, options, NotStoring, cancellationToken);
|
||||||
var statusClient = new GrpcStatus.StatusService.StatusServiceClient(connection.Channel);
|
|
||||||
GrpcStatus.GetHistorianConsoleStatusResponse response = statusClient.GetHistorianConsoleStatus(
|
|
||||||
new GrpcStatus.GetHistorianConsoleStatusRequest { StrHandle = session.StringHandle },
|
|
||||||
connection.Metadata,
|
|
||||||
DateTime.UtcNow.Add(options.RequestTimeout),
|
|
||||||
cancellationToken);
|
|
||||||
|
|
||||||
if (response.Status?.BSuccess ?? false)
|
|
||||||
{
|
|
||||||
// Measured: server reachable, storage console reporting normally → not-storing baseline.
|
|
||||||
return NotStoring(errorOccurred: false, error: null);
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] err = response.Status?.BtError?.ToByteArray() ?? [];
|
|
||||||
string detail = err.Length == 0 ? "GetHistorianConsoleStatus returned failure." : Convert.ToHexString(err);
|
|
||||||
return NotStoring(errorOccurred: true, error: $"GetHistorianConsoleStatus failed: {detail}");
|
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
@@ -111,6 +107,36 @@ internal static class HistorianGrpcStatusClient
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Spike/Phase-1 seam (pending.md A1): run GetHistorianConsoleStatus against an EXTERNALLY-supplied,
|
||||||
|
// already-authenticated connection + string handle — NO Create()/handshake here. GetStoreForwardStatus
|
||||||
|
// delegates so the per-call path and the reuse path share one RPC implementation (DRY). The
|
||||||
|
// unreachable/auth-failure try/catch (which must also cover the handshake) stays with the per-call
|
||||||
|
// method; this seam runs only the RPC + result mapping against the supplied session.
|
||||||
|
internal static HistorianStoreForwardStatus GetStoreForwardStatusOnSession(
|
||||||
|
HistorianGrpcConnection connection,
|
||||||
|
string stringHandle,
|
||||||
|
HistorianClientOptions options,
|
||||||
|
Func<bool, string?, HistorianStoreForwardStatus> notStoring,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var statusClient = new GrpcStatus.StatusService.StatusServiceClient(connection.Channel);
|
||||||
|
GrpcStatus.GetHistorianConsoleStatusResponse response = statusClient.GetHistorianConsoleStatus(
|
||||||
|
new GrpcStatus.GetHistorianConsoleStatusRequest { StrHandle = stringHandle },
|
||||||
|
connection.Metadata,
|
||||||
|
DateTime.UtcNow.Add(options.RequestTimeout),
|
||||||
|
cancellationToken);
|
||||||
|
|
||||||
|
if (response.Status?.BSuccess ?? false)
|
||||||
|
{
|
||||||
|
// Measured: server reachable, storage console reporting normally → not-storing baseline.
|
||||||
|
return notStoring(false, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] err = response.Status?.BtError?.ToByteArray() ?? [];
|
||||||
|
string detail = err.Length == 0 ? "GetHistorianConsoleStatus returned failure." : Convert.ToHexString(err);
|
||||||
|
return notStoring(true, $"GetHistorianConsoleStatus failed: {detail}");
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns a <em>measured</em> connection status over the 2023 R2 gRPC transport (plan #5). Mirrors
|
/// Returns a <em>measured</em> connection status over the 2023 R2 gRPC transport (plan #5). Mirrors
|
||||||
/// <see cref="Wcf.HistorianWcfStatusClient"/>'s synthesize-from-handshake approach: it opens an
|
/// <see cref="Wcf.HistorianWcfStatusClient"/>'s synthesize-from-handshake approach: it opens an
|
||||||
@@ -135,11 +161,7 @@ internal static class HistorianGrpcStatusClient
|
|||||||
// A successful OpenConnection yields a non-empty storage-session GUID — proof the server and
|
// A successful OpenConnection yields a non-empty storage-session GUID — proof the server and
|
||||||
// its storage session are reachable, the gRPC analog of the WCF handshake probe.
|
// its storage session are reachable, the gRPC analog of the WCF handshake probe.
|
||||||
HistorianGrpcHandshake.Session session = HistorianGrpcHandshake.OpenSession(connection, options, cancellationToken);
|
HistorianGrpcHandshake.Session session = HistorianGrpcHandshake.OpenSession(connection, options, cancellationToken);
|
||||||
connected = session.StorageSessionId != Guid.Empty;
|
(connected, error) = EvaluateConnectionStatusOnSession(connection, session);
|
||||||
if (!connected)
|
|
||||||
{
|
|
||||||
error = "OpenConnection returned an empty storage-session handle.";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
@@ -162,6 +184,21 @@ internal static class HistorianGrpcStatusClient
|
|||||||
ConnectionKind: HistorianConnectionKind.Process);
|
ConnectionKind: HistorianConnectionKind.Process);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Spike/Phase-1 seam (pending.md A1): evaluate connection status against an EXTERNALLY-supplied,
|
||||||
|
// already-authenticated connection + session — NO Create()/handshake here. GetConnectionStatus
|
||||||
|
// delegates so the per-call path and the reuse path share one evaluation (DRY). Unlike the other
|
||||||
|
// status seams there is no follow-on RPC: connectivity is derived entirely from the handshake's own
|
||||||
|
// storage-session GUID (a successful OpenConnection yields a non-empty GUID). The unreachable/auth
|
||||||
|
// try/catch (which must also cover the handshake) stays with the per-call method.
|
||||||
|
internal static (bool Connected, string? Error) EvaluateConnectionStatusOnSession(
|
||||||
|
HistorianGrpcConnection connection,
|
||||||
|
HistorianGrpcHandshake.Session session)
|
||||||
|
{
|
||||||
|
bool connected = session.StorageSessionId != Guid.Empty;
|
||||||
|
string? error = connected ? null : "OpenConnection returned an empty storage-session handle.";
|
||||||
|
return (connected, error);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reads the Historian server's system time-zone name (roadmap item R1.3,
|
/// Reads the Historian server's system time-zone name (roadmap item R1.3,
|
||||||
/// <c>StatusService.GetSystemTimeZoneName</c>). Unlike the 2020 WCF surface — where the native
|
/// <c>StatusService.GetSystemTimeZoneName</c>). Unlike the 2020 WCF surface — where the native
|
||||||
|
|||||||
@@ -56,7 +56,18 @@ internal sealed class HistorianGrpcTagWriteOrchestrator
|
|||||||
{
|
{
|
||||||
using HistorianGrpcConnection connection = HistorianGrpcChannelFactory.Create(_options);
|
using HistorianGrpcConnection connection = HistorianGrpcChannelFactory.Create(_options);
|
||||||
HistorianGrpcHandshake.Session session = HistorianGrpcHandshake.OpenSession(connection, _options, cancellationToken, WriteEnabledConnectionMode);
|
HistorianGrpcHandshake.Session session = HistorianGrpcHandshake.OpenSession(connection, _options, cancellationToken, WriteEnabledConnectionMode);
|
||||||
|
return EnsureTagOnSession(connection, session, definition, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spike/Phase-1 seam (pending.md A1): run EnsureTags against an EXTERNALLY-supplied, already-
|
||||||
|
// authenticated write-enabled (0x401) connection + session — NO Create()/handshake here. EnsureTag
|
||||||
|
// delegates so the per-call path and the reuse path share one op implementation (DRY).
|
||||||
|
internal bool EnsureTagOnSession(
|
||||||
|
HistorianGrpcConnection connection,
|
||||||
|
HistorianGrpcHandshake.Session session,
|
||||||
|
HistorianTagDefinition definition,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
byte[] payload = HistorianTagWriteProtocol.SerializeAnalogCTagMetadata(
|
byte[] payload = HistorianTagWriteProtocol.SerializeAnalogCTagMetadata(
|
||||||
tagName: definition.TagName,
|
tagName: definition.TagName,
|
||||||
description: definition.Description,
|
description: definition.Description,
|
||||||
@@ -97,7 +108,18 @@ internal sealed class HistorianGrpcTagWriteOrchestrator
|
|||||||
{
|
{
|
||||||
using HistorianGrpcConnection connection = HistorianGrpcChannelFactory.Create(_options);
|
using HistorianGrpcConnection connection = HistorianGrpcChannelFactory.Create(_options);
|
||||||
HistorianGrpcHandshake.Session session = HistorianGrpcHandshake.OpenSession(connection, _options, cancellationToken, WriteEnabledConnectionMode);
|
HistorianGrpcHandshake.Session session = HistorianGrpcHandshake.OpenSession(connection, _options, cancellationToken, WriteEnabledConnectionMode);
|
||||||
|
return DeleteTagOnSession(connection, session, tagName, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spike/Phase-1 seam (pending.md A1): run DeleteTags against an EXTERNALLY-supplied, already-
|
||||||
|
// authenticated write-enabled (0x401) connection + session — NO Create()/handshake here. DeleteTag
|
||||||
|
// delegates so the per-call path and the reuse path share one op implementation (DRY).
|
||||||
|
internal bool DeleteTagOnSession(
|
||||||
|
HistorianGrpcConnection connection,
|
||||||
|
HistorianGrpcHandshake.Session session,
|
||||||
|
string tagName,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
// DeleteTags takes the transient uint client handle (not the string handle), per the WCF wire capture.
|
// DeleteTags takes the transient uint client handle (not the string handle), per the WCF wire capture.
|
||||||
byte[] tagNames = HistorianTagWriteProtocol.SerializeDeleteTagNames([tagName]);
|
byte[] tagNames = HistorianTagWriteProtocol.SerializeDeleteTagNames([tagName]);
|
||||||
var historyClient = new GrpcHistory.HistoryService.HistoryServiceClient(connection.Channel);
|
var historyClient = new GrpcHistory.HistoryService.HistoryServiceClient(connection.Channel);
|
||||||
@@ -131,7 +153,20 @@ internal sealed class HistorianGrpcTagWriteOrchestrator
|
|||||||
{
|
{
|
||||||
using HistorianGrpcConnection connection = HistorianGrpcChannelFactory.Create(_options);
|
using HistorianGrpcConnection connection = HistorianGrpcChannelFactory.Create(_options);
|
||||||
HistorianGrpcHandshake.Session session = HistorianGrpcHandshake.OpenSession(connection, _options, cancellationToken, WriteEnabledConnectionMode);
|
HistorianGrpcHandshake.Session session = HistorianGrpcHandshake.OpenSession(connection, _options, cancellationToken, WriteEnabledConnectionMode);
|
||||||
|
return AddTagExtendedPropertiesOnSession(connection, session, tagName, properties, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spike/Phase-1 seam (pending.md A1): run AddTagExtendedProperties against an EXTERNALLY-supplied,
|
||||||
|
// already-authenticated write-enabled (0x401) connection + session — NO Create()/handshake here.
|
||||||
|
// AddTagExtendedProperties delegates so the per-call path and the reuse path share one op
|
||||||
|
// implementation (DRY).
|
||||||
|
internal bool AddTagExtendedPropertiesOnSession(
|
||||||
|
HistorianGrpcConnection connection,
|
||||||
|
HistorianGrpcHandshake.Session session,
|
||||||
|
string tagName,
|
||||||
|
IReadOnlyList<HistorianTagExtendedProperty> properties,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
byte[] inBuff = HistorianTagExtendedPropertyProtocol.SerializeAddRequest(tagName, properties);
|
byte[] inBuff = HistorianTagExtendedPropertyProtocol.SerializeAddRequest(tagName, properties);
|
||||||
var historyClient = new GrpcHistory.HistoryService.HistoryServiceClient(connection.Channel);
|
var historyClient = new GrpcHistory.HistoryService.HistoryServiceClient(connection.Channel);
|
||||||
GrpcHistory.AddTagExtendedPropertiesResponse response = historyClient.AddTagExtendedProperties(
|
GrpcHistory.AddTagExtendedPropertiesResponse response = historyClient.AddTagExtendedProperties(
|
||||||
@@ -275,7 +310,18 @@ internal sealed class HistorianGrpcTagWriteOrchestrator
|
|||||||
{
|
{
|
||||||
using HistorianGrpcConnection connection = HistorianGrpcChannelFactory.Create(_options);
|
using HistorianGrpcConnection connection = HistorianGrpcChannelFactory.Create(_options);
|
||||||
HistorianGrpcHandshake.Session session = HistorianGrpcHandshake.OpenSession(connection, _options, cancellationToken, WriteEnabledConnectionMode);
|
HistorianGrpcHandshake.Session session = HistorianGrpcHandshake.OpenSession(connection, _options, cancellationToken, WriteEnabledConnectionMode);
|
||||||
|
return RenameTagsOnSession(connection, session, pairs, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spike/Phase-1 seam (pending.md A1): run StartJob (rename) against an EXTERNALLY-supplied, already-
|
||||||
|
// authenticated write-enabled (0x401) connection + session — NO Create()/handshake here. RenameTags
|
||||||
|
// delegates so the per-call path and the reuse path share one op implementation (DRY).
|
||||||
|
internal HistorianTagRenameResult RenameTagsOnSession(
|
||||||
|
HistorianGrpcConnection connection,
|
||||||
|
HistorianGrpcHandshake.Session session,
|
||||||
|
IReadOnlyList<(string OldName, string NewName)> pairs,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
byte[] jobBuffer = HistorianTagRenameProtocol.SerializeRenameJob(pairs);
|
byte[] jobBuffer = HistorianTagRenameProtocol.SerializeRenameJob(pairs);
|
||||||
var historyClient = new GrpcHistory.HistoryService.HistoryServiceClient(connection.Channel);
|
var historyClient = new GrpcHistory.HistoryService.HistoryServiceClient(connection.Channel);
|
||||||
GrpcHistory.StartJobResponse response = historyClient.StartJob(
|
GrpcHistory.StartJobResponse response = historyClient.StartJob(
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
namespace AVEVA.Historian.Client;
|
||||||
|
|
||||||
|
/// <summary>Connection mode for an authenticated session. WriteEnabled (0x401) is a superset that
|
||||||
|
/// also serves reads (live-verified 2026-06-25); ReadOnly (0x402) is read-only.</summary>
|
||||||
|
public enum HistorianSessionKind { ReadOnly, WriteEnabled }
|
||||||
Reference in New Issue
Block a user