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

WindhoverLabs / yamcs-opcua / #16

10 Jul 2024 11:54PM UTC coverage: 66.703% (-0.1%) from 66.811%
#16

push

lorenzo-gomez-windhover
-Cleanup

1 of 2 new or added lines in 1 file covered. (50.0%)

1 existing line in 1 file now uncovered.

617 of 925 relevant lines covered (66.7%)

0.67 hits per line

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

66.19
/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.AtomicInteger;
63
import java.util.concurrent.atomic.AtomicLong;
64
import java.util.function.Supplier;
65
import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
66
import org.eclipse.milo.opcua.sdk.client.api.config.OpcUaClientConfig;
67
import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaMonitoredItem;
68
import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaSubscription;
69
import org.eclipse.milo.opcua.sdk.client.nodes.UaNode;
70
import org.eclipse.milo.opcua.sdk.client.subscriptions.ManagedDataItem;
71
import org.eclipse.milo.opcua.sdk.client.subscriptions.ManagedSubscription;
72
import org.eclipse.milo.opcua.stack.client.DiscoveryClient;
73
import org.eclipse.milo.opcua.stack.core.AttributeId;
74
import org.eclipse.milo.opcua.stack.core.Identifiers;
75
import org.eclipse.milo.opcua.stack.core.UaException;
76
import org.eclipse.milo.opcua.stack.core.types.builtin.ByteString;
77
import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue;
78
import org.eclipse.milo.opcua.stack.core.types.builtin.DateTime;
79
import org.eclipse.milo.opcua.stack.core.types.builtin.ExtensionObject;
80
import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText;
81
import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId;
82
import org.eclipse.milo.opcua.stack.core.types.builtin.QualifiedName;
83
import org.eclipse.milo.opcua.stack.core.types.builtin.StatusCode;
84
import org.eclipse.milo.opcua.stack.core.types.builtin.Variant;
85
import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger;
86
import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UShort;
87
import org.eclipse.milo.opcua.stack.core.types.enumerated.BrowseDirection;
88
import org.eclipse.milo.opcua.stack.core.types.enumerated.BrowseResultMask;
89
import org.eclipse.milo.opcua.stack.core.types.enumerated.IdType;
90
import org.eclipse.milo.opcua.stack.core.types.enumerated.MonitoringMode;
91
import org.eclipse.milo.opcua.stack.core.types.enumerated.NodeClass;
92
import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn;
93
import org.eclipse.milo.opcua.stack.core.types.structured.BrowseDescription;
94
import org.eclipse.milo.opcua.stack.core.types.structured.BrowsePath;
95
import org.eclipse.milo.opcua.stack.core.types.structured.BrowsePathResult;
96
import org.eclipse.milo.opcua.stack.core.types.structured.BrowseResult;
97
import org.eclipse.milo.opcua.stack.core.types.structured.ContentFilter;
98
import org.eclipse.milo.opcua.stack.core.types.structured.EndpointDescription;
99
import org.eclipse.milo.opcua.stack.core.types.structured.EventFilter;
100
import org.eclipse.milo.opcua.stack.core.types.structured.MonitoredItemCreateRequest;
101
import org.eclipse.milo.opcua.stack.core.types.structured.MonitoringParameters;
102
import org.eclipse.milo.opcua.stack.core.types.structured.ReadValueId;
103
import org.eclipse.milo.opcua.stack.core.types.structured.ReferenceDescription;
104
import org.eclipse.milo.opcua.stack.core.types.structured.RelativePath;
105
import org.eclipse.milo.opcua.stack.core.types.structured.RelativePathElement;
106
import org.eclipse.milo.opcua.stack.core.types.structured.SimpleAttributeOperand;
107
import org.eclipse.milo.opcua.stack.core.types.structured.TranslateBrowsePathsToNodeIdsResponse;
108
import org.slf4j.Logger;
109
import org.slf4j.LoggerFactory;
110
import org.yamcs.ConfigurationException;
111
import org.yamcs.Spec;
112
import org.yamcs.Spec.OptionType;
113
import org.yamcs.StandardTupleDefinitions;
114
import org.yamcs.ValidationException;
115
import org.yamcs.YConfiguration;
116
import org.yamcs.YamcsServer;
117
import org.yamcs.http.NotFoundException;
118
import org.yamcs.mdb.XtceAssembler;
119
import org.yamcs.parameter.ParameterValue;
120
import org.yamcs.parameter.SystemParametersProducer;
121
import org.yamcs.parameter.SystemParametersService;
122
import org.yamcs.protobuf.Event.EventSeverity;
123
import org.yamcs.protobuf.Yamcs.NamedObjectId;
124
import org.yamcs.protobuf.Yamcs.Value.Type;
125
import org.yamcs.tctm.AbstractLink;
126
import org.yamcs.tctm.Link;
127
import org.yamcs.tctm.LinkAction;
128
import org.yamcs.utils.ValueUtility;
129
import org.yamcs.xtce.AbsoluteTimeParameterType;
130
import org.yamcs.xtce.AggregateParameterType;
131
import org.yamcs.xtce.BinaryParameterType;
132
import org.yamcs.xtce.BooleanParameterType;
133
import org.yamcs.xtce.EnumeratedParameterType;
134
import org.yamcs.xtce.FloatParameterType;
135
import org.yamcs.xtce.IntegerParameterType;
136
import org.yamcs.xtce.Member;
137
import org.yamcs.xtce.NameDescription;
138
import org.yamcs.xtce.Parameter;
139
import org.yamcs.xtce.ParameterType;
140
import org.yamcs.xtce.SpaceSystem;
141
import org.yamcs.xtce.StringParameterType;
142
import org.yamcs.xtce.XtceDb;
143
import org.yamcs.yarch.DataType;
144
import org.yamcs.yarch.Stream;
145
import org.yamcs.yarch.Tuple;
146
import org.yamcs.yarch.TupleDefinition;
147
import org.yamcs.yarch.YarchDatabase;
148
import org.yamcs.yarch.YarchDatabaseInstance;
149
import org.yamcs.yarch.protobuf.Db.Event;
150

