背景
在公司项目中,需要用到和PLC进行通讯,经过搜索后查询到使用JAVA与PLC通信两种方式,测试后达到正常读写的目的,于是记录下学习过程。
环境
Spring+SpringMVC+MybatisPlus / SpringBoot
PLC: 西门子 S7-1500/S7-1200(1214C)
PLC设置
第一步: 使用 TIA Portal 创建DB数据块,设置地址为:12 (地址可设置为1-59999 任意一个数字);
第二步:在DB块中插入新行,此处设置了8个Bool类型,1个Byte、3个String(参考)
首次添加时不会显示“偏移量”,需要右键数据块,选择属性,在‘常规-属性’中,取消‘优化的块访问’后将程序下载到设备后即可以看到偏移量。
正在准备设备模拟文章中...
JAVA使用S7Connector读写数据
读取/写入单个数据
第一步: 在pom中增加依赖:添加s7Connector依赖。
<!-- https://mvnrepository.com/artifact/com.github.s7connector/s7connector --><dependency><groupId>com.github.s7connector</groupId><artifactId>s7connector</artifactId><version>2.1</version></dependency>
当前项目中需要和PLC通信实现读取PLC地址中的数据、向PLC地址中写入数据,所以下面通过三个方法来实现 连接-读取-写入 这三个目标。
第二步: 初始化PLC链接
/*** 初始化PLC连接*/public S7Connector initConnect(){//PLC地址String ipAddress = "192.168.1.2";//默认端口String port = "102";S7Connector s7Connector = S7ConnectorFactory.buildTCPConnector().withHost(ipAddress).withPort(port).withTimeout(10000) //连接超时时间.withRack(0) .withSlot(1) .build();S7Serializer s7Serializer2L = S7SerializerFactory.buildSerializer(s7Connector);return s7Connector;}
第三步:读取PLC地址中的数据
PLC中待读取的数据地址为DB1000,偏移量为2,数据类型word (2位的16进制数据);
(稍后补充PLC截图)
读取PLC中数据很简单,调用s7connect.read 即可,但是需要根据PLC中存储的数据类型将二进制数组解析为Java数据类型;
/**
* 读取PLC中的数据,字符串类型
*
**/
public void readPlcData() {S7Connector s7Connector = initConnect(); //第一个参数:DaveArea.DB 表示读取PLC的地址区域为DB//第二个参数:DB地址,若plc中是DB1000,则填1000 //第三个参数:数据长度, <=plc中两个偏移量的间隔,当前偏移量为1000,下一个地址偏移量为1100,则长度可填 0-1000;//第三个参数:偏移量byte[] barcodeByte = s7Connector.read(DaveArea.DB, 1000, 2, 0);//由于当前PLC地址中保存的数据类型是字符串类型,所以直接将byte[] 转成string即可;String barcode = new String(barcodeByte);System.out.println(barcode);try {s7Connector.close();} catch (IOException e) {e.printStackTrace();}}
读取PLC 中word类型的数据如下:
/**
* 读取PLC中的数据
*
**/
public void readPlcData() {S7Connector s7Connector = initConnect(); //第一个参数:DaveArea.DB 表示读取PLC的地址区域为DB//第二个参数:DB地址,若plc中是DB1000,则填1000 //第三个参数:数据长度, <=plc中两个偏移量的间隔,当前偏移量为1000,下一个地址偏移量为1100,则长度可填 0-1000;//第四个参数:偏移量byte[] barcodeByte = s7Connector.read(DaveArea.DB, 1000, 2, 0);//由于当前PLC地址中保存的数据类型是字符串类型,所以直接将byte[] 转成string即可;String barcode =byteToHex(barcodeByte);System.out.println(barcode);try {s7Connector.close();} catch (IOException e) {e.printStackTrace();}}/*** byte数组转hex* @param bytes* @return*/public static String byteToHex(byte[] bytes){String strHex = "";StringBuilder sb = new StringBuilder("");for (int n = 0; n < bytes.length; n++) {strHex = Integer.toHexString(bytes[n] & 0xFF);sb.append((strHex.length() == 1) ? "0" + strHex : strHex); // 每个字节由两个字符表示,位数不够,高位补0}return sb.toString().trim();}
第四步:向PLC地址中写入数据
写入数据与读取数据类似,调用write方法即可;但是注意写数据时数据类型需要转成二进制数组;
/**
* 向PLC中写数据
*
**/
public void writePlcData() {S7Connector s7Connector = initConnect(); //第一个参数:DaveArea.DB 表示读取PLC的地址区域为DB//第二个参数:DB地址,若plc中是DB1000,则填1000 //第三个参数:偏移量//第四个参数:写入的数据 二进制数组byte[],由于plc中地址的数据类型是word,所以写入的数据必须是4位的16进制数据connector2L.write(DaveArea.DB,1000, 4,hexStringToBytes("0001"));try {s7Connector.close();} catch (IOException e) {e.printStackTrace();}}/*** 将16进制字符串转成二进制数组* @param hexString* @return*/public static byte[] hexStringToBytes(String hexString) {if (hexString == null || hexString.equals("")) {return null;}hexString = hexString.toUpperCase();int length = hexString.length() / 2;char[] hexChars = hexString.toCharArray();byte[] d = new byte[length];for (int i = 0; i < length; i++) {int pos = i * 2;d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1]));}return d;}
以上方式为读取/写入数据块单个偏移量的数据,下面的方式为批量读取/写入数据块数据。
批量读取/写入数据
与单个读取/写入数据相同,第一步也是需要创建连接,然后再进行读取/写入:
第一步:创建连接
/*** 初始化PLC连接*/public S7Connector initConnect(){//PLC地址String ipAddress = "192.168.1.2";//默认端口String port = "102";S7Connector s7Connector = S7ConnectorFactory.buildTCPConnector().withHost(ipAddress).withPort(port).withTimeout(10000) //连接超时时间.withRack(0) .withSlot(1) .build();S7Serializer s7Serializer2L = S7SerializerFactory.buildSerializer(s7Connector);return s7Connector;}
第二步:根据数据块以及偏移量创建对象
import com.github.s7connector.api.annotation.S7Variable;
import com.github.s7connector.impl.utils.S7Type;
import lombok.Data;@Data
public class PLCData {/*** type是这个点位在PLC中设置的类型,源码会解析其长度;* byteOffset对应PLC偏移量中的整数部分;* bitOffset指偏移量的小数部分,bitOffset指第几个bit.* byteOffset和bitOffset 也可理解为返回的byte[]中第byteOffset到bitOffset*/@S7Variable(type= S7Type.BOOL,byteOffset = 0,bitOffset = 0)public Boolean data10;//bool型的值不要用private@S7Variable(type=S7Type.BOOL,byteOffset = 0,bitOffset = 1)public Boolean data11;@S7Variable(type=S7Type.BOOL,byteOffset = 0,bitOffset = 2)public Boolean data12;@S7Variable(type=S7Type.BOOL,byteOffset = 0,bitOffset = 3)public Boolean data13;@S7Variable(type=S7Type.BOOL,byteOffset = 0,bitOffset = 4)public Boolean data14;@S7Variable(type=S7Type.BOOL,byteOffset = 0,bitOffset = 5)public Boolean data15;@S7Variable(type=S7Type.BOOL,byteOffset = 0,bitOffset = 6)public Boolean data16;@S7Variable(type=S7Type.BOOL,byteOffset = 0,bitOffset = 7)public Boolean data17;@S7Variable(type=S7Type.BYTE,byteOffset = 1,bitOffset = 0)public Byte dataB1;@S7Variable(type=S7Type.STRING,byteOffset = 2,bitOffset = 0)public String dataS1;@S7Variable(type=S7Type.STRING,byteOffset = 258,bitOffset = 40)public String dataS2;@S7Variable(type=S7Type.STRING,byteOffset = 514,bitOffset = 40)public String dataS3;@S7Variable(type=S7Type.STRING,byteOffset = 770)public String dataS4;}
第三步:读取数据
通过dispense 即可读取在对象中设置的所有数据(根据偏移量自动填充到对象的属性中。)
//参数1:根据偏移量和数据类型创建的对象
//参数2:DB数据块地址
//参数3:数据偏移量-返回的byte向后延几位
PlcDb plcDb = S7ConnectUti.getS7Serializer().dispense(PlcDb.class,12,0);
第四步:写入数据
/**
* address: DB数据块地址
* offset: 偏移量
* plcDb : 根据DB块的偏移量和数据类型创建的对象-需要修改的数据对应的属性需要赋值
*/public static void writeBooleanPlcData(Integer address,Integer offset,PlcDb plcDb) {getS7Serializer().store(plcDb,address, offset);}//示例
PlcDb plcDbWrite = new PlcDb();
plcDbWrite.setData10(true);
S7ConnectUti.writeBooleanPlcData(12,0,plcDbWrite);
源码地址:
PLCConnectDemo: JAVA连接PLC,S7connect、OPCUA(Milo)
方法在utils下面的 S7ConnectUti 中。
问题记录:
1. 提示错误:Result: the CPU does not support reading a bit block of length<>1
检查查询的实体类中字段是否为public,若为private则会出现错误。
2. 提示错误: the desired address is beyond limit for this PLC
JAVA数据类型和PLC中的数据类型不匹配,我的错误为PLC中类型为Array[0..9] of char,不能使用String直接接收,需要使用上文中【读取PLC地址中的数据】单独读取并进行解析。