• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

WindhoverLabs / yamcs-opcua / #17

11 Jul 2024 05:27PM UTC coverage: 69.565% (+2.9%) from 66.703%
#17

push

lorenzo-gomez-windhover
-Add intrinsic types for OPCUA nodes. WIP.
-Update unit tests.

35 of 78 new or added lines in 1 file covered. (44.87%)

188 existing lines in 1 file now uncovered.

688 of 989 relevant lines covered (69.57%)

0.7 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

69.13
/src/main/java/com/windhoverlabs/yamcs/opcua/OPCUALink.java
1
/****************************************************************************
2
 *
3
 *   Copyright (c) 2024 Windhover Labs, L.L.C. All rights reserved.
4
 *
5
 * Redistribution and use in source and binary forms, with or without
6
 * modification, are permitted provided that the following conditions
7
 * are met:
8
 *
9
 * 1. Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 * 2. Redistributions in binary form must reproduce the above copyright
12
 *    notice, this list of conditions and the following disclaimer in
13
 *    the documentation and/or other materials provided with the
14
 *    distribution.
15
 * 3. Neither the name Windhover Labs nor the names of its
16
 *    contributors may be used to endorse or promote products derived
17
 *    from this software without specific prior written permission.
18
 *
19
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
22
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
23
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
24
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
25
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
26
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
27
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
29
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30
 * POSSIBILITY OF SUCH DAMAGE.
31
 *
32
 *****************************************************************************/
33

34
package com.windhoverlabs.yamcs.opcua;
35

36
import static com.google.common.collect.Lists.newArrayList;
37
import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.uint;
38
import static org.eclipse.milo.opcua.stack.core.util.ConversionUtil.toList;
39
import static org.yamcs.parameter.SystemParametersService.getPV;
40
import static org.yamcs.xtce.NameDescription.qualifiedName;
41

42
import com.google.gson.JsonObject;
43
import java.io.BufferedWriter;
44
import java.io.File;
45
import java.nio.file.Files;
46
import java.nio.file.Path;
47
import java.nio.file.Paths;
48
import java.nio.file.StandardOpenOption;
49
import java.time.Instant;
50
import java.time.temporal.ChronoUnit;
51
import java.util.ArrayList;
52
import java.util.Arrays;
53
import java.util.HashMap;
54
import java.util.HashSet;
55
import java.util.List;
56
import java.util.Map;
57
import java.util.Objects;
58
import java.util.Set;
59
import java.util.concurrent.CompletableFuture;
60
import java.util.concurrent.ConcurrentHashMap;
61
import java.util.concurrent.ExecutionException;
62
import java.util.concurrent.atomic.AtomicLong;
63
import java.util.function.Supplier;
64
import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
65
import org.eclipse.milo.opcua.sdk.client.api.config.OpcUaClientConfig;
66
import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaMonitoredItem;
67
import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaSubscription;
68
import org.eclipse.milo.opcua.sdk.client.nodes.UaNode;
69
import org.eclipse.milo.opcua.sdk.client.subscriptions.ManagedDataItem;
70
import org.eclipse.milo.opcua.sdk.client.subscriptions.ManagedSubscription;
71
import org.eclipse.milo.opcua.stack.client.DiscoveryClient;
72
import org.eclipse.milo.opcua.stack.core.AttributeId;
73
import org.eclipse.milo.opcua.stack.core.Identifiers;
74
import org.eclipse.milo.opcua.stack.core.UaException;
75
import org.eclipse.milo.opcua.stack.core.types.builtin.ByteString;
76
import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue;
77
import org.eclipse.milo.opcua.stack.core.types.builtin.DateTime;
78
import org.eclipse.milo.opcua.stack.core.types.builtin.ExtensionObject;
79
import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText;
80
import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId;
81
import org.eclipse.milo.opcua.stack.core.types.builtin.QualifiedName;
82
import org.eclipse.milo.opcua.stack.core.types.builtin.StatusCode;
83
import org.eclipse.milo.opcua.stack.core.types.builtin.Variant;
84
import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger;
85
import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UShort;
86
import org.eclipse.milo.opcua.stack.core.types.enumerated.BrowseDirection;
87
import org.eclipse.milo.opcua.stack.core.types.enumerated.BrowseResultMask;
88
import org.eclipse.milo.opcua.stack.core.types.enumerated.IdType;
89
import org.eclipse.milo.opcua.stack.core.types.enumerated.MonitoringMode;
90
import org.eclipse.milo.opcua.stack.core.types.enumerated.NodeClass;
91
import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn;
92
import org.eclipse.milo.opcua.stack.core.types.structured.BrowseDescription;
93
import org.eclipse.milo.opcua.stack.core.types.structured.BrowsePath;
94
import org.eclipse.milo.opcua.stack.core.types.structured.BrowsePathResult;
95
import org.eclipse.milo.opcua.stack.core.types.structured.BrowseResult;
96
import org.eclipse.milo.opcua.stack.core.types.structured.ContentFilter;
97
import org.eclipse.milo.opcua.stack.core.types.structured.EndpointDescription;
98
import org.eclipse.milo.opcua.stack.core.types.structured.EventFilter;
99
import org.eclipse.milo.opcua.stack.core.types.structured.MonitoredItemCreateRequest;
100
import org.eclipse.milo.opcua.stack.core.types.structured.MonitoringParameters;
101
import org.eclipse.milo.opcua.stack.core.types.structured.ReadValueId;
102
import org.eclipse.milo.opcua.stack.core.types.structured.ReferenceDescription;
103
import org.eclipse.milo.opcua.stack.core.types.structured.RelativePath;
104
import org.eclipse.milo.opcua.stack.core.types.structured.RelativePathElement;
105
import org.eclipse.milo.opcua.stack.core.types.structured.SimpleAttributeOperand;
106
import org.eclipse.milo.opcua.stack.core.types.structured.TranslateBrowsePathsToNodeIdsResponse;
107
import org.slf4j.Logger;
108
import org.slf4j.LoggerFactory;
109
import org.yamcs.ConfigurationException;
110
import org.yamcs.Spec;
111
import org.yamcs.Spec.OptionType;
112
import org.yamcs.StandardTupleDefinitions;
113
import org.yamcs.ValidationException;
114
import org.yamcs.YConfiguration;
115
import org.yamcs.YamcsServer;
116
import org.yamcs.http.NotFoundException;
117
import org.yamcs.mdb.XtceAssembler;
118
import org.yamcs.parameter.ParameterValue;
119
import org.yamcs.parameter.SystemParametersProducer;
120
import org.yamcs.parameter.SystemParametersService;
121
import org.yamcs.protobuf.Event.EventSeverity;
122
import org.yamcs.protobuf.Yamcs.NamedObjectId;
123
import org.yamcs.protobuf.Yamcs.Value.Type;
124
import org.yamcs.tctm.AbstractLink;
125
import org.yamcs.tctm.Link;
126
import org.yamcs.tctm.LinkAction;
127
import org.yamcs.utils.ValueUtility;
128
import org.yamcs.xtce.AbsoluteTimeParameterType;
129
import org.yamcs.xtce.AggregateParameterType;
130
import org.yamcs.xtce.BinaryParameterType;
131
import org.yamcs.xtce.BooleanParameterType;
132
import org.yamcs.xtce.EnumeratedParameterType;
133
import org.yamcs.xtce.FloatParameterType;
134
import org.yamcs.xtce.IntegerParameterType;
135
import org.yamcs.xtce.Member;
136
import org.yamcs.xtce.NameDescription;
137
import org.yamcs.xtce.Parameter;
138
import org.yamcs.xtce.ParameterType;
139
import org.yamcs.xtce.SpaceSystem;
140
import org.yamcs.xtce.StringParameterType;
141
import org.yamcs.xtce.XtceDb;
142
import org.yamcs.yarch.DataType;
143
import org.yamcs.yarch.Stream;
144
import org.yamcs.yarch.Tuple;
145
import org.yamcs.yarch.TupleDefinition;
146
import org.yamcs.yarch.YarchDatabase;
147
import org.yamcs.yarch.YarchDatabaseInstance;
148
import org.yamcs.yarch.protobuf.Db.Event;
149

150
/**
151
 * Implementation of the OPCUA protocol as a YAMCS link. Maps configured nodes(see docs for details)
152
 * to yamcs PVs and subscribes to OPCUA variables for realtime updates.
153
 *
154
 * @author Lorenzo Gomez
155
 */
