Skip to content

feat: describe IED #51

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: describeAccessPoint
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions describe.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,9 +149,9 @@ const baseEnumType = testScl.querySelector("#someID")!;
const diffEnumType = testScl.querySelector("#someDiffID")!;
const equalEnumType = testScl.querySelector("#someOtherID")!;

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

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

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

it("returns different description with unequal AccessPoint elements", () =>
expect(JSON.stringify(describeSclElement(baseAP))).to.not.equal(
JSON.stringify(describeSclElement(diffAP)),
it("returns different description with unequal IED elements", () =>
expect(JSON.stringify(describeSclElement(baseIED))).to.not.equal(
JSON.stringify(describeSclElement(diffIED)),
));
});
5 changes: 4 additions & 1 deletion describe.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { AccessPoint, AccessPointDescription } from "./describe/AccessPoint.js";
import { IED, IEDDescription } from "./describe/IED.js";
import { Private, PrivateDescription } from "./describe/Private.js";
import { Text, TextDescription } from "./describe/Text.js";
import { EnumType, EnumTypeDescription } from "./describe/EnumType.js";
Expand All @@ -24,7 +25,8 @@ export type Description =
| LDeviceDescription
| ServerDescription
| ServicesDescription
| AccessPointDescription;
| AccessPointDescription
| IEDDescription;
const sclElementDescriptors: Partial<
Record<string, (element: Element) => Description | undefined>
> = {
Expand All @@ -40,6 +42,7 @@ const sclElementDescriptors: Partial<
Server,
Services,
AccessPoint,
IED,
};

