diff --git a/docs/Historian.md b/docs/Historian.md index 0faa5f86..e7ab23ec 100644 --- a/docs/Historian.md +++ b/docs/Historian.md @@ -787,7 +787,7 @@ and evicts its shared historian session (see the HistorianGateway issue text in (histsdk evidence: `Quality = 133`, `OpcQuality = 192`) — the value in effect there, not a stored sample. Starting 1 ms early puts that row outside every bucket. A pre-window row only proves "the tag has earlier data" (start-of-data Partial, Count's in-range zero), and only when its **OPC quality is not Bad** — a "no earlier data" NULL row still carries a value on the wire (histsdk sets - `NumericValue = 0` on every numeric row and `StringValue = ""` on a string one) but with OPC quality 0. + `NumericValue = 0` on every numeric row and `StringValue = ""` on a string one) but with a Bad OPC quality (captured as 1, no-data; histsdk string-read-layout.md). - **Earlier-data probe.** When the data range matters (every per-sample aggregate but `Average`) and no non-Bad pre-window row arrived, a 2-row read `[1970-01-01, start)` asks whether the tag has data before the window. Normally this fires only for a tag whose history really begins inside the window. diff --git a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway/RawAggregateReader.cs b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway/RawAggregateReader.cs index f2e59d80..64738bca 100644 --- a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway/RawAggregateReader.cs +++ b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway/RawAggregateReader.cs @@ -30,7 +30,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway; /// (1 ms) BEFORE the window, so that row lands outside every bucket and can never be counted as a sample. A row /// before the window counts only as evidence that the tag has earlier data, and only when its OPC quality is not /// Bad — a "no earlier data" NULL row still arrives WITH a value (histsdk sets NumericValue = 0 on every -/// numeric row, StringValue = "" on a string one) but with OPC quality 0. +/// numeric row, StringValue = "" on a string one) but with a Bad OPC quality (captured: 1, no-data). /// /// /// The data range. The start/end-of-data Partial rules and Count's in-range empty interval need the tag's @@ -142,7 +142,7 @@ internal static class RawAggregateReader // latest at or after its earlier edge. A row before the window (the AVEVA boundary row, or a stored sample in // the margin) proves earlier data only when its OPC quality is not Bad: AVEVA's boundary row for a tag with no // earlier data is a NULL row, which arrives WITH a value (histsdk sets NumericValue = 0 / StringValue "") and - // OPC quality 0. + // a Bad OPC quality (captured: 1, no-data). if (t >= plan.StartUtc || IsNotBadOnTheWire(raw)) { if (t <= plan.EndUtc && (earliest is null || t < earliest)) earliest = t; @@ -199,7 +199,7 @@ internal static class RawAggregateReader /// 's fallback to the AVEVA quality field when opc_quality is 0, which /// is also the OPC DA Bad value a NULL row carries. /// - private static bool IsNotBadOnTheWire(HistorianSample raw) => raw.OpcQuality >= 64; + private static bool IsNotBadOnTheWire(HistorianSample raw) => (byte)raw.OpcQuality >= 64; // low byte, as SampleMapper.ToStatus reads it private static HistoryReadLimitExceededException LimitExceeded(string tag, int maxWindowRows) => new(maxWindowRows,