mirror of
https://github.com/controllerzz/carbus_lib.git
synced 2026-05-22 19:51:41 +03:00
save
This commit is contained in:
22
uds_async/__init__.py
Normal file
22
uds_async/__init__.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from .client import UdsClient
|
||||
from .server import UdsServer
|
||||
|
||||
try:
|
||||
from .types import (
|
||||
UdsRequest,
|
||||
UdsResponse,
|
||||
UdsPositiveResponse,
|
||||
UdsNegativeResponse,
|
||||
ResponseCode,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"UdsClient",
|
||||
"UdsServer",
|
||||
]
|
||||
except ImportError:
|
||||
# если types.py нет или переименован — хотя бы клиент и сервер доступны
|
||||
__all__ = [
|
||||
"UdsClient",
|
||||
"UdsServer",
|
||||
]
|
||||
63
uds_async/client.py
Normal file
63
uds_async/client.py
Normal file
@@ -0,0 +1,63 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
from isotp_async.transport import IsoTpChannel
|
||||
from .exceptions import UdsError, UdsNegativeResponse
|
||||
|
||||
|
||||
@dataclass
|
||||
class UdsClient:
|
||||
|
||||
isotp: IsoTpChannel
|
||||
p2_timeout: float = 1.0
|
||||
|
||||
async def _request(self, payload: bytes) -> bytes:
|
||||
await self.isotp.send_pdu(payload)
|
||||
resp = await self.isotp.recv_pdu(timeout=self.p2_timeout)
|
||||
if resp is None:
|
||||
raise TimeoutError("UDS response timeout")
|
||||
|
||||
if resp[0] == 0x7F:
|
||||
if len(resp) < 3:
|
||||
raise UdsError("Malformed UDS negative response")
|
||||
sid = resp[1]
|
||||
nrc = resp[2]
|
||||
raise UdsNegativeResponse(req_sid=sid, nrc=nrc)
|
||||
|
||||
return resp
|
||||
|
||||
async def diagnostic_session_control(self, session: int) -> bytes:
|
||||
req = bytes([0x10, session & 0xFF])
|
||||
resp = await self._request(req)
|
||||
if resp[0] != 0x50:
|
||||
raise UdsError(f"Unexpected SID 0x{resp[0]:02X} for DSC")
|
||||
return resp
|
||||
|
||||
async def tester_present(self, suppress_response: bool = False) -> Optional[bytes]:
|
||||
|
||||
sub = 0x80 if suppress_response else 0x00
|
||||
req = bytes([0x3E, sub])
|
||||
|
||||
if suppress_response:
|
||||
try:
|
||||
resp = await self._request(req)
|
||||
except TimeoutError:
|
||||
return None
|
||||
return resp
|
||||
|
||||
resp = await self._request(req)
|
||||
if resp[0] != 0x7E:
|
||||
raise UdsError(f"Unexpected SID 0x{resp[0]:02X} for TesterPresent")
|
||||
return resp
|
||||
|
||||
async def read_data_by_identifier(self, did: int) -> bytes:
|
||||
|
||||
req = bytes([0x22, (did >> 8) & 0xFF, did & 0xFF])
|
||||
resp = await self._request(req)
|
||||
if resp[0] != 0x62:
|
||||
raise UdsError(f"Unexpected SID 0x{resp[0]:02X} for RDBI")
|
||||
if len(resp) < 3:
|
||||
raise UdsError("Malformed RDBI response")
|
||||
return resp[3:]
|
||||
16
uds_async/exceptions.py
Normal file
16
uds_async/exceptions.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
class UdsError(Exception):
|
||||
...
|
||||
|
||||
@dataclass
|
||||
class UdsNegativeResponse(UdsError):
|
||||
|
||||
req_sid: int
|
||||
nrc: int
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"UDS NRC 0x{self.nrc:02X} for SID 0x{self.req_sid:02X}"
|
||||
62
uds_async/server.py
Normal file
62
uds_async/server.py
Normal file
@@ -0,0 +1,62 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Awaitable, Callable, Dict, Optional
|
||||
|
||||
from isotp_async.transport import IsoTpChannel
|
||||
from .exceptions import UdsNegativeResponse
|
||||
|
||||
UdsHandler = Callable[[bytes], Awaitable[Optional[bytes]]]
|
||||
|
||||
|
||||
@dataclass
|
||||
class UdsServer:
|
||||
|
||||
isotp: IsoTpChannel
|
||||
p2_timeout: float = 1.0
|
||||
handlers: Dict[int, UdsHandler] = field(default_factory=dict)
|
||||
|
||||
async def serve_forever(self) -> None:
|
||||
while True:
|
||||
try:
|
||||
req = await self.isotp.recv_pdu(timeout=self.p2_timeout)
|
||||
except asyncio.CancelledError:
|
||||
break
|
||||
|
||||
if not req:
|
||||
continue
|
||||
|
||||
sid = req[0]
|
||||
handler = self.handlers.get(sid)
|
||||
if handler is None:
|
||||
# ServiceNotSupported
|
||||
await self._send_negative_response(sid, 0x11)
|
||||
continue
|
||||
|
||||
try:
|
||||
resp = await handler(req)
|
||||
if resp is None:
|
||||
continue
|
||||
|
||||
await self.isotp.send_pdu(resp)
|
||||
|
||||
except UdsNegativeResponse as e:
|
||||
await self._send_negative_response(e.req_sid, e.nrc)
|
||||
|
||||
except Exception:
|
||||
# General programming failure (0x72)
|
||||
await self._send_negative_response(sid, 0x72)
|
||||
|
||||
async def _send_negative_response(self, sid: int, nrc: int) -> None:
|
||||
payload = bytes([0x7F, sid & 0xFF, nrc & 0xFF])
|
||||
await self.isotp.send_pdu(payload)
|
||||
|
||||
def add_handler(self, sid: int, handler: UdsHandler) -> None:
|
||||
self.handlers[sid & 0xFF] = handler
|
||||
|
||||
def service(self, sid: int):
|
||||
def decorator(func: UdsHandler) -> UdsHandler:
|
||||
self.add_handler(sid, func)
|
||||
return func
|
||||
return decorator
|
||||
Reference in New Issue
Block a user