Async CAN / ISO-TP / UDS library for Car Bus Analyzer
Project description
carbus-lib
Асинхронная Python-библиотека для работы с CAN / CAN-FD адаптерами CAN-Hacker / Car Bus Analyzer: низкоуровневый CAN/LIN, ISO-TP, UDS и удалённый доступ к адаптеру через сеть.
- 📡
carbus_async— работа с железом: CAN/CAN-FD, фильтры, терминатор 120 Ω, периодическая отправка, хуки - 📦
isotp_async— ISO-TP (ISO 15765-2): single- и multi-frame - 🩺
uds_async— UDS (ISO 14229): клиент и сервер (эмуляция ЭБУ) - 🌐 Remote / TCP-bridge — удалённый доступ к адаптеру через сеть, как к локальному
Python ≥ 3.10 · зависимости:
pyserial,pyserial-asyncio· целиком наasyncioПротестировано на устройствах с Протоколом версии 22 Поддерживаемые устройства: https://canhacker.ru/shop/
Содержание
- Возможности
- Установка
- Быстрый старт
- CAN-FD и BRS
- Приём и хуки
- Периодическая отправка
- Фильтры
- Терминатор
- Информация об устройстве
- Маршрутизация по CAN-ID
- ISO-TP
- UDS клиент
- UDS сервер
- Удалённая работа
- Логирование
- Примеры
- Лицензия
Возможности
- CAN / CAN-FD: отправка и приём, BRS (bit-rate switching)
- Настройка каналов по битрейту или по точным bit-timing
- Фильтры по 11- и 29-битным ID, очистка фильтров
- Управление встроенным терминатором 120 Ω
- Периодическая отправка кадров (статичные данные или с модификацией на лету)
- Хуки на приём кадров по ID и по маске данных
- Маршрутизация принятых кадров по CAN-ID в отдельные очереди
- ISO-TP (single + multi-frame)
- UDS клиент и UDS сервер (эмуляция ЭБУ)
- Удалённая работа через relay-сервер и TCP-мост
- Подробное логирование всего протокольного трафика
Установка
Из PyPI:
python -m pip install carbus-lib
Либо editable-режим из репозитория (нужно для разработки):
git clone https://github.com/controllerzz/carbus_lib.git
cd carbus_lib
pip install -e .
Быстрый старт
Открыть устройство, настроить классический CAN-канал, отправить и принять кадр.
import asyncio
from carbus_async import CarBusDevice, CanMessage
async def main():
dev = await CarBusDevice.open("COM6", baudrate=115200)
# классический CAN, 500 kbit/s, канал 1
await dev.open_can_channel(channel=1, nominal_bitrate=500_000)
# внутренний терминатор 120 Ω (если поддерживается устройством)
await dev.ensure_terminator(channel=1, enabled=True)
# отправка кадра
msg = CanMessage(can_id=0x7E0, data=b"\x02\x3E\x00\x00\x00\x00\x00\x00")
await dev.send_can(msg, channel=1)
# приём любого кадра -> (channel, CanMessage)
ch, rx = await dev.receive_can()
print("RX:", ch, hex(rx.can_id), rx.data.hex())
await dev.close()
asyncio.run(main())
Классический CAN через open_can_channel поддерживает битрейты
10k / 20k / 33.3k / 50k / 62.5k / 83.3k / 95.2k / 100k / 125k / 250k / 400k / 500k / 800k / 1M.
CAN-FD и BRS
1. По стандартным битрейтам
await dev.open_can_channel(
channel=1,
nominal_bitrate=500_000, # арбитражная фаза
data_bitrate=2_000_000, # фаза данных (используется при BRS)
fd=True,
brs=True,
)
Поддерживаемые битрейты (CAN-модуль 120 МГц, точка выборки ~80%):
- nominal: 125k / 250k / 500k / 1M
- data: 500k / 1M / 2M / 4M / 5M
На этих адаптерах FD-канал настраивается точными bit-timing, а не одиночным индексом скорости данных — иначе канал открывается, но FD/BRS-кадры не уходят в шину.
open_can_channelделает это автоматически. Если тактовая модуля не 120 МГц, передайте её черезcan_clock_hz=....
2. По точным bit-timing
Для нестандартных скоростей задайте тайминги напрямую:
from carbus_async import CanTiming
await dev.open_can_channel_custom(
channel=1,
nominal_timing=CanTiming(prescaler=15, tq_seg1=12, tq_seg2=3, sjw=1), # 500k @ 120 МГц
data_timing=CanTiming(prescaler=6, tq_seg1=7, tq_seg2=2, sjw=1), # 2M @ 120 МГц
fd=True,
brs=True,
)
Отправка FD-кадра
Тип кадра задаётся флагами fd / brs в самом CanMessage:
# FD-кадр с переключением скорости (BRS), 16 байт данных
msg = CanMessage(can_id=0x100, data=bytes(range(16)), fd=True, brs=True)
await dev.send_can(msg, channel=1)
Допустимые длины данных CAN-FD: 0–8, 12, 16, 20, 24, 32, 48, 64 байт.
Приём и хуки
Способы приёма:
ch, msg = await dev.receive_can() # с любого канала
msg = await dev.receive_can_on(channel=1) # только канал 1
msg = await dev.receive_can_on_timeout(channel=1, timeout=1.0) # None при таймауте
Хуки (вызываются на каждый подходящий принятый кадр):
# по CAN-ID
@dev.on_can_id(0x7E0)
async def on_engine(ch, msg):
print("ENGINE:", hex(msg.can_id), msg.data.hex())
# по CAN-ID + совпадению данных по маске
@dev.on_can_match(
can_id=0x7E0,
value=b"\x02\x10\x00",
mask=b"\xFF\xFF\x00",
)
async def on_session_control(ch, msg):
print("DiagnosticSessionControl")
Периодическая отправка
from carbus_async import PeriodicCanSender
sender = PeriodicCanSender(dev)
# статичные данные, период 100 мс (классический CAN)
sender.add(
"heartbeat",
channel=1,
can_id=0x123,
data=b"\x01\x02\x03\x04\x05\x06\x07\x08",
period_s=0.1,
)
# CAN-FD + BRS со счётчиком в первом байте
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,
fd=True,
brs=True,
modify=mod,
)
# управление задачами
await sender.remove("cnt") # остановить и удалить одну задачу
await sender.stop_all() # остановить все
Параметры add(...): channel, can_id, data, period_s,
modify (функция (tick, data) -> bytes, может быть async), extended,
fd, brs, rtr, echo, confirm, autostart.
Фильтры
11-битные фильтры занимают индексы 0..27, 29-битные — 28..35.
# очистить все фильтры на канале 1
await dev.clear_all_filters(1)
# пропускать только ответы с ID 0x7E8 (11-бит)
await dev.set_std_id_filter(channel=1, index=0, can_id=0x7E8, mask=0x7FF)
# 29-битный фильтр
await dev.set_ext_id_filter(channel=1, index=28, can_id=0x18DAF110)
Терминатор
await dev.set_terminator(channel=1, enabled=True)
await dev.set_terminator(channel=2, enabled=False)
# проверка поддержки и включение
if await dev.has_terminator(channel=1):
await dev.set_terminator(channel=1, enabled=True)
# включить, только если устройство это поддерживает
await dev.ensure_terminator(channel=1, enabled=True)
Информация об устройстве
info = await dev.get_device_info()
print("HW:", info.hardware_name)
print("FW:", info.firmware_version)
print("Serial:", info.serial_int)
print("Каналы:", info.channel_types) # {1: 'CANFD', 2: 'CAN', ...}
print("Частоты:", info.channel_frequencies) # {1: 120000000, ...} Гц
print("Фичи:",
"gateway" if info.feature_gateway else "",
"isotp" if info.feature_isotp else "",
"txbuf" if info.feature_tx_buffer else "",
"txtask" if info.feature_tx_task else "")
Маршрутизация по CAN-ID
CanIdRouter раскладывает принятые кадры по отдельным очередям на каждый CAN-ID —
удобно, когда на одном канале несколько независимых потоков.
from carbus_async import CanIdRouter
router = CanIdRouter(dev, channel=1)
await router.start()
q = router.get_queue(0x7E8) # очередь только для кадров с ID 0x7E8
msg = await q.get()
print(msg.data.hex())
await router.stop()
Роутер можно передать в open_isotp(..., router=router), чтобы держать несколько
ISO-TP соединений на одном канале, не «перехватывая» кадры друг друга.
ISO-TP
ISO-TP-канал строится поверх CarBusDevice:
from isotp_async import open_isotp
isotp = await open_isotp(dev, channel=1, tx_id=0x7E0, rx_id=0x7E8)
# ReadDataByIdentifier F190 (VIN)
await isotp.send_pdu(b"\x22\xF1\x90")
# полный ответ (single или multi-frame)
resp = await isotp.recv_pdu(timeout=5.0)
print("ISO-TP:", resp.hex())
UDS клиент
from isotp_async import open_isotp
from uds_async import UdsClient
isotp = await open_isotp(dev, channel=1, tx_id=0x7E0, rx_id=0x7E8)
uds = UdsClient(isotp)
await uds.diagnostic_session_control(0x03) # extended session
vin = await uds.read_data_by_identifier(0xF190)
print("VIN:", vin.decode(errors="ignore"))
await uds.tester_present()
Доступные сервисы: diagnostic_session_control, read_data_by_identifier,
write_data_by_identifier, security_access_get_seed,
security_access_send_key, tester_present.
UDS сервер
Эмуляция ЭБУ: на каждый сервис (SID) вешается обработчик, возвращающий полный
ответный PDU. Отрицательный ответ — через UdsNegativeResponse.
from isotp_async import open_isotp
from uds_async import UdsServer
from uds_async.exceptions import UdsNegativeResponse
# слушаем запросы на 0x7E0, отвечаем на 0x7E8
isotp = await open_isotp(dev, channel=1, tx_id=0x7E8, rx_id=0x7E0)
server = UdsServer(isotp)
@server.service(0x22) # ReadDataByIdentifier
async def on_rdbi(req: bytes) -> bytes:
did = (req[1] << 8) | req[2]
if did == 0xF190:
return b"\x62\xF1\x90" + b"WVWZZZ1KZAW000001"
raise UdsNegativeResponse(req_sid=0x22, nrc=0x31) # requestOutOfRange
await server.serve_forever()
Удалённая работа
Через relay-сервер (любой IP)
Можно воспользоваться бесплатным сервером:
IP: 185.42.26.80
PORT: 9000
Сервер / Relay (машина с белым IP):
carbus-relay-server --host 0.0.0.0 --port 9000
# или
python -m carbus_async.remote.server --host 0.0.0.0 --port 9000
Агент (машина, к которой подключён адаптер):
carbus-relay-agent --port COM6 --baudrate 115200 --server <IP_СЕРВЕРА>:9000 --serial 5957 --password 1234
# или
python -m carbus_async.remote.agent --port COM6 --baudrate 115200 --server <IP_СЕРВЕРА>:9000 --serial 5957 --password 1234
| параметр | назначение |
|---|---|
--server <IP>:9000 |
адрес и порт relay-сервера |
--serial 5957 |
серийный номер устройства |
--password 1234 |
пароль для удалённого доступа |
Клиент (удалённая машина):
from carbus_async import open_remote_device
dev = await open_remote_device("185.42.26.80", 9000, serial=5957, password="1234")
# дальше — обычный API CarBusDevice
Через TCP-мост в локальной сети
Сервер (рядом с адаптером):
python -m carbus_async.tcp_bridge --serial COM6 --port 7000
Клиент (та же сеть) — тот же API, но через open_tcp:
import asyncio
from carbus_async import CarBusDevice, CanMessage
async def main():
dev = await CarBusDevice.open_tcp("192.168.1.10", 7000)
await dev.open_can_channel(channel=1, nominal_bitrate=500_000)
msg = CanMessage(can_id=0x321, data=b"\x01\x02\x03\x04")
await dev.send_can(msg, channel=1)
ch, rx = await dev.receive_can()
print("REMOTE RX:", ch, hex(rx.can_id), rx.data.hex())
await dev.close()
asyncio.run(main())
Логирование
import logging
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
)
Логгеры:
carbus_async.wire.*— сырые кадры по USB/TCP (TX/RX)carbus_async.device.*— высокоуровневые события, ошибки,BUS_ERROR- дополнительные логгеры в
isotp_async/uds_async
Примеры
Готовые скрипты — в каталоге example/:
| файл | что показывает |
|---|---|
can_periodic_message.py |
периодическая отправка (классический CAN) |
canfd_periodic_message.py |
периодическая отправка CAN-FD / BRS |
can_message_hook.py |
хуки на приём кадров |
uds_service22_scaner.py |
скан UDS-сервиса 0x22 (RDBI) |
uds_id_scaner.py |
перебор идентификаторов (DID) |
uds_ecu_emulated.py |
эмуляция ЭБУ (UDS-сервер) |
car_ecus_emulated.py |
эмуляция нескольких ЭБУ |
remote_can_periodic_message.py |
периодика через relay-сервер |
remote_can_bridge.py |
мост CAN ↔ удалённое устройство |
remote_relay.py |
запуск relay-сервера |
Лицензия
MIT — распространяется «как есть», без каких-либо гарантий. См. LICENSE.
Pull Requests и предложения по улучшению приветствуются 🚗
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file carbus_lib-0.1.9.tar.gz.
File metadata
- Download URL: carbus_lib-0.1.9.tar.gz
- Upload date:
- Size: 37.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
812674d5d57e5163624c80655802990daab12e417b7e72e5f0353200d5dd54ca
|
|
| MD5 |
fa28614fdc1ae3f37fe6565fd880629f
|
|
| BLAKE2b-256 |
6b6f4db7452dbf2d28d682b7881b6b4b8f0b620100f780a0ae9f156eaab12e97
|
File details
Details for the file carbus_lib-0.1.9-py3-none-any.whl.
File metadata
- Download URL: carbus_lib-0.1.9-py3-none-any.whl
- Upload date:
- Size: 38.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
62a3a4242e79a68d7eb4d27bdbdc5e0590dac12a95aec43a261e123e762a6e52
|
|
| MD5 |
3ed0afe880fcb7b35ab51cbd833623ce
|
|
| BLAKE2b-256 |
fc8bb2e9c10d1a7fdaaa6aef282b4d31098cbf99a1c932a71ecb8aca828ee3db
|