Python实现CAN报文转换工具教程


Posted in Python onMay 05, 2020

一、CAN报文简介

CAN是控制器局域网络(Controller Area Network, CAN)的简称,是由以研发和生产汽车电子产品著称的德国BOSCH公司开发的,并最终成为国际标准(ISO 11898),是国际上应用最广泛的现场总线之一。 在北美和西欧,CAN总线协议已经成为汽车计算机控制系统和嵌入式工业控制局域网的标准总线,并且拥有以CAN为底层协议专为大型货车和重工机械车辆设计的J1939协议。

CAN总线以报文为单位进行数据传送。CAN报文按照帧格式可分为标准帧和扩展帧,标准帧是具有11位标识符的CAN帧,扩展帧是具有29位标识符的CAN帧。按照帧类型可分为:1.从发送节点向其它节点发送数据;2.远程帧:向其它节点请求发送具有同一识别符的数据帧;3.错误帧:指明已检测到总线错误;4.过载帧:过载帧用以在数据帧(或远程帧)之间提供一附加的延时。共有两种编码格式:Intel格式和Motorola格式,在编码优缺点上,Motorola格式与Intel格式并没有孰优孰劣之分,只不过根据设计者的习惯,由用户自主选择罢了。当然,对于使用者来讲,在进行解析之前,就必须要知道编码的格式是哪一种,否则,就不能保证正确地解析信号的含义。以下就以8位字节编码方式的CAN总线信号为例,详细分析一下两者之间的区别。

Intel编码格式

当一个信号的数据长度不超过1个字节(8位)并且信号在一个字节内实现(即该信号没有跨字节实现):该信号的高位(S_msb)将被放在该字节的高位,信号的低位(S_lsb)将被放在该字节的低位。

当一个信号的数据长度超过1个字节(8位)或者数据长度不超过一个字节但是采用跨字节方式实现时:该信号的高位(S_msb)将被放在高字节(MSB)的高位,信号的低位(S_lsb)将被放在低字节(LSB)的低位。

Motorola编码格式

当一个信号的数据长度不超过1个字节(8位)并且信号在一个字节内实现(即该信号没有跨字节实现):该信号的高位(S_msb)将被放在该字节的高位,信号的低位(S_lsb)将被放在该字节的低位。

当一个信号的数据长度超过1个字节(8位)或者数据长度不超过一个字节但是采用跨字节方式实现时:该信号的高位(S_msb)将被放在低字节(MSB)的高位,信号的低位(S_lsb)将被放在高字节(LSB)的低位。

可以看出,当一个信号的数据长度不超过1Byte时,Intel与Motorola两种格式的编码结果没有什么不同,完全一样。当信号的数据长度超过1Byte时,两者的编码结果出现了明显的不同。

二、CAN报文转换工具需求分析

1、 支持标准帧的CAN报文的转换,扩展帧暂不支持

2、 CAN报文支持Intel、motorola两种编码,先支持motorola格式,后期追加Intel格式

3、 工具具有一定的容错处理能力、报告生成能力

4、 制定统一格式,方便使用者修改测试脚本

5、增加交互模式,键盘输入,控制台输出;例如:

提示语:startBit:length:minValue:maxValue:setValue

输入:35:1:0:1:1

或:35:1:::1

控制台输出:00 00 00 00 08 00 00 00

Intel和Motorola编码举例:

Python实现CAN报文转换工具教程

三、交互模式

代码如下:

import sys
print("----------------欢迎使用CAN报文转换工具交互模式----------------")
print("请输入CAN信号,格式为:startBit:length:minValue:maxValue:setValue")
print("例如:32:1:0:1:1")
print("或者省略minValue和maxValue:35:1:::1")
print("信号输入结束请再按一次回车")
 
#十进制转换成二进制list
def octToBin(octNum, bit):
 while(octNum != 0):
 bit.append(octNum%2)
 octNum = int(octNum/2)
 for i in range(64-len(bit)):
 bit.append(0)
 
