├── candb.cmd ├── README.md └── candb.py /candb.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | rem = """ Do any custom setup like setting environment variables etc if required here ... 3 | c:\python27\python -x "%~f0" %1 %2 %3 %4 %5 %6 %7 %8 %9 4 | goto endofPython """ 5 | 6 | import os 7 | import sys 8 | import subprocess as sp 9 | 10 | 11 | #bc = os.path.join(os.getcwd(), 'candb.py') 12 | bc = '''G:\\SampleCode\\PyCharm\\template\\candb.py''' 13 | sp.call([bc] + sys.argv[1:], shell=True) 14 | 15 | rem = """ 16 | :endofPython """ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | Generate CAN dbc file with OEM defined CAN matrix (*.xls). Class `CanDatabase` represents the CAN network and the architecture is similar to Vector Candb++. 3 | 4 | # Manul 5 | ## Install 6 | 1. Put file path of 'candb.cmd' into system evironment variables. 7 | 2. Modify 'candb.py' file path in 'candb.cmd'. 8 | 9 | ## Command 10 | Several command can be used in Command Line: 11 | - `candb -h` show command help. 12 | - `candb gen` generate dbc from excel. 13 | 14 | ### Usage 15 | candb [-h] [-s SHEETNAME] [-t TEMPLATE] [-d] {gen} filename 16 | - `gen` command is used to generate dbc from excel. 17 | - `filename` the path of excle. 18 | - `-s` specify a sheetname used in the excle workbook, optinal. 19 | - `-t` specify a template to parse excel, optional. If not given, template is generated automatically. 20 | - `-d` show more debug info. 21 | 22 | ### Example 23 | ```C 24 | candb gen SAIC_XXXX.xls 25 | ``` 26 | 27 | ## Import as module 28 | Use method `import_excel` to load network from excel. Parameters are defined as below:
29 | * path: Matrix file's path 30 | * sheet: Sheet name of matrix in the excel 31 | * template: Template file which descripes matrix format 32 | 33 | Use method `save` to write to file.
34 | ```python 35 | database = CanDatabase() 36 | database.import_excel("BAIC_IPC_Matrix_CAN_20161008.xls", "IPC", "b100k_gasoline") 37 | database.save() 38 | ``` 39 | -------------------------------------------------------------------------------- /candb.py: -------------------------------------------------------------------------------- 1 | 2 | """\ 3 | This module provides CAN database(*dbc) and matrix(*xls) operation functions. 4 | 5 | 6 | """ 7 | import re 8 | import xlrd 9 | import sys 10 | import traceback 11 | import os 12 | 13 | reload(sys) 14 | sys.setdefaultencoding('utf-8') 15 | 16 | # enable or disable debug info display, this switch is controlled by -d option. 17 | debug_enable = False 18 | 19 | # symbol definition of DBC format file 20 | NEW_SYMBOLS = [ 21 | 'NS_DESC_', 'CM_', 'BA_DEF_', 'BA_', 'VAL_', 'CAT_DEF_', 'CAT_', 22 | 'FILTER', 'BA_DEF_DEF_', 'EV_DATA_', 'ENVVAR_DATA_', 'SGTYPE_', 23 | 'SGTYPE_VAL_', 'BA_DEF_SGTYPE_', 'BA_SGTYPE_', 'SIG_TYPE_REF_', 24 | 'VAL_TABLE_', 'SIG_GROUP_', 'SIG_VALTYPE_', 'SIGTYPE_VALTYPE_', 25 | 'BO_TX_BU_', 'BA_DEF_REL_', 'BA_REL_', 'BA_DEF_DEF_REL_', 26 | 'BU_SG_REL_', 'BU_EV_REL_', 'BU_BO_REL_', 'SG_MUL_VAL_' 27 | ] 28 | 29 | 30 | # pre-defined attribution definitions 31 | # object type name value type min max default value range 32 | ATTR_DEFS_INIT = [ 33 | ["Message", "DiagRequest", "Enumeration", "", "", "No", ["No", "Yes"]], 34 | ["Message", "DiagResponse", "Enumeration", "", "", "No", ["No", "Yes"]], 35 | ["Message", "DiagState", "Enumeration", "", "", "No", ["No", "Yes"]], 36 | ["Message", "GenMsgCycleTime", "Integer", 0, 0, 0, []], 37 | ["Message", "GenMsgCycleTimeActive", "Integer", 0, 0, 0, []], 38 | ["Message", "GenMsgCycleTimeFast", "Integer", 0, 0, 0, []], 39 | ["Message", "GenMsgDelayTime", "Integer", 0, 0, 0, []], 40 | ["Message", "GenMsgILSupport", "Enumeration", "", "", "No", ["No", "Yes"]], 41 | ["Message", "GenMsgNrOfRepetition", "Integer", 0, 0, 0, []], 42 | ["Message", "GenMsgSendType", "Enumeration", "", "", "cycle", ["cycle", "NoSendType", "IfActive"]], 43 | ["Message", "GenMsgStartDelayTime", "Integer", 0, 65535, 0, []], 44 | ["Message", "NmMessage", "Enumeration", "", "", "No", ["No", "Yes"]], 45 | ["Network", "BusType", "String", "", "", "CAN", []], 46 | ["Network", "Manufacturer", "String", "", "", "", []], 47 | ["Network", "NmBaseAddress", "Hex", 0x0, 0x7FF, 0x400, []], 48 | ["Network", "NmMessageCount", "Integer", 0, 255, 128, []], 49 | ["Network", "NmType", "String", "", "", "", []], 50 | ["Network", "DBName", "String", "", "", "", []], 51 | ["Node", "DiagStationAddress", "Hex", 0x0, 0xFF, 0x0, []], 52 | ["Node", "ILUsed", "Enumeration", "", "", "No", ["No", "Yes"]], 53 | ["Node", "NmCAN", "Integer", 0, 2, 0, []], 54 | ["Node", "NmNode", "Enumeration", "", "", "Not", ["Not", "Yes"]], 55 | ["Node", "NmStationAddress", "Hex", 0x0, 0xFF, 0x0, []], 56 | ["Node", "NodeLayerModules", "String", "", "", "CANoeILNVector.dll", []], 57 | ["Signal", "GenSigInactiveValue", "Integer", 0, 0, 0, []], 58 | ["Signal", "GenSigSendType", "Enumeration", "", "", "cycle", ["cycle", "OnChange", "OnWrite", "IfActive", "OnChangeWithRepetition", "OnWriteWithRepetition", "IfActiveWithRepetition"]], 59 | ["Signal", "GenSigStartValue", "Integer", 0, 0, 0, []], 60 | ["Signal", "GenSigTimeoutValue", "Integer", 0, 1000000000, 0, []], 61 | ] 62 | 63 | # matrix template dict 64 | # Matrix parser use this dict to parse can network information from excel. The 65 | # 'key's are used in this module and the 'ValueTable' are used to match excel 66 | # column header. 'ValueTable' may include multi string. 67 | MATRIX_TEMPLATE_MAP = { 68 | "msg_name_col": ["MsgName"], 69 | "msg_type_col": ["MsgType"], 70 | "msg_id_col": ["MsgID"], 71 | "msg_send_type_col": ["MsgSendType"], 72 | "msg_cycle_col": ["MsgCycleTime"], 73 | "msg_len_col": ["MsgLength"], 74 | "sig_name_col": ["SignalName"], 75 | "sig_comment_col": ["SignalDescription"], 76 | "sig_byte_order_col": ["ByteOrder"], 77 | "sig_start_bit_col": ["StartBit"], 78 | "sig_len_col": ["BitLength"], 79 | "sig_value_type_col": ["DateType"], 80 | "sig_factor_col": ["Resolution"], 81 | "sig_offset_col": ["Offset"], 82 | "sig_min_phys_col": ["SignalMin.Value(phys)"], 83 | "sig_max_phys_col": ["SignalMax.Value(phys)"], 84 | "sig_init_val_col": ["InitialValue(Hex)"], 85 | "sig_unit_col": ["Unit"], 86 | "sig_val_col": ["SignalValueDescription"], 87 | } 88 | 89 | # excel workbook sheets with name in this list are ignored 90 | MATRIX_SHEET_IGNORE = ["Cover", "History", "Legend", "ECU Version", ] 91 | 92 | NODE_NAME_MAX = 8 93 | 94 | 95 | class MatrixTemplate(object): 96 | def __init__(self): 97 | self.msg_name_col = 0 98 | self.msg_type_col = 0 99 | self.msg_id_col = 0 100 | self.msg_send_type_col = 0 101 | self.msg_cycle_col = 0 102 | self.msg_len_col = 0 103 | self.sig_name_col = 0 104 | self.sig_comment_col = 0 105 | self.sig_byte_order_col = 0 106 | self.sig_start_bit_col = 0 107 | self.sig_len_col = 0 108 | self.sig_value_type_col = 0 109 | self.sig_factor_col = 0 110 | self.sig_offset_col = 0 111 | self.sig_min_phys_col = 0 112 | self.sig_max_phys_col = 0 113 | self.sig_init_val_col = 0 114 | self.sig_unit_col = 0 115 | self.sig_val_col = 0 116 | self.nodes = {} 117 | 118 | self.start_row = 0 # start row number of valid data 119 | 120 | def members(self): 121 | return sorted(vars(self).items(), key=lambda item:item[1]) 122 | 123 | def __str__(self): 124 | s = [] 125 | for key, var in self.members(): 126 | if type(var) == int: 127 | s.append(" %s : %d (%s)" % (key, var, get_xls_col(var))) 128 | return '\n'.join(s) 129 | 130 | 131 | def get_xls_col(val): 132 | """ 133 | get_xls_col(col) --> string 134 | 135 | Convert int column number to excel column symbol like A, B, AB .etc 136 | """ 137 | if type(val) == type(0): 138 | if val <= 25: 139 | s = chr(val+0x41) 140 | elif val <100: 141 | s = chr(val/26-1+0x41)+chr(val%26+0x41) 142 | else: 143 | raise ValueError("column number too large: ", str(val)) 144 | return s 145 | else: 146 | raise TypeError("column number only support int: ", str(val)) 147 | 148 | 149 | def get_list_item(list_object): 150 | """ 151 | get_list_item(list_object) --> object(string) 152 | 153 | Show object list to terminal and get selection object from ternimal. 154 | """ 155 | list_index = 0 156 | for item in list_object: 157 | print " %2d %s" %(list_index, item) 158 | list_index += 1 159 | while True: 160 | user_input = raw_input() 161 | if user_input.isdigit(): 162 | select_index = int(user_input) 163 | if select_index < len(list_object): 164 | return list_object[select_index] 165 | else: 166 | print "input over range" 167 | else: 168 | print "input invalid" 169 | 170 | 171 | def parse_sheetname(workbook): 172 | """ 173 | Get sheet name of can matrix in the xls workbook. 174 | Only informations in this sheet are used. 175 | 176 | """ 177 | sheets = [] 178 | for sheetname in workbook.sheet_names(): 179 | if sheetname == "Matrix": 180 | return sheetname 181 | if sheetname not in MATRIX_SHEET_IGNORE: 182 | sheets.append(sheetname) 183 | if len(sheets)==1: 184 | return sheets[0] 185 | elif len(sheets)>=2: 186 | print "Select one sheet blow:" 187 | # print " "," ".join(sheets) 188 | # return raw_input() 189 | return get_list_item(sheets) 190 | else: 191 | print "Select one sheet blow:" 192 | # print " "," ".join(workbook.sheet_names()) 193 | # return raw_input() 194 | return get_list_item(workbook.sheet_names()) 195 | 196 | 197 | def parse_template(sheet): 198 | """ 199 | parse_template(sheet) -> MatrixTemplate 200 | 201 | Parse column headers of xls sheet and the result of column numbers is 202 | returned as MatrixTemplate object 203 | """ 204 | # find table header row 205 | header_row_num = 0xFFFF 206 | for row_num in range(0, sheet.nrows): 207 | if sheet.row_values(row_num)[0].find("Msg Name") != -1: 208 | #print "table header row number: %d" % row_num 209 | header_row_num = row_num 210 | if header_row_num == 0xFFFF: 211 | raise ValueError("Can't find \"Msg Name\" in this sheet") 212 | # get header info 213 | template = MatrixTemplate() 214 | for col_num in range(0, sheet.ncols): 215 | value = sheet.row_values(header_row_num)[col_num] 216 | if value is not None: 217 | value = value.replace(" ","") 218 | for col_name in MATRIX_TEMPLATE_MAP.keys(): 219 | for col_header in MATRIX_TEMPLATE_MAP[col_name]: 220 | if col_header in value and getattr(template,col_name)==0: 221 | setattr(template, col_name, col_num) 222 | break 223 | template.start_row = header_row_num + 1 224 | # get ECU nodes 225 | node_start_col = template.sig_val_col 226 | for col in range(node_start_col, sheet.ncols): 227 | value = sheet.row_values(header_row_num)[col] 228 | if value is not None and value != '': 229 | value = value.replace(" ","") 230 | if len(value) <= NODE_NAME_MAX: 231 | template.nodes[value] = col 232 | # print "detected nodes: ", template.nodes 233 | return template 234 | 235 | 236 | def parse_sig_vals(val_str): 237 | """ 238 | parse_sig_vals(val_str) -> {valuetable} 239 | 240 | Get signal key:value pairs from string. Returns None if failed. 241 | """ 242 | vals = {} 243 | if val_str is not None and val_str != '': 244 | token = re.split('[\;\:\\n]+', val_str.strip()) 245 | if len(token) >= 2: 246 | if len(token) % 2 == 0: 247 | for i in range(0, len(token), 2): 248 | try: 249 | val = getint(token[i]) 250 | desc = token[i + 1].replace('\"','').strip() 251 | vals[desc] = val 252 | except ValueError: 253 | # print "waring: ignored signal value definition: " ,token[i], token[i+1] 254 | raise 255 | return vals 256 | else: 257 | # print "waring: ignored signal value description: ", val_str 258 | raise ValueError() 259 | else: 260 | raise ValueError(val_str) 261 | else: 262 | return None 263 | 264 | 265 | def getint(str, default=None): 266 | """ 267 | getint(str) -> int 268 | 269 | Convert string to int number. If default is given, default value is returned 270 | while str is None. 271 | """ 272 | if str == '': 273 | if default==None: 274 | raise ValueError("None type object is unexpected") 275 | else: 276 | return default 277 | else: 278 | try: 279 | val = int(str) 280 | return val 281 | except (ValueError, TypeError): 282 | try: 283 | val = int(str, 16) 284 | return val 285 | except: 286 | raise 287 | 288 | def motorola_msb_2_motorola_backward(start_bit, sig_size, frame_size): 289 | msb_bytes = start_bit//8 290 | msb_byte_bit = start_bit%8 291 | byte_remain_bits = msb_byte_bit + 1 292 | remain_bits = sig_size 293 | backward_start_bit = (frame_size *8 -1) - (8 * msb_bytes) - (7 - msb_byte_bit) 294 | while remain_bits > 0: 295 | if remain_bits > byte_remain_bits: 296 | remain_bits -= byte_remain_bits 297 | backward_start_bit -= byte_remain_bits 298 | byte_remain_bits = 8 299 | else: 300 | backward_start_bit = backward_start_bit - remain_bits + 1 301 | remain_bits = 0 302 | return backward_start_bit 303 | 304 | class CanNetwork(object): 305 | def __init__(self, init=True): 306 | self.nodes = [] 307 | self.messages = [] 308 | self.name = 'CAN' 309 | self.val_tables = [] 310 | self.version = '' 311 | self.new_symbols = NEW_SYMBOLS 312 | self.attr_defs = [] 313 | self._filename = '' 314 | 315 | if init: 316 | self._init_attr_defs() 317 | 318 | def _init_attr_defs(self): 319 | for attr_def in ATTR_DEFS_INIT: 320 | self.attr_defs.append(CanAttribution(attr_def[1], attr_def[0], attr_def[2], attr_def[3], attr_def[4], 321 | attr_def[5], attr_def[6])) 322 | 323 | def __str__(self): 324 | # ! version 325 | lines = ['VERSION ' + r'""'] 326 | lines.append('\n\n\n') 327 | 328 | # ! new_symbols 329 | lines.append('NS_ :\n') 330 | for symbol in self.new_symbols: 331 | lines.append(' ' + symbol + '\n') 332 | lines.append('\n') 333 | 334 | # ! bit_timming 335 | lines.append("BS_:\n") 336 | lines.append('\n') 337 | 338 | # ! nodes 339 | line = ["BU_:"] 340 | for node in self.nodes: 341 | line.append(node) 342 | lines.append(' '.join(line) + '\n\n\n') 343 | 344 | # ! messages 345 | for msg in self.messages: 346 | lines.append(str(msg) + '\n\n') 347 | lines.append('\n\n') 348 | 349 | # ! comments 350 | lines.append('''CM_ " "''' + ";" + "\n") 351 | for msg in self.messages: 352 | # # todo 353 | for sig in msg.signals: 354 | comment = sig.comment 355 | if comment != "": 356 | line = ["CM_", "SG_", str(msg.msg_id), sig.name, "\"" + comment + "\"" + ";"] 357 | lines.append(" ".join(line) + '\n') 358 | 359 | # ! attribution defines 360 | for attr_def in self.attr_defs: 361 | line = ["BA_DEF_"] 362 | obj_type = attr_def.object_type 363 | if (obj_type == "Node"): 364 | line.append("BU_") 365 | elif (obj_type == "Message"): 366 | line.append("BO_") 367 | elif (obj_type == "Signal"): 368 | line.append("SG_") 369 | ##elif (obj_type == "Network") 370 | line.append(" \"" + attr_def.name + "\"") 371 | val_type = attr_def.value_type 372 | if (val_type == "Enumeration"): 373 | line.append("ENUM") 374 | val_range = [] 375 | for val in attr_def.values: 376 | val_range.append("\"" + val + "\"") 377 | line.append(",".join(val_range) + ";") 378 | elif (val_type == "String"): 379 | line.append("STRING" + " " + ";") 380 | elif (val_type == "Hex"): 381 | line.append("HEX") 382 | line.append(str(attr_def.min)) 383 | line.append(str(attr_def.max) + ";") 384 | elif (val_type == "Integer"): 385 | line.append("INT") 386 | line.append(str(attr_def.min)) 387 | line.append(str(attr_def.max) + ";") 388 | lines.append(" ".join(line) + '\n') 389 | 390 | # ! attribution default values 391 | for attr_def in self.attr_defs: 392 | line = ["BA_DEF_DEF_"] 393 | line.append(" \"" + attr_def.name + "\"") 394 | val_type = attr_def.value_type 395 | if (val_type == "Enumeration"): 396 | line.append("\"" + attr_def.default + "\"" + ";") 397 | elif (val_type == "String"): 398 | line.append("\"" + attr_def.default + "\"" + ";") 399 | elif (val_type == "Hex"): 400 | line.append(str(attr_def.default) + ";") 401 | elif (val_type == "Integer"): 402 | line.append(str(attr_def.default) + ";") 403 | lines.append(" ".join(line) + '\n') 404 | 405 | # ! attribution value of object 406 | # build-in value "DBName" 407 | line = ["BA_"] 408 | line.append('''"DBName"''') 409 | line.append(self.name + ";") 410 | lines.append(" ".join(line) + '\n') 411 | # ! message attribution values 412 | for msg in self.messages: 413 | for attr_def in self.attr_defs: 414 | if (msg.attrs.has_key(attr_def.name)): 415 | if (msg.attrs[attr_def.name] != attr_def.default): 416 | line = ["BA_"] 417 | line.append("\"" + attr_def.name + "\"") 418 | line.append("BO_") 419 | line.append(str(msg.msg_id)) 420 | if (attr_def.value_type == "Enumeration"): 421 | # write enum index instead of enum value 422 | line.append(str(attr_def.values.index(str(msg.attrs[attr_def.name]))) + ";") 423 | else: 424 | line.append(str(msg.attrs[attr_def.name]) + ";") 425 | lines.append(" ".join(line) + '\n') 426 | # ! signal attribution values 427 | for msg in self.messages: 428 | for sig in msg.signals: 429 | if sig.init_val is not None and sig.init_val is not 0: 430 | line = ["BA_"] 431 | line.append('''"GenSigStartValue"''') 432 | line.append("SG_") 433 | line.append(str(msg.msg_id)) 434 | line.append(sig.name) 435 | line.append(str(sig.init_val)) 436 | line.append(';') 437 | lines.append(' '.join(line) + '\n') 438 | # ! Value table define 439 | for msg in self.messages: 440 | for sig in msg.signals: 441 | if sig.values is not None and len(sig.values) >= 1: 442 | line = ['VAL_'] 443 | line.append(str(msg.msg_id)) 444 | line.append(sig.name) 445 | for key in sig.values: 446 | line.append(str(sig.values[key])) 447 | line.append('"'+ key +'"') 448 | line.append(';') 449 | lines.append(' '.join(line) + '\n') 450 | return ''.join(lines) 451 | 452 | def add_attr_def(self, name, object_type, value_type, minvalue, maxvalue, default, values=None): 453 | index = None 454 | for i in range (0, len(self.attr_defs)): 455 | if self.attr_defs[i].name == name: 456 | index = i 457 | break 458 | if index is None: 459 | attr_def = CanAttribution(name, object_type, value_type, minvalue, maxvalue, default, values) 460 | self.attr_defs.append(attr_def) 461 | else: 462 | print("info: override default attribution definition \'{}\'".format(name)) 463 | self.attr_defs[index].object_type = object_type 464 | self.attr_defs[index].value_type = value_type 465 | self.attr_defs[index].min = minvalue 466 | self.attr_defs[index].max = maxvalue 467 | self.attr_defs[index].default = default 468 | self.attr_defs[index].values = values 469 | #print(self.attr_defs[index]) 470 | 471 | def get_attr_def(self, name): 472 | ret = None 473 | for attr_def in self.attr_defs: 474 | if attr_def.name == name: 475 | ret = attr_def 476 | return ret 477 | 478 | def set_msg_attr(self, msg_id, attr_name, value): 479 | for msg in self.messages: 480 | if msg.msg_id == msg_id: 481 | msg.set_attr(attr_name, value) 482 | 483 | def get_msg_attr(self, msg_id, attr_name): 484 | value = None 485 | for msg in self.messages: 486 | if msg.msg_id == msg_id: 487 | if msg.attrs.has_key(attr_name): 488 | value = msg.attrs[attr_name] 489 | else: 490 | attr_def = self.get_attr_def(attr_name) 491 | if attr_def is not None: 492 | value = attr_def.default 493 | return value 494 | 495 | def set_sig_attr(self, msg_id, sig_name, attr_name, attr_value): 496 | for msg in self.messages: 497 | if msg.msg_id == msg_id: 498 | for sig in msg.signals: 499 | if sig.name == sig_name: 500 | sig.set_attr(attr_name, attr_value) 501 | 502 | 503 | def convert_attr_def_value(self, attr_def_name, value_str): 504 | ''' 505 | Convert string to a value with AttributionDefinition's data type. 506 | 507 | Except: 508 | ValueError: attribuiton definition is not exit, or value type 509 | of AttrDef is not support yet. 510 | ''' 511 | attr_def_value_type = None 512 | for attr_def in self.attr_defs: 513 | if attr_def.name == attr_def_name: 514 | attr_def_value_type = attr_def.value_type 515 | attr_def_values = attr_def.values 516 | break 517 | if attr_def_value_type == 'Integer': 518 | value = int(value_str) 519 | elif attr_def_value_type == 'Float': 520 | value = float(value_str) 521 | elif attr_def_value_type == 'String': 522 | value = value_str 523 | elif attr_def_value_type == 'Enumeration': 524 | if value_str.isdigit(): 525 | value = attr_def_values[int(value_str)] 526 | else: 527 | value = value_str[1:-1] 528 | elif attr_def_value_type == 'Hex': 529 | value = int(value_str) 530 | elif attr_def_value_type is None: 531 | raise ValueError("Undefined attribution definition: {}".format(attr_def_name)) 532 | else: 533 | raise ValueError("Unkown attribution definition value type: {}".format(attr_def_value_type)) 534 | return value 535 | 536 | def sort(self, option='id'): 537 | messages = self.messages 538 | if option == 'id': 539 | # sort by msg_id, id is treated as string, NOT numbers, to keep the same with candb++ 540 | messages.sort(key=lambda msg: str(msg.msg_id)) 541 | for msg in messages: 542 | signals = msg.signals 543 | signals.sort(key=lambda sig: sig.start_bit) 544 | elif option == 'name': 545 | messages.sort(key=lambda msg: msg.name) 546 | else: 547 | raise ValueError("Invalid sort option \'{}\'".format(option)) 548 | 549 | def load(self, path): 550 | dbc = open(path, 'r') 551 | 552 | for line in dbc: 553 | line_trimmed = line.strip() 554 | line_split = re.split('[\s\(\)\[\]\|\,\:\@\;]+', line_trimmed) 555 | if len(line_split) >= 2: 556 | # Node 557 | if line_split[0] == 'BU_': 558 | self.nodes.extend(line_split[1:]) 559 | # Message 560 | elif line_split[0] == 'BO_': 561 | msg = CanMessage() 562 | msg.msg_id = int(line_split[1]) 563 | msg.name = line_split[2] 564 | msg.dlc = int(line_split[3]) 565 | msg.sender = line_split[4] 566 | self.messages.append(msg) 567 | # Signal 568 | elif line_split[0] == 'SG_': 569 | sig = CanSignal() 570 | sig.name = line_split[1] 571 | sig.start_bit = int(line_split[2]) 572 | sig.sig_len = int(line_split[3]) 573 | sig.byte_order = line_split[4][:1] 574 | sig.value_type = line_split[4][1:] 575 | sig.factor = line_split[5] 576 | sig.offset = line_split[6] 577 | sig.min = line_split[7] 578 | sig.max = line_split[8] 579 | sig.unit = line_split[9][1:-1] # remove quotation makes 580 | sig.receivers = line_split[10:] # receiver is a list 581 | msg.signals.append(sig) 582 | # Attribution Definition 583 | elif line_split[0] == 'BA_DEF_': 584 | attr_def_object = line_split[1] 585 | if attr_def_object == 'BO_': 586 | attr_def_object_type = 'Message' 587 | attr_def_name_offset = 2 588 | elif attr_def_object == 'SG_': 589 | attr_def_object_type = 'Signal' 590 | attr_def_name_offset = 2 591 | else: # Network 592 | attr_def_object_type = 'Network' 593 | attr_def_name_offset = 1 594 | 595 | attr_def_name = line_split[attr_def_name_offset][1:-1] # remove quotation makes 596 | attr_def_value_type = line_split[attr_def_name_offset + 1] 597 | attr_def_default = '' 598 | 599 | if attr_def_value_type == 'ENUM': 600 | attr_def_value_type_str = 'Enumeration' 601 | attr_def_value_min = '' 602 | attr_def_value_max = '' 603 | attr_def_values = map(lambda val:val[1:-1], line_split[attr_def_name_offset + 2:]) #remove " 604 | elif attr_def_value_type == 'FLOAT': 605 | attr_def_value_type_str = 'Float' 606 | attr_def_value_min = float(line_split[attr_def_name_offset + 2]) 607 | attr_def_value_max = float(line_split[attr_def_name_offset + 3]) 608 | attr_def_values = [] 609 | elif attr_def_value_type == 'INT': 610 | attr_def_value_type_str = 'Integer' 611 | attr_def_value_min = int(float(line_split[attr_def_name_offset + 2])) 612 | attr_def_value_max = int(float(line_split[attr_def_name_offset + 3])) 613 | attr_def_values = [] 614 | elif attr_def_value_type == 'HEX': 615 | attr_def_value_type_str = 'Hex' 616 | attr_def_value_min = int(line_split[attr_def_name_offset + 2]) 617 | attr_def_value_max = int(line_split[attr_def_name_offset + 3]) 618 | attr_def_values = [] 619 | elif attr_def_value_type == 'STRING': 620 | attr_def_value_type_str = 'String' 621 | attr_def_value_min = '' 622 | attr_def_value_max = '' 623 | attr_def_values = [] 624 | else: 625 | raise ValueError("Unkown attribution definition value type: {}".format(attr_def_value_type)) 626 | 627 | self.add_attr_def(attr_def_name, attr_def_object_type, attr_def_value_type_str, \ 628 | attr_def_value_min, attr_def_value_max, attr_def_default, attr_def_values) 629 | # Attribution Definition Default Value 630 | elif line_split[0] == 'BA_DEF_DEF_': 631 | attr_def_name = line_split[1][1:-1] #remove " 632 | attr_def_value_type_str = None 633 | attr_def_default = self.convert_attr_def_value(attr_def_name, line_split[2]) 634 | attr_def = self.get_attr_def(attr_def_name) 635 | if attr_def is not None: 636 | attr_def.default = attr_def_default 637 | # Object Attribution definition 638 | elif line_split[0] == 'BA_': 639 | attr_name = line_split[1][1:-1] # remove " 640 | attr_object = line_split[2] 641 | if attr_object == 'BO_': 642 | attr_msg_id = int(line_split[3]) 643 | attr_value = self.convert_attr_def_value(attr_name, line_split[4]) 644 | if attr_value is not None: 645 | self.set_msg_attr(attr_msg_id, attr_name, attr_value) 646 | else: 647 | raise ValueError("Msg \'{}\' attribuition \'{}\' value is {}".format(attr_msg_id, attr_name, line_split[4])) 648 | elif attr_object == 'SG_': 649 | pass 650 | # Value Tables 651 | elif line_split[0] == 'VAL_': 652 | quote_split = re.split('\s*\"\s*', line_trimmed) 653 | line_split = re.split('\s+', quote_split[0]) 654 | line_split.extend(quote_split[1:-1]) 655 | val_msg_id = int(line_split[1]) 656 | val_sig_name = line_split[2] 657 | values = {} 658 | for i in range(3, len(line_split)-1, 2): 659 | try: 660 | values[line_split[i+1]] = int(line_split[i]) 661 | except: 662 | print(line_split) 663 | raise 664 | self.set_sig_attr(val_msg_id, val_sig_name, 'values', values) 665 | 666 | dbc.close() 667 | 668 | def save(self, path=None): 669 | if (path == None): 670 | file = open(self._filename + ".dbc", "w") 671 | else: 672 | file = open(path, 'w') 673 | # file.write(unicode.encode(str(self), "utf-8")) 674 | file.write(str(self)) 675 | 676 | def import_excel(self, path, sheetname=None, template=None): 677 | # Open file 678 | book = xlrd.open_workbook(path) 679 | # open sheet 680 | if sheetname is not None: 681 | print "use specified sheet: ", sheetname 682 | sheet = book.sheet_by_name(sheetname) 683 | else: 684 | sheetname = parse_sheetname(book) 685 | print "select sheet: ", sheetname 686 | sheet = book.sheet_by_name(sheetname) 687 | 688 | # import template 689 | if template is not None: 690 | print 'use specified template: ', template 691 | import_string = "import templates." + template + " as template" 692 | exec import_string 693 | else: 694 | print "parse template" 695 | template = parse_template(sheet) 696 | if debug_enable: 697 | print template 698 | # ! load network information 699 | filename = os.path.basename(path).split(".") 700 | self._filename = ".".join(filename[:-1]) 701 | #self.name = ".".join(filename[:-1]).replace(" ", "_").replace('.', '_').replace('-', '_') # use filename as default DBName 702 | self.name = "CAN" 703 | 704 | # ! load nodes information 705 | self.nodes = template.nodes.keys() 706 | 707 | # ! load messages 708 | messages = self.messages 709 | nrows = sheet.nrows 710 | for row in range(template.start_row, nrows): 711 | row_values = sheet.row_values(row) 712 | msg_name = row_values[template.msg_name_col] 713 | if (msg_name != ''): 714 | # This row defines a message! 715 | message = CanMessage() 716 | signals = message.signals 717 | message.name = msg_name.replace(' ', '') 718 | # message.type = row_values[template.msg_type_col] # todo: should set candb attribution instead 719 | message.msg_id = getint(row_values[template.msg_id_col]) 720 | message.dlc = getint(row_values[template.msg_len_col]) 721 | send_type = row_values[template.msg_send_type_col].upper().strip() 722 | if (send_type == "CYCLE") or (send_type == "CE"): # todo: CE is treated as cycle 723 | try: 724 | msg_cycle = getint(row_values[template.msg_cycle_col]) 725 | except ValueError: 726 | print "warning: message %s\'s cycle time \"%s\" is invalid, auto set to \'0\'" % (message.name, row_values[template.msg_cycle_col]) 727 | msg_cycle = 0 728 | message.set_attr("GenMsgCycleTime", msg_cycle) 729 | message.set_attr("GenMsgSendType", "cycle") 730 | else: 731 | message.set_attr("GenMsgSendType", "NoSendType") 732 | 733 | # message sender 734 | message.sender = None 735 | for nodename in template.nodes: 736 | nodecol = template.nodes[nodename] 737 | sender = row_values[nodecol].strip().upper() 738 | if sender == "S": 739 | message.sender = nodename 740 | elif sender == "R": 741 | message.receivers.append(nodename) 742 | if message.sender is None: 743 | message.sender = 'Vector__XXX' 744 | messages.append(message) 745 | else: 746 | sig_name = row_values[template.sig_name_col] 747 | if (sig_name != ''): 748 | # This row defines a signal! 749 | signal = CanSignal() 750 | signal.name = sig_name.replace(' ', '') 751 | signal.start_bit = getint(row_values[template.sig_start_bit_col]) 752 | signal.sig_len = getint(row_values[template.sig_len_col]) 753 | 754 | byte_order_type = row_values[template.sig_byte_order_col] 755 | if (byte_order_type.upper() == "MOTOROLA LSB"): # todo 756 | signal.byte_order = '1' 757 | elif (byte_order_type.upper() == "MOTOROLA MSB"): 758 | signal.byte_order = '0' 759 | else: 760 | signal.byte_order = '0' 761 | raise ValueError("Unknown signal byte order type: \"%s\""%byte_order_type) # todo: intel 762 | 763 | if (row_values[template.sig_value_type_col].upper() == "UNSIGNED"): 764 | signal.value_type = '+' 765 | else: 766 | signal.value_type = '-' 767 | 768 | signal.factor = row_values[template.sig_factor_col] 769 | signal.offset = row_values[template.sig_offset_col] 770 | signal.min = row_values[template.sig_min_phys_col] 771 | signal.max = row_values[template.sig_max_phys_col] 772 | signal.unit = row_values[template.sig_unit_col] 773 | signal.init_val = getint((row_values[template.sig_init_val_col]), 0) 774 | try: 775 | signal.values = parse_sig_vals(row_values[template.sig_val_col]) 776 | except ValueError: 777 | if debug_enable: 778 | print "warning: signal %s\'s value table is ignored" % signal.name 779 | else: 780 | pass 781 | signal.comment = row_values[template.sig_comment_col].replace("\"", "\'").replace("\r", '\n') # todo 782 | # get signal receivers 783 | signal.receivers = [] 784 | for nodename in template.nodes: 785 | nodecol = template.nodes[nodename] 786 | receiver = row_values[nodecol].strip().upper() 787 | if receiver == "R": 788 | signal.receivers.append(nodename) 789 | elif receiver == "S": 790 | if message.sender == 'Vector__XXX': 791 | message.sender = nodename 792 | print "warning: message %s\'s sender is set to \"%s\" via signal" %(message.name, nodename) 793 | else: 794 | print "warning: message %s\'s sender is conflict to signal \"%s\"" %(message.name, nodename) 795 | else: 796 | pass 797 | if len(signal.receivers) == 0: 798 | if len(message.receivers) > 0: 799 | signal.receivers = message.receivers 800 | else: 801 | signal.receivers.append('Vector__XXX') 802 | signals.append(signal) 803 | self.sort(); 804 | 805 | 806 | class CanMessage(object): 807 | def __init__(self, name='', msg_id=0, dlc=8, sender='Vector__XXX'): 808 | ''' 809 | name: message name 810 | id: message id (11bit or 29 bit) 811 | dlc: message data length 812 | sender: message send node 813 | ''' 814 | self.name = name 815 | self.msg_id = msg_id 816 | self.send_type = '' 817 | self.dlc = dlc 818 | self.sender = sender 819 | self.signals = [] 820 | self.attrs = {} 821 | self.receivers = [] 822 | 823 | def __str__(self): 824 | para = [] 825 | line = ["BO_", str(self.msg_id), self.name + ":", str(self.dlc), self.sender] 826 | para.append(" ".join(line)) 827 | for sig in self.signals: 828 | para.append(str(sig)) 829 | return '\n '.join(para) 830 | 831 | def set_attr(self, name, value): 832 | self.attrs[name] = value 833 | 834 | 835 | class CanSignal(object): 836 | def __init__(self, name='', start_bit=0, sig_len=1, init_val=0): 837 | self.name = name 838 | self.start_bit = start_bit 839 | self.sig_len = sig_len 840 | self.init_val = init_val 841 | self.byte_order = '0' 842 | self.value_type = '+' 843 | self.factor = 1 844 | self.offset = 0 845 | self.min = 0 846 | self.max = 1 847 | self.unit = '' 848 | self.values = {} 849 | self.receivers = [] 850 | self.comment = '' 851 | 852 | def __str__(self): 853 | line = ["SG_", self.name, ":", 854 | str(self.start_bit) + "|" + str(self.sig_len) + "@" + self.byte_order + self.value_type, 855 | "(" + str(self.factor) + "," + str(self.offset) + ")", "[" + str(self.min) + "|" + str(self.max) + "]", 856 | "\"" + str(self.unit) + "\"", ','.join(self.receivers)] 857 | return " ".join(line) 858 | 859 | def set_attr(self, name, value): 860 | if name == 'values': 861 | self.values = value 862 | else: 863 | raise ValueError("Unsupport set attr of \'{}\'".format(name)) 864 | 865 | def get_attr(self, name): 866 | if name == 'values': 867 | return self.values 868 | else: 869 | raise ValueError("Unsupport set attr of \'{}\'".format(name)) 870 | 871 | 872 | class CanAttribution(object): 873 | def __init__(self, name, object_type, value_type, minvalue, maxvalue, default, values=None): 874 | self.name = name 875 | self.object_type = object_type 876 | self.value_type = value_type 877 | self.min = minvalue 878 | self.max = maxvalue 879 | self.default = default 880 | self.values = values 881 | 882 | def __str__(self): 883 | line = ["name: {}".format(self.name)] 884 | line.append("object type: {}".format(self.object_type)) 885 | line.append("value type: {}".format(self.value_type)) 886 | line.append("minimum: {}".format(self.min)) 887 | line.append("maximum: {}".format(self.max)) 888 | line.append("default: {}".format(self.default)) 889 | line.append("values:") 890 | if self.value_type == "Enumeration" and self.values is not None: 891 | for i in range(0, len(self.values)): 892 | line.append(" {:d}: {}".format(i, self.values[i])) 893 | return '\n'.join(line) 894 | 895 | def parse_args(): 896 | """ 897 | Parse command line commands. 898 | """ 899 | import argparse 900 | parse = argparse.ArgumentParser() 901 | subparser = parse.add_subparsers(title="subcommands") 902 | 903 | parse_gen = subparser.add_parser("gen", help="Generate dbc from excle file") 904 | parse_gen.add_argument("filename", help="The xls file to generate dbc") 905 | parse_gen.add_argument("-s","--sheetname",help="set sheet name of xls",default=None) 906 | parse_gen.add_argument("-t","--template",help="Choose a template",default=None) 907 | parse_gen.add_argument("-d","--debug",help="show debug info",action="store_true", dest="debug_switch", default=False) 908 | parse_gen.set_defaults(func=cmd_gen) 909 | 910 | parse_sort = subparser.add_parser("sort", help="Sort dbc message and signals") 911 | parse_sort.add_argument("filename", help="Dbc filename") 912 | parse_sort.add_argument("-o","--output", help="Specify output file path", default=None) 913 | parse_sort.set_defaults(func=cmd_sort) 914 | 915 | parse_cmp = subparser.add_parser("cmp", help="Compare difference bettween two dbc files") 916 | parse_cmp.add_argument("filename1", help="The base file to be compared with") 917 | parse_cmp.add_argument("filename2", help="The new file to be compared") 918 | parse_cmp.set_defaults(func=cmd_cmp) 919 | 920 | args = parse.parse_args() 921 | args.func(args) 922 | 923 | 924 | def cmd_gen(args): 925 | global debug_enable 926 | debug_enable = args.debug_switch 927 | try: 928 | can = CanNetwork() 929 | can.import_excel(args.filename, args.sheetname, args.template) 930 | can.save() 931 | except IOError,e: 932 | print e 933 | except xlrd.biffh.XLRDError,e: 934 | print e 935 | 936 | 937 | def cmd_sort(args): 938 | can = CanNetwork() 939 | can.load(args.filename) 940 | can.sort() 941 | if args.output is None: 942 | can.save("sorted.dbc") 943 | else: 944 | can.save(args.output) 945 | 946 | 947 | def cmd_cmp(args): 948 | print "Compare function is comming soon!" 949 | 950 | 951 | if __name__ == '__main__': 952 | parse_args() 953 | 954 | 955 | --------------------------------------------------------------------------------