Merge pull request 'Phase 2 PR 7 — wire IHistoryProvider.ReadProcessedAsync end-to-end' (#6) from phase-2-pr7-history-processed into v2
This commit was merged in pull request #6.
This commit is contained in:
@@ -127,6 +127,15 @@ public sealed class DbBackedGalaxyBackend(GalaxyRepository repository) : IGalaxy
|
||||
Tags = System.Array.Empty<HistoryTagValues>(),
|
||||
});
|
||||
|
||||
public Task<HistoryReadProcessedResponse> HistoryReadProcessedAsync(
|
||||
HistoryReadProcessedRequest req, CancellationToken ct)
|
||||
=> Task.FromResult(new HistoryReadProcessedResponse
|
||||
{
|
||||
Success = false,
|
||||
Error = "MXAccess + Historian code lift pending (Phase 2 Task B.1)",
|
||||
Values = System.Array.Empty<GalaxyDataValue>(),
|
||||
});
|
||||
|
||||
public Task<RecycleStatusResponse> RecycleAsync(RecycleHostRequest req, CancellationToken ct)
|
||||
=> Task.FromResult(new RecycleStatusResponse { Accepted = true, GraceSeconds = 15 });
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ public interface IGalaxyBackend
|
||||
Task AcknowledgeAlarmAsync(AlarmAckRequest req, CancellationToken ct);
|
||||
|
||||
Task<HistoryReadResponse> HistoryReadAsync(HistoryReadRequest req, CancellationToken ct);
|
||||
Task<HistoryReadProcessedResponse> HistoryReadProcessedAsync(HistoryReadProcessedRequest req, CancellationToken ct);
|
||||
|
||||
Task<RecycleStatusResponse> RecycleAsync(RecycleHostRequest req, CancellationToken ct);
|
||||
}
|
||||
|
||||
@@ -264,6 +264,48 @@ public sealed class MxAccessGalaxyBackend : IGalaxyBackend, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<HistoryReadProcessedResponse> HistoryReadProcessedAsync(
|
||||
HistoryReadProcessedRequest req, CancellationToken ct)
|
||||
{
|
||||
if (_historian is null)
|
||||
return new HistoryReadProcessedResponse
|
||||
{
|
||||
Success = false,
|
||||
Error = "Historian disabled — no OTOPCUA_HISTORIAN_ENABLED configuration",
|
||||
Values = Array.Empty<GalaxyDataValue>(),
|
||||
};
|
||||
|
||||
if (req.IntervalMs <= 0)
|
||||
return new HistoryReadProcessedResponse
|
||||
{
|
||||
Success = false,
|
||||
Error = "HistoryReadProcessed requires IntervalMs > 0",
|
||||
Values = Array.Empty<GalaxyDataValue>(),
|
||||
};
|
||||
|
||||
var start = DateTimeOffset.FromUnixTimeMilliseconds(req.StartUtcUnixMs).UtcDateTime;
|
||||
var end = DateTimeOffset.FromUnixTimeMilliseconds(req.EndUtcUnixMs).UtcDateTime;
|
||||
|
||||
try
|
||||
{
|
||||
var samples = await _historian.ReadAggregateAsync(
|
||||
req.TagReference, start, end, req.IntervalMs, req.AggregateColumn, ct).ConfigureAwait(false);
|
||||
|
||||
var wire = samples.Select(s => ToWire(req.TagReference, s)).ToArray();
|
||||
return new HistoryReadProcessedResponse { Success = true, Values = wire };
|
||||
}
|
||||
catch (OperationCanceledException) { throw; }
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new HistoryReadProcessedResponse
|
||||
{
|
||||
Success = false,
|
||||
Error = $"Historian aggregate read failed: {ex.Message}",
|
||||
Values = Array.Empty<GalaxyDataValue>(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public Task<RecycleStatusResponse> RecycleAsync(RecycleHostRequest req, CancellationToken ct)
|
||||
=> Task.FromResult(new RecycleStatusResponse { Accepted = true, GraceSeconds = 15 });
|
||||
|
||||
@@ -305,6 +347,21 @@ public sealed class MxAccessGalaxyBackend : IGalaxyBackend, IDisposable
|
||||
return 0x80000000u; // Bad
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps a <see cref="HistorianAggregateSample"/> (one aggregate bucket) to the IPC wire
|
||||
/// shape. A null <see cref="HistorianAggregateSample.Value"/> means the aggregate was
|
||||
/// unavailable for the bucket — the Proxy translates that to OPC UA <c>BadNoData</c>.
|
||||
/// </summary>
|
||||
private static GalaxyDataValue ToWire(string reference, HistorianAggregateSample sample) => new()
|
||||
{
|
||||
TagReference = reference,
|
||||
ValueBytes = sample.Value is null ? null : MessagePackSerializer.Serialize(sample.Value.Value),
|
||||
ValueMessagePackType = 0,
|
||||
StatusCode = sample.Value is null ? 0x800E0000u /* BadNoData */ : 0x00000000u,
|
||||
SourceTimestampUtcUnixMs = new DateTimeOffset(sample.TimestampUtc, TimeSpan.Zero).ToUnixTimeMilliseconds(),
|
||||
ServerTimestampUtcUnixMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
|
||||
};
|
||||
|
||||
private static GalaxyAttributeInfo MapAttribute(GalaxyAttributeRow row) => new()
|
||||
{
|
||||
AttributeName = row.AttributeName,
|
||||
|
||||
@@ -85,6 +85,15 @@ public sealed class StubGalaxyBackend : IGalaxyBackend
|
||||
Tags = System.Array.Empty<HistoryTagValues>(),
|
||||
});
|
||||
|
||||
public Task<HistoryReadProcessedResponse> HistoryReadProcessedAsync(
|
||||
HistoryReadProcessedRequest req, CancellationToken ct)
|
||||
=> Task.FromResult(new HistoryReadProcessedResponse
|
||||
{
|
||||
Success = false,
|
||||
Error = "stub: MXAccess code lift pending (Phase 2 Task B.1)",
|
||||
Values = System.Array.Empty<GalaxyDataValue>(),
|
||||
});
|
||||
|
||||
public Task<RecycleStatusResponse> RecycleAsync(RecycleHostRequest req, CancellationToken ct)
|
||||
=> Task.FromResult(new RecycleStatusResponse
|
||||
{
|
||||
|
||||
@@ -80,6 +80,13 @@ public sealed class GalaxyFrameHandler(IGalaxyBackend backend, ILogger logger) :
|
||||
await writer.WriteAsync(MessageKind.HistoryReadResponse, resp, ct);
|
||||
return;
|
||||
}
|
||||
case MessageKind.HistoryReadProcessedRequest:
|
||||
{
|
||||
var resp = await backend.HistoryReadProcessedAsync(
|
||||
Deserialize<HistoryReadProcessedRequest>(body), ct);
|
||||
await writer.WriteAsync(MessageKind.HistoryReadProcessedResponse, resp, ct);
|
||||
return;
|
||||
}
|
||||
case MessageKind.RecycleHostRequest:
|
||||
{
|
||||
var resp = await backend.RecycleAsync(Deserialize<RecycleHostRequest>(body), ct);
|
||||
|
||||
Reference in New Issue
Block a user