SIMPACK与Python联合仿真——1. 预备知识

预备知识包括SIMPACK联合仿真、硬件在环基本特性、Linux系统基础操作、C语言编译与Python代码编写、TCP通信等前置知识。在Linux系统中安装SIMPACK请参见博客《在Linux/Ubuntu系统中安装SIMPACK2021x》

1. 引言

使用Python与SIMPACK进行数据交互,可以在Linux系统中通过进程间通信的方式(例如TCP通信等),将Python与编译后的SIMPACK Realtime C文件在本机上进行通信,实现联合仿真。

(当然,如果使用 Python - MATLAB - Simulink - SIMPACK 的数据链路实现SIMPACK与Python联合仿真也不是不行!不过由于本博客主要关注数据通信的实现,而不调用MATLAB等官方封装好的模块,因此不是本博客讨论的范围)

在Linux系统的SIMPACK安装目录中,spck_rt_example.c给出了调用SIMPACK Realtime APIs的示例程序。该C程序具有唤起SIMPACK Realtime仿真模块、获取u-Inputs和y-Outputs维度、输出仿真的真实世界用时等功能。spck_rt_example的位置如下:

.../Simpack-2021x/run/realtime/examples/spck_rt_example.c

进一步在上述C语言程序中补充与Python进行进程间通信的代码即可。

常见进程间通信的方式有:管道(Pipe)、消息队列(Message Queue)、信号(Signal)、套接字(Socket)、信号量(Semaphore)、TCP/UDP和共享内存等。考虑到联合仿真需要为Python与SIMPACK一步一步轮流执行,TCP通信具有的序列号、确认应答、重传机制等机制等保证了数据的顺序和不丢失,非常适合本场景。

SIMPACK Realtime给出的APIs也不是和SIMPACK计算核心程序直接通信,而是通过POSIX/UDP通信与SIMPACK计算核心通信,UDP通信用于硬件外设与跨软件仿真,POSIX用于本地计算机纯软件仿真。

由于SIMPACK Realtime模块设计的出发点是为了SIMPACK软件能够支持实体外设,例如方向盘、dSpace仿真目标机、CAN总线外设等,其传输的数据有较大传输丢失、延时的可能,如果直接与SIMPACK计算核心通信或使用TCP通信,将不可避免发生SIMPACK仿真等待一个“已经永远错过”的信号的情况从而导致联合仿真/实时仿真/硬件在环失败,这种连接硬件外设的情况应采用UDP通信,UDP通信同时也支持无硬件外设的软件仿真。

非硬件外设与SIMPACK Realtime模块通信时,例如本博客关注的SIMPACK与Python联合仿真,是通过进程间通信进行数据交互,包括Python与调用了SIMPACK Realtime APIs的C程序(以下简称C程序)之间的TCP通信,以及C程序与SIMPACK计算核心程序之间的POSIX/UDP通信。此时,SIMPACK与Python联合仿真的数据传输方式为

Python <-- TCP -->

调用了SIMPACK Realtime

APIs的C程序

<-- POSIX / UDP -->

SIMPACK

计算核心程序

其中,SIMPACK计算核心程序是用户无法访问与修改的,都需要通过C程序调用封装的APIs进行数据交互。

2. SIMPACK Realtime APIs

在realtime文件夹内,给出了spck_rt_example.c所需的头文件spck_rt_v1.h。列举了用于与SIMPACK实时求解器进行交互的接口函数如下:

#if !defined(SPCK_RT_API) || SPCK_RT_API < 2
#define SpckRtInitUDP          SpckRtInitUDP_V1
#define SpckRtInitPM           SpckRtInitPM_V1
#define SpckRtFinish           SpckRtFinish_V1
#define SpckRtExitSolver       SpckRtExitSolver_V1
#define SpckRtGetUYDim         SpckRtGetUYDim_V1
#define SpckRtGetModelName     SpckRtGetModelName_V1
#define SpckRtGetUInputName    SpckRtGetUInputName_V1
#define SpckRtGetYOutputName   SpckRtGetYOutputName_V1
#define SpckRtSetSubVarString  SpckRtSetSubVarString_V1
#define SpckRtGetSubVar        SpckRtGetSubVar_V1
#define SpckRtGetSubVarByIndex SpckRtGetSubVarByIndex_V1
#define SpckRtGetY             SpckRtGetY_V1
#define SpckRtSetU             SpckRtSetU_V1
#define SpckRtStart            SpckRtStart_V1
#define SpckRtAdvance          SpckRtAdvance_V1
#endif

可以看出,其中随着SIMPACK官方的版本迭代,又封装了一层有“_V1”后缀的函数。在C代码中使用的是不带“_V1”后缀的函数,实际上会调用含有“_V1”后缀的函数。后续代码中为了简化描述,统一使用不带“_V1”后缀的函数名称,两者是等价的。

