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 earthSwitchIcon = 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/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 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'); + }); + }); +});