Skip to content
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
6 changes: 6 additions & 0 deletions describe/LN.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ const scl = new DOMParser().parseFromString(
</SDI>
</SDI>
</DOI>
<LogControl name="anotherLog" logName="logName" />
<LogControl name="log" dataSet="baseDataSet" logName="logName" reasonCode="true" logEna="true" intgPd="0" bufTime="0" >
<TrgOps dchg="false" qchg="false" dupd="false" period="false" gi="false" />
</LogControl>
<ReportControl name="report" datSet="baseDataSet" intgPd="0" indexed="true" buffered="true" bufTime="0" confRev="0" >
<TrgOps dchg="false" qchg="false" dupd="false" period="false" gi="false" />
<OptFields seqNum="false" timeStamp="false" dataSet="false" reasonCode="false" dataRef="false" entryID="false" configRef="false" bufOvfl="false"/>
Expand All @@ -55,6 +59,8 @@ const scl = new DOMParser().parseFromString(
<FCDA iedName="IED1" ldInst="lDevice" prefix="" lnClass="LLN0" lnInst="" doName="Beh" daName="stVal" fc="ST" />
<FCDA iedName="IED1" ldInst="lDevice" lnClass="LLN0" doName="Beh" fc="ST" />
</DataSet>
<LogControl name="log" dataSet="equalDataSet" logName="logName" />
<LogControl name="anotherLog" logName="logName" />
<ReportControl name="anotherReport" />
<ReportControl name="report" datSet="equalDataSet" />
</LN>
Expand Down
21 changes: 21 additions & 0 deletions describe/LN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
LNodeTypeDescription,
isLNodeTypeDescription,
} from "./LNodeType.js";
import { LogControlDescription, describeLogControl } from "./LogControl.js";
import { NamingDescription, describeNaming } from "./Naming.js";
import {
ReportControlDescription,
Expand All @@ -17,6 +18,7 @@ import { describeVal, compareBySGroup } from "./Val.js";

export interface LNDescription extends NamingDescription {
reports: Record<string, ReportControlDescription>;
logControls: Record<string, LogControlDescription>;
lnType: LNodeTypeDescription;
}

Expand Down Expand Up @@ -68,6 +70,24 @@ function getNextDataType(
else return dataType.bdas[path[index]];
}

function logControls(element: Element): Record<string, LogControlDescription> {
const unsortedLogControls: Record<string, LogControlDescription> = {};

Array.from(element.children)
.filter((child) => child.tagName === "LogControl")
.forEach((logControl) => {
const name = logControl.getAttribute("name");
const logControlDescription = describeLogControl(logControl);
if (name && !unsortedLogControls[name] && logControlDescription)
unsortedLogControls[name] = logControlDescription;
});

return sortRecord(unsortedLogControls) as Record<
string,
LogControlDescription
>;
}

/** Returns leaf data attribute (BDA or DA) from
* LNodeTypeDescription containing vals
* @param path - parent DOI/SDI/DAI name attributes
Expand Down Expand Up @@ -138,5 +158,6 @@ export function LN(element: Element): LNDescription | undefined {
...describeNaming(element),
lnType,
reports: reportControls(element),
logControls: logControls(element),
};
}
80 changes: 80 additions & 0 deletions describe/LogControl.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { expect } from "chai";
import { describeLogControl } from "./LogControl.js";

const scl = new DOMParser().parseFromString(
`<SCL xmlns="http://www.iec.ch/61850/2003/SCL" >
<IED name="IED1">
<AccessPoint name="AP1">
<Server>
<LDevice inst="lDevice">
<LN0 lnClass="LLN0" inst="" lnType="LLN0">
<DataSet name="baseDataSet" >
<FCDA iedName="IED1" ldInst="lDevice" lnClass="XCBR" lnInst="1" doName="Pos" daName="stVal" fc="ST" />
<FCDA iedName="IED1" ldInst="lDevice" prefix="" lnClass="XCBR" lnInst="1" doName="Pos" daName="q" fc="ST" />
<FCDA iedName="IED1" ldInst="lDevice" lnClass="LLN0" doName="Beh" daName="stVal" fc="ST" />
<FCDA iedName="IED1" ldInst="lDevice" prefix="" lnClass="LLN0" lnInst="" doName="Beh" fc="ST" />
</DataSet>
<DataSet name="equalDataSet" >
<FCDA iedName="IED1" ldInst="lDevice" prefix="" lnClass="XCBR" lnInst="1" doName="Pos" daName="stVal" fc="ST" />
<FCDA iedName="IED1" ldInst="lDevice" lnClass="XCBR" lnInst="1" doName="Pos" daName="q" fc="ST" />
<FCDA iedName="IED1" ldInst="lDevice" prefix="" lnClass="LLN0" lnInst="" doName="Beh" daName="stVal" fc="ST" />
<FCDA iedName="IED1" ldInst="lDevice" lnClass="LLN0" doName="Beh" fc="ST" />
</DataSet>
<DataSet name="diffDataSet" >
<Private type="private" />
<FCDA iedName="IED1" ldInst="lDevice" prefix="" lnClass="XCBR" lnInst="1" doName="Pos" daName="stVal" fc="ST" />
<FCDA iedName="IED1" ldInst="lDevice" lnClass="XCBR" lnInst="1" doName="Pos" daName="q" fc="ST" />
<FCDA iedName="IED1" ldInst="lDevice" prefix="" lnClass="LLN0" lnInst="" doName="Beh" daName="stVal" fc="ST" />
<FCDA iedName="IED1" ldInst="lDevice" lnClass="LLN0" doName="Beh" fc="ST" />
</DataSet>
<DataSet name="invalidDataSet" >
<FCDA ldInst="lDevice" prefix="" lnClass="XCBR" lnInst="1" doName="Pos" daName="stVal" fc="ST" />
</DataSet>
<DataSet name="invalidDataSet" >
<FCDA ldInst="lDevice" prefix="" lnClass="XCBR" lnInst="1" doName="Pos" daName="stVal" fc="ST" />
</DataSet>
<LogControl name="baseLogControl" datSet="baseDataSet" ldInst="lDevice" prefix="" lnClass="LLN0" logName="logName" logEna="true" bufTime="0" >
<TrgOps dchg="false" qchg="false" dupd="false" period="false" gi="false" />
</LogControl>
<LogControl name="diffLogControl" datSet="diffDataSet" intgPd="1000" reasonCode="false" logEna="false" logName="logName" >
<TrgOps dchg="false" qchg="true" dupd="false" period="true" gi="false" />
</LogControl>
<LogControl name="equalLogControl" datSet="equalDataSet" ldInst="lDevice" logName="logName" reasonCode="true" />
<LogControl name="missingLogName" datSet="diffDataSet" />
<LogControl name="invalidLogControl" datSet="invalidDataSet" />
</LN0>
</LDevice>
</Server>
</AccessPoint>
</IED>
</SCL>`,
"application/xml",
);

const baseLogControl = scl.querySelector(`LogControl[name="baseLogControl"]`)!;
const equalLogControl = scl.querySelector(
'LogControl[name="equalLogControl"]',
)!;
const diffLogControl = scl.querySelector('LogControl[name="diffLogControl"]')!;
const invalidLogControl = scl.querySelector(
'LogControl[name="invalidLogControl"]',
)!;
const missingLogName = scl.querySelector('LogControl[name="missingLogName"]')!;

describe("Description for SCL schema type LogControl", () => {
it("returns undefined with invalid data", () =>
expect(describeLogControl(invalidLogControl)).to.be.undefined);

it("returns undefined with invalid logName attribute", () =>
expect(describeLogControl(missingLogName)).to.be.undefined);

it("returns same description with semantically equal Control's", () =>
expect(JSON.stringify(describeLogControl(baseLogControl))).to.equal(
JSON.stringify(describeLogControl(equalLogControl)),
));

it("returns different description with unequal Control elements", () =>
expect(JSON.stringify(describeLogControl(baseLogControl))).to.not.equal(
JSON.stringify(describeLogControl(diffLogControl)),
));
});
52 changes: 52 additions & 0 deletions describe/LogControl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import {
ControlWithTriggerOptDescription,
describeControlWithTriggerOpt,
} from "./ControlWithTriggerOpt.js";

export interface LogControlDescription
extends ControlWithTriggerOptDescription {
/** ReportControl attribute ldInst. */
ldInst: string | null;
/** LogControl attribute prefix defaulted to "". */
prefix: string;
/** LogControl attribute lnClass defaulted to "LLN0". */
lnClass: string;
/** LogControl attribute lnInst. */
lnInst: string | null;
/** LogControl attribute logName. Must point to valid `Log` element */
logName: string;
/** LogControl attribute logEna defaulted to true. */
logEna: boolean;
/** LogControl attribute reasonCode defaulted to true. */
reasonCode: boolean;
/** LogCOntrol attribute bufTime defaulted to "0". */
bufTime: number;
}

export function describeLogControl(
element: Element,
): LogControlDescription | undefined {
const controlWithTriggerOptDesc = describeControlWithTriggerOpt(element);
if (!controlWithTriggerOptDesc) return;

const logName = element.getAttribute("logName");
if (!logName) return;

const logControlDescription: LogControlDescription = {
...controlWithTriggerOptDesc,
ldInst: element.getAttribute("ldInst"),
prefix: element.getAttribute("prefix") ?? "",
lnClass: element.getAttribute("lnClass") ?? "LLN0",
lnInst: element.getAttribute("lnInst"),
logEna: element.getAttribute("logEna") === "false" ? false : true,
reasonCode: element.getAttribute("reasonCode") === "false" ? false : true,
bufTime: 0,
logName,
};

const bufTime = element.getAttribute("bufTime");
if (bufTime && !isNaN(parseInt(bufTime, 10)))
logControlDescription.bufTime = parseInt(bufTime, 10);

return logControlDescription;
}
2 changes: 2 additions & 0 deletions utils.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { DADescription } from "./describe/DADescription.js";
import { DODescription } from "./describe/DODescription.js";
import { LogControlDescription } from "./describe/LogControl.js";
import { ReportControlDescription } from "./describe/ReportControl.js";
import { SDODescription } from "./describe/SDODescription.js";

type SortedObjects =
| DADescription
| LogControlDescription
| SDODescription
| ReportControlDescription
| DODescription;
Expand Down