各API函数功能分别如下:

  • SpckRtInitUDP:用于通过UDP通信初始化SIMPACK实时求解器。包括启动求解器(如果尚未运行)、加载给定模型、获取模型信息(维度信息,输入u-Inputs和和输出y-Outputs的名称)以及获取初始的y-Outputs值。

  • SpckRtInitPM:用于通过POSIX消息队列通信来初始化SIMPACK实时求解器。其功能和SpckRtInitUDP类似,但通信方式不同。

  • SpckRtFinish:关闭SIMPACK实时求解器。

  • SpckRtExitSolver:用于强制退出SIMPACK Realtime求解器。此函数可以代替SpckRtFinish调用,或者在其之后调用。

  • SpckRtGetUYDim:返回SIMPACK模型u-Inputs和y-Outputs的维度。

  • SpckRtGetModelName:返回当前已加载的模型的名称(文件名)。

  • SpckRtGetUInputName:返回给定u-Inputs的名称。

  • SpckRtGetYOutputName:返回给定y-Outputs的名称。

  • SpckRtSetSubVarString:用于设置SubVar的字符串值。如果现有的subvar有单位制(例如,米),则值字符串也必须包含一个兼容的单位制(任何长度单位制,如米、毫米、英寸等)。如果成功,返回0,否则返回非0(例如,invalid string with invalid dimension、subvar does not exist、not exported for realtime solver)。

  • SpckRtGetSubVar:返回具有给定名称的SubVar的值和注释。给定的SubVar必须通过实时求解器设置进行导出。如果找不到SubVar,则返回-1,值设置为0。

  • SpckRtGetSubVarByIndex:通过导出的索引(从0开始)返回SubVar的值和注释。如果索引无效,则返回-1。

  • SpckRtGetY:在SpckRtInit或SpckRtAdvance之后获取当前的y-输出值。如果先前改变了subvars或初始u-输入,这将触发新的RHS调用以更新所有的y-输出,前提是不处于实时模式。

  • SpckRtSetU:用于为下一个SpckRtAdvance复制给定的u-Inputs。

  • SpckRtStart:用于初始化实时积分器。

  • SpckRtAdvance:用于推进时间积分至少到时间t,起始于0。如果t大于积分器内部时间,积分器将执行配置步长和通信步长的时间步,直到至少达到t。如果t小于内部时间,则不执行积分步骤。不进行步长减小以精确达到t。此函数主要用于计算机内的仿真进行时间与现实世界时间的同步。

上述函数的使用在官方案例程序spck_rt_example.c中大多已经被示范性使用了,以上功能介绍可以更好地理解spck_rt_example.c代码。

3. SIMPACK Realtime中涉及的通信协议选择

SIMPACK Realtime默认使用POSIX消息队列(POSIX message queue)进行C程序与SIMPACK计算核心程序之间的进程间通信,可以切换为UDP通信。POSIX消息队列(POSIX message queue)和UDP(User Datagram Protocol)都是用于进程间通信(IPC)的方式,但是它们在通信模型、可靠性和效率等方面存在一些主要的区别。

  • 通信模型:POSIX消息队列是一种基于操作系统内核的进程间通信方式,它允许在同一台机器上的进程之间发送和接收数据。POSIX消息队列的通信是同步的,发送方在发送消息后将阻塞,直到接收方接收消息。相反,UDP是一种网络通信协议,它允许在不同机器上的进程之间进行通信。UDP的通信是异步的,发送方在发送数据包后不会等待确认,而是立即返回。

  • 可靠性:POSIX消息队列通信是可靠的,因为它保证消息将被正确地传递给接收方,除非发生像队列满等错误。另一方面,UDP是一种无连接的协议,它不保证数据包的到达,因此可能会丢失数据包。

  • 效率:POSIX消息队列由于只需要在同一台机器上的进程之间进行通信,因此在许多情况下比UDP更高效。然而,如果需要跨网络进行通信,那么UDP可能会更有效,尽管它可能需要应用层来处理丢包和错误恢复。

Python程序通过TCP通信协议与C程序(调用了SIMPACK Realtime APIs的C语言程序)需要考虑传输的特性,以进行传输加速。

  • TCP通信中需要禁用Nagle算法。在SIMPACK与Python进行每步的数据交互时,数据量都是几个到几十字节的“小数据”,但是需要大量多次发送与接收。这种需要频繁地发送少量数据,并且对于实时性有较高的要求(类似于多数实时游戏、VoIP通信、股票交易等),禁用Nagle算法可以减少延迟提高响应速度、提高网络吞吐量。Nagle算法会导致所谓的"Nagle延迟",Nagle算法试图组合多个小数据包以进行发送,会极大增加传输延迟。禁用Nagle算法让数据可以立即发送。实测禁用Nagle算法可以加快SIMPACK与Python之间的联合仿真数据的传输速度达到几百倍以上。
  • 设置套接字选项SO_REUSEADDR,使得在Python程序与C程序之间的TCP套接字关闭后立刻重新使用相同的端口。在正常情况下,当一个TCP连接关闭后,相同的端口会进入TIME_WAIT状态,并且需要等待一段时间(通常是几分钟)才能再次被使用。SO_REUSEADDR选项可以立刻重新使用相同的端口,而不需要等待这个端口从TIME_WAIT状态中恢复。由于SIMPACK与Python联合仿真需要多次频繁地启动和停止数据交互,需要在每次联合仿真结束后立刻重用之前关闭的端口,因此需要设置套接字选项SO_REUSEADDR。

下一节将建立SIMPACK模型、定义u-Inputs和y-Outputs、编写C/Python通信程序。

猜你喜欢

转载自blog.csdn.net/wenquantongxin/article/details/130696350
今日推荐