Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 93 additions & 54 deletions charger/chargeparse.py
Original file line number Diff line number Diff line change
@@ -1,81 +1,120 @@
#!/usr/bin/env python3
# based on cantldr.py by jerkey
# opens ks_can2serial canbus logs generated by https://github.com/jerkey/ks_can/blob/teslalog/ks_can2serial.ino
inFile = open('/tmp/can2serial.txt')

RESET = '\033[0m' # https://misc.flogisoft.com/bash/tip_colors_and_formatting
GREEN = '\033[32m'
RED = '\033[31m'
YELLOW = '\033[33m'

ids = [530,546,770,924,930,551] # list of CAN IDs we care about
lastMsg = [' '] * len(ids) # empty so it's not too short to compare with
CHGPH1_vBat = 0.0 # in case we want to tag this data onto some other ID for reference

def parseCan(id,data):
global CHGPH1_vBat, ids, lastMsg
if id==530:
def parseCan(can_id, data, previousMsg):
global CHGPH1_vBat
if can_id==530:
BMS_contactorState = int(data[5],16) # lower 4 bits of third byte
BMS_state = int(data[4],16) # high 4 bits of third byte
BMS_contactorStateText = {6:"BMS_CTRSET_CLEANING",5:"BMS_CTRSET_WELD",4:"BMS_CTRSET_SNA",3:"BMS_CTRSET_OPENING",2:"BMS_CTRSET_CLOSED",1:"BMS_CTRSET_PRECHARGE",0:"BMS_CTRSET_OPEN"}
BMS_stateText = {15:"BMS_SNA",8:"BMS_WELD",7:"BMS_FAULT",6:"BMS_CLEARFAULT",5:"BMS_CHARGERVOLTAGE",4:"BMS_FASTCHARGE",3:"BMS_CHARGER",2:"BMS_SUPPORT",1:"BMS_DRIVE",0:"BMS_STANDBY"}
message = 'BMS_contactorState:'+BMS_contactorStateText.get(BMS_contactorState,' ').ljust(20) + ' BMS_state:' + BMS_stateText.get(BMS_state,' ').ljust(18)
elif id==546:
BMS_chargeCommand = int(data[2:4]+data[0:2],16) * 0.001 #: 0|16@1+ (0.001,0) [0|40] "kW" CHG "BMS Commanded AC power";
BMS_chargeVoltageLimit = int(data[6:8]+data[4:6],16) * 0.01 #: 16|16@1+ (0.01,0) [0|600] "V" CHG "BMS Pack voltage limit";
BMS_chargeLineCurrentLimit = int(data[11]+data[8:10],16) % 512 * 0.16666 #: 32|9@1+ (0.16666,0) [0|85.16] "A" CHG "BMS Line current limit";
elif can_id==546:
#: 0|16@1+ (0.001,0) [0|40] "kW" CHG "BMS Commanded AC power";
BMS_chargeCommand = int(data[2:4]+data[0:2],16) * 0.001
#: 16|16@1+ (0.01,0) [0|600] "V" CHG "BMS Pack voltage limit";
BMS_chargeVoltageLimit = int(data[6:8]+data[4:6],16) * 0.01
#: 32|9@1+ (0.16666,0) [0|85.16] "A" CHG "BMS Line current limit";
BMS_chargeLineCurrentLimit = int(data[11]+data[8:10],16) % 512 * 0.16666
message = 'BMS_chargeCommand:'+('%.0f' % BMS_chargeCommand).rjust(2)+'kW BMS_chargeVoltageLimit:'+('%.0f' % BMS_chargeVoltageLimit).rjust(3)+'V BMS_chargeLineCurrentLimit:'+('%.0f' % BMS_chargeLineCurrentLimit).rjust(2)+'A '+data[10:12]
BMS_chargeEnable = (int(data[10],16) & 6) >> 1 #: 45|2@1+ (1,0) [0|0] 32 "" CP,CHG "BMS Charge Enable";
#: 45|2@1+ (1,0) [0|0] 32 "" CP,CHG "BMS Charge Enable";
BMS_chargeEnable = (int(data[10],16) & 6) >> 1
message += ' BMS_chargeEnable:'+str(BMS_chargeEnable)
message += ' BMS_chargeClearFaults' if int(data[11],16) & 4 else ''# : 42|1@1+ (1,0) [0|0] aka 4 "" CHG "BMS Clear Faults";
message += ' BMS_fcRequest' if int(data[11],16) & 8 else ''#: 43|1@1+ (1,0) [0|0] 8 "" CP,CHG "The BMS requests the charger to enable the FC sequence and turn on the BMS FC CAN Relay.";
message += ' BMS_chgVLimitMode' if int(data[10],16) & 1 else ''# : 44|1@1+ (1,0) [0|0] 16 "" CHG "Tells the charger to either follow the BMS_chargeLimit or to track the pack voltage to prevent current spikes";
# : 42|1@1+ (1,0) [0|0] aka 4 "" CHG "BMS Clear Faults";
message += ' BMS_chargeClearFaults' if int(data[11],16) & 4 else ''
#: 43|1@1+ (1,0) [0|0] 8 "" CP,CHG "The BMS requests the charger to enable the FC sequence and turn on the BMS FC CAN Relay.";
message += ' BMS_fcRequest' if int(data[11],16) & 8 else ''
# : 44|1@1+ (1,0) [0|0] 16 "" CHG "Tells the charger to either follow the BMS_chargeLimit or to track the pack voltage to prevent current spikes";
message += ' BMS_chgVLimitMode' if int(data[10],16) & 1 else ''
#BMS_chargeFC_statusCode : 48|4@1+ (1,0) [0|0] "" CHG 15 "PT_FC_STATUS_NODATA" 14 "PT_FC_STATUS_MALFUNCTION" 13 "PT_FC_STATUS_NOTCOMPATIBLE" 6 "PT_FC_STATUS_EXT_ISOACTIVE" 5 "PT_FC_STATUS_INT_ISOACTIVE" 4 "PT_FC_STATUS_UTILITY" 3 "PT_FC_STATUS_SHUTDOWN" 2 "PT_FC_STATUS_PRELIMITEXCEEDED" 1 "PT_FC_STATUS_READY" 0 "PT_FC_STATUS_NOTREADY_SNA" ;
#BMS_chargeFC_type : 52|3@1+ (1,0) [0|7] "" GTW,CHG 7 "PT_FC_TYPE_SNA" 6 "PT_FC_TYPE_OTHER" 3 "PT_FC_TYPE_CC_EVSE" 2 "PT_FC_TYPE_CHINAMO" 1 "PT_FC_TYPE_CHADEMO" 0 "PT_FC_TYPE_SUPERCHARGER" ;
elif id==770:
BMS_socMin = (int(data[3]+data[0:2],16) & 1023) * 0.1 # "BMS State Of Charge (SOC). This is the minimum displayed brick SOC. This is NOT cell SOC"
elif can_id==770:
# "BMS State Of Charge (SOC). This is the minimum displayed brick SOC. This is NOT cell SOC"
BMS_socMin = (int(data[3]+data[0:2],16) & 1023) * 0.1
message = ('BMS_socMin:%.0f' % BMS_socMin)+'%'+' CHGPH1_vBat:%.0f' % CHGPH1_vBat
message = lastMsg[ids.index(id)] # a hack to prevent printing anything
elif id==924: # didn't see this at all in the 2018-4-9 capture
CC_currentLimit_PT = int(data[0:2],16) * 0.5 # "A" CHG,GTW "periodic TEN_SECONDS chargeModeModelS,periodic TEN_SECONDS voltageModeModelS"
CC_pilotState_PT = int(data[3],16) & 3 #: 8|2@1+ (1,0) [0|3] "" CHG,GTW
message = previousMsg # a hack to prevent printing anything
elif can_id==924: # didn't see this at all in the 2018-4-9 capture
# "A" CHG,GTW "periodic TEN_SECONDS chargeModeModelS,periodic TEN_SECONDS voltageModeModelS"
CC_currentLimit_PT = int(data[0:2],16) * 0.5
#: 8|2@1+ (1,0) [0|3] "" CHG,GTW
CC_pilotState_PT = int(data[3],16) & 3
CC_pilotState_PTText = {3:"PT_CC_PILOT_STATE_SNA",2:"PT_CC_PILOT_STATE_FAULTED",1:"PT_CC_PILOT_STATE_IDLE",0:"PT_CC_PILOT_STATE_READY"}
CC_numPhases_PT = int(data[3],16) >> 2 #: 10|2@1+ (1,0) [0|3] "" CHG,GTW
CC_lineVoltage_PT = int(data[7]+data[4:6],16) & 511 #: 16|9@1+ (1,0) [0|510] "V" CHG,GTW "periodic TEN_SECONDS chargeModeModelS,periodic TEN_SECONDS voltageModeModelS"
#: 10|2@1+ (1,0) [0|3] "" CHG,GTW
CC_numPhases_PT = int(data[3],16) >> 2
#: 16|9@1+ (1,0) [0|510] "V" CHG,GTW "periodic TEN_SECONDS chargeModeModelS,periodic TEN_SECONDS voltageModeModelS"
CC_lineVoltage_PT = int(data[7]+data[4:6],16) & 511
message = ('CC_currentLimit_PT:%.0f' % CC_currentLimit_PT)+' CC_pilotState_PT:'+CC_pilotState_PTText.get(CC_pilotState_PT,' ').ljust(25)
elif id==930:
message = 'BMS_fullChargeComplete:'+str(int(data[2],16) & 1) #: 12|1@1+ (1,0) [0|0] "" CP,GTW "This bit indicates if the BMS is fully charged"
message += ' BMS_fastChargerPresent:'+str(int(data[2],16) & 4) #: 14|1@1+ (1,0) [0|0] "" CP,GTW "TRUE when the fast charger Status Message has been received within the last 1 second"
message += ' BMS_fcContactorCloseRequest:'+str(int(data[2],16) & 8) #: 15|1@1+ (1,0) [0|0] "" CHG,CHGS,GTW "1 if the charger should close the FC contactors"
message += ' BMS_fcContactorPwrIsOn:'+str(int(data[6:8],16) & 16) #: 28|1@1+ (1,0) [0|0] "" CHG,CHGS,GTW "This bit indicates if the BMShas powered the FC Contactor Power line which is used by the charger to diagnose its h/w"
elif id==551:
CHGPH1_vBat = int(data[6:8]+data[4:6],16) * 0.010528564 #: 16|16@1+ (0.010528564,0) [0|690] "V" CHGVI BO_ 551 CHGPH1_HVStatus: 8 CHGPH1
message = lastMsg[ids.index(id)] # a hack to prevent printing anything
elif can_id==930:
#: 12|1@1+ (1,0) [0|0] "" CP,GTW "This bit indicates if the BMS is fully charged"
message = 'BMS_fullChargeComplete:'+str(int(data[2],16) & 1)
#: 14|1@1+ (1,0) [0|0] "" CP,GTW "TRUE when the fast charger Status Message has been received within the last 1 second"
message += ' BMS_fastChargerPresent:'+str(int(data[2],16) & 4)
#: 15|1@1+ (1,0) [0|0] "" CHG,CHGS,GTW "1 if the charger should close the FC contactors"
message += ' BMS_fcContactorCloseRequest:'+str(int(data[2],16) & 8)
#: 28|1@1+ (1,0) [0|0] "" CHG,CHGS,GTW "This bit indicates if the BMShas powered the FC Contactor Power line which is used by the charger to diagnose its h/w"
message += ' BMS_fcContactorPwrIsOn:'+str(int(data[6:8],16) & 16)
elif can_id==551:
#: 16|16@1+ (0.010528564,0) [0|690] "V" CHGVI BO_ 551 CHGPH1_HVStatus: 8 CHGPH1
CHGPH1_vBat = int(data[6:8]+data[4:6],16) * 0.010528564
message = previousMsg # a hack to prevent printing anything
else:
message = data
return message

