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中MySQLdb模块用法实例
Nov 10 Python
在Python中使用__slots__方法的详细教程
Apr 28 Python
使用Python的Flask框架表单插件Flask-WTF实现Web登录验证
Jul 12 Python
Python2.7基于淘宝接口获取IP地址所在地理位置的方法【测试可用】
Jun 07 Python
Python列表解析配合if else的方法
Jun 23 Python
Python minidom模块用法示例【DOM写入和解析XML】
Mar 25 Python
Python玩转加密的技巧【推荐】
May 13 Python
深入了解Django View(视图系统)
Jul 23 Python
python wav模块获取采样率 采样点声道量化位数(实例代码)
Jan 22 Python
python获取百度热榜链接的实例方法
Aug 25 Python
Python3.7安装PyQt5 运行配置Pycharm的详细教程
Oct 15 Python
python 通过pip freeze、dowload打离线包及自动安装的过程详解(适用于保密的离线环境
Dec 14 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判断输入不超过mysql的varchar字段的长度范围
2011/06/24 PHP
修改ThinkPHP缓存为Memcache的方法
2014/06/25 PHP
php实现与erlang的二进制通讯实例解析
2014/07/23 PHP
php框架CodeIgniter使用redis的方法分析
2018/04/13 PHP
PHP创建XML的方法示例【基于DOMDocument类及SimpleXMLElement类】
2019/09/10 PHP
php 使用ActiveMQ发送消息,与处理消息操作示例
2020/02/23 PHP
json数据处理技巧(字段带空格、增加字段、排序等等)
2013/06/14 Javascript
js实现通用的微信分享组件示例
2014/03/10 Javascript
轻松创建nodejs服务器(5):事件处理程序
2014/12/18 NodeJs
JavaScript对象参数的引用传递
2016/01/14 Javascript
使用命令行工具npm新创建一个vue项目的方法
2017/12/27 Javascript
vue组件与复用详解
2018/04/08 Javascript
echarts饼图各个板块之间的空隙如何实现
2020/12/01 Javascript
[46:57]EG vs Winstrike 2018国际邀请赛小组赛BO2 第二场 8.18
2018/08/19 DOTA
Python解惑之整数比较详解
2017/04/24 Python
Python绘制3d螺旋曲线图实例代码
2017/12/20 Python
12个步骤教你理解Python装饰器
2019/07/01 Python
python实现字符串完美拆分split()的方法
2019/07/16 Python
python二进制读写及特殊码同步实现详解
2019/10/11 Python
使用Python实现 学生学籍管理系统
2019/11/26 Python
django ListView的使用 ListView中获取url中的参数值方式
2020/03/27 Python
VIVOBAREFOOT赤脚鞋:让您的脚做自然的事情
2017/06/01 全球购物
LODI女鞋在线商店:阿利坎特的鞋类品牌
2019/02/15 全球购物
Pandora德国官网:购买潘多拉手链、戒指、项链和耳环
2020/02/20 全球购物
怎样让char类型的东西转换成int类型
2013/12/09 面试题
幼师自荐信
2013/10/26 职场文书
计算机专业毕业生自荐信
2013/12/31 职场文书
社团成立邀请函
2014/01/08 职场文书
2014学习全国两会精神心得体会2000字
2014/03/11 职场文书
歌颂祖国的演讲稿
2014/05/04 职场文书
英语教育专业自荐信
2014/05/29 职场文书
2014年英语工作总结
2014/12/20 职场文书
新店开张宣传语
2015/07/13 职场文书
物业管理交接协议书
2016/03/24 职场文书
导游词之岳阳楼
2019/09/25 职场文书
Axios代理配置及封装响应拦截处理方式
2022/04/07 Vue.js