| /* |
| * 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.s7.readwrite.protocol; |
| |
| import io.netty.buffer.ByteBuf; |
| import io.netty.buffer.Unpooled; |
| import org.apache.commons.lang3.tuple.ImmutablePair; |
| import org.apache.commons.lang3.tuple.Pair; |
| import org.apache.plc4x.java.api.exceptions.PlcProtocolException; |
| import org.apache.plc4x.java.api.exceptions.PlcRuntimeException; |
| import org.apache.plc4x.java.api.messages.PlcReadRequest; |
| import org.apache.plc4x.java.api.messages.PlcReadResponse; |
| import org.apache.plc4x.java.api.messages.PlcResponse; |
| import org.apache.plc4x.java.api.model.PlcField; |
| import org.apache.plc4x.java.api.types.PlcResponseCode; |
| import org.apache.plc4x.java.s7.readwrite.COTPPacket; |
| import org.apache.plc4x.java.s7.readwrite.COTPPacketConnectionRequest; |
| import org.apache.plc4x.java.s7.readwrite.COTPPacketConnectionResponse; |
| import org.apache.plc4x.java.s7.readwrite.COTPPacketData; |
| import org.apache.plc4x.java.s7.readwrite.COTPParameter; |
| import org.apache.plc4x.java.s7.readwrite.COTPParameterCalledTsap; |
| import org.apache.plc4x.java.s7.readwrite.COTPParameterCallingTsap; |
| import org.apache.plc4x.java.s7.readwrite.COTPParameterTpduSize; |
| import org.apache.plc4x.java.s7.readwrite.S7Address; |
| import org.apache.plc4x.java.s7.readwrite.S7AddressAny; |
| import org.apache.plc4x.java.s7.readwrite.S7Message; |
| import org.apache.plc4x.java.s7.readwrite.S7MessageRequest; |
| import org.apache.plc4x.java.s7.readwrite.S7MessageResponse; |
| import org.apache.plc4x.java.s7.readwrite.S7MessageUserData; |
| import org.apache.plc4x.java.s7.readwrite.S7ParameterReadVarRequest; |
| import org.apache.plc4x.java.s7.readwrite.S7ParameterReadVarResponse; |
| import org.apache.plc4x.java.s7.readwrite.S7ParameterSetupCommunication; |
| import org.apache.plc4x.java.s7.readwrite.S7ParameterUserData; |
| import org.apache.plc4x.java.s7.readwrite.S7ParameterUserDataItem; |
| import org.apache.plc4x.java.s7.readwrite.S7ParameterUserDataItemCPUFunctions; |
| import org.apache.plc4x.java.s7.readwrite.S7PayloadReadVarRequest; |
| import org.apache.plc4x.java.s7.readwrite.S7PayloadReadVarResponse; |
| import org.apache.plc4x.java.s7.readwrite.S7PayloadSetupCommunication; |
| import org.apache.plc4x.java.s7.readwrite.S7PayloadUserData; |
| import org.apache.plc4x.java.s7.readwrite.S7PayloadUserDataItem; |
| import org.apache.plc4x.java.s7.readwrite.S7PayloadUserDataItemCpuFunctionReadSzlRequest; |
| import org.apache.plc4x.java.s7.readwrite.S7PayloadUserDataItemCpuFunctionReadSzlResponse; |
| import org.apache.plc4x.java.s7.readwrite.S7VarPayloadDataItem; |
| import org.apache.plc4x.java.s7.readwrite.S7VarRequestParameterItem; |
| import org.apache.plc4x.java.s7.readwrite.S7VarRequestParameterItemAddress; |
| import org.apache.plc4x.java.s7.readwrite.SzlDataTreeItem; |
| import org.apache.plc4x.java.s7.readwrite.SzlId; |
| import org.apache.plc4x.java.s7.readwrite.TPKTPacket; |
| import org.apache.plc4x.java.s7.readwrite.connection.S7Configuration; |
| import org.apache.plc4x.java.s7.readwrite.types.COTPProtocolClass; |
| import org.apache.plc4x.java.s7.readwrite.types.COTPTpduSize; |
| import org.apache.plc4x.java.s7.readwrite.types.DataTransportErrorCode; |
| import org.apache.plc4x.java.s7.readwrite.types.DataTransportSize; |
| import org.apache.plc4x.java.s7.readwrite.types.DeviceGroup; |
| import org.apache.plc4x.java.s7.readwrite.types.S7ControllerType; |
| import org.apache.plc4x.java.s7.readwrite.types.SzlModuleTypeClass; |
| import org.apache.plc4x.java.s7.readwrite.types.SzlSublist; |
| import org.apache.plc4x.java.s7.readwrite.utils.S7Field; |
| import org.apache.plc4x.java.s7.readwrite.utils.S7TsapIdEncoder; |
| import org.apache.plc4x.java.spi.ConversationContext; |
| import org.apache.plc4x.java.spi.Plc4xProtocolBase; |
| import org.apache.plc4x.java.spi.messages.DefaultPlcReadRequest; |
| import org.apache.plc4x.java.spi.messages.DefaultPlcReadResponse; |
| import org.apache.plc4x.java.spi.messages.InternalPlcReadRequest; |
| import org.apache.plc4x.java.spi.messages.items.BaseDefaultFieldItem; |
| import org.apache.plc4x.java.spi.messages.items.DefaultBigIntegerFieldItem; |
| import org.apache.plc4x.java.spi.messages.items.DefaultBooleanFieldItem; |
| import org.apache.plc4x.java.spi.messages.items.DefaultByteFieldItem; |
| import org.apache.plc4x.java.spi.messages.items.DefaultDoubleFieldItem; |
| import org.apache.plc4x.java.spi.messages.items.DefaultFloatFieldItem; |
| import org.apache.plc4x.java.spi.messages.items.DefaultIntegerFieldItem; |
| import org.apache.plc4x.java.spi.messages.items.DefaultLocalDateFieldItem; |
| import org.apache.plc4x.java.spi.messages.items.DefaultLocalDateTimeFieldItem; |
| import org.apache.plc4x.java.spi.messages.items.DefaultLocalTimeFieldItem; |
| import org.apache.plc4x.java.spi.messages.items.DefaultLongFieldItem; |
| import org.apache.plc4x.java.spi.messages.items.DefaultShortFieldItem; |
| import org.apache.plc4x.java.spi.messages.items.DefaultStringFieldItem; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import java.lang.reflect.Array; |
| import java.math.BigInteger; |
| import java.nio.charset.StandardCharsets; |
| import java.time.Duration; |
| import java.time.LocalDate; |
| import java.time.LocalDateTime; |
| import java.time.LocalTime; |
| import java.time.temporal.ChronoUnit; |
| import java.util.ArrayList; |
| import java.util.BitSet; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.concurrent.CompletableFuture; |
| import java.util.concurrent.atomic.AtomicInteger; |
| import java.util.function.Function; |
| import java.util.stream.Collectors; |
| import java.util.stream.IntStream; |
| |
| public class Plc4xS7Protocol extends Plc4xProtocolBase<TPKTPacket> { |
| |
| private static final Logger logger = LoggerFactory.getLogger(Plc4xS7Protocol.class); |
| |
| private final int callingTsapId; |
| private int calledTsapId; |
| private COTPTpduSize cotpTpduSize; |
| private int pduSize; |
| private int maxAmqCaller; |
| private int maxAmqCallee; |
| private S7ControllerType controllerType; |
| |
| private static final AtomicInteger tpduGenerator = new AtomicInteger(10); |
| |
| public Plc4xS7Protocol(S7Configuration configuration) { |
| this.callingTsapId = S7TsapIdEncoder.encodeS7TsapId(DeviceGroup.PG_OR_PC, configuration.rack, configuration.slot); |
| ; |
| this.calledTsapId = S7TsapIdEncoder.encodeS7TsapId(DeviceGroup.OS, 0, 0); |
| |
| this.controllerType = configuration.controllerType == null ? S7ControllerType.ANY : S7ControllerType.valueOf(configuration.controllerType); |
| if (controllerType == S7ControllerType.LOGO && configuration.pduSize == 1024) { |
| configuration.pduSize = 480; |
| } |
| |
| this.cotpTpduSize = getNearestMatchingTpduSize(configuration.pduSize); |
| ; |
| this.pduSize = cotpTpduSize.getSizeInBytes() - 16; |
| this.maxAmqCaller = configuration.maxAmqCaller; |
| this.maxAmqCallee = configuration.maxAmqCallee; |
| } |
| |
| /** |
| * Iterate over all values until one is found that the given tpdu size will fit. |
| * |
| * @param tpduSizeParameter requested tpdu size. |
| * @return smallest {@link COTPTpduSize} which will fit a given size of tpdu. |
| */ |
| protected COTPTpduSize getNearestMatchingTpduSize(short tpduSizeParameter) { |
| for (COTPTpduSize value : COTPTpduSize.values()) { |
| if (value.getSizeInBytes() >= tpduSizeParameter) { |
| return value; |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public void onConnect(ConversationContext<TPKTPacket> context) { |
| logger.debug("ISO Transport Protocol Sending Connection Request"); |
| // Open the session on ISO Transport Protocol first. |
| TPKTPacket packet = new TPKTPacket(createCOTPConnectionRequest(calledTsapId, callingTsapId, cotpTpduSize)); |
| |
| context.sendRequest(packet) |
| .expectResponse(TPKTPacket.class, Duration.ofMillis(1000)) |
| .check(p -> p.getPayload() instanceof COTPPacketConnectionResponse) |
| .unwrap(p -> (COTPPacketConnectionResponse) p.getPayload()) |
| .handle(cotpPacketConnectionResponse -> { |
| context.sendRequest(createS7ConnectionRequest(cotpPacketConnectionResponse)) |
| .expectResponse(TPKTPacket.class, Duration.ofMillis(1000)) |
| .unwrap(TPKTPacket::getPayload) |
| .only(COTPPacketData.class) |
| .unwrap(COTPPacket::getPayload) |
| .only(S7MessageResponse.class) |
| .unwrap(S7Message::getParameter) |
| .only(S7ParameterSetupCommunication.class) |
| .handle(setupCommunication -> { |
| // Save some data from the response. |
| maxAmqCaller = setupCommunication.getMaxAmqCaller(); |
| maxAmqCallee = setupCommunication.getMaxAmqCallee(); |
| pduSize = setupCommunication.getPduLength(); |
| |
| // Only if the controller type is set to "ANY", then try to identify the PLC type. |
| if (controllerType != S7ControllerType.ANY) { |
| // Send an event that connection setup is complete. |
| context.fireConnected(); |
| return; |
| } |
| // Prepare a message to request the remote to identify itself. |
| TPKTPacket tpktPacket = createIdentifyRemoteMessage(); |
| context.sendRequest(tpktPacket) |
| .expectResponse(TPKTPacket.class, Duration.ofMillis(1000)) |
| .check(p -> p.getPayload() instanceof COTPPacketData) |
| .unwrap(p -> ((COTPPacketData) p.getPayload())) |
| .check(p -> p.getPayload() instanceof S7MessageUserData) |
| .unwrap(p -> ((S7MessageUserData) p.getPayload())) |
| .check(p -> p.getPayload() instanceof S7PayloadUserData) |
| .handle(messageUserData -> { |
| S7PayloadUserData payloadUserData = (S7PayloadUserData) messageUserData.getPayload(); |
| extractControllerTypeAndFireConnected(context, payloadUserData); |
| }); |
| }); |
| }); |
| } |
| |
| @Override public CompletableFuture<PlcReadResponse> read(PlcReadRequest readRequest) { |
| CompletableFuture<PlcReadResponse> future = new CompletableFuture<>(); |
| DefaultPlcReadRequest request = (DefaultPlcReadRequest) readRequest; |
| List<S7VarRequestParameterItem> requestItems = new ArrayList<>(request.getNumberOfFields()); |
| for (PlcField field : request.getFields()) { |
| requestItems.add(new S7VarRequestParameterItemAddress(toS7Address(field))); |
| } |
| final int tpduId = tpduGenerator.getAndIncrement(); |
| TPKTPacket tpktPacket = new TPKTPacket(new COTPPacketData(null, |
| new S7MessageRequest(tpduId, |
| new S7ParameterReadVarRequest(requestItems.toArray(new S7VarRequestParameterItem[0])), |
| new S7PayloadReadVarRequest()), |
| true, (short) tpduId)); |
| |
| context.sendRequest(tpktPacket) |
| .expectResponse(TPKTPacket.class, Duration.ofMillis(1000)) |
| .onTimeout(future::completeExceptionally) |
| .onError((p, e) -> future.completeExceptionally(e)) |
| .check(p -> p.getPayload() instanceof COTPPacketData) |
| .unwrap(p -> ((COTPPacketData) p.getPayload())) |
| .check(p -> p.getPayload() instanceof S7MessageResponse) |
| .unwrap(p -> ((S7MessageResponse) p.getPayload())) |
| .check(p -> p.getTpduReference() == tpduId) |
| .check(p -> p.getParameter() instanceof S7ParameterReadVarResponse) |
| .handle(p -> { |
| try { |
| future.complete(((PlcReadResponse) decodeReadResponse(p, ((InternalPlcReadRequest) readRequest)))); |
| } catch (PlcProtocolException e) { |
| e.printStackTrace(); |
| } |
| }); |
| return future; |
| } |
| |
| private void extractControllerTypeAndFireConnected(ConversationContext<TPKTPacket> context, S7PayloadUserData payloadUserData) { |
| for (S7PayloadUserDataItem item : payloadUserData.getItems()) { |
| if (!(item instanceof S7PayloadUserDataItemCpuFunctionReadSzlResponse)) { |
| continue; |
| } |
| S7PayloadUserDataItemCpuFunctionReadSzlResponse readSzlResponseItem = |
| (S7PayloadUserDataItemCpuFunctionReadSzlResponse) item; |
| for (SzlDataTreeItem readSzlResponseItemItem : readSzlResponseItem.getItems()) { |
| if (readSzlResponseItemItem.getItemIndex() != 0x0001) { |
| continue; |
| } |
| final String articleNumber = new String(readSzlResponseItemItem.getMlfb()); |
| controllerType = lookupControllerType(articleNumber); |
| |
| // Send an event that connection setup is complete. |
| context.fireConnected(); |
| } |
| } |
| } |
| |
| private static TPKTPacket createIdentifyRemoteMessage() { |
| S7MessageUserData identifyRemoteMessage = new S7MessageUserData(1, new S7ParameterUserData(new S7ParameterUserDataItem[]{ |
| new S7ParameterUserDataItemCPUFunctions((short) 0x11, (byte) 0x4, (byte) 0x4, (short) 0x01, (short) 0x00, null, null, null) |
| }), new S7PayloadUserData(new S7PayloadUserDataItem[]{ |
| new S7PayloadUserDataItemCpuFunctionReadSzlRequest(DataTransportErrorCode.OK, DataTransportSize.OCTET_STRING, new SzlId(SzlModuleTypeClass.CPU, (byte) 0x00, SzlSublist.MODULE_IDENTIFICATION), 0x0000) |
| })); |
| COTPPacketData cotpPacketData = new COTPPacketData(null, identifyRemoteMessage, true, (short) 2); |
| return new TPKTPacket(cotpPacketData); |
| } |
| |
| private TPKTPacket createS7ConnectionRequest(COTPPacketConnectionResponse cotpPacketConnectionResponse) { |
| for (COTPParameter parameter : cotpPacketConnectionResponse.getParameters()) { |
| if (parameter instanceof COTPParameterCalledTsap) { |
| COTPParameterCalledTsap cotpParameterCalledTsap = (COTPParameterCalledTsap) parameter; |
| calledTsapId = cotpParameterCalledTsap.getTsapId(); |
| } else if (parameter instanceof COTPParameterTpduSize) { |
| COTPParameterTpduSize cotpParameterTpduSize = (COTPParameterTpduSize) parameter; |
| cotpTpduSize = cotpParameterTpduSize.getTpduSize(); |
| } else if (parameter instanceof COTPParameterCallingTsap) { |
| // Ignore this ... |
| } else { |
| logger.warn("Got unknown parameter type '" + parameter.getClass().getName() + "'"); |
| } |
| } |
| |
| // Send an S7 login message. |
| S7ParameterSetupCommunication s7ParameterSetupCommunication = |
| new S7ParameterSetupCommunication(maxAmqCaller, maxAmqCallee, pduSize); |
| S7Message s7Message = new S7MessageRequest(0, s7ParameterSetupCommunication, |
| new S7PayloadSetupCommunication()); |
| COTPPacketData cotpPacketData = new COTPPacketData(null, s7Message, true, (short) 1); |
| return new TPKTPacket(cotpPacketData); |
| } |
| |
| private static COTPPacketConnectionRequest createCOTPConnectionRequest(int calledTsapId, int callingTsapId, COTPTpduSize cotpTpduSize) { |
| return new COTPPacketConnectionRequest( |
| new COTPParameter[]{ |
| new COTPParameterCalledTsap(calledTsapId), |
| new COTPParameterCallingTsap(callingTsapId), |
| new COTPParameterTpduSize(cotpTpduSize) |
| }, null, (short) 0x0000, (short) 0x000F, COTPProtocolClass.CLASS_0); |
| } |
| |
| private PlcResponse decodeReadResponse(S7MessageResponse responseMessage, InternalPlcReadRequest plcReadRequest) throws PlcProtocolException { |
| S7PayloadReadVarResponse payload = (S7PayloadReadVarResponse) responseMessage.getPayload(); |
| |
| // If the numbers of items don't match, we're in big trouble as the only |
| // way to know how to interpret the responses is by aligning them with the |
| // items from the request as this information is not returned by the PLC. |
| if (plcReadRequest.getNumberOfFields() != payload.getItems().length) { |
| throw new PlcProtocolException( |
| "The number of requested items doesn't match the number of returned items"); |
| } |
| |
| Map<String, Pair<PlcResponseCode, BaseDefaultFieldItem>> values = new HashMap<>(); |
| S7VarPayloadDataItem[] payloadItems = payload.getItems(); |
| int index = 0; |
| for (String fieldName : plcReadRequest.getFieldNames()) { |
| S7Field field = (S7Field) plcReadRequest.getField(fieldName); |
| S7VarPayloadDataItem payloadItem = payloadItems[index]; |
| |
| PlcResponseCode responseCode = decodeResponseCode(payloadItem.getReturnCode()); |
| BaseDefaultFieldItem fieldItem = null; |
| ByteBuf data = Unpooled.wrappedBuffer(payloadItem.getData()); |
| if (responseCode == PlcResponseCode.OK) { |
| fieldItem = mapFieldItem(fieldName, field, data); |
| } |
| Pair<PlcResponseCode, BaseDefaultFieldItem> result = new ImmutablePair<>(responseCode, fieldItem); |
| values.put(fieldName, result); |
| index++; |
| } |
| |
| return new DefaultPlcReadResponse(plcReadRequest, values); |
| } |
| |
| private BaseDefaultFieldItem mapFieldItem(String fieldName, S7Field field, ByteBuf data) { |
| try { |
| switch (field.getDataType()) { |
| // ----------------------------------------- |
| // Bit |
| // ----------------------------------------- |
| case BOOL: |
| return decodeReadResponseBitField(field, data); |
| // ----------------------------------------- |
| // Bit-strings |
| // ----------------------------------------- |
| case BYTE: // 1 byte |
| return decodeReadResponseByteBitStringField(field, data); |
| case WORD: // 2 byte (16 bit) |
| return decodeReadResponseShortBitStringField(field, data); |
| case DWORD: // 4 byte (32 bit) |
| return decodeReadResponseIntegerBitStringField(field, data); |
| case LWORD: // 8 byte (64 bit) |
| return decodeReadResponseLongBitStringField(field, data); |
| // ----------------------------------------- |
| // Integers |
| // ----------------------------------------- |
| // 8 bit: |
| case SINT: |
| return decodeReadResponseSignedByteField(field, data); |
| case USINT: |
| return decodeReadResponseUnsignedByteField(field, data); |
| // 16 bit: |
| case INT: |
| return decodeReadResponseSignedShortField(field, data); |
| case UINT: |
| return decodeReadResponseUnsignedShortField(field, data); |
| // 32 bit: |
| case DINT: |
| return decodeReadResponseSignedIntegerField(field, data); |
| case UDINT: |
| return decodeReadResponseUnsignedIntegerField(field, data); |
| // 64 bit: |
| case LINT: |
| return decodeReadResponseSignedLongField(field, data); |
| case ULINT: |
| return decodeReadResponseUnsignedLongField(field, data); |
| // ----------------------------------------- |
| // Floating point values |
| // ----------------------------------------- |
| case REAL: |
| return decodeReadResponseFloatField(field, data); |
| case LREAL: |
| return decodeReadResponseDoubleField(field, data); |
| // ----------------------------------------- |
| // Characters & Strings |
| // ----------------------------------------- |
| case CHAR: // 1 byte (8 bit) |
| return decodeReadResponseFixedLengthStringField(1, false, data); |
| case WCHAR: // 2 byte |
| return decodeReadResponseFixedLengthStringField(1, true, data); |
| case STRING: |
| return decodeReadResponseVarLengthStringField(false, data); |
| case WSTRING: |
| return decodeReadResponseVarLengthStringField(true, data); |
| // ----------------------------------------- |
| // TIA Date-Formats |
| // ----------------------------------------- |
| case DATE_AND_TIME: |
| return decodeReadResponseDateAndTime(field, data); |
| case TIME_OF_DAY: |
| return decodeReadResponseTimeOfDay(field, data); |
| case DATE: |
| return decodeReadResponseDate(field, data); |
| default: |
| throw new PlcProtocolException("Unsupported type " + field.getDataType()); |
| } |
| } catch (Exception e) { |
| logger.warn("Some other error occurred casting field {}, FieldInformation: {}", fieldName, field, e); |
| return null; |
| } |
| } |
| |
| private PlcResponseCode decodeResponseCode(DataTransportErrorCode dataTransportErrorCode) { |
| if (dataTransportErrorCode == null) { |
| return PlcResponseCode.INTERNAL_ERROR; |
| } |
| switch (dataTransportErrorCode) { |
| case OK: |
| return PlcResponseCode.OK; |
| case NOT_FOUND: |
| return PlcResponseCode.NOT_FOUND; |
| case INVALID_ADDRESS: |
| return PlcResponseCode.INVALID_ADDRESS; |
| case DATA_TYPE_NOT_SUPPORTED: |
| return PlcResponseCode.INVALID_DATATYPE; |
| default: |
| return PlcResponseCode.INTERNAL_ERROR; |
| } |
| } |
| |
| BaseDefaultFieldItem decodeReadResponseBitField(S7Field field, ByteBuf data) { |
| Boolean[] booleans = readAllValues(Boolean.class, field, i -> data.readByte() != 0x00); |
| return new DefaultBooleanFieldItem(booleans); |
| } |
| |
| BaseDefaultFieldItem decodeReadResponseByteBitStringField(S7Field field, ByteBuf data) { |
| byte[] bytes = new byte[field.getNumElements()]; |
| data.readBytes(bytes); |
| return decodeBitStringField(bytes); |
| } |
| |
| BaseDefaultFieldItem decodeReadResponseShortBitStringField(S7Field field, ByteBuf data) { |
| byte[] bytes = new byte[field.getNumElements() * 2]; |
| data.readBytes(bytes); |
| return decodeBitStringField(bytes); |
| } |
| |
| BaseDefaultFieldItem decodeReadResponseIntegerBitStringField(S7Field field, ByteBuf data) { |
| byte[] bytes = new byte[field.getNumElements() * 4]; |
| data.readBytes(bytes); |
| return decodeBitStringField(bytes); |
| } |
| |
| BaseDefaultFieldItem decodeReadResponseLongBitStringField(S7Field field, ByteBuf data) { |
| byte[] bytes = new byte[field.getNumElements() * 8]; |
| data.readBytes(bytes); |
| return decodeBitStringField(bytes); |
| } |
| |
| BaseDefaultFieldItem decodeBitStringField(byte[] bytes) { |
| BitSet bitSet = BitSet.valueOf(bytes); |
| Boolean[] booleanValues = new Boolean[8 * bytes.length]; |
| int k = 0; |
| for (int i = bytes.length - 1; i >= 0; i--) { |
| for (int j = 0; j < 8; j++) { |
| booleanValues[k++] = bitSet.get(8 * i + j); |
| } |
| } |
| return new DefaultBooleanFieldItem(booleanValues); |
| } |
| |
| BaseDefaultFieldItem decodeReadResponseSignedByteField(S7Field field, ByteBuf data) { |
| Byte[] bytes = readAllValues(Byte.class, field, i -> data.readByte()); |
| return new DefaultByteFieldItem(bytes); |
| } |
| |
| BaseDefaultFieldItem decodeReadResponseUnsignedByteField(S7Field field, ByteBuf data) { |
| Short[] shorts = readAllValues(Short.class, field, i -> data.readUnsignedByte()); |
| return new DefaultShortFieldItem(shorts); |
| } |
| |
| BaseDefaultFieldItem decodeReadResponseSignedShortField(S7Field field, ByteBuf data) { |
| Short[] shorts = readAllValues(Short.class, field, i -> data.readShort()); |
| return new DefaultShortFieldItem(shorts); |
| } |
| |
| BaseDefaultFieldItem decodeReadResponseUnsignedShortField(S7Field field, ByteBuf data) { |
| Integer[] ints = readAllValues(Integer.class, field, i -> data.readUnsignedShort()); |
| return new DefaultIntegerFieldItem(ints); |
| } |
| |
| BaseDefaultFieldItem decodeReadResponseSignedIntegerField(S7Field field, ByteBuf data) { |
| Integer[] ints = readAllValues(Integer.class, field, i -> data.readInt()); |
| return new DefaultIntegerFieldItem(ints); |
| } |
| |
| BaseDefaultFieldItem decodeReadResponseUnsignedIntegerField(S7Field field, ByteBuf data) { |
| Long[] longs = readAllValues(Long.class, field, i -> data.readUnsignedInt()); |
| return new DefaultLongFieldItem(longs); |
| } |
| |
| BaseDefaultFieldItem decodeReadResponseSignedLongField(S7Field field, ByteBuf data) { |
| Long[] longs = readAllValues(Long.class, field, i -> data.readLong()); |
| return new DefaultLongFieldItem(longs); |
| } |
| |
| BaseDefaultFieldItem decodeReadResponseUnsignedLongField(S7Field field, ByteBuf data) { |
| BigInteger[] bigIntegers = readAllValues(BigInteger.class, field, i -> readUnsigned64BitInteger(data)); |
| return new DefaultBigIntegerFieldItem(bigIntegers); |
| } |
| |
| BaseDefaultFieldItem decodeReadResponseFloatField(S7Field field, ByteBuf data) { |
| Float[] floats = readAllValues(Float.class, field, i -> data.readFloat()); |
| return new DefaultFloatFieldItem(floats); |
| } |
| |
| BaseDefaultFieldItem decodeReadResponseDoubleField(S7Field field, ByteBuf data) { |
| Double[] doubles = readAllValues(Double.class, field, i -> data.readDouble()); |
| return new DefaultDoubleFieldItem(doubles); |
| } |
| |
| BaseDefaultFieldItem decodeReadResponseFixedLengthStringField(int numChars, boolean isUtf16, ByteBuf data) { |
| int numBytes = isUtf16 ? numChars * 2 : numChars; |
| String stringValue = data.readCharSequence(numBytes, StandardCharsets.UTF_8).toString(); |
| return new DefaultStringFieldItem(stringValue); |
| } |
| |
| BaseDefaultFieldItem decodeReadResponseVarLengthStringField(boolean isUtf16, ByteBuf data) { |
| // Max length ... ignored. |
| data.skipBytes(1); |
| |
| //reading out byte and transforming that to an unsigned byte within an integer, otherwise longer strings are failing |
| byte currentLengthByte = data.readByte(); |
| int currentLength = currentLengthByte & 0xFF; |
| return decodeReadResponseFixedLengthStringField(currentLength, isUtf16, data); |
| } |
| |
| BaseDefaultFieldItem decodeReadResponseDateAndTime(S7Field field, ByteBuf data) { |
| LocalDateTime[] localDateTimes = readAllValues(LocalDateTime.class, field, i -> readDateAndTime(data)); |
| return new DefaultLocalDateTimeFieldItem(localDateTimes); |
| } |
| |
| BaseDefaultFieldItem decodeReadResponseTimeOfDay(S7Field field, ByteBuf data) { |
| LocalTime[] localTimes = readAllValues(LocalTime.class, field, i -> readTimeOfDay(data)); |
| return new DefaultLocalTimeFieldItem(localTimes); |
| } |
| |
| BaseDefaultFieldItem decodeReadResponseDate(S7Field field, ByteBuf data) { |
| LocalDate[] localTimes = readAllValues(LocalDate.class, field, i -> readDate(data)); |
| return new DefaultLocalDateFieldItem(localTimes); |
| } |
| |
| private static <T> T[] readAllValues(Class<T> clazz, S7Field field, Function<Integer, T> extract) { |
| try { |
| return IntStream.rangeClosed(1, field.getNumElements()) |
| .mapToObj(extract::apply) |
| .collect(Collectors.toList()) |
| .toArray((T[]) Array.newInstance(clazz, 0)); |
| } catch (IndexOutOfBoundsException e) { |
| throw new PlcRuntimeException("To few bytes in the buffer to read requested type", e); |
| } |
| } |
| |
| private static BigInteger readUnsigned64BitInteger(ByteBuf data) { |
| byte[] bytes = new byte[9]; |
| // Set the first byte to 0 |
| bytes[0] = 0; |
| // Read the next 8 bytes into the rest. |
| data.readBytes(bytes, 1, 8); |
| return new BigInteger(bytes); |
| } |
| |
| LocalDateTime readDateAndTime(ByteBuf data) { |
| //per definition for Date_And_Time only the first 6 bytes are used |
| |
| int year = convertByteToBcd(data.readByte()); |
| int month = convertByteToBcd(data.readByte()); |
| int day = convertByteToBcd(data.readByte()); |
| int hour = convertByteToBcd(data.readByte()); |
| int minute = convertByteToBcd(data.readByte()); |
| int second = convertByteToBcd(data.readByte()); |
| //skip the last 2 bytes no information present |
| data.readByte(); |
| data.readByte(); |
| |
| //data-type ranges from 1990 up to 2089 |
| if (year >= 90) { |
| year += 1900; |
| } else { |
| year += 2000; |
| } |
| |
| return LocalDateTime.of(year, month, day, hour, minute, second); |
| } |
| |
| LocalTime readTimeOfDay(ByteBuf data) { |
| //per definition for Date_And_Time only the first 6 bytes are used |
| int millisSinsMidnight = data.readInt(); |
| return LocalTime.now().withHour(0).withMinute(0).withSecond(0).withNano(0).plus(millisSinsMidnight, ChronoUnit.MILLIS); |
| } |
| |
| LocalDate readDate(ByteBuf data) { |
| //per definition for Date_And_Time only the first 6 bytes are used |
| int daysSince1990 = data.readUnsignedShort(); |
| return LocalDate.now().withYear(1990).withDayOfMonth(1).withMonth(1).plus(daysSince1990, ChronoUnit.DAYS); |
| } |
| |
| /** |
| * converts incoming byte to an integer regarding used BCD format |
| * |
| * @param incomingByte |
| * @return converted BCD number |
| */ |
| private static int convertByteToBcd(byte incomingByte) { |
| int dec = (incomingByte >> 4) * 10; |
| return dec + (incomingByte & 0x0f); |
| } |
| |
| /** |
| * Little helper method to parse Siemens article numbers and extract the type of controller. |
| * |
| * @param articleNumber article number string. |
| * @return type of controller. |
| */ |
| private S7ControllerType lookupControllerType(String articleNumber) { |
| if (!articleNumber.startsWith("6ES7 ")) { |
| return S7ControllerType.ANY; |
| } |
| String model = articleNumber.substring(articleNumber.indexOf(' ') + 1, articleNumber.indexOf(' ') + 2); |
| switch (model) { |
| case "2": |
| return S7ControllerType.S7_1200; |
| case "5": |
| return S7ControllerType.S7_1500; |
| case "3": |
| return S7ControllerType.S7_300; |
| case "4": |
| return S7ControllerType.S7_400; |
| default: |
| if (logger.isInfoEnabled()) { |
| logger.info(String.format("Looking up unknown article number %s", articleNumber)); |
| } |
| return S7ControllerType.ANY; |
| } |
| } |
| |
| protected S7Address toS7Address(PlcField field) { |
| if (!(field instanceof S7Field)) { |
| throw new RuntimeException("Unsupported address type " + field.getClass().getName()); |
| } |
| S7Field s7Field = (S7Field) field; |
| return new S7AddressAny(s7Field.getDataType(), s7Field.getNumElements(), s7Field.getBlockNumber(), |
| s7Field.getMemoryArea(), s7Field.getByteOffset(), s7Field.getBitOffset()); |
| } |
| |
| } |