Skip to content
Merged
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
3 changes: 3 additions & 0 deletions .clang-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
BasedOnStyle: Google
ColumnLimit: 140
29 changes: 29 additions & 0 deletions .clang-tidy
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
Checks: >
bugprone-*,
-bugprone-easily-swappable-parameters,
-bugprone-exception-escape,
google-build-explicit-make-pairs,
google-build-namespaces,
google-build-using-namespace,
google-explicit-constructor,
google-global-names-in-headers,
google-readability-braces-around-statements,
google-readability-casting,
google-readability-namespace-comments,
-google-readability-todo,
google-runtime-int,
google-runtime-operator,
modernize-use-nullptr,
modernize-use-override,
modernize-use-emplace,
performance-for-range-copy,
performance-unnecessary-copy-initialization,
readability-braces-around-statements,
readability-const-return-type,
readability-container-size-empty,
readability-delete-null-pointer,
readability-else-after-return

WarningsAsErrors: ''
HeaderFilterRegex: 'include/cymon/.*'
40 changes: 40 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: CI
on:
push:
branches: [main, "copilot/**"]
pull_request:
branches: [main]

jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install tools
run: |
sudo apt-get update && sudo apt-get install -y \
cmake ninja-build clang clang-format clang-tidy

- name: Configure
run: |
cmake -B build -G Ninja \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_CXX_COMPILER=clang++ \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON

- name: Build
run: cmake --build build

- name: Test
run: ctest --test-dir build --output-on-failure

- name: clang-format check
run: |
find include src tests -name "*.hpp" -o -name "*.cpp" | \
xargs clang-format --dry-run --Werror

- name: clang-tidy
run: |
find src -name "*.cpp" | \
xargs clang-tidy -p build/
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,6 @@ vcpkg_installed/
# test output & cache
Testing/
.cache/

# nunavut generated headers
generated/
43 changes: 43 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Copyright 2026 Tecnologic
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

