From 0e72fb00b2288983ea1969d6f0d785d30c60e717 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20Garc=C3=ADa?= Date: Fri, 18 Oct 2024 14:26:35 -0400 Subject: [PATCH 1/5] Fix coil read problem. --- .../base/optimizer/ModbusOptimizer.java | 58 ++++++++++--------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/plc4j/drivers/modbus/src/main/java/org/apache/plc4x/java/modbus/base/optimizer/ModbusOptimizer.java b/plc4j/drivers/modbus/src/main/java/org/apache/plc4x/java/modbus/base/optimizer/ModbusOptimizer.java index db838c6a0b0..76daa183867 100644 --- a/plc4j/drivers/modbus/src/main/java/org/apache/plc4x/java/modbus/base/optimizer/ModbusOptimizer.java +++ b/plc4j/drivers/modbus/src/main/java/org/apache/plc4x/java/modbus/base/optimizer/ModbusOptimizer.java @@ -19,6 +19,8 @@ package org.apache.plc4x.java.modbus.base.optimizer; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; import org.apache.plc4x.java.api.messages.PlcReadRequest; import org.apache.plc4x.java.api.messages.PlcReadResponse; import org.apache.plc4x.java.api.model.PlcTag; @@ -192,33 +194,7 @@ protected PlcReadResponse processReadResponses(PlcReadRequest readRequest, Map

(response.getResponseCode(), null)); - break; - } - - // Coils are read completely different from registers. - ModbusTagCoil coilTag = (ModbusTagCoil) modbusTag; - - // Calculate the byte that contains the response for this Coil - byte[] responseData = response.getResponseData(); - int bitPosition = coilTag.getAddress() - response.startingAddress; - int bytePosition = bitPosition / 8; - int bitPositionInByte = bitPosition % 8; - boolean isBitSet = (responseData[bytePosition] & (1 << bitPositionInByte)) != 0; - values.put(tagName, new DefaultPlcResponseItem<>(PlcResponseCode.OK, new PlcBOOL(isBitSet))); - break; - } - } - // Read a normal register. - else if (response.matchesRegister(modbusTag)) { + if (response.matchesRegister(modbusTag)) { // If this response was invalid, return all associated addresses as equally invalid. // TODO: Possibly it would be worth doing a single item request for each of these // tags in order to find out which ones are actually invalid as if one item in the @@ -407,9 +383,35 @@ public byte[] getResponseData() { public byte[] getResponseDataForTag(ModbusTag modbusTag) { byte[] itemData = new byte[modbusTag.getLengthBytes()]; - System.arraycopy(responseData, (modbusTag.getAddress() - startingAddress) * 2, itemData, 0, modbusTag.getLengthBytes()); + int value = 0; + switch(modbusTag.getDataType()) { + case BOOL: { + itemData = new byte[responseData.length]; + for (int i= 0; i < responseData.length; i++){ + itemData[i] = byteReverse(responseData[i]); + } + } + break; + default: + System.arraycopy(responseData, + (modbusTag.getAddress() - startingAddress) * 2, + itemData, 0, modbusTag.getLengthBytes()); + } + return itemData; } + + public static byte byteReverse(byte x) { + byte b = 0; + for (int i = 0; i < 8; ++i) { + b<<=1; + b|=( x &1); + x>>=1; + } + return b; + } + + } protected interface TagFactory { From 9bb6943451474999094dabde53aea49f7e913e65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20Garc=C3=ADa?= Date: Fri, 25 Oct 2024 13:42:39 -0400 Subject: [PATCH 2/5] Add test case for coils reading. --- .../base/optimizer/ModbusCoilArrayTest.java | 172 ++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 plc4j/drivers/modbus/src/test/java/org/apache/plc4x/java/modbus/base/optimizer/ModbusCoilArrayTest.java diff --git a/plc4j/drivers/modbus/src/test/java/org/apache/plc4x/java/modbus/base/optimizer/ModbusCoilArrayTest.java b/plc4j/drivers/modbus/src/test/java/org/apache/plc4x/java/modbus/base/optimizer/ModbusCoilArrayTest.java new file mode 100644 index 00000000000..1f7c69c8731 --- /dev/null +++ b/plc4j/drivers/modbus/src/test/java/org/apache/plc4x/java/modbus/base/optimizer/ModbusCoilArrayTest.java @@ -0,0 +1,172 @@ +/* + * 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.modbus.base.optimizer; + +import org.apache.commons.codec.binary.Hex; +import org.apache.plc4x.java.api.messages.PlcReadRequest; +import org.apache.plc4x.java.api.messages.PlcReadResponse; +import org.apache.plc4x.java.api.model.PlcTag; +import org.apache.plc4x.java.api.types.PlcResponseCode; +import org.apache.plc4x.java.modbus.base.tag.ModbusTagCoil; +import org.apache.plc4x.java.modbus.base.tag.ModbusTagHandler; +import org.apache.plc4x.java.modbus.base.tag.ModbusTagHoldingRegister; +import org.apache.plc4x.java.modbus.tcp.context.ModbusTcpContext; +import org.apache.plc4x.java.modbus.types.ModbusByteOrder; +import org.apache.plc4x.java.spi.connection.PlcTagHandler; +import org.apache.plc4x.java.spi.messages.DefaultPlcReadRequest; +import org.apache.plc4x.java.spi.messages.DefaultPlcReadResponse; +import org.apache.plc4x.java.spi.messages.PlcReader; +import org.apache.plc4x.java.spi.messages.utils.DefaultPlcResponseItem; +import org.apache.plc4x.java.spi.optimizer.BaseOptimizer; +import org.apache.plc4x.java.spi.values.PlcRawByteArray; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.apache.plc4x.java.spi.values.PlcBOOL; + +/* +* The objective of this test is to evaluate the response order for an array +* of coils that are continuously requested. +* +* The response pattern is previously defined in the devices under test +* in order to evaluate their standard response using the frame capture tool. +* +* Register Value +* 0x00001 true +* 0x00002 false +* 0x00003 true +* 0x00004 false +* 0x00005 true +* 0x00006 false +* 0x00007 true +* 0x00008 false +* 0x00009 false +* 0x00010 true +* 0x00011 false +* 0x00012 true +* 0x00013 false +* 0x00014 true +* 0x00015 false +* 0x00016 true +* 0x00017 false +* 0x00018 true +* 0x00019 false +* 0x00020 true +* +* This test was generated against the response patterns of the following devices. +* +* . OpenModSim +* . ModbusTools +* . Applicom +* +* +* Rx: 00 48 00 00 00 06 01 01 00 00 00 14 +* Tx: 00 48 00 00 00 06 01 01 03 55 AA 0A +* ^^ ^^ ^^ +* Test parser: +* https://rapidscada.net/modbus/ +* +*/ +public class ModbusCoilArrayTest { + + @Test + public void testLittleEndianByteSwap() throws Exception { + Map input = new LinkedHashMap<>(); + input.put("variable01", new String[]{"coil:1:BOOL[20]", "false"}); + String[] response = {"true","false","true","false","true","false","true","false","false","true","false","true","false","true","false","true","false","true","false","true"}; + + PlcReader mockPlcReader = Mockito.mock(PlcReader.class); + PlcTagHandler modbusTagHandler = new ModbusTagHandler(); + PlcReadRequest.Builder builder = new DefaultPlcReadRequest.Builder(mockPlcReader, modbusTagHandler); + for (String name : input.keySet()) { + String[] data = input.get(name); + builder.addTagAddress(name, data[0]); + } + PlcReadRequest readRequest = builder.build(); + + ModbusOptimizer sut = new ModbusOptimizer(); + + ModbusTcpContext mockContext = Mockito.mock(ModbusTcpContext.class); + + //TODO: In the optimizer it reads the byte array as BIG_ENDIAN, + // regardless of what is placed here. + Mockito.when(mockContext.getByteOrder()).thenReturn(ModbusByteOrder.LITTLE_ENDIAN); + + Mockito.when(mockContext.getMaxCoilsPerRequest()).thenReturn(2000); + Mockito.when(mockContext.getMaxRegistersPerRequest()).thenReturn(125); + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Do the first part of the optimizer ... split up into multiple requests ... + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + List optimizedReadRequests = sut.processReadRequest(readRequest, mockContext); + + // Validate the results of this first step. + + Assertions.assertNotNull(optimizedReadRequests); + Assertions.assertEquals(1, optimizedReadRequests.size()); + + // Check the expected first request (Coil) + PlcReadRequest firstRequest = optimizedReadRequests.get(0); + Assertions.assertEquals(1, firstRequest.getNumberOfTags()); + Assertions.assertEquals("coils0", firstRequest.getTagNames().stream().findFirst().orElseThrow()); + PlcTag coilsTag = firstRequest.getTag("coils0"); + Assertions.assertInstanceOf(ModbusTagCoil.class, coilsTag); + ModbusTagCoil coil = (ModbusTagCoil) coilsTag; + Assertions.assertEquals(0, coil.getAddress()); + Assertions.assertEquals(20, coil.getNumberOfElements()); + + // Prepare the results as we got them on the wire. + + Map> readResponses = new HashMap<>(); + readResponses.put(firstRequest, new BaseOptimizer.SubResponse<>( + new DefaultPlcReadResponse(firstRequest, Map.of( + "coils0", new DefaultPlcResponseItem<>(PlcResponseCode.OK, new PlcRawByteArray(Hex.decodeHex("55AA0A"))))))); + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Process the responses + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + PlcReadResponse readResponse = sut.processReadResponses(readRequest, readResponses, mockContext); + + // Check if there were no invalid items + List failedTags = readResponse.getTagNames().stream().filter(tagName -> readResponse.getResponseCode(tagName) != PlcResponseCode.OK).collect(Collectors.toList()); + failedTags.forEach(failedTag -> Assertions.fail("Field " + failedTag + "failed.")); + + + var plcResValues = readResponse.getObject("variable01"); + + List plcValues = (List) plcResValues ; + // Check if the returned values match the expected ones + + int i = 0; + for (PlcBOOL plcBool:plcValues) { + Assertions.assertEquals(response[i], plcBool.getString()); + i++; + } + } + +} From ea130df7fbc4d705ec965b911e6cb531f9e5e9e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20Garc=C3=ADa?= Date: Mon, 28 Oct 2024 12:47:28 -0400 Subject: [PATCH 3/5] Adds COUNTER type for read/write. --- .../knxnetip/readwrite/KnxManufacturer.java | 5 +- .../java/s7/readwrite/TransportSize.java | 13 +++ .../plc4x/java/s7/readwrite/tag/S7Tag.java | 19 ++- .../java/s7/readwrite/DatatypesTest.java | 19 ++- .../s7/readwrite/ManualS7CounterTest.java | 108 ++++++++++++++++++ .../protocols/knxnetip/knx-master-data.mspec | 5 +- .../src/main/resources/protocols/s7/s7.mspec | 1 + 7 files changed, 161 insertions(+), 9 deletions(-) create mode 100644 plc4j/drivers/s7/src/test/java/org/apache/plc4x/java/s7/readwrite/ManualS7CounterTest.java diff --git a/plc4j/drivers/knxnetip/src/main/generated/org/apache/plc4x/java/knxnetip/readwrite/KnxManufacturer.java b/plc4j/drivers/knxnetip/src/main/generated/org/apache/plc4x/java/knxnetip/readwrite/KnxManufacturer.java index 7df49b8722f..01fa99e2fd8 100644 --- a/plc4j/drivers/knxnetip/src/main/generated/org/apache/plc4x/java/knxnetip/readwrite/KnxManufacturer.java +++ b/plc4j/drivers/knxnetip/src/main/generated/org/apache/plc4x/java/knxnetip/readwrite/KnxManufacturer.java @@ -802,9 +802,10 @@ public enum KnxManufacturer { M_XIAMEN_LEELEN_TECHNOLOGY_CO__LTD_( (int) 689, (int) 747, (String) "Xiamen Leelen Technology Co.,Ltd."), M_LEDNX((int) 690, (int) 748, (String) "LedNX"), - M_ABB___RESERVED((int) 691, (int) 43954, (String) "ABB - reserved"), + M_EBELONG((int) 691, (int) 749, (String) "ebelong"), + M_ABB___RESERVED((int) 692, (int) 43954, (String) "ABB - reserved"), M_BUSCH_JAEGER_ELEKTRO___RESERVED( - (int) 692, (int) 43959, (String) "Busch-Jaeger Elektro - reserved"); + (int) 693, (int) 43959, (String) "Busch-Jaeger Elektro - reserved"); private static final Map map; static { diff --git a/plc4j/drivers/s7/src/main/generated/org/apache/plc4x/java/s7/readwrite/TransportSize.java b/plc4j/drivers/s7/src/main/generated/org/apache/plc4x/java/s7/readwrite/TransportSize.java index 61f68f789c9..dcbc23bb284 100644 --- a/plc4j/drivers/s7/src/main/generated/org/apache/plc4x/java/s7/readwrite/TransportSize.java +++ b/plc4j/drivers/s7/src/main/generated/org/apache/plc4x/java/s7/readwrite/TransportSize.java @@ -195,6 +195,19 @@ public enum TransportSize { DataTransportSize.BYTE_WORD_DWORD, (String) "IEC61131_ULINT", TransportSize.INT), + COUNTER( + (short) 0x1C, + (boolean) true, + (boolean) false, + (short) 0x1C, + (short) 2, + (boolean) true, + (boolean) true, + (short) 'X', + (boolean) true, + DataTransportSize.OCTET_STRING, + (String) "IEC61131_INT", + null), REAL( (short) 0x0E, (boolean) true, diff --git a/plc4j/drivers/s7/src/main/java/org/apache/plc4x/java/s7/readwrite/tag/S7Tag.java b/plc4j/drivers/s7/src/main/java/org/apache/plc4x/java/s7/readwrite/tag/S7Tag.java index 5b8e1328e8d..0019e61aa6b 100644 --- a/plc4j/drivers/s7/src/main/java/org/apache/plc4x/java/s7/readwrite/tag/S7Tag.java +++ b/plc4j/drivers/s7/src/main/java/org/apache/plc4x/java/s7/readwrite/tag/S7Tag.java @@ -77,9 +77,22 @@ public S7Tag(TransportSize dataType, MemoryArea memoryArea, this.dataType = dataType; this.memoryArea = memoryArea; this.blockNumber = blockNumber; - this.byteOffset = byteOffset; - this.bitOffset = bitOffset; this.numElements = numElements; + + //TODO: Should this address conversion be done in the mspec? + switch (dataType) { + case COUNTER: { + this.bitOffset = (byte) ((byteOffset) & 0x0007); + this.byteOffset = (byteOffset >> 3); + break; + } + default :{ + this.byteOffset = byteOffset; + this.bitOffset = bitOffset; + } + + } + } @Override @@ -97,6 +110,8 @@ public PlcValueType getPlcValueType() { return PlcValueType.DATE_AND_LTIME; case "DTL": return PlcValueType.DATE_AND_LTIME; + case "COUNTER": + return PlcValueType.WORD; default: return PlcValueType.valueOf(dataType.name()); } diff --git a/plc4j/drivers/s7/src/test/java/org/apache/plc4x/java/s7/readwrite/DatatypesTest.java b/plc4j/drivers/s7/src/test/java/org/apache/plc4x/java/s7/readwrite/DatatypesTest.java index 45d58e33091..d17a163c4a1 100644 --- a/plc4j/drivers/s7/src/test/java/org/apache/plc4x/java/s7/readwrite/DatatypesTest.java +++ b/plc4j/drivers/s7/src/test/java/org/apache/plc4x/java/s7/readwrite/DatatypesTest.java @@ -27,7 +27,17 @@ public class DatatypesTest { public static void main(String[] args) throws Exception { //try (PlcConnection connection = new DefaultPlcDriverManager().getConnection("s7://192.168.24.83")) { - try (PlcConnection connection = new DefaultPlcDriverManager().getConnection("s7://192.168.23.30")) { + // + String URL1 = "s7://192.168.23.30"; + + // + String URL2 = "s7://192.168.0.47?remote-rack=0&" + + "remote-slot=3&" + + "controller-type=S7_400&read-timeout=8&" + + "ping=false&ping-time=2&retry-time=3"; + + + try (PlcConnection connection = new DefaultPlcDriverManager().getConnection(URL1)) { final PlcReadRequest.Builder builder = connection.readRequestBuilder(); builder.addTagAddress("bool-value-1", "%DB2:0.0:BOOL"); // true builder.addTagAddress("bool-value-2", "%DB2:2.1:BOOL"); // false @@ -53,9 +63,12 @@ public static void main(String[] args) throws Exception { builder.addTagAddress("udint-array", "%DB2:50:UDINT[2]"); // 12345, 23456 builder.addTagAddress("real-value", "%DB2:58:REAL"); // 3.14159 builder.addTagAddress("real-array", "%DB2:62:REAL[2]"); // 12.345, 12.345 - builder.addTagAddress("lreal-value", "%DB2:70:LREAL"); // 3.14159265358979 - builder.addTagAddress("lreal-array", "%DB2:78:LREAL[2]"); // 1.2345, -1.2345 + +// builder.addTagAddress("lreal-value", "%DB2:70:LREAL"); // 3.14159265358979 +// builder.addTagAddress("lreal-array", "%DB2:78:LREAL[2]"); // 1.2345, -1.2345 + builder.addTagAddress("string-value", "%DB2:94:STRING(10)"); // "Hurz" + // When reading a sized STRING array, this has to be translated into multiple items //builder.addField("string-array", "%DB2:350:STRING(10)[2]"); // "Wolf", "Lamm" builder.addTagAddress("time-value", "%DB2:862:TIME"); // 1234ms diff --git a/plc4j/drivers/s7/src/test/java/org/apache/plc4x/java/s7/readwrite/ManualS7CounterTest.java b/plc4j/drivers/s7/src/test/java/org/apache/plc4x/java/s7/readwrite/ManualS7CounterTest.java new file mode 100644 index 00000000000..1ebadc9c77d --- /dev/null +++ b/plc4j/drivers/s7/src/test/java/org/apache/plc4x/java/s7/readwrite/ManualS7CounterTest.java @@ -0,0 +1,108 @@ +/* + * 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 + * + * https://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.s7.readwrite; + +import org.apache.plc4x.java.DefaultPlcDriverManager; +import org.apache.plc4x.java.api.PlcConnection; +import org.apache.plc4x.java.api.messages.PlcReadRequest; +import org.apache.plc4x.java.api.messages.PlcReadResponse; +import org.apache.plc4x.java.api.messages.PlcWriteRequest; +import org.apache.plc4x.java.api.messages.PlcWriteResponse; +import org.apache.plc4x.java.api.types.PlcResponseCode; +import org.apache.plc4x.java.api.value.PlcValue; + +public class ManualS7CounterTest { + + public static void main(String[] args) throws Exception { + + //Maybe a S7-1200 + String URL1 = "s7://192.168.23.30"; + + //S7-400 + String URL2 = "s7://192.168.0.47?remote-rack=0&" + + "remote-slot=3&" + + "controller-type=S7_400&read-timeout=8&" + + "ping=false&ping-time=2&retry-time=3"; + + + try (PlcConnection connection = new DefaultPlcDriverManager().getConnection(URL2)) { + final PlcReadRequest.Builder readBuilder = connection.readRequestBuilder(); + + final PlcWriteRequest.Builder writeBuilder = connection.writeRequestBuilder(); + + writeBuilder.addTagAddress("counter-0", "%C0:COUNTER", Integer.decode("0x0123")); + writeBuilder.addTagAddress("counter-1", "%C9:COUNTER", Integer.decode("0x0456")); + writeBuilder.addTagAddress("counter-2", "%C12:COUNTER", Integer.decode("0x0789")); + writeBuilder.addTagAddress("counter-3", "%C18:COUNTER", Integer.decode("0x0012")); + + final PlcWriteRequest writeRequest = writeBuilder.build(); + final PlcWriteResponse writeResposne = writeRequest.execute().get(); + + if ( writeResposne.getResponseCode("counter-3") == PlcResponseCode.OK ){ + System.out.println("Write the counter"); + } else { + System.out.println("Problems...."); + } + + readBuilder.addTagAddress("counter-0", "%C0:COUNTER"); // Set this counter to 123 + readBuilder.addTagAddress("counter-1", "%C9:COUNTER"); // Set this counter to 456 + readBuilder.addTagAddress("counter-2", "%C12:COUNTER"); // Set this counter to 789 + readBuilder.addTagAddress("counter-3", "%C18:COUNTER"); // Set this counter to 012 + readBuilder.addTagAddress("counters", "%C0:COUNTER[20]"); // Set this counter to 000 + + final PlcReadRequest readRequest = readBuilder.build(); + + final PlcReadResponse readResponse = readRequest.execute().get(); + + System.out.println(readResponse); + + byte[] responseBytes = readResponse.getPlcValue("counter-0").getRaw(); + + short bcd_0 = readResponse.getShort("counter-0"); + short bcd_1 = readResponse.getShort("counter-1"); + short bcd_2 = readResponse.getShort("counter-2"); + short bcd_3 = readResponse.getShort("counter-3"); + System.out.println("counter-0 = " + convertShortToBcd(bcd_0)); + System.out.println("counter-1 = " + convertShortToBcd(bcd_1)); + System.out.println("counter-2 = " + convertShortToBcd(bcd_2)); + System.out.println("counter-3 = " + convertShortToBcd(bcd_3)); + + PlcValue plcValues = readResponse.getPlcValue("counters"); + + System.out.println(plcValues.toString()); + + if (plcValues.isList()) { + System.out.println(plcValues.getIndex(0).getShort() == bcd_0); + System.out.println(plcValues.getIndex(9).getShort() == bcd_1); + System.out.println(plcValues.getIndex(12).getShort() == bcd_2); + System.out.println(plcValues.getIndex(18).getShort() == bcd_3); + } + + + } + } + + + private static short convertShortToBcd(short incomingShort) { + return (short) (((incomingShort >> 8) & 0x0F) * 100 + + ((incomingShort >> 4) & 0x0F) * 10 + + (incomingShort & 0x000f)); + } + +} diff --git a/protocols/knxnetip/src/main/generated/protocols/knxnetip/knx-master-data.mspec b/protocols/knxnetip/src/main/generated/protocols/knxnetip/knx-master-data.mspec index 1af52d8eb5e..d0647c18742 100644 --- a/protocols/knxnetip/src/main/generated/protocols/knxnetip/knx-master-data.mspec +++ b/protocols/knxnetip/src/main/generated/protocols/knxnetip/knx-master-data.mspec @@ -1440,8 +1440,9 @@ ['688' M_ITALIANA_CONDUTTORI_SRL ['746', '"Italiana Conduttori srl"']] ['689' M_XIAMEN_LEELEN_TECHNOLOGY_CO__LTD_ ['747', '"Xiamen Leelen Technology Co.,Ltd."']] ['690' M_LEDNX ['748', '"LedNX"']] - ['691' M_ABB___RESERVED ['43954', '"ABB - reserved"']] - ['692' M_BUSCH_JAEGER_ELEKTRO___RESERVED ['43959', '"Busch-Jaeger Elektro - reserved"']] + ['691' M_EBELONG ['749', '"ebelong"']] + ['692' M_ABB___RESERVED ['43954', '"ABB - reserved"']] + ['693' M_BUSCH_JAEGER_ELEKTRO___RESERVED ['43959', '"Busch-Jaeger Elektro - reserved"']] ] diff --git a/protocols/s7/src/main/resources/protocols/s7/s7.mspec b/protocols/s7/src/main/resources/protocols/s7/s7.mspec index 5e7ef2888b0..c7faf3f891b 100644 --- a/protocols/s7/src/main/resources/protocols/s7/s7.mspec +++ b/protocols/s7/src/main/resources/protocols/s7/s7.mspec @@ -897,6 +897,7 @@ ['0x0B' UDINT ['0x07' , 'D' , '4' , 'INT' , 'INTEGER' , 'IEC61131_UDINT' , 'false' , 'false' , 'true' , 'true' , 'true' ]] ['0x0C' LINT ['0x00' , 'X' , '8' , 'INT' , 'BYTE_WORD_DWORD' , 'IEC61131_LINT' , 'false' , 'false' , 'false' , 'true' , 'false' ]] ['0x0D' ULINT ['0x00' , 'X' , '8' , 'INT' , 'BYTE_WORD_DWORD' , 'IEC61131_ULINT' , 'false' , 'false' , 'false' , 'true' , 'false' ]] + ['0x1C' COUNTER ['0x1C' , 'X' , '2' , 'null' , 'OCTET_STRING' , 'IEC61131_INT' , 'true' , 'true' , 'true' , 'true' , 'false' ]] // Floating point values ['0x0E' REAL ['0x08' , 'D' , '4' , 'null' , 'REAL' , 'IEC61131_REAL' , 'true' , 'true' , 'true' , 'true' , 'true' ]] From eb05ae9b1a060bb83b7e0001ca18479dd02dd5eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20Garc=C3=ADa?= Date: Sun, 1 Dec 2024 19:34:02 -0400 Subject: [PATCH 4/5] Fix read coil and inputregisters. --- .../base/optimizer/ModbusOptimizer.java | 41 +++++++++++++++---- .../plc4x/java/modbus/ManualDriverTest.java | 2 +- .../modbus/ManualModbusTCPDriverTest.java | 33 ++++++++++++--- .../optimizer/LittleEndianByteSwapTest.java | 9 ++-- 4 files changed, 69 insertions(+), 16 deletions(-) diff --git a/plc4j/drivers/modbus/src/main/java/org/apache/plc4x/java/modbus/base/optimizer/ModbusOptimizer.java b/plc4j/drivers/modbus/src/main/java/org/apache/plc4x/java/modbus/base/optimizer/ModbusOptimizer.java index 76daa183867..b93405ea28b 100644 --- a/plc4j/drivers/modbus/src/main/java/org/apache/plc4x/java/modbus/base/optimizer/ModbusOptimizer.java +++ b/plc4j/drivers/modbus/src/main/java/org/apache/plc4x/java/modbus/base/optimizer/ModbusOptimizer.java @@ -206,7 +206,21 @@ protected PlcReadResponse processReadResponses(PlcReadRequest readRequest, Map

