VSOA Client 端开发
本节主要介绍 VSOA Client 端如何开发。案例使用 IGC1500 实现 VSOA Client,获取 VSOA Server 端的周期性发布的数据,并打印到终端控制台中。
操作步骤
步骤 1:创建 VSOA 应用工程
参考“IGC 控制器双机通讯 > VSOA Server 端 SylixOS 开发”中 步骤 1 的前 3 点操作,创建 app_vsoa_client 工程。
VSOA 和 yyjson 的相关依赖头文件路径。选择 cloudnative_sdk 工程中 libjson 和 libvsoa 文件夹路径。
添加 VSOA 和 yyjson 依赖库文件。选择 cloudnative_sdk 工程中 libvsoa-client.so、libvsoa-json.so。
参考“IGC 控制器双机通讯 > VSOA Server 端 SylixOS 开发”中 步骤 2 描述的通讯数据协议定义,在 app_vsoa_client 工程
src
目录下,添加packet_json.h
文件,解析 VSOA Server 发布的 json 格式电机数据信息,同时提供电机启动/停止控制 RPC 方法的 json 格式组包接口。#ifndef SRC_PACKET_JSON_H_ #define SRC_PACKET_JSON_H_ #include <stdbool.h> #include <stdint.h> typedef struct { uint32_t unix_time; char motor_name[64]; bool run_flag; int32_t motor_speed; }state_data_t; #ifdef __cplusplus extern "C" { #endif bool state_data_parse (const char *str, size_t len, state_data_t *state_data); bool control_ack_parse (const char *str, size_t len, bool *state, char *err_msg); char *ethercat_motor_control_json_stringify (bool run_flag); void json_stringify_free (char *str); #ifdef __cplusplus } #endif #endif
state_data_t 结构体说明
typedef struct { uint32_t unix_time; char motor_name[64]; bool run_flag; int32_t motor_speed; }state_data_t;
API 接口说明
API 接口名称 说明 state_data_parse (const char *str, size_t len, state_data_t *state_data); 电机数据 json 数据包解包接口。 bool conctrol_ack_parse (const char *str, size_t len, bool *state, char *err_msg); conctrol 应答数据解析接口。 char *ethercat_motor_control_json_stringify (bool run_flag); control RPC json 数据包组包接口。 void json_stringify_free (char *str); 电机数据发布 json 数据包组包接口。
在 app_vsoa_client 工程 src 目录下,添加 packet_json.cpp 文件。
#include <stdlib.h> #include <string.h> #include <stdbool.h> #include <time.h> #include "packet_json.h" #include "yyjson.h" bool state_data_parse (const char *str, size_t len, state_data_t *state_data) { yyjson_val *object = NULL; yyjson_val *sub_object = NULL; if ((NULL == str) || (0 == len) || (NULL == state_data)) { return (false); } yyjson_doc *doc = yyjson_read(str, len, 0); if (NULL == doc) { return (false); } yyjson_val *root = yyjson_doc_get_root(doc); if (NULL == root) { goto state_data_parse_error; } object = yyjson_obj_get(root, "time"); if (NULL == object) { goto state_data_parse_error; } state_data->unix_time = yyjson_get_int(object); object = yyjson_obj_get(root, "name"); if (NULL == object) { goto state_data_parse_error; } strcpy(state_data->motor_name, yyjson_get_str(object)); sub_object = yyjson_obj_get(root, "data"); if (NULL == sub_object) { goto state_data_parse_error; } object = yyjson_obj_get(sub_object, "run"); if (NULL == object) { goto state_data_parse_error; } state_data->run_flag = yyjson_get_bool(object); object = yyjson_obj_get(sub_object, "rotateSpeed"); if (NULL == object) { goto state_data_parse_error; } state_data->motor_speed = yyjson_get_int(object); yyjson_doc_free(doc); return (true); state_data_parse_error: yyjson_doc_free(doc); return (false); } bool control_ack_parse (const char *str, size_t len, bool *state, char *err_msg) { yyjson_val *object = NULL; if ((NULL == str) || (0 == len) || (NULL == state)) { return (false); } yyjson_doc *doc = yyjson_read(str, len, 0); if (NULL == doc) { return (false); } yyjson_val *root = yyjson_doc_get_root(doc); if (NULL == root) { goto control_ack_parse_error; } object = yyjson_obj_get(root, "state"); if (NULL == object) { goto control_ack_parse_error; } if (0 != strcmp(yyjson_get_str(object), "ok")) { *state = false; object = yyjson_obj_get(root, "errorMessage"); if (NULL == object) { printf("no errorMessage!\n"); goto control_ack_parse_error; } strcpy(err_msg, yyjson_get_str(object)); } else { *state = true; } yyjson_doc_free(doc); return (true); control_ack_parse_error: yyjson_doc_free(doc); return (false); } char *ethercat_motor_control_json_stringify (bool run_flag) { char *string = NULL; yyjson_mut_doc *doc = yyjson_mut_doc_new(NULL); yyjson_mut_val *root = yyjson_mut_obj(doc); yyjson_mut_doc_set_root(doc, root); yyjson_mut_obj_add_bool(doc, root, "run", run_flag); string = yyjson_mut_write(doc, 0, NULL); yyjson_mut_doc_free(doc); return (string); } void json_stringify_free (char *str) { if (str) { free(str); } }
添加逻辑代码 app_vsoa_client.cpp。由于 SylixOS VSOA 模型接口和 CODESYS VSOA 模型接口在使用上存在部分差异。CODESYS VSOA 模型接口的 URL 并非由用户设置,而是由一定规则自动生成。根据不同的 Server 端 VSOA 模型开发方式,app_vsoa_client.cpp 文件中的逻辑代码也有所差异,具体代码功能如下:
使用 SylixOS 开发 VSOA Server 端时,Client 端代码如下:
#include <stdio.h> #include <stdlib.h> #include <string.h> #ifdef SYLIXOS #include <sys/vproc.h> #endif #include <sys/select.h> #include <netinet/in.h> #include <arpa/inet.h> #include "vsoa_client.h" #include "packet_json.h" /* * vsoa server ip port passwd */ #define MY_SERVER_IP "192.168.1.253" #define MY_SERVER_PORT (6001) #define MY_SERVER_PASSWD "123456" static vsoa_client_t *client; static state_data_t state_data; /* * On subscribed messages received * when receive server publish /state, then execute the function */ static void onmessage (void *arg, struct vsoa_client *client, vsoa_url_t *url, vsoa_payload_t *payload, bool quick) { if (0 == strncmp(url->url, "/state", url->url_len)) { memset(&state_data, 0, sizeof(state_data)); /* * according to the protocol to parse * and print the data * time msg send time(sencond) * name motor device name * run motor run or stop state * speed motor current speed(cnt) */ if (state_data_parse(payload->param, payload->param_len, &state_data)) { printf("time:%d s, name:%s, run:%s, speed:%d\r\n", state_data.unix_time, state_data.motor_name, (true == state_data.run_flag) ? "true" : "false", state_data.motor_speed); } else { printf("receive URL: %.*s ,parse faild , payload:\n %.*s\n", (int)url->url_len, url->url, (int)payload->param_len, payload->param); } } else { printf("receive URL: %.*s payload:\n %.*s\n", (int)url->url_len, url->url, (int)payload->param_len, payload->param); } } int main (int argc, char **argv) { int max_fd, cnt; fd_set fds; char info[256]; struct sockaddr_in addr; struct timespec timeout = { 1, 0 }; #ifdef SYLIXOS vprocExitModeSet(getpid(), LW_VPROC_EXIT_FORCE); #endif /* * vsoa server ip and port */ bzero(&addr, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr(MY_SERVER_IP); addr.sin_port = htons(MY_SERVER_PORT); addr.sin_len = sizeof(struct sockaddr_in); /* * create a vsoa client */ client = vsoa_client_create(onmessage, NULL); if (!client) { fprintf(stderr, "Can not create VSOA client!\n"); return (-1); } /* * connect vsoa server by server's ip and port */ if (!vsoa_client_connect(client, (struct sockaddr *)&addr, sizeof(struct sockaddr_in), &timeout, MY_SERVER_PASSWD, info, sizeof(info))) { vsoa_client_close(client); fprintf(stderr, "Can not connect to VSOA server!\n"); return (-1); } /* * subscribe /state url topic */ vsoa_url_t l_url = {0}; l_url.url = (char *)"/state"; l_url.url_len = strlen(l_url.url); vsoa_client_subscribe(client, &l_url, NULL, NULL, NULL); /* * start circular monitoring */ while (1) { FD_ZERO(&fds); max_fd = vsoa_client_fds(client, &fds); cnt = pselect(max_fd + 1, &fds, NULL, NULL, &timeout, NULL); if (cnt > 0) { if (!vsoa_client_input_fds(client, &fds)) { vsoa_client_close(client); fprintf(stderr, "Connection lost!\n"); return (-1); } } } return (0); }
完整 app_vsoa_client.cpp 请参考 app_vsoa_client 工程示例。
使用 CODESYS 开发 VSOA Server 端时,Client 端代码如下:
说明:
本章节 CODESYS VSOA 模型接口演示电机模型的数据发布 URL 为 /plc/motor0/state,Control 方法的 URL 为 /plc/motor0/control。除 URL 不同之外,其他的代码与 SylixOS 开发 VSOA Server 端时的代码均相同。
static void onmessage (void *arg, struct vsoa_client *client, vsoa_url_t *url, vsoa_payload_t *payload, bool quick) { if (0 == strncmp(url->url, "/plc/motor0/state", url->url_len)) { ... } } int main (int argc, char **argv) { ... vsoa_url_t l_url = {0}; l_url.url = (char *)"/plc/motor0/state"; l_url.url_len = strlen(l_url.url); ... return (0); }
完整 app_vsoa_client.cpp 请参考 app_vsoa_client_codesys 工程示例。
app_vsoa_client 工程结构。
步骤 2:部署工程环境
将 app_vsoa_client 部署到 IGC1500 控制器中,目标设备及路径如图所示。
右击 app_vsoa_client 应用工程进行上传部署。
步骤 3:查看运行效果
确保作为 VSOA Server 端的 IGC1503 已经正常运行 app_igh_user_vsoa 程序。
参考“快速入门 > SylixOS 数据采集 > 部署应用”中 步骤 3,通过 Telnet 登录到 IGC1500 控制器。
[[ (R) [[[[ [[[[ [[ [[[[ [[[[ [[ [[ [[ [[ [[ [[ [[ [[ [[ [[ [[ [[[[ [[ [[ [[ [[ [[ [[ [[ [[ [[ [[ [[ [[ [[ [[ [[ [[ [[ [[ [[ [[ [[[[ [[ [[ [[ [[ [[ [[ [[ [[ [[ [[ [[ [[ [[ [[ [[ [[ [[ [[[[ [[ [[ [[ [[ [[ [[ [[ [[ [[ [[ [[ [[ [[ [[ [[ [[[[ [[[[ [[[[[[ [[[[[[ [[ [[ [[[[ [[[[ [[ [[ KERNEL: LongWing(C) 2.5.3 [[[[ COPYRIGHT ACOINFO Co. Ltd. 2006 - 2023 SylixOS license: Commercial & GPL. SylixOS kernel version: 2.5.3 Code name: Enterprise SylixOS ecs version: 2.5.4 CPU : Allwinner T3 (Quad-core ARM Cortex-A7 1.2GHz VFPv4) CACHE : 64KBytes(D-32K/I-32K) L1-Cache per core, 512KBytes L2-Cache PACKET : IGC1500 Packet for Acoinfo ROM SIZE: 0x00400000 Bytes (0x00000000 - 0x003fffff) RAM SIZE: 0x3ff00000 Bytes (0x40100000 - 0x7fffffff) BSP : BSP Version 1.1.5 (Release) (Build Feb 22 2023 10:05:54) [root@sylixos:/root]#
进入 /apps/app_vsoa_client 目录后,执行 app_vsoa_client 程序。
使用 SylixOS 开发 VSOA Server 端时,运行效果如下:
[root@sylixos:/root]# cd /apps/app_vsoa_client/ [root@sylixos:/apps/app_vsoa_client]# ./app_vsoa_client time:1681808977 s, name:SV660_1Axis_00913, run:true, speed:1344000 time:1681808978 s, name:SV660_1Axis_00913, run:true, speed:1344000 time:1681808979 s, name:SV660_1Axis_00913, run:true, speed:1408000
使用 CODESYS 开发 VSOA Server 端时,运行效果如下:
[root@sylixos:/root]# cd /apps/app_vsoa_client/ [root@sylixos:/apps/app_vsoa_client]# ./app_vsoa_client time:946870985 s, name:motor0, run:true, speed:1 time:946870985 s, name:motor0, run:true, speed:1 time:946870985 s, name:motor0, run:true, speed:1