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.9-cp314-cp314t-win_arm64.whl (321.4 kB view details)

Uploaded CPython 3.14tWindows ARM64

pscan_pythoncan-0.1.9-cp313-cp313t-win_arm64.whl (324.4 kB view details)

Uploaded CPython 3.13tWindows ARM64

pscan_pythoncan-0.1.9-cp38-abi3-win_arm64.whl (326.3 kB view details)

Uploaded CPython 3.8+Windows ARM64

pscan_pythoncan-0.1.9-cp38-abi3-win_amd64.whl (346.1 kB view details)

Uploaded CPython 3.8+Windows x86-64

pscan_pythoncan-0.1.9-cp38-abi3-win32.whl (322.1 kB view details)

Uploaded CPython 3.8+Windows x86

pscan_pythoncan-0.1.9-cp38-abi3-macosx_11_0_arm64.whl (302.6 kB view details)

Uploaded CPython 3.8+macOS 11.0+ ARM64

pscan_pythoncan-0.1.9-cp38-abi3-macosx_10_12_x86_64.whl (324.0 kB view details)

Uploaded CPython 3.8+macOS 10.12+ x86-64

File details

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

File metadata

File hashes

Hashes for pscan_pythoncan-0.1.9-cp314-cp314t-win_arm64.whl
Algorithm Hash digest
SHA256 9c1ecac5f2edb4789bf01a74a3f1380161de578d09f6ba588961cf560dc4e979
MD5 468852bfcfb0fd945e40b6528cc42c58
BLAKE2b-256 0ecd5284a6a35971673e208493e93a54e30befae2aaa7990a56f0481a5d97cf2

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for pscan_pythoncan-0.1.9-cp313-cp313t-win_arm64.whl
Algorithm Hash digest
SHA256 7a2edd3084f8648cec38ae4437bfe80b905dd53f6e528c42fb6b4a92ebfd5ac7
MD5 249b88dcc6f4fbd3d3c01435d0c12ce5
BLAKE2b-256 d05ae1e59bc237477a15b5cfd31a99ec0c1ab6eafbc6b02e66acc7c1d2dbbbbc

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for pscan_pythoncan-0.1.9-cp38-abi3-win_arm64.whl
Algorithm Hash digest
SHA256 6e80fd1910cd0fe6468a881e3100ff33c6bc3663bcba7d7d79878165b80653d6
MD5 e6ce45bf5b627685581b29950eda6878
BLAKE2b-256 61b34e3b638d0b076aa9025d367c9e4f16ca87deb5aedf36c0d1bab98791faec

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for pscan_pythoncan-0.1.9-cp38-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 79b2e3221c5a33b24bdc6166fe48cbb9b10f9a911a8c63992bb51cd803e95cd1
MD5 a2595c428de148f01af1a6e46d593239
BLAKE2b-256 670f03aa96444547cd1684085f3517e873c762712b8f697812e5e10eafaa0a2a

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for pscan_pythoncan-0.1.9-cp38-abi3-win32.whl
Algorithm Hash digest
SHA256 b54894f2d542e65c644d7c1629ef1571d15ce5a16b389476d1bc989322363a2c
MD5 282780535fe61c54e417047a79a45538
BLAKE2b-256 40f423f52ca606ce1199c7489f21ee923b95028eba50611921fead3926c95e40

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for pscan_pythoncan-0.1.9-cp38-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 4c8096e698a649754f9c85f9599a201dd5158078fd4433f9ee2ccaed71afd426
MD5 695a3f7bff7e949ce38c75ee1c5e39ab
BLAKE2b-256 e44f9c9d987d1afda7cf9b356536732e9353155a4c7a2a7c2fe39890bfd1b62f

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for pscan_pythoncan-0.1.9-cp38-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 06c80cae2860d634160a589c1676a69600f179eeda3527a7fbb0ccececaf6b31
MD5 a9e8835f1747332bb614060231a49a06
BLAKE2b-256 3ab9eea6c984c971fab078063ee95c0debdade4f83c5d8cbe9ca3e72cd1081f3

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