Skip to main content

Native Python-CAN hardware backend for PSCAN USB devices

Project description

pscan-pythoncan

A native python-can hardware interface for PSCAN USB CAN adapters.

pscan-pythoncan extends python-can by registering a new pscan interface backend. Once installed, you can use the standard can.Bus() API to send and receive CAN frames through your PSCAN hardware — no extra drivers or DLLs required. The heavy lifting (USB I/O, frame parsing, multi-frame buffering, and message filtering) is handled by a compiled native extension for maximum performance.

Key Features

  • Drop-in python-can backend — works with can.Bus(), can.Notifier, can.Logger, and all standard python-can tools.
  • Serial number based device selection — connect to a specific adapter by its unique serial number instead of an ambiguous channel or port name.
  • User-friendly name support — open devices by a short name stored in the adapter's EEPROM.
  • Auto-detection — automatically finds and connects to the first available PSCAN adapter.
  • High-performance core — frame serialization, deserialization, multi-frame USB bulk reads, and software filtering all run natively, not in pure Python.
  • Listen-only and loopback modes — built-in CAN controller mode configuration.
  • Bus state monitoring — read error counters and CAN controller state (Error Active / Warning / Passive / Bus-Off).

Installation

pip install pscan-pythoncan

python-can >= 4.0.0 is installed automatically as a dependency.

Supported platforms: Windows (x86, x64, aarch64). Python 3.8 and above.


Quick Start

import can

# Open the first available PSCAN device at 500 kbit/s
bus = can.Bus(interface='pscan', bitrate=500000)

# Send a CAN frame
msg = can.Message(arbitration_id=0x123, data=[0x11, 0x22, 0x33, 0x44], is_extended_id=False)
bus.send(msg)

# Receive a CAN frame (1 second timeout)
recv_msg = bus.recv(timeout=1.0)
if recv_msg is not None:
    print(recv_msg)

# Always shut down when done
bus.shutdown()

Opening a Device

PSCAN adapters are identified by their serial number (printed on the device label) or by an optional user-friendly name stored in EEPROM. This avoids the ambiguity of generic channel numbers or COM ports — you always connect to exactly the hardware you intend.

By serial number (recommended)

bus = can.Bus(interface='pscan', serial='P1P.IN:XFG:H001', bitrate=500000)

By user-friendly name

If your adapter has been programmed with a short name (e.g. via PSCANStudio), you can open it by that name:

bus = can.Bus(interface='pscan', name='MyBMS', bitrate=500000)

Auto-detect (single device)

When only one PSCAN adapter is connected you can omit serial and name:

bus = can.Bus(interface='pscan', bitrate=500000)

Constructor Parameters

All parameters are passed through the standard can.Bus() constructor:

bus = can.Bus(
    interface='pscan',
    channel='PSCAN_USB1',       # Channel label (for python-can compatibility)
    serial='P1P.IN:XFG:H001',  # Device serial number
    name='MyBMS',               # Device user-friendly name (alternative to serial)
    bitrate=500000,             # Bitrate in bits per second
    sample_point=875,           # Sample point in permille (875 = 87.5%)
    listen_only=False,          # Enable listen-only mode (no TX, no ACK)
    loopback=False,             # Enable loopback mode (TX echoed to RX)
    can_filters=None,           # Message filters (see Filtering section)
)
Parameter Type Default Description
serial str None Serial number of the PSCAN adapter. Used to select a specific device when multiple adapters are connected.
name str None User-friendly name stored in the adapter's EEPROM (takes priority over serial).
bitrate int 500000 CAN bus bitrate in bits per second. Common values: 125000, 250000, 500000, 1000000.
sample_point int 875 CAN sample point in permille. 875 means 87.5%. Typical range: 750875.
listen_only bool False When True, the adapter will not transmit any frames and will not send ACK bits on the bus. Useful for passive monitoring.
loopback bool False When True, transmitted frames are echoed back to the receive path. Useful for testing without a second node.
led_mode int 1 Controls the hardware LED visual behavior natively. 0=Off, 1=On (Default), 2=ActiveOnly.
channel str 'PSCAN_USB1' Channel identifier string for python-can compatibility. Not used for device selection.
can_filters list None List of filter dictionaries. See Message Filtering below.

Device selection priority: name > serial > auto-detect first device.


Sending Messages

Use the standard bus.send() method:

