diff --git a/nl-common/src/main/java/org/nl/common/exception/ExceptionConstant.java b/nl-common/src/main/java/org/nl/common/exception/ExceptionConstant.java new file mode 100644 index 0000000..b140ce1 --- /dev/null +++ b/nl-common/src/main/java/org/nl/common/exception/ExceptionConstant.java @@ -0,0 +1,47 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.common.exception; + +/** + * 异常 相关常量 + * + * @author pnoker + * @version 2025.9.0 + * @since 2022.1.0 + */ +public class ExceptionConstant { + + /** + * 公共类实例化错误提示 + */ + public static final String UTILITY_CLASS = "Utility class"; + + /** + * 没有可用的服务 + */ + public static final String NO_AVAILABLE_SERVER = "No available server for client"; + + /** + * 租户, 用户信息不匹配 + */ + public static final String NO_AVAILABLE_AUTH = "Tenant, user information does not match"; + + private ExceptionConstant() { + throw new IllegalStateException(ExceptionConstant.UTILITY_CLASS); + } +} diff --git a/nl-common/src/main/java/org/nl/common/util/DecodeUtil.java b/nl-common/src/main/java/org/nl/common/util/DecodeUtil.java new file mode 100644 index 0000000..d3d5bb6 --- /dev/null +++ b/nl-common/src/main/java/org/nl/common/util/DecodeUtil.java @@ -0,0 +1,146 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.common.util; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.codec.digest.DigestUtils; +import org.nl.common.exception.ExceptionConstant; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.HexFormat; + +/** + * 编码 相关工具类 + * + * @author pnoker + * @version 2025.9.0 + * @since 2022.1.0 + */ +@Slf4j +public class DecodeUtil { + + private DecodeUtil() { + throw new IllegalStateException(ExceptionConstant.UTILITY_CLASS); + } + + /** + * 字节转字符串 + *

+ * UTF-8 + * + * @param bytes 字节数组 + * @return 字符串 + */ + public static String byteToString(byte[] bytes) { + return new String(bytes, StandardCharsets.UTF_8); + } + + /** + * 字符串转字节 + *

+ * UTF-8 + * + * @param content 字符串 + * @return 字节数组 + */ + public static byte[] stringToByte(String content) { + return content.getBytes(StandardCharsets.UTF_8); + } + + /** + * 获取 MD5 编码 + * + * @param content 字符串 + * @return MD5 字符串 + */ + public static String md5(String content) { + return DigestUtils.md5Hex(content); + } + + /** + * 获取 MD5 编码 + * + * @param content 字符串 + * @param salt 盐值 + * @return MD5 字符串 + */ + public static String md5(String content, String salt) { + return md5(content + salt); + } + + /** + * 将字节流进行Base64编码 + * + * @param bytes Byte Array + * @return Byte Array + */ + public static byte[] encode(byte[] bytes) { + return Base64.getEncoder().encode(bytes); + } + + /** + * 将字符串进行Base64编码 + * + * @param content 字符串 + * @return Byte Array + */ + public static byte[] encode(String content) { + return encode(stringToByte(content)); + } + + /** + * 必须配合encode使用, 用于encode编码之后解码 + * + * @param bytes Byte Array + * @return Byte Array + */ + public static byte[] decode(byte[] bytes) { + return Base64.getDecoder().decode(bytes); + } + + /** + * 必须配合encode使用, 用于encode编码之后解码 + * + * @param content 字符串 + * @return Byte Array + */ + public static byte[] decode(String content) { + return decode(stringToByte(content)); + } + + /** + * 将字符串进行16进制编码 + * + * @param content 字符串 + * @return String + */ + public static String enHexCode(String content) { + return HexFormat.of().formatHex((stringToByte(content))); + } + + /** + * 必须配合enHexCode使用, 用于enHexCode编码之后解码 + * + * @param content 字符串 + * @return Byte Array + */ + public static byte[] deHexCode(String content) { + return HexFormat.of().parseHex(content); + } +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/PlcS7ProtocolDriverImpl.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/PlcS7ProtocolDriverImpl.java new file mode 100644 index 0000000..82c60c0 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/PlcS7ProtocolDriverImpl.java @@ -0,0 +1,167 @@ +package org.nl.iot.core.driver.protocol.plcs7; + +import jakarta.annotation.Resource; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.nl.common.exception.CommonException; +import org.nl.iot.core.driver.bo.AttributeBO; +import org.nl.iot.core.driver.entity.RValue; +import org.nl.iot.core.driver.entity.WValue; +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.PlcS7PointVariable; +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.S7Connector; +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.S7Serializer; +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.factory.S7ConnectorFactory; +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.factory.S7SerializerFactory; +import org.nl.iot.core.driver.service.DriverCustomService; +import org.nl.iot.modular.iot.entity.IotConfig; +import org.nl.iot.modular.iot.entity.IotConnect; +import org.springframework.stereotype.Service; + +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** + * + * @author: lyd + * @date: 2026/3/3 + */ +@Slf4j +@Service +public class PlcS7ProtocolDriverImpl implements DriverCustomService { + + /** + * Plc Connector Map + * 仅供参考 + */ + private Map connectMap; + @Override + public void initial() { + connectMap = new ConcurrentHashMap<>(16); + } + + @Override + public void schedule() { + + } + + @Override + public RValue read(Map driverConfig, Map pointConfig, IotConnect connect, IotConfig config) { + /* + * PLC S7 数据读取逻辑 + * + * 提示: 此处逻辑仅供参考, 请务必结合实际应用场景进行修改。 + * 该方法用于从 PLC S7 设备中读取指定点位的数据。 + * 1. 获取设备的 S7 连接器。 + * 2. 加锁以确保线程安全。 + * 3. 使用 S7 序列化器读取点位数据。 + * 4. 将读取到的数据封装为 RValue 对象返回。 + * 5. 捕获并记录异常, 确保锁在 finally 块中释放。 + */ + log.debug("Plc S7 Read, device: {}, point: {}", driverConfig, pointConfig); + MyS7Connector myS7Connector = getS7Connector(connect.getId().toString(), driverConfig); + + try { + myS7Connector.lock.writeLock().lock(); + S7Serializer serializer = S7SerializerFactory.buildSerializer(myS7Connector.getConnector()); + String type = pointConfig.get("data_type").getValueByClass(String.class); + PlcS7PointVariable plcs7PointVariable = getPointVariable(pointConfig, type); + return new RValue(config, connect, String.valueOf(serializer.dispense(plcs7PointVariable))); + } catch (Exception e) { + log.error("Plc S7 Read Error: {}", e.getMessage()); + return null; + } finally { + myS7Connector.lock.writeLock().unlock(); + } + } + + @Override + public Boolean write(Map driverConfig, Map pointConfig, IotConnect device, IotConfig point, WValue wValue) { + return null; + } + + /** + * 获取 PLC S7 连接器 + *

+ * 该方法用于从缓存中获取指定设备的 S7 连接器。如果缓存中不存在该设备的连接器, + * 则会根据驱动配置信息创建一个新的连接器, 并将其缓存以供后续使用。 + *

+ * 连接器创建过程中, 会从驱动配置中获取主机地址和端口号, 并初始化读写锁以确保线程安全。 + * 如果连接器创建失败, 将抛出 {@link CommonException} 异常。 + * + * @param connectId 设备ID, 用于标识唯一的设备连接器 + * @param driverConfig 驱动配置信息, 包含连接 PLC 所需的主机地址和端口号等参数 + * @return 返回与设备ID对应的 {@link MyS7Connector} 对象, 包含 S7 连接器和读写锁 + * @throws CommonException 如果连接器创建失败, 抛出此异常 + */ + private MyS7Connector getS7Connector(String connectId, Map driverConfig) { + MyS7Connector myS7Connector = connectMap.get(connectId); + if (Objects.isNull(myS7Connector)) { + myS7Connector = new MyS7Connector(); + + log.debug("Plc S7 Connection Info {}", driverConfig); + try { + S7Connector s7Connector = S7ConnectorFactory.buildTCPConnector() + .withHost(driverConfig.get("host").getValueByClass(String.class)) + .withPort(driverConfig.get("port").getValueByClass(Integer.class)) + .build(); + myS7Connector.setLock(new ReentrantReadWriteLock()); + myS7Connector.setConnector(s7Connector); + } catch (Exception e) { + throw new CommonException("new s7connector fail" + e.getMessage()); + } + connectMap.put(connectId, myS7Connector); + } + return myS7Connector; + } + + /** + * 获取 PLC S7 点位变量信息 + *

+ * 该方法用于从点位配置中提取 PLC S7 点位变量信息, 并封装为 {@link PlcS7PointVariable} 对象。 + * 点位配置中应包含以下关键属性: + * - dbNum: 数据块编号 + * - byteOffset: 字节偏移量 + * - bitOffset: 位偏移量 + * - blockSize: 数据块大小 + * - type: 点位数据类型 + *

+ * 如果点位配置中缺少上述任一属性, 将抛出 {@link NullPointerException} 异常。 + * + * @param pointConfig 点位配置信息, 包含点位变量的相关属性 + * @param type 点位数据类型, 用于标识点位数据的类型 + * @return 返回封装好的 {@link PlcS7PointVariable} 对象, 包含点位变量的详细信息 + * @throws NullPointerException 如果点位配置中缺少必要的属性, 抛出此异常 + */ + private PlcS7PointVariable getPointVariable(Map pointConfig, String type) { + log.debug("Plc S7 Point Attribute Config {}", pointConfig); + return new PlcS7PointVariable( + pointConfig.get("dbNum").getValueByClass(Integer.class), + pointConfig.get("byteOffset").getValueByClass(Integer.class), + pointConfig.get("bitOffset").getValueByClass(Integer.class), + pointConfig.get("blockSize").getValueByClass(Integer.class), + type); + } + + /** + * MyS7Connector 内部类 + *

+ * 该类用于封装与 PLC S7 连接相关的信息, 包括读写锁和 S7 连接器。 + * 读写锁 {@link ReentrantReadWriteLock} 用于确保在多线程环境下对 S7 连接器的操作是线程安全的。 + * S7 连接器 {@link S7Connector} 用于与 PLC S7 设备进行通信。 + *

+ * 该类提供了无参构造函数和全参构造函数, 并使用了 Lombok 注解自动生成 getter 和 setter 方法。 + */ + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + private static class MyS7Connector { + private ReentrantReadWriteLock lock; + private S7Connector connector; + } +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/PlcS7PointVariable.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/PlcS7PointVariable.java new file mode 100644 index 0000000..5a1e1a4 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/PlcS7PointVariable.java @@ -0,0 +1,98 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.plcs7.com.github.s7; + +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.S7Type; +import lombok.Getter; +import lombok.Setter; + +import java.util.Date; + +/** + * @author pnoker + * @version 2025.9.0 + * @since 2022.1.0 + */ +@Getter +@Setter +public class PlcS7PointVariable { + private int dbNum; + private int byteOffset; + private int bitOffset; + private int size; + private S7Type type; + private Class fieldType; + + public PlcS7PointVariable(int dbNum, int byteOffset, int bitOffset, int size, String s7Type) { + this.dbNum = dbNum; + this.byteOffset = byteOffset; + this.bitOffset = bitOffset; + this.size = size; + getS7TypeAndType(s7Type); + + } + + private void getS7TypeAndType(String s7Type) { + switch (s7Type) { + case "bool": + this.type = S7Type.BOOL; + this.fieldType = Boolean.class; + break; + case "byte": + this.type = S7Type.BYTE; + this.fieldType = Byte.class; + break; + case "int": + this.type = S7Type.INT; + this.fieldType = Short.class; + break; + case "dint": + this.type = S7Type.DINT; + this.fieldType = Long.class; + break; + case "word": + this.type = S7Type.WORD; + this.fieldType = Integer.class; + break; + case "dword": + this.type = S7Type.DWORD; + this.fieldType = Long.class; + break; + case "real": + this.type = S7Type.REAL; + this.fieldType = Float.class; + break; + case "date": + this.type = S7Type.DATE; + this.fieldType = Date.class; + break; + case "time": + this.type = S7Type.TIME; + this.fieldType = Long.class; + break; + case "datetime": + this.type = S7Type.DATE_AND_TIME; + this.fieldType = Long.class; + break; + default: + this.type = S7Type.STRING; + this.fieldType = Boolean.class; + break; + } + } +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/S7Exception.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/S7Exception.java new file mode 100644 index 0000000..ba47783 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/S7Exception.java @@ -0,0 +1,65 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package org.nl.iot.core.driver.protocol.plcs7.com.github.s7; + +/** + * The Class S7Exception is an exception related to S7 Communication + * + * @author Thomas Rudin + */ +public final class S7Exception extends RuntimeException { + + /** + * The Constant serialVersionUID. + */ + private static final long serialVersionUID = 1L; + + /** + * Instantiates a new s7 exception. + */ + public S7Exception() { + } + + /** + * Instantiates a new s7 exception. + * + * @param message the message + */ + public S7Exception(final String message) { + super(message); + } + + /** + * Instantiates a new s7 exception. + * + * @param message the message + * @param cause the cause + */ + public S7Exception(final String message, final Throwable cause) { + super(message, cause); + } + + /** + * Instantiates a new s7 exception. + * + * @param cause the cause + */ + public S7Exception(final Throwable cause) { + super(cause); + } + +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/DaveArea.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/DaveArea.java new file mode 100644 index 0000000..2a7eefd --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/DaveArea.java @@ -0,0 +1,57 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api; + +/** + * @author Thomas Rudin + */ +public enum DaveArea { + ANALOGINPUTS200(6), // System info of 200 family + ANALOGOUTPUTS200(7), // System flags of 200 family + COUNTER(28), // analog inputs of 200 family + COUNTER200(30), // analog outputs of 200 family + DB(0x84), // Peripheral I/O + DI(0x85), + FLAGS(0x83), + INPUTS(0x81), + LOCAL(0x86), // data blocks + OUTPUTS(0x82), // instance data blocks + P(0x80), // not tested + SYSTEM_INFO(3), // local of caller + SYSTEM_FLAGS(5), // S7 counters + TIMER(29), // S7 timers + TIMER200(31), // IEC counters (200 family) + V(0x87); // IEC timers (200 family) + + /** + * Function Code + */ + final int code; + + DaveArea(final int code) { + this.code = code; + } + + /** + * Returns the function code as associated + * + * @return code + */ + public int getCode() { + return this.code; + } +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/S7Connector.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/S7Connector.java new file mode 100644 index 0000000..6fb9c32 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/S7Connector.java @@ -0,0 +1,46 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api; + +import java.io.Closeable; + +/** + * @author Thomas Rudin + */ +public interface S7Connector extends Closeable { + /** + * Reads an area + * + * @param area DaveArea + * @param areaNumber Area Number + * @param bytes Byte Number + * @param offset Byte Offset + * @return Byte Array + */ + public byte[] read(DaveArea area, int areaNumber, int bytes, int offset); + + /** + * Writes an area + * + * @param area DaveArea + * @param areaNumber Area Number + * @param offset Byte Offset + * @param buffer Write Byte Array + */ + public void write(DaveArea area, int areaNumber, int offset, byte[] buffer); + +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/S7Serializable.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/S7Serializable.java new file mode 100644 index 0000000..5db3e52 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/S7Serializable.java @@ -0,0 +1,69 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api; + +/** + * The Interface S7Serializable API + * + * @author Thomas Rudin + */ +public interface S7Serializable { + + /** + * Extracts a java type from a byte buffer. + * + * @param the generic type + * @param targetClass the target class + * @param buffer the buffer + * @param byteOffset the byte offset + * @param bitOffset the bit offset + * @return the t + */ + public T extract(Class targetClass, byte[] buffer, int byteOffset, int bitOffset); + + /** + * Returns the S7-Type. + * + * @return the s7 type + */ + public S7Type getS7Type(); + + /** + * Returns the size of the s7 type bytes. + * + * @return the size in bits + */ + public int getSizeInBits(); + + /** + * Returns the size of the s7 type bytes. + * + * @return the size in bytes + */ + public int getSizeInBytes(); + + /** + * Inserts a Java Object to the byte buffer. + * + * @param javaType the java type + * @param buffer the buffer + * @param byteOffset the byte offset + * @param bitOffset the bit offset + * @param size the size + */ + public void insert(Object javaType, byte[] buffer, int byteOffset, int bitOffset, int size); +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/S7Serializer.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/S7Serializer.java new file mode 100644 index 0000000..0284d7a --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/S7Serializer.java @@ -0,0 +1,72 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api; + + +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.PlcS7PointVariable; +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.S7Exception; + +/** + * @author Thomas Rudin + */ +public interface S7Serializer { + + /** + * Dispenses an Object from the mapping of the Datablock. + * + * @param the generic type + * @param beanClass the bean class + * @param dbNum the db num + * @param byteOffset the byte offset + * @return the t + * @throws S7Exception the s7 exception + */ + T dispense(Class beanClass, int dbNum, int byteOffset) throws S7Exception; + + /** + * Dispense. + * + * @param the generic type + * @param beanClass the bean class + * @param dbNum the db num + * @param byteOffset the byte offset + * @param blockSize the block size + * @return the t + * @throws S7Exception the s7 exception + */ + T dispense(Class beanClass, int dbNum, int byteOffset, int blockSize) throws S7Exception; + + + /** + * Dispense. + * + * @param plcs7PointVariable the point + * @return Object + * @throws S7Exception the s7 exception + */ + Object dispense(PlcS7PointVariable plcs7PointVariable) throws S7Exception; + + /** + * Stores an Object to the Datablock. + * + * @param bean the bean + * @param dbNum the db num + * @param byteOffset the byte offset + */ + void store(Object bean, int dbNum, int byteOffset); + +} \ No newline at end of file diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/S7Type.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/S7Type.java new file mode 100644 index 0000000..8a509e1 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/S7Type.java @@ -0,0 +1,116 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api; + +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.impl.serializer.converter.*; + +/** + * Type of the Address + * + * @author Thomas Rudin Libnodave: + * libnodave.sourceforge.net + */ +public enum S7Type { + /** + * Boolean type + */ + BOOL(BitConverter.class, 0, 1), + + /** + * Byte type + */ + BYTE(ByteConverter.class, 1, 0), + + /** + * A INT-type + */ + INT(ShortConverter.class, 2, 0), + + /** + * A DINT-type (same as DWORD-type) + */ + DINT(LongConverter.class, 4, 0), + + /** + * A Word-type (same as int-type) + */ + WORD(IntegerConverter.class, 2, 0), + /** + * Double word + */ + DWORD(LongConverter.class, 4, 0), + + /** + * Real-type, corresponds to float or double + */ + REAL(RealConverter.class, 4, 0), + + /** + * String type, size must be specified manually + */ + STRING(StringConverter.class, 2, 0), + + /** + * Simple Date with 2 bytes in length + */ + DATE(DateConverter.class, 2, 0), + + /** + * Time-type, 4 bytes in length, number of millis + */ + TIME(TimeConverter.class, 4, 0), + + /** + * Full Date and time format with precision in milliseconds + */ + DATE_AND_TIME(DateAndTimeConverter.class, 8, 0), + + /** + * Structure type + */ + STRUCT(StructConverter.class, 0, 0); + + private final int byteSize; + private final int bitSize; + + private final Class serializer; + + /** + * Enum Constructor + * + * @param serializer S7Serializable + * @param byteSize A Byte Size + * @param bitSize A bit Size + */ + S7Type(final Class serializer, final int byteSize, final int bitSize) { + this.serializer = serializer; + this.bitSize = bitSize; + this.byteSize = byteSize; + } + + public int getBitSize() { + return this.bitSize; + } + + public int getByteSize() { + return this.byteSize; + } + + public Class getSerializer() { + return this.serializer; + } +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/SiemensPLCS.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/SiemensPLCS.java new file mode 100644 index 0000000..b0f8ca0 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/SiemensPLCS.java @@ -0,0 +1,62 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api; + +/** + * @author pnoker + * @version 2025.9.0 + * @since 2022.1.0 + */ +public enum SiemensPLCS { + + /** + * S200 + */ + S_200, + + /** + * S200Smart + */ + S_200_SMART, + + /** + * except the 200 series + */ + S_NON_200, + + /** + * S300 + */ + S_300, + + /** + * S400 + */ + S_400, + + /** + * S1200 + */ + S_1200, + + /** + * S1500 + */ + S_1500, + +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/annotation/Array.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/annotation/Array.java new file mode 100644 index 0000000..069e573 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/annotation/Array.java @@ -0,0 +1,31 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.annotation; + +import java.lang.annotation.*; + +/** + * Annotation for array-declaration + * + * @author Thomas Rudin + */ +@Target(value = {ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Array { + int size(); +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/annotation/Datablock.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/annotation/Datablock.java new file mode 100644 index 0000000..f7e37f1 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/annotation/Datablock.java @@ -0,0 +1,30 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.annotation; + +import java.lang.annotation.*; + +/** + * Annotation for a datablock + * + * @author Thomas Rudin + */ +@Target(value = {ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Datablock { +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/annotation/S7Variable.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/annotation/S7Variable.java new file mode 100644 index 0000000..7a143ac --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/annotation/S7Variable.java @@ -0,0 +1,67 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.annotation; + +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.S7Type; + +import java.lang.annotation.*; + +/** + * Defines an Offset in a DB + * + * @author Thomas Rudin + */ +@Target(value = {ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface S7Variable { + /** + * The size of the array + * + * @return Size + */ + int arraySize() default 1; + + /** + * The bit offset, if any + * + * @return Offset + */ + int bitOffset() default 0; + + /** + * The Byte Offset + * + * @return Offset + */ + int byteOffset(); + + /** + * The specified size (for String) + * + * @return Size + */ + int size() default 0; + + /** + * The corresponding S7 Type + * + * @return S7Type + */ + S7Type type(); + +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/factory/S7ConnectorFactory.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/factory/S7ConnectorFactory.java new file mode 100644 index 0000000..f8a122a --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/factory/S7ConnectorFactory.java @@ -0,0 +1,129 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.factory; + +import org.nl.common.exception.ExceptionConstant; +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.S7Connector; +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.SiemensPLCS; +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.impl.S7TCPConnection; + +/** + * S7 connector factory, currently only for TCP connections + * + * @author Thomas Rudin + */ +public class S7ConnectorFactory { + + private S7ConnectorFactory() { + throw new IllegalStateException(ExceptionConstant.UTILITY_CLASS); + } + + /** + * @param type choose a siemens plc type to build a tcp connector. + * @return returns a new TCP connection builder + */ + public static TCPConnectionBuilder buildTCPConnector(SiemensPLCS type) { + return new TCPConnectionBuilder(type); + } + + public static TCPConnectionBuilder buildTCPConnector() { + return new TCPConnectionBuilder(SiemensPLCS.S_NON_200); + } + + /** + * TCP Connection builder + */ + public static class TCPConnectionBuilder { + + private final SiemensPLCS plcsType; + private String host; + private int rack = 0; + private int slot = 2; + private int port = 102; + private int timeout = 2000; + + TCPConnectionBuilder(SiemensPLCS type) { + this.plcsType = type; + } + + /** + * Builds a connection with given params + * + * @return S7Connector + */ + public S7Connector build() { + return new S7TCPConnection(this.host, this.rack, this.slot, this.port, this.timeout, this.plcsType); + } + + /** + * use hostname/ip + * + * @param host Host + * @return TCPConnectionBuilder + */ + public TCPConnectionBuilder withHost(final String host) { + this.host = host; + return this; + } + + /** + * use port, default is 102 + * + * @param port Port + * @return TCPConnectionBuilder + */ + public TCPConnectionBuilder withPort(final int port) { + this.port = port; + return this; + } + + /** + * use rack, default is 0 + * + * @param rack Rack + * @return TCPConnectionBuilder + */ + public TCPConnectionBuilder withRack(final int rack) { + this.rack = rack; + return this; + } + + /** + * use slot, default is 2 + * + * @param slot Slot + * @return TCPConnectionBuilder + */ + public TCPConnectionBuilder withSlot(final int slot) { + this.slot = slot; + return this; + } + + /** + * use timeout, default is 2000 + * + * @param timeout Timeout + * @return TCPConnectionBuilder + */ + public TCPConnectionBuilder withTimeout(final int timeout) { + this.timeout = timeout; + return this; + } + + } + +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/factory/S7SerializerFactory.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/factory/S7SerializerFactory.java new file mode 100644 index 0000000..45eff4d --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/factory/S7SerializerFactory.java @@ -0,0 +1,45 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.factory; + +import org.nl.common.exception.ExceptionConstant; +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.S7Connector; +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.S7Serializer; +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.impl.serializer.S7SerializerImpl; + +/** + * S7 Serializer factory + * + * @author Thomas Rudin + */ +public class S7SerializerFactory { + + private S7SerializerFactory() { + throw new IllegalStateException(ExceptionConstant.UTILITY_CLASS); + } + + /** + * Builds a new serializer with given connector + * + * @param connector the connector to use + * @return a serializer instance + */ + public static S7Serializer buildSerializer(final S7Connector connector) { + return new S7SerializerImpl(connector); + } + +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/S7BaseConnection.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/S7BaseConnection.java new file mode 100644 index 0000000..bddc2de --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/S7BaseConnection.java @@ -0,0 +1,133 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.impl; + +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.DaveArea; +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.S7Connector; +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.impl.nodave.Nodave; +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.impl.nodave.S7Connection; + +/** + * Base connection implementation for the S7 PLC communication using the Libnodave library. + * Libnodave is an open-source library for communicating with Siemens S7 PLCs. + * For more information, visit: http://libnodave.sourceforge.net/ + * + * @author Thomas Rudin + */ +public abstract class S7BaseConnection implements S7Connector { + + /** + * The Constant PROPERTY_AREA. + */ + public static final String PROPERTY_AREA = "area"; + /** + * The Constant PROPERTY_AREANUMBER. + */ + public static final String PROPERTY_AREANUMBER = "areanumber"; + /** + * The Constant PROPERTY_BYTES. + */ + public static final String PROPERTY_BYTES = "bytes"; + /** + * The Constant PROPERTY_OFFSET. + */ + public static final String PROPERTY_OFFSET = "offset"; + /** + * The Constant MAX_SIZE. + */ + private static final int MAX_SIZE = 96; + /** + * The dc. + */ + private S7Connection dc; + + /** + * Checks the Result. + * + * @param libnodaveResult the libnodave result + */ + public static void checkResult(final int libnodaveResult) { + if (libnodaveResult != Nodave.RESULT_OK) { + final String msg = Nodave.strerror(libnodaveResult); + throw new IllegalArgumentException("Result: " + msg); + } + } + + /** + * Dump data + * + * @param b the byte stream + */ + protected static void dump(final byte[] b) { + for (final byte element : b) { + System.out.print(Integer.toHexString(element & 0xFF) + ","); + } + } + + /** + * Initialize the connection + * + * @param dc the connection instance + */ + protected void init(final S7Connection dc) { + this.dc = dc; + } + + @Override + public synchronized byte[] read(final DaveArea area, final int areaNumber, final int bytes, final int offset) { + if (bytes > MAX_SIZE) { + final byte[] ret = new byte[bytes]; + + final byte[] currentBuffer = this.read(area, areaNumber, MAX_SIZE, offset); + System.arraycopy(currentBuffer, 0, ret, 0, currentBuffer.length); + + final byte[] nextBuffer = this.read(area, areaNumber, bytes - MAX_SIZE, offset + MAX_SIZE); + System.arraycopy(nextBuffer, 0, ret, currentBuffer.length, nextBuffer.length); + + return ret; + } else { + final byte[] buffer = new byte[bytes]; + final int ret = this.dc.readBytes(area, areaNumber, offset, bytes, buffer); + + checkResult(ret); + return buffer; + } + } + + + @Override + public synchronized void write(final DaveArea area, final int areaNumber, final int offset, final byte[] buffer) { + if (buffer.length > MAX_SIZE) { + // Split buffer + final byte[] subBuffer = new byte[MAX_SIZE]; + final byte[] nextBuffer = new byte[buffer.length - subBuffer.length]; + + System.arraycopy(buffer, 0, subBuffer, 0, subBuffer.length); + System.arraycopy(buffer, MAX_SIZE, nextBuffer, 0, nextBuffer.length); + + this.write(area, areaNumber, offset, subBuffer); + this.write(area, areaNumber, offset + subBuffer.length, nextBuffer); + } else { + // Size fits + final int ret = this.dc.writeBytes(area, areaNumber, offset, buffer.length, buffer); + // Check return-value + checkResult(ret); + } + } + + +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/S7TCPConnection.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/S7TCPConnection.java new file mode 100644 index 0000000..457c163 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/S7TCPConnection.java @@ -0,0 +1,145 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.impl; + +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.S7Exception; +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.DaveArea; +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.SiemensPLCS; +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.impl.nodave.Nodave; +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.impl.nodave.PLCinterface; +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.impl.nodave.TCPConnection; + +import java.net.InetSocketAddress; +import java.net.Socket; + +/** + * TCP_Connection to a S7 PLC + *

+ * 参考: http://libnodave.sourceforge.net + * + * @author Thomas Rudin + */ +public final class S7TCPConnection extends S7BaseConnection { + + /** + * The Host to connect to + */ + private final String host; + /** + * The port to connect to + */ + private final int port; + /** + * Rack number + */ + private final int rack; + /** + * Slot number + */ + private final int slot; + /** + * Timeout number + */ + private final int timeout; + /** + * To connect device type,such as S200 + */ + private final SiemensPLCS siemensPLCS; + /** + * The Connection + */ + private TCPConnection tcpConnection; + /** + * The Interface + */ + private PLCinterface plCinterface; + /** + * The Socket + */ + private Socket socket; + + /** + * Creates a new Instance to the given host, rack, slot and port + * + * @param host Host + * @param rack Rack + * @param slot Slot + * @param port Port + * @param timeout Timeout + * @param siemensPLCS SiemensPLCS + * @throws S7Exception S7Exception + */ + public S7TCPConnection(final String host, final int rack, final int slot, final int port, final int timeout, final SiemensPLCS siemensPLCS) throws S7Exception { + this.host = host; + this.rack = rack; + this.slot = slot; + this.port = port; + this.timeout = timeout; + this.siemensPLCS = siemensPLCS; + this.setupSocket(); + } + + @Override + public void close() { + try { + this.socket.close(); + } catch (final Exception e) { + e.printStackTrace(); + } + } + + /** + * Sets up the socket + */ + private void setupSocket() { + try { + this.socket = new Socket(); + this.socket.setSoTimeout(2000); + this.socket.connect(new InetSocketAddress(this.host, this.port), this.timeout); + + //select the plc interface protocol by the plcsType + int protocol; + switch (this.siemensPLCS) { + case S_200: + protocol = Nodave.PROTOCOL_ISOTCP243; + break; + case S_NON_200: + case S_300: + case S_400: + case S_1200: + case S_1500: + case S_200_SMART: + default: + protocol = Nodave.PROTOCOL_ISOTCP; + break; + } + this.plCinterface = new PLCinterface(this.socket.getOutputStream(), this.socket.getInputStream(), "IF1", + DaveArea.LOCAL.getCode(), // TODO Local MPI-Address? + protocol); + + this.tcpConnection = new TCPConnection(this.plCinterface, this.rack, this.slot); + final int res = this.tcpConnection.connectPLC(); + checkResult(res); + + super.init(this.tcpConnection); + } catch (final Exception e) { + throw new S7Exception("constructor", e); + } + + } + +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/nodave/Nodave.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/nodave/Nodave.java new file mode 100644 index 0000000..f341224 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/nodave/Nodave.java @@ -0,0 +1,383 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/* + Part of Libnodave, a free communication libray for Siemens S7 + + (C) Thomas Hergenhahn (thomas.hergenhahn@web.de) 2005. + + Libnodave is free software; you can redistribute it and/or modify + it under the terms of the GNU Library General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + Libnodave is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this; see the file COPYING. If not, write to + the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +package org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.impl.nodave; + +/** + * @author Thomas Rudin + */ +public final class Nodave { + public final static int MAX_RAW_LEN = 2048; + public final static int MPIReachable = 0x30; + public final static int MPIunused = 0x10; + public final static int OrderCodeSize = 21; + + public final static int PartnerListSize = 126; + + public final static int PROTOCOL_ISOTCP = 4; + public final static int PROTOCOL_ISOTCP243 = 5; + public final static int PROTOCOL_MPI_IBH = 223; // MPI with IBH NetLink MPI + // to ethernet gateway + public final static int PROTOCOL_MPI_NLPRO = 230; // MPI with IBH NetLink + // MPI to ethernet + // gateway + public final static int PROTOCOL_NLPRO = 230; // MPI with IBH NetLink MPI to + // ethernet gateway + // to ethernet gateway + public final static int PROTOCOL_PPI_IBH = 224; // PPI with IBH NetLink MPI + + public final static int RESULT_ADDRESS_OUT_OF_RANGE = 5; + /* means the write data size doesn't fit item size */ + public final static int RESULT_CANNOT_EVALUATE_PDU = -123; + public final static int RESULT_CPU_RETURNED_NO_DATA = -124; + public final static int RESULT_EMPTY_RESULT_ERROR = -126; + + public final static int RESULT_EMPTY_RESULT_SET_ERROR = -127; + + public final static int RESULT_ITEM_NOT_AVAILABLE = 10; + /* means a a piece of data is not available in the CPU, e.g. */ + /* when trying to read a non existing DB */ + /* CPU tells it doesn't support to read a bit block with a */ + /* length other than 1 bit. */ + public final static int RESULT_ITEM_NOT_AVAILABLE200 = 3; + /* means a a piece of data is not available in the CPU, e.g. */ + /* when trying to read a non existing DB or bit bloc of length<>1 */ + /* This code seems to be specific to 200 family. */ + /* CPU tells there is no peripheral at address */ + public final static int RESULT_MULTIPLE_BITS_NOT_SUPPORTED = 6; + public final static int RESULT_NO_PERIPHERAL_AT_ADDRESS = 1; + + public final static int RESULT_OK = 0; /* means all ok */ + public final static int RESULT_SHORT_PACKET = -1024; + public final static int RESULT_TIMEOUT = -1025; + public final static int RESULT_UNEXPECTED_FUNC = -128; + public final static int RESULT_UNKNOWN_DATA_UNIT_SIZE = -129; + + public final static int RESULT_UNKNOWN_ERROR = -125; + /* means the data address is beyond the CPUs address range */ + public final static int RESULT_WRITE_DATA_SIZE_MISMATCH = 7; + + private Nodave() { + // Not needed because of utility class + } + + public static float BEFloat(final byte[] b, final int pos) { + int i = 0; + // System.out.println("pos" + pos); + + i |= Nodave.USByte(b, pos); + i <<= 8; + i |= Nodave.USByte(b, pos + 1); + i <<= 8; + i |= Nodave.USByte(b, pos + 2); + i <<= 8; + i |= Nodave.USByte(b, pos + 3); + final float f = Float.intBitsToFloat(i); + return (f); + } + + public static byte[] bswap_16(int a) { + final byte[] b = new byte[2]; + b[1] = (byte) (a & 0xff); + a = a >> 8; + b[0] = (byte) (a & 0xff); + return b; + } + + public static byte[] bswap_32(int a) { + final byte[] b = new byte[4]; + b[3] = (byte) (a & 0xff); + a = a >> 8; + b[2] = (byte) (a & 0xff); + a = a >> 8; + b[1] = (byte) (a & 0xff); + a = a >> 8; + b[0] = (byte) (a & 0xff); + return b; + } + + public static byte[] bswap_32(long a) { + final byte[] b = new byte[4]; + b[3] = (byte) (a & 0xff); + a = a >> 8; + b[2] = (byte) (a & 0xff); + a = a >> 8; + b[1] = (byte) (a & 0xff); + a = a >> 8; + b[0] = (byte) (a & 0xff); + return b; + } + + /** + * This doesn't swap anything, but the name fits into the series + * + * @param a value + * @return Byte Array + */ + public static byte[] bswap_8(final int a) { + final byte[] b = new byte[1]; + b[0] = (byte) (a & 0xff); + return b; + } + + /** + * Dumps len hex codes from byte array mem beginning at index start + * + * @param text Text + * @param mem Mem + * @param start Start + * @param len Length + */ + public static void dump(final String text, final byte[] mem, final int start, final int len) { + System.out.print(text + " "); + for (int i = start; i < (start + len); i++) { + int j = mem[i]; + if (j < 0) { + j += 256; + } + String s = Integer.toHexString(j); + if (s.length() < 2) { + s = "0" + s; + } + System.out.print(s + ","); + } + System.out.println(" "); + } + + public static long SBELong(final byte[] b, final int pos) { + final int i = b[pos]; + int j = b[pos + 1]; + int k = b[pos + 2]; + int l = b[pos + 3]; + // if (i < 0) + // i += 256; + if (j < 0) { + j += 256; + } + if (k < 0) { + k += 256; + } + if (l < 0) { + l += 256; + } + return ((256 * k) + l) + (65536L * ((256 * i) + j)); + } + + public static int SBEWord(final byte[] b, final int pos) { + final int i = b[pos]; + int k = b[pos + 1]; + // if (i < 0) + // i += 256; + if (k < 0) { + k += 256; + } + return ((256 * i) + k); + } + + public static int SByte(final byte[] b, final int pos) { + final int i = b[pos]; + return (i); + } + + public static void setBEFloat(final byte[] b, final int pos, final float f) { + int a = Float.floatToIntBits(f); + b[pos + 3] = (byte) (a & 0xff); + a = a >> 8; + b[pos + 2] = (byte) (a & 0xff); + a = a >> 8; + b[pos + 1] = (byte) (a & 0xff); + a = a >> 8; + b[pos] = (byte) (a & 0xff); + } + + public static void setUSBELong(final byte[] b, final int pos, long a) { + b[pos + 3] = (byte) (a & 0xff); + a = a >> 8; + b[pos + 2] = (byte) (a & 0xff); + a = a >> 8; + b[pos + 1] = (byte) (a & 0xff); + a = a >> 8; + b[pos] = (byte) (a & 0xff); + } + + public static void setUSBEWord(final byte[] b, final int pos, final int val) { + b[pos] = ((byte) (val / 0x100)); + b[pos + 1] = ((byte) (val % 0x100)); + } + + public static void setUSByte(final byte[] b, final int pos, final int val) { + b[pos] = ((byte) (val & 0xff)); + } + + public static String strerror(final int code) { + switch (code) { + case RESULT_OK: + return "ok"; + case RESULT_MULTIPLE_BITS_NOT_SUPPORTED: + return "the CPU doesn't support reading a bit block of length<>1"; + case RESULT_ITEM_NOT_AVAILABLE: + return "the desired item is not available in the PLC"; + case RESULT_ITEM_NOT_AVAILABLE200: + return "the desired item is not available in the PLC (200 family)"; + case RESULT_ADDRESS_OUT_OF_RANGE: + return "the desired address is beyond limit for this PLC"; + case RESULT_CPU_RETURNED_NO_DATA: + return "the PLC returned a packet with no result data"; + case Nodave.RESULT_UNKNOWN_ERROR: + return "the PLC returned an error code not understood by this library"; + case Nodave.RESULT_EMPTY_RESULT_ERROR: + return "this result contains no data"; + case Nodave.RESULT_EMPTY_RESULT_SET_ERROR: + return "can't work with an undefined result set"; + case Nodave.RESULT_CANNOT_EVALUATE_PDU: + return "can't evaluate the received PDU"; + case Nodave.RESULT_WRITE_DATA_SIZE_MISMATCH: + return "Write data size error"; + case Nodave.RESULT_NO_PERIPHERAL_AT_ADDRESS: + return "No data from I/O module"; + case Nodave.RESULT_UNEXPECTED_FUNC: + return "Unexpected function code in answer"; + case Nodave.RESULT_UNKNOWN_DATA_UNIT_SIZE: + return "PLC responds wit an unknown data type"; + case Nodave.RESULT_SHORT_PACKET: + return "Short packet from PLC"; + case Nodave.RESULT_TIMEOUT: + return "Timeout when waiting for PLC response"; + case 0x8000: + return "function already occupied."; + case 0x8001: + return "not allowed in current operating status."; + case 0x8101: + return "hardware fault."; + case 0x8103: + return "object access not allowed."; + case 0x8104: + return "context is not supported."; + case 0x8105: + return "invalid address."; + case 0x8106: + return "data type not supported."; + case 0x8107: + return "data type not consistent."; + case 0x810A: + return "object doesn't exist."; + case 0x8500: + return "incorrect PDU size."; + case 0x8702: + return "address invalid."; + case 0xd201: + return "block name syntax error."; + case 0xd202: + return "syntax error function parameter."; + case 0xd203: + return "syntax error block type."; + case 0xd204: + return "no linked block in storage medium."; + case 0xd205: + return "object already exists."; + case 0xd206: + return "object already exists."; + case 0xd207: + return "block exists in EPROM."; + case 0xd209: + return "block doesn't exist."; + case 0xd20e: + return "no block doesn't exist."; + case 0xd210: + return "block number too big."; + case 0xd240: + return "unfinished block transfer in progress?"; + case 0xd241: + return "protected by password."; + default: + return "no message defined for code: " + code + "!"; + } + } + + public static byte[] toPLCfloat(final double d) { + final float f = (float) d; + return toPLCfloat(f); + } + + public static byte[] toPLCfloat(final float f) { + final int i = Float.floatToIntBits(f); + return bswap_32(i); + } + + public static long USBELong(final byte[] b, final int pos) { + int i = b[pos]; + int j = b[pos + 1]; + int k = b[pos + 2]; + int l = b[pos + 3]; + // System.out.println( + // pos + " 0:" + i + " 1:" + j + " 2:" + k + " 3:" + l); + if (i < 0) { + i += 256; + } + if (j < 0) { + j += 256; + } + if (k < 0) { + k += 256; + } + if (l < 0) { + l += 256; + } + return ((256 * k) + l) + (65536L * ((256 * i) + j)); + } + + public static int USBEWord(final byte[] b, final int pos) { + int i = b[pos]; + int k = b[pos + 1]; + if (i < 0) { + i += 256; + } + if (k < 0) { + k += 256; + } + return ((256 * i) + k); + } + + public static int USByte(final byte[] b, final int pos) { + int i = b[pos]; + if (i < 0) { + i += 256; + } + return (i); + } + +} \ No newline at end of file diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/nodave/PDU.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/nodave/PDU.java new file mode 100644 index 0000000..6bb4449 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/nodave/PDU.java @@ -0,0 +1,496 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/* + Part of Libnodave, a free communication libray for Siemens S7 + + (C) Thomas Hergenhahn (thomas.hergenhahn@web.de) 2005. + + Libnodave is free software; you can redistribute it and/or modify + it under the terms of the GNU Library General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + Libnodave is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this; see the file COPYING. If not, write to + the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +package org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.impl.nodave; + +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.DaveArea; + +/** + * @author Thomas Rudin + */ +public final class PDU { + /** + * known function codes + */ + public final static byte FUNC_READ = 4; + + public final static byte FUNC_WRITE = 5; + + public int data; + public int param; // the position of the parameters; + public int plen; + public int udata; + public int udlen; + int dlen; + int error; + int header; // the position of the header; + int hlen; + byte[] mem; + + /** + * set up the PDU information + * + * @param mem Mem + * @param pos Pos + */ + public PDU(final byte[] mem, final int pos) { + this.mem = mem; + this.header = pos; + } + + public int addBitVarToReadRequest(final int area, final int DBnum, final int start, final int len) { + final byte pa[] = {0x12, 0x0a, 0x10, 0x01, /* single bits */ + 0x00, 0x1A, /* insert length in bytes here */ + 0x00, 0x0B, /* insert DB number here */ + (byte) 0x84, /* change this to real area code */ + 0x00, 0x00, (byte) 0xC0 /* insert start address in bits */ + }; + Nodave.setUSBEWord(pa, 4, len); + Nodave.setUSBEWord(pa, 6, DBnum); + Nodave.setUSBELong(pa, 8, start); + Nodave.setUSByte(pa, 8, area); + + this.mem[this.param + 1]++; + System.arraycopy(pa, 0, this.mem, this.param + this.plen, pa.length); + this.plen += pa.length; + Nodave.setUSBEWord(this.mem, this.header + 6, this.plen); + return 0; + + } + + public void addBitVarToWriteRequest(final DaveArea area, final int DBnum, final int start, final int byteCount, + final byte[] buffer) { + final byte da[] = {0, 3, 0, 0,}; + final byte pa[] = {0x12, 0x0a, 0x10, 0x01, /* single bit */ + 0, 0, /* insert length in bytes here */ + 0, 0, /* insert DB number here */ + 0, /* change this to real area code */ + 0, 0, 0 /* insert start address in bits */ + }; + if ((area == DaveArea.TIMER) || (area == DaveArea.COUNTER) || (area == DaveArea.TIMER200) + || (area == DaveArea.COUNTER200)) { + pa[3] = (byte) area.getCode(); + pa[4] = (byte) (((byteCount + 1) / 2) / 0x100); + pa[5] = (byte) (((byteCount + 1) / 2) & 0xff); + } else if ((area == DaveArea.ANALOGINPUTS200) || (area == DaveArea.ANALOGOUTPUTS200)) { + pa[3] = 4; + pa[4] = (byte) (((byteCount + 1) / 2) / 0x100); + pa[5] = (byte) (((byteCount + 1) / 2) & 0xff); + } else { + pa[4] = (byte) (byteCount / 0x100); + pa[5] = (byte) (byteCount & 0xff); + } + pa[6] = (byte) (DBnum / 256); + pa[7] = (byte) (DBnum & 0xff); + pa[8] = (byte) area.getCode(); + pa[11] = (byte) (start & 0xff); + pa[10] = (byte) ((start / 0x100) & 0xff); + pa[9] = (byte) (start / 0x10000); + + if ((this.dlen % 2) != 0) { + this.addData(da, 1); + } + + this.mem[this.param + 1]++; + if (this.dlen > 0) { + final byte[] saveData = new byte[this.dlen]; + System.arraycopy(this.mem, this.data, saveData, 0, this.dlen); + System.arraycopy(saveData, 0, this.mem, this.data + pa.length, this.dlen); + } + System.arraycopy(pa, 0, this.mem, this.param + this.plen, pa.length); + this.plen += pa.length; + Nodave.setUSBEWord(this.mem, this.header + 6, this.plen); + this.data = this.param + this.plen; + + this.addData(da); + this.addValue(buffer); + } + + /** + * Add data after parameters, set dlen as needed. Needs valid header and + * parameters + */ + void addData(final byte[] newData) { + final int appPos = this.data + this.dlen; // append to this position + this.dlen += newData.length; + System.arraycopy(newData, 0, this.mem, appPos, newData.length); + Nodave.setUSBEWord(this.mem, this.header + 8, this.dlen); + } + + /** + * Add len bytes of len after parameters from a maybe longer block of bytes. + * Set dlen as needed. Needs valid header and parameters + * + * @param newData New Data + * @param len Length + */ + public void addData(final byte[] newData, final int len) { + final int appPos = this.data + this.dlen; // append to this position + this.dlen += len; + System.arraycopy(newData, 0, this.mem, appPos, len); + Nodave.setUSBEWord(this.mem, this.header + 8, this.dlen); + } + + public void addParam(final byte[] pa) { + this.plen = pa.length; + System.arraycopy(pa, 0, this.mem, this.param, this.plen); + Nodave.setUSBEWord(this.mem, this.header + 6, this.plen); + // mem[header + 6] = (byte) (pa.length / 256); + // mem[header + 7] = (byte) (pa.length % 256); + this.data = this.param + this.plen; + this.dlen = 0; + } + + /* + * add data in user data. Add a user data header, if not yet present. + */ + public void addUserData(final byte[] da) { + final byte udh[] = {(byte) 0xff, 9, 0, 0}; + if (this.dlen == 0) { + this.addData(udh); + } + this.addValue(da); + } + + /** + * Add values after value header in data, adjust dlen and data count. Needs + * valid header,parameters,data,dlen + */ + void addValue(final byte[] values) { + int valCount = (0x100 * this.mem[this.data + 2]) + this.mem[this.data + 3]; + if (this.mem[this.data + 1] == 4) { // bit data, length is in bits + valCount += 8 * values.length; + } else if (this.mem[this.data + 1] == 9) { // byte data, length is in + // bytes + valCount += values.length; + } else { + // XXX + } + if (this.udata == 0) { + this.udata = this.data + 4; + } + this.udlen += values.length; + Nodave.setUSBEWord(this.mem, this.data + 2, valCount); + this.addData(values); + } + + public int addVarToReadRequest(final DaveArea area, final int DBnum, int start, final int len) { + final byte[] pa = {0x12, 0x0a, 0x10, + 0x02, /* 1=single bit, 2=byte, 4=word */ + 0x00, 0x1A, /* length in bytes */ + 0x00, 0x0B, /* DB number */ + (byte) 0x84, // * area code */ + 0x00, 0x00, (byte) 0xC0 /* start address in bits */ + }; + + if ((area == DaveArea.ANALOGINPUTS200) || (area == DaveArea.ANALOGOUTPUTS200)) { + pa[3] = 4; + start *= 8; /* bits */ + } else if ((area == DaveArea.TIMER) || (area == DaveArea.COUNTER) || (area == DaveArea.TIMER200) + || (area == DaveArea.COUNTER200)) { + pa[3] = (byte) area.getCode(); + } else { + start *= 8; /* bits */ + } + + Nodave.setUSBEWord(pa, 4, len); + Nodave.setUSBEWord(pa, 6, DBnum); + Nodave.setUSBELong(pa, 8, start); + Nodave.setUSByte(pa, 8, area.getCode()); + + this.mem[this.param + 1]++; + System.arraycopy(pa, 0, this.mem, this.param + this.plen, pa.length); + this.plen += pa.length; + Nodave.setUSBEWord(this.mem, this.header + 6, this.plen); + /** + * TODO calc length of result. Do not add variable if it would exceed + * max. result length. + */ + return 0; + } + + public void addVarToWriteRequest(final DaveArea area, final int DBnum, int start, final int byteCount, + final byte[] buffer) { + final byte da[] = {0, 4, 0, 0,}; + final byte pa[] = {0x12, 0x0a, 0x10, 0x02, + /* unit (for count?, for consistency?) byte */ + 0, 0, /* length in bytes */ + 0, 0, /* DB number */ + 0, /* area code */ + 0, 0, 0 /* start address in bits */ + }; + if ((area == DaveArea.TIMER) || (area == DaveArea.COUNTER) || (area == DaveArea.TIMER200) + || (area == DaveArea.COUNTER200)) { + pa[3] = (byte) area.getCode(); + pa[4] = (byte) (((byteCount + 1) / 2) / 0x100); + pa[5] = (byte) (((byteCount + 1) / 2) & 0xff); + } else if ((area == DaveArea.ANALOGINPUTS200) || (area == DaveArea.ANALOGOUTPUTS200)) { + pa[3] = 4; + pa[4] = (byte) (((byteCount + 1) / 2) / 0x100); + pa[5] = (byte) (((byteCount + 1) / 2) & 0xff); + } else { + pa[4] = (byte) (byteCount / 0x100); + pa[5] = (byte) (byteCount & 0xff); + } + pa[6] = (byte) (DBnum / 256); + pa[7] = (byte) (DBnum & 0xff); + pa[8] = (byte) (area.getCode()); + start *= 8; /* number of bits */ + pa[11] = (byte) (start & 0xff); + pa[10] = (byte) ((start / 0x100) & 0xff); + pa[9] = (byte) (start / 0x10000); + if ((this.dlen % 2) != 0) { + this.addData(da, 1); + } + this.mem[this.param + 1]++; + if (this.dlen > 0) { + final byte[] saveData = new byte[this.dlen]; + System.arraycopy(this.mem, this.data, saveData, 0, this.dlen); + System.arraycopy(saveData, 0, this.mem, this.data + pa.length, this.dlen); + } + System.arraycopy(pa, 0, this.mem, this.param + this.plen, pa.length); + this.plen += pa.length; + Nodave.setUSBEWord(this.mem, this.header + 6, this.plen); + this.data = this.param + this.plen; + this.addData(da); + this.addValue(buffer); + } + + /** + * construct a write request for a single item in PLC memory. + */ + /* + * void constructWriteRequest( int area, int DBnum, int start, int len, + * byte[] buffer) { byte pa[] = new byte[14]; byte da[] = { 0, 4, 0, 0 }; + * pa[0] = PDU.FUNC_WRITE; pa[1] = (byte) 0x01; pa[2] = (byte) 0x12; pa[3] = + * (byte) 0x0a; pa[4] = (byte) 0x10; pa[5] = (byte) 0x02; + * + * Nodave.setUSBEWord(pa, 6, len); Nodave.setUSBEWord(pa, 8, DBnum); + * Nodave.setUSBELong(pa, 10, 8 * start); // the bit address + * Nodave.setUSByte(pa, 10, area); initHeader(1); addParam(pa); addData(da); + * addValue(buffer); if ((Nodave.Debug & Nodave.DEBUG_PDU) != 0) { dump(); } + * } + */ + + /** + * display information about a PDU + */ + public void dump() { + Nodave.dump("PDU header ", this.mem, this.header, this.hlen); + System.out.println("plen: " + this.plen + " dlen: " + this.dlen); + Nodave.dump("Parameter", this.mem, this.param, this.plen); + if (this.dlen > 0) { + Nodave.dump("Data ", this.mem, this.data, this.dlen); + } + if (this.udlen > 0) { + Nodave.dump("result Data ", this.mem, this.udata, this.udlen); + } + } + + public int getError() { + return this.error; + } + + /** + * return the function code of the PDU + * + * @return Function Code Of PDU + */ + public int getFunc() { + return Nodave.USByte(this.mem, this.param + 0); + } + + /* + * typedef struct { uc P; // allways 0x32 uc type; // a type? type 2 and 3 + * headers are two bytes longer. uc a,b; // currently unknown us number; // + * Number, can be used to identify answers corresponding to requests us + * plen; // length of parameters which follow this header us dlen; // length + * of data which follows the parameters uc x[2]; // only present in type 2 + * and 3 headers. This may contain error information. } PDUHeader; + */ + + /** + * return the number of the PDU + * + * @return number + */ + public int getNumber() { + return Nodave.USBEWord(this.mem, this.header + 4); + } + + /** + * set the number of the PDU + * + * @param n number + */ + public void setNumber(final int n) { + Nodave.setUSBEWord(this.mem, this.header + 4, n); + } + + /** + * reserve space for the header of a new PDU + * + * @param type Type + */ + public void initHeader(final int type) { + if ((type == 2) || (type == 3)) { + this.hlen = 12; + } else { + this.hlen = 10; + } + for (int i = 0; i < this.hlen; i++) { + this.mem[this.header + i] = 0; + } + this.param = this.header + this.hlen; + this.mem[this.header] = (byte) 0x32; + this.mem[this.header + 1] = (byte) type; + this.dlen = 0; + this.plen = 0; + this.udlen = 0; + this.data = 0; + this.udata = 0; + } + + public void initReadRequest() { + final byte pa[] = new byte[2]; + pa[0] = PDU.FUNC_READ; + pa[1] = (byte) 0x00; + this.initHeader(1); + this.addParam(pa); + } + + /** + * prepare a read request with no item. + */ + public void prepareReadRequest() { + final byte pa[] = new byte[2]; + pa[0] = PDU.FUNC_READ; + pa[1] = (byte) 0x00; + this.initHeader(1); + this.addParam(pa); + } + + /** + * prepare a write request with no item. + */ + public void prepareWriteRequest() { + final byte pa[] = new byte[2]; + pa[0] = PDU.FUNC_WRITE; + pa[1] = (byte) 0x00; + this.initHeader(1); + this.addParam(pa); + } + + /** + * Set up a PDU instance to reflect the structure of data present in the + * memory area given to initHeader. Needs valid header. + * + * @return Result + */ + public int setupReceivedPDU() { + int res = Nodave.RESULT_CANNOT_EVALUATE_PDU; // just assume the worst + if ((this.mem[this.header + 1] == 2) || (this.mem[this.header + 1] == 3)) { + this.hlen = 12; + res = Nodave.USBEWord(this.mem, this.header + 10); + } else { + this.error = 0; + this.hlen = 10; + res = 0; + } + this.param = this.header + this.hlen; + this.plen = Nodave.USBEWord(this.mem, this.header + 6); + this.data = this.param + this.plen; + this.dlen = Nodave.USBEWord(this.mem, this.header + 8); + this.udlen = 0; + this.udata = 0; + return res; + } + + public int testPGReadResult() { + if (this.mem[this.param] != 0) { + return Nodave.RESULT_UNEXPECTED_FUNC; + } + return this.testResultData(); + } + + ; + + int testReadResult() { + if (this.mem[this.param] != FUNC_READ) { + return Nodave.RESULT_UNEXPECTED_FUNC; + } + return this.testResultData(); + } + + /* + + */ + int testResultData() { + int res = Nodave.RESULT_CANNOT_EVALUATE_PDU; // just assume the worst + if ((this.mem[this.data] == (byte) 255) && (this.dlen > 4)) { + res = Nodave.RESULT_OK; + this.udata = this.data + 4; + // udlen=data[2]*0x100+data[3]; + this.udlen = Nodave.USBEWord(this.mem, this.data + 2); + if (this.mem[this.data + 1] == 4) { + this.udlen >>= 3; /* len is in bits, adjust */ + } else if (this.mem[this.data + 1] == 9) { + /* len is already in bytes, ok */ + } else if (this.mem[this.data + 1] == 3) { + /* len is in bits, but there is a byte per result bit, ok */ + } else { + res = Nodave.RESULT_UNKNOWN_DATA_UNIT_SIZE; + } + } else { + res = this.mem[this.data]; + } + return res; + } + + int testWriteResult() { + int res = Nodave.RESULT_CANNOT_EVALUATE_PDU; + if (this.mem[this.param] != FUNC_WRITE) { + return Nodave.RESULT_UNEXPECTED_FUNC; + } + if ((this.mem[this.data] == 255)) { + res = Nodave.RESULT_OK; + } else { + res = this.mem[this.data]; + } + return res; + } + +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/nodave/PLCinterface.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/nodave/PLCinterface.java new file mode 100644 index 0000000..b24eda3 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/nodave/PLCinterface.java @@ -0,0 +1,104 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/* + Part of Libnodave, a free communication libray for Siemens S7 + + (C) Thomas Hergenhahn (thomas.hergenhahn@web.de) 2005. + + Libnodave is free software; you can redistribute it and/or modify + it under the terms of the GNU Library General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + Libnodave is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this; see the file COPYING. If not, write to + the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +package org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.impl.nodave; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * @author Thomas Rudin + */ +public final class PLCinterface { + InputStream in; + int localMPI; // the adapter's MPI address + String name; + + OutputStream out; + int protocol; // The kind of transport used on this interface. + int wp, rp; + + public PLCinterface(final OutputStream out, final InputStream in, final String name, final int localMPI, + final int protocol) { + this.init(out, in, name, localMPI, protocol); + } + + public void init(final OutputStream oStream, final InputStream iStream, final String name, final int localMPI, + final int protocol) { + this.out = oStream; + this.in = iStream; + this.name = name; + this.localMPI = localMPI; + this.protocol = protocol; + } + + public int read(final byte[] b, int start, int len) { + int res; + try { + int retry = 0; + while ((this.in.available() <= 0) && (retry < 500)) { + try { + if (retry > 0) { + Thread.sleep(1); + } + retry++; + } catch (final InterruptedException e) { + e.printStackTrace(); + } + } + res = 0; + while ((this.in.available() > 0) && (len > 0)) { + res = this.in.read(b, start, len); + start += res; + len -= res; + } + return res; + } catch (final IOException e) { + e.printStackTrace(); + return 0; + } + } + + public void write(final byte[] b, final int start, final int len) { + try { + this.out.write(b, start, len); + } catch (final IOException e) { + System.err.println("Interface.write: " + e); + } + } + +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/nodave/Result.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/nodave/Result.java new file mode 100644 index 0000000..defcfa1 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/nodave/Result.java @@ -0,0 +1,49 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/* + Part of Libnodave, a free communication libray for Siemens S7 + + (C) Thomas Hergenhahn (thomas.hergenhahn@web.de) 2005. + + Libnodave is free software; you can redistribute it and/or modify + it under the terms of the GNU Library General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + Libnodave is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this; see the file COPYING. If not, write to + the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +package org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.impl.nodave; + +/** + * @author Thomas Hergenhahn + *

+ * To change the template for this generated type comment go to + * Window-Preferences-Java-Code Generation-Code and Comments + */ +public final class Result { + public int bufferStart; + public int error; + public int length; +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/nodave/ResultSet.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/nodave/ResultSet.java new file mode 100644 index 0000000..4bff25e --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/nodave/ResultSet.java @@ -0,0 +1,65 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/* + Part of Libnodave, a free communication libray for Siemens S7 + + (C) Thomas Hergenhahn (thomas.hergenhahn@web.de) 2005. + + Libnodave is free software; you can redistribute it and/or modify + it under the terms of the GNU Library General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + Libnodave is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this; see the file COPYING. If not, write to + the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +package org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.impl.nodave; + +/** + * @author Thomas Hergenhahn + */ +public final class ResultSet { + public Result[] results; + private int errorState, numResults; + + public int getErrorState() { + return this.errorState; + } + + public void setErrorState(final int error) { + this.errorState = error; + } + + ; + + public int getNumResults() { + return this.numResults; + } + + public void setNumResults(final int nr) { + this.numResults = nr; + } + + ; +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/nodave/S7Connection.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/nodave/S7Connection.java new file mode 100644 index 0000000..de35e58 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/nodave/S7Connection.java @@ -0,0 +1,423 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/* + Part of Libnodave, a free communication libray for Siemens S7 + + (C) Thomas Hergenhahn (thomas.hergenhahn@web.de) 2005. + + Libnodave is free software; you can redistribute it and/or modify + it under the terms of the GNU Library General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + Libnodave is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this; see the file COPYING. If not, write to + the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +package org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.impl.nodave; + +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.DaveArea; + +import java.util.concurrent.Semaphore; + +/** + * This class comprises the variables and methods common to connections to an S7 + * PLC regardless of the type of transport. + * + * @author Thomas Hergenhahn + */ +public abstract class S7Connection { + static int tmo_normal = 150; + public int maxPDUlength; + public byte messageNumber = 0; + public byte[] msgIn; + public byte[] msgOut; + public int packetNumber = 0; // packetNumber in transport layer + public int PDUstartIn; + public int PDUstartOut; + public Semaphore semaphore; + int answLen; // length of last message + /** + * position in result data, incremented when variables are extracted without + * position + */ + int dataPointer; + PLCinterface iface; // pointer to used interface + PDU rcvdPDU; + /** + * absolute begin of result data + */ + int udata; + + public S7Connection(final PLCinterface ifa) { + this.iface = ifa; + this.msgIn = new byte[Nodave.MAX_RAW_LEN]; + this.msgOut = new byte[Nodave.MAX_RAW_LEN]; + this.PDUstartIn = 0; + this.PDUstartOut = 0; + this.semaphore = new Semaphore(1); + } + + abstract public int exchange(PDU p1); + + // int numResults; + /* + * class Result { int error; byte[] data; } + */ + /* + * Read a predefined set of values from the PLC. Return ok or an error state + * If a buffer pointer is provided, data will be copied into this buffer. If + * it's NULL you can get your data from the resultPointer in daveConnection + * long as you do not send further requests. + */ + public ResultSet execReadRequest(final PDU p) { + PDU p2; + int errorState; + errorState = this.exchange(p); + + p2 = new PDU(this.msgIn, this.PDUstartIn); + p2.setupReceivedPDU(); + /* + * if (p2.udlen == 0) { dataPointer = 0; answLen = 0; return + * Nodave.RESULT_CPU_RETURNED_NO_DATA; } + */ + final ResultSet rs = new ResultSet(); + if (p2.mem[p2.param + 0] == PDU.FUNC_READ) { + int numResults = p2.mem[p2.param + 1]; + // System.out.println("Results " + numResults); + rs.results = new Result[numResults]; + int pos = p2.data; + for (int i = 0; i < numResults; i++) { + final Result r = new Result(); + r.error = Nodave.USByte(p2.mem, pos); + if (r.error == 255) { + + final int type = Nodave.USByte(p2.mem, pos + 1); + int len = Nodave.USBEWord(p2.mem, pos + 2); + r.error = 0; + // System.out.println("Raw length " + len); + if (type == 4) { + len /= 8; + } else if (type == 3) { + ; // length is ok + } + + // System.out.println("Byte length " + len); + // r.data = new byte[len]; + + // System.arraycopy(p2.mem, pos + 4, r.data, 0, len); + // Nodave.dump("Result " + i + ":", r.data, 0, len); + r.bufferStart = pos + 4; + pos += len; + if ((len % 2) == 1) { + pos++; + } + } else { + System.out.println("Error " + r.error); + } + pos += 4; + rs.results[i] = r; + } + numResults = p2.mem[p2.param + 1]; + rs.setNumResults(numResults); + this.dataPointer = p2.udata; + this.answLen = p2.udlen; + // } + } else { + errorState |= 2048; + } + this.semaphore.release(); + rs.setErrorState(errorState); + return rs; + } + + public int getBYTE() { + this.dataPointer += 1; + return Nodave.SByte(this.msgIn, this.dataPointer - 1); + } + + public int getBYTE(final int pos) { + return Nodave.SByte(this.msgIn, this.udata + pos); + } + + public int getCHAR() { + this.dataPointer += 1; + return Nodave.SByte(this.msgIn, this.dataPointer - 1); + } + + public int getCHAR(final int pos) { + return Nodave.SByte(this.msgIn, this.udata + pos); + } + + /** + * get a signed 32bit value from the current position in result bytes + * + * @return value + */ + public long getDINT() { + this.dataPointer += 4; + return Nodave.SBELong(this.msgIn, this.dataPointer - 4); + } + + /** + * get a signed 32bit value from the specified position in result bytes + * + * @param pos Pos + * @return Value + */ + public long getDINT(final int pos) { + return Nodave.SBELong(this.msgIn, this.udata + pos); + } + + /** + * get an unsigned 32bit value from the specified position in result bytes + * + * @param pos Pos + * @return value + */ + public long getDWORD(final int pos) { + // System.out.println("getDWORD pos " + pos); + return Nodave.USBELong(this.msgIn, this.udata + pos); + } + + /** + * get a float value from the current position in result bytes + * + * @return value + */ + public float getFloat() { + this.dataPointer += 4; + return Nodave.BEFloat(this.msgIn, this.dataPointer - 4); + } + + /* + * The following methods are here to give Siemens users their usual data + * types: + */ + + /** + * get a float value from the specified position in result bytes + * + * @param pos Pos + * @return value + */ + public float getFloat(final int pos) { + // System.out.println("getFloat pos " + pos); + return Nodave.BEFloat(this.msgIn, this.udata + pos); + } + + public int getINT() { + this.dataPointer += 2; + return Nodave.SBEWord(this.msgIn, this.dataPointer - 2); + } + + public int getINT(final int pos) { + return Nodave.SBEWord(this.msgIn, this.udata + pos); + } + + public int getPPIresponse() { + return 0; + } + + /* + * public void sendYOURTURN() { } + */ + public int getResponse() { + return 0; + } + + public int getS16(final int pos) { + return Nodave.SBEWord(this.msgIn, this.udata + pos); + } + + public long getS32(final int pos) { + return Nodave.SBELong(this.msgIn, this.udata + pos); + } + + public int getS8(final int pos) { + return Nodave.SByte(this.msgIn, this.udata + pos); + } + + /** + * get an unsigned 32bit value from the current position in result bytes + * + * @return value + */ + public long getU32() { + this.dataPointer += 4; + return Nodave.USBELong(this.msgIn, this.dataPointer - 4); + } + + public int getUS16(final int pos) { + return Nodave.USBEWord(this.msgIn, this.udata + pos); + } + + public long getUS32(final int pos) { + return Nodave.USBELong(this.msgIn, this.udata + pos); + } + + public int getUS8(final int pos) { + return Nodave.USByte(this.msgIn, this.udata + pos); + } + + /** + * get an unsigned 16bit value from the current position in result bytes + * + * @return word + */ + public int getWORD() { + this.dataPointer += 2; + return Nodave.USBEWord(this.msgIn, this.dataPointer - 2); + } + + /** + * get an unsigned 16bit value from the specified position in result bytes + * + * @param pos Pos + * @return word + */ + public int getWORD(final int pos) { + return Nodave.USBEWord(this.msgIn, this.udata + pos); + } + + /** + * build the PDU for a PDU length negotiation + * + * @return word + */ + public int negPDUlengthRequest() { + int res; + final PDU p = new PDU(this.msgOut, this.PDUstartOut); + final byte pa[] = {(byte) 0xF0, 0, 0x00, 0x01, 0x00, 0x01, 0x03, (byte) 0xC0,}; + p.initHeader(1); + p.addParam(pa); + res = this.exchange(p); + if (res != 0) { + return res; + } + final PDU p2 = new PDU(this.msgIn, this.PDUstartIn); + res = p2.setupReceivedPDU(); + if (res != 0) { + return res; + } + this.maxPDUlength = Nodave.USBEWord(this.msgIn, p2.param + 6); + return res; + } + + public int readBytes(final DaveArea area, final int DBnum, final int start, final int len, final byte[] buffer) { + int res = 0; + try { + this.semaphore.acquire(); + } catch (final InterruptedException e) { + e.printStackTrace(); + } + final PDU p1 = new PDU(this.msgOut, this.PDUstartOut); + p1.initReadRequest(); + p1.addVarToReadRequest(area, DBnum, start, len); + + res = this.exchange(p1); + if (res != Nodave.RESULT_OK) { + this.semaphore.release(); + return res; + } + final PDU p2 = new PDU(this.msgIn, this.PDUstartIn); + res = p2.setupReceivedPDU(); + if (res != Nodave.RESULT_OK) { + this.semaphore.release(); + return res; + } + + res = p2.testReadResult(); + if (res != Nodave.RESULT_OK) { + this.semaphore.release(); + return res; + } + if (p2.udlen == 0) { + this.semaphore.release(); + return Nodave.RESULT_CPU_RETURNED_NO_DATA; + } + /* + * copy to user buffer and setup internal buffer pointers: + */ + if (buffer != null) { + System.arraycopy(p2.mem, p2.udata, buffer, 0, p2.udlen); + } + + this.dataPointer = p2.udata; + this.udata = p2.udata; + this.answLen = p2.udlen; + this.semaphore.release(); + return res; + } + + public int sendMsg(final PDU p) { + return 0; + } + + public void sendRequestData(final int alt) { + } + + public int useResult(final ResultSet rs, final int number) { + System.out.println("rs.getNumResults: " + rs.getNumResults() + " number: " + number); + if (rs.getNumResults() > number) { + this.dataPointer = rs.results[number].bufferStart; + return 0; + // udata=rs.results[number].bufferStart; + } + return -33; + } + + ; + + /* + * Write len bytes to PLC memory area "area", data block DBnum. + */ + public int writeBytes(final DaveArea area, final int DBnum, final int start, final int len, final byte[] buffer) { + int errorState = 0; + this.semaphore.release(); + final PDU p1 = new PDU(this.msgOut, this.PDUstartOut); + + // p1.constructWriteRequest(area, DBnum, start, len, buffer); + p1.prepareWriteRequest(); + p1.addVarToWriteRequest(area, DBnum, start, len, buffer); + + errorState = this.exchange(p1); + + if (errorState == 0) { + final PDU p2 = new PDU(this.msgIn, this.PDUstartIn); + p2.setupReceivedPDU(); + + if (p2.mem[p2.param + 0] == PDU.FUNC_WRITE) { + if (p2.mem[p2.data + 0] == (byte) 0xFF) { + this.semaphore.release(); + return 0; + } + } else { + errorState |= 4096; + } + } + this.semaphore.release(); + return errorState; + } + +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/nodave/TCPConnection.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/nodave/TCPConnection.java new file mode 100644 index 0000000..ac294b8 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/nodave/TCPConnection.java @@ -0,0 +1,155 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/* + Part of Libnodave, a free communication libray for Siemens S7 + + (C) Thomas Hergenhahn (thomas.hergenhahn@web.de) 2005. + + Libnodave is free software; you can redistribute it and/or modify + it under the terms of the GNU Library General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + Libnodave is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this; see the file COPYING. If not, write to + the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +package org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.impl.nodave; + +/** + * The Class TCPConnection. + * + * @author Thomas Rudin + */ +public final class TCPConnection extends S7Connection { + + /** + * The rack. + */ + int rack; + + /** + * The slot. + */ + int slot; + + /** + * Instantiates a new TCP connection. + * + * @param ifa the plc interface + * @param rack the rack + * @param slot the slot + */ + public TCPConnection(final PLCinterface ifa, final int rack, final int slot) { + super(ifa); + this.rack = rack; + this.slot = slot; + this.PDUstartIn = 7; + this.PDUstartOut = 7; + } + + /** + * We have our own connectPLC(), but no disconnect() Open connection to a + * PLC. This assumes that dc is initialized by daveNewConnection and is not + * yet used. (or reused for the same PLC ?) + * + * @return the int + */ + public int connectPLC() { + int packetLength; + if (iface.protocol == Nodave.PROTOCOL_ISOTCP243) { + final byte[] b243 = { + (byte) 0x11, (byte) 0xE0, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x00, + (byte) 0xC1, (byte) 0x02, (byte) 0x4D, (byte) 0x57, (byte) 0xC2, (byte) 0x02, (byte) 0x4D, (byte) 0x57, + (byte) 0xC0, (byte) 0x01, (byte) 0x09 + }; + System.arraycopy(b243, 0, this.msgOut, 4, b243.length); + packetLength = b243.length; + } else { + final byte[] b4 = { + (byte) 0x11, (byte) 0xE0, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x00, + (byte) 0xC1, (byte) 0x02, (byte) 0x01, (byte) 0x00, (byte) 0xC2, (byte) 0x02, (byte) 0x01, (byte) 0x02, + (byte) 0xC0, (byte) 0x01, (byte) 0x09 + }; + System.arraycopy(b4, 0, this.msgOut, 4, b4.length); + this.msgOut[17] = (byte) (this.rack + 1); + this.msgOut[18] = (byte) this.slot; + packetLength = b4.length; + } + this.sendISOPacket(packetLength); + this.readISOPacket(); + /* + * PDU p = new PDU(msgOut, 7); p.initHeader(1); p.addParam(b61); + * exchange(p); return (0); + */ + return this.negPDUlengthRequest(); + } + + @Override + public int exchange(final PDU p1) { + this.msgOut[4] = (byte) 0x02; + this.msgOut[5] = (byte) 0xf0; + this.msgOut[6] = (byte) 0x80; + this.sendISOPacket(3 + p1.hlen + p1.plen + p1.dlen); + this.readISOPacket(); + return 0; + } + + /** + * Read iso packet. + * + * @return the int + */ + protected int readISOPacket() { + int res = this.iface.read(this.msgIn, 0, 4); + if (res == 4) { + final int len = (0x100 * this.msgIn[2]) + this.msgIn[3]; + res += this.iface.read(this.msgIn, 4, len); + } else { + return 0; + } + return res; + } + + /** + * Send iso packet. + * + * @param size the size + * @return the int + */ + protected int sendISOPacket(int size) { + size += 4; + this.msgOut[0] = (byte) 0x03; + this.msgOut[1] = (byte) 0x0; + this.msgOut[2] = (byte) (size / 0x100); + this.msgOut[3] = (byte) (size % 0x100); + /* + * if (messageNumber == 0) { messageNumber = 1; msgOut[11] = (byte) + * ((messageNumber + 1) & 0xff); messageNumber++; messageNumber &= 0xff; + * //!! } + */ + + this.iface.write(this.msgOut, 0, size); + return 0; + } +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/serializer/S7SerializerImpl.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/serializer/S7SerializerImpl.java new file mode 100644 index 0000000..489a8ea --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/serializer/S7SerializerImpl.java @@ -0,0 +1,197 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.impl.serializer; + +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.PlcS7PointVariable; +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.S7Exception; +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.DaveArea; +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.S7Connector; +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.S7Serializer; +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.impl.serializer.parser.BeanEntry; +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.impl.serializer.parser.BeanParseResult; +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.impl.serializer.parser.BeanParser; +import lombok.extern.slf4j.Slf4j; + +import java.lang.reflect.Array; + +/** + * The Class S7Serializer is responsible for serializing S7 TCP Connection + */ +@Slf4j +public final class S7SerializerImpl implements S7Serializer { + + /** + * The Connector. + */ + private final S7Connector connector; + + /** + * Instantiates a new s7 serializer. + * + * @param connector the connector + */ + public S7SerializerImpl(final S7Connector connector) { + this.connector = connector; + } + + public static Object extractBytes(PlcS7PointVariable plcs7PointVariable, final byte[] buffer, final int byteOffset) { + try { + final BeanEntry entry = BeanParser.parse(plcs7PointVariable); + return entry.serializer.extract(entry.type, buffer, entry.byteOffset + byteOffset, entry.bitOffset); + } catch (Exception e) { + throw new S7Exception("extractBytes", e); + } + } + + /** + * Extracts bytes from a buffer. + * + * @param the generic type + * @param beanClass the bean class + * @param buffer the buffer + * @param byteOffset the byte offset + * @return the t + */ + public static T extractBytes(final Class beanClass, final byte[] buffer, final int byteOffset) { + log.trace("Extracting type {} from buffer with size: {} at offset {}", beanClass.getName(), buffer.length, byteOffset); + + try { + final T obj = beanClass.newInstance(); + final BeanParseResult result = BeanParser.parse(beanClass); + for (final BeanEntry entry : result.entries) { + Object value = null; + if (entry.isArray) { + value = Array.newInstance(entry.type, entry.arraySize); + for (int i = 0; i < entry.arraySize; i++) { + final Object component = entry.serializer.extract(entry.type, buffer, + entry.byteOffset + byteOffset + (i * entry.s7type.getByteSize()), + entry.bitOffset + (i * entry.s7type.getBitSize())); + Array.set(value, i, component); + } + } else { + value = entry.serializer.extract(entry.type, buffer, entry.byteOffset + byteOffset, entry.bitOffset); + } + + if (entry.field.getType() == byte[].class) { + //Special case issue #45 + Byte[] oldValue = (Byte[]) value; + + value = new byte[oldValue.length]; + + for (int i = 0; i < oldValue.length; i++) { + ((byte[]) value)[i] = oldValue[i]; + } + } + + entry.field.set(obj, value); + } + + return obj; + } catch (final Exception e) { + throw new S7Exception("extractBytes", e); + } + } + + /** + * Inserts the bytes to the buffer. + * + * @param bean the bean + * @param buffer the buffer + * @param byteOffset the byte offset + */ + public static void insertBytes(final Object bean, final byte[] buffer, final int byteOffset) { + log.trace("Inerting buffer with size: {} at offset {} into bean: {}", buffer.length, byteOffset, bean); + + try { + final BeanParseResult result = BeanParser.parse(bean); + + for (final BeanEntry entry : result.entries) { + final Object fieldValue = entry.field.get(bean); + + if (fieldValue != null) { + if (entry.isArray) { + for (int i = 0; i < entry.arraySize; i++) { + final Object arrayItem = Array.get(fieldValue, i); + + if (arrayItem != null) { + entry.serializer.insert(arrayItem, buffer, + entry.byteOffset + byteOffset + (i * entry.s7type.getByteSize()), + entry.bitOffset + (i * entry.s7type.getBitSize()), entry.size); + } + } + } else { + entry.serializer.insert(fieldValue, buffer, entry.byteOffset + byteOffset, entry.bitOffset, + entry.size); + } + } + } + } catch (final Exception e) { + throw new S7Exception("insertBytes", e); + } + } + + @Override + public synchronized T dispense(final Class beanClass, final int dbNum, final int byteOffset) throws S7Exception { + try { + final BeanParseResult result = BeanParser.parse(beanClass); + final byte[] buffer = this.connector.read(DaveArea.DB, dbNum, result.blockSize, byteOffset); + return extractBytes(beanClass, buffer, 0); + } catch (final Exception e) { + throw new S7Exception("dispense", e); + } + } + + @Override + public synchronized T dispense(final Class beanClass, final int dbNum, final int byteOffset, final int blockSize) throws S7Exception { + try { + final byte[] buffer = this.connector.read(DaveArea.DB, dbNum, blockSize, byteOffset); + return extractBytes(beanClass, buffer, 0); + } catch (final Exception e) { + throw new S7Exception("dispense dbnum(" + dbNum + ") byteoffset(" + byteOffset + ") blocksize(" + blockSize + ")", e); + } + } + + /** + * add by pnoker + */ + @Override + public Object dispense(PlcS7PointVariable plcs7PointVariable) throws S7Exception { + try { + final byte[] buffer = this.connector.read(DaveArea.DB, plcs7PointVariable.getDbNum(), plcs7PointVariable.getSize(), plcs7PointVariable.getByteOffset()); + return extractBytes(plcs7PointVariable, buffer, 0); + } catch (final Exception e) { + throw new S7Exception("dispense dbnum(" + plcs7PointVariable.getDbNum() + ") byteoffset(" + plcs7PointVariable.getByteOffset() + ") blocksize(" + plcs7PointVariable.getSize() + ")", e); + } + } + + @Override + public synchronized void store(final Object bean, final int dbNum, final int byteOffset) { + try { + final BeanParseResult result = BeanParser.parse(bean); + + final byte[] buffer = new byte[result.blockSize]; + log.trace("store-buffer-size: " + buffer.length); + + insertBytes(bean, buffer, 0); + + this.connector.write(DaveArea.DB, dbNum, byteOffset, buffer); + } catch (final Exception e) { + throw new S7Exception("store", e); + } + } + +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/serializer/converter/BitConverter.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/serializer/converter/BitConverter.java new file mode 100644 index 0000000..0aa7a93 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/serializer/converter/BitConverter.java @@ -0,0 +1,66 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.impl.serializer.converter; + +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.S7Serializable; +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.S7Type; + +/** + * The Class BitConverter is responsible for converting bit values + */ +public final class BitConverter implements S7Serializable { + + + @Override + public T extract(final Class targetClass, final byte[] buffer, final int byteOffset, final int bitOffset) { + final byte bufValue = buffer[byteOffset]; + return targetClass.cast(bufValue == (bufValue | (0x01 << bitOffset))); + } + + + @Override + public S7Type getS7Type() { + return S7Type.BOOL; + } + + + @Override + public int getSizeInBits() { + return 1; + } + + + @Override + public int getSizeInBytes() { + return 0; + } + + + @Override + public void insert(final Object javaType, final byte[] buffer, final int byteOffset, final int bitOffset, + final int size) { + final Boolean value = (Boolean) javaType; + + //thx to @mfriedemann (https://github.com/mfriedemann) + if (value) { + buffer[byteOffset] |= (0x01 << bitOffset); + } else { + buffer[byteOffset] &= ~(0x01 << bitOffset); + } + } + +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/serializer/converter/ByteConverter.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/serializer/converter/ByteConverter.java new file mode 100644 index 0000000..c420e91 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/serializer/converter/ByteConverter.java @@ -0,0 +1,51 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.impl.serializer.converter; + +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.S7Serializable; +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.S7Type; + +public class ByteConverter implements S7Serializable { + + @Override + public T extract(final Class targetClass, final byte[] buffer, final int byteOffset, final int bitOffset) { + return targetClass.cast(buffer[byteOffset]); + } + + @Override + public S7Type getS7Type() { + return S7Type.BYTE; + } + + @Override + public int getSizeInBits() { + return 0; + } + + @Override + public int getSizeInBytes() { + return 1; + } + + @Override + public void insert(final Object javaType, final byte[] buffer, final int byteOffset, final int bitOffset, + final int size) { + final Byte value = (Byte) javaType; + buffer[byteOffset] = value; + } + +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/serializer/converter/DateAndTimeConverter.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/serializer/converter/DateAndTimeConverter.java new file mode 100644 index 0000000..6ae68ff --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/serializer/converter/DateAndTimeConverter.java @@ -0,0 +1,182 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.impl.serializer.converter; + +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.S7Type; + +import java.util.Calendar; +import java.util.Date; + +public final class DateAndTimeConverter extends ByteConverter { + + public static final int OFFSET_DAY = 2; + public static final int OFFSET_HOUR = 3; + public static final int OFFSET_MILLIS_1_AND_DOW = 7; + public static final int OFFSET_MILLIS_100_10 = 6; + public static final int OFFSET_MINUTE = 4; + public static final int OFFSET_MONTH = 1; + public static final int OFFSET_SECOND = 5; + public static final int OFFSET_YEAR = 0; + + // 18, 1,16,16, 5,80,0,3, (dec) + // 12, 1,10,10, 5,50,0,3, (hex) + // 12-01-10 10:05:50.000 + + @Override + public T extract(final Class targetClass, final byte[] buffer, final int byteOffset, final int bitOffset) { + final Calendar c = Calendar.getInstance(); + c.clear(); + + int year = this.getFromPLC(buffer, OFFSET_YEAR + byteOffset); + + if (year < 90) { + // 1900 - 1989 + year += 2000; + } else { + // 2000 - 2090 + year += 1900; + } + + int month = this.getFromPLC(buffer, OFFSET_MONTH + byteOffset); + + if (month > 0) { + month--; + } + + c.set(Calendar.YEAR, year); + c.set(Calendar.MONTH, month); + c.set(Calendar.DAY_OF_MONTH, this.getFromPLC(buffer, OFFSET_DAY + byteOffset)); + c.set(Calendar.HOUR_OF_DAY, this.getFromPLC(buffer, OFFSET_HOUR + byteOffset)); + c.set(Calendar.MINUTE, this.getFromPLC(buffer, OFFSET_MINUTE + byteOffset)); + c.set(Calendar.SECOND, this.getFromPLC(buffer, OFFSET_SECOND + byteOffset)); + + /* + * TODO byte upperMillis = super.extract(Byte.class, buffer, + * OFFSET_MILLIS_100_10+byteOffset, bitOffset); byte lowerMillis = + * super.extract(Byte.class, buffer, OFFSET_MILLIS_1_AND_DOW+byteOffset, + * bitOffset); + * + * int ms100 = ( upperMillis >> 4 ); int ms10 = ( upperMillis & 0x0F ); + * int ms1 = ( lowerMillis >> 4 ); + * + * int millis = ms1 + ( 10*ms10 ) + ( 100*ms100 ); + * c.set(Calendar.MILLISECOND, millis); + * + * int dow = ( lowerMillis & 0x0F ); c.set(Calendar.DAY_OF_WEEK, dow); + */ + + return targetClass.cast(c.getTime()); + } + + /** + * Dec to Hex 10 = 0a 16 = 0f 17 = 10 + * + * @param buffer Byte Array + * @param offset Offset + * @return Byte + */ + public byte getFromPLC(final byte[] buffer, final int offset) { + try { + final byte ret = super.extract(Byte.class, buffer, offset, 0); + return (byte) Integer.parseInt(Integer.toHexString(ret & 0xFF)); + } catch (final NumberFormatException e) { + return 0; + } + } + + @Override + public S7Type getS7Type() { + return S7Type.DATE_AND_TIME; + } + + @Override + public int getSizeInBits() { + return 0; + } + + @Override + public int getSizeInBytes() { + return 8; + } + + @Override + public void insert(final Object javaType, final byte[] buffer, final int byteOffset, final int bitOffset, + final int size) { + final Date date = (Date) javaType; + final Calendar c = Calendar.getInstance(); + c.setTime(date); + + int year = c.get(Calendar.YEAR); + + /* + * if (year < 1990 || year > 2090) throw new + * S7Exception("Invalid year: " + year + " @ offset: " + byteOffset); + */ + + if (year < 2000) { + // 1990 -1999 + year -= 1900; + } else { + // 2000 - 2089 + year -= 2000; + } + + this.putToPLC(buffer, byteOffset + OFFSET_YEAR, year); + this.putToPLC(buffer, byteOffset + OFFSET_MONTH, c.get(Calendar.MONTH) + 1); + this.putToPLC(buffer, byteOffset + OFFSET_DAY, c.get(Calendar.DAY_OF_MONTH)); + this.putToPLC(buffer, byteOffset + OFFSET_HOUR, c.get(Calendar.HOUR_OF_DAY)); + this.putToPLC(buffer, byteOffset + OFFSET_MINUTE, c.get(Calendar.MINUTE)); + this.putToPLC(buffer, byteOffset + OFFSET_SECOND, c.get(Calendar.SECOND)); + + /* + * TODO int msec1 = 0, msec10 = 0, msec100 = 0; Integer millis = + * c.get(Calendar.MILLISECOND); String mStr = millis.toString(); + * + * if (mStr.length() > 2) { msec100 = Integer.parseInt( + * mStr.substring(0, 1) ); msec10 = Integer.parseInt( mStr.substring(1, + * 2) ); msec1 = Integer.parseInt( mStr.substring(2, 3) ); } else if + * (mStr.length() > 1) { msec10 = Integer.parseInt( mStr.substring(0, 1) + * ); msec1 = Integer.parseInt( mStr.substring(1, 2) ); } else { msec1 = + * Integer.parseInt( mStr.substring(0, 1) ); } + * + * super.insert( (byte)( (byte)msec10 | (byte)(msec100 << 4) ), buffer, + * OFFSET_MILLIS_100_10+byteOffset, 0, 1); + * + * int dow = c.get(Calendar.DAY_OF_WEEK); + * + * super.insert( (byte)( (byte)dow | (byte)(msec1 << 4) ), buffer, + * OFFSET_MILLIS_1_AND_DOW+byteOffset, 0, 1); + */ + } + + /** + * Hex to dec 0a = 10 0f = 16 10 = 17 + * + * @param buffer Byte Array + * @param offset Offset + * @param i int + */ + public void putToPLC(final byte[] buffer, final int offset, final int i) { + try { + final int ret = Integer.parseInt("" + i, 16); + buffer[offset] = (byte) ret; + } catch (final NumberFormatException e) { + return; + } + } + +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/serializer/converter/DateConverter.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/serializer/converter/DateConverter.java new file mode 100644 index 0000000..26e1a73 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/serializer/converter/DateConverter.java @@ -0,0 +1,95 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.impl.serializer.converter; + +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.S7Type; + +import java.util.Calendar; +import java.util.Date; + +public final class DateConverter extends IntegerConverter { + + private static final long MILLI_TO_DAY_FACTOR = 24 * 60 * 60 * 1000; + + /** + * 1.1.1990 + */ + private static final long OFFSET_1990; + + static { + final Calendar c = Calendar.getInstance(); + c.clear(); + c.set(Calendar.HOUR_OF_DAY, 0); + c.set(Calendar.YEAR, 1990); + + OFFSET_1990 = c.getTime().getTime(); + } + + + @Override + public T extract(final Class targetClass, final byte[] buffer, final int byteOffset, final int bitOffset) { + final long days = super.extract(Integer.class, buffer, byteOffset, bitOffset); + + long millis = days * MILLI_TO_DAY_FACTOR; + + millis += OFFSET_1990; + + final Calendar c = Calendar.getInstance(); + c.setTimeInMillis(millis); + c.set(Calendar.MILLISECOND, 0); + c.set(Calendar.SECOND, 0); + c.set(Calendar.MINUTE, 0); + c.set(Calendar.HOUR_OF_DAY, 0); + + return targetClass.cast(c.getTime()); + } + + + @Override + public S7Type getS7Type() { + return S7Type.DATE; + } + + + @Override + public void insert(final Object javaType, final byte[] buffer, final int byteOffset, final int bitOffset, + final int size) { + final Date d = (Date) javaType; + + long millis = d.getTime(); + + millis -= OFFSET_1990; + + final double days = (double) millis / (double) MILLI_TO_DAY_FACTOR; + + final long ROUND = 1000; + + final long expected = (long) ((days * MILLI_TO_DAY_FACTOR) / ROUND); + final long actual = millis / ROUND; + + if (expected != actual) { + throw new IllegalArgumentException("Expected: " + expected + " got: " + actual); + } + + if (millis < 0) { + super.insert(0, buffer, byteOffset, bitOffset, 2); + } else { + super.insert((int) Math.round(days), buffer, byteOffset, bitOffset, 2); + } + } + +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/serializer/converter/IntegerConverter.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/serializer/converter/IntegerConverter.java new file mode 100644 index 0000000..aab9db3 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/serializer/converter/IntegerConverter.java @@ -0,0 +1,62 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.impl.serializer.converter; + +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.S7Serializable; +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.S7Type; + +public class IntegerConverter implements S7Serializable { + + private static final int OFFSET_HIGH_BYTE = 0; + private static final int OFFSET_LOW_BYTE = 1; + + @Override + public T extract(final Class targetClass, final byte[] buffer, final int byteOffset, final int bitOffset) { + final byte lower = buffer[byteOffset + OFFSET_LOW_BYTE]; + final byte higher = buffer[byteOffset + OFFSET_HIGH_BYTE]; + + final Integer i = (lower & 0xFF) | ((higher << 8) & 0xFF00); + + return targetClass.cast(i); + } + + @Override + public S7Type getS7Type() { + return S7Type.WORD; + } + + @Override + public int getSizeInBits() { + return 0; + } + + @Override + public int getSizeInBytes() { + return 2; + } + + @Override + public void insert(final Object javaType, final byte[] buffer, final int byteOffset, final int bitOffset, + final int size) { + final Integer value = (Integer) javaType; + final byte lower = (byte) ((value) & 0xFF); + final byte higher = (byte) ((value >> 8) & 0xFF); + buffer[byteOffset + OFFSET_LOW_BYTE] = lower; + buffer[byteOffset + OFFSET_HIGH_BYTE] = higher; + } + +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/serializer/converter/LongConverter.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/serializer/converter/LongConverter.java new file mode 100644 index 0000000..9b2bcc4 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/serializer/converter/LongConverter.java @@ -0,0 +1,68 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.impl.serializer.converter; + +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.S7Serializable; +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.S7Type; + +public final class LongConverter implements S7Serializable { + + @Override + public T extract(final Class targetClass, final byte[] buffer, final int byteOffset, final int bitOffset) { + final byte b1 = buffer[byteOffset]; + final byte b2 = buffer[byteOffset + 1]; + final byte b3 = buffer[byteOffset + 2]; + final byte b4 = buffer[byteOffset + 3]; + + final Integer i = ((b4) & 0x000000FF) | + ((b3 << 8) & 0x0000FF00) | + ((b2 << 16) & 0x00FF0000) | + ((b1 << 24) & 0xFF000000); + + return targetClass.cast(i.longValue()); + } + + @Override + public S7Type getS7Type() { + return S7Type.DWORD; + } + + @Override + public int getSizeInBits() { + return 0; + } + + @Override + public int getSizeInBytes() { + return 4; + } + + @Override + public void insert(final Object javaType, final byte[] buffer, final int byteOffset, final int bitOffset, + final int size) { + final Long value = (Long) javaType; + final byte b4 = (byte) ((value) & 0xFF); + final byte b3 = (byte) ((value >> 8) & 0xFF); + final byte b2 = (byte) ((value >> 16) & 0xFF); + final byte b1 = (byte) ((value >> 24) & 0xFF); + buffer[byteOffset] = b1; + buffer[byteOffset + 1] = b2; + buffer[byteOffset + 2] = b3; + buffer[byteOffset + 3] = b4; + } + +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/serializer/converter/RealConverter.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/serializer/converter/RealConverter.java new file mode 100644 index 0000000..34d50ca --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/serializer/converter/RealConverter.java @@ -0,0 +1,74 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.impl.serializer.converter; + +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.S7Serializable; +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.S7Type; + +public final class RealConverter implements S7Serializable { + + private static final int OFFSET_POS1 = 0; + private static final int OFFSET_POS2 = 1; + private static final int OFFSET_POS3 = 2; + private static final int OFFSET_POS4 = 3; + + @Override + public T extract(final Class targetClass, final byte[] buffer, final int byteOffset, final int bitOffset) { + final int iValue = ((buffer[byteOffset + OFFSET_POS4] & 0xFF)) + | ((buffer[byteOffset + OFFSET_POS3] & 0xFF) << 8) | ((buffer[byteOffset + OFFSET_POS2] & 0xFF) << 16) + | ((buffer[byteOffset + OFFSET_POS1] & 0xFF) << 24); + + final Float fValue = Float.intBitsToFloat(iValue); + + Object ret = fValue; + + if (targetClass == Double.class) { + ret = Double.parseDouble(fValue.toString()); + } + + return targetClass.cast(ret); + } + + @Override + public S7Type getS7Type() { + return S7Type.REAL; + } + + @Override + public int getSizeInBits() { + return 0; + } + + @Override + public int getSizeInBytes() { + return 4; + } + + @Override + public void insert(final Object javaType, final byte[] buffer, final int byteOffset, final int bitOffset, + final int size) { + final float fValue = Float.parseFloat(javaType.toString()); + + final int iValue = Float.floatToIntBits(fValue); + + buffer[byteOffset + OFFSET_POS4] = (byte) ((iValue) & 0xFF); + buffer[byteOffset + OFFSET_POS3] = (byte) ((iValue >> 8) & 0xFF); + buffer[byteOffset + OFFSET_POS2] = (byte) ((iValue >> 16) & 0xFF); + buffer[byteOffset + OFFSET_POS1] = (byte) ((iValue >> 24) & 0xFF); + } + +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/serializer/converter/ShortConverter.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/serializer/converter/ShortConverter.java new file mode 100644 index 0000000..8977cde --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/serializer/converter/ShortConverter.java @@ -0,0 +1,63 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.impl.serializer.converter; + +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.S7Serializable; +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.S7Type; + +public class ShortConverter implements S7Serializable { + + private static final short OFFSET_HIGH_BYTE = 0; + private static final short OFFSET_LOW_BYTE = 1; + + @Override + public T extract(final Class targetClass, final byte[] buffer, final int byteOffset, final int bitOffset) { + final byte lower = buffer[byteOffset + OFFSET_LOW_BYTE]; + final byte higher = buffer[byteOffset + OFFSET_HIGH_BYTE]; + + final Integer i = (lower & 0xFF) | ((higher << 8) & 0xFF00); + + return targetClass.cast(i.shortValue()); + + } + + @Override + public S7Type getS7Type() { + return S7Type.INT; + } + + @Override + public int getSizeInBits() { + return 0; + } + + @Override + public int getSizeInBytes() { + return 2; + } + + @Override + public void insert(final Object javaType, final byte[] buffer, final int byteOffset, final int bitOffset, + final int size) { + final Short value = (Short) javaType; + final byte lower = (byte) ((value) & 0xFF); + final byte higher = (byte) ((value >> 8) & 0xFF); + buffer[byteOffset + OFFSET_LOW_BYTE] = lower; + buffer[byteOffset + OFFSET_HIGH_BYTE] = higher; + } + +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/serializer/converter/StringConverter.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/serializer/converter/StringConverter.java new file mode 100644 index 0000000..0ca5b14 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/serializer/converter/StringConverter.java @@ -0,0 +1,71 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.impl.serializer.converter; + +import org.nl.common.util.DecodeUtil; +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.S7Serializable; +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.S7Type; + +public final class StringConverter implements S7Serializable { + + private static final int OFFSET_CURRENT_LENGTH = 1; + private static final int OFFSET_OVERALL_LENGTH = 0; + private static final int OFFSET_START = 2; + + @Override + public T extract(final Class targetClass, final byte[] buffer, final int byteOffset, final int bitOffset) { + final int len = buffer[byteOffset + OFFSET_CURRENT_LENGTH] & 0xFF; + + return targetClass.cast(new String(buffer, byteOffset + OFFSET_START, len)); + } + + @Override + public S7Type getS7Type() { + return S7Type.STRING; + } + + @Override + public int getSizeInBits() { + // Not static + return 0; + } + + @Override + public int getSizeInBytes() { + // Not static + return 2; // 2 bytes overhead + } + + @Override + public void insert(final Object javaType, final byte[] buffer, final int byteOffset, final int bitOffset, + final int size) { + final String value = (String) javaType; + + final int len = value.length(); + + if (len > size) { + throw new IllegalArgumentException("String to big: " + len); + } + + buffer[byteOffset + OFFSET_OVERALL_LENGTH] = (byte) size; + buffer[byteOffset + OFFSET_CURRENT_LENGTH] = (byte) len; + + final byte[] strBytes = DecodeUtil.stringToByte(value); + System.arraycopy(strBytes, 0, buffer, byteOffset + OFFSET_START, len); + } + +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/serializer/converter/StructConverter.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/serializer/converter/StructConverter.java new file mode 100644 index 0000000..0dc68a7 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/serializer/converter/StructConverter.java @@ -0,0 +1,51 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.impl.serializer.converter; + +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.S7Serializable; +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.S7Type; +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.impl.serializer.S7SerializerImpl; + +public final class StructConverter implements S7Serializable { + + @Override + public T extract(final Class targetClass, final byte[] buffer, final int byteOffset, final int bitOffset) { + return S7SerializerImpl.extractBytes(targetClass, buffer, byteOffset); + } + + @Override + public S7Type getS7Type() { + return null; + } + + @Override + public int getSizeInBits() { + return 0; + } + + @Override + public int getSizeInBytes() { + return 0; + } + + @Override + public void insert(final Object javaType, final byte[] buffer, final int byteOffset, final int bitOffset, + final int size) { + S7SerializerImpl.insertBytes(javaType, buffer, byteOffset); + } + +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/serializer/converter/TimeConverter.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/serializer/converter/TimeConverter.java new file mode 100644 index 0000000..e230c1b --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/serializer/converter/TimeConverter.java @@ -0,0 +1,62 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.impl.serializer.converter; + +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.S7Type; + +public final class TimeConverter extends ByteConverter { + + @Override + public T extract(final Class targetClass, final byte[] buffer, final int byteOffset, final int bitOffset) { + final byte b1 = super.extract(Byte.class, buffer, byteOffset + 3, bitOffset); + final byte b2 = super.extract(Byte.class, buffer, byteOffset + 2, bitOffset); + final byte b3 = super.extract(Byte.class, buffer, byteOffset + 1, bitOffset); + final byte b4 = super.extract(Byte.class, buffer, byteOffset, bitOffset); + + final long l = ((long) b1 & 0xFF) | ((long) b2 & 0xFF) << 8 | ((long) b3 & 0xFF) << 16 + | ((long) b4 & 0xFF) << 24; + + return targetClass.cast(l); + } + + @Override + public S7Type getS7Type() { + return S7Type.TIME; + } + + @Override + public int getSizeInBytes() { + return 4; + } + + @Override + public void insert(final Object javaType, final byte[] buffer, final int byteOffset, final int bitOffset, + final int size) { + final long l = (Long) javaType; + + final byte b1 = (byte) ((byte) (l) & 0xFF); + final byte b2 = (byte) ((byte) (l >> 8) & 0xFF); + final byte b3 = (byte) ((byte) (l >> 16) & 0xFF); + final byte b4 = (byte) ((byte) (l >> 24) & 0xFF); + + super.insert(b1, buffer, byteOffset + 3, bitOffset, 1); + super.insert(b2, buffer, byteOffset + 2, bitOffset, 1); + super.insert(b3, buffer, byteOffset + 1, bitOffset, 1); + super.insert(b4, buffer, byteOffset, bitOffset, 1); + } + +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/serializer/parser/BeanEntry.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/serializer/parser/BeanEntry.java new file mode 100644 index 0000000..afef7a1 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/serializer/parser/BeanEntry.java @@ -0,0 +1,64 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.impl.serializer.parser; + +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.S7Serializable; +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.S7Type; + +import java.lang.reflect.Field; + +/** + * A Bean-Entry + * + * @author Thomas Rudin + */ +public final class BeanEntry { + /** + * The Array size + */ + public int arraySize; + + /** + * Offsets and size + */ + public int byteOffset, bitOffset, size; + + /** + * The corresponding field + */ + public Field field; + + /** + * Array type + */ + public boolean isArray; + + /** + * The S7 Type + */ + public S7Type s7type; + + /** + * The corresponding serializer + */ + public S7Serializable serializer; + + /** + * The Java type + */ + public Class type; +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/serializer/parser/BeanParseResult.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/serializer/parser/BeanParseResult.java new file mode 100644 index 0000000..79b8874 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/serializer/parser/BeanParseResult.java @@ -0,0 +1,33 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.impl.serializer.parser; + +import java.util.Vector; + +public final class BeanParseResult { + + /** + * The needed blocksize + */ + public int blockSize; + + /** + * The Bean entries + */ + public Vector entries = new Vector(); + +} diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/serializer/parser/BeanParser.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/serializer/parser/BeanParser.java new file mode 100644 index 0000000..2025c2a --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/com/github/s7/api/impl/serializer/parser/BeanParser.java @@ -0,0 +1,169 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.impl.serializer.parser; + +import org.nl.common.exception.ExceptionConstant; +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.PlcS7PointVariable; +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.S7Serializable; +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.S7Type; +import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.annotation.S7Variable; +import lombok.extern.slf4j.Slf4j; + +import java.lang.reflect.Field; + +@Slf4j +public final class BeanParser { + + private BeanParser() { + throw new IllegalStateException(ExceptionConstant.UTILITY_CLASS); + } + + /** + * Returns the wrapper for the primitive type + * + * @param primitiveType Class + * @return Class + */ + private static Class getWrapperForPrimitiveType(final Class primitiveType) { + if (primitiveType == boolean.class) { + return Boolean.class; + } else if (primitiveType == byte.class) { + return Byte.class; + } else if (primitiveType == int.class) { + return Integer.class; + } else if (primitiveType == float.class) { + return Float.class; + } else if (primitiveType == double.class) { + return Double.class; + } else if (primitiveType == long.class) { + return Long.class; + } else { + // Fallback + return primitiveType; + } + } + + + public static BeanEntry parse(PlcS7PointVariable plcs7PointVariable) throws Exception { + final BeanEntry entry = new BeanEntry(); + entry.byteOffset = plcs7PointVariable.getByteOffset(); + entry.bitOffset = plcs7PointVariable.getBitOffset(); + entry.size = plcs7PointVariable.getSize(); + entry.s7type = plcs7PointVariable.getType(); + entry.type = getWrapperForPrimitiveType(plcs7PointVariable.getFieldType()); + entry.serializer = entry.s7type.getSerializer().getDeclaredConstructor().newInstance(); + + return entry; + } + + /** + * Parses a Class + * + * @param jclass Class + * @return BeanParseResult + * @throws Exception Exception + */ + public static BeanParseResult parse(final Class jclass) throws Exception { + final BeanParseResult res = new BeanParseResult(); + log.trace("Parsing: " + jclass.getName()); + + for (final Field field : jclass.getFields()) { + final S7Variable dataAnnotation = field.getAnnotation(S7Variable.class); + + if (dataAnnotation != null) { + log.trace("Parsing field: " + field.getName()); + log.trace(" type: " + dataAnnotation.type()); + log.trace(" byteOffset: " + dataAnnotation.byteOffset()); + log.trace(" bitOffset: " + dataAnnotation.bitOffset()); + log.trace(" size: " + dataAnnotation.size()); + log.trace(" arraySize: " + dataAnnotation.arraySize()); + + final int offset = dataAnnotation.byteOffset(); + + // update max offset + if (offset > res.blockSize) { + res.blockSize = offset; + } + + if (dataAnnotation.type() == S7Type.STRUCT) { + // recurse + log.trace("Recursing..."); + final BeanParseResult subResult = parse(field.getType()); + res.blockSize += subResult.blockSize; + log.trace(" New blocksize: " + res.blockSize); + } + + log.trace(" New blocksize (+offset): " + res.blockSize); + + // Add dynamic size + res.blockSize += dataAnnotation.size(); + + // Plain element + final BeanEntry entry = new BeanEntry(); + entry.byteOffset = dataAnnotation.byteOffset(); + entry.bitOffset = dataAnnotation.bitOffset(); + entry.field = field; + entry.type = getWrapperForPrimitiveType(field.getType()); + entry.size = dataAnnotation.size(); + entry.s7type = dataAnnotation.type(); + entry.isArray = field.getType().isArray(); + entry.arraySize = dataAnnotation.arraySize(); + + if (entry.isArray) { + entry.type = getWrapperForPrimitiveType(entry.type.getComponentType()); + } + + // Create new serializer + final S7Serializable s = entry.s7type.getSerializer().getDeclaredConstructor().newInstance(); + entry.serializer = s; + + res.blockSize += (s.getSizeInBytes() * dataAnnotation.arraySize()); + log.trace(" New blocksize (+array): " + res.blockSize); + + if (s.getSizeInBits() > 0) { + boolean offsetOfBitAlreadyKnown = false; + for (final BeanEntry parsedEntry : res.entries) { + if (parsedEntry.byteOffset == entry.byteOffset) { + offsetOfBitAlreadyKnown = true; + } + } + if (!offsetOfBitAlreadyKnown) { + res.blockSize++; + } + } + + res.entries.add(entry); + } + } + + log.trace("Parsing done, overall size: " + res.blockSize); + + return res; + } + + /** + * Parses an Object + * + * @param obj Object + * @return BeanParseResult + * @throws Exception Exception + */ + public static BeanParseResult parse(final Object obj) throws Exception { + return parse(obj.getClass()); + } + +} diff --git a/nl-vue/package.json b/nl-vue/package.json index 0d1de68..f2dc4b2 100644 --- a/nl-vue/package.json +++ b/nl-vue/package.json @@ -10,7 +10,6 @@ "license": "Apache-2.0", "author": "yubaoshan", "scripts": { - "install": "npm i", "serve": "vite --host 0.0.0.0", "dev": "vite --mode development --host 0.0.0.0", "preview": "vite preview", diff --git a/nl-web-app/src/test/java/org/nl/ApiTest.java b/nl-web-app/src/test/java/org/nl/ApiTest.java index 9064125..0a1f1df 100644 --- a/nl-web-app/src/test/java/org/nl/ApiTest.java +++ b/nl-web-app/src/test/java/org/nl/ApiTest.java @@ -151,4 +151,9 @@ public class ApiTest { e.printStackTrace(); } } + + @Test + public void plcS7TestRead() { + + } }