151
public class OPCUALink extends AbstractLink implements Runnable, SystemParametersProducer {
1✔
152

153
  class NodeIDAttrPair {
154
    NodeId nodeID;
155
    AttributeId attrID;
156

157
    public NodeIDAttrPair(NodeId newNodeID, AttributeId newAttrID) {
1✔
158
      this.nodeID = newNodeID;
1✔
159
      this.attrID = newAttrID;
1✔
160
    }
1✔
161

162
    public int hashCode() {
163
      return Objects.hash(this.nodeID, this.attrID);
1✔
164
    }
165

166
    public boolean equals(Object obj) {
167
      return (this.hashCode() == obj.hashCode());
1✔
168
    }
169
  }
170

171
  class NodePath {
1✔
172
    String path;
173

174
    HashMap<Object, Object> rootNodeID = new HashMap<Object, Object>();
1✔
175
  }
176

177
  public enum OPCUAStatus {
1✔
178
    OPCUA_INIT_CONFIG,
1✔
179
    OPCUA_INIT_TREE,
1✔
180
    OPCUA_INIT_GENERATE_XTCE,
1✔
181
    OPCUA_INIT_EVENTS,
1✔
182
    OPCUA_INIT_DATA_SUBSCRIPTION,
1✔
183
    OPCUA_INIT_ALL_DATA_QUERY,
1✔
184
    OPCUA_OK
1✔
185
  }
186

187
  /* Configuration Defaults */
188
  static final String STREAM_NAME = "opcua_params";
189

190
  /* Internal member attributes. */
191
  protected Thread thread;
192

193
  private String opcuaStreamName;
194

195
  // /yamcs/<server_id>
196
  private String parametersNamespace;
197
  XtceDb mdb;
198

199
  Stream opcuaStream;
200

201
  private static TupleDefinition gftdef = StandardTupleDefinitions.PARAMETER.copy();
1✔
202

203
  private AggregateParameterType opcuaAttrsType;
204
  private AggregateParameterType opcuaNodeIdNumericType;
205
  private AggregateParameterType opcuaNodeIdStringType;
206
  private ManagedSubscription opcuaSubscription;
207

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

210
  private int rootNamespaceIndex;
211

212
  private String rootIdentifier; // Relative to the rootNamespaceIndex
213

214
  private IdType rootIdentifierType; // Relative to the rootNamespaceIndex
215

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

227
  private OpcUaClient client;
228

229
  protected AtomicLong inCount = new AtomicLong(0);
1✔
230

231
  private Status linkStatus = Status.OK;
1✔
232

233
  /* Configuration Parameters */
234

235
  private String discoverURL;
236
  private String endpointURL;
237
  private boolean queryAllNodesAtStartup;
238
  private String outputFile;
239
  private int publishInterval; // milliseconds
240

241
  private ArrayList<NodePath> relativeNodePaths = new ArrayList<NodePath>();
1✔
242

243
  private final AtomicLong clientHandles = new AtomicLong(1L);
1✔
244

245
  /* System parameters*/
246

247
  private Parameter OPCUAStatusParam;
248
  private OPCUAStatus currentOPCUAStatus;
249
  private Parameter OPCUAActiveSubsParam;
250
  private AtomicLong OPCUAActiveSubs = new AtomicLong(0);
1✔
251

252
  LinkAction startAction =
1✔
253
      new LinkAction("query_all", "Query All OPCUA Server Data") {
1✔
254
        @Override
255
        public JsonObject execute(Link link, JsonObject jsonObject) {
256

257
          internalLogger.info("Executing query_all action");
1✔
258
          CompletableFuture.supplyAsync(
1✔
259
                  (Supplier<Integer>)
260
                      () -> {
261
                        queryAllOPCUAData();
1✔
262

263
                        return 0;
1✔
264
                      })
265
              .whenComplete(
1✔
266
                  (vaue, e) -> {
267
                    internalLogger.info("query_all action Complete");
1✔
268
                  });
1✔
269

270
          return jsonObject;
1✔
271
        }
272
      };
273

274
  public OPCUAStatus getCurrentOPCUAStatus() {
275
    return currentOPCUAStatus;
1✔
276
  }
277

278
  @Override
279
  public Spec getSpec() {
280
    Spec spec = new Spec();
1✔
281

282
    /* Define our configuration parameters. */
283
    spec.addOption("name", OptionType.STRING).withRequired(true);
1✔
284
    spec.addOption("class", OptionType.STRING).withRequired(true);
1✔
285
    spec.addOption("opcuaStream", OptionType.STRING).withRequired(true);
1✔
286
    spec.addOption("endpointUrl", OptionType.STRING).withRequired(true);
1✔
287
    spec.addOption("discoveryUrl", OptionType.STRING).withRequired(true);
1✔
288
    spec.addOption("xtceOutputFile", OptionType.STRING).withRequired(true);
1✔
289
    spec.addOption("parametersNamespace", OptionType.STRING).withRequired(true);
1✔
290
    spec.addOption("publishInterval", OptionType.INTEGER).withRequired(true);
1✔
291
    spec.addOption("queryAllNodesAtStartup", OptionType.BOOLEAN).withRequired(false);
1✔
292

293
    Spec rootNodeIDSpec = new Spec();
1✔
294

295
    rootNodeIDSpec.addOption("namespaceIndex", OptionType.INTEGER).withRequired(true);
1✔
296
    rootNodeIDSpec.addOption("identifier", OptionType.STRING).withRequired(true);
1✔
297
    rootNodeIDSpec.addOption("identifierType", OptionType.STRING).withRequired(true);
1✔
298

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

301
    Spec nodePathSpec = new Spec();
1✔
302
    nodePathSpec.addOption("path", OptionType.STRING);
1✔
303
    nodePathSpec
1✔
304
        .addOption("rootNodeID", OptionType.MAP)
1✔
305
        .withRequired(true)
1✔
306
        .withSpec(rootNodeIDSpec);
1✔
307

308
    spec.addOption("nodePaths", OptionType.LIST)
1✔
309
        .withElementType(OptionType.MAP)
1✔
310
        .withRequired(true)
1✔
311
        .withSpec(nodePathSpec);
1✔
312

313
    return spec;
1✔
314
  }
315

316
  @Override
317
  public void init(String yamcsInstance, String serviceName, YConfiguration config)
318
      throws ConfigurationException {
319
    super.init(yamcsInstance, serviceName, config);
1✔
320

321
    /* Local variables */
322
    this.config = config;
1✔
323
    /* Validate the configuration that the user passed us. */
324
    try {
325
      config = getSpec().validate(config);
1✔
326
    } catch (ValidationException e) {
×
327
      log.error("Failed configuration validation.", e);
×
NEW
328
      notifyFailed(e);
×
329
    }
1✔
330
    YarchDatabaseInstance ydb = YarchDatabase.getInstance(yamcsInstance);
1✔
331

332
    this.opcuaStreamName = config.getString("opcuaStream");
1✔
333

334
    opcuaStream = getStream(ydb, opcuaStreamName);
1✔
335

336
    this.endpointURL = config.getString("endpointUrl");
1✔
337

338
    this.discoverURL = config.getString("discoveryUrl");
1✔
339

340
    this.parametersNamespace = config.getString("parametersNamespace");
1✔
341

342
    queryAllNodesAtStartup = config.getBoolean("queryAllNodesAtStartup", false);
1✔
343

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

346
    rootNamespaceIndex = (int) root.get("namespaceIndex");
1✔
347

348
    rootIdentifier = (String) root.get("identifier");
1✔
349
    rootIdentifierType = IdType.valueOf((String) root.get("identifierType"));
1✔
350

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

353
    for (Map<Object, Object> path : nodePaths) {
1✔
354
      NodePath nodePath = new NodePath();
1✔
355
      nodePath.path = (String) path.get("path");
1✔
356
      nodePath.rootNodeID = (HashMap<Object, Object>) path.get("rootNodeID");
1✔
357
      relativeNodePaths.add(nodePath);
1✔
358
    }
1✔
359

360
    mdb = YamcsServer.getServer().getInstance(yamcsInstance).getXtceDb();
1✔
361

362
    outputFile = config.getString("xtceOutputFile");
1✔
363
    publishInterval = config.getInt("publishInterval");
1✔
364
  }
1✔
365

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

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

389
    // Maybe some non-xtce namespace like MDB:OPS Name
390
    id = NamedObjectId.newBuilder().setNamespace(namespace).setName(name).build();
×
391
    spaceSystem = mdb.getSpaceSystem(id);
×
392
    if (spaceSystem != null) {
×
393
      return spaceSystem;
×
394
    }
395

396
    throw new NotFoundException("No such space system");
×
397
  }
398

399
  private void opcuaInit() {
400
    //          FIXME:Might need to move this function to start(), maybe...
401

402
    createOPCUAAttrAggregateType();
1✔
403
    createOPCUANodeIdTypes();
1✔
404
    mdb.addParameterType(opcuaAttrsType, true);
1✔
405

406
    final CompletableFuture<OpcUaClient> future = new CompletableFuture<>();
1✔
407

408
    try {
409

410
      currentOPCUAStatus = OPCUAStatus.OPCUA_INIT_TREE;
1✔
411

412
      browseOPCUATree(client, future);
1✔
413

414
      currentOPCUAStatus = OPCUAStatus.OPCUA_INIT_GENERATE_XTCE;
1✔
415

416
      var spaceSystem = verifySpaceSystem(mdb, "/");
1✔
417

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

420
      BufferedWriter writer = null;
1✔
421

422
      if (outputFile != null) {
1✔
423
        writer =
1✔
424
            Files.newBufferedWriter(
1✔
425
                Paths.get(outputFile),
1✔
426
                StandardOpenOption.CREATE,
427
                StandardOpenOption.TRUNCATE_EXISTING);
428
      } else writer = null;
×
429

430
      writer.write(xtce);
1✔
431

432
      writer.flush();
1✔
433
      writer.close();
1✔
434

435
      currentOPCUAStatus = OPCUAStatus.OPCUA_INIT_EVENTS;
1✔
436
      subscribeToEvents(client);
1✔
437

438
    } catch (Exception e) {
×
439
      // TODO Auto-generated catch block
440
      e.printStackTrace();
×
441
      return;
×
442
    }
1✔
443
    try {
444
      currentOPCUAStatus = OPCUAStatus.OPCUA_INIT_DATA_SUBSCRIPTION;
1✔
445
      createOPCUASubscriptions();
1✔
446
    } catch (Exception e) {
×
447
      // TODO Auto-generated catch block
448
      e.printStackTrace();
×
449
    }
1✔
450
  }
1✔
451

452
  private void opcuaClientConnect() throws Exception {
453
    client = configureClient();
1✔
454
    connectToOPCUAServer(client);
1✔
455
  }
1✔
456

457
  private static Stream getStream(YarchDatabaseInstance ydb, String streamName) {
458
    Stream stream = ydb.getStream(streamName);
1✔
459
    if (stream == null) {
1✔
460
      try {
461
        ydb.execute("create stream " + streamName + gftdef.getStringDefinition());
1✔
462
      } catch (Exception e) {
×
463
        throw new ConfigurationException(e);
×
464
      }
1✔
465

466
      stream = ydb.getStream(streamName);
1✔
467
    }
468
    return stream;
1✔
469
  }
470

471
  @Override
472
  public void doDisable() {
473
    /* If the thread is created, interrupt it. */
474
    if (thread != null) {
1✔
475
      thread.interrupt();
1✔
476
    }
477

478
    linkStatus = Status.DISABLED;
1✔
479
  }
1✔
480

481
  @Override
482
  public void doEnable() {
483
    linkStatus = Status.OK;
1✔
484
  }
1✔
485

486
  @Override
487
  public String getDetailedStatus() {
488
    if (isDisabled()) {
1✔
489
      return String.format("DISABLED");
1✔
490
    } else {
491
      return String.format("OK, received %d packets", inCount.get());
1✔
492
    }
493
  }
494

495
  @Override
496
  public Status connectionStatus() {
497
    return linkStatus;
1✔
498
  }
499

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

521
    /* Create and start the new thread. */
522
    thread = new Thread(this);
1✔
523
    thread.setName(this.getClass().getSimpleName() + "-" + linkName);
1✔
524
    thread.start();
1✔
525

526
    notifyStarted();
1✔
527
  }
1✔
528

529
  @Override
530
  protected void doStop() {
531
    try {
532
      client.disconnect().get();
1✔
533
    } catch (InterruptedException | ExecutionException e) {
×
534
      // TODO Auto-generated catch block
535
      e.printStackTrace();
×
536
    }
1✔
537
    if (thread != null) {
1✔
538
      thread.interrupt();
1✔
539
    }
540

541
    notifyStopped();
1✔
542
  }
1✔
543

544
  @Override
545
  public void run() {
546
    opcuaInit();
1✔
547
    if (queryAllNodesAtStartup) {
1✔
548
      //            NOTE:I'm not sure if queryAllOPCUAData should block...
549
      currentOPCUAStatus = OPCUAStatus.OPCUA_INIT_ALL_DATA_QUERY;
1✔
550
      queryAllOPCUAData();
1✔
551
    }
552
    /* Enter our main loop */
553
    while (isRunningAndEnabled()) {
1✔
554
      currentOPCUAStatus = OPCUAStatus.OPCUA_OK;
1✔
555
    }
556
  }
1✔
557

