Skip to content

Commit f510446

Browse files
JakobVogelsangca-d
andauthored
feat(wizards/sampledvaluecontrol): add create wizard (openscd#744)
* feat(wizards/sampledvaluecontrol): add create wizard * refactor(wizards/sampledvaluecontrol): render only falsy multicast checkbox The attribute `multicast` allows to select between 9-1 and 9-2 type sampled value stream. As 9-1 is deprecated and has not been used widely the setting can only be set from false to true not the other way around. To not confuse the user multicast checkbox is only shown when false * refactor(wizards/sampledvaluecontrol): limit sampledvalue data picker - lnClass TCTR and TVTR - data object cdc SAV - data attributes name instMag and q * fix(wizards/sampledvaluecontrol): create action for undefined multicast input * fix(wizard/sampledvaluecontrol): translation for missing access point * fix(wizards/sampledvaluecontrol): make use of the proper data picker for SampledValueControl blocks * test(wizards/sampledvaluecontrol): add unit tests * test(wizards/sampledvaluecontrol): add integration tests * test(wizards/sampledvaluecontrol): add integration tests * refactor(wizards/sampledvaluecontrol): add translation * refactor(wizards/sampledvaluecontrol): better wording * test(wizards): update smv control wizard snapshots * test(wizards): update smvcontrol wizard snapshots Co-authored-by: cad <[email protected]> Co-authored-by: Christian Dinkel <[email protected]>
1 parent 67f5ed4 commit f510446

File tree

15 files changed

+1637
-263
lines changed

15 files changed

+1637
-263
lines changed

src/translations/de.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export const de: Translations = {
6060
sampleRate: 'Abtastrate zu Telegram hinzufügen',
6161
security: 'Potentiel in Zukunft für z.B. digitale Signature',
6262
synchSourceId: 'Identität der Zeitquelle zu Telegram hinzufügen',
63+
SampledValueControl: 'Sampled Value Kontrollblock',
6364
iedName: 'Referenziertes IED',
6465
ldInst: 'Referenziertes logisches Gerät',
6566
prefix: 'Präfix des logischen Knotens',
@@ -541,6 +542,8 @@ export const de: Translations = {
541542
action: {
542543
addaddress: 'SMV bearbeitet ({{identity}})',
543544
},
545+
missingaccp:
546+
'AccessPoint is nicht verbunden. SMV kann nicht hinzugefügt werden.',
544547
},
545548
subscriber: {
546549
title: 'Subscriber Update',
@@ -618,6 +621,11 @@ export const de: Translations = {
618621
gsecontrol: {
619622
wizard: { location: 'Ablageort der GOOSE wählen' },
620623
},
624+
samvpledvaluecontrol: {
625+
wizard: {
626+
location: 'Ablageort des Select Sampled Value Control Block wählen',
627+
},
628+
},
621629
add: 'Hinzufügen',
622630
new: 'Neu',
623631
remove: 'Entfernen',

src/translations/en.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ export const en = {
5858
sampleRate: 'Add sample rate to SMV packet',
5959
security: 'Potential future use. e.g. digital signature',
6060
synchSourceId: 'Add sync source id to SMV packet',
61+
SampledValueControl: 'Sampled Value Control Block',
6162
iedName: 'Referenced IED',
6263
ldInst: 'Referenced Logical Device',
6364
prefix: 'Prefix of the Logical Node',
@@ -537,6 +538,7 @@ export const en = {
537538
action: {
538539
addaddress: 'Edit SMV ({{identity}})',
539540
},
541+
missingaccp: 'AccessPoint is not connected. SMV cannot be created.',
540542
},
541543
subscriber: {
542544
title: 'Subscriber update',
@@ -612,6 +614,9 @@ export const en = {
612614
gsecontrol: {
613615
wizard: { location: 'Select GOOSE Control Block Location' },
614616
},
617+
samvpledvaluecontrol: {
618+
wizard: { location: 'Select Sampled Value Control Block Location' },
619+
},
615620
add: 'Add',
616621
new: 'New',
617622
remove: 'Remove',

src/wizards/address.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ interface ContentOptions {
1414
attributes: Record<string, string | null>;
1515
}
1616

17-
export function contentGseWizard(content: ContentOptions): TemplateResult[] {
17+
export function contentGseOrSmvWizard(
18+
content: ContentOptions
19+
): TemplateResult[] {
1820
return [
1921
html`<mwc-formfield
2022
label="${translate('connectedap.wizard.addschemainsttype')}"

src/wizards/foundation/enums.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export const valKindEnum = ['Spec', 'Conf', 'RO', 'Set'];
5656

5757
export const smpModEnum = ['SmpPerPeriod', 'SmpPerSec', 'SecPerSmp'];
5858

59-
export const securityEnableEnum = [
59+
export const securityEnabledEnum = [
6060
'None',
6161
'Signature',
6262
'SignatureAndEncryption',

src/wizards/foundation/finder.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,61 @@ export function dataAttributePicker(server: Element): TemplateResult {
8888
.getTitle=${(path: string[]) => path[path.length - 1]}
8989
></finder-list>`;
9090
}
91+
92+
export function getSMVDataChildren(parent: Element): Element[] {
93+
if (parent.tagName === 'Server')
94+
return Array.from(parent.children).filter(
95+
child =>
96+
child.tagName === 'LDevice' &&
97+
child.querySelector('LN[lnClass="TCTR"],LN[lnClass="TVTR"]')
98+
);
99+
100+
if (parent.tagName === 'LDevice')
101+
return Array.from(parent.children).filter(
102+
child =>
103+
child.tagName === 'LN' &&
104+
(child.getAttribute('lnClass') === 'TCTR' ||
105+
child.getAttribute('lnClass') === 'TVTR')
106+
);
107+
108+
const id =
109+
parent.tagName === 'LN' || parent.tagName === 'LN0'
110+
? parent.getAttribute('lnType')
111+
: parent.getAttribute('type');
112+
const dataType = parent.ownerDocument.querySelector(
113+
`LNodeType[id="${id}"], DOType[id="${id}"], DAType[id="${id}"]`
114+
);
115+
116+
if (!dataType) return [];
117+
118+
if (dataType?.tagName === 'LNodeType') {
119+
return Array.from(dataType.querySelectorAll('DO') ?? []).filter(dO =>
120+
dO.ownerDocument.querySelector(
121+
`DOType[id="${dO.getAttribute('type')}"][cdc="SAV"]`
122+
)
123+
);
124+
}
125+
126+
if (dataType?.tagName === 'DOType') {
127+
return Array.from(dataType.querySelectorAll('DA') ?? []).filter(
128+
dA =>
129+
dA.getAttribute('name') === 'instMag' || dA.getAttribute('name') === 'q'
130+
);
131+
}
132+
133+
return Array.from(
134+
parent.ownerDocument.querySelectorAll(
135+
`LNodeType[id="${id}"] > DO, DOType[id="${id}"] > SDO, DOType[id="${id}"] > DA, DAType[id="${id}"] > BDA`
136+
)
137+
);
138+
}
139+
140+
export function sampledValueDataPicker(server: Element): TemplateResult {
141+
return html`<finder-list
142+
multi
143+
paths=${JSON.stringify([['Server: ' + identity(server)]])}
144+
.read=${getReader(server, getSMVDataChildren)}
145+
.getDisplayString=${getDisplayString}
146+
.getTitle=${(path: string[]) => path[path.length - 1]}
147+
></finder-list>`;
148+
}

src/wizards/gse.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {
1515
WizardActor,
1616
WizardInputElement,
1717
} from '../foundation.js';
18-
import { contentGseWizard, updateAddress } from './address.js';
18+
import { contentGseOrSmvWizard, updateAddress } from './address.js';
1919

2020
export function getMTimeAction(
2121
type: 'MinTime' | 'MaxTime',
@@ -146,7 +146,7 @@ export function editGseWizard(element: Element): Wizard {
146146
action: updateGSEAction(element),
147147
},
148148
content: [
149-
...contentGseWizard({ hasInstType, attributes }),
149+
...contentGseOrSmvWizard({ hasInstType, attributes }),
150150
html`<wizard-textfield
151151
label="MinTime"
152152
.maybeValue=${minTime}

src/wizards/gsecontrol.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ import {
3535
import { maxLength, patterns } from './foundation/limits.js';
3636
import { editDataSetWizard } from './dataset.js';
3737
import { editGseWizard } from './gse.js';
38-
import { securityEnableEnum } from './foundation/enums.js';
38+
import { securityEnabledEnum } from './foundation/enums.js';
3939
import { dataAttributePicker, iEDPicker } from './foundation/finder.js';
4040
import { FinderList } from '../finder-list.js';
4141
import { newFCDA } from './fcda.js';
@@ -45,7 +45,7 @@ import {
4545
uniqueAppId,
4646
uniqueMacAddress,
4747
} from './foundation/scl.js';
48-
import { contentGseWizard, createAddressElement } from './address.js';
48+
import { contentGseOrSmvWizard, createAddressElement } from './address.js';
4949

5050
interface ContentOptions {
5151
name: string | null;
@@ -119,7 +119,7 @@ export function contentGseControlWizard(
119119
nullable
120120
required
121121
helper="${translate('scl.securityEnable')}"
122-
>${securityEnableEnum.map(
122+
>${securityEnabledEnum.map(
123123
type => html`<mwc-list-item value="${type}">${type}</mwc-list-item>`
124124
)}</wizard-select
125125
>`,
@@ -267,7 +267,7 @@ export function createGseControlWizard(ln0OrLn: Element): Wizard {
267267
{
268268
title: get('wizard.title.add', { tagName: 'GSE' }),
269269
content: [
270-
...contentGseWizard({ hasInstType, attributes }),
270+
...contentGseOrSmvWizard({ hasInstType, attributes }),
271271
html`<wizard-textfield
272272
label="MinTime"
273273
.maybeValue=${minTime}

0 commit comments

Comments
 (0)