for line in inFile: # '268:00000000B3000000 16\n' is what a line looks like
if line.find('CAN')!=0: # swallow the init lines from ks_can2serial.ino
id = int(line.split(':')[0],16)
if id in ids: # we ignore CAN IDs not in our list
idIndex = ids.index(id)
parsedLine = parseCan(id,line.split(' ')[0][4:20])
if lastMsg[idIndex] != parsedLine: # ignore messages that haven't changed since we last saw them
print(str(id)+'\t',end='')
# print(parsedLine+';'+str(len(lastMsg[idIndex]))+':'+str(len(parsedLine)))
for i in range(len(parsedLine)): # print character by character, colored according to same or changed
if lastMsg[idIndex].ljust(len(parsedLine))[i]==parsedLine[i]:
print(RESET+parsedLine[i],end='')
else:
print(RED+parsedLine[i],end='')
for i in range(len(parsedLine),128): # pad data with spaces
print(' ',end='')
linetime = int(line[:-1].split(' ')[1]) # get the time in milliseconds from the log
mins = int(linetime / (1000*60))
secs = linetime % 60000 / 1000
lastMsg[idIndex] = parsedLine # store the latest line to compare with for next time
print('\t'+YELLOW+str(mins).zfill(3)+':',end='') # print the number of minutes:
if secs < 10:
print('0',end='')
print(str(secs)+RESET) # print the number of seconds (a float) and ANSI RESET
def main(data):
msgs = {}
for i in [530,546,770,924,930,551]: # list of CAN IDs we care about
# Empty so it's not too short to compare with. Using historical size from string literal.
msgs[i] = ' ' * 239

