https://github.com/hurence/opc-simple
OPC made simple
https://github.com/hurence/opc-simple
iiot industry-4 opc-da opc-standard opc-ua reactive-streams rxjava2
Last synced: 5 months ago
JSON representation
OPC made simple
- Host: GitHub
- URL: https://github.com/hurence/opc-simple
- Owner: Hurence
- License: apache-2.0
- Created: 2018-04-16T07:46:40.000Z (over 7 years ago)
- Default Branch: master
- Last Pushed: 2022-08-17T19:58:44.000Z (about 3 years ago)
- Last Synced: 2025-04-19T01:32:38.276Z (6 months ago)
- Topics: iiot, industry-4, opc-da, opc-standard, opc-ua, reactive-streams, rxjava2
- Language: Java
- Size: 178 KB
- Stars: 17
- Watchers: 8
- Forks: 9
- Open Issues: 3
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# OPC Simple
OPC DA/UA made simple by [Hurence](https://www.hurence.com).
An easy to use reactive and quite painless OPC UA/DA java library.
Main benefits:
- I support both OPC-DA and OPC-UA with a harmonized unique API.
- I'm reactive (based on ReactiveX) and nonblocking operations makes me performing very fast.
- I'm open source (Apache 2.0)
- I'm portable (java based. No native code needed)## Getting Started
These instructions will help you to quick start using opc simple.
### Building
You can build on your machine using maven and a jdk >= 1.8.
Just trigger:
```
mvn clean install
```### Include in your project (with maven)
Add The maven dependency
```com.github.Hurence
opc-simple
3.0.0-rc1```
And the needed repositories
```
repository>
jitpack.io
https://jitpack.io
```### Examples
A step by step series of examples to showcase basic use cases.
#### Preamble
The library is built as close as possible to the reactive manifesto paradigms and is based on the
[RxJava](http://reactivex.io/) library.If you are not familiar with reactive programming, observer patterns, backpressure or with the rx-java
library in general, you can have further readings on the
[RxJava wiki](https://github.com/ReactiveX/RxJava/wiki/Additional-Reading)##### Connect to an OPC-DA server
As a prerequisite you should have an up an running OPC-DA server. In this example we'll use the
[Matrikon OPC simulation server](https://www.matrikonopc.com/products/opc-drivers/opc-simulation-server.aspx).Please feel free to change connection settings reflecting your real environment.
Follows a simple blocking example (see after below for more complex reactive examples).
```java
//create a connection profile
OpcDaConnectionProfile connectionProfile = new OpcDaConnectionProfile()
//change with the appropriate clsid
.withComClsId("F8582CF2-88FB-11D0-B850-00C0F0104305")
.withCredentials(new NtLmCredentials()
.withDomain("OPC-DOMAIN")
.withUser("OPC")
.withPassword("opc"))
.withConnectionUri(new URI("opc.da://192.168.99.100"))
.withSocketTimeout(Duration.of(5, ChronoUnit.SECONDS));
//Create an instance of a da operations
OpcDaOperations opcDaOperations = new OpcDaTemplate();
//connect using our profile
opcDaOperations.connect(connectionProfile).ignoreElement().blockingAwait();
```
##### Connect to an OPC-UA server
As a prerequisite you should have an up an running OPC-UA server. In this example we'll use the
[Prosys OPC-UA simulation server](https://www.prosysopc.com/products/opc-ua-simulation-server/).Please feel free to change connection settings reflecting your real environment.
Follows a simple blocking example (see after below for more complex reactive examples).
```java
//create a connection profile
OpcUaConnectionProfile connectionProfile = new new OpcUaConnectionProfile()
.withConnectionUri(URI.create("opc.tcp://localhost:53530/OPCUA/SimulationServer"))
.withClientIdUri("hurence:opc-simple:client:test")
.withClientName("Simple OPC test client")
.withSocketTimeout(Duration.ofSeconds(5));
//Create an instance of a ua operations
OpcUaOperations opcUaOperations = new OpcUaTemplate();
//connect using our profile
opcUaOperations.connect(connectionProfile)
.doOnError(throwable -> logger.error("Unable to connect", throwable))
.ignoreElement().blockingAwait();
```
#### Browse a list of tags
Assuming a connection is already in place, just browse the tags and print to stdout.
Blocking example:
````java
opcDaOperations.browseTags().foreachBlocking(System.out::println);
//execution here is resumed when browse completed
````Or in a "reactive way"
````java
opcDaOperations.browseTags().subscribe(System.out::println);
// code after is executed immediately without blocking (println is done asynchronously)
System.out.println("I'm a reactive OPC-Simple application :-)");````
#### Browse the tree branch by branch
Sometimes browsing the whole tree is too much time and resource consuming.
As an alternative you can browse level by level.For instance you can browse what's inside the group _Square Waves_:
````java
opcDaOperations.fetchNextTreeLevel("Square Waves")
.subscribe(System.out::println);
````#### Using Sessions
Session are stateful abstractions sharing Connection.
Hence multiple session can be created per connection.Session is the main entry point for the following actions:
* Read
* Write
* StreamWhen creating a session you should specify some parameters depending on the OPC standard you are using (e.g. direct read from hardware for OPC-DA).
Sessions should be created and released (beware leaks!) through the Connection object.
> SessionProfile and OpcOperations interface extends AutoCloseable interface.
> Hence you can use the handy *try-with-resources* syntax without taking care about destroying connection or sessions.Reactive tips:
> - Close your sessions in a *doFinally* block if you want to avoid leaks and you do not need anymore the session
> after downstream completes.
> - You can use *flatmap* operator to chain flows after creation of a connection or a session.
> - You can handle backpressure and tune up the scheduler to be used for observe/subscribe operations.
> The library itself does not make any assumption on it.##### Create an OPC-DA session
An example (blocking version):
````java
OpcDaSessionProfile sessionProfile = new OpcDaSessionProfile()
// direct read from device
.withDirectRead(false)
// refresh period
.withRefreshInterval(Duration.ofMillis(100));try (OpcSession session = opcDaOperations.createSession(sessionProfile).blockingGet()) {
//do something useful with your session
}
````##### Create an OPC-UA session
An example (still blocking):
````java
OpcUaSessionProfile sessionProfile = new OpcUaSessionProfile()
//the publication window
.withPublicationInterval(Duration.ofMillis(100));
try (OpcSession session = opcUaOperations.createSession(sessionProfile).blockingGet()) {
//do something useful with your session
}
````##### Create an OPC-UA session (reactive way)
A more efficient nonblocking example here:
````java
final OpcUaTemplate opcUaTemplate = new OpcUaTemplate()
// first create a session with the desired profile
opcUaTemplate.createSession(new OpcUaSessionProfile()
.withPublicationInterval(Duration.ofMillis(100))))
// we got a single. Encapsulate in a flowable and chain
.toFlowable()
.flatMap(opcUaSession ->
//do something more interesting with your session
Flowable
.empty()
//avoid open session leaks
.doFinally(opcUaSession::close)
)
.subscribe(...);
````#### Stream some tags readings
Assuming a connection is already in place, just stream tags values
and as soon as possible print their values to stdout.````java
final OpcDaTemplate opcDaTemplate = new OpcDaTemplate()
// first create a session with the desired profile
opcDaTemplate
.createSession(new OpcDaSessionProfile()
// direct read from device
.withDirectRead(false)
// refresh period
.withRefreshInterval(Duration.ofMillis(100))
)
// we got a single. Encapsulate in a flowable and chain
.toFlowable()
.flatMap(opcUaSession ->
// attach a stream to the session
opcUaSession.stream("Square Waves.Real8", Duration.ofMillis(100))
// close the session upon completion or error
.doFinally(opcUaSession::close)
)
//buffer in case of backpressure (but you can also discard or keep latest)
.onBackpressureBuffer()
//avoid blocking current thread for iowaits
.subscribeOn(Schedulers.io())
//take only first 100 elements
.limit(100)
//subscribe to events (upstream will start emitting events)
.subscribe(opcData-> doSomethingWithData(opcData));
````#### Advanced: managing automatic reconnection
With ReactiveX you can handle your stream as you want and even do some retry on error.
A quick example:
````java
//assumes connectionProfile and sessionProfile have already been defined.
daTemplate
//establish a connection
.connect(connectionProfile)
.toFlowable()
.flatMap(client -> client.createSession(sessionProfile)
//when ready create a subscription and start streaming some data
.toFlowable()
.flatMap(session ->
session.stream("Saw-toothed Waves.UInt4", Duration.ofMillis(100))
)
//do not forget to close connections
.doFinally(client::close)
)
//log upstream failures
.doOnError(throwable -> logger.warn("An error occurred. Retrying: " + throwable.getMessage()))
// Retry anything in case something failed failed
// You can use exp backoff or immediate as well
.retryWhen(throwable -> throwable.delay(1, TimeUnit.SECONDS))
// handle schedulers
.subscribeOn(Schedulers.io())
// handle backpressure
.onBackpressureBuffer()
// finally do something with this data :-)
.subscribe(opcData-> doSomethingWithData(opcData));````
### Integrate with other reactive frameworks
Rx-Java uses its Scheduler and Threading models but sometimes there is the need to use another
already in place thread pool.Here below you will find some examples.
#### Integrate with Vert.x
In order to best integrate with[Vert.x](https://vertx.io/) you should tell OPC simple to use the
already in-place event loops provided by Vert.xFirst of all, you need to import the rx-fied version of Vertx:
```
io.vertx
vertx-rx-java2
3.6.2
```Then, as suggested by rx, you can override defaults schedulers in this way:
````java
RxJavaPlugins.setComputationSchedulerHandler(s -> RxHelper.scheduler(vertx));
RxJavaPlugins.setIoSchedulerHandler(s -> RxHelper.blockingScheduler(vertx));
RxJavaPlugins.setNewThreadSchedulerHandler(s -> RxHelper.scheduler(vertx));
````The framework will do the rest to chose the right scheduler for blocking and computation operations.
You can still use *subscribeOn* and *observeOn* to better tune the performances according to Rx-Java
best practices.## Authors
* **Andrea Marziali** - *Initial work* - [amarziali](https://github.com/amarziali)
See also the list of [contributors](https://github.com/Hurence/opc-simple/contributors) who participated in this project.
## License
This project is licensed under the Apache 2.0 License - see the [LICENSE](LICENSE) file for details
## Changelog
Everything is tracked on a dedicate [CHANGELOG](CHANGELOG.md) file.
## Acknowledgments
* Thanks to OpenSCADA and Utgard project contributors for their great work.
* Thanks to Apache Milo for the great OPC-UA implementation.