# Standard CAN frame (11-bit ID)
msg = can.Message(arbitration_id=0x123, data=[0x01, 0x02, 0x03], is_extended_id=False)
bus.send(msg)

# Extended CAN frame (29-bit ID)
msg = can.Message(arbitration_id=0x1ABCDEF0, data=[0xAA, 0xBB], is_extended_id=True)
bus.send(msg)

# Remote Transmit Request (RTR)
msg = can.Message(arbitration_id=0x200, is_remote_frame=True, dlc=8, is_extended_id=False)
bus.send(msg)

# With a custom timeout (seconds)
bus.send(msg, timeout=0.1)  # 100 ms timeout

Notes:

  • Default send timeout is 50 ms when no timeout argument is provided.
  • Sending in listen-only mode raises can.CanOperationError.
  • CAN FD frames are not supported and will raise NotImplementedError.

Receiving Messages

Use the standard bus.recv() method:

# Blocking receive with timeout
msg = bus.recv(timeout=1.0)  # Wait up to 1 second

# Non-blocking receive
msg = bus.recv(timeout=0)

# Blocking forever (use with caution)
msg = bus.recv()  # Blocks until a frame arrives

The returned can.Message object contains:

Field Description
timestamp Hardware timestamp in seconds (microsecond resolution from the adapter)
arbitration_id CAN ID (11-bit or 29-bit)
is_extended_id True if 29-bit extended frame
is_remote_frame True if RTR frame
is_error_frame True if error frame
dlc Data Length Code (0–8)
data Frame payload as bytearray
channel Channel info string (e.g. "PSCAN: P1P.IN:XFG:H001")

Event-Driven Receive with Notifier

For background message processing, use can.Notifier:

class MyListener(can.Listener):
    def on_message_received(self, msg):
        print(f"RX: 0x{msg.arbitration_id:03X}  [{msg.dlc}]  {msg.data.hex()}")

listener = MyListener()
notifier = can.Notifier(bus, [listener])

# Main thread is free to do other work...
import time
time.sleep(10)

notifier.stop()
bus.shutdown()

Message Filtering

Filters are applied in the compiled backend for high performance — frames that don't match are discarded before they ever reach Python.

# Accept only CAN ID 0x123 (standard frames)
bus.set_filters([{"can_id": 0x123, "can_mask": 0x7FF, "extended": False}])

# Accept IDs 0x200–0x2FF (standard frames)
bus.set_filters([{"can_id": 0x200, "can_mask": 0x700, "extended": False}])

# Accept extended frames with ID 0x1ABCDE00–0x1ABCDEFF
bus.set_filters([{"can_id": 0x1ABCDE00, "can_mask": 0x1FFFFF00, "extended": True}])

# Multiple filters (frame passes if it matches ANY filter)
bus.set_filters([
    {"can_id": 0x100, "can_mask": 0x7FF, "extended": False},
    {"can_id": 0x200, "can_mask": 0x7FF, "extended": False},
])

# Clear all filters (accept everything)
bus.set_filters(None)
Filter Key Type Description
can_id int CAN ID to match
can_mask int Bitmask. A frame passes if (frame_id & can_mask) == (can_id & can_mask).
extended bool or omitted True = match only extended (29-bit) frames. False = match only standard (11-bit) frames. Omit to match both.

You can also set filters when constructing the bus:

bus = can.Bus(
    interface='pscan',
    bitrate=500000,
    can_filters=[{"can_id": 0x123, "can_mask": 0x7FF}]
)

Bus State & Error Monitoring

Reading the bus state

state = bus.state  # Returns can.BusState enum

if state == can.BusState.ACTIVE:
    print("Bus is Error Active (normal operation)")
elif state == can.BusState.PASSIVE:
    print("Bus is in Error Warning or Error Passive state")
elif state == can.BusState.ERROR:
    print("Bus-Off or Stopped")

Setting the bus state

bus.state = can.BusState.ACTIVE   # Go bus-on
bus.state = can.BusState.ERROR    # Go bus-off
bus.state = can.BusState.PASSIVE  # Switch to listen-only mode

Flushing the transmit buffer

Discard all pending outgoing messages:

bus.flush_tx_buffer()

Shutdown

Always shut down the bus when you are done to release the USB device:

bus.shutdown()

The bus can also be used as a context manager:

with can.Bus(interface='pscan', bitrate=500000) as bus:
    bus.send(can.Message(arbitration_id=0x123, data=[1, 2, 3]))
    msg = bus.recv(timeout=1.0)
# Bus is automatically shut down here

PSCAN-Specific Functions

The following methods are PSCAN-specific extensions that go beyond the standard python-can API.

Listing connected devices

Enumerate all connected PSCAN adapters and their serial numbers before opening a bus:

from pscan_pythoncan import PscanBus

serials = PscanBus.list_pscan_devices()
print(f"Connected PSCAN devices: {serials}")
# Example output: ['P1P.IN:XFG:H001', 'P2P.IN:ABC:D002']

This is also available through the standard python-can device discovery:

configs = can.detect_available_configs(interfaces=['pscan'])
print(configs)
# [{'interface': 'pscan', 'channel': 'PSCAN_USB1', 'serial': 'P1P.IN:XFG:H001'}, ...]

Querying device information

Read the hardware version, firmware version, and number of CAN channels from an open adapter:

bus = can.Bus(interface='pscan', bitrate=500000)

hw_version, fw_version, channel_count = bus.get_device_info()
print(f"Hardware : {hw_version}")
print(f"Firmware : {fw_version}")
print(f"Channels : {channel_count}")

Accessing Native Properties Directly

pscan-pythoncan exposes advanced parameters natively out of the classic CAN hardware over Python properties. Note that bus.name and integers update the physical EEPROM arrays seamlessly!

# Read or Overwrite the internal hardware short-name via USB
print(f"Current Name: {bus.name}")
bus.name = "MyBMS"

# Dynamically change the native physical indicator LED mode
bus.led_mode = 2  # Set to ActiveOnly (blinks on traffic)
bus.led_mode = 1  # Standard On

# Instantly pull real physical adapter bounds
caps = bus.capabilities
print(f"Clock Frequency: {caps['clock_freq']} Hz")
print(f"TX Buffer Elements: {caps['tx_data_buffer_size']}")

Logging & Debugging

The library uses Python's standard logging module under the logger name can.pscan:

import logging
logging.basicConfig(level=logging.DEBUG)

Complete Example

import can
import time
from pscan_pythoncan import PscanBus

# --- Device Discovery ---
devices = PscanBus.list_pscan_devices()
print(f"Found {len(devices)} PSCAN adapter(s): {devices}")

if not devices:
    print("No PSCAN devices found.")
    exit(1)

# --- Open bus by serial number ---
with can.Bus(interface='pscan', serial=devices[0], bitrate=500000) as bus:

    # Print adapter info
    hw, fw, ch = bus.get_device_info()
    print(f"Adapter: HW={hw}  FW={fw}  Channels={ch}")

    # Set up a filter to accept only IDs 0x100–0x1FF
    bus.set_filters([{"can_id": 0x100, "can_mask": 0x700, "extended": False}])

    # Send a frame
    tx_msg = can.Message(
        arbitration_id=0x150,
        data=[0xDE, 0xAD, 0xBE, 0xEF],
        is_extended_id=False
    )
    bus.send(tx_msg)
    print(f"TX: {tx_msg}")

    # Receive frames for 5 seconds
    end_time = time.time() + 5
    count = 0
    while time.time() < end_time:
        msg = bus.recv(timeout=0.5)
        if msg is not None:
            count += 1
            print(f"RX: 0x{msg.arbitration_id:03X}  [{msg.dlc}]  {msg.data.hex()}")

    print(f"Received {count} messages in 5 seconds.")

# Bus is automatically shut down by the context manager


API Reference

PscanBus (extends can.BusABC)

Constructor

PscanBus(channel, serial, name, bitrate, sample_point, listen_only, loopback, can_filters)

Standard python-can methods

Method Description
send(msg, timeout=None) Transmit a can.Message. Default timeout: 50 ms.
recv(timeout=None) Receive a can.Message. Returns None on timeout.
set_filters(filters) Set message acceptance filters (applied in native code).
shutdown() Go bus-off, release the USB device, and clean up.
flush_tx_buffer() Discard all pending messages in the transmit queue.
state (property) Read or set the bus state (can.BusState.ACTIVE / PASSIVE / ERROR).

PSCAN-specific methods

Method Description
PscanBus.list_pscan_devices() Static. Returns List[str] of serial numbers for all connected PSCAN adapters.
get_device_info() Returns (hw_version: str, fw_version: str, channel_count: int) for the open adapter.
name (property) Read or set the 15-character physical name tag stored on the device ROM.
led_mode (property) Read or set the physical LED mode (0=Off, 1=On, 2=ActiveOnly).
capabilities (property) Read a Python Dictionary containing structural hardware lengths and frequency mapping natively parsed by the backend extension.

Feature Compatibility

Supported python-can features

Feature Status Notes
can.Bus() instantiation ✅ Supported Via interface='pscan'
bus.send() ✅ Supported Standard (11-bit) and Extended (29-bit) CAN frames
bus.recv() ✅ Supported With hardware timestamps (microsecond resolution)
Remote Transmit Request (RTR) ✅ Supported Send and receive RTR frames
Error Frame detection (RX) ✅ Supported Error frames are flagged in received messages
bus.set_filters() ✅ Supported High-performance filtering in native code, not pure Python
bus.state property ✅ Supported Read and write; maps to Error Active / Warning / Passive / Bus-Off
bus.flush_tx_buffer() ✅ Supported Clears the hardware transmit queue
bus.shutdown() ✅ Supported Goes bus-off and releases the USB device
Context manager (with) ✅ Supported with can.Bus(...) as bus: auto-shuts down on exit
Iterator protocol ✅ Supported for msg in bus: works (inherited from BusABC)
can.Notifier ✅ Supported Background receive thread with listener callbacks
can.Logger / can.Printer ✅ Supported All standard python-can listeners work
can.detect_available_configs() ✅ Supported Returns list of connected PSCAN adapters with serial numbers
Listen-only mode ✅ Supported listen_only=True — no TX, no ACK on the bus
Loopback mode ✅ Supported loopback=True — TX frames echoed to RX
Periodic send (bus.send_periodic()) ✅ Supported High-precision timing via dedicated OS threads. Bypasses Python's GIL for ~1μs jitter.
Multi-threading ✅ Supported Safe for use with can.Notifier, background workers, and concurrent send/recv across Python threads.

Currently unsupported python-can features

The following standard python-can features are not yet implemented in this version. Attempting to use them will either raise an exception or fall back to default BusABC behavior.

Feature Status Details
CAN FD ❌ Blocked The PSCAN-USB hardware currently supports Classical CAN only. bus strictly aborts fd=True/bitrates > 1Mbps/msg.is_fd directly returning NotImplementedError via explicit Python exceptions saving devs from silent execution.
Multi-channel support ❌ Not supported All operations are hardcoded to CAN channel 0. If your PSCAN adapter has multiple CAN channels, only the first channel is accessible. To use a second channel, open a separate can.Bus() instance with a different adapter.
Data bitrate / bitrate switch (BRS) ❌ Blocked CAN FD bitrate switching triggers native safety protocols enforcing Classic architecture bounds. Explicitly throws NotImplementedError("CAN FD and Bitrate Switching (BRS) are not yet supported").
Hardware-level bus statistics ❌ Not available There is no get_bus_statistics() method. You can read the bus state and error counters via bus.state and the raw bus._dev.get_bus_state() call (which returns state, rx_error_count, tx_error_count), but aggregate statistics like bus load, messages-per-second, or frame counts must be implemented in your application using a can.Listener.
Hardware-level message filtering ⚠️ Software only Filters set via bus.set_filters() are applied in the native extension (software filtering) before frames reach Python. They are not pushed down to the CAN controller hardware. All frames are still received via USB and filtered in software on the host.

Note: Software filtering runs natively and easily handles 100% bus loads without dropping frames, completely transparent to your Python application.


License

See the project repository for license details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

pscan_pythoncan-0.1.8-cp314-cp314t-win_arm64.whl (298.7 kB view details)

Uploaded CPython 3.14tWindows ARM64

pscan_pythoncan-0.1.8-cp313-cp313t-win_arm64.whl (300.7 kB view details)

Uploaded CPython 3.13tWindows ARM64

pscan_pythoncan-0.1.8-cp38-abi3-win_arm64.whl (303.8 kB view details)

Uploaded CPython 3.8+Windows ARM64

pscan_pythoncan-0.1.8-cp38-abi3-win_amd64.whl (323.9 kB view details)

Uploaded CPython 3.8+Windows x86-64

pscan_pythoncan-0.1.8-cp38-abi3-win32.whl (300.3 kB view details)

