Replies: 10 comments
-
I don't have time to materialize this from scratch right now, but if somebody else out there is going through the migration and wants to try to document the process I can help out where possible. I've been incrementally updating my large Milo codebase as 1.0 development happened, so all the details of that have already been booted from my main memory. Very very high level and possibly incomplete list of API changes:
|
Beta Was this translation helpful? Give feedback.
-
Sharing notes I've taken on my upgrade journey. Successfully packaged app locally with mvn after these steps, afraid to touch bazel build until I figure a couple more TODOs out. I will update as a figure more things out. (before all else) forked main branch / created new branch Update dependencies / build filesmaven dependency updatesUpdate pom.xmlBefore: <dependency>
<groupId>org.eclipse.milo</groupId>
<artifactId>sdk-client</artifactId>
<version>0.6.16</version>
</dependency>
<dependency>
<groupId>org.eclipse.milo</groupId>
<artifactId>stack-core</artifactId>
<version>0.6.16</version>
</dependency>
After: <dependency>
<groupId>org.eclipse.milo</groupId>
<artifactId>milo-sdk-client</artifactId>
<version>1.0.2</version>
</dependency>
<dependency>
<groupId>org.eclipse.milo</groupId>
<artifactId>milo-stack-core</artifactId>
<version>1.0.2</version>
</dependency> Clean/Refresh IDE Workspace(VSCode)
bazel dependency updates
# before
"org.eclipse.milo:sdk-client:0.6.16",
"org.eclipse.milo:stack-core:0.6.16",
"org.eclipse.milo:stack-client:0.6.16",
# after
"org.eclipse.milo:milo-sdk-client:1.0.2",
"org.eclipse.milo:milo-stack-core:1.0.2",
# NOTE: stack-client appears to have gone away
# TODO: bazel/buildozer will probably howl about additional transitive dependencies
Update milo packages in your codeWith my IDE still closed, I started by running
I also ran rm -rf ~/.m2/repository/org/eclipse/milo/milo/0.6.16
rm -rf ~/.m2/repository/org/eclipse/milo/opc-ua-sdk
rm -rf ~/.m2/repository/org/eclipse/milo/sdk-client
rm -rf ~/.m2/repository/org/eclipse/milo/stack-client
rm -rf ~/.m2/repository/org/eclipse/milo/opc-ua-stack
rm -rf ~/.m2/repository/org/eclipse/milo/sdk-core
rm -rf ~/.m2/repository/org/eclipse/milo/stack-core to intentionally break all references to old milo packages When you reopen your IDE, you'll have loads of linter errors. Don't panic, its time to get to work OpcUaClient calls
// I replaced this call
return client.getSubscriptionManager()
.createSubscription(publishingInterval)
.thenAccept(subscription -> this.uaSubscription = subscription);
// with this:
var subscription = new OpcUaSubscription(client, publishingInterval);
// optionally, add a subscription listener (instanceof SubscriptionListener)
this.uaSubscription = subscription;
return subscription.createAsync()
.toCompletableFuture()
.thenAccept(unit -> Logger.debug("Created new OpcUaSubscription with id: {}",
subscription.getSubscriptionId())
)
.exceptionally(ex -> {
Logger.error("Failed to create OpcUaSubscription: {}", ex.getMessage());
return null;
});
// alternatively use the synchronous call OpcUaSubscription::create
// ... create a List of OpcUaMonitoredItem and attach them to the subscription with OpcUaSubscription::addMonitoredItems
// after you've got these attached to the subscription, call OpcUaSubscription::synchronizeMonitoredItems (throws checked UaException) NOTE: The changes to migrate from TLDR: new workflow for subscribing to nodes:
ValueConsumer -> DataValueListener// find and replace this
import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaMonitoredItem.ValueConsumer;
// ... with this
import org.eclipse.milo.opcua.sdk.client.subscriptions.OpcUaMonitoredItem.DataValueListener;
UaMonitoredItem -> OpcUaMonitoredItem// find and replace this
import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaMonitoredItem;
// ... with this
import org.eclipse.milo.opcua.sdk.client.subscriptions.OpcUaMonitoredItem;
UaSubscription -> OpcUaSubscription// find and replace this
import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaSubscription;
// ... with this
import org.eclipse.milo.opcua.sdk.client.subscriptions.OpcUaSubscription;
UaStructure -> UaStructuredType// find and replace this
import org.eclipse.milo.opcua.stack.core.serialization.UaStructure;
// ... with this
import org.eclipse.milo.opcua.stack.core.types.UaStructuredType;
// find and replace this
import org.eclipse.milo.opcua.stack.core.serialization.codecs.GenericDataTypeCodec;
// ... with this
import org.eclipse.milo.opcua.stack.core.encoding.GenericDataTypeCodec; // find and replace this
import org.eclipse.milo.opcua.stack.core.serialization.SerializationContext;
// ... with this
import org.eclipse.milo.opcua.stack.core.encoding.EncodingContext;
// find and replace these
import org.eclipse.milo.opcua.stack.core.serialization.UaDecoder;
import org.eclipse.milo.opcua.stack.core.serialization.UaEncoder;
// ... with this
import org.eclipse.milo.opcua.stack.core.encoding.UaDecoder;
import org.eclipse.milo.opcua.stack.core.encoding.UaEncoder;
DataTypeTreeSessionInitializer & DataTypeTreeThese classes have gone away, mostly I was using these in debug stacks and unit tests to inspect codec registration failures. Their replacement is unclear. EDIT: see my next comment below |
Beta Was this translation helpful? Give feedback.
-
Nice, thanks. You're probably one of very few people who are using all the stuff related to doing your own codegen. I think most users will have a much easier time.
MonitoredItemCreateRequest is one of the structures defined by OPC UA and used in the CreateMonitoredItems service call. Everything but the client handle should be configurable on the OpcUaMonitoredItem.
See:
It's lazily created for you (deferring to the initializer) when you call |
Beta Was this translation helpful? Give feedback.
-
Additional notes: SessionInitializerArguments for the DynamicDataTypeManagerCodec registration has changed // old syntax:
client.getDynamicDataTypeManager().registerCodec(binaryEncodingId, codec);
// new syntax
client.getDynamicDataTypeManager().registerType(dataTypeId, codec, binaryEncodingId, xmlEncodingId, jsonEncodingId); NOTE: DataTypeTree// find and replace this
import org.eclipse.milo.opcua.sdk.core.DataTypeTree;
// ... with this
import org.eclipse.milo.opcua.sdk.core.typetree.DataTypeTree; Whereas before this was fetched from the DataTypeDictionaryTypeNode// find and replace this
import org.eclipse.milo.opcua.sdk.client.model.nodes.variables.DataTypeDictionaryTypeNode;
// ... with this
import org.eclipse.milo.opcua.sdk.client.model.variables.DataTypeDictionaryTypeNode; IdentityProvider// find and replace these
import org.eclipse.milo.opcua.sdk.client.api.identity.IdentityProvider;
import org.eclipse.milo.opcua.sdk.client.api.identity.X509IdentityProvider;
// ...with these
import org.eclipse.milo.opcua.sdk.client.identity.IdentityProvider;
import org.eclipse.milo.opcua.sdk.client.identity.X509IdentityProvider; IDE NonsenseAfter upgrading, VSCode still wants the old milo packages for some reason. Clearing the java server again didn't do the trick for me, but this did:
After you're done migrating, it is probably best to double-check that you're not mixing old & new milo artifacts. In bash |
Beta Was this translation helpful? Give feedback.
-
ExtensionObject vs DynamicStuctTypeIn milo 0.6.x calling The (roughly) similar call chain in milo 1.x yields After looking at milo examples I realized I needed to change my codec registration to use the |
Beta Was this translation helpful? Give feedback.
-
another small gotcha OpcUaClient::writeValue -> OpcUaClient::writeValuesmilo 0.6.x permitted a null status code in a |
Beta Was this translation helpful? Give feedback.
-
@peterfranciscook can you detail this a little more?
I can't reproduce: @Test
void asyncWriteWithNullStatusCodeDoesNotHang() throws Exception {
NodeId nodeId = testNodeId();
//noinspection DataFlowIssue
DataValue dvWithNullStatus =
DataValue.newValue().setValue(new Variant(123)).setStatus(null).build();
CompletableFuture<List<StatusCode>> future =
client.writeValuesAsync(List.of(nodeId), List.of(dvWithNullStatus));
final CountDownLatch latch = new CountDownLatch(1);
final AtomicBoolean completedExceptionally = new AtomicBoolean(false);
final AtomicBoolean completedSuccessfully = new AtomicBoolean(false);
future.whenComplete(
(sc, ex) -> {
if (ex != null) {
ex.printStackTrace(System.err);
completedExceptionally.set(true);
} else {
System.out.println("Completed: " + sc);
completedSuccessfully.set(true);
}
latch.countDown();
});
assertTrue(latch.await(2, TimeUnit.SECONDS), "expected async writeValues() to complete");
assertTrue(
completedExceptionally.get(), "expected async writeValues() to complete exceptionally");
assertFalse(
completedSuccessfully.get(), "expected async writeValues() to complete exceptionally");
} |
Beta Was this translation helpful? Give feedback.
-
@Test
void smokeTestNodeWrite() {
Service opcUaService = injector.getInstance(Key.get(Service.class, Names.named("OpcUaService")));
try {
// connects to plc, groks tree, performs initialization tasks, subscribes to nodes
opcUaService.startAsync().awaitRunning();
// roughly the same as your test, except I'm using a different DataValue constructor
OpcUaClient client = injector.getInstance(OpcUaClient.class);
NodeId nodeId = new NodeId(4, 22); // kickout pressure / psi / float
Variant variant = new Variant(2345.6f);
DataValue dataValue = new DataValue(variant, null, null);
final CountDownLatch latch = new CountDownLatch(1);
final AtomicBoolean completedExceptionally = new AtomicBoolean(false);
final AtomicBoolean completedSuccessfully = new AtomicBoolean(false);
var future = client.writeValuesAsync(List.of(nodeId), List.of(dataValue))
.whenComplete((sc, ex) -> {
if (ex != null) {
ex.printStackTrace();
Logger.error("Error writing values: {}", ex.getMessage());
} else {
Logger.debug("Completed: " + sc);
}
latch.countDown();
});
assertTrue(latch.await(2, TimeUnit.SECONDS), "expected async writeValues() to complete");
assertTrue(
completedExceptionally.get(), "expected async writeValues() to complete exceptionally");
assertFalse(
completedSuccessfully.get(), "expected async writeValues() to complete exceptionally");
} catch (Exception e) {
Logger.error(e);
e.printStackTrace();
} finally {
opcUaService.stopAsync().awaitTerminated();
}
} outputs:
|
Beta Was this translation helpful? Give feedback.
-
@peterfranciscook so what's the issue? Your test demonstrates the same thing mine does - the CompletableFuture does complete (exceptionally), with the NPE reported in the nested exceptions. It is expected that forcing a null StatusCode in the DataValue results in an NPE now. But this isn't failing silently. |
Beta Was this translation helpful? Give feedback.
-
Ohhhhh ok, I was not understanding you on Friday. When I say It "fails silently" in an async context, I meant this - if I call |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
I would like to request a migration guide to help users upgrade to version 1.0.0. Please include:
A comprehensive guide will assist users in making a smooth transition to the new version.
For reference, the Apache Camel team (see https://issues.apache.org/jira/browse/CAMEL-22086) has indicated they need a migration guide to begin upgrading the Camel Milo component. Providing such a guide will help downstream projects like Camel smoothly adopt the new version.
Beta Was this translation helpful? Give feedback.
All reactions