OPC UA JAVA开发笔记(四):数据写入

这一节我们来将如何实现Client端的数据持续写入OPC UA。一下程序均在Spring Boot环境中,请先添加相应的依赖

  1. 首先,我们准备一个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);
  1. 首先从axis_data这个url拿去对应的JSON对象,并将其装换为JSON字符串,运用的是restTemplate的exchange方法从对应的REST API获取JSON
  2. 从RepsonceEntity中获取Body,也就是对应的json字符串
  3. 运用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数据写入。

猜你喜欢

转载自blog.csdn.net/qq_41989109/article/details/104701481