Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sensors: codegen for attr/chan/trig #83077

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
189 changes: 189 additions & 0 deletions cmake/modules/extensions.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ include(CheckCXXCompilerFlag)
# 7.2 add_llext_* build control functions
# 7.3 llext helper functions
# 8. Script mode handling
# 9 Miscellaneous functions and macros
# 9.1 Sensor Library

########################################################
# 1. Zephyr-aware extensions
Expand Down Expand Up @@ -5528,6 +5530,70 @@ macro(zephyr_check_flags_exclusive function prefix)
endif()
endmacro()

#
# Wrapper around cmake_parse_arguments that fails with an error if any arguments
# remained unparsed or a required argument was not provided.
#
# All parsed arguments are prefixed with "arg_". This helper can only be used
# by functions, not macros.
#
# Required Arguments:
#
# NUM_POSITIONAL_ARGS - PARSE_ARGV <N> arguments for
# cmake_parse_arguments
#
# Optional Args:
#
# OPTION_ARGS - <option> arguments for cmake_parse_arguments
# ONE_VALUE_ARGS - <one_value_keywords> arguments for cmake_parse_arguments
# MULTI_VALUE_ARGS - <multi_value_keywords> arguments for
# cmake_parse_arguments
# REQUIRED_ARGS - required arguments which must be set, these may any
# argument type (<option>, <one_value_keywords>, and/or
# <multi_value_keywords>)
#
macro(zephyr_parse_arguments)
# First parse the arguments to this macro.
set(options)
set(single_args NUM_POSITIONAL_ARGS)
set(multi_args OPTION_ARGS ONE_VALUE_ARGS MULTI_VALUE_ARGS REQUIRED_ARGS)
cmake_parse_arguments(z_parse_arg
"${options}" "${single_args}" "${multi_args}" ${ARGN}
)
zephyr_check_arguments_required("zephyr_parse_arguments" "z_parse_arg" NUM_POSITIONAL_ARGS)
if(NOT "${z_parse_arg_UNPARSED_ARGUMENTS}" STREQUAL "")
message(FATAL_ERROR "Unexpected arguments to zephyr_parse_arguments: "
"${z_parse_arg_UNPARSED_ARGUMENTS}")
endif()

# Now that we have the macro's arguments, process the caller's arguments.
zephyr_parse_arguments_strict("${CMAKE_CURRENT_FUNCTION}"
"${z_parse_arg_NUM_POSITIONAL_ARGS}"
"${z_parse_arg_OPTION_ARGS}"
"${z_parse_arg_ONE_VALUE_ARGS}"
"${z_parse_arg_MULTI_VALUE_ARGS}"
)
zephyr_check_arguments_required(
"${CMAKE_CURRENT_FUNCTION}" "arg" ${z_parse_arg_REQUIRED_ARGS}
)
endmacro()

#
# Wrapper around cmake_parse_arguments that fails with an error if any arguments
# remained unparsed.
macro(zephyr_parse_arguments_strict function start_arg options one multi)
cmake_parse_arguments(PARSE_ARGV
"${start_arg}" arg "${options}" "${one}" "${multi}"
)
if(NOT "${arg_UNPARSED_ARGUMENTS}" STREQUAL "")
set(_all_args ${options} ${one} ${multi})
message(FATAL_ERROR
"Unexpected arguments to ${function}: ${arg_UNPARSED_ARGUMENTS}\n"
"Valid arguments: ${_all_args}"
)
endif()
endmacro()

########################################################
# 7. Linkable loadable extensions (llext)
########################################################
Expand Down Expand Up @@ -5939,3 +6005,126 @@ if(CMAKE_SCRIPT_MODE_FILE)
# This silence the error: 'Unknown CMake command "yaml_context"'
endfunction()
endif()

# 9 Miscellaneous helper functions

