Skip to content

Commit 664eb0b

Browse files
author
Dennis Labordus
authored
Merge pull request #799 from openscd/104-add-address
feat(104/AddAddress): Add Address Element (including SCL Private Element) for first set of CDC's
2 parents 440a2f6 + 2a41d07 commit 664eb0b

File tree

19 files changed

+771
-87
lines changed

19 files changed

+771
-87
lines changed

src/editors/protocol104/doi-container.ts

+6-20
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,14 @@ import { newWizardEvent } from "../../foundation.js";
2323
import '../../action-pane.js';
2424

2525
import {
26+
get104DetailsLine,
2627
getCdcValue,
2728
getFullPath,
2829
PRIVATE_TYPE_104
2930
} from "./foundation/foundation.js";
3031
import { editAddressWizard } from "./wizards/address.js";
3132
import { showDOIInfoWizard } from "./wizards/doi.js";
33+
import { } from "./foundation/private.js";
3234

3335

3436
/**
@@ -58,30 +60,14 @@ export class Doi104Container extends LitElement {
5860
this.requestUpdate();
5961
}
6062

61-
private openEditAddressWizard(addressElement: Element): void {
62-
this.dispatchEvent(newWizardEvent(editAddressWizard(addressElement)));
63+
private openEditAddressWizard(daiElement: Element, addressElement: Element): void {
64+
this.dispatchEvent(newWizardEvent(editAddressWizard(daiElement, addressElement)));
6365
}
6466

6567
private openEditTiWizard(): void {
6668
this.dispatchEvent(newWizardEvent(showDOIInfoWizard(this.element)));
6769
}
6870

69-
private static get104DetailsLine(address: Element): string {
70-
const values = [];
71-
72-
if (address.hasAttribute('casdu')) values.push(`casdu: ${address.getAttribute('casdu')}`);
73-
if (address.hasAttribute('ioa')) values.push(`ioa: ${address.getAttribute('ioa')}`);
74-
if (address.hasAttribute('ti')) values.push(`ti: ${address.getAttribute('ti')}`);
75-
if (address.hasAttribute('expectedValue')) values.push(`expectedValue: ${address.getAttribute('expectedValue')}`);
76-
if (address.hasAttribute('unitMultiplier')) values.push(`unitMultiplier: ${address.getAttribute('unitMultiplier')}`);
77-
if (address.hasAttribute('scaleMultiplier')) values.push(`scaleMultiplier: ${address.getAttribute('scaleMultiplier')}`);
78-
if (address.hasAttribute('scaleOffset')) values.push(`scaleOffset: ${address.getAttribute('scaleOffset')}`);
79-
if (address.hasAttribute('inverted')) values.push(`inverted: ${address.getAttribute('inverted')}`);
80-
if (address.hasAttribute('check')) values.push(`check: ${address.getAttribute('check')}`);
81-
82-
return values.join(", ");
83-
}
84-
8571
private header(): TemplateResult {
8672
const fullPath = getFullPath(this.element, 'IED')
8773
const cdc = getCdcValue(this.element);
@@ -95,11 +81,11 @@ export class Doi104Container extends LitElement {
9581
return html`
9682
<mwc-list-item graphic="icon" hasMeta>
9783
<span slot="graphic">&nbsp;</span>
98-
<span>${Doi104Container.get104DetailsLine(addressElement)}</span>
84+
<span>${get104DetailsLine(addressElement)}</span>
9985
<span slot="meta">
10086
<mwc-icon-button
10187
icon="edit"
102-
@click=${() => this.openEditAddressWizard(addressElement)}>
88+
@click=${() => this.openEditAddressWizard(daiElement, addressElement)}>
10389
</mwc-icon-button>
10490
</span>
10591
</mwc-list-item>
+187
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
import { Create } from "../../../foundation.js";
2+
3+
import {addPrefixAndNamespaceToDocument, createPrivateAddress, createPrivateElement} from "./private.js";
4+
5+
/**
6+
* List of supported Common Data Classes in the 104 protocol.
7+
*/
8+
export const supportedCdcTypes = ['ACT', 'ASG', 'BCR', 'CMV', 'DPS', 'ING', 'INS', 'MV', 'SEC', 'SPG', 'SPS'] as const;
9+
export type SupportedCdcType = typeof supportedCdcTypes[number];
10+
11+
type CreateFunction = (daiElement: Element, selectedTi: string) => Create[];
12+
13+
/**
14+
* Record with configuration information on how to create Address elements for the 104 protocol.
15+
* Per supported Common Data Class (CDC) two record sets can be configured, one for the monitoring part
16+
* and one for the control part.
17+
* Per set the key of the record will be the ti value, meaning the list of keys will be the supported
18+
* ti values allowed for the CDC.
19+
* For each supported ti value there is information on how to find the DAI Element to which to create
20+
* the Address element(s).
21+
*/
22+
export const cdcProcessings: Record<
23+
SupportedCdcType,
24+
{
25+
monitor: Record<string, {
26+
filter: string;
27+
create: CreateFunction;
28+
}>,
29+
control: Record<string, {
30+
filter: string;
31+
create: CreateFunction;
32+
}>,
33+
}
34+
> = {
35+
ACT: {
36+
monitor: {
37+
'30': {
38+
filter: 'DAI[name="general"], DAI[name="phsA"], DAI[name="phsB"], DAI[name="phsC"], DAI[name="neut"]',
39+
create: createSingleAddressAction
40+
},
41+
'39': {
42+
filter: 'DAI[name="general"]',
43+
create: createSingleAddressAction
44+
}
45+
},
46+
control: {}
47+
},
48+
ASG: {
49+
monitor: {
50+
'63': {
51+
filter: 'SDI[name="setMag"] > DAI[name="f"]',
52+
create: createSingleAddressAction
53+
}
54+
},
55+
control: {}
56+
},
57+
BCR: {
58+
monitor: {
59+
'37': {
60+
filter: 'DAI[name="actVal"], DAI[name="frVal"]',
61+
create: createSingleAddressAction
62+
},
63+
},
64+
control: {}
65+
},
66+
CMV: {
67+
monitor: {
68+
'35': {
69+
filter: 'SDI[name="mag"] > DAI[name="i"], SDI[name="ang"] > DAI[name="i"]',
70+
create: createSingleAddressAction
71+
},
72+
'36': {
73+
filter: 'SDI[name="mag"] > DAI[name="f"], SDI[name="ang"] > DAI[name="f"]',
74+
create: createSingleAddressAction
75+
}
76+
},
77+
control: {}
78+
},
79+
DPS: {
80+
monitor: {
81+
'31': {
82+
filter: 'DAI[name="stVal"]',
83+
create: createSingleAddressAction
84+
}
85+
},
86+
control: {}
87+
},
88+
ING: {
89+
monitor: {
90+
'62': {
91+
filter: 'DAI[name="setVal"]',
92+
create: createSingleAddressAction
93+
}
94+
},
95+
control: {}
96+
},
97+
INS: {
98+
monitor: {
99+
'30': {
100+
filter: 'DAI[name="stVal"]',
101+
create: createInvertedAddressAction
102+
},
103+
'33': {
104+
filter: 'DAI[name="stVal"]',
105+
create: createSingleAddressAction
106+
},
107+
'35': {
108+
filter: 'DAI[name="stVal"]',
109+
create: createSingleAddressAction
110+
}
111+
},
112+
control: {}
113+
},
114+
MV: {
115+
monitor: {
116+
'35': {
117+
filter: 'SDI[name="mag"] > DAI[name="i"]',
118+
create: createSingleAddressAction
119+
},
120+
'36': {
121+
filter: 'SDI[name="mag"] > DAI[name="f"]',
122+
create: createSingleAddressAction
123+
}
124+
},
125+
control: {}
126+
},
127+
SEC: {
128+
monitor: {
129+
'37': {
130+
filter: 'DAI[name="cnt"]',
131+
create: createSingleAddressAction
132+
}
133+
},
134+
control: {}
135+
},
136+
SPG: {
137+
monitor: {
138+
'58': {
139+
filter: 'DAI[name="setVal"]',
140+
create: createSingleAddressAction
141+
}
142+
},
143+
control: {}
144+
},
145+
SPS: {
146+
monitor: {
147+
'30': {
148+
filter: 'DAI[name="stVal"]',
149+
create: createSingleAddressAction
150+
}
151+
},
152+
control: {}
153+
},
154+
};
155+
156+
/**
157+
* Create a new SCL Private element and add one 104 Address element below this.
158+
* Set the attribute value of 'ti' to the passed ti value.
159+
*
160+
* @param daiElement - The DAI Element to use to set namespace on the root element and create new elements.
161+
* @param ti - The value to be set on the attribute 'ti'.
162+
*/
163+
function createSingleAddressAction(daiElement: Element, ti: string): Create[] {
164+
addPrefixAndNamespaceToDocument(daiElement);
165+
166+
const privateElement = createPrivateElement(daiElement);
167+
createPrivateAddress(daiElement, privateElement, ti);
168+
return [{new: {parent: daiElement, element: privateElement}}];
169+
}
170+
171+
/**
172+
* Create a new SCL Private element and add two 104 Address elements, one without the attribute 'inverted' meaning
173+
* 'false' and the other element with the attribute 'inverted' to 'true'.
174+
* Also set the attribute value of 'ti' to the passed ti value.
175+
*
176+
* @param daiElement - The DAI Element to use to set namespace on the root element and create new elements.
177+
* @param ti - The value to be set on the attribute 'ti'.
178+
*/
179+
function createInvertedAddressAction(daiElement: Element, ti: string): Create[] {
180+
addPrefixAndNamespaceToDocument(daiElement);
181+
182+
const privateElement = createPrivateElement(daiElement);
183+
createPrivateAddress(daiElement, privateElement, ti);
184+
const invertedAddressElement = createPrivateAddress(daiElement, privateElement, ti);
185+
invertedAddressElement.setAttribute('inverted', 'true');
186+
return [{new: {parent: daiElement, element: privateElement}}];
187+
}

