Skip to content

Commit 3f8939a

Browse files
authored
Merge pull request #129 from DiamondLightSource/128-add-support-for-multiple-pvs-per-widget
128 add support for multiple PVs per widget
2 parents d65f442 + 71c6568 commit 3f8939a

39 files changed

+900
-325
lines changed

src/redux/csState.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@ export interface PvState {
2727
readonly: boolean;
2828
}
2929

30+
export type PvDatum = PvState & {
31+
effectivePvName: string;
32+
};
33+
34+
export type PvDataCollection = {
35+
pvData: PvDatum[];
36+
};
37+
3038
export interface FullPvState extends PvState {
3139
initializingPvName: string;
3240
}

src/types/props.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export type GenericProp =
1515
| boolean
1616
| number
1717
| PV
18+
| { pvName: PV }[]
1819
| Color
1920
| Font
2021
| Border

src/ui/hooks/useConnection.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React from "react";
22
import { useSubscription } from "./useSubscription";
33
import { useSelector } from "react-redux";
4-
import { CsState } from "../../redux/csState";
4+
import { CsState, PvDataCollection } from "../../redux/csState";
55
import { pvStateSelector, PvArrayResults, pvStateComparator } from "./utils";
66
import { SubscriptionType } from "../../connection/plugin";
77
import { DType } from "../../types/dtypes";
@@ -27,6 +27,7 @@ export function useConnection(
2727
let readonly = false;
2828
let value = undefined;
2929
let effectivePvName = "undefined";
30+
3031
if (pvName !== undefined) {
3132
const [pvState, effPvName] = pvResults[pvName];
3233
effectivePvName = effPvName;
@@ -36,5 +37,35 @@ export function useConnection(
3637
value = pvState.value;
3738
}
3839
}
40+
3941
return [effectivePvName, connected, readonly, value];
4042
}
43+
44+
export const useConnectionMultiplePv = (
45+
id: string,
46+
pvNames: string[],
47+
type?: SubscriptionType
48+
): PvDataCollection => {
49+
const pvNameArray = pvNames.filter(x => !!x);
50+
const typeArray = !type ? [] : [type];
51+
52+
useSubscription(id, pvNameArray, typeArray);
53+
54+
const pvResults = useSelector(
55+
(state: CsState): PvArrayResults => pvStateSelector(pvNameArray, state),
56+
pvStateComparator
57+
);
58+
59+
return {
60+
pvData: pvNameArray.map(pvName => {
61+
const [pvState, effPvName] = pvResults[pvName];
62+
63+
return {
64+
effectivePvName: effPvName,
65+
connected: pvState?.connected ?? false,
66+
readonly: pvState?.readonly ?? false,
67+
value: pvState?.value
68+
};
69+
})
70+
};
71+
};

src/ui/widgets/BoolButton/boolButton.test.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { DType } from "../../../types/dtypes";
55
import { Color } from "../../../types";
66
import { ThemeProvider } from "@mui/material";
77
import { phoebusTheme } from "../../../phoebusTheme";
8+
import { PvDatum } from "../../../redux/csState";
89

