Skip to content

Commit 99a1d1b

Browse files
authored
Fix wwhobd dtc subfunctions (#278)
1 parent 0629774 commit 99a1d1b

File tree

5 files changed

+67
-39
lines changed

5 files changed

+67
-39
lines changed

test/ClientServerTest.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
from udsoncan.connections import QueueConnection
22
from test.ThreadableTest import ThreadableTest
33
from udsoncan.client import Client
4+
from typing import Optional
45

56
class ClientServerTest(ThreadableTest):
7+
_standard_version:Optional[int]
8+
69
def __init__(self, *args, **kwargs):
10+
self._standard_version = None
711
ThreadableTest.__init__(self, *args, **kwargs)
12+
13+
def set_standard(self, version:int):
14+
self._standard_version = version
815

916
def setUp(self):
1017
self.conn = QueueConnection(name='unittest', mtu=4095)
@@ -15,6 +22,8 @@ def clientSetUp(self):
1522
self.udsclient.set_config('exception_on_invalid_response', True)
1623
self.udsclient.set_config('exception_on_unexpected_response', True)
1724
self.udsclient.set_config('exception_on_negative_response', True)
25+
if self._standard_version is not None:
26+
self.udsclient.set_config('standard_version', self._standard_version)
1827

1928
self.udsclient.open()
2029
if hasattr(self, "postClientSetUp"):

test/client/test_read_dtc_information.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2347,26 +2347,31 @@ def __init__(self, *args, **kwargs):
23472347
class TestReportMirrorMemoryDTCByStatusMask(ClientServerTest, GenericTestStatusMaskRequest_DtcAndStatusMaskResponse): # Subfn = 0xF
23482348
def __init__(self, *args, **kwargs):
23492349
ClientServerTest.__init__(self, *args, **kwargs)
2350+
self.set_standard(2013) # Removed starting form 2020
23502351
GenericTestStatusMaskRequest_DtcAndStatusMaskResponse.__init__(self, subfunction=0xf, client_function = 'get_mirrormemory_dtc_by_status_mask')
23512352

23522353
class TestReportMirrorMemoryDTCExtendedDataRecordByDTCNumber(ClientServerTest, GenericReportExtendedDataByRecordNumber): # Subfn = 0x10
23532354
def __init__(self, *args, **kwargs):
23542355
ClientServerTest.__init__(self, *args, **kwargs)
2356+
self.set_standard(2013) # Removed starting form 2020
23552357
GenericReportExtendedDataByRecordNumber.__init__(self, subfunction=0x10, client_function = 'get_mirrormemory_dtc_extended_data_by_dtc_number')
23562358

23572359
class TestReportNumberOfMirrorMemoryDTCByStatusMask(ClientServerTest, GenericTest_RequestStatusMask_ResponseNumberOfDTC): # Subfn = 0x11
23582360
def __init__(self, *args, **kwargs):
23592361
ClientServerTest.__init__(self, *args, **kwargs)
2362+
self.set_standard(2013) # Removed starting form 2020
23602363
GenericTestStatusMaskRequest_DtcAndStatusMaskResponse.__init__(self, subfunction=0x11, client_function = 'get_mirrormemory_number_of_dtc_by_status_mask')
23612364

23622365
class TestReportNumberOfEmissionsRelatedOBDDTCByStatusMask(ClientServerTest, GenericTest_RequestStatusMask_ResponseNumberOfDTC): # Subfn = 0x12
23632366
def __init__(self, *args, **kwargs):
23642367
ClientServerTest.__init__(self, *args, **kwargs)
2368+
self.set_standard(2013) # Removed starting form 2020
23652369
GenericTestStatusMaskRequest_DtcAndStatusMaskResponse.__init__(self, subfunction=0x12, client_function = 'get_number_of_emission_dtc_by_status_mask')
23662370

23672371
class TestReportEmissionsRelatedOBDDTCByStatusMask(ClientServerTest, GenericTestStatusMaskRequest_DtcAndStatusMaskResponse): # Subfn = 0x13
23682372
def __init__(self, *args, **kwargs):
23692373
ClientServerTest.__init__(self, *args, **kwargs)
2374+
self.set_standard(2013) # Removed starting form 2020
23702375
GenericTestStatusMaskRequest_DtcAndStatusMaskResponse.__init__(self, subfunction=0x13, client_function = 'get_emission_dtc_by_status_mask')
23712376

23722377
class TestReportDTCFaultDetectionCounter(ClientServerTest): # Subfn = 0x14
@@ -2605,9 +2610,6 @@ def _test_functional_group_verification(self):
26052610
with self.assertRaises(ValueError):
26062611
self.udsclient.get_wwh_obd_dtc_by_status_mask(None, status_mask=2, severity_mask=0xA0, dtc_class=4)
26072612

2608-
with self.assertRaises(ValueError):
2609-
self.udsclient.get_wwh_obd_dtc_by_status_mask(0xff, status_mask=2, severity_mask=0xA0, dtc_class=4)
2610-
26112613
def assert_no_data_response_with_severity_class(self, response):
26122614
self.assertEqual(len(response.service_data.dtcs), 0)
26132615
self.assertEqual(response.service_data.dtc_count, 0)
@@ -2732,8 +2734,6 @@ def _test_functional_group_verification(self):
27322734
with self.assertRaises(ValueError):
27332735
self.udsclient.get_wwh_obd_dtc_with_permanent_status(None)
27342736

2735-
with self.assertRaises(ValueError):
2736-
self.udsclient.get_wwh_obd_dtc_with_permanent_status(0xff)
27372737

27382738
def assert_no_data_response_with_severity_class(self, response):
27392739
self.assertEqual(len(response.service_data.dtcs), 0)
@@ -3092,10 +3092,10 @@ def test_record_number_out_of_range_response_exception(self):
30923092
pass
30933093

30943094
def _test_record_number_out_of_range_response_exception(self):
3095-
response = Response(service = ReadDTCInformation, code = Response.Code.PositiveResponse, data = self.sb + b'\xF0\x12\x34\x56\x20\x01\x02\x03\x04\x05')
3095+
response = Response(service = ReadDTCInformation, code = Response.Code.PositiveResponse, data = self.sb + b'\xF0\x12\x34\x56\x20\x01\x02\x03\x04')
30963096

30973097
with self.assertRaises(InvalidResponseException):
3098-
# Do not go thourgh the client because out of range would raised at request time.
3098+
# Do not go through the client because out of range would raised at request time.
30993099
ReadDTCInformation.interpret_response(response, ReadDTCInformation.Subfunction.reportDTCExtDataRecordByRecordNumber, extended_data_size={0x123456 : 5})
31003100

31013101
def test_duplicate_dtc(self):
@@ -3126,7 +3126,7 @@ def _test_oob_values(self):
31263126
self.udsclient.get_dtc_extended_data_by_record_number(record_number = 'asd', data_size=5)
31273127

31283128
with self.assertRaises(NotImplementedError):
3129-
self.udsclient.set_config('standard_version', 2013)
3129+
self.udsclient.set_config('standard_version', 2006)
31303130
self.udsclient.get_dtc_extended_data_by_record_number(record_number = 0x33, data_size=5)
31313131
self.udsclient.set_config('standard_version', latest_standard)
31323132

@@ -3417,7 +3417,7 @@ def _test_oob_value(self):
34173417
self.udsclient.get_user_defined_memory_dtc_by_status_mask(0x12, 'aaa')
34183418

34193419
with self.assertRaises(NotImplementedError):
3420-
self.udsclient.set_config('standard_version', 2013)
3420+
self.udsclient.set_config('standard_version', 2006)
34213421
self.udsclient.get_user_defined_memory_dtc_by_status_mask(0x10, 0x20)
34223422
self.udsclient.set_config('standard_version', latest_standard)
34233423

@@ -3857,7 +3857,7 @@ def _test_oob_values(self):
38573857
self.udsclient.get_user_defined_dtc_snapshot_by_dtc_number(dtc=0x123456, record_number=0x02, memory_selection = 0x100)
38583858

38593859
with self.assertRaises(NotImplementedError):
3860-
self.udsclient.set_config('standard_version', 2013)
3860+
self.udsclient.set_config('standard_version', 2006)
38613861
self.udsclient.get_user_defined_dtc_snapshot_by_dtc_number(dtc=0x123456, record_number=0x02, memory_selection = 0x99)
38623862
self.udsclient.set_config('standard_version', latest_standard)
38633863

@@ -4182,7 +4182,7 @@ def _test_oob_values(self):
41824182

41834183

41844184
with self.assertRaises(NotImplementedError):
4185-
self.udsclient.set_config('standard_version', 2013)
4185+
self.udsclient.set_config('standard_version', 2006)
41864186
self.udsclient.get_user_defined_dtc_extended_data_by_dtc_number(dtc=0x123456, data_size=3, record_number=0x99, memory_selection=0x88)
41874187
self.udsclient.set_config('standard_version', latest_standard)
41884188

udsoncan/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,12 @@
2626
__author__ = 'Pier-Yves Lessard'
2727

2828
latest_standard = 2020
29+
valid_standards = [2006,2013,2020]
30+
2931
__default_log_config_file = path.join(path.dirname(path.abspath(__file__)), 'logging.conf')
3032

3133

32-
def setup_logging(config_file=__default_log_config_file):
34+
def setup_logging(config_file:str=__default_log_config_file):
3335
"""
3436
This function setup the logger accordingly to the module provided cfg file
3537
"""

udsoncan/client.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414

1515
from udsoncan.exceptions import *
1616
from udsoncan.configs import default_client_config
17-
from udsoncan.typing import ClientConfig, TypedDict
17+
from udsoncan.typing import ClientConfig
18+
from udsoncan import valid_standards
1819
import logging
1920
import binascii
2021
import functools
@@ -160,7 +161,7 @@ def refresh_config(self) -> None:
160161
self.validate_config()
161162

162163
def validate_config(self) -> None:
163-
if self.config['standard_version'] not in [2006, 2013, 2020]:
164+
if self.config['standard_version'] not in valid_standards:
164165
raise ConfigError('Valid standard versions are 2006, 2013, 2020. %s is not supported' % self.config['standard_version'])
165166

166167
# Decorator to apply on functions that the user will call.

udsoncan/services/ReadDTCInformation.py

Lines changed: 41 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import struct
2-
from udsoncan import Dtc, check_did_config, make_did_codec_from_definition, fetch_codec_definition_from_config, latest_standard, DIDConfig
2+
from udsoncan import Dtc, check_did_config, make_did_codec_from_definition, fetch_codec_definition_from_config, latest_standard, valid_standards, DIDConfig
33
from udsoncan.Request import Request
44
from udsoncan.Response import Response
55
from udsoncan.exceptions import *
@@ -168,10 +168,12 @@ def assert_extended_data_size_int_or_dict(cls,
168168
tools.validate_int(extended_data_size[dtcid], min=0, max=0xFFF, name='Extended data size for DTC=0x%06x' % dtcid)
169169

170170
@classmethod
171-
def assert_functional_group_id(cls, functional_group_id: Optional[int], subfunction) -> None:
171+
def assert_functional_group_id(cls, functional_group_id: Optional[int], subfunction:int) -> None:
172172
if functional_group_id is None:
173173
raise ValueError('functional_group_id must be provided for subfunction 0x%02x' % subfunction)
174-
tools.validate_int(functional_group_id, min=0, max=0xFE, name='Functional Group ID')
174+
# ISO-14229:2020 Disallow a value of 0xFF. But ISO-27145-3 Defines it as "All functional system groups".
175+
# Allowing 0xFF because of the ambiguity
176+
tools.validate_int(functional_group_id, min=0, max=0xFF, name='Functional Group ID')
175177

176178
@classmethod
177179
def pack_dtc(cls, dtcid: int) -> bytes:
@@ -180,6 +182,9 @@ def pack_dtc(cls, dtcid: int) -> bytes:
180182
@classmethod
181183
def check_subfunction_valid(cls, subfunction: int, standard_version: int = latest_standard) -> None:
182184
tools.validate_int(subfunction, min=1, max=0xFF, name='Subfunction')
185+
if standard_version not in valid_standards:
186+
raise ValueError(f"Standard version {standard_version} is not valid")
187+
183188
vlist = vars(cls)
184189
ok = True
185190
for v in vlist:
@@ -190,20 +195,33 @@ def check_subfunction_valid(cls, subfunction: int, standard_version: int = lates
190195
raise ValueError('Unknown subfunction : 0x%02x', subfunction)
191196

192197
# These subfunction have been added in the 2020 version of the standard
193-
subfunction2020 = [
194-
cls.Subfunction.reportUserDefMemoryDTCByStatusMask,
195-
cls.Subfunction.reportDTCExtDataRecordByRecordNumber,
196-
cls.Subfunction.reportUserDefMemoryDTCSnapshotRecordByDTCNumber,
197-
cls.Subfunction.reportUserDefMemoryDTCExtDataRecordByDTCNumber,
198-
cls.Subfunction.reportSupportedDTCExtDataRecord,
199-
cls.Subfunction.reportWWHOBDDTCByMaskRecord,
200-
cls.Subfunction.reportWWHOBDDTCWithPermanentStatus,
201-
cls.Subfunction.reportDTCInformationByDTCReadinessGroupIdentifier,
202-
]
203-
204-
if subfunction in subfunction2020 and standard_version < 2020:
205-
raise NotImplementedError('The subfunction 0x%02x has been introduced in standard version 2020 but decoding is requested to be done as per %d version' % (
206-
subfunction, standard_version))
198+
subfunction_disallow_map:Dict[int, List[int]] = {
199+
2006: [
200+
cls.Subfunction.reportDTCExtDataRecordByRecordNumber,
201+
cls.Subfunction.reportUserDefMemoryDTCByStatusMask,
202+
cls.Subfunction.reportUserDefMemoryDTCSnapshotRecordByDTCNumber,
203+
cls.Subfunction.reportUserDefMemoryDTCExtDataRecordByDTCNumber,
204+
#cls.Subfunction.reportDTCExtendedDataRecordIdentification, # Not implemented
205+
cls.Subfunction.reportWWHOBDDTCByMaskRecord,
206+
cls.Subfunction.reportWWHOBDDTCWithPermanentStatus,
207+
cls.Subfunction.reportDTCInformationByDTCReadinessGroupIdentifier,
208+
],
209+
2013: [
210+
#cls.Subfunction.reportDTCExtendedDataRecordIdentification, # Not implemented
211+
cls.Subfunction.reportDTCInformationByDTCReadinessGroupIdentifier,
212+
],
213+
2020: [
214+
cls.Subfunction.reportMirrorMemoryDTCByStatusMask,
215+
#cls.Subfunction.reportMirrorMemoryDTCExtDataRecordByDTCNumber, # Not implemented
216+
cls.Subfunction.reportNumberOfMirrorMemoryDTCByStatusMask,
217+
#cls.Subfunction.reportNumberOfEmissionsOBDDTCByStatusMask, # Not implemented
218+
#cls.Subfunction.reportEmissionsOBDDTCByStatusMask, # Not implemented
219+
]
220+
}
221+
222+
if standard_version in subfunction_disallow_map:
223+
if subfunction in subfunction_disallow_map[standard_version]:
224+
raise NotImplementedError(f"Subfunction 0x{subfunction:02x} is not allowed by ISO-14229:{standard_version}. Check for a different version of the standard")
207225

208226
@classmethod
209227
def make_request(cls,
@@ -248,6 +266,8 @@ def make_request(cls,
248266
249267
250268
:raises ValueError: If parameters are out of range, missing or wrong type
269+
:raises NotImplementedError: If the requested subfunction is not supported by the active ISO-14229 standard version
270+
251271
"""
252272

253273
# Request grouping for subfunctions that have the same request format
@@ -391,11 +411,11 @@ def make_request(cls,
391411
elif subfunction == ReadDTCInformation.Subfunction.reportWWHOBDDTCByMaskRecord:
392412
cls.assert_status_mask(status_mask, subfunction)
393413
cls.assert_severity_mask(severity_mask, subfunction)
394-
cls.assert_functional_group_id(functional_group_id, subfunction) # Maximum specified by ISO-14229:2020
414+
cls.assert_functional_group_id(functional_group_id, subfunction)
395415
req.data = struct.pack('BBB', functional_group_id, status_mask, severity_mask)
396416

397417
elif subfunction == ReadDTCInformation.Subfunction.reportWWHOBDDTCWithPermanentStatus:
398-
cls.assert_functional_group_id(functional_group_id, subfunction) # Maximum specified by ISO-14229:2020
418+
cls.assert_functional_group_id(functional_group_id, subfunction)
399419
req.data = struct.pack('B', functional_group_id)
400420

401421
return req
@@ -852,9 +872,6 @@ def interpret_response(cls,
852872
raise InvalidResponseException(response, 'Incomplete response from server. Missing DTCExtDataRecordNumber')
853873

854874
record_number = int(response.data[1])
855-
if record_number > 0xEF:
856-
raise InvalidResponseException(
857-
response, 'Server returned a RecordNumber of %d which is out of range (00-EF) according to ISO-14229:2020', record_number)
858875

859876
actual_byte = 2
860877
received_dtcs = set()
@@ -931,9 +948,8 @@ def interpret_response(cls,
931948
else:
932949
raise NotImplementedError("Unreachable code")
933950

934-
if response.service_data.functional_group_id > 0xFE:
935-
raise InvalidResponseException(response, "FunctionalGroupIdentifier returned by the server is not smaller or equal than 0xFE")
936-
951+
# Don't check functional_group_id on purpose. range 0 to FF is accepted
952+
937953
if response.service_data.dtc_format not in [Dtc.Format.SAE_J2012_DA_DTCFormat_04, Dtc.Format.SAE_J1939_73]:
938954
raise InvalidResponseException(response, "DTCFormatIdentifier returned by the server is not one of the following: SAE_J2012-DA_DTCFormat_04 (4), SAE_J1939-73_DTCFormat(2). Got 0x%02x" % response.service_data.dtc_format)
939955

0 commit comments

Comments
 (0)