leighleighleigh / JCAN

An easy-to-use SocketCAN library for Python and C++, built in Rust.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

JCAN

An easy-to-use SocketCAN library for Python and C++, built in Rust, using cxx-rs and pyo3.

Warning: I have never used Rust before and I don't know what I'm doing

Feature Status / TODO

  • Blocking send/receive in C++ (jcan.h) and Python (jcan)
  • aarch64 build for Jetson TX2
  • Replace maturin build system with manual scripts, or setuptools-rust
  • Rename of jcan_python to just jcan
  • Usage examples for C++ and Python
  • PyPi package release
  • Benchmark and speedtest against python-can (see utils/speedtest.sh, typically speedup is 200% with jcan)
  • Build an example of JCAN + ROS2 Foxy usage
  • Receive function for specific CAN IDs (e.g receive_with_id(id : u32))
  • Non-blocking receive functions, which return a list of buffered Frames
  • Implement asyncronous send/receive callback methods
  • Convenience methods for Frame building, e.g: setting specific bits in a byte, named IDs
  • TOML-based 'CAN device interface' files, which generate methods like set_motor_speed(0.5f) / set_heater(True), etc...

Installation

Download the latest builds from the Releases Page!

For python, it's as easy as...

pip install jcan

For C++, you'll need to download the latest build and add it to your include path manually - check the examples folder for cmake usage.

Examples

For local development, you can setup a virtual CAN interface with the vcan.sh script.
You can then use the can-utils package (apt install can-utils) to interact with the vcan0 interface, on the command line.

Python

Python example showing most of the JCAN features

#!/usr/bin/env python
import jcan
import time

if __name__ == "__main__":
    bus = jcan.Bus()

    # Set a filter, from list...
    # bus.set_id_filter([0x1A0,0x1A1,0x1A2,0x1A3])

    # .. or from a mask!
    bus.set_id_filter_mask(0x1A0, 0xFF0)

    # This is our callback function for new frames
    def on_frame_five(frame : jcan.Frame):
        print(f"FRAME 1A5: {frame.data}")

    def on_frame_d(frame : jcan.Frame):
        print(f"FRAME 1AD {frame.data}")
        # print(frame.data[0])

    bus.add_callback(0x1A5, on_frame_five)
    bus.add_callback(0x1AD, on_frame_d)

    bus.open("vcan0")

    while True:
        # The list of values will be cast to uint8's by JCAN library - so be careful to double check the values!
        # frameToSend = jcan.Frame(0x200, [time.time()%255, (time.time()*1000)%255])
        # print(f"Sending {frameToSend}")
        # bus.send(frameToSend)

        # Spin is required for our callbacks to be processed.
        # Make sure .spin is called from your MAIN THREAD
        bus.spin()

        # bus.spin is non-blocking if nothing is there - resulting in a 'busy' loop
        # this sleep is to prevent that. In your code, you will probably be doing more important things here!
        time.sleep(0.01)

C++ example showing Frame building and sending.

C++14

#include <stdint.h>
#include <stdio.h>
#include <vector>
#include "jcan/jcan.h"

using namespace leigh::jcan;

/* 
A basic example of sending and recieving CAN Frames with JCAN
*/

int main(int argc, char **argv) {
    // Build new Bus object, which is a unique pointer to a Bus object
    std::unique_ptr<Bus> bus = new_bus();

    // Set ID filter using a vector of allowed IDs we can receive
    // std::vector<uint32_t> allowed_ids = {0x100, 0x123, 0x456, 0x789};
    // bus->set_id_filter(allowed_ids);

    // We can also also set a mask of allowed IDs
    // The filter below will only accept frames who's ID is 0x1A#, where '#' can be anything.
    // Combinations of base+mask can be used to make a very flexible filter.. but it can get quite confusing, too!
    // The format used below, of the form 'base_id + part of ID we don't care about',
    // is a nice simple way to use this feature.
    bus->set_id_filter_mask(0x1A0,0xFF0);

    // Open the bus
    bus->open("vcan0");

    // Loop forever, sending frames and printing the ones we recieve
    unsigned char i = 0;

    while(true)
    {
        i++;
        Frame frameToSend = new_frame(0x200, {i,i/10,i/100,i%2,i%3,i%4,i%5,i*10});
        printf("Sending: %s...\n",frameToSend.to_string().c_str());
        bus->send(frameToSend);

        printf("Waiting for frame...\n");
        Frame frameReceived = bus->receive();
        printf("Received: %s\n", frameReceived.to_string().c_str());
    }

    return 0;
}

Lots more examples can be found in the examples folder!

C++ examples can be built with make, which uses the Makefile in each directory to run cmake for you.

Quirks / Known Bugs

  • A dedicated scripts-postbuild crate is used to move all the build-artifacts (libjcan.a, jcan.h, etc...) into /out/<profile>/<target>/jcan

Development

# [Required] Install Nix or NixOS, visit
https://nixos.org/download.html

# Get code
git clone https://github.com/leighleighleigh/JCAN

# To build the C++ libraries for your Nix system,
# in a development environment:
nix-shell

# To build cross-compiled C++ libraries for x86 and ARM64,
# and produce Python 3.8+ compatible wheels:
nix-shell cross-build.nix

# Build outputs, including python wheels, can then be found in the ./out/ folder

About

An easy-to-use SocketCAN library for Python and C++, built in Rust.

License:MIT License


Languages

Language:Rust 75.0%Language:Python 8.9%Language:C++ 6.7%Language:Nix 6.0%Language:Shell 3.3%Language:Just 0.1%