drivers/uavcan: subtree merge last working libuavcan (preserving history) (#23819)

- fully absorb the libuavcan submodule (renamed libdronecan to deconflict) up to our last good working commit and preserve all history (upstream libuavcan was broken and then marked deprecated)
 - fixes https://github.com/PX4/PX4-Autopilot/issues/23727
 - this puts us in a much better position to be able to evolve the library going forward ow that we have full control in tree
This commit is contained in:
Daniel Agar
2024-10-17 16:20:19 -04:00
committed by GitHub
238 changed files with 40766 additions and 46 deletions
+6 -4
View File
@@ -2,10 +2,6 @@
path = src/modules/mavlink/mavlink
url = https://github.com/mavlink/mavlink.git
branch = master
[submodule "src/drivers/uavcan/libuavcan"]
path = src/drivers/uavcan/libuavcan
url = https://github.com/dronecan/libuavcan.git
branch = main
[submodule "Tools/simulation/jmavsim/jMAVSim"]
path = Tools/simulation/jmavsim/jMAVSim
url = https://github.com/PX4/jMAVSim.git
@@ -87,3 +83,9 @@
path = src/drivers/actuators/vertiq_io/iq-module-communication-cpp
url = https://github.com/PX4/iq-module-communication-cpp.git
branch = master
[submodule "src/drivers/uavcan/libdronecan/dsdl"]
path = src/drivers/uavcan/libdronecan/dsdl
url = https://github.com/PX4/DSDL.git
[submodule "src/drivers/uavcan/libdronecan/libuavcan/dsdl_compiler/pydronecan"]
path = src/drivers/uavcan/libdronecan/libuavcan/dsdl_compiler/pydronecan
url = https://github.com/dronecan/pydronecan
+1 -1
View File
@@ -828,7 +828,7 @@ RECURSIVE = YES
# Note that relative paths are relative to the directory from which doxygen is
# run.
EXCLUDE = @CMAKE_SOURCE_DIR@/src/modules/uavcan/libuavcan \
EXCLUDE = @CMAKE_SOURCE_DIR@/src/modules/uavcan/libdronecan \
@CMAKE_SOURCE_DIR@/src/examples \
@CMAKE_SOURCE_DIR@/src/templates
+1 -1
View File
@@ -11,7 +11,7 @@ exec find boards msg src platforms test \
-path platforms/nuttx/NuttX -prune -o \
-path platforms/qurt/dspal -prune -o \
-path src/drivers/ins/vectornav/libvnc -prune -o \
-path src/drivers/uavcan/libuavcan -prune -o \
-path src/drivers/uavcan/libdronecan -prune -o \
-path src/drivers/uavcan/uavcan_drivers/kinetis/driver/include/uavcan_kinetis -prune -o \
-path src/drivers/cyphal/libcanard -prune -o \
-path src/lib/crypto/monocypher -prune -o \
+22 -20
View File
@@ -31,12 +31,14 @@
#
############################################################################
set(LIBUAVCAN_DIR ${PX4_SOURCE_DIR}/src/drivers/uavcan/libuavcan)
set(LIBUAVCAN_DIR_DRIVERS ${PX4_SOURCE_DIR}/src/drivers/uavcan/uavcan_drivers)
set(LIBDRONECAN_DIR ${PX4_SOURCE_DIR}/src/drivers/uavcan/libdronecan)
set(LIBDRONECAN_DIR_DRIVERS ${PX4_SOURCE_DIR}/src/drivers/uavcan/uavcan_drivers)
px4_add_git_submodule(TARGET git_uavcan PATH ${LIBUAVCAN_DIR})
set(DSDLC_DIR "${PX4_SOURCE_DIR}/src/drivers/uavcan/libdronecan/dsdl")
px4_add_git_submodule(TARGET git_uavcan_dsdl PATH ${DSDLC_DIR})
px4_add_git_submodule(TARGET git_uavcan_pydronecan PATH ${LIBDRONECAN_DIR}/libuavcan/dsdl_compiler/pydronecan)
set(UAVCAN_USE_CPP03 ON CACHE BOOL "uavcan cpp03")
set(UAVCAN_PLATFORM "generic")
if(CONFIG_ARCH_CHIP)
@@ -90,25 +92,24 @@ add_compile_options(
-Wno-address-of-packed-member
)
set(CMAKE_WARN_DEPRECATED OFF CACHE BOOL "" FORCE) # silence libuavcan deprecation warning for now (TODO: fix and remove)
add_subdirectory(${LIBUAVCAN_DIR} libuavcan EXCLUDE_FROM_ALL)
add_subdirectory(${LIBDRONECAN_DIR} libdronecan EXCLUDE_FROM_ALL)
add_dependencies(uavcan prebuild_targets)
# driver
add_subdirectory(${LIBUAVCAN_DIR_DRIVERS}/${UAVCAN_DRIVER}/driver libuavcan_drivers EXCLUDE_FROM_ALL)
add_subdirectory(${LIBDRONECAN_DIR_DRIVERS}/${UAVCAN_DRIVER}/driver libuavcan_drivers EXCLUDE_FROM_ALL)
target_include_directories(uavcan_${UAVCAN_DRIVER}_driver PUBLIC
${LIBUAVCAN_DIR}/libuavcan/include
${LIBUAVCAN_DIR}/libuavcan/include/dsdlc_generated
${LIBDRONECAN_DIR}/libuavcan/include
${LIBDRONECAN_DIR}/libuavcan/include/dsdlc_generated
)
# generated DSDL
set(DSDLC_DIR "${PX4_SOURCE_DIR}/src/drivers/uavcan/dsdl")
set(DSDLC_INPUTS
"${LIBUAVCAN_DIR}/dsdl/ardupilot"
"${LIBUAVCAN_DIR}/dsdl/com"
"${LIBUAVCAN_DIR}/dsdl/cuav"
"${LIBUAVCAN_DIR}/dsdl/dronecan"
"${LIBUAVCAN_DIR}/dsdl/uavcan"
"${DSDLC_DIR}/ardupilot"
"${DSDLC_DIR}/com"
"${DSDLC_DIR}/cuav"
"${DSDLC_DIR}/dronecan"
"${DSDLC_DIR}/uavcan"
)
set(DSDLC_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/include/dsdlc_generated")
@@ -119,7 +120,7 @@ foreach(DSDLC_INPUT ${DSDLC_INPUTS})
endforeach(DSDLC_INPUT)
add_custom_command(OUTPUT px4_uavcan_dsdlc_run.stamp
COMMAND
${PYTHON_EXECUTABLE} ${LIBUAVCAN_DIR}/libuavcan/dsdl_compiler/libuavcan_dsdlc
${PYTHON_EXECUTABLE} ${LIBDRONECAN_DIR}/libuavcan/dsdl_compiler/libuavcan_dsdlc
--outdir ${DSDLC_OUTPUT} ${DSDLC_INPUTS}
#--verbose
COMMAND ${CMAKE_COMMAND} -E touch px4_uavcan_dsdlc_run.stamp
@@ -138,10 +139,10 @@ px4_add_module(
#-DDEBUG_BUILD
INCLUDES
${DSDLC_OUTPUT}
${LIBUAVCAN_DIR}/libuavcan/include
${LIBUAVCAN_DIR}/libuavcan/include/dsdlc_generated
${LIBUAVCAN_DIR}/libuavcan_drivers/posix/include
${LIBUAVCAN_DIR_DRIVERS}/${UAVCAN_DRIVER}/driver/include
${LIBDRONECAN_DIR}/libuavcan/include
${LIBDRONECAN_DIR}/libuavcan/include/dsdlc_generated
${LIBDRONECAN_DIR_DRIVERS}/posix/include
${LIBDRONECAN_DIR_DRIVERS}/${UAVCAN_DRIVER}/driver/include
SRCS
arming_status.cpp
arming_status.hpp
@@ -191,7 +192,8 @@ px4_add_module(
mixer_module
version
git_uavcan
git_uavcan_dsdl
git_uavcan_pydronecan
uavcan_${UAVCAN_DRIVER}_driver
drivers_rangefinder # Fix undefined reference when no distance sensors are selected
+30
View File
@@ -0,0 +1,30 @@
# Build outputs
*.o
*.d
lib*.so
lib*.so.*
*.a
build*/
.dep
__pycache__
*.pyc
# Eclipse
.metadata
.settings
.project
.cproject
.pydevproject
.gdbinit
# vsstudio code
.vscode
# vagrant
.vagrant
# libuavcan DSDL compiler default output directory
dsdlc_generated
# Log files
*.log
@@ -0,0 +1,113 @@
#
# Copyright (C) 2014 Pavel Kirienko <pavel.kirienko@gmail.com>
#
cmake_minimum_required(VERSION 3.9)
project(uavcan C CXX)
#
# Build options
#
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(DEFAULT_UAVCAN_PLATFORM "linux")
endif()
# options are listed in a table format below
set(opts
# name: type: default value: string options list : description
"CMAKE_BUILD_TYPE:STRING:RelWithDebInfo:Debug Release RelWithDebInfo MinSizeRel:Build type."
"CMAKE_CXX_FLAGS:STRING:::C++ flags."
"CMAKE_C_FLAGS:STRING:::C flags."
"UAVCAN_PLATFORM:STRING:generic:generic kinetis linux stm32:Platform."
"CONTINUOUS_INTEGRATION_BUILD:BOOL:OFF::Disable error redirection and timing tests"
"UAVCAN_CMAKE_VERBOSE:BOOL:OFF::Verbose CMake configure output"
)
foreach(_opt ${opts})
# arguments are : delimited
string(REPLACE ":" ";" _opt ${_opt})
list(GET _opt 0 _name)
list(GET _opt 1 _type)
list(GET _opt 2 _default)
list(GET _opt 3 _options)
list(GET _opt 4 _descr)
# options are space delimited
string(REPLACE " " ";" _options "${_options}")
# if a default has not already been defined, use default from table
if(NOT DEFINED DEFAULT_${_name})
set(DEFAULT_${_name} ${_default})
endif()
# option has not been set already or it is empty, set it with the default
if(NOT DEFINED ${_name} OR ${_name} STREQUAL "")
set(${_name} ${DEFAULT_${_name}})
endif()
# create a cache from the variable and force it to set
if(UAVCAN_CMAKE_VERBOSE)
message(STATUS "${_name}\t: ${${_name}} : ${_descr}")
endif()
set("${_name}" "${${_name}}" CACHE "${_type}" "${_descr}" FORCE)
# if an options list is provided for the cache, set it
if("${_type}" STREQUAL "STRING" AND NOT "${_options}" STREQUAL "")
set_property(CACHE ${_name} PROPERTY STRINGS ${_options})
endif()
endforeach()
#
# Set flags
#
include_directories(
./libuavcan/include/
./libuavcan/include/dsdlc_generated
)
#
# Install
#
# DSDL definitions
install(DIRECTORY dsdl DESTINATION share/uavcan)
#
# Googletest
#
if( CMAKE_BUILD_TYPE STREQUAL "Debug" )
# (Taken from googletest/README.md documentation)
# GTest executables
# Download and unpack googletest at configure time
configure_file(CMakeLists.txt.in googletest-download/CMakeLists.txt)
execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" .
RESULT_VARIABLE result
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/googletest-download )
if(result)
message(WARNING "CMake step for googletest failed: ${result}")
else()
execute_process(COMMAND ${CMAKE_COMMAND} --build .
RESULT_VARIABLE result
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/googletest-download )
if(result)
message(WARNING "Build step for googletest failed: ${result}")
else()
# Prevent overriding the parent project's compiler/linker
# settings on Windows
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
# Add googletest directly to our build. This defines
# the gtest and gtest_main targets.
add_subdirectory(${CMAKE_BINARY_DIR}/googletest-src
${CMAKE_BINARY_DIR}/googletest-build
EXCLUDE_FROM_ALL)
set(GTEST_FOUND ON)
set(BUILD_TESTING ON)
enable_testing()
endif()
endif()
endif()
#
# Subdirectories
#
# library
add_subdirectory(libuavcan)
# vim: set et ft=cmake fenc=utf-8 ff=unix sts=4 sw=4 ts=4 :
@@ -0,0 +1,15 @@
cmake_minimum_required(VERSION 3.9)
project(googletest-download NONE)
include(ExternalProject)
ExternalProject_Add(googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG ba96d0b1161f540656efdaed035b3c062b60e006
SOURCE_DIR "${CMAKE_BINARY_DIR}/googletest-src"
BINARY_DIR "${CMAKE_BINARY_DIR}/googletest-build"
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
TEST_COMMAND ""
)
+20
View File
@@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2014 Pavel Kirienko
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:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
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.
+120
View File
@@ -0,0 +1,120 @@
DroneCAN stack in C++
=====================
Portable reference implementation of the [DroneCAN protocol stack](http://dronecan.org) in C++ for embedded systems
and Linux.
DroneCAN is a lightweight protocol designed for reliable communication in aerospace and robotic applications via CAN bus.
## Documentation
* [DroneCAN website](http://dronecan.org)
* [DroneCAN forum](https://dronecan.org/discord)
## Library usage
### Cloning the repository
```bash
git clone https://github.com/DroneCAN/libuavcan
cd libuavcan
git submodule update --init
```
If this repository is used as a git submodule in your project, make sure to use `--recursive` when updating it.
### Using in a Linux application
Libuavcan can be built as a static library and installed on the system globally as shown below.
```bash
mkdir build
cd build
cmake .. # Default build type is RelWithDebInfo, which can be overriden if needed.
make -j8
sudo make install
```
The following components will be installed:
* Libuavcan headers and the static library
* Generated DSDL headers
* Libuavcan DSDL compiler (a Python script named `libuavcan_dsdlc`)
* Libuavcan DSDL compiler's support library (a Python package named `libuavcan_dsdl_compiler`)
Note that Pyuavcan (an implementation of DroneCAN in Python) will not be installed.
You will need to install it separately if you intend to use the Libuavcan's DSDL compiler in your applications.
It is also possible to use the library as a submodule rather than installing it system-wide.
Please refer to the example applications supplied with the Linux platform driver for more information.
### Using with an embedded system
For ARM targets, it is recommended to use [GCC ARM Embedded](https://launchpad.net/gcc-arm-embedded);
however, any other standard-compliant C++ compiler should also work.
## Library development
Despite the fact that the library itself can be used on virtually any platform that has a standard-compliant
C++11 compiler, the library development process assumes that the host OS is Linux.
Prerequisites:
* Google test library for C++ - gtest (dowloaded as part of the build from [github](https://github.com/google/googletest))
* C++11 capable compiler with GCC-like interface (e.g. GCC, Clang)
* CMake 2.8+
* Optional: static analysis tool for C++ - cppcheck (on Debian/Ubuntu use package `cppcheck`)
Building the debug version and running the unit tests:
```bash
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Debug
make -j8
make ARGS=-VV test
```
Test outputs can be found in the build directory under `libuavcan`.
> Note that unit tests suffixed with "_RealTime" must be executed in real time, otherwise they may produce false warnings;
this implies that they will likely fail if ran on a virtual machine or on a highly loaded system.
### Vagrant
Vagrant can be used to setup a compatible Ubuntu virtual image. Follow the instructions on [Vagrantup](https://www.vagrantup.com/) to install virtualbox and vagrant then do:
```bash
vagrant up
vagrant ssh
mkdir build
cd build
mkdir build && cd build && cmake .. -DCMAKE_BUILD_TYPE=Debug -DCONTINUOUS_INTEGRATION_BUILD=1
```
> Note that -DCONTINUOUS_INTEGRATION_BUILD=1 is required for this build as the realtime unit tests will not work on a virt.
You can build using commands like:
```bash
vagrant ssh -c "cd /vagrant/build && make -j4 && make test"
```
or to run a single test:
```bash
vagrant ssh -c "cd /vagrant/build && make libuavcan_test && ./libuavcan/libuavcan_test --gtest_filter=Node.Basic"
```
### Developing with Eclipse
An Eclipse project can be generated like that:
```bash
cmake ../../libuavcan -G"Eclipse CDT4 - Unix Makefiles" \
-DCMAKE_ECLIPSE_VERSION=4.3 \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_CXX_COMPILER_ARG1=-std=c++11
```
Path `../../libuavcan` in the command above points at the directory where the top-level `CMakeLists.txt` is located;
you may need to adjust this per your environment.
Note that the directory where Eclipse project is generated must not be a descendant of the source directory.
@@ -0,0 +1,146 @@
#
# Copyright (C) 2014 Pavel Kirienko <pavel.kirienko@gmail.com>
#
cmake_minimum_required(VERSION 3.9)
if(DEFINED CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE ${CMAKE_BUILD_TYPE} CACHE STRING "Debug Release RelWithDebInfo MinSizeRel")
else()
set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "Debug Release RelWithDebInfo MinSizeRel")
endif()
# Detecting whether we need to add debug targets
string(TOLOWER "${CMAKE_BUILD_TYPE}" build_type_lower)
if (build_type_lower STREQUAL "debug")
set(DEBUG_BUILD 1)
message(STATUS "Debug build")
else ()
set(DEBUG_BUILD 0)
endif ()
project(libuavcan)
find_package(PythonInterp)
if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
set(COMPILER_IS_GCC_COMPATIBLE 1)
else ()
set(COMPILER_IS_GCC_COMPATIBLE 0)
endif ()
#
# DSDL compiler invocation
# Probably output files should be saved into CMake output dir?
#
set(DSDLC_INPUTS "test/dsdl_test/root_ns_a" "test/dsdl_test/root_ns_b" "${CMAKE_CURRENT_SOURCE_DIR}/../dsdl/uavcan")
set(DSDLC_OUTPUT "include/dsdlc_generated")
set(DSDLC_INPUT_FILES "")
foreach(DSDLC_INPUT ${DSDLC_INPUTS})
file(GLOB_RECURSE DSDLC_NEW_INPUT_FILES ${CMAKE_CURRENT_SOURCE_DIR} "${DSDLC_INPUT}/*.uavcan")
set(DSDLC_INPUT_FILES ${DSDLC_INPUT_FILES} ${DSDLC_NEW_INPUT_FILES})
endforeach(DSDLC_INPUT)
add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/libuavcan_dsdlc_run.stamp
COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/dsdl_compiler/libuavcan_dsdlc ${DSDLC_INPUTS} -O${DSDLC_OUTPUT}
COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_BINARY_DIR}/libuavcan_dsdlc_run.stamp
DEPENDS ${DSDLC_INPUT_FILES}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMENT "Running dsdl compiler")
add_custom_target(libuavcan_dsdlc DEPENDS ${CMAKE_BINARY_DIR}/libuavcan_dsdlc_run.stamp)
include_directories(${DSDLC_OUTPUT})
#
# Compiler flags
#
if (COMPILER_IS_GCC_COMPATIBLE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wundef")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
endif ()
if (DEBUG_BUILD)
add_definitions(-DUAVCAN_DEBUG=1)
endif ()
include_directories(include)
#
# libuavcan
#
file(GLOB_RECURSE LIBUAVCAN_CXX_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "src/*.cpp")
add_library(uavcan STATIC ${LIBUAVCAN_CXX_FILES})
add_dependencies(uavcan libuavcan_dsdlc)
install(TARGETS uavcan DESTINATION lib)
install(DIRECTORY include/uavcan DESTINATION include)
install(DIRECTORY include/dsdlc_generated/uavcan DESTINATION include) # Generated and lib's .hpp
#
# Tests and static analysis - only for debug builds
#
function(add_libuavcan_test name library flags) # Adds GTest executable and creates target to execute it every build
find_package(Threads REQUIRED)
file(GLOB_RECURSE TEST_CXX_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "test/*.cpp")
add_executable(${name} ${TEST_CXX_FILES})
add_dependencies(${name} ${library})
if (flags)
set_target_properties(${name} PROPERTIES COMPILE_FLAGS ${flags})
endif ()
target_link_libraries(${name} gmock_main)
target_link_libraries(${name} ${library})
if (${UAVCAN_PLATFORM} STREQUAL "linux")
target_link_libraries(${name} rt)
endif()
# Tests run automatically upon successful build
# If failing tests need to be investigated with debugger, use 'make --ignore-errors'
if (CONTINUOUS_INTEGRATION_BUILD)
# Don't redirect test output, and don't run tests suffixed with "RealTime"
add_test(NAME ${name}
COMMAND ${name} --gtest_filter=-*RealTime
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
else ()
add_test(NAME ${name}
COMMAND ${name} 1>"${name}.log" 2>&1
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
endif()
endfunction()
if (DEBUG_BUILD)
message(STATUS "Debug build (note: requires gtest)")
if (COMPILER_IS_GCC_COMPATIBLE)
# No such thing as too many warnings
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Werror -pedantic -Wfloat-equal -Wconversion")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsign-conversion -Wcast-align -Wmissing-declarations -Wlogical-op")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wdouble-promotion -Wswitch-enum -Wtype-limits -Wno-error=array-bounds")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wzero-as-null-pointer-constant -Wnon-virtual-dtor")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Woverloaded-virtual -Wsign-promo -Wold-style-cast")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-error=deprecated-declarations")
set(optim_flags "-O3 -DNDEBUG -g0")
else ()
message(STATUS "Compiler ID: ${CMAKE_CXX_COMPILER_ID}")
message(FATAL_ERROR "This compiler cannot be used to build tests; use release build instead.")
endif ()
# Additional flavours of the library
add_library(uavcan_optim STATIC ${LIBUAVCAN_CXX_FILES})
set_target_properties(uavcan_optim PROPERTIES COMPILE_FLAGS ${optim_flags})
add_dependencies(uavcan_optim libuavcan_dsdlc)
if (GTEST_FOUND)
message(STATUS "GTest found, tests will be built and run.")
add_libuavcan_test(libuavcan_test uavcan "") # Default
add_libuavcan_test(libuavcan_test_optim uavcan_optim "${optim_flags}") # Max optimization
else (GTEST_FOUND)
message(STATUS "GTest was not found, tests will not be built")
endif (GTEST_FOUND)
else ()
message(STATUS "Release build type: " ${CMAKE_BUILD_TYPE})
endif ()
# vim: set et ft=cmake fenc=utf-8 ff=unix sts=4 sw=4 ts=4 :
+18
View File
@@ -0,0 +1,18 @@
#!/bin/sh
#
# cppcheck static analysis
# For Debian based: apt-get install cppcheck
#
num_cores=$(grep -c ^processor /proc/cpuinfo)
if [ -z "$num_cores" ]; then
echo "Hey, it looks like we're not on Linux. Please fix this script to add support for this OS."
num_cores=4
fi
echo "Number of threads for cppcheck: $num_cores"
# TODO: with future versions of cppcheck, add --library=glibc
cppcheck . --error-exitcode=1 --quiet --enable=all --platform=unix64 --std=c99 --std=c++11 \
--inline-suppr --force --template=gcc -j$num_cores \
-U__BIGGEST_ALIGNMENT__ -UUAVCAN_MEM_POOL_BLOCK_SIZE -UBIG_ENDIAN -UBYTE_ORDER \
-Iinclude $@
@@ -0,0 +1,313 @@
#!/usr/bin/env python
#
# UAVCAN DSDL compiler for libuavcan
#
# Copyright (C) 2014 Pavel Kirienko <pavel.kirienko@gmail.com>
#
'''
This module implements the core functionality of the UAVCAN DSDL compiler for libuavcan.
Supported Python versions: 3.2+, 2.7.
It accepts a list of root namespaces and produces the set of C++ header files for libuavcan.
It is based on the DSDL parsing package from pyuavcan.
'''
from __future__ import division, absolute_import, print_function, unicode_literals
import sys, os, logging, errno, re
from .pyratemp import Template
from dronecan import dsdl
# Python 2.7 compatibility
try:
str = unicode
except NameError:
pass
OUTPUT_FILE_EXTENSION = 'hpp'
OUTPUT_FILE_PERMISSIONS = 0o444 # Read only for all
TEMPLATE_FILENAME = os.path.join(os.path.dirname(__file__), 'data_type_template.tmpl')
__all__ = ['run', 'logger', 'DsdlCompilerException']
class DsdlCompilerException(Exception):
pass
logger = logging.getLogger(__name__)
def run(source_dirs, include_dirs, output_dir):
'''
This function takes a list of root namespace directories (containing DSDL definition files to parse), a
possibly empty list of search directories (containing DSDL definition files that can be referenced from the types
that are going to be parsed), and the output directory path (possibly nonexistent) where the generated C++
header files will be stored.
Note that this module features lazy write, i.e. if an output file does already exist and its content is not going
to change, it will not be overwritten. This feature allows to avoid unnecessary recompilation of dependent object
files.
Args:
source_dirs List of root namespace directories to parse.
include_dirs List of root namespace directories with referenced types (possibly empty). This list is
automaitcally extended with source_dirs.
output_dir Output directory path. Will be created if doesn't exist.
'''
assert isinstance(source_dirs, list)
assert isinstance(include_dirs, list)
output_dir = str(output_dir)
types = run_parser(source_dirs, include_dirs + source_dirs)
if not types:
die('No type definitions were found')
logger.info('%d types total', len(types))
run_generator(types, output_dir)
# -----------------
def pretty_filename(filename):
try:
a = os.path.abspath(filename)
r = os.path.relpath(filename)
return a if '..' in r else r
except ValueError:
return filename
def type_output_filename(t):
assert t.category == t.CATEGORY_COMPOUND
return t.full_name.replace('.', os.path.sep) + '.' + OUTPUT_FILE_EXTENSION
def makedirs(path):
try:
try:
os.makedirs(path, exist_ok=True) # May throw "File exists" when executed as root, which is wrong
except TypeError:
os.makedirs(path) # Python 2.7 compatibility
except OSError as ex:
if ex.errno != errno.EEXIST: # http://stackoverflow.com/questions/12468022
raise
def die(text):
raise DsdlCompilerException(str(text))
def run_parser(source_dirs, search_dirs):
try:
types = dsdl.parse_namespaces(source_dirs, search_dirs)
except dsdl.DsdlException as ex:
logger.info('Parser failure', exc_info=True)
die(ex)
return types
def run_generator(types, dest_dir):
try:
template_expander = make_template_expander(TEMPLATE_FILENAME)
dest_dir = os.path.abspath(dest_dir) # Removing '..'
makedirs(dest_dir)
for t in types:
logger.info('Generating type %s', t.full_name)
filename = os.path.join(dest_dir, type_output_filename(t))
text = generate_one_type(template_expander, t)
write_generated_data(filename, text)
except Exception as ex:
logger.info('Generator failure', exc_info=True)
die(ex)
def write_generated_data(filename, data):
dirname = os.path.dirname(filename)
makedirs(dirname)
# Lazy update - file will not be rewritten if its content is not going to change
if os.path.exists(filename):
with open(filename) as f:
existing_data = f.read()
if data == existing_data:
logger.info('Up to date [%s]', pretty_filename(filename))
return
logger.info('Rewriting [%s]', pretty_filename(filename))
os.remove(filename)
else:
logger.info('Creating [%s]', pretty_filename(filename))
# Full rewrite
with open(filename, 'w') as f:
f.write(data)
try:
os.chmod(filename, OUTPUT_FILE_PERMISSIONS)
except (OSError, IOError) as ex:
logger.warning('Failed to set permissions for %s: %s', pretty_filename(filename), ex)
def type_to_cpp_type(t):
if t.category == t.CATEGORY_PRIMITIVE:
cast_mode = {
t.CAST_MODE_SATURATED: '::uavcan::CastModeSaturate',
t.CAST_MODE_TRUNCATED: '::uavcan::CastModeTruncate',
}[t.cast_mode]
if t.kind == t.KIND_FLOAT:
return '::uavcan::FloatSpec< %d, %s >' % (t.bitlen, cast_mode)
else:
signedness = {
t.KIND_BOOLEAN: '::uavcan::SignednessUnsigned',
t.KIND_UNSIGNED_INT: '::uavcan::SignednessUnsigned',
t.KIND_SIGNED_INT: '::uavcan::SignednessSigned',
}[t.kind]
return '::uavcan::IntegerSpec< %d, %s, %s >' % (t.bitlen, signedness, cast_mode)
elif t.category == t.CATEGORY_ARRAY:
value_type = type_to_cpp_type(t.value_type)
mode = {
t.MODE_STATIC: '::uavcan::ArrayModeStatic',
t.MODE_DYNAMIC: '::uavcan::ArrayModeDynamic',
}[t.mode]
return '::uavcan::Array< %s, %s, %d >' % (value_type, mode, t.max_size)
elif t.category == t.CATEGORY_COMPOUND:
return '::' + t.full_name.replace('.', '::')
elif t.category == t.CATEGORY_VOID:
return '::uavcan::IntegerSpec< %d, ::uavcan::SignednessUnsigned, ::uavcan::CastModeSaturate >' % t.bitlen
else:
raise DsdlCompilerException('Unknown type category: %s' % t.category)
def generate_one_type(template_expander, t):
t.short_name = t.full_name.split('.')[-1]
t.cpp_type_name = t.short_name + '_'
t.cpp_full_type_name = '::' + t.full_name.replace('.', '::')
t.include_guard = t.full_name.replace('.', '_').upper() + '_HPP_INCLUDED'
# Dependencies (no duplicates)
def fields_includes(fields):
def detect_include(t):
if t.category == t.CATEGORY_COMPOUND:
return type_output_filename(t)
if t.category == t.CATEGORY_ARRAY:
return detect_include(t.value_type)
return list(sorted(set(filter(None, [detect_include(x.type) for x in fields]))))
if t.kind == t.KIND_MESSAGE:
t.cpp_includes = fields_includes(t.fields)
else:
t.cpp_includes = fields_includes(t.request_fields + t.response_fields)
t.cpp_namespace_components = t.full_name.split('.')[:-1]
t.has_default_dtid = t.default_dtid is not None
# Attribute types
def inject_cpp_types(attributes):
void_index = 0
for a in attributes:
a.cpp_type = type_to_cpp_type(a.type)
a.void = a.type.category == a.type.CATEGORY_VOID
if a.void:
assert not a.name
a.name = '_void_%d' % void_index
void_index += 1
if t.kind == t.KIND_MESSAGE:
inject_cpp_types(t.fields)
inject_cpp_types(t.constants)
t.all_attributes = t.fields + t.constants
t.union = t.union and len(t.fields)
else:
inject_cpp_types(t.request_fields)
inject_cpp_types(t.request_constants)
inject_cpp_types(t.response_fields)
inject_cpp_types(t.response_constants)
t.all_attributes = t.request_fields + t.request_constants + t.response_fields + t.response_constants
t.request_union = t.request_union and len(t.request_fields)
t.response_union = t.response_union and len(t.response_fields)
# Constant properties
def inject_constant_info(constants):
for c in constants:
if c.type.kind == c.type.KIND_FLOAT:
float(c.string_value) # Making sure that this is a valid float literal
c.cpp_value = c.string_value
else:
int(c.string_value) # Making sure that this is a valid integer literal
c.cpp_value = c.string_value
if c.type.kind == c.type.KIND_UNSIGNED_INT:
c.cpp_value += 'U'
if t.kind == t.KIND_MESSAGE:
inject_constant_info(t.constants)
else:
inject_constant_info(t.request_constants)
inject_constant_info(t.response_constants)
# Data type kind
t.cpp_kind = {
t.KIND_MESSAGE: '::uavcan::DataTypeKindMessage',
t.KIND_SERVICE: '::uavcan::DataTypeKindService',
}[t.kind]
# Generation
text = template_expander(t=t) # t for Type
text = '\n'.join(x.rstrip() for x in text.splitlines())
text = text.replace('\n\n\n\n\n', '\n\n').replace('\n\n\n\n', '\n\n').replace('\n\n\n', '\n\n')
text = text.replace('{\n\n ', '{\n ')
return text
def make_template_expander(filename):
'''
Templating is based on pyratemp (http://www.simple-is-better.org/template/pyratemp.html).
The pyratemp's syntax is rather verbose and not so human friendly, so we define some
custom extensions to make it easier to read and write.
The resulting syntax somewhat resembles Mako (which was used earlier instead of pyratemp):
Substitution:
${expression}
Line joining through backslash (replaced with a single space):
${foo(bar(very_long_arument=42, \
second_line=72))}
Blocks:
% for a in range(10):
% if a == 5:
${foo()}
% endif
% endfor
The extended syntax is converted into pyratemp's through regexp substitution.
'''
with open(filename) as f:
template_text = f.read()
# Backslash-newline elimination
template_text = re.sub(r'\\\r{0,1}\n\ *', r' ', template_text)
# Substitution syntax transformation: ${foo} ==> $!foo!$
template_text = re.sub(r'([^\$]{0,1})\$\{([^\}]+)\}', r'\1$!\2!$', template_text)
# Flow control expression transformation: % foo: ==> <!--(foo)-->
template_text = re.sub(r'(?m)^(\ *)\%\ *(.+?):{0,1}$', r'\1<!--(\2)-->', template_text)
# Block termination transformation: <!--(endfoo)--> ==> <!--(end)-->
template_text = re.sub(r'\<\!--\(end[a-z]+\)--\>', r'<!--(end)-->', template_text)
# Pyratemp workaround.
# The problem is that if there's no empty line after a macro declaration, first line will be doubly indented.
# Workaround:
# 1. Remove trailing comments
# 2. Add a newline after each macro declaration
template_text = re.sub(r'\ *\#\!.*', '', template_text)
template_text = re.sub(r'(\<\!--\(macro\ [a-zA-Z0-9_]+\)--\>.*?)', r'\1\n', template_text)
# Preprocessed text output for debugging
# with open(filename + '.d', 'w') as f:
# f.write(template_text)
template = Template(template_text)
def expand(**args):
# This function adds one indentation level (4 spaces); it will be used from the template
args['indent'] = lambda text, idnt = ' ': idnt + text.replace('\n', '\n' + idnt)
# This function works like enumerate(), telling you whether the current item is the last one
def enum_last_value(iterable, start=0):
it = iter(iterable)
count = start
try:
last = next(it)
except StopIteration:
return
for val in it:
yield count, False, last
last = val
count += 1
yield count, True, last
args['enum_last_value'] = enum_last_value
return template(**args)
return expand
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,64 @@
#!/usr/bin/env python
#
# UAVCAN DSDL compiler for libuavcan
# Supported Python versions: 3.2+, 2.7.
#
# Copyright (C) 2014 Pavel Kirienko <pavel.kirienko@gmail.com>
#
from __future__ import division, absolute_import, print_function, unicode_literals
import os, sys, logging, argparse
# This trickery allows to use the compiler even if pyuavcan is not installed in the system.
# This is extremely important, as it makes the compiler (and therefore libuavcan in general)
# totally dependency-free, except for the Python interpreter itself.
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
LOCAL_PYUAVCAN_DIR = os.path.join(SCRIPT_DIR, 'pydronecan')
RUNNING_FROM_SRC_DIR = os.path.isdir(LOCAL_PYUAVCAN_DIR)
if RUNNING_FROM_SRC_DIR:
#print('Running from the source directory')
sys.path.insert(0, SCRIPT_DIR)
sys.path.insert(0, LOCAL_PYUAVCAN_DIR)
def configure_logging(verbosity):
fmt = '%(message)s'
level = { 0: logging.WARNING, 1: logging.INFO, 2: logging.DEBUG }.get(verbosity or 0, logging.DEBUG)
logging.basicConfig(stream=sys.stderr, level=level, format=fmt)
def die(text):
print(text, file=sys.stderr)
exit(1)
DEFAULT_OUTDIR = 'dsdlc_generated'
DESCRIPTION = '''UAVCAN DSDL compiler for libuavcan.
Takes an input directory that contains an hierarchy of DSDL
definitions and converts it into compatible hierarchy of C++ types for libuavcan.
This script can be used directly from the source directory, no installation required!
Supported Python versions: 3.2+, 2.7.
'''
argparser = argparse.ArgumentParser(description=DESCRIPTION)
argparser.add_argument('source_dir', nargs='+', help='source directory with DSDL definitions')
argparser.add_argument('--verbose', '-v', action='count', help='verbosity level (-v, -vv)')
argparser.add_argument('--outdir', '-O', default=DEFAULT_OUTDIR, help='output directory, default %s' % DEFAULT_OUTDIR)
argparser.add_argument('--incdir', '-I', default=[], action='append', help=
'''nested type namespaces, one path per argument. Can be also specified through the environment variable
UAVCAN_DSDL_INCLUDE_PATH, where the path entries are separated by colons ":"''')
args = argparser.parse_args()
configure_logging(args.verbose)
try:
extra_incdir = os.environ['UAVCAN_DSDL_INCLUDE_PATH'].split(':')
logging.info('Additional include directories: %s', extra_incdir)
args.incdir += extra_incdir
except KeyError:
pass
from libuavcan_dsdl_compiler import run as dsdlc_run
try:
dsdlc_run(args.source_dir, args.incdir, args.outdir)
except Exception as ex:
logging.error('Compiler failure', exc_info=True)
die(str(ex))
@@ -0,0 +1,276 @@
/*
* Copyright (C) 2014 Pavel Kirienko <pavel.kirienko@gmail.com>
*/
#ifndef UAVCAN_BUILD_CONFIG_HPP_INCLUDED
#define UAVCAN_BUILD_CONFIG_HPP_INCLUDED
/**
* UAVCAN version definition
*/
#define UAVCAN_VERSION_MAJOR 1
#define UAVCAN_VERSION_MINOR 0
/**
* UAVCAN_CPP_VERSION - version of the C++ standard used during compilation.
* This definition contains the integer year number after which the standard was named:
* - 2003 for C++03
* - 2011 for C++11
*
* This config automatically sets according to the actual C++ standard used by the compiler.
*
* In C++03 mode the library has almost zero dependency on the C++ standard library, which allows
* to use it on platforms with a very limited C++ support. On the other hand, C++11 mode requires
* many parts of the standard library (e.g. <functional>), thus the user might want to force older
* standard than used by the compiler, in which case this symbol can be overridden manually via
* compiler flags.
*/
#define UAVCAN_CPP11 2011
#define UAVCAN_CPP03 2003
#ifndef UAVCAN_CPP_VERSION
# if __cplusplus > 201200
# error Unsupported C++ standard. You can explicitly set UAVCAN_CPP_VERSION=UAVCAN_CPP11 to silence this error.
# elif (__cplusplus > 201100) || defined(__GXX_EXPERIMENTAL_CXX0X__)
# define UAVCAN_CPP_VERSION UAVCAN_CPP11
# else
# define UAVCAN_CPP_VERSION UAVCAN_CPP03
# endif
#endif
/**
* The library uses UAVCAN_NULLPTR instead of UAVCAN_NULLPTR and nullptr in order to allow the use of
* -Wzero-as-null-pointer-constant.
*/
#ifndef UAVCAN_NULLPTR
# if UAVCAN_CPP_VERSION >= UAVCAN_CPP11
# define UAVCAN_NULLPTR nullptr
# else
# define UAVCAN_NULLPTR NULL
# endif
#endif
/**
* By default, libuavcan enables all features if it detects that it is being built for a general-purpose
* target like Linux. Value of this macro influences other configuration options located below in this file.
* This macro can be overriden if needed.
*/
#ifndef UAVCAN_GENERAL_PURPOSE_PLATFORM
# if (defined(__linux__) || defined(__linux) || defined(__APPLE__) ||\
defined(_WIN64) || defined(_WIN32) || defined(__ANDROID__) ||\
defined(_SYSTYPE_BSD) || defined(__FreeBSD__))
# define UAVCAN_GENERAL_PURPOSE_PLATFORM 1
# else
# define UAVCAN_GENERAL_PURPOSE_PLATFORM 0
# endif
#endif
/**
* This macro enables built-in runtime checks and debug output via printf().
* Should be used only for library development.
*/
#ifndef UAVCAN_DEBUG
# define UAVCAN_DEBUG 0
#endif
/**
* This option allows to select whether libuavcan should throw exceptions on fatal errors, or try to handle
* errors differently. By default, exceptions will be enabled only if the library is built for a general-purpose
* OS like Linux. Set UAVCAN_EXCEPTIONS explicitly to override.
*/
#ifndef UAVCAN_EXCEPTIONS
# define UAVCAN_EXCEPTIONS UAVCAN_GENERAL_PURPOSE_PLATFORM
#endif
/**
* This specification is used by some error reporting functions like in the Logger class.
* The default can be overriden by defining the macro UAVCAN_NOEXCEPT explicitly, e.g. via compiler options.
*/
#ifndef UAVCAN_NOEXCEPT
# if UAVCAN_EXCEPTIONS
# if UAVCAN_CPP_VERSION >= UAVCAN_CPP11
# define UAVCAN_NOEXCEPT noexcept
# else
# define UAVCAN_NOEXCEPT throw()
# endif
# else
# define UAVCAN_NOEXCEPT
# endif
#endif
/**
* Declaration visibility
* http://gcc.gnu.org/wiki/Visibility
*/
#ifndef UAVCAN_EXPORT
# define UAVCAN_EXPORT
#endif
/**
* Trade-off between ROM/RAM usage and functionality/determinism.
* Note that this feature is not well tested and should be avoided.
* Use code search for UAVCAN_TINY to find what functionality will be disabled.
* This is particularly useful for embedded systems with less than 40kB of ROM.
*/
#ifndef UAVCAN_TINY
# define UAVCAN_TINY 0
#endif
/**
* Disable the global data type registry, which can save some space on embedded systems.
*/
#ifndef UAVCAN_NO_GLOBAL_DATA_TYPE_REGISTRY
# define UAVCAN_NO_GLOBAL_DATA_TYPE_REGISTRY 0
#endif
/**
* toString() methods will be disabled by default, unless the library is built for a general-purpose target like Linux.
* It is not recommended to enable toString() on embedded targets as code size will explode.
*/
#ifndef UAVCAN_TOSTRING
# if UAVCAN_EXCEPTIONS
# define UAVCAN_TOSTRING UAVCAN_GENERAL_PURPOSE_PLATFORM
# else
# define UAVCAN_TOSTRING 0
# endif
#endif
#if UAVCAN_TOSTRING
# if !UAVCAN_EXCEPTIONS
# error UAVCAN_TOSTRING requires UAVCAN_EXCEPTIONS
# endif
# include <string>
#endif
/**
* Some C++ implementations are half-broken because they don't implement the placement new operator.
* If UAVCAN_IMPLEMENT_PLACEMENT_NEW is defined, libuavcan will implement its own operator new (std::size_t, void*)
* and its delete() counterpart, instead of relying on the standard header <new>.
*/
#ifndef UAVCAN_IMPLEMENT_PLACEMENT_NEW
# define UAVCAN_IMPLEMENT_PLACEMENT_NEW 0
#endif
/**
* Allows the user's application to provide custom implementation of uavcan::snprintf(),
* which is often useful on deeply embedded systems.
*/
#ifndef UAVCAN_USE_EXTERNAL_SNPRINTF
# define UAVCAN_USE_EXTERNAL_SNPRINTF 0
#endif
/**
* Allows the user's application to provide a custom implementation of IEEE754Converter::nativeIeeeToHalf and
* IEEE754Converter::halfToNativeIeee.
*/
#ifndef UAVCAN_USE_EXTERNAL_FLOAT16_CONVERSION
# define UAVCAN_USE_EXTERNAL_FLOAT16_CONVERSION 0
#endif
/**
* Run time checks.
* Resolves to the standard assert() by default.
* Disabled completely if UAVCAN_NO_ASSERTIONS is defined.
*/
#ifndef UAVCAN_ASSERT
# ifndef UAVCAN_NO_ASSERTIONS
# define UAVCAN_NO_ASSERTIONS 0
# endif
# if UAVCAN_NO_ASSERTIONS
# define UAVCAN_ASSERT(x)
# else
# define UAVCAN_ASSERT(x) assert(x)
# endif
#endif
#ifndef UAVCAN_LIKELY
# if __GNUC__
# define UAVCAN_LIKELY(x) __builtin_expect(!!(x), true)
# else
# define UAVCAN_LIKELY(x) (x)
# endif
#endif
#ifndef UAVCAN_UNLIKELY
# if __GNUC__
# define UAVCAN_UNLIKELY(x) __builtin_expect(!!(x), false)
# else
# define UAVCAN_UNLIKELY(x) (x)
# endif
#endif
namespace uavcan
{
/**
* Memory pool block size.
*
* The default of 64 bytes should be OK for any target arch up to AMD64 and any compiler.
*
* The library leverages compile-time checks to ensure that all types that are subject to dynamic allocation
* fit this size, otherwise compilation fails.
*
* For platforms featuring small pointer width (16..32 bits), UAVCAN_MEM_POOL_BLOCK_SIZE can often be safely
* reduced to 56 or even 48 bytes, which leads to lower memory footprint.
*
* Note that the pool block size shall be aligned at biggest alignment of the target platform (detected and
* checked automatically at compile time).
*/
#ifdef UAVCAN_MEM_POOL_BLOCK_SIZE
/// Explicitly specified by the user.
static const unsigned MemPoolBlockSize = UAVCAN_MEM_POOL_BLOCK_SIZE;
#elif defined(__BIGGEST_ALIGNMENT__) && (__BIGGEST_ALIGNMENT__ <= 8)
/// Convenient default for GCC-like compilers - if alignment allows, pool block size can be safely reduced.
static const unsigned MemPoolBlockSize = 56;
#else
/// Safe default that should be OK for any platform.
static const unsigned MemPoolBlockSize = 64;
#endif
#ifdef __BIGGEST_ALIGNMENT__
static const unsigned MemPoolAlignment = __BIGGEST_ALIGNMENT__;
#else
static const unsigned MemPoolAlignment = 16;
#endif
typedef char _alignment_check_for_MEM_POOL_BLOCK_SIZE[((MemPoolBlockSize & (MemPoolAlignment - 1)) == 0) ? 1 : -1];
/**
* This class that allows to check at compile time whether type T can be allocated using the memory pool.
* If the check fails, compilation fails.
*/
template <typename T>
struct UAVCAN_EXPORT IsDynamicallyAllocatable
{
static void check()
{
char dummy[(sizeof(T) <= MemPoolBlockSize) ? 1 : -1] = { '0' };
(void)dummy;
}
};
/**
* Float comparison precision.
* For details refer to:
* http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/
* https://code.google.com/p/googletest/source/browse/trunk/include/gtest/internal/gtest-internal.h
*/
#ifdef UAVCAN_FLOAT_COMPARISON_EPSILON_MULT
static const unsigned FloatComparisonEpsilonMult = UAVCAN_FLOAT_COMPARISON_EPSILON_MULT;
#else
static const unsigned FloatComparisonEpsilonMult = 10;
#endif
/**
* Maximum number of CAN acceptance filters available on the platform
*/
#ifdef UAVCAN_MAX_CAN_ACCEPTANCE_FILTERS
/// Explicitly specified by the user.
static const unsigned MaxCanAcceptanceFilters = UAVCAN_MAX_CAN_ACCEPTANCE_FILTERS;
#else
/// Default that should be OK for any platform.
static const unsigned MaxCanAcceptanceFilters = 32;
#endif
}
#endif // UAVCAN_BUILD_CONFIG_HPP_INCLUDED
@@ -0,0 +1,176 @@
/*
* Copyright (C) 2014 Pavel Kirienko <pavel.kirienko@gmail.com>
*/
#ifndef UAVCAN_DATA_TYPE_HPP_INCLUDED
#define UAVCAN_DATA_TYPE_HPP_INCLUDED
#include <cassert>
#include <cstring>
#include <uavcan/std.hpp>
#include <uavcan/build_config.hpp>
#include <uavcan/transport/transfer.hpp>
namespace uavcan
{
class UAVCAN_EXPORT TransferCRC;
enum DataTypeKind
{
DataTypeKindService,
DataTypeKindMessage
};
static const uint8_t NumDataTypeKinds = 2;
static inline DataTypeKind getDataTypeKindForTransferType(const TransferType tt)
{
if (tt == TransferTypeServiceResponse ||
tt == TransferTypeServiceRequest)
{
return DataTypeKindService;
}
else if (tt == TransferTypeMessageBroadcast)
{
return DataTypeKindMessage;
}
else
{
UAVCAN_ASSERT(0);
return DataTypeKind(0);
}
}
class UAVCAN_EXPORT DataTypeID
{
uint32_t value_;
public:
static const uint16_t MaxServiceDataTypeIDValue = 255;
static const uint16_t MaxMessageDataTypeIDValue = 65535;
static const uint16_t MaxPossibleDataTypeIDValue = MaxMessageDataTypeIDValue;
DataTypeID() : value_(0xFFFFFFFFUL) { }
DataTypeID(uint16_t id) // Implicit
: value_(id)
{ }
static DataTypeID getMaxValueForDataTypeKind(const DataTypeKind dtkind);
bool isValidForDataTypeKind(DataTypeKind dtkind) const
{
return value_ <= getMaxValueForDataTypeKind(dtkind).get();
}
uint16_t get() const { return static_cast<uint16_t>(value_); }
bool operator==(DataTypeID rhs) const { return value_ == rhs.value_; }
bool operator!=(DataTypeID rhs) const { return value_ != rhs.value_; }
bool operator<(DataTypeID rhs) const { return value_ < rhs.value_; }
bool operator>(DataTypeID rhs) const { return value_ > rhs.value_; }
bool operator<=(DataTypeID rhs) const { return value_ <= rhs.value_; }
bool operator>=(DataTypeID rhs) const { return value_ >= rhs.value_; }
};
/**
* CRC-64-WE
* Description: http://reveng.sourceforge.net/crc-catalogue/17plus.htm#crc.cat-bits.64
* Initial value: 0xFFFFFFFFFFFFFFFF
* Poly: 0x42F0E1EBA9EA3693
* Reverse: no
* Output xor: 0xFFFFFFFFFFFFFFFF
* Check: 0x62EC59E3F1A4F00A
*/
class UAVCAN_EXPORT DataTypeSignatureCRC
{
uint64_t crc_;
public:
static DataTypeSignatureCRC extend(uint64_t crc);
DataTypeSignatureCRC() : crc_(0xFFFFFFFFFFFFFFFFULL) { }
void add(uint8_t byte);
void add(const uint8_t* bytes, unsigned len);
uint64_t get() const { return crc_ ^ 0xFFFFFFFFFFFFFFFFULL; }
};
class UAVCAN_EXPORT DataTypeSignature
{
uint64_t value_;
void mixin64(uint64_t x);
public:
DataTypeSignature() : value_(0) { }
explicit DataTypeSignature(uint64_t value) : value_(value) { }
void extend(DataTypeSignature dts);
TransferCRC toTransferCRC() const;
uint64_t get() const { return value_; }
bool operator==(DataTypeSignature rhs) const { return value_ == rhs.value_; }
bool operator!=(DataTypeSignature rhs) const { return !operator==(rhs); }
};
/**
* This class contains complete description of a data type.
*/
class UAVCAN_EXPORT DataTypeDescriptor
{
DataTypeSignature signature_;
const char* full_name_;
DataTypeKind kind_;
DataTypeID id_;
public:
static const unsigned MaxFullNameLen = 80;
DataTypeDescriptor() :
full_name_(""),
kind_(DataTypeKind(0))
{ }
DataTypeDescriptor(DataTypeKind kind, DataTypeID id, const DataTypeSignature& signature, const char* name) :
signature_(signature),
full_name_(name),
kind_(kind),
id_(id)
{
UAVCAN_ASSERT((kind == DataTypeKindMessage) || (kind == DataTypeKindService));
UAVCAN_ASSERT(name);
UAVCAN_ASSERT(std::strlen(name) <= MaxFullNameLen);
}
bool isValid() const;
DataTypeKind getKind() const { return kind_; }
DataTypeID getID() const { return id_; }
const DataTypeSignature& getSignature() const { return signature_; }
const char* getFullName() const { return full_name_; }
bool match(DataTypeKind kind, const char* name) const;
bool match(DataTypeKind kind, DataTypeID id) const;
bool operator!=(const DataTypeDescriptor& rhs) const { return !operator==(rhs); }
bool operator==(const DataTypeDescriptor& rhs) const;
#if UAVCAN_TOSTRING
std::string toString() const;
#endif
};
}
#endif // UAVCAN_DATA_TYPE_HPP_INCLUDED
@@ -0,0 +1,35 @@
/*
* Debug stuff, should only be used for library development.
* Copyright (C) 2014 Pavel Kirienko <pavel.kirienko@gmail.com>
*/
#ifndef UAVCAN_DEBUG_HPP_INCLUDED
#define UAVCAN_DEBUG_HPP_INCLUDED
#include <uavcan/build_config.hpp>
#if UAVCAN_DEBUG
# include <cstdio>
# include <cstdarg>
# if __GNUC__
__attribute__ ((format(printf, 2, 3)))
# endif
static void UAVCAN_TRACE(const char* src, const char* fmt, ...)
{
va_list args;
(void)std::printf("UAVCAN: %s: ", src);
va_start(args, fmt);
(void)std::vprintf(fmt, args);
va_end(args);
(void)std::puts("");
}
#else
# define UAVCAN_TRACE(...) ((void)0)
#endif
#endif // UAVCAN_DEBUG_HPP_INCLUDED
@@ -0,0 +1,249 @@
/*
* CAN bus driver interface.
* Copyright (C) 2014 Pavel Kirienko <pavel.kirienko@gmail.com>
*/
#ifndef UAVCAN_DRIVER_CAN_HPP_INCLUDED
#define UAVCAN_DRIVER_CAN_HPP_INCLUDED
#include <cassert>
#include <uavcan/std.hpp>
#include <uavcan/build_config.hpp>
#include <uavcan/driver/system_clock.hpp>
namespace uavcan
{
/**
* This limit is defined by the specification.
*/
enum { MaxCanIfaces = 3 };
/**
* Raw CAN frame, as passed to/from the CAN driver.
*/
struct UAVCAN_EXPORT CanFrame
{
static const uint32_t MaskStdID = 0x000007FFU;
static const uint32_t MaskExtID = 0x1FFFFFFFU;
static const uint32_t FlagEFF = 1U << 31; ///< Extended frame format
static const uint32_t FlagRTR = 1U << 30; ///< Remote transmission request
static const uint32_t FlagERR = 1U << 29; ///< Error frame
static const uint8_t MaxDataLen = 8;
uint32_t id; ///< CAN ID with flags (above)
uint8_t data[MaxDataLen];
uint8_t dlc; ///< Data Length Code
CanFrame() :
id(0),
dlc(0)
{
fill(data, data + MaxDataLen, uint8_t(0));
}
CanFrame(uint32_t can_id, const uint8_t* can_data, uint8_t data_len) :
id(can_id),
dlc((data_len > MaxDataLen) ? MaxDataLen : data_len)
{
UAVCAN_ASSERT(can_data != UAVCAN_NULLPTR);
UAVCAN_ASSERT(data_len == dlc);
(void)copy(can_data, can_data + dlc, this->data);
}
bool operator!=(const CanFrame& rhs) const { return !operator==(rhs); }
bool operator==(const CanFrame& rhs) const
{
return (id == rhs.id) && (dlc == rhs.dlc) && equal(data, data + dlc, rhs.data);
}
bool isExtended() const { return id & FlagEFF; }
bool isRemoteTransmissionRequest() const { return id & FlagRTR; }
bool isErrorFrame() const { return id & FlagERR; }
#if UAVCAN_TOSTRING
enum StringRepresentation
{
StrTight, ///< Minimum string length (default)
StrAligned ///< Fixed formatting for any frame
};
std::string toString(StringRepresentation mode = StrTight) const;
#endif
/**
* CAN frame arbitration rules, particularly STD vs EXT:
* Marco Di Natale - "Understanding and using the Controller Area Network"
* http://www6.in.tum.de/pub/Main/TeachingWs2013MSE/CANbus.pdf
*/
bool priorityHigherThan(const CanFrame& rhs) const;
bool priorityLowerThan(const CanFrame& rhs) const { return rhs.priorityHigherThan(*this); }
};
/**
* CAN hardware filter config struct.
* Flags from @ref CanFrame can be applied to define frame type (EFF, EXT, etc.).
* @ref ICanIface::configureFilters().
*/
struct UAVCAN_EXPORT CanFilterConfig
{
uint32_t id;
uint32_t mask;
bool operator==(const CanFilterConfig& rhs) const
{
return rhs.id == id && rhs.mask == mask;
}
CanFilterConfig() :
id(0),
mask(0)
{ }
};
/**
* Events to look for during @ref ICanDriver::select() call.
* Bit position defines iface index, e.g. read = 1 << 2 to read from the third iface.
*/
struct UAVCAN_EXPORT CanSelectMasks
{
uint8_t read;
uint8_t write;
CanSelectMasks() :
read(0),
write(0)
{ }
};
/**
* Special IO flags.
*
* @ref CanIOFlagLoopback - Send the frame back to RX with true TX timestamps.
*
* @ref CanIOFlagAbortOnError - Abort transmission on first bus error instead of retransmitting. This does not
* affect the case of arbitration loss, in which case the retransmission will work
* as usual. This flag is used together with anonymous messages which allows to
* implement CSMA bus access. Read the spec for details.
*/
typedef uint16_t CanIOFlags;
static const CanIOFlags CanIOFlagLoopback = 1;
static const CanIOFlags CanIOFlagAbortOnError = 2;
/**
* Single non-blocking CAN interface.
*/
class UAVCAN_EXPORT ICanIface
{
public:
virtual ~ICanIface() { }
/**
* Non-blocking transmission.
*
* If the frame wasn't transmitted upon TX deadline, the driver should discard it.
*
* Note that it is LIKELY that the library will want to send the frames that were passed into the select()
* method as the next ones to transmit, but it is NOT guaranteed. The library can replace those with new
* frames between the calls.
*
* @return 1 = one frame transmitted, 0 = TX buffer full, negative for error.
*/
virtual int16_t send(const CanFrame& frame, MonotonicTime tx_deadline, CanIOFlags flags) = 0;
/**
* Non-blocking reception.
*
* Timestamps should be provided by the CAN driver, ideally by the hardware CAN controller.
*
* Monotonic timestamp is required and can be not precise since it is needed only for
* protocol timing validation (transfer timeouts and inter-transfer intervals).
*
* UTC timestamp is optional, if available it will be used for precise time synchronization;
* must be set to zero if not available.
*
* Refer to @ref ISystemClock to learn more about timestamps.
*
* @param [out] out_ts_monotonic Monotonic timestamp, mandatory.
* @param [out] out_ts_utc UTC timestamp, optional, zero if unknown.
* @return 1 = one frame received, 0 = RX buffer empty, negative for error.
*/
virtual int16_t receive(CanFrame& out_frame, MonotonicTime& out_ts_monotonic, UtcTime& out_ts_utc,
CanIOFlags& out_flags) = 0;
/**
* Configure the hardware CAN filters. @ref CanFilterConfig.
*
* @return 0 = success, negative for error.
*/
virtual int16_t configureFilters(const CanFilterConfig* filter_configs, uint16_t num_configs) = 0;
/**
* Number of available hardware filters.
*/
virtual uint16_t getNumFilters() const = 0;
/**
* Continuously incrementing counter of hardware errors.
* Arbitration lost should not be treated as a hardware error.
*/
virtual uint64_t getErrorCount() const = 0;
};
/**
* Generic CAN driver.
*/
class UAVCAN_EXPORT ICanDriver
{
public:
virtual ~ICanDriver() { }
/**
* Returns an interface by index, or null pointer if the index is out of range.
*/
virtual ICanIface* getIface(uint8_t iface_index) = 0;
/**
* Default implementation of this method calls the non-const overload of getIface().
* Can be overriden by the application if necessary.
*/
virtual const ICanIface* getIface(uint8_t iface_index) const
{
return const_cast<ICanDriver*>(this)->getIface(iface_index);
}
/**
* Total number of available CAN interfaces.
* This value shall not change after initialization.
*/
virtual uint8_t getNumIfaces() const = 0;
/**
* Block until the deadline, or one of the specified interfaces becomes available for read or write.
*
* Iface masks will be modified by the driver to indicate which exactly interfaces are available for IO.
*
* Bit position in the masks defines interface index.
*
* Note that it is allowed to return from this method even if no requested events actually happened, or if
* there are events that were not requested by the library.
*
* The pending TX argument contains an array of pointers to CAN frames that the library wants to transmit
* next, per interface. This is intended to allow the driver to properly prioritize transmissions; many
* drivers will not need to use it. If a write flag for the given interface is set to one in the select mask
* structure, then the corresponding pointer is guaranteed to be valid (not UAVCAN_NULLPTR).
*
* @param [in,out] inout_masks Masks indicating which interfaces are needed/available for IO.
* @param [in] pending_tx Array of frames, per interface, that are likely to be transmitted next.
* @param [in] blocking_deadline Zero means non-blocking operation.
* @return Positive number of ready interfaces or negative error code.
*/
virtual int16_t select(CanSelectMasks& inout_masks,
const CanFrame* (& pending_tx)[MaxCanIfaces],
MonotonicTime blocking_deadline) = 0;
};
}
#endif // UAVCAN_DRIVER_CAN_HPP_INCLUDED
@@ -0,0 +1,60 @@
/*
* System clock driver interface.
* Copyright (C) 2014 Pavel Kirienko <pavel.kirienko@gmail.com>
*/
#ifndef UAVCAN_DRIVER_SYSTEM_CLOCK_HPP_INCLUDED
#define UAVCAN_DRIVER_SYSTEM_CLOCK_HPP_INCLUDED
#include <uavcan/std.hpp>
#include <uavcan/build_config.hpp>
#include <uavcan/time.hpp>
namespace uavcan
{
/**
* System clock interface - monotonic and UTC.
*/
class UAVCAN_EXPORT ISystemClock
{
public:
virtual ~ISystemClock() { }
/**
* Monototic system clock.
*
* This clock shall never jump or change rate; the base time is irrelevant.
* This clock is mandatory and must remain functional at all times.
*
* On POSIX systems use clock_gettime() with CLOCK_MONOTONIC.
*/
virtual MonotonicTime getMonotonic() const = 0;
/**
* Global network clock.
* It doesn't have to be UTC, the name is a bit misleading - actual time base doesn't matter.
*
* This clock can be synchronized with other nodes on the bus, hence it can jump and/or change
* rate occasionally.
* This clock is optional; if it is not supported, return zero. Also return zero if the UTC time
* is not available yet (e.g. the device has just started up with no battery clock).
*
* For POSIX refer to clock_gettime(), gettimeofday().
*/
virtual UtcTime getUtc() const = 0;
/**
* Adjust the network-synchronized clock.
* Refer to @ref getUtc() for details.
*
* For POSIX refer to adjtime(), settimeofday().
*
* @param [in] adjustment Amount of time to add to the clock value.
*/
virtual void adjustUtc(UtcDuration adjustment) = 0;
};
}
#endif // UAVCAN_DRIVER_SYSTEM_CLOCK_HPP_INCLUDED
@@ -0,0 +1,210 @@
/*
* Copyright (C) 2014 Pavel Kirienko <pavel.kirienko@gmail.com>
*/
#ifndef UAVCAN_DYNAMIC_MEMORY_HPP_INCLUDED
#define UAVCAN_DYNAMIC_MEMORY_HPP_INCLUDED
#include <cassert>
#include <cstdlib>
#include <cstring>
#include <uavcan/std.hpp>
#include <uavcan/util/templates.hpp>
#include <uavcan/util/placement_new.hpp>
#include <uavcan/build_config.hpp>
namespace uavcan
{
/**
* This interface is used by other library components that need dynamic memory.
*/
class UAVCAN_EXPORT IPoolAllocator
{
public:
virtual ~IPoolAllocator() { }
virtual void* allocate(std::size_t size) = 0;
virtual void deallocate(const void* ptr) = 0;
/**
* Returns the maximum number of blocks this allocator can allocate.
*/
virtual uint16_t getBlockCapacity() const = 0;
};
/**
* Classic implementation of a pool allocator (Meyers).
*
* The allocator can be made thread-safe (optional) by means of providing a RAII-lock type via the second template
* argument. The allocator uses the lock only to access the shared state, therefore critical sections are only a few
* cycles long, which implies that it should be acceptable to use hardware IRQ disabling instead of a mutex for
* performance reasons. For example, an IRQ-based RAII-lock type can be implemented as follows:
* struct RaiiSynchronizer
* {
* RaiiSynchronizer() { __disable_irq(); }
* ~RaiiSynchronizer() { __enable_irq(); }
* };
*/
template <std::size_t PoolSize,
uint8_t BlockSize,
typename RaiiSynchronizer = char>
class UAVCAN_EXPORT PoolAllocator : public IPoolAllocator,
Noncopyable
{
union Node
{
uint8_t data[BlockSize];
Node* next;
};
Node* free_list_;
union
{
uint8_t bytes[PoolSize];
long double _aligner1;
long long _aligner2;
Node _aligner3;
} pool_;
uint16_t used_;
uint16_t max_used_;
public:
static const uint16_t NumBlocks = PoolSize / BlockSize;
PoolAllocator();
virtual void* allocate(std::size_t size) override;
virtual void deallocate(const void* ptr) override;
virtual uint16_t getBlockCapacity() const override { return NumBlocks; }
/**
* Return the number of blocks that are currently allocated/unallocated.
*/
uint16_t getNumUsedBlocks() const
{
RaiiSynchronizer lock;
(void)lock;
return used_;
}
uint16_t getNumFreeBlocks() const
{
RaiiSynchronizer lock;
(void)lock;
return static_cast<uint16_t>(NumBlocks - used_);
}
/**
* Returns the maximum number of blocks that were ever allocated at the same time.
*/
uint16_t getPeakNumUsedBlocks() const
{
RaiiSynchronizer lock;
(void)lock;
return max_used_;
}
};
/**
* Limits the maximum number of blocks that can be allocated in a given allocator.
*/
class LimitedPoolAllocator : public IPoolAllocator
{
IPoolAllocator& allocator_;
const uint16_t max_blocks_;
uint16_t used_blocks_;
public:
LimitedPoolAllocator(IPoolAllocator& allocator, std::size_t max_blocks)
: allocator_(allocator)
, max_blocks_(static_cast<uint16_t>(min<std::size_t>(max_blocks, 0xFFFFU)))
, used_blocks_(0)
{
UAVCAN_ASSERT(max_blocks_ > 0);
}
virtual void* allocate(std::size_t size) override;
virtual void deallocate(const void* ptr) override;
virtual uint16_t getBlockCapacity() const override;
};
// ----------------------------------------------------------------------------
/*
* PoolAllocator<>
*/
template <std::size_t PoolSize, uint8_t BlockSize, typename RaiiSynchronizer>
const uint16_t PoolAllocator<PoolSize, BlockSize, RaiiSynchronizer>::NumBlocks;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-align"
template <std::size_t PoolSize, uint8_t BlockSize, typename RaiiSynchronizer>
PoolAllocator<PoolSize, BlockSize, RaiiSynchronizer>::PoolAllocator() :
free_list_(reinterpret_cast<Node*>(pool_.bytes)),
used_(0),
max_used_(0)
{
// The limit is imposed by the width of the pool usage tracking variables.
StaticAssert<((PoolSize / BlockSize) <= 0xFFFFU)>::check();
(void)std::memset(pool_.bytes, 0, PoolSize);
for (unsigned i = 0; (i + 1) < (NumBlocks - 1 + 1); i++) // -Werror=type-limits
{
// coverity[dead_error_line : FALSE]
free_list_[i].next = free_list_ + i + 1;
}
free_list_[NumBlocks - 1].next = UAVCAN_NULLPTR;
}
#pragma GCC diagnostic pop
template <std::size_t PoolSize, uint8_t BlockSize, typename RaiiSynchronizer>
void* PoolAllocator<PoolSize, BlockSize, RaiiSynchronizer>::allocate(std::size_t size)
{
if (free_list_ == UAVCAN_NULLPTR || size > BlockSize)
{
return UAVCAN_NULLPTR;
}
RaiiSynchronizer lock;
(void)lock;
void* pmem = free_list_;
free_list_ = free_list_->next;
// Statistics
UAVCAN_ASSERT(used_ < NumBlocks);
used_++;
if (used_ > max_used_)
{
max_used_ = used_;
}
return pmem;
}
template <std::size_t PoolSize, uint8_t BlockSize, typename RaiiSynchronizer>
void PoolAllocator<PoolSize, BlockSize, RaiiSynchronizer>::deallocate(const void* ptr)
{
if (ptr == UAVCAN_NULLPTR)
{
return;
}
RaiiSynchronizer lock;
(void)lock;
Node* p = static_cast<Node*>(const_cast<void*>(ptr));
p->next = free_list_;
free_list_ = p;
// Statistics
UAVCAN_ASSERT(used_ > 0);
used_--;
}
}
#endif // UAVCAN_DYNAMIC_MEMORY_HPP_INCLUDED
@@ -0,0 +1,60 @@
/*
* Copyright (C) 2014 Pavel Kirienko <pavel.kirienko@gmail.com>
*/
#ifndef UAVCAN_ERROR_HPP_INCLUDED
#define UAVCAN_ERROR_HPP_INCLUDED
#include <uavcan/build_config.hpp>
#include <uavcan/std.hpp>
namespace uavcan
{
namespace
{
/**
* Common error codes.
*
* Functions that return signed integers may also return inverted error codes,
* i.e. returned value should be inverted back to get the actual error code.
*
* Return code 0 (zero) means no error.
*
* @{
*/
const int16_t ErrFailure = 1; ///< General failure
const int16_t ErrInvalidParam = 2;
const int16_t ErrMemory = 3;
const int16_t ErrDriver = 4; ///< Platform driver error
const int16_t ErrUnknownDataType = 5;
const int16_t ErrInvalidMarshalData = 6;
const int16_t ErrInvalidTransferListener = 7;
const int16_t ErrNotInited = 8;
const int16_t ErrRecursiveCall = 9;
const int16_t ErrLogic = 10;
const int16_t ErrPassiveMode = 11; ///< Operation not permitted in passive mode
const int16_t ErrTransferTooLong = 12; ///< Transfer of this length cannot be sent with given transfer type
const int16_t ErrInvalidConfiguration = 13;
/**
* @}
*/
}
/**
* Fatal error handler.
* Behavior:
* - If exceptions are enabled, throws std::runtime_error() with the supplied message text;
* - If assertions are enabled (see UAVCAN_ASSERT()), aborts execution using zero assertion.
* - Otherwise aborts execution via std::abort().
*/
#if __GNUC__
__attribute__ ((noreturn))
#endif
UAVCAN_EXPORT
// coverity[+kill]
void handleFatalError(const char* msg);
}
#endif // UAVCAN_ERROR_HPP_INCLUDED
@@ -0,0 +1,220 @@
/*
* Copyright (C) 2015 Pavel Kirienko <pavel.kirienko@gmail.com>
*/
#ifndef UAVCAN_HELPERS_HEAP_BASED_POOL_ALLOCATOR_HPP_INCLUDED
#define UAVCAN_HELPERS_HEAP_BASED_POOL_ALLOCATOR_HPP_INCLUDED
#include <cstdlib>
#include <uavcan/build_config.hpp>
#include <uavcan/debug.hpp>
#include <uavcan/dynamic_memory.hpp>
namespace uavcan
{
/**
* A special-purpose implementation of a pool allocator that keeps the pool in the heap using malloc()/free().
* The pool grows dynamically, ad-hoc, thus using as little memory as possible.
*
* Allocated blocks will not be freed back automatically, but there are two ways to force their deallocation:
* - Call @ref shrink() - this method frees all blocks that are unused at the moment.
* - Destroy the object - the desctructor calls @ref shrink().
*
* The pool can be limited in growth with hard and soft limits.
* The soft limit defines the value that will be reported via @ref IPoolAllocator::getBlockCapacity().
* The hard limit defines the maximum number of blocks that can be allocated from heap.
* Typically, the hard limit should be equal or greater than the soft limit.
*
* The allocator can be made thread-safe (optional) by means of providing a RAII-lock type via the second template
* argument. The allocator uses the lock only to access the shared state, therefore critical sections are only a few
* cycles long, which implies that it should be acceptable to use hardware IRQ disabling instead of a mutex for
* performance reasons. For example, an IRQ-based RAII-lock type can be implemented as follows:
* struct RaiiSynchronizer
* {
* RaiiSynchronizer() { __disable_irq(); }
* ~RaiiSynchronizer() { __enable_irq(); }
* };
*/
template <std::size_t BlockSize,
typename RaiiSynchronizer = char>
class UAVCAN_EXPORT HeapBasedPoolAllocator : public IPoolAllocator,
Noncopyable
{
union Node
{
Node* next;
private:
uint8_t data[BlockSize];
long double _aligner1;
long long _aligner2;
};
const uint16_t capacity_soft_limit_;
const uint16_t capacity_hard_limit_;
uint16_t num_reserved_blocks_;
uint16_t num_allocated_blocks_;
Node* reserve_;
public:
/**
* The allocator initializes with empty reserve, so first allocations will be served from heap.
*
* @param block_capacity_soft_limit Block capacity that will be reported via @ref getBlockCapacity().
*
* @param block_capacity_hard_limit Real block capacity limit; the number of allocated blocks will never
* exceed this value. Hard limit should be higher than soft limit.
* Default value is two times the soft limit.
*/
HeapBasedPoolAllocator(uint16_t block_capacity_soft_limit,
uint16_t block_capacity_hard_limit = 0) :
capacity_soft_limit_(block_capacity_soft_limit),
capacity_hard_limit_((block_capacity_hard_limit > 0) ? block_capacity_hard_limit :
static_cast<uint16_t>(min(static_cast<uint32_t>(block_capacity_soft_limit) * 2U,
static_cast<uint32_t>(NumericTraits<uint16_t>::max())))),
num_reserved_blocks_(0),
num_allocated_blocks_(0),
reserve_(UAVCAN_NULLPTR)
{ }
/**
* The destructor de-allocates all blocks that are currently in the reserve.
* BLOCKS THAT ARE CURRENTLY HELD BY THE APPLICATION WILL LEAK.
*/
~HeapBasedPoolAllocator()
{
shrink();
#if UAVCAN_DEBUG
if (num_allocated_blocks_ > 0)
{
UAVCAN_TRACE("HeapBasedPoolAllocator", "%u BLOCKS LEAKED", num_allocated_blocks_);
}
#endif
}
/**
* Takes a block from the reserve, unless it's empty.
* In the latter case, allocates a new block in the heap.
*/
virtual void* allocate(std::size_t size) override
{
if (size > BlockSize)
{
return UAVCAN_NULLPTR;
}
{
RaiiSynchronizer lock;
(void)lock;
Node* const p = reserve_;
if (UAVCAN_LIKELY(p != UAVCAN_NULLPTR))
{
reserve_ = reserve_->next;
num_allocated_blocks_++;
return p;
}
if (num_reserved_blocks_ >= capacity_hard_limit_) // Hard limit reached, no further allocations
{
return UAVCAN_NULLPTR;
}
}
// Unlikely branch
void* const m = std::malloc(sizeof(Node));
if (m != UAVCAN_NULLPTR)
{
RaiiSynchronizer lock;
(void)lock;
num_reserved_blocks_++;
num_allocated_blocks_++;
}
return m;
}
/**
* Puts the block back to reserve.
* The block will not be free()d automatically; see @ref shrink().
*/
virtual void deallocate(const void* ptr) override
{
if (ptr != UAVCAN_NULLPTR)
{
RaiiSynchronizer lock;
(void)lock;
Node* const node = static_cast<Node*>(const_cast<void*>(ptr));
node->next = reserve_;
reserve_ = node;
num_allocated_blocks_--;
}
}
/**
* The soft limit.
*/
virtual uint16_t getBlockCapacity() const override { return capacity_soft_limit_; }
/**
* The hard limit.
*/
uint16_t getBlockCapacityHardLimit() const { return capacity_hard_limit_; }
/**
* Frees all blocks that are not in use at the moment.
* Post-condition is getNumAllocatedBlocks() == getNumReservedBlocks().
*/
void shrink()
{
Node* p = UAVCAN_NULLPTR;
for (;;)
{
{
RaiiSynchronizer lock;
(void)lock;
// Removing from reserve and updating the counter.
p = reserve_;
if (p != UAVCAN_NULLPTR)
{
reserve_ = reserve_->next;
num_reserved_blocks_--;
}
else
{
break;
}
}
// Then freeing, having left the critical section.
std::free(p);
}
}
/**
* Number of blocks that are currently in use by the application.
*/
uint16_t getNumAllocatedBlocks() const
{
RaiiSynchronizer lock;
(void)lock;
return num_allocated_blocks_;
}
/**
* Number of blocks that are acquired from the heap.
*/
uint16_t getNumReservedBlocks() const
{
RaiiSynchronizer lock;
(void)lock;
return num_reserved_blocks_;
}
};
}
#endif
@@ -0,0 +1,63 @@
/*
* Copyright (C) 2014 Pavel Kirienko <pavel.kirienko@gmail.com>
*/
#ifndef UAVCAN_HELPERS_OSTREAM_HPP_INCLUDED
#define UAVCAN_HELPERS_OSTREAM_HPP_INCLUDED
#include <uavcan/util/templates.hpp>
#include <cstdio>
namespace uavcan
{
/**
* Compact replacement for std::ostream for use on embedded systems.
* Can be used for printing UAVCAN messages to stdout.
*
* Relevant discussion: https://groups.google.com/forum/#!topic/px4users/6c1CLNutN90
*
* Usage:
* OStream::instance() << "Hello world!" << OStream::endl;
*/
class UAVCAN_EXPORT OStream : uavcan::Noncopyable
{
OStream() { }
public:
static OStream& instance()
{
static OStream s;
return s;
}
static OStream& endl(OStream& stream)
{
std::printf("\n");
return stream;
}
};
inline OStream& operator<<(OStream& s, long long x) { std::printf("%lld", x); return s; }
inline OStream& operator<<(OStream& s, unsigned long long x) { std::printf("%llu", x); return s; }
inline OStream& operator<<(OStream& s, long x) { std::printf("%ld", x); return s; }
inline OStream& operator<<(OStream& s, unsigned long x) { std::printf("%lu", x); return s; }
inline OStream& operator<<(OStream& s, int x) { std::printf("%d", x); return s; }
inline OStream& operator<<(OStream& s, unsigned int x) { std::printf("%u", x); return s; }
inline OStream& operator<<(OStream& s, short x) { return operator<<(s, static_cast<int>(x)); }
inline OStream& operator<<(OStream& s, unsigned short x) { return operator<<(s, static_cast<unsigned>(x)); }
inline OStream& operator<<(OStream& s, long double x) { std::printf("%Lg", x); return s; }
inline OStream& operator<<(OStream& s, double x) { std::printf("%g", x); return s; }
inline OStream& operator<<(OStream& s, float x) { return operator<<(s, static_cast<double>(x)); }
inline OStream& operator<<(OStream& s, char x) { std::printf("%c", x); return s; }
inline OStream& operator<<(OStream& s, const char* x) { std::printf("%s", x); return s; }
inline OStream& operator<<(OStream& s, OStream&(*manip)(OStream&)) { return manip(s); }
}
#endif // UAVCAN_HELPERS_OSTREAM_HPP_INCLUDED
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,93 @@
/*
* Copyright (C) 2014 Pavel Kirienko <pavel.kirienko@gmail.com>
*/
#ifndef UAVCAN_MARSHAL_BIT_STREAM_HPP_INCLUDED
#define UAVCAN_MARSHAL_BIT_STREAM_HPP_INCLUDED
#include <cassert>
#include <uavcan/std.hpp>
#include <uavcan/util/templates.hpp>
#include <uavcan/transport/abstract_transfer_buffer.hpp>
#include <uavcan/build_config.hpp>
namespace uavcan
{
/**
* This function implements fast copy of unaligned bit arrays. It isn't part of the library API, so it is not exported.
* @param src_org Source array
* @param src_offset Bit offset of the first source byte
* @param src_len Number of bits to copy
* @param dst_org Destination array
* @param dst_offset Bit offset of the first destination byte
*/
void bitarrayCopy(const unsigned char* src_org, std::size_t src_offset, std::size_t src_len,
unsigned char* dst_org, std::size_t dst_offset);
/**
* This class treats a chunk of memory as an array of bits.
* It is used by the bit codec for serialization/deserialization.
*/
class UAVCAN_EXPORT BitStream
{
static const unsigned MaxBytesPerRW = 16;
ITransferBuffer& buf_;
unsigned bit_offset_;
uint8_t byte_cache_;
static inline unsigned bitlenToBytelen(unsigned bits) { return (bits + 7) / 8; }
static inline void copyBitArrayAlignedToUnaligned(const uint8_t* src_org, unsigned src_len,
uint8_t* dst_org, unsigned dst_offset)
{
bitarrayCopy(reinterpret_cast<const unsigned char*>(src_org), 0, src_len,
reinterpret_cast<unsigned char*>(dst_org), dst_offset);
}
static inline void copyBitArrayUnalignedToAligned(const uint8_t* src_org, unsigned src_offset, unsigned src_len,
uint8_t* dst_org)
{
bitarrayCopy(reinterpret_cast<const unsigned char*>(src_org), src_offset, src_len,
reinterpret_cast<unsigned char*>(dst_org), 0);
}
public:
static const unsigned MaxBitsPerRW = MaxBytesPerRW * 8;
enum
{
ResultOutOfBuffer = 0,
ResultOk = 1
};
explicit BitStream(ITransferBuffer& buf)
: buf_(buf)
, bit_offset_(0)
, byte_cache_(0)
{
StaticAssert<sizeof(uint8_t) == 1>::check();
}
/**
* Write/read calls interpret bytes as bit arrays, 8 bits per byte, where the most
* significant bits have lower index, i.e.:
* Hex: 55 2d
* Bits: 01010101 00101101
* Indices: 0 .. 7 8 .. 15
* Return values:
* Negative - Error
* Zero - Out of buffer space
* Positive - OK
*/
int write(const uint8_t* bytes, const unsigned bitlen);
int read(uint8_t* bytes, const unsigned bitlen);
#if UAVCAN_TOSTRING
std::string toString() const;
#endif
};
}
#endif // UAVCAN_MARSHAL_BIT_STREAM_HPP_INCLUDED
@@ -0,0 +1,142 @@
/*
* Copyright (C) 2014 Pavel Kirienko <pavel.kirienko@gmail.com>
*/
#ifndef UAVCAN_MARSHAL_CHAR_ARRAY_FORMATTER_HPP_INCLUDED
#define UAVCAN_MARSHAL_CHAR_ARRAY_FORMATTER_HPP_INCLUDED
#include <uavcan/build_config.hpp>
#include <uavcan/marshal/array.hpp>
#if !defined(UAVCAN_CPP_VERSION) || !defined(UAVCAN_CPP11)
# error UAVCAN_CPP_VERSION
#endif
#if UAVCAN_CPP_VERSION >= UAVCAN_CPP11
# include <type_traits>
#endif
namespace uavcan
{
#if UAVCAN_CPP_VERSION >= UAVCAN_CPP11
template <typename ArrayType_>
class UAVCAN_EXPORT CharArrayFormatter
{
ArrayType_& array_;
template <typename T>
typename std::enable_if<std::is_floating_point<T>::value>::type
writeValue(T value)
{
array_.template appendFormatted<double>("%g", double(value));
}
template <typename T>
typename std::enable_if<std::is_integral<T>::value>::type
writeValue(T value)
{
if (std::is_same<T, char>())
{
if (array_.size() != array_.capacity())
{
array_.push_back(typename ArrayType_::ValueType(value));
}
}
else if (std::is_signed<T>())
{
array_.template appendFormatted<long long>("%lli", static_cast<long long>(value));
}
else
{
array_.template appendFormatted<unsigned long long>("%llu", static_cast<unsigned long long>(value));
}
}
template <typename T>
typename std::enable_if<std::is_pointer<T>::value && !std::is_same<T, const char*>::value>::type
writeValue(T value)
{
array_.template appendFormatted<const void*>("%p", static_cast<const void*>(value));
}
void writeValue(const char* value)
{
array_.template appendFormatted<const char*>("%s", value);
}
public:
typedef ArrayType_ ArrayType;
explicit CharArrayFormatter(ArrayType& array)
: array_(array)
{ }
ArrayType& getArray() { return array_; }
const ArrayType& getArray() const { return array_; }
void write(const char* text)
{
writeValue(text);
}
template <typename T, typename... Args>
void write(const char* s, T value, Args... args)
{
while (s && *s)
{
if (*s == '%')
{
s += 1;
if (*s != '%')
{
writeValue(value);
write(++s, args...);
break;
}
}
writeValue(*s++);
}
}
};
#else
template <typename ArrayType_>
class UAVCAN_EXPORT CharArrayFormatter
{
ArrayType_& array_;
public:
typedef ArrayType_ ArrayType;
CharArrayFormatter(ArrayType& array)
: array_(array)
{ }
ArrayType& getArray() { return array_; }
const ArrayType& getArray() const { return array_; }
void write(const char* const text)
{
array_.template appendFormatted<const char*>("%s", text);
}
/**
* This version does not support more than one formatted argument, though it can be improved.
* And it is unsafe.
* There is typesafe version for C++11 above.
*/
template <typename A>
void write(const char* const format, const A value)
{
array_.template appendFormatted<A>(format, value);
}
};
#endif
}
#endif // UAVCAN_MARSHAL_CHAR_ARRAY_FORMATTER_HPP_INCLUDED
@@ -0,0 +1,238 @@
/*
* Copyright (C) 2014 Pavel Kirienko <pavel.kirienko@gmail.com>
*/
#ifndef UAVCAN_MARSHAL_FLOAT_SPEC_HPP_INCLUDED
#define UAVCAN_MARSHAL_FLOAT_SPEC_HPP_INCLUDED
#include <cmath>
#include <uavcan/std.hpp>
#include <uavcan/data_type.hpp>
#include <uavcan/util/templates.hpp>
#include <uavcan/build_config.hpp>
#include <uavcan/marshal/type_util.hpp>
#include <uavcan/marshal/integer_spec.hpp>
#ifndef UAVCAN_CPP_VERSION
# error UAVCAN_CPP_VERSION
#endif
#if UAVCAN_CPP_VERSION >= UAVCAN_CPP11
# include <limits> // Assuming that in C++11 mode all standard headers are available
#endif
namespace uavcan
{
template <unsigned BitLen>
struct NativeFloatSelector
{
struct ErrorNoSuchFloat;
typedef typename Select<(sizeof(float) * 8 >= BitLen), float,
typename Select<(sizeof(double) * 8 >= BitLen), double,
typename Select<(sizeof(long double) * 8 >= BitLen), long double,
ErrorNoSuchFloat>::Result>::Result>::Result Type;
};
class UAVCAN_EXPORT IEEE754Converter
{
// TODO: Non-IEEE float support
static uint16_t nativeIeeeToHalf(float value);
static float halfToNativeIeee(uint16_t value);
IEEE754Converter();
template <unsigned BitLen>
static void enforceIeee()
{
/*
* Some compilers may have is_iec559 to be defined false despite the fact that IEEE754 is supported.
* An acceptable workaround would be to put an #ifdef here.
*/
#if UAVCAN_CPP_VERSION >= UAVCAN_CPP11
StaticAssert<std::numeric_limits<typename NativeFloatSelector<BitLen>::Type>::is_iec559>::check();
#endif
}
public:
#if UAVCAN_CPP_VERSION >= UAVCAN_CPP11
/// UAVCAN requires rounding to nearest for all float conversions
static std::float_round_style roundstyle() { return std::round_to_nearest; }
#endif
template <unsigned BitLen>
static typename IntegerSpec<BitLen, SignednessUnsigned, CastModeTruncate>::StorageType
toIeee(typename NativeFloatSelector<BitLen>::Type value)
{
enforceIeee<BitLen>();
union
{
typename IntegerSpec<BitLen, SignednessUnsigned, CastModeTruncate>::StorageType i;
typename NativeFloatSelector<BitLen>::Type f;
} u;
StaticAssert<sizeof(u.f) * 8 == BitLen>::check();
u.f = value;
return u.i;
}
template <unsigned BitLen>
static typename NativeFloatSelector<BitLen>::Type
toNative(typename IntegerSpec<BitLen, SignednessUnsigned, CastModeTruncate>::StorageType value)
{
enforceIeee<BitLen>();
union
{
typename IntegerSpec<BitLen, SignednessUnsigned, CastModeTruncate>::StorageType i;
typename NativeFloatSelector<BitLen>::Type f;
} u;
StaticAssert<sizeof(u.f) * 8 == BitLen>::check();
u.i = value;
return u.f;
}
};
template <>
inline typename IntegerSpec<16, SignednessUnsigned, CastModeTruncate>::StorageType
IEEE754Converter::toIeee<16>(typename NativeFloatSelector<16>::Type value)
{
return nativeIeeeToHalf(value);
}
template <>
inline typename NativeFloatSelector<16>::Type
IEEE754Converter::toNative<16>(typename IntegerSpec<16, SignednessUnsigned, CastModeTruncate>::StorageType value)
{
return halfToNativeIeee(value);
}
template <unsigned BitLen> struct IEEE754Limits;
template <> struct IEEE754Limits<16>
{
typedef typename NativeFloatSelector<16>::Type NativeType;
static NativeType max() { return static_cast<NativeType>(65504.0); }
static NativeType epsilon() { return static_cast<NativeType>(9.77e-04); }
};
template <> struct IEEE754Limits<32>
{
typedef typename NativeFloatSelector<32>::Type NativeType;
static NativeType max() { return static_cast<NativeType>(3.40282346638528859812e+38); }
static NativeType epsilon() { return static_cast<NativeType>(1.19209289550781250000e-7); }
};
template <> struct IEEE754Limits<64>
{
typedef typename NativeFloatSelector<64>::Type NativeType;
static NativeType max() { return static_cast<NativeType>(1.79769313486231570815e+308L); }
static NativeType epsilon() { return static_cast<NativeType>(2.22044604925031308085e-16L); }
};
template <unsigned BitLen_, CastMode CastMode>
class UAVCAN_EXPORT FloatSpec : public IEEE754Limits<BitLen_>
{
FloatSpec();
public:
enum { BitLen = BitLen_ };
enum { MinBitLen = BitLen };
enum { MaxBitLen = BitLen };
enum { IsPrimitive = 1 };
typedef typename NativeFloatSelector<BitLen>::Type StorageType;
#if UAVCAN_CPP_VERSION < UAVCAN_CPP11
enum { IsExactRepresentation = (sizeof(StorageType) * 8 == BitLen) };
#else
enum { IsExactRepresentation = (sizeof(StorageType) * 8 == BitLen) && std::numeric_limits<StorageType>::is_iec559 };
#endif
using IEEE754Limits<BitLen>::max;
using IEEE754Limits<BitLen>::epsilon;
#if UAVCAN_CPP_VERSION >= UAVCAN_CPP11
static std::float_round_style roundstyle() { return IEEE754Converter::roundstyle(); }
#endif
static int encode(StorageType value, ScalarCodec& codec, TailArrayOptimizationMode)
{
// cppcheck-suppress duplicateExpression
if (CastMode == CastModeSaturate)
{
saturate(value);
}
else
{
truncate(value);
}
return codec.encode<BitLen>(IEEE754Converter::toIeee<BitLen>(value));
}
static int decode(StorageType& out_value, ScalarCodec& codec, TailArrayOptimizationMode)
{
typename IntegerSpec<BitLen, SignednessUnsigned, CastModeTruncate>::StorageType ieee = 0;
const int res = codec.decode<BitLen>(ieee);
if (res <= 0)
{
return res;
}
out_value = IEEE754Converter::toNative<BitLen>(ieee);
return res;
}
static void extendDataTypeSignature(DataTypeSignature&) { }
private:
static inline void saturate(StorageType& value)
{
if ((IsExactRepresentation == 0) && isFinite(value))
{
if (value > max())
{
value = max();
}
else if (value < -max())
{
value = -max();
}
else
{
; // Valid range
}
}
}
static inline void truncate(StorageType& value)
{
if ((IsExactRepresentation == 0) && isFinite(value))
{
if (value > max())
{
value = NumericTraits<StorageType>::infinity();
}
else if (value < -max())
{
value = -NumericTraits<StorageType>::infinity();
}
else
{
; // Valid range
}
}
}
};
template <unsigned BitLen, CastMode CastMode>
class UAVCAN_EXPORT YamlStreamer<FloatSpec<BitLen, CastMode> >
{
typedef typename FloatSpec<BitLen, CastMode>::StorageType StorageType;
public:
template <typename Stream> // cppcheck-suppress passedByValue
static void stream(Stream& s, const StorageType value, int)
{
s << value;
}
};
}
#endif // UAVCAN_MARSHAL_FLOAT_SPEC_HPP_INCLUDED
@@ -0,0 +1,210 @@
/*
* Copyright (C) 2014 Pavel Kirienko <pavel.kirienko@gmail.com>
*/
#ifndef UAVCAN_MARSHAL_INTEGER_SPEC_HPP_INCLUDED
#define UAVCAN_MARSHAL_INTEGER_SPEC_HPP_INCLUDED
#include <uavcan/std.hpp>
#include <uavcan/data_type.hpp>
#include <uavcan/util/templates.hpp>
#include <uavcan/marshal/scalar_codec.hpp>
#include <uavcan/marshal/type_util.hpp>
namespace uavcan
{
enum Signedness { SignednessUnsigned, SignednessSigned };
/**
* This template will be used for signed and unsigned integers more than 1 bit long.
* There are explicit specializations for booleans below.
*/
template <unsigned BitLen_, Signedness Signedness, CastMode CastMode>
class UAVCAN_EXPORT IntegerSpec
{
struct ErrorNoSuchInteger;
public:
enum { IsSigned = Signedness == SignednessSigned };
enum { BitLen = BitLen_ };
enum { MinBitLen = BitLen };
enum { MaxBitLen = BitLen };
enum { IsPrimitive = 1 };
typedef typename Select<(BitLen <= 8), typename Select<IsSigned, int8_t, uint8_t>::Result,
typename Select<(BitLen <= 16), typename Select<IsSigned, int16_t, uint16_t>::Result,
typename Select<(BitLen <= 32), typename Select<IsSigned, int32_t, uint32_t>::Result,
typename Select<(BitLen <= 64), typename Select<IsSigned, int64_t, uint64_t>::Result,
ErrorNoSuchInteger>::Result>::Result>::Result>::Result StorageType;
typedef typename IntegerSpec<BitLen, SignednessUnsigned, CastMode>::StorageType UnsignedStorageType;
private:
IntegerSpec();
struct LimitsImplGeneric
{
static StorageType max()
{
StaticAssert<(sizeof(uintmax_t) >= 8)>::check();
if (IsSigned == 0)
{
return StorageType((uintmax_t(1) << static_cast<unsigned>(BitLen)) - 1U);
}
else
{
return StorageType((uintmax_t(1) << (static_cast<unsigned>(BitLen) - 1U)) - 1);
}
}
static UnsignedStorageType mask()
{
StaticAssert<(sizeof(uintmax_t) >= 8U)>::check();
return UnsignedStorageType((uintmax_t(1) << static_cast<unsigned>(BitLen)) - 1U);
}
};
struct LimitsImpl64
{
static StorageType max()
{
return StorageType((IsSigned == 0) ? 0xFFFFFFFFFFFFFFFFULL : 0x7FFFFFFFFFFFFFFFLL);
}
static UnsignedStorageType mask() { return 0xFFFFFFFFFFFFFFFFULL; }
};
typedef typename Select<(BitLen == 64), LimitsImpl64, LimitsImplGeneric>::Result Limits;
static void saturate(StorageType& value)
{
if (value > max())
{
value = max();
}
else if (value <= min()) // 'Less or Equal' allows to suppress compiler warning on unsigned types
{
value = min();
}
else
{
; // Valid range
}
}
static void truncate(StorageType& value) { value = value & StorageType(mask()); }
static void validate()
{
StaticAssert<(BitLen <= (sizeof(StorageType) * 8))>::check();
// coverity[result_independent_of_operands : FALSE]
UAVCAN_ASSERT(max() <= NumericTraits<StorageType>::max());
// coverity[result_independent_of_operands : FALSE]
UAVCAN_ASSERT(min() >= NumericTraits<StorageType>::min());
}
public:
static StorageType max() { return Limits::max(); }
static StorageType min() { return IsSigned ? StorageType(-max() - 1) : 0; }
static UnsignedStorageType mask() { return Limits::mask(); }
static int encode(StorageType value, ScalarCodec& codec, TailArrayOptimizationMode)
{
validate();
// cppcheck-suppress duplicateExpression
if (CastMode == CastModeSaturate)
{
saturate(value);
}
else
{
truncate(value);
}
return codec.encode<BitLen>(value);
}
static int decode(StorageType& out_value, ScalarCodec& codec, TailArrayOptimizationMode)
{
validate();
return codec.decode<BitLen>(out_value);
}
static void extendDataTypeSignature(DataTypeSignature&) { }
};
/**
* Boolean specialization
*/
template <CastMode CastMode>
class UAVCAN_EXPORT IntegerSpec<1, SignednessUnsigned, CastMode>
{
public:
enum { IsSigned = 0 };
enum { BitLen = 1 };
enum { MinBitLen = 1 };
enum { MaxBitLen = 1 };
enum { IsPrimitive = 1 };
typedef bool StorageType;
typedef bool UnsignedStorageType;
private:
IntegerSpec();
public:
static StorageType max() { return true; }
static StorageType min() { return false; }
static UnsignedStorageType mask() { return true; }
static int encode(StorageType value, ScalarCodec& codec, TailArrayOptimizationMode)
{
return codec.encode<BitLen>(value);
}
static int decode(StorageType& out_value, ScalarCodec& codec, TailArrayOptimizationMode)
{
return codec.decode<BitLen>(out_value);
}
static void extendDataTypeSignature(DataTypeSignature&) { }
};
template <CastMode CastMode>
class IntegerSpec<1, SignednessSigned, CastMode>; // Invalid instantiation
template <Signedness Signedness, CastMode CastMode>
class IntegerSpec<0, Signedness, CastMode>; // Invalid instantiation
template <typename T>
struct IsIntegerSpec
{
enum { Result = 0 };
};
template <unsigned BitLen, Signedness Signedness, CastMode CastMode>
struct IsIntegerSpec<IntegerSpec<BitLen, Signedness, CastMode> >
{
enum { Result = 1 };
};
template <unsigned BitLen, Signedness Signedness, CastMode CastMode>
class UAVCAN_EXPORT YamlStreamer<IntegerSpec<BitLen, Signedness, CastMode> >
{
typedef IntegerSpec<BitLen, Signedness, CastMode> RawType;
typedef typename RawType::StorageType StorageType;
public:
template <typename Stream> // cppcheck-suppress passedByValue
static void stream(Stream& s, const StorageType value, int)
{
// Get rid of character types - we want its integer representation, not ASCII code
typedef typename Select<(sizeof(StorageType) >= sizeof(int)), StorageType,
typename Select<RawType::IsSigned, int, unsigned>::Result >::Result TempType;
s << TempType(value);
}
};
}
#endif // UAVCAN_MARSHAL_INTEGER_SPEC_HPP_INCLUDED
@@ -0,0 +1,140 @@
/*
* Copyright (C) 2014 Pavel Kirienko <pavel.kirienko@gmail.com>
*/
#ifndef UAVCAN_MARSHAL_SCALAR_CODEC_HPP_INCLUDED
#define UAVCAN_MARSHAL_SCALAR_CODEC_HPP_INCLUDED
#include <cassert>
#include <uavcan/std.hpp>
#include <uavcan/build_config.hpp>
#include <uavcan/util/templates.hpp>
#include <uavcan/marshal/bit_stream.hpp>
namespace uavcan
{
/**
* This class implements fast encoding/decoding of primitive type scalars into/from bit arrays.
* It uses the compile-time type information to eliminate run-time operations where possible.
*/
class UAVCAN_EXPORT ScalarCodec
{
BitStream& stream_;
static void swapByteOrder(uint8_t* bytes, unsigned len);
template <unsigned BitLen, unsigned Size>
static typename EnableIf<(BitLen > 8)>::Type
convertByteOrder(uint8_t (&bytes)[Size])
{
#if defined(BYTE_ORDER) && defined(BIG_ENDIAN)
static const bool big_endian = BYTE_ORDER == BIG_ENDIAN;
#else
union { long int l; char c[sizeof(long int)]; } u;
u.l = 1;
const bool big_endian = u.c[sizeof(long int) - 1] == 1;
#endif
/*
* I didn't have any big endian machine nearby, so big endian support wasn't tested yet.
* It is likely to be OK anyway, so feel free to remove this UAVCAN_ASSERT() as needed.
*/
UAVCAN_ASSERT(big_endian == false);
if (big_endian)
{
swapByteOrder(bytes, Size);
}
}
template <unsigned BitLen, unsigned Size>
static typename EnableIf<(BitLen <= 8)>::Type
convertByteOrder(uint8_t (&)[Size]) { }
template <unsigned BitLen, typename T>
static typename EnableIf<static_cast<bool>(NumericTraits<T>::IsSigned) && ((sizeof(T) * 8) > BitLen)>::Type
fixTwosComplement(T& value)
{
StaticAssert<NumericTraits<T>::IsInteger>::check(); // Not applicable to floating point types
if (value & (T(1) << (BitLen - 1))) // The most significant bit is set --> negative
{
value |= T(T(0xFFFFFFFFFFFFFFFFULL) & ~((T(1) << BitLen) - 1));
}
}
template <unsigned BitLen, typename T>
static typename EnableIf<!static_cast<bool>(NumericTraits<T>::IsSigned) || ((sizeof(T) * 8) == BitLen)>::Type
fixTwosComplement(T&) { }
template <unsigned BitLen, typename T>
static typename EnableIf<((sizeof(T) * 8) > BitLen)>::Type
clearExtraBits(T& value)
{
value &= (T(1) << BitLen) - 1; // Signedness doesn't matter
}
template <unsigned BitLen, typename T>
static typename EnableIf<((sizeof(T) * 8) == BitLen)>::Type
clearExtraBits(T&) { }
template <unsigned BitLen, typename T>
void validate()
{
StaticAssert<((sizeof(T) * 8) >= BitLen)>::check();
StaticAssert<(BitLen <= BitStream::MaxBitsPerRW)>::check();
StaticAssert<static_cast<bool>(NumericTraits<T>::IsSigned) ? (BitLen > 1) : true>::check();
}
int encodeBytesImpl(uint8_t* bytes, unsigned bitlen);
int decodeBytesImpl(uint8_t* bytes, unsigned bitlen);
public:
explicit ScalarCodec(BitStream& stream)
: stream_(stream)
{ }
template <unsigned BitLen, typename T>
int encode(const T value);
template <unsigned BitLen, typename T>
int decode(T& value);
};
// ----------------------------------------------------------------------------
template <unsigned BitLen, typename T>
int ScalarCodec::encode(const T value)
{
validate<BitLen, T>();
union ByteUnion
{
T value;
uint8_t bytes[sizeof(T)];
} byte_union;
byte_union.value = value;
clearExtraBits<BitLen, T>(byte_union.value);
convertByteOrder<BitLen>(byte_union.bytes);
return encodeBytesImpl(byte_union.bytes, BitLen);
}
template <unsigned BitLen, typename T>
int ScalarCodec::decode(T& value)
{
validate<BitLen, T>();
union ByteUnion
{
T value;
uint8_t bytes[sizeof(T)];
} byte_union;
byte_union.value = T();
const int read_res = decodeBytesImpl(byte_union.bytes, BitLen);
if (read_res > 0)
{
convertByteOrder<BitLen>(byte_union.bytes);
fixTwosComplement<BitLen, T>(byte_union.value);
value = byte_union.value;
}
return read_res;
}
}
#endif // UAVCAN_MARSHAL_SCALAR_CODEC_HPP_INCLUDED
@@ -0,0 +1,88 @@
/*
* Copyright (C) 2014 Pavel Kirienko <pavel.kirienko@gmail.com>
*/
#ifndef UAVCAN_MARSHAL_TYPE_UTIL_HPP_INCLUDED
#define UAVCAN_MARSHAL_TYPE_UTIL_HPP_INCLUDED
#include <uavcan/build_config.hpp>
#include <uavcan/util/templates.hpp>
#include <uavcan/util/comparison.hpp>
namespace uavcan
{
/**
* Read the specs to learn more about cast modes.
*/
enum CastMode { CastModeSaturate, CastModeTruncate };
/**
* Read the specs to learn more about tail array optimizations.
*/
enum TailArrayOptimizationMode { TailArrayOptDisabled, TailArrayOptEnabled };
/**
* Compile-time: Returns the number of bits needed to represent an integer value.
*/
template <unsigned long Num>
struct IntegerBitLen
{
enum { Result = 1 + IntegerBitLen<(Num >> 1)>::Result };
};
template <>
struct IntegerBitLen<0>
{
enum { Result = 0 };
};
/**
* Compile-time: Returns the number of bytes needed to contain the given number of bits. Assumes 1 byte == 8 bit.
*/
template <unsigned long BitLen>
struct BitLenToByteLen
{
enum { Result = (BitLen + 7) / 8 };
};
/**
* Compile-time: Helper for integer and float specialization classes. Returns the platform-specific storage type.
*/
template <typename T, typename Enable = void>
struct StorageType
{
typedef T Type;
};
template <typename T>
struct StorageType<T, typename EnableIfType<typename T::StorageType>::Type>
{
typedef typename T::StorageType Type;
};
/**
* Compile-time: Whether T is a primitive type on this platform.
*/
template <typename T>
class IsPrimitiveType
{
typedef char Yes;
struct No { Yes dummy[8]; };
template <typename U>
static typename EnableIf<U::IsPrimitive, Yes>::Type test(int);
template <typename>
static No test(...);
public:
enum { Result = sizeof(test<T>(0)) == sizeof(Yes) };
};
/**
* Streams a given value into YAML string. Please see the specializations.
*/
template <typename T>
class UAVCAN_EXPORT YamlStreamer;
}
#endif // UAVCAN_MARSHAL_TYPE_UTIL_HPP_INCLUDED
@@ -0,0 +1,13 @@
/*
* Copyright (C) 2014 Pavel Kirienko <pavel.kirienko@gmail.com>
*/
#ifndef UAVCAN_MARSHAL_TYPES_HPP_INCLUDED
#define UAVCAN_MARSHAL_TYPES_HPP_INCLUDED
#include <uavcan/marshal/integer_spec.hpp>
#include <uavcan/marshal/float_spec.hpp>
#include <uavcan/marshal/array.hpp>
#include <uavcan/marshal/type_util.hpp>
#endif // UAVCAN_MARSHAL_TYPES_HPP_INCLUDED
@@ -0,0 +1,130 @@
/*
* Copyright (C) 2014 Pavel Kirienko <pavel.kirienko@gmail.com>
*/
#ifndef UAVCAN_NODE_ABSTRACT_NODE_HPP_INCLUDED
#define UAVCAN_NODE_ABSTRACT_NODE_HPP_INCLUDED
#include <uavcan/build_config.hpp>
#include <uavcan/dynamic_memory.hpp>
#include <uavcan/node/scheduler.hpp>
namespace uavcan
{
/**
* Abstract node class. If you're going to implement your own node class for your application,
* please inherit this class so it can be used with default publisher, subscriber, server, etc. classes.
* Normally you don't need to use it directly though - please refer to the class Node<> instead.
*/
class UAVCAN_EXPORT INode
{
public:
virtual ~INode() { }
virtual IPoolAllocator& getAllocator() = 0;
virtual Scheduler& getScheduler() = 0;
virtual const Scheduler& getScheduler() const = 0;
virtual void registerInternalFailure(const char* msg) = 0;
Dispatcher& getDispatcher() { return getScheduler().getDispatcher(); }
const Dispatcher& getDispatcher() const { return getScheduler().getDispatcher(); }
ISystemClock& getSystemClock() { return getScheduler().getSystemClock(); }
MonotonicTime getMonotonicTime() const { return getScheduler().getMonotonicTime(); }
UtcTime getUtcTime() const { return getScheduler().getUtcTime(); }
/**
* Returns the Node ID of this node.
* If Node ID was not set yet, an invalid value will be returned.
*/
NodeID getNodeID() const { return getScheduler().getDispatcher().getNodeID(); }
/**
* Sets the Node ID of this node.
* Node ID can be assigned only once. This method returns true if the Node ID was successfully assigned, otherwise
* it returns false.
* As long as a valid Node ID is not set, the node will remain in passive mode.
* Using a non-unicast Node ID puts the node into passive mode (as default).
*/
bool setNodeID(NodeID nid)
{
return getScheduler().getDispatcher().setNodeID(nid);
}
/**
* Whether the node is in passive mode, i.e. can't transmit anything to the bus.
* Please read the specs to learn more.
*/
bool isPassiveMode() const { return getScheduler().getDispatcher().isPassiveMode(); }
/**
* Same as @ref spin(MonotonicDuration), but the deadline is specified as an absolute time value
* rather than duration.
*/
int spin(MonotonicTime deadline)
{
return getScheduler().spin(deadline);
}
/**
* Runs the node.
* Normally your application should not block anywhere else.
* Block inside this method forever or call it periodically.
* This method returns 0 if no errors occurred, or a negative error code if something failed (see error.hpp).
*/
int spin(MonotonicDuration duration)
{
return getScheduler().spin(getMonotonicTime() + duration);
}
/**
* This method is designed for non-blocking applications.
* Instead of blocking, it returns immediately once all available CAN frames and timer events are processed.
* Note that this is unlike plain @ref spin(), which will strictly return when the deadline is reached,
* even if there still are unprocessed events.
* This method returns 0 if no errors occurred, or a negative error code if something failed (see error.hpp).
*/
int spinOnce()
{
return getScheduler().spinOnce();
}
/**
* This method allows to directly transmit a raw CAN frame circumventing the whole UAVCAN stack.
* Mandatory parameters:
*
* @param frame CAN frame to be transmitted.
*
* @param tx_deadline The frame will be discarded if it could not be transmitted by this time.
*
* @param iface_mask This bitmask allows to select what CAN interfaces this frame should go into.
* Example:
* - 1 - the frame will be sent only to iface 0.
* - 4 - the frame will be sent only to iface 2.
* - 3 - the frame will be sent to ifaces 0 and 1.
*
* Optional parameters:
*
* @param qos Quality of service. Please refer to the CAN IO manager for details.
*
* @param flags CAN IO flags. Please refer to the CAN driver API for details.
*/
int injectTxFrame(const CanFrame& frame, MonotonicTime tx_deadline, uint8_t iface_mask,
CanTxQueue::Qos qos = CanTxQueue::Volatile,
CanIOFlags flags = 0)
{
return getDispatcher().getCanIOManager().send(frame, tx_deadline, MonotonicTime(), iface_mask, qos, flags);
}
#if !UAVCAN_TINY
/**
* The @ref IRxFrameListener interface allows one to monitor all incoming CAN frames.
* This feature can be used to implement multithreaded nodes, or to add secondary protocol support.
*/
void removeRxFrameListener() { getDispatcher().removeRxFrameListener(); }
void installRxFrameListener(IRxFrameListener* lst) { getDispatcher().installRxFrameListener(lst); }
#endif
};
}
#endif // UAVCAN_NODE_ABSTRACT_NODE_HPP_INCLUDED
@@ -0,0 +1,201 @@
/*
* Copyright (C) 2014 Pavel Kirienko <pavel.kirienko@gmail.com>
*/
#ifndef UAVCAN_NODE_GENERIC_PUBLISHER_HPP_INCLUDED
#define UAVCAN_NODE_GENERIC_PUBLISHER_HPP_INCLUDED
#include <uavcan/error.hpp>
#include <uavcan/node/abstract_node.hpp>
#include <uavcan/data_type.hpp>
#include <uavcan/node/global_data_type_registry.hpp>
#include <uavcan/debug.hpp>
#include <uavcan/transport/transfer_buffer.hpp>
#include <uavcan/transport/transfer_sender.hpp>
#include <uavcan/marshal/scalar_codec.hpp>
#include <uavcan/marshal/types.hpp>
namespace uavcan
{
class GenericPublisherBase : Noncopyable
{
TransferSender sender_;
MonotonicDuration tx_timeout_;
INode& node_;
protected:
GenericPublisherBase(INode& node, MonotonicDuration tx_timeout,
MonotonicDuration max_transfer_interval)
: sender_(node.getDispatcher(), max_transfer_interval)
, tx_timeout_(tx_timeout)
, node_(node)
{
setTxTimeout(tx_timeout);
#if UAVCAN_DEBUG
UAVCAN_ASSERT(getTxTimeout() == tx_timeout); // Making sure default values are OK
#endif
}
~GenericPublisherBase() { }
bool isInited() const;
int doInit(DataTypeKind dtkind, const char* dtname, CanTxQueue::Qos qos);
MonotonicTime getTxDeadline() const;
int genericPublish(const StaticTransferBufferImpl& buffer, TransferType transfer_type,
NodeID dst_node_id, TransferID* tid, MonotonicTime blocking_deadline);
TransferSender& getTransferSender() { return sender_; }
const TransferSender& getTransferSender() const { return sender_; }
public:
static MonotonicDuration getMinTxTimeout() { return MonotonicDuration::fromUSec(200); }
static MonotonicDuration getMaxTxTimeout() { return MonotonicDuration::fromMSec(60000); }
MonotonicDuration getTxTimeout() const { return tx_timeout_; }
void setTxTimeout(MonotonicDuration tx_timeout);
/**
* By default, attempt to send a transfer from passive mode will result in an error @ref ErrPassive.
* This option allows to enable sending anonymous transfers from passive mode.
*/
void allowAnonymousTransfers()
{
sender_.allowAnonymousTransfers();
}
/**
* Priority of outgoing transfers.
*/
TransferPriority getPriority() const { return sender_.getPriority(); }
void setPriority(const TransferPriority prio) { sender_.setPriority(prio); }
INode& getNode() const { return node_; }
};
/**
* Generic publisher, suitable for messages and services.
* DataSpec - data type specification class
* DataStruct - instantiable class
*/
template <typename DataSpec, typename DataStruct>
class UAVCAN_EXPORT GenericPublisher : public GenericPublisherBase
{
struct ZeroTransferBuffer : public StaticTransferBufferImpl
{
ZeroTransferBuffer() : StaticTransferBufferImpl(UAVCAN_NULLPTR, 0) { }
};
typedef typename Select<DataStruct::MaxBitLen == 0,
ZeroTransferBuffer,
StaticTransferBuffer<BitLenToByteLen<DataStruct::MaxBitLen>::Result> >::Result Buffer;
enum
{
Qos = (DataTypeKind(DataSpec::DataTypeKind) == DataTypeKindMessage) ?
CanTxQueue::Volatile : CanTxQueue::Persistent
};
int checkInit();
int doEncode(const DataStruct& message, ITransferBuffer& buffer) const;
int genericPublish(const DataStruct& message, TransferType transfer_type, NodeID dst_node_id,
TransferID* tid, MonotonicTime blocking_deadline);
public:
/**
* @param max_transfer_interval Maximum expected time interval between subsequent publications. Leave default.
*/
GenericPublisher(INode& node, MonotonicDuration tx_timeout,
MonotonicDuration max_transfer_interval = TransferSender::getDefaultMaxTransferInterval())
: GenericPublisherBase(node, tx_timeout, max_transfer_interval)
{ }
~GenericPublisher() { }
/**
* Init method can be called prior first publication, but it's not necessary
* because the publisher can be automatically initialized ad-hoc.
*/
int init()
{
return checkInit();
}
/**
* This overload allows to set the priority; otherwise it's the same.
*/
int init(TransferPriority priority)
{
setPriority(priority);
return checkInit();
}
int publish(const DataStruct& message, TransferType transfer_type, NodeID dst_node_id,
MonotonicTime blocking_deadline = MonotonicTime())
{
return genericPublish(message, transfer_type, dst_node_id, UAVCAN_NULLPTR, blocking_deadline);
}
int publish(const DataStruct& message, TransferType transfer_type, NodeID dst_node_id, TransferID tid,
MonotonicTime blocking_deadline = MonotonicTime())
{
return genericPublish(message, transfer_type, dst_node_id, &tid, blocking_deadline);
}
};
// ----------------------------------------------------------------------------
template <typename DataSpec, typename DataStruct>
int GenericPublisher<DataSpec, DataStruct>::checkInit()
{
if (isInited())
{
return 0;
}
return doInit(DataTypeKind(DataSpec::DataTypeKind), DataSpec::getDataTypeFullName(), CanTxQueue::Qos(Qos));
}
template <typename DataSpec, typename DataStruct>
int GenericPublisher<DataSpec, DataStruct>::doEncode(const DataStruct& message, ITransferBuffer& buffer) const
{
BitStream bitstream(buffer);
ScalarCodec codec(bitstream);
const int encode_res = DataStruct::encode(message, codec);
if (encode_res <= 0)
{
UAVCAN_ASSERT(0); // Impossible, internal error
return -ErrInvalidMarshalData;
}
return encode_res;
}
template <typename DataSpec, typename DataStruct>
int GenericPublisher<DataSpec, DataStruct>::genericPublish(const DataStruct& message, TransferType transfer_type,
NodeID dst_node_id, TransferID* tid,
MonotonicTime blocking_deadline)
{
const int res = checkInit();
if (res < 0)
{
return res;
}
Buffer buffer;
const int encode_res = doEncode(message, buffer);
if (encode_res < 0)
{
return encode_res;
}
return GenericPublisherBase::genericPublish(buffer, transfer_type, dst_node_id, tid, blocking_deadline);
}
}
#endif // UAVCAN_NODE_GENERIC_PUBLISHER_HPP_INCLUDED
@@ -0,0 +1,299 @@
/*
* Copyright (C) 2014 Pavel Kirienko <pavel.kirienko@gmail.com>
*/
#ifndef UAVCAN_NODE_GENERIC_SUBSCRIBER_HPP_INCLUDED
#define UAVCAN_NODE_GENERIC_SUBSCRIBER_HPP_INCLUDED
#include <uavcan/error.hpp>
#include <uavcan/node/abstract_node.hpp>
#include <uavcan/data_type.hpp>
#include <uavcan/node/global_data_type_registry.hpp>
#include <uavcan/util/templates.hpp>
#include <uavcan/util/lazy_constructor.hpp>
#include <uavcan/debug.hpp>
#include <uavcan/transport/transfer_listener.hpp>
#include <uavcan/marshal/scalar_codec.hpp>
#include <uavcan/marshal/types.hpp>
namespace uavcan
{
/**
* This class extends the data structure with extra information obtained from the transport layer,
* such as Source Node ID, timestamps, Transfer ID, index of the interface this transfer was picked up from, etc.
*
* PLEASE NOTE that since this class inherits the data structure type, subscription callbacks can accept either
* object of this class or the data structure type directly if the extra information is not needed.
*
* For example, both of these callbacks can be used with the same data structure 'Foo':
* void first(const ReceivedDataStructure<Foo>& msg);
* void second(const Foo& msg);
* In the latter case, an implicit cast will happen before the callback is invoked.
*
* This class is not copyable because it holds a reference to a stack-allocated transfer descriptor object.
* You can slice cast it to the underlying data type though, which would be copyable:
* DataType dt = rds; // where rds is of type ReceivedDataStructure<DataType>
* // dt is now copyable
*/
template <typename DataType_>
class UAVCAN_EXPORT ReceivedDataStructure : public DataType_, Noncopyable
{
const IncomingTransfer* const _transfer_; ///< Such weird name is necessary to avoid clashing with DataType fields
template <typename Ret, Ret(IncomingTransfer::*Fun) () const>
Ret safeget() const
{
if (_transfer_ == UAVCAN_NULLPTR)
{
return Ret();
}
return (_transfer_->*Fun)();
}
protected:
ReceivedDataStructure()
: _transfer_(UAVCAN_NULLPTR)
{ }
ReceivedDataStructure(const IncomingTransfer* arg_transfer)
: _transfer_(arg_transfer)
{
UAVCAN_ASSERT(arg_transfer != UAVCAN_NULLPTR);
}
public:
typedef DataType_ DataType;
MonotonicTime getMonotonicTimestamp() const
{
return safeget<MonotonicTime, &IncomingTransfer::getMonotonicTimestamp>();
}
UtcTime getUtcTimestamp() const { return safeget<UtcTime, &IncomingTransfer::getUtcTimestamp>(); }
TransferPriority getPriority() const { return safeget<TransferPriority, &IncomingTransfer::getPriority>(); }
TransferType getTransferType() const { return safeget<TransferType, &IncomingTransfer::getTransferType>(); }
TransferID getTransferID() const { return safeget<TransferID, &IncomingTransfer::getTransferID>(); }
NodeID getSrcNodeID() const { return safeget<NodeID, &IncomingTransfer::getSrcNodeID>(); }
uint8_t getIfaceIndex() const { return safeget<uint8_t, &IncomingTransfer::getIfaceIndex>(); }
bool isAnonymousTransfer() const { return safeget<bool, &IncomingTransfer::isAnonymousTransfer>(); }
};
/**
* This operator neatly prints the data structure prepended with extra data from the transport layer.
* The extra data will be represented as YAML comment.
*/
template <typename Stream, typename DataType>
static Stream& operator<<(Stream& s, const ReceivedDataStructure<DataType>& rds)
{
s << "# Received struct ts_m=" << rds.getMonotonicTimestamp()
<< " ts_utc=" << rds.getUtcTimestamp()
<< " snid=" << int(rds.getSrcNodeID().get()) << "\n";
s << static_cast<const DataType&>(rds);
return s;
}
class GenericSubscriberBase : Noncopyable
{
protected:
INode& node_;
uint32_t failure_count_;
explicit GenericSubscriberBase(INode& node)
: node_(node)
, failure_count_(0)
{ }
~GenericSubscriberBase() { }
int genericStart(TransferListener* listener, bool (Dispatcher::*registration_method)(TransferListener*));
void stop(TransferListener* listener);
public:
/**
* Returns the number of failed attempts to decode received message. Generally, a failed attempt means either:
* - Transient failure in the transport layer.
* - Incompatible data types.
*/
uint32_t getFailureCount() const { return failure_count_; }
INode& getNode() const { return node_; }
};
/**
* Please note that the reference passed to the RX callback points to a stack-allocated object, which means
* that it gets invalidated shortly after the callback returns.
*/
template <typename DataSpec, typename DataStruct, typename TransferListenerType>
class UAVCAN_EXPORT GenericSubscriber : public GenericSubscriberBase
{
typedef GenericSubscriber<DataSpec, DataStruct, TransferListenerType> SelfType;
// We need to break the inheritance chain here to implement lazy initialization
class TransferForwarder : public TransferListenerType
{
SelfType& obj_;
void handleIncomingTransfer(IncomingTransfer& transfer) override
{
obj_.handleIncomingTransfer(transfer);
}
public:
TransferForwarder(SelfType& obj,
const DataTypeDescriptor& data_type,
uint16_t max_buffer_size,
IPoolAllocator& allocator) :
TransferListenerType(obj.node_.getDispatcher().getTransferPerfCounter(),
data_type,
max_buffer_size,
allocator),
obj_(obj)
{ }
};
LazyConstructor<TransferForwarder> forwarder_;
int checkInit();
void handleIncomingTransfer(IncomingTransfer& transfer);
int genericStart(bool (Dispatcher::*registration_method)(TransferListener*));
protected:
struct ReceivedDataStructureSpec : public ReceivedDataStructure<DataStruct>
{
ReceivedDataStructureSpec() { }
ReceivedDataStructureSpec(const IncomingTransfer* arg_transfer) :
ReceivedDataStructure<DataStruct>(arg_transfer)
{ }
};
explicit GenericSubscriber(INode& node) : GenericSubscriberBase(node)
{ }
virtual ~GenericSubscriber() { stop(); }
virtual void handleReceivedDataStruct(ReceivedDataStructure<DataStruct>&) = 0;
int startAsMessageListener()
{
UAVCAN_TRACE("GenericSubscriber", "Start as message listener; dtname=%s", DataSpec::getDataTypeFullName());
return genericStart(&Dispatcher::registerMessageListener);
}
int startAsServiceRequestListener()
{
UAVCAN_TRACE("GenericSubscriber", "Start as service request listener; dtname=%s",
DataSpec::getDataTypeFullName());
return genericStart(&Dispatcher::registerServiceRequestListener);
}
int startAsServiceResponseListener()
{
UAVCAN_TRACE("GenericSubscriber", "Start as service response listener; dtname=%s",
DataSpec::getDataTypeFullName());
return genericStart(&Dispatcher::registerServiceResponseListener);
}
/**
* By default, anonymous transfers will be ignored.
* This option allows to enable reception of anonymous transfers.
*/
void allowAnonymousTransfers()
{
forwarder_->allowAnonymousTransfers();
}
/**
* Terminate the subscription.
* Dispatcher core will remove this instance from the subscribers list.
*/
void stop()
{
UAVCAN_TRACE("GenericSubscriber", "Stop; dtname=%s", DataSpec::getDataTypeFullName());
GenericSubscriberBase::stop(forwarder_);
}
TransferListenerType* getTransferListener() { return forwarder_; }
};
// ----------------------------------------------------------------------------
/*
* GenericSubscriber
*/
template <typename DataSpec, typename DataStruct, typename TransferListenerType>
int GenericSubscriber<DataSpec, DataStruct, TransferListenerType>::checkInit()
{
if (forwarder_)
{
return 0;
}
GlobalDataTypeRegistry::instance().freeze();
const DataTypeDescriptor* const descr =
GlobalDataTypeRegistry::instance().find(DataTypeKind(DataSpec::DataTypeKind), DataSpec::getDataTypeFullName());
if (descr == UAVCAN_NULLPTR)
{
UAVCAN_TRACE("GenericSubscriber", "Type [%s] is not registered", DataSpec::getDataTypeFullName());
return -ErrUnknownDataType;
}
static const uint16_t MaxBufferSize = BitLenToByteLen<DataStruct::MaxBitLen>::Result;
forwarder_.template construct<SelfType&, const DataTypeDescriptor&, uint16_t, IPoolAllocator&>
(*this, *descr, MaxBufferSize, node_.getAllocator());
return 0;
}
template <typename DataSpec, typename DataStruct, typename TransferListenerType>
void GenericSubscriber<DataSpec, DataStruct, TransferListenerType>::handleIncomingTransfer(IncomingTransfer& transfer)
{
ReceivedDataStructureSpec rx_struct(&transfer);
/*
* Decoding into the temporary storage
*/
BitStream bitstream(transfer);
ScalarCodec codec(bitstream);
const int decode_res = DataStruct::decode(rx_struct, codec);
// We don't need the data anymore, the memory can be reused from the callback:
transfer.release();
if (decode_res <= 0)
{
UAVCAN_TRACE("GenericSubscriber", "Unable to decode the message [%i] [%s]",
decode_res, DataSpec::getDataTypeFullName());
failure_count_++;
node_.getDispatcher().getTransferPerfCounter().addError();
return;
}
/*
* Invoking the callback
*/
handleReceivedDataStruct(rx_struct);
}
template <typename DataSpec, typename DataStruct, typename TransferListenerType>
int GenericSubscriber<DataSpec, DataStruct, TransferListenerType>::
genericStart(bool (Dispatcher::*registration_method)(TransferListener*))
{
const int res = checkInit();
if (res < 0)
{
UAVCAN_TRACE("GenericSubscriber", "Initialization failure [%s]", DataSpec::getDataTypeFullName());
return res;
}
return GenericSubscriberBase::genericStart(forwarder_, registration_method);
}
}
#endif // UAVCAN_NODE_GENERIC_SUBSCRIBER_HPP_INCLUDED
@@ -0,0 +1,241 @@
/*
* Copyright (C) 2014 Pavel Kirienko <pavel.kirienko@gmail.com>
*/
#ifndef UAVCAN_NODE_GLOBAL_DATA_TYPE_REGISTRY_HPP_INCLUDED
#define UAVCAN_NODE_GLOBAL_DATA_TYPE_REGISTRY_HPP_INCLUDED
#include <cassert>
#include <uavcan/error.hpp>
#include <uavcan/std.hpp>
#include <uavcan/data_type.hpp>
#include <uavcan/util/templates.hpp>
#include <uavcan/util/linked_list.hpp>
#if UAVCAN_DEBUG
# include <uavcan/debug.hpp>
#endif
namespace uavcan
{
/**
* This singleton is shared among all existing node instances. It is instantiated automatically
* when the C++ runtime executes contstructors before main().
*
* Its purpose is to keep the list of all UAVCAN data types known and used by this application.
*
* Also, the mapping between Data Type name and its Data Type ID is also stored in this singleton.
* UAVCAN data types with default Data Type ID that are autogenerated by the libuavcan DSDL compiler
* are registered automatically before main() (refer to the generated headers to see how exactly).
* Data types that don't have a default Data Type ID must be registered manually using the methods
* of this class (read the method documentation).
*
* Attempt to use a data type that was not registered with this singleton (e.g. publish, subscribe,
* perform a service call etc.) will fail with an error code @ref ErrUnknownDataType.
*/
class UAVCAN_EXPORT GlobalDataTypeRegistry : Noncopyable
{
struct Entry : public LinkedListNode<Entry>
{
DataTypeDescriptor descriptor;
Entry() { }
Entry(DataTypeKind kind, DataTypeID id, const DataTypeSignature& signature, const char* name)
: descriptor(kind, id, signature, name)
{ }
};
struct EntryInsertionComparator
{
const DataTypeID id;
explicit EntryInsertionComparator(const Entry* dtd)
: id((dtd == UAVCAN_NULLPTR) ? DataTypeID() : dtd->descriptor.getID())
{
UAVCAN_ASSERT(dtd != UAVCAN_NULLPTR);
}
bool operator()(const Entry* entry) const
{
UAVCAN_ASSERT(entry != UAVCAN_NULLPTR);
return entry->descriptor.getID() > id;
}
};
public:
/**
* Result of data type registration
*/
enum RegistrationResult
{
RegistrationResultOk, ///< Success, data type is now registered and can be used.
RegistrationResultCollision, ///< Data type name or ID is not unique.
RegistrationResultInvalidParams, ///< Invalid input parameters.
RegistrationResultFrozen ///< Data Type Registery has been frozen and can't be modified anymore.
};
private:
typedef LinkedListRoot<Entry> List;
mutable List msgs_;
mutable List srvs_;
bool frozen_;
GlobalDataTypeRegistry() : frozen_(false) { }
List* selectList(DataTypeKind kind) const;
RegistrationResult remove(Entry* dtd);
RegistrationResult registImpl(Entry* dtd);
public:
/**
* Returns the reference to the singleton.
*/
static GlobalDataTypeRegistry& instance();
/**
* Register a data type 'Type' with ID 'id'.
* If this data type was registered earlier, its old registration will be overridden.
* This method will fail if the data type registry is frozen.
*
* @tparam Type Autogenerated UAVCAN data type to register. Data types are generated by the
* libuavcan DSDL compiler from DSDL definitions.
*
* @param id Data Type ID for this data type.
*/
template <typename Type>
RegistrationResult registerDataType(DataTypeID id);
/**
* Data Type registry needs to be frozen before a node instance can use it in
* order to prevent accidental change in data type configuration on a running
* node.
*
* This method will be called automatically by the node during start up, so
* the user does not need to call it from the application manually. Subsequent
* calls will not have any effect.
*
* Once frozen, data type registry can't be unfrozen.
*/
void freeze();
bool isFrozen() const { return frozen_; }
/**
* Finds data type descriptor by full data type name, e.g. "uavcan.protocol.NodeStatus".
* Messages are searched first, then services.
* Returns null pointer if the data type with this name is not registered.
* @param name Full data type name
* @return Descriptor for this data type or null pointer if not found
*/
const DataTypeDescriptor* find(const char* name) const;
/**
* Finds data type descriptor by full data type name, e.g. "uavcan.protocol.NodeStatus", and data type kind.
* Returns null pointer if the data type with this name is not registered.
* @param kind Data Type Kind - message or service
* @param name Full data type name
* @return Descriptor for this data type or null pointer if not found
*/
const DataTypeDescriptor* find(DataTypeKind kind, const char* name) const;
/**
* Finds data type descriptor by data type ID.
* Returns null pointer if the data type with this ID is not registered.
* @param kind Data Type Kind - message or service
* @param dtid Data Type ID
* @return Descriptor for this data type or null pointer if not found
*/
const DataTypeDescriptor* find(DataTypeKind kind, DataTypeID dtid) const;
/**
* Returns the number of registered message types.
*/
unsigned getNumMessageTypes() const { return msgs_.getLength(); }
/**
* Returns the number of registered service types.
*/
unsigned getNumServiceTypes() const { return srvs_.getLength(); }
#if UAVCAN_DEBUG
/// Required for unit testing
void reset()
{
UAVCAN_TRACE("GlobalDataTypeRegistry", "Reset; was frozen: %i, num msgs: %u, num srvs: %u",
int(frozen_), getNumMessageTypes(), getNumServiceTypes());
frozen_ = false;
while (msgs_.get())
{
msgs_.remove(msgs_.get());
}
while (srvs_.get())
{
srvs_.remove(srvs_.get());
}
}
#endif
};
/**
* This class is used by autogenerated data types to register with the
* data type registry automatically before main() is called. Note that
* if a generated data type header file is not included by any translation
* unit of the application, the data type will not be registered.
*
* Data type needs to have a default ID to be registrable by this class.
*/
template <typename Type>
struct UAVCAN_EXPORT DefaultDataTypeRegistrator
{
DefaultDataTypeRegistrator()
{
#if !UAVCAN_NO_GLOBAL_DATA_TYPE_REGISTRY
const GlobalDataTypeRegistry::RegistrationResult res =
GlobalDataTypeRegistry::instance().registerDataType<Type>(Type::DefaultDataTypeID);
if (res != GlobalDataTypeRegistry::RegistrationResultOk)
{
handleFatalError("Type reg failed");
}
#endif
}
};
// ----------------------------------------------------------------------------
/*
* GlobalDataTypeRegistry
*/
template <typename Type>
GlobalDataTypeRegistry::RegistrationResult GlobalDataTypeRegistry::registerDataType(DataTypeID id)
{
if (isFrozen())
{
return RegistrationResultFrozen;
}
static Entry entry;
{
const RegistrationResult remove_res = remove(&entry);
if (remove_res != RegistrationResultOk)
{
return remove_res;
}
}
// We can't just overwrite the entry itself because it's noncopyable
entry.descriptor = DataTypeDescriptor(DataTypeKind(Type::DataTypeKind), id,
Type::getDataTypeSignature(), Type::getDataTypeFullName());
{
const RegistrationResult remove_res = remove(&entry);
if (remove_res != RegistrationResultOk)
{
return remove_res;
}
}
return registImpl(&entry);
}
}
#endif // UAVCAN_NODE_GLOBAL_DATA_TYPE_REGISTRY_HPP_INCLUDED
@@ -0,0 +1,319 @@
/*
* Copyright (C) 2014 Pavel Kirienko <pavel.kirienko@gmail.com>
*/
#ifndef UAVCAN_NODE_NODE_HPP_INCLUDED
#define UAVCAN_NODE_NODE_HPP_INCLUDED
#include <cassert>
#include <uavcan/error.hpp>
#include <uavcan/build_config.hpp>
#include <uavcan/node/abstract_node.hpp>
// High-level functionality available by default
#include <uavcan/protocol/node_status_provider.hpp>
#if !UAVCAN_TINY
# include <uavcan/protocol/data_type_info_provider.hpp>
# include <uavcan/protocol/logger.hpp>
# include <uavcan/protocol/restart_request_server.hpp>
# include <uavcan/protocol/transport_stats_provider.hpp>
#endif
#if !defined(UAVCAN_CPP_VERSION) || !defined(UAVCAN_CPP11)
# error UAVCAN_CPP_VERSION
#endif
namespace uavcan
{
/**
* This is the top-level node API.
* A custom node class can be implemented if needed, in which case it shall inherit INode.
*
* @tparam MemPoolSize Size of memory pool for this node, in bytes.
* Please refer to the documentation for details.
* If this value is zero, the constructor will accept a reference to user-provided allocator.
*/
template <std::size_t MemPoolSize = 0>
class UAVCAN_EXPORT Node : public INode
{
typedef typename
Select<(MemPoolSize > 0),
PoolAllocator<MemPoolSize, MemPoolBlockSize>, // If pool size is specified, use default allocator
IPoolAllocator& // Otherwise use reference to user-provided allocator
>::Result Allocator;
Allocator pool_allocator_;
Scheduler scheduler_;
NodeStatusProvider proto_nsp_;
#if !UAVCAN_TINY
DataTypeInfoProvider proto_dtp_;
Logger proto_logger_;
RestartRequestServer proto_rrs_;
TransportStatsProvider proto_tsp_;
#endif
uint64_t internal_failure_cnt_;
bool started_;
void commonInit()
{
internal_failure_cnt_ = 0;
started_ = false;
}
protected:
virtual void registerInternalFailure(const char* msg) override
{
internal_failure_cnt_++;
UAVCAN_TRACE("Node", "Internal failure: %s", msg);
#if UAVCAN_TINY
(void)msg;
#else
(void)getLogger().log(protocol::debug::LogLevel::ERROR, "UAVCAN", msg);
#endif
}
public:
/**
* This overload is only valid if MemPoolSize > 0.
*/
Node(ICanDriver& can_driver,
ISystemClock& system_clock) :
scheduler_(can_driver, pool_allocator_, system_clock),
proto_nsp_(*this)
#if !UAVCAN_TINY
, proto_dtp_(*this)
, proto_logger_(*this)
, proto_rrs_(*this)
, proto_tsp_(*this)
#endif
{
commonInit();
}
/**
* This overload is only valid if MemPoolSize == 0.
*/
Node(ICanDriver& can_driver,
ISystemClock& system_clock,
IPoolAllocator& allocator) :
pool_allocator_(allocator),
scheduler_(can_driver, pool_allocator_, system_clock),
proto_nsp_(*this)
#if !UAVCAN_TINY
, proto_dtp_(*this)
, proto_logger_(*this)
, proto_rrs_(*this)
, proto_tsp_(*this)
#endif
{
commonInit();
}
virtual typename RemoveReference<Allocator>::Type& getAllocator() override { return pool_allocator_; }
virtual Scheduler& getScheduler() override { return scheduler_; }
virtual const Scheduler& getScheduler() const override { return scheduler_; }
int spin(MonotonicTime deadline)
{
if (started_)
{
return INode::spin(deadline);
}
return -ErrNotInited;
}
int spin(MonotonicDuration duration)
{
if (started_)
{
return INode::spin(duration);
}
return -ErrNotInited;
}
int spinOnce()
{
if (started_)
{
return INode::spinOnce();
}
return -ErrNotInited;
}
bool isStarted() const { return started_; }
uint64_t getInternalFailureCount() const { return internal_failure_cnt_; }
/**
* Starts the node and publishes uavcan.protocol.NodeStatus immediately.
* Does not so anything if the node is already started.
* Once started, the node can't stop.
* If the node failed to start up, it's recommended to destroy the current node instance and start over.
* Returns negative error code.
* @param node_status_transfer_priority Transfer priority that will be used for outgoing NodeStatus messages.
* Normal priority is used by default.
*/
int start(const TransferPriority node_status_transfer_priority = TransferPriority::Default);
/**
* Gets/sets the node name, e.g. "com.example.product_name". The node name can be set only once.
* The name must be set before the node is started, otherwise the node will refuse to start up.
*/
const NodeStatusProvider::NodeName& getName() const { return proto_nsp_.getName(); }
void setName(const NodeStatusProvider::NodeName& name) { proto_nsp_.setName(name); }
/**
* Node health code helpers.
*/
void setHealthOk() { proto_nsp_.setHealthOk(); }
void setHealthWarning() { proto_nsp_.setHealthWarning(); }
void setHealthError() { proto_nsp_.setHealthError(); }
void setHealthCritical() { proto_nsp_.setHealthCritical(); }
/**
* Node mode code helpers.
* Note that INITIALIZATION is the default mode; the application has to manually set it to OPERATIONAL.
*/
void setModeOperational() { proto_nsp_.setModeOperational(); }
void setModeInitialization() { proto_nsp_.setModeInitialization(); }
void setModeMaintenance() { proto_nsp_.setModeMaintenance(); }
void setModeSoftwareUpdate() { proto_nsp_.setModeSoftwareUpdate(); }
void setModeOfflineAndPublish()
{
proto_nsp_.setModeOffline();
(void)proto_nsp_.forcePublish();
}
/**
* Updates the vendor-specific status code.
*/
void setVendorSpecificStatusCode(NodeStatusProvider::VendorSpecificStatusCode code)
{
proto_nsp_.setVendorSpecificStatusCode(code);
}
/**
* Gets/sets the node version information.
*/
void setSoftwareVersion(const protocol::SoftwareVersion& version) { proto_nsp_.setSoftwareVersion(version); }
void setHardwareVersion(const protocol::HardwareVersion& version) { proto_nsp_.setHardwareVersion(version); }
const protocol::SoftwareVersion& getSoftwareVersion() const { return proto_nsp_.getSoftwareVersion(); }
const protocol::HardwareVersion& getHardwareVersion() const { return proto_nsp_.getHardwareVersion(); }
NodeStatusProvider& getNodeStatusProvider() { return proto_nsp_; }
#if !UAVCAN_TINY
/**
* Restart handler can be installed to handle external node restart requests (highly recommended).
*/
void setRestartRequestHandler(IRestartRequestHandler* handler) { proto_rrs_.setHandler(handler); }
RestartRequestServer& getRestartRequestServer() { return proto_rrs_; }
/**
* Node logging.
* Logging calls are passed directly into the @ref Logger instance.
* Type safe log formatting is supported only in C++11 mode.
* @{
*/
#if UAVCAN_CPP_VERSION >= UAVCAN_CPP11
template <typename... Args>
inline void logDebug(const char* source, const char* format, Args... args)
{
(void)proto_logger_.logDebug(source, format, args...);
}
template <typename... Args>
inline void logInfo(const char* source, const char* format, Args... args)
{
(void)proto_logger_.logInfo(source, format, args...);
}
template <typename... Args>
inline void logWarning(const char* source, const char* format, Args... args)
{
(void)proto_logger_.logWarning(source, format, args...);
}
template <typename... Args>
inline void logError(const char* source, const char* format, Args... args)
{
(void)proto_logger_.logError(source, format, args...);
}
#else
void logDebug(const char* source, const char* text) { (void)proto_logger_.logDebug(source, text); }
void logInfo(const char* source, const char* text) { (void)proto_logger_.logInfo(source, text); }
void logWarning(const char* source, const char* text) { (void)proto_logger_.logWarning(source, text); }
void logError(const char* source, const char* text) { (void)proto_logger_.logError(source, text); }
#endif
/**
* @}
*/
/**
* Use this method to configure logging.
*/
Logger& getLogger() { return proto_logger_; }
#endif // UAVCAN_TINY
};
// ----------------------------------------------------------------------------
template <std::size_t MemPoolSize_>
int Node<MemPoolSize_>::start(const TransferPriority priority)
{
if (started_)
{
return 0;
}
GlobalDataTypeRegistry::instance().freeze();
int res = 0;
res = proto_nsp_.startAndPublish(priority);
if (res < 0)
{
goto fail;
}
#if !UAVCAN_TINY
res = proto_dtp_.start();
if (res < 0)
{
goto fail;
}
res = proto_logger_.init();
if (res < 0)
{
goto fail;
}
res = proto_rrs_.start();
if (res < 0)
{
goto fail;
}
res = proto_tsp_.start();
if (res < 0)
{
goto fail;
}
#endif
started_ = res >= 0;
return res;
fail:
UAVCAN_ASSERT(res < 0);
return res;
}
}
#endif // UAVCAN_NODE_NODE_HPP_INCLUDED
@@ -0,0 +1,89 @@
/*
* Copyright (C) 2014 Pavel Kirienko <pavel.kirienko@gmail.com>
*/
#ifndef UAVCAN_NODE_PUBLISHER_HPP_INCLUDED
#define UAVCAN_NODE_PUBLISHER_HPP_INCLUDED
#include <uavcan/node/generic_publisher.hpp>
namespace uavcan
{
/**
* Use this class to publish messages to the bus (broadcast, unicast, or both).
*
* @tparam DataType_ Message data type
*/
template <typename DataType_>
class UAVCAN_EXPORT Publisher : protected GenericPublisher<DataType_, DataType_>
{
typedef GenericPublisher<DataType_, DataType_> BaseType;
public:
typedef DataType_ DataType; ///< Message data type
/**
* @param node Node instance this publisher will be registered with.
*
* @param tx_timeout If CAN frames of this message are not delivered to the bus
* in this amount of time, they will be discarded. Default value
* is good enough for rather high-frequency, high-priority messages.
*
* @param max_transfer_interval Maximum expected transfer interval. It's absolutely safe
* to leave default value here. It just defines how soon the
* Transfer ID tracking objects associated with this message type
* will be garbage collected by the library if it's no longer
* being published.
*/
explicit Publisher(INode& node, MonotonicDuration tx_timeout = getDefaultTxTimeout(),
MonotonicDuration max_transfer_interval = TransferSender::getDefaultMaxTransferInterval())
: BaseType(node, tx_timeout, max_transfer_interval)
{
#if UAVCAN_DEBUG
UAVCAN_ASSERT(getTxTimeout() == tx_timeout); // Making sure default values are OK
#endif
StaticAssert<DataTypeKind(DataType::DataTypeKind) == DataTypeKindMessage>::check();
}
/**
* Broadcast the message.
* Returns negative error code.
*/
int broadcast(const DataType& message)
{
return BaseType::publish(message, TransferTypeMessageBroadcast, NodeID::Broadcast);
}
/**
* Warning: You probably don't want to use this method; it's for advanced use cases like
* e.g. network time synchronization. Use the overloaded method with fewer arguments instead.
* This overload allows to explicitly specify Transfer ID.
* Returns negative error code.
*/
int broadcast(const DataType& message, TransferID tid)
{
return BaseType::publish(message, TransferTypeMessageBroadcast, NodeID::Broadcast, tid);
}
static MonotonicDuration getDefaultTxTimeout() { return MonotonicDuration::fromMSec(100); }
/**
* Init method can be called prior first publication, but it's not necessary
* because the publisher can be automatically initialized ad-hoc.
*/
using BaseType::init;
using BaseType::allowAnonymousTransfers;
using BaseType::getTransferSender;
using BaseType::getMinTxTimeout;
using BaseType::getMaxTxTimeout;
using BaseType::getTxTimeout;
using BaseType::setTxTimeout;
using BaseType::getPriority;
using BaseType::setPriority;
using BaseType::getNode;
};
}
#endif // UAVCAN_NODE_PUBLISHER_HPP_INCLUDED
@@ -0,0 +1,155 @@
/*
* Copyright (C) 2014 Pavel Kirienko <pavel.kirienko@gmail.com>
*/
#ifndef UAVCAN_NODE_SCHEDULER_HPP_INCLUDED
#define UAVCAN_NODE_SCHEDULER_HPP_INCLUDED
#include <uavcan/error.hpp>
#include <uavcan/util/linked_list.hpp>
#include <uavcan/transport/dispatcher.hpp>
namespace uavcan
{
class UAVCAN_EXPORT Scheduler;
class UAVCAN_EXPORT DeadlineHandler : public LinkedListNode<DeadlineHandler>
{
MonotonicTime deadline_;
protected:
Scheduler& scheduler_;
explicit DeadlineHandler(Scheduler& scheduler)
: scheduler_(scheduler)
{ }
virtual ~DeadlineHandler() { stop(); }
public:
virtual void handleDeadline(MonotonicTime current) = 0;
void startWithDeadline(MonotonicTime deadline);
void startWithDelay(MonotonicDuration delay);
void generateDeadlineImmediately() { startWithDeadline(MonotonicTime::fromUSec(1)); }
void stop();
bool isRunning() const;
MonotonicTime getDeadline() const { return deadline_; }
Scheduler& getScheduler() const { return scheduler_; }
};
class UAVCAN_EXPORT DeadlineScheduler : Noncopyable
{
LinkedListRoot<DeadlineHandler> handlers_; // Ordered by deadline, lowest first
public:
void add(DeadlineHandler* mdh);
void remove(DeadlineHandler* mdh);
bool doesExist(const DeadlineHandler* mdh) const;
unsigned getNumHandlers() const { return handlers_.getLength(); }
MonotonicTime pollAndGetMonotonicTime(ISystemClock& sysclock);
MonotonicTime getEarliestDeadline() const;
};
/**
* This class distributes processing time between library components (IO handling, deadline callbacks, ...).
*/
class UAVCAN_EXPORT Scheduler : Noncopyable
{
enum { DefaultDeadlineResolutionMs = 5 };
enum { MinDeadlineResolutionMs = 1 };
enum { MaxDeadlineResolutionMs = 100 };
enum { DefaultCleanupPeriodMs = 1000 };
enum { MinCleanupPeriodMs = 10 };
enum { MaxCleanupPeriodMs = 10000 };
DeadlineScheduler deadline_scheduler_;
Dispatcher dispatcher_;
MonotonicTime prev_cleanup_ts_;
MonotonicDuration deadline_resolution_;
MonotonicDuration cleanup_period_;
bool inside_spin_;
struct InsideSpinSetter
{
Scheduler& owner;
InsideSpinSetter(Scheduler& o)
: owner(o)
{
owner.inside_spin_ = true;
}
~InsideSpinSetter() { owner.inside_spin_ = false; }
};
MonotonicTime computeDispatcherSpinDeadline(MonotonicTime spin_deadline) const;
void pollCleanup(MonotonicTime mono_ts, uint32_t num_frames_processed_with_last_spin);
public:
Scheduler(ICanDriver& can_driver, IPoolAllocator& allocator, ISystemClock& sysclock)
: dispatcher_(can_driver, allocator, sysclock)
, prev_cleanup_ts_(sysclock.getMonotonic())
, deadline_resolution_(MonotonicDuration::fromMSec(DefaultDeadlineResolutionMs))
, cleanup_period_(MonotonicDuration::fromMSec(DefaultCleanupPeriodMs))
, inside_spin_(false)
{ }
/**
* Spin until the deadline, or until some error occurs.
* This function will return strictly when the deadline is reached, even if there are unprocessed frames.
* Returns negative error code.
*/
int spin(MonotonicTime deadline);
/**
* Non-blocking version of @ref spin() - spins until all pending frames and events are processed,
* or until some error occurs. If there's nothing to do, returns immediately.
* Returns negative error code.
*/
int spinOnce();
DeadlineScheduler& getDeadlineScheduler() { return deadline_scheduler_; }
Dispatcher& getDispatcher() { return dispatcher_; }
const Dispatcher& getDispatcher() const { return dispatcher_; }
ISystemClock& getSystemClock() { return dispatcher_.getSystemClock(); }
MonotonicTime getMonotonicTime() const { return dispatcher_.getSystemClock().getMonotonic(); }
UtcTime getUtcTime() const { return dispatcher_.getSystemClock().getUtc(); }
/**
* Worst case deadline callback resolution.
* Higher resolution increases CPU usage.
*/
MonotonicDuration getDeadlineResolution() const { return deadline_resolution_; }
void setDeadlineResolution(MonotonicDuration res)
{
res = min(res, MonotonicDuration::fromMSec(MaxDeadlineResolutionMs));
res = max(res, MonotonicDuration::fromMSec(MinDeadlineResolutionMs));
deadline_resolution_ = res;
}
/**
* How often the scheduler will run cleanup (listeners, outgoing transfer registry, ...).
* Cleanup execution time grows linearly with number of listeners and number of items
* in the Outgoing Transfer ID registry.
* Lower period increases CPU usage.
*/
MonotonicDuration getCleanupPeriod() const { return cleanup_period_; }
void setCleanupPeriod(MonotonicDuration period)
{
period = min(period, MonotonicDuration::fromMSec(MaxCleanupPeriodMs));
period = max(period, MonotonicDuration::fromMSec(MinCleanupPeriodMs));
cleanup_period_ = period;
}
};
}
#endif // UAVCAN_NODE_SCHEDULER_HPP_INCLUDED
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,201 @@
/*
* Copyright (C) 2014 Pavel Kirienko <pavel.kirienko@gmail.com>
*/
#ifndef UAVCAN_NODE_SERVICE_SERVER_HPP_INCLUDED
#define UAVCAN_NODE_SERVICE_SERVER_HPP_INCLUDED
#include <uavcan/build_config.hpp>
#include <uavcan/node/generic_publisher.hpp>
#include <uavcan/node/generic_subscriber.hpp>
#if !defined(UAVCAN_CPP_VERSION) || !defined(UAVCAN_CPP11)
# error UAVCAN_CPP_VERSION
#endif
#if UAVCAN_CPP_VERSION >= UAVCAN_CPP11
# include <functional>
#endif
namespace uavcan
{
/**
* This type can be used in place of the response type in a service server callback to get more advanced control
* of service request processing.
*
* PLEASE NOTE that since this class inherits the response type, service server callbacks can accept either
* object of this class or the response type directly if the extra options are not needed.
*
* For example, both of these callbacks can be used with the same service type 'Foo':
*
* void first(const ReceivedDataStructure<Foo::Request>& request,
* ServiceResponseDataStructure<Foo::Response>& response);
*
* void second(const Foo::Request& request,
* Foo::Response& response);
*
* In the latter case, an implicit cast will happen before the callback is invoked.
*/
template <typename ResponseDataType_>
class ServiceResponseDataStructure : public ResponseDataType_
{
// Fields are weirdly named to avoid name clashing with the inherited data type
bool _enabled_;
public:
typedef ResponseDataType_ ResponseDataType;
ServiceResponseDataStructure()
: _enabled_(true)
{ }
/**
* When disabled, the server will not transmit the response transfer.
* By default it is enabled, i.e. response will be sent.
*/
void setResponseEnabled(bool x) { _enabled_ = x; }
/**
* Whether the response will be sent. By default it will.
*/
bool isResponseEnabled() const { return _enabled_; }
};
/**
* Use this class to implement UAVCAN service servers.
*
* Note that the references passed to the callback may point to stack-allocated objects, which means that the
* references get invalidated once the callback returns.
*
* @tparam DataType_ Service data type.
*
* @tparam Callback_ Service calls will be delivered through the callback of this type, and service
* response will be returned via the output parameter of the callback. Note that
* the reference to service response data struct passed to the callback always points
* to a default initialized response object.
* Please also refer to @ref ReceivedDataStructure<> and @ref ServiceResponseDataStructure<>.
* In C++11 mode this type defaults to std::function<>.
* In C++03 mode this type defaults to a plain function pointer; use binder to
* call member functions as callbacks.
*/
template <typename DataType_,
#if UAVCAN_CPP_VERSION >= UAVCAN_CPP11
typename Callback_ = std::function<void (const ReceivedDataStructure<typename DataType_::Request>&,
ServiceResponseDataStructure<typename DataType_::Response>&)>
#else
typename Callback_ = void (*)(const ReceivedDataStructure<typename DataType_::Request>&,
ServiceResponseDataStructure<typename DataType_::Response>&)
#endif
>
class UAVCAN_EXPORT ServiceServer
: public GenericSubscriber<DataType_, typename DataType_::Request, TransferListener>
{
public:
typedef DataType_ DataType;
typedef typename DataType::Request RequestType;
typedef typename DataType::Response ResponseType;
typedef Callback_ Callback;
private:
typedef GenericSubscriber<DataType, RequestType, TransferListener> SubscriberType;
typedef GenericPublisher<DataType, ResponseType> PublisherType;
PublisherType publisher_;
Callback callback_;
uint32_t response_failure_count_;
virtual void handleReceivedDataStruct(ReceivedDataStructure<RequestType>& request) override
{
UAVCAN_ASSERT(request.getTransferType() == TransferTypeServiceRequest);
ServiceResponseDataStructure<ResponseType> response;
if (coerceOrFallback<bool>(callback_, true))
{
UAVCAN_ASSERT(response.isResponseEnabled()); // Enabled by default
callback_(request, response);
}
else
{
handleFatalError("Srv serv clbk");
}
if (response.isResponseEnabled())
{
publisher_.setPriority(request.getPriority()); // Responding at the same priority.
const int res = publisher_.publish(response, TransferTypeServiceResponse, request.getSrcNodeID(),
request.getTransferID());
if (res < 0)
{
UAVCAN_TRACE("ServiceServer", "Response publication failure: %i", res);
publisher_.getNode().getDispatcher().getTransferPerfCounter().addError();
response_failure_count_++;
}
}
else
{
UAVCAN_TRACE("ServiceServer", "Response was suppressed by the application");
}
}
public:
explicit ServiceServer(INode& node)
: SubscriberType(node)
, publisher_(node, getDefaultTxTimeout())
, callback_()
, response_failure_count_(0)
{
UAVCAN_ASSERT(getTxTimeout() == getDefaultTxTimeout()); // Making sure it is valid
StaticAssert<DataTypeKind(DataType::DataTypeKind) == DataTypeKindService>::check();
}
/**
* Starts the server.
* Incoming service requests will be passed to the application via the callback.
*/
int start(const Callback& callback)
{
stop();
if (!coerceOrFallback<bool>(callback, true))
{
UAVCAN_TRACE("ServiceServer", "Invalid callback");
return -ErrInvalidParam;
}
callback_ = callback;
const int publisher_res = publisher_.init();
if (publisher_res < 0)
{
UAVCAN_TRACE("ServiceServer", "Publisher initialization failure: %i", publisher_res);
return publisher_res;
}
return SubscriberType::startAsServiceRequestListener();
}
/**
* Stops the server.
*/
using SubscriberType::stop;
static MonotonicDuration getDefaultTxTimeout() { return MonotonicDuration::fromMSec(1000); }
static MonotonicDuration getMinTxTimeout() { return PublisherType::getMinTxTimeout(); }
static MonotonicDuration getMaxTxTimeout() { return PublisherType::getMaxTxTimeout(); }
MonotonicDuration getTxTimeout() const { return publisher_.getTxTimeout(); }
void setTxTimeout(MonotonicDuration tx_timeout) { publisher_.setTxTimeout(tx_timeout); }
/**
* Returns the number of failed attempts to decode data structs. Generally, a failed attempt means either:
* - Transient failure in the transport layer.
* - Incompatible data types.
*/
uint32_t getRequestFailureCount() const { return SubscriberType::getFailureCount(); }
uint32_t getResponseFailureCount() const { return response_failure_count_; }
};
}
#endif // UAVCAN_NODE_SERVICE_SERVER_HPP_INCLUDED
@@ -0,0 +1,76 @@
/*
* Copyright (C) 2015 Pavel Kirienko <pavel.kirienko@gmail.com>
*/
#ifndef UAVCAN_SUB_NODE_NODE_HPP_INCLUDED
#define UAVCAN_SUB_NODE_NODE_HPP_INCLUDED
#include <cassert>
#include <uavcan/build_config.hpp>
#include <uavcan/node/abstract_node.hpp>
#if UAVCAN_TINY
# error "This functionality is not available in tiny mode"
#endif
namespace uavcan
{
/**
* This node object can be used in multiprocess UAVCAN nodes.
* Please refer to the @ref Node<> for documentation concerning the template arguments; refer to the tutorials
* to lean how to use libuavcan in multiprocess applications.
*/
template <std::size_t MemPoolSize = 0>
class UAVCAN_EXPORT SubNode : public INode
{
typedef typename
Select<(MemPoolSize > 0),
PoolAllocator<MemPoolSize, MemPoolBlockSize>, // If pool size is specified, use default allocator
IPoolAllocator& // Otherwise use reference to user-provided allocator
>::Result Allocator;
Allocator pool_allocator_;
Scheduler scheduler_;
uint64_t internal_failure_cnt_;
protected:
virtual void registerInternalFailure(const char* msg)
{
internal_failure_cnt_++;
UAVCAN_TRACE("Node", "Internal failure: %s", msg);
(void)msg;
}
public:
/**
* This overload is only valid if MemPoolSize > 0.
*/
SubNode(ICanDriver& can_driver,
ISystemClock& system_clock) :
scheduler_(can_driver, pool_allocator_, system_clock),
internal_failure_cnt_(0)
{ }
/**
* This overload is only valid if MemPoolSize == 0.
*/
SubNode(ICanDriver& can_driver,
ISystemClock& system_clock,
IPoolAllocator& allocator) :
pool_allocator_(allocator),
scheduler_(can_driver, pool_allocator_, system_clock),
internal_failure_cnt_(0)
{ }
virtual typename RemoveReference<Allocator>::Type& getAllocator() { return pool_allocator_; }
virtual Scheduler& getScheduler() { return scheduler_; }
virtual const Scheduler& getScheduler() const { return scheduler_; }
uint64_t getInternalFailureCount() const { return internal_failure_cnt_; }
};
}
#endif // Include guard
@@ -0,0 +1,104 @@
/*
* Copyright (C) 2014 Pavel Kirienko <pavel.kirienko@gmail.com>
*/
#ifndef UAVCAN_NODE_SUBSCRIBER_HPP_INCLUDED
#define UAVCAN_NODE_SUBSCRIBER_HPP_INCLUDED
#include <cassert>
#include <uavcan/build_config.hpp>
#include <uavcan/node/generic_subscriber.hpp>
#if !defined(UAVCAN_CPP_VERSION) || !defined(UAVCAN_CPP11)
# error UAVCAN_CPP_VERSION
#endif
#if UAVCAN_CPP_VERSION >= UAVCAN_CPP11
# include <functional>
#endif
namespace uavcan
{
/**
* Use this class to subscribe to a message.
*
* @tparam DataType_ Message data type.
*
* @tparam Callback_ Type of the callback that will be used to deliver received messages
* into the application. Type of the argument of the callback can be either:
* - DataType_&
* - const DataType_&
* - ReceivedDataStructure<DataType_>&
* - const ReceivedDataStructure<DataType_>&
* For the first two options, @ref ReceivedDataStructure<> will be casted implicitly.
* In C++11 mode this type defaults to std::function<>.
* In C++03 mode this type defaults to a plain function pointer; use binder to
* call member functions as callbacks.
*/
template <typename DataType_,
#if UAVCAN_CPP_VERSION >= UAVCAN_CPP11
typename Callback_ = std::function<void (const ReceivedDataStructure<DataType_>&)>
#else
typename Callback_ = void (*)(const ReceivedDataStructure<DataType_>&)
#endif
>
class UAVCAN_EXPORT Subscriber
: public GenericSubscriber<DataType_, DataType_, TransferListener>
{
public:
typedef Callback_ Callback;
private:
typedef GenericSubscriber<DataType_, DataType_, TransferListener> BaseType;
Callback callback_;
virtual void handleReceivedDataStruct(ReceivedDataStructure<DataType_>& msg) override
{
if (coerceOrFallback<bool>(callback_, true))
{
callback_(msg);
}
else
{
handleFatalError("Sub clbk");
}
}
public:
typedef DataType_ DataType;
explicit Subscriber(INode& node)
: BaseType(node)
, callback_()
{
StaticAssert<DataTypeKind(DataType::DataTypeKind) == DataTypeKindMessage>::check();
}
/**
* Begin receiving messages.
* Each message will be passed to the application via the callback.
* Returns negative error code.
*/
int start(const Callback& callback)
{
stop();
if (!coerceOrFallback<bool>(callback, true))
{
UAVCAN_TRACE("Subscriber", "Invalid callback");
return -ErrInvalidParam;
}
callback_ = callback;
return BaseType::startAsMessageListener();
}
using BaseType::allowAnonymousTransfers;
using BaseType::stop;
using BaseType::getFailureCount;
};
}
#endif // UAVCAN_NODE_SUBSCRIBER_HPP_INCLUDED
@@ -0,0 +1,148 @@
/*
* Copyright (C) 2014 Pavel Kirienko <pavel.kirienko@gmail.com>
*/
#ifndef UAVCAN_NODE_TIMER_HPP_INCLUDED
#define UAVCAN_NODE_TIMER_HPP_INCLUDED
#include <uavcan/std.hpp>
#include <uavcan/error.hpp>
#include <uavcan/build_config.hpp>
#include <uavcan/util/linked_list.hpp>
#include <uavcan/node/scheduler.hpp>
#include <uavcan/node/abstract_node.hpp>
#include <uavcan/util/templates.hpp>
#if !defined(UAVCAN_CPP11) || !defined(UAVCAN_CPP_VERSION)
# error UAVCAN_CPP_VERSION
#endif
#if UAVCAN_CPP_VERSION >= UAVCAN_CPP11
# include <functional>
#endif
namespace uavcan
{
class UAVCAN_EXPORT TimerBase;
/**
* Objects of this type will be supplied into timer callbacks.
*/
struct UAVCAN_EXPORT TimerEvent
{
MonotonicTime scheduled_time; ///< Time when the timer callback was expected to be invoked
MonotonicTime real_time; ///< True time when the timer callback was invoked
TimerEvent(MonotonicTime arg_scheduled_time, MonotonicTime arg_real_time)
: scheduled_time(arg_scheduled_time)
, real_time(arg_real_time)
{ }
};
/**
* Inherit this class if you need a timer callback method in your class.
*/
class UAVCAN_EXPORT TimerBase : private DeadlineHandler
{
MonotonicDuration period_;
virtual void handleDeadline(MonotonicTime current) override;
public:
using DeadlineHandler::stop;
using DeadlineHandler::isRunning;
using DeadlineHandler::getDeadline;
using DeadlineHandler::getScheduler;
explicit TimerBase(INode& node)
: DeadlineHandler(node.getScheduler())
, period_(MonotonicDuration::getInfinite())
{ }
/**
* Various ways to start the timer - periodically or once.
* If it is running already, it will be restarted.
* If the deadline is in the past, the event will fire immediately.
* In periodic mode the timer does not accumulate error over time.
*/
void startOneShotWithDeadline(MonotonicTime deadline);
void startOneShotWithDelay(MonotonicDuration delay);
void startPeriodic(MonotonicDuration period);
/**
* Returns period if the timer is in periodic mode.
* Returns infinite duration if the timer is in one-shot mode or stopped.
*/
MonotonicDuration getPeriod() const { return period_; }
/**
* Implement this method in your class to receive callbacks.
*/
virtual void handleTimerEvent(const TimerEvent& event) = 0;
};
/**
* Wrapper over TimerBase that forwards callbacks into arbitrary handlers, like
* functor objects, member functions or static functions.
*
* Callback must be set before the first event; otherwise the event will generate a fatal error.
*
* Also take a look at @ref MethodBinder<>, which may come useful if C++11 features are not available.
*
* @tparam Callback_ Callback type. Shall accept const reference to TimerEvent as its argument.
*/
template <typename Callback_>
class UAVCAN_EXPORT TimerEventForwarder : public TimerBase
{
public:
typedef Callback_ Callback;
private:
Callback callback_;
virtual void handleTimerEvent(const TimerEvent& event)
{
if (coerceOrFallback<bool>(callback_, true))
{
callback_(event);
}
else
{
handleFatalError("Invalid timer callback");
}
}
public:
explicit TimerEventForwarder(INode& node)
: TimerBase(node)
, callback_()
{ }
TimerEventForwarder(INode& node, const Callback& callback)
: TimerBase(node)
, callback_(callback)
{ }
/**
* Get/set the callback object.
* Callback must be set before the first event happens; otherwise the event will generate a fatal error.
*/
const Callback& getCallback() const { return callback_; }
void setCallback(const Callback& callback) { callback_ = callback; }
};
#if UAVCAN_CPP_VERSION >= UAVCAN_CPP11
/**
* Use this timer in C++11 mode.
* Callback type is std::function<>.
*/
typedef TimerEventForwarder<std::function<void (const TimerEvent& event)> > Timer;
#endif
}
#endif // UAVCAN_NODE_TIMER_HPP_INCLUDED
@@ -0,0 +1,13 @@
High-level protocol logic
=========================
Classes defined in this directory implement some high-level functions of UAVCAN.
Since most applications won't use all of them at the same time, their definitions are provided right in the header
files rather than in source files, in order to not pollute the build outputs with unused code (which is also a
requirement for most safety-critical software).
However, some of the high-level functions that are either always used by the library itself or those that are extremely
likely to be used by the application are defined in .cpp files.
When adding a new class here, please think twice before putting its definition into a .cpp file.
@@ -0,0 +1,137 @@
/*
* Copyright (C) 2014 Pavel Kirienko <pavel.kirienko@gmail.com>
*/
#ifndef UAVCAN_PROTOCOL_DATA_TYPE_INFO_PROVIDER_HPP_INCLUDED
#define UAVCAN_PROTOCOL_DATA_TYPE_INFO_PROVIDER_HPP_INCLUDED
#include <uavcan/node/service_server.hpp>
#include <uavcan/util/method_binder.hpp>
#include <uavcan/build_config.hpp>
#include <uavcan/protocol/GetDataTypeInfo.hpp>
#include <uavcan/debug.hpp>
namespace uavcan
{
/**
* This class implements the standard services for data type introspection.
* Once started it does not require any attention from the application.
* The user does not need to deal with it directly - it's started by the node class.
*/
class UAVCAN_EXPORT DataTypeInfoProvider : Noncopyable
{
typedef MethodBinder<DataTypeInfoProvider*,
void (DataTypeInfoProvider::*)(const protocol::GetDataTypeInfo::Request&,
protocol::GetDataTypeInfo::Response&)> GetDataTypeInfoCallback;
ServiceServer<protocol::GetDataTypeInfo, GetDataTypeInfoCallback> gdti_srv_;
INode& getNode() { return gdti_srv_.getNode(); }
static bool isValidDataTypeKind(DataTypeKind kind)
{
return (kind == DataTypeKindMessage) || (kind == DataTypeKindService);
}
void handleGetDataTypeInfoRequest(const protocol::GetDataTypeInfo::Request& request,
protocol::GetDataTypeInfo::Response& response)
{
/*
* Asking the Global Data Type Registry for the matching type descriptor, either by name or by ID
*/
const DataTypeDescriptor* desc = UAVCAN_NULLPTR;
if (request.name.empty())
{
response.id = request.id; // Pre-setting the fields so they have meaningful values even in
response.kind = request.kind; // ...case of failure.
if (!isValidDataTypeKind(DataTypeKind(request.kind.value)))
{
UAVCAN_TRACE("DataTypeInfoProvider", "GetDataTypeInfo request with invalid DataTypeKind %i",
static_cast<int>(request.kind.value));
return;
}
desc = GlobalDataTypeRegistry::instance().find(DataTypeKind(request.kind.value), request.id);
}
else
{
response.name = request.name;
desc = GlobalDataTypeRegistry::instance().find(request.name.c_str());
}
if (desc == UAVCAN_NULLPTR)
{
UAVCAN_TRACE("DataTypeInfoProvider",
"Cannot process GetDataTypeInfo for nonexistent type: dtid=%i dtk=%i name='%s'",
static_cast<int>(request.id), static_cast<int>(request.kind.value), request.name.c_str());
return;
}
UAVCAN_TRACE("DataTypeInfoProvider", "GetDataTypeInfo request for %s", desc->toString().c_str());
/*
* Filling the response struct
*/
response.signature = desc->getSignature().get();
response.id = desc->getID().get();
response.kind.value = desc->getKind();
response.flags = protocol::GetDataTypeInfo::Response::FLAG_KNOWN;
response.name = desc->getFullName();
const Dispatcher& dispatcher = getNode().getDispatcher();
if (desc->getKind() == DataTypeKindService)
{
if (dispatcher.hasServer(desc->getID().get()))
{
response.flags |= protocol::GetDataTypeInfo::Response::FLAG_SERVING;
}
}
else if (desc->getKind() == DataTypeKindMessage)
{
if (dispatcher.hasSubscriber(desc->getID().get()))
{
response.flags |= protocol::GetDataTypeInfo::Response::FLAG_SUBSCRIBED;
}
if (dispatcher.hasPublisher(desc->getID().get()))
{
response.flags |= protocol::GetDataTypeInfo::Response::FLAG_PUBLISHING;
}
}
else
{
UAVCAN_ASSERT(0); // That means that GDTR somehow found a type of an unknown kind. The horror.
}
}
public:
explicit DataTypeInfoProvider(INode& node) :
gdti_srv_(node)
{ }
int start()
{
int res = 0;
res = gdti_srv_.start(GetDataTypeInfoCallback(this, &DataTypeInfoProvider::handleGetDataTypeInfoRequest));
if (res < 0)
{
goto fail;
}
UAVCAN_ASSERT(res >= 0);
return res;
fail:
UAVCAN_ASSERT(res < 0);
gdti_srv_.stop();
return res;
}
};
}
#endif // UAVCAN_PROTOCOL_DATA_TYPE_INFO_PROVIDER_HPP_INCLUDED
@@ -0,0 +1,112 @@
/*
* Copyright (C) 2015 Pavel Kirienko <pavel.kirienko@gmail.com>
*/
#ifndef UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_CLIENT_HPP_INCLUDED
#define UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_CLIENT_HPP_INCLUDED
#include <uavcan/node/subscriber.hpp>
#include <uavcan/node/publisher.hpp>
#include <uavcan/node/timer.hpp>
#include <uavcan/util/method_binder.hpp>
#include <uavcan/build_config.hpp>
#include <uavcan/protocol/dynamic_node_id/Allocation.hpp>
#include <uavcan/protocol/HardwareVersion.hpp>
namespace uavcan
{
/**
* This class implements client-side logic of dynamic node ID allocation procedure.
*
* Once started, the object will be publishing dynamic node ID allocation requests at the default frequency defined
* by the specification, until a Node ID is granted by the allocator.
*
* If the local node is equipped with redundant CAN interfaces, all of them will be used for publishing requests
* and listening for responses.
*
* Once dynamic allocation is complete (or not needed anymore), the object can be deleted.
*
* Note that this class uses std::rand(), which must be correctly seeded before use.
*/
class UAVCAN_EXPORT DynamicNodeIDClient : private TimerBase
{
typedef MethodBinder<DynamicNodeIDClient*,
void (DynamicNodeIDClient::*)
(const ReceivedDataStructure<protocol::dynamic_node_id::Allocation>&)>
AllocationCallback;
enum Mode
{
ModeWaitingForTimeSlot,
ModeDelayBeforeFollowup,
NumModes
};
Publisher<protocol::dynamic_node_id::Allocation> dnida_pub_;
Subscriber<protocol::dynamic_node_id::Allocation, AllocationCallback> dnida_sub_;
uint8_t unique_id_[protocol::HardwareVersion::FieldTypes::unique_id::MaxSize];
uint8_t size_of_received_unique_id_;
NodeID preferred_node_id_;
NodeID allocated_node_id_;
NodeID allocator_node_id_;
void terminate();
static MonotonicDuration getRandomDuration(uint32_t lower_bound_msec, uint32_t upper_bound_msec);
void restartTimer(const Mode mode);
virtual void handleTimerEvent(const TimerEvent&) override;
void handleAllocation(const ReceivedDataStructure<protocol::dynamic_node_id::Allocation>& msg);
public:
typedef protocol::HardwareVersion::FieldTypes::unique_id UniqueID;
DynamicNodeIDClient(INode& node)
: TimerBase(node)
, dnida_pub_(node)
, dnida_sub_(node)
, size_of_received_unique_id_(0)
{ }
/**
* @param unique_id Unique ID of the local node. Must be the same as in the hardware version struct.
* @param preferred_node_id Node ID that the application would like to take; set to broadcast (zero) if
* the application doesn't have any preference (this is default).
* @param transfer_priority Transfer priority, Normal by default.
* @return Zero on success
* Negative error code on failure
* -ErrLogic if 1. the node is not in passive mode or 2. the client is already started
*/
int start(const UniqueID& unique_id,
const NodeID preferred_node_id = NodeID::Broadcast,
const TransferPriority transfer_priority = TransferPriority::OneHigherThanLowest);
/**
* Use this method to determine when allocation is complete.
*/
bool isAllocationComplete() const { return getAllocatedNodeID().isUnicast(); }
/**
* This method allows to retrieve the node ID that was allocated to the local node.
* If no node ID was allocated yet, the returned node ID will be invalid (non-unicast).
* @return If allocation is complete, a valid unicast node ID will be returned.
* If allocation is not complete yet, a non-unicast node ID will be returned.
*/
NodeID getAllocatedNodeID() const { return allocated_node_id_; }
/**
* This method allows to retrieve node ID of the allocator that granted our Node ID.
* If no node ID was allocated yet, the returned node ID will be invalid (non-unicast).
* @return If allocation is complete, a valid unicast node ID will be returned.
* If allocation is not complete yet, an non-unicast node ID will be returned.
*/
NodeID getAllocatorNodeID() const { return allocator_node_id_; }
};
}
#endif // UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_CLIENT_HPP_INCLUDED
@@ -0,0 +1,114 @@
/*
* Copyright (C) 2015 Pavel Kirienko <pavel.kirienko@gmail.com>
*/
#ifndef UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_SERVER_SERVER_HPP_INCLUDED
#define UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_SERVER_SERVER_HPP_INCLUDED
#include <uavcan/build_config.hpp>
#include <uavcan/debug.hpp>
#include <uavcan/protocol/dynamic_node_id_server/allocation_request_manager.hpp>
#include <uavcan/protocol/dynamic_node_id_server/node_discoverer.hpp>
#include <uavcan/protocol/dynamic_node_id_server/event.hpp>
namespace uavcan
{
namespace dynamic_node_id_server
{
class AbstractServer : protected IAllocationRequestHandler
, protected INodeDiscoveryHandler
{
UniqueID own_unique_id_;
MonotonicTime started_at_;
protected:
INode& node_;
IEventTracer& tracer_;
AllocationRequestManager allocation_request_manager_;
NodeDiscoverer node_discoverer_;
AbstractServer(INode& node,
IEventTracer& tracer) :
node_(node),
tracer_(tracer),
allocation_request_manager_(node, tracer, *this),
node_discoverer_(node, tracer, *this)
{ }
const UniqueID& getOwnUniqueID() const { return own_unique_id_; }
int init(const UniqueID& own_unique_id, const TransferPriority priority)
{
int res = 0;
own_unique_id_ = own_unique_id;
res = allocation_request_manager_.init(priority);
if (res < 0)
{
return res;
}
res = node_discoverer_.init(priority);
if (res < 0)
{
return res;
}
started_at_ = node_.getMonotonicTime();
return 0;
}
public:
/**
* This can be used to guess if there are any un-allocated dynamic nodes left in the network.
*/
bool guessIfAllDynamicNodesAreAllocated(
const MonotonicDuration& allocation_activity_timeout =
MonotonicDuration::fromMSec(Allocation::MAX_REQUEST_PERIOD_MS * 2),
const MonotonicDuration& min_uptime = MonotonicDuration::fromMSec(6000)) const
{
const MonotonicTime ts = node_.getMonotonicTime();
/*
* If uptime is not large enough, the allocator may be unaware about some nodes yet.
*/
const MonotonicDuration uptime = ts - started_at_;
if (uptime < max(allocation_activity_timeout, min_uptime))
{
return false;
}
/*
* If there are any undiscovered nodes, assume that allocation is still happening.
*/
if (node_discoverer_.hasUnknownNodes())
{
return false;
}
/*
* Lastly, check if there wasn't any allocation messages detected on the bus in the specified amount of time.
*/
const MonotonicDuration since_allocation_activity =
ts - allocation_request_manager_.getTimeOfLastAllocationActivity();
if (since_allocation_activity < allocation_activity_timeout)
{
return false;
}
return true;
}
/**
* This is useful for debugging/testing/monitoring.
*/
const NodeDiscoverer& getNodeDiscoverer() const { return node_discoverer_; }
};
}
}
#endif // Include guard

Some files were not shown because too many files have changed in this diff Show More