# 9.1 Sensor Library
# Generates a sensor library
#
# Args:
# OUT_HEADER: The path/to/header.h to generate
# SOURCES: YAML files defining sensors
# OUT_INCLUDES: [optional] The include path to expose in the final library, if
# not defined, the root of the 'out_header' will be used so including the
# header will be done via '#include "path/to/header.h"'
# INPUTS: [optional] YAML files included by the sensors, these will be
# used to optimize re-building.
# GENERATOR: [optional] Python generator script, if not set, the default
# Pigweed generator will be used.
# GENERATOR_ARGS: [optional] Command line arguments to pass to the generator.
# GENERATOR_INCLUDES: [optional] Include paths to pass to the generator. These
# are used to resolve the sensor dependencies.
# PUBLIC_DEPS: [optional] Public dependencies to pass to the final generated
# target.
#
# Example use:
# zephyr_sensor_library(my_sensors
# OUT_HEADER
# ${CMAKE_BINARY_DIR}/generated/include/my/app/sensors.h
# OUT_INCLUDES
# ${CMAKE_BINARY_DIR}/generated/include
# SOURCES
# sensors/bma4xx.yaml
# sensors/bmi160.yaml
# INPUTS
# sensors/attributes.yaml
# sensors/channels.yaml
# sensors/triggers.yaml
# sensors/units.yaml
# GENERATOR
# scripts/sensor_header_generator.py
# GENERATOR_ARGS
# -v
# -experimental
# GENERATOR_INCLUDES
# ${CMAKE_CURRENT_LIST_DIR}
# PUBLIC_DEPS
# pw_sensor.types
# pw_containers
# )
function(zephyr_sensor_library NAME)
zephyr_parse_arguments(
NUM_POSITIONAL_ARGS
1
MULTI_VALUE_ARGS
INPUTS
GENERATOR_INCLUDES
SOURCES
GENERATOR_ARGS
PUBLIC_DEPS
OUT_INCLUDES
ONE_VALUE_ARGS
OUT_HEADER
GENERATOR
REQUIRED_ARGS
GENERATOR_INCLUDES
SOURCES
OUT_HEADER
)

if("${arg_GENERATOR}" STREQUAL "")
set(arg_GENERATOR "pw_sensor.constants_generator")

if("${arg_GENERATOR_ARGS}" STREQUAL "")
set(arg_GENERATOR_ARGS --package pw.sensor)
endif()
endif()

if(IS_ABSOLUTE "${arg_OUT_HEADER}")
if("${arg_OUT_INCLUDES}" STREQUAL "")
message(FATAL_ERROR "Invalid absolute path OUT_HEADER=${arg_OUT_HEADER}, missing OUT_INCLUDES")
endif()

set(output_file "${arg_OUT_HEADER}")
else()
set(output_file "${CMAKE_CURRENT_BINARY_DIR}/${arg_OUT_HEADER}")
if("${arg_OUT_INCLUDES}" STREQUAL "")
set(arg_OUT_INCLUDES "${CMAKE_CURRENT_BINARY_DIR}")
endif()
endif()

string(REPLACE ";" " " generator_args "${arg_GENERATOR_ARGS}")

set(include_list)

foreach(item IN LISTS arg_GENERATOR_INCLUDES)
list(APPEND include_list "-I" "${item}")
endforeach()

add_custom_command(
OUTPUT ${output_file}
COMMAND ${PYTHON_EXECUTABLE} -m pw_sensor.sensor_desc
${include_list}
-g "${PYTHON_EXECUTABLE} ${arg_GENERATOR} ${generator_args}"
-o ${output_file}
${arg_SOURCES}
DEPENDS
${arg_GENERATOR}
${arg_INPUTS}
${arg_SOURCES}
)
add_custom_target(${NAME}.__generate_constants
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are there full stops in target names?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not? I've used them before in other projects. I'm happy to remove them. I use them to denote subpackage like targets. Makes it harder for things to collide when generating targets.

I'm happy to remove the . if you feel strongly about it.

DEPENDS
${output_file}
)
add_library(${NAME} STATIC
${output_file}
)
zephyr_link_libraries(${NAME})
target_link_libraries(${NAME} PUBLIC ${arg_PUBLIC_DEPS})
target_include_directories(${NAME} PUBLIC
${arg_OUT_INCLUDES}
)
add_dependencies(${NAME} ${NAME}.__generate_constants)
set_target_properties(${NAME} PROPERTIES LINKER_LANGUAGE CXX)
endfunction(zephyr_sensor_library)
18 changes: 14 additions & 4 deletions doc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,12 @@ endfunction()
#-------------------------------------------------------------------------------
# Doxygen (standalone)