sig = []
startBit = []
length = []
setValue = []
#输入CAN信号
while True:
 input_str = input()
 if not len(input_str):
 break
 if(input_str.count(":")<4):
 print("输入格式错误,参数缺少setValue,请重新输入!")
 continue
 if(input_str.split(":")[4]==""):
 print("setValue参数不能为空,请重新输入!")
 continue
 sig.append(input_str)
#解析CAN信号
for i in range(len(sig)):
 startBit.append(int(sig[i].split(":")[0]))
 length.append(int(sig[i].split(":")[1]))
 setValue.append(int(sig[i].split(":")[4]))
#CAN数组存放CAN报文值 
CAN = []
for i in range(64):
 CAN.append(-1)
for i in range(len(startBit)):
 #长度超过1Byte的情况,暂不支持
 if(length[i]>16):
 print("CAN信号长度超过2Byte,暂不支持!!!")
 sys.stdin.readline()
 sys.exit()
 #长度未超过1Byte的情况且未跨字节的信号
 if((startBit[i]%8 + length[i])<=8):
 for j in range(length[i]):
  bit = []
  #setValue的二进制值按字节位从低到高填
  octToBin(setValue[i],bit)
  #填满字节长度值
  if(CAN[startBit[i]+j]==-1):
  CAN[startBit[i]+j] = bit[j]
  #字节存在冲突
  else:
  print(sig[i] + "字节位存在冲突,生成CAN报文失败!!!")
  sys.stdin.readline()
  sys.exit()
 #跨字节的信号
 else:
 #高位位数和低位位数
 highLen = 8 - startBit[i]%8
 lowLen = length[i] - highLen
 bit = []
 #setValue的二进制值按字节位从低到高填
 octToBin(setValue[i],bit)
 #先填进信号的高位
 for j1 in range(highLen):
  if(CAN[startBit[i]+j1]==-1):
  CAN[startBit[i]+j1] = bit[j1]
  #字节存在冲突
  else:
  print(sig[i] + "字节位存在冲突,生成CAN报文失败!!!")
  sys.stdin.readline()
  sys.exit()
 #再填进信号的低位
 for j2 in range(lowLen):
  if(CAN[(int(startBit[i]/8)-1)*8+j2]==-1):
  CAN[(int(startBit[i]/8)-1)*8+j2] = bit[highLen+j2]
  #字节存在冲突
  else:
  print(sig[i] + "字节位存在冲突,生成CAN报文失败!!!")
  sys.stdin.readline()
  sys.exit()
#剩余位默认值设为0
for i in range(64):
 if(CAN[i]==-1):
 CAN[i] = 0
#----------------将二进制list每隔8位转换成十六进制输出----------------
#其中,map()将list中的数字转成字符串,按照Motorola格式每隔8位采用了逆序
# ''.join()将二进制list转换成二进制字符串,int()将二进制字符串转换成十进制
#hex()再将十进制转换成十六进制,upper()转换成大写,两个lstrip()将"0X"删除,
#zfill()填充两位,输出不换行,以空格分隔
print(hex(int(''.join(map(str,CAN[7::-1])),2)).upper().lstrip("0").lstrip("X").zfill(2) + " ",end="")
print(hex(int(''.join(map(str,CAN[15:7:-1])),2)).upper().lstrip("0").lstrip("X").zfill(2) + " ",end="")
print(hex(int(''.join(map(str,CAN[23:15:-1])),2)).upper().lstrip("0").lstrip("X").zfill(2) + " ",end="")
print(hex(int(''.join(map(str,CAN[31:23:-1])),2)).upper().lstrip("0").lstrip("X").zfill(2) + " ",end="")
print(hex(int(''.join(map(str,CAN[39:31:-1])),2)).upper().lstrip("0").lstrip("X").zfill(2) + " ",end="")
print(hex(int(''.join(map(str,CAN[47:39:-1])),2)).upper().lstrip("0").lstrip("X").zfill(2) + " ",end="")
print(hex(int(''.join(map(str,CAN[55:47:-1])),2)).upper().lstrip("0").lstrip("X").zfill(2) + " ",end="")
print(hex(int(''.join(map(str,CAN[63:55:-1])),2)).upper().lstrip("0").lstrip("X").zfill(2))

