一、需求
使用java对接西门子S7-1500,之前都是通过偏移量直接读取寄存器地址,通过注解 @S7Variable(address = “DB800.8.0”, type = EDataType.BOOL) 就完成。
<dependency>
<groupId>com.github.xingshuangs</groupId>
<artifactId>iot-communication</artifactId>
<version>1.4.4</version>
</dependency>
现在PLC方说使用符号的方式,如下图:
于是技术栈选型使用Milo库的方式进行对接。
二、PLC需要提供的内容
- 连接地址:opc.tcp://10.19.171.XX:4840。
- NodeId:一般不改的话默认是3。
- Identifier:对应实际接口里的完整字段路径,PLC可以在他们的软件中找到或者Java开发根据接口自己拼接都可以。
三、Java程序开发准备
- 引入依赖,在pom.xml文件中新增
<dependency>
<groupId>org.eclipse.milo</groupId>
<artifactId>sdk-client</artifactId>
<version>0.6.8</version>
</dependency>
- 创建连接(以下是无安全验证方式连接),以下的连接只是为了拿到opcUaClient对象,后续的所有读写订阅都是通过opcUaClient进行操作的。
private static final String endpointUrl = "opc.tcp://10.19.171.xx:4840";
public static OpcUaClient opcUaClient;
public static void main(String[] args) {
try {
SecurityPolicy securityPolicy = SecurityPolicy.None;
List<EndpointDescription> endpoints;
try {
endpoints = DiscoveryClient.getEndpoints(endpointUrl).get();
} catch (Throwable ex) {
// 发现服务
String discoveryUrl = endpointUrl;
if (!discoveryUrl.endsWith("/")) {
discoveryUrl += "/";
}
discoveryUrl += "discovery";
endpoints = DiscoveryClient.getEndpoints(discoveryUrl).get();
}
EndpointDescription endpoint = endpoints.stream()
.filter(e -> e.getSecurityPolicyUri().equals(securityPolicy.getUri()))
.findFirst()
.orElseThrow(() -> new Exception("没有连接上端点"));
OpcUaClientConfig config = OpcUaClientConfig.builder()
.setApplicationName(LocalizedText.english("eclipse milo opc-ua client"))
.setApplicationUri("urn:eclipse:milo:examples:client")
//.setCertificate(loader.getClientCertificate())
//.setKeyPair(loader.getClientKeyPair())
.setEndpoint(endpoint)
//根据匿名验证和第三个用户名验证方式设置传入对象 AnonymousProvider(匿名方式)UsernameProvider(账户密码)new UsernameProvider("admin","123456")
.setIdentityProvider(new AnonymousProvider())
.setRequestTimeout(UInteger.valueOf(5000))
.build();
opcUaClient = OpcUaClient.create(config);
} catch (Exception e) {
System.out.println("创建客户端失败");
}
}
- 数据读取
public static void readValue() throws ExecutionException, InterruptedException {
// 获取全局opcUaClient创建连接
opcUaClient.connect().get();
// 注意这里"\"LES_DB\".\"OP30_materialShel01\".\"Rec\".\"TakeCmpl\""里面需要用双引号,最终效果就是这样
NodeId nodeId = new NodeId(3, "\"LES_DB\".\"OP30_materialShel01\".\"Rec\".\"TakeCmpl\"");
DataValue value = opcUaClient.readValue(0.0, TimestampsToReturn.Both, nodeId).get();
System.out.println("数据读取,值为:" + value.getValue().getValue());
}
- 数据写入
public static void writeValue(boolean value) throws ExecutionException, InterruptedException {
// 获取全局opcUaClient创建连接
opcUaClient.connect().get();
//创建变量节点
NodeId nodeId = new NodeId(3, "\"LES_DB\".\"OP30_materialShel01\".\"Rec\".\"TakeCmpl\"");
//创建Variant对象和DataValue对象
Variant v = new Variant(value);
DataValue dataValue = new DataValue(v, null, null);
StatusCode statusCode = opcUaClient.writeValue(nodeId, dataValue).get();
System.out.println("写入结果:" + statusCode.isGood());
}
- 数据订阅,如果你有当数据变化时候进行事件触发时候则可以使用订阅的方式
public static void createSubscription() throws ExecutionException, InterruptedException {
//创建连接
opcUaClient.connect().get();
//创建发布间隔1000ms的订阅对象
UaSubscription subscription = opcUaClient.getSubscriptionManager().createSubscription(1000.0).get();
//创建订阅的变量
NodeId nodeId = new NodeId(3, "\"LES_DB\".\"OP30_materialShel01\".\"Rec\".\"TakeCmpl\"");
ReadValueId readValueId = new ReadValueId(nodeId, AttributeId.Value.uid(), null, null);
//创建监控的参数
MonitoringParameters parameters = new MonitoringParameters(
uint(1),
1000.0, // sampling interval
null, // filter, null means use default
uint(10), // queue size
true // discard oldest
);
//创建监控项请求
//该请求最后用于创建订阅。
MonitoredItemCreateRequest request = new MonitoredItemCreateRequest(readValueId, MonitoringMode.Reporting, parameters);
List<MonitoredItemCreateRequest> requests = new ArrayList<>();
requests.add(request);
//创建监控项,并且注册变量值改变时候的回调函数。
List<UaMonitoredItem> items = subscription.createMonitoredItems(
TimestampsToReturn.Both,
requests,
(item, id) -> {
item.setValueConsumer((item2, value) -> {
System.out.println("nodeid :" + item.getReadValueId().getNodeId());
System.out.println("value :" + value.getValue().getValue());
});
}
).get();
}
四、总结
用符号的方式来对接PLC操作还是比较零碎的,主要是没有现成的框架能够将plc接口内容直接映射成java对象,当大量接口需要对接时体会尤为明显,这就需要java开发对接口内容再进行封装使得满足自己的业务需求。
我试过接口路径拿到LES_DB.OP30_materialShel01.Rec,打印结果如下: