Skip to main content

CoDrive

Simple Python API for Cosyne motor controllers over CAN FD.

The controller speaks CoLink, a simplified CiA 402 profile. codrive handles the bus, the frame coding and the node addressing, so you work with drives instead of COB-IDs.

Install

pip install codrive

Connect to drive

from codrive import Link

# Auto-connect to the shipped USB adapter
link = Link()
link.connect()

# Add drive with ID 1
drive = link.add_drive(node_id=1)

# Print state name
print(drive.state.name)

# Disconnect from bus
link.disconnect()

connect() opens the shipped adapter and configures it automatically. add_drive waits for the drive to answer, so an absent node raises TimeoutError.

In one scope, a with block connects and disconnects for you:

with Link() as link:
    drive = link.add_drive(1)

Other CAN FD adapters

Pass any CAN FD capable python-can bus to Link(). Configure it for the controller's bus yourself as SAE J2284-5: 500 kbit/s arbitration at an 80 % sample point, 5 Mbit/s data at a 75 % sample point.

Using a PEAK PCAN-USB FD adapter:

import can
from codrive import Link

timing = can.BitTimingFd(
    f_clock=80_000_000,
    nom_brp=1, nom_tseg1=127, nom_tseg2=32, nom_sjw=32,
    data_brp=1, data_tseg1=11, data_tseg2=4, data_sjw=4,
)
bus = can.Bus(interface="pcan", channel="PCAN_USBBUS1", timing=timing)

with Link(bus) as link:
    drive = link.add_drive(node_id=1)
    print(drive.state.name)

bus.shutdown()

A bus you open stays yours — the link leaves it open on disconnect, so close it yourself.

Move

Enable and set profile

Set the ramp limits once, enable, then command a mode. Each mode has its own call, so the units are visible at the call site:

drive.set_profile(velocity=800.0, acceleration=4000.0, torque_slope=5.0)
drive.enable()

enable() sets no mode, and disable(), quick_stop() and reset_fault() clear the mode and setpoints. An enable therefore never starts motion by itself: after a stop or a fault the drive comes back up idle. The set_profile() limits are kept, since a limit cannot produce motion on its own.

Profile position mode

# Move to 1 rad using the velocity and acceleration from set_profile()
drive.move_to(1.0)
drive.wait_for_target()

# Move to 2 rad using the specified velocity and acceleration
drive.move_to(2.0, velocity=200.0, acceleration=1000.0)
drive.wait_for_target()

# Start position control without movement
current_position = drive.position
drive.move_to(current_position)

Profile velocity mode

# Run at 300 rad/s using the acceleration from set_profile()
drive.run_at(300.0)
drive.wait_for_target()

# Run at 50 rad/s using the specified acceleration
drive.run_at(50.0, acceleration=1000.0)
drive.wait_for_target()

Profile torque mode

# Set drive torque to 0.2 Nm using the torque slope from set_profile()
drive.apply_torque(0.2)
drive.wait_for_target()

# Set drive torque to 0.0 Nm using the specified torque slope
drive.apply_torque(0.0, slope=2.0)
drive.wait_for_target()

Stop and disable

# Standard method
drive.run_at(0.0)
drive.wait_for_target()
drive.disable()

# Also supported
drive.quick_stop()
drive.wait_for_target()
drive.disable()

wait_for_target() means the profile generator reached the commanded value, not that the axis did — there is no following-error term, so it can return while the machine is still turning. Add your own dwell before cutting torque.

Important: Do not call drive.disable() while the drive is spinning!

Record and plot

Once the drive has answered, its latest status is available as

print(drive.position)       # rad
print(drive.velocity)       # rad/s
print(drive.torque)         # Nm
print(drive.state.name)     # e.g. OPERATION_ENABLED
status = drive.status       # for all status information

The received status can be recorded using

rec = link.record()

drive.move_to(1.0)
drive.wait_for_target()

rec.stop()
rec.plot()

Start and stop can sit in different cells or functions. Inside one scope, use a with block:

with link.record() as rec:
    drive.move_to(1.0)
    drive.wait_for_target()

Read the columns yourself for anything beyond plot():

import matplotlib.pyplot as plt

samples = rec.samples()
plt.plot(samples.time, samples.position)

Faults

A fault ends the motion directly. reset_fault() only clears the fault if its cause is gone; while it persists the drive stays in FAULT and the call raises TimeoutError.

from codrive import DriveState

if drive.state is DriveState.FAULT:
    print(repr(drive.error))   # e.g. <ErrorCode.DC_LINK_UNDERVOLTAGE: 12832>
    drive.reset_fault()

drive.error returns ErrorCode.NONE when there is no fault, and None for a code codrive does not name — drive.status.error_code still holds the raw number. Both are falsy, so compare against ErrorCode.NONE rather than testing for truth.

Multiple drives

All drives share one receive task, dispatched by node id:

with Link() as link:
    axis1 = link.add_drive(1)
    axis2 = link.add_drive(2)

    for axis in (axis1, axis2):
        axis.set_profile(velocity=800.0, acceleration=4000.0)
        axis.enable()

    with link.record() as rec:
        axis1.move_to(1.57)         # both axes move at once
        axis2.move_to(-1.57)
        axis1.wait_for_target()
        axis2.wait_for_target()

    axis1.disable()
    axis2.disable()

rec.plot()       # every node, one colour each
rec.plot(axis1)  # one drive, by object or node id

Status

Early, and the API may still change.

License

Apache 2.0

Metadata

Release files for codrive 0.2.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for codrive 0.2.1
File Size Uploaded Provenance Actions
codrive-0.2.1.tar.gz 15.2 kB Without provenance Details for codrive-0.2.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for codrive 0.2.1
File Interpreter ABI Platform Actions
codrive-0.2.1-py3-none-any.whl Python 3 none any Details for codrive-0.2.1-py3-none-any.whl

Total release size: 33.1 kB

Release files / codrive-0.2.1.tar.gz

Download URL codrive-0.2.1.tar.gz
Size 15.2 kB
Tags Source
SHA-256 checksum
How to use checksums
2591116420b79805f0506805595f841de5783a84e615030bba2cdcb1729890b7
BLAKE2b-256 checksum
How to use checksums
2cdde1c01f4e414845af0be13892c677b2737b89955f31c1f988df05bdab3f18
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.6

Release files / codrive-0.2.1-py3-none-any.whl

Download URL codrive-0.2.1-py3-none-any.whl
Size 17.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4cb2e8629819632aac148a0adc18c4294b9d79268b81fbfdcbf69530128f464e
BLAKE2b-256 checksum
How to use checksums
0ba6468b5db2c14634e669f3546f6e387f2b3be9a6151bb212d982b4e7be5c23
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.6

Security for codrive 0.2.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.2.1 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page