v0.1.5 add periodic messages
This commit is contained in:
parent
e3ca999777
commit
8015b6b5f2
36
README.md
36
README.md
|
|
@ -146,6 +146,42 @@ await dev.set_terminator(channel=2, enabled=False)
|
|||
|
||||
````
|
||||
|
||||
## Отправка периодичных сообщений:
|
||||
С константными данными
|
||||
````python
|
||||
from carbus_async import PeriodicCanSender
|
||||
|
||||
sender = PeriodicCanSender(dev)
|
||||
sender.add(
|
||||
"heartbeat",
|
||||
channel=1,
|
||||
can_id=0x123,
|
||||
data=b"\x01\x02\x03\x04\x05\x06\x07\x08",
|
||||
period_s=0.1,
|
||||
)
|
||||
````
|
||||
|
||||
С модификацией данных
|
||||
````python
|
||||
from carbus_async import PeriodicCanSender
|
||||
|
||||
sender = PeriodicCanSender(dev)
|
||||
|
||||
def mod(tick, data):
|
||||
b = bytearray(data)
|
||||
b[0] = tick & 0xFF
|
||||
return bytes(b)
|
||||
|
||||
sender.add(
|
||||
"cnt",
|
||||
channel=1,
|
||||
can_id=0x100,
|
||||
data=b"\x00" * 8,
|
||||
period_s=0.5,
|
||||
modify=mod)
|
||||
````
|
||||
|
||||
|
||||
## Хуки подписка на сообщение / сообщение + данные по маске:
|
||||
Подписка по CAN ID
|
||||
````python
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ from .device import CarBusDevice
|
|||
from .messages import CanMessage, MessageDirection
|
||||
from .exceptions import CarBusError, CommandError, SyncError
|
||||
from .can_router import CanIdRouter, RoutedCarBusCanTransport
|
||||
from .periodic import PeriodicCanSender, PeriodicJob
|
||||
|
||||
__all__ = [
|
||||
"CarBusDevice",
|
||||
|
|
@ -12,4 +13,6 @@ __all__ = [
|
|||
"SyncError",
|
||||
"CanIdRouter",
|
||||
"RoutedCarBusCanTransport",
|
||||
"PeriodicCanSender",
|
||||
"PeriodicJob",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,139 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
from typing import Awaitable, Callable, Optional, Union
|
||||
|
||||
from .messages import CanMessage
|
||||
from .device import CarBusDevice
|
||||
|
||||
ModifyFn = Callable[[int, bytes], Union[bytes, Awaitable[bytes]]]
|
||||
|
||||
|
||||
@dataclass
|
||||
class PeriodicJob:
|
||||
name: str
|
||||
can_id: int
|
||||
data: bytes
|
||||
period_s: float
|
||||
channel: int = 1
|
||||
extended: bool = False
|
||||
fd: bool = False
|
||||
brs: bool = False
|
||||
rtr: bool = False
|
||||
echo: bool = False
|
||||
confirm: bool = False
|
||||
modify: Optional[ModifyFn] = None
|
||||
|
||||
_task: Optional[asyncio.Task] = None
|
||||
_stop: asyncio.Event = asyncio.Event()
|
||||
|
||||
def start(self, dev: CarBusDevice) -> None:
|
||||
if self._task is not None and not self._task.done():
|
||||
return
|
||||
self._stop = asyncio.Event()
|
||||
self._task = asyncio.create_task(self._run(dev), name=f"PeriodicJob:{self.name}")
|
||||
|
||||
def _done(t: asyncio.Task):
|
||||
exc = t.exception()
|
||||
if exc:
|
||||
print(f"[PeriodicJob:{self.name}] crashed:", repr(exc))
|
||||
|
||||
self._task.add_done_callback(_done)
|
||||
|
||||
async def stop(self) -> None:
|
||||
self._stop.set()
|
||||
if self._task is not None:
|
||||
self._task.cancel()
|
||||
with asyncio.CancelledError.__suppress_context__ if False else None:
|
||||
pass
|
||||
try:
|
||||
await self._task
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
self._task = None
|
||||
|
||||
async def _run(self, dev: CarBusDevice) -> None:
|
||||
tick = 0
|
||||
next_t = time.perf_counter()
|
||||
|
||||
while not self._stop.is_set():
|
||||
now = time.perf_counter()
|
||||
if now < next_t:
|
||||
await asyncio.sleep(next_t - now)
|
||||
next_t += self.period_s
|
||||
|
||||
out = self.data
|
||||
if self.modify is not None:
|
||||
res = self.modify(tick, out)
|
||||
out = await res if asyncio.iscoroutine(res) else res
|
||||
|
||||
msg = CanMessage(
|
||||
can_id=self.can_id,
|
||||
data=out,
|
||||
)
|
||||
await dev.send_can(
|
||||
msg,
|
||||
channel=self.channel,
|
||||
confirm=self.confirm,
|
||||
echo=self.echo,
|
||||
)
|
||||
tick += 1
|
||||
|
||||
|
||||
class PeriodicCanSender:
|
||||
def __init__(self, dev: CarBusDevice):
|
||||
self._dev = dev
|
||||
self._jobs: dict[str, PeriodicJob] = {}
|
||||
|
||||
def add(
|
||||
self,
|
||||
name: str,
|
||||
*,
|
||||
channel: int,
|
||||
can_id: int,
|
||||
data: bytes,
|
||||
period_s: float,
|
||||
modify: Optional[ModifyFn] = None,
|
||||
extended: bool = False,
|
||||
fd: bool = False,
|
||||
brs: bool = False,
|
||||
rtr: bool = False,
|
||||
echo: bool = False,
|
||||
confirm: bool = False,
|
||||
autostart: bool = True,
|
||||
) -> PeriodicJob:
|
||||
if name in self._jobs:
|
||||
raise ValueError(f"Periodic job '{name}' already exists")
|
||||
|
||||
job = PeriodicJob(
|
||||
name=name,
|
||||
can_id=can_id,
|
||||
data=data,
|
||||
period_s=period_s,
|
||||
channel=channel,
|
||||
extended=extended,
|
||||
fd=fd,
|
||||
brs=brs,
|
||||
rtr=rtr,
|
||||
echo=echo,
|
||||
confirm=confirm,
|
||||
modify=modify,
|
||||
)
|
||||
self._jobs[name] = job
|
||||
if autostart:
|
||||
job.start(self._dev)
|
||||
return job
|
||||
|
||||
def get(self, name: str) -> PeriodicJob:
|
||||
return self._jobs[name]
|
||||
|
||||
async def remove(self, name: str) -> None:
|
||||
job = self._jobs.pop(name, None)
|
||||
if job is not None:
|
||||
await job.stop()
|
||||
|
||||
async def stop_all(self) -> None:
|
||||
await asyncio.gather(*(job.stop() for job in self._jobs.values()), return_exceptions=True)
|
||||
self._jobs.clear()
|
||||
|
|
@ -1,10 +1,7 @@
|
|||
import asyncio
|
||||
|
||||
from carbus_async import CarBusDevice, CanMessage
|
||||
from isotp_async import open_isotp
|
||||
from uds_async import UdsClient
|
||||
import signal
|
||||
import logging
|
||||
|
||||
|
||||
async def wait_forever() -> None:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
import asyncio
|
||||
|
||||
from carbus_async import CarBusDevice, PeriodicCanSender
|
||||
|
||||
|
||||
async def main(is_debug=False):
|
||||
dev = await CarBusDevice.open("COM6")
|
||||
|
||||
await dev.open_can_channel(
|
||||
channel=1,
|
||||
nominal_bitrate=500_000,
|
||||
)
|
||||
|
||||
await dev.set_terminator(channel=1, enabled=True)
|
||||
|
||||
sender = PeriodicCanSender(dev)
|
||||
|
||||
def mod(tick, data):
|
||||
b = bytearray(data)
|
||||
b[0] = tick & 0xFF
|
||||
return bytes(b)
|
||||
|
||||
sender.add(
|
||||
"cnt",
|
||||
channel=1,
|
||||
can_id=0x100,
|
||||
data=b"\x00" * 8,
|
||||
period_s=0.5,
|
||||
modify=mod)
|
||||
|
||||
sender.add(
|
||||
"heartbeat",
|
||||
channel=1,
|
||||
can_id=0x123,
|
||||
data=b"\x01\x02\x03\x04\x05\x06\x07\x08",
|
||||
period_s=0.1,
|
||||
)
|
||||
|
||||
try:
|
||||
await asyncio.Event().wait()
|
||||
finally:
|
||||
await sender.stop_all()
|
||||
await dev.close()
|
||||
|
||||
return
|
||||
|
||||
|
||||
asyncio.run(main())
|
||||
|
|
@ -4,18 +4,18 @@ from dataclasses import dataclass
|
|||
from pathlib import Path
|
||||
from typing import Any, Dict
|
||||
|
||||
from carbus_async import CanIdRouter, RoutedCarBusCanTransport
|
||||
from carbus_async import CanIdRouter, RoutedCarBusCanTransport, PeriodicCanSender
|
||||
from carbus_async.device import CarBusDevice
|
||||
from isotp_async import open_isotp, IsoTpConnection
|
||||
from uds_async import UdsServer
|
||||
from uds_async.exceptions import UdsNegativeResponse, UdsError
|
||||
|
||||
# import logging
|
||||
#
|
||||
# logging.basicConfig(
|
||||
# level=logging.DEBUG,
|
||||
# format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
||||
# )
|
||||
import logging
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.DEBUG,
|
||||
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
||||
)
|
||||
|
||||
TESTER_ID = 0x740
|
||||
ECU_ID = 0x760
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||
|
||||
[project]
|
||||
name = "carbus-lib"
|
||||
version = "0.1.4"
|
||||
version = "0.1.5"
|
||||
description = "Async CAN / ISO-TP / UDS library for Car Bus Analyzer"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
|
|
|
|||
Loading…
Reference in New Issue