Skip to content

Commit b726003

Browse files
feat: describe SampledValueControl
1 parent 45cd21d commit b726003

File tree

2 files changed

+221
-0
lines changed

2 files changed

+221
-0
lines changed

describe/SampledValueControl.spec.ts

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import { expect } from "chai";
2+
3+
import { describeSampledValueControl } from "./SampledValueControl.js";
4+
5+
const scl = new DOMParser().parseFromString(
6+
`<SCL xmlns="http://www.iec.ch/61850/2003/SCL" >
7+
<IED name="IED1">
8+
<AccessPoint name="AP1">
9+
<Server>
10+
<LDevice inst="lDevice">
11+
<LN0 lnClass="LLN0" inst="" lnType="LLN0">
12+
<DataSet name="baseDataSet" >
13+
<FCDA iedName="IED1" ldInst="lDevice" lnClass="XCBR" lnInst="1" doName="Pos" daName="stVal" fc="ST" />
14+
<FCDA iedName="IED1" ldInst="lDevice" prefix="" lnClass="XCBR" lnInst="1" doName="Pos" daName="q" fc="ST" />
15+
<FCDA iedName="IED1" ldInst="lDevice" lnClass="LLN0" doName="Beh" daName="stVal" fc="ST" />
16+
<FCDA iedName="IED1" ldInst="lDevice" prefix="" lnClass="LLN0" lnInst="" doName="Beh" fc="ST" />
17+
</DataSet>
18+
<DataSet name="equalDataSet" >
19+
<FCDA iedName="IED1" ldInst="lDevice" prefix="" lnClass="XCBR" lnInst="1" doName="Pos" daName="stVal" fc="ST" />
20+
<FCDA iedName="IED1" ldInst="lDevice" lnClass="XCBR" lnInst="1" doName="Pos" daName="q" fc="ST" />
21+
<FCDA iedName="IED1" ldInst="lDevice" prefix="" lnClass="LLN0" lnInst="" doName="Beh" daName="stVal" fc="ST" />
22+
<FCDA iedName="IED1" ldInst="lDevice" lnClass="LLN0" doName="Beh" fc="ST" />
23+
</DataSet>
24+
<DataSet name="diffDataSet" >
25+
<Private type="private" />
26+
<FCDA iedName="IED1" ldInst="lDevice" prefix="" lnClass="XCBR" lnInst="1" doName="Pos" daName="stVal" fc="ST" />
27+
<FCDA iedName="IED1" ldInst="lDevice" lnClass="XCBR" lnInst="1" doName="Pos" daName="q" fc="ST" />
28+
<FCDA iedName="IED1" ldInst="lDevice" prefix="" lnClass="LLN0" lnInst="" doName="Beh" daName="stVal" fc="ST" />
29+
<FCDA iedName="IED1" ldInst="lDevice" lnClass="LLN0" doName="Beh" fc="ST" />
30+
</DataSet>
31+
<DataSet name="invalidDataSet" >
32+
<FCDA ldInst="lDevice" prefix="" lnClass="XCBR" lnInst="1" doName="Pos" daName="stVal" fc="ST" />
33+
</DataSet>
34+
<SampledValueControl name="smvControl1" datSet="baseDataSet" confRev="1" smvID="smvID" multicast="true" securityEnable="Signature" smpRate="80" nofASDU="1" smpMod="SmpPerPeriod" >
35+
<SmvOpts refreshTime="false" sampleSynchronized="true" sampleRate="false" dataSet="false" security="false" timestamp="false" synchSourceId="false" />
36+
<IEDName apRef="AP1" ldInst="ldInst" prefix="CB" lnClass="CSWI" lnInst="1">IED3</IEDName>
37+
<IEDName >IED1</IEDName>
38+
<IEDName apRef="AP1" ldInst="ldInst" lnClass="LLN0">IED2</IEDName>
39+
<IEDName >IED1</IEDName>
40+
<Protocol mustUnderstand="true">R-GOOSE</Protocol>
41+
</SampledValueControl>
42+
<SampledValueControl name="smvControl2" datSet="equalDataSet" confRev="1" smvID="smvID" securityEnable="Signature" smpRate="80" nofASDU="1" >
43+
<SmvOpts />
44+
<IEDName >IED1</IEDName>
45+
<IEDName apRef="AP1" ldInst="ldInst" prefix="" lnClass="LLN0" lnInst="" >IED2</IEDName>
46+
<IEDName apRef="AP1" ldInst="ldInst" prefix="CB" lnClass="CSWI" lnInst="1" >IED3</IEDName>
47+
<IEDName >IED1</IEDName>
48+
<Protocol mustUnderstand="true">R-GOOSE</Protocol>
49+
</SampledValueControl>
50+
<SampledValueControl name="smvControl5" datSet="diffDataSet" smpRate="80" nofASDU="1" multicast="false" >
51+
<SmvOpts refreshTime="true" sampleSynchronized="false" sampleRate="true" dataSet="true" security="true" timestamp="true" synchSourceId="true" />
52+
</SampledValueControl>
53+
<SampledValueControl name="smvControl3" datSet="invalidDataSet" />
54+
<SampledValueControl name="smvControl4" datSet="invalidReference" />
55+
<SampledValueControl name="missingSmpRate" datSet="diffDataSet" nofASDU="1" >
56+
<SmvOpts />
57+
</SampledValueControl>
58+
<SampledValueControl name="missingNofASDU" datSet="diffDataSet" smpRate="80" >
59+
<SmvOpts />
60+
</SampledValueControl>
61+
<SampledValueControl name="missingSmpOpts" datSet="diffDataSet" smpRate="80" nofASDU="1" />
62+
</LN0>
63+
</LDevice>
64+
</Server>
65+
</AccessPoint>
66+
</IED>
67+
</SCL>`,
68+
"application/xml"
69+
);
70+
71+
const baseSampledValueControl = scl.querySelector(`*[datSet="baseDataSet"]`)!;
72+
const equalSampledValueControl = scl.querySelector('*[datSet="equalDataSet"]')!;
73+
const diffSampledValueControl = scl.querySelector('*[datSet="diffDataSet"]')!;
74+
const invalidDataSet = scl.querySelector('*[datSet="invalidDataSet"]')!;
75+
const invalidReference = scl.querySelector('*[datSet="invalidReference"]')!;
76+
const missingSmpRate = scl.querySelector(
77+
'SampledValueControl[name="missingSmpRate"]'
78+
)!;
79+
const missingNofASDU = scl.querySelector(
80+
'SampledValueControl[name="missingNofASDU"]'
81+
)!;
82+
const missingSmpOpts = scl.querySelector(
83+
'SampledValueControl[name="missingSmpOpts"]'
84+
)!;
85+
86+
describe("Description for SCL schema type SampledValueControl", () => {
87+
it("returns undefined when referenced DataSet is undefined", () =>
88+
expect(describeSampledValueControl(invalidDataSet)).to.be.undefined);
89+
90+
it("returns undefined with missing referenced DataSet", () =>
91+
expect(describeSampledValueControl(invalidReference)).to.be.undefined);
92+
93+
it("returns undefined with missing smpRate attribute", () =>
94+
expect(describeSampledValueControl(missingSmpRate)).to.be.undefined);
95+
96+
it("returns undefined with missing nofASDU attribute", () =>
97+
expect(describeSampledValueControl(missingNofASDU)).to.be.undefined);
98+
99+
it("returns undefined with missing SmpOpts child", () =>
100+
expect(describeSampledValueControl(missingSmpOpts)).to.be.undefined);
101+
102+
it("returns same description with semantically equal SampledValueControl's", () =>
103+
expect(
104+
JSON.stringify(describeSampledValueControl(baseSampledValueControl))
105+
).to.equal(
106+
JSON.stringify(describeSampledValueControl(equalSampledValueControl))
107+
));
108+
109+
it("returns different description with unequal SampledValueControl elements", () => {
110+
console.log(describeSampledValueControl(diffSampledValueControl));
111+
expect(
112+
JSON.stringify(describeSampledValueControl(baseSampledValueControl))
113+
).to.not.equal(
114+
JSON.stringify(describeSampledValueControl(diffSampledValueControl))
115+
);
116+
});
117+
});

