cannect 1.0.2__py3-none-any.whl → 1.0.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cannect/config.py +2 -3
- cannect/core/can/ascet/comdef.py +15 -15
- cannect/core/can/ascet/comrx.py +4 -4
- cannect/core/can/ascet/diag.py +26 -26
- cannect/core/can/db/_dbc.py +394 -0
- cannect/core/can/db/reader.py +56 -10
- cannect/core/can/db/specification/wrapper.py +37 -38
- cannect/core/can/db/vcs.py +10 -3
- cannect/core/ir/changehistory.py +118 -144
- cannect/core/ir/delivereables.py +4 -9
- cannect/core/ir/diff.py +6 -4
- cannect/core/ir/ir.py +41 -59
- cannect/core/subversion.py +25 -0
- cannect/core/testcase/unitcase.py +1 -1
- cannect/errors.py +6 -0
- cannect/utils/ppt.py +1 -4
- cannect/utils/tools.py +11 -3
- cannect-1.0.4.dist-info/METADATA +655 -0
- {cannect-1.0.2.dist-info → cannect-1.0.4.dist-info}/RECORD +22 -21
- cannect-1.0.2.dist-info/METADATA +0 -214
- {cannect-1.0.2.dist-info → cannect-1.0.4.dist-info}/WHEEL +0 -0
- {cannect-1.0.2.dist-info → cannect-1.0.4.dist-info}/licenses/LICENSE +0 -0
- {cannect-1.0.2.dist-info → cannect-1.0.4.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
from pandas import DataFrame
|
|
2
|
+
import re
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def to_dbc(file:str, df:DataFrame):
|
|
6
|
+
|
|
7
|
+
filtered_df = df
|
|
8
|
+
with open(file, 'w') as f:
|
|
9
|
+
# DBC 파일 기본 헤더 작성
|
|
10
|
+
f.write("VERSION \"\"\n\n\n"
|
|
11
|
+
"NS_ :\n"
|
|
12
|
+
" NS_DESC_\n"
|
|
13
|
+
" CM_\n"
|
|
14
|
+
" BA_DEF_\n"
|
|
15
|
+
" BA_\n"
|
|
16
|
+
" VAL_\n"
|
|
17
|
+
" CAT_DEF_\n"
|
|
18
|
+
" CAT_\n"
|
|
19
|
+
" FILTER\n"
|
|
20
|
+
" BA_DEF_DEF_\n"
|
|
21
|
+
" EV_DATA_\n"
|
|
22
|
+
" ENVVAR_DATA_\n"
|
|
23
|
+
" SGTYPE_\n"
|
|
24
|
+
" SGTYPE_VAL_\n"
|
|
25
|
+
" BA_DEF_SGTYPE_\n"
|
|
26
|
+
" BA_SGTYPE_\n"
|
|
27
|
+
" SIG_TYPE_REF_\n"
|
|
28
|
+
" VAL_TABLE_\n"
|
|
29
|
+
" SIG_GROUP_\n"
|
|
30
|
+
" SIG_VALTYPE_\n"
|
|
31
|
+
" SIGTYPE_VALTYPE_\n"
|
|
32
|
+
" BO_TX_BU_\n"
|
|
33
|
+
" BA_DEF_REL_\n"
|
|
34
|
+
" BA_REL_\n"
|
|
35
|
+
" BA_DEF_DEF_REL_\n"
|
|
36
|
+
" BU_SG_REL_\n"
|
|
37
|
+
" BU_EV_REL_\n"
|
|
38
|
+
" BU_BO_REL_\n"
|
|
39
|
+
" SG_MUL_VAL_\n\n"
|
|
40
|
+
"BS_:\n\n"
|
|
41
|
+
"BU_: \n\n")
|
|
42
|
+
|
|
43
|
+
processed_messages = set() # Message 기준으로 중복 체크 (Signal은 중복 허용)
|
|
44
|
+
|
|
45
|
+
for index, row in filtered_df.iterrows():
|
|
46
|
+
Message_id = row.get("ID", None)
|
|
47
|
+
Message_name = row.get("Message", "Unknown_Message")
|
|
48
|
+
DLC = row.get("DLC", None)
|
|
49
|
+
|
|
50
|
+
Send_ECU = row.get("ECU", None)
|
|
51
|
+
# ECU 컬럼이 공란일 경우 'Blank'로 설정
|
|
52
|
+
Send_ECU = Send_ECU if Send_ECU and str(Send_ECU).strip() else "Blank"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
Message_DLC = int(DLC) if DLC is not None else 8
|
|
57
|
+
except ValueError:
|
|
58
|
+
Message_DLC = 8
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
Message_id = int(Message_id, 16) if Message_id is not None else None
|
|
62
|
+
except ValueError:
|
|
63
|
+
print(f"Warning: Message ID {Message_id} 변환 실패")
|
|
64
|
+
Message_id = None
|
|
65
|
+
|
|
66
|
+
if Message_name not in processed_messages: # Message 기준으로 중복 체크
|
|
67
|
+
f.write(f"BO_ {Message_id} {Message_name}: {Message_DLC} {Send_ECU}\n")
|
|
68
|
+
processed_messages.add(Message_name) # Message 기준으로 추가
|
|
69
|
+
|
|
70
|
+
Signal_Name = row.get("Signal", None)
|
|
71
|
+
if Signal_Name:
|
|
72
|
+
StartBit = row.get("Startbit", 0)#컬럼명 대소문자 구분 필@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
|
73
|
+
Length = row.get("Length", 0)
|
|
74
|
+
ByteOrder = row.get("ByteOrder", "Intel")
|
|
75
|
+
|
|
76
|
+
Endian = 1 if ByteOrder == "Intel" else 0
|
|
77
|
+
|
|
78
|
+
Value_Type = row.get("Value Type", "Unsigned")
|
|
79
|
+
code = "+" if Value_Type == "Unsigned" else "-"
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
StartBit = int(StartBit)
|
|
83
|
+
Length = int(Length)
|
|
84
|
+
Factor = float(row.get("Factor", 1)) # Factor는 float으로 처리
|
|
85
|
+
Offset = float(row.get("Offset", 0))
|
|
86
|
+
|
|
87
|
+
# Min과 Max 값이 비어 있을 경우 기본값 0 설정
|
|
88
|
+
Min = float(row.get("Min", 0) or 0)
|
|
89
|
+
Max = float(row.get("Max", 0) or 0)
|
|
90
|
+
|
|
91
|
+
Unit = row.get("Unit", None) # Unit 값을 가져옴
|
|
92
|
+
|
|
93
|
+
except ValueError:
|
|
94
|
+
Factor = 1.0
|
|
95
|
+
Offset = 0.0
|
|
96
|
+
Min = 0.0
|
|
97
|
+
Max = 0.0
|
|
98
|
+
|
|
99
|
+
# Factor 소수점 확인 후 출력 형식 조정
|
|
100
|
+
if isinstance(Factor, float) and Factor.is_integer():
|
|
101
|
+
Factor = int(Factor) # 소수점이 없으면 int로 변환
|
|
102
|
+
if isinstance(Offset, float) and Offset.is_integer():
|
|
103
|
+
Offset = int(Offset) # 소수점이 없으면 int로 변환
|
|
104
|
+
if isinstance(Min, float) and Min.is_integer():
|
|
105
|
+
Min = int(Min) # 소수점이 없으면 int로 변환
|
|
106
|
+
if isinstance(Max, float) and Max.is_integer():
|
|
107
|
+
Max = int(Max) # 소수점이 없으면 int로 변환
|
|
108
|
+
|
|
109
|
+
Sig_Receivers = row.get("Sig Receivers", "Vector__XXX")
|
|
110
|
+
# 값이 None이 아니고 문자열이 있는 경우 공백 제거
|
|
111
|
+
Sig_Receivers = "".join(Sig_Receivers.split()) if Sig_Receivers and str(
|
|
112
|
+
Sig_Receivers).strip() else "Blank"
|
|
113
|
+
|
|
114
|
+
f.write(f' SG_ {Signal_Name} : {StartBit}|{Length}@{Endian}{code} ({Factor},{Offset}) [{Min}|{Max}] "{Unit}" {Sig_Receivers}\n')
|
|
115
|
+
|
|
116
|
+
else:
|
|
117
|
+
print(f"Warning: Row {index}에 'Signal' 값이 없습니다.")
|
|
118
|
+
f.write("\n")
|
|
119
|
+
|
|
120
|
+
#------------------------------------------------------------------------------------
|
|
121
|
+
|
|
122
|
+
# 중복 방지용 집합
|
|
123
|
+
processed_messages = set() # Message 기준으로 중복 체크
|
|
124
|
+
|
|
125
|
+
for index, row in filtered_df.iterrows():
|
|
126
|
+
Message_name = row.get("Message", "Unknown_Message")
|
|
127
|
+
Message_id = row.get("ID", None)
|
|
128
|
+
|
|
129
|
+
Send_ECU = row.get("ECU", None)
|
|
130
|
+
# ECU 컬럼이 공란일 경우 'Blank'로 설정
|
|
131
|
+
Send_ECU = Send_ECU if Send_ECU and str(Send_ECU).strip() else "Blank"
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
try:
|
|
135
|
+
Message_id = int(Message_id, 16) if Message_id is not None else None
|
|
136
|
+
except ValueError:
|
|
137
|
+
print(f"Warning: Message ID {Message_id} 변환 실패")
|
|
138
|
+
Message_id = None
|
|
139
|
+
|
|
140
|
+
# 중복된 Message_name 방지
|
|
141
|
+
if Message_name not in processed_messages: # Message 기준으로 중복 체크
|
|
142
|
+
f.write(f"\nBO_TX_BU_ {Message_id} : {Send_ECU};")
|
|
143
|
+
processed_messages.add(Message_name) # Message 기준으로 추가
|
|
144
|
+
|
|
145
|
+
f.write("\n\n")
|
|
146
|
+
# ------------------------------------------------------------------------------------
|
|
147
|
+
|
|
148
|
+
processed_messages = set()
|
|
149
|
+
|
|
150
|
+
for index, row in filtered_df.iterrows():
|
|
151
|
+
Message_name = row.get("Message", "Unknown_Message")
|
|
152
|
+
Message_id = row.get("ID", None)
|
|
153
|
+
Send_Type = row.get("Send Type", None)
|
|
154
|
+
|
|
155
|
+
try:
|
|
156
|
+
Message_id = int(Message_id, 16) if Message_id is not None else None
|
|
157
|
+
except ValueError:
|
|
158
|
+
print(f"Warning: Message ID {Message_id} 변환 실패")
|
|
159
|
+
Message_id = None
|
|
160
|
+
|
|
161
|
+
if Message_name not in processed_messages: # Message 기준으로 중복 체크
|
|
162
|
+
# Send_Type에 따른 주석 값 설정
|
|
163
|
+
if Send_Type == "EW":
|
|
164
|
+
send_type_value = "[EW] On Event and On Write"
|
|
165
|
+
elif Send_Type == "P":
|
|
166
|
+
send_type_value = "[P] Periodic"
|
|
167
|
+
elif Send_Type == "EC":
|
|
168
|
+
send_type_value = "[EC] On Event and On Change"
|
|
169
|
+
elif Send_Type == "PE":
|
|
170
|
+
send_type_value = "[PE] Periodic and On Event"
|
|
171
|
+
else:
|
|
172
|
+
send_type_value = ""
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
f.write(f"CM_ BO_ {Message_id} \"{send_type_value}\";\n")
|
|
176
|
+
processed_messages.add(Message_name) # Message 기준으로 추가
|
|
177
|
+
|
|
178
|
+
Signal_Name = row.get("Signal", None)
|
|
179
|
+
if Signal_Name:
|
|
180
|
+
Definition = row.get("Definition", None) # Definition 값을 가져옴
|
|
181
|
+
|
|
182
|
+
if Definition: # Definition 값이 존재할 경우에만 처리
|
|
183
|
+
f.write(f"CM_ SG_ {Message_id} {Signal_Name} \"{Definition}\";\n")
|
|
184
|
+
|
|
185
|
+
f.write("\n")
|
|
186
|
+
# ------------------------------------------------------------------------------------
|
|
187
|
+
|
|
188
|
+
f.write(
|
|
189
|
+
'BA_DEF_ BO_ "VFrameFormat" ENUM "StandardCAN","ExtendedCAN","reserved","reserved","reserved","reserved","reserved","reserved","reserved","reserved","reserved","reserved","reserved","reserved","StandardCAN_FD","ExtendedCAN_FD";\n'
|
|
190
|
+
'BA_DEF_ BO_ "GenMsgSendType" ENUM "Cyclic","NoMsgSendType";\n'
|
|
191
|
+
'BA_DEF_ BO_ "GenMsgCycleTime" INT 0 2000;\n'
|
|
192
|
+
)
|
|
193
|
+
f.write("\n")
|
|
194
|
+
# ------------------------------------------------------------------------------------
|
|
195
|
+
|
|
196
|
+
f.write(
|
|
197
|
+
'BA_DEF_ SG_ "GenSigStartValue" INT 0 1215752192;\n'
|
|
198
|
+
'BA_DEF_ SG_ "UserSigValidity" ENUM "B+", "ACC", "IG", "IG1", "IG2", "IG3", "IG1 or IG2", "IG1 or IG3", "Undef";\n'
|
|
199
|
+
'BA_DEF_ SG_ "GenSigSendType" ENUM "Cyclic", "OnWrite", "OnWriteWithRepetition", "OnChange", "OnChangeWithRepetition", "IfActive", "IfActiveWithRepetition", "NoSigSendType", "OnChangeAndIfActive", "OnChangeAndIfActiveWithRepetition";\n'
|
|
200
|
+
)
|
|
201
|
+
f.write("\n")
|
|
202
|
+
# ------------------------------------------------------------------------------------
|
|
203
|
+
#DBC 내 Networks 컬럼 생성
|
|
204
|
+
f.write(
|
|
205
|
+
'BA_DEF_ "DBName" STRING ;\n'
|
|
206
|
+
'BA_DEF_ "BusType" STRING ;\n'
|
|
207
|
+
'BA_DEF_ "Baudrate" INT 50000 1000000;\n'
|
|
208
|
+
'BA_DEF_ "Manufacturer" STRING ;\n'
|
|
209
|
+
)
|
|
210
|
+
f.write("\n")
|
|
211
|
+
# ------------------------------------------------------------------------------------
|
|
212
|
+
# DBC 내 Networks에 생성된 컬럼의 값 삽입
|
|
213
|
+
# BA_DEF_DEF_ 블록 작성
|
|
214
|
+
f.write(
|
|
215
|
+
'BA_DEF_DEF_ "Manufacturer" "(c)HYUNDAI KEFICO CAN그룹";\n'
|
|
216
|
+
'BA_DEF_DEF_ "DBName" "CANFD_V01";\n'
|
|
217
|
+
'BA_DEF_DEF_ "BusType" "CAN FD";\n'
|
|
218
|
+
'BA_DEF_DEF_ "Baudrate" 500000;\n'
|
|
219
|
+
'BA_DEF_DEF_ "GenSigStartValue" 0;\n'
|
|
220
|
+
'BA_DEF_DEF_ "GenMsgSendType" "NoMsgSendType";\n'
|
|
221
|
+
'BA_DEF_DEF_ "UserSigValidity" "IG";\n'
|
|
222
|
+
'BA_DEF_DEF_ "GenMsgCycleTime" 200;\n'
|
|
223
|
+
)
|
|
224
|
+
f.write("\n")
|
|
225
|
+
# ------------------------------------------------------------------------------------
|
|
226
|
+
# BA_ 블록 작성
|
|
227
|
+
|
|
228
|
+
processed_messages = set()
|
|
229
|
+
|
|
230
|
+
for index, row in filtered_df.iterrows():
|
|
231
|
+
Message_name = row.get("Message", "Unknown_Message")
|
|
232
|
+
Message_id = row.get("ID", None)
|
|
233
|
+
cycle_time = row.get("Cycle Time", None)
|
|
234
|
+
Send_Type = row.get("Send Type", None)
|
|
235
|
+
|
|
236
|
+
try:
|
|
237
|
+
# Convert cycle_time to int, if available
|
|
238
|
+
cycle_time = int(cycle_time) if cycle_time is not None else None
|
|
239
|
+
except ValueError:
|
|
240
|
+
print(f"Warning: Cycle Time {cycle_time} 변환 실패")
|
|
241
|
+
cycle_time = None
|
|
242
|
+
|
|
243
|
+
try:
|
|
244
|
+
Message_id = int(Message_id, 16) if Message_id is not None else None
|
|
245
|
+
except ValueError:
|
|
246
|
+
print(f"Warning: Message ID {Message_id} 변환 실패")
|
|
247
|
+
Message_id = None
|
|
248
|
+
|
|
249
|
+
if Message_name not in processed_messages:
|
|
250
|
+
# Send_Type 값에 따른 처리
|
|
251
|
+
send_type_value = 0 if Send_Type in ["P", "PE"] else 1
|
|
252
|
+
|
|
253
|
+
f.write(
|
|
254
|
+
f'BA_ "GenMsgSendType" BO_ {Message_id} {send_type_value};\n'
|
|
255
|
+
f'BA_ "GenMsgCycleTime" BO_ {Message_id} {cycle_time};\n'
|
|
256
|
+
#f'BA_ "GenMsgILSupport" BO_ {Message_id} 0;\n'
|
|
257
|
+
#f'BA_ "TpMessage" BO_ {Message_id} 1;\n'
|
|
258
|
+
#f'BA_ "GenMsgDelayTime" BO_ {Message_id} 0;\n'
|
|
259
|
+
#f'BA_ "GenMsgStartDelayTime" BO_ {Message_id} 0;\n'
|
|
260
|
+
f'BA_ "VFrameFormat" BO_ {Message_id} 14;\n\n'
|
|
261
|
+
)
|
|
262
|
+
processed_messages.add(Message_name)
|
|
263
|
+
|
|
264
|
+
f.write("\n")
|
|
265
|
+
|
|
266
|
+
# ------------------------------------------------------------------------------------
|
|
267
|
+
# BA_ 블록 작성
|
|
268
|
+
|
|
269
|
+
for index, row in filtered_df.iterrows():
|
|
270
|
+
Message_name = row.get("Message", "Unknown_Message")
|
|
271
|
+
Message_id = row.get("ID", None)
|
|
272
|
+
Signal_Name = row.get("Signal", None)
|
|
273
|
+
GenSigStartValue = row.get("GenSigStartValue", None)
|
|
274
|
+
UserSigValidity = row.get("UserSigValidity", None)
|
|
275
|
+
|
|
276
|
+
if Message_id is not None:
|
|
277
|
+
try:
|
|
278
|
+
# 16진수로 변환
|
|
279
|
+
Message_id = int(Message_id, 16)
|
|
280
|
+
GenSigStartValue = int(GenSigStartValue, 16)
|
|
281
|
+
except ValueError:
|
|
282
|
+
print(f"Warning: Message ID {Message_id} 변환 실패")
|
|
283
|
+
Message_id = None
|
|
284
|
+
GenSigStartValue = None
|
|
285
|
+
|
|
286
|
+
# UserSigValidity 값을 변환
|
|
287
|
+
user_sig_validity_map = {
|
|
288
|
+
"B+": 0,
|
|
289
|
+
"ACC": 1,
|
|
290
|
+
"IG": 2,
|
|
291
|
+
"IG1": 3,
|
|
292
|
+
"IG2": 4,
|
|
293
|
+
"IG3": 5,
|
|
294
|
+
"IG1 or IG2": 6,
|
|
295
|
+
"IG1 or IG3": 7,
|
|
296
|
+
"Undef": 8
|
|
297
|
+
}
|
|
298
|
+
UserSigValidity = user_sig_validity_map.get(UserSigValidity, 8) # 기본값은 8로 설정
|
|
299
|
+
|
|
300
|
+
# 파일에 작성
|
|
301
|
+
if Message_id is not None:
|
|
302
|
+
send_type_value = 0 if Send_Type in ["P", "PE"] else 1
|
|
303
|
+
|
|
304
|
+
f.write(
|
|
305
|
+
#f'BA_ "GenSigSendType" SG_ {Message_id} {Signal_Name} {};\n'
|
|
306
|
+
f'BA_ "GenSigStartValue" SG_ {Message_id} {Signal_Name} {GenSigStartValue};\n'
|
|
307
|
+
f'BA_ "UserSigValidity" SG_ {Message_id} {Signal_Name} {UserSigValidity};\n'
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
f.write("\n")
|
|
311
|
+
# ------------------------------------------------------------------------------------
|
|
312
|
+
|
|
313
|
+
for index, row in filtered_df.iterrows():
|
|
314
|
+
Message_id = row.get("ID", None)
|
|
315
|
+
Signal_Name = row.get("Signal", None)
|
|
316
|
+
value_Table = row.get("Value Table", None)
|
|
317
|
+
|
|
318
|
+
# value_Table이 없으면 해당 행을 건너뛰기
|
|
319
|
+
if value_Table is None or value_Table == "":
|
|
320
|
+
continue
|
|
321
|
+
|
|
322
|
+
# value_Table에서 따옴표(") 제거
|
|
323
|
+
value_Table = value_Table.replace('"', '')
|
|
324
|
+
|
|
325
|
+
# Message ID 16진수 변환
|
|
326
|
+
if Message_id is not None:
|
|
327
|
+
try:
|
|
328
|
+
Message_id = int(Message_id, 16) # 16진수에서 10진수로 변환
|
|
329
|
+
except ValueError:
|
|
330
|
+
print(f"Warning: Message ID {Message_id} 변환 실패")
|
|
331
|
+
Message_id = None
|
|
332
|
+
|
|
333
|
+
# value_Table 값 변환
|
|
334
|
+
if value_Table is not None:
|
|
335
|
+
# 각 항목을 /로 구분
|
|
336
|
+
items = value_Table.split('/')
|
|
337
|
+
|
|
338
|
+
converted_value_table = []
|
|
339
|
+
for item in items:
|
|
340
|
+
# B0:Description 형식 처리 (비트 자리 값)
|
|
341
|
+
match = re.match(r"B(\d+):(.+)", item.strip())
|
|
342
|
+
if match:
|
|
343
|
+
bit_position = int(match.group(1)) # 비트 자리수
|
|
344
|
+
description = match.group(2).strip()
|
|
345
|
+
|
|
346
|
+
# 2의 비트 자리수 승 계산
|
|
347
|
+
bit_value = 2 ** bit_position
|
|
348
|
+
|
|
349
|
+
# 변환된 값과 설명을 리스트에 추가
|
|
350
|
+
converted_value_table.append(f'{bit_value} "{description}"')
|
|
351
|
+
|
|
352
|
+
else:
|
|
353
|
+
# 기존 처리: 0x0~0xFFFF:CRCValue 또는 0x0:Description 형식 처리
|
|
354
|
+
match = re.match(r"(0x[0-9A-F]+)~(0x[0-9A-F]+):(.+)", item.strip())
|
|
355
|
+
if match:
|
|
356
|
+
start_hex = match.group(1)
|
|
357
|
+
end_hex = match.group(2)
|
|
358
|
+
description = match.group(3).strip()
|
|
359
|
+
|
|
360
|
+
# 16진수 값을 10진수로 변환
|
|
361
|
+
start_dec = int(start_hex, 16)
|
|
362
|
+
end_dec = int(end_hex, 16)
|
|
363
|
+
|
|
364
|
+
# 변환된 값과 설명을 리스트에 추가
|
|
365
|
+
converted_value_table.append(
|
|
366
|
+
f'{start_dec} "{start_hex}~{end_hex}:{description}" {end_dec} "{start_hex}~{end_hex}:{description}"')
|
|
367
|
+
else:
|
|
368
|
+
# 0x0:Description 형식 처리 (범위가 없을 경우)
|
|
369
|
+
match = re.match(r"(0x[0-9A-F]+):(.+)", item.strip())
|
|
370
|
+
if match:
|
|
371
|
+
hex_value = match.group(1)
|
|
372
|
+
description = match.group(2).strip()
|
|
373
|
+
|
|
374
|
+
# 16진수 값을 10진수로 변환
|
|
375
|
+
try:
|
|
376
|
+
decimal_value = int(hex_value, 16)
|
|
377
|
+
# 변환된 값과 설명을 리스트에 추가
|
|
378
|
+
converted_value_table.append(f'{decimal_value} "{description}"')
|
|
379
|
+
except ValueError:
|
|
380
|
+
print(f"Warning: {hex_value} 변환 실패")
|
|
381
|
+
continue # 변환 실패 시 해당 항목은 건너뜀
|
|
382
|
+
|
|
383
|
+
# 변환된 값들이 하나도 없으면 해당 행은 표출하지 않음
|
|
384
|
+
if converted_value_table:
|
|
385
|
+
value_table_str = " ".join(converted_value_table)
|
|
386
|
+
else:
|
|
387
|
+
value_table_str = None # 변환된 값이 없으면 value_table_str을 None으로 설정
|
|
388
|
+
|
|
389
|
+
# 파일에 작성 (value_table_str이 None이 아니면 작성)
|
|
390
|
+
if Message_id is not None and Signal_Name and value_table_str:
|
|
391
|
+
f.write(f'VAL_ {Message_id} {Signal_Name} {value_table_str} ;\n')
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
f.write("\n")
|
cannect/core/can/db/reader.py
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
from cannect.config import env
|
|
1
2
|
from cannect.core.can.db.schema import SCHEMA
|
|
2
3
|
from cannect.core.can.db.vcs import CANDBVcs
|
|
4
|
+
from cannect.core.can.db._dbc import to_dbc
|
|
5
|
+
from cannect.errors import CANDBDuplicationError
|
|
3
6
|
from cannect.schema.candb import CanSignal, CanMessage
|
|
4
7
|
from cannect.schema.datadictionary import DataDictionary
|
|
5
8
|
|
|
6
|
-
|
|
7
9
|
from pandas import DataFrame
|
|
8
|
-
from typing import
|
|
10
|
+
from typing import Union
|
|
9
11
|
import pandas as pd
|
|
10
12
|
import os
|
|
11
13
|
|
|
@@ -17,14 +19,14 @@ class CANDBReader:
|
|
|
17
19
|
"""
|
|
18
20
|
def __init__(self, src:Union[str, DataFrame]='', **kwargs):
|
|
19
21
|
if isinstance(src, DataFrame):
|
|
20
|
-
source = kwargs
|
|
21
|
-
traceability = kwargs
|
|
22
|
+
source = kwargs.get('source', 'direct')
|
|
23
|
+
traceability = kwargs.get("traceability",'untraceable')
|
|
22
24
|
__db__ = src.copy()
|
|
23
25
|
else:
|
|
24
26
|
if not str(src):
|
|
25
27
|
src = CANDBVcs().json
|
|
26
28
|
source = src
|
|
27
|
-
traceability = "_".join(os.path.basename(source).split("
|
|
29
|
+
traceability = "_".join(os.path.basename(source).split(".")[:-1])
|
|
28
30
|
__db__ = pd.read_json(source, orient='index')
|
|
29
31
|
|
|
30
32
|
__db__ = __db__[~__db__["Message"].isna()]
|
|
@@ -70,12 +72,17 @@ class CANDBReader:
|
|
|
70
72
|
except AttributeError:
|
|
71
73
|
return self.db.__getattr__(item)
|
|
72
74
|
|
|
75
|
+
def _check_engine_spec(self, engine_spec:str):
|
|
76
|
+
if not engine_spec.lower() in ["ice", "hev"]:
|
|
77
|
+
raise KeyError(f'Unsupported engine_spec: {engine_spec.upper()}')
|
|
78
|
+
return engine_spec.upper()
|
|
79
|
+
|
|
73
80
|
@property
|
|
74
81
|
def messages(self) -> DataDictionary:
|
|
75
82
|
return DataDictionary({msg:CanMessage(df) for msg, df in self.db.groupby(by="Message")})
|
|
76
83
|
|
|
77
84
|
@property
|
|
78
|
-
def signals(self) ->
|
|
85
|
+
def signals(self) -> DataDictionary:
|
|
79
86
|
return DataDictionary({str(sig["Signal"]):CanSignal(sig) for _, sig in self.db.iterrows()})
|
|
80
87
|
|
|
81
88
|
def mode(self, engine_spec:str):
|
|
@@ -84,6 +91,41 @@ class CANDBReader:
|
|
|
84
91
|
def is_developer_mode(self):
|
|
85
92
|
return "Channel" in self.db.columns
|
|
86
93
|
|
|
94
|
+
def to_dbc(self, engine_spec:str, channel:Union[int, str], **kwargs):
|
|
95
|
+
engine_spec = self._check_engine_spec(engine_spec)
|
|
96
|
+
if isinstance(channel, int):
|
|
97
|
+
if engine_spec == "HEV":
|
|
98
|
+
channel = {1:'P', 2:'H', 3:'L'}[channel]
|
|
99
|
+
else:
|
|
100
|
+
channel = {1:'P', 2:'L', 3:'L'}[channel]
|
|
101
|
+
if not channel.upper() in ['P', 'H', 'L']:
|
|
102
|
+
raise KeyError(f'Unsupported channel: {channel.upper()}')
|
|
103
|
+
channel = channel.upper()
|
|
104
|
+
|
|
105
|
+
base = self.mode(engine_spec).db
|
|
106
|
+
base = base[base[f'{engine_spec} Channel'] == channel]
|
|
107
|
+
if 'Codeword' in kwargs:
|
|
108
|
+
base = base[base['Codeword'].str.replace(" ", "") == kwargs['Codeword'].replace(" ", "")]
|
|
109
|
+
if 'SystemConstant' in kwargs:
|
|
110
|
+
base = base[base['SystemConstant'].str.replace(" ", "") == kwargs['SystemConstant'].replace(" ", "")]
|
|
111
|
+
|
|
112
|
+
for _id, df in base.groupby("ID"):
|
|
113
|
+
msg = df["Message"].unique()
|
|
114
|
+
if len(msg) > 1:
|
|
115
|
+
raise CANDBDuplicationError(f'IN CHANNEL: {channel}, ID: "{_id}" IS DUPLICATED BY "{msg.tolist()}", SPECIFY {{Codeword}} or {{SystemConstant}}')
|
|
116
|
+
|
|
117
|
+
if channel == 'L' and engine_spec == 'HEV':
|
|
118
|
+
n_channel = '3'
|
|
119
|
+
else:
|
|
120
|
+
n_channel = {'P': 1, 'H': 2, 'L': '2, 3'}[channel]
|
|
121
|
+
filename = f'{engine_spec}-CAN{n_channel}'
|
|
122
|
+
if kwargs:
|
|
123
|
+
filename += "-" + "-".join([f'{{{val}}}' for val in kwargs.values()])
|
|
124
|
+
filename += '.dbc'
|
|
125
|
+
to_dbc(env.DOWNLOADS / filename, base)
|
|
126
|
+
return
|
|
127
|
+
|
|
128
|
+
|
|
87
129
|
def to_developer_mode(self, engine_spec:str):
|
|
88
130
|
"""
|
|
89
131
|
:param engine_spec: ["ICE", "HEV"]
|
|
@@ -137,12 +179,16 @@ if __name__ == "__main__":
|
|
|
137
179
|
|
|
138
180
|
db = CANDBReader()
|
|
139
181
|
# print(db)
|
|
140
|
-
print(db.source)
|
|
141
|
-
print(db.traceability)
|
|
142
|
-
print(db.revision)
|
|
182
|
+
# print(db.source)
|
|
183
|
+
# print(db.traceability)
|
|
184
|
+
# print(db.revision)
|
|
143
185
|
# print(db.to_developer_mode("ICE").revision)
|
|
144
186
|
# print(db.messages['ABS_ESC_01_10ms'])
|
|
145
187
|
# print(db.columns)
|
|
146
188
|
# print(db.is_developer_mode())
|
|
147
189
|
# db2 = db.to_developer_mode("HEV")
|
|
148
|
-
# print(db2[db2["Message"].str.contains("MCU")])
|
|
190
|
+
# print(db2[db2["Message"].str.contains("MCU")])
|
|
191
|
+
|
|
192
|
+
db.to_dbc("ICE", 1, Codeword="Cfg_CanSTDDB_C==2")
|
|
193
|
+
db.to_dbc("ICE", 2)
|
|
194
|
+
db.to_dbc("ICE", 3)
|
|
@@ -7,9 +7,8 @@ from cannect.utils import tools
|
|
|
7
7
|
|
|
8
8
|
from datetime import datetime
|
|
9
9
|
from docx import Document
|
|
10
|
-
from docx.parts.styles import Styles
|
|
11
10
|
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
|
|
12
|
-
from docx.shared import
|
|
11
|
+
from docx.shared import Inches
|
|
13
12
|
from pandas import DataFrame
|
|
14
13
|
from pathlib import Path
|
|
15
14
|
from tqdm.auto import tqdm
|
|
@@ -22,7 +21,7 @@ class Specification:
|
|
|
22
21
|
def __init__(self, db:Union[CANDBReader, DataFrame]):
|
|
23
22
|
if isinstance(db, DataFrame):
|
|
24
23
|
db = CANDBReader(db)
|
|
25
|
-
self.
|
|
24
|
+
self._db = db
|
|
26
25
|
|
|
27
26
|
if not (env.SVN_CANDB / r"사양서/resource").exists():
|
|
28
27
|
Subversion.update(env.SVN_CANDB / "사양서")
|
|
@@ -35,101 +34,101 @@ class Specification:
|
|
|
35
34
|
os.rename(template.parent / 'default', template.parent / 'default.docx')
|
|
36
35
|
time.sleep(0.5)
|
|
37
36
|
|
|
38
|
-
self.
|
|
39
|
-
self.
|
|
37
|
+
self._doc = doc = Document()
|
|
38
|
+
self._style = CustomStyle(doc)
|
|
40
39
|
return
|
|
41
40
|
|
|
42
|
-
def
|
|
43
|
-
self.
|
|
41
|
+
def _set_title(self):
|
|
42
|
+
self._doc.add_paragraph(
|
|
44
43
|
"\n자체제어기 EMS/ASW\n통신 사양서(CAN-FD)\n\n",
|
|
45
|
-
style=self.
|
|
44
|
+
style=self._style.title
|
|
46
45
|
)
|
|
47
46
|
return
|
|
48
47
|
|
|
49
|
-
def
|
|
48
|
+
def _set_ci(self):
|
|
50
49
|
png = env.SVN_CANDB / r"사양서/resource/ci_cover.png"
|
|
51
|
-
paragraph = self.
|
|
50
|
+
paragraph = self._doc.add_paragraph()
|
|
52
51
|
runner = paragraph.add_run()
|
|
53
52
|
runner.add_picture(str(png), width=Inches(6))
|
|
54
53
|
paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
|
|
55
|
-
self.
|
|
54
|
+
self._doc.add_paragraph("\n", style=self._style.title)
|
|
56
55
|
return
|
|
57
56
|
|
|
58
|
-
def
|
|
57
|
+
def _set_overview(self):
|
|
59
58
|
items = {
|
|
60
|
-
'DATABASE': f"자체제어기/EMS CAN-FD {self.
|
|
59
|
+
'DATABASE': f"자체제어기/EMS CAN-FD {self._db.revision}",
|
|
61
60
|
'COMPANY': env.COMPANY,
|
|
62
61
|
'DIVISION': env.DIVISION,
|
|
63
62
|
'RELEASE': datetime.now().strftime("%Y-%m-%d"),
|
|
64
63
|
}
|
|
65
64
|
|
|
66
|
-
table = self.
|
|
65
|
+
table = self._doc.add_table(rows=len(items), cols=2)
|
|
67
66
|
table.style = 'Table Grid'
|
|
68
67
|
for n, (key, value) in enumerate(items.items()):
|
|
69
68
|
left = table.rows[n].cells[0]
|
|
70
69
|
left.width = Inches(1)
|
|
71
70
|
name = left.paragraphs[0]
|
|
72
|
-
name.style = self.
|
|
71
|
+
name.style = self._style.overview_left
|
|
73
72
|
name.text = key
|
|
74
73
|
|
|
75
74
|
right = table.rows[n].cells[1]
|
|
76
|
-
right.width = self.
|
|
75
|
+
right.width = self._doc.sections[0].page_width
|
|
77
76
|
text = right.paragraphs[0]
|
|
78
|
-
text.style = self.
|
|
77
|
+
text.style = self._style.overview_right
|
|
79
78
|
text.text = value
|
|
80
79
|
return
|
|
81
80
|
|
|
82
|
-
def
|
|
83
|
-
section = self.
|
|
81
|
+
def _set_margin(self):
|
|
82
|
+
section = self._doc.sections[0]
|
|
84
83
|
section.left_margin = \
|
|
85
84
|
section.right_margin = Inches(0.5)
|
|
86
85
|
section.bottom_margin = \
|
|
87
86
|
section.top_margin = Inches(1)
|
|
88
87
|
return
|
|
89
88
|
|
|
90
|
-
def
|
|
91
|
-
header = self.
|
|
89
|
+
def _set_header(self, version:str):
|
|
90
|
+
header = self._doc.sections[0].header
|
|
92
91
|
for paragraph in header.paragraphs:
|
|
93
92
|
p = getattr(paragraph, "_element")
|
|
94
93
|
p.getparent().remove(p)
|
|
95
94
|
|
|
96
|
-
table = header.add_table(rows=1, cols=3, width=self.
|
|
95
|
+
table = header.add_table(rows=1, cols=3, width=self._doc.sections[0].page_width)
|
|
97
96
|
|
|
98
97
|
left = table.rows[0].cells[0].paragraphs[0]
|
|
99
98
|
left.text = "자체제어기 EMS/ASW CAN-FD" + "\n" + f"DOC {version}"
|
|
100
|
-
left.style = self.
|
|
99
|
+
left.style = self._style.header_left
|
|
101
100
|
|
|
102
101
|
right = table.rows[0].cells[2].paragraphs[0]
|
|
103
102
|
right.text = env.DIVISION + "\n" + env.COMPANY
|
|
104
|
-
right.style = self.
|
|
103
|
+
right.style = self._style.header_right
|
|
105
104
|
return
|
|
106
105
|
|
|
107
|
-
def
|
|
108
|
-
footer = self.
|
|
106
|
+
def _set_footer(self):
|
|
107
|
+
footer = self._doc.sections[0].footer
|
|
109
108
|
for paragraph in footer.paragraphs:
|
|
110
109
|
p = getattr(paragraph, "_element")
|
|
111
110
|
p.getparent().remove(p)
|
|
112
|
-
table = footer.add_table(rows=1, cols=1, width=self.
|
|
111
|
+
table = footer.add_table(rows=1, cols=1, width=self._doc.sections[0].page_width)
|
|
113
112
|
cell = table.rows[0].cells[0].paragraphs[0]
|
|
114
113
|
cell.text = env.COPYRIGHT
|
|
115
|
-
cell.style = self.
|
|
114
|
+
cell.style = self._style.footer
|
|
116
115
|
return
|
|
117
116
|
|
|
118
117
|
def generate(self, filename:str):
|
|
119
118
|
if not filename.endswith('.docx'):
|
|
120
119
|
filename += '.docx'
|
|
121
|
-
objs = [(msg, obj) for msg, obj in self.
|
|
120
|
+
objs = [(msg, obj) for msg, obj in self._db.messages.items()]
|
|
122
121
|
objs = sorted(objs, key=lambda x: x[0])
|
|
123
122
|
|
|
124
|
-
self.
|
|
125
|
-
self.
|
|
126
|
-
self.
|
|
127
|
-
self.
|
|
128
|
-
self.
|
|
129
|
-
self.
|
|
123
|
+
self._set_margin()
|
|
124
|
+
self._set_header(self._db.revision)
|
|
125
|
+
self._set_footer()
|
|
126
|
+
self._set_title()
|
|
127
|
+
self._set_ci()
|
|
128
|
+
self._set_overview()
|
|
130
129
|
|
|
131
|
-
self.
|
|
132
|
-
message = Message(self.
|
|
130
|
+
self._doc.add_section()
|
|
131
|
+
message = Message(self._doc)
|
|
133
132
|
message.addHeading("EMS TRANSMIT")
|
|
134
133
|
transmit = tqdm([obj for _, obj in objs if obj["ECU"] == "EMS"])
|
|
135
134
|
for obj in transmit:
|
|
@@ -150,7 +149,7 @@ class Specification:
|
|
|
150
149
|
message.addSignalList(obj)
|
|
151
150
|
message.addSignalProperty(obj)
|
|
152
151
|
|
|
153
|
-
self.
|
|
152
|
+
self._doc.save(env.DOWNLOADS / filename)
|
|
154
153
|
return
|
|
155
154
|
|
|
156
155
|
|