VSOA Client 端开发

更新时间:
2024-04-26
下载文档

VSOA Client 端开发

本节主要介绍 VSOA Client 端如何开发。案例使用 IGC1500 实现 VSOA Client,获取 VSOA Server 端的周期性发布的数据,并打印到终端控制台中。

操作步骤

步骤 1:创建 VSOA 应用工程

  1. 参考“IGC 控制器双机通讯 > VSOA Server 端 SylixOS 开发”中 步骤 1 的前 3 点操作,创建 app_vsoa_client 工程。

  2. VSOA 和 yyjson 的相关依赖头文件路径。选择 cloudnative_sdk 工程中 libjson 和 libvsoa 文件夹路径。

  3. 添加 VSOA 和 yyjson 依赖库文件。选择 cloudnative_sdk 工程中 libvsoa-client.so、libvsoa-json.so。

  4. 参考“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 数据包组包接口。
  5. 在 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);
        }
    }
    
  6. 添加逻辑代码 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 工程示例open in new window

    • 使用 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 工程示例open in new window

  7. app_vsoa_client 工程结构。

步骤 2:部署工程环境

  1. 将 app_vsoa_client 部署到 IGC1500 控制器中,目标设备及路径如图所示。

  2. 右击 app_vsoa_client 应用工程进行上传部署。

步骤 3:查看运行效果

  1. 确保作为 VSOA Server 端的 IGC1503 已经正常运行 app_igh_user_vsoa 程序。

  2. 参考“快速入门 > 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]# 
    
  3. 进入 /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
      
文档内容是否对您有所帮助?
有帮助
没帮助