| // |
| // Licensed to the Apache Software Foundation (ASF) under one or more |
| // contributor license agreements. See the NOTICE file distributed with |
| // this work for additional information regarding copyright ownership. |
| // The ASF licenses this file to You under the Apache License, Version 2.0 |
| // (the "License"); you may not use this file except in compliance with |
| // the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| // |
| |
| == Getting Started |
| |
| === Using the PLC4X API directly |
| |
| In order to write a valid PLC4X Java application, all you need, is to add a dependency to the `api module`. |
| When using Maven, all you need to do is add this dependency: |
| |
| [subs=attributes+] |
| ---- |
| <dependency> |
| <groupId>org.apache.plc4x</groupId> |
| <artifactId>plc4j-api</artifactId> |
| <version>{project-version}</version> |
| </dependency> |
| ---- |
| |
| This will allow you to write a valid application, that compiles fine. |
| However in order to actually connect to a device using a given protocol, you need to add this protocol implementation to the classpath. |
| |
| For example in order to communicate with an `S7 device` using the `S7 Protocol`, you would need to add the following dependency: |
| |
| [subs=attributes+] |
| ---- |
| <dependency> |
| <groupId>org.apache.plc4x</groupId> |
| <artifactId>plc4j-driver-s7</artifactId> |
| <version>{project-version}</version> |
| <scope>runtime</scope> |
| </dependency> |
| ---- |
| |
| So as soon as your project has the API and a driver implementation available, you first need to get a `PlcConnection` instance. |
| This is done via the `PlcDriverManager` by asking this to create an instance for a given `PLC4X connection string`. |
| |
| ---- |
| String connectionString = "s7://10.10.64.20/1/1"; |
| |
| try (PlcConnection plcConnection = new PlcDriverManager().getConnection(connectionString)) { |
| |
| ... do something with the connection here ... |
| |
| } |
| ---- |
| |
| PLC4X generally supports a very limited set of functions, which is not due to the fact, that we didn't implement things, but that PLCs generally support a very limited set of functions. |
| |
| The basic functions supported by PLCs and therefore supported by PLC4X are: |
| |
| * Read data |
| * Write data |
| * Subscribe for data |
| * Execute functions in the PLC |
| * List resources in the PLC |
| |
| In general we will try to offer as many features as possible. |
| So if a protocol doesn't support subscription based communication it is our goal to simulate this by polling in the background so it is transparent for the users. |
| |
| But there are some cases in which we can't simulate or features are simply disabled intentionally: |
| |
| * If a PLC and/or protocol don't support executing of functions, we simply can't provide this functionality. |
| * We will be providing stripped down versions of drivers, that for example intentionally don't support any writing of data and executing of functions. |
| |
| Therefore we use metadata to check programmatically, if a given feature is available: |
| |
| ---- |
| // Check if this connection support reading of data. |
| if (!plcConnection.getMetadata().canRead()) { |
| logger.error("This connection doesn't support reading."); |
| return; |
| } |
| ---- |
| |
| As soon as you have ensured that a feature is available, you are ready to build a first request. |
| This is done by getting a `PlcRequestBuilder`: |
| |
| ---- |
| // Create a new read request: |
| // - Give the single item requested the alias name "value" |
| PlcReadRequest.Builder builder = plcConnection.readRequestBuilder(); |
| builder.addItem("value-1", "%Q0.4:BOOL"); |
| builder.addItem("value-2", "%Q0:BYTE"); |
| builder.addItem("value-3", "%I0.2:BOOL"); |
| builder.addItem("value-4", "%DB.DB1.4:INT"); |
| PlcReadRequest readRequest = builder.build(); |
| ---- |
| |
| |
| So, as you can see, you prepare a request, by adding items to the request and in the end by calling the `build` method. |
| |
| The request is sent to the PLC by issuing the `execute` method on the request object: |
| |
| ---- |
| CompletableFuture<? extends PlcReadResponse> asyncResponse = readRequest.execute(); |
| asyncResponse.whenComplete((response, throwable) -> { |
| ... process the response ... |
| }); |
| ---- |
| |
| In general all requests are executed asynchronously. |
| So as soon as the request is fully processed, the callback gets called and will contain a `readResponse`, if everything went right or a `throwable` if there were problems. |
| |
| However if you want to write your code in a more synchronous fashion, the following alternative will provide this: |
| |
| ---- |
| PlcReadResponse response = readRequest.execute().get(); |
| ---- |
| |
| Processing of the responses is identical in both cases. |
| The following example will demonstrate some of the options you have: |
| |
| ---- |
| for (String fieldName : response.getFieldNames()) { |
| if(response.getResponseCode(fieldName) == PlcResponseCode.OK) { |
| int numValues = response.getNumberOfValues(fieldName); |
| // If it's just one element, output just one single line. |
| if(numValues == 1) { |
| logger.info("Value[" + fieldName + "]: " + response.getObject(fieldName)); |
| } |
| // If it's more than one element, output each in a single row. |
| else { |
| logger.info("Value[" + fieldName + "]:"); |
| for(int i = 0; i < numValues; i++) { |
| logger.info(" - " + response.getObject(fieldName, i)); |
| } |
| } |
| } |
| // Something went wrong, to output an error message instead. |
| else { |
| logger.error("Error[" + fieldName + "]: " + response.getResponseCode(fieldName).name()); |
| } |
| } |
| ---- |
| |
| In the for loop, we are demonstrating how the user can iterate over the address aliases in the response. |
| In case of an ordinary read request, this will be predefined by the items in the request, however in case of a subscription response, the response might only contain some of the items that were subscribed. |
| |
| Before accessing the data, it is advisable to check if an item was correctly returned. |
| This is done by the `getResponseCode` method for a given alias. |
| If this is `PlcResponseCode.OK`, everything is ok, however it could be one of the following: |
| |
| - NOT_FOUND |
| - ACCESS_DENIED |
| - INVALID_ADDRESS |
| - INVALID_DATATYPE |
| - INTERNAL_ERROR |
| - RESPONSE_PENDING |
| |
| Assuming the return code was `OK`, we can continue accessing the data. |
| |
| As some addresses support reading arrays, with the method `getNumberOfValues` the user can check how many items of a given type are returned. |
| For convenience the response object has single-argument methods for accessing the data, which default to returning the first element. |
| |
| response.getObject(fieldName) |
| |
| If you want to access a given element number, please use the two-argument version instead: |
| |
| response.getObject(fieldName, 42) |
| |
| PLC4X provides getters and setters for a wide variety of Java types and automatically handles the type conversion. |
| However when for example trying to get a long-value as a byte and the long-value exceeds the range supported by the smaller type, a `RuntimeException` of type `PlcIncompatibleDatatypeException`. |
| In order to avoid causing this exception to be thrown, however there are `isValid{TypeName}` methods that you can use to check if the value is compatible. |