From 8fab275b1634cba71e75cfa88b20f1906018b6bf Mon Sep 17 00:00:00 2001 From: Christopher Lepski Date: Tue, 1 Oct 2024 15:39:17 +0200 Subject: [PATCH 1/5] feat: Add 104 export menu plugin --- packages/compas-open-scd/public/js/plugins.js | 9 ++ packages/plugins/src/menu/Export104.ts | 74 ++++++++++ .../plugins/src/menu/export104/foundation.ts | 134 ++++++++++++++++++ 3 files changed, 217 insertions(+) create mode 100644 packages/plugins/src/menu/Export104.ts create mode 100644 packages/plugins/src/menu/export104/foundation.ts diff --git a/packages/compas-open-scd/public/js/plugins.js b/packages/compas-open-scd/public/js/plugins.js index eecda32d5c..748fad2f6b 100644 --- a/packages/compas-open-scd/public/js/plugins.js +++ b/packages/compas-open-scd/public/js/plugins.js @@ -336,4 +336,13 @@ export const officialPlugins = [ requireDoc: true, position: 'middle', }, + { + name: 'Export IEC 104 CSV', + src: '/plugins/src/menu/Export104.js', + icon: 'sim_card_download', + default: false, + kind: 'menu', + requireDoc: true, + position: 'middle', + }, ]; diff --git a/packages/plugins/src/menu/Export104.ts b/packages/plugins/src/menu/Export104.ts new file mode 100644 index 0000000000..5982e736f7 --- /dev/null +++ b/packages/plugins/src/menu/Export104.ts @@ -0,0 +1,74 @@ +import { LitElement, property } from 'lit-element'; +import { stringify } from 'csv-stringify/browser/esm/sync'; + +import { extractAllSignal104Data, Signal104 } from './export104/foundation.js'; + + + +export default class Export104 extends LitElement { + @property({ attribute: false }) doc!: XMLDocument; + @property() docName!: string; + + private readonly csvHeaders = [ + 'Id', + 'Name', + 'Signal Number', + 'mIOA', + 'cIOA' + ]; + + async run(): Promise { + + const allSignal104Data = extractAllSignal104Data(this.doc); + const csvLines = this.generateCsvLines(allSignal104Data); + + const csvContent = stringify(csvLines, { + header: true, + columns: this.csvHeaders, + }); + const csvBlob = new Blob([csvContent], { + type: 'text/csv', + }); + + this.downloadCsv(csvBlob); + + console.log(csvLines); + + console.log('Export104', allSignal104Data); + } + + private generateCsvLines(allSignal104Data: Signal104[]): string[][] { + const lines: string[][] = []; + + for(const signal104Data of allSignal104Data) { + const line = [ + '', + signal104Data.name ?? '', + signal104Data.signalNumber ?? '', + ]; + + if (signal104Data.isMonitorSignal) { + line.push(signal104Data.ioa ?? '', ''); + } else { + line.push('', signal104Data.ioa ?? ''); + } + + lines.push(line); + } + + return lines; + } + + private downloadCsv(csvBlob: Blob): void { + const a = document.createElement('a'); + a.download = this.docName + '-104-signals.csv'; + a.href = URL.createObjectURL(csvBlob); + a.dataset.downloadurl = ['text/csv', a.download, a.href].join(':'); + a.style.display = 'none'; + + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(a.href); + } +} \ No newline at end of file diff --git a/packages/plugins/src/menu/export104/foundation.ts b/packages/plugins/src/menu/export104/foundation.ts new file mode 100644 index 0000000000..32b6600a71 --- /dev/null +++ b/packages/plugins/src/menu/export104/foundation.ts @@ -0,0 +1,134 @@ +// TODO: Share with 104 editor plugin? +export const PROTOCOL_104_PRIVATE = 'IEC_60870_5_104'; + +export interface Signal104 { + name: string | null; + signalNumber: string | null; + isMonitorSignal: boolean; + ioa: string | null; + ti: string | null; +} + +enum SignalType { + Monitor, + Control, + Unknown +} + +const private104Selector = `Private[type="${PROTOCOL_104_PRIVATE}"]`; + +export function extractAllSignal104Data(doc: XMLDocument): Signal104[] { + const allSignal104Data: Signal104[] = []; + const address104Elements = doc.querySelectorAll(`${private104Selector} > Address`); + + address104Elements.forEach((addressElement) => { + const signal104Data = extractSignal104Data(addressElement, doc); + + if (signal104Data) { + allSignal104Data.push(signal104Data); + } + }); + + console.log(address104Elements); + + return allSignal104Data; +} + +function extractSignal104Data(addressElement: Element, doc: XMLDocument): Signal104 | null { + const ti = addressElement.getAttribute('ti'); + const ioa = addressElement.getAttribute('ioa'); + + // By convention the last four digits of the ioa are the signalnumber, see https://github.com/com-pas/compas-open-scd/issues/334 + if (ti === null || ioa === null || ioa.length < 4) { + console.log('No ti, io or io too short'); + return null; + } + const signalNumber = ioa.slice(-4); + + const signalType = getSignalType(ti); + if (signalType === SignalType.Unknown) { + console.log('Unknown signal type'); + return null; + } + const isMonitorSignal = signalType === SignalType.Monitor; + + // TODO: Doi desc, Bay name, VoltageLevel name, Substation name + + addressElement.parentElement; + const parentDOI = addressElement.closest('DOI'); + + if (!parentDOI) { + console.log('No parent DOI'); + return null; + } + + const doiDesc = parentDOI.getAttribute('desc'); + const parentIED = parentDOI.closest('IED'); + if (!parentIED) { + console.log('No parent IED'); + return null; + } + + const iedName = parentIED.getAttribute('name'); + + const lNodeQuery = `Substation > VoltageLevel > Bay LNode[iedName="${iedName}"]`; + console.log(lNodeQuery); + const parentLNode = doc.querySelector(lNodeQuery); + + if (!parentLNode) { + console.log('No parent LNode'); + return null; + } + + const parentBay = parentLNode.closest('Bay'); + + if (!parentBay) { + console.log('No parent Bay'); + return null; + } + + const bayName = parentBay.getAttribute('name'); + const parentVoltageLevel = parentBay.closest('VoltageLevel'); + + if (!parentVoltageLevel) { + console.log('No parent VL'); + return null; + } + + const voltageLevelName = parentVoltageLevel.getAttribute('name'); + const parentSubstation = parentVoltageLevel.closest('Substation'); + + if (!parentSubstation) { + console.log('No parent Substation'); + return null; + } + + const substationName = parentSubstation.getAttribute('name'); + + const name = `${substationName}${voltageLevelName}${bayName}${doiDesc}`; + + return { + name, + signalNumber, + isMonitorSignal, + ti, + ioa + } +} + +// For signal classification details see https://github.com/com-pas/compas-open-scd/issues/334 +function getSignalType(tiString: string): SignalType { + const ti = parseInt(tiString); + + if (isNaN(ti)) { + return SignalType.Unknown; + } + + if ((ti >= 1 && ti <= 21) || (ti >= 30 && ti <= 40)) { + return SignalType.Monitor; + } else if ((ti >= 45 && ti <= 51) || (ti >= 58 && ti <= 64)) { + return SignalType.Control; + } else { + return SignalType.Unknown; + } +} From ee4f8f6d4f9d1238fa9575098bd2a63606447ffe Mon Sep 17 00:00:00 2001 From: Christopher Lepski Date: Wed, 2 Oct 2024 14:44:19 +0200 Subject: [PATCH 2/5] feat: Adjust bay searching logic and add log events for errors --- packages/plugins/src/menu/Export104.ts | 25 ++++-- .../plugins/src/menu/export104/foundation.ts | 87 +++++++++---------- 2 files changed, 60 insertions(+), 52 deletions(-) diff --git a/packages/plugins/src/menu/Export104.ts b/packages/plugins/src/menu/Export104.ts index 5982e736f7..288453a602 100644 --- a/packages/plugins/src/menu/Export104.ts +++ b/packages/plugins/src/menu/Export104.ts @@ -1,5 +1,6 @@ import { LitElement, property } from 'lit-element'; import { stringify } from 'csv-stringify/browser/esm/sync'; +import { newLogEvent } from '@openscd/core/foundation/deprecated/history.js'; import { extractAllSignal104Data, Signal104 } from './export104/foundation.js'; @@ -18,9 +19,19 @@ export default class Export104 extends LitElement { ]; async run(): Promise { + const { signals, errors } = extractAllSignal104Data(this.doc); - const allSignal104Data = extractAllSignal104Data(this.doc); - const csvLines = this.generateCsvLines(allSignal104Data); + errors.forEach((error) => this.logWarning(error)); + + if (signals.length === 0) { + this.dispatchEvent(newLogEvent({ + kind: 'info', + title: 'Export 104 found no signals', + })); + return; + } + + const csvLines = this.generateCsvLines(signals); const csvContent = stringify(csvLines, { header: true, @@ -31,10 +42,14 @@ export default class Export104 extends LitElement { }); this.downloadCsv(csvBlob); + } - console.log(csvLines); - - console.log('Export104', allSignal104Data); + private logWarning(errorMessage: string): void { + this.dispatchEvent(newLogEvent({ + kind: 'warning', + title: 'Export 104 found invalid signal', + message: errorMessage, + })); } private generateCsvLines(allSignal104Data: Signal104[]): string[][] { diff --git a/packages/plugins/src/menu/export104/foundation.ts b/packages/plugins/src/menu/export104/foundation.ts index 32b6600a71..362f7c2d8f 100644 --- a/packages/plugins/src/menu/export104/foundation.ts +++ b/packages/plugins/src/menu/export104/foundation.ts @@ -9,6 +9,11 @@ export interface Signal104 { ti: string | null; } +interface ExtractSignal104Result { + signal: Signal104 | null; + error?: string; +} + enum SignalType { Monitor, Control, @@ -17,90 +22,67 @@ enum SignalType { const private104Selector = `Private[type="${PROTOCOL_104_PRIVATE}"]`; -export function extractAllSignal104Data(doc: XMLDocument): Signal104[] { - const allSignal104Data: Signal104[] = []; +export function extractAllSignal104Data(doc: XMLDocument): { signals: Signal104[], errors: string[] } { + const signals: Signal104[] = []; + const errors: string[] = []; const address104Elements = doc.querySelectorAll(`${private104Selector} > Address`); address104Elements.forEach((addressElement) => { - const signal104Data = extractSignal104Data(addressElement, doc); + const signal104Result = extractSignal104Data(addressElement, doc); - if (signal104Data) { - allSignal104Data.push(signal104Data); + if (signal104Result.error) { + errors.push(signal104Result.error); + } else { + signals.push(signal104Result.signal!); } }); - console.log(address104Elements); - - return allSignal104Data; + return { signals, errors }; } -function extractSignal104Data(addressElement: Element, doc: XMLDocument): Signal104 | null { +function extractSignal104Data(addressElement: Element, doc: XMLDocument): ExtractSignal104Result { const ti = addressElement.getAttribute('ti'); const ioa = addressElement.getAttribute('ioa'); // By convention the last four digits of the ioa are the signalnumber, see https://github.com/com-pas/compas-open-scd/issues/334 if (ti === null || ioa === null || ioa.length < 4) { - console.log('No ti, io or io too short'); - return null; + return { signal: null, error: `ti or ioa are missing or ioa is less than 4 digits, ti: ${ti}, ioa: ${ioa}` }; } - const signalNumber = ioa.slice(-4); + const { signalNumber, bayName } = splitIoa(ioa); const signalType = getSignalType(ti); if (signalType === SignalType.Unknown) { - console.log('Unknown signal type'); - return null; + return { signal: null, error: `Unknown signal type for ti: ${ti}, ioa: ${ioa}` }; } const isMonitorSignal = signalType === SignalType.Monitor; - // TODO: Doi desc, Bay name, VoltageLevel name, Substation name - addressElement.parentElement; const parentDOI = addressElement.closest('DOI'); if (!parentDOI) { - console.log('No parent DOI'); - return null; + return { signal: null, error: `No parent DOI found for address with ioa: ${ioa}` }; } const doiDesc = parentDOI.getAttribute('desc'); - const parentIED = parentDOI.closest('IED'); - if (!parentIED) { - console.log('No parent IED'); - return null; - } - const iedName = parentIED.getAttribute('name'); - - const lNodeQuery = `Substation > VoltageLevel > Bay LNode[iedName="${iedName}"]`; - console.log(lNodeQuery); - const parentLNode = doc.querySelector(lNodeQuery); - - if (!parentLNode) { - console.log('No parent LNode'); - return null; - } - - const parentBay = parentLNode.closest('Bay'); + const parentBayQuery = `:root > Substation > VoltageLevel > Bay[name="${bayName}"]`; + const parentBay = doc.querySelector(parentBayQuery); if (!parentBay) { - console.log('No parent Bay'); - return null; + return { signal: null, error: `No bay found with bayname: ${bayName} for ioa ${ioa}` }; } - const bayName = parentBay.getAttribute('name'); const parentVoltageLevel = parentBay.closest('VoltageLevel'); if (!parentVoltageLevel) { - console.log('No parent VL'); - return null; + return { signal: null, error: `No parent voltage level found for bay ${bayName} for ioa ${ioa}` }; } const voltageLevelName = parentVoltageLevel.getAttribute('name'); const parentSubstation = parentVoltageLevel.closest('Substation'); if (!parentSubstation) { - console.log('No parent Substation'); - return null; + return { signal: null, error: `No parent substation found for voltage level ${voltageLevelName} for ioa ${ioa}` }; } const substationName = parentSubstation.getAttribute('name'); @@ -108,11 +90,13 @@ function extractSignal104Data(addressElement: Element, doc: XMLDocument): Signal const name = `${substationName}${voltageLevelName}${bayName}${doiDesc}`; return { - name, - signalNumber, - isMonitorSignal, - ti, - ioa + signal: { + name, + signalNumber, + isMonitorSignal, + ti, + ioa + } } } @@ -132,3 +116,12 @@ function getSignalType(tiString: string): SignalType { return SignalType.Unknown; } } + +// By Alliander convention the last four digits of the ioa are the signalnumber and the rest is the bay number +// And every bay name consists of "V" + bay number +function splitIoa(ioa: string): { signalNumber: string, bayName: string } { + const signalNumber = ioa.slice(-4); + const bayName = `V${ioa.slice(0, -4)}`; + + return { signalNumber, bayName }; +} From b552a718c8d0a4ab9df5ed2218be370a38d42cd5 Mon Sep 17 00:00:00 2001 From: Christopher Lepski Date: Mon, 7 Oct 2024 14:17:18 +0200 Subject: [PATCH 3/5] test: Add unit tests --- .../test/testfiles/export104/export104.scd | 897 ++++++++++++++++++ .../unit/menu/104Export/foundation.test.ts | 63 ++ 2 files changed, 960 insertions(+) create mode 100644 packages/plugins/test/testfiles/export104/export104.scd create mode 100644 packages/plugins/test/unit/menu/104Export/foundation.test.ts diff --git a/packages/plugins/test/testfiles/export104/export104.scd b/packages/plugins/test/testfiles/export104/export104.scd new file mode 100644 index 0000000000..1941595a3f --- /dev/null +++ b/packages/plugins/test/testfiles/export104/export104.scd @@ -0,0 +1,897 @@ + + +
+ + + 110 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

