autouds 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.
- autouds/__init__.py +13 -0
- autouds/__main__.py +44 -0
- autouds/_handler.py +106 -0
- autouds/_models.py +226 -0
- autouds/_tables.py +441 -0
- autouds/app.py +270 -0
- autouds-0.1.0.dist-info/METADATA +15 -0
- autouds-0.1.0.dist-info/RECORD +10 -0
- autouds-0.1.0.dist-info/WHEEL +4 -0
- autouds-0.1.0.dist-info/entry_points.txt +2 -0
autouds/__init__.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@文件: __init__.py
|
|
3
|
+
@作者: 雷小鸥
|
|
4
|
+
@日期: 2026/6/9 18:58
|
|
5
|
+
@许可: MIT License
|
|
6
|
+
@描述:
|
|
7
|
+
@版本: Version 0.1
|
|
8
|
+
"""
|
|
9
|
+
from ._handler import handler, set_send_impl
|
|
10
|
+
from ._models import Response, Request, UdsError
|
|
11
|
+
from .app import App
|
|
12
|
+
|
|
13
|
+
__all__ = ['handler', 'set_send_impl', 'Response', 'Request', 'UdsError', 'App']
|
autouds/__main__.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@文件: __main__.py
|
|
3
|
+
@作者: 雷小鸥
|
|
4
|
+
@日期: 2026/6/10 08:51
|
|
5
|
+
@许可: MIT License
|
|
6
|
+
@描述: UDS 诊断入口
|
|
7
|
+
@版本: Version 0.1
|
|
8
|
+
"""
|
|
9
|
+
import logging
|
|
10
|
+
from . import App
|
|
11
|
+
|
|
12
|
+
logging.basicConfig(
|
|
13
|
+
level=logging.INFO,
|
|
14
|
+
format='%(asctime)s - %(name)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(funcName)s - %(message)s',
|
|
15
|
+
datefmt='%Y-%m-%d %H:%M:%S'
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def main():
|
|
20
|
+
app = App(
|
|
21
|
+
ip='<tester-ip>',
|
|
22
|
+
ecus={
|
|
23
|
+
'mcu': (0x1001, '<ecu-ip>', 0),
|
|
24
|
+
'soc': (0x1002, '<ecu-ip>', 0),
|
|
25
|
+
},
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
# 诊断会话控制
|
|
29
|
+
p2, p2_star = app.default()
|
|
30
|
+
print(f'default: P2={p2}, P2*={p2_star}')
|
|
31
|
+
|
|
32
|
+
# 切换到 soc
|
|
33
|
+
app.on('soc')
|
|
34
|
+
|
|
35
|
+
# 安全访问
|
|
36
|
+
seed = app.l1()
|
|
37
|
+
print(f'L1 seed: {seed.hex()}')
|
|
38
|
+
|
|
39
|
+
app.l1_ex(seed)
|
|
40
|
+
print('L1 unlock done')
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
if __name__ == '__main__':
|
|
44
|
+
main()
|
autouds/_handler.py
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@文件: _handler.py
|
|
3
|
+
@作者: 雷小鸥
|
|
4
|
+
@日期: 2026/6/11 12:01
|
|
5
|
+
@许可: MIT License
|
|
6
|
+
@描述: handler 装饰器 + 发送桥接。
|
|
7
|
+
@版本: Version 0.1
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import functools
|
|
12
|
+
from typing import Callable, Generator, ParamSpec, TypeVar
|
|
13
|
+
from ._models import Request, Response, UdsError
|
|
14
|
+
|
|
15
|
+
P = ParamSpec("P")
|
|
16
|
+
R = TypeVar("R")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def handler(*args, **kwargs):
|
|
20
|
+
"""装饰器工厂:预建 Request,驱动用户生成器完成 UDS 交互。"""
|
|
21
|
+
fn = args[0] if len(args) == 1 else None
|
|
22
|
+
|
|
23
|
+
def decorator(f: Callable[..., Generator[bytes | None, Response, R]]) -> Callable[..., R]:
|
|
24
|
+
@functools.wraps(f)
|
|
25
|
+
def wrapper(self, *inner_args, **inner_kwargs):
|
|
26
|
+
# 1. 从装饰器参数取初始值(可能为 None)
|
|
27
|
+
s_name = kwargs.get('service_name', None)
|
|
28
|
+
s_id = kwargs.get('s_id', None)
|
|
29
|
+
fn_name = kwargs.get('sub_fn_name', None)
|
|
30
|
+
sub_fn = kwargs.get('sub_fn', None)
|
|
31
|
+
supp = kwargs.get('suppress_positive_response', None)
|
|
32
|
+
|
|
33
|
+
# 2. 调用时关键字覆盖:不传则保留装饰器参数
|
|
34
|
+
s_name = inner_kwargs.pop('service_name', s_name)
|
|
35
|
+
s_id = inner_kwargs.pop('s_id', s_id)
|
|
36
|
+
fn_name = inner_kwargs.pop('sub_fn_name', fn_name)
|
|
37
|
+
sub_fn = inner_kwargs.pop('sub_fn', sub_fn)
|
|
38
|
+
supp = inner_kwargs.pop('suppress_positive_response', supp)
|
|
39
|
+
|
|
40
|
+
# 3. 最终默认值
|
|
41
|
+
if supp is None:
|
|
42
|
+
supp = False
|
|
43
|
+
|
|
44
|
+
# 4. 工厂形式:驱动生成器获取 params
|
|
45
|
+
gen = f(self, *inner_args, **inner_kwargs)
|
|
46
|
+
params = next(gen) # 驱动生成器到第一个 yield
|
|
47
|
+
if params is not None and not isinstance(params, bytes):
|
|
48
|
+
raise TypeError("生成器第一个 yield 必须为 bytes 或 None")
|
|
49
|
+
|
|
50
|
+
request = Request.model_validate({
|
|
51
|
+
'service_name' : s_name,
|
|
52
|
+
'service_id' : s_id,
|
|
53
|
+
'sub_fn_name' : fn_name,
|
|
54
|
+
'sub_fn' : sub_fn,
|
|
55
|
+
'suppress_positive_response': supp,
|
|
56
|
+
'params' : params
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
# 6. 发送请求、收集响应
|
|
60
|
+
resp: Response | None = None
|
|
61
|
+
for raw in send(request.raw):
|
|
62
|
+
resp = Response.model_validate({
|
|
63
|
+
'raw': raw, 'request': request, 'father': resp
|
|
64
|
+
})
|
|
65
|
+
if not (resp.is_negative and resp.nrc == 0x78):
|
|
66
|
+
break
|
|
67
|
+
|
|
68
|
+
if resp is None:
|
|
69
|
+
resp = Response.model_validate({'request': request})
|
|
70
|
+
|
|
71
|
+
if resp.is_negative:
|
|
72
|
+
raise UdsError(resp)
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
gen.send(resp)
|
|
76
|
+
except StopIteration as e:
|
|
77
|
+
return e.value
|
|
78
|
+
finally:
|
|
79
|
+
gen.close()
|
|
80
|
+
|
|
81
|
+
raise RuntimeError(
|
|
82
|
+
"generator must return immediately after receiving Response"
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
return wrapper
|
|
86
|
+
|
|
87
|
+
return decorator(fn) if fn else decorator
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
91
|
+
# 发送桥接
|
|
92
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
93
|
+
|
|
94
|
+
_send_impl = None
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def set_send_impl(func):
|
|
98
|
+
"""注入真正的 send 实现(生成器函数,接收 bytes 载荷,yield 原始响应字节)。"""
|
|
99
|
+
global _send_impl
|
|
100
|
+
_send_impl = func
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def send(payload: bytes):
|
|
104
|
+
if _send_impl is None:
|
|
105
|
+
raise RuntimeError("请先调用 set_send_impl() 设置发送函数")
|
|
106
|
+
yield from _send_impl(payload)
|
autouds/_models.py
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@文件: _models.py
|
|
3
|
+
@作者: 雷小鸥
|
|
4
|
+
@日期: 2026/6/11 12:02
|
|
5
|
+
@许可: MIT License
|
|
6
|
+
@描述:
|
|
7
|
+
UDS Request / Response 模型
|
|
8
|
+
|
|
9
|
+
Request — 构造时可用 model_validate 传入语义名,内部查表转换:
|
|
10
|
+
- Request.model_validate({'service_name': 'session', 'sub_fn_name': 'default'})
|
|
11
|
+
- Request.model_validate({'service_id': 0x10, 'sub_fn': 0x01})
|
|
12
|
+
|
|
13
|
+
Response — 两阶段:先创建空壳,收到 raw 后 _parse 填充
|
|
14
|
+
|
|
15
|
+
@版本: Version 0.1
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from pydantic import BaseModel, Field, model_validator, PrivateAttr
|
|
21
|
+
from ._tables import SERVICE_MAP, SUB_FN_MAP, NRC_MAP
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
25
|
+
# Request
|
|
26
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
27
|
+
|
|
28
|
+
class Request(BaseModel):
|
|
29
|
+
service_id: int = Field(..., ge=0x00, le=0xFF)
|
|
30
|
+
sub_fn: int | None = Field(None, ge=0x00, le=0x7F)
|
|
31
|
+
suppress_positive_response: bool = False
|
|
32
|
+
|
|
33
|
+
params: bytes | None = Field(None)
|
|
34
|
+
|
|
35
|
+
_fn_byte: bytes | None = PrivateAttr(None)
|
|
36
|
+
|
|
37
|
+
# noinspection PyPropertyDefinition
|
|
38
|
+
@property
|
|
39
|
+
def raw(self) -> bytes:
|
|
40
|
+
return self.service_id.to_bytes(1, 'big') + (self._fn_byte or b'') + (self.params or b'')
|
|
41
|
+
|
|
42
|
+
# noinspection PyPropertyDefinition
|
|
43
|
+
@property
|
|
44
|
+
def hex(self) -> str:
|
|
45
|
+
return ' '.join(f'{b:02X}' for b in self.raw)
|
|
46
|
+
|
|
47
|
+
# noinspection PyNestedDecorators
|
|
48
|
+
@model_validator(mode='before')
|
|
49
|
+
@classmethod
|
|
50
|
+
def _preprocess(cls, data):
|
|
51
|
+
"""统一处理 service_name / sub_fn_name,并按依赖顺序验证。"""
|
|
52
|
+
if not isinstance(data, dict):
|
|
53
|
+
return data
|
|
54
|
+
|
|
55
|
+
# 1. 解析 service_name -> service_id
|
|
56
|
+
s_name = data.pop('service_name', None)
|
|
57
|
+
if s_name is not None:
|
|
58
|
+
resolved_sid = SERVICE_MAP.get(s_name)
|
|
59
|
+
if resolved_sid is None:
|
|
60
|
+
raise ValueError(f'未知服务名: {s_name!r}')
|
|
61
|
+
existing_sid = data.get('service_id')
|
|
62
|
+
if existing_sid is not None and existing_sid != resolved_sid:
|
|
63
|
+
raise ValueError(
|
|
64
|
+
f'service_name({s_name!r}) 与 service_id(0x{existing_sid:02X}) 冲突'
|
|
65
|
+
)
|
|
66
|
+
data['service_id'] = resolved_sid
|
|
67
|
+
|
|
68
|
+
# 2. 尝试获取 service_id(可能上一步已设置,或用户直接提供)
|
|
69
|
+
s_id = data.get('service_id')
|
|
70
|
+
|
|
71
|
+
# 3. 解析 sub_fn_name -> sub_fn(此时必须已知 service_id)
|
|
72
|
+
sub_fn_name = data.pop('sub_fn_name', None)
|
|
73
|
+
if sub_fn_name is not None:
|
|
74
|
+
if s_id is None:
|
|
75
|
+
raise ValueError(
|
|
76
|
+
'提供了 sub_fn_name 但未指定服务,请同时提供 service_id 或 service_name'
|
|
77
|
+
)
|
|
78
|
+
table = SUB_FN_MAP.get(s_id)
|
|
79
|
+
if table is None or not table.get('enable'):
|
|
80
|
+
raise ValueError(
|
|
81
|
+
f'服务 0x{s_id:02X} 不支持子功能,却提供了 sub_fn_name: {sub_fn_name!r}'
|
|
82
|
+
)
|
|
83
|
+
resolved_fn = table.get(sub_fn_name)
|
|
84
|
+
if resolved_fn is None:
|
|
85
|
+
raise ValueError(
|
|
86
|
+
f'未知子功能名: {sub_fn_name!r} (服务 0x{s_id:02X})'
|
|
87
|
+
)
|
|
88
|
+
existing_fn = data.get('sub_fn')
|
|
89
|
+
if existing_fn is not None and existing_fn != resolved_fn:
|
|
90
|
+
raise ValueError(
|
|
91
|
+
f'sub_fn_name({sub_fn_name!r}) 与 sub_fn(0x{existing_fn:02X}) 冲突'
|
|
92
|
+
)
|
|
93
|
+
data['sub_fn'] = resolved_fn
|
|
94
|
+
|
|
95
|
+
# 4. 验证直接传入的 sub_fn 值(如果存在)
|
|
96
|
+
fn = data.get('sub_fn')
|
|
97
|
+
if fn is not None:
|
|
98
|
+
if not isinstance(fn, int) or not (0x00 <= fn <= 0x7F):
|
|
99
|
+
raise ValueError('sub_fn 必须是 0x00~0x7F 的整数')
|
|
100
|
+
if s_id is None:
|
|
101
|
+
raise ValueError('提供了 sub_fn 但未指定服务,请同时提供 service_id 或 service_name')
|
|
102
|
+
table = SUB_FN_MAP.get(s_id)
|
|
103
|
+
if table is None or not table.get('enable'):
|
|
104
|
+
raise ValueError(f'服务 0x{s_id:02X} 不支持子功能,却提供了 sub_fn=0x{fn:02X}')
|
|
105
|
+
|
|
106
|
+
allowed_values = {v for k, v in table.items() if k != 'enable'}
|
|
107
|
+
if fn not in allowed_values:
|
|
108
|
+
raise ValueError(f'sub_fn=0x{fn:02X} 对于服务 0x{s_id:02X} 无效')
|
|
109
|
+
|
|
110
|
+
return data
|
|
111
|
+
|
|
112
|
+
@model_validator(mode='after')
|
|
113
|
+
def _build_fn_byte(self):
|
|
114
|
+
"""根据 sub_fn 和 suppress_positive_response 构造功能字节。"""
|
|
115
|
+
if self.sub_fn is None:
|
|
116
|
+
self._fn_byte = b''
|
|
117
|
+
else:
|
|
118
|
+
fn_byte = self.sub_fn
|
|
119
|
+
if self.suppress_positive_response:
|
|
120
|
+
fn_byte |= 0x80
|
|
121
|
+
self._fn_byte = fn_byte.to_bytes(1, 'big')
|
|
122
|
+
return self
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
126
|
+
# Response
|
|
127
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
128
|
+
|
|
129
|
+
class Response(BaseModel):
|
|
130
|
+
father: Response | None = None
|
|
131
|
+
request: Request
|
|
132
|
+
raw: bytes = b''
|
|
133
|
+
|
|
134
|
+
# ── 解析后的字段 ──
|
|
135
|
+
ok: bool = False
|
|
136
|
+
is_negative: bool = False
|
|
137
|
+
|
|
138
|
+
service_id: int = 0
|
|
139
|
+
sub_fn: int | None = None
|
|
140
|
+
params: bytes = b''
|
|
141
|
+
|
|
142
|
+
request_id: int = 0
|
|
143
|
+
nrc: int = 0
|
|
144
|
+
nrc_desc: str = ''
|
|
145
|
+
|
|
146
|
+
# noinspection PyPropertyDefinition
|
|
147
|
+
@property
|
|
148
|
+
def hex(self) -> str:
|
|
149
|
+
return ' '.join(f'{b:02X}' for b in self.raw)
|
|
150
|
+
|
|
151
|
+
@model_validator(mode='after')
|
|
152
|
+
def _parse(self):
|
|
153
|
+
if not self.raw:
|
|
154
|
+
return self
|
|
155
|
+
|
|
156
|
+
raw = self.raw
|
|
157
|
+
s_id = raw[0]
|
|
158
|
+
|
|
159
|
+
match s_id:
|
|
160
|
+
case 0x7F:
|
|
161
|
+
self.__handle_negative(raw)
|
|
162
|
+
case _:
|
|
163
|
+
self.__handle_positive(raw)
|
|
164
|
+
return self
|
|
165
|
+
|
|
166
|
+
def __handle_negative(self, raw: bytes):
|
|
167
|
+
if len(raw) < 3:
|
|
168
|
+
raise ValueError("负响应长度不足,至少需要 3 字节")
|
|
169
|
+
request_id = raw[1]
|
|
170
|
+
if request_id != self.request.service_id:
|
|
171
|
+
raise ValueError(
|
|
172
|
+
f"负响应的请求 SID 不匹配:期望 0x{self.request.service_id:02X},"
|
|
173
|
+
f"收到 0x{request_id:02X}"
|
|
174
|
+
)
|
|
175
|
+
self.request_id = request_id
|
|
176
|
+
self.is_negative = True
|
|
177
|
+
self.service_id = raw[0]
|
|
178
|
+
self.nrc = raw[2]
|
|
179
|
+
self.nrc_desc = NRC_MAP.get(self.nrc, "Unknown NRC")
|
|
180
|
+
|
|
181
|
+
def __handle_positive(self, raw: bytes):
|
|
182
|
+
expected_sid = self.request.service_id | 0x40
|
|
183
|
+
s_id = raw[0]
|
|
184
|
+
if s_id != expected_sid:
|
|
185
|
+
raise ValueError(f"无效响应:期望 SID 0x{expected_sid:02X},收到 0x{s_id:02X}")
|
|
186
|
+
|
|
187
|
+
self.ok = True
|
|
188
|
+
self.is_negative = False
|
|
189
|
+
self.service_id = s_id
|
|
190
|
+
|
|
191
|
+
# 判断服务是否需要子功能字节
|
|
192
|
+
table = SUB_FN_MAP.get(self.request.service_id)
|
|
193
|
+
need_sub_fn = (table is not None and table.get('enable', False))
|
|
194
|
+
|
|
195
|
+
if not need_sub_fn:
|
|
196
|
+
self.sub_fn = None
|
|
197
|
+
self.params = raw[1:] if len(raw) > 1 else b''
|
|
198
|
+
return
|
|
199
|
+
|
|
200
|
+
if len(raw) < 2:
|
|
201
|
+
raise ValueError(f"服务 0x{self.request.service_id:02X} 需要子功能,但响应缺少子功能字节")
|
|
202
|
+
|
|
203
|
+
if self.request.sub_fn is None:
|
|
204
|
+
raise ValueError("服务需要子功能但请求未提供子功能")
|
|
205
|
+
|
|
206
|
+
response_sub_fn = raw[1]
|
|
207
|
+
if response_sub_fn != self.request.sub_fn:
|
|
208
|
+
raise ValueError(
|
|
209
|
+
f"响应子功能不匹配:期望 0x{self.request.sub_fn:02X},"
|
|
210
|
+
f"收到 0x{response_sub_fn:02X}"
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
self.sub_fn = response_sub_fn
|
|
214
|
+
self.params = raw[2:] if len(raw) > 2 else b''
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
218
|
+
# UdsError
|
|
219
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
220
|
+
|
|
221
|
+
class UdsError(Exception):
|
|
222
|
+
"""UDS 负响应异常,通过 resp 属性获取完整 Response。"""
|
|
223
|
+
|
|
224
|
+
def __init__(self, resp: Response):
|
|
225
|
+
self.resp = resp
|
|
226
|
+
super().__init__(f'{resp.nrc_desc} 0x{resp.nrc:02X}')
|
autouds/_tables.py
ADDED
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@文件: _tables.py
|
|
3
|
+
@作者: 雷小鸥
|
|
4
|
+
@日期: 2026/6/11 12:03
|
|
5
|
+
@许可: MIT License
|
|
6
|
+
@描述: UDS 服务标识符映射表,按 ISO 14229-1 分类。
|
|
7
|
+
@版本: Version 0.1
|
|
8
|
+
"""
|
|
9
|
+
SERVICE_MAP = {
|
|
10
|
+
# ═══════════════════════════════════════
|
|
11
|
+
# 诊断和通信管理类
|
|
12
|
+
# ═══════════════════════════════════════
|
|
13
|
+
'会话' : 0x10,
|
|
14
|
+
'Diagnostic Session Control' : 0x10,
|
|
15
|
+
'session' : 0x10,
|
|
16
|
+
|
|
17
|
+
'ECU 复位' : 0x11,
|
|
18
|
+
'ECU Reset' : 0x11,
|
|
19
|
+
'reset' : 0x11,
|
|
20
|
+
|
|
21
|
+
'安全访问' : 0x27,
|
|
22
|
+
'Security Access' : 0x27,
|
|
23
|
+
'security_access' : 0x27,
|
|
24
|
+
'unlock' : 0x27,
|
|
25
|
+
|
|
26
|
+
'通讯控制' : 0x28,
|
|
27
|
+
'Communication Control' : 0x28,
|
|
28
|
+
'communication_control' : 0x28,
|
|
29
|
+
'comm' : 0x28,
|
|
30
|
+
|
|
31
|
+
'待机' : 0x3E,
|
|
32
|
+
'Tester Present' : 0x3E,
|
|
33
|
+
'tester_present' : 0x3E,
|
|
34
|
+
'present' : 0x3E,
|
|
35
|
+
|
|
36
|
+
'访问时间参数' : 0x83,
|
|
37
|
+
'Access Timing Parameter' : 0x83,
|
|
38
|
+
'timing' : 0x83,
|
|
39
|
+
|
|
40
|
+
'安全数据传输' : 0x84,
|
|
41
|
+
'Secured Data Transmission' : 0x84,
|
|
42
|
+
'transmission' : 0x84,
|
|
43
|
+
|
|
44
|
+
'控制DTC的设置' : 0x85,
|
|
45
|
+
'Control DTC Setting' : 0x85,
|
|
46
|
+
'dtc_setting' : 0x85,
|
|
47
|
+
|
|
48
|
+
'事件响应' : 0x86,
|
|
49
|
+
'Response On Event' : 0x86,
|
|
50
|
+
'event' : 0x86,
|
|
51
|
+
|
|
52
|
+
'链路控制' : 0x87,
|
|
53
|
+
'Link Control' : 0x87,
|
|
54
|
+
'link' : 0x87,
|
|
55
|
+
|
|
56
|
+
# ═══════════════════════════════════════
|
|
57
|
+
# 数据传输类
|
|
58
|
+
# ═══════════════════════════════════════
|
|
59
|
+
'标识符读数据' : 0x22,
|
|
60
|
+
'Read Data By Identifier' : 0x22,
|
|
61
|
+
'read_did' : 0x22,
|
|
62
|
+
|
|
63
|
+
'地址读内存' : 0x23,
|
|
64
|
+
'Read Memory By Address' : 0x23,
|
|
65
|
+
'read_memory' : 0x23,
|
|
66
|
+
'read_mem' : 0x23,
|
|
67
|
+
|
|
68
|
+
'标识符读比例数据' : 0x24,
|
|
69
|
+
'Read Scaling Data By Identifier' : 0x24,
|
|
70
|
+
'scaling' : 0x24,
|
|
71
|
+
|
|
72
|
+
'周期标识符读数据' : 0x2A,
|
|
73
|
+
'Read Data By Periodic Identifier' : 0x2A,
|
|
74
|
+
'periodic' : 0x2A,
|
|
75
|
+
|
|
76
|
+
'动态定义标识符' : 0x2C,
|
|
77
|
+
'Dynamically Define Data Identifier': 0x2C,
|
|
78
|
+
'define' : 0x2C,
|
|
79
|
+
|
|
80
|
+
'标识符写数据' : 0x2E,
|
|
81
|
+
'Write Data By Identifier' : 0x2E,
|
|
82
|
+
'write_did' : 0x2E,
|
|
83
|
+
|
|
84
|
+
'地址写内存' : 0x3D,
|
|
85
|
+
'Write Memory By Address' : 0x3D,
|
|
86
|
+
'write_mem' : 0x3D,
|
|
87
|
+
|
|
88
|
+
# ═══════════════════════════════════════
|
|
89
|
+
# 存储数据传输类
|
|
90
|
+
# ═══════════════════════════════════════
|
|
91
|
+
'清除诊断信息' : 0x14,
|
|
92
|
+
'Clear Diagnostic Information' : 0x14,
|
|
93
|
+
'clear_dtc' : 0x14,
|
|
94
|
+
|
|
95
|
+
'读取故障信息码' : 0x19,
|
|
96
|
+
'Read DTC Information' : 0x19,
|
|
97
|
+
'read_dtc' : 0x19,
|
|
98
|
+
|
|
99
|
+
# ═══════════════════════════════════════
|
|
100
|
+
# 输入输出控制类
|
|
101
|
+
# ═══════════════════════════════════════
|
|
102
|
+
'通过标识符控制输入输出' : 0x2F,
|
|
103
|
+
'Input Output Control By Identifier': 0x2F,
|
|
104
|
+
'io' : 0x2F,
|
|
105
|
+
|
|
106
|
+
# ═══════════════════════════════════════
|
|
107
|
+
# 例程功能类
|
|
108
|
+
# ═══════════════════════════════════════
|
|
109
|
+
'例行程序控制' : 0x31,
|
|
110
|
+
'Routine Control' : 0x31,
|
|
111
|
+
'routine' : 0x31,
|
|
112
|
+
|
|
113
|
+
# ═══════════════════════════════════════
|
|
114
|
+
# 上传下载类
|
|
115
|
+
# ═══════════════════════════════════════
|
|
116
|
+
'请求下载' : 0x34,
|
|
117
|
+
'Request Download' : 0x34,
|
|
118
|
+
'download' : 0x34,
|
|
119
|
+
|
|
120
|
+
'请求上传' : 0x35,
|
|
121
|
+
'Request Upload' : 0x35,
|
|
122
|
+
'upload' : 0x35,
|
|
123
|
+
|
|
124
|
+
'数据传输' : 0x36,
|
|
125
|
+
'Transfer Data' : 0x36,
|
|
126
|
+
'transfer' : 0x36,
|
|
127
|
+
|
|
128
|
+
'请求退出传输' : 0x37,
|
|
129
|
+
'Request Transfer Exit' : 0x37,
|
|
130
|
+
'exit_tf' : 0x37,
|
|
131
|
+
|
|
132
|
+
'请求文件传输' : 0x38,
|
|
133
|
+
'Request File Transfer' : 0x38,
|
|
134
|
+
'file' : 0x38,
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
# ── 子功能映射 ──
|
|
138
|
+
# 键为 service_id (int),值为 { enable: bool, 中文/英文/短key: sub_fn 值 }
|
|
139
|
+
# enable=True 表示该服务支持子功能,子功能字段有效
|
|
140
|
+
|
|
141
|
+
SUB_FN_MAP = {
|
|
142
|
+
# ── 诊断会话控制 (0x10) ──
|
|
143
|
+
0x10: {
|
|
144
|
+
'enable' : True,
|
|
145
|
+
|
|
146
|
+
'默认' : 0x01,
|
|
147
|
+
'default session' : 0x01,
|
|
148
|
+
'default' : 0x01,
|
|
149
|
+
|
|
150
|
+
'编程' : 0x02,
|
|
151
|
+
'programming session': 0x02,
|
|
152
|
+
'program' : 0x02,
|
|
153
|
+
|
|
154
|
+
'拓展' : 0x03,
|
|
155
|
+
'extended session' : 0x03,
|
|
156
|
+
'extend' : 0x03,
|
|
157
|
+
|
|
158
|
+
'安全' : 0x04,
|
|
159
|
+
'security session' : 0x04,
|
|
160
|
+
'safety' : 0x04,
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
# ── ECU 复位 (0x11) ──
|
|
164
|
+
0x11: {
|
|
165
|
+
'enable' : True,
|
|
166
|
+
|
|
167
|
+
'硬' : 0x01,
|
|
168
|
+
'hard reset' : 0x01,
|
|
169
|
+
'hard' : 0x01,
|
|
170
|
+
|
|
171
|
+
'钥匙' : 0x02,
|
|
172
|
+
'key off on' : 0x02,
|
|
173
|
+
'key' : 0x02,
|
|
174
|
+
|
|
175
|
+
'软' : 0x03,
|
|
176
|
+
'soft reset' : 0x03,
|
|
177
|
+
'soft' : 0x03,
|
|
178
|
+
|
|
179
|
+
'启用快速断电' : 0x04,
|
|
180
|
+
'enable rapid power down' : 0x04,
|
|
181
|
+
'on' : 0x04,
|
|
182
|
+
|
|
183
|
+
'禁用快速断电' : 0x05,
|
|
184
|
+
'disable rapid power down': 0x05,
|
|
185
|
+
'off' : 0x05,
|
|
186
|
+
},
|
|
187
|
+
|
|
188
|
+
# ── 安全访问 (0x27) ──
|
|
189
|
+
0x27: {
|
|
190
|
+
'enable' : True,
|
|
191
|
+
|
|
192
|
+
'L1' : 0x01,
|
|
193
|
+
'L1 Ex' : 0x02,
|
|
194
|
+
|
|
195
|
+
'level 1' : 0x01,
|
|
196
|
+
'level 1 extend' : 0x02,
|
|
197
|
+
|
|
198
|
+
'L5' : 0x05,
|
|
199
|
+
'L5 Ex' : 0x06,
|
|
200
|
+
|
|
201
|
+
'level 5' : 0x05,
|
|
202
|
+
'level 5 extend' : 0x06,
|
|
203
|
+
|
|
204
|
+
'L19' : 0x19,
|
|
205
|
+
'L19 Ex' : 0x1A,
|
|
206
|
+
|
|
207
|
+
'level 19' : 0x19,
|
|
208
|
+
'level 19 extend': 0x1A,
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
# ── 通讯控制 (0x28) ──
|
|
212
|
+
0x28: {
|
|
213
|
+
'enable' : True,
|
|
214
|
+
|
|
215
|
+
'启用接收和发送' : 0x00,
|
|
216
|
+
'enable rx and tx' : 0x00,
|
|
217
|
+
'en_rx_tx' : 0x00,
|
|
218
|
+
|
|
219
|
+
'启用接收禁用发送' : 0x01,
|
|
220
|
+
'enable rx disable tx' : 0x01,
|
|
221
|
+
'rx' : 0x01,
|
|
222
|
+
|
|
223
|
+
'禁用接收启用发送' : 0x02,
|
|
224
|
+
'disable rx enable tx' : 0x02,
|
|
225
|
+
'tx' : 0x02,
|
|
226
|
+
|
|
227
|
+
'禁用接收和发送' : 0x03,
|
|
228
|
+
'disable rx and tx' : 0x03,
|
|
229
|
+
'dis_rx_tx' : 0x03,
|
|
230
|
+
|
|
231
|
+
'启用接收禁用发送增强' : 0x04,
|
|
232
|
+
'enable rx disable tx enhanced': 0x04,
|
|
233
|
+
'rx_enhanced' : 0x04,
|
|
234
|
+
|
|
235
|
+
'启用接收和发送增强' : 0x05,
|
|
236
|
+
'enable rx and tx enhanced' : 0x05,
|
|
237
|
+
'rx_tx_enhanced' : 0x05,
|
|
238
|
+
},
|
|
239
|
+
|
|
240
|
+
# ── 待机握手 (0x3E) ──
|
|
241
|
+
0x3E: {
|
|
242
|
+
'enable' : True,
|
|
243
|
+
|
|
244
|
+
'响应' : 0x00,
|
|
245
|
+
'response': 0x00,
|
|
246
|
+
'resp' : 0x00,
|
|
247
|
+
},
|
|
248
|
+
|
|
249
|
+
# ── 控制 DTC 设置 (0x85) ──
|
|
250
|
+
0x85: {
|
|
251
|
+
'enable' : True,
|
|
252
|
+
|
|
253
|
+
'允许 DTC 更新' : 0x01,
|
|
254
|
+
'enable dtc update' : 0x01,
|
|
255
|
+
'on' : 0x01,
|
|
256
|
+
|
|
257
|
+
'禁止 DTC 更新' : 0x02,
|
|
258
|
+
'disable dtc update': 0x02,
|
|
259
|
+
'off' : 0x02,
|
|
260
|
+
},
|
|
261
|
+
|
|
262
|
+
0x22: {'enable': False},
|
|
263
|
+
0x23: {'enable': False},
|
|
264
|
+
|
|
265
|
+
# ── 读取 DTC 信息 (0x19) ──
|
|
266
|
+
0x14: {'enable': False},
|
|
267
|
+
0x19: {
|
|
268
|
+
'enable' : True,
|
|
269
|
+
'通过状态掩码报告 DTC 数量' : 0x01,
|
|
270
|
+
'report number of dtc by status mask' : 0x01,
|
|
271
|
+
'count_by_mask' : 0x01,
|
|
272
|
+
|
|
273
|
+
'通过状态掩码报告 DTC' : 0x02,
|
|
274
|
+
'report dtc by status mask' : 0x02,
|
|
275
|
+
'dtc_by_mask' : 0x02,
|
|
276
|
+
|
|
277
|
+
'获取 DTC 快照记录 ID' : 0x03,
|
|
278
|
+
'report dtc snapshot identification' : 0x03,
|
|
279
|
+
'snapshot_id' : 0x03,
|
|
280
|
+
|
|
281
|
+
'请求指定 DTC 快照信息' : 0x04,
|
|
282
|
+
'report dtc snapshot record by dtc number' : 0x04,
|
|
283
|
+
'snapshot_by_dtc' : 0x04,
|
|
284
|
+
|
|
285
|
+
'通过记录 ID 报告 DTC 存储数据' : 0x05,
|
|
286
|
+
'report dtc stored data by record number' : 0x05,
|
|
287
|
+
'stored_data' : 0x05,
|
|
288
|
+
|
|
289
|
+
'通过 DTC 报告扩展数据' : 0x06,
|
|
290
|
+
'report dtc record by dtc number' : 0x06,
|
|
291
|
+
'ext_data' : 0x06,
|
|
292
|
+
|
|
293
|
+
'通过严重性掩码报告 DTC 数量' : 0x07,
|
|
294
|
+
'report number of dtc by severity mask record' : 0x07,
|
|
295
|
+
'count_by_severity' : 0x07,
|
|
296
|
+
|
|
297
|
+
'通过严重性掩码报告 DTC' : 0x08,
|
|
298
|
+
'report dtc by severity mask record' : 0x08,
|
|
299
|
+
'dtc_by_severity' : 0x08,
|
|
300
|
+
|
|
301
|
+
'报告 DTC 严重性信息' : 0x09,
|
|
302
|
+
'report severity information of dtc' : 0x09,
|
|
303
|
+
'severity_info' : 0x09,
|
|
304
|
+
|
|
305
|
+
'读取支持的所有 DTC 列表' : 0x0A,
|
|
306
|
+
'report supported dtc' : 0x0A,
|
|
307
|
+
'supported_dtc' : 0x0A,
|
|
308
|
+
|
|
309
|
+
'报告第一次测试失败的 DTC' : 0x0B,
|
|
310
|
+
'report first test failed dtc' : 0x0B,
|
|
311
|
+
'first_test_failed' : 0x0B,
|
|
312
|
+
|
|
313
|
+
'报告第一次确认的 DTC' : 0x0C,
|
|
314
|
+
'report first confirmed dtc' : 0x0C,
|
|
315
|
+
'first_confirmed' : 0x0C,
|
|
316
|
+
|
|
317
|
+
'报告最近一次测试失败的 DTC' : 0x0D,
|
|
318
|
+
'report most recent test failed dtc' : 0x0D,
|
|
319
|
+
'recent_test_failed' : 0x0D,
|
|
320
|
+
|
|
321
|
+
'报告最近一次确认的 DTC' : 0x0E,
|
|
322
|
+
'report most recent confirmed dtc' : 0x0E,
|
|
323
|
+
'recent_confirmed' : 0x0E,
|
|
324
|
+
|
|
325
|
+
'报告 DTC 故障检测计数器' : 0x14,
|
|
326
|
+
'report dtc fault detection counter' : 0x14,
|
|
327
|
+
'fault_counter' : 0x14,
|
|
328
|
+
|
|
329
|
+
'用户定义内存 DTC 扩展数据' : 0x19,
|
|
330
|
+
'report user def memory dtc ext data record by dtc number': 0x19,
|
|
331
|
+
'user_def_memory' : 0x19,
|
|
332
|
+
},
|
|
333
|
+
|
|
334
|
+
0x2F: {'enable': False},
|
|
335
|
+
0x34: {'enable': False},
|
|
336
|
+
0x35: {'enable': False},
|
|
337
|
+
0x36: {'enable': False},
|
|
338
|
+
0x37: {'enable': False},
|
|
339
|
+
|
|
340
|
+
# ── 例行程序控制 (0x31) ──
|
|
341
|
+
0x31: {
|
|
342
|
+
'enable' : True,
|
|
343
|
+
'启动程序' : 0x01,
|
|
344
|
+
'start routine' : 0x01,
|
|
345
|
+
'start' : 0x01,
|
|
346
|
+
|
|
347
|
+
'停止程序' : 0x02,
|
|
348
|
+
'stop routine' : 0x02,
|
|
349
|
+
'stop' : 0x02,
|
|
350
|
+
|
|
351
|
+
'请求运行结果' : 0x03,
|
|
352
|
+
'request routine results': 0x03,
|
|
353
|
+
'result' : 0x03,
|
|
354
|
+
},
|
|
355
|
+
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
NRC_MAP = {
|
|
359
|
+
# ── 0x01 ~ 0x0F 暂时保留 ──
|
|
360
|
+
|
|
361
|
+
# ── 0x10 ~ 0x14 通用错误 ──
|
|
362
|
+
0x10: '未知错误,服务暂时被拒绝',
|
|
363
|
+
0x11: '不支持该服务请求',
|
|
364
|
+
0x12: '不支持子功能',
|
|
365
|
+
0x13: '消息长度或格式错误',
|
|
366
|
+
0x14: '请求长度超出',
|
|
367
|
+
|
|
368
|
+
# ── 0x15 ~ 0x20 暂时保留 ──
|
|
369
|
+
|
|
370
|
+
# ── 0x21 ~ 0x26 条件/顺序错误 ──
|
|
371
|
+
0x21: '服务端正忙',
|
|
372
|
+
0x22: '条件不满足',
|
|
373
|
+
|
|
374
|
+
# 0x23 暂时保留
|
|
375
|
+
|
|
376
|
+
0x24: '请求顺序错误',
|
|
377
|
+
0x25: '指令已被接收,但是未被执行',
|
|
378
|
+
0x26: '失败的操作导致当前操作无法执行',
|
|
379
|
+
|
|
380
|
+
# ── 0x27 ~ 0x30 暂时保留 ──
|
|
381
|
+
|
|
382
|
+
# ── 0x31 ~ 0x36 参数/安全错误 ──
|
|
383
|
+
0x31: '参数错误',
|
|
384
|
+
|
|
385
|
+
# 0x32 暂时保留
|
|
386
|
+
|
|
387
|
+
0x33: '安全校验未通过',
|
|
388
|
+
|
|
389
|
+
# 0x34 暂时保留
|
|
390
|
+
|
|
391
|
+
0x35: '密钥不匹配',
|
|
392
|
+
0x36: '已达到解锁最大错误次数',
|
|
393
|
+
0x37: '请求延时未过期,请稍后再试',
|
|
394
|
+
|
|
395
|
+
# ── 0x38 ~ 0x4F 为扩展数据链路安全性文档暂时保留 ──
|
|
396
|
+
# ── 0x50 ~ 0x6F 暂时保留 ──
|
|
397
|
+
|
|
398
|
+
# ── 0x70 ~ 0x73 上传下载错误 ──
|
|
399
|
+
0x70: '不允许上传下载',
|
|
400
|
+
0x71: '数据传输中断',
|
|
401
|
+
0x72: '擦除或烧写内存错误',
|
|
402
|
+
0x73: '块序列计数错误',
|
|
403
|
+
|
|
404
|
+
# ── 0x74 ~ 0x77 暂时保留 ──
|
|
405
|
+
|
|
406
|
+
0x78: '收到请求,延迟响应',
|
|
407
|
+
|
|
408
|
+
# ── 0x79 ~ 0x7D 暂时保留 ──
|
|
409
|
+
|
|
410
|
+
0x7E: '当前会话下子功能不支持',
|
|
411
|
+
0x7F: '当前会话下子服务不支持',
|
|
412
|
+
|
|
413
|
+
# 0x80 暂时保留
|
|
414
|
+
|
|
415
|
+
# ── 0x81 ~ 0x93 条件不满足类 ──
|
|
416
|
+
0x81: '每分钟转速(RPM)太高',
|
|
417
|
+
0x82: '每分钟转速太低',
|
|
418
|
+
0x83: '当前引擎正在运行',
|
|
419
|
+
0x84: '当前引擎未运行',
|
|
420
|
+
0x85: '截止当前时间引擎运行时间太短',
|
|
421
|
+
0x86: '温度过高',
|
|
422
|
+
0x87: '温度过低',
|
|
423
|
+
0x88: '车速过高',
|
|
424
|
+
0x89: '车速过低',
|
|
425
|
+
0x8A: '油门/踏板过高(超过了当前要求的最大阈值)',
|
|
426
|
+
0x8B: '油门/踏板过低',
|
|
427
|
+
0x8C: '变速器档位不在空挡',
|
|
428
|
+
0x8D: '变速器档位不在排档',
|
|
429
|
+
|
|
430
|
+
# 0x8E 暂时保留
|
|
431
|
+
|
|
432
|
+
0x8F: '制动开关没有关闭',
|
|
433
|
+
0x90: '换挡杆不在驻车档',
|
|
434
|
+
0x91: '变矩器离合器锁定',
|
|
435
|
+
0x92: '电压过高',
|
|
436
|
+
0x93: '电压过低',
|
|
437
|
+
|
|
438
|
+
# ── 0x94 ~ 0xEF 为"条件不满足"类错误码扩展暂时保留 ──
|
|
439
|
+
# ── 0xF0 ~ 0xFE 为汽车制造商保留 ──
|
|
440
|
+
# ── 0xFF 暂时保留 ──
|
|
441
|
+
}
|
autouds/app.py
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@文件: app.py
|
|
3
|
+
@作者: 雷小鸥
|
|
4
|
+
@日期: 2026/6/10
|
|
5
|
+
@许可: MIT License
|
|
6
|
+
@描述: UDS App 基类,封装所有 UDS 服务方法
|
|
7
|
+
@版本: Version 0.1
|
|
8
|
+
"""
|
|
9
|
+
from typing import Generator
|
|
10
|
+
from autodoip import Endpoint, Config as DoIpConfig
|
|
11
|
+
from ._handler import handler, set_send_impl
|
|
12
|
+
from ._models import Response
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class App:
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
ecus: dict[str, tuple[int, str, int]],
|
|
19
|
+
ip: str,
|
|
20
|
+
port: int = 13400,
|
|
21
|
+
tester: int = 0x0E80,
|
|
22
|
+
doip_config: DoIpConfig | None = None,
|
|
23
|
+
):
|
|
24
|
+
self._ecus = ecus
|
|
25
|
+
self._first_name = next(iter(ecus))
|
|
26
|
+
|
|
27
|
+
endpoint_ecus = {
|
|
28
|
+
addr: (ecu_ip, port)
|
|
29
|
+
for addr, ecu_ip, port in ecus.values()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
self._endpoint = Endpoint(ip=ip, ecus=endpoint_ecus, port=port, tester=tester, config=doip_config or DoIpConfig())
|
|
33
|
+
self._endpoint.start()
|
|
34
|
+
self._endpoint.select(ecus[self._first_name][0])
|
|
35
|
+
set_send_impl(self._endpoint.conversation)
|
|
36
|
+
|
|
37
|
+
def on(self, name: str):
|
|
38
|
+
"""切换到指定 ECU。"""
|
|
39
|
+
addr, _, _ = self._ecus[name]
|
|
40
|
+
self._endpoint.select(addr)
|
|
41
|
+
|
|
42
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
43
|
+
# 诊断会话控制 (0x10)
|
|
44
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
45
|
+
|
|
46
|
+
@handler(service_name='session', sub_fn_name='default')
|
|
47
|
+
def default(self) -> Generator[None, Response, tuple[int, int]]:
|
|
48
|
+
resp: Response = yield
|
|
49
|
+
if not resp.params:
|
|
50
|
+
return 0, 0
|
|
51
|
+
params: bytes = resp.params
|
|
52
|
+
p2 = int.from_bytes(params[0:2], byteorder='big')
|
|
53
|
+
p2_star = int.from_bytes(params[2:4], byteorder='big') * 10
|
|
54
|
+
return p2, p2_star
|
|
55
|
+
|
|
56
|
+
@handler(service_name='session', sub_fn_name='program')
|
|
57
|
+
def program(self) -> Generator[None, Response, tuple[int, int]]:
|
|
58
|
+
resp: Response = yield
|
|
59
|
+
if not resp.params:
|
|
60
|
+
return 0, 0
|
|
61
|
+
params: bytes = resp.params
|
|
62
|
+
p2 = int.from_bytes(params[0:2], byteorder='big')
|
|
63
|
+
p2_star = int.from_bytes(params[2:4], byteorder='big') * 10
|
|
64
|
+
return p2, p2_star
|
|
65
|
+
|
|
66
|
+
@handler(service_name='session', sub_fn_name='extend')
|
|
67
|
+
def extend(self) -> Generator[None, Response, tuple[int, int]]:
|
|
68
|
+
resp: Response = yield
|
|
69
|
+
if not resp.params:
|
|
70
|
+
return 0, 0
|
|
71
|
+
params: bytes = resp.params
|
|
72
|
+
p2 = int.from_bytes(params[0:2], byteorder='big')
|
|
73
|
+
p2_star = int.from_bytes(params[2:4], byteorder='big') * 10
|
|
74
|
+
return p2, p2_star
|
|
75
|
+
|
|
76
|
+
@handler(service_name='session', sub_fn_name='safety')
|
|
77
|
+
def safety(self) -> Generator[None, Response, tuple[int, int]]:
|
|
78
|
+
resp: Response = yield
|
|
79
|
+
if not resp.params:
|
|
80
|
+
return 0, 0
|
|
81
|
+
params: bytes = resp.params
|
|
82
|
+
p2 = int.from_bytes(params[0:2], byteorder='big')
|
|
83
|
+
p2_star = int.from_bytes(params[2:4], byteorder='big') * 10
|
|
84
|
+
return p2, p2_star
|
|
85
|
+
|
|
86
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
87
|
+
# ECU 复位 (0x11)
|
|
88
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
89
|
+
|
|
90
|
+
@handler(service_name='reset', sub_fn_name='hard')
|
|
91
|
+
def hard(self) -> Generator[None, Response, Response]:
|
|
92
|
+
resp: Response = yield
|
|
93
|
+
return resp
|
|
94
|
+
|
|
95
|
+
@handler(service_name='reset', sub_fn_name='key')
|
|
96
|
+
def key(self) -> Generator[None, Response, Response]:
|
|
97
|
+
resp: Response = yield
|
|
98
|
+
return resp
|
|
99
|
+
|
|
100
|
+
@handler(service_name='reset', sub_fn_name='soft')
|
|
101
|
+
def soft(self) -> Generator[None, Response, Response]:
|
|
102
|
+
resp: Response = yield
|
|
103
|
+
return resp
|
|
104
|
+
|
|
105
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
106
|
+
# 待机握手 (0x3E)
|
|
107
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
108
|
+
|
|
109
|
+
@handler(service_name='present', sub_fn_name='resp', suppress_positive_response=True)
|
|
110
|
+
def present(self) -> Generator[None, Response, None]:
|
|
111
|
+
yield
|
|
112
|
+
|
|
113
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
114
|
+
# 安全访问 (0x27)
|
|
115
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
116
|
+
|
|
117
|
+
@handler(service_name='unlock', sub_fn_name='L1')
|
|
118
|
+
def l1(self) -> Generator[None, Response, bytes]:
|
|
119
|
+
resp: Response = yield
|
|
120
|
+
if not resp.params:
|
|
121
|
+
return b''
|
|
122
|
+
return resp.params
|
|
123
|
+
|
|
124
|
+
@handler(service_name='unlock', sub_fn_name='L1 Ex')
|
|
125
|
+
def l1_ex(self, payload: bytes) -> Generator[bytes, Response, None]:
|
|
126
|
+
yield payload
|
|
127
|
+
|
|
128
|
+
@handler(service_name='unlock', sub_fn_name='L5')
|
|
129
|
+
def l5(self) -> Generator[None, Response, bytes]:
|
|
130
|
+
resp: Response = yield
|
|
131
|
+
if not resp.params:
|
|
132
|
+
return b''
|
|
133
|
+
return resp.params
|
|
134
|
+
|
|
135
|
+
@handler(service_name='unlock', sub_fn_name='L5 Ex')
|
|
136
|
+
def l5_ex(self, payload: bytes) -> Generator[bytes, Response, None]:
|
|
137
|
+
yield payload
|
|
138
|
+
|
|
139
|
+
@handler(service_name='unlock', sub_fn_name='L19')
|
|
140
|
+
def l19(self) -> Generator[None, Response, bytes]:
|
|
141
|
+
resp: Response = yield
|
|
142
|
+
if not resp.params:
|
|
143
|
+
return b''
|
|
144
|
+
return resp.params
|
|
145
|
+
|
|
146
|
+
@handler(service_name='unlock', sub_fn_name='L19 Ex')
|
|
147
|
+
def l19_ex(self, payload: bytes) -> Generator[bytes, Response, None]:
|
|
148
|
+
yield payload
|
|
149
|
+
|
|
150
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
151
|
+
# 通讯控制 (0x28)
|
|
152
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
153
|
+
|
|
154
|
+
@handler(service_name='comm', sub_fn_name='en_rx_tx')
|
|
155
|
+
def comm_enable_rx_tx(self) -> Generator[None, Response, Response]:
|
|
156
|
+
resp: Response = yield
|
|
157
|
+
return resp
|
|
158
|
+
|
|
159
|
+
@handler(service_name='comm', sub_fn_name='dis_rx_tx')
|
|
160
|
+
def comm_disable_rx_tx(self) -> Generator[None, Response, Response]:
|
|
161
|
+
resp: Response = yield
|
|
162
|
+
return resp
|
|
163
|
+
|
|
164
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
165
|
+
# 控制 DTC 设置 (0x85)
|
|
166
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
167
|
+
|
|
168
|
+
@handler(service_name='dtc_setting', sub_fn_name='on')
|
|
169
|
+
def dtc_on(self) -> Generator[None, Response, Response]:
|
|
170
|
+
resp: Response = yield
|
|
171
|
+
return resp
|
|
172
|
+
|
|
173
|
+
@handler(service_name='dtc_setting', sub_fn_name='off')
|
|
174
|
+
def dtc_off(self) -> Generator[None, Response, Response]:
|
|
175
|
+
resp: Response = yield
|
|
176
|
+
return resp
|
|
177
|
+
|
|
178
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
179
|
+
# 清除诊断信息 (0x14)
|
|
180
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
181
|
+
|
|
182
|
+
@handler(service_name='clear_dtc')
|
|
183
|
+
def clear_dtc(self) -> Generator[None, Response, Response]:
|
|
184
|
+
resp: Response = yield
|
|
185
|
+
return resp
|
|
186
|
+
|
|
187
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
188
|
+
# 读取故障信息码 (0x19)
|
|
189
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
190
|
+
|
|
191
|
+
@handler(service_name='read_dtc', sub_fn_name='count_by_mask')
|
|
192
|
+
def read_dtc_count(self) -> Generator[None, Response, Response]:
|
|
193
|
+
resp: Response = yield
|
|
194
|
+
return resp
|
|
195
|
+
|
|
196
|
+
@handler(service_name='read_dtc', sub_fn_name='dtc_by_mask')
|
|
197
|
+
def read_dtc_by_mask(self) -> Generator[None, Response, Response]:
|
|
198
|
+
resp: Response = yield
|
|
199
|
+
return resp
|
|
200
|
+
|
|
201
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
202
|
+
# 标识符读数据 (0x22)
|
|
203
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
204
|
+
|
|
205
|
+
@handler(service_name='read_did')
|
|
206
|
+
def read_did(self, did: int) -> Generator[bytes, Response, bytes]:
|
|
207
|
+
resp: Response = yield did.to_bytes(2, byteorder='big')
|
|
208
|
+
if not resp.params:
|
|
209
|
+
return b''
|
|
210
|
+
return resp.params
|
|
211
|
+
|
|
212
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
213
|
+
# 标识符写数据 (0x2E)
|
|
214
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
215
|
+
|
|
216
|
+
@handler(service_name='write_did')
|
|
217
|
+
def write_did(self, did: int, payload: bytes) -> Generator[bytes, Response, None]:
|
|
218
|
+
yield did.to_bytes(2, byteorder='big') + payload
|
|
219
|
+
|
|
220
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
221
|
+
# 通过标识符控制输入输出 (0x2F)
|
|
222
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
223
|
+
|
|
224
|
+
@handler(service_name='io')
|
|
225
|
+
def io_control(self) -> Generator[None, Response, Response]:
|
|
226
|
+
resp: Response = yield
|
|
227
|
+
return resp
|
|
228
|
+
|
|
229
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
230
|
+
# 例行程序控制 (0x31)
|
|
231
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
232
|
+
|
|
233
|
+
@handler(service_name='routine', sub_fn_name='start')
|
|
234
|
+
def routine_start(self) -> Generator[None, Response, Response]:
|
|
235
|
+
resp: Response = yield
|
|
236
|
+
return resp
|
|
237
|
+
|
|
238
|
+
@handler(service_name='routine', sub_fn_name='stop')
|
|
239
|
+
def routine_stop(self) -> Generator[None, Response, Response]:
|
|
240
|
+
resp: Response = yield
|
|
241
|
+
return resp
|
|
242
|
+
|
|
243
|
+
@handler(service_name='routine', sub_fn_name='result')
|
|
244
|
+
def routine_result(self) -> Generator[None, Response, Response]:
|
|
245
|
+
resp: Response = yield
|
|
246
|
+
return resp
|
|
247
|
+
|
|
248
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
249
|
+
# 上传下载类 (0x34 / 0x35 / 0x36 / 0x37)
|
|
250
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
251
|
+
|
|
252
|
+
@handler(service_name='download')
|
|
253
|
+
def request_download(self) -> Generator[None, Response, Response]:
|
|
254
|
+
resp: Response = yield
|
|
255
|
+
return resp
|
|
256
|
+
|
|
257
|
+
@handler(service_name='upload')
|
|
258
|
+
def request_upload(self) -> Generator[None, Response, Response]:
|
|
259
|
+
resp: Response = yield
|
|
260
|
+
return resp
|
|
261
|
+
|
|
262
|
+
@handler(service_name='transfer')
|
|
263
|
+
def transfer_data(self) -> Generator[None, Response, Response]:
|
|
264
|
+
resp: Response = yield
|
|
265
|
+
return resp
|
|
266
|
+
|
|
267
|
+
@handler(service_name='exit_tf')
|
|
268
|
+
def exit_transfer(self) -> Generator[None, Response, Response]:
|
|
269
|
+
resp: Response = yield
|
|
270
|
+
return resp
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: autouds
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: UDS (Unified Diagnostic Services) diagnostic tool over DoIP — ISO 14229-1
|
|
5
|
+
Project-URL: Homepage, https://github.com/leno166/autouds
|
|
6
|
+
Project-URL: Source, https://github.com/leno166/autouds
|
|
7
|
+
Author: leno augenstern
|
|
8
|
+
License: MIT
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Topic :: System :: Networking
|
|
13
|
+
Requires-Python: >=3.14
|
|
14
|
+
Requires-Dist: autodoip>=0.1.4
|
|
15
|
+
Requires-Dist: pydantic>=2.0
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
autouds/__init__.py,sha256=39bu5LUuNPCW9_kctnFBqCbO8TyY61ol-zlas7wDak4,333
|
|
2
|
+
autouds/__main__.py,sha256=2py7m-rBoexlARxOBdhHK731bqdi_Ykg9zMhMYxvqos,846
|
|
3
|
+
autouds/_handler.py,sha256=plvZdRAGjkoUJPawxRxE0UyC1m90V2NDURStje8t36A,4040
|
|
4
|
+
autouds/_models.py,sha256=mFb9QQelrVc1Amk40wk4qAAv6THtC1o0JNiEeiRxu-E,9081
|
|
5
|
+
autouds/_tables.py,sha256=OKtAjcUr-D_EiqKonFhhGU_VVq-HWCbweDgErL-6dSw,16935
|
|
6
|
+
autouds/app.py,sha256=gnIjY2UHTjPZvLGvGhglLDV6lH2lE_gVXHp2ZTYHLhk,13647
|
|
7
|
+
autouds-0.1.0.dist-info/METADATA,sha256=mezS47lPU0b7_xb9_hQlutnSja5aubM8HCn0qgY_07o,559
|
|
8
|
+
autouds-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
9
|
+
autouds-0.1.0.dist-info/entry_points.txt,sha256=KI_yA34-jbMSPARJVU8b9i7TK9God1BMjaKU0FdOKgQ,50
|
|
10
|
+
autouds-0.1.0.dist-info/RECORD,,
|