Skip to content

Commit 0bd6c46

Browse files
feat: Server
1 parent 0ac170f commit 0bd6c46

File tree

4 files changed

+317
-9
lines changed

4 files changed

+317
-9
lines changed

describe.spec.ts

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ const testScl = new DOMParser().parseFromString(
88
xmlns:sxy="http://www.iec.ch/61850/2003/SCLcoordinates"
99
xmlns:ens="http://somevalidURI"
1010
>
11-
<IED name="IED">
11+
<IED name="IED1">
1212
<AccessPoint name="AP1">
1313
<Server>
14+
<Authentication />
1415
<LDevice inst="ldInst1">
1516
<LN0 lnClass="LLN0" inst="" lnType="baseLLN0"/>
1617
<LN lnClass="XCBR" inst="1" lnType="baseXCBR"/>
@@ -26,6 +27,40 @@ const testScl = new DOMParser().parseFromString(
2627
</Server>
2728
</AccessPoint>
2829
</IED>
30+
<IED name="IED2">
31+
<AccessPoint name="AP1">
32+
<Server>
33+
<Authentication />
34+
<LDevice inst="ldInst1">
35+
<LN0 lnClass="LLN0" inst="" lnType="baseLLN0"/>
36+
<LN lnClass="XCBR" inst="1" lnType="baseXCBR"/>
37+
</LDevice>
38+
<LDevice inst="ldInst2">
39+
<LN0 lnClass="LLN0" inst="" lnType="equalLLN0"/>
40+
<LN lnClass="XCBR" inst="1" lnType="equalXCBR"/>
41+
</LDevice>
42+
<LDevice inst="ldInst3">
43+
<LN0 lnClass="LLN0" inst="" lnType="diffLLN0"/>
44+
<LN lnClass="XCBR" inst="1" lnType="diffXCBR"/>
45+
</LDevice>
46+
</Server>
47+
</AccessPoint>
48+
</IED>
49+
<IED name="IED3">
50+
<AccessPoint name="AP1">
51+
<Server>
52+
<Authentication />
53+
<LDevice inst="ldInst1">
54+
<LN0 lnClass="LLN0" inst="" lnType="baseLLN0"/>
55+
<LN lnClass="XCBR" inst="1" lnType="baseXCBR"/>
56+
</LDevice>
57+
<LDevice inst="ldInst2">
58+
<LN0 lnClass="LLN0" inst="" lnType="equalLLN0"/>
59+
<LN lnClass="XCBR" inst="1" lnType="equalXCBR"/>
60+
</LDevice>
61+
</Server>
62+
</AccessPoint>
63+
</IED>
2964
<DataTypeTemplates>
3065
<LNodeType lnClass="LLN0" id="baseLLN0" >
3166
<DO name="Beh" type="someEqualDOType" />
@@ -114,9 +149,15 @@ const baseEnumType = testScl.querySelector("#someID")!;
114149
const diffEnumType = testScl.querySelector("#someDiffID")!;
115150
const equalEnumType = testScl.querySelector("#someOtherID")!;
116151

117-
const baseLDevice = testScl.querySelector(`LDevice[inst="ldInst1"]`)!;
118-
const equalLDevice = testScl.querySelector(`LDevice[inst="ldInst2"]`)!;
119-
const diffLDevice = testScl.querySelector(`LDevice[inst="ldInst3"]`)!;
152+
const baseServer = testScl.querySelector(
153+
`IED[name="IED1"] LDevice[inst="ldInst1"]`
154+
)!;
155+
const equalServer = testScl.querySelector(
156+
`IED[name="IED2"] LDevice[inst="ldInst2"]`
157+
)!;
158+
const diffServer = testScl.querySelector(
159+
`IED[name="IED2"] LDevice[inst="ldInst3"]`
160+
)!;
120161

121162
describe("Describe SCL elements function", () => {
122163
it("returns undefined with missing describe function", () =>
@@ -136,12 +177,12 @@ describe("Describe SCL elements function", () => {
136177
));
137178

138179
it("returns same description with semantically equal LDevice's", () =>
139-
expect(JSON.stringify(describeSclElement(baseLDevice))).to.equal(
140-
JSON.stringify(describeSclElement(equalLDevice))
180+
expect(JSON.stringify(describeSclElement(baseServer))).to.equal(
181+
JSON.stringify(describeSclElement(equalServer))
141182
));
142183

143184
it("returns different description with unequal LDevice elements", () =>
144-
expect(JSON.stringify(describeSclElement(baseLDevice))).to.not.equal(
145-
JSON.stringify(describeSclElement(diffLDevice))
185+
expect(JSON.stringify(describeSclElement(baseServer))).to.not.equal(
186+
JSON.stringify(describeSclElement(diffServer))
146187
));
147188
});

describe.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { LDevice, LDeviceDescription } from "./describe/LDevice.js";
77
import { LNodeType, LNodeTypeDescription } from "./describe/LNodeType.js";
88
import { LN, LNDescription } from "./describe/LN.js";
99
import { LN0, LN0Description } from "./describe/LN0.js";
10+
import { Server, ServerDescription } from "./describe/Server.js";
1011

1112
export type Description =
1213
| PrivateDescription
@@ -17,7 +18,8 @@ export type Description =
1718
| LNodeTypeDescription
1819
| LNDescription
1920
| LN0Description
20-
| LDeviceDescription;
21+
| LDeviceDescription
22+
| ServerDescription;
2123
const sclElementDescriptors: Partial<
2224
Record<string, (element: Element) => Description | undefined>
2325
> = {
@@ -30,6 +32,7 @@ const sclElementDescriptors: Partial<
3032
LN,
3133
LN0,
3234
LDevice,
35+
Server,
3336
};
3437

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

describe/Server.spec.ts

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import { expect } from "chai";
2+
3+
import { Server } from "./Server.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 timeout="30">
10+
<Authentication />
11+
<LDevice inst="lDevice1">
12+
<LN0 lnClass="LLN0" inst="" lnType="LLN0" />
13+
<LN lnClass="MMXU" inst="1" lnType="MMXU" />
14+
<LN lnClass="MMXU" inst="2" lnType="MMXU" />
15+
</LDevice>
16+
<LDevice inst="lDevice2">
17+
<LN0 lnClass="LLN0" inst="" lnType="LLN0" />
18+
<LN lnClass="MMXU" inst="1" lnType="MMXU" />
19+
</LDevice>
20+
<Association iedName="IED3" ldInst="lDevice2" lnClass="LLN0" lnInst="" kind="pre-established" associationId="someId" />
21+
<Association iedName="IED3" ldInst="lDevice2" lnClass="MMXU" lnInst="1" kind="predefined" associationId="someId" />
22+
<Association desc="" iedName="IED3" ldInst="lDevice2" lnClass="MMXU" lnInst="2" kind="predefined" associationId="someId" />
23+
<Association desc="" iedName="IED3" ldInst="lDevice2" lnClass="MMXU" lnInst="2" kind="predefined" associationId="someId" />
24+
</Server>
25+
</AccessPoint>
26+
</IED>
27+
<IED name="IED2">
28+
<AccessPoint name="AP1">
29+
<Server>
30+
<Authentication none="true" password="false" weak="false" strong="false" certificate="false" />
31+
<LDevice inst="lDevice2">
32+
<LN0 lnClass="LLN0" inst="" lnType="LLN0" />
33+
<LN lnClass="MMXU" inst="1" lnType="MMXU" />
34+
</LDevice>
35+
<LDevice inst="lDevice1">
36+
<LN0 lnClass="LLN0" inst="" lnType="LLN0" />
37+
<LN lnClass="MMXU" inst="2" lnType="MMXU" />
38+
<LN lnClass="MMXU" inst="1" lnType="MMXU" />
39+
</LDevice>
40+
<Association iedName="IED3" ldInst="lDevice2" lnClass="MMXU" lnInst="1" />
41+
<Association desc="" iedName="IED3" ldInst="lDevice2" lnClass="MMXU" lnInst="1" kind="predefined" associationId="someId" />
42+
<Association iedName="IED3" ldInst="lDevice2" lnClass="LLN0" lnInst="" kind="pre-established" associationId="someId" />
43+
<Association desc="" iedName="IED3" ldInst="lDevice2" lnClass="MMXU" lnInst="2" kind="predefined" associationId="someId" />
44+
<Association desc="" iedName="IED3" ldInst="lDevice2" lnClass="MMXU" lnInst="2" kind="predefined" associationId="someId" />
45+
</Server>
46+
</AccessPoint>
47+
</IED>
48+
<IED name="IED3">
49+
<AccessPoint name="AP1">
50+
<Server timeout="13">
51+
<Authentication none="false" password="true" weak="true" strong="true" certificate="true" />
52+
<LDevice inst="lDevice1">
53+
<LN0 lnClass="LLN0" inst="" lnType="LLN02" />
54+
<LN lnClass="MMXU" inst="2" lnType="MMXU" />
55+
</LDevice>
56+
</Server>
57+
</AccessPoint>
58+
</IED>
59+
<IED name="IED4">
60+
<AccessPoint name="AP1">
61+
<Server timeout="13">
62+
<LDevice inst="lDevice1">
63+
<LN0 lnClass="LLN0" inst="" lnType="LLN02" />
64+
<LN lnClass="MMXU" inst="2" lnType="MMXU" />
65+
</LDevice>
66+
</Server>
67+
</AccessPoint>
68+
</IED>
69+
<DataTypeTemplates>
70+
<LNodeType id="LLN0" desc="desc" lnClass="LLN0">
71+
<DO name="Beh" type="BehENS"/>
72+
</LNodeType>
73+
<LNodeType id="MMXU" desc="desc" lnClass="MMXU">
74+
<DO name="A" type="WYE"/>
75+
</LNodeType>
76+
<DOType cdc="ENS" id="BehENS" >
77+
<DA name="stVal" bType="Enum" type="BehModKind" fc="ST" >
78+
<Val>off</Val>
79+
</DA>
80+
</DOType>
81+
<DOType id="WYE" cdc="WYE">
82+
<SDO name="phsA" type="CMV" />
83+
</DOType>
84+
<DOType id="CMV" cdc="CMV" >
85+
<DA name="cVal" bType="Struct" fc="MX" type="Vector"/>
86+
</DOType>
87+
<DAType id="Vector" >
88+
<BDA name="mag" bType="Struct" type="AnalogueValue" />
89+
</DAType>
90+
<DAType id="AnalogueValue" >
91+
<BDA name="f" bType="FLOAT32" >
92+
<Val sGroup="3">60.60</Val>
93+
<Val sGroup="1">10.10</Val>
94+
<Val sGroup="2">40.10</Val>
95+
</BDA>
96+
</DAType>
97+
<EnumType id="BehModKind" >
98+
<EnumVal ord="1">on</EnumVal>
99+
<EnumVal ord="3">test</EnumVal>
100+
<EnumVal ord="5">off</EnumVal>
101+
</EnumType>
102+
</DataTypeTemplates>
103+
</SCL>`,
104+
"application/xml"
105+
);
106+
107+
const baseServer = scl.querySelector('IED[name="IED1"] Server')!;
108+
const equalServer = scl.querySelector('IED[name="IED2"] Server')!;
109+
const diffServer = scl.querySelector('IED[name="IED3"] Server')!;
110+
const invalidServer = scl.querySelector('IED[name="IED4"] Server')!;
111+
112+
describe("Description for SCL schema type LDevice", () => {
113+
it("returns undefined with missing lnType attribute", () =>
114+
expect(Server(invalidServer)).to.be.undefined);
115+
116+
it("default timeout attribute if present", () =>
117+
expect(Server(equalServer)?.timeout).to.equal(30));
118+
119+
it("default timeout attribute if present", () =>
120+
expect(Server(diffServer)?.timeout).to.equal(13));
121+
122+
it("returns same description with semantically equal LDevice's", () =>
123+
expect(JSON.stringify(Server(baseServer))).to.equal(
124+
JSON.stringify(Server(equalServer))
125+
));
126+
127+
it("returns different description with unequal LDevice elements", () =>
128+
expect(JSON.stringify(Server(baseServer))).to.not.equal(
129+
JSON.stringify(Server(diffServer))
130+
));
131+
});

describe/Server.ts

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import { sortRecord } from "../utils.js";
2+
import { LDevice, LDeviceDescription } from "./LDevice.js";
3+
import { NamingDescription, describeNaming } from "./Naming.js";
4+
5+
function compareAssociations(a: Association, b: Association): number {
6+
const stringifiedA = JSON.stringify(a);
7+
const stringifiedB = JSON.stringify(b);
8+
9+
if (stringifiedA < stringifiedB) return -1;
10+
if (stringifiedA > stringifiedB) return 1;
11+
return 0;
12+
}
13+
14+
type Authentication = {
15+
/** Authentication attribute none defaulted to true */
16+
none: boolean;
17+
/** Authentication attribute password defaulted to false */
18+
password: boolean;
19+
/** Authentication attribute weak defaulted to false */
20+
weak: boolean;
21+
/** Authentication attribute strong defaulted to false */
22+
strong: boolean;
23+
/** Authentication attribute certificate defaulted to false */
24+
certificate: boolean;
25+
};
26+
27+
type Association = {
28+
desc?: string;
29+
iedName: string;
30+
ldInst: string;
31+
/** Association attribute prefix defaulted to empty string */
32+
prefix: string;
33+
/** Association attribute lnClass */
34+
lnClass: string;
35+
/** Association attribute lnInst */
36+
lnInst: string;
37+
/** Association attribute kind */
38+
kind: "pre-established" | "predefined";
39+
/** Association attribute associationId */
40+
associationId?: string;
41+
};
42+
43+
export interface ServerDescription extends NamingDescription {
44+
/** Server attribute timeout defaulted to 30 */
45+
timeout: number;
46+
/** Server child Authentication */
47+
authentication: Authentication;
48+
/** Server children LDevice */
49+
lDevices: Record<string, LDeviceDescription>;
50+
/** Server children Association */
51+
associations: Association[];
52+
}
53+
54+
function associations(element: Element): Association[] {
55+
const associations: Association[] = [];
56+
Array.from(element.children)
57+
.filter((child) => child.tagName === "Association")
58+
.forEach((assoc) => {
59+
const kind = assoc.getAttribute("kind");
60+
const associationId = assoc.getAttribute("associationId");
61+
const iedName = assoc.getAttribute("iedName");
62+
const ldInst = assoc.getAttribute("ldInst");
63+
const desc = assoc.getAttribute("desc") ?? "";
64+
const prefix = assoc.getAttribute("prefix") ?? "";
65+
const lnClass = assoc.getAttribute("lnClass");
66+
const lnInst = assoc.getAttribute("lnInst");
67+
68+
if (
69+
!kind ||
70+
!["pre-established", "predefined"].includes(kind) ||
71+
!iedName ||
72+
!ldInst ||
73+
!lnClass ||
74+
lnInst === null
75+
)
76+
return;
77+
78+
const association: Association = {
79+
kind: kind as "pre-established" | "predefined",
80+
desc,
81+
iedName,
82+
ldInst,
83+
prefix,
84+
lnClass,
85+
lnInst,
86+
};
87+
if (associationId) association.associationId = associationId;
88+
89+
associations.push(association);
90+
});
91+
92+
return associations.sort(compareAssociations);
93+
}
94+
95+
function authentication(element: Element): Authentication {
96+
return {
97+
none: element.getAttribute("none") === "false" ? false : true,
98+
password: element.getAttribute("password") === "true" ? true : false,
99+
weak: element.getAttribute("weak") === "true" ? true : false,
100+
strong: element.getAttribute("strong") === "true" ? true : false,
101+
certificate: element.getAttribute("certificate") === "true" ? true : false,
102+
};
103+
}
104+
105+
function lDevices(element: Element): Record<string, LDeviceDescription> {
106+
const lns: Record<string, LDeviceDescription> = {};
107+
let unsortedLDevices: Record<string, LDeviceDescription> = {};
108+
Array.from(element.children)
109+
.filter((child) => child.tagName === "LDevice")
110+
.forEach((lDevice) => {
111+
const inst = lDevice.getAttribute("inst");
112+
const lDeviceDescription = LDevice(lDevice);
113+
if (inst && !unsortedLDevices[inst] && lDeviceDescription)
114+
unsortedLDevices[inst] = lDeviceDescription;
115+
});
116+
117+
return sortRecord(unsortedLDevices);
118+
}
119+
120+
export function Server(element: Element): ServerDescription | undefined {
121+
const auth = element.querySelector(":scope > Authentication");
122+
if (!auth) return;
123+
124+
const serverDescription: ServerDescription = {
125+
...describeNaming(element),
126+
timeout: parseInt(element.getAttribute("timeout") ?? "30", 10),
127+
lDevices: lDevices(element),
128+
authentication: authentication(auth),
129+
associations: associations(element),
130+
};
131+
132+
return serverDescription;
133+
}

0 commit comments

Comments
 (0)