运行截图:

Python实现CAN报文转换工具教程

错误提示:

Python实现CAN报文转换工具教程

四、配置项模式

配置文件如下:

##注释
::start
#编码格式:0=Intel;1=Motorola
encodeType=1
#帧格式:0=标准帧;1=扩展帧;
canMode=0
#帧类型:0=数据帧;...
canType=0
#默认初始值(0~1)
defaultValue=0
#MSG定义
msgName=BCM_FrP01
msgID=0x2CD
#长度(BYTE)
msgLength=8
#signal定义
#sigName=name:startBit:length:minValue:maxValue:setValue
#sigName=ReverseSw:25:6:0:1:13
#sigName=Trunk_BackDoor_Sts:33:2:0:1:2
#sigName=DRVUnlockState:37:2:0:1:3
#sigName=HeadLampLowBeam:40:8:0:1:60
#sigName=HoodStatus:51:1:0:1:0
#sigName=HeadLampHighBeam:52:1:0:1:0
#sigName=RLDoorStatus:59:1:0:1:0
#sigName=RRDoorStatus:58:1:0:1:0
#sigName=PsgDoorStatus:57:2:0:1:0
sigName=One:0:8:0:255:165
sigName=Two:24:12:0:4095:1701
sigName=Three:54:5:0:31:25
::end
::start
#编码格式:0=Intel;1=Motorola
encodeType=1
#帧格式:0=标准帧;1=扩展帧;
canMode=0
#帧类型:0=数据帧;...
canType=0
#默认初始值(0~1)
defaultValue=0
#MSG定义
msgName=BCM_FrP
msgID=0x2CD
#长度(BYTE)
msgLength=8
#signal定义
#sigName=name:startBit:length:minValue:maxValue:setValue
#sigName=ReverseSw:25:6:0:1:13
#sigName=Trunk_BackDoor_Sts:33:2:0:1:2
#sigName=DRVUnlockState:37:2:0:1:3
#sigName=HeadLampLowBeam:40:8:0:1:60
#sigName=HoodStatus:51:1:0:1:0
#sigName=HeadLampHighBeam:52:1:0:1:0
#sigName=RLDoorStatus:59:1:0:1:0
#sigName=RRDoorStatus:58:1:0:1:0
#sigName=PsgDoorStatus:57:2:0:1:0
sigName=One:35:1:0:1:1
::end

代码如下:

#!/usr/bin/python
defaultValue = 0
sigName = []
startBit = []
length = []
minValue = []
maxValue = []
setValue = []
#CAN数组存放CAN报文值
CAN = []
logFile = open("log.txt","w")
def parseConfig():
 config = open("Config.txt","r")
 
 count = 0
 isError = False
 for line in config:
 line = line.strip()
 #注释
 if(line.find("#")>=0):
  continue
 #开始标记
 elif(line.find("::start")>=0):
  count = count + 1
  isError = False
  if(count>1):
  sigName.clear()
  startBit.clear()
  length.clear()
  setValue.clear()
  continue
  else:
  continue
 elif(isError == True):
  continue
 #编码格式
 elif(line.find("encodeType")>=0):
  encodeType = line.split("=")[1]
  if(encodeType != "1"):
  isError = True
  print(str(count) + ". CAN报文生成失败!!!目前仅支持Motorola编码格式,暂不支持Intel编码格式!")
  logFile.write("%d. CAN报文生成失败!!!目前仅支持Motorola编码格式,暂不支持Intel编码格式!\n" % count)
  continue
 #帧格式
 elif(line.find("canMode")>=0):
  canMode = line.split("=")[1]
  if(canMode != "0"):
  isError = True
  print(str(count) + ". CAN报文生成失败!!!目前仅支持标准帧,暂不支持扩展帧!")
  logFile.write("%d. CAN报文生成失败!!!目前仅支持标准帧,暂不支持扩展帧!\n" % count)
  continue
 #帧类型
 elif(line.find("canType")>=0):
  canType = line.split("=")[1]
  if(canType != "0"):
  isError = True
  print(str(count) + ". CAN报文生成失败!!!目前仅支持数据帧,暂不支持其他帧!")
  logFile.write("%d. CAN报文生成失败!!!目前仅支持数据帧,暂不支持其他帧!\n" % count)
  continue
 #默认初始值
 elif(line.find("defaultValue")>=0):
  global defaultValue
  defaultValue = int(line.split("=")[1])
 #MSG名称
 elif(line.find("msgName")>=0):
  msgName = line.split("=")[1]
 #MSGID
 elif(line.find("msgID")>=0):
  msgID = line.split("=")[1]
 #MSG长度
 elif(line.find("msgLength")>=0):
  msgLength = line.split("=")[1]
 #signal定义
 elif(line.find("sigName")>=0):
  sigName.append(line.split(":")[0].split("=")[1])
  startBit.append(int(line.split(":")[1]))
  length.append(int(line.split(":")[2]))
  #minValue.append(int(line.split(":")[3]))
  #maxValue.append(int(line.split(":")[4]))
  setValue.append(int(line.split(":")[5]))
 elif(line.find("::end")>=0):
  
  rV,errMsg = getCANMessage()
  if(rV == "-1"):
  isError = True
  print(str(count) + ". CAN报文生成失败!!!" + errMsg)
  logFile.write("%d. CAN报文生成失败!!!%s\n" % (count,errMsg))
  continue
  
  print(str(count) + ". CAN报文生成成功!!!")
  logFile.write("%d. CAN报文生成成功!!!\n" % count)
  #----------------------------输出标题信息----------------------------
  print("msgName\t\tmsgID\t\tmsgLen\t\tmsgData")
  logFile.write("msgName\t\tmsgID\t\tmsgLen\t\tmsgData\n")
  if(len(msgName)<8):
  print(msgName + "\t\t",end="")
  logFile.write("%s\t\t" % msgName)
  else:
  print(msgName + "\t",end="")
  logFile.write("%s\t" % msgName)
  print(msgID + "\t\t",end="")
  logFile.write("%s\t\t" % msgID)
  print(msgLength + "\t\t",end="")
  logFile.write("%s\t\t" % msgLength)
  #----------------将二进制list每隔8位转换成十六进制输出----------------
  #其中,map()将list中的数字转成字符串,按照Motorola格式每隔8位采用了逆序
  # ''.join()将二进制list转换成二进制字符串,int()将二进制字符串转换成十进制
  #hex()再将十进制转换成十六进制,upper()转换成大写,两个lstrip()将"0X"删除,
  #zfill()填充两位,输出不换行,以空格分隔
  print(hex(int(''.join(map(str,CAN[7::-1])),2)).upper().lstrip("0").lstrip("X").zfill(2) + " ",end="")
  print(hex(int(''.join(map(str,CAN[15:7:-1])),2)).upper().lstrip("0").lstrip("X").zfill(2) + " ",end="")
  print(hex(int(''.join(map(str,CAN[23:15:-1])),2)).upper().lstrip("0").lstrip("X").zfill(2) + " ",end="")
  print(hex(int(''.join(map(str,CAN[31:23:-1])),2)).upper().lstrip("0").lstrip("X").zfill(2) + " ",end="")
  print(hex(int(''.join(map(str,CAN[39:31:-1])),2)).upper().lstrip("0").lstrip("X").zfill(2) + " ",end="")
  print(hex(int(''.join(map(str,CAN[47:39:-1])),2)).upper().lstrip("0").lstrip("X").zfill(2) + " ",end="")
  print(hex(int(''.join(map(str,CAN[55:47:-1])),2)).upper().lstrip("0").lstrip("X").zfill(2) + " ",end="")
  print(hex(int(''.join(map(str,CAN[63:55:-1])),2)).upper().lstrip("0").lstrip("X").zfill(2))
  logFile.write("%s " % hex(int(''.join(map(str,CAN[7::-1])),2)).upper().lstrip("0").lstrip("X").zfill(2))
  logFile.write("%s " % hex(int(''.join(map(str,CAN[15:7:-1])),2)).upper().lstrip("0").lstrip("X").zfill(2))
  logFile.write("%s " % hex(int(''.join(map(str,CAN[23:15:-1])),2)).upper().lstrip("0").lstrip("X").zfill(2))
  logFile.write("%s " % hex(int(''.join(map(str,CAN[31:23:-1])),2)).upper().lstrip("0").lstrip("X").zfill(2))
  logFile.write("%s " % hex(int(''.join(map(str,CAN[39:31:-1])),2)).upper().lstrip("0").lstrip("X").zfill(2))
  logFile.write("%s " % hex(int(''.join(map(str,CAN[47:39:-1])),2)).upper().lstrip("0").lstrip("X").zfill(2))
  logFile.write("%s " % hex(int(''.join(map(str,CAN[55:47:-1])),2)).upper().lstrip("0").lstrip("X").zfill(2))
  logFile.write("%s\n" % hex(int(''.join(map(str,CAN[63:55:-1])),2)).upper().lstrip("0").lstrip("X").zfill(2))
  
 config.close()
 
