本章目标
- 创建专栏的CANoe工程;
- 熟悉CANoe提供的常见接口;
- 开发CANoe library的基础功能。
前言
在介绍完behave之后,我们来讨论一下CANoe库的开发。
由于在实际测试中会涉及到大量CANoe的操作,因此,我们需要利用CANoe提供的COM接口来开发一个方便易用的库。
COM接口可以简单理解为CANoe提供的基础接口,其他程序可以通过这些接口控制CANoe。
CANoe准备
安装
那么在开始这项任务之前,首先需要做一些准备工作:完成CANoe的安装。如果你还没有安装CANoe,请参考以下链接进行安装:
https://blog.youkuaiyun.com/weixin_48143996/article/details/129486663
创建工程
以下是创建工程的详细步骤:
- 创建工程
首先,我们需要创建一个新的CANoe工程。在CANoe打开的主界面上,点击“File”按钮,并按照下图中所示的步骤进行设置。
![]](https://img-blog.csdnimg.cn/e484a4c23f3e488c92c31e5fc701f9b2.png)
- 保存工程
在创建工程后,需要将其保存到PyCharm工程目录下。在CANoe中,点击“Save”按钮并按照下图中所示的步骤进行设置。
- 添加通讯arxml
接下来,我们需要将PowerTrain.arxml文件放置在database文件夹中,如下图所示。
可以在安装的CANoe Sample configuration中找到PowerTrain.arxml,参考路径:C:\Users\Public\Documents\Vector\CANoe\Sample Configurations 16.4.4\CAN\CANSystemDemo_Autosar\Databases
然后,在CANoe工程中导入该arxml文件。
4. 配置CAN1,CAN2为CAN FD
由于现在普遍使用CAN FD,这里将CAN1和CAN2配置成CAN FD。在CANoe中,按照下图所示的步骤进行设置。
- 添加系统变量
需要添加两个系统变量:send_eng_msg和eng_speed。在CANoe中,按照下图所示的步骤创建这些变量。
- 添加节点
接下来,在CAN1上添加一个节点。请注意,这里是CAN1,而不是CAN2。在CANoe中,按照下图所示的步骤进行设置。
- 编写节点CAPL
CAPL是一种类C的编程语言,大家现在不用深究,看代码中的注释,明白大概是干什么就行。
最后,我们需要使用CAPL编写节点代码。在打开的CAPL browser(如果没有打开就再点击上图中第三步的按键)中,按照下图所示的步骤进行设置,并输入以下代码:
/*@!Encoding:936*/
includes
{
}
variables
{
msTimer timer500; // 声明的这个CAPL文件中的全局变量,msTimer类型(毫秒计时器)
}
on start
{
}
on timer timer500 // 事件触发,当timer500计时完成会被触发,timer500的设置在下面
{
// 这里的内容,每次timer500被触发后都会执行一遍
int spd; // 声明局部变量spd,存放系统变量eng_speed的值
pdu EngineData eng_pdu; // 把arxml中的pdu“EngineData”声明为变量eng_pdu,便于后面设置信号,发送
spd = sysGetVariableInt(sysvar::pyCANoe::eng_speed); // 获取系统变量的值
eng_pdu.EngSpeed = spd; // 信号赋值
triggerPDU(eng_pdu); // 触发PDU发送,把PDU可以当作message
}
on sysvar pyCANoe::send_eng_msg // 事件触发,当send_eng_msg的值“改变”时,会触发该部分程序
{
int val;
val = sysGetVariableInt(sysvar::pyCANoe::send_eng_msg); // 获取系统变量的值
if (val == 0) { // 根据系统变量的值,确定是要开启timer,还是关闭timer
write("stop sending engine message");
cancelTimer(timer500); // 关闭timer,也就是停止报文发送,因为报文是由这个timer触发的
}
else if (val == 1) {
write("start sending engine message");
setTimerCyclic(timer500, 500); // 设置循环timer,timer会不断循环计时500ms,到了就触发。
}
}
on sysvar pyCANoe::eng_speed // 这部分只是用来输出系统变量的值的变化情况
{
write("sysvar eng_speed set to %d", @pyCANoe::eng_speed);
}
仿真验证
在开发过程中,验证是一个至关重要的步骤,而仿真验证是其中一种非常有效的方法,它可以确保每个部件都能按照预期工作,并且在整个系统组装之前就进行了测试。
在进行仿真验证时,我们首先会验证每个部件的功能是否符合预期。例如这里我们我们先验证写好的CAPL能否通过直接改变系统变量来发送报文,如果验证成功,那么说明CAPL这部分的功能是按照我们的预期执行的。接下来,如果使用Python程序执行相同操作时出现了问题,那么大概率就是Python程序的问题。这样的方法使得我们可以快速排除问题,并且避免在后期的开发中浪费时间和资源。
具体来说,我们可以按照下图所示的顺序开启仿真,并更改eng_speed(改变信号的值)和send_eng_msg(控制报文是否发送)变量的值,观察Trace窗口是否有相关变化。
Python控制CANoe基础接口
运行CANoe软件
下面的代码会检查是否有CANoe在运行,如果没有,那么就会打开一个CANoe,如果已经CANoe运行,程序就会连接上已有的CANoe。代码执行成功之后,返回的是"Application"对象。
import time,os
from win32com.client import *
from win32api import Sleep
import types
app = DispatchEx("CANoe.Application")
打开configuration
根据传入的文件路径,打开相应的配置文件。
cfgPath = "D:\demo.cfg"
app.Open(cfgPath)
启动仿真
获取“Measurement”对象并启动仿真,为了等待仿真启动,加入了3秒的等待时间。
meas = app.Measurement
meas.Start()
time.sleep(3)
读写信号
获取"Bus"对象,注意这里的传入参数并不是网络名称,而是总线类型,即便有多个CAN网络,也是通过下面的方式获取,传入参数可以有其他值,例如:“LIN”,“FlexRay”,“Ethernet”。
can_bus = app.GetBus('CAN')
基于"Bus"对象,传入channel,报文名称以及信号名称可以获取相应的"Signal"对象,然后读取或者写入信号的值。
但有时当写入信号的值时,可能会看到CANoe软件报错,具体报错内容不记得了。这个问题通常是由于采用的IG模块或者自动从通讯数据库读取初始值后自动发送报文,且没有配置interaction layer所导致的。我没有深入研究这个问题,如果遇到这种情况,可以咨询Vector。
平时我习惯在CAPL中定义报文的发送,并通过系统变量来更改特定信号的值。然后外部程序改变系统变量的值就可以改变信号的值。
另外,设置信号值的时候,可能会遇到数据类型不匹配的情况,需要注意一下。
sig1 = can_bus.GetSignal(0,'EngineData','EngSpeed')
# 读信号的值,physical value
print("signal value:",sig1.Value)
# 读信号的值,raw value
print("signal raw value:",sig1.RawValue)
# 写信号的值
sig1.Value = 1000
读写系统变量
程序可以根据名称获取"namespace"对象,通过变量名称获取"Variable"对象,然后读、写系统变量的值,写变量的值的时候也需要注意数据类型,namespace和变量名称请查看下面的图。
ns_name = "can_py_inter"
var_name = "voltage_value"
ns_obj = app.System.Namespaces(ns_name)
sysvar_obj = sys_ns.Variables(var_name)
# 读系统变量的值
sys_value = sysvar_obj.Value
# 写系统变量的值
sysvar_obj.Value = 12
COM接口使用
在使用COM接口时,我们经常会面临一些困惑。例如,我们如何知道各个对象具有哪些成员和方法?如果你遇到这类问题,可以参考下面文章中的“vector help文档阅读部分”进行解决。
https://zhuanlan.zhihu.com/p/604627246
Library开发
库安装
首先需要安装pywin32库。具体操作请参见以下截图:
代码详解
完整的代码放在后面了,这部分仔细介绍各部分代码。
init方法
此外,在代码编写过程中,我们需要仔细理解每个部分的含义。其中,init方法是一个特殊函数,当您初始化一个类的对象时,该方法将被执行。在本例中,我们定义了一个名为CANoeInterface的类,当您执行以下代码时,init方法将被调用:
init方法(方法就是函数,只不过在面向对象的编程语言中,通常会把类中定义的函数称为这个类的方法,专栏中会混用这两个名词)是当你初始化一个类(类是class的翻译,就是类型的意思)的对象时会执行的内容,控制CANoe的类的名字是CANoeInterface,当执行下面这一句代码时就会调用init方法。
n_canoe = CANoeInterface(project_folder + config_name)
以下是完整的代码,并包含了对各个部分的详尽注释,请仔细查看。
class CANoeInterface:
def __init__(self, a_config_name):
self.config_name = a_config_name # 把传入参数赋给类中的一个变量,这样类中的各个方法都可以使用
self.app = DispatchEx("CANoe.Application")
# 1. 这一步会建立python程序与CANoe软件之间的连接,如果CANoe没有打开,这一句代码会打开CANoe,
# 如果已经打开了,则会直接连接这个已经打开了的CANoe;
# 2. 如果你的电脑上安装了不同版本的CANoe,这里默认会打开最后安装的CANoe,因为最后安装的CANoe会更新
# 注册表中的某个值,程序会根据注册表的这个值来确定要打开的CANoe;例如你电脑上先安装了CANoe 11,后面又
# 安装了CANoe 16,此时运行程序会自动打开CANoe 16,如果你希望Python控制CANoe 11,有两个方法:1) 提前
# 打开CANoe 11,参考1中的说明,因为提前打开了CANoe 11,所以程序会直接与CANoe 11建立连接,2) CANoe安装
# 目录下有个程序可以更新注册表,具体的内容我不记得了,后面找到信息了补上。
# 3. 还有一个方法是Dispatch(),也可以打开CANoe,好像就是不论有没有已经打开的CANoe,都会重新打开一个,我
# 没有使用这个方法。
self.ver = self.app.Version # 获取CANoe的版本
self.running = lambda: self.app.Measurement.Running
# lambda是Python中的匿名函数(就是没有名字的函数,因为有时候程序员觉得给函数取名字也很费劲)
# 这里self.running是一个函数,后面每一次使用self.running(),就相当于调用
# return self.app.Measurement.Running,即CANoe仿真是否在运行。
# 不能去掉lambda,直接使用self.running = self.app.Measurement.Running,因为这样只是
# 获取现在的值,后面值就不会改变了。
self.app.Configuration.Modified = False
# 这一步是标记configuration没有做过修改,因为操作CANoe的时候(调整窗口,增加观测量等),经常会导致
# CANoe configuration的变更,如下图所示,config名字右上角带星号说明有变更了,而程序在遇到变更过的
# configuration时,直接打开别的config会报错(人在此时操作,打开别的config时,会收到提示是否要保存
# 这个config)。很多时候这种变更是不需要保存的,所以程序这里直接标记为没有变更,后续的操作就能正常执行。
self.stop_measurement()
# 这一步操作的原因和上一步类似,因为有时候你操作的CANoe可能正处于运行中(例如上一次测试异常中断,但是
# CANoe没有停),所以需要判断状态并停止运行。
print(f"Open config: {self.config_name}")
self.app.Open(self.config_name) # 打开你需要的config
Measurement start/stop方法
def start_measurement(self):
# 开始仿真的函数
if not self.running():
# 和上面解释的一样,self.running是一个函数,这里调用函数来判断measurement是否在运行
# 当measurement没有运行时,执行下面代码
print("Starting measurement")
self.app.Measurement.Start() # 控制开始measurement
sleep(2) # 开始仿真需要一些时间,这里做一个延时,避免后续的操作在measurement还没有开始就执行
def stop_measurement(self):
# 同上,停止measurement的代码
if self.running():
print("Stopping measurement")
self.app.Measurement.Stop()
sleep(2)
def close_canoe(self):
# 关闭CANoe程序的代码;实际测试时通常不执行
# 两个原因:
# 1. CANoe打开比较费时,
# 2. 测试完成之后CANoe中还保存了测试的trace,方便查看
if self.app is not None:
print("Close CANoe")
self.app.Quit()
self.app = None
get/set system variable
def get_system_variable(self, sysvar_full_name):
# sysvar_full_name: py_CANoe::eng_speed
tokens = sysvar_full_name.split("::")
namespace = self.app.System.Namespaces(tokens[0])
# tokens[0]是py_CANoe,即namespace的名字
var_value = namespace.Variables(tokens[1]).Value
# tokens[1]是eng_speed,即系统变量的名字
return var_value
def set_system_variable(self, sysvar_full_name, value):
# 和get_sys_var相同
tokens = sysvar_full_name.split("::")
namespace = self.app.System.Namespaces(tokens[0])
sys_value = namespace.Variables(tokens[1])
if isinstance(sys_value.Value, int):
# 这里的判断是确定该系统变量的类型,并进行相应的转型,我一般是使用int,所以没有尝试别的数据类型
# 如果类型不匹配会导致赋值失败,且Python不会报错。
set_value = int(value)
else:
set_value = float(value)
sys_value.Value = set_value
get/set signal
这里set_signal_value需要配置interaction layer,我一直没有弄,有些问题,后续补上。
def get_signal_value(self, signal_full_name):
# signal_full_name: CAN::EngineData::EngSpeed
tokens = signal_full_name.split("::")
signal = self.app.GetBus(tokens[0]).GetSignal(0, tokens[1], tokens[2])
# tokens[0]: CAN(Bus类型), tokens[1]: EngineData(message或者pdu名称), tokens[2]: EngSpeed(信号名)
return signal.Value
def set_signal_value(self, signal_full_name, value):
tokens = signal_full_name.split("::")
signal = self.app.GetBus(tokens[0]).GetSignal(0, tokens[1], tokens[2])
signal.Value = float(value)
功能测试
测试代码
在完成功能开发后,为了确保程序的正确性,需要进行功能测试。我们通过添加以下代码到py_canoe.py文件中来进行测试:
if __name__ == '__main__':
# 这一句是判断是否执行的当前python文件,如果是在别的文件中执行,则不会运行以下代码。
# 只有在直接运行py_canoe.py的时候会运行下面的代码,别的文件中import py_canoe时不会运行。
project_folder = r"D:\11_projects\21_demo\09_auto_test"
config_name = r"\canoe_project\auto_test.cfg"
n_canoe = CANoeInterface(project_folder + config_name)
n_canoe.start_measurement()
send_msg_flag = "pyCANoe::send_eng_msg"
sys_var_eng_spd = "pyCANoe::eng_speed"
print("send_msg_flag value at start:", n_canoe.get_system_variable(send_msg_flag))
print("eng_spd value at start:", n_canoe.get_system_variable(sys_var_eng_spd))
n_canoe.set_system_variable(send_msg_flag, 1)
sleep(1)
n_canoe.set_system_variable(sys_var_eng_spd, 3000)
sleep(2)
print("send_msg_flag value after change:", n_canoe.get_system_variable(send_msg_flag))
print("eng_spd value after change:", n_canoe.get_system_variable(sys_var_eng_spd))
# set/get signal的部分后面再补
n_canoe.stop_measurement()
Python输出
在执行了上述代码后,Python会输出以下信息:
CANoe trace
在CANoe的Trace窗口,我们可以看出信号的变化情况:
总结
本章我们完成了Python控制CANoe的基本功能开发,在后续,还将继续扩展该库,以提供更多的功能。