910
const BoolButtonRenderer = (boolButtonProps: any): JSX.Element => {
1011
return (
@@ -14,8 +15,15 @@ const BoolButtonRenderer = (boolButtonProps: any): JSX.Element => {
1415
);
1516
};
1617

18+
const TEST_PVDATUM = {
19+
effectivePvName: "TEST:PV",
20+
connected: true,
21+
readonly: true,
22+
value: new DType({ doubleValue: 1 })
23+
} as Partial<PvDatum> as PvDatum;
24+
1725
const TEST_PROPS = {
18-
value: new DType({ doubleValue: 1 }),
26+
pvData: [TEST_PVDATUM],
1927
width: 45,
2028
height: 20,
2129
onColor: Color.fromRgba(0, 235, 10),
@@ -116,7 +124,7 @@ describe("<BoolButton />", (): void => {
116124
test("on click change led colour if no text ", async (): Promise<void> => {
117125
const boolButtonProps = {
118126
...TEST_PROPS,
119-
value: new DType({ doubleValue: 0 }),
127+
pvData: [{ ...TEST_PVDATUM, value: new DType({ doubleValue: 0 }) }],
120128
onLabel: "",
121129
offLabel: "",
122130
showLed: true

src/ui/widgets/BoolButton/boolButton.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { writePv } from "../../hooks/useSubscription";
1717
import { DType } from "../../../types/dtypes";
1818
import { WIDGET_DEFAULT_SIZES } from "../EmbeddedDisplay/bobParser";
1919
import { Button as MuiButton, styled, useTheme } from "@mui/material";
20+
import { getPvValueAndName } from "../utils";
2021

2122
// For HTML button, these are the sizes of the buffer on
2223
// width and height. Must take into account when allocating
@@ -88,8 +89,7 @@ export const BoolButtonComponent = (
8889
height = WIDGET_DEFAULT_SIZES["bool_button"][1],
8990
foregroundColor = theme.palette.primary.contrastText,
9091
backgroundColor = theme.palette.primary.main,
91-
pvName,
92-
value,
92+
pvData,
9393
onState = 1,
9494
offState = 0,
9595
onColor = Color.fromRgba(0, 255, 0),
@@ -100,6 +100,7 @@ export const BoolButtonComponent = (
100100
labelsFromPv = false,
101101
enabled = true
102102
} = props;
103+
const { value, effectivePvName: pvName } = getPvValueAndName(pvData);
103104

104105
const font = props.font?.css() ?? theme.typography;
105106

src/ui/widgets/ByteMonitor/byteMonitor.test.tsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
import { Color } from "../../../types";
88
import renderer, { ReactTestRendererJSON } from "react-test-renderer";
99
import { DType } from "../../../types/dtypes";
10+
import { PvDatum } from "../../../redux/csState";
1011

1112
const ByteMonitorRenderer = (byteMonitorProps: any): ReactTestRendererJSON => {
1213
return renderer
@@ -17,7 +18,14 @@ const ByteMonitorRenderer = (byteMonitorProps: any): ReactTestRendererJSON => {
1718
describe("<ByteMonitorComponent />", (): void => {
1819
test("default properties are added to bytemonitor component", (): void => {
1920
const byteMonitorProps = {
20-
value: new DType({ doubleValue: 15 }),
21+
pvData: [
22+
{
23+
effectivePvName: "TEST:PV",
24+
connected: true,
25+
readonly: true,
26+
value: new DType({ doubleValue: 15 })
27+
} as Partial<PvDatum> as PvDatum
28+
],
2129
height: 40,
2230
width: 40
2331
};
@@ -33,7 +41,14 @@ describe("<ByteMonitorComponent />", (): void => {
3341
});
3442
test("overwrite bytemonitor default values", (): void => {
3543
const byteMonitorProps = {
36-
value: new DType({ doubleValue: 2 }),
44+
pvData: [
45+
{
46+
effectivePvName: "TEST:PV",
47+
connected: true,
48+
readonly: true,
49+
value: new DType({ doubleValue: 2 })
50+
} as Partial<PvDatum> as PvDatum
51+
],
3752
height: 50,
3853
width: 50,
3954
startBit: 8,

src/ui/widgets/ByteMonitor/byteMonitor.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { registerWidget } from "../register";
1111
import classes from "./byteMonitor.module.css";
1212
import { Color } from "../../../types/color";
1313
import { WIDGET_DEFAULT_SIZES } from "../EmbeddedDisplay/bobParser";
14+
import { getPvValueAndName } from "../utils";
1415

1516
export const ByteMonitorProps = {
1617
width: IntPropOpt,
@@ -41,7 +42,7 @@ export const ByteMonitorComponent = (
4142
props: ByteMonitorComponentProps
4243
): JSX.Element => {
4344
const {
44-
value,
45+
pvData,
4546
startBit = 0,
4647
horizontal = true,
4748
bitReverse = false,
@@ -54,6 +55,7 @@ export const ByteMonitorComponent = (
5455
width = WIDGET_DEFAULT_SIZES["byte_monitor"][0],
5556
height = WIDGET_DEFAULT_SIZES["byte_monitor"][1]
5657
} = props;
58+
const { value } = getPvValueAndName(pvData);
5759

5860
// Check for a value, otherwise set to 0
5961
const doubleValue = value?.getDoubleValue() || 0;

src/ui/widgets/Checkbox/checkbox.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
} from "@mui/material";
1818
import { writePv } from "../../hooks/useSubscription";
1919
import { DType } from "../../../types";
20+
import { getPvValueAndName } from "../utils";
2021

2122
export const CheckboxProps = {
2223
label: StringPropOpt,
@@ -65,7 +66,8 @@ export const CheckboxComponent = (
6566
props: CheckboxComponentProps
6667
): JSX.Element => {
6768
const theme = useTheme();
68-
const { enabled = true, label = "Label", value, pvName } = props;
69+
const { enabled = true, label = "Label", pvData } = props;
70+
const { value, effectivePvName: pvName } = getPvValueAndName(pvData);
6971
const checked = Boolean(value?.getDoubleValue());
7072

7173
const handleChange = (event: any): void => {

src/ui/widgets/ChoiceButton/choiceButton.test.tsx

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { DDisplay, DType } from "../../../types/dtypes";
55
import { Color, Font } from "../../../types";
66
import { ThemeProvider } from "@mui/material";
77
import { phoebusTheme } from "../../../phoebusTheme";
8+
import { PvDatum } from "../../../redux/csState";
89

910
const ChoiceButtonRenderer = (choiceButtonProps: any): JSX.Element => {
1011
return (
@@ -17,7 +18,14 @@ const ChoiceButtonRenderer = (choiceButtonProps: any): JSX.Element => {
1718
describe("<ChoiceButton />", (): void => {
1819
test("it renders ChoiceButton with default props", (): void => {
1920
const choiceButtonProps = {
20-
value: null
21+
pvData: [
22+
{
23+
effectivePvName: "TEST:PV",
24+
connected: true,
25+
readonly: true,
26+
value: undefined
27+
} as Partial<PvDatum> as PvDatum
28+
]
2129
};
2230
const { getAllByRole } = render(ChoiceButtonRenderer(choiceButtonProps));
2331
const buttons = getAllByRole("button") as Array<HTMLButtonElement>;
@@ -37,7 +45,14 @@ describe("<ChoiceButton />", (): void => {
3745

3846
test("pass props to widget", (): void => {
3947
const choiceButtonProps = {
40-
value: new DType({ doubleValue: 0 }),
48+
pvData: [
49+
{
50+
effectivePvName: "TEST:PV",
51+
connected: true,
52+
readonly: true,
53+
value: new DType({ doubleValue: 0 })
54+
} as Partial<PvDatum> as PvDatum
55+
],
4156
width: 60,
4257
height: 140,
4358
font: new Font(12),
@@ -68,12 +83,19 @@ describe("<ChoiceButton />", (): void => {
6883

6984
test("pass props to widget, using itemsFromPv", (): void => {
7085
const choiceButtonProps = {
71-
value: new DType(
72-
{ doubleValue: 0 },
73-
undefined,
74-
undefined,
75-
new DDisplay({ choices: ["hi", "Hello"] })
76-
),
86+
pvData: [
87+
{
88+
effectivePvName: "TEST:PV",
89+
connected: true,
90+
readonly: true,
91+
value: new DType(
92+
{ doubleValue: 0 },
93+
undefined,
94+
undefined,
95+
new DDisplay({ choices: ["hi", "Hello"] })
96+
)
97+
} as Partial<PvDatum> as PvDatum
98+
],
7799
items: ["one", "two", "three"],
78100
horizontal: false,
79101
itemsFromPv: true,
@@ -90,7 +112,7 @@ describe("<ChoiceButton />", (): void => {
90112

91113
test("selecting a button", (): void => {
92114
const choiceButtonProps = {
93-
value: null
115+
pvData: []
94116
};
95117
const { getAllByRole } = render(ChoiceButtonRenderer(choiceButtonProps));
96118
const buttons = getAllByRole("button") as Array<HTMLButtonElement>;

src/ui/widgets/ChoiceButton/choiceButton.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
useTheme
2222
} from "@mui/material";
2323
import { Color } from "../../../types";
24+
import { getPvValueAndName } from "../utils";
2425

2526
const ChoiceButtonProps = {
2627
pvName: StringPropOpt,
@@ -71,16 +72,17 @@ export const ChoiceButtonComponent = (
7172
const {
7273
width = 100,
7374
height = 43,
74-
value = null,
75+
pvData,
7576
enabled = true,
7677
itemsFromPv = true,
77-
pvName,
7878
items = ["Item 1", "Item 2"],
7979
horizontal = true,
8080
foregroundColor = theme.palette.primary.contrastText,
8181
backgroundColor = theme.palette.primary.main,
8282
selectedColor = Color.fromRgba(200, 200, 200)
8383
} = props;
84+
const { value, effectivePvName: pvName } = getPvValueAndName(pvData);
85+
8486
const font = props.font?.css() ?? theme.typography;
8587
const [selected, setSelected] = useState(value?.getDoubleValue());
8688

0 commit comments

Comments
 (0)