├── 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 |
--------------------------------------------------------------------------------