Skip to content

Fix wwhobd dtc subfunctions #278

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions test/ClientServerTest.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
from udsoncan.connections import QueueConnection
from test.ThreadableTest import ThreadableTest
from udsoncan.client import Client
from typing import Optional

class ClientServerTest(ThreadableTest):
_standard_version:Optional[int]

def __init__(self, *args, **kwargs):
self._standard_version = None
ThreadableTest.__init__(self, *args, **kwargs)

def set_standard(self, version:int):
self._standard_version = version

def setUp(self):
self.conn = QueueConnection(name='unittest', mtu=4095)
Expand All @@ -15,6 +22,8 @@ def clientSetUp(self):
self.udsclient.set_config('exception_on_invalid_response', True)
self.udsclient.set_config('exception_on_unexpected_response', True)
self.udsclient.set_config('exception_on_negative_response', True)
if self._standard_version is not None:
self.udsclient.set_config('standard_version', self._standard_version)

self.udsclient.open()
if hasattr(self, "postClientSetUp"):
Expand Down
7 changes: 6 additions & 1 deletion test/client/test_read_dtc_information.py
Original file line number Diff line number Diff line change
Expand Up @@ -2347,26 +2347,31 @@ def __init__(self, *args, **kwargs):
class TestReportMirrorMemoryDTCByStatusMask(ClientServerTest, GenericTestStatusMaskRequest_DtcAndStatusMaskResponse): # Subfn = 0xF
def __init__(self, *args, **kwargs):
ClientServerTest.__init__(self, *args, **kwargs)
self.set_standard(2013) # Removed starting form 2020
GenericTestStatusMaskRequest_DtcAndStatusMaskResponse.__init__(self, subfunction=0xf, client_function = 'get_mirrormemory_dtc_by_status_mask')

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

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

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

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

class TestReportDTCFaultDetectionCounter(ClientServerTest): # Subfn = 0x14
Expand Down Expand Up @@ -3417,7 +3422,7 @@ def _test_oob_value(self):
self.udsclient.get_user_defined_memory_dtc_by_status_mask(0x12, 'aaa')

with self.assertRaises(NotImplementedError):
self.udsclient.set_config('standard_version', 2013)
self.udsclient.set_config('standard_version', 2006)
self.udsclient.get_user_defined_memory_dtc_by_status_mask(0x10, 0x20)
self.udsclient.set_config('standard_version', latest_standard)

Expand Down
4 changes: 3 additions & 1 deletion udsoncan/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,12 @@
__author__ = 'Pier-Yves Lessard'

latest_standard = 2020
valid_standards = [2006,2013,2020]

__default_log_config_file = path.join(path.dirname(path.abspath(__file__)), 'logging.conf')


def setup_logging(config_file=__default_log_config_file):
def setup_logging(config_file:str=__default_log_config_file):
"""
This function setup the logger accordingly to the module provided cfg file
"""
Expand Down
5 changes: 3 additions & 2 deletions udsoncan/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@

from udsoncan.exceptions import *
from udsoncan.configs import default_client_config
from udsoncan.typing import ClientConfig, TypedDict
from udsoncan.typing import ClientConfig
from udsoncan import valid_standards
import logging
import binascii
import functools
Expand Down Expand Up @@ -160,7 +161,7 @@ def refresh_config(self) -> None:
self.validate_config()

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