558
  private void queryAllOPCUAData() {
559

560
    TupleDefinition tdef = gftdef.copy();
1✔
561
    List<Object> cols = new ArrayList<>(4 + nodeIDToParamsMap.keySet().size());
1✔
562

563
    tdef = gftdef.copy();
1✔
564
    long gentime = timeService.getMissionTime();
1✔
565
    cols.add(gentime);
1✔
566
    cols.add(parametersNamespace);
1✔
567
    cols.add(0);
1✔
568
    cols.add(gentime);
1✔
569

570
    int columnCount = 0;
1✔
571

572
    Set<NodeId> nodeSet = new HashSet<NodeId>();
1✔
573
    /**
574
     * FIXME:This is super inefficient... The reason we collect these nodeIDs in a set is because
575
     * otherwise we will have redundant subscription(s) since there is more than 1 attribute per
576
     * nodeID given how nodeIDToParamsMap is designed
577
     */
578
    for (NodeIDAttrPair pair : nodeIDToParamsMap.keySet()) {
1✔
579
      nodeSet.add(pair.nodeID);
1✔
580
    }
1✔
581

582
    for (NodeId nId : nodeSet) {
1✔
583
      UaNode node;
584

585
      try {
586
        node = client.getAddressSpace().getNode(nId);
1✔
587

588
        DataValue nodeClass = node.readAttribute(AttributeId.NodeClass);
1✔
589

590
        switch (NodeClass.from((int) nodeClass.getValue().getValue())) {
1✔
591
          case DataType:
592
            //                tdef.addColumn(pair.getValue().getQualifiedName(),
593
            // DataType.PARAMETER_VALUE);
594
            //                cols.add(getPV(pair.getValue(), Instant.now().toEpochMilli(),
595
            // "PlaceHolder"));
596
            //            columnCount++;
597
            break;
×
598
          case Method:
599
            //                tdef.addColumn(pair.getValue().getQualifiedName(),
600
            // DataType.PARAMETER_VALUE);
601
            //                cols.add(getPV(pair.getValue(), Instant.now().toEpochMilli(),
602
            // "PlaceHolder"));
603
            //            columnCount++;
604
            break;
×
605
          case Object:
606
            //                tdef.addColumn(pair.getValue().getQualifiedName(),
607
            // DataType.PARAMETER_VALUE);
608
            //                cols.add(getPV(pair.getValue(), Instant.now().toEpochMilli(),
609
            // "PlaceHolder"));
610
            //            columnCount++;
611
            for (AttributeId attr : AttributeId.VARIABLE_ATTRIBUTES) {
1✔
612

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

615
              if (p.getParameterType() == null) {
1✔
616
                String value = "";
×
617
                if (node.readAttribute(attr).getValue().isNull()) {
×
618
                  value = "NULL";
×
619
                } else {
620
                  value = node.readAttribute(attr).getValue().getValue().toString();
×
621
                }
622

623
                tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
×
624
                cols.add(getPV(p, Instant.now().toEpochMilli(), value));
×
625
                continue;
×
626
              }
627

628
              //                  System.out.println("Param value type:" + p.getParameterType());
629

630
              switch (p.getParameterType().getValueType()) {
1✔
631
                case AGGREGATE:
632
                  {
633
                    String value = "";
×
634
                    if (node.readAttribute(attr).getValue().isNull()) {
×
635
                      value = "NULL";
×
636
                    } else {
637
                      value = node.readAttribute(attr).getValue().getValue().toString();
×
638
                    }
639

640
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
×
641
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
×
642
                  }
643
                  break;
×
644
                case ARRAY:
645
                  {
646
                    String value = "";
×
647
                    if (node.readAttribute(attr).getValue().isNull()) {
×
648
                      value = "NULL";
×
649
                    } else {
650
                      value = node.readAttribute(attr).getValue().getValue().toString();
×
651
                    }
652

653
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
×
654
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
×
655
                  }
656
                  break;
×
657
                case BINARY:
658
                  {
659
                    String value = "";
×
660
                    if (node.readAttribute(attr).getValue().isNull()) {
×
661
                      value = "NULL";
×
662
                    } else {
663
                      value = node.readAttribute(attr).getValue().getValue().toString();
×
664
                    }
665

666
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
×
667
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
×
668
                  }
669
                  break;
×
670
                case BOOLEAN:
671
                  {
672
                    String value = "";
×
673
                    if (node.readAttribute(attr).getValue().isNull()) {
×
674
                      value = "NULL";
×
675
                    } else {
676
                      value = node.readAttribute(attr).getValue().getValue().toString();
×
677
                    }
678

679
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
×
680
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
×
681
                  }
682
                  break;
×
683
                case DOUBLE:
684
                  {
685
                    double value = 0;
×
686
                    if (node.readAttribute(attr).getValue().isNull()) {
×
687
                      //                      value = null;
688
                      //                            FIXME:Log warning
689
                    } else {
690
                      value = (double) node.readAttribute(attr).getValue().getValue();
×
691
                    }
692

693
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
×
694
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
×
695
                  }
696
                  break;
×
697
                case ENUMERATED:
698
                  {
699
                    String value = "";
×
700
                    if (node.readAttribute(attr).getValue().isNull()) {
×
701
                      value = "NULL";
×
702
                    } else {
703
                      value = node.readAttribute(attr).getValue().getValue().toString();
×
704
                    }
705

706
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
×
707
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
×
708
                  }
709
                  break;
×
710
                case FLOAT:
711
                  {
712
                    float value = 0;
×
713
                    if (node.readAttribute(attr).getValue().isNull()) {
×
714
                      //                      value = null;
715
                      //                            FIXME:Log warning
716
                    } else {
717
                      value = (float) node.readAttribute(attr).getValue().getValue();
×
718
                    }
719

720
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
×
721
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
×
722
                  }
723
                  break;
×
724
                case NONE:
725
                  {
726
                    String value = "";
×
727
                    if (node.readAttribute(attr).getValue().isNull()) {
×
728
                      value = "NULL";
×
729
                    } else {
730
                      value = node.readAttribute(attr).getValue().getValue().toString();
×
731
                    }
732

733
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
×
734
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
×
735
                  }
736
                  break;
×
737
                case SINT32:
738
                  {
739
                    int value = 0;
×
740
                    if (node.readAttribute(attr).getValue().isNull()) {
×
741
                      //                      value = null;
742
                      //                            FIXME:Log warning
743
                    } else {
744
                      value = (int) node.readAttribute(attr).getValue().getValue();
×
745
                    }
746

747
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
×
748
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
×
749
                  }
750
                  break;
×
751
                case SINT64:
752
                  {
753
                    long value = 0;
×
754
                    if (node.readAttribute(attr).getValue().isNull()) {
×
755
                      //                      value = null;
756
                      //                            FIXME:Log warning
757
                    } else {
758
                      value = (long) node.readAttribute(attr).getValue().getValue();
×
759
                    }
760

761
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
×
762
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
×
763
                  }
764
                  break;
×
765
                case STRING:
766
                  {
767
                    String value = "";
1✔
768
                    if (node.readAttribute(attr).getValue().isNull()) {
1✔
769
                      value = "NULL";
1✔
770
                    } else {
771
                      value = node.readAttribute(attr).getValue().getValue().toString();
1✔
772
                    }
773

774
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
1✔
775
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
1✔
776
                  }
777
                  break;
1✔
778
                case TIMESTAMP:
779
                  {
780
                    String value = "";
×
781
                    if (node.readAttribute(attr).getValue().isNull()) {
×
782
                      value = "NULL";
×
783
                    } else {
784
                      value = node.readAttribute(attr).getValue().getValue().toString();
×
785
                    }
786

787
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
×
788
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
×
789
                  }
790
                  break;
×
791
                case UINT32:
792
                  {
793
                    long value = 0;
×
794
                    if (node.readAttribute(attr).getValue().isNull()) {
×
795
                      //                      value = null;
796
                      //                            FIXME:Log warning
797
                    } else {
798
                      value = (long) node.readAttribute(attr).getValue().getValue();
×
799
                    }
800

801
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
×
802
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
×
803
                  }
804
                  break;
×
805
                case UINT64:
806
                  {
807
                    long value = 0;
×
808
                    if (node.readAttribute(attr).getValue().isNull()) {
×
809
                      //                      value = null;
810
                      //                            FIXME:Log warning
811
                    } else {
812
                      value = (long) node.readAttribute(attr).getValue().getValue();
×
813
                    }
814

815
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
×
816
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
×
817
                  }
818
                  break;
×
819
                default:
820
                  break;
821
              }
822

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

825
              columnCount++;
1✔
826
            }
1✔
827
            break;
1✔
828
          case ObjectType:
829
            //                tdef.addColumn(pair.getValue().getQualifiedName(),
830
            // DataType.PARAMETER_VALUE);
831
            //                cols.add(getPV(pair.getValue(), Instant.now().toEpochMilli(),
832
            // "PlaceHolder"));
833
            //            columnCount++;
834
            break;
×
835
          case ReferenceType:
836
            //                tdef.addColumn(pair.getValue().getQualifiedName(),
837
            // DataType.PARAMETER_VALUE);
838
            //                cols.add(getPV(pair.getValue(), Instant.now().toEpochMilli(),
839
            // "PlaceHolder"));
840
            //            columnCount++;
841
            break;
×
842
          case Unspecified:
843
            //                tdef.addColumn(pair.getValue().getQualifiedName(),
844
            // DataType.PARAMETER_VALUE);
845
            //                cols.add(getPV(pair.getValue(), Instant.now().toEpochMilli(),
846
            // "PlaceHolder"));
847
            //            columnCount++;
848
            break;
×
849
          case Variable:
850
            for (AttributeId attr : AttributeId.VARIABLE_ATTRIBUTES) {
1✔
851

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

854
              if (p.getParameterType() == null) {
1✔
855

856
                String value = "";
×
857
                if (node.readAttribute(attr).getValue().isNull()) {
×
858
                  value = "NULL";
×
859
                } else {
860
                  value = node.readAttribute(attr).getValue().getValue().toString();
×
861
                }
862

863
                tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
×
864
                cols.add(getPV(p, Instant.now().toEpochMilli(), value));
×
865
                continue;
×
866
              }
867

868
              //              System.out.println("Param value type:" + p.getParameterType());
869

870
              switch (p.getParameterType().getValueType()) {
1✔
871
                case AGGREGATE:
872
                  {
873
                    String value = "";
×
874
                    if (node.readAttribute(attr).getValue().isNull()) {
×
875
                      value = "NULL";
×
876
                    } else {
877
                      value = node.readAttribute(attr).getValue().getValue().toString();
×
878
                    }
879

880
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
×
881
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
×
882
                  }
883
                  break;
×
884
                case ARRAY:
885
                  {
886
                    String value = "";
×
887
                    if (node.readAttribute(attr).getValue().isNull()) {
×
888
                      value = "NULL";
×
889
                    } else {
890
                      value = node.readAttribute(attr).getValue().getValue().toString();
×
891
                    }
892

893
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
×
894
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
×
895
                  }
896
                  break;
×
897
                case BINARY:
898
                  {
899
                    String value = "";
×
900
                    if (node.readAttribute(attr).getValue().isNull()) {
×
901
                      value = "NULL";
×
902
                    } else {
903
                      value = node.readAttribute(attr).getValue().getValue().toString();
×
904
                    }
905

906
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
×
907
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
×
908
                  }
909
                  break;
×
910
                case BOOLEAN:
911
                  {
912
                    String value = "";
×
913
                    if (node.readAttribute(attr).getValue().isNull()) {
×
914
                      value = "NULL";
×
915
                    } else {
916
                      value = node.readAttribute(attr).getValue().getValue().toString();
×
917
                    }
918

919
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
×
920
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
×
921
                  }
922
                  break;
×
923
                case DOUBLE:
924
                  {
925
                    double value = 0;
×
926
                    if (node.readAttribute(attr).getValue().isNull()) {
×
927
                      //                      value = null;
928
                      //                            FIXME:Log warning
929
                    } else {
930
                      value = (double) node.readAttribute(attr).getValue().getValue();
×
931
                    }
932

933
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
×
934
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
×
935
                  }
936
                  break;
×
937
                case ENUMERATED:
938
                  {
939
                    String value = "";
×
940
                    if (node.readAttribute(attr).getValue().isNull()) {
×
941
                      value = "NULL";
×
942
                    } else {
943
                      value = node.readAttribute(attr).getValue().getValue().toString();
×
944
                    }
945

946
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
×
947
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
×
948
                  }
949
                  break;
×
950
                case FLOAT:
951
                  {
952
                    float value = 0;
×
953
                    if (node.readAttribute(attr).getValue().isNull()) {
×
954
                      //                      value = null;
955
                      //                            FIXME:Log warning
956
                    } else {
957
                      value = (float) node.readAttribute(attr).getValue().getValue();
×
958
                    }
959

960
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
×
961
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
×
962
                  }
963
                  break;
×
964
                case NONE:
965
                  {
966
                    String value = "";
×
967
                    if (node.readAttribute(attr).getValue().isNull()) {
×
968
                      value = "NULL";
×
969
                    } else {
970
                      value = node.readAttribute(attr).getValue().getValue().toString();
×
971
                    }
972

973
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
×
974
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
×
975
                  }
976
                  break;
×
977
                case SINT32:
978
                  {
979
                    int value = 0;
×
980
                    if (node.readAttribute(attr).getValue().isNull()) {
×
981
                      //                      value = null;
982
                      //                            FIXME:Log warning
983
                    } else {
984
                      value = (int) node.readAttribute(attr).getValue().getValue();
×
985
                    }
986

987
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
×
988
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
×
989
                  }
990
                  break;
×
991
                case SINT64:
992
                  {
993
                    long value = 0;
×
994
                    if (node.readAttribute(attr).getValue().isNull()) {
×
995
                      //                      value = null;
996
                      //                            FIXME:Log warning
997
                    } else {
998
                      value = (long) node.readAttribute(attr).getValue().getValue();
×
999
                    }
1000

1001
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
×
1002
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
×
1003
                  }
1004
                  break;
×
1005
                case STRING:
1006
                  {
1007
                    String value = "";
1✔
1008
                    if (node.readAttribute(attr).getValue().isNull()) {
1✔
1009
                      value = "NULL";
1✔
1010
                    } else {
1011
                      value = node.readAttribute(attr).getValue().getValue().toString();
1✔
1012
                    }
1013

1014
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
1✔
1015
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
1✔
1016
                  }
1017
                  break;
1✔
1018
                case TIMESTAMP:
1019
                  {
1020
                    String value = "";
×
1021
                    if (node.readAttribute(attr).getValue().isNull()) {
×
1022
                      value = "NULL";
×
1023
                    } else {
1024
                      value = node.readAttribute(attr).getValue().getValue().toString();
×
1025
                    }
1026

1027
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
×
1028
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
×
1029
                  }
1030
                  break;
×
1031
                case UINT32:
1032
                  {
1033
                    long value = 0;
×
1034
                    if (node.readAttribute(attr).getValue().isNull()) {
×
1035
                      //                      value = null;
1036
                      //                            FIXME:Log warning
1037
                    } else {
1038
                      value = (long) node.readAttribute(attr).getValue().getValue();
×
1039
                    }
1040

1041
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
×
1042
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
×
1043
                  }
1044
                  break;
×
1045
                case UINT64:
1046
                  {
1047
                    long value = 0;
×
1048
                    if (node.readAttribute(attr).getValue().isNull()) {
×
1049
                      //                      value = null;
1050
                      //                            FIXME:Log warning
1051
                    } else {
1052
                      value = (long) node.readAttribute(attr).getValue().getValue();
×
1053
                    }
1054

1055
                    tdef.addColumn(p.getQualifiedName(), DataType.PARAMETER_VALUE);
×
1056
                    cols.add(getPV(p, Instant.now().toEpochMilli(), value));
×
1057
                  }
1058
                  break;
×
1059
                default:
1060
                  break;
1061
              }
1062

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

1065
              columnCount++;
1✔
1066
            }
1✔
1067
            break;
1✔
1068
          case VariableType:
UNCOV
1069
            break;
×
1070
          case View:
1071
            break;
×
1072
          default:
1073
            break;
1074
        }
1075

1076
      } catch (UaException e) {
×
1077
        // TODO Auto-generated catch block
1078
        e.printStackTrace();
×
1079
        continue;
×
1080
      }
1✔
1081
    }
1✔
1082

1083
    /**
1084
     * FIXME:Need to come up with a mechanism to not update certain values that are up to date...
1085
     * The more I think about it, it might make sense to have "static" and "runtime" namespaces
1086
     */
1087
    pushTuple(tdef, cols);
1✔
1088

1089
    inCount.getAndAdd(columnCount);
1✔
1090
  }
