python-can-j1939 0.1.0__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.
- j1939/Dm14Query.py +331 -0
- j1939/Dm14Server.py +399 -0
- j1939/__init__.py +11 -0
- j1939/controller_application.py +363 -0
- j1939/diagnostic_messages.py +448 -0
- j1939/electronic_control_unit.py +499 -0
- j1939/error_info.py +93 -0
- j1939/j1939_21.py +543 -0
- j1939/j1939_22.py +845 -0
- j1939/memory_access.py +387 -0
- j1939/message_id.py +51 -0
- j1939/name.py +268 -0
- j1939/parameter_group_number.py +136 -0
- j1939/version.py +1 -0
- python_can_j1939-0.1.0.dist-info/METADATA +325 -0
- python_can_j1939-0.1.0.dist-info/RECORD +19 -0
- python_can_j1939-0.1.0.dist-info/WHEEL +5 -0
- python_can_j1939-0.1.0.dist-info/licenses/LICENSE +22 -0
- python_can_j1939-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
import j1939
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
logger = logging.getLogger(__name__)
|
|
5
|
+
|
|
6
|
+
class DTC:
|
|
7
|
+
"""
|
|
8
|
+
Parser/encoder for J1939 DTC (Diagnostic Trouble Code).
|
|
9
|
+
|
|
10
|
+
Supports the four SAE J1939-73 SPN conversion methods:
|
|
11
|
+
- CM 1: SPN MSBs in byte 1, mid in byte 2, LSBs+FMI in byte 3, CM bit = 1
|
|
12
|
+
- CM 2: SPN mid in byte 1, MSBs in byte 2, LSBs+FMI in byte 3, CM bit = 1
|
|
13
|
+
- CM 3: SPN LSBs/mid/MSBs in bytes 1/2/3 (modern layout), CM bit = 1
|
|
14
|
+
- CM 4: same byte layout as CM 3, CM bit = 0 (current standard)
|
|
15
|
+
|
|
16
|
+
The on-wire CM bit only distinguishes {1,2,3} (bit=1) from {4} (bit=0).
|
|
17
|
+
CM 1 vs CM 2 vs CM 3 are not separable from the bytes alone; when
|
|
18
|
+
decoding raw bytes with CM bit = 1, the caller must indicate which one
|
|
19
|
+
was used (defaults to CM 3 — the most common legacy layout).
|
|
20
|
+
"""
|
|
21
|
+
def __init__(self, dtc=None, spn=None, fmi=None, oc=0, cm=4):
|
|
22
|
+
if dtc is not None:
|
|
23
|
+
self._cm = cm
|
|
24
|
+
self._dtc = dtc
|
|
25
|
+
self._oc = ((dtc >> 24) & 0x7F)
|
|
26
|
+
cm_bit = ((dtc >> 31) & 0x01)
|
|
27
|
+
b1 = dtc & 0xFF
|
|
28
|
+
b2 = (dtc >> 8) & 0xFF
|
|
29
|
+
b3 = (dtc >> 16) & 0xFF
|
|
30
|
+
self._fmi = b3 & 0x1F
|
|
31
|
+
spn_low3 = (b3 >> 5) & 0x07
|
|
32
|
+
if cm in (3, 4):
|
|
33
|
+
self._spn = b1 | (b2 << 8) | (spn_low3 << 16)
|
|
34
|
+
elif cm == 1:
|
|
35
|
+
# b1 = SPN[18:11], b2 = SPN[10:3], b3[7:5] = SPN[2:0]
|
|
36
|
+
self._spn = (b1 << 11) | (b2 << 3) | spn_low3
|
|
37
|
+
elif cm == 2:
|
|
38
|
+
# b1 = SPN[10:3], b2 = SPN[18:11], b3[7:5] = SPN[2:0]
|
|
39
|
+
self._spn = (b2 << 11) | (b1 << 3) | spn_low3
|
|
40
|
+
else:
|
|
41
|
+
raise ValueError(f"Invalid conversion method: {cm}. Must be 1, 2, 3, or 4.")
|
|
42
|
+
# Sanity-check the CM bit against the requested method
|
|
43
|
+
expected_cm_bit = 0 if cm == 4 else 1
|
|
44
|
+
if cm_bit != expected_cm_bit:
|
|
45
|
+
logger.warning("DM01: CM bit %d does not match requested conversion method %d", cm_bit, cm)
|
|
46
|
+
else:
|
|
47
|
+
if cm not in (1, 2, 3, 4):
|
|
48
|
+
raise ValueError(f"Invalid conversion method: {cm}. Must be 1, 2, 3, or 4.")
|
|
49
|
+
self._spn = spn
|
|
50
|
+
self._fmi = fmi
|
|
51
|
+
self._oc = oc
|
|
52
|
+
self._cm = cm
|
|
53
|
+
if cm == 1:
|
|
54
|
+
b1 = (spn >> 11) & 0xFF
|
|
55
|
+
b2 = (spn >> 3) & 0xFF
|
|
56
|
+
elif cm == 2:
|
|
57
|
+
b1 = (spn >> 3) & 0xFF
|
|
58
|
+
b2 = (spn >> 11) & 0xFF
|
|
59
|
+
else: # cm in (3, 4)
|
|
60
|
+
b1 = spn & 0xFF
|
|
61
|
+
b2 = (spn >> 8) & 0xFF
|
|
62
|
+
b3 = (((spn >> 16) & 0x07) << 5) | (fmi & 0x1F) if cm in (3, 4) \
|
|
63
|
+
else ((spn & 0x07) << 5) | (fmi & 0x1F)
|
|
64
|
+
b4 = oc & 0x7F
|
|
65
|
+
if cm != 4:
|
|
66
|
+
b4 |= 0x80
|
|
67
|
+
self._dtc = b1 | (b2 << 8) | (b3 << 16) | (b4 << 24)
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def spn(self):
|
|
71
|
+
"""
|
|
72
|
+
:return:
|
|
73
|
+
SPN Suspect Parameter Number
|
|
74
|
+
|
|
75
|
+
:rtype: int
|
|
76
|
+
"""
|
|
77
|
+
return self._spn
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def fmi(self):
|
|
81
|
+
"""
|
|
82
|
+
:return:
|
|
83
|
+
FMI Failure Mode Identifier
|
|
84
|
+
|
|
85
|
+
:rtype: int
|
|
86
|
+
"""
|
|
87
|
+
return self._fmi
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def oc(self):
|
|
91
|
+
"""
|
|
92
|
+
:return:
|
|
93
|
+
DTC occurrence counter
|
|
94
|
+
|
|
95
|
+
:rtype: int
|
|
96
|
+
"""
|
|
97
|
+
return self._oc
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def cm(self):
|
|
101
|
+
"""
|
|
102
|
+
:return:
|
|
103
|
+
SPN conversion method (1, 2, 3, or 4 per SAE J1939-73)
|
|
104
|
+
|
|
105
|
+
:rtype: int
|
|
106
|
+
"""
|
|
107
|
+
return self._cm
|
|
108
|
+
|
|
109
|
+
@property
|
|
110
|
+
def dtc(self):
|
|
111
|
+
"""
|
|
112
|
+
:return:
|
|
113
|
+
DTC Diagnostic Trouble Code
|
|
114
|
+
|
|
115
|
+
:rtype: int
|
|
116
|
+
"""
|
|
117
|
+
return self._dtc
|
|
118
|
+
|
|
119
|
+
class DtcLamp:
|
|
120
|
+
"""Diagnostic trouble code lamp status
|
|
121
|
+
"""
|
|
122
|
+
OFF = 0
|
|
123
|
+
ON = 1
|
|
124
|
+
ON_SLOW_FLASH = 2
|
|
125
|
+
ON_FAST_FLASH = 3
|
|
126
|
+
NA = 4
|
|
127
|
+
|
|
128
|
+
_KEYS = ['pl', 'awl', 'rsl', 'mil']
|
|
129
|
+
_DATA_LUT = {OFF: [0,3], ON: [1,3], ON_SLOW_FLASH: [1,0], ON_FAST_FLASH: [1,1], NA: [3,3]}
|
|
130
|
+
|
|
131
|
+
def get_status(self, lamp, flash):
|
|
132
|
+
status = self.NA
|
|
133
|
+
if lamp == 0:
|
|
134
|
+
status = self.OFF
|
|
135
|
+
elif lamp == 1:
|
|
136
|
+
if flash == 0:
|
|
137
|
+
status = self.ON_SLOW_FLASH
|
|
138
|
+
elif flash == 1:
|
|
139
|
+
status = self.ON_FAST_FLASH
|
|
140
|
+
elif flash == 3:
|
|
141
|
+
status = self.ON
|
|
142
|
+
return status
|
|
143
|
+
|
|
144
|
+
def get_data(self, status_dic):
|
|
145
|
+
data = [0]*2
|
|
146
|
+
for idx, lamp_key in enumerate(self._KEYS):
|
|
147
|
+
# initialize not available lamps
|
|
148
|
+
if status_dic.get(lamp_key) == None:
|
|
149
|
+
status_dic[lamp_key] = DtcLamp.OFF
|
|
150
|
+
elif status_dic[lamp_key] not in self._DATA_LUT:
|
|
151
|
+
status_dic[lamp_key] = DtcLamp.OFF
|
|
152
|
+
logger.error("Lamp status n/a")
|
|
153
|
+
lamp, flash = self._DATA_LUT[status_dic[lamp_key]]
|
|
154
|
+
|
|
155
|
+
data[0] |= (lamp << (idx*2))
|
|
156
|
+
data[1] |= (flash << (idx*2))
|
|
157
|
+
|
|
158
|
+
return data
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class Dm1:
|
|
162
|
+
"""Active Diagnostic Trouble Codes (DM1)
|
|
163
|
+
|
|
164
|
+
Parser for DM1
|
|
165
|
+
|
|
166
|
+
DM1 provides diagnostic lamp status and diagnostic trouble codes (DTCs).
|
|
167
|
+
Together, the lamp and DTC information convey the diagnostic condition
|
|
168
|
+
of the transmitting electronic component to other components on the network.
|
|
169
|
+
Occurrence counts may be provided.
|
|
170
|
+
"""
|
|
171
|
+
_msg_subscriber_added = False
|
|
172
|
+
|
|
173
|
+
def __init__(self, ca: j1939.ControllerApplication, rx_cm_bit_set: int = 3):
|
|
174
|
+
"""
|
|
175
|
+
:param obj ca: j1939 controller application
|
|
176
|
+
:param int rx_cm_bit_set:
|
|
177
|
+
SPN conversion method (1, 2, or 3) to assume when a received DTC
|
|
178
|
+
has its CM bit set. The on-wire CM bit cannot distinguish CMs 1,
|
|
179
|
+
2 and 3 — only between {1,2,3} (bit=1) and 4 (bit=0). Defaults to
|
|
180
|
+
3 (the most common legacy layout). CM 4 is auto-detected.
|
|
181
|
+
"""
|
|
182
|
+
if rx_cm_bit_set not in (1, 2, 3):
|
|
183
|
+
raise ValueError(f"rx_cm_bit_set must be 1, 2, or 3 (got {rx_cm_bit_set})")
|
|
184
|
+
self._pgn = j1939.ParameterGroupNumber.PGN.DM01
|
|
185
|
+
self._lamp_status = {}
|
|
186
|
+
self._dtc_dic_list = []
|
|
187
|
+
self._data = []
|
|
188
|
+
self._subscribers = []
|
|
189
|
+
self._ca = ca
|
|
190
|
+
self._rx_cm_bit_set = rx_cm_bit_set
|
|
191
|
+
|
|
192
|
+
def subscribe(self, callback):
|
|
193
|
+
"""Add the given callback to the Dm1 message notification stream.
|
|
194
|
+
|
|
195
|
+
:param callback:
|
|
196
|
+
Function to call when Dm1 message is received.
|
|
197
|
+
"""
|
|
198
|
+
if self._msg_subscriber_added == False:
|
|
199
|
+
self._ca.subscribe(self._receive)
|
|
200
|
+
self._msg_subscriber_added = True
|
|
201
|
+
|
|
202
|
+
self._subscribers.append(callback)
|
|
203
|
+
|
|
204
|
+
def unsubscribe(self, callback):
|
|
205
|
+
"""Stop listening for Dm1 message.
|
|
206
|
+
|
|
207
|
+
:param callback:
|
|
208
|
+
Function to call when Dm1 message is received.
|
|
209
|
+
"""
|
|
210
|
+
self._subscribers.remove(callback)
|
|
211
|
+
|
|
212
|
+
def start_send(self, callback, cycletime=1):
|
|
213
|
+
"""Start cyclic sending of Dm1 message
|
|
214
|
+
|
|
215
|
+
:param callback:
|
|
216
|
+
Function to call before Dm1 message is sent
|
|
217
|
+
:param int cycletime:
|
|
218
|
+
Optional send cycletime
|
|
219
|
+
cycletime is 1s if not specified
|
|
220
|
+
:param int priority:
|
|
221
|
+
priority of Dm1 message
|
|
222
|
+
"""
|
|
223
|
+
cookie = {'cb': callback,}
|
|
224
|
+
self._ca.add_timer(delta_time=cycletime, callback=self._send, cookie=cookie)
|
|
225
|
+
|
|
226
|
+
def stop_send(self):
|
|
227
|
+
"""Stop cyclic sending of Dm1 message
|
|
228
|
+
"""
|
|
229
|
+
self._ca.remove_timer(callback=self._send)
|
|
230
|
+
|
|
231
|
+
@property
|
|
232
|
+
def dtc_dic_list(self):
|
|
233
|
+
"""
|
|
234
|
+
:return:
|
|
235
|
+
list of dictionaries of all DTCs included in DM1
|
|
236
|
+
|
|
237
|
+
:rtype: list of dic: 'spn', 'fmi', 'oc'
|
|
238
|
+
"""
|
|
239
|
+
return self._dtc_dic_list
|
|
240
|
+
|
|
241
|
+
@property
|
|
242
|
+
def lamp_status(self):
|
|
243
|
+
"""
|
|
244
|
+
:return:
|
|
245
|
+
global lamp status for the DM1
|
|
246
|
+
|
|
247
|
+
:rtype: dic: 'pl', 'awl', 'rsl', 'mil'
|
|
248
|
+
"""
|
|
249
|
+
return self._lamp_status
|
|
250
|
+
|
|
251
|
+
@property
|
|
252
|
+
def data(self):
|
|
253
|
+
"""
|
|
254
|
+
:return:
|
|
255
|
+
j1939 pdu payload
|
|
256
|
+
|
|
257
|
+
:rtype: list of int
|
|
258
|
+
"""
|
|
259
|
+
return self._data
|
|
260
|
+
|
|
261
|
+
def _receive(self, priority, pgn, sa, timestamp, data):
|
|
262
|
+
if pgn == self._pgn:
|
|
263
|
+
self._data = data
|
|
264
|
+
self._parse_dm1_receive_data()
|
|
265
|
+
self._notify_subscribers(sa, timestamp)
|
|
266
|
+
|
|
267
|
+
def _send(self, cookie):
|
|
268
|
+
# get dm1 data
|
|
269
|
+
self._lamp_status, self._dtc_dic_list = cookie['cb']()
|
|
270
|
+
|
|
271
|
+
# create payload - lamp status
|
|
272
|
+
self._data = DtcLamp().get_data(self._lamp_status)
|
|
273
|
+
|
|
274
|
+
# create payload - dtc
|
|
275
|
+
for dtc_dic in self._dtc_dic_list:
|
|
276
|
+
# not optional arguments
|
|
277
|
+
if dtc_dic.get('spn') == None:
|
|
278
|
+
continue
|
|
279
|
+
if dtc_dic.get('fmi') == None:
|
|
280
|
+
continue
|
|
281
|
+
# optional arguments
|
|
282
|
+
if dtc_dic.get('oc') == None:
|
|
283
|
+
dtc_dic['oc'] = 0
|
|
284
|
+
cm = dtc_dic.get('cm', 4)
|
|
285
|
+
|
|
286
|
+
dtc = DTC(spn=dtc_dic['spn'], fmi=dtc_dic['fmi'], oc=dtc_dic['oc'], cm=cm).dtc
|
|
287
|
+
self._data.append(dtc & 0xFF)
|
|
288
|
+
self._data.append((dtc >> 8) & 0xFF)
|
|
289
|
+
self._data.append((dtc >> 16) & 0xFF)
|
|
290
|
+
self._data.append((dtc >> 24) & 0xFF)
|
|
291
|
+
|
|
292
|
+
# no dtcs to report
|
|
293
|
+
if len(self._data) == 2:
|
|
294
|
+
self._data.extend([0x00, 0x00, 0x00, 0x00, 0xff, 0xff])
|
|
295
|
+
# one dtc to report
|
|
296
|
+
elif len(self._data) == 6:
|
|
297
|
+
self._data.extend([0xff, 0xff])
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
# Default Priority: 6
|
|
301
|
+
# priority should be 7 when transport protocol is used (SAE J1939-21 requirement)
|
|
302
|
+
if len(self._data) > 8:
|
|
303
|
+
priority = 7
|
|
304
|
+
else:
|
|
305
|
+
priority = 6
|
|
306
|
+
# send pgn
|
|
307
|
+
self._ca.send_pgn(0, (self._pgn >> 8) & 0xFF, self._pgn & 0xFF, priority, self._data )
|
|
308
|
+
|
|
309
|
+
# returning true keeps the timer event active
|
|
310
|
+
return True
|
|
311
|
+
|
|
312
|
+
def _parse_dm1_receive_data(self):
|
|
313
|
+
length = len(self._data)
|
|
314
|
+
if length < 6:
|
|
315
|
+
logger.error("DM01: length shorted than 6 bytes")
|
|
316
|
+
return
|
|
317
|
+
|
|
318
|
+
dtc_length = length - 2
|
|
319
|
+
if (length != 8) and (dtc_length % 4) != 0:
|
|
320
|
+
logger.error("DM01: DTC length incorrect")
|
|
321
|
+
return
|
|
322
|
+
|
|
323
|
+
# calculate numboer of DTCs
|
|
324
|
+
number_dtc = int(dtc_length / 4)
|
|
325
|
+
|
|
326
|
+
# get lamp status
|
|
327
|
+
self._lamp_status['pl'] = DtcLamp().get_status( self._data[0] & 0x03, self._data[1] & 0x03)
|
|
328
|
+
self._lamp_status['awl'] = DtcLamp().get_status((self._data[0] >> 2) & 0x03, (self._data[1] >> 2) & 0x03)
|
|
329
|
+
self._lamp_status['rsl'] = DtcLamp().get_status((self._data[0] >> 4) & 0x03, (self._data[1] >> 4) & 0x03)
|
|
330
|
+
self._lamp_status['mil'] = DtcLamp().get_status((self._data[0] >> 6) & 0x03, (self._data[1] >> 6) & 0x03)
|
|
331
|
+
|
|
332
|
+
# get DTC (Diagnostic Trouble Code)
|
|
333
|
+
self._dtc_dic_list = []
|
|
334
|
+
for i in range(number_dtc):
|
|
335
|
+
dtc_int = ( (self._data[i*4+2] & 0xff)
|
|
336
|
+
| ((self._data[i*4+3] & 0xff) << 8)
|
|
337
|
+
| ((self._data[i*4+4] & 0xff) << 16)
|
|
338
|
+
| ((self._data[i*4+5] & 0xff) << 24))
|
|
339
|
+
|
|
340
|
+
if dtc_int == 0x0:
|
|
341
|
+
# according to J1939 standard after 2004, if all these bytes are 0x00 then then no data is available for the dtc
|
|
342
|
+
# so we should not add this to the dtc list since it is not a valid dtc
|
|
343
|
+
continue
|
|
344
|
+
|
|
345
|
+
cm = 4 if ((dtc_int >> 31) & 0x01) == 0 else self._rx_cm_bit_set
|
|
346
|
+
dtc = DTC(dtc=dtc_int, cm=cm)
|
|
347
|
+
self._dtc_dic_list.append( {'spn': dtc.spn, 'fmi': dtc.fmi, 'oc': dtc.oc, 'cm': dtc.cm } )
|
|
348
|
+
|
|
349
|
+
def _notify_subscribers(self, sa, timestamp):
|
|
350
|
+
for callback in self._subscribers:
|
|
351
|
+
callback(sa, self.lamp_status.copy(), self._dtc_dic_list.copy(), timestamp)
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
class Dm11:
|
|
355
|
+
"""Diagnostic Data Clear/Reset for Active DTCs (DM11)
|
|
356
|
+
"""
|
|
357
|
+
def __init__(self, ca: j1939.ControllerApplication):
|
|
358
|
+
"""
|
|
359
|
+
:param obj ca: j1939 controller application
|
|
360
|
+
"""
|
|
361
|
+
self._pgn = j1939.ParameterGroupNumber.PGN.DM11
|
|
362
|
+
self._ca = ca
|
|
363
|
+
self._subscribers_req_clear = []
|
|
364
|
+
self._subscribers_ack_clear = []
|
|
365
|
+
ca.subscribe_request(self._on_request)
|
|
366
|
+
ca.subscribe_acknowledge(self._on_acknowledge)
|
|
367
|
+
|
|
368
|
+
def request_clear_all(self, destination):
|
|
369
|
+
self._ca.send_request(0, self._pgn, destination)
|
|
370
|
+
|
|
371
|
+
def subscribe_request_clear_all(self, callback):
|
|
372
|
+
self._subscribers_req_clear.append(callback)
|
|
373
|
+
|
|
374
|
+
def subscribe_acknowledge_clear_all(self, callback):
|
|
375
|
+
self._subscribers_ack_clear.append(callback)
|
|
376
|
+
|
|
377
|
+
def _on_request(self, src_address, dest_address, pgn):
|
|
378
|
+
for subscriber in self._subscribers_req_clear:
|
|
379
|
+
subscriber(src_address, dest_address, pgn)
|
|
380
|
+
# TODO: send acknowledge
|
|
381
|
+
|
|
382
|
+
def _on_acknowledge(self, src_address, dest_address, pgn):
|
|
383
|
+
for subscriber in self._subscribers_ack_clear:
|
|
384
|
+
# TODO
|
|
385
|
+
pass
|
|
386
|
+
|
|
387
|
+
class Dm22:
|
|
388
|
+
"""Individual Clear/Reset of Active and Previously Active DTC (DM22)
|
|
389
|
+
"""
|
|
390
|
+
class DTC_CLR_CTRL:
|
|
391
|
+
"""Individual DTC Clear/Reset Control Byte
|
|
392
|
+
"""
|
|
393
|
+
PA_REQ = 1 # Request to clear/reset a specific previously active DTC
|
|
394
|
+
PA_ACK = 2 # Positive acknowledge of clear/reset of a specific previously active DTC
|
|
395
|
+
PA_NACK = 3 # Negative acknowledge of clear/reset of a specific previously active DTC
|
|
396
|
+
ACT_REQ = 17 # Request to clear/reset a specific active DTC
|
|
397
|
+
ACT_ACK = 18 # Positive acknowledge of clear/reset of a specific active DTC
|
|
398
|
+
ACT_NACK = 19 # Negative acknowledge of clear/reset of a specific active DTC
|
|
399
|
+
|
|
400
|
+
class DTC_CLR_CTRL_SPECIFIC:
|
|
401
|
+
"""Control Byte Specific Indicator for Individual DTC Clear
|
|
402
|
+
"""
|
|
403
|
+
GENERAL_NACK = 0
|
|
404
|
+
ACCESS_DENIED = 1
|
|
405
|
+
DTC_UNKNOWN = 2
|
|
406
|
+
DTC_PA_NOT_ACTIVE = 3
|
|
407
|
+
DTC_ACT_NOT_ACTIVE = 4
|
|
408
|
+
|
|
409
|
+
def __init__(self, ca: j1939.ControllerApplication):
|
|
410
|
+
"""
|
|
411
|
+
:param obj ca: j1939 controller application
|
|
412
|
+
"""
|
|
413
|
+
self._pgn = j1939.ParameterGroupNumber.PGN.DM22
|
|
414
|
+
self._ca = ca
|
|
415
|
+
|
|
416
|
+
def request_clear_act_dtc(self, dest_address, spn, fmi):
|
|
417
|
+
"""Request to Clear/Reset Active DTC
|
|
418
|
+
|
|
419
|
+
:param dest_address:
|
|
420
|
+
destination address of the node
|
|
421
|
+
:param spn:
|
|
422
|
+
spn of the dtc to be cleared
|
|
423
|
+
:param spn:
|
|
424
|
+
fmi of the dtc to be cleared
|
|
425
|
+
"""
|
|
426
|
+
self._send_request(self.DTC_CLR_CTRL.ACT_REQ, dest_address, fmi, spn)
|
|
427
|
+
|
|
428
|
+
def request_clear_pa_dtc(self, dest_address, spn, fmi):
|
|
429
|
+
"""Request to Clear/Reset Previously Active DTC
|
|
430
|
+
|
|
431
|
+
:param dest_address:
|
|
432
|
+
destination address of the node
|
|
433
|
+
:param spn:
|
|
434
|
+
spn of the dtc to be cleared
|
|
435
|
+
:param spn:
|
|
436
|
+
fmi of the dtc to be cleared
|
|
437
|
+
"""
|
|
438
|
+
self._send_request(self.DTC_CLR_CTRL.PA_REQ, dest_address, fmi, spn)
|
|
439
|
+
|
|
440
|
+
def _send_request(self, control_byte, dest_address, fmi, spn):
|
|
441
|
+
data = [0xFF]*8
|
|
442
|
+
data[0] = control_byte
|
|
443
|
+
data[5] = spn & 0xFF
|
|
444
|
+
data[6] = (spn >> 8) & 0xFF
|
|
445
|
+
data[7] = ((spn >> 22) & 0xE0) | (fmi & 0x1F)
|
|
446
|
+
|
|
447
|
+
# send pgn
|
|
448
|
+
self._ca.send_pgn(0, (self._pgn >> 8) & 0xFF, dest_address & 0xFF, 6, data)
|