CAPL语言的.can
文件的搭建
image.png
创建一个Nodes文件夹,把我们的.can文件存储到这;
这样创建的这个capl文件,是在工程运行后,如果触发了某些条件,会运行里面的某些功能代码;所以说capl是一门事件驱动型语言;
CAPL语言中的一些基本数据类型
int age;
int num = 30;
float tall = 1.72;
const float myPI = 3.14;
//枚举,默认值是 0, 可以设置
enum Gender {
MALE = 1,
FEMALE
};
//结构体
struct Humen{
char name[100];
byte age;
float tall;
enum Gender sex;
};
struct point{
int x;
int y;
};
struct point mypoint1 = {34,56};
struct point mypoint2 = {
x=34,
y=56
};
//声明一个毫秒级定时器, 需要绑定事件,如果需要开启和关闭;
msTimer t1;
msTimer t2;
msTimer t3;//报文的指定发送次数
int t3count = 0;
int t1Count = 0; //执行的次数
//这个是秒级的 timer t;
int i = 0;
int j = 0;
//定义AEBlog日志是否开启记录;
int LoggingAEB_Start = 0;
int EngineMsgCount = 0;//记录引擎的报文触发次数;
int LoggingHeadLightStarted = 0; //车灯的当前状态记录
char LoggingHeadLight[50] = "LoggingHeadLight";
int currMaxSpeed = 0;//当前的发动机最高转速;
int speedFileHandle = 0;//存储转速的文件句柄;
下面是对定义的数据类型的一些使用
on key 'w'{
byte count1;
byte count2;
enum Gender zsySex = FEMALE;
// 定义Position类型的同时,声明一个Position变量veh1,并赋值为Left
enum Position { Front=1, Rear=2, Left=3, Right=4} veh1 = Left;
// 使用Position声明一个变量veh2,并赋值为Front
enum Position veh2 = Front;
char gender = 'F'; //char在C语言中占一个字节
char nameStr[5] = "ABCD"; //还有一个隐藏的/0,所以最多4个字符
char nameStr2[100] = "zhaungshaoyun"; //这个就是一般的字符串的写法,C中没有字符串的定义,只有字符数组;
char numStr1 = 'B';
int num2 = 2;
char numStr3 ;
//声明数组Array
int intArray[10] = {1,2,3,4,5};
//这个是字符数组,但是不是字符串
char strArray[10] = {'a','b','c','d','a'};
// name2是字符串的字符数组,必须以'\0'结尾
char name2[6] = {'S', 'm', 'i', 't', 'h', '\0'};
//二维数组
int scores[2][3] = {
{1,2,3},
{4,5,6}
};
char currTime[100];
//计算字符串,数组的长度
write("%d-----%d------%d------%d",elCount(nameStr),elCount(nameStr2),elCount(strArray),elCount(scores));
//5--------100----------10------------2 ,二维的是计算行,列应该要明确固定写死。
numStr3 = numStr1+'!'; // 字符可以进行运算,因为有对应的ASCII
// B = 66 ! = 33 , = 99 是小写的c
write("字符相加:%c , %c",numStr3 , (char)(numStr1 + num2) );
write("%c",gender);
write("%d",zsySex);
count1 = 5;
count2 = -5;
write("byte占一个字节,超出范围会截取:%d , %d",count1,count2);
//随机数 [0;100]
writeDbgLevel(5,"随机数:%d",random(100));
write("7.32四舍无入:%d", _round(7.32));
getLocalTimeString(currTime);
write("当前时间:%s", currTime);
write("30度角的正弦值为:%.2f", sin(30 * pi / 180));
}
//结构体
On key 's'{
struct Humen Jame = {
name = "jame",
age = 18,
tall = 1.77,
sex = FEMALE
};
struct Humen mery;
// mery.name = "jame";
//CAPL中是没有 字符串 这种明确的数据类型的,结构中的name的数据类型是char[],也就是char的数组。
//数组一旦初始化,不能重新整体赋值,只能访问其中的元素;
//所以上面重新进行赋值的操作是行不通的,只能对数组单个元素操作,比如mery.name[0] = 'j';
mery.age = 19;
mery.tall = 1.53;
mery.sex = MALE;
write("name = %s",Jame.name);
write("name = %s",mery.name);
}
下面是定时器的使用
//定时器的启动
on key 'a'{
setTimer(t1,200);
setTimer(t2,100);
}
//定时器事件绑定
On timer t1{
write("time定时器启动了");
t1Count++;
if(t1Count < 10){
setTimer(t1,200);
}
}
On timer t2{
sendDoorStateMsg();
}
void sendDoorStateMsg(){
message DoorState msg;
msg.LeftDoorState = i%2;
i++;
output(msg);
write("发送了一帧报文");
//开始定时器
setTimer(t2,100);
//取消定时器
//cancelTimer(t2);
}
下面是定义一个报文
//报文
On key 'b'{
message DoorState msg1; //DoorState的 ID = 0x666
message 0x666 msg2;
// 访问报文变量msg1的属性
write("报文的ID:%#x", msg1.id);
write("报文的名称:%s", msg1.name);
write("报文的DLC(数据长度):%d", msg1.dlc);
write("报文的ID:%#x", msg2.id);
write("报文的名称:%s", msg2.name);
write("报文的DLC(数据长度):%d", msg2.dlc);
//配置报文的信号值,使用报文变量配置,来着dbc文件;
msg1.LeftDoorState = 1;
msg1.RightDoorState = 0;
msg1.LeftWindowPosition = 50;
msg1.RightWindowPosition = 73;
// 使用output函数向总线上输出报文msg1
output(msg1);
}
在进行CAPL的报文创建时
image.png
这个DoorState 的报文是车门的报文,来着我们加载的dbc文件:
image.png
下面是log输出的方式和函数的调用方法
On key 'c'{
// 4 代表输出的位置是 Test page ,0123是信息类型
writeLineEx(4,0,"一个成功的输出");
writeLineEx(4,1,"一个信息的输出");
writeLineEx(4,2,"一个警告的输出");
writeLineEx(4,3,"一个错误的输出");
write("配置了输出登记后的write输出");
writeDbgLevel(2,"按照等级输出");
add(1,3);
}
//函数
void add(int a,int b){
writeDbgLevel(5,"%d",a+b);
}
long addBig(int a,int b){
return a+b;
}
//以报文为参数的函数
void sendMsg(message * msg,int count){
//执行一些操作,然后可以发送报文
//.......
int i;
for( i=0;i<count ;i++){
}
output(msg);
}
下面是程序的生命周期函数
//生命周期
//开始启动时调用
On start {
writeDbgLevel(5,"启动");
//配置输出的等级,一般是全局配置的,只对writeDbgLevel方法生效;
setWriteDbgLevel(5);
//配置log日志的输出路径和文件名
setLogFileName("LoggingAEB","ZSYLogging/{LoggingBlock}_{MeasurementStart}.asc");
speedFileHandle = openFileWrite("test/EngineSpeedReport.txt",2);
if(speedFileHandle){
write("打开存储转速文本成功");
}else{
write("打开存储转速文本失败");
}
}
//结束时调用
On stopMeasurement{
writeDbgLevel(5,"结束");
//关闭文件
if(speedFileHandle){
fileClose(speedFileHandle);
}
}
//结束程序
On key 'd'{
writeDbgLevel(5,"结束程序");
stop();
}
通过CAPL控制 LoggingAEB 日志模块的启动和停止
On key 'e'{
//log日志的开启和停止,数据录入到文件中,不会再次创建新的文件,是一个asc文件;
//如果关闭了刚才重新开始,会生成一个新的日志文件;
if(LoggingAEB_Start == 0){
LoggingAEB_Start = 1;
// 启动名为LoggingAEB的日志模块的记录
startLogging("LoggingAEB");
}else{
LoggingAEB_Start = 0;
stopLogging("LoggingAEB");
}
}
如果需要CAPL控制日志的记录,需要如下操作,代码才能生效:
image.png
文件的读写操作
//文件的 数据写入操作,返回文件句柄
int writeSomeMsgToFile(char filePath[]){
int fh;
char name[50] = "小庄";
int age = 18;
float tall = 1.73;
char mutStr[100]; //定义一个可变字符串,用于字符串的拼接
// 以覆盖写入文本字符的模式(0)打开文件,返回代表这个文件的句柄
// 注意:如果使用模式2,则表示追加写入
fh = openFileWrite(filePath,0);
if(fh){
//打开文件成功,进行写入操作
//1.字符串拼接
snprintf(mutStr, elCount(mutStr), "我是%s,今年%d岁,我的身高%.2f米。\n", name, age, tall);
//2.写入数据
filePutString(mutStr,elCount(mutStr),fh);
filePutString("XXXXXXXXX",100,fh);
filePutString("OOOOOO",100,fh);
//3.关闭文件
fileClose(fh);
}
return fh;
}
//文件的 数据读取操作,返回文件句柄
int readSomeMsgToFile(char filePath[]){
int fh;//文件句柄
char msg[100];
fh = openFileRead(filePath,0);
if(fh){
while(fileGetString(msg,elCount(msg),fh)){
write("读取的每一行的内容:%s",msg);
}
fileClose(fh);
}
return fh;
}
//文件的读写
On key 'f'{
/*
这里如果是直接读取文件内已经存在的信息,
没办法设置读取的encode,导致可能乱码,这
个应该修改txt的文本格式;
*/
char filePath[50] = "test/zsyMsg.txt"; //文件地址,必须带文件后缀名
if(writeSomeMsgToFile(filePath)){
writeDbgLevel(5,"写入成功");
}else{
writeDbgLevel(5,"写入失败");
}
if(readSomeMsgToFile(filePath)){
writeDbgLevel(5,"读取成功");
}else{
writeDbgLevel(5,"读失败");
}
}
image.png
通过timer来实现多次执行某一件事情
On timer t3{
t3count++;
writeDbgLevel(5,"执行 了%d次",t3count); //可以把这个log换成报文发送
if(t3count < 10){
setTimer(t3,50);
}
}
On key 'g'{
setTimer(t3,50);
}
报文的监测
// 当总线上监测到了EngineState报文的时候触发
On message EngineState {
write("第%d次检测到%s[%#x]报文,报文的传输方向[%d], 引擎的开关[%d], 引擎的转速[%d], 报文的长度[%d],报文的字节[%#X]",
++EngineMsgCount, this.name, this.id, this.dir, this.OnOff, this.EngineSpeed,this.dlc, this.word(0));
}
// 监测LightState报文的出现(每次监测到报文出现,就触发一次
On message LightState{
//writeDbgLevel(5,"监测到LightState报文");
if(this.HeadLight == 1 && LoggingHeadLightStarted == 0){
//已开灯 + 未记录日志
startLogging(LoggingHeadLight);
LoggingHeadLightStarted = 1;
writeDbgLevel(1, "开始记录LoggingHeadLight的日志");
}else if(this.HeadLight == 0 && LoggingHeadLightStarted == 1){
stopLogging(LoggingHeadLight);
LoggingHeadLightStarted = 0;
writeDbgLevel(1, "停止记录LoggingHeadLight的日志");
}
}
报文中的信号的监测 (发生了改变,比如从0到1了,才会触发)
//监测报文下面的信号的变化
On signal HeadLight {
write("信号HeadLight发生了变化,信号的值为:%d", (int)this);
write("引擎的开关%d",(int)$OnOff);
write("引擎的转速%d",(int)$EngineSpeed);
write("引擎的值%d",(int)$EngineSpeed);
write("引擎的物理量%d",(int)$EngineSpeed.phys);
write("引擎的原始值%d",(int)$EngineSpeed.raw);
//这个方法直接监测信号值控制开启关闭,比上面的更直接;
if(this == 1){
startLogging(LoggingHeadLight);
writeDbgLevel(1, "开始记录LoggingHeadLight的日志");
}else if(this == 0){
stopLogging(LoggingHeadLight);
writeDbgLevel(1, "停止记录LoggingHeadLight的日志");
}
}
监测信号值的更新,其实就是报文上报收到了,就触发的意思
On signal_update HeadLight{
//这个信号50毫秒上报一次,所以这个打印50毫秒触发一次;
write("接收到了头灯的信号:%d",(int)this);
}
信号变化的简单使用示例:
需求:检测EngineState报文中的信号的变化,每次变化判定是否是本次乘车过程中的最高转速;
EngineState报文中,有开关信号OnOff,和转速信号EngineSpeed;
如果是最高转速,写入报告文件;
On start {
speedFileHandle = openFileWrite("test/EngineSpeedReport.txt",2);
if(speedFileHandle){
write("打开存储转速文本成功");
}else{
write("打开存储转速文本失败");
}
}
//结束时调用
On stopMeasurement{
writeDbgLevel(5,"结束");
//关闭文件
if(speedFileHandle){
fileClose(speedFileHandle);
}
}
On signal EngineSpeed
{
char info[200]; //存储的转速信息
char currentTime[200];//当前时间
char pattern[100] = "[%s] 出现了目前的最高转速 [%d]\n"; //一个待用的拼接字符串
if(this > currMaxSpeed){
getLocalTimeString(currentTime);
currMaxSpeed = (int)this;
snprintf(info,elCount(info),pattern,currentTime,currMaxSpeed);
//文件的打开和关闭操作,放在了程序的启动和关闭上了
filePutString(info,elCount(info),speedFileHandle);
}
}