blob: 1b214853f0f80b956a276d3b5c9cbcdd8e9b299f [file] [log] [blame]
/*
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.sandbox.java.dynamic.s7.actions;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.scxml2.ActionExecutionContext;
import org.apache.plc4x.java.api.exceptions.PlcProtocolException;
import org.apache.plc4x.java.api.messages.PlcReadResponse;
import org.apache.plc4x.java.api.types.PlcResponseCode;
import org.apache.plc4x.java.base.messages.DefaultPlcReadResponse;
import org.apache.plc4x.java.base.messages.InternalPlcReadRequest;
import org.apache.plc4x.java.base.messages.PlcRequestContainer;
import org.apache.plc4x.java.base.messages.items.*;
import org.apache.plc4x.sandbox.java.dynamic.actions.ReceiveResponseAction;
import org.apache.plc4x.sandbox.java.dynamic.s7.types.DataTransportErrorCode;
import org.apache.plc4x.sandbox.java.dynamic.s7.utils.S7Field;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.Namespace;
import org.jdom2.filter.Filters;
import org.jdom2.xpath.XPathExpression;
import org.jdom2.xpath.XPathFactory;
import javax.xml.bind.DatatypeConverter;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.*;
public class S7DecodeReadResponseAction extends ReceiveResponseAction {
@Override
protected void processResponse(Document message, ActionExecutionContext ctx, PlcRequestContainer container) {
InternalPlcReadRequest readRequest = (InternalPlcReadRequest) container.getRequest();
LinkedHashSet<String> fieldNames = readRequest.getFieldNames();
List<Namespace> namespaces = message.getRootElement().getNamespacesInScope();
XPathFactory xPathFactory = XPathFactory.instance();
XPathExpression<Element> xpath = xPathFactory.compile(
"/s7:TpktMessage/userData/userData/s7:S7ResponseMessage/payloads/payload/s7:S7ResponsePayloadReadVar/item", Filters.element(), null, namespaces);
List<Element> items = xpath.evaluate(message);
// If the sizes don't match, something is wrong.
if(items.size() != fieldNames.size()) {
container.getResponseFuture().completeExceptionally(
new PlcProtocolException("Response item size doesn't match request item size."));
return;
}
Map<String, Pair<PlcResponseCode, BaseDefaultFieldItem>> responseItems = new HashMap<>(items.size());
int i = 0;
for (String fieldName : fieldNames) {
S7Field field = (S7Field) readRequest.getField(fieldName);
Element item = items.get(i);
String returnCode = item.getChild("returnCode").getTextTrim();
byte returnCodeValue = (byte) (Short.valueOf(returnCode) & 0xFF);
PlcResponseCode responseCode = null;
BaseDefaultFieldItem fieldItem = null;
switch(DataTransportErrorCode.valueOf(returnCodeValue)) {
case RESERVED:
responseCode = PlcResponseCode.INTERNAL_ERROR;
break;
case ACCESS_DENIED:
responseCode = PlcResponseCode.ACCESS_DENIED;
break;
case DATA_TYPE_NOT_SUPPORTED:
responseCode = PlcResponseCode.INVALID_DATATYPE;
break;
case INVALID_ADDRESS:
responseCode = PlcResponseCode.INVALID_ADDRESS;
break;
case NOT_FOUND:
responseCode = PlcResponseCode.NOT_FOUND;
break;
case OK: {
responseCode = PlcResponseCode.OK;
// Convert the hex encoded payload into a real byte array.
byte[] data = DatatypeConverter.parseHexBinary(item.getChild("data").getTextTrim());
// Depending on the field type in the request, interpret the data in the response accordingly.
switch (field.getDataType()) {
case BOOL: {
BitSet bits = BitSet.valueOf(data);
Boolean[] values = new Boolean[field.getNumElements()];
for (int bitNr = 0; bitNr < field.getNumElements(); bitNr++) {
values[bitNr] = bits.get(bitNr);
}
fieldItem = new DefaultBooleanFieldItem(values);
break;
}
// -----------------------------------------
// Bit strings
// In Tia and Step7 these types are marked as bit-strings
// which is a sequence of bits. Therefore the result will
// have more items than the request requested.
// -----------------------------------------
case BYTE: {
BitSet bits = BitSet.valueOf(data);
Boolean[] values = new Boolean[field.getNumElements() * 8];
for (int bitNr = 0; bitNr < field.getNumElements() * 8; bitNr++) {
values[bitNr] = bits.get(bitNr);
}
fieldItem = new DefaultBooleanFieldItem(values);
break;
}
case WORD: {
BitSet bits = BitSet.valueOf(data);
Boolean[] values = new Boolean[field.getNumElements() * 16];
for (int bitNr = 0; bitNr < field.getNumElements() * 16; bitNr++) {
values[bitNr] = bits.get(bitNr);
}
fieldItem = new DefaultBooleanFieldItem(values);
break;
}
case DWORD: {
BitSet bits = BitSet.valueOf(data);
Boolean[] values = new Boolean[field.getNumElements() * 32];
for (int bitNr = 0; bitNr < field.getNumElements() * 32; bitNr++) {
values[bitNr] = bits.get(bitNr);
}
fieldItem = new DefaultBooleanFieldItem(values);
break;
}
case LWORD: {
BitSet bits = BitSet.valueOf(data);
Boolean[] values = new Boolean[field.getNumElements() * 64];
for (int bitNr = 0; bitNr < field.getNumElements() * 64; bitNr++) {
values[bitNr] = bits.get(bitNr);
}
fieldItem = new DefaultBooleanFieldItem(values);
break;
}
// -----------------------------------------
// Integers
// -----------------------------------------
// (Signed) Small Int (8 bit)
case SINT: {
Byte[] values = new Byte[field.getNumElements()];
for (int valueNr = 0; valueNr < field.getNumElements(); valueNr++) {
values[valueNr] = data[valueNr];
}
fieldItem = new DefaultByteFieldItem(values);
break;
}
// Unsigned Small Int (8 bit)
case USINT: {
Short[] values = new Short[field.getNumElements()];
for (int valueNr = 0; valueNr < field.getNumElements(); valueNr++) {
values[valueNr] = (short) (data[valueNr] & 0xff);
}
fieldItem = new DefaultShortFieldItem(values);
break;
}
// Signed Int (16 bit)
case INT: {
ByteBuffer buffer = ByteBuffer.wrap(data);
Short[] values = new Short[field.getNumElements()];
for (int valueNr = 0; valueNr < field.getNumElements(); valueNr++) {
values[valueNr] = buffer.getShort();
}
fieldItem = new DefaultShortFieldItem(values);
break;
}
// Unsigned Int (16 bit)
case UINT: {
ByteBuffer buffer = ByteBuffer.wrap(data);
Integer[] values = new Integer[field.getNumElements()];
for (int valueNr = 0; valueNr < field.getNumElements(); valueNr++) {
values[valueNr] = (buffer.getShort() & 0xFFFF);
}
fieldItem = new DefaultIntegerFieldItem(values);
break;
}
// Double Precision Int (32 bit)
case DINT: {
ByteBuffer buffer = ByteBuffer.wrap(data);
Integer[] values = new Integer[field.getNumElements()];
for (int valueNr = 0; valueNr < field.getNumElements(); valueNr++) {
values[valueNr] = buffer.getInt();
}
fieldItem = new DefaultIntegerFieldItem(values);
break;
}
// Unsigned Double Precision Int (32 bit)
case UDINT: {
ByteBuffer buffer = ByteBuffer.wrap(data);
Long[] values = new Long[field.getNumElements()];
for (int valueNr = 0; valueNr < field.getNumElements(); valueNr++) {
values[valueNr] = (buffer.getInt() & 0xFFFFFFFFL);
}
fieldItem = new DefaultLongFieldItem(values);
break;
}
// Quadrupal Precision Int (64 bit)
case LINT: {
ByteBuffer buffer = ByteBuffer.wrap(data);
Long[] values = new Long[field.getNumElements()];
for (int valueNr = 0; valueNr < field.getNumElements(); valueNr++) {
values[valueNr] = buffer.getLong();
}
fieldItem = new DefaultLongFieldItem(values);
break;
}
// Unsigned Quadrupal Precision Int (64 bit)
case ULINT: {
BigInteger[] values = new BigInteger[field.getNumElements()];
for (int valueNr = 0; valueNr < field.getNumElements(); valueNr++) {
byte[] biBytes = new byte[]{data[valueNr * 8], data[(valueNr * 8) + 1],
data[(valueNr * 8) + 2], data[(valueNr * 8) + 3], data[(valueNr * 8) + 4],
data[(valueNr * 8) + 5], data[(valueNr * 8) + 6], data[(valueNr * 8) + 7]};
values[valueNr] = new BigInteger(biBytes);
}
fieldItem = new DefaultBigIntegerFieldItem(values);
break;
}
// -----------------------------------------
// Reals
// -----------------------------------------
// (32 bit)
case REAL: {
ByteBuffer buffer = ByteBuffer.wrap(data);
Float[] values = new Float[field.getNumElements()];
for (int valueNr = 0; valueNr < field.getNumElements(); valueNr++) {
values[valueNr] = buffer.getFloat();
}
fieldItem = new DefaultFloatFieldItem(values);
break;
}
// (64 bit)
case LREAL: {
ByteBuffer buffer = ByteBuffer.wrap(data);
Double[] values = new Double[field.getNumElements()];
for (int valueNr = 0; valueNr < field.getNumElements(); valueNr++) {
values[valueNr] = buffer.getDouble();
}
fieldItem = new DefaultDoubleFieldItem(values);
break;
}
// -----------------------------------------
// Durations
// -----------------------------------------
// IEC time
case TIME:
break;
case LTIME:
break;
// -----------------------------------------
// Date
// -----------------------------------------
// IEC date (yyyy-m-d)
case DATE:
break;
// -----------------------------------------
// Time of day
// -----------------------------------------
// Time (hh:mm:ss.S)
case TIME_OF_DAY:
break;
// -----------------------------------------
// Date and time of day
// -----------------------------------------
case DATE_AND_TIME:
break;
// -----------------------------------------
// ASCII Strings
// -----------------------------------------
// Single-byte character
case CHAR:
break;
// Double-byte character
case WCHAR:
break;
// Variable-length single-byte character string
case STRING:
break;
// Variable-length double-byte character string
case WSTRING:
break;
}
}
}
responseItems.put(fieldName, new ImmutablePair<>(responseCode, fieldItem));
i++;
}
PlcReadResponse response = new DefaultPlcReadResponse(readRequest, responseItems);
container.getResponseFuture().complete(response);
}
}