From db3df71a54b1be3b8bcc12fc5545ee081496c6f6 Mon Sep 17 00:00:00 2001 From: georgweiss Date: Thu, 10 Sep 2026 10:00:54 +0200 Subject: [PATCH 1/2] Read Display information from each sample for raw requests --- .../appliance/ApplianceValueIterator.java | 77 ++++++++---- ...ApplianceValueIteratorExtractDataTest.java | 119 ++++++++++++++++-- 2 files changed, 164 insertions(+), 32 deletions(-) diff --git a/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceValueIterator.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceValueIterator.java index 708e469aab..af9b7d58e3 100644 --- a/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceValueIterator.java +++ b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceValueIterator.java @@ -154,7 +154,7 @@ public VType next() { * * @param dataMessage source of data * @return the appropriate VType data object - * @throws IOException + * @throws UnsupportedOperationException */ protected VType extractData(EpicsMessage dataMessage) { PayloadType type = mainStream.getPayLoadInfo().getType(); @@ -166,7 +166,7 @@ protected VType extractData(EpicsMessage dataMessage) { type == PayloadType.SCALAR_FLOAT || type == PayloadType.SCALAR_INT || type == PayloadType.SCALAR_SHORT) { - if (display==null) display = getDisplay(mainStream.getPayLoadInfo()); + if (display==null) display = getDisplay(dataMessage); return VNumber.of(dataMessage.getNumberValue(), alarm, time, display); } else if (type == PayloadType.SCALAR_ENUM) { @@ -271,7 +271,7 @@ public void close() { } /** - * Extract the display properties (min, max, alarm limits) from the given payloadinfo. + * Extract the display properties (min, max, alarm limits) from the given {@link PayloadInfo}. * * @param info the info to extract the limits from * @return the display @@ -284,30 +284,11 @@ protected Display getDisplay(PayloadInfo info) { } } - String lopr = headers.get(ApplianceArchiveReaderConstants.LOPR); - String low = headers.get(ApplianceArchiveReaderConstants.LOW); - String lolo = headers.get(ApplianceArchiveReaderConstants.LOLO); - String egu = headers.get(ApplianceArchiveReaderConstants.EGU); - String prec = headers.get(ApplianceArchiveReaderConstants.PREC); - String high = headers.get(ApplianceArchiveReaderConstants.HIGH); - String hihi = headers.get(ApplianceArchiveReaderConstants.HIHI); - String hopr = headers.get(ApplianceArchiveReaderConstants.HOPR); - - final Range range = Range.of((lopr != null) ? Double.parseDouble(lopr) : Double.NaN, - (hopr != null) ? Double.parseDouble(hopr) : Double.NaN); - return Display.of(range, - Range.of((lolo != null) ? Double.parseDouble(lolo) : Double.NaN, - (hihi != null) ? Double.parseDouble(hihi) : Double.NaN), - Range.of((low != null) ? Double.parseDouble(low) : Double.NaN, - (high != null) ? Double.parseDouble(high) : Double.NaN), - range, - (egu != null) ? egu : "", - (prec != null) ? NumberFormats.precisionFormat((int) Math.round(Double.parseDouble(prec))) - : NumberFormats.toStringFormat()); + return getDisplay(headers); } /** - * Extract the labels from the given payloadinfo when processing Enum Values. + * Extract the labels from the given payloadinfo when processing Enum Values. * EnumLabels list empty if payloadinfo from request without "fetchLatestMetadata" set to true * * @param info the info to extract the labels @@ -363,4 +344,52 @@ protected static AlarmSeverity getSeverity(int severity) { protected static String getStatus(int status) { return ChannelAccessStatusUtil.idToName(status); } + + /** + * Determines the {@link Display} for the sample. For optimized requests, this falls back to getting it + * from the payload info. For raw requests, it is expected to be present in the field values, i.e. + * each sample may define {@link Display} differently. + * @param message the info to extract the limits from + * @return A {@link Display} object. + */ + protected Display getDisplay(EpicsMessage message) { + Map fieldValues = message.getFieldValues(); + if (fieldValues == null) { + fieldValues = new HashMap<>(); + } + if (fieldValues.isEmpty()) { + for (FieldValue fieldValue : mainStream.getPayLoadInfo().getHeadersList()) { + if (!fieldValues.containsKey(fieldValue.getName())) { + fieldValues.put(fieldValue.getName(), fieldValue.getVal()); + } + } + } + + return getDisplay(fieldValues); + } + + private Display getDisplay(Map fieldValues) { + + String low = fieldValues.get(ApplianceArchiveReaderConstants.LOW); + String lolo = fieldValues.get(ApplianceArchiveReaderConstants.LOLO); + String high = fieldValues.get(ApplianceArchiveReaderConstants.HIGH); + String hihi = fieldValues.get(ApplianceArchiveReaderConstants.HIHI); + + String egu = fieldValues.get(ApplianceArchiveReaderConstants.EGU); + String prec = fieldValues.get(ApplianceArchiveReaderConstants.PREC); + String lopr = fieldValues.get(ApplianceArchiveReaderConstants.LOPR); + String hopr = fieldValues.get(ApplianceArchiveReaderConstants.HOPR); + + final Range range = Range.of((lopr != null) ? Double.parseDouble(lopr) : Double.NaN, + (hopr != null) ? Double.parseDouble(hopr) : Double.NaN); + return Display.of(range, + Range.of((lolo != null) ? Double.parseDouble(lolo) : Double.NaN, + (hihi != null) ? Double.parseDouble(hihi) : Double.NaN), + Range.of((low != null) ? Double.parseDouble(low) : Double.NaN, + (high != null) ? Double.parseDouble(high) : Double.NaN), + range, + (egu != null) ? egu : "", + (prec != null) ? NumberFormats.precisionFormat((int) Math.round(Double.parseDouble(prec))) + : NumberFormats.toStringFormat()); + } } diff --git a/app/trends/archive-reader/src/test/java/org/phoebus/archive/reader/appliance/ApplianceValueIteratorExtractDataTest.java b/app/trends/archive-reader/src/test/java/org/phoebus/archive/reader/appliance/ApplianceValueIteratorExtractDataTest.java index ba95d438e8..de924cb1fa 100644 --- a/app/trends/archive-reader/src/test/java/org/phoebus/archive/reader/appliance/ApplianceValueIteratorExtractDataTest.java +++ b/app/trends/archive-reader/src/test/java/org/phoebus/archive/reader/appliance/ApplianceValueIteratorExtractDataTest.java @@ -7,19 +7,16 @@ import edu.stanford.slac.archiverappliance.PB.EPICSEvent.PayloadType; import org.epics.archiverappliance.retrieval.client.EpicsMessage; import org.epics.archiverappliance.retrieval.client.GenMsgIterator; -import org.epics.vtype.AlarmSeverity; -import org.epics.vtype.VByteArray; -import org.epics.vtype.VDoubleArray; -import org.epics.vtype.VEnum; -import org.epics.vtype.VIntArray; -import org.epics.vtype.VNumber; -import org.epics.vtype.VString; -import org.epics.vtype.VType; +import org.epics.vtype.*; import org.junit.jupiter.api.Test; +import org.phoebus.util.time.TimestampHelper; import java.sql.Timestamp; import java.time.Instant; +import java.time.temporal.TemporalUnit; import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; @@ -57,6 +54,12 @@ private static GenMsgIterator streamOfType(PayloadType type, FieldValue... heade return s; } + private static GenMsgIterator streamOfTypeWithFieldValues(PayloadType type, FieldValue... fieldValues) { + GenMsgIterator s = mock(GenMsgIterator.class); + when(s.iterator()).thenReturn(Collections.emptyIterator()); + return s; + } + private static DataExtractIterator iteratorWithStream(GenMsgIterator stream) { FakeDataRetrieval dr = new FakeDataRetrieval(stream); return new DataExtractIterator(new FakeApplianceArchiveReader(dr), stream); @@ -240,4 +243,104 @@ void waveformByteYieldsVByteArray() { assertEquals((byte) 2, vba.getData().getByte(1)); assertEquals((byte) 3, vba.getData().getByte(2)); } + + @Test + void testExtractDisplayFromPayloadInfo() throws Exception { + GenMsgIterator iter = mock(GenMsgIterator.class); + FakeDataRetrieval fakeDataRetrieval = new FakeDataRetrieval(iter); + FakeApplianceArchiveReader fakeApplianceArchiveReader = new FakeApplianceArchiveReader(fakeDataRetrieval); + ApplianceValueIterator applianceValueIterator = new ApplianceRawValueIterator(fakeApplianceArchiveReader, "PV:NAME", Instant.EPOCH, Instant.now()); + + PayloadInfo payloadInfo = PayloadInfo.newBuilder() + .setType(PayloadType.SCALAR_DOUBLE) + .setPvname("PV:NAME") + .addHeaders(FieldValue.newBuilder().setName(ApplianceArchiveReaderConstants.LOLO).setVal("-100").build()) + .addHeaders(FieldValue.newBuilder().setName(ApplianceArchiveReaderConstants.LOW).setVal("-50").build()) + .addHeaders(FieldValue.newBuilder().setName(ApplianceArchiveReaderConstants.HIGH).setVal("50").build()) + .addHeaders(FieldValue.newBuilder().setName(ApplianceArchiveReaderConstants.HIHI).setVal("100").build()) + .addHeaders(FieldValue.newBuilder().setName(ApplianceArchiveReaderConstants.LOPR).setVal("-200").build()) + .addHeaders(FieldValue.newBuilder().setName(ApplianceArchiveReaderConstants.HOPR).setVal("200").build()) + .setYear(2000).build(); + + Display display = applianceValueIterator.getDisplay(payloadInfo); + + assertEquals(-100, display.getAlarmRange().getMinimum()); + assertEquals(100, display.getAlarmRange().getMaximum()); + assertEquals(-50, display.getWarningRange().getMinimum()); + assertEquals(50, display.getWarningRange().getMaximum()); + assertEquals(-200, display.getControlRange().getMinimum()); + assertEquals(200, display.getControlRange().getMaximum()); + } + + @Test + void testExtractDisplayFromEpicsMessage() throws Exception { + + GenMsgIterator iter = mock(GenMsgIterator.class); + FakeDataRetrieval fakeDataRetrieval = new FakeDataRetrieval(iter); + FakeApplianceArchiveReader fakeApplianceArchiveReader = new FakeApplianceArchiveReader(fakeDataRetrieval); + ApplianceValueIterator applianceValueIterator = new ApplianceRawValueIterator(fakeApplianceArchiveReader, "PV:NAME", Instant.EPOCH, Instant.now()); + + PayloadInfo payloadInfo = PayloadInfo.newBuilder() + .setType(PayloadType.SCALAR_DOUBLE) + .setPvname("PV:NAME") + .addHeaders(FieldValue.newBuilder().setName(ApplianceArchiveReaderConstants.LOLO).setVal("-100").build()) + .addHeaders(FieldValue.newBuilder().setName(ApplianceArchiveReaderConstants.LOW).setVal("-50").build()) + .addHeaders(FieldValue.newBuilder().setName(ApplianceArchiveReaderConstants.HIGH).setVal("50").build()) + .addHeaders(FieldValue.newBuilder().setName(ApplianceArchiveReaderConstants.HIHI).setVal("100").build()) + .addHeaders(FieldValue.newBuilder().setName(ApplianceArchiveReaderConstants.LOPR).setVal("-200").build()) + .addHeaders(FieldValue.newBuilder().setName(ApplianceArchiveReaderConstants.HOPR).setVal("200").build()) + .setYear(2000).build(); + when(iter.getPayLoadInfo()).thenReturn(payloadInfo); + EpicsMessage epicsMessage = mock(EpicsMessage.class); + when(epicsMessage.getTimestamp()).thenReturn(new Timestamp(1000000L)); + Map fieldValues = new HashMap(); + fieldValues.put(ApplianceArchiveReaderConstants.LOLO, "-10"); + fieldValues.put(ApplianceArchiveReaderConstants.LOW, "-5"); + fieldValues.put(ApplianceArchiveReaderConstants.HIGH, "5"); + fieldValues.put(ApplianceArchiveReaderConstants.HIHI, "10"); + fieldValues.put(ApplianceArchiveReaderConstants.LOPR, "-20"); + fieldValues.put(ApplianceArchiveReaderConstants.HOPR, "20"); + when(epicsMessage.getFieldValues()).thenReturn(fieldValues); + when(epicsMessage.getNumberValue()).thenReturn(771); + VType vType = applianceValueIterator.extractData(epicsMessage); + assertEquals(-10, Display.displayOf(vType).getAlarmRange().getMinimum()); + assertEquals(-5, Display.displayOf(vType).getWarningRange().getMinimum()); + assertEquals(5, Display.displayOf(vType).getWarningRange().getMaximum()); + assertEquals(10, Display.displayOf(vType).getAlarmRange().getMaximum()); + assertEquals(-20, Display.displayOf(vType).getControlRange().getMinimum()); + assertEquals(20, Display.displayOf(vType).getControlRange().getMaximum()); + } + + @Test + void testExtractDisplayFromEpicsMessageWithNullFieldValues() throws Exception { + + GenMsgIterator iter = mock(GenMsgIterator.class); + FakeDataRetrieval fakeDataRetrieval = new FakeDataRetrieval(iter); + FakeApplianceArchiveReader fakeApplianceArchiveReader = new FakeApplianceArchiveReader(fakeDataRetrieval); + ApplianceValueIterator applianceValueIterator = new ApplianceRawValueIterator(fakeApplianceArchiveReader, "PV:NAME", Instant.EPOCH, Instant.now()); + + PayloadInfo payloadInfo = PayloadInfo.newBuilder() + .setType(PayloadType.SCALAR_DOUBLE) + .setPvname("PV:NAME") + .addHeaders(FieldValue.newBuilder().setName(ApplianceArchiveReaderConstants.LOLO).setVal("-100").build()) + .addHeaders(FieldValue.newBuilder().setName(ApplianceArchiveReaderConstants.LOW).setVal("-50").build()) + .addHeaders(FieldValue.newBuilder().setName(ApplianceArchiveReaderConstants.HIGH).setVal("50").build()) + .addHeaders(FieldValue.newBuilder().setName(ApplianceArchiveReaderConstants.HIHI).setVal("100").build()) + .addHeaders(FieldValue.newBuilder().setName(ApplianceArchiveReaderConstants.LOPR).setVal("-200").build()) + .addHeaders(FieldValue.newBuilder().setName(ApplianceArchiveReaderConstants.HOPR).setVal("200").build()) + .setYear(2000).build(); + when(iter.getPayLoadInfo()).thenReturn(payloadInfo); + EpicsMessage epicsMessage = mock(EpicsMessage.class); + when(epicsMessage.getTimestamp()).thenReturn(new Timestamp(1000000L)); + when(epicsMessage.getFieldValues()).thenReturn(null); + when(epicsMessage.getNumberValue()).thenReturn(771); + VType vType = applianceValueIterator.extractData(epicsMessage); + assertEquals(-100, Display.displayOf(vType).getAlarmRange().getMinimum()); + assertEquals(-50, Display.displayOf(vType).getWarningRange().getMinimum()); + assertEquals(50, Display.displayOf(vType).getWarningRange().getMaximum()); + assertEquals(100, Display.displayOf(vType).getAlarmRange().getMaximum()); + assertEquals(-200, Display.displayOf(vType).getControlRange().getMinimum()); + assertEquals(200, Display.displayOf(vType).getControlRange().getMaximum()); + } + } From c87483b35ab4abd6003ecea76a7720b8dbabb59b Mon Sep 17 00:00:00 2001 From: georgweiss Date: Thu, 10 Sep 2026 10:11:01 +0200 Subject: [PATCH 2/2] Fixing linter complaints --- .../appliance/ApplianceValueIteratorExtractDataTest.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/app/trends/archive-reader/src/test/java/org/phoebus/archive/reader/appliance/ApplianceValueIteratorExtractDataTest.java b/app/trends/archive-reader/src/test/java/org/phoebus/archive/reader/appliance/ApplianceValueIteratorExtractDataTest.java index de924cb1fa..2978076b3c 100644 --- a/app/trends/archive-reader/src/test/java/org/phoebus/archive/reader/appliance/ApplianceValueIteratorExtractDataTest.java +++ b/app/trends/archive-reader/src/test/java/org/phoebus/archive/reader/appliance/ApplianceValueIteratorExtractDataTest.java @@ -9,11 +9,9 @@ import org.epics.archiverappliance.retrieval.client.GenMsgIterator; import org.epics.vtype.*; import org.junit.jupiter.api.Test; -import org.phoebus.util.time.TimestampHelper; import java.sql.Timestamp; import java.time.Instant; -import java.time.temporal.TemporalUnit; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -54,12 +52,6 @@ private static GenMsgIterator streamOfType(PayloadType type, FieldValue... heade return s; } - private static GenMsgIterator streamOfTypeWithFieldValues(PayloadType type, FieldValue... fieldValues) { - GenMsgIterator s = mock(GenMsgIterator.class); - when(s.iterator()).thenReturn(Collections.emptyIterator()); - return s; - } - private static DataExtractIterator iteratorWithStream(GenMsgIterator stream) { FakeDataRetrieval dr = new FakeDataRetrieval(stream); return new DataExtractIterator(new FakeApplianceArchiveReader(dr), stream);