Skip to content

Commit 4fb8ce3

Browse files
JakobVogelsangJakob Vogelsang
authored andcommitted
feat: describe ReportControl
1 parent 962c2c0 commit 4fb8ce3

File tree

10 files changed

+675
-2
lines changed

10 files changed

+675
-2
lines changed

describe/Control.spec.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { expect } from "chai";
2+
3+
import { describeControl } from "./Control.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+
<GSEControl name="gseControl" datSet="baseDataSet" />
35+
<SampledValueControl name="smvControl" datSet="equalDataSet" />
36+
<ReportControl name="reportControl" datSet="diffDataSet" />
37+
<ReportControl name="reportControl1" />
38+
<ReportControl name="reportControl2" datSet="invalidDataSet" />
39+
</LN0>
40+
</LDevice>
41+
</Server>
42+
</AccessPoint>
43+
</IED>
44+
</SCL>`,
45+
"application/xml"
46+
);
47+
48+
const baseDataSet = scl.querySelector(`*[datSet="baseDataSet"]`)!;
49+
const equalDataSet = scl.querySelector('*[datSet="equalDataSet"]')!;
50+
const diffDataSet = scl.querySelector('*[datSet="diffDataSet"]')!;
51+
52+
const missingDatSet = scl.querySelector("*:not([datSet])")!;
53+
const invalidDataSet = scl.querySelector('*[datSet="invalidDataSet"]')!;
54+
const parentLessControl = new DOMParser()
55+
.parseFromString(
56+
`<ReportControl name="reportControl" datSet="parentLessDataSet" >`,
57+
"application/xml"
58+
)
59+
.querySelector("ReportControl")!;
60+
61+
describe("Description for SCL schema type tControl", () => {
62+
it("returns dataSet free object with missing datSet element", () =>
63+
expect(describeControl(missingDatSet)).to.not.have.property("dataSet"));
64+
65+
it("returns undefined with missing DataSet", () =>
66+
expect(describeControl(parentLessControl)).to.be.undefined);
67+
68+
it("returns undefined when referenced DataSet is undefined", () =>
69+
expect(describeControl(invalidDataSet)).to.be.undefined);
70+
71+
it("returns same description with semantically equal Control's", () =>
72+
expect(JSON.stringify(describeControl(baseDataSet))).to.equal(
73+
JSON.stringify(describeControl(equalDataSet))
74+
));
75+
76+
it("returns different description with unequal Control elements", () =>
77+
expect(JSON.stringify(describeControl(baseDataSet))).to.not.equal(
78+
JSON.stringify(describeControl(diffDataSet))
79+
));
80+
});

describe/Control.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { DataSetDescription, describeDataSet } from "./DataSetDescription.js";
2+
import { NamingDescription, describeNaming } from "./Naming.js";
3+
4+
export interface ControlDescription extends NamingDescription {
5+
dataSet?: DataSetDescription;
6+
}
7+
8+
export function describeControl(
9+
element: Element
10+
): ControlDescription | undefined {
11+
const datSet = element.getAttribute("datSet");
12+
if (!datSet) return { ...describeNaming(element) };
13+
14+
const dataSet = Array.from(element.parentElement?.children ?? []).find(
15+
(child) =>
16+
child.tagName === "DataSet" &&
17+
child.getAttribute("name") &&
18+
child.getAttribute("name") === datSet
19+
);
20+
if (!dataSet) return;
21+
22+
const dataSetDescription = describeDataSet(dataSet);
23+
if (!dataSetDescription) return;
24+
25+
return { ...describeNaming(element), dataSet: dataSetDescription };
26+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { expect } from "chai";
2+
3+
import { describeControlWithTriggerOpt } from "./ControlWithTriggerOpt.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+
<ReportControl name="reportControl" datSet="baseDataSet" intgPd="0">
35+
<TrgOps dchg="false" qchg="false" dupd="false" period="false" gi="false" />
36+
</ReportControl>
37+
<LogControl name="logControl" datSet="equalDataSet" />
38+
<ReportControl name="reportControl1" datSet="diffDataSet" />
39+
<LogControl name="logControl1" datSet="invalidDataSet" />
40+
<LogControl name="logControl2" datSet="invalidReference" />
41+
</LN0>
42+
</LDevice>
43+
</Server>
44+
</AccessPoint>
45+
</IED>
46+
</SCL>`,
47+
"application/xml"
48+
);
49+
50+
const baseDataSet = scl.querySelector(`*[datSet="baseDataSet"]`)!;
51+
const equalDataSet = scl.querySelector('*[datSet="equalDataSet"]')!;
52+
const diffDataSet = scl.querySelector('*[datSet="diffDataSet"]')!;
53+
const invalidDataSet = scl.querySelector('*[datSet="invalidDataSet"]')!;
54+
const invalidReference = scl.querySelector('*[datSet="invalidReference"]')!;
55+
56+
describe("Description for SCL schema type tControlWithTriggerOpt", () => {
57+
it("returns undefined when referenced DataSet is undefined", () =>
58+
expect(describeControlWithTriggerOpt(invalidDataSet)).to.be.undefined);
59+
60+
it("returns undefined with missing referenced DataSet", () =>
61+
expect(describeControlWithTriggerOpt(invalidReference)).to.be.undefined);
62+
63+
it("returns same description with semantically equal Control's", () =>
64+
expect(JSON.stringify(describeControlWithTriggerOpt(baseDataSet))).to.equal(
65+
JSON.stringify(describeControlWithTriggerOpt(equalDataSet))
66+
));
67+
68+
it("returns different description with unequal Control elements", () =>
69+
expect(
70+
JSON.stringify(describeControlWithTriggerOpt(baseDataSet))
71+
).to.not.equal(JSON.stringify(describeControlWithTriggerOpt(diffDataSet))));
72+
});

