Skip to content

Commit 041f588

Browse files
feat: describe LogControl
1 parent 4fb8ce3 commit 041f588

File tree

4 files changed

+156
-0
lines changed

4 files changed

+156
-0
lines changed

describe/LN.spec.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ const scl = new DOMParser().parseFromString(
3535
</SDI>
3636
</SDI>
3737
</DOI>
38+
<LogControl name="anotherLog" logName="logName" />
39+
<LogControl name="log" dataSet="baseDataSet" logName="logName" reasonCode="true" logEna="true" intgPd="0" bufTime="0" >
40+
<TrgOps dchg="false" qchg="false" dupd="false" period="false" gi="false" />
41+
</LogControl>
3842
<ReportControl name="report" datSet="baseDataSet" intgPd="0" indexed="true" buffered="true" bufTime="0" confRev="0" >
3943
<TrgOps dchg="false" qchg="false" dupd="false" period="false" gi="false" />
4044
<OptFields seqNum="false" timeStamp="false" dataSet="false" reasonCode="false" dataRef="false" entryID="false" configRef="false" bufOvfl="false"/>
@@ -55,6 +59,8 @@ const scl = new DOMParser().parseFromString(
5559
<FCDA iedName="IED1" ldInst="lDevice" prefix="" lnClass="LLN0" lnInst="" doName="Beh" daName="stVal" fc="ST" />
5660
<FCDA iedName="IED1" ldInst="lDevice" lnClass="LLN0" doName="Beh" fc="ST" />
5761
</DataSet>
62+
<LogControl name="log" dataSet="equalDataSet" logName="logName" />
63+
<LogControl name="anotherLog" logName="logName" />
5864
<ReportControl name="anotherReport" />
5965
<ReportControl name="report" datSet="equalDataSet" />
6066
</LN>

describe/LN.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { sortRecord } from "../utils.js";
22
import { AbstractDataAttributeDescription } from "./AbstractDataAttribute.js";
33
import { LNodeType, LNodeTypeDescription } from "./LNodeType.js";
4+
import { LogControlDescription, describeLogControl } from "./LogControl.js";
45
import { NamingDescription, describeNaming } from "./Naming.js";
56
import {
67
ReportControlDescription,
@@ -10,6 +11,7 @@ import { describeVal, compareBySGroup } from "./Val.js";
1011

1112
export interface LNDescription extends NamingDescription {
1213
reports: Record<string, ReportControlDescription>;
14+
logControls: Record<string, LogControlDescription>;
1315
lnType: LNodeTypeDescription;
1416
}
1517

@@ -30,6 +32,21 @@ function reportControls(
3032
return sortRecord(unsortedReports);
3133
}
3234

35+
function logControls(element: Element): Record<string, LogControlDescription> {
36+
const unsortedLogControls: Record<string, LogControlDescription> = {};
37+
38+
Array.from(element.children)
39+
.filter((child) => child.tagName === "LogControl")
40+
.forEach((logControl) => {
41+
const name = logControl.getAttribute("name");
42+
const logControlDescription = describeLogControl(logControl);
43+
if (name && !unsortedLogControls[name] && logControlDescription)
44+
unsortedLogControls[name] = logControlDescription;
45+
});
46+
47+
return sortRecord(unsortedLogControls);
48+
}
49+
3350
/** Returns leaf data attribute (BDA or DA) from
3451
* LNodeTypeDescription containing vals
3552
* @param path - parent DOI/SDI/DAI name attributes
@@ -125,5 +142,6 @@ export function LN(element: Element): LNDescription | undefined {
125142
...describeNaming(element),
126143
lnType,
127144
reports: reportControls(element),
145+
logControls: logControls(element),
128146
};
129147
}

describe/LogControl.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+
import { describeLogControl } from "./LogControl.js";
3+
4+
const scl = new DOMParser().parseFromString(
5+
`<SCL xmlns="http://www.iec.ch/61850/2003/SCL" >
6+
<IED name="IED1">
7+
<AccessPoint name="AP1">
8+
<Server>
9+
<LDevice inst="lDevice">
10+
<LN0 lnClass="LLN0" inst="" lnType="LLN0">
11+
<DataSet name="baseDataSet" >
12+
<FCDA iedName="IED1" ldInst="lDevice" lnClass="XCBR" lnInst="1" doName="Pos" daName="stVal" fc="ST" />
13+
<FCDA iedName="IED1" ldInst="lDevice" prefix="" lnClass="XCBR" lnInst="1" doName="Pos" daName="q" fc="ST" />
14+
<FCDA iedName="IED1" ldInst="lDevice" lnClass="LLN0" doName="Beh" daName="stVal" fc="ST" />
15+
<FCDA iedName="IED1" ldInst="lDevice" prefix="" lnClass="LLN0" lnInst="" doName="Beh" fc="ST" />
16+
</DataSet>
17+
<DataSet name="equalDataSet" >
18+
<FCDA iedName="IED1" ldInst="lDevice" prefix="" lnClass="XCBR" lnInst="1" doName="Pos" daName="stVal" fc="ST" />
19+
<FCDA iedName="IED1" ldInst="lDevice" lnClass="XCBR" lnInst="1" doName="Pos" daName="q" fc="ST" />
20+
<FCDA iedName="IED1" ldInst="lDevice" prefix="" lnClass="LLN0" lnInst="" doName="Beh" daName="stVal" fc="ST" />
21+
<FCDA iedName="IED1" ldInst="lDevice" lnClass="LLN0" doName="Beh" fc="ST" />
22+
</DataSet>
23+
<DataSet name="diffDataSet" >
24+
<Private type="private" />
25+
<FCDA iedName="IED1" ldInst="lDevice" prefix="" lnClass="XCBR" lnInst="1" doName="Pos" daName="stVal" fc="ST" />
26+
<FCDA iedName="IED1" ldInst="lDevice" lnClass="XCBR" lnInst="1" doName="Pos" daName="q" fc="ST" />
27+
<FCDA iedName="IED1" ldInst="lDevice" prefix="" lnClass="LLN0" lnInst="" doName="Beh" daName="stVal" fc="ST" />
28+
<FCDA iedName="IED1" ldInst="lDevice" lnClass="LLN0" doName="Beh" fc="ST" />
29+
</DataSet>
30+
<DataSet name="invalidDataSet" >
31+
<FCDA ldInst="lDevice" prefix="" lnClass="XCBR" lnInst="1" doName="Pos" daName="stVal" fc="ST" />
32+
</DataSet>
33+
<DataSet name="invalidDataSet" >
34+
<FCDA ldInst="lDevice" prefix="" lnClass="XCBR" lnInst="1" doName="Pos" daName="stVal" fc="ST" />
35+
</DataSet>
36+
<LogControl name="baseLogControl" datSet="baseDataSet" ldInst="lDevice" prefix="" lnClass="LLN0" logName="logName" logEna="true" bufTime="0" >
37+
<TrgOps dchg="false" qchg="false" dupd="false" period="false" gi="false" />
38+
</LogControl>
39+
<LogControl name="diffLogControl" datSet="diffDataSet" intgPd="1000" reasonCode="false" logEna="false" logName="logName" >
40+
<TrgOps dchg="false" qchg="true" dupd="false" period="true" gi="false" />
41+
</LogControl>
42+
<LogControl name="equalLogControl" datSet="equalDataSet" ldInst="lDevice" logName="logName" reasonCode="true" />
43+
<LogControl name="missingLogName" datSet="diffDataSet" />
44+
<LogControl name="invalidLogControl" datSet="invalidDataSet" />
45+
</LN0>
46+
</LDevice>
47+
</Server>
48+
</AccessPoint>
49+
</IED>
50+
</SCL>`,
51+
"application/xml"
52+
);
53+
54+
const baseLogControl = scl.querySelector(`LogControl[name="baseLogControl"]`)!;
55+
const equalLogControl = scl.querySelector(
56+
'LogControl[name="equalLogControl"]'
57+
)!;
58+
const diffLogControl = scl.querySelector('LogControl[name="diffLogControl"]')!;
59+
const invalidLogControl = scl.querySelector(
60+
'LogControl[name="invalidLogControl"]'
61+
)!;
62+
const missingLogName = scl.querySelector('LogControl[name="missingLogName"]')!;
63+
64+
describe("Description for SCL schema type LogControl", () => {
65+
it("returns undefined with invalid data", () =>
66+
expect(describeLogControl(invalidLogControl)).to.be.undefined);
67+
68+
it("returns undefined with invalid logName attribute", () =>
69+
expect(describeLogControl(missingLogName)).to.be.undefined);
70+
71+
it("returns same description with semantically equal Control's", () =>
72+
expect(JSON.stringify(describeLogControl(baseLogControl))).to.equal(
73+
JSON.stringify(describeLogControl(equalLogControl))
74+
));
75+
76+
it("returns different description with unequal Control elements", () =>
77+
expect(JSON.stringify(describeLogControl(baseLogControl))).to.not.equal(
78+
JSON.stringify(describeLogControl(diffLogControl))
79+
));
80+
});

describe/LogControl.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import {
2+
ControlWithTriggerOptDescription,
3+
describeControlWithTriggerOpt,
4+
} from "./ControlWithTriggerOpt.js";
5+
6+
export interface LogControlDescription
7+
extends ControlWithTriggerOptDescription {
8+
/** ReportControl attribute ldInst. */
9+
ldInst: string | null;
10+
/** LogControl attribute prefix defaulted to "". */
11+
prefix: string;
12+
/** LogControl attribute lnClass defaulted to "LLN0". */
13+
lnClass: string;
14+
/** LogControl attribute lnInst. */
15+
lnInst: string | null;
16+
/** LogControl attribute logName. Must point to valid `Log` element */
17+
logName: string;
18+
/** LogControl attribute logEna defaulted to true. */
19+
logEna: boolean;
20+
/** LogControl attribute reasonCode defaulted to true. */
21+
reasonCode: boolean;
22+
/** LogCOntrol attribute bufTime defaulted to "0". */
23+
bufTime: number;
24+
}
25+
26+
export function describeLogControl(
27+
element: Element
28+
): LogControlDescription | undefined {
29+
const controlWithTriggerOptDesc = describeControlWithTriggerOpt(element);
30+
if (!controlWithTriggerOptDesc) return;
31+
32+
const logName = element.getAttribute("logName");
33+
if (!logName) return;
34+
35+
const logControlDescription: LogControlDescription = {
36+
...controlWithTriggerOptDesc,
37+
ldInst: element.getAttribute("ldInst"),
38+
prefix: element.getAttribute("prefix") ?? "",
39+
lnClass: element.getAttribute("lnClass") ?? "LLN0",
40+
lnInst: element.getAttribute("lnInst"),
41+
logEna: element.getAttribute("logEna") === "false" ? false : true,
42+
reasonCode: element.getAttribute("reasonCode") === "false" ? false : true,
43+
bufTime: 0,
44+
logName,
45+
};
46+
47+
const bufTime = element.getAttribute("bufTime");
48+
if (bufTime && !isNaN(parseInt(bufTime, 10)))
49+
logControlDescription.bufTime = parseInt(bufTime, 10);
50+
51+
return logControlDescription;
52+
}

0 commit comments

Comments
 (0)