这一节我们来将如何实现Client端的数据持续写入OPC UA。一下程序均在Spring Boot环境中,请先添加相应的依赖
- 首先,我们准备一个RestController用于提供JSON数据。
@RestController
@RequestMapping("/coors")
public class GreetingController {
//主要用于产生随机数
final Random random = new Random();
@RequestMapping("/relative")
public Coordinate relative() {
return new Coordinate(random.nextDouble(),random.nextDouble(),random.nextDouble());
}
@RequestMapping("/machine")
public Coordinate machine() {
return new Coordinate(random.nextDouble(),random.nextDouble(),random.nextDouble());
}
}
我们获取数据的REST API为:http://localhost:8080/coors/machine
为了不断的从REST API中获取JSON数据并解析,首先我们引入fastjson用来解析JSON数据
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.61</version>
</dependency>
我们需要写入的JSON数据格式为(这里可以用GSON生成对应的类):
{ "X": 10.0, "Z": 10.0, "Y": 20.0 }
新建一个Model用于存储数据
@Data
public class Coordinate {
/**
* X : 10.0
* Z : 10.0
* Y : 20.0
*/
private double X;
private double Z;
private double Y;
public List<Variant> getVariants(){
return Arrays.asList(new Variant(X), new Variant(Y), new Variant(Z));
}
}
其中getVariants方法返回一个包含三个轴坐标值的List集合
接着我们新建一个类,类中添加如下方法,用于将数据写入:
public OpcUaOperation{
public void writeNodeValues(List<NodeId> nodeIds, List<Variant> variants) {
try {
//连接到唯一运行的client,可以根据自己的情况建立一个OpcUAClient
OpcUaClient client = ClientGen.getOpcUaClient();
//创建连接
client.connect().get();
List<DataValue> values =
variants.stream().map(DataValue::valueOnly).collect(toList());
List<StatusCode> statusCodes =
client.writeValues(nodeIds, values).get();
//如果写入出错则打印错误信息
statusCodes.forEach(statusCode -> {
if (statusCode.isBad()) log.error("The Writing is wrong");
});
} catch (Exception e) {
log.error(e.getMessage());
}
}
}
为了不断的从REST API中获取数据,我们采用Spring Boot中的@Scheduled注解
这个注解会使被标记的方法每隔n毫秒执行一次 fixed表示固定的时间间隔
@Slf4j
@Component
@EnableScheduling //通过这个注解开启Scheduled
public class CNCCoorsCollecting {
@Autowired
OpcUaOperation opcUaOperation;
@Autowired
RestTemplate restTemplate;
//必须是http://开头的,不然restTemplate不能解析
private static String cncUrl = "http://localhost:8080";
@Scheduled(fixedDelay = 50)
public void coorsCollecting() {
try {
String axis_data = cncUrl + "/coors/machine";
ResponseEntity<String> responseEntity =
restTemplate.exchange(axis_data, HttpMethod.GET, null, String.class);
String json = responseEntity.getBody();
//获取到JSON对象所封装的数据
Coordinate coordinate = JSON.parseObject(json, Coordinate.class);
opcUaOperation.writeNodeValues(KND.axises, coordinate.getVariants());
} catch (Exception e) {
log.error("Axises Data Collecting Wrong");
}
}
}
其中这一段特别说明一下
ResponseEntity<String> responseEntity =
restTemplate.exchange(axis_data, HttpMethod.GET, null, String.class);
String json = responseEntity.getBody();
//获取到JSON对象所封装的数据
Coordinate coordinate = JSON.parseObject(json, Coordinate.class);
- 首先从axis_data这个url拿去对应的JSON对象,并将其装换为JSON字符串,运用的是restTemplate的exchange方法从对应的REST API获取JSON
- 从RepsonceEntity中获取Body,也就是对应的json字符串
- 运用JSON.parseObject(json, Coordinate.class) 将字符串转换为对应的JavaBean对象
其中KND.axises的定义如下,是一个包含了三个轴的Identifier的List集合
public static String KND_AXIS_X = KND_AXIS + prefix + "X";
public static String KND_AXIS_Y = KND_AXIS + prefix + "Y";
public static String KND_AXIS_Z = KND_AXIS + prefix + "Z";
//初始化轴集合
public static final List<NodeId> axises = Arrays.asList(
new NodeId(CNC, KND_AXIS_X),
new NodeId(CNC, KND_AXIS_Y),
new NodeId(CNC, KND_AXIS_Z));
接下来我们来改造原本的数据写入程序,有阅读过milo源码的朋友们应该知道,作者Kevin是一个函数式编程的忠实爱好者,他的许多的类都是基于Java 8的各种函数式以及多线程包编写的,我们按照他的思路,对于原本的单线程写入程序进行改造。
CompletableFuture.supplyAsync(() ->
restTemplate.exchange(axis_data, HttpMethod.GET, null, String.class))
.thenApply(HttpEntity::getBody)
.thenApply(json -> JSON.parseObject(json, Coordinate.class))
.thenAccept(coordinate -> opcUaOperation.writeNodeValues(KND.axises, coordinate.getVariants()))
.get();
虽然整体写入可以应用多线程的思想,但是整个执行流程有着明确的先后顺序,所以用thenApply方法将前后的结果依次连接。
thenApply可以将上一个CompletableFuture的结果作为输入,并且将这一部分流水线单线程化。
.
注意这里的多线程主要是为了与客户端的其他操作并行,在数据获取这个流水线中它依然需要单线程执行 – 因为有严格的先后顺序关系
这样我们就实现了对于一个有着固定工业IP的设备的OPC UA数据写入。