Compare commits
160 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a7a85b0c1f | |||
| 457d0f9345 | |||
| eeb057effd | |||
| c7e117b08e | |||
| af57c4dfd1 | |||
| db30001140 | |||
| d69636bf7b | |||
| 59a584561d | |||
| f7aba4af4e | |||
| 21bbaf846e | |||
| aacbdd5864 | |||
| bb59f47549 | |||
| 1c7277d141 | |||
| d29ebafea0 | |||
| 94982d50b9 | |||
| 0503705a13 | |||
| ef9eef2bc3 | |||
| 2b3b318abe | |||
| f3a4c69597 | |||
| 032e9ef8d5 | |||
| 4a1bcb1516 | |||
| d6e1e7ebc0 | |||
| 9a4f80a9d6 | |||
| 7a55a65589 | |||
| 14b97a52ed | |||
| 5845f1a41d | |||
| 6ea90c2dae | |||
| 121b7db045 | |||
| d88dd2cf44 | |||
| b3bf0e2cb9 | |||
| 45ad5c83ee | |||
| 10e615e75e | |||
| 05515743c5 | |||
| b2c73f6bed | |||
| 797a95e6a1 | |||
| 972979cc10 | |||
| ba955ef633 | |||
| a32b4f05d1 | |||
| c8474edad7 | |||
| 58e9b09995 | |||
| c2c6d409dd | |||
| 8dba0fdfc4 | |||
| 67af9f02da | |||
| e824685c19 | |||
| 9cf1398f5c | |||
| f76f718e80 | |||
| cdade17905 | |||
| 5af7e531b6 | |||
| 018c1f1e1d | |||
| 5e522178d8 | |||
| 7574f3f59a | |||
| 0de031c74b | |||
| ebbb2b1345 | |||
| 3bf8146ca3 | |||
| f32a472c5d | |||
| 7994c2f6e2 | |||
| 9ab155560a | |||
| 720babd39d | |||
| 5c3bc6c324 | |||
| b53ef42124 | |||
| babfda4d0f | |||
| 88dd872c95 | |||
| b8255234de | |||
| 1a4f7f97bd | |||
| 13a948a2d3 | |||
| 07c48d78d1 | |||
| 7b6bfbad68 | |||
| 393326052c | |||
| b3d0565e11 | |||
| 287dd6be56 | |||
| 0b2fde3484 | |||
| c1286627ab | |||
| 2234df1de2 | |||
| 4db3581be9 | |||
| 7efe622dd2 | |||
| f658e97ed0 | |||
| f8c5fad841 | |||
| 626a84cc78 | |||
| a68143810e | |||
| 109cd9eb03 | |||
| 7b830f0a68 | |||
| b65b0f2370 | |||
| 2a8a6edf22 | |||
| 10e19a3237 | |||
| 9e83a99c9c | |||
| b576d41595 | |||
| b89c8cdc4f | |||
| 682b9e121b | |||
| bcf81594e7 | |||
| 1b9acd5603 | |||
| dc23a61410 | |||
| e9b4e15b79 | |||
| fca665d44e | |||
| 862acf0fe3 | |||
| 6650664529 | |||
| e297a260d9 | |||
| d54ef04c47 | |||
| 92399ba283 | |||
| 6f4a2dd649 | |||
| 0e872042ee | |||
| 266cabcddb | |||
| 444555e9b6 | |||
| 3373393755 | |||
| dc864bad00 | |||
| 452d1966fc | |||
| f7dcb7307d | |||
| bd0118531f | |||
| 4bfcdf37da | |||
| 2b8f6b6ad5 | |||
| a5cf996ed2 | |||
| 71c2b855ec | |||
| 06c5f4503f | |||
| bede123691 | |||
| 83c937ae8f | |||
| d39dfb5334 | |||
| 44cfd7ab69 | |||
| b277baa76d | |||
| 5db1cfdac8 | |||
| a4d99e5d4d | |||
| 01ad1ff073 | |||
| 8e1d609ca1 | |||
| 10afec2532 | |||
| 66a9db13c3 | |||
| d9042c6510 | |||
| 870057a680 | |||
| e444cd1e04 | |||
| 6bc5bd30d5 | |||
| 56367402d7 | |||
| 035accf553 | |||
| 55f21c5436 | |||
| d1b99852a8 | |||
| 66bb30cef5 | |||
| 068a885bff | |||
| 7d453beb65 | |||
| 49c9caa317 | |||
| b06e9693c5 | |||
| e4e700c362 | |||
| d317f1fb06 | |||
| edd223b083 | |||
| 945c5b397b | |||
| 5017bf5f92 | |||
| 95d5c46e43 | |||
| 4a55ff9bf2 | |||
| 27ff4a3a0a | |||
| eddee05e41 | |||
| ccc7fd8e04 | |||
| eb810e62e9 | |||
| 3a50be05f8 | |||
| 168d8d616c | |||
| 16775c6f1e | |||
| 385b7d5a3c | |||
| d857999fdf | |||
| fa2609f4ce | |||
| a91a995407 | |||
| eb5875fe0d | |||
| 56b8e83a09 | |||
| a66d91fa31 | |||
| b69572eee7 | |||
| b771856330 | |||
| c7ca889e9c |
Vendored
+5
-1
@@ -78,7 +78,11 @@
|
||||
"*.ipp": "cpp",
|
||||
"unordered_set": "cpp",
|
||||
"forward_list": "cpp",
|
||||
"barrier": "cpp"
|
||||
"barrier": "cpp",
|
||||
"strstream": "cpp",
|
||||
"regex": "cpp",
|
||||
"stacktrace": "cpp",
|
||||
"stdfloat": "cpp"
|
||||
},
|
||||
"editor.rulers": [80, 120],
|
||||
"editor.tabSize": 4,
|
||||
|
||||
+56
-6
@@ -4,6 +4,7 @@ project(salmanoff VERSION 0.01.000 LANGUAGES CXX)
|
||||
include(CMakeDependentOption)
|
||||
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/DAPSS.cmake)
|
||||
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/DebugOpts.cmake)
|
||||
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/VerifyBoostDynamic.cmake)
|
||||
|
||||
# Set C++ standard
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
@@ -32,6 +33,22 @@ if(NOT MRNTT_DEVMGR_REATTACHER_PERIOD_MS GREATER 0)
|
||||
"MRNTT_DEVMGR_REATTACHER_PERIOD_MS must be a positive integer > 0")
|
||||
endif()
|
||||
|
||||
# Stimulus buffer frame period configuration
|
||||
set(STIMBUFF_FRAME_PERIOD_MS 33
|
||||
CACHE STRING "Stimulus buffer frame period (ms)")
|
||||
if(NOT STIMBUFF_FRAME_PERIOD_MS GREATER 0)
|
||||
message(FATAL_ERROR
|
||||
"STIMBUFF_FRAME_PERIOD_MS must be a positive integer > 0")
|
||||
endif()
|
||||
|
||||
# Stimulus buffer frame retry delay configuration
|
||||
set(STIMBUFF_FRAME_RETRY_DELAY_MS 1
|
||||
CACHE STRING "Stimulus buffer frame retry delay (ms)")
|
||||
if(NOT STIMBUFF_FRAME_RETRY_DELAY_MS GREATER 0)
|
||||
message(FATAL_ERROR
|
||||
"STIMBUFF_FRAME_RETRY_DELAY_MS must be a positive integer > 0")
|
||||
endif()
|
||||
|
||||
# World thread configuration
|
||||
option(WORLD_USE_BODY_THREAD
|
||||
"Use body thread for world component instead of separate world thread" OFF)
|
||||
@@ -44,6 +61,14 @@ if(ENABLE_DEBUG_LOCKS)
|
||||
set(CONFIG_ENABLE_DEBUG_LOCKS TRUE)
|
||||
endif()
|
||||
|
||||
# Set the debug trace callables variable for config.h
|
||||
if(ENABLE_DEBUG_TRACE_CALLABLES)
|
||||
set(CONFIG_DEBUG_TRACE_CALLABLES TRUE)
|
||||
# Suppress frame-address warnings when using __builtin_return_address()
|
||||
# with values above 0 (See callableTracer.h).
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-frame-address")
|
||||
endif()
|
||||
|
||||
# Set the world thread variable for config.h
|
||||
if(WORLD_USE_BODY_THREAD)
|
||||
set(CONFIG_WORLD_USE_BODY_THREAD TRUE)
|
||||
@@ -51,6 +76,10 @@ endif()
|
||||
|
||||
# Set the timeout variable for config.h
|
||||
set(CONFIG_DEBUG_QUTEX_DEADLOCK_TIMEOUT_MS ${DEBUG_QUTEX_DEADLOCK_TIMEOUT_MS})
|
||||
# Set the stimulus buffer frame period variable for config.h
|
||||
set(CONFIG_STIMBUFF_FRAME_PERIOD_MS ${STIMBUFF_FRAME_PERIOD_MS})
|
||||
# Set the stimulus buffer frame retry delay variable for config.h
|
||||
set(CONFIG_STIMBUFF_FRAME_RETRY_DELAY_MS ${STIMBUFF_FRAME_RETRY_DELAY_MS})
|
||||
|
||||
# Configure config.h
|
||||
configure_file(
|
||||
@@ -67,10 +96,22 @@ include_directories(
|
||||
)
|
||||
|
||||
# Find core dependencies
|
||||
# Boost 1.72.0 is required to ensure that a certain bug where boost::asio
|
||||
# objects depend on specific copies of symbols, and boost will cause a segfault
|
||||
# if boost::asio objects are used inside of a dlopen()'d library, is fixed.
|
||||
find_package(Boost 1.73.0 REQUIRED COMPONENTS system)
|
||||
# We cannot use header-only Boost.Asio because we need both our dlopen()'d
|
||||
# libraries and the main binary to refer to the same instances of boost::asio's
|
||||
# metadata. If we use header-only Boost.Asio, each dlopen()'d library will have
|
||||
# its own copy of boost::asio's metadata, which will cause a segfault if
|
||||
# boost::asio objects are used inside of a dlopen()'d library.
|
||||
#
|
||||
# Honestly, I never liked this whole "header-only" idea so I'm happy to be rid
|
||||
# of it.
|
||||
#
|
||||
# Tell CMake we're linking against the shared library (not header-only)
|
||||
set(Boost_USE_STATIC_LIBS OFF)
|
||||
set(Boost_USE_HEADER_ONLY OFF)
|
||||
find_package(Boost REQUIRED COMPONENTS system log)
|
||||
# Define BOOST_ALL_DYN_LINK project-wide to ensure all Boost libraries use dynamic linking
|
||||
add_compile_definitions(BOOST_ALL_DYN_LINK)
|
||||
|
||||
find_package(PkgConfig REQUIRED)
|
||||
find_package(FLEX REQUIRED)
|
||||
find_package(BISON REQUIRED)
|
||||
@@ -85,19 +126,28 @@ endif()
|
||||
if(ENABLE_TESTS)
|
||||
add_subdirectory(third_party)
|
||||
endif()
|
||||
add_subdirectory(compile)
|
||||
# Add core components
|
||||
add_subdirectory(smocore)
|
||||
add_subdirectory(commonLibs)
|
||||
add_subdirectory(senseApis)
|
||||
add_subdirectory(stimBuffApis)
|
||||
add_subdirectory(wilzorApis)
|
||||
add_subdirectory(devices)
|
||||
|
||||
# Main executable
|
||||
add_executable(salmanoff main.cpp)
|
||||
target_link_libraries(salmanoff
|
||||
Boost::system Boost::log
|
||||
smocore
|
||||
${Boost_LIBRARIES}
|
||||
${DL_LIBRARY}
|
||||
attachmentSupport
|
||||
)
|
||||
|
||||
# Verify Boost dynamic dependencies after build
|
||||
add_custom_command(TARGET salmanoff POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -DVERIFY_FILE="$<TARGET_FILE:salmanoff>"
|
||||
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/VerifyBoostDynamic.cmake
|
||||
COMMENT "Verifying Boost dynamic dependencies for salmanoff"
|
||||
)
|
||||
|
||||
# Add all registered DAPSS targets as dependencies
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
# Enable debug locking features
|
||||
option(ENABLE_DEBUG_LOCKS "Enable debug features for locking system" ON)
|
||||
|
||||
# Enable callable tracing for debugging boost::asio post operations
|
||||
option(ENABLE_DEBUG_TRACE_CALLABLES "Enable callable tracing for debugging boost::asio post operations" OFF)
|
||||
|
||||
# Qutex deadlock detection configuration
|
||||
# Always define the variable in cache so it appears in ccmake
|
||||
set(DEBUG_QUTEX_DEADLOCK_TIMEOUT_MS 500 CACHE STRING
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
# SMO_VERIFY_BOOST_DYNAMIC_DEPENDENCY
|
||||
# Verifies that a target file (executable or shared library) has Boost libraries
|
||||
# in its dynamic dependency list via ldd.
|
||||
#
|
||||
# Usage as function:
|
||||
# SMO_VERIFY_BOOST_DYNAMIC_DEPENDENCY(<target_file>)
|
||||
#
|
||||
# Usage as script (with -P):
|
||||
# cmake -DVERIFY_FILE=<target_file> -P VerifyBoostDynamic.cmake
|
||||
#
|
||||
# This function/script:
|
||||
# 1. Runs ldd on the target file
|
||||
# 2. Checks for boost libraries in the dependency list
|
||||
# 3. Reports success or failure with appropriate messages
|
||||
#
|
||||
function(SMO_VERIFY_BOOST_DYNAMIC_DEPENDENCY target_file)
|
||||
_verify_boost_dynamic_dependency("${target_file}")
|
||||
endfunction()
|
||||
|
||||
# Internal implementation that can be called from script mode or function mode
|
||||
function(_verify_boost_dynamic_dependency target_file)
|
||||
if(NOT EXISTS "${target_file}")
|
||||
message(WARNING "SMO_VERIFY_BOOST_DYNAMIC_DEPENDENCY: Target file '${target_file}' does not exist")
|
||||
return()
|
||||
endif()
|
||||
|
||||
# Run ldd on the target file
|
||||
execute_process(
|
||||
COMMAND ldd "${target_file}"
|
||||
OUTPUT_VARIABLE ldd_output
|
||||
ERROR_VARIABLE ldd_error
|
||||
RESULT_VARIABLE ldd_result
|
||||
)
|
||||
|
||||
if(ldd_result)
|
||||
message(WARNING "SMO_VERIFY_BOOST_DYNAMIC_DEPENDENCY: Failed to run ldd on '${target_file}': ${ldd_error}")
|
||||
return()
|
||||
endif()
|
||||
|
||||
# Check if output contains boost libraries
|
||||
string(TOLOWER "${ldd_output}" ldd_output_lower)
|
||||
string(FIND "${ldd_output_lower}" "libboost" boost_found)
|
||||
|
||||
if(boost_found EQUAL -1)
|
||||
message(STATUS "SMO_VERIFY_BOOST_DYNAMIC_DEPENDENCY: WARNING - No Boost libraries found in dependencies of '${target_file}'")
|
||||
message(STATUS "ldd output:")
|
||||
message(STATUS "${ldd_output}")
|
||||
else()
|
||||
# Extract boost library lines
|
||||
string(REGEX MATCHALL "libboost[^\n]*" boost_libs "${ldd_output}")
|
||||
message(STATUS "SMO_VERIFY_BOOST_DYNAMIC_DEPENDENCY: SUCCESS - Boost libraries found in '${target_file}':")
|
||||
foreach(boost_lib ${boost_libs})
|
||||
string(STRIP "${boost_lib}" boost_lib_stripped)
|
||||
message(STATUS " ${boost_lib_stripped}")
|
||||
endforeach()
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
# Script mode: if VERIFY_FILE is defined, run the verification
|
||||
if(VERIFY_FILE)
|
||||
_verify_boost_dynamic_dependency("${VERIFY_FILE}")
|
||||
endif()
|
||||
|
||||
@@ -31,7 +31,7 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||
# Search for libraries and headers in the target directories
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE BOTH)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
|
||||
|
||||
# Set pkg-config to use the cross-compiled libraries
|
||||
set(ENV{PKG_CONFIG_PATH} "/usr/aarch64-linux-gnu/lib/pkgconfig:/usr/lib/aarch64-linux-gnu/pkgconfig")
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
# ----------------------------------------------------------------------------------
|
||||
# MANDATORY USER VARIABLE
|
||||
# ----------------------------------------------------------------------------------
|
||||
# IMPORTANT: This variable MUST be set when running CMake to specify where the
|
||||
# laptop's sysroot (the root directory of the mounted laptop filesystem) is located.
|
||||
#
|
||||
# Usage example: cmake -DCMAKE_TOOLCHAIN_FILE=laptop_x86_sysroot.cmake
|
||||
# -DTARGET_SYSROOT=/mnt/laptop_sysroot/ <path_to_source>
|
||||
#
|
||||
# If the variable is not defined, we fall back to a common system root path for safety.
|
||||
if(NOT DEFINED TARGET_SYSROOT)
|
||||
set(TARGET_SYSROOT "/usr/lib/x86_64-linux-gnu")
|
||||
message(STATUS "TARGET_SYSROOT not explicitly defined. Defaulting to ${TARGET_SYSROOT}")
|
||||
endif()
|
||||
message(STATUS "Using TARGET_SYSROOT: ${TARGET_SYSROOT}")
|
||||
|
||||
set(TARGET_TRIPLE x86_64-linux-gnu) # Standard Debian/Ubuntu triple
|
||||
|
||||
# ----------------------------------------------------------------------------------
|
||||
# SYSROOT and COMPILER CONFIGURATION
|
||||
# ----------------------------------------------------------------------------------
|
||||
|
||||
set(CMAKE_CROSSCOMPILING TRUE)
|
||||
set(CMAKE_SYSROOT ${TARGET_SYSROOT})
|
||||
message(STATUS "Using CMAKE_SYSROOT: ${CMAKE_SYSROOT}")
|
||||
|
||||
# The CMAKE_FIND_ROOT_PATH tells CMake where to look for programs, libraries, etc.
|
||||
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
|
||||
|
||||
# 1. Architecture and Platform Identification
|
||||
set(CMAKE_SYSTEM_NAME Linux)
|
||||
set(CMAKE_SYSTEM_PROCESSOR x86_64)
|
||||
|
||||
set(CMAKE_C_COMPILER ${TARGET_TRIPLE}-gcc)
|
||||
set(CMAKE_CXX_COMPILER ${TARGET_TRIPLE}-g++)
|
||||
|
||||
# ----------------------------------------------------------------------------------
|
||||
# PKG-CONFIG CONFIGURATION (CRUCIAL FOR CROSS-COMPILING)
|
||||
# ----------------------------------------------------------------------------------
|
||||
|
||||
# 1. Define the search path for .pc files, relative to the sysroot.
|
||||
# This ensures we look in the target's standard pkgconfig locations.
|
||||
set(PKG_CONFIG_SEARCH_PATHS
|
||||
"${CMAKE_SYSROOT}/usr/lib/${TARGET_TRIPLE}/pkgconfig" # Primary location on Debian/Ubuntu
|
||||
"${CMAKE_SYSROOT}/usr/share/pkgconfig" # Secondary shared location
|
||||
"${CMAKE_SYSROOT}/usr/lib/pkgconfig" # Another common location
|
||||
)
|
||||
|
||||
# Join the paths using the system's path separator (colon on Linux)
|
||||
string(REPLACE ";" ":" PKG_CONFIG_LIBDIR_STRING "${PKG_CONFIG_SEARCH_PATHS}")
|
||||
|
||||
# Set the environment variable PKG_CONFIG_LIBDIR
|
||||
# This tells pkg-config exactly where to find the x86_64 .pc files.
|
||||
# 2. Set the sysroot directory for pkg-config
|
||||
# This tells pkg-config to prepend CMAKE_SYSROOT to any paths it finds in the .pc files.
|
||||
set(ENV{PKG_CONFIG_SYSROOT_DIR} ${CMAKE_SYSROOT})
|
||||
set(ENV{PKG_CONFIG_LIBDIR} ${PKG_CONFIG_LIBDIR_STRING})
|
||||
set(ENV{PKG_CONFIG_PATH} "")
|
||||
|
||||
message(STATUS "PKG_CONFIG_SYSROOT_DIR set to: ${CMAKE_SYSROOT}")
|
||||
message(STATUS "PKG_CONFIG_LIBDIR set to: ${PKG_CONFIG_LIBDIR_STRING}")
|
||||
|
||||
# ----------------------------------------------------------------------------------
|
||||
# CMAkE FIND BEHAVIOR
|
||||
# ----------------------------------------------------------------------------------
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
add_subdirectory(xcbXorg)
|
||||
add_subdirectory(livoxProto1)
|
||||
add_subdirectory(attachmentSupport)
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
add_library(attachmentSupport SHARED
|
||||
stimulusBuffer.cpp
|
||||
)
|
||||
|
||||
target_include_directories(attachmentSupport PUBLIC
|
||||
${Boost_INCLUDE_DIRS}
|
||||
${CMAKE_SOURCE_DIR}/include
|
||||
${CMAKE_BINARY_DIR}/include
|
||||
)
|
||||
|
||||
target_link_libraries(attachmentSupport PUBLIC
|
||||
Boost::system
|
||||
Boost::log
|
||||
)
|
||||
|
||||
# Verify Boost dynamic dependencies after build
|
||||
add_custom_command(TARGET attachmentSupport POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -DVERIFY_FILE="$<TARGET_FILE:attachmentSupport>"
|
||||
-P ${CMAKE_SOURCE_DIR}/cmake/VerifyBoostDynamic.cmake
|
||||
COMMENT "Verifying Boost dynamic dependencies for attachmentSupport"
|
||||
)
|
||||
|
||||
# Install rules
|
||||
install(TARGETS attachmentSupport DESTINATION lib)
|
||||
@@ -0,0 +1,103 @@
|
||||
#include <boostAsioLinkageFix.h>
|
||||
#include <iostream>
|
||||
#include <config.h>
|
||||
#include <componentThread.h>
|
||||
#include <boost/asio/io_service.hpp>
|
||||
#include <boost/asio/deadline_timer.hpp>
|
||||
#include <boost/system/error_code.hpp>
|
||||
#include <spinLock.h>
|
||||
#include <asynchronousBridge.h>
|
||||
#include <user/stimulusBuffer.h>
|
||||
|
||||
namespace smo {
|
||||
namespace stim_buff {
|
||||
|
||||
void StimulusBuffer::stop()
|
||||
{
|
||||
shouldContinue.store(false);
|
||||
|
||||
// Set up a timeout bridge using the io_service
|
||||
boost::asio::deadline_timer delayTimer(ioService);
|
||||
AsynchronousBridge bridge(ioService);
|
||||
|
||||
// Set up the delay to let in-flight operation finish
|
||||
delayTimer.expires_from_now(
|
||||
boost::posix_time::milliseconds(getStopDelayMs()));
|
||||
|
||||
delayTimer.async_wait(
|
||||
[&bridge](const boost::system::error_code& error)
|
||||
{
|
||||
(void)error;
|
||||
|
||||
// Always signal complete, whether timeout expired or was cancelled
|
||||
bridge.setAsyncOperationComplete();
|
||||
});
|
||||
|
||||
bridge.waitForAsyncOperationCompleteOrIoServiceStopped();
|
||||
|
||||
std::cout << __func__ << ": Stopped stimulus buffer for device "
|
||||
<< deviceAttachmentSpec->deviceSelector << std::endl;
|
||||
|
||||
// After delay, cancel timer and perform cleanup
|
||||
timer.cancel();
|
||||
}
|
||||
|
||||
void StimulusBuffer::scheduleNextTimeout(int delayMs)
|
||||
{
|
||||
if (!shouldContinue.load())
|
||||
{ return; }
|
||||
|
||||
// Schedule the next timeout using the provided delay
|
||||
timer.expires_from_now(
|
||||
boost::posix_time::milliseconds(delayMs));
|
||||
|
||||
timer.async_wait(
|
||||
std::bind(
|
||||
&StimulusBuffer::onTimeout, this, std::placeholders::_1));
|
||||
}
|
||||
|
||||
void StimulusBuffer::onTimeout(const boost::system::error_code& error)
|
||||
{
|
||||
// Timer was cancelled, which is expected when stopping
|
||||
if (error == boost::asio::error::operation_aborted) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (error)
|
||||
{
|
||||
std::cerr << "StimulusBuffer: Timer error: " << error.message()
|
||||
<< std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!shouldContinue.load())
|
||||
{ return; }
|
||||
|
||||
/** EXPLANATION:
|
||||
* We need to ensure that there's only ever one stimframe being produced
|
||||
* during any CONFIG_STIMBUFF_FRAME_PERIOD_MS period. To guarantee this, we
|
||||
* use a spinlock.
|
||||
*
|
||||
* When a new frame is to be produced, the async producer will first acquire
|
||||
* the frameAssemblyLimiter spinlock. This way, when the next timeout is
|
||||
* fired it can check whether its predecessor stimframe has finished being
|
||||
* produced. If the preceding stimframe is still being produced, then we'll
|
||||
* sleep for CONFIG_STIMBUFF_FRAME_RETRY_DELAY_MS ms before trying again.
|
||||
*/
|
||||
int nextWakeupDelayMs;
|
||||
if (frameAssemblyRateLimiter.tryAcquire())
|
||||
{ nextWakeupDelayMs = CONFIG_STIMBUFF_FRAME_PERIOD_MS; }
|
||||
else
|
||||
{ nextWakeupDelayMs = CONFIG_STIMBUFF_FRAME_RETRY_DELAY_MS; }
|
||||
|
||||
// Call the derived class's frame production handler
|
||||
stimFrameProductionTimesliceInd();
|
||||
// Note: The lock should be released when frame production completes
|
||||
|
||||
// Schedule next timeout with the pre-determined duration
|
||||
scheduleNextTimeout(nextWakeupDelayMs);
|
||||
}
|
||||
|
||||
} // namespace stim_buff
|
||||
} // namespace smo
|
||||
|
||||
@@ -7,12 +7,22 @@ if(ENABLE_LIB_livoxProto1)
|
||||
device.cpp
|
||||
protocol.cpp
|
||||
broadcastListener.cpp
|
||||
udpCommandDemuxer.cpp
|
||||
)
|
||||
|
||||
# Set config define for header generation
|
||||
add_compile_definitions(CONFIG_LIB_LIVOXPROTO1_ENABLED)
|
||||
target_include_directories(livoxProto1 PUBLIC ${Boost_INCLUDE_DIRS})
|
||||
target_link_libraries(livoxProto1 ${Boost_LIBRARIES})
|
||||
target_link_libraries(livoxProto1 PUBLIC
|
||||
Boost::system Boost::log
|
||||
attachmentSupport)
|
||||
|
||||
# Verify Boost dynamic dependencies after build
|
||||
add_custom_command(TARGET livoxProto1 POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -DVERIFY_FILE="$<TARGET_FILE:livoxProto1>"
|
||||
-P ${CMAKE_SOURCE_DIR}/cmake/VerifyBoostDynamic.cmake
|
||||
COMMENT "Verifying Boost dynamic dependencies for livoxProto1"
|
||||
)
|
||||
|
||||
# Install rules
|
||||
install(TARGETS livoxProto1 DESTINATION lib)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <opts.h>
|
||||
#include <componentThread.h>
|
||||
#include "broadcastListener.h"
|
||||
|
||||
namespace livoxProto1 {
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
#ifndef BROADCAST_LISTENER_H
|
||||
#define BROADCAST_LISTENER_H
|
||||
|
||||
#include <boostAsioLinkageFix.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <atomic>
|
||||
#include <boost/asio/ip/udp.hpp>
|
||||
#include <user/senseApiDesc.h>
|
||||
#include "device.h"
|
||||
|
||||
|
||||
@@ -29,7 +29,8 @@ ProtoState& getProtoState()
|
||||
}
|
||||
|
||||
DeviceManager::DeviceManager()
|
||||
: broadcastListener(protoState.componentThread)
|
||||
: broadcastListener(protoState.componentThread),
|
||||
udpCommandDemuxer(protoState.componentThread, *this)
|
||||
{
|
||||
broadcastListener.setDeviceGoneAwayCb(deviceGoneAwayInd);
|
||||
}
|
||||
@@ -128,7 +129,7 @@ public:
|
||||
void DeviceManager::getOrCreateDeviceReq(
|
||||
const std::string &deviceIdentifier,
|
||||
const std::shared_ptr<smo::ComponentThread>& componentThread,
|
||||
int handshakeTimeoutMs, int retryDelayMs,
|
||||
int commandTimeoutMs, int retryDelayMs,
|
||||
const std::string& smoIp, uint8_t smoSubnetNbits,
|
||||
uint16_t dataPort, uint16_t cmdPort, uint16_t imuPort,
|
||||
smo::Callback<livoxProto1_getOrCreateDeviceReqCbFn> callback)
|
||||
@@ -162,7 +163,7 @@ void DeviceManager::getOrCreateDeviceReq(
|
||||
// Device doesn't exist, create a new one but don't add it to collection yet
|
||||
auto newDevice = std::make_shared<Device>(
|
||||
deviceIdentifier, componentThread,
|
||||
handshakeTimeoutMs, retryDelayMs,
|
||||
commandTimeoutMs, retryDelayMs,
|
||||
smoIp, smoSubnetNbits,
|
||||
dataPort, cmdPort, imuPort);
|
||||
|
||||
@@ -229,7 +230,7 @@ void DeviceManager::destroyDeviceReq(
|
||||
std::shared_ptr<Device> device = getDevice(dev->discoveredDevice).
|
||||
value_or(nullptr);
|
||||
|
||||
if (!device)
|
||||
if (!device || device->nAttachedStimBuffs > 0)
|
||||
{
|
||||
callback.callbackFn(false);
|
||||
return;
|
||||
@@ -245,7 +246,7 @@ void DeviceManager::destroyDeviceReq(
|
||||
}
|
||||
|
||||
void main(const std::shared_ptr<smo::ComponentThread> &componentThread,
|
||||
const smo::sense_api::SmoCallbacks& smoCallbacks)
|
||||
const smo::stim_buff::SmoCallbacks& smoCallbacks)
|
||||
{
|
||||
if (protoState.isInitialized) {
|
||||
return;
|
||||
@@ -256,6 +257,7 @@ void main(const std::shared_ptr<smo::ComponentThread> &componentThread,
|
||||
protoState.smoCallbacks = smoCallbacks;
|
||||
protoState.deviceManager = std::make_unique<DeviceManager>();
|
||||
protoState.deviceManager->broadcastListener.start();
|
||||
protoState.deviceManager->udpCommandDemuxer.start();
|
||||
}
|
||||
|
||||
void exit(void)
|
||||
@@ -264,10 +266,11 @@ void exit(void)
|
||||
return;
|
||||
}
|
||||
|
||||
protoState.deviceManager->udpCommandDemuxer.stop();
|
||||
protoState.deviceManager->broadcastListener.stop();
|
||||
protoState.deviceManager.reset();
|
||||
protoState.componentThread.reset();
|
||||
protoState.isInitialized = false;
|
||||
}
|
||||
|
||||
} // namespace livoxProto1
|
||||
} // namespace livoxProto1
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <user/senseApiDesc.h>
|
||||
#include "device.h"
|
||||
#include "broadcastListener.h"
|
||||
#include "udpCommandDemuxer.h"
|
||||
#include "livoxProto1.h"
|
||||
#include <callback.h>
|
||||
|
||||
@@ -25,7 +26,7 @@ public:
|
||||
void getOrCreateDeviceReq(
|
||||
const std::string &deviceIdentifier,
|
||||
const std::shared_ptr<smo::ComponentThread>& componentThread,
|
||||
int handshakeTimeoutMs, int retryDelayMs,
|
||||
int commandTimeoutMs, int retryDelayMs,
|
||||
const std::string& smoIp, uint8_t smoSubnetNbits,
|
||||
uint16_t dataPort, uint16_t cmdPort, uint16_t imuPort,
|
||||
smo::Callback<livoxProto1_getOrCreateDeviceReqCbFn> callback);
|
||||
@@ -50,6 +51,7 @@ private:
|
||||
public:
|
||||
std::vector<std::shared_ptr<Device>> devices;
|
||||
comms::BroadcastListener broadcastListener;
|
||||
comms::UdpCommandDemuxer udpCommandDemuxer;
|
||||
|
||||
// Nested continuation class for async device creation
|
||||
class GetOrCreateDeviceReq;
|
||||
@@ -58,7 +60,7 @@ public:
|
||||
|
||||
void main(
|
||||
const std::shared_ptr<smo::ComponentThread> &componentThread,
|
||||
const smo::sense_api::SmoCallbacks& smoCallbacks);
|
||||
const smo::stim_buff::SmoCallbacks& smoCallbacks);
|
||||
void exit(void);
|
||||
|
||||
// Global state structure
|
||||
@@ -67,7 +69,7 @@ struct ProtoState
|
||||
bool isInitialized = false;
|
||||
std::shared_ptr<smo::ComponentThread> componentThread;
|
||||
std::unique_ptr<DeviceManager> deviceManager;
|
||||
smo::sense_api::SmoCallbacks smoCallbacks;
|
||||
smo::stim_buff::SmoCallbacks smoCallbacks;
|
||||
};
|
||||
|
||||
// Access to global state for extern "C" functions
|
||||
|
||||
+1376
-188
File diff suppressed because it is too large
Load Diff
+122
-17
@@ -1,20 +1,33 @@
|
||||
#ifndef LIVOX_PROTO1_DEVICE_H
|
||||
#define LIVOX_PROTO1_DEVICE_H
|
||||
|
||||
#include <boostAsioLinkageFix.h>
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <atomic>
|
||||
#include <optional>
|
||||
#include <functional>
|
||||
#include <unordered_map>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <unistd.h>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/asio/deadline_timer.hpp>
|
||||
#include <boost/asio/posix/stream_descriptor.hpp>
|
||||
#include "protocol.h"
|
||||
#include <callback.h>
|
||||
|
||||
// Custom hash function for std::pair<uint8_t, uint8_t>
|
||||
namespace std {
|
||||
template<>
|
||||
struct hash<std::pair<uint8_t, uint8_t>> {
|
||||
size_t operator()(const std::pair<uint8_t, uint8_t>& p) const noexcept {
|
||||
return (static_cast<size_t>(p.first) << 8) | static_cast<size_t>(p.second);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Forward declaration
|
||||
namespace smo {
|
||||
class ComponentThread;
|
||||
@@ -62,25 +75,15 @@ class Device
|
||||
public:
|
||||
Device(const std::string &deviceIdentifier,
|
||||
const std::shared_ptr<smo::ComponentThread>& componentThread,
|
||||
int handshakeTimeoutMs, int retryDelayMs,
|
||||
int commandTimeoutMs, int retryDelayMs,
|
||||
const std::string& smoIp, uint8_t smoSubnetNbits,
|
||||
uint16_t dataPort, uint16_t cmdPort, uint16_t imuPort);
|
||||
~Device();
|
||||
|
||||
public:
|
||||
comms::DiscoveredDevice discoveredDevice;
|
||||
|
||||
// Configuration
|
||||
std::shared_ptr<smo::ComponentThread> componentThread;
|
||||
int handshakeTimeoutMs, retryDelayMs;
|
||||
std::string smoIp;
|
||||
std::string detectedSmoListeningIp;
|
||||
uint8_t smoSubnetNbits;
|
||||
uint16_t dataPort, cmdPort, imuPort;
|
||||
|
||||
private:
|
||||
// Heartbeat mechanism
|
||||
void startHeartbeat();
|
||||
void stopHeartbeat();
|
||||
void sendHeartbeat();
|
||||
void onHeartbeatTimer(const boost::system::error_code& error);
|
||||
std::string generateClientDeviceIpFromSerialNumber(
|
||||
@@ -95,21 +98,38 @@ private:
|
||||
class ConnectByDeviceIdentifierReq;
|
||||
class ExecuteHandshakeReq;
|
||||
class DisconnectReq;
|
||||
class EnablePcloudDataReq;
|
||||
class DisablePcloudDataReq;
|
||||
class SetReturnModeReq;
|
||||
class GetReturnModeReq;
|
||||
|
||||
public:
|
||||
enum class ReturnMode : uint8_t
|
||||
{
|
||||
SingleFirst = 0x00,
|
||||
SingleStrongest = 0x01,
|
||||
Dual = 0x02,
|
||||
Triple = 0x03
|
||||
};
|
||||
|
||||
// Utility methods
|
||||
std::optional<std::string> getSmoIp(const std::string& deviceIP);
|
||||
|
||||
// Callback function type definitions for async methods
|
||||
typedef std::function<void(bool success)> connectReqCbFn;
|
||||
typedef std::function<
|
||||
void(bool success, const std::string& ipAddr, int fd)>
|
||||
void(bool success, const std::string& ipAddr)>
|
||||
connectToKnownDeviceReqCbFn;
|
||||
typedef std::function<
|
||||
void(bool success, const std::string& ipAddr, int fd)>
|
||||
void(bool success, const std::string& ipAddr)>
|
||||
connectByDeviceIdentifierReqCbFn;
|
||||
typedef std::function<void(bool success, int fd)> executeHandshakeReqCbFn;
|
||||
typedef std::function<void(bool success)> executeHandshakeReqCbFn;
|
||||
typedef std::function<void(bool success)> disconnectReqCbFn;
|
||||
typedef std::function<void(bool success)> enablePcloudDataReqCbFn;
|
||||
typedef std::function<void(bool success)> disablePcloudDataReqCbFn;
|
||||
typedef std::function<void(bool success)> setReturnModeReqCbFn;
|
||||
typedef std::function<void(bool success, uint8_t returnMode)>
|
||||
getReturnModeReqCbFn;
|
||||
|
||||
// Async connection methods
|
||||
void connectReq(smo::Callback<connectReqCbFn> callback);
|
||||
@@ -121,11 +141,96 @@ public:
|
||||
const std::string& deviceIP,
|
||||
smo::Callback<executeHandshakeReqCbFn> callback);
|
||||
void disconnectReq(smo::Callback<disconnectReqCbFn> callback);
|
||||
void enablePcloudDataReq(smo::Callback<enablePcloudDataReqCbFn> callback);
|
||||
void disablePcloudDataReq(smo::Callback<disablePcloudDataReqCbFn> callback);
|
||||
void setReturnModeReq(
|
||||
uint8_t returnMode, smo::Callback<setReturnModeReqCbFn> callback);
|
||||
void getReturnModeReq(smo::Callback<getReturnModeReqCbFn> callback);
|
||||
|
||||
public:
|
||||
comms::DiscoveredDevice discoveredDevice;
|
||||
std::atomic<size_t> nAttachedStimBuffs;
|
||||
|
||||
// Configuration
|
||||
std::shared_ptr<smo::ComponentThread> componentThread;
|
||||
int commandTimeoutMs, retryDelayMs;
|
||||
std::string smoIp;
|
||||
std::string detectedSmoListeningIp;
|
||||
uint8_t smoSubnetNbits;
|
||||
uint16_t dataPort, cmdPort, imuPort;
|
||||
|
||||
// Heartbeat state
|
||||
std::unique_ptr<boost::asio::deadline_timer> heartbeatTimer;
|
||||
int heartbeatFd; // Socket file descriptor used for heartbeat
|
||||
std::atomic<bool> heartbeatActive;
|
||||
|
||||
// Point cloud data state
|
||||
std::atomic<bool> pcloudDataActive;
|
||||
|
||||
// Cached last-known return mode for this device
|
||||
ReturnMode currentReturnMode = ReturnMode::SingleFirst;
|
||||
|
||||
public:
|
||||
// UDP datagram handling
|
||||
void handleUdpDgram(
|
||||
const uint8_t* data, ssize_t bytesReceived,
|
||||
const struct sockaddr_in& senderAddr);
|
||||
|
||||
// Command handler registration
|
||||
void registerUdpCommandHandler(
|
||||
uint8_t cmd_set, uint8_t cmd_id,
|
||||
std::function<void(
|
||||
const uint8_t* data, ssize_t bytesReceived,
|
||||
const struct sockaddr_in& senderAddr)> handler,
|
||||
const std::string& deviceIP = "");
|
||||
|
||||
void unregisterUdpCommandHandler(
|
||||
uint8_t cmd_set, uint8_t cmd_id, const std::string& deviceIP = "");
|
||||
|
||||
private:
|
||||
// Point cloud data setup
|
||||
void cleanupPcloudDataSocket();
|
||||
|
||||
/** EXPLANATION:
|
||||
* This is the "straightforward" map of command set and command id to
|
||||
* handlers. This is useful for any commands which are guaranteed to be
|
||||
* issued to the device *AFTER* the device has successfully been added
|
||||
* to the DeviceManager's list of devices.
|
||||
*
|
||||
* I.e: it cannot be used for commands which are issued to the device before
|
||||
* getOrCreateDevice() has added the device to the DeviceManager's list of
|
||||
* devices.
|
||||
*/
|
||||
// Command handler map
|
||||
std::unordered_map<
|
||||
std::pair<uint8_t, uint8_t>,
|
||||
std::function<void(
|
||||
const uint8_t* data, ssize_t bytesReceived,
|
||||
const struct sockaddr_in& senderAddr)>> udpCommandHandlers;
|
||||
|
||||
public:
|
||||
/** EXPLANATION:
|
||||
* This is the "temporary" map of command set and command id to
|
||||
* handlers. This is useful for any commands which are issued to the device
|
||||
* while it is being constructed.
|
||||
*
|
||||
* I.e: it shouldn't be used for cmds which are issued to the device after
|
||||
* getOrCreateDevice() has added the device to the DeviceManager's list of
|
||||
* devices. It will work for such commands, but we'd kind of prefer to use
|
||||
* the "straightforward" map above for such commands.
|
||||
*
|
||||
* NOTE:
|
||||
* There's a strong argument to be made for just getting rid of the
|
||||
* "straightforward" map above and just using this one, tho.
|
||||
*/
|
||||
struct CommandHandler {
|
||||
uint8_t cmd_set;
|
||||
uint8_t cmd_id;
|
||||
std::function<void(
|
||||
const uint8_t* data, ssize_t bytesReceived,
|
||||
const struct sockaddr_in& senderAddr)> handler;
|
||||
};
|
||||
static std::unordered_map<std::string, std::vector<CommandHandler>>
|
||||
devicesUnderConstruction;
|
||||
};
|
||||
|
||||
} // namespace livoxProto1
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
#include <boostAsioLinkageFix.h>
|
||||
#include <stdexcept>
|
||||
#include <callback.h>
|
||||
#include <boost/asio/posix/stream_descriptor.hpp>
|
||||
#include "livoxProto1.h"
|
||||
#include "device.h"
|
||||
#include "core.h"
|
||||
#include "udpCommandDemuxer.h"
|
||||
|
||||
|
||||
extern "C" {
|
||||
@@ -10,7 +13,7 @@ extern "C" {
|
||||
void livoxProto1_getOrCreateDeviceReq(
|
||||
const std::string& deviceIdentifier,
|
||||
const std::shared_ptr<smo::ComponentThread>& componentThread,
|
||||
int handshakeTimeoutMs, int retryDelayMs,
|
||||
int commandTimeoutMs, int retryDelayMs,
|
||||
const std::string& smoIp, uint8_t smoSubnetNbits,
|
||||
uint16_t dataPort, uint16_t cmdPort, uint16_t imuPort,
|
||||
smo::Callback<livoxProto1_getOrCreateDeviceReqCbFn> callback
|
||||
@@ -28,7 +31,7 @@ void livoxProto1_getOrCreateDeviceReq(
|
||||
// Delegate to DeviceManager
|
||||
protoState.deviceManager->getOrCreateDeviceReq(
|
||||
deviceIdentifier, componentThread,
|
||||
handshakeTimeoutMs, retryDelayMs,
|
||||
commandTimeoutMs, retryDelayMs,
|
||||
smoIp, smoSubnetNbits,
|
||||
dataPort, cmdPort, imuPort,
|
||||
callback);
|
||||
@@ -52,7 +55,7 @@ void livoxProto1_destroyDeviceReq(
|
||||
|
||||
void livoxProto1_main(
|
||||
const std::shared_ptr<smo::ComponentThread>& componentThread,
|
||||
const smo::sense_api::SmoCallbacks& smoCallbacks)
|
||||
const smo::stim_buff::SmoCallbacks& smoCallbacks)
|
||||
{
|
||||
livoxProto1::main(componentThread, smoCallbacks);
|
||||
}
|
||||
@@ -62,4 +65,59 @@ void livoxProto1_exit(void)
|
||||
livoxProto1::exit();
|
||||
}
|
||||
|
||||
void livoxProto1_device_enablePcloudDataReq(
|
||||
std::shared_ptr<livoxProto1::Device> device,
|
||||
smo::Callback<livoxProto1_device_enablePcloudDataReqCbFn> callback
|
||||
)
|
||||
{
|
||||
if (!device)
|
||||
{
|
||||
throw std::runtime_error(std::string(__func__)
|
||||
+ ": Device pointer is null");
|
||||
}
|
||||
|
||||
device->enablePcloudDataReq(callback);
|
||||
}
|
||||
|
||||
void livoxProto1_device_disablePcloudDataReq(
|
||||
std::shared_ptr<livoxProto1::Device> device,
|
||||
smo::Callback<livoxProto1_device_disablePcloudDataReqCbFn> callback
|
||||
)
|
||||
{
|
||||
if (!device)
|
||||
{
|
||||
throw std::runtime_error(std::string(__func__)
|
||||
+ ": Device pointer is null");
|
||||
}
|
||||
|
||||
device->disablePcloudDataReq(callback);
|
||||
}
|
||||
|
||||
void livoxProto1_device_getReturnModeReq(
|
||||
std::shared_ptr<livoxProto1::Device> device,
|
||||
smo::Callback<livoxProto1_device_getReturnModeReqCbFn> callback
|
||||
)
|
||||
{
|
||||
if (!device)
|
||||
{
|
||||
throw std::runtime_error(std::string(__func__)
|
||||
+ ": Device pointer is null");
|
||||
}
|
||||
|
||||
device->getReturnModeReq(callback);
|
||||
}
|
||||
|
||||
std::shared_ptr<boost::asio::posix::stream_descriptor>
|
||||
livoxProto1_getPcloudDataFdDesc(void)
|
||||
{
|
||||
auto& protoState = livoxProto1::getProtoState();
|
||||
if (!protoState.deviceManager)
|
||||
{
|
||||
throw std::runtime_error(std::string(__func__)
|
||||
+ ": DeviceManager not initialized");
|
||||
}
|
||||
|
||||
return protoState.deviceManager->udpCommandDemuxer.getPcloudDataFdDesc();
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
#ifndef LIVOXPROTO1_H
|
||||
#define LIVOXPROTO1_H
|
||||
|
||||
#include <boostAsioLinkageFix.h>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <callback.h>
|
||||
#include <boost/asio/posix/stream_descriptor.hpp>
|
||||
|
||||
// Forward declarations
|
||||
namespace smo {
|
||||
namespace sense_api {
|
||||
namespace stim_buff {
|
||||
struct SmoCallbacks;
|
||||
}
|
||||
class ComponentThread;
|
||||
@@ -30,7 +32,7 @@ extern "C" {
|
||||
*/
|
||||
typedef void livoxProto1_mainFn(
|
||||
const std::shared_ptr<smo::ComponentThread>& componentThread,
|
||||
const smo::sense_api::SmoCallbacks& smoCallbacks);
|
||||
const smo::stim_buff::SmoCallbacks& smoCallbacks);
|
||||
|
||||
/**
|
||||
* Cleanup the Livox protocol library
|
||||
@@ -41,7 +43,7 @@ typedef void livoxProto1_exitFn(void);
|
||||
* Create a new Livox device connection
|
||||
* @param deviceIdentifier The device identifier (broadcast code)
|
||||
* @param componentThread Component thread for async operations
|
||||
* @param handshakeTimeoutMs Handshake timeout in milliseconds (default: 1000)
|
||||
* @param commandTimeoutMs Command timeout in milliseconds (default: 1000)
|
||||
* @param retryDelayMs Retry delay in milliseconds (default: 3000)
|
||||
* @param smoIp SMO IP address (empty string for auto-detection)
|
||||
* @param smoSubnetNbits SMO subnet mask bits (e.g., 24 for /24, 16 for /16)
|
||||
@@ -57,7 +59,7 @@ typedef std::function<
|
||||
typedef void livoxProto1_getOrCreateDeviceReqFn(
|
||||
const std::string& deviceIdentifier,
|
||||
const std::shared_ptr<smo::ComponentThread>& componentThread,
|
||||
int handshakeTimeoutMs, int retryDelayMs,
|
||||
int commandTimeoutMs, int retryDelayMs,
|
||||
const std::string& smoIp, uint8_t smoSubnetNbits,
|
||||
uint16_t dataPort, uint16_t cmdPort, uint16_t imuPort,
|
||||
smo::Callback<livoxProto1_getOrCreateDeviceReqCbFn> callback);
|
||||
@@ -67,14 +69,39 @@ typedef void livoxProto1_destroyDeviceReqFn(
|
||||
std::shared_ptr<livoxProto1::Device> device,
|
||||
smo::Callback<livoxProto1_destroyDeviceReqCbFn> callback);
|
||||
|
||||
typedef std::function<void(bool success)>
|
||||
livoxProto1_device_enablePcloudDataReqCbFn;
|
||||
typedef void livoxProto1_device_enablePcloudDataReqFn(
|
||||
std::shared_ptr<livoxProto1::Device> device,
|
||||
smo::Callback<livoxProto1_device_enablePcloudDataReqCbFn> callback);
|
||||
|
||||
typedef std::function<void(bool success)>
|
||||
livoxProto1_device_disablePcloudDataReqCbFn;
|
||||
typedef void livoxProto1_device_disablePcloudDataReqFn(
|
||||
std::shared_ptr<livoxProto1::Device> device,
|
||||
smo::Callback<livoxProto1_device_disablePcloudDataReqCbFn> callback);
|
||||
|
||||
typedef std::function<void(bool success, uint8_t returnMode)>
|
||||
livoxProto1_device_getReturnModeReqCbFn;
|
||||
typedef void livoxProto1_device_getReturnModeReqFn(
|
||||
std::shared_ptr<livoxProto1::Device> device,
|
||||
smo::Callback<livoxProto1_device_getReturnModeReqCbFn> callback);
|
||||
|
||||
typedef std::shared_ptr<boost::asio::posix::stream_descriptor>
|
||||
livoxProto1_getPcloudDataFdDescFn(void);
|
||||
|
||||
livoxProto1_mainFn livoxProto1_main;
|
||||
livoxProto1_exitFn livoxProto1_exit;
|
||||
livoxProto1_getOrCreateDeviceReqFn livoxProto1_getOrCreateDeviceReq;
|
||||
livoxProto1_destroyDeviceReqFn livoxProto1_destroyDeviceReq;
|
||||
livoxProto1_device_enablePcloudDataReqFn livoxProto1_device_enablePcloudDataReq;
|
||||
livoxProto1_device_disablePcloudDataReqFn
|
||||
livoxProto1_device_disablePcloudDataReq;
|
||||
livoxProto1_device_getReturnModeReqFn livoxProto1_device_getReturnModeReq;
|
||||
livoxProto1_getPcloudDataFdDescFn livoxProto1_getPcloudDataFdDesc;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // LIVOXPROTO1_H
|
||||
#endif // LIVOXPROTO1_H
|
||||
|
||||
@@ -230,7 +230,6 @@ uint32_t HandshakeRequest::calculateCrc32() const
|
||||
return comms::calculateCrc32(messageData, messageSize);
|
||||
}
|
||||
|
||||
|
||||
void HandshakeRequest::swapContentsToProtocolEndianness()
|
||||
{
|
||||
// Protocol uses little-endian, so on little-endian machines, no swap needed
|
||||
@@ -246,26 +245,6 @@ void HandshakeRequest::swapContentsToProtocolEndianness()
|
||||
// Note: footer.swapToHostEndianness() swaps CRC, so we skip it here
|
||||
}
|
||||
|
||||
void HandshakeRequest::swapContentsToHostEndianness()
|
||||
{
|
||||
if (endian::isLittleEndian()) { return; }
|
||||
header.swapToHostEndianness();
|
||||
command.swapToHostEndianness();
|
||||
data_port = __builtin_bswap16(data_port);
|
||||
cmd_port = __builtin_bswap16(cmd_port);
|
||||
imu_port = __builtin_bswap16(imu_port);
|
||||
// Note: footer.swapToHostEndianness() swaps CRC, so we skip it here
|
||||
}
|
||||
|
||||
|
||||
bool HandshakeRequest::sanityCheck() const
|
||||
{
|
||||
return header.sanityCheck() &&
|
||||
command.sanityCheck() &&
|
||||
(command.cmd_set == 0x00) && (command.cmd_id == 0x01) &&
|
||||
footer.sanityCheck();
|
||||
}
|
||||
|
||||
// HandshakeResponse methods
|
||||
void HandshakeResponse::swapContentsToHostEndianness()
|
||||
{
|
||||
@@ -583,7 +562,6 @@ uint32_t HeartbeatMessage::calculateCrc32() const
|
||||
return comms::calculateCrc32(messageData, messageSize);
|
||||
}
|
||||
|
||||
|
||||
void HeartbeatMessage::swapContentsToProtocolEndianness()
|
||||
{
|
||||
// Protocol is little-endian, so if host is already little-endian, no swap needed
|
||||
@@ -598,46 +576,6 @@ void HeartbeatMessage::swapContentsToProtocolEndianness()
|
||||
// Note: footer.swapToProtocolEndianness() swaps CRC, so we skip it here
|
||||
}
|
||||
|
||||
void HeartbeatMessage::swapContentsToHostEndianness()
|
||||
{
|
||||
// If host is already little-endian, no swap needed
|
||||
if (endian::isLittleEndian()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Host is big-endian, need to swap from little-endian protocol to big-endian host
|
||||
// Only swap content fields, not CRC fields
|
||||
header.swapToHostEndianness();
|
||||
command.swapToHostEndianness();
|
||||
// Note: footer.swapToHostEndianness() swaps CRC, so we skip it here
|
||||
}
|
||||
|
||||
|
||||
bool HeartbeatMessage::sanityCheck() const
|
||||
{
|
||||
return header.sanityCheck() &&
|
||||
command.sanityCheck() &&
|
||||
(command.cmd_set == 0x00) && (command.cmd_id == 0x03) &&
|
||||
footer.sanityCheck();
|
||||
}
|
||||
|
||||
bool HeartbeatMessage::validateCrc32() const
|
||||
{
|
||||
// Use the calculateCrc32 method to avoid code duplication
|
||||
uint32_t calculatedCrc = calculateCrc32();
|
||||
|
||||
// Compare with the CRC in the footer
|
||||
bool isValid = (calculatedCrc == footer.crc_32);
|
||||
|
||||
// Debug output only if validation fails
|
||||
if (!isValid) {
|
||||
std::cout << "HeartbeatMessage CRC32 Debug: calculated=0x" << std::hex << calculatedCrc
|
||||
<< ", received=0x" << footer.crc_32 << std::dec << std::endl;
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
// DisconnectMessage methods
|
||||
DisconnectMessage::DisconnectMessage()
|
||||
{
|
||||
@@ -681,26 +619,69 @@ void DisconnectMessage::swapContentsToProtocolEndianness()
|
||||
// Note: footer.swapToProtocolEndianness() swaps CRC, so we skip it here
|
||||
}
|
||||
|
||||
bool DisconnectMessage::sanityCheck() const
|
||||
// StartStopSamplingMessage methods
|
||||
StartStopSamplingMessage::StartStopSamplingMessage()
|
||||
{
|
||||
return header.sanityCheck() &&
|
||||
command.sanityCheck() &&
|
||||
(command.cmd_set == 0x00) && (command.cmd_id == 0x06) &&
|
||||
footer.sanityCheck();
|
||||
// Initialize header
|
||||
header.sof = 0xAA;
|
||||
header.version = 1;
|
||||
header.length = sizeof(StartStopSamplingMessage);
|
||||
header.cmd_type = 0x02; // MSG type
|
||||
header.seq_num = 0; // Will be set by caller if needed
|
||||
header.crc_16 = 0; // Will be calculated
|
||||
|
||||
// Initialize command
|
||||
command.cmd_set = 0x00; // General command set
|
||||
command.cmd_id = 0x04; // Sampling command ID
|
||||
|
||||
// Initialize data - enable flag will be set manually by caller
|
||||
enable = 0x00; // Default to stop, caller will override
|
||||
|
||||
// Initialize footer
|
||||
footer.crc_32 = 0; // Will be calculated
|
||||
}
|
||||
|
||||
bool DisconnectMessage::validateCrc32() const
|
||||
uint32_t StartStopSamplingMessage::calculateCrc32() const
|
||||
{
|
||||
// Use the calculateCrc32 method to avoid code duplication
|
||||
uint32_t calculatedCrc = calculateCrc32();
|
||||
// Calculate CRC32 for the entire message excluding the footer CRC32 field
|
||||
const uint8_t* messageData = reinterpret_cast<const uint8_t*>(this);
|
||||
size_t messageSize = sizeof(StartStopSamplingMessage) - sizeof(footer.crc_32);
|
||||
|
||||
// Compare with the CRC in the footer
|
||||
return comms::calculateCrc32(messageData, messageSize);
|
||||
}
|
||||
|
||||
void StartStopSamplingMessage::swapContentsToProtocolEndianness()
|
||||
{
|
||||
header.swapToProtocolEndianness();
|
||||
command.swapToProtocolEndianness();
|
||||
}
|
||||
|
||||
// SamplingResponse methods
|
||||
void SamplingResponse::swapContentsToHostEndianness()
|
||||
{
|
||||
header.swapToHostEndianness();
|
||||
command.swapToHostEndianness();
|
||||
footer.swapToHostEndianness();
|
||||
}
|
||||
|
||||
bool SamplingResponse::sanityCheck() const
|
||||
{
|
||||
return header.sanityCheck() && command.sanityCheck() && footer.sanityCheck();
|
||||
}
|
||||
|
||||
bool SamplingResponse::validateCrc32() const
|
||||
{
|
||||
// Calculate CRC32 for the entire message excluding the footer CRC32 field
|
||||
const uint8_t* messageData = reinterpret_cast<const uint8_t*>(this);
|
||||
size_t messageSize = sizeof(SamplingResponse) - sizeof(footer.crc_32);
|
||||
|
||||
uint32_t calculatedCrc = comms::calculateCrc32(messageData, messageSize);
|
||||
bool isValid = (calculatedCrc == footer.crc_32);
|
||||
|
||||
// Debug output only if validation fails
|
||||
if (!isValid)
|
||||
{
|
||||
std::cout << "DisconnectMessage CRC32 Debug: calculated=0x"
|
||||
std::cout << "SamplingResponse CRC32 Debug: calculated=0x"
|
||||
<< std::hex << calculatedCrc
|
||||
<< ", received=0x" << footer.crc_32 << std::dec << std::endl;
|
||||
}
|
||||
@@ -708,5 +689,166 @@ bool DisconnectMessage::validateCrc32() const
|
||||
return isValid;
|
||||
}
|
||||
|
||||
// HeartbeatACK methods
|
||||
void HeartbeatACK::swapContentsToHostEndianness()
|
||||
{
|
||||
if (endian::isLittleEndian()) { return; }
|
||||
// Only swap content fields, not CRC fields
|
||||
header.swapToHostEndianness();
|
||||
command.swapToHostEndianness();
|
||||
ack_msg = __builtin_bswap32(ack_msg);
|
||||
// Note: footer.swapToHostEndianness() swaps CRC, so we skip it here
|
||||
}
|
||||
|
||||
bool HeartbeatACK::sanityCheck() const
|
||||
{
|
||||
return header.sanityCheck() &&
|
||||
command.sanityCheck() &&
|
||||
(command.cmd_set == 0x00) && (command.cmd_id == 0x03) &&
|
||||
footer.sanityCheck();
|
||||
}
|
||||
|
||||
bool HeartbeatACK::validateCrc32() const
|
||||
{
|
||||
// Calculate CRC32 for the entire message excluding the footer.crc_32 field
|
||||
const uint8_t* messageData = reinterpret_cast<const uint8_t*>(this);
|
||||
size_t messageSize = sizeof(HeartbeatACK) - sizeof(footer.crc_32);
|
||||
uint32_t calculatedCrc = comms::calculateCrc32(messageData, messageSize);
|
||||
|
||||
// Compare with the CRC in the footer
|
||||
bool isValid = (calculatedCrc == footer.crc_32);
|
||||
|
||||
// Debug output only if validation fails
|
||||
if (!isValid) {
|
||||
std::cout << "HeartbeatACK CRC32 Debug: calculated=0x" << std::hex << calculatedCrc
|
||||
<< ", received=0x" << footer.crc_32 << std::dec << std::endl;
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
// SetLiDARReturnMode methods
|
||||
SetLiDARReturnMode::SetLiDARReturnMode()
|
||||
{
|
||||
// Initialize header
|
||||
header.sof = 0xAA;
|
||||
header.version = 0x01;
|
||||
header.length = sizeof(SetLiDARReturnMode);
|
||||
header.crc_16 = 0; // Will be calculated later
|
||||
|
||||
// Initialize command
|
||||
command.cmd_set = 0x01; // LiDAR Command
|
||||
command.cmd_id = 0x06; // Set LiDAR Return Mode
|
||||
|
||||
// Initialize mode (default to Single Return First)
|
||||
mode = 0x00;
|
||||
|
||||
// Initialize footer
|
||||
footer.crc_32 = 0; // Will be calculated later
|
||||
}
|
||||
|
||||
uint32_t SetLiDARReturnMode::calculateCrc32() const
|
||||
{
|
||||
const uint8_t* messageData = reinterpret_cast<const uint8_t*>(this);
|
||||
size_t messageSize = sizeof(SetLiDARReturnMode) - sizeof(footer.crc_32);
|
||||
return comms::calculateCrc32(messageData, messageSize);
|
||||
}
|
||||
|
||||
void SetLiDARReturnMode::swapContentsToProtocolEndianness()
|
||||
{
|
||||
if (endian::isLittleEndian()) { return; }
|
||||
header.swapToProtocolEndianness();
|
||||
command.swapToProtocolEndianness();
|
||||
// mode is uint8_t, no endianness conversion needed
|
||||
footer.swapToProtocolEndianness();
|
||||
}
|
||||
|
||||
// SetLiDARReturnModeResponse methods
|
||||
void SetLiDARReturnModeResponse::swapContentsToHostEndianness()
|
||||
{
|
||||
if (endian::isLittleEndian()) { return; }
|
||||
header.swapToHostEndianness();
|
||||
command.swapToHostEndianness();
|
||||
// ret_code is uint8_t, no endianness conversion needed
|
||||
// Note: footer.swapToHostEndianness() swaps CRC, so we skip it here
|
||||
}
|
||||
|
||||
bool SetLiDARReturnModeResponse::sanityCheck() const
|
||||
{
|
||||
return header.sanityCheck() &&
|
||||
command.sanityCheck() &&
|
||||
(command.cmd_set == 0x01) && (command.cmd_id == 0x06) &&
|
||||
(ret_code <= 0x01) && // Valid return codes: 0x00-0x01
|
||||
footer.sanityCheck();
|
||||
}
|
||||
|
||||
bool SetLiDARReturnModeResponse::validateCrc32() const
|
||||
{
|
||||
const uint8_t* messageData = reinterpret_cast<const uint8_t*>(this);
|
||||
size_t messageSize = sizeof(SetLiDARReturnModeResponse) - sizeof(footer.crc_32);
|
||||
uint32_t calculatedCrc = comms::calculateCrc32(messageData, messageSize);
|
||||
return (calculatedCrc == footer.crc_32);
|
||||
}
|
||||
|
||||
// GetLiDARReturnMode methods
|
||||
GetLiDARReturnMode::GetLiDARReturnMode()
|
||||
{
|
||||
// Initialize header
|
||||
header.sof = 0xAA;
|
||||
header.version = 0x01;
|
||||
header.length = sizeof(GetLiDARReturnMode);
|
||||
header.crc_16 = 0; // Will be calculated later
|
||||
|
||||
// Initialize command
|
||||
command.cmd_set = 0x01; // LiDAR Command
|
||||
command.cmd_id = 0x07; // Get LiDAR Return Mode
|
||||
|
||||
// Initialize footer
|
||||
footer.crc_32 = 0; // Will be calculated later
|
||||
}
|
||||
|
||||
uint32_t GetLiDARReturnMode::calculateCrc32() const
|
||||
{
|
||||
const uint8_t* messageData = reinterpret_cast<const uint8_t*>(this);
|
||||
size_t messageSize = sizeof(GetLiDARReturnMode) - sizeof(footer.crc_32);
|
||||
return comms::calculateCrc32(messageData, messageSize);
|
||||
}
|
||||
|
||||
void GetLiDARReturnMode::swapContentsToProtocolEndianness()
|
||||
{
|
||||
if (endian::isLittleEndian()) { return; }
|
||||
header.swapToProtocolEndianness();
|
||||
command.swapToProtocolEndianness();
|
||||
footer.swapToProtocolEndianness();
|
||||
}
|
||||
|
||||
// GetLiDARReturnModeResponse methods
|
||||
void GetLiDARReturnModeResponse::swapContentsToHostEndianness()
|
||||
{
|
||||
if (endian::isLittleEndian()) { return; }
|
||||
header.swapToHostEndianness();
|
||||
command.swapToHostEndianness();
|
||||
// ret_code and mode are uint8_t, no endianness conversion needed
|
||||
// Note: footer.swapToHostEndianness() swaps CRC, so we skip it here
|
||||
}
|
||||
|
||||
bool GetLiDARReturnModeResponse::sanityCheck() const
|
||||
{
|
||||
return header.sanityCheck() &&
|
||||
command.sanityCheck() &&
|
||||
(command.cmd_set == 0x01) && (command.cmd_id == 0x07) &&
|
||||
(ret_code <= 0x01) && // Valid return codes: 0x00-0x01
|
||||
(mode <= 0x03) && // Valid modes: 0x00-0x03
|
||||
footer.sanityCheck();
|
||||
}
|
||||
|
||||
bool GetLiDARReturnModeResponse::validateCrc32() const
|
||||
{
|
||||
const uint8_t* messageData = reinterpret_cast<const uint8_t*>(this);
|
||||
size_t messageSize = sizeof(GetLiDARReturnModeResponse) - sizeof(footer.crc_32);
|
||||
uint32_t calculatedCrc = comms::calculateCrc32(messageData, messageSize);
|
||||
return (calculatedCrc == footer.crc_32);
|
||||
}
|
||||
|
||||
} // namespace comms
|
||||
} // namespace livoxProto1
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#ifndef LIVOXPROTO1_PROTOCOL_H
|
||||
#define LIVOXPROTO1_PROTOCOL_H
|
||||
|
||||
#include <boostAsioLinkageFix.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
@@ -191,8 +192,6 @@ struct HandshakeRequest
|
||||
// Calculate CRC32 for the entire message
|
||||
uint32_t calculateCrc32() const;
|
||||
void swapContentsToProtocolEndianness();
|
||||
void swapContentsToHostEndianness();
|
||||
bool sanityCheck() const;
|
||||
} __attribute__((packed));
|
||||
|
||||
/** EXPLANATION:
|
||||
@@ -224,9 +223,6 @@ struct HeartbeatMessage
|
||||
HeartbeatMessage();
|
||||
uint32_t calculateCrc32() const;
|
||||
void swapContentsToProtocolEndianness();
|
||||
void swapContentsToHostEndianness();
|
||||
bool sanityCheck() const;
|
||||
bool validateCrc32() const;
|
||||
} __attribute__((packed));
|
||||
|
||||
/** EXPLANATION:
|
||||
@@ -242,6 +238,119 @@ struct DisconnectMessage
|
||||
DisconnectMessage();
|
||||
uint32_t calculateCrc32() const;
|
||||
void swapContentsToProtocolEndianness();
|
||||
} __attribute__((packed));
|
||||
|
||||
/** EXPLANATION:
|
||||
* Complete start/stop sampling command frame for enabling/disabling point cloud data from Livox devices.
|
||||
* This is the complete wire format including header, command fields, data, and footer.
|
||||
*/
|
||||
struct StartStopSamplingMessage
|
||||
{
|
||||
Header header; // 0-8: Protocol frame header
|
||||
Command command; // 9-10: Command identification
|
||||
uint8_t enable; // 11: Enable flag (0x01 = Start, 0x00 = Stop)
|
||||
Footer footer; // 12-15: Protocol frame footer
|
||||
|
||||
StartStopSamplingMessage();
|
||||
uint32_t calculateCrc32() const;
|
||||
void swapContentsToProtocolEndianness();
|
||||
} __attribute__((packed));
|
||||
|
||||
/** EXPLANATION:
|
||||
* Complete sampling response frame from Livox devices.
|
||||
* This is the complete wire format including header, command fields, data, and footer.
|
||||
*/
|
||||
struct SamplingResponse
|
||||
{
|
||||
Header header; // 0-8: Protocol frame header
|
||||
Command command; // 9-10: Command identification
|
||||
uint8_t ret_code; // 11: Return Code (0x00 = Success, 0x01 = Fail)
|
||||
Footer footer; // 12-15: Protocol frame footer
|
||||
|
||||
void swapContentsToHostEndianness();
|
||||
bool sanityCheck() const;
|
||||
bool validateCrc32() const;
|
||||
} __attribute__((packed));
|
||||
|
||||
/** EXPLANATION:
|
||||
* Complete heartbeat ACK response frame from Livox devices.
|
||||
* This is the complete wire format including header, command fields, data, and footer.
|
||||
*/
|
||||
struct HeartbeatACK
|
||||
{
|
||||
Header header; // 0-8: Protocol frame header
|
||||
Command command; // 9-10: Command identification
|
||||
uint8_t ret_code; // 11: Return Code (0x00 = Success, 0x01 = Fail)
|
||||
uint8_t work_state; // 12: LiDAR/Hub State (0x00: Initializing, 0x01: Normal, 0x02: Power-Saving, 0x03: Standby, 0x04: Error)
|
||||
uint8_t feature_msg; // 13: LiDAR Feature Message (Bit0: Rain/Fog Suppression Switch)
|
||||
uint32_t ack_msg; // 14-17: ACK Message (Initialization Progress or Status Code)
|
||||
Footer footer; // 18-21: Protocol frame footer
|
||||
|
||||
void swapContentsToHostEndianness();
|
||||
bool sanityCheck() const;
|
||||
bool validateCrc32() const;
|
||||
} __attribute__((packed));
|
||||
|
||||
/** EXPLANATION:
|
||||
* Complete set LiDAR return mode command frame for Livox devices.
|
||||
* This is the complete wire format including header, command fields, data, and footer.
|
||||
*/
|
||||
struct SetLiDARReturnMode
|
||||
{
|
||||
Header header; // 0-8: Protocol frame header
|
||||
Command command; // 9-10: Command identification
|
||||
uint8_t mode; // 11: Return Mode (0x00: Single Return First, 0x01: Single Return Strongest, 0x02: Dual Return, 0x03: Triple Return)
|
||||
Footer footer; // 12-15: Protocol frame footer
|
||||
|
||||
SetLiDARReturnMode();
|
||||
uint32_t calculateCrc32() const;
|
||||
void swapContentsToProtocolEndianness();
|
||||
} __attribute__((packed));
|
||||
|
||||
/** EXPLANATION:
|
||||
* Complete set LiDAR return mode response frame from Livox devices.
|
||||
* This is the complete wire format including header, command fields, data, and footer.
|
||||
*/
|
||||
struct SetLiDARReturnModeResponse
|
||||
{
|
||||
Header header; // 0-8: Protocol frame header
|
||||
Command command; // 9-10: Command identification
|
||||
uint8_t ret_code; // 11: Return Code (0x00 = Success, 0x01 = Fail)
|
||||
Footer footer; // 12-15: Protocol frame footer
|
||||
|
||||
void swapContentsToHostEndianness();
|
||||
bool sanityCheck() const;
|
||||
bool validateCrc32() const;
|
||||
} __attribute__((packed));
|
||||
|
||||
/** EXPLANATION:
|
||||
* Complete get LiDAR return mode command frame for Livox devices.
|
||||
* This is the complete wire format including header, command fields, data, and footer.
|
||||
*/
|
||||
struct GetLiDARReturnMode
|
||||
{
|
||||
Header header; // 0-8: Protocol frame header
|
||||
Command command; // 9-10: Command identification
|
||||
Footer footer; // 11-14: Protocol frame footer
|
||||
|
||||
GetLiDARReturnMode();
|
||||
uint32_t calculateCrc32() const;
|
||||
void swapContentsToProtocolEndianness();
|
||||
} __attribute__((packed));
|
||||
|
||||
/** EXPLANATION:
|
||||
* Complete get LiDAR return mode response frame from Livox devices.
|
||||
* This is the complete wire format including header, command fields, data, and footer.
|
||||
*/
|
||||
struct GetLiDARReturnModeResponse
|
||||
{
|
||||
Header header; // 0-8: Protocol frame header
|
||||
Command command; // 9-10: Command identification
|
||||
uint8_t ret_code; // 11: Return Code (0x00 = Success, 0x01 = Fail)
|
||||
uint8_t mode; // 12: Return Mode (0x00: Single Return First, 0x01: Single Return Strongest, 0x02: Dual Return, 0x03: Triple Return)
|
||||
Footer footer; // 13-16: Protocol frame footer
|
||||
|
||||
void swapContentsToHostEndianness();
|
||||
bool sanityCheck() const;
|
||||
bool validateCrc32() const;
|
||||
} __attribute__((packed));
|
||||
|
||||
@@ -0,0 +1,331 @@
|
||||
#include <iostream>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "udpCommandDemuxer.h"
|
||||
#include "core.h"
|
||||
#include "device.h"
|
||||
|
||||
namespace livoxProto1 {
|
||||
namespace comms {
|
||||
|
||||
UdpCommandDemuxer::UdpCommandDemuxer(
|
||||
const std::shared_ptr<smo::ComponentThread> &componentThread,
|
||||
DeviceManager &deviceManager,
|
||||
uint16_t commandPort,
|
||||
uint16_t dataPort
|
||||
)
|
||||
: componentThread(componentThread), deviceManager(deviceManager),
|
||||
commandPort(commandPort), dataPort(dataPort),
|
||||
senderAddrLen(sizeof(senderAddr))
|
||||
{
|
||||
}
|
||||
|
||||
UdpCommandDemuxer::~UdpCommandDemuxer()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
void UdpCommandDemuxer::start()
|
||||
{
|
||||
if (isActive.load())
|
||||
{
|
||||
std::cerr << __func__ << ": Demuxer is already running"
|
||||
<< std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
setupSockets();
|
||||
isActive.store(true);
|
||||
shouldStop.store(false);
|
||||
|
||||
// Start the async receive loop
|
||||
startAsyncReceive();
|
||||
|
||||
std::cout
|
||||
<< __func__ << ": UDP Command Demuxer started on port "
|
||||
<< commandPort << std::endl;
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
std::cerr
|
||||
<< __func__ << ": Failed to start demuxer: "
|
||||
<< e.what() << std::endl;
|
||||
isActive.store(false);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void UdpCommandDemuxer::stop()
|
||||
{
|
||||
if (!isActive.load())
|
||||
{ return; }
|
||||
|
||||
shouldStop.store(true);
|
||||
|
||||
// Close socket and cleanup
|
||||
if (cmdEndpointFdDesc)
|
||||
{
|
||||
cmdEndpointFdDesc->cancel();
|
||||
cmdEndpointFdDesc.reset();
|
||||
}
|
||||
|
||||
if (pcloudDataFdDesc)
|
||||
{
|
||||
pcloudDataFdDesc->cancel();
|
||||
pcloudDataFdDesc.reset();
|
||||
}
|
||||
|
||||
isActive.store(false);
|
||||
std::cout
|
||||
<< __func__ << ": UDP Command Demuxer stopped"
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
void UdpCommandDemuxer::setupSockets()
|
||||
{
|
||||
setupCommandSocket();
|
||||
setupPcloudDataSocket();
|
||||
}
|
||||
|
||||
void UdpCommandDemuxer::setupCommandSocket()
|
||||
{
|
||||
// RAII class to manage socket file descriptor
|
||||
struct SocketRAII
|
||||
{
|
||||
int fd;
|
||||
SocketRAII(int socketFd) : fd(socketFd) {}
|
||||
~SocketRAII() { if (fd >= 0) close(fd); }
|
||||
void commit() { fd = -1; } // Transfer ownership, prevent close
|
||||
int getFd() const { return fd; }
|
||||
bool isValid() const { return fd >= 0; }
|
||||
};
|
||||
|
||||
// Create UDP socket
|
||||
SocketRAII socketGuard(socket(AF_INET, SOCK_DGRAM, 0));
|
||||
if (!socketGuard.isValid())
|
||||
{
|
||||
throw std::runtime_error(
|
||||
std::string(__func__)
|
||||
+ ": Failed to create socket: " + strerror(errno));
|
||||
}
|
||||
|
||||
// Set socket to non-blocking mode
|
||||
int flags = fcntl(socketGuard.getFd(), F_GETFL, 0);
|
||||
if (flags < 0 || fcntl(
|
||||
socketGuard.getFd(), F_SETFL, flags | O_NONBLOCK) < 0)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
std::string(__func__)
|
||||
+ ": Failed to set non-blocking mode: " + strerror(errno));
|
||||
}
|
||||
|
||||
// Bind to command port
|
||||
struct sockaddr_in localAddr;
|
||||
memset(&localAddr, 0, sizeof(localAddr));
|
||||
localAddr.sin_family = AF_INET;
|
||||
localAddr.sin_addr.s_addr = INADDR_ANY;
|
||||
localAddr.sin_port = htons(commandPort);
|
||||
|
||||
if (bind(
|
||||
socketGuard.getFd(), (struct sockaddr *)&localAddr,
|
||||
sizeof(localAddr)) < 0)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
std::string(__func__) + ": Failed to bind to port "
|
||||
+ std::to_string(commandPort) + ": " + strerror(errno));
|
||||
}
|
||||
|
||||
// Create boost wrapper for async operations
|
||||
cmdEndpointFdDesc = std::make_shared<boost::asio::posix::stream_descriptor>(
|
||||
componentThread->getIoService(), socketGuard.getFd());
|
||||
|
||||
// Transfer ownership, prevent auto-close
|
||||
socketGuard.commit();
|
||||
}
|
||||
|
||||
void UdpCommandDemuxer::setupPcloudDataSocket()
|
||||
{
|
||||
// RAII class to manage socket file descriptor
|
||||
struct SocketRAII
|
||||
{
|
||||
int fd;
|
||||
SocketRAII(int socketFd) : fd(socketFd) {}
|
||||
~SocketRAII() { if (fd >= 0) close(fd); }
|
||||
void commit() { fd = -1; } // Transfer ownership, prevent close
|
||||
int getFd() const { return fd; }
|
||||
bool isValid() const { return fd >= 0; }
|
||||
};
|
||||
|
||||
// Create UDP socket for point cloud data reception
|
||||
SocketRAII socketGuard(socket(AF_INET, SOCK_DGRAM, 0));
|
||||
if (!socketGuard.isValid())
|
||||
{
|
||||
throw std::runtime_error(
|
||||
std::string(__func__)
|
||||
+ ": Failed to create socket: " + strerror(errno));
|
||||
}
|
||||
|
||||
// Set socket to non-blocking mode
|
||||
int flags = fcntl(socketGuard.getFd(), F_GETFL, 0);
|
||||
if (flags < 0 ||
|
||||
fcntl(socketGuard.getFd(), F_SETFL, flags | O_NONBLOCK) < 0)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
std::string(__func__)
|
||||
+ ": Failed to set non-blocking mode: " + strerror(errno));
|
||||
}
|
||||
|
||||
// Bind to the data port
|
||||
struct sockaddr_in localAddr;
|
||||
memset(&localAddr, 0, sizeof(localAddr));
|
||||
localAddr.sin_family = AF_INET;
|
||||
localAddr.sin_addr.s_addr = INADDR_ANY;
|
||||
localAddr.sin_port = htons(dataPort);
|
||||
|
||||
if (bind(
|
||||
socketGuard.getFd(), (struct sockaddr *)&localAddr,
|
||||
sizeof(localAddr)) < 0)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
std::string(__func__) + ": Failed to bind to data port: "
|
||||
+ std::to_string(dataPort) + ": " + strerror(errno));
|
||||
}
|
||||
|
||||
// Create boost wrapper for async operations
|
||||
pcloudDataFdDesc = std::make_shared<boost::asio::posix::stream_descriptor>(
|
||||
componentThread->getIoService(), socketGuard.getFd());
|
||||
|
||||
// Transfer ownership, prevent auto-close
|
||||
socketGuard.commit();
|
||||
}
|
||||
|
||||
void UdpCommandDemuxer::startAsyncReceive()
|
||||
{
|
||||
if (!isActive.load() || shouldStop.load())
|
||||
{ return; }
|
||||
|
||||
cmdEndpointFdDesc->async_wait(
|
||||
boost::asio::posix::stream_descriptor::wait_read,
|
||||
std::bind(
|
||||
&UdpCommandDemuxer::onDataReady, this, std::placeholders::_1));
|
||||
}
|
||||
|
||||
void UdpCommandDemuxer::onDataReady(const boost::system::error_code &error)
|
||||
{
|
||||
if (error)
|
||||
{
|
||||
if (error != boost::asio::error::operation_aborted)
|
||||
{
|
||||
std::cerr
|
||||
<< __func__ << ": Socket error: "
|
||||
<< error.message() << std::endl;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isActive.load() || shouldStop.load())
|
||||
{ return; }
|
||||
|
||||
// Read the data
|
||||
bytesReceived = recvfrom(
|
||||
cmdEndpointFdDesc->native_handle(), receiveBuffer,
|
||||
sizeof(receiveBuffer), 0,
|
||||
(struct sockaddr *)&senderAddr, &senderAddrLen);
|
||||
|
||||
if (bytesReceived > 0) {
|
||||
processIncomingData();
|
||||
}
|
||||
else if (bytesReceived < 0)
|
||||
{
|
||||
if (errno != EAGAIN && errno != EWOULDBLOCK)
|
||||
{
|
||||
std::cerr << __func__ << ": recvfrom error: "
|
||||
<< strerror(errno) << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// Continue listening for more data
|
||||
startAsyncReceive();
|
||||
}
|
||||
|
||||
void UdpCommandDemuxer::processIncomingData()
|
||||
{
|
||||
if (bytesReceived < 2)
|
||||
{
|
||||
// Too small to contain any meaningful data
|
||||
return;
|
||||
}
|
||||
|
||||
// Extract source IP address
|
||||
char sourceIP[INET_ADDRSTRLEN];
|
||||
inet_ntop(AF_INET, &senderAddr.sin_addr, sourceIP, INET_ADDRSTRLEN);
|
||||
|
||||
// First, find device with matching IP address in DeviceManager collection
|
||||
for (const auto &device : deviceManager.devices)
|
||||
{
|
||||
if (device->discoveredDevice.ipAddr != sourceIP) { continue; }
|
||||
|
||||
// Found matching device, route the datagram to it
|
||||
try
|
||||
{
|
||||
device->handleUdpDgram(
|
||||
receiveBuffer, bytesReceived, senderAddr);
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
std::cerr
|
||||
<< __func__ << ": Device handler exception for IP "
|
||||
<< sourceIP << ": " << e.what() << std::endl;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// If not found in DeviceManager, check temporary collection (devices under construction)
|
||||
auto tempIt = livoxProto1::Device::devicesUnderConstruction.find(sourceIP);
|
||||
if (tempIt != livoxProto1::Device::devicesUnderConstruction.end())
|
||||
{
|
||||
// Extract command set and command ID from the datagram
|
||||
if (bytesReceived >= static_cast<ssize_t>(
|
||||
sizeof(livoxProto1::comms::Header) + sizeof(livoxProto1::comms::Command)))
|
||||
{
|
||||
uint8_t cmd_set = receiveBuffer[sizeof(livoxProto1::comms::Header)];
|
||||
uint8_t cmd_id = receiveBuffer[sizeof(livoxProto1::comms::Header) + 1];
|
||||
|
||||
// Found matching device in temporary collection, invoke matching handlers
|
||||
for (const auto& cmdHandler : tempIt->second)
|
||||
{
|
||||
if (cmdHandler.cmd_set != cmd_set || cmdHandler.cmd_id != cmd_id)
|
||||
{ continue; }
|
||||
|
||||
try
|
||||
{
|
||||
cmdHandler.handler(receiveBuffer, bytesReceived, senderAddr);
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
std::cerr
|
||||
<< __func__ << ": Temporary device handler exception for IP "
|
||||
<< sourceIP << ": " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// No device found with matching IP in either collection, discard the data
|
||||
std::cerr
|
||||
<< __func__ << ": No device found for source IP "
|
||||
<< sourceIP << ", discarding datagram" << std::endl;
|
||||
}
|
||||
|
||||
} // namespace comms
|
||||
} // namespace livoxProto1
|
||||
@@ -0,0 +1,97 @@
|
||||
#ifndef UDP_COMMAND_DEMUXER_H
|
||||
#define UDP_COMMAND_DEMUXER_H
|
||||
|
||||
#include <boostAsioLinkageFix.h>
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <boost/asio/posix/stream_descriptor.hpp>
|
||||
#include <componentThread.h>
|
||||
|
||||
namespace livoxProto1 {
|
||||
|
||||
// Forward declarations
|
||||
class DeviceManager;
|
||||
|
||||
namespace comms {
|
||||
|
||||
/**
|
||||
* UdpCommandDemuxer - Routes UDP command datagrams to appropriate devices
|
||||
*
|
||||
* This class listens on the command port (65000) for incoming UDP datagrams
|
||||
* from Livox devices and routes them to the appropriate Device based on
|
||||
* the source IP address.
|
||||
*
|
||||
* The reason we need a whole class for this is because we use the same port
|
||||
* numbers for all connected devices, so we have no way to distinguish between
|
||||
* devices except based on the devices' IP addrs. Since all commands are sent
|
||||
* over UDP, our sockets don't have built-in binding to a specific source IP.
|
||||
*
|
||||
* So we need to discriminate between source IPs manually, and demultiplex
|
||||
* the dgrams received from different devices manually.
|
||||
*
|
||||
* We'll prolly also have to do the same thing for point cloud and IMU data, so
|
||||
* we'll prolly end up renaming this class to UdpResponseDemuxer.
|
||||
*/
|
||||
class UdpCommandDemuxer
|
||||
{
|
||||
public:
|
||||
UdpCommandDemuxer(
|
||||
const std::shared_ptr<smo::ComponentThread>& componentThread,
|
||||
DeviceManager& deviceManager,
|
||||
uint16_t commandPort = 56001,
|
||||
uint16_t dataPort = 56000);
|
||||
|
||||
~UdpCommandDemuxer();
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
bool isRunning() const { return isActive.load(); }
|
||||
|
||||
// Get shared pointer to command endpoint for handshake use
|
||||
std::shared_ptr<boost::asio::posix::stream_descriptor>
|
||||
getCmdEndpointFdDesc() const
|
||||
{
|
||||
return cmdEndpointFdDesc;
|
||||
}
|
||||
|
||||
// Get shared pointer to pcloud data fd for use in IoUringAssemblyEngine
|
||||
std::shared_ptr<boost::asio::posix::stream_descriptor>
|
||||
getPcloudDataFdDesc() const
|
||||
{
|
||||
return pcloudDataFdDesc;
|
||||
}
|
||||
|
||||
private:
|
||||
// Socket and async objects
|
||||
std::shared_ptr<boost::asio::posix::stream_descriptor> pcloudDataFdDesc;
|
||||
// Socket and async objects
|
||||
std::shared_ptr<boost::asio::posix::stream_descriptor> cmdEndpointFdDesc;
|
||||
|
||||
private:
|
||||
void setupSockets();
|
||||
void setupCommandSocket();
|
||||
void setupPcloudDataSocket();
|
||||
void startAsyncReceive();
|
||||
void onDataReady(const boost::system::error_code& error);
|
||||
void processIncomingData();
|
||||
|
||||
std::shared_ptr<smo::ComponentThread> componentThread;
|
||||
DeviceManager& deviceManager;
|
||||
uint16_t commandPort;
|
||||
uint16_t dataPort;
|
||||
|
||||
// State management
|
||||
std::atomic<bool> isActive{false};
|
||||
std::atomic<bool> shouldStop{false};
|
||||
|
||||
// Receive buffer
|
||||
uint8_t receiveBuffer[1024];
|
||||
struct sockaddr_in senderAddr;
|
||||
socklen_t senderAddrLen;
|
||||
ssize_t bytesReceived;
|
||||
};
|
||||
|
||||
} // namespace comms
|
||||
} // namespace livoxProto1
|
||||
|
||||
#endif // UDP_COMMAND_DEMUXER_H
|
||||
@@ -13,7 +13,7 @@ if(ENABLE_LIB_xcbXorg)
|
||||
# Set config define for header generation
|
||||
add_compile_definitions(CONFIG_LIB_XCBXORG_ENABLED)
|
||||
target_include_directories(xcbXorg PUBLIC ${XCB_INCLUDE_DIRS})
|
||||
target_link_libraries(xcbXorg ${XCB_LIBRARIES})
|
||||
target_link_libraries(xcbXorg ${XCB_LIBRARIES} attachmentSupport)
|
||||
|
||||
# Install rules
|
||||
install(TARGETS xcbXorg DESTINATION lib)
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
if(COMPILE_CL_CHECKS)
|
||||
find_package(OpenCL REQUIRED)
|
||||
|
||||
add_executable(clshmemlatency clshmemlatency.cpp)
|
||||
target_include_directories(clshmemlatency
|
||||
PUBLIC ${OpenCL_INCLUDE_DIRS})
|
||||
target_link_libraries(clshmemlatency
|
||||
${OpenCL_LIBRARY})
|
||||
add_executable(clshmemcheck clshmemcheck.cpp)
|
||||
target_include_directories(clshmemcheck
|
||||
PUBLIC ${OpenCL_INCLUDE_DIRS})
|
||||
target_link_libraries(clshmemcheck
|
||||
${OpenCL_LIBRARY})
|
||||
add_executable(clzerocopycheck clzerocopycheck.cpp)
|
||||
target_include_directories(clzerocopycheck
|
||||
PUBLIC ${OpenCL_INCLUDE_DIRS})
|
||||
target_link_libraries(clzerocopycheck
|
||||
${OpenCL_LIBRARY})
|
||||
endif()
|
||||
@@ -0,0 +1,90 @@
|
||||
#include <CL/cl.h>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
|
||||
void checkCLError(cl_int err, const char* msg) {
|
||||
if (err != CL_SUCCESS) {
|
||||
std::cerr << "OpenCL Error " << err << " at: " << msg << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
cl_uint numPlatforms = 0;
|
||||
checkCLError(clGetPlatformIDs(0, nullptr, &numPlatforms), "get num platforms");
|
||||
std::vector<cl_platform_id> platforms(numPlatforms);
|
||||
checkCLError(clGetPlatformIDs(numPlatforms, platforms.data(), nullptr), "get platforms");
|
||||
|
||||
std::cout << "Found " << numPlatforms << " OpenCL platforms\n\n";
|
||||
|
||||
for (cl_uint p = 0; p < numPlatforms; ++p) {
|
||||
char platformName[256];
|
||||
clGetPlatformInfo(platforms[p], CL_PLATFORM_NAME, sizeof(platformName), platformName, nullptr);
|
||||
std::cout << "Platform " << p << ": " << platformName << "\n";
|
||||
|
||||
cl_uint numDevices = 0;
|
||||
clGetDeviceIDs(platforms[p], CL_DEVICE_TYPE_ALL, 0, nullptr, &numDevices);
|
||||
std::vector<cl_device_id> devices(numDevices);
|
||||
clGetDeviceIDs(platforms[p], CL_DEVICE_TYPE_ALL, numDevices, devices.data(), nullptr);
|
||||
|
||||
for (cl_uint d = 0; d < numDevices; ++d) {
|
||||
char deviceName[256];
|
||||
clGetDeviceInfo(devices[d], CL_DEVICE_NAME, sizeof(deviceName), deviceName, nullptr);
|
||||
std::cout << " Device " << d << ": " << deviceName << "\n";
|
||||
|
||||
cl_bool unifiedMem = CL_FALSE;
|
||||
clGetDeviceInfo(devices[d], CL_DEVICE_HOST_UNIFIED_MEMORY, sizeof(unifiedMem), &unifiedMem, nullptr);
|
||||
std::cout << " Host-Device unified memory: " << (unifiedMem ? "Yes" : "No") << "\n";
|
||||
|
||||
#ifdef CL_DEVICE_SVM_CAPABILITIES
|
||||
cl_device_svm_capabilities svmCaps = 0;
|
||||
clGetDeviceInfo(devices[d], CL_DEVICE_SVM_CAPABILITIES, sizeof(svmCaps), &svmCaps, nullptr);
|
||||
std::cout << " SVM capabilities:\n";
|
||||
if (!svmCaps) std::cout << " None\n";
|
||||
if (svmCaps & CL_DEVICE_SVM_COARSE_GRAIN_BUFFER)
|
||||
std::cout << " - Coarse-grain buffer sharing\n";
|
||||
if (svmCaps & CL_DEVICE_SVM_FINE_GRAIN_BUFFER)
|
||||
std::cout << " - Fine-grain buffer sharing\n";
|
||||
if (svmCaps & CL_DEVICE_SVM_FINE_GRAIN_SYSTEM)
|
||||
std::cout << " - Fine-grain system sharing\n";
|
||||
if (svmCaps & CL_DEVICE_SVM_ATOMICS)
|
||||
std::cout << " - Atomics supported\n";
|
||||
#endif
|
||||
|
||||
// Optional runtime test: check if CL_MEM_USE_HOST_PTR buffer reuses pointer
|
||||
const size_t bufSize = 1024 * 1024;
|
||||
std::vector<char> hostBuffer(bufSize, 42);
|
||||
|
||||
cl_int err;
|
||||
cl_context ctx = clCreateContext(nullptr, 1, &devices[d], nullptr, nullptr, &err);
|
||||
checkCLError(err, "create context");
|
||||
|
||||
cl_mem buf = clCreateBuffer(ctx, CL_MEM_USE_HOST_PTR, bufSize, hostBuffer.data(), &err);
|
||||
checkCLError(err, "create buffer");
|
||||
|
||||
cl_command_queue q = clCreateCommandQueue(ctx, devices[d], 0, &err);
|
||||
checkCLError(err, "create queue");
|
||||
|
||||
// Simple host → device → host round-trip test
|
||||
cl_event evt;
|
||||
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
|
||||
void* mapped = clEnqueueMapBuffer(q, buf, CL_TRUE, CL_MAP_READ, 0, bufSize, 0, nullptr, &evt, &err);
|
||||
checkCLError(err, "map buffer");
|
||||
clWaitForEvents(1, &evt);
|
||||
|
||||
clReleaseMemObject(buf);
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
std::chrono::duration<double, std::milli> elapsed = end - start;
|
||||
std::cout << " Map latency: " << elapsed.count() << " ms (lower → likely zero-copy)\n";
|
||||
|
||||
clReleaseCommandQueue(q);
|
||||
clReleaseContext(ctx);
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
#include <CL/cl.h>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
|
||||
void checkCLError(cl_int err, const char* msg) {
|
||||
if (err != CL_SUCCESS) {
|
||||
std::cerr << "OpenCL Error " << err << " at: " << msg << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------
|
||||
// Kernel source
|
||||
// Simple mock kernel that simulates splitting XYZ/I
|
||||
// Each "point" is 16 bytes (XYZ + Intensity)
|
||||
const char* kernelSrc = R"CLC(
|
||||
__kernel void xyz_i_split(__global uchar* assembly,
|
||||
__global uchar* xyzOut,
|
||||
__global uchar* iOut,
|
||||
const uint numPoints) {
|
||||
uint gid = get_global_id(0);
|
||||
if (gid >= numPoints) return;
|
||||
|
||||
uint offset = gid * 16;
|
||||
// Copy XYZ (12 bytes) to xyzOut
|
||||
for (int i=0; i<12; ++i)
|
||||
xyzOut[gid*12 + i] = assembly[offset + i];
|
||||
|
||||
// Copy Intensity (4 bytes) to iOut
|
||||
for (int i=0; i<4; ++i)
|
||||
iOut[gid*4 + i] = assembly[offset + 12 + i];
|
||||
}
|
||||
)CLC";
|
||||
|
||||
int main() {
|
||||
// --------------------
|
||||
// CHANGE THIS VALUE to set number of points per assembly buffer
|
||||
const size_t numPointsPerAssembly = 100000; // e.g., ~3333 points per fill
|
||||
const size_t bytesPerPoint = 16; // 12 bytes XYZ + 4 bytes I
|
||||
|
||||
const size_t assemblyBufSize = numPointsPerAssembly * bytesPerPoint;
|
||||
const size_t xyzBufSize = numPointsPerAssembly * 12;
|
||||
const size_t iBufSize = numPointsPerAssembly * 4;
|
||||
|
||||
cl_uint numPlatforms = 0;
|
||||
checkCLError(clGetPlatformIDs(0, nullptr, &numPlatforms), "get num platforms");
|
||||
std::vector<cl_platform_id> platforms(numPlatforms);
|
||||
checkCLError(clGetPlatformIDs(numPlatforms, platforms.data(), nullptr), "get platforms");
|
||||
|
||||
std::cout << "Found " << numPlatforms << " OpenCL platforms\n\n";
|
||||
|
||||
for (cl_uint p = 0; p < numPlatforms; ++p) {
|
||||
char platformName[256];
|
||||
clGetPlatformInfo(platforms[p], CL_PLATFORM_NAME, sizeof(platformName), platformName, nullptr);
|
||||
std::cout << "Platform " << p << ": " << platformName << "\n";
|
||||
|
||||
cl_uint numDevices = 0;
|
||||
clGetDeviceIDs(platforms[p], CL_DEVICE_TYPE_ALL, 0, nullptr, &numDevices);
|
||||
std::vector<cl_device_id> devices(numDevices);
|
||||
clGetDeviceIDs(platforms[p], CL_DEVICE_TYPE_ALL, numDevices, devices.data(), nullptr);
|
||||
|
||||
for (cl_uint d = 0; d < numDevices; ++d) {
|
||||
char deviceName[256];
|
||||
clGetDeviceInfo(devices[d], CL_DEVICE_NAME, sizeof(deviceName), deviceName, nullptr);
|
||||
std::cout << " Device " << d << ": " << deviceName << "\n";
|
||||
|
||||
cl_int err;
|
||||
cl_context ctx = clCreateContext(nullptr, 1, &devices[d], nullptr, nullptr, &err);
|
||||
checkCLError(err, "create context");
|
||||
|
||||
cl_command_queue q = clCreateCommandQueue(ctx, devices[d], 0, &err);
|
||||
checkCLError(err, "create queue");
|
||||
|
||||
// --------------------
|
||||
// Allocate host buffers
|
||||
std::vector<unsigned char> assemblyHost(assemblyBufSize, 42);
|
||||
std::vector<unsigned char> xyzHost(xyzBufSize, 0);
|
||||
std::vector<unsigned char> iHost(iBufSize, 0);
|
||||
|
||||
std::vector<unsigned char> xyzHostCPU(xyzBufSize, 0);
|
||||
std::vector<unsigned char> iHostCPU(iBufSize, 0);
|
||||
|
||||
// Create CL buffers
|
||||
cl_mem assemblyBuf = clCreateBuffer(ctx, CL_MEM_USE_HOST_PTR, assemblyBufSize, assemblyHost.data(), &err);
|
||||
checkCLError(err, "create assembly buffer");
|
||||
cl_mem xyzBuf = clCreateBuffer(ctx, CL_MEM_USE_HOST_PTR, xyzBufSize, xyzHost.data(), &err);
|
||||
checkCLError(err, "create xyz buffer");
|
||||
cl_mem iBuf = clCreateBuffer(ctx, CL_MEM_USE_HOST_PTR, iBufSize, iHost.data(), &err);
|
||||
checkCLError(err, "create i buffer");
|
||||
|
||||
// Build program
|
||||
cl_program prog = clCreateProgramWithSource(ctx, 1, &kernelSrc, nullptr, &err);
|
||||
checkCLError(err, "create program");
|
||||
|
||||
err = clBuildProgram(prog, 1, &devices[d], nullptr, nullptr, nullptr);
|
||||
if (err != CL_SUCCESS) {
|
||||
// Print build log
|
||||
size_t logSize = 0;
|
||||
clGetProgramBuildInfo(prog, devices[d], CL_PROGRAM_BUILD_LOG, 0, nullptr, &logSize);
|
||||
std::vector<char> log(logSize);
|
||||
clGetProgramBuildInfo(prog, devices[d], CL_PROGRAM_BUILD_LOG, logSize, log.data(), nullptr);
|
||||
std::cerr << log.data() << "\n";
|
||||
}
|
||||
checkCLError(err, "build program");
|
||||
|
||||
cl_kernel kernel = clCreateKernel(prog, "xyz_i_split", &err);
|
||||
checkCLError(err, "create kernel");
|
||||
|
||||
// Set kernel args
|
||||
clSetKernelArg(kernel, 0, sizeof(cl_mem), &assemblyBuf);
|
||||
clSetKernelArg(kernel, 1, sizeof(cl_mem), &xyzBuf);
|
||||
clSetKernelArg(kernel, 2, sizeof(cl_mem), &iBuf);
|
||||
clSetKernelArg(kernel, 3, sizeof(cl_uint), &numPointsPerAssembly);
|
||||
|
||||
const size_t globalWorkSize = numPointsPerAssembly;
|
||||
|
||||
// --------------------
|
||||
// Run a few iterations
|
||||
for (int iter = 0; iter < 5; ++iter) {
|
||||
cl_event evt;
|
||||
auto t0 = std::chrono::high_resolution_clock::now();
|
||||
|
||||
void* mappedAssembly = clEnqueueMapBuffer(q, assemblyBuf, CL_TRUE, CL_MAP_READ, 0, assemblyBufSize, 0, nullptr, &evt, &err);
|
||||
checkCLError(err, "map assembly buffer");
|
||||
clWaitForEvents(1, &evt);
|
||||
|
||||
auto t1 = std::chrono::high_resolution_clock::now();
|
||||
|
||||
err = clEnqueueNDRangeKernel(q, kernel, 1, nullptr, &globalWorkSize, nullptr, 0, nullptr, &evt);
|
||||
checkCLError(err, "enqueue kernel");
|
||||
clWaitForEvents(1, &evt);
|
||||
|
||||
auto t2 = std::chrono::high_resolution_clock::now();
|
||||
|
||||
cl_event unmapEvt;
|
||||
err = clEnqueueUnmapMemObject(q, assemblyBuf, mappedAssembly, 0, nullptr, &unmapEvt);
|
||||
checkCLError(err, "unmap assembly buffer");
|
||||
clWaitForEvents(1, &unmapEvt);
|
||||
|
||||
auto t3 = std::chrono::high_resolution_clock::now();
|
||||
|
||||
// --------------------
|
||||
// Host CPU split
|
||||
auto cpuStart = std::chrono::high_resolution_clock::now();
|
||||
for (size_t pt = 0; pt < numPointsPerAssembly; ++pt) {
|
||||
size_t off = pt * 16;
|
||||
for (int i = 0; i < 12; ++i)
|
||||
xyzHostCPU[pt*12 + i] = assemblyHost[off + i];
|
||||
for (int i = 0; i < 4; ++i)
|
||||
iHostCPU[pt*4 + i] = assemblyHost[off + 12 + i];
|
||||
}
|
||||
auto cpuEnd = std::chrono::high_resolution_clock::now();
|
||||
|
||||
std::chrono::duration<double, std::milli> mapElapsed = t1 - t0;
|
||||
std::chrono::duration<double, std::milli> kernelElapsed = t2 - t1;
|
||||
std::chrono::duration<double, std::milli> unmapElapsed = t3 - t2;
|
||||
std::chrono::duration<double, std::milli> cpuElapsed = cpuEnd - cpuStart;
|
||||
|
||||
std::cout << "Iteration " << iter
|
||||
<< " | Map: " << mapElapsed.count()
|
||||
<< " ms | Kernel: " << kernelElapsed.count()
|
||||
<< " ms | Unmap: " << unmapElapsed.count()
|
||||
<< " ms | CPU Split: " << cpuElapsed.count() << " ms\n";
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
clReleaseKernel(kernel);
|
||||
clReleaseProgram(prog);
|
||||
clReleaseMemObject(assemblyBuf);
|
||||
clReleaseMemObject(xyzBuf);
|
||||
clReleaseMemObject(iBuf);
|
||||
clReleaseCommandQueue(q);
|
||||
clReleaseContext(ctx);
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
#include <CL/cl.h>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <cstring>
|
||||
|
||||
#define CHECK(err, msg) \
|
||||
if (err != CL_SUCCESS) { \
|
||||
std::cerr << "ERROR: " << msg << " (" << err << ")\n"; \
|
||||
return 1; \
|
||||
}
|
||||
|
||||
const char *kernelSrc = R"CLC(
|
||||
__kernel void check_shared(__global const int* in, __global int* out) {
|
||||
int gid = get_global_id(0);
|
||||
out[gid] = in[gid] + 42; // simple deterministic transform
|
||||
}
|
||||
)CLC";
|
||||
|
||||
int main() {
|
||||
cl_int err;
|
||||
|
||||
// Pick first available device
|
||||
cl_uint numPlatforms;
|
||||
CHECK(clGetPlatformIDs(0, nullptr, &numPlatforms), "clGetPlatformIDs count");
|
||||
std::vector<cl_platform_id> plats(numPlatforms);
|
||||
CHECK(clGetPlatformIDs(numPlatforms, plats.data(), nullptr), "clGetPlatformIDs");
|
||||
|
||||
cl_platform_id plat = plats[0];
|
||||
cl_device_id dev;
|
||||
CHECK(clGetDeviceIDs(plat, CL_DEVICE_TYPE_GPU, 1, &dev, nullptr), "clGetDeviceIDs");
|
||||
|
||||
cl_context ctx = clCreateContext(nullptr, 1, &dev, nullptr, nullptr, &err);
|
||||
CHECK(err, "clCreateContext");
|
||||
|
||||
cl_command_queue q = clCreateCommandQueue(ctx, dev, 0, &err);
|
||||
CHECK(err, "clCreateCommandQueue");
|
||||
|
||||
// Create program and kernel
|
||||
const size_t srcLen = std::strlen(kernelSrc);
|
||||
cl_program prog = clCreateProgramWithSource(ctx, 1, &kernelSrc, &srcLen, &err);
|
||||
CHECK(err, "clCreateProgramWithSource");
|
||||
|
||||
err = clBuildProgram(prog, 1, &dev, nullptr, nullptr, nullptr);
|
||||
if (err != CL_SUCCESS) {
|
||||
size_t logSize;
|
||||
clGetProgramBuildInfo(prog, dev, CL_PROGRAM_BUILD_LOG, 0, nullptr, &logSize);
|
||||
std::vector<char> log(logSize);
|
||||
clGetProgramBuildInfo(prog, dev, CL_PROGRAM_BUILD_LOG, logSize, log.data(), nullptr);
|
||||
std::cerr << "--- Build Log ---\n" << log.data() << "\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
cl_kernel krn = clCreateKernel(prog, "check_shared", &err);
|
||||
CHECK(err, "clCreateKernel");
|
||||
|
||||
const size_t N = 8;
|
||||
size_t bufSize = N * sizeof(int);
|
||||
|
||||
// Allocate host-visible buffer
|
||||
cl_mem bufIn = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_ALLOC_HOST_PTR, bufSize, nullptr, &err);
|
||||
CHECK(err, "clCreateBuffer input");
|
||||
cl_mem bufOut = clCreateBuffer(ctx, CL_MEM_WRITE_ONLY | CL_MEM_ALLOC_HOST_PTR, bufSize, nullptr, &err);
|
||||
CHECK(err, "clCreateBuffer output");
|
||||
|
||||
// Map the buffer (should return pointer to real host memory if unified)
|
||||
int* hostPtr = (int*)clEnqueueMapBuffer(q, bufIn, CL_TRUE, CL_MAP_WRITE, 0, bufSize, 0, nullptr, nullptr, &err);
|
||||
CHECK(err, "clEnqueueMapBuffer");
|
||||
|
||||
std::cout << "Mapped host pointer: " << static_cast<void*>(hostPtr) << "\n";
|
||||
|
||||
// Write pattern directly into mapped memory
|
||||
for (size_t i = 0; i < N; ++i)
|
||||
hostPtr[i] = 100 + i;
|
||||
|
||||
// No clEnqueueWriteBuffer call! We rely on shared memory.
|
||||
clEnqueueUnmapMemObject(q, bufIn, hostPtr, 0, nullptr, nullptr);
|
||||
clFinish(q);
|
||||
|
||||
// Set kernel args
|
||||
clSetKernelArg(krn, 0, sizeof(cl_mem), &bufIn);
|
||||
clSetKernelArg(krn, 1, sizeof(cl_mem), &bufOut);
|
||||
|
||||
size_t global = N;
|
||||
err = clEnqueueNDRangeKernel(q, krn, 1, nullptr, &global, nullptr, 0, nullptr, nullptr);
|
||||
CHECK(err, "clEnqueueNDRangeKernel");
|
||||
clFinish(q);
|
||||
|
||||
// Read back result
|
||||
int* outPtr = (int*)clEnqueueMapBuffer(q, bufOut, CL_TRUE, CL_MAP_READ, 0, bufSize, 0, nullptr, nullptr, &err);
|
||||
CHECK(err, "map output");
|
||||
|
||||
std::cout << "Result: ";
|
||||
for (size_t i = 0; i < N; ++i)
|
||||
std::cout << outPtr[i] << " ";
|
||||
std::cout << "\n";
|
||||
|
||||
// Validate
|
||||
bool ok = true;
|
||||
for (size_t i = 0; i < N; ++i)
|
||||
if (outPtr[i] != 142 + i) ok = false;
|
||||
|
||||
std::cout << (ok ? "✅ GPU saw host writes (zero-copy confirmed)\n"
|
||||
: "❌ GPU did not see host writes (copying or staging occurred)\n");
|
||||
|
||||
clEnqueueUnmapMemObject(q, bufOut, outPtr, 0, nullptr, nullptr);
|
||||
clFinish(q);
|
||||
|
||||
clReleaseMemObject(bufIn);
|
||||
clReleaseMemObject(bufOut);
|
||||
clReleaseKernel(krn);
|
||||
clReleaseProgram(prog);
|
||||
clReleaseCommandQueue(q);
|
||||
clReleaseContext(ctx);
|
||||
return 0;
|
||||
}
|
||||
+1
-3
@@ -1,3 +1 @@
|
||||
+edev|avia0|
|
||||
structural-implexor|livoxGen1()|livoxProto1()
|
||||
|3JEDK380010Z39
|
||||
+edev|avia0|structural-qualeiface()|livoxGen1()|livoxProto1()|3JEDK380010Z39
|
||||
|
||||
+1
-3
@@ -1,3 +1 @@
|
||||
+edev|win0|
|
||||
visual-implexor|xcb(dev-substring)|xorg(display=1|screen=0)
|
||||
|mut
|
||||
+edev|win0|visual-qualeiface()|xcb(dev-substring)|xorg(display=1|screen=0)|mut
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
# DeviceSpec: API `drm()` from server `linux()`:
|
||||
# DeviceSpec: stim-buff-api `drm()` from provider `linux()`:
|
||||
|
||||
The API is Linux DRM/KMS. The server is Linux itself. This server provides
|
||||
direct capture of frames at the kernel so it works for both Linux and Wayland.
|
||||
There's a program known as GPU Screen Recorder that is able to use this to
|
||||
capture specific windows on X11, but the window-specific capture doesn't work
|
||||
with Wayland. Irrespective, whole-screen capture works on both GFX servers.
|
||||
The stim-buff-api is Linux DRM/KMS. The provider is Linux itself. This provider
|
||||
enables direct capture of frames at the kernel level, so it works for both X11
|
||||
and Wayland. There is a program called GPU Screen Recorder that can use this
|
||||
API to capture specific windows on X11, but window-specific capture does not
|
||||
work with Wayland. However, whole-screen capture works on both graphics
|
||||
servers.
|
||||
|
||||
Notes:
|
||||
* `modetest` utility in ubuntu package `libdrm-tests` is relevant.
|
||||
|
||||
@@ -1,24 +1,25 @@
|
||||
# DeviceSpec: API `xcb`, provider `xorg`
|
||||
# DAP Spec: stim-buff-api `xcb`, provider `xorg`
|
||||
|
||||
## Overview
|
||||
|
||||
The `xcb` API with the `xorg` provider allows Salmanoff to interact with Xorg
|
||||
The `xcb` stim-buff-api with the `xorg` provider allows Salmanoff to interact with Xorg
|
||||
server windows. This can be used to capture visual data from specific windows
|
||||
or entire screens managed by the Xorg server.
|
||||
|
||||
## DeviceSpec Format
|
||||
## DAP Spec Format
|
||||
|
||||
The general format of a device-spec for the `xcb` API with the `xorg` provider
|
||||
The general format of a DAP spec for the `xcb` stim-buff-api with the `xorg` provider
|
||||
is:
|
||||
```
|
||||
sensor-type|implexor|xcb(api-params)|xorg(provider-params)|deviceSelector
|
||||
sensor-type|dev-identifier|quale-iface-api|xcb(stim-buff-api-params)|xorg(provider-params)|deviceSelector
|
||||
```
|
||||
|
||||
* `sensor-type` is always either '`+idev`' (interoceptor), '`+edev`'
|
||||
(extrospector), or '`+adev`' (actuator).
|
||||
* `implexor` is the name of the implexor algorithm that should be used with
|
||||
the data that is provided by the `provider` via the `api`.
|
||||
* `api` is `xcb` in this case, and the `api-params` in parentheses may be
|
||||
* `dev-identifier` is a user-defined name for this specific device instance.
|
||||
* `quale-iface-api` is the name of the StimIface library that should be used to
|
||||
process the data from the stim buffer.
|
||||
* `stim-buff-api` is `xcb` in this case, and the `stim-buff-api-params` in parentheses may be
|
||||
omitted, in which case the parentheses will be empty, but the parentheses
|
||||
must always be written out.
|
||||
* `provider` is `xorg` in this case, and the `provider-params` in parentheses
|
||||
@@ -28,11 +29,11 @@ sensor-type|implexor|xcb(api-params)|xorg(provider-params)|deviceSelector
|
||||
identify the specific window or screen you want to access via that
|
||||
`provider`.
|
||||
|
||||
## `api-params` and `provider-params`
|
||||
## `stim-buff-api-params` and `provider-params`
|
||||
|
||||
### `api-params`
|
||||
### `stim-buff-api-params`
|
||||
|
||||
The `api-params` for the `xcb` API can include the following:
|
||||
The `stim-buff-api-params` for the `xcb` stim-buff-api can include the following:
|
||||
|
||||
* `dev-id` or `devid`: Specifies that the `deviceSelector` is a numeric window
|
||||
ID. The ID can be specified in decimal or hexadecimal format.
|
||||
@@ -97,25 +98,25 @@ xcb(dev-string)|My\ Exact\ Window\ Name
|
||||
|
||||
### To attach a specific window by name (substring match):
|
||||
```
|
||||
+edev|visual-implexor|xcb(dev-substring)|xorg(display=0|screen=0)|my-window
|
||||
+edev|my-window|visual-qualeiface|xcb(dev-substring)|xorg(display=0|screen=0)|my-window
|
||||
```
|
||||
This will attach to a window whose name contains "my-window" as a substring.
|
||||
|
||||
### To attach a specific window by exact name:
|
||||
```
|
||||
+edev|visual-implexor|xcb(dev-string)|xorg(display=0|screen=0)|My\ Exact\ Window\ Name
|
||||
+edev|my-window|visual-qualeiface|xcb(dev-string)|xorg(display=0|screen=0)|My\ Exact\ Window\ Name
|
||||
```
|
||||
This will attach to a window whose name exactly matches "My Exact Window Name".
|
||||
|
||||
### To attach a specific window by numeric ID:
|
||||
```
|
||||
+edev|visual-implexor|xcb(dev-id)|xorg(display=0|screen=0)|123456
|
||||
+edev|my-window|visual-qualeiface|xcb(dev-id)|xorg(display=0|screen=0)|123456
|
||||
```
|
||||
This will attach to a window with the numeric ID `123456`.
|
||||
|
||||
### To attach the entire screen:
|
||||
```
|
||||
+edev|visual-implexor|xcb()|xorg(display=0|screen=0)|all
|
||||
+edev|my-screen|visual-qualeiface|xcb()|xorg(display=0|screen=0)|all
|
||||
```
|
||||
This will attach to the entire screen `0` of display `0`.
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# Postrin path design:
|
||||
|
||||
## Negtrin and postrin weighting:
|
||||
|
||||
I am skeptical that treating negtrins and postrins as having equal importance
|
||||
will produce a working system.
|
||||
|
||||
### Frustrator Negtrin model: Postrins as intrinsically desirable:
|
||||
|
||||
In the frustrator model, postrins are intrinsically desirable and negtrins are
|
||||
only grasped as important in that they forcibly direct Drctr's attention away
|
||||
from its sampling/pursuit of a postrin. Because of this they're grasped as being
|
||||
bad because they frustrate the intrinsic goal of pursuing/sampling a postrin.
|
||||
|
||||
### Equiprioritized intrin model:
|
||||
|
||||
In this model
|
||||
@@ -0,0 +1,294 @@
|
||||
# Device Attachment Pipeline (DAP) Specification DSL: attaching sensors and actuators to SMO.
|
||||
|
||||
## DAP Specs vs DA Specs:
|
||||
|
||||
**DAP (Device Attachment Pipeline) specs** are human-readable DSL
|
||||
specifications that describe a pipeline of steps to connect a particular
|
||||
device role on a particular device-identifier to Salmanoff. This document
|
||||
describes the DAP specification format.
|
||||
|
||||
**DA (Device Attachment) specs** are compiled binary structs used internally
|
||||
by SMO after DAP specs have been parsed into binary format. DA specs are the
|
||||
internal representation that the system actually uses.
|
||||
|
||||
**Multiple Input Formats**: DAP specs can be parsed from multiple
|
||||
human-readable formats. For example, we intend to eventually extend ROS's
|
||||
URDF XML format to specify device attachment specs (URDFDA specs), which
|
||||
would also get compiled into the same DA spec binary format.
|
||||
|
||||
## Attaching sensors:
|
||||
|
||||
Sensors are input devices to Salmanoff. Salmanoff will perceive them as
|
||||
perceptual inputs -- like your own sense organs. For example, if you attach a
|
||||
camera as a sensor, salmanoff will experience it in the same way that you
|
||||
experience the visual sense data from your eyes.
|
||||
|
||||
## QualeIface (Quale Interface):
|
||||
|
||||
A QualeIface is a **Quale** **I**nter**face** library that connects to a
|
||||
particular stim buffer and allows the mind to process the stim features
|
||||
presented in the device's stim buffers. QualeIface libraries replace the
|
||||
previous notion of an implexor. They provide the interface between raw device
|
||||
data and the mind's processing capabilities.
|
||||
|
||||
## Device Attachment Pipeline (DAP) Specification Format:
|
||||
|
||||
The general format of a DAP specification is:
|
||||
```
|
||||
sensor-type|dev-identifier|quale-iface-api(quale-iface-api-params)|stim-buff-api(api-params)|provider(provider-params)|dev-selector
|
||||
```
|
||||
|
||||
* `sensor-type` is always either '`+idev`' (interoceptor), '`+edev`'
|
||||
(extrospector), or '`+adev`' (actuator).
|
||||
* `dev-identifier` is a user-defined name for this specific device instance.
|
||||
This represents a logical device that can be accessed through multiple
|
||||
providers and may expose multiple stim features. In a sense it's like a
|
||||
sense organ or sense modality.
|
||||
* `quale-iface-api` is the name of the QualeIface library that should be used to
|
||||
process the data from the stim buffer. This replaces the previous implexor
|
||||
concept. The `quale-iface-api-params` in parentheses may be omitted, in which
|
||||
case the parentheses will be empty, but the parentheses must always be written out.
|
||||
* `stim-buff-api` is the interface that provides access to a specific stim
|
||||
buffer from the device. A single device may have multiple stim buffers
|
||||
(e.g., audio output, microphone input, different data streams). The
|
||||
`api-params` in parentheses may be omitted, in which case the parentheses
|
||||
will be empty, but the parentheses must always be written out.
|
||||
* `provider` may be a userspace daemon or an OS kernel that provides access to
|
||||
the device's I/O functionality; and thereby allows the `stim-buff-api` to
|
||||
construct and present a stim-buffer to Salmanoff. The `provider-params` in
|
||||
parentheses may be omitted, in which case the parentheses will be empty, but
|
||||
the parentheses must always be written out.
|
||||
* `dev-selector` is the idiosyncratic label/name used by the `provider` to
|
||||
identify the specific device you want to attach via that `provider`.
|
||||
|
||||
## `quale-iface-api-params`, `stim-buff-api-params` and `provider-params`:
|
||||
|
||||
If there's more than one parameter item in a list of `quale-iface-api-params`,
|
||||
`stim-buff-api-params`, or `provider-params`, then the individual items in a
|
||||
list should be separated by the h-bar character (`|`). E.g:
|
||||
```
|
||||
+edev|soundcard0|audio-qualeiface(param1|param2)|alsa-audio(shmem|param2|param3)|alsa()|cardname
|
||||
```
|
||||
|
||||
Each parameter must be in one of these forms:
|
||||
* key=value
|
||||
* key=
|
||||
* key
|
||||
|
||||
### Important Note on `stim-buff-api-params`:
|
||||
|
||||
The `stim-buff-api-params` should **never** include options related to the
|
||||
stim buffer's type or format. The `stim-buff-api` must read and infer such
|
||||
configuration details from the `quale-iface-api` portion of the DAP spec, and
|
||||
configure itself accordingly to enable connection by the specified
|
||||
quale-iface library in the way that it has been configured.
|
||||
|
||||
`stim-buff-api-params` are for options that are:
|
||||
- Device-specific (not modality-wide)
|
||||
- Specific to this particular stim-feature as provided by this device
|
||||
- Configuration parameters needed by the stim-buff-api to properly interface
|
||||
with the device
|
||||
|
||||
Examples of appropriate `stim-buff-api-params`:
|
||||
- Buffer size settings
|
||||
- Device-specific communication parameters
|
||||
- Hardware-specific configuration options
|
||||
- Connection timeouts or retry settings
|
||||
|
||||
Examples of **inappropriate** `stim-buff-api-params`:
|
||||
- Data format specifications (should be inferred from stim-iface-api)
|
||||
- Color space settings (should be determined by the stim-iface library)
|
||||
- Processing algorithm parameters (belong to the stim-iface library)
|
||||
|
||||
## Logical View and Multiple Access Patterns:
|
||||
|
||||
### Single Device, Multiple Providers:
|
||||
|
||||
A single `dev-identifier` can unite several `dev-selectors` from multiple
|
||||
providers. For example, a sound card device `soundcard0` could be accessed
|
||||
through:
|
||||
|
||||
* `ident: soundcard0, provider: alsa` - Provides access to the card via ALSA
|
||||
API for audio output
|
||||
* `ident: soundcard0, provider: linux-driver-direct-file-ops` - Provides direct
|
||||
connection to Linux driver via read/write posix FD calls for beeper sound
|
||||
output
|
||||
* `ident: soundcard0, provider: alsa` - Provides access to the card via ALSA
|
||||
for microphone input
|
||||
|
||||
So a single physical device is accessed via multiple providers, each with
|
||||
different selectors.
|
||||
|
||||
### Single Device, Same Provider, Different Stim-Buff-APIs:
|
||||
|
||||
A device could have different `stim-buff-apis`, possibly provided by different
|
||||
shared libraries:
|
||||
|
||||
* `ident: soundcard0, provider: alsa, stim-buff-api: alsa-audio` - For audio
|
||||
output
|
||||
* `ident: soundcard0, provider: alsa, stim-buff-api: alsa-mic` - For microphone
|
||||
input
|
||||
|
||||
Different stim-buff-apis may be packaged into the same shared library, or
|
||||
multiple libraries may dlopen a common library behind the scenes.
|
||||
|
||||
### Stim Features and Buffers:
|
||||
|
||||
Logically, a `dev-identifier` represents a sense modality. Each device can
|
||||
export multiple stim features. For example, an eye can export:
|
||||
- Color data
|
||||
- Light intensity data
|
||||
- Thermal heat data
|
||||
- Pain input data
|
||||
|
||||
Each stim feature is exposed as a stim buffer, provided by a `stim-buff-api`.
|
||||
Stim-buff-apis rely upon providers to implement the device-specific operations
|
||||
required to effectuate the stim-buff controls.
|
||||
|
||||
## Examples:
|
||||
|
||||
### To attach a particular window from a window manager:
|
||||
```
|
||||
+edev|my-window|visual-qualeiface()|wayland()|wayland(server-socket)|window0
|
||||
```
|
||||
Connect to the Wayland server that's listening on `server-socket`, using the
|
||||
`wayland` stim-buff-api. Ask that Wayland server to give salmanoff read-access to all of
|
||||
the frames composited into the window buffer for `window0`. Use salmanoff's
|
||||
`visual-qualeiface` to process the visual data from that `window0`'s compositor buffer.
|
||||
|
||||
### To attach a window manager's entire rendered desktop:
|
||||
```
|
||||
+edev|my-desktop|visual-qualeiface()|wayland()|wayland(listen-socket)|all
|
||||
```
|
||||
In most cases, this is basically the same as attempting to attach all of the
|
||||
underlying GFX server's output.
|
||||
|
||||
Connect to the Wayland server that's listening on `listen-socket`, using the
|
||||
`wayland` stim-buff-api. Ask that Wayland server to give salmanoff read-access to the
|
||||
entire compositor framebuffer. Use salmanoff's `visual-qualeiface` to process the visual data from
|
||||
that Wayland server's compositor buffer.
|
||||
|
||||
### To attach all of an Xorg server's gfx output to all screens:
|
||||
```
|
||||
+edev|my-xorg-display|visual-qualeiface()|x11()|xorg(listen-socket)|all
|
||||
```
|
||||
|
||||
Connect to the Xorg server that's listening on `listen-socket`, using the `x11`
|
||||
stim-buff-api. Ask that Xorg server to let Salmanoff read out all of the frames written
|
||||
out to all screens. Use salmanoff's `visual-qualeiface` to process the visual data from the
|
||||
server's gfx framebuffer.
|
||||
|
||||
In most cases, this is basically the same as attempting to attach all of the
|
||||
WM's output.
|
||||
|
||||
* Implementation note:
|
||||
https://stackoverflow.com/questions/33845447/how-do-i-talk-to-an-x-server-in-c-without-a-graphics-library
|
||||
Seems relevant.
|
||||
|
||||
### To attach all of an Xorg server's gfx output to a particular screen:
|
||||
```
|
||||
+edev|my-screen|visual-qualeiface()|x11()|xorg(listen-socket)|:0
|
||||
```
|
||||
Connect to the Xorg server that's listening on `listen-socket`, using the `x11`
|
||||
stim-buff-api. Ask that Xorg server to let Salmanoff read out all of the frames written
|
||||
out to display `:0`. Use salmanoff's `visual-qualeiface` to process the visual data from display
|
||||
`:0`'s framebuffer.
|
||||
|
||||
* Implementation note:
|
||||
https://stackoverflow.com/questions/33845447/how-do-i-talk-to-an-x-server-in-c-without-a-graphics-library
|
||||
Seems relevant.
|
||||
|
||||
### To attach a camera device by connecting directly to its Linux driver:
|
||||
```
|
||||
+edev|my-camera|visual-qualeiface()|v4l()|linux()|/dev/video0
|
||||
```
|
||||
We specify that we want to use the `linux` kernel's loaded driver to connect
|
||||
to communicate with `/dev/video0`, via the `Video4Linux` stim-buff-api. We want salmanoff
|
||||
to use the `visual-qualeiface` library to process the visual data from `/dev/video0`'s stim buffer.
|
||||
|
||||
If `/dev/video0` is already consumed by another process, this may likely fail.
|
||||
|
||||
### To attach a microphone that's managed by ALSA server:
|
||||
```
|
||||
+edev|my-microphone|audio-qualeiface()|alsa-mic(shmem)|alsa()|cardname
|
||||
```
|
||||
|
||||
Connect to the ALSA server via `shmem`, using the `alsa-mic` stim-buff-api. Request access to
|
||||
the microphone function of the sound card with the name `cardname`. Use the
|
||||
`audio-qualeiface` library to process the audio data from `cardname`'s microphone stim buffer.
|
||||
|
||||
### To attach a thermal sensor managed by Linux:
|
||||
```
|
||||
+idev|my-thermal|thermal-qualeiface()|thermal-zone()|linux()|/sys/class/thermal_zone0
|
||||
```
|
||||
|
||||
Use the `thermal-zone` SysFS stim-buff-api provided by `linux` to connect to the sensor
|
||||
`/sys/class/thermal_zone0`. Use the `thermal-qualeiface` library to process the thermal data from
|
||||
`thermal_zone0`'s heat stim buffer.
|
||||
|
||||
## Multiple Provider Examples:
|
||||
|
||||
### Single Sound Card Device with Multiple Providers:
|
||||
|
||||
The same physical sound card `soundcard0` can be accessed through different providers:
|
||||
|
||||
```
|
||||
+edev|soundcard0|audio-qualeiface()|alsa-audio()|alsa()|card0
|
||||
|| +edev|soundcard0|audio-qualeiface()|direct-file-ops()|linux()|/dev/snd/pcmC0D0p
|
||||
|| +idev|soundcard0|audio-qualeiface()|alsa-mic()|alsa()|card0
|
||||
```
|
||||
|
||||
This shows:
|
||||
- `soundcard0` accessed via ALSA provider for audio output (`alsa-audio` stim-buff-api)
|
||||
- `soundcard0` accessed via Linux provider for direct file operations (`direct-file-ops` stim-buff-api)
|
||||
- `soundcard0` accessed via ALSA provider for microphone input (`alsa-mic` stim-buff-api)
|
||||
|
||||
### Single Camera Device with Multiple Stim-Buff-APIs:
|
||||
|
||||
A camera device `camera0` might expose different data streams:
|
||||
|
||||
```
|
||||
+edev|camera0|visual-qualeiface()|v4l-rgb()|linux()|/dev/video0
|
||||
|| +edev|camera0|visual-qualeiface()|v4l-yuv()|linux()|/dev/video0
|
||||
|| +idev|camera0|thermal-qualeiface()|v4l-thermal()|linux()|/dev/video0
|
||||
```
|
||||
|
||||
This shows the same camera device providing:
|
||||
- RGB color data via `v4l-rgb` stim-buff-api
|
||||
- YUV color data via `v4l-yuv` stim-buff-api
|
||||
- Thermal data via `v4l-thermal` stim-buff-api
|
||||
|
||||
## Attaching actuators:
|
||||
|
||||
Actuators are Salmanoff's way of enacting changes in the external world.
|
||||
They're like your libs, or your mouth. Actuators enable salmanoff to write
|
||||
outputs to the world outside.
|
||||
|
||||
### Wilzors:
|
||||
|
||||
Actuator devices are analogous to your body's limbs. Salmanoff controls these
|
||||
by using `wilzor` algorithms. Wilzor is a contraction of **Wil**lpower
|
||||
Actuat**Or** but with a 'Z' in the middle to make it sound cooler. Different
|
||||
types of devices will require different wilzor algorithms. You need to know
|
||||
what type of wilzor algorithm needs to be used to enable salmanoff to control
|
||||
your actuator device.
|
||||
|
||||
The general format for an actuator's device attachment specification follows the same pattern:
|
||||
```
|
||||
+adev|dev-identifier|wilzor-algorithm(quale-iface-api-params)|stim-buff-api(api-params)|provider(provider-params)|dev-selector
|
||||
```
|
||||
|
||||
Where `wilzor-algorithm` is the specific wilzor algorithm needed to control the actuator device.
|
||||
|
||||
## Device Attachment Pipeline (DAP) specification files:
|
||||
|
||||
Inside of a DAP specification file, you can list any number of
|
||||
DAP specifications.
|
||||
Separate individual DAP specifications with two consecutive h-bar
|
||||
characters (`||`),
|
||||
like this:
|
||||
```
|
||||
+edev|my-window|visual-qualeiface()|wayland()|wayland(server-socket)|window0
|
||||
|| +edev|my-xorg-display|visual-qualeiface()|x11()|xorg(listen-socket)|all
|
||||
|| +idev|my-thermal|thermal-qualeiface()|thermal-zone()|linux()|/sys/class/thermal_zone0
|
||||
```
|
||||
@@ -1,168 +0,0 @@
|
||||
# Device Attachment Specification DSL: attaching sensors and actuators to SMO.
|
||||
|
||||
## Attaching sensors:
|
||||
|
||||
Sensors are input devices to Salmanoff. Salmanoff will perceive them as
|
||||
perceptual inputs -- like your own sense organs. For example, if you attach a
|
||||
camera as a sensor, salmanoff will experience it in the same way that you
|
||||
experience the visual sense data from your eyes.
|
||||
|
||||
## Implexors:
|
||||
|
||||
An implexor is an **Imp**licit **Ex**istent isolat**Or** algorithm. It's
|
||||
basically what conventional ML/LLM/ANN developers call an ROI ("Region of
|
||||
Interest") extraction algorithm. An Implex algorithm is used to scan a frame
|
||||
of input sensor data and detect objects and patterns within it.
|
||||
|
||||
## Sensor device attachment specification:
|
||||
|
||||
The general format of a device attachment specification for a sensor is:
|
||||
```
|
||||
sensor-type|dev-identifier
|
||||
|implexor|api(api-params)|provider(provider-params)|deviceselector
|
||||
```
|
||||
|
||||
* `sensor-type` is always either '`+idev`' (interoceptor), '`+edev`'
|
||||
(extrospector), or '`+adev`' (actuator).
|
||||
* `dev-identifier` is a user-defined name for this specific device instance.
|
||||
* `implexor` is the name of the implexor algorithm that should be used with
|
||||
the data that is provided by the `provider` via the `api`.
|
||||
* `api` is the interface that the provider uses to export perceptual data for
|
||||
salmanoff to read. Salmanoff will run the `implexor` algorithm on the data
|
||||
from this `api`. The `api-param` in parentheses may be omitted, in which
|
||||
case the parentheses will be empty, but the parentheses must always be
|
||||
written out.
|
||||
* `provider` may be a userspace daemon or an OS kernel that provides perceptual
|
||||
data via the `api`. The `provider-params` in parentheses may be omitted, in
|
||||
which case the parenthesis will be empty, but the parentheses must always be
|
||||
written out.
|
||||
* `device selector` is the idiosyncratic label/name used by the `provider` to
|
||||
identify the specific device you want to attach via that `provider`.
|
||||
|
||||
## `API-params` and `provider-params`:
|
||||
|
||||
If there's more than one parameter item in a list of `api-params` or
|
||||
`provider-params`, then the individual items in a list of `api-param` or
|
||||
`provider-params` should be separated by the h-bar character (`|`). E.g:
|
||||
```
|
||||
+edev|audio-implexor|alsa(shmem|param2|param3)|alsa()|cardname
|
||||
```
|
||||
|
||||
Each parameter must be in one of these forms:
|
||||
* key=value
|
||||
* key=
|
||||
* key
|
||||
|
||||
Some examples follow:
|
||||
|
||||
### To attach a particular window from a window manager:
|
||||
```
|
||||
+edev|my-window|visual-implexor|wayland()|wayland(server-socket)|window0
|
||||
```
|
||||
Connect to the Wayland server that's listening on `server-socket`, using the
|
||||
`wayland` api. Ask that Wayland server to give salmanoff read-access to all of
|
||||
the frames composited into the window buffer for `window0`. Use salmanoff's
|
||||
`visual-implexor` to implex from that `window0`'s compositor data.
|
||||
|
||||
### To attach a window manager's entire rendered desktop:
|
||||
```
|
||||
+edev|my-desktop|visual-implexor|wayland()|wayland(listen-socket)|all
|
||||
```
|
||||
In most cases, this is basically the same as attempting to attach all of the
|
||||
underlying GFX server's output.
|
||||
|
||||
Connect to the Wayland server that's listening on `listen-socket`, using the
|
||||
`wayland` api. Ask that Wayland server to give salmanoff read-access to the
|
||||
entire compositor framebuffer. Use salmanoff's `visual-implexor` to implex from
|
||||
that Wayland server's compositor data.
|
||||
|
||||
### To attach all of an Xorg server's gfx output to all screens:
|
||||
```
|
||||
+edev|my-xorg-display|visual-implexor|x11()|xorg(listen-socket)|all
|
||||
```
|
||||
|
||||
Connect to the Xorg server that's listening on `listen-socket`, using the `x11`
|
||||
api. Ask that Xorg server to let Salmanoff read out all of the frames written
|
||||
out to all screens. Use salmanoff's `visual-implexor` to implex from the
|
||||
server's gfx framebuffer data.
|
||||
|
||||
In most cases, this is basically the same as attempting to attach all of the
|
||||
WM's output.
|
||||
|
||||
* Implementation note:
|
||||
https://stackoverflow.com/questions/33845447/how-do-i-talk-to-an-x-server-in-c-without-a-graphics-library
|
||||
Seems relevant.
|
||||
|
||||
### To attach all of an Xorg server's gfx output to a particular screen:
|
||||
```
|
||||
+edev|my-screen|visual-implexor|x11()|xorg(listen-socket)|:0
|
||||
```
|
||||
Connect to the Xorg server that's listening on `listen-socket`, using the `x11`
|
||||
api. Ask that Xorg server to let Salmanoff read out all of the frames written
|
||||
out to display `:0`. Use salmanoff's `visual-implexor` to implex from display
|
||||
`:0`'s framebuffer data.
|
||||
|
||||
* Implementation note:
|
||||
https://stackoverflow.com/questions/33845447/how-do-i-talk-to-an-x-server-in-c-without-a-graphics-library
|
||||
Seems relevant.
|
||||
|
||||
### To attach a camera device by connecting directly to its Linux driver:
|
||||
```
|
||||
+edev|my-camera|visual-implexor|v4l()|linux()|/dev/video0
|
||||
```
|
||||
We specify that we want to use the `linux` kernel's loaded driver to connect
|
||||
to communicate with `/dev/video0`, via the `Video4Linux` API. We want salmanoff
|
||||
to use the `visual-implexor` algorithm to implex from `/dev/video0`'s data.
|
||||
|
||||
If `/dev/video0` is already consumed by another process, this may likely fail.
|
||||
|
||||
### To attach a microphone that's managed by ALSA server:
|
||||
```
|
||||
+edev|my-microphone|audio-implexor|alsa(shmem)|alsa()|cardname
|
||||
```
|
||||
|
||||
Connect to the ALSA server via `shmem`, using the `alsa` API. Request access to
|
||||
the microphone function of the sound card with the name `cardname`. Use the
|
||||
`audio-implexor` algorithm to implex from `cardname`'s microphone data.
|
||||
|
||||
### To attach a thermal sensor managed by Linux:
|
||||
```
|
||||
+idev|my-thermal|thermal-implexor|thermal-zone()|linux()|/sys/class/thermal_zone0
|
||||
```
|
||||
|
||||
Use the `thermal-zone` SysFS API provided by `linux` to connect to the sensor
|
||||
`/sys/class/thermal_zone0`. Use the `thermal-implexor` implexor to implex from
|
||||
`thermal_zone0`'s heat data.
|
||||
|
||||
## Attaching actuators:
|
||||
|
||||
Actuators are Salmanoff's way of enacting changes in the external world.
|
||||
They're like your libs, or your mouth. Actuators enable salmanoff to write
|
||||
outputs to the world outside.
|
||||
|
||||
### Wilzors:
|
||||
|
||||
Actuator devices are analogous to your body's limbs. Salmanoff controls these
|
||||
by using `wilzor` algorithms. Wilzor is a contraction of **Wil**lpower
|
||||
Actuat**Or** but with a 'Z' in the middle to make it sound cooler. Different
|
||||
types of devices will require different wilzor algorithms. You need to know
|
||||
what type of wilzor algorithm needs to be used to enable salmanoff to control
|
||||
your actuator device.
|
||||
|
||||
The general format for an actuator's device attachment specification is:
|
||||
```
|
||||
WIP: TBD.
|
||||
```
|
||||
|
||||
## Device attachment specification files:
|
||||
|
||||
Inside of a device attachment specification file, you can list any number of
|
||||
device attachment specifications.
|
||||
Separate individual device attachment specifications with two consecutive h-bar
|
||||
characters (`||`),
|
||||
like this:
|
||||
```
|
||||
+edev|my-window|visual-implexor|wayland()|wayland(server-socket)|window0
|
||||
|| +edev|my-xorg-display|visual-implexor|x11()|xorg(listen-socket)|all
|
||||
|| +idev|my-thermal|thermal-implexor|thermal-zone()|linux()|/sys/class/thermal_zone0
|
||||
```
|
||||
@@ -1,14 +1,23 @@
|
||||
# LivoxGen1Lidar Device Attachment Protocol (DAP) Specification
|
||||
# LivoxGen1Lidar Device Attachment Pipeline (DAP) Specification
|
||||
|
||||
## Overview
|
||||
|
||||
The LivoxGen1Lidar DAP specification defines how to attach to Livox Gen1 LiDAR devices and access their various data streams using a unified API with mode-based parameter selection.
|
||||
The LivoxGen1Lidar DAP specification describes how to attach to Livox Gen1
|
||||
LiDAR devices and access their various data streams. The Livox Gen1 LiDAR
|
||||
presents multiple stim features, each with its own dedicated stim-buff-api.
|
||||
|
||||
## API Structure
|
||||
## Stim-Buff-API Structure
|
||||
|
||||
The LivoxGen1Lidar DAP uses a unified API name `livoxGen1` with mode-based parameters to specify which data interface to present.
|
||||
The LivoxGen1Lidar DAP uses multiple stim-buff-api names, one for each stim feature it presents:
|
||||
|
||||
## Device Attachment Specifications
|
||||
* `livoxGen1-pcloud` - Point cloud coordinate data
|
||||
* `livoxGen1-pcloudIntensity` - Point cloud intensity/reflectivity data
|
||||
* `livoxGen1-gyro` - Gyroscope data from internal IMU
|
||||
* `livoxGen1-accel` - Accelerometer data from internal IMU
|
||||
|
||||
Each stim-buff-api is designed to work with specific stim-iface libraries that understand the data format and processing requirements for that particular stim feature.
|
||||
|
||||
## DAP Specifications
|
||||
|
||||
### 1. Point Cloud Intensity Data Device (Interoceptor)
|
||||
|
||||
@@ -16,21 +25,11 @@ The LivoxGen1Lidar DAP uses a unified API name `livoxGen1` with mode-based param
|
||||
|
||||
**Syntax**:
|
||||
```
|
||||
+idev | avia0 | structural-implexor | livoxGen1(mode=pointCloudIntensity) | livoxProto1(handshake-timeout-ms=1000,retry-delay-ms=3000,smo-ip=192.168.1.50,smo-subnet-nbits=24) | 3JEDK380010Z39
|
||||
+idev | avia0 | pcloudIntensity | livoxGen1-pcloudIntensity(data-rate-hz=10) | livoxProto1(command-timeout-ms=1000,retry-delay-ms=3000,smo-ip=192.168.1.50,smo-subnet-nbits=24) | 3JEDK380010Z39
|
||||
```
|
||||
|
||||
**Alternative Syntax** (all equivalent):
|
||||
```
|
||||
+idev | avia0 | structural-implexor | livoxGen1(stim=pcloudIntensity) | livoxProto1(handshake-timeout-ms=1000,retry-delay-ms=3000,smo-ip=192.168.1.50,smo-subnet-nbits=24) | 3JEDK380010Z39
|
||||
+idev | avia0 | structural-implexor | livoxGen1(affordance=pCloudI) | livoxProto1(handshake-timeout-ms=1000,retry-delay-ms=3000,smo-ip=192.168.1.50,smo-subnet-nbits=24) | 3JEDK380010Z39
|
||||
```
|
||||
|
||||
**Mode Parameter Values** (synonymous):
|
||||
- `pointCloudIntensity`
|
||||
- `pcloudIntensity`
|
||||
- `pCloudIntensity`
|
||||
- `pCloudI`
|
||||
- `pcloudI`
|
||||
**Stim-Buff-API**: `livoxGen1-pcloudIntensity`
|
||||
**Quale-Iface-API**: `pcloudIntensity` - Processes intensity/reflectivity data from point clouds
|
||||
|
||||
### 2. Point Cloud Coordinate Data Device (Extrospector)
|
||||
|
||||
@@ -38,15 +37,21 @@ The LivoxGen1Lidar DAP uses a unified API name `livoxGen1` with mode-based param
|
||||
|
||||
**Syntax**:
|
||||
```
|
||||
+edev | avia0 | structural-implexor | livoxGen1(mode=pcloud,format=xyz) | livoxProto1(handshake-timeout-ms=1000,retry-delay-ms=3000,smo-ip=192.168.1.50,smo-subnet-nbits=24) | 3JEDK380010Z39
|
||||
+edev | avia0 | pcloud(format=xyz) | livoxGen1-pcloud(data-rate-hz=10) | livoxProto1(command-timeout-ms=1000,retry-delay-ms=3000,smo-ip=192.168.1.50,smo-subnet-nbits=24) | 3JEDK380010Z39
|
||||
```
|
||||
|
||||
**Mode Parameter Values** (synonymous):
|
||||
- `pcloud`
|
||||
- `pCloud`
|
||||
- `pointCloud`
|
||||
**Alternative Format Examples**:
|
||||
```
|
||||
+edev | avia0 | pcloud(format=spherical) | livoxGen1-pcloud(data-rate-hz=10) | livoxProto1(command-timeout-ms=1000,retry-delay-ms=3000,smo-ip=192.168.1.50,smo-subnet-nbits=24) | 3JEDK380010Z39
|
||||
+edev | avia0 | pcloud(format=spherical-cartesian) | livoxGen1-pcloud(data-rate-hz=10) | livoxProto1(command-timeout-ms=1000,retry-delay-ms=3000,smo-ip=192.168.1.50,smo-subnet-nbits=24) | 3JEDK380010Z39
|
||||
+edev | avia0 | pcloud(format=dual-cartesian) | livoxGen1-pcloud(data-rate-hz=10) | livoxProto1(command-timeout-ms=1000,retry-delay-ms=3000,smo-ip=192.168.1.50,smo-subnet-nbits=24) | 3JEDK380010Z39
|
||||
+edev | avia0 | pcloud(format=dual-spherical) | livoxGen1-pcloud(data-rate-hz=10) | livoxProto1(command-timeout-ms=1000,retry-delay-ms=3000,smo-ip=192.168.1.50,smo-subnet-nbits=24) | 3JEDK380010Z39
|
||||
```
|
||||
|
||||
**Format Parameter** (for point cloud modes):
|
||||
**Stim-Buff-API**: `livoxGen1-pcloud`
|
||||
**Quale-Iface-API**: `pcloud` - Processes spatial coordinate data from point clouds
|
||||
|
||||
**Format Parameter Values** (for pcloud quale-iface-api):
|
||||
- `xyz`: Standard Cartesian coordinates (X, Y, Z)
|
||||
- `spherical`: Raw spherical coordinates
|
||||
- `spherical-cartesian`: Spherical coordinates converted to Cartesian
|
||||
@@ -62,11 +67,11 @@ The LivoxGen1Lidar DAP uses a unified API name `livoxGen1` with mode-based param
|
||||
|
||||
**Syntax**:
|
||||
```
|
||||
+idev | avia0 | gyro-implexor | livoxGen1(mode=gyro) | livoxProto1(handshake-timeout-ms=1000,retry-delay-ms=3000,smo-ip=192.168.1.50,smo-subnet-nbits=24) | 3JEDK380010Z39
|
||||
+idev | avia0 | gyro | livoxGen1-gyro(data-rate-hz=100) | livoxProto1(command-timeout-ms=1000,retry-delay-ms=3000,smo-ip=192.168.1.50,smo-subnet-nbits=24) | 3JEDK380010Z39
|
||||
```
|
||||
|
||||
**Mode Parameter Values**:
|
||||
- `gyro`
|
||||
**Stim-Buff-API**: `livoxGen1-gyro`
|
||||
**Quale-Iface-API**: `gyro` - Processes gyroscope angular velocity data
|
||||
|
||||
### 4. IMU Accelerometer Data Device (Interoceptor)
|
||||
|
||||
@@ -74,11 +79,11 @@ The LivoxGen1Lidar DAP uses a unified API name `livoxGen1` with mode-based param
|
||||
|
||||
**Syntax**:
|
||||
```
|
||||
+idev | avia0 | accel-implexor | livoxGen1(mode=accel) | livoxProto1(handshake-timeout-ms=1000,retry-delay-ms=3000,smo-ip=192.168.1.50,smo-subnet-nbits=24) | 3JEDK380010Z39
|
||||
+idev | avia0 | accel | livoxGen1-accel(data-rate-hz=100) | livoxProto1(command-timeout-ms=1000,retry-delay-ms=3000,smo-ip=192.168.1.50,smo-subnet-nbits=24) | 3JEDK380010Z39
|
||||
```
|
||||
|
||||
**Mode Parameter Values**:
|
||||
- `accel`
|
||||
**Stim-Buff-API**: `livoxGen1-accel`
|
||||
**Quale-Iface-API**: `accel` - Processes accelerometer linear acceleration data
|
||||
|
||||
## Provider Parameters
|
||||
|
||||
@@ -86,10 +91,10 @@ The LivoxGen1Lidar DAP uses a unified API name `livoxGen1` with mode-based param
|
||||
|
||||
The `livoxProto1` provider accepts the following parameters:
|
||||
|
||||
**handshake-timeout-ms** (optional):
|
||||
- Specifies the timeout for handshake operations when connecting to devices
|
||||
**command-timeout-ms** / **cmd-timeout-ms** (optional, synonyms):
|
||||
- Specifies the timeout for command operations when communicating with devices
|
||||
- Value: Integer number of milliseconds
|
||||
- Example: `handshake-timeout-ms=1000` (1 second timeout)
|
||||
- Example: `command-timeout-ms=1000` or `cmd-timeout-ms=1000` (1 second timeout)
|
||||
- Default: 1000ms if not specified
|
||||
|
||||
**retry-delay-ms** (optional):
|
||||
@@ -124,16 +129,26 @@ The `livoxProto1` provider accepts the following parameters:
|
||||
|
||||
## Parameter Summary
|
||||
|
||||
### Mode/Stim/Affordance Parameter Values
|
||||
### Stim-Buff-API Names
|
||||
|
||||
| Data Type | Mode Values | Description |
|
||||
|-----------|-------------|-------------|
|
||||
| Point Cloud Intensity | `pointCloudIntensity`, `pcloudIntensity`, `pCloudIntensity`, `pCloudI`, `pcloudI` | Light intensity/reflectivity data |
|
||||
| Point Cloud Coordinates | `pcloud`, `pCloud`, `pointCloud` | Spatial coordinate data |
|
||||
| Gyroscope | `gyro` | Angular velocity measurements |
|
||||
| Accelerometer | `accel` | Linear acceleration measurements |
|
||||
| Stim Feature | Stim-Buff-API | Quale-Iface-API | Description |
|
||||
|--------------|---------------|----------------|-------------|
|
||||
| Point Cloud Intensity | `livoxGen1-pcloudIntensity` | `pcloudIntensity` | Light intensity/reflectivity data |
|
||||
| Point Cloud Coordinates | `livoxGen1-pcloud` | `pcloud` | Spatial coordinate data |
|
||||
| Gyroscope | `livoxGen1-gyro` | `gyro` | Angular velocity measurements |
|
||||
| Accelerometer | `livoxGen1-accel` | `accel` | Linear acceleration measurements |
|
||||
|
||||
### Format Parameter Values (for point cloud modes)
|
||||
### Stim-Buff-API Parameters
|
||||
|
||||
Each stim-buff-api accepts device-specific parameters:
|
||||
|
||||
| Parameter | Description | Example |
|
||||
|-----------|-------------|---------|
|
||||
| `data-rate-hz` | Data sampling rate in Hz | `data-rate-hz=10` |
|
||||
|
||||
### Quale-Iface-API Parameters
|
||||
|
||||
The `pcloud` quale-iface-api accepts format parameters:
|
||||
|
||||
| Format | Description |
|
||||
|--------|-------------|
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
# Quale Interface APIs (QualeIface APIs)
|
||||
|
||||
## Overview
|
||||
|
||||
QualeIface APIs are libraries that connect to particular stim buffers and allow the mind to process the stim features presented in the device's stim buffers. They provide the interface between raw device data and the mind's processing capabilities.
|
||||
|
||||
## Universally Understood Parameters
|
||||
|
||||
The following parameters are universally understood across all QualeIface API implementations.
|
||||
|
||||
### `history-buffer-duration-ms` / `hist-buff-duration-ms` / `histbuff-duration-ms` / `histbuff-ms`
|
||||
|
||||
**Synonyms:**
|
||||
- `history-buffer-duration-ms`
|
||||
- `hist-buff-duration-ms`
|
||||
- `histbuff-duration-ms`
|
||||
- `histbuff-ms`
|
||||
|
||||
**Description:**
|
||||
This parameter determines how long the history of the particular StimBuff being attached to the DAP spec's device role will be. The value is specified in milliseconds and determines the duration of historical data that will be maintained in the stimulus buffer.
|
||||
|
||||
**Specification:**
|
||||
- The parameter is specified as part of the `quale-iface-api-params` in the DAP specification
|
||||
- The value is an integer representing milliseconds
|
||||
- If multiple synonyms are specified, the lattermost (last encountered) synonym takes precedence
|
||||
- If not specified, a default value of 30000ms (30 seconds) is used
|
||||
|
||||
**Example:**
|
||||
```
|
||||
+edev|my-device|visual-qualeiface(histbuff-ms=60000)|v4l()|linux()|/dev/video0
|
||||
```
|
||||
|
||||
This example sets the history buffer duration to 60000ms (60 seconds).
|
||||
|
||||
**Note:**
|
||||
This parameter is specific to each stimbuff/deviceRole combination. Different device roles can have different history buffer durations based on their requirements.
|
||||
|
||||
## Overview
|
||||
|
||||
QualeIface APIs are interface libraries that connect to particular stim buffers and allow the mind to process the stim features presented in the device's stim buffers. They provide the interface between raw device data and the mind's processing capabilities.
|
||||
|
||||
## Universally Understood QualeIface API Parameters
|
||||
|
||||
This document describes quale-iface-api-params that are universally understood across all QualeIface implementations.
|
||||
|
||||
### history-buffer-duration-ms / hist-buff-duration-ms / histbuff-duration-ms / histbuff-ms
|
||||
|
||||
**Purpose:** Determines how long the history of the particular StimBuff being attached to the DAP spec's device role will be.
|
||||
|
||||
**Synonyms:**
|
||||
- `history-buffer-duration-ms` (full canonical name)
|
||||
- `hist-buff-duration-ms` (abbreviated)
|
||||
- `histbuff-duration-ms` (shortened)
|
||||
- `histbuff-ms` (shortest)
|
||||
|
||||
**Type:** Integer (milliseconds)
|
||||
|
||||
**Scope:** Specific to each stimbuff/deviceRole. Each device attachment can specify its own history buffer duration independently.
|
||||
|
||||
**Default:** If not specified, implementations typically use a default value (commonly 30000ms = 30 seconds).
|
||||
|
||||
**Usage:** The value specifies the duration in milliseconds for which stimulus frames will be retained in the history buffer. This affects how many slots are allocated in the ring buffer: `nSlots = histbuffMs / CONFIG_STIMBUFF_FRAME_PERIOD_MS`.
|
||||
|
||||
**Example:**
|
||||
```
|
||||
+edev|my-camera|visual-qualeiface(histbuff-ms=60000)|v4l()|linux()|/dev/video0
|
||||
```
|
||||
|
||||
This example sets a 60-second history buffer duration for the visual qualeiface processing the camera's point cloud data.
|
||||
|
||||
**Notes:**
|
||||
- If multiple synonyms are specified in the same parameter list, the lattermost one takes precedence
|
||||
- The parameter is parsed from the `quale-iface-api-params` section of the DAP specification
|
||||
- This parameter is specific to each device attachment, allowing different devices to have different history durations
|
||||
|
||||
|
||||
This document describes universally understood quale-iface-api-params that can be used across different QualeIface implementations.
|
||||
|
||||
## history-buffer-duration-ms
|
||||
|
||||
### Synonyms
|
||||
The `history-buffer-duration-ms` parameter can be specified using any of the following names:
|
||||
- `history-buffer-duration-ms` (full form)
|
||||
- `hist-buff-duration-ms` (abbreviated)
|
||||
- `histbuff-duration-ms` (abbreviated, no dashes)
|
||||
- `histbuff-ms` (shortest form)
|
||||
|
||||
### Description
|
||||
The `history-buffer-duration-ms` parameter determines how long the history of the particular StimBuff being attached to the DAP spec's device role will be. This value specifies the duration in milliseconds for which stimulus frame history will be maintained in the buffer.
|
||||
|
||||
### Usage
|
||||
This parameter is **specific to each stimbuff/deviceRole**. Each device attachment can have its own history buffer duration, allowing fine-grained control over memory usage and history retention for different sensor types.
|
||||
|
||||
### Example
|
||||
```
|
||||
+edev|my-camera|visual-qualeiface(histbuff-ms=30000)|v4l()|linux()|/dev/video0
|
||||
```
|
||||
|
||||
This example sets a 30-second history buffer for the camera device's stimulus buffer.
|
||||
|
||||
### Default Value
|
||||
If not specified, the default value is **30000 milliseconds (30 seconds)**.
|
||||
|
||||
### Notes
|
||||
- The parameter value should be specified as an integer representing milliseconds
|
||||
- Later synonyms in the parameter list will override earlier ones if multiple are specified
|
||||
- The actual number of buffer slots allocated will be calculated based on this duration divided by the frame period (CONFIG_STIMBUFF_FRAME_PERIOD_MS)
|
||||
|
||||
|
||||
This document describes universally understood parameters that can be used in quale-iface-api-params for device attachment specifications.
|
||||
|
||||
## history-buffer-duration-ms
|
||||
|
||||
**Synonyms:**
|
||||
- `history-buffer-duration-ms`
|
||||
- `hist-buff-duration-ms`
|
||||
- `histbuff-duration-ms`
|
||||
- `histbuff-ms`
|
||||
|
||||
**Description:**
|
||||
|
||||
The `history-buffer-duration-ms` parameter determines how long the history of the particular StimBuff being attached to the DAP spec's device role will be. This parameter is specific to each stimbuff/deviceRole combination.
|
||||
|
||||
**Usage:**
|
||||
|
||||
This parameter specifies the duration in milliseconds for which historical stimulus frame data will be retained in the buffer. The value determines the number of frames that can be stored, based on the frame period configured for the stimulus buffer.
|
||||
|
||||
**Example:**
|
||||
|
||||
```
|
||||
+edev|avia0|structural-qualeiface(histbuff-ms=60000)|livoxGen1()|livoxProto1()|3JEDK380010Z39
|
||||
```
|
||||
|
||||
This example sets the history buffer duration to 60000ms (60 seconds) for the avia0 device.
|
||||
|
||||
**Default Value:**
|
||||
|
||||
If not specified, the default value is 30000ms (30 seconds).
|
||||
|
||||
**Notes:**
|
||||
|
||||
- The parameter value should be specified in milliseconds
|
||||
- Multiple synonyms can be used, with later synonyms in the parameter list taking precedence
|
||||
- This parameter is parsed from the quale-iface-api-params, not from stim-buff-api-params or provider-params
|
||||
- The actual number of buffer slots is calculated as: `histbuffMs / CONFIG_STIMBUFF_FRAME_PERIOD_MS`
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
#ifndef ASYNCHRONOUS_BRIDGE_H
|
||||
#define ASYNCHRONOUS_BRIDGE_H
|
||||
|
||||
#include <boostAsioLinkageFix.h>
|
||||
#include <atomic>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/asio/io_service.hpp>
|
||||
|
||||
namespace smo {
|
||||
|
||||
@@ -31,6 +32,16 @@ public:
|
||||
io_service.run_one();
|
||||
if (isAsyncOperationComplete.load() || io_service.stopped())
|
||||
{ break; }
|
||||
|
||||
/** EXPLANATION:
|
||||
* In the mrntt and mind thread loops we call checkException() after
|
||||
* run() returns, but we don't have to do that here because
|
||||
* setException() calls stop.
|
||||
*
|
||||
* So if an exception is set on our thread, we'll break out of this
|
||||
* loop due to the check for stopped() above, and that'll take us
|
||||
* back out to the main loop, where we'll catch the exception.
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
#include <memory>
|
||||
#include <exception>
|
||||
#include <componentThread.h>
|
||||
#include <lockSet.h>
|
||||
#include <callback.h>
|
||||
#include <callableTracer.h>
|
||||
#include <asynchronousContinuationChainLink.h>
|
||||
|
||||
|
||||
@@ -50,12 +50,18 @@ public:
|
||||
* This macro should be used by the caller to bubble the exception to the
|
||||
* caller.
|
||||
*/
|
||||
#define CONT_SET_EXC(continuation, type, exc_obj) \
|
||||
#define CALLEE_SETEXC(continuation, type, exc_obj) \
|
||||
(continuation)->exception = std::make_exception_ptr<type>(exc_obj)
|
||||
|
||||
#define CONT_SET_EXC_AND_RET(continuation, type, exc_obj) \
|
||||
#define CALLEE_SETEXC_CALLCB(continuation, type, exc_obj) \
|
||||
do { \
|
||||
(continuation)->exception = std::make_exception_ptr<type>(exc_obj); \
|
||||
CALLEE_SETEXC(continuation, type, exc_obj); \
|
||||
(continuation)->callOriginalCb(); \
|
||||
} while(0)
|
||||
|
||||
#define CALLEE_SETEXC_CALLCB_RET(continuation, type, exc_obj) \
|
||||
do { \
|
||||
CALLEE_SETEXC_CALLCB(continuation, type, exc_obj); \
|
||||
return; \
|
||||
} while(0)
|
||||
|
||||
@@ -136,10 +142,10 @@ public:
|
||||
.callbackFn)
|
||||
{
|
||||
caller->getIoService().post(
|
||||
std::bind(
|
||||
STC(std::bind(
|
||||
AsynchronousContinuation<OriginalCbFnT>::originalCallback
|
||||
.callbackFn,
|
||||
std::forward<Args>(args)...));
|
||||
std::forward<Args>(args)...)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
#ifndef CALLABLE_TRACER_H
|
||||
#define CALLABLE_TRACER_H
|
||||
|
||||
#include <config.h>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <cstdint>
|
||||
#include <opts.h>
|
||||
|
||||
namespace smo {
|
||||
|
||||
/**
|
||||
* @brief CallableTracer - Wraps callables with metadata for debugging
|
||||
*
|
||||
* This class wraps any callable object with metadata (caller function name,
|
||||
* line number, and return addresses) to help debug cases where callables
|
||||
* posted to boost::asio::io_service have gone out of scope. The metadata
|
||||
* can be accessed from the callable's address when debugging.
|
||||
*/
|
||||
class CallableTracer
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor that wraps a callable with metadata
|
||||
* @param callerFuncName The name of the function that created this callable
|
||||
* @param callerLine The line number where this callable was created
|
||||
* @param returnAddr0 The return address of the direct caller
|
||||
* @param returnAddr1 The return address of the caller before that
|
||||
* @param callable The callable object to wrap
|
||||
*/
|
||||
template<typename CallableT>
|
||||
explicit CallableTracer(
|
||||
const char* callerFuncName,
|
||||
int callerLine,
|
||||
void* returnAddr0,
|
||||
void* returnAddr1,
|
||||
CallableT&& callable)
|
||||
: callerFuncName(callerFuncName),
|
||||
callerLine(callerLine),
|
||||
returnAddr0(returnAddr0),
|
||||
returnAddr1(returnAddr1),
|
||||
callable(std::forward<CallableT>(callable))
|
||||
{}
|
||||
|
||||
void operator()()
|
||||
{
|
||||
if (OptionParser::getOptions().traceCallables)
|
||||
{
|
||||
std::cout << "" << __func__ << ": On thread "
|
||||
<< (ComponentThread::tlsInitialized()
|
||||
? ComponentThread::getSelf()->name : "<TLS un-init'ed>")
|
||||
<< ": Calling callable posted by:\n"
|
||||
<< "\t" << callerFuncName << "\n\tat line " << (int)callerLine
|
||||
<< " return addr 0: " << returnAddr0
|
||||
<< ", return addr 1: " << returnAddr1
|
||||
<< std::endl;
|
||||
}
|
||||
callable();
|
||||
}
|
||||
|
||||
public:
|
||||
/// Name of the function that created this callable
|
||||
std::string callerFuncName;
|
||||
/// Line number where this callable was created
|
||||
int callerLine;
|
||||
/// Return address of the direct caller
|
||||
void* returnAddr0;
|
||||
/// Return address of the caller before that
|
||||
void* returnAddr1;
|
||||
|
||||
private:
|
||||
/// The wrapped callable (type-erased using std::function)
|
||||
std::function<void()> callable;
|
||||
};
|
||||
|
||||
} // namespace smo
|
||||
|
||||
/**
|
||||
* @brief STC - SMO Traceable Callable macro
|
||||
*
|
||||
* When CONFIG_DEBUG_TRACE_CALLABLES is defined, wraps the callable with
|
||||
* CallableTracer to store metadata (caller function name, line number,
|
||||
* and return addresses). When not defined, returns the callable directly
|
||||
* with no overhead.
|
||||
*
|
||||
* Uses compiler-specific macros to get fully qualified function names:
|
||||
* - GCC/Clang: __PRETTY_FUNCTION__ (includes full signature with namespace/class)
|
||||
* - MSVC: __FUNCSIG__ (includes full signature)
|
||||
* - Fallback: __func__ (unqualified function name only)
|
||||
*
|
||||
* Uses compiler-specific builtins to get return addresses:
|
||||
* - GCC/Clang: __builtin_return_address(0) and __builtin_return_address(1)
|
||||
* - MSVC: _ReturnAddress() (only one level available)
|
||||
* - Fallback: nullptr for return addresses
|
||||
*
|
||||
* Usage:
|
||||
* thread->getIoService().post(
|
||||
* STC(std::bind(&SomeClass::method, this, arg1, arg2)));
|
||||
*/
|
||||
#ifdef CONFIG_DEBUG_TRACE_CALLABLES
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
// GCC/Clang: __PRETTY_FUNCTION__ gives full signature
|
||||
// e.g., "void smo::SomeClass::method(int, int)"
|
||||
// __builtin_return_address(0) = direct caller
|
||||
// __builtin_return_address(1) = caller before that
|
||||
#define STC(arg) smo::CallableTracer( \
|
||||
__PRETTY_FUNCTION__, \
|
||||
__LINE__, \
|
||||
__builtin_return_address(0), \
|
||||
__builtin_return_address(1), \
|
||||
arg)
|
||||
#elif defined(_MSC_VER)
|
||||
// MSVC: __FUNCSIG__ gives full signature
|
||||
// e.g., "void __cdecl smo::SomeClass::method(int, int)"
|
||||
// _ReturnAddress() = direct caller (only one level available)
|
||||
#include <intrin.h>
|
||||
#define STC(arg) smo::CallableTracer( \
|
||||
__FUNCSIG__, \
|
||||
__LINE__, \
|
||||
_ReturnAddress(), \
|
||||
nullptr, \
|
||||
arg)
|
||||
#else
|
||||
// Fallback to standard __func__ (unqualified name only)
|
||||
// No return address support
|
||||
#define STC(arg) smo::CallableTracer( \
|
||||
__func__, \
|
||||
__LINE__, \
|
||||
nullptr, \
|
||||
nullptr, \
|
||||
arg)
|
||||
#endif
|
||||
#else
|
||||
#define STC(arg) arg
|
||||
#endif
|
||||
|
||||
#endif // CALLABLE_TRACER_H
|
||||
@@ -2,7 +2,6 @@
|
||||
#define CALLBACK_H
|
||||
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
|
||||
namespace smo {
|
||||
|
||||
|
||||
+16
-22
@@ -11,6 +11,9 @@
|
||||
|
||||
/* Device manager reattacher configuration */
|
||||
#define CONFIG_MRNTT_DEVMGR_REATTACHER_PERIOD_MS @MRNTT_DEVMGR_REATTACHER_PERIOD_MS@
|
||||
/* Stimulus buffer frame period configuration */
|
||||
#define CONFIG_STIMBUFF_FRAME_PERIOD_MS @CONFIG_STIMBUFF_FRAME_PERIOD_MS@
|
||||
#define CONFIG_STIMBUFF_FRAME_RETRY_DELAY_MS @CONFIG_STIMBUFF_FRAME_RETRY_DELAY_MS@
|
||||
|
||||
/* World thread configuration */
|
||||
#cmakedefine CONFIG_WORLD_USE_BODY_THREAD
|
||||
@@ -19,6 +22,9 @@
|
||||
#cmakedefine CONFIG_ENABLE_DEBUG_LOCKS
|
||||
#cmakedefine CONFIG_DEBUG_QUTEX_DEADLOCK_TIMEOUT_MS @DEBUG_QUTEX_DEADLOCK_TIMEOUT_MS@
|
||||
|
||||
/* Debug callable tracing configuration */
|
||||
#cmakedefine CONFIG_DEBUG_TRACE_CALLABLES
|
||||
|
||||
/* Cross-compilation configuration */
|
||||
#cmakedefine CMAKE_CROSSCOMPILING
|
||||
|
||||
@@ -26,32 +32,20 @@
|
||||
#cmakedefine CONFIG_LIB_XCBXORG_ENABLED
|
||||
#cmakedefine CONFIG_LIB_ALSA_ENABLED
|
||||
|
||||
/* Sense APIs */
|
||||
#cmakedefine CONFIG_SENSEAPI_XCBWINDOW_ENABLED
|
||||
#cmakedefine CONFIG_SENSEAPI_V4L_ENABLED
|
||||
#cmakedefine CONFIG_SENSEAPI_ALSAMIC_ENABLED
|
||||
#cmakedefine CONFIG_SENSEAPI_LIVOX_ENABLED
|
||||
#cmakedefine CONFIG_SENSEAPI_R3LIVE_ENABLED
|
||||
#cmakedefine CONFIG_SENSEAPI_FASTLIO2_ENABLED
|
||||
#cmakedefine CONFIG_SENSEAPI_ADALIO2_ENABLED
|
||||
#cmakedefine CONFIG_SENSEAPI_DEEPLIO2_ENABLED
|
||||
/* Stim Buff APIs */
|
||||
#cmakedefine CONFIG_STIMBUFFAPI_XCBWINDOW_ENABLED
|
||||
#cmakedefine CONFIG_STIMBUFFAPI_LIVOXGEN1_ENABLED
|
||||
#cmakedefine CONFIG_STIMBUFFAPI_V4L_ENABLED
|
||||
#cmakedefine CONFIG_STIMBUFFAPI_ALSAMIC_ENABLED
|
||||
#cmakedefine CONFIG_STIMBUFFAPI_LIVOX_ENABLED
|
||||
#cmakedefine CONFIG_STIMBUFFAPI_R3LIVE_ENABLED
|
||||
#cmakedefine CONFIG_STIMBUFFAPI_FASTLIO2_ENABLED
|
||||
#cmakedefine CONFIG_STIMBUFFAPI_ADALIO2_ENABLED
|
||||
#cmakedefine CONFIG_STIMBUFFAPI_DEEPLIO2_ENABLED
|
||||
|
||||
/* Wilzor APIs */
|
||||
#cmakedefine CONFIG_WILZORAPI_XCBMOUSE_ENABLED
|
||||
#cmakedefine CONFIG_WILZORAPI_XCBKEYBOARD_ENABLED
|
||||
#cmakedefine CONFIG_WILZORAPI_ALSAVOICE_ENABLED
|
||||
|
||||
/* Legacy defines for backward compatibility */
|
||||
#cmakedefine CONFIG_XCBWINDOW_ENABLED
|
||||
#cmakedefine CONFIG_V4L_ENABLED
|
||||
#cmakedefine CONFIG_ALSAMIC_ENABLED
|
||||
#cmakedefine CONFIG_LIVOX_ENABLED
|
||||
#cmakedefine CONFIG_R3LIVE_ENABLED
|
||||
#cmakedefine CONFIG_FASTLIO2_ENABLED
|
||||
#cmakedefine CONFIG_ADALIO2_ENABLED
|
||||
#cmakedefine CONFIG_DEEPLIO2_ENABLED
|
||||
#cmakedefine CONFIG_XCBMOUSE_ENABLED
|
||||
#cmakedefine CONFIG_XCBKEYBOARD_ENABLED
|
||||
#cmakedefine CONFIG_ALSAVOICE_ENABLED
|
||||
|
||||
#endif /* _CONFIG_H */
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
#define LOCK_SET_H
|
||||
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <atomic>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
#include <memory>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#ifndef LOCKER_AND_INVOKER_BASE_H
|
||||
#define LOCKER_AND_INVOKER_BASE_H
|
||||
|
||||
#include <functional>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
|
||||
|
||||
@@ -3,10 +3,8 @@
|
||||
|
||||
#include <config.h>
|
||||
#include <list>
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <stdexcept>
|
||||
#include <spinLock.h>
|
||||
#include <lockerAndInvokerBase.h>
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#define SERIALIZED_ASYNCHRONOUS_CONTINUATION_H
|
||||
|
||||
#include <config.h>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
|
||||
@@ -72,6 +72,34 @@ public:
|
||||
locked.store(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief RAII guard for SpinLock
|
||||
* Locks the spinlock on construction and unlocks on destruction
|
||||
*/
|
||||
class Guard
|
||||
{
|
||||
public:
|
||||
explicit Guard(SpinLock& lock)
|
||||
: lock_(lock)
|
||||
{
|
||||
lock_.acquire();
|
||||
}
|
||||
|
||||
~Guard()
|
||||
{
|
||||
lock_.release();
|
||||
}
|
||||
|
||||
// Non-copyable, non-movable
|
||||
Guard(const Guard&) = delete;
|
||||
Guard& operator=(const Guard&) = delete;
|
||||
Guard(Guard&&) = delete;
|
||||
Guard& operator=(Guard&&) = delete;
|
||||
|
||||
private:
|
||||
SpinLock& lock_;
|
||||
};
|
||||
|
||||
private:
|
||||
std::atomic<bool> locked;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
#ifndef _COMBINATORIAL_LOGIC_EXPRESSION_H
|
||||
#define _COMBINATORIAL_LOGIC_EXPRESSION_H
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <user/logic.h>
|
||||
#include <mentalEntity.h>
|
||||
#include <concept.h>
|
||||
#include <user/stimFrame.h>
|
||||
|
||||
namespace smo {
|
||||
namespace cologex {
|
||||
|
||||
class Comparator
|
||||
: public MentalEntity, public logic::Operand
|
||||
{
|
||||
public:
|
||||
/** EXPLANATION:
|
||||
* The reference for a Comparator is the fixed mentity or range of mentities
|
||||
* that this comparator is intended to validate a match against.
|
||||
*
|
||||
* There are several mentities against which a comparator can match. At the
|
||||
* time of writing, we're fairly sure that these will be at minimum,
|
||||
* qualia, chronomena and mentena.
|
||||
*/
|
||||
std::shared_ptr<MentalEntity> reference;
|
||||
};
|
||||
|
||||
class ComparatorExpression
|
||||
: public logic::UnaryExpression
|
||||
{
|
||||
public:
|
||||
ComparatorExpression(
|
||||
logic::Operator &op, std::shared_ptr<Comparator> &comparator
|
||||
)
|
||||
: logic::UnaryExpression(
|
||||
op, std::static_pointer_cast<logic::Operand>(comparator))
|
||||
{}
|
||||
};
|
||||
|
||||
class CombinatorialLogicExpression
|
||||
: public MentalEntity, public logic::Expression, public Concept
|
||||
{
|
||||
public:
|
||||
|
||||
};
|
||||
|
||||
class CombinatorialLogicExpressionSeq
|
||||
: public MentalEntity, public Concept
|
||||
{
|
||||
public:
|
||||
std::vector<
|
||||
std::pair<stim_buff::SimultaneityStamp, CombinatorialLogicExpression>
|
||||
> expressions;
|
||||
};
|
||||
|
||||
typedef CombinatorialLogicExpression Cologex;
|
||||
typedef CombinatorialLogicExpressionSeq CologexSeq;
|
||||
|
||||
} // namespace cologex
|
||||
} // namespace smo
|
||||
|
||||
#endif
|
||||
@@ -25,6 +25,8 @@ public:
|
||||
{
|
||||
return deviceIdentifier == other.deviceIdentifier &&
|
||||
sensorType == other.sensorType &&
|
||||
qualeIfaceApi == other.qualeIfaceApi &&
|
||||
stimBuffApi == other.stimBuffApi &&
|
||||
provider == other.provider &&
|
||||
deviceSelector == other.deviceSelector;
|
||||
}
|
||||
@@ -32,9 +34,10 @@ public:
|
||||
public:
|
||||
std::string deviceIdentifier;
|
||||
char sensorType;
|
||||
std::string implexor;
|
||||
std::string api;
|
||||
std::vector<std::pair<std::string,std::string>> apiParams;
|
||||
std::string qualeIfaceApi;
|
||||
std::vector<std::pair<std::string,std::string>> qualeIfaceApiParams;
|
||||
std::string stimBuffApi;
|
||||
std::vector<std::pair<std::string,std::string>> stimBuffApiParams;
|
||||
std::string provider;
|
||||
std::vector<std::pair<std::string,std::string>> providerParams;
|
||||
std::string deviceSelector;
|
||||
@@ -44,9 +47,18 @@ public:
|
||||
std::ostringstream os;
|
||||
os << "Device Identifier: " << deviceIdentifier
|
||||
<< ", Sensor Type: " << sensorType
|
||||
<< ", Implexor: " << implexor << ", API: " << api
|
||||
<< ", API Params: (";
|
||||
for (const auto& param : apiParams)
|
||||
<< ", QualeIface API: " << qualeIfaceApi << ", QualeIface API Params: (";
|
||||
for (const auto& param : qualeIfaceApiParams)
|
||||
{
|
||||
os << param.first;
|
||||
if (!param.second.empty()) {
|
||||
os << "=" << param.second;
|
||||
}
|
||||
os << " ";
|
||||
}
|
||||
os << "), StimBuff API: " << stimBuffApi
|
||||
<< ", StimBuff API Params: (";
|
||||
for (const auto& param : stimBuffApiParams)
|
||||
{
|
||||
os << param.first;
|
||||
if (!param.second.empty()) {
|
||||
@@ -69,28 +81,29 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Parse a required integer parameter from provider parameters
|
||||
* @param spec The device attachment specification
|
||||
* @brief Parse a required integer parameter from a parameter list
|
||||
* @param params The parameter vector to search in
|
||||
* @param paramName The name of the parameter to parse
|
||||
* @return The parsed integer value
|
||||
* @throws std::runtime_error if parameter is not found or cannot be parsed
|
||||
*/
|
||||
static int parseRequiredParamAsInt(
|
||||
const DeviceAttachmentSpec& spec, const std::string& paramName
|
||||
const std::vector<std::pair<std::string,std::string>>& params,
|
||||
const std::string& paramName
|
||||
)
|
||||
{
|
||||
auto it = std::find_if(
|
||||
spec.providerParams.begin(),
|
||||
spec.providerParams.end(),
|
||||
params.begin(),
|
||||
params.end(),
|
||||
[¶mName](const auto& param) {
|
||||
return param.first == paramName;
|
||||
}
|
||||
);
|
||||
|
||||
if (it == spec.providerParams.end())
|
||||
if (it == params.end())
|
||||
{
|
||||
throw std::runtime_error(
|
||||
"No " + paramName + " specified in provider params");
|
||||
"No " + paramName + " specified in params");
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
+31
-28
@@ -4,14 +4,17 @@
|
||||
#include <stdbool.h>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <preprocessor.h>
|
||||
#include <componentThread.h>
|
||||
#include <user/deviceAttachmentSpec.h>
|
||||
#include <callback.h>
|
||||
|
||||
namespace smo {
|
||||
namespace sense_api {
|
||||
|
||||
class ComponentThread;
|
||||
|
||||
namespace stim_buff {
|
||||
|
||||
/**
|
||||
* @brief Threading model descriptor for senseApi libraries.
|
||||
@@ -82,7 +85,7 @@ struct SmoCallbacks
|
||||
|
||||
struct Sal_Mgmt_LibOps
|
||||
{
|
||||
/* When Salmanoff loads a sense API lib, it calls this function to initialize
|
||||
/* When Salmanoff loads a stim buff API lib, it calls this function to initialize
|
||||
* the lib. When this returns, the lib should be ready to attach devices.
|
||||
*/
|
||||
sal_mlo_initializeIndFn *initializeInd;
|
||||
@@ -91,7 +94,7 @@ struct Sal_Mgmt_LibOps
|
||||
*/
|
||||
sal_mlo_finalizeIndFn *finalizeInd;
|
||||
/* Salmanoff calls this to attach a device to the lib. When it returns, the
|
||||
* device should be attached and ready to be implexed.
|
||||
* device should be attached and ready to present its stimbuff.
|
||||
*/
|
||||
sal_mlo_attachDeviceReqFn *attachDeviceReq;
|
||||
// When this returns, the device should be detached.
|
||||
@@ -109,16 +112,16 @@ struct Sal_Mgmt_LibOps
|
||||
}
|
||||
};
|
||||
|
||||
/* Exported by all sense API Libraries to tell Salmanoff what API the lib uses
|
||||
* to connect to providers; and also to state which implexor APIs it exports.
|
||||
/* Exported by all stim buff API Libraries to tell Salmanoff what API the lib uses
|
||||
* to connect to providers; and also to state which quale-iface APIs it exports.
|
||||
*/
|
||||
class SenseApiDesc
|
||||
class StimBuffApiDesc
|
||||
{
|
||||
public:
|
||||
class ExportedImplexorApiDesc
|
||||
class ExportedQualeIfaceApiDesc
|
||||
{
|
||||
public:
|
||||
static bool sanityCheck(const ExportedImplexorApiDesc &desc)
|
||||
static bool sanityCheck(const ExportedQualeIfaceApiDesc &desc)
|
||||
{
|
||||
if (desc.name.empty()) { return false; }
|
||||
return true;
|
||||
@@ -132,45 +135,45 @@ public:
|
||||
std::string stringify() const
|
||||
{
|
||||
std::string result = "Name: " + name + "\n";
|
||||
result += "Exported Implexor APIs:\n";
|
||||
for (const auto& api : exportedImplexorApis) {
|
||||
result += "Exported QualeIface APIs:\n";
|
||||
for (const auto& api : exportedQualeIfaceApis) {
|
||||
result += " - " + api.name + "\n";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool sanityCheck(const SenseApiDesc &desc)
|
||||
static bool sanityCheck(const StimBuffApiDesc &desc)
|
||||
{
|
||||
if (desc.name.empty() || desc.exportedImplexorApis.empty()) {
|
||||
if (desc.name.empty() || desc.exportedQualeIfaceApis.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto& api : desc.exportedImplexorApis) {
|
||||
if (!ExportedImplexorApiDesc::sanityCheck(api)) { return false; }
|
||||
for (const auto& api : desc.exportedQualeIfaceApis) {
|
||||
if (!ExportedQualeIfaceApiDesc::sanityCheck(api)) { return false; }
|
||||
}
|
||||
|
||||
return Sal_Mgmt_LibOps::sanityCheck(desc.sal_mgmt_libOps);
|
||||
}
|
||||
|
||||
std::string name;
|
||||
// These are the implexors whose APIs this lib exports.
|
||||
std::vector<ExportedImplexorApiDesc> exportedImplexorApis;
|
||||
// These are the quale-iface APIs this lib exports.
|
||||
std::vector<ExportedQualeIfaceApiDesc> exportedQualeIfaceApis;
|
||||
Sal_Mgmt_LibOps sal_mgmt_libOps;
|
||||
};
|
||||
|
||||
|
||||
#define SMO_GET_SENSE_API_DESC_FN_NAME getSenseApiDesc
|
||||
#define SMO_GET_SENSE_API_DESC_FN_NAME_STR \
|
||||
SMO_QUOTE(SMO_GET_SENSE_API_DESC_FN_NAME)
|
||||
#define SMO_GET_SENSE_API_DESC_FN_TYPEDEF \
|
||||
SMO_CONCAT(SMO_GET_SENSE_API_DESC_FN_NAME, Fn)
|
||||
#define SMO_GET_STIM_BUFF_API_DESC_FN_NAME getStimBuffApiDesc
|
||||
#define SMO_GET_STIM_BUFF_API_DESC_FN_NAME_STR \
|
||||
SMO_QUOTE(SMO_GET_STIM_BUFF_API_DESC_FN_NAME)
|
||||
#define SMO_GET_STIM_BUFF_API_DESC_FN_TYPEDEF \
|
||||
SMO_CONCAT(SMO_GET_STIM_BUFF_API_DESC_FN_NAME, Fn)
|
||||
|
||||
/* Every Sense API library must define a global instance of this
|
||||
/* Every Stim Buff API library must define a global instance of this
|
||||
* function. Salmanoff will search for it and invoke it via dlsym().
|
||||
*
|
||||
* The function must return a SenseApiDesc struct that Smo will tell
|
||||
* Smo what implexors can be used with it & what APIs it exports.
|
||||
* The SenseApiDesc struct also gives Smo pointers to API functions
|
||||
* The function must return a StimBuffApiDesc struct that Smo will tell
|
||||
* Smo what quale-iface APIs can be used with it & what APIs it exports.
|
||||
* The StimBuffApiDesc struct also gives Smo pointers to API functions
|
||||
* to invoke for communication between Smo and the library.
|
||||
*
|
||||
* The SmoCallbacks parameter provides the library with access to
|
||||
@@ -178,11 +181,11 @@ public:
|
||||
* The SmoThreadingModelDesc parameter provides the library with access to
|
||||
* the io_service for network operations and event handling.
|
||||
*/
|
||||
typedef const SenseApiDesc &(SMO_GET_SENSE_API_DESC_FN_TYPEDEF)(
|
||||
typedef const StimBuffApiDesc &(SMO_GET_STIM_BUFF_API_DESC_FN_TYPEDEF)(
|
||||
const SmoCallbacks& callbacks,
|
||||
const SmoThreadingModelDesc& threadingModel);
|
||||
|
||||
} // namespace sense_api
|
||||
} // namespace stim_buff
|
||||
} // namespace smo
|
||||
|
||||
#endif // __USER_SENSE_API_LIB_H__
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
#ifndef _SEQUENCE_LOCK_H
|
||||
#define _SEQUENCE_LOCK_H
|
||||
|
||||
#include <atomic>
|
||||
#include <optional>
|
||||
|
||||
namespace smo {
|
||||
|
||||
/**
|
||||
* @brief Sequence lock synchronization primitive
|
||||
*
|
||||
* A reader-writer synchronization primitive where writers increment the
|
||||
* sequence number (odd = writing in progress, even = stable) and readers
|
||||
* check the sequence number to detect concurrent modifications.
|
||||
*/
|
||||
class SequenceLock
|
||||
{
|
||||
public:
|
||||
SequenceLock()
|
||||
: sequenceNo(0)
|
||||
{}
|
||||
|
||||
~SequenceLock() = default;
|
||||
|
||||
// Non-copyable, non-movable (std::atomic is neither copyable nor movable)
|
||||
SequenceLock(const SequenceLock&) = delete;
|
||||
SequenceLock& operator=(const SequenceLock&) = delete;
|
||||
SequenceLock(SequenceLock&&) = delete;
|
||||
SequenceLock& operator=(SequenceLock&&) = delete;
|
||||
|
||||
/* Atomically increments sequenceNo and issues a release barrier.
|
||||
* Makes the sequence number odd, indicating a write is in progress.
|
||||
*/
|
||||
void writeAcquire()
|
||||
{ sequenceNo.fetch_add(1, std::memory_order_release); }
|
||||
|
||||
/* Atomically increments sequenceNo and issues a release barrier.
|
||||
* Makes the sequence number even again, indicating write is complete.
|
||||
*/
|
||||
void writeRelease()
|
||||
{ sequenceNo.fetch_add(1, std::memory_order_release); }
|
||||
|
||||
/* Issues an acquire barrier and checks if the sequence number is even
|
||||
* (stable state). If odd (writer active), returns nullopt. Otherwise
|
||||
* returns the sequence number.
|
||||
*
|
||||
* @return std::nullopt if writer is active, otherwise the sequence number
|
||||
*/
|
||||
std::optional<size_t> readAcquire()
|
||||
{
|
||||
size_t seq = sequenceNo.load(std::memory_order_acquire);
|
||||
if (seq & 1) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return seq;
|
||||
}
|
||||
|
||||
/* Issues an acquire barrier and checks if the sequence number matches
|
||||
* the original value from readAcquire(). If equal, the read was consistent.
|
||||
*
|
||||
* @param originalSequenceNo The sequence number obtained from readAcquire()
|
||||
* @return true if read was consistent, false if writer modified during read
|
||||
*/
|
||||
bool readRelease(size_t originalSequenceNo)
|
||||
{
|
||||
size_t seq = sequenceNo.load(std::memory_order_acquire);
|
||||
return seq == originalSequenceNo;
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic<size_t> sequenceNo;
|
||||
};
|
||||
|
||||
} // namespace smo
|
||||
|
||||
#endif // _SEQUENCE_LOCK_H
|
||||
@@ -0,0 +1,140 @@
|
||||
#ifndef _SP_MC_RING_BUFFER_H
|
||||
#define _SP_MC_RING_BUFFER_H
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <cstddef>
|
||||
#include <stdexcept>
|
||||
#include <algorithm>
|
||||
#include <user/sequenceLock.h>
|
||||
|
||||
namespace smo {
|
||||
|
||||
/**
|
||||
* @brief Single-producer, multi-consumer ring buffer w/per-slot sequence locks
|
||||
*
|
||||
* A ring buffer that maintains data alignment constraints while providing
|
||||
* lock-free read access through per-slot sequence locks. The locks are kept
|
||||
* separate from the data to preserve alignment requirements for the input
|
||||
* engine.
|
||||
*/
|
||||
class SpMcRingBuffer
|
||||
{
|
||||
public:
|
||||
class InputEngineConstraints
|
||||
{
|
||||
public:
|
||||
InputEngineConstraints(
|
||||
size_t slotStartAlignmentNBytes_,
|
||||
size_t slotPadToNBytes_)
|
||||
: slotStartAlignmentNBytes(slotStartAlignmentNBytes_),
|
||||
slotPadToNBytes(slotPadToNBytes_)
|
||||
{}
|
||||
|
||||
~InputEngineConstraints() = default;
|
||||
|
||||
// Input-engine layout/constraints
|
||||
size_t slotStartAlignmentNBytes; // power-of-2 alignment (e.g., 4096)
|
||||
size_t slotPadToNBytes; // minimum size per slot
|
||||
};
|
||||
|
||||
public:
|
||||
/** EXPLANATION:
|
||||
* Constructor initializes the ring buffer with the given constraints and
|
||||
* number of slots. Calculates stride and allocates data buffer and sequence
|
||||
* locks array.
|
||||
*/
|
||||
explicit SpMcRingBuffer(
|
||||
size_t nSlots_,
|
||||
const InputEngineConstraints& constraints_)
|
||||
: nSlots(nSlots_), strideNBytes(0), bufferNBytes(0),
|
||||
constraints(constraints_)
|
||||
{
|
||||
if (nSlots == 0)
|
||||
{
|
||||
throw std::invalid_argument(std::string(__func__)
|
||||
+ ": SpMcRingBuffer: nSlots must be > 0");
|
||||
}
|
||||
|
||||
computeStrideAndBufferSize();
|
||||
// Allocate data buffer: bufferNBytes (aligned up to alignment)
|
||||
data.resize(bufferNBytes);
|
||||
// Initialize sequence locks array: one lock per slot
|
||||
// Use unique_ptr array since SequenceLock is not copyable or movable
|
||||
sequenceLocks = std::make_unique<SequenceLock[]>(nSlots);
|
||||
}
|
||||
|
||||
~SpMcRingBuffer() = default;
|
||||
|
||||
// Non-copyable, movable
|
||||
SpMcRingBuffer(const SpMcRingBuffer&) = delete;
|
||||
SpMcRingBuffer& operator=(const SpMcRingBuffer&) = delete;
|
||||
SpMcRingBuffer(SpMcRingBuffer&&) = default;
|
||||
SpMcRingBuffer& operator=(SpMcRingBuffer&&) = default;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Get a reference to data at the specified slot
|
||||
*
|
||||
* @tparam T The type of data stored in the slot
|
||||
* @param slotIndex The index of the slot (0-based)
|
||||
* @return Reference to T at the slot
|
||||
* @throws std::out_of_range if slotIndex >= nSlots
|
||||
*/
|
||||
template<typename T>
|
||||
T& getDataAtSlot(size_t slotIndex)
|
||||
{
|
||||
if (slotIndex >= nSlots)
|
||||
{
|
||||
throw std::out_of_range(std::string(__func__)
|
||||
+ ": SpMcRingBuffer: slotIndex must be < nSlots");
|
||||
}
|
||||
|
||||
size_t offset = slotIndex * strideNBytes;
|
||||
return *reinterpret_cast<T*>(data.data() + offset);
|
||||
}
|
||||
|
||||
SequenceLock& getSequenceLockAtSlot(size_t slotIndex)
|
||||
{
|
||||
if (slotIndex >= nSlots)
|
||||
{
|
||||
throw std::out_of_range(std::string(__func__)
|
||||
+ ": SpMcRingBuffer: slotIndex must be < nSlots");
|
||||
}
|
||||
return sequenceLocks[slotIndex];
|
||||
}
|
||||
|
||||
private:
|
||||
void computeStrideAndBufferSize()
|
||||
{
|
||||
// Stride is the maximum of alignment and padding
|
||||
strideNBytes = std::max(
|
||||
constraints.slotStartAlignmentNBytes,
|
||||
constraints.slotPadToNBytes);
|
||||
|
||||
// Buffer size is nSlots * strideNBytes, aligned up to alignment
|
||||
size_t rawSize = nSlots * strideNBytes;
|
||||
bufferNBytes = ((rawSize + constraints.slotStartAlignmentNBytes - 1)
|
||||
/ constraints.slotStartAlignmentNBytes)
|
||||
* constraints.slotStartAlignmentNBytes;
|
||||
}
|
||||
|
||||
// Buffer data
|
||||
std::vector<uint8_t> data;
|
||||
|
||||
// Sequence locks array: one lock per slot
|
||||
// Use unique_ptr array since SequenceLock is not copyable or movable
|
||||
std::unique_ptr<SequenceLock[]> sequenceLocks;
|
||||
|
||||
public:
|
||||
// Layout/invariants
|
||||
size_t nSlots;
|
||||
size_t strideNBytes;
|
||||
size_t bufferNBytes;
|
||||
InputEngineConstraints constraints;
|
||||
};
|
||||
|
||||
} // namespace smo
|
||||
|
||||
#endif // _SP_MC_RING_BUFFER_H
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
#ifndef _STENCIL_H
|
||||
#define _STENCIL_H
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <user/stimFrame.h>
|
||||
#include <mentalEntity.h>
|
||||
|
||||
namespace smo {
|
||||
namespace cologex {
|
||||
|
||||
/**
|
||||
* Stencil represents range descriptions for sub-regions of sensor data frames.
|
||||
*
|
||||
* When a sensor yields frames with multiple values per frame, the Stencil class
|
||||
* allows the stimbufflib driver to describe the subset of the input data that
|
||||
* is relevant to SMO. For example:
|
||||
*
|
||||
* * A HSB format camera might treat brightness values above 128 as
|
||||
* negtrins, creating a Stencil that denotes all offsets in a
|
||||
* frame that exceed 128.
|
||||
*
|
||||
* * A lidar yielding XYZI[ntensity] might consider I values exceeding 128 to be
|
||||
* negtrins, creating a Stencil listing all values in the point
|
||||
* cloud that exceed 128.
|
||||
*
|
||||
* The Stencil internally represents offsets with ranges or other efficient
|
||||
* formats to describe offsets (e.g., by row). The internal format is opaque to
|
||||
* the stimbufflib, which describes relevant ranges by calling Stencil methods.
|
||||
*/
|
||||
class Stencil
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Constructor that takes a shared_ptr to StimFrame and produces a completed
|
||||
* Stencil. The Stencil scans the StimFrame and efficiently allocates
|
||||
* internal structures to describe the stencil ranges.
|
||||
*
|
||||
* @param frame Shared pointer to the StimFrame to analyze
|
||||
* @param threshold The threshold value for determining relevant data
|
||||
*/
|
||||
Stencil(
|
||||
const std::shared_ptr<stim_buff::StimFrame> &frame,
|
||||
const uint32_t threshold)
|
||||
: frame(frame), threshold(threshold)
|
||||
{}
|
||||
virtual ~Stencil() = default;
|
||||
|
||||
/**
|
||||
* Pure virtual method for derived classes to implement their specific
|
||||
* threshold analysis logic. Returns true if there are values above threshold,
|
||||
* false otherwise.
|
||||
*/
|
||||
virtual bool analyzeFrame() = 0;
|
||||
|
||||
/**
|
||||
* Stencil is constructed from a StimFrame. If the input StimFrame had no
|
||||
* values above threshold, then the Stencil will have no data.
|
||||
*/
|
||||
virtual bool hasData() const = 0;
|
||||
operator bool() const { return hasData(); }
|
||||
bool operator!() const { return !hasData(); }
|
||||
|
||||
// Return the number of relevant ranges/offsets in this Stencil.
|
||||
virtual size_t getRelevantCount() const = 0;
|
||||
// Return true if the offset is relevant, false otherwise
|
||||
virtual bool isRelevant(size_t offset) const = 0;
|
||||
|
||||
/**
|
||||
* Build internal stencil metadata from the shared_ptr member to describe
|
||||
* the range of StimFrame values that are relevant.
|
||||
*/
|
||||
virtual bool buildStencilMetadata() = 0;
|
||||
|
||||
protected:
|
||||
uint32_t threshold;
|
||||
std::shared_ptr<stim_buff::StimFrame> frame;
|
||||
};
|
||||
|
||||
} // namespace cologex
|
||||
} // namespace smo
|
||||
|
||||
#endif // _STENCIL_H
|
||||
@@ -0,0 +1,70 @@
|
||||
#ifndef _STIM_FRAME_H
|
||||
#define _STIM_FRAME_H
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace smo {
|
||||
namespace stim_buff {
|
||||
|
||||
/** EXPLANATION:
|
||||
* A simultaneity stamp is a timestamp that is used to determine whether two
|
||||
* stimulus frames occured simultaneously. Its purpose is adamantly *NOT* to
|
||||
* record or denote the "absolute" time of the stimulus frames. I cannot stress
|
||||
* this enough. The simultaneity stamp is NOT used to record "when" the stimulus
|
||||
* frame occured. It is used *SOLELY* to record that two or more stimulus frames
|
||||
* occured at the same time.
|
||||
*
|
||||
* The SMO has absolutely no notion of "absolute" time. It only has a notion of
|
||||
* simultaneity among stimulus frames. Any notions of "absolute" time are built
|
||||
* up consciously and volitionally by the running mind, and not baked into the
|
||||
* underlying software (i.e: Salmanoff).
|
||||
*
|
||||
* We need about 36 bits of unique simultaneity per year, assuming that we only
|
||||
* expect to capture 1000 stim frames per second. 1000 is a lot of stim frames
|
||||
* per second. If we use a 64 bit integer, that leaves us with 2^28 years
|
||||
* before our simultaneity stamps roll over. That's 256 million years.
|
||||
*
|
||||
* The calculation we used to arrive at 36 bits is as follows:
|
||||
* hex(86400 * 400 * 1000) = 0x80befc000
|
||||
* * 86400 = seconds per day.
|
||||
* * 400 = days per year.
|
||||
* * 1000 = stim frames per second.
|
||||
* As you can see, our extremely cautious calculation resulted in 36 bits.
|
||||
* If we use a UUID (128 bits), we can basically be fairly sure we won't
|
||||
* rollover for ...aeons. Now the question is: should we use a UUID or a 64 bit
|
||||
* integer?
|
||||
*
|
||||
* It's important to note that simultaneity stamps are not used in all mental
|
||||
* entities. They're only used in raw chronomena recordings, and possibly
|
||||
* also in artificed memory chronomena. Among the artificed chronomena, their
|
||||
* simultaneity lifetime is usually self-contained. Only the raw, observed
|
||||
* chronomena have to retain a lifetime that is basically "the person's
|
||||
* lifespan" (though not even necessarily that long).
|
||||
*
|
||||
* It may not even necessarily need to be lifespan-unique because the purpose of
|
||||
* simultaneity stamps is to denote simultaneity among the stim frames that are
|
||||
* __actually stored__ in the mind's memories. So if we forgot all stim frames
|
||||
* with simultaneity stamps that older than say, 1000, then we can re-use all
|
||||
* the simultaneity stamps that are numerically less than 1000. So there's some
|
||||
* dynamic recycling, and we can prolly keep track of the oldest simultaneity
|
||||
* stamp that we are currently using.
|
||||
*
|
||||
* Also, since simultaneity stamps are *NOT* used to record "when" the stimulus
|
||||
* frame occured, we can also periodically run a reclaiming daemon process on
|
||||
* our stored memories, which will try to defragment the simultaneity stamps
|
||||
* in use by currently stored chronomena. Or we can silently mutate the
|
||||
* simultaneity stamps of chronomena when committing them to backing storage;
|
||||
* as well as when loading them from backing storage.
|
||||
*/
|
||||
typedef uint64_t SimultaneityStamp;
|
||||
|
||||
class StimFrame
|
||||
{
|
||||
public:
|
||||
SimultaneityStamp simultaneityStamp;
|
||||
};
|
||||
|
||||
} // namespace stim_buff
|
||||
} // namespace smo
|
||||
|
||||
#endif // _STIM_FRAME_H
|
||||
@@ -0,0 +1,114 @@
|
||||
#ifndef _STIMULUS_BUFFER_H
|
||||
#define _STIMULUS_BUFFER_H
|
||||
|
||||
#include <boostAsioLinkageFix.h>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <cstdint>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <config.h>
|
||||
#include <boost/asio/io_service.hpp>
|
||||
#include <boost/asio/deadline_timer.hpp>
|
||||
#include <spinLock.h>
|
||||
#include <asynchronousBridge.h>
|
||||
#include <user/spMcRingBuffer.h>
|
||||
#include "stimFrame.h"
|
||||
#include "deviceAttachmentSpec.h"
|
||||
|
||||
namespace smo {
|
||||
namespace stim_buff {
|
||||
|
||||
/**
|
||||
* StimulusBuffer manages a collection of stimulus frames with simultaneity stamps.
|
||||
*
|
||||
* This buffer is designed to hold stimulus frames that have been assembled
|
||||
* from raw sensor data (e.g., Livox Avia point cloud data) and are ready
|
||||
* for processing by the mind layer.
|
||||
*
|
||||
* The buffer provides thread-safe operations for adding frames, retrieving
|
||||
* frames, and managing the buffer state.
|
||||
*/
|
||||
class StimulusBuffer
|
||||
{
|
||||
public:
|
||||
class PcloudFormatDesc
|
||||
{
|
||||
public:
|
||||
enum class Format
|
||||
{
|
||||
XYZ,
|
||||
XYZI,
|
||||
};
|
||||
|
||||
public:
|
||||
Format format;
|
||||
};
|
||||
|
||||
public:
|
||||
explicit StimulusBuffer(
|
||||
const std::shared_ptr<device::DeviceAttachmentSpec>
|
||||
&deviceAttachmentSpec,
|
||||
size_t nSlots,
|
||||
const SpMcRingBuffer::InputEngineConstraints& ringBufferConstraints,
|
||||
boost::asio::io_service& ioService_)
|
||||
: deviceAttachmentSpec(deviceAttachmentSpec),
|
||||
ringBuffer(nSlots, ringBufferConstraints),
|
||||
ioService(ioService_),
|
||||
shouldContinue(false), timer(ioService)
|
||||
{}
|
||||
|
||||
virtual ~StimulusBuffer() = default;
|
||||
|
||||
// Non-copyable, movable
|
||||
StimulusBuffer(const StimulusBuffer&) = delete;
|
||||
StimulusBuffer& operator=(const StimulusBuffer&) = delete;
|
||||
StimulusBuffer(StimulusBuffer&&) = default;
|
||||
StimulusBuffer& operator=(StimulusBuffer&&) = default;
|
||||
|
||||
// Control methods
|
||||
virtual void start()
|
||||
{
|
||||
std::cout << __func__ << ": Starting stimulus buffer for device "
|
||||
<< deviceAttachmentSpec->deviceSelector << std::endl;
|
||||
|
||||
shouldContinue.store(true);
|
||||
scheduleNextTimeout();
|
||||
}
|
||||
|
||||
virtual void stop();
|
||||
|
||||
protected:
|
||||
// Virtual functions for derived classes to override
|
||||
virtual int getStopDelayMs() const
|
||||
{
|
||||
return CONFIG_STIMBUFF_FRAME_PERIOD_MS;
|
||||
}
|
||||
|
||||
virtual void stimFrameProductionTimesliceInd() = 0;
|
||||
|
||||
private:
|
||||
void onTimeout(const boost::system::error_code& error);
|
||||
|
||||
public:
|
||||
std::shared_ptr<device::DeviceAttachmentSpec> deviceAttachmentSpec;
|
||||
std::vector<StimFrame> frames_;
|
||||
|
||||
protected:
|
||||
SpinLock frameAssemblyRateLimiter;
|
||||
SpMcRingBuffer ringBuffer;
|
||||
|
||||
private:
|
||||
boost::asio::io_service& ioService;
|
||||
std::atomic<bool> shouldContinue;
|
||||
boost::asio::deadline_timer timer;
|
||||
|
||||
void scheduleNextTimeout(int delayMs = CONFIG_STIMBUFF_FRAME_PERIOD_MS);
|
||||
};
|
||||
|
||||
} // namespace stim_buff
|
||||
} // namespace smo
|
||||
|
||||
#endif // _STIMULUS_BUFFER_H
|
||||
@@ -1,10 +1,12 @@
|
||||
#include <iostream>
|
||||
#include <pthread.h>
|
||||
#include <componentThread.h>
|
||||
#include <marionette/marionette.h>
|
||||
|
||||
|
||||
int main(int argc, char *argv[], char *envp[])
|
||||
{
|
||||
pthread_setname_np(pthread_self(), "smo:CRT:main");
|
||||
/* We don't do anything inside of main()
|
||||
* Main merely waits for the marionette thread to exit.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
==========================================
|
||||
Iteration 67 - Thu Oct 30 08:41:13 PM AST 2025
|
||||
==========================================
|
||||
|
||||
GNU gdb (Ubuntu 15.0.50.20240403-0ubuntu1) 15.0.50.20240403-git
|
||||
Copyright (C) 2024 Free Software Foundation, Inc.
|
||||
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
|
||||
This is free software: you are free to change and redistribute it.
|
||||
There is NO WARRANTY, to the extent permitted by law.
|
||||
Type "show copying" and "show warranty" for details.
|
||||
This GDB was configured as "x86_64-linux-gnu".
|
||||
Type "show configuration" for configuration details.
|
||||
For bug reporting instructions, please see:
|
||||
<https://www.gnu.org/software/gdb/bugs/>.
|
||||
Find the GDB manual and other documentation resources online at:
|
||||
<http://www.gnu.org/software/gdb/documentation/>.
|
||||
|
||||
For help, type "help".
|
||||
Type "apropos word" to search for commands related to "word"...
|
||||
Reading symbols from salmanoff...
|
||||
SIGINT is used by the debugger.
|
||||
Are you sure you want to change it? (y or n) [answered Y; input not from terminal]
|
||||
Waiting 2281ms before sending SIGINT...
|
||||
Starting program...
|
||||
|
||||
This GDB supports auto-downloading debuginfo from the following URLs:
|
||||
<https://debuginfod.ubuntu.com>
|
||||
Enable debuginfod for this session? (y or [n]) [answered N; input not from terminal]
|
||||
Debuginfod has been disabled.
|
||||
To make this setting permanent, add 'set debuginfod enabled off' to .gdbinit.
|
||||
[Thread debugging using libthread_db enabled]
|
||||
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
|
||||
[New Thread 0x7ffff77ff6c0 (LWP 805031)]
|
||||
CRT:main: about to JOLT Mrntt with cmdline args
|
||||
main: Waiting for command line JOLT
|
||||
Mrntt:operator():JOLTED: setting cmdline args
|
||||
main: salmanoff 0.01.000
|
||||
main: DAP Specs:
|
||||
DAP Spec Files: devices/bodies/dell-laptop.daps
|
||||
Stim Buff API Library Paths: commonLibs/livoxProto1/ commonLibs/xcbXorg/ stimBuffApis/xcbWindow/ stimBuffApis/livoxGen1/
|
||||
Stim Buff API Libraries: libxcbWindow.so liblivoxGen1.so
|
||||
|
||||
initializeSalmanoff: Entered.
|
||||
|
||||
main: Entering event loop
|
||||
[New Thread 0x7ffff6ffe6c0 (LWP 805032)]
|
||||
[New Thread 0x7ffff67fd6c0 (LWP 805033)]
|
||||
[New Thread 0x7ffff5ffc6c0 (LWP 805034)]
|
||||
[New Thread 0x7ffff57fb6c0 (LWP 805035)]
|
||||
[New Thread 0x7ffff4ffa6c0 (LWP 805036)]
|
||||
distributeAndPinThreadsAcrossCpus: Distributed 5 threads across 4 CPUs
|
||||
joltThreadReq1_posted: Thread 'director': handling JOLT request.
|
||||
joltThreadReq1_posted: Thread 'simulator': handling JOLT request.
|
||||
joltThreadReq1_posted: Thread 'subconscious': handling JOLT request.
|
||||
joltThreadReq1_posted: Thread 'body': handling JOLT request.
|
||||
body:main: Entering event loop
|
||||
simulator:main: Entering event loop
|
||||
subconscious:main: Entering event loop
|
||||
joltThreadReq1_posted: Thread 'world': handling JOLT request.
|
||||
world:main: Entering event loop
|
||||
Mrntt: All mind threads JOLTed.
|
||||
director:main: Entering event loop
|
||||
startThreadReq1_posted: Thread 'director': handling startThread.
|
||||
startThreadReq1_posted: Thread 'body': handling startThread.
|
||||
startThreadReq1_posted: Thread 'simulator': handling startThread.
|
||||
startThreadReq1_posted: Thread 'world': handling startThread.
|
||||
startThreadReq1_posted: Thread 'subconscious': handling startThread.
|
||||
Mrntt: All mind threads started.
|
||||
Library Path: libxcbWindow.so
|
||||
Stim Buff API Descriptor: Name: xcb
|
||||
Exported QualeIface APIs:
|
||||
- visual-qualeiface
|
||||
|
||||
|
||||
Library Path: liblivoxGen1.so
|
||||
Stim Buff API Descriptor: Name: livoxGen1
|
||||
Exported QualeIface APIs:
|
||||
- pcloud
|
||||
- pcloudIntensity
|
||||
- gyro
|
||||
- accel
|
||||
|
||||
|
||||
|
||||
start: BroadcastListener started on port 55000
|
||||
start: UDP Command Demuxer started on port 56001
|
||||
attachStimBuffDeviceReq1_posted: Attaching edev win0 to world thread
|
||||
xcbWindow_attachDeviceReq: Attached X11 window:
|
||||
Display: 1, Screen: 0, MatchType: substring, Target: "mut", Found: "mutter guard window" (matched substring 'mut')
|
||||
attachStimBuffDeviceReq1_posted: Attaching edev avia0 to world thread
|
||||
getOrCreateDeviceReq1: Connection failed for device 3JEDK380010Z39
|
||||
attachDeviceReq1: Failed to create/find Livox device: 3JEDK380010Z39
|
||||
newDeviceAttachmentSpecInd2: Attach failed for device spec Device Identifier: avia0, Sensor Type: e, QualeIface API: structural-qualeiface, StimBuff API: livoxGen1, StimBuff API Params: (), Provider: livoxProto1, Provider Params: (), Device Selector: 3JEDK380010Z39
|
||||
|
||||
attachAllUnattachedDevicesFromReq2: Failed to attach device: avia0
|
||||
Mrntt: attached 1 of 2 sense devices.
|
||||
Mrntt: Body component initialized.
|
||||
negtrinEventInd: Handling negtrin event.
|
||||
marionetteInitializeReqCb: Marionette initialized.
|
||||
broadcastMsgInd: Discovered new Livox device: DiscoveredDevice{identifier='3JEDK380010Z391', ipAddr='10.42.0.139', deviceType=7 (Avia)}
|
||||
attachStimBuffDeviceReq1_posted: Attaching edev avia0 to world thread
|
||||
attachDeviceReq1: Successfully attached/found Livox device: 3JEDK380010Z39 (ID: avia0)
|
||||
Sending SIGINT to program (PID: 805028)...
|
||||
SIGINT (Ctrl+C) received. Initiating shutdown...
|
||||
Mrntt: About to detach all sense devices.
|
||||
xcbWindow_detachDeviceReq: Detached X11 window device:
|
||||
Device Identifier: win0, Sensor Type: e, QualeIface API: visual-qualeiface, StimBuff API: xcb, StimBuff API Params: (dev-substring ), Provider: xorg, Provider Params: (display=1 screen=0 ), Device Selector: mut
|
||||
|
||||
Mrntt: Successfully detached 1 of 1 sense devices.
|
||||
Mrntt: About to finalize all stim buff api libs.
|
||||
stop: UDP Command Demuxer stopped
|
||||
stop: BroadcastListener stopped
|
||||
broadcastMsgInd: Error receiving broadcast message: Operation canceled
|
||||
Mrntt: About to unload all stim buff api libs.
|
||||
|
||||
Thread 7 "salmanoff" received signal SIGSEGV, Segmentation fault.
|
||||
[Switching to Thread 0x7ffff4ffa6c0 (LWP 805036)]
|
||||
0x0000000000000000 in ?? ()
|
||||
|
||||
=== SEGFAULT DETECTED ===
|
||||
#0 0x0000000000000000 in ?? ()
|
||||
#1 0x00007ffff7ace057 in smo::stim_buff::AttachDeviceReq::attachDeviceReq2 (this=0x7ffff00098a0,
|
||||
context=std::shared_ptr<smo::stim_buff::AttachDeviceReq> (use count 4, weak count 1) = {...}, error=...) at /home/latentprion/gits/salmanoff-git/stimBuffApis/livoxGen1/livoxGen1.cpp:160
|
||||
#2 0x00007ffff7ae6584 in std::__invoke_impl<void, void (smo::stim_buff::AttachDeviceReq::*&)(std::shared_ptr<smo::stim_buff::AttachDeviceReq>, boost::system::error_code const&), smo::stim_buff::AttachDeviceReq*&, std::shared_ptr<smo::stim_buff::AttachDeviceReq>&, boost::system::error_code const&> (
|
||||
__f=@0x7ffff4ff9a80: (void (smo::stim_buff::AttachDeviceReq::*)(smo::stim_buff::AttachDeviceReq * const, std::shared_ptr<smo::stim_buff::AttachDeviceReq>, const boost::system::error_code &)) 0x7ffff7acde68 <smo::stim_buff::AttachDeviceReq::attachDeviceReq2(std::shared_ptr<smo::stim_buff::AttachDeviceReq>, boost::system::error_code const&)>,
|
||||
__t=@0x7ffff4ff9aa0: 0x7ffff00098a0) at /usr/include/c++/13/bits/invoke.h:74
|
||||
#3 0x00007ffff7ae42b1 in std::__invoke<void (smo::stim_buff::AttachDeviceReq::*&)(std::shared_ptr<smo::stim_buff::AttachDeviceReq>, boost::system::error_code const&), smo::stim_buff::AttachDeviceReq*&, std::shared_ptr<smo::stim_buff::AttachDeviceReq>&, boost::system::error_code const&> (
|
||||
__fn=@0x7ffff4ff9a80: (void (smo::stim_buff::AttachDeviceReq::*)(smo::stim_buff::AttachDeviceReq * const, std::shared_ptr<smo::stim_buff::AttachDeviceReq>, const boost::system::error_code &)) 0x7ffff7acde68 <smo::stim_buff::AttachDeviceReq::attachDeviceReq2(std::shared_ptr<smo::stim_buff::AttachDeviceReq>, boost::system::error_code const&)>)
|
||||
at /usr/include/c++/13/bits/invoke.h:96
|
||||
#4 0x00007ffff7ae1fe1 in std::_Bind<void (smo::stim_buff::AttachDeviceReq::*(smo::stim_buff::AttachDeviceReq*, std::shared_ptr<smo::stim_buff::AttachDeviceReq>, std::_Placeholder<1>))(std::shared_ptr<smo::stim_buff::AttachDeviceReq>, boost::system::error_code const&)>::__call<void, boost::system::error_code const&, 0ul, 1ul, 2ul>(std::tuple<boost::system::error_code const&>&&, std::_Index_tuple<0ul, 1ul, 2ul>) (this=0x7ffff4ff9a80, __args=...) at /usr/include/c++/13/functional:506
|
||||
#5 0x00007ffff7ade79a in std::_Bind<void (smo::stim_buff::AttachDeviceReq::*(smo::stim_buff::AttachDeviceReq*, std::shared_ptr<smo::stim_buff::AttachDeviceReq>, std::_Placeholder<1>))(std::shared_ptr<smo::stim_buff::AttachDeviceReq>, boost::system::error_code const&)>::operator()<boost::system::error_code const&, void>(boost::system::error_code const&) (this=0x7ffff4ff9a80)
|
||||
at /usr/include/c++/13/functional:591
|
||||
#6 0x00007ffff7aec999 in boost::asio::detail::binder1<std::_Bind<void (smo::stim_buff::AttachDeviceReq::*(smo::stim_buff::AttachDeviceReq*, std::shared_ptr<smo::stim_buff::AttachDeviceReq>, std::_Placeholder<1>))(std::shared_ptr<smo::stim_buff::AttachDeviceReq>, boost::system::error_code const&)>, boost::system::error_code>::operator()() (this=0x7ffff4ff9a80)
|
||||
at /usr/include/boost/asio/detail/bind_handler.hpp:171
|
||||
#7 0x00007ffff7aebd0e in boost::asio::asio_handler_invoke<boost::asio::detail::binder1<std::_Bind<void (smo::stim_buff::AttachDeviceReq::*(smo::stim_buff::AttachDeviceReq*, std::shared_ptr<smo::stim_buff::AttachDeviceReq>, std::_Placeholder<1>))(std::shared_ptr<smo::stim_buff::AttachDeviceReq>, boost::system::error_code const&)>, boost::system::error_code> >(boost::asio::detail::binder1<std::_Bind<void (smo::stim_buff::AttachDeviceReq::*(smo::stim_buff::AttachDeviceReq*, std::shared_ptr<smo::stim_buff::AttachDeviceReq>, std::_Placeholder<1>))(std::shared_ptr<smo::stim_buff::AttachDeviceReq>, boost::system::error_code const&)>, boost::system::error_code>&, ...) (function=...) at /usr/include/boost/asio/handler_invoke_hook.hpp:88
|
||||
#8 0x00007ffff7aea450 in boost_asio_handler_invoke_helpers::invoke<boost::asio::detail::binder1<std::_Bind<void (smo::stim_buff::AttachDeviceReq::*(smo::stim_buff::AttachDeviceReq*, std::shared_ptr<smo::stim_buff::AttachDeviceReq>, std::_Placeholder<1>))(std::shared_ptr<smo::stim_buff::AttachDeviceReq>, boost::system::error_code const&)>, boost::system::error_code>, std::_Bind<void (smo::stim_buff::AttachDeviceReq::*(smo::stim_buff::AttachDeviceReq*, std::shared_ptr<smo::stim_buff::AttachDeviceReq>, std::_Placeholder<1>))(std::shared_ptr<smo::stim_buff::AttachDeviceReq>, boost::system::error_code const&)> >(boost::asio::detail::binder1<std::_Bind<void (smo::stim_buff::AttachDeviceReq::*(smo::stim_buff::AttachDeviceReq*, std::shared_ptr<smo::stim_buff::AttachDeviceReq>, std::_Placeholder<1>))(std::shared_ptr<smo::stim_buff::AttachDeviceReq>, boost::system::error_code const&)>, boost::system::error_code>&, std::_Bind<void (smo::stim_buff::AttachDeviceReq::*(smo::stim_buff::AttachDeviceReq*, std::shared_ptr<smo::stim_buff::AttachDeviceReq>, std::_Placeholder<1>))(std::shared_ptr<smo::stim_buff::AttachDeviceReq>, boost::system::error_code const&)>&) (function=..., context=...) at /usr/include/boost/asio/detail/handler_invoke_helpers.hpp:54
|
||||
#9 0x00007ffff7ae8790 in boost::asio::detail::handler_work<std::_Bind<void (smo::stim_buff::AttachDeviceReq::*(smo::stim_buff::AttachDeviceReq*, std::shared_ptr<smo::stim_buff::AttachDeviceReq>, std::_Placeholder<1>))(std::shared_ptr<smo::stim_buff::AttachDeviceReq>, boost::system::error_code const&)>, boost::asio::any_io_executor, void>::complete<boost::asio::detail::binder1<std::_Bind<void (smo::stim_buff::AttachDeviceReq::*(smo::stim_buff::AttachDeviceReq*, std::shared_ptr<smo::stim_buff::AttachDeviceReq>, std::_Placeholder<1>))(std::shared_ptr<smo::stim_buff::AttachDeviceReq>, boost::system::error_code const&)>, boost::system::error_code> >(boost::asio::detail::binder1<std::_Bind<void (smo::stim_buff::AttachDeviceReq::*(smo::stim_buff::AttachDeviceReq*, std::shared_ptr<smo::stim_buff::AttachDeviceReq>, std::_Placeholder<1>))(std::shared_ptr<smo::stim_buff::AttachDeviceReq>, boost::system::error_code const&)>, boost::system::error_code>&, std::_Bind<void (smo::stim_buff::AttachDeviceReq::*(smo::stim_buff::AttachDeviceReq*, std::shared_ptr<smo::stim_buff::AttachDeviceReq>, std::_Placeholder<1>))(std::shared_ptr<smo::stim_buff::AttachDeviceReq>, boost::system::error_code const&)>&) (this=0x7ffff4ff9a40, function=..., handler=...) at /usr/include/boost/asio/detail/handler_work.hpp:524
|
||||
#10 0x00007ffff7ae6986 in boost::asio::detail::wait_handler<std::_Bind<void (smo::stim_buff::AttachDeviceReq::*(smo::stim_buff::AttachDeviceReq*, std::shared_ptr<smo::stim_buff::AttachDeviceReq>, std::_Placeholder<1>))(std::shared_ptr<smo::stim_buff::AttachDeviceReq>, boost::system::error_code const&)>, boost::asio::any_io_executor>::do_complete(void*, boost::asio::detail::scheduler_operation*, boost::system::error_code const&, unsigned long) (owner=0x7ffff0007970, base=0x7fffe400a180) at /usr/include/boost/asio/detail/wait_handler.hpp:76
|
||||
#11 0x000055555556d35e in boost::asio::detail::scheduler_operation::complete (this=0x7fffe400a180, owner=0x7ffff0007970, ec=..., bytes_transferred=0)
|
||||
at /usr/include/boost/asio/detail/scheduler_operation.hpp:40
|
||||
#12 0x00005555555706e7 in boost::asio::detail::scheduler::do_run_one (this=0x7ffff0007970, lock=..., this_thread=..., ec=...) at /usr/include/boost/asio/detail/impl/scheduler.ipp:493
|
||||
#13 0x00005555555700b9 in boost::asio::detail::scheduler::run (this=0x7ffff0007970, ec=...) at /usr/include/boost/asio/detail/impl/scheduler.ipp:210
|
||||
#14 0x0000555555570a9d in boost::asio::io_context::run (this=0x7ffff0007900) at /usr/include/boost/asio/impl/io_context.ipp:64
|
||||
#15 0x00005555555f6b10 in smo::MindThread::main (self=...) at /home/latentprion/gits/salmanoff-git/smocore/componentThread.cpp:82
|
||||
#16 0x00005555555f4ed3 in std::__invoke_impl<void, void (*)(smo::MindThread&), std::reference_wrapper<smo::MindThread> > (
|
||||
__f=@0x7ffff0007bf0: 0x5555555f6984 <smo::MindThread::main(smo::MindThread&)>) at /usr/include/c++/13/bits/invoke.h:61
|
||||
#17 0x00005555555f4e41 in std::__invoke<void (*)(smo::MindThread&), std::reference_wrapper<smo::MindThread> > (
|
||||
__fn=@0x7ffff0007bf0: 0x5555555f6984 <smo::MindThread::main(smo::MindThread&)>) at /usr/include/c++/13/bits/invoke.h:96
|
||||
#18 0x00005555555f4d2f in std::thread::_Invoker<std::tuple<void (*)(smo::MindThread&), std::reference_wrapper<smo::MindThread> > >::_M_invoke<0ul, 1ul> (this=0x7ffff0007be8)
|
||||
at /usr/include/c++/13/bits/std_thread.h:292
|
||||
#19 0x00005555555f4c88 in std::thread::_Invoker<std::tuple<void (*)(smo::MindThread&), std::reference_wrapper<smo::MindThread> > >::operator() (this=0x7ffff0007be8)
|
||||
at /usr/include/c++/13/bits/std_thread.h:299
|
||||
#20 0x00005555555f4bdc in std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (*)(smo::MindThread&), std::reference_wrapper<smo::MindThread> > > >::_M_run (this=0x7ffff0007be0)
|
||||
at /usr/include/c++/13/bits/std_thread.h:244
|
||||
#21 0x00007ffff7cecdb4 in ?? () from /lib/x86_64-linux-gnu/libstdc++.so.6
|
||||
#22 0x00007ffff789caa4 in start_thread (arg=<optimized out>) at ./nptl/pthread_create.c:447
|
||||
#23 0x00007ffff7929c6c in clone3 () at ../sysdeps/unix/sysv/linux/x86_64/clone3.S:78
|
||||
|
||||
=== GDB is now interactive - you can inspect the state ===
|
||||
[Thread 0x7ffff4ffa6c0 (LWP 805036) exited]
|
||||
[Thread 0x7ffff57fb6c0 (LWP 805035) exited]
|
||||
[Thread 0x7ffff5ffc6c0 (LWP 805034) exited]
|
||||
[Thread 0x7ffff67fd6c0 (LWP 805033) exited]
|
||||
[Thread 0x7ffff6ffe6c0 (LWP 805032) exited]
|
||||
[Thread 0x7ffff7f5f780 (LWP 805028) exited]
|
||||
[Thread 0x7ffff77ff6c0 (LWP 805031) exited]
|
||||
[New process 805028]
|
||||
|
||||
Program terminated with signal SIGSEGV, Segmentation fault.
|
||||
The program no longer exists.
|
||||
(gdb)
|
||||
@@ -0,0 +1,123 @@
|
||||
# GDB command file for reproducing UdpCommandDemuxer heisenbug
|
||||
# This script runs salmanoff, waits a random time, sends SIGINT, and catches segfaults
|
||||
|
||||
# Disable pager so output doesn't pause for user input
|
||||
set pagination off
|
||||
|
||||
# Set up signal handling - catch segfaults and stop
|
||||
handle SIGSEGV stop print
|
||||
# Allow SIGINT to pass through to program silently - make it unremarkable
|
||||
# nostop: don't stop execution, noprint: don't print message, pass: pass to program
|
||||
handle SIGINT nostop noprint pass
|
||||
|
||||
# Use Python to set up automatic handling of stop events and SIGINT injection
|
||||
python
|
||||
import time
|
||||
import random
|
||||
import threading
|
||||
import os
|
||||
import signal
|
||||
|
||||
sigint_thread_started = False
|
||||
|
||||
def send_sigint_after_delay():
|
||||
# Wait random milliseconds between 2000-3000
|
||||
delay_ms = random.randint(2000, 3000)
|
||||
print(f"Waiting {delay_ms}ms before sending SIGINT...")
|
||||
time.sleep(delay_ms / 1000.0)
|
||||
|
||||
# Send SIGINT directly to the process using its PID
|
||||
# This works even when the program is running (unlike gdb.execute("signal SIGINT"))
|
||||
try:
|
||||
inferior = gdb.selected_inferior()
|
||||
if inferior and inferior.is_valid():
|
||||
pid = inferior.pid
|
||||
print(f"Sending SIGINT to program (PID: {pid})...")
|
||||
os.kill(pid, signal.SIGINT)
|
||||
else:
|
||||
print("Program is not running - cannot send SIGINT")
|
||||
except Exception as e:
|
||||
print(f"Failed to send SIGINT: {e}")
|
||||
|
||||
def start_sigint_thread():
|
||||
global sigint_thread_started
|
||||
if not sigint_thread_started:
|
||||
sigint_thread_started = True
|
||||
thread = threading.Thread(target=send_sigint_after_delay, daemon=True)
|
||||
thread.start()
|
||||
|
||||
# Hook to check stop reason and handle segfaults
|
||||
def stop_handler(event):
|
||||
if isinstance(event, gdb.SignalEvent):
|
||||
if event.stop_signal == "SIGSEGV":
|
||||
# Segfault detected
|
||||
print("\n=== SEGFAULT DETECTED ===")
|
||||
gdb.execute("bt")
|
||||
print("\n=== GDB is now interactive - you can inspect the state ===")
|
||||
# Don't quit - stay in interactive mode
|
||||
elif event.stop_signal == "SIGINT":
|
||||
# SIGINT received - with "nostop pass", SIGINT should pass through automatically
|
||||
# But if we get here (shouldn't happen with nostop), just let it pass
|
||||
pass
|
||||
elif isinstance(event, gdb.ExitedEvent):
|
||||
# Program exited normally
|
||||
if event.exit_code == 0:
|
||||
print("\nProgram exited normally. Continuing loop...")
|
||||
gdb.post_event(lambda: gdb.execute("quit", False))
|
||||
else:
|
||||
print(f"\nProgram exited with code {event.exit_code}")
|
||||
gdb.post_event(lambda: gdb.execute("quit", False))
|
||||
|
||||
# Hook for when program continues/starts running
|
||||
def cont_handler(event):
|
||||
# When program continues (or starts running), start the SIGINT thread
|
||||
start_sigint_thread()
|
||||
|
||||
# Register event handlers
|
||||
gdb.events.stop.connect(stop_handler)
|
||||
gdb.events.cont.connect(cont_handler)
|
||||
|
||||
# Start SIGINT thread before running - it will wait and then send SIGINT
|
||||
# The thread will send SIGINT even if program is stopped (signal will be delivered on continue)
|
||||
start_sigint_thread()
|
||||
end
|
||||
|
||||
# Start the program
|
||||
echo Starting program...\n
|
||||
run
|
||||
|
||||
# After run completes, check if program exited or stopped
|
||||
# If program exited, quit GDB. If program stopped (has threads), continue.
|
||||
python
|
||||
try:
|
||||
inferior = gdb.selected_inferior()
|
||||
if inferior and inferior.is_valid():
|
||||
# Check if there are threads (indicates program has not exited)
|
||||
try:
|
||||
threads = inferior.threads()
|
||||
if threads:
|
||||
# Program has threads - continue execution
|
||||
# SIGINT thread is already running and will send signal when ready
|
||||
gdb.execute("continue", False)
|
||||
else:
|
||||
# No threads - program has exited
|
||||
print("\nProgram has exited (no threads found).")
|
||||
gdb.execute("quit", False)
|
||||
except Exception as e:
|
||||
# If we can't check threads, assume program exited
|
||||
print(f"\nError checking threads: {e}")
|
||||
print("Assuming program exited.")
|
||||
gdb.execute("quit", False)
|
||||
else:
|
||||
# Inferior is not valid - program has exited
|
||||
print("\nProgram has exited (inferior not valid).")
|
||||
gdb.execute("quit", False)
|
||||
except Exception as e:
|
||||
print(f"Error checking program state: {e}")
|
||||
# If we can't determine state, try to quit
|
||||
try:
|
||||
gdb.execute("quit", False)
|
||||
except:
|
||||
pass
|
||||
end
|
||||
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
#!/bin/bash
|
||||
# Script to reproduce UdpCommandDemuxer race condition heisenbug
|
||||
# Runs salmanoff in GDB repeatedly, injecting SIGINT at random intervals
|
||||
#
|
||||
# Usage: ./reproduce_heisenbug.sh [WORKING_DIR]
|
||||
# WORKING_DIR: Working directory where salmanoff binary and all paths are relative to
|
||||
# If not provided, uses WORKING_DIR environment variable, or defaults to project root
|
||||
#
|
||||
# Environment variables:
|
||||
# WORKING_DIR: Working directory (can be overridden by command-line argument)
|
||||
|
||||
# Get the directory where this script is located
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
# Determine working directory (command-line arg > env var > default)
|
||||
if [ -n "$1" ]; then
|
||||
WORKING_DIR="$1"
|
||||
elif [ -n "$WORKING_DIR" ]; then
|
||||
# Use environment variable
|
||||
:
|
||||
else
|
||||
# Default to project root
|
||||
WORKING_DIR="$PROJECT_ROOT"
|
||||
fi
|
||||
|
||||
# Convert to absolute path
|
||||
WORKING_DIR="$(cd "$WORKING_DIR" && pwd)"
|
||||
|
||||
# Check if working directory exists
|
||||
if [ ! -d "$WORKING_DIR" ]; then
|
||||
echo "Error: Working directory does not exist: $WORKING_DIR" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Paths - all relative to working directory
|
||||
SALMANOFF_BINARY="$WORKING_DIR/salmanoff"
|
||||
GDB_SCRIPT="$SCRIPT_DIR/gdb_heisenbug.gdb"
|
||||
|
||||
# Check if binary exists
|
||||
if [ ! -f "$SALMANOFF_BINARY" ]; then
|
||||
echo "Error: salmanoff binary not found at $SALMANOFF_BINARY" >&2
|
||||
echo "Working directory: $WORKING_DIR" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if GDB script exists
|
||||
if [ ! -f "$GDB_SCRIPT" ]; then
|
||||
echo "Error: GDB script not found at $GDB_SCRIPT" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Command line arguments for salmanoff
|
||||
SALMANOFF_ARGS=(
|
||||
-p commonLibs/livoxProto1/
|
||||
-p commonLibs/xcbXorg/
|
||||
-p stimBuffApis/xcbWindow/
|
||||
-p stimBuffApis/livoxGen1/
|
||||
-a libxcbWindow.so
|
||||
-a liblivoxGen1.so
|
||||
-d devices/bodies/dell-laptop.daps
|
||||
)
|
||||
|
||||
echo "=== UdpCommandDemuxer Heisenbug Reproduction Script ==="
|
||||
echo "Working Directory: $WORKING_DIR"
|
||||
echo "Binary: $SALMANOFF_BINARY"
|
||||
echo "GDB Script: $GDB_SCRIPT"
|
||||
echo "Arguments: ${SALMANOFF_ARGS[*]}"
|
||||
echo ""
|
||||
echo "Press Ctrl+C to stop the loop"
|
||||
echo ""
|
||||
|
||||
# Change to working directory so all relative paths are resolved correctly
|
||||
cd "$WORKING_DIR"
|
||||
|
||||
# Loop counter
|
||||
ITERATION=0
|
||||
|
||||
# Main loop
|
||||
while true; do
|
||||
ITERATION=$((ITERATION + 1))
|
||||
echo "=========================================="
|
||||
echo "Iteration $ITERATION - $(date)"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
# Run GDB with the command file
|
||||
# GDB will stay interactive on segfault, exit on normal completion
|
||||
# When GDB stays interactive (on segfault), this will wait for user to quit GDB
|
||||
# When GDB exits normally (program completed), exit code will be 0 and loop continues
|
||||
# Note: We use a relative path to salmanoff binary since we're already in WORKING_DIR
|
||||
SALMANOFF_RELATIVE="salmanoff"
|
||||
if gdb -x "$GDB_SCRIPT" --args "$SALMANOFF_RELATIVE" "${SALMANOFF_ARGS[@]}"; then
|
||||
# GDB exited successfully (program completed normally)
|
||||
EXIT_CODE=0
|
||||
else
|
||||
# GDB exited with error (unexpected exit or user interrupted)
|
||||
EXIT_CODE=$?
|
||||
echo ""
|
||||
echo "GDB exited with code $EXIT_CODE"
|
||||
if [ $EXIT_CODE -ne 0 ] && [ $EXIT_CODE -ne 130 ]; then
|
||||
# Exit code 130 is SIGINT (user pressed Ctrl+C), which is expected
|
||||
echo "Unexpected GDB exit - check output above"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Iteration $ITERATION complete. Starting next iteration in 1 second..."
|
||||
sleep 1
|
||||
echo ""
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "Loop terminated."
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
cmake_dependent_option(ENABLE_SENSEAPI_livoxGen1
|
||||
"Enable Livox Gen1 LiDAR sense API" ON
|
||||
"ENABLE_LIB_livoxProto1" OFF)
|
||||
|
||||
if(ENABLE_SENSEAPI_livoxGen1)
|
||||
add_library(livoxGen1 SHARED
|
||||
livoxGen1.cpp
|
||||
)
|
||||
|
||||
# Set config define for header generation
|
||||
add_compile_definitions(CONFIG_SENSEAPI_LIVOXGEN1_ENABLED)
|
||||
target_include_directories(livoxGen1 PUBLIC
|
||||
${Boost_INCLUDE_DIRS}
|
||||
${CMAKE_SOURCE_DIR}/commonLibs
|
||||
)
|
||||
target_link_libraries(livoxGen1
|
||||
${Boost_LIBRARIES}
|
||||
)
|
||||
|
||||
# Install rules
|
||||
install(TARGETS livoxGen1 DESTINATION lib)
|
||||
endif()
|
||||
@@ -1,416 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <functional>
|
||||
#include <algorithm>
|
||||
#include <dlfcn.h>
|
||||
#include <opts.h>
|
||||
#include <user/senseApiDesc.h>
|
||||
#include <user/deviceAttachmentSpec.h>
|
||||
#include <callback.h>
|
||||
#include <livoxProto1/livoxProto1.h>
|
||||
#include <livoxProto1/device.h>
|
||||
#include <asynchronousContinuation.h>
|
||||
|
||||
|
||||
namespace smo {
|
||||
namespace sense_api {
|
||||
|
||||
// Salmanoff hooks, obtained from SMO_GET_SENSE_API_DESC_FN_NAME().
|
||||
static const SmoCallbacks* smoHooksPtr = nullptr;
|
||||
static SmoThreadingModelDesc smoThreadingModelDesc;
|
||||
|
||||
// LivoxProto1 library state
|
||||
struct LivoxProto1DllState
|
||||
{
|
||||
LivoxProto1DllState()
|
||||
: dlopenHandle(nullptr, DlCloser),
|
||||
livoxProto1_main(nullptr),
|
||||
livoxProto1_exit(nullptr),
|
||||
livoxProto1_getOrCreateDeviceReq(nullptr),
|
||||
livoxProto1_destroyDeviceReq(nullptr)
|
||||
{}
|
||||
|
||||
static void DlCloser(void* handle)
|
||||
{
|
||||
if (handle) {
|
||||
dlclose(handle);
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<void, void(*)(void*)> dlopenHandle;
|
||||
livoxProto1_mainFn *livoxProto1_main;
|
||||
livoxProto1_exitFn *livoxProto1_exit;
|
||||
livoxProto1_getOrCreateDeviceReqFn *livoxProto1_getOrCreateDeviceReq;
|
||||
livoxProto1_destroyDeviceReqFn *livoxProto1_destroyDeviceReq;
|
||||
};
|
||||
|
||||
static LivoxProto1DllState livoxProto1;
|
||||
|
||||
// Attached Livox devices
|
||||
static std::vector<std::shared_ptr<livoxProto1::Device>> g_attachedDevices;
|
||||
|
||||
// Continuation classes for async operations
|
||||
class AttachDeviceReq
|
||||
: public smo::NonPostedAsynchronousContinuation<sal_mlo_attachDeviceReqCbFn>
|
||||
{
|
||||
public:
|
||||
AttachDeviceReq(
|
||||
const std::shared_ptr<smo::device::DeviceAttachmentSpec>& spec,
|
||||
smo::Callback<sal_mlo_attachDeviceReqCbFn> cb)
|
||||
: smo::NonPostedAsynchronousContinuation<sal_mlo_attachDeviceReqCbFn>(
|
||||
std::move(cb)),
|
||||
spec(spec)
|
||||
{}
|
||||
|
||||
public:
|
||||
const std::shared_ptr<smo::device::DeviceAttachmentSpec> spec;
|
||||
|
||||
public:
|
||||
void attachDeviceReq1(
|
||||
std::shared_ptr<AttachDeviceReq> context,
|
||||
bool success, std::shared_ptr<livoxProto1::Device> dev)
|
||||
{
|
||||
if (!dev)
|
||||
{
|
||||
std::cerr << __func__ << ": Failed to create Livox device: "
|
||||
<< context->spec->deviceSelector << std::endl;
|
||||
context->callOriginalCb(false, context->spec);
|
||||
return;
|
||||
}
|
||||
|
||||
g_attachedDevices.push_back(dev);
|
||||
if (1 || OptionParser::getOptions().verbose)
|
||||
{
|
||||
std::cout << __func__ << ": Successfully attached Livox "
|
||||
"device: " << context->spec->deviceSelector << " (ID: "
|
||||
<< context->spec->deviceIdentifier << ")\n";
|
||||
}
|
||||
|
||||
context->callOriginalCb(success, context->spec);
|
||||
}
|
||||
};
|
||||
|
||||
class DetachDeviceReq
|
||||
: public smo::NonPostedAsynchronousContinuation<sal_mlo_detachDeviceReqCbFn>
|
||||
{
|
||||
public:
|
||||
DetachDeviceReq(
|
||||
const std::shared_ptr<smo::device::DeviceAttachmentSpec>& spec,
|
||||
smo::Callback<sal_mlo_detachDeviceReqCbFn> cb)
|
||||
: smo::NonPostedAsynchronousContinuation<sal_mlo_detachDeviceReqCbFn>(
|
||||
std::move(cb)),
|
||||
spec(spec)
|
||||
{}
|
||||
|
||||
public:
|
||||
const std::shared_ptr<smo::device::DeviceAttachmentSpec> spec;
|
||||
|
||||
public:
|
||||
void detachDeviceReq1(
|
||||
std::shared_ptr<DetachDeviceReq> context,
|
||||
bool success)
|
||||
{
|
||||
if (!success)
|
||||
{
|
||||
std::cerr << __func__ << ": Failed to destroy Livox device: "
|
||||
<< context->spec->deviceIdentifier << "\n";
|
||||
context->callOriginalCb(false, context->spec);
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the device in g_attachedDevices and remove it.
|
||||
auto eraseIt = std::find_if(
|
||||
g_attachedDevices.begin(), g_attachedDevices.end(),
|
||||
[context](const std::shared_ptr<livoxProto1::Device>& dev)
|
||||
{
|
||||
const std::string& devId = dev->discoveredDevice.deviceIdentifier;
|
||||
std::string devIdPrefix = devId.substr(
|
||||
0, std::min<size_t>(14, devId.size()));
|
||||
return devIdPrefix == context->spec->deviceSelector.substr(
|
||||
0, std::min<size_t>(14, context->spec->deviceSelector.size()));
|
||||
}
|
||||
);
|
||||
|
||||
if (eraseIt == g_attachedDevices.end())
|
||||
{
|
||||
std::cerr << __func__ << ": Race condition: device not found "
|
||||
"in g_attachedDevices for detachment: "
|
||||
<< context->spec->deviceIdentifier << "\n";
|
||||
context->callOriginalCb(false, context->spec);
|
||||
return;
|
||||
}
|
||||
|
||||
g_attachedDevices.erase(eraseIt);
|
||||
std::cout << __func__ << ": Successfully detached Livox device: "
|
||||
<< context->spec->deviceIdentifier << "\n";
|
||||
|
||||
context->callOriginalCb(success, context->spec);
|
||||
}
|
||||
};
|
||||
|
||||
// Callback function declarations
|
||||
extern "C" sal_mlo_initializeIndFn livoxGen1_initializeInd;
|
||||
extern "C" sal_mlo_finalizeIndFn livoxGen1_finalizeInd;
|
||||
extern "C" sal_mlo_attachDeviceReqFn livoxGen1_attachDeviceReq;
|
||||
extern "C" sal_mlo_detachDeviceReqFn livoxGen1_detachDeviceReq;
|
||||
|
||||
// Sense API descriptor
|
||||
static const SenseApiDesc livoxGen1ApiDesc = {
|
||||
.name = "livoxGen1",
|
||||
.exportedImplexorApis = {
|
||||
{.name = "pointCloudCoords"},
|
||||
{.name = "pointCloudIntensity"},
|
||||
{.name = "gyro"},
|
||||
{.name = "accel"}
|
||||
},
|
||||
.sal_mgmt_libOps = {
|
||||
.initializeInd = livoxGen1_initializeInd,
|
||||
.finalizeInd = livoxGen1_finalizeInd,
|
||||
.attachDeviceReq = livoxGen1_attachDeviceReq,
|
||||
.detachDeviceReq = livoxGen1_detachDeviceReq
|
||||
}
|
||||
};
|
||||
|
||||
// Callback function implementations
|
||||
extern "C" int livoxGen1_initializeInd(void)
|
||||
{
|
||||
if (!smoHooksPtr)
|
||||
{
|
||||
throw std::runtime_error(std::string(__func__) + ": SMO hooks "
|
||||
"pointers not filled in.");
|
||||
}
|
||||
|
||||
// Load LivoxProto1 library
|
||||
auto libPath = smoHooksPtr->searchForLibInSmoSearchPaths(
|
||||
"liblivoxProto1.so");
|
||||
|
||||
livoxProto1.dlopenHandle.reset(dlopen(
|
||||
libPath.value_or("liblivoxProto1.so").c_str(), RTLD_LAZY));
|
||||
|
||||
if (!livoxProto1.dlopenHandle)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
std::string(__func__) +
|
||||
": Failed to load LivoxProto1 library: " +
|
||||
(dlerror() ? dlerror() : "unknown error"));
|
||||
}
|
||||
|
||||
// Get LivoxProto1 library functions
|
||||
livoxProto1.livoxProto1_main = reinterpret_cast<livoxProto1_mainFn *>(
|
||||
dlsym(livoxProto1.dlopenHandle.get(), "livoxProto1_main"));
|
||||
livoxProto1.livoxProto1_exit = reinterpret_cast<livoxProto1_exitFn *>(
|
||||
dlsym(livoxProto1.dlopenHandle.get(), "livoxProto1_exit"));
|
||||
livoxProto1.livoxProto1_getOrCreateDeviceReq = reinterpret_cast<
|
||||
livoxProto1_getOrCreateDeviceReqFn *>(
|
||||
dlsym(
|
||||
livoxProto1.dlopenHandle.get(),
|
||||
"livoxProto1_getOrCreateDeviceReq"));
|
||||
livoxProto1.livoxProto1_destroyDeviceReq = reinterpret_cast<
|
||||
livoxProto1_destroyDeviceReqFn *>(
|
||||
dlsym(
|
||||
livoxProto1.dlopenHandle.get(),
|
||||
"livoxProto1_destroyDeviceReq"));
|
||||
|
||||
if (!livoxProto1.livoxProto1_main || !livoxProto1.livoxProto1_exit
|
||||
|| !livoxProto1.livoxProto1_getOrCreateDeviceReq
|
||||
|| !livoxProto1.livoxProto1_destroyDeviceReq)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
std::string(__func__) +
|
||||
": Failed to get LivoxProto1 library functions");
|
||||
}
|
||||
|
||||
// Call LivoxProto1 library main function
|
||||
(*livoxProto1.livoxProto1_main)(
|
||||
smoThreadingModelDesc.componentThread, *smoHooksPtr);
|
||||
|
||||
return 0; // Success
|
||||
}
|
||||
|
||||
extern "C" int livoxGen1_finalizeInd(void)
|
||||
{
|
||||
// Clear all attached devices
|
||||
g_attachedDevices.clear();
|
||||
|
||||
// Call LivoxProto1 library exit function
|
||||
if (livoxProto1.livoxProto1_exit) {
|
||||
(*livoxProto1.livoxProto1_exit)();
|
||||
}
|
||||
|
||||
if (livoxProto1.dlopenHandle)
|
||||
{
|
||||
dlclose(livoxProto1.dlopenHandle.get());
|
||||
livoxProto1.dlopenHandle.reset();
|
||||
}
|
||||
|
||||
livoxProto1 = LivoxProto1DllState();
|
||||
return 0; // Success
|
||||
}
|
||||
|
||||
extern "C" void livoxGen1_attachDeviceReq(
|
||||
const std::shared_ptr<smo::device::DeviceAttachmentSpec>& desc,
|
||||
const std::shared_ptr<smo::ComponentThread>& componentThread,
|
||||
Callback<smo::sense_api::sal_mlo_attachDeviceReqCbFn> cb
|
||||
)
|
||||
{
|
||||
if (!livoxProto1.livoxProto1_getOrCreateDeviceReq)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
std::string(__func__) + ": LivoxProto1 getOrCreateDevice function "
|
||||
"not available");
|
||||
}
|
||||
|
||||
for (const auto& dev : g_attachedDevices)
|
||||
{
|
||||
if (dev->discoveredDevice.deviceIdentifier == desc->deviceIdentifier)
|
||||
{
|
||||
cb.callbackFn(true, desc);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse integer parameters from provider params with defaults
|
||||
/* The Livox Avia will generally respond to a handshake request within
|
||||
* 50ms. So we set the handshake timeout to 300ms to be safe.
|
||||
*/
|
||||
int handshakeTimeoutMs = 300; // Default: 50ms
|
||||
/* Based on testing on a Livox Avia, the device will generally resume
|
||||
* sending broadcast advertisement dgrams after about 5 seconds at most.
|
||||
* Generally, it will resume sending them within 1-2 seconds.
|
||||
*/
|
||||
int retryDelayMs = 5250; // Default: 500ms
|
||||
uint8_t smoSubnetNbits = 24; // Default: /24 subnet
|
||||
uint16_t dataPort = 56000; // Default data port
|
||||
uint16_t cmdPort = 56001; // Default command port
|
||||
uint16_t imuPort = 56002; // Default IMU port
|
||||
// Default: empty string (will trigger IP auto-detection)
|
||||
std::string smoIp = "";
|
||||
|
||||
// Parse optional integer parameters from provider params
|
||||
for (const auto& param : desc->providerParams)
|
||||
{
|
||||
if (param.first == "handshake-timeout-ms")
|
||||
{
|
||||
handshakeTimeoutMs = smo::device::DeviceAttachmentSpec
|
||||
::parseRequiredParamAsInt(*desc, "handshake-timeout-ms");
|
||||
} else if (param.first == "retry-delay-ms")
|
||||
{
|
||||
retryDelayMs = smo::device::DeviceAttachmentSpec
|
||||
::parseRequiredParamAsInt(*desc, "retry-delay-ms");
|
||||
} else if (param.first == "smo-subnet-nbits")
|
||||
{
|
||||
smoSubnetNbits = static_cast<uint8_t>(
|
||||
smo::device::DeviceAttachmentSpec
|
||||
::parseRequiredParamAsInt(*desc, "smo-subnet-nbits"));
|
||||
} else if (param.first == "data-port")
|
||||
{
|
||||
dataPort = static_cast<uint16_t>(
|
||||
smo::device::DeviceAttachmentSpec
|
||||
::parseRequiredParamAsInt(*desc, "data-port"));
|
||||
} else if (param.first == "cmd-port")
|
||||
{
|
||||
cmdPort = static_cast<uint16_t>(
|
||||
smo::device::DeviceAttachmentSpec
|
||||
::parseRequiredParamAsInt(*desc, "cmd-port"));
|
||||
} else if (param.first == "imu-port")
|
||||
{
|
||||
imuPort = static_cast<uint16_t>(
|
||||
smo::device::DeviceAttachmentSpec
|
||||
::parseRequiredParamAsInt(*desc, "imu-port"));
|
||||
} else if (param.first == "smo-ip")
|
||||
{
|
||||
if (param.second.empty())
|
||||
{
|
||||
throw std::runtime_error(
|
||||
std::string(__func__) + ": smo-ip parameter is empty");
|
||||
}
|
||||
if (param.second.find('.') == std::string::npos ||
|
||||
std::count(param.second.begin(), param.second.end(), '.') != 3)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
std::string(__func__) + ": smo-ip parameter is not an "
|
||||
"IPv4 address");
|
||||
}
|
||||
smoIp = param.second;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error(
|
||||
std::string(__func__) + ": Unknown provider parameter: "
|
||||
+ param.first);
|
||||
}
|
||||
}
|
||||
|
||||
auto request = std::make_shared<AttachDeviceReq>(desc, cb);
|
||||
|
||||
(*livoxProto1.livoxProto1_getOrCreateDeviceReq)(
|
||||
desc->deviceSelector, // deviceIdentifier (broadcast code)
|
||||
componentThread,
|
||||
handshakeTimeoutMs, retryDelayMs,
|
||||
smoIp, smoSubnetNbits,
|
||||
dataPort, cmdPort, imuPort,
|
||||
{request, std::bind(
|
||||
&AttachDeviceReq::attachDeviceReq1,
|
||||
request.get(), request,
|
||||
std::placeholders::_1, std::placeholders::_2)});
|
||||
}
|
||||
|
||||
extern "C" void livoxGen1_detachDeviceReq(
|
||||
const std::shared_ptr<smo::device::DeviceAttachmentSpec>& desc,
|
||||
Callback<smo::sense_api::sal_mlo_detachDeviceReqCbFn> cb
|
||||
)
|
||||
{
|
||||
// Find and remove the device from our collection
|
||||
auto it = std::find_if(g_attachedDevices.begin(), g_attachedDevices.end(),
|
||||
[&desc](const std::shared_ptr<livoxProto1::Device>& dev) {
|
||||
/** EXPLANATION:
|
||||
* Compare the first 14 characters of the deviceIdentifier with
|
||||
* the first 14 characters of the deviceSelector
|
||||
*/
|
||||
const std::string& devId = dev->discoveredDevice.deviceIdentifier;
|
||||
std::string devIdPrefix = devId.substr(
|
||||
0, std::min<size_t>(14, devId.size()));
|
||||
|
||||
return devIdPrefix == desc->deviceSelector.substr(
|
||||
0, std::min<size_t>(14, desc->deviceSelector.size()));
|
||||
}
|
||||
);
|
||||
|
||||
if (it == g_attachedDevices.end())
|
||||
{
|
||||
std::cerr << std::string(__func__)
|
||||
<< ": Device not found for detachment: "
|
||||
<< desc->deviceIdentifier << std::endl;
|
||||
cb.callbackFn(false, desc);
|
||||
return;
|
||||
}
|
||||
|
||||
auto request = std::make_shared<DetachDeviceReq>(desc, cb);
|
||||
|
||||
(*livoxProto1.livoxProto1_destroyDeviceReq)(
|
||||
*it,
|
||||
{request, std::bind(
|
||||
&DetachDeviceReq::detachDeviceReq1,
|
||||
request.get(), request,
|
||||
std::placeholders::_1)});
|
||||
}
|
||||
|
||||
// Exported function
|
||||
extern "C" smo::sense_api::SMO_GET_SENSE_API_DESC_FN_TYPEDEF
|
||||
SMO_GET_SENSE_API_DESC_FN_NAME;
|
||||
|
||||
const smo::sense_api::SenseApiDesc& SMO_GET_SENSE_API_DESC_FN_NAME(
|
||||
const smo::sense_api::SmoCallbacks& callbacks,
|
||||
const smo::sense_api::SmoThreadingModelDesc& threadingModel)
|
||||
{
|
||||
smoHooksPtr = &callbacks;
|
||||
smoThreadingModelDesc = threadingModel;
|
||||
|
||||
return livoxGen1ApiDesc;
|
||||
}
|
||||
|
||||
} // namespace sense_api
|
||||
} // namespace smo
|
||||
+11
-3
@@ -8,18 +8,21 @@ add_library(smocore STATIC
|
||||
opts.cpp
|
||||
componentThread.cpp
|
||||
component.cpp
|
||||
painfulQuale.cpp
|
||||
qutex.cpp
|
||||
lockerAndInvokerBase.cpp
|
||||
|
||||
# Body
|
||||
body/body.cpp
|
||||
|
||||
# Director
|
||||
director/director.cpp
|
||||
|
||||
# Marionette
|
||||
marionette/main.cpp
|
||||
marionette/salmanoff.cpp
|
||||
marionette/lifetime.cpp
|
||||
marionette/qualeEvent.cpp
|
||||
marionette/negtrinEvent.cpp
|
||||
|
||||
# DeviceManager
|
||||
deviceManager/deviceManager.cpp
|
||||
@@ -29,7 +32,7 @@ add_library(smocore STATIC
|
||||
${YACC_OUTPUT}
|
||||
|
||||
# SenseApis
|
||||
senseApis/senseApiManager.cpp
|
||||
stimBuffApis/stimBuffApiManager.cpp
|
||||
|
||||
# MindManager
|
||||
mindManager/mindManager.cpp
|
||||
@@ -43,8 +46,13 @@ endif()
|
||||
target_include_directories(smocore PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
${Boost_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
# Link against pthread for CPU affinity functions
|
||||
find_package(Threads REQUIRED)
|
||||
target_link_libraries(smocore PRIVATE Threads::Threads)
|
||||
target_link_libraries(smocore PRIVATE
|
||||
Threads::Threads
|
||||
Boost::system
|
||||
Boost::log
|
||||
)
|
||||
|
||||
+17
-15
@@ -3,10 +3,11 @@
|
||||
#include <asynchronousContinuation.h>
|
||||
#include <asynchronousLoop.h>
|
||||
#include <callback.h>
|
||||
#include <callableTracer.h>
|
||||
#include <body/body.h>
|
||||
#include <componentThread.h>
|
||||
#include <mind.h>
|
||||
#include <senseApis/senseApiManager.h>
|
||||
#include <stimBuffApis/stimBuffApiManager.h>
|
||||
#include <deviceManager/deviceManager.h>
|
||||
|
||||
namespace smo {
|
||||
@@ -50,11 +51,11 @@ public:
|
||||
* For example, liblivoxProto1's BroadcastListener will use this thread
|
||||
* to listen for UDP broadcast dgrams from Livox devices.
|
||||
*
|
||||
* Right now we use Marionette, but there's a strong argument for using
|
||||
* We used to use Marionette, but there's a strong argument for using
|
||||
* Body instead since it's meant to handle device-management operations.
|
||||
*/
|
||||
sense_api::SenseApiManager::getInstance()
|
||||
.loadAllSenseApiLibsFromOptions(caller);
|
||||
stim_buff::StimBuffApiManager::getInstance()
|
||||
.loadAllStimBuffApiLibsFromOptions(parent.body.thread);
|
||||
|
||||
/** EXPLANATION:
|
||||
* Consider body::initializeReq to have been called if even one of its
|
||||
@@ -63,15 +64,16 @@ public:
|
||||
*/
|
||||
context->parent.bodyComponentInitialized = true;
|
||||
|
||||
std::cout << sense_api::SenseApiManager::getInstance().stringifyLibs()
|
||||
std::cout << stim_buff::StimBuffApiManager::getInstance().stringifyLibs()
|
||||
<< std::endl;
|
||||
|
||||
if (OptionParser::getOptions().verbose)
|
||||
{
|
||||
std::cout << __func__ << ": About to initializeAllSenseApiLibs"
|
||||
std::cout << __func__ << ": About to initializeAllStimBuffApiLibs"
|
||||
<< '\n';
|
||||
}
|
||||
sense_api::SenseApiManager::getInstance().initializeAllSenseApiLibs();
|
||||
stim_buff::StimBuffApiManager::getInstance()
|
||||
.initializeAllStimBuffApiLibs();
|
||||
|
||||
if (OptionParser::getOptions().verbose)
|
||||
{
|
||||
@@ -134,11 +136,11 @@ public:
|
||||
<< results.nSucceeded << " of " << results.nTotal
|
||||
<< " sense devices." << "\n";
|
||||
|
||||
std::cout << "Mrntt: About to finalize all sense api libs." << "\n";
|
||||
sense_api::SenseApiManager::getInstance().finalizeAllSenseApiLibs();
|
||||
std::cout << "Mrntt: About to finalize all stim buff api libs." << "\n";
|
||||
stim_buff::StimBuffApiManager::getInstance().finalizeAllStimBuffApiLibs();
|
||||
|
||||
std::cout << "Mrntt: About to unload all sense api libs." << "\n";
|
||||
sense_api::SenseApiManager::getInstance().unloadAllSenseApiLibs();
|
||||
std::cout << "Mrntt: About to unload all stim buff api libs." << "\n";
|
||||
stim_buff::StimBuffApiManager::getInstance().unloadAllStimBuffApiLibs();
|
||||
callOriginalCb(results.nSucceeded == results.nTotal);
|
||||
}
|
||||
};
|
||||
@@ -157,9 +159,9 @@ void Body::initializeReq(Callback<bodyLifetimeMgmtOpCbFn> callback)
|
||||
parent, mrntt, callback);
|
||||
|
||||
thread->getIoService().post(
|
||||
std::bind(
|
||||
STC(std::bind(
|
||||
&InitializeReq::initializeReq1_posted,
|
||||
request.get(), request));
|
||||
request.get(), request)));
|
||||
}
|
||||
|
||||
void Body::finalizeReq(Callback<bodyLifetimeMgmtOpCbFn> callback)
|
||||
@@ -186,9 +188,9 @@ void Body::finalizeReq(Callback<bodyLifetimeMgmtOpCbFn> callback)
|
||||
parent, mrntt, callback);
|
||||
|
||||
thread->getIoService().post(
|
||||
std::bind(
|
||||
STC(std::bind(
|
||||
&FinalizeReq::finalizeReq1_posted,
|
||||
request.get(), request));
|
||||
request.get(), request)));
|
||||
}
|
||||
|
||||
} // namespace body
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <component.h>
|
||||
#include <marionette/marionette.h>
|
||||
|
||||
namespace smo {
|
||||
|
||||
|
||||
+23
-13
@@ -1,11 +1,14 @@
|
||||
#include <boostAsioLinkageFix.h>
|
||||
#include <unistd.h>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <pthread.h>
|
||||
#include <sched.h>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/asio/io_service.hpp>
|
||||
#include <opts.h>
|
||||
#include <asynchronousContinuation.h>
|
||||
#include <callback.h>
|
||||
#include <callableTracer.h>
|
||||
#include <mind.h>
|
||||
#include <mindManager/mindManager.h>
|
||||
#include <componentThread.h>
|
||||
@@ -31,6 +34,11 @@ void MindThread::initializeTls(void)
|
||||
thisComponentThread = shared_from_this();
|
||||
}
|
||||
|
||||
bool ComponentThread::tlsInitialized(void)
|
||||
{
|
||||
return thisComponentThread != nullptr;
|
||||
}
|
||||
|
||||
const std::shared_ptr<ComponentThread> ComponentThread::getSelf(void)
|
||||
{
|
||||
if (!thisComponentThread)
|
||||
@@ -44,6 +52,8 @@ const std::shared_ptr<ComponentThread> ComponentThread::getSelf(void)
|
||||
|
||||
void MindThread::main(MindThread& self)
|
||||
{
|
||||
std::string threadName = "smo:" + self.name;
|
||||
pthread_setname_np(pthread_self(), threadName.c_str());
|
||||
|
||||
if (OptionParser::getOptions().verbose)
|
||||
{
|
||||
@@ -236,9 +246,9 @@ void MindThread::joltThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback)
|
||||
mrntt, target, callback);
|
||||
|
||||
this->getIoService().post(
|
||||
std::bind(
|
||||
STC(std::bind(
|
||||
&ThreadLifetimeMgmtOp::joltThreadReq1_posted,
|
||||
request.get(), request));
|
||||
request.get(), request)));
|
||||
}
|
||||
|
||||
// Thread management method implementations
|
||||
@@ -249,9 +259,9 @@ void MindThread::startThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback)
|
||||
caller, shared_from_this(), callback);
|
||||
|
||||
this->getIoService().post(
|
||||
std::bind(
|
||||
STC(std::bind(
|
||||
&ThreadLifetimeMgmtOp::startThreadReq1_posted,
|
||||
request.get(), request));
|
||||
request.get(), request)));
|
||||
}
|
||||
|
||||
void MindThread::exitThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback)
|
||||
@@ -261,14 +271,14 @@ void MindThread::exitThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback)
|
||||
caller, shared_from_this(), callback);
|
||||
|
||||
this->getIoService().post(
|
||||
std::bind(
|
||||
STC(std::bind(
|
||||
&ThreadLifetimeMgmtOp::exitThreadReq1_mainQueue_posted,
|
||||
request.get(), request));
|
||||
request.get(), request)));
|
||||
|
||||
pause_io_service.post(
|
||||
std::bind(
|
||||
STC(std::bind(
|
||||
&ThreadLifetimeMgmtOp::exitThreadReq1_pauseQueue_posted,
|
||||
request.get(), request));
|
||||
request.get(), request)));
|
||||
}
|
||||
|
||||
void MindThread::pauseThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback)
|
||||
@@ -284,9 +294,9 @@ void MindThread::pauseThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback)
|
||||
caller, shared_from_this(), callback);
|
||||
|
||||
this->getIoService().post(
|
||||
std::bind(
|
||||
STC(std::bind(
|
||||
&ThreadLifetimeMgmtOp::pauseThreadReq1_posted,
|
||||
request.get(), request));
|
||||
request.get(), request)));
|
||||
}
|
||||
|
||||
void MindThread::resumeThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback)
|
||||
@@ -303,9 +313,9 @@ void MindThread::resumeThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback)
|
||||
caller, shared_from_this(), callback);
|
||||
|
||||
pause_io_service.post(
|
||||
std::bind(
|
||||
STC(std::bind(
|
||||
&ThreadLifetimeMgmtOp::resumeThreadReq1_posted,
|
||||
request.get(), request));
|
||||
request.get(), request)));
|
||||
}
|
||||
|
||||
// CPU management method implementations
|
||||
|
||||
@@ -106,18 +106,20 @@ extrospector_spec:
|
||||
;
|
||||
|
||||
spec_body:
|
||||
STRING PIPE STRING PIPE STRING LPAREN opt_params RPAREN PIPE STRING LPAREN opt_params RPAREN PIPE STRING {
|
||||
STRING PIPE STRING LPAREN opt_params RPAREN PIPE STRING LPAREN opt_params RPAREN PIPE STRING LPAREN opt_params RPAREN PIPE STRING {
|
||||
$$ = new smo::device::DeviceAttachmentSpec();
|
||||
$$->deviceIdentifier = std::string($1);
|
||||
$$->sensorType = '\0'; // This will be set by the parent rule
|
||||
$$->implexor = std::string($3);
|
||||
$$->api = std::string($5);
|
||||
$$->apiParams = std::move(*$7);
|
||||
$$->provider = std::string($10);
|
||||
$$->providerParams = std::move(*$12);
|
||||
$$->deviceSelector = std::string($15);
|
||||
delete $7;
|
||||
delete $12;
|
||||
$$->qualeIfaceApi = std::string($3);
|
||||
$$->qualeIfaceApiParams = std::move(*$5);
|
||||
$$->stimBuffApi = std::string($8);
|
||||
$$->stimBuffApiParams = std::move(*$10);
|
||||
$$->provider = std::string($13);
|
||||
$$->providerParams = std::move(*$15);
|
||||
$$->deviceSelector = std::string($18);
|
||||
delete $5;
|
||||
delete $10;
|
||||
delete $15;
|
||||
}
|
||||
;
|
||||
|
||||
|
||||
@@ -9,10 +9,11 @@
|
||||
#include <asynchronousContinuation.h>
|
||||
#include <serializedAsynchronousContinuation.h>
|
||||
#include <callback.h>
|
||||
#include <callableTracer.h>
|
||||
#include <componentThread.h>
|
||||
#include <deviceManager/deviceManager.h>
|
||||
#include <deviceManager/deviceReattacher.h>
|
||||
#include <senseApis/senseApiManager.h>
|
||||
#include <stimBuffApis/stimBuffApiManager.h>
|
||||
#include <marionette/marionette.h>
|
||||
#include <mind.h>
|
||||
|
||||
@@ -132,7 +133,7 @@ public:
|
||||
return;
|
||||
}
|
||||
|
||||
DeviceManager::getInstance().attachSenseDeviceReq(
|
||||
DeviceManager::getInstance().attachStimBuffDeviceReq(
|
||||
specPtr,
|
||||
{context, std::bind(
|
||||
&NewDeviceAttachmentSpecInd::newDeviceAttachmentSpecInd2,
|
||||
@@ -209,8 +210,8 @@ public:
|
||||
return;
|
||||
}
|
||||
|
||||
// Call detachSenseDeviceReq first - only clean up metadata if this succeeds
|
||||
DeviceManager::getInstance().detachSenseDeviceReq(
|
||||
// Call detachStimBuffDeviceReq first - only clean up metadata if this succeeds
|
||||
DeviceManager::getInstance().detachStimBuffDeviceReq(
|
||||
specPtr,
|
||||
{context, std::bind(
|
||||
&RemoveDeviceAttachmentSpecReq::removeDeviceAttachmentSpecReq2,
|
||||
@@ -329,24 +330,25 @@ void DeviceManager::removeDeviceAttachmentSpecReq(
|
||||
request.get(), request));
|
||||
}
|
||||
|
||||
class DeviceManager::AttachSenseDeviceReq
|
||||
: public SerializedAsynchronousContinuation<attachSenseDeviceReqCbFn>
|
||||
class DeviceManager::AttachStimBuffDeviceReq
|
||||
: public SerializedAsynchronousContinuation<
|
||||
DeviceManager::attachStimBuffDeviceReqCbFn>
|
||||
{
|
||||
public:
|
||||
AttachSenseDeviceReq(
|
||||
AttachStimBuffDeviceReq(
|
||||
const std::shared_ptr<DeviceAttachmentSpec>& spec,
|
||||
const std::shared_ptr<ComponentThread> &caller,
|
||||
Callback<attachSenseDeviceReqCbFn> cb,
|
||||
std::shared_ptr<sense_api::SenseApiLib> &senseApiLib,
|
||||
Callback<DeviceManager::attachStimBuffDeviceReqCbFn> cb,
|
||||
std::shared_ptr<stim_buff::StimBuffApiLib> &stimBuffApiLib,
|
||||
std::vector<std::reference_wrapper<Qutex>> requiredLocks)
|
||||
: SerializedAsynchronousContinuation<attachSenseDeviceReqCbFn>(
|
||||
: SerializedAsynchronousContinuation<attachStimBuffDeviceReqCbFn>(
|
||||
caller, cb, requiredLocks),
|
||||
spec(spec), senseApiLib(senseApiLib)
|
||||
spec(spec), stimBuffApiLib(stimBuffApiLib)
|
||||
{}
|
||||
|
||||
public:
|
||||
void attachSenseDeviceReq1_posted(
|
||||
[[maybe_unused]] std::shared_ptr<AttachSenseDeviceReq> context
|
||||
void attachStimBuffDeviceReq1_posted(
|
||||
[[maybe_unused]] std::shared_ptr<AttachStimBuffDeviceReq> context
|
||||
)
|
||||
{
|
||||
if (caller->id != ComponentThread::MRNTT)
|
||||
@@ -358,23 +360,24 @@ public:
|
||||
return;
|
||||
}
|
||||
|
||||
if (senseApiLib->isBeingDestroyed.load())
|
||||
if (stimBuffApiLib->isBeingDestroyed.load())
|
||||
{
|
||||
std::cerr << std::string(__func__) + ": Library is being destroyed"
|
||||
<< " for API '" << spec->api << "'. Bailing out." << std::endl;
|
||||
<< " for API '" << spec->stimBuffApi << "'. Bailing out.\n";
|
||||
callOriginalCb(false, spec);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!senseApiLib->senseApiDesc.sal_mgmt_libOps.attachDeviceReq)
|
||||
if (!stimBuffApiLib->stimBuffApiDesc.sal_mgmt_libOps.attachDeviceReq)
|
||||
{
|
||||
std::cerr << std::string(__func__) + ": attachDeviceReq() is NULL "
|
||||
"for library '" << senseApiLib->libraryPath << "'" << std::endl;
|
||||
"for library '" << stimBuffApiLib->libraryPath << "'"
|
||||
<< std::endl;
|
||||
callOriginalCb(false, spec);
|
||||
return;
|
||||
}
|
||||
|
||||
releaseQutexEarly(sense_api::SenseApiManager::getInstance().qutex);
|
||||
releaseQutexEarly(stim_buff::StimBuffApiManager::getInstance().qutex);
|
||||
|
||||
/** EXPLANATION:
|
||||
* We pass in either the body or world thread here, depending on whether
|
||||
@@ -397,16 +400,16 @@ public:
|
||||
<< spec->deviceIdentifier << " to body thread" << "\n";
|
||||
}
|
||||
|
||||
senseApiLib->senseApiDesc.sal_mgmt_libOps.attachDeviceReq(
|
||||
stimBuffApiLib->stimBuffApiDesc.sal_mgmt_libOps.attachDeviceReq(
|
||||
spec, threadForAttachment,
|
||||
{context, std::bind(
|
||||
&AttachSenseDeviceReq::attachSenseDeviceReq2,
|
||||
&AttachStimBuffDeviceReq::attachStimBuffDeviceReq2,
|
||||
context.get(), context,
|
||||
std::placeholders::_1, std::placeholders::_2)});
|
||||
}
|
||||
|
||||
void attachSenseDeviceReq2(
|
||||
[[maybe_unused]] std::shared_ptr<AttachSenseDeviceReq> context,
|
||||
void attachStimBuffDeviceReq2(
|
||||
[[maybe_unused]] std::shared_ptr<AttachStimBuffDeviceReq> context,
|
||||
bool success,
|
||||
std::shared_ptr<DeviceAttachmentSpec> deviceSpec
|
||||
)
|
||||
@@ -414,8 +417,8 @@ public:
|
||||
callOriginalCb(success, deviceSpec);
|
||||
}
|
||||
|
||||
void detachSenseDeviceReq1_posted(
|
||||
[[maybe_unused]] std::shared_ptr<DetachSenseDeviceReq> context
|
||||
void detachStimBuffDeviceReq1_posted(
|
||||
[[maybe_unused]] std::shared_ptr<AttachStimBuffDeviceReq> context
|
||||
)
|
||||
{
|
||||
if (caller->id != ComponentThread::MRNTT)
|
||||
@@ -427,34 +430,35 @@ public:
|
||||
return;
|
||||
}
|
||||
|
||||
if (senseApiLib->isBeingDestroyed.load())
|
||||
if (stimBuffApiLib->isBeingDestroyed.load())
|
||||
{
|
||||
std::cerr << std::string(__func__) + ": Library is being destroyed"
|
||||
<< " for API '" << spec->api << "'. Bailing out." << std::endl;
|
||||
<< " for API '" << spec->stimBuffApi << "'. Bailing out.\n";
|
||||
callOriginalCb(false, spec);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!senseApiLib->senseApiDesc.sal_mgmt_libOps.detachDeviceReq)
|
||||
if (!stimBuffApiLib->stimBuffApiDesc.sal_mgmt_libOps.detachDeviceReq)
|
||||
{
|
||||
std::cerr << std::string(__func__) + ": detachDeviceReq() is NULL "
|
||||
"for library '" << senseApiLib->libraryPath << "'" << std::endl;
|
||||
"for library '" << stimBuffApiLib->libraryPath << "'"
|
||||
<< std::endl;
|
||||
callOriginalCb(false, spec);
|
||||
return;
|
||||
}
|
||||
|
||||
releaseQutexEarly(sense_api::SenseApiManager::getInstance().qutex);
|
||||
releaseQutexEarly(stim_buff::StimBuffApiManager::getInstance().qutex);
|
||||
|
||||
senseApiLib->senseApiDesc.sal_mgmt_libOps.detachDeviceReq(
|
||||
stimBuffApiLib->stimBuffApiDesc.sal_mgmt_libOps.detachDeviceReq(
|
||||
spec,
|
||||
{context, std::bind(
|
||||
&DetachSenseDeviceReq::detachSenseDeviceReq2,
|
||||
&AttachStimBuffDeviceReq::detachStimBuffDeviceReq2,
|
||||
context.get(), context,
|
||||
std::placeholders::_1, std::placeholders::_2)});
|
||||
}
|
||||
}
|
||||
|
||||
void detachSenseDeviceReq2(
|
||||
[[maybe_unused]] std::shared_ptr<DetachSenseDeviceReq> context,
|
||||
void detachStimBuffDeviceReq2(
|
||||
[[maybe_unused]] std::shared_ptr<AttachStimBuffDeviceReq> context,
|
||||
bool success,
|
||||
std::shared_ptr<DeviceAttachmentSpec> deviceSpec
|
||||
)
|
||||
@@ -464,76 +468,76 @@ public:
|
||||
|
||||
public:
|
||||
std::shared_ptr<DeviceAttachmentSpec> spec;
|
||||
std::shared_ptr<sense_api::SenseApiLib> senseApiLib;
|
||||
std::shared_ptr<stim_buff::StimBuffApiLib> stimBuffApiLib;
|
||||
};
|
||||
|
||||
void DeviceManager::attachSenseDeviceReq(
|
||||
void DeviceManager::attachStimBuffDeviceReq(
|
||||
const std::shared_ptr<DeviceAttachmentSpec>& spec,
|
||||
Callback<attachSenseDeviceReqCbFn> cb
|
||||
Callback<attachStimBuffDeviceReqCbFn> cb
|
||||
)
|
||||
{
|
||||
const auto& caller = ComponentThread::getSelf();
|
||||
|
||||
// Get the sense API lib's qutex
|
||||
auto libOpt = sense_api::SenseApiManager::getInstance()
|
||||
.getSenseApiLibByApiName(spec->api);
|
||||
// Get the stim buff API lib's qutex
|
||||
auto libOpt = stim_buff::StimBuffApiManager::getInstance()
|
||||
.getStimBuffApiLibByApiName(spec->stimBuffApi);
|
||||
|
||||
if (!libOpt)
|
||||
{
|
||||
std::cerr << "attachSenseDeviceReq: No library found for API '"
|
||||
<< spec->api << "'" << std::endl;
|
||||
std::cerr << "attachStimBuffDeviceReq: No library found for API '"
|
||||
<< spec->stimBuffApi << "'" << std::endl;
|
||||
cb.callbackFn(false, spec);
|
||||
return;
|
||||
}
|
||||
|
||||
auto& lib = *libOpt.value();
|
||||
|
||||
auto request = std::make_shared<AttachSenseDeviceReq>(
|
||||
auto request = std::make_shared<AttachStimBuffDeviceReq>(
|
||||
spec, caller, cb, libOpt.value(),
|
||||
LockSet<attachSenseDeviceReqCbFn>::Set{
|
||||
std::ref(sense_api::SenseApiManager::getInstance().qutex),
|
||||
LockSet<attachStimBuffDeviceReqCbFn>::Set{
|
||||
std::ref(stim_buff::StimBuffApiManager::getInstance().qutex),
|
||||
std::ref(lib.qutex)
|
||||
});
|
||||
|
||||
AttachSenseDeviceReq::LockerAndInvoker lockvoker(
|
||||
AttachStimBuffDeviceReq::LockerAndInvoker lockvoker(
|
||||
*request, mrntt::mrntt.thread,
|
||||
std::bind(
|
||||
&AttachSenseDeviceReq::attachSenseDeviceReq1_posted,
|
||||
&AttachStimBuffDeviceReq::attachStimBuffDeviceReq1_posted,
|
||||
request.get(), request));
|
||||
}
|
||||
|
||||
void DeviceManager::detachSenseDeviceReq(
|
||||
void DeviceManager::detachStimBuffDeviceReq(
|
||||
const std::shared_ptr<DeviceAttachmentSpec>& spec,
|
||||
Callback<detachSenseDeviceReqCbFn> cb
|
||||
Callback<detachStimBuffDeviceReqCbFn> cb
|
||||
)
|
||||
{
|
||||
const auto& caller = ComponentThread::getSelf();
|
||||
|
||||
// Get the sense API lib's qutex
|
||||
auto libOpt = sense_api::SenseApiManager::getInstance()
|
||||
.getSenseApiLibByApiName(spec->api);
|
||||
// Get the stim buff API lib's qutex
|
||||
auto libOpt = stim_buff::StimBuffApiManager::getInstance()
|
||||
.getStimBuffApiLibByApiName(spec->stimBuffApi);
|
||||
|
||||
if (!libOpt)
|
||||
{
|
||||
std::cerr << "detachSenseDeviceReq: No library found for API '"
|
||||
<< spec->api << "'" << std::endl;
|
||||
std::cerr << "detachStimBuffDeviceReq: No library found for API '"
|
||||
<< spec->stimBuffApi << "'" << std::endl;
|
||||
cb.callbackFn(false, spec);
|
||||
return;
|
||||
}
|
||||
|
||||
auto& lib = *libOpt.value();
|
||||
|
||||
auto request = std::make_shared<DetachSenseDeviceReq>(
|
||||
auto request = std::make_shared<DetachStimBuffDeviceReq>(
|
||||
spec, caller, cb, libOpt.value(),
|
||||
LockSet<detachSenseDeviceReqCbFn>::Set{
|
||||
std::ref(sense_api::SenseApiManager::getInstance().qutex),
|
||||
LockSet<detachStimBuffDeviceReqCbFn>::Set{
|
||||
std::ref(stim_buff::StimBuffApiManager::getInstance().qutex),
|
||||
std::ref(lib.qutex)
|
||||
});
|
||||
|
||||
DetachSenseDeviceReq::LockerAndInvoker lockvoker(
|
||||
DetachStimBuffDeviceReq::LockerAndInvoker lockvoker(
|
||||
*request, mrntt::mrntt.thread,
|
||||
std::bind(
|
||||
&DetachSenseDeviceReq::detachSenseDeviceReq1_posted,
|
||||
&DetachStimBuffDeviceReq::detachStimBuffDeviceReq1_posted,
|
||||
request.get(), request));
|
||||
}
|
||||
|
||||
@@ -623,10 +627,10 @@ void DeviceManager::attachAllUnattachedDevicesFromReq(
|
||||
specs->size(), specs, caller, std::move(cb));
|
||||
|
||||
mrntt::mrntt.thread->getIoService().post(
|
||||
std::bind(
|
||||
STC(std::bind(
|
||||
&AttachAllUnattachedDevicesFromReq
|
||||
::attachAllUnattachedDevicesFromReq1_posted,
|
||||
request.get(), request));
|
||||
request.get(), request)));
|
||||
}
|
||||
|
||||
void DeviceManager::attachAllUnattachedDevicesFromCmdlineReq(
|
||||
@@ -711,7 +715,7 @@ void DeviceManager::attachAllUnattachedDevicesFromKnownListReq(
|
||||
)
|
||||
{
|
||||
const auto& caller = ComponentThread::getSelf();
|
||||
|
||||
|
||||
auto request = std::make_shared<AttachAllUnattachedDevicesFromKnownListReq>(
|
||||
caller, cb,
|
||||
LockSet<attachAllUnattachedDevicesFromReqCbFn>::Set{
|
||||
@@ -746,7 +750,7 @@ public:
|
||||
{
|
||||
for (const auto& deviceRole : DeviceManager::attachedDeviceRoles)
|
||||
{
|
||||
DeviceManager::getInstance().detachSenseDeviceReq(
|
||||
DeviceManager::getInstance().detachStimBuffDeviceReq(
|
||||
deviceRole->deviceAttachmentSpec,
|
||||
{context, std::bind(
|
||||
&DetachAllAttachedDeviceRoles::detachAllAttachedDeviceRoles2,
|
||||
@@ -804,9 +808,9 @@ void DeviceManager::detachAllAttachedDeviceRoles(
|
||||
caller, std::move(cb));
|
||||
|
||||
mrntt::mrntt.thread->getIoService().post(
|
||||
std::bind(
|
||||
STC(std::bind(
|
||||
&DetachAllAttachedDeviceRoles::detachAllAttachedDeviceRoles1_posted,
|
||||
request.get(), request));
|
||||
request.get(), request)));
|
||||
}
|
||||
|
||||
void DeviceManager::initializeDeviceReattacher()
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <functional>
|
||||
#include <componentThread.h>
|
||||
#include <callback.h>
|
||||
#include <asynchronousBridge.h>
|
||||
#include <deviceManager/deviceReattacher.h>
|
||||
#include <deviceManager/deviceManager.h>
|
||||
|
||||
@@ -35,6 +36,24 @@ void DeviceReattacher::stop()
|
||||
{
|
||||
shouldContinue.store(false);
|
||||
timer.cancel();
|
||||
|
||||
// Set up a timeout bridge using the provided ioThread's io_service
|
||||
auto& ioService = ioThread->getIoService();
|
||||
boost::asio::deadline_timer timeoutTimer(ioService);
|
||||
AsynchronousBridge bridge(ioService);
|
||||
|
||||
// Set up the timeout for ~10ms
|
||||
timeoutTimer.expires_from_now(boost::posix_time::milliseconds(20));
|
||||
timeoutTimer.async_wait(
|
||||
[&bridge](const boost::system::error_code& error)
|
||||
{
|
||||
(void)error;
|
||||
|
||||
// Always signal complete, whether timeout expired or was cancelled
|
||||
bridge.setAsyncOperationComplete();
|
||||
});
|
||||
|
||||
bridge.waitForAsyncOperationCompleteOrIoServiceStopped();
|
||||
}
|
||||
|
||||
void DeviceReattacher::scheduleNextTimeout()
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
#include <iostream>
|
||||
#include <director/director.h>
|
||||
#include <goal.h>
|
||||
|
||||
namespace smo {
|
||||
namespace director {
|
||||
|
||||
void Director::negtrinEventInd(void)
|
||||
{
|
||||
/** EXPLANATION:
|
||||
* The essence of a negtrin event, to Director is that it generates a new Goal
|
||||
* object and enqueues it onto the Director's negtrins queue. It's this auto-goal
|
||||
* generation that gives negtrins their intrinsic undesirability.
|
||||
*
|
||||
* We don't sample the negtrin, evaluate it and then conclude that it's
|
||||
* undesirable. We don't even produce a negative value judgment. We skip
|
||||
* right past both the evaluation and the value judgment production and
|
||||
* just generate the goal immediately.
|
||||
*
|
||||
* I'm unsure whether this is correct: it may well be that we ought to
|
||||
* simply produce a negative value judgment and then let the Director
|
||||
* create a goal to alleviate the negtrin.
|
||||
*
|
||||
* At any rate, for now, this is our implementation.
|
||||
*/
|
||||
std::cout << __func__ << ": Handling negtrin event." << std::endl;
|
||||
}
|
||||
|
||||
void Director::intrinEventInd(void)
|
||||
{
|
||||
std::cout << __func__ << ": Handling intrin event." << std::endl;
|
||||
}
|
||||
|
||||
void Director::postrinEventInd(void)
|
||||
{
|
||||
/** EXPLANATION:
|
||||
* When a postrin event occurs, a goal is auto-generated, but this goal is
|
||||
* a bit different from the goals that are auto-generated for negtrins.
|
||||
*
|
||||
* A negtrin's goal is to either: get to 0; reduce the negtrin below its
|
||||
* intolerable threshold; or reduce it somewhat even if not below the
|
||||
* tolerable threshold. This is very easy to represent as a cologex.
|
||||
*
|
||||
* A postrin's goal is to: persist the postrin indefinitely; and increase
|
||||
* its intensity if possible; and to store it away as something worth
|
||||
* re-triggering if some external distractor/frustrator interrupts the
|
||||
* persistent sampling of the postrin.
|
||||
*
|
||||
* I can think of how to encode the negtrin goal as a cologex, but I can't
|
||||
* think of how to encode the postrin goal as a cologex.
|
||||
*
|
||||
* With respect to the "store away for future re-triggering" aspect of the
|
||||
* postrin goal we can encode this by merely refusing to remove any postrin
|
||||
* goal from the goal prioQ. I suppose negtrins differ in that we do remove
|
||||
* them from the goal prioQ when they're resolved.
|
||||
*/
|
||||
std::cout << __func__ << ": Handling postrin event." << std::endl;
|
||||
}
|
||||
|
||||
} // namespace director
|
||||
} // namespace smo
|
||||
@@ -0,0 +1,25 @@
|
||||
#ifndef BOOST_ASIO_LINKAGE_FIX_H
|
||||
#define BOOST_ASIO_LINKAGE_FIX_H
|
||||
|
||||
#include <boost/asio/detail/call_stack.hpp>
|
||||
#include <boost/asio/detail/thread_context.hpp>
|
||||
#include <boost/asio/detail/tss_ptr.hpp>
|
||||
|
||||
namespace boost {
|
||||
namespace asio {
|
||||
namespace detail {
|
||||
|
||||
/** EXPLANATION:
|
||||
* Extern declaration of the template instantiation
|
||||
* This ensures that the .o translation units don't have their
|
||||
* own copies of `call_stack<>::top_` defined in them.
|
||||
*/
|
||||
extern template
|
||||
tss_ptr<call_stack<thread_context, thread_info_base>::context>
|
||||
call_stack<thread_context, thread_info_base>::top_;
|
||||
|
||||
} // namespace detail
|
||||
} // namespace asio
|
||||
} // namespace boost
|
||||
|
||||
#endif // BOOST_ASIO_LINKAGE_FIX_H
|
||||
@@ -1,30 +1,12 @@
|
||||
#ifndef _CHRONOMENON_H
|
||||
#define _CHRONOMENON_H
|
||||
|
||||
#include <vector>
|
||||
#include <qualeBundle.h>
|
||||
#include <mentalEntity.h>
|
||||
namespace smo {
|
||||
|
||||
class Chronomenon
|
||||
: public MentalEntity
|
||||
{
|
||||
public:
|
||||
class Timestamp
|
||||
{
|
||||
uintptr_t value;
|
||||
};
|
||||
|
||||
class Duration
|
||||
{
|
||||
uintptr_t value;
|
||||
};
|
||||
|
||||
public:
|
||||
Chronomenon extract(Timestamp start, Duration len);
|
||||
|
||||
public:
|
||||
std::vector<QualeBundle> qualia;
|
||||
};
|
||||
|
||||
#endif
|
||||
} // namespace smo
|
||||
|
||||
#endif // _CHRONOMENON_H
|
||||
|
||||
@@ -34,26 +34,6 @@ public:
|
||||
Mind &parent;
|
||||
};
|
||||
|
||||
namespace mrntt {
|
||||
|
||||
class MarionetteComponent
|
||||
: public Component
|
||||
{
|
||||
public:
|
||||
MarionetteComponent(const std::shared_ptr<ComponentThread> &thread);
|
||||
~MarionetteComponent() = default;
|
||||
|
||||
public:
|
||||
typedef std::function<void(bool)> mrnttLifetimeMgmtOpCbFn;
|
||||
void initializeReq(Callback<mrnttLifetimeMgmtOpCbFn> callback);
|
||||
void finalizeReq(Callback<mrnttLifetimeMgmtOpCbFn> callback);
|
||||
|
||||
private:
|
||||
class MrnttLifetimeMgmtOp;
|
||||
};
|
||||
|
||||
} // namespace mrntt
|
||||
|
||||
} // namespace smo
|
||||
|
||||
#endif // COMPONENT_H
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
#ifndef COMPONENT_THREAD_H
|
||||
#define COMPONENT_THREAD_H
|
||||
|
||||
#include <boostAsioLinkageFix.h>
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/asio/io_service.hpp>
|
||||
#include <stdexcept>
|
||||
#include <queue>
|
||||
#include <functional>
|
||||
@@ -48,6 +49,7 @@ public:
|
||||
boost::asio::io_service& getIoService(void) { return io_service; }
|
||||
|
||||
static const std::shared_ptr<ComponentThread> getSelf(void);
|
||||
static bool tlsInitialized(void);
|
||||
static std::shared_ptr<MarionetteThread> getMrntt();
|
||||
|
||||
typedef void (mainFn)(ComponentThread &self);
|
||||
|
||||
@@ -1,51 +1,14 @@
|
||||
#ifndef _CONCEPT_H
|
||||
#define _CONCEPT_H
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <logic.h>
|
||||
#include <mentalEntity.h>
|
||||
|
||||
namespace smo {
|
||||
namespace concepts {
|
||||
namespace cologex {
|
||||
|
||||
class Comparator
|
||||
: public MentalEntity, public logic::Operand
|
||||
class Concept
|
||||
{
|
||||
public:
|
||||
/** EXPLANATION:
|
||||
* The reference for a Comparator is the fixed mentity or range of mentities
|
||||
* that this comparator is intended to validate a match against.
|
||||
*
|
||||
* There are several mentities against which a comparator can match. At the
|
||||
* time of writing, we're fairly sure that these will be at minimum,
|
||||
* qualia, chronomena and mentena.
|
||||
*/
|
||||
std::shared_ptr<MentalEntity> reference;
|
||||
};
|
||||
|
||||
class ComparatorExpression
|
||||
: public logic::UnaryExpression
|
||||
{
|
||||
public:
|
||||
ComparatorExpression(
|
||||
logic::Operator &op, std::shared_ptr<Comparator> &comparator
|
||||
)
|
||||
: logic::UnaryExpression(
|
||||
op, std::static_pointer_cast<logic::Operand>(comparator))
|
||||
{}
|
||||
};
|
||||
|
||||
class CombinatorialLogicExpression
|
||||
: public MentalEntity, public logic::Expression
|
||||
{
|
||||
public:
|
||||
|
||||
};
|
||||
|
||||
typedef CombinatorialLogicExpression Concept;
|
||||
|
||||
} // namespace concept
|
||||
} // namespace cologex
|
||||
} // namespace smo
|
||||
|
||||
#endif
|
||||
#endif // _CONCEPT_H
|
||||
|
||||
@@ -61,15 +61,15 @@ public:
|
||||
Callback<removeDeviceAttachmentSpecReqCbFn> callback);
|
||||
|
||||
// Device attachment/detachment methods moved from SenseApiManager
|
||||
typedef sense_api::sal_mlo_attachDeviceReqCbFn attachSenseDeviceReqCbFn;
|
||||
typedef sense_api::sal_mlo_detachDeviceReqCbFn detachSenseDeviceReqCbFn;
|
||||
typedef stim_buff::sal_mlo_attachDeviceReqCbFn attachStimBuffDeviceReqCbFn;
|
||||
typedef stim_buff::sal_mlo_detachDeviceReqCbFn detachStimBuffDeviceReqCbFn;
|
||||
|
||||
void attachSenseDeviceReq(
|
||||
void attachStimBuffDeviceReq(
|
||||
const std::shared_ptr<DeviceAttachmentSpec>& spec,
|
||||
Callback<attachSenseDeviceReqCbFn> cb);
|
||||
void detachSenseDeviceReq(
|
||||
Callback<attachStimBuffDeviceReqCbFn> cb);
|
||||
void detachStimBuffDeviceReq(
|
||||
const std::shared_ptr<DeviceAttachmentSpec>& spec,
|
||||
Callback<detachSenseDeviceReqCbFn> cb);
|
||||
Callback<detachStimBuffDeviceReqCbFn> cb);
|
||||
|
||||
typedef std::function<void(AsynchronousLoop &results)>
|
||||
attachAllUnattachedDevicesFromReqCbFn;
|
||||
@@ -108,8 +108,8 @@ private:
|
||||
|
||||
class NewDeviceAttachmentSpecInd;
|
||||
class RemoveDeviceAttachmentSpecReq;
|
||||
class AttachSenseDeviceReq;
|
||||
typedef AttachSenseDeviceReq DetachSenseDeviceReq;
|
||||
class AttachStimBuffDeviceReq;
|
||||
typedef AttachStimBuffDeviceReq DetachStimBuffDeviceReq;
|
||||
class AttachAllUnattachedDevicesFromReq;
|
||||
class AttachAllUnattachedDevicesFromKnownListReq;
|
||||
class DetachAllAttachedDeviceRoles;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
#ifndef DEVICEREATTACHER_H
|
||||
#define DEVICEREATTACHER_H
|
||||
|
||||
#include <boostAsioLinkageFix.h>
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/asio/deadline_timer.hpp>
|
||||
|
||||
namespace smo {
|
||||
|
||||
|
||||
@@ -23,6 +23,10 @@ public:
|
||||
|
||||
~Director() = default;
|
||||
|
||||
void negtrinEventInd(void);
|
||||
void intrinEventInd(void);
|
||||
void postrinEventInd(void);
|
||||
|
||||
/** EXPLANATION:
|
||||
* We allow SMO to prioritize negtrins over injected goals, so that it can
|
||||
* prioritize pain mitigation. We may decide to change this in the future.
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
#define _GOAL_H
|
||||
|
||||
#include <concept.h>
|
||||
#include <mentalEntity.h>
|
||||
|
||||
namespace smo {
|
||||
|
||||
class Goal
|
||||
: public concepts::Concept {
|
||||
: public cologex::Concept, public MentalEntity
|
||||
{
|
||||
public:
|
||||
Goal() = default;
|
||||
~Goal() = default;
|
||||
@@ -14,4 +16,4 @@ public:
|
||||
|
||||
} // namespace smo
|
||||
|
||||
#endif
|
||||
#endif // _GOAL_H
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
#ifndef _LOGIC_H
|
||||
#define _LOGIC_H
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace smo {
|
||||
namespace logic {
|
||||
|
||||
class ExpressionPart
|
||||
{
|
||||
};
|
||||
|
||||
class Operator
|
||||
: public ExpressionPart
|
||||
{
|
||||
};
|
||||
|
||||
class OperatorAnd
|
||||
: public Operator
|
||||
{
|
||||
};
|
||||
|
||||
class OperatorOr
|
||||
: public Operator
|
||||
{
|
||||
};
|
||||
|
||||
class OperatorNot
|
||||
: public Operator
|
||||
{
|
||||
};
|
||||
|
||||
class Operand
|
||||
: public ExpressionPart
|
||||
{
|
||||
};
|
||||
|
||||
class UnaryExpression
|
||||
: public ExpressionPart
|
||||
{
|
||||
public:
|
||||
UnaryExpression(Operator &op, const std::shared_ptr<Operand> &operand)
|
||||
: parts(std::make_pair(op, operand))
|
||||
{}
|
||||
public:
|
||||
std::pair<Operator, std::shared_ptr<Operand>> parts;
|
||||
};
|
||||
|
||||
// Expressions can be chained as parts of a larger expression
|
||||
class Expression
|
||||
: public ExpressionPart
|
||||
{
|
||||
public:
|
||||
// This will eventually take in some data to be evaluated for a match.
|
||||
virtual bool evaluate(void) = 0;
|
||||
|
||||
public:
|
||||
std::vector<std::shared_ptr<ExpressionPart>> parts;
|
||||
};
|
||||
|
||||
} // namespace logic
|
||||
} // namespace smo
|
||||
|
||||
#endif
|
||||
@@ -20,7 +20,6 @@ namespace smo {
|
||||
* dropped. So we may very well literally forget those qualia that get dropped
|
||||
* from the LruLifos. (Because LruLifos have a fixed size.)
|
||||
*/
|
||||
|
||||
class LruLifo {
|
||||
public:
|
||||
LruLifo() = default;
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include <cstdint>
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <componentThread.h>
|
||||
#include <component.h>
|
||||
|
||||
namespace smo {
|
||||
@@ -13,6 +12,22 @@ class MarionetteThread;
|
||||
|
||||
namespace mrntt {
|
||||
|
||||
class MarionetteComponent
|
||||
: public Component
|
||||
{
|
||||
public:
|
||||
MarionetteComponent(const std::shared_ptr<ComponentThread> &thread);
|
||||
~MarionetteComponent() = default;
|
||||
|
||||
public:
|
||||
typedef std::function<void(bool)> mrnttLifetimeMgmtOpCbFn;
|
||||
void initializeReq(Callback<mrnttLifetimeMgmtOpCbFn> callback);
|
||||
void finalizeReq(Callback<mrnttLifetimeMgmtOpCbFn> callback);
|
||||
|
||||
private:
|
||||
class MrnttLifetimeMgmtOp;
|
||||
};
|
||||
|
||||
extern std::atomic<int> exitCode;
|
||||
void exitMarionetteLoop();
|
||||
void marionetteFinalizeReqCb(bool success);
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
class OptionParser
|
||||
{
|
||||
public:
|
||||
OptionParser() : verbose(false), printUsage(false) {}
|
||||
OptionParser() : verbose(false), printUsage(false), traceCallables(false)
|
||||
{}
|
||||
~OptionParser() = default;
|
||||
|
||||
void parseArguments(int argc, char *argv[], char **envp);
|
||||
@@ -38,7 +39,7 @@ public:
|
||||
std::vector<std::string> senseApiLibs;
|
||||
std::string dapSpecs;
|
||||
std::vector<std::string> dapSpecFiles;
|
||||
bool verbose, printUsage;
|
||||
bool verbose, printUsage, traceCallables;
|
||||
|
||||
static struct option longOptions[];
|
||||
};
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
#ifndef _PHENO_FRAME_H
|
||||
#define _PHENO_FRAME_H
|
||||
|
||||
#include <vector>
|
||||
#include <chronomenon.h>
|
||||
#include <mentalEntity.h>
|
||||
#include <user/stimFrame.h>
|
||||
|
||||
namespace smo {
|
||||
|
||||
class PhenoFrame
|
||||
: public Chronomenon, public MentalEntity
|
||||
{
|
||||
public:
|
||||
/** FIXME:
|
||||
* May be better to use a std::map here, where the key is a kind of ID
|
||||
* assigned to the stimulus source.
|
||||
*/
|
||||
std::vector<stim_buff::StimFrame> stimuli;
|
||||
};
|
||||
|
||||
class PhenoSeq
|
||||
: public Chronomenon, public MentalEntity
|
||||
{
|
||||
public:
|
||||
std::vector<std::pair<stim_buff::SimultaneityStamp, PhenoFrame>> frames;
|
||||
};
|
||||
|
||||
} // namespace smo
|
||||
|
||||
#endif // _PHENO_FRAME_H
|
||||
@@ -1,16 +0,0 @@
|
||||
#ifndef _QUALE_BUNDLE_H
|
||||
#define _QUALE_BUNDLE_H
|
||||
|
||||
#include <config.h>
|
||||
#include <array>
|
||||
#include <quale.h>
|
||||
|
||||
#define CONFIG_NUM_SENSORS 5
|
||||
|
||||
typedef std::array<Quale, CONFIG_NUM_SENSORS> QualeBundle_t;
|
||||
class QualeBundle
|
||||
{
|
||||
QualeBundle_t qualia;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include <componentThread.h>
|
||||
|
||||
namespace smo {
|
||||
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
#ifndef SENSE_API_MANAGER_H
|
||||
#define SENSE_API_MANAGER_H
|
||||
|
||||
#include <config.h>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#include <functional>
|
||||
#include <componentThread.h>
|
||||
#include <asynchronousLoop.h>
|
||||
#include <senseApis/senseApiLib.h>
|
||||
#include <user/deviceAttachmentSpec.h>
|
||||
#include <callback.h>
|
||||
#include <qutex.h>
|
||||
|
||||
namespace smo {
|
||||
namespace sense_api {
|
||||
|
||||
class SenseApiManager
|
||||
{
|
||||
public:
|
||||
static SenseApiManager& getInstance()
|
||||
{
|
||||
static SenseApiManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void initialize(void)
|
||||
{};
|
||||
void finalize(void)
|
||||
{};
|
||||
|
||||
SenseApiLib& loadSenseApiLib(
|
||||
const std::string& libraryPath,
|
||||
const std::shared_ptr<ComponentThread>& componentThread);
|
||||
|
||||
std::optional<std::shared_ptr<SenseApiLib>> getSenseApiLib(
|
||||
const std::string& libraryPath);
|
||||
std::optional<std::shared_ptr<SenseApiLib>> getSenseApiLibByApiName(
|
||||
const std::string& apiName);
|
||||
void unloadSenseApiLib(const std::string& libraryPath);
|
||||
|
||||
void initializeSenseApiLib(SenseApiLib& lib);
|
||||
void finalizeSenseApiLib(SenseApiLib& lib);
|
||||
|
||||
void loadAllSenseApiLibsFromOptions(
|
||||
const std::shared_ptr<ComponentThread>& componentThread);
|
||||
|
||||
void unloadAllSenseApiLibs(void);
|
||||
void initializeAllSenseApiLibs(void);
|
||||
void finalizeAllSenseApiLibs(void);
|
||||
|
||||
std::string stringifyLibs() const;
|
||||
|
||||
private:
|
||||
SenseApiManager()
|
||||
: qutex("SenseApiManager")
|
||||
{}
|
||||
~SenseApiManager() = default;
|
||||
|
||||
SenseApiManager(const SenseApiManager&) = delete;
|
||||
SenseApiManager& operator=(const SenseApiManager&) = delete;
|
||||
|
||||
std::vector<std::shared_ptr<SenseApiLib>> senseApiLibs;
|
||||
|
||||
public:
|
||||
Qutex qutex;
|
||||
|
||||
public:
|
||||
static std::optional<std::string> searchForLibInSmoSearchPaths(
|
||||
const std::string& libraryPath);
|
||||
};
|
||||
|
||||
} // namespace sense_api
|
||||
} // namespace smo
|
||||
|
||||
#endif // SENSE_API_MANAGER_H
|
||||
+22
-21
@@ -11,12 +11,12 @@
|
||||
#include <qutex.h>
|
||||
|
||||
namespace smo {
|
||||
namespace sense_api {
|
||||
namespace stim_buff {
|
||||
|
||||
class SenseApiLib
|
||||
class StimBuffApiLib
|
||||
{
|
||||
private:
|
||||
friend class SenseApiManager;
|
||||
friend class StimBuffApiManager;
|
||||
struct DlCloser
|
||||
{
|
||||
void operator()(void* handle) const
|
||||
@@ -28,24 +28,25 @@ private:
|
||||
};
|
||||
|
||||
public:
|
||||
SenseApiLib(
|
||||
StimBuffApiLib(
|
||||
const std::string& path, void *_dlopen_handle,
|
||||
SMO_GET_SENSE_API_DESC_FN_TYPEDEF *descFn)
|
||||
: libraryPath(path), qutex("SenseApiLib-" + path), isBeingDestroyed(false),
|
||||
SMO_GET_STIM_BUFF_API_DESC_FN_TYPEDEF *descFn)
|
||||
: libraryPath(path), qutex("StimBuffApiLib-" + path),
|
||||
isBeingDestroyed(false),
|
||||
dlopen_handle(_dlopen_handle, DlCloser()),
|
||||
SMO_GET_SENSE_API_DESC_FN_NAME(descFn)
|
||||
SMO_GET_STIM_BUFF_API_DESC_FN_NAME(descFn)
|
||||
{}
|
||||
|
||||
void setSenseApiDesc(const SenseApiDesc &desc)
|
||||
void setStimBuffApiDesc(const StimBuffApiDesc &desc)
|
||||
{
|
||||
if (!SenseApiDesc::sanityCheck(desc))
|
||||
if (!StimBuffApiDesc::sanityCheck(desc))
|
||||
{
|
||||
throw std::runtime_error(
|
||||
std::string(__func__) + ": Sanity check failed for sense API "
|
||||
std::string(__func__) + ": Sanity check failed for stim buff API "
|
||||
"descriptor in library '" + libraryPath + "'");
|
||||
}
|
||||
|
||||
senseApiDesc = desc;
|
||||
stimBuffApiDesc = desc;
|
||||
}
|
||||
|
||||
public:
|
||||
@@ -54,36 +55,36 @@ public:
|
||||
std::atomic<bool> isBeingDestroyed;
|
||||
std::unique_ptr<void, DlCloser> dlopen_handle;
|
||||
/* UNIMPLEMENTED: API-specific cmdline options. These affect this specific
|
||||
* sense api lib's behaviour globally.
|
||||
* stim buff api lib's behaviour globally.
|
||||
*/
|
||||
std::vector<std::string> options;
|
||||
|
||||
/**
|
||||
* @brief Every sense API lib is required to provide a function that returns
|
||||
* a SenseApiDesc struct. This struct states which API the lib uses to
|
||||
* connect Salmanoff to the sense provider it supports.
|
||||
* @brief Each stim buff API library must provide a function returning a
|
||||
* StimBuffApiDesc. This struct specifies which API the library uses to
|
||||
* connect Salmanoff to its supported stim buff provider.
|
||||
*
|
||||
* This getter function should be visible to dlsym() so that Salmanoff can
|
||||
* find it in the lib after loading it, and call it.
|
||||
*/
|
||||
std::function<SMO_GET_SENSE_API_DESC_FN_TYPEDEF>
|
||||
SMO_GET_SENSE_API_DESC_FN_NAME;
|
||||
std::function<SMO_GET_STIM_BUFF_API_DESC_FN_TYPEDEF>
|
||||
SMO_GET_STIM_BUFF_API_DESC_FN_NAME;
|
||||
|
||||
/**
|
||||
* @brief Salmanoff will call the `SMO_GET_SENSE_API_DESC_FN_NAME` getter
|
||||
* @brief Salmanoff will call the `SMO_GET_STIM_BUFF_API_DESC_FN_NAME` getter
|
||||
* function and use the data it provides in order to fill out this
|
||||
* descriptor.
|
||||
*/
|
||||
SenseApiDesc senseApiDesc;
|
||||
StimBuffApiDesc stimBuffApiDesc;
|
||||
|
||||
std::string stringify() const {
|
||||
std::string result = "Library Path: " + libraryPath + "\n";
|
||||
result += "Sense API Descriptor: " + senseApiDesc.stringify() + "\n";
|
||||
result += "Stim Buff API Descriptor: " + stimBuffApiDesc.stringify() + "\n";
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace sense_api
|
||||
} // namespace stim_buff
|
||||
} // namespace smo
|
||||
|
||||
#endif // SENSE_API_PROVIDER_DESC_H
|
||||
@@ -0,0 +1,77 @@
|
||||
#ifndef SENSE_API_MANAGER_H
|
||||
#define SENSE_API_MANAGER_H
|
||||
|
||||
#include <config.h>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#include <functional>
|
||||
#include <asynchronousLoop.h>
|
||||
#include <stimBuffApis/stimBuffApiLib.h>
|
||||
#include <user/deviceAttachmentSpec.h>
|
||||
#include <callback.h>
|
||||
#include <qutex.h>
|
||||
|
||||
namespace smo {
|
||||
namespace stim_buff {
|
||||
|
||||
class StimBuffApiManager
|
||||
{
|
||||
public:
|
||||
static StimBuffApiManager& getInstance()
|
||||
{
|
||||
static StimBuffApiManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void initialize(void)
|
||||
{};
|
||||
void finalize(void)
|
||||
{};
|
||||
|
||||
StimBuffApiLib& loadStimBuffApiLib(
|
||||
const std::string& libraryPath,
|
||||
const std::shared_ptr<ComponentThread>& componentThread);
|
||||
|
||||
std::optional<std::shared_ptr<StimBuffApiLib>> getStimBuffApiLib(
|
||||
const std::string& libraryPath);
|
||||
std::optional<std::shared_ptr<StimBuffApiLib>> getStimBuffApiLibByApiName(
|
||||
const std::string& apiName);
|
||||
void unloadStimBuffApiLib(const std::string& libraryPath);
|
||||
|
||||
void initializeStimBuffApiLib(StimBuffApiLib& lib);
|
||||
void finalizeStimBuffApiLib(StimBuffApiLib& lib);
|
||||
|
||||
void loadAllStimBuffApiLibsFromOptions(
|
||||
const std::shared_ptr<ComponentThread>& componentThread);
|
||||
|
||||
void unloadAllStimBuffApiLibs(void);
|
||||
void initializeAllStimBuffApiLibs(void);
|
||||
void finalizeAllStimBuffApiLibs(void);
|
||||
|
||||
std::string stringifyLibs() const;
|
||||
|
||||
private:
|
||||
StimBuffApiManager()
|
||||
: qutex("StimBuffApiManager")
|
||||
{}
|
||||
~StimBuffApiManager() = default;
|
||||
|
||||
StimBuffApiManager(const StimBuffApiManager&) = delete;
|
||||
StimBuffApiManager& operator=(const StimBuffApiManager&) = delete;
|
||||
|
||||
std::vector<std::shared_ptr<StimBuffApiLib>> stimBuffApiLibs;
|
||||
|
||||
public:
|
||||
Qutex qutex;
|
||||
|
||||
public:
|
||||
static std::optional<std::string> searchForLibInSmoSearchPaths(
|
||||
const std::string& libraryPath);
|
||||
};
|
||||
|
||||
} // namespace stim_buff
|
||||
} // namespace smo
|
||||
|
||||
#endif // SENSE_API_MANAGER_H
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <asynchronousContinuation.h>
|
||||
#include <asynchronousLoop.h>
|
||||
#include <callback.h>
|
||||
#include <callableTracer.h>
|
||||
#include <component.h>
|
||||
#include <componentThread.h>
|
||||
#include <deviceManager/deviceManager.h>
|
||||
@@ -58,6 +59,10 @@ public:
|
||||
}
|
||||
|
||||
device::DeviceManager::getInstance().initializeDeviceReattacher();
|
||||
|
||||
// Call negtrinEventInd on the Director in the final callback
|
||||
smo::mind::globalMind->director.negtrinEventInd();
|
||||
|
||||
context->callOriginalCb(success);
|
||||
}
|
||||
|
||||
@@ -74,6 +79,22 @@ public:
|
||||
|
||||
device::DeviceManager::getInstance().finalizeDeviceReattacher();
|
||||
|
||||
/** FIXME:
|
||||
* It may be necessary to add a delay here to ensure that all in-flight
|
||||
* timer timeouts have finished executing? Or some other mechanism.
|
||||
*
|
||||
* We need some way to ensure that in-flight timeouts don't get fired
|
||||
* during the finalize sequence. This is because they may depend on
|
||||
* state that is being finalized or has been finalized at the point
|
||||
* when they timeout.
|
||||
*
|
||||
* This seems to be actually happening with the delayed calls to
|
||||
* AttachDeviceReq::attachDeviceReq2() inside of livoxGen1.cpp.
|
||||
*
|
||||
* One tactic might be to shut down device reattacher before finalizing
|
||||
* and pause for a bit before continuing to shutdown other components.
|
||||
*/
|
||||
|
||||
smo::mind::globalMind->finalizeReq({context, std::bind(
|
||||
&MrnttLifetimeMgmtOp::finalizeReq2,
|
||||
this, context, std::placeholders::_1)});
|
||||
@@ -111,9 +132,9 @@ void MarionetteComponent::initializeReq(
|
||||
*this, mrntt, callback);
|
||||
|
||||
mrntt->getIoService().post(
|
||||
std::bind(
|
||||
STC(std::bind(
|
||||
&MrnttLifetimeMgmtOp::initializeReq1_posted,
|
||||
request.get(), request));
|
||||
request.get(), request)));
|
||||
}
|
||||
|
||||
void MarionetteComponent::finalizeReq(
|
||||
@@ -131,9 +152,9 @@ void MarionetteComponent::finalizeReq(
|
||||
*this, mrntt, callback);
|
||||
|
||||
mrntt->getIoService().post(
|
||||
std::bind(
|
||||
STC(std::bind(
|
||||
&MrnttLifetimeMgmtOp::finalizeReq1_posted,
|
||||
request.get(), request));
|
||||
request.get(), request)));
|
||||
}
|
||||
|
||||
} // namespace mrntt
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include <boostAsioLinkageFix.h>
|
||||
#include <config.h>
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <exception>
|
||||
#include <opts.h>
|
||||
#include <typeinfo>
|
||||
@@ -65,6 +67,8 @@ void marionetteInitializeReqCb(bool success)
|
||||
|
||||
void MarionetteThread::main(MarionetteThread& self)
|
||||
{
|
||||
std::string threadName = "smo:" + self.name;
|
||||
pthread_setname_np(pthread_self(), threadName.c_str());
|
||||
// Wait for CRT's main() to post us the command line args.
|
||||
std::cout << __func__ << ": Waiting for command line JOLT" << std::endl;
|
||||
self.getIoService().run();
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
#include <iostream>
|
||||
#include <component.h>
|
||||
#include <nonNeutralQualia.h>
|
||||
#include <marionette/marionette.h>
|
||||
|
||||
|
||||
namespace smo {
|
||||
namespace mrntt {
|
||||
|
||||
} // namespace mrntt
|
||||
} // namespace smo
|
||||
@@ -1,7 +1,7 @@
|
||||
#include <iostream>
|
||||
#include <mindManager/mindManager.h>
|
||||
#include <deviceManager/deviceManager.h>
|
||||
#include <senseApis/senseApiManager.h>
|
||||
#include <stimBuffApis/stimBuffApiManager.h>
|
||||
#include <salmanoff.h>
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ void initializeSalmanoff(void)
|
||||
std::cout << __func__ << ": Entered." << std::endl;
|
||||
|
||||
mind::MindManager::getInstance().initialize();
|
||||
sense_api::SenseApiManager::getInstance().initialize();
|
||||
stim_buff::StimBuffApiManager::getInstance().initialize();
|
||||
device::DeviceManager::getInstance().initialize();
|
||||
device::DeviceManager::getInstance().collateAllDapSpecs();
|
||||
device::DeviceManager::getInstance().parseAllDapSpecs();
|
||||
@@ -23,7 +23,7 @@ void shutdownSalmanoff(void)
|
||||
{
|
||||
std::cout << __func__ << ": Entered." << std::endl;
|
||||
device::DeviceManager::getInstance().finalize();
|
||||
sense_api::SenseApiManager::getInstance().finalize();
|
||||
stim_buff::StimBuffApiManager::getInstance().finalize();
|
||||
mind::MindManager::getInstance().finalize();
|
||||
}
|
||||
|
||||
|
||||
+6
-5
@@ -3,11 +3,12 @@
|
||||
#include <asynchronousContinuation.h>
|
||||
#include <asynchronousLoop.h>
|
||||
#include <callback.h>
|
||||
#include <callableTracer.h>
|
||||
#include <mind.h>
|
||||
#include <componentThread.h>
|
||||
#include <director/director.h>
|
||||
#include <simulator/simulator.h>
|
||||
#include <senseApis/senseApiManager.h>
|
||||
#include <stimBuffApis/stimBuffApiManager.h>
|
||||
#include <marionette/marionette.h>
|
||||
|
||||
namespace smo {
|
||||
@@ -215,9 +216,9 @@ void Mind::initializeReq(Callback<mindLifetimeMgmtOpCbFn> callback)
|
||||
*this, caller, callback);
|
||||
|
||||
mrntt::mrntt.thread->getIoService().post(
|
||||
std::bind(
|
||||
STC(std::bind(
|
||||
&MindLifetimeMgmtOp::initializeReq1_posted,
|
||||
request.get(), request));
|
||||
request.get(), request)));
|
||||
}
|
||||
|
||||
void Mind::finalizeReq(Callback<mindLifetimeMgmtOpCbFn> callback)
|
||||
@@ -227,9 +228,9 @@ void Mind::finalizeReq(Callback<mindLifetimeMgmtOpCbFn> callback)
|
||||
*this, caller, callback);
|
||||
|
||||
mrntt::mrntt.thread->getIoService().post(
|
||||
std::bind(
|
||||
STC(std::bind(
|
||||
&MindLifetimeMgmtOp::finalizeReq1_posted,
|
||||
request.get(), request));
|
||||
request.get(), request)));
|
||||
}
|
||||
|
||||
void Mind::distributeAndPinThreadsAcrossCpus()
|
||||
|
||||
+16
-5
@@ -43,6 +43,9 @@ struct option OptionParser::longOptions[] = {
|
||||
{"apipath", required_argument, 0, 'p'},
|
||||
{"libpath", required_argument, 0, 'p'},
|
||||
{"verbose", no_argument, 0, 'v'},
|
||||
{"trace-callables", no_argument, 0, 't'},
|
||||
{"call-trace", no_argument, 0, 't'},
|
||||
{"calltrace", no_argument, 0, 't'},
|
||||
{"help", no_argument, 0, 'h'},
|
||||
{0, 0, 0, 0}
|
||||
};
|
||||
@@ -58,7 +61,7 @@ void OptionParser::parseArguments(int argc, char *argv[], char **envp)
|
||||
optind = 1; // Reset optind to 1 before parsing
|
||||
opterr = 0;
|
||||
while ((opt = getopt_long(
|
||||
argc, argv, "s:d:a:p:vh?", longOptions, &optionIndex)) != -1)
|
||||
argc, argv, "s:d:a:p:vht?", longOptions, &optionIndex)) != -1)
|
||||
{
|
||||
switch (opt)
|
||||
{
|
||||
@@ -91,6 +94,9 @@ void OptionParser::parseArguments(int argc, char *argv[], char **envp)
|
||||
case 'v':
|
||||
verbose = true;
|
||||
break;
|
||||
case 't':
|
||||
traceCallables = true;
|
||||
break;
|
||||
case 'h':
|
||||
throw JustPrintUsageNoError(*this);
|
||||
case '?':
|
||||
@@ -108,9 +114,10 @@ std::string OptionParser::getUsage() const
|
||||
"[-a|--api-lib|--apilib|--api|--lib <filename>] "
|
||||
"[-p|--api-lib-path|--apipath|--libpath <directory>] "
|
||||
"[-v|--verbose] "
|
||||
"[-t|--trace-callables|--call-trace|--calltrace] "
|
||||
"[-h|--help]\n\n"
|
||||
"Example DAP spec:\n"
|
||||
" -s '+edev|device-identifier|visual-implexor|xcb(dev-substring)|xorg(display=0|screen=0)|my-window'";
|
||||
" -s '+edev|my-cam|video-qualeiface|v4l2-video(fps-hz=30)|v4l2()|/dev/video0'";
|
||||
}
|
||||
|
||||
std::string OptionParser::stringifyOptions(void) const
|
||||
@@ -121,7 +128,11 @@ std::string OptionParser::stringifyOptions(void) const
|
||||
oss << "Verbose mode is on" << std::endl;
|
||||
}
|
||||
|
||||
oss << "DAP Specs: " << dapSpecs << std::endl;
|
||||
if (traceCallables) {
|
||||
oss << "Callable tracing is enabled" << std::endl;
|
||||
}
|
||||
|
||||
oss << "Cmdline DAP Specs: " << dapSpecs << std::endl;
|
||||
|
||||
oss << "DAP Spec Files: ";
|
||||
for (const auto& file : dapSpecFiles) {
|
||||
@@ -129,12 +140,12 @@ std::string OptionParser::stringifyOptions(void) const
|
||||
}
|
||||
oss << std::endl;
|
||||
|
||||
oss << "Sense API Library Paths: ";
|
||||
oss << "Stim Buff API Library Paths: ";
|
||||
for (const auto& path : senseApiLibPath) {
|
||||
oss << path << " ";
|
||||
}
|
||||
oss << std::endl;
|
||||
oss << "Sense API Libraries: ";
|
||||
oss << "Stim Buff API Libraries: ";
|
||||
for (const auto& lib : senseApiLibs) {
|
||||
oss << lib << " ";
|
||||
}
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
|
||||
#include <nonNeutralQualia.h>
|
||||
+18
-1
@@ -123,8 +123,25 @@ void Qutex::backoff(
|
||||
": backoff called on empty queue - this should never happen");
|
||||
}
|
||||
|
||||
/* Check if failedAcquirer is at the front of the queue with
|
||||
* nRequiredLocks == 1. This should never happen because an
|
||||
* acquirer at the front of the queue with nRequiredLocks == 1
|
||||
* should always succeed.
|
||||
*/
|
||||
const LockerAndInvokerBase& oldFront = *queue.front();
|
||||
if (oldFront == failedAcquirer && nRequiredLocks == 1)
|
||||
{
|
||||
lock.release();
|
||||
|
||||
throw std::runtime_error(
|
||||
std::string(__func__) +
|
||||
": Failed acquirer is at front of queue with nRequiredLocks==1 - "
|
||||
"acquirer at front of queue with nRequiredLocks==1 should always "
|
||||
"succeed.");
|
||||
}
|
||||
|
||||
// Rotate queue members if failedAcquirer is at front of queue
|
||||
if ((*queue.front()) == failedAcquirer && nQItems > 1)
|
||||
if (oldFront == failedAcquirer && nQItems > 1)
|
||||
{
|
||||
/** EXPLANATION:
|
||||
* Rotate the top LockSet.size() items in the queue by moving
|
||||
|
||||
+54
-54
@@ -2,8 +2,8 @@
|
||||
#include <stdexcept>
|
||||
#include <optional>
|
||||
#include <filesystem>
|
||||
#include <senseApis/senseApiManager.h>
|
||||
#include <senseApis/senseApiLib.h>
|
||||
#include <stimBuffApis/stimBuffApiManager.h>
|
||||
#include <stimBuffApis/stimBuffApiLib.h>
|
||||
#include <opts.h>
|
||||
#include <asynchronousBridge.h>
|
||||
#include <asynchronousContinuation.h>
|
||||
@@ -18,7 +18,7 @@
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace smo {
|
||||
namespace sense_api {
|
||||
namespace stim_buff {
|
||||
|
||||
/**
|
||||
* @brief Searches for a library in predefined locations
|
||||
@@ -45,7 +45,7 @@ static std::optional<std::string> searchForLibInSmoSearchPaths(
|
||||
const auto& options = OptionParser::getOptions();
|
||||
if (!options.senseApiLibPath.empty())
|
||||
{
|
||||
// Insert all sense API library paths at the beginning of search paths
|
||||
// Insert all stim buff API library paths at the beginning of search paths
|
||||
searchPaths.insert(
|
||||
searchPaths.begin(),
|
||||
options.senseApiLibPath.begin(),
|
||||
@@ -79,7 +79,7 @@ static std::shared_ptr<ComponentThread> ComponentThread_getSelf()
|
||||
return ComponentThread::getSelf();
|
||||
}
|
||||
|
||||
/* Hooks to be provided to senseApiLibs, enabling them to call into Salmanoff
|
||||
/* Hooks to be provided to stimBuffApiLibs, enabling them to call into Salmanoff
|
||||
* code.
|
||||
*/
|
||||
static SmoCallbacks smoCallbacks =
|
||||
@@ -93,13 +93,13 @@ static SmoThreadingModelDesc smoThreadingModelDesc = {
|
||||
.componentThread = nullptr
|
||||
};
|
||||
|
||||
std::optional<std::string> SenseApiManager::searchForLibInSmoSearchPaths(
|
||||
std::optional<std::string> StimBuffApiManager::searchForLibInSmoSearchPaths(
|
||||
const std::string& libraryPath)
|
||||
{
|
||||
return ::smo::sense_api::searchForLibInSmoSearchPaths(libraryPath);
|
||||
return ::smo::stim_buff::searchForLibInSmoSearchPaths(libraryPath);
|
||||
}
|
||||
|
||||
SenseApiLib& SenseApiManager::loadSenseApiLib(
|
||||
StimBuffApiLib& StimBuffApiManager::loadStimBuffApiLib(
|
||||
const std::string& libraryPath,
|
||||
const std::shared_ptr<ComponentThread>& componentThread
|
||||
)
|
||||
@@ -110,7 +110,7 @@ SenseApiLib& SenseApiManager::loadSenseApiLib(
|
||||
|
||||
// Clear any existing error
|
||||
dlerror();
|
||||
auto dlopen_handle = std::unique_ptr<void, SenseApiLib::DlCloser>(
|
||||
auto dlopen_handle = std::unique_ptr<void, StimBuffApiLib::DlCloser>(
|
||||
dlopen(resolvedPath.c_str(), RTLD_LAZY));
|
||||
if (!dlopen_handle && fullPath.has_value())
|
||||
{
|
||||
@@ -133,13 +133,13 @@ SenseApiLib& SenseApiManager::loadSenseApiLib(
|
||||
}
|
||||
|
||||
// Initialize getSenseApiDescriptor
|
||||
auto func = reinterpret_cast<SMO_GET_SENSE_API_DESC_FN_TYPEDEF *>(
|
||||
dlsym(dlopen_handle.get(), SMO_GET_SENSE_API_DESC_FN_NAME_STR));
|
||||
auto func = reinterpret_cast<SMO_GET_STIM_BUFF_API_DESC_FN_TYPEDEF *>(
|
||||
dlsym(dlopen_handle.get(), SMO_GET_STIM_BUFF_API_DESC_FN_NAME_STR));
|
||||
if (!func)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
std::string(__func__) + ": dlsym('"
|
||||
SMO_GET_SENSE_API_DESC_FN_NAME_STR "') failed for library '"
|
||||
SMO_GET_STIM_BUFF_API_DESC_FN_NAME_STR "') failed for library '"
|
||||
+ libraryPath + "'");
|
||||
}
|
||||
|
||||
@@ -148,53 +148,53 @@ SenseApiLib& SenseApiManager::loadSenseApiLib(
|
||||
smoThreadingModelDesc.componentThread = componentThread;
|
||||
}
|
||||
|
||||
const SenseApiDesc &libApiDesc = func(
|
||||
const StimBuffApiDesc &libApiDesc = func(
|
||||
smoCallbacks, smoThreadingModelDesc);
|
||||
|
||||
auto lib = std::make_shared<SenseApiLib>(
|
||||
auto lib = std::make_shared<StimBuffApiLib>(
|
||||
libraryPath, dlopen_handle.release(), func);
|
||||
lib->setSenseApiDesc(libApiDesc);
|
||||
senseApiLibs.push_back(lib);
|
||||
return *senseApiLibs.back();
|
||||
lib->setStimBuffApiDesc(libApiDesc);
|
||||
stimBuffApiLibs.push_back(lib);
|
||||
return *stimBuffApiLibs.back();
|
||||
}
|
||||
|
||||
std::optional<std::shared_ptr<SenseApiLib>>
|
||||
SenseApiManager::getSenseApiLib(const std::string& libraryPath)
|
||||
std::optional<std::shared_ptr<StimBuffApiLib>>
|
||||
StimBuffApiManager::getStimBuffApiLib(const std::string& libraryPath)
|
||||
{
|
||||
auto it = std::find_if(senseApiLibs.begin(), senseApiLibs.end(),
|
||||
[&libPath = libraryPath](const std::shared_ptr<SenseApiLib>& lib) {
|
||||
auto it = std::find_if(stimBuffApiLibs.begin(), stimBuffApiLibs.end(),
|
||||
[&libPath = libraryPath](const std::shared_ptr<StimBuffApiLib>& lib) {
|
||||
return lib->libraryPath == libPath;
|
||||
}
|
||||
);
|
||||
|
||||
if (it != senseApiLibs.end()) { return *it; }
|
||||
if (it != stimBuffApiLibs.end()) { return *it; }
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<std::shared_ptr<SenseApiLib>>
|
||||
SenseApiManager::getSenseApiLibByApiName(const std::string& apiName)
|
||||
std::optional<std::shared_ptr<StimBuffApiLib>>
|
||||
StimBuffApiManager::getStimBuffApiLibByApiName(const std::string& apiName)
|
||||
{
|
||||
auto it = std::find_if(senseApiLibs.begin(), senseApiLibs.end(),
|
||||
[&apiName](const std::shared_ptr<SenseApiLib>& lib) {
|
||||
return lib->senseApiDesc.name == apiName;
|
||||
auto it = std::find_if(stimBuffApiLibs.begin(), stimBuffApiLibs.end(),
|
||||
[&apiName](const std::shared_ptr<StimBuffApiLib>& lib) {
|
||||
return lib->stimBuffApiDesc.name == apiName;
|
||||
}
|
||||
);
|
||||
|
||||
if (it != senseApiLibs.end()) { return *it; }
|
||||
if (it != stimBuffApiLibs.end()) { return *it; }
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void SenseApiManager::unloadSenseApiLib(const std::string& libraryPath)
|
||||
void StimBuffApiManager::unloadStimBuffApiLib(const std::string& libraryPath)
|
||||
{
|
||||
auto it = std::find_if(senseApiLibs.begin(), senseApiLibs.end(),
|
||||
[&lpath = libraryPath](const std::shared_ptr<SenseApiLib>& lib) {
|
||||
auto it = std::find_if(stimBuffApiLibs.begin(), stimBuffApiLibs.end(),
|
||||
[&lpath = libraryPath](const std::shared_ptr<StimBuffApiLib>& lib) {
|
||||
return lib->libraryPath == lpath;
|
||||
}
|
||||
);
|
||||
|
||||
if (it != senseApiLibs.end())
|
||||
if (it != stimBuffApiLibs.end())
|
||||
{
|
||||
senseApiLibs.erase(it);
|
||||
stimBuffApiLibs.erase(it);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -202,75 +202,75 @@ void SenseApiManager::unloadSenseApiLib(const std::string& libraryPath)
|
||||
<< libraryPath << '\n';
|
||||
}
|
||||
|
||||
void SenseApiManager::unloadAllSenseApiLibs(void)
|
||||
void StimBuffApiManager::unloadAllStimBuffApiLibs(void)
|
||||
{
|
||||
senseApiLibs.clear();
|
||||
stimBuffApiLibs.clear();
|
||||
}
|
||||
|
||||
void SenseApiManager::loadAllSenseApiLibsFromOptions(
|
||||
void StimBuffApiManager::loadAllStimBuffApiLibsFromOptions(
|
||||
const std::shared_ptr<ComponentThread>& componentThread
|
||||
)
|
||||
{
|
||||
const auto& options = OptionParser::getOptions();
|
||||
for (const auto& libPath : options.senseApiLibs) {
|
||||
loadSenseApiLib(libPath, componentThread);
|
||||
loadStimBuffApiLib(libPath, componentThread);
|
||||
}
|
||||
}
|
||||
|
||||
std::string SenseApiManager::stringifyLibs() const
|
||||
std::string StimBuffApiManager::stringifyLibs() const
|
||||
{
|
||||
std::string result;
|
||||
for (const auto& lib : senseApiLibs) {
|
||||
for (const auto& lib : stimBuffApiLibs) {
|
||||
result += lib->stringify() + "\n";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void SenseApiManager::initializeSenseApiLib(SenseApiLib& lib)
|
||||
void StimBuffApiManager::initializeStimBuffApiLib(StimBuffApiLib& lib)
|
||||
{
|
||||
/** FIXME:
|
||||
* When we eventually make this method async, this method should acquire
|
||||
* the SenseApiManager's main CRUD qutex.
|
||||
* the StimBuffApiManager's main CRUD qutex.
|
||||
*/
|
||||
if (!lib.senseApiDesc.sal_mgmt_libOps.initializeInd)
|
||||
if (!lib.stimBuffApiDesc.sal_mgmt_libOps.initializeInd)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
std::string(__func__) + ": initializeInd() is NULL for library '"
|
||||
+ lib.libraryPath + "'");
|
||||
}
|
||||
lib.senseApiDesc.sal_mgmt_libOps.initializeInd();
|
||||
lib.stimBuffApiDesc.sal_mgmt_libOps.initializeInd();
|
||||
}
|
||||
|
||||
void SenseApiManager::finalizeSenseApiLib(SenseApiLib& lib)
|
||||
void StimBuffApiManager::finalizeStimBuffApiLib(StimBuffApiLib& lib)
|
||||
{
|
||||
/** FIXME:
|
||||
* When we eventually make this method async, this flag should only be set
|
||||
* after acquiring the SenseApiManager's main CRUD qutex.
|
||||
* after acquiring the StimBuffApiManager's main CRUD qutex.
|
||||
*/
|
||||
lib.isBeingDestroyed.store(true);
|
||||
if (!lib.senseApiDesc.sal_mgmt_libOps.finalizeInd)
|
||||
if (!lib.stimBuffApiDesc.sal_mgmt_libOps.finalizeInd)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
std::string(__func__) + ": finalizeInd() is NULL for library '"
|
||||
+ lib.libraryPath + "'");
|
||||
}
|
||||
lib.senseApiDesc.sal_mgmt_libOps.finalizeInd();
|
||||
lib.stimBuffApiDesc.sal_mgmt_libOps.finalizeInd();
|
||||
}
|
||||
|
||||
void SenseApiManager::initializeAllSenseApiLibs(void)
|
||||
void StimBuffApiManager::initializeAllStimBuffApiLibs(void)
|
||||
{
|
||||
for (auto& lib : senseApiLibs) {
|
||||
initializeSenseApiLib(*lib);
|
||||
for (auto& lib : stimBuffApiLibs) {
|
||||
initializeStimBuffApiLib(*lib);
|
||||
}
|
||||
}
|
||||
|
||||
void SenseApiManager::finalizeAllSenseApiLibs(void)
|
||||
void StimBuffApiManager::finalizeAllStimBuffApiLibs(void)
|
||||
{
|
||||
for (auto& lib : senseApiLibs) {
|
||||
finalizeSenseApiLib(*lib);
|
||||
for (auto& lib : stimBuffApiLibs) {
|
||||
finalizeStimBuffApiLib(*lib);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace sense_api
|
||||
} // namespace stim_buff
|
||||
} // namespace smo
|
||||
@@ -0,0 +1,75 @@
|
||||
cmake_dependent_option(ENABLE_STIMBUFFAPI_livoxGen1
|
||||
"Enable Livox Gen1 LiDAR stim buff API" ON
|
||||
"ENABLE_LIB_livoxProto1" OFF)
|
||||
|
||||
if(ENABLE_STIMBUFFAPI_livoxGen1)
|
||||
# Set CONFIG variable for config.h
|
||||
set(CONFIG_STIMBUFFAPI_LIVOXGEN1_ENABLED 1)
|
||||
|
||||
# Find liburing using pkg-config
|
||||
pkg_check_modules(URING REQUIRED liburing)
|
||||
|
||||
# Find OpenCL: try find_package first, fall back to pkg-config
|
||||
find_package(OpenCL QUIET)
|
||||
if(OpenCL_FOUND)
|
||||
# Normalize find_package variables to match pkg_check_modules naming
|
||||
set(OPENCL_FOUND TRUE)
|
||||
set(OPENCL_INCLUDE_DIRS ${OpenCL_INCLUDE_DIRS})
|
||||
# Handle both OpenCL_LIBRARY (singular) and OpenCL_LIBRARIES (plural)
|
||||
if(OpenCL_LIBRARIES)
|
||||
set(OPENCL_LIBRARIES ${OpenCL_LIBRARIES})
|
||||
else()
|
||||
set(OPENCL_LIBRARIES ${OpenCL_LIBRARY})
|
||||
endif()
|
||||
set(OPENCL_LIBRARY_DIRS "")
|
||||
message(STATUS "Found OpenCL using find_package")
|
||||
else()
|
||||
# Fall back to pkg-config
|
||||
pkg_check_modules(OPENCL OpenCL)
|
||||
if(NOT OPENCL_FOUND)
|
||||
message(FATAL_ERROR
|
||||
"Failed to find OpenCL: both find_package and "
|
||||
"pkg_check_modules failed. Try installing the "
|
||||
"'ocl-icd-opencl-dev' package (or the appropriate "
|
||||
"OpenCL development package for your system)."
|
||||
)
|
||||
endif()
|
||||
message(STATUS "Found OpenCL using pkg-config")
|
||||
endif()
|
||||
|
||||
add_library(livoxGen1 SHARED
|
||||
livoxGen1.cpp
|
||||
stagingBuffer.cpp
|
||||
pcloudStimulusBuffer.cpp
|
||||
ioUringAssemblyEngine.cpp
|
||||
openClSplittingEngine.cpp
|
||||
)
|
||||
|
||||
target_include_directories(livoxGen1 PUBLIC
|
||||
${Boost_INCLUDE_DIRS}
|
||||
${CMAKE_SOURCE_DIR}/commonLibs
|
||||
${URING_INCLUDE_DIRS}
|
||||
${OPENCL_INCLUDE_DIRS}
|
||||
)
|
||||
target_link_libraries(livoxGen1 PUBLIC
|
||||
Boost::system
|
||||
Boost::log
|
||||
${URING_LIBRARIES}
|
||||
${OPENCL_LIBRARIES}
|
||||
attachmentSupport
|
||||
)
|
||||
target_link_directories(livoxGen1 PUBLIC
|
||||
${URING_LIBRARY_DIRS}
|
||||
${OPENCL_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
# Verify Boost dynamic dependencies after build
|
||||
add_custom_command(TARGET livoxGen1 POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -DVERIFY_FILE="$<TARGET_FILE:livoxGen1>"
|
||||
-P ${CMAKE_SOURCE_DIR}/cmake/VerifyBoostDynamic.cmake
|
||||
COMMENT "Verifying Boost dynamic dependencies for livoxGen1"
|
||||
)
|
||||
|
||||
# Install rules
|
||||
install(TARGETS livoxGen1 DESTINATION lib)
|
||||
endif()
|
||||
@@ -0,0 +1,65 @@
|
||||
#ifndef _LIVOX_GEN1_FRAME_ASSEMBLY_DESC_H
|
||||
#define _LIVOX_GEN1_FRAME_ASSEMBLY_DESC_H
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
|
||||
namespace smo {
|
||||
namespace stim_buff {
|
||||
|
||||
class FrameAssemblyDesc
|
||||
{
|
||||
public:
|
||||
struct SlotDesc
|
||||
{
|
||||
size_t offsetBytes; // offset from frame base
|
||||
uint8_t* vaddr; // direct pointer into StagingBuffer memory
|
||||
size_t nBytes; // slot capacity in bytes
|
||||
};
|
||||
|
||||
public:
|
||||
FrameAssemblyDesc() = default;
|
||||
|
||||
FrameAssemblyDesc(
|
||||
size_t n, size_t slotSize,
|
||||
size_t frameStride,
|
||||
std::vector<SlotDesc> slotList)
|
||||
: numSlots(n), slotSizeBytes(slotSize),
|
||||
frameStrideBytes(frameStride),
|
||||
slots(std::move(slotList)) {}
|
||||
|
||||
inline std::string stringify() const {
|
||||
std::ostringstream oss;
|
||||
oss << "FrameAssemblyDesc{"
|
||||
<< "numSlots=" << numSlots
|
||||
<< ", slotSizeBytes=" << slotSizeBytes
|
||||
<< ", frameStrideBytes=" << frameStrideBytes
|
||||
<< ", slots=[";
|
||||
const size_t preview = slots.size() < 4 ? slots.size() : 4;
|
||||
for (size_t i = 0; i < preview; ++i) {
|
||||
oss << "{off=" << slots[i].offsetBytes
|
||||
<< ", nBytes=" << slots[i].nBytes
|
||||
<< ", vaddr=" << (const void*)slots[i].vaddr << "}";
|
||||
if (i + 1 < preview) oss << ",";
|
||||
}
|
||||
if (slots.size() > preview) oss << ", ...";
|
||||
oss << "]}";
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
public:
|
||||
size_t numSlots;
|
||||
size_t slotSizeBytes;
|
||||
size_t frameStrideBytes;
|
||||
std::vector<SlotDesc> slots;
|
||||
};
|
||||
|
||||
} // namespace stim_buff
|
||||
} // namespace smo
|
||||
|
||||
#endif // _LIVOX_GEN1_FRAME_ASSEMBLY_DESC_H
|
||||
|
||||
|
||||
@@ -0,0 +1,626 @@
|
||||
#include <boostAsioLinkageFix.h>
|
||||
#include <config.h>
|
||||
#include <opts.h>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
#include <functional>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/eventfd.h>
|
||||
#include <sys/uio.h>
|
||||
#include <sys/poll.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <boost/system/error_code.hpp>
|
||||
#include <livoxProto1/device.h>
|
||||
#include <livoxProto1/livoxProto1.h>
|
||||
#include <asynchronousContinuation.h>
|
||||
#include <asynchronousLoop.h>
|
||||
#include <asynchronousBridge.h>
|
||||
#include <callback.h>
|
||||
#include <callableTracer.h>
|
||||
#include <spinLock.h>
|
||||
#include "ioUringAssemblyEngine.h"
|
||||
#include "pcloudStimulusBuffer.h"
|
||||
#include "livoxGen1.h"
|
||||
|
||||
namespace smo {
|
||||
namespace stim_buff {
|
||||
|
||||
inline LivoxProto1DllState& getLivoxProto1State() { return livoxProto1; }
|
||||
|
||||
struct DummyLivoxEthHeader
|
||||
{
|
||||
enum : uint32_t {
|
||||
INVALID_ERR_CODE = 0xFFFFFFFFu
|
||||
};
|
||||
enum : uint8_t {
|
||||
INVALID_TIMESTAMP_TYPE = 0xFFu,
|
||||
INVALID_DATA_TYPE = 0xFFu
|
||||
};
|
||||
|
||||
uint8_t version, slot, id, rsvd;
|
||||
uint32_t err_code;
|
||||
uint8_t timestamp_type, data_type;
|
||||
uint8_t timestamp[8];
|
||||
};
|
||||
|
||||
IoUringAssemblyEngine::IoUringAssemblyEngine(PcloudStimulusBuffer& parent_)
|
||||
: parent(parent_),
|
||||
frameAssemblyDesc(nullptr), ring{},
|
||||
isSetup(false),
|
||||
eventfdFd(-1), eventfdDesc(nullptr), eventfd_value(0),
|
||||
stallTimer(parent_.device->componentThread->getIoService()),
|
||||
isAssembling(false)
|
||||
{}
|
||||
|
||||
bool IoUringAssemblyEngine::setup()
|
||||
{
|
||||
if (isSetup)
|
||||
{ return false; }
|
||||
|
||||
// Get FrameAssemblyDesc from staging buffer
|
||||
frameAssemblyDesc = static_cast<std::shared_ptr<FrameAssemblyDesc>>(
|
||||
parent.assemblyBuffer);
|
||||
|
||||
if (!frameAssemblyDesc || frameAssemblyDesc->slots.empty())
|
||||
{ return false; }
|
||||
|
||||
// Get point cloud data socket descriptor from UdpCommandDemuxer
|
||||
auto& livoxState = getLivoxProto1State();
|
||||
if (!livoxState.livoxProto1_getPcloudDataFdDesc)
|
||||
{ return false; }
|
||||
pcloudDataFdDesc = (*livoxState.livoxProto1_getPcloudDataFdDesc)();
|
||||
if (!pcloudDataFdDesc)
|
||||
{ return false; }
|
||||
|
||||
// Get UDP socket file descriptor
|
||||
int udpFd = pcloudDataFdDesc->native_handle();
|
||||
if (udpFd < 0)
|
||||
{ return false; }
|
||||
|
||||
// Declare iovec early to avoid goto crossing initialization
|
||||
struct iovec iov;
|
||||
int ret;
|
||||
|
||||
/** EXPLANATION:
|
||||
* Initialize io_uring ring - allocate SQEs and CQEs for one frame assembly
|
||||
* One SQE per slot (one datagram per slot), plus one extra for cancel
|
||||
* operations, since io_uring_prep_cancel() requires a valid SQE. So we
|
||||
* alloc 1 extra SQE to guarantee that we will always have an available SQE
|
||||
* for cancel operations.
|
||||
*/
|
||||
ret = io_uring_queue_init(
|
||||
static_cast<unsigned int>(frameAssemblyDesc->numSlots + 1), &ring, 0);
|
||||
if (ret < 0)
|
||||
{ goto cleanup; }
|
||||
|
||||
// Register staging buffer with io_uring for DMA-apt I/O
|
||||
iov = parent.assemblyBuffer.getIoUringRegisterIoVec();
|
||||
ret = io_uring_register_buffers(&ring, &iov, 1);
|
||||
if (ret < 0)
|
||||
{ goto cleanup_ring; }
|
||||
|
||||
// Create eventfd for CQE notifications (used with boost's unified loop)
|
||||
eventfdFd = eventfd(0, EFD_NONBLOCK);
|
||||
if (eventfdFd < 0)
|
||||
{ goto cleanup_buffers; }
|
||||
|
||||
// Register eventfd with io_uring
|
||||
ret = io_uring_register_eventfd(&ring, eventfdFd);
|
||||
if (ret < 0)
|
||||
{ goto cleanup_eventfd; }
|
||||
|
||||
isSetup = true;
|
||||
return true;
|
||||
|
||||
cleanup_eventfd:
|
||||
close(eventfdFd);
|
||||
eventfdFd = -1;
|
||||
cleanup_buffers:
|
||||
io_uring_unregister_buffers(&ring);
|
||||
cleanup_ring:
|
||||
io_uring_queue_exit(&ring);
|
||||
cleanup:
|
||||
return false;
|
||||
}
|
||||
|
||||
void IoUringAssemblyEngine::finalize()
|
||||
{
|
||||
// Call stop() to cancel in-flight operations (stop() already cancels the timer)
|
||||
stop();
|
||||
|
||||
if (eventfdFd >= 0)
|
||||
{
|
||||
io_uring_unregister_eventfd(&ring);
|
||||
close(eventfdFd);
|
||||
eventfdFd = -1;
|
||||
}
|
||||
|
||||
if (isSetup)
|
||||
{
|
||||
io_uring_unregister_buffers(&ring);
|
||||
io_uring_queue_exit(&ring);
|
||||
isSetup = false;
|
||||
}
|
||||
|
||||
// Reset state to allow setup() to be called again
|
||||
frameAssemblyDesc = nullptr;
|
||||
}
|
||||
|
||||
void IoUringAssemblyEngine::resetAndAssembleFrame(
|
||||
resetAndAssembleFrameCbFn onCqeReady)
|
||||
{
|
||||
if (!onCqeReady)
|
||||
{
|
||||
throw std::runtime_error(std::string(__func__)
|
||||
+ ": onCqeReady callback is invalid");
|
||||
}
|
||||
|
||||
if (!frameAssemblyDesc || !pcloudDataFdDesc || eventfdFd < 0)
|
||||
{
|
||||
throw std::runtime_error(std::string(__func__)
|
||||
+ ": invalid state: "
|
||||
+ ( !frameAssemblyDesc ? "frameAssemblyDesc is null; " : "" )
|
||||
+ ( !pcloudDataFdDesc ? "pcloudDataFdDesc is null; " : "" )
|
||||
+ ( eventfdFd < 0 ? "eventfdFd is invalid." : "" ));
|
||||
}
|
||||
|
||||
// eventfdDesc should not be valid when resetAndAssembleFrame is called
|
||||
if (eventfdDesc)
|
||||
{
|
||||
throw std::runtime_error(std::string(__func__)
|
||||
+ ": eventfdDesc is already set");
|
||||
}
|
||||
|
||||
// Store the callback for re-arming
|
||||
onCqeReadyCallback = std::move(onCqeReady);
|
||||
|
||||
/** EXPLANATION:
|
||||
* Flush eventfd state: poll and read any pending events before creating
|
||||
* descriptor.
|
||||
* Use poll() to check if data is available (non-blocking check).
|
||||
* If data is available, read it to flush.
|
||||
*/
|
||||
struct pollfd pfd;
|
||||
pfd.fd = eventfdFd;
|
||||
pfd.events = POLLIN;
|
||||
pfd.revents = 0;
|
||||
int poll_ret = poll(&pfd, 1, 0); // Timeout 0 = non-blocking
|
||||
if (poll_ret > 0 && (pfd.revents & POLLIN))
|
||||
{
|
||||
uint64_t discard;
|
||||
ssize_t ret = read(eventfdFd, &discard, sizeof(discard));
|
||||
(void)ret; // Ignore errors - just trying to flush
|
||||
}
|
||||
|
||||
eventfdDesc = std::make_unique<boost::asio::posix::stream_descriptor>(
|
||||
parent.device->componentThread->getIoService(), eventfdFd);
|
||||
|
||||
if (!eventfdDesc)
|
||||
{
|
||||
throw std::runtime_error(std::string(__func__)
|
||||
+ ": failed to create eventfd stream descriptor");
|
||||
}
|
||||
|
||||
// Get UDP socket file descriptor
|
||||
int udpFd = pcloudDataFdDesc->native_handle();
|
||||
if (udpFd < 0)
|
||||
{
|
||||
throw std::runtime_error(std::string(__func__)
|
||||
+ ": invalid UDP socket file descriptor");
|
||||
}
|
||||
|
||||
// Prepare SQEs for each slot in the frame
|
||||
struct io_uring_sqe *sqe;
|
||||
for (size_t i = 0; i < frameAssemblyDesc->numSlots; ++i)
|
||||
{
|
||||
sqe = io_uring_get_sqe(&ring);
|
||||
if (!sqe)
|
||||
{
|
||||
throw std::runtime_error(std::string(__func__)
|
||||
+ ": failed to get SQE for slot " + std::to_string(i));
|
||||
}
|
||||
|
||||
const auto& slot = frameAssemblyDesc->slots[i];
|
||||
|
||||
// Prepare recvmsg SQE for this slot
|
||||
struct msghdr msg = {};
|
||||
struct iovec iov;
|
||||
iov.iov_base = slot.vaddr;
|
||||
iov.iov_len = slot.nBytes;
|
||||
msg.msg_iov = &iov;
|
||||
msg.msg_iovlen = 1;
|
||||
|
||||
io_uring_prep_recvmsg(sqe, udpFd, &msg, 0);
|
||||
// Set user_data to slot index for tracking
|
||||
io_uring_sqe_set_data(sqe, reinterpret_cast<void*>(i));
|
||||
}
|
||||
|
||||
// Submit all SQEs
|
||||
int ret = io_uring_submit(&ring);
|
||||
if (ret < 0)
|
||||
{
|
||||
throw std::runtime_error(std::string(__func__)
|
||||
+ ": io_uring_submit failed: " + std::strerror(errno)
|
||||
+ " (errno=" + std::to_string(errno) + ")");
|
||||
}
|
||||
|
||||
// Set assembly flag
|
||||
isAssembling = true;
|
||||
// Start listening for CQE notifications on eventfd
|
||||
eventfdDesc->async_read_some(
|
||||
boost::asio::buffer(&eventfd_value, sizeof(eventfd_value)),
|
||||
std::bind(
|
||||
&IoUringAssemblyEngine::onEventfdRead, this,
|
||||
std::placeholders::_1,
|
||||
std::placeholders::_2));
|
||||
}
|
||||
|
||||
void IoUringAssemblyEngine::stop(bool doAcquireLock)
|
||||
{
|
||||
// Clear assembly flag first to signal onEventfdRead to stop re-arming
|
||||
// Acquire and release lock tightly around setting the flag
|
||||
if (doAcquireLock)
|
||||
{
|
||||
SpinLock::Guard lock(isAssemblingLock);
|
||||
isAssembling = false;
|
||||
} else {
|
||||
isAssembling = false;
|
||||
}
|
||||
|
||||
/** FIXME:
|
||||
* There's a problem with this bridge here.
|
||||
*
|
||||
* We can't delay during every call to stop because under normal operating
|
||||
* conditions, this whole assembly process should be able to move as fast
|
||||
* as possible and to receive as much data as possible without maximum
|
||||
* throughput.
|
||||
*
|
||||
* Yet we need to delay briefly here to ensure that the onEventfdRead loop
|
||||
* has a chance to see the flag and halt.
|
||||
*
|
||||
* We need to analyze this carefully and figure out what the correct
|
||||
* conditions are for being certain that we aren't destroying state while
|
||||
* the eventfdRead loop is still running; and we need to figure out how to
|
||||
* ensure that we only delay when absolutely necessary.
|
||||
*/
|
||||
|
||||
// Cancel in-flight stall timeout timer
|
||||
stallTimer.cancel();
|
||||
onCqeReadyCallback = std::move([](void *, int){});
|
||||
|
||||
if (isSetup)
|
||||
{
|
||||
struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
|
||||
if (!sqe)
|
||||
{
|
||||
std::cerr << __func__ << ": failed to get SQE for cancel op. "
|
||||
<< "Continuing cleanup without cancelling.\n";
|
||||
|
||||
goto cleanup_eventfd;
|
||||
}
|
||||
|
||||
/* Cancel all in-flight operations on our ring
|
||||
* using IORING_ASYNC_CANCEL_ANY. Identify the CQE for the cancel
|
||||
* op as numSlots since numSlots is an invalid slot index for a
|
||||
* real slot.
|
||||
*/
|
||||
io_uring_prep_cancel(
|
||||
sqe, reinterpret_cast<void*>(frameAssemblyDesc->numSlots),
|
||||
IORING_ASYNC_CANCEL_ANY);
|
||||
|
||||
io_uring_submit(&ring);
|
||||
|
||||
/* Wait for cancellation to complete. According to the man page,
|
||||
* cancellation is synchronous and a CQE is guaranteed to be
|
||||
* generated by the time submission returns.
|
||||
*/
|
||||
struct io_uring_cqe *cqe;
|
||||
bool sawCancelCqe = false;
|
||||
while (io_uring_peek_cqe(&ring, &cqe) == 0)
|
||||
{
|
||||
// Call seen() on all CQEs for completeness/correctness.
|
||||
io_uring_cqe_seen(&ring, cqe);
|
||||
void *user_data = io_uring_cqe_get_data(cqe);
|
||||
if (user_data == reinterpret_cast<void*>(
|
||||
frameAssemblyDesc->numSlots))
|
||||
{
|
||||
sawCancelCqe = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!sawCancelCqe && OptionParser::getOptions().verbose) {
|
||||
std::cerr << __func__ << ": no CQE seen for cancel operation\n";
|
||||
}
|
||||
}
|
||||
|
||||
cleanup_eventfd:
|
||||
if (eventfdDesc)
|
||||
{
|
||||
/** EXPLANATION:
|
||||
* The goal here is to ensure that our io_service's event loop will not
|
||||
* get any events from the eventfd after we've called stop(). So we
|
||||
* completely deinitialize the eventfd descriptor.
|
||||
*
|
||||
* But we still want to reuse the underlying eventfd file descriptor,
|
||||
* itself in the next resetAndAssembleFrame() cycle, so we call
|
||||
* release() instead of reset() to ensure that the underlying fd
|
||||
* is not closed.
|
||||
*
|
||||
* However, we need to close the descriptor's association with the
|
||||
* io_service before releasing it, otherwise Boost.Asio will complain
|
||||
* when we try to create a new descriptor with the same fd.
|
||||
*/
|
||||
eventfdDesc->cancel();
|
||||
eventfdDesc->release();
|
||||
/* Destroy the descriptor object (now that it's unregistered, destroying
|
||||
* it won't close the fd since release() transferred ownership back)
|
||||
*/
|
||||
eventfdDesc.reset();
|
||||
}
|
||||
}
|
||||
|
||||
// Continuation class for assembleFrameReq
|
||||
class IoUringAssemblyEngine::AssembleFrameReq
|
||||
: public PostedAsynchronousContinuation<
|
||||
IoUringAssemblyEngine::assembleFrameReqCbFn>
|
||||
{
|
||||
public:
|
||||
AssembleFrameReq(
|
||||
IoUringAssemblyEngine& engine_,
|
||||
const std::shared_ptr<ComponentThread>& caller,
|
||||
Callback<IoUringAssemblyEngine::assembleFrameReqCbFn> cb)
|
||||
: PostedAsynchronousContinuation<
|
||||
IoUringAssemblyEngine::assembleFrameReqCbFn>(caller, cb),
|
||||
engine(engine_),
|
||||
loop(engine_.frameAssemblyDesc->numSlots),
|
||||
timerFired(false), handlerExecuted(false)
|
||||
{}
|
||||
|
||||
public:
|
||||
void assembleFrameReq1_posted(
|
||||
std::shared_ptr<AssembleFrameReq> context)
|
||||
{
|
||||
if (!engine.frameAssemblyDesc)
|
||||
{
|
||||
throw std::runtime_error(std::string(__func__)
|
||||
+ ": frameAssemblyDesc is null");
|
||||
}
|
||||
|
||||
// Initialize loop with number of slots
|
||||
context->loop = AsynchronousLoop(engine.frameAssemblyDesc->numSlots);
|
||||
|
||||
/** FIXME:
|
||||
* I'm suspicious of this std::bind return object here. What if us
|
||||
* setting it to null inside of stop() doesn't actually cause the
|
||||
* object to be destroyed? This would cause this contin's sh_ptr's
|
||||
* reference count to never reach 0, causing a memory leak.
|
||||
*/
|
||||
engine.resetAndAssembleFrame(
|
||||
std::bind(&AssembleFrameReq::assembleFrameReq2_2,
|
||||
context.get(), context,
|
||||
std::placeholders::_1, std::placeholders::_2));
|
||||
|
||||
// Set up timeout timer for CONFIG_STIMBUFF_FRAME_PERIOD_MS/2 ms
|
||||
engine.stallTimer.expires_from_now(
|
||||
boost::posix_time::milliseconds(
|
||||
CONFIG_STIMBUFF_FRAME_PERIOD_MS / 2));
|
||||
engine.stallTimer.async_wait(
|
||||
std::bind(&AssembleFrameReq::assembleFrameReq2_1,
|
||||
context.get(), context,
|
||||
std::placeholders::_1));
|
||||
}
|
||||
|
||||
void assembleFrameReq2_1(
|
||||
std::shared_ptr<AssembleFrameReq> context,
|
||||
const boost::system::error_code& error)
|
||||
{
|
||||
// Check if timer was cancelled (ignore if operation_aborted)
|
||||
if (error == boost::asio::error::operation_aborted) { return; }
|
||||
|
||||
// Set timer fired flag
|
||||
context->timerFired.store(true);
|
||||
context->assembleFrameReq3(context);
|
||||
}
|
||||
|
||||
void assembleFrameReq2_2(
|
||||
std::shared_ptr<AssembleFrameReq> context,
|
||||
void *user_data, int cqe_result)
|
||||
{
|
||||
(void)user_data; // Not used - we just track success/failure counts
|
||||
|
||||
// Caller decides success: result >= 0 means success
|
||||
bool success = (cqe_result >= 0);
|
||||
if (context->loop.incrementSuccessOrFailureAndTestForCompletionDueTo(
|
||||
success))
|
||||
{
|
||||
// Loop is complete, call oracle function
|
||||
context->assembleFrameReq3(context);
|
||||
}
|
||||
}
|
||||
|
||||
void assembleFrameReq3(std::shared_ptr<AssembleFrameReq> context)
|
||||
{
|
||||
// Ensure we only execute once using atomic exchange
|
||||
if (context->handlerExecuted.exchange(true)) { return; }
|
||||
// Cancel the timer, stop the engine and process frame, if any.
|
||||
context->engine.stop(false);
|
||||
|
||||
/** EXPLANATION:
|
||||
* Timeout doesn't necessarily mean error.
|
||||
*
|
||||
* If we received zero dgrams from the device, that is indeed an error.
|
||||
* But if we received some dgrams, but not all, that is not an error:
|
||||
* it just means we didn't receive as much data as we would have liked.
|
||||
*/
|
||||
|
||||
// Error: no slots succeeded - no data received successfully.
|
||||
if (context->loop.nSucceeded.load() == 0)
|
||||
{
|
||||
context->callOriginalCb(false, context->loop);
|
||||
return;
|
||||
}
|
||||
|
||||
if (context->loop.nSucceeded.load() >= context->loop.nTotal)
|
||||
{
|
||||
// Success: all or more slots succeeded
|
||||
if (context->loop.nSucceeded.load() > context->loop.nTotal)
|
||||
{
|
||||
std::cerr << __func__ << ": nSucceeded > nTotal: succ ("
|
||||
<< context->loop.nSucceeded.load()
|
||||
<< ") > nTotal (" << context->loop.nTotal << ")\n";
|
||||
}
|
||||
|
||||
context->callOriginalCb(true, context->loop);
|
||||
return;
|
||||
}
|
||||
|
||||
if (context->loop.nSucceeded.load() < context->loop.nTotal)
|
||||
{
|
||||
// Success: some slots succeeded (less than total)
|
||||
// Note: dummy fill for un-assembled slots will be implemented later
|
||||
context->callOriginalCb(true, context->loop);
|
||||
return;
|
||||
}
|
||||
|
||||
if (OptionParser::getOptions().verbose)
|
||||
{
|
||||
std::cerr << __func__ << ": Invalid state: nSucceeded ("
|
||||
<< context->loop.nSucceeded.load()
|
||||
<< ") < nTotal (" << context->loop.nTotal << ")" << std::endl;
|
||||
}
|
||||
|
||||
context->callOriginalCb(false, context->loop);
|
||||
return;
|
||||
}
|
||||
|
||||
public:
|
||||
IoUringAssemblyEngine& engine;
|
||||
AsynchronousLoop loop;
|
||||
std::atomic<bool> timerFired;
|
||||
std::atomic<bool> handlerExecuted;
|
||||
};
|
||||
|
||||
void IoUringAssemblyEngine::assembleFrameReq(
|
||||
Callback<assembleFrameReqCbFn> cb)
|
||||
{
|
||||
if (!frameAssemblyDesc)
|
||||
{
|
||||
throw std::runtime_error(std::string(__func__)
|
||||
+ ": frameAssemblyDesc is null");
|
||||
}
|
||||
|
||||
const auto& caller = smoHooksPtr->ComponentThread_getSelf();
|
||||
auto request = std::make_shared<AssembleFrameReq>(
|
||||
*this, caller, std::move(cb));
|
||||
|
||||
parent.device->componentThread->getIoService().post(
|
||||
STC(std::bind(
|
||||
&AssembleFrameReq::assembleFrameReq1_posted,
|
||||
request.get(), request)));
|
||||
}
|
||||
|
||||
void IoUringAssemblyEngine::onEventfdRead(
|
||||
const boost::system::error_code& error,
|
||||
std::size_t bytes_transferred)
|
||||
{
|
||||
(void)bytes_transferred;
|
||||
|
||||
// Ignore cancellation errors
|
||||
if (error == boost::asio::error::operation_aborted) { return; }
|
||||
|
||||
/** EXPLANATION:
|
||||
* This lock should be held throughout this method to ensure that the
|
||||
* IoUringAssemblyEngine's per-assembly state isn't destroyed while this
|
||||
* handler is running.
|
||||
*/
|
||||
SpinLock::Guard lock(isAssemblingLock);
|
||||
if (!isAssembling) { return; }
|
||||
|
||||
/** FIXME:
|
||||
* It may be necessary to specifically check for and handle the cancel op
|
||||
* CQE here. I'm not sure as yet though, but I'll highlight it here for now.
|
||||
*/
|
||||
|
||||
// Process all available CQEs and call callback for each one
|
||||
struct io_uring_cqe *cqe;
|
||||
while (io_uring_peek_cqe(&ring, &cqe) == 0)
|
||||
{
|
||||
// Get user_data from the CQE
|
||||
void* user_data = io_uring_cqe_get_data(cqe);
|
||||
// Get result from the CQE
|
||||
int cqe_result = cqe->res;
|
||||
// Mark the CQE as seen
|
||||
io_uring_cqe_seen(&ring, cqe);
|
||||
|
||||
/** EXPLANATION:
|
||||
* Call the user-provided callback for this CQE with its user_data and
|
||||
* result.
|
||||
*
|
||||
* 1. Notice that we call the caller's cb *after* marking the CQE as
|
||||
* seen. We may later need to change this if the caller needs
|
||||
* information from the CQE before it is marked as seen.
|
||||
*
|
||||
* 2. Notice that we do not check for or filter out the cancel op CQE
|
||||
* here. The caller's handler will be able to see the cancel op CQE
|
||||
* because of this.
|
||||
*/
|
||||
if (onCqeReadyCallback) {
|
||||
onCqeReadyCallback(user_data, cqe_result);
|
||||
}
|
||||
}
|
||||
|
||||
// Re-arm the eventfd read for next CQE notification
|
||||
// Only re-arm if assembly is still active (stop() hasn't been called)
|
||||
if (eventfdDesc && eventfdFd >= 0)
|
||||
{
|
||||
eventfdDesc->async_read_some(
|
||||
boost::asio::buffer(&eventfd_value, sizeof(eventfd_value)),
|
||||
std::bind(
|
||||
&IoUringAssemblyEngine::onEventfdRead, this,
|
||||
std::placeholders::_1,
|
||||
std::placeholders::_2));
|
||||
}
|
||||
}
|
||||
|
||||
void IoUringAssemblyEngine::cancelIncompleteAndFillDummies()
|
||||
{
|
||||
if (!frameAssemblyDesc)
|
||||
{ return; }
|
||||
|
||||
for (size_t i = 0; i < frameAssemblyDesc->numSlots; ++i)
|
||||
{
|
||||
// In the real path, decide from CQE accounting whether slot i completed.
|
||||
// Here, demonstrate dummy header insertion API.
|
||||
auto* hdr = reinterpret_cast<DummyLivoxEthHeader*>(frameAssemblyDesc->slots[i].vaddr);
|
||||
hdr->err_code = DummyLivoxEthHeader::INVALID_ERR_CODE;
|
||||
hdr->timestamp_type = DummyLivoxEthHeader::INVALID_TIMESTAMP_TYPE;
|
||||
hdr->data_type = DummyLivoxEthHeader::INVALID_DATA_TYPE;
|
||||
}
|
||||
}
|
||||
|
||||
size_t IoUringAssemblyEngine::computePointsPerDgram(int returnMode)
|
||||
{
|
||||
/*
|
||||
* Map modes to points per datagram based on Livox docs
|
||||
* 1: first, 2: strongest -> 96 samples => 96 points
|
||||
* 3: dual -> 48 samples * 2 points = 96
|
||||
* 4: triple -> 30 samples * 3 points = 90
|
||||
*/
|
||||
switch (returnMode)
|
||||
{
|
||||
case static_cast<int>(livoxProto1::Device::ReturnMode::SingleFirst):
|
||||
case static_cast<int>(livoxProto1::Device::ReturnMode::SingleStrongest):
|
||||
case static_cast<int>(livoxProto1::Device::ReturnMode::Dual):
|
||||
return 96u;
|
||||
case static_cast<int>(livoxProto1::Device::ReturnMode::Triple):
|
||||
return 90u;
|
||||
default:
|
||||
throw std::runtime_error(
|
||||
std::string(__func__) + ": Unknown returnMode "
|
||||
+ std::to_string(returnMode));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace stim_buff
|
||||
} // namespace smo
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user