1✔
1091

1092
  private synchronized void pushTuple(TupleDefinition tdef, List<Object> cols) {
1093
    Tuple t;
1094
    t = new Tuple(tdef, cols);
1✔
1095
    opcuaStream.emitTuple(t);
1✔
1096
  }
1✔
1097

1098
  private static ParameterType getOrCreateType(
1099
      XtceDb mdb, String name, Supplier<ParameterType.Builder<?>> supplier) {
1100

1101
    String fqn = XtceDb.YAMCS_SPACESYSTEM_NAME + NameDescription.PATH_SEPARATOR + name;
1✔
1102
    ParameterType ptype = mdb.getParameterType(fqn);
1✔
1103
    if (ptype != null) {
1✔
1104
      return ptype;
1✔
1105
    }
1106
    ParameterType.Builder<?> typeb = supplier.get().setName(name);
×
1107

1108
    ptype = typeb.build();
×
1109
    ((NameDescription) ptype).setQualifiedName(fqn);
×
1110

1111
    return mdb.addSystemParameterType(ptype);
×
1112
  }
1113

1114
  public static ParameterType getBasicType(XtceDb mdb, Type type) {
1115
    ParameterType pType = null;
1✔
1116
    switch (type) {
1✔
1117
      case BINARY:
1118
        return getOrCreateType(mdb, "binary", () -> new BinaryParameterType.Builder());
×
1119
      case BOOLEAN:
1120
        return getOrCreateType(mdb, "boolean", () -> new BooleanParameterType.Builder());
×
1121
      case STRING:
1122
        pType = getOrCreateType(mdb, "string", () -> new StringParameterType.Builder());
1✔
1123
        break;
1✔
1124
      case FLOAT:
1125
        return getOrCreateType(
×
1126
            mdb, "float32", () -> new FloatParameterType.Builder().setSizeInBits(32));
×
1127
      case DOUBLE:
1128
        return getOrCreateType(
×
1129
            mdb, "float64", () -> new FloatParameterType.Builder().setSizeInBits(64));
×
1130
      case SINT32:
1131
        return getOrCreateType(
×
1132
            mdb,
1133
            "sint32",
1134
            () -> new IntegerParameterType.Builder().setSizeInBits(32).setSigned(true));
×
1135
      case SINT64:
1136
        return getOrCreateType(
×
1137
            mdb,
1138
            "sint64",
1139
            () -> new IntegerParameterType.Builder().setSizeInBits(64).setSigned(true));
×
1140
      case UINT32:
1141
        return getOrCreateType(
×
1142
            mdb,
1143
            "uint32",
1144
            () -> new IntegerParameterType.Builder().setSizeInBits(32).setSigned(false));
×
1145
      case UINT64:
1146
        return getOrCreateType(
1✔
1147
            mdb,
1148
            "uint64",
1149
            () -> new IntegerParameterType.Builder().setSizeInBits(64).setSigned(false));
×
1150
      case TIMESTAMP:
1151
        return getOrCreateType(mdb, "time", () -> new AbsoluteTimeParameterType.Builder());
×
1152
      case ENUMERATED:
1153
        return getOrCreateType(mdb, "enum", () -> new EnumeratedParameterType.Builder());
×
1154
    }
1155

1156
    return pType;
1✔
1157
  }
1158

1159
  public static ParameterValue getNewPv(Parameter parameter, long time) {
1160
    ParameterValue pv = new ParameterValue(parameter);
1✔
1161
    pv.setAcquisitionTime(time);
1✔
1162
    pv.setGenerationTime(time);
1✔
1163
    return pv;
1✔
1164
  }
1165

1166
  public static ParameterValue getPV(Parameter parameter, long time, String v) {
1167
    ParameterValue pv = getNewPv(parameter, time);
1✔
1168
    pv.setEngValue(ValueUtility.getStringValue(v));
1✔
1169
    return pv;
1✔
1170
  }
1171

1172
  public static ParameterValue getPV(Parameter parameter, long time, double v) {
1173
    ParameterValue pv = getNewPv(parameter, time);
×
1174
    pv.setEngValue(ValueUtility.getDoubleValue(v));
×
1175
    return pv;
×
1176
  }
1177

1178
  public static ParameterValue getPV(Parameter parameter, long time, float v) {
1179
    ParameterValue pv = getNewPv(parameter, time);
×
1180
    pv.setEngValue(ValueUtility.getFloatValue(v));
×
1181
    return pv;
×
1182
  }
1183

1184
  public static ParameterValue getPV(Parameter parameter, long time, boolean v) {
1185
    ParameterValue pv = getNewPv(parameter, time);
×
1186
    pv.setEngValue(ValueUtility.getBooleanValue(v));
×
1187
    return pv;
×
1188
  }
1189

1190
  public static ParameterValue getPV(Parameter parameter, long time, long v) {
1191
    ParameterValue pv = getNewPv(parameter, time);
×
1192
    pv.setEngValue(ValueUtility.getSint64Value(v));
×
1193
    return pv;
×
1194
  }
