py-uds-demo 26.0.1__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.
- py_uds_demo/__init__.py +0 -0
- py_uds_demo/__main__.py +57 -0
- py_uds_demo/core/__init__.py +0 -0
- py_uds_demo/core/client.py +80 -0
- py_uds_demo/core/server.py +227 -0
- py_uds_demo/core/utils/__init__.py +0 -0
- py_uds_demo/core/utils/helpers.py +314 -0
- py_uds_demo/core/utils/responses.py +55 -0
- py_uds_demo/core/utils/services/__init__.py +0 -0
- py_uds_demo/core/utils/services/data_transmission.py +398 -0
- py_uds_demo/core/utils/services/diagnostic_and_commmunication_management.py +755 -0
- py_uds_demo/core/utils/services/input_output_contol.py +63 -0
- py_uds_demo/core/utils/services/negative_response.py +1 -0
- py_uds_demo/core/utils/services/remote_activation_of_routine.py +80 -0
- py_uds_demo/core/utils/services/stored_data_transmission.py +132 -0
- py_uds_demo/core/utils/services/upload_download.py +189 -0
- py_uds_demo/interface/__init__.py +0 -0
- py_uds_demo/interface/api.py +30 -0
- py_uds_demo/interface/cli.py +51 -0
- py_uds_demo/interface/gui.py +83 -0
- py_uds_demo/interface/web.py +422 -0
- py_uds_demo-26.0.1.dist-info/METADATA +53 -0
- py_uds_demo-26.0.1.dist-info/RECORD +24 -0
- py_uds_demo-26.0.1.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
if TYPE_CHECKING:
|
|
3
|
+
from py_uds_demo.core.server import UdsServer
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class InputOutputControlByIdentifier:
|
|
7
|
+
"""
|
|
8
|
+
Handles Input Output Control By Identifier (0x2F) service requests.
|
|
9
|
+
|
|
10
|
+
What:
|
|
11
|
+
This service allows a client to take control of a server's (ECU's)
|
|
12
|
+
inputs and outputs.
|
|
13
|
+
|
|
14
|
+
Why:
|
|
15
|
+
It's used for testing and diagnostics. For example, a technician can
|
|
16
|
+
use it to manually activate an actuator (like a fan or a motor) to
|
|
17
|
+
verify its operation, or to simulate a sensor input to see how the
|
|
18
|
+
ECU responds.
|
|
19
|
+
|
|
20
|
+
How:
|
|
21
|
+
The client sends a request with the SID 0x2F, a Data Identifier (DID)
|
|
22
|
+
to specify the I/O channel, and a control option (e.g., return
|
|
23
|
+
control to ECU, freeze current state, short term adjustment).
|
|
24
|
+
|
|
25
|
+
Real-world example:
|
|
26
|
+
A technician suspects a radiator fan is faulty. They use a diagnostic
|
|
27
|
+
tool to send an Input Output Control By Identifier request to the
|
|
28
|
+
engine control unit, commanding it to turn on the fan. If the fan
|
|
29
|
+
starts, the technician knows the fan motor is working and the issue
|
|
30
|
+
lies elsewhere, perhaps with the temperature sensor or control logic.
|
|
31
|
+
|
|
32
|
+
Attributes:
|
|
33
|
+
uds_server: The UDS server instance.
|
|
34
|
+
io_control_status: A dictionary to store the status of I/O controls.
|
|
35
|
+
"""
|
|
36
|
+
def __init__(self, uds_server: 'UdsServer') -> None:
|
|
37
|
+
self.uds_server: 'UdsServer' = uds_server
|
|
38
|
+
self.io_control_status = {}
|
|
39
|
+
|
|
40
|
+
def process_request(self, data_stream: list) -> list:
|
|
41
|
+
"""
|
|
42
|
+
Processes an Input Output Control By Identifier request.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
data_stream: The request data stream.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
A list of bytes representing the response.
|
|
49
|
+
"""
|
|
50
|
+
if len(data_stream) < 4:
|
|
51
|
+
return self.uds_server.negative_response.report_negative_response(
|
|
52
|
+
self.uds_server.SID.IOCBI, self.uds_server.NRC.INCORRECT_MESSAGE_LENGTH_OR_INVALID_FORMAT
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
did = (data_stream[1] << 8) | data_stream[2]
|
|
56
|
+
control_option = data_stream[3]
|
|
57
|
+
|
|
58
|
+
# In a real ECU, you would check if the DID is valid for I/O control
|
|
59
|
+
# and if the control option is supported.
|
|
60
|
+
|
|
61
|
+
self.io_control_status[did] = control_option
|
|
62
|
+
|
|
63
|
+
return self.uds_server.positive_response.report_positive_response(self.uds_server.SID.IOCBI, data_stream[1:3])
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
if TYPE_CHECKING:
|
|
3
|
+
from py_uds_demo.core.server import UdsServer
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class RoutineControl:
|
|
7
|
+
"""
|
|
8
|
+
Handles Routine Control (0x31) service requests.
|
|
9
|
+
|
|
10
|
+
What:
|
|
11
|
+
This service is used to start, stop, and request the results of a
|
|
12
|
+
routine in the server (ECU).
|
|
13
|
+
|
|
14
|
+
Why:
|
|
15
|
+
Routines are used to perform more complex tasks than what can be
|
|
16
|
+
achieved with a simple read or write service. This can include
|
|
17
|
+
things like running a self-test, erasing memory, or learning new
|
|
18
|
+
adaptive values.
|
|
19
|
+
|
|
20
|
+
How:
|
|
21
|
+
The client sends a request with the SID 0x31, a sub-function
|
|
22
|
+
(e.g., startRoutine, stopRoutine, requestRoutineResults), and a
|
|
23
|
+
2-byte routine identifier.
|
|
24
|
+
|
|
25
|
+
Real-world example:
|
|
26
|
+
A technician wants to perform a self-test on the ABS. They use a
|
|
27
|
+
diagnostic tool to send a Routine Control request with the
|
|
28
|
+
'startRoutine' sub-function and the routine identifier for the ABS
|
|
29
|
+
self-test. After the routine completes, they send another request
|
|
30
|
+
with 'requestRoutineResults' to check if the test passed.
|
|
31
|
+
|
|
32
|
+
Attributes:
|
|
33
|
+
uds_server: The UDS server instance.
|
|
34
|
+
routine_status: A dictionary to store the status of routines.
|
|
35
|
+
"""
|
|
36
|
+
def __init__(self, uds_server: 'UdsServer') -> None:
|
|
37
|
+
self.uds_server: 'UdsServer' = uds_server
|
|
38
|
+
self.routine_status = {}
|
|
39
|
+
|
|
40
|
+
def process_request(self, data_stream: list) -> list:
|
|
41
|
+
"""
|
|
42
|
+
Processes a Routine Control request.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
data_stream: The request data stream.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
A list of bytes representing the response.
|
|
49
|
+
"""
|
|
50
|
+
if len(data_stream) < 4:
|
|
51
|
+
return self.uds_server.negative_response.report_negative_response(
|
|
52
|
+
self.uds_server.SID.RC, self.uds_server.NRC.INCORRECT_MESSAGE_LENGTH_OR_INVALID_FORMAT
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
sub_function = data_stream[1]
|
|
56
|
+
routine_id = (data_stream[2] << 8) | data_stream[3]
|
|
57
|
+
|
|
58
|
+
if sub_function == self.uds_server.SFID.START_ROUTINE:
|
|
59
|
+
self.routine_status[routine_id] = "Started"
|
|
60
|
+
return self.uds_server.positive_response.report_positive_response(self.uds_server.SID.RC, data_stream[1:4])
|
|
61
|
+
|
|
62
|
+
elif sub_function == self.uds_server.SFID.STOP_ROUTINE:
|
|
63
|
+
self.routine_status[routine_id] = "Stopped"
|
|
64
|
+
return self.uds_server.positive_response.report_positive_response(self.uds_server.SID.RC, data_stream[1:4])
|
|
65
|
+
|
|
66
|
+
elif sub_function == self.uds_server.SFID.REQUEST_ROUTINE_RESULT:
|
|
67
|
+
if routine_id in self.routine_status:
|
|
68
|
+
# Returning a dummy result
|
|
69
|
+
return self.uds_server.positive_response.report_positive_response(
|
|
70
|
+
self.uds_server.SID.RC, data_stream[1:4] + [0x01, 0x02, 0x03]
|
|
71
|
+
)
|
|
72
|
+
else:
|
|
73
|
+
return self.uds_server.negative_response.report_negative_response(
|
|
74
|
+
self.uds_server.SID.RC, self.uds_server.NRC.REQUEST_OUT_OF_RANGE
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
else:
|
|
78
|
+
return self.uds_server.negative_response.report_negative_response(
|
|
79
|
+
self.uds_server.SID.RC, self.uds_server.NRC.SUB_FUNCTION_NOT_SUPPORTED
|
|
80
|
+
)
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
if TYPE_CHECKING:
|
|
3
|
+
from py_uds_demo.core.server import UdsServer
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ClearDiagnosticInformation:
|
|
7
|
+
"""
|
|
8
|
+
Handles Clear Diagnostic Information (0x14) service requests.
|
|
9
|
+
|
|
10
|
+
What:
|
|
11
|
+
This service is used to clear Diagnostic Trouble Codes (DTCs) from
|
|
12
|
+
the server's (ECU's) memory.
|
|
13
|
+
|
|
14
|
+
Why:
|
|
15
|
+
After a vehicle has been repaired, the stored DTCs related to the
|
|
16
|
+
fixed issue need to be cleared. This service provides the means to
|
|
17
|
+
do so.
|
|
18
|
+
|
|
19
|
+
How:
|
|
20
|
+
The client sends a request with the SID 0x14, followed by a 3-byte
|
|
21
|
+
groupOfDTC parameter, which specifies which DTCs to clear. A value
|
|
22
|
+
of 0xFFFFFF is typically used to clear all DTCs.
|
|
23
|
+
|
|
24
|
+
Real-world example:
|
|
25
|
+
A "Check Engine" light is on. A technician reads the DTCs and finds
|
|
26
|
+
a code for a faulty sensor. After replacing the sensor, the technician
|
|
27
|
+
uses this service to clear the DTC, which turns off the light.
|
|
28
|
+
|
|
29
|
+
Attributes:
|
|
30
|
+
uds_server: The UDS server instance.
|
|
31
|
+
"""
|
|
32
|
+
def __init__(self, uds_server: 'UdsServer') -> None:
|
|
33
|
+
self.uds_server: 'UdsServer' = uds_server
|
|
34
|
+
|
|
35
|
+
def process_request(self, data_stream: list) -> list:
|
|
36
|
+
"""
|
|
37
|
+
Processes a Clear Diagnostic Information request.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
data_stream: The request data stream.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
A list of bytes representing the response.
|
|
44
|
+
"""
|
|
45
|
+
if self.uds_server.control_dtc_setting.dtc_setting == self.uds_server.SFID.OFF:
|
|
46
|
+
return self.uds_server.negative_response.report_negative_response(
|
|
47
|
+
self.uds_server.SID.CDTCI, self.uds_server.NRC.CONDITIONS_NOT_CORRECT
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
self.uds_server.memory.dtcs = []
|
|
51
|
+
return self.uds_server.positive_response.report_positive_response(self.uds_server.SID.CDTCI, [])
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class ReadDtcInformation:
|
|
55
|
+
"""
|
|
56
|
+
Handles Read DTC Information (0x19) service requests.
|
|
57
|
+
|
|
58
|
+
What:
|
|
59
|
+
This service is used to read Diagnostic Trouble Codes (DTCs) and
|
|
60
|
+
related data from the server's (ECU's) memory.
|
|
61
|
+
|
|
62
|
+
Why:
|
|
63
|
+
It's the primary service for diagnosing vehicle problems. By reading
|
|
64
|
+
DTCs, a technician can identify the system or component that is
|
|
65
|
+
faulty. It also allows reading additional data, like "freeze frames"
|
|
66
|
+
or "snapshot data," which is a snapshot of the vehicle's state at
|
|
67
|
+
the time the fault occurred.
|
|
68
|
+
|
|
69
|
+
How:
|
|
70
|
+
The client sends a request with the SID 0x19 and a sub-function that
|
|
71
|
+
specifies what information to read (e.g., number of DTCs, DTCs by
|
|
72
|
+
status mask, snapshot data).
|
|
73
|
+
|
|
74
|
+
Real-world example:
|
|
75
|
+
A technician connects a diagnostic tool to a car with the "Check
|
|
76
|
+
Engine" light on. The tool uses this service with the
|
|
77
|
+
'reportDTCByStatusMask' sub-function to retrieve all active DTCs.
|
|
78
|
+
The tool might then use another sub-function to read the snapshot
|
|
79
|
+
data for a specific DTC to get more context about when the fault
|
|
80
|
+
occurred.
|
|
81
|
+
|
|
82
|
+
Attributes:
|
|
83
|
+
uds_server: The UDS server instance.
|
|
84
|
+
"""
|
|
85
|
+
def __init__(self, uds_server: 'UdsServer') -> None:
|
|
86
|
+
self.uds_server: 'UdsServer' = uds_server
|
|
87
|
+
|
|
88
|
+
def process_request(self, data_stream: list) -> list:
|
|
89
|
+
"""
|
|
90
|
+
Processes a Read DTC Information request.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
data_stream: The request data stream.
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
A list of bytes representing the response.
|
|
97
|
+
"""
|
|
98
|
+
if len(data_stream) < 2:
|
|
99
|
+
return self.uds_server.negative_response.report_negative_response(
|
|
100
|
+
self.uds_server.SID.RDTCI, self.uds_server.NRC.INCORRECT_MESSAGE_LENGTH_OR_INVALID_FORMAT
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
sub_function = data_stream[1]
|
|
104
|
+
|
|
105
|
+
if sub_function == self.uds_server.SFID.REPORT_NUMBER_OF_DTC_BY_STATUS_MASK:
|
|
106
|
+
if len(data_stream) != 3:
|
|
107
|
+
return self.uds_server.negative_response.report_negative_response(
|
|
108
|
+
self.uds_server.SID.RDTCI, self.uds_server.NRC.INCORRECT_MESSAGE_LENGTH_OR_INVALID_FORMAT
|
|
109
|
+
)
|
|
110
|
+
status_mask = data_stream[2]
|
|
111
|
+
# In this simulation, we'll just count all DTCs regardless of status mask
|
|
112
|
+
num_dtcs = len(self.uds_server.memory.dtcs)
|
|
113
|
+
return self.uds_server.positive_response.report_positive_response(
|
|
114
|
+
self.uds_server.SID.RDTCI, [sub_function, status_mask, 0x01, num_dtcs]
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
elif sub_function == self.uds_server.SFID.REPORT_DTC_BY_STATUS_MASK:
|
|
118
|
+
if len(data_stream) != 3:
|
|
119
|
+
return self.uds_server.negative_response.report_negative_response(
|
|
120
|
+
self.uds_server.SID.RDTCI, self.uds_server.NRC.INCORRECT_MESSAGE_LENGTH_OR_INVALID_FORMAT
|
|
121
|
+
)
|
|
122
|
+
status_mask = data_stream[2]
|
|
123
|
+
# In this simulation, we'll return all DTCs regardless of status mask
|
|
124
|
+
response_data = [sub_function, status_mask]
|
|
125
|
+
for dtc in self.uds_server.memory.dtcs:
|
|
126
|
+
response_data.extend(dtc)
|
|
127
|
+
return self.uds_server.positive_response.report_positive_response(self.uds_server.SID.RDTCI, response_data)
|
|
128
|
+
|
|
129
|
+
else:
|
|
130
|
+
return self.uds_server.negative_response.report_negative_response(
|
|
131
|
+
self.uds_server.SID.RDTCI, self.uds_server.NRC.SUB_FUNCTION_NOT_SUPPORTED
|
|
132
|
+
)
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
if TYPE_CHECKING:
|
|
3
|
+
from py_uds_demo.core.server import UdsServer
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class RequestDownload:
|
|
7
|
+
"""
|
|
8
|
+
Handles Request Download (0x34) service requests.
|
|
9
|
+
|
|
10
|
+
What:
|
|
11
|
+
This service is used to initiate a data download from the client to
|
|
12
|
+
the server (ECU). It's the first step in the process of flashing new
|
|
13
|
+
software or writing a large block of data to the ECU.
|
|
14
|
+
|
|
15
|
+
Why:
|
|
16
|
+
It prepares the ECU to receive data, and the ECU can specify the
|
|
17
|
+
maximum size of the data blocks it can accept at a time.
|
|
18
|
+
|
|
19
|
+
How:
|
|
20
|
+
The client sends a request with the SID 0x34, the memory address
|
|
21
|
+
where the data should be stored, and the total size of the data.
|
|
22
|
+
|
|
23
|
+
Note:
|
|
24
|
+
This service is not fully implemented in this simulator.
|
|
25
|
+
"""
|
|
26
|
+
def __init__(self, uds_server: 'UdsServer') -> None:
|
|
27
|
+
self.uds_server: 'UdsServer' = uds_server
|
|
28
|
+
|
|
29
|
+
def process_request(self, data_stream: list) -> list:
|
|
30
|
+
"""
|
|
31
|
+
Processes a Request Download request.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
data_stream: The request data stream.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
A negative response, as this service is not supported.
|
|
38
|
+
"""
|
|
39
|
+
return self.uds_server.negative_response.report_negative_response(
|
|
40
|
+
self.uds_server.SID.RD, self.uds_server.NRC.SERVICE_NOT_SUPPORTED
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class RequestUpload:
|
|
45
|
+
"""
|
|
46
|
+
Handles Request Upload (0x35) service requests.
|
|
47
|
+
|
|
48
|
+
What:
|
|
49
|
+
This service is used to initiate a data upload from the server (ECU)
|
|
50
|
+
to the client.
|
|
51
|
+
|
|
52
|
+
Why:
|
|
53
|
+
It's used to read large blocks of data from the ECU, such as log
|
|
54
|
+
files, calibration data, or the entire memory content.
|
|
55
|
+
|
|
56
|
+
How:
|
|
57
|
+
The client sends a request with the SID 0x35, the memory address
|
|
58
|
+
of the data to be uploaded, and the size of the data.
|
|
59
|
+
|
|
60
|
+
Note:
|
|
61
|
+
This service is not fully implemented in this simulator.
|
|
62
|
+
"""
|
|
63
|
+
def __init__(self, uds_server: 'UdsServer') -> None:
|
|
64
|
+
self.uds_server: 'UdsServer' = uds_server
|
|
65
|
+
|
|
66
|
+
def process_request(self, data_stream: list) -> list:
|
|
67
|
+
"""
|
|
68
|
+
Processes a Request Upload request.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
data_stream: The request data stream.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
A negative response, as this service is not supported.
|
|
75
|
+
"""
|
|
76
|
+
return self.uds_server.negative_response.report_negative_response(
|
|
77
|
+
self.uds_server.SID.RU, self.uds_server.NRC.SERVICE_NOT_SUPPORTED
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class TransferData:
|
|
82
|
+
"""
|
|
83
|
+
Handles Transfer Data (0x36) service requests.
|
|
84
|
+
|
|
85
|
+
What:
|
|
86
|
+
This service is used to transfer data blocks between the client and
|
|
87
|
+
the server during an upload or download operation.
|
|
88
|
+
|
|
89
|
+
Why:
|
|
90
|
+
It's the workhorse of the data transfer process, responsible for
|
|
91
|
+
moving the actual data in chunks.
|
|
92
|
+
|
|
93
|
+
How:
|
|
94
|
+
After a download or upload is initiated, the client (for downloads)
|
|
95
|
+
or server (for uploads) sends a sequence of Transfer Data requests,
|
|
96
|
+
each containing a block of data.
|
|
97
|
+
|
|
98
|
+
Note:
|
|
99
|
+
This service is not fully implemented in this simulator.
|
|
100
|
+
"""
|
|
101
|
+
def __init__(self, uds_server: 'UdsServer') -> None:
|
|
102
|
+
self.uds_server: 'UdsServer' = uds_server
|
|
103
|
+
|
|
104
|
+
def process_request(self, data_stream: list) -> list:
|
|
105
|
+
"""
|
|
106
|
+
Processes a Transfer Data request.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
data_stream: The request data stream.
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
A negative response, as this service is not supported.
|
|
113
|
+
"""
|
|
114
|
+
return self.uds_server.negative_response.report_negative_response(
|
|
115
|
+
self.uds_server.SID.TD, self.uds_server.NRC.SERVICE_NOT_SUPPORTED
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class RequestTransferExit:
|
|
120
|
+
"""
|
|
121
|
+
Handles Request Transfer Exit (0x37) service requests.
|
|
122
|
+
|
|
123
|
+
What:
|
|
124
|
+
This service is used to terminate a data transfer sequence.
|
|
125
|
+
|
|
126
|
+
Why:
|
|
127
|
+
It signals the end of the upload or download process, allowing the
|
|
128
|
+
server to perform any necessary cleanup or verification.
|
|
129
|
+
|
|
130
|
+
How:
|
|
131
|
+
The client sends a request with the SID 0x37 to indicate that the
|
|
132
|
+
transfer is complete.
|
|
133
|
+
|
|
134
|
+
Note:
|
|
135
|
+
This service is not fully implemented in this simulator.
|
|
136
|
+
"""
|
|
137
|
+
def __init__(self, uds_server: 'UdsServer') -> None:
|
|
138
|
+
self.uds_server: 'UdsServer' = uds_server
|
|
139
|
+
|
|
140
|
+
def process_request(self, data_stream: list) -> list:
|
|
141
|
+
"""
|
|
142
|
+
Processes a Request Transfer Exit request.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
data_stream: The request data stream.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
A negative response, as this service is not supported.
|
|
149
|
+
"""
|
|
150
|
+
return self.uds_server.negative_response.report_negative_response(
|
|
151
|
+
self.uds_server.SID.RTE, self.uds_server.NRC.SERVICE_NOT_SUPPORTED
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class RequestFileTransfer:
|
|
156
|
+
"""
|
|
157
|
+
Handles Request File Transfer (0x38) service requests.
|
|
158
|
+
|
|
159
|
+
What:
|
|
160
|
+
This service provides a more advanced and flexible way to transfer
|
|
161
|
+
files between the client and the server, often with file-system-like
|
|
162
|
+
operations.
|
|
163
|
+
|
|
164
|
+
Why:
|
|
165
|
+
It's designed to be more powerful than the older upload/download
|
|
166
|
+
services, supporting more complex use cases.
|
|
167
|
+
|
|
168
|
+
How:
|
|
169
|
+
The specifics are complex and can vary between implementations.
|
|
170
|
+
|
|
171
|
+
Note:
|
|
172
|
+
This service is not fully implemented in this simulator.
|
|
173
|
+
"""
|
|
174
|
+
def __init__(self, uds_server: 'UdsServer') -> None:
|
|
175
|
+
self.uds_server: 'UdsServer' = uds_server
|
|
176
|
+
|
|
177
|
+
def process_request(self, data_stream: list) -> list:
|
|
178
|
+
"""
|
|
179
|
+
Processes a Request File Transfer request.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
data_stream: The request data stream.
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
A negative response, as this service is not supported.
|
|
186
|
+
"""
|
|
187
|
+
return self.uds_server.negative_response.report_negative_response(
|
|
188
|
+
self.uds_server.SID.RFT, self.uds_server.NRC.SERVICE_NOT_SUPPORTED
|
|
189
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from fastapi import FastAPI, HTTPException
|
|
2
|
+
from pydantic import BaseModel
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
from py_uds_demo.core.client import UdsClient
|
|
6
|
+
|
|
7
|
+
app = FastAPI()
|
|
8
|
+
client = UdsClient()
|
|
9
|
+
|
|
10
|
+
class UdsRequest(BaseModel):
|
|
11
|
+
data: List[int]
|
|
12
|
+
|
|
13
|
+
@app.post("/send_request")
|
|
14
|
+
async def send_request(request: UdsRequest):
|
|
15
|
+
"""
|
|
16
|
+
Sends a UDS request to the server.
|
|
17
|
+
"""
|
|
18
|
+
response = client.send_request(request.data, False)
|
|
19
|
+
return {"response": response}
|
|
20
|
+
|
|
21
|
+
@app.get("/help/{sid}")
|
|
22
|
+
async def get_help(sid: int):
|
|
23
|
+
"""
|
|
24
|
+
Returns the docstring for a given service ID.
|
|
25
|
+
"""
|
|
26
|
+
service = client.server.service_map.get(sid)
|
|
27
|
+
if service:
|
|
28
|
+
return {"docstring": service.__doc__}
|
|
29
|
+
else:
|
|
30
|
+
raise HTTPException(status_code=404, detail=f"No help found for SID 0x{sid:02X}")
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from py_uds_demo.core.client import UdsClient
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Cli:
|
|
5
|
+
def __init__(self):
|
|
6
|
+
self.client = UdsClient()
|
|
7
|
+
|
|
8
|
+
def _help(self):
|
|
9
|
+
print("💡 Displaying help information.")
|
|
10
|
+
print("💡 UDS CLI Help:")
|
|
11
|
+
print("💡 Enter diagnostic requests in hex format (e.g., 22 F1 87).")
|
|
12
|
+
print("💡 Type 'exit' or 'q' to quit the CLI.")
|
|
13
|
+
print("💡 Type 'help' or 'h' or '?' for general help.")
|
|
14
|
+
print("💡 Type 'help <SID>' for help on a specific service (e.g., 'help 10').")
|
|
15
|
+
|
|
16
|
+
def run(self):
|
|
17
|
+
print("🏃➡️ Running UDS Simulation in CLI mode.")
|
|
18
|
+
self._help()
|
|
19
|
+
while True:
|
|
20
|
+
user_input = input("💉 ")
|
|
21
|
+
if user_input.lower() in ['exit', 'q']:
|
|
22
|
+
print("👋 Closed UDS Simulation CLI mode.")
|
|
23
|
+
break
|
|
24
|
+
if user_input.lower().startswith('help '):
|
|
25
|
+
try:
|
|
26
|
+
sid_str = user_input.split(' ')[1]
|
|
27
|
+
sid = int(sid_str, 16)
|
|
28
|
+
service = self.client.server.service_map.get(sid)
|
|
29
|
+
if service:
|
|
30
|
+
print(service.__doc__)
|
|
31
|
+
else:
|
|
32
|
+
print(f"😡 No help found for SID 0x{sid:02X}.")
|
|
33
|
+
except (ValueError, IndexError):
|
|
34
|
+
print("😡 Invalid help command. Use 'help <SID_in_hex>' (e.g., 'help 10').")
|
|
35
|
+
continue
|
|
36
|
+
if user_input.lower() in ['help', 'h', '?']:
|
|
37
|
+
self._help()
|
|
38
|
+
continue
|
|
39
|
+
diagnostic_request_clean = user_input.replace(" ", "")
|
|
40
|
+
try:
|
|
41
|
+
diagnostic_request_stream = [int(diagnostic_request_clean[i:i+2], 16) for i in range(0, len(diagnostic_request_clean), 2)]
|
|
42
|
+
response = self.client.send_request(diagnostic_request_stream, True)
|
|
43
|
+
print(response)
|
|
44
|
+
except ValueError:
|
|
45
|
+
print(f"😡 Invalid input({user_input}). Please enter a valid hex string.")
|
|
46
|
+
continue
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
if __name__ == "__main__":
|
|
50
|
+
cli = Cli()
|
|
51
|
+
cli.run()
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
from dearpygui import dearpygui as dpg
|
|
2
|
+
from py_uds_demo.core.client import UdsClient
|
|
3
|
+
|
|
4
|
+
class Gui:
|
|
5
|
+
def __init__(self):
|
|
6
|
+
self.client = UdsClient()
|
|
7
|
+
self._setup_ui()
|
|
8
|
+
|
|
9
|
+
def _setup_ui(self):
|
|
10
|
+
dpg.create_context()
|
|
11
|
+
dpg.create_viewport(title="UDS Simulation GUI", width=920, height=500)
|
|
12
|
+
|
|
13
|
+
with dpg.window(label="UDS Simulation GUI", width=900, height=480, tag="main_window"):
|
|
14
|
+
with dpg.group(horizontal=True):
|
|
15
|
+
dpg.add_checkbox(label="Tester Present", callback=self._toggle_tester_present, tag="tester_present_checkbox")
|
|
16
|
+
|
|
17
|
+
with dpg.child_window(border=True, autosize_x=True, autosize_y=False, height=120):
|
|
18
|
+
with dpg.group(horizontal=True):
|
|
19
|
+
dpg.add_text("Tx Request")
|
|
20
|
+
dpg.add_input_text(label="", width=400, tag="request_entry", on_enter=True, callback=self._send_request_callback)
|
|
21
|
+
dpg.add_button(label="Send", callback=self._send_request_callback)
|
|
22
|
+
dpg.add_text("Rx Response")
|
|
23
|
+
dpg.add_input_text(label="", width=850, height=50, multiline=True, tag="response_textbox", readonly=True)
|
|
24
|
+
|
|
25
|
+
with dpg.child_window(border=True, autosize_x=True, autosize_y=False, height=120):
|
|
26
|
+
dpg.add_text("History")
|
|
27
|
+
dpg.add_input_text(multiline=True, width=850, height=80, tag="history_textbox", readonly=True)
|
|
28
|
+
|
|
29
|
+
with dpg.group(horizontal=True):
|
|
30
|
+
dpg.add_text("Help")
|
|
31
|
+
dpg.add_input_text(label="", width=200, tag="help_entry", hint="Enter SID (e.g., 10)", on_enter=True, callback=self._show_help_callback)
|
|
32
|
+
dpg.add_button(label="Get Help", callback=self._show_help_callback)
|
|
33
|
+
|
|
34
|
+
dpg.setup_dearpygui()
|
|
35
|
+
dpg.show_viewport()
|
|
36
|
+
dpg.set_primary_window("main_window", True)
|
|
37
|
+
dpg.start_dearpygui()
|
|
38
|
+
dpg.destroy_context()
|
|
39
|
+
|
|
40
|
+
def _show_help_callback(self, sender, app_data, user_data):
|
|
41
|
+
sid_str = dpg.get_value("help_entry")
|
|
42
|
+
try:
|
|
43
|
+
sid = int(sid_str, 16)
|
|
44
|
+
service = self.client.server.service_map.get(sid)
|
|
45
|
+
if service:
|
|
46
|
+
title = f"Help for SID 0x{sid:02X}"
|
|
47
|
+
message = service.__doc__ or "No documentation available."
|
|
48
|
+
else:
|
|
49
|
+
title = "Error"
|
|
50
|
+
message = f"No help found for SID 0x{sid:02X}"
|
|
51
|
+
except ValueError:
|
|
52
|
+
title = "Error"
|
|
53
|
+
message = "Invalid SID. Please enter a valid hex value."
|
|
54
|
+
|
|
55
|
+
if dpg.does_item_exist("help_modal"):
|
|
56
|
+
dpg.delete_item("help_modal")
|
|
57
|
+
with dpg.window(label=title, modal=True, tag="help_modal", no_close=False, width=700, height=300):
|
|
58
|
+
dpg.add_text(message)
|
|
59
|
+
dpg.add_button(label="Close", callback=lambda: dpg.delete_item("help_modal"))
|
|
60
|
+
|
|
61
|
+
def _send_request_callback(self, sender=None, app_data=None, user_data=None):
|
|
62
|
+
request_data = dpg.get_value("request_entry").replace(" ", "")
|
|
63
|
+
try:
|
|
64
|
+
request_data_stream = [int(request_data[i:i+2], 16) for i in range(0, len(request_data), 2)]
|
|
65
|
+
request_data_formatted = " ".join(f"{b:02X}" for b in request_data_stream)
|
|
66
|
+
response_data = self.client.send_request(request_data_stream, False)
|
|
67
|
+
response_data_formatted = " ".join(f"{b:02X}" for b in response_data)
|
|
68
|
+
except ValueError:
|
|
69
|
+
request_data_formatted = f"😡 Invalid input({request_data}). Please enter a valid hex string."
|
|
70
|
+
response_data_formatted = ""
|
|
71
|
+
except Exception as e:
|
|
72
|
+
request_data_formatted = f"😡 An error occurred: {e}"
|
|
73
|
+
response_data_formatted = ""
|
|
74
|
+
dpg.set_value("response_textbox", response_data_formatted)
|
|
75
|
+
current_history = dpg.get_value("history_textbox")
|
|
76
|
+
new_entry = f"-> {request_data_formatted}\n<- {response_data_formatted}\n"
|
|
77
|
+
dpg.set_value("history_textbox", new_entry + current_history)
|
|
78
|
+
|
|
79
|
+
def _toggle_tester_present(self, sender, app_data, user_data):
|
|
80
|
+
self.client.server.diagnostic_session_control.tester_present_active = bool(dpg.get_value("tester_present_checkbox"))
|
|
81
|
+
|
|
82
|
+
if __name__ == "__main__":
|
|
83
|
+
Gui()
|