From 551088f8a92a187d0fae475a5230d6d8fcbaa7ed Mon Sep 17 00:00:00 2001 From: Leigh Oliver Date: Sun, 22 Jan 2023 15:53:19 +1100 Subject: [PATCH 1/3] Added python tests using nox, added Errors for frames being made with 0 length or length ge 8 --- .gitignore | 10 ++++++-- crossbuild.sh | 2 +- jcan-python/Cargo.toml | 2 +- jcan-python/noxfile.py | 11 ++++++++ jcan-python/pytest.ini | 0 jcan-python/setup.py | 7 +++++ jcan-python/src/lib.rs | 21 ++++++++++++--- jcan-python/tests/test_jcan.py | 33 ++++++++++++++++++++++++ jcan/Cargo.toml | 2 +- jcan/src/lib.rs | 47 +++++++++++++++++++++------------- requirements.txt | 6 ++++- scripts-postbuild/Cargo.toml | 2 +- scripts-postbuild/build.rs | 11 +++++++- 13 files changed, 125 insertions(+), 29 deletions(-) create mode 100644 jcan-python/noxfile.py create mode 100644 jcan-python/pytest.ini create mode 100644 jcan-python/tests/test_jcan.py diff --git a/.gitignore b/.gitignore index 5796105..5c44515 100644 --- a/.gitignore +++ b/.gitignore @@ -19,8 +19,14 @@ Cargo.lock **/*.rs.bk # .envrc direnv, we don't want to git-track the virtual env -.direnv/** -.venv/** +**.direnv/** +**.venv/** +**.nox/** # Ignore .vscode folder .vscode/** + +# Ignore pycache +**/__pycache__/** + + diff --git a/crossbuild.sh b/crossbuild.sh index d5fd9bf..adc1978 100755 --- a/crossbuild.sh +++ b/crossbuild.sh @@ -78,4 +78,4 @@ function build_target { build_target "aarch64-unknown-linux-gnu" # Build for x86_64 -build_target "x86_64-unknown-linux-gnu" \ No newline at end of file +build_target "x86_64-unknown-linux-gnu" diff --git a/jcan-python/Cargo.toml b/jcan-python/Cargo.toml index fd043a3..106d5a4 100644 --- a/jcan-python/Cargo.toml +++ b/jcan-python/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "jcan_python" -version = "0.1.2" +version = "0.1.3" edition = "2021" publish = false diff --git a/jcan-python/noxfile.py b/jcan-python/noxfile.py new file mode 100644 index 0000000..fd313c4 --- /dev/null +++ b/jcan-python/noxfile.py @@ -0,0 +1,11 @@ +from os.path import dirname + +import nox + +SETUPTOOLS_RUST = dirname(__file__) + +@nox.session() +def test(session: nox.Session): + session.install(SETUPTOOLS_RUST, "pytest") + session.install("--no-build-isolation", ".") + session.run("pytest", *session.posargs) \ No newline at end of file diff --git a/jcan-python/pytest.ini b/jcan-python/pytest.ini new file mode 100644 index 0000000..e69de29 diff --git a/jcan-python/setup.py b/jcan-python/setup.py index 5bfe0d2..0623fc8 100644 --- a/jcan-python/setup.py +++ b/jcan-python/setup.py @@ -16,4 +16,11 @@ ) ], include_package_data=True, + # Requirements + install_requires=[ + "setuptools-rust", + ], + setup_requires=[ + "setuptools-rust", + ], ) diff --git a/jcan-python/src/lib.rs b/jcan-python/src/lib.rs index 5ba9bd5..a205bc4 100644 --- a/jcan-python/src/lib.rs +++ b/jcan-python/src/lib.rs @@ -31,8 +31,14 @@ struct PyJFrame { impl PyJBus { #[new] fn new(interface: String) -> PyResult { + // Unbox the result of new_jbus, and return it + let bus = new_jbus(interface).map_err(|e| { + PyOSError::new_err(format!("Error opening bus: {}", e)) + })?; + + // bus is a Box, so we need to dereference it Ok(PyJBus { - bus: *new_jbus(interface), + bus: *bus, }) } @@ -46,6 +52,16 @@ impl PyJBus { }) } + // Implement the receive_with_id method for the PyJBus + fn receive_with_id(&mut self, id: u32) -> PyResult { + let frame = self.bus.receive_with_id(id).map_err(|e| { + PyOSError::new_err(format!("Error receiving frame: {}", e)) + })?; + Ok(PyJFrame { + frame, + }) + } + // Implement the send method for the PyJBus fn send(&mut self, frame: PyJFrame) -> PyResult<()> { self.bus.send(frame.frame).map_err(|e| { @@ -73,11 +89,10 @@ impl PyJFrame { } } - #[pymodule] fn jcan_python(_py: Python<'_>, m: &PyModule) -> PyResult<()> { m.add_class::()?; m.add_class::()?; Ok(()) -} \ No newline at end of file +} diff --git a/jcan-python/tests/test_jcan.py b/jcan-python/tests/test_jcan.py new file mode 100644 index 0000000..cf7cbf6 --- /dev/null +++ b/jcan-python/tests/test_jcan.py @@ -0,0 +1,33 @@ +import jcan +import pytest + +def test_jcan_bus_open_failure(): + with pytest.raises(IOError): + channel = "vcan99" + bus = jcan.Bus(channel) + +def test_jcan_standard_frame(): + try: + print("Creating standard frame with ID 0x123") + f = jcan.Frame(0x123, bytes([0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88])) + print(f) + assert True + except: + assert False + +def test_jcan_ext_frame(): + try: + print("Creating extended frame with ID 0xF00123") + f = jcan.Frame(0xF00123, bytes([0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88])) + print(f) + assert True + except: + assert False + +def test_jcan_standard_frame_large(): + with pytest.raises(IOError): + f = jcan.Frame(0x123, bytes([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA])) + +def test_jcan_standard_frame_empty(): + with pytest.raises(IOError): + f = jcan.Frame(0x200, bytes([])) \ No newline at end of file diff --git a/jcan/Cargo.toml b/jcan/Cargo.toml index fc7feb9..447dbde 100644 --- a/jcan/Cargo.toml +++ b/jcan/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "jcan" -version = "0.1.2" +version = "0.1.3" edition = "2021" publish = false diff --git a/jcan/src/lib.rs b/jcan/src/lib.rs index 6e74847..9c7eed5 100644 --- a/jcan/src/lib.rs +++ b/jcan/src/lib.rs @@ -14,17 +14,13 @@ pub mod ffi { data: Vec, } - #[derive(Debug)] - pub struct JError { - message: String, - } - extern "Rust" { #[cxx_name = "Bus"] type JBus; #[cxx_name = "open_bus"] - fn new_jbus(interface: String) -> Box; + fn new_jbus(interface: String) -> Result>; fn receive(self: &mut JBus) -> Result; + fn receive_with_id(self: &mut JBus, id: u32) -> Result; fn send(self: &mut JBus, frame: JFrame) -> Result<()>; fn new_jframe(id: u32, data: Vec) -> Result; fn to_string(self: &JFrame) -> String; @@ -55,6 +51,16 @@ impl JBus { }; Ok(frame) } + + // Keeps receiving frames, returning the first frame with the given id + pub fn receive_with_id(&mut self, id: u32) -> Result { + loop { + let frame = self.receive()?; + if frame.id == id { + return Ok(frame); + } + } + } // Blocks until a frame is sent pub fn send(&mut self, frame: ffi::JFrame) -> Result<(), std::io::Error> { @@ -77,25 +83,30 @@ impl JBus { } } -// Implement Display for JError -impl std::fmt::Display for ffi::JError { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{}", self.message) - } -} // Public 'builder' method used to create C++ instances of the opaque JBus type -pub fn new_jbus(interface: String) -> Box { - // If an error is caught here, it will be propagated to the C++ side - // as a std::runtime_error, with the message "CanSocketOpenError" - // TODO: Make this fail gracefully on C++ - let socket = CanSocket::open(&interface).expect("CanSocketOpenError"); - Box::new(JBus { socket }) +pub fn new_jbus(interface: String) -> Result, std::io::Error> { + // Open and map to Error if it fails + let socket = CanSocket::open(&interface).map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; + Ok(Box::new(JBus { + socket, + })) } // Builder for jframe, used to create C++ instances of the opaque JFrame type // Takes in a u32 id, and a Vec data pub fn new_jframe(id: u32, data: Vec) -> Result { + // Check if data is empty (zero length) + if data.len() == 0 { + // Print error message that shows N = 0 bytes + return Err(std::io::Error::new(std::io::ErrorKind::Other, "Data length cannot be 0 bytes")); + } + + // Check if data is too long + if data.len() > 8 { + // Print error message that shows N > 8 bytes + return Err(std::io::Error::new(std::io::ErrorKind::Other, format!("Data length {} > 8 bytes", data.len()))); + } Ok(ffi::JFrame { id, data, diff --git a/requirements.txt b/requirements.txt index 775bb16..1134d65 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,14 @@ +attrs==22.2.0 +exceptiongroup==1.1.0 +iniconfig==2.0.0 msgpack==1.0.4 packaging==23.0 patchelf==0.17.2.0 +pluggy==1.0.0 +pytest==7.2.1 python-can==4.1.0 semantic-version==2.10.0 setuptools-rust==1.5.2 tomli==2.0.1 typing_extensions==4.4.0 wrapt==1.14.1 -wheel diff --git a/scripts-postbuild/Cargo.toml b/scripts-postbuild/Cargo.toml index 6f0f523..5d7a3ab 100644 --- a/scripts-postbuild/Cargo.toml +++ b/scripts-postbuild/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "scripts_postbuild" -version = "0.1.2" +version = "0.1.3" edition = "2021" publish = false diff --git a/scripts-postbuild/build.rs b/scripts-postbuild/build.rs index 92b5a56..d87f208 100644 --- a/scripts-postbuild/build.rs +++ b/scripts-postbuild/build.rs @@ -23,7 +23,7 @@ fn main() { // - jcan.cc // - cxx.h // This directry can then easily be include-ed in C++ projects - + let target = env::var("TARGET").unwrap(); println!("cargo:warning=target: {}", target); @@ -36,6 +36,15 @@ fn main() { // We will be targeting the 'jcan' subdirectory - this is hard-coded. let manifest_dir = project_dir; + // Delete the project-level out directory + let out_dir = Path::new(&manifest_dir).join("out"); + println!("cargo:warning=project_out_dir: {}", out_dir.display()); + + // Remove the out directory, if it exists + if out_dir.exists() { + fs::remove_dir_all(&out_dir).unwrap(); + } + let out_dir = Path::new(&manifest_dir).join("out").join(&profile).join(&target).join("jcan"); println!("cargo:warning=out_dir: {}", out_dir.display()); From 0e7d5671b0c21eecedd25dd28f3e4e7d1a941226 Mon Sep 17 00:00:00 2001 From: Leigh Oliver Date: Sun, 22 Jan 2023 16:04:10 +1100 Subject: [PATCH 2/3] Validated working examples for receive_with_id --- examples/cpp_candump/CMakeLists.txt | 12 ++++---- examples/cpp_candump_with_id/.gitignore | 1 + examples/cpp_candump_with_id/CMakeLists.txt | 31 +++++++++++++++++++++ examples/cpp_candump_with_id/build.sh | 5 ++++ examples/cpp_candump_with_id/include/jcan | 1 + examples/cpp_candump_with_id/src/main.cpp | 30 ++++++++++++++++++++ examples/python_candump_with_id/main.py | 14 ++++++++++ scripts-postbuild/build.rs | 5 ---- 8 files changed, 88 insertions(+), 11 deletions(-) create mode 100644 examples/cpp_candump_with_id/.gitignore create mode 100644 examples/cpp_candump_with_id/CMakeLists.txt create mode 100755 examples/cpp_candump_with_id/build.sh create mode 120000 examples/cpp_candump_with_id/include/jcan create mode 100644 examples/cpp_candump_with_id/src/main.cpp create mode 100644 examples/python_candump_with_id/main.py diff --git a/examples/cpp_candump/CMakeLists.txt b/examples/cpp_candump/CMakeLists.txt index 6e0d1a5..03d8a8c 100644 --- a/examples/cpp_candump/CMakeLists.txt +++ b/examples/cpp_candump/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.22) -project(cpp_cansend) +project(cpp_candump) # ROS Foxy / Ubuntu 20 targets C++14 set(CMAKE_CXX_STANDARD 14) @@ -7,22 +7,22 @@ set(CMAKE_CXX_STANDARD 14) set(JCAN_BRIDGE_CPP ${CMAKE_SOURCE_DIR}/include/jcan/jcan.cc) set(JCAN_LIB ${CMAKE_SOURCE_DIR}/include/jcan/${CMAKE_STATIC_LIBRARY_PREFIX}jcan${CMAKE_STATIC_LIBRARY_SUFFIX}) -add_executable(cpp_cansend src/main.cpp ${JCAN_BRIDGE_CPP}) +add_executable(cpp_candump src/main.cpp ${JCAN_BRIDGE_CPP}) target_include_directories( - cpp_cansend + cpp_candump PRIVATE include/ ${CMAKE_SOURCE_DIR}/include/jcan/ ) -target_link_libraries(cpp_cansend ${JCAN_LIB}) +target_link_libraries(cpp_candump ${JCAN_LIB}) # Windows-only configuration if(WIN32) - target_link_libraries(cpp_cansend userenv ws2_32 bcrypt) + target_link_libraries(cpp_candump userenv ws2_32 bcrypt) set_target_properties( - cpp_cansend + cpp_candump PROPERTIES MSVC_RUNTIME_LIBRARY "MultiThreadedDLL" RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR} diff --git a/examples/cpp_candump_with_id/.gitignore b/examples/cpp_candump_with_id/.gitignore new file mode 100644 index 0000000..f65519e --- /dev/null +++ b/examples/cpp_candump_with_id/.gitignore @@ -0,0 +1 @@ +build/** diff --git a/examples/cpp_candump_with_id/CMakeLists.txt b/examples/cpp_candump_with_id/CMakeLists.txt new file mode 100644 index 0000000..80ec715 --- /dev/null +++ b/examples/cpp_candump_with_id/CMakeLists.txt @@ -0,0 +1,31 @@ +cmake_minimum_required(VERSION 3.22) +project(cpp_candump_with_id) + +# ROS Foxy / Ubuntu 20 targets C++14 +set(CMAKE_CXX_STANDARD 14) + +set(JCAN_BRIDGE_CPP ${CMAKE_SOURCE_DIR}/include/jcan/jcan.cc) +set(JCAN_LIB ${CMAKE_SOURCE_DIR}/include/jcan/${CMAKE_STATIC_LIBRARY_PREFIX}jcan${CMAKE_STATIC_LIBRARY_SUFFIX}) + +add_executable(cpp_candump_with_id src/main.cpp ${JCAN_BRIDGE_CPP}) + +target_include_directories( + cpp_candump_with_id + PRIVATE + include/ + ${CMAKE_SOURCE_DIR}/include/jcan/ +) + +target_link_libraries(cpp_candump_with_id ${JCAN_LIB}) + +# Windows-only configuration +if(WIN32) + target_link_libraries(cpp_candump_with_id userenv ws2_32 bcrypt) + set_target_properties( + cpp_candump_with_id + PROPERTIES + MSVC_RUNTIME_LIBRARY "MultiThreadedDLL" + RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR} + RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR} + ) +endif() diff --git a/examples/cpp_candump_with_id/build.sh b/examples/cpp_candump_with_id/build.sh new file mode 100755 index 0000000..867ca8e --- /dev/null +++ b/examples/cpp_candump_with_id/build.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +mkdir -p build +cd build +cmake .. +cmake --build . \ No newline at end of file diff --git a/examples/cpp_candump_with_id/include/jcan b/examples/cpp_candump_with_id/include/jcan new file mode 120000 index 0000000..585f0cc --- /dev/null +++ b/examples/cpp_candump_with_id/include/jcan @@ -0,0 +1 @@ +../../../out/release/x86_64-unknown-linux-gnu/jcan \ No newline at end of file diff --git a/examples/cpp_candump_with_id/src/main.cpp b/examples/cpp_candump_with_id/src/main.cpp new file mode 100644 index 0000000..67ab2aa --- /dev/null +++ b/examples/cpp_candump_with_id/src/main.cpp @@ -0,0 +1,30 @@ +#include +#include +#include +#include "jcan.h" + +using namespace org::jcan; + +/* +C++-14 example of using the jcan library. +*/ + +// main function which opens a JBus, and prints incoming frames +int main(int argc, char **argv) { + // Open the CAN bus, and un-boxes it! + Bus *bus = org::jcan::open_bus("vcan0").into_raw(); + + // Run forever + while (1) { + // Say we are only interested in frames with id 0x42 + printf("Waiting for frame with id 0x42\n"); + + // Receve a frame + Frame frame = bus->receive_with_id(0x42); + + // Print frame using it's to_string method + printf("%s\n", frame.to_string().c_str()); + } + + return 0; +} \ No newline at end of file diff --git a/examples/python_candump_with_id/main.py b/examples/python_candump_with_id/main.py new file mode 100644 index 0000000..5ffbc1d --- /dev/null +++ b/examples/python_candump_with_id/main.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python +import jcan + +bustype = 'socketcan' +channel = 'vcan0' + +bus = jcan.Bus(channel) + +while True: + print("Waiting for frame with ID 0x42") + f = bus.receive_with_id(0x42) + print(str(f)) + + diff --git a/scripts-postbuild/build.rs b/scripts-postbuild/build.rs index d87f208..f1cd7fb 100644 --- a/scripts-postbuild/build.rs +++ b/scripts-postbuild/build.rs @@ -40,11 +40,6 @@ fn main() { let out_dir = Path::new(&manifest_dir).join("out"); println!("cargo:warning=project_out_dir: {}", out_dir.display()); - // Remove the out directory, if it exists - if out_dir.exists() { - fs::remove_dir_all(&out_dir).unwrap(); - } - let out_dir = Path::new(&manifest_dir).join("out").join(&profile).join(&target).join("jcan"); println!("cargo:warning=out_dir: {}", out_dir.display()); From 21a759e646ed85ab912f8a2c7f15e6f4e7df431c Mon Sep 17 00:00:00 2001 From: Leigh Oliver Date: Sun, 22 Jan 2023 16:07:24 +1100 Subject: [PATCH 3/3] Bumped jcan python version --- jcan-python/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jcan-python/setup.py b/jcan-python/setup.py index 0623fc8..7740903 100644 --- a/jcan-python/setup.py +++ b/jcan-python/setup.py @@ -4,7 +4,7 @@ setup( name="jcan", - version="0.1.2", + version="0.1.3", packages=["jcan"], zip_safe=False, rust_extensions=[