2)) { + byte[] ret = new byte[2]; + ret[1] = 0x00; + ret[0] = (isSet(responseData, modbusTag.getLogicalAddress()) ?(byte) 0x01 : 0x00); + readBuffer = getReadBuffer(ret, ModbusByteOrder.BIG_ENDIAN); + } else { + readBuffer = getReadBuffer(responseData, ModbusByteOrder.BIG_ENDIAN); + } + } else { + readBuffer = getReadBuffer(responseData, modbusContext.getByteOrder()); + } + try { PlcValue plcValue = DataItem.staticParse(readBuffer, modbusTag.getDataType(), modbusTag.getNumberOfElements(), @@ -383,13 +397,19 @@ public byte[] getResponseData() { public byte[] getResponseDataForTag(ModbusTag modbusTag) { byte[] itemData = new byte[modbusTag.getLengthBytes()]; - int value = 0; + int value = 0; switch(modbusTag.getDataType()) { case BOOL: { itemData = new byte[responseData.length]; - for (int i= 0; i < responseData.length; i++){ - itemData[i] = byteReverse(responseData[i]); - } + if ((modbusTag instanceof ModbusTagCoil) || (modbusTag instanceof ModbusTagDiscreteInput)) { + for (int i= 0; i < responseData.length; i++){ + itemData[i] = byteReverse(responseData[i]); + } + } else { + for (int i= 0; i < responseData.length; i++){ + itemData[i] = responseData[i]; + } + } } break; default: @@ -410,10 +430,17 @@ public static byte byteReverse(byte x) { } return b; } - - + } + + public boolean isSet(byte[] arr, int bit) { + int index = bit / 8; + int bitPosition = 8 - bit % 8; + return (arr[index] >> bitPosition & 1) == 1; + } + + protected interface TagFactory { PlcTag createTag(int address, int count, ModbusDataType dataType); } diff --git a/plc4j/drivers/modbus/src/test/java/org/apache/plc4x/java/modbus/ManualDriverTest.java b/plc4j/drivers/modbus/src/test/java/org/apache/plc4x/java/modbus/ManualDriverTest.java index 297adf5eb3b..2ab15cf87a7 100644 --- a/plc4j/drivers/modbus/src/test/java/org/apache/plc4x/java/modbus/ManualDriverTest.java +++ b/plc4j/drivers/modbus/src/test/java/org/apache/plc4x/java/modbus/ManualDriverTest.java @@ -35,7 +35,7 @@ public class ManualDriverTest { */ public static void main(String[] args) throws Exception { //final PlcConnection connection = new DefaultPlcDriverManager().getConnection("modbus-tcp://10.211.55.3?default-payload-byte-order=BIG_ENDIAN"); - final PlcConnection connection = new DefaultPlcDriverManager().getConnection("modbus-tcp://10.211.55.3?default-payload-byte-order=LITTLE_ENDIAN"); + final PlcConnection connection = new DefaultPlcDriverManager().getConnection("modbus-tcp://10.10.1.200:10502?default-payload-byte-order=LITTLE_ENDIAN"); //final PlcConnection connection = new DefaultPlcDriverManager().getConnection("modbus-tcp://10.211.55.3?default-payload-byte-order=BIG_ENDIAN_BYTE_SWAP"); //final PlcConnection connection = new DefaultPlcDriverManager().getConnection("modbus-tcp://10.211.55.3?default-payload-byte-order=LITTLE_ENDIAN_BYTE_SWAP"); final PlcReadRequest readRequest = connection.readRequestBuilder() diff --git a/plc4j/drivers/modbus/src/test/java/org/apache/plc4x/java/modbus/ManualModbusTCPDriverTest.java b/plc4j/drivers/modbus/src/test/java/org/apache/plc4x/java/modbus/ManualModbusTCPDriverTest.java index 46f3b02d100..db1c5ee7db7 100644 --- a/plc4j/drivers/modbus/src/test/java/org/apache/plc4x/java/modbus/ManualModbusTCPDriverTest.java +++ b/plc4j/drivers/modbus/src/test/java/org/apache/plc4x/java/modbus/ManualModbusTCPDriverTest.java @@ -30,6 +30,29 @@ public class ManualModbusTCPDriverTest extends ManualTest { * * Located in "main" * + * Reference server/client: ModbusTools + * https://github.com/serhmarch/ModbusTools/releases/tag/v0.3.8 + * + * If you report any improvement points on the Modbus driver, + * please run the tests on the reference device indicated above. + * One of the virtues and weaknesses of the Modbus protocol is + * that it gives creative freedom to manufacturers for the implementation + * of Scalar types, so the indicated device is the reference. + * + * Modbus Application Protocol Specification, V1.1.b + * Section 4.2 Data Encoding + * MODBUS uses a ‘big-Endian’ representation for addresses and data items. + * This means that when a numerical quantity larger than a single byte is + * transmitted, the most significant byte is sent first. So for example + * + * Register size value + * 16 - bits 0x1234 the first byte sent is 0x12 then 0x34 + * + * + * https://www.h-schmidt.net/FloatConverter/IEEE754.html + * 123456.00 -> 47 f1 20 00 + * Modbus -> 20 00 47 f1 + hurz_BOOL := TRUE; hurz_BYTE := 42; @@ -69,22 +92,22 @@ public ManualModbusTCPDriverTest(String connectionString) { } public static void main(String[] args) throws Exception { - ManualModbusTCPDriverTest test = new ManualModbusTCPDriverTest("modbus-tcp://192.168.23.30"); + ManualModbusTCPDriverTest test = new ManualModbusTCPDriverTest("modbus-tcp://10.10.1.200:10502?default-payload-byte-order=LITTLE_ENDIAN_BYTE_SWAP"); test.addTestCase("holding-register:1:BOOL", new PlcBOOL(true)); // 0001 test.addTestCase("holding-register:2:BYTE", new PlcBYTE(42)); // 2A //test.addTestCase("holding-register:3:WORD", new PlcWORD(42424)); // A5B8 - test.addTestCase("holding-register:4:DWORD", new PlcDWORD(4242442424L)); // FCDE 88B8 + test.addTestCase("holding-register:4:DWORD", new PlcDWORD(424244242L)); // 1949 7412 // test.addTestCase("holding-register:6:LWORD", new PlcLWORD(4242442424242424242L)); // FCDE 88B8 FCDE 88B8 test.addTestCase("holding-register:10:SINT", new PlcSINT(-42)); // D6 test.addTestCase("holding-register:11:USINT", new PlcUSINT(42)); // 2A test.addTestCase("holding-register:12:INT", new PlcINT(-2424)); // F688 test.addTestCase("holding-register:13:UINT", new PlcUINT(42424)); // A5B8 - test.addTestCase("holding-register:14:DINT", new PlcDINT(-242442424)); // F18C 9F48 - test.addTestCase("holding-register:16:UDINT", new PlcUDINT(4242442424L));// FCDE 88B8 + test.addTestCase("holding-register:14:DINT", new PlcDINT(-242442424)); // 1949 7412 + test.addTestCase("holding-register:16:UDINT", new PlcUDINT(424244242L));// FCDE 88B8 test.addTestCase("holding-register:18:LINT", new PlcLINT(-4242442424242424242L));// C51F D117 B2FB B64E test.addTestCase("holding-register:22:ULINT", new PlcULINT(4242442424242424242L));// 3AE0 2EE8 4D04 49B2 test.addTestCase("holding-register:26:REAL", new PlcREAL(3.141593F));// 4049 0FDC - test.addTestCase("holding-register:28:LREAL", new PlcLREAL(2.71828182846D)); // 4005 BF0A 8B14 5FCF + test.addTestCase("holding-register:28:LREAL", new PlcLREAL(2.71)); // 4005 BF0A 8B14 5FCF //test.addTestCase("holding-register:32:TIME", "PT1.234S"); // 04D2 //test.addTestCase("holding-register::LTIME", "PT24015H23M12.034002044S"); //test.addTestCase("holding-register::DATE", "1998-03-28"); diff --git a/plc4j/drivers/modbus/src/test/java/org/apache/plc4x/java/modbus/base/optimizer/LittleEndianByteSwapTest.java b/plc4j/drivers/modbus/src/test/java/org/apache/plc4x/java/modbus/base/optimizer/LittleEndianByteSwapTest.java index cad86516331..37f6c5fb166 100644 --- a/plc4j/drivers/modbus/src/test/java/org/apache/plc4x/java/modbus/base/optimizer/LittleEndianByteSwapTest.java +++ b/plc4j/drivers/modbus/src/test/java/org/apache/plc4x/java/modbus/base/optimizer/LittleEndianByteSwapTest.java @@ -98,6 +98,7 @@ public void testLittleEndianByteSwap() throws Exception { input.put("variable44", new String[]{"holding-register:177:REAL", "0.3768"}); input.put("variable45", new String[]{"holding-register:181:REAL", "0.0"}); input.put("variable46", new String[]{"holding-register:185:REAL", "-0.01143"}); + input.put("variable47", new String[]{"coil:1:BOOL", "false"}); input.put("variable48", new String[]{"coil:3:BOOL", "false"}); input.put("variable49", new String[]{"coil:5:BOOL", "true"}); @@ -183,9 +184,9 @@ public void testLittleEndianByteSwap() throws Exception { input.put("variable129", new String[]{"coil:165:BOOL", "false"}); input.put("variable130", new String[]{"coil:167:BOOL", "false"}); input.put("variable131", new String[]{"coil:169:BOOL", "false"}); - input.put("variable132", new String[]{"coil:171:BOOL", "false"}); + input.put("variable132", new String[]{"coil:171:BOOL", "true"}); input.put("variable133", new String[]{"coil:173:BOOL", "false"}); - input.put("variable134", new String[]{"coil:175:BOOL", "false"}); + input.put("variable134", new String[]{"coil:175:BOOL", "true"}); PlcReader mockPlcReader = Mockito.mock(PlcReader.class); PlcTagHandler modbusTagHandler = new ModbusTagHandler(); PlcReadRequest.Builder builder = new DefaultPlcReadRequest.Builder(mockPlcReader, modbusTagHandler); @@ -248,7 +249,8 @@ public void testLittleEndianByteSwap() throws Exception { Map> readResponses = new HashMap<>(); readResponses.put(firstRequest, new BaseOptimizer.SubResponse<>( new DefaultPlcReadResponse(firstRequest, Map.of( - "coils0", new DefaultPlcResponseItem<>(PlcResponseCode.OK, new PlcRawByteArray(Hex.decodeHex("3060480c00c084000000000000000000000000000000"))))))); + //"coils0", new DefaultPlcResponseItem<>(PlcResponseCode.OK, new PlcRawByteArray(Hex.decodeHex("3060480c00c084000000000000000000000000000000"))))))); + "coils0", new DefaultPlcResponseItem<>(PlcResponseCode.OK, new PlcRawByteArray(Hex.decodeHex("10404004004004000000000000000000000000000044"))))))); readResponses.put(secondRequest, new BaseOptimizer.SubResponse<>( new DefaultPlcReadResponse(secondRequest, Map.of( "registers0", new DefaultPlcResponseItem<>(PlcResponseCode.OK, new PlcRawByteArray(Hex.decodeHex("134141b000000000f80146c3000000003852c31f000000000b7841ae00000000fc0046e000000000028fc31f00000000c50441ab00000000540046dd00000000c000c31e000000006e973f32000000009998420300000000a5e33c9b00000000ccd041fc00000000020c3f2f00000000c3f9409e0000000047ae3f39000000009fbe3f3a00000000fbe83ff900000000d70a3aa30000000033333e33000000008f5c412400000000b83143d4000000005c293f5700000000c28f3f0500000000ac093f3c00000000fbe7413a0000000000000000000000000000000000000000e0df3f3b0000000023a33f3900000000a3d74220"))))))); @@ -270,6 +272,7 @@ public void testLittleEndianByteSwap() throws Exception { for (String name : input.keySet()) { String[] data = input.get(name); String readValue = readResponse.getString(name); + System.out.println(name + " : " + data[1] + " : " + readValue ); Assertions.assertEquals(data[1], readValue); } } From a6703378e6dd29ed768a71cafd95ecb659ae724d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20Garc=C3=ADa?= Date: Sun, 1 Dec 2024 20:17:58 -0400 Subject: [PATCH 5/5] Fix Bacnet Id enum duplicate. --- .../apache/plc4x/java/bacnetip/readwrite/BACnetVendorId.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plc4j/drivers/bacnet/src/main/generated/org/apache/plc4x/java/bacnetip/readwrite/BACnetVendorId.java b/plc4j/drivers/bacnet/src/main/generated/org/apache/plc4x/java/bacnetip/readwrite/BACnetVendorId.java index 65e2b593452..2c331debb5d 100644 --- a/plc4j/drivers/bacnet/src/main/generated/org/apache/plc4x/java/bacnetip/readwrite/BACnetVendorId.java +++ b/plc4j/drivers/bacnet/src/main/generated/org/apache/plc4x/java/bacnetip/readwrite/BACnetVendorId.java @@ -50,7 +50,7 @@ public enum BACnetVendorId { TACAB((int) 19, (int) 19, (String) "TAC AB"), HEWLETT_PACKARD_COMPANY((int) 20, (int) 20, (String) "Hewlett-Packard Company"), DORSETTES_INC((int) 21, (int) 21, (String) "Dorsette’s Inc."), - SIEMENS_SCHWEIZAG_FORMERLY_CERBERUSAG( + SIEMENS_SCHWEIZA_FORMERLY_CERBERUSAG( (int) 22, (int) 22, (String) "Siemens Schweiz AG (Formerly: Cerberus AG)"), YORK_CONTROLS_GROUP((int) 23, (int) 23, (String) "York Controls Group"), AUTOMATED_LOGIC_CORPORATION((int) 24, (int) 24, (String) "Automated Logic Corporation"),