1195

1196
  public static ParameterValue getUnsignedIntPV(Parameter parameter, long time, int v) {
1197
    ParameterValue pv = getNewPv(parameter, time);
×
1198
    pv.setEngValue(ValueUtility.getUint64Value(v));
×
1199
    return pv;
×
1200
  }
1201

1202
  //
1203
  //  public static <T extends Enum<T>> ParameterValue getPV(Parameter parameter, long time, T v) {
1204
  //    ParameterValue pv = getNewPv(parameter, time);
1205
  //    pv.setEngValue(ValueUtility.getEnumeratedValue(v.ordinal(), v.name()));
1206
  //    return pv;
1207
  //  }
1208
  //
1209
  //  public static ParameterValue getPV(Parameter parameter, long time, Value v) {
1210
  //    ParameterValue pv = getNewPv(parameter, time);
1211
  //    pv.setEngValue(v);
1212
  //    return pv;
1213
  //  }
1214

1215
  @Override
1216
  public Status getLinkStatus() {
1217
    return linkStatus;
1✔
1218
  }
1219

1220
  @Override
1221
  public boolean isDisabled() {
1222
    // TODO Auto-generated method stub
1223
    return linkStatus == Status.DISABLED;
1✔
1224
  }
1225

1226
  @Override
1227
  public long getDataInCount() {
1228
    // TODO Auto-generated method stub
1229
    return inCount.get();
1✔
1230
  }
1231

1232
  @Override
1233
  public long getDataOutCount() {
1234
    // TODO Auto-generated method stub
1235
    return 0;
1✔
1236
  }
1237

1238
  @Override
1239
  public void resetCounters() {
1240
    // TODO Auto-generated method stub
1241
    inCount.set(0);
1✔
1242
  }
1✔
1243

1244
  private OpcUaClient configureClient() throws Exception {
1245
    Path securityTempDir = Paths.get(System.getProperty("java.io.tmpdir"), "client", "security");
1✔
1246
    Files.createDirectories(securityTempDir);
1✔
1247
    if (!Files.exists(securityTempDir)) {
1✔
1248
      throw new Exception("unable to create security dir: " + securityTempDir);
×
1249
    }
1250

1251
    File pkiDir = securityTempDir.resolve("pki").toFile();
1✔
1252

1253
    LoggerFactory.getLogger(getClass()).info("security dir: {}", securityTempDir.toAbsolutePath());
1✔
1254
    LoggerFactory.getLogger(getClass()).info("security pki dir: {}", pkiDir.getAbsolutePath());
1✔
1255

1256
    //    trustListManager = new DefaultTrustListManager(pkiDir);
1257
    List<EndpointDescription> endpoints = DiscoveryClient.getEndpoints(discoverURL).get();
1✔
1258

1259
    //    FIXME:At the moment, we do not support certificates...
1260
    EndpointDescription selectedEndpoint = null;
1✔
1261
    for (var endpoint : endpoints) {
1✔
1262
      switch (endpoint.getSecurityMode()) {
1✔
1263
        case Invalid:
1264
          //                        FIXME:Add log message
1265
          break;
×
1266
        case None:
1267
          //                        FIXME:Add log message
1268
          selectedEndpoint = endpoint;
1✔
1269
          break;
1✔
1270
          //                        FIXME:Add log message
1271
        case Sign:
1272
          break;
×
1273
        case SignAndEncrypt:
1274
          //                        FIXME:Add log message
1275
          break;
×
1276
        default:
1277
          break;
1278
      }
1279

1280
      if (selectedEndpoint != null) {
1✔
1281
        break;
1✔
1282
      }
1283
    }
×
1284

1285
    if (selectedEndpoint == null) {
1✔
1286
      throw new Exception("No viable endpoint found from list:" + endpoints);
×
1287
    }
1288

1289
    OpcUaClientConfig builder = OpcUaClientConfig.builder().setEndpoint(selectedEndpoint).build();
1✔
1290

1291
    return OpcUaClient.create(builder);
1✔
1292
  }
1293

1294
  private void browseNodeWithReferences(String indent, OpcUaClient client, NodeId browseRoot) {
1295
    BrowseDescription browse =
1✔
1296
        new BrowseDescription(
1297
            browseRoot,
1298
            BrowseDirection.Forward,
1299
            Identifiers.References,
1300
            true,
1✔
1301
            uint(NodeClass.Object.getValue() | NodeClass.Variable.getValue()),
1✔
1302
            uint(BrowseResultMask.All.getValue()));
1✔
1303

1304
    try {
1305

1306
      BrowseResult browseResult = client.browse(browse).get();
1✔
1307

1308
      List<ReferenceDescription> references = toList(browseResult.getReferences());
1✔
1309

1310
      if (references.isEmpty()) {
1✔
1311
        //              FIXME:Add log here
1312

1313
        return;
1✔
1314
      }
1315

1316
      for (ReferenceDescription rd : references) {
1✔
1317
        Object desc = null;
1✔
1318
        Object value = null;
1✔
1319
        UaNode node = null;
1✔
1320
        try {
1321

1322
          node =
1✔
1323
              client
1324
                  .getAddressSpace()
1✔
1325
                  .getNode(rd.getNodeId().toNodeId(client.getNamespaceTable()).get());
1✔
1326
          DataValue attr = node.readAttribute(AttributeId.Description);
1✔
1327
          desc = attr.getValue().getValue();
1✔
1328

1329
          attr = node.readAttribute(AttributeId.Value);
1✔
1330

1331
          value = attr.getValue();
1✔
1332

1333
        } catch (UaException e) {
×
1334
          // TODO Auto-generated catch block
1335
          e.printStackTrace();
×
1336
        }
1✔
1337

1338
        if (node != null) {
1✔
1339
          addOPCUAPV(client, node);
1✔
1340

1341
          log.debug(
1✔
1342
              "{} Node={}, Desc={}, Value={}", indent, rd.getBrowseName().getName(), desc, value);
1✔
1343

1344
          if (rd.getIsForward()) {}
1✔
1345

1346
          // recursively browse to children
1347
          rd.getNodeId()
1✔
1348
              .toNodeId(client.getNamespaceTable())
1✔
1349
              .ifPresent(nodeId -> browseNodeWithReferences(indent + "  ", client, nodeId));
1✔
1350
        }
1351
      }
1✔
1352

1353
    } catch (InterruptedException e1) {
×
1354
      // TODO Auto-generated catch block
1355
      e1.printStackTrace();
×
1356
    } catch (ExecutionException e1) {
×
1357
      // TODO Auto-generated catch block
1358
      e1.printStackTrace();
×
1359
    }
1✔
1360
  }
1✔
1361

1362
  private void addOPCUAPV(OpcUaClient client, UaNode node) {
1363
    if (node.getBrowseName()
1✔
1364
        .getName()
1✔
1365
        .contains(Character.toString(NameDescription.PATH_SEPARATOR))) {
1✔
1366
      internalLogger.info(
1✔
1367
          "{} ignored since it contains a {} character",
1368
          node.getBrowseName().getName(),
1✔
1369
          Character.toString(NameDescription.PATH_SEPARATOR));
1✔
1370

1371
    } else {
1372

1373
      /**
1374
       * NOTE:For now we'll just flatten all the attributes instead of using an aggregate type for
1375
       * attributes
1376
       */
1377
      for (AttributeId attr : AttributeId.values()) {
1✔
1378

1379
        ParameterType ptype = OPCUAAttrTypeToParamType(attr, node);
1✔
1380

1381
        String opcuaTranslatedQName = translateNodeToParamQName(client, node, attr);
1✔
1382
        Parameter p = VariableParam.getForFullyQualifiedName(opcuaTranslatedQName);
1✔
1383

1384
        p.setParameterType(ptype);
1✔
1385

1386
        if (mdb.getParameter(p.getQualifiedName()) == null) {
1✔
1387
          log.debug("Adding OPCUA object as parameter to mdb:{}", p.getQualifiedName());
1✔
1388
          mdb.addParameter(p, true);
1✔
1389

1390
          nodeIDToParamsMap.put(new NodeIDAttrPair(node.getNodeId(), attr), (VariableParam) p);
1✔
1391
        }
1392
      }
1393
    }
1394
  }
1✔
1395

1396
  private String translateNodeToParamQName(OpcUaClient client, UaNode node, AttributeId attr) {
1397
    LocalizedText localizedDisplayName = null;
1✔
1398
    try {
1399

1400
      localizedDisplayName =
1✔
1401
          (LocalizedText) (node.readAttribute(AttributeId.DisplayName).getValue().getValue());
1✔
1402
    } catch (UaException e) {
×
1403
      // TODO Auto-generated catch block
1404
      e.printStackTrace();
×
1405
    }
1✔
1406
    String opcuaTranslatedQName =
1✔
1407
        qualifiedName(
1✔
1408
            parametersNamespace
1409
                + NameDescription.PATH_SEPARATOR
1410
                + node.getNodeId().toParseableString().replace(";", "-")
1✔
1411
                + NameDescription.PATH_SEPARATOR
1412
                + localizedDisplayName.getText(),
1✔
1413
            attr.toString());
1✔
1414

1415
    return opcuaTranslatedQName;
1✔
1416
  }
1417

1418
  /**
1419
   * Same as translateNodeToParamQName but uses display name from the node instead of the
1420
   *
1421
   * @param client
1422
   * @param rd
1423
   * @param attr
1424
   * @return
1425
   */
1426
  //  private String translateNodeDisplayNameToParamQName(
1427
  //      OpcUaClient client, ReferenceDescription rd, AttributeId attr) {
1428
  //    String opcuaTranslatedQName =
1429
  //        qualifiedName(
1430
  //            parametersNamespace
1431
  //                + NameDescription.PATH_SEPARATOR
1432
  //                + rd.getNodeId()
1433
  //                    .toNodeId(client.getNamespaceTable())
1434
  //                    .get()
1435
  //                    .toParseableString()
1436
  //                    .replace(";", "-"),
1437
  //            attr.toString());
1438
  //
1439
  //    UaNode node = null;
1440
  //    try {
1441
  //      node =
1442
  //          client
1443
  //              .getAddressSpace()
1444
  //              .getNode(rd.getNodeId().toNodeId(client.getNamespaceTable()).get());
