Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ REACT_APP_PVWS_HTTP_URL=http://localhost:8081/pvws/
REACT_APP_LIVE_MONITOR_WARN=50
REACT_APP_LIVE_MONITOR_MAX=100

# By default, PV Info treats byte arrays as strings. Change this to false to treat them as byte arrays.
# PVWS was updated in 2024 to send "b64byt" instead of using the "text" field for these waveforms of chars
# https://github.com/ornl-epics/pvws/pull/23
PVWS_TREAT_BYTE_ARRAY_AS_STRING=true

# By default, PV Info does not allow the user to subscribe to waveform PVs. Switch this to true
# to allow viewing of waveform PVs. Viewing large waveforms isn't useful in PV Info but
# for small waveforms it can be nice to see. PVWS uses EPICS_CA_MAX_ARRAY_BYTES and that
Expand Down
101 changes: 101 additions & 0 deletions src/api.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ApiProxyConnector } from "@elastic/search-ui-elasticsearch-connector";
import { toByteArray } from "base64-js";
import CustomElasticSearchAPIConnector from "./components/caputlog/CustomElasticSearchAPIConnector";

const channelFinderURL = import.meta.env.PROD ? import.meta.env.REACT_APP_CF_URL : import.meta.env.REACT_APP_CF_URL_DEV;
Expand Down Expand Up @@ -239,6 +240,105 @@ async function standardQuery(requestURI, options = null, handleData = null) {
})
}

function parseWebSocketMessage(jsonMessage, fixedPrecision = null) {
if (jsonMessage === null) {
return null;
}
else if (jsonMessage.type === "update") {
if (jsonMessage.pv === undefined) {
console.log("Websocket message without a PV name");
return null;
}
let pvData = jsonMessage;
if ("alarm_low" in pvData) {
if (pvData.alarm_low === "NaN" || pvData.alarm_low === "Infinity" || pvData.alarm_low === "-Infinity") {
pvData.alarm_low = "n/a";
}
}
if ("alarm_high" in pvData) {
if (pvData.alarm_high === "NaN" || pvData.alarm_high === "Infinity" || pvData.alarm_high === "-Infinity") {
pvData.alarm_high = "n/a";
}
}
if ("warn_low" in pvData) {
if (pvData.warn_low === "NaN" || pvData.warn_low === "Infinity" || pvData.warn_low === "-Infinity") {
pvData.warn_low = "n/a";
}
}
if ("warn_high" in pvData) {
if (pvData.warn_high === "NaN" || pvData.warn_high === "Infinity" || pvData.warn_high === "-Infinity") {
pvData.warn_high = "n/a";
}
}
if ("seconds" in pvData) {
if ("nanos" in pvData) {
pvData.timestamp = new Date(pvData.seconds * 1000 + (pvData.nanos * 1e-6)).toLocaleString();
} else {
pvData.timestamp = new Date(pvData.seconds * 1000).toLocaleString();
}
}
// determine the "pv_value" which will be displayed in the UI
// see "handleMessage" in https://github.com/ornl-epics/pvws/blob/main/src/main/webapp/js/pvws.js
if ("text" in pvData) {
pvData.pv_value = pvData.text;
} else if ("b64dbl" in pvData) {
let bytes = toByteArray(pvData.b64dbl);
let value_array = new Float64Array(bytes.buffer);
pvData.pv_value = Array.prototype.slice.call(value_array);
} else if ("b64int" in pvData) {
let bytes = toByteArray(pvData.b64int);
let value_array = new Int32Array(bytes.buffer);
pvData.pv_value = Array.prototype.slice.call(value_array);
} else if ("b64byt" in pvData) {
let bytes = toByteArray(pvData.b64byt);
if (import.meta.env.PVWS_TREAT_BYTE_ARRAY_AS_STRING === "false") {
let value_array = new Uint8Array(bytes.buffer);
pvData.pv_value = Array.prototype.slice.call(value_array);
} else {
try {
const decoder = new TextDecoder('utf-8');
pvData.pv_value = decoder.decode(bytes);
} catch (error) {
console.log("Error decoding byte array: ", error.message);
}
}
} else if ("b64flt" in pvData) {
let bytes = toByteArray(pvData.b64flt);
let value_array = new Float32Array(bytes.buffer);
pvData.pv_value = Array.prototype.slice.call(value_array);
} else if ("b64srt" in pvData) {
let bytes = toByteArray(pvData.b64srt);
let value_array = new Int16Array(bytes.buffer);
pvData.pv_value = Array.prototype.slice.call(value_array);
} else if ("value" in pvData) {
if (fixedPrecision) {
if ((Number(pvData.value) >= 0.01 && Number(pvData.value) < 1000000000) || (Number(pvData.value) <= -0.01 && Number(pvData.value) > -1000000000) || Number(pvData.value) === 0) {
pvData.pv_value = Number(pvData.value.toFixed(Number(fixedPrecision)));
} else {
pvData.pv_value = Number(pvData.value).toExponential(Number(fixedPrecision));
}
}
else {
// if precision was explicitly set (and badly assume 0 is not explicit) then use that
if (pvData.precision !== null && pvData.precision !== "" && !isNaN(pvData.precision) && pvData.precision !== 0) {
pvData.pv_value = (Number(pvData.value) >= 0.01 || Number(pvData.value) === 0) ? Number(pvData.value.toFixed(Number(pvData.precision))) : Number(pvData.value).toExponential(Number(pvData.precision));
}
// otherwise show full value
else {
pvData.pv_value = (Number(pvData.value) >= 0.01 || Number(pvData.value) === 0) ? Number(pvData.value) : Number(pvData.value).toExponential();
}
}
} else {
pvData.pv_value = null;
}
return pvData;
}
else {
console.log("Unknown message type");
return null;
}
}

