侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

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

Python  /  管理员 发布于 7年前   433

一、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编码举例:

三、交互模式

代码如下:

import sysprint("----------------欢迎使用CAN报文转换工具交互模式----------------")print("请输入CAN信号,格式为:startBit:length:minValue:maxValue:setValue")print("例如:32:1:0:1:1")print("或者省略minValue和maxValue:35:1:::1")print("信号输入结束请再按一次回车") #十进制转换成二进制listdef 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()#剩余位默认值设为0for 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))  

运行截图:

错误提示:

四、配置项模式

配置文件如下:

##注释::start#编码格式:0=Intel;1=MotorolaencodeType=1#帧格式:0=标准帧;1=扩展帧;canMode=0#帧类型:0=数据帧;...canType=0#默认初始值(0~1)defaultValue=0#MSG定义msgName=BCM_FrP01msgID=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:0sigName=One:0:8:0:255:165sigName=Two:24:12:0:4095:1701sigName=Three:54:5:0:31:25::end::start#编码格式:0=Intel;1=MotorolaencodeType=1#帧格式:0=标准帧;1=扩展帧;canMode=0#帧类型:0=数据帧;...canType=0#默认初始值(0~1)defaultValue=0#MSG定义msgName=BCM_FrPmsgID=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:0sigName=One:35:1:0:1:1::end

代码如下:

#!/usr/bin/pythondefaultValue = 0sigName = []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报文生成成功!!!msgNamemsgIDmsgLenmsgDataBCM_FrP010x2CD8A5 00 06 A5 00 06 40 002. CAN报文生成成功!!!msgNamemsgIDmsgLenmsgDataBCM_FrP0x2CD800 00 00 00 08 00 00 00

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


  • 上一条:
    python中pop()函数如何使用
    下一条:
    python TCP包注入方式
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • 在python语言中Flask框架的学习及简单功能示例(0个评论)
    • 在Python语言中实现GUI全屏倒计时代码示例(0个评论)
    • Python + zipfile库实现zip文件解压自动化脚本示例(0个评论)
    • python爬虫BeautifulSoup快速抓取网站图片(1个评论)
    • vscode 配置 python3开发环境的方法(0个评论)
    • 近期文章
    • 在go中实现一个常用的先进先出的缓存淘汰算法示例代码(0个评论)
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • 在go + gin中gorm实现指定搜索/区间搜索分页列表功能接口实例(0个评论)
    • 在go语言中实现IP/CIDR的ip和netmask互转及IP段形式互转及ip是否存在IP/CIDR(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2016-10
    • 2016-11
    • 2018-04
    • 2020-03
    • 2020-04
    • 2020-05
    • 2020-06
    • 2022-01
    • 2023-07
    • 2023-10
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客