#十进制转换成二进制list  
def octToBin(octNum, bit):
 while(octNum != 0):
 bit.append(octNum%2)
 octNum = int(octNum/2)
 for i in range(64-len(bit)):
 bit.append(0)
 
#获取CAN报文值
def getCANMessage():
 CAN.clear()
 for i in range(64):
 CAN.append(-1)
 for i in range(len(startBit)):
 #长度超过1Byte的情况,暂不支持
 if(length[i]>16):
  errMsg = " CAN信号长度超过2Byte,暂不支持!!!"
  #print(sigName[i] + errMsg)
  return "-1",errMsg
 #长度未超过1Byte的情况且未跨字节的信号
 if((startBit[i]%8 + length[i])<=8):
  for j in range(length[i]):
  bit = []
  #setValue的二进制值按字节位从低到高填
  octToBin(setValue[i],bit)
  #填满字节长度值
  if(CAN[startBit[i]+j]==-1):
   CAN[startBit[i]+j] = bit[j]
  #字节存在冲突
  else:
   errMsg = " 字节位存在冲突,生成CAN报文失败!!!"
   #print(sigName[i] + errMsg)
   return "-1",errMsg
 #跨字节的信号
 else:
  #高位位数和低位位数
  highLen = 8 - startBit[i]%8
  lowLen = length[i] - highLen
  bit = []
  #setValue的二进制值按字节位从低到高填
  octToBin(setValue[i],bit)
  #先填进信号的高位
  for j1 in range(highLen):
  if(CAN[startBit[i]+j1]==-1):
   CAN[startBit[i]+j1] = bit[j1]
  #字节存在冲突
  else:
   errMsg = " 字节位存在冲突,生成CAN报文失败!!!"
   #print(sigName[i] + errMsg)
   return "-1",errMsg
  #再填进信号的低位
  for j2 in range(lowLen):
  if(CAN[(int(startBit[i]/8)-1)*8+j2]==-1):
   CAN[(int(startBit[i]/8)-1)*8+j2] = bit[highLen+j2]
  #字节存在冲突
  else:
   errMsg = " 字节位存在冲突,生成CAN报文失败!!!"
   #print(sigName[i] + errMsg)
   return "-1",errMsg
 #剩余位设为默认值
 for i in range(64):
 if(CAN[i]==-1):
  CAN[i] = defaultValue
 
 #若无错误则返回正确值
 return "0","success!"
 
if __name__ == "__main__":
 #调用parseConfig()函数开始执行程序
 parseConfig()

运行结果:

1. CAN报文生成成功!!!
msgName		msgID		msgLen		msgData
BCM_FrP01	0x2CD		8		A5 00 06 A5 00 06 40 00
2. CAN报文生成成功!!!
msgName		msgID		msgLen		msgData
BCM_FrP		0x2CD		8		00 00 00 00 08 00 00 00

