From edb7edb2dfeb322d13be07441ed84a2f91fb8711 Mon Sep 17 00:00:00 2001 From: Tynan Ford Date: Thu, 14 Dec 2023 20:52:03 -0800 Subject: [PATCH] Add option to display live value of waveform PVs --- .env | 6 ++++ package-lock.json | 24 +++++++++++-- package.json | 3 +- .../home/queryresults/value/Value.jsx | 35 ++++++++++++++----- .../valuecheckbox/ValueCheckbox.jsx | 9 ++--- src/components/pv/KeyValuePair.jsx | 4 +++ src/components/pv/PV.jsx | 3 ++ src/components/pv/valuetable/ValueTable.jsx | 26 +++++++++++++- 8 files changed, 90 insertions(+), 20 deletions(-) diff --git a/.env b/.env index 9da84f2..62afc44 100644 --- a/.env +++ b/.env @@ -105,6 +105,12 @@ 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 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 +# can be used to limit giant waveforms coming across the websocket connection as a mitigation +REACT_APP_PVWS_ALLOW_WAVEFORMS=false + ###################################################################################### # Archiver Web Viewer ###################################################################################### diff --git a/package-lock.json b/package-lock.json index 1309f93..d7eefb4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pvinfo", - "version": "2.0.0", + "version": "2.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pvinfo", - "version": "2.0.0", + "version": "2.1.0", "dependencies": { "@emotion/react": "^11.9.0", "@emotion/styled": "^11.8.1", @@ -17,6 +17,7 @@ "@testing-library/react": "^13.1.1", "@testing-library/user-event": "^14.1.1", "@vitejs/plugin-react": "^4.1.0", + "base64-js": "^1.5.1", "he": "^1.2.0", "react": "^18.1.0", "react-dom": "^18.1.0", @@ -2100,6 +2101,25 @@ "npm": ">=6" } }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/braces": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", diff --git a/package.json b/package.json index 831f008..e543d74 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "pvinfo", "homepage": "https://yourdomainhere/pvinfo", - "version": "2.0.1", + "version": "2.1.0", "private": true, "dependencies": { "@emotion/react": "^11.9.0", @@ -13,6 +13,7 @@ "@testing-library/react": "^13.1.1", "@testing-library/user-event": "^14.1.1", "@vitejs/plugin-react": "^4.1.0", + "base64-js": "^1.5.1", "he": "^1.2.0", "react": "^18.1.0", "react-dom": "^18.1.0", diff --git a/src/components/home/queryresults/value/Value.jsx b/src/components/home/queryresults/value/Value.jsx index 897d65b..cea5a54 100644 --- a/src/components/home/queryresults/value/Value.jsx +++ b/src/components/home/queryresults/value/Value.jsx @@ -3,6 +3,7 @@ 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, @@ -34,6 +35,8 @@ function Value(props) { 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) { @@ -49,6 +52,18 @@ function Value(props) { 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))); @@ -81,15 +96,17 @@ function Value(props) { } const severityName = pvSeverity === "UNDEFINED" || pvSeverity === "INVALID" ? ` (${pvSeverity})` : null - if (pvUnit !== undefined) { - return ( -
{`${pvValue} ${pvUnit}`}{severityName}
- ); - } - else if (pvValue !== undefined) { - return ( -
{pvValue}{severityName}
- ); + if (pvValue !== undefined) { + if (Array.isArray(pvValue)) { + return ( +
{`[ ${pvValue.join(', ')} ] ${pvUnit}`}{severityName}
+ ) + } + else { + return ( +
{`${pvValue} ${pvUnit}`}{severityName}
+ ); + } } else { return ( diff --git a/src/components/home/queryresults/valuecheckbox/ValueCheckbox.jsx b/src/components/home/queryresults/valuecheckbox/ValueCheckbox.jsx index 995adcc..0c2441e 100644 --- a/src/components/home/queryresults/valuecheckbox/ValueCheckbox.jsx +++ b/src/components/home/queryresults/valuecheckbox/ValueCheckbox.jsx @@ -14,11 +14,6 @@ const propTypes = { updateCurrentChecked: PropTypes.func, } -// TODO - check if these changes make performance better or worse or same -// fix dependency array issues, cleanup -// create PR into current PR branch -// then will need to talk to mitch and get things merged - function ValueCheckbox(props) { // open web socket for sending subscribe/clear messages // filter all messages as false since we don't need to read anything in parent component @@ -34,7 +29,7 @@ function ValueCheckbox(props) { } useEffect(() => { - if (props.pvStatus === "Inactive" || props.recordType === "waveform") { + if (props.pvStatus === "Inactive" || (import.meta.env.REACT_APP_PVWS_ALLOW_WAVEFORMS !== "true" && props.recordType === "waveform")) { setEnabled(false); } }, [props.pvStatus, props.recordType]) @@ -54,7 +49,7 @@ function ValueCheckbox(props) { Monitor
{props.pvName}}> diff --git a/src/components/pv/KeyValuePair.jsx b/src/components/pv/KeyValuePair.jsx index f8538bf..2810046 100644 --- a/src/components/pv/KeyValuePair.jsx +++ b/src/components/pv/KeyValuePair.jsx @@ -5,6 +5,8 @@ import PropTypes from 'prop-types'; const propTypes = { title: PropTypes.string, + url: PropTypes.string, + textColor: PropTypes.string, value: PropTypes.any, } @@ -27,6 +29,8 @@ function KeyValuePair(props) { )) ) : props.url ? ( {props.value} + ) : Array.isArray(props.value) ? ( + [{props.value.join(', ')}] ) : ( {props.value} )} diff --git a/src/components/pv/PV.jsx b/src/components/pv/PV.jsx index ddeb419..1ad5fe9 100644 --- a/src/components/pv/PV.jsx +++ b/src/components/pv/PV.jsx @@ -175,6 +175,9 @@ function PV(props) { { import.meta.env.REACT_APP_USE_PVWS === "true" ? + import.meta.env.REACT_APP_PVWS_ALLOW_WAVEFORMS === "true" ? + } disabled={pvData?.pvStatus?.value === "Inactive"} label="Enable Live PV Monitoring" /> + : } disabled={pvData?.pvStatus?.value === "Inactive" || pvData?.recordType?.value === "waveform"} label="Enable Live PV Monitoring" /> : null } diff --git a/src/components/pv/valuetable/ValueTable.jsx b/src/components/pv/valuetable/ValueTable.jsx index ddf9974..8cb1463 100644 --- a/src/components/pv/valuetable/ValueTable.jsx +++ b/src/components/pv/valuetable/ValueTable.jsx @@ -1,6 +1,7 @@ import React, { Fragment, useState, useEffect } from "react"; import { Typography } from "@mui/material"; import useWebSocket from 'react-use-websocket'; +import { toByteArray } from 'base64-js'; import api from "../../../api"; import colors from "../../../colors"; import PropTypes from "prop-types"; @@ -60,7 +61,7 @@ function ValueTable(props) { handleSeverity("warning"); handleOpenErrorAlert(true); } - else if (props.pvData.recordType?.value === "waveform") { + else if (import.meta.env.REACT_APP_PVWS_ALLOW_WAVEFORMS !== "true" && props.pvData.recordType?.value === "waveform") { handleErrorMessage("Can't show live PV values - Waveform record type not supported"); handleSeverity("warning"); handleOpenErrorAlert(true); @@ -175,6 +176,29 @@ function ValueTable(props) { } } + // see "handleMessage" in https://github.com/ornl-epics/pvws/blob/main/src/main/webapp/js/pvws.js + else if ("b64dbl" in message) { + if (!props.snapshot) { + let bytes = toByteArray(message.b64dbl); + let value_array = new Float64Array(bytes.buffer); + value_array = Array.prototype.slice.call(value_array); + setPVValue(value_array); + } + if (snapshot) { + setSnapshot(false); + } + } + else if ("b64int" in message) { + if (!props.snapshot) { + let bytes = toByteArray(message.b64int); + let value_array = new Int32Array(bytes.buffer); + value_array = Array.prototype.slice.call(value_array); + setPVValue(value_array); + } + if (snapshot) { + setSnapshot(false); + } + } else if ("value" in message) { if (!props.snapshot) { // if precision was explicitly set (and badly assume 0 is not explicit) then use that