为实现一些相对复杂的系统开发或建模仿真,可以借助MathWorks® Simulink在图形化的工程界面中编辑和测试模型,或者使用MathWorks提供的现成实例与模块以零基础快速搭建所需的仿真环境。但若要让他人运行自己搭建的模型,并根据需要自行修改部分模型参数,或是发布为可供他人使用的仿真工具,则需要将模型打包为独立的可执行文件,并设计前端图形化界面以查看仿真输出结果、修改仿真参数。
模型打包的官方推荐做法是使用MATLAB®自带的Simulink Compiler工具,将编译好的 Simulink模型和用于设置、运行和分析仿真的 MATLAB代码一起打包,且可以附带由 MATLAB App设计工具设计的UI。该方法理论上实现不难,打包出的程序只需Runtime环境即可运行。但MATLAB App设计工具并非主流前端设计工具,需要学习成本,且制作的UI界面较为简单,不满足对前端界面要求较高的场景;此外,打包生成的代码可读性较差,网上现有的案例资源等又极少,一旦报错,很难定位错误,效率不高。
(资料图)
解决方案提出:
为解决该问题,就需要抛弃官方提供的MATLAB App设计工具和Simulink Compiler打包工具,自行设计前端界面、获取Simulink运行数据并打包发布。一种可行的方案是通过主流前端设计工具制作前端GUI界面,并通过外部会话访问MATLAB,以此控制Simulink模型运行并获取运行时的数据,如可以采用官方提供的用于Python的MATLAB Engine API(/help/matlab/matlab_external/),通过Python会话在后台调用MATLAB,将仿真实时状态和模型参数等显示在以PyQt制作的前端UI界面中,最后通过Pyinstaller打包发布。
该方案可以实现复杂的前端软件设计与Simulink模型仿真相结合,且体积不大、兼容性较好,但需安装有MATLAB环境而非Runtime,即发布给他人时除软件本体外还需附带完整MATLAB安装包或对方已安装MATLAB(若未安装则Simulink仿真部分不可用,但不影响软件其他功能)。本质上Simulink模型并没有被编译发布,只是调用了相关api接口使模型在后台仿真运行。官方文档(/help/matlab/)中也指出了:
“MATLAB Engine API for Python 可提供一个包,供 Python 将 MATLAB 作为计算引擎来调用。引擎应用程序需要已安装版本的 MATLAB;您无法在只有 MATLAB Runtime 的机器上运行 MATLAB Engine。”
目前,该方案在网上可供参考的资源不多,文中可能存在错误和可优化的部分。我使用的Simulink模型为纯电动汽车建模(EvReferenceApplication),模型细节可参考的我的另一篇文章(/article1)。前端GUI界面使用PyQt5设计,制作一个小型仿真系统软件界面来控制电动汽车模型仿真的启停以及部分模型参数的修改,同时实时读取并显示仿真输出数据。软件界面设计需在保证功能完整的前提下尽量高效美观。
安装用于 Python 的 MATLAB Engine API
安装前需要确认电脑中的Python和MATLAB版本是否兼容,可以查看官方文档页面。(/support/requirements/)
在MATLAB根目录\extern\engines\python文件夹中找到,即自动安装脚本文件;控制台进入该目录(若Python安装在虚拟环境则要在对应虚拟环境终端进入),输入指令python install,安装会自动执行。安装完成后MATLAB Engine会以Python包的形式存在,可以通过pip list查询。
除此之外,MATLAB R2022b及以上也可以通过pip安装,详情可参考官方文档。(/help/matlab/matlab_external/)
MATLAB Engine需要读取_文件中的地址信息来调用电脑中的MATLAB,该文件默认存放于Python包文件夹\matlab\engine中,且仅在安装时自动生成,记录本机MATLAB地址。若本机MATLAB地址改变、_文件丢失等都会导致寻址失败。因此不难想到,若要在打包发布时将MATLAB Engine以python包的形式包含,移植到他人电脑中运行,则必须手动修改_文件中的地址信息为对方电脑中的MATLAB地址,否则无法正常运行。
MATLAB Engine需要读取的文件地址信息(即_文件)如下:
修改python包代码
为保证MATLAB Engine在打包移植后仍能正常读取配置文件,获取目标系统环境信息及MATLAB地址,需对包中部分代码进行修改。
在本机Python包文件夹\matlab\engine\__init__.py中找到调用_文件所在代码:
_arch_filename = (_module_folder, "_")
将其改为:
_arch_filename = (('.'), "")
其中“”可以是任意文本文件,需自行编写代码,在程序运行时根据当前系统环境在程序运行根目录下生成该文件,从而实现MATLAB Engine包寻址。
在python中调用MATLAB Engine
官方文档(/help/matlab/matlab_external/)中给出了在python中调用MATLAB Engine的代码示例和说明可供参考,但大多较为简略,且实际编程时还需结合以编程方式运行仿真(/help/simulink/ug/),用代码代替Simulink® UI 与仿真进行交互,如果以前从未接触过Simulink的代码形式,可能遇到不少问题。本仿真系统中用到的部分代码见下表。
其中,本仿真系统用到的部分set_param中的parameter参数与对应的value值见下表。需注意表中所有参数均为字符串。
另外需要注意的是,在python中调用MATLAB Engine相当于访问外部会话,属于耗时操作,若写在主程序线程中将会导致线程阻塞,影响前端GUI界面运行,需要利用多线程通信来解决该问题。
打包发布
使用pyinstaller打包发布python程序,但pyinstaller无法正确识别MATLAB Engine包,此时需要手动复制该包;若需要打包为单一可执行程序(.exe),则需要配置hooks来添加指定包。
在主程序.py目录下新建文件夹,命名为hooks,其内新建py文件,内容如下:
from import collect_all
datas, binaries, hiddenimports = collect_all('matlab')
打包时额外添加指令:--additional-hooks-dir=hooks。如:
pyinstaller -F -w --additional-hooks-dir=hooks 主程序.py
即可正确打包为单一可执行程序。
由于B站专栏格式限制,部分内容或代码可能存在格式异常。原文内容请前往/article1