src/editors/protocol104/foundation/foundation.ts

+29
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,35 @@ export function getCdcValue(doiElement: Element): string | null {
6868
return null;
6969
}
7070

71+
/**
72+
* All available Address attributes that can be displayed.
73+
*/
74+
const addressAttributes = [
75+
'casdu',
76+
'ioa',
77+
'ti',
78+
'expectedValue',
79+
'unitMultiplier',
80+
'scaleMultiplier',
81+
'scaleOffset',
82+
'inverted',
83+
'check',
84+
];
85+
86+
/**
87+
* Create a string to display all information about a 104 Address element.
88+
* A list of attributes is used to determine what can be displayed if available.
89+
*
90+
* @param address - The Address element from which to retrieve all attribute values.
91+
* @returns A string to display with all attribute values.
92+
*/
93+
export function get104DetailsLine(address: Element): string {
94+
return addressAttributes
95+
.filter(attrName => address.hasAttribute(attrName))
96+
.map(attrName => `${attrName}: ${address.getAttribute(attrName)}`)
97+
.join(', ');
98+
}
99+
71100
/**
72101
* Indicates if the combination cdc/ti should handle/process the attribute "expected" of the Address Element.
73102
*
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
export const PROTOCOL_104_PRIVATE = "IEC_60870_5_104";
2+
export const PROTOCOL_104_NS = "http://www.iec.ch/61850-80-1/2007/IEC_60870-5-104";
3+
export const PROTOCOL_104_PREFIX = "IEC_60870_5_104";
4+
5+
6+
/**
7+
* Will add the namespace of the 104 Protocol to the Root Element of the Document (SCL) as prefix to
8+
* be used with all 104 elements (Address).
9+
*
10+
* @param element - Element to get the Document and Root Element from.
11+
*/
12+
export function addPrefixAndNamespaceToDocument(element: Element): void {
13+
const rootElement = element.ownerDocument.firstElementChild!;
14+
if (!rootElement.hasAttribute('xmlns:' + PROTOCOL_104_PREFIX)) {
15+
rootElement.setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:' + PROTOCOL_104_PREFIX, PROTOCOL_104_NS);
16+
}
17+
}
18+
19+
/**
20+
* Create an SCL Private Element with the type set to the 104 Protocol.
21+
*
22+
* @param daiElement - The DAI Element used to create the new element from.
23+
* @returns The created Private Element, <b>not</b> yet added to the DAI Element.
24+
*/
25+
export function createPrivateElement(daiElement: Element): Element {
26+
const privateElement = daiElement.ownerDocument.createElement("Private");
27+
privateElement.setAttribute('type', PROTOCOL_104_PRIVATE);
28+
return privateElement;
29+
}
30+
31+
/**
32+
* Create a 104 Address element which will be added to the passed Private element. The attribute 'ti' will
33+
* also be set to value passed.
34+
*
35+
* @param daiElement - The DAI Element used to create the new element from.
36+
* @param privateElement - The Private Element to which the Address element will be added.
37+
* @param ti - The value for the attribute 'ti'.
38+
*/
39+
export function createPrivateAddress(daiElement: Element, privateElement: Element, ti: string): Element {
40+
const addressElement = daiElement.ownerDocument.createElementNS(PROTOCOL_104_NS, "Address");
41+
addressElement.setAttribute('ti', ti);
42+
privateElement.append(addressElement);
43+
return addressElement;
44+
}

