背景
在公司项目中,需要用到和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地址中的数据】单独读取并进行解析。