Skip to content

Commit 25fae6f

Browse files
author
Rob Tjalma
authored
feat(plugins/Subscription): Create 'Subscription' plugin for GOOSE subscriptions
1 parent a58f305 commit 25fae6f

27 files changed

+2406
-29
lines changed

public/js/plugins.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@ export const officialPlugins = [
2020
default: false,
2121
kind: 'editor',
2222
},
23+
{
24+
name: 'Subscription',
25+
src: '/src/editors/Subscription.js',
26+
icon: 'link',
27+
default: true,
28+
kind: 'editor',
29+
},
2330
{
2431
name: 'Sampled Values Subscriber',
2532
src: '/src/editors/SampledValues.js',

src/editors/SampledValues.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { LitElement, html, TemplateResult, property, css } from 'lit-element';
22

33
import '@material/mwc-fab';
44

5-
import './sampledvalues/subscriber-ied-list.js';
5+
import './sampledvalues/subscriber-ied-list-smv.js';
66
import './sampledvalues/sampled-values-list.js';
77

88
/** An editor [[`plugin`]] for subscribing IEDs to Sampled Values. */
@@ -18,7 +18,7 @@ export default class SampledValuesPlugin extends LitElement {
1818
<sampled-values-list .doc=${this.doc}></sampled-values-list>
1919
</section>
2020
<section>
21-
<subscriber-ied-list .doc=${this.doc}></subscriber-ied-list>
21+
<subscriber-ied-list-smv .doc=${this.doc}></subscriber-ied-list-smv>
2222
</section>
2323
</div>`;
2424
}

src/editors/Subscription.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { LitElement, html, TemplateResult, property, css } from 'lit-element';
2+
3+
import '@material/mwc-fab';
4+
5+
import './subscription/subscriber-ied-list-goose.js';
6+
import './subscription/publisher-goose-list.js';
7+
8+
/** An editor [[`plugin`]] for subscribing IEDs to GOOSE messages using the ABB subscription method. */
9+
export default class SubscriptionABBPlugin extends LitElement {
10+
/** The document being edited as provided to plugins by [[`OpenSCD`]]. */
11+
@property()
12+
doc!: XMLDocument;
13+
14+
render(): TemplateResult {
15+
return html`
16+
<div id="containerTemplates">
17+
<section>
18+
<publisher-goose-list .doc=${this.doc}></publisher-goose-list>
19+
</section>
20+
<section>
21+
<subscriber-ied-list-goose .doc=${this.doc}></subscriber-ied-list-goose>
22+
</section>
23+
</div>`;
24+
}
25+
26+
static styles = css`
27+
:host {
28+
width: 100vw;
29+
}
30+
31+
section {
32+
width: 49vw;
33+
}
34+
35+
#containerTemplates {
36+
display: grid;
37+
grid-gap: 12px;
38+
padding: 8px 12px 16px;
39+
box-sizing: border-box;
40+
grid-template-columns: repeat(auto-fit, minmax(316px, auto));
41+
}
42+
43+
@media (max-width: 387px) {
44+
#containerTemplates {
45+
grid-template-columns: repeat(auto-fit, minmax(196px, auto));
46+
}
47+
}
48+
`;
49+
}

src/editors/sampledvalues/elements/ied-element.ts renamed to src/editors/sampledvalues/elements/ied-element-smv.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import '@material/mwc-list/mwc-list-item';
1111

1212
import { newIEDSampledValuesSubscriptionEvent, SubscribeStatus } from '../foundation.js';
1313

14-
@customElement('ied-element')
15-
export class IEDElement extends LitElement {
14+
@customElement('ied-element-smv')
15+
export class IEDElementSmv extends LitElement {
1616
/** Holding the IED element */
1717
@property({ attribute: false })
1818
element!: Element;

src/editors/sampledvalues/subscriber-ied-list.ts renamed to src/editors/sampledvalues/subscriber-ied-list-smv.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import '@material/mwc-icon';
1313
import '@material/mwc-list';
1414
import '@material/mwc-list/mwc-list-item';
1515

16-
import './elements/ied-element.js';
16+
import './elements/ied-element-smv.js';
1717
import {
1818
Create,
1919
createElement,
@@ -97,8 +97,8 @@ const localState: State = {
9797
};
9898

9999
/** An sub element for subscribing and unsubscribing IEDs to Sampled Values messages. */
100-
@customElement('subscriber-ied-list')
101-
export class SubscriberIEDList extends LitElement {
100+
@customElement('subscriber-ied-list-smv')
101+
export class SubscriberIEDListSmv extends LitElement {
102102
@property({ attribute: false })
103103
doc!: XMLDocument;
104104

@@ -128,6 +128,7 @@ export class SubscriberIEDList extends LitElement {
128128
* @param event - Incoming event.
129129
*/
130130
private async onSampledValuesDataSetEvent(event: SampledValuesSelectEvent) {
131+
console.log('onSMVSelect')
131132
localState.currentSampledValuesControl = event.detail.sampledValuesControl;
132133
localState.currentDataset = event.detail.dataset;
133134
localState.currentSampledValuesIEDName = localState.currentSampledValuesControl
@@ -195,6 +196,7 @@ export class SubscriberIEDList extends LitElement {
195196
* @param event - Incoming event.
196197
*/
197198
private async onIEDSubscriptionEvent(event: IEDSampledValuesSubscriptionEvent) {
199+
console.log('onSMVIEDSub')
198200
switch (event.detail.subscribeStatus) {
199201
case SubscribeStatus.Full: {
200202
this.unsubscribe(event.detail.ied);
@@ -377,10 +379,10 @@ export class SubscriberIEDList extends LitElement {
377379
${localState.subscribedIeds.length > 0
378380
? localState.subscribedIeds.map(
379381
ied =>
380-
html`<ied-element
382+
html`<ied-element-smv
381383
.status=${SubscribeStatus.Full}
382384
.element=${ied.element}
383-
></ied-element>`
385+
></ied-element-smv>`
384386
)
385387
: html`<mwc-list-item graphic="avatar" noninteractive>
386388
<span>${translate('sampledvalues.none')}</span>
@@ -398,10 +400,10 @@ export class SubscriberIEDList extends LitElement {
398400
${partialSubscribedIeds.length > 0
399401
? partialSubscribedIeds.map(
400402
ied =>
401-
html`<ied-element
403+
html`<ied-element-smv
402404
.status=${SubscribeStatus.Partial}
403405
.element=${ied.element}
404-
></ied-element>`
406+
></ied-element-smv>`
405407
)
406408
: html`<mwc-list-item graphic="avatar" noninteractive>
407409
<span>${translate('sampledvalues.none')}</span>
@@ -419,10 +421,10 @@ export class SubscriberIEDList extends LitElement {
419421
${availableIeds.length > 0
420422
? availableIeds.map(
421423
ied =>
422-
html`<ied-element
424+
html`<ied-element-smv
423425
.status=${SubscribeStatus.None}
424426
.element=${ied.element}
425-
></ied-element>`
427+
></ied-element-smv>`
426428
)
427429
: html`<mwc-list-item graphic="avatar" noninteractive>
428430
<span>${translate('sampledvalues.none')}</span>
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import {
2+
customElement,
3+
html,
4+
LitElement,
5+
property,
6+
TemplateResult,
7+
} from 'lit-element';
8+
9+
import '@material/mwc-icon';
10+
import '@material/mwc-list/mwc-list-item';
11+
12+
import { newGOOSESelectEvent } from '../foundation.js';
13+
import { gooseIcon } from '../../../icons/icons.js';
14+
15+
@customElement('goose-message')
16+
export class GOOSEMessage extends LitElement {
17+
/** Holding the GSEControl element */
18+
@property({ attribute: false })
19+
element!: Element;
20+
21+
private onGooseSelect = () => {
22+
const ln = this.element.parentElement;
23+
const dataset = ln?.querySelector(
24+
`DataSet[name=${this.element.getAttribute('datSet')}]`
25+
);
26+
this.dispatchEvent(newGOOSESelectEvent(this.element, dataset!));
27+
};
28+
29+
render(): TemplateResult {
30+
return html`<mwc-list-item @click=${this.onGooseSelect} graphic="large">
31+
<span>${this.element.getAttribute('name')}</span>
32+
<mwc-icon slot="graphic">${gooseIcon}</mwc-icon>
33+
</mwc-list-item>`;
34+
}
35+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import {
2+
customElement,
3+
html,
4+
LitElement,
5+
property,
6+
TemplateResult,
7+
} from 'lit-element';
8+
9+
import '@material/mwc-icon';
10+
import '@material/mwc-list/mwc-list-item';
11+
12+
import { newIEDSubscriptionEvent, SubscribeStatus } from '../foundation.js';
13+
14+
@customElement('ied-element-goose')
15+
export class IEDElementGoose extends LitElement {
16+
/** Holding the IED element */
17+
@property({ attribute: false })
18+
element!: Element;
19+
20+
@property({ attribute: false })
21+
status!: SubscribeStatus;
22+
23+
private onIedSelect = () => {
24+
this.dispatchEvent(
25+
newIEDSubscriptionEvent(this.element, this.status ?? SubscribeStatus.None)
26+
);
27+
};
28+
29+
render(): TemplateResult {
30+
return html`<mwc-list-item
31+
@click=${this.onIedSelect}
32+
graphic="avatar"
33+
hasMeta
34+
>
35+
<span>${this.element.getAttribute('name')}</span>
36+
<mwc-icon slot="graphic"
37+
>${this.status == SubscribeStatus.Full
38+
? html`clear`
39+
: html`add`}</mwc-icon
40+
>
41+
</mwc-list-item>`;
42+
}
43+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import { css } from 'lit-element';
2+
3+
/**
4+
* Enumeration stating the Subscribe status of a IED to a GOOSE.
5+
*/
6+
export enum SubscribeStatus {
7+
Full,
8+
Partial,
9+
None,
10+
}
11+
12+
export interface GOOSESelectDetail {
13+
gseControl: Element;
14+
dataset: Element;
15+
}
16+
export type GOOSESelectEvent = CustomEvent<GOOSESelectDetail>;
17+
export function newGOOSESelectEvent(
18+
gseControl: Element,
19+
dataset: Element,
20+
eventInitDict?: CustomEventInit<GOOSESelectDetail>
21+
): GOOSESelectEvent {
22+
return new CustomEvent<GOOSESelectDetail>('goose-dataset', {
23+
bubbles: true,
24+
composed: true,
25+
...eventInitDict,
26+
detail: { gseControl, dataset, ...eventInitDict?.detail },
27+
});
28+
}
29+
30+
export interface IEDSubscriptionDetail {
31+
element: Element;
32+
subscribeStatus: SubscribeStatus;
33+
}
34+
export type IEDSubscriptionEvent = CustomEvent<IEDSubscriptionDetail>;
35+
export function newIEDSubscriptionEvent(
36+
element: Element,
37+
subscribeStatus: SubscribeStatus
38+
): IEDSubscriptionEvent {
39+
return new CustomEvent<IEDSubscriptionDetail>('ied-subscription', {
40+
bubbles: true,
41+
composed: true,
42+
detail: { element, subscribeStatus },
43+
});
44+
}
45+
46+
/** Common `CSS` styles used by DataTypeTemplate subeditors */
47+
export const styles = css`
48+
:host(.moving) section {
49+
opacity: 0.3;
50+
}
51+
52+
section {
53+
background-color: var(--mdc-theme-surface);
54+
transition: all 200ms linear;
55+
outline-color: var(--mdc-theme-primary);
56+
outline-style: solid;
57+
outline-width: 0px;
58+
opacity: 1;
59+
}
60+
61+
section:focus {
62+
box-shadow: 0 8px 10px 1px rgba(0, 0, 0, 0.14),
63+
0 3px 14px 2px rgba(0, 0, 0, 0.12), 0 5px 5px -3px rgba(0, 0, 0, 0.2);
64+
}
65+
66+
section:focus-within {
67+
outline-width: 2px;
68+
transition: all 250ms linear;
69+
}
70+
71+
h1,
72+
h2,
73+
h3 {
74+
color: var(--mdc-theme-on-surface);
75+
font-family: 'Roboto', sans-serif;
76+
font-weight: 300;
77+
overflow: hidden;
78+
white-space: nowrap;
79+
text-overflow: ellipsis;
80+
margin: 0px;
81+
line-height: 48px;
82+
padding-left: 0.3em;
83+
transition: background-color 150ms linear;
84+
}
85+
86+
section:focus-within > h1,
87+
section:focus-within > h2,
88+
section:focus-within > h3 {
89+
color: var(--mdc-theme-surface);
90+
background-color: var(--mdc-theme-primary);
91+
transition: background-color 200ms linear;
92+
}
93+
94+
h1 > nav,
95+
h2 > nav,
96+
h3 > nav,
97+
h1 > abbr > mwc-icon-button,
98+
h2 > abbr > mwc-icon-button,
99+
h3 > abbr > mwc-icon-button {
100+
float: right;
101+
}
102+
103+
abbr[title] {
104+
border-bottom: none !important;
105+
cursor: inherit !important;
106+
text-decoration: none !important;
107+
}
108+
`;
109+
110+
declare global {
111+
interface ElementEventMap {
112+
['goose-dataset']: GOOSESelectEvent;
113+
['ied-subscription']: IEDSubscriptionEvent;
114+
}
115+
}

0 commit comments

Comments
 (0)