├── .github └── workflows │ ├── python.yml │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── LICENSE.txt ├── README.md ├── driver ├── .gitignore ├── Cargo.toml ├── MANIFEST.in ├── README.md ├── build-wheels.sh ├── build.rs ├── cantact │ └── __init__.py ├── examples │ ├── dump.py │ ├── send_one.py │ └── udsoncan.py ├── manylinux-build.sh ├── pyproject.toml ├── setup.py └── src │ ├── c │ ├── cantact.h │ └── mod.rs │ ├── device │ ├── gsusb.rs │ └── mod.rs │ ├── lib.rs │ └── python.rs └── src ├── cfg.rs ├── cli.yml ├── config.rs ├── dump.rs ├── helpers.rs ├── main.rs └── send.rs /.github/workflows/python.yml: -------------------------------------------------------------------------------- 1 | name: Python 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build_sdist: 7 | name: sdist Build 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v4 11 | - name: Install Build Deps 12 | run: sudo apt install libusb-1.0-0-dev 13 | - name: setup pkg-config 14 | run: export PKG_CONFIG_PATH=/usr/lib/pkgconfig:/usr/share/pkgconfig 15 | 16 | - name: Set up Python 17 | uses: actions/setup-python@v5 18 | with: 19 | python-version: 3.12 20 | - name: Install Latest Rust Nightly 21 | uses: dtolnay/rust-toolchain@master 22 | with: 23 | toolchain: nightly 24 | - name: Python Build 25 | run: | 26 | pip install -U setuptools wheel setuptools-rust twine 27 | cd driver 28 | python setup.py build sdist 29 | - name: Publish to TestPyPI 30 | env: 31 | TWINE_USERNAME: ericevenchick 32 | TWINE_PASSWORD: ${{ secrets.test_pypi_password }} 33 | TWINE_REPOSITORY: testpypi 34 | run: | 35 | python -m twine upload --skip-existing driver/dist/* 36 | - name: Publish to PyPI 37 | if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') 38 | env: 39 | TWINE_USERNAME: ericevenchick 40 | TWINE_PASSWORD: ${{ secrets.test_pypi_password }} 41 | run: | 42 | python -m twine upload --skip-existing driver/dist/* 43 | build_manylinux: 44 | name: manylinux2014 Build 45 | runs-on: ubuntu-latest 46 | strategy: 47 | matrix: 48 | python-version: [3.8, 3.9, "3.10", 3.11, 3.12] 49 | steps: 50 | - uses: actions/checkout@v4 51 | - name: Run manylinux Builds 52 | uses: docker://quay.io/pypa/manylinux2014_x86_64 53 | with: 54 | entrypoint: driver/build-wheels.sh 55 | - name: Upload Artifacts 56 | uses: actions/upload-artifact@v4 57 | with: 58 | name: dist-manylinux2014-${{ matrix.python-version }} 59 | path: driver/dist/ 60 | - name: Publish to TestPyPI 61 | env: 62 | TWINE_USERNAME: ericevenchick 63 | TWINE_PASSWORD: ${{ secrets.test_pypi_password }} 64 | TWINE_REPOSITORY: testpypi 65 | run: | 66 | pip install -U twine 67 | python -m twine upload --skip-existing driver/dist/* 68 | - name: Publish to PyPI 69 | if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') 70 | env: 71 | TWINE_USERNAME: ericevenchick 72 | TWINE_PASSWORD: ${{ secrets.pypi_password }} 73 | run: | 74 | python -m twine upload --skip-existing driver/dist/* 75 | build_windows: 76 | name: Windows Build 77 | runs-on: windows-latest 78 | strategy: 79 | matrix: 80 | python-version: [3.8, 3.9, "3.10", 3.11, 3.12] 81 | steps: 82 | - uses: actions/checkout@v4 83 | - name: Set up Python ${{ matrix.python-version }} 84 | uses: actions/setup-python@v5 85 | with: 86 | python-version: ${{ matrix.python-version }} 87 | - name: Install libusb 88 | run: | 89 | git clone https://github.com/Microsoft/vcpkg.git 90 | cd vcpkg 91 | ./bootstrap-vcpkg.bat 92 | ./vcpkg integrate install 93 | vcpkg install libusb:x64-windows-static-md 94 | - name: Install Latest Rust Nightly 95 | uses: dtolnay/rust-toolchain@master 96 | with: 97 | toolchain: nightly 98 | - name: Python Build 99 | run: | 100 | pip install -U setuptools wheel setuptools-rust twine 101 | cd driver 102 | python setup.py build bdist_wheel 103 | - name: Upload Artifacts 104 | uses: actions/upload-artifact@v4 105 | with: 106 | name: dist-windows-${{ matrix.python-version }} 107 | 108 | path: driver/dist 109 | - name: Publish to TestPyPI 110 | env: 111 | TWINE_USERNAME: ericevenchick 112 | TWINE_PASSWORD: ${{ secrets.test_pypi_password }} 113 | TWINE_REPOSITORY: testpypi 114 | run: | 115 | python -m twine upload --skip-existing driver/dist/* 116 | - name: Publish to PyPI 117 | if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') 118 | env: 119 | TWINE_USERNAME: ericevenchick 120 | TWINE_PASSWORD: ${{ secrets.pypi_password }} 121 | run: | 122 | python -m twine upload --skip-existing driver/dist/* 123 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | on: [push] 2 | 3 | name: Rust 4 | 5 | jobs: 6 | build: 7 | name: Build 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v4 11 | - name: Install Build Deps 12 | run: sudo apt install libusb-1.0-0-dev 13 | - name: setup pkg-config 14 | run: export PKG_CONFIG_PATH=/usr/lib/pkgconfig:/usr/share/pkgconfig 15 | - name: Install Latest Nightly 16 | uses: dtolnay/rust-toolchain@master 17 | with: 18 | toolchain: nightly 19 | - name: Run Cargo Build 20 | run: cargo build --release --all-features 21 | clippy_check: 22 | name: Clippy Check 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v4 26 | - name: Install Build Deps 27 | run: sudo apt install libusb-1.0-0-dev 28 | - name: setup pkg-config 29 | run: export PKG_CONFIG_PATH=/usr/lib/pkgconfig:/usr/share/pkgconfig 30 | - uses: dtolnay/rust-toolchain@master 31 | with: 32 | toolchain: nightly 33 | components: clippy 34 | - uses: giraffate/clippy-action@v1 35 | with: 36 | github_token: ${{ secrets.GITHUB_TOKEN }} 37 | reporter: 'github-pr-review' 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | *.dll 5 | *.lib 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cantact" 3 | version = "0.1.3" 4 | authors = ["Eric Evenchick "] 5 | license = "MIT" 6 | keywords = ["can", "usb"] 7 | categories = ["command-line-utilities", "hardware-support"] 8 | repository = "https://github.com/linklayer/cantact" 9 | homepage = "http://cantact.io/" 10 | readme = "README.md" 11 | description = "CLI for CANtact Controller Area Network tools." 12 | 13 | edition = "2018" 14 | default-run="can" 15 | 16 | [workspace] 17 | members = ['driver'] 18 | 19 | [[bin]] 20 | name = "can" 21 | path = "src/main.rs" 22 | 23 | [dependencies] 24 | cantact-driver = {path = "driver", version = "0.1.2"} 25 | ctrlc = "3.1.4" 26 | clap = { version = "2.33.3", features = ["yaml"]} 27 | toml = "0.5.6" 28 | serde = { version = "1.0", features = ["derive"]} 29 | app_dirs = "1.2.1" 30 | log = "0.4.8" 31 | simplelog = "0.8.0" 32 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2020 Linklayer Labs 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | driver/README.md -------------------------------------------------------------------------------- /driver/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | py-env 3 | 4 | # python build artifacts 5 | *.so 6 | *.egg-info/ 7 | dist/ 8 | build/ 9 | __pycache__/ 10 | *.egg-link 11 | -------------------------------------------------------------------------------- /driver/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cantact-driver" 3 | version = "0.1.3" 4 | authors = ["Eric Evenchick "] 5 | license = "MIT" 6 | keywords = ["can", "usb", "driver"] 7 | categories = ["command-line-utilities", "hardware-support"] 8 | repository = "https://github.com/linklayer/cantact" 9 | homepage = "http://cantact.io/" 10 | readme = "../README.md" 11 | description = "Driver and API for CANtact Controller Area Network tools." 12 | 13 | edition = "2018" 14 | build = "build.rs" 15 | 16 | [lib] 17 | name = "cantact" 18 | crate-type = ["cdylib", "rlib"] 19 | 20 | [features] 21 | python = ["pyo3"] 22 | 23 | [dependencies] 24 | libusb1-sys = {version = "0.3" } 25 | libc = "0.2.71" 26 | crossbeam-channel = "0.4" 27 | serde = { version = "1.0", features = ["derive"]} 28 | pyo3 = { version = "0.10.1", features = ["extension-module"], optional = true} 29 | -------------------------------------------------------------------------------- /driver/MANIFEST.in: -------------------------------------------------------------------------------- 1 | include Cargo.toml 2 | recursive-include src * 3 | include build.rs 4 | -------------------------------------------------------------------------------- /driver/README.md: -------------------------------------------------------------------------------- 1 | # CANtact 2 | [![crates.io](https://img.shields.io/crates/v/cantact?label=cantact)](https://crates.io/crates/cantact) 3 | [![crates.io](https://img.shields.io/crates/v/cantact-driver?label=cantact-driver)](https://crates.io/crates/cantact-driver) 4 | [![PyPI](https://img.shields.io/pypi/v/cantact)](https://pypi.org/project/cantact/) 5 | [![docs.rs](https://docs.rs/cantact-driver/badge.svg)](https://docs.rs/cantact-driver/) 6 | ![Rust Build](https://github.com/linklayer/cantact/workflows/Rust/badge.svg) 7 | ![Python Build](https://github.com/linklayer/cantact/workflows/Python/badge.svg) 8 | 9 | Software support for CANtact devices. Includes a driver (see `driver/`), APIs, and a cross-platform command line interface. 10 | 11 | ## Getting a Device 12 | 13 | CANtact Pro is currently a pre-launch project on CrowdSupply. You can subscribe on the [product page](https://www.crowdsupply.com/linklayer-labs/cantact-pro) 14 | to get updates about the hardware release. 15 | 16 | This tool should work fine with other CANtact/gs_usb compatible devices such as CANable. 17 | 18 | ## Installing 19 | 20 | The CLI and driver are built using `cargo`, which can be installed using [rustup](https://rustup.rs/). 21 | 22 | Once `cargo` is installed, use it to build and install the `can` binary: 23 | 24 | ``` 25 | git clone https://github.com/linklayer/cantact 26 | cd cantact 27 | cargo install --path . 28 | ``` 29 | 30 | ### Setting udev Rules (Linux only) 31 | 32 | On Linux, only root can access the device by default. This results in a `DeviceNotFound` error when trying to access the device as a normal user. 33 | To allow access for all users, create a file at `/etc/udev/rules.d/99-cantact.rules` which contains: 34 | ``` 35 | SUBSYSTEM=="usb", ATTRS{idVendor}=="1d50", ATTRS{idProduct}=="606f", MODE="0666" 36 | ``` 37 | 38 | Then reload the udev rules: 39 | ``` 40 | sudo udevadm control --reload-rules 41 | sudo udevadm trigger 42 | ``` 43 | 44 | 45 | ## Command Line Interface 46 | 47 | The CLI is invoked using the `can` binary: 48 | 49 | ``` 50 | can help 51 | 52 | can 0.1.0 53 | Eric Evenchick 54 | Command line utilities for CANtact devices 55 | 56 | USAGE: 57 | can [FLAGS] [SUBCOMMAND] 58 | 59 | FLAGS: 60 | -h, --help Prints help information 61 | -v, --verbose Print verbose debugging information 62 | -V, --version Prints version information 63 | 64 | SUBCOMMANDS: 65 | cfg Set device configurations 66 | dump Receive and display CAN frames 67 | help Prints this message or the help of the given subcommand(s) 68 | send Send a single CAN frame 69 | ``` 70 | 71 | The `can cfg` command is used to set the bitrate and other device settings. Once set, other commands will use these options. 72 | 73 | For example, to set channels 0 and 1 to 500000 kbps, then dump all frames on all channels: 74 | 75 | ``` 76 | can cfg --channel 0 --bitrate 500000 77 | can cfg --channel 1 --bitrate 500000 78 | can dump 79 | ``` 80 | 81 | Use `can help [subcommand]` for additional documentation. 82 | 83 | ## Rust Support 84 | 85 | The driver can be used from Rust by installing the [`cantact-driver` crate](https://crates.io/crates/cantact-driver). 86 | Documentation for the crate can be found on [docs.rs](https://docs.rs/cantact-driver/). 87 | 88 | ## Python Support 89 | 90 | CANtact supports Python 3.5+ on Windows, macOS, and Linux. The Python modules are hosted on [PyPI](https://pypi.org/project/cantact/). 91 | Installation requires `pip 19.0+` (for manylinux2010 wheels). 92 | 93 | Python end-users should not use this repository directly. Instead, install Python support using `pip`: 94 | 95 | ``` 96 | python3 -m pip -U pip 97 | python3 -m pip install cantact 98 | ``` 99 | 100 | This will attempt to install a binary distribution for your system. If none exists, it will attempt to build 101 | from source. This requires nightly rust, which can be enabled by running `rustup default nightly` before 102 | installing. 103 | 104 | See the `examples/` folder for Python examples. [python-can](https://github.com/hardbyte/python-can/) supports 105 | CANtact, and is recommended over using the `cantact` module directly. To install CANtact, `python-can`, 106 | and run a test: 107 | 108 | ``` 109 | python3 -m pip install cantact git+https://github.com/ericevenchick/python-can@cantact 110 | can_logger.py -i cantact -c 0 -b 500000 111 | ``` 112 | 113 | ### Building Python Support 114 | 115 | Building Python support is only required if you want to make modifications to the `cantact` Python module, or if 116 | you are using a platform that does not have packaged support. 117 | 118 | Python support is implemented using [PyO3](https://github.com/PyO3/pyo3), and is gated by the `python` feature. 119 | Thanks to [rust-setuptools](https://github.com/PyO3/setuptools-rust), the `cantact` Python module can be built 120 | like any other Python module using `setuptools`. PyO3 requires nightly Rust, which can be configured using `rustup override`. 121 | 122 | ``` 123 | cd driver 124 | rustup override set nightly 125 | python setup.py build 126 | ``` 127 | 128 | Python builds for Windows, macOS, and manylinux are automated using [Github Actions](https://github.com/linklayer/cantact/actions?query=workflow%3APython). 129 | Tagged releases are automatically pushed to PyPI. 130 | 131 | ## C / C++ Support 132 | 133 | C / C++ support is provided by the driver. This is currently used to implement [BUSMASTER](https://rbei-etas.github.io/busmaster/) 134 | support on Windows. 135 | -------------------------------------------------------------------------------- /driver/build-wheels.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -ex 3 | 4 | yum install -y libusb-devel libusbx-devel 5 | 6 | curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain nightly -y 7 | export PATH="$HOME/.cargo/bin:$PATH" 8 | 9 | cd driver/ 10 | 11 | for PYBIN in /opt/python/{cp38-cp38,cp39-cp39,cp310-cp310,cp311-cp311,cp312-cp312}/bin; do 12 | export PYTHON_SYS_EXECUTABLE="$PYBIN/python" 13 | 14 | "${PYBIN}/python" -m pip install -U setuptools wheel setuptools-rust 15 | "${PYBIN}/python" setup.py bdist_wheel 16 | done 17 | 18 | for whl in dist/*.whl; do 19 | auditwheel repair "$whl" -w dist/ 20 | done 21 | 22 | # remove non-manylinux wheels 23 | rm dist/*-linux*.whl 24 | -------------------------------------------------------------------------------- /driver/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!(r"cargo:rustc-link-search=C:\Program Files\Python38\libs") 3 | } 4 | -------------------------------------------------------------------------------- /driver/cantact/__init__.py: -------------------------------------------------------------------------------- 1 | from .cantact import * 2 | -------------------------------------------------------------------------------- /driver/examples/dump.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | 4 | """ 5 | Python CANtact dump example. 6 | 7 | A simple example of dumping frames to stdout using the cantact module. 8 | 9 | Note, most users will want to use the python-can package instead 10 | of direct access! See send_one.py for an example. 11 | """ 12 | 13 | import cantact 14 | 15 | # create the interface 16 | intf = cantact.Interface() 17 | 18 | # set the CAN bitrate 19 | intf.set_bitrate(0, 500000) 20 | 21 | # enable channel 0 22 | intf.set_enabled(0, True) 23 | 24 | # start the interface 25 | intf.start() 26 | 27 | while True: 28 | try: 29 | # wait for frame with 10 ms timeout 30 | f = intf.recv(10) 31 | if f != None: 32 | # received frame 33 | print(f) 34 | except KeyboardInterrupt: 35 | # ctrl-c pressed, close the interface 36 | intf.stop() 37 | break 38 | -------------------------------------------------------------------------------- /driver/examples/send_one.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | 4 | """ 5 | This example shows how sending a single message works using the python-can 6 | package with CANtact. 7 | """ 8 | 9 | from __future__ import print_function 10 | 11 | import can 12 | import cantact 13 | 14 | 15 | def send_one(): 16 | 17 | bus = can.interface.Bus( 18 | bustype="cantact", channel="0", bitrate=500000, monitor=False 19 | ) 20 | 21 | msg = can.Message( 22 | arbitration_id=0xC0FFEE, data=[0, 25, 0, 1, 3, 1, 4, 1], is_extended_id=True 23 | ) 24 | 25 | try: 26 | bus.send(msg) 27 | print("Message sent on {}".format(bus.channel_info)) 28 | except can.CanError: 29 | print("Message NOT sent") 30 | 31 | 32 | if __name__ == "__main__": 33 | send_one() 34 | -------------------------------------------------------------------------------- /driver/examples/udsoncan.py: -------------------------------------------------------------------------------- 1 | import can 2 | import isotp 3 | import udsoncan 4 | from udsoncan.connections import PythonIsoTpConnection 5 | from udsoncan.client import Client 6 | from udsoncan.exceptions import * 7 | from udsoncan.services import * 8 | 9 | udsoncan.setup_logging() 10 | 11 | bus = can.interface.Bus(bustype="cantact", channel="0", bitrate=500000) 12 | addr = isotp.Address( 13 | addressing_mode=isotp.AddressingMode.Normal_11bits, txid=0x123, rxid=0x456 14 | ) 15 | tp = isotp.CanStack(bus, address=addr) 16 | conn = PythonIsoTpConnection(tp) 17 | client = Client(conn) 18 | 19 | conn.open() 20 | client.ecu_reset(ECUReset.ResetType.hardReset) 21 | print("done") 22 | conn.close() 23 | -------------------------------------------------------------------------------- /driver/manylinux-build.sh: -------------------------------------------------------------------------------- 1 | sudo docker run --rm -v `pwd`:/io quay.io/pypa/manylinux2010_x86_64 /io/build-wheels.sh 2 | -------------------------------------------------------------------------------- /driver/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools", "wheel", "setuptools-rust"] 3 | -------------------------------------------------------------------------------- /driver/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | 4 | from setuptools import setup 5 | 6 | try: 7 | from setuptools_rust import RustExtension 8 | except ImportError: 9 | import subprocess 10 | 11 | errno = subprocess.call([sys.executable, "-m", "pip", "install", "setuptools-rust"]) 12 | if errno: 13 | print("Please install setuptools-rust package") 14 | raise SystemExit(errno) 15 | else: 16 | from setuptools_rust import RustExtension 17 | 18 | setup_requires = ["setuptools-rust>=0.10.1", "wheel"] 19 | install_requires = [] 20 | 21 | with open("README.md", "r") as fh: 22 | long_description = fh.read() 23 | 24 | setup( 25 | name="cantact", 26 | version="0.1.3", 27 | author="Eric Evenchick", 28 | author_email="eric@evenchick.com", 29 | description="Support for the CANtact Controller Area Network Devices", 30 | long_description=long_description, 31 | long_description_content_type="text/markdown", 32 | url="https://github.com/linklayer/cantact", 33 | classifiers=[ 34 | "License :: OSI Approved :: MIT License", 35 | "Development Status :: 3 - Alpha", 36 | "Intended Audience :: Developers", 37 | "Programming Language :: Python", 38 | "Programming Language :: Rust", 39 | "Operating System :: POSIX :: Linux", 40 | "Operating System :: MacOS :: MacOS X", 41 | "Operating System :: Microsoft :: Windows", 42 | "Topic :: System :: Hardware :: Hardware Drivers", 43 | "Topic :: System :: Networking", 44 | "Topic :: Software Development :: Embedded Systems", 45 | ], 46 | packages=["cantact"], 47 | rust_extensions=[RustExtension("cantact.cantact", features=["python"],)], 48 | install_requires=install_requires, 49 | setup_requires=setup_requires, 50 | python_requires=">=3.5", 51 | include_package_data=True, 52 | zip_safe=False, 53 | ) 54 | -------------------------------------------------------------------------------- /driver/src/c/cantact.h: -------------------------------------------------------------------------------- 1 | #ifndef CANTACT_H_ 2 | #define CANTACT_H_ 3 | 4 | #include 5 | 6 | typedef void* cantacthnd; 7 | 8 | struct CantactFrame { 9 | uint8_t channel; 10 | uint32_t id; 11 | uint8_t dlc; 12 | uint8_t data[8]; 13 | uint8_t ext; 14 | uint8_t fd; 15 | uint8_t loopback; 16 | uint8_t rtr; 17 | }; 18 | 19 | extern "C" { 20 | __declspec(dllimport) cantacthnd cantact_init(); 21 | __declspec(dllimport) int32_t cantact_deinit(cantacthnd hnd); 22 | 23 | __declspec(dllimport) int32_t cantact_open(cantacthnd hnd); 24 | __declspec(dllimport) int32_t cantact_close(cantacthnd hnd); 25 | 26 | __declspec(dllimport) int32_t cantact_set_rx_callback(cantacthnd hnd, void(__cdecl* callback)(CantactFrame* f)); 27 | 28 | __declspec(dllimport) int32_t cantact_start(cantacthnd hnd); 29 | __declspec(dllimport) int32_t cantact_stop(cantacthnd hnd); 30 | 31 | __declspec(dllimport) int32_t cantact_transmit(cantacthnd hnd, const struct CantactFrame f); 32 | 33 | __declspec(dllimport) int32_t cantact_set_bitrate(cantacthnd hnd, uint8_t channel, uint32_t bitrate); 34 | __declspec(dllimport) int32_t cantact_set_enabled(cantacthnd hnd, uint8_t channel, uint8_t enabled); 35 | __declspec(dllimport) int32_t cantact_set_monitor(cantacthnd hnd, uint8_t channel, uint8_t enabled); 36 | __declspec(dllimport) int32_t cantact_set_hw_loopback(cantacthnd hnd, uint8_t channel, uint8_t enabled); 37 | 38 | __declspec(dllimport) int32_t cantact_get_channel_count(cantacthnd hnd); 39 | } 40 | 41 | #endif -------------------------------------------------------------------------------- /driver/src/c/mod.rs: -------------------------------------------------------------------------------- 1 | //! Implementation of C/C++ bindings. 2 | //! 3 | //! All functions are unsafe since they dereference a context pointer 4 | //! provided from C. 5 | //! 6 | //! TODO: put a simple example here. 7 | //! 8 | 9 | #![allow(clippy::missing_safety_doc)] 10 | 11 | use crate::{Frame, Interface}; 12 | 13 | /// A CAN frame in a C representation 14 | #[repr(C)] 15 | pub struct CFrame { 16 | channel: u8, 17 | id: u32, 18 | dlc: u8, 19 | data: [u8; 64], 20 | // these types are boolean flags, but C FFI hates bools 21 | // use u8s instead: 1 = true, 0 = false 22 | ext: u8, 23 | fd: u8, 24 | brs: u8, 25 | esi: u8, 26 | loopback: u8, 27 | rtr: u8, 28 | err: u8, 29 | } 30 | impl CFrame { 31 | fn from_frame(f: Frame) -> CFrame { 32 | CFrame { 33 | channel: f.channel, 34 | id: f.can_id, 35 | dlc: f.can_dlc, 36 | data: f.data_as_array(), 37 | ext: if f.ext { 1 } else { 0 }, 38 | fd: if f.fd { 1 } else { 0 }, 39 | brs: if f.brs { 1 } else { 0 }, 40 | esi: if f.esi { 1 } else { 0 }, 41 | loopback: if f.loopback { 1 } else { 0 }, 42 | rtr: if f.rtr { 1 } else { 0 }, 43 | err: if f.err { 1 } else { 0 }, 44 | } 45 | } 46 | } 47 | 48 | /// Interface state. A pointer to this struct is provided when initializing the 49 | /// library. All other functions require a pointer to this struct as the first 50 | /// argument. 51 | #[repr(C)] 52 | pub struct CInterface { 53 | i: Option, 54 | c_rx_cb: Option, 55 | } 56 | 57 | /// Create a new CANtact interface, returning a pointer to the interface. 58 | /// This pointer must be provided as the first argument to all other calls in 59 | /// this library. 60 | /// 61 | /// If this function fails, it returns a null pointer (0). 62 | #[no_mangle] 63 | pub extern "C" fn cantact_init() -> *mut CInterface { 64 | Box::into_raw(Box::new(CInterface { 65 | i: None, 66 | c_rx_cb: None, 67 | })) 68 | } 69 | 70 | /// Clean up a CANtact interface. 71 | /// After calling, the pointer is no longer valid. 72 | #[no_mangle] 73 | pub unsafe extern "C" fn cantact_deinit(ptr: *mut CInterface) -> i32 { 74 | Box::from_raw(ptr); 75 | 0 76 | } 77 | 78 | /// Set the receive callback function. This function will be called when a 79 | /// frame is received. 80 | #[no_mangle] 81 | pub unsafe extern "C" fn cantact_set_rx_callback( 82 | ptr: *mut CInterface, 83 | cb: Option, 84 | ) -> i32 { 85 | let mut ci = &mut *ptr; 86 | ci.c_rx_cb = cb; 87 | 0 88 | } 89 | 90 | /// Open the device. This must be called before any interaction with the 91 | /// device (changing settings, starting communication). 92 | #[no_mangle] 93 | pub unsafe extern "C" fn cantact_open(ptr: *mut CInterface) -> i32 { 94 | let i = match Interface::new() { 95 | Ok(i) => i, 96 | Err(_) => return -1, 97 | }; 98 | let ci = &mut *ptr; 99 | ci.i = Some(i); 100 | 0 101 | } 102 | 103 | /// Close the device. After closing, no interaction with the device 104 | /// can be performed. 105 | #[no_mangle] 106 | pub unsafe extern "C" fn cantact_close(ptr: *mut CInterface) -> i32 { 107 | let mut ci = &mut *ptr; 108 | ci.i = None; 109 | 0 110 | } 111 | 112 | /// Start CAN communication. This will enable all configured CAN channels. 113 | /// 114 | /// This function starts a thread which will call the registered callback 115 | /// when a frame is received. 116 | #[no_mangle] 117 | pub unsafe extern "C" fn cantact_start(ptr: *mut CInterface) -> i32 { 118 | let ci = &mut *ptr; 119 | 120 | let cb = ci.c_rx_cb; 121 | match &mut ci.i { 122 | Some(i) => i 123 | .start(move |f: Frame| { 124 | match cb { 125 | None => {} 126 | Some(cb) => { 127 | cb(&CFrame::from_frame(f)); 128 | } 129 | }; 130 | }) 131 | .expect("failed to start device"), 132 | None => return -1, 133 | }; 134 | 0 135 | } 136 | 137 | /// Stop CAN communication. This will stop all configured CAN channels. 138 | #[no_mangle] 139 | pub unsafe extern "C" fn cantact_stop(ptr: *mut CInterface) -> i32 { 140 | let ci = &mut *ptr; 141 | match &mut ci.i { 142 | Some(i) => i.stop().expect("failed to stop device"), 143 | None => return -1, 144 | } 145 | 0 146 | } 147 | 148 | /// Transmit a frame. Can only be called if the device is running. 149 | #[no_mangle] 150 | pub unsafe extern "C" fn cantact_transmit(ptr: *mut CInterface, cf: CFrame) -> i32 { 151 | let ci = &mut *ptr; 152 | let f = Frame { 153 | channel: 0, //cf.channel, 154 | can_id: cf.id, 155 | can_dlc: cf.dlc, 156 | data: cf.data.to_vec(), 157 | ext: cf.ext > 0, 158 | fd: cf.fd > 0, 159 | brs: cf.brs > 0, 160 | esi: cf.esi > 0, 161 | loopback: false, 162 | rtr: cf.rtr > 0, 163 | err: cf.err > 0, 164 | timestamp: None, 165 | }; 166 | match &mut ci.i { 167 | Some(i) => i.send(f).expect("failed to transmit frame"), 168 | None => return -1, 169 | }; 170 | 0 171 | } 172 | 173 | /// Sets the bitrate for a chanel to the given value in bits per second. 174 | #[no_mangle] 175 | pub unsafe extern "C" fn cantact_set_bitrate( 176 | ptr: *mut CInterface, 177 | channel: u8, 178 | bitrate: u32, 179 | ) -> i32 { 180 | let ci = &mut *ptr; 181 | match &mut ci.i { 182 | Some(i) => i 183 | .set_bitrate(channel as usize, bitrate) 184 | .expect("failed to set bitrate"), 185 | None => return -1, 186 | } 187 | 0 188 | } 189 | 190 | /// Enable or disable a channel. 191 | #[no_mangle] 192 | pub unsafe extern "C" fn cantact_set_enabled( 193 | ptr: *mut CInterface, 194 | channel: u8, 195 | enabled: u8, 196 | ) -> i32 { 197 | let ci = &mut *ptr; 198 | match &mut ci.i { 199 | Some(i) => i 200 | .set_enabled(channel as usize, enabled > 0) 201 | .expect("failed to enable channel"), 202 | None => return -1, 203 | } 204 | 0 205 | } 206 | 207 | /// Enable or disable bus monitoring mode for a channel. When enabled, channel 208 | /// will not transmit frames or acknoweldgements. 209 | #[no_mangle] 210 | pub unsafe extern "C" fn cantact_set_monitor( 211 | ptr: *mut CInterface, 212 | channel: u8, 213 | enabled: u8, 214 | ) -> i32 { 215 | let ci = &mut *ptr; 216 | match &mut ci.i { 217 | Some(i) => i 218 | .set_monitor(channel as usize, enabled > 0) 219 | .expect("failed to set monitoring mode"), 220 | None => return -1, 221 | } 222 | 0 223 | } 224 | 225 | /// Enable or disable hardware loopback for a channel. This will cause sent 226 | /// frames to be received. This mode is mostly intended for device testing. 227 | #[no_mangle] 228 | pub unsafe extern "C" fn cantact_set_hw_loopback( 229 | ptr: *mut CInterface, 230 | channel: u8, 231 | enabled: u8, 232 | ) -> i32 { 233 | let ci = &mut *ptr; 234 | match &mut ci.i { 235 | Some(i) => i 236 | .set_loopback(channel as usize, enabled > 0) 237 | .expect("failed to enable channel"), 238 | None => return -1, 239 | } 240 | 0 241 | } 242 | 243 | /// Get the number of CAN channels the device has. 244 | /// 245 | /// Returns the number of channels or a negative error code on failure. 246 | #[no_mangle] 247 | pub unsafe extern "C" fn cantact_get_channel_count(ptr: *mut CInterface) -> i32 { 248 | let ci = &mut *ptr; 249 | match &mut ci.i { 250 | Some(i) => i.channels() as i32, 251 | None => -1, 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /driver/src/device/gsusb.rs: -------------------------------------------------------------------------------- 1 | //! Structure declarations for the GSUSB protocol 2 | #![allow(dead_code)] 3 | 4 | // can id is OR'd with flag when frame is extended 5 | pub(crate) const GSUSB_EXT_FLAG: u32 = 0x8000_0000; 6 | // can id is OR'd with flag when frame is RTR 7 | pub(crate) const GSUSB_RTR_FLAG: u32 = 0x4000_0000; 8 | // can id is OR'd with flag when frame is an error frame 9 | pub(crate) const GSUSB_ERR_FLAG: u32 = 0x2000_0000; 10 | // echo id for non-loopback frames 11 | pub(crate) const GSUSB_RX_ECHO_ID: u32 = 0xFFFF_FFFF; 12 | 13 | // device features bit map 14 | pub(crate) const GS_CAN_FEATURE_NORMAL: u32 = 0; 15 | pub(crate) const GS_CAN_FEATURE_LISTEN_ONLY: u32 = 1; 16 | pub(crate) const GS_CAN_FEATURE_LOOP_BACK: u32 = 1 << 1; 17 | pub(crate) const GS_CAN_FEATURE_TRIPLE_SAMPLE: u32 = 1 << 2; 18 | pub(crate) const GS_CAN_FEATURE_ONE_SHOT: u32 = 1 << 3; 19 | pub(crate) const GS_CAN_FEATURE_HW_TIMESTAMP: u32 = 1 << 4; 20 | pub(crate) const GS_CAN_FEATURE_IDENTIFY: u32 = 1 << 5; 21 | pub(crate) const GS_CAN_FEATURE_USER_ID: u32 = 1 << 6; 22 | pub(crate) const GS_CAN_FEATURE_PAD_PKTS_TO_MAX_PKT_SIZE: u32 = 1 << 7; 23 | pub(crate) const GS_CAN_FEATURE_FD: u32 = 1 << 8; 24 | 25 | // device mode bit map 26 | pub(crate) const GS_CAN_MODE_NORMAL: u32 = 0; 27 | pub(crate) const GS_CAN_MODE_LISTEN_ONLY: u32 = 1; 28 | pub(crate) const GS_CAN_MODE_LOOP_BACK: u32 = 1 << 1; 29 | pub(crate) const GS_CAN_MODE_TRIPLE_SAMPLE: u32 = 1 << 2; 30 | pub(crate) const GS_CAN_MODE_ONE_SHOT: u32 = 1 << 3; 31 | pub(crate) const GS_CAN_MODE_HW_TIMESTAMP: u32 = 1 << 4; 32 | pub(crate) const GS_CAN_MODE_PAD_PKTS_TO_MAX_PKT_SIZE: u32 = 1 << 7; 33 | pub(crate) const GS_CAN_MODE_FD: u32 = 1 << 8; 34 | 35 | // frame flags bit map 36 | pub(crate) const GS_CAN_FLAG_OVERFLOW: u8 = 1; 37 | pub(crate) const GS_CAN_FLAG_FD: u8 = 1 << 1; 38 | pub(crate) const GS_CAN_FLAG_BRS: u8 = 1 << 2; 39 | pub(crate) const GS_CAN_FLAG_ESI: u8 = 1 << 3; 40 | 41 | #[repr(u8)] 42 | #[derive(Debug)] 43 | pub(crate) enum UsbBreq { 44 | HostFormat = 0, 45 | BitTiming, 46 | Mode, 47 | Berr, 48 | BitTimingConsts, 49 | DeviceConfig, 50 | Timestamp, 51 | Identify, 52 | DataBitTiming, 53 | } 54 | 55 | #[repr(u8)] 56 | pub(crate) enum CanMode { 57 | Reset = 0, 58 | Start, 59 | } 60 | 61 | #[repr(u8)] 62 | pub(crate) enum CanState { 63 | ErrorActive = 0, 64 | ErrorWarning, 65 | ErrorPassive, 66 | BusOff, 67 | Stopped, 68 | Sleeping, 69 | } 70 | 71 | fn u32_from_le_bytes(bs: &[u8]) -> u32 { 72 | let arr: [u8; 4] = [bs[0], bs[1], bs[2], bs[3]]; 73 | u32::from_le_bytes(arr) 74 | } 75 | 76 | #[repr(C)] 77 | pub(crate) struct Mode { 78 | pub mode: u32, 79 | pub flags: u32, 80 | } 81 | impl Mode { 82 | pub(crate) fn to_le_bytes(&self) -> Vec { 83 | let mut data: Vec = vec![]; 84 | data.extend_from_slice(&self.mode.to_le_bytes()); 85 | data.extend_from_slice(&self.flags.to_le_bytes()); 86 | data 87 | } 88 | } 89 | 90 | #[repr(C)] 91 | #[derive(Debug)] 92 | pub(crate) struct BitTiming { 93 | pub prop_seg: u32, 94 | pub phase_seg1: u32, 95 | pub phase_seg2: u32, 96 | pub sjw: u32, 97 | pub brp: u32, 98 | } 99 | impl BitTiming { 100 | pub(crate) fn to_le_bytes(&self) -> Vec { 101 | let mut data: Vec = vec![]; 102 | data.extend_from_slice(&self.prop_seg.to_le_bytes()); 103 | data.extend_from_slice(&self.phase_seg1.to_le_bytes()); 104 | data.extend_from_slice(&self.phase_seg2.to_le_bytes()); 105 | data.extend_from_slice(&self.sjw.to_le_bytes()); 106 | data.extend_from_slice(&self.brp.to_le_bytes()); 107 | data 108 | } 109 | } 110 | 111 | #[derive(Debug)] 112 | #[repr(C)] 113 | pub(crate) struct BitTimingConsts { 114 | pub(crate) feature: u32, 115 | pub(crate) fclk_can: u32, 116 | tseg1_min: u32, 117 | tseg1_max: u32, 118 | tseg2_min: u32, 119 | tseg2_max: u32, 120 | sjw_max: u32, 121 | brp_min: u32, 122 | brp_max: u32, 123 | brp_inc: u32, 124 | } 125 | impl BitTimingConsts { 126 | pub(crate) fn from_le_bytes(bs: &[u8]) -> BitTimingConsts { 127 | BitTimingConsts { 128 | feature: u32_from_le_bytes(&bs[0..4]), 129 | fclk_can: u32_from_le_bytes(&bs[4..8]), 130 | tseg1_min: u32_from_le_bytes(&bs[8..12]), 131 | tseg1_max: u32_from_le_bytes(&bs[12..16]), 132 | tseg2_min: u32_from_le_bytes(&bs[16..20]), 133 | tseg2_max: u32_from_le_bytes(&bs[20..24]), 134 | sjw_max: u32_from_le_bytes(&bs[24..28]), 135 | brp_min: u32_from_le_bytes(&bs[28..32]), 136 | brp_max: u32_from_le_bytes(&bs[32..36]), 137 | brp_inc: u32_from_le_bytes(&bs[36..40]), 138 | } 139 | } 140 | } 141 | 142 | #[derive(Debug)] 143 | #[repr(C)] 144 | pub(crate) struct DeviceConfig { 145 | reserved1: u8, 146 | reserved2: u8, 147 | reserved3: u8, 148 | pub(crate) icount: u8, 149 | pub(crate) sw_version: u32, 150 | pub(crate) hw_version: u32, 151 | } 152 | impl DeviceConfig { 153 | pub(crate) fn from_le_bytes(bs: &[u8]) -> DeviceConfig { 154 | DeviceConfig { 155 | reserved1: bs[0], 156 | reserved2: bs[1], 157 | reserved3: bs[2], 158 | icount: bs[3], 159 | sw_version: u32_from_le_bytes(&bs[4..8]), 160 | hw_version: u32_from_le_bytes(&bs[8..12]), 161 | } 162 | } 163 | } 164 | 165 | #[repr(C)] 166 | pub(crate) struct HostFrame { 167 | pub echo_id: u32, 168 | pub can_id: u32, 169 | 170 | pub can_dlc: u8, 171 | pub channel: u8, 172 | pub flags: u8, 173 | pub reserved: u8, 174 | 175 | pub data: [u8; 64], 176 | } 177 | impl HostFrame { 178 | pub(crate) fn from_le_bytes(bs: &[u8]) -> HostFrame { 179 | let mut data: [u8; 64] = [0u8; 64]; 180 | // copy data bytes to array 181 | data[..(bs.len() - 12)].clone_from_slice(&bs[12..]); 182 | HostFrame { 183 | echo_id: u32_from_le_bytes(&bs[0..4]), 184 | can_id: u32_from_le_bytes(&bs[4..8]), 185 | can_dlc: bs[8], 186 | channel: bs[9], 187 | flags: bs[10], 188 | reserved: bs[11], 189 | data, 190 | } 191 | } 192 | pub(crate) fn to_le_bytes(&self) -> Vec { 193 | let mut data: Vec = vec![]; 194 | data.extend_from_slice(&self.echo_id.to_le_bytes()); 195 | data.extend_from_slice(&self.can_id.to_le_bytes()); 196 | data.push(self.can_dlc); 197 | data.push(self.channel); 198 | data.push(self.flags); 199 | data.push(self.reserved); 200 | if ((self.flags & GS_CAN_FLAG_FD) != 0 || self.can_dlc > 8) { 201 | data.extend_from_slice(&self.data); 202 | } else { // legacy gs_host_frame is limited to 8 bytes of data 203 | data.extend_from_slice(&self.data[..8]); 204 | } 205 | data 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /driver/src/device/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | use crossbeam_channel::{unbounded, Receiver, Sender, TryRecvError}; 3 | use libc::c_void; 4 | use libusb1_sys::constants::*; 5 | use libusb1_sys::*; 6 | use std::mem; 7 | use std::mem::size_of; 8 | use std::ptr; 9 | use std::sync::atomic::{AtomicBool, Ordering}; 10 | use std::sync::Arc; 11 | use std::sync::RwLock; 12 | use std::thread; 13 | 14 | pub mod gsusb; 15 | pub(crate) use gsusb::*; 16 | 17 | // CANtact USB VID / PID 18 | const USB_VID: u16 = 0x1d50; 19 | const USB_PID: u16 = 0x606f; 20 | 21 | // buffer size for control in/out transfers 22 | const CTRL_BUF_SIZE: usize = 64; 23 | // number of bulk in transfers 24 | const BULK_IN_TRANSFER_COUNT: usize = 32; 25 | // buffer size for bulk in transfer 26 | const BULK_IN_BUF_SIZE: usize = 76; 27 | // timeout for bulk in transfers 28 | const BULK_IN_TIMEOUT_MS: u32 = 5000; 29 | 30 | #[derive(Debug)] 31 | pub enum Error { 32 | Libusb(&'static str, i32), 33 | DeviceNotFound, 34 | TransferAllocFailed, 35 | InvalidControlResponse, 36 | } 37 | 38 | #[derive(Debug)] 39 | pub(crate) struct UsbContext { 40 | ctx: *mut libusb_context, 41 | } 42 | 43 | unsafe impl Send for UsbContext {} 44 | unsafe impl Sync for UsbContext {} 45 | 46 | impl UsbContext { 47 | pub(crate) fn new() -> UsbContext { 48 | let mut context = mem::MaybeUninit::<*mut libusb_context>::uninit(); 49 | match unsafe { libusb_init(context.as_mut_ptr()) } { 50 | LIBUSB_SUCCESS => UsbContext { 51 | ctx: unsafe { context.assume_init() }, 52 | }, 53 | _ => panic!("could not initialize libusb context"), 54 | } 55 | } 56 | fn as_ptr(&self) -> *mut libusb_context { 57 | self.ctx 58 | } 59 | } 60 | impl Drop for UsbContext { 61 | fn drop(&mut self) { 62 | unsafe { libusb_exit(self.ctx) } 63 | } 64 | } 65 | 66 | pub(crate) struct Device { 67 | ctx: Arc, 68 | hnd: ptr::NonNull, 69 | running: Arc, 70 | 71 | ctrl_transfer: ptr::NonNull, 72 | ctrl_buf: [u8; CTRL_BUF_SIZE], 73 | ctrl_transfer_pending: RwLock, 74 | 75 | out_transfer: ptr::NonNull, 76 | out_buf: Vec, 77 | out_transfer_pending: RwLock, 78 | 79 | in_transfers: [*mut libusb_transfer; BULK_IN_TRANSFER_COUNT], 80 | in_bufs: [[u8; BULK_IN_BUF_SIZE]; BULK_IN_TRANSFER_COUNT], 81 | 82 | can_rx_send: Sender, 83 | pub can_rx_recv: Receiver, 84 | } 85 | 86 | extern "system" fn ctrl_cb(xfer: *mut libusb_transfer) { 87 | let dev_ptr = unsafe { (*xfer).user_data as *mut Device }; 88 | let dev = unsafe { &mut *dev_ptr }; 89 | 90 | let _status = unsafe { (*xfer).status }; 91 | 92 | *dev.ctrl_transfer_pending.write().unwrap() = false; 93 | } 94 | extern "system" fn bulk_out_cb(xfer: *mut libusb_transfer) { 95 | let dev_ptr = unsafe { (*xfer).user_data as *mut Device }; 96 | let dev = unsafe { &mut *dev_ptr }; 97 | let _status = unsafe { (*xfer).status }; 98 | 99 | *dev.out_transfer_pending.write().unwrap() = false; 100 | } 101 | 102 | extern "system" fn bulk_in_cb(xfer: *mut libusb_transfer) { 103 | let dev_ptr = unsafe { (*xfer).user_data as *mut Device }; 104 | let dev = unsafe { &mut *dev_ptr }; 105 | let status = unsafe { (*xfer).status }; 106 | 107 | if status == LIBUSB_TRANSFER_COMPLETED { 108 | let frame_data = unsafe { std::slice::from_raw_parts((*xfer).buffer, BULK_IN_BUF_SIZE) }; 109 | let f = HostFrame::from_le_bytes(frame_data); 110 | dev.can_rx_send.send(f).unwrap(); 111 | } 112 | if status != LIBUSB_TRANSFER_CANCELLED { 113 | // resubmit the transfer unless it was cancelled 114 | unsafe { 115 | libusb_submit_transfer(xfer); 116 | } 117 | } 118 | } 119 | 120 | impl Device { 121 | pub(crate) fn new(ctx: UsbContext) -> Result { 122 | let hnd = unsafe { libusb_open_device_with_vid_pid(ctx.as_ptr(), USB_VID, USB_PID) }; 123 | if hnd.is_null() { 124 | return Err(Error::DeviceNotFound); 125 | } 126 | 127 | match unsafe { libusb_detach_kernel_driver(hnd, 0) } { 128 | LIBUSB_SUCCESS => {} 129 | LIBUSB_ERROR_NOT_FOUND => { /* device already disconnected */ } 130 | LIBUSB_ERROR_NOT_SUPPORTED => { /* can't detach on this system (not linux) */ } 131 | e => return Err(Error::Libusb("libusb_detach_kernel_driver", e)), 132 | } 133 | 134 | match unsafe { libusb_claim_interface(hnd, 0) } { 135 | LIBUSB_SUCCESS => {} 136 | e => return Err(Error::Libusb("libusb_claim_interface", e)), 137 | } 138 | 139 | let ctrl_transfer = unsafe { libusb_alloc_transfer(0) }; 140 | if ctrl_transfer.is_null() { 141 | return Err(Error::TransferAllocFailed); 142 | } 143 | 144 | let in_bufs: [[u8; BULK_IN_BUF_SIZE]; BULK_IN_TRANSFER_COUNT] = 145 | [[0u8; BULK_IN_BUF_SIZE]; BULK_IN_TRANSFER_COUNT]; 146 | 147 | let (send, recv) = unbounded(); 148 | 149 | let d = Device { 150 | ctx: Arc::new(ctx), 151 | hnd: unsafe { ptr::NonNull::new_unchecked(hnd) }, 152 | running: Arc::new(AtomicBool::new(true)), 153 | 154 | ctrl_transfer: unsafe { ptr::NonNull::new_unchecked(ctrl_transfer) }, 155 | ctrl_buf: [0u8; CTRL_BUF_SIZE], 156 | ctrl_transfer_pending: RwLock::from(false), 157 | 158 | out_transfer: unsafe { ptr::NonNull::new_unchecked(ctrl_transfer) }, 159 | out_buf: vec![], 160 | out_transfer_pending: RwLock::from(false), 161 | 162 | in_transfers: [ptr::null_mut(); BULK_IN_TRANSFER_COUNT], 163 | in_bufs, 164 | 165 | can_rx_send: send, 166 | can_rx_recv: recv, 167 | }; 168 | 169 | // start the libusb event thread 170 | let ctx = d.ctx.clone(); 171 | let running = d.running.clone(); 172 | thread::spawn(move || { 173 | while running.load(Ordering::SeqCst) { 174 | unsafe { 175 | libusb_handle_events(ctx.as_ptr()); 176 | } 177 | } 178 | }); 179 | 180 | Ok(d) 181 | } 182 | 183 | pub(crate) fn start_transfers(&mut self) -> Result<(), Error> { 184 | // create the in transfers, fill the transfers, and submit them 185 | for i in 0..BULK_IN_TRANSFER_COUNT { 186 | let xfer = unsafe { libusb_alloc_transfer(0) }; 187 | if xfer.is_null() { 188 | return Err(Error::TransferAllocFailed); 189 | } 190 | self.in_transfers[i] = xfer; 191 | self.fill_bulk_in_transfer(i); 192 | 193 | match unsafe { libusb_submit_transfer(self.in_transfers[i]) } { 194 | LIBUSB_SUCCESS => {} 195 | e => return Err(Error::Libusb("start_transfers: libusb_submit_transfer", e)), 196 | }; 197 | } 198 | Ok(()) 199 | } 200 | 201 | pub(crate) fn stop_transfers(&self) -> Result<(), Error> { 202 | // cancel all bulk in transfers 203 | for xfer in self.in_transfers.iter() { 204 | if xfer.is_null() { 205 | // ignore null transfers 206 | continue; 207 | } 208 | match unsafe { libusb_cancel_transfer(*xfer) } { 209 | LIBUSB_SUCCESS => {} 210 | LIBUSB_ERROR_NOT_FOUND => { /* already destroyed */ } 211 | e => return Err(Error::Libusb("libusb_cancel_transfer", e)), 212 | } 213 | } 214 | Ok(()) 215 | } 216 | 217 | fn fill_control_transfer( 218 | &mut self, 219 | request_type: u8, 220 | request: u8, 221 | value: u16, 222 | index: u16, 223 | data: &[u8], 224 | ) { 225 | let mut transfer = unsafe { &mut *self.ctrl_transfer.as_ptr() }; 226 | 227 | // clear buffer 228 | self.ctrl_buf = [0u8; CTRL_BUF_SIZE]; 229 | // setup packet 230 | self.ctrl_buf[0] = request_type; // bmRequestType 231 | self.ctrl_buf[1] = request; // bRequest 232 | self.ctrl_buf[2] = (value & 0xFF) as u8; // wValue 233 | self.ctrl_buf[3] = (value >> 8) as u8; 234 | self.ctrl_buf[4] = (index & 0xFF) as u8; // wIndex 235 | self.ctrl_buf[5] = (index >> 8) as u8; 236 | self.ctrl_buf[6] = (data.len() & 0xFF) as u8; // wLength 237 | self.ctrl_buf[7] = (data.len() >> 8) as u8; 238 | 239 | // copy control out data 240 | self.ctrl_buf[8..(data.len() + 8)].clone_from_slice(data); 241 | 242 | transfer.dev_handle = self.hnd.as_ptr(); 243 | transfer.endpoint = 0; 244 | transfer.transfer_type = LIBUSB_TRANSFER_TYPE_CONTROL; 245 | transfer.timeout = 1000; 246 | transfer.buffer = self.ctrl_buf.as_mut_ptr(); 247 | transfer.length = self.ctrl_buf.len() as i32; 248 | transfer.callback = ctrl_cb; 249 | transfer.user_data = self as *mut _ as *mut c_void; 250 | } 251 | 252 | fn fill_bulk_out_transfer(&mut self, transfer: *mut libusb_transfer) { 253 | let mut transfer = unsafe { &mut *transfer }; 254 | let buf = &mut self.out_buf; 255 | 256 | transfer.dev_handle = self.hnd.as_ptr(); 257 | transfer.endpoint = 0x02; // bulk out ep 258 | transfer.transfer_type = LIBUSB_TRANSFER_TYPE_BULK; 259 | transfer.timeout = 1000; 260 | transfer.buffer = buf.as_mut_ptr(); 261 | transfer.length = buf.len() as i32; 262 | transfer.callback = bulk_out_cb; 263 | transfer.user_data = self as *mut _ as *mut c_void; 264 | } 265 | 266 | fn fill_bulk_in_transfer(&mut self, idx: usize) { 267 | let mut transfer = unsafe { &mut *self.in_transfers[idx] }; 268 | let buf = &mut self.in_bufs[idx]; 269 | 270 | transfer.dev_handle = self.hnd.as_ptr(); 271 | transfer.endpoint = 0x81; // bulk in ep 272 | transfer.transfer_type = LIBUSB_TRANSFER_TYPE_BULK; 273 | transfer.timeout = BULK_IN_TIMEOUT_MS; 274 | transfer.buffer = buf.as_mut_ptr(); 275 | transfer.length = buf.len() as i32; 276 | transfer.callback = bulk_in_cb; 277 | transfer.user_data = self as *mut _ as *mut c_void; 278 | } 279 | 280 | fn control_out(&mut self, req: UsbBreq, channel: u16, data: &[u8]) -> Result<(), Error> { 281 | // bmRequestType: direction = out, type = vendor, recipient = interface 282 | let rt = 0b0100_0001; 283 | self.fill_control_transfer(rt, req as u8, channel, 0, data); 284 | *self.ctrl_transfer_pending.write().unwrap() = true; 285 | match unsafe { libusb_submit_transfer(self.ctrl_transfer.as_ptr()) } { 286 | LIBUSB_SUCCESS => {} 287 | e => return Err(Error::Libusb("control_out: libusb_submit_transfer", e)), 288 | } 289 | 290 | // wait for transfer to complete 291 | while *self.ctrl_transfer_pending.read().unwrap() {} 292 | 293 | Ok(()) 294 | } 295 | 296 | fn control_in(&mut self, req: UsbBreq, channel: u16, len: usize) -> Result, Error> { 297 | // bmRequestType: direction = in, type = vendor, recipient = interface 298 | let rt = 0b1100_0001; 299 | self.fill_control_transfer(rt, req as u8, channel, 0, vec![0u8; len].as_slice()); 300 | *self.ctrl_transfer_pending.write().unwrap() = true; 301 | match unsafe { libusb_submit_transfer(self.ctrl_transfer.as_ptr()) } { 302 | LIBUSB_SUCCESS => {} 303 | e => return Err(Error::Libusb("control_in: libusb_submit_transfer", e)), 304 | } 305 | 306 | // wait for transfer to complete 307 | while *self.ctrl_transfer_pending.read().unwrap() {} 308 | let xfer_len = unsafe { (*self.ctrl_transfer.as_ptr()).actual_length } as usize; 309 | if xfer_len < len { 310 | // we didn't get the full struct we asked for 311 | return Err(Error::InvalidControlResponse); 312 | } 313 | 314 | Ok(self.ctrl_buf[8..8 + xfer_len].to_vec()) 315 | } 316 | 317 | pub(crate) fn set_host_format(&mut self, val: u32) -> Result<(), Error> { 318 | let channel = 0; 319 | self.control_out(UsbBreq::HostFormat, channel, &val.to_le_bytes()) 320 | } 321 | 322 | pub(crate) fn set_bit_timing(&mut self, channel: u16, timing: BitTiming) -> Result<(), Error> { 323 | self.control_out(UsbBreq::BitTiming, channel, &timing.to_le_bytes()) 324 | } 325 | 326 | pub(crate) fn set_data_bit_timing( 327 | &mut self, 328 | channel: u16, 329 | timing: BitTiming, 330 | ) -> Result<(), Error> { 331 | self.control_out(UsbBreq::DataBitTiming, channel, &timing.to_le_bytes()) 332 | } 333 | 334 | pub(crate) fn set_mode(&mut self, channel: u16, device_mode: Mode) -> Result<(), Error> { 335 | self.control_out(UsbBreq::Mode, channel, &device_mode.to_le_bytes()) 336 | } 337 | 338 | pub(crate) fn set_identify(&mut self, val: u32) -> Result<(), Error> { 339 | let channel = 0; 340 | self.control_out(UsbBreq::Identify, channel, &val.to_le_bytes()) 341 | } 342 | 343 | pub(crate) fn set_berr(&mut self, val: u32) -> Result<(), Error> { 344 | // TODO 345 | let channel = 0; 346 | self.control_out(UsbBreq::Berr, channel, &val.to_le_bytes()) 347 | } 348 | 349 | pub(crate) fn get_device_config(&mut self) -> Result { 350 | let channel = 0; 351 | let data = self.control_in(UsbBreq::DeviceConfig, channel, size_of::())?; 352 | Ok(DeviceConfig::from_le_bytes(&data)) 353 | } 354 | 355 | pub(crate) fn get_bit_timing_consts(&mut self) -> Result { 356 | let channel = 0; 357 | let data = self.control_in( 358 | UsbBreq::BitTimingConsts, 359 | channel, 360 | size_of::(), 361 | )?; 362 | Ok(BitTimingConsts::from_le_bytes(&data)) 363 | } 364 | 365 | pub(crate) fn get_timestamp(&mut self) -> Result { 366 | let channel = 0; 367 | let data = self.control_in(UsbBreq::Timestamp, channel, size_of::())?; 368 | let bytes = [data[0], data[1], data[2], data[3]]; 369 | Ok(u32::from_le_bytes(bytes)) 370 | } 371 | 372 | pub(crate) fn send(&mut self, frame: HostFrame) -> Result<(), Error> { 373 | self.out_buf.clear(); 374 | self.out_buf.append(&mut frame.to_le_bytes()); 375 | 376 | self.fill_bulk_out_transfer(self.out_transfer.as_ptr()); 377 | *self.out_transfer_pending.write().unwrap() = true; 378 | 379 | match unsafe { libusb_submit_transfer(self.out_transfer.as_ptr()) } { 380 | LIBUSB_SUCCESS => {} 381 | e => return Err(Error::Libusb("send: libusb_submit_transfer", e)), 382 | } 383 | 384 | // wait for transfer to complete 385 | while *self.out_transfer_pending.read().unwrap() {} 386 | 387 | Ok(()) 388 | } 389 | 390 | pub(crate) fn try_recv(&self) -> Option { 391 | match self.can_rx_recv.try_recv() { 392 | Ok(f) => Some(f), 393 | Err(TryRecvError::Empty) => None, 394 | Err(TryRecvError::Disconnected) => None, 395 | } 396 | } 397 | pub(crate) fn recv(&self) -> HostFrame { 398 | match self.can_rx_recv.recv() { 399 | Ok(f) => f, 400 | Err(e) => panic!("{}", e), 401 | } 402 | } 403 | } 404 | 405 | impl Drop for Device { 406 | fn drop(&mut self) { 407 | // stop the threau 408 | self.running.store(false, Ordering::SeqCst); 409 | 410 | self.stop_transfers().unwrap(); 411 | unsafe { 412 | libusb_release_interface(self.hnd.as_ptr(), 0); 413 | libusb_close(self.hnd.as_ptr()); 414 | } 415 | } 416 | } 417 | -------------------------------------------------------------------------------- /driver/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate provides a userspace driver for the CANtact family of 2 | //! Controller Area Network (CAN) devices. 3 | //! 4 | //! The rust library provided by this crate can be used directly to build 5 | //! applications for CANtact. The crate also provides bindings for other 6 | //! langauges. 7 | 8 | #![warn(missing_docs)] 9 | 10 | use std::fmt; 11 | use std::sync::{Arc, RwLock}; 12 | use std::thread; 13 | use std::time; 14 | 15 | use crossbeam_channel::RecvError; 16 | 17 | use serde::{Deserialize, Serialize}; 18 | 19 | mod device; 20 | use device::gsusb::*; 21 | use device::*; 22 | 23 | pub mod c; 24 | /// Implementation of Python bindings 25 | #[cfg(feature = "python")] 26 | pub mod python; 27 | 28 | /// Errors generated by this library 29 | #[derive(Debug)] 30 | pub enum Error { 31 | /// Errors from device interaction. 32 | DeviceError(device::Error), 33 | /// The device could not be found, or the user does not have permissions to access it. 34 | DeviceNotFound, 35 | /// Timeout while communicating with the device. 36 | Timeout, 37 | /// Attempted to perform an action on a device that is running when this is not allowed. 38 | Running, 39 | /// Attempted to perform an action on a device that is not running when this is not allowed. 40 | NotRunning, 41 | /// Requested channel index does not exist on device. 42 | InvalidChannel, 43 | /// The requested bitrate cannot be set within an acceptable tolerance 44 | InvalidBitrate(u32), 45 | /// The requested set of features is not supported by the device 46 | UnsupportedFeature(&'static str), 47 | } 48 | impl From for Error { 49 | fn from(e: device::Error) -> Error { 50 | // TODO 51 | // this could do a much better job of converting 52 | Error::DeviceError(e) 53 | } 54 | } 55 | 56 | /// Controller Area Network Frame 57 | #[derive(Debug, Clone)] 58 | pub struct Frame { 59 | /// CAN frame arbitration ID. 60 | pub can_id: u32, 61 | 62 | /// CAN frame Data Length Code (DLC). 63 | pub can_dlc: u8, 64 | 65 | /// Device channel used to send or receive the frame. 66 | pub channel: u8, 67 | 68 | /// Frame data contents. 69 | pub data: Vec, 70 | 71 | /// Extended (29 bit) arbitration identifier if true, 72 | /// standard (11 bit) arbitration identifer if false. 73 | pub ext: bool, 74 | 75 | /// CAN Flexible Data (CAN-FD) frame flag. 76 | pub fd: bool, 77 | 78 | /// CAN-FD Bit Rate Switch (BRS) flag. 79 | pub brs: bool, 80 | 81 | /// CAN-FD Error State Indicator (ESI) flag. 82 | pub esi: bool, 83 | 84 | /// Loopback flag. When true, frame was sent by this device/channel. 85 | /// False for received frames. 86 | pub loopback: bool, 87 | 88 | /// Error frame flag. 89 | pub err: bool, 90 | 91 | /// Remote Transmission Request (RTR) flag. 92 | pub rtr: bool, 93 | 94 | /// Timestamp when frame was received 95 | pub timestamp: Option, 96 | } 97 | impl Frame { 98 | fn data_as_array(&self) -> [u8; 64] { 99 | let mut data = [0u8; 64]; 100 | let len = std::cmp::min(self.data.len(), data.len()); 101 | data[..len].copy_from_slice(&self.data[..len]); 102 | data 103 | } 104 | // convert to a frame format expected by the device 105 | fn to_host_frame(&self) -> HostFrame { 106 | // if frame is extended, set the extended bit in host frame CAN ID 107 | let mut can_id = if self.ext { 108 | self.can_id | GSUSB_EXT_FLAG 109 | } else { 110 | self.can_id 111 | }; 112 | // apply RTR and ERR flags 113 | can_id = if self.rtr { 114 | can_id | GSUSB_RTR_FLAG 115 | } else { 116 | can_id 117 | }; 118 | can_id = if self.err { 119 | can_id | GSUSB_ERR_FLAG 120 | } else { 121 | can_id 122 | }; 123 | 124 | HostFrame { 125 | echo_id: 1, 126 | flags: if self.fd { GS_CAN_FLAG_FD } else { 0 }, 127 | reserved: 0, 128 | can_id, 129 | can_dlc: self.can_dlc, 130 | channel: self.channel, 131 | data: self.data_as_array(), 132 | } 133 | } 134 | /// Returns a default CAN frame with all values set to zero/false. 135 | pub fn default() -> Frame { 136 | Frame { 137 | can_id: 0, 138 | can_dlc: 0, 139 | data: vec![0; 64], 140 | channel: 0, 141 | ext: false, 142 | fd: false, 143 | loopback: false, 144 | rtr: false, 145 | brs: false, 146 | esi: false, 147 | err: false, 148 | timestamp: None, 149 | } 150 | } 151 | fn from_host_frame(hf: HostFrame) -> Frame { 152 | // check the extended bit of host frame 153 | // if set, frame is extended 154 | let ext = (hf.can_id & GSUSB_EXT_FLAG) > 0; 155 | // check the RTR and ERR bits of host frame ID 156 | let rtr = (hf.can_id & GSUSB_RTR_FLAG) > 0; 157 | let err = (hf.can_id & GSUSB_ERR_FLAG) > 0; 158 | // remove flags from CAN ID 159 | let can_id = hf.can_id & 0x1FFF_FFFF; 160 | // loopback frame if echo_id is not -1 161 | let loopback = hf.echo_id != GSUSB_RX_ECHO_ID; 162 | // apply FD flags 163 | let fd = (hf.flags & GS_CAN_FLAG_FD) > 0; 164 | let brs = (hf.flags & GS_CAN_FLAG_BRS) > 0; 165 | let esi = (hf.flags & GS_CAN_FLAG_ESI) > 0; 166 | 167 | Frame { 168 | can_id, 169 | can_dlc: hf.can_dlc, 170 | data: hf.data.to_vec(), 171 | channel: hf.channel, 172 | ext, 173 | loopback, 174 | rtr, 175 | fd, 176 | brs, 177 | esi, 178 | err, 179 | timestamp: None, 180 | } 181 | } 182 | 183 | /// Return the length of data in this frame. This is the DLC for non-FD frames. 184 | pub fn data_len(&self) -> usize { 185 | match self.can_dlc { 186 | 0..=8 => self.can_dlc as usize, 187 | 9 => 12, 188 | 10 => 16, 189 | 11 => 20, 190 | 12 => 24, 191 | 13 => 32, 192 | 14 => 48, 193 | 15 => 64, 194 | 16..=u8::MAX => panic!("invalid DLC value"), 195 | } 196 | } 197 | } 198 | 199 | /// Configuration for a device's CAN channel. 200 | #[derive(Debug, Serialize, Deserialize, Clone)] 201 | pub struct Channel { 202 | /// Bitrate of the channel in bits/second 203 | pub bitrate: u32, 204 | /// When true, channel should be enabled when device starts 205 | pub enabled: bool, 206 | /// When true, device is configured in hardware loopback mode 207 | pub loopback: bool, 208 | /// When true, device will not transmit on the bus. 209 | pub monitor: bool, 210 | /// When true, CAN FD is enabled for the device 211 | pub fd: bool, 212 | /// CAN FD data bitrate of the channel in bits/second 213 | pub data_bitrate: u32, 214 | } 215 | 216 | /// Interface for interacting with CANtact devices 217 | pub struct Interface { 218 | dev: Device, 219 | running: Arc>, 220 | 221 | can_clock: u32, 222 | // zero indexed (0 = 1 channel, 1 = 2 channels, etc...) 223 | channel_count: usize, 224 | sw_version: u32, 225 | hw_version: u32, 226 | features: u32, 227 | 228 | channels: Vec, 229 | } 230 | 231 | impl fmt::Debug for Interface { 232 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 233 | f.debug_struct("Interface") 234 | .field("running", &(*self.running.read().unwrap())) 235 | .field("can_clock", &self.can_clock) 236 | .field("channel_count", &self.channel_count) 237 | .field("sw_version", &self.sw_version) 238 | .field("hw_version", &self.hw_version) 239 | .field("channels", &self.channels) 240 | .finish() 241 | } 242 | } 243 | 244 | impl Interface { 245 | /// Creates a new interface. This always selects the first device found by 246 | /// libusb. If no device is found, Error::DeviceNotFound is returned. 247 | pub fn new() -> Result { 248 | let mut dev = match Device::new(UsbContext::new()) { 249 | Ok(d) => d, 250 | Err(_) => return Err(Error::DeviceNotFound), 251 | }; 252 | 253 | let dev_config = dev.get_device_config()?; 254 | let bt_consts = dev.get_bit_timing_consts()?; 255 | 256 | let channel_count = dev_config.icount as usize; 257 | 258 | let mut channels = Vec::new(); 259 | // note: channel_count is zero indexed 260 | for _ in 0..(channel_count + 1) { 261 | channels.push(Channel { 262 | bitrate: 0, 263 | enabled: true, 264 | loopback: false, 265 | monitor: false, 266 | fd: false, 267 | data_bitrate: 0, 268 | }); 269 | } 270 | 271 | let i = Interface { 272 | dev, 273 | running: Arc::new(RwLock::from(false)), 274 | 275 | channel_count, 276 | can_clock: bt_consts.fclk_can, 277 | sw_version: dev_config.sw_version, 278 | hw_version: dev_config.hw_version, 279 | features: bt_consts.feature, 280 | 281 | channels, 282 | }; 283 | 284 | Ok(i) 285 | } 286 | 287 | /// Start CAN communication on all configured channels. 288 | /// 289 | /// After starting the device, `Interface.send` can be used to send frames. 290 | /// For every received frame, the `rx_callback` closure will be called. 291 | pub fn start( 292 | &mut self, 293 | mut rx_callback: impl FnMut(Frame) + Sync + Send + 'static, 294 | ) -> Result<(), Error> { 295 | // tell the device to go on bus 296 | for (i, ch) in self.channels.iter().enumerate() { 297 | let mut flags = 0; 298 | // for each mode flag, check that the feature is supported before applying feature 299 | // this is necessary since the feature flags are pub 300 | if ch.monitor { 301 | if (self.features & GS_CAN_FEATURE_LISTEN_ONLY) == 0 { 302 | return Err(Error::UnsupportedFeature("Monitor")); 303 | } 304 | flags |= GS_CAN_MODE_LISTEN_ONLY; 305 | } 306 | if ch.loopback { 307 | if (self.features & GS_CAN_FEATURE_LOOP_BACK) == 0 { 308 | return Err(Error::UnsupportedFeature("Loopback")); 309 | } 310 | flags |= GS_CAN_MODE_LOOP_BACK; 311 | } 312 | if ch.fd { 313 | if !self.supports_fd() { 314 | return Err(Error::UnsupportedFeature("FD")); 315 | } 316 | flags |= GS_CAN_MODE_FD; 317 | } 318 | 319 | let mode = Mode { 320 | mode: CanMode::Start as u32, 321 | flags, 322 | }; 323 | if ch.enabled { 324 | self.dev.set_mode(i as u16, mode).unwrap(); 325 | } 326 | } 327 | 328 | { 329 | *self.running.write().unwrap() = true; 330 | } 331 | 332 | // rx callback thread 333 | let can_rx = self.dev.can_rx_recv.clone(); 334 | let running = Arc::clone(&self.running); 335 | let start_time = time::Instant::now(); 336 | thread::spawn(move || { 337 | while *running.read().unwrap() { 338 | match can_rx.recv() { 339 | Ok(hf) => { 340 | let mut f = Frame::from_host_frame(hf); 341 | f.timestamp = Some(time::Instant::now().duration_since(start_time)); 342 | rx_callback(f) 343 | } 344 | Err(RecvError) => { 345 | // channel disconnected 346 | break; 347 | } 348 | } 349 | } 350 | }); 351 | 352 | self.dev.start_transfers().unwrap(); 353 | Ok(()) 354 | } 355 | 356 | /// Stop CAN communication on all channels. 357 | pub fn stop(&mut self) -> Result<(), Error> { 358 | // TODO multi-channel 359 | for (i, ch) in self.channels.iter().enumerate() { 360 | let mode = Mode { 361 | mode: CanMode::Reset as u32, 362 | flags: 0, 363 | }; 364 | if ch.enabled { 365 | self.dev.set_mode(i as u16, mode).unwrap(); 366 | } 367 | } 368 | 369 | self.dev.stop_transfers().unwrap(); 370 | *self.running.write().unwrap() = false; 371 | Ok(()) 372 | } 373 | 374 | /// Set bitrate for specified channel to requested bitrate value in bits per second. 375 | pub fn set_bitrate(&mut self, channel: usize, bitrate: u32) -> Result<(), Error> { 376 | if channel > self.channel_count { 377 | return Err(Error::InvalidChannel); 378 | } 379 | 380 | let bt = calculate_bit_timing(self.can_clock, bitrate)?; 381 | self.dev 382 | .set_bit_timing(channel as u16, bt) 383 | .expect("failed to set bit timing"); 384 | 385 | self.channels[channel].bitrate = bitrate; 386 | Ok(()) 387 | } 388 | 389 | /// Set CAN FD data bitrate for specified channel to requested bitrate value in bits per second. 390 | pub fn set_data_bitrate(&mut self, channel: usize, bitrate: u32) -> Result<(), Error> { 391 | if !self.supports_fd() { 392 | return Err(Error::UnsupportedFeature("FD")); 393 | } 394 | 395 | if channel > self.channel_count { 396 | return Err(Error::InvalidChannel); 397 | } 398 | 399 | let bt = calculate_bit_timing(self.can_clock, bitrate)?; 400 | self.dev 401 | .set_data_bit_timing(channel as u16, bt) 402 | .expect("failed to set bit timing"); 403 | 404 | self.channels[channel].data_bitrate = bitrate; 405 | Ok(()) 406 | } 407 | 408 | /// Set a custom bit timing for the specified channel. 409 | pub fn set_bit_timing( 410 | &mut self, 411 | channel: usize, 412 | brp: u32, 413 | phase_seg1: u32, 414 | phase_seg2: u32, 415 | sjw: u32, 416 | ) -> Result<(), Error> { 417 | let bt = BitTiming { 418 | brp, 419 | prop_seg: 0, 420 | phase_seg1, 421 | phase_seg2, 422 | sjw, 423 | }; 424 | self.dev 425 | .set_bit_timing(channel as u16, bt) 426 | .expect("failed to set bit timing"); 427 | Ok(()) 428 | } 429 | 430 | /// Enable or disable a channel's listen only mode. When this mode is enabled, 431 | /// the device will not transmit any frames, errors, or acknowledgements. 432 | pub fn set_monitor(&mut self, channel: usize, enabled: bool) -> Result<(), Error> { 433 | if self.features & GS_CAN_FEATURE_LISTEN_ONLY == 0 { 434 | return Err(Error::UnsupportedFeature("Monitor")); 435 | } 436 | if channel > self.channel_count { 437 | return Err(Error::InvalidChannel); 438 | } 439 | if *self.running.read().unwrap() { 440 | return Err(Error::Running); 441 | } 442 | 443 | self.channels[channel].monitor = enabled; 444 | Ok(()) 445 | } 446 | 447 | /// Enable or disable a channel's listen only mode. When this mode is enabled, 448 | /// the device will not transmit any frames, errors, or acknowledgements. 449 | pub fn set_enabled(&mut self, channel: usize, enabled: bool) -> Result<(), Error> { 450 | if channel > self.channel_count { 451 | return Err(Error::InvalidChannel); 452 | } 453 | if *self.running.read().unwrap() { 454 | return Err(Error::Running); 455 | } 456 | 457 | self.channels[channel].enabled = enabled; 458 | Ok(()) 459 | } 460 | 461 | /// Enable or disable a channel's loopback mode. When this mode is enabled, 462 | /// frames sent by the device will be received by the device 463 | /// *as if they had been sent by another node on the bus*. 464 | /// 465 | /// This mode is primarily intended for device testing! 466 | pub fn set_loopback(&mut self, channel: usize, enabled: bool) -> Result<(), Error> { 467 | if self.features & GS_CAN_FEATURE_LOOP_BACK == 0 { 468 | return Err(Error::UnsupportedFeature("Loopback")); 469 | } 470 | if channel > self.channel_count { 471 | return Err(Error::InvalidChannel); 472 | } 473 | if *self.running.read().unwrap() { 474 | return Err(Error::Running); 475 | } 476 | 477 | self.channels[channel].loopback = enabled; 478 | Ok(()) 479 | } 480 | 481 | /// Enable or disable CAN FD support for a channel 482 | pub fn set_fd(&mut self, channel: usize, enabled: bool) -> Result<(), Error> { 483 | if !self.supports_fd() { 484 | return Err(Error::UnsupportedFeature("FD")); 485 | } 486 | if channel > self.channel_count { 487 | return Err(Error::InvalidChannel); 488 | } 489 | if *self.running.read().unwrap() { 490 | return Err(Error::Running); 491 | } 492 | 493 | self.channels[channel].fd = enabled; 494 | Ok(()) 495 | } 496 | 497 | /// Returns true if device suports CAN-FD operation, false otherwise. 498 | pub fn supports_fd(&self) -> bool { 499 | (self.features & GS_CAN_FEATURE_FD) > 0 500 | } 501 | 502 | /// Send a CAN frame using the device 503 | pub fn send(&mut self, f: Frame) -> Result<(), Error> { 504 | if !*self.running.read().unwrap() { 505 | return Err(Error::NotRunning); 506 | } 507 | 508 | self.dev.send(f.to_host_frame()).unwrap(); 509 | Ok(()) 510 | } 511 | 512 | /// Returns the number of channels this Interface has 513 | pub fn channels(&self) -> usize { 514 | self.channel_count + 1 515 | } 516 | } 517 | 518 | fn calculate_bit_timing(clk: u32, bitrate: u32) -> Result { 519 | let max_brp = 32; 520 | let min_seg1 = 3; 521 | let max_seg1 = 18; 522 | let min_seg2 = 2; 523 | let max_seg2 = 8; 524 | let tolerances = vec![0.0, 0.1 / 100.0, 0.5 / 100.0]; 525 | 526 | for tolerance in tolerances { 527 | let tmp = clk as f32 / bitrate as f32; 528 | for brp in 1..(max_brp + 1) { 529 | let btq = tmp / brp as f32; 530 | let btq_rounded = btq.round() as u32; 531 | 532 | if (4..=32).contains(&btq_rounded) { 533 | let err = ((btq / (btq_rounded as f32) - 1.0) * 10000.0).round() / 10000.0; 534 | if err.abs() > tolerance { 535 | // error is not acceptable 536 | continue; 537 | } 538 | } 539 | 540 | for seg1 in min_seg1..max_seg1 { 541 | // subtract 1 from seg2 to account for propagation phase 542 | let seg2 = btq_rounded - seg1 - 1; 543 | if seg2 < min_seg2 || seg2 > max_seg2 { 544 | // invalid seg2 value 545 | continue; 546 | } 547 | // brp, seg1, and seg2 are all valid 548 | return Ok(BitTiming { 549 | brp, 550 | prop_seg: 0, 551 | phase_seg1: seg1, 552 | phase_seg2: seg2, 553 | sjw: 1, 554 | }); 555 | } 556 | } 557 | } 558 | Err(Error::InvalidBitrate(bitrate)) 559 | } 560 | 561 | #[allow(dead_code)] 562 | fn effective_bitrate(clk: u32, bt: BitTiming) -> u32 { 563 | clk / bt.brp / (bt.prop_seg + bt.phase_seg1 + bt.phase_seg2 + 1) 564 | } 565 | 566 | #[cfg(test)] 567 | mod tests { 568 | use super::*; 569 | #[test] 570 | fn test_bit_timing() { 571 | let clk = 24000000; 572 | let bitrates = vec![1000000, 500000, 250000, 125000, 33333]; 573 | for b in bitrates { 574 | let bt = calculate_bit_timing(clk, b).unwrap(); 575 | 576 | // ensure error < 0.5% 577 | println!("{:?}", &bt); 578 | let err = 100.0 * (1.0 - (effective_bitrate(clk, bt) as f32 / b as f32).abs()); 579 | println!("{:?}", err); 580 | assert!(err < 0.5); 581 | } 582 | } 583 | } 584 | -------------------------------------------------------------------------------- /driver/src/python.rs: -------------------------------------------------------------------------------- 1 | use crate::Error; 2 | use crate::{Frame, Interface}; 3 | use crossbeam_channel::{unbounded, Receiver, RecvTimeoutError, Sender}; 4 | use pyo3::exceptions; 5 | use pyo3::prelude::*; 6 | use pyo3::types::PyDict; 7 | 8 | #[pyclass(name = Interface)] 9 | struct PyInterface { 10 | i: Interface, 11 | rx_recv: Receiver, 12 | rx_send: Sender, 13 | } 14 | impl IntoPy for Frame { 15 | fn into_py(self, py: Python) -> PyObject { 16 | let d = PyDict::new(py); 17 | d.set_item("id", self.can_id).unwrap(); 18 | d.set_item("dlc", self.can_dlc).unwrap(); 19 | d.set_item("data", self.data.to_vec()).unwrap(); 20 | d.set_item("extended", self.ext).unwrap(); 21 | d.set_item("rtr", self.rtr).unwrap(); 22 | d.set_item("channel", self.channel).unwrap(); 23 | d.set_item("loopback", self.loopback).unwrap(); 24 | match self.timestamp { 25 | Some(t) => d 26 | .set_item("timestamp", t.as_micros() as f32 / 1000000.0) 27 | .unwrap(), 28 | None => d.set_item("timestamp", 0).unwrap(), 29 | }; 30 | d.to_object(py) 31 | } 32 | } 33 | 34 | impl std::convert::From for PyErr { 35 | fn from(err: Error) -> PyErr { 36 | PyErr::new::(format!("{:?}", err)) 37 | } 38 | } 39 | 40 | #[pymethods] 41 | impl PyInterface { 42 | #[new] 43 | fn new() -> PyResult { 44 | let mut i = Interface::new()?; 45 | 46 | // disable all channels by default 47 | for n in 0..i.channels.len() { 48 | i.set_enabled(n, false)?; 49 | } 50 | 51 | let (send, recv) = unbounded(); 52 | Ok(PyInterface { 53 | i, 54 | rx_recv: recv, 55 | rx_send: send, 56 | }) 57 | } 58 | 59 | fn set_bitrate(&mut self, channel: usize, bitrate: u32) -> PyResult<()> { 60 | self.i.set_bitrate(channel, bitrate)?; 61 | Ok(()) 62 | } 63 | 64 | fn set_bit_timing( 65 | &mut self, 66 | channel: usize, 67 | brp: u32, 68 | phase_seg1: u32, 69 | phase_seg2: u32, 70 | sjw: u32, 71 | ) -> PyResult<()> { 72 | self.i 73 | .set_bit_timing(channel, brp, phase_seg1, phase_seg2, sjw)?; 74 | Ok(()) 75 | } 76 | 77 | fn set_enabled(&mut self, channel: usize, enabled: bool) -> PyResult<()> { 78 | self.i.set_enabled(channel, enabled)?; 79 | Ok(()) 80 | } 81 | 82 | fn set_monitor(&mut self, channel: usize, enabled: bool) -> PyResult<()> { 83 | self.i.set_monitor(channel, enabled)?; 84 | Ok(()) 85 | } 86 | 87 | fn start(&mut self) -> PyResult<()> { 88 | let rx = self.rx_send.clone(); 89 | 90 | self.i.start(move |f: Frame| { 91 | match rx.send(f) { 92 | Ok(_) => {} 93 | Err(_) => { 94 | panic!("error starting device") 95 | } 96 | }; 97 | })?; 98 | 99 | Ok(()) 100 | } 101 | 102 | fn stop(&mut self) -> PyResult<()> { 103 | self.i.stop()?; 104 | Ok(()) 105 | } 106 | 107 | fn recv(&self, timeout_ms: u64) -> PyResult> { 108 | let f = match self 109 | .rx_recv 110 | .recv_timeout(std::time::Duration::from_millis(timeout_ms)) 111 | { 112 | Ok(f) => f, 113 | Err(RecvTimeoutError::Timeout) => return Ok(None), 114 | Err(RecvTimeoutError::Disconnected) => panic!("device thread died"), 115 | }; 116 | Ok(Some(f)) 117 | } 118 | 119 | fn send( 120 | &mut self, 121 | channel: u8, 122 | id: u32, 123 | ext: bool, 124 | rtr: bool, 125 | dlc: u8, 126 | data: Vec, 127 | ) -> PyResult<()> { 128 | let mut data_array: Vec = vec![0; 64]; 129 | for i in 0..dlc as usize { 130 | if i < dlc.into() { 131 | data_array[i] = data[i]; 132 | } 133 | } 134 | self.i.send(Frame { 135 | can_id: id, 136 | can_dlc: dlc, 137 | ext, 138 | rtr, 139 | data: data_array, 140 | channel, 141 | loopback: false, 142 | fd: false, 143 | brs: false, 144 | err: false, 145 | esi: false, 146 | timestamp: None, 147 | })?; 148 | Ok(()) 149 | } 150 | 151 | fn channel_count(&self) -> PyResult { 152 | Ok(self.i.channels()) 153 | } 154 | } 155 | 156 | #[pymodule] 157 | fn cantact(_py: Python, m: &PyModule) -> PyResult<()> { 158 | m.add_class::()?; 159 | Ok(()) 160 | } 161 | -------------------------------------------------------------------------------- /src/cfg.rs: -------------------------------------------------------------------------------- 1 | use crate::Error; 2 | use clap::ArgMatches; 3 | 4 | use crate::config::Config; 5 | use crate::helpers; 6 | 7 | pub fn cmd(matches: &ArgMatches) -> Result<(), Error> { 8 | let mut config = Config::read(); 9 | 10 | let ch = match helpers::parse_channel(matches)? { 11 | None => { 12 | // if no channel is provided, print the current configuration 13 | print!("{}", config); 14 | return Ok(()); 15 | } 16 | Some(ch) => ch, 17 | }; 18 | 19 | if matches.is_present("disable") { 20 | config.channels[ch].enabled = false; 21 | } else { 22 | config.channels[ch].enabled = true; 23 | } 24 | 25 | if matches.is_present("loopback") { 26 | config.channels[ch].loopback = true; 27 | } else { 28 | config.channels[ch].loopback = false; 29 | } 30 | 31 | if matches.is_present("monitor") { 32 | config.channels[ch].monitor = true; 33 | } else { 34 | config.channels[ch].monitor = false; 35 | } 36 | 37 | if matches.is_present("fd") { 38 | config.channels[ch].fd = true; 39 | } else { 40 | config.channels[ch].fd = false; 41 | } 42 | 43 | if matches.is_present("bitrate") { 44 | let bitrate = match matches.value_of("bitrate").unwrap().parse::() { 45 | Err(_) => { 46 | return Err(Error::InvalidArgument(String::from( 47 | "invalid bitrate value", 48 | ))) 49 | } 50 | Ok(b) => b, 51 | }; 52 | config.channels[ch].bitrate = bitrate; 53 | } 54 | 55 | if matches.is_present("data_bitrate") { 56 | let data_bitrate = match matches.value_of("data_bitrate").unwrap().parse::() { 57 | Err(_) => { 58 | return Err(Error::InvalidArgument(String::from( 59 | "invalid data_bitrate value", 60 | ))) 61 | } 62 | Ok(b) => b, 63 | }; 64 | config.channels[ch].data_bitrate = data_bitrate; 65 | } 66 | 67 | config.write().unwrap(); 68 | 69 | print!("{}", config); 70 | Ok(()) 71 | } 72 | -------------------------------------------------------------------------------- /src/cli.yml: -------------------------------------------------------------------------------- 1 | name: can 2 | version: "0.1.2" 3 | author: Eric Evenchick 4 | about: Command line utilities for CANtact devices 5 | args: 6 | - verbose: 7 | long: verbose 8 | short: v 9 | help: Print verbose debugging information 10 | subcommands: 11 | - cfg: 12 | about: Set device configurations 13 | args: 14 | - channel: 15 | short: c 16 | long: channel 17 | help: Channel to configure 18 | takes_value: true 19 | - bitrate: 20 | short: b 21 | long: bitrate 22 | help: Channel bitrate in bits/second 23 | takes_value: true 24 | - data_bitrate: 25 | short: z 26 | long: data_bitrate 27 | help: Channel data bitrate in bits/second (used only in CAN-FD mode) 28 | takes_value: true 29 | - disable: 30 | short: d 31 | long: disable 32 | help: Disable this channel 33 | - monitor: 34 | short: m 35 | long: monitor 36 | help: Enable monitor (listen only) mode 37 | - loopback: 38 | short: l 39 | long: loopback 40 | help: Enable hardware loopback mode 41 | - fd: 42 | short: f 43 | long: fd 44 | help: Enable CAN-FD mode 45 | - dump: 46 | about: Receive and display CAN frames 47 | args: 48 | - channel: 49 | short: c 50 | long: channel 51 | help: Channel(s) to listen on 52 | takes_value: true 53 | - filter: 54 | short: f 55 | help: "CAN filter to apply, formatted as [id]:[mask]\nExample: 0x123:0x7FF will match only ID 0x123" 56 | takes_value: true 57 | - send: 58 | about: Send a single CAN frame 59 | args: 60 | - extended: 61 | long: extended 62 | short: e 63 | help: Force extended identifier (only required if id <= 0x7FF) 64 | - channel: 65 | help: Channel to transmit on 66 | required: true 67 | - identifier: 68 | help: CAN identifier to transmit 69 | required: true 70 | - data: 71 | help: CAN data to transmit 72 | required: true 73 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use crate::Error; 2 | use app_dirs::*; 3 | use cantact::{Channel, Interface}; 4 | use log::info; 5 | use serde::{Deserialize, Serialize}; 6 | use std::fmt; 7 | use std::fs; 8 | use std::fs::File; 9 | use std::io; 10 | use std::io::prelude::*; 11 | use std::path::Path; 12 | 13 | const APP_INFO: AppInfo = AppInfo { 14 | name: "cantact", 15 | author: "Linklayer", 16 | }; 17 | const CFG_FILE: &str = "cantact.toml"; 18 | const DEFAULT_CONFIG: Channel = Channel { 19 | bitrate: 500_000, 20 | data_bitrate: 500_000, 21 | loopback: false, 22 | monitor: false, 23 | fd: false, 24 | enabled: true, 25 | }; 26 | 27 | #[derive(Debug, Serialize, Deserialize)] 28 | pub struct Config { 29 | #[serde(rename = "channel")] 30 | pub channels: Vec, 31 | } 32 | impl fmt::Display for Config { 33 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 34 | writeln!(f, "Channels:")?; 35 | for (n, ch) in self.channels.iter().enumerate() { 36 | writeln!(f, "\t{} -> {:?}", n, ch)?; 37 | } 38 | Ok(()) 39 | } 40 | } 41 | 42 | impl Config { 43 | pub fn default() -> Config { 44 | Config { 45 | channels: vec![DEFAULT_CONFIG, DEFAULT_CONFIG], 46 | } 47 | } 48 | 49 | // since config files are not mandatory, this should never fail 50 | pub fn read() -> Config { 51 | let dir = match get_app_root(AppDataType::UserConfig, &APP_INFO) { 52 | Ok(d) => d, 53 | Err(_) => return Config::default(), 54 | }; 55 | let filename = Path::new("").join(dir).join(CFG_FILE); 56 | let s = match fs::read_to_string(&filename) { 57 | Ok(s) => s, 58 | Err(_) => return Config::default(), 59 | }; 60 | let result = toml::from_str(&s).unwrap_or_else(|_| Config::default()); 61 | info!("read configuration from {:?}", filename); 62 | result 63 | } 64 | 65 | pub fn write(&self) -> io::Result<()> { 66 | let dir = match get_app_root(AppDataType::UserConfig, &APP_INFO) { 67 | Ok(d) => d, 68 | Err(AppDirsError::NotSupported) => panic!("platform does not support configuation"), 69 | Err(AppDirsError::Io(e)) => { 70 | panic!("IO error determining configuration location: {:?}", e) 71 | } 72 | Err(AppDirsError::InvalidAppInfo) => panic!("app info struct is invalid"), 73 | }; 74 | fs::create_dir_all(&dir)?; 75 | let filename = Path::new("").join(dir).join(CFG_FILE); 76 | info!("writing configuration to {:?}", filename); 77 | 78 | let mut file = File::create(filename)?; 79 | file.write_all(toml::to_string(&self).unwrap().as_bytes()) 80 | } 81 | 82 | pub fn apply_to_interface(&self, i: &mut Interface) -> Result<(), Error> { 83 | for (n, ch) in self.channels.iter().enumerate() { 84 | if n > (i.channels() - 1) { 85 | // device doesn't have as many channels as config, ignore the rest 86 | break; 87 | } 88 | i.set_bitrate(n, ch.bitrate)?; 89 | i.set_enabled(n, ch.enabled)?; 90 | i.set_loopback(n, ch.loopback)?; 91 | i.set_monitor(n, ch.monitor)?; 92 | if i.supports_fd() { 93 | i.set_fd(n, ch.fd)?; 94 | i.set_data_bitrate(n, ch.data_bitrate)?; 95 | } 96 | } 97 | Ok(()) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/dump.rs: -------------------------------------------------------------------------------- 1 | use crate::Error; 2 | use cantact::{Frame, Interface}; 3 | use clap::ArgMatches; 4 | use log::info; 5 | 6 | use crate::config::Config; 7 | use crate::helpers; 8 | 9 | fn print_frame(f: Frame) { 10 | let ts = match f.timestamp { 11 | Some(t) => format!("{:.6}\t", t.as_secs_f32()), 12 | None => String::new(), 13 | }; 14 | 15 | if f.err { 16 | println!("{} ch:{} error frame", ts, f.channel); 17 | } 18 | 19 | let mut s = format!("{} ch:{} {:03X}", ts, f.channel, f.can_id,); 20 | 21 | s = if f.fd { 22 | format!("{} [{:02}] ", s, f.data_len()) 23 | } else { 24 | format!("{} [{:01}] ", s, f.data_len()) 25 | }; 26 | 27 | for b in f.data.iter().take(f.data_len()) { 28 | s = format!("{}{:02X} ", s, b); 29 | } 30 | println!("{}", s) 31 | } 32 | 33 | pub fn cmd(matches: &ArgMatches) -> Result<(), Error> { 34 | let flag = helpers::initialize_ctrlc(); 35 | let mut config = Config::read(); 36 | 37 | let ch = helpers::parse_channel(matches)?; 38 | match ch { 39 | None => { /* no channel specified, follow config */ } 40 | Some(ch) => { 41 | // channel specified, disable all others 42 | for n in 0..config.channels.len() { 43 | if n != ch { 44 | config.channels[n].enabled = false; 45 | } 46 | } 47 | } 48 | } 49 | info!("config: {:?}", config); 50 | 51 | // initialize the interface 52 | let mut i = Interface::new()?; 53 | config.apply_to_interface(&mut i)?; 54 | 55 | // start the device 56 | info!("starting dump"); 57 | i.start(move |f: Frame| { 58 | print_frame(f); 59 | }) 60 | .expect("failed to start device"); 61 | 62 | helpers::wait_for_ctrlc(&flag); 63 | 64 | i.stop().expect("failed to stop device"); 65 | Ok(()) 66 | } 67 | -------------------------------------------------------------------------------- /src/helpers.rs: -------------------------------------------------------------------------------- 1 | use crate::Error; 2 | use clap::ArgMatches; 3 | use std::sync::atomic::{AtomicBool, Ordering}; 4 | use std::sync::Arc; 5 | 6 | const MAX_CHANNELS: usize = 2; 7 | 8 | pub fn initialize_ctrlc() -> Arc { 9 | let flag = Arc::new(AtomicBool::new(false)); 10 | let f = flag.clone(); 11 | 12 | ctrlc::set_handler(move || { 13 | f.store(true, Ordering::SeqCst); 14 | }) 15 | .expect("Error setting Ctrl-C handler"); 16 | 17 | flag 18 | } 19 | 20 | pub fn check_ctrlc(f: &Arc) -> bool { 21 | f.load(Ordering::SeqCst) 22 | } 23 | 24 | pub fn wait_for_ctrlc(f: &Arc) { 25 | while !check_ctrlc(f) { 26 | std::thread::sleep(std::time::Duration::from_millis(100)); 27 | } 28 | } 29 | 30 | pub fn parse_channel(matches: &ArgMatches) -> Result, Error> { 31 | if !matches.is_present("channel") { 32 | return Ok(None); 33 | } 34 | let ch_str = matches.value_of("channel").unwrap(); 35 | match ch_str.parse::() { 36 | Err(_) => Err(Error::InvalidArgument(String::from( 37 | "invalid channel value", 38 | ))), 39 | Ok(ch) if ch > MAX_CHANNELS => Err(Error::InvalidArgument(String::from( 40 | "channel value out of range", 41 | ))), 42 | Ok(ch) => Ok(Some(ch)), 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use cantact::Error as DevError; 2 | use clap::load_yaml; 3 | use clap::App; 4 | use simplelog::*; 5 | 6 | // commands 7 | mod cfg; 8 | mod dump; 9 | mod send; 10 | 11 | pub mod config; 12 | pub mod helpers; 13 | 14 | #[derive(Debug)] 15 | pub enum Error { 16 | DeviceError(DevError), 17 | InvalidArgument(String), 18 | } 19 | impl From for Error { 20 | fn from(de: DevError) -> Error { 21 | Error::DeviceError(de) 22 | } 23 | } 24 | 25 | fn main() { 26 | let yaml = load_yaml!("cli.yml"); 27 | let matches = App::from(yaml).get_matches(); 28 | 29 | let logger = if matches.is_present("verbose") { 30 | TermLogger::init(LevelFilter::Info, Config::default(), TerminalMode::Mixed) 31 | } else { 32 | TermLogger::init(LevelFilter::Warn, Config::default(), TerminalMode::Mixed) 33 | }; 34 | logger.expect("failed to initialize logging"); 35 | 36 | let result = match matches.subcommand() { 37 | ("dump", Some(m)) => dump::cmd(m), 38 | ("send", Some(m)) => send::cmd(m), 39 | ("cfg", Some(m)) => cfg::cmd(m), 40 | _ => Ok(()), 41 | }; 42 | 43 | match result { 44 | Ok(_) => {} 45 | Err(e) => println!("error: {:?}", e), 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/send.rs: -------------------------------------------------------------------------------- 1 | use crate::Error; 2 | use cantact::{Frame, Interface}; 3 | use clap::ArgMatches; 4 | use log::info; 5 | use std::thread; 6 | use std::time::Duration; 7 | 8 | use crate::config::Config; 9 | use crate::helpers; 10 | 11 | pub fn cmd(matches: &ArgMatches) -> Result<(), Error> { 12 | let flag = helpers::initialize_ctrlc(); 13 | 14 | let mut config = Config::read(); 15 | 16 | let ch = helpers::parse_channel(matches)?; 17 | match ch { 18 | None => { /* no channel specified, follow config */ } 19 | Some(ch) => { 20 | // channel specified, disable all others 21 | for n in 0..config.channels.len() { 22 | if n != ch { 23 | config.channels[n].enabled = false; 24 | } 25 | } 26 | } 27 | } 28 | info!("config: {:?}", config); 29 | 30 | // initialize the interface 31 | let mut i = Interface::new()?; 32 | config.apply_to_interface(&mut i)?; 33 | 34 | // start the device 35 | info!("starting dump"); 36 | i.start(move |_: Frame| {}).expect("failed to start device"); 37 | 38 | let mut count = 0; 39 | let mut f = Frame::default(); 40 | f.can_dlc = 8; 41 | loop { 42 | f.can_id = count % 0x800; 43 | i.send(f.clone()).unwrap(); 44 | count += 1; 45 | if count % 1000 == 0 { 46 | println!("{}", count) 47 | } 48 | thread::sleep(Duration::from_millis(10)); 49 | if helpers::check_ctrlc(&flag) { 50 | break; 51 | } 52 | } 53 | i.stop()?; 54 | Ok(()) 55 | } 56 | --------------------------------------------------------------------------------