cmake_minimum_required(VERSION 3.20)
project(cymon-lib VERSION 0.1.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

# Library
add_library(cymon
src/cymon/trigger.cpp
src/cymon/sample_buffer.cpp
src/cymon/device.cpp
)
target_include_directories(cymon PUBLIC include)
target_compile_options(cymon PRIVATE -Wall -Wextra -Wpedantic)

# Optional nunavut DSDL code generation
option(CYMON_GENERATE_DSDL "Generate C++ types from DSDL via nunavut" OFF)
if(CYMON_GENERATE_DSDL)
include(cmake/nunavut.cmake)
generate_cyphal_types(cymon ${CMAKE_CURRENT_SOURCE_DIR}/dsdl)
endif()

# Tests
option(CYMON_BUILD_TESTS "Build tests" ON)
if(CYMON_BUILD_TESTS)
enable_testing()
add_subdirectory(tests)
endif()
86 changes: 85 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,85 @@
# cymon-lib
# cymon-lib

[![CI](https://github.com/Tecnologic/cymon-lib/actions/workflows/ci.yml/badge.svg)](https://github.com/Tecnologic/cymon-lib/actions/workflows/ci.yml)

Platform-agnostic C++17 library for Cyphal-connected embedded nodes.
Exposes named scalar variables and oscilloscope-style capture via a Cyphal
CAN-FD service protocol. The library is pure C++ with no Cyphal/canard
dependency — the integrating application calls the library's handlers when
it receives a Cyphal service request.

## Features

- **Named variable registry** — register up to `CYMON_MAX_VARS` (default 64)
scalar variables with a `std::function<float()>` getter and iterate them
via a `GetVariableList`-style index query.
- **Oscilloscope-style capture** — configurable number of channels,
sample count, pre-trigger depth, and trigger modes (rising, falling,
hysteresis).
- **Paged readout** — `ReadSamples` returns interleaved float frames in pages
so the caller can fit data into Cyphal transfer payloads.
- **Hardware-agnostic timer interface** — call `Device::Tick()` from your
timer ISR/task; report the actual achieved period with
`set_actual_sample_period_us()`.
- **Compile-time limits** — override `CYMON_MAX_BUFFER_BYTES` and
`CYMON_MAX_VARS` via preprocessor defines before including any header.

## Protocol (DSDL)

All DSDL types live under `dsdl/cymon/` and are **unregulated** (no numeric
port-ID prefix).

| Service / Message | Description |
|-------------------------|--------------------------------------------------|
| `GetVariableList.1.0` | Iterator-style variable enumeration (index→name) |
| `GetBufferInfo.1.0` | Query buffer limits |
| `SetupCapture.1.0` | Configure channels, sample count, pretrigger |
| `ArmTrigger.1.0` | Arm/disarm trigger with level & mode |
| `ReadSamples.1.0` | Paged readout of captured samples |
| `TriggerEvent.1.0` | Broadcast message emitted when trigger fires |

## Build

```bash
cmake -B build -DCMAKE_BUILD_TYPE=Debug
cmake --build build
ctest --test-dir build --output-on-failure
```

To build with a specific compiler:

```bash
cmake -B build -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_BUILD_TYPE=Debug
cmake --build build
```

To disable tests:

```bash
cmake -B build -DCYMON_BUILD_TESTS=OFF
cmake --build build
```

## Integration

```cpp
#include "cymon/device.hpp"

cymon::Device device;

// Register variables (call once at startup).
device.RegisterVariable("speed", "rpm", [] { return read_speed(); });
device.RegisterVariable("current", "A", [] { return read_current(); });

// In your Cyphal service handler:
auto resp = device.HandleSetupCapture(config);
auto resp = device.HandleArmTrigger(arm_cfg);
auto resp = device.HandleReadSamples(frame_offset, max_frames);

// In your hardware timer ISR / task:
device.Tick();
```

## License

Apache 2.0 — see [LICENSE](LICENSE).
53 changes: 53 additions & 0 deletions cmake/nunavut.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Copyright 2026 Tecnologic
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# generate_cyphal_types(target dsdl_dir)
# Generates C++ headers from DSDL files using nunavut and adds them to target.
function(generate_cyphal_types target dsdl_dir)
find_program(NUNAVUT_EXECUTABLE nunavut
HINTS "$ENV{HOME}/.local/bin" "/usr/local/bin"
DOC "nunavut DSDL code generation tool"
)

if(NOT NUNAVUT_EXECUTABLE)
message(FATAL_ERROR
"nunavut not found. Install it with: pip install nunavut\n"
"Then re-run cmake."
)
endif()

set(generated_dir "${CMAKE_BINARY_DIR}/generated")
file(MAKE_DIRECTORY "${generated_dir}")

file(GLOB_RECURSE dsdl_files "${dsdl_dir}/*.dsdl")

add_custom_command(
OUTPUT "${generated_dir}/.nunavut_stamp"
COMMAND "${NUNAVUT_EXECUTABLE}"
--target-language c++
--outdir "${generated_dir}"
"${dsdl_dir}"
COMMAND "${CMAKE_COMMAND}" -E touch "${generated_dir}/.nunavut_stamp"
DEPENDS ${dsdl_files}
COMMENT "Generating Cyphal C++ types from DSDL"
VERBATIM
)

add_custom_target(nunavut_generate
DEPENDS "${generated_dir}/.nunavut_stamp"
)

add_dependencies(${target} nunavut_generate)
target_include_directories(${target} PUBLIC "${generated_dir}")
endfunction()
22 changes: 22 additions & 0 deletions dsdl/cymon/ArmTrigger.1.0.dsdl
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Copyright 2026 Tecnologic
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

bool arm
uint8 source_variable_id
cymon.TriggerMode.1.0 mode
float32 level
float32 hysteresis_band
---
bool ok
uint8 error_code
20 changes: 20 additions & 0 deletions dsdl/cymon/GetBufferInfo.1.0.dsdl
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Copyright 2026 Tecnologic
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Empty request — returns capture buffer limits.

---
uint32 buffer_bytes
uint8 max_channels
uint16 max_frames # total frames storable; divide by your channel count to get per-channel depth
22 changes: 22 additions & 0 deletions dsdl/cymon/GetVariableList.1.0.dsdl
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Copyright 2026 Tecnologic
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Iterator-style variable list request, similar to uavcan.register.List.
# Request the variable at the given index; empty name signals end of list.

uint16 index
---
uint8 id
uint8[<=32] name # UTF-8 variable name; empty = end of list sentinel
uint8[<=8] unit # engineering unit string, e.g. "rpm", "A", "V"
23 changes: 23 additions & 0 deletions dsdl/cymon/ReadSamples.1.0.dsdl
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Copyright 2026 Tecnologic
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Paged read-out of captured samples.

uint16 frame_offset # page index: 0 = first page, increment by max_frames to page through capture
uint8 max_frames
---
bool triggered
bool end_of_data
uint8 num_channels
float32[<=64] samples # interleaved: num_channels floats per frame
22 changes: 22 additions & 0 deletions dsdl/cymon/SetupCapture.1.0.dsdl
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Copyright 2026 Tecnologic
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

uint8[<=16] channel_ids
float32 sample_period_us
uint16 num_samples
uint16 pretrigger_samples
---
bool ok
uint8 error_code
float32 actual_sample_period_us # period actually used by hardware, may differ from requested
Loading