src/editors/protocol104/values-container.ts

+26-4
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,17 @@ import {
66
property,
77
TemplateResult
88
} from "lit-element";
9+
import { get, translate } from "lit-translate";
910

10-
import { compareNames } from "../../foundation.js";
11+
import {
12+
compareNames,
13+
newWizardEvent
14+
} from "../../foundation.js";
1115

1216
import './ied-container.js';
1317

1418
import { PRIVATE_TYPE_104 } from "./foundation/foundation.js";
15-
import {translate} from "lit-translate";
19+
import { selectDoiWizard } from "./wizards/selectDoi.js";
1620

1721
/**
1822
* Container that will render an 'ied-104-container' for every IED which contains DAI Elements related to the
@@ -29,22 +33,40 @@ export class Values104Container extends LitElement {
2933
.sort((a,b) => compareNames(a,b));
3034
}
3135

36+
37+
/** Opens a [[`WizardDialog`]] for creating a new `Substation` element. */
38+
private openCreateAddressWizard(): void {
39+
this.dispatchEvent(newWizardEvent(selectDoiWizard(this.doc)));
40+
}
41+
3242
render(): TemplateResult {
3343
const ieds = this.getIEDElements();
3444
if (ieds.length > 0) {
3545
return html `
3646
${ieds.map(iedElement => {
3747
return html `<ied-104-container .element="${iedElement}"></ied-104-container>`;
3848
})}
39-
`;
49+
<h1>
50+
<mwc-fab extended
51+
icon="add"
52+
label="${get('protocol104.wizard.title.addAddress')}"
53+
@click=${() => this.openCreateAddressWizard()}>
54+
</mwc-fab>
55+
</h1> `;
4056
}
4157
return html `
4258
<h1>
43-
<span style="color: var(--base1)">${translate('protocol104.missing')}</span>
59+
<span style="color: var(--base1)">${translate('protocol104.values.missing')}</span>
4460
</h1>`;
4561
}
4662

4763
static styles = css `
64+
mwc-fab {
65+
position: fixed;
66+
bottom: 32px;
67+
right: 32px;
68+
}
69+
4870
h1 {
4971
color: var(--mdc-theme-on-surface);
5072
font-family: 'Roboto', sans-serif;

0 commit comments

Comments
 (0)