VSOA Server 端 SylixOS 开发
本节主要介绍 VSOA Server 端如何使用 SylixOS 开发环境进行开发。
前提条件
必须先按照 SylixOS EtherCAT 开发 进行操作完成 Server 端功能代码,再结合后续的操作步骤将功能代码加入 VSOA 微服务框架,实现 VSOA Server 端周期性发布电机数据功能。
操作步骤
步骤 1:创建 VSOA 应用工程
选择 File > New > SylixOS App,新建 VSOA 应用工程。
在 App 创建界面的 Project name 框中输入工程名,如 app_igh_user_vsoa,单击 Next,进入工程配置界面。
单击 Workspace 选择 App 工程依赖的 SylixOS ECS Base 工程,单击 Finish。
完成 App 工程创建后,参考“SylixOS EtherCAT 开发”中 步骤 5 添加代码,将 libigh 文件夹复制到app_igh_user_vsoa 工程中,将 struct.h 复制到 app_igh_user_vsoa 工程 src 文件夹下,清空 app_igh_user_vsoa.cpp 文件内容,并把 app_igh_user.c 的代码全部复制到 app_igh_user_user_vsoa.cpp 文件中。
添加 IgH 依赖头文件。参考“SylixOS EtherCAT 开发”中 步骤 4 的操作 1 和 2,添加工程依赖的 IgH 功能头文件路径。
添加 VSOA 依赖头文件。单击 Workspace,在弹框中选择 cloudnative_sdk 工程中 VSOA 相关的头文件路径。
添加 yyjson 依赖头文件。单击 Workspace,在弹框中选择 cloudnative_sdk 工程中 yyjson 相关的头文件路径。
添加 IgH 依赖库文件。参考“SylixOS EtherCAT 开发”中 步骤 4 的操作 3 和 4,添加 libIgh.a、libecatdc.a 库依赖,并配置路径。
添加 VSOA 依赖库文件。单击 Workspace,弹框中选择 cloudnative_sdk 工程中的 libvsoa-server.so 进行添加。
添加 yyjson 依赖库文件。单击 Workspace,弹框中选择 cloudnative_sdk 工程中的 libvsoa-json.so 进行添加。
步骤 2:编写组包解包代码
为规范电机数据的发布,VSOA Server 端发布电机数据采用 json 格式,VSOA Server 端发布电机数据的格式如下。
Server address:
- IP: 192.168.1.253
- port: 6001
获取数据方式:
VSOA Server 周期性发布 url 为 “/state” 的数据,订阅该 url 的 VSOA Client 从而可以获取对应的电机数据信息。
json 格式定义:
参数说明
字段 类型 说明 time number 数据发布时间(UTC) name string 设备名称 data object 数据对象 data 字段说明
字段 类型 说明 run boolean 操作电机启停,true 为启动,false 为停止 rotateSpeed number 电机转速
json 数据包示例:
{ "time": 1679905529, "name": "device", "data": { "run": true, "rotateSpeed": 10 } }
VSOA Server 提供一个控制伺服电机的启动与停止的 RPC 接口,数据格式如下。
设置数据方式:
VSOA Server 提供 url 为 “/control” 的 RPC SET 方法,订阅该 url 的 VSOA Client 可以通过调用该 url 的 RPC SET 方法,从而可以控制伺服电机的启动与停止。
json 格式定义:
字段 类型 是否必填 说明 run boolean 是 操作电机启停,true 为启动,false 为停止 json 数据包示例:
{ "run": true }
接口返回成功
{ "state": "ok" }
接口返回失败
{ "state": "faild", "errorMessage": "这是一个错误提示" }
在 app_igh_user_vsoa 工程的 src 文件夹目录下,添加 packet_json.h 文件。文件的内容提供 json 数据组包、解包接口 API,代码如下。
#ifndef SRC_PACKET_JSON_H_ #define SRC_PACKET_JSON_H_ #include <stdbool.h> #include <stdint.h> typedef struct { char motor_name[64]; bool run_flag; int32_t motor_speed; }ethercat_motor_data_t; #ifdef __cplusplus extern "C" { #endif bool ethercat_motor_control_parse (const char *str, size_t len, bool *run_flag); char *ethercat_motor_data_json_stringify (ethercat_motor_data_t motor_data); void json_stringify_free (char *str); #ifdef __cplusplus } #endif #endif
ethercat_motor_data_t 结构体说明
typedef struct { char motor_name[64]; bool run_flag; int32_t motor_speed; }ethercat_motor_data_t;
API 接口说明
API 接口名称 说明 bool ethercat_motor_control_parse (const char *str, size_t len, bool *run_flag); control RPC json 数据包解包接口。 char *ethercat_motor_data_json_stringify (ethercat_motor_data_t motor_data); 电机数据发布 json 数据包组包接口。 void json_stringify_free (char *str); 电机数据发布 json 数据包组包接口。
在 app_igh_user_vsoa 工程的 src 文件夹目录下,添加 packet_json.cpp 文件。文件的内容提供 json 数据组包、解包接口实现,代码如下。
#include <stdlib.h> #include <string.h> #include <stdbool.h> #include <time.h> #include "packet_json.h" #include "yyjson.h" bool ethercat_motor_control_parse (const char *str, size_t len, bool *run_flag) { yyjson_val *object = NULL; if ((NULL == str) || (0 == len) || (NULL == run_flag)) { 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 ethercat_motor_conctrl_parse_error; } object = yyjson_obj_get(root, "run"); if (NULL == object) { goto ethercat_motor_conctrl_parse_error; } *run_flag = yyjson_get_bool(object); yyjson_doc_free(doc); return (true); ethercat_motor_conctrl_parse_error: yyjson_doc_free(doc); return (false); } char *ethercat_motor_data_json_stringify (ethercat_motor_data_t motor_data) { 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); /* * get time */ time_t unix_time = time(NULL); yyjson_mut_obj_add_int(doc, root, "time", unix_time); yyjson_mut_obj_add_str(doc, root, "name", motor_data.motor_name); yyjson_mut_val *data = yyjson_mut_obj(doc); if (!data) { goto ethercat_motor_data_json_stringify_error; } yyjson_mut_obj_add(data, yyjson_mut_str(doc, "run"), yyjson_mut_bool(doc, motor_data.run_flag)); yyjson_mut_obj_add(data, yyjson_mut_str(doc, "rotateSpeed"), yyjson_mut_int(doc, motor_data.motor_speed)); yyjson_mut_obj_add(root, yyjson_mut_str(doc, "data"), data); string = yyjson_mut_write(doc, 0, NULL); yyjson_mut_doc_free(doc); return (string); ethercat_motor_data_json_stringify_error: yyjson_mut_doc_free(doc); return (NULL); } void json_stringify_free (char *str) { if (str) { free(str); } }
步骤 3:编写 VSOA 代码
在 app_igh_user_vsoa 工程的 src 文件夹目录下,添加 user_vsoa_server.h 文件。文件的内容为 VSOA Server 封装的 API,代码如下。
#ifndef SRC_USER_VSOA_SERVER_H_ #define SRC_USER_VSOA_SERVER_H_ #include <stdint.h> #include "vsoa_server.h" #include "vsoa_platform.h" #ifdef __cplusplus extern "C" { #endif struct user_vsoa_server_t; struct user_vsoa_server_t *create_user_vsoa_server (const char *name, const char *passwd, uint16_t port); int user_vsoa_server_add_rpc (struct user_vsoa_server_t *server, const char *url, vsoa_server_cmd_func_t callback, void *arg); int user_vsoa_server_publish_msg (struct user_vsoa_server_t *server, const char *url, char *param, uint16_t param_len, char *data, uint16_t data_len); int user_vsoa_server_start (struct user_vsoa_server_t *server); int user_vsoa_server_stop (struct user_vsoa_server_t *server); int user_vsoa_server_destroy (struct user_vsoa_server_t *server); #ifdef __cplusplus } #endif #endif
API 接口说明
函数 API 功能注解 struct user_vsoa_server_t *create_user_vsoa_server (const char *name, const char *passwd, uint16_t port); 创建一个 VSOA Server。 int user_vsoa_server_add_rpc (struct user_vsoa_server_t *server, const char *url, vsoa_server_cmd_func_t callback, void *arg); 向 VSOA Sever 注册 RPC 方法。 int user_vsoa_server_start (struct user_vsoa_server_t *server); 启动 VSOA Server。 int user_vsoa_server_stop (struct user_vsoa_server_t *server); 停止 VSOA Server。 int user_vsoa_server_destroy (struct user_vsoa_server_t *server); 销毁 VSOA Server。
在 app_igh_user_vsoa 工程的 src 文件夹目录下,添加 user_vsoa_server.cpp 文件,文件的内容为 VSOA Server 封装 API 的实现,代码如下。
#include <vector> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> #include <iostream> #include <pthread.h> #include "user_vsoa_server.h" #define COUNT_ARRY(a) (sizeof(a)/sizeof(a[0])) struct user_vsoa_server_t{ vsoa_server_t *vsoa_server; char passwd[16]; char name[128]; uint16_t port; pthread_t pthread_id; uint8_t thread_run; pthread_mutex_t mutex; }; /* * Command '/echo' routine */ static void command_echo (void *arg, vsoa_server_t *server, vsoa_cli_id_t cid, vsoa_header_t *vsoa_hdr, vsoa_url_t *url, vsoa_payload_t *payload) { uint32_t seqno = vsoa_parser_get_seqno(vsoa_hdr); vsoa_server_cli_reply(server, cid, 0, seqno, 0, payload); } static void user_vsoa_server_set_thread_run(struct user_vsoa_server_t *server, uint8_t value) { pthread_mutex_lock(&server->mutex); server->thread_run = (value == 0) ? 0 : 1; pthread_mutex_unlock(&server->mutex); } static void *user_vsoa_server_poll_thread (void *arg) { struct user_vsoa_server_t *server = (struct user_vsoa_server_t *)arg; if (!server->vsoa_server) { return (NULL); } char name[16] = {0}; sprintf(name, "vsoa_%*.s", 10, server->name); pthread_setname_np(pthread_self(), name); int cnt, max_fd; fd_set fds; struct timespec timeout = { 1, 0 }; user_vsoa_server_set_thread_run(server, 1); while (server->thread_run) { FD_ZERO(&fds); max_fd = vsoa_server_fds(server->vsoa_server, &fds); cnt = pselect(max_fd + 1, &fds, NULL, NULL, &timeout, NULL); if (cnt > 0) { vsoa_server_input_fds(server->vsoa_server, &fds); } } user_vsoa_server_set_thread_run(server, 0); return (NULL); } struct user_vsoa_server_t *create_user_vsoa_server (const char *name, const char *passwd, uint16_t port) { struct user_vsoa_server_t *server; server = (struct user_vsoa_server_t *)malloc(sizeof(struct user_vsoa_server_t)); if (NULL == server) { return (NULL); } server->vsoa_server = NULL; server->pthread_id = 0; server->port = 0; server->thread_run = 0; memset(server->name, 0, sizeof(server->name)); memset(server->passwd, 0, sizeof(server->passwd)); memset(server->name, 0, sizeof(server->name)); strcpy(server->name, name); strncpy(server->passwd, passwd, COUNT_ARRY(server->passwd) - 1); server->port = port; pthread_mutex_init(&server->mutex, NULL); char vsoa_name[512] = {0}; strcpy(server->name, name); snprintf(vsoa_name, sizeof(vsoa_name), "{\"name\":\"%s server\"}", (name == NULL) ? "vsoa" : name); server->vsoa_server = vsoa_server_create(vsoa_name); if (!server->vsoa_server) { printf("create vsoa server:%s fail\r\n", server->name); goto create_err; } if (strlen(server->passwd)) { vsoa_server_passwd(server->vsoa_server, server->passwd); } vsoa_url_t url; url.url = (char *)"/echo"; url.url_len = strlen(url.url); vsoa_server_add_listener(server->vsoa_server, &url, command_echo, NULL); return (server); create_err: if (server) { if (server->vsoa_server) { vsoa_server_close(server->vsoa_server); } free(server); } return (NULL); } int user_vsoa_server_add_rpc (struct user_vsoa_server_t *server, const char *url, vsoa_server_cmd_func_t callback, void *arg) { int ret; if ((NULL == url) || (NULL == callback) || (NULL == server) || (NULL == server->vsoa_server) || (0 != server->thread_run)) { return (-1); } vsoa_url_t rpc_url; rpc_url.url = (char *)url; rpc_url.url_len = strlen(rpc_url.url); ret = vsoa_server_add_listener(server->vsoa_server, &rpc_url, callback, arg); return (ret); } int user_vsoa_server_publish_msg (struct user_vsoa_server_t *server, const char *url, char *param, uint16_t param_len, char *data, uint16_t data_len) { int ret; if ((NULL == url) || (NULL == server) || (NULL == server->vsoa_server) || (0 == server->thread_run)) { return (-1); } vsoa_url_t l_url; l_url.url = (char *)url; l_url.url_len = strlen(l_url.url); vsoa_payload_t payload; payload.data = data; payload.data_len = data_len; payload.param = param; payload.param_len = param_len; ret = vsoa_server_publish(server->vsoa_server, &l_url, &payload); return (ret); } int user_vsoa_server_start (struct user_vsoa_server_t *server) { if ((NULL == server) || (NULL == server->vsoa_server) || (0 != server->thread_run)) { return (-1); } user_vsoa_server_set_thread_run(server, 0); struct sockaddr_in addr; bzero(&addr, sizeof(struct sockaddr_in)); addr.sin_family = AF_INET; addr.sin_port = htons(server->port); addr.sin_addr.s_addr = INADDR_ANY; if (!vsoa_server_start(server->vsoa_server, (struct sockaddr *)&addr, sizeof(struct sockaddr_in))) { fprintf(stderr, "Can not start VSOA server!\n"); return (-1); } /* * Create thread */ pthread_create(&server->pthread_id, NULL, user_vsoa_server_poll_thread, server); return (0); } int user_vsoa_server_stop (struct user_vsoa_server_t *server) { if ((NULL == server) || (NULL == server->vsoa_server) || (1 != server->thread_run)) { return (0); } user_vsoa_server_set_thread_run(server, 0); pthread_join(server->pthread_id, NULL); return (0); } int user_vsoa_server_destroy (struct user_vsoa_server_t *server) { if ((NULL == server) || (NULL == server->vsoa_server) || (1 != server->thread_run)) { goto destroy_exit; } user_vsoa_server_set_thread_run(server, 0); pthread_join(server->pthread_id, NULL); destroy_exit: if (server->vsoa_server) { vsoa_server_close(server->vsoa_server); } if (server) { free(server); } return (0); }
在 app_igh_user_vsoa.cpp 文件中添加头文件 user_vsoa_server.h 和 packet_json.h。
#include "user_vsoa_server.h" #include "packet_json.h"
在 app_igh_user_vsoa.cpp 文件中,完成创建 EtherCAT 的实时任务后,创建 VSOA Server 实例。
/* * create vsoa server */ struct user_vsoa_server_t *server = create_user_vsoa_server("ethercat_vsoa", "123456", 6001);
在 app_igh_user_vsoa.cpp 文件中,完成创建 VSOA Server 实例后,注册 VSOA Server RPC control 回调处理函数。
static void control (void *arg, vsoa_server_t *server, vsoa_cli_id_t id, vsoa_header_t *vsoa_hdr, vsoa_url_t *url, vsoa_payload_t *payload) { uint32_t seqno = vsoa_parser_get_seqno(vsoa_hdr); bool runFlag = false; vsoa_payload_t sendPload; /* * when motor not reset done, * not allowed to conctrol motor */ if (!bResetDone) { sendPload.data = NULL; sendPload.data_len = 0; sendPload.param = (char *)"{\"state\":\"failed\",\"errorMessage\":\"motor in resetting, please try again wait minute.\"}"; sendPload.param_len = strlen(sendPload.param); vsoa_server_cli_reply(server, id, 0, seqno, 0, &sendPload); return ; } /* * conctrol motor run or stop */ if (ethercat_motor_control_parse (payload->param, payload->param_len, &runFlag)) { if (true == runFlag) { uiDirection = 0; } else { if (2 != uiDirection) { uiDirection = 2; } } sendPload.data = NULL; sendPload.data_len = 0; sendPload.param = (char *)"{\"state\":\"ok\"}"; } else { sendPload.data = NULL; sendPload.data_len = 0; sendPload.param = (char *)"{\"state\":\"failed\",\"errorMessage\":\"this is a error\"}"; } sendPload.param_len = strlen(sendPload.param); vsoa_server_cli_reply(server, id, 0, seqno, 0, &sendPload); }
/* * register rpc callback */ user_vsoa_server_add_rpc (server, "/control", control, NULL);
在 app_igh_user_vsoa.cpp 文件中,完成 VSOA Server 实例创建以及 VSOA Server RPC 注册后,启动 VSOA Server。
user_vsoa_server_start(server);
在 app_igh_user_vsoa.cpp 文件中,完成 VSOA Server 启动后,VSOA Server 周期发布电机数据。
while (run) { usleep(1000 * 1000); /* * wait motor reset done */ if (!bResetDone) { continue ; } /* * print process status */ volatile tTxPDO *data = &driverData[0]; char param[1024] = {0}; sprintf(param, "status_word:%x mode:%x actual_pos:%d spee:%d error_code:%x actual_torque:%d\r\n", data->status_word, data->display_mode, data->actual_pos, data->actual_speed, data->error_code, data->actual_torque); printf(param); ethercat_motor_data_t motorData = {0}; ec_slave_info_t slave_info; ecrt_master_get_slave(master, 0, &slave_info ); strcpy(motorData.motor_name, slave_info.name); motorData.motor_speed = data->actual_speed; motorData.run_flag = (0 == uiDirection) ? true : false; char *pstr = ethercat_motor_data_json_stringify(motorData); user_vsoa_server_publish_msg(server, "/state", pstr, strlen(pstr), NULL, 0); json_stringify_free(pstr); } ecrt_master_deactivate(master); user_vsoa_server_stop(server); user_vsoa_server_destroy(server); return 0;
完整 app_igh_user_vsoa.cpp 请参考 app_igh_user_vsoa 工程示例。
app_igh_user_vsoa 工程结构。
步骤 4:部署工程环境
将 app_igh_user_vsoa 工程部署到 IGC1503 控制器“/apps/app_igh_user_vsoa”目录,配置上传路径如下所示。
右键 app_igh_user_vsoa 工程进行上传部署。
参考 “SylixOS EtherCAT 开发”中 步骤 6 完成主站 ec_master.ko 以及 rt_netcard.ko 部署。
步骤 5:查看运行效果
参考“快速体验 > SylixOS 数据采集 > 部署应用”中 步骤 3,通过 Telnet 登录到 IGC1503 控制器。
[[ (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 : IGC1503-1 ALLWINNER T3 Packet 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]#
参考 “SylixOS EtherCAT 开发”中 步骤 7 配置完成网卡配置。
参考 “SylixOS EtherCAT 开发”中 步骤 8 加载 Igh EtherCAT 驱动。
说明:
insmod ec_master.ko 加载驱动时,因为板卡硬件差异情况,终端打印的信息或许会有些许差异。
[root@sylixos:/]# insmod /lib/modules/ec_master.ko use MAC addr: 02:81:01:6e:98:96 module /lib/modules/ec_master.ko register ok, handle: 0x43ea0d10 [root@sylixos:/]# insmod /lib/modules/rt_netcard.ko use Dev: gmac EtherCAT port. module /lib/modules/rt_netcard.ko register ok, handle: 0x4400f050
进入 /apps/app_igh_user_vsoa 目录后,执行 app_igh_user_vsoa 程序。
[root@sylixos:/apps/app_igh_user_vsoa]# ls app_igh_user_vsoa [root@sylixos:/apps/app_igh_user_vsoa]# ./app_igh_user_vsoa Bus scan successful - 1 slaves. slave[0]-- vendor_id:0x100000, product_code:0xc010d EtherCAT WARNING 0: 2 datagrams UNMATCHED! Activating master succes! Cycle Thread Started. dc ref time 627360758452 start run! Move step 30000 1 slave(s). AL states: 0x02. Link is up. First master diff: 26845. AL states: 0x01. AL states: 0x02. Domain1_output: WC 1. Domain1_output: State 2. AL states: 0x04. Domain1_input: WC 1. Domain1_input: State 2. AL states: 0x08. reset done ! status_word:1637 mode:8 actual_pos:693321 actual_speed:1536000 error_code:0 actual_torque:16 status_word:1637 mode:8 actual_pos:2108198 actual_speed:1216000 error_code:0 actual_torque:16 status_word:1637 mode:8 actual_pos:3523610 actual_speed:1408000 error_code:0 actual_torque:17 status_word:1637 mode:8 actual_pos:4939358 actual_speed:1280000 error_code:0 actual_torque:14