Skip to content

Commit 5fb092a

Browse files
committed
move more logic out of instrumentdata, add tests for logic moved
1 parent 15c579d commit 5fb092a

File tree

7 files changed

+370
-235
lines changed

7 files changed

+370
-235
lines changed

app/components/Instrument.test.ts

Lines changed: 163 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,24 @@
1-
import { findPVInDashboard } from "@/app/components/Instrument";
2-
import { DashboardArr, IfcPV } from "@/app/types";
1+
import {
2+
CSSB,
3+
findPVInDashboard,
4+
findPVInGroups,
5+
getGroupsWithBlocksFromConfigOutput,
6+
RC_ENABLE,
7+
RC_INRANGE,
8+
SP_RBV, storePrecision,
9+
subscribeToBlockPVs,
10+
toPrecision,
11+
yesToBoolean,
12+
} from "@/app/components/Instrument";
13+
import {
14+
ConfigOutput,
15+
DashboardArr,
16+
IfcBlock,
17+
IfcGroup,
18+
IfcPV, IfcPVWSMessage,
19+
IfcPVWSRequest,
20+
PVWSRequestType,
21+
} from "@/app/types";
322

423
test("findPVinDashboard finds a pv in the dashboard and returns it", () => {
524
const prefix = "UNITTESTING";
@@ -69,3 +88,145 @@ test("findPVinDashboard does not find a PV in the dashboard and returns undefine
6988
const result = findPVInDashboard(dashboard, pvToTestFor.pvaddress);
7089
expect(result).toBe(undefined);
7190
});
91+
92+
test("findPVInGroups returns a block when it finds one", () => {
93+
const blockName = "blockName";
94+
const prefix = "IN:INSTRUMENT";
95+
const groups: Array<IfcGroup> = [
96+
{
97+
name: "aGroup",
98+
blocks: [
99+
{
100+
human_readable_name: blockName,
101+
pvaddress: "some:underlying:pv:name",
102+
},
103+
],
104+
},
105+
];
106+
107+
findPVInGroups(groups, prefix, prefix + CSSB + blockName);
108+
});
109+
110+
test("subscribeToBlockPVs subscribes to all run control PVs", () => {
111+
const mockSendJsonMessage = jest.fn();
112+
const aBlock = "INST:CS:SB:SomeBlock";
113+
subscribeToBlockPVs(mockSendJsonMessage, aBlock);
114+
expect(mockSendJsonMessage.mock.calls.length).toBe(1);
115+
const expectedCall: IfcPVWSRequest = {
116+
type: PVWSRequestType.subscribe,
117+
pvs: [aBlock, aBlock + RC_ENABLE, aBlock + RC_INRANGE, aBlock + SP_RBV],
118+
};
119+
expect(JSON.stringify(mockSendJsonMessage.mock.calls[0][0])).toBe(
120+
JSON.stringify(expectedCall),
121+
);
122+
});
123+
124+
test("toPrecision does nothing to string value ", () => {
125+
const expectedValue = "untouched";
126+
const aBlock: IfcBlock = {
127+
pvaddress: "SOME:BLOCK:STR",
128+
value: expectedValue,
129+
};
130+
expect(toPrecision(aBlock, expectedValue)).toBe(expectedValue);
131+
});
132+
133+
test("toPrecision does nothing to block without pv ", () => {
134+
const expectedValue = 0.00123456;
135+
const aBlock: IfcBlock = {
136+
pvaddress: "SOME:BLOCK:STR",
137+
value: expectedValue,
138+
};
139+
expect(toPrecision(aBlock, expectedValue)).toBe(expectedValue);
140+
});
141+
142+
test("toPrecision truncates block if it has precision", () => {
143+
const originalValue = 0.00123456;
144+
const precision = 3;
145+
const aBlock: IfcBlock = {
146+
pvaddress: "SOME:BLOCK:STR",
147+
value: originalValue,
148+
precision: precision,
149+
};
150+
expect(toPrecision(aBlock, originalValue)).toBe(
151+
originalValue.toFixed(precision),
152+
);
153+
});
154+
155+
test("yesToBoolean works with YES as value", () => {
156+
expect(yesToBoolean("YES")).toBe(true);
157+
});
158+
159+
test("yesToBoolean works with NO as value", () => {
160+
expect(yesToBoolean("NO")).toBe(false);
161+
});
162+
163+
test("getGroupsWithBlocksFromConfigOutput gets blocks from blockserver groups", () => {
164+
const blockNameToTest = "aBlock";
165+
const groupNameToTest = "aGroup";
166+
const prefix = "TESTING";
167+
const mockSendJsonMessage = jest.fn();
168+
169+
const configOutput: ConfigOutput = {
170+
groups: [{ blocks: [blockNameToTest], name: groupNameToTest }],
171+
blocks: [
172+
{
173+
name: blockNameToTest,
174+
component: "",
175+
local: true,
176+
pv: "A:BLOCK",
177+
set_block: false,
178+
highlimit: 0,
179+
lowlimit: 0,
180+
log_rate: 0,
181+
log_deadband: 0,
182+
log_periodic: true,
183+
runcontrol: true,
184+
suspend_on_invalid: true,
185+
visible: true,
186+
},
187+
],
188+
components: [],
189+
description: "",
190+
name: "",
191+
configuresBlockGWAndArchiver: false,
192+
iocs: [],
193+
isDynamic: false,
194+
isProtected: false,
195+
synoptic: "",
196+
component_iocs: [],
197+
history: [],
198+
};
199+
const groups = getGroupsWithBlocksFromConfigOutput(
200+
configOutput,
201+
mockSendJsonMessage,
202+
prefix,
203+
);
204+
expect(groups[0].name).toBe(groupNameToTest);
205+
expect(groups[0].blocks[0].human_readable_name).toBe(blockNameToTest);
206+
});
207+
208+
test("subscribeToBlockPVs subscribes to blocks, their run control and their SP:RBV PVs", () => {
209+
const mockSendJsonMessage = jest.fn();
210+
const blockAddress = "IN:INST:CS:SB:Block1";
211+
subscribeToBlockPVs(mockSendJsonMessage, blockAddress);
212+
expect(mockSendJsonMessage).toBeCalledTimes(1);
213+
const call: Array<string> = mockSendJsonMessage.mock.calls[0][0].pvs;
214+
expect(call).toEqual(
215+
expect.arrayContaining([
216+
blockAddress,
217+
blockAddress + RC_ENABLE,
218+
blockAddress + RC_INRANGE,
219+
blockAddress + SP_RBV,
220+
]),
221+
);
222+
});
223+
224+
test("storePrecision adds precision to a block if it is the first update", () => {
225+
const precision = 3;
226+
const message: IfcPVWSMessage = {type:"update", pv:"",precision:precision}
227+
let blockWithoutPrecision: IfcBlock = {pvaddress:""}
228+
storePrecision(message, blockWithoutPrecision)
229+
expect(blockWithoutPrecision.precision).toEqual(precision)
230+
})
231+
232+

app/components/Instrument.ts

Lines changed: 98 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
1-
import { DashboardArr, IfcBlock, IfcGroup, IfcPV } from "@/app/types";
2-
import { CSSB } from "@/app/components/InstrumentData";
1+
import {
2+
ConfigOutput,
3+
ConfigOutputBlock,
4+
DashboardArr,
5+
IfcBlock,
6+
IfcGroup,
7+
IfcPV,
8+
IfcPVWSMessage,
9+
IfcPVWSRequest,
10+
PVWSRequestType,
11+
} from "@/app/types";
12+
import {
13+
ExponentialOnThresholdFormat,
14+
findPVByAddress,
15+
} from "@/app/components/PVutils";
316

417
const DASHBOARD = "CS:DASHBOARD:TAB:";
518

@@ -186,7 +199,89 @@ export function findPVInDashboard(
186199
dashboard: DashboardArr,
187200
pvAddress: string,
188201
): undefined | IfcPV {
189-
return dashboard.flat(3).find((pv: IfcPV) => pv.pvaddress == pvAddress);
202+
return findPVByAddress(dashboard.flat(3), pvAddress);
203+
}
204+
205+
export const RC_ENABLE = ":RC:ENABLE";
206+
export const RC_INRANGE = ":RC:INRANGE";
207+
export const SP_RBV = ":SP:RBV";
208+
export const CSSB = "CS:SB:";
209+
210+
export function toPrecision(
211+
block: IfcPV,
212+
pvVal: number | string,
213+
): string | number {
214+
return block.precision
215+
? ExponentialOnThresholdFormat(pvVal, block.precision)
216+
: pvVal;
217+
}
218+
219+
export function storePrecision(updatedPV: IfcPVWSMessage, block: IfcBlock) {
220+
const prec = updatedPV.precision;
221+
if (prec != null && prec > 0 && !block.precision) {
222+
// this is likely the first update, and contains precision information which is not repeated on a normal value update - store this in the block for later truncation (see below)
223+
block.precision = prec;
224+
}
225+
}
226+
227+
export function yesToBoolean(pvVal: string | number) {
228+
return pvVal == "YES";
229+
}
230+
231+
export function subscribeToBlockPVs(
232+
sendJsonMessage: (a: IfcPVWSRequest) => void,
233+
block_address: string,
234+
) {
235+
/**
236+
* Subscribes to a block and its associated run control PVs
237+
*/
238+
sendJsonMessage({
239+
type: PVWSRequestType.subscribe,
240+
pvs: [
241+
block_address,
242+
block_address + RC_ENABLE,
243+
block_address + RC_INRANGE,
244+
block_address + SP_RBV,
245+
],
246+
});
247+
}
248+
249+
/**
250+
* Parse the blockserver's current configuration output
251+
* and create an array of groups which contain blocks.
252+
*/
253+
export function getGroupsWithBlocksFromConfigOutput(
254+
configOutput: ConfigOutput,
255+
sendJsonMessage: (a: IfcPVWSRequest) => void,
256+
prefix: string,
257+
): Array<IfcGroup> {
258+
const groups = configOutput.groups;
259+
let newGroups: Array<IfcGroup> = [];
260+
for (const group of groups) {
261+
const groupName = group.name;
262+
let blocks: Array<IfcBlock> = [];
263+
for (const block of group.blocks) {
264+
const newBlock = configOutput.blocks.find(
265+
(b: ConfigOutputBlock) => b.name === block,
266+
);
267+
if (newBlock) {
268+
blocks.push({
269+
pvaddress: newBlock.pv,
270+
human_readable_name: newBlock.name,
271+
low_rc: newBlock.lowlimit,
272+
high_rc: newBlock.highlimit,
273+
visible: newBlock.visible,
274+
});
275+
const fullyQualifiedBlockPVAddress = prefix + CSSB + newBlock.name;
276+
subscribeToBlockPVs(sendJsonMessage, fullyQualifiedBlockPVAddress);
277+
}
278+
}
279+
newGroups.push({
280+
name: groupName,
281+
blocks: blocks,
282+
});
283+
}
284+
return newGroups;
190285
}
191286

192287
export function findPVInGroups(

app/components/InstrumentData.test.ts

Lines changed: 0 additions & 105 deletions
This file was deleted.

0 commit comments

Comments
 (0)