const logEnum = {
ONLINE_LOG: "online_log",
ALARM_LOG: "alarm_log"
Expand Down Expand Up @@ -269,6 +369,7 @@ const api = {
HELPERS_ENUM: queryHelperEnum,
CAPUTLOG_URL: caputlogURL,
CAPUTLOG_CONNECTOR: caputLogConnector,
PARSE_WEBSOCKET_MSG: parseWebSocketMessage,
}

export default api;
57 changes: 10 additions & 47 deletions src/components/home/queryresults/value/Value.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import useWebSocket from 'react-use-websocket';
import api from '../../../../api';
import colors from '../../../../colors';
import PropTypes from "prop-types";
import { toByteArray } from 'base64-js';

const propTypes = {
pvName: PropTypes.string,
Expand All @@ -27,54 +26,18 @@ function Value(props) {

// parse web socket message. filter on useWebSocket above means we only parse messages for this PV
useEffect(() => {
if (lastJsonMessage === null) {
return;
const jsonMessage = api.PARSE_WEBSOCKET_MSG(lastJsonMessage, 2); // fix precision to 2 on the PV table
if (jsonMessage === null) {
return; // unable to parse, could be invalid message type, no PV name, null lastJsonMessage
}
const jsonMessage = lastJsonMessage;
if (jsonMessage.type === "update") {
const severity = jsonMessage.severity;
const units = jsonMessage.units;
const text = jsonMessage.text;
const b64dbl = jsonMessage.b64dbl;
const b64int = jsonMessage.b64int;
const value = jsonMessage.value;
const pv = jsonMessage.pv;
if (pv === undefined) {
console.log("Websocket message without an PV name");
return;
}
if (severity !== undefined) {
setPVSeverity(severity);
}
if (units !== undefined) {
setPVUnit(units);
}
if (text !== undefined) {
setPVValue(text);
}
else if (b64dbl !== undefined) {
let bytes = toByteArray(b64dbl);
let value_array = new Float64Array(bytes.buffer);
value_array = Array.prototype.slice.call(value_array);
setPVValue(value_array);
}
else if (b64int !== undefined) {
let bytes = toByteArray(b64int);
let value_array = new Int32Array(bytes.buffer);
value_array = Array.prototype.slice.call(value_array);
setPVValue(value_array);
}
else if (value !== undefined) {
if ((Number(value) >= 0.01 && Number(value) < 1000000000) || (Number(value) <= -0.01 && Number(value) > -1000000000) || Number(value) === 0) {
setPVValue(Number(value.toFixed(2)));
}
else {
setPVValue(Number(value).toExponential(2));
}
}
if ("severity" in jsonMessage) {
setPVSeverity(jsonMessage.severity);
}
else {
console.log("Unexpected message type: ", jsonMessage);
if ("units" in jsonMessage) {
setPVUnit(jsonMessage.units);
}
if ("pv_value" in jsonMessage) {
setPVValue(jsonMessage.pv_value);
}
}, [lastJsonMessage]);

Expand Down
Loading