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.
@@ -0,0 +1,499 @@
1
+ from __future__ import annotations
2
+
3
+ import heapq
4
+ import logging
5
+ import can
6
+ from can import Listener
7
+ import time
8
+ import threading
9
+ import queue
10
+ from .controller_application import ControllerApplication
11
+ from .parameter_group_number import ParameterGroupNumber
12
+ from .j1939_21 import J1939_21
13
+ from .j1939_22 import J1939_22
14
+ from .message_id import FrameFormat
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+ class ElectronicControlUnit:
19
+ """ElectronicControlUnit (ECU) holding one or more ControllerApplications (CAs)."""
20
+
21
+
22
+ def __init__(self, data_link_layer='j1939-21', max_cmdt_packets=1, minimum_tp_rts_cts_dt_interval=None, minimum_tp_bam_dt_interval=None, send_message=None):
23
+ """
24
+ :param data_link_layer:
25
+ specify data-link-layer, 'j1939-21' or 'j1939-22'
26
+ """
27
+ if send_message:
28
+ self.send_message = send_message
29
+
30
+ #: A python-can :class:`can.BusABC` instance
31
+ self._bus = None
32
+ # Locking object for send
33
+ self._send_lock = threading.Lock()
34
+
35
+ if max_cmdt_packets > 0xFF:
36
+ raise ValueError("max number of segments that can be sent is 0xFF")
37
+
38
+ # set data link layer
39
+ if data_link_layer == 'j1939-21':
40
+ self.j1939_dll = J1939_21(self.send_message, self._protocol_wakeup, self._notify_subscribers, max_cmdt_packets, minimum_tp_rts_cts_dt_interval, minimum_tp_bam_dt_interval, self._is_message_acceptable)
41
+ elif data_link_layer == 'j1939-22':
42
+ self.j1939_dll = J1939_22(self.send_message, self._protocol_wakeup, self._notify_subscribers, max_cmdt_packets, minimum_tp_rts_cts_dt_interval, minimum_tp_bam_dt_interval, self._is_message_acceptable)
43
+ else:
44
+ raise ValueError("either 'j1939-21' or 'j1939-22' must be provided for data link layer")
45
+
46
+ #: Includes at least MessageListener.
47
+ self._listeners = [MessageListener(self)]
48
+ self._notifier = None
49
+
50
+ self._subscribers = []
51
+ self._subscribers_lock = threading.RLock()
52
+
53
+ # Heap-based timer event list: (deadline, seq, callback, cookie, delta_time)
54
+ self._timer_events = []
55
+ self._timer_seq = 0
56
+ self._timer_events_lock = threading.RLock()
57
+
58
+ # Dependent lifecycle registry. Any object that needs to be stopped before the ECU's own threads should be registered here. See :meth:`register_dependent`.
59
+ self._dependents = []
60
+ self._dependents_lock = threading.RLock()
61
+ self._stopping = False
62
+
63
+ self._job_thread_end = threading.Event()
64
+
65
+ # Protocol thread: owns TP/BAM timeout management only — no user callbacks
66
+ logger.info("Starting ECU protocol thread")
67
+ self._protocol_wakeup_queue = queue.Queue()
68
+ self._protocol_thread = threading.Thread(
69
+ target=self._protocol_job_thread, name='j1939.ecu protocol_thread')
70
+ self._protocol_thread.daemon = True
71
+
72
+ # Timer thread: owns application cyclic callbacks only
73
+ logger.info("Starting ECU timer thread")
74
+ self._timer_wakeup_queue = queue.Queue()
75
+ self._timer_thread = threading.Thread(
76
+ target=self._timer_job_thread, name='j1939.ecu timer_thread')
77
+ self._timer_thread.daemon = True
78
+
79
+ self._protocol_thread.start()
80
+ self._timer_thread.start()
81
+
82
+
83
+ def stop(self):
84
+ """Stops the ECU background handling
85
+
86
+ This Function explicitly stops the background handling of the ECU.
87
+
88
+ Before stopping the ECU's own protocol/timer threads, every registered
89
+ dependent (see :meth:`register_dependent`) has its ``stop()`` method
90
+ invoked in LIFO order. Exceptions raised by a dependent's ``stop()``
91
+ are logged and swallowed so a single misbehaving dependent cannot
92
+ prevent the rest of the shutdown from completing.
93
+ """
94
+ # Snapshot dependents under lock, then mark the ECU as stopping so any
95
+ # late registrations are rejected.
96
+ with self._dependents_lock:
97
+ self._stopping = True
98
+ dependents = list(self._dependents)
99
+ self._dependents.clear()
100
+
101
+ # LIFO: most-recently registered first.
102
+ for dep in reversed(dependents):
103
+ try:
104
+ dep.stop()
105
+ except Exception:
106
+ logger.exception("Error stopping dependent %r", dep)
107
+
108
+ self._job_thread_end.set()
109
+ self._protocol_wakeup_queue.put(1)
110
+ self._timer_wakeup_queue.put(1)
111
+ self._protocol_thread.join()
112
+ self._timer_thread.join()
113
+
114
+ def register_dependent(self, dependent):
115
+ """Register a helper whose ``stop()`` should be called by :meth:`stop`.
116
+
117
+ Any helper object that owns threads, timers, or other resources tied
118
+ to this ECU should call this during construction. ``ecu.stop()`` will
119
+ invoke ``dependent.stop()`` in LIFO order before tearing down its own
120
+ threads.
121
+
122
+ Duplicate registrations of the same object (by identity) are silently
123
+ ignored.
124
+
125
+ :param dependent:
126
+ Any object exposing a no-arg ``stop()`` method.
127
+
128
+ :raises RuntimeError:
129
+ If called while the ECU is shutting down.
130
+ :raises TypeError:
131
+ If ``dependent`` does not expose a callable ``stop`` attribute.
132
+ """
133
+ if not callable(getattr(dependent, 'stop', None)):
134
+ raise TypeError(
135
+ "dependent must expose a callable stop() method")
136
+ with self._dependents_lock:
137
+ if self._stopping:
138
+ raise RuntimeError(
139
+ "Cannot register a dependent while the ECU is stopping")
140
+ for existing in self._dependents:
141
+ if existing is dependent:
142
+ return
143
+ self._dependents.append(dependent)
144
+
145
+ def unregister_dependent(self, dependent):
146
+ """Remove a previously-registered dependent.
147
+
148
+ :param dependent:
149
+ The object previously passed to :meth:`register_dependent`.
150
+ """
151
+ with self._dependents_lock:
152
+ self._dependents = [
153
+ d for d in self._dependents if d is not dependent]
154
+
155
+ def add_timer(self, delta_time, callback, cookie=None):
156
+ """Adds a callback to the list of timer events
157
+
158
+ :param delta_time:
159
+ The time in seconds after which the event is to be triggered.
160
+ :param callback:
161
+ The callback function to call
162
+ """
163
+ deadline = time.monotonic() + delta_time
164
+ with self._timer_events_lock:
165
+ heapq.heappush(self._timer_events,
166
+ (deadline, self._timer_seq, callback, cookie, delta_time))
167
+ self._timer_seq += 1
168
+ self._timer_wakeup_queue.put(1)
169
+
170
+ def remove_timer(self, callback):
171
+ """Removes ALL entries from the timer event list for the given callback
172
+
173
+ :param callback:
174
+ The callback to be removed from the timer event list
175
+ """
176
+ with self._timer_events_lock:
177
+ self._timer_events = [e for e in self._timer_events if e[2] != callback]
178
+ heapq.heapify(self._timer_events)
179
+ self._timer_wakeup_queue.put(1)
180
+
181
+ def connect(self, *args, **kwargs):
182
+ """Connect to CAN bus using python-can.
183
+
184
+ Arguments are passed directly to :class:`can.BusABC`. Typically these
185
+ may include:
186
+
187
+ :param channel:
188
+ Backend specific channel for the CAN interface.
189
+ :param str interface:
190
+ Name of the interface (formerly ``bustype``, renamed in python-can v4.2). See
191
+ `python-can manual <https://python-can.readthedocs.io/en/latest/configuration.html#interface-names>`__
192
+ for full list of supported interfaces.
193
+ :param int bitrate:
194
+ Bitrate in bit/s.
195
+
196
+ :raises can.CanError:
197
+ When connection fails.
198
+ """
199
+ self._bus = can.interface.Bus(*args, **kwargs)
200
+ logger.info("Connected to '%s'", self._bus.channel_info)
201
+ self._notifier = can.Notifier(self._bus, self._listeners, 1)
202
+ return self._bus
203
+
204
+ def disconnect(self):
205
+ """Disconnect from the CAN bus.
206
+
207
+ Must be overridden in a subclass if a custom interface is used.
208
+ """
209
+ self._notifier.stop()
210
+ self._bus.shutdown()
211
+ self._bus = None
212
+
213
+ def subscribe(self, callback, device_address=None):
214
+ """Add the given callback to the message notification stream.
215
+
216
+ :param callback:
217
+ Function to call when message is received.
218
+ :param int device_address:
219
+ Device address of the application.
220
+ This is a simple way for peer-to-peer reception without adding a controller-application.
221
+ Only one device address can be entered. Multiple device addresses are only possible with controller applications.
222
+ Note: TP.CMDT will only be received if the destination address is bound to a controller application.
223
+ """
224
+ with self._subscribers_lock:
225
+ self._subscribers.append({'cb': callback, 'dev_adr': device_address})
226
+
227
+ def unsubscribe(self, callback):
228
+ """Stop listening for message.
229
+
230
+ :param callback:
231
+ Function to call when message is received.
232
+ """
233
+ with self._subscribers_lock:
234
+ self._subscribers = [d for d in self._subscribers if d['cb'] != callback]
235
+
236
+
237
+ def add_ca(self, **kwargs):
238
+ """Add a ControllerApplication to the ECU.
239
+
240
+ :param controller_application:
241
+ A :class:`j1939.ControllerApplication` object.
242
+
243
+ :param name:
244
+ A :class:`j1939.Name` object.
245
+
246
+ :param device_address:
247
+ An integer representing the device address to announce to the bus.
248
+
249
+ :return:
250
+ The CA object that was added.
251
+
252
+ :rtype: r3964.ControllerApplication
253
+ """
254
+ if 'controller_application' in kwargs:
255
+ ca = kwargs['controller_application']
256
+ else:
257
+ if 'name' not in kwargs:
258
+ raise ValueError("either 'controller_application' or 'name' must be provided")
259
+ name = kwargs.get('name')
260
+ da = kwargs.get('device_address', None)
261
+ ca = ControllerApplication(name, da)
262
+
263
+ self.j1939_dll.add_ca(ca)
264
+ ca.associate_ecu(self)
265
+ return ca
266
+
267
+ def remove_ca(self, device_address):
268
+ """Remove a ControllerApplication from the ECU.
269
+
270
+ :param int device_address:
271
+ A integer representing the device address
272
+
273
+ :return:
274
+ True if the ControllerApplication was successfully removed, otherwise False is returned.
275
+ """
276
+ return self.j1939_dll.remove_ca(device_address)
277
+
278
+ def add_bus(self, bus):
279
+ """Add a bus to the ECU.
280
+
281
+ :param bus:
282
+ A :class:`can.BusABC` object.
283
+ """
284
+ self._bus = bus
285
+
286
+ def add_notifier(self, notifier):
287
+ """Add a notifier to the ECU.
288
+
289
+ :param notifier:
290
+ A :class:`can.Notifier` object.
291
+ """
292
+ self._notifier = notifier
293
+ for listener in self._listeners:
294
+ self._notifier.add_listener(listener)
295
+
296
+ def remove_bus(self):
297
+ """Remove the bus from the ECU.
298
+ """
299
+ self._bus = None
300
+
301
+ def remove_notifier(self):
302
+ """Remove the notifier from the ECU.
303
+ """
304
+ for listener in self._listeners:
305
+ self._notifier.remove_listener(listener)
306
+ self._notifier = None
307
+
308
+ def send_pgn(self, data_page, pdu_format, pdu_specific, priority, src_address, data, time_limit=0, frame_format=FrameFormat.FEFF):
309
+ """send a pgn
310
+ :param int data_page: data page
311
+ :param int pdu_format: pdu format
312
+ :param int pdu_specific: pdu specific
313
+ :param int priority: message priority
314
+ :param int src_address: address of the transmitter
315
+ :param list data: payload, each list index represents one payload byte
316
+ :param time_limit: option j1939-22 multi-pg: specify a time limit in s (e.g. 0.1 == 100ms),
317
+ after this time, the multi-pg will be sent. several pgs can thus be combined in one multi-pg.
318
+ 0 or no time-limit means immediate sending.
319
+ """
320
+ return self.j1939_dll.send_pgn(data_page, pdu_format, pdu_specific, priority, src_address, data, time_limit, frame_format)
321
+
322
+ def send_message(self, can_id, extended_id, data, fd_format=False):
323
+ """Send a raw CAN message to the bus.
324
+
325
+ This method may be overridden in a subclass if you need to integrate
326
+ this library with a custom backend.
327
+ It is safe to call this from multiple threads.
328
+
329
+ :param int can_id:
330
+ CAN-ID of the message (always 29-bit)
331
+ :param data:
332
+ Data to be transmitted (anything that can be converted to bytes)
333
+ :param fd_format:
334
+ fd format means bitrate switching and payload of max 64Bytes is active
335
+
336
+ :raises can.CanError:
337
+ When the message fails to be transmitted
338
+ """
339
+
340
+ if not self._bus:
341
+ raise RuntimeError("Not connected to CAN bus")
342
+ msg = can.Message(is_extended_id=extended_id,
343
+ arbitration_id=can_id,
344
+ data=data,
345
+ is_fd=fd_format,
346
+ bitrate_switch=fd_format
347
+ )
348
+ with self._send_lock:
349
+ self._bus.send(msg)
350
+ # TODO: check error receivement
351
+
352
+ def notify(self, can_id, data, timestamp):
353
+ """Feed incoming CAN message into this ecu.
354
+
355
+ If a custom interface is used, this function must be called for each
356
+ 29-bit standard message read from the CAN bus.
357
+
358
+ :param int can_id:
359
+ CAN-ID of the message (always 29-bit)
360
+ :param bytearray data:
361
+ Data part of the message (0 - 8 bytes)
362
+ :param float timestamp:
363
+ The timestamp field in a CAN message is a floating point number
364
+ representing when the message was received since the epoch in
365
+ seconds.
366
+ Where possible this will be timestamped in hardware.
367
+ """
368
+ self.j1939_dll.notify(can_id, data, timestamp)
369
+
370
+ def add_bus_filters(self, filters: can.typechecking.CanFilters | None):
371
+ """Add bus filters to the underlying CAN bus.
372
+
373
+ :param filters:
374
+ An iterable of dictionaries each containing a "can_id",
375
+ a "can_mask", and an optional "extended" key
376
+ """
377
+ if self._bus is None:
378
+ raise RuntimeError("Not connected to CAN bus")
379
+ self._bus.set_filters(filters)
380
+
381
+ def _protocol_job_thread(self):
382
+ """Protocol thread: handles TP/BAM timeout management only.
383
+
384
+ This thread is isolated from application timer callbacks so that slow
385
+ user callbacks cannot delay protocol-level timeouts (which would cause
386
+ spurious ABORT messages on the bus).
387
+ """
388
+ while not self._job_thread_end.is_set():
389
+ now = time.monotonic()
390
+ next_wakeup = self.j1939_dll.async_job_thread(now)
391
+ time_to_sleep = next_wakeup - time.monotonic()
392
+ if time_to_sleep > 0:
393
+ try:
394
+ self._protocol_wakeup_queue.get(True, time_to_sleep)
395
+ except queue.Empty:
396
+ pass
397
+
398
+ def _timer_job_thread(self):
399
+ """Timer thread: handles application cyclic callbacks only.
400
+
401
+ Uses a heapq (min-heap keyed by deadline) for O(log n) scheduling.
402
+ Woken early via _timer_wakeup_queue whenever a timer is added/removed.
403
+ Callbacks returning True are rescheduled; returning False are removed.
404
+ """
405
+ while not self._job_thread_end.is_set():
406
+ now = time.monotonic()
407
+ next_wakeup = now + 5.0
408
+
409
+ with self._timer_events_lock:
410
+ while self._timer_events and self._timer_events[0][0] <= now:
411
+ deadline, seq, cb, cookie, delta = heapq.heappop(self._timer_events)
412
+ logger.debug("Deadline for timer event reached")
413
+ try:
414
+ reschedule = (cb(cookie) is True)
415
+ except Exception:
416
+ #TODO: is there a better way to handle exceptions in user callbacks?
417
+ # We don't want one bad callback to break the timer thread,
418
+ # but we also don't want to just swallow it silently.
419
+ logger.exception("Timer callback failed: %r", cb)
420
+ reschedule = False
421
+ if reschedule:
422
+ # reschedule: advance deadline past now to avoid burst catch-up
423
+ new_deadline = deadline + delta
424
+ while new_deadline < now:
425
+ new_deadline += delta
426
+ heapq.heappush(self._timer_events,
427
+ (new_deadline, self._timer_seq, cb, cookie, delta))
428
+ self._timer_seq += 1
429
+ # returning False (or None) means remove — already popped, nothing to do
430
+
431
+ if self._timer_events:
432
+ next_wakeup = self._timer_events[0][0]
433
+
434
+ time_to_sleep = next_wakeup - time.monotonic()
435
+ if time_to_sleep > 0:
436
+ try:
437
+ self._timer_wakeup_queue.get(True, time_to_sleep)
438
+ except queue.Empty:
439
+ pass
440
+
441
+ def _protocol_wakeup(self):
442
+ """Wakeup the protocol job thread.
443
+
444
+ Called by the DLL (j1939_21/j1939_22) when TP state changes require
445
+ immediate re-evaluation of protocol deadlines.
446
+ """
447
+ self._protocol_wakeup_queue.put(1)
448
+
449
+ def _notify_subscribers(self, priority, pgn, sa, dest, timestamp, data):
450
+ """Feed incoming message to subscribers.
451
+
452
+ :param int priority:
453
+ Priority of the message
454
+ :param int pgn:
455
+ Parameter Group Number of the message
456
+ :param int sa:
457
+ Source Address of the message
458
+ :param int dest:
459
+ Destination Address of the message
460
+ :param int timestamp:
461
+ Timestamp of the CAN message
462
+ :param bytearray data:
463
+ Data of the PDU
464
+ """
465
+ logger.debug("notify subscribers for PGN {}".format(pgn))
466
+ # Snapshot under lock so subscribe/unsubscribe from any thread is safe.
467
+ with self._subscribers_lock:
468
+ snapshot = list(self._subscribers)
469
+ for dic in snapshot:
470
+ if (dic['dev_adr'] is None) or (dest == ParameterGroupNumber.Address.GLOBAL) or (callable(dic['dev_adr']) and dic['dev_adr'](dest)) or (dest == dic['dev_adr']):
471
+ dic['cb'](priority, pgn, sa, timestamp, data)
472
+
473
+ def _is_message_acceptable(self, dest):
474
+ with self._subscribers_lock:
475
+ return any(d['dev_adr'] == dest for d in self._subscribers)
476
+
477
+ class MessageListener(Listener):
478
+ """Listens for messages on CAN bus and feeds them to an ECU instance.
479
+
480
+ :param j1939.ElectronicControlUnit ecu:
481
+ The ECU to notify on new messages.
482
+ """
483
+
484
+ def __init__(self, ecu : ElectronicControlUnit):
485
+ self.ecu = ecu
486
+ self.stopped = False
487
+
488
+ def on_message_received(self, msg : can.Message):
489
+ if self.stopped or msg.is_error_frame or msg.is_remote_frame or (msg.is_extended_id == False):
490
+ return
491
+
492
+ try:
493
+ self.ecu.notify(msg.arbitration_id, msg.data, msg.timestamp)
494
+ except Exception as e:
495
+ # Exceptions in any callbaks should not affect CAN processing
496
+ logger.error(str(e))
497
+
498
+ def stop(self):
499
+ self.stopped = True
j1939/error_info.py ADDED
@@ -0,0 +1,93 @@
1
+ from enum import Enum
2
+
3
+ class J1939Error(Enum):
4
+ """
5
+ Enum of general errors based off of SAE Mobilus guidelines
6
+ """
7
+ NO_ERROR = 0x0
8
+ UNKNOWN_ERROR = 0x1
9
+ BUSY = 0x2
10
+ BUSY_ERASE_REQUEST = 0x10
11
+ BUSY_READ_REQUEST = 0x11
12
+ BUSY_WRITE_REQUEST = 0x12
13
+ BUSY_STATUS_REQUEST = 0x13
14
+ BUSY_BOOT_LOAD_REQUEST = 0x16
15
+ BUSY_EDCP_GENERATION_REQUEST = 0x17
16
+ BUSY_UNKNOW_REQUEST = 0x1F
17
+ EDC_PARAMETER_ERROR = 0x20
18
+ RAM_ERROR = 0x21
19
+ FLASH_ERROR = 0x22
20
+ PROM_ERROR = 0x23
21
+ INTERNAL_ERROR = 0x24
22
+ GENERAL_ADDRESSING_ERROR = 0x100
23
+ ADDRESS_NOT_ON_BOUNDARY = 0x101
24
+ ADDRESS_INVALID_LENGTH = 0x102
25
+ ADDRESS_MEMORY_OVERFLOW = 0x103
26
+ ADDRESS_DATA_ERASE_REQUIRED = 0x104
27
+ ADDRESS_PROGRAM_ERASE_REQUIRED = 0x105
28
+ ADDRESS_TX_ERASE_PROGRAM_REQUIRED = 0x106
29
+ ADDRESS_BOOT_LOAD_OUT_OF_RANGE = 0x107
30
+ ADDRESS_BOOT_LOAD_NOT_ON_BOUNDARY = 0x108
31
+ DATA_OUT_OF_RANGE = 0x109
32
+ DATA_NAME_UNEXPECTED = 0x10A
33
+ SECURITY_GENERAL = 0x1000
34
+ SECURITY_INVALID_PASSWORD = 0x1001
35
+ SECURITY_INVALID_LEVEL = 0x1002
36
+ SECURITY_INVALID_KEY = 0x1003
37
+ SECURITY_NOT_DIAGNOSTIC = 0x1004
38
+ SECURITY_INCORRECT_MODE = 0x1005
39
+ SECURITY_ENGINE_RUNNING = 0x1006
40
+ SECURITY_VEHICLE_MOVING = 0x1007
41
+ ABORT_EXTERNAL = 0x10000
42
+ MAX_RETRY = 0x10001
43
+ NO_RESPONSE = 0x10002
44
+ INITILIZATION_TIMEOUT = 0x10003
45
+ COMPLETION_TIMEOUT = 0x10004
46
+ NO_INDICATOR = 0xFFFFFF
47
+
48
+
49
+ """
50
+ Dictionary of error codes and their corresponding error message
51
+ """
52
+ ErrorInfo = {
53
+ J1939Error.NO_ERROR.value: "No error",
54
+ J1939Error.UNKNOWN_ERROR.value: "Unknown error",
55
+ J1939Error.BUSY.value: "Busy",
56
+ J1939Error.BUSY_ERASE_REQUEST.value: "Busy: erase request",
57
+ J1939Error.BUSY_READ_REQUEST.value: "Busy: read request",
58
+ J1939Error.BUSY_WRITE_REQUEST.value: "Busy: write request",
59
+ J1939Error.BUSY_STATUS_REQUEST.value: "Busy status request",
60
+ J1939Error.BUSY_BOOT_LOAD_REQUEST.value: "Busy: boot load request",
61
+ J1939Error.BUSY_EDCP_GENERATION_REQUEST.value: "Busy: EDCP generation request",
62
+ J1939Error.BUSY_UNKNOW_REQUEST.value: "Busy: unknown request",
63
+ J1939Error.EDC_PARAMETER_ERROR.value: "EDC parameter error",
64
+ J1939Error.RAM_ERROR.value: "RAM error",
65
+ J1939Error.FLASH_ERROR.value: "Flash error",
66
+ J1939Error.PROM_ERROR.value: "PROM error",
67
+ J1939Error.INTERNAL_ERROR.value: "Internal error",
68
+ J1939Error.GENERAL_ADDRESSING_ERROR.value: "General addressing error",
69
+ J1939Error.ADDRESS_NOT_ON_BOUNDARY.value: "Address: not on boundary",
70
+ J1939Error.ADDRESS_INVALID_LENGTH.value: "Address: invalid length",
71
+ J1939Error.ADDRESS_MEMORY_OVERFLOW.value: "Address: memory overflow",
72
+ J1939Error.ADDRESS_DATA_ERASE_REQUIRED.value: "Address: data erase required",
73
+ J1939Error.ADDRESS_PROGRAM_ERASE_REQUIRED.value: "Address: program erase required",
74
+ J1939Error.ADDRESS_TX_ERASE_PROGRAM_REQUIRED.value: "Address: TX erase program required",
75
+ J1939Error.ADDRESS_BOOT_LOAD_OUT_OF_RANGE.value: "Address: boot load out of range",
76
+ J1939Error.ADDRESS_BOOT_LOAD_NOT_ON_BOUNDARY.value: "Address: boot load not on boundary",
77
+ J1939Error.DATA_OUT_OF_RANGE.value: "Data out of range",
78
+ J1939Error.DATA_NAME_UNEXPECTED.value: "Data name unexpected",
79
+ J1939Error.SECURITY_GENERAL.value: "Security: general",
80
+ J1939Error.SECURITY_INVALID_PASSWORD.value: "Security: invalid password",
81
+ J1939Error.SECURITY_INVALID_LEVEL.value: "Security: invalid level",
82
+ J1939Error.SECURITY_INVALID_KEY.value: "Security: invalid key",
83
+ J1939Error.SECURITY_NOT_DIAGNOSTIC.value: "Security: not diagnostic",
84
+ J1939Error.SECURITY_INCORRECT_MODE.value: "Security: incorrect mode",
85
+ J1939Error.SECURITY_ENGINE_RUNNING.value: "Security: engine running",
86
+ J1939Error.SECURITY_VEHICLE_MOVING.value: "Security: vehicle moving",
87
+ J1939Error.ABORT_EXTERNAL.value: "Abort external",
88
+ J1939Error.MAX_RETRY.value: "Max retry",
89
+ J1939Error.NO_RESPONSE.value: "No response",
90
+ J1939Error.INITILIZATION_TIMEOUT.value: "Initilization timeout",
91
+ J1939Error.COMPLETION_TIMEOUT.value: "Completion timeout",
92
+ J1939Error.NO_INDICATOR.value: "No indicator",
93
+ }