Skip to content

Commit f3c5f0d

Browse files
davidt315David Tarazi
andauthored
Initial migration from internal source (#1)
Co-authored-by: David Tarazi <david@polymathrobotics.com>
1 parent c78f6f4 commit f3c5f0d

22 files changed

Lines changed: 1850 additions & 3 deletions

‎.github/workflows/build.yml‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
---
2+
3+
name: Build and test
4+
on:
5+
pull_request:
6+
push:
7+
branches:
8+
- main
9+
10+
jobs:
11+
build_and_test:
12+
runs-on: ubuntu-latest
13+
strategy:
14+
fail-fast: false
15+
matrix:
16+
ros: [humble, jazzy, lyrical]
17+
name: ROS 2 ${{ matrix.ros }}
18+
container:
19+
image: polymathrobotics/ros:${{ matrix.ros }}-builder-ubuntu
20+
# rosdep installs pip deps (e.g. python3-cantools-pip) globally as root.
21+
# On Python >= 3.11 (Ubuntu 24.04 images: jazzy, lyrical), PEP 668 requires
22+
# opting in to installing alongside externally-managed packages.
23+
env:
24+
PIP_BREAK_SYSTEM_PACKAGES: 1
25+
steps:
26+
- uses: actions/checkout@v4
27+
- uses: ros-tooling/action-ros-ci@v0.4
28+
with:
29+
target-ros2-distro: ${{ matrix.ros }}
30+
coverage-result: false
31+
- uses: actions/upload-artifact@v4
32+
with:
33+
name: colcon-logs-${{ matrix.ros }}
34+
path: ros_ws/log

‎.github/workflows/pre-commit.yml‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
name: pre-commit
3+
4+
on:
5+
pull_request:
6+
push:
7+
branches: [main]
8+
9+
jobs:
10+
pre-commit:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v3
14+
- uses: actions/setup-python@v3
15+
- uses: pre-commit/action@v3.0.1

‎.gitignore‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/.ruff.toml
2+
/*.egg-info/
3+
__pycache__/
4+
/.pytest_tmp*/

‎.pre-commit-config.yaml‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
repos:
3+
- repo: https://github.com/polymathrobotics/polymath_code_standard
4+
rev: v2.2.0
5+
hooks:
6+
# Basic checks and fixes that apply to any text file and the git repository itself
7+
- id: polymath-general
8+
- id: polymath-copyright
9+
args: [--license, Apache-2.0, --copyright-org, 'Polymath Robotics, Inc.', --reuse-style]
10+
# Specific languages
11+
- id: polymath-python
12+
- id: polymath-cpp
13+
- id: polymath-ros
14+
- id: polymath-shell
15+
- id: polymath-cmake
16+
- id: polymath-docker
17+
- id: polymath-markdown
18+
- id: polymath-xml
19+
- id: polymath-yaml
20+
- id: polymath-toml
21+
- id: polymath-json

‎LICENSE‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@
186186
same "printed page" as the copyright notice for easier
187187
identification within third-party archives.
188188

189-
Copyright [yyyy] [name of copyright owner]
189+
Copyright 2026 Polymath Robotics, Inc.
190190

191191
Licensed under the Apache License, Version 2.0 (the "License");
192192
you may not use this file except in compliance with the License.

‎README.md‎

Lines changed: 93 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,93 @@
1-
# dbc_gen_cpp
2-
DBC CAN Message Type Generator for cpp
1+
# DBC CAN Message Type Generator for C++
2+
3+
Extends the code generation capability of `cantools`, which outputs C sources from DBC frame definitions, to create idiomatic C++ interfaces for using CAN message types.
4+
5+
## Usage
6+
7+
In `package.xml`, you'll need this as a `build_depend`.
8+
9+
In your `CMakeLists.txt`:
10+
11+
```cmake
12+
find_package(dbc_gen_cpp REQUIRED)
13+
...
14+
15+
generate_dbc_cpp(my_can_library_name
16+
DBC ${CMAKE_CURRENT_SOURCE_DIR}/database.dbc
17+
)
18+
19+
...
20+
21+
target_link_libraries(my_library PUBLIC my_can_library_name)
22+
```
23+
24+
### CAN Messages
25+
26+
Message structs are defined in the library name's namespace.
27+
28+
They can be implicitly converted to `can_frame` from `<linux/can.h>`
29+
30+
```c++
31+
#include "my_can_library_name/my_can_library_name.hpp"
32+
33+
...
34+
35+
my_can_library_name::MessageName message{value};
36+
my_can_socket->send(message);
37+
can_frame frame = message;
38+
my_can_socket->send(frame);
39+
40+
```
41+
42+
### CAN Handler - Receive/Subscribe to CAN Messages
43+
44+
A helper class `dbc_gen_cpp::CANHandler` is provided.
45+
46+
Simply register a handler function for a type via `set_handler`, then forward all `can_frame`s received to the `handle()` method to trigger the registered handler functions with the typed structs.
47+
48+
```c++
49+
dbc_gen_cpp::CANHandler handler;
50+
handler.set_handler<my_can_library_name::MessageName>(
51+
[](const my_can_library_name::MessageName & message) {
52+
printf('Received MessageName (value %f)\n', message.value);
53+
});
54+
55+
my_can_socket.on_receive(
56+
[&](can_frame frame) {
57+
handler.handle(frame);
58+
});
59+
```
60+
61+
### J1939-Specific Handling
62+
63+
#### **How is J1939 Defined in DBCs**
64+
DBC files indicate whether a message uses **Standard CAN** or **J1939** via the two lines below.
65+
The first line defines the attribute itself. The second line is applied **per message** and
66+
should appear once for each message that is intended to be treated as J1939, using that
67+
message’s specific CAN ID.
68+
69+
```
70+
BA_DEF_ BO_ "VFrameFormat" ENUM "StandardCAN","ExtendedCAN","reserved","J1939PG";
71+
BA_ "VFrameFormat" BO_ 2364539904 3;
72+
```
73+
74+
#### **J1939 Specific Constants**
75+
The following will only be defined if a message is labeled as J1939 in the DBC:
76+
- static constexpr uint32_t Pgn
77+
- static constexpr uint8_t DefaultPriority = 3;
78+
- static constexpr uint8_t SourceAddress = 37;
79+
- static constexpr bool IsPduBroadcast = true;
80+
81+
The following will only be defined if the message is J1939 and it's of type PDU1 (destination specific):
82+
- static constexpr uint8_t DefaultDestinationAddress
83+
84+
> Note: If you want to safely test if a message is of the J1939 Standard, use the variable `IsJ1939`.
85+
86+
#### **J1939 Logic Changes**
87+
1. When passing a can frame into the explicit constructor, it will only check if the PGN of the incoming frame matches, instead of the whole CAN ID.
88+
89+
# Tests for dbc_gen_cpp
90+
91+
Since [`dbc_gen_cpp`](dbc_gen_cpp/) provides mostly functionality via the `install/` space with CMake functions and a Python package with importlib-registered Jinja templates, it's not possible to test the full usage of that package internally.
92+
93+
`test_dbc_gen_cpp` is fully dedicated to providing tests, it is not meant to be used as a dependency by any package.

‎dbc_gen_cpp/CMakeLists.txt‎

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# SPDX-FileCopyrightText: 2026 Polymath Robotics, Inc.
2+
# SPDX-License-Identifier: Apache-2.0
3+
cmake_minimum_required(VERSION 3.22)
4+
project(dbc_gen_cpp)
5+
6+
if(NOT CMAKE_CXX_STANDARD)
7+
set(CMAKE_CXX_STANDARD 17)
8+
endif()
9+
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
10+
add_compile_options(-Wall -Wextra -Wpedantic)
11+
add_link_options(-Wl,-no-undefined)
12+
endif()
13+
14+
find_package(ament_cmake REQUIRED)
15+
find_package(ament_cmake_python REQUIRED)
16+
17+
ament_python_install_package(${PROJECT_NAME}
18+
SCRIPTS_DESTINATION bin
19+
)
20+
21+
add_library(${PROJECT_NAME} INTERFACE)
22+
target_include_directories(${PROJECT_NAME} INTERFACE
23+
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
24+
$<INSTALL_INTERFACE:include/${PROJECT_NAME}>
25+
)
26+
27+
install(
28+
TARGETS ${PROJECT_NAME}
29+
EXPORT ${PROJECT_NAME}Targets
30+
ARCHIVE DESTINATION lib
31+
LIBRARY DESTINATION lib
32+
RUNTIME DESTINATION bin
33+
INCLUDES DESTINATION include
34+
)
35+
install(
36+
EXPORT ${PROJECT_NAME}Targets
37+
NAMESPACE ${PROJECT_NAME}::
38+
DESTINATION share/${PROJECT_NAME}/cmake
39+
)
40+
install(
41+
DIRECTORY include/
42+
DESTINATION include
43+
)
44+
install(
45+
DIRECTORY cmake
46+
DESTINATION share/${PROJECT_NAME}
47+
)
48+
49+
if(BUILD_TESTING)
50+
endif()
51+
52+
ament_export_targets(${PROJECT_NAME}Targets HAS_LIBRARY_TARGET)
53+
ament_package(
54+
CONFIG_EXTRAS "dbc_gen_cpp-extras.cmake"
55+
)
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# SPDX-FileCopyrightText: 2026 Polymath Robotics, Inc.
2+
# SPDX-License-Identifier: Apache-2.0
3+
function(generate_dbc_cpp library_name)
4+
find_package(Python3 REQUIRED COMPONENTS Interpreter)
5+
6+
set(one_value_args DBC)
7+
cmake_parse_arguments(ARG "" "${one_value_args}" "" ${ARGN})
8+
if(NOT ARG_DBC)
9+
message(FATAL_ERROR "generate_dbc_cpp: Missing required keyword argument DBC")
10+
endif()
11+
12+
set(gen_basedir ${CMAKE_CURRENT_BINARY_DIR}/dbc_gen_cpp)
13+
set(gen_dir ${gen_basedir}/${library_name})
14+
15+
set(generated_c ${gen_dir}/${library_name}.c)
16+
set(generated_h ${gen_dir}/${library_name}.h)
17+
set(generated_hpp ${gen_dir}/${library_name}.hpp)
18+
set(generated_files ${generated_c} ${generated_h} ${generated_hpp})
19+
20+
# Kind of awkward, but makes the sources get regenerated if the generator tool changes, by depending on the generator sources.
21+
# Finds the python module directory via Python3 import
22+
execute_process(
23+
COMMAND ${Python3_EXECUTABLE} -c "import dbc_gen_cpp, os; print(os.path.dirname(dbc_gen_cpp.__file__))"
24+
OUTPUT_VARIABLE GENERATOR_DIR
25+
OUTPUT_STRIP_TRAILING_WHITESPACE
26+
)
27+
file(GLOB_RECURSE GENERATOR_SOURCES CONFIGURE_DEPENDS
28+
"${GENERATOR_DIR}/*.py"
29+
"${GENERATOR_DIR}/templates/*.j2"
30+
)
31+
32+
# Generate the source files
33+
add_custom_command(
34+
OUTPUT ${generated_files}
35+
COMMAND ${Python3_EXECUTABLE} -m dbc_gen_cpp ${ARG_DBC} -o ${gen_dir} -n ${library_name}
36+
DEPENDS ${ARG_DBC} ${GENERATOR_SOURCES}
37+
COMMENT "Generating C source from DBC ${ARG_DBC} with cantools"
38+
VERBATIM
39+
)
40+
add_custom_target(${library_name}_c_sources
41+
DEPENDS ${generated_c} ${generated_h}
42+
)
43+
44+
# Create the library target from generated sources
45+
add_library(${library_name} STATIC ${generated_c})
46+
add_dependencies(${library_name} ${library_name}_c_sources)
47+
target_link_libraries(${library_name} PUBLIC dbc_gen_cpp::dbc_gen_cpp)
48+
target_include_directories(${library_name}
49+
PUBLIC ${gen_basedir}
50+
)
51+
52+
# Install the generated files to the install space, just in case they're included in public headers
53+
install(
54+
FILES ${generated_h} ${generated_hpp}
55+
DESTINATION include/${library_name}
56+
)
57+
endfunction()
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# SPDX-FileCopyrightText: 2026 Polymath Robotics, Inc.
2+
# SPDX-License-Identifier: Apache-2.0
3+
include("${dbc_gen_cpp_DIR}/generate_dbc_cpp.cmake")
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# SPDX-FileCopyrightText: 2026 Polymath Robotics, Inc.
2+
# SPDX-License-Identifier: Apache-2.0

0 commit comments

Comments
 (0)