for line in data: # '268:00000000B3000000 16\n' is what a line looks like
if line.find('CAN') == 0:
# swallow the init lines from ks_can2serial.ino
continue
can_id = int(line.split(':')[0], 16)
if can_id not in msgs.keys():
# we ignore CAN IDs not in our list
continue
#parsedLine = parseCan(can_id, line.split(' ')[0][4:20])
previous = msgs[can_id]
parsedLine = parseCan(can_id, line[4:20], previous)
if previous == parsedLine:
# ignore messages that haven't changed since we last saw them
continue

print(str(can_id) + '\t', end='')
# print(parsedLine+';'+str(len(previous))+':'+str(len(parsedLine)))

previous_justified = previous.ljust(len(parsedLine))
for i,c in enumerate(parsedLine):
# print character by character, colored according to same or changed
if previous_justified[i] == c:
print(RESET + c, end='')
else:
print(RED + c, end='')

pad = ' ' * (128 - len(parsedLine))
print(pad, end='')

msgs[can_id] = parsedLine # store the latest line to compare with for next time
linetime = int(line[:-1].split(' ')[1]) # get the time in milliseconds from the log
mins = int(linetime / (1000*60))
secs = linetime % 60000 / 1000
minutes, seconds = str(mins).zfill(3), str(secs)
seconds = '0' + seconds if secs < 10 else seconds
print('\t' + YELLOW + minutes + ':' + seconds + RESET)

if __name__ == '__main__':
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('CANdata', type=argparse.FileType('r'))
data = parser.parse_args().CANdata
main(data)
3 changes: 3 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
caneton==1.12.0
certifi==2018.8.24
dpkt==1.9.1