Skip to content

Commit 9d288de

Browse files
committed
Fix bugs with opc conversion
1 parent 3ef880e commit 9d288de

3 files changed

Lines changed: 155 additions & 66 deletions

File tree

packages/septic/src/cli/opc.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ interface OpcOptions {
1414
config: string;
1515
output: string;
1616
direction: "sopc-to-ua" | "ua-to-sopc";
17+
simulator?: boolean;
1718
}
1819

1920
async function loadSepticConfig(filePath: string): Promise<SepticCnfg> {
@@ -39,7 +40,11 @@ async function handler(options: OpcOptions): Promise<void> {
3940
const cnfg = await loadSepticConfig(options.config);
4041

4142
console.log(`Converting OPC objects (${options.direction})...`);
42-
const converted = convertOPCObjects(cnfg, options.direction);
43+
const converted = convertOPCObjects(
44+
cnfg,
45+
options.direction,
46+
options.simulator,
47+
);
4348

4449
const outputPath = path.resolve(options.output);
4550
const outputDir = path.dirname(outputPath);
@@ -75,10 +80,20 @@ export const opcCommand: CommandModule<object, OpcOptions> = {
7580
choices: ["sopc-to-ua", "ua-to-sopc"],
7681
demandOption: true,
7782
})
83+
.option("simulator", {
84+
alias: "s",
85+
type: "boolean",
86+
description: "Enable simulator mode",
87+
demandOption: false,
88+
})
7889
.example(
7990
"$0 opc input.cnfg output.cnfg --direction sopc-to-ua",
8091
"Convert Sopc objects to UA objects",
8192
)
93+
.example(
94+
"$0 opc input.cnfg output.cnfg --direction sopc-to-ua --simulator",
95+
"Convert Sopc objects to UA objects with simulator mode enabled",
96+
)
8297
.example(
8398
"$0 opc input.cnfg output.cnfg -d ua-to-sopc",
8499
"Convert UA objects to Sopc objects",

packages/septic/src/elements.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@ function formatNumericList(values: string[]): string {
347347

348348
function formatList(values: string[]): string {
349349
let formatted = `${values.length - 1}`;
350-
values.forEach((val) => {
350+
values.slice(1).forEach((val) => {
351351
formatted += " ".repeat(spacesBetweenValues) + val;
352352
});
353353
return formatted;

packages/septic/src/opc.ts

Lines changed: 138 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,13 @@ const SOPC_TO_UA_TYPE_MAP: Record<string, string> = {
2020
};
2121

2222
// Attribute mappings for each object type pair
23-
const UA_TO_SOPC_ATTR_MAP: Record<string, Record<string, string>> = {
23+
const UA_TO_SOPC_ATTR_MAP: Record<string, Record<string, string | string[]>> = {
2424
UAProcToSopcProc: {
2525
yPulse: "PulsTag",
2626
xSchedule: "ScheduleTag",
2727
},
2828
UAApplToSopcProc: {
2929
xAllowActive: "AllowActiveTag",
30-
xDesiredMode: "DesModeTag",
3130
yStatus: "StatusTag",
3231
},
3332
UACvr: {
@@ -47,7 +46,6 @@ const UA_TO_SOPC_ATTR_MAP: Record<string, Record<string, string>> = {
4746
xNotValid: "NotValidTag",
4847
xProcessValue: "PVTag",
4948
yCalcSetpoint: "SpCalcTag",
50-
xDesiredActive: "MvSwitchTag",
5149
yActive: "MvActiveTag",
5250
xAuto: "AutoTag",
5351
xExtern: "CompTag",
@@ -80,14 +78,13 @@ const UA_TO_SOPC_ATTR_MAP: Record<string, Record<string, string>> = {
8078
},
8179
};
8280

83-
const SOPC_TO_UA_ATTR_MAP: Record<string, Record<string, string>> = {
81+
const SOPC_TO_UA_ATTR_MAP: Record<string, Record<string, string | string[]>> = {
8482
SopcProcToUAProc: {
8583
PulsTag: "yPulse",
8684
ScheduleTag: "xSchedule",
8785
},
8886
SopcProcToUAAppl: {
89-
AllowActiveTag: "xAllowActive",
90-
DesModeTag: "xDesiredMode",
87+
AllowActiveTag: ["xAllowActive", "xDesiredMode"],
9188
StatusTag: "yStatus",
9289
},
9390

@@ -119,11 +116,10 @@ const SOPC_TO_UA_ATTR_MAP: Record<string, Record<string, string>> = {
119116
AutoTag: "xAuto",
120117
WhiTag: "xWindupHigh",
121118
WloTag: "xWindupLow",
122-
CompTag: "xExtern",
119+
CompTag: ["xExtern", "xDesiredActive"],
123120
ToCompTag: "yToComp",
124121
ToLocalTag: "yToLocal",
125122
MvActiveTag: "yActive",
126-
MvSwitchTag: "xDesiredActive",
127123
},
128124
SopcEvr: {
129125
MeasTag: "yMeas",
@@ -154,14 +150,19 @@ export function sopcToUA(obj: SepticObject): SepticObject {
154150
const uaAttrName = attrMap[attr.key];
155151
if (uaAttrName) {
156152
const value = getUAValue(attr);
157-
uaObj
158-
.getAttribute(uaAttrName)
159-
?.setValues(
160-
createAttrValues(
161-
[value.length ? `"s=${value}"` : `""`],
162-
SepticTokenType.string,
163-
),
164-
);
153+
const uaAttrNames = Array.isArray(uaAttrName)
154+
? uaAttrName
155+
: [uaAttrName];
156+
for (const name of uaAttrNames) {
157+
uaObj
158+
.getAttribute(name)
159+
?.setValues(
160+
createAttrValues(
161+
[value.length ? `"s=${value}"` : `""`],
162+
SepticTokenType.string,
163+
),
164+
);
165+
}
165166
}
166167
}
167168
return uaObj;
@@ -177,6 +178,10 @@ function getUAValue(attr: SepticAttribute): string {
177178
}
178179
return value;
179180
}
181+
182+
function getSopcValue(attr: SepticAttribute): string {
183+
return attr.getFirstValue()?.replace("s=", "") || "";
184+
}
180185
/**
181186
* Converts a UA object to a Sopc object
182187
* @param obj The UA object to convert
@@ -198,24 +203,56 @@ export function uaToSopc(obj: SepticObject): SepticObject {
198203
for (const attr of obj.attributes) {
199204
const sopcAttrName = attrMap[attr.key];
200205
if (sopcAttrName) {
201-
sopcObj
202-
.getAttribute(sopcAttrName)
203-
?.setValues(
204-
createAttrValues(
205-
[attr.values[0]?.value.replace("s=", "") || ""],
206-
SepticTokenType.string,
207-
),
208-
);
206+
const sopcAttrNames = Array.isArray(sopcAttrName)
207+
? sopcAttrName
208+
: [sopcAttrName];
209+
for (const name of sopcAttrNames) {
210+
const value = getSopcValue(attr);
211+
if (value === "") {
212+
continue;
213+
}
214+
sopcObj
215+
.getAttribute(name)
216+
?.setValues(
217+
createAttrValues(
218+
[`"${value}"`],
219+
SepticTokenType.string,
220+
),
221+
);
222+
}
209223
}
210224
}
211225
return sopcObj;
212226
}
213227

214-
function sopcToUAProcAndAppl(sopcProc: SepticObject): SepticObject[] {
228+
function sopcToUAProcAndAppl(
229+
sopcProc: SepticObject,
230+
simulator: boolean = false,
231+
): SepticObject[] {
215232
const generator = new SepticObjectGenerator();
233+
const attributes = [
234+
{
235+
key: "DefaultNameSpaceURI",
236+
type: SepticTokenType.string,
237+
values: ['"urn:equinor:MiniOpcUaServer"'],
238+
},
239+
{
240+
key: "SecurityMode",
241+
type: SepticTokenType.identifier,
242+
values: ["UA_MESSAGESECURITYMODE_NONE"],
243+
},
244+
];
245+
if (simulator) {
246+
attributes.push({
247+
key: "RunMenu",
248+
type: SepticTokenType.identifier,
249+
values: ["ON"],
250+
});
251+
}
216252
const uaProc = generator.createObject(
217253
"UAProc",
218254
sopcProc.identifier?.name || "",
255+
attributes,
219256
);
220257
const uaAppl = generator.createObject(
221258
"UAAppl",
@@ -228,38 +265,52 @@ function sopcToUAProcAndAppl(sopcProc: SepticObject): SepticObject[] {
228265
const uaProcAttr = attrsMapUAProc[attr.key];
229266
const value = getUAValue(attr);
230267
if (uaProcAttr) {
231-
uaProc
232-
.getAttribute(uaProcAttr)
233-
?.setValues(
234-
createAttrValues(
235-
[value.length ? `"s=${value}"` : `""`],
236-
SepticTokenType.string,
237-
),
238-
);
268+
const uaProcAttrNames = Array.isArray(uaProcAttr)
269+
? uaProcAttr
270+
: [uaProcAttr];
271+
for (const name of uaProcAttrNames) {
272+
uaProc
273+
.getAttribute(name)
274+
?.setValues(
275+
createAttrValues(
276+
[value.length ? `"s=${value}"` : `""`],
277+
SepticTokenType.string,
278+
),
279+
);
280+
}
239281
}
240282
const uaApplAttr = attrsMapUAAppl[attr.key];
241283
if (uaApplAttr) {
242-
uaAppl
243-
.getAttribute(uaApplAttr)
244-
?.setValues(
245-
createAttrValues(
246-
[value.length ? `"s=${value}"` : `""`],
247-
SepticTokenType.string,
248-
),
249-
);
284+
const uaApplAttrNames = Array.isArray(uaApplAttr)
285+
? uaApplAttr
286+
: [uaApplAttr];
287+
for (const name of uaApplAttrNames) {
288+
uaAppl
289+
.getAttribute(name)
290+
?.setValues(
291+
createAttrValues(
292+
[value.length ? `"s=${value}"` : `""`],
293+
SepticTokenType.string,
294+
),
295+
);
296+
}
250297
}
251298
}
299+
if (simulator) {
300+
return [uaProc];
301+
}
252302
return [uaProc, uaAppl];
253303
}
254304

255305
function uaProcAndApplToSopc(
256306
uaProc: SepticObject,
257-
uaAppl: SepticObject,
307+
uaAppl: SepticObject | undefined,
308+
simulator: boolean = false,
258309
): SepticObject | undefined {
259310
const generator = new SepticObjectGenerator();
260311
const sopcProc = generator.createObject(
261312
"SopcProc",
262-
uaAppl.identifier?.name || "",
313+
uaAppl?.identifier?.name || uaProc.identifier?.name || "",
263314
[
264315
{
265316
key: "ServName",
@@ -268,8 +319,8 @@ function uaProcAndApplToSopc(
268319
},
269320
{
270321
key: "Site",
271-
type: SepticTokenType.string,
272-
values: ["AIM_AIMGUI"],
322+
type: SepticTokenType.identifier,
323+
values: [simulator ? "OPCSIMUL" : "AIM_AIMGUI"],
273324
},
274325
],
275326
);
@@ -279,27 +330,44 @@ function uaProcAndApplToSopc(
279330
for (const attr of uaProc.attributes) {
280331
const sopcAttr = attrsMapUAProc[attr.key];
281332
if (sopcAttr) {
282-
sopcProc
283-
.getAttribute(sopcAttr)
284-
?.setValues(
285-
createAttrValues(
286-
[attr.values[0]?.value.replace("s=", "") || ""],
287-
SepticTokenType.string,
288-
),
289-
);
333+
const sopcAttrNames = Array.isArray(sopcAttr)
334+
? sopcAttr
335+
: [sopcAttr];
336+
for (const name of sopcAttrNames) {
337+
const value = getSopcValue(attr);
338+
if (value === "") {
339+
continue;
340+
}
341+
sopcProc
342+
.getAttribute(name)
343+
?.setValues(
344+
createAttrValues(
345+
[`"${value}"`],
346+
SepticTokenType.string,
347+
),
348+
);
349+
}
290350
}
291351
}
352+
if (!uaAppl) {
353+
return sopcProc;
354+
}
292355
for (const attr of uaAppl.attributes) {
293356
const sopcAttr = attrsMapUAAppl[attr.key];
294357
if (sopcAttr) {
295-
sopcProc
296-
.getAttribute(sopcAttr)
297-
?.setValues(
298-
createAttrValues(
299-
[attr.values[0]?.value.replace("s=", "") || ""],
300-
SepticTokenType.string,
301-
),
302-
);
358+
const sopcAttrNames = Array.isArray(sopcAttr)
359+
? sopcAttr
360+
: [sopcAttr];
361+
for (const name of sopcAttrNames) {
362+
sopcProc
363+
.getAttribute(name)
364+
?.setValues(
365+
createAttrValues(
366+
[attr.values[0]?.value.replace("s=", "") || ""],
367+
SepticTokenType.string,
368+
),
369+
);
370+
}
303371
}
304372
}
305373
return sopcProc;
@@ -308,22 +376,28 @@ function uaProcAndApplToSopc(
308376
export function convertOPCObjects(
309377
cnfg: SepticCnfg,
310378
direction: "sopc-to-ua" | "ua-to-sopc",
379+
simulator: boolean = false,
311380
): SepticCnfg {
312381
const typeMap =
313382
direction === "sopc-to-ua" ? SOPC_TO_UA_TYPE_MAP : UA_TO_SOPC_TYPE_MAP;
314383
const convertFn = direction === "sopc-to-ua" ? sopcToUA : uaToSopc;
315384
for (let i = 0; i < cnfg.objects.length; i++) {
316385
const obj = cnfg.objects[i]!;
317386
if (obj.type == "SopcProc" && direction === "sopc-to-ua") {
318-
const uaObjects = sopcToUAProcAndAppl(obj);
387+
const uaObjects = sopcToUAProcAndAppl(obj, simulator);
319388
cnfg.objects.splice(i, 1, ...uaObjects);
320389
i += uaObjects.length - 1;
321390
continue;
322391
}
323392
if (obj.type == "UAProc" && direction === "ua-to-sopc") {
324-
const sopcProc = uaProcAndApplToSopc(obj, cnfg.objects[i + 1]!);
393+
const uaAppls = cnfg.getObjectsByType("UAAppl");
394+
const sopcProc = uaProcAndApplToSopc(
395+
obj,
396+
uaAppls.length ? uaAppls[0] : undefined,
397+
simulator,
398+
);
325399
if (sopcProc) {
326-
cnfg.objects.splice(i, 2, sopcProc);
400+
cnfg.objects.splice(i, uaAppls.length ? 2 : 1, sopcProc);
327401
}
328402
}
329403
if (typeMap[obj.type]) {

0 commit comments

Comments
 (0)