156
public class OPCUALink extends AbstractLink implements Runnable, SystemParametersProducer {
1✔
157

158
  class NodeIDAttrPair {
159
    NodeId nodeID;
160
    AttributeId attrID;
161

162
    public NodeIDAttrPair(NodeId newNodeID, AttributeId newAttrID) {
1✔
163
      this.nodeID = newNodeID;
1✔
164
      this.attrID = newAttrID;
1✔
165
    }
1✔
166

167
    public int hashCode() {
168
      return Objects.hash(this.nodeID, this.attrID);
1✔
169
    }
170

171
    public boolean equals(Object obj) {
172
      return (this.hashCode() == obj.hashCode());
1✔
173
    }
174
  }
175

176
  class NodePath {
1✔
177
    String path;
178

179
    HashMap<Object, Object> rootNodeID = new HashMap<Object, Object>();
1✔
180
  }
181

182
  public enum OPCUAStatus {
1✔
183
    OPCUA_INIT_CONFIG,
1✔
184
    OPCUA_INIT_TREE,
1✔
185
    OPCUA_INIT_GENERATE_XTCE,
1✔
186
    OPCUA_INIT_EVENTS,
1✔
187
    OPCUA_INIT_DATA_SUBSCRIPTION,
1✔
188
    OPCUA_INIT_ALL_DATA_QUERY,
1✔
189
    OPCUA_OK
1✔
190
  }
191

192
  /* Configuration Defaults */
193
  static final String STREAM_NAME = "opcua_params";
194

195
  /* Internal member attributes. */
196
  protected Thread thread;
197

198
  private String opcuaStreamName;
199

200
  // /yamcs/<server_id>
201
  private String parametersNamespace;
202
  XtceDb mdb;
203

204
  Stream opcuaStream;
205

206
  private static TupleDefinition gftdef = StandardTupleDefinitions.PARAMETER.copy();
1✔
207

208
  private AggregateParameterType opcuaAttrsType;
209
  private AggregateParameterType opcuaNodeIdNumericType;
210
  private AggregateParameterType opcuaNodeIdStringType;
211
  private ManagedSubscription opcuaSubscription;
212

213
  private static final Logger internalLogger = LoggerFactory.getLogger(OPCUALink.class.getName());
1✔
214

215
  private int rootNamespaceIndex;
216

217
  private String rootIdentifier; // Relative to the rootNamespaceIndex
218

219
  private IdType rootIdentifierType; // Relative to the rootNamespaceIndex
220

221
  /**
222
   * @note ALWAYS re-use params as org.yamcs.parameter.ParameterRequestManager.param2RequestMap uses
223
   *     the object inside a map that was added to the mdb for the very fist time. If when
224
   *     publishing the PV, we create a new VariableParam object clients will NOT receive real-time
225
   *     updates as the new object VariableParam inside the new PV won't match the one inside
226
   *     org.yamcs.parameter.ParameterRequestManager.param2RequestMap since the object hashes do not
227
   *     match (since VariableParam does not override its hash function).
228
   */
229
  private ConcurrentHashMap<NodeIDAttrPair, VariableParam> nodeIDToParamsMap =
1✔
230
      new ConcurrentHashMap<NodeIDAttrPair, VariableParam>();
231

232
  private OpcUaClient client;
233

234
  protected AtomicLong inCount = new AtomicLong(0);
1✔
235

236
  private Status linkStatus = Status.OK;
1✔
237

238
  /* Configuration Parameters */
239

240
  private String discoverURL;
241
  private String endpointURL;
242
  private boolean queryAllNodesAtStartup;
243
  private String outputFile;
244
  private int publishInterval; // milliseconds
245

246
  private ArrayList<NodePath> relativeNodePaths = new ArrayList<NodePath>();
1✔
247

248
  private final AtomicLong clientHandles = new AtomicLong(1L);
1✔
249

250
  /* System parameters*/
251

252
  private Parameter OPCUAStatusParam;
253
  private OPCUAStatus currentOPCUAStatus;
254
  private Parameter OPCUAActiveSubsParam;
255
  private AtomicLong OPCUAActiveSubs = new AtomicLong(0);
1✔
256

257
  LinkAction startAction =
1✔
258
      new LinkAction("query_all", "Query All OPCUA Server Data") {
1✔
259
        @Override
260
        public JsonObject execute(Link link, JsonObject jsonObject) {
261

262
          internalLogger.info("Executing query_all action");
1✔
263
          CompletableFuture.supplyAsync(
1✔
264
                  (Supplier<Integer>)
265
                      () -> {
UNCOV
266
                        queryAllOPCUAData();
×
267

UNCOV
268
                        return 0;
×
269
                      })
270
              .whenComplete(
1✔
271
                  (vaue, e) -> {
272
                    internalLogger.info("query_all action Complete");
1✔
273
                  });
1✔
274

275
          return jsonObject;
1✔
276
        }
277
      };
278

279
  public OPCUAStatus getCurrentOPCUAStatus() {
280
    return currentOPCUAStatus;
1✔
281
  }
282

283
  @Override
284
  public Spec getSpec() {
285
    Spec spec = new Spec();
1✔
286

287
    /* Define our configuration parameters. */
288
    spec.addOption("name", OptionType.STRING).withRequired(true);
1✔
289
    spec.addOption("class", OptionType.STRING).withRequired(true);
1✔
290
    spec.addOption("opcuaStream", OptionType.STRING).withRequired(true);
1✔
291
    spec.addOption("endpointUrl", OptionType.STRING).withRequired(true);
1✔
292
    spec.addOption("discoveryUrl", OptionType.STRING).withRequired(true);
1✔
293
    spec.addOption("xtceOutputFile", OptionType.STRING).withRequired(true);
1✔
294
    spec.addOption("parametersNamespace", OptionType.STRING).withRequired(true);
1✔
295
    spec.addOption("publishInterval", OptionType.INTEGER).withRequired(true);
1✔
296
    spec.addOption("queryAllNodesAtStartup", OptionType.BOOLEAN).withRequired(false);
1✔
297

298
    Spec rootNodeIDSpec = new Spec();
1✔
299

300
    rootNodeIDSpec.addOption("namespaceIndex", OptionType.INTEGER).withRequired(true);
1✔
301
    rootNodeIDSpec.addOption("identifier", OptionType.STRING).withRequired(true);
1✔
302
    rootNodeIDSpec.addOption("identifierType", OptionType.STRING).withRequired(true);
1✔
303

304
    spec.addOption("rootNodeID", OptionType.MAP).withRequired(false).withSpec(rootNodeIDSpec);
1✔
305

306
    Spec nodePathSpec = new Spec();
1✔
307
    nodePathSpec.addOption("path", OptionType.STRING);
1✔
308
    nodePathSpec
1✔
309
        .addOption("rootNodeID", OptionType.MAP)
1✔
310
        .withRequired(true)
1✔
311
        .withSpec(rootNodeIDSpec);
1✔
312

313
    spec.addOption("nodePaths", OptionType.LIST)
1✔
314
        .withElementType(OptionType.MAP)
1✔
315
        .withRequired(true)
1✔
316
        .withSpec(nodePathSpec);
1✔
317

318
    return spec;
1✔
319
  }
320

321
  @Override
322
  public void init(String yamcsInstance, String serviceName, YConfiguration config)
323
      throws ConfigurationException {
324
    super.init(yamcsInstance, serviceName, config);
1✔
325

326
    /* Local variables */
327
    this.config = config;
1✔
328
    /* Validate the configuration that the user passed us. */
329
    try {
330
      config = getSpec().validate(config);
1✔
UNCOV
331
    } catch (ValidationException e) {
×
UNCOV
332
      log.error("Failed configuration validation.", e);
×
UNCOV
333
      notifyFailed(e);
×
334
    }
1✔
335
    YarchDatabaseInstance ydb = YarchDatabase.getInstance(yamcsInstance);
1✔
336

337
    this.opcuaStreamName = config.getString("opcuaStream");
1✔
338

339
    opcuaStream = getStream(ydb, opcuaStreamName);
1✔
340

341
    this.endpointURL = config.getString("endpointUrl");
1✔
342

343
    this.discoverURL = config.getString("discoveryUrl");
1✔
344

345
    this.parametersNamespace = config.getString("parametersNamespace");
1✔
346

347
    queryAllNodesAtStartup = config.getBoolean("queryAllNodesAtStartup", false);
1✔
348

349
    Map<Object, Object> root = config.getMap("rootNodeID");
1✔
350

351
    rootNamespaceIndex = (int) root.get("namespaceIndex");
1✔
352

353
    rootIdentifier = (String) root.get("identifier");
1✔
354
    rootIdentifierType = IdType.valueOf((String) root.get("identifierType"));
1✔
355

356
    List<Map<Object, Object>> nodePaths = config.getList("nodePaths");
1✔
357

358
    for (Map<Object, Object> path : nodePaths) {
1✔
359
      NodePath nodePath = new NodePath();
1✔
360
      nodePath.path = (String) path.get("path");
1✔
361
      nodePath.rootNodeID = (HashMap<Object, Object>) path.get("rootNodeID");
1✔
362
      relativeNodePaths.add(nodePath);
1✔
363
    }
1✔
364

365
    mdb = YamcsServer.getServer().getInstance(yamcsInstance).getXtceDb();
1✔
366

367
    outputFile = config.getString("xtceOutputFile");
1✔
368
    publishInterval = config.getInt("publishInterval");
1✔
369
  }
1✔
370

371
  private static SpaceSystem verifySpaceSystem(XtceDb mdb, String pathName) {
372
    String namespace;
373
    String name;
374
    int lastSlash = pathName.lastIndexOf('/');
1✔
375
    if ("/".equals(pathName)) {
1✔
376
      namespace = "";
1✔
377
      name = "";
1✔
378
    } else if (lastSlash == -1 || lastSlash == pathName.length() - 1) {
×
UNCOV
379
      namespace = "";
×
UNCOV
380
      name = pathName;
×
381
    } else {
UNCOV
382
      namespace = pathName.substring(0, lastSlash);
×
UNCOV
383
      name = pathName.substring(lastSlash + 1);
×
384
    }
385

386
    // First try with a prefixed slash (should be the common case)
387
    NamedObjectId id =
388
        NamedObjectId.newBuilder().setNamespace("/" + namespace).setName(name).build();
1✔
389
    SpaceSystem spaceSystem = mdb.getSpaceSystem(id);
1✔
390
    if (spaceSystem != null) {
1✔
391
      return spaceSystem;
1✔
392
    }
393

394
    // Maybe some non-xtce namespace like MDB:OPS Name
UNCOV
395
    id = NamedObjectId.newBuilder().setNamespace(namespace).setName(name).build();
×
396
    spaceSystem = mdb.getSpaceSystem(id);
×
UNCOV
397
    if (spaceSystem != null) {
×
UNCOV
398
      return spaceSystem;
×
399
    }
400

UNCOV
401
    throw new NotFoundException("No such space system");
×
402
  }
403

404
  /**
405
   * Initializes all PV mappings to OPCUA nodes and realtime subscriptions(managed data items in
406
   * OPCUA terms).
407
   */
408
  private void opcuaInit() {
409
    //          FIXME:Might need to move this function to start(), maybe...
410

411
    createOPCUAAttrAggregateType();
1✔
412
    createOPCUANodeIdTypes();
1✔
413
    mdb.addParameterType(opcuaAttrsType, true);
1✔
414

415
    try {
416

417
      currentOPCUAStatus = OPCUAStatus.OPCUA_INIT_TREE;
1✔
418

419
      browseOPCUATree(client);
1✔
420

421
      currentOPCUAStatus = OPCUAStatus.OPCUA_INIT_GENERATE_XTCE;
1✔
422

423
      var spaceSystem = verifySpaceSystem(mdb, "/");
1✔
424

425
      var xtce = new XtceAssembler().toXtce(mdb, spaceSystem.getQualifiedName(), fqn -> true);
1✔
426

427
      BufferedWriter writer = null;
1✔
428

429
      if (outputFile != null) {
1✔
430
        writer =
1✔
431
            Files.newBufferedWriter(
1✔
432
                Paths.get(outputFile),
1✔
433
                StandardOpenOption.CREATE,
434
                StandardOpenOption.TRUNCATE_EXISTING);
UNCOV
435
      } else writer = null;
×
436

437
      writer.write(xtce);
1✔
438

439
      writer.flush();
1✔
440
      writer.close();
1✔
441

442
      currentOPCUAStatus = OPCUAStatus.OPCUA_INIT_EVENTS;
1✔
443
      subscribeToEvents(client);
1✔
444

UNCOV
445
    } catch (Exception e) {
×
446
      // TODO Auto-generated catch block
UNCOV
447
      e.printStackTrace();
×
448
      return;
×
449
    }
1✔
450
    try {
451
      currentOPCUAStatus = OPCUAStatus.OPCUA_INIT_DATA_SUBSCRIPTION;
1✔
452
      createOPCUASubscriptions();
1✔
UNCOV
453
    } catch (Exception e) {
×
454
      // TODO Auto-generated catch block
UNCOV
455
      e.printStackTrace();
×
456
    }
1✔
457
  }
1✔
458

459
  private void opcuaClientConnect() throws Exception {
460
    client = configureClient();
1✔
461
    connectToOPCUAServer(client);
1✔
462
  }
1✔
463

464
  private static Stream getStream(YarchDatabaseInstance ydb, String streamName) {
465
    Stream stream = ydb.getStream(streamName);
1✔
466
    if (stream == null) {
1✔
467
      try {
468
        ydb.execute("create stream " + streamName + gftdef.getStringDefinition());
1✔
UNCOV
469
      } catch (Exception e) {
×
UNCOV
470
        throw new ConfigurationException(e);
×
471
      }
1✔
472

473
      stream = ydb.getStream(streamName);
1✔
474
    }
475
    return stream;
1✔
476
  }
477

478
  @Override
479
  public void doDisable() {
480
    /* If the thread is created, interrupt it. */
481
    if (thread != null) {
1✔
482
      thread.interrupt();
1✔
483
    }
484

485
    linkStatus = Status.DISABLED;
1✔
486
  }
1✔
487

488
  @Override
489
  public void doEnable() {
490
    linkStatus = Status.OK;
1✔
491
  }
1✔
492

493
  @Override
494
  public String getDetailedStatus() {
495
    if (isDisabled()) {
1✔
496
      return String.format("DISABLED");
1✔
497
    } else {
498
      return String.format("OK, received %d packets", inCount.get());
1✔
499
    }
500
  }
501

502
  @Override
503
  public Status connectionStatus() {
504
    return linkStatus;
1✔
505
  }
506

507
  @Override
508
  protected void doStart() {
509
    try {
510
      opcuaClientConnect();
1✔
UNCOV
511
    } catch (Exception e) {
×
UNCOV
512
      e.printStackTrace();
×
UNCOV
513
      linkStatus = Status.FAILED;
×
UNCOV
514
      notifyFailed(e);
×
UNCOV
515
      return;
×
516
    }
1✔
517
    if (!isDisabled()) {
1✔
518
      doEnable();
1✔
519
    }
520
    startAction.addChangeListener(
1✔
521
        () -> {
522
          /**
523
           * TODO:Might be useful if we want turn off any functionality when the action is disabled
524
           * for instance..
525
           */
UNCOV
526
        });
×
527

528
    /* Create and start the new thread. */
529
    thread = new Thread(this);
1✔
530
    thread.setName(this.getClass().getSimpleName() + "-" + linkName);
1✔
531
    thread.start();
1✔
532

533
    notifyStarted();
1✔
534
  }
1✔
535

536
  @Override
537
  protected void doStop() {
538
    try {
539
      client.disconnect().get();
1✔
UNCOV
540
    } catch (InterruptedException | ExecutionException e) {
×
541
      // TODO Auto-generated catch block
UNCOV
542
      e.printStackTrace();
×
543
    }
1✔
544
    if (thread != null) {
1✔
545
      thread.interrupt();
1✔
546
    }
547

548
    notifyStopped();
1✔
549
  }
1✔
550

551
  @Override
552
  public void run() {
553
    opcuaInit();
1✔
554
    if (queryAllNodesAtStartup) {
1✔
555
      //            NOTE:I'm not sure if queryAllOPCUAData should block...
UNCOV
556
      currentOPCUAStatus = OPCUAStatus.OPCUA_INIT_ALL_DATA_QUERY;
×
UNCOV
557
      queryAllOPCUAData();
×
558
    }
559
    /* Enter our main loop */
560
    while (isRunningAndEnabled()) {
1✔
561
      currentOPCUAStatus = OPCUAStatus.OPCUA_OK;
1✔
562
    }
563
  }
1✔
564

565
  /**
566
   * Reads all attributes of all configured nodes and updates their corresponding PV. Useful for
567
   * querying data from the OPCUA server once, data such as browse names, NodeIds, etc.
568
   */
569
  private void queryAllOPCUAData() {
570
    TupleDefinition tdef = gftdef.copy();
1✔
571
    List<Object> cols = new ArrayList<>(4 + nodeIDToParamsMap.keySet().size());
1✔
572

573
    tdef = gftdef.copy();
1✔
574
    long gentime = timeService.getMissionTime();
1✔
575
    cols.add(gentime);
1✔
576
    cols.add(parametersNamespace);
1✔
577
    cols.add(0);
1✔
578
    cols.add(gentime);
1✔
579

580
    int columnCount = 0;
1✔
581

582
    Set<NodeId> nodeSet = new HashSet<NodeId>();
1✔
583
    /**
584
     * FIXME:This is super inefficient... The reason we collect these nodeIDs in a set is because
585
     * otherwise we will have redundant subscription(s) since there is more than 1 attribute per
586
     * nodeID given how nodeIDToParamsMap is designed
587
     */
588
    for (NodeIDAttrPair pair : nodeIDToParamsMap.keySet()) {
1✔
589
      nodeSet.add(pair.nodeID);
1✔
590
    }
1✔
591

592
    for (NodeId nId : nodeSet) {
1✔
593
      UaNode node;
594

595
      try {
596
        node = client.getAddressSpace().getNode(nId);
1✔
597

598
        DataValue nodeClass = node.readAttribute(AttributeId.NodeClass);
1✔
599

600
        switch (NodeClass.from((int) nodeClass.getValue().getValue())) {
1✔
601
          case DataType:
602
            //                tdef.addColumn(pair.getValue().getQualifiedName(),
603
            // DataType.PARAMETER_VALUE);
604
            //                cols.add(getPV(pair.getValue(), Instant.now().toEpochMilli(),
605
            // "PlaceHolder"));
606
            //            columnCount++;
UNCOV
607
            break;
×
608
          case Method:
609
            //                tdef.addColumn(pair.getValue().getQualifiedName(),
610
            // DataType.PARAMETER_VALUE);
611
            //                cols.add(getPV(pair.getValue(), Instant.now().toEpochMilli(),
612
            // "PlaceHolder"));
613
            //            columnCount++;
UNCOV
614
            break;
×
615
          case Object:
616
            //                tdef.addColumn(pair.getValue().getQualifiedName(),
617
            // DataType.PARAMETER_VALUE);
618
            //                cols.add(getPV(pair.getValue(), Instant.now().toEpochMilli(),
619
            // "PlaceHolder"));
620
            //            columnCount++;
621
            for (AttributeId attr : AttributeId.VARIABLE_ATTRIBUTES) {
1✔
622

623
              VariableParam p = nodeIDToParamsMap.get(new NodeIDAttrPair(nId, attr));
1✔
624

625
              if (p.getParameterType() == null) {
1✔
UNCOV
626
                String value = "";
×
UNCOV
627
                if (node.readAttribute(attr).getValue().isNull()) {
×
UNCOV
628
                  value = "NULL";
×
629
                } else {
UNCOV
630
                  value = node.readAttribute(attr).getValue().getValue().toString();
×
631
                }
632

633
                tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
×
634
                cols.add(getPV(p, Instant.now().toEpochMilli(), value));
×
UNCOV
635
                continue;
×
636
              }
637

638
              switch (p.getParameterType().getValueType()) {
1✔
639
                case AGGREGATE:
640
                  {
UNCOV
641
                    String value = "";
×
UNCOV
642
                    if (node.readAttribute(attr).getValue().isNull()) {
×
643
                      value = "NULL";
×
644
                    } else {
645
                      value = node.readAttribute(attr).getValue().getValue().toString();
×
646
                    }
647

UNCOV
648
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
×
UNCOV
649
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
×
650
                  }
651
                  break;
×
652
                case ARRAY:
653
                  {
UNCOV
654
                    String value = "";
×
UNCOV
655
                    if (node.readAttribute(attr).getValue().isNull()) {
×
656
                      value = "NULL";
×
657
                    } else {
658
                      value = node.readAttribute(attr).getValue().getValue().toString();
×
659
                    }
660

UNCOV
661
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
×
UNCOV
662
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
×
663
                  }
664
                  break;
×
665
                case BINARY:
666
                  {
UNCOV
667
                    String value = "";
×
UNCOV
668
                    if (node.readAttribute(attr).getValue().isNull()) {
×
669
                      value = "NULL";
×
670
                    } else {
671
                      value = node.readAttribute(attr).getValue().getValue().toString();
×
672
                    }
673

UNCOV
674
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
×
UNCOV
675
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
×
676
                  }
677
                  break;
×
678
                case BOOLEAN:
679
                  {
UNCOV
680
                    String value = "";
×
UNCOV
681
                    if (node.readAttribute(attr).getValue().isNull()) {
×
682
                      value = "NULL";
×
683
                    } else {
UNCOV
684
                      value = node.readAttribute(attr).getValue().getValue().toString();
×
685
                    }
686

687
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
×
UNCOV
688
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
×
689
                  }
690
                  break;
×
691
                case DOUBLE:
692
                  {
693
                    double value = 0;
×
UNCOV
694
                    if (node.readAttribute(attr).getValue().isNull()) {
×
695
                      //                      value = null;
696
                      //                            FIXME:Log warning
697
                    } else {
698
                      value = (double) node.readAttribute(attr).getValue().getValue();
×
699
                    }
700

UNCOV
701
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
×
UNCOV
702
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
×
703
                  }
704
                  break;
×
705
                case ENUMERATED:
706
                  {
UNCOV
707
                    String value = "";
×
UNCOV
708
                    if (node.readAttribute(attr).getValue().isNull()) {
×
709
                      value = "NULL";
×
710
                    } else {
UNCOV
711
                      value = node.readAttribute(attr).getValue().getValue().toString();
×
712
                    }
713

714
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
×
UNCOV
715
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
×
716
                  }
717
                  break;
×
718
                case FLOAT:
719
                  {
720
                    float value = 0;
×
UNCOV
721
                    if (node.readAttribute(attr).getValue().isNull()) {
×
722
                      //                      value = null;
723
                      //                            FIXME:Log warning
724
                    } else {
725
                      value = (float) node.readAttribute(attr).getValue().getValue();
×
726
                    }
727

UNCOV
728
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
×
UNCOV
729
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
×
730
                  }
731
                  break;
×
732
                case NONE:
733
                  {
UNCOV
734
                    String value = "";
×
UNCOV
735
                    if (node.readAttribute(attr).getValue().isNull()) {
×
736
                      value = "NULL";
×
737
                    } else {
UNCOV
738
                      value = node.readAttribute(attr).getValue().getValue().toString();
×
739
                    }
740

741
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
×
UNCOV
742
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
×
743
                  }
744
                  break;
×
745
                case SINT32:
746
                  {
747
                    int value = 0;
×
UNCOV
748
                    if (node.readAttribute(attr).getValue().isNull()) {
×
749
                      //                      value = null;
750
                      //                            FIXME:Log warning
751
                    } else {
UNCOV
752
                      value = (int) node.readAttribute(attr).getValue().getValue();
×
753
                    }
754

755
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
×
UNCOV
756
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
×
757
                  }
758
                  break;
×
759
                case SINT64:
760
                  {
761
                    long value = 0;
×
UNCOV
762
                    if (node.readAttribute(attr).getValue().isNull()) {
×
763
                      //                      value = null;
764
                      //                            FIXME:Log warning
765
                    } else {
UNCOV
766
                      value = (long) node.readAttribute(attr).getValue().getValue();
×
767
                    }
768

UNCOV
769
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
×
UNCOV
770
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
×
771
                  }
UNCOV
772
                  break;
×
773
                case STRING:
774
                  {
775
                    String value = "";
1✔
776
                    if (node.readAttribute(attr).getValue().isNull()) {
1✔
777
                      value = "NULL";
1✔
778
                    } else {
779
                      value = node.readAttribute(attr).getValue().getValue().toString();
1✔
780
                    }
781

782
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
1✔
783
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
1✔
784
                  }
785
                  break;
1✔
786
                case TIMESTAMP:
787
                  {
UNCOV
788
                    String value = "";
×
UNCOV
789
                    if (node.readAttribute(attr).getValue().isNull()) {
×
790
                      value = "NULL";
×
791
                    } else {
UNCOV
792
                      value = node.readAttribute(attr).getValue().getValue().toString();
×
793
                    }
794

795
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
×
UNCOV
796
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
×
797
                  }
798
                  break;
×
799
                case UINT32:
800
                  {
801
                    long value = 0;
×
UNCOV
802
                    if (node.readAttribute(attr).getValue().isNull()) {
×
803
                      //                      value = null;
804
                      //                            FIXME:Log warning
805
                    } else {
UNCOV
806
                      value = (long) node.readAttribute(attr).getValue().getValue();
×
807
                    }
808

809
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
×
UNCOV
810
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
×
811
                  }
812
                  break;
×
813
                case UINT64:
814
                  {
815
                    long value = 0;
×
UNCOV
816
                    if (node.readAttribute(attr).getValue().isNull()) {
×
817
                      //                      value = null;
818
                      //                            FIXME:Log warning
819
                    } else {
UNCOV
820
                      value = (long) node.readAttribute(attr).getValue().getValue();
×
821
                    }
822

UNCOV
823
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
×
UNCOV
824
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
×
825
                  }
UNCOV
826
                  break;
×
827
                default:
828
                  break;
829
              }
830

831
              log.debug("Pushing {} to stream", p.toString());
1✔
832

833
              columnCount++;
1✔
834
            }
1✔
835
            break;
1✔
836
          case ObjectType:
837
            //                tdef.addColumn(pair.getValue().getQualifiedName(),
838
            // DataType.PARAMETER_VALUE);
839
            //                cols.add(getPV(pair.getValue(), Instant.now().toEpochMilli(),
840
            // "PlaceHolder"));
841
            //            columnCount++;
UNCOV
842
            break;
×
843
          case ReferenceType:
844
            //                tdef.addColumn(pair.getValue().getQualifiedName(),
845
            // DataType.PARAMETER_VALUE);
846
            //                cols.add(getPV(pair.getValue(), Instant.now().toEpochMilli(),
847
            // "PlaceHolder"));
848
            //            columnCount++;
UNCOV
849
            break;
×
850
          case Unspecified:
851
            //                tdef.addColumn(pair.getValue().getQualifiedName(),
852
            // DataType.PARAMETER_VALUE);
853
            //                cols.add(getPV(pair.getValue(), Instant.now().toEpochMilli(),
854
            // "PlaceHolder"));
855
            //            columnCount++;
UNCOV
856
            break;
×
857
          case Variable:
858
            for (AttributeId attr : AttributeId.VARIABLE_ATTRIBUTES) {
1✔
859

860
              VariableParam p = nodeIDToParamsMap.get(new NodeIDAttrPair(nId, attr));
1✔
861

862
              if (p.getParameterType() == null) {
1✔
863

UNCOV
864
                String value = "";
×
UNCOV
865
                if (node.readAttribute(attr).getValue().isNull()) {
×
UNCOV
866
                  value = "NULL";
×
867
                } else {
UNCOV
868
                  value = node.readAttribute(attr).getValue().getValue().toString();
×
869
                }
870

871
                tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
×
872
                cols.add(getPV(p, Instant.now().toEpochMilli(), value));
×
UNCOV
873
                continue;
×
874
              }
875

876
              switch (p.getParameterType().getValueType()) {
1✔
877
                case AGGREGATE:
878
                  {
UNCOV
879
                    String value = "";
×
UNCOV
880
                    if (node.readAttribute(attr).getValue().isNull()) {
×
881
                      value = "NULL";
×
882
                    } else {
883
                      value = node.readAttribute(attr).getValue().getValue().toString();
×
884
                    }
885

UNCOV
886
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
×
UNCOV
887
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
×
888
                  }
889
                  break;
×
890
                case ARRAY:
891
                  {
UNCOV
892
                    String value = "";
×
UNCOV
893
                    if (node.readAttribute(attr).getValue().isNull()) {
×
894
                      value = "NULL";
×
895
                    } else {
896
                      value = node.readAttribute(attr).getValue().getValue().toString();
×
897
                    }
898

UNCOV
899
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
×
UNCOV
900
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
×
901
                  }
902
                  break;
×
903
                case BINARY:
904
                  {
UNCOV
905
                    String value = "";
×
UNCOV
906
                    if (node.readAttribute(attr).getValue().isNull()) {
×
907
                      value = "NULL";
×
908
                    } else {
909
                      value = node.readAttribute(attr).getValue().getValue().toString();
×
910
                    }
911

UNCOV
912
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
×
UNCOV
913
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
×
914
                  }
915
                  break;
×
916
                case BOOLEAN:
917
                  {
918
                    String value = "";
1✔
919
                    if (node.readAttribute(attr).getValue().isNull()) {
1✔
920
                      value = "NULL";
×
921
                    } else {
922
                      value = node.readAttribute(attr).getValue().getValue().toString();
1✔
923
                    }
924

925
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
1✔
926
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
1✔
927
                  }
928
                  break;
1✔
929
                case DOUBLE:
930
                  {
931
                    double value = 0;
1✔
932
                    if (node.readAttribute(attr).getValue().isNull()) {
1✔
933
                      //                      value = null;
934
                      //                            FIXME:Log warning
935
                    } else {
936
                      value = (double) node.readAttribute(attr).getValue().getValue();
1✔
937
                    }
938

939
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
1✔
940
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
1✔
941
                  }
942
                  break;
1✔
943
                case ENUMERATED:
944
                  {
UNCOV
945
                    String value = "";
×
UNCOV
946
                    if (node.readAttribute(attr).getValue().isNull()) {
×
947
                      value = "NULL";
×
948
                    } else {
UNCOV
949
                      value = node.readAttribute(attr).getValue().getValue().toString();
×
950
                    }
951

952
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
×
UNCOV
953
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
×
954
                  }
955
                  break;
×
956
                case FLOAT:
957
                  {
958
                    float value = 0;
×
UNCOV
959
                    if (node.readAttribute(attr).getValue().isNull()) {
×
960
                      //                      value = null;
961
                      //                            FIXME:Log warning
962
                    } else {
963
                      value = (float) node.readAttribute(attr).getValue().getValue();
×
964
                    }
965

UNCOV
966
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
×
UNCOV
967
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
×
968
                  }
969
                  break;
×
970
                case NONE:
971
                  {
UNCOV
972
                    String value = "";
×
UNCOV
973
                    if (node.readAttribute(attr).getValue().isNull()) {
×
974
                      value = "NULL";
×
975
                    } else {
UNCOV
976
                      value = node.readAttribute(attr).getValue().getValue().toString();
×
977
                    }
978

979
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
×
UNCOV
980
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
×
981
                  }
982
                  break;
×
983
                case SINT32:
984
                  {
985
                    int value = 0;
1✔
986
                    if (node.readAttribute(attr).getValue().isNull()) {
1✔
987
                      //                      value = null;
988
                      //                            FIXME:Log warning
989
                    } else {
990
                      value = (int) node.readAttribute(attr).getValue().getValue();
1✔
991
                    }
992

993
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
1✔
994
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
1✔
995
                  }
996
                  break;
1✔
997
                case SINT64:
998
                  {
999
                    long value = 0;
1✔
1000
                    if (node.readAttribute(attr).getValue().isNull()) {
1✔
1001
                      //                      value = null;
1002
                      //                            FIXME:Log warning
1003
                    } else {
1004
                      value = (long) node.readAttribute(attr).getValue().getValue();
1✔
1005
                    }
1006

1007
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
1✔
1008
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
1✔
1009
                  }
1010
                  break;
1✔
1011
                case STRING:
1012
                  {
1013
                    String value = "";
1✔
1014
                    if (node.readAttribute(attr).getValue().isNull()) {
1✔
1015
                      value = "NULL";
1✔
1016
                    } else {
1017
                      value = node.readAttribute(attr).getValue().getValue().toString();
1✔
1018
                    }
1019

1020
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
1✔
1021
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
1✔
1022
                  }
1023
                  break;
1✔
1024
                case TIMESTAMP:
1025
                  {
UNCOV
1026
                    String value = "";
×
UNCOV
1027
                    if (node.readAttribute(attr).getValue().isNull()) {
×
1028
                      value = "NULL";
×
1029
                    } else {
UNCOV
1030
                      value = node.readAttribute(attr).getValue().getValue().toString();
×
1031
                    }
1032

1033
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
×
UNCOV
1034
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
×
1035
                  }
1036
                  break;
×
1037
                case UINT32:
1038
                  {
1039
                    long value = 0;
×
UNCOV
1040
                    if (node.readAttribute(attr).getValue().isNull()) {
×
1041
                      //                      value = null;
1042
                      //                            FIXME:Log warning
1043
                    } else {
UNCOV
1044
                      value = (long) node.readAttribute(attr).getValue().getValue();
×
1045
                    }
1046

1047
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
×
UNCOV
1048
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
×
1049
                  }
1050
                  break;
×
1051
                case UINT64:
1052
                  {
1053
                    long value = 0;
×
UNCOV
1054
                    if (node.readAttribute(attr).getValue().isNull()) {
×
1055
                      //                      value = null;
1056
                      //                            FIXME:Log warning
1057
                    } else {
UNCOV
1058
                      value = (long) node.readAttribute(attr).getValue().getValue();
×
1059
                    }
1060

UNCOV
1061
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
×
UNCOV
1062
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
×
1063
                  }
1064
                  break;
×
1065
                default:
1066
                  break;
1067
              }
1068

1069
              log.debug("Pushing {} to stream", p.toString());
1✔
1070

1071
              columnCount++;
1✔
1072
            }
1✔
1073
            break;
1✔
1074
          case VariableType:
UNCOV
1075
            break;
×
1076
          case View:
UNCOV
1077
            break;
×
1078
          default:
1079
            break;
1080
        }
1081

UNCOV
1082
      } catch (UaException e) {
×
1083
        // TODO Auto-generated catch block
UNCOV
1084
        e.printStackTrace();
×
UNCOV
1085
        continue;
×
1086
      }
1✔
1087
    }
1✔
1088

1089
    /**
1090
     * FIXME:Need to come up with a mechanism to not update certain values that are up to date...
1091
     * The more I think about it, it might make sense to have "static" and "runtime" namespaces
1092
     */
UNCOV
1093
    pushTuple(tdef, cols);
×
1094

UNCOV
1095
    inCount.getAndAdd(columnCount);
×
UNCOV
1096
  }
×
1097

1098
  private synchronized void pushTuple(TupleDefinition tdef, List<Object> cols) {
1099
    Tuple t;
1100
    t = new Tuple(tdef, cols);
1✔
1101
    opcuaStream.emitTuple(t);
1✔
1102
  }
1✔
1103

1104
  private static ParameterType getOrCreateType(
1105
      XtceDb mdb, String name, Supplier<ParameterType.Builder<?>> supplier) {
1106

1107
    String fqn = XtceDb.YAMCS_SPACESYSTEM_NAME + NameDescription.PATH_SEPARATOR + name;
1✔
1108
    ParameterType ptype = mdb.getParameterType(fqn);
1✔
1109
    if (ptype != null) {
1✔
1110
      return ptype;
1✔
1111
    }
1112
    ParameterType.Builder<?> typeb = supplier.get().setName(name);
1✔
1113

1114
    ptype = typeb.build();
1✔
1115
    ((NameDescription) ptype).setQualifiedName(fqn);
1✔
1116

1117
    return mdb.addSystemParameterType(ptype);
1✔
1118
  }
1119

1120
  public static ParameterType getBasicType(XtceDb mdb, Type type) {
1121
    ParameterType pType = null;
1✔
1122
    switch (type) {
1✔
1123
      case BINARY:
1124
        return getOrCreateType(mdb, "binary", () -> new BinaryParameterType.Builder());
×
1125
      case BOOLEAN:
1126
        return getOrCreateType(mdb, "boolean", () -> new BooleanParameterType.Builder());
1✔
1127
      case STRING:
1128
        pType = getOrCreateType(mdb, "string", () -> new StringParameterType.Builder());
1✔
1129
        break;
1✔
1130
      case FLOAT:
1131
        return getOrCreateType(
1✔
1132
            mdb, "float32", () -> new FloatParameterType.Builder().setSizeInBits(32));
1✔
1133
      case DOUBLE:
1134
        return getOrCreateType(
1✔
1135
            mdb, "float64", () -> new FloatParameterType.Builder().setSizeInBits(64));
1✔
1136
      case SINT32:
1137
        return getOrCreateType(
1✔
1138
            mdb,
1139
            "sint32",
UNCOV
1140
            () -> new IntegerParameterType.Builder().setSizeInBits(32).setSigned(true));
×
1141
      case SINT64:
1142
        return getOrCreateType(
1✔
1143
            mdb,
1144
            "sint64",
1145
            () -> new IntegerParameterType.Builder().setSizeInBits(64).setSigned(true));
1✔
1146
      case UINT32:
UNCOV
1147
        return getOrCreateType(
×
1148
            mdb,
1149
            "uint32",
UNCOV
1150
            () -> new IntegerParameterType.Builder().setSizeInBits(32).setSigned(false));
×
1151
      case UINT64:
1152
        return getOrCreateType(
1✔
1153
            mdb,
1154
            "uint64",
UNCOV
1155
            () -> new IntegerParameterType.Builder().setSizeInBits(64).setSigned(false));
×
1156
      case TIMESTAMP:
UNCOV
1157
        return getOrCreateType(mdb, "time", () -> new AbsoluteTimeParameterType.Builder());
×
1158
      case ENUMERATED:
UNCOV
1159
        return getOrCreateType(mdb, "enum", () -> new EnumeratedParameterType.Builder());
×
1160
      case AGGREGATE:
NEW
1161
        break;
×
1162
      case ARRAY:
NEW
1163
        break;
×
1164
      case NONE:
NEW
1165
        break;
×
1166
      default:
1167
        break;
1168
    }
1169

1170
    return pType;
1✔
1171
  }
1172

1173
  public static ParameterValue getNewPv(Parameter parameter, long time) {
1174
    ParameterValue pv = new ParameterValue(parameter);
1✔
1175
    pv.setAcquisitionTime(time);
1✔
1176
    pv.setGenerationTime(time);
1✔
1177
    return pv;
1✔
1178
  }
1179

1180
  public static ParameterValue getPV(Parameter parameter, long time, String v) {
1181
    ParameterValue pv = getNewPv(parameter, time);
1✔
1182
    pv.setEngValue(ValueUtility.getStringValue(v));
1✔
1183
    return pv;
1✔
1184
  }
1185

1186
  public static ParameterValue getPV(Parameter parameter, long time, double v) {
1187
    ParameterValue pv = getNewPv(parameter, time);
1✔
1188
    pv.setEngValue(ValueUtility.getDoubleValue(v));
1✔
1189
    return pv;
1✔
1190
  }
1191

1192
  public static ParameterValue getPV(Parameter parameter, long time, float v) {
1193
    ParameterValue pv = getNewPv(parameter, time);
1✔
1194
    pv.setEngValue(ValueUtility.getFloatValue(v));
1✔
1195
    return pv;
1✔
1196
  }
1197

1198
  public static ParameterValue getPV(Parameter parameter, long time, boolean v) {
1199
    ParameterValue pv = getNewPv(parameter, time);
1✔
1200
    pv.setEngValue(ValueUtility.getBooleanValue(v));
1✔
1201
    return pv;
1✔
1202
  }
1203

1204
  public static ParameterValue getPV(Parameter parameter, long time, long v) {
1205
    ParameterValue pv = getNewPv(parameter, time);
1✔
1206
    pv.setEngValue(ValueUtility.getSint64Value(v));
1✔
1207
    return pv;
1✔
1208
  }
1209

1210
  public static ParameterValue getUnsignedIntPV(Parameter parameter, long time, int v) {
UNCOV
1211
    ParameterValue pv = getNewPv(parameter, time);
×
UNCOV
1212
    pv.setEngValue(ValueUtility.getUint64Value(v));
×
UNCOV
1213
    return pv;
×
1214
  }
1215

1216
  //  private static ParameterValue getPV(Parameter parameter, long time, Object v) {
1217
  //            ParameterValue pv = getNewPv(parameter, time);
1218
  //            pv.setEngValue(ValueUtility.getStringValue(v));
1219
  //            return pv;
1220
  //          }
1221

1222
  @Override
1223
  public Status getLinkStatus() {
1224
    return linkStatus;
1✔
1225
  }
1226

1227
  @Override
1228
  public boolean isDisabled() {
1229
    // TODO Auto-generated method stub
1230
    return linkStatus == Status.DISABLED;
1✔
1231
  }
1232

1233
  @Override
1234
  public long getDataInCount() {
1235
    // TODO Auto-generated method stub
1236
    return inCount.get();
1✔
1237
  }
1238

1239
  @Override
1240
  public long getDataOutCount() {
1241
    // TODO Auto-generated method stub
1242
    return 0;
1✔
1243
  }
1244

1245
  @Override
1246
  public void resetCounters() {
1247
    // TODO Auto-generated method stub
1248
    inCount.set(0);
1✔
1249
  }
1✔
1250

1251
  /**
1252
   * Selects first non-secured endpoint from endpoints found at discover URL. At the moment secured
1253
   * endpoints are not supported.
1254
   *
1255
   * @return
1256
   * @throws Exception
1257
   */
1258
  private OpcUaClient configureClient() throws Exception {
1259
    Path securityTempDir = Paths.get(System.getProperty("java.io.tmpdir"), "client", "security");
1✔
1260
    Files.createDirectories(securityTempDir);
1✔
1261
    if (!Files.exists(securityTempDir)) {
1✔
UNCOV
1262
      throw new Exception("unable to create security dir: " + securityTempDir);
×
1263
    }
1264

1265
    File pkiDir = securityTempDir.resolve("pki").toFile();
1✔
1266

1267
    LoggerFactory.getLogger(getClass()).info("security dir: {}", securityTempDir.toAbsolutePath());
1✔
1268
    LoggerFactory.getLogger(getClass()).info("security pki dir: {}", pkiDir.getAbsolutePath());
1✔
1269

1270
    //    trustListManager = new DefaultTrustListManager(pkiDir);
1271
    List<EndpointDescription> endpoints = DiscoveryClient.getEndpoints(discoverURL).get();
1✔
1272

1273
    //    FIXME:At the moment, we do not support certificates...
1274
    EndpointDescription selectedEndpoint = null;
1✔
1275
    for (var endpoint : endpoints) {
1✔
1276
      switch (endpoint.getSecurityMode()) {
1✔
1277
        case Invalid:
1278
          //                        FIXME:Add log message
UNCOV
1279
          break;
×
1280
        case None:
1281
          //                        FIXME:Add log message
1282
          selectedEndpoint = endpoint;
1✔
1283
          break;
1✔
1284
          //                        FIXME:Add log message
1285
        case Sign:
UNCOV
1286
          break;
×
1287
        case SignAndEncrypt:
1288
          //                        FIXME:Add log message
UNCOV
1289
          break;
×
1290
        default:
1291
          break;
1292
      }
1293

1294
      if (selectedEndpoint != null) {
1✔
1295
        break;
1✔
1296
      }
UNCOV
1297
    }
×
1298

1299
    if (selectedEndpoint == null) {
1✔
UNCOV
1300
      throw new Exception("No viable endpoint found from list:" + endpoints);
×
1301
    }
1302

1303
    OpcUaClientConfig builder = OpcUaClientConfig.builder().setEndpoint(selectedEndpoint).build();
1✔
1304

1305
    return OpcUaClient.create(builder);
1✔
1306
  }
1307

1308
  /**
1309
   * Browse all nodes starting from browseRoot.
1310
   *
1311
   * @param indent
1312
   * @param client
1313
   * @param browseRoot
1314
   */
1315
  private void browseNodeWithReferences(String indent, OpcUaClient client, NodeId browseRoot) {
1316
    BrowseDescription browse =
1✔
1317
        new BrowseDescription(
1318
            browseRoot,
1319
            BrowseDirection.Forward,
1320
            Identifiers.References,
1321
            true,
1✔
1322
            uint(NodeClass.Object.getValue() | NodeClass.Variable.getValue()),
1✔
1323
            uint(BrowseResultMask.All.getValue()));
1✔
1324

1325
    try {
1326

1327
      BrowseResult browseResult = client.browse(browse).get();
1✔
1328

1329
      List<ReferenceDescription> references = toList(browseResult.getReferences());
1✔
1330

1331
      if (references.isEmpty()) {
1✔
1332
        //              FIXME:Add log here
1333

1334
        return;
1✔
1335
      }
1336

1337
      for (ReferenceDescription rd : references) {
1✔
1338
        Object desc = null;
1✔
1339
        Object value = null;
1✔
1340
        UaNode node = null;
1✔
1341
        try {
1342

1343
          node =
1✔
1344
              client
1345
                  .getAddressSpace()
1✔
1346
                  .getNode(rd.getNodeId().toNodeId(client.getNamespaceTable()).get());
1✔
1347
          DataValue attr = node.readAttribute(AttributeId.Description);
1✔
1348
          desc = attr.getValue().getValue();
1✔
1349

1350
          attr = node.readAttribute(AttributeId.Value);
1✔
1351

1352
          value = attr.getValue();
1✔
1353

UNCOV
1354
        } catch (UaException e) {
×
1355
          // TODO Auto-generated catch block
UNCOV
1356
          e.printStackTrace();
×
1357
        }
1✔
1358

1359
        if (node != null) {
1✔
1360
          addOPCUAPV(client, node);
1✔
1361

1362
          log.debug(
1✔
1363
              "{} Node={}, Desc={}, Value={}", indent, rd.getBrowseName().getName(), desc, value);
1✔
1364

1365
          if (rd.getIsForward()) {}
1✔
1366

1367
          // recursively browse to children
1368
          rd.getNodeId()
1✔
1369
              .toNodeId(client.getNamespaceTable())
1✔
1370
              .ifPresent(nodeId -> browseNodeWithReferences(indent + "  ", client, nodeId));
1✔
1371
        }
1372
      }
1✔
1373

UNCOV
1374
    } catch (InterruptedException e1) {
×
1375
      // TODO Auto-generated catch block
UNCOV
1376
      e1.printStackTrace();
×
UNCOV
1377
    } catch (ExecutionException e1) {
×
1378
      // TODO Auto-generated catch block
UNCOV
1379
      e1.printStackTrace();
×
1380
    }
1✔
1381
  }
1✔
1382

1383
  /**
1384
   * Adds new PV with the name of node.
1385
   *
1386
   * @param client
1387
   * @param node
1388
   */
1389
  private void addOPCUAPV(OpcUaClient client, UaNode node) {
1390

1391
    if (node.getBrowseName()
1✔
1392
        .getName()
1✔
1393
        .contains(Character.toString(NameDescription.PATH_SEPARATOR))) {
1✔
1394
      internalLogger.info(
1✔
1395
          "{} ignored since it contains a {} character",
1396
          node.getBrowseName().getName(),
1✔
1397
          Character.toString(NameDescription.PATH_SEPARATOR));
1✔
1398

1399
    } else {
1400

1401
      /**
1402
       * NOTE:For now we'll just flatten all the attributes instead of using an aggregate type for
1403
       * attributes
1404
       */
1405
      for (AttributeId attr : AttributeId.values()) {
1✔
1406

1407
        ParameterType ptype = OPCUAAttrTypeToParamType(attr, node);
1✔
1408

1409
        String opcuaTranslatedQName = translateNodeToParamQName(client, node, attr);
1✔
1410
        Parameter p = VariableParam.getForFullyQualifiedName(opcuaTranslatedQName);
1✔
1411

1412
        p.setParameterType(ptype);
1✔
1413

1414
        if (mdb.getParameter(p.getQualifiedName()) == null) {
1✔
1415
          log.debug("Adding OPCUA object as parameter to mdb:{}", p.getQualifiedName());
1✔
1416
          mdb.addParameter(p, true);
1✔
1417

1418
          nodeIDToParamsMap.put(new NodeIDAttrPair(node.getNodeId(), attr), (VariableParam) p);
1✔
1419
        }
1420
      }
1421
    }
1422
  }
1✔
1423

1424
  /**
1425
   * Map nodeID name to a qualified name that can be used for a YAMCS PV.
1426
   *
1427
   * @param client
1428
   * @param node
1429
   * @param attr
1430
   * @return
1431
   */
1432
  private String translateNodeToParamQName(OpcUaClient client, UaNode node, AttributeId attr) {
1433
    LocalizedText localizedDisplayName = null;
1✔
1434
    try {
1435

1436
      localizedDisplayName =
1✔
1437
          (LocalizedText) (node.readAttribute(AttributeId.DisplayName).getValue().getValue());
1✔
UNCOV
1438
    } catch (UaException e) {
×
1439
      // TODO Auto-generated catch block
UNCOV
1440
      e.printStackTrace();
×
1441
    }
1✔
1442
    String opcuaTranslatedQName =
1✔
1443
        qualifiedName(
1✔
1444
            parametersNamespace
1445
                + NameDescription.PATH_SEPARATOR
1446
                + node.getNodeId().toParseableString().replace(";", "-")
1✔
1447
                + NameDescription.PATH_SEPARATOR
1448
                + localizedDisplayName.getText(),
1✔
1449
            attr.toString());
1✔
1450

1451
    return opcuaTranslatedQName;
1✔
1452
  }
1453

1454
  /**
1455
   * Same as translateNodeToParamQName but uses display name from the node instead of the
1456
   *
1457
   * @param client
1458
   * @param rd
1459
   * @param attr
1460
   * @return
1461
   */
1462
  //  private String translateNodeDisplayNameToParamQName(
1463
  //      OpcUaClient client, ReferenceDescription rd, AttributeId attr) {
1464
  //    String opcuaTranslatedQName =
1465
  //        qualifiedName(
1466
  //            parametersNamespace
1467
  //                + NameDescription.PATH_SEPARATOR
1468
  //                + rd.getNodeId()
1469
  //                    .toNodeId(client.getNamespaceTable())
1470
  //                    .get()
1471
  //                    .toParseableString()
1472
  //                    .replace(";", "-"),
1473
  //            attr.toString());
1474
  //
1475
  //    UaNode node = null;
1476
  //    try {
1477
  //      node =
1478
  //          client
1479
  //              .getAddressSpace()
1480
  //              .getNode(rd.getNodeId().toNodeId(client.getNamespaceTable()).get());
1481
  //    } catch (UaException e) {
1482
  //      // TODO Auto-generated catch block
1483
  //      e.printStackTrace();
1484
  //    }
1485
  //
1486
  //    String displayName = null;
1487
  //    LocalizedText localizedDisplayName = null;
1488
  //    try {
1489
  //      displayName =
1490
  // node.readAttribute(AttributeId.DisplayName).getValue().getValue().toString();
1491
  //
1492
  //      localizedDisplayName =
1493
  //          (LocalizedText) (node.readAttribute(AttributeId.DisplayName).getValue().getValue());
1494
  //    } catch (UaException e) {
1495
  //      // TODO Auto-generated catch block
1496
  //      e.printStackTrace();
1497
  //    }
1498
  //
1499
  //    //            String opcuaTranslatedQName =
1500
  //    //                qualifiedName(
1501
  //    //                    parametersNamespace + NameDescription.PATH_SEPARATOR +
1502
  //    // localizedDisplayName.getText(),
1503
  //    //                    attr.toString());
1504
  //    return opcuaTranslatedQName;
1505
  //  }
1506

1507
  /**
1508
   * Browse node at nodePath relative to browseRoot.
1509
   *
1510
   * @param indent
1511
   * @param client
1512
   * @param browseRoot
1513
   * @param nodePath in the format of "0:Root,0:Objects,2:HelloWorld,2:MyObject,2:Bar"
1514
   */
1515
  private void browsePath(String indent, OpcUaClient client, NodeId startingNode, String nodePath) {
1516
    internalLogger.info("Browsing at " + startingNode);
1✔
1517
    ArrayList<String> rPathTokens = new ArrayList<String>();
1✔
1518
    ArrayList<RelativePathElement> relaitivePathElements = new ArrayList<RelativePathElement>();
1✔
1519

1520
    for (var pathToken : nodePath.split(",")) {
1✔
1521
      rPathTokens.add(nodePath);
1✔
1522

1523
      int namespaceIndex = 0;
1✔
1524

1525
      String namespaceName = "";
1✔
1526

1527
      namespaceIndex = Integer.parseInt(pathToken.split(":")[0]);
1✔
1528

1529
      namespaceName = pathToken.split(":")[1];
1✔
1530

1531
      relaitivePathElements.add(
1✔
1532
          new RelativePathElement(
1533
              Identifiers.HierarchicalReferences,
1534
              false,
1✔
1535
              true,
1✔
1536
              new QualifiedName(namespaceIndex, namespaceName)));
1537
    }
1538

1539
    ArrayList<BrowsePath> list = new ArrayList<BrowsePath>();
1✔
1540

1541
    RelativePathElement[] elements = new RelativePathElement[relaitivePathElements.size()];
1✔
1542

1543
    relaitivePathElements.toArray(elements);
1✔
1544

1545
    list.add(new BrowsePath(startingNode, new RelativePath(elements)));
1✔
1546

1547
    TranslateBrowsePathsToNodeIdsResponse response = null;
1✔
1548
    try {
1549
      response = client.translateBrowsePaths(list).get();
1✔
UNCOV
1550
    } catch (InterruptedException e) {
×
1551
      // TODO Auto-generated catch block
UNCOV
1552
      e.printStackTrace();
×
UNCOV
1553
    } catch (ExecutionException e) {
×
1554
      // TODO Auto-generated catch block
UNCOV
1555
      e.printStackTrace();
×
1556
    }
1✔
1557

1558
    BrowsePathResult result = Arrays.asList(response.getResults()).get(0);
1✔
1559
    StatusCode statusCode = result.getStatusCode();
1✔
1560

1561
    if (statusCode.isBad()) {
1✔
UNCOV
1562
      log.warn("Bad status code:" + statusCode);
×
UNCOV
1563
      return;
×
1564
    } else if (statusCode.isUncertain()) {
1✔
UNCOV
1565
      log.warn("Uncertain status code:" + statusCode);
×
UNCOV
1566
      return;
×
1567
    }
1568

1569
    try {
1570
      UaNode node =
1✔
1571
          client
1572
              .getAddressSpace()
1✔
1573
              .getNode(
1✔
1574
                  result.getTargets()[0].getTargetId().toNodeId(client.getNamespaceTable()).get());
1✔
1575

1576
      addOPCUAPV(client, node);
1✔
UNCOV
1577
    } catch (UaException e) {
×
1578
      // TODO Auto-generated catch block
UNCOV
1579
      e.printStackTrace();
×
1580
    }
1✔
1581
  }
1✔
1582

1583
  private void createOPCUASubscriptions() {
1584
    createDataChangeListener();
1✔
1585
    Set<NodeId> nodeSet = new HashSet<NodeId>();
1✔
1586
    /**
1587
     * FIXME:This is super inefficient... The reason we collect these nodeIDs in a set is because
1588
     * otherwise we will have redundant subscription(s) since there is more than 1 attribute per
1589
     * nodeID given how nodeIDToParamsMap is designed
1590
     */
1591
    for (NodeIDAttrPair pair : nodeIDToParamsMap.keySet()) {
1✔
1592
      nodeSet.add(pair.nodeID);
1✔
1593
    }
1✔
1594
    for (NodeId id : nodeSet) {
1✔
1595
      Variant nodeClass = null;
1✔
1596
      try {
1597
        UaNode node = client.getAddressSpace().getNode(id);
1✔
1598

1599
        nodeClass = node.readAttribute(AttributeId.NodeClass).getValue();
1✔
1600

UNCOV
1601
      } catch (UaException e) {
×
1602
        // TODO Auto-generated catch block
UNCOV
1603
        e.printStackTrace();
×
1604
      }
1✔
1605
      if (nodeClass != null) {
1✔
1606
        try {
1607
          switch (NodeClass.from((int) nodeClass.getValue())) {
1✔
1608
              // As per the spec, the only thing we can subscribe to is Variables
1609
            case Variable:
1610
              ManagedDataItem dataItem = opcuaSubscription.createDataItem(id);
1✔
1611
              OPCUAActiveSubs.addAndGet(1);
1✔
1612
              log.debug("Status code for dataItem:{}", dataItem.getStatusCode());
1✔
1613
              break;
1614
          }
UNCOV
1615
        } catch (UaException e) {
×
1616
          // TODO Auto-generated catch block
UNCOV
1617
          e.printStackTrace();
×
1618
        }
1✔
1619
      }
1620
    }
1✔
1621
  }
1✔
1622

1623
  public void connectToOPCUAServer(OpcUaClient client) throws Exception {
1624
    internalLogger.info("Connecting to OPCUA server...");
1✔
1625
    client.connect().get();
1✔
1626

1627
    addAction(startAction);
1✔
1628
    startAction.setEnabled(true);
1✔
1629
  }
1✔
1630

1631
  /**
1632
   * Browses the tree on the OPCUA server and maps them to YAMCS Parameters.
1633
   *
1634
   * @param client
1635
   */
1636
  private void browseOPCUATree(OpcUaClient client) {
1637
    // start browsing at root folder
1638
    internalLogger.info("Browsing OPCUA...");
1✔
1639
    for (var p : relativeNodePaths) {
1✔
1640
      int namespaceIndex = (int) p.rootNodeID.get("namespaceIndex");
1✔
1641
      String identifier = (String) p.rootNodeID.get("identifier");
1✔
1642
      IdType identifierType = IdType.valueOf((String) p.rootNodeID.get("identifierType"));
1✔
1643

1644
      browsePath(
1✔
1645
          endpointURL, client, getNewNodeID(identifierType, namespaceIndex, identifier), p.path);
1✔
1646
    }
1✔
1647

1648
    NodeId nodeID = null;
1✔
1649
    nodeID = getNewNodeID(rootIdentifierType, rootNamespaceIndex, rootIdentifier);
1✔
1650

1651
    //  FIXME:Make root default when no namespaceIndex/identifier pair is specified
1652
    browseNodeWithReferences("", client, nodeID);
1✔
1653
  }
1✔
1654

1655
  /**
1656
   * Get new OPCUA-compliant NodeID object create from NamespaceIndex and Identifier. At the moment
1657
   * only String and Numeric node ids are supported.
1658
   *
1659
   * @param rootIdentifierType
1660
   * @param NamespaceIndex
1661
   * @param Identifier
1662
   * @return
1663
   */
1664
  private NodeId getNewNodeID(IdType rootIdentifierType, int NamespaceIndex, String Identifier) {
1665
    NodeId nodeID = null;
1✔
1666
    switch (rootIdentifierType) {
1✔
1667
      case Guid:
1668
        //                FIXME
UNCOV
1669
        break;
×
1670
      case Numeric:
1671
        nodeID = new NodeId(NamespaceIndex, Integer.parseInt(Identifier));
1✔
1672
        //        browseNodes(endpointURL, client, nodeID);
1673
        break;
1✔
1674
      case Opaque:
1675
        //                FIXME
UNCOV
1676
        break;
×
1677
      case String:
UNCOV
1678
        nodeID = new NodeId(NamespaceIndex, Identifier);
×
1679
        //        browseNodes(endpointURL, client, nodeID);
UNCOV
1680
        break;
×
1681
      default:
1682
        break;
1683
    }
1684
    return nodeID;
1✔
1685
  }
1686

1687
  /** Data listener for realtime OPCUA server updates. */
1688
  private void createDataChangeListener() {
1689
    try {
1690
      opcuaSubscription = ManagedSubscription.create(client, publishInterval);
1✔
UNCOV
1691
    } catch (UaException e) {
×
1692
      // TODO Auto-generated catch block
UNCOV
1693
      e.printStackTrace();
×
1694
    }
1✔
1695
    opcuaSubscription.addDataChangeListener(
1✔
1696
        (items, values) -> {
1697
          for (int i = 0; i < items.size(); i++) {
1✔
1698
            NodeIDAttrPair nodeAttrKey =
1✔
1699
                new NodeIDAttrPair(items.get(i).getNodeId(), AttributeId.Value);
1✔
1700
            log.debug(
1✔
1701
                "subscription value received: item={}, value={}",
1702
                items.get(i).getNodeId(),
1✔
1703
                values.get(i).getValue());
1✔
1704

1705
            log.debug(
1✔
1706
                "Pushing new PV for param name {} which is mapped to NodeID {}",
1707
                nodeIDToParamsMap.get(nodeAttrKey),
1✔
1708
                items.get(i).getNodeId());
1✔
1709

1710
            TupleDefinition tdef = gftdef.copy();
1✔
1711
            List<Object> cols = new ArrayList<>(4 + 1);
1✔
1712
            //            FIXME: Add leap seconds.... as config or get it from YAMCS API.
1713
            long gentime =
1✔
1714
                values
1715
                    .get(i)
1✔
1716
                    .getSourceTime()
1✔
1717
                    .getJavaInstant()
1✔
1718
                    .plus(37, ChronoUnit.SECONDS)
1✔
1719
                    .toEpochMilli();
1✔
1720
            cols.add(gentime);
1✔
1721
            cols.add(parametersNamespace);
1✔
1722
            cols.add(0);
1✔
1723
            cols.add(gentime);
1✔
1724

1725
            /**
1726
             * TODO:Not sure if this is the best way to do this since the aggregate values will be
1727
             * partially updated. Another potential approach might be to decouple the live OPCUA
1728
             * data(subscriptions) via namespaces. For example; have a "special" namespace called
1729
             * "subscriptions" that ONLY gets updated with items. And maybe another namespace for
1730
             * static data...maybe.
1731
             *
1732
             * <p>Another option is to flatten everything and have no aggregate types at all. That
1733
             * approach might even simplify the code quite a bit...
1734
             *
1735
             * <p>Another question worth answering before moving forward is to find out whether or
1736
             * not it is concrete in the OPCUA protocol what data can change in real time and which
1737
             * data is "static". Not sure if there is any "static" data given that clients have the
1738
             * ability of writing to values... might be worth a test.
1739
             */
1740
            log.debug(
1✔
1741
                "Data({}) chnage triggered for {}",
1742
                values.get(i).getValue(),
1✔
1743
                nodeIDToParamsMap.get(nodeAttrKey));
1✔
1744

1745
            if (nodeIDToParamsMap.get(nodeAttrKey) == null) {
1✔
UNCOV
1746
              log.debug("No parameter mapping found for {}", nodeAttrKey.nodeID);
×
UNCOV
1747
              continue;
×
1748
            } else {
1749
              log.debug(
1✔
1750
                  String.format(
1✔
1751
                      "parameter mapping found for {} and {}",
1752
                      nodeAttrKey.nodeID,
1753
                      nodeAttrKey.attrID));
1754
            }
1755

1756
            if (values.get(i).getValue() != null && values.get(i).getValue().getValue() != null) {
1✔
1757
              //              tdef.addColumn(
1758
              //                  nodeIDToParamsMap.get(nodeAttrKey).getQualifiedName(),
1759
              // DataType.PARAMETER_VALUE);
1760

1761
              switch (nodeIDToParamsMap.get(nodeAttrKey).getParameterType().getValueType()) {
1✔
1762
                case AGGREGATE:
1763
                  {
NEW
1764
                    String value = (String) values.get(i).getValue().getValue();
×
1765

NEW
1766
                    tdef.addColumn(
×
NEW
1767
                        nodeIDToParamsMap.get(nodeAttrKey).getQualifiedName(),
×
1768
                        DataType.PARAMETER_VALUE);
NEW
1769
                    cols.add(getPV(nodeIDToParamsMap.get(nodeAttrKey), gentime, value));
×
1770
                  }
NEW
1771
                  break;
×
1772
                case ARRAY:
1773
                  {
NEW
1774
                    String value = (String) values.get(i).getValue().getValue();
×
1775

NEW
1776
                    tdef.addColumn(
×
NEW
1777
                        nodeIDToParamsMap.get(nodeAttrKey).getQualifiedName(),
×
1778
                        DataType.PARAMETER_VALUE);
NEW
1779
                    cols.add(getPV(nodeIDToParamsMap.get(nodeAttrKey), gentime, value));
×
1780
                  }
NEW
1781
                  break;
×
1782
                case BINARY:
1783
                  {
NEW
1784
                    String value = (String) values.get(i).getValue().getValue();
×
NEW
1785
                    tdef.addColumn(
×
NEW
1786
                        nodeIDToParamsMap.get(nodeAttrKey).getQualifiedName(),
×
1787
                        DataType.PARAMETER_VALUE);
NEW
1788
                    cols.add(getPV(nodeIDToParamsMap.get(nodeAttrKey), gentime, value));
×
1789
                  }
NEW
1790
                  break;
×
1791
                case BOOLEAN:
1792
                  {
1793
                    boolean value = (boolean) values.get(i).getValue().getValue();
1✔
1794

1795
                    tdef.addColumn(
1✔
1796
                        nodeIDToParamsMap.get(nodeAttrKey).getQualifiedName(),
1✔
1797
                        DataType.PARAMETER_VALUE);
1798
                    cols.add(getPV(nodeIDToParamsMap.get(nodeAttrKey), gentime, value));
1✔
1799
                  }
1800
                  break;
1✔
1801
                case DOUBLE:
1802
                  {
1803
                    double value = (double) values.get(i).getValue().getValue();
1✔
1804

1805
                    tdef.addColumn(
1✔
1806
                        nodeIDToParamsMap.get(nodeAttrKey).getQualifiedName(),
1✔
1807
                        DataType.PARAMETER_VALUE);
1808
                    cols.add(getPV(nodeIDToParamsMap.get(nodeAttrKey), gentime, value));
1✔
1809
                  }
1810
                  break;
1✔
1811
                case ENUMERATED:
1812
                  {
NEW
1813
                    String value = (String) values.get(i).getValue().getValue();
×
1814

NEW
1815
                    tdef.addColumn(
×
NEW
1816
                        nodeIDToParamsMap.get(nodeAttrKey).getQualifiedName(),
×
1817
                        DataType.PARAMETER_VALUE);
NEW
1818
                    cols.add(getPV(nodeIDToParamsMap.get(nodeAttrKey), gentime, value));
×
1819
                  }
NEW
1820
                  break;
×
1821
                case FLOAT:
1822
                  {
1823
                    float value = (float) values.get(i).getValue().getValue();
1✔
1824

1825
                    tdef.addColumn(
1✔
1826
                        nodeIDToParamsMap.get(nodeAttrKey).getQualifiedName(),
1✔
1827
                        DataType.PARAMETER_VALUE);
1828
                    cols.add(getPV(nodeIDToParamsMap.get(nodeAttrKey), gentime, value));
1✔
1829
                  }
1830
                  break;
1✔
1831
                case NONE:
1832
                  {
NEW
1833
                    String value = (String) values.get(i).getValue().getValue();
×
1834

NEW
1835
                    tdef.addColumn(
×
NEW
1836
                        nodeIDToParamsMap.get(nodeAttrKey).getQualifiedName(),
×
1837
                        DataType.PARAMETER_VALUE);
NEW
1838
                    cols.add(getPV(nodeIDToParamsMap.get(nodeAttrKey), gentime, value));
×
1839
                  }
NEW
1840
                  break;
×
1841
                case SINT32:
1842
                  {
1843
                    int value = (int) values.get(i).getValue().getValue();
1✔
1844
                    tdef.addColumn(
1✔
1845
                        nodeIDToParamsMap.get(nodeAttrKey).getQualifiedName(),
1✔
1846
                        DataType.PARAMETER_VALUE);
1847
                    cols.add(getPV(nodeIDToParamsMap.get(nodeAttrKey), gentime, value));
1✔
1848
                  }
1849
                  break;
1✔
1850
                case SINT64:
1851
                  {
1852
                    long value = (long) values.get(i).getValue().getValue();
1✔
1853

1854
                    tdef.addColumn(
1✔
1855
                        nodeIDToParamsMap.get(nodeAttrKey).getQualifiedName(),
1✔
1856
                        DataType.PARAMETER_VALUE);
1857
                    cols.add(getPV(nodeIDToParamsMap.get(nodeAttrKey), gentime, value));
1✔
1858
                  }
1859
                  break;
1✔
1860
                case STRING:
1861
                  {
1862
                    String value = (String) values.get(i).getValue().getValue().toString();
1✔
1863

1864
                    tdef.addColumn(
1✔
1865
                        nodeIDToParamsMap.get(nodeAttrKey).getQualifiedName(),
1✔
1866
                        DataType.PARAMETER_VALUE);
1867
                    cols.add(getPV(nodeIDToParamsMap.get(nodeAttrKey), gentime, value));
1✔
1868
                  }
1869
                  break;
1✔
1870
                case TIMESTAMP:
1871
                  {
NEW
1872
                    String value = (String) values.get(i).getValue().getValue();
×
1873

NEW
1874
                    tdef.addColumn(
×
NEW
1875
                        nodeIDToParamsMap.get(nodeAttrKey).getQualifiedName(),
×
1876
                        DataType.PARAMETER_VALUE);
NEW
1877
                    cols.add(getPV(nodeIDToParamsMap.get(nodeAttrKey), gentime, value));
×
1878
                  }
NEW
1879
                  break;
×
1880
                case UINT32:
1881
                  {
NEW
1882
                    int value = (int) values.get(i).getValue().getValue();
×
NEW
1883
                    tdef.addColumn(
×
NEW
1884
                        nodeIDToParamsMap.get(nodeAttrKey).getQualifiedName(),
×
1885
                        DataType.PARAMETER_VALUE);
NEW
1886
                    cols.add(getPV(nodeIDToParamsMap.get(nodeAttrKey), gentime, value));
×
1887
                  }
NEW
1888
                  break;
×
1889
                case UINT64:
1890
                  {
NEW
1891
                    long value = (long) values.get(i).getValue().getValue();
×
1892

NEW
1893
                    tdef.addColumn(
×
NEW
1894
                        nodeIDToParamsMap.get(nodeAttrKey).getQualifiedName(),
×
1895
                        DataType.PARAMETER_VALUE);
NEW
1896
                    cols.add(getPV(nodeIDToParamsMap.get(nodeAttrKey), gentime, value));
×
1897
                  }
NEW
1898
                  break;
×
1899
                default:
1900
                  break;
1901
              }
1902

1903
              //              cols.add(
1904
              //                  getPV(
1905
              //                      nodeIDToParamsMap.get(nodeAttrKey),
1906
              //                      gentime,
1907
              //                      values.get(i).getValue().getValue()));
1908

1909
              pushTuple(tdef, cols);
1✔
1910

1911
              inCount.getAndAdd(1);
1✔
1912
            } else {
1913
              // TODO:Add some type emptyValue count for OPS.
1914
              log.warn(
1✔
1915
                  "Data chnage triggered for {}, but it empty. This should not happen.",
1916
                  nodeIDToParamsMap.get(nodeAttrKey).getQualifiedName());
1✔
1917
            }
1918
          }
1919
        });
1✔
1920
  }
1✔
1921

1922
  /**
1923
   * This method is here for future growth in case we find there is a benefit to using aggregate
1924
   * types
1925
   */
1926
  private void createOPCUAAttrAggregateType() {
1927

1928
    AggregateParameterType.Builder opcuaAttrsTypeBuidlder = new AggregateParameterType.Builder();
1✔
1929

1930
    opcuaAttrsType = new AggregateParameterType.Builder().setName("OPCUObjectAttributes").build();
1✔
1931

1932
    opcuaAttrsTypeBuidlder.setName("OPCUObjectAttributes");
1✔
1933
    for (AttributeId attr : AttributeId.values()) {
1✔
1934
      opcuaAttrsTypeBuidlder.addMember(new Member(attr.toString(), getBasicType(mdb, Type.STRING)));
1✔
1935
    }
1936

1937
    opcuaAttrsType = opcuaAttrsTypeBuidlder.build();
1✔
1938
    ((NameDescription) opcuaAttrsType)
1✔
1939
        .setQualifiedName(qualifiedName(parametersNamespace, opcuaAttrsType.getName()));
1✔
1940
  }
1✔
1941

1942
  /**
1943
   * This method is here for future growth in case we find there is a benefit to using aggregate
1944
   * types
1945
   */
1946
  private void createOPCUANodeIdTypes() {
1947
    AggregateParameterType.Builder opcuaAttrsNumericNodeIdBuidlder =
1✔
1948
        new AggregateParameterType.Builder();
1949

1950
    opcuaAttrsNumericNodeIdBuidlder.setName("OPCUA_Numeric_NodeId");
1✔
1951
    opcuaAttrsNumericNodeIdBuidlder.addMember(
1✔
1952
        new Member("namespaceIndex", getBasicType(mdb, Type.UINT64)));
1✔
1953
    opcuaAttrsNumericNodeIdBuidlder.addMember(
1✔
1954
        new Member("identifier", getBasicType(mdb, Type.UINT64)));
1✔
1955

1956
    opcuaNodeIdNumericType = opcuaAttrsNumericNodeIdBuidlder.build();
1✔
1957
    ((NameDescription) opcuaNodeIdNumericType)
1✔
1958
        .setQualifiedName(qualifiedName(parametersNamespace, opcuaNodeIdNumericType.getName()));
1✔
1959

1960
    AggregateParameterType.Builder opcuaAttrsTypeStringBuidlder =
1✔
1961
        new AggregateParameterType.Builder();
1962

1963
    opcuaAttrsTypeStringBuidlder.setName("OPCUA_String_NodeId");
1✔
1964
    opcuaAttrsTypeStringBuidlder.addMember(
1✔
1965
        new Member("namespaceIndex", getBasicType(mdb, Type.UINT64)));
1✔
1966
    opcuaAttrsTypeStringBuidlder.addMember(
1✔
1967
        new Member("identifier", getBasicType(mdb, Type.STRING)));
1✔
1968

1969
    opcuaNodeIdStringType = opcuaAttrsTypeStringBuidlder.build();
1✔
1970
    ((NameDescription) opcuaNodeIdStringType)
1✔
1971
        .setQualifiedName(qualifiedName(parametersNamespace, opcuaNodeIdStringType.getName()));
1✔
1972

1973
    mdb.addParameterType(opcuaNodeIdNumericType, true);
1✔
1974
    mdb.addParameterType(opcuaNodeIdStringType, true);
1✔
1975
  }
1✔
1976

1977
  private ParameterType OPCUAAttrTypeToParamType(AttributeId attr, UaNode node) {
1978
    ParameterType pType = null;
1✔
1979

1980
    switch (attr) {
1✔
1981
      case AccessLevel:
1982
        pType = getBasicType(mdb, Type.STRING);
1✔
1983
        break;
1✔
1984
      case ArrayDimensions:
1985
        pType = getBasicType(mdb, Type.STRING);
1✔
1986
        break;
1✔
1987
      case BrowseName:
1988
        pType = getBasicType(mdb, Type.STRING);
1✔
1989
        break;
1✔
1990
      case ContainsNoLoops:
1991
        pType = getBasicType(mdb, Type.STRING);
1✔
1992
        break;
1✔
1993
      case DataType:
1994
        pType = getBasicType(mdb, Type.STRING);
1✔
1995
        break;
1✔
1996
      case Description:
1997
        pType = getBasicType(mdb, Type.STRING);
1✔
1998
        break;
1✔
1999
      case DisplayName:
2000
        pType = getBasicType(mdb, Type.STRING);
1✔
2001
        break;
1✔
2002
      case EventNotifier:
2003
        pType = getBasicType(mdb, Type.STRING);
1✔
2004
        break;
1✔
2005
      case Executable:
2006
        pType = getBasicType(mdb, Type.STRING);
1✔
2007
        break;
1✔
2008
      case Historizing:
2009
        pType = getBasicType(mdb, Type.STRING);
1✔
2010
        break;
1✔
2011
      case InverseName:
2012
        pType = getBasicType(mdb, Type.STRING);
1✔
2013
        break;
1✔
2014
      case IsAbstract:
2015
        pType = getBasicType(mdb, Type.STRING);
1✔
2016
        break;
1✔
2017
      case MinimumSamplingInterval:
2018
        pType = getBasicType(mdb, Type.STRING);
1✔
2019
        break;
1✔
2020
      case NodeClass:
2021
        pType = getBasicType(mdb, Type.STRING);
1✔
2022
        break;
1✔
2023
      case NodeId:
2024
        pType = getBasicType(mdb, Type.STRING);
1✔
2025
        break;
1✔
2026
      case Symmetric:
2027
        pType = getBasicType(mdb, Type.STRING);
1✔
2028
        break;
1✔
2029
      case UserAccessLevel:
2030
        pType = getBasicType(mdb, Type.STRING);
1✔
2031
        break;
1✔
2032
      case UserExecutable:
2033
        pType = getBasicType(mdb, Type.STRING);
1✔
2034
        break;
1✔
2035
      case UserWriteMask:
2036
        pType = getBasicType(mdb, Type.STRING);
1✔
2037
        break;
1✔
2038
      case Value:
2039
        try {
2040

2041
          var value = node.readAttribute(attr).getValue();
1✔
2042

2043
          if (value.isNotNull()) {
1✔
2044

2045
            Object valueObject = value.getValue();
1✔
2046

2047
            if (valueObject instanceof Short) {
1✔
2048
              pType = getBasicType(mdb, Type.SINT32);
1✔
2049
            } else if (valueObject instanceof Integer) {
1✔
2050
              pType = getBasicType(mdb, Type.SINT32);
1✔
2051

2052
            } else if (valueObject instanceof Long) {
1✔
2053
              pType = getBasicType(mdb, Type.SINT64);
1✔
2054
            } else if (valueObject instanceof Double) {
1✔
2055
              pType = getBasicType(mdb, Type.DOUBLE);
1✔
2056
            } else if (valueObject instanceof Float) {
1✔
2057
              pType = getBasicType(mdb, Type.FLOAT);
1✔
2058
            } else if (valueObject instanceof Character) {
1✔
UNCOV
2059
              pType = getBasicType(mdb, Type.STRING);
×
2060
            } else if (valueObject instanceof String) {
1✔
2061
              pType = getBasicType(mdb, Type.STRING);
1✔
2062

2063
            } else if (valueObject instanceof Boolean) {
1✔
2064
              pType = getBasicType(mdb, Type.BOOLEAN);
1✔
2065
            } else {
2066
              pType = getBasicType(mdb, Type.STRING);
1✔
2067
            }
2068
          } else {
1✔
2069
            pType = getBasicType(mdb, Type.STRING);
1✔
2070
          }
2071

UNCOV
2072
        } catch (UaException e) {
×
2073
          // TODO Auto-generated catch block
UNCOV
2074
          e.printStackTrace();
×
2075
          //                        FIXME:Add log message
2076
        }
1✔
UNCOV
2077
        break;
×
2078
      case ValueRank:
2079
        pType = getBasicType(mdb, Type.STRING);
1✔
2080
        break;
1✔
2081
      case WriteMask:
2082
        pType = getBasicType(mdb, Type.STRING);
1✔
2083
        break;
1✔
2084
      default:
2085
        break;
2086
    }
2087

2088
    return pType;
1✔
2089
  }
2090

2091
  private void subscribeToEvents(OpcUaClient client)
2092
      throws InterruptedException, ExecutionException {
2093
    // create a subscription and a monitored item
2094
    UaSubscription subscription = client.getSubscriptionManager().createSubscription(1000.0).get();
1✔
2095

2096
    ReadValueId readValueId =
1✔
2097
        new ReadValueId(
2098
            Identifiers.Server, AttributeId.EventNotifier.uid(), null, QualifiedName.NULL_VALUE);
1✔
2099

2100
    // client handle must be unique per item
2101
    UInteger clientHandle = uint(clientHandles.getAndIncrement());
1✔
2102

2103
    EventFilter eventFilter =
1✔
2104
        new EventFilter(
2105
            new SimpleAttributeOperand[] {
2106
              new SimpleAttributeOperand(
2107
                  Identifiers.BaseEventType,
2108
                  new QualifiedName[] {new QualifiedName(0, "EventId")},
2109
                  AttributeId.Value.uid(),
1✔
2110
                  null),
2111
              new SimpleAttributeOperand(
2112
                  Identifiers.BaseEventType,
2113
                  new QualifiedName[] {new QualifiedName(0, "EventType")},
2114
                  AttributeId.Value.uid(),
1✔
2115
                  null),
2116
              new SimpleAttributeOperand(
2117
                  Identifiers.BaseEventType,
2118
                  new QualifiedName[] {new QualifiedName(0, "Severity")},
2119
                  AttributeId.Value.uid(),
1✔
2120
                  null),
2121
              new SimpleAttributeOperand(
2122
                  Identifiers.BaseEventType,
2123
                  new QualifiedName[] {new QualifiedName(0, "Time")},
2124
                  AttributeId.Value.uid(),
1✔
2125
                  null),
2126
              new SimpleAttributeOperand(
2127
                  Identifiers.BaseEventType,
2128
                  new QualifiedName[] {new QualifiedName(0, "Message")},
2129
                  AttributeId.Value.uid(),
1✔
2130
                  null)
2131
            },
2132
            new ContentFilter(null));
2133

2134
    MonitoringParameters parameters =
1✔
2135
        new MonitoringParameters(
2136
            clientHandle,
2137
            0.0,
1✔
2138
            ExtensionObject.encode(client.getStaticSerializationContext(), eventFilter),
1✔
2139
            uint(10),
1✔
2140
            true);
1✔
2141

2142
    MonitoredItemCreateRequest request =
1✔
2143
        new MonitoredItemCreateRequest(readValueId, MonitoringMode.Reporting, parameters);
2144

2145
    List<UaMonitoredItem> items =
1✔
2146
        subscription.createMonitoredItems(TimestampsToReturn.Both, newArrayList(request)).get();
1✔
2147

2148
    // do something with the value updates
2149
    UaMonitoredItem monitoredItem = items.get(0);
1✔
2150

2151
    monitoredItem.setEventConsumer(
1✔
2152
        (item, vs) -> {
2153
          internalLogger.info("Event Received from {}", item.getReadValueId().getNodeId());
1✔
2154

2155
          StringBuilder eventText = new StringBuilder();
1✔
2156

2157
          ByteString eventId;
2158
          NodeId eventType;
2159
          UShort eventSeverity;
2160
          DateTime eventTime;
2161
          LocalizedText eventMessage;
2162

2163
          for (int i = 0; i < vs.length; i++) {
1✔
2164
            internalLogger.info("\tvariant[{}]: {}", i, vs[i].getValue());
1✔
2165
          }
2166

2167
          eventId = (ByteString) vs[0].getValue();
1✔
2168
          eventType = (NodeId) vs[1].getValue();
1✔
2169
          eventSeverity = (UShort) vs[2].getValue();
1✔
2170
          eventTime = (DateTime) vs[3].getValue();
1✔
2171
          eventMessage = (LocalizedText) vs[4].getValue();
1✔
2172

2173
          //          FIXME:Map these values to YAMCS API
2174
          eventText.append("eventId:" + eventId);
1✔
2175
          eventText.append("\n");
1✔
2176
          eventText.append("eventType:" + eventType);
1✔
2177
          eventText.append("\n");
1✔
2178
          eventText.append("eventSeverity:" + eventSeverity);
1✔
2179
          eventText.append("\n");
1✔
2180
          eventText.append("eventTime:" + eventTime);
1✔
2181
          eventText.append("\n");
1✔
2182
          eventText.append("eventMessage:" + eventMessage);
1✔
2183
          org.yamcs.yarch.protobuf.Db.Event ev =
2184
              Event.newBuilder()
1✔
2185
                  .setGenerationTime(YamcsServer.getTimeService(yamcsInstance).getMissionTime())
1✔
2186
                  .setGenerationTime(YamcsServer.getTimeService(yamcsInstance).getMissionTime())
1✔
2187
                  .setSource(this.linkName)
1✔
2188
                  .setType(this.linkName)
1✔
2189
                  .setMessage(eventText.toString())
1✔
2190
                  .setSeverity(EventSeverity.INFO)
1✔
2191
                  .build();
1✔
2192
          eventProducer.sendEvent(ev);
1✔
2193
        });
1✔
2194
  }
1✔
2195

2196
  @Override
2197
  public void setupSystemParameters(SystemParametersService sysParamService) {
2198
    super.setupSystemParameters(sysParamService);
1✔
2199
    //          currentOPCUAStatus;
2200
    OPCUAStatusParam =
1✔
2201
        sysParamService.createEnumeratedSystemParameter(
1✔
2202
            linkName + "/OPCUAStatusParam",
2203
            OPCUAStatus.class,
2204
            "The current status of OPCUA client");
2205
    EnumeratedParameterType spLinkStatusType =
1✔
2206
        (EnumeratedParameterType) OPCUAStatusParam.getParameterType();
1✔
2207
    spLinkStatusType
1✔
2208
        .enumValue(OPCUAStatus.OPCUA_INIT_CONFIG.name())
1✔
2209
        .setDescription(
1✔
2210
            "This link is in the configuration stage(Configuring OPCUA parameters such as certificates)");
2211
    spLinkStatusType
1✔
2212
        .enumValue(OPCUAStatus.OPCUA_INIT_TREE.name())
1✔
2213
        .setDescription(
1✔
2214
            "The link is parsing the OPCUA Tree and mapping them to PVs."
2215
                + " Depending on configuration, this can take a while.");
2216
    spLinkStatusType
1✔
2217
        .enumValue(OPCUAStatus.OPCUA_INIT_EVENTS.name())
1✔
2218
        .setDescription("The link is configuring and subscribing to OPCUA events");
1✔
2219
    spLinkStatusType
1✔
2220
        .enumValue(OPCUAStatus.OPCUA_INIT_DATA_SUBSCRIPTION.name())
1✔
2221
        .setDescription(
1✔
2222
            "The link is creating subscriptions for each node that was parsed from the tree"
2223
                + "that has a Value attribute.");
2224
    spLinkStatusType
1✔
2225
        .enumValue(OPCUAStatus.OPCUA_INIT_ALL_DATA_QUERY.name())
1✔
2226
        .setDescription(
1✔
2227
            "The link is querying all attributes of all parsed nodes."
2228
                + "This is can be configured to be done at startup.");
2229
    spLinkStatusType
1✔
2230
        .enumValue(OPCUAStatus.OPCUA_OK.name())
1✔
2231
        .setDescription(
1✔
2232
            "The link is done with all OPCUA initialization. It is in an usable state.");
2233

2234
    OPCUAActiveSubsParam =
1✔
2235
        sysParamService.createSystemParameter(
1✔
2236
            linkName + "/OPCUAActiveSubs",
2237
            Type.UINT64,
2238
            "The total number of active opcua subscriptions");
2239
  }
1✔
2240

2241
  @Override
2242
  public List<ParameterValue> getSystemParameters() {
2243
    long time = getCurrentTime();
1✔
2244

2245
    ArrayList<ParameterValue> list = new ArrayList<>();
1✔
2246

2247
    list.add(
1✔
2248
        org.yamcs.parameter.SystemParametersService.getPV(
1✔
2249
            OPCUAStatusParam, time, currentOPCUAStatus));
2250

2251
    list.add(
1✔
2252
        org.yamcs.parameter.SystemParametersService.getPV(
1✔
2253
            OPCUAActiveSubsParam, time, OPCUAActiveSubs.get()));
1✔
2254
    try {
2255
      super.collectSystemParameters(time, list);
1✔
UNCOV
2256
    } catch (Exception e) {
×
UNCOV
2257
      log.error("Exception caught when collecting link system parameters", e);
×
2258
    }
1✔
2259
    return list;
1✔
2260
  }
2261
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc