Metadata-Version: 2.4
Name: python-can-busmust
Version: 0.1.0a1
Summary: Alpha native PyUSB BUSMUST CAN and CAN FD backend for python-can
License-Expression: GPL-2.0-or-later
Project-URL: Homepage, https://github.com/madprogrammer/python-can-busmust
Project-URL: Repository, https://github.com/madprogrammer/python-can-busmust
Project-URL: Issues, https://github.com/madprogrammer/python-can-busmust/issues
Project-URL: Documentation, https://github.com/madprogrammer/python-can-busmust#readme
Project-URL: Changelog, https://github.com/madprogrammer/python-can-busmust/blob/main/CHANGELOG.md
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: System :: Hardware :: Hardware Drivers
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: python-can<5,>=4.3
Requires-Dist: pyusb<2,>=1.2
Provides-Extra: test
Requires-Dist: pytest>=7; extra == "test"
Requires-Dist: build; extra == "test"
Requires-Dist: can-isotp<3,>=2; extra == "test"
Requires-Dist: cantools>=39; extra == "test"
Dynamic: license-file

# python-can-busmust

**Alpha release: 0.1.0a1. Not yet validated on physical hardware.**
The software tests use simulated USB devices. APIs and firmware compatibility
may change as real-adapter testing proceeds. This is an independent project,
not an official BUSMUST release.

A native Python/PyUSB driver for BUSMUST USB CAN and CAN FD adapters. It talks
directly to the device protocol described by the [bmsocketcan reference](https://github.com/busmust/bmsocketcan/tree/70919fbc2a1be146498ca0df941d1d8a0a93c74b);
it does not load the BMAPI shared library or require SocketCAN.

The installed package registers `interface="busmust"` with `python-can` using
its [plugin interface](https://python-can.readthedocs.io/en/stable/plugin-interface.html).
Standard `can.Message`, software filters, `Notifier`, logging, asynchronous
listeners, `ThreadSafeBus`, and software periodic transmission are supported.
Libraries accepting a `can.BusABC`, such as can-isotp and cantools, can use this
bus directly. See [the protocol analysis](https://github.com/madprogrammer/python-can-busmust/blob/main/docs/protocol.md) for implementation
details and differences from the kernel driver.

## Install

Python 3.9 or newer and a libusb backend are required:

```sh
# macOS
brew install libusb

# Debian/Ubuntu
sudo apt install libusb-1.0-0

# Install the alpha from PyPI, in your Python environment
python -m pip install --pre python-can-busmust

# Or pin this release
python -m pip install python-can-busmust==0.1.0a1
```

On Windows, install a libusb-compatible driver (usually WinUSB via Zadig) for
the adapter's **CAN interface 0** and make libusb available to PyUSB. For a
composite device, keep the other interfaces intact. Switching to WinUSB may
prevent the vendor application's driver from opening that interface. Consult
the [PyUSB installation guide](https://github.com/pyusb/pyusb#installing) for
backend setup.

On Linux, grant your user access to the adapter, for example with a udev rule
`SUBSYSTEM=="usb", ATTR{idVendor}=="0810", MODE="0660", TAG+="uaccess"`.
Reload udev rules and reconnect the adapter. Stop applications using its kernel
CAN interfaces before opening it with `detach_kernel_driver=True`. The default
refuses to detach an active kernel driver; an explicitly detached driver is
reattached when the last bus on that adapter shuts down.

## Send and receive

```python
import can

with can.Bus(interface="busmust", channel=0, bitrate=500_000,
             fd=True, data_bitrate=2_000_000, termination=120) as bus:
    bus.send(can.Message(arbitration_id=0x123, is_extended_id=False,
                         data=[0xDE, 0xAD, 0xBE, 0xEF]), timeout=0.5)
    message = bus.recv(timeout=1.0)
    print(message)
```

Channels are **zero-based**. Set bitrates, sample points, and termination to
match the physical bus. `termination=None` preserves the device setting;
use `0` to disable or `120` to enable its resistor.

```python
configs = can.detect_available_configs(interfaces=["busmust"])
print(configs)

# Select an adapter explicitly when more than one is attached.
bus = can.Bus(interface="busmust", channel=0, serial="YOUR_SERIAL",
              bitrate=500_000)
try:
    bus.set_filters([{"can_id": 0x700, "can_mask": 0x700, "extended": False}])
    print(bus.get_status())
finally:
    bus.shutdown()
```

Separate channels on the same adapter share a USB reader and have independent
receive queues. Open each channel once per process; different processes cannot
share the adapter. Closing one channel leaves the others running.

```python
with can.Bus(interface="busmust", channel=0) as first, \
     can.Bus(interface="busmust", channel=1) as second:
    listener = can.BufferedReader()
    notifier = can.Notifier(second, [listener])
    try:
        task = first.send_periodic(
            can.Message(arbitration_id=0x123, is_extended_id=False, data=[1]),
            period=0.1,
        )
        print(listener.get_message(timeout=1.0))
        task.stop()
    finally:
        notifier.stop()
```

This example requires a two-channel adapter and a correctly wired bus between
the channels. `examples/loopback.py` provides a single-channel firmware loopback
smoke test. No hardware transmissions are needed to run the automated tests.

## Options

| Argument | Default | Meaning |
| --- | --- | --- |
| `channel` | `0` | Zero-based physical CAN port; numeric strings accepted |
| `bitrate` | `500_000` | Arbitration bitrate in bit/s; whole kbit/s |
| `fd` | `False` | Enable CAN FD alongside Classical CAN |
| `data_bitrate` | `2_000_000` | CAN FD data bitrate in bit/s |
| `sample_point` | `87.5` | Arbitration sample point in percent |
| `data_sample_point` | `80.0` | Data sample point in percent |
| `termination` | `None` | Preserve, disable (`0`), or enable (`120`) termination |
| `serial` | `None` | Exact USB serial number |
| `product_id` | `None` | Integer USB product ID |
| `usb_bus`, `usb_address` | `None` | USB location selectors returned by discovery |
| `listen_only` | `False` | Receive without acknowledging; sending rejected |
| `loopback` | `False` | Internal firmware loopback |
| `receive_own_messages` | `False` | Request and deliver firmware TX echoes as `is_rx=False` |
| `non_iso` | `False` | Non-ISO CAN FD mode; requires `fd=True` |
| `one_shot` | `False` | Disable firmware automatic retransmission |
| `can_filters` | `None` | Standard python-can software filters |
| `rx_queue_size` | `10_000` | Maximum buffered frames per channel |
| `detach_kernel_driver` | `False` | Explicitly allow temporary USB interface detachment |

The reference protocol encodes sample points as integer percentages, so 87.5
is sent as 87. Explicit `can.BitTiming`/`BitTimingFd` configuration is unsupported.
Validation checks the wire format's range; firmware determines supported rates.

## Supported device IDs

All use USB vendor ID `0810`. These are the reference implementation's mappings;
listing an ID is not a claim of hardware validation.

| Generation | Product | Product ID | CAN channels |
| --- | --- | --- | --- |
| 2 | X1 / X1 Pro | F012 / F112 | 1 |
| 2 | X2 / X4 / X8 Pi | F122 / F142 / F182 | 2 / 4 / 8 |
| 2.5 | X2R / X4R | E122 / E142 | 2 / 4 |
| 3 | X1 / X2 / X4 | F013 / F023 / F043 | 1 / 2 / 4 |
| 3 | XL2 / XL4 | 0043 / 0083 | 2 / 4 |

## Behavior and limits

- `send()` waits for USB submission, **not a CAN-bus acknowledgment**. `timeout`
  is in seconds; `None` uses a 1-second USB transfer timeout, and `0` uses a
  minimum 1 ms because libusb interprets zero as infinite. Partial writes are
  reported and never retried automatically. A timed-out transfer may have sent
  data; avoid blind retries for commands with side effects.
- CAN FD payloads between legal DLC lengths are zero-padded to the next legal
  length. Receiving reports the on-wire length, e.g. 9 transmitted bytes become
  a 12-byte frame. RTR messages retain their requested DLC and have empty data.
- Gen3 timestamp tails provide UTC microseconds. Older devices' wrapping
  microsecond clocks are anchored to host epoch time on the first received
  frame; absolute accuracy includes USB latency. Echoes do not advance the
  receive timestamp tracker. Clock reset requires reopening the adapter.
- USB disconnects, malformed CAN envelopes, and queue overflow raise
  `can.CanOperationError`. Overflow fails the affected channel rather than
  silently dropping frames. Reopen failed channels; reconnect all channels
  after a USB session failure. Stop `Notifier` before shutting down its bus.
- Status flags/counters are available through `get_status()` and `bus.state`.
  This driver does not synthesize SocketCAN error frames or implement the
  kernel driver's firmware-specific bus-off recovery procedures.
- Hardware TX tasks, routes, offline logging/replay, persistent configuration,
  PTP synchronization, and hardware acceptance-filter optimization are outside
  this implementation. Periodic sending and filtering run in Python.
- No physical adapter validation or throughput guarantees are claimed. Firmware
  echo behavior and cross-platform backend setup need hardware verification.

## Development and hardware feedback

Clone the repository before running the development commands:

```sh
git clone https://github.com/madprogrammer/python-can-busmust.git
cd python-can-busmust
python -m pip install -e '.[test]'
python -m pytest
python -m build
```

Tests use the real `python-can` and PyUSB packages with a simulated USB device.
They cover protocol vectors, shared-channel lifecycle, transfer failures,
timestamps, plugin discovery, and python-can listener/periodic APIs.

For hardware reports, include the model/product ID, firmware version, OS,
Python version, bitrate/sample-point settings, and a minimal reproduction in
[a GitHub issue](https://github.com/madprogrammer/python-can-busmust/issues).
Start with `python examples/loopback.py`, then test a correctly terminated
two-node bus. Internal loopback alone does not establish physical-bus operation.

## License and provenance

GPL-2.0-or-later, see [LICENSE](https://github.com/madprogrammer/python-can-busmust/blob/main/LICENSE). Protocol behavior was derived from
the GPL-licensed `bmsocketcan/src/bmcan_proto.c`, `bmcan_usb.c`,
`bmcan_netdev.c`, and `inc/bmcan_core.h` (BUSMUST, 2026), at upstream commit
`70919fbc2a1be146498ca0df941d1d8a0a93c74b`. A local reference clone may be kept
in the ignored `bmsocketcan/` directory. Its separately licensed vendor headers are not
copied into or required by this Python distribution.
