Skip to content

Commit 779af85

Browse files
feat: describe IED
1 parent 084d57c commit 779af85

File tree

5 files changed

+307
-11
lines changed

5 files changed

+307
-11
lines changed

describe.spec.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -149,9 +149,9 @@ const baseEnumType = testScl.querySelector("#someID")!;
149149
const diffEnumType = testScl.querySelector("#someDiffID")!;
150150
const equalEnumType = testScl.querySelector("#someOtherID")!;
151151

152-
const baseAP = testScl.querySelector(`IED[name="IED1"]>AccessPoint`)!;
153-
const equalAP = testScl.querySelector(`IED[name="IED2"]>AccessPoint`)!;
154-
const diffAP = testScl.querySelector(`IED[name="IED3"]>AccessPoint`)!;
152+
const baseIED = testScl.querySelector(`IED[name="IED1"]`)!;
153+
const equalIED = testScl.querySelector(`IED[name="IED2"]`)!;
154+
const diffIED = testScl.querySelector(`IED[name="IED3"]`)!;
155155

156156
describe("Describe SCL elements function", () => {
157157
it("returns undefined with missing describe function", () =>
@@ -170,13 +170,13 @@ describe("Describe SCL elements function", () => {
170170
JSON.stringify(describeSclElement(equalEnumType)),
171171
));
172172

173-
it("returns same description with semantically equal AccessPoint's", () =>
174-
expect(JSON.stringify(describeSclElement(baseAP))).to.equal(
175-
JSON.stringify(describeSclElement(equalAP)),
173+
it("returns same description with semantically equal IED's", () =>
174+
expect(JSON.stringify(describeSclElement(baseIED))).to.equal(
175+
JSON.stringify(describeSclElement(equalIED)),
176176
));
177177

178-
it("returns different description with unequal AccessPoint elements", () =>
179-
expect(JSON.stringify(describeSclElement(baseAP))).to.not.equal(
180-
JSON.stringify(describeSclElement(diffAP)),
178+
it("returns different description with unequal IED elements", () =>
179+
expect(JSON.stringify(describeSclElement(baseIED))).to.not.equal(
180+
JSON.stringify(describeSclElement(diffIED)),
181181
));
182182
});

describe.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { AccessPoint, AccessPointDescription } from "./describe/AccessPoint.js";
2+
import { IED, IEDDescription } from "./describe/IED.js";
23
import { Private, PrivateDescription } from "./describe/Private.js";
34
import { Text, TextDescription } from "./describe/Text.js";
45
import { EnumType, EnumTypeDescription } from "./describe/EnumType.js";
@@ -24,7 +25,8 @@ export type Description =
2425
| LDeviceDescription
2526
| ServerDescription
2627
| ServicesDescription
27-
| AccessPointDescription;
28+
| AccessPointDescription
29+
| IEDDescription;
2830
const sclElementDescriptors: Partial<
2931
Record<string, (element: Element) => Description | undefined>
3032
> = {
@@ -40,6 +42,7 @@ const sclElementDescriptors: Partial<
4042
Server,
4143
Services,
4244
AccessPoint,
45+
IED,
4346
};
4447

4548
export function describe(element: Element): Description | undefined {

describe/IED.spec.ts

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
import { expect } from "chai";
2+
3+
import { IED } from "./IED";
4+
5+
const scl = new DOMParser().parseFromString(
6+
`<SCL xmlns="http://www.iec.ch/61850/2003/SCL" >
7+
<IED name="IED1">
8+
<Services />
9+
<AccessPoint name="AP1" router="false" clock="false" kdc="false">
10+
<Server timeout="30">
11+
<Authentication />
12+
<LDevice inst="lDevice1">
13+
<LN0 lnClass="LLN0" inst="" lnType="LLN0" />
14+
<LN lnClass="MMXU" inst="1" lnType="MMXU" />
15+
<LN lnClass="MMXU" inst="2" lnType="MMXU" />
16+
</LDevice>
17+
<LDevice inst="lDevice2">
18+
<LN0 lnClass="LLN0" inst="" lnType="LLN0" />
19+
<LN lnClass="MMXU" inst="1" lnType="MMXU" />
20+
</LDevice>
21+
</Server>
22+
</AccessPoint>
23+
<KDC iedName="IED2" apName="AP1" />
24+
<KDC iedName="IED4" apName="AP1" />
25+
<KDC iedName="IED3" apName="AP1" />
26+
<KDC iedName="IED2" apName="AP1" />
27+
</IED>
28+
<IED name="IED2" originalSclVersion="2003" originalSclRevision="A" originalSclRelease="1" engRight="full">
29+
<Services />
30+
<AccessPoint name="AP1">
31+
<Server>
32+
<Authentication none="true" password="false" weak="false" strong="false" certificate="false" />
33+
<LDevice inst="lDevice2">
34+
<LN0 lnClass="LLN0" inst="" lnType="LLN0" />
35+
<LN lnClass="MMXU" inst="1" lnType="MMXU" />
36+
</LDevice>
37+
<LDevice inst="lDevice1">
38+
<LN0 lnClass="LLN0" inst="" lnType="LLN0" />
39+
<LN lnClass="MMXU" inst="2" lnType="MMXU" />
40+
<LN lnClass="MMXU" inst="1" lnType="MMXU" />
41+
</LDevice>
42+
</Server>
43+
</AccessPoint>
44+
<KDC iedName="IED3" apName="AP1" />
45+
<KDC iedName="IED4" apName="AP1" />
46+
<KDC apName="AP1" />
47+
<KDC iedName="IED5" />
48+
<KDC iedName="IED2" apName="AP1" />
49+
<KDC iedName="IED2" apName="AP1" />
50+
</IED>
51+
<IED name="IED3" originalSclVersion="2007" originalSclRevision="B" originalSclRelease="4" engRight="fix" type="type" manufacturer="manufacturer" configVersion="3" owner="owner" >
52+
<AccessPoint name="AP1" router="true" clock="true" kdc="true">
53+
<Server timeout="13">
54+
<Authentication none="false" password="true" weak="true" strong="true" certificate="true" />
55+
<LDevice inst="lDevice1">
56+
<LN0 lnClass="LLN0" inst="" lnType="LLN02" />
57+
<LN lnClass="MMXU" inst="2" lnType="MMXU" />
58+
</LDevice>
59+
</Server>
60+
</AccessPoint>
61+
</IED>
62+
<IED name="IED4">
63+
<AccessPoint name="AP1">
64+
<Server timeout="13">
65+
<LDevice inst="lDevice1">
66+
<LN0 lnClass="LLN0" inst="" lnType="LLN02" />
67+
<LN lnClass="MMXU" inst="2" lnType="MMXU" />
68+
</LDevice>
69+
</Server>
70+
</AccessPoint>
71+
</IED>
72+
<IED name="IED5">
73+
<AccessPoint >
74+
<LN lnClass="MMXU" inst="1" lnType="invalidType" />
75+
</AccessPoint>
76+
</IED>
77+
<IED name="IED6">
78+
<AccessPoint name="AP1">
79+
<ServerAt />
80+
</AccessPoint>
81+
</IED>
82+
<DataTypeTemplates>
83+
<LNodeType id="LLN0" desc="desc" lnClass="LLN0">
84+
<DO name="Beh" type="BehENS"/>
85+
</LNodeType>
86+
<LNodeType id="MMXU" desc="desc" lnClass="MMXU">
87+
<DO name="A" type="WYE"/>
88+
</LNodeType>
89+
<DOType cdc="ENS" id="BehENS" >
90+
<DA name="stVal" bType="Enum" type="BehModKind" fc="ST" >
91+
<Val>off</Val>
92+
</DA>
93+
</DOType>
94+
<DOType id="WYE" cdc="WYE">
95+
<SDO name="phsA" type="CMV" />
96+
</DOType>
97+
<DOType id="CMV" cdc="CMV" >
98+
<DA name="cVal" bType="Struct" fc="MX" type="Vector"/>
99+
</DOType>
100+
<DAType id="Vector" >
101+
<BDA name="mag" bType="Struct" type="AnalogueValue" />
102+
</DAType>
103+
<DAType id="AnalogueValue" >
104+
<BDA name="f" bType="FLOAT32" >
105+
<Val sGroup="3">60.60</Val>
106+
<Val sGroup="1">10.10</Val>
107+
<Val sGroup="2">40.10</Val>
108+
</BDA>
109+
</DAType>
110+
<EnumType id="BehModKind" >
111+
<EnumVal ord="1">on</EnumVal>
112+
<EnumVal ord="3">test</EnumVal>
113+
<EnumVal ord="5">off</EnumVal>
114+
</EnumType>
115+
</DataTypeTemplates>
116+
</SCL>`,
117+
"application/xml",
118+
);
119+
120+
const baseIED = scl.querySelector('IED[name="IED1"]')!;
121+
const equalIED = scl.querySelector('IED[name="IED2"]')!;
122+
const diffIED = scl.querySelector('IED[name="IED3"]')!;
123+
const invalidIED1 = scl.querySelector('IED[name="IED4"]')!;
124+
const invalidIED2 = scl.querySelector('IED[name="IED5"]')!;
125+
126+
describe("Description for SCL schema type IED", () => {
127+
it("returns undefined with invalid AccessPoint", () => {
128+
expect(IED(invalidIED1)).to.be.undefined;
129+
expect(IED(invalidIED2)).to.be.undefined;
130+
});
131+
132+
it("return originalSclVersion attribute defaulting to 2003", () =>
133+
expect(IED(baseIED)?.originalSclVersion).to.be.equal(2003));
134+
135+
it("return originalSclRevision attribute defaulting to 'A'", () =>
136+
expect(IED(baseIED)?.originalSclRevision).to.be.equal("A"));
137+
138+
it("return originalSclRelease attribute defaulting to 1", () =>
139+
expect(IED(baseIED)?.originalSclRelease).to.be.equal(1));
140+
141+
it("return type attribute ", () =>
142+
expect(IED(diffIED)?.type).to.be.equal("type"));
143+
144+
it("return manufacturer attribute ", () =>
145+
expect(IED(diffIED)?.manufacturer).to.be.equal("manufacturer"));
146+
147+
it("return configVersion attribute ", () =>
148+
expect(IED(diffIED)?.configVersion).to.be.equal("3"));
149+
150+
it("return owner attribute ", () =>
151+
expect(IED(diffIED)?.owner).to.be.equal("owner"));
152+
153+
it("returns same description with semantically equal IED's", () =>
154+
expect(JSON.stringify(IED(baseIED))).to.equal(
155+
JSON.stringify(IED(equalIED)),
156+
));
157+
158+
it("returns different description with unequal IED elements", () =>
159+
expect(JSON.stringify(IED(baseIED))).to.not.equal(
160+
JSON.stringify(IED(diffIED)),
161+
));
162+
});

describe/IED.ts

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import { sortRecord } from "../utils.js";
2+
import { AccessPoint, AccessPointDescription } from "./AccessPoint.js";
3+
import { NamingDescription, describeNaming } from "./Naming.js";
4+
import { Services, ServicesDescription } from "./Services.js";
5+
6+
function compareKDCs(a: KDCDescription, b: KDCDescription): number {
7+
const stringifiedA = JSON.stringify(a);
8+
const stringifiedB = JSON.stringify(b);
9+
10+
if (stringifiedA < stringifiedB) return -1;
11+
if (stringifiedA > stringifiedB) return 1;
12+
return 0;
13+
}
14+
15+
interface KDCDescription {
16+
/** IED attribute iedName */
17+
iedName: string;
18+
/** IED attribute apName */
19+
apName: string;
20+
}
21+
22+
export interface IEDDescription extends NamingDescription {
23+
/** IED attribute type */
24+
type?: string;
25+
/** IED attribute manufacturer */
26+
manufacturer?: string;
27+
/** IED attribute configVersion */
28+
configVersion?: string;
29+
/** IED attribute originalSclVersion defaulting 2003*/
30+
originalSclVersion: number;
31+
/** IED attribute originalSclRevision defaulting "A"*/
32+
originalSclRevision: string;
33+
/** IED attribute originalSclRelease defaulting 1*/
34+
originalSclRelease: number;
35+
/** IED attribute engRight defaulting "full" */
36+
engRight: string;
37+
/** IED attribute owner */
38+
owner?: string;
39+
/** IED child Services */
40+
services?: ServicesDescription;
41+
/** IED children AccessPoint */
42+
accessPoints: Record<string, AccessPointDescription>;
43+
/** IED children KDC */
44+
kDCs: KDCDescription[];
45+
}
46+
47+
function kdcDescription(element: Element): KDCDescription | undefined {
48+
const iedName = element.getAttribute("iedName");
49+
const apName = element.getAttribute("apName");
50+
if (!iedName || !apName) return;
51+
52+
return { iedName, apName };
53+
}
54+
55+
function kDCs(parent: Element): KDCDescription[] {
56+
const kdcDescriptions: KDCDescription[] = [];
57+
parent.querySelectorAll(":scope > KDC").forEach((kdc) => {
58+
const kdcDesc = kdcDescription(kdc);
59+
if (kdcDesc) kdcDescriptions.push(kdcDesc);
60+
});
61+
62+
return kdcDescriptions.sort(compareKDCs);
63+
}
64+
65+
function sortedAccessPointDescriptions(
66+
parent: Element,
67+
): Record<string, AccessPointDescription> | undefined {
68+
const accessPoints: Record<string, AccessPointDescription> = {};
69+
let existUndefinedAPs = false;
70+
Array.from(parent.querySelectorAll(":scope > AccessPoint")).forEach(
71+
(accessPoint) => {
72+
const name = accessPoint.getAttribute("name");
73+
if (!name) {
74+
existUndefinedAPs = true;
75+
return;
76+
}
77+
78+
const accessPointDescription = AccessPoint(accessPoint);
79+
if (!accessPointDescription) {
80+
existUndefinedAPs = true;
81+
return;
82+
}
83+
84+
accessPoints[name] = accessPointDescription;
85+
},
86+
);
87+
if (existUndefinedAPs) return;
88+
89+
return sortRecord(accessPoints) as Record<string, AccessPointDescription>;
90+
}
91+
92+
export function IED(element: Element): IEDDescription | undefined {
93+
const accessPoints = sortedAccessPointDescriptions(element);
94+
if (!accessPoints) return;
95+
96+
const iedDescription: IEDDescription = {
97+
...describeNaming(element),
98+
originalSclVersion: element.getAttribute("originalSclVersion")
99+
? parseInt(element.getAttribute("originalSclVersion")!, 10)
100+
: 2003,
101+
originalSclRevision: element.getAttribute("originalSclRevision")
102+
? element.getAttribute("originalSclRevision")!
103+
: "A",
104+
originalSclRelease: element.getAttribute("originalSclRelease")
105+
? parseInt(element.getAttribute("originalSclRelease")!, 10)
106+
: 1,
107+
engRight: element.getAttribute("engRight")
108+
? element.getAttribute("engRight")!
109+
: "full",
110+
accessPoints,
111+
kDCs: kDCs(element),
112+
};
113+
114+
const type = element.getAttribute("type");
115+
if (type) iedDescription.type = type;
116+
117+
const manufacturer = element.getAttribute("manufacturer");
118+
if (manufacturer) iedDescription.manufacturer = manufacturer;
119+
120+
const configVersion = element.getAttribute("configVersion");
121+
if (configVersion) iedDescription.configVersion = configVersion;
122+
123+
const owner = element.getAttribute("owner");
124+
if (owner) iedDescription.owner = owner;
125+
126+
const servicesElement = element.querySelector(":scope > Services");
127+
if (servicesElement) iedDescription.services = Services(servicesElement);
128+
129+
return iedDescription;
130+
}

utils.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Certificate } from "./describe/AccessPoint.js";
1+
import { AccessPointDescription, Certificate } from "./describe/AccessPoint.js";
22
import { DADescription } from "./describe/DADescription.js";
33
import { DODescription } from "./describe/DODescription.js";
44
import { GSEControlDescription } from "./describe/GSEControl.js";
@@ -11,6 +11,7 @@ import { SDODescription } from "./describe/SDODescription.js";
1111
import { SampledValueControlDescription } from "./describe/SampledValueControl.js";
1212

1313
type SortedObjects =
14+
| AccessPointDescription
1415
| Certificate
1516
| DADescription
1617
| GSEControlDescription

0 commit comments

Comments
 (0)