# Decorator to apply on functions that the user will call.
Expand Down
61 changes: 39 additions & 22 deletions udsoncan/services/ReadDTCInformation.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import struct
from udsoncan import Dtc, check_did_config, make_did_codec_from_definition, fetch_codec_definition_from_config, latest_standard, DIDConfig
from udsoncan import Dtc, check_did_config, make_did_codec_from_definition, fetch_codec_definition_from_config, latest_standard, valid_standards, DIDConfig
from udsoncan.Request import Request
from udsoncan.Response import Response
from udsoncan.exceptions import *
Expand Down Expand Up @@ -168,10 +168,12 @@ def assert_extended_data_size_int_or_dict(cls,
tools.validate_int(extended_data_size[dtcid], min=0, max=0xFFF, name='Extended data size for DTC=0x%06x' % dtcid)

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

@classmethod
def pack_dtc(cls, dtcid: int) -> bytes:
Expand All @@ -180,6 +182,9 @@ def pack_dtc(cls, dtcid: int) -> bytes:
@classmethod
def check_subfunction_valid(cls, subfunction: int, standard_version: int = latest_standard) -> None:
tools.validate_int(subfunction, min=1, max=0xFF, name='Subfunction')
if standard_version not in valid_standards:
raise ValueError(f"Standard version {standard_version} is not valid")

vlist = vars(cls)
ok = True
for v in vlist:
Expand All @@ -190,20 +195,33 @@ def check_subfunction_valid(cls, subfunction: int, standard_version: int = lates
raise ValueError('Unknown subfunction : 0x%02x', subfunction)

# These subfunction have been added in the 2020 version of the standard
subfunction2020 = [
cls.Subfunction.reportUserDefMemoryDTCByStatusMask,
cls.Subfunction.reportDTCExtDataRecordByRecordNumber,
cls.Subfunction.reportUserDefMemoryDTCSnapshotRecordByDTCNumber,
cls.Subfunction.reportUserDefMemoryDTCExtDataRecordByDTCNumber,
cls.Subfunction.reportSupportedDTCExtDataRecord,
cls.Subfunction.reportWWHOBDDTCByMaskRecord,
cls.Subfunction.reportWWHOBDDTCWithPermanentStatus,
cls.Subfunction.reportDTCInformationByDTCReadinessGroupIdentifier,
]

if subfunction in subfunction2020 and standard_version < 2020:
raise NotImplementedError('The subfunction 0x%02x has been introduced in standard version 2020 but decoding is requested to be done as per %d version' % (
subfunction, standard_version))
subfunction_disallow_map:Dict[int, List[int]] = {
2006: [
cls.Subfunction.reportDTCExtDataRecordByRecordNumber,
cls.Subfunction.reportUserDefMemoryDTCByStatusMask,
cls.Subfunction.reportUserDefMemoryDTCSnapshotRecordByDTCNumber,
cls.Subfunction.reportUserDefMemoryDTCExtDataRecordByDTCNumber,
#cls.Subfunction.reportDTCExtendedDataRecordIdentification, # Not implemented
cls.Subfunction.reportWWHOBDDTCByMaskRecord,
cls.Subfunction.reportWWHOBDDTCWithPermanentStatus,
cls.Subfunction.reportDTCInformationByDTCReadinessGroupIdentifier,
],
2013: [
#cls.Subfunction.reportDTCExtendedDataRecordIdentification, # Not implemented
cls.Subfunction.reportDTCInformationByDTCReadinessGroupIdentifier,
],
2020: [
cls.Subfunction.reportMirrorMemoryDTCByStatusMask,
#cls.Subfunction.reportMirrorMemoryDTCExtDataRecordByDTCNumber, # Not implemented
cls.Subfunction.reportNumberOfMirrorMemoryDTCByStatusMask,
#cls.Subfunction.reportNumberOfEmissionsOBDDTCByStatusMask, # Not implemented
#cls.Subfunction.reportEmissionsOBDDTCByStatusMask, # Not implemented
]
}

if standard_version in subfunction_disallow_map:
if subfunction in subfunction_disallow_map[standard_version]:
raise NotImplementedError(f"Subfunction 0x{subfunction:02x} is not allowed by ISO-14229:{standard_version}. Check for a different version of the standard")

@classmethod
def make_request(cls,
Expand Down Expand Up @@ -248,6 +266,8 @@ def make_request(cls,


:raises ValueError: If parameters are out of range, missing or wrong type
:raises NotImplementedError: If the requested subfunction is not supported by the active ISO-14229 standard version

"""

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

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

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

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

actual_byte = 2
received_dtcs = set()
Expand Down