From 9944b49fc72492a0862b048170ca70ff03bafa84 Mon Sep 17 00:00:00 2001 From: Daniel Mulholland Date: Wed, 29 Dec 2021 18:13:37 +1300 Subject: [PATCH 01/13] Add earth switch icon --- src/icons.ts | 53 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 40 insertions(+), 13 deletions(-) diff --git a/src/icons.ts b/src/icons.ts index cb77ae478c..0f1988425e 100644 --- a/src/icons.ts +++ b/src/icons.ts @@ -456,53 +456,80 @@ export const voltageTransformerIcon = html` + viewBox="0 0 25 35" +> + /> + + + `; export const generalConductingEquipmentIcon = html` Date: Wed, 29 Dec 2021 20:27:54 +1300 Subject: [PATCH 02/13] Revise earth switch detection logic. Closes #459 --- src/wizards/conductingequipment.ts | 4 +++- src/zeroline/foundation.ts | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/wizards/conductingequipment.ts b/src/wizards/conductingequipment.ts index 9cb7291da3..e7aa161f04 100644 --- a/src/wizards/conductingequipment.ts +++ b/src/wizards/conductingequipment.ts @@ -51,7 +51,9 @@ const types: Partial> = { function typeStr(condEq: Element): string { return condEq.getAttribute('type') === 'DIS' && - condEq.querySelector('Terminal')?.getAttribute('cNodeName') === 'grounded' + Array.from(condEq.querySelectorAll('Terminal')) + .map(t => t.getAttribute('cNodeName')) + .includes('grounded') ? 'ERS' : condEq.getAttribute('type') ?? ''; } diff --git a/src/zeroline/foundation.ts b/src/zeroline/foundation.ts index 87256ec158..38d0fb5496 100644 --- a/src/zeroline/foundation.ts +++ b/src/zeroline/foundation.ts @@ -226,7 +226,9 @@ export function getIcon(condEq: Element): TemplateResult { function typeStr(condEq: Element): string { if ( condEq.getAttribute('type') === 'DIS' && - condEq.querySelector('Terminal')?.getAttribute('cNodeName') === 'grounded' + Array.from(condEq.querySelectorAll('Terminal')) + .map(t => t.getAttribute('cNodeName')) + .includes('grounded') ) { return 'ERS'; } else { From e2bcffb1077d823366bb1b2f53e8fed6cd2afa8c Mon Sep 17 00:00:00 2001 From: Daniel Mulholland Date: Wed, 29 Dec 2021 20:43:45 +1300 Subject: [PATCH 03/13] Reduce viewbox to 25x25 px for ES icon --- src/icons.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/icons.ts b/src/icons.ts index 0f1988425e..f56c0bd97c 100644 --- a/src/icons.ts +++ b/src/icons.ts @@ -456,7 +456,7 @@ export const voltageTransformerIcon = html` Date: Sat, 8 Jan 2022 12:21:14 +1300 Subject: [PATCH 04/13] Remove bottom line from ES --- src/icons.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/icons.ts b/src/icons.ts index f56c0bd97c..6c470bf154 100644 --- a/src/icons.ts +++ b/src/icons.ts @@ -467,15 +467,6 @@ export const earthSwitchIcon = html` - Date: Sat, 15 Jan 2022 09:40:55 +1300 Subject: [PATCH 05/13] Add test for ERS type logic. Adjust logic for XSWI LNode --- src/editors/SingleLineDiagram.ts | 10 +- src/wizards/conductingequipment.ts | 94 ++- src/zeroline/foundation.ts | 14 +- test/testfiles/conductingequipmentwizard.scd | 697 ++++++++++++++++++ .../conductingequipmentwizard.test.ts | 56 ++ 5 files changed, 847 insertions(+), 24 deletions(-) create mode 100644 test/testfiles/conductingequipmentwizard.scd create mode 100644 test/unit/editors/substation/conductingequipmentwizard.test.ts diff --git a/src/editors/SingleLineDiagram.ts b/src/editors/SingleLineDiagram.ts index 065091eb87..1554bda77b 100644 --- a/src/editors/SingleLineDiagram.ts +++ b/src/editors/SingleLineDiagram.ts @@ -140,11 +140,13 @@ export default class SingleLineDiagramPlugin extends LitElement { * Draw all equipment and connections of the selected Substation. */ private drawSubstation(): void { - const substationGroup = createSubstationElement(this.selectedSubstation!); - this.svg.appendChild(substationGroup); + if (this.selectedSubstation !== undefined) { + const substationGroup = createSubstationElement(this.selectedSubstation!); + this.svg.appendChild(substationGroup); - this.drawPowerTransformers(this.selectedSubstation!, substationGroup); - this.drawVoltageLevels(this.selectedSubstation!, substationGroup); + this.drawPowerTransformers(this.selectedSubstation!, substationGroup); + this.drawVoltageLevels(this.selectedSubstation!, substationGroup); + } } /** diff --git a/src/wizards/conductingequipment.ts b/src/wizards/conductingequipment.ts index e7aa161f04..549091a62a 100644 --- a/src/wizards/conductingequipment.ts +++ b/src/wizards/conductingequipment.ts @@ -15,6 +15,7 @@ import { WizardInput, } from '../foundation.js'; import { updateNamingAction } from './foundation/actions.js'; +import { getDirections } from '../editors/singlelinediagram/sld-drawing.js'; const types: Partial> = { // standard @@ -49,13 +50,92 @@ const types: Partial> = { TCR: 'Thyristor Controlled Reactive Component', }; -function typeStr(condEq: Element): string { - return condEq.getAttribute('type') === 'DIS' && - Array.from(condEq.querySelectorAll('Terminal')) - .map(t => t.getAttribute('cNodeName')) - .includes('grounded') - ? 'ERS' - : condEq.getAttribute('type') ?? ''; +function getLNodefromIED(lNode: Element | null): Element | null { + if (!lNode) return null; + const [iedName, ldInst, lnClass, lnInst, lnType] = [ + 'iedName', + 'ldInst', + 'lnClass', + 'lnInst', + 'lnType', + ].map(attribute => lNode?.getAttribute(attribute)); + return (lNode?.getRootNode()).querySelector(`IED[name='${iedName}'] > AccessPoint > Server > LDevice[inst='${ldInst}'] > LN[inst='${lnInst}'][lnType='${lnType}'][lnClass='${lnClass}']`); +} + +function getDataAttributeValue(lNode: Element, doName: string, sdName: string | undefined, daName: string): string | undefined { + const sdDef = sdName ? ` > SDI[name='${sdName}']` : ''; + const daInstantiated = lNode.querySelector(`DOI[name='${doName}']${sdDef} > DAI[name='${daName}'`); + if (daInstantiated) { + // data attribute is fully instantiated within a DOI/SDI, look for it within: DOI > ?SDI > DAI + return daInstantiated.querySelector('Val')?.innerHTML.trim(); + } else { + const rootNode = lNode?.getRootNode(); + const lNodeType = lNode.getAttribute('type'); + const lnClass = lNode.getAttribute('lnClass'); + if (sdName) { + // definition to be found on the subdata type + // this code path has not been tested but may be of use elsewhere + const sdo = rootNode.querySelector(`DataTypeTemplates > LNodeType[id=${lNodeType}][class=${lnClass}] > SDO[name=${doName}]`); + if (sdo) { + const sdoRef = sdo.getAttribute('type'); + return rootNode.querySelector(`DataTypeTemplates > DOType[id=${sdoRef}] > DA[name='${daName}'] > Val`)?.innerHTML.trim(); + } + // definition missing + return undefined; + } else { + // definition must be on the data object type + const doObj = rootNode.querySelector(`DataTypeTemplates > LNodeType[id=${lNodeType}][class=${lnClass}] > DO[name=${doName}]`); + if (doObj) { + const doRef = doObj.getAttribute('type'); + return rootNode.querySelector(`DataTypeTemplates > DOType[id=${doRef}] > DA[name='${daName}'] > Val`)?.innerHTML.trim(); + } + // definition missing + return undefined; + } + } +} + +/** + * 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')) + .map(t => t.getAttribute('cNodeName')) + .includes('grounded') + ); +} + +/** + * Looks to see if the Conducting Equipment contains an XSWI logical node. If so, check if the XSWI definition + * includes SwTyp and if stVal indicates an Earth/Earthing Switch. + * @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 lNode = getLNodefromIED(lNodeXSWI); + if (lNode) { + const swTypVal = getDataAttributeValue(lNode, 'SwTyp', undefined, 'stVal'); + return swTypVal ? ['Earthing Switch', 'High Speed Earthing Switch'].includes(swTypVal) : false; + } else { + return 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 38d0fb5496..ac0a159dc2 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,19 +224,6 @@ export function getIcon(condEq: Element): TemplateResult { return typeIcons[typeStr(condEq)] ?? generalConductingEquipmentIcon; } -function typeStr(condEq: Element): string { - if ( - condEq.getAttribute('type') === 'DIS' && - Array.from(condEq.querySelectorAll('Terminal')) - .map(t => t.getAttribute('cNodeName')) - .includes('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 0000000000..72d831cfc5 --- /dev/null +++ b/test/testfiles/conductingequipmentwizard.scd @@ -0,0 +1,697 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + + + + + + Earthing Switch + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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/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 + + As + + 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 0000000000..34579e4859 --- /dev/null +++ b/test/unit/editors/substation/conductingequipmentwizard.test.ts @@ -0,0 +1,56 @@ +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 logicalNode 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 logicalNode and DOI:SWTyp > 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 logicalNode within SubEquipment and DOI:SWTyp > DAI:stVal defining an "Earthing Switch"', () => { + expect( + typeStr( + doc.querySelector('ConductingEquipment[name="ES279"]')! + ) + ).to.equal('ERS'); + }); + + }); +}); From 1a4d3a26def2af91c74f38607d5e1f1e663a3c80 Mon Sep 17 00:00:00 2001 From: Daniel Mulholland Date: Sat, 15 Jan 2022 09:41:14 +1300 Subject: [PATCH 06/13] Flip ERS icon --- src/icons.ts | 128 ++++++++++++++++++++++++--------------------------- 1 file changed, 61 insertions(+), 67 deletions(-) diff --git a/src/icons.ts b/src/icons.ts index 6c470bf154..0810d7ccd2 100644 --- a/src/icons.ts +++ b/src/icons.ts @@ -455,73 +455,67 @@ export const voltageTransformerIcon = html``; export const earthSwitchIcon = html` - - - - - - - - `; +viewBox="0 0 25 25" +version="1.1" +xmlns="http://www.w3.org/2000/svg" +xmlns:svg="http://www.w3.org/2000/svg"> + + + + +> + + +`; export const generalConductingEquipmentIcon = html` Date: Sat, 15 Jan 2022 09:47:29 +1300 Subject: [PATCH 07/13] lint --- src/wizards/conductingequipment.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wizards/conductingequipment.ts b/src/wizards/conductingequipment.ts index 549091a62a..bba191d594 100644 --- a/src/wizards/conductingequipment.ts +++ b/src/wizards/conductingequipment.ts @@ -120,7 +120,7 @@ function containsEarthSwitchDefinition(condEq: Element): boolean { const swTypVal = getDataAttributeValue(lNode, 'SwTyp', undefined, 'stVal'); return swTypVal ? ['Earthing Switch', 'High Speed Earthing Switch'].includes(swTypVal) : false; } else { - return false; + return false; } } From aa5503695572143436d2e616ee9a292a47df3183 Mon Sep 17 00:00:00 2001 From: Daniel Mulholland Date: Sat, 15 Jan 2022 09:52:57 +1300 Subject: [PATCH 08/13] revert changes to single line diagram --- src/editors/SingleLineDiagram.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/editors/SingleLineDiagram.ts b/src/editors/SingleLineDiagram.ts index 1554bda77b..065091eb87 100644 --- a/src/editors/SingleLineDiagram.ts +++ b/src/editors/SingleLineDiagram.ts @@ -140,13 +140,11 @@ export default class SingleLineDiagramPlugin extends LitElement { * Draw all equipment and connections of the selected Substation. */ private drawSubstation(): void { - if (this.selectedSubstation !== undefined) { - const substationGroup = createSubstationElement(this.selectedSubstation!); - this.svg.appendChild(substationGroup); + const substationGroup = createSubstationElement(this.selectedSubstation!); + this.svg.appendChild(substationGroup); - this.drawPowerTransformers(this.selectedSubstation!, substationGroup); - this.drawVoltageLevels(this.selectedSubstation!, substationGroup); - } + this.drawPowerTransformers(this.selectedSubstation!, substationGroup); + this.drawVoltageLevels(this.selectedSubstation!, substationGroup); } /** From 58f0407e10ec7bd0e5d75d408f7e4bf3ae984096 Mon Sep 17 00:00:00 2001 From: Daniel Mulholland Date: Sat, 15 Jan 2022 09:57:44 +1300 Subject: [PATCH 09/13] Add docstring --- src/wizards/conductingequipment.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/wizards/conductingequipment.ts b/src/wizards/conductingequipment.ts index bba191d594..e679509145 100644 --- a/src/wizards/conductingequipment.ts +++ b/src/wizards/conductingequipment.ts @@ -50,6 +50,11 @@ const types: Partial> = { TCR: 'Thyristor Controlled Reactive Component', }; +/** + * Finds Logical Node from IED based on Logical Node in the Substation SCL section. + * @param lNode - Logical Node in Substation SCL section. + * @returns - Logical Node in the IED SCL section. + */ function getLNodefromIED(lNode: Element | null): Element | null { if (!lNode) return null; const [iedName, ldInst, lnClass, lnInst, lnType] = [ @@ -62,6 +67,14 @@ function getLNodefromIED(lNode: Element | null): Element | null { return (lNode?.getRootNode()).querySelector(`IED[name='${iedName}'] > AccessPoint > Server > LDevice[inst='${ldInst}'] > LN[inst='${lnInst}'][lnType='${lnType}'][lnClass='${lnClass}']`); } +/** + * Finds data attribute by inspecting an IED's logical node or types. + * @param lNode - LNode within the IED section. + * @param doName - name for data object (e.g. SwTyp). + * @param sdName - id for sub data object. + * @param daName - name for data attribute (e.g. stVal). + * @returns - value of type as a string. + */ function getDataAttributeValue(lNode: Element, doName: string, sdName: string | undefined, daName: string): string | undefined { const sdDef = sdName ? ` > SDI[name='${sdName}']` : ''; const daInstantiated = lNode.querySelector(`DOI[name='${doName}']${sdDef} > DAI[name='${daName}'`); From 3d4726ae4d104085e44b4b43fc2dd3645f80b878 Mon Sep 17 00:00:00 2001 From: Daniel Mulholland Date: Sun, 23 Jan 2022 22:23:16 +1300 Subject: [PATCH 10/13] Respond to review comments. Support prefix attribute for LNs. Support fully qualified LN without IED name specified. --- src/wizards/conductingequipment.ts | 125 ++++++++++-------- test/testfiles/conductingequipmentwizard.scd | 11 +- .../conductingequipmentwizard.test.ts | 14 +- 3 files changed, 90 insertions(+), 60 deletions(-) diff --git a/src/wizards/conductingequipment.ts b/src/wizards/conductingequipment.ts index e679509145..24c76217f9 100644 --- a/src/wizards/conductingequipment.ts +++ b/src/wizards/conductingequipment.ts @@ -7,6 +7,7 @@ import '@material/mwc-select'; import '../wizard-textfield.js'; import { createElement, + crossProduct, EditorAction, getValue, isPublic, @@ -15,7 +16,6 @@ import { WizardInput, } from '../foundation.js'; import { updateNamingAction } from './foundation/actions.js'; -import { getDirections } from '../editors/singlelinediagram/sld-drawing.js'; const types: Partial> = { // standard @@ -51,61 +51,69 @@ const types: Partial> = { }; /** - * Finds Logical Node from IED based on Logical Node in the Substation SCL section. - * @param lNode - Logical Node in Substation SCL section. - * @returns - Logical Node in the IED SCL section. + * 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 getLNodefromIED(lNode: Element | null): Element | null { +function getLogicalNodeInstance(lNode: Element | null): Element | null { if (!lNode) return null; - const [iedName, ldInst, lnClass, lnInst, lnType] = [ + const [lnInst, lnClass, iedName, ldInst, prefix, lnType] = [ + 'lnInst', + 'lnClass', 'iedName', 'ldInst', - 'lnClass', - 'lnInst', + 'prefix', 'lnType', - ].map(attribute => lNode?.getAttribute(attribute)); - return (lNode?.getRootNode()).querySelector(`IED[name='${iedName}'] > AccessPoint > Server > LDevice[inst='${ldInst}'] > LN[inst='${lnInst}'][lnType='${lnType}'][lnClass='${lnClass}']`); + ].map(attribute => lNode?.getAttribute(attribute) || ''); + const iedSelector = [`IED[name="${iedName}"]`, 'IED']; + const lDevicePath = ['AccessPoint > Server']; + const lDeviceSelector = [ + `LDevice[inst="${ldInst}"] > LN[inst="${lnInst}"][lnType="${lnType}"][lnClass="${lnClass}"]`, + ]; + const lDevicePrefixSelector = [`[prefix="${prefix}"]`, ':not(prefix)']; + return lNode.ownerDocument.querySelector( + crossProduct( + iedSelector, + [' > '], + lDevicePath, + [' > '], + lDeviceSelector, + lDevicePrefixSelector + ) + .map(strings => strings.join('')) + .join(',') + ); } /** - * Finds data attribute by inspecting an IED's logical node or types. - * @param lNode - LNode within the IED section. - * @param doName - name for data object (e.g. SwTyp). - * @param sdName - id for sub data object. - * @param daName - name for data attribute (e.g. stVal). + * 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 getDataAttributeValue(lNode: Element, doName: string, sdName: string | undefined, daName: string): string | undefined { - const sdDef = sdName ? ` > SDI[name='${sdName}']` : ''; - const daInstantiated = lNode.querySelector(`DOI[name='${doName}']${sdDef} > DAI[name='${daName}'`); - if (daInstantiated) { - // data attribute is fully instantiated within a DOI/SDI, look for it within: DOI > ?SDI > DAI +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(); - } else { - const rootNode = lNode?.getRootNode(); - const lNodeType = lNode.getAttribute('type'); - const lnClass = lNode.getAttribute('lnClass'); - if (sdName) { - // definition to be found on the subdata type - // this code path has not been tested but may be of use elsewhere - const sdo = rootNode.querySelector(`DataTypeTemplates > LNodeType[id=${lNodeType}][class=${lnClass}] > SDO[name=${doName}]`); - if (sdo) { - const sdoRef = sdo.getAttribute('type'); - return rootNode.querySelector(`DataTypeTemplates > DOType[id=${sdoRef}] > DA[name='${daName}'] > Val`)?.innerHTML.trim(); - } - // definition missing - return undefined; - } else { - // definition must be on the data object type - const doObj = rootNode.querySelector(`DataTypeTemplates > LNodeType[id=${lNodeType}][class=${lnClass}] > DO[name=${doName}]`); - if (doObj) { - const doRef = doObj.getAttribute('type'); - return rootNode.querySelector(`DataTypeTemplates > DOType[id=${doRef}] > DA[name='${daName}'] > Val`)?.innerHTML.trim(); - } - // definition missing - return undefined; - } + const rootNode = lN?.ownerDocument; + const lNodeType = lN.getAttribute('type'); + const lnClass = lN.getAttribute('lnClass'); + // definition must be on the data object type + const doObj = rootNode.querySelector( + `DataTypeTemplates > LNodeType[id="${lNodeType}"][class="${lnClass}"] > DO[name="SwTyp"]` + ); + if (doObj) { + const doRef = doObj.getAttribute('type'); + return rootNode + .querySelector( + `DataTypeTemplates > DOType[id="${doRef}"] > DA[name="stVal"] > Val` + ) + ?.innerHTML.trim(); } + // definition missing + return undefined; } /** @@ -114,26 +122,27 @@ function getDataAttributeValue(lNode: Element, doName: string, sdName: string | * @returns if any terminal of the ConductingEquipment is grounded. */ function containsGroundedTerminal(condEq: Element): boolean { - return (Array.from(condEq.querySelectorAll('Terminal')) - .map(t => t.getAttribute('cNodeName')) - .includes('grounded') + return Array.from(condEq.querySelectorAll('Terminal')).some( + t => t.getAttribute('cNodeName') === 'grounded' ); } /** - * Looks to see if the Conducting Equipment contains an XSWI logical node. If so, check if the XSWI definition + * 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. - * @param condEq - SCL ConductingEquipment + * @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 lNode = getLNodefromIED(lNodeXSWI); - if (lNode) { - const swTypVal = getDataAttributeValue(lNode, 'SwTyp', undefined, 'stVal'); - return swTypVal ? ['Earthing Switch', 'High Speed Earthing Switch'].includes(swTypVal) : false; + const lNXSWI = condEq.querySelector('LNode[lnClass="XSWI"]'); + const lN = getLogicalNodeInstance(lNXSWI); + if (lN) { + const swTypVal = getSwitchTypeValue(lN); + return swTypVal + ? ['Earthing Switch', 'High Speed Earthing Switch'].includes(swTypVal) + : false; } else { - return false; + return false; } } @@ -143,7 +152,11 @@ function containsEarthSwitchDefinition(condEq: Element): boolean { * @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)))) { + if ( + containsGroundedTerminal(condEq) || + (condEq.getAttribute('type') === 'DIS' && + containsEarthSwitchDefinition(condEq)) + ) { // these checks only carried out for a three phase system return 'ERS'; } else { diff --git a/test/testfiles/conductingequipmentwizard.scd b/test/testfiles/conductingequipmentwizard.scd index 72d831cfc5..dc66846700 100644 --- a/test/testfiles/conductingequipmentwizard.scd +++ b/test/testfiles/conductingequipmentwizard.scd @@ -47,12 +47,21 @@ + + + + + + + + + @@ -123,7 +132,7 @@ - + diff --git a/test/unit/editors/substation/conductingequipmentwizard.test.ts b/test/unit/editors/substation/conductingequipmentwizard.test.ts index 34579e4859..73d506be77 100644 --- a/test/unit/editors/substation/conductingequipmentwizard.test.ts +++ b/test/unit/editors/substation/conductingequipmentwizard.test.ts @@ -28,7 +28,7 @@ describe('conductingequipmentwizard', () => { ).to.equal('ERS'); }); - it('has no grounded connectivityNodes but has an XSWI logicalNode and DOI:SWTyp > DAI:stVal defining an "Earthing Switch"', () => { + 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"]')! @@ -36,7 +36,7 @@ describe('conductingequipmentwizard', () => { ).to.equal('ERS'); }); - it('has no grounded connectivityNodes but has an XSWI logicalNode and DOI:SWTyp > DOType > DA:stVal defining an "Earthing Switch"', () => { + it('has no grounded connectivityNodes but has an XSWI LN and DOI:SWTyp > DOType > DA:stVal defining an "Earthing Switch"', () => { expect( typeStr( doc.querySelector('ConductingEquipment[name="ES269"]')! @@ -44,7 +44,7 @@ describe('conductingequipmentwizard', () => { ).to.equal('ERS'); }); - it('has no grounded connectivityNodes but has an XSWI logicalNode within SubEquipment and DOI:SWTyp > DAI:stVal defining an "Earthing Switch"', () => { + 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"]')! @@ -52,5 +52,13 @@ describe('conductingequipmentwizard', () => { ).to.equal('ERS'); }); + it('has no grounded connectivityNodes but has an IED with name "None" and an XSWI LN with a prefix and DOI:SWTyp > DAI:stVal defining an "Earthing Switch"', () => { + expect( + typeStr( + doc.querySelector('ConductingEquipment[name="ES289"]')! + ) + ).to.equal('ERS'); + }); + }); }); From 0679a6784c83ac7617ab819417da220186c0d3a5 Mon Sep 17 00:00:00 2001 From: Daniel Mulholland Date: Mon, 24 Jan 2022 13:00:47 +1300 Subject: [PATCH 11/13] Respond to review comments, correct errors --- src/wizards/conductingequipment.ts | 80 +++++++++++-------- test/testfiles/conductingequipmentwizard.scd | 34 +++++--- .../conductingequipmentwizard.test.ts | 32 +++----- 3 files changed, 78 insertions(+), 68 deletions(-) diff --git a/src/wizards/conductingequipment.ts b/src/wizards/conductingequipment.ts index 24c76217f9..3cfbe13bb0 100644 --- a/src/wizards/conductingequipment.ts +++ b/src/wizards/conductingequipment.ts @@ -57,34 +57,55 @@ const types: Partial> = { */ function getLogicalNodeInstance(lNode: Element | null): Element | null { if (!lNode) return null; - const [lnInst, lnClass, iedName, ldInst, prefix, lnType] = [ + const [lnInst, lnClass, iedName, ldInst, prefix] = [ 'lnInst', 'lnClass', 'iedName', 'ldInst', 'prefix', - 'lnType', - ].map(attribute => lNode?.getAttribute(attribute) || ''); + ].map(attribute => lNode?.getAttribute(attribute)); const iedSelector = [`IED[name="${iedName}"]`, 'IED']; const lDevicePath = ['AccessPoint > Server']; - const lDeviceSelector = [ - `LDevice[inst="${ldInst}"] > LN[inst="${lnInst}"][lnType="${lnType}"][lnClass="${lnClass}"]`, + const lNSelector = [ + `LDevice[inst="${ldInst}"] > LN[inst="${lnInst}"][lnClass="${lnClass}"]`, ]; - const lDevicePrefixSelector = [`[prefix="${prefix}"]`, ':not(prefix)']; + const lNPrefixSelector = + prefix && prefix !== '' + ? [`[prefix="${prefix}"]`] + : ['[prefix=""]', ':not(prefix)']; return lNode.ownerDocument.querySelector( crossProduct( iedSelector, [' > '], lDevicePath, [' > '], - lDeviceSelector, - lDevicePrefixSelector + lNSelector, + lNPrefixSelector ) .map(strings => strings.join('')) .join(',') ); } +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. @@ -92,28 +113,15 @@ function getLogicalNodeInstance(lNode: Element | null): Element | null { */ function getSwitchTypeValue(lN: Element): string | undefined { const daInstantiated = lN.querySelector( - 'DOI[name="SwTyp"] > DAI[name="stVal"' + 'DOI[name="SwTyp"] > DAI[name="stVal"]' ); // definition is on instantiated object - if (daInstantiated) + if (daInstantiated) { return daInstantiated.querySelector('Val')?.innerHTML.trim(); - const rootNode = lN?.ownerDocument; - const lNodeType = lN.getAttribute('type'); - const lnClass = lN.getAttribute('lnClass'); - // definition must be on the data object type - const doObj = rootNode.querySelector( - `DataTypeTemplates > LNodeType[id="${lNodeType}"][class="${lnClass}"] > DO[name="SwTyp"]` - ); - if (doObj) { - const doRef = doObj.getAttribute('type'); - return rootNode - .querySelector( - `DataTypeTemplates > DOType[id="${doRef}"] > DA[name="stVal"] > Val` - ) - ?.innerHTML.trim(); + // definition must be on the data object type + } else { + return getSwitchTypeValueFromDTT(lN); } - // definition missing - return undefined; } /** @@ -129,21 +137,23 @@ function containsGroundedTerminal(condEq: Element): boolean { /** * 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. + * 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 lNXSWI = condEq.querySelector('LNode[lnClass="XSWI"]'); - const lN = getLogicalNodeInstance(lNXSWI); + const lNodeXSWI = condEq.querySelector('LNode[lnClass="XSWI"]'); + const lN = getLogicalNodeInstance(lNodeXSWI); + let swTypVal; if (lN) { - const swTypVal = getSwitchTypeValue(lN); - return swTypVal - ? ['Earthing Switch', 'High Speed Earthing Switch'].includes(swTypVal) - : false; - } else { - return false; + swTypVal = getSwitchTypeValue(lN); + } else if (lNodeXSWI) { + swTypVal = getSwitchTypeValueFromDTT(lNodeXSWI); } + return swTypVal + ? ['Earthing Switch', 'High Speed Earthing Switch'].includes(swTypVal) + : false; } /** diff --git a/test/testfiles/conductingequipmentwizard.scd b/test/testfiles/conductingequipmentwizard.scd index dc66846700..abe6036a64 100644 --- a/test/testfiles/conductingequipmentwizard.scd +++ b/test/testfiles/conductingequipmentwizard.scd @@ -9,13 +9,15 @@ - + + + @@ -23,6 +25,7 @@ + @@ -31,6 +34,7 @@ + @@ -39,17 +43,19 @@ + + + - - - + + @@ -132,7 +138,7 @@ - + @@ -188,11 +194,7 @@ - - - Earthing Switch - - + @@ -233,6 +235,18 @@ + + + + + + + + + + + + diff --git a/test/unit/editors/substation/conductingequipmentwizard.test.ts b/test/unit/editors/substation/conductingequipmentwizard.test.ts index 73d506be77..db0cfdfffc 100644 --- a/test/unit/editors/substation/conductingequipmentwizard.test.ts +++ b/test/unit/editors/substation/conductingequipmentwizard.test.ts @@ -3,7 +3,6 @@ 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 () => { @@ -14,51 +13,38 @@ describe('conductingequipmentwizard', () => { it('has the first terminal grounded', () => { expect( - typeStr( - doc.querySelector('ConductingEquipment[name="ES239"]')! - ) + typeStr(doc.querySelector('ConductingEquipment[name="ES239"]')!) ).to.equal('ERS'); }); it('has the second terminal grounded', () => { expect( - typeStr( - doc.querySelector('ConductingEquipment[name="ES249"]')! - ) + 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"]')! - ) + typeStr(doc.querySelector('ConductingEquipment[name="ES259"]')!) ).to.equal('ERS'); }); - - it('has no grounded connectivityNodes but has an XSWI LN and DOI:SWTyp > DOType > DA:stVal defining an "Earthing Switch"', () => { + + 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"]')! - ) + 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"]')! - ) + typeStr(doc.querySelector('ConductingEquipment[name="ES279"]')!) ).to.equal('ERS'); }); - it('has no grounded connectivityNodes but has an IED with name "None" and an XSWI LN with a prefix and DOI:SWTyp > DAI:stVal defining an "Earthing Switch"', () => { + 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"]')! - ) + typeStr(doc.querySelector('ConductingEquipment[name="ES289"]')!) ).to.equal('ERS'); }); - }); }); From 6d1b96752b6535ecc626c957c7a0a91b12f71f7d Mon Sep 17 00:00:00 2001 From: Daniel Mulholland Date: Mon, 24 Jan 2022 13:18:49 +1300 Subject: [PATCH 12/13] Move ERS arm to LHS, starting from grounded side --- src/icons.ts | 126 +++++++++++++++++++++++++++------------------------ 1 file changed, 66 insertions(+), 60 deletions(-) diff --git a/src/icons.ts b/src/icons.ts index 0810d7ccd2..7d4bb1c862 100644 --- a/src/icons.ts +++ b/src/icons.ts @@ -455,66 +455,72 @@ export const voltageTransformerIcon = html``; export const earthSwitchIcon = html` - - - - -> - - + viewBox="0 0 25 25" + xmlns="http://www.w3.org/2000/svg" +> + + + + + + + `; export const generalConductingEquipmentIcon = html` Date: Mon, 24 Jan 2022 13:43:46 +1300 Subject: [PATCH 13/13] Add docstring --- src/wizards/conductingequipment.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/wizards/conductingequipment.ts b/src/wizards/conductingequipment.ts index 3cfbe13bb0..a584fa5958 100644 --- a/src/wizards/conductingequipment.ts +++ b/src/wizards/conductingequipment.ts @@ -87,6 +87,11 @@ function getLogicalNodeInstance(lNode: Element | null): Element | null { ); } +/** + * 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');