以上这篇Python实现CAN报文转换工具教程就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持三水点靠木。

Python 相关文章推荐
Python装饰器用法示例小结
Feb 11 Python
Python 实现字符串中指定位置插入一个字符
May 02 Python
使用Python的Django和layim实现即时通讯的方法
May 25 Python
从django的中间件直接返回请求的方法
May 30 Python
对numpy.append()里的axis的用法详解
Jun 28 Python
Python爬取成语接龙类网站
Oct 19 Python
python sorted方法和列表使用解析
Nov 18 Python
Python利用Scrapy框架爬取豆瓣电影示例
Jan 17 Python
python标准库os库的函数介绍
Feb 12 Python
tensorflow将图片保存为tfrecord和tfrecord的读取方式
Feb 17 Python
django下创建多个app并设置urls方法
Aug 02 Python
教你怎么用Python监控愉客行车程
Apr 29 Python
python TCP包注入方式
May 05 #Python
python构造IP报文实例
May 05 #Python
python3通过udp实现组播数据的发送和接收操作
May 05 #Python
解决python使用list()时总是报错的问题
May 05 #Python
python requests.get带header
May 05 #Python
python中urllib.request和requests的使用及区别详解
May 05 #Python
python requests包的request()函数中的参数-params和data的区别介绍
May 05 #Python
You might like
php读取msn上的用户信息类
2008/12/05 PHP
PHP学习笔记(二) 了解PHP的基本语法以及目录结构
2014/08/04 PHP
php PDO实现的事务回滚示例
2017/03/23 PHP
PHP 爬取网页的主要方法
2018/07/13 PHP
JS注释所产生的bug 即使注释也会执行
2013/11/19 Javascript
[原创]推荐10款最热门jQuery UI框架
2014/08/19 Javascript
JavaScript实现俄罗斯方块游戏过程分析及源码分享
2015/03/23 Javascript
原生js实现的贪吃蛇网页版游戏完整实例
2015/05/18 Javascript
JavaScript编写点击查看大图的页面半透明遮罩层效果实例
2016/05/09 Javascript
Jquery UI实现一次拖拽多个选中的元素操作
2020/12/01 Javascript
微信小程序canvas写字板效果及实例
2017/06/15 Javascript
jQuery实现base64前台加密解密功能详解
2017/08/29 jQuery
JS实现显示当前日期的实例代码
2018/07/03 Javascript
JavaScript实现邮箱后缀提示功能的示例代码
2018/12/13 Javascript
vue和better-scroll实现列表左右联动效果详解
2019/04/29 Javascript
vue2.0实现列表数据增加和删除
2020/06/17 Javascript
全网小程序接口请求封装实例代码
2020/11/06 Javascript
[52:06]完美世界DOTA2联赛决赛日 Inki vs LBZS 第一场 11.08
2020/11/10 DOTA
使用python的chardet库获得文件编码并修改编码
2014/01/22 Python
python使用WMI检测windows系统信息、硬盘信息、网卡信息的方法
2015/05/15 Python
详解Python编程中包的概念与管理
2015/10/16 Python
Python提取转移文件夹内所有.jpg文件并查看每一帧的方法
2019/06/27 Python
python开发前景如何
2020/06/11 Python
Django扫码抽奖平台的配置过程详解
2021/01/14 Python
韩国11街:11STREET
2018/03/27 全球购物
下列程序在32位linux或unix中的结果是什么
2014/03/25 面试题
旅游业大学生创业计划书
2014/01/31 职场文书
可贵的沉默教学反思
2014/02/06 职场文书
英语专业职业生涯规划范文
2014/03/05 职场文书
2014年秋季开学寄语
2014/08/02 职场文书
人力资源管理求职信
2014/08/07 职场文书
房屋买卖协议书范本
2014/09/27 职场文书
解放思想大讨论活动总结
2015/05/09 职场文书
迎新生欢迎词2015
2015/07/16 职场文书
Python+Tkinter打造签名设计工具
2022/04/01 Python
教你在 Java 中实现 Dijkstra 最短路算法的方法
2022/04/08 Java/Android