1445
  //    } catch (UaException e) {
1446
  //      // TODO Auto-generated catch block
1447
  //      e.printStackTrace();
1448
  //    }
1449
  //
1450
  //    String displayName = null;
1451
  //    LocalizedText localizedDisplayName = null;
1452
  //    try {
1453
  //      displayName =
1454
  // node.readAttribute(AttributeId.DisplayName).getValue().getValue().toString();
1455
  //
1456
  //      localizedDisplayName =
1457
  //          (LocalizedText) (node.readAttribute(AttributeId.DisplayName).getValue().getValue());
1458
  //    } catch (UaException e) {
1459
  //      // TODO Auto-generated catch block
1460
  //      e.printStackTrace();
1461
  //    }
1462
  //
1463
  //    //            String opcuaTranslatedQName =
1464
  //    //                qualifiedName(
1465
  //    //                    parametersNamespace + NameDescription.PATH_SEPARATOR +
1466
  //    // localizedDisplayName.getText(),
1467
  //    //                    attr.toString());
1468
  //    return opcuaTranslatedQName;
1469
  //  }
1470

1471
  /**
1472
   * @param indent
1473
   * @param client
1474
   * @param browseRoot
1475
   * @param nodePath in the format of "0:Root,0:Objects,2:HelloWorld,2:MyObject,2:Bar"
1476
   */
1477
  private void browsePath(String indent, OpcUaClient client, NodeId startingNode, String nodePath) {
1478
    internalLogger.info("Browsing at " + startingNode);
1✔
1479
    ArrayList<String> rPathTokens = new ArrayList<String>();
1✔
1480
    ArrayList<RelativePathElement> relaitivePathElements = new ArrayList<RelativePathElement>();
1✔
1481

1482
    for (var pathToken : nodePath.split(",")) {
1✔
1483
      rPathTokens.add(nodePath);
1✔
1484

1485
      int namespaceIndex = 0;
1✔
1486

1487
      String namespaceName = "";
1✔
1488

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

1491
      namespaceName = pathToken.split(":")[1];
1✔
1492

1493
      relaitivePathElements.add(
1✔
1494
          new RelativePathElement(
1495
              Identifiers.HierarchicalReferences,
1496
              false,
1✔
1497
              true,
1✔
1498
              new QualifiedName(namespaceIndex, namespaceName)));
1499
    }
1500

1501
    ArrayList<BrowsePath> list = new ArrayList<BrowsePath>();
1✔
1502

1503
    RelativePathElement[] elements = new RelativePathElement[relaitivePathElements.size()];
1✔
1504

1505
    relaitivePathElements.toArray(elements);
1✔
1506

1507
    list.add(new BrowsePath(startingNode, new RelativePath(elements)));
1✔
1508

1509
    TranslateBrowsePathsToNodeIdsResponse response = null;
1✔
1510
    try {
1511
      response = client.translateBrowsePaths(list).get();
1✔
1512
    } catch (InterruptedException e) {
×
1513
      // TODO Auto-generated catch block
1514
      e.printStackTrace();
×
1515
    } catch (ExecutionException e) {
×
1516
      // TODO Auto-generated catch block
1517
      e.printStackTrace();
×
1518
    }
1✔
1519

1520
    BrowsePathResult result = Arrays.asList(response.getResults()).get(0);
1✔
1521
    StatusCode statusCode = result.getStatusCode();
1✔
1522

1523
    if (statusCode.isBad()) {
1✔
1524
      log.warn("Bad status code:" + statusCode);
×
1525
      return;
×
1526
    } else if (statusCode.isUncertain()) {
1✔
1527
      log.warn("Uncertain status code:" + statusCode);
×
1528
      return;
×
1529
    }
1530

1531
    try {
1532
      UaNode node =
1✔
1533
          client
1534
              .getAddressSpace()
1✔
1535
              .getNode(
1✔
1536
                  result.getTargets()[0].getTargetId().toNodeId(client.getNamespaceTable()).get());
1✔
1537

1538
      addOPCUAPV(client, node);
1✔
1539
    } catch (UaException e) {
×
1540
      // TODO Auto-generated catch block
1541
      e.printStackTrace();
×
1542
    }
1✔
1543
  }
1✔
1544

1545
  private void createOPCUASubscriptions() {
1546
    createDataChangeListener();
1✔
1547
    Set<NodeId> nodeSet = new HashSet<NodeId>();
1✔
1548
    /**
1549
     * FIXME:This is super inefficient... The reason we collect these nodeIDs in a set is because
1550
     * otherwise we will have redundant subscription(s) since there is more than 1 attribute per
1551
     * nodeID given how nodeIDToParamsMap is designed
1552
     */
1553
    for (NodeIDAttrPair pair : nodeIDToParamsMap.keySet()) {
1✔
1554
      nodeSet.add(pair.nodeID);
1✔
1555
    }
1✔
1556
    for (NodeId id : nodeSet) {
1✔
1557
      Variant nodeClass = null;
1✔
1558
      try {
1559
        UaNode node = client.getAddressSpace().getNode(id);
1✔
1560

1561
        nodeClass = node.readAttribute(AttributeId.NodeClass).getValue();
1✔
1562

1563
      } catch (UaException e) {
×
1564
        // TODO Auto-generated catch block
1565
        e.printStackTrace();
×
1566
      }
1✔
1567
      if (nodeClass != null) {
1✔
1568
        try {
1569
          switch (NodeClass.from((int) nodeClass.getValue())) {
1✔
1570
              // As per the spec, the only thing we can subscribe to is Variables
1571
            case Variable:
1572
              ManagedDataItem dataItem = opcuaSubscription.createDataItem(id);
1✔
1573
              OPCUAActiveSubs.addAndGet(1);
1✔
1574
              log.debug("Status code for dataItem:{}", dataItem.getStatusCode());
1✔
1575
              break;
1576
          }
1577
        } catch (UaException e) {
×
1578
          // TODO Auto-generated catch block
1579
          e.printStackTrace();
×
1580
        }
1✔
1581
      }
1582
    }
1✔
1583
  }
1✔
1584

1585
  public void connectToOPCUAServer(OpcUaClient client) throws Exception {
1586
    internalLogger.info("Connecting to OPCUA server...");
1✔
1587
    client.connect().get();
1✔
1588

1589
    addAction(startAction);
1✔
1590
    startAction.setEnabled(true);
1✔
1591
  }
1✔
1592

1593
  /**
1594
   * Browses the tree on the OPCUA server and maps them to YAMCS Parameters.
1595
   *
1596
   * @param client
1597
   * @param future
1598
   */
1599
  private void browseOPCUATree(OpcUaClient client, CompletableFuture<OpcUaClient> future) {
1600
    // start browsing at root folder
1601
    internalLogger.info("Browsing OPCUA...");
1✔
1602
    for (var p : relativeNodePaths) {
1✔
1603

1604
      int namespaceIndex = (int) p.rootNodeID.get("namespaceIndex");
1✔
1605

1606
      String identifier = (String) p.rootNodeID.get("identifier");
1✔
1607
      IdType identifierType = IdType.valueOf((String) p.rootNodeID.get("identifierType"));
1✔
1608

1609
      browsePath(
1✔
1610
          endpointURL, client, getNewNodeID(identifierType, namespaceIndex, identifier), p.path);
1✔
1611
    }
1✔
1612

1613
    NodeId nodeID = null;
1✔
1614
    nodeID = getNewNodeID(rootIdentifierType, rootNamespaceIndex, rootIdentifier);
1✔
1615

1616
    //  FIXME:Make root default when no namespaceIndex/identifier pair is specified
1617
    browseNodeWithReferences("", client, nodeID);
1✔
1618

1619
    future.complete(client);
1✔
1620
  }
1✔
1621

1622
  private NodeId getNewNodeID(IdType rootIdentifierType, int NamespaceIndex, String Identifier) {
1623
    NodeId nodeID = null;
1✔
1624
    switch (rootIdentifierType) {
1✔
1625
      case Guid:
1626
        //                FIXME
1627
        break;
×
1628
      case Numeric:
1629
        nodeID = new NodeId(NamespaceIndex, Integer.parseInt(Identifier));
1✔
1630
        //        browseNodes(endpointURL, client, nodeID);
1631
        break;
1✔
1632
      case Opaque:
1633
        //                FIXME
1634
        break;
×
1635
      case String:
1636
        nodeID = new NodeId(NamespaceIndex, Identifier);
×
1637
        //        browseNodes(endpointURL, client, nodeID);
1638
        break;
×
1639
      default:
1640
        break;
1641
    }
1642
    return nodeID;
1✔
1643
  }
1644