describe/SampledValueControl.ts

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import {
2+
ControlWithIEDNameDescription,
3+
describeControlWithIEDName,
4+
} from "./ControlWithIEDName.js";
5+
6+
type SmvOpts = {
7+
/** SmvOpts attribute refreshTime defaulted to false */
8+
refreshTime: boolean;
9+
/** SmvOpts attribute sampleSynchronized defaulted to true */
10+
sampleSynchronized: boolean;
11+
/** SmvOpts attribute sampleRate defaulted to false */
12+
sampleRate: boolean;
13+
/** SmvOpts attribute dataSet defaulted to false */
14+
dataSet: boolean;
15+
/** SmvOpts attribute security defaulted to false */
16+
security: boolean;
17+
/** SmvOpts attribute timestamp defaulted to false */
18+
timestamp: boolean;
19+
/** SmvOpts attribute synchSourceId defaulted to false */
20+
synchSourceId: boolean;
21+
};
22+
23+
function smvOpts(element: Element): SmvOpts | undefined {
24+
const smvOpts = element.querySelector(":scope > SmvOpts");
25+
if (!smvOpts) return;
26+
27+
const some: SmvOpts = {
28+
refreshTime: smvOpts.getAttribute("refreshTime") === "true" ? true : false,
29+
sampleSynchronized:
30+
smvOpts.getAttribute("sampleSynchronized") === "false" ? false : true,
31+
sampleRate: smvOpts.getAttribute("sampleRate") === "true" ? true : false,
32+
dataSet: smvOpts.getAttribute("dataSet") === "true" ? true : false,
33+
security: smvOpts.getAttribute("security") === "true" ? true : false,
34+
timestamp: smvOpts.getAttribute("timestamp") === "true" ? true : false,
35+
synchSourceId:
36+
smvOpts.getAttribute("synchSourceId") === "true" ? true : false,
37+
};
38+
39+
return some;
40+
}
41+
42+
export interface SampledValueControlDescription
43+
extends ControlWithIEDNameDescription {
44+
/** SampledValueControl attribute multicast defaulted to true */
45+
multicast: boolean;
46+
/** SampledValueControl attribute smvID */
47+
smvID: string;
48+
/** SampledValueControl attribute smpRate*/
49+
smpRate: number;
50+
/** SampledValueControl attribute nofASDU */
51+
nofASDU: number;
52+
/** SampledValueControl attribute smpMod defaulted to "SmpPerPeriod" */
53+
smpMod: "SmpPerPeriod" | "SmpPerSec" | "SecPerSmp";
54+
/** SampleValueControl attribute securityEnable defaulted to "None" */
55+
securityEnable: "None" | "Signature" | "SignatureAndEncryption";
56+
/** SampledValueControl child Protocol */
57+
protocol?: { mustUnderstand: true; val: "R-SV" };
58+
/** SampledValueControl child SmvOpts */
59+
SmvOpts: SmvOpts;
60+
}
61+
62+
export function describeSampledValueControl(
63+
element: Element
64+
): SampledValueControlDescription | undefined {
65+
const controlWithTriggerOptDesc = describeControlWithIEDName(element);
66+
if (!controlWithTriggerOptDesc) return;
67+
68+
const smpRate = element.getAttribute("smpRate");
69+
if (!smpRate || isNaN(parseInt(smpRate, 10))) return;
70+
71+
const nofASDU = element.getAttribute("nofASDU");
72+
if (!nofASDU || isNaN(parseInt(nofASDU, 10))) return;
73+
74+
const SmvOpts = smvOpts(element);
75+
if (!SmvOpts) return;
76+
77+
const gseControlDescription: SampledValueControlDescription = {
78+
...controlWithTriggerOptDesc,
79+
multicast: element.getAttribute("multicast") === "false" ? false : true,
80+
smvID: element.getAttribute("smvID") ?? "",
81+
smpRate: parseInt(smpRate, 10),
82+
nofASDU: parseInt(nofASDU, 10),
83+
smpMod: element.getAttribute("smpMod")
84+
? (element.getAttribute("smpMod") as
85+
| "SecPerSmp"
86+
| "SmpPerSec"
87+
| "SmpPerPeriod")
88+
: "SmpPerPeriod",
89+
securityEnable: element.getAttribute("securityEnable")
90+
? (element.getAttribute("securityEnable") as
91+
| "Signature"
92+
| "SignatureAndEncryption")
93+
: "None",
94+
SmvOpts: SmvOpts,
95+
};
96+
97+
const protocol = Array.from(element.children).find(
98+
(child) => child.tagName === "Protocol"
99+
);
100+
if (protocol)
101+
gseControlDescription.protocol = { mustUnderstand: true, val: "R-SV" };
102+
103+
return gseControlDescription;
104+
}

0 commit comments

Comments
 (0)