192.128.0.1

+

255.255.255.0

+

8

+

12

+

30

+

15

+

10

+

20

+

192.128.0.2

+

255.255.255.0

+

8

+

12

+

30

+

15

+

10

+

20

+

controlled-station

+
+
+ +
+

192.128.0.10

+

255.255.255.0

+

8

+

12

+

30

+

15

+

10

+

20

+

controlled-station

+
+
+ +
+

192.128.0.11

+

255.255.255.0

+

8

+

12

+

30

+

15

+

10

+

20

+

controlled-station

+
+
+
+ + +
+

192.128.0.1

+

255.255.255.0

+

8

+

12

+

30

+

15

+

10

+

20

+

192.128.0.2

+

255.255.255.0

+

8

+

12

+

30

+

15

+

10

+

20

+

controlled-station

+
+
+ +
+

192.128.0.10

+

255.255.255.0

+

8

+

12

+

30

+

15

+

10

+

20

+

controlled-station

+
+
+ +
+

192.128.0.11

+

255.255.255.0

+

8

+

12

+

30

+

15

+

10

+

20

+

controlled-station

+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + direct-with-normal-security + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IEC 61850-7-4:2007B2 + + + + + MyExt:2022A1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + status-only + direct-with-normal-security + sbo-with-normal-security + direct-with-enhanced-security + sbo-with-enhanced-security + + + unknown + forward + backward + both + + + unknown + forward + backward + + + y + z + a + f + p + n + µ + m + c + d + + da + h + k + M + G + T + P + E + Z + Y + + + not-supported + bay-control + station-control + remote-control + automatic-bay + automatic-station + automatic-remote + maintenance + process + + + pos-neg-zero + dir-quad-zero + + + MS + PER_CYCLE + CYCLE + DAY + WEEK + MONTH + YEAR + EXTERNAL + + + unknown + critical + major + minor + warning + + + + m + kg + s + A + K + mol + cd + deg + rad + sr + Gy + Bq + °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 + Watts + 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 + Ohm/m + percent/s + + + on + blocked + test + test/blocked + off + + + Ok + Warning + Alarm + + + diff --git a/packages/plugins/test/unit/menu/104Export/foundation.test.ts b/packages/plugins/test/unit/menu/104Export/foundation.test.ts new file mode 100644 index 0000000000..e166260452 --- /dev/null +++ b/packages/plugins/test/unit/menu/104Export/foundation.test.ts @@ -0,0 +1,63 @@ +import { expect } from '@open-wc/testing'; + +import { extractAllSignal104Data } from '../../../../src/menu/Export104/foundation.js'; + +describe('Export104 foundation', () => { + let doc: XMLDocument; + + beforeEach(async () => { + doc = await fetch('/test/testfiles/export104/export104.scd') + .then(response => response.text()) + .then(str => new DOMParser().parseFromString(str, 'application/xml')); + }); + + it('should extract all signal 104 data', () => { + const { signals, errors } = extractAllSignal104Data(doc); + + const expectedSignals = [ + { + name: 'S1F2V105Control1', + signalNumber: '3001', + isMonitorSignal: false, + ioa: '1053001', + ti: '50' + }, + { + name: 'S1F2V105Control2', + signalNumber: '3002', + isMonitorSignal: false, + ioa: '1053002', + ti: '64' + }, + { + name: 'S1F1V104Behavior', + signalNumber: '2001', + isMonitorSignal: true, + ioa: '1042001', + ti: '35' + }, + { + name: 'S1F1V104Behavior', + signalNumber: '2002', + isMonitorSignal: true, + ioa: '1042002', + ti: '35' + }, + { + name: 'S1F1V103Behavior', + signalNumber: '1003', + isMonitorSignal: true, + ioa: '1031003', + ti: '35' + }, + ]; + + const expectedErrors = [ + 'Unknown signal type for ti: 25, ioa: 1031001', + 'ti or ioa are missing or ioa is less than 4 digits, ti: 35, ioa: 103', + ]; + + expect(errors).to.deep.equal(expectedErrors); + expect(signals).to.deep.equal(expectedSignals); + }); +}); From 5d35235af47256ec0463ae5b0a043eaddb750016 Mon Sep 17 00:00:00 2001 From: Christopher Lepski Date: Mon, 7 Oct 2024 14:50:10 +0200 Subject: [PATCH 4/5] chore: Add translations --- packages/compas-open-scd/src/translations/de.ts | 12 ++++++++++++ packages/compas-open-scd/src/translations/en.ts | 12 ++++++++++++ packages/plugins/src/menu/Export104.ts | 5 +++-- packages/plugins/src/menu/export104/foundation.ts | 15 ++++++++------- 4 files changed, 35 insertions(+), 9 deletions(-) diff --git a/packages/compas-open-scd/src/translations/de.ts b/packages/compas-open-scd/src/translations/de.ts index 7da88c94ae..9a959590a9 100644 --- a/packages/compas-open-scd/src/translations/de.ts +++ b/packages/compas-open-scd/src/translations/de.ts @@ -537,6 +537,18 @@ export const de: Translations = { scaleMultiplierHelper: '???', scaleOffsetHelper: '???', }, + export: { + noSignalsFound: 'Export 104 hat keine Signale gefunden', + invalidSignalWarning: 'Export 104 hat ein ungültiges Signal gefunden', + errors: { + tiOrIoaInvalid: 'ti or ioa fehlen oder ioa hat weniger als 4 Zeichen, ti: "{{ ti }}", ioa: "{{ ioa }}"', + unknownSignalType: 'Unbekannter Signaltyp für ti: "{{ ti }}", ioa: "{{ ioa }}"', + noDoi: 'Es wurde kein Eltern DOI Element gefunden für ioa: "{{ ioa }}"', + noBay: 'Es wurde kein Bay Element mit dem Namen "{{ bayName }}" für ioa: "{{ ioa }}" gefunden', + noVoltageLevel: 'Es wurde kein VoltageLevel Element für Bay "{{ bayName }}" gefunden für ioa "{{ ioa }}"', + noSubstation: 'Es wurde kein Substation Element gefunden für VoltageLevel "{{ voltageLevelName }}" für ioa "{{ ioa }}"' + } + } }, 'compare-ied': { selectProjectTitle: 'Lade IEDs aus Vorlage', diff --git a/packages/compas-open-scd/src/translations/en.ts b/packages/compas-open-scd/src/translations/en.ts index 7c7cab0efb..ad06ff3975 100644 --- a/packages/compas-open-scd/src/translations/en.ts +++ b/packages/compas-open-scd/src/translations/en.ts @@ -536,6 +536,18 @@ export const en = { scaleMultiplierHelper: 'Scale Multiplier', scaleOffsetHelper: 'Scale Offset', }, + export: { + noSignalsFound: 'Export 104 found no signals', + invalidSignalWarning: 'Export 104 found invalid signal', + errors: { + tiOrIoaInvalid: 'ti or ioa are missing or ioa is less than 4 digits, ti: "{{ ti }}", ioa: "{{ ioa }}"', + unknownSignalType: 'Unknown signal type for ti: "{{ ti }}", ioa: "{{ ioa }}"', + noDoi: 'No parent DOI found for address with ioa: "{{ ioa }}"', + noBay: 'No Bay found bayname: "{{ bayName }}" for address with ioa: "{{ ioa }}"', + noVoltageLevel: 'No parent voltage level found for bay "{{ bayName }}" for ioa "{{ ioa }}"', + noSubstation: 'No parent substation found for voltage level "{{ voltageLevelName }}" for ioa "{{ ioa }}"' + } + } }, 'compare-ied': { selectProjectTitle: 'Select template project to Compare IED with', diff --git a/packages/plugins/src/menu/Export104.ts b/packages/plugins/src/menu/Export104.ts index 288453a602..9203cf503a 100644 --- a/packages/plugins/src/menu/Export104.ts +++ b/packages/plugins/src/menu/Export104.ts @@ -3,6 +3,7 @@ import { stringify } from 'csv-stringify/browser/esm/sync'; import { newLogEvent } from '@openscd/core/foundation/deprecated/history.js'; import { extractAllSignal104Data, Signal104 } from './export104/foundation.js'; +import { get } from 'lit-translate'; @@ -26,7 +27,7 @@ export default class Export104 extends LitElement { if (signals.length === 0) { this.dispatchEvent(newLogEvent({ kind: 'info', - title: 'Export 104 found no signals', + title: get('protocol104.export.noSignalsFound'), })); return; } @@ -47,7 +48,7 @@ export default class Export104 extends LitElement { private logWarning(errorMessage: string): void { this.dispatchEvent(newLogEvent({ kind: 'warning', - title: 'Export 104 found invalid signal', + title: get('protocol104.export.invalidSignalWarning'), message: errorMessage, })); } diff --git a/packages/plugins/src/menu/export104/foundation.ts b/packages/plugins/src/menu/export104/foundation.ts index 362f7c2d8f..7198161ba2 100644 --- a/packages/plugins/src/menu/export104/foundation.ts +++ b/packages/plugins/src/menu/export104/foundation.ts @@ -1,4 +1,5 @@ -// TODO: Share with 104 editor plugin? +import { get } from "lit-translate"; + export const PROTOCOL_104_PRIVATE = 'IEC_60870_5_104'; export interface Signal104 { @@ -46,13 +47,13 @@ function extractSignal104Data(addressElement: Element, doc: XMLDocument): Extrac // By convention the last four digits of the ioa are the signalnumber, see https://github.com/com-pas/compas-open-scd/issues/334 if (ti === null || ioa === null || ioa.length < 4) { - return { signal: null, error: `ti or ioa are missing or ioa is less than 4 digits, ti: ${ti}, ioa: ${ioa}` }; + return { signal: null, error: get('protocol104.export.errors.tiOrIoaInvalid', { ti: ti ?? '', ioa: ioa ?? '' }) }; } const { signalNumber, bayName } = splitIoa(ioa); const signalType = getSignalType(ti); if (signalType === SignalType.Unknown) { - return { signal: null, error: `Unknown signal type for ti: ${ti}, ioa: ${ioa}` }; + return { signal: null, error: get('protocol104.export.errors.unknownSignalType', { ti: ti ?? '', ioa: ioa ?? '' }) }; } const isMonitorSignal = signalType === SignalType.Monitor; @@ -60,7 +61,7 @@ function extractSignal104Data(addressElement: Element, doc: XMLDocument): Extrac const parentDOI = addressElement.closest('DOI'); if (!parentDOI) { - return { signal: null, error: `No parent DOI found for address with ioa: ${ioa}` }; + return { signal: null, error: get('protocol104.export.errors.noDoi', { ioa: ioa ?? '' }) }; } const doiDesc = parentDOI.getAttribute('desc'); @@ -69,20 +70,20 @@ function extractSignal104Data(addressElement: Element, doc: XMLDocument): Extrac const parentBay = doc.querySelector(parentBayQuery); if (!parentBay) { - return { signal: null, error: `No bay found with bayname: ${bayName} for ioa ${ioa}` }; + return { signal: null, error: get('protocol104.export.errors.noBay', { bayName, ioa: ioa ?? '' }) }; } const parentVoltageLevel = parentBay.closest('VoltageLevel'); if (!parentVoltageLevel) { - return { signal: null, error: `No parent voltage level found for bay ${bayName} for ioa ${ioa}` }; + return { signal: null, error: get('protocol104.export.errors.noVoltageLevel', { bayName, ioa: ioa ?? '' }) }; } const voltageLevelName = parentVoltageLevel.getAttribute('name'); const parentSubstation = parentVoltageLevel.closest('Substation'); if (!parentSubstation) { - return { signal: null, error: `No parent substation found for voltage level ${voltageLevelName} for ioa ${ioa}` }; + return { signal: null, error: get('protocol104.export.errors.noSubstation', { voltageLevelName: voltageLevelName ?? '', ioa: ioa ?? '' }) }; } const substationName = parentSubstation.getAttribute('name'); From 36f1e72ec8b57d5c3d0f770865eb5ee64fa7a8e0 Mon Sep 17 00:00:00 2001 From: Christopher Lepski Date: Mon, 7 Oct 2024 15:02:00 +0200 Subject: [PATCH 5/5] test: Fix test --- packages/plugins/test/unit/menu/104Export/foundation.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/plugins/test/unit/menu/104Export/foundation.test.ts b/packages/plugins/test/unit/menu/104Export/foundation.test.ts index e166260452..ee1411f294 100644 --- a/packages/plugins/test/unit/menu/104Export/foundation.test.ts +++ b/packages/plugins/test/unit/menu/104Export/foundation.test.ts @@ -53,8 +53,8 @@ describe('Export104 foundation', () => { ]; const expectedErrors = [ - 'Unknown signal type for ti: 25, ioa: 1031001', - 'ti or ioa are missing or ioa is less than 4 digits, ti: 35, ioa: 103', + '[protocol104.export.errors.unknownSignalType]', + '[protocol104.export.errors.tiOrIoaInvalid]', ]; expect(errors).to.deep.equal(expectedErrors);