1645
  private void createDataChangeListener() {
1646
    try {
1647
      opcuaSubscription = ManagedSubscription.create(client, publishInterval);
1✔
1648
    } catch (UaException e) {
×
1649
      // TODO Auto-generated catch block
1650
      e.printStackTrace();
×
1651
    }
1✔
1652
    opcuaSubscription.addDataChangeListener(
1✔
1653
        (items, values) -> {
1654
          for (int i = 0; i < items.size(); i++) {
1✔
1655
            NodeIDAttrPair nodeAttrKey =
1✔
1656
                new NodeIDAttrPair(items.get(i).getNodeId(), AttributeId.Value);
1✔
1657
            log.debug(
1✔
1658
                "subscription value received: item={}, value={}",
1659
                items.get(i).getNodeId(),
1✔
1660
                values.get(i).getValue());
1✔
1661

1662
            log.debug(
1✔
1663
                "Pushing new PV for param name {} which is mapped to NodeID {}",
1664
                nodeIDToParamsMap.get(nodeAttrKey),
1✔
1665
                items.get(i).getNodeId());
1✔
1666

1667
            TupleDefinition tdef = gftdef.copy();
1✔
1668
            List<Object> cols = new ArrayList<>(4 + 1);
1✔
1669
            //            FIXME: Add leap seconds.... as config or get it from YAMCS API.
1670
            long gentime =
1✔
1671
                values
1672
                    .get(i)
1✔
1673
                    .getSourceTime()
1✔
1674
                    .getJavaInstant()
1✔
1675
                    .plus(37, ChronoUnit.SECONDS)
1✔
1676
                    .toEpochMilli();
1✔
1677
            cols.add(gentime);
1✔
1678
            cols.add(parametersNamespace);
1✔
1679
            cols.add(0);
1✔
1680
            cols.add(gentime);
1✔
1681

1682
            /**
1683
             * TODO:Not sure if this is the best way to do this since the aggregate values will be
1684
             * partially updated. Another potential approach might be to decouple the live OPCUA
1685
             * data(subscriptions) via namespaces. For example; have a "special" namespace called
1686
             * "subscriptions" that ONLY gets updated with items. And maybe another namespace for
1687
             * static data...maybe.
1688
             *
1689
             * <p>Another option is to flatten everything and have no aggregate types at all. That
1690
             * approach might even simplify the code quite a bit...
1691
             *
1692
             * <p>Another question worth answering before moving forward is to find whether or not
1693
             * it is concrete in the OPCUA protocol what data can change in real time and which data
1694
             * is "static". Not sure if there is any "static" data given that clients have the
1695
             * ability of writing to values... might be worth a test.
1696
             */
1697

1698
            // FIMXE:Properly add aggregatevalues instead of getPV flat values
1699
            //            AggregateValue v = new
1700
            // AggregateValue(fileStoreAggrType.getMemberNames());
1701
            //            v.setMemberValue("total", ValueUtility.getSint64Value(ts / 1024));
1702
            //            v.setMemberValue("available", ValueUtility.getSint64Value(av / 1024));
1703
            //            v.setMemberValue("percentageUse", ValueUtility.getFloatValue(perc));
1704
            //
1705
            //            ParameterValue pv = new ParameterValue(storep.param);
1706
            //            pv.setGenerationTime(gentime);
1707
            //            pv.setAcquisitionTime(gentime);
1708
            //            pv.setAcquisitionStatus(AcquisitionStatus.ACQUIRED);
1709
            //            pv.setEngValue(v);
1710

1711
            log.debug(
1✔
1712
                "Data({}) chnage triggered for {}",
1713
                values.get(i).getValue(),
1✔
1714
                nodeIDToParamsMap.get(nodeAttrKey));
1✔
1715

1716
            if (nodeIDToParamsMap.get(nodeAttrKey) == null) {
1✔
1717
              log.debug("No parameter mapping found for {}", nodeAttrKey.nodeID);
×
1718
              continue;
×
1719
            } else {
1720
              log.debug(
1✔
1721
                  String.format(
1✔
1722
                      "parameter mapping found for {} and {}",
1723
                      nodeAttrKey.nodeID,
1724
                      nodeAttrKey.attrID));
1725
            }
1726

1727
            if (values.get(i).getValue() != null) {
1✔
1728
              tdef.addColumn(
1✔
1729
                  nodeIDToParamsMap.get(nodeAttrKey).getQualifiedName(), DataType.PARAMETER_VALUE);
1✔
1730

1731
              cols.add(
1✔
1732
                  getPV(
1✔
1733
                      nodeIDToParamsMap.get(nodeAttrKey),
1✔
1734
                      gentime,
1735
                      values.get(i).getValue().toString()));
1✔
1736

1737
              pushTuple(tdef, cols);
1✔
1738

1739
              inCount.getAndAdd(1);
1✔
1740
            } else {
1741
              // TODO:Add some type emptyValue count for OPS.
1742
              log.warn(
×
1743
                  "Data chnage triggered for {}, but it empty. This should not happen.",
1744
                  nodeIDToParamsMap.get(nodeAttrKey).getQualifiedName());
×
1745
            }
1746
          }
1747
        });
1✔
1748
  }
1✔
1749

1750
  /**
1751
   * This method is here for future growth in case we find there is a benefit to using aggregate
1752
   * types
1753
   */
1754
  private void createOPCUAAttrAggregateType() {
1755

1756
    AggregateParameterType.Builder opcuaAttrsTypeBuidlder = new AggregateParameterType.Builder();
1✔
1757

1758
    opcuaAttrsType = new AggregateParameterType.Builder().setName("OPCUObjectAttributes").build();
1✔
1759

1760
    opcuaAttrsTypeBuidlder.setName("OPCUObjectAttributes");
1✔
1761
    for (AttributeId attr : AttributeId.values()) {
1✔
1762
      opcuaAttrsTypeBuidlder.addMember(new Member(attr.toString(), getBasicType(mdb, Type.STRING)));
1✔
1763
    }
1764

1765
    opcuaAttrsType = opcuaAttrsTypeBuidlder.build();
1✔
1766
    ((NameDescription) opcuaAttrsType)
1✔
1767
        .setQualifiedName(qualifiedName(parametersNamespace, opcuaAttrsType.getName()));
1✔
1768
  }
1✔
1769

1770
  /**
1771
   * This method is here for future growth in case we find there is a benefit to using aggregate
1772
   * types
1773
   */
1774
  private void createOPCUANodeIdTypes() {
1775
    AggregateParameterType.Builder opcuaAttrsNumericNodeIdBuidlder =
1✔
1776
        new AggregateParameterType.Builder();
1777

1778
    opcuaAttrsNumericNodeIdBuidlder.setName("OPCUA_Numeric_NodeId");
1✔
1779
    opcuaAttrsNumericNodeIdBuidlder.addMember(
1✔
1780
        new Member("namespaceIndex", getBasicType(mdb, Type.UINT64)));
1✔
1781
    opcuaAttrsNumericNodeIdBuidlder.addMember(
1✔
1782
        new Member("identifier", getBasicType(mdb, Type.UINT64)));
1✔
1783

1784
    opcuaNodeIdNumericType = opcuaAttrsNumericNodeIdBuidlder.build();
1✔
1785
    ((NameDescription) opcuaNodeIdNumericType)
1✔
1786
        .setQualifiedName(qualifiedName(parametersNamespace, opcuaNodeIdNumericType.getName()));
1✔
1787

1788
    AggregateParameterType.Builder opcuaAttrsTypeStringBuidlder =
1✔
1789
        new AggregateParameterType.Builder();
1790

1791
    opcuaAttrsTypeStringBuidlder.setName("OPCUA_String_NodeId");
1✔
1792
    opcuaAttrsTypeStringBuidlder.addMember(
1✔
1793
        new Member("namespaceIndex", getBasicType(mdb, Type.UINT64)));
1✔
1794
    opcuaAttrsTypeStringBuidlder.addMember(
1✔
1795
        new Member("identifier", getBasicType(mdb, Type.STRING)));
1✔
1796

1797
    opcuaNodeIdStringType = opcuaAttrsTypeStringBuidlder.build();
1✔
1798
    ((NameDescription) opcuaNodeIdStringType)
1✔
1799
        .setQualifiedName(qualifiedName(parametersNamespace, opcuaNodeIdStringType.getName()));
1✔
1800

1801
    mdb.addParameterType(opcuaNodeIdNumericType, true);
1✔
1802
    mdb.addParameterType(opcuaNodeIdStringType, true);
1✔
1803
  }
1✔
1804

1805
  private ParameterType OPCUAAttrTypeToParamType(AttributeId attr, UaNode node) {
1806
    ParameterType pType = null;
1✔
1807

1808
    switch (attr) {
1✔
1809
      case AccessLevel:
1810
        pType = getBasicType(mdb, Type.STRING);
1✔
1811
        break;
1✔
1812
      case ArrayDimensions:
1813
        pType = getBasicType(mdb, Type.STRING);
1✔
1814
        break;
1✔
1815
      case BrowseName:
1816
        pType = getBasicType(mdb, Type.STRING);
1✔
1817
        break;
1✔
1818
      case ContainsNoLoops:
1819
        pType = getBasicType(mdb, Type.STRING);
1✔
1820
        break;
1✔
1821
      case DataType:
1822
        pType = getBasicType(mdb, Type.STRING);
1✔
1823
        break;
1✔
1824
      case Description:
1825
        pType = getBasicType(mdb, Type.STRING);
1✔
1826
        break;
1✔
1827
      case DisplayName:
1828
        pType = getBasicType(mdb, Type.STRING);
1✔
1829
        break;
1✔
1830
      case EventNotifier:
1831
        pType = getBasicType(mdb, Type.STRING);
1✔
1832
        break;
1✔
1833
      case Executable:
1834
        pType = getBasicType(mdb, Type.STRING);
1✔
1835
        break;
1✔
1836
      case Historizing:
1837
        pType = getBasicType(mdb, Type.STRING);
1✔
1838
        break;
1✔
1839
      case InverseName:
1840
        pType = getBasicType(mdb, Type.STRING);
1✔
1841
        break;
1✔
1842
      case IsAbstract:
1843
        pType = getBasicType(mdb, Type.STRING);
1✔
1844
        break;
1✔
1845
      case MinimumSamplingInterval:
1846
        pType = getBasicType(mdb, Type.STRING);
1✔
1847
        break;
1✔
1848
      case NodeClass:
1849
        pType = getBasicType(mdb, Type.STRING);
1✔
1850
        break;
1✔
1851
      case NodeId:
1852
        pType = getBasicType(mdb, Type.STRING);
1✔
1853
        break;
1✔
1854
      case Symmetric:
1855
        pType = getBasicType(mdb, Type.STRING);
1✔
1856
        break;
1✔
1857
      case UserAccessLevel:
1858
        pType = getBasicType(mdb, Type.STRING);
1✔
1859
        break;
1✔
1860
      case UserExecutable:
1861
        pType = getBasicType(mdb, Type.STRING);
1✔
1862
        break;
1✔
1863
      case UserWriteMask:
1864
        pType = getBasicType(mdb, Type.STRING);
1✔
1865
        break;
1✔
1866
      case Value:
1867
        try {
1868

1869
          var value = node.readAttribute(attr).getValue();
1✔
1870
          client.readValue(0, TimestampsToReturn.Both, node.getNodeId());
1✔
1871
          if (value.isNotNull()) {
1✔
1872

1873
            Object valueObject = value.getValue().getClass();
1✔
1874

1875
            if (valueObject instanceof Short) {
1✔
1876
              pType = getBasicType(mdb, Type.SINT32);
×
1877
            }
1878

1879
            if (valueObject instanceof Integer) {
1✔
1880
              pType = getBasicType(mdb, Type.SINT32);
×
1881
            }
1882

1883
            if (valueObject instanceof Long) {
1✔
1884
              pType = getBasicType(mdb, Type.SINT64);
×
1885
            } else if (valueObject instanceof Double) {
1✔
1886
              pType = getBasicType(mdb, Type.DOUBLE);
×
1887
            } else if (valueObject instanceof Float) {
1✔
1888
              pType = getBasicType(mdb, Type.FLOAT);
×
1889
            } else if (valueObject instanceof Character) {
1✔
1890
              pType = getBasicType(mdb, Type.STRING);
×
1891
            } else if (valueObject instanceof String) {
1✔
1892
              pType = getBasicType(mdb, Type.STRING);
×
1893

1894
            } else if (valueObject instanceof Boolean) {
1✔
1895
              pType = getBasicType(mdb, Type.BOOLEAN);
×
1896
            } else {
1897
              pType = getBasicType(mdb, Type.STRING);
1✔
1898
            }
1899
          } else {
1✔
1900
            pType = getBasicType(mdb, Type.STRING);
1✔
1901
          }
1902

1903
        } catch (UaException e) {
×
1904
          // TODO Auto-generated catch block
1905
          e.printStackTrace();
×
1906
          //                        FIXME:Add log message
1907
        }
1✔
1908
        break;
×
1909
      case ValueRank:
1910
        pType = getBasicType(mdb, Type.STRING);
1✔
1911
        break;
1✔
1912
      case WriteMask:
1913
        pType = getBasicType(mdb, Type.STRING);
1✔
1914
        break;
1✔
1915
      default:
1916
        break;
1917
    }
1918

1919
    return pType;
1✔
1920
  }
