Skip to content

Commit b814c00

Browse files
fix(editor/subscriber): make sure to add all mendatory attributes to ExtRef
1 parent a9bad36 commit b814c00

File tree

10 files changed

+1974
-367
lines changed

10 files changed

+1974
-367
lines changed

src/editors/subscription/foundation.ts

Lines changed: 118 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { css, LitElement, query } from 'lit-element';
2-
import { compareNames } from '../../foundation.js';
2+
import { compareNames, createElement } from '../../foundation.js';
3+
import { getFcdaReferences } from '../../foundation/ied.js';
34

45
export enum View {
56
PUBLISHER,
@@ -47,6 +48,122 @@ export function newIEDSelectEvent(
4748
});
4849
}
4950

51+
export function existExtRef(parentInputs: Element, fcda: Element): boolean {
52+
const iedName = fcda.closest('IED')?.getAttribute('name');
53+
if (!iedName) return false;
54+
55+
return !!parentInputs.querySelector(
56+
`ExtRef[iedName=${iedName}]` + `${getFcdaReferences(fcda)}`
57+
);
58+
}
59+
60+
export function canCreateValidExtRef(
61+
fcda: Element,
62+
controlBlock: Element | undefined
63+
): boolean {
64+
const iedName = fcda.closest('IED')?.getAttribute('name');
65+
const [ldInst, lnClass, lnInst, doName] = [
66+
'ldInst',
67+
'lnClass',
68+
'lnInst',
69+
'doName',
70+
].map(attr => fcda.getAttribute(attr));
71+
72+
if (!iedName || !ldInst || !lnClass || !lnInst || !doName) return false;
73+
74+
if (controlBlock === undefined) return true; //for serviceType `Poll`
75+
76+
const srcLDInst = controlBlock.closest('LDevice')?.getAttribute('inst');
77+
const srcLNClass = controlBlock.closest('LN0,LN')?.getAttribute('lnClass');
78+
const srcLNInst = controlBlock.closest('LN0,LN')?.getAttribute('inst');
79+
const srcCBName = controlBlock.getAttribute('name');
80+
81+
if (
82+
!srcLDInst ||
83+
!srcLNClass ||
84+
!srcCBName ||
85+
typeof srcLNInst !== 'string' //empty string is allowed in `LN0`
86+
)
87+
return false;
88+
89+
return true;
90+
}
91+
92+
const serviceTypes: Partial<Record<string, string>> = {
93+
ReportControl: 'Report',
94+
GSEControl: 'GOOSE',
95+
SampledValueControl: 'SMV',
96+
};
97+
98+
/**
99+
* @param controlBlock - `ReportControl`, `GSEControl` or `SamepldValueControl` source element
100+
* @param fCDA - the source data. can be data attribute or data obejct (missing daName)
101+
* @returns ExtRef element
102+
*/
103+
export function createExtRefElement(
104+
controlBlock: Element | undefined,
105+
fCDA: Element
106+
): Element {
107+
const iedName = fCDA.closest('IED')?.getAttribute('name') ?? null;
108+
const [ldInst, prefix, lnClass, lnInst, doName, daName] = [
109+
'ldInst',
110+
'prefix',
111+
'lnClass',
112+
'lnInst',
113+
'doName',
114+
'daName',
115+
].map(attr => fCDA.getAttribute(attr));
116+
if (fCDA.ownerDocument.documentElement.getAttribute('version') !== '2007')
117+
//Ed1 does not define serviceType and its MCD attribute starting with src...
118+
return createElement(fCDA.ownerDocument, 'ExtRef', {
119+
iedName,
120+
ldInst,
121+
lnClass,
122+
lnInst,
123+
prefix,
124+
doName,
125+
daName,
126+
});
127+
128+
if (!controlBlock || !serviceTypes[controlBlock.tagName])
129+
//for invalid control block tag name assume polling
130+
return createElement(fCDA.ownerDocument, 'ExtRef', {
131+
iedName,
132+
serviceType: 'Poll',
133+
ldInst,
134+
lnClass,
135+
lnInst,
136+
prefix,
137+
doName,
138+
daName,
139+
});
140+
141+
// default is empty string as attributes are mendaroty acc to IEC 61850-6 >Ed2
142+
const srcLDInst = controlBlock.closest('LDevice')?.getAttribute('inst') ?? '';
143+
const srcPrefix =
144+
controlBlock.closest('LN0,LN')?.getAttribute('prefix') ?? '';
145+
const srcLNClass =
146+
controlBlock.closest('LN0,LN')?.getAttribute('lnClass') ?? '';
147+
const srcLNInst = controlBlock.closest('LN0,LN')?.getAttribute('inst') ?? '';
148+
const srcCBName = controlBlock.getAttribute('name') ?? '';
149+
150+
return createElement(fCDA.ownerDocument, 'ExtRef', {
151+
iedName,
152+
serviceType: serviceTypes[controlBlock.tagName]!,
153+
ldInst,
154+
lnClass,
155+
lnInst,
156+
prefix,
157+
doName,
158+
daName,
159+
srcLDInst,
160+
srcPrefix,
161+
srcLNClass,
162+
srcLNInst,
163+
srcCBName,
164+
});
165+
}
166+
50167
export function getOrderedIeds(doc: XMLDocument): Element[] {
51168
return doc
52169
? Array.from(doc.querySelectorAll(':root > IED')).sort((a, b) =>

src/editors/subscription/goose/foundation.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,18 @@ export function newGOOSESelectEvent(
1919
}
2020

2121
export interface GooseSubscriptionDetail {
22-
ied: Element;
22+
element: Element;
2323
subscribeStatus: SubscribeStatus;
2424
}
2525
export type GooseSubscriptionEvent = CustomEvent<GooseSubscriptionDetail>;
2626
export function newGooseSubscriptionEvent(
27-
ied: Element,
27+
element: Element,
2828
subscribeStatus: SubscribeStatus
2929
): GooseSubscriptionEvent {
3030
return new CustomEvent<GooseSubscriptionDetail>('goose-subscription', {
3131
bubbles: true,
3232
composed: true,
33-
detail: { ied: ied, subscribeStatus },
33+
detail: { element, subscribeStatus },
3434
});
3535
}
3636

src/editors/subscription/goose/subscriber-list.ts

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ import {
2828
getFcdaReferences,
2929
} from '../../../foundation/ied.js';
3030
import {
31+
canCreateValidExtRef,
32+
createExtRefElement,
33+
existExtRef,
3134
IEDSelectEvent,
3235
ListElement,
3336
styles,
@@ -43,7 +46,7 @@ let view: View = View.PUBLISHER;
4346
/** An sub element for subscribing and unsubscribing IEDs to GOOSE messages. */
4447
@customElement('subscriber-list-goose')
4548
export class SubscriberList extends SubscriberListContainer {
46-
@property()
49+
@property({ attribute: false })
4750
doc!: XMLDocument;
4851

4952
/** Current selected GOOSE message (when in GOOSE Publisher view) */
@@ -196,14 +199,16 @@ export class SubscriberList extends SubscriberListContainer {
196199
}
197200

198201
private async onGooseSubscriptionEvent(event: GooseSubscriptionEvent) {
199-
let iedToSubscribe = event.detail.ied;
202+
let iedToSubscribe = event.detail.element;
200203

201204
if (view == View.SUBSCRIBER) {
202-
const dataSetName = event.detail.ied.getAttribute('datSet');
203-
this.currentUsedDataset = event.detail.ied.parentElement?.querySelector(
204-
`DataSet[name="${dataSetName}"]`
205-
);
206-
this.currentGooseIedName = event.detail.ied
205+
const dataSetName = event.detail.element.getAttribute('datSet');
206+
this.currentUsedDataset =
207+
event.detail.element.parentElement?.querySelector(
208+
`DataSet[name="${dataSetName}"]`
209+
);
210+
this.currentSelectedGseControl = event.detail.element;
211+
this.currentGooseIedName = event.detail.element
207212
.closest('IED')
208213
?.getAttribute('name');
209214
iedToSubscribe = this.currentSelectedIed!;
@@ -245,21 +250,13 @@ export class SubscriberList extends SubscriberListContainer {
245250
const actions: Create[] = [];
246251
this.currentUsedDataset!.querySelectorAll('FCDA').forEach(fcda => {
247252
if (
248-
!inputsElement!.querySelector(
249-
`ExtRef[iedName=${this.currentGooseIedName}]` +
250-
`${getFcdaReferences(fcda)}`
251-
)
253+
!existExtRef(inputsElement!, fcda) &&
254+
canCreateValidExtRef(fcda, this.currentSelectedGseControl)
252255
) {
253-
const extRef = createElement(ied.ownerDocument, 'ExtRef', {
254-
iedName: this.currentGooseIedName!,
255-
serviceType: 'GOOSE',
256-
ldInst: fcda.getAttribute('ldInst') ?? '',
257-
lnClass: fcda.getAttribute('lnClass') ?? '',
258-
lnInst: fcda.getAttribute('lnInst') ?? '',
259-
prefix: fcda.getAttribute('prefix') ?? '',
260-
doName: fcda.getAttribute('doName') ?? '',
261-
daName: fcda.getAttribute('daName') ?? '',
262-
});
256+
const extRef = createExtRefElement(
257+
this.currentSelectedGseControl,
258+
fcda
259+
);
263260

264261
if (inputsElement?.parentElement)
265262
actions.push({ new: { parent: inputsElement!, element: extRef } });

src/editors/subscription/sampledvalues/foundation.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,18 @@ export function newSmvSelectEvent(
1919
}
2020

2121
export interface SmvSubscriptionDetail {
22-
ied: Element;
22+
element: Element;
2323
subscribeStatus: SubscribeStatus;
2424
}
2525
export type SmvSubscriptionEvent = CustomEvent<SmvSubscriptionDetail>;
2626
export function newSmvSubscriptionEvent(
27-
ied: Element,
27+
element: Element,
2828
subscribeStatus: SubscribeStatus
2929
): SmvSubscriptionEvent {
3030
return new CustomEvent<SmvSubscriptionDetail>('smv-subscription', {
3131
bubbles: true,
3232
composed: true,
33-
detail: { ied, subscribeStatus },
33+
detail: { element, subscribeStatus },
3434
});
3535
}
3636

src/editors/subscription/sampledvalues/subscriber-list.ts

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ import {
2828
getFcdaReferences,
2929
} from '../../../foundation/ied.js';
3030
import {
31+
canCreateValidExtRef,
32+
createExtRefElement,
33+
existExtRef,
3134
IEDSelectEvent,
3235
ListElement,
3336
styles,
@@ -43,7 +46,7 @@ let view: View = View.PUBLISHER;
4346
/** An sub element for subscribing and unsubscribing IEDs to Sampled Values messages. */
4447
@customElement('subscriber-list-smv')
4548
export class SubscriberList extends SubscriberListContainer {
46-
@property()
49+
@property({ attribute: false })
4750
doc!: XMLDocument;
4851

4952
/** Current selected Sampled Values element (when in GOOSE Publisher view) */
@@ -198,14 +201,16 @@ export class SubscriberList extends SubscriberListContainer {
198201
}
199202

200203
private async onIEDSubscriptionEvent(event: SmvSubscriptionEvent) {
201-
let iedToSubscribe = event.detail.ied;
204+
let iedToSubscribe = event.detail.element;
202205

203206
if (view == View.SUBSCRIBER) {
204-
const dataSetName = event.detail.ied.getAttribute('datSet');
205-
this.currentUsedDataset = event.detail.ied.parentElement?.querySelector(
206-
`DataSet[name="${dataSetName}"]`
207-
);
208-
this.currentSmvIedName = event.detail.ied
207+
const dataSetName = event.detail.element.getAttribute('datSet');
208+
this.currentUsedDataset =
209+
event.detail.element.parentElement?.querySelector(
210+
`DataSet[name="${dataSetName}"]`
211+
);
212+
this.currentSelectedSmvControl = event.detail.element;
213+
this.currentSmvIedName = event.detail.element
209214
.closest('IED')
210215
?.getAttribute('name');
211216
iedToSubscribe = this.currentSelectedIed!;
@@ -247,21 +252,13 @@ export class SubscriberList extends SubscriberListContainer {
247252
const actions: Create[] = [];
248253
this.currentUsedDataset!.querySelectorAll('FCDA').forEach(fcda => {
249254
if (
250-
!inputsElement!.querySelector(
251-
`ExtRef[iedName=${this.currentSmvIedName}]` +
252-
`${getFcdaReferences(fcda)}`
253-
)
255+
!existExtRef(inputsElement!, fcda) &&
256+
canCreateValidExtRef(fcda, this.currentSelectedSmvControl)
254257
) {
255-
const extRef = createElement(ied.ownerDocument, 'ExtRef', {
256-
iedName: this.currentSmvIedName!,
257-
serviceType: 'SMV',
258-
ldInst: fcda.getAttribute('ldInst') ?? '',
259-
lnClass: fcda.getAttribute('lnClass') ?? '',
260-
lnInst: fcda.getAttribute('lnInst') ?? '',
261-
prefix: fcda.getAttribute('prefix') ?? '',
262-
doName: fcda.getAttribute('doName') ?? '',
263-
daName: fcda.getAttribute('daName') ?? '',
264-
});
258+
const extRef = createExtRefElement(
259+
this.currentSelectedSmvControl,
260+
fcda
261+
);
265262

266263
if (inputsElement?.parentElement)
267264
actions.push({ new: { parent: inputsElement!, element: extRef } });

0 commit comments

Comments
 (0)