forked from pylessard/python-udsoncan
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdtc.py
239 lines (192 loc) · 9.89 KB
/
dtc.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
__all__ = ['Dtc']
import struct
import inspect
from typing import Optional, List, Any, Union
class Dtc:
"""
Defines a Diagnostic Trouble Code which consist of a 3-byte ID, a status, a severity and some diagnostic data.
:param dtcid: The 3-byte ID of the DTC
:type dtcid: int
"""
class Format:
"""
Provide a list of DTC formats and their indices. These values are used by the :ref:`The ReadDTCInformation<ReadDtcInformation>` when requesting a number of DTCs.
"""
ISO15031_6 = 0 # 2006
SAE_J2012_DA_DTCFormat_00 = 0 # 2013 / 2020
ISO14229_1 = 1
SAE_J1939_73 = 2
ISO11992_4 = 3
SAE_J2012_DA_DTCFormat_04 = 4
@classmethod
def get_name(cls, given_id: Optional[int]) -> Optional[str]:
if given_id is None:
return ""
for member in inspect.getmembers(cls):
if isinstance(member[1], int):
if member[1] == given_id:
return member[0]
return None
class FunctionalGroupIdentifiers:
"""
Provides a list of FunctionalGroupIdentifiers (Table D.15) which are used by the :ref:`ReadDTCInformation<ReadDtcInformation>` when requesting a number of DTCs.
"""
EMISSIONS_SYSTEM_GROUP = 0x33
SAFETY_SYSTEM_GROUP = 0xD0
VOBD_SYSTEM = 0xFE
# DTC Status byte
# This byte is an 8-bit flag indicating how much we are sure that a DTC is active.
class Status:
"""
Represents a DTC status which consists of 8 boolean flags (a byte). All flags can be set after instantiation without problems.
:param test_failed: DTC is no longer failed at the time of the request
:type test_failed: bool
:param test_failed_this_operation_cycle: DTC never failed on the current operation cycle.
:type test_failed_this_operation_cycle: bool
:param pending: DTC failed on the current or previous operation cycle.
:type pending: bool
:param confirmed: DTC is not confirmed at the time of the request.
:type confirmed: bool
:param test_not_completed_since_last_clear: DTC test has been completed since the last codeclear.
:type test_not_completed_since_last_clear: bool
:param test_failed_since_last_clear: DTC test failed at least once since last code clear.
:type test_failed_since_last_clear: bool
:param test_not_completed_this_operation_cycle: DTC test completed this operation cycle.
:type test_not_completed_this_operation_cycle: bool
:param warning_indicator_requested: Server is not requesting warningIndicator to be active.
:type warning_indicator_requested: bool
"""
test_failed: bool
test_failed_this_operation_cycle: bool
pending: bool
confirmed: bool
test_not_completed_since_last_clear: bool
test_failed_since_last_clear: bool
test_not_completed_this_operation_cycle: bool
warning_indicator_requested: bool
def __init__(self,
test_failed: bool = False,
test_failed_this_operation_cycle: bool = False,
pending: bool = False,
confirmed: bool = False,
test_not_completed_since_last_clear: bool = False,
test_failed_since_last_clear: bool = False,
test_not_completed_this_operation_cycle: bool = False,
warning_indicator_requested: bool = False):
self.test_failed = test_failed
self.test_failed_this_operation_cycle = test_failed_this_operation_cycle
self.pending = pending
self.confirmed = confirmed
self.test_not_completed_since_last_clear = test_not_completed_since_last_clear
self.test_failed_since_last_clear = test_failed_since_last_clear
self.test_not_completed_this_operation_cycle = test_not_completed_this_operation_cycle
self.warning_indicator_requested = warning_indicator_requested
def get_byte_as_int(self) -> int: # Returns the status byte as an integer
byte = 0
byte |= 0x1 if self.test_failed else 0
byte |= 0x2 if self.test_failed_this_operation_cycle else 0
byte |= 0x4 if self.pending else 0
byte |= 0x8 if self.confirmed else 0
byte |= 0x10 if self.test_not_completed_since_last_clear else 0
byte |= 0x20 if self.test_failed_since_last_clear else 0
byte |= 0x40 if self.test_not_completed_this_operation_cycle else 0
byte |= 0x80 if self.warning_indicator_requested else 0
return byte
def get_byte(self) -> bytes: # Returns the status byte in "bytes" format for payload creation
return struct.pack('B', self.get_byte_as_int())
def set_byte(self, byte: Union[bytes, int]) -> None: # Set all the status flags from the status byte
if not isinstance(byte, int) and not isinstance(byte, bytes):
raise ValueError('Given byte must be an integer or bytes object.')
if isinstance(byte, bytes):
if len(byte) != 1:
raise ValueError("Expected 1 byte to set the DTC Status")
byte = int(byte[0])
self.test_failed = True if byte & 0x01 > 0 else False
self.test_failed_this_operation_cycle = True if byte & 0x02 > 0 else False
self.pending = True if byte & 0x04 > 0 else False
self.confirmed = True if byte & 0x08 > 0 else False
self.test_not_completed_since_last_clear = True if byte & 0x10 > 0 else False
self.test_failed_since_last_clear = True if byte & 0x20 > 0 else False
self.test_not_completed_this_operation_cycle = True if byte & 0x40 > 0 else False
self.warning_indicator_requested = True if byte & 0x80 > 0 else False
@classmethod
def from_byte(cls, byte: Union[bytes, int]) -> "Dtc.Status":
status = cls()
status.set_byte(byte)
return status
# DTC Severity byte, it's a 3-bit indicator telling how serious a trouble code is.
class Severity:
"""
Represents a DTC severity which consists of 3 boolean flags. All flags can be set after instantiation without problems.
:param maintenance_only: This value indicates that the failure requests maintenance only
:type maintenance_only: bool
:param check_at_next_exit: This value indicates that the failure requires a check of the vehicle at the next halt.
:type check_at_next_exit: bool
:param check_immediately: This value indicates that the failure requires an immediate check of the vehicle.
:type check_immediately: bool
"""
maintenance_only: bool
check_at_next_exit: bool
check_immediately: bool
def __init__(self, maintenance_only: bool = False, check_at_next_exit: bool = False, check_immediately: bool = False):
self.maintenance_only = maintenance_only
self.check_at_next_exit = check_at_next_exit
self.check_immediately = check_immediately
def get_byte_as_int(self) -> int:
byte = 0
byte |= 0x20 if self.maintenance_only else 0
byte |= 0x40 if self.check_at_next_exit else 0
byte |= 0x80 if self.check_immediately else 0
return byte
def get_byte(self) -> bytes:
return struct.pack('B', self.get_byte_as_int())
def set_byte(self, byte: Union[bytes, int]) -> None:
if not isinstance(byte, int) and not isinstance(byte, bytes):
raise ValueError('Given byte must be an integer or bytes object.')
if isinstance(byte, bytes):
if len(byte) != 1:
raise ValueError("Expected 1 byte to set the DTC Status")
byte = int(byte[0])
self.maintenance_only = True if byte & 0x20 > 0 else False
self.check_at_next_exit = True if byte & 0x40 > 0 else False
self.check_immediately = True if byte & 0x80 > 0 else False
@property
def available(self):
return True if self.get_byte_as_int() > 0 else False
# A snapshot data. Not defined by ISO14229 and implementation specific.
# To read this data, the client must have a DID codec set in its config.
class Snapshot:
record_number: Optional[int] = None
did: Optional[int] = None
data: Optional[bytes] = None
raw_data: Optional[bytes] = b''
# Extended data. Not defined by ISO14229 and implementation specific
# Only raw data can be given to user.
class ExtendedData:
record_number: Optional[int] = None
raw_data: Optional[bytes] = b''
id: int
status: "Dtc.Status"
snapshots: List[Union["Dtc.Snapshot", int]]
extended_data: List["Dtc.ExtendedData"]
severity: "Dtc.Severity"
functional_unit: Any
fault_counter: Optional[int]
def __init__(self, dtcid: int):
self.id = dtcid
self.status = Dtc.Status()
self.snapshots = [] # . DID codec must be configured
self.extended_data = []
self.severity = Dtc.Severity()
self.functional_unit = None # Implementation specific (ISO 14229 D.4)
# Common practice is to detect a specific failure many times before setting the DTC active. This counter should tell the actual count.
self.fault_counter = None
def __repr__(self) -> str:
return '<DTC ID=0x%06x, Status=0x%02x, Severity=0x%02x at 0x%08x>' % (self.id, self.status.get_byte_as_int(), self.severity.get_byte_as_int(), id(self))
def id_iso(self) -> str:
return '%c%i%03X-%02X' % (
['P', 'C', 'B', 'U'][self.id >> 22],
(self.id >> 20) & 3,
(self.id >> 8) & 0xFFF,
(self.id) & 0xFF
)