1921

1922
  private void subscribeToEvents(OpcUaClient client)
1923
      throws InterruptedException, ExecutionException {
1924
    // create a subscription and a monitored item
1925
    UaSubscription subscription = client.getSubscriptionManager().createSubscription(1000.0).get();
1✔
1926

1927
    ReadValueId readValueId =
1✔
1928
        new ReadValueId(
1929
            Identifiers.Server, AttributeId.EventNotifier.uid(), null, QualifiedName.NULL_VALUE);
1✔
1930

1931
    // client handle must be unique per item
1932
    UInteger clientHandle = uint(clientHandles.getAndIncrement());
1✔
1933

1934
    EventFilter eventFilter =
1✔
1935
        new EventFilter(
1936
            new SimpleAttributeOperand[] {
1937
              new SimpleAttributeOperand(
1938
                  Identifiers.BaseEventType,
1939
                  new QualifiedName[] {new QualifiedName(0, "EventId")},
1940
                  AttributeId.Value.uid(),
1✔
1941
                  null),
1942
              new SimpleAttributeOperand(
1943
                  Identifiers.BaseEventType,
1944
                  new QualifiedName[] {new QualifiedName(0, "EventType")},
1945
                  AttributeId.Value.uid(),
1✔
1946
                  null),
1947
              new SimpleAttributeOperand(
1948
                  Identifiers.BaseEventType,
1949
                  new QualifiedName[] {new QualifiedName(0, "Severity")},
1950
                  AttributeId.Value.uid(),
1✔
1951
                  null),
1952
              new SimpleAttributeOperand(
1953
                  Identifiers.BaseEventType,
1954
                  new QualifiedName[] {new QualifiedName(0, "Time")},
1955
                  AttributeId.Value.uid(),
1✔
1956
                  null),
1957
              new SimpleAttributeOperand(
1958
                  Identifiers.BaseEventType,
1959
                  new QualifiedName[] {new QualifiedName(0, "Message")},
1960
                  AttributeId.Value.uid(),
1✔
1961
                  null)
1962
            },
1963
            new ContentFilter(null));
1964

1965
    MonitoringParameters parameters =
1✔
1966
        new MonitoringParameters(
1967
            clientHandle,
1968
            0.0,
1✔
1969
            ExtensionObject.encode(client.getStaticSerializationContext(), eventFilter),
1✔
1970
            uint(10),
1✔
1971
            true);
1✔
1972

1973
    MonitoredItemCreateRequest request =
1✔
1974
        new MonitoredItemCreateRequest(readValueId, MonitoringMode.Reporting, parameters);
1975

1976
    List<UaMonitoredItem> items =
1✔
1977
        subscription.createMonitoredItems(TimestampsToReturn.Both, newArrayList(request)).get();
1✔
1978

1979
    // do something with the value updates
1980
    UaMonitoredItem monitoredItem = items.get(0);
1✔
1981

1982
    final AtomicInteger eventCount = new AtomicInteger(0);
1✔
1983

1984
    monitoredItem.setEventConsumer(
1✔
1985
        (item, vs) -> {
1986
          internalLogger.info("Event Received from {}", item.getReadValueId().getNodeId());
1✔
1987

1988
          //          System.out.println("Event Received from" + item.getReadValueId().getNodeId());
1989

1990
          StringBuilder eventText = new StringBuilder();
1✔
1991

1992
          ByteString eventId;
1993
          NodeId eventType;
1994
          UShort eventSeverity;
1995
          DateTime eventTime;
1996
          LocalizedText eventMessage;
1997

1998
          for (int i = 0; i < vs.length; i++) {
1✔
1999
            internalLogger.info("\tvariant[{}]: {}", i, vs[i].getValue());
1✔
2000
          }
2001

2002
          eventId = (ByteString) vs[0].getValue();
1✔
2003
          eventType = (NodeId) vs[1].getValue();
1✔
2004
          eventSeverity = (UShort) vs[2].getValue();
1✔
2005
          eventTime = (DateTime) vs[3].getValue();
1✔
2006
          eventMessage = (LocalizedText) vs[4].getValue();
1✔
2007

2008
          //          FIXME:Map these values to YAMCS API
2009
          eventText.append("eventId:" + eventId);
1✔
2010
          eventText.append("\n");
1✔
2011
          eventText.append("eventType:" + eventType);
1✔
2012
          eventText.append("\n");
1✔
2013
          eventText.append("eventSeverity:" + eventSeverity);
1✔
2014
          eventText.append("\n");
1✔
2015
          eventText.append("eventTime:" + eventTime);
1✔
2016
          eventText.append("\n");
1✔
2017
          eventText.append("eventMessage:" + eventMessage);
1✔
2018
          org.yamcs.yarch.protobuf.Db.Event ev =
2019
              Event.newBuilder()
1✔
2020
                  .setGenerationTime(YamcsServer.getTimeService(yamcsInstance).getMissionTime())
1✔
2021
                  .setGenerationTime(YamcsServer.getTimeService(yamcsInstance).getMissionTime())
1✔
2022
                  .setSource(this.linkName)
1✔
2023
                  .setType(this.linkName)
1✔
2024
                  .setMessage(eventText.toString())
1✔
2025
                  .setSeverity(EventSeverity.INFO)
1✔
2026
                  .build();
1✔
2027
          eventProducer.sendEvent(ev);
1✔
2028
        });
1✔
2029
  }
1✔
2030

2031
  @Override
2032
  public void setupSystemParameters(SystemParametersService sysParamService) {
2033
    super.setupSystemParameters(sysParamService);
1✔
2034
    //          currentOPCUAStatus;
2035
    OPCUAStatusParam =
1✔
2036
        sysParamService.createEnumeratedSystemParameter(
1✔
2037
            linkName + "/OPCUAStatusParam",
2038
            OPCUAStatus.class,
2039
            "The current status of OPCUA client");
2040
    EnumeratedParameterType spLinkStatusType =
1✔
2041
        (EnumeratedParameterType) OPCUAStatusParam.getParameterType();
1✔
2042
    spLinkStatusType
1✔
2043
        .enumValue(OPCUAStatus.OPCUA_INIT_CONFIG.name())
1✔
2044
        .setDescription(
1✔
2045
            "This link is in the configuration stage(Configuring OPCUA parameters such as certificates)");
2046
    spLinkStatusType
1✔
2047
        .enumValue(OPCUAStatus.OPCUA_INIT_TREE.name())
1✔
2048
        .setDescription(
1✔
2049
            "The link is parsing the OPCUA Tree and mapping them to PVs."
2050
                + " Depending on configuration, this can take a while.");
2051
    spLinkStatusType
1✔
2052
        .enumValue(OPCUAStatus.OPCUA_INIT_EVENTS.name())
1✔
2053
        .setDescription("The link is configuring and subscribing to OPCUA events");
1✔
2054
    spLinkStatusType
1✔
2055
        .enumValue(OPCUAStatus.OPCUA_INIT_DATA_SUBSCRIPTION.name())
1✔
2056
        .setDescription(
1✔
2057
            "The link is creating subscriptions for each node that was parsed from the tree"
2058
                + "that has a Value attribute.");
2059
    spLinkStatusType
1✔
2060
        .enumValue(OPCUAStatus.OPCUA_INIT_ALL_DATA_QUERY.name())
1✔
2061
        .setDescription(
1✔
2062
            "The link is querying all attributes of all parsed nodes."
2063
                + "This is can be configured to be done at startup.");
2064
    spLinkStatusType
1✔
2065
        .enumValue(OPCUAStatus.OPCUA_OK.name())
1✔
2066
        .setDescription(
1✔
2067
            "The link is done with all OPCUA initialization. It is in an usable state.");
2068

2069
    OPCUAActiveSubsParam =
1✔
2070
        sysParamService.createSystemParameter(
1✔
2071
            linkName + "/OPCUAActiveSubs",
2072
            Type.UINT64,
2073
            "The total number of active opcua subscriptions");
2074
  }
1✔
2075

2076
  @Override
2077
  public List<ParameterValue> getSystemParameters() {
2078
    long time = getCurrentTime();
1✔
2079

2080
    ArrayList<ParameterValue> list = new ArrayList<>();
1✔
2081

2082
    list.add(
1✔
2083
        org.yamcs.parameter.SystemParametersService.getPV(
1✔
2084
            OPCUAStatusParam, time, currentOPCUAStatus));
2085

2086
    list.add(
1✔
2087
        org.yamcs.parameter.SystemParametersService.getPV(
1✔
2088
            OPCUAActiveSubsParam, time, OPCUAActiveSubs.get()));
1✔
2089
    try {
2090
      super.collectSystemParameters(time, list);
1✔
2091
    } catch (Exception e) {
×
2092
      log.error("Exception caught when collecting link system parameters", e);
×
2093
    }
1✔
2094
    return list;
1✔
2095
  }
2096
}
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