Uploaded CPython 3.8+Windows x86

pscan_pythoncan-0.1.8-cp38-abi3-macosx_11_0_arm64.whl (281.1 kB view details)

Uploaded CPython 3.8+macOS 11.0+ ARM64

pscan_pythoncan-0.1.8-cp38-abi3-macosx_10_12_x86_64.whl (300.5 kB view details)

Uploaded CPython 3.8+macOS 10.12+ x86-64

File details

Details for the file pscan_pythoncan-0.1.8-cp314-cp314t-win_arm64.whl.

File metadata

File hashes

Hashes for pscan_pythoncan-0.1.8-cp314-cp314t-win_arm64.whl
Algorithm Hash digest
SHA256 f0797c3fc133726a6e5ad46c480ad509671c39b1346a9a4002f2311d4b5b8b25
MD5 7f0af38ce7e74190a1563a7acf7571e0
BLAKE2b-256 a4b03833a6dc4fb9f9aa06e10950d8e75c515e9a157e8e06c4cb77ef5dcee06a

See more details on using hashes here.

File details

Details for the file pscan_pythoncan-0.1.8-cp313-cp313t-win_arm64.whl.

File metadata

File hashes

Hashes for pscan_pythoncan-0.1.8-cp313-cp313t-win_arm64.whl
Algorithm Hash digest
SHA256 2b6dd88f5faeef60416f6604f06fc68ed4a4c5c26951528f02fd7c7a47ab7b95
MD5 1662fe3f5ce437b6ab3d7ffec034bf57
BLAKE2b-256 419c5b496954c3c37a4dee1f8f9a7a4fa4fd973a0a24e3183e4ff4792c156294

See more details on using hashes here.

File details

Details for the file pscan_pythoncan-0.1.8-cp38-abi3-win_arm64.whl.

File metadata

File hashes

Hashes for pscan_pythoncan-0.1.8-cp38-abi3-win_arm64.whl
Algorithm Hash digest
SHA256 4a639d42745ad2de46702ddb151d27878c0ef43034293a01e732a5388674c2f5
MD5 078797510caab76603da98cf31dc80e0
BLAKE2b-256 c9c3eed04d71d4708fcec3c509f9ee42bd57be026d02193444a422b9e212fbbb

See more details on using hashes here.

File details

Details for the file pscan_pythoncan-0.1.8-cp38-abi3-win_amd64.whl.

File metadata

File hashes

Hashes for pscan_pythoncan-0.1.8-cp38-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 f2e6e5483005402a71e8993a374d355ca6c9282becf05c29ec3708b880ac1f0a
MD5 2f4710e82ee70b161287e9e78017522a
BLAKE2b-256 65d4b99dcc36b050bae7208f187cf7fa1fc2af307d6e75839a8d2ac85427d85e

See more details on using hashes here.

File details

Details for the file pscan_pythoncan-0.1.8-cp38-abi3-win32.whl.

File metadata

File hashes

Hashes for pscan_pythoncan-0.1.8-cp38-abi3-win32.whl
Algorithm Hash digest
SHA256 2ec7ea99a5f842aca23d6368db92b9fae4165068c367f419c3ae1ecf689bee75
MD5 486a88fd4d9043a44b302b9799438eb0
BLAKE2b-256 535e79d02042e00d25ec5d859c0bd7b072f065b3be3fc9fbe845d4ce46674b0d

See more details on using hashes here.

File details

Details for the file pscan_pythoncan-0.1.8-cp38-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for pscan_pythoncan-0.1.8-cp38-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 4ba2535a3f85760cbb91bad0e21500e14c54de1229ed4660fe6c7fa5e429b5b5
MD5 355cb6f42450264d2ea7d271fc25fe1b
BLAKE2b-256 4e4269e4956ae9bce76c7b205d92bff1b29895e5a834dffd625597715a5d320e

See more details on using hashes here.

File details

Details for the file pscan_pythoncan-0.1.8-cp38-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for pscan_pythoncan-0.1.8-cp38-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 05e4f3dca83a8a4df48170a4ebeada725e1b6a474aaba2b7b4a637ee401861c2
MD5 834b70fb2bac5bb79c16c7e0e2d5e58e
BLAKE2b-256 1a7e07e4cee26b3adbc5e2db41512fc3700e8d77207db658ee0c58dced3aa652

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page