├── .clang-format
├── .github
└── workflows
│ └── build.yml
├── .gitignore
├── .gitmodules
├── .vscode
└── settings.json
├── LICENSE
├── README.md
├── differential_pressure_sensor
├── .idea
│ └── dictionaries
│ │ └── pavel.xml
├── CMakeLists.txt
├── README.md
├── docs
│ ├── monitor-initial.png
│ └── monitor.png
└── src
│ └── main.c
├── libcyphal_demo
├── .clang-format
├── .clang-tidy
├── CMakeLists.txt
├── CMakePresets.json
├── README.md
└── src
│ ├── CMakeLists.txt
│ ├── any_transport_bag.hpp
│ ├── application.cpp
│ ├── application.hpp
│ ├── exec_cmd_provider.hpp
│ ├── file_downloader.hpp
│ ├── main.cpp
│ ├── no_cpp_heap.cpp
│ ├── platform
│ ├── block_memory_resource.hpp
│ ├── bsd
│ │ └── kqueue_single_threaded_executor.hpp
│ ├── common_helpers.hpp
│ ├── defines.hpp
│ ├── linux
│ │ ├── can
│ │ │ └── can_media.hpp
│ │ └── epoll_single_threaded_executor.hpp
│ ├── o1_heap_memory_resource.hpp
│ ├── posix
│ │ ├── posix_executor_extension.hpp
│ │ ├── posix_platform_error.hpp
│ │ └── udp
│ │ │ ├── udp_media.hpp
│ │ │ └── udp_sockets.hpp
│ ├── storage.hpp
│ └── string.hpp
│ ├── transport_bag_can.hpp
│ └── transport_bag_udp.hpp
├── libudpard_demo
├── .clang-tidy
├── .idea
│ └── dictionaries
│ │ └── pavel.xml
├── CMakeLists.txt
├── README.md
├── docs
│ ├── wireshark-pnp.png
│ ├── yakut-monitor-data.png
│ └── yakut-monitor-pnp.png
└── src
│ ├── crc64we.h
│ ├── main.c
│ ├── memory_block.h
│ ├── register.c
│ ├── register.h
│ ├── storage.c
│ └── storage.h
├── shared
├── register
│ ├── register.c
│ ├── register.cmake
│ └── register.h
├── socketcan
│ ├── socketcan.c
│ ├── socketcan.cmake
│ └── socketcan.h
└── udp
│ ├── udp.c
│ ├── udp.cmake
│ └── udp.h
├── submodules
└── cavl
│ └── cavl.h
└── udral_servo
├── .idea
└── dictionaries
│ └── pavel.xml
├── CMakeLists.txt
├── README.md
├── docs
├── monitor-initial.png
└── monitor.png
└── src
└── main.c
/.clang-format:
--------------------------------------------------------------------------------
1 | ---
2 | Language: Cpp
3 | # BasedOnStyle: LLVM
4 | AccessModifierOffset: -4
5 | AlignAfterOpenBracket: Align
6 | AlignConsecutiveAssignments: true
7 | AlignConsecutiveDeclarations: true
8 | AlignEscapedNewlines: Left
9 | AlignOperands: true
10 | AlignTrailingComments: true
11 | AllowAllParametersOfDeclarationOnNextLine: false
12 | AllowShortBlocksOnASingleLine: Never
13 | AllowShortCaseLabelsOnASingleLine: false
14 | AllowShortFunctionsOnASingleLine: Inline
15 | AllowShortIfStatementsOnASingleLine: Never
16 | AllowShortLoopsOnASingleLine: false
17 | AlwaysBreakAfterDefinitionReturnType: None
18 | AlwaysBreakAfterReturnType: None
19 | AlwaysBreakBeforeMultilineStrings: false
20 | AlwaysBreakTemplateDeclarations: Yes
21 | BinPackArguments: false
22 | BinPackParameters: false
23 | BraceWrapping:
24 | AfterCaseLabel: true
25 | AfterClass: true
26 | AfterControlStatement: true
27 | AfterEnum: true
28 | AfterFunction: true
29 | AfterNamespace: true
30 | AfterStruct: true
31 | AfterUnion: true
32 | BeforeCatch: true
33 | BeforeElse: true
34 | IndentBraces: false
35 | SplitEmptyFunction: false
36 | SplitEmptyRecord: false
37 | SplitEmptyNamespace: false
38 | AfterExternBlock: false # Keeps the contents un-indented.
39 | BreakBeforeBinaryOperators: None
40 | BreakBeforeBraces: Custom
41 | BreakBeforeTernaryOperators: true
42 | BreakConstructorInitializers: AfterColon
43 | # BreakInheritanceList: AfterColon
44 | BreakStringLiterals: true
45 | ColumnLimit: 120
46 | CommentPragmas: '^ (coverity|pragma:)'
47 | CompactNamespaces: false
48 | ConstructorInitializerAllOnOneLineOrOnePerLine: true
49 | ConstructorInitializerIndentWidth: 4
50 | ContinuationIndentWidth: 4
51 | Cpp11BracedListStyle: true
52 | DerivePointerAlignment: false
53 | DisableFormat: false
54 | ExperimentalAutoDetectBinPacking: false
55 | FixNamespaceComments: true
56 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ]
57 | IncludeBlocks: Preserve
58 | IndentCaseLabels: false
59 | IndentPPDirectives: AfterHash
60 | IndentWidth: 4
61 | IndentWrappedFunctionNames: false
62 | KeepEmptyLinesAtTheStartOfBlocks: false
63 | MacroBlockBegin: ''
64 | MacroBlockEnd: ''
65 | MaxEmptyLinesToKeep: 1
66 | NamespaceIndentation: None
67 | PenaltyBreakAssignment: 2
68 | PenaltyBreakBeforeFirstCallParameter: 10000 # Raised intentionally; prefer breaking all
69 | PenaltyBreakComment: 300
70 | PenaltyBreakFirstLessLess: 120
71 | PenaltyBreakString: 1000
72 | PenaltyExcessCharacter: 1000000
73 | PenaltyReturnTypeOnItsOwnLine: 10000 # Raised intentionally because it hurts readability
74 | PointerAlignment: Left
75 | ReflowComments: true
76 | SortIncludes: Never
77 | SortUsingDeclarations: false
78 | SpaceAfterCStyleCast: true
79 | SpaceAfterTemplateKeyword: true
80 | SpaceBeforeAssignmentOperators: true
81 | SpaceBeforeCpp11BracedList: false
82 | SpaceBeforeInheritanceColon: true
83 | SpaceBeforeParens: ControlStatements
84 | SpaceBeforeCtorInitializerColon: true
85 | SpaceBeforeRangeBasedForLoopColon: true
86 | SpaceInEmptyParentheses: false
87 | SpacesBeforeTrailingComments: 2
88 | SpacesInAngles: false
89 | SpacesInCStyleCastParentheses: false
90 | SpacesInContainerLiterals: false
91 | SpacesInParentheses: false
92 | SpacesInSquareBrackets: false
93 | Standard: c++14
94 | TabWidth: 8
95 | UseTab: Never
96 | ...
97 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: build
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | - 'issue/*'
8 | pull_request:
9 | branches:
10 | - main
11 | - 'issue/*'
12 |
13 | env:
14 | # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
15 | BUILD_TYPE: Release
16 |
17 | jobs:
18 | build_libcyphal_demo:
19 | name: Build LibCyphal demo
20 | runs-on: ubuntu-latest
21 |
22 | steps:
23 | - uses: actions/checkout@v4
24 | with:
25 | submodules: recursive
26 |
27 | - name: Install Ninja
28 | run: sudo apt-get install ninja-build
29 |
30 | - name: Configure CMake
31 | run: cd ${{github.workspace}}/libcyphal_demo && cmake --preset Demo-Linux
32 |
33 | - name: Build Debug
34 | run: cd ${{github.workspace}}/libcyphal_demo && cmake --build --preset Demo-Linux-Debug
35 |
36 | - name: Build Release
37 | run: cd ${{github.workspace}}/libcyphal_demo && cmake --build --preset Demo-Linux-Release
38 |
39 | build_libudpard_demo:
40 | name: Build LibUDPard demo
41 | runs-on: ubuntu-latest
42 |
43 | steps:
44 | - uses: actions/checkout@v4
45 | with:
46 | submodules: recursive
47 |
48 | - name: Configure CMake
49 | run: cmake -B ${{github.workspace}}/libudpard_demo/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} ${{github.workspace}}/libudpard_demo
50 |
51 | - name: Build
52 | run: cmake --build ${{github.workspace}}/libudpard_demo/build --config ${{env.BUILD_TYPE}}
53 |
54 | build_differential_pressure_sensor:
55 | name: Build Differential Pressure Sensor demo
56 | runs-on: ubuntu-latest
57 |
58 | steps:
59 | - uses: actions/checkout@v4
60 | with:
61 | submodules: recursive
62 |
63 | - name: Configure CMake
64 | run: cmake -B ${{github.workspace}}/differential_pressure_sensor/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} ${{github.workspace}}/differential_pressure_sensor
65 |
66 | - name: Build
67 | run: cmake --build ${{github.workspace}}/differential_pressure_sensor/build --config ${{env.BUILD_TYPE}}
68 |
69 |
70 | build_udral_servo:
71 | name: Build UDRAL Servo demo
72 | runs-on: ubuntu-latest
73 |
74 | steps:
75 | - uses: actions/checkout@v4
76 | with:
77 | submodules: recursive
78 |
79 | - name: Configure CMake
80 | run: cmake -B ${{github.workspace}}/udral_servo/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} ${{github.workspace}}/udral_servo
81 |
82 | - name: Build
83 | run: cmake --build ${{github.workspace}}/udral_servo/build --config ${{env.BUILD_TYPE}}
84 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.d
2 | *.o
3 | *.ko
4 | *.obj
5 | *.elf
6 | *.ilk
7 | *.map
8 | *.exp
9 | *.gch
10 | *.pch
11 | *.lib
12 | *.a
13 | *.la
14 | *.lo
15 | *.dll
16 | *.so
17 | *.so.*
18 | *.dylib
19 | *.exe
20 | *.out
21 | *.app
22 | *.i*86
23 | *.x86_64
24 | *.hex
25 | *.dSYM/
26 | *.su
27 | *.idb
28 | *.pdb
29 | *.mod*
30 | .tmp_versions/
31 | modules.order
32 | Module.symvers
33 | Mkfile.old
34 | dkms.conf
35 | build/
36 | cmake-build*/
37 |
38 | # DSDL compilation outputs
39 | .compiled/
40 | .transpiled/
41 |
42 | # Node register files
43 | *.cfg
44 |
45 | # IDE and tools
46 | .gdbinit
47 | **/.idea/*
48 | !**/.idea/dictionaries
49 | !**/.idea/dictionaries/*
50 | .venv
51 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "submodules/libcanard"]
2 | path = submodules/libcanard
3 | url = https://github.com/UAVCAN/libcanard
4 | branch = v4
5 | [submodule "submodules/o1heap"]
6 | path = submodules/o1heap
7 | url = https://github.com/pavel-kirienko/o1heap
8 | [submodule "submodules/libudpard"]
9 | path = submodules/libudpard
10 | url = https://github.com/OpenCyphal/libudpard
11 | branch = v2
12 | [submodule "submodules/cetl"]
13 | path = submodules/cetl
14 | url = https://github.com/OpenCyphal/CETL.git
15 | [submodule "submodules/libcyphal"]
16 | path = submodules/libcyphal
17 | url = https://github.com/OpenCyphal-Garage/libcyphal.git
18 | [submodule "submodules/nunavut"]
19 | path = submodules/nunavut
20 | url = https://github.com/OpenCyphal/nunavut.git
21 | branch = 3.0.preview
22 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "cSpell.words": [
3 | "baremetal",
4 | "cetl",
5 | "Cyphal",
6 | "libcyphal",
7 | "POSIX",
8 | "uavcan"
9 | ],
10 | "cmake.sourceDirectory": "/home/sergei/Develop/git/OpenCyphal-Garage/demos/libcyphal_demo"
11 | }
12 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 OpenCyphal
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Demo applications and references
2 |
3 | [](https://forum.opencyphal.org)
4 |
5 | A weakly organized collection of usage demos and examples that can be used to bootstrap product development.
6 |
7 |
8 | ## Background
9 |
10 | Adopting Cyphal may seem like a paradigm shift for an engineer experienced with prior-art technologies
11 | due to its focus on service-orientation and zero-cost abstraction.
12 | In order to make sense of the materials presented here,
13 | **you should first read [the Cyphal Guide](https://opencyphal.org/guide)**.
14 | For a more hands-on experience, consider completing the
15 | [PyCyphal tutorial](https://pycyphal.readthedocs.io/en/stable/pages/demo.html).
16 |
17 |
18 | ## How to use this repository
19 |
20 | There is a separate directory per demo.
21 | Demos may depend on the components published by the OpenCyphal team, such as
22 | [Libcanard](https://github.com/OpenCyphal/libcanard) or the
23 | [public regulated DSDL definitions](https://github.com/OpenCyphal/public_regulated_data_types/).
24 | These are collected under `submodules/`.
25 | You will need to add them to your application separately in whatever way suits your workflow best ---
26 | as a Git submodule, by copy-pasting the sources, using CMake's `ExternalProject_Add()`, etc.
27 |
--------------------------------------------------------------------------------
/differential_pressure_sensor/.idea/dictionaries/pavel.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | afdx
5 | allocatee
6 | antipattern
7 | appveyor
8 | ardu
9 | argparse
10 | asctime
11 | asyncio
12 | atexit
13 | aton
14 | autoconfiguration
15 | autodoc
16 | baremetal
17 | baudrate
18 | bgcolor
19 | blin
20 | bools
21 | bufferless
22 | byref
23 | bysource
24 | candump
25 | canfd
26 | canid
27 | caplog
28 | capturable
29 | ccitt
30 | cmsg
31 | coloredlogs
32 | computron
33 | comspec
34 | conftest
35 | constrainedness
36 | constructible
37 | contnode
38 | creationflags
39 | ctrunc
40 | cyber
41 | cyphal
42 | datagrams
43 | deadbeef
44 | deallocated
45 | debian
46 | decaxta
47 | deduplicator
48 | deduplicators
49 | demultiplexer
50 | demultiplexing
51 | demux
52 | dereplicated
53 | deserializing
54 | dhcp
55 | diehard
56 | disbalance
57 | dlen
58 | docname
59 | doctree
60 | doesn
61 | dronecode
62 | dscp
63 | dsdl
64 | dsonar
65 | dtype
66 | elif
67 | emptor
68 | endfor
69 | endmacro
70 | ethertype
71 | facto
72 | ffee
73 | ffff
74 | fgsfds
75 | findalldevs
76 | fontname
77 | frombuffer
78 | fyodor
79 | geez
80 | gendsdl
81 | genericity
82 | getenv
83 | getpeername
84 | getsockopt
85 | gibibyte
86 | gibibytes
87 | graphviz
88 | hacky
89 | hardbass
90 | hdlc
91 | hitl
92 | hostmask
93 | hwgrep
94 | hwmaj
95 | hwmin
96 | iana
97 | icmp
98 | iface
99 | ifaces
100 | ifidx
101 | inaddr
102 | inet
103 | intersphinx
104 | intravehicular
105 | ipproto
106 | iscsi
107 | isfinite
108 | kibibyte
109 | kirienko
110 | koopman
111 | levelname
112 | libcanard
113 | libpcap
114 | libuavcan
115 | linkcode
116 | lnid
117 | lsmod
118 | mcfloatface
119 | mebibytes
120 | memcpy
121 | memoryview
122 | mismaintenance
123 | modifyitems
124 | modprobe
125 | mult
126 | multiframe
127 | multithreaded
128 | mypy
129 | mypypath
130 | ncat
131 | ndarray
132 | ndim
133 | ndis
134 | netcat
135 | netfilter
136 | netstat
137 | nfrag
138 | nihil
139 | nmap
140 | nnvg
141 | noqa
142 | norecursedirs
143 | nosignatures
144 | npcap
145 | ntdll
146 | octothorp
147 | onboard
148 | opencyphal
149 | packbits
150 | pathlib
151 | pcap
152 | pcapy
153 | perfcounters
154 | pfft
155 | pitot
156 | pizdec
157 | pizdets
158 | pkgutil
159 | popen
160 | powershell
161 | prio
162 | prog
163 | protip
164 | pydev
165 | pydsdl
166 | pydsdlgen
167 | pygments
168 | pylint
169 | pyserial
170 | pytest
171 | pythonasynciodebug
172 | pythoncan
173 | pythonpath
174 | pythonunbuffered
175 | pyuavcan
176 | pyyaml
177 | quantizer
178 | qube
179 | qwertyui
180 | qwertyuiop
181 | raii
182 | rankdir
183 | rawsource
184 | readlines
185 | readthedocs
186 | reasm
187 | reasms
188 | reassembler
189 | reassemblers
190 | rechunk
191 | recvfrom
192 | recvmsg
193 | refdoc
194 | refid
195 | refragment
196 | refragmented
197 | reftarget
198 | reftype
199 | reftypes
200 | relbar
201 | relbars
202 | representer
203 | representers
204 | repurposeability
205 | reuseport
206 | roundtrip
207 | rtfd
208 | rtps
209 | ruamel
210 | runas
211 | sapog
212 | searchbox
213 | sendable
214 | sendmsg
215 | sert
216 | serv
217 | setcap
218 | setpoint
219 | setsockopt
220 | sheremet
221 | signedness
222 | sitecustomize
223 | sitl
224 | slcan
225 | sockaddr
226 | socketcan
227 | socketcanfd
228 | sphinxarg
229 | sssss
230 | ssssssss
231 | stdint
232 | strictification
233 | subcommand
234 | subcommands
235 | sublayers
236 | submoduling
237 | subnets
238 | subparser
239 | suka
240 | supernum
241 | supremum
242 | swmaj
243 | swmin
244 | synth
245 | systeminfo
246 | telega
247 | tempfile
248 | templatedir
249 | testpaths
250 | thumby
251 | timestamping
252 | tobytes
253 | tocfile
254 | toctree
255 | todos
256 | tradeoff
257 | typecheck
258 | uart
259 | uavcan
260 | uber
261 | udpros
262 | udral
263 | ulong
264 | uname
265 | unconfigured
266 | undisable
267 | undoc
268 | unhashable
269 | unicast
270 | unseparate
271 | unstropped
272 | upvote
273 | usec
274 | usercustomize
275 | vcan
276 | versioning
277 | veyor
278 | virtualization
279 | voldemort
280 | vpaun
281 | vssc
282 | weakref
283 | winpcap
284 | wireshark
285 | worl
286 | wpcap
287 | xbee
288 | xfer
289 | zubax
290 |
291 |
292 |
--------------------------------------------------------------------------------
/differential_pressure_sensor/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # This software is distributed under the terms of the MIT License.
2 | # Copyright (C) 2021 OpenCyphal
3 | # Author: Pavel Kirienko
4 |
5 | cmake_minimum_required(VERSION 3.17)
6 | project(differential_pressure_sensor C)
7 |
8 | set(submodules "${CMAKE_CURRENT_SOURCE_DIR}/../submodules")
9 | set(CMAKE_PREFIX_PATH "${submodules}/nunavut")
10 |
11 | set(CMAKE_C_STANDARD 11)
12 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Werror -pedantic -fstrict-aliasing")
13 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wdouble-promotion -Wswitch-enum -Wfloat-equal -Wundef")
14 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wconversion -Wtype-limits")
15 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wsign-conversion -Wcast-align -Wmissing-declarations")
16 |
17 | # Forward the revision information to the compiler so that we could expose it at runtime. This is entirely optional.
18 | execute_process(
19 | COMMAND git rev-parse --short=16 HEAD
20 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
21 | OUTPUT_VARIABLE vcs_revision_id
22 | OUTPUT_STRIP_TRAILING_WHITESPACE
23 | )
24 | message(STATUS "vcs_revision_id: ${vcs_revision_id}")
25 | add_definitions(
26 | -DVERSION_MAJOR=1
27 | -DVERSION_MINOR=0
28 | -DVCS_REVISION_ID=0x${vcs_revision_id}ULL
29 | -DNODE_NAME="org.opencyphal.demos.differential_pressure"
30 | )
31 |
32 | ## Transpile DSDL into C using Nunavut. This uses this repo's built-in submodules to setup Nunavut. See
33 | # CMAKE_PREFIX_PATH above for how this is resolved to the local submodules.
34 | find_package(Nunavut 3.0 REQUIRED)
35 |
36 | set(LOCAL_PUBLIC_TYPES
37 | uavcan/node/430.GetInfo.1.0.dsdl
38 | uavcan/node/435.ExecuteCommand.1.1.dsdl
39 | uavcan/node/7509.Heartbeat.1.0.dsdl
40 | uavcan/node/port/7510.List.0.1.dsdl
41 | uavcan/pnp/8165.NodeIDAllocationData.2.0.dsdl
42 | uavcan/register/384.Access.1.0.dsdl
43 | uavcan/register/385.List.1.0.dsdl
44 | uavcan/si/unit/pressure/Scalar.1.0.dsdl
45 | uavcan/si/unit/temperature/Scalar.1.0.dsdl
46 | )
47 |
48 | add_cyphal_library(
49 | NAME dsdl_uavcan
50 | EXACT_NAME
51 | LANGUAGE c
52 | LANGUAGE_STANDARD c${CMAKE_C_STANDARD}
53 | DSDL_FILES ${LOCAL_PUBLIC_TYPES}
54 | SERIALIZATION_ASSERT assert
55 | EXPORT_MANIFEST
56 | OUT_LIBRARY_TARGET LOCAL_TYPES_C_LIBRARY
57 | )
58 |
59 | # Build libcanard.
60 | add_library(canard STATIC ${submodules}/libcanard/libcanard/canard.c)
61 | include_directories(SYSTEM ${submodules}/libcanard/libcanard)
62 |
63 | # Build o1heap -- a hard real-time deterministic memory allocator for embedded systems.
64 | add_library(o1heap STATIC ${submodules}/o1heap/o1heap/o1heap.c)
65 | include_directories(SYSTEM ${submodules}/o1heap/o1heap/)
66 |
67 | include(${CMAKE_CURRENT_SOURCE_DIR}/../shared/register/register.cmake)
68 | target_link_libraries(shared_register
69 | PRIVATE ${LOCAL_TYPES_C_LIBRARY}
70 | )
71 |
72 | include(${CMAKE_CURRENT_SOURCE_DIR}/../shared/socketcan/socketcan.cmake)
73 |
74 | # Build the application.
75 | add_executable(differential_pressure_sensor
76 | src/main.c
77 | )
78 | target_link_libraries(differential_pressure_sensor
79 | ${LOCAL_TYPES_C_LIBRARY}
80 | canard
81 | o1heap
82 | shared_register
83 | shared_socketcan
84 | )
85 |
--------------------------------------------------------------------------------
/differential_pressure_sensor/README.md:
--------------------------------------------------------------------------------
1 | # Differential pressure sensor demo
2 |
3 | ## Purpose
4 |
5 | This demo implements a simple differential pressure & static air temperature sensor node
6 | in a highly portable C application that can be trivially adapted to run in a baremetal environment.
7 | Unless ported, the demo is intended for evaluation on GNU/Linux.
8 |
9 | This demo supports only Cyphal/CAN at the moment, but it can be extended to support Cyphal/UDP or Cyphal/serial.
10 |
11 |
12 | ## Preparation
13 |
14 | You will need GNU/Linux, CMake, a C11 compiler, [Yakut](https://github.com/OpenCyphal/yakut),
15 | and [SocketCAN utils](https://github.com/linux-can/can-utils).
16 |
17 | Build the demo as follows:
18 |
19 | ```bash
20 | git clone --recursive https://github.com/OpenCyphal/demos
21 | cd demos/differential_pressure_sensor
22 | mkdir build && cd build
23 | cmake .. && make
24 | ```
25 |
26 |
27 | ## Running
28 |
29 | Set up a virtual CAN bus `vcan0`:
30 |
31 | ```bash
32 | modprobe can
33 | modprobe can_raw
34 | modprobe vcan
35 | ip link add dev vcan0 type vcan
36 | ip link set vcan0 mtu 72 # Enable CAN FD by configuring the MTU of 64+8
37 | ip link set up vcan0
38 | ```
39 |
40 | Launch the node
41 | (it is built to emulate an embedded system so it does not accept any arguments or environment variables):
42 |
43 | ```bash
44 | ./differential_pressure_sensor
45 | ```
46 |
47 | It may print a few informational messages and then go silent.
48 |
49 | Fire up the CAN dump utility from SocketCAN utils and see what's happening on the bus.
50 | You should see the PnP node-ID allocation requests being sent by our node irregularly:
51 |
52 | ```bash
53 | $ candump -decaxta vcan0
54 | (1616445708.288978) vcan0 TX B - 197FE510 [20] FF FF C6 69 73 51 FF 4A EC 29 CD BA AB F2 FB E3 46 7C 00 E9
55 | (1616445711.289044) vcan0 TX B - 197FE510 [20] FF FF C6 69 73 51 FF 4A EC 29 CD BA AB F2 FB E3 46 7C 00 EA
56 | # and so on...
57 | ```
58 |
59 | It will keep doing this forever until it got an allocation response from the node-ID allocator.
60 |
61 | Next, we launch a PnP node-ID allocator available in Yakut (PX4 also implements one):
62 |
63 | ```bash
64 | export UAVCAN__CAN__IFACE="socketcan:vcan0"
65 | export UAVCAN__NODE__ID=127 # This node-ID is for Yakut.
66 | y mon --plug-and-play ~/allocation_table.db
67 | ```
68 |
69 | This command will run the monitor together with the allocator.
70 | You will see our node get itself a node-ID allocated,
71 | then roughly the following picture should appear on the monitor:
72 |
73 |
74 |
75 | That means that our node is running, but it is unable to publish measurements because the respective subjects
76 | remain unconfigured.
77 | So let's configure them (do not stop the monitor though, otherwise you won't know what's happening on the bus),
78 | assuming that the node got allocated the node-ID of 125.
79 | First, it helps to know what registers are available at all:
80 |
81 | ```bash
82 | $ export UAVCAN__CAN__IFACE="socketcan:vcan0"
83 | $ export UAVCAN__NODE__ID=126 # This node-ID is for Yakut.
84 | $ y rl 125
85 | [reg.udral.service.pitot, uavcan.can.mtu, uavcan.node.description, uavcan.node.id, uavcan.node.unique_id, uavcan.pub.airspeed.differential_pressure.id, uavcan.pub.airspeed.differential_pressure.type, uavcan.pub.airspeed.static_air_temperature.id, uavcan.pub.airspeed.static_air_temperature.type, udral.pnp.cookie]
86 | $ y rl 125, | y rb # You can also read all registers like this
87 | # (output not shown)
88 | ```
89 |
90 | Configure the subject-IDs:
91 |
92 | ```bash
93 | y r 125 uavcan.pub.airspeed.differential_pressure.id 100
94 | y r 125 uavcan.pub.airspeed.static_air_temperature.id 101
95 | ```
96 |
97 | The node is configured now, but we need to restart it before the configuration parameter changes take effect:
98 |
99 | ```bash
100 | y cmd 125 restart -e
101 | ```
102 |
103 | You should see candump start printing a lot more frames because the demo is now publishing the sensor data.
104 | The monitor will also show the subjects that we just configured.
105 |
106 |
107 |
108 | You can subscribe to the published differential pressure using Yakut as follows:
109 |
110 | ```bash
111 | y sub 100:uavcan.si.unit.pressure.scalar
112 | ```
113 |
114 | You can erase the configuration and go back to factory defaults as follows:
115 |
116 | ```bash
117 | y cmd 125 factory_reset
118 | ```
119 |
120 |
121 | ## Porting
122 |
123 | Just read the code.
124 |
125 | The files `socketcan.[ch]` were taken from .
126 | You may (or may not) find something relevant for your target platform there, too.
127 |
--------------------------------------------------------------------------------
/differential_pressure_sensor/docs/monitor-initial.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenCyphal-Garage/demos/87741d8242bcb27b39e22115559a4b91e92ffe06/differential_pressure_sensor/docs/monitor-initial.png
--------------------------------------------------------------------------------
/differential_pressure_sensor/docs/monitor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenCyphal-Garage/demos/87741d8242bcb27b39e22115559a4b91e92ffe06/differential_pressure_sensor/docs/monitor.png
--------------------------------------------------------------------------------
/libcyphal_demo/.clang-format:
--------------------------------------------------------------------------------
1 | ---
2 | Language: Cpp
3 | BasedOnStyle: LLVM
4 | AccessModifierOffset: -4
5 | AlignAfterOpenBracket: Align
6 | AlignConsecutiveAssignments: true
7 | AlignConsecutiveDeclarations: true
8 | AlignEscapedNewlines: Left
9 | AlignOperands: true
10 | AlignTrailingComments: true
11 | AllowAllParametersOfDeclarationOnNextLine: false
12 | AllowShortBlocksOnASingleLine: Never
13 | AllowShortCaseLabelsOnASingleLine: false
14 | AllowShortFunctionsOnASingleLine: Empty
15 | AllowShortIfStatementsOnASingleLine: false
16 | AllowShortLoopsOnASingleLine: false
17 | AlwaysBreakAfterDefinitionReturnType: None
18 | AlwaysBreakAfterReturnType: None
19 | AlwaysBreakBeforeMultilineStrings: false
20 | AlwaysBreakTemplateDeclarations: Yes
21 | BinPackArguments: false
22 | BinPackParameters: false
23 | BreakBeforeBinaryOperators: None
24 | BreakBeforeBraces: Custom
25 | BraceWrapping:
26 | SplitEmptyRecord: false
27 | AfterEnum: true
28 | AfterStruct: true
29 | AfterClass: true
30 | AfterControlStatement: true
31 | AfterFunction: true
32 | AfterUnion: true
33 | AfterNamespace: true
34 | AfterExternBlock: true
35 | BeforeElse: true
36 |
37 | BreakBeforeTernaryOperators: true
38 | BreakConstructorInitializers: BeforeComma
39 | BreakStringLiterals: true
40 | ColumnLimit: 120
41 | CommentPragmas: '^ (coverity|NOSONAR|pragma:)'
42 | CompactNamespaces: false
43 | ConstructorInitializerAllOnOneLineOrOnePerLine: false
44 | ConstructorInitializerIndentWidth: 4
45 | ContinuationIndentWidth: 4
46 | Cpp11BracedListStyle: true
47 | DerivePointerAlignment: false
48 | DisableFormat: false
49 | FixNamespaceComments: true
50 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ]
51 | IncludeBlocks: Preserve
52 | IndentCaseLabels: false
53 | IndentPPDirectives: AfterHash
54 | IndentWidth: 4
55 | IndentWrappedFunctionNames: false
56 | KeepEmptyLinesAtTheStartOfBlocks: false
57 | MacroBlockBegin: ''
58 | MacroBlockEnd: ''
59 | MaxEmptyLinesToKeep: 1
60 | NamespaceIndentation: None
61 | PenaltyBreakAssignment: 2
62 | PenaltyBreakBeforeFirstCallParameter: 10000 # Raised intentionally; prefer breaking all
63 | PenaltyBreakComment: 300
64 | PenaltyBreakFirstLessLess: 120
65 | PenaltyBreakString: 1000
66 | PenaltyExcessCharacter: 1000000
67 | PenaltyReturnTypeOnItsOwnLine: 10000 # Raised intentionally because it hurts readability
68 | PointerAlignment: Left
69 | ReflowComments: true
70 | SortIncludes: Never
71 | SortUsingDeclarations: false
72 | SpaceAfterCStyleCast: true
73 | SpaceAfterTemplateKeyword: true
74 | SpaceBeforeAssignmentOperators: true
75 | SpaceBeforeParens: ControlStatements
76 | SpaceInEmptyParentheses: false
77 | SpacesBeforeTrailingComments: 2
78 | SpacesInAngles: false
79 | SpacesInCStyleCastParentheses: false
80 | SpacesInContainerLiterals: false
81 | SpacesInParentheses: false
82 | SpacesInSquareBrackets: false
83 | Standard: c++14
84 | TabWidth: 8
85 | UseTab: Never
86 | ...
87 |
--------------------------------------------------------------------------------
/libcyphal_demo/.clang-tidy:
--------------------------------------------------------------------------------
1 | Checks: >-
2 | boost-*,
3 | bugprone-*,
4 | cert-*,
5 | clang-analyzer-*,
6 | cppcoreguidelines-*,
7 | google-*,
8 | hicpp-*,
9 | llvm-*,
10 | misc-*,
11 | modernize-*,
12 | performance-*,
13 | portability-*,
14 | readability-*,
15 | -clang-analyzer-core.uninitialized.Assign,
16 | -cppcoreguidelines-avoid-const-or-ref-data-members,
17 | -cppcoreguidelines-use-default-member-init,
18 | -google-readability-avoid-underscore-in-googletest-name,
19 | -google-readability-todo,
20 | -llvm-header-guard,
21 | -modernize-concat-nested-namespaces,
22 | -modernize-type-traits,
23 | -modernize-use-constraints,
24 | -modernize-use-default-member-init,
25 | -modernize-use-nodiscard,
26 | -readability-avoid-const-params-in-decls,
27 | -readability-identifier-length,
28 | -*-use-trailing-return-type,
29 | -*-named-parameter,
30 | CheckOptions:
31 | - key: readability-function-cognitive-complexity.Threshold
32 | value: '90'
33 | - key: readability-magic-numbers.IgnoredIntegerValues
34 | value: '1;2;3;4;5;8;10;16;20;32;60;64;100;128;256;500;512;1000'
35 | WarningsAsErrors: '*'
36 | FormatStyle: file
37 |
--------------------------------------------------------------------------------
/libcyphal_demo/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # This software is distributed under the terms of the MIT License.
2 | # Copyright (C) OpenCyphal Development Team
3 | # Copyright Amazon.com Inc. or its affiliates.
4 | # SPDX-License-Identifier: MIT
5 | # Author: Sergei Shirokov
6 |
7 | cmake_minimum_required(VERSION 3.25)
8 |
9 | project(libcyphal_demo
10 | LANGUAGES CXX C
11 | HOMEPAGE_URL https://github.com/OpenCyphal-Garage/libcyphal)
12 |
13 | set(CMAKE_CXX_STANDARD "14" CACHE STRING "C++ standard to use when compiling.")
14 | set(DISABLE_CPP_EXCEPTIONS ON CACHE STRING "Disable C++ exceptions.")
15 |
16 | option(CETL_ENABLE_DEBUG_ASSERT "Enable or disable runtime CETL asserts." ON)
17 |
18 | set(CXX_FLAG_SET "")
19 | if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
20 | if (DISABLE_CPP_EXCEPTIONS)
21 | message(STATUS "DISABLE_CPP_EXCEPTIONS is true. Adding -fno-exceptions to compiler flags.")
22 | list(APPEND CXX_FLAG_SET "-fno-exceptions")
23 | endif()
24 | endif()
25 | if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
26 | # Disable PSABI warnings in GCC (on RPi).
27 | list(APPEND CXX_FLAG_SET "-Wno-psabi")
28 | endif()
29 | add_compile_options("$<$:${CXX_FLAG_SET}>")
30 |
31 | if (CETL_ENABLE_DEBUG_ASSERT)
32 | add_compile_definitions("CETL_ENABLE_DEBUG_ASSERT=1")
33 | endif()
34 |
35 | # Set the output binary directory
36 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
37 |
38 | set(submodules_dir "${CMAKE_SOURCE_DIR}/../submodules")
39 |
40 | # Set up static analysis.
41 | set(STATIC_ANALYSIS ON CACHE BOOL "enable static analysis")
42 | if (STATIC_ANALYSIS)
43 | # clang-tidy (separate config files per directory)
44 | find_program(clang_tidy NAMES clang-tidy)
45 | if (NOT clang_tidy)
46 | message(WARNING "Could not locate clang-tidy")
47 | endif ()
48 | message(STATUS "Using clang-tidy: ${clang_tidy}")
49 | endif ()
50 |
51 | # Pull in Nunavut's cmake integration
52 | find_package("Nunavut" 3.0 REQUIRED)
53 |
54 | # libcyphal requires PMR support for Nunavut generated code.
55 | if (${CMAKE_CXX_STANDARD} STREQUAL "14")
56 | set(CYPHAL_LANGUAGE_STANDARD "cetl++14-17")
57 | else ()
58 | set(CYPHAL_LANGUAGE_STANDARD "c++${CMAKE_CXX_STANDARD}-pmr")
59 | endif ()
60 |
61 | # Forward the revision information to the compiler so that we could expose it at runtime. This is entirely optional.
62 | execute_process(
63 | COMMAND git rev-parse --short=16 HEAD
64 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
65 | OUTPUT_VARIABLE vcs_revision_id
66 | OUTPUT_STRIP_TRAILING_WHITESPACE
67 | )
68 | message(STATUS "vcs_revision_id: ${vcs_revision_id}")
69 | add_definitions(
70 | -DVERSION_MAJOR=1
71 | -DVERSION_MINOR=0
72 | -DVCS_REVISION_ID=0x${vcs_revision_id}ULL
73 | -DNODE_NAME="org.opencyphal.demos.libcyphal"
74 | )
75 | if (DEFINED PLATFORM_OS_TYPE)
76 | if (${PLATFORM_OS_TYPE} STREQUAL "bsd")
77 | add_definitions(-DPLATFORM_OS_TYPE_BSD)
78 | elseif (${PLATFORM_OS_TYPE} STREQUAL "linux")
79 | add_definitions(-DPLATFORM_OS_TYPE_LINUX)
80 | endif ()
81 | endif ()
82 |
83 | add_definitions(-DNUNAVUT_ASSERT=assert)
84 |
85 | # Define the LibUDPard static library build target.
86 | add_library(udpard STATIC ${submodules_dir}/libudpard/libudpard/udpard.c)
87 | target_include_directories(udpard INTERFACE SYSTEM ${submodules_dir}/libudpard/libudpard)
88 | include(${CMAKE_SOURCE_DIR}/../shared/udp/udp.cmake)
89 |
90 | if (${PLATFORM_OS_TYPE} STREQUAL "linux")
91 | # Define the LibCANard static library build target.
92 | add_library(canard STATIC ${submodules_dir}/libcanard/libcanard/canard.c)
93 | target_include_directories(canard INTERFACE SYSTEM ${submodules_dir}/libcanard/libcanard)
94 | include(${CMAKE_SOURCE_DIR}/../shared/socketcan/socketcan.cmake)
95 | endif ()
96 |
97 | # Build o1heap -- a hard real-time deterministic memory allocator for embedded systems.
98 | add_library(o1heap STATIC ${submodules_dir}/o1heap/o1heap/o1heap.c)
99 | target_include_directories(o1heap INTERFACE SYSTEM ${submodules_dir}/o1heap/o1heap)
100 |
101 | add_subdirectory(src)
102 |
--------------------------------------------------------------------------------
/libcyphal_demo/CMakePresets.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 6,
3 | "cmakeMinimumRequired": {
4 | "major": 3,
5 | "minor": 25,
6 | "patch": 0
7 | },
8 | "configurePresets": [
9 | {
10 | "name": "config-common",
11 | "hidden": true,
12 | "description": "Common configuration",
13 | "generator": "Ninja Multi-Config",
14 | "binaryDir": "${sourceDir}/build",
15 | "warnings": {
16 | "deprecated": true,
17 | "uninitialized": true
18 | },
19 | "cacheVariables": {
20 | "CMAKE_EXPORT_COMPILE_COMMANDS": "ON",
21 | "CMAKE_CONFIGURATION_TYPES": "Release;Debug",
22 | "CMAKE_CROSS_CONFIGS": "all",
23 | "CMAKE_DEFAULT_BUILD_TYPE": "Release",
24 | "CMAKE_DEFAULT_CONFIGS": "Release",
25 | "CMAKE_PREFIX_PATH": "${sourceDir}/../submodules/nunavut",
26 | "CMAKE_CXX_FLAGS": "-DCETL_ENABLE_DEBUG_ASSERT=1"
27 | }
28 | },
29 | {
30 | "name": "config-linux",
31 | "hidden": true,
32 | "cacheVariables": {
33 | "PLATFORM_OS_TYPE": "linux"
34 | }
35 | },
36 | {
37 | "name": "config-bsd",
38 | "hidden": true,
39 | "cacheVariables": {
40 | "PLATFORM_OS_TYPE": "bsd"
41 | }
42 | },
43 | {
44 | "name": "Demo-Linux",
45 | "displayName": "Linux Demo",
46 | "description": "Configures Demo for Linux.",
47 | "inherits": [
48 | "config-common",
49 | "config-linux"
50 | ]
51 | },
52 | {
53 | "name": "Demo-Linux-Coverage",
54 | "displayName": "Linux Demo (Coverage)",
55 | "description": "Configures Demo for Linux with coverage.",
56 | "inherits": [
57 | "config-common",
58 | "config-linux"
59 | ],
60 | "binaryDir": "${sourceDir}/cmake-build-coverage",
61 | "cacheVariables": {
62 | "CMAKE_C_FLAGS": "--coverage",
63 | "CMAKE_CXX_FLAGS": "--coverage",
64 | "NO_STATIC_ANALYSIS": "ON"
65 | }
66 | },
67 | {
68 | "name": "Demo-BSD",
69 | "displayName": "BSD Demo",
70 | "description": "Configures Demo for BSD",
71 | "inherits": [
72 | "config-common",
73 | "config-bsd"
74 | ],
75 | "cacheVariables": {
76 | "CMAKE_C_COMPILER": "clang",
77 | "CMAKE_CXX_COMPILER": "clang++"
78 | }
79 | }
80 | ],
81 | "buildPresets": [
82 | {
83 | "name": "Demo-Linux-Debug",
84 | "displayName": "Linux Demo (Debug)",
85 | "description": "Builds Demo for Linux",
86 | "configurePreset": "Demo-Linux",
87 | "configuration": "Debug"
88 | },
89 | {
90 | "name": "Demo-Linux-Debug-Coverage",
91 | "displayName": "Linux Demo (Debug, Coverage)",
92 | "description": "Builds Demo for Linux with coverage",
93 | "configurePreset": "Demo-Linux-Coverage",
94 | "configuration": "Debug"
95 | },
96 | {
97 | "name": "Demo-Linux-Release",
98 | "displayName": "Linux Demo (Release)",
99 | "description": "Builds Demo for Linux",
100 | "configurePreset": "Demo-Linux",
101 | "configuration": "Release"
102 | },
103 | {
104 | "name": "Demo-BSD-Debug",
105 | "displayName": "BSD Demo (Debug)",
106 | "description": "Builds Demo for BSD",
107 | "configurePreset": "Demo-BSD",
108 | "configuration": "Debug"
109 | },
110 | {
111 | "name": "Demo-BSD-Release",
112 | "displayName": "BSD Demo (Release)",
113 | "description": "Builds Demo for BSD",
114 | "configurePreset": "Demo-BSD",
115 | "configuration": "Release"
116 | }
117 | ],
118 | "testPresets": [
119 | {
120 | "name": "Demo-Debug",
121 | "displayName": "Test Demo (Debug)",
122 | "description": "Tests Demo",
123 | "configurePreset": "Demo-Linux",
124 | "configuration": "Debug"
125 | },
126 | {
127 | "name": "Demo-Release",
128 | "displayName": "Test Demo (Release)",
129 | "description": "Tests Demo",
130 | "configurePreset": "Demo-Linux",
131 | "configuration": "Release"
132 | }
133 | ]
134 | }
135 |
--------------------------------------------------------------------------------
/libcyphal_demo/README.md:
--------------------------------------------------------------------------------
1 | # LibCyphal demo application
2 |
3 | This demo application is a usage demonstrator for [LibCyphal](https://github.com/OpenCyphal-Garage/libcyphal) ---
4 | a compact multi-transport Cyphal implementation for high-integrity systems written in C++14 and above.
5 | It implements a simple Cyphal node that showcases the following features:
6 |
7 | - Fixed port-ID and non-fixed port-ID publishers.
8 | - Fixed port-ID and non-fixed port-ID subscribers.
9 | - Fixed port-ID RPC server.
10 | - Plug-and-play node-ID allocation unless it is configured statically.
11 | - Fast Cyphal Register API and non-volatile storage for the persistent registers.
12 | - Support for redundant network interfaces.
13 |
14 | This document will walk you through the process of building, running, and evaluating the demo
15 | on a GNU/Linux-based OS.
16 | It can be easily ported to another platform, such as a baremetal MCU,
17 | by replacing the POSIX socket API and stdio with suitable alternatives;
18 | for details, please consult with:
19 | - `platform/posix/udp/*` files regarding UDP transport
20 | - `platform/linux/can/*` files regarding CAN transport
21 | - `platform/linux/epoll_single_threaded_executor.hpp` file regarding the executor using epoll (Debian)
22 | - `platform/linux/kqueue_single_threaded_executor.hpp` file regarding the executor using kqueue (BSD/Darwin)
23 | - `platform/storage.hpp` file regarding the non-volatile storage
24 | - `platform/o1_heap_memory_resource.hpp` file regarding the memory resource
25 |
26 | ## Preparation
27 |
28 | You will need GNU/Linux, CMake, a C++14 compiler.
29 | Install:
30 | - [Yakut](https://github.com/OpenCyphal/yakut) CLI tool,
31 | - For CAN transport [SocketCAN utils](https://github.com/linux-can/can-utils)
32 | - For UDP transport Wireshark with the [Cyphal plugins](https://github.com/OpenCyphal/wireshark_plugins)
33 |
34 | Build the demo:
35 |
36 | ```shell
37 | git clone --recursive https://github.com/OpenCyphal/demos
38 | cd demos/libcyphal_demo
39 | ```
40 | Then one of the two presets depending on your system:
41 |
42 | - `Demo-Linux` - Debian-based Linux distros like Ubuntu.
43 | - `Demo-BSD` – Mac OS
44 |
45 | ```
46 | cmake --preset Demo-Linux
47 | cmake --build --preset Demo-Linux-Debug
48 | ```
49 |
--------------------------------------------------------------------------------
/libcyphal_demo/src/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # This software is distributed under the terms of the MIT License.
2 | # Copyright (C) OpenCyphal Development Team
3 | # Copyright Amazon.com Inc. or its affiliates.
4 | # SPDX-License-Identifier: MIT
5 | # Author: Sergei Shirokov
6 |
7 | cmake_minimum_required(VERSION 3.25)
8 |
9 | # Define type generation and header library all in one go.
10 | #
11 | set(dsdl_types_in_demo # List all the DSDL types used in the engine
12 | uavcan/file/405.GetInfo.0.2.dsdl
13 | uavcan/file/408.Read.1.1.dsdl
14 | uavcan/node/430.GetInfo.1.0.dsdl
15 | uavcan/node/435.ExecuteCommand.1.3.dsdl
16 | uavcan/node/7509.Heartbeat.1.0.dsdl
17 | uavcan/register/384.Access.1.0.dsdl
18 | uavcan/register/385.List.1.0.dsdl
19 | )
20 | add_cyphal_library(
21 | NAME demo
22 | DSDL_FILES ${dsdl_types_in_demo}
23 | ALLOW_EXPERIMENTAL_LANGUAGES
24 | LANGUAGE cpp
25 | LANGUAGE_STANDARD ${CYPHAL_LANGUAGE_STANDARD}
26 | OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/dsdl_transpiled
27 | OUT_LIBRARY_TARGET demo_transpiled
28 | )
29 |
30 | # Define the demo application build target and link it with the library.
31 | add_executable(demo
32 | ${CMAKE_SOURCE_DIR}/src/application.cpp
33 | ${CMAKE_SOURCE_DIR}/src/main.cpp
34 | ${CMAKE_SOURCE_DIR}/src/no_cpp_heap.cpp
35 | )
36 |
37 | target_link_libraries(demo
38 | PRIVATE o1heap udpard shared_udp
39 | PRIVATE ${demo_transpiled}
40 | )
41 | if (${PLATFORM_OS_TYPE} STREQUAL "linux")
42 | target_link_libraries(demo
43 | PRIVATE canard shared_socketcan
44 | )
45 | endif ()
46 |
47 | target_include_directories(demo PRIVATE ${CMAKE_SOURCE_DIR}/src)
48 | target_include_directories(demo PRIVATE ${submodules_dir}/cetl/include)
49 | target_include_directories(demo PRIVATE ${submodules_dir}/libcyphal/include)
50 |
51 | if (STATIC_ANALYSIS)
52 | set_target_properties(demo PROPERTIES C_CLANG_TIDY "${clang_tidy}")
53 | endif ()
54 |
--------------------------------------------------------------------------------
/libcyphal_demo/src/any_transport_bag.hpp:
--------------------------------------------------------------------------------
1 | // This software is distributed under the terms of the MIT License.
2 | // Copyright (C) OpenCyphal Development Team
3 | // Copyright Amazon.com Inc. or its affiliates.
4 | // SPDX-License-Identifier: MIT
5 | // Author: Sergei Shirokov
6 |
7 | #ifndef ANY_TRANSPORT_BAG_HPP_INCLUDED
8 | #define ANY_TRANSPORT_BAG_HPP_INCLUDED
9 |
10 | #include
11 | #include
12 |
13 | /// Represents storage of some (UDP, CAN) libcyphal transport and its media.
14 | ///
15 | class AnyTransportBag
16 | {
17 | public:
18 | using Ptr = libcyphal::UniquePtr;
19 | using Transport = libcyphal::transport::ITransport;
20 |
21 | AnyTransportBag(const AnyTransportBag&) = delete;
22 | AnyTransportBag(AnyTransportBag&&) noexcept = delete;
23 | AnyTransportBag& operator=(const AnyTransportBag&) = delete;
24 | AnyTransportBag& operator=(AnyTransportBag&&) noexcept = delete;
25 |
26 | virtual Transport& getTransport() const = 0;
27 |
28 | protected:
29 | AnyTransportBag() = default;
30 | ~AnyTransportBag() = default;
31 |
32 | }; // AnyTransportBag
33 |
34 | #endif // ANY_TRANSPORT_BAG_HPP_INCLUDED
35 |
--------------------------------------------------------------------------------
/libcyphal_demo/src/application.cpp:
--------------------------------------------------------------------------------
1 | // This software is distributed under the terms of the MIT License.
2 | // Copyright (C) OpenCyphal Development Team
3 | // Copyright Amazon.com Inc. or its affiliates.
4 | // SPDX-License-Identifier: MIT
5 | // Author: Sergei Shirokov
6 |
7 | #include "application.hpp"
8 |
9 | #include
10 | #include
11 |
12 | #include
13 | #include
14 |
15 | #include
16 | #include
17 | #include
18 | #include
19 | #include
20 | #include
21 | #include // for std::stoul
22 |
23 | namespace
24 | {
25 |
26 | constexpr std::size_t HeapSize = 128ULL * 1024ULL;
27 | alignas(O1HEAP_ALIGNMENT) std::array s_heap_arena{}; // NOLINT
28 |
29 | constexpr std::size_t BlockHeapSize = 128ULL * 1024ULL;
30 | alignas(O1HEAP_ALIGNMENT) std::array s_block_heap_arena{}; // NOLINT
31 |
32 | } // namespace
33 |
34 | Application::Application(const char* const root_path)
35 | : o1_heap_mr_{s_heap_arena}
36 | , o1_block_heap_mr_{s_block_heap_arena}
37 | , media_block_mr_{o1_block_heap_mr_}
38 | , storage_{root_path}
39 | , registry_{o1_heap_mr_}
40 | , regs_{o1_heap_mr_, registry_, media_block_mr_}
41 | {
42 | cetl::pmr::set_default_resource(&o1_heap_mr_);
43 |
44 | load(storage_, registry_);
45 |
46 | // Maybe override some of the registry values with environment variables.
47 | //
48 | auto iface_params = getIfaceParams();
49 | if (const auto* const iface_addresses_str = std::getenv("CYPHAL__UDP__IFACE"))
50 | {
51 | iface_params.udp_iface.value() = iface_addresses_str;
52 | }
53 | if (const auto* const iface_mtu_str = std::getenv("CYPHAL__UDP__MTU"))
54 | {
55 | iface_params.udp_mtu.value()[0] = static_cast(std::stoul(iface_mtu_str));
56 | }
57 | if (const auto* const iface_addresses_str = std::getenv("CYPHAL__CAN__IFACE"))
58 | {
59 | iface_params.can_iface.value() = iface_addresses_str;
60 | }
61 | if (const auto* const iface_mtu_str = std::getenv("CYPHAL__CAN__MTU"))
62 | {
63 | iface_params.can_mtu.value()[0] = static_cast(std::stoul(iface_mtu_str));
64 | }
65 | auto node_params = getNodeParams();
66 | if (const auto* const node_id_str = std::getenv("CYPHAL__NODE__ID"))
67 | {
68 | node_params.id.value()[0] = static_cast(std::stoul(node_id_str));
69 | }
70 | }
71 |
72 | Application::~Application()
73 | {
74 | save(storage_, registry_);
75 |
76 | const auto o1_diag = o1_heap_mr_.queryDiagnostics();
77 | std::cout << "O(1) Heap diagnostics:" << "\n"
78 | << " capacity=" << o1_diag.capacity << "\n"
79 | << " allocated=" << o1_diag.allocated << "\n"
80 | << " peak_allocated=" << o1_diag.peak_allocated << "\n"
81 | << " peak_request_size=" << o1_diag.peak_request_size << "\n"
82 | << " oom_count=" << o1_diag.oom_count << "\n";
83 |
84 | const auto blk_diag = media_block_mr_.queryDiagnostics();
85 | std::cout << "Media block memory diagnostics:" << "\n"
86 | << " capacity=" << blk_diag.capacity << "\n"
87 | << " allocated=" << blk_diag.allocated << "\n"
88 | << " peak_allocated=" << blk_diag.peak_allocated << "\n"
89 | << " block_size=" << blk_diag.block_size << "\n"
90 | << " oom_count=" << blk_diag.oom_count << "\n";
91 |
92 | cetl::pmr::set_default_resource(cetl::pmr::new_delete_resource());
93 | }
94 |
95 | /// Returns the 128-bit unique-ID of the local node. This value is used in `uavcan.node.GetInfo.Response`.
96 | ///
97 | Application::UniqueId Application::getUniqueId()
98 | {
99 | UniqueId out_unique_id = {};
100 |
101 | const auto result = storage_.get(".unique_id", out_unique_id);
102 | if (cetl::get_if(&result) != nullptr)
103 | {
104 | std::random_device rd; // Seed for the random number engine
105 | std::mt19937 gen{rd()}; // Mersenne Twister engine
106 | std::uniform_int_distribution dis{0, 255}; // Distribution range for bytes
107 |
108 | // Populate the default; it is only used at the first run.
109 | for (auto& b : out_unique_id)
110 | {
111 | b = dis(gen);
112 | }
113 |
114 | (void) storage_.put(".unique_id", out_unique_id);
115 | }
116 |
117 | return out_unique_id;
118 | }
119 |
120 | Application::Regs::Value Application::Regs::getSysInfoMemBlock() const
121 | {
122 | Value value{{&o1_heap_mr_}};
123 | auto& uint64s = value.set_natural64();
124 |
125 | const auto diagnostics = media_block_mr_.queryDiagnostics();
126 | uint64s.value.reserve(5); // NOLINT five fields gonna push
127 | uint64s.value.push_back(diagnostics.capacity);
128 | uint64s.value.push_back(diagnostics.allocated);
129 | uint64s.value.push_back(diagnostics.peak_allocated);
130 | uint64s.value.push_back(diagnostics.block_size);
131 | uint64s.value.push_back(diagnostics.oom_count);
132 |
133 | return value;
134 | }
135 |
136 | Application::Regs::Value Application::Regs::getSysInfoMemGeneral() const
137 | {
138 | Value value{{&o1_heap_mr_}};
139 | auto& uint64s = value.set_natural64();
140 |
141 | const auto diagnostics = o1_heap_mr_.queryDiagnostics();
142 | uint64s.value.reserve(5); // NOLINT five fields gonna push
143 | uint64s.value.push_back(diagnostics.capacity);
144 | uint64s.value.push_back(diagnostics.allocated);
145 | uint64s.value.push_back(diagnostics.peak_allocated);
146 | uint64s.value.push_back(diagnostics.peak_request_size);
147 | uint64s.value.push_back(diagnostics.oom_count);
148 |
149 | return value;
150 | }
151 |
--------------------------------------------------------------------------------
/libcyphal_demo/src/exec_cmd_provider.hpp:
--------------------------------------------------------------------------------
1 | // This software is distributed under the terms of the MIT License.
2 | // Copyright (C) OpenCyphal Development Team
3 | // Copyright Amazon.com Inc. or its affiliates.
4 | // SPDX-License-Identifier: MIT
5 | // Author: Sergei Shirokov
6 |
7 | #ifndef COMMAND_PROVIDER_HPP_INCLUDED
8 | #define COMMAND_PROVIDER_HPP_INCLUDED
9 |
10 | #include "libcyphal/application/node.hpp"
11 | #include "libcyphal/presentation/presentation.hpp"
12 | #include "libcyphal/presentation/server.hpp"
13 | #include "libcyphal/time_provider.hpp"
14 | #include "libcyphal/transport/types.hpp"
15 | #include "libcyphal/types.hpp"
16 |
17 | #include
18 |
19 | #include
20 |
21 | #include
22 | #include
23 |
24 | /// Defines 'ExecuteCommand' provider component for the application node.
25 | ///
26 | /// Internally, it uses the 'ExecuteCommand' service server to handle incoming requests.
27 | ///
28 | /// No Sonar cpp:S4963 'The "Rule-of-Zero" should be followed'
29 | /// b/c we do directly handle resources here (namely capturing of `this` in the request callback).
30 | ///
31 | template
32 | class ExecCmdProvider // NOSONAR cpp:S4963
33 | {
34 | public:
35 | using Service = uavcan::node::ExecuteCommand_1_3;
36 | using Server = libcyphal::presentation::ServiceServer;
37 |
38 | /// Defines the response type for the ExecuteCommand provider.
39 | ///
40 | using Response = Service::Response;
41 |
42 | /// Defines the request type for the ExecuteCommand provider.
43 | ///
44 | using Request = Service::Request;
45 |
46 | /// Typealias to the request command type of the ExecuteCommand provider.
47 | ///
48 | /// `std::uint16_t` is used as the command type.
49 | // Use `Request::COMMAND_XXX` constants to access standard command values.
50 | ///
51 | using Command = Request::_traits_::TypeOf::command;
52 |
53 | /// Factory method to create a ExecuteCommand instance.
54 | ///
55 | /// @param node The application layer node instance. In use to access heartbeat producer.
56 | /// @param presentation The presentation layer instance. In use to create 'ExecuteCommand' service server.
57 | /// @param time_provider The time provider - in use to calculate RPC call deadlines.
58 | /// @return The ExecuteCommand provider instance or a failure.
59 | ///
60 | static auto make(libcyphal::application::Node& node,
61 | libcyphal::presentation::Presentation& presentation,
62 | libcyphal::ITimeProvider& time_provider)
63 | -> libcyphal::Expected
64 | {
65 | auto maybe_srv = presentation.makeServer();
66 | if (auto* const failure = cetl::get_if(&maybe_srv))
67 | {
68 | return std::move(*failure);
69 | }
70 |
71 | return Derived{node, presentation, time_provider, cetl::get(std::move(maybe_srv))};
72 | }
73 |
74 | ExecCmdProvider(ExecCmdProvider&& other) noexcept
75 | : alloc_{other.alloc_}
76 | , server_{std::move(other.server_)}
77 | , response_timeout_{other.response_timeout_}
78 | {
79 | // We have to set up request callback again (b/c it captures its own `this` pointer),
80 | setupOnRequestCallback();
81 | }
82 |
83 | virtual ~ExecCmdProvider() = default;
84 |
85 | ExecCmdProvider(const ExecCmdProvider&) = delete;
86 | ExecCmdProvider& operator=(const ExecCmdProvider&) = delete;
87 | ExecCmdProvider& operator=(ExecCmdProvider&&) noexcept = delete;
88 |
89 | /// Sets the response transmission timeout (default is 1s).
90 | ///
91 | /// @param timeout Duration of the response transmission timeout. Applied for the next response transmission.
92 | ///
93 | void setResponseTimeout(const libcyphal::Duration& timeout) noexcept
94 | {
95 | response_timeout_ = timeout;
96 | }
97 |
98 | /// Handles incoming command requests.
99 | ///
100 | /// This method is called by the service server when a new request is received.
101 | /// The user should override the method to handle custom commands.
102 | /// If the method returns `false`, the server will respond with a `STATUS_BAD_COMMAND` status.
103 | ///
104 | /// @param command The command to be executed.
105 | /// @param parameter The command parameter.
106 | /// @param metadata The transport RX metadata.
107 | /// @param response The response to be sent back to the requester.
108 | ///
109 | virtual bool onCommand(const Request::_traits_::TypeOf::command command,
110 | const cetl::string_view parameter,
111 | const libcyphal::transport::ServiceRxMetadata& metadata,
112 | Response& response) noexcept
113 | {
114 | (void) command;
115 | (void) parameter;
116 | (void) metadata;
117 | (void) response;
118 |
119 | return false;
120 | }
121 |
122 | protected:
123 | ExecCmdProvider(const libcyphal::presentation::Presentation& presentation, Server&& server)
124 | : alloc_{&presentation.memory()}
125 | , server_{std::move(server)}
126 | , response_timeout_{std::chrono::seconds{1}}
127 | {
128 | setupOnRequestCallback();
129 | }
130 |
131 | private:
132 | void setupOnRequestCallback()
133 | {
134 | server_.setOnRequestCallback([this](const auto& arg, auto continuation) {
135 | //
136 | Response response{alloc_};
137 | if (!onCommand(arg.request.command, makeStringView(arg.request.parameter), arg.metadata, response))
138 | {
139 | response.status = Response::STATUS_BAD_COMMAND;
140 | }
141 |
142 | // There is nothing we can do about possible continuation failures - we just ignore them.
143 | // TODO: Introduce error handler at the node level.
144 | (void) continuation(arg.approx_now + response_timeout_, response);
145 | });
146 | }
147 |
148 | /// Makes a new string view from request string parameter.
149 | ///
150 | static cetl::string_view makeStringView(const Request::_traits_::TypeOf::parameter& container)
151 | {
152 | // No Lint and Sonar cpp:S3630 "reinterpret_cast" should not be used" b/c we need to access container raw data.
153 | // NOLINTNEXTLINE(*-pro-type-reinterpret-cast)
154 | return {reinterpret_cast(container.data()), container.size()}; // NOSONAR
155 | }
156 |
157 | // MARK: Data members:
158 |
159 | Response::allocator_type alloc_;
160 | Server server_;
161 | libcyphal::Duration response_timeout_;
162 |
163 | }; // ExecCmdProvider
164 |
165 | #endif // COMMAND_PROVIDER_HPP_INCLUDED
166 |
--------------------------------------------------------------------------------
/libcyphal_demo/src/no_cpp_heap.cpp:
--------------------------------------------------------------------------------
1 | // This software is distributed under the terms of the MIT License.
2 | // Copyright (C) OpenCyphal Development Team
3 | // Copyright Amazon.com Inc. or its affiliates.
4 | // SPDX-License-Identifier: MIT
5 | // Author: Sergei Shirokov
6 |
7 | #include
8 |
9 | #include
10 | #include
11 | #include
12 |
13 | #if (__cplusplus >= CETL_CPP_STANDARD_17)
14 | # include
15 | #endif
16 |
17 | // Disable std c++ heap allocations.
18 | // In this demo we gonna use only stack and PMR allocations.
19 | //
20 | extern void* operator new(std::size_t)
21 | {
22 | std::cerr << "operator `new(size_t)` has been called";
23 | std::exit(1);
24 | }
25 | extern void operator delete(void*) noexcept
26 | {
27 | std::cerr << "operator `delete(void*)` has been called";
28 | std::exit(1);
29 | }
30 |
31 | #if (__cplusplus >= CETL_CPP_STANDARD_17)
32 |
33 | extern void* operator new(std::size_t, std::align_val_t)
34 | {
35 | std::cerr << "operator `new(size_t, align_val_t)` has been called";
36 | std::exit(1);
37 | }
38 | extern void operator delete(void*, std::align_val_t) noexcept
39 | {
40 | std::cerr << "operator `delete(void*, align_val_t)` has been called";
41 | std::exit(1);
42 | }
43 |
44 | #endif // (__cplusplus >= CETL_CPP_STANDARD_17)
45 |
--------------------------------------------------------------------------------
/libcyphal_demo/src/platform/block_memory_resource.hpp:
--------------------------------------------------------------------------------
1 | // This software is distributed under the terms of the MIT License.
2 | // Copyright (C) OpenCyphal Development Team
3 | // Copyright Amazon.com Inc. or its affiliates.
4 | // SPDX-License-Identifier: MIT
5 | // Author: Sergei Shirokov
6 |
7 | #ifndef PLATFORM_BLOCK_MEMORY_RESOURCE_HPP
8 | #define PLATFORM_BLOCK_MEMORY_RESOURCE_HPP
9 |
10 | #include
11 | #include
12 | #include
13 |
14 | #include
15 | #include
16 | #include
17 | #include
18 | #include
19 |
20 | namespace platform
21 | {
22 |
23 | /// Implements a C++17 PMR memory resource that uses a pool of pre-allocated blocks.
24 | ///
25 | class BlockMemoryResource final : public cetl::pmr::memory_resource
26 | {
27 | public:
28 | struct Diagnostics final
29 | {
30 | std::size_t capacity;
31 | std::size_t allocated;
32 | std::size_t peak_allocated;
33 | std::size_t block_size;
34 | std::uint64_t oom_count;
35 |
36 | }; // Diagnostics
37 |
38 | explicit BlockMemoryResource(cetl::pmr::memory_resource& memory)
39 | : memory_{memory}
40 | , pool_ptr_{nullptr, {&memory, 0U}}
41 | {
42 | }
43 |
44 | ~BlockMemoryResource() override = default;
45 |
46 | BlockMemoryResource(BlockMemoryResource&&) = delete;
47 | BlockMemoryResource(const BlockMemoryResource&) = delete;
48 | BlockMemoryResource& operator=(BlockMemoryResource&&) = delete;
49 | BlockMemoryResource& operator=(const BlockMemoryResource&) = delete;
50 |
51 | /// Initializes the memory pool.
52 | ///
53 | /// Normally, such setup functionality of this method should be in the constructor.
54 | /// However, we need to pass this block memory resource to a media first.
55 | /// Then the media will be passed to the transport creation,
56 | /// and only then we can set up this block memory resource according to MTU of the transport
57 | /// and total number of redundant media. To break this dependency cycle,
58 | /// we have to separate the setup of the block memory resource from its construction.
59 | ///
60 | void setup(const std::size_t pool_size, const std::size_t block_size, const std::size_t alignment)
61 | {
62 | CETL_DEBUG_ASSERT(!pool_ptr_, "");
63 | CETL_DEBUG_ASSERT(block_size > 0U, "");
64 | CETL_DEBUG_ASSERT(pool_size >= alignment, "");
65 | CETL_DEBUG_ASSERT(alignment && !(alignment & (alignment - 1)), "Should be a power of 2");
66 |
67 | pool_ptr_ = PoolPtr{memory_.allocate(pool_size), {&memory_, pool_size}};
68 | if (!pool_ptr_)
69 | {
70 | CETL_DEBUG_ASSERT(false, "Failed to allocate memory pool");
71 | return;
72 | }
73 |
74 | // Internal implementation requires at least `alignof(void*)` alignment -
75 | // b/c we link free blocks in the pool using pointers.
76 | alignment_ = std::max(alignment, alignof(void*));
77 |
78 | // Enforce alignment and padding of the input arguments. We may waste some space as a result.
79 | const std::size_t bs = (block_size + alignment_ - 1U) & ~(alignment_ - 1U);
80 | std::size_t sz_bytes = pool_size;
81 | auto* ptr = reinterpret_cast(pool_ptr_.get()); // NOLINT
82 | while ((reinterpret_cast(ptr) % alignment_) != 0U) // NOLINT
83 | {
84 | ptr++; // NOLINT
85 | if (sz_bytes > 0U)
86 | {
87 | sz_bytes--;
88 | }
89 | }
90 |
91 | block_size_ = bs;
92 | block_count_ = sz_bytes / bs;
93 | head_ = reinterpret_cast(ptr); // NOLINT
94 |
95 | for (std::size_t i = 0U; i < block_count_; i++)
96 | {
97 | *reinterpret_cast(ptr + (i * bs)) = // NOLINT
98 | ((i + 1U) < block_count_) ? static_cast(ptr + ((i + 1U) * bs)) : nullptr; // NOLINT
99 | }
100 | }
101 |
102 | Diagnostics queryDiagnostics() const noexcept
103 | {
104 | return {block_count_, used_blocks_, used_blocks_peak_, block_size_, oom_count_};
105 | }
106 |
107 | protected:
108 | // MARK: cetl::pmr::memory_resource
109 |
110 | void* do_allocate(std::size_t size_bytes, std::size_t alignment) override // NOLINT
111 | {
112 | if (alignment > alignment_)
113 | {
114 | #if defined(__cpp_exceptions)
115 | throw std::bad_alloc();
116 | #else
117 | return nullptr;
118 | #endif
119 | }
120 |
121 | // C++ standard (basic.stc.dynamic.allocation) requires that a memory allocation never returns
122 | // nullptr (even for the zero).
123 | // So, we have to handle this case explicitly by returning a non-null pointer to an empty storage.
124 | if (size_bytes == 0U)
125 | {
126 | return empty_storage_.data();
127 | }
128 |
129 | void* out = nullptr;
130 | request_count_++;
131 | if (size_bytes <= block_size_)
132 | {
133 | out = static_cast(head_); // NOLINT
134 | if (head_ != nullptr)
135 | {
136 | head_ = static_cast(*head_); // NOLINT
137 | used_blocks_++;
138 | used_blocks_peak_ = std::max(used_blocks_, used_blocks_peak_);
139 | }
140 | }
141 | if (out == nullptr)
142 | {
143 | oom_count_++;
144 | }
145 | return out;
146 | }
147 |
148 | void do_deallocate(void* ptr, std::size_t size_bytes, std::size_t alignment) override // NOLINT
149 | {
150 | CETL_DEBUG_ASSERT((nullptr != ptr) || (0U == size_bytes), "");
151 | CETL_DEBUG_ASSERT(size_bytes <= block_size_, "");
152 | (void) size_bytes;
153 | (void) alignment;
154 |
155 | // See `do_allocate` special case for zero bytes.
156 | if (ptr == empty_storage_.data())
157 | {
158 | CETL_DEBUG_ASSERT(0U == size_bytes, "");
159 | return;
160 | }
161 |
162 | if (ptr != nullptr)
163 | {
164 | *static_cast(ptr) = static_cast(head_);
165 | head_ = static_cast(ptr);
166 | CETL_DEBUG_ASSERT(used_blocks_ > 0U, "");
167 | used_blocks_--;
168 | }
169 | }
170 |
171 | bool do_is_equal(const cetl::pmr::memory_resource& other) const noexcept override
172 | {
173 | return this == &other;
174 | }
175 |
176 | private:
177 | using PoolPtr = std::unique_ptr>;
178 |
179 | cetl::pmr::memory_resource& memory_;
180 | PoolPtr pool_ptr_;
181 | std::size_t alignment_{0U};
182 | void** head_{nullptr};
183 | std::size_t block_count_{0U};
184 | std::size_t block_size_{0U};
185 | std::size_t used_blocks_{0U};
186 | std::size_t used_blocks_peak_{0U};
187 | std::size_t request_count_{0U};
188 | std::size_t oom_count_{0U};
189 |
190 | // See `do_allocate` special case for zero bytes.
191 | // Note that we still need at least one byte - b/c `std::array<..., 0>::data()` returns `nullptr`.
192 | std::array empty_storage_{};
193 |
194 | }; // BlockMemoryResource
195 |
196 | } // namespace platform
197 |
198 | #endif // PLATFORM_BLOCK_MEMORY_RESOURCE_HPP
199 |
--------------------------------------------------------------------------------
/libcyphal_demo/src/platform/bsd/kqueue_single_threaded_executor.hpp:
--------------------------------------------------------------------------------
1 | // This software is distributed under the terms of the MIT License.
2 | // Copyright (C) OpenCyphal Development Team
3 | // Copyright Amazon.com Inc. or its affiliates.
4 | // SPDX-License-Identifier: MIT
5 | // Author: Sergei Shirokov
6 |
7 | #ifndef PLATFORM_BSD_KQUEUE_SINGLE_THREADED_EXECUTOR_HPP_INCLUDED
8 | #define PLATFORM_BSD_KQUEUE_SINGLE_THREADED_EXECUTOR_HPP_INCLUDED
9 |
10 | #include "platform/posix/posix_executor_extension.hpp"
11 | #include "platform/posix/posix_platform_error.hpp"
12 |
13 | #include
14 | #include
15 | #include
16 | #include
17 | #include
18 | #include
19 | #include
20 | #include
21 | #include
22 |
23 | #include
24 | #include
25 | #include
26 | #include
27 | #include
28 | #include
29 | #include
30 | #include
31 | #include
32 | #include
33 | #include
34 | #include
35 | #include
36 |
37 | namespace platform
38 | {
39 | namespace bsd
40 | {
41 |
42 | /// @brief Defines BSD Linux platform-specific single-threaded executor based on `kqueue` mechanism.
43 | ///
44 | class KqueueSingleThreadedExecutor final : public libcyphal::platform::SingleThreadedExecutor,
45 | public posix::IPosixExecutorExtension
46 | {
47 | public:
48 | KqueueSingleThreadedExecutor()
49 | : kqueuefd_{::kqueue()}
50 | , total_awaitables_{0}
51 | {
52 | }
53 |
54 | KqueueSingleThreadedExecutor(const KqueueSingleThreadedExecutor&) = delete;
55 | KqueueSingleThreadedExecutor(KqueueSingleThreadedExecutor&&) noexcept = delete;
56 | KqueueSingleThreadedExecutor& operator=(const KqueueSingleThreadedExecutor&) = delete;
57 | KqueueSingleThreadedExecutor& operator=(KqueueSingleThreadedExecutor&&) noexcept = delete;
58 |
59 | ~KqueueSingleThreadedExecutor() override
60 | {
61 | if (kqueuefd_ >= 0)
62 | {
63 | ::close(kqueuefd_);
64 | }
65 | }
66 |
67 | CETL_NODISCARD cetl::optional pollAwaitableResourcesFor(
68 | const cetl::optional timeout) const override
69 | {
70 | CETL_DEBUG_ASSERT((total_awaitables_ > 0) || timeout,
71 | "Infinite timeout without awaitables means that we will sleep forever.");
72 |
73 | if (total_awaitables_ == 0)
74 | {
75 | if (!timeout)
76 | {
77 | return libcyphal::ArgumentError{};
78 | }
79 |
80 | std::this_thread::sleep_for(*timeout);
81 | return cetl::nullopt;
82 | }
83 |
84 | // Convert libcyphal timeout (if any) to the `struct timespec` timeout in ns.
85 | // Any possible negative timeout will be treated as zero (return immediately from the `::kevent`).
86 | //
87 | struct timespec timeout_spec
88 | {};
89 | const struct timespec* timeout_spec_ptr = nullptr;
90 | if (timeout)
91 | {
92 | using PollDuration = std::chrono::nanoseconds;
93 | using TimeoutNsType = decltype(timespec::tv_nsec);
94 |
95 | // Fill nanoseconds part of the timeout spec taking into account the maximum possible value.
96 | //
97 | timeout_spec.tv_nsec = static_cast( //
98 | std::max(static_cast(0),
99 | std::min(std::chrono::duration_cast(*timeout).count(),
100 | static_cast(std::numeric_limits::max()))));
101 |
102 | timeout_spec_ptr = &timeout_spec;
103 | }
104 |
105 | std::array evs{};
106 | const int kqueue_result = ::kevent(kqueuefd_, nullptr, 0, evs.data(), evs.size(), timeout_spec_ptr);
107 | if (kqueue_result < 0)
108 | {
109 | const auto err = errno;
110 | if (err == EINTR)
111 | {
112 | // Normally, we would just retry a system call (`::kevent`),
113 | // but we need updated timeout (from the main loop).
114 | return cetl::nullopt;
115 | }
116 | return libcyphal::transport::PlatformError{posix::PosixPlatformError{err}};
117 | }
118 | if (kqueue_result == 0)
119 | {
120 | return cetl::nullopt;
121 | }
122 | const auto kqueue_nfds = static_cast(kqueue_result);
123 |
124 | const auto now_time = now();
125 | for (std::size_t index = 0; index < kqueue_nfds; ++index)
126 | {
127 | const KEvent& ev = evs[index];
128 | if (auto* const cb_interface = static_cast(ev.udata))
129 | {
130 | cb_interface->schedule(Callback::Schedule::Once{now_time});
131 | }
132 | }
133 |
134 | return cetl::nullopt;
135 | }
136 |
137 | protected:
138 | // MARK: - IPosixExecutorExtension
139 |
140 | CETL_NODISCARD Callback::Any registerAwaitableCallback(Callback::Function&& function,
141 | const Trigger::Variant& trigger) override
142 | {
143 | AwaitableNode new_cb_node{*this, std::move(function)};
144 |
145 | cetl::visit( //
146 | cetl::make_overloaded(
147 | [&new_cb_node](const Trigger::Readable& readable) {
148 | //
149 | new_cb_node.setup(readable.fd, EVFILT_READ);
150 | },
151 | [&new_cb_node](const Trigger::Writable& writable) {
152 | //
153 | new_cb_node.setup(writable.fd, EVFILT_WRITE);
154 | }),
155 | trigger);
156 |
157 | insertCallbackNode(new_cb_node);
158 | return {std::move(new_cb_node)};
159 | }
160 |
161 | // MARK: - RTTI
162 |
163 | CETL_NODISCARD void* _cast_(const cetl::type_id& id) & noexcept override
164 | {
165 | if (id == IPosixExecutorExtension::_get_type_id_())
166 | {
167 | return static_cast(this);
168 | }
169 | return Base::_cast_(id);
170 | }
171 | CETL_NODISCARD const void* _cast_(const cetl::type_id& id) const& noexcept override
172 | {
173 | if (id == IPosixExecutorExtension::_get_type_id_())
174 | {
175 | return static_cast(this);
176 | }
177 | return Base::_cast_(id);
178 | }
179 |
180 | private:
181 | using KEvent = struct kevent;
182 | using Base = SingleThreadedExecutor;
183 | using Self = KqueueSingleThreadedExecutor;
184 |
185 | /// No Sonar cpp:S4963 b/c `AwaitableNode` supports move operation.
186 | ///
187 | class AwaitableNode final : public CallbackNode // NOSONAR cpp:S4963
188 | {
189 | public:
190 | AwaitableNode(Self& executor, Callback::Function&& function)
191 | : CallbackNode{executor, std::move(function)}
192 | , fd_{-1}
193 | , filter_{0}
194 | {
195 | }
196 |
197 | ~AwaitableNode() override
198 | {
199 | if (fd_ >= 0)
200 | {
201 | KEvent ev{};
202 | EV_SET(&ev, fd_, filter_, EV_DELETE, 0, 0, 0);
203 | ::kevent(getExecutor().kqueuefd_, &ev, 1, nullptr, 0, nullptr);
204 | getExecutor().total_awaitables_--;
205 | }
206 | }
207 |
208 | AwaitableNode(AwaitableNode&& other) noexcept
209 | : CallbackNode(std::move(static_cast(other)))
210 | , fd_{std::exchange(other.fd_, -1)}
211 | , filter_{std::exchange(other.filter_, 0)}
212 | {
213 | if (fd_ >= 0)
214 | {
215 | KEvent ev{};
216 | EV_SET(&ev, fd_, filter_, EV_DELETE, 0, 0, 0);
217 | ::kevent(getExecutor().kqueuefd_, &ev, 1, nullptr, 0, nullptr);
218 | EV_SET(&ev, fd_, filter_, EV_ADD, 0, 0, this);
219 | ::kevent(getExecutor().kqueuefd_, &ev, 1, nullptr, 0, nullptr);
220 | }
221 | }
222 |
223 | AwaitableNode(const AwaitableNode&) = delete;
224 | AwaitableNode& operator=(const AwaitableNode&) = delete;
225 | AwaitableNode& operator=(AwaitableNode&& other) noexcept = delete;
226 |
227 | int fd() const noexcept
228 | {
229 | return fd_;
230 | }
231 |
232 | std::int16_t filter() const noexcept
233 | {
234 | return filter_;
235 | }
236 |
237 | void setup(const int fd, const std::int16_t filter) noexcept
238 | {
239 | CETL_DEBUG_ASSERT(fd >= 0, "");
240 | CETL_DEBUG_ASSERT(filter != 0, "");
241 |
242 | fd_ = fd;
243 | filter_ = filter;
244 |
245 | getExecutor().total_awaitables_++;
246 | KEvent ev{};
247 | EV_SET(&ev, fd, filter_, EV_ADD, 0, 0, this);
248 | ::kevent(getExecutor().kqueuefd_, &ev, 1, nullptr, 0, nullptr);
249 | }
250 |
251 | private:
252 | Self& getExecutor() noexcept
253 | {
254 | // No lint b/c we know for sure that the executor is of type `Self`.
255 | // NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast)
256 | return static_cast(executor());
257 | }
258 |
259 | // MARK: Data members:
260 |
261 | int fd_;
262 | std::int16_t filter_;
263 |
264 | }; // AwaitableNode
265 |
266 | // MARK: - Data members:
267 |
268 | static constexpr int MaxEvents = 16;
269 |
270 | int kqueuefd_;
271 | std::size_t total_awaitables_;
272 |
273 | }; // KqueueSingleThreadedExecutor
274 |
275 | } // namespace bsd
276 | } // namespace platform
277 |
278 | #endif // PLATFORM_BSD_KQUEUE_SINGLE_THREADED_EXECUTOR_HPP_INCLUDED
279 |
--------------------------------------------------------------------------------
/libcyphal_demo/src/platform/common_helpers.hpp:
--------------------------------------------------------------------------------
1 | // This software is distributed under the terms of the MIT License.
2 | // Copyright (C) OpenCyphal Development Team
3 | // Copyright Amazon.com Inc. or its affiliates.
4 | // SPDX-License-Identifier: MIT
5 | // Author: Sergei Shirokov
6 |
7 | #ifndef PLATFORM_COMMON_HELPERS_HPP_INCLUDED
8 | #define PLATFORM_COMMON_HELPERS_HPP_INCLUDED
9 |
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include
15 |
16 | #ifdef __linux__
17 | #include
18 | #endif
19 |
20 | #include
21 |
22 | namespace platform
23 | {
24 |
25 | struct CommonHelpers
26 | {
27 | struct Printers
28 | {
29 | static cetl::string_view describeError(const libcyphal::ArgumentError&)
30 | {
31 | return "ArgumentError";
32 | }
33 | static cetl::string_view describeError(const libcyphal::MemoryError&)
34 | {
35 | return "MemoryError";
36 | }
37 | static cetl::string_view describeError(const libcyphal::transport::AnonymousError&)
38 | {
39 | return "AnonymousError";
40 | }
41 | static cetl::string_view describeError(const libcyphal::transport::CapacityError&)
42 | {
43 | return "CapacityError";
44 | }
45 | static cetl::string_view describeError(const libcyphal::transport::AlreadyExistsError&)
46 | {
47 | return "AlreadyExistsError";
48 | }
49 | static cetl::string_view describeError(const libcyphal::transport::PlatformError& error)
50 | {
51 | return "PlatformError";
52 | }
53 |
54 | static cetl::string_view describeAnyFailure(const libcyphal::transport::AnyFailure& failure)
55 | {
56 | return cetl::visit([](const auto& error) { return describeError(error); }, failure);
57 | }
58 | };
59 |
60 | #ifdef __linux__
61 |
62 | struct Can
63 | {
64 | static cetl::optional transientErrorReporter(
65 | libcyphal::transport::can::ICanTransport::TransientErrorReport::Variant& report_var)
66 | {
67 | using Report = libcyphal::transport::can::ICanTransport::TransientErrorReport;
68 |
69 | cetl::visit( //
70 | cetl::make_overloaded(
71 | [](const Report::CanardTxPush& report) {
72 | std::cerr << "Failed to push TX frame to canard "
73 | << "(mediaIdx=" << static_cast(report.media_index) << ").\n"
74 | << Printers::describeAnyFailure(report.failure) << "\n";
75 | },
76 | [](const Report::CanardRxAccept& report) {
77 | std::cerr << "Failed to accept RX frame at canard "
78 | << "(mediaIdx=" << static_cast(report.media_index) << ").\n"
79 | << Printers::describeAnyFailure(report.failure) << "\n";
80 | },
81 | [](const Report::MediaPop& report) {
82 | std::cerr << "Failed to pop frame from media "
83 | << "(mediaIdx=" << static_cast(report.media_index) << ").\n"
84 | << Printers::describeAnyFailure(report.failure) << "\n";
85 | },
86 | [](const Report::ConfigureMedia& report) {
87 | std::cerr << "Failed to configure CAN.\n"
88 | << Printers::describeAnyFailure(report.failure) << "\n";
89 | },
90 | [](const Report::MediaConfig& report) {
91 | std::cerr << "Failed to configure media "
92 | << "(mediaIdx=" << static_cast(report.media_index) << ").\n"
93 | << Printers::describeAnyFailure(report.failure) << "\n";
94 | },
95 | [](const Report::MediaPush& report) {
96 | std::cerr << "Failed to push frame to media "
97 | << "(mediaIdx=" << static_cast(report.media_index) << ").\n"
98 | << Printers::describeAnyFailure(report.failure) << "\n";
99 | }),
100 | report_var);
101 |
102 | // "swallows" all transient failures, thus giving a chance
103 | // to other redundant media interfaces to continue.
104 | return cetl::nullopt;
105 | }
106 |
107 | }; // Can
108 |
109 | #endif // __linux__
110 |
111 | struct Udp
112 | {
113 | static cetl::optional transientErrorReporter(
114 | libcyphal::transport::udp::IUdpTransport::TransientErrorReport::Variant& report_var)
115 | {
116 | using Report = libcyphal::transport::udp::IUdpTransport::TransientErrorReport;
117 |
118 | cetl::visit( //
119 | cetl::make_overloaded(
120 | [](const Report::UdpardTxPublish& report) {
121 | std::cerr << "Failed to TX message frame to udpard "
122 | << "(mediaIdx=" << static_cast(report.media_index) << ").\n"
123 | << Printers::describeAnyFailure(report.failure) << "\n";
124 | },
125 | [](const Report::UdpardTxRequest& report) {
126 | std::cerr << "Failed to TX request frame to udpard "
127 | << "(mediaIdx=" << static_cast(report.media_index) << ").\n"
128 | << Printers::describeAnyFailure(report.failure) << "\n";
129 | },
130 | [](const Report::UdpardTxRespond& report) {
131 | std::cerr << "Failed to TX response frame to udpard "
132 | << "(mediaIdx=" << static_cast(report.media_index) << ").\n"
133 | << Printers::describeAnyFailure(report.failure) << "\n";
134 | },
135 | [](const Report::UdpardRxMsgReceive& report) {
136 | std::cerr << "Failed to accept RX message frame at udpard "
137 | << Printers::describeAnyFailure(report.failure) << "\n";
138 | },
139 | [](const Report::UdpardRxSvcReceive& report) {
140 | std::cerr << "Failed to accept RX service frame at udpard "
141 | << "(mediaIdx=" << static_cast(report.media_index) << ").\n"
142 | << Printers::describeAnyFailure(report.failure) << "\n";
143 | },
144 | [](const Report::MediaMakeRxSocket& report) {
145 | std::cerr << "Failed to make RX socket " << "(mediaIdx=" << static_cast(report.media_index)
146 | << ").\n"
147 | << Printers::describeAnyFailure(report.failure) << "\n";
148 | },
149 | [](const Report::MediaMakeTxSocket& report) {
150 | std::cerr << "Failed to make TX socket " << "(mediaIdx=" << static_cast(report.media_index)
151 | << ").\n"
152 | << Printers::describeAnyFailure(report.failure) << "\n";
153 | },
154 | [](const Report::MediaTxSocketSend& report) {
155 | std::cerr << "Failed to TX frame to socket "
156 | << "(mediaIdx=" << static_cast(report.media_index) << ").\n"
157 | << Printers::describeAnyFailure(report.failure) << "\n";
158 | },
159 | [](const Report::MediaRxSocketReceive& report) {
160 | std::cerr << "Failed to RX frame from socket "
161 | << "(mediaIdx=" << static_cast(report.media_index) << ").\n"
162 | << Printers::describeAnyFailure(report.failure) << "\n";
163 | }),
164 | report_var);
165 |
166 | // "swallows" all transient failures, thus giving a chance
167 | // to other redundant media interfaces to continue.
168 | return cetl::nullopt;
169 | }
170 |
171 | }; // Udp
172 |
173 | }; // CommonHelpers
174 |
175 | } // namespace platform
176 |
177 | #endif // PLATFORM_COMMON_HELPERS_HPP_INCLUDED
178 |
--------------------------------------------------------------------------------
/libcyphal_demo/src/platform/defines.hpp:
--------------------------------------------------------------------------------
1 | // This software is distributed under the terms of the MIT License.
2 | // Copyright (C) OpenCyphal Development Team
3 | // Copyright Amazon.com Inc. or its affiliates.
4 | // SPDX-License-Identifier: MIT
5 | // Author: Sergei Shirokov
6 |
7 | #ifndef PLATFORM_DEFINES_HPP_INCLUDED
8 | #define PLATFORM_DEFINES_HPP_INCLUDED
9 |
10 | #ifdef PLATFORM_OS_TYPE_BSD
11 | # include "bsd/kqueue_single_threaded_executor.hpp"
12 | #else
13 | # include "linux/epoll_single_threaded_executor.hpp"
14 | #endif
15 |
16 | namespace platform
17 | {
18 |
19 | #ifdef PLATFORM_OS_TYPE_BSD
20 | using SingleThreadedExecutor = bsd::KqueueSingleThreadedExecutor;
21 | #else
22 | using SingleThreadedExecutor = Linux::EpollSingleThreadedExecutor;
23 | #endif
24 |
25 | } // namespace platform
26 |
27 | #endif // PLATFORM_DEFINES_HPP_INCLUDED
28 |
--------------------------------------------------------------------------------
/libcyphal_demo/src/platform/linux/epoll_single_threaded_executor.hpp:
--------------------------------------------------------------------------------
1 | // This software is distributed under the terms of the MIT License.
2 | // Copyright (C) OpenCyphal Development Team
3 | // Copyright Amazon.com Inc. or its affiliates.
4 | // SPDX-License-Identifier: MIT
5 | // Author: Sergei Shirokov
6 |
7 | #ifndef PLATFORM_LINUX_EPOLL_SINGLE_THREADED_EXECUTOR_HPP_INCLUDED
8 | #define PLATFORM_LINUX_EPOLL_SINGLE_THREADED_EXECUTOR_HPP_INCLUDED
9 |
10 | #include "platform/posix/posix_executor_extension.hpp"
11 | #include "platform/posix/posix_platform_error.hpp"
12 |
13 | #include
14 | #include
15 | #include
16 | #include
17 | #include
18 | #include
19 | #include
20 | #include
21 | #include
22 |
23 | #include
24 | #include
25 | #include
26 | #include
27 | #include
28 | #include
29 | #include
30 | #include
31 | #include
32 | #include
33 | #include
34 |
35 | namespace platform
36 | {
37 | namespace Linux
38 | {
39 |
40 | /// @brief Defines Linux platform-specific single-threaded executor based on `epoll` mechanism.
41 | ///
42 | class EpollSingleThreadedExecutor final : public libcyphal::platform::SingleThreadedExecutor,
43 | public posix::IPosixExecutorExtension
44 | {
45 | public:
46 | EpollSingleThreadedExecutor()
47 | : epollfd_{::epoll_create1(0)}
48 | , total_awaitables_{0}
49 | {
50 | }
51 |
52 | EpollSingleThreadedExecutor(const EpollSingleThreadedExecutor&) = delete;
53 | EpollSingleThreadedExecutor(EpollSingleThreadedExecutor&&) noexcept = delete;
54 | EpollSingleThreadedExecutor& operator=(const EpollSingleThreadedExecutor&) = delete;
55 | EpollSingleThreadedExecutor& operator=(EpollSingleThreadedExecutor&&) noexcept = delete;
56 |
57 | ~EpollSingleThreadedExecutor() override
58 | {
59 | if (epollfd_ >= 0)
60 | {
61 | ::close(epollfd_);
62 | }
63 | }
64 |
65 | CETL_NODISCARD cetl::optional pollAwaitableResourcesFor(
66 | const cetl::optional timeout) const override
67 | {
68 | CETL_DEBUG_ASSERT((total_awaitables_ > 0) || timeout,
69 | "Infinite timeout without awaitables means that we will sleep forever.");
70 |
71 | if (total_awaitables_ == 0)
72 | {
73 | if (!timeout)
74 | {
75 | return libcyphal::ArgumentError{};
76 | }
77 |
78 | std::this_thread::sleep_for(*timeout);
79 | return cetl::nullopt;
80 | }
81 |
82 | // Make sure that timeout is within the range of `::epoll_wait()`'s `int` timeout parameter.
83 | // Any possible negative timeout will be treated as zero (return immediately from the `::epoll_wait`).
84 | //
85 | int clamped_timeout_ms = -1; // "infinite" timeout
86 | if (timeout)
87 | {
88 | using PollDuration = std::chrono::milliseconds;
89 |
90 | clamped_timeout_ms = static_cast( //
91 | std::max(static_cast(0),
92 | std::min(std::chrono::duration_cast(*timeout).count(),
93 | static_cast(std::numeric_limits::max()))));
94 | }
95 |
96 | std::array evs{};
97 | const int epoll_result = ::epoll_wait(epollfd_, evs.data(), evs.size(), clamped_timeout_ms);
98 | if (epoll_result < 0)
99 | {
100 | const auto err = errno;
101 | if (err == EINTR)
102 | {
103 | // Normally, we would just retry a system call (`::epoll_wait`),
104 | // but we need updated timeout (from the main loop).
105 | return cetl::nullopt;
106 | }
107 | return libcyphal::transport::PlatformError{posix::PosixPlatformError{err}};
108 | }
109 | if (epoll_result == 0)
110 | {
111 | return cetl::nullopt;
112 | }
113 | const auto epoll_nfds = static_cast(epoll_result);
114 |
115 | const auto now_time = now();
116 | for (std::size_t index = 0; index < epoll_nfds; ++index)
117 | {
118 | const epoll_event& ev = evs[index];
119 | if (auto* const cb_interface = static_cast(ev.data.ptr))
120 | {
121 | cb_interface->schedule(Callback::Schedule::Once{now_time});
122 | }
123 | }
124 |
125 | return cetl::nullopt;
126 | }
127 |
128 | protected:
129 | // MARK: - IPosixExecutorExtension
130 |
131 | CETL_NODISCARD Callback::Any registerAwaitableCallback(Callback::Function&& function,
132 | const Trigger::Variant& trigger) override
133 | {
134 | AwaitableNode new_cb_node{*this, std::move(function)};
135 |
136 | cetl::visit( //
137 | cetl::make_overloaded(
138 | [&new_cb_node](const Trigger::Readable& readable) {
139 | //
140 | new_cb_node.setup(readable.fd, EPOLLIN);
141 | },
142 | [&new_cb_node](const Trigger::Writable& writable) {
143 | //
144 | new_cb_node.setup(writable.fd, EPOLLOUT);
145 | }),
146 | trigger);
147 |
148 | insertCallbackNode(new_cb_node);
149 | return {std::move(new_cb_node)};
150 | }
151 |
152 | // MARK: - RTTI
153 |
154 | CETL_NODISCARD void* _cast_(const cetl::type_id& id) & noexcept override
155 | {
156 | if (id == IPosixExecutorExtension::_get_type_id_())
157 | {
158 | return static_cast(this);
159 | }
160 | return Base::_cast_(id);
161 | }
162 | CETL_NODISCARD const void* _cast_(const cetl::type_id& id) const& noexcept override
163 | {
164 | if (id == IPosixExecutorExtension::_get_type_id_())
165 | {
166 | return static_cast(this);
167 | }
168 | return Base::_cast_(id);
169 | }
170 |
171 | private:
172 | using Base = SingleThreadedExecutor;
173 | using Self = EpollSingleThreadedExecutor;
174 |
175 | /// No Sonar cpp:S4963 b/c `AwaitableNode` supports move operation.
176 | ///
177 | class AwaitableNode final : public CallbackNode // NOSONAR cpp:S4963
178 | {
179 | public:
180 | AwaitableNode(Self& executor, Callback::Function&& function)
181 | : CallbackNode{executor, std::move(function)}
182 | , fd_{-1}
183 | , events_{0}
184 | {
185 | }
186 |
187 | ~AwaitableNode() override
188 | {
189 | if (fd_ >= 0)
190 | {
191 | ::epoll_ctl(getExecutor().epollfd_, EPOLL_CTL_DEL, fd_, nullptr);
192 | getExecutor().total_awaitables_--;
193 | }
194 | }
195 |
196 | AwaitableNode(AwaitableNode&& other) noexcept
197 | : CallbackNode(std::move(other))
198 | , fd_{std::exchange(other.fd_, -1)}
199 | , events_{std::exchange(other.events_, 0)}
200 | {
201 | if (fd_ >= 0)
202 | {
203 | ::epoll_event ev{events_, {this}};
204 | ::epoll_ctl(getExecutor().epollfd_, EPOLL_CTL_MOD, fd_, &ev);
205 | }
206 | }
207 |
208 | AwaitableNode(const AwaitableNode&) = delete;
209 | AwaitableNode& operator=(const AwaitableNode&) = delete;
210 | AwaitableNode& operator=(AwaitableNode&& other) noexcept = delete;
211 |
212 | int fd() const noexcept
213 | {
214 | return fd_;
215 | }
216 |
217 | std::uint32_t events() const noexcept
218 | {
219 | return events_;
220 | }
221 |
222 | void setup(const int fd, const std::uint32_t events) noexcept
223 | {
224 | CETL_DEBUG_ASSERT(fd >= 0, "");
225 | CETL_DEBUG_ASSERT(events != 0, "");
226 |
227 | fd_ = fd;
228 | events_ = events;
229 |
230 | getExecutor().total_awaitables_++;
231 | ::epoll_event ev{events_, {this}};
232 | ::epoll_ctl(getExecutor().epollfd_, EPOLL_CTL_ADD, fd_, &ev);
233 | }
234 |
235 | private:
236 | Self& getExecutor() noexcept
237 | {
238 | // No lint b/c we know for sure that the executor is of type `Self`.
239 | // NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast)
240 | return static_cast(executor());
241 | }
242 |
243 | // MARK: Data members:
244 |
245 | int fd_;
246 | std::uint32_t events_;
247 |
248 | }; // AwaitableNode
249 |
250 | // MARK: - Data members:
251 |
252 | static constexpr int MaxEpollEvents = 16;
253 |
254 | int epollfd_;
255 | std::size_t total_awaitables_;
256 |
257 | }; // LinuxSingleThreadedExecutor
258 |
259 | } // namespace Linux
260 | } // namespace platform
261 |
262 | #endif // PLATFORM_LINUX_EPOLL_SINGLE_THREADED_EXECUTOR_HPP_INCLUDED
263 |
--------------------------------------------------------------------------------
/libcyphal_demo/src/platform/o1_heap_memory_resource.hpp:
--------------------------------------------------------------------------------
1 | // This software is distributed under the terms of the MIT License.
2 | // Copyright (C) OpenCyphal Development Team
3 | // Copyright Amazon.com Inc. or its affiliates.
4 | // SPDX-License-Identifier: MIT
5 | // Author: Sergei Shirokov
6 |
7 | #ifndef PLATFORM_O1_HEAP_MEMORY_RESOURCE_HPP
8 | #define PLATFORM_O1_HEAP_MEMORY_RESOURCE_HPP
9 |
10 | #include
11 | #include
12 | #include
13 |
14 | #include
15 | #include
16 | #include
17 | #include
18 | #include
19 |
20 | namespace platform
21 | {
22 |
23 | /// Implements a C++17 PMR memory resource that uses the O(1) heap.
24 | ///
25 | class O1HeapMemoryResource final : public cetl::pmr::memory_resource
26 | {
27 | public:
28 | template
29 | explicit O1HeapMemoryResource(std::array& heap_arena)
30 | : o1_heap_{o1heapInit(heap_arena.data(), heap_arena.size())}
31 | {
32 | CETL_DEBUG_ASSERT(o1_heap_ != nullptr, "");
33 | }
34 |
35 | O1HeapDiagnostics queryDiagnostics() const noexcept
36 | {
37 | return o1heapGetDiagnostics(o1_heap_);
38 | }
39 |
40 | private:
41 | // MARK: cetl::pmr::memory_resource
42 |
43 | void* do_allocate(std::size_t size_bytes, std::size_t alignment) override // NOLINT
44 | {
45 | if (alignment > alignof(std::max_align_t))
46 | {
47 | #if defined(__cpp_exceptions)
48 | throw std::bad_alloc();
49 | #else
50 | return nullptr;
51 | #endif
52 | }
53 |
54 | // О1Heap is a C-library, and it follows `malloc` convention of returning nullptr on requesting zero bytes.
55 | // In contrast, C++ standard (basic.stc.dynamic.allocation) requires that a memory allocation never returns
56 | // nullptr (even for the zero).
57 | // So, we have to handle this case explicitly by returning a non-null pointer to an empty storage.
58 | if (size_bytes == 0)
59 | {
60 | return empty_storage_.data();
61 | }
62 |
63 | return o1heapAllocate(o1_heap_, size_bytes);
64 | }
65 |
66 | void do_deallocate(void* ptr, std::size_t size_bytes, std::size_t alignment) override // NOLINT
67 | {
68 | CETL_DEBUG_ASSERT((nullptr != ptr) || (0 == size_bytes), "");
69 | (void) size_bytes;
70 | (void) alignment;
71 |
72 | // See `do_allocate` special case for zero bytes.
73 | if (ptr == empty_storage_.data())
74 | {
75 | CETL_DEBUG_ASSERT(0 == size_bytes, "");
76 | return;
77 | }
78 |
79 | o1heapFree(o1_heap_, ptr);
80 | }
81 |
82 | #if (__cplusplus < CETL_CPP_STANDARD_17)
83 |
84 | void* do_reallocate(void* ptr,
85 | std::size_t old_size_bytes,
86 | std::size_t new_size_bytes, // NOLINT
87 | std::size_t alignment) override
88 | {
89 | CETL_DEBUG_ASSERT((nullptr != ptr) || (0 == old_size_bytes), "");
90 | (void) alignment;
91 |
92 | // See `do_allocate` special case for zero bytes.
93 | if (new_size_bytes == 0)
94 | {
95 | if (ptr != empty_storage_.data())
96 | {
97 | o1heapFree(o1_heap_, ptr);
98 | }
99 | return empty_storage_.data();
100 | }
101 |
102 | if (auto* const new_ptr = o1heapAllocate(o1_heap_, new_size_bytes))
103 | {
104 | std::memmove(new_ptr, ptr, std::min(old_size_bytes, new_size_bytes));
105 |
106 | // See `do_allocate` special case for zero bytes.
107 | if (ptr != empty_storage_.data())
108 | {
109 | o1heapFree(o1_heap_, ptr);
110 | }
111 |
112 | return new_ptr;
113 | }
114 | return nullptr;
115 | }
116 |
117 | #endif
118 |
119 | bool do_is_equal(const cetl::pmr::memory_resource& rhs) const noexcept override
120 | {
121 | return (&rhs == this);
122 | }
123 |
124 | // MARK: Data members:
125 |
126 | O1HeapInstance* o1_heap_;
127 |
128 | // See `do_allocate` special case for zero bytes.
129 | // Note that we still need at least one byte - b/c `std::array<..., 0>::data()` returns `nullptr`.
130 | std::array empty_storage_{};
131 |
132 | }; // O1HeapMemoryResource
133 |
134 | } // namespace platform
135 |
136 | #endif // PLATFORM_O1_HEAP_MEMORY_RESOURCE_HPP
137 |
--------------------------------------------------------------------------------
/libcyphal_demo/src/platform/posix/posix_executor_extension.hpp:
--------------------------------------------------------------------------------
1 | // This software is distributed under the terms of the MIT License.
2 | // Copyright (C) OpenCyphal Development Team
3 | // Copyright Amazon.com Inc. or its affiliates.
4 | // SPDX-License-Identifier: MIT
5 | // Author: Sergei Shirokov
6 |
7 | #ifndef PLATFORM_POSIX_EXECUTOR_EXTENSION_HPP_INCLUDED
8 | #define PLATFORM_POSIX_EXECUTOR_EXTENSION_HPP_INCLUDED
9 |
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include
15 | #include
16 | #include
17 |
18 | namespace platform
19 | {
20 | namespace posix
21 | {
22 |
23 | class IPosixExecutorExtension
24 | {
25 | // FFE3771E-7962-4CEA-ACA6-ED7895699080
26 | using TypeIdType = cetl::
27 | // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers, readability-magic-numbers)
28 | type_id_type<0xFF, 0xE3, 0x77, 0x1E, 0x79, 0x62, 0x4C, 0xEA, 0xAC, 0xA6, 0xED, 0x78, 0x95, 0x69, 0x90, 0x80>;
29 |
30 | public:
31 | IPosixExecutorExtension(const IPosixExecutorExtension&) = delete;
32 | IPosixExecutorExtension(IPosixExecutorExtension&&) noexcept = delete;
33 | IPosixExecutorExtension& operator=(const IPosixExecutorExtension&) = delete;
34 | IPosixExecutorExtension& operator=(IPosixExecutorExtension&&) noexcept = delete;
35 |
36 | struct Trigger
37 | {
38 | struct Readable
39 | {
40 | int fd;
41 | };
42 | struct Writable
43 | {
44 | int fd;
45 | };
46 |
47 | using Variant = cetl::variant;
48 | };
49 |
50 | CETL_NODISCARD virtual libcyphal::IExecutor::Callback::Any registerAwaitableCallback(
51 | libcyphal::IExecutor::Callback::Function&& function,
52 | const Trigger::Variant& trigger) = 0;
53 |
54 | using PollFailure = cetl::variant;
55 |
56 | CETL_NODISCARD virtual cetl::optional pollAwaitableResourcesFor(
57 | const cetl::optional timeout) const = 0;
58 |
59 | // MARK: RTTI
60 |
61 | static constexpr cetl::type_id _get_type_id_() noexcept
62 | {
63 | return cetl::type_id_type_value();
64 | }
65 |
66 | protected:
67 | IPosixExecutorExtension() = default;
68 | ~IPosixExecutorExtension() = default;
69 |
70 | }; // IPosixExecutorExtension
71 |
72 | } // namespace posix
73 | } // namespace platform
74 |
75 | #endif // PLATFORM_POSIX_EXECUTOR_EXTENSION_HPP_INCLUDED
76 |
--------------------------------------------------------------------------------
/libcyphal_demo/src/platform/posix/posix_platform_error.hpp:
--------------------------------------------------------------------------------
1 | // This software is distributed under the terms of the MIT License.
2 | // Copyright (C) OpenCyphal Development Team
3 | // Copyright Amazon.com Inc. or its affiliates.
4 | // SPDX-License-Identifier: MIT
5 | // Author: Sergei Shirokov
6 |
7 | #ifndef PLATFORM_POSIX_PLATFORM_ERROR_HPP_INCLUDED
8 | #define PLATFORM_POSIX_PLATFORM_ERROR_HPP_INCLUDED
9 |
10 | #include
11 | #include
12 |
13 | #include
14 |
15 | namespace platform
16 | {
17 | namespace posix
18 | {
19 |
20 | class PosixPlatformError final : public libcyphal::transport::IPlatformError
21 | {
22 | public:
23 | explicit PosixPlatformError(const int err)
24 | : code_{err}
25 | {
26 | CETL_DEBUG_ASSERT(err > 0, "");
27 | }
28 | virtual ~PosixPlatformError() = default;
29 | PosixPlatformError(const PosixPlatformError&) = default;
30 | PosixPlatformError(PosixPlatformError&&) noexcept = default;
31 | PosixPlatformError& operator=(const PosixPlatformError&) = default;
32 | PosixPlatformError& operator=(PosixPlatformError&&) noexcept = default;
33 |
34 | // MARK: IPlatformError
35 |
36 | /// Gets platform-specific error code.
37 | ///
38 | /// In this case, the error code is the POSIX error code (aka `errno`).
39 | ///
40 | std::uint32_t code() const noexcept override
41 | {
42 | return static_cast(code_);
43 | }
44 |
45 | private:
46 | int code_;
47 |
48 | }; // PosixPlatformError
49 |
50 | } // namespace posix
51 | } // namespace platform
52 |
53 | #endif // PLATFORM_POSIX_PLATFORM_ERROR_HPP_INCLUDED
54 |
--------------------------------------------------------------------------------
/libcyphal_demo/src/platform/posix/udp/udp_media.hpp:
--------------------------------------------------------------------------------
1 | // This software is distributed under the terms of the MIT License.
2 | // Copyright (C) OpenCyphal Development Team
3 | // Copyright Amazon.com Inc. or its affiliates.
4 | // SPDX-License-Identifier: MIT
5 | // Author: Sergei Shirokov
6 |
7 | #ifndef PLATFORM_POSIX_UDP_MEDIA_HPP_INCLUDED
8 | #define PLATFORM_POSIX_UDP_MEDIA_HPP_INCLUDED
9 |
10 | #include "platform/string.hpp"
11 | #include "udp_sockets.hpp"
12 |
13 | #include
14 |
15 | #include
16 | #include
17 | #include
18 | #include
19 | #include
20 |
21 | #include
22 | #include
23 | #include
24 |
25 | namespace platform
26 | {
27 | namespace posix
28 | {
29 |
30 | class UdpMedia final : public libcyphal::transport::udp::IMedia
31 | {
32 | public:
33 | UdpMedia(cetl::pmr::memory_resource& general_mr,
34 | libcyphal::IExecutor& executor,
35 | const cetl::string_view iface_address,
36 | cetl::pmr::memory_resource& tx_mr)
37 | : general_mr_{general_mr}
38 | , executor_{executor}
39 | , iface_address_{iface_address}
40 | , iface_mtu_{UDPARD_MTU_DEFAULT}
41 | , tx_mr_{tx_mr}
42 | {
43 | }
44 |
45 | ~UdpMedia() = default;
46 |
47 | UdpMedia(const UdpMedia&) = delete;
48 | UdpMedia& operator=(const UdpMedia&) = delete;
49 | UdpMedia* operator=(UdpMedia&&) noexcept = delete;
50 |
51 | UdpMedia(UdpMedia&& other) noexcept
52 | : general_mr_{other.general_mr_}
53 | , executor_{other.executor_}
54 | , iface_address_{other.iface_address_}
55 | , iface_mtu_{other.iface_mtu_}
56 | , tx_mr_{other.tx_mr_}
57 | {
58 | }
59 |
60 | void setAddress(const cetl::string_view iface_address)
61 | {
62 | iface_address_ = iface_address;
63 | }
64 |
65 | void setMtu(const std::size_t mtu)
66 | {
67 | iface_mtu_ = mtu;
68 | }
69 |
70 | private:
71 | // MARK: - IMedia
72 |
73 | MakeTxSocketResult::Type makeTxSocket() override
74 | {
75 | return UdpTxSocket::make(general_mr_, executor_, iface_address_.data(), iface_mtu_);
76 | }
77 |
78 | MakeRxSocketResult::Type makeRxSocket(const libcyphal::transport::udp::IpEndpoint& multicast_endpoint) override
79 | {
80 | return UdpRxSocket::make(general_mr_, executor_, iface_address_.data(), multicast_endpoint);
81 | }
82 |
83 | cetl::pmr::memory_resource& getTxMemoryResource() override
84 | {
85 | return tx_mr_;
86 | }
87 |
88 | // MARK: Data members:
89 |
90 | cetl::pmr::memory_resource& general_mr_;
91 | libcyphal::IExecutor& executor_;
92 | String<64> iface_address_;
93 | std::size_t iface_mtu_;
94 | cetl::pmr::memory_resource& tx_mr_;
95 |
96 | }; // UdpMedia
97 |
98 | // MARK: -
99 |
100 | struct UdpMediaCollection
101 | {
102 | UdpMediaCollection(cetl::pmr::memory_resource& general_mr,
103 | libcyphal::IExecutor& executor,
104 | cetl::pmr::memory_resource& tx_mr)
105 | : media_array_{{//
106 | {general_mr, executor, "", tx_mr},
107 | {general_mr, executor, "", tx_mr},
108 | {general_mr, executor, "", tx_mr}}}
109 | {
110 | }
111 |
112 | void parse(const cetl::string_view iface_addresses, const std::size_t iface_mtu)
113 | {
114 | // Split addresses by spaces.
115 | //
116 | std::size_t index = 0;
117 | std::size_t curr = 0;
118 | while ((curr != cetl::string_view::npos) && (index < MaxUdpMedia))
119 | {
120 | const auto next = iface_addresses.find(' ', curr);
121 | const auto iface_address = iface_addresses.substr(curr, next - curr);
122 | if (!iface_address.empty())
123 | {
124 | auto& media = media_array_[index]; // NOLINT
125 |
126 | media.setAddress(iface_address);
127 | media.setMtu(iface_mtu);
128 | index++;
129 | }
130 |
131 | curr = std::max(next + 1, next); // `+1` to skip the space
132 | }
133 |
134 | media_ifaces_ = {};
135 | for (std::size_t i = 0; i < index; i++)
136 | {
137 | media_ifaces_[i] = &media_array_[i]; // NOLINT
138 | }
139 | }
140 |
141 | cetl::span span()
142 | {
143 | return {media_ifaces_.data(), media_ifaces_.size()};
144 | }
145 |
146 | std::size_t count() const
147 | {
148 | return std::count_if(media_ifaces_.cbegin(), media_ifaces_.cend(), [](const auto* iface) {
149 | //
150 | return iface != nullptr;
151 | });
152 | }
153 |
154 | private:
155 | static constexpr std::size_t MaxUdpMedia = 3;
156 |
157 | std::array media_array_;
158 | std::array media_ifaces_{};
159 |
160 | }; // UdpMediaCollection
161 |
162 | } // namespace posix
163 | } // namespace platform
164 |
165 | #endif // PLATFORM_POSIX_UDP_MEDIA_HPP_INCLUDED
166 |
--------------------------------------------------------------------------------
/libcyphal_demo/src/platform/posix/udp/udp_sockets.hpp:
--------------------------------------------------------------------------------
1 | // This software is distributed under the terms of the MIT License.
2 | // Copyright (C) OpenCyphal Development Team
3 | // Copyright Amazon.com Inc. or its affiliates.
4 | // SPDX-License-Identifier: MIT
5 | // Author: Sergei Shirokov
6 |
7 | #ifndef PLATFORM_POSIX_UDP_SOCKETS_HPP_INCLUDED
8 | #define PLATFORM_POSIX_UDP_SOCKETS_HPP_INCLUDED
9 |
10 | #include "platform/posix/posix_executor_extension.hpp"
11 | #include "platform/posix/posix_platform_error.hpp"
12 | #include "udp.h"
13 |
14 | #include
15 | #include
16 | #include
17 | #include
18 | #include
19 | #include
20 | #include
21 | #include
22 | #include
23 | #include
24 |
25 | #include
26 | #include
27 | #include
28 | #include
29 | #include
30 |
31 | namespace platform
32 | {
33 | namespace posix
34 | {
35 |
36 | class UdpTxSocket final : public libcyphal::transport::udp::ITxSocket
37 | {
38 | public:
39 | CETL_NODISCARD static libcyphal::transport::udp::IMedia::MakeTxSocketResult::Type make(
40 | cetl::pmr::memory_resource& memory,
41 | libcyphal::IExecutor& executor,
42 | const char* const iface_address,
43 | const std::size_t iface_mtu)
44 | {
45 | UDPTxHandle handle{-1};
46 | const auto result = ::udpTxInit(&handle, ::udpParseIfaceAddress(iface_address));
47 | if (result < 0)
48 | {
49 | return libcyphal::transport::PlatformError{PosixPlatformError{-result}};
50 | }
51 |
52 | auto tx_socket = libcyphal::makeUniquePtr(memory, executor, handle, iface_mtu);
53 | if (tx_socket == nullptr)
54 | {
55 | ::udpTxClose(&handle);
56 | return libcyphal::MemoryError{};
57 | }
58 |
59 | return tx_socket;
60 | }
61 |
62 | UdpTxSocket(libcyphal::IExecutor& executor, UDPTxHandle udp_handle, const std::size_t iface_mtu)
63 | : udp_handle_{udp_handle}
64 | , executor_{executor}
65 | , iface_mtu_{iface_mtu}
66 | {
67 | CETL_DEBUG_ASSERT(udp_handle_.fd >= 0, "");
68 | }
69 |
70 | ~UdpTxSocket()
71 | {
72 | ::udpTxClose(&udp_handle_);
73 | }
74 |
75 | UdpTxSocket(const UdpTxSocket&) = delete;
76 | UdpTxSocket(UdpTxSocket&&) noexcept = delete;
77 | UdpTxSocket& operator=(const UdpTxSocket&) = delete;
78 | UdpTxSocket& operator=(UdpTxSocket&&) noexcept = delete;
79 |
80 | private:
81 | // MARK: ITxSocket
82 |
83 | std::size_t getMtu() const noexcept override
84 | {
85 | return iface_mtu_;
86 | }
87 |
88 | SendResult::Type send(const libcyphal::TimePoint,
89 | const libcyphal::transport::udp::IpEndpoint multicast_endpoint,
90 | const std::uint8_t dscp,
91 | const libcyphal::transport::PayloadFragments payload_fragments) override
92 | {
93 | CETL_DEBUG_ASSERT(udp_handle_.fd >= 0, "");
94 | CETL_DEBUG_ASSERT(payload_fragments.size() == 1, "");
95 |
96 | const std::int16_t result = ::udpTxSend(&udp_handle_,
97 | multicast_endpoint.ip_address,
98 | multicast_endpoint.udp_port,
99 | dscp,
100 | payload_fragments[0].size(),
101 | payload_fragments[0].data());
102 | if (result < 0)
103 | {
104 | return libcyphal::transport::PlatformError{PosixPlatformError{-result}};
105 | }
106 |
107 | return SendResult::Success{result == 1};
108 | }
109 |
110 | CETL_NODISCARD libcyphal::IExecutor::Callback::Any registerCallback(
111 | libcyphal::IExecutor::Callback::Function&& function) override
112 | {
113 | auto* const posix_executor_ext = cetl::rtti_cast(&executor_);
114 | if (nullptr == posix_executor_ext)
115 | {
116 | return {};
117 | }
118 |
119 | CETL_DEBUG_ASSERT(udp_handle_.fd >= 0, "");
120 | return posix_executor_ext->registerAwaitableCallback(std::move(function),
121 | IPosixExecutorExtension::Trigger::Writable{
122 | udp_handle_.fd});
123 | }
124 |
125 | // MARK: Data members:
126 |
127 | UDPTxHandle udp_handle_;
128 | libcyphal::IExecutor& executor_;
129 | std::size_t iface_mtu_;
130 |
131 | }; // UdpTxSocket
132 |
133 | // MARK: -
134 |
135 | class UdpRxSocket final : public libcyphal::transport::udp::IRxSocket
136 | {
137 | public:
138 | CETL_NODISCARD static libcyphal::transport::udp::IMedia::MakeRxSocketResult::Type make(
139 | cetl::pmr::memory_resource& memory,
140 | libcyphal::IExecutor& executor,
141 | const std::string& address,
142 | const libcyphal::transport::udp::IpEndpoint& endpoint)
143 | {
144 | UDPRxHandle handle{-1};
145 | const auto result =
146 | ::udpRxInit(&handle, ::udpParseIfaceAddress(address.c_str()), endpoint.ip_address, endpoint.udp_port);
147 | if (result < 0)
148 | {
149 | return libcyphal::transport::PlatformError{PosixPlatformError{-result}};
150 | }
151 |
152 | auto rx_socket = libcyphal::makeUniquePtr(memory, executor, handle, memory);
153 | if (rx_socket == nullptr)
154 | {
155 | ::udpRxClose(&handle);
156 | return libcyphal::MemoryError{};
157 | }
158 |
159 | return rx_socket;
160 | }
161 |
162 | UdpRxSocket(libcyphal::IExecutor& executor, UDPRxHandle udp_handle, cetl::pmr::memory_resource& memory)
163 | : udp_handle_{udp_handle}
164 | , executor_{executor}
165 | , memory_{memory}
166 | {
167 | CETL_DEBUG_ASSERT(udp_handle_.fd >= 0, "");
168 | }
169 |
170 | ~UdpRxSocket()
171 | {
172 | ::udpRxClose(&udp_handle_);
173 | }
174 |
175 | UdpRxSocket(const UdpRxSocket&) = delete;
176 | UdpRxSocket(UdpRxSocket&&) noexcept = delete;
177 | UdpRxSocket& operator=(const UdpRxSocket&) = delete;
178 | UdpRxSocket& operator=(UdpRxSocket&&) noexcept = delete;
179 |
180 | private:
181 | static constexpr std::size_t BufferSize = 2000;
182 |
183 | // MARK: IRxSocket
184 |
185 | CETL_NODISCARD ReceiveResult::Type receive() override
186 | {
187 | CETL_DEBUG_ASSERT(udp_handle_.fd >= 0, "");
188 |
189 | // Current Udpard api limitation is not allowing to pass bigger buffer than actual data size is.
190 | // Hence, we need temp buffer on stack, and then memory copying.
191 | // TODO: Eliminate tmp buffer and memmove when https://github.com/OpenCyphal/libudpard/issues/58 is resolved.
192 | //
193 | std::array buffer{};
194 | std::size_t inout_size = buffer.size();
195 | const std::int16_t result = ::udpRxReceive(&udp_handle_, &inout_size, buffer.data());
196 | if (result < 0)
197 | {
198 | return libcyphal::transport::PlatformError{PosixPlatformError{-result}};
199 | }
200 | if (result == 0)
201 | {
202 | return cetl::nullopt;
203 | }
204 | //
205 | auto* const allocated_buffer = memory_.allocate(inout_size);
206 | if (nullptr == allocated_buffer)
207 | {
208 | return libcyphal::MemoryError{};
209 | }
210 | (void) std::memmove(allocated_buffer, buffer.data(), inout_size);
211 |
212 | return ReceiveResult::Metadata{executor_.now(),
213 | {static_cast(allocated_buffer),
214 | libcyphal::PmrRawBytesDeleter{inout_size, &memory_}}};
215 | }
216 |
217 | CETL_NODISCARD libcyphal::IExecutor::Callback::Any registerCallback(
218 | libcyphal::IExecutor::Callback::Function&& function) override
219 | {
220 | auto* const posix_executor_ext = cetl::rtti_cast(&executor_);
221 | if (nullptr == posix_executor_ext)
222 | {
223 | return {};
224 | }
225 |
226 | CETL_DEBUG_ASSERT(udp_handle_.fd >= 0, "");
227 | return posix_executor_ext->registerAwaitableCallback(std::move(function),
228 | IPosixExecutorExtension::Trigger::Readable{
229 | udp_handle_.fd});
230 | }
231 |
232 | // MARK: Data members:
233 |
234 | UDPRxHandle udp_handle_;
235 | libcyphal::IExecutor& executor_;
236 | cetl::pmr::memory_resource& memory_;
237 |
238 | }; // UdpRxSocket
239 |
240 | } // namespace posix
241 | } // namespace platform
242 |
243 | #endif // PLATFORM_POSIX_UDP_SOCKETS_HPP_INCLUDED
244 |
--------------------------------------------------------------------------------
/libcyphal_demo/src/platform/storage.hpp:
--------------------------------------------------------------------------------
1 | /// @copyright
2 | /// Copyright (C) OpenCyphal Development Team
3 | /// Copyright Amazon.com Inc. or its affiliates.
4 | /// SPDX-License-Identifier: MIT
5 |
6 | #ifndef PLATFORM_STORAGE_HPP_INCLUDED
7 | #define PLATFORM_STORAGE_HPP_INCLUDED
8 |
9 | #include "string.hpp"
10 |
11 | #include
12 | #include
13 | #include
14 | #include
15 |
16 | #include
17 | #include
18 | #include
19 | #include
20 | #include
21 | #include