describe/ControlWithTriggerOpt.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { ControlDescription, describeControl } from "./Control.js";
2+
3+
type TrgOps = {
4+
/** TrgOpts attribute dchg defaulting to false*/
5+
dchg: boolean;
6+
/** TrgOpts attribute qchg defaulting to false*/
7+
qchg: boolean;
8+
/** TrgOpts attribute dupt defaulting to false*/
9+
dupd: boolean;
10+
/** TrgOpts attribute period defaulting to false*/
11+
period: boolean;
12+
/** TrgOpts attribute gi defaulting to false*/
13+
gi: boolean;
14+
};
15+
16+
export interface ControlWithTriggerOptDescription extends ControlDescription {
17+
trgOps: TrgOps;
18+
/** ReportControl attribute intgPd defaulted to 0 */
19+
intgPd: number;
20+
}
21+
22+
function trgOps(element: Element): TrgOps {
23+
const trgOps = Array.from(element.children).find(
24+
(child) => child.tagName === "TrgOps"
25+
);
26+
if (!trgOps)
27+
return { dchg: false, qchg: false, dupd: false, period: false, gi: false };
28+
29+
const [dchg, qchg, dupd, period, gi] = [
30+
"dchg",
31+
"qchg",
32+
"dupd",
33+
"period",
34+
"gi",
35+
].map((attr) => (trgOps.getAttribute(attr) === "true" ? true : false));
36+
37+
return { dchg, qchg, dupd, period, gi };
38+
}
39+
40+
export function describeControlWithTriggerOpt(
41+
element: Element
42+
): ControlWithTriggerOptDescription | undefined {
43+
const controlDescription = describeControl(element);
44+
if (!controlDescription) return;
45+
46+
const controlWithTriggerOptDesc = {
47+
...controlDescription,
48+
trgOps: trgOps(element),
49+
intgPd: 0,
50+
};
51+
52+
const intgPd = element.getAttribute("intgPd");
53+
if (intgPd && !isNaN(parseInt(intgPd, 10)))
54+
controlWithTriggerOptDesc.intgPd = parseInt(intgPd, 10);
55+
56+
return controlWithTriggerOptDesc;
57+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { expect } from "chai";
2+
3+
import { describeDataSet } from "./DataSetDescription.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="invalidIedName" >
32+
<FCDA ldInst="lDevice" prefix="" lnClass="XCBR" lnInst="1" doName="Pos" daName="stVal" fc="ST" />
33+
</DataSet>
34+
<DataSet name="invalidLDevice" >
35+
<FCDA iedName="IED1" lnClass="XCBR" lnInst="1" doName="Pos" daName="stVal" fc="ST" />
36+
</DataSet>
37+
<DataSet name="invalidLnClass" >
38+
<FCDA iedName="IED1" ldInst="lDevice" lnInst="1" doName="Pos" daName="stVal" fc="ST" />
39+
</DataSet>
40+
<DataSet name="invalidDoName" >
41+
<FCDA iedName="IED1" ldInst="lDevice" lnClass="XCBR" lnInst="1" daName="stVal" fc="ST" />
42+
</DataSet>
43+
<DataSet name="invalidFC" >
44+
<FCDA iedName="IED1" ldInst="lDevice" lnClass="XCBR" lnInst="1" doName="Pos" daName="stVal" />
45+
</DataSet>
46+
</LN0>
47+
</LDevice>
48+
</Server>
49+
</AccessPoint>
50+
</IED>
51+
</SCL>`,
52+
"application/xml"
53+
);
54+
55+
const baseDataSet = scl.querySelector(`*[name="baseDataSet"]`)!;
56+
const equalDataSet = scl.querySelector('*[name="equalDataSet"]')!;
57+
const diffDataSet = scl.querySelector('*[name="diffDataSet"]')!;
58+
59+
const invalidIedName = scl.querySelector('*[name="invalidIedName"]')!;
60+
const invalidLDevice = scl.querySelector('*[name="invalidLDevice"]')!;
61+
const invalidLnClass = scl.querySelector('*[name="invalidLnClass"]')!;
62+
const invalidDoName = scl.querySelector('*[name="invalidDoName"]')!;
63+
const invalidFC = scl.querySelector('*[name="invalidFC"]')!;
64+
65+
describe("Description for SCL schema type DataSet", () => {
66+
it("returns undefined with invalid FCDA child element definition", () => {
67+
expect(describeDataSet(invalidIedName)).to.be.undefined;
68+
expect(describeDataSet(invalidLDevice)).to.be.undefined;
69+
expect(describeDataSet(invalidLnClass)).to.be.undefined;
70+
expect(describeDataSet(invalidDoName)).to.be.undefined;
71+
expect(describeDataSet(invalidFC)).to.be.undefined;
72+
});
73+
74+
it("returns same description with semantically equal DataSet's", () =>
75+
expect(JSON.stringify(describeDataSet(baseDataSet))).to.equal(
76+
JSON.stringify(describeDataSet(equalDataSet))
77+
));
78+
79+
it("returns different description with unequal DataSet elements", () =>
80+
expect(JSON.stringify(describeDataSet(baseDataSet))).to.not.equal(
81+
JSON.stringify(describeDataSet(diffDataSet))
82+
));
83+
});

describe/DataSetDescription.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { describeNaming, NamingDescription } from "./Naming.js";
2+
3+
type FCDA = {
4+
iedName: string;
5+
ldInst: string;
6+
prefix: string;
7+
lnClass: string;
8+
lnInst: string;
9+
doName: string;
10+
daName?: string;
11+
fc: string;
12+
};
13+
14+
export interface DataSetDescription extends NamingDescription {
15+
data: FCDA[];
16+
}
17+
18+
export function describeDataSet(
19+
element: Element
20+
): DataSetDescription | undefined {
21+
const maybeData = Array.from(element.children)
22+
.filter((child) => child.tagName === "FCDA")
23+
.map((fcda) => {
24+
const [iedName, ldInst, prefix, lnClass, lnInst, doName, daName, fc] = [
25+
"iedName",
26+
"ldInst",
27+
"prefix",
28+
"lnClass",
29+
"lnInst",
30+
"doName",
31+
"daName",
32+
"fc",
33+
].map((attr) => fcda.getAttribute(attr));
34+
35+
if (!iedName || !ldInst || !lnClass || !doName || !fc) return;
36+
37+
const fcd = {
38+
iedName,
39+
ldInst,
40+
prefix: prefix ?? "",
41+
lnClass,
42+
lnInst: lnInst ?? "",
43+
doName,
44+
fc,
45+
};
46+
return daName ? { ...fcd, daName } : { ...fcd };
47+
});
48+
49+
if (maybeData.some((fcda) => fcda === undefined)) return undefined;
50+
51+
return { ...describeNaming(element), data: maybeData as FCDA[] };
52+
}

0 commit comments

Comments
 (0)