This is an automated email from the ASF dual-hosted git repository. ldywicki pushed a commit to branch feature/socketcan-0.8-preparations in repository https://gitbox.apache.org/repos/asf/plc4x.git
commit 7a681adf9c239ffbd2b0fe101ce15ae60cf2ed4f Author: Ćukasz Dywicki <[email protected]> AuthorDate: Wed Oct 7 11:31:43 2020 +0200 CANopen microstone - PDO & subscriptions. --- .../resources/templates/java/data-io-template.ftlh | 4 +- .../org/apache/plc4x/java/api/value/PlcValues.java | 471 +++++++++++++++++++++ .../src/main/resources/protocols/can/canopen.mspec | 40 +- .../apache/plc4x/java/can/CANOpenPlcDriver.java | 5 + .../apache/plc4x/java/can/field/CANOpenField.java | 2 + .../plc4x/java/can/field/CANOpenFieldHandler.java | 20 +- .../plc4x/java/can/field/CANOpenPDOField.java | 64 +++ .../java/can/protocol/CANOpenProtocolLogic.java | 137 +++++- .../can/protocol/CANOpenSubscriptionHandle.java | 28 ++ 9 files changed, 734 insertions(+), 37 deletions(-) diff --git a/build-utils/language-java/src/main/resources/templates/java/data-io-template.ftlh b/build-utils/language-java/src/main/resources/templates/java/data-io-template.ftlh index 0f36832..9de8c7a 100644 --- a/build-utils/language-java/src/main/resources/templates/java/data-io-template.ftlh +++ b/build-utils/language-java/src/main/resources/templates/java/data-io-template.ftlh @@ -120,10 +120,10 @@ public class ${type.name}IO { <#if helper.isLengthArrayField(field)> // Length array int _${field.name}Length = ${helper.toParseExpression(field, field.loopExpression, type.parserArguments)}; - List<${helper.getNonPrimitiveLanguageTypeNameForField(field)}> ${field.name} = new LinkedList<>(); + List<${helper.getNonPrimitiveLanguageTypeNameForField(field)}> _${field.name}List = new LinkedList<>(); int ${field.name}EndPos = io.getPos() + _${field.name}Length; while(io.getPos() < ${field.name}EndPos) { - ${field.name}.add(<#if helper.isSimpleTypeReference(field.type)>${helper.getReadBufferReadMethodCall(field.type)}<#else>${field.type.name}IO.staticParse(io<#if field.params?has_content>, <#list field.params as parserArgument>(${helper.getLanguageTypeNameForTypeReference(helper.getArgumentType(field.type, parserArgument?index), true)}) (${helper.toParseExpression(field, parserArgument, type.parserArguments)})<#sep>, </#sep></#list></#if>)</#if>); + _${field.name}List.add(<#if helper.isSimpleTypeReference(field.type)>${helper.getReadBufferReadMethodCall(field.type)}<#else>${field.type.name}IO.staticParse(io<#if field.params?has_content>, <#list field.params as parserArgument>(${helper.getLanguageTypeNameForTypeReference(helper.getArgumentType(field.type, parserArgument?index), true)}) (${helper.toParseExpression(field, parserArgument, type.parserArguments)})<#sep>, </#sep></#list></#if>)</#if>); <#-- After parsing, update the current position, but only if it's needed --> <#if field.loopExpression.contains("curPos")> curPos = io.getPos() - startPos; diff --git a/plc4j/api/src/main/java/org/apache/plc4x/java/api/value/PlcValues.java b/plc4j/api/src/main/java/org/apache/plc4x/java/api/value/PlcValues.java new file mode 100644 index 0000000..03b3761 --- /dev/null +++ b/plc4j/api/src/main/java/org/apache/plc4x/java/api/value/PlcValues.java @@ -0,0 +1,471 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.plc4x.java.api.value; + +import org.apache.plc4x.java.api.exceptions.PlcIncompatibleDatatypeException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.stream.Collectors; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class PlcValues { + + private static final Logger LOGGER = LoggerFactory.getLogger(PlcValues.class); + + public static PlcValue of(Boolean b) { + return new PlcBOOL(b); + } + + public static PlcValue of(boolean b) { + return new PlcBOOL(b); + } + + public static PlcValue of(Boolean[] b) { + if(b != null) { + if(b.length == 1) { + return new PlcBOOL(b[0]); + } else if(b.length > 1) { + List<PlcBOOL> plcValues = new LinkedList<>(); + for (int i = 0; i < b.length; i++) { + plcValues.add(new PlcBOOL(b[i])); + } + return new PlcList(plcValues); + } + } + return null; + } + + public static PlcValue of(boolean[] b) { + if(b != null) { + if(b.length == 1) { + return new PlcBOOL(b[0]); + } else if(b.length > 1) { + List<PlcBOOL> plcValues = new LinkedList<>(); + for (int i = 0; i < b.length; i++) { + plcValues.add(new PlcBOOL(b[i])); + } + return new PlcList(plcValues); + } + } + return null; + } + + public static PlcValue of(Byte i) { + return new PlcSINT(i); + } + + public static PlcValue of(byte i) { + return new PlcSINT(i); + } + + public static PlcValue of(Byte[] i) { + if(i != null) { + if(i.length == 1) { + return new PlcSINT(i[0]); + } else if(i.length > 1) { + List<PlcBYTE> plcValues = new LinkedList<>(); + for (int j = 0; j < i.length; j++) { + plcValues.add(new PlcBYTE(i[j])); + } + return new PlcList(plcValues); + } + } + return null; + } + + public static PlcValue of(byte[] i) { + if(i != null) { + if(i.length == 1) { + return new PlcSINT(i[0]); + } else if(i.length > 1) { + List<PlcBYTE> plcValues = new LinkedList<>(); + for (int j = 0; j < i.length; j++) { + plcValues.add(new PlcBYTE(i[j])); + } + return new PlcList(plcValues); + } + } + return null; + } + + public static PlcValue of(Short i) { + return new PlcINT(i); + } + + public static PlcValue of(short i) { + return new PlcINT(i); + } + + public static PlcValue of(Short[] i) { + if(i != null) { + if(i.length == 1) { + return new PlcINT(i[0]); + } else if(i.length > 1) { + List<PlcINT> plcValues = new LinkedList<>(); + for (int j = 0; j < i.length; j++) { + plcValues.add(new PlcINT(i[j])); + } + return new PlcList(plcValues); + } + } + return null; + } + + public static PlcValue of(short[] i) { + if(i != null) { + if(i.length == 1) { + return new PlcINT(i[0]); + } else if(i.length > 1) { + List<PlcINT> plcValues = new LinkedList<>(); + for (int j = 0; j < i.length; j++) { + plcValues.add(new PlcINT(i[j])); + } + return new PlcList(plcValues); + } + } + return null; + } + + public static PlcValue of(Integer i) { + return new PlcDINT(i); + } + + public static PlcValue of(int i) { + return new PlcDINT(i); + } + + public static PlcValue of(Integer[] i) { + if(i != null) { + if(i.length == 1) { + return new PlcDINT(i[0]); + } else if(i.length > 1) { + List<PlcDINT> plcValues = new LinkedList<>(); + for (int j = 0; j < i.length; j++) { + plcValues.add(new PlcDINT(i[j])); + } + return new PlcList(plcValues); + } + } + return null; + } + + public static PlcValue of(int[] i) { + if(i != null) { + if(i.length == 1) { + return new PlcDINT(i[0]); + } else if(i.length > 1) { + List<PlcDINT> plcValues = new LinkedList<>(); + for (int j = 0; j < i.length; j++) { + plcValues.add(new PlcDINT(i[j])); + } + return new PlcList(plcValues); + } + } + return null; + } + + public static PlcValue of(Long i) { + return new PlcLINT(i); + } + + public static PlcValue of(long i) { + return new PlcLINT(i); + } + + public static PlcValue of(Long[] i) { + if(i != null) { + if(i.length == 1) { + return new PlcLINT(i[0]); + } else if(i.length > 1) { + List<PlcLINT> plcValues = new LinkedList<>(); + for (int j = 0; j < i.length; j++) { + plcValues.add(new PlcLINT(i[j])); + } + return new PlcList(plcValues); + } + } + return null; + } + + public static PlcValue of(long[] i) { + if(i != null) { + if(i.length == 1) { + return new PlcLINT(i[0]); + } else if(i.length > 1) { + List<PlcLINT> plcValues = new LinkedList<>(); + for (int j = 0; j < i.length; j++) { + plcValues.add(new PlcLINT(i[j])); + } + return new PlcList(plcValues); + } + } + return null; + } + + public static PlcValue of(BigInteger i) { + return new PlcBigInteger(i); + } + + public static PlcValue of(BigInteger[] i) { + if(i != null) { + if(i.length == 1) { + return new PlcBigInteger(i[0]); + } else if(i.length > 1) { + List<PlcBigInteger> plcValues = new LinkedList<>(); + for (int j = 0; j < i.length; j++) { + plcValues.add(new PlcBigInteger(i[j])); + } + return new PlcList(plcValues); + } + } + return null; + } + + public static PlcValue of(Float i) { + return new PlcREAL(i); + } + + public static PlcValue of(float i) { + return new PlcREAL(i); + } + + public static PlcValue of(Float[] i) { + if(i != null) { + if(i.length == 1) { + return new PlcREAL(i[0]); + } else if(i.length > 1) { + List<PlcREAL> plcValues = new LinkedList<>(); + for (int j = 0; j < i.length; j++) { + plcValues.add(new PlcREAL(i[j])); + } + return new PlcList(plcValues); + } + } + return null; + } + + public static PlcValue of(float[] i) { + if(i != null) { + if(i.length == 1) { + return new PlcREAL(i[0]); + } else if(i.length > 1) { + List<PlcREAL> plcValues = new LinkedList<>(); + for (int j = 0; j < i.length; j++) { + plcValues.add(new PlcREAL(i[j])); + } + return new PlcList(plcValues); + } + } + return null; + } + + public static PlcValue of(Double i) { + return new PlcLREAL(i); + } + + public static PlcValue of(double i) { + return new PlcLREAL(i); + } + + public static PlcValue of(Double[] i) { + if(i != null) { + if(i.length == 1) { + return new PlcLREAL(i[0]); + } else if(i.length > 1) { + List<PlcLREAL> plcValues = new LinkedList<>(); + for (int j = 0; j < i.length; j++) { + plcValues.add(new PlcLREAL(i[j])); + } + return new PlcList(plcValues); + } + } + return null; + } + + public static PlcValue of(double[] i) { + if(i != null) { + if(i.length == 1) { + return new PlcLREAL(i[0]); + } else if(i.length > 1) { + List<PlcLREAL> plcValues = new LinkedList<>(); + for (int j = 0; j < i.length; j++) { + plcValues.add(new PlcLREAL(i[j])); + } + return new PlcList(plcValues); + } + } + return null; + } + + public static PlcValue of(BigDecimal i) { + return new PlcBigDecimal(i); + } + + public static PlcValue of(BigDecimal[] i) { + if(i != null) { + if(i.length == 1) { + return new PlcBigDecimal(i[0]); + } else if(i.length > 1) { + List<PlcBigDecimal> plcValues = new LinkedList<>(); + for (int j = 0; j < i.length; j++) { + plcValues.add(new PlcBigDecimal(i[j])); + } + return new PlcList(plcValues); + } + } + return null; + } + + public static PlcValue of(String s) { + return new PlcString(s); + } + + public static PlcValue of(String[] s) { + if(s != null) { + if(s.length == 1) { + return new PlcString(s[0]); + } else if(s.length > 1) { + List<PlcString> plcValues = new LinkedList<>(); + for (int j = 0; j < s.length; j++) { + plcValues.add(new PlcString(s[j])); + } + return new PlcList(plcValues); + } + } + return null; + } + + public static PlcValue of(LocalTime s) { + return new PlcTime(s); + } + + public static PlcValue of(LocalTime[] s) { + if(s != null) { + if(s.length == 1) { + return new PlcTime(s[0]); + } else if(s.length > 1) { + List<PlcTime> plcValues = new LinkedList<>(); + for (int j = 0; j < s.length; j++) { + plcValues.add(new PlcTime(s[j])); + } + return new PlcList(plcValues); + } + } + return null; + } + + public static PlcValue of(LocalDate s) { + return new PlcDate(s); + } + + public static PlcValue of(LocalDate[] s) { + if(s != null) { + if(s.length == 1) { + return new PlcDate(s[0]); + } else if(s.length > 1) { + List<PlcDate> plcValues = new LinkedList<>(); + for (int j = 0; j < s.length; j++) { + plcValues.add(new PlcDate(s[j])); + } + return new PlcList(plcValues); + } + } + return null; + } + + public static PlcValue of(LocalDateTime s) { + return new PlcDateTime(s); + } + + public static PlcValue of(LocalDateTime[] s) { + if(s != null) { + if(s.length == 1) { + return new PlcDateTime(s[0]); + } else if(s.length > 1) { + List<PlcDateTime> plcValues = new LinkedList<>(); + for (int j = 0; j < s.length; j++) { + plcValues.add(new PlcDateTime(s[j])); + } + return new PlcList(plcValues); + } + } + return null; + } + + public static PlcValue of(List<PlcValue> list) { + return new PlcList(list); + } + + public static PlcValue of(PlcValue... items) { + return new PlcList(Arrays.asList(items)); + } + + public static PlcValue of(String key, PlcValue value) { + return new PlcStruct(Collections.singletonMap(key, value)); + } + + public static PlcValue of(Map<String, PlcValue> map) { + return new PlcStruct(map); + } + + public static PlcValue of(Object o) { + if(o == null) { + return new PlcNull(); + } + if (o instanceof Byte) { + return new PlcBYTE((Byte) o); + } + + try { + String simpleName = o.getClass().getSimpleName(); + Class<?> clazz = o.getClass(); + if (o instanceof List) { + simpleName = "List"; + clazz = List.class; + } else if(clazz.isArray()) { + simpleName = "List"; + clazz = List.class; + Object[] objectArray = (Object[]) o; + o = Arrays.asList(objectArray); + } + // If it's one of the LocalDate, LocalTime or LocalDateTime, cut off the "Local". + if(simpleName.startsWith("Local")) { + simpleName = simpleName.substring(5); + } + Constructor<?> constructor = Class.forName(PlcValues.class.getPackage().getName() + ".Plc" + simpleName).getDeclaredConstructor(clazz); + return ((PlcValue) constructor.newInstance(o)); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException | ClassNotFoundException e) { + LOGGER.warn("Cannot wrap", e); + throw new PlcIncompatibleDatatypeException(o.getClass()); + } + } +} diff --git a/protocols/can/src/main/resources/protocols/can/canopen.mspec b/protocols/can/src/main/resources/protocols/can/canopen.mspec index 3b1e49d..10afdf9 100644 --- a/protocols/can/src/main/resources/protocols/can/canopen.mspec +++ b/protocols/can/src/main/resources/protocols/can/canopen.mspec @@ -272,64 +272,64 @@ [dataIo 'DataItem' [CANOpenDataType 'dataType', int 32 'size'] [typeSwitch 'dataType' - ['CANOpenDataType.BOOLEAN' Boolean + ['CANOpenDataType.BOOLEAN' BOOL [simple bit 'value'] ] - ['CANOpenDataType.UNSIGNED8' Integer + ['CANOpenDataType.UNSIGNED8' USINT [simple uint 8 'value'] ] - ['CANOpenDataType.UNSIGNED16' Integer + ['CANOpenDataType.UNSIGNED16' UINT [simple uint 16 'value'] ] - ['CANOpenDataType.UNSIGNED24' Long + ['CANOpenDataType.UNSIGNED24' UDINT [simple uint 24 'value'] ] - ['CANOpenDataType.UNSIGNED32' Long + ['CANOpenDataType.UNSIGNED32' UDINT [simple uint 32 'value'] ] - ['CANOpenDataType.UNSIGNED40' BigInteger + ['CANOpenDataType.UNSIGNED40' ULINT [simple uint 40 'value'] ] - ['CANOpenDataType.UNSIGNED48' BigInteger + ['CANOpenDataType.UNSIGNED48' ULINT [simple uint 48 'value'] ] - ['CANOpenDataType.UNSIGNED56' BigInteger + ['CANOpenDataType.UNSIGNED56' ULINT [simple uint 56 'value'] ] - ['CANOpenDataType.UNSIGNED64' BigInteger + ['CANOpenDataType.UNSIGNED64' ULINT [simple uint 64 'value'] ] - ['CANOpenDataType.INTEGER8' Integer + ['CANOpenDataType.INTEGER8' SINT [simple int 8 'value'] ] - ['CANOpenDataType.INTEGER16' Integer + ['CANOpenDataType.INTEGER16' INT [simple int 16 'value'] ] - ['CANOpenDataType.INTEGER24' Integer + ['CANOpenDataType.INTEGER24' DINT [simple int 24 'value'] ] - ['CANOpenDataType.INTEGER32' Integer + ['CANOpenDataType.INTEGER32' DINT [simple int 32 'value'] ] - ['CANOpenDataType.INTEGER40' Long + ['CANOpenDataType.INTEGER40' LINT [simple int 40 'value'] ] - ['CANOpenDataType.INTEGER48' Long + ['CANOpenDataType.INTEGER48' LINT [simple int 48 'value'] ] - ['CANOpenDataType.INTEGER56' Long + ['CANOpenDataType.INTEGER56' LINT [simple int 56 'value'] ] - ['CANOpenDataType.INTEGER64' Long + ['CANOpenDataType.INTEGER64' LINT [simple int 64 'value'] ] - ['CANOpenDataType.REAL32' Float + ['CANOpenDataType.REAL32' REAL [simple float 8.23 'value'] ] - ['CANOpenDataType.REAL64' Double + ['CANOpenDataType.REAL64' LREAL [simple float 11.52 'value'] ] - ['CANOpenDataType.RECORD' List + ['CANOpenDataType.RECORD' List [int 32 'size'] [array int 8 'value' length 'size'] ] ['CANOpenDataType.OCTET_STRING' String diff --git a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/CANOpenPlcDriver.java b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/CANOpenPlcDriver.java index a8a5f7a..c34806f 100644 --- a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/CANOpenPlcDriver.java +++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/CANOpenPlcDriver.java @@ -58,6 +58,11 @@ public class CANOpenPlcDriver extends GeneratedDriverBase<SocketCANFrame> { } @Override + protected boolean canSubscribe() { + return true; + } + + @Override protected boolean canWrite() { return true; } diff --git a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/field/CANOpenField.java b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/field/CANOpenField.java index 6458848..6917351 100644 --- a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/field/CANOpenField.java +++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/field/CANOpenField.java @@ -44,6 +44,8 @@ public abstract class CANOpenField implements PlcField { public static CANOpenField of(String addressString) throws PlcInvalidFieldException { if (CANOpenSDOField.matches(addressString)) { return CANOpenSDOField.of(addressString); + } else if (CANOpenPDOField.matches(addressString)) { + return CANOpenPDOField.of(addressString); } throw new PlcInvalidFieldException("Unable to parse address: " + addressString); diff --git a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/field/CANOpenFieldHandler.java b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/field/CANOpenFieldHandler.java index cf37533..cb61195 100644 --- a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/field/CANOpenFieldHandler.java +++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/field/CANOpenFieldHandler.java @@ -21,13 +21,13 @@ package org.apache.plc4x.java.can.field; import org.apache.plc4x.java.api.exceptions.PlcInvalidFieldException; import org.apache.plc4x.java.api.exceptions.PlcRuntimeException; import org.apache.plc4x.java.api.model.PlcField; -import org.apache.plc4x.java.api.value.PlcList; -import org.apache.plc4x.java.api.value.PlcString; -import org.apache.plc4x.java.api.value.PlcValue; +import org.apache.plc4x.java.api.value.*; import org.apache.plc4x.java.spi.connection.DefaultPlcFieldHandler; import org.apache.plc4x.java.spi.connection.PlcFieldHandler; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.stream.Collectors; public class CANOpenFieldHandler extends DefaultPlcFieldHandler implements PlcFieldHandler { @@ -55,4 +55,18 @@ public class CANOpenFieldHandler extends DefaultPlcFieldHandler implements PlcFi throw new PlcRuntimeException("Invalid encoder for type " + coField.getCanOpenDataType().name()); } + + @Override + public PlcValue encodeByte(PlcField field, Object[] values) { + List<PlcValue> resultSet = new ArrayList<>(); + for (Object item : values) { + resultSet.add(PlcValues.of((Byte) item)); + } + + if (resultSet.size() == 1) { + return resultSet.get(0); + } else { + return new PlcList(resultSet); + } + } } diff --git a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/field/CANOpenPDOField.java b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/field/CANOpenPDOField.java new file mode 100644 index 0000000..f65e5b3 --- /dev/null +++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/field/CANOpenPDOField.java @@ -0,0 +1,64 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +*/ +package org.apache.plc4x.java.can.field; + +import org.apache.plc4x.java.api.exceptions.PlcInvalidFieldException; +import org.apache.plc4x.java.canopen.readwrite.types.CANOpenDataType; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class CANOpenPDOField extends CANOpenField { + + public static final Pattern ADDRESS_PATTERN = Pattern.compile("PDO:" + CANOpenField.NODE_PATTERN + ":(?<canDataType>\\w+)(\\[(?<numberOfElements>\\d)])?"); + private final CANOpenDataType canOpenDataType; + + public CANOpenPDOField(int node, CANOpenDataType canOpenDataType) { + super(node); + this.canOpenDataType = canOpenDataType; + } + + public CANOpenDataType getCanOpenDataType() { + return canOpenDataType; + } + + public static boolean matches(String addressString) { + return ADDRESS_PATTERN.matcher(addressString).matches(); + } + + public static Matcher getMatcher(String addressString) throws PlcInvalidFieldException { + Matcher matcher = ADDRESS_PATTERN.matcher(addressString); + if (matcher.matches()) { + return matcher; + } + + throw new PlcInvalidFieldException(addressString, ADDRESS_PATTERN); + } + + public static CANOpenPDOField of(String addressString) { + Matcher matcher = getMatcher(addressString); + int nodeId = Integer.parseInt(matcher.group("nodeId")); + + String canDataTypeString = matcher.group("canDataType"); + CANOpenDataType canOpenDataType = CANOpenDataType.valueOf(canDataTypeString); + + return new CANOpenPDOField(nodeId, canOpenDataType); + } + +} diff --git a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/protocol/CANOpenProtocolLogic.java b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/protocol/CANOpenProtocolLogic.java index b5aa34e..d96fe19 100644 --- a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/protocol/CANOpenProtocolLogic.java +++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/protocol/CANOpenProtocolLogic.java @@ -19,8 +19,12 @@ under the License. package org.apache.plc4x.java.can.protocol; import org.apache.plc4x.java.api.messages.*; +import org.apache.plc4x.java.api.model.PlcConsumerRegistration; import org.apache.plc4x.java.api.model.PlcField; +import org.apache.plc4x.java.api.model.PlcSubscriptionHandle; import org.apache.plc4x.java.api.types.PlcResponseCode; +import org.apache.plc4x.java.api.types.PlcSubscriptionType; +import org.apache.plc4x.java.api.value.PlcList; import org.apache.plc4x.java.api.value.PlcNull; import org.apache.plc4x.java.api.value.PlcValue; import org.apache.plc4x.java.can.api.CANFrame; @@ -31,6 +35,7 @@ import org.apache.plc4x.java.can.api.conversation.canopen.SDOUploadConversation; import org.apache.plc4x.java.can.configuration.CANConfiguration; import org.apache.plc4x.java.can.context.CANOpenDriverContext; import org.apache.plc4x.java.can.field.CANOpenField; +import org.apache.plc4x.java.can.field.CANOpenPDOField; import org.apache.plc4x.java.can.field.CANOpenSDOField; import org.apache.plc4x.java.can.socketcan.SocketCANConversation; import org.apache.plc4x.java.canopen.readwrite.*; @@ -49,18 +54,28 @@ import org.apache.plc4x.java.spi.generation.ReadBuffer; import org.apache.plc4x.java.spi.generation.WriteBuffer; import org.apache.plc4x.java.spi.messages.*; import org.apache.plc4x.java.spi.messages.utils.ResponseItem; +import org.apache.plc4x.java.spi.model.DefaultPlcConsumerRegistration; +import org.apache.plc4x.java.spi.model.InternalPlcSubscriptionHandle; +import org.apache.plc4x.java.spi.model.SubscriptionPlcField; import org.apache.plc4x.java.spi.transaction.RequestTransactionManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.time.Duration; +import java.time.Instant; +import java.util.Collection; +import java.util.Collections; import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; -public class CANOpenProtocolLogic extends Plc4xProtocolBase<SocketCANFrame> implements HasConfiguration<CANConfiguration> { +public class CANOpenProtocolLogic extends Plc4xProtocolBase<SocketCANFrame> implements HasConfiguration<CANConfiguration>, PlcSubscriber { private static final Duration REQUEST_TIMEOUT = Duration.ofSeconds(10L); private Logger logger = LoggerFactory.getLogger(CANOpenProtocolLogic.class); @@ -71,6 +86,8 @@ public class CANOpenProtocolLogic extends Plc4xProtocolBase<SocketCANFrame> impl private CANOpenDriverContext canContext; private CANConversation<CANFrame> conversation; + private Map<DefaultPlcConsumerRegistration, Consumer<PlcSubscriptionEvent>> consumers = new ConcurrentHashMap<>(); + @Override public void setConfiguration(CANConfiguration configuration) { this.configuration = configuration; @@ -130,7 +147,7 @@ public class CANOpenProtocolLogic extends Plc4xProtocolBase<SocketCANFrame> impl public CompletableFuture<PlcWriteResponse> write(PlcWriteRequest writeRequest) { CompletableFuture<PlcWriteResponse> response = new CompletableFuture<>(); if (writeRequest.getFieldNames().size() != 1) { - response.completeExceptionally(new IllegalArgumentException("Unsupported field")); + response.completeExceptionally(new IllegalArgumentException("You can write only one field at the time")); return response; } @@ -140,12 +157,16 @@ public class CANOpenProtocolLogic extends Plc4xProtocolBase<SocketCANFrame> impl return response; } - if (!(field instanceof CANOpenSDOField)) { - response.completeExceptionally(new IllegalArgumentException("Only CANOpenSDOField instances are supported")); + if (field instanceof CANOpenSDOField) { + writeInternally((InternalPlcWriteRequest) writeRequest, (CANOpenSDOField) field, response); return response; - }; + } + if (field instanceof CANOpenPDOField) { + writeInternally((InternalPlcWriteRequest) writeRequest, (CANOpenPDOField) field, response); + return response; + } - writeInternally((InternalPlcWriteRequest) writeRequest, (CANOpenSDOField) field, response); + response.completeExceptionally(new IllegalArgumentException("Only CANOpenSDOField instances are supported")); return response; } @@ -158,15 +179,31 @@ public class CANOpenProtocolLogic extends Plc4xProtocolBase<SocketCANFrame> impl try { download.execute((value, error) -> { String fieldName = writeRequest.getFieldNames().iterator().next(); - Map<String, PlcResponseCode> fields = new HashMap<>(); - fields.put(fieldName, PlcResponseCode.OK); - response.complete(new DefaultPlcWriteResponse(writeRequest, fields)); + response.complete(new DefaultPlcWriteResponse(writeRequest, Collections.singletonMap(fieldName, PlcResponseCode.OK))); }); } catch (Exception e) { response.completeExceptionally(e); } } + private void writeInternally(InternalPlcWriteRequest writeRequest, CANOpenPDOField field, CompletableFuture<PlcWriteResponse> response) { + PlcValue writeValue = writeRequest.getPlcValues().get(0); + + try { + String fieldName = writeRequest.getFieldNames().iterator().next(); + // + WriteBuffer buffer = DataItemIO.staticSerialize(writeValue, field.getCanOpenDataType(), writeValue.getLength() / 8, true); + if (buffer != null) { + context.sendToWire(new SocketCANFrame(field.getNodeId(), buffer.getData())); + response.complete(new DefaultPlcWriteResponse(writeRequest, Collections.singletonMap(fieldName, PlcResponseCode.OK))); + } else { + response.complete(new DefaultPlcWriteResponse(writeRequest, Collections.singletonMap(fieldName, PlcResponseCode.INVALID_DATA))); + } + } catch (Exception e) { + response.completeExceptionally(e); + } + } + public CompletableFuture<PlcReadResponse> read(PlcReadRequest readRequest) { CompletableFuture<PlcReadResponse> response = new CompletableFuture<>(); if (readRequest.getFieldNames().size() != 1) { @@ -190,9 +227,28 @@ public class CANOpenProtocolLogic extends Plc4xProtocolBase<SocketCANFrame> impl } @Override - public CompletableFuture<PlcSubscriptionResponse> subscribe(PlcSubscriptionRequest subscriptionRequest) { - ((InternalPlcSubscriptionRequest) subscriptionRequest).getSubscriptionFields().get(0).getPlcSubscriptionType(); - return super.subscribe(subscriptionRequest); + public CompletableFuture<PlcSubscriptionResponse> subscribe(PlcSubscriptionRequest request) { + InternalPlcSubscriptionRequest rq = (InternalPlcSubscriptionRequest) request; + + List<SubscriptionPlcField> fields = rq.getSubscriptionFields(); + + Map<String, ResponseItem<PlcSubscriptionHandle>> answers = new LinkedHashMap<>(); + DefaultPlcSubscriptionResponse response = new DefaultPlcSubscriptionResponse(rq, answers); + + for (Map.Entry<String, SubscriptionPlcField> entry : rq.getSubscriptionPlcFieldMap().entrySet()) { + SubscriptionPlcField subscription = entry.getValue(); + if (subscription.getPlcSubscriptionType() != PlcSubscriptionType.EVENT) { + answers.put(entry.getKey(), new ResponseItem<>(PlcResponseCode.UNSUPPORTED, null)); + } else if (!(subscription.getPlcField() instanceof CANOpenPDOField)) { + answers.put(entry.getKey(), new ResponseItem<>(PlcResponseCode.INVALID_ADDRESS, null)); + } else { + answers.put(entry.getKey(), new ResponseItem<>(PlcResponseCode.OK, + new CANOpenSubscriptionHandle(this, entry.getKey(), (CANOpenPDOField) subscription.getPlcField()) + )); + } + } + + return CompletableFuture.completedFuture(response); } private void readInternally(InternalPlcReadRequest readRequest, CANOpenSDOField field, CompletableFuture<PlcReadResponse> response) { @@ -220,6 +276,12 @@ public class CANOpenProtocolLogic extends Plc4xProtocolBase<SocketCANFrame> impl if (service != null) { logger.info("Decoded CANOpen {} from {}, message {}", service, Math.abs(service.getMin() - msg.getIdentifier()), payload); + + if (service.getPdo() && payload instanceof CANOpenPDOPayload) { + logger.info("Broadcasting PDO to subscribers"); + publishEvent(msg.getIdentifier(), (CANOpenPDOPayload) payload); + } + } else { logger.info("CAN message {}, {}", msg.getIdentifier(), msg); } @@ -234,6 +296,57 @@ public class CANOpenProtocolLogic extends Plc4xProtocolBase<SocketCANFrame> impl // } } + private void publishEvent(int nodeId, CANOpenPDOPayload payload) { + for (Map.Entry<DefaultPlcConsumerRegistration, Consumer<PlcSubscriptionEvent>> entry : consumers.entrySet()) { + DefaultPlcConsumerRegistration registration = entry.getKey(); + Consumer<PlcSubscriptionEvent> consumer = entry.getValue(); + + for (InternalPlcSubscriptionHandle handler : registration.getAssociatedHandles()) { + if (handler instanceof CANOpenSubscriptionHandle) { + CANOpenSubscriptionHandle handle = (CANOpenSubscriptionHandle) handler; + + if (handle.matches(nodeId)) { + CANOpenPDOField field = handle.getField(); + byte[] data = payload.getPdo().getData(); + try { + PlcValue value = DataItemIO.staticParse(new ReadBuffer(data, true), field.getCanOpenDataType(), data.length); + DefaultPlcSubscriptionEvent event = new DefaultPlcSubscriptionEvent( + Instant.now(), + Collections.singletonMap( + handle.getName(), + new ResponseItem<>(PlcResponseCode.OK, value) + ) + ); + consumer.accept(event); + } catch (ParseException e) { + logger.warn("Could not parse data to desired type: {}", field.getCanOpenDataType(), e); + DefaultPlcSubscriptionEvent event = new DefaultPlcSubscriptionEvent( + Instant.now(), + Collections.singletonMap( + handle.getName(), + new ResponseItem<>(PlcResponseCode.INVALID_DATA, new PlcNull()) + ) + ); + consumer.accept(event); + } + } + } + } + } + } + + @Override + public PlcConsumerRegistration register(Consumer<PlcSubscriptionEvent> consumer, Collection<PlcSubscriptionHandle> handles) { + final DefaultPlcConsumerRegistration consumerRegistration =new DefaultPlcConsumerRegistration(this, consumer, handles.toArray(new InternalPlcSubscriptionHandle[0])); + consumers.put(consumerRegistration, consumer); + return consumerRegistration; + } + + @Override + public void unregister(PlcConsumerRegistration registration) { + consumers.remove(registration); + } + @Override public void close(ConversationContext<SocketCANFrame> context) { diff --git a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/protocol/CANOpenSubscriptionHandle.java b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/protocol/CANOpenSubscriptionHandle.java new file mode 100644 index 0000000..07ecb3b --- /dev/null +++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/protocol/CANOpenSubscriptionHandle.java @@ -0,0 +1,28 @@ +package org.apache.plc4x.java.can.protocol; + +import org.apache.plc4x.java.can.field.CANOpenPDOField; +import org.apache.plc4x.java.spi.messages.PlcSubscriber; +import org.apache.plc4x.java.spi.model.DefaultPlcSubscriptionHandle; + +public class CANOpenSubscriptionHandle extends DefaultPlcSubscriptionHandle { + private final String name; + private final CANOpenPDOField field; + + public CANOpenSubscriptionHandle(PlcSubscriber subscriber, String name, CANOpenPDOField field) { + super(subscriber); + this.name = name; + this.field = field; + } + + public boolean matches(int identifier) { + return field.getNodeId() == 0 || field.getNodeId() == identifier; + } + + public String getName() { + return name; + } + + public CANOpenPDOField getField() { + return field; + } +}