set(CMAKE_C_ARCHIVE_CREATE "<CMAKE_C_COMPILER> cr <TARGET> <OBJECTS>")
set(CMAKE_C_ARCHIVE_FINISH "<CMAKE_C_COMPILER> s <TARGET>")
add_library(zephyr_interface INTERFACE)
include(${ZEPHYR_BASE}/drivers/sensor/codegen.cmake)
set_target_properties(zephyr_sensor.constants PROPERTIES LINKER_LANGUAGE C)

set(DOXY_OUT ${CMAKE_CURRENT_BINARY_DIR}/doxygen)
set(DOXYFILE_IN ${CMAKE_CURRENT_LIST_DIR}/zephyr.doxyfile.in)
set(DOXYFILE_OUT ${CMAKE_CURRENT_BINARY_DIR}/zephyr.doxyfile)
Expand All @@ -116,6 +122,8 @@ set_target_properties(
ADDITIONAL_CLEAN_FILES "${DOXY_OUT}"
)

add_dependencies(doxygen zephyr_sensor.constants.__generate_constants)

#-------------------------------------------------------------------------------
# devicetree

Expand Down Expand Up @@ -149,6 +157,8 @@ set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${GEN_DEVICETREE_
#-------------------------------------------------------------------------------
# html

set(SPHINXOPTS_EXTRA ${SPHINXOPTS_EXTRA} -D sensor_yaml_files="${sensor_yaml_files}")

add_doc_target(
html
COMMAND ${CMAKE_COMMAND} -E env ${SPHINX_ENV} OUTPUT_DIR=${DOCS_HTML_DIR}
Expand All @@ -172,7 +182,7 @@ set_target_properties(
ADDITIONAL_CLEAN_FILES "${DOCS_SRC_DIR};${DOCS_HTML_DIR};${DOCS_DOCTREE_DIR}"
)

add_dependencies(html devicetree)
add_dependencies(html devicetree zephyr_sensor.constants.__generate_constants)

#-------------------------------------------------------------------------------
# html-live
Expand Down Expand Up @@ -202,7 +212,7 @@ set_target_properties(
ADDITIONAL_CLEAN_FILES "${DOCS_SRC_DIR};${DOCS_HTML_DIR};${DOCS_DOCTREE_DIR}"
)

add_dependencies(html-live devicetree)
add_dependencies(html-live devicetree zephyr_sensor.constants.__generate_constants)
#-------------------------------------------------------------------------------
# pdf

Expand Down Expand Up @@ -230,7 +240,7 @@ set_target_properties(
ADDITIONAL_CLEAN_FILES "${DOCS_SRC_DIR};${DOCS_LATEX_DIR};${DOCS_DOCTREE_DIR}"
)

add_dependencies(latex devicetree)
add_dependencies(latex devicetree zephyr_sensor.constants.__generate_constants)

if(LATEX_PDFLATEX_FOUND AND LATEXMK)
if(WIN32)
Expand All @@ -252,7 +262,7 @@ if(LATEX_PDFLATEX_FOUND AND LATEXMK)
USES_TERMINAL
)

add_dependencies(pdf latex)
add_dependencies(pdf latex zephyr_sensor.constants.__generate_constants)
endif()

#-------------------------------------------------------------------------------
Expand Down
1 change: 1 addition & 0 deletions doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@
"fmt": True,
"fmt_vars": {
"ZEPHYR_BASE": str(ZEPHYR_BASE),
"CMAKE_BINARY_DIR": str(ZEPHYR_BUILD.parent),
"ZEPHYR_VERSION": version,
},
"outdir_var": "DOXY_OUT",
Expand Down
67 changes: 67 additions & 0 deletions doc/hardware/peripherals/sensor/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,77 @@ Implementing Read and Decode
* MUST implement :c:type:`sensor_get_decoder_t` returning the
:c:struct:`sensor_decoder_api` for that device type.

Adding your ``sensor.yaml``
===========================

Sensor YAML files can be added to enhance your driver writing experience. Each
sensor can introduce a ``sensor.yaml`` file which follows the schema described
in `pw_sensor`_. In tree, the file will automatically be picked up by the build
system and entries will be created for your driver. See the example at
``tests/drivers/sensor/generator/`` for the macro usage, you can do things
like:

* Get the number of attributes, channels, and triggers supported by a specific
compatible string.
* Get the number of instances of a specific channel of a sensor.
* Check if a driver supports setting a specific attribute/channel pair.
* Automatically index your drivers into the Zephyr documentation

The primary use case for these is to verify that a DT entry's compatible value
supports specific requirements. For example:

* Assume we're building a lid angle calculation which requires 2
accelerometers. These accelerometers will be specified in the devicetree
using the chosen nodes so we can access them via ``DT_CHOSEN(lid_accel)``
and ``DT_CHOSEN(base_accel)``.
* We can now run a static assert like
``BUILD_ASSERT(SENSOR_SPEC_CHAN_INST_COUNT(DT_PROP(DT_CHOSEN(lid_accel), compatible), accel_xyz) > 0);``

Additional features are in development and will allow drivers to:

* Generate boilerplate code for the sensor API such as attribute set/get,
submit, etc.
* Generate common config/data struct definitions.
* Abstract the bus API so you can read/write registers without worrying about
i2c/spi/etc.
* Optimize memory allocation based to YAML metadata. Using the static data
available in the ``sensor.yaml`` file, we will be able to allocate the right
size buffer for streaming and one-shot reading APIs as well as client side
buffers.

Adding sensors out of tree
--------------------------

Zephyr supports adding sensors from modules. There are 3 CMake variables that
should be updated to make that happen:

* ``ZEPHYR_EXTRA_SENSOR_YAML_FILES``: this is a cmake list containing full
paths to sensor YAML files that are not in the Zephyr tree.
* ``ZEPHYR_EXTRA_SENSOR_INCLUDE_PATHS``: this is a cmake list containing full
paths to directories which contains custom attributes, channels, triggers, or
units. These paths are directories which the ``zephyr_sensor_library`` cmake
function will use to find definition files which are included in your sensor
YAML file. It's similar to header include paths.
* ``ZEPHYR_EXTRA_SENSOR_DEFINITION_FILES``: this is a cmake list containing
full paths to files containing custom attributes, channels, triggers, or
units. The file list is used to determine when the sensor library needs to be
rebuilt.

Why ``sensor.yaml`` files?
--------------------------

* We already use YAML files to describe boards.
* They allow us to add metadata per driver type. See below for a comparison to
devicetree.
* They're portable. Downstream users may implement additional generators to do
new things.

.. _sensor-api-reference:

API Reference
***************

.. doxygengroup:: sensor_interface
.. doxygengroup:: sensor_emulator_backend

.. _`pw_sensor`: https://pigweed.dev/pw_sensor/py/
7 changes: 5 additions & 2 deletions doc/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# DOC: used to generate docs

sphinx
sphinx>=8.1
sphinx_rtd_theme~=3.0
sphinx-tabs
sphinx-tabs>=3.4.7
sphinxcontrib-svg2pdfconverter
pygments>=2.9,!=2.19.0
sphinx-notfound-page
Expand All @@ -24,3 +24,6 @@ doxmlparser

# Used by the Zephyr domain to organize code samples
anytree

# Used to generate the sensor catalog
pigweed>=0.0.20
Comment on lines +28 to +29
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why does every user need this?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Pigweed python library is where we developed the sensor codegen library. This allowed us to start onboarding teams that don't use Zephyr to use the same sensor standard descriptor without having to add Zephyr as a dependency (yet).

1 change: 1 addition & 0 deletions doc/zephyr.doxyfile.in
Original file line number Diff line number Diff line change
Expand Up @@ -972,6 +972,7 @@ WARN_LOGFILE =

INPUT = @ZEPHYR_BASE@/doc/_doxygen/mainpage.md \
@ZEPHYR_BASE@/doc/_doxygen/groups.dox \
@CMAKE_BINARY_DIR@/zephyr_sensor.constants/ \
@ZEPHYR_BASE@/kernel/include/kernel_arch_interface.h \
@ZEPHYR_BASE@/include/zephyr/arch/cache.h \
@ZEPHYR_BASE@/include/zephyr/sys/atomic.h \
Expand Down
2 changes: 2 additions & 0 deletions drivers/sensor/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ add_subdirectory_ifdef(CONFIG_VEAA_X_3 veaa_x_3)
add_subdirectory_ifdef(CONFIG_VOLTAGE_DIVIDER voltage_divider)
add_subdirectory_ifdef(CONFIG_TACH_ENE_KB1200 ene_tach_kb1200)

include(codegen.cmake)

zephyr_syscall_header(${ZEPHYR_BASE}/include/zephyr/drivers/sensor.h)

zephyr_library()
Expand Down
Loading
Loading