diff --git a/src/editors/subscription/goose-message.ts b/src/editors/subscription/elements/goose-message.ts similarity index 52% rename from src/editors/subscription/goose-message.ts rename to src/editors/subscription/elements/goose-message.ts index 16a11499b..29eb73e19 100644 --- a/src/editors/subscription/goose-message.ts +++ b/src/editors/subscription/elements/goose-message.ts @@ -1,13 +1,16 @@ import { - css, customElement, html, LitElement, property, TemplateResult, } from 'lit-element'; -import { newGOOSESelectEvent } from '../../foundation.js'; -import { gooseIcon } from '../../icons.js'; + +import '@material/mwc-icon'; +import '@material/mwc-list/mwc-list-item'; + +import { gooseIcon } from '../../../icons.js'; +import { newGOOSESelectEvent } from '../foundation.js'; @customElement('goose-message') export class GOOSEMessage extends LitElement { @@ -17,24 +20,16 @@ export class GOOSEMessage extends LitElement { private onGooseSelect = () => { const ln = this.element.parentElement; - const dataset = ln?.querySelector(`DataSet[name=${this.element.getAttribute('datSet')}]`); - this.dispatchEvent( - newGOOSESelectEvent( - this.element.closest('IED')?.getAttribute('name') ?? '', - this.element, - dataset! - ) + const dataset = ln?.querySelector( + `DataSet[name=${this.element.getAttribute('datSet')}]` ); + this.dispatchEvent(newGOOSESelectEvent(this.element, dataset!)); }; render(): TemplateResult { - return html`<mwc-list-item - @click=${this.onGooseSelect} - graphic="large"> + return html`<mwc-list-item @click=${this.onGooseSelect} graphic="large"> <span>${this.element.getAttribute('name')}</span> <mwc-icon slot="graphic">${gooseIcon}</mwc-icon> </mwc-list-item>`; } - - static styles = css``; } diff --git a/src/editors/subscription/elements/ied-element.ts b/src/editors/subscription/elements/ied-element.ts new file mode 100644 index 000000000..ff11cbe9c --- /dev/null +++ b/src/editors/subscription/elements/ied-element.ts @@ -0,0 +1,43 @@ +import { + customElement, + html, + LitElement, + property, + TemplateResult, +} from 'lit-element'; + +import '@material/mwc-icon'; +import '@material/mwc-list/mwc-list-item'; + +import { newIEDSubscriptionEvent, SubscribeStatus } from '../foundation.js'; + +@customElement('ied-element') +export class IEDElement extends LitElement { + /** Holding the IED element */ + @property({ attribute: false }) + element!: Element; + + @property({ attribute: false }) + status!: SubscribeStatus; + + private onIedSelect = () => { + this.dispatchEvent( + newIEDSubscriptionEvent(this.element, this.status ?? SubscribeStatus.None) + ); + }; + + render(): TemplateResult { + return html`<mwc-list-item + @click=${this.onIedSelect} + graphic="avatar" + hasMeta + > + <span>${this.element.getAttribute('name')}</span> + <mwc-icon slot="graphic" + >${this.status == SubscribeStatus.Full + ? html`clear` + : html`add`}</mwc-icon + > + </mwc-list-item>`; + } +} diff --git a/src/editors/subscription/foundation.ts b/src/editors/subscription/foundation.ts new file mode 100644 index 000000000..c1445a711 --- /dev/null +++ b/src/editors/subscription/foundation.ts @@ -0,0 +1,115 @@ +import { css } from 'lit-element'; + +/** + * Enumeration stating the Subscribe status of a IED to a GOOSE. + */ +export enum SubscribeStatus { + Full, + Partial, + None, +} + +export interface GOOSESelectDetail { + gseControl: Element; + dataset: Element; +} +export type GOOSESelectEvent = CustomEvent<GOOSESelectDetail>; +export function newGOOSESelectEvent( + gseControl: Element, + dataset: Element, + eventInitDict?: CustomEventInit<GOOSESelectDetail> +): GOOSESelectEvent { + return new CustomEvent<GOOSESelectDetail>('goose-dataset', { + bubbles: true, + composed: true, + ...eventInitDict, + detail: { gseControl, dataset, ...eventInitDict?.detail }, + }); +} + +export interface IEDSubscriptionDetail { + element: Element; + subscribeStatus: SubscribeStatus; +} +export type IEDSubscriptionEvent = CustomEvent<IEDSubscriptionDetail>; +export function newIEDSubscriptionEvent( + element: Element, + subscribeStatus: SubscribeStatus +): IEDSubscriptionEvent { + return new CustomEvent<IEDSubscriptionDetail>('ied-subscription', { + bubbles: true, + composed: true, + detail: { element, subscribeStatus }, + }); +} + +/** Common `CSS` styles used by DataTypeTemplate subeditors */ +export const styles = css` + :host(.moving) section { + opacity: 0.3; + } + + section { + background-color: var(--mdc-theme-surface); + transition: all 200ms linear; + outline-color: var(--mdc-theme-primary); + outline-style: solid; + outline-width: 0px; + opacity: 1; + } + + section:focus { + box-shadow: 0 8px 10px 1px rgba(0, 0, 0, 0.14), + 0 3px 14px 2px rgba(0, 0, 0, 0.12), 0 5px 5px -3px rgba(0, 0, 0, 0.2); + } + + section:focus-within { + outline-width: 2px; + transition: all 250ms linear; + } + + h1, + h2, + h3 { + color: var(--mdc-theme-on-surface); + font-family: 'Roboto', sans-serif; + font-weight: 300; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + margin: 0px; + line-height: 48px; + padding-left: 0.3em; + transition: background-color 150ms linear; + } + + section:focus-within > h1, + section:focus-within > h2, + section:focus-within > h3 { + color: var(--mdc-theme-surface); + background-color: var(--mdc-theme-primary); + transition: background-color 200ms linear; + } + + h1 > nav, + h2 > nav, + h3 > nav, + h1 > abbr > mwc-icon-button, + h2 > abbr > mwc-icon-button, + h3 > abbr > mwc-icon-button { + float: right; + } + + abbr[title] { + border-bottom: none !important; + cursor: inherit !important; + text-decoration: none !important; + } +`; + +declare global { + interface ElementEventMap { + ['goose-dataset']: GOOSESelectEvent; + ['ied-subscription']: IEDSubscriptionEvent; + } +} diff --git a/src/editors/subscription/publisher-goose-list.ts b/src/editors/subscription/publisher-goose-list.ts index 1bf63fbf7..146229b78 100644 --- a/src/editors/subscription/publisher-goose-list.ts +++ b/src/editors/subscription/publisher-goose-list.ts @@ -6,22 +6,27 @@ import { property, TemplateResult, } from 'lit-element'; +import { translate } from 'lit-translate'; -import './goose-message.js'; +import '@material/mwc-icon'; +import '@material/mwc-list'; +import '@material/mwc-list/mwc-list-item'; -import { translate } from 'lit-translate'; +import './elements/goose-message.js'; import { compareNames, getNameAttribute } from '../../foundation.js'; -import { styles } from '../templates/foundation.js'; +import { styles } from './foundation.js'; /** An sub element for showing all published GOOSE messages per IED. */ @customElement('publisher-goose-list') export class PublisherGOOSEList extends LitElement { - @property() + @property({ attribute: false }) doc!: XMLDocument; - private get ieds() : Element[] { - return (this.doc) - ? Array.from(this.doc.querySelectorAll(':root > IED')).sort((a,b) => compareNames(a,b)) + private get ieds(): Element[] { + return this.doc + ? Array.from(this.doc.querySelectorAll(':root > IED')).sort((a, b) => + compareNames(a, b) + ) : []; } @@ -30,37 +35,40 @@ export class PublisherGOOSEList extends LitElement { * @param ied - The IED to search through. * @returns All the published GOOSE messages of this specific IED. */ - private getGSEControls(ied: Element) : Element[] { - return Array.from(ied.querySelectorAll(':scope > AccessPoint > Server > LDevice > LN0[lnClass="LLN0"] > GSEControl')); + private getGSEControls(ied: Element): Element[] { + return Array.from( + ied.querySelectorAll( + ':scope > AccessPoint > Server > LDevice > LN0 > GSEControl' + ) + ); } render(): TemplateResult { - return html` - <section> + return html` <section> <h1>${translate('subscription.publisherGoose.title')}</h1> <mwc-list> ${this.ieds.map(ied => - ied.querySelector('GSEControl') ? - html` - <mwc-list-item noninteractive graphic="icon"> - <span class="iedListTitle">${getNameAttribute(ied)}</span> - <mwc-icon slot="graphic">developer_board</mwc-icon> - </mwc-list-item> - <li divider role="separator"></li> - ${this.getGSEControls(ied).map(control => - html`<goose-message - .element=${control} - ></goose-message>`)} - ` : `` - ) - } - </mwc-list> - </section>`; + ied.querySelector('GSEControl') + ? html` + <mwc-list-item noninteractive graphic="icon"> + <span class="iedListTitle">${getNameAttribute(ied)}</span> + <mwc-icon slot="graphic">developer_board</mwc-icon> + </mwc-list-item> + <li divider role="separator"></li> + ${this.getGSEControls(ied).map( + control => + html`<goose-message .element=${control}></goose-message>` + )} + ` + : `` + )} + </mwc-list> + </section>`; } static styles = css` ${styles} - + mwc-list { height: 100vh; overflow-y: scroll; diff --git a/src/editors/subscription/subscriber-ied-list.ts b/src/editors/subscription/subscriber-ied-list.ts index ac1c2cdbc..bfd2bd887 100644 --- a/src/editors/subscription/subscriber-ied-list.ts +++ b/src/editors/subscription/subscriber-ied-list.ts @@ -8,8 +8,27 @@ import { TemplateResult, } from 'lit-element'; import { translate } from 'lit-translate'; -import { GOOSESelectEvent } from '../../foundation.js'; -import { styles } from '../templates/foundation.js'; + +import '@material/mwc-icon'; +import '@material/mwc-list'; +import '@material/mwc-list/mwc-list-item'; + +import './elements/ied-element.js'; +import { + Create, + createElement, + Delete, + identity, + newActionEvent, + selector, +} from '../../foundation.js'; +import { + GOOSESelectEvent, + IEDSubscriptionEvent, + newGOOSESelectEvent, + styles, + SubscribeStatus, +} from './foundation.js'; /** * An IED within this IED list has 2 properties: @@ -25,40 +44,81 @@ interface IED { * All available FCDA references that are used to link ExtRefs. */ const fcdaReferences = [ - "ldInst", - "lnClass", - "lnInst", - "prefix", - "doName", - "daName", + 'ldInst', + 'lnClass', + 'lnInst', + 'prefix', + 'doName', + 'daName', ]; -/** An sub element for subscribing and unsubscribing IEDs to GOOSE messages. */ -@customElement('subscriber-ied-list') -export class SubscriberIEDList extends LitElement { - @property() - doc!: XMLDocument; +/** + * Get all the FCDA attributes containing values from a specific element. + * @param elementContainingFcdaReferences - The element to use + * @returns FCDA references + */ +function getFcdaReferences(elementContainingFcdaReferences: Element): string { + return fcdaReferences + .map(fcdaRef => + elementContainingFcdaReferences.getAttribute(fcdaRef) + ? `[${fcdaRef}="${elementContainingFcdaReferences.getAttribute(fcdaRef)}"]` + : '' + ) + .join(''); +} + +/** + * Internal persistent state, so it's not lost when + * subscribing / unsubscribing. + */ +interface State { + /** Current selected GSEControl element */ + currentGseControl: Element | undefined; + + /** The current selected dataset */ + currentDataset: Element | undefined; + + /** The name of the IED belonging to the current selected GOOSE */ + currentGooseIEDName: string | undefined | null; /** List holding all current subscribed IEDs. */ - subscribedIeds: IED[] = []; + subscribedIeds: IED[]; /** List holding all current avaialble IEDs which are not subscribed. */ - availableIeds: IED[] = []; + availableIeds: IED[]; +} - /** Current selected IED. */ - iedName!: string; +const localState: State = { + currentGseControl: undefined, + currentDataset: undefined, + currentGooseIEDName: undefined, + subscribedIeds: [], + availableIeds: [], +}; + +/** An sub element for subscribing and unsubscribing IEDs to GOOSE messages. */ +@customElement('subscriber-ied-list') +export class SubscriberIEDList extends LitElement { + @property({ attribute: false }) + doc!: XMLDocument; - /** Current selected GSEControl element. */ - gseControl!: Element; - @query('div') subscriberWrapper!: Element; constructor() { super(); this.onGOOSEDataSetEvent = this.onGOOSEDataSetEvent.bind(this); - const openScdElement = document.querySelector('open-scd'); - if (openScdElement) { - openScdElement.addEventListener('goose-dataset', this.onGOOSEDataSetEvent); + this.onIEDSubscriptionEvent = this.onIEDSubscriptionEvent.bind(this); + + const parentDiv = this.closest('div[id="containerTemplates"]'); + if (parentDiv) { + parentDiv.addEventListener( + 'goose-dataset', + this.onGOOSEDataSetEvent + ); + parentDiv.addEventListener( + 'ied-subscription', + this.onIEDSubscriptionEvent + ); } } @@ -68,139 +128,319 @@ export class SubscriberIEDList extends LitElement { * @param event - Incoming event. */ private async onGOOSEDataSetEvent(event: GOOSESelectEvent) { - this.iedName = event.detail.iedName; - this.gseControl = event.detail.gseControl; + localState.currentGseControl = event.detail.gseControl; + localState.currentDataset = event.detail.dataset; + localState.currentGooseIEDName = localState.currentGseControl + .closest('IED') + ?.getAttribute('name'); - const dataSet = event.detail.dataset; + localState.subscribedIeds = []; + localState.availableIeds = []; - this.clearIedLists(); + Array.from(this.doc.querySelectorAll(':root > IED')) + .filter(ied => ied.getAttribute('name') != localState.currentGooseIEDName) + .forEach(ied => { + const inputElements = ied.querySelectorAll(`LN0 > Inputs, LN > Inputs`); - Array.from(this.doc.querySelectorAll(':root > IED')).forEach(ied => { - const inputElements = ied.querySelectorAll(`LN0 > Inputs, LN > Inputs`); + let numberOfLinkedExtRefs = 0; - let numberOfLinkedExtRefs = 0; - - /** - * If no Inputs element is found, we can safely say it's not subscribed. - */ - if (!inputElements) { - this.availableIeds.push({element: ied}); - return; - } + /** + * If no Inputs element is found, we can safely say it's not subscribed. + */ + if (!inputElements) { + localState.availableIeds.push({ element: ied }); + return; + } - /** - * Count all the linked ExtRefs. - */ - dataSet.querySelectorAll('FCDA').forEach(fcda => { - inputElements.forEach(inputs => { - if(inputs.querySelector(`ExtRef[iedName=${event.detail.iedName}]` + - `${fcdaReferences.map(fcdaRef => - fcda.getAttribute(fcdaRef) - ? `[${fcdaRef}="${fcda.getAttribute(fcdaRef)}"]` - : '').join('') - }`)) { + /** + * Count all the linked ExtRefs. + */ + localState.currentDataset!.querySelectorAll('FCDA').forEach(fcda => { + inputElements.forEach(inputs => { + if ( + inputs.querySelector( + `ExtRef[iedName=${localState.currentGooseIEDName}]` + + `${getFcdaReferences(fcda)}` + ) + ) { numberOfLinkedExtRefs++; } - }) - }) + }); + }); + + /** + * Make a distinction between not subscribed at all, + * partially subscribed and fully subscribed. + */ + if (numberOfLinkedExtRefs == 0) { + localState.availableIeds.push({ element: ied }); + return; + } - /** - * Make a distinction between not subscribed at all, - * partially subscribed and fully subscribed. - */ - if (numberOfLinkedExtRefs == 0) { - this.availableIeds.push({element: ied}); - return; + if ( + numberOfLinkedExtRefs >= + localState.currentDataset!.querySelectorAll('FCDA').length + ) { + localState.subscribedIeds.push({ element: ied }); + } else { + localState.availableIeds.push({ element: ied, partial: true }); + } + }); + + this.requestUpdate(); + } + + /** + * When a IEDSubscriptionEvent is received, check if + * @param event - Incoming event. + */ + private async onIEDSubscriptionEvent(event: IEDSubscriptionEvent) { + switch (event.detail.subscribeStatus) { + case SubscribeStatus.Full: { + this.unsubscribe(event.detail.element); + break; + } + case SubscribeStatus.Partial: { + this.subscribe(event.detail.element); + break; + } + case SubscribeStatus.None: { + this.subscribe(event.detail.element); + break; } + } + } + + /** + * Full subscribe a given IED to the current dataset. + * @param ied - Given IED to subscribe. + */ + private async subscribe(ied: Element): Promise<void> { + if (!ied.querySelector('LN0')) return; - if (numberOfLinkedExtRefs == dataSet.querySelectorAll('FCDA').length) { - this.subscribedIeds.push({element: ied}); - } else { - this.availableIeds.push({element: ied, partial: true}); + let inputsElement = ied.querySelector('LN0 > Inputs'); + if (!inputsElement) + inputsElement = createElement(ied.ownerDocument, 'Inputs', {}); + + const actions: Create[] = []; + localState.currentDataset!.querySelectorAll('FCDA').forEach(fcda => { + if ( + !inputsElement!.querySelector( + `ExtRef[iedName=${localState.currentGooseIEDName}]` + + `${getFcdaReferences(fcda)}` + ) + ) { + const extRef = createElement(ied.ownerDocument, 'ExtRef', { + iedName: localState.currentGooseIEDName!, + serviceType: 'GOOSE', + ldInst: fcda.getAttribute('ldInst') ?? '', + lnClass: fcda.getAttribute('lnClass') ?? '', + lnInst: fcda.getAttribute('lnInst') ?? '', + prefix: fcda.getAttribute('prefix') ?? '', + doName: fcda.getAttribute('doName') ?? '', + daName: fcda.getAttribute('daName') ?? '', + }); + + if (inputsElement?.parentElement) + actions.push({ new: { parent: inputsElement!, element: extRef } }); + else inputsElement?.appendChild(extRef); } + }); + + /** If the IED doesn't have a Inputs element, just append it to the first LN0 element. */ + const title = 'Connect'; + if (inputsElement.parentElement) { + this.dispatchEvent(newActionEvent({ title, actions })); + } else { + const inputAction: Create = { + new: { parent: ied.querySelector('LN0')!, element: inputsElement }, + }; + this.dispatchEvent(newActionEvent({ title, actions: [inputAction] })); + } + + this.dispatchEvent( + newGOOSESelectEvent( + localState.currentGseControl!, + localState.currentDataset! + ) + ); + } + + /** + * Unsubscribing a given IED to the current dataset. + * @param ied - Given IED to unsubscribe. + */ + private unsubscribe(ied: Element): void { + const actions: Delete[] = []; + ied.querySelectorAll('LN0 > Inputs, LN > Inputs').forEach(inputs => { + localState.currentDataset!.querySelectorAll('FCDA').forEach(fcda => { + const extRef = inputs.querySelector( + `ExtRef[iedName=${localState.currentGooseIEDName}]` + + `${getFcdaReferences(fcda)}` + ); + + if (extRef) actions.push({ old: { parent: inputs, element: extRef } }); + }); + - }) + }); - this.requestUpdate(); + this.dispatchEvent( + newActionEvent({ + title: 'Disconnect', + actions: this.extendDeleteActions(actions), + }) + ); + + this.dispatchEvent( + newGOOSESelectEvent( + localState.currentGseControl!, + localState.currentDataset! + ) + ); } /** - * Clear all the IED lists. + * Creating Delete actions in case Inputs elements are empty. + * @param extRefDeleteActions - All Delete actions for ExtRefs. + * @returns Possible delete actions for empty Inputs elements. */ - private clearIedLists() { - this.subscribedIeds = []; - this.availableIeds = []; + private extendDeleteActions(extRefDeleteActions: Delete[]): Delete[] { + if (!extRefDeleteActions.length) return []; + + // Initialize with the already existing ExtRef Delete actions. + const extendedDeleteActions: Delete[] = extRefDeleteActions; + const inputsMap: Record<string, Element> = {}; + + for (const extRefDeleteAction of extRefDeleteActions) { + const extRef = <Element>extRefDeleteAction.old.element; + const inputsElement = <Element>extRefDeleteAction.old.parent; + + const id = identity(inputsElement); + if (!inputsMap[id]) inputsMap[id] = <Element>(inputsElement.cloneNode(true)); + + const linkedExtRef = inputsMap[id].querySelector(`ExtRef[iedName=${extRef.getAttribute('iedName')}]` + + `${getFcdaReferences(extRef)}`); + + if (linkedExtRef) inputsMap[id].removeChild(linkedExtRef); + } + + // create delete action for each empty inputs + Object.entries(inputsMap).forEach(([key, value]) => { + if (value.children.length ! == 0) { + const doc = extRefDeleteActions[0].old.parent.ownerDocument!; + const inputs = doc.querySelector(selector('Inputs', key)); + + if (inputs && inputs.parentElement) { + extendedDeleteActions.push({ + old: { parent: inputs.parentElement, element: inputs }, + }); + } + } + }); + + return extendedDeleteActions; } protected updated(): void { if (this.subscriberWrapper) { - this.subscriberWrapper.scrollTo(0,0); + this.subscriberWrapper.scrollTo(0, 0); } } render(): TemplateResult { - const partialSubscribedIeds = this.availableIeds.filter(ied => ied.partial); - const gseControlName = this.gseControl?.getAttribute('name') ?? undefined; + const partialSubscribedIeds = localState.availableIeds.filter( + ied => ied.partial + ); + const availableIeds = localState.availableIeds.filter( + ied => !ied.partial + ); + const gseControlName = + localState.currentGseControl?.getAttribute('name') ?? undefined; return html` <section> - <h1>${translate('subscription.subscriberIed.title', { - selected: gseControlName ? this.iedName + ' > ' + gseControlName : 'IED' - })}</h1> - ${this.gseControl ? - html`<div class="subscriberWrapper"> - <mwc-list> - <mwc-list-item noninteractive> - <span class="iedListTitle">${translate('subscription.subscriberIed.subscribed')}</span> - </mwc-list-item> - <li divider role="separator"></li> - ${this.subscribedIeds.length > 0 ? - this.subscribedIeds.map(ied => html` - <mwc-list-item graphic="avatar" hasMeta> - <span>${ied.element.getAttribute('name')}</span> - <mwc-icon slot="graphic">clear</mwc-icon> - </mwc-list-item>`) - : html`<mwc-list-item graphic="avatar" noninteractive> - <span>${translate('subscription.none')}</span> - </mwc-list-item>`} - </mwc-list> - <mwc-list> + <h1> + ${translate('subscription.subscriberIed.title', { + selected: gseControlName + ? localState.currentGooseIEDName + ' > ' + gseControlName + : 'IED', + })} + </h1> + ${localState.currentGseControl + ? html`<div class="subscriberWrapper"> + <mwc-list id="subscribedIeds"> + <mwc-list-item noninteractive> + <span class="iedListTitle" + >${translate('subscription.subscriberIed.subscribed')}</span + > + </mwc-list-item> + <li divider role="separator"></li> + ${localState.subscribedIeds.length > 0 + ? localState.subscribedIeds.map( + ied => + html`<ied-element + .status=${SubscribeStatus.Full} + .element=${ied.element} + ></ied-element>` + ) + : html`<mwc-list-item graphic="avatar" noninteractive> + <span>${translate('subscription.none')}</span> + </mwc-list-item>`} + </mwc-list> + <mwc-list id="partialSubscribedIeds"> + <mwc-list-item noninteractive> + <span class="iedListTitle" + >${translate( + 'subscription.subscriberIed.partiallySubscribed' + )}</span + > + </mwc-list-item> + <li divider role="separator"></li> + ${partialSubscribedIeds.length > 0 + ? partialSubscribedIeds.map( + ied => + html`<ied-element + .status=${SubscribeStatus.Partial} + .element=${ied.element} + ></ied-element>` + ) + : html`<mwc-list-item graphic="avatar" noninteractive> + <span>${translate('subscription.none')}</span> + </mwc-list-item>`} + </mwc-list> + <mwc-list id="notSubscribedIeds"> + <mwc-list-item noninteractive> + <span class="iedListTitle" + >${translate( + 'subscription.subscriberIed.availableToSubscribe' + )}</span + > + </mwc-list-item> + <li divider role="separator"></li> + ${availableIeds.length > 0 + ? availableIeds.map( + ied => + html`<ied-element + .status=${SubscribeStatus.None} + .element=${ied.element} + ></ied-element>` + ) + : html`<mwc-list-item graphic="avatar" noninteractive> + <span>${translate('subscription.none')}</span> + </mwc-list-item>`} + </mwc-list> + </div>` + : html`<mwc-list> <mwc-list-item noninteractive> - <span class="iedListTitle">${translate('subscription.subscriberIed.partiallySubscribed')}</span> - </mwc-list-item> - <li divider role="separator"></li> - ${partialSubscribedIeds.length > 0 ? - partialSubscribedIeds.map(ied => html` - <mwc-list-item graphic="avatar" hasMeta> - <span>${ied.element.getAttribute('name')}</span> - <mwc-icon slot="graphic">add</mwc-icon> - </mwc-list-item>`) - : html`<mwc-list-item graphic="avatar" noninteractive> - <span>${translate('subscription.none')}</span> - </mwc-list-item>`} - </mwc-list> - <mwc-list> - <mwc-list-item noninteractive> - <span class="iedListTitle">${translate('subscription.subscriberIed.availableToSubscribe')}</span> - </mwc-list-item> - <li divider role="separator"></li> - ${this.availableIeds.length > 0 ? - this.availableIeds.map(ied => html` - <mwc-list-item graphic="avatar" hasMeta> - <span>${ied.element.getAttribute('name')}</span> - <mwc-icon slot="graphic">add</mwc-icon> - </mwc-list-item>`) - : html`<mwc-list-item graphic="avatar" noninteractive> - <span>${translate('subscription.none')}</span> - </mwc-list-item>`} - </mwc-list>` : html`<mwc-list> - <mwc-list-item noninteractive> - <span class="iedListTitle">${translate('subscription.subscriberIed.noGooseMessageSelected')}</span> + <span class="iedListTitle">${translate( + 'subscription.subscriberIed.noGooseMessageSelected' + )}</span> </mwc-list-item> </mwc-list> </div>`} - </section> - `; + </section> + `; } static styles = css` @@ -211,7 +451,7 @@ export class SubscriberIEDList extends LitElement { white-space: unset; text-overflow: unset; } - + .subscriberWrapper { height: 100vh; overflow-y: scroll; diff --git a/src/foundation.ts b/src/foundation.ts index 23d2d1b01..460a8048b 100644 --- a/src/foundation.ts +++ b/src/foundation.ts @@ -280,26 +280,6 @@ export interface ResetDetail { kind: 'reset'; } -export interface GOOSESelectDetail { - iedName: string; - gseControl: Element; - dataset: Element; -} -export type GOOSESelectEvent = CustomEvent<GOOSESelectDetail>; -export function newGOOSESelectEvent( - iedName: string, - gseControl: Element, - dataset: Element, - eventInitDict?: CustomEventInit<GOOSESelectDetail> -): GOOSESelectEvent { - return new CustomEvent<GOOSESelectDetail>('goose-dataset', { - bubbles: true, - composed: true, - ...eventInitDict, - detail: { iedName, gseControl, dataset, ...eventInitDict?.detail }, - }); -} - export type LogDetail = InfoDetail | CommitDetail | ResetDetail; export type LogEvent = CustomEvent<LogDetail>; export function newLogEvent( @@ -2657,7 +2637,6 @@ declare global { ['open-doc']: OpenDocEvent; ['wizard']: WizardEvent; ['validate']: ValidateEvent; - ['goose-dataset']: GOOSESelectEvent; ['log']: LogEvent; ['issue']: IssueEvent; } diff --git a/test/integration/editors/subscription/Subscription.test.ts b/test/integration/editors/subscription/Subscription.test.ts new file mode 100644 index 000000000..49f6e12b4 --- /dev/null +++ b/test/integration/editors/subscription/Subscription.test.ts @@ -0,0 +1,78 @@ +import { html, fixture, expect } from '@open-wc/testing'; + +import '../../../mock-wizard.js'; + +import Subscription from '../../../../src/editors/Subscription.js'; +import { Editing } from '../../../../src/Editing.js'; +import { Wizarding } from '../../../../src/Wizarding.js'; + +describe('Subscription Plugin', () => { + customElements.define('subscription-plugin', Wizarding(Editing(Subscription))); + let element: Subscription; + let doc: XMLDocument; + + beforeEach(async () => { + doc = await fetch('/test/testfiles/valid2007B4ForSubscription.scd') + .then(response => response.text()) + .then(str => new DOMParser().parseFromString(str, 'application/xml')); + + element = await fixture(html`<subscription-plugin .doc=${doc} ></subscription-plugin>`); + }); + + describe('initially', () => { + it('the GOOSE list looks like the latest snapshot', async () => { + await expect(element.shadowRoot?.querySelector('publisher-goose-list')).shadowDom.to.equalSnapshot(); + }); + + it('the IED list looks like the latest snapshot', async () => { + await expect(element.shadowRoot?.querySelector('subscriber-ied-list')).shadowDom.to.equalSnapshot(); + }); + }); + + describe('when selecting a GOOSE message', () => { + beforeEach(async () => { + const gseMsg = element.shadowRoot?.querySelector('publisher-goose-list') + ?.shadowRoot?.querySelectorAll('goose-message')[2].shadowRoot?.querySelector('mwc-list-item'); + + (<HTMLElement>(gseMsg)).click(); + }); + + it('the list on the right will initially show the subscribed / partially subscribed / not subscribed IEDs', async () => { + await element.updateComplete; + expect(element.shadowRoot?.querySelector('subscriber-ied-list')).shadowDom.to.equalSnapshot(); + }); + + describe('and you subscribe a non-subscribed IED', () => { + it('it looks like the latest snapshot', async () => { + const ied = element.shadowRoot?.querySelector('subscriber-ied-list') + ?.shadowRoot?.querySelectorAll('ied-element')[2].shadowRoot?.querySelector('mwc-list-item'); + + (<HTMLElement>(ied)).click(); + await element.updateComplete; + expect(element.shadowRoot?.querySelector('subscriber-ied-list')).shadowDom.to.equalSnapshot(); + }); + }); + + describe('and you unsubscribe a subscribed IED', () => { + it('it looks like the latest snapshot', async () => { + const ied = element.shadowRoot?.querySelector('subscriber-ied-list') + ?.shadowRoot?.querySelectorAll('ied-element')[0].shadowRoot?.querySelector('mwc-list-item'); + + (<HTMLElement>(ied)).click(); + await element.updateComplete; + expect(element.shadowRoot?.querySelector('subscriber-ied-list')).shadowDom.to.equalSnapshot(); + }); + }); + + describe('and you subscribe a partially subscribed IED', () => { + it('it looks like the latest snapshot', async () => { + const ied = element.shadowRoot?.querySelector('subscriber-ied-list') + ?.shadowRoot?.querySelectorAll('ied-element')[1].shadowRoot?.querySelector('mwc-list-item'); + + (<HTMLElement>(ied)).click(); + await element.updateComplete; + expect(element.shadowRoot?.querySelector('subscriber-ied-list')).shadowDom.to.equalSnapshot(); + }); + }); + }); +}); diff --git a/test/integration/editors/subscription/__snapshots__/Subscription.test.snap.js b/test/integration/editors/subscription/__snapshots__/Subscription.test.snap.js new file mode 100644 index 000000000..9fdce293c --- /dev/null +++ b/test/integration/editors/subscription/__snapshots__/Subscription.test.snap.js @@ -0,0 +1,388 @@ +/* @web/test-runner snapshot v1 */ +export const snapshots = {}; + +snapshots["Subscription Plugin initially the GOOSE list looks like the latest snapshot"] = +`<section> + <h1> + [subscription.publisherGoose.title] + </h1> + <mwc-list> + <mwc-list-item + aria-disabled="false" + graphic="icon" + noninteractive="" + tabindex="-1" + > + <span class="iedListTitle"> + IED1 + </span> + <mwc-icon slot="graphic"> + developer_board + </mwc-icon> + </mwc-list-item> + <li + divider="" + role="separator" + > + </li> + <goose-message> + </goose-message> + <goose-message> + </goose-message> + <mwc-list-item + aria-disabled="false" + graphic="icon" + noninteractive="" + tabindex="-1" + > + <span class="iedListTitle"> + IED2 + </span> + <mwc-icon slot="graphic"> + developer_board + </mwc-icon> + </mwc-list-item> + <li + divider="" + role="separator" + > + </li> + <goose-message> + </goose-message> + <mwc-list-item + aria-disabled="false" + graphic="icon" + noninteractive="" + tabindex="-1" + > + <span class="iedListTitle"> + IED4 + </span> + <mwc-icon slot="graphic"> + developer_board + </mwc-icon> + </mwc-list-item> + <li + divider="" + role="separator" + > + </li> + <goose-message> + </goose-message> + <goose-message> + </goose-message> + </mwc-list> +</section> +`; +/* end snapshot Subscription Plugin initially the GOOSE list looks like the latest snapshot */ + +snapshots["Subscription Plugin initially the IED list looks like the latest snapshot"] = +`<section> + <h1> + [subscription.subscriberIed.title] + </h1> + <mwc-list> + <mwc-list-item + aria-disabled="false" + noninteractive="" + tabindex="-1" + > + <span class="iedListTitle"> + [subscription.subscriberIed.noGooseMessageSelected] + </span> + </mwc-list-item> + </mwc-list> +</section> +`; +/* end snapshot Subscription Plugin initially the IED list looks like the latest snapshot */ + +snapshots["Subscription Plugin when selecting a GOOSE message the list on the right will initially show the subscribed / partially subscribed / not subscribed IEDs"] = +`<section> + <h1> + [subscription.subscriberIed.title] + </h1> + <div class="subscriberWrapper"> + <mwc-list id="subscribedIeds"> + <mwc-list-item + aria-disabled="false" + noninteractive="" + tabindex="-1" + > + <span class="iedListTitle"> + [subscription.subscriberIed.subscribed] + </span> + </mwc-list-item> + <li + divider="" + role="separator" + > + </li> + <ied-element> + </ied-element> + </mwc-list> + <mwc-list id="partialSubscribedIeds"> + <mwc-list-item + aria-disabled="false" + noninteractive="" + tabindex="-1" + > + <span class="iedListTitle"> + [subscription.subscriberIed.partiallySubscribed] + </span> + </mwc-list-item> + <li + divider="" + role="separator" + > + </li> + <ied-element> + </ied-element> + </mwc-list> + <mwc-list id="notSubscribedIeds"> + <mwc-list-item + aria-disabled="false" + noninteractive="" + tabindex="-1" + > + <span class="iedListTitle"> + [subscription.subscriberIed.availableToSubscribe] + </span> + </mwc-list-item> + <li + divider="" + role="separator" + > + </li> + <ied-element> + </ied-element> + </mwc-list> + </div> +</section> +`; +/* end snapshot Subscription Plugin when selecting a GOOSE message the list on the right will initially show the subscribed / partially subscribed / not subscribed IEDs */ + +snapshots["Subscription Plugin when selecting a GOOSE message and you subscribe a non-subscribed IED it looks like the latest snapshot"] = +`<section> + <h1> + [subscription.subscriberIed.title] + </h1> + <div class="subscriberWrapper"> + <mwc-list id="subscribedIeds"> + <mwc-list-item + aria-disabled="false" + noninteractive="" + tabindex="-1" + > + <span class="iedListTitle"> + [subscription.subscriberIed.subscribed] + </span> + </mwc-list-item> + <li + divider="" + role="separator" + > + </li> + <ied-element> + </ied-element> + <ied-element> + </ied-element> + </mwc-list> + <mwc-list id="partialSubscribedIeds"> + <mwc-list-item + aria-disabled="false" + noninteractive="" + tabindex="-1" + > + <span class="iedListTitle"> + [subscription.subscriberIed.partiallySubscribed] + </span> + </mwc-list-item> + <li + divider="" + role="separator" + > + </li> + <ied-element> + </ied-element> + </mwc-list> + <mwc-list id="notSubscribedIeds"> + <mwc-list-item + aria-disabled="false" + noninteractive="" + tabindex="-1" + > + <span class="iedListTitle"> + [subscription.subscriberIed.availableToSubscribe] + </span> + </mwc-list-item> + <li + divider="" + role="separator" + > + </li> + <mwc-list-item + aria-disabled="false" + graphic="avatar" + noninteractive="" + tabindex="-1" + > + <span> + [subscription.none] + </span> + </mwc-list-item> + </mwc-list> + </div> +</section> +`; +/* end snapshot Subscription Plugin when selecting a GOOSE message and you subscribe a non-subscribed IED it looks like the latest snapshot */ + +snapshots["Subscription Plugin when selecting a GOOSE message and you unsubscribe a subscribed IED it looks like the latest snapshot"] = +`<section> + <h1> + [subscription.subscriberIed.title] + </h1> + <div class="subscriberWrapper"> + <mwc-list id="subscribedIeds"> + <mwc-list-item + aria-disabled="false" + noninteractive="" + tabindex="-1" + > + <span class="iedListTitle"> + [subscription.subscriberIed.subscribed] + </span> + </mwc-list-item> + <li + divider="" + role="separator" + > + </li> + <mwc-list-item + aria-disabled="false" + graphic="avatar" + noninteractive="" + tabindex="-1" + > + <span> + [subscription.none] + </span> + </mwc-list-item> + </mwc-list> + <mwc-list id="partialSubscribedIeds"> + <mwc-list-item + aria-disabled="false" + noninteractive="" + tabindex="-1" + > + <span class="iedListTitle"> + [subscription.subscriberIed.partiallySubscribed] + </span> + </mwc-list-item> + <li + divider="" + role="separator" + > + </li> + <ied-element> + </ied-element> + </mwc-list> + <mwc-list id="notSubscribedIeds"> + <mwc-list-item + aria-disabled="false" + noninteractive="" + tabindex="-1" + > + <span class="iedListTitle"> + [subscription.subscriberIed.availableToSubscribe] + </span> + </mwc-list-item> + <li + divider="" + role="separator" + > + </li> + <ied-element> + </ied-element> + <ied-element> + </ied-element> + </mwc-list> + </div> +</section> +`; +/* end snapshot Subscription Plugin when selecting a GOOSE message and you unsubscribe a subscribed IED it looks like the latest snapshot */ + +snapshots["Subscription Plugin when selecting a GOOSE message and you subscribe a partially subscribed IED it looks like the latest snapshot"] = +`<section> + <h1> + [subscription.subscriberIed.title] + </h1> + <div class="subscriberWrapper"> + <mwc-list id="subscribedIeds"> + <mwc-list-item + aria-disabled="false" + noninteractive="" + tabindex="-1" + > + <span class="iedListTitle"> + [subscription.subscriberIed.subscribed] + </span> + </mwc-list-item> + <li + divider="" + role="separator" + > + </li> + <ied-element> + </ied-element> + <ied-element> + </ied-element> + </mwc-list> + <mwc-list id="partialSubscribedIeds"> + <mwc-list-item + aria-disabled="false" + noninteractive="" + tabindex="-1" + > + <span class="iedListTitle"> + [subscription.subscriberIed.partiallySubscribed] + </span> + </mwc-list-item> + <li + divider="" + role="separator" + > + </li> + <mwc-list-item + aria-disabled="false" + graphic="avatar" + noninteractive="" + tabindex="-1" + > + <span> + [subscription.none] + </span> + </mwc-list-item> + </mwc-list> + <mwc-list id="notSubscribedIeds"> + <mwc-list-item + aria-disabled="false" + noninteractive="" + tabindex="-1" + > + <span class="iedListTitle"> + [subscription.subscriberIed.availableToSubscribe] + </span> + </mwc-list-item> + <li + divider="" + role="separator" + > + </li> + <ied-element> + </ied-element> + </mwc-list> + </div> +</section> +`; +/* end snapshot Subscription Plugin when selecting a GOOSE message and you subscribe a partially subscribed IED it looks like the latest snapshot */ + diff --git a/test/testfiles/valid2007B4ForSubscription.scd b/test/testfiles/valid2007B4ForSubscription.scd new file mode 100644 index 000000000..b9ea199ca --- /dev/null +++ b/test/testfiles/valid2007B4ForSubscription.scd @@ -0,0 +1,787 @@ +<?xml version="1.0" encoding="UTF-8"?> +<SCL version="2007" revision="B" release="4" xmlns:sxy="http://www.iec.ch/61850/2003/SCLcoordinates" xmlns="http://www.iec.ch/61850/2003/SCL" xmlns:txy="http://www.iec.ch/61850/2003/Terminal" xmlns:scl="http://www.iec.ch/61850/2003/SCL" xsi:schemaLocation="http://www.iec.ch/61850/2003/SCL SCL.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:IEC_60870_5_104="http://www.iec.ch/61850-80-1/2007/SCL"> + <Header id="TrainingIEC61850" version="1" revision="143" toolID="IEC 61850 System Configurator, Version: V5.90 " nameStructure="IEDName"> + <Text>TrainingIEC61850</Text> + <History> + <Hitem version="1" revision="143" when="Wednesday, September 25, 2019 9:11:36 AM" who="Licenced User: OMICRON electronics GmbH JakVog00 Machine: JAKVOG00LW01 User: JakVog00" what="Station is upgraded from IEC 61850 System Configurator, Version: V5.80 HF1 to V5.90 ." why="IEC 61850 System Configurator Automatic Startup: Insert Comment" /> + </History> + </Header> + <Substation name="AA1" desc="Substation"> + <VoltageLevel name="E1" desc="Voltage Level" nomFreq="50.0" numPhases="3"> + <Voltage unit="V" multiplier="k">110.0</Voltage> + <Bay name="COUPLING_BAY" desc="Bay"> + <Private type="dummyType"> + <ekaf:LNode xmlns:ekaf="http://www.dummyURL.com/dummyNS" iedName="IED2" ldInst="CBSWr" lnClass="LPHD" lnInst="1"/> + </Private> + <LNode iedName="IED2" ldInst="CBSW" lnClass="LPHD" lnInst="1"/> + <LNode iedName="IED2" ldInst="CBSW" lnClass="XSWI" lnInst="3"/> + <ConductingEquipment type="CBR" name="QA1" desc="coupling field ciscuit breaker"/> + <ConductingEquipment type="DIS" name="QB1" desc="busbar disconnector QB1"> + <LNode iedName="IED2" ldInst="CBSW" lnClass="XSWI" lnInst="2"/> + </ConductingEquipment> + <ConductingEquipment type="DIS" name="QB2" desc="busbar disconnector QB2"/> + <ConductingEquipment type="DIS" name="QC11" desc="busbar earth switch QC11"> + <Terminal name="T1" connectivityNode="AA1/E1/COUPLING_BAY/L2" substationName="AA1" voltageLevelName="E1" bayName="COUPLING_BAY" cNodeName="L2"/> + <Terminal connectivityNode="AA1/E1/COUPLING_BAY/grounded" name="T2" substationName="AA1" voltageLevelName="E1" bayName="COUPLING_BAY" cNodeName="grounded"/> + </ConductingEquipment> + <ConductingEquipment type="DIS" name="QC21" desc="busbar disconnector Q12"> + <Terminal connectivityNode="AA1/E1/COUPLING_BAY/grounded" name="T1" substationName="AA1" voltageLevelName="E1" bayName="COUPLING_BAY" cNodeName="grounded"/> + </ConductingEquipment> + <ConnectivityNode pathName="AA1/E1/COUPLING_BAY/L2" name="L2"/> + </Bay> + <Bay name="Bay2" desc="Bay2"> + </Bay> + </VoltageLevel> + <VoltageLevel name="J1" desc="Voltage Level"> + <Voltage unit="V" multiplier="k">20</Voltage> + <Bay name="Bay1" desc="Bay1"> + </Bay> + </VoltageLevel> + </Substation> + <Communication> + <SubNetwork name="StationBus" desc="desc" type="8-MMS"> + <BitRate unit="b/s">100.0</BitRate> + <ConnectedAP iedName="IED1" apName="P1"> + <Address> + <P type="IP">192.168.210.111</P> + <P type="IP-SUBNET">255.255.255.0</P> + <P type="IP-GATEWAY">192.168.210.1</P> + <P type="OSI-AP-Title">1,3,9999,23</P> + <P type="OSI-AE-Qualifier">23</P> + <P type="OSI-PSEL">00000001</P> + <P type="OSI-SSEL">0001</P> + <P type="OSI-TSEL">0001</P> + </Address> + <GSE ldInst="CircuitBreaker_CB1" cbName="GCB"> + <Address> + <P type="MAC-Address">01-0C-CD-01-00-10</P> + <P type="VLAN-ID">005</P> + <P type="VLAN-PRIORITY">4</P> + <P type="APPID">0010</P> + </Address> + </GSE> + <PhysConn type="RedConn"> + <P type="Plug">RJ45</P> + </PhysConn> + </ConnectedAP> + </SubNetwork> + <SubNetwork name="ProcessBus" type="8-MMS"> + <ConnectedAP iedName="IED2" apName="P1"> + <Address> + <P type="IP">192.168.0.112</P> + <P type="IP-SUBNET">255.255.255.0</P> + <P type="IP-GATEWAY">192.168.210.1</P> + <P type="OSI-AP-Title">1,3,9999,23</P> + <P type="OSI-AE-Qualifier">23</P> + <P type="OSI-PSEL">00000001</P> + <P type="OSI-SSEL">0001</P> + <P type="OSI-TSEL">0001</P> + </Address> + </ConnectedAP> + <ConnectedAP iedName="IED3" apName="P1"> + <Address> + <P type="IP">192.168.0.113</P> + <P type="IP-SUBNET">255.255.255.0</P> + <P type="IP-GATEWAY">192.168.210.1</P> + <P type="OSI-AP-Title">1,3,9999,23</P> + <P type="OSI-AE-Qualifier">23</P> + <P type="OSI-PSEL">00000001</P> + <P type="OSI-SSEL">0001</P> + <P type="OSI-TSEL">0001</P> + </Address> + <SMV ldInst="MU01" cbName="MSVCB01"> + <Address> + <P type="MAC-Address">01-0C-CD-04-00-20</P> + <P type="VLAN-ID">007</P> + <P type="VLAN-PRIORITY">4</P> + <P type="APPID">4002</P> + </Address> + </SMV> + </ConnectedAP> + </SubNetwork> + </Communication> + <IED name="IED1" type="DummyIED" manufacturer="DummyManufactorer" configVersion="1" originalSclVersion="2007" originalSclRevision="B" owner="DummyOwner"> + <Services> + <DynAssociation /> + <GetDirectory /> + <GetDataObjectDefinition /> + <DataObjectDirectory /> + <GetDataSetValue /> + <SetDataSetValue /> + <DataSetDirectory /> + <ConfDataSet modify="false" max="3" /> + <DynDataSet max="42" /> + <ReadWrite /> + <ConfReportControl bufConf="false" max="10"/> + <GetCBValues /> + <ReportSettings rptID="Dyn" optFields="Dyn" bufTime="Dyn" trgOps="Dyn" intgPd="Dyn" resvTms="true" owner="true" /> + <GOOSE max="1" /> + <GSSE max="0" /> + <ConfLNs fixPrefix="true" fixLnInst="true" /> + </Services> + <AccessPoint name="P1"> + <Server> + <Authentication none="true" /> + <LDevice inst="CircuitBreaker_CB1"> + <LN0 lnClass="LLN0" inst="" lnType="Dummy.LLN0"> + <DataSet name="GooseDataSet1"> + <FCDA ldInst="CircuitBreaker_CB1" prefix="" lnClass="XCBR" lnInst="1" doName="Pos" daName="stVal" fc="ST"/> + <FCDA ldInst="CircuitBreaker_CB1" prefix="" lnClass="XCBR" lnInst="1" doName="Pos" daName="q" fc="ST"/> + <FCDA ldInst="CircuitBreaker_CB1" prefix="" lnClass="CSWI" lnInst="1" doName="Pos" daName="stVal" fc="ST"/> + <FCDA ldInst="Disconnectors" prefix="DC" lnClass="XSWI" lnInst="1" doName="Pos" daName="stVal" fc="ST"/> + <FCDA ldInst="Disconnectors" prefix="DC" lnClass="XSWI" lnInst="1" doName="Pos" daName="q" fc="ST"/> + </DataSet> + <GSEControl type="GOOSE" appID="0001" fixedOffs="false" confRev="1" name="GCB" datSet="GooseDataSet1"> + <IEDName apRef="P1" ldInst="CircuitBreaker_CB1" lnClass="CSWI" lnInst="1">IED2</IEDName> + </GSEControl> + <GSEControl type="GOOSE" appID="0003" fixedOffs="false" confRev="1" name="GCB2"/> + </LN0> + <LN lnClass="XCBR" inst="1" lnType="Dummy.XCBR1"> + <DOI name="Pos"> + <DAI name="ctlModel"> + <Val>status-only</Val> + </DAI> + </DOI> + </LN> + <LN lnClass="CSWI" inst="1" lnType="Dummy.CSWIwithoutCtlModel"> + <DOI name="Pos"> + <DAI name="ctlModel"> + <Val>sbo-with-enhanced-security</Val> + </DAI> + </DOI> + </LN> + <LN prefix="CB" lnClass="XCBR" inst="2" lnType="Dummy.XCBR1"> + <DOI name="Pos"> + <DAI name="ctlModel"> + <Val>status-only</Val> + </DAI> + </DOI> + </LN> + <LN prefix="CB" lnClass="CSWI" inst="2" lnType="Dummy.CSWIwithoutCtlModel"> + <DOI name="Pos"> + <SDI name="pulseConfig"> + <DAI name="numPls"> + <Val>1</Val> + </DAI> + </SDI> + <DAI name="ctlModel"> + <Val>sbo-with-enhanced-security</Val> + </DAI> + </DOI> + </LN> + </LDevice> + <LDevice inst="Disconnectors"> + <LN0 lnClass="LLN0" inst="" lnType="Dummy.LLN0"/> + <LN prefix="DC" lnClass="XSWI" inst="1" lnType="Dummy.XSWI1"> + <DOI name="Pos"> + <DAI name="ctlModel"> + <Val>status-only</Val> + </DAI> + </DOI> + </LN> + <LN prefix="DC" lnClass="CSWI" inst="1" lnType="Dummy.CSWI"> + <Inputs> + <ExtRef iedName="IED2" ldInst="CBSW" lnClass="XSWI" lnInst="2" doName="Pos" daName="stVal" serviceType="GOOSE" srcCBName="GCB" srcLDInst="CBSW" srcLNClass="LLN0" intAddr="intAddr"/> + <ExtRef iedName="IED2" ldInst="CBSW" lnClass="XSWI" lnInst="2" doName="Pos" daName="q"/> + <ExtRef iedName="IED2" ldInst="CBSW" lnClass="XSWI" lnInst="3" doName="Pos" daName="q"/> + <ExtRef ldInst="CBSW" lnClass="XSWI" lnInst="2" doName="Pos" daName="t" intAddr="stVal-t"/> + </Inputs> + </LN> + <LN prefix="DC" lnClass="CILO" inst="1" lnType="Dummy.CILO"/> + <LN lnClass="XSWI" inst="3" lnType="Dummy.XSWI1"> + <DOI name="Pos"> + <DAI name="ctlModel"> + <Val>status-only</Val> + </DAI> + </DOI> + </LN> + <LN lnClass="CSWI" inst="3" lnType="Dummy.CSWI"/> + <LN lnClass="CILO" inst="3" lnType="Dummy.CILO"/> + <LN lnClass="XSWI" inst="2" lnType="Dummy.XSWI1"> + <DOI name="Pos"> + <DAI name="ctlModel"> + <Val>direct-with-normal-security</Val> + </DAI> + </DOI> + </LN> + <LN lnClass="CSWI" inst="2" lnType="Dummy.CSWIwithoutCtlModel"> + <DOI name="Pos"> + <DAI name="ctlModel"> + <Val>sbo-with-normal-security</Val> + </DAI> + </DOI> + </LN> + </LDevice> + </Server> + </AccessPoint> + <KDC iedName="IED1" apName="P1"/> + </IED> + <IED name="IED2" type="DummyIED" manufacturer="DummyManufactorer" configVersion="1" originalSclVersion="2007" originalSclRevision="B" owner="DummyOwner"> + <Services> + <DynAssociation /> + <GetDirectory /> + <GetDataObjectDefinition /> + <DataObjectDirectory /> + <GetDataSetValue /> + <SetDataSetValue /> + <DataSetDirectory /> + <ConfDataSet modify="false" max="3" /> + <DynDataSet max="42" /> + <ReadWrite /> + <ConfReportControl max="10" /> + <GetCBValues /> + <ReportSettings rptID="Dyn" optFields="Dyn" bufTime="Dyn" trgOps="Dyn" intgPd="Dyn" resvTms="true" owner="true" /> + <GOOSE max="1" /> + <GSSE max="0" /> + <ConfLNs fixPrefix="true" fixLnInst="true" /> + </Services> + <AccessPoint name="P1"> + <Server> + <Authentication /> + <LDevice inst="CBSW"> + <LN0 lnClass="LLN0" inst="" lnType="Dummy.LLN0"> + <Private type="dummyType"> + <esld:FCDA xmlns:esld="http://www.dummyURL.com/dummyNS" ldInst="CBSW" prefix="" lnClass="XSWI" lnInst="2" doName="Pos" daName="stVal" fc="ST"/> + </Private> + <DataSet name="GooseDataSet1"> + <FCDA ldInst="CBSW" prefix="" lnClass="XSWI" lnInst="2" doName="Pos" daName="stVal" fc="ST"/> + <FCDA ldInst="CBSW" prefix="" lnClass="XSWI" lnInst="2" doName="Pos" daName="q" fc="ST"/> + <FCDA ldInst="CBSW" prefix="" lnClass="XSWI" lnInst="3" doName="Pos" daName="q" fc="ST"/> + </DataSet> + <GSEControl type="GOOSE" appID="0002" fixedOffs="false" confRev="1" name="GCB" datSet="GooseDataSet1"> + </GSEControl> + </LN0> + <LN lnClass="LPHD" inst="1" lnType="Dummy.LPHD1"/> + <LN lnClass="THARDE" inst="1" lnType="Dummy.THARDE"/> + <LN lnClass="XCBR" inst="1" lnType="Dummy.XCBR1"> + <DOI name="Pos"> + <DAI name="ctlModel"> + <Val>status-only</Val> + </DAI> + </DOI> + </LN> + <LN lnClass="XSWI" inst="1" lnType="Dummy.XSWI1"> + <DataSet name="dataSet"> + <FCDA ldInst="CBSW" lnClass="XSWI" lnInst="1" doName="Pos" daName="stVal" fc="ST"/> + <FCDA ldInst="CBSW" lnClass="XSWI" lnInst="1" doName="Pos" daName="q" fc="ST"/> + </DataSet> + <ReportControl rptID="IED2/CBSW/XSWI/SwitchGearBRCB" confRev="9" buffered="true" bufTime="100" indexed="true" intgPd="0" name="ReportCb" datSet="dataSet"> + <TrgOps dchg="true" qchg="true" dupd="false" period="false" gi="true"/> + <OptFields seqNum="true" timeStamp="true" dataSet="true" reasonCode="true" dataRef="false" entryID="false" configRef="true" bufOvfl="false"/> + <RptEnabled max="5"> + <ClientLN apRef="P1" ldInst="CircuitBreaker_CB1" lnClass="XCBR" lnInst="1" iedName="IED1"/> + </RptEnabled> + </ReportControl> + <DOI name="Pos"> + <DAI name="ctlModel"> + <Val>status-only</Val> + </DAI> + </DOI> + <Inputs> + <ExtRef iedName="IED1" ldInst="Disconnectors" prefix="DC" lnClass="XSWI" lnInst="1" doName="Pos" daName="stVal"/> + <ExtRef iedName="IED1" ldInst="Disconnectors" prefix="DC" lnClass="XSWI" lnInst="1" doName="Pos" daName="q"/> + </Inputs> + </LN> + <LN lnClass="XSWI" inst="2" lnType="Dummy.XSWI1"> + <DataSet name="dataSet"> + <FCDA ldInst="CBSW" lnClass="XSWI" lnInst="1" doName="Pos" daName="stVal" fc="ST"/> + <FCDA ldInst="CBSW" lnClass="XSWI" lnInst="1" doName="Pos" daName="q" fc="ST"/> + </DataSet> + <ReportControl rptID="IED2/CBSW/XSWI/SwitchGearBRCB" confRev="9" buffered="true" bufTime="100" indexed="true" intgPd="0" name="ReportCb" datSet="dataSet"> + <TrgOps dchg="true" qchg="true" dupd="false" period="false" gi="true"/> + <OptFields seqNum="true" timeStamp="true" dataSet="true" reasonCode="true" dataRef="false" entryID="false" configRef="true" bufOvfl="false"/> + <RptEnabled max="5"> + <ClientLN apRef="P1" ldInst="CircuitBreaker_CB1" lnClass="XCBR" lnInst="1" iedName="IED1"/> + </RptEnabled> + </ReportControl> + <DOI name="Pos"> + <DAI name="ctlModel"> + <Val>status-only</Val> + </DAI> + </DOI> + </LN> + <LN lnClass="XSWI" inst="3" lnType="Dummy.XSWI1"> + <DOI name="Pos"> + <DAI name="ctlModel"> + <Val>status-only</Val> + </DAI> + </DOI> + </LN> + <LN lnClass="GGIO" inst="1" lnType="Dummy.GGIO1"/> + </LDevice> + <LDevice inst="CircuitBreaker_CB1"> + <LN0 lnClass="LLN0" inst="" lnType="Dummy.LLN0"/> + <LN lnClass="XCBR" inst="1" lnType="Dummy.XCBR1"> + <DOI name="Pos"> + <DAI name="ctlModel"> + <Val>status-only</Val> + </DAI> + </DOI> + </LN> + <LN lnClass="CSWI" inst="1" lnType="Dummy.CSWIwithoutCtlModel"> + <DOI name="Pos"> + <DAI name="ctlModel"> + <Val>direct-with-enhanced-security</Val> + </DAI> + </DOI> + <Inputs> + <ExtRef iedName="IED1" ldInst="CircuitBreaker_CB1" prefix="" lnClass="XCBR" lnInst="1" doName="Pos" daName="stVal"/> + <ExtRef iedName="IED1" ldInst="CircuitBreaker_CB1" prefix="" lnClass="XCBR" lnInst="1" doName="Pos" daName="q"/> + </Inputs> + </LN> + </LDevice> + </Server> + </AccessPoint> + </IED> + <IED name="IED3" type="DummyIED" manufacturer="DummyManufactorer" configVersion="1" originalSclVersion="2007" originalSclRevision="B" owner="DummyOwner"> + <Services> + <DynAssociation /> + <GetDirectory /> + <GetDataObjectDefinition /> + <DataObjectDirectory /> + <GetDataSetValue /> + <SetDataSetValue /> + <DataSetDirectory /> + <ConfDataSet modify="false" max="3" /> + <DynDataSet max="42" /> + <ReadWrite /> + <ConfReportControl bufConf="false" max="10"/> + <GetCBValues /> + <ReportSettings rptID="Dyn" optFields="Dyn" bufTime="Dyn" trgOps="Dyn" intgPd="Dyn" resvTms="true" owner="true" /> + <GOOSE max="1" /> + <GSSE max="0" /> + <ConfLNs fixPrefix="true" fixLnInst="true" /> + </Services> + <AccessPoint name="P1"> + <Server> + <Authentication none="true" /> + <LDevice inst="MU01"> + <LN0 lnClass="LLN0" inst="" lnType="Dummy.LLN0.two"> + <DataSet name="PhsMeas1"> + <FCDA ldInst="MU01" prefix="I01A" lnClass="TCTR" lnInst="1" doName="Amp" daName="instMag.i" fc="MX"/> + <FCDA ldInst="MU01" prefix="I01A" lnClass="TCTR" lnInst="1" doName="Amp" daName="q" fc="MX"/> + <FCDA ldInst="MU01" prefix="I01B" lnClass="TCTR" lnInst="2" doName="Amp" daName="instMag.i" fc="MX"/> + <FCDA ldInst="MU01" prefix="I01B" lnClass="TCTR" lnInst="2" doName="Amp" daName="q" fc="MX"/> + <FCDA ldInst="MU01" prefix="I01C" lnClass="TCTR" lnInst="3" doName="Amp" daName="instMag.i" fc="MX"/> + <FCDA ldInst="MU01" prefix="I01C" lnClass="TCTR" lnInst="3" doName="Amp" daName="q" fc="MX"/> + <FCDA ldInst="MU01" prefix="I01N" lnClass="TCTR" lnInst="4" doName="Amp" daName="instMag.i" fc="MX"/> + <FCDA ldInst="MU01" prefix="I01N" lnClass="TCTR" lnInst="4" doName="Amp" daName="q" fc="MX"/> + <FCDA ldInst="MU01" prefix="U01A" lnClass="TVTR" lnInst="1" doName="Vol" daName="instMag.i" fc="MX"/> + <FCDA ldInst="MU01" prefix="U01A" lnClass="TVTR" lnInst="1" doName="Vol" daName="q" fc="MX"/> + <FCDA ldInst="MU01" prefix="U01B" lnClass="TVTR" lnInst="2" doName="Vol" daName="instMag.i" fc="MX"/> + <FCDA ldInst="MU01" prefix="U01B" lnClass="TVTR" lnInst="2" doName="Vol" daName="q" fc="MX"/> + <FCDA ldInst="MU01" prefix="U01C" lnClass="TVTR" lnInst="3" doName="Vol" daName="instMag.i" fc="MX"/> + <FCDA ldInst="MU01" prefix="U01C" lnClass="TVTR" lnInst="3" doName="Vol" daName="q" fc="MX"/> + <FCDA ldInst="MU01" prefix="U01N" lnClass="TVTR" lnInst="4" doName="Vol" daName="instMag.i" fc="MX"/> + <FCDA ldInst="MU01" prefix="U01N" lnClass="TVTR" lnInst="4" doName="Vol" daName="q" fc="MX"/> + </DataSet> + <SampledValueControl smvID="IED3_SMVID" multicast="true" smpRate="80" nofASDU="1" confRev="1" name="MSVCB01" datSet="PhsMeas1"> + <SmvOpts/> + </SampledValueControl> + </LN0> + <LN prefix="I01A" lnClass="TCTR" inst="1" lnType="DummyTCTR" /> + <LN prefix="I01B" lnClass="TCTR" inst="2" lnType="DummyTCTR" /> + <LN prefix="I01C" lnClass="TCTR" inst="3" lnType="DummyTCTR" /> + <LN prefix="I01N" lnClass="TCTR" inst="4" lnType="DummyTCTR" /> + <LN prefix="U01A" lnClass="TVTR" inst="1" lnType="DummyTVTR" /> + <LN prefix="U01B" lnClass="TVTR" inst="2" lnType="DummyTVTR" /> + <LN prefix="U01C" lnClass="TVTR" inst="3" lnType="DummyTVTR" /> + <LN prefix="U01N" lnClass="TVTR" inst="4" lnType="DummyTVTR" /> + </LDevice> + </Server> + </AccessPoint> + <AccessPoint name="P2"> + </AccessPoint> + </IED> + <IED name="IED4" type="DummyIED" manufacturer="DummyManufactorer" configVersion="1" originalSclVersion="2007" originalSclRevision="B" owner="DummyOwner"> + <Services> + <DynAssociation /> + <GetDirectory /> + <GetDataObjectDefinition /> + <DataObjectDirectory /> + <GetDataSetValue /> + <SetDataSetValue /> + <DataSetDirectory /> + <ConfDataSet modify="false" max="3" /> + <DynDataSet max="42" /> + <ReadWrite /> + <ConfReportControl bufConf="false" max="10"/> + <GetCBValues /> + <ReportSettings rptID="Dyn" optFields="Dyn" bufTime="Dyn" trgOps="Dyn" intgPd="Dyn" resvTms="true" owner="true" /> + <GOOSE max="1" /> + <GSSE max="0" /> + <ConfLNs fixPrefix="true" fixLnInst="true" /> + </Services> + <AccessPoint name="P1"> + <Server> + <Authentication none="true" /> + <LDevice inst="CircuitBreaker_CB1"> + <LN0 lnClass="LLN0" inst="" lnType="Dummy.LLN0"> + <DataSet name="GooseDataSet1"> + <FCDA ldInst="CircuitBreaker_CB1" prefix="" lnClass="XCBR" lnInst="1" doName="Pos" daName="stVal" fc="ST"/> + <FCDA ldInst="CircuitBreaker_CB1" prefix="" lnClass="XCBR" lnInst="1" doName="Pos" daName="q" fc="ST"/> + <FCDA ldInst="CircuitBreaker_CB1" prefix="" lnClass="CSWI" lnInst="1" doName="Pos" daName="stVal" fc="ST"/> + <FCDA ldInst="Disconnectors" prefix="DC" lnClass="XSWI" lnInst="1" doName="Pos" daName="stVal" fc="ST"/> + <FCDA ldInst="Disconnectors" prefix="DC" lnClass="XSWI" lnInst="1" doName="Pos" daName="q" fc="ST"/> + </DataSet> + <GSEControl type="GOOSE" appID="0001" fixedOffs="false" confRev="1" name="GCB" datSet="GooseDataSet1"> + <IEDName apRef="P1" ldInst="CircuitBreaker_CB1" lnClass="CSWI" lnInst="1">IED2</IEDName> + </GSEControl> + <GSEControl type="GOOSE" appID="0003" fixedOffs="false" confRev="1" name="GCB2"/> + </LN0> + <LN lnClass="XCBR" inst="1" lnType="Dummy.XCBR1"> + <DOI name="Pos"> + <DAI name="ctlModel"> + <Val>status-only</Val> + </DAI> + </DOI> + </LN> + <LN lnClass="CSWI" inst="1" lnType="Dummy.CSWIwithoutCtlModel"> + <DOI name="Pos"> + <DAI name="ctlModel"> + <Val>sbo-with-enhanced-security</Val> + </DAI> + </DOI> + </LN> + <LN prefix="CB" lnClass="XCBR" inst="2" lnType="Dummy.XCBR1"> + <DOI name="Pos"> + <DAI name="ctlModel"> + <Val>status-only</Val> + </DAI> + </DOI> + </LN> + <LN prefix="CB" lnClass="CSWI" inst="2" lnType="Dummy.CSWIwithoutCtlModel"> + <DOI name="Pos"> + <SDI name="pulseConfig"> + <DAI name="numPls"> + <Val>1</Val> + </DAI> + </SDI> + <DAI name="ctlModel"> + <Val>sbo-with-enhanced-security</Val> + </DAI> + </DOI> + </LN> + </LDevice> + <LDevice inst="Disconnectors"> + <LN0 lnClass="LLN0" inst="" lnType="Dummy.LLN0"/> + <LN prefix="DC" lnClass="XSWI" inst="1" lnType="Dummy.XSWI1"> + <DOI name="Pos"> + <DAI name="ctlModel"> + <Val>status-only</Val> + </DAI> + </DOI> + </LN> + <LN prefix="DC" lnClass="CSWI" inst="1" lnType="Dummy.CSWI"> + <Inputs> + <ExtRef iedName="IED2" ldInst="CBSW" prefix="" lnClass="XSWI" lnInst="2" doName="Pos" daName="stVal" fc="ST"/> + <ExtRef iedName="IED2" ldInst="CBSW" prefix="" lnClass="XSWI" lnInst="2" doName="Pos" daName="q" fc="ST"/> + </Inputs> + </LN> + <LN prefix="DC" lnClass="CILO" inst="1" lnType="Dummy.CILO"/> + <LN lnClass="XSWI" inst="3" lnType="Dummy.XSWI1"> + <DOI name="Pos"> + <DAI name="ctlModel"> + <Val>status-only</Val> + </DAI> + </DOI> + </LN> + <LN lnClass="CSWI" inst="3" lnType="Dummy.CSWI"/> + <LN lnClass="CILO" inst="3" lnType="Dummy.CILO"/> + <LN lnClass="XSWI" inst="2" lnType="Dummy.XSWI1"> + <DOI name="Pos"> + <DAI name="ctlModel"> + <Val>direct-with-normal-security</Val> + </DAI> + </DOI> + </LN> + <LN lnClass="CSWI" inst="2" lnType="Dummy.CSWIwithoutCtlModel"> + <DOI name="Pos"> + <DAI name="ctlModel"> + <Val>sbo-with-normal-security</Val> + </DAI> + </DOI> + </LN> + </LDevice> + </Server> + </AccessPoint> + <KDC iedName="IED1" apName="P1"/> + </IED> + <DataTypeTemplates> + <LNodeType lnClass="LLN0" id="Dummy.LLN0"> + <DO name="Mod" type="Dummy.LLN0.Mod" /> + <DO name="ExtendedMod" type="Dummy.LLN0.ExtendedMod" /> + <DO name="Beh" type="Dummy.LLN0.Beh" /> + <DO name="Health" type="Dummy.LLN0.Health" /> + <DO name="NamPlt" type="Dummy.LLN0.NamPlt" /> + </LNodeType> + <LNodeType lnClass="LLN0" id="Dummy.LLN0.two"> + <DO name="Mod" type="Dummy.LLN0.Mod" /> + <DO name="Beh" type="Dummy.LLN0.Beh" /> + <DO name="Health" type="Dummy.LLN0.Health" /> + <DO name="NamPlt" type="Dummy.LLN0.NamPlt" /> + </LNodeType> + <LNodeType lnClass="LPHD" id="Dummy.LPHD1"> + <DO name="PhyNam" type="Dummy.LPHD1.PhyNam" /> + <DO name="PhyHealth" type="Dummy.LLN0.Health" /> + <DO name="Proxy" type="Dummy.SPS" /> + <DO name="Sim" type="Dummy.LPHD1.Sim" /> + </LNodeType> + <LNodeType lnClass="XCBR" id="Dummy.XCBR1"> + <DO name="Beh" type="Dummy.LLN0.Beh" /> + <DO name="NamPlt" type="Dummy.XCBR1.NamPlt" /> + <DO name="Loc" type="Dummy.SPS" /> + <DO name="OpCnt" type="Dummy.XCBR1.OpCnt" /> + <DO name="Pos" type="Dummy.XCBR1.Pos" /> + <DO name="BlkOpn" type="Dummy.XCBR1.BlkOpn" /> + <DO name="BlkCls" type="Dummy.XCBR1.BlkOpn" /> + </LNodeType> + <LNodeType lnClass="CSWI" id="Dummy.CSWI"> + <DO name="Beh" type="Dummy.LLN0.Beh" /> + <DO name="NamPlt" type="Dummy.XCBR1.NamPlt" /> + <DO name="Loc" type="Dummy.SPS" /> + <DO name="OpCnt" type="Dummy.XCBR1.OpCnt" /> + <DO name="Pos" type="Dummy.CSWI.Pos1" /> + </LNodeType> + <LNodeType lnClass="CILO" id="Dummy.CILO"> + <DO name="Beh" type="Dummy.LLN0.Beh" /> + <DO name="NamPlt" type="Dummy.XCBR1.NamPlt" /> + <DO name="EnaOpn" type="Dummy.SPS"/> + <DO name="EnaCls" type="Dummy.SPS"/> + </LNodeType> + <LNodeType lnClass="CSWI" id="Dummy.CSWIwithoutCtlModel"> + <DO name="Beh" type="Dummy.LLN0.Beh" /> + <DO name="NamPlt" type="Dummy.XCBR1.NamPlt" /> + <DO name="Loc" type="Dummy.SPS" /> + <DO name="OpCnt" type="Dummy.XCBR1.OpCnt" /> + <DO name="Pos" type="Dummy.CSWI.Pos2" /> + </LNodeType> + <LNodeType lnClass="XSWI" id="Dummy.XSWI1"> + <DO name="Beh" type="Dummy.LLN0.Beh" /> + <DO name="NamPlt" type="Dummy.XCBR1.NamPlt" /> + <DO name="Loc" type="Dummy.SPS" /> + <DO name="OpCnt" type="Dummy.XCBR1.OpCnt" /> + <DO name="Pos" type="Dummy.XCBR1.Pos" /> + <DO name="BlkOpn" type="Dummy.XCBR1.BlkOpn" /> + <DO name="BlkCls" type="Dummy.XCBR1.BlkOpn" /> + </LNodeType> + <LNodeType lnClass="GGIO" id="Dummy.GGIO1"> + <DO name="Beh" type="Dummy.LLN0.Beh" /> + <DO name="NamPlt" type="Dummy.XCBR1.NamPlt" /> + <DO name="Ind1" type="Dummy.SPS" /> + <DO name="SPCSO1" type="Dummy.LPHD1.Sim" /> + </LNodeType> + <LNodeType lnClass="TCTR" id="DummyTCTR"> + <DO name="Mod" type="Dummy.LLN0.Mod"/> + <DO name="Beh" type="Dummy.LLN0.Beh"/> + <DO name="NamPlt" type="Dummy.XCBR1.NamPlt"/> + <DO name="Amp" type="DummySAV"/> + </LNodeType> + <LNodeType lnClass="TVTR" id="DummyTVTR"> + <DO name="Mod" type="Dummy.LLN0.Mod"/> + <DO name="Beh" type="Dummy.LLN0.Beh"/> + <DO name="NamPlt" type="Dummy.XCBR1.NamPlt"/> + <DO name="Vol" type="DummySAV"/> + </LNodeType> + <DOType cdc="SAV" id="DummySAV"> + <DA fc="MX" name="instMag" bType="Struct" type="AnalogueValue_i"/> + <DA fc="MX" qchg="true" name="q" bType="Quality"/> + <DA fc="CF" name="sVC" bType="Struct" type="ScaledValueConfig"/> + </DOType> + <DOType cdc="ENC" id="Dummy.LLN0.Mod"> + <DA fc="ST" name="stVal" bType="Enum" type="Dummy_Beh" /> + <DA fc="ST" name="q" bType="Quality" /> + <DA fc="ST" name="t" bType="Timestamp" /> + <DA fc="ST" name="stSeld" bType="BOOLEAN" /> + <DA fc="OR" name="opRcvd" bType="BOOLEAN" /> + <DA fc="OR" name="opOk" bType="BOOLEAN" /> + <DA fc="OR" name="tOpOk" bType="Timestamp" /> + <DA fc="CF" name="ctlModel" bType="Enum" type="Dummy_ctlModel" /> + <DA fc="CF" name="sboTimeout" bType="INT32U" /> + <DA fc="CF" name="operTimeout" bType="INT32U" /> + <DA fc="CO" name="SBO" bType="ObjRef" /> + <DA fc="CO" name="SBOw" bType="Struct" type="Dummy.LLN0.Mod.SBOw" /> + <DA fc="CO" name="Oper" bType="Struct" type="Dummy.LLN0.Mod.SBOw" /> + <DA fc="CO" name="Cancel" bType="Struct" type="Dummy.LLN0.Mod.Cancel" /> + </DOType> + <DOType cdc="ENC" id="Dummy.LLN0.ExtendedMod"> + <SDO fc="ST" name="someSdo" type="someSdoType"/> + <DA fc="ST" name="stVal" bType="Enum" type="Dummy_Beh" /> + <DA fc="ST" name="q" bType="Quality" /> + <DA fc="ST" name="t" bType="Timestamp" /> + <DA fc="ST" name="stSeld" bType="BOOLEAN" /> + <DA fc="OR" name="opRcvd" bType="BOOLEAN" /> + <DA fc="OR" name="opOk" bType="BOOLEAN" /> + <DA fc="OR" name="tOpOk" bType="Timestamp" /> + <DA fc="CF" name="ctlModel" bType="Enum" type="Dummy_ctlModel" /> + <DA fc="CF" name="sboTimeout" bType="INT32U" /> + <DA fc="CF" name="operTimeout" bType="INT32U" /> + <DA fc="CO" name="SBO" bType="ObjRef" /> + <DA fc="CO" name="SBOw" bType="Struct" type="Dummy.LLN0.Mod.SBOw" /> + <DA fc="CO" name="Oper" bType="Struct" type="Dummy.LLN0.Mod.SBOw" /> + <DA fc="CO" name="Cancel" bType="Struct" type="Dummy.LLN0.Mod.Cancel" /> + </DOType> + <DOType cdc="CMV" id="someSdoType"> + <DA fc="MX" qchg="true" name="q" bType="Quality"/> + <DA fc="MX" name="t" bType="Timestamp"/> + </DOType> + <DOType cdc="ENS" id="Dummy.LLN0.Beh"> + <DA fc="ST" name="stVal" bType="Enum" type="Dummy_Beh" /> + <DA fc="ST" name="q" bType="Quality" /> + <DA fc="ST" name="t" bType="Timestamp" /> + </DOType> + <DOType cdc="ENS" id="Dummy.LLN0.Health"> + <DA fc="ST" name="stVal" bType="Enum" type="Dummy_Health" /> + <DA fc="ST" name="q" bType="Quality" /> + <DA fc="ST" name="t" bType="Timestamp" /> + </DOType> + <DOType cdc="LPL" id="Dummy.LLN0.NamPlt"> + <DA fc="DC" name="vendor" bType="VisString255" /> + <DA fc="DC" name="swRev" bType="VisString255" /> + <DA fc="DC" name="d" bType="VisString255" /> + <DA fc="DC" name="configRev" bType="VisString255" /> + <DA fc="EX" name="ldNs" bType="VisString255" /> + </DOType> + <DOType cdc="DPL" id="Dummy.LPHD1.PhyNam"> + <DA fc="DC" name="vendor" bType="VisString255" /> + <DA fc="DC" name="hwRev" bType="VisString255" /> + <DA fc="DC" name="swRev" bType="VisString255" /> + <DA fc="DC" name="serNum" bType="VisString255" /> + <DA fc="DC" name="model" bType="VisString255" /> + </DOType> + <DOType cdc="SPC" id="Dummy.LPHD1.Sim"> + <DA fc="ST" name="stVal" bType="BOOLEAN" /> + <DA fc="ST" name="q" bType="Quality" /> + <DA fc="ST" name="t" bType="Timestamp" /> + <DA fc="ST" name="stSeld" bType="BOOLEAN" /> + <DA fc="OR" name="opRcvd" bType="BOOLEAN" /> + <DA fc="OR" name="opOk" bType="BOOLEAN" /> + <DA fc="OR" name="tOpOk" bType="Timestamp" /> + <DA fc="CF" name="ctlModel" bType="Enum" type="Dummy_ctlModel" /> + <DA fc="CF" name="sboTimeout" bType="INT32U" /> + <DA fc="CF" name="operTimeout" bType="INT32U" /> + <DA fc="DC" name="d" bType="VisString255" /> + <DA fc="CO" name="SBO" bType="ObjRef" /> + <DA fc="CO" name="SBOw" bType="Struct" type="Dummy.LPHD1.Sim.SBOw" /> + <DA fc="CO" name="Oper" bType="Struct" type="Dummy.LPHD1.Sim.SBOw" /> + <DA fc="CO" name="Cancel" bType="Struct" type="Dummy.LPHD1.Sim.Cancel" /> + </DOType> + <DOType cdc="DPC" id="Dummy.XCBR1.Pos"> + <DA fc="ST" name="stVal" bType="Dbpos" /> + <DA fc="ST" name="q" bType="Quality" /> + <DA fc="ST" name="t" bType="Timestamp" /> + <DA fc="CF" name="ctlModel" bType="Enum" type="Dummy_ctlModel" /> + <DA fc="DC" name="d" bType="VisString255" /> + </DOType> + <DOType cdc="DPC" id="Dummy.CSWI.Pos1"> + <DA fc="ST" name="stVal" bType="Dbpos" /> + <DA fc="ST" name="q" bType="Quality" /> + <DA fc="ST" name="t" bType="Timestamp" /> + <DA fc="CF" name="ctlModel" bType="Enum" type="Dummy_ctlModel"> + <Val>sbo-with-enhanced-security</Val> + </DA> + <DA fc="DC" name="d" bType="VisString255" /> + </DOType> + <DOType cdc="DPC" id="Dummy.CSWI.Pos2"> + <DA fc="ST" name="stVal" bType="Dbpos" /> + <DA fc="ST" name="q" bType="Quality" /> + <DA fc="ST" name="t" bType="Timestamp" /> + <DA fc="CF" name="ctlModel" bType="Enum" type="Dummy_ctlModel"/> + <DA fc="DC" name="d" bType="VisString255" /> + </DOType> + <DOType cdc="INS" id="Dummy.XCBR1.OpCnt"> + <DA fc="ST" name="stVal" bType="INT32" /> + <DA fc="ST" name="q" bType="Quality" /> + <DA fc="ST" name="t" bType="Timestamp" /> + </DOType> + <DOType cdc="LPL" id="Dummy.XCBR1.NamPlt"> + <DA fc="DC" name="vendor" bType="VisString255" /> + <DA fc="DC" name="swRev" bType="VisString255" /> + <DA fc="DC" name="d" bType="VisString255" /> + </DOType> + <DOType cdc="SPC" id="Dummy.XCBR1.BlkOpn"> + <DA fc="ST" name="stVal" bType="BOOLEAN" /> + <DA fc="ST" name="q" bType="Quality" /> + <DA fc="ST" name="t" bType="Timestamp" /> + <DA fc="CF" name="ctlModel" bType="Enum" type="Dummy_ctlModel" /> + <DA fc="DC" name="d" bType="VisString255" /> + </DOType> + <DOType cdc="SPS" id="Dummy.SPS"> + <DA fc="ST" dchg="true" name="stVal" bType="BOOLEAN"/> + <DA fc="ST" qchg="true" name="q" bType="Quality"/> + <DA fc="ST" name="t" bType="Timestamp"/> + </DOType> + <DAType id="AnalogueValue_i"> + <BDA name="i" bType="INT32"/> + </DAType> + <DAType id="ScaledValueConfig"> + <BDA name="scaleFactor" bType="FLOAT32"/> + <BDA name="offset" bType="FLOAT32"/> + </DAType> + <DAType id="Dummy_origin"> + <BDA name="orCat" bType="Enum" type="Dummy_orCategory" /> + <BDA name="orIdent" bType="Octet64" /> + </DAType> + <DAType id="Dummy.LLN0.Mod.SBOw"> + <BDA name="ctlVal" bType="Enum" type="Dummy_Beh" /> + <BDA name="origin" bType="Struct" type="Dummy_origin" /> + <BDA name="ctlNum" bType="INT8U" /> + <BDA name="T" bType="Timestamp" /> + <BDA name="Test" bType="BOOLEAN" /> + <BDA name="Check" bType="Check" /> + <ProtNs>IEC 61850-8-1:2003</ProtNs> + </DAType> + <DAType id="Dummy.LLN0.Mod.Cancel"> + <BDA name="ctlVal" bType="Enum" type="Dummy_Beh" /> + <BDA name="origin" bType="Struct" type="Dummy_origin" /> + <BDA name="ctlNum" bType="INT8U" /> + <BDA name="T" bType="Timestamp" /> + <BDA name="Test" bType="BOOLEAN" /> + </DAType> + <DAType id="Dummy.LPHD1.Sim.SBOw"> + <BDA name="ctlVal" bType="BOOLEAN" /> + <BDA name="origin" bType="Struct" type="Dummy_origin" /> + <BDA name="ctlNum" bType="INT8U" /> + <BDA name="T" bType="Timestamp" /> + <BDA name="Test" bType="BOOLEAN" /> + <BDA name="Check" bType="Check" /> + </DAType> + <DAType id="Dummy.LPHD1.Sim.Cancel"> + <BDA name="ctlVal" bType="BOOLEAN" /> + <BDA name="origin" bType="Struct" type="Dummy_origin" /> + <BDA name="ctlNum" bType="INT8U" /> + <BDA name="T" bType="Timestamp" /> + <BDA name="Test" bType="BOOLEAN" /> + </DAType> + <EnumType id="Dummy_ctlModel"> + <EnumVal ord="0">status-only</EnumVal> + <EnumVal ord="1">direct-with-normal-security</EnumVal> + <EnumVal ord="2">sbo-with-normal-security</EnumVal> + <EnumVal ord="3">direct-with-enhanced-security</EnumVal> + <EnumVal ord="4">sbo-with-enhanced-security</EnumVal> + </EnumType> + <EnumType id="Dummy_Beh"> + <EnumVal ord="1">on</EnumVal> + <EnumVal ord="2">blocked</EnumVal> + <EnumVal ord="3">test</EnumVal> + <EnumVal ord="4">test/blocked</EnumVal> + <EnumVal ord="5">off</EnumVal> + </EnumType> + <EnumType id="Dummy_Health"> + <EnumVal ord="1">Ok</EnumVal> + <EnumVal ord="2">Warning</EnumVal> + <EnumVal ord="3">Alarm</EnumVal> + </EnumType> + <EnumType id="Dummy_orCategory"> + <EnumVal ord="0">not-supported</EnumVal> + <EnumVal ord="1">bay-control</EnumVal> + <EnumVal ord="2">station-control</EnumVal> + <EnumVal ord="3">remote-control</EnumVal> + <EnumVal ord="4">automatic-bay</EnumVal> + <EnumVal ord="5">automatic-station</EnumVal> + <EnumVal ord="6">automatic-remote</EnumVal> + <EnumVal ord="7">maintenance</EnumVal> + <EnumVal ord="8">process</EnumVal> + </EnumType> + </DataTypeTemplates> +</SCL> \ No newline at end of file diff --git a/test/unit/editors/subscription/__snapshots__/publisher-goose-list.test.snap.js b/test/unit/editors/subscription/__snapshots__/publisher-goose-list.test.snap.js index ea177daf0..7bf81ed85 100644 --- a/test/unit/editors/subscription/__snapshots__/publisher-goose-list.test.snap.js +++ b/test/unit/editors/subscription/__snapshots__/publisher-goose-list.test.snap.js @@ -54,3 +54,14 @@ snapshots["publisher-goose-list looks like the latest snapshot"] = `; /* end snapshot publisher-goose-list looks like the latest snapshot */ +snapshots["publisher-goose-list looks like the latest snapshot without a doc loaded"] = +`<section> + <h1> + [subscription.publisherGoose.title] + </h1> + <mwc-list> + </mwc-list> +</section> +`; +/* end snapshot publisher-goose-list looks like the latest snapshot without a doc loaded */ + diff --git a/test/unit/editors/subscription/__snapshots__/subscriber-ied-list.test.snap.js b/test/unit/editors/subscription/__snapshots__/subscriber-ied-list.test.snap.js index 6fa76b070..a3105d42a 100644 --- a/test/unit/editors/subscription/__snapshots__/subscriber-ied-list.test.snap.js +++ b/test/unit/editors/subscription/__snapshots__/subscriber-ied-list.test.snap.js @@ -1,7 +1,7 @@ /* @web/test-runner snapshot v1 */ export const snapshots = {}; -snapshots["subscriber-ied-list looks like the latest snapshot"] = +snapshots["subscriber-ied-list initially looks like the latest snapshot"] = `<section> <h1> [subscription.subscriberIed.title] @@ -19,5 +19,5 @@ snapshots["subscriber-ied-list looks like the latest snapshot"] = </mwc-list> </section> `; -/* end snapshot subscriber-ied-list looks like the latest snapshot */ +/* end snapshot subscriber-ied-list initially looks like the latest snapshot */ diff --git a/test/unit/editors/subscription/elements/__snapshots__/goose-message.test.snap.js b/test/unit/editors/subscription/elements/__snapshots__/goose-message.test.snap.js new file mode 100644 index 000000000..9e2e07de5 --- /dev/null +++ b/test/unit/editors/subscription/elements/__snapshots__/goose-message.test.snap.js @@ -0,0 +1,19 @@ +/* @web/test-runner snapshot v1 */ +export const snapshots = {}; + +snapshots["goose-message looks like the latest snapshot"] = +`<mwc-list-item + aria-disabled="false" + graphic="large" + mwc-list-item="" + tabindex="-1" +> + <span> + GCB + </span> + <mwc-icon slot="graphic"> + </mwc-icon> +</mwc-list-item> +`; +/* end snapshot goose-message looks like the latest snapshot */ + diff --git a/test/unit/editors/subscription/elements/__snapshots__/ied-element.test.snap.js b/test/unit/editors/subscription/elements/__snapshots__/ied-element.test.snap.js new file mode 100644 index 000000000..f61bc9b17 --- /dev/null +++ b/test/unit/editors/subscription/elements/__snapshots__/ied-element.test.snap.js @@ -0,0 +1,21 @@ +/* @web/test-runner snapshot v1 */ +export const snapshots = {}; + +snapshots["ied-element looks like the latest snapshot"] = +`<mwc-list-item + aria-disabled="false" + graphic="avatar" + hasmeta="" + mwc-list-item="" + tabindex="-1" +> + <span> + IED2 + </span> + <mwc-icon slot="graphic"> + add + </mwc-icon> +</mwc-list-item> +`; +/* end snapshot ied-element looks like the latest snapshot */ + diff --git a/test/unit/editors/subscription/elements/goose-message.test.ts b/test/unit/editors/subscription/elements/goose-message.test.ts new file mode 100644 index 000000000..2e4614149 --- /dev/null +++ b/test/unit/editors/subscription/elements/goose-message.test.ts @@ -0,0 +1,41 @@ +import { html, fixture, expect } from '@open-wc/testing'; +import { spy } from 'sinon'; + +import '../../../../../src/editors/subscription/elements/goose-message.js' +import { GOOSEMessage } from '../../../../../src/editors/subscription/elements/goose-message.js'; + +describe('goose-message', () => { + let element: GOOSEMessage; + let validSCL: XMLDocument; + + let gseControl: Element; + + beforeEach(async () => { + validSCL = await fetch('/test/testfiles/valid2007B4.scd') + .then(response => response.text()) + .then(str => new DOMParser().parseFromString(str, 'application/xml')); + + gseControl = validSCL.querySelector('GSEControl[name="GCB"]')!; + + element = await fixture(html`<goose-message + .element=${gseControl} + ></goose-message>`); + }); + + it('a newGOOSESelectEvent is fired when clicking the goose message element', async () => { + const newGOOSESelectEvent = spy(); + window.addEventListener('goose-dataset', newGOOSESelectEvent); + + const listItem = <HTMLElement>(element.shadowRoot!.querySelector('mwc-list-item')); + listItem.click(); + + await element.requestUpdate(); + expect(newGOOSESelectEvent).to.have.been.called; + expect(newGOOSESelectEvent.args[0][0].detail['gseControl']).to.eql(gseControl); + expect(newGOOSESelectEvent.args[0][0].detail['dataset']).to.eql(gseControl.closest('IED')?.querySelector('DataSet[name="GooseDataSet1"]')); + }); + + it('looks like the latest snapshot', async () => { + await expect(element).shadowDom.to.equalSnapshot(); + }); +}); diff --git a/test/unit/editors/subscription/elements/ied-element.test.ts b/test/unit/editors/subscription/elements/ied-element.test.ts new file mode 100644 index 000000000..06a5ba6a7 --- /dev/null +++ b/test/unit/editors/subscription/elements/ied-element.test.ts @@ -0,0 +1,42 @@ +import { html, fixture, expect } from '@open-wc/testing'; +import { spy } from 'sinon'; + +import '../../../../../src/editors/subscription/elements/ied-element.js' +import { IEDElement } from '../../../../../src/editors/subscription/elements/ied-element.js'; +import { SubscribeStatus } from '../../../../../src/editors/subscription/foundation.js'; + +describe('ied-element', () => { + let element: IEDElement; + let validSCL: XMLDocument; + + let iedElement: Element; + + beforeEach(async () => { + validSCL = await fetch('/test/testfiles/valid2007B4.scd') + .then(response => response.text()) + .then(str => new DOMParser().parseFromString(str, 'application/xml')); + + iedElement = validSCL.querySelector('IED[name="IED2"]')!; + + element = await fixture(html`<ied-element + .element=${iedElement} + ></ied-element>`); + }); + + it('a newGOOSESelectEvent is fired when clicking the goose message element', async () => { + const newIEDSubscriptionEvent = spy(); + window.addEventListener('ied-subscription', newIEDSubscriptionEvent); + + const listItem = <HTMLElement>(element.shadowRoot!.querySelector('mwc-list-item')); + listItem.click(); + + await element.requestUpdate(); + expect(newIEDSubscriptionEvent).to.have.been.called; + expect(newIEDSubscriptionEvent.args[0][0].detail['element']).to.eql(iedElement); + expect(newIEDSubscriptionEvent.args[0][0].detail['subscribeStatus']).to.eql(SubscribeStatus.None); + }); + + it('looks like the latest snapshot', async () => { + await expect(element).shadowDom.to.equalSnapshot(); + }); +}); diff --git a/test/unit/editors/subscription/goose-message.test.ts b/test/unit/editors/subscription/goose-message.test.ts deleted file mode 100644 index fcd972e5d..000000000 --- a/test/unit/editors/subscription/goose-message.test.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { html, fixture, expect } from '@open-wc/testing'; - -import '../../../../src/editors/subscription/goose-message.js' -import { GOOSEMessage } from '../../../../src/editors/subscription/goose-message.js'; - -describe('goose-message', () => { - let element: GOOSEMessage; - let validSCL: XMLDocument; - - beforeEach(async () => { - validSCL = await fetch('/test/testfiles/valid2007B4.scd') - .then(response => response.text()) - .then(str => new DOMParser().parseFromString(str, 'application/xml')); - - element = await fixture(html`<goose-message - .element=${validSCL.querySelector('GSEControl[name="GCB"]')} - ></goose-message>`); - }); - - it('looks like the latest snapshot', async () => { - await expect(element).shadowDom.to.equalSnapshot(); - }); -}); diff --git a/test/unit/editors/subscription/publisher-goose-list.test.ts b/test/unit/editors/subscription/publisher-goose-list.test.ts index 0690448f0..19d7a8725 100644 --- a/test/unit/editors/subscription/publisher-goose-list.test.ts +++ b/test/unit/editors/subscription/publisher-goose-list.test.ts @@ -20,4 +20,10 @@ describe('publisher-goose-list', () => { it('looks like the latest snapshot', async () => { await expect(element).shadowDom.to.equalSnapshot(); }); + + it('looks like the latest snapshot without a doc loaded', async () => { + element = await fixture(html`<publisher-goose-list></publisher-goose-list>`); + + await expect(element).shadowDom.to.equalSnapshot(); + }); }); diff --git a/test/unit/editors/subscription/subscriber-ied-list.test.ts b/test/unit/editors/subscription/subscriber-ied-list.test.ts index 9bcc8aac8..4b3732dc1 100644 --- a/test/unit/editors/subscription/subscriber-ied-list.test.ts +++ b/test/unit/editors/subscription/subscriber-ied-list.test.ts @@ -17,7 +17,7 @@ describe('subscriber-ied-list', () => { ></subscriber-ied-list>`); }); - it('looks like the latest snapshot', async () => { + it('initially looks like the latest snapshot', async () => { await expect(element).shadowDom.to.equalSnapshot(); }); });