diff --git a/src/icons.ts b/src/icons.ts
index cb77ae478..7d4bb1c86 100644
--- a/src/icons.ts
+++ b/src/icons.ts
@@ -455,55 +455,73 @@ export const voltageTransformerIcon = html``;
export const generalConductingEquipmentIcon = html`> = {
TCR: 'Thyristor Controlled Reactive Component',
};
-function typeStr(condEq: Element): string {
- return condEq.getAttribute('type') === 'DIS' &&
- condEq.querySelector('Terminal')?.getAttribute('cNodeName') === 'grounded'
- ? 'ERS'
- : condEq.getAttribute('type') ?? '';
+/**
+ * Finds LN within IED based on LNode from Substation SCL section.
+ * @param lNode - LNode in Substation SCL section.
+ * @returns - LN in the IED SCL section.
+ */
+function getLogicalNodeInstance(lNode: Element | null): Element | null {
+ if (!lNode) return null;
+ const [lnInst, lnClass, iedName, ldInst, prefix] = [
+ 'lnInst',
+ 'lnClass',
+ 'iedName',
+ 'ldInst',
+ 'prefix',
+ ].map(attribute => lNode?.getAttribute(attribute));
+ const iedSelector = [`IED[name="${iedName}"]`, 'IED'];
+ const lDevicePath = ['AccessPoint > Server'];
+ const lNSelector = [
+ `LDevice[inst="${ldInst}"] > LN[inst="${lnInst}"][lnClass="${lnClass}"]`,
+ ];
+ const lNPrefixSelector =
+ prefix && prefix !== ''
+ ? [`[prefix="${prefix}"]`]
+ : ['[prefix=""]', ':not(prefix)'];
+ return lNode.ownerDocument.querySelector(
+ crossProduct(
+ iedSelector,
+ [' > '],
+ lDevicePath,
+ [' > '],
+ lNSelector,
+ lNPrefixSelector
+ )
+ .map(strings => strings.join(''))
+ .join(',')
+ );
+}
+
+/**
+ * Searches DataTypeTemplates for SwTyp:stVal from an LN or LNode reference.
+ * @param lNorlNode - SCL IED Logical Node or LNode from Substation section.
+ * @returns - value of stVal data attribute if found.
+ */
+function getSwitchTypeValueFromDTT(lNorlNode: Element): string | undefined {
+ const rootNode = lNorlNode?.ownerDocument;
+ const lNodeType = lNorlNode.getAttribute('lnType');
+ const lnClass = lNorlNode.getAttribute('lnClass');
+ const dObj = rootNode.querySelector(
+ `DataTypeTemplates > LNodeType[id="${lNodeType}"][lnClass="${lnClass}"] > DO[name="SwTyp"]`
+ );
+ if (dObj) {
+ const dORef = dObj.getAttribute('type');
+ return rootNode
+ .querySelector(
+ `DataTypeTemplates > DOType[id="${dORef}"] > DA[name="stVal"] > Val`
+ )
+ ?.innerHTML.trim();
+ }
+ // definition missing
+ return undefined;
+}
+
+/**
+ * Finds whether a logical node has the SwTyp:stVal data attribute.
+ * @param lN - logical node within the IED section.
+ * @returns - value of type as a string.
+ */
+function getSwitchTypeValue(lN: Element): string | undefined {
+ const daInstantiated = lN.querySelector(
+ 'DOI[name="SwTyp"] > DAI[name="stVal"]'
+ );
+ // definition is on instantiated object
+ if (daInstantiated) {
+ return daInstantiated.querySelector('Val')?.innerHTML.trim();
+ // definition must be on the data object type
+ } else {
+ return getSwitchTypeValueFromDTT(lN);
+ }
+}
+
+/**
+ * Returns true if any terminal of the ConductingEquipment has a connectivity node name 'grounded'.
+ * @param condEq - SCL ConductingEquipment.
+ * @returns if any terminal of the ConductingEquipment is grounded.
+ */
+function containsGroundedTerminal(condEq: Element): boolean {
+ return Array.from(condEq.querySelectorAll('Terminal')).some(
+ t => t.getAttribute('cNodeName') === 'grounded'
+ );
+}
+
+/**
+ * Looks to see if the Conducting Equipment contains an XSWI LN. If so, check if the XSWI definition
+ * includes SwTyp and if stVal indicates an Earth/Earthing Switch by looking at either the IED or the
+ * DataTypeTemplates.
+ * @param condEq - SCL ConductingEquipment.
+ * @returns true if an earth switch is found, false otherwise.
+ */
+function containsEarthSwitchDefinition(condEq: Element): boolean {
+ const lNodeXSWI = condEq.querySelector('LNode[lnClass="XSWI"]');
+ const lN = getLogicalNodeInstance(lNodeXSWI);
+ let swTypVal;
+ if (lN) {
+ swTypVal = getSwitchTypeValue(lN);
+ } else if (lNodeXSWI) {
+ swTypVal = getSwitchTypeValueFromDTT(lNodeXSWI);
+ }
+ return swTypVal
+ ? ['Earthing Switch', 'High Speed Earthing Switch'].includes(swTypVal)
+ : false;
+}
+
+/**
+ * Find the type of an SCL conducting equipment. For earth switches derive this from terminals or XSWI logical node definition.
+ * @param condEq - SCL ConductingEquipment
+ * @returns Three letter primary apparatus device type as defined in IEC 61850-6.
+ */
+export function typeStr(condEq: Element): string {
+ if (
+ containsGroundedTerminal(condEq) ||
+ (condEq.getAttribute('type') === 'DIS' &&
+ containsEarthSwitchDefinition(condEq))
+ ) {
+ // these checks only carried out for a three phase system
+ return 'ERS';
+ } else {
+ return condEq.getAttribute('type') ?? '';
+ }
}
function typeName(condEq: Element): string {
diff --git a/src/zeroline/foundation.ts b/src/zeroline/foundation.ts
index 87256ec15..ac0a159dc 100644
--- a/src/zeroline/foundation.ts
+++ b/src/zeroline/foundation.ts
@@ -12,6 +12,7 @@ import {
import { BayEditor } from './bay-editor.js';
import { SubstationEditor } from './substation-editor.js';
import { VoltageLevelEditor } from './voltage-level-editor.js';
+import { typeStr } from '../wizards/conductingequipment.js';
function containsReference(element: Element, iedName: string): boolean {
return Array.from(element.getElementsByTagName('LNode'))
@@ -223,17 +224,6 @@ export function getIcon(condEq: Element): TemplateResult {
return typeIcons[typeStr(condEq)] ?? generalConductingEquipmentIcon;
}
-function typeStr(condEq: Element): string {
- if (
- condEq.getAttribute('type') === 'DIS' &&
- condEq.querySelector('Terminal')?.getAttribute('cNodeName') === 'grounded'
- ) {
- return 'ERS';
- } else {
- return condEq.getAttribute('type') ?? '';
- }
-}
-
const typeIcons: Partial> = {
CBR: circuitBreakerIcon,
DIS: disconnectorIcon,
diff --git a/test/testfiles/conductingequipmentwizard.scd b/test/testfiles/conductingequipmentwizard.scd
new file mode 100644
index 000000000..abe6036a6
--- /dev/null
+++ b/test/testfiles/conductingequipmentwizard.scd
@@ -0,0 +1,720 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 2021-08-31 19:49:47
+
+
+ IEC 61850-7-4:2007B
+
+
+
+
+
+ IEC 61850-7-4:2007B
+
+
+
+
+ IEC 61850-7-4:2007B
+
+
+
+
+ false
+
+
+ IEC 61850-7-4:2007B
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ pulse
+
+
+ 10000
+
+
+ 0
+
+
+ 1
+
+
+
+
+
+
+
+ Earthing Switch
+
+
+
+
+
+
+
+
+
+
+
+
+
+ pulse
+
+
+ 10000
+
+
+ 0
+
+
+ 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SIPROTEC5
+
+
+
+
+
+
+
+
+
+ direct-with-normal-security
+
+
+ SIPROTEC5
+
+
+
+
+
+
+
+
+ SIPROTEC5
+
+
+
+
+
+
+
+
+
+ direct-with-normal-security
+
+
+
+
+
+ SIEMENS
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ direct-with-normal-security
+
+
+
+
+
+ FreedomInc
+
+
+
+
+
+ IEC 61850-7-4:2007B
+
+
+
+
+
+
+
+
+
+ on
+
+
+
+
+ status-only
+
+
+
+
+
+
+
+ status-only
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ status-only
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ direct-with-normal-security
+
+
+
+
+
+
+
+
+
+
+ direct-with-normal-security
+
+
+
+
+
+
+
+
+
+
+ Earthing Switch
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ IEC 61850-8-1:2003
+
+
+
+
+
+
+
+
+ IEC 61850-8-1:2003
+
+
+
+
+
+
+
+
+
+
+
+ IEC 61850-8-1:2003
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ IEC 61850-8-1:2003
+
+
+
+
+
+
+
+ 0
+
+
+
+
+
+
+
+ 0
+
+
+
+
+
+
+
+ 1
+
+
+ 0
+
+
+
+ not-supported
+ bay-control
+ station-control
+ remote-control
+ automatic-bay
+ automatic-station
+ automatic-remote
+ maintenance
+ process
+
+
+ direct-with-normal-security
+
+
+ Ok
+ Warning
+ Alarm
+
+
+ on
+ test
+ off
+
+
+ on
+ test
+
+
+ kV/mA
+ kA/V
+ ohm/m
+ ppm/°C
+ F/m
+ H/m
+ ms/kPa
+ kPa
+ ms/V
+ ms/K
+ V/s
+ I/Ir*s
+ 1/week
+ 1/d
+ 1/h
+ 1/min
+ periods
+ GB
+ cycle
+ mile
+ inch
+ °F
+ I/IrObj
+ MB
+ KB
+ Bytes
+ p.u.
+ day(s)
+ %
+ F/mi
+ ohm/mi
+ F/km
+ ohm/km
+
+ m
+ kg
+ s
+ A
+ K
+ mol
+ cd
+ deg
+ rad
+ sr
+ Gy
+ °C
+ Sv
+ F
+ C
+ S
+ H
+ V
+ ohm
+ J
+ N
+ Hz
+ lx
+ Lm
+ Wb
+ T
+ W
+ Pa
+ m²
+ m³
+ m/s
+ m/s²
+ m³/s
+ m/m³
+ M
+ kg/m³
+ m²/s
+ W/m K
+ J/K
+ ppm
+ 1/s
+ rad/s
+ W/m²
+ J/m²
+ S/m
+ K/s
+ Pa/s
+ J/kg K
+ VA
+ VAr
+ phi
+ cos(phi)
+ Vs
+ V²
+ As
+ A²
+ A²t
+ VAh
+ Wh
+ VArh
+ V/Hz
+ Hz/s
+ char
+ char/s
+ kgm²
+ dB
+ J/Wh
+ W/s
+ l/s
+ dBm
+ h
+ min
+
+
+ y
+ z
+ a
+ f
+ p
+ n
+ µ
+ m
+ c
+ d
+
+ da
+ h
+ k
+ M
+ G
+ T
+ P
+ E
+ Z
+ Y
+
+
+ status-only
+ direct-with-normal-security
+ sbo-with-normal-security
+ direct-with-enhanced-security
+ sbo-with-enhanced-security
+
+
+ status-only
+
+
+ Time
+ WeekDay
+ WeekOfYear
+ DayOfMonth
+ DayOfYear
+
+
+ Hour
+ Day
+ Week
+ Month
+ Year
+
+
+ Monday
+ Tuesday
+ Wednesday
+ Thursday
+ Friday
+ Saturday
+ Sunday
+
+
+ January
+ February
+ March
+ April
+ May
+ June
+ July
+ August
+ September
+ October
+ November
+ December
+
+
+ pulse
+ persistent
+
+
+ status-only
+ direct-with-normal-security
+
+
+ direct-with-enhanced-security
+
+
+ Load Break
+ Disconnector
+ Earthing Switch
+ High Speed Earthing Switch
+
+
+
\ No newline at end of file
diff --git a/test/unit/editors/substation/conductingequipmentwizard.test.ts b/test/unit/editors/substation/conductingequipmentwizard.test.ts
new file mode 100644
index 000000000..db0cfdfff
--- /dev/null
+++ b/test/unit/editors/substation/conductingequipmentwizard.test.ts
@@ -0,0 +1,50 @@
+import { expect } from '@open-wc/testing';
+
+import { typeStr } from '../../../../src/wizards/conductingequipment.js';
+
+describe('conductingequipmentwizard', () => {
+ describe('recognises an earth switch in the conducting equipment wizard that', () => {
+ let doc: Document;
+ beforeEach(async () => {
+ doc = await fetch('/test/testfiles/conductingequipmentwizard.scd')
+ .then(response => response.text())
+ .then(str => new DOMParser().parseFromString(str, 'application/xml'));
+ });
+
+ it('has the first terminal grounded', () => {
+ expect(
+ typeStr(doc.querySelector('ConductingEquipment[name="ES239"]')!)
+ ).to.equal('ERS');
+ });
+
+ it('has the second terminal grounded', () => {
+ expect(
+ typeStr(doc.querySelector('ConductingEquipment[name="ES249"]')!)
+ ).to.equal('ERS');
+ });
+
+ it('has no grounded connectivityNodes but has an XSWI LN and DOI:SWTyp > DAI:stVal defining an "Earthing Switch"', () => {
+ expect(
+ typeStr(doc.querySelector('ConductingEquipment[name="ES259"]')!)
+ ).to.equal('ERS');
+ });
+
+ it('has no grounded connectivityNodes but has an XSWI LN and LNodeType > DOType > DA:stVal defining an "Earthing Switch"', () => {
+ expect(
+ typeStr(doc.querySelector('ConductingEquipment[name="ES269"]')!)
+ ).to.equal('ERS');
+ });
+
+ it('has no grounded connectivityNodes but has an XSWI LN within SubEquipment and DOI:SWTyp > DAI:stVal defining an "Earthing Switch"', () => {
+ expect(
+ typeStr(doc.querySelector('ConductingEquipment[name="ES279"]')!)
+ ).to.equal('ERS');
+ });
+
+ it('has no grounded connectivityNodes but has an IED with name "None" and a definition in the DataTypeTemplates', () => {
+ expect(
+ typeStr(doc.querySelector('ConductingEquipment[name="ES289"]')!)
+ ).to.equal('ERS');
+ });
+ });
+});