删除多余代码

wangjiaqi23
Surponess 1 week ago
parent a5ba3337fd
commit 5afa9204b5

@ -1,21 +0,0 @@
# Python
__pycache__/
*.pyc
*.pyo
*.egg-info/
venv/
# Database
zhitu.db
*.db
# Audio data (large files)
*.wav
# Generated results
results/
# IDE
.idea/
.vscode/
*.iml

@ -1,2 +0,0 @@
# software

@ -1,11 +0,0 @@
{
"created_time": "2026-04-20T02:11:45Z",
"files": [
],
"folders": [
],
"id": "1542",
"modified_time": "2026-04-20T02:11:45Z",
"signature": "4031563627892149089",
"version": 3
}

@ -1,4 +0,0 @@
# Mark binary files
*.onnx binary
*.bin binary
*.wav binary

@ -1,42 +0,0 @@
# Build artifacts
build/
build/*.o
build/*.obj
build/*.exe
CMakeCache.txt
CMakeFiles/
cmake-build-*/
Makefile
# IDE
.vs/
.vscode/
*.user
*.suo
*.sln
*.vcxproj*
# Python cache
scripts/__pycache__/
*.pyc
*.pyo
__pycache__/
# Temporary files
*.tmp
*.log
*.swp
*.bak
# OS
.DS_Store
Thumbs.db
# Model training artifacts
checkpoints/
*.pth
runs/
# Output files
output/*.wav
output/*.txt

@ -1,29 +0,0 @@
# Changelog
## [Unreleased]
### Added
- 完整的单元测试套件:`test_core_lib.exe`音频缓冲区、预加重、GCC-PHAT、距离估计、威胁跟踪、特征提取器形状验证
- `run_demo.bat` 一键运行脚本:自动构建并执行核心测试 + ONNX 推理 + 离线演示
- `build_core_test.bat`:单独编译所有测试可执行文件
- JSON 格式 label map 支持(兼容旧版 `"index: label"` 文本格式)
- `.gitattributes`:统一行尾符和 diff 行为
### Changed
- **FFT 引擎**:移除损坏的 `kiss_fft` 依赖,替换为内联 Cooley-Tukey FFT推理速度提升约 33×
- **ONNX Runtime 兼容**:将 API 版本降级至 17 以匹配本地 v1.17.1 DLL
- **输入张量形状**:调整为 `[batch=1, time=63, n_mels=64]` 以匹配 ONNX 导出模型
- **AudioBuffer API**:统一为 `Push`/`Pop`/`Get`/`Clear`/`Size`
- **DistanceEstimator**`GetReferenceSpl("threat")` 使用配置值(默认 150 dB替代硬编码 160 dB
- **代码风格**:统一所有 `size_t``std::size_t`
- **CMakeLists.txt**清理多余空行install 规则仅在 `BUILD_ROS_WRAPPER=ON` 时生效
- **`.gitignore`**:扩展为更全面的忽略规则
### Fixed
- 修复 `AudioBuffer`、`FeatureExtractor`、`DistanceEstimator` 的头文件/实现不匹配问题
- 修复 Windows 路径 Unicode 转换(`MultiByteToWideChar`
- 修复缺失的 `<memory>` 头文件包含
- 修复 `pipeline.cpp` 和 ROS 包装器以使用 `Pipeline::Process` 返回 `AcousticFrame`
### Removed
- `kiss_fft` 第三方库(头文件和实现均缺失,无法编译)

@ -1,221 +0,0 @@
cmake_minimum_required(VERSION 3.10)
project(acoustic_analyzer)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Windows / MinGW fixes
if(WIN32)
add_compile_definitions(_USE_MATH_DEFINES)
endif()
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU")
add_compile_options("-Wa,-mbig-obj")
endif()
option(BUILD_TESTS "Build unit tests" ON)
option(BUILD_ROS_WRAPPER "Build ROS wrapper node" OFF)
option(BUILD_ONNX_TESTS "Build ONNX classifier tests" ON)
# Eigen3 (bundled)
set(EIGEN3_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/third_party/eigen-3.4.0")
if(NOT EXISTS "${EIGEN3_INCLUDE_DIR}/Eigen/Core")
message(FATAL_ERROR "Eigen3 not found at ${EIGEN3_INCLUDE_DIR}")
endif()
# yaml-cpp (optional)
set(YAML_CPP_ROOT "")
if(WIN32)
set(_conda_yaml "C:/Users/$ENV{USERNAME}/miniconda3/Library")
if(EXISTS "${_conda_yaml}/include/yaml-cpp/yaml.h")
set(YAML_CPP_ROOT "${_conda_yaml}")
message(STATUS "Found yaml-cpp in conda: ${YAML_CPP_ROOT}")
endif()
endif()
set(YAML_CPP_FOUND FALSE)
find_package(yaml-cpp QUIET)
if(yaml-cpp_FOUND AND TARGET yaml-cpp)
set(YAML_CPP_TARGET yaml-cpp)
set(YAML_CPP_FOUND TRUE)
if(NOT yaml-cpp_INCLUDE_DIRS AND DEFINED YAML_CPP_INCLUDE_DIR)
set(yaml-cpp_INCLUDE_DIRS "${YAML_CPP_INCLUDE_DIR}")
endif()
elseif(yaml-cpp_FOUND AND DEFINED YAML_CPP_INCLUDE_DIR)
set(YAML_CPP_TARGET yaml-cpp)
set(YAML_CPP_FOUND TRUE)
add_library(yaml-cpp UNKNOWN IMPORTED)
set_target_properties(yaml-cpp PROPERTIES
IMPORTED_LOCATION "${YAML_CPP_LIBRARY_DIR}/yaml-cpp.lib"
INTERFACE_INCLUDE_DIRECTORIES "${YAML_CPP_INCLUDE_DIR}"
)
if(NOT yaml-cpp_INCLUDE_DIRS)
set(yaml-cpp_INCLUDE_DIRS "${YAML_CPP_INCLUDE_DIR}")
endif()
elseif(YAML_CPP_ROOT)
set(yaml-cpp_INCLUDE_DIRS "${YAML_CPP_ROOT}/include")
set(yaml-cpp_LIBRARIES "${YAML_CPP_ROOT}/lib/yaml-cpp.lib")
if(EXISTS "${yaml-cpp_LIBRARIES}")
add_library(yaml-cpp UNKNOWN IMPORTED)
set_target_properties(yaml-cpp PROPERTIES
IMPORTED_LOCATION "${yaml-cpp_LIBRARIES}"
INTERFACE_INCLUDE_DIRECTORIES "${yaml-cpp_INCLUDE_DIRS}"
)
set(YAML_CPP_TARGET yaml-cpp)
set(YAML_CPP_FOUND TRUE)
endif()
endif()
if(YAML_CPP_FOUND)
message(STATUS "yaml-cpp found. Pipeline will be built.")
else()
message(WARNING "yaml-cpp not found. Pipeline and ROS wrapper will be disabled.")
endif()
# ONNX Runtime
set(ONNXRuntime_DIR "${CMAKE_CURRENT_SOURCE_DIR}/third_party/onnxruntime" CACHE PATH "ONNX Runtime root")
set(ONNXRuntime_INCLUDE_DIRS "${ONNXRuntime_DIR}/include")
if(WIN32)
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU")
set(ONNXRuntime_LIB_CANDIDATE "${ONNXRuntime_DIR}/lib/libonnxruntime.a")
else()
set(ONNXRuntime_LIB_CANDIDATE "${ONNXRuntime_DIR}/lib/onnxruntime.lib")
endif()
else()
set(ONNXRuntime_LIB_CANDIDATE "${ONNXRuntime_DIR}/lib/libonnxruntime.so")
endif()
if(EXISTS "${ONNXRuntime_LIB_CANDIDATE}")
set(ONNXRuntime_LIBS "${ONNXRuntime_LIB_CANDIDATE}")
set(ONNXRuntime_FOUND TRUE)
message(STATUS "ONNX Runtime found: ${ONNXRuntime_LIBS}")
else()
set(ONNXRuntime_FOUND FALSE)
message(WARNING "ONNX Runtime NOT found. ONNX tests disabled.")
endif()
# Core library sources
set(CORE_BASE_SOURCES
src/core/fft_utils.cpp
src/core/audio_buffer.cpp
src/core/feature_extractor.cpp
src/core/gcc_phat_localizer.cpp
src/core/distance_estimator.cpp
src/core/threat_tracker.cpp
)
set(CORE_ONNX_SOURCES
src/core/gunshot_classifier.cpp
)
if(YAML_CPP_FOUND)
list(APPEND CORE_ONNX_SOURCES src/core/pipeline.cpp)
endif()
set(IO_SOURCES
src/io/wav_file_source.cpp
)
if(NOT WIN32)
list(APPEND IO_SOURCES src/io/mobile_phone_source.cpp)
endif()
# Build base core library
add_library(${PROJECT_NAME}_core_base STATIC ${CORE_BASE_SOURCES})
target_include_directories(${PROJECT_NAME}_core_base PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
${EIGEN3_INCLUDE_DIR}
)
# Full core library
if(ONNXRuntime_FOUND)
add_library(${PROJECT_NAME}_core STATIC ${CORE_BASE_SOURCES} ${CORE_ONNX_SOURCES} ${IO_SOURCES})
target_include_directories(${PROJECT_NAME}_core PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
${ONNXRuntime_INCLUDE_DIRS}
${EIGEN3_INCLUDE_DIR}
)
if(YAML_CPP_FOUND)
target_include_directories(${PROJECT_NAME}_core PUBLIC ${yaml-cpp_INCLUDE_DIRS})
target_link_libraries(${PROJECT_NAME}_core PUBLIC ${YAML_CPP_TARGET})
endif()
target_link_libraries(${PROJECT_NAME}_core PUBLIC ${ONNXRuntime_LIBS})
if(NOT WIN32)
target_link_libraries(${PROJECT_NAME}_core PUBLIC m)
endif()
else()
add_library(${PROJECT_NAME}_core ALIAS ${PROJECT_NAME}_core_base)
endif()
# ROS Wrapper
if(BUILD_ROS_WRAPPER)
find_package(catkin REQUIRED COMPONENTS
roscpp
std_msgs
sensor_msgs
geometry_msgs
message_generation
)
add_message_files(FILES AcousticThreat.msg AcousticThreatArray.msg)
generate_messages(DEPENDENCIES std_msgs)
catkin_package(CATKIN_DEPENDS roscpp std_msgs sensor_msgs message_runtime)
add_executable(${PROJECT_NAME}_node
src/ros/acoustic_node.cpp
src/ros/threat_publisher.cpp
src/main.cpp
)
target_include_directories(${PROJECT_NAME}_node PRIVATE ${catkin_INCLUDE_DIRS})
target_link_libraries(${PROJECT_NAME}_node ${PROJECT_NAME}_core ${catkin_LIBRARIES})
add_dependencies(${PROJECT_NAME}_node ${PROJECT_NAME}_generate_messages_cpp)
endif()
# Unit tests
if(BUILD_TESTS)
enable_testing()
add_executable(test_core_lib tests/test_core_lib.cpp ${CORE_BASE_SOURCES})
target_include_directories(test_core_lib PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/include ${EIGEN3_INCLUDE_DIR})
if(NOT WIN32)
target_link_libraries(test_core_lib PRIVATE m)
endif()
add_test(NAME core_lib COMMAND test_core_lib)
add_executable(extract_mel_cpp tests/extract_mel_cpp.cpp
src/core/fft_utils.cpp src/core/feature_extractor.cpp)
target_include_directories(extract_mel_cpp PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/include ${EIGEN3_INCLUDE_DIR})
if(NOT WIN32)
target_link_libraries(extract_mel_cpp PRIVATE m)
endif()
if(ONNXRuntime_FOUND AND BUILD_ONNX_TESTS)
add_executable(test_classifier_cpp tests/test_classifier_cpp.cpp
src/core/fft_utils.cpp src/core/feature_extractor.cpp src/core/gunshot_classifier.cpp)
target_include_directories(test_classifier_cpp PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/include ${EIGEN3_INCLUDE_DIR} ${ONNXRuntime_INCLUDE_DIRS})
target_compile_definitions(test_classifier_cpp PRIVATE _stdcall=)
target_link_libraries(test_classifier_cpp PRIVATE ${ONNXRuntime_LIBS})
if(NOT WIN32)
target_link_libraries(test_classifier_cpp PRIVATE m)
endif()
add_test(NAME classifier_cpp COMMAND test_classifier_cpp
--model ${CMAKE_CURRENT_SOURCE_DIR}/models/gunshot_classifier.onnx
--wav ${CMAKE_CURRENT_SOURCE_DIR}/dataset/binary/val/ambient/drone_noise_val_000.wav)
endif()
endif()
# Install
if(BUILD_ROS_WRAPPER AND TARGET ${PROJECT_NAME}_core)
install(TARGETS ${PROJECT_NAME}_core
ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)
install(DIRECTORY include/${PROJECT_NAME}/ DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION})
install(DIRECTORY config/ DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}/config)
install(DIRECTORY models/ DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}/models)
endif()

@ -1,131 +0,0 @@
# Acoustic Analyzer Module
无人机声学威胁检测模块基于深度学习CNN-GRU的枪声/爆炸声分类器,结合麦克风阵列 GCC-PHAT 定位与 SPL 衰减模型测距。
## 模块结构
```
acoustic/
├── include/acoustic_analyzer/ # 公共头文件
│ ├── core/ # 核心算法
│ │ ├── audio_buffer.h # 音频循环缓冲区
│ │ ├── distance_estimator.h # SPL 距离估计器
│ │ ├── feature_extractor.h # Mel 频谱图提取
│ │ ├── fft_utils.h # FFT 工具
│ │ ├── gcc_phat_localizer.h # GCC-PHAT 声源定位
│ │ ├── gunshot_classifier.h # ONNX 分类器
│ │ ├── pipeline.h # 完整处理流水线
│ │ ├── threat_tracker.h # 威胁跟踪/去重
│ │ └── types.h # 公共类型定义
│ ├── io/ # 音频输入源
│ │ ├── audio_source.h
│ │ ├── mobile_phone_source.h
│ │ └── wav_file_source.h # WAV 文件读取
│ └── ros/ # ROS 包装器
│ ├── acoustic_node.h
│ └── threat_publisher.h
├── src/ # 实现文件
│ ├── core/
│ ├── io/
│ └── ros/
├── tests/ # 测试程序
│ ├── demo_offline.cpp # 离线推理演示
│ ├── test_core_lib.cpp
│ ├── test_classifier_cpp.cpp
│ └── extract_mel_cpp.cpp
├── scripts/ # Python 训练/导出脚本
│ ├── train_binary_classifier.py
│ ├── export_onnx.py
│ ├── generate_dataset.py
│ └── verify_val_accuracy.py
├── models/ # 模型文件
│ ├── gunshot_classifier.onnx
│ ├── label_map.json
│ └── mel_filter_bank.bin
├── config/
│ └── acoustic_params.yaml # 流水线配置
├── third_party/ # 头文件级依赖
│ ├── eigen-3.4.0/ # 矩阵运算
│ └── onnxruntime/ # ONNX Runtime C++ API
├── CMakeLists.txt
├── build_demo.bat # Windows 一键编译
├── batch_test.bat # Windows 一键测试
└── package.xml # ROS 包描述
```
## 快速开始Windows + MinGW
### 1. 编译离线演示程序
```bat
build_demo.bat
```
### 2. 运行单文件测试
```bat
build\demo_offline.exe dataset\binary\val\ambient\drone_noise_val_000.wav
build\demo_offline.exe dataset\binary\val\threat\synth_threat_val_000.wav
```
### 3. 批量验证整个数据集
```bat
batch_test.bat
:: 或直接传目录(支持递归子目录)
build\demo_offline.exe dataset\binary\val
```
### 4. 使用自定义模型
```bat
build\demo_offline.exe some_audio.wav --model my_model.onnx --label_map my_labels.json
```
## 模型训练Python
```bash
# 1. 生成合成数据集(或准备真实音频)
cd scripts
python generate_dataset.py
# 2. 训练二分类器
python train_binary_classifier.py --data_dir ../dataset/binary --epochs 50
# 3. 导出 ONNX
python export_onnx.py --checkpoint checkpoints/best.pth --output ../models/gunshot_classifier.onnx
```
## ROS 集成Ubuntu
```bash
cd ~/catkin_ws/src
git clone <repo_url>
cd ~/catkin_ws
catkin_make -DCMAKE_BUILD_TYPE=Release
source devel/setup.bash
roslaunch acoustic_analyzer acoustic_node.launch
```
## 依赖
| 依赖 | 用途 | 状态 |
|------|------|------|
| Eigen 3.4.0 | 矩阵运算 | Bundled |
| ONNX Runtime 1.17+ | 模型推理 | Bundled headers + DLL/lib |
| yaml-cpp | 配置解析 | Ubuntu: `apt install libyaml-cpp-dev` |
| kiss_fft | ~~FFT~~ | **已移除**,使用内联实现 |
## 已知限制
- 当前分类器基于合成数据集训练,仅用于代码集成验证。实际部署需真实威胁音频重新训练。
- Windows 下完整流水线Pipeline 类)需要 yaml-cpp离线 demo 已绕过此依赖。
- 距离估计依赖 SPL 校准,合成音频的归一化幅度会导致距离值偏大。
## 更新日志
详见 [CHANGELOG.md](CHANGELOG.md)。
## 许可证
与父项目保持一致。

@ -1,32 +0,0 @@
@echo off
chcp 65001 >nul
setlocal EnableDelayedExpansion
echo ==========================================
echo Acoustic Analyzer Batch Validation Test
echo ==========================================
set DEMO=build\demo_offline.exe
if not exist %DEMO% (
echo [ERROR] demo_offline.exe not found. Run build_demo.bat first.
exit /b 1
)
echo.
echo [TEST 1] Validation set (all subdirectories)...
%DEMO% dataset\binary\val
if %ERRORLEVEL% NEQ 0 (
echo [FAIL] Validation test failed.
exit /b 1
)
echo.
echo [TEST 2] Single ambient file...
%DEMO% dataset\binary\val\ambient\drone_noise_val_000.wav
echo.
echo [TEST 3] Single threat file...
%DEMO% dataset\binary\val\threat\synth_threat_val_000.wav
echo.
echo [OK] All batch tests completed.
endlocal

@ -1,52 +0,0 @@
@echo off
chcp 65001 >nul
setlocal EnableDelayedExpansion
echo ==========================================
echo Acoustic Module - CMake MinGW Build
echo ==========================================
set SRC_ROOT=%~dp0
set TEMP_SRC=C:\temp\acoustic_src
set TEMP_BUILD=C:\temp\acoustic_build
set OUTPUT_DIR=%SRC_ROOT%build
REM --- Copy source to temp English path (workaround for Chinese path issues) ---
echo [INFO] Copying source to %TEMP_SRC% ...
if exist %TEMP_SRC% rmdir /s /q %TEMP_SRC%
if exist %TEMP_BUILD% rmdir /s /q %TEMP_BUILD%
mkdir %TEMP_SRC%
xcopy /s /e /i /q "%SRC_ROOT%*" "%TEMP_SRC%\" >nul
echo [INFO] Running CMake ...
mkdir %TEMP_BUILD%
cd /d %TEMP_BUILD%
cmake "%TEMP_SRC%" -G "MinGW Makefiles" ^
-DBUILD_TESTS=ON ^
-DBUILD_ONNX_TESTS=ON ^
-DBUILD_ROS_WRAPPER=OFF ^
-DCMAKE_BUILD_TYPE=Release
if %ERRORLEVEL% NEQ 0 (
echo [FAIL] CMake configuration failed.
exit /b 1
)
echo [INFO] Building with mingw32-make ...
mingw32-make -j4
if %ERRORLEVEL% NEQ 0 (
echo [FAIL] Build failed.
exit /b 1
)
REM --- Copy executables back ---
if not exist "%OUTPUT_DIR%" mkdir "%OUTPUT_DIR%"
for %%E in (test_core_lib.exe extract_mel_cpp.exe test_classifier_cpp.exe demo_offline.exe) do (
if exist "%TEMP_BUILD%\%%E" (
copy /y "%TEMP_BUILD%\%%E" "%OUTPUT_DIR%\" >nul
echo [OK] Copied %%E
)
)
echo.
echo [OK] CMake MinGW build complete. Executables in %OUTPUT_DIR%
endlocal

@ -1,70 +0,0 @@
@echo off
chcp 65001 >nul
setlocal EnableDelayedExpansion
echo ==========================================
echo Acoustic Core Library Test Build
echo ==========================================
set SRC_ROOT=%~dp0
set EIGEN=%SRC_ROOT%third_party\eigen-3.4.0
set ONNX_INC=%SRC_ROOT%third_party\onnxruntime\include
set INCLUDES=-I%SRC_ROOT%include -I%EIGEN%
set FLAGS=-std=c++17 -O2 -D_USE_MATH_DEFINES -Wa,-mbig-obj
if not exist build mkdir build
echo [1/3] Building test_core_lib.exe ...
g++ %FLAGS% %INCLUDES% ^
tests\test_core_lib.cpp ^
src\core\fft_utils.cpp ^
src\core\audio_buffer.cpp ^
src\core\feature_extractor.cpp ^
src\core\distance_estimator.cpp ^
src\core\gcc_phat_localizer.cpp ^
src\core\threat_tracker.cpp ^
src\io\wav_file_source.cpp ^
-o build\test_core_lib.exe ^
-D_stdcall=
if %ERRORLEVEL% NEQ 0 (
echo [FAIL] test_core_lib build failed.
exit /b 1
)
echo [2/3] Building extract_mel_cpp.exe ...
g++ %FLAGS% %INCLUDES% ^
tests\extract_mel_cpp.cpp ^
src\core\fft_utils.cpp ^
src\core\feature_extractor.cpp ^
src\io\wav_file_source.cpp ^
-o build\extract_mel_cpp.exe ^
-D_stdcall=
if %ERRORLEVEL% NEQ 0 (
echo [FAIL] extract_mel_cpp build failed.
exit /b 1
)
echo [3/3] Building test_classifier_cpp.exe ...
set ONNX_LIB=%SRC_ROOT%third_party\onnxruntime\lib\libonnxruntime.a
g++ %FLAGS% %INCLUDES% -I%ONNX_INC% ^
tests\test_classifier_cpp.cpp ^
src\core\fft_utils.cpp ^
src\core\feature_extractor.cpp ^
src\core\gunshot_classifier.cpp ^
src\io\wav_file_source.cpp ^
-o build\test_classifier_cpp.exe ^
%ONNX_LIB% -D_stdcall=
if %ERRORLEVEL% NEQ 0 (
echo [FAIL] test_classifier_cpp build failed.
exit /b 1
)
echo.
echo [OK] All test executables built successfully.
echo.
echo Run tests:
echo build\test_core_lib.exe
echo build\extract_mel_cpp.exe dataset\binary\val\ambient\xxx.wav
echo build\test_classifier_cpp.exe --model models\gunshot_classifier.onnx --wav dataset\binary\val\ambient\xxx.wav --label_map models\label_map.json
endlocal

@ -1,40 +0,0 @@
@echo off
chcp 65001 >nul
setlocal EnableDelayedExpansion
echo ==========================================
echo Acoustic Offline Demo Build (Windows)
echo ==========================================
set SRC_ROOT=%~dp0
set EIGEN=%SRC_ROOT%third_party\eigen-3.4.0
set ONNX_INC=%SRC_ROOT%third_party\onnxruntime\include
set ONNX_LIB=%SRC_ROOT%third_party\onnxruntime\lib\libonnxruntime.a
set INCLUDES=-I%SRC_ROOT%include -I%EIGEN% -I%ONNX_INC%
set FLAGS=-std=c++17 -O2 -D_USE_MATH_DEFINES -Wa,-mbig-obj
set LIBS=%ONNX_LIB%
if not exist build mkdir build
echo [1/1] Building demo_offline.exe (simplified, no yaml-cpp dependency) ...
g++ %FLAGS% %INCLUDES% ^
tests\demo_offline.cpp ^
src\core\fft_utils.cpp ^
src\core\feature_extractor.cpp ^
src\core\distance_estimator.cpp ^
src\core\gunshot_classifier.cpp ^
src\io\wav_file_source.cpp ^
-o build\demo_offline.exe ^
%LIBS% -D_stdcall=
if %ERRORLEVEL% NEQ 0 (
echo [FAIL] demo_offline build failed.
exit /b 1
)
echo.
echo [OK] demo_offline.exe built successfully in build\
echo.
echo Usage: build\demo_offline.exe dataset\binary\val\ambient\xxx.wav
echo build\demo_offline.exe dataset\binary\val\threat\xxx.wav
endlocal

@ -1,47 +0,0 @@
@echo off
chcp 65001 >nul
setlocal EnableDelayedExpansion
echo ==========================================
echo Acoustic Offline Multichannel Demo Build
echo ==========================================
set SRC_ROOT=%~dp0
set EIGEN=%SRC_ROOT%third_party\eigen-3.4.0
set ONNX_INC=%SRC_ROOT%third_party\onnxruntime\include
set ONNX_LIB=%SRC_ROOT%third_party\onnxruntime\lib\libonnxruntime.a
set INCLUDES=-I%SRC_ROOT%include -I%EIGEN% -I%ONNX_INC%
set FLAGS=-std=c++17 -O2 -D_USE_MATH_DEFINES -Wa,-mbig-obj
set LIBS=%ONNX_LIB%
if not exist build mkdir build
echo [1/1] Building demo_offline_multichannel.exe ...
g++ %FLAGS% %INCLUDES% ^
tests\demo_offline_multichannel.cpp ^
src\core\pipeline.cpp ^
src\core\audio_buffer.cpp ^
src\core\fft_utils.cpp ^
src\core\feature_extractor.cpp ^
src\core\gunshot_classifier.cpp ^
src\core\gcc_phat_localizer.cpp ^
src\core\distance_estimator.cpp ^
src\core\threat_tracker.cpp ^
src\io\wav_file_source.cpp ^
-o build\demo_offline_multichannel.exe ^
%LIBS% -D_stdcall=
if %ERRORLEVEL% NEQ 0 (
echo [FAIL] demo_offline_multichannel build failed.
exit /b 1
)
echo.
echo [OK] demo_offline_multichannel.exe built successfully in build\^
echo.
echo Usage: build\demo_offline_multichannel.exe dataset\multichannel_test.wav --num_mics 4 --layout cross
echo build\demo_offline_multichannel.exe dataset\real\threat\ --threshold 0.6 --num_mics 4
echo.
echo With ground-truth for error analysis:
echo build\demo_offline_multichannel.exe synth_90deg_100m.wav --ground_azimuth 90 --ground_distance 100
endlocal

@ -1,74 +0,0 @@
# 声源分析模块默认配置
# 本文件由 acoustic_node 通过 ros::param 或 Pipeline 直接加载
# ── 音频参数 ───────────────────────────────
audio:
sample_rate: 16000
chunk_duration: 2.0 # 分析窗口长度 (秒)
hop_duration: 0.5 # 步进 (秒)
n_channels: 4 # 麦克风数量
# ── 音频源选择 ──────────────────────────────
# [TEMP] 临时方案: 手机麦克风单通道测试
# [FINAL] 最终方案: 麦克风阵列多通道定位
# 可选值: "mic_array" (最终), "mobile_phone" (临时), "wav_file" (离线测试)
source:
type: "mic_array" # [FINAL] 最终方案4 通道麦克风阵列
# 临时方案参数 (手机音频)
mobile_phone_topic: "/mobile_phone/audio"
mobile_phone_timeout: 10.0 # 等待连接超时 (秒)
# 离线测试参数
wav_file_path: "" # 若 type="wav_file",填写 WAV 路径
# ── 麦克风阵列几何 (单位: 米) ──────────────
# layout 可选: cross, linear, circular, custom
# 使用 custom 时,需要填写 positions 数组 [x, y, z]
# [TEMP] 临时方案手机单通道num_mics=1, layout 任意
# [FINAL] 最终方案4 通道阵列num_mics=4, layout=cross
mic_array:
num_mics: 4 # [FINAL] 4 通道十字阵列
layout: "cross" # 单通道时此值被忽略
spacing: 0.15
positions:
- [0.0, 0.15, 0.0] # Mic 1 (前)
- [0.15, 0.0, 0.0] # Mic 2 (右)
- [0.0, -0.15, 0.0] # Mic 3 (后)
- [-0.15, 0.0, 0.0] # Mic 4 (左)
# ── 特征提取 ────────────────────────────────
features:
n_mels: 64
n_fft: 2048
hop_length: 512
f_min: 0.0
f_max: 8000.0
preemphasis: 0.97
# ── 分类器 ──────────────────────────────────
classifier:
model_path: "$(find acoustic_analyzer)/models/gunshot_classifier.onnx"
label_map_path: "$(find acoustic_analyzer)/models/label_map.json"
threshold: 0.7
smoothing_window: 3
# ── 定位 ────────────────────────────────────
localization:
algorithm: "gcc_phat"
max_tdoa: 0.00044 # 最大时延 (0.15m / 343m/s)
interpolation_factor: 4 # 互相关插值倍数
# ── 距离估计 ─────────────────────────────────
distance:
reference_spl:
threat: 150 # [FINAL] 二分类模型统一使用 threat 参考 SPL
gunshot: 150 # 兼容 4 类模型
artillery: 180
explosion: 170
attenuation_alpha: 0.6
kalman_process_noise: 0.01
kalman_measurement_noise: 5.0
# ── 输出 ────────────────────────────────────
output:
publish_rate: 10 # 最大输出频率 (Hz)
min_event_interval: 0.3 # 同一威胁最小上报间隔 (秒)

@ -1,83 +0,0 @@
#ifndef ACOUSTIC_ANALYZER_CORE_AUDIO_BUFFER_H_
#define ACOUSTIC_ANALYZER_CORE_AUDIO_BUFFER_H_
#include <atomic>
#include <cstddef>
#include <cstdint>
#include <vector>
namespace acoustic {
/**
* @brief
*
* 线
*/
class AudioBuffer {
public:
/**
* @brief
* @param capacity_samples
* @param num_channels
*/
AudioBuffer(std::size_t capacity_samples, std::size_t num_channels = 1);
~AudioBuffer() = default;
// 禁止拷贝,允许移动
AudioBuffer(const AudioBuffer&) = delete;
AudioBuffer& operator=(const AudioBuffer&) = delete;
AudioBuffer(AudioBuffer&&) = default;
AudioBuffer& operator=(AudioBuffer&&) = default;
/**
* @brief
* @param samples [ch0_s0, ch1_s0, ..., chN_s0, ch0_s1, ...]
* @return = num_channels sample
*/
std::size_t Push(const std::vector<float>& samples);
/**
* @brief
* @param num_frames
* @return <= num_frames * num_channels
*/
std::vector<float> Pop(std::size_t num_frames);
/**
* @brief
* @param offset
* @param num_frames
* @return
*/
std::vector<float> Get(std::size_t offset, std::size_t num_frames) const;
/**
* @brief
*/
std::size_t Size() const noexcept;
/**
* @brief
*/
std::size_t Capacity() const noexcept;
/**
* @brief
*/
void Clear();
std::size_t NumChannels() const noexcept;
private:
std::size_t capacity_frames_;
std::size_t num_channels_;
std::vector<float> buffer_; ///< 循环存储区
std::atomic<std::size_t> head_{0}; ///< 写入位置
std::atomic<std::size_t> tail_{0}; ///< 读取位置
std::atomic<std::size_t> size_{0}; ///< 当前有效帧数
};
} // namespace acoustic
#endif // ACOUSTIC_ANALYZER_CORE_AUDIO_BUFFER_H_

@ -1,69 +0,0 @@
#ifndef ACOUSTIC_ANALYZER_CORE_DISTANCE_ESTIMATOR_H_
#define ACOUSTIC_ANALYZER_CORE_DISTANCE_ESTIMATOR_H_
#include <string>
#include <vector>
#include "acoustic_analyzer/core/types.h"
#include <memory>
namespace acoustic {
/**
* @brief SPL ()
*
* 使 Kalman
*/
class DistanceEstimator {
public:
/**
* @brief
* @param config SPLKalman
*/
explicit DistanceEstimator(const DistanceConfig& config);
~DistanceEstimator();
// 禁止拷贝,允许移动
DistanceEstimator(const DistanceEstimator&) = delete;
DistanceEstimator& operator=(const DistanceEstimator&) = delete;
DistanceEstimator(DistanceEstimator&&) noexcept;
DistanceEstimator& operator=(DistanceEstimator&&) noexcept;
/**
* @brief SPL (dB)
* @param audio_signal
* @return SPL (dB)
*/
float ComputeSpl(const std::vector<float>& audio_signal);
/**
* @brief SPL
* @param spl SPL (dB)
* @param sound_type (gunshot / artillery / explosion / threat)
* @return ()
*/
float Estimate(float spl, const std::string& sound_type);
/**
* @brief 使 Kalman
* @param raw_distance ()
* @return ()
*/
float UpdateKalman(float raw_distance);
/**
* @brief Kalman
*/
void Reset();
const DistanceConfig& Config() const noexcept;
private:
class Impl;
std::unique_ptr<Impl> impl_;
};
} // namespace acoustic
#endif // ACOUSTIC_ANALYZER_CORE_DISTANCE_ESTIMATOR_H_

@ -1,73 +0,0 @@
#ifndef ACOUSTIC_ANALYZER_CORE_FEATURE_EXTRACTOR_H_
#define ACOUSTIC_ANALYZER_CORE_FEATURE_EXTRACTOR_H_
#include <Eigen/Core>
#include <cstddef>
#include <memory>
#include <vector>
namespace acoustic {
/**
* @brief Mel Spectrogram
*
* Mel
*/
class FeatureExtractor {
public:
/**
* @brief
* @param sample_rate (Hz)
* @param n_fft FFT
* @param hop_length
* @param n_mels Mel
* @param f_min (Hz)
* @param f_max (Hz)
* @param preemphasis
*/
FeatureExtractor(int sample_rate,
int n_fft,
int hop_length,
int n_mels,
float f_min = 0.0f,
float f_max = 8000.0f,
float preemphasis = 0.97f);
~FeatureExtractor();
// 禁止拷贝,允许移动
FeatureExtractor(const FeatureExtractor&) = delete;
FeatureExtractor& operator=(const FeatureExtractor&) = delete;
FeatureExtractor(FeatureExtractor&&) noexcept;
FeatureExtractor& operator=(FeatureExtractor&&) noexcept;
/**
* @brief Mel Spectrogram
* @param audio_samples float32
* @return Mel (n_mels × num_frames)
*/
Eigen::MatrixXf MelSpectrogram(const std::vector<float>& audio_samples);
/**
* @brief Mel Spectrogram
* @param audio_samples [ch0_s0, ch1_s0, ..., ch0_s1, ...]
* @param num_channels
* @return Mel
*/
std::vector<Eigen::MatrixXf> MelSpectrogramMultiChannel(
const std::vector<float>& audio_samples,
std::size_t num_channels);
int SampleRate() const noexcept;
int NFFT() const noexcept;
int HopLength() const noexcept;
int NMels() const noexcept;
private:
class Impl;
std::unique_ptr<Impl> impl_;
};
} // namespace acoustic
#endif // ACOUSTIC_ANALYZER_CORE_FEATURE_EXTRACTOR_H_

@ -1,61 +0,0 @@
#ifndef ACOUSTIC_ANALYZER_CORE_FFT_UTILS_H_
#define ACOUSTIC_ANALYZER_CORE_FFT_UTILS_H_
#include <complex>
#include <cstddef>
#include <memory>
#include <vector>
namespace acoustic {
// 自由函数声明(供 FeatureExtractor 内部使用)
void apply_preemphasis(const float* in, float* out, std::size_t n, float coef);
void compute_power_spectrum(const float* frame, int n_fft, float* power_out);
/**
* @brief FFT
*
* FFT
*/
class FftUtils {
public:
/**
* @brief FFT
* @param nfft FFT
*/
explicit FftUtils(std::size_t nfft);
~FftUtils();
// 禁止拷贝,允许移动
FftUtils(const FftUtils&) = delete;
FftUtils& operator=(const FftUtils&) = delete;
FftUtils(FftUtils&& other) noexcept;
FftUtils& operator=(FftUtils&& other) noexcept;
/**
* @brief FFT
* @param input = nfft
* @param output = nfft
*/
void ComputeFft(const std::vector<std::complex<float>>& input,
std::vector<std::complex<float>>& output);
/**
* @brief
* @param input = nfft
* @param output = nfft / 2 + 1
*/
void ComputePowerSpectrum(const std::vector<float>& input,
std::vector<float>& output);
std::size_t Nfft() const noexcept;
private:
class Impl;
std::unique_ptr<Impl> impl_;
};
} // namespace acoustic
#endif // ACOUSTIC_ANALYZER_CORE_FFT_UTILS_H_

@ -1,62 +0,0 @@
#ifndef ACOUSTIC_ANALYZER_CORE_GCC_PHAT_LOCALIZER_H_
#define ACOUSTIC_ANALYZER_CORE_GCC_PHAT_LOCALIZER_H_
#include <Eigen/Core>
#include <memory>
#include <utility>
#include <vector>
#include "acoustic_analyzer/core/types.h"
namespace acoustic {
/**
* @brief GCC-PHAT
*
* (TDOA)
*/
class GccPhatLocalizer {
public:
/**
* @brief
* @param mic_config
* @param sample_rate (Hz)
* @param max_tdoa ()
* @param interp_factor
*/
GccPhatLocalizer(const MicArrayConfig& mic_config,
int sample_rate,
float max_tdoa,
int interp_factor = 4);
~GccPhatLocalizer();
// 禁止拷贝,允许移动
GccPhatLocalizer(const GccPhatLocalizer&) = delete;
GccPhatLocalizer& operator=(const GccPhatLocalizer&) = delete;
GccPhatLocalizer(GccPhatLocalizer&&) noexcept;
GccPhatLocalizer& operator=(GccPhatLocalizer&&) noexcept;
/**
* @brief
* @param multi_channel_audio (num_samples × num_mics)
* @return (azimuth, elevation)
*/
std::pair<float, float> Localize(const Eigen::MatrixXf& multi_channel_audio);
/**
* @brief 使 std::vector 便
*/
std::pair<float, float> Localize(const std::vector<float>& flat_samples,
std::size_t num_channels);
const MicArrayConfig& MicConfig() const noexcept;
private:
class Impl;
std::unique_ptr<Impl> impl_;
};
} // namespace acoustic
#endif // ACOUSTIC_ANALYZER_CORE_GCC_PHAT_LOCALIZER_H_

@ -1,57 +0,0 @@
#ifndef ACOUSTIC_ANALYZER_CORE_GUNSHOT_CLASSIFIER_H_
#define ACOUSTIC_ANALYZER_CORE_GUNSHOT_CLASSIFIER_H_
#include <Eigen/Core>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "acoustic_analyzer/core/types.h"
namespace acoustic {
/**
* @brief ONNX Runtime /
*/
class GunshotClassifier {
public:
/**
* @brief 使
* @param config
*/
explicit GunshotClassifier(const ClassifierConfig& config);
~GunshotClassifier();
// 禁止拷贝,允许移动
GunshotClassifier(const GunshotClassifier&) = delete;
GunshotClassifier& operator=(const GunshotClassifier&) = delete;
GunshotClassifier(GunshotClassifier&&) noexcept;
GunshotClassifier& operator=(GunshotClassifier&&) noexcept;
/**
* @brief Mel Spectrogram
* @param mel_spectrogram Mel (n_mels × time_frames)
* @return (, )
*/
std::pair<std::string, float> Predict(const Eigen::MatrixXf& mel_spectrogram);
/**
* @brief
*/
const std::vector<std::string>& Labels() const noexcept;
/**
* @brief
*/
bool IsLoaded() const noexcept;
private:
class Impl;
std::unique_ptr<Impl> impl_;
};
} // namespace acoustic
#endif // ACOUSTIC_ANALYZER_CORE_GUNSHOT_CLASSIFIER_H_

@ -1,69 +0,0 @@
#ifndef ACOUSTIC_ANALYZER_CORE_PIPELINE_H_
#define ACOUSTIC_ANALYZER_CORE_PIPELINE_H_
#include <memory>
#include <string>
#include <vector>
#include "acoustic_analyzer/core/types.h"
namespace acoustic {
// 前向声明
class AudioBuffer;
class FeatureExtractor;
class GunshotClassifier;
class GccPhatLocalizer;
class DistanceEstimator;
class ThreatTracker;
/**
* @brief 线
*
*
*/
class Pipeline {
public:
/**
* @brief 使线
* @param config 线
*/
explicit Pipeline(const PipelineConfig& config);
~Pipeline();
// 禁止拷贝,允许移动
Pipeline(const Pipeline&) = delete;
Pipeline& operator=(const Pipeline&) = delete;
Pipeline(Pipeline&&) noexcept;
Pipeline& operator=(Pipeline&&) noexcept;
/**
* @brief
* @param audio_samples
* @return is_clear=true
*/
AcousticFrame Process(const std::vector<float>& audio_samples);
/**
* @brief YAML 线
* @param yaml_path YAML
* @return PipelineConfig
*/
static PipelineConfig FromYaml(const std::string& yaml_path);
/**
* @brief 线Kalman
*/
void Reset();
const PipelineConfig& Config() const noexcept;
private:
class Impl;
std::unique_ptr<Impl> impl_;
};
} // namespace acoustic
#endif // ACOUSTIC_ANALYZER_CORE_PIPELINE_H_

@ -1,80 +0,0 @@
#ifndef ACOUSTIC_ANALYZER_CORE_THREAT_TRACKER_H_
#define ACOUSTIC_ANALYZER_CORE_THREAT_TRACKER_H_
#include <string>
#include <vector>
#include "acoustic_analyzer/core/types.h"
namespace acoustic {
/**
* @brief
*
* ID
*
*/
class ThreatTracker {
public:
/**
* @brief
* @param min_event_interval ()
* @param azimuth_gate_deg ()
* @param max_missing_frames
*/
explicit ThreatTracker(float min_event_interval = 0.3f,
float azimuth_gate_deg = 15.0f,
int max_missing_frames = 5);
~ThreatTracker() = default;
// 禁止拷贝,允许移动
ThreatTracker(const ThreatTracker&) = delete;
ThreatTracker& operator=(const ThreatTracker&) = delete;
ThreatTracker(ThreatTracker&&) = default;
ThreatTracker& operator=(ThreatTracker&&) = default;
/**
* @brief
* @param detected_threats
* @return
*/
std::vector<AcousticThreat> Update(
const std::vector<AcousticThreat>& detected_threats);
/**
* @brief
*/
void Reset();
/**
* @brief
*/
std::size_t NumTracked() const noexcept;
private:
struct TrackedThreat {
std::string threat_id;
Timestamp last_publish_time;
float last_azimuth = 0.0f;
float last_elevation = 0.0f;
std::string sound_type;
float confidence = 0.0f;
float distance = -1.0f;
float distance_confidence = 0.0f;
int missing_frames = 0;
bool ever_published = false;
};
float min_event_interval_;
float azimuth_gate_deg_;
int max_missing_frames_;
std::vector<TrackedThreat> tracks_;
uint64_t id_counter_ = 0;
std::string GenerateId();
};
} // namespace acoustic
#endif // ACOUSTIC_ANALYZER_CORE_THREAT_TRACKER_H_

@ -1,107 +0,0 @@
#ifndef ACOUSTIC_ANALYZER_CORE_TYPES_H_
#define ACOUSTIC_ANALYZER_CORE_TYPES_H_
#include <array>
#include <chrono>
#include <cstdint>
#include <string>
#include <vector>
#ifdef ROS_TIME_INCLUDED
#include <ros/time.h>
#endif
namespace acoustic {
#ifdef ROS_TIME_INCLUDED
using Timestamp = ros::Time;
#else
using Timestamp = std::chrono::steady_clock::time_point;
#endif
/**
* @brief
*/
struct AcousticThreat {
Timestamp timestamp;
std::string threat_id; ///< 唯一威胁标识符
std::string sound_type; ///< 声音类别 (gunshot / artillery / explosion / ambient)
float confidence = 0.0f; ///< 分类置信度 [0, 1]
float azimuth = 0.0f; ///< 方位角 0~360°0=正北,顺时针
float elevation = 0.0f; ///< 俯仰角 -90~90°
float distance = -1.0f; ///< 估计距离 (米)-1 表示未知
float distance_confidence = 0.0f; ///< 距离估计置信度 [0, 1]
};
/**
* @brief
*/
struct AcousticFrame {
Timestamp timestamp;
std::vector<AcousticThreat> threats;
bool is_clear = true; ///< true = 当前帧无威胁
};
/**
* @brief
*/
struct MicArrayConfig {
uint32_t num_mics = 4;
float spacing = 0.15f; ///< 相邻麦克风间距 (米)
std::vector<std::array<float, 3>> positions; ///< 自定义坐标 [x, y, z]
std::string layout = "cross"; ///< cross / linear / circular / custom
};
/**
* @brief
*/
struct ClassifierConfig {
std::string model_path; ///< ONNX 模型路径
std::string label_map_path; ///< 标签映射 JSON 路径
float threshold = 0.7f; ///< 检测阈值
uint32_t smoothing_window = 3; ///< 后处理平滑窗口
};
/**
* @brief
*/
struct DistanceConfig {
float ref_spl_gunshot = 150.0f; ///< 枪声参考 SPL (dB)
float ref_spl_artillery = 180.0f; ///< 火炮参考 SPL (dB)
float ref_spl_explosion = 170.0f; ///< 爆炸参考 SPL (dB)
float attenuation_alpha = 0.6f; ///< 空气吸收衰减系数
float spl_calibration_offset = 0.0f; ///< SPL 校准偏移
float kalman_process_noise = 0.01f; ///< Kalman 过程噪声
float kalman_measurement_noise = 5.0f; ///< Kalman 测量噪声
};
/**
* @brief 线
*/
struct PipelineConfig {
uint32_t sample_rate = 16000;
float chunk_duration = 2.0f; ///< 分析窗口长度 (秒)
float hop_duration = 0.5f; ///< 步进长度 (秒)
uint32_t n_mels = 64; ///< Mel 滤波器组数量
float confidence_threshold = 0.7f; ///< 输出置信度阈值
float min_event_interval = 0.3f; ///< 同一威胁最小上报间隔 (秒)
// 特征提取参数(原 hard-coded现可配置
uint32_t n_fft = 2048; ///< FFT 长度
uint32_t hop_length = 512; ///< 帧移样本数
float f_min = 0.0f; ///< 最低频率 (Hz)
float f_max = 8000.0f; ///< 最高频率 (Hz)
float preemphasis = 0.97f; ///< 预加重系数
// 定位参数(原 hard-coded现可配置
float max_tdoa = 0.00044f; ///< 最大允许时延 (秒)
int interpolation_factor = 4; ///< GCC-PHAT 插值倍数
MicArrayConfig mic_array;
ClassifierConfig classifier;
DistanceConfig distance;
};
} // namespace acoustic
#endif // ACOUSTIC_ANALYZER_CORE_TYPES_H_

@ -1,55 +0,0 @@
#ifndef ACOUSTIC_ANALYZER_IO_AUDIO_SOURCE_H_
#define ACOUSTIC_ANALYZER_IO_AUDIO_SOURCE_H_
#include <cstddef>
#include <string>
#include <vector>
namespace acoustic {
/**
* @brief
*
* WAV UDP
*/
class AudioSource {
public:
virtual ~AudioSource() = default;
/**
* @brief
* @return true
*/
virtual bool Open() = 0;
/**
* @brief
* @param num_samples = num_samples * NumChannels()
* @return EOF
*/
virtual std::vector<float> Read(std::size_t num_samples) = 0;
/**
* @brief
*/
virtual void Close() = 0;
/**
* @brief
*/
virtual std::size_t NumChannels() const = 0;
/**
* @brief (Hz)
*/
virtual int SampleRate() const = 0;
/**
* @brief
*/
virtual bool IsOpen() const = 0;
};
} // namespace acoustic
#endif // ACOUSTIC_ANALYZER_IO_AUDIO_SOURCE_H_

@ -1,54 +0,0 @@
#ifndef ACOUSTIC_ANALYZER_IO_MOBILE_PHONE_SOURCE_H_
#define ACOUSTIC_ANALYZER_IO_MOBILE_PHONE_SOURCE_H_
#include <cstddef>
#include <memory>
#include <string>
#include <vector>
#include "acoustic_analyzer/io/audio_source.h"
// 前置声明 ROS 类型,避免强制依赖 ros.h
namespace ros {
class NodeHandle;
}
namespace acoustic {
/**
* @brief UDP/RosTopic
*
* ROS Topic UDP
* 使
*/
class MobilePhoneSource : public AudioSource {
public:
/**
* @brief
* @param nh ROS
* @param topic topic
* @param timeout_sec ()
* @param sample_rate (Hz)
*/
MobilePhoneSource(ros::NodeHandle& nh,
const std::string& topic,
float timeout_sec,
int sample_rate = 16000);
~MobilePhoneSource() override;
bool Open() override;
std::vector<float> Read(std::size_t num_samples) override;
void Close() override;
std::size_t NumChannels() const override;
int SampleRate() const override;
bool IsOpen() const override;
private:
class Impl;
std::unique_ptr<Impl> impl_;
};
} // namespace acoustic
#endif // ACOUSTIC_ANALYZER_IO_MOBILE_PHONE_SOURCE_H_

@ -1,48 +0,0 @@
#ifndef ACOUSTIC_ANALYZER_IO_WAV_FILE_SOURCE_H_
#define ACOUSTIC_ANALYZER_IO_WAV_FILE_SOURCE_H_
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <string>
#include <vector>
namespace acoustic {
/**
* @brief WAV
*
* 16-bit PCM WAV
*/
class WavFileSource {
public:
explicit WavFileSource(const std::string& path, int target_sample_rate = 16000);
~WavFileSource();
bool open();
std::size_t read(std::vector<std::vector<float>>& out, std::size_t max_samples);
void close();
std::size_t num_channels() const { return num_channels_; }
int sample_rate() const { return target_sample_rate_ > 0 ? target_sample_rate_ : file_sample_rate_; }
int file_sample_rate() const { return file_sample_rate_; }
std::size_t total_samples() const { return total_samples_; }
bool is_open() const { return fp_ != nullptr; }
private:
bool parse_wav_header();
void resample_if_needed(std::vector<float>& mono, int src_rate, int dst_rate);
std::string path_;
int target_sample_rate_;
std::FILE* fp_;
uint16_t num_channels_;
uint32_t file_sample_rate_;
uint16_t bits_per_sample_;
long data_start_;
std::size_t total_samples_;
std::size_t read_pos_;
};
} // namespace acoustic
#endif // ACOUSTIC_ANALYZER_IO_WAV_FILE_SOURCE_H_

@ -1,15 +0,0 @@
#pragma once
#include <memory>
#include <ros/ros.h>
namespace acoustic {
class ThreatPublisher;
class AcousticNode;
std::unique_ptr<AcousticNode> create_acoustic_node(
ros::NodeHandle& nh,
ros::NodeHandle& pnh,
ThreatPublisher* publisher = nullptr);
} // namespace acoustic

@ -1,54 +0,0 @@
#ifndef ACOUSTIC_ANALYZER_ROS_THREAT_PUBLISHER_H_
#define ACOUSTIC_ANALYZER_ROS_THREAT_PUBLISHER_H_
#include <memory>
#include <string>
#include <ros/ros.h>
#include "acoustic_analyzer/core/types.h"
namespace acoustic {
/**
* @brief ROS
*
* AcousticFrame AcousticThreatArray topic
*/
class ThreatPublisher {
public:
/**
* @brief
* @param nh ROS
* @param topic topic /acoustic/threats
*/
ThreatPublisher(ros::NodeHandle& nh,
const std::string& topic = "/acoustic/threats");
~ThreatPublisher() = default;
// 禁止拷贝,允许移动
ThreatPublisher(const ThreatPublisher&) = delete;
ThreatPublisher& operator=(const ThreatPublisher&) = delete;
ThreatPublisher(ThreatPublisher&&) = default;
ThreatPublisher& operator=(ThreatPublisher&&) = default;
/**
* @brief
* @param frame is_clear
*/
void Publish(const AcousticFrame& frame);
/**
* @brief
*/
std::size_t NumPublished() const noexcept;
private:
class Impl;
std::unique_ptr<Impl> impl_;
};
} // namespace acoustic
#endif // ACOUSTIC_ANALYZER_ROS_THREAT_PUBLISHER_H_

@ -1,8 +0,0 @@
# 单条声源威胁信息
string threat_id
string sound_type
float32 confidence
float32 azimuth # 0~360, 0=North, clockwise
float32 elevation # -90~90
float32 distance # -1 means unknown
float32 distance_confidence

@ -1,3 +0,0 @@
# 一帧声源威胁数组
Header header
AcousticThreat[] threats

@ -1,20 +0,0 @@
<?xml version="1.0"?>
<package format="2">
<name>acoustic_analyzer</name>
<version>1.0.0</version>
<description>Acoustic threat analyzer for ZhiTuTouSong UAV system. Gunshot/artillery detection, GCC-PHAT localization, and distance estimation.</description>
<maintainer email="team@zhitutousong.com">ZhiTu Team</maintainer>
<license>MIT</license>
<buildtool_depend>catkin</buildtool_depend>
<depend>roscpp</depend>
<depend>std_msgs</depend>
<depend>sensor_msgs</depend>
<depend>geometry_msgs</depend>
<depend>message_generation</depend>
<depend>message_runtime</depend>
<export>
</export>
</package>

@ -1,64 +0,0 @@
@echo off
chcp 65001 >nul
setlocal EnableDelayedExpansion
echo ==========================================
echo Acoustic Module - One-Click Demo
echo ==========================================
set BUILD_DIR=%~dp0build
set MODEL=models\gunshot_classifier.onnx
set VAL_DIR=dataset\binary\val
REM --- Ensure executables exist ---
if not exist "%BUILD_DIR%\test_core_lib.exe" (
echo [INFO] test_core_lib.exe not found, building core tests first...
call "%~dp0build_core_test.bat"
if %ERRORLEVEL% NEQ 0 exit /b 1
)
if not exist "%BUILD_DIR%\demo_offline.exe" (
echo [INFO] demo_offline.exe not found, building demo first...
call "%~dp0build_demo.bat"
if %ERRORLEVEL% NEQ 0 exit /b 1
)
REM --- Step 1: Core unit tests ---
echo.
echo [Step 1/3] Running core unit tests...
"%BUILD_DIR%\test_core_lib.exe"
if %ERRORLEVEL% NEQ 0 (
echo [FAIL] Core unit tests failed.
exit /b 1
)
REM --- Step 2: ONNX quick inference ---
echo.
echo [Step 2/3] Running ONNX classifier test...
if exist "%MODEL%" (
if exist "%VAL_DIR%\ambient" (
for %%F in ("%VAL_DIR%\ambient\*.wav") do (
echo Testing: %%F
"%BUILD_DIR%\test_classifier_cpp.exe" --model "%MODEL%" --wav "%%F"
goto :break_onnx
)
:break_onnx
) else (
echo [SKIP] No val data found for ONNX test
)
) else (
echo [SKIP] ONNX model not found: %MODEL%
)
REM --- Step 3: Offline pipeline demo ---
echo.
echo [Step 3/3] Running offline pipeline demo...
if exist "%VAL_DIR%" (
"%BUILD_DIR%\demo_offline.exe" "%VAL_DIR%"
) else (
echo [SKIP] Val dataset not found: %VAL_DIR%
)
echo.
echo [OK] Demo complete.
endlocal

@ -1,143 +0,0 @@
#!/usr/bin/env python3
"""
17 种数据增强脚本
应用于 dataset/train 下的音频文件生成增强后的副本
"""
import os
import argparse
import random
import numpy as np
import torch
import torchaudio
import torchaudio.functional as F
def set_seed(seed=42):
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
def add_white_noise(waveform, snr_db=20):
noise = torch.randn_like(waveform)
signal_power = waveform.pow(2).mean()
noise_power = noise.pow(2).mean()
snr_linear = 10 ** (snr_db / 10)
scale = (signal_power / (noise_power * snr_linear)) ** 0.5
return waveform + noise * scale
def time_stretch(waveform, orig_sr, rate):
# librosa style time stretch using phase vocoder is complex;
# fallback to resample which changes both pitch and speed
if rate <= 0:
return waveform
new_sr = int(orig_sr * rate)
return F.resample(waveform, orig_sr, new_sr)
def pitch_shift(waveform, sr, n_steps):
return F.pitch_shift(waveform, sr, n_steps)
def random_gain(waveform, min_gain=0.8, max_gain=1.2):
gain = random.uniform(min_gain, max_gain)
return waveform * gain
def random_polarity_invert(waveform):
if random.random() < 0.5:
return -waveform
return waveform
def random_crop_pad(waveform, target_len):
if waveform.shape[-1] > target_len:
start = random.randint(0, waveform.shape[-1] - target_len)
return waveform[:, start:start + target_len]
else:
pad = target_len - waveform.shape[-1]
return torch.nn.functional.pad(waveform, (0, pad))
def high_pass_filter(waveform, sr, cutoff=200):
# Simple FIR approximation: subtract moving average
kernel_size = int(sr / cutoff)
if kernel_size < 2:
return waveform
padding = kernel_size // 2
smoothed = torch.nn.functional.avg_pool1d(
torch.nn.functional.pad(waveform, (padding, padding)), kernel_size, stride=1)
return waveform - smoothed
def low_pass_filter(waveform, sr, cutoff=4000):
# Simple moving average lowpass
kernel_size = int(sr / cutoff)
if kernel_size < 2:
return waveform
padding = kernel_size // 2
return torch.nn.functional.avg_pool1d(
torch.nn.functional.pad(waveform, (padding, padding)), kernel_size, stride=1)
def apply_augmentations(waveform, sr):
"""Apply a random subset of augmentations."""
aug_list = [
lambda x: add_white_noise(x, snr_db=random.uniform(10, 30)),
lambda x: time_stretch(x, sr, random.uniform(0.9, 1.1)),
lambda x: pitch_shift(x, sr, random.uniform(-2, 2)),
lambda x: random_gain(x),
lambda x: random_polarity_invert(x),
lambda x: high_pass_filter(x, sr),
lambda x: low_pass_filter(x, sr),
]
num_aug = random.randint(1, 3)
chosen = random.sample(aug_list, num_aug)
for fn in chosen:
waveform = fn(waveform)
# Clamp
waveform = torch.clamp(waveform, -1.0, 1.0)
return waveform
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--input', required=True, help='Input dataset root (e.g., dataset/binary/train)')
parser.add_argument('--output', required=True, help='Output dataset root')
parser.add_argument('--n_augment', type=int, default=3, help='Copies per original file')
parser.add_argument('--sr', type=int, default=16000)
args = parser.parse_args()
set_seed(42)
os.makedirs(args.output, exist_ok=True)
for cls in sorted(os.listdir(args.input)):
cls_in = os.path.join(args.input, cls)
if not os.path.isdir(cls_in):
continue
cls_out = os.path.join(args.output, cls)
os.makedirs(cls_out, exist_ok=True)
for fname in os.listdir(cls_in):
if not fname.endswith('.wav'):
continue
path = os.path.join(cls_in, fname)
waveform, sr = torchaudio.load(path)
if sr != args.sr:
waveform = F.resample(waveform, sr, args.sr)
# Save original
torchaudio.save(os.path.join(cls_out, fname), waveform, args.sr)
# Generate augmented copies
for i in range(args.n_augment):
aug = apply_augmentations(waveform.clone(), args.sr)
# Ensure same length
if aug.shape[-1] != waveform.shape[-1]:
aug = random_crop_pad(aug, waveform.shape[-1])
out_name = fname.replace('.wav', f'_aug{i}.wav')
torchaudio.save(os.path.join(cls_out, out_name), aug, args.sr)
print("Augmentation complete.")
if __name__ == '__main__':
main()

@ -1,83 +0,0 @@
#!/usr/bin/env python3
"""
ONNX 导出脚本
PyTorch 训练好的模型导出为 ONNX 格式
兼容 torch 2.x使用传统 TorchScript 导出器 (dynamo=False)
"""
import argparse
import os
import torch
import torch.nn as nn
class CNNGRU(nn.Module):
def __init__(self, n_mels=64, num_classes=2):
super().__init__()
self.cnn = nn.Sequential(
nn.Conv1d(n_mels, 64, kernel_size=3, padding=1),
nn.ReLU(),
nn.MaxPool1d(2),
nn.Conv1d(64, 128, kernel_size=3, padding=1),
nn.ReLU(),
nn.MaxPool1d(2),
nn.Conv1d(128, 256, kernel_size=3, padding=1),
nn.ReLU()
)
self.gru = nn.GRU(256, 128, batch_first=True, bidirectional=True)
self.fc = nn.Sequential(
nn.Linear(256, 64),
nn.ReLU(),
nn.Dropout(0.3),
nn.Linear(64, num_classes)
)
def forward(self, x):
# x: [B, T, n_mels]
x = x.transpose(1, 2)
x = self.cnn(x)
x = x.transpose(1, 2)
x, _ = self.gru(x)
x = x.mean(dim=1)
return self.fc(x)
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--checkpoint', required=True, help='Path to .pth checkpoint')
parser.add_argument('--output', default='models/gunshot_classifier.onnx')
parser.add_argument('--n_mels', type=int, default=64)
parser.add_argument('--num_classes', type=int, default=2)
parser.add_argument('--opset', type=int, default=13)
args = parser.parse_args()
device = torch.device('cpu')
model = CNNGRU(n_mels=args.n_mels, num_classes=args.num_classes).to(device)
ckpt = torch.load(args.checkpoint, map_location=device)
model.load_state_dict(ckpt['model'])
model.eval()
dummy_input = torch.randn(1, 63, args.n_mels, device=device)
os.makedirs(os.path.dirname(args.output) or '.', exist_ok=True)
torch.onnx.export(
model,
dummy_input,
args.output,
export_params=True,
opset_version=args.opset,
do_constant_folding=True,
input_names=['input'],
output_names=['output'],
dynamic_axes={
'input': {0: 'batch_size'},
'output': {0: 'batch_size'}
},
dynamo=False # Use traditional exporter for GRU compatibility
)
print(f"ONNX model exported to {args.output}")
print(f"Input shape: {dummy_input.shape} -> [batch, time, n_mels]")
if __name__ == '__main__':
main()

@ -1,72 +0,0 @@
#!/usr/bin/env python3
"""
4 通道模拟信号生成
根据指定方位角和距离生成十字阵列 WAV 文件
"""
import argparse
import numpy as np
import soundfile as sf
def generate_gunshot_signal(sr=16000, duration=2.0, freq=1000, decay=0.05):
t = np.linspace(0, duration, int(sr * duration))
# Impulse-like exponential decay sine
envelope = np.exp(-t / decay)
signal = np.sin(2 * np.pi * freq * t) * envelope
# Normalize
signal = signal / np.max(np.abs(signal) + 1e-10)
return signal.astype(np.float32)
def simulate_array(signal, azimuth_deg, distance_m, sr=16000, spacing=0.15, c=343.0):
"""
Cross array: mic positions [front, right, back, left]
Returns 4 channels with appropriate delays and attenuation
"""
az = np.deg2rad(azimuth_deg)
# Source position in plane
src_x = distance_m * np.sin(az)
src_y = distance_m * np.cos(az)
mics = np.array([
[0.0, spacing],
[spacing, 0.0],
[0.0, -spacing],
[-spacing, 0.0]
])
channels = []
for mic in mics:
dist = np.sqrt((src_x - mic[0])**2 + (src_y - mic[1])**2)
delay_sec = dist / c
delay_samples = int(delay_sec * sr)
# Amplitude attenuation (1/r)
att = 1.0 / (dist + 1e-6)
ch = np.zeros_like(signal)
if delay_samples < len(signal):
ch[delay_samples:] = signal[:len(signal) - delay_samples] * att
channels.append(ch)
return np.stack(channels, axis=0) # [4, samples]
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--output', default='dataset/multichannel_test.wav')
parser.add_argument('--azimuth', type=float, default=90.0, help='Azimuth in degrees')
parser.add_argument('--distance', type=float, default=100.0, help='Distance in meters')
parser.add_argument('--sr', type=int, default=16000)
parser.add_argument('--duration', type=float, default=2.0)
args = parser.parse_args()
signal = generate_gunshot_signal(args.sr, args.duration)
multich = simulate_array(signal, args.azimuth, args.distance, args.sr)
# Normalize to prevent clipping
max_val = np.max(np.abs(multich))
if max_val > 0:
multich = multich / max_val * 0.9
# soundfile expects [samples, channels] for multi-channel
sf.write(args.output, multich.T, args.sr, subtype="PCM_16")
print(f"Generated {args.output}: azimuth={args.azimuth}°, distance={args.distance}m, shape={multich.shape}")
if __name__ == '__main__':
main()

@ -1,243 +0,0 @@
#!/usr/bin/env python3
"""
噪声注入批量测试脚本
功能
1. 对干净音频按指定 SNR 注入噪声生成测试样本
2. 调用 C++ 离线推理程序批量检测
3. 输出 CSV 统计结果便于绘制 Pd-SNR / 定位误差-SNR 曲线
用法示例
# 单通道分类测试
python inject_noise_eval.py \\
--clean_dir dataset/real/threat \\
--noise noise_samples/drone_hover.wav \\
--snrs 20 10 5 0 -5 \\
--exe build/demo_offline.exe \\
--output results/threat_vs_drone_noise.csv
# 多通道定位测试
python inject_noise_eval.py \\
--clean_dir dataset/multichannel \\
--noise noise_samples/white.wav \\
--snrs 20 10 5 0 \\
--exe build/demo_offline_multichannel.exe \\
--num_mics 4 --layout cross \\
--output results/loc_snr_sweep.csv
"""
import argparse
import csv
import os
import subprocess
import sys
import tempfile
from pathlib import Path
import numpy as np
import soundfile as sf
def list_wav_files(directory: str):
"""递归列出目录下所有 wav 文件。"""
return sorted([str(p) for p in Path(directory).rglob("*.wav")])
def compute_rms(signal: np.ndarray):
"""计算信号 RMS。"""
return np.sqrt(np.mean(signal ** 2))
def mix_with_noise(clean: np.ndarray, noise: np.ndarray, target_snr_db: float):
"""
将噪声以指定 SNR 混入干净信号
如果噪声比干净信号短会循环拼接
支持多通道 cleannoise 会先混为单通道再扩展到匹配通道数或直接用同通道数
"""
# Ensure 2D: [channels, samples]
if clean.ndim == 1:
clean = clean.reshape(1, -1)
if noise.ndim == 1:
noise = noise.reshape(1, -1)
n_ch, n_samples = clean.shape
# Tile or truncate noise to match length
noise_tiled = np.tile(noise, (1, int(np.ceil(n_samples / noise.shape[1])) + 1))
noise_tiled = noise_tiled[:, :n_samples]
# If noise has fewer channels, replicate first channel
if noise_tiled.shape[0] < n_ch:
noise_tiled = np.repeat(noise_tiled[:1, :], n_ch, axis=0)
elif noise_tiled.shape[0] > n_ch:
noise_tiled = noise_tiled[:n_ch, :]
# Compute per-channel RMS and average for a global scaling factor
clean_rms = compute_rms(clean)
if clean_rms < 1e-10:
return clean # silent signal, return as-is
noise_rms = compute_rms(noise_tiled)
if noise_rms < 1e-10:
return clean
target_noise_rms = clean_rms / (10 ** (target_snr_db / 20.0))
scale = target_noise_rms / noise_rms
mixed = clean + noise_tiled * scale
# Normalize to prevent clipping
max_val = np.max(np.abs(mixed))
if max_val > 1.0:
mixed = mixed / max_val * 0.95
return mixed
def run_classifier(exe_path: str, wav_path: str, extra_args: list) -> dict:
"""
调用 C++ 离线推理程序解析其标准输出
返回字典包含 pred_label, confidence
"""
import re
cmd = [exe_path, wav_path] + extra_args
try:
result = subprocess.run(cmd, capture_output=True, timeout=30)
# Decode with GBK first (Windows console default), fallback to UTF-8
try:
output = result.stdout.decode("gbk", errors="replace")
except Exception:
output = result.stdout.decode("utf-8", errors="replace")
except Exception as e:
return {"error": str(e)}
parsed = {}
# Robust regex parsing for output like:
# File: xxx.wav | True: threat | Pred: threat | Conf: 0.9234 | Az: 90.00° | El: 0.00° | Dist: 95.20m
for line in output.splitlines():
if line.startswith("File:") and "Pred:" in line:
m = re.search(r"Pred:\s*(\S+)", line)
if m:
parsed["pred"] = m.group(1)
m = re.search(r"Conf:\s*([0-9.]+)", line)
if m:
parsed["conf"] = float(m.group(1))
m = re.search(r"Az:\s*([0-9.+-]+)", line)
if m:
parsed["azimuth"] = float(m.group(1))
m = re.search(r"El:\s*([0-9.+-]+)", line)
if m:
parsed["elevation"] = float(m.group(1))
m = re.search(r"Dist:\s*([0-9.+-]+)", line)
if m:
parsed["distance"] = float(m.group(1))
break
return parsed
def main():
parser = argparse.ArgumentParser(description="Noise injection evaluation for acoustic analyzer")
parser.add_argument("--clean_dir", required=True, help="Directory containing clean WAV files")
parser.add_argument("--noise", required=True, help="Noise WAV file path")
parser.add_argument("--snrs", nargs="+", type=float, required=True,
help="List of SNR values in dB, e.g. 20 10 5 0 -5")
parser.add_argument("--exe", required=True, help="Path to demo_offline or demo_offline_multichannel exe")
parser.add_argument("--output", required=True, help="Output CSV path")
parser.add_argument("--num_mics", type=int, default=1, help="Number of channels (for multichannel exe)")
parser.add_argument("--layout", default="cross", help="Array layout (for multichannel exe)")
parser.add_argument("--threshold", type=float, default=0.5, help="Detection threshold")
parser.add_argument("--ground_azimuth", type=float, default=None, help="Ground-truth azimuth for error calc")
parser.add_argument("--ground_distance", type=float, default=None, help="Ground-truth distance for error calc")
args = parser.parse_args()
clean_files = list_wav_files(args.clean_dir)
if not clean_files:
print(f"[ERROR] No WAV files found in {args.clean_dir}")
sys.exit(1)
noise_data, noise_sr = sf.read(args.noise, dtype="float32")
if noise_data.ndim == 1:
noise_data = noise_data.reshape(1, -1)
else:
noise_data = noise_data.T # [channels, samples]
# Prepare extra args for C++ exe
extra_args = [f"--threshold", str(args.threshold)]
if "multichannel" in os.path.basename(args.exe).lower():
extra_args += ["--num_mics", str(args.num_mics), "--layout", args.layout]
if args.ground_azimuth is not None:
extra_args += ["--ground_azimuth", str(args.ground_azimuth)]
if args.ground_distance is not None:
extra_args += ["--ground_distance", str(args.ground_distance)]
# CSV header
fieldnames = ["file", "true_label", "snr_db", "pred_label", "confidence",
"azimuth", "elevation", "distance"]
if args.ground_azimuth is not None:
fieldnames.append("azimuth_error")
if args.ground_distance is not None:
fieldnames.append("distance_error")
os.makedirs(os.path.dirname(args.output) or ".", exist_ok=True)
with open(args.output, "w", newline="", encoding="utf-8") as csvfile:
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
total_runs = len(clean_files) * len(args.snrs)
run_idx = 0
with tempfile.TemporaryDirectory() as tmpdir:
for clean_path in clean_files:
clean_data, clean_sr = sf.read(clean_path, dtype="float32")
true_label = Path(clean_path).parent.name
# Ensure 2D
if clean_data.ndim == 1:
clean_data = clean_data.reshape(1, -1)
else:
clean_data = clean_data.T
for snr in args.snrs:
run_idx += 1
mixed = mix_with_noise(clean_data, noise_data, snr)
# Save temp WAV (interleaved)
if mixed.shape[0] == 1:
mixed_interleaved = mixed[0]
else:
mixed_interleaved = mixed.T # [samples, channels]
tmp_wav = os.path.join(tmpdir, f"mix_{run_idx:04d}.wav")
sf.write(tmp_wav, mixed_interleaved, clean_sr, subtype="PCM_16")
# Run inference
parsed = run_classifier(args.exe, tmp_wav, extra_args)
row = {
"file": Path(clean_path).name,
"true_label": true_label,
"snr_db": snr,
"pred_label": parsed.get("pred", "error"),
"confidence": parsed.get("conf", 0.0),
"azimuth": parsed.get("azimuth", ""),
"elevation": parsed.get("elevation", ""),
"distance": parsed.get("distance", ""),
}
if args.ground_azimuth is not None and "azimuth" in parsed:
err = abs(parsed["azimuth"] - args.ground_azimuth)
if err > 180:
err = 360 - err
row["azimuth_error"] = round(err, 2)
if args.ground_distance is not None and "distance" in parsed:
row["distance_error"] = round(abs(parsed["distance"] - args.ground_distance), 2)
writer.writerow(row)
print(f"[{run_idx}/{total_runs}] {Path(clean_path).name} SNR={snr}dB -> {row['pred_label']} ({row['confidence']:.3f})")
print(f"\n[OK] Results saved to {args.output}")
if __name__ == "__main__":
main()

@ -1,243 +0,0 @@
#!/usr/bin/env python3
"""
二分类 CNN-GRU 训练脚本
支持 --resume checkpoint 恢复
使用 WeightedRandomSampler + Mixup
"""
import argparse
import os
import json
import random
import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import Dataset, DataLoader, WeightedRandomSampler
from torchaudio.transforms import MelSpectrogram
import torchaudio.functional as F
import soundfile as sf
def set_seed(seed=42):
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
class AudioDataset(Dataset):
def __init__(self, root_dir, n_mels=64, n_fft=2048, hop_length=512, sr=16000, augment=False):
self.samples = []
self.labels = []
self.classes = sorted(os.listdir(root_dir))
self.class_to_idx = {c: i for i, c in enumerate(self.classes)}
self.sr = sr
self.augment = augment
self.mel = MelSpectrogram(sample_rate=sr, n_fft=n_fft, hop_length=hop_length,
n_mels=n_mels, f_min=0, f_max=8000, center=False)
for cls in self.classes:
cls_dir = os.path.join(root_dir, cls)
if not os.path.isdir(cls_dir):
continue
for fname in os.listdir(cls_dir):
if fname.endswith('.wav'):
self.samples.append(os.path.join(cls_dir, fname))
self.labels.append(self.class_to_idx[cls])
def __len__(self):
return len(self.samples)
def __getitem__(self, idx):
path = self.samples[idx]
waveform, sr = sf.read(path, dtype='float32')
if waveform.ndim == 1:
waveform = waveform[np.newaxis, :]
else:
waveform = waveform.T # [channels, samples]
waveform = torch.from_numpy(waveform)
if sr != self.sr:
waveform = F.resample(waveform, sr, self.sr)
if waveform.shape[0] > 1:
waveform = waveform.mean(dim=0, keepdim=True)
# Pad/truncate to 2 seconds
target_len = self.sr * 2
if waveform.shape[1] < target_len:
waveform = torch.nn.functional.pad(waveform, (0, target_len - waveform.shape[1]))
else:
waveform = waveform[:, :target_len]
if self.augment:
waveform = self.apply_augment(waveform)
mel = self.mel(waveform) # [1, n_mels, T]
mel = (mel + 1e-10).log10() * 10.0
# Normalize per mel
mel = (mel - mel.mean(dim=-1, keepdim=True)) / (mel.std(dim=-1, keepdim=True) + 1e-8)
# Pad/truncate time to 63
T = mel.shape[-1]
if T < 63:
mel = torch.nn.functional.pad(mel, (0, 63 - T))
else:
mel = mel[:, :, :63]
return mel.squeeze(0).transpose(0, 1), self.labels[idx] # [T, n_mels]
def apply_augment(self, waveform):
if random.random() < 0.5:
gain = random.uniform(0.8, 1.2)
waveform = waveform * gain
if random.random() < 0.3:
noise = torch.randn_like(waveform) * 0.005
waveform = waveform + noise
return waveform
class CNNGRU(nn.Module):
def __init__(self, n_mels=64, num_classes=2):
super().__init__()
self.cnn = nn.Sequential(
nn.Conv1d(n_mels, 64, kernel_size=3, padding=1),
nn.ReLU(),
nn.MaxPool1d(2),
nn.Conv1d(64, 128, kernel_size=3, padding=1),
nn.ReLU(),
nn.MaxPool1d(2),
nn.Conv1d(128, 256, kernel_size=3, padding=1),
nn.ReLU(),
nn.AdaptiveAvgPool1d(16)
)
self.gru = nn.GRU(256, 128, batch_first=True, bidirectional=True)
self.fc = nn.Sequential(
nn.Linear(256, 64),
nn.ReLU(),
nn.Dropout(0.3),
nn.Linear(64, num_classes)
)
def forward(self, x):
# x: [B, T, n_mels]
x = x.transpose(1, 2) # [B, n_mels, T]
x = self.cnn(x) # [B, 256, 16]
x = x.transpose(1, 2) # [B, 16, 256]
x, _ = self.gru(x)
x = x.mean(dim=1)
return self.fc(x)
def mixup_data(x, y, alpha=0.4):
if alpha > 0:
lam = np.random.beta(alpha, alpha)
else:
lam = 1
batch_size = x.size(0)
index = torch.randperm(batch_size).to(x.device)
mixed_x = lam * x + (1 - lam) * x[index]
y_a, y_b = y, y[index]
return mixed_x, y_a, y_b, lam
def train_epoch(model, loader, criterion, optimizer, device, use_mixup=True):
model.train()
total_loss = 0
correct = 0
total = 0
for x, y in loader:
x, y = x.to(device), y.to(device)
optimizer.zero_grad()
if use_mixup:
x, y_a, y_b, lam = mixup_data(x, y)
outputs = model(x)
loss = lam * criterion(outputs, y_a) + (1 - lam) * criterion(outputs, y_b)
else:
outputs = model(x)
loss = criterion(outputs, y)
loss.backward()
optimizer.step()
total_loss += loss.item()
_, predicted = outputs.max(1)
total += y.size(0)
if not use_mixup:
correct += predicted.eq(y).sum().item()
else:
correct += (lam * predicted.eq(y_a).sum().item()
+ (1 - lam) * predicted.eq(y_b).sum().item())
return total_loss / len(loader), correct / total
def eval_epoch(model, loader, criterion, device):
model.eval()
correct = 0
total = 0
with torch.no_grad():
for x, y in loader:
x, y = x.to(device), y.to(device)
outputs = model(x)
_, predicted = outputs.max(1)
total += y.size(0)
correct += predicted.eq(y).sum().item()
return correct / total
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--data', default='dataset/binary/train')
parser.add_argument('--val_data', default='dataset/binary/val')
parser.add_argument('--epochs', type=int, default=30)
parser.add_argument('--batch_size', type=int, default=16)
parser.add_argument('--lr', type=float, default=1e-3)
parser.add_argument('--resume', default='')
parser.add_argument('--output', default='train_output')
args = parser.parse_args()
set_seed(42)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
train_ds = AudioDataset(args.data, augment=True)
val_ds = AudioDataset(args.val_data, augment=False) if os.path.isdir(args.val_data) else None
# Weighted sampler
class_counts = np.bincount(train_ds.labels)
class_weights = 1.0 / class_counts
sample_weights = [class_weights[l] for l in train_ds.labels]
sampler = WeightedRandomSampler(sample_weights, len(sample_weights), replacement=True)
train_loader = DataLoader(train_ds, batch_size=args.batch_size, sampler=sampler)
val_loader = DataLoader(val_ds, batch_size=args.batch_size, shuffle=False) if val_ds else None
model = CNNGRU(num_classes=len(train_ds.classes)).to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=args.lr)
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.5)
start_epoch = 0
if args.resume and os.path.isfile(args.resume):
ckpt = torch.load(args.resume, map_location=device)
model.load_state_dict(ckpt['model'])
optimizer.load_state_dict(ckpt['optimizer'])
start_epoch = ckpt['epoch'] + 1
print(f"Resumed from epoch {start_epoch}")
os.makedirs(args.output, exist_ok=True)
best_acc = 0.0
for epoch in range(start_epoch, args.epochs):
train_loss, train_acc = train_epoch(model, train_loader, criterion, optimizer, device)
val_acc = eval_epoch(model, val_loader, criterion, device) if val_loader else 0.0
scheduler.step()
print(f"Epoch {epoch+1}/{args.epochs} | Loss: {train_loss:.4f} | Train Acc: {train_acc:.4f} | Val Acc: {val_acc:.4f}")
if val_acc > best_acc:
best_acc = val_acc
torch.save({
'epoch': epoch,
'model': model.state_dict(),
'optimizer': optimizer.state_dict(),
'acc': val_acc
}, os.path.join(args.output, 'best_model.pth'))
# Save label map
label_map = {str(i): c for i, c in enumerate(train_ds.classes)}
with open(os.path.join(args.output, 'label_map.json'), 'w') as f:
json.dump(label_map, f, indent=2)
print(f"Training complete. Best val acc: {best_acc:.4f}")
if __name__ == '__main__':
import torchaudio
main()

@ -1,60 +0,0 @@
#!/usr/bin/env python3
"""
C++/Python 特征一致性验证
对比 C++ FeatureExtractor 输出与 Python librosa 输出
"""
import argparse
import numpy as np
import torch
import torchaudio
import torchaudio.functional as F
import librosa
def extract_python_mel(wav_path, sr=16000, n_fft=2048, hop_length=512, n_mels=64, fmax=8000):
y, sr_loaded = librosa.load(wav_path, sr=sr, mono=True)
mel = librosa.feature.melspectrogram(
y=y, sr=sr, n_fft=n_fft, hop_length=hop_length,
n_mels=n_mels, fmax=fmax, htk=True, center=False
)
mel_db = librosa.power_to_db(mel, ref=1.0)
# Normalize per mel
mel_db = (mel_db - mel_db.mean(axis=1, keepdims=True)) / (mel_db.std(axis=1, keepdims=True) + 1e-8)
# Pad/truncate to 63 frames
if mel_db.shape[1] < 63:
mel_db = np.pad(mel_db, ((0, 0), (0, 63 - mel_db.shape[1])), mode='edge')
else:
mel_db = mel_db[:, :63]
return mel_db
def load_cpp_mel(bin_path, n_mels=64, n_frames=63):
with open(bin_path, 'rb') as f:
data = np.fromfile(f, dtype=np.float32)
return data.reshape(n_mels, n_frames)
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--wav', required=True)
parser.add_argument('--cpp_bin', required=True, help='C++ extracted mel binary')
parser.add_argument('--sr', type=int, default=16000)
args = parser.parse_args()
py_mel = extract_python_mel(args.wav, sr=args.sr)
cpp_mel = load_cpp_mel(args.cpp_bin)
diff = np.abs(py_mel - cpp_mel)
print(f"Python shape: {py_mel.shape}, C++ shape: {cpp_mel.shape}")
print(f"Max absolute diff: {diff.max():.6f}")
print(f"Mean absolute diff: {diff.mean():.6f}")
print(f"RMSE: {np.sqrt(np.mean((py_mel - cpp_mel)**2)):.6f}")
if diff.max() < 0.01:
print("[PASS] Feature consistency within tolerance.")
else:
print("[FAIL] Feature mismatch detected.")
if __name__ == '__main__':
main()

@ -1,75 +0,0 @@
#!/usr/bin/env python3
"""
验证集准确率验证
遍历 val 目录使用 ONNX Runtime 进行推理统计准确率
"""
import argparse
import os
import json
import numpy as np
import onnxruntime as ort
import torchaudio
import torchaudio.functional as F
import librosa
def extract_mel(wav_path, sr=16000, n_fft=2048, hop_length=512, n_mels=64, fmax=8000):
y, _ = librosa.load(wav_path, sr=sr, mono=True)
mel = librosa.feature.melspectrogram(
y=y, sr=sr, n_fft=n_fft, hop_length=hop_length,
n_mels=n_mels, fmax=fmax, htk=True, center=False
)
mel_db = librosa.power_to_db(mel, ref=1.0)
mel_db = (mel_db - mel_db.mean(axis=1, keepdims=True)) / (mel_db.std(axis=1, keepdims=True) + 1e-8)
if mel_db.shape[1] < 63:
mel_db = np.pad(mel_db, ((0, 0), (0, 63 - mel_db.shape[1])), mode='edge')
else:
mel_db = mel_db[:, :63]
return mel_db.astype(np.float32).T # [T, n_mels]
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--model', default='models/gunshot_classifier.onnx')
parser.add_argument('--val_dir', default='dataset/val')
parser.add_argument('--label_map', default='models/label_map.json')
parser.add_argument('--threshold', type=float, default=0.7)
args = parser.parse_args()
with open(args.label_map) as f:
label_map = json.load(f)
idx_to_label = {int(k): v for k, v in label_map.items()}
session = ort.InferenceSession(args.model, providers=['CPUExecutionProvider'])
input_name = session.get_inputs()[0].name
correct = 0
total = 0
for true_label in sorted(os.listdir(args.val_dir)):
cls_dir = os.path.join(args.val_dir, true_label)
if not os.path.isdir(cls_dir):
continue
for fname in sorted(os.listdir(cls_dir)):
if not fname.endswith('.wav'):
continue
path = os.path.join(cls_dir, fname)
mel = extract_mel(path)
mel = np.expand_dims(mel, axis=0) # [1, T, n_mels]
outputs = session.run(None, {input_name: mel})
probs = outputs[0][0]
pred_idx = int(np.argmax(probs))
pred_conf = float(probs[pred_idx])
pred_label = idx_to_label.get(pred_idx, 'unknown')
if pred_conf < args.threshold:
pred_label = 'ambient'
if pred_label == true_label:
correct += 1
total += 1
print(f"{path}: pred={pred_label} (conf={pred_conf:.4f}), true={true_label}")
acc = correct / total if total > 0 else 0.0
print(f"\nTotal: {total}, Correct: {correct}, Accuracy: {acc:.4f} ({acc*100:.2f}%)")
if __name__ == '__main__':
main()

@ -1,80 +0,0 @@
#include "acoustic_analyzer/core/audio_buffer.h"
#include <algorithm>
#include <cstring>
namespace acoustic {
AudioBuffer::AudioBuffer(std::size_t capacity_samples, std::size_t num_channels)
: capacity_frames_(capacity_samples > 0 ? capacity_samples : 1),
num_channels_(num_channels),
buffer_(capacity_frames_ * num_channels),
head_(0),
tail_(0),
size_(0) {
}
std::size_t AudioBuffer::Push(const std::vector<float>& samples) {
if (num_channels_ == 0) return 0;
std::size_t total_frames = samples.size() / num_channels_;
for (std::size_t f = 0; f < total_frames; ++f) {
for (std::size_t ch = 0; ch < num_channels_; ++ch) {
std::size_t idx = head_ * num_channels_ + ch;
buffer_[idx] = samples[f * num_channels_ + ch];
}
head_ = (head_ + 1) % capacity_frames_;
if (size_ < capacity_frames_) {
++size_;
} else {
tail_ = (tail_ + 1) % capacity_frames_;
}
}
return total_frames;
}
std::vector<float> AudioBuffer::Pop(std::size_t num_frames) {
std::size_t frames_to_read = std::min(num_frames, size_);
std::vector<float> result(frames_to_read * num_channels_);
for (std::size_t f = 0; f < frames_to_read; ++f) {
for (std::size_t ch = 0; ch < num_channels_; ++ch) {
result[f * num_channels_ + ch] = buffer_[tail_ * num_channels_ + ch];
}
tail_ = (tail_ + 1) % capacity_frames_;
}
size_ -= frames_to_read;
return result;
}
std::vector<float> AudioBuffer::Get(std::size_t offset, std::size_t num_frames) const {
if (offset >= size_) return {};
std::size_t frames_to_read = std::min(num_frames, size_ - offset);
std::vector<float> result(frames_to_read * num_channels_);
std::size_t start = (tail_ + offset) % capacity_frames_;
for (std::size_t f = 0; f < frames_to_read; ++f) {
std::size_t pos = (start + f) % capacity_frames_;
for (std::size_t ch = 0; ch < num_channels_; ++ch) {
result[f * num_channels_ + ch] = buffer_[pos * num_channels_ + ch];
}
}
return result;
}
std::size_t AudioBuffer::Size() const noexcept {
return size_;
}
std::size_t AudioBuffer::Capacity() const noexcept {
return capacity_frames_;
}
void AudioBuffer::Clear() {
head_ = 0;
tail_ = 0;
size_ = 0;
std::fill(buffer_.begin(), buffer_.end(), 0.0f);
}
std::size_t AudioBuffer::NumChannels() const noexcept {
return num_channels_;
}
} // namespace acoustic

@ -1,94 +0,0 @@
#include "acoustic_analyzer/core/distance_estimator.h"
#include <cmath>
#include <algorithm>
#include <memory>
namespace acoustic {
struct DistanceEstimator::Impl {
DistanceConfig config;
float kalman_x_ = 0.0f;
float kalman_p_ = 1.0f;
bool kalman_init_ = false;
explicit Impl(const DistanceConfig& cfg) : config(cfg) {}
float ComputeSplInternal(const std::vector<float>& audio_signal) {
if (audio_signal.empty()) return 0.0f;
double sum = 0.0;
for (float s : audio_signal) {
sum += static_cast<double>(s) * s;
}
float rms = std::sqrt(static_cast<float>(sum) / audio_signal.size());
float spl = 20.0f * std::log10(std::max(rms, 1e-10f)) + 94.0f + config.spl_calibration_offset;
return spl;
}
float GetReferenceSpl(const std::string& sound_type) {
if (sound_type == "gunshot") return config.ref_spl_gunshot;
if (sound_type == "artillery") return config.ref_spl_artillery;
if (sound_type == "explosion") return config.ref_spl_explosion;
if (sound_type == "threat") return config.ref_spl_gunshot; // binary model uses gunshot ref
// Fallback: use gunshot reference
return config.ref_spl_gunshot > 0.0f ? config.ref_spl_gunshot : 150.0f;
}
float EstimateInternal(float spl, const std::string& sound_type) {
float L_ref = GetReferenceSpl(sound_type);
float alpha = config.attenuation_alpha;
if (alpha < 1e-6f) alpha = 0.6f;
float d = std::pow(10.0f, (L_ref - spl) / (20.0f * alpha));
d = std::max(d, 1.0f);
d = std::min(d, 5000.0f);
return d;
}
float UpdateKalmanInternal(float raw_distance) {
float Q = config.kalman_process_noise;
float R = config.kalman_measurement_noise;
if (!kalman_init_) {
kalman_x_ = raw_distance;
kalman_p_ = 1.0f;
kalman_init_ = true;
}
// Predict
kalman_p_ += Q;
// Update
float K = kalman_p_ / (kalman_p_ + R);
kalman_x_ += K * (raw_distance - kalman_x_);
kalman_p_ = (1.0f - K) * kalman_p_;
return kalman_x_;
}
};
DistanceEstimator::DistanceEstimator(const DistanceConfig& config)
: impl_(std::make_unique<Impl>(config)) {}
DistanceEstimator::~DistanceEstimator() = default;
DistanceEstimator::DistanceEstimator(DistanceEstimator&&) noexcept = default;
DistanceEstimator& DistanceEstimator::operator=(DistanceEstimator&&) noexcept = default;
float DistanceEstimator::ComputeSpl(const std::vector<float>& audio_signal) {
return impl_->ComputeSplInternal(audio_signal);
}
float DistanceEstimator::Estimate(float spl, const std::string& sound_type) {
return impl_->EstimateInternal(spl, sound_type);
}
float DistanceEstimator::UpdateKalman(float raw_distance) {
return impl_->UpdateKalmanInternal(raw_distance);
}
void DistanceEstimator::Reset() {
impl_->kalman_init_ = false;
impl_->kalman_p_ = 1.0f;
}
const DistanceConfig& DistanceEstimator::Config() const noexcept {
return impl_->config;
}
} // namespace acoustic

@ -1,186 +0,0 @@
#include "acoustic_analyzer/core/feature_extractor.h"
#include "acoustic_analyzer/core/fft_utils.h"
#include <cmath>
#include <algorithm>
#include <numeric>
#include <fstream>
#include <memory>
namespace acoustic {
struct FeatureExtractor::Impl {
int sample_rate;
int n_fft;
int hop_length;
int n_mels;
float f_min;
float f_max;
float preemphasis;
std::vector<float> window;
std::vector<float> mel_filter_bank;
bool mel_loaded = false;
Impl(int sr, int fft, int hop, int mels, float fmin, float fmax, float pre)
: sample_rate(sr), n_fft(fft), hop_length(hop), n_mels(mels),
f_min(fmin), f_max(fmax), preemphasis(pre) {
window.resize(n_fft);
for (int i = 0; i < n_fft; ++i) {
window[i] = 0.5f - 0.5f * std::cos(2.0f * static_cast<float>(M_PI) * i / (n_fft - 1));
}
int n_fft_bins = n_fft / 2 + 1;
mel_filter_bank.resize(n_mels * n_fft_bins);
// Try to load from models/mel_filter_bank.bin
std::ifstream f("models/mel_filter_bank.bin", std::ios::binary);
if (f) {
f.read(reinterpret_cast<char*>(mel_filter_bank.data()),
mel_filter_bank.size() * sizeof(float));
if (f.gcount() == static_cast<std::streamsize>(mel_filter_bank.size() * sizeof(float))) {
mel_loaded = true;
}
}
if (!mel_loaded) {
GenerateMelFilterBank();
mel_loaded = true;
}
}
void GenerateMelFilterBank() {
int n_fft_bins = n_fft / 2 + 1;
float mel_min = 2595.0f * std::log10(1.0f + f_min / 700.0f);
float mel_max = 2595.0f * std::log10(1.0f + f_max / 700.0f);
std::vector<float> mel_points(n_mels + 2);
for (int i = 0; i < n_mels + 2; ++i) {
float mel = mel_min + i * (mel_max - mel_min) / (n_mels + 1);
mel_points[i] = 700.0f * (std::pow(10.0f, mel / 2595.0f) - 1.0f);
}
std::vector<float> bin_points(n_mels + 2);
for (int i = 0; i < n_mels + 2; ++i) {
bin_points[i] = std::floor((n_fft + 1) * mel_points[i] / sample_rate);
}
for (int i = 0; i < n_mels; ++i) {
for (int j = 0; j < n_fft_bins; ++j) {
float val = 0.0f;
float left = bin_points[i];
float center = bin_points[i + 1];
float right = bin_points[i + 2];
if (j >= left && j <= center && center > left) {
val = (j - left) / (center - left);
} else if (j >= center && j <= right && right > center) {
val = (right - j) / (right - center);
}
mel_filter_bank[i * n_fft_bins + j] = val;
}
}
}
Eigen::MatrixXf ComputeMelSpec(const std::vector<float>& audio) {
std::size_t n_samples = audio.size();
int n_fft_bins = n_fft / 2 + 1;
int n_frames = static_cast<int>((static_cast<int>(n_samples) - n_fft) / hop_length) + 1;
if (n_frames < 1) n_frames = 1;
std::vector<float> preemph(n_samples);
apply_preemphasis(audio.data(), preemph.data(), n_samples, preemphasis);
std::vector<float> fft_buf(n_fft);
std::vector<float> power(n_fft_bins);
Eigen::MatrixXf mel_spec(n_mels, n_frames);
for (int t = 0; t < n_frames; ++t) {
int start = t * hop_length;
for (int i = 0; i < n_fft; ++i) {
std::size_t idx = start + i;
fft_buf[i] = (idx < n_samples ? preemph[idx] : 0.0f) * window[i];
}
compute_power_spectrum(fft_buf.data(), n_fft, power.data());
for (int m = 0; m < n_mels; ++m) {
float sum = 0.0f;
for (int b = 0; b < n_fft_bins; ++b) {
sum += power[b] * mel_filter_bank[m * n_fft_bins + b];
}
mel_spec(m, t) = std::log(std::max(sum, 1e-10f));
}
}
// Normalize per mel bin (standardization)
for (int m = 0; m < n_mels; ++m) {
float mean = 0.0f;
for (int t = 0; t < n_frames; ++t) {
mean += mel_spec(m, t);
}
mean /= n_frames;
float variance = 0.0f;
for (int t = 0; t < n_frames; ++t) {
float d = mel_spec(m, t) - mean;
variance += d * d;
}
float stddev = std::sqrt(variance / n_frames + 1e-8f);
for (int t = 0; t < n_frames; ++t) {
mel_spec(m, t) = (mel_spec(m, t) - mean) / stddev;
}
}
// Pad or truncate to target 63 frames with edge padding
const int target_frames = 63;
if (n_frames < target_frames) {
Eigen::MatrixXf padded(n_mels, target_frames);
int copy_frames = n_frames;
int pad_left = (target_frames - copy_frames) / 2;
padded.block(0, pad_left, n_mels, copy_frames) = mel_spec.block(0, 0, n_mels, copy_frames);
// Edge padding on left
for (int t = 0; t < pad_left; ++t) {
padded.col(t) = mel_spec.col(0);
}
// Edge padding on right
for (int t = pad_left + copy_frames; t < target_frames; ++t) {
padded.col(t) = mel_spec.col(copy_frames - 1);
}
return padded;
} else if (n_frames > target_frames) {
return mel_spec.block(0, 0, n_mels, target_frames);
}
return mel_spec;
}
};
FeatureExtractor::FeatureExtractor(int sample_rate,
int n_fft,
int hop_length,
int n_mels,
float f_min,
float f_max,
float preemphasis)
: impl_(std::make_unique<Impl>(sample_rate, n_fft, hop_length, n_mels, f_min, f_max, preemphasis)) {}
FeatureExtractor::~FeatureExtractor() = default;
FeatureExtractor::FeatureExtractor(FeatureExtractor&&) noexcept = default;
FeatureExtractor& FeatureExtractor::operator=(FeatureExtractor&&) noexcept = default;
Eigen::MatrixXf FeatureExtractor::MelSpectrogram(const std::vector<float>& audio_samples) {
return impl_->ComputeMelSpec(audio_samples);
}
std::vector<Eigen::MatrixXf> FeatureExtractor::MelSpectrogramMultiChannel(
const std::vector<float>& audio_samples,
std::size_t num_channels) {
if (num_channels == 0) return {};
std::size_t total = audio_samples.size();
std::size_t samples_per_channel = total / num_channels;
std::vector<Eigen::MatrixXf> results;
results.reserve(num_channels);
for (std::size_t ch = 0; ch < num_channels; ++ch) {
std::vector<float> ch_audio(samples_per_channel);
for (std::size_t i = 0; i < samples_per_channel; ++i) {
ch_audio[i] = audio_samples[i * num_channels + ch];
}
results.push_back(impl_->ComputeMelSpec(ch_audio));
}
return results;
}
int FeatureExtractor::SampleRate() const noexcept { return impl_->sample_rate; }
int FeatureExtractor::NFFT() const noexcept { return impl_->n_fft; }
int FeatureExtractor::HopLength() const noexcept { return impl_->hop_length; }
int FeatureExtractor::NMels() const noexcept { return impl_->n_mels; }
} // namespace acoustic

@ -1,92 +0,0 @@
#include "acoustic_analyzer/core/fft_utils.h"
#include <cmath>
#include <fstream>
namespace acoustic {
void apply_preemphasis(const float* in, float* out, std::size_t n, float coef) {
if (n == 0) return;
out[0] = in[0];
for (std::size_t i = 1; i < n; ++i) {
out[i] = in[i] - coef * in[i - 1];
}
}
void apply_hann_window(float* data, std::size_t n) {
for (std::size_t i = 0; i < n; ++i) {
float w = 0.5f - 0.5f * std::cos(2.0f * static_cast<float>(M_PI) * i / (n - 1));
data[i] *= w;
}
}
namespace {
// Iterative Cooley-Tukey complex FFT (power-of-2 sizes only).
struct Cpx { float r = 0.0f, i = 0.0f; };
void fft_inplace(Cpx* data, int n, bool inverse) {
int j = 0;
for (int i = 1; i < n; ++i) {
int bit = n >> 1;
for (; j & bit; bit >>= 1) j ^= bit;
j ^= bit;
if (i < j) std::swap(data[i], data[j]);
}
for (int len = 2; len <= n; len <<= 1) {
float ang = 2.0f * static_cast<float>(M_PI) / len * (inverse ? 1.0f : -1.0f);
float wlen_r = std::cos(ang);
float wlen_i = std::sin(ang);
for (int i = 0; i < n; i += len) {
float wr = 1.0f, wi = 0.0f;
for (int k = 0; k < len / 2; ++k) {
int u = i + k;
int v = i + k + len / 2;
float ur = data[u].r, ui = data[u].i;
float vr = data[v].r * wr - data[v].i * wi;
float vi = data[v].r * wi + data[v].i * wr;
data[u].r = ur + vr;
data[u].i = ui + vi;
data[v].r = ur - vr;
data[v].i = ui - vi;
float wnr = wr * wlen_r - wi * wlen_i;
float wni = wr * wlen_i + wi * wlen_r;
wr = wnr;
wi = wni;
}
}
}
if (inverse) {
float inv_n = 1.0f / n;
for (int i = 0; i < n; ++i) {
data[i].r *= inv_n;
data[i].i *= inv_n;
}
}
}
} // anonymous namespace
void compute_power_spectrum(const float* frame, int n_fft, float* power_out) {
int bins = n_fft / 2 + 1;
std::vector<Cpx> data(n_fft);
for (int i = 0; i < n_fft; ++i) {
data[i].r = frame[i];
data[i].i = 0.0f;
}
fft_inplace(data.data(), n_fft, false);
for (int i = 0; i < bins; ++i) {
power_out[i] = data[i].r * data[i].r + data[i].i * data[i].i;
}
}
bool load_mel_filter_bank(const std::string& path, int n_mels, int n_fft_bins,
std::vector<float>& filter_bank) {
std::ifstream f(path, std::ios::binary);
if (!f) return false;
std::size_t expected = static_cast<std::size_t>(n_mels) * n_fft_bins;
filter_bank.resize(expected);
f.read(reinterpret_cast<char*>(filter_bank.data()), expected * sizeof(float));
return f.gcount() == static_cast<std::streamsize>(expected * sizeof(float));
}
} // namespace acoustic

@ -1,236 +0,0 @@
#include "acoustic_analyzer/core/gcc_phat_localizer.h"
#include <cmath>
#include <vector>
#include <Eigen/SVD>
#include <memory>
namespace acoustic {
namespace {
// Simple iterative Cooley-Tukey FFT for power-of-2 sizes.
// Sufficient for GCC-PHAT delay estimation.
struct Complex { float r = 0.0f, i = 0.0f; };
void fft_iterative(Complex* data, int n, bool inverse) {
int j = 0;
for (int i = 1; i < n; ++i) {
int bit = n >> 1;
for (; j & bit; bit >>= 1) j ^= bit;
j ^= bit;
if (i < j) std::swap(data[i], data[j]);
}
for (int len = 2; len <= n; len <<= 1) {
float ang = 2.0f * static_cast<float>(M_PI) / len * (inverse ? 1.0f : -1.0f);
float wlen_r = std::cos(ang);
float wlen_i = std::sin(ang);
for (int i = 0; i < n; i += len) {
float wr = 1.0f, wi = 0.0f;
for (int k = 0; k < len / 2; ++k) {
int u = i + k;
int v = i + k + len / 2;
float ur = data[u].r, ui = data[u].i;
float vr = data[v].r * wr - data[v].i * wi;
float vi = data[v].r * wi + data[v].i * wr;
data[u].r = ur + vr;
data[u].i = ui + vi;
data[v].r = ur - vr;
data[v].i = ui - vi;
float wnr = wr * wlen_r - wi * wlen_i;
float wni = wr * wlen_i + wi * wlen_r;
wr = wnr;
wi = wni;
}
}
}
if (inverse) {
float inv_n = 1.0f / n;
for (int i = 0; i < n; ++i) {
data[i].r *= inv_n;
data[i].i *= inv_n;
}
}
}
} // anonymous namespace
struct GccPhatLocalizer::Impl {
MicArrayConfig mic_config;
int sample_rate;
float max_tdoa;
int interp_factor;
Impl(const MicArrayConfig& config, int sr, float max_t, int interp)
: mic_config(config), sample_rate(sr), max_tdoa(max_t), interp_factor(interp) {
if (mic_config.layout == "cross" && mic_config.positions.empty()) {
float s = mic_config.spacing;
mic_config.positions = {
{0.0f, s, 0.0f},
{ s, 0.0f, 0.0f},
{0.0f, -s, 0.0f},
{-s, 0.0f, 0.0f}
};
} else if (mic_config.layout == "linear" && mic_config.positions.empty()) {
float s = mic_config.spacing;
for (uint32_t i = 0; i < mic_config.num_mics; ++i) {
mic_config.positions.push_back({i * s, 0.0f, 0.0f});
}
} else if (mic_config.layout == "circular" && mic_config.positions.empty()) {
float s = mic_config.spacing;
float radius = s / (2.0f * std::sin(static_cast<float>(M_PI) / mic_config.num_mics));
for (uint32_t i = 0; i < mic_config.num_mics; ++i) {
float angle = 2.0f * static_cast<float>(M_PI) * i / mic_config.num_mics;
mic_config.positions.push_back({radius * std::cos(angle), radius * std::sin(angle), 0.0f});
}
}
}
float ComputeGccPhatDelay(const float* ch1, const float* ch2, std::size_t n) {
if (n == 0) return 0.0f;
// 防止 nfft 溢出:上限 1<<22约 400 万点,足够 16kHz@120s 音频)
constexpr std::size_t MAX_NFFT = static_cast<std::size_t>(1) << 22;
std::size_t nfft = 1;
while (nfft < 2 * n && nfft < MAX_NFFT) nfft <<= 1;
if (nfft > MAX_NFFT) nfft = MAX_NFFT;
std::vector<Complex> a(nfft), b(nfft), c(nfft);
for (std::size_t i = 0; i < nfft; ++i) {
a[i].r = (i < n) ? ch1[i] : 0.0f; a[i].i = 0;
b[i].r = (i < n) ? ch2[i] : 0.0f; b[i].i = 0;
}
fft_iterative(a.data(), static_cast<int>(nfft), false);
fft_iterative(b.data(), static_cast<int>(nfft), false);
for (std::size_t i = 0; i < nfft; ++i) {
float real = a[i].r * b[i].r + a[i].i * b[i].i;
float imag = a[i].i * b[i].r - a[i].r * b[i].i;
float mag = std::sqrt(real * real + imag * imag) + 1e-12f;
c[i].r = real / mag;
c[i].i = imag / mag;
}
fft_iterative(c.data(), static_cast<int>(nfft), true);
float max_val = -1e30f;
int max_idx = 0;
for (std::size_t i = 0; i < nfft; ++i) {
float v = c[i].r * c[i].r + c[i].i * c[i].i;
if (v > max_val) { max_val = v; max_idx = static_cast<int>(i); }
}
if (max_idx > static_cast<int>(nfft) / 2) max_idx -= static_cast<int>(nfft);
// Parabolic interpolation
int idx_left = max_idx - 1;
int idx_right = max_idx + 1;
float y0 = c[(idx_left + static_cast<int>(nfft)) % static_cast<int>(nfft)].r;
float y1 = c[(max_idx + static_cast<int>(nfft)) % static_cast<int>(nfft)].r;
float y2 = c[(idx_right + static_cast<int>(nfft)) % static_cast<int>(nfft)].r;
float denom = y0 - 2.0f * y1 + y2;
float p = 0.0f;
if (std::abs(denom) > 1e-6f) {
p = 0.5f * (y0 - y2) / denom;
}
float delay_samples = max_idx + p;
float delay_sec = delay_samples / static_cast<float>(sample_rate);
// Note: GCC-PHAT IFFT peak at tau corresponds to ch2[n] = ch1[n - tau],
// so tau is actually the negative of the physical delay of ch2 relative to ch1.
return -delay_sec;
}
bool SolveDirection(const std::vector<float>& delays, float& azimuth, float& elevation) {
int M = mic_config.num_mics;
if (M < 2 || delays.size() != static_cast<std::size_t>(M * (M - 1) / 2)) {
return false;
}
const float c = 343.0f;
int pair_idx = 0;
int pairs = M * (M - 1) / 2;
Eigen::MatrixXf A(pairs, 3);
Eigen::VectorXf b(pairs);
for (int i = 0; i < M; ++i) {
for (int j = i + 1; j < M; ++j) {
Eigen::Vector3f d;
d.x() = mic_config.positions[j][0] - mic_config.positions[i][0];
d.y() = mic_config.positions[j][1] - mic_config.positions[i][1];
d.z() = mic_config.positions[j][2] - mic_config.positions[i][2];
A.row(pair_idx) = d.transpose();
b(pair_idx) = -delays[pair_idx] * c;
pair_idx++;
}
}
// SVD least squares
Eigen::JacobiSVD<Eigen::MatrixXf> svd(A, Eigen::ComputeThinU | Eigen::ComputeThinV);
Eigen::Vector3f dir = svd.solve(b);
if (dir.norm() < 1e-6f) return false;
dir.normalize();
// Azimuth: angle from y-axis (North) clockwise
azimuth = std::atan2(dir.x(), dir.y()) * 180.0f / static_cast<float>(M_PI);
if (azimuth < 0.0f) azimuth += 360.0f;
// Elevation
float horiz = std::sqrt(dir.x() * dir.x() + dir.y() * dir.y());
elevation = std::atan2(dir.z(), horiz) * 180.0f / static_cast<float>(M_PI);
return true;
}
};
GccPhatLocalizer::GccPhatLocalizer(const MicArrayConfig& mic_config,
int sample_rate,
float max_tdoa,
int interp_factor)
: impl_(std::make_unique<Impl>(mic_config, sample_rate, max_tdoa, interp_factor)) {}
GccPhatLocalizer::~GccPhatLocalizer() = default;
GccPhatLocalizer::GccPhatLocalizer(GccPhatLocalizer&&) noexcept = default;
GccPhatLocalizer& GccPhatLocalizer::operator=(GccPhatLocalizer&&) noexcept = default;
std::pair<float, float> GccPhatLocalizer::Localize(const Eigen::MatrixXf& multi_channel_audio) {
int M = impl_->mic_config.num_mics;
if (M < 2 || multi_channel_audio.cols() < M) return {0.0f, 0.0f};
std::size_t n = multi_channel_audio.rows();
if (n == 0) return {0.0f, 0.0f};
std::vector<float> delays;
for (int i = 0; i < M; ++i) {
for (int j = i + 1; j < M; ++j) {
std::vector<float> ch1(n), ch2(n);
for (std::size_t s = 0; s < n; ++s) {
ch1[s] = multi_channel_audio(static_cast<int>(s), i);
ch2[s] = multi_channel_audio(static_cast<int>(s), j);
}
float d = impl_->ComputeGccPhatDelay(ch1.data(), ch2.data(), n);
if (std::abs(d) > impl_->max_tdoa) d = (d > 0 ? impl_->max_tdoa : -impl_->max_tdoa);
delays.push_back(d);
}
}
float azimuth = 0.0f, elevation = 0.0f;
if (impl_->SolveDirection(delays, azimuth, elevation)) {
return {azimuth, elevation};
}
return {0.0f, 0.0f};
}
std::pair<float, float> GccPhatLocalizer::Localize(const std::vector<float>& flat_samples,
std::size_t num_channels) {
if (num_channels == 0) return {0.0f, 0.0f};
std::size_t total = flat_samples.size();
std::size_t samples_per_channel = total / num_channels;
if (samples_per_channel == 0) return {0.0f, 0.0f};
Eigen::MatrixXf mat(static_cast<int>(samples_per_channel), static_cast<int>(num_channels));
for (std::size_t ch = 0; ch < num_channels; ++ch) {
for (std::size_t i = 0; i < samples_per_channel; ++i) {
mat(static_cast<int>(i), static_cast<int>(ch)) = flat_samples[i * num_channels + ch];
}
}
return Localize(mat);
}
const MicArrayConfig& GccPhatLocalizer::MicConfig() const noexcept {
return impl_->mic_config;
}
} // namespace acoustic

@ -1,238 +0,0 @@
#include "acoustic_analyzer/core/gunshot_classifier.h"
#include <cmath>
#include <fstream>
#include <sstream>
#include <iostream>
#include <algorithm>
#include <numeric>
#include <memory>
#ifdef _WIN32
#include <windows.h>
#endif
#include <onnxruntime_cxx_api.h>
namespace acoustic {
struct GunshotClassifier::Impl {
ClassifierConfig config;
std::vector<std::string> labels;
bool loaded = false;
Ort::Env env;
std::unique_ptr<Ort::Session> session;
std::unique_ptr<Ort::SessionOptions> session_options;
explicit Impl(const ClassifierConfig& cfg)
: config(cfg), env(ORT_LOGGING_LEVEL_WARNING, "acoustic") {
session_options = std::make_unique<Ort::SessionOptions>();
session_options->SetIntraOpNumThreads(1);
session_options->SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_EXTENDED);
}
bool LoadLabelMap(const std::string& path) {
std::ifstream f(path);
if (!f) return false;
std::string content((std::istreambuf_iterator<char>(f)),
std::istreambuf_iterator<char>());
labels.clear();
// Try simple JSON object format: {"0": "ambient", "1": "threat"}
if (content.find('{') != std::string::npos) {
std::size_t pos = 0;
while ((pos = content.find('"', pos)) != std::string::npos) {
std::size_t key_start = pos + 1;
std::size_t key_end = content.find('"', key_start);
if (key_end == std::string::npos) break;
std::string key = content.substr(key_start, key_end - key_start);
int idx = -1;
try { idx = std::stoi(key); } catch (...) {}
std::size_t colon = content.find(':', key_end);
if (colon == std::string::npos) break;
std::size_t val_quote = content.find('"', colon);
if (val_quote == std::string::npos) break;
std::size_t val_end = content.find('"', val_quote + 1);
if (val_end == std::string::npos) break;
std::string label = content.substr(val_quote + 1, val_end - val_quote - 1);
if (idx >= 0) {
if (idx >= static_cast<int>(labels.size())) labels.resize(idx + 1);
labels[idx] = label;
}
pos = val_end + 1;
}
} else {
// Fallback: "index: label" line format
std::size_t line_start = 0;
while (line_start < content.size()) {
std::size_t line_end = content.find('\n', line_start);
if (line_end == std::string::npos) line_end = content.size();
std::string line = content.substr(line_start, line_end - line_start);
line_start = line_end + 1;
std::size_t pos = line.find(':');
if (pos == std::string::npos) continue;
int idx = 0;
try {
idx = std::stoi(line.substr(0, pos));
} catch (...) {
continue;
}
std::string label = line.substr(pos + 1);
std::size_t start = label.find_first_not_of(" \t\r\n");
std::size_t end = label.find_last_not_of(" \t\r\n");
if (start != std::string::npos && end != std::string::npos) {
label = label.substr(start, end - start + 1);
} else {
label.clear();
}
if (idx >= static_cast<int>(labels.size())) {
labels.resize(idx + 1);
}
labels[idx] = label;
}
}
// Remove empty entries and compact
std::vector<std::string> cleaned;
for (const auto& l : labels) {
if (!l.empty()) cleaned.push_back(l);
}
labels = std::move(cleaned);
return !labels.empty();
}
bool LoadModel(const std::string& path) {
// Verify file exists before attempting ONNX load
std::ifstream check(path, std::ios::binary);
if (!check) {
std::cerr << "[ERROR] Model file not found: " << path << std::endl;
return false;
}
check.close();
#ifdef _WIN32
int wlen = MultiByteToWideChar(CP_UTF8, 0, path.c_str(), -1, nullptr, 0);
std::wstring wpath;
if (wlen > 0) {
wpath.resize(static_cast<std::size_t>(wlen));
MultiByteToWideChar(CP_UTF8, 0, path.c_str(), -1, &wpath[0], wlen);
}
try {
session = std::make_unique<Ort::Session>(env, wpath.c_str(), *session_options);
} catch (const Ort::Exception& e) {
std::cerr << "[ERROR] ONNX Runtime failed to load model: " << e.what() << std::endl;
return false;
}
#else
try {
session = std::make_unique<Ort::Session>(env, path.c_str(), *session_options);
} catch (const Ort::Exception& e) {
std::cerr << "[ERROR] ONNX Runtime failed to load model: " << e.what() << std::endl;
return false;
}
#endif
return true;
}
bool Initialize() {
if (!config.label_map_path.empty()) {
if (!LoadLabelMap(config.label_map_path)) {
// Continue even if label map fails; may have embedded labels
}
}
if (!config.model_path.empty()) {
if (!LoadModel(config.model_path)) {
return false;
}
}
loaded = session != nullptr;
return loaded;
}
std::pair<std::string, float> Predict(const Eigen::MatrixXf& mel_spectrogram) {
if (!loaded || session == nullptr) return {"unknown", 0.0f};
int n_mels = static_cast<int>(mel_spectrogram.rows());
int n_frames = static_cast<int>(mel_spectrogram.cols());
// Flatten to std::vector<float> with layout [batch, time_frames, n_mels]
std::vector<float> input_data(static_cast<std::size_t>(n_mels) * n_frames);
for (int t = 0; t < n_frames; ++t) {
for (int m = 0; m < n_mels; ++m) {
input_data[t * n_mels + m] = mel_spectrogram(m, t);
}
}
// Cached memory info and I/O names (created once on first use)
static Ort::MemoryInfo memory_info = Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault);
static const char* input_names[] = {"input"};
static const char* output_names[] = {"output"};
std::vector<int64_t> input_shape = {1, n_frames, n_mels};
Ort::Value input_tensor = Ort::Value::CreateTensor<float>(
memory_info, input_data.data(), input_data.size(),
input_shape.data(), input_shape.size());
// Run inference
auto output_tensors = session->Run(
Ort::RunOptions{nullptr},
input_names, &input_tensor, 1,
output_names, 1);
// Get output
float* output_data = output_tensors.front().GetTensorMutableData<float>();
auto output_shape = output_tensors.front().GetTensorTypeAndShapeInfo().GetShape();
std::size_t num_classes = labels.empty() ? static_cast<std::size_t>(output_shape.back()) : labels.size();
if (num_classes == 0) num_classes = 2;
// Softmax
std::vector<float> probs(num_classes);
float max_val = output_data[0];
for (std::size_t i = 1; i < num_classes; ++i) {
if (output_data[i] > max_val) max_val = output_data[i];
}
float sum = 0.0f;
for (std::size_t i = 0; i < num_classes; ++i) {
probs[i] = std::exp(output_data[i] - max_val);
sum += probs[i];
}
for (std::size_t i = 0; i < num_classes; ++i) {
probs[i] /= sum;
}
// Find best
std::size_t best_idx = 0;
for (std::size_t i = 1; i < num_classes; ++i) {
if (probs[i] > probs[best_idx]) best_idx = i;
}
if (best_idx >= labels.size()) return {"unknown", probs[best_idx]};
return {labels[best_idx], probs[best_idx]};
}
};
GunshotClassifier::GunshotClassifier(const ClassifierConfig& config)
: impl_(std::make_unique<Impl>(config)) {
impl_->Initialize();
}
GunshotClassifier::~GunshotClassifier() = default;
GunshotClassifier::GunshotClassifier(GunshotClassifier&&) noexcept = default;
GunshotClassifier& GunshotClassifier::operator=(GunshotClassifier&&) noexcept = default;
std::pair<std::string, float> GunshotClassifier::Predict(const Eigen::MatrixXf& mel_spectrogram) {
return impl_->Predict(mel_spectrogram);
}
const std::vector<std::string>& GunshotClassifier::Labels() const noexcept {
return impl_->labels;
}
bool GunshotClassifier::IsLoaded() const noexcept {
return impl_->loaded;
}
} // namespace acoustic

@ -1,395 +0,0 @@
#include "acoustic_analyzer/core/pipeline.h"
#include "acoustic_analyzer/core/audio_buffer.h"
#include "acoustic_analyzer/core/feature_extractor.h"
#include "acoustic_analyzer/core/gunshot_classifier.h"
#include "acoustic_analyzer/core/gcc_phat_localizer.h"
#include "acoustic_analyzer/core/distance_estimator.h"
#include "acoustic_analyzer/core/threat_tracker.h"
// yaml-cpp is optional: if absent, Pipeline still works but FromYaml returns defaults
#if defined(__has_include)
# if __has_include(<yaml-cpp/yaml.h>)
# define ACOUSTIC_HAS_YAML_CPP 1
# endif
#endif
#ifdef ACOUSTIC_HAS_YAML_CPP
#include <yaml-cpp/yaml.h>
#endif
#include <cmath>
#include <algorithm>
#include <numeric>
#include <memory>
namespace acoustic {
struct Pipeline::Impl {
PipelineConfig config;
std::unique_ptr<AudioBuffer> audio_buffer;
std::unique_ptr<FeatureExtractor> feature_extractor;
std::unique_ptr<GunshotClassifier> classifier;
std::unique_ptr<GccPhatLocalizer> localizer;
std::unique_ptr<DistanceEstimator> distance_estimator;
std::unique_ptr<ThreatTracker> threat_tracker;
// Temporal smoothing state: per-class probability history
std::vector<std::vector<float>> class_history_;
int smoothing_window_ = 3;
explicit Impl(const PipelineConfig& cfg) : config(cfg) {
smoothing_window_ = static_cast<int>(cfg.classifier.smoothing_window);
if (smoothing_window_ < 1) smoothing_window_ = 1;
ResetModules();
}
void ResetModules() {
std::size_t chunk_samples = static_cast<std::size_t>(config.sample_rate * config.chunk_duration);
audio_buffer = std::make_unique<AudioBuffer>(chunk_samples,
static_cast<int>(config.mic_array.num_mics));
feature_extractor = std::make_unique<FeatureExtractor>(
static_cast<int>(config.sample_rate),
static_cast<int>(config.n_fft),
static_cast<int>(config.hop_length),
static_cast<int>(config.n_mels),
config.f_min,
config.f_max,
config.preemphasis);
classifier = std::make_unique<GunshotClassifier>(config.classifier);
if (config.mic_array.num_mics >= 2) {
localizer = std::make_unique<GccPhatLocalizer>(
config.mic_array,
static_cast<int>(config.sample_rate),
config.max_tdoa,
config.interpolation_factor);
}
distance_estimator = std::make_unique<DistanceEstimator>(config.distance);
threat_tracker = std::make_unique<ThreatTracker>(
config.min_event_interval, 15.0f, 5);
// Dynamic class history dimension from classifier labels
int num_classes = static_cast<int>(classifier->Labels().size());
if (num_classes == 0) num_classes = 2; // fallback
class_history_.assign(num_classes, std::vector<float>());
}
bool VadGate(const std::vector<float>& audio) {
if (audio.empty()) return false;
// Energy in dB
float energy = 0.0f;
for (float s : audio) energy += s * s;
energy /= static_cast<float>(audio.size());
float energy_db = 10.0f * std::log10(energy + 1e-10f);
// Zero-crossing rate
std::size_t zcr = 0;
for (std::size_t i = 1; i < audio.size(); ++i) {
if ((audio[i] >= 0.0f) != (audio[i - 1] >= 0.0f)) ++zcr;
}
float zcr_rate = static_cast<float>(zcr) / (audio.size() - 1);
bool energy_ok = energy_db > -60.0f;
bool zcr_ok = zcr_rate > 0.01f && zcr_rate < 0.5f;
return energy_ok && zcr_ok;
}
std::vector<float> TemporalSmoothing(const std::vector<float>& probs) {
int num_classes = static_cast<int>(probs.size());
if (num_classes == 0) return probs;
if (static_cast<int>(class_history_.size()) != num_classes) {
class_history_.assign(num_classes, std::vector<float>());
}
std::vector<float> smoothed(num_classes);
for (int i = 0; i < num_classes; ++i) {
auto& hist = class_history_[i];
hist.push_back(probs[i]);
if (static_cast<int>(hist.size()) > smoothing_window_) {
hist.erase(hist.begin());
}
float sum = 0.0f;
for (float v : hist) sum += v;
smoothed[i] = sum / static_cast<float>(hist.size());
}
return smoothed;
}
AcousticFrame Process(const std::vector<float>& audio_samples) {
AcousticFrame frame;
frame.timestamp = std::chrono::steady_clock::now();
frame.is_clear = true;
if (audio_samples.empty()) return frame;
// Push interleaved multi-channel audio into ring buffer
audio_buffer->Push(audio_samples);
std::size_t chunk_samples = static_cast<std::size_t>(config.sample_rate * config.chunk_duration);
// VAD on first channel
std::size_t avail = audio_buffer->Size();
std::size_t offset = (avail > chunk_samples) ? (avail - chunk_samples) : 0;
auto latest_flat = audio_buffer->Get(offset, chunk_samples);
std::vector<float> latest;
latest.reserve(latest_flat.size() / config.mic_array.num_mics);
for (std::size_t i = 0; i < latest_flat.size(); i += config.mic_array.num_mics) {
latest.push_back(latest_flat[i]);
}
if (!VadGate(latest)) {
return frame;
}
// Feature extraction: multi-channel Mel spectrogram
auto mel_specs = feature_extractor->MelSpectrogramMultiChannel(
audio_samples, config.mic_array.num_mics);
// Average across channels in frequency domain
Eigen::MatrixXf avg_mel;
if (!mel_specs.empty()) {
avg_mel = mel_specs[0];
for (std::size_t i = 1; i < mel_specs.size(); ++i) {
avg_mel += mel_specs[i];
}
if (mel_specs.size() > 1) {
avg_mel /= static_cast<float>(mel_specs.size());
}
}
// Classification
auto [label, confidence] = classifier->Predict(avg_mel);
// Build probability vector for temporal smoothing
const auto& labels = classifier->Labels();
int num_classes = static_cast<int>(labels.size());
if (num_classes == 0) num_classes = 2;
std::vector<float> probs(num_classes, 0.0f);
int label_idx = -1;
for (int i = 0; i < num_classes; ++i) {
if (i < static_cast<int>(labels.size()) && labels[i] == label) {
label_idx = i;
break;
}
}
if (label_idx >= 0) probs[label_idx] = confidence;
auto smoothed = TemporalSmoothing(probs);
// Find best after smoothing
float best_conf = 0.0f;
int best_idx = 0;
for (int i = 0; i < num_classes; ++i) {
if (smoothed[i] > best_conf) {
best_conf = smoothed[i];
best_idx = i;
}
}
if (best_idx < static_cast<int>(labels.size()) &&
best_conf >= config.confidence_threshold) {
std::string best_label = labels[best_idx];
// Localization
float azimuth = 0.0f;
float elevation = 0.0f;
if (localizer && config.mic_array.num_mics >= 2) {
Eigen::MatrixXf audio_mat(static_cast<int>(chunk_samples),
static_cast<int>(config.mic_array.num_mics));
for (std::size_t ch = 0; ch < config.mic_array.num_mics; ++ch) {
std::vector<float> ch_data;
ch_data.reserve(latest_flat.size() / config.mic_array.num_mics);
for (std::size_t i = ch; i < latest_flat.size(); i += config.mic_array.num_mics) {
ch_data.push_back(latest_flat[i]);
}
for (std::size_t i = 0; i < ch_data.size(); ++i) {
audio_mat(static_cast<int>(i), static_cast<int>(ch)) = ch_data[i];
}
}
auto [az, el] = localizer->Localize(audio_mat);
azimuth = az;
elevation = el;
}
// Distance estimation
float distance = -1.0f;
float distance_conf = 0.0f;
if (distance_estimator) {
float spl = distance_estimator->ComputeSpl(latest);
float raw_distance = distance_estimator->Estimate(spl, best_label);
distance = distance_estimator->UpdateKalman(raw_distance);
distance_conf = 0.8f;
}
AcousticThreat threat;
threat.timestamp = frame.timestamp;
threat.threat_id = "";
threat.sound_type = best_label;
threat.confidence = best_conf;
threat.azimuth = azimuth;
threat.elevation = elevation;
threat.distance = distance;
threat.distance_confidence = distance_conf;
auto filtered = threat_tracker->Update({threat});
frame.threats = std::move(filtered);
frame.is_clear = frame.threats.empty();
}
return frame;
}
};
Pipeline::Pipeline(const PipelineConfig& config)
: impl_(std::make_unique<Impl>(config)) {}
Pipeline::~Pipeline() = default;
Pipeline::Pipeline(Pipeline&&) noexcept = default;
Pipeline& Pipeline::operator=(Pipeline&&) noexcept = default;
AcousticFrame Pipeline::Process(const std::vector<float>& audio_samples) {
return impl_->Process(audio_samples);
}
PipelineConfig Pipeline::FromYaml(const std::string& yaml_path) {
PipelineConfig config;
#ifdef ACOUSTIC_HAS_YAML_CPP
try {
YAML::Node root = YAML::LoadFile(yaml_path);
if (root["audio"]) {
auto audio = root["audio"];
if (audio["sample_rate"])
config.sample_rate = audio["sample_rate"].as<uint32_t>();
if (audio["chunk_duration"])
config.chunk_duration = audio["chunk_duration"].as<float>();
if (audio["hop_duration"])
config.hop_duration = audio["hop_duration"].as<float>();
if (audio["n_channels"])
config.mic_array.num_mics = audio["n_channels"].as<uint32_t>();
}
if (root["source"]) {
auto src = root["source"];
if (src["type"]) src["type"].as<std::string>();
}
if (root["mic_array"]) {
auto mic = root["mic_array"];
if (mic["num_mics"])
config.mic_array.num_mics = mic["num_mics"].as<uint32_t>();
if (mic["layout"])
config.mic_array.layout = mic["layout"].as<std::string>();
if (mic["spacing"])
config.mic_array.spacing = mic["spacing"].as<float>();
if (mic["positions"]) {
config.mic_array.positions.clear();
for (const auto& pos : mic["positions"]) {
std::array<float, 3> p = {0.0f, 0.0f, 0.0f};
if (pos.size() > 0) p[0] = pos[0].as<float>();
if (pos.size() > 1) p[1] = pos[1].as<float>();
if (pos.size() > 2) p[2] = pos[2].as<float>();
config.mic_array.positions.push_back(p);
}
}
}
if (root["features"]) {
auto feat = root["features"];
if (feat["n_mels"]) config.n_mels = feat["n_mels"].as<uint32_t>();
if (feat["n_fft"]) {
config.n_fft = feat["n_fft"].as<uint32_t>();
}
if (feat["hop_length"]) {
config.hop_length = feat["hop_length"].as<uint32_t>();
}
if (feat["f_min"]) {
config.f_min = feat["f_min"].as<float>();
}
if (feat["f_max"]) {
config.f_max = feat["f_max"].as<float>();
}
if (feat["preemphasis"]) {
config.preemphasis = feat["preemphasis"].as<float>();
}
}
if (root["classifier"]) {
auto cls = root["classifier"];
if (cls["model_path"])
config.classifier.model_path = cls["model_path"].as<std::string>();
if (cls["label_map_path"])
config.classifier.label_map_path = cls["label_map_path"].as<std::string>();
if (cls["threshold"])
config.classifier.threshold = cls["threshold"].as<float>();
if (cls["smoothing_window"])
config.classifier.smoothing_window = cls["smoothing_window"].as<uint32_t>();
}
if (root["localization"]) {
auto loc = root["localization"];
if (loc["max_tdoa"]) {
config.max_tdoa = loc["max_tdoa"].as<float>();
}
if (loc["interpolation_factor"]) {
config.interpolation_factor = loc["interpolation_factor"].as<int>();
}
}
if (root["distance"]) {
auto dist = root["distance"];
if (dist["attenuation_alpha"])
config.distance.attenuation_alpha = dist["attenuation_alpha"].as<float>();
if (dist["kalman_process_noise"])
config.distance.kalman_process_noise =
dist["kalman_process_noise"].as<float>();
if (dist["kalman_measurement_noise"])
config.distance.kalman_measurement_noise =
dist["kalman_measurement_noise"].as<float>();
if (dist["reference_spl"]) {
auto ref = dist["reference_spl"];
if (ref["threat"])
config.distance.ref_spl_gunshot = ref["threat"].as<float>();
if (ref["gunshot"])
config.distance.ref_spl_gunshot = ref["gunshot"].as<float>();
if (ref["artillery"])
config.distance.ref_spl_artillery = ref["artillery"].as<float>();
if (ref["explosion"])
config.distance.ref_spl_explosion = ref["explosion"].as<float>();
}
}
if (root["output"]) {
auto out = root["output"];
if (out["publish_rate"]) {
// Not stored directly
}
if (out["min_event_interval"])
config.min_event_interval = out["min_event_interval"].as<float>();
}
} catch (const YAML::Exception& e) {
(void)e;
// On parse error, return defaults
}
#else
(void)yaml_path;
#endif
return config;
}
void Pipeline::Reset() {
impl_->audio_buffer->Clear();
impl_->threat_tracker->Reset();
impl_->distance_estimator->Reset();
for (auto& hist : impl_->class_history_) {
hist.clear();
}
}
const PipelineConfig& Pipeline::Config() const noexcept {
return impl_->config;
}
} // namespace acoustic

@ -1,128 +0,0 @@
#include "acoustic_analyzer/core/threat_tracker.h"
#include <sstream>
#include <iomanip>
#include <cmath>
#include <algorithm>
#include <chrono>
namespace acoustic {
ThreatTracker::ThreatTracker(float min_event_interval,
float azimuth_gate_deg,
int max_missing_frames)
: min_event_interval_(min_event_interval),
azimuth_gate_deg_(azimuth_gate_deg),
max_missing_frames_(max_missing_frames),
id_counter_(0) {}
std::string ThreatTracker::GenerateId() {
std::ostringstream oss;
oss << "THREAT-" << std::setw(4) << std::setfill('0') << ++id_counter_;
return oss.str();
}
void ThreatTracker::Reset() {
tracks_.clear();
id_counter_ = 0;
}
std::size_t ThreatTracker::NumTracked() const noexcept {
return tracks_.size();
}
std::vector<AcousticThreat> ThreatTracker::Update(
const std::vector<AcousticThreat>& detected_threats) {
auto now = std::chrono::steady_clock::now();
// Increment missing frames for all existing tracks
for (auto& track : tracks_) {
track.missing_frames++;
}
// Nearest-neighbor data association (azimuth difference < azimuth_gate_deg_)
std::vector<bool> det_matched(detected_threats.size(), false);
for (auto& track : tracks_) {
if (track.missing_frames > max_missing_frames_) continue;
float best_diff = azimuth_gate_deg_;
std::size_t best_idx = detected_threats.size();
for (std::size_t d = 0; d < detected_threats.size(); ++d) {
if (det_matched[d]) continue;
float diff = std::abs(detected_threats[d].azimuth - track.last_azimuth);
if (diff > 180.0f) diff = 360.0f - diff;
if (diff < best_diff) {
best_diff = diff;
best_idx = d;
}
}
if (best_idx < detected_threats.size()) {
const auto& det = detected_threats[best_idx];
track.last_azimuth = det.azimuth;
track.last_elevation = det.elevation;
track.sound_type = det.sound_type;
track.confidence = det.confidence;
track.distance = det.distance;
track.distance_confidence = det.distance_confidence;
track.missing_frames = 0;
det_matched[best_idx] = true;
}
}
// Initialize new tracks for unmatched detections
for (std::size_t d = 0; d < detected_threats.size(); ++d) {
if (det_matched[d]) continue;
TrackedThreat track;
track.threat_id = GenerateId();
track.last_azimuth = detected_threats[d].azimuth;
track.last_elevation = detected_threats[d].elevation;
track.sound_type = detected_threats[d].sound_type;
track.confidence = detected_threats[d].confidence;
track.distance = detected_threats[d].distance;
track.distance_confidence = detected_threats[d].distance_confidence;
track.missing_frames = 0;
track.ever_published = false;
tracks_.push_back(std::move(track));
}
// Remove stale tracks (连续 5 帧未检测淘汰)
tracks_.erase(
std::remove_if(tracks_.begin(), tracks_.end(),
[this](const TrackedThreat& t) {
return t.missing_frames > max_missing_frames_;
}),
tracks_.end());
// Filter by min_event_interval and return active threats
std::vector<AcousticThreat> output;
for (auto& track : tracks_) {
if (track.missing_frames != 0) continue;
bool should_publish = !track.ever_published;
if (!should_publish) {
float elapsed = std::chrono::duration_cast<std::chrono::duration<float>>(
now - track.last_publish_time).count();
should_publish = (elapsed >= min_event_interval_);
}
if (should_publish) {
track.ever_published = true;
track.last_publish_time = now;
AcousticThreat threat;
threat.timestamp = now;
threat.threat_id = track.threat_id;
threat.sound_type = track.sound_type;
threat.confidence = track.confidence;
threat.azimuth = track.last_azimuth;
threat.elevation = track.last_elevation;
threat.distance = track.distance;
threat.distance_confidence = track.distance_confidence;
output.push_back(threat);
}
}
return output;
}
} // namespace acoustic

@ -1,157 +0,0 @@
#include "acoustic_analyzer/io/mobile_phone_source.h"
#include <ros/ros.h>
#include <cstring>
#include <cmath>
#include <chrono>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>
#include <fcntl.h>
#include <arpa/inet.h>
namespace acoustic {
struct MobilePhoneSource::Impl {
int port_ = 0;
int sample_rate_ = 16000;
float timeout_sec_ = 10.0f;
int sockfd_ = -1;
bool running_ = false;
std::thread recv_thread_;
std::mutex mutex_;
std::condition_variable cv_;
std::queue<float> buffer_;
std::size_t max_queue_size_ = 0; // 队列容量上限(采样点数)
Impl(int port, int sample_rate, float timeout_sec)
: port_(port), sample_rate_(sample_rate), timeout_sec_(timeout_sec) {
// 默认上限5 秒音频数据
max_queue_size_ = static_cast<std::size_t>(sample_rate) * 5;
}
~Impl() { Close(); }
bool Open() {
sockfd_ = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd_ < 0) return false;
int opt = 1;
setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_port = htons(port_);
addr.sin_addr.s_addr = INADDR_ANY;
if (bind(sockfd_, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) < 0) {
::close(sockfd_);
sockfd_ = -1;
return false;
}
int flags = fcntl(sockfd_, F_GETFL, 0);
fcntl(sockfd_, F_SETFL, flags | O_NONBLOCK);
running_ = true;
recv_thread_ = std::thread(&Impl::ReceiveLoop, this);
return true;
}
void Close() {
running_ = false;
if (recv_thread_.joinable()) recv_thread_.join();
if (sockfd_ >= 0) {
::close(sockfd_);
sockfd_ = -1;
}
}
void ReceiveLoop() {
std::vector<float> packet_buf(2048);
sockaddr_in client_addr{};
socklen_t addr_len = sizeof(client_addr);
auto start = std::chrono::steady_clock::now();
while (running_) {
ssize_t n = recvfrom(sockfd_, packet_buf.data(),
packet_buf.size() * sizeof(float),
0, reinterpret_cast<sockaddr*>(&client_addr), &addr_len);
if (n > 0) {
std::size_t samples = static_cast<std::size_t>(n) / sizeof(float);
std::lock_guard<std::mutex> lock(mutex_);
for (std::size_t i = 0; i < samples; ++i) {
// 队列超限时丢弃最旧数据(环形队列策略)
if (buffer_.size() >= max_queue_size_ && !buffer_.empty()) {
buffer_.pop();
}
buffer_.push(packet_buf[i]);
}
cv_.notify_one();
start = std::chrono::steady_clock::now();
} else {
auto now = std::chrono::steady_clock::now();
float elapsed = std::chrono::duration<float>(now - start).count();
if (elapsed > timeout_sec_) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
}
}
std::vector<float> Read(std::size_t max_samples) {
std::unique_lock<std::mutex> lock(mutex_);
if (buffer_.empty()) {
cv_.wait_for(lock, std::chrono::milliseconds(100));
}
std::size_t available = std::min(max_samples, buffer_.size());
std::vector<float> out(available);
for (std::size_t i = 0; i < available; ++i) {
out[i] = buffer_.front();
buffer_.pop();
}
return out;
}
};
MobilePhoneSource::MobilePhoneSource(ros::NodeHandle& nh,
const std::string& topic,
float timeout_sec,
int sample_rate)
: impl_(std::make_unique<Impl>(0, sample_rate, timeout_sec)) {
(void)nh;
(void)topic;
// Topic-based ROS subscriber could be added here; currently using UDP fallback.
}
MobilePhoneSource::~MobilePhoneSource() = default;
bool MobilePhoneSource::Open() {
return impl_->Open();
}
std::vector<float> MobilePhoneSource::Read(std::size_t num_samples) {
return impl_->Read(num_samples);
}
void MobilePhoneSource::Close() {
impl_->Close();
}
std::size_t MobilePhoneSource::NumChannels() const {
return 1;
}
int MobilePhoneSource::SampleRate() const {
return impl_->sample_rate_;
}
bool MobilePhoneSource::IsOpen() const {
return impl_->sockfd_ >= 0;
}
} // namespace acoustic

@ -1,167 +0,0 @@
#include "acoustic_analyzer/io/wav_file_source.h"
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdint>
namespace acoustic {
struct WavHeader {
char riff[4];
uint32_t file_size;
char wave[4];
char fmt[4];
uint32_t fmt_size;
uint16_t audio_format;
uint16_t num_channels;
uint32_t sample_rate;
uint32_t byte_rate;
uint16_t block_align;
uint16_t bits_per_sample;
};
WavFileSource::WavFileSource(const std::string& path, int target_sample_rate)
: path_(path), target_sample_rate_(target_sample_rate), fp_(nullptr) {}
WavFileSource::~WavFileSource() {
close();
}
void WavFileSource::close() {
if (fp_) {
fclose(fp_);
fp_ = nullptr;
}
}
bool WavFileSource::open() {
close();
fp_ = fopen(path_.c_str(), "rb");
if (!fp_) return false;
if (!parse_wav_header()) {
close();
return false;
}
return true;
}
bool WavFileSource::parse_wav_header() {
WavHeader hdr;
if (fread(&hdr, sizeof(hdr), 1, fp_) != 1) return false;
if (std::memcmp(hdr.riff, "RIFF", 4) != 0 || std::memcmp(hdr.wave, "WAVE", 4) != 0) return false;
if (hdr.audio_format != 1 && hdr.audio_format != 3) return false; // PCM or IEEE float
num_channels_ = hdr.num_channels;
file_sample_rate_ = hdr.sample_rate;
bits_per_sample_ = hdr.bits_per_sample;
// Find data chunk
char chunk_id[4];
uint32_t chunk_size;
while (fread(chunk_id, 4, 1, fp_) == 1) {
if (fread(&chunk_size, 4, 1, fp_) != 1) return false;
if (std::memcmp(chunk_id, "data", 4) == 0) {
data_start_ = ftell(fp_);
int bytes_per_sample = bits_per_sample_ / 8;
if (bytes_per_sample == 0) bytes_per_sample = 1;
total_samples_ = chunk_size / (num_channels_ * bytes_per_sample);
read_pos_ = 0;
return true;
}
fseek(fp_, chunk_size, SEEK_CUR);
}
return false;
}
namespace {
float convert_sample(const uint8_t* raw, int bytes, bool is_float) {
if (is_float) {
if (bytes == 4) {
float val;
std::memcpy(&val, raw, 4);
return val;
}
return 0.0f;
}
// Integer PCM
if (bytes == 1) {
int32_t val = static_cast<int32_t>(raw[0]) - 128;
return val / 128.0f;
} else if (bytes == 2) {
int16_t val;
std::memcpy(&val, raw, 2);
return val / 32768.0f;
} else if (bytes == 3) {
int32_t val = (static_cast<int32_t>(raw[2]) << 16)
| (static_cast<int32_t>(raw[1]) << 8)
| static_cast<int32_t>(raw[0]);
// Sign-extend 24-bit to 32-bit
if (val & 0x800000) val |= 0xFF000000;
return val / 8388608.0f;
} else if (bytes >= 4) {
int32_t val;
std::memcpy(&val, raw, 4);
return std::max(-1.0f, std::min(1.0f, val / 2147483648.0f));
}
return 0.0f;
}
} // anonymous namespace
void WavFileSource::resample_if_needed(std::vector<float>& mono, int src_rate, int dst_rate) {
if (src_rate == dst_rate || dst_rate <= 0) return;
double ratio = static_cast<double>(dst_rate) / src_rate;
std::size_t new_len = static_cast<std::size_t>(mono.size() * ratio);
std::vector<float> resampled(new_len);
for (std::size_t i = 0; i < new_len; ++i) {
double src_idx = i / ratio;
std::size_t idx0 = static_cast<std::size_t>(src_idx);
std::size_t idx1 = std::min(idx0 + 1, mono.size() - 1);
double frac = src_idx - idx0;
resampled[i] = static_cast<float>(mono[idx0] * (1.0 - frac) + mono[idx1] * frac);
}
mono.swap(resampled);
}
std::size_t WavFileSource::read(std::vector<std::vector<float>>& out, std::size_t max_samples) {
if (!fp_) return 0;
std::size_t samples_to_read = std::min(max_samples, total_samples_ - read_pos_);
if (samples_to_read == 0) return 0;
int bytes_per_sample = bits_per_sample_ / 8;
if (bytes_per_sample == 0) bytes_per_sample = 1;
int block_align = num_channels_ * bytes_per_sample;
bool is_float = (bits_per_sample_ == 32); // Assume 32-bit is float if audio_format==3
std::vector<uint8_t> raw(samples_to_read * block_align);
fseek(fp_, static_cast<long>(data_start_ + read_pos_ * block_align), SEEK_SET);
std::size_t read_blocks = fread(raw.data(), block_align, samples_to_read, fp_);
if (read_blocks == 0) return 0;
out.resize(num_channels_);
for (int ch = 0; ch < num_channels_; ++ch) {
out[ch].resize(read_blocks);
}
for (std::size_t i = 0; i < read_blocks; ++i) {
for (int ch = 0; ch < num_channels_; ++ch) {
out[ch][i] = convert_sample(&raw[i * block_align + ch * bytes_per_sample],
bytes_per_sample, is_float);
}
}
read_pos_ += read_blocks;
// Resample if needed
if (target_sample_rate_ > 0 && target_sample_rate_ != file_sample_rate_) {
for (int ch = 0; ch < num_channels_; ++ch) {
resample_if_needed(out[ch], file_sample_rate_, target_sample_rate_);
}
return out[0].size();
}
return read_blocks;
}
} // namespace acoustic

@ -1,15 +0,0 @@
#include <ros/ros.h>
#include "acoustic_analyzer/ros/acoustic_node.h"
#include "acoustic_analyzer/ros/threat_publisher.h"
int main(int argc, char** argv) {
ros::init(argc, argv, "acoustic_analyzer_node");
ros::NodeHandle nh;
ros::NodeHandle pnh("~");
acoustic::ThreatPublisher publisher(nh);
auto node = acoustic::create_acoustic_node(nh, pnh, &publisher);
node->run();
return 0;
}

@ -1,202 +0,0 @@
#include "acoustic_analyzer/ros/acoustic_node.h"
#include "acoustic_analyzer/core/pipeline.h"
#include "acoustic_analyzer/io/wav_file_source.h"
#ifndef _WIN32
#include "acoustic_analyzer/io/mobile_phone_source.h"
#endif
#include <ros/ros.h>
#include <ros/package.h>
#include <std_msgs/Float32MultiArray.h>
#include <yaml-cpp/yaml.h>
#include <memory>
#include <vector>
#include <string>
#include <regex>
namespace acoustic {
class AcousticNode {
public:
AcousticNode(ros::NodeHandle& nh, ros::NodeHandle& pnh, ThreatPublisher* publisher)
: nh_(nh), pnh_(pnh), threat_publisher_(publisher) {
load_params();
init_pipeline();
init_source();
}
void run() {
ros::Rate rate(params_.publish_rate);
while (ros::ok()) {
ros::spinOnce();
if (source_type_ == "wav_file" && wav_source_) {
process_wav_source();
}
rate.sleep();
}
}
private:
ros::NodeHandle nh_;
ros::NodeHandle pnh_;
ros::Subscriber audio_sub_;
ThreatPublisher* threat_publisher_ = nullptr;
std::string source_type_;
std::unique_ptr<Pipeline> pipeline_;
std::unique_ptr<WavFileSource> wav_source_;
#ifndef _WIN32
std::unique_ptr<MobilePhoneSource> phone_source_;
#endif
struct Params {
int sample_rate = 16000;
float chunk_duration = 2.0f;
float hop_duration = 0.5f;
int n_channels = 4;
std::string source_type;
std::string mobile_phone_topic;
float mobile_phone_timeout = 10.0f;
std::string wav_file_path;
std::string model_path;
std::string label_map_path;
float publish_rate = 10.0f;
} params_;
/**
* @brief YAML $(find package_name)
*/
static std::string expandFindPath(const std::string& path) {
static const std::regex findRegex(R"(\$\(find\s+([^)]+)\))");
std::smatch match;
std::string result = path;
if (std::regex_search(path, match, findRegex) && match.size() > 1) {
std::string pkgPath = ros::package::getPath(match[1].str());
if (!pkgPath.empty()) {
result = match.prefix().str() + pkgPath + match.suffix().str();
} else {
ROS_WARN("ros::package::getPath(%s) returned empty, keeping raw path", match[1].str().c_str());
}
}
return result;
}
void load_params() {
std::string yaml_path;
pnh_.param<std::string>("config_file", yaml_path, "");
if (!yaml_path.empty()) {
try {
YAML::Node config = YAML::LoadFile(yaml_path);
params_.sample_rate = config["audio"]["sample_rate"].as<int>(16000);
params_.chunk_duration = config["audio"]["chunk_duration"].as<float>(2.0f);
params_.hop_duration = config["audio"]["hop_duration"].as<float>(0.5f);
params_.n_channels = config["audio"]["n_channels"].as<int>(4);
params_.source_type = config["source"]["type"].as<std::string>("mic_array");
params_.mobile_phone_topic = config["source"]["mobile_phone_topic"].as<std::string>("/mobile_phone/audio");
params_.mobile_phone_timeout = config["source"]["mobile_phone_timeout"].as<float>(10.0f);
params_.wav_file_path = config["source"]["wav_file_path"].as<std::string>("");
params_.model_path = expandFindPath(config["classifier"]["model_path"].as<std::string>(""));
params_.label_map_path = expandFindPath(config["classifier"]["label_map_path"].as<std::string>(""));
params_.publish_rate = config["output"]["publish_rate"].as<float>(10.0f);
} catch (const std::exception& e) {
ROS_WARN("Failed to load YAML config: %s", e.what());
}
}
source_type_ = params_.source_type;
}
void init_pipeline() {
PipelineConfig config;
config.sample_rate = static_cast<uint32_t>(params_.sample_rate);
config.chunk_duration = params_.chunk_duration;
config.hop_duration = params_.hop_duration;
config.n_mels = 64;
config.classifier.model_path = params_.model_path;
config.classifier.label_map_path = params_.label_map_path;
config.classifier.threshold = 0.7f;
config.classifier.smoothing_window = 3;
config.mic_array.num_mics = static_cast<uint32_t>(params_.n_channels);
config.mic_array.layout = "cross";
config.mic_array.spacing = 0.15f;
pipeline_ = std::make_unique<Pipeline>(config);
}
void init_source() {
if (source_type_ == "mic_array") {
audio_sub_ = nh_.subscribe("/microphone_array/audio", 10,
&AcousticNode::on_mic_array_audio, this);
} else if (source_type_ == "mobile_phone") {
audio_sub_ = nh_.subscribe(params_.mobile_phone_topic, 10,
&AcousticNode::on_mobile_phone_audio, this);
} else if (source_type_ == "wav_file") {
if (!params_.wav_file_path.empty()) {
wav_source_ = std::make_unique<WavFileSource>(params_.wav_file_path, params_.sample_rate);
if (!wav_source_->open()) {
ROS_ERROR("Failed to open WAV file: %s", params_.wav_file_path.c_str());
}
}
}
}
// Convert multi-channel vector-of-vectors to flat interleaved format
std::vector<float> flatten_audio(const std::vector<std::vector<float>>& audio, int channels) {
if (audio.empty() || channels == 0) return {};
size_t samples = audio[0].size();
std::vector<float> flat(samples * channels);
for (size_t s = 0; s < samples; ++s) {
for (int ch = 0; ch < channels; ++ch) {
flat[s * channels + ch] = (ch < static_cast<int>(audio.size()) && s < audio[ch].size())
? audio[ch][s] : 0.0f;
}
}
return flat;
}
void publish_if_available(const AcousticFrame& frame) {
if (threat_publisher_ && !frame.is_clear) {
threat_publisher_->Publish(frame);
}
}
void on_mic_array_audio(const std_msgs::Float32MultiArray::ConstPtr& msg) {
if (msg->layout.dim.size() < 2) return;
int channels = static_cast<int>(msg->layout.dim[0].size);
int samples = static_cast<int>(msg->layout.dim[1].size);
if (channels == 0 || samples == 0) return;
// Assuming data is interleaved or [channels x samples] row-major
std::vector<float> flat(msg->data.begin(), msg->data.end());
auto frame = pipeline_->Process(flat);
publish_if_available(frame);
}
void on_mobile_phone_audio(const std_msgs::Float32MultiArray::ConstPtr& msg) {
if (msg->data.empty()) return;
std::vector<float> flat(msg->data.begin(), msg->data.end());
auto frame = pipeline_->Process(flat);
publish_if_available(frame);
}
void process_wav_source() {
if (!wav_source_ || !wav_source_->is_open()) return;
size_t chunk_samples = static_cast<size_t>(params_.chunk_duration * params_.sample_rate);
std::vector<std::vector<float>> audio;
size_t got = wav_source_->read(audio, chunk_samples);
if (got == 0) {
ROS_INFO("WAV playback finished");
ros::shutdown();
return;
}
auto flat = flatten_audio(audio, static_cast<int>(wav_source_->num_channels()));
auto frame = pipeline_->Process(flat);
publish_if_available(frame);
}
};
std::unique_ptr<AcousticNode> create_acoustic_node(
ros::NodeHandle& nh,
ros::NodeHandle& pnh,
ThreatPublisher* publisher) {
return std::make_unique<AcousticNode>(nh, pnh, publisher);
}
} // namespace acoustic

@ -1,45 +0,0 @@
#include "acoustic_analyzer/ros/threat_publisher.h"
#include <acoustic_analyzer/AcousticThreat.h>
namespace acoustic {
struct ThreatPublisher::Impl {
ros::Publisher pub_;
std::size_t num_published_ = 0;
};
ThreatPublisher::ThreatPublisher(ros::NodeHandle& nh, const std::string& topic)
: impl_(std::make_unique<Impl>()) {
impl_->pub_ = nh.advertise<acoustic_analyzer::AcousticThreatArray>(topic, 10);
}
ThreatPublisher::~ThreatPublisher() = default;
ThreatPublisher::ThreatPublisher(ThreatPublisher&&) noexcept = default;
ThreatPublisher& ThreatPublisher::operator=(ThreatPublisher&&) noexcept = default;
void ThreatPublisher::Publish(const AcousticFrame& frame) {
acoustic_analyzer::AcousticThreatArray msg;
msg.header.stamp = ros::Time::now();
msg.header.frame_id = "acoustic_array";
for (const auto& t : frame.threats) {
acoustic_analyzer::AcousticThreat threat_msg;
threat_msg.threat_id = t.threat_id;
threat_msg.sound_type = t.sound_type;
threat_msg.confidence = t.confidence;
threat_msg.azimuth = t.azimuth;
threat_msg.elevation = t.elevation;
threat_msg.distance = t.distance;
threat_msg.distance_confidence = t.distance_confidence;
msg.threats.push_back(threat_msg);
}
impl_->pub_.publish(msg);
++impl_->num_published_;
}
std::size_t ThreatPublisher::NumPublished() const noexcept {
return impl_->num_published_;
}
} // namespace acoustic

@ -1,245 +0,0 @@
#include <iostream>
#include <iomanip>
#include <vector>
#include <string>
#include <filesystem>
#include <algorithm>
#include <cmath>
#include <numeric>
#include <map>
#include <chrono>
#include "acoustic_analyzer/core/feature_extractor.h"
#include "acoustic_analyzer/core/gunshot_classifier.h"
#include "acoustic_analyzer/core/distance_estimator.h"
#include "acoustic_analyzer/io/wav_file_source.h"
namespace fs = std::filesystem;
using namespace acoustic;
struct Prediction {
std::string file_path;
std::string true_label;
std::string pred_label;
float confidence = 0.0f;
float distance = -1.0f;
};
void print_usage(const char* prog) {
std::cerr << "Usage: " << prog << " <file_or_dir> [--model <onnx>] [--label_map <json>] [--threshold <float>]" << std::endl;
std::cerr << " --model <path> ONNX model path (default: models/gunshot_classifier.onnx)" << std::endl;
std::cerr << " --label_map <path> Label map file (default: models/label_map.json)" << std::endl;
std::cerr << " --threshold <float> Detection threshold (default: 0.5)" << std::endl;
}
bool ends_with(const std::string& s, const std::string& suffix) {
if (s.size() < suffix.size()) return false;
return s.compare(s.size() - suffix.size(), suffix.size(), suffix) == 0;
}
std::string get_parent_folder_name(const std::string& path) {
fs::path p(path);
if (p.has_parent_path()) {
return p.parent_path().filename().string();
}
return "";
}
float compute_spl(const std::vector<float>& audio) {
if (audio.empty()) return -100.0f;
float rms = 0.0f;
for (float s : audio) rms += s * s;
rms = std::sqrt(rms / audio.size());
return 20.0f * std::log10(rms + 1e-10f) + 94.0f;
}
Prediction process_file(const std::string& path,
GunshotClassifier& classifier,
FeatureExtractor& extractor) {
Prediction result;
result.file_path = path;
result.true_label = get_parent_folder_name(path);
WavFileSource wav(path);
if (!wav.open()) {
std::cerr << "[SKIP] Cannot open: " << path << std::endl;
result.pred_label = "error";
return result;
}
int sr = wav.sample_rate();
std::vector<std::vector<float>> audio;
size_t chunk = static_cast<size_t>(sr * 2.0);
size_t got = wav.read(audio, chunk);
if (got == 0 || audio.empty()) {
result.pred_label = "empty";
return result;
}
Eigen::MatrixXf mel = extractor.MelSpectrogram(audio[0]);
auto [label, confidence] = classifier.Predict(mel);
result.pred_label = label;
result.confidence = confidence;
if (label != "ambient" && confidence > 0.5f) {
DistanceConfig dc;
dc.ref_spl_gunshot = 150.0f;
dc.attenuation_alpha = 0.6f;
DistanceEstimator de(dc);
float spl = compute_spl(audio[0]);
float dist = de.Estimate(spl, label);
dist = de.UpdateKalman(dist);
result.distance = dist;
}
std::cout << "File: " << fs::path(path).filename().string()
<< " | True: " << result.true_label
<< " | Pred: " << label
<< " | Conf: " << std::fixed << std::setprecision(4) << confidence
<< " | Dist: " << std::setprecision(2) << result.distance << "m" << std::endl;
return result;
}
void collect_wav_files(const std::string& target, std::vector<std::string>& out) {
if (fs::is_regular_file(target) && ends_with(target, ".wav")) {
out.push_back(target);
return;
}
if (!fs::is_directory(target)) return;
for (const auto& entry : fs::recursive_directory_iterator(target)) {
if (entry.is_regular_file() && ends_with(entry.path().string(), ".wav")) {
out.push_back(entry.path().string());
}
}
std::sort(out.begin(), out.end());
}
void print_report(const std::vector<Prediction>& results) {
std::map<std::string, int> total_by_true;
std::map<std::string, int> correct_by_true;
std::map<std::string, float> conf_sum_by_true;
std::map<std::string, std::map<std::string, int>> confusion;
int total = 0, correct = 0;
for (const auto& r : results) {
if (r.pred_label == "error" || r.pred_label == "empty") continue;
total++;
total_by_true[r.true_label]++;
conf_sum_by_true[r.true_label] += r.confidence;
confusion[r.true_label][r.pred_label]++;
if (r.true_label == r.pred_label) {
correct++;
correct_by_true[r.true_label]++;
}
}
std::cout << "\n==========================================" << std::endl;
std::cout << " VALIDATION REPORT" << std::endl;
std::cout << "==========================================" << std::endl;
std::cout << "Total samples: " << total << std::endl;
std::cout << "Correct: " << correct << std::endl;
std::cout << "Accuracy: " << std::fixed << std::setprecision(2)
<< (total > 0 ? 100.0f * correct / total : 0.0f) << "%" << std::endl;
std::cout << "\nPer-class breakdown:" << std::endl;
for (const auto& kv : total_by_true) {
const std::string& cls = kv.first;
int cls_total = kv.second;
int cls_correct = correct_by_true[cls];
float avg_conf = conf_sum_by_true[cls] / cls_total;
std::cout << " " << std::setw(10) << std::left << cls
<< " Count: " << std::setw(3) << cls_total
<< " Correct: " << std::setw(3) << cls_correct
<< " Acc: " << std::setw(6) << std::fixed << std::setprecision(2)
<< (100.0f * cls_correct / cls_total) << "%"
<< " AvgConf: " << std::setprecision(4) << avg_conf << std::endl;
}
std::cout << "\nConfusion matrix (rows=true, cols=pred):" << std::endl;
std::vector<std::string> labels;
for (const auto& row : confusion) labels.push_back(row.first);
for (const auto& row : confusion) {
for (const auto& col : row.second) {
if (std::find(labels.begin(), labels.end(), col.first) == labels.end()) {
labels.push_back(col.first);
}
}
}
std::sort(labels.begin(), labels.end());
std::cout << std::setw(12) << " ";
for (const auto& l : labels) std::cout << std::setw(10) << l;
std::cout << std::endl;
for (const auto& true_l : labels) {
std::cout << std::setw(10) << std::left << true_l << " ";
for (const auto& pred_l : labels) {
int count = confusion.count(true_l) ? confusion[true_l].count(pred_l) ? confusion[true_l].at(pred_l) : 0 : 0;
std::cout << std::setw(10) << count;
}
std::cout << std::endl;
}
std::cout << "==========================================" << std::endl;
}
int main(int argc, char** argv) {
if (argc < 2 || std::strcmp(argv[1], "--help") == 0 || std::strcmp(argv[1], "-h") == 0) {
print_usage(argv[0]);
return argc < 2 ? 1 : 0;
}
std::string target = argv[1];
std::string model_path = "models/gunshot_classifier.onnx";
std::string label_map_path = "models/label_map.json";
float threshold = 0.5f;
for (int i = 2; i < argc; ++i) {
if (std::strcmp(argv[i], "--model") == 0 && i + 1 < argc) model_path = argv[++i];
else if (std::strcmp(argv[i], "--label_map") == 0 && i + 1 < argc) label_map_path = argv[++i];
else if (std::strcmp(argv[i], "--threshold") == 0 && i + 1 < argc) threshold = std::stof(argv[++i]);
}
ClassifierConfig cc;
cc.model_path = model_path;
cc.label_map_path = label_map_path;
cc.threshold = threshold;
cc.smoothing_window = 1;
GunshotClassifier classifier(cc);
if (!classifier.IsLoaded()) {
std::cerr << "[ERROR] Failed to load classifier model: " << model_path << std::endl;
return 1;
}
std::cout << "Model loaded: " << model_path << std::endl;
std::cout << "Labels: ";
for (const auto& l : classifier.Labels()) std::cout << l << " ";
std::cout << "\n" << std::endl;
std::vector<std::string> files;
collect_wav_files(target, files);
if (files.empty()) {
std::cerr << "No .wav files found in: " << target << std::endl;
return 1;
}
std::cout << "Found " << files.size() << " WAV file(s)." << std::endl;
FeatureExtractor extractor(16000, 2048, 512, 64, 0.0f, 8000.0f, 0.97f);
std::vector<Prediction> results;
results.reserve(files.size());
double total_ms = 0.0;
for (const auto& f : files) {
auto t0 = std::chrono::steady_clock::now();
results.push_back(process_file(f, classifier, extractor));
auto t1 = std::chrono::steady_clock::now();
total_ms += std::chrono::duration<double, std::milli>(t1 - t0).count();
}
std::cout << "\nTotal inference time: " << std::fixed << std::setprecision(2)
<< total_ms << " ms"
<< " | Avg per file: " << (files.empty() ? 0.0 : total_ms / files.size())
<< " ms" << std::endl;
print_report(results);
return 0;
}

@ -1,331 +0,0 @@
#include <iostream>
#include <iomanip>
#include <vector>
#include <string>
#include <filesystem>
#include <algorithm>
#include <cmath>
#include <numeric>
#include <map>
#include <chrono>
#include <cstring>
#include "acoustic_analyzer/core/pipeline.h"
#include "acoustic_analyzer/io/wav_file_source.h"
namespace fs = std::filesystem;
using namespace acoustic;
struct Prediction {
std::string file_path;
std::string true_label;
std::string pred_label;
float confidence = 0.0f;
float azimuth = 0.0f;
float elevation = 0.0f;
float distance = -1.0f;
bool detected = false;
};
void print_usage(const char* prog) {
std::cerr << "Usage: " << prog << " <file_or_dir> [options]" << std::endl;
std::cerr << "Options:" << std::endl;
std::cerr << " --model <path> ONNX model path (default: models/gunshot_classifier.onnx)" << std::endl;
std::cerr << " --label_map <path> Label map file (default: models/label_map.json)" << std::endl;
std::cerr << " --threshold <float> Detection threshold (default: 0.5)" << std::endl;
std::cerr << " --num_mics <int> Number of channels in WAV (default: 4)" << std::endl;
std::cerr << " --spacing <float> Mic spacing in meters (default: 0.15)" << std::endl;
std::cerr << " --layout <str> Array layout: cross/linear/circular (default: cross)" << std::endl;
std::cerr << " --ref_spl <float> Reference SPL for distance estimation (default: 150)" << std::endl;
std::cerr << " --ground_azimuth <float> Ground-truth azimuth for error calc (optional)" << std::endl;
std::cerr << " --ground_distance <float> Ground-truth distance for error calc (optional)" << std::endl;
}
bool ends_with(const std::string& s, const std::string& suffix) {
if (s.size() < suffix.size()) return false;
return s.compare(s.size() - suffix.size(), suffix.size(), suffix) == 0;
}
std::string get_parent_folder_name(const std::string& path) {
fs::path p(path);
if (p.has_parent_path()) {
return p.parent_path().filename().string();
}
return "";
}
// Convert vector-of-vectors [channels][samples] to flat interleaved [ch0_s0, ch1_s0, ...]
std::vector<float> flatten_audio(const std::vector<std::vector<float>>& audio, int channels) {
if (audio.empty() || channels == 0) return {};
size_t samples = audio[0].size();
std::vector<float> flat(samples * channels);
for (size_t s = 0; s < samples; ++s) {
for (int ch = 0; ch < channels; ++ch) {
flat[s * channels + ch] = (ch < static_cast<int>(audio.size()) && s < audio[ch].size())
? audio[ch][s] : 0.0f;
}
}
return flat;
}
Prediction process_file(const std::string& path,
Pipeline& pipeline,
int num_mics,
float ground_azimuth,
float ground_distance) {
Prediction result;
result.file_path = path;
result.true_label = get_parent_folder_name(path);
WavFileSource wav(path);
if (!wav.open()) {
std::cerr << "[SKIP] Cannot open: " << path << std::endl;
result.pred_label = "error";
return result;
}
int sr = wav.sample_rate();
int file_ch = wav.num_channels();
if (file_ch != num_mics) {
std::cerr << "[WARN] " << path << " has " << file_ch
<< " channels, expected " << num_mics << std::endl;
}
// Use actual file channels if mismatch, but pipeline config should match
int effective_channels = (file_ch < num_mics) ? file_ch : num_mics;
size_t chunk_samples = static_cast<size_t>(sr * pipeline.Config().chunk_duration);
std::vector<std::vector<float>> audio;
size_t got = wav.read(audio, chunk_samples);
if (got == 0 || audio.empty()) {
result.pred_label = "empty";
return result;
}
// Process only the first chunk (like demo_offline)
// For sliding-window analysis, call Process() on hop-sized chunks
auto flat = flatten_audio(audio, effective_channels);
auto frame = pipeline.Process(flat);
if (!frame.is_clear && !frame.threats.empty()) {
const auto& t = frame.threats[0];
result.detected = true;
result.pred_label = t.sound_type;
result.confidence = t.confidence;
result.azimuth = t.azimuth;
result.elevation = t.elevation;
result.distance = t.distance;
} else {
result.pred_label = "ambient";
result.confidence = 0.0f;
}
std::cout << "File: " << fs::path(path).filename().string()
<< " | True: " << result.true_label
<< " | Pred: " << result.pred_label
<< " | Conf: " << std::fixed << std::setprecision(4) << result.confidence;
if (result.detected) {
std::cout << " | Az: " << std::setprecision(2) << result.azimuth << "°"
<< " | El: " << std::setprecision(2) << result.elevation << "°"
<< " | Dist: " << std::setprecision(2) << result.distance << "m";
if (ground_azimuth >= 0.0f) {
float az_err = std::fabs(result.azimuth - ground_azimuth);
if (az_err > 180.0f) az_err = 360.0f - az_err;
std::cout << " | AzErr: " << az_err << "°";
}
if (ground_distance >= 0.0f) {
std::cout << " | DistErr: " << std::fabs(result.distance - ground_distance) << "m";
}
}
std::cout << std::endl;
return result;
}
void collect_wav_files(const std::string& target, std::vector<std::string>& out) {
if (fs::is_regular_file(target) && ends_with(target, ".wav")) {
out.push_back(target);
return;
}
if (!fs::is_directory(target)) return;
for (const auto& entry : fs::recursive_directory_iterator(target)) {
if (entry.is_regular_file() && ends_with(entry.path().string(), ".wav")) {
out.push_back(entry.path().string());
}
}
std::sort(out.begin(), out.end());
}
void print_report(const std::vector<Prediction>& results,
float ground_azimuth,
float ground_distance) {
std::map<std::string, int> total_by_true;
std::map<std::string, int> correct_by_true;
std::map<std::string, float> conf_sum_by_true;
std::map<std::string, std::map<std::string, int>> confusion;
int total = 0, correct = 0;
int detected_count = 0;
float az_err_sum = 0.0f, dist_err_sum = 0.0f;
int az_err_count = 0, dist_err_count = 0;
for (const auto& r : results) {
if (r.pred_label == "error" || r.pred_label == "empty") continue;
total++;
total_by_true[r.true_label]++;
conf_sum_by_true[r.true_label] += r.confidence;
confusion[r.true_label][r.pred_label]++;
if (r.true_label == r.pred_label) {
correct++;
correct_by_true[r.true_label]++;
}
if (r.detected) {
detected_count++;
if (ground_azimuth >= 0.0f) {
float az_err = std::fabs(r.azimuth - ground_azimuth);
if (az_err > 180.0f) az_err = 360.0f - az_err;
az_err_sum += az_err;
az_err_count++;
}
if (ground_distance >= 0.0f) {
dist_err_sum += std::fabs(r.distance - ground_distance);
dist_err_count++;
}
}
}
std::cout << "\n==========================================" << std::endl;
std::cout << " MULTICHANNEL VALIDATION REPORT" << std::endl;
std::cout << "==========================================" << std::endl;
std::cout << "Total samples: " << total << std::endl;
std::cout << "Correct: " << correct << std::endl;
std::cout << "Accuracy: " << std::fixed << std::setprecision(2)
<< (total > 0 ? 100.0f * correct / total : 0.0f) << "%" << std::endl;
std::cout << "Detected frames: " << detected_count << std::endl;
if (az_err_count > 0) {
std::cout << "Azimuth RMSE: " << std::setprecision(2)
<< std::sqrt(az_err_sum / az_err_count) << "°" << std::endl;
}
if (dist_err_count > 0) {
std::cout << "Distance MAE: " << std::setprecision(2)
<< (dist_err_sum / dist_err_count) << "m" << std::endl;
}
std::cout << "\nPer-class breakdown:" << std::endl;
for (const auto& kv : total_by_true) {
const std::string& cls = kv.first;
int cls_total = kv.second;
int cls_correct = correct_by_true[cls];
float avg_conf = conf_sum_by_true[cls] / cls_total;
std::cout << " " << std::setw(10) << std::left << cls
<< " Count: " << std::setw(3) << cls_total
<< " Correct: " << std::setw(3) << cls_correct
<< " Acc: " << std::setw(6) << std::fixed << std::setprecision(2)
<< (100.0f * cls_correct / cls_total) << "%"
<< " AvgConf: " << std::setprecision(4) << avg_conf << std::endl;
}
std::cout << "\nConfusion matrix (rows=true, cols=pred):" << std::endl;
std::vector<std::string> labels;
for (const auto& row : confusion) labels.push_back(row.first);
for (const auto& row : confusion) {
for (const auto& col : row.second) {
if (std::find(labels.begin(), labels.end(), col.first) == labels.end()) {
labels.push_back(col.first);
}
}
}
std::sort(labels.begin(), labels.end());
std::cout << std::setw(12) << " ";
for (const auto& l : labels) std::cout << std::setw(10) << l;
std::cout << std::endl;
for (const auto& true_l : labels) {
std::cout << std::setw(10) << std::left << true_l << " ";
for (const auto& pred_l : labels) {
int count = confusion.count(true_l) ? confusion[true_l].count(pred_l) ? confusion[true_l].at(pred_l) : 0 : 0;
std::cout << std::setw(10) << count;
}
std::cout << std::endl;
}
std::cout << "==========================================" << std::endl;
}
int main(int argc, char** argv) {
if (argc < 2 || std::strcmp(argv[1], "--help") == 0 || std::strcmp(argv[1], "-h") == 0) {
print_usage(argv[0]);
return argc < 2 ? 1 : 0;
}
std::string target = argv[1];
std::string model_path = "models/gunshot_classifier.onnx";
std::string label_map_path = "models/label_map.json";
float threshold = 0.5f;
int num_mics = 4;
float spacing = 0.15f;
std::string layout = "cross";
float ref_spl = 150.0f;
float ground_azimuth = -1.0f;
float ground_distance = -1.0f;
for (int i = 2; i < argc; ++i) {
if (std::strcmp(argv[i], "--model") == 0 && i + 1 < argc) model_path = argv[++i];
else if (std::strcmp(argv[i], "--label_map") == 0 && i + 1 < argc) label_map_path = argv[++i];
else if (std::strcmp(argv[i], "--threshold") == 0 && i + 1 < argc) threshold = std::stof(argv[++i]);
else if (std::strcmp(argv[i], "--num_mics") == 0 && i + 1 < argc) num_mics = std::stoi(argv[++i]);
else if (std::strcmp(argv[i], "--spacing") == 0 && i + 1 < argc) spacing = std::stof(argv[++i]);
else if (std::strcmp(argv[i], "--layout") == 0 && i + 1 < argc) layout = argv[++i];
else if (std::strcmp(argv[i], "--ref_spl") == 0 && i + 1 < argc) ref_spl = std::stof(argv[++i]);
else if (std::strcmp(argv[i], "--ground_azimuth") == 0 && i + 1 < argc) ground_azimuth = std::stof(argv[++i]);
else if (std::strcmp(argv[i], "--ground_distance") == 0 && i + 1 < argc) ground_distance = std::stof(argv[++i]);
}
// Build PipelineConfig directly (no yaml-cpp needed)
PipelineConfig config;
config.sample_rate = 16000;
config.chunk_duration = 2.0f;
config.hop_duration = 0.5f;
config.n_mels = 64;
config.confidence_threshold = threshold;
config.classifier.model_path = model_path;
config.classifier.label_map_path = label_map_path;
config.classifier.threshold = threshold;
config.classifier.smoothing_window = 1; // offline: no temporal smoothing
config.mic_array.num_mics = static_cast<uint32_t>(num_mics);
config.mic_array.layout = layout;
config.mic_array.spacing = spacing;
config.distance.ref_spl_gunshot = ref_spl;
config.distance.ref_spl_artillery = ref_spl;
config.distance.ref_spl_explosion = ref_spl;
Pipeline pipeline(config);
std::vector<std::string> files;
collect_wav_files(target, files);
if (files.empty()) {
std::cerr << "No .wav files found in: " << target << std::endl;
return 1;
}
std::cout << "Found " << files.size() << " WAV file(s)." << std::endl;
std::cout << "Channels: " << num_mics << ", Layout: " << layout
<< ", Spacing: " << spacing << "m" << std::endl;
std::cout << std::endl;
std::vector<Prediction> results;
results.reserve(files.size());
double total_ms = 0.0;
for (const auto& f : files) {
auto t0 = std::chrono::steady_clock::now();
results.push_back(process_file(f, pipeline, num_mics, ground_azimuth, ground_distance));
auto t1 = std::chrono::steady_clock::now();
total_ms += std::chrono::duration<double, std::milli>(t1 - t0).count();
}
std::cout << "\nTotal inference time: " << std::fixed << std::setprecision(2)
<< total_ms << " ms"
<< " | Avg per file: " << (files.empty() ? 0.0 : total_ms / files.size())
<< " ms" << std::endl;
print_report(results, ground_azimuth, ground_distance);
return 0;
}

@ -1,51 +0,0 @@
#include <iostream>
#include <vector>
#include <cmath>
#include <fstream>
#include <algorithm>
#include "acoustic_analyzer/core/feature_extractor.h"
#include "acoustic_analyzer/io/wav_file_source.h"
using namespace acoustic;
int main(int argc, char** argv) {
if (argc < 2) {
std::cerr << "Usage: " << argv[0] << " <wav_file> [output_bin]" << std::endl;
return 1;
}
WavFileSource wav(argv[1]);
if (!wav.open()) {
std::cerr << "Failed to open WAV" << std::endl;
return 1;
}
std::vector<std::vector<float>> audio;
wav.read(audio, wav.total_samples());
if (audio.empty()) {
std::cerr << "Empty audio" << std::endl;
return 1;
}
FeatureExtractor ext(wav.sample_rate(), 2048, 512, 64);
Eigen::MatrixXf mel = ext.MelSpectrogram(audio[0]);
std::cout << "Extracted Mel shape: " << mel.rows() << " x " << mel.cols() << std::endl;
// Flatten for min/max stats
std::vector<float> mel_flat(mel.size());
for (int i = 0; i < mel.rows(); ++i) {
for (int j = 0; j < mel.cols(); ++j) {
mel_flat[i * mel.cols() + j] = mel(i, j);
}
}
std::cout << "Min: " << *std::min_element(mel_flat.begin(), mel_flat.end())
<< " Max: " << *std::max_element(mel_flat.begin(), mel_flat.end()) << std::endl;
if (argc >= 3) {
std::ofstream out(argv[2], std::ios::binary);
out.write(reinterpret_cast<const char*>(mel_flat.data()), mel_flat.size() * sizeof(float));
std::cout << "Saved to " << argv[2] << std::endl;
}
return 0;
}

@ -1,67 +0,0 @@
#include <iostream>
#include <vector>
#include <string>
#include <cmath>
#include <cstring>
#include "acoustic_analyzer/core/gunshot_classifier.h"
#include "acoustic_analyzer/core/feature_extractor.h"
#include "acoustic_analyzer/io/wav_file_source.h"
using namespace acoustic;
void print_usage(const char* prog) {
std::cerr << "Usage: " << prog << " --model <onnx> --wav <wav_file> [--label_map <json>]" << std::endl;
}
int main(int argc, char** argv) {
std::string model_path;
std::string wav_path;
std::string label_map_path;
for (int i = 1; i < argc; ++i) {
if (std::strcmp(argv[i], "--model") == 0 && i + 1 < argc) {
model_path = argv[++i];
} else if (std::strcmp(argv[i], "--wav") == 0 && i + 1 < argc) {
wav_path = argv[++i];
} else if (std::strcmp(argv[i], "--label_map") == 0 && i + 1 < argc) {
label_map_path = argv[++i];
}
}
if (model_path.empty() || wav_path.empty()) {
print_usage(argv[0]);
return 1;
}
WavFileSource wav(wav_path);
if (!wav.open()) {
std::cerr << "Failed to open WAV: " << wav_path << std::endl;
return 1;
}
std::vector<std::vector<float>> audio;
size_t n = wav.read(audio, wav.total_samples());
if (n == 0 || audio.empty()) {
std::cerr << "Empty WAV file" << std::endl;
return 1;
}
FeatureExtractor extractor(wav.sample_rate(), 2048, 512, 64);
Eigen::MatrixXf mel = extractor.MelSpectrogram(audio[0]);
ClassifierConfig cc;
cc.model_path = model_path;
cc.label_map_path = label_map_path;
cc.threshold = 0.7f;
cc.smoothing_window = 3;
GunshotClassifier classifier(cc);
if (!classifier.IsLoaded()) {
std::cerr << "Classifier init failed" << std::endl;
return 1;
}
auto [label, confidence] = classifier.Predict(mel);
std::cout << "Label: " << label << std::endl;
std::cout << "Confidence: " << confidence << std::endl;
return 0;
}

@ -1,170 +0,0 @@
#include <iostream>
#include <vector>
#include <cmath>
#include <cassert>
#include <cstring>
#include "acoustic_analyzer/core/audio_buffer.h"
#include "acoustic_analyzer/core/feature_extractor.h"
#include "acoustic_analyzer/core/gcc_phat_localizer.h"
#include "acoustic_analyzer/core/distance_estimator.h"
#include "acoustic_analyzer/core/threat_tracker.h"
#include "acoustic_analyzer/core/fft_utils.h"
using namespace acoustic;
bool near(float a, float b, float eps = 1e-3f) {
return std::abs(a - b) < eps;
}
void test_audio_buffer() {
AudioBuffer buf(100, 2);
std::vector<float> data(200);
for (int i = 0; i < 200; ++i) data[i] = static_cast<float>(i);
buf.Push(data);
assert(buf.Size() == 100);
assert(buf.NumChannels() == 2);
auto popped = buf.Pop(10);
assert(popped.size() == 10 * 2);
assert(near(popped[0], 0.0f)); // ch0, sample 0 (oldest)
assert(near(popped[1], 1.0f)); // ch1, sample 0
std::cout << "[PASS] audio_buffer" << std::endl;
}
void test_preemphasis() {
float in[] = {1.0f, 0.5f, 0.25f};
float out[3];
apply_preemphasis(in, out, 3, 0.97f);
assert(near(out[0], 1.0f));
assert(near(out[1], 0.5f - 0.97f * 1.0f, 1e-4f));
std::cout << "[PASS] preemphasis" << std::endl;
}
void test_gcc_phat_cross_array() {
MicArrayConfig config;
config.num_mics = 4;
config.layout = "cross";
config.spacing = 0.15f;
GccPhatLocalizer loc(config, 16000, 0.00044f, 4);
// Simulate a signal arriving from 90 degrees (right)
int n = 512;
Eigen::MatrixXf audio(n, 4);
for (int i = 0; i < n; ++i) {
float s = std::sin(2.0f * static_cast<float>(M_PI) * 1000.0f * i / 16000.0f);
audio(i, 0) = s;
audio(i, 1) = s;
audio(i, 2) = s;
audio(i, 3) = s;
}
float delay_samples = 0.15f / 343.0f * 16000.0f;
for (int i = 0; i < n; ++i) {
int idx = static_cast<int>(i + delay_samples);
if (idx < n) audio(idx, 1) = audio(i, 0);
idx = static_cast<int>(i - delay_samples);
if (idx >= 0) audio(idx, 3) = audio(i, 0);
}
auto [azimuth, elevation] = loc.Localize(audio);
assert(azimuth >= 0.0f && azimuth < 360.0f);
std::cout << "[PASS] gcc_phat_cross_array (azimuth=" << azimuth << ")" << std::endl;
}
void test_distance_estimator() {
DistanceConfig config;
config.ref_spl_gunshot = 150.0f;
config.attenuation_alpha = 0.6f;
DistanceEstimator est(config);
float dist = est.Estimate(120.0f, "gunshot");
assert(dist > 0.0f && dist < 5000.0f);
std::cout << "[PASS] distance_estimator (dist=" << dist << "m)" << std::endl;
}
void test_threat_tracker() {
ThreatTracker tracker(0.3f, 15.0f, 5);
AcousticThreat t1;
t1.sound_type = "gunshot";
t1.confidence = 0.9f;
t1.azimuth = 45.0f;
t1.elevation = 0.0f;
t1.distance = 100.0f;
t1.distance_confidence = 0.8f;
auto out = tracker.Update({t1});
assert(!out.empty());
assert(out[0].sound_type == "gunshot");
assert(out[0].threat_id == "THREAT-0001");
std::cout << "[PASS] threat_tracker" << std::endl;
}
void test_feature_extractor_shape() {
FeatureExtractor ext(16000, 2048, 512, 64);
std::vector<float> audio(16000 * 2, 0.0f); // 2 seconds
for (int i = 0; i < 32000; ++i) {
audio[i] = std::sin(2.0f * static_cast<float>(M_PI) * 500.0f * i / 16000.0f) * 0.1f;
}
Eigen::MatrixXf mel = ext.MelSpectrogram(audio);
assert(mel.rows() == 64);
assert(mel.cols() == 63); // padded/truncated to 63 frames
std::cout << "[PASS] feature_extractor_shape (" << mel.rows() << " x " << mel.cols() << ")" << std::endl;
}
void test_audio_buffer_overflow() {
AudioBuffer buf(10, 1);
std::vector<float> data(25, 1.0f);
buf.Push(data);
assert(buf.Size() == 10); // should cap at capacity
auto popped = buf.Pop(5);
assert(popped.size() == 5);
assert(buf.Size() == 5);
std::cout << "[PASS] audio_buffer_overflow" << std::endl;
}
void test_distance_estimator_multi_class() {
DistanceConfig config;
config.ref_spl_gunshot = 150.0f;
config.ref_spl_artillery = 180.0f;
config.ref_spl_explosion = 170.0f;
config.attenuation_alpha = 0.6f;
DistanceEstimator est(config);
float d1 = est.Estimate(120.0f, "gunshot");
float d2 = est.Estimate(120.0f, "artillery");
float d3 = est.Estimate(120.0f, "explosion");
assert(d2 > d1); // artillery has higher ref SPL -> farther estimated distance at same SPL
assert(d3 > d1);
std::cout << "[PASS] distance_estimator_multi_class" << std::endl;
}
void test_threat_tracker_filtering() {
ThreatTracker tracker(0.3f, 15.0f, 5);
AcousticThreat t1;
t1.sound_type = "gunshot";
t1.confidence = 0.9f;
t1.azimuth = 45.0f;
t1.distance = 100.0f;
// First update should publish
auto out1 = tracker.Update({t1});
assert(out1.size() == 1);
// Immediate second update with same azimuth should be filtered (interval < 0.3s)
auto out2 = tracker.Update({t1});
assert(out2.empty());
std::cout << "[PASS] threat_tracker_filtering" << std::endl;
}
int main() {
std::cout << "Running core library tests..." << std::endl;
test_audio_buffer();
test_audio_buffer_overflow();
test_preemphasis();
test_gcc_phat_cross_array();
test_distance_estimator();
test_distance_estimator_multi_class();
test_threat_tracker();
test_threat_tracker_filtering();
test_feature_extractor_shape();
std::cout << "All tests passed!" << std::endl;
return 0;
}

@ -1,43 +0,0 @@
############################################################################
#
# Copyright (c) 2015 PX4 Development Team. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
# 3. Neither the name PX4 nor the names of its contributors may be
# used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
############################################################################
px4_add_module(
MODULE modules__airship_att_control
MAIN airship_att_control
STACK_MAX 3500
COMPILE_FLAGS
SRCS
airship_att_control_main.cpp
DEPENDS
px4_work_queue
)

@ -1,99 +0,0 @@
/****************************************************************************
*
* Copyright (c) 2013-2018 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name PX4 nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
#include <px4_platform_common/module.h>
#include <px4_platform_common/module_params.h>
#include <uORB/Publication.hpp>
#include <uORB/Subscription.hpp>
#include <uORB/SubscriptionCallback.hpp>
#include <uORB/topics/vehicle_status.h>
#include <uORB/topics/actuator_controls.h>
#include <uORB/topics/manual_control_setpoint.h>
#include <uORB/topics/parameter_update.h>
#include <uORB/topics/vehicle_angular_velocity.h>
using namespace time_literals;
/**
* Airship attitude control app start / stop handling function
*/
extern "C" __EXPORT int airship_att_control_main(int argc, char *argv[]);
class AirshipAttitudeControl : public ModuleBase<AirshipAttitudeControl>, public ModuleParams,
public px4::WorkItem
{
public:
AirshipAttitudeControl();
virtual ~AirshipAttitudeControl();
/** @see ModuleBase */
static int task_spawn(int argc, char *argv[]);
/** @see ModuleBase */
static int custom_command(int argc, char *argv[]);
/** @see ModuleBase */
static int print_usage(const char *reason = nullptr);
/** @see ModuleBase::print_status() */
int print_status() override;
void Run() override;
bool init();
private:
/**
* Check for parameter update and handle it.
*/
void parameter_update_poll();
void publish_actuator_controls();
uORB::SubscriptionInterval _parameter_update_sub{ORB_ID(parameter_update), 1_s}; /**< parameter updates subscription */
uORB::Subscription _vehicle_status_sub{ORB_ID(vehicle_status)}; /**< vehicle status subscription */
uORB::Subscription _manual_control_sp_sub{ORB_ID(manual_control_setpoint)}; /**< manual control setpoint subscription */
uORB::SubscriptionCallbackWorkItem _vehicle_angular_velocity_sub{this, ORB_ID(vehicle_angular_velocity)};
uORB::Publication<actuator_controls_s> _actuators_0_pub;
struct manual_control_setpoint_s _manual_control_sp {}; /**< manual control setpoint */
struct vehicle_status_s _vehicle_status {}; /**< vehicle status */
struct actuator_controls_s _actuators {}; /**< actuator controls */
perf_counter_t _loop_perf; /**< loop performance counter */
};

@ -1,213 +0,0 @@
/****************************************************************************
*
* Copyright (c) 2013-2019 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name PX4 nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
/**
* @file airship_att_control_main.cpp
* Airship attitude controller.
*
* @author Anton Erasmus <anton@flycloudline.com>
*/
#include "airship_att_control.hpp"
using namespace matrix;
AirshipAttitudeControl::AirshipAttitudeControl() :
ModuleParams(nullptr),
WorkItem(MODULE_NAME, px4::wq_configurations::rate_ctrl),
_actuators_0_pub(ORB_ID(actuator_controls_0)),
_loop_perf(perf_alloc(PC_ELAPSED, "airship_att_control"))
{
}
AirshipAttitudeControl::~AirshipAttitudeControl()
{
perf_free(_loop_perf);
}
bool
AirshipAttitudeControl::init()
{
if (!_vehicle_angular_velocity_sub.registerCallback()) {
PX4_ERR("vehicle_angular_velocity callback registration failed!");
return false;
}
return true;
}
void
AirshipAttitudeControl::parameter_update_poll()
{
// check for parameter updates
if (_parameter_update_sub.updated()) {
// clear update
parameter_update_s pupdate;
_parameter_update_sub.copy(&pupdate);
// update parameters from storage
updateParams();
}
}
void
AirshipAttitudeControl::publish_actuator_controls()
{
// zero actuators if not armed
if (_vehicle_status.arming_state != vehicle_status_s::ARMING_STATE_ARMED) {
for (uint8_t i = 0 ; i < 4 ; i++) {
_actuators.control[i] = 0.0f;
}
} else {
_actuators.control[0] = 0.0f;
_actuators.control[1] = _manual_control_sp.x;
_actuators.control[2] = _manual_control_sp.r;
_actuators.control[3] = _manual_control_sp.z;
}
// note: _actuators.timestamp_sample is set in AirshipAttitudeControl::Run()
_actuators.timestamp = hrt_absolute_time();
_actuators_0_pub.publish(_actuators);
}
void
AirshipAttitudeControl::Run()
{
if (should_exit()) {
_vehicle_angular_velocity_sub.unregisterCallback();
exit_and_cleanup();
return;
}
perf_begin(_loop_perf);
/* run controller on gyro changes */
vehicle_angular_velocity_s angular_velocity;
if (_vehicle_angular_velocity_sub.update(&angular_velocity)) {
const Vector3f rates{angular_velocity.xyz};
_actuators.timestamp_sample = angular_velocity.timestamp_sample;
/* run the rate controller immediately after a gyro update */
publish_actuator_controls();
/* check for updates in manual control topic */
if (_manual_control_sp_sub.updated()) {
_manual_control_sp_sub.update(&_manual_control_sp);
}
/* check for updates in vehicle status topic */
if (_vehicle_status_sub.updated()) {
_vehicle_status_sub.update(&_vehicle_status);
}
parameter_update_poll();
}
perf_end(_loop_perf);
}
int AirshipAttitudeControl::task_spawn(int argc, char *argv[])
{
AirshipAttitudeControl *instance = new AirshipAttitudeControl();
if (instance) {
_object.store(instance);
_task_id = task_id_is_work_queue;
if (instance->init()) {
return PX4_OK;
}
} else {
PX4_ERR("alloc failed");
}
delete instance;
_object.store(nullptr);
_task_id = -1;
return PX4_ERROR;
}
int AirshipAttitudeControl::print_status()
{
PX4_INFO("Running");
perf_print_counter(_loop_perf);
print_message(_actuators);
return 0;
}
int AirshipAttitudeControl::custom_command(int argc, char *argv[])
{
return print_usage("unknown command");
}
int AirshipAttitudeControl::print_usage(const char *reason)
{
if (reason) {
PX4_WARN("%s\n", reason);
}
PRINT_MODULE_DESCRIPTION(
R"DESCR_STR(
### Description
This implements the airship attitude and rate controller. Ideally it would
take attitude setpoints (`vehicle_attitude_setpoint`) or rate setpoints (in acro mode
via `manual_control_setpoint` topic) as inputs and outputs actuator control messages.
Currently it is feeding the `manual_control_setpoint` topic directly to the actuators.
### Implementation
To reduce control latency, the module directly polls on the gyro topic published by the IMU driver.
)DESCR_STR");
PRINT_MODULE_USAGE_NAME("airship_att_control", "controller");
PRINT_MODULE_USAGE_COMMAND("start");
PRINT_MODULE_USAGE_DEFAULT_COMMANDS();
return 0;
}
int airship_att_control_main(int argc, char *argv[])
{
return AirshipAttitudeControl::main(argc, argv);
}

@ -1,42 +0,0 @@
############################################################################
#
# Copyright (c) 2018 PX4 Development Team. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
# 3. Neither the name PX4 nor the names of its contributors may be
# used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
############################################################################
px4_add_module(
MODULE modules__airspeed_selector
MAIN airspeed_selector
SRCS
airspeed_selector_main.cpp
DEPENDS
git_ecl
ecl_airdata
AirspeedValidator
)

@ -1,675 +0,0 @@
/****************************************************************************
*
* Copyright (c) 2018 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name PX4 nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
#include <drivers/drv_hrt.h>
#include <ecl/airdata/WindEstimator.hpp>
#include <matrix/math.hpp>
#include <parameters/param.h>
#include <perf/perf_counter.h>
#include <px4_platform_common/module.h>
#include <px4_platform_common/module_params.h>
#include <px4_platform_common/px4_work_queue/ScheduledWorkItem.hpp>
#include <lib/airspeed/airspeed.h>
#include <AirspeedValidator.hpp>
#include <systemlib/mavlink_log.h>
#include <uORB/Subscription.hpp>
#include <uORB/SubscriptionMultiArray.hpp>
#include <uORB/topics/airspeed.h>
#include <uORB/topics/airspeed_validated.h>
#include <uORB/topics/estimator_selector_status.h>
#include <uORB/topics/estimator_status.h>
#include <uORB/topics/mavlink_log.h>
#include <uORB/topics/parameter_update.h>
#include <uORB/topics/position_setpoint.h>
#include <uORB/Publication.hpp>
#include <uORB/PublicationMulti.hpp>
#include <uORB/topics/vehicle_acceleration.h>
#include <uORB/topics/vehicle_air_data.h>
#include <uORB/topics/vehicle_attitude.h>
#include <uORB/topics/vehicle_land_detected.h>
#include <uORB/topics/vehicle_local_position.h>
#include <uORB/topics/vehicle_status.h>
#include <uORB/topics/vtol_vehicle_status.h>
#include <uORB/topics/airspeed_wind.h>
using namespace time_literals;
static constexpr uint32_t SCHEDULE_INTERVAL{100_ms}; /**< The schedule interval in usec (10 Hz) */
using matrix::Dcmf;
using matrix::Quatf;
using matrix::Vector2f;
using matrix::Vector3f;
class AirspeedModule : public ModuleBase<AirspeedModule>, public ModuleParams,
public px4::ScheduledWorkItem
{
public:
AirspeedModule();
~AirspeedModule() override;
/** @see ModuleBase */
static int task_spawn(int argc, char *argv[]);
/** @see ModuleBase */
static int custom_command(int argc, char *argv[]);
/** @see ModuleBase */
static int print_usage(const char *reason = nullptr);
private:
void Run() override;
static constexpr int MAX_NUM_AIRSPEED_SENSORS = 3; /**< Support max 3 airspeed sensors */
enum airspeed_index {
DISABLED_INDEX = -1,
GROUND_MINUS_WIND_INDEX,
FIRST_SENSOR_INDEX,
SECOND_SENSOR_INDEX,
THIRD_SENSOR_INDEX
};
uORB::Publication<airspeed_validated_s> _airspeed_validated_pub {ORB_ID(airspeed_validated)}; /**< airspeed validated topic*/
uORB::PublicationMulti<airspeed_wind_s> _wind_est_pub[MAX_NUM_AIRSPEED_SENSORS + 1] {{ORB_ID(airspeed_wind)}, {ORB_ID(airspeed_wind)}, {ORB_ID(airspeed_wind)}, {ORB_ID(airspeed_wind)}}; /**< wind estimate topic (for each airspeed validator + purely sideslip fusion) */
orb_advert_t _mavlink_log_pub {nullptr}; /**< mavlink log topic*/
uORB::SubscriptionInterval _parameter_update_sub{ORB_ID(parameter_update), 1_s};
uORB::Subscription _estimator_selector_status_sub{ORB_ID(estimator_selector_status)};
uORB::Subscription _estimator_status_sub{ORB_ID(estimator_status)};
uORB::Subscription _vehicle_acceleration_sub{ORB_ID(vehicle_acceleration)};
uORB::Subscription _vehicle_air_data_sub{ORB_ID(vehicle_air_data)};
uORB::Subscription _vehicle_attitude_sub{ORB_ID(vehicle_attitude)};
uORB::Subscription _vehicle_land_detected_sub{ORB_ID(vehicle_land_detected)};
uORB::Subscription _vehicle_local_position_sub{ORB_ID(vehicle_local_position)};
uORB::Subscription _vehicle_status_sub{ORB_ID(vehicle_status)};
uORB::Subscription _vtol_vehicle_status_sub{ORB_ID(vtol_vehicle_status)};
uORB::Subscription _position_setpoint_sub{ORB_ID(position_setpoint)};
uORB::SubscriptionMultiArray<airspeed_s, MAX_NUM_AIRSPEED_SENSORS> _airspeed_subs{ORB_ID::airspeed};
estimator_status_s _estimator_status {};
vehicle_acceleration_s _accel {};
vehicle_air_data_s _vehicle_air_data {};
vehicle_attitude_s _vehicle_attitude {};
vehicle_land_detected_s _vehicle_land_detected {};
vehicle_local_position_s _vehicle_local_position {};
vehicle_status_s _vehicle_status {};
vtol_vehicle_status_s _vtol_vehicle_status {};
position_setpoint_s _position_setpoint {};
WindEstimator _wind_estimator_sideslip; /**< wind estimator instance only fusing sideslip */
airspeed_wind_s _wind_estimate_sideslip {}; /**< wind estimate message for wind estimator instance only fusing sideslip */
int32_t _number_of_airspeed_sensors{0}; /**< number of airspeed sensors in use (detected during initialization)*/
int32_t _prev_number_of_airspeed_sensors{0}; /**< number of airspeed sensors in previous loop (to detect a new added sensor)*/
AirspeedValidator _airspeed_validator[MAX_NUM_AIRSPEED_SENSORS] {}; /**< airspeedValidator instances (one for each sensor) */
hrt_abstime _time_now_usec{0};
int _valid_airspeed_index{-2}; /**< index of currently chosen (valid) airspeed sensor */
int _prev_airspeed_index{-2}; /**< previously chosen airspeed sensor index */
bool _initialized{false}; /**< module initialized*/
bool _vehicle_local_position_valid{false}; /**< local position (from GPS) valid */
bool _in_takeoff_situation{true}; /**< in takeoff situation (defined as not yet stall speed reached) */
float _ground_minus_wind_TAS{0.0f}; /**< true airspeed from groundspeed minus windspeed */
float _ground_minus_wind_CAS{0.0f}; /**< calibrated airspeed from groundspeed minus windspeed */
bool _scale_estimation_previously_on{false}; /**< scale_estimation was on in the last cycle */
hrt_abstime _time_last_airspeed_update[MAX_NUM_AIRSPEED_SENSORS] {};
perf_counter_t _perf_elapsed{};
DEFINE_PARAMETERS(
(ParamFloat<px4::params::ASPD_W_P_NOISE>) _param_west_w_p_noise,
(ParamFloat<px4::params::ASPD_SC_P_NOISE>) _param_west_sc_p_noise,
(ParamFloat<px4::params::ASPD_TAS_NOISE>) _param_west_tas_noise,
(ParamFloat<px4::params::ASPD_BETA_NOISE>) _param_west_beta_noise,
(ParamInt<px4::params::ASPD_TAS_GATE>) _param_west_tas_gate,
(ParamInt<px4::params::ASPD_BETA_GATE>) _param_west_beta_gate,
(ParamInt<px4::params::ASPD_SCALE_EST>) _param_west_scale_estimation_on,
(ParamFloat<px4::params::ASPD_SCALE>) _param_west_airspeed_scale,
(ParamInt<px4::params::ASPD_PRIMARY>) _param_airspeed_primary_index,
(ParamInt<px4::params::ASPD_DO_CHECKS>) _param_airspeed_checks_on,
(ParamInt<px4::params::ASPD_FALLBACK_GW>) _param_airspeed_fallback_gw,
(ParamFloat<px4::params::ASPD_FS_INNOV>) _tas_innov_threshold, /**< innovation check threshold */
(ParamFloat<px4::params::ASPD_FS_INTEG>) _tas_innov_integ_threshold, /**< innovation check integrator threshold */
(ParamInt<px4::params::ASPD_FS_T_STOP>) _checks_fail_delay, /**< delay to declare airspeed invalid */
(ParamInt<px4::params::ASPD_FS_T_START>) _checks_clear_delay, /**< delay to declare airspeed valid again */
(ParamFloat<px4::params::FW_AIRSPD_STALL>) _param_fw_airspd_stall
)
void init(); /**< initialization of the airspeed validator instances */
void check_for_connected_airspeed_sensors(); /**< check for airspeed sensors (airspeed topics) and get _number_of_airspeed_sensors */
void update_params(); /**< update parameters */
void poll_topics(); /**< poll all topics required beside airspeed (e.g. current temperature) */
void update_wind_estimator_sideslip(); /**< update the wind estimator instance only fusing sideslip */
void update_ground_minus_wind_airspeed(); /**< update airspeed estimate based on groundspeed minus windspeed */
void select_airspeed_and_publish(); /**< select airspeed sensor (or groundspeed-windspeed) */
};
AirspeedModule::AirspeedModule():
ModuleParams(nullptr),
ScheduledWorkItem(MODULE_NAME, px4::wq_configurations::nav_and_controllers)
{
// initialise parameters
update_params();
_perf_elapsed = perf_alloc(PC_ELAPSED, MODULE_NAME": elapsed");
}
AirspeedModule::~AirspeedModule()
{
ScheduleClear();
perf_free(_perf_elapsed);
}
int
AirspeedModule::task_spawn(int argc, char *argv[])
{
AirspeedModule *dev = new AirspeedModule();
// check if the trampoline is called for the first time
if (!dev) {
PX4_ERR("alloc failed");
return PX4_ERROR;
}
_object.store(dev);
dev->ScheduleOnInterval(SCHEDULE_INTERVAL, 10000);
_task_id = task_id_is_work_queue;
return PX4_OK;
}
void
AirspeedModule::init()
{
check_for_connected_airspeed_sensors();
// Set the default sensor
if (_param_airspeed_primary_index.get() > _number_of_airspeed_sensors) {
// constrain the index to the number of sensors connected
_valid_airspeed_index = math::min(_param_airspeed_primary_index.get(), _number_of_airspeed_sensors);
if (_number_of_airspeed_sensors == 0) {
mavlink_log_info(&_mavlink_log_pub,
"No airspeed sensor detected. Switch to non-airspeed mode.");
} else {
mavlink_log_info(&_mavlink_log_pub,
"Primary airspeed index bigger than number connected sensors. Take last sensor.");
}
} else {
// set index to the one provided in the parameter ASPD_PRIMARY
_valid_airspeed_index = _param_airspeed_primary_index.get();
}
_prev_airspeed_index = _valid_airspeed_index; // used to detect a sensor switching
}
void
AirspeedModule::check_for_connected_airspeed_sensors()
{
// check for new connected airspeed sensor
int detected_airspeed_sensors = 0;
if (_param_airspeed_primary_index.get() > 0) {
for (int i = 0; i < _airspeed_subs.size(); i++) {
if (!_airspeed_subs[i].advertised()) {
break;
}
detected_airspeed_sensors = i + 1;
}
} else {
// user has selected groundspeed-windspeed as primary source, or disabled airspeed
detected_airspeed_sensors = 0;
}
_number_of_airspeed_sensors = detected_airspeed_sensors;
}
void
AirspeedModule::Run()
{
_time_now_usec = hrt_absolute_time(); // hrt time of the current cycle
// do not run the airspeed selector until 2s after system boot,
// as data from airspeed sensor and estimator may not be valid yet
if (_time_now_usec < 2_s) {
return;
}
perf_begin(_perf_elapsed);
if (!_initialized) {
init(); // initialize airspeed validator instances
_initialized = true;
}
parameter_update_s update;
if (_parameter_update_sub.update(&update)) {
update_params();
}
bool armed = (_vehicle_status.arming_state == vehicle_status_s::ARMING_STATE_ARMED);
// check for new connected airspeed sensors as long as we're disarmed
if (!armed) {
check_for_connected_airspeed_sensors();
}
poll_topics();
update_wind_estimator_sideslip();
update_ground_minus_wind_airspeed();
if (_number_of_airspeed_sensors > 0) {
// disable checks if not a fixed-wing or the vehicle is landing/landed, as then airspeed can fall below stall speed
// and wind estimate isn't accurate anymore. Even better would be to have a reliable "ground_contact" detection
// for fixed-wing landings.
const bool in_air_fixed_wing = !_vehicle_land_detected.landed &&
_position_setpoint.type != position_setpoint_s::SETPOINT_TYPE_LAND &&
_vehicle_status.vehicle_type == vehicle_status_s::VEHICLE_TYPE_FIXED_WING;
// Prepare data for airspeed_validator
struct airspeed_validator_update_data input_data = {};
input_data.timestamp = _time_now_usec;
input_data.lpos_vx = _vehicle_local_position.vx;
input_data.lpos_vy = _vehicle_local_position.vy;
input_data.lpos_vz = _vehicle_local_position.vz;
input_data.lpos_valid = _vehicle_local_position_valid;
input_data.lpos_evh = _vehicle_local_position.evh;
input_data.lpos_evv = _vehicle_local_position.evv;
input_data.att_q[0] = _vehicle_attitude.q[0];
input_data.att_q[1] = _vehicle_attitude.q[1];
input_data.att_q[2] = _vehicle_attitude.q[2];
input_data.att_q[3] = _vehicle_attitude.q[3];
input_data.air_pressure_pa = _vehicle_air_data.baro_pressure_pa;
input_data.accel_z = _accel.xyz[2];
input_data.vel_test_ratio = _estimator_status.vel_test_ratio;
input_data.mag_test_ratio = _estimator_status.mag_test_ratio;
// iterate through all airspeed sensors, poll new data from them and update their validators
for (int i = 0; i < _number_of_airspeed_sensors; i++) {
// poll raw airspeed topic of the i-th sensor
airspeed_s airspeed_raw;
if (_airspeed_subs[i].update(&airspeed_raw)) {
input_data.airspeed_indicated_raw = airspeed_raw.indicated_airspeed_m_s;
input_data.airspeed_true_raw = airspeed_raw.true_airspeed_m_s;
input_data.airspeed_timestamp = airspeed_raw.timestamp;
input_data.air_temperature_celsius = airspeed_raw.air_temperature_celsius;
// takeoff situation is active from start till one of the sensors' IAS or groundspeed_CAS is above stall speed
if (_in_takeoff_situation &&
(airspeed_raw.indicated_airspeed_m_s > _param_fw_airspd_stall.get() ||
_ground_minus_wind_CAS > _param_fw_airspd_stall.get())) {
_in_takeoff_situation = false;
}
// reset takeoff_situation to true when not in air and not in fixed-wing mode
if (!in_air_fixed_wing) {
_in_takeoff_situation = true;
}
input_data.in_fixed_wing_flight = (armed && in_air_fixed_wing && !_in_takeoff_situation);
// push input data into airspeed validator
_airspeed_validator[i].update_airspeed_validator(input_data);
_time_last_airspeed_update[i] = _time_now_usec;
} else if (_time_now_usec - _time_last_airspeed_update[i] > 1_s) {
// declare airspeed invalid if more then 1s since last raw airspeed update
_airspeed_validator[i].reset_airspeed_to_invalid(_time_now_usec);
}
}
}
select_airspeed_and_publish();
perf_end(_perf_elapsed);
if (should_exit()) {
exit_and_cleanup();
}
}
void AirspeedModule::update_params()
{
updateParams();
_wind_estimator_sideslip.set_wind_p_noise(_param_west_w_p_noise.get());
_wind_estimator_sideslip.set_tas_scale_p_noise(_param_west_sc_p_noise.get());
_wind_estimator_sideslip.set_tas_noise(_param_west_tas_noise.get());
_wind_estimator_sideslip.set_beta_noise(_param_west_beta_noise.get());
_wind_estimator_sideslip.set_tas_gate(_param_west_tas_gate.get());
_wind_estimator_sideslip.set_beta_gate(_param_west_beta_gate.get());
for (int i = 0; i < _number_of_airspeed_sensors; i++) {
_airspeed_validator[i].set_wind_estimator_wind_p_noise(_param_west_w_p_noise.get());
_airspeed_validator[i].set_wind_estimator_tas_scale_p_noise(_param_west_sc_p_noise.get());
_airspeed_validator[i].set_wind_estimator_tas_noise(_param_west_tas_noise.get());
_airspeed_validator[i].set_wind_estimator_beta_noise(_param_west_beta_noise.get());
_airspeed_validator[i].set_wind_estimator_tas_gate(_param_west_tas_gate.get());
_airspeed_validator[i].set_wind_estimator_beta_gate(_param_west_beta_gate.get());
_airspeed_validator[i].set_wind_estimator_scale_estimation_on(_param_west_scale_estimation_on.get());
// only apply manual entered airspeed scale to first airspeed measurement
// TODO: enable multiple airspeed sensors
_airspeed_validator[0].set_airspeed_scale_manual(_param_west_airspeed_scale.get());
_airspeed_validator[i].set_tas_innov_threshold(_tas_innov_threshold.get());
_airspeed_validator[i].set_tas_innov_integ_threshold(_tas_innov_integ_threshold.get());
_airspeed_validator[i].set_checks_fail_delay(_checks_fail_delay.get());
_airspeed_validator[i].set_checks_clear_delay(_checks_clear_delay.get());
_airspeed_validator[i].set_airspeed_stall(_param_fw_airspd_stall.get());
}
// if the airspeed scale estimation is enabled and the airspeed is valid,
// then set the scale inside the wind estimator to -1 such that it starts to estimate it
if (!_scale_estimation_previously_on && _param_west_scale_estimation_on.get()) {
if (_valid_airspeed_index > 0) {
// set it to a negative value to start estimation inside wind estimator
_airspeed_validator[0].set_airspeed_scale_manual(-1.0f);
} else {
mavlink_log_info(&_mavlink_log_pub, "Airspeed: can't estimate scale as no valid sensor.");
_param_west_scale_estimation_on.set(0); // reset this param to 0 as estimation was not turned on
_param_west_scale_estimation_on.commit_no_notification();
}
} else if (_scale_estimation_previously_on && !_param_west_scale_estimation_on.get()) {
if (_valid_airspeed_index > 0) {
_param_west_airspeed_scale.set(_airspeed_validator[_valid_airspeed_index - 1].get_CAS_scale());
_param_west_airspeed_scale.commit_no_notification();
_airspeed_validator[_valid_airspeed_index - 1].set_airspeed_scale_manual(_param_west_airspeed_scale.get());
mavlink_log_info(&_mavlink_log_pub, "Airspeed: estimated scale (ASPD_SCALE): %0.2f",
(double)_airspeed_validator[_valid_airspeed_index - 1].get_CAS_scale());
} else {
mavlink_log_info(&_mavlink_log_pub, "Airspeed: can't estimate scale as no valid sensor.");
}
}
_scale_estimation_previously_on = _param_west_scale_estimation_on.get();
}
void AirspeedModule::poll_topics()
{
// use primary estimator_status
if (_estimator_selector_status_sub.updated()) {
estimator_selector_status_s estimator_selector_status;
if (_estimator_selector_status_sub.copy(&estimator_selector_status)) {
if (estimator_selector_status.primary_instance != _estimator_status_sub.get_instance()) {
_estimator_status_sub.ChangeInstance(estimator_selector_status.primary_instance);
}
}
}
_estimator_status_sub.update(&_estimator_status);
_vehicle_acceleration_sub.update(&_accel);
_vehicle_air_data_sub.update(&_vehicle_air_data);
_vehicle_attitude_sub.update(&_vehicle_attitude);
_vehicle_land_detected_sub.update(&_vehicle_land_detected);
_vehicle_status_sub.update(&_vehicle_status);
_vtol_vehicle_status_sub.update(&_vtol_vehicle_status);
_vehicle_local_position_sub.update(&_vehicle_local_position);
_position_setpoint_sub.update(&_position_setpoint);
_vehicle_local_position_valid = (_time_now_usec - _vehicle_local_position.timestamp < 1_s)
&& (_vehicle_local_position.timestamp > 0) && _vehicle_local_position.v_xy_valid;
}
void AirspeedModule::update_wind_estimator_sideslip()
{
// update wind and airspeed estimator
_wind_estimator_sideslip.update(_time_now_usec);
if (_vehicle_local_position_valid && !_vtol_vehicle_status.vtol_in_rw_mode) {
Vector3f vI(_vehicle_local_position.vx, _vehicle_local_position.vy, _vehicle_local_position.vz);
Quatf q(_vehicle_attitude.q);
_wind_estimator_sideslip.fuse_beta(_time_now_usec, vI, q);
}
_wind_estimate_sideslip.timestamp = _time_now_usec;
float wind[2];
_wind_estimator_sideslip.get_wind(wind);
_wind_estimate_sideslip.windspeed_north = wind[0];
_wind_estimate_sideslip.windspeed_east = wind[1];
float wind_cov[2];
_wind_estimator_sideslip.get_wind_var(wind_cov);
_wind_estimate_sideslip.variance_north = wind_cov[0];
_wind_estimate_sideslip.variance_east = wind_cov[1];
_wind_estimate_sideslip.tas_innov = _wind_estimator_sideslip.get_tas_innov();
_wind_estimate_sideslip.tas_innov_var = _wind_estimator_sideslip.get_tas_innov_var();
_wind_estimate_sideslip.beta_innov = _wind_estimator_sideslip.get_beta_innov();
_wind_estimate_sideslip.beta_innov_var = _wind_estimator_sideslip.get_beta_innov_var();
_wind_estimate_sideslip.tas_scale = _wind_estimator_sideslip.get_tas_scale();
_wind_estimate_sideslip.source = airspeed_wind_s::SOURCE_AS_BETA_ONLY;
}
void AirspeedModule::update_ground_minus_wind_airspeed()
{
// calculate airspeed estimate based on groundspeed-windspeed to use as fallback
const float TAS_north = _vehicle_local_position.vx - _wind_estimate_sideslip.windspeed_north;
const float TAS_east = _vehicle_local_position.vy - _wind_estimate_sideslip.windspeed_east;
const float TAS_down = _vehicle_local_position.vz; // no wind estimate in z
_ground_minus_wind_TAS = sqrtf(TAS_north * TAS_north + TAS_east * TAS_east + TAS_down * TAS_down);
_ground_minus_wind_CAS = calc_CAS_from_TAS(_ground_minus_wind_TAS, _vehicle_air_data.baro_pressure_pa,
_vehicle_air_data.baro_temp_celcius);
}
void AirspeedModule::select_airspeed_and_publish()
{
const bool airspeed_sensor_switching_necessary = _prev_airspeed_index < airspeed_index::FIRST_SENSOR_INDEX ||
!_airspeed_validator[_prev_airspeed_index - 1].get_airspeed_valid();
const bool airspeed_sensor_switching_allowed = _number_of_airspeed_sensors > 0 &&
_param_airspeed_primary_index.get() > airspeed_index::GROUND_MINUS_WIND_INDEX && _param_airspeed_checks_on.get();
const bool airspeed_sensor_added = _prev_number_of_airspeed_sensors < _number_of_airspeed_sensors;
if (airspeed_sensor_switching_necessary && (airspeed_sensor_switching_allowed || airspeed_sensor_added)) {
_valid_airspeed_index = airspeed_index::DISABLED_INDEX;
// loop through all sensors and take the first valid one
for (int i = 0; i < _number_of_airspeed_sensors; i++) {
if (_airspeed_validator[i].get_airspeed_valid()) {
_valid_airspeed_index = i + 1;
break;
}
}
}
// check if airspeed based on ground-wind speed is valid and can be published
if (_param_airspeed_primary_index.get() > airspeed_index::DISABLED_INDEX &&
(_valid_airspeed_index < airspeed_index::FIRST_SENSOR_INDEX
|| _param_airspeed_primary_index.get() == airspeed_index::GROUND_MINUS_WIND_INDEX)) {
// _vehicle_local_position_valid determines if ground-wind estimate is valid
if (_vehicle_local_position_valid &&
(_param_airspeed_fallback_gw.get() || _param_airspeed_primary_index.get() == airspeed_index::GROUND_MINUS_WIND_INDEX)) {
_valid_airspeed_index = airspeed_index::GROUND_MINUS_WIND_INDEX;
} else {
_valid_airspeed_index = airspeed_index::DISABLED_INDEX;
}
}
// print warning or info, depending of whether airspeed got declared invalid or healthy
if (_valid_airspeed_index != _prev_airspeed_index &&
(_number_of_airspeed_sensors > 0 || !_vehicle_land_detected.landed) &&
_valid_airspeed_index != _prev_airspeed_index) {
if (_prev_airspeed_index > 0) {
mavlink_log_critical(&_mavlink_log_pub, "Airspeed sensor failure detected (%i, %i)", _prev_airspeed_index,
_valid_airspeed_index);
} else if (_prev_airspeed_index == 0 && _valid_airspeed_index == -1) {
mavlink_log_info(&_mavlink_log_pub, "Airspeed estimation invalid");
} else if (_prev_airspeed_index == -1 && _valid_airspeed_index == 0) {
mavlink_log_info(&_mavlink_log_pub, "Airspeed estimation valid");
} else {
mavlink_log_info(&_mavlink_log_pub, "Airspeed sensor healthy, start using again (%i, %i)", _prev_airspeed_index,
_valid_airspeed_index);
}
}
_prev_airspeed_index = _valid_airspeed_index;
_prev_number_of_airspeed_sensors = _number_of_airspeed_sensors;
airspeed_validated_s airspeed_validated = {};
airspeed_validated.timestamp = _time_now_usec;
airspeed_validated.true_ground_minus_wind_m_s = NAN;
airspeed_validated.calibrated_ground_minus_wind_m_s = NAN;
airspeed_validated.indicated_airspeed_m_s = NAN;
airspeed_validated.calibrated_airspeed_m_s = NAN;
airspeed_validated.true_airspeed_m_s = NAN;
airspeed_validated.airspeed_sensor_measurement_valid = false;
airspeed_validated.selected_airspeed_index = _valid_airspeed_index;
switch (_valid_airspeed_index) {
case airspeed_index::DISABLED_INDEX:
break;
case airspeed_index::GROUND_MINUS_WIND_INDEX:
airspeed_validated.indicated_airspeed_m_s = _ground_minus_wind_CAS;
airspeed_validated.calibrated_airspeed_m_s = _ground_minus_wind_CAS;
airspeed_validated.true_airspeed_m_s = _ground_minus_wind_TAS;
airspeed_validated.calibrated_ground_minus_wind_m_s = _ground_minus_wind_CAS;
airspeed_validated.true_ground_minus_wind_m_s = _ground_minus_wind_TAS;
break;
default:
airspeed_validated.indicated_airspeed_m_s = _airspeed_validator[_valid_airspeed_index - 1].get_IAS();
airspeed_validated.calibrated_airspeed_m_s = _airspeed_validator[_valid_airspeed_index - 1].get_CAS();
airspeed_validated.true_airspeed_m_s = _airspeed_validator[_valid_airspeed_index - 1].get_TAS();
airspeed_validated.calibrated_ground_minus_wind_m_s = _ground_minus_wind_CAS;
airspeed_validated.true_ground_minus_wind_m_s = _ground_minus_wind_TAS;
airspeed_validated.airspeed_sensor_measurement_valid = true;
break;
}
_airspeed_validated_pub.publish(airspeed_validated);
_wind_est_pub[0].publish(_wind_estimate_sideslip);
// publish the wind estimator states from all airspeed validators
for (int i = 0; i < _number_of_airspeed_sensors; i++) {
airspeed_wind_s wind_est = _airspeed_validator[i].get_wind_estimator_states(_time_now_usec);
if (i == 0) {
wind_est.source = airspeed_wind_s::SOURCE_AS_SENSOR_1;
} else if (i == 1) {
wind_est.source = airspeed_wind_s::SOURCE_AS_SENSOR_2;
} else {
wind_est.source = airspeed_wind_s::SOURCE_AS_SENSOR_3;
}
_wind_est_pub[i + 1].publish(wind_est);
}
}
int AirspeedModule::custom_command(int argc, char *argv[])
{
if (!is_running()) {
int ret = AirspeedModule::task_spawn(argc, argv);
if (ret) {
return ret;
}
}
return print_usage("unknown command");
}
int AirspeedModule::print_usage(const char *reason)
{
if (reason) {
PX4_WARN("%s\n", reason);
}
PRINT_MODULE_DESCRIPTION(
R"DESCR_STR(
### Description
This module provides a single airspeed_validated topic, containing indicated (IAS),
calibrated (CAS), true airspeed (TAS) and the information if the estimation currently
is invalid and if based sensor readings or on groundspeed minus windspeed.
Supporting the input of multiple "raw" airspeed inputs, this module automatically switches
to a valid sensor in case of failure detection. For failure detection as well as for
the estimation of a scale factor from IAS to CAS, it runs several wind estimators
and also publishes those.
)DESCR_STR");
PRINT_MODULE_USAGE_NAME("airspeed_estimator", "estimator");
PRINT_MODULE_USAGE_COMMAND("start");
PRINT_MODULE_USAGE_DEFAULT_COMMANDS();
return 0;
}
extern "C" __EXPORT int airspeed_selector_main(int argc, char *argv[])
{
return AirspeedModule::main(argc, argv);
}

@ -1,186 +0,0 @@
/**
* Airspeed Selector: Wind estimator wind process noise
*
* Wind process noise of the internal wind estimator(s) of the airspeed selector.
*
* @min 0
* @max 1
* @unit m/s^2
* @group Airspeed Validator
*/
PARAM_DEFINE_FLOAT(ASPD_W_P_NOISE, 0.1f);
/**
* Airspeed Selector: Wind estimator true airspeed scale process noise
*
* Airspeed scale process noise of the internal wind estimator(s) of the airspeed selector.
*
* @min 0
* @max 0.1
* @unit Hz
* @group Airspeed Validator
*/
PARAM_DEFINE_FLOAT(ASPD_SC_P_NOISE, 0.0001);
/**
* Airspeed Selector: Wind estimator true airspeed measurement noise
*
* True airspeed measurement noise of the internal wind estimator(s) of the airspeed selector.
*
* @min 0
* @max 4
* @unit m/s
* @group Airspeed Validator
*/
PARAM_DEFINE_FLOAT(ASPD_TAS_NOISE, 1.4);
/**
* Airspeed Selector: Wind estimator sideslip measurement noise
*
* Sideslip measurement noise of the internal wind estimator(s) of the airspeed selector.
*
* @min 0
* @max 1
* @unit rad
* @group Airspeed Validator
*/
PARAM_DEFINE_FLOAT(ASPD_BETA_NOISE, 0.3);
/**
* Airspeed Selector: Gate size for true airspeed fusion
*
* Sets the number of standard deviations used by the innovation consistency test.
*
* @min 1
* @max 5
* @unit SD
* @group Airspeed Validator
*/
PARAM_DEFINE_INT32(ASPD_TAS_GATE, 3);
/**
* Airspeed Selector: Gate size for sideslip angle fusion
*
* Sets the number of standard deviations used by the innovation consistency test.
*
* @min 1
* @max 5
* @unit SD
* @group Airspeed Validator
*/
PARAM_DEFINE_INT32(ASPD_BETA_GATE, 1);
/**
* Automatic airspeed scale estimation on
*
* Turns the automatic airspeed scale (scale from IAS to CAS) on or off. It is recommended to fly level
* altitude while performing the estimation. Set to 1 to start estimation (best when already flying).
* Set to 0 to end scale estimation. The estimated scale is then saved using the ASPD_SCALE parameter.
*
* @boolean
* @group Airspeed Validator
*/
PARAM_DEFINE_INT32(ASPD_SCALE_EST, 0);
/**
* Airspeed scale (scale from IAS to CAS)
*
* Scale can either be entered manually, or estimated in-flight by setting ASPD_SCALE_EST to 1.
*
* @min 0.5
* @max 1.5
* @group Airspeed Validator
*/
PARAM_DEFINE_FLOAT(ASPD_SCALE, 1.0f);
/**
* Index or primary airspeed measurement source
*
* @value -1 Disabled
* @value 0 Groundspeed minus windspeed
* @value 1 First airspeed sensor
* @value 2 Second airspeed sensor
* @value 3 Third airspeed sensor
*
* @reboot_required true
* @group Airspeed Validator
*/
PARAM_DEFINE_INT32(ASPD_PRIMARY, 1);
/**
* Enable checks on airspeed sensors
*
* If set to true then the data comming from the airspeed sensors is checked for validity. Only applied if ASPD_PRIMARY > 0.
*
* @boolean
* @group Airspeed Validator
*/
PARAM_DEFINE_INT32(ASPD_DO_CHECKS, 1);
/**
* Enable fallback to sensor-less airspeed estimation
*
* If set to true and airspeed checks are enabled, it will use a sensor-less airspeed estimation based on groundspeed
* minus windspeed if no other airspeed sensor available to fall back to.
*
* @value 0 Disable fallback to sensor-less estimation
* @value 1 Enable fallback to sensor-less estimation
* @boolean
* @group Airspeed Validator
*/
PARAM_DEFINE_INT32(ASPD_FALLBACK_GW, 0);
/**
* Airspeed failsafe consistency threshold
*
* This specifies the minimum airspeed test ratio required to trigger a failsafe. Larger values make the check less sensitive,
* smaller values make it more sensitive. Start with a value of 1.0 when tuning. When tas_test_ratio is > 1.0 it indicates the
* inconsistency between predicted and measured airspeed is large enough to cause the wind EKF to reject airspeed measurements.
* The time required to detect a fault when the threshold is exceeded depends on the size of the exceedance and is controlled by the ASPD_FS_INTEG parameter.
*
* @min 0.5
* @max 3.0
* @group Airspeed Validator
*/
PARAM_DEFINE_FLOAT(ASPD_FS_INNOV, 1.0f);
/**
* Airspeed failsafe consistency delay
*
* This sets the time integral of airspeed test ratio exceedance above ASPD_FS_INNOV required to trigger a failsafe.
* For example if ASPD_FS_INNOV is 1 and estimator_status.tas_test_ratio is 2.0, then the exceedance is 1.0 and the integral will
* rise at a rate of 1.0/second. A negative value disables the check. Larger positive values make the check less sensitive, smaller positive values
* make it more sensitive.
*
* @unit s
* @max 30.0
* @group Airspeed Validator
*/
PARAM_DEFINE_FLOAT(ASPD_FS_INTEG, 5.0f);
/**
* Airspeed failsafe stop delay
*
* Delay before stopping use of airspeed sensor if checks indicate sensor is bad.
*
* @unit s
* @group Airspeed Validator
* @min 1
* @max 10
*/
PARAM_DEFINE_INT32(ASPD_FS_T_STOP, 2);
/**
* Airspeed failsafe start delay
*
* Delay before switching back to using airspeed sensor if checks indicate sensor is good.
* Set to a negative value to disable the re-enabling in flight.
*
* @unit s
* @group Airspeed Validator
* @min -1
* @max 1000
*/
PARAM_DEFINE_INT32(ASPD_FS_T_START, -1);

@ -1,115 +0,0 @@
/****************************************************************************
*
* Copyright (c) 2019 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name PX4 nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
/**
* @file AngularVelocityControl.cpp
*/
#include <AngularVelocityControl.hpp>
#include <px4_platform_common/defines.h>
using namespace matrix;
void AngularVelocityControl::setGains(const Vector3f &P, const Vector3f &I, const Vector3f &D)
{
_gain_p = P;
_gain_i = I;
_gain_d = D;
}
void AngularVelocityControl::setSaturationStatus(const matrix::Vector<bool, 3> &saturation_positive,
const matrix::Vector<bool, 3> &saturation_negative)
{
_saturation_positive = saturation_positive;
_saturation_negative = saturation_negative;
}
void AngularVelocityControl::update(const Vector3f &angular_velocity, const Vector3f &angular_velocity_sp,
const Vector3f &angular_acceleration, const float dt, const bool landed)
{
// angular rates error
Vector3f angular_velocity_error = angular_velocity_sp - angular_velocity;
// P + D control
_angular_accel_sp = _gain_p.emult(angular_velocity_error) - _gain_d.emult(angular_acceleration);
// I + FF control
Vector3f torque_feedforward = _angular_velocity_int + _gain_ff.emult(angular_velocity_sp);
// compute torque setpoint
_torque_sp = _inertia * _angular_accel_sp + torque_feedforward + angular_velocity.cross(_inertia * angular_velocity);
// update integral only if we are not landed
if (!landed) {
updateIntegral(angular_velocity_error, dt);
}
}
void AngularVelocityControl::updateIntegral(Vector3f &angular_velocity_error, const float dt)
{
for (int i = 0; i < 3; i++) {
// prevent further positive control saturation
if (_saturation_positive(i)) {
angular_velocity_error(i) = math::min(angular_velocity_error(i), 0.f);
}
// prevent further negative control saturation
if (_saturation_negative(i)) {
angular_velocity_error(i) = math::max(angular_velocity_error(i), 0.f);
}
// I term factor: reduce the I gain with increasing rate error.
// This counteracts a non-linear effect where the integral builds up quickly upon a large setpoint
// change (noticeable in a bounce-back effect after a flip).
// The formula leads to a gradual decrease w/o steps, while only affecting the cases where it should:
// with the parameter set to 400 degrees, up to 100 deg rate error, i_factor is almost 1 (having no effect),
// and up to 200 deg error leads to <25% reduction of I.
float i_factor = angular_velocity_error(i) / math::radians(400.f);
i_factor = math::max(0.0f, 1.f - i_factor * i_factor);
// Perform the integration using a first order method
float angular_velocity_i = _angular_velocity_int(i) + i_factor * _gain_i(i) * angular_velocity_error(i) * dt;
// do not propagate the result if out of range or invalid
if (PX4_ISFINITE(angular_velocity_i)) {
_angular_velocity_int(i) = math::constrain(angular_velocity_i, -_lim_int(i), _lim_int(i));
}
}
}
void AngularVelocityControl::reset()
{
_angular_velocity_int.zero();
_torque_sp.zero();
_angular_accel_sp.zero();
}

@ -1,149 +0,0 @@
/****************************************************************************
*
* Copyright (c) 2019 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name PX4 nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
/**
* @file AngularVelocityControl.hpp
*
* PID 3 axis angular velocity control.
*/
#pragma once
#include <matrix/matrix/math.hpp>
#include <lib/mixer/MultirotorMixer/MultirotorMixer.hpp>
class AngularVelocityControl
{
public:
AngularVelocityControl() = default;
~AngularVelocityControl() = default;
/**
* Set the control gains
* @param P 3D vector of proportional gains for body x,y,z axis
* @param I 3D vector of integral gains
* @param D 3D vector of derivative gains
*/
void setGains(const matrix::Vector3f &P, const matrix::Vector3f &I, const matrix::Vector3f &D);
/**
* Set the mximum absolute value of the integrator for all axes
* @param integrator_limit limit value for all axes x, y, z
*/
void setIntegratorLimit(const matrix::Vector3f &integrator_limit) { _lim_int = integrator_limit; };
/**
* Set direct angular velocity setpoint to torque feed forward gain
* @see _gain_ff
* @param FF 3D vector of feed forward gains for body x,y,z axis
*/
void setFeedForwardGain(const matrix::Vector3f &FF) { _gain_ff = FF; };
/**
* Set inertia matrix
* @see _inertia
* @param inertia inertia matrix
*/
void setInertiaMatrix(const matrix::Matrix3f &inertia) { _inertia = inertia; };
/**
* Set saturation status
* @see _saturation_positive
* @see _saturation_negative
* @param saturation_positive positive saturation
* @param saturation_negative negative saturation
*/
void setSaturationStatus(const matrix::Vector<bool, 3> &saturation_positive,
const matrix::Vector<bool, 3> &saturation_negative);
/**
* Run one control loop cycle calculation
* @param angular_velocity estimation of the current vehicle angular velocity
* @param angular_velocity_sp desired vehicle angular velocity setpoint
* @param angular_acceleration estimation of the current vehicle angular acceleration
* @param dt elapsed time since last update
* @param landed whether the vehicle is on the ground
*/
void update(const matrix::Vector3f &angular_velocity, const matrix::Vector3f &angular_velocity_sp,
const matrix::Vector3f &angular_acceleration, const float dt, const bool landed);
/**
* Get the desired angular acceleration
* @see _angular_accel_sp
*/
const matrix::Vector3f &getAngularAccelerationSetpoint() {return _angular_accel_sp;};
/**
* Get the torque vector to apply to the vehicle
* @see _torque_sp
*/
const matrix::Vector3f &getTorqueSetpoint() {return _torque_sp;};
/**
* Get the integral term
* @see _torque_sp
*/
const matrix::Vector3f &getIntegral() {return _angular_velocity_int;};
/**
* Set the integral term to 0 to prevent windup
* @see _angular_velocity_int
*/
void resetIntegral() { _angular_velocity_int.zero(); }
/**
* ReSet the controller state
*/
void reset();
private:
void updateIntegral(matrix::Vector3f &angular_velocity_error, const float dt);
// Gains
matrix::Vector3f _gain_p; ///< proportional gain for all axes x, y, z
matrix::Vector3f _gain_i; ///< integral gain
matrix::Vector3f _gain_d; ///< derivative gain
matrix::Vector3f _lim_int; ///< integrator term maximum absolute value
matrix::Vector3f _gain_ff; ///< direct angular velocity to torque feed forward gain
matrix::Matrix3f _inertia{matrix::eye<float, 3>()}; ///< inertia matrix
// States
matrix::Vector3f _angular_velocity_int;
matrix::Vector<bool, 3> _saturation_positive;
matrix::Vector<bool, 3> _saturation_negative;
// Output
matrix::Vector3f _angular_accel_sp; //< Angular acceleration setpoint computed using P and D gains
matrix::Vector3f _torque_sp; //< Torque setpoint to apply to the vehicle
};

@ -1,45 +0,0 @@
/****************************************************************************
*
* Copyright (C) 2019 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name PX4 nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
#include <gtest/gtest.h>
#include <AngularVelocityControl.hpp>
using namespace matrix;
TEST(AngularVelocityControlTest, AllZeroCase)
{
AngularVelocityControl control;
control.update(Vector3f(), Vector3f(), Vector3f(), 0.f, false);
Vector3f torque = control.getTorqueSetpoint();
EXPECT_EQ(torque, Vector3f());
}

@ -1,41 +0,0 @@
############################################################################
#
# Copyright (c) 2019 PX4 Development Team. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
# 3. Neither the name PX4 nor the names of its contributors may be
# used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
############################################################################
px4_add_library(AngularVelocityControl
AngularVelocityControl.cpp
)
target_compile_options(AngularVelocityControl PRIVATE ${MAX_CUSTOM_OPT_LEVEL})
target_include_directories(AngularVelocityControl PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(AngularVelocityControl PRIVATE mathlib)
px4_add_unit_gtest(SRC AngularVelocityControlTest.cpp LINKLIBS AngularVelocityControl)

@ -1,342 +0,0 @@
/****************************************************************************
*
* Copyright (c) 2013-2019 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name PX4 nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
#include "AngularVelocityController.hpp"
#include <drivers/drv_hrt.h>
#include <circuit_breaker/circuit_breaker.h>
#include <mathlib/math/Limits.hpp>
#include <mathlib/math/Functions.hpp>
#include <ecl/geo/geo.h>
using namespace matrix;
using namespace time_literals;
AngularVelocityController::AngularVelocityController() :
ModuleParams(nullptr),
WorkItem(MODULE_NAME, px4::wq_configurations::ctrl_alloc),
_loop_perf(perf_alloc(PC_ELAPSED, MODULE_NAME": cycle"))
{
_vehicle_status.vehicle_type = vehicle_status_s::VEHICLE_TYPE_ROTARY_WING;
parameters_updated();
}
AngularVelocityController::~AngularVelocityController()
{
perf_free(_loop_perf);
}
bool
AngularVelocityController::init()
{
if (!_vehicle_angular_velocity_sub.registerCallback()) {
PX4_ERR("vehicle_angular_velocity callback registration failed!");
return false;
}
return true;
}
void
AngularVelocityController::parameters_updated()
{
// Control parameters
// The controller gain K is used to convert the parallel (P + I/s + sD) form
// to the ideal (K * [1 + 1/sTi + sTd]) form
const Vector3f k_gains = Vector3f(_param_avc_x_k.get(), _param_avc_y_k.get(), _param_avc_z_k.get());
_control.setGains(
k_gains.emult(Vector3f(_param_avc_x_p.get(), _param_avc_y_p.get(), _param_avc_z_p.get())),
k_gains.emult(Vector3f(_param_avc_x_i.get(), _param_avc_y_i.get(), _param_avc_z_i.get())),
k_gains.emult(Vector3f(_param_avc_x_d.get(), _param_avc_y_d.get(), _param_avc_z_d.get())));
_control.setIntegratorLimit(
Vector3f(_param_avc_x_i_lim.get(), _param_avc_y_i_lim.get(), _param_avc_z_i_lim.get()));
_control.setFeedForwardGain(
Vector3f(_param_avc_x_ff.get(), _param_avc_y_ff.get(), _param_avc_z_ff.get()));
// inertia matrix
const float inertia[3][3] = {
{_param_vm_inertia_xx.get(), _param_vm_inertia_xy.get(), _param_vm_inertia_xz.get()},
{_param_vm_inertia_xy.get(), _param_vm_inertia_yy.get(), _param_vm_inertia_yz.get()},
{_param_vm_inertia_xz.get(), _param_vm_inertia_yz.get(), _param_vm_inertia_zz.get()}
};
_control.setInertiaMatrix(matrix::Matrix3f(inertia));
// Hover thrust
if (!_param_mpc_use_hte.get()) {
_hover_thrust = _param_mpc_thr_hover.get();
}
}
void
AngularVelocityController::Run()
{
if (should_exit()) {
_vehicle_angular_velocity_sub.unregisterCallback();
exit_and_cleanup();
return;
}
perf_begin(_loop_perf);
// Check if parameters have changed
if (_parameter_update_sub.updated()) {
// clear update
parameter_update_s param_update;
_parameter_update_sub.copy(&param_update);
updateParams();
parameters_updated();
}
/* run controller on gyro changes */
vehicle_angular_velocity_s vehicle_angular_velocity;
if (_vehicle_angular_velocity_sub.update(&vehicle_angular_velocity)) {
const hrt_abstime now = hrt_absolute_time();
_timestamp_sample = vehicle_angular_velocity.timestamp_sample;
// Guard against too small (< 0.2ms) and too large (> 20ms) dt's.
const float dt = math::constrain(((now - _last_run) / 1e6f), 0.0002f, 0.02f);
_last_run = now;
const Vector3f angular_velocity{vehicle_angular_velocity.xyz};
/* check for updates in other topics */
_vehicle_status_sub.update(&_vehicle_status);
_vehicle_control_mode_sub.update(&_vehicle_control_mode);
if (_vehicle_land_detected_sub.updated()) {
vehicle_land_detected_s vehicle_land_detected;
if (_vehicle_land_detected_sub.copy(&vehicle_land_detected)) {
_landed = vehicle_land_detected.landed;
_maybe_landed = vehicle_land_detected.maybe_landed;
}
}
// Check for updates of hover thrust
if (_param_mpc_use_hte.get()) {
hover_thrust_estimate_s hte;
if (_hover_thrust_estimate_sub.update(&hte)) {
_hover_thrust = hte.hover_thrust;
}
}
// check angular acceleration topic
vehicle_angular_acceleration_s vehicle_angular_acceleration;
if (_vehicle_angular_acceleration_sub.update(&vehicle_angular_acceleration)) {
_angular_acceleration = Vector3f(vehicle_angular_acceleration.xyz);
}
// check rates setpoint topic
vehicle_rates_setpoint_s vehicle_rates_setpoint;
if (_vehicle_rates_setpoint_sub.update(&vehicle_rates_setpoint)) {
_angular_velocity_sp(0) = vehicle_rates_setpoint.roll;
_angular_velocity_sp(1) = vehicle_rates_setpoint.pitch;
_angular_velocity_sp(2) = vehicle_rates_setpoint.yaw;
_thrust_sp = Vector3f(vehicle_rates_setpoint.thrust_body);
// Scale _thrust_sp in Newton, assuming _hover_thrust is equivalent to 1G
_thrust_sp *= (_param_vm_mass.get() * CONSTANTS_ONE_G / _hover_thrust);
}
// run the controller
if (_vehicle_control_mode.flag_control_rates_enabled) {
// reset integral if disarmed
if (!_vehicle_control_mode.flag_armed || _vehicle_status.vehicle_type != vehicle_status_s::VEHICLE_TYPE_ROTARY_WING) {
_control.resetIntegral();
}
// update saturation status from mixer feedback
control_allocator_status_s control_allocator_status;
if (_control_allocator_status_sub.update(&control_allocator_status)) {
Vector<bool, 3> saturation_positive;
Vector<bool, 3> saturation_negative;
if (!control_allocator_status.torque_setpoint_achieved) {
for (size_t i = 0; i < 3; i++) {
if (control_allocator_status.unallocated_torque[i] > FLT_EPSILON) {
saturation_positive(i) = true;
} else if (control_allocator_status.unallocated_torque[i] < -FLT_EPSILON) {
saturation_negative(i) = true;
}
}
}
_control.setSaturationStatus(saturation_positive, saturation_negative);
}
// run rate controller
_control.update(angular_velocity, _angular_velocity_sp, _angular_acceleration, dt, _maybe_landed || _landed);
// publish rate controller status
rate_ctrl_status_s rate_ctrl_status{};
Vector3f integral = _control.getIntegral();
rate_ctrl_status.timestamp = hrt_absolute_time();
rate_ctrl_status.rollspeed_integ = integral(0);
rate_ctrl_status.pitchspeed_integ = integral(1);
rate_ctrl_status.yawspeed_integ = integral(2);
_rate_ctrl_status_pub.publish(rate_ctrl_status);
// publish controller output
publish_angular_acceleration_setpoint();
publish_torque_setpoint();
publish_thrust_setpoint();
}
}
perf_end(_loop_perf);
}
void
AngularVelocityController::publish_angular_acceleration_setpoint()
{
Vector3f angular_accel_sp = _control.getAngularAccelerationSetpoint();
vehicle_angular_acceleration_setpoint_s v_angular_accel_sp = {};
v_angular_accel_sp.timestamp = hrt_absolute_time();
v_angular_accel_sp.timestamp_sample = _timestamp_sample;
v_angular_accel_sp.xyz[0] = (PX4_ISFINITE(angular_accel_sp(0))) ? angular_accel_sp(0) : 0.0f;
v_angular_accel_sp.xyz[1] = (PX4_ISFINITE(angular_accel_sp(1))) ? angular_accel_sp(1) : 0.0f;
v_angular_accel_sp.xyz[2] = (PX4_ISFINITE(angular_accel_sp(2))) ? angular_accel_sp(2) : 0.0f;
_vehicle_angular_acceleration_setpoint_pub.publish(v_angular_accel_sp);
}
void
AngularVelocityController::publish_torque_setpoint()
{
Vector3f torque_sp = _control.getTorqueSetpoint();
vehicle_torque_setpoint_s v_torque_sp = {};
v_torque_sp.timestamp = hrt_absolute_time();
v_torque_sp.timestamp_sample = _timestamp_sample;
v_torque_sp.xyz[0] = (PX4_ISFINITE(torque_sp(0))) ? torque_sp(0) : 0.0f;
v_torque_sp.xyz[1] = (PX4_ISFINITE(torque_sp(1))) ? torque_sp(1) : 0.0f;
v_torque_sp.xyz[2] = (PX4_ISFINITE(torque_sp(2))) ? torque_sp(2) : 0.0f;
_vehicle_torque_setpoint_pub.publish(v_torque_sp);
}
void
AngularVelocityController::publish_thrust_setpoint()
{
vehicle_thrust_setpoint_s v_thrust_sp = {};
v_thrust_sp.timestamp = hrt_absolute_time();
v_thrust_sp.timestamp_sample = _timestamp_sample;
v_thrust_sp.xyz[0] = (PX4_ISFINITE(_thrust_sp(0))) ? (_thrust_sp(0)) : 0.0f;
v_thrust_sp.xyz[1] = (PX4_ISFINITE(_thrust_sp(1))) ? (_thrust_sp(1)) : 0.0f;
v_thrust_sp.xyz[2] = (PX4_ISFINITE(_thrust_sp(2))) ? (_thrust_sp(2)) : 0.0f;
_vehicle_thrust_setpoint_pub.publish(v_thrust_sp);
}
int AngularVelocityController::task_spawn(int argc, char *argv[])
{
AngularVelocityController *instance = new AngularVelocityController();
if (instance) {
_object.store(instance);
_task_id = task_id_is_work_queue;
if (instance->init()) {
return PX4_OK;
}
} else {
PX4_ERR("alloc failed");
}
delete instance;
_object.store(nullptr);
_task_id = -1;
return PX4_ERROR;
}
int AngularVelocityController::print_status()
{
PX4_INFO("Running");
perf_print_counter(_loop_perf);
return 0;
}
int AngularVelocityController::custom_command(int argc, char *argv[])
{
return print_usage("unknown command");
}
int AngularVelocityController::print_usage(const char *reason)
{
if (reason) {
PX4_WARN("%s\n", reason);
}
PRINT_MODULE_DESCRIPTION(
R"DESCR_STR(
### Description
This implements the angular velocity controller.
It takes angular velocity setpoints and measured angular
velocity as inputs and outputs actuator setpoints.
The controller has a PID loop for angular rate error.
)DESCR_STR");
PRINT_MODULE_USAGE_NAME(MODULE_NAME, "controller");
PRINT_MODULE_USAGE_COMMAND("start");
PRINT_MODULE_USAGE_DEFAULT_COMMANDS();
return 0;
}
/**
* Angular velocity controller app start / stop handling function
*/
extern "C" __EXPORT int angular_velocity_controller_main(int argc, char *argv[]);
int angular_velocity_controller_main(int argc, char *argv[])
{
return AngularVelocityController::main(argc, argv);
}

@ -1,178 +0,0 @@
/****************************************************************************
*
* Copyright (c) 2013-2019 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name PX4 nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
#pragma once
#include <AngularVelocityControl.hpp>
#include <lib/matrix/matrix/math.hpp>
#include <lib/perf/perf_counter.h>
#include <px4_platform_common/defines.h>
#include <px4_platform_common/module.h>
#include <px4_platform_common/module_params.h>
#include <px4_platform_common/posix.h>
#include <px4_platform_common/px4_work_queue/ScheduledWorkItem.hpp>
#include <uORB/Publication.hpp>
#include <uORB/Subscription.hpp>
#include <uORB/SubscriptionCallback.hpp>
#include <uORB/topics/control_allocator_status.h>
#include <uORB/topics/hover_thrust_estimate.h>
#include <uORB/topics/parameter_update.h>
#include <uORB/topics/rate_ctrl_status.h>
#include <uORB/topics/vehicle_angular_acceleration.h>
#include <uORB/topics/vehicle_angular_acceleration_setpoint.h>
#include <uORB/topics/vehicle_angular_velocity.h>
#include <uORB/topics/vehicle_control_mode.h>
#include <uORB/topics/vehicle_land_detected.h>
#include <uORB/topics/vehicle_rates_setpoint.h>
#include <uORB/topics/vehicle_status.h>
#include <uORB/topics/vehicle_thrust_setpoint.h>
#include <uORB/topics/vehicle_torque_setpoint.h>
using namespace time_literals;
class AngularVelocityController : public ModuleBase<AngularVelocityController>, public ModuleParams,
public px4::WorkItem
{
public:
AngularVelocityController();
virtual ~AngularVelocityController();
/** @see ModuleBase */
static int task_spawn(int argc, char *argv[]);
/** @see ModuleBase */
static int custom_command(int argc, char *argv[]);
/** @see ModuleBase */
static int print_usage(const char *reason = nullptr);
/** @see ModuleBase::print_status() */
int print_status() override;
void Run() override;
bool init();
private:
/**
* initialize some vectors/matrices from parameters
*/
void parameters_updated();
void vehicle_status_poll();
void publish_angular_acceleration_setpoint();
void publish_torque_setpoint();
void publish_thrust_setpoint();
void publish_actuator_controls();
AngularVelocityControl _control; ///< class for control calculations
uORB::SubscriptionInterval _parameter_update_sub{ORB_ID(parameter_update), 1_s};
uORB::Subscription _control_allocator_status_sub{ORB_ID(control_allocator_status)}; /**< motor limits subscription */
uORB::Subscription _vehicle_angular_acceleration_sub{ORB_ID(vehicle_angular_acceleration)}; /**< vehicle angular acceleration subscription */
uORB::Subscription _vehicle_control_mode_sub{ORB_ID(vehicle_control_mode)}; /**< vehicle control mode subscription */
uORB::Subscription _vehicle_land_detected_sub{ORB_ID(vehicle_land_detected)}; /**< vehicle land detected subscription */
uORB::Subscription _vehicle_rates_setpoint_sub{ORB_ID(vehicle_rates_setpoint)}; /**< vehicle rates setpoint subscription */
uORB::Subscription _vehicle_status_sub{ORB_ID(vehicle_status)}; /**< vehicle status subscription */
uORB::Subscription _hover_thrust_estimate_sub{ORB_ID(hover_thrust_estimate)};
uORB::SubscriptionCallbackWorkItem _vehicle_angular_velocity_sub{this, ORB_ID(vehicle_angular_velocity)};
uORB::Publication<rate_ctrl_status_s> _rate_ctrl_status_pub{ORB_ID(rate_ctrl_status)}; /**< controller status publication */
uORB::Publication<vehicle_angular_acceleration_setpoint_s> _vehicle_angular_acceleration_setpoint_pub{ORB_ID(vehicle_angular_acceleration_setpoint)}; /**< angular acceleration setpoint publication */
uORB::Publication<vehicle_thrust_setpoint_s> _vehicle_thrust_setpoint_pub{ORB_ID(vehicle_thrust_setpoint)}; /**< thrust setpoint publication */
uORB::Publication<vehicle_torque_setpoint_s> _vehicle_torque_setpoint_pub{ORB_ID(vehicle_torque_setpoint)}; /**< torque setpoint publication */
vehicle_control_mode_s _vehicle_control_mode{};
vehicle_status_s _vehicle_status{};
bool _landed{true};
bool _maybe_landed{true};
float _battery_status_scale{0.0f};
perf_counter_t _loop_perf; /**< loop duration performance counter */
matrix::Vector3f _angular_velocity_sp; /**< angular velocity setpoint */
matrix::Vector3f _angular_acceleration; /**< angular acceleration (estimated) */
matrix::Vector3f _thrust_sp; /**< thrust setpoint */
float _hover_thrust{0.5f}; /**< Normalized hover thrust **/
bool _gear_state_initialized{false}; /**< true if the gear state has been initialized */
hrt_abstime _task_start{hrt_absolute_time()};
hrt_abstime _last_run{0};
hrt_abstime _timestamp_sample{0};
DEFINE_PARAMETERS(
(ParamFloat<px4::params::AVC_X_P>) _param_avc_x_p,
(ParamFloat<px4::params::AVC_X_I>) _param_avc_x_i,
(ParamFloat<px4::params::AVC_X_I_LIM>) _param_avc_x_i_lim,
(ParamFloat<px4::params::AVC_X_D>) _param_avc_x_d,
(ParamFloat<px4::params::AVC_X_FF>) _param_avc_x_ff,
(ParamFloat<px4::params::AVC_X_K>) _param_avc_x_k,
(ParamFloat<px4::params::AVC_Y_P>) _param_avc_y_p,
(ParamFloat<px4::params::AVC_Y_I>) _param_avc_y_i,
(ParamFloat<px4::params::AVC_Y_I_LIM>) _param_avc_y_i_lim,
(ParamFloat<px4::params::AVC_Y_D>) _param_avc_y_d,
(ParamFloat<px4::params::AVC_Y_FF>) _param_avc_y_ff,
(ParamFloat<px4::params::AVC_Y_K>) _param_avc_y_k,
(ParamFloat<px4::params::AVC_Z_P>) _param_avc_z_p,
(ParamFloat<px4::params::AVC_Z_I>) _param_avc_z_i,
(ParamFloat<px4::params::AVC_Z_I_LIM>) _param_avc_z_i_lim,
(ParamFloat<px4::params::AVC_Z_D>) _param_avc_z_d,
(ParamFloat<px4::params::AVC_Z_FF>) _param_avc_z_ff,
(ParamFloat<px4::params::AVC_Z_K>) _param_avc_z_k,
(ParamFloat<px4::params::VM_MASS>) _param_vm_mass,
(ParamFloat<px4::params::VM_INERTIA_XX>) _param_vm_inertia_xx,
(ParamFloat<px4::params::VM_INERTIA_YY>) _param_vm_inertia_yy,
(ParamFloat<px4::params::VM_INERTIA_ZZ>) _param_vm_inertia_zz,
(ParamFloat<px4::params::VM_INERTIA_XY>) _param_vm_inertia_xy,
(ParamFloat<px4::params::VM_INERTIA_XZ>) _param_vm_inertia_xz,
(ParamFloat<px4::params::VM_INERTIA_YZ>) _param_vm_inertia_yz,
(ParamFloat<px4::params::MPC_THR_HOVER>) _param_mpc_thr_hover,
(ParamBool<px4::params::MPC_USE_HTE>) _param_mpc_use_hte
)
};

@ -1,49 +0,0 @@
############################################################################
#
# Copyright (c) 2019 PX4 Development Team. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
# 3. Neither the name PX4 nor the names of its contributors may be
# used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
############################################################################
add_subdirectory(AngularVelocityControl)
px4_add_module(
MODULE modules__angular_velocity_control
MAIN angular_velocity_controller
COMPILE_FLAGS
${MAX_CUSTOM_OPT_LEVEL}
SRCS
AngularVelocityController.cpp
AngularVelocityController.hpp
DEPENDS
circuit_breaker
mathlib
AngularVelocityControl
px4_work_queue
)

@ -1,297 +0,0 @@
/****************************************************************************
*
* Copyright (c) 2013-2019 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name PX4 nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
/**
* @file angular_velocity_controller_params.c
* Parameters for angular velocity controller.
*
* @author Lorenz Meier <lorenz@px4.io>
* @author Anton Babushkin <anton@px4.io>
* @author Julien Lecoeur <julien.lecoeur@gmail.com>
*/
/**
* Body X axis angular velocity P gain
*
* Body X axis angular velocity proportional gain, i.e. control output for angular speed error 1 rad/s.
*
* @unit 1/s
* @min 0.0
* @max 20.0
* @decimal 3
* @increment 0.01
* @group Angular Velocity Control
*/
PARAM_DEFINE_FLOAT(AVC_X_P, 18.f);
/**
* Body X axis angular velocity I gain
*
* Body X axis angular velocity integral gain. Can be set to compensate static thrust difference or gravity center offset.
*
* @unit Nm/rad
* @min 0.0
* @decimal 3
* @increment 0.01
* @group Angular Velocity Control
*/
PARAM_DEFINE_FLOAT(AVC_X_I, 0.2f);
/**
* Body X axis angular velocity integrator limit
*
* Body X axis angular velocity integrator limit. Can be set to increase the amount of integrator available to counteract disturbances or reduced to improve settling time after large roll moment trim changes.
*
* @unit Nm
* @min 0.0
* @decimal 2
* @increment 0.01
* @group Angular Velocity Control
*/
PARAM_DEFINE_FLOAT(AVC_X_I_LIM, 0.3f);
/**
* Body X axis angular velocity D gain
*
* Body X axis angular velocity differential gain. Small values help reduce fast oscillations. If value is too big oscillations will appear again.
*
* @min 0.0
* @max 2.0
* @decimal 4
* @increment 0.01
* @group Angular Velocity Control
*/
PARAM_DEFINE_FLOAT(AVC_X_D, 0.36f);
/**
* Body X axis angular velocity feedforward gain
*
* Improves tracking performance.
*
* @unit Nm/(rad/s)
* @min 0.0
* @decimal 4
* @group Angular Velocity Control
*/
PARAM_DEFINE_FLOAT(AVC_X_FF, 0.0f);
/**
* Body X axis angular velocity controller gain
*
* Global gain of the controller.
*
* This gain scales the P, I and D terms of the controller:
* output = AVC_X_K * (AVC_X_P * error
* + AVC_X_I * error_integral
* + AVC_X_D * error_derivative)
* Set AVC_X_P=1 to implement a PID in the ideal form.
* Set AVC_X_K=1 to implement a PID in the parallel form.
*
* @min 0.0
* @max 5.0
* @decimal 4
* @increment 0.0005
* @group Angular Velocity Control
*/
PARAM_DEFINE_FLOAT(AVC_X_K, 1.0f);
/**
* Body Y axis angular velocity P gain
*
* Body Y axis angular velocity proportional gain, i.e. control output for angular speed error 1 rad/s.
*
* @unit 1/s
* @min 0.0
* @max 20.0
* @decimal 3
* @increment 0.01
* @group Angular Velocity Control
*/
PARAM_DEFINE_FLOAT(AVC_Y_P, 18.f);
/**
* Body Y axis angular velocity I gain
*
* Body Y axis angular velocity integral gain. Can be set to compensate static thrust difference or gravity center offset.
*
* @unit Nm/rad
* @min 0.0
* @decimal 3
* @increment 0.01
* @group Angular Velocity Control
*/
PARAM_DEFINE_FLOAT(AVC_Y_I, 0.2f);
/**
* Body Y axis angular velocity integrator limit
*
* Body Y axis angular velocity integrator limit. Can be set to increase the amount of integrator available to counteract disturbances or reduced to improve settling time after large pitch moment trim changes.
*
* @unit Nm
* @min 0.0
* @decimal 2
* @increment 0.01
* @group Angular Velocity Control
*/
PARAM_DEFINE_FLOAT(AVC_Y_I_LIM, 0.3f);
/**
* Body Y axis angular velocity D gain
*
* Body Y axis angular velocity differential gain. Small values help reduce fast oscillations. If value is too big oscillations will appear again.
*
* @min 0.0
* @max 2.0
* @decimal 4
* @increment 0.01
* @group Angular Velocity Control
*/
PARAM_DEFINE_FLOAT(AVC_Y_D, 0.36f);
/**
* Body Y axis angular velocity feedforward
*
* Improves tracking performance.
*
* @unit Nm/(rad/s)
* @min 0.0
* @decimal 4
* @group Angular Velocity Control
*/
PARAM_DEFINE_FLOAT(AVC_Y_FF, 0.0f);
/**
* Body Y axis angular velocity controller gain
*
* Global gain of the controller.
*
* This gain scales the P, I and D terms of the controller:
* output = AVC_Y_K * (AVC_Y_P * error
* + AVC_Y_I * error_integral
* + AVC_Y_D * error_derivative)
* Set AVC_Y_P=1 to implement a PID in the ideal form.
* Set AVC_Y_K=1 to implement a PID in the parallel form.
*
* @min 0.0
* @max 20.0
* @decimal 4
* @increment 0.0005
* @group Angular Velocity Control
*/
PARAM_DEFINE_FLOAT(AVC_Y_K, 1.0f);
/**
* Body Z axis angular velocity P gain
*
* Body Z axis angular velocity proportional gain, i.e. control output for angular speed error 1 rad/s.
*
* @unit 1/s
* @min 0.0
* @max 20.0
* @decimal 2
* @increment 0.01
* @group Angular Velocity Control
*/
PARAM_DEFINE_FLOAT(AVC_Z_P, 7.f);
/**
* Body Z axis angular velocity I gain
*
* Body Z axis angular velocity integral gain. Can be set to compensate static thrust difference or gravity center offset.
*
* @unit Nm/rad
* @min 0.0
* @decimal 2
* @increment 0.01
* @group Angular Velocity Control
*/
PARAM_DEFINE_FLOAT(AVC_Z_I, 0.1f);
/**
* Body Z axis angular velocity integrator limit
*
* Body Z axis angular velocity integrator limit. Can be set to increase the amount of integrator available to counteract disturbances or reduced to improve settling time after large yaw moment trim changes.
*
* @unit Nm
* @min 0.0
* @decimal 2
* @increment 0.01
* @group Angular Velocity Control
*/
PARAM_DEFINE_FLOAT(AVC_Z_I_LIM, 0.30f);
/**
* Body Z axis angular velocity D gain
*
* Body Z axis angular velocity differential gain. Small values help reduce fast oscillations. If value is too big oscillations will appear again.
*
* @min 0.0
* @max 2.0
* @decimal 2
* @increment 0.01
* @group Angular Velocity Control
*/
PARAM_DEFINE_FLOAT(AVC_Z_D, 0.0f);
/**
* Body Z axis angular velocity feedforward
*
* Improves tracking performance.
*
* @unit Nm/(rad/s)
* @min 0.0
* @decimal 4
* @increment 0.01
* @group Angular Velocity Control
*/
PARAM_DEFINE_FLOAT(AVC_Z_FF, 0.0f);
/**
* Body Z axis angular velocity controller gain
*
* Global gain of the controller.
*
* This gain scales the P, I and D terms of the controller:
* output = AVC_Z_K * (AVC_Z_P * error
* + AVC_Z_I * error_integral
* + AVC_Z_D * error_derivative)
* Set AVC_Z_P=1 to implement a PID in the ideal form.
* Set AVC_Z_K=1 to implement a PID in the parallel form.
*
* @min 0.0
* @max 5.0
* @decimal 4
* @increment 0.0005
* @group Angular Velocity Control
*/
PARAM_DEFINE_FLOAT(AVC_Z_K, 1.0f);

@ -1,109 +0,0 @@
/****************************************************************************
*
* Copyright (c) 2013-2019 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name PX4 nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
/**
* @file vehicle_model_params.c
* Parameters for vehicle model.
*
* @author Julien Lecoeur <julien.lecoeur@gmail.com>
*/
/**
* Mass
*
* @unit kg
* @decimal 5
* @increment 0.00001
* @group Vehicle Model
*/
PARAM_DEFINE_FLOAT(VM_MASS, 1.f);
/**
* Inertia matrix, XX component
*
* @unit kg m^2
* @decimal 5
* @increment 0.00001
* @group Vehicle Model
*/
PARAM_DEFINE_FLOAT(VM_INERTIA_XX, 0.01f);
/**
* Inertia matrix, YY component
*
* @unit kg m^2
* @decimal 5
* @increment 0.00001
* @group Vehicle Model
*/
PARAM_DEFINE_FLOAT(VM_INERTIA_YY, 0.01f);
/**
* Inertia matrix, ZZ component
*
* @unit kg m^2
* @decimal 5
* @increment 0.00001
* @group Vehicle Model
*/
PARAM_DEFINE_FLOAT(VM_INERTIA_ZZ, 0.01f);
/**
* Inertia matrix, XY component
*
* @unit kg m^2
* @decimal 5
* @increment 0.00001
* @group Vehicle Model
*/
PARAM_DEFINE_FLOAT(VM_INERTIA_XY, 0.f);
/**
* Inertia matrix, XZ component
*
* @unit kg m^2
* @decimal 5
* @increment 0.00001
* @group Vehicle Model
*/
PARAM_DEFINE_FLOAT(VM_INERTIA_XZ, 0.f);
/**
* Inertia matrix, YZ component
*
* @unit kg m^2
* @decimal 5
* @increment 0.00001
* @group Vehicle Model
*/
PARAM_DEFINE_FLOAT(VM_INERTIA_YZ, 0.f);

@ -1,45 +0,0 @@
############################################################################
#
# Copyright (c) 2015 PX4 Development Team. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
# 3. Neither the name PX4 nor the names of its contributors may be
# used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
#############################################################################
set(MODULE_CFLAGS)
px4_add_module(
MODULE modules__attitude_estimator_q
MAIN attitude_estimator_q
COMPILE_FLAGS
STACK_MAX 1600
SRCS
attitude_estimator_q_main.cpp
DEPENDS
git_ecl
ecl_geo_lookup
)

@ -1,615 +0,0 @@
/****************************************************************************
*
* Copyright (c) 2015 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name PX4 nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
/*
* @file attitude_estimator_q_main.cpp
*
* Attitude estimator (quaternion based)
*
* @author Anton Babushkin <anton.babushkin@me.com>
*/
#include <float.h>
#include <drivers/drv_hrt.h>
#include <lib/ecl/geo/geo.h>
#include <lib/ecl/geo_lookup/geo_mag_declination.h>
#include <lib/mathlib/mathlib.h>
#include <lib/parameters/param.h>
#include <matrix/math.hpp>
#include <px4_platform_common/defines.h>
#include <px4_platform_common/module.h>
#include <px4_platform_common/module_params.h>
#include <px4_platform_common/posix.h>
#include <uORB/Publication.hpp>
#include <uORB/Subscription.hpp>
#include <uORB/SubscriptionCallback.hpp>
#include <uORB/topics/parameter_update.h>
#include <uORB/topics/sensor_combined.h>
#include <uORB/topics/vehicle_attitude.h>
#include <uORB/topics/vehicle_gps_position.h>
#include <uORB/topics/vehicle_local_position.h>
#include <uORB/topics/vehicle_magnetometer.h>
#include <uORB/topics/vehicle_odometry.h>
using matrix::Dcmf;
using matrix::Eulerf;
using matrix::Quatf;
using matrix::Vector3f;
using matrix::wrap_pi;
using namespace time_literals;
class AttitudeEstimatorQ : public ModuleBase<AttitudeEstimatorQ>, public ModuleParams, public px4::WorkItem
{
public:
AttitudeEstimatorQ();
~AttitudeEstimatorQ() override = default;
/** @see ModuleBase */
static int task_spawn(int argc, char *argv[]);
/** @see ModuleBase */
static int custom_command(int argc, char *argv[]);
/** @see ModuleBase */
static int print_usage(const char *reason = nullptr);
bool init();
private:
void Run() override;
void update_parameters(bool force = false);
bool init_attq();
bool update(float dt);
// Update magnetic declination (in rads) immediately changing yaw rotation
void update_mag_declination(float new_declination);
const float _eo_max_std_dev = 100.0f; /**< Maximum permissible standard deviation for estimated orientation */
const float _dt_min = 0.00001f;
const float _dt_max = 0.02f;
uORB::SubscriptionCallbackWorkItem _sensors_sub{this, ORB_ID(sensor_combined)};
uORB::SubscriptionInterval _parameter_update_sub{ORB_ID(parameter_update), 1_s};
uORB::Subscription _gps_sub{ORB_ID(vehicle_gps_position)};
uORB::Subscription _local_position_sub{ORB_ID(vehicle_local_position)};
uORB::Subscription _vision_odom_sub{ORB_ID(vehicle_visual_odometry)};
uORB::Subscription _mocap_odom_sub{ORB_ID(vehicle_mocap_odometry)};
uORB::Subscription _magnetometer_sub{ORB_ID(vehicle_magnetometer)};
uORB::Publication<vehicle_attitude_s> _att_pub{ORB_ID(vehicle_attitude)};
float _mag_decl{0.0f};
float _bias_max{0.0f};
Vector3f _gyro;
Vector3f _accel;
Vector3f _mag;
Vector3f _vision_hdg;
Vector3f _mocap_hdg;
Quatf _q;
Vector3f _rates;
Vector3f _gyro_bias;
Vector3f _vel_prev;
hrt_abstime _vel_prev_t{0};
Vector3f _pos_acc;
hrt_abstime _last_time{0};
bool _inited{false};
bool _data_good{false};
bool _ext_hdg_good{false};
DEFINE_PARAMETERS(
(ParamFloat<px4::params::ATT_W_ACC>) _param_att_w_acc,
(ParamFloat<px4::params::ATT_W_MAG>) _param_att_w_mag,
(ParamFloat<px4::params::ATT_W_EXT_HDG>) _param_att_w_ext_hdg,
(ParamFloat<px4::params::ATT_W_GYRO_BIAS>) _param_att_w_gyro_bias,
(ParamFloat<px4::params::ATT_MAG_DECL>) _param_att_mag_decl,
(ParamInt<px4::params::ATT_MAG_DECL_A>) _param_att_mag_decl_a,
(ParamInt<px4::params::ATT_EXT_HDG_M>) _param_att_ext_hdg_m,
(ParamInt<px4::params::ATT_ACC_COMP>) _param_att_acc_comp,
(ParamFloat<px4::params::ATT_BIAS_MAX>) _param_att_bias_mas,
(ParamInt<px4::params::SYS_HAS_MAG>) _param_sys_has_mag
)
};
AttitudeEstimatorQ::AttitudeEstimatorQ() :
ModuleParams(nullptr),
WorkItem(MODULE_NAME, px4::wq_configurations::nav_and_controllers)
{
_vel_prev.zero();
_pos_acc.zero();
_gyro.zero();
_accel.zero();
_mag.zero();
_vision_hdg.zero();
_mocap_hdg.zero();
_q.zero();
_rates.zero();
_gyro_bias.zero();
update_parameters(true);
}
bool
AttitudeEstimatorQ::init()
{
if (!_sensors_sub.registerCallback()) {
PX4_ERR("sensor combined callback registration failed!");
return false;
}
return true;
}
void
AttitudeEstimatorQ::Run()
{
if (should_exit()) {
_sensors_sub.unregisterCallback();
exit_and_cleanup();
return;
}
sensor_combined_s sensors;
if (_sensors_sub.update(&sensors)) {
update_parameters();
// Feed validator with recent sensor data
if (sensors.timestamp > 0) {
_gyro(0) = sensors.gyro_rad[0];
_gyro(1) = sensors.gyro_rad[1];
_gyro(2) = sensors.gyro_rad[2];
}
if (sensors.accelerometer_timestamp_relative != sensor_combined_s::RELATIVE_TIMESTAMP_INVALID) {
_accel(0) = sensors.accelerometer_m_s2[0];
_accel(1) = sensors.accelerometer_m_s2[1];
_accel(2) = sensors.accelerometer_m_s2[2];
if (_accel.length() < 0.01f) {
PX4_ERR("degenerate accel!");
return;
}
}
// Update magnetometer
if (_magnetometer_sub.updated()) {
vehicle_magnetometer_s magnetometer;
if (_magnetometer_sub.copy(&magnetometer)) {
_mag(0) = magnetometer.magnetometer_ga[0];
_mag(1) = magnetometer.magnetometer_ga[1];
_mag(2) = magnetometer.magnetometer_ga[2];
if (_mag.length() < 0.01f) {
PX4_ERR("degenerate mag!");
return;
}
}
}
_data_good = true;
// Update vision and motion capture heading
_ext_hdg_good = false;
if (_vision_odom_sub.updated()) {
vehicle_odometry_s vision;
if (_vision_odom_sub.copy(&vision)) {
// validation check for vision attitude data
bool vision_att_valid = PX4_ISFINITE(vision.q[0])
&& (PX4_ISFINITE(vision.pose_covariance[vision.COVARIANCE_MATRIX_ROLL_VARIANCE]) ? sqrtf(fmaxf(
vision.pose_covariance[vision.COVARIANCE_MATRIX_ROLL_VARIANCE],
fmaxf(vision.pose_covariance[vision.COVARIANCE_MATRIX_PITCH_VARIANCE],
vision.pose_covariance[vision.COVARIANCE_MATRIX_YAW_VARIANCE]))) <= _eo_max_std_dev : true);
if (vision_att_valid) {
Dcmf Rvis = Quatf(vision.q);
Vector3f v(1.0f, 0.0f, 0.4f);
// Rvis is Rwr (robot respect to world) while v is respect to world.
// Hence Rvis must be transposed having (Rwr)' * Vw
// Rrw * Vw = vn. This way we have consistency
_vision_hdg = Rvis.transpose() * v;
// vision external heading usage (ATT_EXT_HDG_M 1)
if (_param_att_ext_hdg_m.get() == 1) {
// Check for timeouts on data
_ext_hdg_good = vision.timestamp_sample > 0 && (hrt_elapsed_time(&vision.timestamp_sample) < 500000);
}
}
}
}
if (_mocap_odom_sub.updated()) {
vehicle_odometry_s mocap;
if (_mocap_odom_sub.copy(&mocap)) {
// validation check for mocap attitude data
bool mocap_att_valid = PX4_ISFINITE(mocap.q[0])
&& (PX4_ISFINITE(mocap.pose_covariance[mocap.COVARIANCE_MATRIX_ROLL_VARIANCE]) ? sqrtf(fmaxf(
mocap.pose_covariance[mocap.COVARIANCE_MATRIX_ROLL_VARIANCE],
fmaxf(mocap.pose_covariance[mocap.COVARIANCE_MATRIX_PITCH_VARIANCE],
mocap.pose_covariance[mocap.COVARIANCE_MATRIX_YAW_VARIANCE]))) <= _eo_max_std_dev : true);
if (mocap_att_valid) {
Dcmf Rmoc = Quatf(mocap.q);
Vector3f v(1.0f, 0.0f, 0.4f);
// Rmoc is Rwr (robot respect to world) while v is respect to world.
// Hence Rmoc must be transposed having (Rwr)' * Vw
// Rrw * Vw = vn. This way we have consistency
_mocap_hdg = Rmoc.transpose() * v;
// Motion Capture external heading usage (ATT_EXT_HDG_M 2)
if (_param_att_ext_hdg_m.get() == 2) {
// Check for timeouts on data
_ext_hdg_good = mocap.timestamp_sample > 0 && (hrt_elapsed_time(&mocap.timestamp_sample) < 500000);
}
}
}
}
if (_gps_sub.updated()) {
vehicle_gps_position_s gps;
if (_gps_sub.copy(&gps)) {
if (_param_att_mag_decl_a.get() && (gps.eph < 20.0f)) {
/* set magnetic declination automatically */
update_mag_declination(get_mag_declination_radians(gps.lat, gps.lon));
}
}
}
if (_local_position_sub.updated()) {
vehicle_local_position_s lpos;
if (_local_position_sub.copy(&lpos)) {
if (_param_att_acc_comp.get() && (hrt_elapsed_time(&lpos.timestamp) < 20_ms)
&& lpos.v_xy_valid && lpos.v_z_valid && (lpos.eph < 5.0f) && _inited) {
/* position data is actual */
const Vector3f vel(lpos.vx, lpos.vy, lpos.vz);
/* velocity updated */
if (_vel_prev_t != 0 && lpos.timestamp != _vel_prev_t) {
float vel_dt = (lpos.timestamp - _vel_prev_t) / 1e6f;
/* calculate acceleration in body frame */
_pos_acc = _q.conjugate_inversed((vel - _vel_prev) / vel_dt);
}
_vel_prev_t = lpos.timestamp;
_vel_prev = vel;
} else {
/* position data is outdated, reset acceleration */
_pos_acc.zero();
_vel_prev.zero();
_vel_prev_t = 0;
}
}
}
/* time from previous iteration */
hrt_abstime now = hrt_absolute_time();
const float dt = math::constrain((now - _last_time) / 1e6f, _dt_min, _dt_max);
_last_time = now;
if (update(dt)) {
vehicle_attitude_s att = {};
att.timestamp_sample = sensors.timestamp;
_q.copyTo(att.q);
/* the instance count is not used here */
att.timestamp = hrt_absolute_time();
_att_pub.publish(att);
}
}
}
void
AttitudeEstimatorQ::update_parameters(bool force)
{
// check for parameter updates
if (_parameter_update_sub.updated() || force) {
// clear update
parameter_update_s pupdate;
_parameter_update_sub.copy(&pupdate);
// update parameters from storage
updateParams();
// disable mag fusion if the system does not have a mag
if (_param_sys_has_mag.get() == 0) {
_param_att_w_mag.set(0.0f);
}
// if the weight is zero (=mag disabled), make sure the estimator initializes
if (_param_att_w_mag.get() < FLT_EPSILON) {
_mag(0) = 1.f;
_mag(1) = 0.f;
_mag(2) = 0.f;
}
update_mag_declination(math::radians(_param_att_mag_decl.get()));
}
}
bool
AttitudeEstimatorQ::init_attq()
{
// Rotation matrix can be easily constructed from acceleration and mag field vectors
// 'k' is Earth Z axis (Down) unit vector in body frame
Vector3f k = -_accel;
k.normalize();
// 'i' is Earth X axis (North) unit vector in body frame, orthogonal with 'k'
Vector3f i = (_mag - k * (_mag * k));
i.normalize();
// 'j' is Earth Y axis (East) unit vector in body frame, orthogonal with 'k' and 'i'
Vector3f j = k % i;
// Fill rotation matrix
Dcmf R;
R.row(0) = i;
R.row(1) = j;
R.row(2) = k;
// Convert to quaternion
_q = R;
// Compensate for magnetic declination
Quatf decl_rotation = Eulerf(0.0f, 0.0f, _mag_decl);
_q = _q * decl_rotation;
_q.normalize();
if (PX4_ISFINITE(_q(0)) && PX4_ISFINITE(_q(1)) &&
PX4_ISFINITE(_q(2)) && PX4_ISFINITE(_q(3)) &&
_q.length() > 0.95f && _q.length() < 1.05f) {
_inited = true;
} else {
_inited = false;
}
return _inited;
}
bool
AttitudeEstimatorQ::update(float dt)
{
if (!_inited) {
if (!_data_good) {
return false;
}
return init_attq();
}
Quatf q_last = _q;
// Angular rate of correction
Vector3f corr;
float spinRate = _gyro.length();
if (_param_att_ext_hdg_m.get() > 0 && _ext_hdg_good) {
if (_param_att_ext_hdg_m.get() == 1) {
// Vision heading correction
// Project heading to global frame and extract XY component
Vector3f vision_hdg_earth = _q.conjugate(_vision_hdg);
float vision_hdg_err = wrap_pi(atan2f(vision_hdg_earth(1), vision_hdg_earth(0)));
// Project correction to body frame
corr += _q.conjugate_inversed(Vector3f(0.0f, 0.0f, -vision_hdg_err)) * _param_att_w_ext_hdg.get();
}
if (_param_att_ext_hdg_m.get() == 2) {
// Mocap heading correction
// Project heading to global frame and extract XY component
Vector3f mocap_hdg_earth = _q.conjugate(_mocap_hdg);
float mocap_hdg_err = wrap_pi(atan2f(mocap_hdg_earth(1), mocap_hdg_earth(0)));
// Project correction to body frame
corr += _q.conjugate_inversed(Vector3f(0.0f, 0.0f, -mocap_hdg_err)) * _param_att_w_ext_hdg.get();
}
}
if (_param_att_ext_hdg_m.get() == 0 || !_ext_hdg_good) {
// Magnetometer correction
// Project mag field vector to global frame and extract XY component
Vector3f mag_earth = _q.conjugate(_mag);
float mag_err = wrap_pi(atan2f(mag_earth(1), mag_earth(0)) - _mag_decl);
float gainMult = 1.0f;
const float fifty_dps = 0.873f;
if (spinRate > fifty_dps) {
gainMult = math::min(spinRate / fifty_dps, 10.0f);
}
// Project magnetometer correction to body frame
corr += _q.conjugate_inversed(Vector3f(0.0f, 0.0f, -mag_err)) * _param_att_w_mag.get() * gainMult;
}
_q.normalize();
// Accelerometer correction
// Project 'k' unit vector of earth frame to body frame
// Vector3f k = _q.conjugate_inversed(Vector3f(0.0f, 0.0f, 1.0f));
// Optimized version with dropped zeros
Vector3f k(
2.0f * (_q(1) * _q(3) - _q(0) * _q(2)),
2.0f * (_q(2) * _q(3) + _q(0) * _q(1)),
(_q(0) * _q(0) - _q(1) * _q(1) - _q(2) * _q(2) + _q(3) * _q(3))
);
// If we are not using acceleration compensation based on GPS velocity,
// fuse accel data only if its norm is close to 1 g (reduces drift).
const float accel_norm_sq = _accel.norm_squared();
const float upper_accel_limit = CONSTANTS_ONE_G * 1.1f;
const float lower_accel_limit = CONSTANTS_ONE_G * 0.9f;
if (_param_att_acc_comp.get() || ((accel_norm_sq > lower_accel_limit * lower_accel_limit) &&
(accel_norm_sq < upper_accel_limit * upper_accel_limit))) {
corr += (k % (_accel - _pos_acc).normalized()) * _param_att_w_acc.get();
}
// Gyro bias estimation
if (spinRate < 0.175f) {
_gyro_bias += corr * (_param_att_w_gyro_bias.get() * dt);
for (int i = 0; i < 3; i++) {
_gyro_bias(i) = math::constrain(_gyro_bias(i), -_bias_max, _bias_max);
}
}
_rates = _gyro + _gyro_bias;
// Feed forward gyro
corr += _rates;
// Apply correction to state
_q += _q.derivative1(corr) * dt;
// Normalize quaternion
_q.normalize();
if (!(PX4_ISFINITE(_q(0)) && PX4_ISFINITE(_q(1)) &&
PX4_ISFINITE(_q(2)) && PX4_ISFINITE(_q(3)))) {
// Reset quaternion to last good state
_q = q_last;
_rates.zero();
_gyro_bias.zero();
return false;
}
return true;
}
void
AttitudeEstimatorQ::update_mag_declination(float new_declination)
{
// Apply initial declination or trivial rotations without changing estimation
if (!_inited || fabsf(new_declination - _mag_decl) < 0.0001f) {
_mag_decl = new_declination;
} else {
// Immediately rotate current estimation to avoid gyro bias growth
Quatf decl_rotation = Eulerf(0.0f, 0.0f, new_declination - _mag_decl);
_q = _q * decl_rotation;
_mag_decl = new_declination;
}
}
int
AttitudeEstimatorQ::custom_command(int argc, char *argv[])
{
return print_usage("unknown command");
}
int
AttitudeEstimatorQ::task_spawn(int argc, char *argv[])
{
AttitudeEstimatorQ *instance = new AttitudeEstimatorQ();
if (instance) {
_object.store(instance);
_task_id = task_id_is_work_queue;
if (instance->init()) {
return PX4_OK;
}
} else {
PX4_ERR("alloc failed");
}
delete instance;
_object.store(nullptr);
_task_id = -1;
return PX4_ERROR;
}
int
AttitudeEstimatorQ::print_usage(const char *reason)
{
if (reason) {
PX4_WARN("%s\n", reason);
}
PRINT_MODULE_DESCRIPTION(
R"DESCR_STR(
### Description
Attitude estimator q.
)DESCR_STR");
PRINT_MODULE_USAGE_NAME("AttitudeEstimatorQ", "estimator");
PRINT_MODULE_USAGE_COMMAND("start");
PRINT_MODULE_USAGE_DEFAULT_COMMANDS();
return 0;
}
extern "C" __EXPORT int attitude_estimator_q_main(int argc, char *argv[])
{
return AttitudeEstimatorQ::main(argc, argv);
}

@ -1,136 +0,0 @@
/****************************************************************************
*
* Copyright (c) 2015 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name PX4 nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
/*
* @file attitude_estimator_q_params.c
*
* Parameters for attitude estimator (quaternion based)
*
* @author Anton Babushkin <anton.babushkin@me.com>
*/
/**
* Complimentary filter accelerometer weight
*
* @group Attitude Q estimator
* @min 0
* @max 1
* @decimal 2
*/
PARAM_DEFINE_FLOAT(ATT_W_ACC, 0.2f);
/**
* Complimentary filter magnetometer weight
*
* Set to 0 to avoid using the magnetometer.
*
* @group Attitude Q estimator
* @min 0
* @max 1
* @decimal 2
*/
PARAM_DEFINE_FLOAT(ATT_W_MAG, 0.1f);
/**
* Complimentary filter external heading weight
*
* @group Attitude Q estimator
* @min 0
* @max 1
*/
PARAM_DEFINE_FLOAT(ATT_W_EXT_HDG, 0.1f);
/**
* Complimentary filter gyroscope bias weight
*
* @group Attitude Q estimator
* @min 0
* @max 1
* @decimal 2
*/
PARAM_DEFINE_FLOAT(ATT_W_GYRO_BIAS, 0.1f);
/**
* Magnetic declination, in degrees
*
* This parameter is not used in normal operation,
* as the declination is looked up based on the
* GPS coordinates of the vehicle.
*
* @group Attitude Q estimator
* @unit deg
* @decimal 2
*/
PARAM_DEFINE_FLOAT(ATT_MAG_DECL, 0.0f);
/**
* Automatic GPS based declination compensation
*
* @group Attitude Q estimator
* @boolean
*/
PARAM_DEFINE_INT32(ATT_MAG_DECL_A, 1);
/**
* External heading usage mode (from Motion capture/Vision)
*
* Set to 1 to use heading estimate from vision.
* Set to 2 to use heading from motion capture.
*
* @group Attitude Q estimator
* @value 0 None
* @value 1 Vision
* @value 2 Motion Capture
* @min 0
* @max 2
*/
PARAM_DEFINE_INT32(ATT_EXT_HDG_M, 0);
/**
* Acceleration compensation based on GPS velocity.
*
* @group Attitude Q estimator
* @boolean
*/
PARAM_DEFINE_INT32(ATT_ACC_COMP, 1);
/**
* Gyro bias limit
*
* @group Attitude Q estimator
* @unit rad/s
* @min 0
* @max 2
* @decimal 3
*/
PARAM_DEFINE_FLOAT(ATT_BIAS_MAX, 0.05f);

@ -1,47 +0,0 @@
############################################################################
#
# Copyright (c) 2015-2020 PX4 Development Team. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
# 3. Neither the name PX4 nor the names of its contributors may be
# used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
############################################################################
px4_add_module(
MODULE modules__battery_status
MAIN battery_status
SRCS
battery_status.cpp
analog_battery.cpp
MODULE_CONFIG
module.yaml
DEPENDS
battery
conversion
drivers__device
mathlib
)

@ -1,167 +0,0 @@
/****************************************************************************
*
* Copyright (c) 2019-2021 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name PX4 nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
#include <stdio.h>
#include <lib/battery/battery.h>
#include "analog_battery.h"
// Defaults to use if the parameters are not set
#if BOARD_NUMBER_BRICKS > 0
#if defined(BOARD_BATT_V_LIST) && defined(BOARD_BATT_I_LIST)
static constexpr int DEFAULT_V_CHANNEL[BOARD_NUMBER_BRICKS] = BOARD_BATT_V_LIST;
static constexpr int DEFAULT_I_CHANNEL[BOARD_NUMBER_BRICKS] = BOARD_BATT_I_LIST;
#else
static constexpr int DEFAULT_V_CHANNEL[BOARD_NUMBER_BRICKS] = {0};
static constexpr int DEFAULT_I_CHANNEL[BOARD_NUMBER_BRICKS] = {0};
#endif
#else
static constexpr int DEFAULT_V_CHANNEL[1] = {0};
static constexpr int DEFAULT_I_CHANNEL[1] = {0};
#endif
AnalogBattery::AnalogBattery(int index, ModuleParams *parent, const int sample_interval_us) :
Battery(index, parent, sample_interval_us)
{
char param_name[17];
_analog_param_handles.v_offs_cur = param_find("BAT_V_OFFS_CURR");
snprintf(param_name, sizeof(param_name), "BAT%d_V_DIV", index);
_analog_param_handles.v_div = param_find(param_name);
snprintf(param_name, sizeof(param_name), "BAT%d_A_PER_V", index);
_analog_param_handles.a_per_v = param_find(param_name);
snprintf(param_name, sizeof(param_name), "BAT%d_V_CHANNEL", index);
_analog_param_handles.v_channel = param_find(param_name);
snprintf(param_name, sizeof(param_name), "BAT%d_I_CHANNEL", index);
_analog_param_handles.i_channel = param_find(param_name);
_analog_param_handles.v_div_old = param_find("BAT_V_DIV");
_analog_param_handles.a_per_v_old = param_find("BAT_A_PER_V");
_analog_param_handles.adc_channel_old = param_find("BAT_ADC_CHANNEL");
}
void
AnalogBattery::updateBatteryStatusADC(hrt_abstime timestamp, float voltage_raw, float current_raw,
int source, int priority, float throttle_normalized)
{
float voltage_v = voltage_raw * _analog_params.v_div;
float current_a = (current_raw - _analog_params.v_offs_cur) * _analog_params.a_per_v;
bool connected = voltage_v > BOARD_ADC_OPEN_CIRCUIT_V &&
(BOARD_ADC_OPEN_CIRCUIT_V <= BOARD_VALID_UV || is_valid());
Battery::updateBatteryStatus(timestamp, voltage_v, current_a, connected,
source, priority, throttle_normalized);
}
bool AnalogBattery::is_valid()
{
#ifdef BOARD_BRICK_VALID_LIST
bool valid[BOARD_NUMBER_BRICKS] = BOARD_BRICK_VALID_LIST;
return valid[_index - 1];
#else
// TODO: Maybe return false instead?
return true;
#endif
}
int AnalogBattery::get_voltage_channel()
{
if (_analog_params.v_channel >= 0) {
return _analog_params.v_channel;
} else {
return DEFAULT_V_CHANNEL[_index - 1];
}
}
int AnalogBattery::get_current_channel()
{
if (_analog_params.i_channel >= 0) {
return _analog_params.i_channel;
} else {
return DEFAULT_I_CHANNEL[_index - 1];
}
}
void
AnalogBattery::updateParams()
{
if (_index == 1) {
migrateParam<float>(_analog_param_handles.v_div_old, _analog_param_handles.v_div, &_analog_params.v_div_old,
&_analog_params.v_div, _first_parameter_update);
migrateParam<float>(_analog_param_handles.a_per_v_old, _analog_param_handles.a_per_v, &_analog_params.a_per_v_old,
&_analog_params.a_per_v, _first_parameter_update);
migrateParam<int32_t>(_analog_param_handles.adc_channel_old, _analog_param_handles.v_channel,
&_analog_params.adc_channel_old, &_analog_params.v_channel, _first_parameter_update);
} else {
param_get(_analog_param_handles.v_div, &_analog_params.v_div);
param_get(_analog_param_handles.a_per_v, &_analog_params.a_per_v);
param_get(_analog_param_handles.v_channel, &_analog_params.v_channel);
}
param_get(_analog_param_handles.i_channel, &_analog_params.i_channel);
param_get(_analog_param_handles.v_offs_cur, &_analog_params.v_offs_cur);
if (_analog_params.v_div < 0.0f) {
/* apply scaling according to defaults if set to default */
_analog_params.v_div = BOARD_BATTERY1_V_DIV;
param_set_no_notification(_analog_param_handles.v_div, &_analog_params.v_div);
if (_index == 1) {
_analog_params.v_div_old = BOARD_BATTERY1_V_DIV;
param_set_no_notification(_analog_param_handles.v_div_old, &_analog_params.v_div_old);
}
}
if (_analog_params.a_per_v < 0.0f) {
/* apply scaling according to defaults if set to default */
_analog_params.a_per_v = BOARD_BATTERY1_A_PER_V;
param_set_no_notification(_analog_param_handles.a_per_v, &_analog_params.a_per_v);
if (_index == 1) {
_analog_params.a_per_v_old = BOARD_BATTERY1_A_PER_V;
param_set_no_notification(_analog_param_handles.a_per_v_old, &_analog_params.a_per_v_old);
}
}
Battery::updateParams();
}

@ -1,100 +0,0 @@
/****************************************************************************
*
* Copyright (c) 2019-2021 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name PX4 nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
#pragma once
#include <battery/battery.h>
#include <parameters/param.h>
class AnalogBattery : public Battery
{
public:
AnalogBattery(int index, ModuleParams *parent, const int sample_interval_us);
/**
* Update current battery status message.
*
* @param voltage_raw Battery voltage read from ADC, volts
* @param current_raw Voltage of current sense resistor, volts
* @param timestamp Time at which the ADC was read (use hrt_absolute_time())
* @param source The source as defined by param BAT%d_SOURCE
* @param priority: The brick number -1. The term priority refers to the Vn connection on the LTC4417
* @param throttle_normalized Throttle of the vehicle, between 0 and 1
*/
void updateBatteryStatusADC(hrt_abstime timestamp, float voltage_raw, float current_raw,
int source, int priority, float throttle_normalized);
/**
* Whether the ADC channel for the voltage of this battery is valid.
* Corresponds to BOARD_BRICK_VALID_LIST
*/
bool is_valid();
/**
* Which ADC channel is used for voltage reading of this battery
*/
int get_voltage_channel();
/**
* Which ADC channel is used for current reading of this battery
*/
int get_current_channel();
protected:
struct {
param_t v_offs_cur;
param_t v_div;
param_t a_per_v;
param_t v_channel;
param_t i_channel;
param_t v_div_old;
param_t a_per_v_old;
param_t adc_channel_old;
} _analog_param_handles;
struct {
float v_offs_cur;
float v_div;
float a_per_v;
int32_t v_channel;
int32_t i_channel;
float v_div_old;
float a_per_v_old;
int32_t adc_channel_old;
} _analog_params;
virtual void updateParams() override;
};

@ -1,43 +0,0 @@
/****************************************************************************
*
* Copyright (c) 2012-2017 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name PX4 nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
/**
* Offset in volt as seen by the ADC input of the current sensor.
*
* This offset will be subtracted before calculating the battery
* current based on the voltage.
*
* @group Battery Calibration
* @decimal 8
*/
PARAM_DEFINE_FLOAT(BAT_V_OFFS_CURR, 0.0);

@ -1,39 +0,0 @@
/****************************************************************************
*
* Copyright (c) 2012-2019 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name PX4 nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
/**
* This parameter is deprecated. Please use BAT1_ADC_CHANNEL.
*
* @group Battery Calibration
*/
PARAM_DEFINE_INT32(BAT_ADC_CHANNEL, -1);

@ -1,321 +0,0 @@
/****************************************************************************
*
* Copyright (c) 2012-2019 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name PX4 nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
/**
* @file sensors.cpp
*
* @author Lorenz Meier <lorenz@px4.io>
* @author Julian Oes <julian@oes.ch>
* @author Thomas Gubler <thomas@px4.io>
* @author Anton Babushkin <anton@px4.io>
* @author Beat Küng <beat-kueng@gmx.net>
*/
#include <px4_platform_common/px4_config.h>
#include <px4_platform_common/module.h>
#include <px4_platform_common/module_params.h>
#include <px4_platform_common/getopt.h>
#include <px4_platform_common/posix.h>
#include <px4_platform_common/tasks.h>
#include <px4_platform_common/time.h>
#include <px4_platform_common/log.h>
#include <lib/mathlib/mathlib.h>
#include <drivers/drv_hrt.h>
#include <drivers/drv_adc.h>
#include <lib/parameters/param.h>
#include <lib/perf/perf_counter.h>
#include <lib/battery/battery.h>
#include <lib/conversion/rotation.h>
#include <uORB/Subscription.hpp>
#include <uORB/SubscriptionCallback.hpp>
#include <uORB/Publication.hpp>
#include <uORB/topics/actuator_controls.h>
#include <uORB/topics/parameter_update.h>
#include <uORB/topics/adc_report.h>
#include <px4_platform_common/px4_work_queue/ScheduledWorkItem.hpp>
#include "analog_battery.h"
using namespace time_literals;
/**
* The channel definitions (e.g., ADC_BATTERY_VOLTAGE_CHANNEL, ADC_BATTERY_CURRENT_CHANNEL, and ADC_AIRSPEED_VOLTAGE_CHANNEL) are defined in board_config.h
*/
#ifndef BOARD_NUMBER_BRICKS
#error "battery_status module requires power bricks"
#endif
#if BOARD_NUMBER_BRICKS == 0
#error "battery_status module requires power bricks"
#endif
class BatteryStatus : public ModuleBase<BatteryStatus>, public ModuleParams, public px4::ScheduledWorkItem
{
public:
BatteryStatus();
~BatteryStatus() override;
/** @see ModuleBase */
static int task_spawn(int argc, char *argv[]);
/** @see ModuleBase */
static int custom_command(int argc, char *argv[]);
/** @see ModuleBase */
static int print_usage(const char *reason = nullptr);
bool init();
private:
void Run() override;
uORB::Subscription _actuator_ctrl_0_sub{ORB_ID(actuator_controls_0)}; /**< attitude controls sub */
uORB::SubscriptionInterval _parameter_update_sub{ORB_ID(parameter_update), 1_s}; /**< notification of parameter updates */
uORB::SubscriptionCallbackWorkItem _adc_report_sub{this, ORB_ID(adc_report)};
static constexpr uint32_t SAMPLE_FREQUENCY_HZ = 100;
static constexpr uint32_t SAMPLE_INTERVAL_US = 1_s / SAMPLE_FREQUENCY_HZ;
AnalogBattery _battery1;
#if BOARD_NUMBER_BRICKS > 1
AnalogBattery _battery2;
#endif
AnalogBattery *_analogBatteries[BOARD_NUMBER_BRICKS] {
&_battery1,
#if BOARD_NUMBER_BRICKS > 1
&_battery2,
#endif
}; // End _analogBatteries
perf_counter_t _loop_perf; /**< loop performance counter */
/**
* Check for changes in parameters.
*/
void parameter_update_poll(bool forced = false);
/**
* Poll the ADC and update readings to suit.
*
* @param raw Combined sensor data structure into which
* data should be returned.
*/
void adc_poll();
};
BatteryStatus::BatteryStatus() :
ModuleParams(nullptr),
ScheduledWorkItem(MODULE_NAME, px4::wq_configurations::hp_default),
_battery1(1, this, SAMPLE_INTERVAL_US),
#if BOARD_NUMBER_BRICKS > 1
_battery2(2, this, SAMPLE_INTERVAL_US),
#endif
_loop_perf(perf_alloc(PC_ELAPSED, MODULE_NAME))
{
updateParams();
}
BatteryStatus::~BatteryStatus()
{
ScheduleClear();
}
void
BatteryStatus::parameter_update_poll(bool forced)
{
// check for parameter updates
if (_parameter_update_sub.updated() || forced) {
// clear update
parameter_update_s pupdate;
_parameter_update_sub.copy(&pupdate);
// update parameters from storage
updateParams();
}
}
void
BatteryStatus::adc_poll()
{
/* For legacy support we publish the battery_status for the Battery that is
* associated with the Brick that is the selected source for VDD_5V_IN
* Selection is done in HW ala a LTC4417 or similar, or may be hard coded
* Like in the FMUv4
*/
/* Per Brick readings with default unread channels at 0 */
float bat_current_adc_readings[BOARD_NUMBER_BRICKS] {};
float bat_voltage_adc_readings[BOARD_NUMBER_BRICKS] {};
int selected_source = -1;
adc_report_s adc_report;
if (_adc_report_sub.update(&adc_report)) {
/* Read add channels we got */
for (unsigned i = 0; i < PX4_MAX_ADC_CHANNELS; ++i) {
for (int b = 0; b < BOARD_NUMBER_BRICKS; b++) {
/* Once we have subscriptions, Do this once for the lowest (highest priority
* supply on power controller) that is valid.
*/
if (selected_source < 0 && _analogBatteries[b]->is_valid()) {
/* Indicate the lowest brick (highest priority supply on power controller)
* that is valid as the one that is the selected source for the
* VDD_5V_IN
*/
selected_source = b;
}
/* look for specific channels and process the raw voltage to measurement data */
if (adc_report.channel_id[i] == _analogBatteries[b]->get_voltage_channel()) {
/* Voltage in volts */
bat_voltage_adc_readings[b] = adc_report.raw_data[i] *
adc_report.v_ref /
adc_report.resolution;
} else if (adc_report.channel_id[i] == _analogBatteries[b]->get_current_channel()) {
bat_current_adc_readings[b] = adc_report.raw_data[i] *
adc_report.v_ref /
adc_report.resolution;
}
}
}
for (int b = 0; b < BOARD_NUMBER_BRICKS; b++) {
actuator_controls_s ctrl{};
_actuator_ctrl_0_sub.copy(&ctrl);
_analogBatteries[b]->updateBatteryStatusADC(
hrt_absolute_time(),
bat_voltage_adc_readings[b],
bat_current_adc_readings[b],
battery_status_s::BATTERY_SOURCE_POWER_MODULE,
b,
ctrl.control[actuator_controls_s::INDEX_THROTTLE]
);
}
}
}
void
BatteryStatus::Run()
{
if (should_exit()) {
exit_and_cleanup();
return;
}
perf_begin(_loop_perf);
/* check parameters for updates */
parameter_update_poll();
/* check battery voltage */
adc_poll();
perf_end(_loop_perf);
}
int
BatteryStatus::task_spawn(int argc, char *argv[])
{
BatteryStatus *instance = new BatteryStatus();
if (instance) {
_object.store(instance);
_task_id = task_id_is_work_queue;
if (instance->init()) {
return PX4_OK;
}
} else {
PX4_ERR("alloc failed");
}
delete instance;
_object.store(nullptr);
_task_id = -1;
return PX4_ERROR;
}
bool
BatteryStatus::init()
{
return _adc_report_sub.registerCallback();
}
int BatteryStatus::custom_command(int argc, char *argv[])
{
return print_usage("unknown command");
}
int BatteryStatus::print_usage(const char *reason)
{
if (reason) {
PX4_WARN("%s\n", reason);
}
PRINT_MODULE_DESCRIPTION(
R"DESCR_STR(
### Description
The provided functionality includes:
- Read the output from the ADC driver (via ioctl interface) and publish `battery_status`.
### Implementation
It runs in its own thread and polls on the currently selected gyro topic.
)DESCR_STR");
PRINT_MODULE_USAGE_NAME("battery_status", "system");
PRINT_MODULE_USAGE_COMMAND("start");
PRINT_MODULE_USAGE_DEFAULT_COMMANDS();
return 0;
}
extern "C" __EXPORT int battery_status_main(int argc, char *argv[])
{
return BatteryStatus::main(argc, argv);
}

@ -1,63 +0,0 @@
__max_num_config_instances: &max_num_config_instances 2
module_name: battery_status
parameters:
- group: Battery Calibration
definitions:
BAT${i}_V_DIV:
description:
short: Battery ${i} voltage divider (V divider)
long: |
This is the divider from battery ${i} voltage to ADC voltage.
If using e.g. Mauch power modules the value from the datasheet
can be applied straight here. A value of -1 means to use
the board default.
type: float
decimal: 8
reboot_required: true
num_instances: *max_num_config_instances
instance_start: 1
default: [-1.0, -1.0]
BAT${i}_A_PER_V:
description:
short: Battery ${i} current per volt (A/V)
long: |
The voltage seen by the ADC multiplied by this factor
will determine the battery current. A value of -1 means to use
the board default.
type: float
decimal: 8
reboot_required: true
num_instances: *max_num_config_instances
instance_start: 1
default: [-1.0, -1.0]
BAT${i}_V_CHANNEL:
description:
short: Battery ${i} Voltage ADC Channel
long: |
This parameter specifies the ADC channel used to monitor voltage of main power battery.
A value of -1 means to use the board default.
type: int32
reboot_required: true
num_instances: *max_num_config_instances
instance_start: 1
default: [-1, -1]
BAT${i}_I_CHANNEL:
description:
short: Battery ${i} Current ADC Channel
long: |
This parameter specifies the ADC channel used to monitor current of main power battery.
A value of -1 means to use the board default.
type: int32
reboot_required: true
num_instances: *max_num_config_instances
instance_start: 1
default: [-1, -1]

@ -1,41 +0,0 @@
############################################################################
#
# Copyright (c) 2017-2019 PX4 Development Team. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
# 3. Neither the name PX4 nor the names of its contributors may be
# used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
############################################################################
px4_add_module(
MODULE modules__camera_feedback
MAIN camera_feedback
SRCS
CameraFeedback.cpp
CameraFeedback.hpp
DEPENDS
px4_work_queue
)

@ -1,168 +0,0 @@
/****************************************************************************
*
* Copyright (c) 2017-2019 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name PX4 nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
#include "CameraFeedback.hpp"
CameraFeedback::CameraFeedback() :
ModuleParams(nullptr),
WorkItem(MODULE_NAME, px4::wq_configurations::hp_default)
{
}
bool
CameraFeedback::init()
{
if (!_trigger_sub.registerCallback()) {
PX4_ERR("camera_trigger callback registration failed!");
return false;
}
return true;
}
void
CameraFeedback::Run()
{
if (should_exit()) {
_trigger_sub.unregisterCallback();
exit_and_cleanup();
return;
}
camera_trigger_s trig{};
if (_trigger_sub.update(&trig)) {
// update geotagging subscriptions
vehicle_global_position_s gpos{};
_gpos_sub.copy(&gpos);
vehicle_attitude_s att{};
_att_sub.copy(&att);
if (trig.timestamp == 0 ||
gpos.timestamp == 0 ||
att.timestamp == 0) {
// reject until we have valid data
return;
}
camera_capture_s capture{};
// Fill timestamps
capture.timestamp = trig.timestamp;
capture.timestamp_utc = trig.timestamp_utc;
// Fill image sequence
capture.seq = trig.seq;
// Fill position data
capture.lat = gpos.lat;
capture.lon = gpos.lon;
capture.alt = gpos.alt;
if (gpos.terrain_alt_valid) {
capture.ground_distance = gpos.alt - gpos.terrain_alt;
} else {
capture.ground_distance = -1.0f;
}
// Fill attitude data
// TODO : this needs to be rotated by camera orientation or set to gimbal orientation when available
capture.q[0] = att.q[0];
capture.q[1] = att.q[1];
capture.q[2] = att.q[2];
capture.q[3] = att.q[3];
capture.result = 1;
_capture_pub.publish(capture);
}
}
int
CameraFeedback::task_spawn(int argc, char *argv[])
{
CameraFeedback *instance = new CameraFeedback();
if (instance) {
_object.store(instance);
_task_id = task_id_is_work_queue;
if (instance->init()) {
return PX4_OK;
}
} else {
PX4_ERR("alloc failed");
}
delete instance;
_object.store(nullptr);
_task_id = -1;
return PX4_ERROR;
}
int
CameraFeedback::custom_command(int argc, char *argv[])
{
return print_usage("unknown command");
}
int
CameraFeedback::print_usage(const char *reason)
{
if (reason) {
PX4_WARN("%s\n", reason);
}
PRINT_MODULE_DESCRIPTION(
R"DESCR_STR(
### Description
)DESCR_STR");
PRINT_MODULE_USAGE_NAME("camera_feedback", "system");
PRINT_MODULE_USAGE_COMMAND("start");
PRINT_MODULE_USAGE_DEFAULT_COMMANDS();
return 0;
}
extern "C" __EXPORT int camera_feedback_main(int argc, char *argv[])
{
return CameraFeedback::main(argc, argv);
}

@ -1,86 +0,0 @@
/****************************************************************************
*
* Copyright (c) 2017-2019 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name PX4 nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
/**
*
* Online and offline geotagging from camera feedback
*
* @author Mohammed Kabir <kabir@uasys.io>
*/
#pragma once
#include <lib/mathlib/mathlib.h>
#include <lib/parameters/param.h>
#include <px4_platform_common/px4_config.h>
#include <px4_platform_common/defines.h>
#include <px4_platform_common/module.h>
#include <px4_platform_common/module_params.h>
#include <px4_platform_common/posix.h>
#include <px4_platform_common/px4_work_queue/WorkItem.hpp>
#include <uORB/Publication.hpp>
#include <uORB/Subscription.hpp>
#include <uORB/SubscriptionCallback.hpp>
#include <uORB/topics/camera_capture.h>
#include <uORB/topics/camera_trigger.h>
#include <uORB/topics/vehicle_attitude.h>
#include <uORB/topics/vehicle_global_position.h>
class CameraFeedback : public ModuleBase<CameraFeedback>, public ModuleParams, public px4::WorkItem
{
public:
CameraFeedback();
~CameraFeedback() override = default;
/** @see ModuleBase */
static int task_spawn(int argc, char *argv[]);
/** @see ModuleBase */
static int custom_command(int argc, char *argv[]);
/** @see ModuleBase */
static int print_usage(const char *reason = nullptr);
bool init();
private:
void Run() override;
uORB::SubscriptionCallbackWorkItem _trigger_sub{this, ORB_ID(camera_trigger)};
uORB::Subscription _gpos_sub{ORB_ID(vehicle_global_position)};
uORB::Subscription _att_sub{ORB_ID(vehicle_attitude)};
uORB::Publication<camera_capture_s> _capture_pub{ORB_ID(camera_capture)};
};

@ -1,298 +0,0 @@
/****************************************************************************
*
* Copyright (c) 2017 Intel Corporation. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name PX4 nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
#include "ArmAuthorization.h"
#include <string.h>
#include <unistd.h>
#include <px4_platform_common/defines.h>
#include <px4_platform_common/px4_config.h>
#include <lib/parameters/param.h>
#include <systemlib/mavlink_log.h>
#include <uORB/Publication.hpp>
#include <uORB/topics/vehicle_command.h>
#include <uORB/topics/vehicle_command_ack.h>
using namespace time_literals;
static orb_advert_t *mavlink_log_pub;
static int command_ack_sub = -1;
static hrt_abstime auth_timeout;
static hrt_abstime auth_req_time;
static hrt_abstime _param_com_arm_auth_timout;
static enum arm_auth_methods _param_com_arm_auth_method;
static int32_t _param_com_arm_auth_id;
static enum {
ARM_AUTH_IDLE = 0,
ARM_AUTH_WAITING_AUTH,
ARM_AUTH_WAITING_AUTH_WITH_ACK,
ARM_AUTH_MISSION_APPROVED
} state = ARM_AUTH_IDLE;
static uint8_t *system_id;
static uint8_t _auth_method_arm_req_check();
static uint8_t _auth_method_two_arm_check();
static uint8_t (*arm_check_method[ARM_AUTH_METHOD_LAST])() = {
_auth_method_arm_req_check,
_auth_method_two_arm_check,
};
void arm_auth_param_update()
{
float timeout = 0;
param_get(param_find("COM_ARM_AUTH_TO"), &timeout);
_param_com_arm_auth_timout = timeout * 1_s;
int32_t auth_method = ARM_AUTH_METHOD_ARM_REQ;
param_get(param_find("COM_ARM_AUTH_MET"), &auth_method);
if (auth_method >= 0 && auth_method < ARM_AUTH_METHOD_LAST) {
_param_com_arm_auth_method = (arm_auth_methods)auth_method;
} else {
_param_com_arm_auth_method = ARM_AUTH_METHOD_ARM_REQ;
}
param_get(param_find("COM_ARM_AUTH_ID"), &_param_com_arm_auth_id);
}
static void arm_auth_request_msg_send()
{
vehicle_command_s vcmd{};
vcmd.timestamp = hrt_absolute_time();
vcmd.command = vehicle_command_s::VEHICLE_CMD_ARM_AUTHORIZATION_REQUEST;
vcmd.target_system = _param_com_arm_auth_id;
uORB::Publication<vehicle_command_s> vcmd_pub{ORB_ID(vehicle_command)};
vcmd_pub.publish(vcmd);
}
static uint8_t _auth_method_arm_req_check()
{
switch (state) {
case ARM_AUTH_IDLE:
/* no authentication in process? handle bellow */
break;
case ARM_AUTH_MISSION_APPROVED:
return vehicle_command_ack_s::VEHICLE_RESULT_ACCEPTED;
default:
return vehicle_command_ack_s::VEHICLE_RESULT_DENIED;
}
/* handling ARM_AUTH_IDLE */
arm_auth_request_msg_send();
hrt_abstime now = hrt_absolute_time();
auth_req_time = now;
auth_timeout = now + _param_com_arm_auth_timout;
state = ARM_AUTH_WAITING_AUTH;
while (now < auth_timeout) {
arm_auth_update(now);
if (state != ARM_AUTH_WAITING_AUTH && state != ARM_AUTH_WAITING_AUTH_WITH_ACK) {
break;
}
/* 0.5ms */
px4_usleep(500);
now = hrt_absolute_time();
}
switch (state) {
case ARM_AUTH_WAITING_AUTH:
case ARM_AUTH_WAITING_AUTH_WITH_ACK:
state = ARM_AUTH_IDLE;
mavlink_log_critical(mavlink_log_pub, "Arm auth: No response");
break;
default:
break;
}
return state == ARM_AUTH_MISSION_APPROVED ?
vehicle_command_ack_s::VEHICLE_RESULT_ACCEPTED : vehicle_command_ack_s::VEHICLE_RESULT_DENIED;
}
static uint8_t _auth_method_two_arm_check()
{
switch (state) {
case ARM_AUTH_IDLE:
/* no authentication in process? handle bellow */
break;
case ARM_AUTH_MISSION_APPROVED:
return vehicle_command_ack_s::VEHICLE_RESULT_ACCEPTED;
case ARM_AUTH_WAITING_AUTH:
case ARM_AUTH_WAITING_AUTH_WITH_ACK:
return vehicle_command_ack_s::VEHICLE_RESULT_TEMPORARILY_REJECTED;
default:
return vehicle_command_ack_s::VEHICLE_RESULT_DENIED;
}
/* handling ARM_AUTH_IDLE */
arm_auth_request_msg_send();
hrt_abstime now = hrt_absolute_time();
auth_req_time = now;
auth_timeout = now + _param_com_arm_auth_timout;
state = ARM_AUTH_WAITING_AUTH;
mavlink_log_info(mavlink_log_pub, "Arm auth: Requesting authorization...");
return vehicle_command_ack_s::VEHICLE_RESULT_TEMPORARILY_REJECTED;
}
uint8_t arm_auth_check()
{
if (_param_com_arm_auth_method < ARM_AUTH_METHOD_LAST) {
return arm_check_method[_param_com_arm_auth_method]();
}
return vehicle_command_ack_s::VEHICLE_RESULT_DENIED;
}
void arm_auth_update(hrt_abstime now, bool param_update)
{
if (param_update) {
arm_auth_param_update();
}
switch (state) {
case ARM_AUTH_WAITING_AUTH:
case ARM_AUTH_WAITING_AUTH_WITH_ACK:
/* handle bellow */
break;
case ARM_AUTH_MISSION_APPROVED:
if (now > auth_timeout) {
state = ARM_AUTH_IDLE;
}
return;
case ARM_AUTH_IDLE:
default:
return;
}
/*
* handling ARM_AUTH_WAITING_AUTH, ARM_AUTH_WAITING_AUTH_WITH_ACK
*/
vehicle_command_ack_s command_ack;
bool updated = false;
orb_check(command_ack_sub, &updated);
if (updated) {
orb_copy(ORB_ID(vehicle_command_ack), command_ack_sub, &command_ack);
}
if (updated
&& command_ack.command == vehicle_command_s::VEHICLE_CMD_ARM_AUTHORIZATION_REQUEST
&& command_ack.target_system == *system_id
&& command_ack.timestamp > auth_req_time) {
switch (command_ack.result) {
case vehicle_command_ack_s::VEHICLE_RESULT_IN_PROGRESS:
state = ARM_AUTH_WAITING_AUTH_WITH_ACK;
break;
case vehicle_command_ack_s::VEHICLE_RESULT_ACCEPTED:
mavlink_log_info(mavlink_log_pub,
"Arm auth: Authorized for the next %" PRId32 " seconds",
command_ack.result_param2);
state = ARM_AUTH_MISSION_APPROVED;
auth_timeout = command_ack.timestamp + (command_ack.result_param2 * 1000000);
return;
case vehicle_command_ack_s::VEHICLE_RESULT_TEMPORARILY_REJECTED:
mavlink_log_critical(mavlink_log_pub, "Arm auth: Temporarily rejected");
state = ARM_AUTH_IDLE;
return;
case vehicle_command_ack_s::VEHICLE_RESULT_DENIED:
default:
switch (command_ack.result_param1) {
case vehicle_command_ack_s::ARM_AUTH_DENIED_REASON_NONE:
/* Authorizer will send reason to ground station */
break;
case vehicle_command_ack_s::ARM_AUTH_DENIED_REASON_INVALID_WAYPOINT:
mavlink_log_critical(mavlink_log_pub, "Arm auth: Denied, waypoint %" PRId32 " have a invalid value",
command_ack.result_param2);
break;
case vehicle_command_ack_s::ARM_AUTH_DENIED_REASON_TIMEOUT:
mavlink_log_critical(mavlink_log_pub, "Arm auth: Denied by timeout in authorizer");
break;
case vehicle_command_ack_s::ARM_AUTH_DENIED_REASON_AIRSPACE_IN_USE:
mavlink_log_critical(mavlink_log_pub, "Arm auth: Denied because airspace is in use");
break;
case vehicle_command_ack_s::ARM_AUTH_DENIED_REASON_BAD_WEATHER:
mavlink_log_critical(mavlink_log_pub, "Arm auth: Denied because of bad weather");
break;
case vehicle_command_ack_s::ARM_AUTH_DENIED_REASON_GENERIC:
default:
mavlink_log_critical(mavlink_log_pub, "Arm auth: Denied");
}
state = ARM_AUTH_IDLE;
return;
}
}
if (now > auth_timeout) {
mavlink_log_critical(mavlink_log_pub, "Arm auth: No response");
state = ARM_AUTH_IDLE;
}
}
void arm_auth_init(orb_advert_t *mav_log_pub, uint8_t *sys_id)
{
arm_auth_param_update();
system_id = sys_id;
command_ack_sub = orb_subscribe(ORB_ID(vehicle_command_ack));
mavlink_log_pub = mav_log_pub;
}

@ -1,49 +0,0 @@
/****************************************************************************
*
* Copyright (c) 2017 Intel Corporation. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name PX4 nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
#pragma once
#include <stdint.h>
#include <drivers/drv_hrt.h>
#include <uORB/uORB.h>
enum arm_auth_methods {
ARM_AUTH_METHOD_ARM_REQ = 0,
ARM_AUTH_METHOD_TWO_ARM_REQ,
ARM_AUTH_METHOD_LAST
};
void arm_auth_init(orb_advert_t *mav_log_pub, uint8_t *system_id);
void arm_auth_update(hrt_abstime now, bool param_update = true);
uint8_t arm_auth_check();

@ -1,40 +0,0 @@
############################################################################
#
# Copyright (c) 2019 PX4 Development Team. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
# 3. Neither the name PX4 nor the names of its contributors may be
# used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
############################################################################
px4_add_library(ArmAuthorization
ArmAuthorization.cpp
)
target_include_directories(ArmAuthorization
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
)

@ -1,36 +0,0 @@
############################################################################
#
# Copyright (c) 2019 PX4 Development Team. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
# 3. Neither the name PX4 nor the names of its contributors may be
# used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
############################################################################
add_subdirectory(PreFlightCheck)
add_subdirectory(ArmAuthorization)
add_subdirectory(HealthFlags)

@ -1,37 +0,0 @@
############################################################################
#
# Copyright (c) 2019 PX4 Development Team. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
# 3. Neither the name PX4 nor the names of its contributors may be
# used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
############################################################################
px4_add_library(HealthFlags
HealthFlags.cpp
)
target_include_directories(HealthFlags PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

@ -1,128 +0,0 @@
/****************************************************************************
*
* Copyright (c) 2013-2020 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name PX4 nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
/**
* @file HealthFlags.cpp
*
* Contains helper functions to efficiently set the system health flags from commander and preflight check.
*
* @author Philipp Oettershagen (philipp.oettershagen@mavt.ethz.ch)
*/
#include "HealthFlags.h"
void set_health_flags(uint64_t subsystem_type, bool present, bool enabled, bool ok, vehicle_status_s &status)
{
PX4_DEBUG("set_health_flags: Type %llu pres=%u enabl=%u ok=%u", (long long unsigned int)subsystem_type, present,
enabled, ok);
if (present) {
status.onboard_control_sensors_present |= (uint32_t)subsystem_type;
} else {
status.onboard_control_sensors_present &= ~(uint32_t)subsystem_type;
}
if (enabled) {
status.onboard_control_sensors_enabled |= (uint32_t)subsystem_type;
} else {
status.onboard_control_sensors_enabled &= ~(uint32_t)subsystem_type;
}
if (ok) {
status.onboard_control_sensors_health |= (uint32_t)subsystem_type;
} else {
status.onboard_control_sensors_health &= ~(uint32_t)subsystem_type;
}
}
void set_health_flags_present_healthy(uint64_t subsystem_type, bool present, bool healthy, vehicle_status_s &status)
{
set_health_flags(subsystem_type, present, status.onboard_control_sensors_enabled & (uint32_t)subsystem_type, healthy,
status);
}
void set_health_flags_healthy(uint64_t subsystem_type, bool healthy, vehicle_status_s &status)
{
set_health_flags(subsystem_type, status.onboard_control_sensors_present & (uint32_t)subsystem_type,
status.onboard_control_sensors_enabled & (uint32_t)subsystem_type, healthy, status);
}
void _print_sub(const char *name, const vehicle_status_s &status, uint32_t bit)
{
PX4_INFO("%s:\t\t%s\t%s", name,
(status.onboard_control_sensors_enabled & bit) ? "EN" : " ",
(status.onboard_control_sensors_present & bit) ?
((status.onboard_control_sensors_health & bit) ? "OK" : "ERR") :
(status.onboard_control_sensors_enabled & bit) ? "OFF" : "");
}
void print_health_flags(const vehicle_status_s &status)
{
#ifndef CONSTRAINED_FLASH
PX4_INFO("DEVICE\t\tSTATUS");
PX4_INFO("----------------------------------");
_print_sub("GYRO", status, subsystem_info_s::SUBSYSTEM_TYPE_GYRO);
_print_sub("ACC", status, subsystem_info_s::SUBSYSTEM_TYPE_ACC);
_print_sub("MAG", status, subsystem_info_s::SUBSYSTEM_TYPE_MAG);
_print_sub("PRESS", status, subsystem_info_s::SUBSYSTEM_TYPE_ABSPRESSURE);
_print_sub("AIRSP", status, subsystem_info_s::SUBSYSTEM_TYPE_DIFFPRESSURE);
_print_sub("GPS", status, subsystem_info_s::SUBSYSTEM_TYPE_GPS);
_print_sub("OPT", status, subsystem_info_s::SUBSYSTEM_TYPE_OPTICALFLOW);
_print_sub("VIO", status, subsystem_info_s::SUBSYSTEM_TYPE_CVPOSITION);
_print_sub("LASER", status, subsystem_info_s::SUBSYSTEM_TYPE_LASERPOSITION);
_print_sub("GTRUTH", status, subsystem_info_s::SUBSYSTEM_TYPE_EXTERNALGROUNDTRUTH);
_print_sub("RATES", status, subsystem_info_s::SUBSYSTEM_TYPE_ANGULARRATECONTROL);
_print_sub("ATT", status, subsystem_info_s::SUBSYSTEM_TYPE_ATTITUDESTABILIZATION);
_print_sub("YAW", status, subsystem_info_s::SUBSYSTEM_TYPE_YAWPOSITION);
_print_sub("ALTCTL", status, subsystem_info_s::SUBSYSTEM_TYPE_ALTITUDECONTROL);
_print_sub("POS", status, subsystem_info_s::SUBSYSTEM_TYPE_POSITIONCONTROL);
_print_sub("MOT", status, subsystem_info_s::SUBSYSTEM_TYPE_MOTORCONTROL);
_print_sub("RC ", status, subsystem_info_s::SUBSYSTEM_TYPE_RCRECEIVER);
_print_sub("GYRO2", status, subsystem_info_s::SUBSYSTEM_TYPE_GYRO2);
_print_sub("ACC2", status, subsystem_info_s::SUBSYSTEM_TYPE_ACC2);
_print_sub("MAG2", status, subsystem_info_s::SUBSYSTEM_TYPE_MAG2);
_print_sub("GEOFENCE", status, subsystem_info_s::SUBSYSTEM_TYPE_GEOFENCE);
_print_sub("AHRS", status, subsystem_info_s::SUBSYSTEM_TYPE_AHRS);
_print_sub("TERRAIN", status, subsystem_info_s::SUBSYSTEM_TYPE_TERRAIN);
_print_sub("REVMOT", status, subsystem_info_s::SUBSYSTEM_TYPE_REVERSEMOTOR);
_print_sub("LOGGIN", status, subsystem_info_s::SUBSYSTEM_TYPE_LOGGING);
_print_sub("BATT", status, subsystem_info_s::SUBSYSTEM_TYPE_SENSORBATTERY);
_print_sub("PROX", status, subsystem_info_s::SUBSYSTEM_TYPE_SENSORPROXIMITY);
_print_sub("SATCOM", status, subsystem_info_s::SUBSYSTEM_TYPE_SATCOM);
_print_sub("PREARM", status, subsystem_info_s::SUBSYSTEM_TYPE_PREARM_CHECK);
_print_sub("OBSAVD", status, subsystem_info_s::SUBSYSTEM_TYPE_OBSTACLE_AVOIDANCE);
#endif
}

@ -1,84 +0,0 @@
/****************************************************************************
*
* Copyright (c) 2013 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name PX4 nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
/**
* @file HealthFlags.h
*
* Contains helper functions to efficiently set the system health flags from commander and preflight check.
*
* @author Philipp Oettershagen (philipp.oettershagen@mavt.ethz.ch)
*/
#pragma once
#include <px4_platform_common/log.h>
#include <uORB/topics/vehicle_status.h>
struct subsystem_info_s {
// keep in sync with mavlink MAV_SYS_STATUS_SENSOR
static constexpr uint64_t SUBSYSTEM_TYPE_GYRO = 1 << 0;
static constexpr uint64_t SUBSYSTEM_TYPE_ACC = 1 << 1;
static constexpr uint64_t SUBSYSTEM_TYPE_MAG = 1 << 2;
static constexpr uint64_t SUBSYSTEM_TYPE_ABSPRESSURE = 1 << 3;
static constexpr uint64_t SUBSYSTEM_TYPE_DIFFPRESSURE = 1 << 4;
static constexpr uint64_t SUBSYSTEM_TYPE_GPS = 1 << 5;
static constexpr uint64_t SUBSYSTEM_TYPE_OPTICALFLOW = 1 << 6;
static constexpr uint64_t SUBSYSTEM_TYPE_CVPOSITION = 1 << 7;
static constexpr uint64_t SUBSYSTEM_TYPE_LASERPOSITION = 1 << 8;
static constexpr uint64_t SUBSYSTEM_TYPE_EXTERNALGROUNDTRUTH = 1 << 9;
static constexpr uint64_t SUBSYSTEM_TYPE_ANGULARRATECONTROL = 1 << 10;
static constexpr uint64_t SUBSYSTEM_TYPE_ATTITUDESTABILIZATION = 1 << 11;
static constexpr uint64_t SUBSYSTEM_TYPE_YAWPOSITION = 1 << 12;
static constexpr uint64_t SUBSYSTEM_TYPE_ALTITUDECONTROL = 1 << 13;
static constexpr uint64_t SUBSYSTEM_TYPE_POSITIONCONTROL = 1 << 14;
static constexpr uint64_t SUBSYSTEM_TYPE_MOTORCONTROL = 1 << 15;
static constexpr uint64_t SUBSYSTEM_TYPE_RCRECEIVER = 1 << 16;
static constexpr uint64_t SUBSYSTEM_TYPE_GYRO2 = 1 << 17;
static constexpr uint64_t SUBSYSTEM_TYPE_ACC2 = 1 << 18;
static constexpr uint64_t SUBSYSTEM_TYPE_MAG2 = 1 << 19;
static constexpr uint64_t SUBSYSTEM_TYPE_GEOFENCE = 1 << 20;
static constexpr uint64_t SUBSYSTEM_TYPE_AHRS = 1 << 21;
static constexpr uint64_t SUBSYSTEM_TYPE_TERRAIN = 1 << 22;
static constexpr uint64_t SUBSYSTEM_TYPE_REVERSEMOTOR = 1 << 23;
static constexpr uint64_t SUBSYSTEM_TYPE_LOGGING = 1 << 24;
static constexpr uint64_t SUBSYSTEM_TYPE_SENSORBATTERY = 1 << 25;
static constexpr uint64_t SUBSYSTEM_TYPE_SENSORPROXIMITY = 1 << 26;
static constexpr uint64_t SUBSYSTEM_TYPE_SATCOM = 1 << 27;
static constexpr uint64_t SUBSYSTEM_TYPE_PREARM_CHECK = 1 << 28;
static constexpr uint64_t SUBSYSTEM_TYPE_OBSTACLE_AVOIDANCE = 1 << 29;
};
void set_health_flags(uint64_t subsystem_type, bool present, bool enabled, bool ok, vehicle_status_s &status);
void set_health_flags_present_healthy(uint64_t subsystem_type, bool present, bool healthy, vehicle_status_s &status);
void set_health_flags_healthy(uint64_t subsystem_type, bool healthy, vehicle_status_s &status);
void print_health_flags(const vehicle_status_s &status);

@ -1,54 +0,0 @@
############################################################################
#
# Copyright (c) 2019 PX4 Development Team. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
# 3. Neither the name PX4 nor the names of its contributors may be
# used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
############################################################################
px4_add_library(PreFlightCheck
PreFlightCheck.cpp
checks/preArmCheck.cpp
checks/magnetometerCheck.cpp
checks/magConsistencyCheck.cpp
checks/accelerometerCheck.cpp
checks/gyroCheck.cpp
checks/baroCheck.cpp
checks/imuConsistencyCheck.cpp
checks/airspeedCheck.cpp
checks/rcCalibrationCheck.cpp
checks/powerCheck.cpp
checks/airframeCheck.cpp
checks/ekf2Check.cpp
checks/failureDetectorCheck.cpp
checks/manualControlCheck.cpp
checks/cpuResourceCheck.cpp
checks/sdcardCheck.cpp
)
target_include_directories(PreFlightCheck PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(PreFlightCheck PUBLIC ArmAuthorization HealthFlags sensor_calibration)

@ -1,267 +0,0 @@
/****************************************************************************
*
* Copyright (c) 2019-2020 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name PX4 nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
/**
* @file PreFlightCheck.cpp
*/
#include "PreFlightCheck.hpp"
#include <drivers/drv_hrt.h>
#include <HealthFlags.h>
#include <lib/parameters/param.h>
#include <systemlib/mavlink_log.h>
#include <uORB/Subscription.hpp>
using namespace time_literals;
static constexpr unsigned max_mandatory_gyro_count = 1;
static constexpr unsigned max_optional_gyro_count = 4;
static constexpr unsigned max_mandatory_accel_count = 1;
static constexpr unsigned max_optional_accel_count = 4;
static constexpr unsigned max_mandatory_mag_count = 1;
static constexpr unsigned max_optional_mag_count = 4;
static constexpr unsigned max_mandatory_baro_count = 1;
static constexpr unsigned max_optional_baro_count = 4;
bool PreFlightCheck::preflightCheck(orb_advert_t *mavlink_log_pub, vehicle_status_s &status,
vehicle_status_flags_s &status_flags, bool report_failures, const bool prearm,
const hrt_abstime &time_since_boot)
{
report_failures = (report_failures && status_flags.condition_system_hotplug_timeout
&& !status_flags.condition_calibration_enabled);
bool failed = false;
failed = failed || !airframeCheck(mavlink_log_pub, status);
failed = failed || !sdcardCheck(mavlink_log_pub, status_flags.sd_card_detected_once, report_failures);
/* ---- MAG ---- */
{
int32_t sys_has_mag = 1;
param_get(param_find("SYS_HAS_MAG"), &sys_has_mag);
if (sys_has_mag == 1) {
/* check all sensors individually, but fail only for mandatory ones */
for (unsigned i = 0; i < max_optional_mag_count; i++) {
const bool required = (i < max_mandatory_mag_count) && (sys_has_mag == 1);
bool report_fail = report_failures;
int32_t device_id = -1;
if (!magnetometerCheck(mavlink_log_pub, status, i, !required, device_id, report_fail)) {
if (required) {
failed = true;
}
report_fail = false; // only report the first failure
}
}
// TODO: highest priority mag
/* mag consistency checks (need to be performed after the individual checks) */
if (!magConsistencyCheck(mavlink_log_pub, status, report_failures)) {
failed = true;
}
}
}
/* ---- ACCEL ---- */
{
/* check all sensors individually, but fail only for mandatory ones */
for (unsigned i = 0; i < max_optional_accel_count; i++) {
const bool required = (i < max_mandatory_accel_count);
bool report_fail = report_failures;
int32_t device_id = -1;
if (!accelerometerCheck(mavlink_log_pub, status, i, !required, device_id, report_fail)) {
if (required) {
failed = true;
}
report_fail = false; // only report the first failure
}
}
// TODO: highest priority (from params)
}
/* ---- GYRO ---- */
{
/* check all sensors individually, but fail only for mandatory ones */
for (unsigned i = 0; i < max_optional_gyro_count; i++) {
const bool required = (i < max_mandatory_gyro_count);
bool report_fail = report_failures;
int32_t device_id = -1;
if (!gyroCheck(mavlink_log_pub, status, i, !required, device_id, report_fail)) {
if (required) {
failed = true;
}
report_fail = false; // only report the first failure
}
}
// TODO: highest priority (from params)
}
/* ---- BARO ---- */
{
int32_t sys_has_baro = 1;
param_get(param_find("SYS_HAS_BARO"), &sys_has_baro);
bool baro_fail_reported = false;
/* check all sensors, but fail only for mandatory ones */
for (unsigned i = 0; i < max_optional_baro_count; i++) {
const bool required = (i < max_mandatory_baro_count) && (sys_has_baro == 1);
bool report_fail = (required && report_failures && !baro_fail_reported);
int32_t device_id = -1;
if (!baroCheck(mavlink_log_pub, status, i, !required, device_id, report_fail)) {
if (required) {
baro_fail_reported = true;
}
report_fail = false; // only report the first failure
}
}
}
/* ---- IMU CONSISTENCY ---- */
// To be performed after the individual sensor checks have completed
{
if (!imuConsistencyCheck(mavlink_log_pub, status, report_failures)) {
failed = true;
}
}
/* ---- AIRSPEED ---- */
/* Perform airspeed check only if circuit breaker is not engaged and it's not a rotary wing */
if (!status_flags.circuit_breaker_engaged_airspd_check &&
(status.vehicle_type == vehicle_status_s::VEHICLE_TYPE_FIXED_WING || status.is_vtol)) {
int32_t airspeed_mode = 0;
param_get(param_find("FW_ARSP_MODE"), &airspeed_mode);
const bool optional = (airspeed_mode == 1);
int32_t max_airspeed_check_en = 0;
param_get(param_find("COM_ARM_ARSP_EN"), &max_airspeed_check_en);
float airspeed_trim = 10.0f;
param_get(param_find("FW_AIRSPD_TRIM"), &airspeed_trim);
const float arming_max_airspeed_allowed = airspeed_trim / 2.0f; // set to half of trim airspeed
if (!airspeedCheck(mavlink_log_pub, status, optional, report_failures, prearm, (bool)max_airspeed_check_en,
arming_max_airspeed_allowed)
&& !(bool)optional) {
failed = true;
}
}
/* ---- RC CALIBRATION ---- */
if (status.rc_input_mode == vehicle_status_s::RC_IN_MODE_DEFAULT) {
if (rcCalibrationCheck(mavlink_log_pub, report_failures, status.is_vtol) != OK) {
if (report_failures) {
mavlink_log_critical(mavlink_log_pub, "RC calibration check failed");
}
failed = true;
set_health_flags(subsystem_info_s::SUBSYSTEM_TYPE_RCRECEIVER, status_flags.rc_signal_found_once, true, false, status);
status_flags.rc_calibration_valid = false;
} else {
// The calibration is fine, but only set the overall health state to true if the signal is not currently lost
status_flags.rc_calibration_valid = true;
set_health_flags(subsystem_info_s::SUBSYSTEM_TYPE_RCRECEIVER, status_flags.rc_signal_found_once, true,
!status.rc_signal_lost, status);
}
}
/* ---- SYSTEM POWER ---- */
if (status_flags.condition_power_input_valid && !status_flags.circuit_breaker_engaged_power_check) {
if (!powerCheck(mavlink_log_pub, status, report_failures, prearm)) {
failed = true;
}
}
/* ---- Navigation EKF ---- */
// only check EKF2 data if EKF2 is selected as the estimator and GNSS checking is enabled
int32_t estimator_type = -1;
if (status.vehicle_type == vehicle_status_s::VEHICLE_TYPE_ROTARY_WING && !status.is_vtol) {
param_get(param_find("SYS_MC_EST_GROUP"), &estimator_type);
} else {
// EKF2 is currently the only supported option for FW & VTOL
estimator_type = 2;
}
if (estimator_type == 2) {
const bool ekf_healthy = ekf2Check(mavlink_log_pub, status, false, report_failures) &&
ekf2CheckSensorBias(mavlink_log_pub, report_failures);
// For the first 10 seconds the ekf2 can be unhealthy, and we just mark it
// as not present.
// After that or if report_failures is true, we'll set the flags as is.
if (!ekf_healthy && time_since_boot < 10_s && !report_failures) {
set_health_flags(subsystem_info_s::SUBSYSTEM_TYPE_AHRS, true, false, false, status);
} else {
set_health_flags(subsystem_info_s::SUBSYSTEM_TYPE_AHRS, true, true, ekf_healthy, status);
}
failed |= !ekf_healthy;
}
/* ---- Failure Detector ---- */
if (!failureDetectorCheck(mavlink_log_pub, status, report_failures, prearm)) {
failed = true;
}
failed = failed || !manualControlCheck(mavlink_log_pub, report_failures);
failed = failed || !cpuResourceCheck(mavlink_log_pub, report_failures);
/* Report status */
return !failed;
}

@ -1,124 +0,0 @@
/****************************************************************************
*
* Copyright (c) 2019 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name PX4 nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
/**
* @file PreFlightCheck.hpp
*
* Check if flight is safely possible
* if not prevent it and inform the user.
*/
#pragma once
#include <uORB/topics/safety.h>
#include <uORB/topics/vehicle_status_flags.h>
#include <uORB/topics/vehicle_status.h>
#include <drivers/drv_hrt.h>
class PreFlightCheck
{
public:
PreFlightCheck() = default;
~PreFlightCheck() = default;
/**
* Runs a preflight check on all sensors to see if they are properly calibrated and healthy
*
* The function won't fail the test if optional sensors are not found, however,
* it will fail the test if optional sensors are found but not in working condition.
*
* @param mavlink_log_pub
* Mavlink output orb handle reference for feedback when a sensor fails
* @param checkMag
* true if the magneteometer should be checked
* @param checkAcc
* true if the accelerometers should be checked
* @param checkGyro
* true if the gyroscopes should be checked
* @param checkBaro
* true if the barometer should be checked
* @param checkAirspeed
* true if the airspeed sensor should be checked
* @param checkRC
* true if the Remote Controller should be checked
* @param checkGNSS
* true if the GNSS receiver should be checked
* @param checkPower
* true if the system power should be checked
**/
static bool preflightCheck(orb_advert_t *mavlink_log_pub, vehicle_status_s &status,
vehicle_status_flags_s &status_flags, bool reportFailures, const bool prearm,
const hrt_abstime &time_since_boot);
struct arm_requirements_t {
bool arm_authorization = false;
bool esc_check = false;
bool global_position = false;
bool mission = false;
bool geofence = false;
};
static bool preArmCheck(orb_advert_t *mavlink_log_pub, const vehicle_status_flags_s &status_flags,
const safety_s &safety, const arm_requirements_t &arm_requirements, vehicle_status_s &status,
bool report_fail = true);
private:
static bool magnetometerCheck(orb_advert_t *mavlink_log_pub, vehicle_status_s &status, const uint8_t instance,
const bool optional, int32_t &device_id, const bool report_fail);
static bool magConsistencyCheck(orb_advert_t *mavlink_log_pub, vehicle_status_s &status, const bool report_status);
static bool accelerometerCheck(orb_advert_t *mavlink_log_pub, vehicle_status_s &status, const uint8_t instance,
const bool optional, int32_t &device_id, const bool report_fail);
static bool gyroCheck(orb_advert_t *mavlink_log_pub, vehicle_status_s &status, const uint8_t instance,
const bool optional, int32_t &device_id, const bool report_fail);
static bool baroCheck(orb_advert_t *mavlink_log_pub, vehicle_status_s &status, const uint8_t instance,
const bool optional, int32_t &device_id, const bool report_fail);
static bool imuConsistencyCheck(orb_advert_t *mavlink_log_pub, vehicle_status_s &status, const bool report_status);
static bool airspeedCheck(orb_advert_t *mavlink_log_pub, vehicle_status_s &status, const bool optional,
const bool report_fail, const bool prearm, const bool max_airspeed_check_en, const float arming_max_airspeed_allowed);
static int rcCalibrationCheck(orb_advert_t *mavlink_log_pub, bool report_fail, bool isVTOL);
static bool powerCheck(orb_advert_t *mavlink_log_pub, const vehicle_status_s &status, const bool report_fail,
const bool prearm);
static bool ekf2Check(orb_advert_t *mavlink_log_pub, vehicle_status_s &vehicle_status, const bool optional,
const bool report_fail);
static bool ekf2CheckSensorBias(orb_advert_t *mavlink_log_pub, const bool report_fail);
static bool failureDetectorCheck(orb_advert_t *mavlink_log_pub, const vehicle_status_s &status, const bool report_fail,
const bool prearm);
static bool manualControlCheck(orb_advert_t *mavlink_log_pub, const bool report_fail);
static bool airframeCheck(orb_advert_t *mavlink_log_pub, const vehicle_status_s &status);
static bool cpuResourceCheck(orb_advert_t *mavlink_log_pub, const bool report_fail);
static bool sdcardCheck(orb_advert_t *mavlink_log_pub, bool &sd_card_detected_once, const bool report_fail);
};

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

Loading…
Cancel
Save