From baee45dee66184b0d165d0692c512e2a1a1f1ebe Mon Sep 17 00:00:00 2001 From: Sandro Sabatini Date: Mon, 9 Dec 2024 18:47:40 +0100 Subject: [PATCH 01/10] 1st step: new example code for getValues(), getDeviceInfo(), getDeviceConfigurationInfo(), getDeviceConfigurationInfo(), getFunctionalProfile(), getDataPoints(), getDataPoint() and isConnected() --- .../example/BasicSampleCommunicator.java | 281 +++++++++++++++--- 1 file changed, 244 insertions(+), 37 deletions(-) diff --git a/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/BasicSampleCommunicator.java b/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/BasicSampleCommunicator.java index 1aaaf60..bfbfb1b 100644 --- a/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/BasicSampleCommunicator.java +++ b/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/BasicSampleCommunicator.java @@ -21,19 +21,28 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWIS */ package com.smartgridready.communicator.example; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +import java.util.Properties; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.smartgridready.communicator.common.api.GenDeviceApi; import com.smartgridready.communicator.common.api.SGrDeviceBuilder; +import com.smartgridready.communicator.common.api.dto.ConfigurationValue; +import com.smartgridready.communicator.common.api.dto.DataPoint; +import com.smartgridready.communicator.common.api.dto.DataPointValue; +import com.smartgridready.communicator.common.api.dto.DeviceInfo; +import com.smartgridready.communicator.common.api.dto.FunctionalProfile; +import com.smartgridready.communicator.common.api.dto.GenericAttribute; import com.smartgridready.communicator.example.helper.MockModbusClientFactory; +import com.smartgridready.communicator.rest.exception.RestApiAuthenticationException; +import com.smartgridready.driver.api.common.GenDriverException; import com.smartgridready.driver.api.modbus.GenDriverAPI4ModbusFactory; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.util.Properties; - /** *

* This is a sample implementation of communicator that uses the SmartGridready communication @@ -71,27 +80,41 @@ public class BasicSampleCommunicator { public static void main(String[] argv) { - try { - // Step 1: - // Use the SGrDeviceBuilder class to load the device description (EID) from - // an XML file, input stream or text content. - // Use properties to replace configuration placeholders in EID. - // Create the SGr device instance by calling build(). - // - // This example uses a mocked Modbus driver factory to create the driver instance. - // You may change the factory implementation or just use the default, in order to - // create actual Modbus devices with serial or TCP connection. - // - Properties configProperties = new Properties(); - configProperties.setProperty("serial_port", SERIAL_PORT_NAME); + // Step 1: + // Use the SGrDeviceBuilder class to load the device description (EID) from + // an XML file, input stream or text content. + // Use properties to replace configuration placeholders in EID. + // Create the SGr device instance by calling build(). + // + // This example uses a mocked Modbus driver factory to create the driver instance. + // You may change the factory implementation or just use the default, in order to + // create actual Modbus devices with serial or TCP connection. + // + Properties configProperties = new Properties(); + configProperties.setProperty("serial_port", SERIAL_PORT_NAME); - GenDeviceApi sgcpDevice = new SGrDeviceBuilder() - .useModbusClientFactory(mockModbusFactory) - .useSharedModbusRtu(true) - .eid(getDeviceDescriptionFile(DEVICE_DESCRIPTION_FILE_NAME)) - .properties(configProperties) - .build(); + GenDeviceApi sgcpDevice; + try + { + sgcpDevice = new SGrDeviceBuilder() + // mandatory: inject device description (EID) + .eid(getDeviceDescriptionFile(DEVICE_DESCRIPTION_FILE_NAME)) + // optional: inject the ModbusFactory mock + .useModbusClientFactory(mockModbusFactory) +// .useSharedModbusRtu(true) + // optional: inject the configuration + .properties(configProperties) + .build(); + } + catch ( GenDriverException | RestApiAuthenticationException | IOException e ) + { + LOG.error("Error loading device description. ", e); + return; + } + + try + { // Step 2: // Connect the device instance. Initializes the attached transport. // In case of Modbus RTU this initializes the COM port. @@ -100,8 +123,7 @@ public static void main(String[] argv) { // sgcpDevice.connect(); - // Step 3: - // Read the values from the device. + // Read specific values from the device. // - "PROFILE_VOLTAGE_AC" is the name of the functional profile. // - "VoltageL1", "VoltageL2" and "VoltageL3" are the names of the Datapoints that // report the values corresponding to their names. @@ -109,21 +131,206 @@ public static void main(String[] argv) { // Hint: You can only read values for functional profiles and datapoints that exist // in the device description (EID). // - float val1 = sgcpDevice.getVal(PROFILE_VOLTAGE_AC, "VoltageL1").getFloat32(); - float val2 = sgcpDevice.getVal(PROFILE_VOLTAGE_AC, "VoltageL2").getFloat32(); - float val3 = sgcpDevice.getVal(PROFILE_VOLTAGE_AC, "VoltageL3").getFloat32(); - String log = String.format("Wago-Meter CurrentAC: %.2fV, %.2fV, %.2fV", val1, val2, val3); + var val1 = sgcpDevice.getVal(PROFILE_VOLTAGE_AC, "VoltageL1").getFloat32(); + var val2 = sgcpDevice.getVal(PROFILE_VOLTAGE_AC, "VoltageL2").getFloat32(); + var val3 = sgcpDevice.getVal(PROFILE_VOLTAGE_AC, "VoltageL3").getFloat32(); + var log = String.format("Wago-Meter, %s: L1=%.2fV, L2=%.2fV, L3=%.2fV", PROFILE_VOLTAGE_AC, val1, val2, val3); LOG.info(log); + + var val4 = sgcpDevice.getVal("CurrentDirection", "CurrentDirL1"); + var log2 = String.format("Wago-Meter, %s: L1=%s", "CurrentDirection", val4.getString()); + LOG.info(log2); + + // REMARK: An example for setVal() you find in EnumAndBitmapSampleCommunicator - // Step 4: - // Disconnect from device instance. Closes the attached transport. - // - sgcpDevice.disconnect(); - } catch (Exception e) { - LOG.error("Error loading device description. ", e); - } + // Read all values from the device. + var values = sgcpDevice.getValues(); + LOG.info(valsToString(values)); + + // Get device info + var deviceInfo = sgcpDevice.getDeviceInfo(); + LOG.info(diToString(deviceInfo)); + + // Or simply just the device configuration info. + var configurationInfo = sgcpDevice.getDeviceConfigurationInfo(); + LOG.info("DeviceConfigurationInfo:" + ciToString(configurationInfo, 1)); + + // Or just the functional profiles. + var functionalProfiles = sgcpDevice.getFunctionalProfiles(); + LOG.info("FunctionalProfiles:" + fpsToString(functionalProfiles, 1, true)); + + // Get a specific functional profile. + var functionalProfile = sgcpDevice.getFunctionalProfile(functionalProfiles.get(0).getName()); + LOG.info("FunctionalProfile:" + fpToString(functionalProfile, 1, false)); + + // Get data points of a specific functional profile. + var dataPoints = sgcpDevice.getDataPoints(functionalProfile.getName()); + LOG.info("DataPoints:" + dpsToString(dataPoints, 1, true)); + + // Get a specific data point of a specific functional profile. + var dataPoint = sgcpDevice.getDataPoint(functionalProfile.getName(), dataPoints.get(0).getName()); + LOG.info("DataPoint:" + dpToString(dataPoint, 1, false)); + } + catch (Exception e) + { + LOG.error("Error accessing device. ", e); + } + finally + { + // last Step: + // Disconnect from device instance. Closes the attached transport. + if (sgcpDevice.isConnected()) + { + try + { + LOG.info("Disconnecting ..."); + sgcpDevice.disconnect(); + } + catch ( GenDriverException e ) + { + LOG.error("Error disconnecting device.", e); + } + } + } } + + private static String valsToString(List values) + { + final var sb = new StringBuilder(); + final var tabs = tabsToString(1); + sb.append("Values:"); + values.forEach(value -> sb.append("\n" + tabs + value)); + return sb.toString(); + } + + private static String diToString(DeviceInfo deviceInfo) + { + final var tabs = tabsToString(1); + + return "DeviceInfo:" + + "\n" + tabs + "name: " + deviceInfo.getName() + + "\n" + tabs + "manufacturer: " + deviceInfo.getManufacturer() + + "\n" + tabs + "versionNumber: " + deviceInfo.getVersionNumber() + + "\n" + tabs + "softwareVersion: " + deviceInfo.getSoftwareVersion() + + "\n" + tabs + "hardwareVersion: " + deviceInfo.getHardwareVersion() + + "\n" + tabs + "deviceCategory: " + deviceInfo.getDeviceCategory() + + "\n" + tabs + "interfaceType: " + deviceInfo.getInterfaceType() + + "\n" + tabs + "operationEnvironment: " + deviceInfo.getOperationEnvironment() + + "\n" + tabs + "genericAttributes: " + gaToString(deviceInfo.getGenericAttributes(), 2) + + "\n" + tabs + "configurationInfo: " + ciToString(deviceInfo.getConfigurationInfo(), 2) + + "\n" + tabs + "functionalProfiles: " + fpsToString(deviceInfo.getFunctionalProfiles(), 2, true); + } + + private static String gaToString(List genericAttribute, int numOfTabs) + { + if (genericAttribute.isEmpty()) return "[]"; + + final var sb = new StringBuilder(); + final var tabs = tabsToString(numOfTabs); + genericAttribute.forEach(ga -> + { + sb.append("\n" + tabs + "name: " + ga.getName()); + sb.append("\n" + tabs + "value: " + ga.getValue()); + sb.append("\n" + tabs + "dataType: " + ga.getDataType().getTypeName()); + sb.append("\n" + tabs + "unit: " + ga.getUnit()); + sb.append("\n" + tabs + "children: " + ga.getChildren()); + sb.append("\n" + tabs + "---"); + }); + return sb.toString(); + } + + private static String ciToString(List configurationInfo, int numOfTabs) + { + if (configurationInfo.isEmpty()) return "[]"; + + final var sb = new StringBuilder(); + final var tabs = tabsToString(numOfTabs); + configurationInfo.forEach(ci -> + { + sb.append("\n" + tabs + "name: " + ci.getName()); + sb.append("\n" + tabs + "defaultValue: " + ci.getDefaultValue()); + sb.append("\n" + tabs + "dataType: " + ci.getDataType().getTypeName()); + sb.append("\n" + tabs + "descriptions: " + ci.getDescriptions()); + sb.append("\n" + tabs + "---"); + }); + return sb.toString(); + } + + private static String fpsToString(List functionalProfiles, int numOfTabs, boolean shortLog) + { + if (functionalProfiles.isEmpty()) return "[]"; + + final var sb = new StringBuilder(); + functionalProfiles.forEach(fp -> + { + sb.append(fpToString( fp, numOfTabs, shortLog)); + }); + return sb.toString(); + } + + private static String fpToString(FunctionalProfile fp, int numOfTabs, boolean shortLog) + { + final var sb = new StringBuilder(); + final var tabs = tabsToString(numOfTabs); + + sb.append("\n" + tabs + "name: " + fp.getName()); + + if (!shortLog) + { + sb.append("\n" + tabs + "profileType: " + fp.getProfileType()); + sb.append("\n" + tabs + "category: " + fp.getCategory()); + sb.append("\n" + tabs + "genericAttributes: " + gaToString(fp.getGenericAttributes(), numOfTabs + 1)); + sb.append("\n" + tabs + "dataPoints: " + dpsToString(fp.getDataPoints(), numOfTabs + 1, true)); + sb.append("\n" + tabs + "---"); + } + return sb.toString(); + } + + private static String dpsToString(List dataPoints, int numOfTabs, boolean shortLog) + { + if (dataPoints.isEmpty()) return "[]"; + + final var sb = new StringBuilder(); + dataPoints.forEach(dp -> + { + sb.append(dpToString(dp, numOfTabs, shortLog)); + }); + return sb.toString(); + } + + private static String dpToString(DataPoint dp, int numOfTabs, boolean shortLog) + { + final var sb = new StringBuilder(); + final var tabs = tabsToString(numOfTabs); + sb.append("\n" + tabs + "name: " + dp.getName()); + + if (!shortLog) + { + sb.append("\n" + tabs + "functionalProfileName: " + dp.getFunctionalProfileName()); + sb.append("\n" + tabs + "dataType: " + dp.getDataType().getTypeName()); + sb.append("\n" + tabs + "getUnit: " + dp.getUnit()); + sb.append("\n" + tabs + "permissions: " + dp.getPermissions()); + sb.append("\n" + tabs + "minimumValue: " + dp.getMinimumValue()); + sb.append("\n" + tabs + "maximumValue: " + dp.getMaximumValue()); + sb.append("\n" + tabs + "arrayLen: " + dp.getArrayLen()); + sb.append("\n" + tabs + "genericAttributes: " + gaToString(dp.getGenericAttributes(), numOfTabs + 1)); + sb.append("\n" + tabs + "---"); + } + return sb.toString(); + } + + private static String tabsToString(int numOfTabs) + { + final var sb = new StringBuilder(); + + while (numOfTabs-- > 0 ) + { + sb.append("\t"); + } + return sb.toString(); + } + private static InputStream getDeviceDescriptionFile(String fileName) throws IOException { ClassLoader classloader = Thread.currentThread().getContextClassLoader(); InputStream istr = classloader.getResourceAsStream(fileName); From 245252b454c8e310f9c42a1bef6015eedac9488a Mon Sep 17 00:00:00 2001 From: Sandro Sabatini Date: Wed, 11 Dec 2024 18:26:48 +0100 Subject: [PATCH 02/10] added RestSampleCommunicator, deprecated BasicSampleCommunicatorClassic --- .../BasicSampleCommunicatorClassic.java | 37 +- .../example/RestSampleCommunicator.java | 333 ++++++++++++++++++ ...mmmm_dddd_Shelly_TRV_RestAPILocal_V0.1.xml | 195 ++++++++++ 3 files changed, 547 insertions(+), 18 deletions(-) create mode 100644 SampleCommunicator/src/main/java/com/smartgridready/communicator/example/RestSampleCommunicator.java create mode 100644 SampleCommunicator/src/main/resources/SGr_01_mmmm_dddd_Shelly_TRV_RestAPILocal_V0.1.xml diff --git a/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/BasicSampleCommunicatorClassic.java b/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/BasicSampleCommunicatorClassic.java index 0a0442a..8983088 100644 --- a/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/BasicSampleCommunicatorClassic.java +++ b/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/BasicSampleCommunicatorClassic.java @@ -37,31 +37,32 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWIS import java.net.URL; import java.util.Properties; -/** + +/** *

- * This is a sample implementation of communicator that uses the SmartGridready communication - * handler. The communication handler uses the SmartGridready generic interface to communicate - * with any attached device/product in a generic way. The communication handler converts the - * generic API commands to commands understood by the external interface of the device/product. - *

- * The command translation is done by the communication handler based on a device description - * loaded from a device description XML file. + * This is a sample implementation of communicator that uses the SmartGridready communication handler. The + * communication handler uses the SmartGridready generic interface to communicate with any attached + * device/product in a generic way. The communication handler converts the generic API commands to + * commands understood by the external interface of the device/product. + *

+ * The command translation is done by the communication handler based on a device description loaded from + * a device description XML file. *

* There are several communication protocols/technologies available to communicate with the device: *

- * The communicator is responsible instantiate/load the suitable driver for the attached - * devices/products. - *

- * The example shows the basic steps to set up the communicator to talk to a simple - * SmartGridready Modbus device and read a value from the device. + * The communicator is responsible instantiate/load the suitable driver for the attached devices/products. *

- * Note that this example uses the classic method to create device instances, which is deprecated. + * The example shows the basic steps to set up the communicator to talk to a simple SmartGridready Modbus + * device and read a value from the device. * - **/ + * @deprecated Note that this example uses the classic method to create device instances, which is + * deprecated. + */ +@Deprecated(since = "1.1.0", forRemoval = true) public class BasicSampleCommunicatorClassic { private static final Logger LOG = LoggerFactory.getLogger(BasicSampleCommunicator.class); diff --git a/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/RestSampleCommunicator.java b/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/RestSampleCommunicator.java new file mode 100644 index 0000000..bbe6736 --- /dev/null +++ b/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/RestSampleCommunicator.java @@ -0,0 +1,333 @@ +/* + * Copyright(c) 2024 Verein SmartGridready Switzerland + * + * This Open Source Software is BSD 3 clause licensed: + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.smartgridready.communicator.example; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.text.MessageFormat; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.smartgridready.communicator.common.api.GenDeviceApi; +import com.smartgridready.communicator.common.api.SGrDeviceBuilder; +import com.smartgridready.communicator.common.api.values.Float64Value; +import com.smartgridready.communicator.rest.exception.RestApiAuthenticationException; +import com.smartgridready.driver.api.common.GenDriverException; +import com.smartgridready.driver.api.http.GenHttpClientFactory; +import com.smartgridready.driver.api.http.GenHttpRequest; +import com.smartgridready.driver.api.http.GenHttpResponse; +import com.smartgridready.driver.api.http.GenUriBuilder; +import com.smartgridready.driver.api.http.HttpMethod; + + +/** + * This is a sample implementation of communicator that uses the SmartGridready communication handler. + *

+ * The communication handler uses the SmartGridready generic interface to communicate with any attached + * device/product in a generic way. The communication handler converts the generic API commands to + * commands understood by the external interface of the device/product. + *

+ * The command translation is done by the communication handler based on a device description loaded from + * a device description XML file. + *

+ * There are several communication protocols/technologies available to communicate with the device: + *

+ * The communicator is responsible of loading the appropriate descriptions and parameters of the attached + * devices/products. + *

+ * The example shows the basic steps to set up the communicator to talk to a simple SmartGridready REST + * device and read/writes a value from/to the device. + *

+ * The example also uses the recommended new SGrDeviceBuilder method. + */ +public class RestSampleCommunicator +{ + private static final Logger LOG = LoggerFactory.getLogger(RestSampleCommunicator.class); + + /** This example is tied to this EID-XML. */ + private static final String DEVICE_DESCRIPTION_FILE_NAME = "SGr_01_mmmm_dddd_Shelly_TRV_RestAPILocal_V0.1.xml"; + /** This URI is not important, can have any value. */ + private static final String BASE_URI = "https://example.com/"; + + public static void main(String[] args) + { + // This example uses a mocked REST driver factory to create the driver instance. + // You may change the factory implementation or just use the default, in order to + // create actual REST devices. + + Properties configProperties = new Properties(); + LOG.info("Configuring base_uri to '{}'.", BASE_URI); + configProperties.setProperty("base_uri", BASE_URI); + + GenDeviceApi sgcpDevice; + + try + { + // Use the SGrDeviceBuilder class to load the device description (EID) from + // an XML file, input stream or text content. + // Use properties to replace configuration place holders in EID. + // Create the SGr device instance by calling build(). + sgcpDevice = new SGrDeviceBuilder() + // mandatory: inject device description (EID) + .eid(getDeviceDescriptionFile(DEVICE_DESCRIPTION_FILE_NAME)) + // optional: inject the configuration according to the used EID (in this case required) + .properties(configProperties) + // optional: inject the REST mock (only for this example) + .useRestServiceClientFactory(new RestClientFactory()) + .build(); + } + catch (GenDriverException | RestApiAuthenticationException | IOException e) + { + LOG.error("Error loading device description. ", e); + return; + } + + try + { + // Connect the device instance. Initializes the attached transport. + LOG.info("Connecting ..."); + sgcpDevice.connect(); + + // Read the current temperature + final var PROFILE_NAME = "Thermostat"; + final var temp = sgcpDevice.getVal(PROFILE_NAME, "Temperature"); + LOG.info("Current Temperature is '{}'", temp.getFloat64()); + + // Change the target temperature + final var newTargetTemp = Float64Value.of(22.2); + LOG.info("Writing TargetTemperature '{}'", newTargetTemp); + sgcpDevice.setVal(PROFILE_NAME, "TargetTemperature", newTargetTemp); + + // Read the target temperature + final var readTargetTemp = sgcpDevice.getVal(PROFILE_NAME, "TargetTemperature"); + LOG.info("Current TargetTemperature is now '{}'", readTargetTemp.getFloat64()); + + // Read again the current temperature + final var temp2 = sgcpDevice.getVal(PROFILE_NAME, "Temperature"); + LOG.info("Current Temperature is now '{}'", temp2.getFloat64()); + } + catch (Exception e) + { + LOG.error("Error accessing device. ", e); + } + finally + { + // Disconnect from device instance. Closes the attached transport. + if (sgcpDevice.isConnected()) + { + try + { + LOG.info("Disconnecting ..."); + sgcpDevice.disconnect(); + } + catch (GenDriverException e) + { + LOG.error("Error disconnecting device.", e); + } + } + } + } + + /** + * Reads the EID with the given {@code fileName}. + * + * @param fileName + * name of EID-XML file to read + * @return {InputStream} to file + * @throws FileNotFoundException + * if no EID-XML file with the given {@code fileName} exists + */ + private static InputStream getDeviceDescriptionFile(String fileName) throws FileNotFoundException + { + final var classloader = Thread.currentThread().getContextClassLoader(); + final var istr = classloader.getResourceAsStream(fileName); + + if (istr == null) + { + throw new FileNotFoundException("Unable to load device description file: " + DEVICE_DESCRIPTION_FILE_NAME); + } + + return istr; + } +} + + +/** + * Mock of a REST client for the EID-XML "SGr_01_mmmm_dddd_Shelly_TRV_RestAPILocal_V0.1.xml". + */ +class RestClientFactory implements GenHttpClientFactory +{ + private static final String INITIAL_TEMP = "25.5"; + /** fake temperature memory */ + private static String lastSetTargetTemperature = INITIAL_TEMP; + + @Override + public GenHttpRequest createHttpRequest() + { + return new RestHttpRequest(); + } + + @Override + public GenUriBuilder createUriBuilder(String baseUri) throws URISyntaxException + { + return new RestUriBuilder(); + } + + /** + * Mock of a REST HTTP request. + */ + static class RestHttpRequest implements GenHttpRequest + { + private static final Logger LOG = LoggerFactory.getLogger(RestHttpRequest.class); + + private URI uri; + private HttpMethod httpMethod; + private Map headerMap = new HashMap<>(); + private String body; + private Map formParamMap = new HashMap<>(); + + @Override + public GenHttpResponse execute() throws IOException + { + LOG.debug("httpMethod={}; uri={}; headerMap={}; body={}; formParamMap={}", + httpMethod, uri, headerMap, body, formParamMap); + + // this device sends always all parameters + var response = + MessageFormat.format( + "'{'" + + "\"tmp\" : '{' \"value\" : {0} '}'," + + "\"target_t\" : '{' \"value\" : {1} '}'" + + "'}'", + lastSetTargetTemperature, lastSetTargetTemperature); + return GenHttpResponse.of(response); + } + + @Override + public GenHttpRequest setUri(URI uri) + { + this.uri = uri; + + if (uri.getQuery() != null) + { + var query = uri.getQuery(); + var parts = query.split("="); + lastSetTargetTemperature = parts[1]; + } + + return this; + } + + @Override + public void setHttpMethod(HttpMethod httpMethod) + { + this.httpMethod = httpMethod; + } + + @Override + public void addHeader(String key, String value) + { + this.headerMap.put( key, value ); + } + + @Override + public void setBody(String body) + { + this.body = body; + } + + @Override + public void addFormParam(String key, String value) + { + this.formParamMap.put(key, value); + } + + } + + /** + * Mock of a REST URI builder. + */ + static class RestUriBuilder implements GenUriBuilder + { + private static final Logger LOG = LoggerFactory.getLogger(RestUriBuilder.class); + + private String queryString; + private String path; + private Map queryParameterMap = new HashMap<>(); + + @Override + public GenUriBuilder setQueryString(String queryString) + { + this.queryString = queryString; + return this; + } + + @Override + public GenUriBuilder addPath(String path) + { + this.path = path; + return this; + } + + @Override + public GenUriBuilder addQueryParameter(String name, String value) + { + this.queryParameterMap.put(name, value); + return this; + } + + @Override + public URI build() throws URISyntaxException + { + LOG.debug("path={}; queryString={}; queryParameterMap={}", path, queryString, queryParameterMap); + final var query = new StringBuilder(); + + if ((queryString != null) || (!queryParameterMap.isEmpty())) + { + query.append("?"); + + if (queryString != null) + { + query.append(queryString + " "); + } + + queryParameterMap.forEach( (key,value) -> query.append(key + "=" + value + ",")); + + if (query.toString().endsWith(",")) + { + query.deleteCharAt(query.length() - 1); + } + } + + return URI.create(path + query); + } + + } +} diff --git a/SampleCommunicator/src/main/resources/SGr_01_mmmm_dddd_Shelly_TRV_RestAPILocal_V0.1.xml b/SampleCommunicator/src/main/resources/SGr_01_mmmm_dddd_Shelly_TRV_RestAPILocal_V0.1.xml new file mode 100644 index 0000000..525f46e --- /dev/null +++ b/SampleCommunicator/src/main/resources/SGr_01_mmmm_dddd_Shelly_TRV_RestAPILocal_V0.1.xml @@ -0,0 +1,195 @@ + + + + Shelly TRV Local + Shelly + 0 + + Draft + + 0.1.0 + 2024-10-16 + Matthias Krebs, FHNW + Draft for Testlab + + + + + + + + de + https://www.shelly.com/de-ch/products/product-overview/shelly-trv-1 + + HeatingObject + true + 1.0.0 + 1.0.0 + Shelly + 4m + + 0 + 1 + 0 + + + + + base_uri + + + + http://192.168.33.1 + + Base address for accessing the resource. + en + + + + Basis-Adresse für Zugriffe auf die Ressource. + de + + + + + + + + URI + {{base_uri}} + NoSecurityScheme + + + + + Thermostat + + 0 + HeatingCircuit + Thermostat + 4m + + 0 + 1 + 0 + + + + + Ermöglicht die Steuerung der Zieltemperatur. + + de + + + + Allows controlling the target temperature. + + en + + + + + + Temperature + R + + + + DEGREES_CELSIUS + -40 + 60 + + Messung der Temperatur + de + + + Temperature measurement + en + + + + JSON_number + + +

+ Accept + application/json +
+ + GET + /thermostats/0 + + JMESPathExpression + tmp.value + + + + + + + TargetTemperature + RW + + + + DEGREES_CELSIUS + 4 + 31 + + Einstellung der Zieltemperatur + de + + + Setting of the target temperature + en + + + + JSON_number + + +
+ Accept + application/json +
+
+ GET + /thermostats/0 + + JMESPathExpression + target_t.value + +
+ + +
+ Accept + application/json +
+
+ GET + /thermostats/0 + + + target_t + {{value}} + + + + JMESPathExpression + target_t.value + +
+
+
+ + + + + + \ No newline at end of file From 05e8fca5bce6e15a9b9a7b764ac6e5c45ab52214 Mon Sep 17 00:00:00 2001 From: Sandro Sabatini Date: Thu, 12 Dec 2024 09:46:19 +0100 Subject: [PATCH 03/10] extracted EidLoader --- .../example/BasicSampleCommunicator.java | 43 ++++++-------- .../example/RestSampleCommunicator.java | 27 +-------- .../example/helper/EidLoader.java | 59 +++++++++++++++++++ 3 files changed, 79 insertions(+), 50 deletions(-) create mode 100644 SampleCommunicator/src/main/java/com/smartgridready/communicator/example/helper/EidLoader.java diff --git a/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/BasicSampleCommunicator.java b/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/BasicSampleCommunicator.java index bfbfb1b..1d1f551 100644 --- a/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/BasicSampleCommunicator.java +++ b/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/BasicSampleCommunicator.java @@ -21,9 +21,7 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWIS */ package com.smartgridready.communicator.example; -import java.io.FileNotFoundException; import java.io.IOException; -import java.io.InputStream; import java.util.List; import java.util.Properties; @@ -38,6 +36,7 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWIS import com.smartgridready.communicator.common.api.dto.DeviceInfo; import com.smartgridready.communicator.common.api.dto.FunctionalProfile; import com.smartgridready.communicator.common.api.dto.GenericAttribute; +import com.smartgridready.communicator.example.helper.EidLoader; import com.smartgridready.communicator.example.helper.MockModbusClientFactory; import com.smartgridready.communicator.rest.exception.RestApiAuthenticationException; import com.smartgridready.driver.api.common.GenDriverException; @@ -90,7 +89,7 @@ public static void main(String[] argv) { // You may change the factory implementation or just use the default, in order to // create actual Modbus devices with serial or TCP connection. // - Properties configProperties = new Properties(); + final var configProperties = new Properties(); configProperties.setProperty("serial_port", SERIAL_PORT_NAME); GenDeviceApi sgcpDevice; @@ -99,7 +98,7 @@ public static void main(String[] argv) { { sgcpDevice = new SGrDeviceBuilder() // mandatory: inject device description (EID) - .eid(getDeviceDescriptionFile(DEVICE_DESCRIPTION_FILE_NAME)) + .eid(EidLoader.getDeviceDescriptionFile(DEVICE_DESCRIPTION_FILE_NAME)) // optional: inject the ModbusFactory mock .useModbusClientFactory(mockModbusFactory) // .useSharedModbusRtu(true) @@ -131,44 +130,44 @@ public static void main(String[] argv) { // Hint: You can only read values for functional profiles and datapoints that exist // in the device description (EID). // - var val1 = sgcpDevice.getVal(PROFILE_VOLTAGE_AC, "VoltageL1").getFloat32(); - var val2 = sgcpDevice.getVal(PROFILE_VOLTAGE_AC, "VoltageL2").getFloat32(); - var val3 = sgcpDevice.getVal(PROFILE_VOLTAGE_AC, "VoltageL3").getFloat32(); - var log = String.format("Wago-Meter, %s: L1=%.2fV, L2=%.2fV, L3=%.2fV", PROFILE_VOLTAGE_AC, val1, val2, val3); + final var val1 = sgcpDevice.getVal(PROFILE_VOLTAGE_AC, "VoltageL1").getFloat32(); + final var val2 = sgcpDevice.getVal(PROFILE_VOLTAGE_AC, "VoltageL2").getFloat32(); + final var val3 = sgcpDevice.getVal(PROFILE_VOLTAGE_AC, "VoltageL3").getFloat32(); + final var log = String.format("Wago-Meter, %s: L1=%.2fV, L2=%.2fV, L3=%.2fV", PROFILE_VOLTAGE_AC, val1, val2, val3); LOG.info(log); - var val4 = sgcpDevice.getVal("CurrentDirection", "CurrentDirL1"); - var log2 = String.format("Wago-Meter, %s: L1=%s", "CurrentDirection", val4.getString()); + final var val4 = sgcpDevice.getVal("CurrentDirection", "CurrentDirL1"); + final var log2 = String.format("Wago-Meter, %s: L1=%s", "CurrentDirection", val4.getString()); LOG.info(log2); // REMARK: An example for setVal() you find in EnumAndBitmapSampleCommunicator // Read all values from the device. - var values = sgcpDevice.getValues(); + final var values = sgcpDevice.getValues(); LOG.info(valsToString(values)); // Get device info - var deviceInfo = sgcpDevice.getDeviceInfo(); + final var deviceInfo = sgcpDevice.getDeviceInfo(); LOG.info(diToString(deviceInfo)); // Or simply just the device configuration info. - var configurationInfo = sgcpDevice.getDeviceConfigurationInfo(); + final var configurationInfo = sgcpDevice.getDeviceConfigurationInfo(); LOG.info("DeviceConfigurationInfo:" + ciToString(configurationInfo, 1)); // Or just the functional profiles. - var functionalProfiles = sgcpDevice.getFunctionalProfiles(); + final var functionalProfiles = sgcpDevice.getFunctionalProfiles(); LOG.info("FunctionalProfiles:" + fpsToString(functionalProfiles, 1, true)); // Get a specific functional profile. - var functionalProfile = sgcpDevice.getFunctionalProfile(functionalProfiles.get(0).getName()); + final var functionalProfile = sgcpDevice.getFunctionalProfile(functionalProfiles.get(0).getName()); LOG.info("FunctionalProfile:" + fpToString(functionalProfile, 1, false)); // Get data points of a specific functional profile. - var dataPoints = sgcpDevice.getDataPoints(functionalProfile.getName()); + final var dataPoints = sgcpDevice.getDataPoints(functionalProfile.getName()); LOG.info("DataPoints:" + dpsToString(dataPoints, 1, true)); // Get a specific data point of a specific functional profile. - var dataPoint = sgcpDevice.getDataPoint(functionalProfile.getName(), dataPoints.get(0).getName()); + final var dataPoint = sgcpDevice.getDataPoint(functionalProfile.getName(), dataPoints.get(0).getName()); LOG.info("DataPoint:" + dpToString(dataPoint, 1, false)); } catch (Exception e) @@ -316,6 +315,7 @@ private static String dpToString(DataPoint dp, int numOfTabs, boolean shortLog) sb.append("\n" + tabs + "genericAttributes: " + gaToString(dp.getGenericAttributes(), numOfTabs + 1)); sb.append("\n" + tabs + "---"); } + return sb.toString(); } @@ -331,13 +331,4 @@ private static String tabsToString(int numOfTabs) return sb.toString(); } - private static InputStream getDeviceDescriptionFile(String fileName) throws IOException { - ClassLoader classloader = Thread.currentThread().getContextClassLoader(); - InputStream istr = classloader.getResourceAsStream(fileName); - if (istr != null) { - return istr; - } - - throw new FileNotFoundException("Unable to load device description file: " + DEVICE_DESCRIPTION_FILE_NAME); - } } diff --git a/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/RestSampleCommunicator.java b/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/RestSampleCommunicator.java index bbe6736..10e433f 100644 --- a/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/RestSampleCommunicator.java +++ b/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/RestSampleCommunicator.java @@ -18,9 +18,7 @@ package com.smartgridready.communicator.example; -import java.io.FileNotFoundException; import java.io.IOException; -import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; import java.text.MessageFormat; @@ -34,6 +32,7 @@ import com.smartgridready.communicator.common.api.GenDeviceApi; import com.smartgridready.communicator.common.api.SGrDeviceBuilder; import com.smartgridready.communicator.common.api.values.Float64Value; +import com.smartgridready.communicator.example.helper.EidLoader; import com.smartgridready.communicator.rest.exception.RestApiAuthenticationException; import com.smartgridready.driver.api.common.GenDriverException; import com.smartgridready.driver.api.http.GenHttpClientFactory; @@ -96,7 +95,7 @@ public static void main(String[] args) // Create the SGr device instance by calling build(). sgcpDevice = new SGrDeviceBuilder() // mandatory: inject device description (EID) - .eid(getDeviceDescriptionFile(DEVICE_DESCRIPTION_FILE_NAME)) + .eid(EidLoader.getDeviceDescriptionFile(DEVICE_DESCRIPTION_FILE_NAME)) // optional: inject the configuration according to the used EID (in this case required) .properties(configProperties) // optional: inject the REST mock (only for this example) @@ -155,27 +154,6 @@ public static void main(String[] args) } } - /** - * Reads the EID with the given {@code fileName}. - * - * @param fileName - * name of EID-XML file to read - * @return {InputStream} to file - * @throws FileNotFoundException - * if no EID-XML file with the given {@code fileName} exists - */ - private static InputStream getDeviceDescriptionFile(String fileName) throws FileNotFoundException - { - final var classloader = Thread.currentThread().getContextClassLoader(); - final var istr = classloader.getResourceAsStream(fileName); - - if (istr == null) - { - throw new FileNotFoundException("Unable to load device description file: " + DEVICE_DESCRIPTION_FILE_NAME); - } - - return istr; - } } @@ -330,4 +308,5 @@ public URI build() throws URISyntaxException } } + } diff --git a/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/helper/EidLoader.java b/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/helper/EidLoader.java new file mode 100644 index 0000000..74c26a1 --- /dev/null +++ b/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/helper/EidLoader.java @@ -0,0 +1,59 @@ +/* + * Copyright(c) 2024 Verein SmartGridready Switzerland + * + * This Open Source Software is BSD 3 clause licensed: + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.smartgridready.communicator.example.helper; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; + +/** + * Utility class to load a EID-XML file from the class path. + */ +public final class EidLoader +{ + + /** + * Reads the EID with the given {@code fileName}. + * + * @param fileName + * name of EID-XML file to read + * @return {InputStream} to file + * @throws FileNotFoundException + * if no EID-XML file with the given {@code fileName} exists + */ + public static InputStream getDeviceDescriptionFile(String fileName) throws IOException { + final var classloader = Thread.currentThread().getContextClassLoader(); + final var istr = classloader.getResourceAsStream(fileName); + + if (istr == null) + { + throw new FileNotFoundException("Unable to load device description file: " + fileName); + } + + return istr; + } + + /** + * Hide constructor of this utility class. + */ + private EidLoader() + { + + } +} From d84dcda6b7e5adeae38f0820907cc1504ae2713b Mon Sep 17 00:00:00 2001 From: Sandro Sabatini Date: Thu, 12 Dec 2024 10:03:07 +0100 Subject: [PATCH 04/10] refactored EnumAndBitmapSampleCommunicator to use new SGrDeviceBuilder; refactored Mock and Mockfactory to make them simpler; added copyright --- .../example/BasicSampleCommunicator.java | 46 +++--- .../BasicSampleCommunicatorClassic.java | 19 +-- .../EnumAndBitmapSampleCommunicator.java | 143 +++++++++++------- .../helper/GenDriverAPI4ModbusMock.java | 115 +++++++++----- .../helper/MockModbusClientFactory.java | 93 +++++++++--- 5 files changed, 256 insertions(+), 160 deletions(-) diff --git a/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/BasicSampleCommunicator.java b/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/BasicSampleCommunicator.java index 1d1f551..e0be630 100644 --- a/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/BasicSampleCommunicator.java +++ b/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/BasicSampleCommunicator.java @@ -1,24 +1,21 @@ -/** -*Copyright(c) 2021 Verein SmartGridready Switzerland - * -This Open Source Software is BSD 3 clause licensed: -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: -1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the distribution. -3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from - this software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR -CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED -OF THE POSSIBILITY OF SUCH DAMAGE. - -This Module includes automatically generated code, generated from SmartGridready Modus XML Schema definitions -check for "EI-Modbus" and "Generic" directories in our Namespace http://www.smartgridready.ch/ns/SGr/V0/ +/* + * Copyright(c) 2024 Verein SmartGridready Switzerland + * + * This Open Source Software is BSD 3 clause licensed: + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ -*/ package com.smartgridready.communicator.example; import java.io.IOException; @@ -40,7 +37,6 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWIS import com.smartgridready.communicator.example.helper.MockModbusClientFactory; import com.smartgridready.communicator.rest.exception.RestApiAuthenticationException; import com.smartgridready.driver.api.common.GenDriverException; -import com.smartgridready.driver.api.modbus.GenDriverAPI4ModbusFactory; /** *

@@ -65,14 +61,11 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWIS * SmartGridready Modbus device and read a value from the device. *

* The example also uses the recommended new SGrDeviceBuilder method. - * - **/ + */ public class BasicSampleCommunicator { private static final Logger LOG = LoggerFactory.getLogger(BasicSampleCommunicator.class); - private static final GenDriverAPI4ModbusFactory mockModbusFactory = new MockModbusClientFactory(); - private static final String PROFILE_VOLTAGE_AC = "VoltageAC"; private static final String DEVICE_DESCRIPTION_FILE_NAME = "SGr_04_0014_0000_WAGO_SmartMeterV0.2.1.xml"; private static final String SERIAL_PORT_NAME = "COM3"; @@ -100,8 +93,7 @@ public static void main(String[] argv) { // mandatory: inject device description (EID) .eid(EidLoader.getDeviceDescriptionFile(DEVICE_DESCRIPTION_FILE_NAME)) // optional: inject the ModbusFactory mock - .useModbusClientFactory(mockModbusFactory) -// .useSharedModbusRtu(true) + .useModbusClientFactory(new MockModbusClientFactory(false)) // optional: inject the configuration .properties(configProperties) .build(); diff --git a/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/BasicSampleCommunicatorClassic.java b/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/BasicSampleCommunicatorClassic.java index 8983088..60b6fe7 100644 --- a/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/BasicSampleCommunicatorClassic.java +++ b/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/BasicSampleCommunicatorClassic.java @@ -21,22 +21,19 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWIS */ package com.smartgridready.communicator.example; +import java.io.FileNotFoundException; +import java.net.URL; +import java.util.Properties; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import com.smartgridready.communicator.common.helper.DeviceDescriptionLoader; import com.smartgridready.communicator.example.helper.GenDriverAPI4ModbusMock; import com.smartgridready.communicator.modbus.impl.SGrModbusDevice; -import com.smartgridready.driver.api.modbus.DataBits; import com.smartgridready.driver.api.modbus.GenDriverAPI4Modbus; -import com.smartgridready.driver.api.modbus.Parity; -import com.smartgridready.driver.api.modbus.StopBits; import com.smartgridready.ns.v0.DeviceFrame; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.FileNotFoundException; -import java.net.URL; -import java.util.Properties; - /** *

@@ -92,7 +89,7 @@ public static void main(String[] argv) { // - GenDriverAPI4Modbus mbTCP = new GenDriverAPI4ModbusTCP("127.0.0.1", 502) // - GenDriverAPI4Modbus mbRTU = new GenDriverAPI4ModbusRTU("COM1") // - GenDriverAPI4Modbus mbRTUMock = new GenDriverAPI4ModbusMock(SERIAL_PORT_NAME, 9600, Parity.EVEN, DataBits.EIGHT, StopBits.ONE); + GenDriverAPI4Modbus mbRTUMock = new GenDriverAPI4ModbusMock(false); // Step 2 (Modbus RTU only): // Initialise the serial COM port used by the modbus transport service. diff --git a/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/EnumAndBitmapSampleCommunicator.java b/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/EnumAndBitmapSampleCommunicator.java index 7f5e5e7..1e14089 100644 --- a/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/EnumAndBitmapSampleCommunicator.java +++ b/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/EnumAndBitmapSampleCommunicator.java @@ -1,24 +1,36 @@ +/* + * Copyright(c) 2024 Verein SmartGridready Switzerland + * + * This Open Source Software is BSD 3 clause licensed: + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + package com.smartgridready.communicator.example; -import com.smartgridready.ns.v0.DeviceFrame; -import com.smartgridready.driver.api.modbus.DataBits; -import com.smartgridready.driver.api.modbus.GenDriverAPI4Modbus; -import com.smartgridready.driver.api.modbus.Parity; -import com.smartgridready.driver.api.modbus.StopBits; -import com.smartgridready.communicator.common.api.values.BitmapValue; -import com.smartgridready.communicator.common.api.values.EnumRecord; -import com.smartgridready.communicator.common.api.values.EnumValue; -import com.smartgridready.communicator.common.api.values.Value; -import com.smartgridready.communicator.common.helper.DeviceDescriptionLoader; -import com.smartgridready.communicator.example.helper.GenDriverAPI4ModbusMock; -import com.smartgridready.communicator.modbus.impl.SGrModbusDevice; +import java.io.IOException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.FileNotFoundException; -import java.net.URL; -import java.util.Map; +import com.smartgridready.communicator.common.api.GenDeviceApi; +import com.smartgridready.communicator.common.api.SGrDeviceBuilder; +import com.smartgridready.communicator.common.api.values.BitmapValue; +import com.smartgridready.communicator.common.api.values.EnumValue; +import com.smartgridready.communicator.example.helper.EidLoader; +import com.smartgridready.communicator.example.helper.MockModbusClientFactory; +import com.smartgridready.communicator.rest.exception.RestApiAuthenticationException; +import com.smartgridready.driver.api.common.GenDriverException; /** * This class provides examples on how to handle enumerations and bitmaps, @@ -36,25 +48,42 @@ public class EnumAndBitmapSampleCommunicator { private static final String HEAT_PUMP_OP_STATE = "HPOpState"; private static final String DEVICE_DESCRIPTION_FILE_NAME = "SampleExternalInterfaceFile.xml"; - private static final String SERIAL_PORT_NAME = "COM3"; - - public static void main(String[] argv) { - - try { - // Prepare the communication handler (SGrModbusDevice) for usage: - // See 'BasicSampleCommunicator' for details. - - // load device description - String deviceDescFilePath = getDeviceDescriptionFilePath(); - DeviceDescriptionLoader loader = new DeviceDescriptionLoader(); - DeviceFrame sgcpMeter = loader.load("", deviceDescFilePath); - - // initialize transport - GenDriverAPI4Modbus mbRTUMock = createMockModbusDriver(SERIAL_PORT_NAME, 9600, Parity.EVEN, DataBits.EIGHT, StopBits.ONE); - mbRTUMock.connect(); - // create device instance - SGrModbusDevice sgcpDevice = new SGrModbusDevice(sgcpMeter, mbRTUMock); + public static void main(String[] argv) + { + // Use the SGrDeviceBuilder class to load the device description (EID) from + // an XML file, input stream or text content. + // Create the SGr device instance by calling build(). + // + // This example uses a mocked Modbus driver factory to create the driver instance. + // You may change the factory implementation or just use the default, in order to + // create actual Modbus devices with serial or TCP connection. + // + GenDeviceApi sgcpDevice; + + try + { + sgcpDevice = new SGrDeviceBuilder() + // mandatory: inject device description (EID) + .eid(EidLoader.getDeviceDescriptionFile(DEVICE_DESCRIPTION_FILE_NAME)) + // optional: inject the ModbusFactory mock + .useModbusClientFactory(new MockModbusClientFactory(true)) + .build(); + } + catch ( GenDriverException | RestApiAuthenticationException | IOException e ) + { + LOG.error("Error loading device description. ", e); + return; + } + + try + { + // Connect the device instance. Initializes the attached transport. + // In case of Modbus RTU this initializes the COM port. + // In case of Modbus TCP this initializes the TCP connection. + // In case of messaging this connects to the MQTT broker. + // + sgcpDevice.connect(); // Now we can write set status commands using enum and bitmap values. @@ -69,13 +98,13 @@ public static void main(String[] argv) { LOG.info("Did set HPOpModeCmd to 'WP_DOM_WATER_OP'"); // To read back an enum value use getVal(...).getEnum() which returns an enum record. - EnumRecord opState = sgcpDevice.getVal(HEAT_PUMP_BASE_PROFILE, HEAT_PUMP_OP_CMD).getEnum(); + final var opState = sgcpDevice.getVal(HEAT_PUMP_BASE_PROFILE, HEAT_PUMP_OP_CMD).getEnum(); LOG.info("OP-State literal={}", opState.getLiteral()); LOG.info("OP-State ordinal={}", opState.getOrdinal()); LOG.info("OP-State description={}", opState.getDescription()); // You can also use Value.getString() and Value.toString() - Value opStateVal = sgcpDevice.getVal(HEAT_PUMP_BASE_PROFILE, HEAT_PUMP_OP_CMD); + final var opStateVal = sgcpDevice.getVal(HEAT_PUMP_BASE_PROFILE, HEAT_PUMP_OP_CMD); LOG.info("OP-State EnumValue.getString() = {}", opStateVal.getString()); LOG.info("OP-State EnumValue.toString() = {}", opStateVal); @@ -84,7 +113,7 @@ public static void main(String[] argv) { // The next command reads the heat pump operation state. Within the EI-XML, the operation state is defined as bitmap. // Use getVel(...).getBitmap() to read bitmaps from the device. The result is a Map that contains the literals of all // bits in the bitmap and an according boolean value whether the bit is set or not. - Map bitmap = sgcpDevice.getVal(HEAT_PUMP_BASE_PROFILE, HEAT_PUMP_OP_STATE).getBitmap(); + final var bitmap = sgcpDevice.getVal(HEAT_PUMP_BASE_PROFILE, HEAT_PUMP_OP_STATE).getBitmap(); LOG.info("OP-State bitmap values read:"); bitmap.forEach((literal, isBitSet) -> LOG.info("\t{} = {}", literal, isBitSet)); @@ -99,31 +128,29 @@ public static void main(String[] argv) { sgcpDevice.setVal(HEAT_PUMP_BASE_PROFILE, HEAT_PUMP_OP_STATE, BitmapValue.of(bitmap)); // You can also use Value.getString() and Value.toString() to determine the status of the bitmap: - Value bitmapValue = sgcpDevice.getVal(HEAT_PUMP_BASE_PROFILE, HEAT_PUMP_OP_STATE); + final var bitmapValue = sgcpDevice.getVal(HEAT_PUMP_BASE_PROFILE, HEAT_PUMP_OP_STATE); LOG.info("OP-State BitmapValue.getString() = {}", bitmapValue.getString()); LOG.info("OP-State BitmapValue.toString() = {}", bitmapValue); - - // close transport - mbRTUMock.disconnect(); - } catch (Exception e) { - LOG.error("Error running EnumAndBitmapSampleCommunicator: {}", e.getMessage()); } - } - - - static String getDeviceDescriptionFilePath() throws FileNotFoundException { - ClassLoader classloader = Thread.currentThread().getContextClassLoader(); - URL deviceDesc = classloader.getResource(DEVICE_DESCRIPTION_FILE_NAME); - if (deviceDesc != null && deviceDesc.getPath() != null) { - return deviceDesc.getPath(); - } else { - throw new FileNotFoundException("Unable to load device description file: " + DEVICE_DESCRIPTION_FILE_NAME); + catch (Exception e) + { + LOG.error("Error accessing device. ", e); + } + finally + { + // Disconnect from device instance. Closes the attached transport. + if (sgcpDevice.isConnected()) + { + try + { + LOG.info("Disconnecting ..."); + sgcpDevice.disconnect(); + } + catch ( GenDriverException e ) + { + LOG.error("Error disconnecting device.", e); + } + } } - } - - static GenDriverAPI4Modbus createMockModbusDriver(String comPort, int baudRate, Parity parity, DataBits dataBits, StopBits stopBits) { - GenDriverAPI4ModbusMock mbRTUMock = new GenDriverAPI4ModbusMock(comPort, baudRate, parity, dataBits, stopBits); - mbRTUMock.setIsIntegerType(true); - return mbRTUMock; } } diff --git a/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/helper/GenDriverAPI4ModbusMock.java b/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/helper/GenDriverAPI4ModbusMock.java index 0ce9fbd..017bfb0 100644 --- a/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/helper/GenDriverAPI4ModbusMock.java +++ b/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/helper/GenDriverAPI4ModbusMock.java @@ -1,87 +1,111 @@ +/* + * Copyright(c) 2024 Verein SmartGridready Switzerland + * + * This Open Source Software is BSD 3 clause licensed: + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + package com.smartgridready.communicator.example.helper; import java.nio.IntBuffer; import com.smartgridready.driver.api.common.GenDriverException; -import com.smartgridready.driver.api.modbus.DataBits; import com.smartgridready.driver.api.modbus.GenDriverAPI4Modbus; -import com.smartgridready.driver.api.modbus.Parity; -import com.smartgridready.driver.api.modbus.StopBits; - -public class GenDriverAPI4ModbusMock implements GenDriverAPI4Modbus { - private static final int[] REGISTER_INT_VAL = new int[]{0x00000000, 0x00000005}; +/** + * Mock for a {@code GenDriverAPI4Modbus}. + */ +public class GenDriverAPI4ModbusMock implements GenDriverAPI4Modbus +{ + private static final int[] REGISTER_INT_VAL = new int[] { 0x00000000, 0x00000005 }; - private static final int[] REGISTER_FLOAT_VAL = new int[]{0x0000435c, 0x000051ec}; + private static final int[] REGISTER_FLOAT_VAL = new int[] { 0x0000435c, 0x000051ec }; - private boolean returnInteger; + private final boolean returnInteger; + private boolean isConnected = false; - public void setIsIntegerType(boolean returnInteger) { + /** + * Constructor. + * + * @param returnInteger + * indicates whether this mock returns integer {@code true} or float {@code false} values + */ + public GenDriverAPI4ModbusMock(boolean returnInteger) + { this.returnInteger = returnInteger; } - public GenDriverAPI4ModbusMock() {} - - public GenDriverAPI4ModbusMock(String comPort) {} - - public GenDriverAPI4ModbusMock(String comPort, int baudRate) {} - - public GenDriverAPI4ModbusMock(String comPort, int baudRate, Parity parity) {} - - public GenDriverAPI4ModbusMock(String comPort, int baudRate, Parity parity, DataBits dataBits) {} - - public GenDriverAPI4ModbusMock(String comPort, int baudRate, Parity parity, DataBits dataBits, StopBits stopBits) {} - @Override - public int[] ReadInputRegisters(int startingAddress, int quantity) { + public int[] ReadInputRegisters(int startingAddress, int quantity) + { return prepareReturnValue(quantity); } @Override - public int[] ReadHoldingRegisters(int startingAddress, int quantity) { + public int[] ReadHoldingRegisters(int startingAddress, int quantity) + { return prepareReturnValue(quantity); } @Override - public void disconnect() { + public void disconnect() + { isConnected = false; } @Override - public boolean[] ReadDiscreteInputs(int startingAddress, int quantity) { + public boolean[] ReadDiscreteInputs(int startingAddress, int quantity) + { throw new UnsupportedOperationException("mocking not implemented yet"); } @Override - public boolean[] ReadCoils(int startingAddress, int quantity) { + public boolean[] ReadCoils(int startingAddress, int quantity) + { throw new UnsupportedOperationException("mocking not implemented yet"); } @Override - public void WriteMultipleCoils(int startingAdress, boolean[] values) { + public void WriteMultipleCoils(int startingAdress, boolean[] values) + { // implementation not required yet } - @Override - public void WriteSingleCoil(int startingAdress, boolean value) { + public void WriteSingleCoil(int startingAdress, boolean value) + { // implementation not required yet } @Override - public void WriteMultipleRegisters(int startingAdress, int[] values) { + public void WriteMultipleRegisters(int startingAdress, int[] values) + { // implementation not required yet } @Override - public void WriteSingleRegister(int startingAdress, int value) { + public void WriteSingleRegister(int startingAdress, int value) + { // implementation not required yet } @Override - public boolean connect() throws GenDriverException { - if (isConnected) { + public boolean connect() throws GenDriverException + { + if (isConnected) + { throw new GenDriverException("Do not connect twice"); } @@ -89,27 +113,34 @@ public boolean connect() throws GenDriverException { return isConnected; } - private int[] prepareReturnValue(int quantity) { - - int[] registers = returnInteger ? REGISTER_INT_VAL:REGISTER_FLOAT_VAL; + private int[] prepareReturnValue(int quantity) + { + final var registers = returnInteger ? REGISTER_INT_VAL : REGISTER_FLOAT_VAL; int[] result; - if (quantity==1) { + + if (quantity == 1) + { result = new int[1]; System.arraycopy(registers, 1, result, 0, 1); return result; - } else { + } + else + { IntBuffer buffer = IntBuffer.allocate(quantity); - for (int i=0; i Date: Thu, 12 Dec 2024 10:30:23 +0100 Subject: [PATCH 05/10] unifying, formatting --- .../example/BasicSampleCommunicator.java | 10 +-- .../EnumAndBitmapSampleCommunicator.java | 6 +- .../example/RestSampleCommunicator.java | 8 +- .../example/WagoSmartMeterCommunicator.java | 86 +++++++++---------- .../example/helper/EidLoader.java | 5 +- 5 files changed, 53 insertions(+), 62 deletions(-) diff --git a/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/BasicSampleCommunicator.java b/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/BasicSampleCommunicator.java index e0be630..2cbc51a 100644 --- a/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/BasicSampleCommunicator.java +++ b/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/BasicSampleCommunicator.java @@ -62,16 +62,16 @@ *

* The example also uses the recommended new SGrDeviceBuilder method. */ -public class BasicSampleCommunicator { - +public class BasicSampleCommunicator +{ private static final Logger LOG = LoggerFactory.getLogger(BasicSampleCommunicator.class); private static final String PROFILE_VOLTAGE_AC = "VoltageAC"; private static final String DEVICE_DESCRIPTION_FILE_NAME = "SGr_04_0014_0000_WAGO_SmartMeterV0.2.1.xml"; private static final String SERIAL_PORT_NAME = "COM3"; - public static void main(String[] argv) { - + public static void main(String[] argv) + { // Step 1: // Use the SGrDeviceBuilder class to load the device description (EID) from // an XML file, input stream or text content. @@ -91,7 +91,7 @@ public static void main(String[] argv) { { sgcpDevice = new SGrDeviceBuilder() // mandatory: inject device description (EID) - .eid(EidLoader.getDeviceDescriptionFile(DEVICE_DESCRIPTION_FILE_NAME)) + .eid(EidLoader.getDeviceDescriptionInputStream(DEVICE_DESCRIPTION_FILE_NAME)) // optional: inject the ModbusFactory mock .useModbusClientFactory(new MockModbusClientFactory(false)) // optional: inject the configuration diff --git a/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/EnumAndBitmapSampleCommunicator.java b/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/EnumAndBitmapSampleCommunicator.java index 1e14089..ac11bdb 100644 --- a/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/EnumAndBitmapSampleCommunicator.java +++ b/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/EnumAndBitmapSampleCommunicator.java @@ -39,8 +39,8 @@ * The program uses a mocked modbus driver and can be run without an attached device/product. * All configuration parameters of the EID are hard-coded, therefore no configuration properties need to be set. */ -public class EnumAndBitmapSampleCommunicator { - +public class EnumAndBitmapSampleCommunicator +{ private static final Logger LOG = LoggerFactory.getLogger(EnumAndBitmapSampleCommunicator.class); private static final String HEAT_PUMP_BASE_PROFILE = "HeatPumpBase"; @@ -65,7 +65,7 @@ public static void main(String[] argv) { sgcpDevice = new SGrDeviceBuilder() // mandatory: inject device description (EID) - .eid(EidLoader.getDeviceDescriptionFile(DEVICE_DESCRIPTION_FILE_NAME)) + .eid(EidLoader.getDeviceDescriptionInputStream(DEVICE_DESCRIPTION_FILE_NAME)) // optional: inject the ModbusFactory mock .useModbusClientFactory(new MockModbusClientFactory(true)) .build(); diff --git a/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/RestSampleCommunicator.java b/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/RestSampleCommunicator.java index 10e433f..b2fd4fc 100644 --- a/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/RestSampleCommunicator.java +++ b/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/RestSampleCommunicator.java @@ -95,7 +95,7 @@ public static void main(String[] args) // Create the SGr device instance by calling build(). sgcpDevice = new SGrDeviceBuilder() // mandatory: inject device description (EID) - .eid(EidLoader.getDeviceDescriptionFile(DEVICE_DESCRIPTION_FILE_NAME)) + .eid(EidLoader.getDeviceDescriptionInputStream(DEVICE_DESCRIPTION_FILE_NAME)) // optional: inject the configuration according to the used EID (in this case required) .properties(configProperties) // optional: inject the REST mock (only for this example) @@ -200,10 +200,8 @@ public GenHttpResponse execute() throws IOException // this device sends always all parameters var response = MessageFormat.format( - "'{'" - + "\"tmp\" : '{' \"value\" : {0} '}'," - + "\"target_t\" : '{' \"value\" : {1} '}'" - + "'}'", + "'{' \"tmp\" : '{' \"value\" : {0} '}', " + + "\"target_t\" : '{' \"value\" : {1} '}' '}'", lastSetTargetTemperature, lastSetTargetTemperature); return GenHttpResponse.of(response); } diff --git a/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/WagoSmartMeterCommunicator.java b/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/WagoSmartMeterCommunicator.java index 630800b..ac9c324 100644 --- a/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/WagoSmartMeterCommunicator.java +++ b/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/WagoSmartMeterCommunicator.java @@ -1,62 +1,58 @@ -/** -*Copyright(c) 2022-2024 Verein SmartGridready Switzerland -This Open Source Software is BSD 3 clause licensed: -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: -1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the distribution. -3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from - this software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR -CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED -OF THE POSSIBILITY OF SUCH DAMAGE. - -This Module references automatically generated code, generated from SmartGridready Modbus XML Schema definitions -check for "EI-Modbus" and "Generic" directories in our Namespace http://www.smartgridready.ch/ns/SGr/V0/ - -author: IBT/cb, FHNW/mkr -*/ +/* + * Copyright(c) 2024 Verein SmartGridready Switzerland + * + * This Open Source Software is BSD 3 clause licensed: + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ package com.smartgridready.communicator.example; -import com.smartgridready.communicator.common.api.GenDeviceApi; -import com.smartgridready.communicator.common.api.SGrDeviceBuilder; - -import java.io.InputStream; import java.util.Properties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.smartgridready.communicator.common.api.SGrDeviceBuilder; +import com.smartgridready.communicator.example.helper.EidLoader; + /** - * This class provides an example on how to communicate with a WAGO smart meter - * over Modbus RTU (RS-485), using the current SmartGridready commhandler library. - *
- * The device is instantiated the new fashioned way, using the device builder. - * A shared Modbus driver registry is used in order to support multiple SGr devices on the same serial connection. - *
+ * This class provides an example on how to communicate with a WAGO smart meter over Modbus RTU (RS-485), + * using the current SmartGridready commhandler library. + *

+ * The device is instantiated the new fashioned way, using the device builder. A shared Modbus driver + * registry is used in order to support multiple SGr devices on the same serial connection. + *

* The program requires an actual serial port and an attached device. */ -public class WagoSmartMeterCommunicator { - +public class WagoSmartMeterCommunicator +{ private static final Logger LOG = LoggerFactory.getLogger(WagoSmartMeterCommunicator.class); private static final String DEVICE_DESCRIPTION_FILE_NAME = "SGr_04_0014_0000_WAGO_SmartMeterV0.2.1.xml"; private static final String SERIAL_PORT_NAME = "COM3"; - public static void main(String[] argv) { - - try { + public static void main(String[] argv) + { + try + { // configuration placeholders to be replaced in EID - Properties configProperties = new Properties(); + final var configProperties = new Properties(); configProperties.setProperty("serial_port", SERIAL_PORT_NAME); - GenDeviceApi device = new SGrDeviceBuilder() + final var device = new SGrDeviceBuilder() .useSharedModbusRtu(true) - .eid(getDeviceDescriptionFileStream()) + .eid(EidLoader.getDeviceDescriptionInputStream(DEVICE_DESCRIPTION_FILE_NAME)) .properties(configProperties) .build(); @@ -66,19 +62,15 @@ public static void main(String[] argv) { LOG.info("Device-interface {}", device.getDeviceInfo().getInterfaceType()); // Read the values from all data points and log them - var deviceData = device.getDeviceInfo().getValues(); + final var deviceData = device.getDeviceInfo().getValues(); deviceData.forEach(dataPointValue -> LOG.info(dataPointValue.toString())); // close transport device.disconnect(); - } catch (Exception e) { + } + catch (Exception e) + { LOG.error("Error loading device description.", e); } } - - private static InputStream getDeviceDescriptionFileStream() { - ClassLoader classloader = Thread.currentThread().getContextClassLoader(); - return classloader.getResourceAsStream(DEVICE_DESCRIPTION_FILE_NAME); - } - } diff --git a/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/helper/EidLoader.java b/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/helper/EidLoader.java index 74c26a1..e57850b 100644 --- a/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/helper/EidLoader.java +++ b/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/helper/EidLoader.java @@ -29,7 +29,7 @@ public final class EidLoader { /** - * Reads the EID with the given {@code fileName}. + * Returns an {@code InputStream} to the EID-XML file with the given {@code fileName}. * * @param fileName * name of EID-XML file to read @@ -37,7 +37,8 @@ public final class EidLoader * @throws FileNotFoundException * if no EID-XML file with the given {@code fileName} exists */ - public static InputStream getDeviceDescriptionFile(String fileName) throws IOException { + public static InputStream getDeviceDescriptionInputStream(String fileName) throws IOException + { final var classloader = Thread.currentThread().getContextClassLoader(); final var istr = classloader.getResourceAsStream(fileName); From a93a1aa012baa63d2be89d0797638e046ee03b2d Mon Sep 17 00:00:00 2001 From: Sandro Sabatini Date: Thu, 12 Dec 2024 13:34:24 +0100 Subject: [PATCH 06/10] added sample for MQTT --- .../example/MqttSampleCommunicator.java | 249 ++++ .../SGr_02_mmmmm_dddd_WagoTestsystem_MQTT.xml | 1196 +++++++++++++++++ 2 files changed, 1445 insertions(+) create mode 100644 SampleCommunicator/src/main/java/com/smartgridready/communicator/example/MqttSampleCommunicator.java create mode 100644 SampleCommunicator/src/main/resources/SGr_02_mmmmm_dddd_WagoTestsystem_MQTT.xml diff --git a/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/MqttSampleCommunicator.java b/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/MqttSampleCommunicator.java new file mode 100644 index 0000000..4ffebbe --- /dev/null +++ b/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/MqttSampleCommunicator.java @@ -0,0 +1,249 @@ +/* + * Copyright(c) 2024 Verein SmartGridready Switzerland + * + * This Open Source Software is BSD 3 clause licensed: + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.smartgridready.communicator.example; + +import java.io.IOException; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.smartgridready.communicator.common.api.GenDeviceApi; +import com.smartgridready.communicator.common.api.SGrDeviceBuilder; +import com.smartgridready.communicator.common.api.values.Float32Value; +import com.smartgridready.communicator.example.helper.EidLoader; +import com.smartgridready.communicator.rest.exception.RestApiAuthenticationException; +import com.smartgridready.driver.api.common.GenDriverException; +import com.smartgridready.driver.api.messaging.GenMessagingClient; +import com.smartgridready.driver.api.messaging.GenMessagingClientFactory; +import com.smartgridready.driver.api.messaging.MessageFilterHandler; +import com.smartgridready.driver.api.messaging.model.Message; +import com.smartgridready.driver.api.messaging.model.MessagingInterfaceDescription; +import com.smartgridready.driver.api.messaging.model.MessagingPlatformType; + +import io.vavr.control.Either; + +/** + * This is a sample implementation of communicator that uses the SmartGridready communication handler. + *

+ * The communication handler uses the SmartGridready generic interface to communicate with any attached + * device/product in a generic way. The communication handler converts the generic API commands to + * commands understood by the external interface of the device/product. + *

+ * The command translation is done by the communication handler based on a device description loaded from + * a device description XML file. + *

+ * There are several communication protocols/technologies available to communicate with the device: + *

    + *
  • Modbus RTU
  • + *
  • Modbus TCP
  • + *
  • http / REST Swagger
  • + *
  • MQTT
  • + *
+ * The communicator is responsible of loading the appropriate descriptions and parameters of the attached + * devices/products. + *

+ * The example shows the basic steps to set up the communicator to talk to a simple SmartGridready MQTT + * device and read/writes a value from/to the device. + *

+ * The example also uses the recommended new SGrDeviceBuilder method. + */ +public class MqttSampleCommunicator +{ + private static final Logger LOG = LoggerFactory.getLogger(MqttSampleCommunicator.class); + + /** This example is tied to this EID-XML. */ + private static final String DEVICE_DESCRIPTION_FILE_NAME = "SGr_02_mmmmm_dddd_WagoTestsystem_MQTT.xml"; + + + public static void main(String[] args) + { + // This example uses a mocked MQTT driver factory to create the driver instance. + // You may change the factory implementation or just use the default, in order to + // create actual MQTT devices. + + GenDeviceApi sgcpDevice; + + try + { + // Use the SGrDeviceBuilder class to load the device description (EID) from + // an XML file, input stream or text content. + // No configuration, taking defaults for mock. + // Create the SGr device instance by calling build(). + sgcpDevice = new SGrDeviceBuilder() + // mandatory: inject device description (EID) + .eid(EidLoader.getDeviceDescriptionInputStream(DEVICE_DESCRIPTION_FILE_NAME)) + // optional: inject the MQTT mock (only for this example) + .useMessagingClientFactory(new MockMessagingClientFactory(), MessagingPlatformType.MQTT5) + .build(); + } + catch (GenDriverException | RestApiAuthenticationException | IOException e) + { + LOG.error("Error loading device description. ", e); + return; + } + + try + { + // Connect the device instance. Initializes the attached transport. + LOG.info("Connecting ..."); + sgcpDevice.connect(); + + final var PROFILE_DC_OUT_1 = "VoltageDC_OUT_1"; + final var DATA_POINT_DC = "VoltageDC"; + + if (sgcpDevice.canSubscribe()) + { + LOG.info("Subscribing ..."); + sgcpDevice.subscribe(PROFILE_DC_OUT_1, DATA_POINT_DC, result -> { + if (result.isRight()) LOG.info("Got result: {}", result.get()); + else LOG.info("Got error: {}", result.getLeft()); + }); + } + + // Read the current voltage + final var voltageDc1 = sgcpDevice.getVal(PROFILE_DC_OUT_1, DATA_POINT_DC); + LOG.info("Current VoltageDC is '{}'", voltageDc1.getFloat32()); + + // Set the current voltage + LOG.info("Increasing VoltageDC by 10"); + sgcpDevice.setVal(PROFILE_DC_OUT_1, DATA_POINT_DC, Float32Value.of( voltageDc1.getFloat32() + 10 )); + + final var voltageDc1New = sgcpDevice.getVal(PROFILE_DC_OUT_1, DATA_POINT_DC); + LOG.info("Current VoltageDC is now '{}'", voltageDc1New.getFloat32()); + + if (sgcpDevice.canSubscribe()) + { + LOG.info("Unsubscribing ..."); + sgcpDevice.unsubscribe(PROFILE_DC_OUT_1, DATA_POINT_DC); + } + } + catch (Exception e) + { + LOG.error("Error accessing device. ", e); + } + finally + { + // Disconnect from device instance. Closes the attached transport. + if (sgcpDevice.isConnected()) + { + try + { + LOG.info("Disconnecting ..."); + sgcpDevice.disconnect(); + } + catch (GenDriverException e) + { + LOG.error("Error disconnecting device.", e); + } + } + } + } +} + +/** + * Mock of a MQTT client factory for the EID-XML "SGr_02_mmmmm_dddd_WagoTestsystem_MQTT.xml". + */ +class MockMessagingClientFactory implements GenMessagingClientFactory +{ + + @Override + public GenMessagingClient create(MessagingInterfaceDescription interfaceDescription) + { + return new MqttMessagingClient(); + } + + @Override + public Set getSupportedPlatforms() + { + return Set.of(MessagingPlatformType.MQTT5); + } + + /** + * Mock of a MQTT MessagingClientMock. + */ + static class MqttMessagingClient implements GenMessagingClient + { + private static final Logger LOG = LoggerFactory.getLogger(MqttMessagingClient.class); + + private static final Double INITIAL_VOLTAGE = 22.2; + private Double currentVoltage = INITIAL_VOLTAGE; + + private Consumer> callback; + + @Override + public void close() throws IOException + { + LOG.debug("closing ..."); + } + + @Override + public void sendSync(String topic, Message message) + { + LOG.debug("sendSync for topic '{}', message.payload is '{}'", topic, message.getPayload()); + currentVoltage = Double.parseDouble(message.getPayload()); + + if (callback != null) + { + callback.accept(Either.right(message)); + } + } + + @Override + public Either readSync(String readCmdMessageTopic, + Message readCmdMessage, + String inMessageTopic, + MessageFilterHandler messageFilterHandler, + long timeoutMs) + { + LOG.debug("readSync for topic '{}'", readCmdMessageTopic); + return Either.right(Message.of(currentVoltage.toString())); + } + + @Override + public CompletableFuture> sendAsynch(String topic, Message message) + { + // REMARK: unused in this example + LOG.debug("sendAsynch for topic '{}', message.payload is '{}'", topic, message.getPayload()); + return CompletableFuture.supplyAsync( () -> + { + sendSync(topic, message); + return Either.right(null); + } ); + } + + @Override + public void subscribe(String topic, + MessageFilterHandler messageFilterHandler, + Consumer> callback) throws GenDriverException + { + LOG.debug("subscribing to topic '{}'", topic); + this.callback = callback; + } + + @Override + public void unsubscribe(String topic) throws GenDriverException + { + LOG.debug("unsubscribing from topic '{}'", topic); + this.callback = null; + } + } +} diff --git a/SampleCommunicator/src/main/resources/SGr_02_mmmmm_dddd_WagoTestsystem_MQTT.xml b/SampleCommunicator/src/main/resources/SGr_02_mmmmm_dddd_WagoTestsystem_MQTT.xml new file mode 100644 index 0000000..4279b7d --- /dev/null +++ b/SampleCommunicator/src/main/resources/SGr_02_mmmmm_dddd_WagoTestsystem_MQTT.xml @@ -0,0 +1,1196 @@ + + + + WAGOMeter Testsystem V1.0 + WAGO + 0 + + Draft + + + + WAGO Testsystem + WAGO + + + The WAGO testsystem provides analog and digital outputs and inputs that allows automated and manual + testing of SmartGridready communication-handler implementations for modbus. + en + https://www.wago.com/ + + + Das WAGO Testsystem stellt analoge und digitale Ausgänge und Eingänge zur Verfügung die + es ermöglichen einen SmartGridready Communication-Handler für Modbus zu testen. + de + + https://www.wago.com/ + + + The WAGO testsystem provides analog and digital outputs and inputs that allows automated and manual + testing of SmartGridready communication-handler implementations for modbus. + it + + https://www.wago.com/ + + Generic + true + 1.0.0 + 1.0.0 + mains1Phase + 4 Watt + 4 + + + + broker_host + + + + localhost + + Message broker hostname or IP address. + en + + + + Message-Broker Hostname oder IP-Adresse. + de + + + + + broker_port + + + + 1883 + + Message broker TCP port. + en + + + + Message-Broker TCP-Port. + de + + + + + broker_tls + + + + true + + Message broker TLS. + en + + + + Message-Broker TLS. + de + + + + + broker_tls_verify + + + + false + + Message broker TLS certificate verification. + en + + + + Message-Broker TLS Zertifikatsprüfung. + de + + + + + broker_username + + + + + Message broker user name. + en + + + + Message-Broker User-Name. + de + + + + + broker_password + + + + + Message broker password. + en + + + + Message-Broker Passwort. + de + + + + + device_id + + + + shellyht + + Device identifier used in MQTT topic. + en + + + + Device-Identifier, in MQTT-Topic verwendet. + de + + + + + + + + MQTT5 + + + {{broker_host}} + {{broker_port}} + {{broker_tls}} + {{broker_tls_verify}} + + + + + {{broker_username}} + {{broker_password}} + + + + + + + VoltageDC_IN_1 + + 0 + Metering + VoltageDC + 4 + + 0 + 2 + 3 + + + + An analog input for 0-24V + de + + + + + + VoltageDC + R + + + + VOLTS + 0 + 24 + 0.00073244422742 + + VoltageDC + + + + + + + + actuator/analgue/voltage_dc_in1 + + + + actuator/anaolgue/voltage_dc_out1 + + + + actuator/analogue/voltage_dc_out1 + + + sensorId + 1 + + + + JMESPathExpression + [0].value + + + + + + + + + VoltageDC_IN_2 + + 0 + Metering + VoltageDC + 4 + + 0 + 2 + 3 + + + + An analog input for 0-24V + de + + + + + + VoltageDC + R + + + + VOLTS + 0 + 24 + 0.00073244422742 + + VoltageDC + + + + + + + + sensors/analogue/voltage_dc_in2 + + + + sensors/voltage/voltage_dc_in2 + + + + + + + + VoltageDC_IN_3 + + 0 + Metering + VoltageDC + 4 + + 0 + 2 + 3 + + + + An analog input for 0-24V + de + + + + + + VoltageDC + R + + + + VOLTS + 0 + 24 + 0.00073244422742 + + VoltageDC + + + + + + + + sensors/analogue/voltage_dc_in3 + + + + sensors/voltage/voltage_dc_in3 + + + + + + + + VoltageDC_IN_4 + + 0 + Metering + VoltageDC + 4 + + 0 + 2 + 3 + + + + An analog input for 0-24V + de + + + + + + VoltageDC + R + + + + VOLTS + 0 + 24 + 0.00073244422742 + + VoltageDC + + + + + + + + sensors/analogue/voltage_dc_in4 + + + + sensors/voltage/voltage_dc_in4 + + + + + + + + VoltageDC_IN_5 + + 0 + Metering + VoltageDC + 4 + + 0 + 2 + 3 + + + + An analog input for 0-24V + de + + + + + + VoltageDC + R + + + + VOLTS + 0 + 24 + 0.00073244422742 + + VoltageDC + + + + + + + + sensors/analogue/voltage_dc_in5 + + + + sensors/voltage/voltage_dc_in5 + + + + + + + + VoltageDC_IN_6 + + 0 + Metering + VoltageDC + 4 + + 0 + 2 + 3 + + + + An analog input for 0-24V + de + + + + + + VoltageDC + R + + + + VOLTS + 0 + 24 + 0.00073244422742 + + VoltageDC + + + + + + + + sensors/analogue/voltage_dc_in6 + + + + sensors/voltage/voltage_dc_in6 + + + + + + + + VoltageDC_IN_7 + + 0 + Metering + VoltageDC + 4 + + 0 + 2 + 3 + + + + An analog input for 0-24V + de + + + + + + VoltageDC + R + + + + VOLTS + 0 + 24 + 0.00073244422742 + + VoltageDC + + + + + + + + sensors/analogue/voltage_dc_in7 + + + + sensors/voltage/voltage_dc_in7 + + + + + + + + VoltageDC_IN_8 + + 0 + Metering + VoltageDC + 4 + + 0 + 2 + 3 + + + + An analog input for 0-24V + de + + + + + + VoltageDC + R + + + + VOLTS + 0 + 24 + 0.00073244422742 + + VoltageDC + + + + + + + + sensors/analogue/voltage_dc_in8 + + + + sensors/voltage/voltage_dc_in8 + + + + + + + + VoltageDC_OUT_1 + + 0 + Actuator + VoltageDC + 4 + + 0 + 2 + 3 + + + + + + + VoltageDC + RW + + + + VOLTS + 0 + 24 + 0.00073244422742 + + + + + + + actuators/voltage_dc_out1 + + + + actuators/voltage_dc_out1 + + + + actuators/voltage_dc_out1 + + + + + + + + VoltageDC_OUT_2 + + 0 + Actuator + VoltageDC + 4 + + 0 + 2 + 3 + + + + + + + VoltageDC + RW + + + + VOLTS + 0.00073244422742 + + + + + + + actuators/voltage_dc_out2 + + + + actuators/voltage_dc_out2 + + + + actuators/voltage_dc_out2 + + + + + + + + VoltageDC_OUT_3 + + 0 + Actuator + VoltageDC + 4 + + 0 + 2 + 3 + + + + + + + VoltageDC + RW + + + + VOLTS + 0 + 24 + 0.00073244422742 + + + + + + + actuators/voltage_dc_out3 + + + + actuators/voltage_dc_out3 + + + + actuators/voltage_dc_out3 + + + + + + + + VoltageDC_OUT_4 + + 0 + Actuator + VoltageDC + 4 + + 0 + 2 + 3 + + + + + + + VoltageDC + RW + + + + VOLTS + 0 + 24 + 0.00073244422742 + + + + + + + actuators/voltage_dc_out4 + + + + actuators/voltage_dc_out4 + + + + actuators/voltage_dc_out4 + + + + + + + + VoltageDC_OUT_5 + + 0 + Actuator + VoltageDC + 4 + + 0 + 2 + 3 + + + + + + + VoltageDC + RW + + + + VOLTS + 0 + 24 + 0.00073244422742 + + + + + + + actuators/voltage_dc_out5 + + + + actuators/voltage_dc_out5 + + + + actuators/voltage_dc_out5 + + + + + + + + VoltageDC_OUT_6 + + 0 + Actuator + VoltageDC + 4 + + 0 + 2 + 3 + + + + + + + VoltageDC + RW + + + + VOLTS + 0 + 24 + 0.00073244422742 + + + + + + + actuators/voltage_dc_out6 + + + + actuators/voltage_dc_out6 + + + + actuators/voltage_dc_out6 + + + + + + + + VoltageDC_OUT_7 + + 0 + Actuator + VoltageDC + 4 + + 0 + 2 + 3 + + + + + + + VoltageDC + RW + + + + VOLTS + 0.00073244422742 + + + + + + + actuators/voltage_dc_out7 + + + + actuators/voltage_dc_out7 + + + + actuators/voltage_dc_out7 + + + + + + + + VoltageDC_OUT_8 + + 0 + Actuator + VoltageDC + 4 + + 0 + 2 + 3 + + + + + + + VoltageDC + RW + + + + VOLTS + 0 + 24 + 0.00073244422742 + + + + + + + actuators/voltage_dc_out8 + + + + actuators/voltage_dc_out8 + + + + actuators/voltage_dc_out8 + + + + + + + + DigitalRegister_M1_IN_1 + + 0 + Sensor + DigitalInput + 4 + + 0 + 2 + 3 + + + + + + + Register + R + + + + Sensor_1 + 0001 + + + Sensor_2 + 0002 + + + Sensor_3 + 0004 + + + Sensor_4 + 0008 + + + Sensor_5 + 0010 + + + Sensor_6 + 0020 + + + Sensor_7 + 0040 + + + Sensor_8 + 0080 + + + + NONE + + + + + + + sensors/register_m1_in1 + + + + actuators/voltage_dc_out1 + + + + + + + + DigitalRegister_M2_IN_2 + + 0 + Sensor + DigitalInput + 4 + + 0 + 2 + 3 + + + + + + + Register + R + + + + Sensor_1 + 0001 + + + Sensor_2 + 0002 + + + Sensor_3 + 0004 + + + Sensor_4 + 0008 + + + Sensor_5 + 0010 + + + Sensor_6 + 0020 + + + Sensor_7 + 0040 + + + Sensor_8 + 0080 + + + + NONE + + + + + + + sensors/register_m2_in2 + + + + sensors/register_m2_in2 + + + + + + + + DigitalRegister_M2_OUT_1 + + 0 + Actuator + DigitalOutput + 4 + + 0 + 2 + 3 + + + + + + + Register + RW + + + + Relais_1 + 0001 + + + Relais_2 + 0002 + + + Relais_3 + 0004 + + + Relais_4 + 0008 + + + Relais_5 + 0010 + + + Relais_6 + 0020 + + + Relais_7 + 0040 + + + Relais_8 + 0080 + + + + NONE + + + + + + + actuator/register_m1_out1 + + + + actuator/register_m1_out1 + + + + actuator/register_m1_out1 + + + + + + + + + From f9a3a39e243c7527a4870d1eb34885b4b332c673 Mon Sep 17 00:00:00 2001 From: Sandro Sabatini Date: Thu, 12 Dec 2024 14:38:06 +0100 Subject: [PATCH 07/10] Update gradle to 8.11.1 --- SampleCommunicator/build.gradle | 11 ++++++++--- .../gradle/wrapper/gradle-wrapper.jar | Bin 43453 -> 43583 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- SampleCommunicator/gradlew | 7 +++++-- SampleCommunicator/gradlew.bat | 2 ++ 5 files changed, 16 insertions(+), 6 deletions(-) diff --git a/SampleCommunicator/build.gradle b/SampleCommunicator/build.gradle index ba14ec6..cd0fb25 100644 --- a/SampleCommunicator/build.gradle +++ b/SampleCommunicator/build.gradle @@ -16,14 +16,19 @@ repositories { maven { url "https://nexus.library.smartgridready.ch/repository/maven-releases/" + mavenContent { + releasesOnly() + } } maven { url "https://nexus.library.smartgridready.ch/repository/maven-snapshots/" + mavenContent { + snapshotsOnly() + } } } -sourceSets -{ +sourceSets { main.java.srcDirs = ['src/main/java' ] main.resources.srcDirs = ['src/main/resources' ] } @@ -45,7 +50,7 @@ dependencies { // Logging implementation group: 'org.slf4j', name: 'slf4j-api', version: '2.0.10' - implementation group: 'org.apache.logging.log4j', name: 'log4j-slf4j2-impl', version: '2.23.1' + implementation group: 'org.apache.logging.log4j', name: 'log4j-slf4j2-impl', version: '2.19.0' // Use JUnit test framework implementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.10.2' diff --git a/SampleCommunicator/gradle/wrapper/gradle-wrapper.jar b/SampleCommunicator/gradle/wrapper/gradle-wrapper.jar index e6441136f3d4ba8a0da8d277868979cfbc8ad796..a4b76b9530d66f5e68d973ea569d8e19de379189 100644 GIT binary patch delta 12612 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 HJ4oML2Lo<@i8BwbL^1~GkG`E7C$SEa_ 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 diff --git a/SampleCommunicator/gradle/wrapper/gradle-wrapper.properties b/SampleCommunicator/gradle/wrapper/gradle-wrapper.properties index b82aa23..e2847c8 100644 --- a/SampleCommunicator/gradle/wrapper/gradle-wrapper.properties +++ b/SampleCommunicator/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/SampleCommunicator/gradlew b/SampleCommunicator/gradlew index 1aa94a4..f5feea6 100644 --- a/SampleCommunicator/gradlew +++ b/SampleCommunicator/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,8 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum diff --git a/SampleCommunicator/gradlew.bat b/SampleCommunicator/gradlew.bat index 25da30d..9d21a21 100644 --- a/SampleCommunicator/gradlew.bat +++ b/SampleCommunicator/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## From 83b3a6d287a42408716f66705b2de152d0fb5a13 Mon Sep 17 00:00:00 2001 From: Sandro Sabatini Date: Thu, 12 Dec 2024 14:38:38 +0100 Subject: [PATCH 08/10] fixed copyright notice at the start of the file --- .../AsynchronousSampleCommunicatorTest.java | 18 +++++++++ .../BasicSampleCommunicatorClassic.java | 37 +++++++++---------- 2 files changed, 35 insertions(+), 20 deletions(-) diff --git a/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/AsynchronousSampleCommunicatorTest.java b/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/AsynchronousSampleCommunicatorTest.java index 6d60b15..38c3cac 100644 --- a/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/AsynchronousSampleCommunicatorTest.java +++ b/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/AsynchronousSampleCommunicatorTest.java @@ -1,3 +1,21 @@ +/* + * Copyright(c) 2024 Verein SmartGridready Switzerland + * + * This Open Source Software is BSD 3 clause licensed: + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + package com.smartgridready.communicator.example; import com.smartgridready.driver.api.common.GenDriverException; diff --git a/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/BasicSampleCommunicatorClassic.java b/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/BasicSampleCommunicatorClassic.java index 60b6fe7..724fabd 100644 --- a/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/BasicSampleCommunicatorClassic.java +++ b/SampleCommunicator/src/main/java/com/smartgridready/communicator/example/BasicSampleCommunicatorClassic.java @@ -1,24 +1,21 @@ -/** -*Copyright(c) 2021 Verein SmartGridready Switzerland -* -This Open Source Software is BSD 3 clause licensed: -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: -1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the distribution. -3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from - this software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR -CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED -OF THE POSSIBILITY OF SUCH DAMAGE. - -This Module includes automatically generated code, generated from SmartGridready Modus XML Schema definitions -check for "EI-Modbus" and "Generic" directories in our Namespace http://www.smartgridready.ch/ns/SGr/V0/ +/* + * Copyright(c) 2024 Verein SmartGridready Switzerland + * + * This Open Source Software is BSD 3 clause licensed: + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ -*/ package com.smartgridready.communicator.example; import java.io.FileNotFoundException; From b337d7a22c478fc2fcfaf9b5d9ecbe0e6da14a3b Mon Sep 17 00:00:00 2001 From: Sandro Sabatini Date: Thu, 12 Dec 2024 16:19:10 +0100 Subject: [PATCH 09/10] deleted German readme --- README_DE.md | 181 --------------------------------------------------- 1 file changed, 181 deletions(-) delete mode 100644 README_DE.md diff --git a/README_DE.md b/README_DE.md deleted file mode 100644 index f56f5b0..0000000 --- a/README_DE.md +++ /dev/null @@ -1,181 +0,0 @@ -# SGr-JavaSamples - -SGr-JavaSamples stellt Beispielprojekte zur Verfügung, welche die Anwendung der SGr Communication Handler Library demonstrieren. Ziel ist es eine Testumgebung aufzusetzen, die es ermöglicht, SGr-Komponenten und unterschiedliche 'Products' (Wärmepumpe​, Ladestation​, Wechselrichter​, Batterie​, Stromzähler​ etc.) via SGr-Kommunikationsschnittelle zu verbinden. - -![SGr Architecture Overview](doc/img/SGr-Architecture-Overview.png "SGr Architecture Overview") - -## Komponenten - -### Komponente: Communicator - - - - - - -
Implementer:Communicator Provider (3rd Party)
Beschreibung:Der 'Communicator' kommuniziert mit ein oder mehreren 'Products' über das SGr 'Generic Interface'. -
Verantwortlich: -

Der Communicator instanziiert für jedes Product einen Communication Handler dem eine Beschreibung der Produkteschnittstelle in XML übergeben wird.

-

Der Communicator lädt einen Device-Treiber für die Kommunikationsschnittstelle zum Product (z.B. Modbus RTU/TCP, REST...).

-

Der Communicator liest die Datenpunkte aus oder setzt sie (analysiert und/oder steuert)​.

-

SGrProjekt:SGrJavaSamples/SampleCommunicator
- -

- -### Komponente: Generic Interface - - - - - - -
Implementer:SGr Core Team
Beschreibung:Von SGr vorgegebene 'Product'-unabhängige Schnittstelle. -
Verantwortlich: -

Das Generic Interface wird vom Communicator benutzt, um mit den Product's im SGr Verbund zu kommunizieren.​

-

SGrProjekt:SmartgridReady/SGrSpecifications/SchemaDatabase/SGr/Generic
- -

- -### Komponente: Communication Handler - - - - - - - -
Implementer:SGr Core Team
Beschreibung:Ist die Kernkomponente der SGr Software und damit verantwortlich für die Verarbeitung und Umsetzung des SGr 'Generic Interface' auf das 'External Interface' des 'Product'.
- Wird vom 'Communicator' instanziiert und zur Kommunikation mit den angeschlossenen 'Product' benutzt. -
Verantwortlich: -

​Verantwortlichkeiten sind:
-- Lesen der XML-Device-Profile
-- Verarbeiten von Kommandos der generischen Schnittstelle
-- Umsetzen der Kommandos auf das External Interface des Product (gerätespezifische Schnittstelle)
-- Senden der Kommandos an das Product über den durch das Product vorgegebenen Transportservice -

-

Library:sgr-commhandler-2.0.0.jar
SGrProjekt:SmartgridReady/SGrJava/CommHandler
- -

- -### Komponente: XML (XML-Profile) - - - - - - -
Implementer:Anbieter des 'Product'
Beschreibung:​Das XML File beschreibt die 'Funktionsprofile', Datenpunkte und Attribute, welche über die SGr-Schnittstelle angesprochen werden können​. Weiter stellt das File allgemeine Informationen zum 'Product' zur Verfügung. -
Verantwortlich: -

​Bereitstellen von allgemeinen Daten zum Product.

-

Bereitstellen der zum mapping des SGr Generic Interface auf das External Interface notwendigen Daten.

SGrProjekt:SmartgridReady/SGrSpecifications/XMLInstances/ExtInterfaces
- -

- -### Komponente: Transport Layer (Transport Service) - - - - - - - - -
Implementer:- SGr Core Team
- 3rd Party Provider
Beschreibung:Der 'TransportService' bildet das Bindeglied zur physischen Kommunaktionsschnittstelle zum 'Product'. Das SGr Core Team stellt für Modbus die EasyModbus Library zur Verfügung. -
Verantwortlich: -

​Der SGr Transport Service unterstützt folgende Kommunikations-Technologien, um darauf aufbauend folgende Transport Servies anzubieten:
- - Modbus​, REST/JSON​, Sunspec​
- - Unterstützung geplant:​ OCPP 2.0​, IEC-61968-9​, IEC-608070-5-104

-

Library:easymodbus.jar
SGrProjekt:für Modbus:
SmartgridReady/SGrJavaDrivers/EasyModbus
- -

- -### Komponente: External Interface (EI) - - - -
Implementer:Hersteller des 'Product'
Beschreibung:Das 'External Interface' ist die vom 'Product' bereitgestellte Schnittstelle. -
Verantwortlich: -

Stellt das External Interface des Product zur Verfügung. Diese wird im XML-Profile zum Product beschrieben.

- -

- -### Komponente: Product - - - -
Implementer:Hersteller des 'Product'
Beschreibung:Das Product ist ein Device, das Eigenschaften, Datenpunkte und Ansteuerungsmöglichkeiten zur Verfügung​ stellt. -Z.B. Wärmepumpe​, Ladestation​, Wechselrichter​, Batterie​, Stromzähler​ -
Verantwortlich: -

... -

- -

- -## Anwendung der SGrJavaSamples - - -### Anforderungen / Prerequisites -- Gradle Version >= 8.7. Anm.: Wenn keine IDE mit Gradle-Integration verwendet wird, muss Gradle erst lokal installiert werden: https://gradle.org/install/ -- Java JDK Version >= Java 11 - -### Clone -- Klone dieses Repo auf das lokale Device: https://github.com/SmartgridReady/SGrJavaSamples.git - -### Build -- Go to the local SGrJavaSamples directory (...\SGrJavaSamples\SampleCommunicator) -- Run gradle 'build' target in your IDE or use the command line:
-```bash>gradle clean build``` - -

- -### Code-Beschreibung zum SampleCommunicator - -Step 1: -Use the DeviceBeschreibungLoader class to Load the device Beschreibung from an XML file. -

-```DeviceDescriptionLoader loader = new DeviceDescriptionLoader<>();```
-```SGrModbusDeviceBeschreibungType sgcpMeter = loader.load( XML_BASE_DIR,"SGr_04_0016_xxxx_ABBMeterV0.2.1.xml");``` -

- -Step2: -Load the suitable device driver to communicate with the device. The example below uses mocked driver for modbus RTU. -Change the driver to the real driver, suitable for your device. For example: -

-```GenDriverAPI4Modbus mbTCP = new GenDriverAPI4ModbusTCP("127.0.0.1", 502);```
-```GenDriverAPI4Modbus mbRTU = new GenDriverAPI4ModbusRTU("COM1");```
-```GenDriverAPI4Modbus mbRTUMock = new GenDriverAPI4ModbusRTUMock("COM1");``` -

- -Step 2: -Instantiate a modbus device. Provide the device description and the device driver instance to be used for the device.

-```SGrModbusDevice sgcpDevice = new SGrModbusDevice(sgcpMeter, mbRTUMock);``` -

- -Step 3: -Initialize the serial COM port or TCP connection used by the modbus transport service. -```sgcpDevice.connect();``` -

- -Step 4: Read the values from the device. -- "CurrentAC" is the name of the functional profile. -- "CurrentACL1", "CurrentACL2", "CurrentACL3" and "ActiveNetACN" are the names of the Datapoints that report the values corresponding to their names. - -Hint: You can only read values for functional profiles and datapoints that exist in the device description XML.
-```String val1 = sgcpDevice.getVal("VoltageAC", "VoltageL1");```
-```String val2 = sgcpDevice.getVal("VoltageAC", "VoltageL2");```
-```String val3 = sgcpDevice.getVal("VoltageAC", "VoltageL3");```

- -Step 5: Disconnect device. -```sgcpDevice.disconnect();``` -

- -Der komplette Beispielcode ist auf GitHub:
-https://github.com/SmartgridReady/SGrJavaSamples/blob/documentation/SampleCommunicator/src/main/java/ch/smartgridready/communicator/example/SampleCommunicator.java -

- -## Weiterführende Informationen / Kontakt - -Glossar: *ToDo Link* - -Webseite: https://smartgridready.ch/
-E-Mail: info@smartgridready.ch From a841c864192d5ebf81a02d17881c2b35e1483416 Mon Sep 17 00:00:00 2001 From: Furrer Hans Date: Mon, 16 Dec 2024 09:31:19 +0100 Subject: [PATCH 10/10] Added dependency to log4j-core to build.gradle. --- SampleCommunicator/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/SampleCommunicator/build.gradle b/SampleCommunicator/build.gradle index cd0fb25..ad197ec 100644 --- a/SampleCommunicator/build.gradle +++ b/SampleCommunicator/build.gradle @@ -50,6 +50,7 @@ dependencies { // Logging implementation group: 'org.slf4j', name: 'slf4j-api', version: '2.0.10' + implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.19.0' implementation group: 'org.apache.logging.log4j', name: 'log4j-slf4j2-impl', version: '2.19.0' // Use JUnit test framework