linux 板卡实现vxi11服务
官方lxi协议联盟:LXI Discovery Tool 下载官方协议和工具
lxi协议为统一控制仪器设备通信指定的一套标准协议,其中可通过http,scpi等控制方式,还提供可以设备支持被扫描发现功能(基于http/tcp),为此实现vxi11服务,支持设备被扫描到。需要如下配置。
vxi11 服务是基于rpc远程服务和xdr协议进行通信的,在linux下已经装有rpc工具,为此在linux平台开发更简单
一:下载lxi 协议源码和测试工具参考:
GitHub - Lew-Engineering/libvxi11: MacOS and Linux C++ library for communicating with devices via the VXI-11 TCP/IP protocol
链接存在需要使用的vxi11-rpc.x,其内容可从vxi11标准协议中找到
GitHub - lxi-tools/lxi-tools: Open source LXI tools 这个工具可以安装在windows上,只要会使用lxi-gui 用于扫描设备和通信
下载到的源码中使用了vxi11协议中规定协议,
二:提取vxi11_rpc.x在linux使用rpcgen工具生成rpc通信规范流程代码
因为需要运行rpc相关接口,工程中需要引入rpc库(libtirpc),可以生成静态库的方式引入到工程
运行后会生成上述4个文件,将上述4个文件复制到应用工程中(因为需要运行rpc相关接口,工程中需要引入rpc库(libtirpc),可以生成静态库的方式引入到工程),其中vxi11_rpc_clnt.c实现客户端相关代码,vxi11_rpc_svc.c实现的是服务器的代码,但是只有流程,实际上服务器是需要仪器设备实现的(因为仪器是服务端)。
如下实现方式:vxi11_rpc_svc.c代码如下,vxi11_rpc.h是头文件定义
/** Please do not edit this file.* It was generated using rpcgen.*/#include "vxi11_rpc.h"
#include <stdio.h>
#include <stdlib.h>
#include <rpc/pmap_clnt.h>
#include <string.h>
#include <memory.h>
#include <sys/socket.h>
#include <netinet/in.h>#ifndef SIG_PF
#define SIG_PF void(*)(int)
#endifstatic void
device_async_1(struct svc_req *rqstp, register SVCXPRT *transp)
{union {Device_Link device_abort_1_arg;} argument;char *result;xdrproc_t _xdr_argument, _xdr_result;char *(*local)(char *, struct svc_req *);switch (rqstp->rq_proc) {case NULLPROC:(void) svc_sendreply (transp, (xdrproc_t) xdr_void, (char *)NULL);return;case device_abort:_xdr_argument = (xdrproc_t) xdr_Device_Link;_xdr_result = (xdrproc_t) xdr_Device_Error;local = (char *(*)(char *, struct svc_req *)) device_abort_1_svc;break;default:svcerr_noproc (transp);return;}memset ((char *)&argument, 0, sizeof (argument));if (!svc_getargs (transp, (xdrproc_t) _xdr_argument, (caddr_t) &argument)) {svcerr_decode (transp);return;}result = (*local)((char *)&argument, rqstp);if (result != NULL && !svc_sendreply(transp, (xdrproc_t) _xdr_result, result)) {svcerr_systemerr (transp);}if (!svc_freeargs (transp, (xdrproc_t) _xdr_argument, (caddr_t) &argument)) {fprintf (stderr, "%s", "unable to free arguments");exit (1);}return;
}static void
device_core_1(struct svc_req *rqstp, register SVCXPRT *transp)
{union {Create_LinkParms create_link_1_arg;Device_WriteParms device_write_1_arg;Device_ReadParms device_read_1_arg;Device_GenericParms device_readstb_1_arg;Device_GenericParms device_trigger_1_arg;Device_GenericParms device_clear_1_arg;Device_GenericParms device_remote_1_arg;Device_GenericParms device_local_1_arg;Device_LockParms device_lock_1_arg;Device_Link device_unlock_1_arg;Device_EnableSrqParms device_enable_srq_1_arg;Device_DocmdParms device_docmd_1_arg;Device_Link destroy_link_1_arg;Device_RemoteFunc create_intr_chan_1_arg;} argument;char *result;xdrproc_t _xdr_argument, _xdr_result;char *(*local)(char *, struct svc_req *);switch (rqstp->rq_proc) {case NULLPROC:(void) svc_sendreply (transp, (xdrproc_t) xdr_void, (char *)NULL);return;case create_link:_xdr_argument = (xdrproc_t) xdr_Create_LinkParms;_xdr_result = (xdrproc_t) xdr_Create_LinkResp;local = (char *(*)(char *, struct svc_req *)) create_link_1_svc;break;case device_write:_xdr_argument = (xdrproc_t) xdr_Device_WriteParms;_xdr_result = (xdrproc_t) xdr_Device_WriteResp;local = (char *(*)(char *, struct svc_req *)) device_write_1_svc;break;case device_read:_xdr_argument = (xdrproc_t) xdr_Device_ReadParms;_xdr_result = (xdrproc_t) xdr_Device_ReadResp;local = (char *(*)(char *, struct svc_req *)) device_read_1_svc;break;case device_readstb:_xdr_argument = (xdrproc_t) xdr_Device_GenericParms;_xdr_result = (xdrproc_t) xdr_Device_ReadStbResp;local = (char *(*)(char *, struct svc_req *)) device_readstb_1_svc;break;case device_trigger:_xdr_argument = (xdrproc_t) xdr_Device_GenericParms;_xdr_result = (xdrproc_t) xdr_Device_Error;local = (char *(*)(char *, struct svc_req *)) device_trigger_1_svc;break;case device_clear:_xdr_argument = (xdrproc_t) xdr_Device_GenericParms;_xdr_result = (xdrproc_t) xdr_Device_Error;local = (char *(*)(char *, struct svc_req *)) device_clear_1_svc;break;case device_remote:_xdr_argument = (xdrproc_t) xdr_Device_GenericParms;_xdr_result = (xdrproc_t) xdr_Device_Error;local = (char *(*)(char *, struct svc_req *)) device_remote_1_svc;break;case device_local:_xdr_argument = (xdrproc_t) xdr_Device_GenericParms;_xdr_result = (xdrproc_t) xdr_Device_Error;local = (char *(*)(char *, struct svc_req *)) device_local_1_svc;break;case device_lock:_xdr_argument = (xdrproc_t) xdr_Device_LockParms;_xdr_result = (xdrproc_t) xdr_Device_Error;local = (char *(*)(char *, struct svc_req *)) device_lock_1_svc;break;case device_unlock:_xdr_argument = (xdrproc_t) xdr_Device_Link;_xdr_result = (xdrproc_t) xdr_Device_Error;local = (char *(*)(char *, struct svc_req *)) device_unlock_1_svc;break;case device_enable_srq:_xdr_argument = (xdrproc_t) xdr_Device_EnableSrqParms;_xdr_result = (xdrproc_t) xdr_Device_Error;local = (char *(*)(char *, struct svc_req *)) device_enable_srq_1_svc;break;case device_docmd:_xdr_argument = (xdrproc_t) xdr_Device_DocmdParms;_xdr_result = (xdrproc_t) xdr_Device_DocmdResp;local = (char *(*)(char *, struct svc_req *)) device_docmd_1_svc;break;case destroy_link:_xdr_argument = (xdrproc_t) xdr_Device_Link;_xdr_result = (xdrproc_t) xdr_Device_Error;local = (char *(*)(char *, struct svc_req *)) destroy_link_1_svc;break;case create_intr_chan:_xdr_argument = (xdrproc_t) xdr_Device_RemoteFunc;_xdr_result = (xdrproc_t) xdr_Device_Error;local = (char *(*)(char *, struct svc_req *)) create_intr_chan_1_svc;break;case destroy_intr_chan:_xdr_argument = (xdrproc_t) xdr_void;_xdr_result = (xdrproc_t) xdr_Device_Error;local = (char *(*)(char *, struct svc_req *)) destroy_intr_chan_1_svc;break;default:svcerr_noproc (transp);return;}memset ((char *)&argument, 0, sizeof (argument));if (!svc_getargs (transp, (xdrproc_t) _xdr_argument, (caddr_t) &argument)) {svcerr_decode (transp);return;}result = (*local)((char *)&argument, rqstp);if (result != NULL && !svc_sendreply(transp, (xdrproc_t) _xdr_result, result)) {svcerr_systemerr (transp);}if (!svc_freeargs (transp, (xdrproc_t) _xdr_argument, (caddr_t) &argument)) {fprintf (stderr, "%s", "unable to free arguments");exit (1);}return;
}static void
device_intr_1(struct svc_req *rqstp, register SVCXPRT *transp)
{union {Device_SrqParms device_intr_srq_1_arg;} argument;char *result;xdrproc_t _xdr_argument, _xdr_result;char *(*local)(char *, struct svc_req *);switch (rqstp->rq_proc) {case NULLPROC:(void) svc_sendreply (transp, (xdrproc_t) xdr_void, (char *)NULL);return;case device_intr_srq:_xdr_argument = (xdrproc_t) xdr_Device_SrqParms;_xdr_result = (xdrproc_t) xdr_void;local = (char *(*)(char *, struct svc_req *)) device_intr_srq_1_svc;break;default:svcerr_noproc (transp);return;}memset ((char *)&argument, 0, sizeof (argument));if (!svc_getargs (transp, (xdrproc_t) _xdr_argument, (caddr_t) &argument)) {svcerr_decode (transp);return;}result = (*local)((char *)&argument, rqstp);if (result != NULL && !svc_sendreply(transp, (xdrproc_t) _xdr_result, result)) {svcerr_systemerr (transp);}if (!svc_freeargs (transp, (xdrproc_t) _xdr_argument, (caddr_t) &argument)) {fprintf (stderr, "%s", "unable to free arguments");exit (1);}return;
}int
main (int argc, char **argv)
{register SVCXPRT *transp;pmap_unset (DEVICE_ASYNC, DEVICE_ASYNC_VERSION);pmap_unset (DEVICE_CORE, DEVICE_CORE_VERSION);pmap_unset (DEVICE_INTR, DEVICE_INTR_VERSION);transp = svcudp_create(RPC_ANYSOCK);if (transp == NULL) {fprintf (stderr, "%s", "cannot create udp service.");exit(1);}if (!svc_register(transp, DEVICE_ASYNC, DEVICE_ASYNC_VERSION, device_async_1, IPPROTO_UDP)) {fprintf (stderr, "%s", "unable to register (DEVICE_ASYNC, DEVICE_ASYNC_VERSION, udp).");exit(1);}if (!svc_register(transp, DEVICE_CORE, DEVICE_CORE_VERSION, device_core_1, IPPROTO_UDP)) {fprintf (stderr, "%s", "unable to register (DEVICE_CORE, DEVICE_CORE_VERSION, udp).");exit(1);}if (!svc_register(transp, DEVICE_INTR, DEVICE_INTR_VERSION, device_intr_1, IPPROTO_UDP)) {fprintf (stderr, "%s", "unable to register (DEVICE_INTR, DEVICE_INTR_VERSION, udp).");exit(1);}transp = svctcp_create(RPC_ANYSOCK, 0, 0);if (transp == NULL) {fprintf (stderr, "%s", "cannot create tcp service.");exit(1);}if (!svc_register(transp, DEVICE_ASYNC, DEVICE_ASYNC_VERSION, device_async_1, IPPROTO_TCP)) {fprintf (stderr, "%s", "unable to register (DEVICE_ASYNC, DEVICE_ASYNC_VERSION, tcp).");exit(1);}if (!svc_register(transp, DEVICE_CORE, DEVICE_CORE_VERSION, device_core_1, IPPROTO_TCP)) {fprintf (stderr, "%s", "unable to register (DEVICE_CORE, DEVICE_CORE_VERSION, tcp).");exit(1);}if (!svc_register(transp, DEVICE_INTR, DEVICE_INTR_VERSION, device_intr_1, IPPROTO_TCP)) {fprintf (stderr, "%s", "unable to register (DEVICE_INTR, DEVICE_INTR_VERSION, tcp).");exit(1);}svc_run ();fprintf (stderr, "%s", "svc_run returned");exit (1);/* NOTREACHED */
}
可以看到工具帮忙生成了main函数,此为服务入口并且帮忙注册了udp和tcp服务,实现了三个重要注册的接口,用户需要做的就是实现device_core_1接口内部调用的函数句柄xxx_svc:
Create_LinkResp * create_link_1_svc(Create_LinkParms *, struct svc_req *);extern Device_WriteResp * device_write_1_svc(Device_WriteParms *, struct svc_req *);extern Device_ReadResp * device_read_1_svc(Device_ReadParms *, struct svc_req *);extern Device_ReadStbResp * device_readstb_1_svc(Device_GenericParms *, struct svc_req *);extern Device_Error * device_trigger_1_svc(Device_GenericParms *, struct svc_req *);extern Device_Error * device_clear_1_svc(Device_GenericParms *, struct svc_req *);extern Device_Error * device_remote_1_svc(Device_GenericParms *, struct svc_req *);extern Device_Error * device_local_1_svc(Device_GenericParms *, struct svc_req *);extern Device_Error * device_lock_1_svc(Device_LockParms *, struct svc_req *);extern Device_Error * device_unlock_1_svc(Device_Link *, struct svc_req *);extern Device_Error * device_enable_srq_1_svc(Device_EnableSrqParms *, struct svc_req *);extern Device_DocmdResp * device_docmd_1_svc(Device_DocmdParms *, struct svc_req *);extern Device_Error * destroy_link_1_svc(Device_Link *, struct svc_req *);extern Device_Error * create_intr_chan_1_svc(Device_RemoteFunc *, struct svc_req *);extern Device_Error * destroy_intr_chan_1_svc(void *, struct svc_req *);
三:svc接口实现:
如下实现上述接口操作,可以做到设备端可以被vxi-gui工具扫描到和读写操作
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <rpc/rpc.h>
#include "vxi11_rpc.h" // VXI-11协议定义头文件,需自行生成或获取// 设备信息结构体
typedef struct {char *device_name;char *vendor;char *model;char *serial_number;char *firmware_version;
} VXI11_Device;// 全局设备列表
static VXI11_Device vxi11_devices[] = {{ "inst0", "YourCompany", "Model123", "SN456", "1.0.0" },{ NULL } // 列表结束标记
};// 设备链接结构体
typedef struct {int link_id;char *device_name;int timeout;int lock_type;int lock_id;
} VXI11_Link;// 全局链接列表
#define MAX_LINKS 10
static VXI11_Link vxi11_links[MAX_LINKS];
static int next_link_id = 1;// 创建链接 - 实现Create_Link
Create_LinkResp * create_link_1_svc(Create_LinkParms *parms, struct svc_req *rqstp) {static Create_LinkResp resp;int i, link_index = -1;// 初始化响应memset(&resp, 0, sizeof(resp));// 查找可用的链接槽for (i = 0; i < MAX_LINKS; i++) {if (vxi11_links[i].device_name == NULL) {link_index = i;break;}}if (link_index == -1) {// 没有可用链接槽resp.error = ERR_MAXLINK;return &resp;}// 查找请求的设备for (i = 0; vxi11_devices[i].device_name != NULL; i++) {if (strcmp(parms->device, vxi11_devices[i].device_name) == 0) {break;}}if (vxi11_devices[i].device_name == NULL) {// 设备不存在resp.error = ERR_DEVICE_NOT_ACCESSIBLE;return &resp;}// 创建新链接vxi11_links[link_index].link_id = next_link_id++;vxi11_links[link_index].device_name = strdup(parms->device);vxi11_links[link_index].timeout = parms->lock_timeout;vxi11_links[link_index].lock_type = parms->lock_device;vxi11_links[link_index].lock_id = 0; // 简化实现,未使用锁// 填充响应resp.error = 0; // 成功resp.link = vxi11_links[link_index].link_id;resp.maxRecvSize = 1024 * 1024; // 最大接收大小为1MBreturn &resp;
}// 设备写入 - 实现Device_Write
Device_WriteResp * device_write_1_svc(Device_WriteParms *parms, struct svc_req *rqstp) {static Device_WriteResp resp;int i;// 初始化响应memset(&resp, 0, sizeof(resp));// 查找链接for (i = 0; i < MAX_LINKS; i++) {if (vxi11_links[i].device_name != NULL && vxi11_links[i].link_id == parms->link) {break;}}if (i == MAX_LINKS) {resp.error = ERR_BADLINK;return &resp;}if(strcmp(params.data.data_val,"*idn?")==0) //读取接收到的数据{}// 这里实现实际的设备写入逻辑// 示例代码仅打印写入的数据printf("Writing %d bytes to device %s\n", parms->data.data_len, vxi11_links[i].device_name);// 模拟写入成功resp.error = 0;resp.size = parms->data.data_len;return &resp;
}// 设备读取 - 实现Device_Read
Device_ReadResp * device_read_1_svc(Device_ReadParms *parms, struct svc_req *rqstp) {static Device_ReadResp resp;int i;// 初始化响应memset(&resp, 0, sizeof(resp));// 查找链接for (i = 0; i < MAX_LINKS; i++) {if (vxi11_links[i].device_name != NULL && vxi11_links[i].link_id == parms->link) {break;}}if (i == MAX_LINKS) {resp.error = ERR_BADLINK;return &resp;}// 这里实现实际的设备读取逻辑// 示例代码返回一个固定的响应static char response_data[] = "MyVXI11Device Model123 SN123456 1.0.0"; //回复固定字符int response_size = strlen(response_data);// 调整响应大小以适应请求的最大字节数if (response_size > parms->requestSize) {response_size = parms->requestSize;}// 填充响应数据resp.error = 0;resp.reason = 0; // 正常结束resp.data.data_len = response_size;resp.data.data_val = (char *)malloc(response_size);memcpy(resp.data.data_val, response_data, response_size);return &resp;
}// 设备断开链接 - 实现destroy_link_1_svc
Device_Error * destroy_link_1_svc(Device_Link *parms, struct svc_req *rqstp) {static Device_Error resp;int i;// 初始化响应memset(&resp, 0, sizeof(resp));// 查找链接for (i = 0; i < MAX_LINKS; i++) {if (vxi11_links[i].device_name != NULL && vxi11_links[i].link_id == parms->link) {break;}}if (i == MAX_LINKS) {resp.error = ERR_BADLINK;return &resp;}// 释放链接资源free(vxi11_links[i].device_name);memset(&vxi11_links[i], 0, sizeof(VXI11_Link));// 返回成功resp.error = 0;return &resp;
}int vxi11_server (void)
{register SVCXPRT *transp;// 清除链接列表memset(vxi11_links, 0, sizeof(vxi11_links));pmap_unset (DEVICE_ASYNC, DEVICE_ASYNC_VERSION);pmap_unset (DEVICE_CORE, DEVICE_CORE_VERSION);pmap_unset (DEVICE_INTR, DEVICE_INTR_VERSION);transp = svctcp_create(RPC_ANYSOCK, 0, 0);if (transp == NULL) {fprintf (stderr, "%s", "cannot create tcp service.");return 0;}if (!svc_register(transp, DEVICE_ASYNC, DEVICE_ASYNC_VERSION, device_async_1, IPPROTO_TCP)) {fprintf (stderr, "%s", "unable to register (DEVICE_ASYNC, DEVICE_ASYNC_VERSION, tcp).");return 0;}if (!svc_register(transp, DEVICE_CORE, DEVICE_CORE_VERSION, device_core_1, IPPROTO_TCP)) {fprintf (stderr, "%s", "unable to register (DEVICE_CORE, DEVICE_CORE_VERSION, tcp).");return 0;}if (!svc_register(transp, DEVICE_INTR, DEVICE_INTR_VERSION, device_intr_1, IPPROTO_TCP)) {fprintf (stderr, "%s", "unable to register (DEVICE_INTR, DEVICE_INTR_VERSION, tcp).");return 0;}printf("VXI-11服务已启动,等待连接...\n");// 进入服务循环svc_run();// 不会执行到这里fprintf(stderr, "svc_run() 意外返回\n");return 1;/* NOTREACHED */
}// 主函数 - 注册服务并启动
int main(int argc, char **argv) {register SVCXPRT *transp;// 清除链接列表memset(vxi11_links, 0, sizeof(vxi11_links));// 注册PMAP服务pmap_unset(DEVICE_CORE, DEVICE_CORE_VERSION);pmap_unset(DEVICE_CLUSTER, DEVICE_CLUSTER_VERSION);// 创建TCP传输层transp = svctcp_create(RPC_ANYSOCK, 0, 0);if (transp == NULL) {fprintf(stderr, "无法创建TCP传输层\n");return 1;}// 注册服务到TCP传输层if (!svc_register(transp, DEVICE_CORE, DEVICE_CORE_VERSION, device_core_1, IPPROTO_TCP)) {fprintf(stderr, "无法注册DEVICE_CORE服务到TCP\n");return 1;}if (!svc_register(transp, DEVICE_CLUSTER, DEVICE_CLUSTER_VERSION, device_cluster_1, IPPROTO_TCP)) {fprintf(stderr, "无法注册DEVICE_CLUSTER服务到TCP\n");return 1;}printf("VXI-11服务已启动,等待连接...\n");// 进入服务循环svc_run();// 不会执行到这里fprintf(stderr, "svc_run() 意外返回\n");return 1;
}
四:编译生成的代码烧录到板卡
根文件系统需要提供avahi服务(用于支持扫描识别)和rpcbind(主要用于提供rpc服务,运行的就是udp 111端口)
如果没有rpcbind,设备程序运行后,会报 "cannot create udp service.",并且即使开启了rpcbind也会报 "unable to register (DEVICE_ASYNC, DEVICE_ASYNC_VERSION, udp).",实际上的使用只需要tcp服务就可以,因此可以将上述main函数udp注册的接口屏蔽了即可。
五:成功运行测试工具测试:
点击lxi-gui工具的search ,如下效果:
六:仪器实现web服务:
如果仪器根据vxi11协议实现了web端服务,可以看到使用lxi discover tool工具扫描的时候,可以实现如下效果:
双击描述可以打开web界面。
七:lxi服务端口和标准
回应lxi discover tool信息的http格式可以从官方git文档中找到:
GitHub - LxiStandard/LxiStandard.github.io 并参考内部如下文档LxiStandard.github.io/schemasIN/InstrumentIdentification/rs-sample-identification-1.0.xml at main · LxiStandard/LxiStandard.github.io · GitHub
调试技巧可以使用wireshark查看服务通信时候的数据通信
注意:
其中lxi discover tool扫描接口是通过tcp->http的方式实现的,lxi-gui是通过基于tcp的rpc服务实现的