export function describe(element: Element): Description | undefined {
Expand Down
162 changes: 162 additions & 0 deletions describe/IED.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import { expect } from "chai";

import { IED } from "./IED";

const scl = new DOMParser().parseFromString(
`<SCL xmlns="http://www.iec.ch/61850/2003/SCL" >
<IED name="IED1">
<Services />
<AccessPoint name="AP1" router="false" clock="false" kdc="false">
<Server timeout="30">
<Authentication />
<LDevice inst="lDevice1">
<LN0 lnClass="LLN0" inst="" lnType="LLN0" />
<LN lnClass="MMXU" inst="1" lnType="MMXU" />
<LN lnClass="MMXU" inst="2" lnType="MMXU" />
</LDevice>
<LDevice inst="lDevice2">
<LN0 lnClass="LLN0" inst="" lnType="LLN0" />
<LN lnClass="MMXU" inst="1" lnType="MMXU" />
</LDevice>
</Server>
</AccessPoint>
<KDC iedName="IED2" apName="AP1" />
<KDC iedName="IED4" apName="AP1" />
<KDC iedName="IED3" apName="AP1" />
<KDC iedName="IED2" apName="AP1" />
</IED>
<IED name="IED2" originalSclVersion="2003" originalSclRevision="A" originalSclRelease="1" engRight="full">
<Services />
<AccessPoint name="AP1">
<Server>
<Authentication none="true" password="false" weak="false" strong="false" certificate="false" />
<LDevice inst="lDevice2">
<LN0 lnClass="LLN0" inst="" lnType="LLN0" />
<LN lnClass="MMXU" inst="1" lnType="MMXU" />
</LDevice>
<LDevice inst="lDevice1">
<LN0 lnClass="LLN0" inst="" lnType="LLN0" />
<LN lnClass="MMXU" inst="2" lnType="MMXU" />
<LN lnClass="MMXU" inst="1" lnType="MMXU" />
</LDevice>
</Server>
</AccessPoint>
<KDC iedName="IED3" apName="AP1" />
<KDC iedName="IED4" apName="AP1" />
<KDC apName="AP1" />
<KDC iedName="IED5" />
<KDC iedName="IED2" apName="AP1" />
<KDC iedName="IED2" apName="AP1" />
</IED>
<IED name="IED3" originalSclVersion="2007" originalSclRevision="B" originalSclRelease="4" engRight="fix" type="type" manufacturer="manufacturer" configVersion="3" owner="owner" >
<AccessPoint name="AP1" router="true" clock="true" kdc="true">
<Server timeout="13">
<Authentication none="false" password="true" weak="true" strong="true" certificate="true" />
<LDevice inst="lDevice1">
<LN0 lnClass="LLN0" inst="" lnType="LLN02" />
<LN lnClass="MMXU" inst="2" lnType="MMXU" />
</LDevice>
</Server>
</AccessPoint>
</IED>
<IED name="IED4">
<AccessPoint name="AP1">
<Server timeout="13">
<LDevice inst="lDevice1">
<LN0 lnClass="LLN0" inst="" lnType="LLN02" />
<LN lnClass="MMXU" inst="2" lnType="MMXU" />
</LDevice>
</Server>
</AccessPoint>
</IED>
<IED name="IED5">
<AccessPoint >
<LN lnClass="MMXU" inst="1" lnType="invalidType" />
</AccessPoint>
</IED>
<IED name="IED6">
<AccessPoint name="AP1">
<ServerAt />
</AccessPoint>
</IED>
<DataTypeTemplates>
<LNodeType id="LLN0" desc="desc" lnClass="LLN0">
<DO name="Beh" type="BehENS"/>
</LNodeType>
<LNodeType id="MMXU" desc="desc" lnClass="MMXU">
<DO name="A" type="WYE"/>
</LNodeType>
<DOType cdc="ENS" id="BehENS" >
<DA name="stVal" bType="Enum" type="BehModKind" fc="ST" >
<Val>off</Val>
</DA>
</DOType>
<DOType id="WYE" cdc="WYE">
<SDO name="phsA" type="CMV" />
</DOType>
<DOType id="CMV" cdc="CMV" >
<DA name="cVal" bType="Struct" fc="MX" type="Vector"/>
</DOType>
<DAType id="Vector" >
<BDA name="mag" bType="Struct" type="AnalogueValue" />
</DAType>
<DAType id="AnalogueValue" >
<BDA name="f" bType="FLOAT32" >
<Val sGroup="3">60.60</Val>
<Val sGroup="1">10.10</Val>
<Val sGroup="2">40.10</Val>
</BDA>
</DAType>
<EnumType id="BehModKind" >
<EnumVal ord="1">on</EnumVal>
<EnumVal ord="3">test</EnumVal>
<EnumVal ord="5">off</EnumVal>
</EnumType>
</DataTypeTemplates>
</SCL>`,
"application/xml",
);

const baseIED = scl.querySelector('IED[name="IED1"]')!;
const equalIED = scl.querySelector('IED[name="IED2"]')!;
const diffIED = scl.querySelector('IED[name="IED3"]')!;
const invalidIED1 = scl.querySelector('IED[name="IED4"]')!;
const invalidIED2 = scl.querySelector('IED[name="IED5"]')!;

describe("Description for SCL schema type IED", () => {
it("returns undefined with invalid AccessPoint", () => {
expect(IED(invalidIED1)).to.be.undefined;
expect(IED(invalidIED2)).to.be.undefined;
});

it("return originalSclVersion attribute defaulting to 2003", () =>
expect(IED(baseIED)?.originalSclVersion).to.be.equal(2003));

it("return originalSclRevision attribute defaulting to 'A'", () =>
expect(IED(baseIED)?.originalSclRevision).to.be.equal("A"));

it("return originalSclRelease attribute defaulting to 1", () =>
expect(IED(baseIED)?.originalSclRelease).to.be.equal(1));

it("return type attribute ", () =>
expect(IED(diffIED)?.type).to.be.equal("type"));

it("return manufacturer attribute ", () =>
expect(IED(diffIED)?.manufacturer).to.be.equal("manufacturer"));

it("return configVersion attribute ", () =>
expect(IED(diffIED)?.configVersion).to.be.equal("3"));

it("return owner attribute ", () =>
expect(IED(diffIED)?.owner).to.be.equal("owner"));

it("returns same description with semantically equal IED's", () =>
expect(JSON.stringify(IED(baseIED))).to.equal(
JSON.stringify(IED(equalIED)),
));

it("returns different description with unequal IED elements", () =>
expect(JSON.stringify(IED(baseIED))).to.not.equal(
JSON.stringify(IED(diffIED)),
));
});
130 changes: 130 additions & 0 deletions describe/IED.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { sortRecord } from "../utils.js";
import { AccessPoint, AccessPointDescription } from "./AccessPoint.js";
import { NamingDescription, describeNaming } from "./Naming.js";
import { Services, ServicesDescription } from "./Services.js";

function compareKDCs(a: KDCDescription, b: KDCDescription): number {
const stringifiedA = JSON.stringify(a);
const stringifiedB = JSON.stringify(b);

if (stringifiedA < stringifiedB) return -1;
if (stringifiedA > stringifiedB) return 1;
return 0;
}

interface KDCDescription {
/** IED attribute iedName */
iedName: string;
/** IED attribute apName */
apName: string;
}

export interface IEDDescription extends NamingDescription {
/** IED attribute type */
type?: string;
/** IED attribute manufacturer */
manufacturer?: string;
/** IED attribute configVersion */
configVersion?: string;
/** IED attribute originalSclVersion defaulting 2003*/
originalSclVersion: number;
/** IED attribute originalSclRevision defaulting "A"*/
originalSclRevision: string;
/** IED attribute originalSclRelease defaulting 1*/
originalSclRelease: number;
/** IED attribute engRight defaulting "full" */
engRight: string;
/** IED attribute owner */
owner?: string;
/** IED child Services */
services?: ServicesDescription;
/** IED children AccessPoint */
accessPoints: Record<string, AccessPointDescription>;
/** IED children KDC */
kDCs: KDCDescription[];
}

function kdcDescription(element: Element): KDCDescription | undefined {
const iedName = element.getAttribute("iedName");
const apName = element.getAttribute("apName");
if (!iedName || !apName) return;

return { iedName, apName };
}

function kDCs(parent: Element): KDCDescription[] {
const kdcDescriptions: KDCDescription[] = [];
parent.querySelectorAll(":scope > KDC").forEach((kdc) => {
const kdcDesc = kdcDescription(kdc);
if (kdcDesc) kdcDescriptions.push(kdcDesc);
});

return kdcDescriptions.sort(compareKDCs);
}

function sortedAccessPointDescriptions(
parent: Element,
): Record<string, AccessPointDescription> | undefined {
const accessPoints: Record<string, AccessPointDescription> = {};
let existUndefinedAPs = false;
Array.from(parent.querySelectorAll(":scope > AccessPoint")).forEach(
(accessPoint) => {
const name = accessPoint.getAttribute("name");
if (!name) {
existUndefinedAPs = true;
return;
}

const accessPointDescription = AccessPoint(accessPoint);
if (!accessPointDescription) {
existUndefinedAPs = true;
return;
}

accessPoints[name] = accessPointDescription;
},
);
if (existUndefinedAPs) return;

return sortRecord(accessPoints) as Record<string, AccessPointDescription>;
}

export function IED(element: Element): IEDDescription | undefined {
const accessPoints = sortedAccessPointDescriptions(element);
if (!accessPoints) return;

const iedDescription: IEDDescription = {
...describeNaming(element),
originalSclVersion: element.getAttribute("originalSclVersion")
? parseInt(element.getAttribute("originalSclVersion")!, 10)
: 2003,
originalSclRevision: element.getAttribute("originalSclRevision")
? element.getAttribute("originalSclRevision")!
: "A",
originalSclRelease: element.getAttribute("originalSclRelease")
? parseInt(element.getAttribute("originalSclRelease")!, 10)
: 1,
engRight: element.getAttribute("engRight")
? element.getAttribute("engRight")!
: "full",
accessPoints,
kDCs: kDCs(element),
};

const type = element.getAttribute("type");
if (type) iedDescription.type = type;

const manufacturer = element.getAttribute("manufacturer");
if (manufacturer) iedDescription.manufacturer = manufacturer;

const configVersion = element.getAttribute("configVersion");
if (configVersion) iedDescription.configVersion = configVersion;

const owner = element.getAttribute("owner");
if (owner) iedDescription.owner = owner;

const servicesElement = element.querySelector(":scope > Services");
if (servicesElement) iedDescription.services = Services(servicesElement);

return iedDescription;
}
3 changes: 2 additions & 1 deletion utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Certificate } from "./describe/AccessPoint.js";
import { AccessPointDescription, Certificate } from "./describe/AccessPoint.js";
import { DADescription } from "./describe/DADescription.js";
import { DODescription } from "./describe/DODescription.js";
import { GSEControlDescription } from "./describe/GSEControl.js";
Expand All @@ -11,6 +11,7 @@ import { SDODescription } from "./describe/SDODescription.js";
import { SampledValueControlDescription } from "./describe/SampledValueControl.js";

type SortedObjects =
| AccessPointDescription
| Certificate
| DADescription
| GSEControlDescription
Expand Down