diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 29ab9a5..21cc7f3 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,22 +1,19 @@ { - "name": "Edge Proxy Dev", - "dockerComposeFile": [ - "../docker-compose.yml" - ], - "service": "edge-proxy-dev", - "workspaceFolder": "/app", - - "remoteUser": "dev", - "updateRemoteUserUID": true, - - "postCreateCommand": "sudo chgrp -R developers /app && sudo chmod -R g+w,g+s /app", - - "customizations": { - "vscode": { - "extensions": [ - "ms-vscode.cpptools", - "ms-vscode.cmake-tools" - ] - } - } + "name": "vehicle road counter dev", + "dockerComposeFile": [ + "../docker-compose.yml" + ], + "service": "vehicle-road-counter-dev", + "workspaceFolder": "/app", + "remoteUser": "dev", + "updateRemoteUserUID": true, + "postCreateCommand": "sudo chgrp -R developers /app && sudo chmod -R g+w,g+s /app", + "customizations": { + "vscode": { + "extensions": [ + "ms-vscode.cpptools", + "ms-vscode.cmake-tools" + ] + } + } } \ No newline at end of file diff --git a/.drone.yml b/.drone.yml index 8838282..5c1d083 100644 --- a/.drone.yml +++ b/.drone.yml @@ -3,74 +3,29 @@ type: docker name: build-and-push-arm64 platform: - os: linux - arch: arm64 + os: linux + arch: arm64 trigger: - event: - - tag + event: + - tag steps: - - name: build-and-push-to-gitea - image: 192.168.0.75:3000/guanyuankai/plugins-docker:latest - privileged: true - settings: - registry: 192.168.0.75:3000 - username: - from_secret: gitea_username - password: - from_secret: gitea_password - repo: 192.168.0.75:3000/guanyuankai/Vehicle_Road_Counter - insecure: true - mirror: "https://docker.m.daocloud.io" - tags: - - latest - - "${DRONE_TAG}" - dockerfile: docker/Dockerfile.prod - context: . - platforms: linux/arm64 - - # - name: scp-config-to-edge - # image: appleboy/drone-scp - # settings: - # host: - # from_secret: edge_host_ip - # username: - # from_secret: edge_user - # password: - # from_secret: edge_password - # port: 22 - # target: /opt/edge-proxy - # source: - # - docker-compose.prod.yml - # - mediamtx.yml - # - mosquitto/config/mosquitto.conf - - # - name: deploy-to-edge - # image: appleboy/drone-ssh - # settings: - # host: - # from_secret: edge_host_ip - # username: - # from_secret: edge_user - # password: - # from_secret: edge_password - # port: 22 - - # ignore_stderr: true - # script: - - # - mkdir -p /opt/edge-proxy - # - cd /opt/edge-proxy - # - echo "$REGISTRY_PASSWORD" | docker login 192.168.0.75:3000 -u "$REGISTRY_USER" --password-stdin - - # - docker compose -f docker-compose.prod.yml pull - - # - docker compose -f docker-compose.prod.yml up -d --remove-orphans - - # - docker image prune -f - # environment: - # REGISTRY_USER: - # from_secret: gitea_username - # REGISTRY_PASSWORD: - # from_secret: gitea_password + - name: build-and-push-to-gitea + image: 192.168.0.75:3000/guanyuankai/plugins-docker:latest + privileged: true + settings: + registry: 192.168.0.75:3000 + username: + from_secret: gitea_username + password: + from_secret: gitea_password + repo: 192.168.0.75:3000/guanyuankai/vehicle_road_counter + insecure: true + mirror: "https://docker.m.daocloud.io" + tags: + - latest + - "${DRONE_TAG}" + dockerfile: docker/Dockerfile.prod + context: . + platforms: linux/arm64 diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c5a26dd --- /dev/null +++ b/.editorconfig @@ -0,0 +1,50 @@ +root = true + +# --- 全局设置 --- +[*] +charset = utf-8 +indent_style = tab +indent_size = 4 +tab_width = 4 +insert_final_newline = true +trim_trailing_whitespace = true +max_line_length = 100 + +# --- C# 专属设置 (关键) --- +[*.cs] +# 1. 缩进:强制 Tab +indent_style = tab + +# 2. 大括号风格:K&B (Open brace on same line) +# 这是一个“叛逆”的设置,因为 C# 默认是换行的 +csharp_new_line_before_open_brace = none +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true + +# 3. 命名规范检查 (适配您的 UserDetail / GetUser / userName) +# 说明:EditorConfig 可以配置 Roslyn 分析器来检查命名 + +# 类 (Class) -> PascalCase (UserDetail) +dotnet_naming_rule.classes_must_be_pascal_case.severity = warning +dotnet_naming_rule.classes_must_be_pascal_case.symbols = classes +dotnet_naming_rule.classes_must_be_pascal_case.style = pascal_case + +dotnet_naming_symbols.classes.applicable_kinds = class, struct, enum, interface +dotnet_naming_style.pascal_case.capitalization = pascal_case + +# 方法 (Method) -> PascalCase (GetUser) +dotnet_naming_rule.methods_must_be_pascal_case.severity = warning +dotnet_naming_rule.methods_must_be_pascal_case.symbols = methods +dotnet_naming_rule.methods_must_be_pascal_case.style = pascal_case + +dotnet_naming_symbols.methods.applicable_kinds = method + +# 局部变量/参数 (Function Name/Local) -> camelCase (userName) +dotnet_naming_rule.locals_must_be_camel_case.severity = warning +dotnet_naming_rule.locals_must_be_camel_case.symbols = locals +dotnet_naming_rule.locals_must_be_camel_case.style = camel_case + +dotnet_naming_symbols.locals.applicable_kinds = local, parameter + +dotnet_naming_style.camel_case.capitalization = camel_case diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..6a128b9 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,24 @@ +{ + "recommendations": [ + // --- Python 核心与工具 --- + "ms-python.python", + "ms-python.pylint", // 替换 Flake8,用于静态检查 (命名规范、错误) + "ms-python.autopep8", // 替换 Black,用于代码格式化 (支持 Tab 缩进) + // --- C++ 开发 --- + "ms-vscode.cpptools", + "ms-vscode.cpptools-extension-pack", + // --- Java 开发 --- + "vscjava.vscode-java-pack", + "shengchen.vscode-checkstyle", // 配合 google_checks.xml 修改版可强制 Java 规范 + // --- 前端/通用 (JS/TS) --- + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", // 注意:需配置 .prettierrc 以支持 Tab + // --- 通用工具 --- + "editorconfig.editorconfig", // 核心:统一控制所有语言的 Tab/缩进 + "eamodio.gitlens", + "mhutchie.git-graph" + ], + "unwantedRecommendations": [ + "ms-python.black-formatter" // 明确列入黑名单,防止误装 + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 33110c2..e5a4498 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,82 +1,142 @@ { - "files.associations": { - "cctype": "cpp", - "clocale": "cpp", - "cmath": "cpp", - "csignal": "cpp", - "cstdarg": "cpp", - "cstddef": "cpp", - "cstdio": "cpp", - "cstdlib": "cpp", - "cstring": "cpp", - "ctime": "cpp", - "cwchar": "cpp", - "cwctype": "cpp", - "any": "cpp", - "array": "cpp", - "atomic": "cpp", - "bit": "cpp", - "bitset": "cpp", - "chrono": "cpp", - "codecvt": "cpp", - "compare": "cpp", - "complex": "cpp", - "concepts": "cpp", - "condition_variable": "cpp", - "coroutine": "cpp", - "cstdint": "cpp", - "deque": "cpp", - "list": "cpp", - "map": "cpp", - "set": "cpp", - "string": "cpp", - "unordered_map": "cpp", - "vector": "cpp", - "exception": "cpp", - "algorithm": "cpp", - "functional": "cpp", - "iterator": "cpp", - "memory": "cpp", - "memory_resource": "cpp", - "numeric": "cpp", - "optional": "cpp", - "random": "cpp", - "ratio": "cpp", - "regex": "cpp", - "source_location": "cpp", - "string_view": "cpp", - "system_error": "cpp", - "tuple": "cpp", - "type_traits": "cpp", - "utility": "cpp", - "fstream": "cpp", - "future": "cpp", - "initializer_list": "cpp", - "iomanip": "cpp", - "iosfwd": "cpp", - "iostream": "cpp", - "istream": "cpp", - "limits": "cpp", - "mutex": "cpp", - "new": "cpp", - "numbers": "cpp", - "ostream": "cpp", - "semaphore": "cpp", - "sstream": "cpp", - "stdexcept": "cpp", - "stop_token": "cpp", - "streambuf": "cpp", - "thread": "cpp", - "cinttypes": "cpp", - "typeindex": "cpp", - "typeinfo": "cpp", - "variant": "cpp", - "forward_list": "cpp", - "ranges": "cpp", - "valarray": "cpp", - "charconv": "cpp", - "unordered_set": "cpp", - "shared_mutex": "cpp", - "*.bak": "cpp" - } -} \ No newline at end of file + "files.associations": { + "cctype": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "csignal": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "any": "cpp", + "array": "cpp", + "atomic": "cpp", + "bit": "cpp", + "bitset": "cpp", + "chrono": "cpp", + "codecvt": "cpp", + "compare": "cpp", + "complex": "cpp", + "concepts": "cpp", + "condition_variable": "cpp", + "coroutine": "cpp", + "cstdint": "cpp", + "deque": "cpp", + "list": "cpp", + "map": "cpp", + "set": "cpp", + "string": "cpp", + "unordered_map": "cpp", + "vector": "cpp", + "exception": "cpp", + "algorithm": "cpp", + "functional": "cpp", + "iterator": "cpp", + "memory": "cpp", + "memory_resource": "cpp", + "numeric": "cpp", + "optional": "cpp", + "random": "cpp", + "ratio": "cpp", + "regex": "cpp", + "source_location": "cpp", + "string_view": "cpp", + "system_error": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "utility": "cpp", + "fstream": "cpp", + "future": "cpp", + "initializer_list": "cpp", + "iomanip": "cpp", + "iosfwd": "cpp", + "iostream": "cpp", + "istream": "cpp", + "limits": "cpp", + "mutex": "cpp", + "new": "cpp", + "numbers": "cpp", + "ostream": "cpp", + "semaphore": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "stop_token": "cpp", + "streambuf": "cpp", + "thread": "cpp", + "cinttypes": "cpp", + "typeindex": "cpp", + "typeinfo": "cpp", + "variant": "cpp", + "forward_list": "cpp", + "ranges": "cpp", + "valarray": "cpp", + "charconv": "cpp", + "unordered_set": "cpp", + "shared_mutex": "cpp", + "*.bak": "cpp", + }, + // --- 全局核心风格配置 --- + // 强制使用 Tab,严禁空格 + "editor.insertSpaces": false, + "editor.tabSize": 4, + "editor.detectIndentation": false, // 防止打开非标文件时被带偏 + // 行宽 100 字符辅助线 + "editor.rulers": [], + "editor.wordWrapColumn": 100, + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.organizeImports": "explicit" + }, + // --- Python 配置 (关键修改) --- + "[python]": { + // 替换 Black 为 Autopep8 (因为 Black 不支持 Tab) + "editor.defaultFormatter": "ms-python.autopep8", + "editor.insertSpaces": false, + "editor.tabSize": 4 + }, + // Autopep8 参数:强制 100 字符行宽,允许 Tab + "autopep8.args": [ + "--max-line-length", + "100", + "--ignore", + "E121" // 忽略部分缩进对齐警告 + ], + // Pylint 参数:指定配置文件路径 + "pylint.args": [ + "--rcfile=${workspaceFolder}/.pylintrc" + ], + // Python 环境管理 (保留您原有的设置) + "python-envs.defaultEnvManager": "ms-python.python:system", + "python-envs.defaultPackageManager": "ms-python.python:pip", + "python-envs.pythonProjects": [], + // --- C++ 配置 --- + // 读取根目录的 .clang-format 文件 (Tab, K&B, BigCamel 等规则由此文件控制) + "C_Cpp.clang_format_style": "file", + // --- JavaScript / TypeScript 配置 --- + "[javascript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.insertSpaces": false, // 覆盖 Prettier 默认的 Space + "editor.tabSize": 4, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit" + } + }, + "[typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.insertSpaces": false, // 覆盖 Prettier 默认的 Space + "editor.tabSize": 4, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit" + } + }, + // 注意:Prettier 通常优先读取 .prettierrc 文件。 + // 如果您有 .prettierrc,请务必在其中设置 "useTabs": true, "tabWidth": 4 + // --- JSON 配置 --- + "[json]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + } +} diff --git a/CMakeLists.txt b/CMakeLists.txt index 021a701..c184950 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,14 +17,14 @@ find_package(OpenCV REQUIRED) add_subdirectory(src/vendor/crow) add_library(nlohmann_json INTERFACE) -target_include_directories(nlohmann_json INTERFACE - ${CMAKE_CURRENT_SOURCE_DIR}/src/vendor +target_include_directories(nlohmann_json INTERFACE + ${CMAKE_CURRENT_SOURCE_DIR}/src/vendor ${CMAKE_CURRENT_SOURCE_DIR}/../libs # 如果你的 libs 目录有项目自定义头文件 ) -add_library(edge_proxy_lib STATIC +add_library(vehicle_road_lib STATIC # TCP通讯层 src/network/tcp_server.cc # --- 设备管理模块 --- @@ -34,8 +34,8 @@ add_library(edge_proxy_lib STATIC # --- MQTT 核心模块 --- src/mqtt/mqtt_client.cpp src/mqtt/mqtt_router.cpp - src/mqtt/handler/data_handler.cpp - src/mqtt/handler/command_handler.cpp + src/mqtt/handler/data_handler.cpp + src/mqtt/handler/command_handler.cpp # --- 协议层 --- src/protocol/protocol.cc src/protocol/private_protocol_adapter.cc @@ -43,7 +43,7 @@ add_library(edge_proxy_lib STATIC # 系统监视 src/systemMonitor/system_monitor.cc #modbus - src/modbus/modbus_rtu_client.cc + src/modbus/modbus_rtu_client.cc src/modbus/modbus_rtu_bus_service.cc src/modbus/generic_modbus_parser.cc # --- Modbus Master --- @@ -56,63 +56,52 @@ add_library(edge_proxy_lib STATIC src/web/web_server.cc #SQL src/dataStorage/data_storage.cc - #tts - src/tts/piper_tts_interface.cc #config配置 src/config/config_manager.cc #告警模块 src/alarm/alarm_service.cc - # 推理模块 - src/rknn/video_service.cc - src/rknn/rkYolov5s.cc - src/rknn/rkYolov8.cc - src/rknn/preprocess.cc - src/rknn/postprocess.cc - src/videoServiceManager/video_service_manager.cc - src/algorithm/IntrusionModule.cc - src/algorithm/HumanDetectionModule.cc ) -target_include_directories(edge_proxy_lib PUBLIC - /usr/include/opencv4 +target_include_directories(vehicle_road_lib PUBLIC + /usr/include/opencv4 ${CMAKE_CURRENT_SOURCE_DIR}/src ${GST_INCLUDE_DIRS} - /usr/include/rga + /usr/include/rga ) -target_link_libraries(edge_proxy_lib PRIVATE +target_link_libraries(vehicle_road_lib PRIVATE spdlog::spdlog Boost::system Boost::thread PahoMqttCpp::paho-mqttpp3 SQLite::SQLite3 pthread - # rknn_api - rknnrt + # rknn_api + rknnrt rockchip_mpp rga ${OpenCV_LIBS} ${GST_LIBRARIES} - + ) -target_link_libraries(edge_proxy_lib PUBLIC +target_link_libraries(vehicle_road_lib PUBLIC Crow nlohmann_json ) -# ================================== -# Main Application Target # ================================== -add_executable(edge_proxy +# Main Application Target +# ================================== +add_executable(vehicle_road_counter src/main.cpp ) -target_link_libraries(edge_proxy PRIVATE - edge_proxy_lib +target_link_libraries(vehicle_road_counter PRIVATE + vehicle_road_lib ) # ================================================================= -# 测试目标 +# 测试目标 # ================================================================= # add_executable(test # src/test.cc @@ -147,4 +136,4 @@ target_link_libraries(edge_proxy PRIVATE # ${CMAKE_CURRENT_SOURCE_DIR}/src/streamer/include # ${GST_INCLUDE_DIRS} -# ) \ No newline at end of file +# ) diff --git a/ChangeLog.md b/ChangeLog.md new file mode 100644 index 0000000..5d21af7 --- /dev/null +++ b/ChangeLog.md @@ -0,0 +1,24 @@ +# Changelog + +本项目的所有显著更改都将记录在此文件中。 + +## [Unreleased] + +### Added + +- 新增了 Docker 部署脚本 (MR-102)。 +- 支持了 GPT-4o 模型接口。 + +## [1.0.1] - 2025-11-04 + +### Fixed + +- 修复了用户登录时 Token 过期导致的 500 错误 (#45)。 +- 修复了移动端 CSS 样式错乱问题。 + +## [1.0.0] - 2025-10-01 + +### Added + +- 初始化项目,完成基础架构搭建。 +- 实现标书生成的 MVP 版本。 diff --git a/README.md b/README.md new file mode 100644 index 0000000..2a3d31a --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +基于 CV的车辆道路计数系统 \ No newline at end of file diff --git a/README_DEPLOY.md b/README_DEPLOY.md deleted file mode 100644 index 72def70..0000000 --- a/README_DEPLOY.md +++ /dev/null @@ -1,61 +0,0 @@ -部署操作手册 (Deployment Guide) - -1. 准备工作 (Setup) - -1.1 文件位置 - -确保以下文件在 Git 仓库的根目录: - -.drone.yml (流水线定义) - -docker-compose.prod.yml (生产环境运行配置) - -mediamtx.yml (如果需要) - -1.2 Drone 配置 (Secrets) - -登录 Drone 网页版 -> Settings -> Secrets,添加以下变量: - -gitea_username: Gitea 用户名 - -gitea_password: Gitea 密码 - -edge_host_ip: RK3588 的 IP 地址 (如 192.168.0.123) - -edge_user: RK3588 用户名 (如 dev) - -edge_password: RK3588 密码 - -1.3 RK3588 权限准备 - -在 RK3588 上执行一次: - -sudo mkdir -p /opt/edge-proxy -sudo chown -R forlinx:forlinx /opt/edge-proxy - - -2. 如何触发部署 (How to Deploy) - -因为我们在 .drone.yml 里设置了 trigger: event: [ tag ],所以普通的 git push 不会 触发部署,只会触发编译(如果有另外的 push 事件配置的话)。 - -要发布新版本到设备上,请执行: - -# 1. 提交修改 -git add . -git commit -m "update config" -git push - -# 2. 打标签 (Tag) - 这就是开关! -git tag v1.0.0 -git push origin v1.0.0 - - -Drone 检测到 v1.0.0 这个标签被推送,就会开始干活: - -编译 Docker 镜像。 - -登录 RK3588。 - -上传最新的 docker-compose.prod.yml。 - -拉取镜像并重启容器。 \ No newline at end of file diff --git a/config/config.json b/config/config.json index 72548e2..fb6188c 100644 --- a/config/config.json +++ b/config/config.json @@ -6,7 +6,7 @@ "device_id": "rk3588-proxy-002", "log_level": "info", "mqtt_broker": "tcp://localhost:1883", - "mqtt_client_id_prefix": "edge-proxy-", + "mqtt_client_id_prefix": "vehicle-road-counter-", "piper_executable_path": "/usr/bin/piper", "piper_model_path": "/app/models/model.onnx", "tcp_server_ports": [ diff --git a/config/video_config.json b/config/video_config.json deleted file mode 100644 index 8e12e59..0000000 --- a/config/video_config.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "video_service": { - "enabled": true - }, - "video_streams": [ - { - "enabled": false, - "id": "cam_01_intrusion", - "input_url": "rtsp://admin:hzx12345@192.168.1.10:554/Streaming/Channels/1301", - "module_config": { - "class_num": 80, - "intrusion_zone": [ - 100, - 100, - 1820, - 1820 - ], - "label_path": "/app/models/coco_80_labels_list.txt", - "model_path": "/app/models/RK3588/yolov5s-640-640.rknn", - "rknn_thread_num": 3, - "time_threshold_sec": 3 - }, - "module_type": "intrusion_detection", - "output_rtsp": "rtsp://127.0.0.1:8554/ch1301" - }, - { - "enabled": true, - "id": "cam_01_intrusion", - "input_url": "rtsp://admin:hzx12345@192.168.1.10:554/Streaming/Channels/1301", - "module_config": { - "class_num": 3, - "intrusion_zone": [ - 100, - 100, - 1820, - 1820 - ], - "label_path": "/app/models/human.txt", - "model_path": "/app/models/human_detection.rknn", - "rknn_thread_num": 3, - "time_threshold_sec": 3 - }, - "module_type": "human_detection", - "output_rtsp": "rtsp://127.0.0.1:8554/ch1301" - } - ] -} \ No newline at end of file diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 0ac5b18..e12992a 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -1,65 +1,64 @@ # docker-compose.yml (最终版 - 使用命名卷) services: - edge-proxy: - image: 192.168.0.75:3000/guanyuankai/vehicle-counter:latest - container_name: vehicle-counter-prod - restart: unless-stopped - platform: linux/arm64 - network_mode: "host" - privileged: true - devices: - - /dev/ttyS7:/dev/ttyS7 - - /dev/ttyS9:/dev/ttyS9 - - /dev/snd:/dev/snd - - /dev/mpp_service:/dev/mpp_service - - /dev/rga:/dev/rga - - /dev/dri:/dev/dri - - source: /sys/bus/iio/devices/iio:device0 - target: /sys/bus/iio/devices/iio:device0 - group_add: - - "20" - - "44" - - "110" - - "29" - volumes: - - prod_config_data:/app/config - - - /tmp/.X11-unix:/tmp/.X11-unix:rw - environment: - - DISPLAY=$DISPLAY - - MQTT_HOST=127.0.0.1 - dns: - - 8.8.8.8 - - 114.114.114.114 - depends_on: - mqtt-broker: - condition: service_started + vehicle-road-counter: + image: 192.168.0.75:3000/guanyuankai/vehicle_road_counter:latest + container_name: vehicle-road-counter-prod + restart: unless-stopped + platform: linux/arm64 + network_mode: "host" + privileged: true + devices: + - /dev/ttyS7:/dev/ttyS7 + - /dev/ttyS9:/dev/ttyS9 + - /dev/snd:/dev/snd + - /dev/mpp_service:/dev/mpp_service + - /dev/rga:/dev/rga + - /dev/dri:/dev/dri + - source: /sys/bus/iio/devices/iio:device0 + target: /sys/bus/iio/devices/iio:device0 + group_add: + - "20" + - "44" + - "110" + - "29" + volumes: + - prod_config_data:/app/config - media-gateway: - image: bluenviron/mediamtx:latest - container_name: media-gateway - restart: unless-stopped - network_mode: "host" - privileged: true - volumes: - - ./mediamtx.yml:/mediamtx.yml + - /tmp/.X11-unix:/tmp/.X11-unix:rw + environment: + - DISPLAY=$DISPLAY + - MQTT_HOST=127.0.0.1 + dns: + - 8.8.8.8 + - 114.114.114.114 + depends_on: + mqtt-broker: + condition: service_started - mqtt-broker: - image: eclipse-mosquitto:2.0 - container_name: mqtt-broker - restart: unless-stopped - ports: - - "1883:1883" - volumes: - - ./mosquitto/config:/mosquitto/config - - ./mosquitto/data:/mosquitto/data - - ./mosquitto/log:/mosquitto/log、 - healthcheck: - test: ["CMD-SHELL", "mosquitto_sub -t '$SYS/#' -C 1 | grep -v Error || exit 1"] - interval: 10s - timeout: 10s - retries: 3 + media-gateway: + image: bluenviron/mediamtx:latest + container_name: media-gateway + restart: unless-stopped + network_mode: "host" + privileged: true + volumes: + - ./mediamtx.yml:/mediamtx.yml + mqtt-broker: + image: eclipse-mosquitto:2.0 + container_name: mqtt-broker + restart: unless-stopped + ports: + - "1883:1883" + volumes: + - ./mosquitto/config:/mosquitto/config + - ./mosquitto/data:/mosquitto/data + - ./mosquitto/log:/mosquitto/log、 + healthcheck: + test: [ "CMD-SHELL", "mosquitto_sub -t '$SYS/#' -C 1 | grep -v Error || exit 1" ] + interval: 10s + timeout: 10s + retries: 3 volumes: - prod_config_data: \ No newline at end of file + prod_config_data: diff --git a/docker-compose.yml b/docker-compose.yml index f8b566a..4579c26 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,11 +1,11 @@ # docker-compose.yml services: - edge-proxy-dev: - build: + vehicle-road-counter-dev: + build: context: . dockerfile: docker/Dockerfile - container_name: edge-proxy-shared-dev - image: edge-proxy-edge-proxy-dev:latest + container_name: vehicle-road-counter-dev + image: vehicle-road-counter-dev:latest platform: linux/arm64 network_mode: "host" privileged: true @@ -17,12 +17,12 @@ services: # --- VPU/NPU/RGA/GPU 硬件访问 --- - /dev/mpp_service:/dev/mpp_service - /dev/rga:/dev/rga - - /dev/dri:/dev/dri + - /dev/dri:/dev/dri - source: /sys/bus/iio/devices/iio:device0 target: /sys/bus/iio/devices/iio:device0 group_add: - - "20" + - "20" - "44" - "110" - "29" @@ -30,7 +30,7 @@ services: - .:/app - /tmp/.X11-unix:/tmp/.X11-unix:rw environment: - - DISPLAY=$DISPLAY + - DISPLAY=$DISPLAY # ports: # - "8888:8888" # - "9999:9999" @@ -38,8 +38,8 @@ services: # - "8080:8080" dns: - - 8.8.8.8 - - 114.114.114.114 + - 8.8.8.8 + - 114.114.114.114 command: sleep infinity media-gateway: @@ -59,4 +59,4 @@ services: volumes: - ./mosquitto/config:/mosquitto/config - ./mosquitto/data:/mosquitto/data - - ./mosquitto/log:/mosquitto/log \ No newline at end of file + - ./mosquitto/log:/mosquitto/log diff --git a/docker/Dockerfile b/docker/Dockerfile index ba488e2..f338c61 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,76 +1,76 @@ FROM arm64v8/ubuntu:22.04 ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update && \ - apt-get install -y --no-install-recommends \ - # 基础工具 - sudo \ - build-essential \ - cmake \ - git \ - gdb \ - vim \ - unzip \ - pkg-config \ - # PPA 管理工具 - software-properties-common \ - gpg-agent \ - && \ - add-apt-repository -y ppa:jjriek/panfork-mesa && \ - add-apt-repository -y ppa:jjriek/rockchip && \ - add-apt-repository -y ppa:jjriek/rockchip-multimedia && \ - apt-get update && \ - apt-get install -y --no-install-recommends \ - espeak-ng-data \ - libespeak1 \ - python3 \ - python3-pip \ - libssl-dev \ - libspdlog-dev \ - libsqlite3-dev \ - libboost-all-dev \ - librockchip-mpp-dev \ - librga-dev \ - gstreamer1.0-rockchip \ - libgstreamer1.0-dev \ - libgstreamer-plugins-base1.0-dev \ - alsa-utils \ - libasound2-plugins \ - gstreamer1.0-alsa \ - gstreamer1.0-plugins-base \ - gstreamer1.0-plugins-good \ - gstreamer1.0-plugins-bad \ - gstreamer1.0-plugins-ugly \ - gstreamer1.0-libav \ - gstreamer1.0-tools \ - gstreamer1.0-x \ - gstreamer1.0-alsa \ - gstreamer1.0-pulseaudio \ - gstreamer1.0-rtsp \ - libopencv-dev \ - nmap \ - && \ - groupadd -r developers && \ - useradd -ms /bin/bash -g developers -G sudo dev && \ - groupadd -g 20 dialout || true && \ - groupadd -g 44 video || true && \ - groupadd -g 110 render || true && \ - groupadd -g 29 render || true && \ - usermod -a -G dialout dev && \ - usermod -a -G video dev && \ - usermod -a -G render dev && \ - usermod -a -G audio dev && \ - echo "dev ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/dev-nopasswd + apt-get install -y --no-install-recommends \ + # 基础工具 + sudo \ + build-essential \ + cmake \ + git \ + gdb \ + vim \ + unzip \ + pkg-config \ + # PPA 管理工具 + software-properties-common \ + gpg-agent \ + && \ + add-apt-repository -y ppa:jjriek/panfork-mesa && \ + add-apt-repository -y ppa:jjriek/rockchip && \ + add-apt-repository -y ppa:jjriek/rockchip-multimedia && \ + apt-get update && \ + apt-get install -y --no-install-recommends \ + espeak-ng-data \ + libespeak1 \ + python3 \ + python3-pip \ + libssl-dev \ + libspdlog-dev \ + libsqlite3-dev \ + libboost-all-dev \ + librockchip-mpp-dev \ + librga-dev \ + gstreamer1.0-rockchip \ + libgstreamer1.0-dev \ + libgstreamer-plugins-base1.0-dev \ + alsa-utils \ + libasound2-plugins \ + gstreamer1.0-alsa \ + gstreamer1.0-plugins-base \ + gstreamer1.0-plugins-good \ + gstreamer1.0-plugins-bad \ + gstreamer1.0-plugins-ugly \ + gstreamer1.0-libav \ + gstreamer1.0-tools \ + gstreamer1.0-x \ + gstreamer1.0-alsa \ + gstreamer1.0-pulseaudio \ + gstreamer1.0-rtsp \ + libopencv-dev \ + nmap \ + && \ + groupadd -r developers && \ + useradd -ms /bin/bash -g developers -G sudo dev && \ + groupadd -g 20 dialout || true && \ + groupadd -g 44 video || true && \ + groupadd -g 110 render || true && \ + groupadd -g 29 render || true && \ + usermod -a -G dialout dev && \ + usermod -a -G video dev && \ + usermod -a -G render dev && \ + usermod -a -G audio dev && \ + echo "dev ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/dev-nopasswd RUN echo "umask 0002" > /etc/profile.d/99-shared-umask.sh && \ - chmod +x /etc/profile.d/99-shared-umask.sh + chmod +x /etc/profile.d/99-shared-umask.sh COPY . /tmp/build-context RUN cd /tmp/build-context/external/paho.mqtt.c && \ - cmake -Bbuild -H. -DPAHO_WITH_SSL=ON -DPAHO_BUILD_SAMPLES=OFF -DPAHO_BUILD_DOCUMENTATION=OFF && \ - cmake --build build --target install && \ - cd /tmp/build-context/external/paho.mqtt.cpp && \ - cmake -Bbuild -H. -DPAHO_WITH_SSL=ON -DPAHO_BUILD_SAMPLES=OFF -DPAHO_BUILD_DOCUMENTATION=OFF && \ - cmake --build build --target install && \ - rm -rf /tmp/build-context + cmake -Bbuild -H. -DPAHO_WITH_SSL=ON -DPAHO_BUILD_SAMPLES=OFF -DPAHO_BUILD_DOCUMENTATION=OFF && \ + cmake --build build --target install && \ + cd /tmp/build-context/external/paho.mqtt.cpp && \ + cmake -Bbuild -H. -DPAHO_WITH_SSL=ON -DPAHO_BUILD_SAMPLES=OFF -DPAHO_BUILD_DOCUMENTATION=OFF && \ + cmake --build build --target install && \ + rm -rf /tmp/build-context COPY rknn_sdk/librknn_api/include/rknn_api.h /usr/local/include/ COPY rknn_sdk/rknn_server/aarch64/usr/bin/rknn_server /usr/bin/rknn_server @@ -78,19 +78,17 @@ COPY rknn_sdk/librknn_api/aarch64/librknnrt.so /usr/lib/librknnrt.so COPY rknn_sdk/librknn_api/aarch64/librknn_api.so /usr/lib/librknn_api.so COPY rknn_sdk/whl/*.whl /tmp/rknn_wheels/ RUN pip3 install /tmp/rknn_wheels/*.whl && \ - rm -rf /tmp/rknn_wheels + rm -rf /tmp/rknn_wheels RUN chmod +x /usr/bin/rknn_server RUN ldconfig RUN rm -rf /var/lib/apt/lists/* -COPY piper_models/ /app/piper_models/ USER dev RUN pip install --no-cache-dir --user -i https://mirrors.aliyun.com/pypi/simple/ \ - piper-tts \ - onvif-zeep \ - python-nmap \ - psutil \ - paramiko + onvif-zeep \ + python-nmap \ + psutil \ + paramiko RUN echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bash_profile USER dev CMD ["/bin/bash"] \ No newline at end of file diff --git a/docker/Dockerfile.prod b/docker/Dockerfile.prod index 269c9e8..01a227a 100644 --- a/docker/Dockerfile.prod +++ b/docker/Dockerfile.prod @@ -7,19 +7,19 @@ FROM 192.168.0.75:3000/guanyuankai/ubuntu-arm64:22.04 AS build_env ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update && \ - apt-get install -y --no-install-recommends \ - sudo build-essential cmake git unzip pkg-config \ - software-properties-common gpg-agent \ - && \ - add-apt-repository -y ppa:jjriek/panfork-mesa && \ - add-apt-repository -y ppa:jjriek/rockchip && \ - add-apt-repository -y ppa:jjriek/rockchip-multimedia && \ - apt-get update && \ - apt-get install -y --no-install-recommends \ - libssl-dev libspdlog-dev libsqlite3-dev libboost-all-dev \ - librockchip-mpp-dev librga-dev libgstreamer1.0-dev \ - libgstreamer-plugins-base1.0-dev libasound2-dev libopencv-dev \ - && rm -rf /var/lib/apt/lists/* + apt-get install -y --no-install-recommends \ + sudo build-essential cmake git unzip pkg-config \ + software-properties-common gpg-agent \ + && \ + add-apt-repository -y ppa:jjriek/panfork-mesa && \ + add-apt-repository -y ppa:jjriek/rockchip && \ + add-apt-repository -y ppa:jjriek/rockchip-multimedia && \ + apt-get update && \ + apt-get install -y --no-install-recommends \ + libssl-dev libspdlog-dev libsqlite3-dev libboost-all-dev \ + librockchip-mpp-dev librga-dev libgstreamer1.0-dev \ + libgstreamer-plugins-base1.0-dev libasound2-dev libopencv-dev \ + && rm -rf /var/lib/apt/lists/* COPY . /app WORKDIR /app @@ -29,17 +29,17 @@ COPY rknn_sdk/librknn_api/include/rknn_api.h /usr/local/include/ COPY rknn_sdk/rknn_server/aarch64/usr/bin/rknn_server /usr/bin/rknn_server COPY rknn_sdk/librknn_api/aarch64/librknnrt.so /usr/lib/librknnrt.so COPY rknn_sdk/librknn_api/aarch64/librknn_api.so /usr/lib/librknn_api.so -RUN ldconfig +RUN ldconfig RUN cd /app/external/paho.mqtt.c && \ - cmake -Bbuild -H. -DPAHO_WITH_SSL=ON -DPAHO_BUILD_SAMPLES=OFF -DPAHO_BUILD_DOCUMENTATION=OFF && \ - cmake --build build --target install --parallel $(nproc) && \ - cd /app/external/paho.mqtt.cpp && \ - cmake -Bbuild -H. -DPAHO_WITH_SSL=ON -DPAHO_BUILD_SAMPLES=OFF -DPAHO_BUILD_DOCUMENTATION=OFF && \ - cmake --build build --target install --parallel $(nproc) + cmake -Bbuild -H. -DPAHO_WITH_SSL=ON -DPAHO_BUILD_SAMPLES=OFF -DPAHO_BUILD_DOCUMENTATION=OFF && \ + cmake --build build --target install --parallel $(nproc) && \ + cd /app/external/paho.mqtt.cpp && \ + cmake -Bbuild -H. -DPAHO_WITH_SSL=ON -DPAHO_BUILD_SAMPLES=OFF -DPAHO_BUILD_DOCUMENTATION=OFF && \ + cmake --build build --target install --parallel $(nproc) RUN cmake -S /app -B /app/build -RUN cmake --build /app/build --target edge_proxy --parallel $(nproc) +RUN cmake --build /app/build --target vehicle_road_counter --parallel $(nproc) RUN ls -l /app/build/ @@ -54,45 +54,45 @@ ENV DEBIAN_FRONTEND=noninteractive # 1. (作为 root) 安装系统依赖 # *** 修正:严格遵循原始 PPA 逻辑 *** RUN apt-get update && \ - apt-get install -y --no-install-recommends \ - unzip software-properties-common gpg-agent \ - python3 python3-pip \ - && \ - # *** 先添加 PPA *** - add-apt-repository -y ppa:jjriek/panfork-mesa && \ - add-apt-repository -y ppa:jjriek/rockchip && \ - add-apt-repository -y ppa:jjriek/rockchip-multimedia && \ - # *** 再次 Update,使 PPA 生效 *** - apt-get update && \ - # *** 现在才安装 PPA 中的包 *** - apt-get install -y --no-install-recommends \ - espeak-ng-data libespeak1 \ - libssl-dev libspdlog-dev libsqlite3-dev libboost-all-dev \ - librockchip-mpp-dev librga-dev gstreamer1.0-rockchip \ - libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \ - alsa-utils libasound2-plugins gstreamer1.0-alsa \ - gstreamer1.0-plugins-base gstreamer1.0-plugins-good \ - gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly \ - gstreamer1.0-libav gstreamer1.0-tools gstreamer1.0-x \ - gstreamer1.0-pulseaudio gstreamer1.0-rtsp \ - libopencv-dev nmap \ - && rm -rf /var/lib/apt/lists/* + apt-get install -y --no-install-recommends \ + unzip software-properties-common gpg-agent \ + python3 python3-pip \ + && \ + # *** 先添加 PPA *** + add-apt-repository -y ppa:jjriek/panfork-mesa && \ + add-apt-repository -y ppa:jjriek/rockchip && \ + add-apt-repository -y ppa:jjriek/rockchip-multimedia && \ + # *** 再次 Update,使 PPA 生效 *** + apt-get update && \ + # *** 现在才安装 PPA 中的包 *** + apt-get install -y --no-install-recommends \ + espeak-ng-data libespeak1 \ + libssl-dev libspdlog-dev libsqlite3-dev libboost-all-dev \ + librockchip-mpp-dev librga-dev gstreamer1.0-rockchip \ + libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \ + alsa-utils libasound2-plugins gstreamer1.0-alsa \ + gstreamer1.0-plugins-base gstreamer1.0-plugins-good \ + gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly \ + gstreamer1.0-libav gstreamer1.0-tools gstreamer1.0-x \ + gstreamer1.0-pulseaudio gstreamer1.0-rtsp \ + libopencv-dev nmap \ + && rm -rf /var/lib/apt/lists/* # 2. (作为 root) 创建用户和组 (已移除 sudo) RUN groupadd -r developers && \ - useradd -ms /bin/bash -g developers dev && \ - groupadd -g 20 dialout || true && \ - groupadd -g 44 video || true && \ - groupadd -g 110 render || true && \ - usermod -a -G dialout dev && \ - usermod -a -G video dev && \ - usermod -a -G render dev && \ - usermod -a -G audio dev + useradd -ms /bin/bash -g developers dev && \ + groupadd -g 20 dialout || true && \ + groupadd -g 44 video || true && \ + groupadd -g 110 render || true && \ + usermod -a -G dialout dev && \ + usermod -a -G video dev && \ + usermod -a -G render dev && \ + usermod -a -G audio dev # 3. (作为 root) 复制所有文件 COPY --from=build_env /usr/local/lib/ /usr/local/lib/ COPY --from=build_env /usr/local/include/ /usr/local/include/ -COPY --from=build_env /app/build/edge_proxy /app/edge_proxy +COPY --from=build_env /app/build/vehicle_road_counter /app/vehicle_road_counter WORKDIR /app COPY rknn_sdk/librknn_api/include/rknn_api.h /usr/local/include/ @@ -101,7 +101,7 @@ COPY rknn_sdk/librknn_api/aarch64/librknnrt.so /usr/lib/librknnrt.so COPY rknn_sdk/librknn_api/aarch64/librknn_api.so /usr/lib/librknn_api.so COPY rknn_sdk/whl/*.whl /tmp/rknn_wheels/ RUN pip3 install /tmp/rknn_wheels/*.whl && \ - rm -rf /tmp/rknn_wheels + rm -rf /tmp/rknn_wheels RUN chmod +x /usr/bin/rknn_server RUN ldconfig @@ -121,11 +121,11 @@ ENV PATH="/home/dev/.local/bin:${PATH}" # 7. (作为 dev) 安装 Python 包 RUN pip install --no-cache-dir --user -i https://mirrors.aliyun.com/pypi/simple/ \ - piper-tts \ - onvif-zeep \ - python-nmap \ - psutil \ - paramiko + piper-tts \ + onvif-zeep \ + python-nmap \ + psutil \ + paramiko # 8. (作为 dev) 设置默认命令 -CMD ["/app/edge_proxy"] \ No newline at end of file +CMD ["/app/vehicle_road_counter"] diff --git a/mosquitto/config/mosquitto.conf b/mosquitto/config/mosquitto.conf index ef37c09..a599378 100644 --- a/mosquitto/config/mosquitto.conf +++ b/mosquitto/config/mosquitto.conf @@ -48,7 +48,7 @@ address 192.168.0.54:1883 topic proxy/processed/# out 1 # # 如果您想从云端接收命令到本地,可以再加一条 "in" 规则,例如: -topic commands/my-edge-proxy-device-01/# in 1 +topic commands/my-vehicle-road-counter-device-01/# in 1 # # --- 4. 客户端身份和认证 --- # # 这个桥接在云端 Broker 看来,是一个普通的 MQTT 客户端 diff --git a/piper_models/zh_CN-huayan-medium.onnx b/piper_models/zh_CN-huayan-medium.onnx deleted file mode 100644 index fd1b1c7..0000000 Binary files a/piper_models/zh_CN-huayan-medium.onnx and /dev/null differ diff --git a/piper_models/zh_CN-huayan-medium.onnx.json b/piper_models/zh_CN-huayan-medium.onnx.json deleted file mode 100644 index f0e6e6e..0000000 --- a/piper_models/zh_CN-huayan-medium.onnx.json +++ /dev/null @@ -1,487 +0,0 @@ -{ - "audio": { - "sample_rate": 22050, - "quality": "medium" - }, - "espeak": { - "voice": "cmn" - }, - "inference": { - "noise_scale": 0.667, - "length_scale": 1, - "noise_w": 0.8 - }, - "phoneme_type": "espeak", - "phoneme_map": {}, - "phoneme_id_map": { - "_": [ - 0 - ], - "^": [ - 1 - ], - "$": [ - 2 - ], - " ": [ - 3 - ], - "!": [ - 4 - ], - "'": [ - 5 - ], - "(": [ - 6 - ], - ")": [ - 7 - ], - ",": [ - 8 - ], - "-": [ - 9 - ], - ".": [ - 10 - ], - ":": [ - 11 - ], - ";": [ - 12 - ], - "?": [ - 13 - ], - "a": [ - 14 - ], - "b": [ - 15 - ], - "c": [ - 16 - ], - "d": [ - 17 - ], - "e": [ - 18 - ], - "f": [ - 19 - ], - "h": [ - 20 - ], - "i": [ - 21 - ], - "j": [ - 22 - ], - "k": [ - 23 - ], - "l": [ - 24 - ], - "m": [ - 25 - ], - "n": [ - 26 - ], - "o": [ - 27 - ], - "p": [ - 28 - ], - "q": [ - 29 - ], - "r": [ - 30 - ], - "s": [ - 31 - ], - "t": [ - 32 - ], - "u": [ - 33 - ], - "v": [ - 34 - ], - "w": [ - 35 - ], - "x": [ - 36 - ], - "y": [ - 37 - ], - "z": [ - 38 - ], - "æ": [ - 39 - ], - "ç": [ - 40 - ], - "ð": [ - 41 - ], - "ø": [ - 42 - ], - "ħ": [ - 43 - ], - "ŋ": [ - 44 - ], - "œ": [ - 45 - ], - "ǀ": [ - 46 - ], - "ǁ": [ - 47 - ], - "ǂ": [ - 48 - ], - "ǃ": [ - 49 - ], - "ɐ": [ - 50 - ], - "ɑ": [ - 51 - ], - "ɒ": [ - 52 - ], - "ɓ": [ - 53 - ], - "ɔ": [ - 54 - ], - "ɕ": [ - 55 - ], - "ɖ": [ - 56 - ], - "ɗ": [ - 57 - ], - "ɘ": [ - 58 - ], - "ə": [ - 59 - ], - "ɚ": [ - 60 - ], - "ɛ": [ - 61 - ], - "ɜ": [ - 62 - ], - "ɞ": [ - 63 - ], - "ɟ": [ - 64 - ], - "ɠ": [ - 65 - ], - "ɡ": [ - 66 - ], - "ɢ": [ - 67 - ], - "ɣ": [ - 68 - ], - "ɤ": [ - 69 - ], - "ɥ": [ - 70 - ], - "ɦ": [ - 71 - ], - "ɧ": [ - 72 - ], - "ɨ": [ - 73 - ], - "ɪ": [ - 74 - ], - "ɫ": [ - 75 - ], - "ɬ": [ - 76 - ], - "ɭ": [ - 77 - ], - "ɮ": [ - 78 - ], - "ɯ": [ - 79 - ], - "ɰ": [ - 80 - ], - "ɱ": [ - 81 - ], - "ɲ": [ - 82 - ], - "ɳ": [ - 83 - ], - "ɴ": [ - 84 - ], - "ɵ": [ - 85 - ], - "ɶ": [ - 86 - ], - "ɸ": [ - 87 - ], - "ɹ": [ - 88 - ], - "ɺ": [ - 89 - ], - "ɻ": [ - 90 - ], - "ɽ": [ - 91 - ], - "ɾ": [ - 92 - ], - "ʀ": [ - 93 - ], - "ʁ": [ - 94 - ], - "ʂ": [ - 95 - ], - "ʃ": [ - 96 - ], - "ʄ": [ - 97 - ], - "ʈ": [ - 98 - ], - "ʉ": [ - 99 - ], - "ʊ": [ - 100 - ], - "ʋ": [ - 101 - ], - "ʌ": [ - 102 - ], - "ʍ": [ - 103 - ], - "ʎ": [ - 104 - ], - "ʏ": [ - 105 - ], - "ʐ": [ - 106 - ], - "ʑ": [ - 107 - ], - "ʒ": [ - 108 - ], - "ʔ": [ - 109 - ], - "ʕ": [ - 110 - ], - "ʘ": [ - 111 - ], - "ʙ": [ - 112 - ], - "ʛ": [ - 113 - ], - "ʜ": [ - 114 - ], - "ʝ": [ - 115 - ], - "ʟ": [ - 116 - ], - "ʡ": [ - 117 - ], - "ʢ": [ - 118 - ], - "ʲ": [ - 119 - ], - "ˈ": [ - 120 - ], - "ˌ": [ - 121 - ], - "ː": [ - 122 - ], - "ˑ": [ - 123 - ], - "˞": [ - 124 - ], - "β": [ - 125 - ], - "θ": [ - 126 - ], - "χ": [ - 127 - ], - "ᵻ": [ - 128 - ], - "ⱱ": [ - 129 - ], - "0": [ - 130 - ], - "1": [ - 131 - ], - "2": [ - 132 - ], - "3": [ - 133 - ], - "4": [ - 134 - ], - "5": [ - 135 - ], - "6": [ - 136 - ], - "7": [ - 137 - ], - "8": [ - 138 - ], - "9": [ - 139 - ], - "̧": [ - 140 - ], - "̃": [ - 141 - ], - "̪": [ - 142 - ], - "̯": [ - 143 - ], - "̩": [ - 144 - ], - "ʰ": [ - 145 - ], - "ˤ": [ - 146 - ], - "ε": [ - 147 - ], - "↓": [ - 148 - ], - "#": [ - 149 - ], - "\"": [ - 150 - ], - "↑": [ - 151 - ] - }, - "num_symbols": 256, - "num_speakers": 1, - "speaker_id_map": {}, - "piper_version": "1.0.0", - "language": { - "code": "zh_CN", - "family": "zh", - "region": "CN", - "name_native": "简体中文", - "name_english": "Chinese", - "country_english": "China" - }, - "dataset": "huayan" -} \ No newline at end of file diff --git a/python-work/find_cameras.py b/python-work/find_cameras.py deleted file mode 100644 index 44fde73..0000000 --- a/python-work/find_cameras.py +++ /dev/null @@ -1,292 +0,0 @@ -import ipaddress -import socket -import subprocess -import threading -import concurrent.futures -from typing import List, Set, Union, Dict -import time -import os -from urllib.parse import urlparse -import json -from collections import defaultdict - -active_ips: Set[str] = set() -found_cameras: Dict[str, Dict[str, str]] = defaultdict(dict) # 使用defaultdict,简化内层字典的初始化 -ip_lock = threading.Lock() -camera_lock = threading.Lock() - -# --- Configuration --- -MAX_WORKERS = 100 # Number of concurrent threads for scanning -COMMON_CAMERA_PORTS = [ - 80, # HTTP (web interface) - 443, # HTTPS (secure web interface) - 554, # RTSP (Real Time Streaming Protocol) - 8000, # Often used by Hikvision (SDK/HTTP) - 8080, # Alternative HTTP/RTSP - 8001, # Hikvision stream port - 37777, # Dahua primary port - 37778, # Dahua secondary port - 8002, # Often used for camera APIs or secondary streams -] - -SSH_PORTS = [22] # Potential SSH access for some cameras - -ONVIF_AVAILABLE = False -try: - import psutil - import onvif - try: - from onvif import discovery - _discovery_method = discovery.find_device - print("ONVIF: Using onvif.discovery.find_device for discovery.") - except (ImportError, AttributeError): - if hasattr(onvif, 'discover') and callable(onvif.discover): - _discovery_method = onvif.discover - print("ONVIF: Using top-level onvif.discover() for discovery.") - else: - _discovery_method = None - print("ONVIF: No suitable ONVIF discovery method found in 'onvif' module.") - raise ImportError("No ONVIF discovery method.") # Force into the outer except block - - ONVIF_AVAILABLE = True -except ImportError as e: - print(f"Warning: Required libraries for full ONVIF functionality could not be imported.") - print(f" Error: {e}") - print(f" Please ensure 'psutil' and 'onvif-zeep' are installed:") - print(f" pip install psutil onvif-zeep") - print(f" ONVIF discovery will be skipped.") - try: - import psutil - except ImportError: - psutil = None - _discovery_method = None - - -# --- Imports for SSH service detection --- -PARAMIKO_AVAILABLE = False -try: - import paramiko - PARAMIKO_AVAILABLE = True -except ImportError: - print("Warning: 'paramiko' not installed. SSH service detection will be skipped. " - "Install with 'pip install paramiko' for full functionality.") - - -def get_local_ip() -> str: - """Gets the local IP address of the machine.""" - s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - try: - # Connect to a dummy address. Doesn't actually send data. - s.connect(('10.255.255.255', 1)) - IP = s.getsockname()[0] - except Exception: - IP = '127.0.0.1' - finally: - s.close() - return IP - -def get_all_local_networks() -> List[str]: - """ - Uses psutil to find all active network interfaces and their associated network ranges. - Returns a list of network CIDR strings (e.g., '192.168.1.0/24'). - """ - if not psutil: - print("Warning: 'psutil' not available. Only scanning the /24 subnet of the local IP address.") - local_ip = get_local_ip() - if local_ip == '127.0.0.1': - return [] # Cannot determine external network from localhost - return [str(ipaddress.IPv4Network(f"{local_ip}/24", strict=False))] - - networks = set() - try: - for interface, snics in psutil.net_if_addrs().items(): - for snic in snics: - if snic.family == socket.AF_INET: # IPv4 address - ip_address = snic.address - netmask = snic.netmask - if ip_address and netmask and ip_address != '127.0.0.1': - try: - network_obj = ipaddress.IPv4Network(f"{ip_address}/{netmask}", strict=False) - networks.add(str(network_obj)) - except ipaddress.AddressValueError as e: - print(f"Warning: Could not parse IP address or netmask for {interface}: {ip_address}, {netmask}. Error: {e}") - return list(networks) - except Exception as e: - print(f"Error getting network interfaces with psutil: {e}") - print("Falling back to scanning only the /24 subnet of the local IP address.") - local_ip = get_local_ip() - if local_ip == '127.0.0.1': - return [] - return [str(ipaddress.IPv4Network(f"{local_ip}/24", strict=False))] - -def onvif_discovery_task() -> None: - """Performs ONVIF WS-Discovery to find compatible devices.""" - global ONVIF_AVAILABLE, _discovery_method - if not ONVIF_AVAILABLE or _discovery_method is None: - print("ONVIF: Skipping discovery due to 'psutil' or 'onvif-zeep' not being available or no suitable discovery method.") - return - - print("ONVIF: Starting discovery. This may take a few seconds...") - try: - discovered_device_xaddrs: List[str] = _discovery_method(timeout=5) - if not isinstance(discovered_device_xaddrs, list): - discovered_device_xaddrs = [discovered_device_xaddrs] if discovered_device_xaddrs else [] - - discovered_ips_via_onvif = [] - for xaddr in discovered_device_xaddrs: - try: - parsed_url = urlparse(xaddr) - device_ip = parsed_url.hostname - if device_ip and device_ip not in discovered_ips_via_onvif: - discovered_ips_via_onvif.append(device_ip) - except Exception as url_e: - print(f"ONVIF: Warning: Could not parse IP from device XAddr '{xaddr}': {url_e}") - - if discovered_ips_via_onvif: - print(f"ONVIF: Found {len(discovered_ips_via_onvif)} potential ONVIF devices via WS-Discovery.") - for device_ip in discovered_ips_via_onvif: - with ip_lock: - active_ips.add(device_ip) - with camera_lock: - found_cameras[device_ip]['ONVIF_Discovery'] = "ONVIF Device (WS-Discovery)" - else: - print("ONVIF: No ONVIF devices found via WS-Discovery.") - - except Exception as e: - print(f"ONVIF: Error during ONVIF discovery (using _discovery_method): {e}") - -def check_socket(ip: str, port: int, timeout: float = 0.5) -> bool: - """Attempts to connect to a specific port on an IP address.""" - try: - with socket.create_connection((ip, port), timeout) as sock: - sock.shutdown(socket.SHUT_RDWR) # Gracefully close connection - return True - except (socket.timeout, ConnectionRefusedError, OSError): - return False - except Exception as e: - return False - -def check_ssh_banner(ip: str, port: int, timeout: float = 0.5) -> Union[str, bool]: - """Attempts to get SSH banner to verify SSH service.""" - if not PARAMIKO_AVAILABLE: - return False - - try: - transport = paramiko.Transport((ip, port)) - transport.connect(timeout=timeout) - banner = transport.get_banner() - transport.close() - return banner.strip() - except (paramiko.SSHException, socket.error, socket.timeout): - return False - except Exception as e: - return False - - -def service_scan_task(ip: str) -> None: - """Scans common camera ports and SSH ports on a given IP and updates found_cameras.""" - for port in COMMON_CAMERA_PORTS: - if check_socket(ip, port): - with camera_lock: - if port == 80: - found_cameras[ip]['HTTP'] = f"Open on port {port}" - elif port == 443: - found_cameras[ip]['HTTPS'] = f"Open on port {port}" - elif port == 554: - found_cameras[ip]['RTSP'] = f"Open on port {port}" - elif port == 8000 or port == 8001: # Common for Hikvision - found_cameras[ip]['Hikvision_Service'] = f"Open on port {port}" - elif port == 37777 or port == 37778: # Common for Dahua - found_cameras[ip]['Dahua_Service'] = f"Open on port {port}" - else: - found_cameras[ip][f'TCP_{port}'] = f"Open on port {port}" - - for port in SSH_PORTS: - banner = check_ssh_banner(ip, port) - if banner: - with camera_lock: - found_cameras[ip]['SSH'] = f"Open on port {port} (Banner: {banner})" - - -def main(): - start_time = time.time() - print("--- Starting Network Camera Discovery on RK3588 ---") - - local_ip = get_local_ip() - print(f"Local IP Address: {local_ip}") - - # Get all local networks for scanning - all_local_networks = get_all_local_networks() - if not all_local_networks: - print("No local networks detected for scanning. Exiting.") - return - - print("Detected local networks for scanning:") - for net in all_local_networks: - print(f" - {net}") - # --- EXCLUDE Docker internal networks --- - # If running inside a Docker container, often these are internal. - # Adjust these prefixes based on your Docker network configuration if different. - all_local_networks_filtered = [] - DOCKER_NETWORK_PREFIXES = ["172.17.", "172.18.", "172.19.", "172.20."] # Add more if your Docker uses other 172.x ranges - - for net_cidr in all_local_networks: - is_docker_internal = False - for prefix in DOCKER_NETWORK_PREFIXES: - if net_cidr.startswith(prefix): - is_docker_internal = True - break - - if not is_docker_internal: - all_local_networks_filtered.append(net_cidr) - else: - print(f" - Excluding Docker internal network: {net_cidr}") - if not all_local_networks_filtered: - print("No external local networks found for scanning after filtering Docker networks. Exiting.") - return - - print("\nFiltered local networks for scanning:") - for net in all_local_networks_filtered: - print(f" - {net}") - # --- ONVIF Discovery (runs independently) --- - onvif_discovery_task() - # --- Prepare IPs for Service Scan (without relying on 'ping') --- - all_ips_for_service_scan = set() - # Use the filtered list of networks - for network_str in all_local_networks_filtered: - try: - network = ipaddress.IPv4Network(network_str, strict=False) - for ip_obj in network.hosts(): - all_ips_for_service_scan.add(str(ip_obj)) - except Exception as e: - print(f"Error processing network {network_str}: {e}") - continue - total_ips_for_service_scan = len(all_ips_for_service_scan) - print(f"Proceeding directly to service scan of {total_ips_for_service_scan} IPs on common camera ports.") - - # --- Service Scan for common camera ports --- - if total_ips_for_service_scan > 0: - with concurrent.futures.ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor: - # Map the service scan task to all IPs identified by local networks - executor.map(service_scan_task, list(all_ips_for_service_scan)) - - # Wait for service scan to complete using concurrent.futures - # (executor.map is blocking here, which is fine) - - - # --- Discovery Results --- - print("\n--- Discovery Results ---") - if found_cameras: - print(f"Found {len(found_cameras)} potential camera devices and services:") - for ip, services in found_cameras.items(): - print(f" IP: {ip}") - # Sort services for consistent output - for service, details in sorted(services.items()): - print(f" - {service}: {details}") - else: - print("No network cameras or detectable services found based on ONVIF or port scanning.") - - print(f"\n--- Discovery Finished in {time.time() - start_time:.2f} seconds ---") - -if __name__ == "__main__": - main() diff --git a/python-work/new_find_camera.py b/python-work/new_find_camera.py deleted file mode 100644 index 00616ee..0000000 --- a/python-work/new_find_camera.py +++ /dev/null @@ -1,280 +0,0 @@ -#!/usr/bin/env python3 - -import subprocess -import json -import sys -import ipaddress -import socket -import re -from typing import List, Set, Dict, Any -import time # 导入 time -import threading # 导入 threading - -# 尝试导入 psutil -try: - import psutil -except ImportError: - print("错误: 'psutil' 库未找到。请在 Dockerfile 中或使用 'pip install psutil' 安装。") - sys.exit(1) - -# --- 配置 --- -COMMON_CAMERA_PORTS = [ - 80, # HTTP (web interface) - 443, # HTTPS (secure web interface) - 554, # RTSP (Real Time Streaming Protocol) - 8000, # Often used by Hikvision (SDK/HTTP) - 8080, # Alternative HTTP/RTSP - 8001, # Hikvision stream port - 37777, # Dahua primary port - 37778, # Dahua secondary port - 8002, # Often used for camera APIs or secondary streams -] -NMAP_PORT_STRING = "T:" + ",".join(map(str, COMMON_CAMERA_PORTS)) - -# 用于过滤 Docker 网络的常见前缀 -DOCKER_NETWORK_PREFIXES = ["172.17.", "172.18.", "172.19.", "172.20."] - -# 全局标志,用于停止进度线程 -stop_progress_flag = threading.Event() -start_time = time.time() - -def progress_indicator(): - """在后台线程中运行,定期打印进度信息。""" - interval = 10 # 每 10 秒报告一次 - count = 0 - - # 打印一个提示,说明进度指示器已启动 - print(f"--- Nmap 扫描进行中(每 {interval} 秒更新一次)---", file=sys.stderr) - - while not stop_progress_flag.is_set(): - time_elapsed = time.time() - start_time - - # 使用 is_set() 检查标志,并等待最多 interval 秒 - stop_progress_flag.wait(interval) - - if not stop_progress_flag.is_set(): - count += 1 - # 打印一个简单的状态信息 - print(f"--- 状态更新 #{count}: 扫描已运行 {time_elapsed:.1f} 秒... ---", file=sys.stderr) - -def get_local_ip() -> str: - """获取本机的本地 IP 地址。""" - s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - try: - # 连接到一个虚拟地址 - s.connect(('10.255.255.255', 1)) - IP = s.getsockname()[0] - except Exception: - IP = '127.0.0.1' - finally: - s.close() - return IP - -def get_all_local_networks() -> List[str]: - """ - 使用 psutil 查找所有活动的网络接口及其关联的网络范围。 - 返回一个网络 CIDR 字符串列表 (例如 '192.168.1.0/24')。 - """ - networks = set() - try: - for interface, snics in psutil.net_if_addrs().items(): - for snic in snics: - if snic.family == socket.AF_INET: # IPv4 - ip_address = snic.address - netmask = snic.netmask - if ip_address and netmask and ip_address != '127.0.0.1': - try: - # strict=False 允许主机地址 - network_obj = ipaddress.IPv4Network(f"{ip_address}/{netmask}", strict=False) - networks.add(str(network_obj)) - except ipaddress.AddressValueError as e: - print(f"警告: 无法解析网络 {interface}: {ip_address}, {netmask}。错误: {e}", file=sys.stderr) - return list(networks) - except Exception as e: - print(f"错误: 使用 psutil 获取网络接口时出错: {e}", file=sys.stderr) - print("回退: 仅扫描本地 IP 的 /24 子网。", file=sys.stderr) - local_ip = get_local_ip() - if local_ip == '127.0.0.1': - return [] - return [str(ipaddress.IPv4Network(f"{local_ip}/24", strict=False))] - -def filter_docker_networks(all_networks: List[str]) -> List[str]: - """过滤掉已知的 Docker 内部网络。""" - filtered_networks = [] - print("--- 检测到以下网络 ---", file=sys.stderr) - for net_cidr in all_networks: - print(f" - {net_cidr}", file=sys.stderr) - - for net_cidr in all_networks: - is_docker_internal = False - for prefix in DOCKER_NETWORK_PREFIXES: - if net_cidr.startswith(prefix): - is_docker_internal = True - break - - if not is_docker_internal: - filtered_networks.append(net_cidr) - else: - print(f" - 排除 Docker 内部网络: {net_cidr}", file=sys.stderr) - - if not filtered_networks and all_networks: - print(f"警告: 所有检测到的网络似乎都是 Docker 内部网络。扫描可能不会发现外部设备。", file=sys.stderr) - print(f"将继续扫描所有检测到的网络: {', '.join(all_networks)}", file=sys.stderr) - return all_networks # 如果过滤后为空,但原来不为空,则返回原列表 - - return filtered_networks - -def run_nmap_scan(targets: List[str]) -> str: - """ - 对目标网络运行 nmap 扫描。 - -sV: 服务版本检测 - -p [ports]: 扫描指定的 TCP 端口 - -T4: 加快扫描速度 - -oJ -: 将 JSON 输出到 stdout - """ - print(f"\n--- 开始 Nmap 扫描 (目标: {', '.join(targets)}) ---", file=sys.stderr) - print(f"--- 扫描端口: {NMAP_PORT_STRING} ---", file=sys.stderr) - - # 需要 sudo 权限来运行 nmap - # 您的 Dockerfile 已经为 'dev' 用户设置了 NOPASSWD:ALL - nmap_command = [ - 'sudo', 'nmap', - '-oJ', '-', # 输出 JSON 到 stdout - '-T4', # 更快的时间模板 - '--open', # 只显示打开的端口 - '-sV', # 服务版本指纹识别 - # --- 移除了导致错误的 ONVIF 脚本 --- - # '--script=broadcast-onvif-discover', - # --- --------------------------- --- - '-p', NMAP_PORT_STRING, # 扫描特定端口 - ] + targets - - start_time = time.time() - stop_progress_flag.clear() # 确保标志是清除的 - progress_thread = threading.Thread(target=progress_indicator, daemon=True) - progress_thread.start() - try: - # 设置超时,例如10分钟,以防扫描卡住 - result = subprocess.run(nmap_command, - capture_output=True, - text=True, - check=True, - timeout=600) - stop_progress_flag.set() - progress_thread.join() # 等 - print("--- Nmap 扫描完成 ---", file=sys.stderr) - 待线程清理 - return result.stdout - except subprocess.CalledProcessError as e: - print(f"错误: Nmap 执行失败 (返回码 {e.returncode}):", file=sys.stderr) - print(f"Nmap Stderr: {e.stderr}", file=sys.stderr) - return None - except subprocess.TimeoutExpired as e: - print(f"错误: Nmap 扫描超时 ({e.timeout} 秒)。", file=sys.stderr) - print(f"Nmap Stdout: {e.stdout}", file=sys.stderr) - print(f"Nmap Stderr: {e.stderr}", file=sys.stderr) - return None - except FileNotFoundError: - print("错误: 'sudo' 或 'nmap' 命令未找到。请确保它在 PATH 中并且已安装。", file=sys.stderr) - return None - -def parse_nmap_json(nmap_stdout: str) -> List[Dict[str, Any]]: - """将 nmap 的 JSON 输出解析为我们想要的简洁格式。""" - results = [] - if not nmap_stdout: - return results - - try: - nmap_data = json.loads(nmap_stdout) - except json.JSONDecodeError as e: - print(f"错误: 无法解析 Nmap 的 JSON 输出。{e}", file=sys.stderr) - print(f"原始输出: {nmap_stdout[:500]}...", file=sys.stderr) - return results - - if 'hosts' not in nmap_data: - print("警告: Nmap 输出中未找到 'hosts' 键。", file=sys.stderr) - return results - - for host in nmap_data.get('hosts', []): - if host.get('status', {}).get('state') != 'up': - continue - - ip = host.get('addresses', {}).get('ipv4') - mac = host.get('addresses', {}).get('mac') - if not ip: - continue - - vendor = host.get('vendor', {}).get(mac, None) if mac else None - - device_info = { - "ip": ip, - "mac": mac, - "vendor": vendor, - "services": [], - "onvif_xaddrs": [] # 即使没有脚本,也保留此键以保持格式一致 - } - - # 1. 解析端口和服务 - for port_info in host.get('ports', []): - if port_info.get('state', {}).get('state') == 'open': - service_data = port_info.get('service', {}) - service = { - "port": int(port_info.get('portid')), - "service_name": service_data.get('name'), - "product": service_data.get('product'), - "version": service_data.get('version'), - "extra_info": service_data.get('extrainfo') - } - device_info["services"].append(service) - - # 2. 解析 ONVIF 脚本输出 (现在为空) - # 'hostscript' 字段包含主机级别的脚本结果 - for script in host.get('hostscript', []): - if script.get('id') == 'broadcast-onvif-discover': - # Nmap 脚本输出是一个字符串。我们用正则提取 XAddrs。 - output = script.get('output', '') - # 匹配 'http://...' 格式的 URL,直到遇到空格或换行符 - xaddrs = re.findall(r'http://[^\s,]+', output) - device_info["onvif_xaddrs"] = xaddrs - - # 只有当我们发现了开放服务时才添加该设备 - if device_info["services"]: - results.append(device_info) - - return results - -def main(): - print("--- 启动网络摄像头发现 (基于 Nmap) ---", file=sys.stderr) - - # 1. 获取并过滤本地网络 - all_local_networks = get_all_local_networks() - if not all_local_networks: - print("错误: 未检测到本地网络用于扫描。退出。", file=sys.stderr) - sys.exit(1) - - networks_to_scan = filter_docker_networks(all_local_networks) - - if not networks_to_scan: - print("警告: 过滤后没有可扫描的网络。退出。", file=sys.stderr) - sys.exit(0) - - print("\n--- 将扫描以下网络 ---", file=sys.stderr) - for net in networks_to_scan: - print(f" - {net}", file=sys.stderr) - - # 2. 运行 Nmap 扫描 - nmap_json_output = run_nmap_scan(networks_to_scan) - - if not nmap_json_output: - print("错误: 未从 Nmap 收到任何输出。退出。", file=sys.stderr) - sys.exit(1) - - # 3. 解析 Nmap JSON - final_results = parse_nmap_json(nmap_json_output) - - # 4. 将最终结果以 JSON 格式打印到 stdout - # 注意:stderr 用于打印日志,stdout 仅用于输出最终的 JSON - print(json.dumps(final_results, indent=2)) - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/src/alarm/alarm_defs.h b/src/alarm/alarm_defs.h index 55dcd9f..4428955 100644 --- a/src/alarm/alarm_defs.h +++ b/src/alarm/alarm_defs.h @@ -7,58 +7,91 @@ #include #include -enum class CompareType { GT, LT, EQ, UNKNOWN }; - -enum class AlarmLevel { INFO, WARNING, CRITICAL, UNKNOWN }; - -struct AlarmRule { - std::string rule_id; - std::string device_id; - std::string data_point_name; - CompareType compare_type = CompareType::UNKNOWN; - double threshold = 0.0; - AlarmLevel level = AlarmLevel::INFO; - int debounce_seconds = 0; - std::string message_template; - - std::string alarm_mqtt_topic; - std::string clear_message_template; - - static CompareType stringToCompareType(const std::string& s) { - if (s == "GT") return CompareType::GT; - if (s == "LT") return CompareType::LT; - if (s == "EQ") return CompareType::EQ; - return CompareType::UNKNOWN; - } - - static AlarmLevel stringToLevel(const std::string& s) { - if (s == "INFO") return AlarmLevel::INFO; - if (s == "WARNING") return AlarmLevel::WARNING; - if (s == "CRITICAL") return AlarmLevel::CRITICAL; - return AlarmLevel::UNKNOWN; - } - - static std::string levelToString(AlarmLevel l) { - switch (l) { - case AlarmLevel::INFO: return "INFO"; - case AlarmLevel::WARNING: return "WARNING"; - case AlarmLevel::CRITICAL: return "CRITICAL"; - default: return "UNKNOWN"; - } - } +enum class CompareType +{ + GT, + LT, + EQ, + UNKNOWN }; -enum class AlarmStateType { NORMAL, PENDING, ACTIVE }; +enum class AlarmLevel +{ + INFO, + WARNING, + CRITICAL, + UNKNOWN +}; -struct AlarmState { - AlarmStateType current_state = AlarmStateType::NORMAL; - boost::asio::steady_timer debounce_timer; +struct AlarmRule +{ + std::string rule_id; + std::string device_id; + std::string data_point_name; + CompareType compare_type = CompareType::UNKNOWN; + double threshold = 0.0; + AlarmLevel level = AlarmLevel::INFO; + int debounce_seconds = 0; + std::string message_template; - explicit AlarmState(boost::asio::io_context& io) : debounce_timer(io) {} + std::string alarm_mqtt_topic; + std::string clear_message_template; - AlarmState(const AlarmState&) = delete; - AlarmState& operator=(const AlarmState&) = delete; + static CompareType stringToCompareType(const std::string &s) + { + if (s == "GT") + return CompareType::GT; + if (s == "LT") + return CompareType::LT; + if (s == "EQ") + return CompareType::EQ; + return CompareType::UNKNOWN; + } - AlarmState(AlarmState&&) = default; - AlarmState& operator=(AlarmState&&) = default; -}; \ No newline at end of file + static AlarmLevel stringToLevel(const std::string &s) + { + if (s == "INFO") + return AlarmLevel::INFO; + if (s == "WARNING") + return AlarmLevel::WARNING; + if (s == "CRITICAL") + return AlarmLevel::CRITICAL; + return AlarmLevel::UNKNOWN; + } + + static std::string levelToString(AlarmLevel l) + { + switch (l) + { + case AlarmLevel::INFO: + return "INFO"; + case AlarmLevel::WARNING: + return "WARNING"; + case AlarmLevel::CRITICAL: + return "CRITICAL"; + default: + return "UNKNOWN"; + } + } +}; + +enum class AlarmStateType +{ + NORMAL, + PENDING, + ACTIVE +}; + +struct AlarmState +{ + AlarmStateType current_state = AlarmStateType::NORMAL; + boost::asio::steady_timer debounce_timer; + + explicit AlarmState(boost::asio::io_context &io) : debounce_timer(io) {} + + AlarmState(const AlarmState &) = delete; + AlarmState &operator=(const AlarmState &) = delete; + + AlarmState(AlarmState &&) = default; + AlarmState &operator=(AlarmState &&) = default; +}; diff --git a/src/alarm/alarm_service.cc b/src/alarm/alarm_service.cc index dd640bf..2daf875 100644 --- a/src/alarm/alarm_service.cc +++ b/src/alarm/alarm_service.cc @@ -8,216 +8,243 @@ #include "dataStorage/data_storage.h" AlarmService::AlarmService(boost::asio::io_context &io, - PiperTTSInterface &tts_service, - MqttClient &mqtt_client) - : m_io_context(io), m_tts_service(tts_service), m_mqtt_client(mqtt_client), - m_tts_running(true) { - m_tts_thread = std::thread(&AlarmService::tts_worker, this); - spdlog::info("AlarmService created and TTS worker thread started."); + MqttClient &mqtt_client) + : m_io_context(io), m_mqtt_client(mqtt_client) +{ } -AlarmService::~AlarmService() { - if (m_tts_running) { - stop(); - } - if (m_tts_thread.joinable()) { - m_tts_thread.join(); - } +AlarmService::~AlarmService() +{ } -void AlarmService::stop() { - spdlog::info("Stopping AlarmService TTS worker..."); - { - std::lock_guard lock(m_tts_queue_mutex); - m_tts_running = false; - } - m_tts_cv.notify_one(); +void AlarmService::stop() +{ + spdlog::info("Stopping AlarmService TTS worker..."); + { + } } -bool AlarmService::load_rules(const std::string &config_path) { - m_rules_config_path = config_path; +bool AlarmService::load_rules(const std::string &config_path) +{ + m_rules_config_path = config_path; - std::vector new_rules; - std::map new_states; + std::vector new_rules; + std::map new_states; - if (!parse_rules_from_file(config_path, new_rules, new_states)) { - return false; - } + if (!parse_rules_from_file(config_path, new_rules, new_states)) + { + return false; + } - { - std::lock_guard lock(m_rules_mutex); - m_rules = std::move(new_rules); - m_alarm_states = std::move(new_states); - } + { + std::lock_guard lock(m_rules_mutex); + m_rules = std::move(new_rules); + m_alarm_states = std::move(new_states); + } - spdlog::info("Successfully loaded {} alarm rules from '{}'.", m_rules.size(), - config_path); - return true; + spdlog::info("Successfully loaded {} alarm rules from '{}'.", m_rules.size(), + config_path); + return true; } -bool AlarmService::reload_rules() { - if (m_rules_config_path.empty()) { - spdlog::error("Cannot reload rules: config path not set."); - return false; - } +bool AlarmService::reload_rules() +{ + if (m_rules_config_path.empty()) + { + spdlog::error("Cannot reload rules: config path not set."); + return false; + } - spdlog::info("Attempting to reload alarm rules from '{}'...", - m_rules_config_path); + spdlog::info("Attempting to reload alarm rules from '{}'...", + m_rules_config_path); - std::vector new_rules; - std::map new_states; + std::vector new_rules; + std::map new_states; - if (!parse_rules_from_file(m_rules_config_path, new_rules, new_states)) { - spdlog::error("Failed to parse new rules file, aborting reload."); - return false; - } + if (!parse_rules_from_file(m_rules_config_path, new_rules, new_states)) + { + spdlog::error("Failed to parse new rules file, aborting reload."); + return false; + } - try { + try + { - auto active_alarms = DataStorage::getInstance().getActiveAlarms(); + auto active_alarms = DataStorage::getInstance().getActiveAlarms(); - for (const auto &alarm : active_alarms) { - bool rule_exists = false; + for (const auto &alarm : active_alarms) + { + bool rule_exists = false; - for (const auto &new_rule : new_rules) { - if (new_rule.rule_id == alarm.rule_id) { - rule_exists = true; - break; - } - } + for (const auto &new_rule : new_rules) + { + if (new_rule.rule_id == alarm.rule_id) + { + rule_exists = true; + break; + } + } - if (!rule_exists) { - spdlog::info("Reload Cleanup: Removing zombie alarm for deleted rule " - "'{}' on device '{}'.", - alarm.rule_id, alarm.device_id); + if (!rule_exists) + { + spdlog::info("Reload Cleanup: Removing zombie alarm for deleted rule " + "'{}' on device '{}'.", + alarm.rule_id, alarm.device_id); - AlarmEvent clear_event; - clear_event.rule_id = alarm.rule_id; - clear_event.device_id = alarm.device_id; - clear_event.status = AlarmEventStatus::CLEARED; - clear_event.message = - "Rule deleted from config. Auto-cleared by system."; - clear_event.level = "INFO"; - clear_event.trigger_value = 0.0; - clear_event.timestamp_ms = - std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()) - .count(); + AlarmEvent clear_event; + clear_event.rule_id = alarm.rule_id; + clear_event.device_id = alarm.device_id; + clear_event.status = AlarmEventStatus::CLEARED; + clear_event.message = + "Rule deleted from config. Auto-cleared by system."; + clear_event.level = "INFO"; + clear_event.trigger_value = 0.0; + clear_event.timestamp_ms = + std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); - DataStorage::getInstance().storeAlarmEvent(clear_event); - } - } - } catch (const std::exception &e) { - spdlog::warn("Error during zombie alarm cleanup: {}", e.what()); - } + DataStorage::getInstance().storeAlarmEvent(clear_event); + } + } + } + catch (const std::exception &e) + { + spdlog::warn("Error during zombie alarm cleanup: {}", e.what()); + } - { - std::lock_guard lock(m_rules_mutex); - m_rules = std::move(new_rules); - m_alarm_states = std::move(new_states); - } + { + std::lock_guard lock(m_rules_mutex); + m_rules = std::move(new_rules); + m_alarm_states = std::move(new_states); + } - spdlog::info("Successfully reloaded {} alarm rules.", m_rules.size()); - return true; + spdlog::info("Successfully reloaded {} alarm rules.", m_rules.size()); + return true; } void AlarmService::process_device_data(const std::string &device_id, - const std::string &data_json) { - std::lock_guard lock(m_rules_mutex); - try { - json j_data = json::parse(data_json); - if (!j_data.is_object()) - return; + const std::string &data_json) +{ + std::lock_guard lock(m_rules_mutex); + try + { + json j_data = json::parse(data_json); + if (!j_data.is_object()) + return; - for (auto &rule : m_rules) { - if (rule.device_id == device_id || rule.device_id == "*") { - if (j_data.contains(rule.data_point_name)) { - if (j_data[rule.data_point_name].is_number()) { - double value = j_data[rule.data_point_name].get(); - check_rule_against_value(rule, value, device_id); - } - } - } - } - } catch (const json::exception &e) { - spdlog::error("Failed to parse device data for alarm: {}", e.what()); - } + for (auto &rule : m_rules) + { + if (rule.device_id == device_id || rule.device_id == "*") + { + if (j_data.contains(rule.data_point_name)) + { + if (j_data[rule.data_point_name].is_number()) + { + double value = j_data[rule.data_point_name].get(); + check_rule_against_value(rule, value, device_id); + } + } + } + } + } + catch (const json::exception &e) + { + spdlog::error("Failed to parse device data for alarm: {}", e.what()); + } } -void AlarmService::process_system_data(const std::string &system_data_json) { - std::lock_guard lock(m_rules_mutex); - try { - json j_data = json::parse(system_data_json); - if (!j_data.is_object()) - return; +void AlarmService::process_system_data(const std::string &system_data_json) +{ + std::lock_guard lock(m_rules_mutex); + try + { + json j_data = json::parse(system_data_json); + if (!j_data.is_object()) + return; - const std::string device_id = "proxy_system"; + const std::string device_id = "proxy_system"; - for (auto &rule : m_rules) { - if (rule.device_id == device_id) { - if (j_data.contains(rule.data_point_name)) { - if (j_data[rule.data_point_name].is_number()) { - double value = j_data[rule.data_point_name].get(); - check_rule_against_value(rule, value, device_id); - } - } - } - } - } catch (const json::exception &e) { - spdlog::error("Failed to parse system data for alarm: {}", e.what()); - } + for (auto &rule : m_rules) + { + if (rule.device_id == device_id) + { + if (j_data.contains(rule.data_point_name)) + { + if (j_data[rule.data_point_name].is_number()) + { + double value = j_data[rule.data_point_name].get(); + check_rule_against_value(rule, value, device_id); + } + } + } + } + } + catch (const json::exception &e) + { + spdlog::error("Failed to parse system data for alarm: {}", e.what()); + } } void AlarmService::check_rule_against_value( - AlarmRule &rule, double value, const std::string &actual_device_id) { - bool condition_met = false; - switch (rule.compare_type) { - case CompareType::GT: - condition_met = (value > rule.threshold); - break; - case CompareType::LT: - condition_met = (value < rule.threshold); - break; - case CompareType::EQ: - condition_met = (std::abs(value - rule.threshold) < 1e-9); - break; - default: - break; - } + AlarmRule &rule, double value, const std::string &actual_device_id) +{ + bool condition_met = false; + switch (rule.compare_type) + { + case CompareType::GT: + condition_met = (value > rule.threshold); + break; + case CompareType::LT: + condition_met = (value < rule.threshold); + break; + case CompareType::EQ: + condition_met = (std::abs(value - rule.threshold) < 1e-9); + break; + default: + break; + } - auto it = m_alarm_states.find(rule.rule_id); + auto it = m_alarm_states.find(rule.rule_id); - if (it == m_alarm_states.end()) { - spdlog::warn( - "AlarmState for rule '{}' was missing. Creating it on-the-fly.", - rule.rule_id); + if (it == m_alarm_states.end()) + { + spdlog::warn( + "AlarmState for rule '{}' was missing. Creating it on-the-fly.", + rule.rule_id); - auto result = m_alarm_states.emplace(std::piecewise_construct, - std::forward_as_tuple(rule.rule_id), - std::forward_as_tuple(m_io_context)); - it = result.first; - } + auto result = m_alarm_states.emplace(std::piecewise_construct, + std::forward_as_tuple(rule.rule_id), + std::forward_as_tuple(m_io_context)); + it = result.first; + } - AlarmState &state = it->second; + AlarmState &state = it->second; - if (condition_met) { - if (state.current_state == AlarmStateType::NORMAL) { - state.current_state = AlarmStateType::PENDING; + if (condition_met) + { + if (state.current_state == AlarmStateType::NORMAL) + { + state.current_state = AlarmStateType::PENDING; - if (rule.debounce_seconds <= 0) { - state.current_state = AlarmStateType::ACTIVE; - trigger_alarm_action(rule, value, actual_device_id); - } else { - spdlog::debug("Alarm PENDING: {}", rule.rule_id); - state.debounce_timer.expires_after( - std::chrono::seconds(rule.debounce_seconds)); + if (rule.debounce_seconds <= 0) + { + state.current_state = AlarmStateType::ACTIVE; + trigger_alarm_action(rule, value, actual_device_id); + } + else + { + spdlog::debug("Alarm PENDING: {}", rule.rule_id); + state.debounce_timer.expires_after( + std::chrono::seconds(rule.debounce_seconds)); - std::string rule_id = rule.rule_id; + std::string rule_id = rule.rule_id; - state.debounce_timer.async_wait([this, rule_id, value, - actual_device_id]( - const boost::system::error_code - &ec) { + state.debounce_timer.async_wait([this, rule_id, value, + actual_device_id]( + const boost::system::error_code + &ec) + { std::lock_guard lock(m_rules_mutex); if (ec == boost::asio::error::operation_aborted) { @@ -245,331 +272,330 @@ void AlarmService::check_rule_against_value( current_state.current_state == AlarmStateType::PENDING) { current_state.current_state = AlarmStateType::ACTIVE; trigger_alarm_action(*current_rule, value, actual_device_id); - } - }); - } - } - - } else { - if (state.current_state == AlarmStateType::PENDING) { - state.debounce_timer.cancel(); - state.current_state = AlarmStateType::NORMAL; - } else if (state.current_state == AlarmStateType::ACTIVE) { - state.current_state = AlarmStateType::NORMAL; - clear_alarm(rule, value, actual_device_id); - } - } + } }); + } + } + } + else + { + if (state.current_state == AlarmStateType::PENDING) + { + state.debounce_timer.cancel(); + state.current_state = AlarmStateType::NORMAL; + } + else if (state.current_state == AlarmStateType::ACTIVE) + { + state.current_state = AlarmStateType::NORMAL; + clear_alarm(rule, value, actual_device_id); + } + } } std::string AlarmService::format_message(const AlarmRule &rule, - const std::string &template_str, - double value, - const std::string &actual_device_id) { - std::string msg = template_str; + const std::string &template_str, + double value, + const std::string &actual_device_id) +{ + std::string msg = template_str; - size_t pos = msg.find("{device_id}"); - if (pos != std::string::npos) { - msg.replace(pos, 11, actual_device_id); - } + size_t pos = msg.find("{device_id}"); + if (pos != std::string::npos) + { + msg.replace(pos, 11, actual_device_id); + } - pos = msg.find("{value}"); - if (pos != std::string::npos) { - char buffer[32]; - std::snprintf(buffer, sizeof(buffer), "%.2f", value); - msg.replace(pos, 7, buffer); - } + pos = msg.find("{value}"); + if (pos != std::string::npos) + { + char buffer[32]; + std::snprintf(buffer, sizeof(buffer), "%.2f", value); + msg.replace(pos, 7, buffer); + } - pos = msg.find("{threshold}"); - if (pos != std::string::npos) { - char buffer[32]; - std::snprintf(buffer, sizeof(buffer), "%.2f", rule.threshold); - msg.replace(pos, 11, buffer); - } - return msg; + pos = msg.find("{threshold}"); + if (pos != std::string::npos) + { + char buffer[32]; + std::snprintf(buffer, sizeof(buffer), "%.2f", rule.threshold); + msg.replace(pos, 11, buffer); + } + return msg; } void AlarmService::trigger_alarm_action(AlarmRule &rule, double value, - const std::string &actual_device_id) { - std::string message = - format_message(rule, rule.message_template, value, actual_device_id); - spdlog::warn("[ALARM ACTIVE] (Rule: {}) {}", rule.rule_id, message); + const std::string &actual_device_id) +{ + std::string message = + format_message(rule, rule.message_template, value, actual_device_id); + spdlog::warn("[ALARM ACTIVE] (Rule: {}) {}", rule.rule_id, message); - AlarmEvent event; - event.rule_id = rule.rule_id; - event.device_id = actual_device_id; - event.status = AlarmEventStatus::ACTIVE; - event.level = AlarmRule::levelToString(rule.level); - event.message = message; - event.trigger_value = value; - event.timestamp_ms = std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()) - .count(); + AlarmEvent event; + event.rule_id = rule.rule_id; + event.device_id = actual_device_id; + event.status = AlarmEventStatus::ACTIVE; + event.level = AlarmRule::levelToString(rule.level); + event.message = message; + event.trigger_value = value; + event.timestamp_ms = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); - std::string topic = - rule.alarm_mqtt_topic.empty() ? "proxy/alarms" : rule.alarm_mqtt_topic; - m_mqtt_client.publish(topic, AlarmEvent::toJson(event).dump(), 1, false); + std::string topic = + rule.alarm_mqtt_topic.empty() ? "proxy/alarms" : rule.alarm_mqtt_topic; + m_mqtt_client.publish(topic, AlarmEvent::toJson(event).dump(), 1, false); - if (!DataStorage::getInstance().storeAlarmEvent(event)) { - spdlog::error("Failed to store ACTIVE alarm event for rule: {}", - rule.rule_id); - } + if (!DataStorage::getInstance().storeAlarmEvent(event)) + { + spdlog::error("Failed to store ACTIVE alarm event for rule: {}", + rule.rule_id); + } } void AlarmService::clear_alarm(AlarmRule &rule, double value, - const std::string &actual_device_id) { - spdlog::info("[ALARM CLEARED] (Rule: {})", rule.rule_id); + const std::string &actual_device_id) +{ + spdlog::info("[ALARM CLEARED] (Rule: {})", rule.rule_id); - if (rule.clear_message_template.empty()) { - return; - } + if (rule.clear_message_template.empty()) + { + return; + } - std::string message = format_message(rule, rule.clear_message_template, value, - actual_device_id); + std::string message = format_message(rule, rule.clear_message_template, value, + actual_device_id); - AlarmEvent event; - event.rule_id = rule.rule_id; - event.device_id = actual_device_id; - event.status = AlarmEventStatus::CLEARED; - event.level = AlarmRule::levelToString(rule.level); - event.message = message; - event.trigger_value = value; - event.timestamp_ms = std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()) - .count(); + AlarmEvent event; + event.rule_id = rule.rule_id; + event.device_id = actual_device_id; + event.status = AlarmEventStatus::CLEARED; + event.level = AlarmRule::levelToString(rule.level); + event.message = message; + event.trigger_value = value; + event.timestamp_ms = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); - schedule_tts(message); + std::string topic = + rule.alarm_mqtt_topic.empty() ? "proxy/alarms" : rule.alarm_mqtt_topic; + m_mqtt_client.publish(topic, AlarmEvent::toJson(event).dump(), 1, false); - std::string topic = - rule.alarm_mqtt_topic.empty() ? "proxy/alarms" : rule.alarm_mqtt_topic; - m_mqtt_client.publish(topic, AlarmEvent::toJson(event).dump(), 1, false); - - if (!DataStorage::getInstance().storeAlarmEvent(event)) { - spdlog::error("Failed to store CLEARED alarm event for rule: {}", - rule.rule_id); - } + if (!DataStorage::getInstance().storeAlarmEvent(event)) + { + spdlog::error("Failed to store CLEARED alarm event for rule: {}", + rule.rule_id); + } } -void AlarmService::tts_worker() { - while (m_tts_running) { - std::string message_to_play; - { - std::unique_lock lock(m_tts_queue_mutex); - - m_tts_cv.wait(lock, - [this] { return !m_tts_queue.empty() || !m_tts_running; }); - - if (!m_tts_running && m_tts_queue.empty()) { - break; - } - - if (!m_tts_queue.empty()) { - message_to_play = m_tts_queue.front(); - m_tts_queue.pop(); - } - } - - if (!message_to_play.empty()) { - spdlog::debug("TTS worker playing: {}", message_to_play); - - m_tts_service.say_text_and_play(message_to_play); - } - } - spdlog::info("TTS worker thread stopped."); +nlohmann::json AlarmService::getActiveAlarmsJson() +{ + auto alarms = DataStorage::getInstance().getActiveAlarms(); + json j_alarms = json::array(); + for (const auto &alarm : alarms) + { + j_alarms.push_back(AlarmEvent::toJson(alarm)); + } + return j_alarms; } -void AlarmService::schedule_tts(const std::string &text) { - if (text.empty() || !m_tts_running) - return; - { - std::lock_guard lock(m_tts_queue_mutex); - m_tts_queue.push(text); - } - m_tts_cv.notify_one(); -} - -nlohmann::json AlarmService::getActiveAlarmsJson() { - auto alarms = DataStorage::getInstance().getActiveAlarms(); - json j_alarms = json::array(); - for (const auto &alarm : alarms) { - j_alarms.push_back(AlarmEvent::toJson(alarm)); - } - return j_alarms; -} - -nlohmann::json AlarmService::getAlarmHistoryJson(int limit) { - auto alarms = DataStorage::getInstance().getAlarmHistory(limit); - json j_alarms = json::array(); - for (const auto &alarm : alarms) { - j_alarms.push_back(AlarmEvent::toJson(alarm)); - } - return j_alarms; +nlohmann::json AlarmService::getAlarmHistoryJson(int limit) +{ + auto alarms = DataStorage::getInstance().getAlarmHistory(limit); + json j_alarms = json::array(); + for (const auto &alarm : alarms) + { + j_alarms.push_back(AlarmEvent::toJson(alarm)); + } + return j_alarms; } bool AlarmService::manually_clear_alarm(const std::string &rule_id, - const std::string &device_id) { + const std::string &device_id) +{ - { + { - auto it = m_alarm_states.find(rule_id); - if (it != m_alarm_states.end()) { + auto it = m_alarm_states.find(rule_id); + if (it != m_alarm_states.end()) + { - AlarmState &state = it->second; - state.current_state = AlarmStateType::NORMAL; - state.debounce_timer.cancel(); - } else { - spdlog::warn("Manually clearing alarm for deleted/missing rule '{}'. " - "Cleaning up DB record only.", - rule_id); - } - } + AlarmState &state = it->second; + state.current_state = AlarmStateType::NORMAL; + state.debounce_timer.cancel(); + } + else + { + spdlog::warn("Manually clearing alarm for deleted/missing rule '{}'. " + "Cleaning up DB record only.", + rule_id); + } + } - spdlog::info("[ALARM MANUALLY CLEARED] (Rule: {}) for Device: {}", rule_id, - device_id); + spdlog::info("[ALARM MANUALLY CLEARED] (Rule: {}) for Device: {}", rule_id, + device_id); - AlarmEvent event; - event.rule_id = rule_id; - event.device_id = device_id; - event.status = AlarmEventStatus::CLEARED; - event.message = "Alarm manually cleared by user."; - event.trigger_value = 0.0; - event.timestamp_ms = std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()) - .count(); + AlarmEvent event; + event.rule_id = rule_id; + event.device_id = device_id; + event.status = AlarmEventStatus::CLEARED; + event.message = "Alarm manually cleared by user."; + event.trigger_value = 0.0; + event.timestamp_ms = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); - event.level = "INFO"; - { - for (const auto &rule : m_rules) { - if (rule.rule_id == rule_id) { - event.level = AlarmRule::levelToString(rule.level); - break; - } - } - } + event.level = "INFO"; + { + for (const auto &rule : m_rules) + { + if (rule.rule_id == rule_id) + { + event.level = AlarmRule::levelToString(rule.level); + break; + } + } + } - if (!DataStorage::getInstance().storeAlarmEvent(event)) { - spdlog::error("Failed to store MANUALLY CLEARED alarm event for rule: {}", - rule_id); - return false; - } + if (!DataStorage::getInstance().storeAlarmEvent(event)) + { + spdlog::error("Failed to store MANUALLY CLEARED alarm event for rule: {}", + rule_id); + return false; + } - return true; + return true; } -std::string AlarmService::get_rules_as_json_string() { - std::lock_guard lock(m_rules_mutex); +std::string AlarmService::get_rules_as_json_string() +{ + std::lock_guard lock(m_rules_mutex); - if (m_rules_config_path.empty()) { - spdlog::error("Cannot get rules: config path not set."); - return "[]"; - } + if (m_rules_config_path.empty()) + { + spdlog::error("Cannot get rules: config path not set."); + return "[]"; + } - std::ifstream ifs(m_rules_config_path); - if (!ifs.is_open()) { - spdlog::error("Failed to open alarm rules file for reading: {}", - m_rules_config_path); - return "[]"; - } + std::ifstream ifs(m_rules_config_path); + if (!ifs.is_open()) + { + spdlog::error("Failed to open alarm rules file for reading: {}", + m_rules_config_path); + return "[]"; + } - std::stringstream buffer; - buffer << ifs.rdbuf(); - return buffer.str(); + std::stringstream buffer; + buffer << ifs.rdbuf(); + return buffer.str(); } bool AlarmService::parse_rules_from_content( - const std::string &json_content, std::vector &out_rules, - std::map &out_states) { + const std::string &json_content, std::vector &out_rules, + std::map &out_states) +{ - try { - json j_rules = json::parse(json_content); + try + { + json j_rules = json::parse(json_content); - if (!j_rules.is_array()) { - spdlog::error("Failed to parse rules: JSON content is not an array."); - return false; - } + if (!j_rules.is_array()) + { + spdlog::error("Failed to parse rules: JSON content is not an array."); + return false; + } - out_rules.clear(); - out_states.clear(); + out_rules.clear(); + out_states.clear(); - for (const auto &j_rule : j_rules) { - AlarmRule rule; - rule.rule_id = j_rule.at("rule_id"); - rule.device_id = j_rule.at("device_id"); - rule.data_point_name = j_rule.at("data_point_name"); - rule.compare_type = - AlarmRule::stringToCompareType(j_rule.at("compare_type")); - rule.threshold = j_rule.at("threshold"); - rule.level = AlarmRule::stringToLevel(j_rule.at("level")); - rule.message_template = j_rule.at("message_template"); - rule.debounce_seconds = j_rule.value("debounce_seconds", 0); - rule.alarm_mqtt_topic = j_rule.value("alarm_mqtt_topic", ""); - rule.clear_message_template = j_rule.value("clear_message_template", ""); + for (const auto &j_rule : j_rules) + { + AlarmRule rule; + rule.rule_id = j_rule.at("rule_id"); + rule.device_id = j_rule.at("device_id"); + rule.data_point_name = j_rule.at("data_point_name"); + rule.compare_type = + AlarmRule::stringToCompareType(j_rule.at("compare_type")); + rule.threshold = j_rule.at("threshold"); + rule.level = AlarmRule::stringToLevel(j_rule.at("level")); + rule.message_template = j_rule.at("message_template"); + rule.debounce_seconds = j_rule.value("debounce_seconds", 0); + rule.alarm_mqtt_topic = j_rule.value("alarm_mqtt_topic", ""); + rule.clear_message_template = j_rule.value("clear_message_template", ""); - if (rule.compare_type == CompareType::UNKNOWN) { - spdlog::warn("Skipping rule '{}': Unknown compare_type.", rule.rule_id); - continue; - } + if (rule.compare_type == CompareType::UNKNOWN) + { + spdlog::warn("Skipping rule '{}': Unknown compare_type.", rule.rule_id); + continue; + } - out_rules.push_back(std::move(rule)); + out_rules.push_back(std::move(rule)); - out_states.emplace(std::piecewise_construct, - std::forward_as_tuple(rule.rule_id), - std::forward_as_tuple(m_io_context)); - } - } catch (const json::exception &e) { - spdlog::error("Failed to parse alarm rules content: {}", e.what()); - return false; - } + out_states.emplace(std::piecewise_construct, + std::forward_as_tuple(rule.rule_id), + std::forward_as_tuple(m_io_context)); + } + } + catch (const json::exception &e) + { + spdlog::error("Failed to parse alarm rules content: {}", e.what()); + return false; + } - return true; + return true; } bool AlarmService::parse_rules_from_file( - const std::string &config_path, std::vector &out_rules, - std::map &out_states) { + const std::string &config_path, std::vector &out_rules, + std::map &out_states) +{ - std::ifstream ifs(config_path); - if (!ifs.is_open()) { - spdlog::error("Failed to open alarm rules file: {}", config_path); - return false; - } + std::ifstream ifs(config_path); + if (!ifs.is_open()) + { + spdlog::error("Failed to open alarm rules file: {}", config_path); + return false; + } - std::stringstream buffer; - buffer << ifs.rdbuf(); - std::string file_content = buffer.str(); + std::stringstream buffer; + buffer << ifs.rdbuf(); + std::string file_content = buffer.str(); - return parse_rules_from_content(file_content, out_rules, out_states); + return parse_rules_from_content(file_content, out_rules, out_states); } bool AlarmService::save_rules_from_json_string( - const std::string &json_content) { + const std::string &json_content) +{ - std::vector dummy_rules; - std::map dummy_states; + std::vector dummy_rules; + std::map dummy_states; - if (!parse_rules_from_content(json_content, dummy_rules, dummy_states)) { - spdlog::warn("Failed to save rules: Invalid content or schema."); - return false; - } + if (!parse_rules_from_content(json_content, dummy_rules, dummy_states)) + { + spdlog::warn("Failed to save rules: Invalid content or schema."); + return false; + } - std::lock_guard lock(m_rules_mutex); + std::lock_guard lock(m_rules_mutex); - if (m_rules_config_path.empty()) { - spdlog::error("Cannot save rules: config path not set."); - return false; - } + if (m_rules_config_path.empty()) + { + spdlog::error("Cannot save rules: config path not set."); + return false; + } - std::ofstream ofs(m_rules_config_path, std::ios::trunc); - if (!ofs.is_open()) { - spdlog::error("Failed to open alarm rules file for writing: {}", - m_rules_config_path); - return false; - } + std::ofstream ofs(m_rules_config_path, std::ios::trunc); + if (!ofs.is_open()) + { + spdlog::error("Failed to open alarm rules file for writing: {}", + m_rules_config_path); + return false; + } - ofs << json_content; - ofs.close(); + ofs << json_content; + ofs.close(); - spdlog::info( - "Successfully saved new alarm rules to {}. (Reload is required to apply)", - m_rules_config_path); - return true; -} \ No newline at end of file + spdlog::info( + "Successfully saved new alarm rules to {}. (Reload is required to apply)", + m_rules_config_path); + return true; +} diff --git a/src/alarm/alarm_service.h b/src/alarm/alarm_service.h index 838950b..5dc6f08 100644 --- a/src/alarm/alarm_service.h +++ b/src/alarm/alarm_service.h @@ -15,86 +15,77 @@ #include "alarm_event.h" #include "mqtt/mqtt_client.h" #include "spdlog/spdlog.h" -#include "tts/piper_tts_interface.h" using json = nlohmann::json; -class AlarmService { +class AlarmService +{ public: - AlarmService(boost::asio::io_context &io, PiperTTSInterface &tts_service, - MqttClient &mqtt_client); + AlarmService(boost::asio::io_context &io, MqttClient &mqtt_client); - ~AlarmService(); + ~AlarmService(); - void stop(); + void stop(); - bool load_rules(const std::string &config_path); - bool reload_rules(); + bool load_rules(const std::string &config_path); + bool reload_rules(); - void process_device_data(const std::string &device_id, - const std::string &data_json); + void process_device_data(const std::string &device_id, + const std::string &data_json); - void process_system_data(const std::string &system_data_json); + void process_system_data(const std::string &system_data_json); - nlohmann::json getActiveAlarmsJson(); - nlohmann::json getAlarmHistoryJson(int limit); + nlohmann::json getActiveAlarmsJson(); + nlohmann::json getAlarmHistoryJson(int limit); - /** - * @brief [新增] 手动清除一个当前激活的告警 - * @param rule_id 要清除的规则IDparse_rules_from_fil - * @param device_id 触发该规则的设备ID - * @return 成功则返回 true - */ - bool manually_clear_alarm(const std::string &rule_id, - const std::string &device_id); + /** + * @brief [新增] 手动清除一个当前激活的告警 + * @param rule_id 要清除的规则IDparse_rules_from_fil + * @param device_id 触发该规则的设备ID + * @return 成功则返回 true + */ + bool manually_clear_alarm(const std::string &rule_id, + const std::string &device_id); - std::string get_rules_as_json_string(); + std::string get_rules_as_json_string(); - /** - * @brief [新增] 从 JSON 字符串内容中解析和校验规则 - * 这是所有解析器的核心 - */ - bool parse_rules_from_content(const std::string &json_content, - std::vector &out_rules, - std::map &out_states); + /** + * @brief [新增] 从 JSON 字符串内容中解析和校验规则 + * 这是所有解析器的核心 + */ + bool parse_rules_from_content(const std::string &json_content, + std::vector &out_rules, + std::map &out_states); - /** - * @brief [新增] 验证并保存告警规则到文件 - * @param json_content 包含新规则的完整JSON字符串 - * @return 成功 (JSON有效且写入成功) 则返回 true - */ - bool save_rules_from_json_string(const std::string &json_content); + /** + * @brief [新增] 验证并保存告警规则到文件 + * @param json_content 包含新规则的完整JSON字符串 + * @return 成功 (JSON有效且写入成功) 则返回 true + */ + bool save_rules_from_json_string(const std::string &json_content); private: - void check_rule_against_value(AlarmRule &rule, double value, - const std::string &actual_device_id); - std::string format_message(const AlarmRule &rule, - const std::string &template_str, double value, - const std::string &actual_device_id); - void trigger_alarm_action(AlarmRule &rule, double value, - const std::string &actual_device_id); - void clear_alarm(AlarmRule &rule, double value, - const std::string &actual_device_id); - void tts_worker(); - void schedule_tts(const std::string &text); - bool parse_rules_from_file(const std::string &config_path, - std::vector &out_rules, - std::map &out_states); + void check_rule_against_value(AlarmRule &rule, double value, + const std::string &actual_device_id); + std::string format_message(const AlarmRule &rule, + const std::string &template_str, double value, + const std::string &actual_device_id); + void trigger_alarm_action(AlarmRule &rule, double value, + const std::string &actual_device_id); + void clear_alarm(AlarmRule &rule, double value, + const std::string &actual_device_id); + void tts_worker(); + void schedule_tts(const std::string &text); + bool parse_rules_from_file(const std::string &config_path, + std::vector &out_rules, + std::map &out_states); - boost::asio::io_context &m_io_context; - PiperTTSInterface &m_tts_service; - MqttClient &m_mqtt_client; + boost::asio::io_context &m_io_context; + MqttClient &m_mqtt_client; - std::vector m_rules; - std::map m_alarm_states; + std::vector m_rules; + std::map m_alarm_states; - std::string m_rules_config_path; - std::mutex m_rules_mutex; - - // TTS 播报队列 - std::mutex m_tts_queue_mutex; - std::condition_variable m_tts_cv; - std::queue m_tts_queue; - std::thread m_tts_thread; - bool m_tts_running; -}; \ No newline at end of file + std::string m_rules_config_path; + std::mutex m_rules_mutex; +}; diff --git a/src/algorithm/HumanDetectionModule.cc b/src/algorithm/HumanDetectionModule.cc deleted file mode 100644 index e85e19f..0000000 --- a/src/algorithm/HumanDetectionModule.cc +++ /dev/null @@ -1,251 +0,0 @@ -// src/algorithm/HumanDetectionModule.cc -#include "algorithm/HumanDetectionModule.h" -#include "opencv2/imgproc/imgproc.hpp" -#include "spdlog/spdlog.h" -#include - -#ifndef OBJ_NUMB_MAX_SIZE -#define OBJ_NUMB_MAX_SIZE 64 -#endif - -HumanDetectionModule::HumanDetectionModule(std::string model_path, - int thread_num, - cv::Rect intrusion_zone, - double intrusion_time_threshold) - : model_path_(model_path), thread_num_(thread_num), - intrusion_zone_(intrusion_zone), - intrusion_time_threshold_(intrusion_time_threshold), next_track_id_(1) { - spdlog::info("[HumanDetectionModule] Created. Model: {}, Threads: {}", - model_path_, thread_num_); - - if (intrusion_zone_.width <= 0 || intrusion_zone_.height <= 0) { - spdlog::warn("[HumanDetectionModule] Warning: Intrusion zone is invalid " - "(0,0,0,0). It will be set to default at runtime."); - } -} - -bool HumanDetectionModule::init(const nlohmann::json &module_config) { - - std::string label_path = module_config.value( - "label_path", "/app/edge-proxy/models/coco_80_labels_list.txt"); - int class_num = module_config.value("class_num", 80); - - // --- 核心修改:实例化 rkYolov8 类型的线程池 --- - rknn_pool_ = - std::make_unique>( - model_path_.c_str(), thread_num_, label_path, class_num); - - if (rknn_pool_->init() != 0) { - spdlog::error("[HumanDetectionModule] rknnPool init fail!"); - return false; - } - spdlog::info("[HumanDetectionModule] rknnPool init success (YOLOv8 + RGA)."); - return true; -} - -bool HumanDetectionModule::process(cv::Mat &frame) { - if (frame.empty()) { - return false; - } - - - if (rknn_pool_->put(frame) != 0) { - spdlog::error("[HumanDetectionModule] Failed to put frame into rknnPool."); - return false; - } - - detect_result_group_t detection_results; - if (rknn_pool_->get(detection_results) != 0) { - spdlog::error("[HumanDetectionModule] Failed to get frame from rknnPool."); - return false; - } - - // 更新追踪器逻辑 - this->update_tracker(detection_results, frame.size()); - - // 绘制结果 (直接在 frame 上绘制,因为坐标已经对应原图) - this->draw_results(frame); - - return true; -} - -void HumanDetectionModule::trigger_alarm(int person_id, const cv::Rect &box) { - // 简单的日志报警,实际可对接 MQTT - spdlog::warn("[ALARM] Human Intrusion! ID: {} at [{}, {}, {} x {}]", - person_id, box.x, box.y, box.width, box.height); -} - -double HumanDetectionModule::get_current_time_seconds() { - return std::chrono::duration_cast>( - std::chrono::high_resolution_clock::now().time_since_epoch()) - .count(); -} - -void HumanDetectionModule::update_tracker( - detect_result_group_t &detect_result_group, const cv::Size &frame_size) { - // 初始化默认区域 - if (intrusion_zone_.width <= 0 || intrusion_zone_.height <= 0) { - intrusion_zone_ = cv::Rect(frame_size.width / 4, frame_size.height / 4, - frame_size.width / 2, frame_size.height / 2); - } - - // --- 1. 提取当前帧的检测框 --- - std::vector current_detections; - for (int i = 0; i < detect_result_group.count; i++) { - detect_result_t *det_result = &(detect_result_group.results[i]); - - // 兼容性检查:rkYolov8 可能返回 "person" (如果加载了label) 或 "0" - // (如果只返回ID) - std::string label_name(det_result->name); - bool is_person = (label_name == "person" || label_name == "0"); - - if (is_person) { - // --- 关键差异 --- - // rkYolov8 的 post_process 已经将坐标映射回了原图尺寸。 - // 所以这里不需要像 IntrusionModule 那样乘以 scale_x / scale_y。 - int left = det_result->box.left; - int top = det_result->box.top; - int right = det_result->box.right; - int bottom = det_result->box.bottom; - - // 边界保护 - left = std::max(0, std::min(left, frame_size.width - 1)); - top = std::max(0, std::min(top, frame_size.height - 1)); - right = std::max(left, std::min(right, frame_size.width)); - bottom = std::max(top, std::min(bottom, frame_size.height)); - - if (right > left && bottom > top) { - current_detections.push_back( - cv::Rect(left, top, right - left, bottom - top)); - } - } - } - - // --- 2. 简单的 IOU 追踪逻辑 (保持原有逻辑不变) --- - - // 增加所有现有追踪对象的未见帧数 - for (auto it = tracked_persons_.begin(); it != tracked_persons_.end(); ++it) { - it->second.frames_unseen++; - } - - std::vector matched_track_ids; - - for (const auto &det_box : current_detections) { - int best_match_id = -1; - double max_iou_threshold = 0.3; // IOU 匹配阈值 - double best_iou = 0.0; - - for (auto const &[id, person] : tracked_persons_) { - // 跳过已被当前帧其他检测框匹配过的 ID - bool already_matched = false; - for (int matched_id : matched_track_ids) { - if (id == matched_id) { - already_matched = true; - break; - } - } - if (already_matched) - continue; - - double iou = (double)(det_box & person.box).area() / - (double)(det_box | person.box).area(); - if (iou > best_iou && iou >= max_iou_threshold) { - best_iou = iou; - best_match_id = id; - } - } - - if (best_match_id != -1) { - // 匹配成功:更新位置,重置未见计数 - tracked_persons_[best_match_id].box = det_box; - tracked_persons_[best_match_id].frames_unseen = 0; - matched_track_ids.push_back(best_match_id); - } else { - // 新目标:创建新 ID - // 注意:这里假设 PersonTrackInfo 结构体在头文件中定义,且包含下列字段 - PersonTrackInfo new_person; - new_person.id = next_track_id_++; - new_person.box = det_box; - new_person.entry_time = 0; - new_person.is_in_zone = false; - new_person.alarm_triggered = false; - new_person.frames_unseen = 0; - tracked_persons_[new_person.id] = new_person; - } - } - - // --- 3. 区域入侵判定 --- - double current_time = get_current_time_seconds(); - for (auto it = tracked_persons_.begin(); it != tracked_persons_.end(); ++it) { - PersonTrackInfo &person = it->second; - - // 计算人框与区域的交集 - bool currently_in_zone = (intrusion_zone_ & person.box).area() > 0; - - if (currently_in_zone) { - if (!person.is_in_zone) { - // 刚进入区域 - person.is_in_zone = true; - person.entry_time = current_time; - person.alarm_triggered = false; - } else { - // 已经在区域内,检查时间阈值 - if (!person.alarm_triggered && - (current_time - person.entry_time) >= intrusion_time_threshold_) { - person.alarm_triggered = true; - trigger_alarm(person.id, person.box); - } - } - } else { - // 离开区域 - if (person.is_in_zone) { - person.is_in_zone = false; - person.entry_time = 0; - person.alarm_triggered = false; - } - } - } - - // --- 4. 清理消失的目标 --- - for (auto it = tracked_persons_.begin(); it != tracked_persons_.end(); - /* no inc */) { - if (it->second.frames_unseen > 50) { // 超过 50 帧未检测到则删除 - it = tracked_persons_.erase(it); - } else { - ++it; - } - } -} - -void HumanDetectionModule::draw_results(cv::Mat &frame) { - // 绘制报警区域 - cv::rectangle(frame, this->intrusion_zone_, cv::Scalar(255, 255, 0), 2); - - // 绘制每个追踪的人 - for (auto const &[id, person] : this->tracked_persons_) { - cv::Scalar box_color = - person.alarm_triggered ? cv::Scalar(0, 0, 255) : cv::Scalar(0, 255, 0); - int line_thickness = person.alarm_triggered ? 3 : 2; - - cv::rectangle(frame, person.box, box_color, line_thickness); - - std::string label = "ID: " + std::to_string(id); - if (person.is_in_zone) { - label += fmt::format(" ({:.1f}s)", - get_current_time_seconds() - person.entry_time); - } - - cv::putText(frame, label, cv::Point(person.box.x, person.box.y - 10), - cv::FONT_HERSHEY_SIMPLEX, 0.6, box_color, 2); - } -} - -void HumanDetectionModule::stop() { - spdlog::info("Stopping HumanDetectionModule internal threads..."); - - if (rknn_pool_) { - rknn_pool_->stop(); - } - - spdlog::info("HumanDetectionModule stopped."); -} \ No newline at end of file diff --git a/src/algorithm/HumanDetectionModule.h b/src/algorithm/HumanDetectionModule.h deleted file mode 100644 index 2fe46bf..0000000 --- a/src/algorithm/HumanDetectionModule.h +++ /dev/null @@ -1,55 +0,0 @@ -#pragma once - -#include "IAnalysisModule.h" -#include "rknn/postprocess.h" -#include "rknn/rkYolov8.hpp" -#include "rknn/rknnPool.hpp" -#include -#include -#include -#include -#include -#include - -struct PersonTrackInfo { - int id; - cv::Rect box; - double entry_time; - bool is_in_zone; - bool alarm_triggered; - int frames_unseen; -}; - -class HumanDetectionModule : public IAnalysisModule { -public: - /** - * @brief 构造函数 - */ - HumanDetectionModule(std::string model_path, int thread_num, - cv::Rect intrusion_zone, - double intrusion_time_threshold); - - virtual ~HumanDetectionModule() = default; - - virtual bool init(const nlohmann::json &module_config) override; - virtual bool process(cv::Mat &frame) override; - virtual void stop() override; - -private: - void update_tracker(detect_result_group_t &detect_result_group, - const cv::Size &frame_size); - void draw_results(cv::Mat &frame); - void trigger_alarm(int person_id, const cv::Rect &box); - double get_current_time_seconds(); - - std::string model_path_; - int thread_num_; - - std::unique_ptr> - rknn_pool_; - - cv::Rect intrusion_zone_; - std::map tracked_persons_; - int next_track_id_; - double intrusion_time_threshold_; -}; \ No newline at end of file diff --git a/src/algorithm/IAnalysisModule.h b/src/algorithm/IAnalysisModule.h deleted file mode 100644 index bffc1eb..0000000 --- a/src/algorithm/IAnalysisModule.h +++ /dev/null @@ -1,30 +0,0 @@ -// IAnalysisModule.h -#pragma once - -#include "nlohmann/json.hpp" -#include -#include - -/** - * @brief AI分析模块的抽象基类(接口) - * - * 定义了所有AI视频分析模块(如入侵检测、人脸识别)的统一契约。 - * VideoService 将通过这个接口与具体的AI模块交互。 - */ -class IAnalysisModule { -public: - virtual ~IAnalysisModule() = default; - - /** - * @brief 初始化模块 (例如:加载模型) - */ - virtual bool init(const nlohmann::json &module_config) = 0; - - /** - * @brief 处理单帧视频 (例如:推理、绘制) - * @param frame [in/out] 传入原始帧,模块应在此帧上直接绘制结果。 - */ - virtual bool process(cv::Mat &frame) = 0; - - virtual void stop() = 0; -}; \ No newline at end of file diff --git a/src/algorithm/IntrusionModule.cc b/src/algorithm/IntrusionModule.cc deleted file mode 100644 index 3b53295..0000000 --- a/src/algorithm/IntrusionModule.cc +++ /dev/null @@ -1,230 +0,0 @@ -// IntrusionModule.cc -#include "IntrusionModule.h" -#include "opencv2/imgproc/imgproc.hpp" -#include "spdlog/spdlog.h" -#include - -IntrusionModule::IntrusionModule(std::string model_path, int thread_num, - cv::Rect intrusion_zone, - double intrusion_time_threshold) - : model_path_(model_path), thread_num_(thread_num), - intrusion_zone_(intrusion_zone), - intrusion_time_threshold_(intrusion_time_threshold), next_track_id_(1) // -{ - spdlog::info("[IntrusionModule] Created. Model: {}, Threads: {}", model_path_, - thread_num_); - if (intrusion_zone_.width <= 0 || intrusion_zone_.height <= 0) { - spdlog::warn("[IntrusionModule] Warning: Intrusion zone is invalid " - "(0,0,0,0). It will be set at runtime."); - } -} - -bool IntrusionModule::init(const nlohmann::json &module_config) { - - std::string label_path = module_config.value( - "label_path", "/app/edge-proxy/models/coco_80_labels_list.txt"); - int class_num = module_config.value("class_num", 80); - - rknn_pool_ = - std::make_unique>( - model_path_.c_str(), thread_num_, label_path, class_num); - - if (rknn_pool_->init() != 0) { - spdlog::error("[IntrusionModule] rknnPool init fail!"); - return false; - } - spdlog::info("[IntrusionModule] rknnPool init success."); - return true; -} - -bool IntrusionModule::process(cv::Mat &frame) { - if (frame.empty()) { - return false; - } - - cv::Mat model_input_image; - cv::resize(frame, model_input_image, cv::Size(640, 640)); - if (!model_input_image.isContinuous()) { - model_input_image = model_input_image.clone(); - } - - if (rknn_pool_->put(model_input_image) != 0) { - spdlog::error("[IntrusionModule] Failed to put frame into rknnPool."); - return false; - } - - detect_result_group_t detection_results; - if (rknn_pool_->get(detection_results) != 0) { - spdlog::error("[IntrusionModule] Failed to get frame from rknnPool."); - return false; - } - - this->update_tracker(detection_results, frame.size()); - - this->draw_results(frame); // 直接在传入的 frame 上绘制 - if (!frame.isContinuous()) { - frame = frame.clone(); - } - - return true; -} - -void IntrusionModule::trigger_alarm(int person_id, const cv::Rect &box) { - printf("[ALARM] Intrusion detected! Person ID: %d at location (%d, %d, %d, " - "%d)\n", - person_id, box.x, box.y, box.width, box.height); - // TODO: 在这里实现真正的报警逻辑,例如发送网络消息、写入数据库等。 -} - -double IntrusionModule::get_current_time_seconds() { - return std::chrono::duration_cast>( - std::chrono::high_resolution_clock::now().time_since_epoch()) - .count(); -} - -void IntrusionModule::update_tracker(detect_result_group_t &detect_result_group, - const cv::Size &frame_size) { - if (intrusion_zone_.width <= 0 || intrusion_zone_.height <= 0) { - intrusion_zone_ = cv::Rect(frame_size.width / 4, frame_size.height / 4, - frame_size.width / 2, frame_size.height / 2); - } - - const float model_input_width = 640.0f; - const float model_input_height = 640.0f; - float scale_x = (float)frame_size.width / model_input_width; - float scale_y = (float)frame_size.height / model_input_height; - - std::vector current_detections; - for (int i = 0; i < detect_result_group.count; i++) { - detect_result_t *det_result = &(detect_result_group.results[i]); - if (strcmp(det_result->name, "person") == 0) { - int original_left = static_cast(det_result->box.left * scale_x); - int original_top = static_cast(det_result->box.top * scale_y); - int original_right = static_cast(det_result->box.right * scale_x); - int original_bottom = static_cast(det_result->box.bottom * scale_y); - - original_left = - std::max(0, std::min(original_left, frame_size.width - 1)); - original_top = std::max(0, std::min(original_top, frame_size.height - 1)); - original_right = - std::max(original_left, std::min(original_right, frame_size.width)); - original_bottom = - std::max(original_top, std::min(original_bottom, frame_size.height)); - - if (original_right > original_left && original_bottom > original_top) { - current_detections.push_back(cv::Rect(original_left, original_top, - original_right - original_left, - original_bottom - original_top)); - } - } - } - - for (auto it = tracked_persons_.begin(); it != tracked_persons_.end(); ++it) { - it->second.frames_unseen++; - } - - std::vector matched_track_ids; - - for (const auto &det_box : current_detections) { - int best_match_id = -1; - double max_iou_threshold = 0.3; - double best_iou = 0.0; - - for (auto const &[id, person] : tracked_persons_) { - bool already_matched = false; - for (int matched_id : matched_track_ids) { - if (id == matched_id) { - already_matched = true; - break; - } - } - if (already_matched) { - continue; - } - - double iou = (double)(det_box & person.box).area() / - (double)(det_box | person.box).area(); - if (iou > best_iou && iou >= max_iou_threshold) { - best_iou = iou; - best_match_id = id; - } - } - - if (best_match_id != -1) { - tracked_persons_[best_match_id].box = det_box; - tracked_persons_[best_match_id].frames_unseen = 0; - matched_track_ids.push_back(best_match_id); - } else { - TrackedPerson new_person; - new_person.id = next_track_id_++; - new_person.box = det_box; - new_person.entry_time = 0; - new_person.is_in_zone = false; - new_person.alarm_triggered = false; - new_person.frames_unseen = 0; - tracked_persons_[new_person.id] = new_person; - } - } - - double current_time = get_current_time_seconds(); - for (auto it = tracked_persons_.begin(); it != tracked_persons_.end(); ++it) { - TrackedPerson &person = it->second; - bool currently_in_zone = (intrusion_zone_ & person.box).area() > 0; - - if (currently_in_zone) { - if (!person.is_in_zone) { - person.is_in_zone = true; - person.entry_time = current_time; - person.alarm_triggered = false; - } else { - if (!person.alarm_triggered && - (current_time - person.entry_time) >= intrusion_time_threshold_) { - person.alarm_triggered = true; - trigger_alarm(person.id, person.box); - } - } - } else { - if (person.is_in_zone) { - person.is_in_zone = false; - person.entry_time = 0; - person.alarm_triggered = false; - } - } - } - - for (auto it = tracked_persons_.begin(); it != tracked_persons_.end(); - /* 无自增 */) { - if (it->second.frames_unseen > 50) { - it = tracked_persons_.erase(it); - } else { - ++it; - } - } -} - -void IntrusionModule::draw_results(cv::Mat &frame) { - cv::rectangle(frame, this->intrusion_zone_, cv::Scalar(255, 255, 0), 2); - - for (auto const &[id, person] : this->tracked_persons_) { - cv::Scalar box_color = - person.alarm_triggered ? cv::Scalar(0, 0, 255) : cv::Scalar(0, 255, 0); - int line_thickness = person.alarm_triggered ? 3 : 2; - - cv::rectangle(frame, person.box, box_color, line_thickness); - std::string label = "Person " + std::to_string(id); - if (person.is_in_zone) { - label += " (In Zone)"; - } - cv::putText(frame, label, cv::Point(person.box.x, person.box.y - 10), - cv::FONT_HERSHEY_SIMPLEX, 0.5, box_color, 2); - } -} -void IntrusionModule::stop() { - spdlog::info("Stopping IntrusionModule internal threads..."); - - if (rknn_pool_) { - rknn_pool_->stop(); // <-- 这会阻塞并等待 rknnPool 的所有线程退出 - } - - spdlog::info("IntrusionModule stopped."); -} \ No newline at end of file diff --git a/src/algorithm/IntrusionModule.h b/src/algorithm/IntrusionModule.h deleted file mode 100644 index c4531fb..0000000 --- a/src/algorithm/IntrusionModule.h +++ /dev/null @@ -1,58 +0,0 @@ -// IntrusionModule.h -#pragma once - -#include "IAnalysisModule.h" -#include "rknn/postprocess.h" -#include "rknn/rkYolov5s.hpp" -#include "rknn/rknnPool.hpp" -#include -#include -#include -#include -#include -#include - -struct TrackedPerson { - int id; - cv::Rect box; - double entry_time; - bool is_in_zone; - bool alarm_triggered; - int frames_unseen; -}; - -class IntrusionModule : public IAnalysisModule { -public: - /** - * @brief 构造入侵检测模块 - * @param model_path rknn模型文件路径 - * @param thread_num rknn线程池数量 - * @param intrusion_zone 报警区域 - * @param intrusion_time_threshold 触发报警的时间阈值(秒) - */ - IntrusionModule(std::string model_path, int thread_num, - cv::Rect intrusion_zone, double intrusion_time_threshold); - - virtual ~IntrusionModule() = default; - - virtual bool init(const nlohmann::json &module_config) override; - virtual bool process(cv::Mat &frame) override; - virtual void stop() override; - -private: - void update_tracker(detect_result_group_t &detect_result_group, - const cv::Size &frame_size); - void draw_results(cv::Mat &frame); - void trigger_alarm(int person_id, const cv::Rect &box); - double get_current_time_seconds(); - - std::string model_path_; - int thread_num_; - std::unique_ptr> - rknn_pool_; - - cv::Rect intrusion_zone_; - std::map tracked_persons_; - int next_track_id_; - double intrusion_time_threshold_; -}; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 3037cce..a8cd6f0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,4 +1,4 @@ -// main.cpp (修改后) + #include #include #include @@ -19,244 +19,216 @@ #include "nlohmann/json.hpp" #include "spdlog/spdlog.h" #include "systemMonitor/system_monitor.h" -#include "tts/piper_tts_interface.h" -#include "videoServiceManager/video_service_manager.h" #include "web/web_server.h" -// [新增] 包含 postprocess.h 以便调用 deinitPostProcess -#include "rknn/postprocess.h" - boost::asio::io_context g_io_context; -/** - * @brief 周期性轮询系统状态并发布到 MQTT - */ void poll_system_metrics(boost::asio::steady_timer &timer, - SystemMonitor::SystemMonitor &monitor, - MqttClient &mqtt_client, AlarmService &alarm_service) { - // ... (此函数保持不变) ... - if (g_io_context.stopped()) - return; - auto cpu_util = monitor.getCpuUtilization(); - auto mem_info = monitor.getMemoryInfo(); - double mem_total_gb = mem_info.total_kb / 1024.0 / 1024.0; + SystemMonitor::SystemMonitor &monitor, + MqttClient &mqtt_client, AlarmService &alarm_service) +{ + if (g_io_context.stopped()) + return; + auto cpu_util = monitor.getCpuUtilization(); + auto mem_info = monitor.getMemoryInfo(); + double mem_total_gb = mem_info.total_kb / 1024.0 / 1024.0; - double mem_usage_percentage = - (mem_info.total_kb > 0) - ? (100.0 * (mem_info.total_kb - mem_info.available_kb) / - mem_info.total_kb) - : 0.0; + double mem_usage_percentage = + (mem_info.total_kb > 0) + ? (100.0 * (mem_info.total_kb - mem_info.available_kb) / + mem_info.total_kb) + : 0.0; - auto thermalInfoString = monitor.getChipTemperature(); + auto thermalInfoString = monitor.getChipTemperature(); - std::string topic = "proxy/system_status"; - std::string payload; + std::string topic = "proxy/system_status"; + std::string payload; - try { - nlohmann::json payload_json; - payload_json["cpu_usage"] = cpu_util.totalUsagePercentage; - payload_json["mem_total_gb"] = mem_total_gb; - payload_json["mem_usage_percentage"] = mem_usage_percentage; - payload_json["thermal_info"] = nlohmann::json::parse(thermalInfoString); + try + { + nlohmann::json payload_json; + payload_json["cpu_usage"] = cpu_util.totalUsagePercentage; + payload_json["mem_total_gb"] = mem_total_gb; + payload_json["mem_usage_percentage"] = mem_usage_percentage; + payload_json["thermal_info"] = nlohmann::json::parse(thermalInfoString); - payload = payload_json.dump(); + payload = payload_json.dump(); + } + catch (const nlohmann::json::parse_error &e) + { + spdlog::error("Failed to parse thermalInfo JSON: {}. Sending partial data.", + e.what()); + nlohmann::json fallback_json; + fallback_json["cpu_usage"] = cpu_util.totalUsagePercentage; + fallback_json["mem_total_gb"] = mem_total_gb; + fallback_json["mem_usage_percentage"] = mem_usage_percentage; + fallback_json["thermal_info_error"] = "parsing_failed"; + fallback_json["raw_thermal_info"] = thermalInfoString; - } catch (const nlohmann::json::parse_error &e) { - spdlog::error("Failed to parse thermalInfo JSON: {}. Sending partial data.", - e.what()); - nlohmann::json fallback_json; - fallback_json["cpu_usage"] = cpu_util.totalUsagePercentage; - fallback_json["mem_total_gb"] = mem_total_gb; - fallback_json["mem_usage_percentage"] = mem_usage_percentage; - fallback_json["thermal_info_error"] = "parsing_failed"; - fallback_json["raw_thermal_info"] = thermalInfoString; + payload = fallback_json.dump(); + } - payload = fallback_json.dump(); - } + alarm_service.process_system_data(payload); - alarm_service.process_system_data(payload); + mqtt_client.publish(topic, payload); + spdlog::debug("System metrics published."); - mqtt_client.publish(topic, payload); - spdlog::debug("System metrics published."); - - timer.expires_at(timer.expiry() + std::chrono::seconds(15)); - timer.async_wait(std::bind(poll_system_metrics, std::ref(timer), - std::ref(monitor), std::ref(mqtt_client), - std::ref(alarm_service))); + timer.expires_at(timer.expiry() + std::chrono::seconds(15)); + timer.async_wait(std::bind(poll_system_metrics, std::ref(timer), + std::ref(monitor), std::ref(mqtt_client), + std::ref(alarm_service))); } -int main(int argc, char *argv[]) { - const std::string config_path = "/app/config/config.json"; - if (!ConfigManager::getInstance().load(config_path)) { - std::cerr << "Failed to load configuration from " << config_path - << ". Running with defaults, but this may cause issues." - << std::endl; - } +int main(int argc, char *argv[]) +{ + const std::string config_path = "/app/config/config.json"; + if (!ConfigManager::getInstance().load(config_path)) + { + std::cerr << "Failed to load configuration from " << config_path + << ". Running with defaults, but this may cause issues." + << std::endl; + } - auto &config = ConfigManager::getInstance(); + auto &config = ConfigManager::getInstance(); - try { - spdlog::set_level(spdlog::level::from_str(config.getLogLevel())); - spdlog::info("Edge Proxy starting up..."); - } catch (const spdlog::spdlog_ex &ex) { - std::cerr << "Log initialization failed: " << ex.what() << std::endl; - return 1; - } + try + { + spdlog::set_level(spdlog::level::from_str(config.getLogLevel())); + spdlog::info("Edge Proxy starting up..."); + } + catch (const spdlog::spdlog_ex &ex) + { + std::cerr << "Log initialization failed: " << ex.what() << std::endl; + return 1; + } - spdlog::info("Initializing Data Storage..."); - auto &data_storage = DataStorage::getInstance(); - if (!data_storage.initialize(config.getDataStorageDbPath())) { - spdlog::critical("Failed to initialize DataStorage. Exiting."); - return 1; - } + spdlog::info("Initializing Data Storage..."); + auto &data_storage = DataStorage::getInstance(); + if (!data_storage.initialize(config.getDataStorageDbPath())) + { + spdlog::critical("Failed to initialize DataStorage. Exiting."); + return 1; + } - try { - spdlog::info("Initializing Video Service..."); - VideoServiceManager video_manager; + try + { + spdlog::info("Initializing Video Service..."); - // ... (DataCache, MqttClient, TTSService, AlarmService ... 初始化保持不变) - // ... - DataCache data_cache; - LiveDataCache live_data_cache; - MqttClient mqtt_client(config.getMqttBroker(), config.getMqttClientID()); + DataCache data_cache; + LiveDataCache live_data_cache; + MqttClient mqtt_client(config.getMqttBroker(), config.getMqttClientID()); - PiperTTSInterface tts_service(config.getPiperExecutablePath(), - config.getPiperModelPath()); + AlarmService alarm_service(g_io_context, mqtt_client); - AlarmService alarm_service(g_io_context, tts_service, mqtt_client); + if (!alarm_service.load_rules(config.getAlarmRulesPath())) + { + spdlog::error("Failed to load alarm rules. Alarms may be disabled."); + } - if (!alarm_service.load_rules(config.getAlarmRulesPath())) { - spdlog::error("Failed to load alarm rules. Alarms may be disabled."); - } + auto report_to_mqtt = [&](const UnifiedData &data) + { + if (data_storage.storeProcessedData(data)) + { + spdlog::debug("Successfully stored PROCESSED data for device '{}'", + data.device_id); + } + else + { + spdlog::error("Failed to store PROCESSED data for device '{}'", + data.device_id); + } - // ... (report_to_mqtt, DeviceManager, MqttRouter ... 初始化保持不变) ... - auto report_to_mqtt = [&](const UnifiedData &data) { - if (data_storage.storeProcessedData(data)) { - spdlog::debug("Successfully stored PROCESSED data for device '{}'", - data.device_id); - } else { - spdlog::error("Failed to store PROCESSED data for device '{}'", - data.device_id); - } + live_data_cache.update_data(data.device_id, data.data_json); - live_data_cache.update_data(data.device_id, data.data_json); + alarm_service.process_device_data(data.device_id, data.data_json); - alarm_service.process_device_data(data.device_id, data.data_json); + if (mqtt_client.is_connected()) + { + std::string topic = "devices/" + data.device_id + "/data"; + g_io_context.post([&, topic, payload = data.data_json]() + { mqtt_client.publish(topic, payload, 1, false); }); + } + else + { + spdlog::warn("MQTT disconnected. Caching data for device '{}'.", + data.device_id); + data_cache.add(data); + } + }; - if (mqtt_client.is_connected()) { - std::string topic = "devices/" + data.device_id + "/data"; - g_io_context.post([&, topic, payload = data.data_json]() { - mqtt_client.publish(topic, payload, 1, false); - }); - } else { - spdlog::warn("MQTT disconnected. Caching data for device '{}'.", - data.device_id); - data_cache.add(data); - } - }; + DeviceManager device_manager(g_io_context, report_to_mqtt); + MqttRouter mqtt_router(mqtt_client, device_manager); - DeviceManager device_manager(g_io_context, report_to_mqtt); - MqttRouter mqtt_router(mqtt_client, device_manager); + std::vector listen_ports = config.getTcpServerPorts(); + TCPServer tcp_server(g_io_context, listen_ports, mqtt_client); + SystemMonitor::SystemMonitor monitor; - std::vector listen_ports = config.getTcpServerPorts(); - TCPServer tcp_server(g_io_context, listen_ports, mqtt_client); - SystemMonitor::SystemMonitor monitor; + if (!data_cache.open(config.getDataCacheDbPath())) + { + spdlog::critical("Failed to initialize data cache at '{}'. Exiting.", + config.getDataCacheDbPath()); + return 1; + } + CacheUploader cache_uploader(g_io_context, mqtt_client, data_cache); - if (!data_cache.open(config.getDataCacheDbPath())) { - spdlog::critical("Failed to initialize data cache at '{}'. Exiting.", - config.getDataCacheDbPath()); - return 1; - } - CacheUploader cache_uploader(g_io_context, mqtt_client, data_cache); - - mqtt_client.set_connected_handler([&](const std::string &cause) { + mqtt_client.set_connected_handler([&](const std::string &cause) + { spdlog::info("MQTT client connected: {}", cause); - cache_uploader.start_upload(); - }); + cache_uploader.start_upload(); }); - mqtt_client.connect(); - mqtt_router.start(); + mqtt_client.connect(); + mqtt_router.start(); - monitor.getCpuUtilization(); - boost::asio::steady_timer system_monitor_timer(g_io_context, - std::chrono::seconds(15)); + monitor.getCpuUtilization(); + boost::asio::steady_timer system_monitor_timer(g_io_context, + std::chrono::seconds(15)); - system_monitor_timer.async_wait(std::bind( - poll_system_metrics, std::ref(system_monitor_timer), std::ref(monitor), - std::ref(mqtt_client), std::ref(alarm_service))); + system_monitor_timer.async_wait(std::bind( + poll_system_metrics, std::ref(system_monitor_timer), std::ref(monitor), + std::ref(mqtt_client), std::ref(alarm_service))); - device_manager.load_and_start(config.getDevicesConfigPath()); + device_manager.load_and_start(config.getDevicesConfigPath()); - WebServer web_server(monitor, device_manager, live_data_cache, - alarm_service, config.getWebServerPort()); - web_server.set_shutdown_handler([&]() { + WebServer web_server(monitor, device_manager, live_data_cache, + alarm_service, config.getWebServerPort()); + web_server.set_shutdown_handler([&]() + { spdlog::warn("Received shutdown command from Web API. Shutting down."); - g_io_context.stop(); // <-- 这将触发 signals.async_wait 下方的所有清理逻辑 - }); - web_server.start(); + g_io_context.stop(); }); + web_server.start(); - // ----------------------------------------------------------------- - // [修改] 关键修改点 1: 视频服务启动逻辑 - // ----------------------------------------------------------------- - // (旧代码已删除) + boost::asio::signal_set signals(g_io_context, SIGINT, SIGTERM); + signals.async_wait( + [&](const boost::system::error_code &error, int signal_number) + { + spdlog::warn("Interrupt signal ({}) received. Shutting down.", + signal_number); - // [新代码] - // 1. 从主 config 获取 video_config.json 的 *路径* - std::string video_config_path = config.getVideoConfigPath(); + spdlog::info("[Shutdown] A. Stopping device manager services..."); + device_manager.stop_all(); - // 2. 让 video_manager 自己加载该配置文件 - if (video_manager.load_config(video_config_path)) { - // 3. 启动服务 (load_and_start 内部会检查 "enabled" 标志) - video_manager.load_and_start(); - } else { - spdlog::error("Failed to load video configuration from {}. Video " - "services will not start.", - video_config_path); - } - // ----------------------------------------------------------------- + spdlog::info("[Shutdown] B. Stopping web server..."); + web_server.stop(); - boost::asio::signal_set signals(g_io_context, SIGINT, SIGTERM); - signals.async_wait( - [&](const boost::system::error_code &error, int signal_number) { - spdlog::warn("Interrupt signal ({}) received. Shutting down.", - signal_number); + spdlog::info("[Shutdown] C. Stopping alarm service..."); + alarm_service.stop(); - // a. 停止所有数据采集 - spdlog::info("[Shutdown] A. Stopping device manager services..."); - device_manager.stop_all(); + spdlog::info("[Shutdown] D. Disconnecting from MQTT broker..."); + mqtt_client.disconnect(); - // b. 停止Web服务器 - spdlog::info("[Shutdown] B. Stopping web server..."); - web_server.stop(); + spdlog::info("[Shutdown] E. Stopping main event loop..."); + g_io_context.stop(); + }); - // c. 停止告警服务 (释放TTS线程) - spdlog::info("[Shutdown] C. Stopping alarm service..."); - alarm_service.stop(); + spdlog::info("All services are running. Press Ctrl+C to exit."); + g_io_context.run(); + } + catch (const std::exception &e) + { + spdlog::critical("An unhandled exception occurred: {}", e.what()); + return 1; + } - // d. 断开MQTT - spdlog::info("[Shutdown] D. Disconnecting from MQTT broker..."); - mqtt_client.disconnect(); - - // e. 停止视频服务 - spdlog::info("[Shutdown] E. Stopping video Service loop..."); - video_manager.stop_all(); - - spdlog::info("[Shutdown] F. De-initializing postprocess library..."); - deinitPostProcess(); - - // f. 最后,安全地停止io_context - spdlog::info("[Shutdown] G. Stopping main event loop..."); - g_io_context.stop(); - }); - - spdlog::info("All services are running. Press Ctrl+C to exit."); - g_io_context.run(); - - } catch (const std::exception &e) { - spdlog::critical("An unhandled exception occurred: {}", e.what()); - return 1; - } - - spdlog::info("Server has been shut down gracefully. Exiting."); - return 0; -} \ No newline at end of file + spdlog::info("Server has been shut down gracefully. Exiting."); + return 0; +} diff --git a/src/rknn/ThreadPool.hpp b/src/rknn/ThreadPool.hpp deleted file mode 100644 index 8bf3070..0000000 --- a/src/rknn/ThreadPool.hpp +++ /dev/null @@ -1,168 +0,0 @@ -#ifndef THREADPOOL_H -#define THREADPOOL_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace dpool -{ - - class ThreadPool - { - public: - using MutexGuard = std::lock_guard; - using UniqueLock = std::unique_lock; - using Thread = std::thread; - using ThreadID = std::thread::id; - using Task = std::function; - - ThreadPool() - : ThreadPool(Thread::hardware_concurrency()) - { - } - - explicit ThreadPool(size_t maxThreads) - : quit_(false), - currentThreads_(0), - idleThreads_(0), - maxThreads_(maxThreads) - { - } - - // disable the copy operations - ThreadPool(const ThreadPool &) = delete; - ThreadPool &operator=(const ThreadPool &) = delete; - - ~ThreadPool() - { - { - MutexGuard guard(mutex_); - quit_ = true; - } - cv_.notify_all(); - - for (auto &elem : threads_) - { - assert(elem.second.joinable()); - elem.second.join(); - } - } - - template - auto submit(Func &&func, Ts &&...params) - -> std::future::type> - { - auto execute = std::bind(std::forward(func), std::forward(params)...); - - using ReturnType = typename std::result_of::type; - using PackagedTask = std::packaged_task; - - auto task = std::make_shared(std::move(execute)); - auto result = task->get_future(); - - MutexGuard guard(mutex_); - assert(!quit_); - - tasks_.emplace([task]() - { (*task)(); }); - if (idleThreads_ > 0) - { - cv_.notify_one(); - } - else if (currentThreads_ < maxThreads_) - { - Thread t(&ThreadPool::worker, this); - assert(threads_.find(t.get_id()) == threads_.end()); - threads_[t.get_id()] = std::move(t); - ++currentThreads_; - } - - return result; - } - - size_t threadsNum() const - { - MutexGuard guard(mutex_); - return currentThreads_; - } - - private: - void worker() - { - while (true) - { - Task task; - { - UniqueLock uniqueLock(mutex_); - ++idleThreads_; - auto hasTimedout = !cv_.wait_for(uniqueLock, - std::chrono::seconds(WAIT_SECONDS), - [this]() - { - return quit_ || !tasks_.empty(); - }); - --idleThreads_; - if (tasks_.empty()) - { - if (quit_) - { - --currentThreads_; - return; - } - if (hasTimedout) - { - --currentThreads_; - joinFinishedThreads(); - finishedThreadIDs_.emplace(std::this_thread::get_id()); - return; - } - } - task = std::move(tasks_.front()); - tasks_.pop(); - } - task(); - } - } - - void joinFinishedThreads() - { - while (!finishedThreadIDs_.empty()) - { - auto id = std::move(finishedThreadIDs_.front()); - finishedThreadIDs_.pop(); - auto iter = threads_.find(id); - - assert(iter != threads_.end()); - assert(iter->second.joinable()); - - iter->second.join(); - threads_.erase(iter); - } - } - - static constexpr size_t WAIT_SECONDS = 2; - - bool quit_; - size_t currentThreads_; - size_t idleThreads_; - size_t maxThreads_; - - mutable std::mutex mutex_; - std::condition_variable cv_; - std::queue tasks_; - std::queue finishedThreadIDs_; - std::unordered_map threads_; - }; - - constexpr size_t ThreadPool::WAIT_SECONDS; - -} // namespace dpool - -#endif /* THREADPOOL_H */ \ No newline at end of file diff --git a/src/rknn/coreNum.hpp b/src/rknn/coreNum.hpp deleted file mode 100644 index e293a03..0000000 --- a/src/rknn/coreNum.hpp +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef CORENUM_H -#define CORENUM_H - -#include - -#include "rknn_api.h" - -const int RK3588 = 3; - -// 设置模型需要绑定的核心 -// Set the core of the model that needs to be bound -int get_core_num() -{ - static int core_num = 0; - static std::mutex mtx; - - std::lock_guard lock(mtx); - - int temp = core_num % RK3588; - core_num++; - return temp; -} -#endif \ No newline at end of file diff --git a/src/rknn/drm_func.h b/src/rknn/drm_func.h deleted file mode 100644 index 0a9e3b3..0000000 --- a/src/rknn/drm_func.h +++ /dev/null @@ -1,53 +0,0 @@ -#ifndef __DRM_FUNC_H__ -#define __DRM_FUNC_H__ -#include -#include -#include -#include -#include // open function -#include // close function -#include -#include - - -#include -#include "libdrm/drm_fourcc.h" -#include "xf86drm.h" - -#ifdef __cplusplus -extern "C" { -#endif - -typedef int (* FUNC_DRM_IOCTL)(int fd, unsigned long request, void *arg); - -typedef struct _drm_context{ - void *drm_handle; - FUNC_DRM_IOCTL io_func; -} drm_context; - -/* memory type definitions. */ -enum drm_rockchip_gem_mem_type -{ - /* Physically Continuous memory and used as default. */ - ROCKCHIP_BO_CONTIG = 1 << 0, - /* cachable mapping. */ - ROCKCHIP_BO_CACHABLE = 1 << 1, - /* write-combine mapping. */ - ROCKCHIP_BO_WC = 1 << 2, - ROCKCHIP_BO_SECURE = 1 << 3, - ROCKCHIP_BO_MASK = ROCKCHIP_BO_CONTIG | ROCKCHIP_BO_CACHABLE | - ROCKCHIP_BO_WC | ROCKCHIP_BO_SECURE -}; - -int drm_init(drm_context *drm_ctx); - -void* drm_buf_alloc(drm_context *drm_ctx,int drm_fd, int TexWidth, int TexHeight,int bpp,int *fd,unsigned int *handle,size_t *actual_size); - -int drm_buf_destroy(drm_context *drm_ctx,int drm_fd,int buf_fd, int handle,void *drm_buf,size_t size); - -void drm_deinit(drm_context *drm_ctx, int drm_fd); - -#ifdef __cplusplus -} -#endif -#endif /*__DRM_FUNC_H__*/ \ No newline at end of file diff --git a/src/rknn/postprocess.cc b/src/rknn/postprocess.cc deleted file mode 100644 index 13c62da..0000000 --- a/src/rknn/postprocess.cc +++ /dev/null @@ -1,394 +0,0 @@ -// postprocess.cc (修改后) - -#include "postprocess.h" - -#include -#include -#include -#include -#include -#include - -#include -#include - -static std::vector g_labels; - -const int anchor0[6] = {10, 13, 16, 30, 33, 23}; -const int anchor1[6] = {30, 61, 62, 45, 59, 119}; -const int anchor2[6] = {116, 90, 156, 198, 373, 326}; - -inline static int clamp(float val, int min, int max) { - return val > min ? (val < max ? val : max) : min; -} - -// ... readLine 和 readLines 函数保持不变 ... -char *readLine(FILE *fp, char *buffer, int *len) { - int ch; - int i = 0; - size_t buff_len = 0; - - buffer = (char *)malloc(buff_len + 1); - if (!buffer) - return NULL; - - while ((ch = fgetc(fp)) != '\n' && ch != EOF) { - buff_len++; - void *tmp = realloc(buffer, buff_len + 1); - if (tmp == NULL) { - free(buffer); - return NULL; - } - buffer = (char *)tmp; - - buffer[i] = (char)ch; - i++; - } - buffer[i] = '\0'; - - *len = buff_len; - - if (ch == EOF && (i == 0 || ferror(fp))) { - free(buffer); - return NULL; - } - return buffer; -} - -int readLines(const char *fileName, char *lines[], int max_line) { - FILE *file = fopen(fileName, "r"); - char *s; - int i = 0; - int n = 0; - - if (file == NULL) { - printf("Open %s fail!\n", fileName); - return -1; - } - - while ((s = readLine(file, s, &n)) != NULL) { - lines[i++] = s; - if (i >= max_line) - break; - } - fclose(file); - return i; -} - -// 移除 loadLabelName 函数 - -/** - * @brief [新增] 动态初始化函数 - */ -int initPostProcess(const char *label_path, int class_num) { - // 1. 清理旧数据 (如果重载) - deinitPostProcess(); - printf("initPostProcess: Loading %d labels from %s\n", class_num, label_path); - - // 2. 调整 vector 大小 - try { - g_labels.resize(class_num, nullptr); - } catch (const std::exception &e) { - printf("Failed to resize label vector: %s\n", e.what()); - return -1; - } - - // 3. 调用 readLines 填充 vector - int ret = readLines(label_path, g_labels.data(), class_num); - if (ret < 0) { - printf("readLines failed!\n"); - deinitPostProcess(); // 失败时清理 - return -1; - } - if (ret == 0) { - printf("No labels read from %s. File is empty or invalid.\n", label_path); - deinitPostProcess(); // 失败时清理 - return -1; - } - - printf("Labels loaded successfully (%d lines read).\n", ret); - return 0; -} - -// ... CalculateOverlap, nms, quick_sort_indice_inverse ... 保持不变 ... -static float CalculateOverlap(float xmin0, float ymin0, float xmax0, - float ymax0, float xmin1, float ymin1, - float xmax1, float ymax1) { - float w = fmax(0.f, fmin(xmax0, xmax1) - fmax(xmin0, xmin1) + 1.0); - float h = fmax(0.f, fmin(ymax0, ymax1) - fmax(ymin0, ymin1) + 1.0); - float i = w * h; - float u = (xmax0 - xmin0 + 1.0) * (ymax0 - ymin0 + 1.0) + - (xmax1 - xmin1 + 1.0) * (ymax1 - ymin1 + 1.0) - i; - return u <= 0.f ? 0.f : (i / u); -} - -static int nms(int validCount, std::vector &outputLocations, - std::vector classIds, std::vector &order, int filterId, - float threshold) { - for (int i = 0; i < validCount; ++i) { - if (order[i] == -1 || classIds[i] != filterId) { - continue; - } - int n = order[i]; - for (int j = i + 1; j < validCount; ++j) { - int m = order[j]; - if (m == -1 || classIds[i] != filterId) { - continue; - } - float xmin0 = outputLocations[n * 4 + 0]; - float ymin0 = outputLocations[n * 4 + 1]; - float xmax0 = outputLocations[n * 4 + 0] + outputLocations[n * 4 + 2]; - float ymax0 = outputLocations[n * 4 + 1] + outputLocations[n * 4 + 3]; - - float xmin1 = outputLocations[m * 4 + 0]; - float ymin1 = outputLocations[m * 4 + 1]; - float xmax1 = outputLocations[m * 4 + 0] + outputLocations[m * 4 + 2]; - float ymax1 = outputLocations[m * 4 + 1] + outputLocations[m * 4 + 3]; - - float iou = CalculateOverlap(xmin0, ymin0, xmax0, ymax0, xmin1, ymin1, - xmax1, ymax1); - - if (iou > threshold) { - order[j] = -1; - } - } - } - return 0; -} - -static int quick_sort_indice_inverse(std::vector &input, int left, - int right, std::vector &indices) { - float key; - int key_index; - int low = left; - int high = right; - if (left < right) { - key_index = indices[left]; - key = input[left]; - while (low < high) { - while (low < high && input[high] <= key) { - high--; - } - input[low] = input[high]; - indices[low] = indices[high]; - while (low < high && input[low] >= key) { - low++; - } - input[high] = input[low]; - indices[high] = indices[low]; - } - input[low] = key; - indices[low] = key_index; - quick_sort_indice_inverse(input, left, low - 1, indices); - quick_sort_indice_inverse(input, low + 1, right, indices); - } - return low; -} - -// ... sigmoid, unsigmoid, __clip, qnt_f32_to_affine, deqnt_affine_to_f32 ... -// 保持不变 ... -static float sigmoid(float x) { return 1.0 / (1.0 + expf(-x)); } -static float unsigmoid(float y) { return -1.0 * logf((1.0 / y) - 1.0); } -inline static int32_t __clip(float val, float min, float max) { - float f = val <= min ? min : (val >= max ? max : val); - return f; -} -static int8_t qnt_f32_to_affine(float f32, int32_t zp, float scale) { - float dst_val = (f32 / scale) + zp; - int8_t res = (int8_t)__clip(dst_val, -128, 127); - return res; -} -static float deqnt_affine_to_f32(int8_t qnt, int32_t zp, float scale) { - return ((float)qnt - (float)zp) * scale; -} - -/** - * @brief [修改] process 函数签名 - * @param class_num [新增] 模型的类别数 - */ -static int process(int8_t *input, int *anchor, int grid_h, int grid_w, - int height, int width, int stride, std::vector &boxes, - std::vector &objProbs, std::vector &classId, - float threshold, int32_t zp, float scale, - int class_num) // <-- 新增参数 -{ - int validCount = 0; - int grid_len = grid_h * grid_w; - int8_t thres_i8 = qnt_f32_to_affine(threshold, zp, scale); - - // [修改] 动态计算 prop_box_size - const int prop_box_size = 5 + class_num; - - for (int a = 0; a < 3; a++) { - for (int i = 0; i < grid_h; i++) { - for (int j = 0; j < grid_w; j++) { - // [修改] 使用 prop_box_size - int8_t box_confidence = - input[(prop_box_size * a + 4) * grid_len + i * grid_w + j]; - if (box_confidence >= thres_i8) { - // [修改] 使用 prop_box_size - int offset = (prop_box_size * a) * grid_len + i * grid_w + j; - int8_t *in_ptr = input + offset; - float box_x = (deqnt_affine_to_f32(*in_ptr, zp, scale)) * 2.0 - 0.5; - float box_y = - (deqnt_affine_to_f32(in_ptr[grid_len], zp, scale)) * 2.0 - 0.5; - float box_w = - (deqnt_affine_to_f32(in_ptr[2 * grid_len], zp, scale)) * 2.0; - float box_h = - (deqnt_affine_to_f32(in_ptr[3 * grid_len], zp, scale)) * 2.0; - box_x = (box_x + j) * (float)stride; - box_y = (box_y + i) * (float)stride; - box_w = box_w * box_w * (float)anchor[a * 2]; - box_h = box_h * box_h * (float)anchor[a * 2 + 1]; - box_x -= (box_w / 2.0); - box_y -= (box_h / 2.0); - - int8_t maxClassProbs = in_ptr[5 * grid_len]; - int maxClassId = 0; - // [修改] 使用 class_num - for (int k = 1; k < class_num; ++k) { - int8_t prob = in_ptr[(5 + k) * grid_len]; - if (prob > maxClassProbs) { - maxClassId = k; - maxClassProbs = prob; - } - } - if (maxClassProbs > thres_i8) { - objProbs.push_back( - (deqnt_affine_to_f32(maxClassProbs, zp, scale)) * - (deqnt_affine_to_f32(box_confidence, zp, scale))); - classId.push_back(maxClassId); - validCount++; - boxes.push_back(box_x); - boxes.push_back(box_y); - boxes.push_back(box_w); - boxes.push_back(box_h); - } - } - } - } - } - return validCount; -} - -/** - * @brief [修改] post_process 函数 - */ -int post_process(int8_t *input0, int8_t *input1, int8_t *input2, int model_in_h, - int model_in_w, float conf_threshold, float nms_threshold, - BOX_RECT pads, float scale_w, float scale_h, - std::vector &qnt_zps, std::vector &qnt_scales, - int class_num, // <-- 新增参数 - detect_result_group_t *group) { - // [修改] 移除 static int init = -1; 自动初始化模块 - // 现在假定 initPostProcess 已经被外部调用 - - memset(group, 0, sizeof(detect_result_group_t)); - - std::vector filterBoxes; - std::vector objProbs; - std::vector classId; - - // stride 8 - int stride0 = 8; - int grid_h0 = model_in_h / stride0; - int grid_w0 = model_in_w / stride0; - int validCount0 = 0; - // [修改] 传入 class_num - validCount0 = process(input0, (int *)anchor0, grid_h0, grid_w0, model_in_h, - model_in_w, stride0, filterBoxes, objProbs, classId, - conf_threshold, qnt_zps[0], qnt_scales[0], class_num); - - // stride 16 - int stride1 = 16; - int grid_h1 = model_in_h / stride1; - int grid_w1 = model_in_w / stride1; - int validCount1 = 0; - // [修改] 传入 class_num - validCount1 = process(input1, (int *)anchor1, grid_h1, grid_w1, model_in_h, - model_in_w, stride1, filterBoxes, objProbs, classId, - conf_threshold, qnt_zps[1], qnt_scales[1], class_num); - - // stride 32 - int stride2 = 32; - int grid_h2 = model_in_h / stride2; - int grid_w2 = model_in_w / stride2; - int validCount2 = 0; - // [修改] 传入 class_num - validCount2 = process(input2, (int *)anchor2, grid_h2, grid_w2, model_in_h, - model_in_w, stride2, filterBoxes, objProbs, classId, - conf_threshold, qnt_zps[2], qnt_scales[2], class_num); - - int validCount = validCount0 + validCount1 + validCount2; - // no object detect - if (validCount <= 0) { - return 0; - } - - std::vector indexArray; - for (int i = 0; i < validCount; ++i) { - indexArray.push_back(i); - } - - quick_sort_indice_inverse(objProbs, 0, validCount - 1, indexArray); - - std::set class_set(std::begin(classId), std::end(classId)); - - for (auto c : class_set) { - nms(validCount, filterBoxes, classId, indexArray, c, nms_threshold); - } - - int last_count = 0; - group->count = 0; - /* box valid detect target */ - for (int i = 0; i < validCount; ++i) { - if (indexArray[i] == -1 || last_count >= OBJ_NUMB_MAX_SIZE) { - continue; - } - int n = indexArray[i]; - - float x1 = filterBoxes[n * 4 + 0] - pads.left; - float y1 = filterBoxes[n * 4 + 1] - pads.top; - float x2 = x1 + filterBoxes[n * 4 + 2]; - float y2 = y1 + filterBoxes[n * 4 + 3]; - int id = classId[n]; - float obj_conf = objProbs[i]; - - group->results[last_count].box.left = - (int)(clamp(x1, 0, model_in_w) / scale_w); - group->results[last_count].box.top = - (int)(clamp(y1, 0, model_in_h) / scale_h); - group->results[last_count].box.right = - (int)(clamp(x2, 0, model_in_w) / scale_w); - group->results[last_count].box.bottom = - (int)(clamp(y2, 0, model_in_h) / scale_h); - group->results[last_count].prop = obj_conf; - - // [修改] 安全地从 g_labels 获取标签 - char *label = (char *)"unknown"; // 默认值 - if (id >= 0 && id < g_labels.size() && g_labels[id] != nullptr) { - label = g_labels[id]; - } - - strncpy(group->results[last_count].name, label, OBJ_NAME_MAX_SIZE); - - last_count++; - } - group->count = last_count; - - return 0; -} - -/** - * @brief [修改] deinitPostProcess - */ -void deinitPostProcess() { - // [修改] 遍历 g_labels vector 并释放内存 - for (size_t i = 0; i < g_labels.size(); i++) { - if (g_labels[i] != nullptr) { - free(g_labels[i]); - g_labels[i] = nullptr; - } - } - g_labels.clear(); -} \ No newline at end of file diff --git a/src/rknn/postprocess.h b/src/rknn/postprocess.h deleted file mode 100644 index c669338..0000000 --- a/src/rknn/postprocess.h +++ /dev/null @@ -1,57 +0,0 @@ -#ifndef _RKNN_YOLOV5_DEMO_POSTPROCESS_H_ -#define _RKNN_YOLOV5_DEMO_POSTPROCESS_H_ - -#include -#include - -#define OBJ_NAME_MAX_SIZE 16 -#define OBJ_NUMB_MAX_SIZE 64 -// 移除 #define OBJ_CLASS_NUM 80 -#define NMS_THRESH 0.45 -#define BOX_THRESH 0.25 -// 移除 #define PROP_BOX_SIZE (5 + OBJ_CLASS_NUM) - -typedef struct _BOX_RECT { - int left; - int right; - int top; - int bottom; -} BOX_RECT; - -typedef struct __detect_result_t { - char name[OBJ_NAME_MAX_SIZE]; - BOX_RECT box; - float prop; -} detect_result_t; - -typedef struct _detect_result_group_t { - int id; - int count; - detect_result_t results[OBJ_NUMB_MAX_SIZE]; -} detect_result_group_t; - -/** - * @brief [新增] 初始化后处理模块 - * @param label_path 标签文件的路径 - * @param class_num 模型的类别数 (例如 80) - * @return int 0 表示成功, -1 表示失败 - */ -int initPostProcess(const char *label_path, int class_num); - -/** - * @brief [修改] post_process 函数签名 - * @param class_num [新增] 模型的类别数, 必须与 initPostProcess 中使用的一致 - */ -int post_process(int8_t *input0, int8_t *input1, int8_t *input2, int model_in_h, - int model_in_w, float conf_threshold, float nms_threshold, - BOX_RECT pads, float scale_w, float scale_h, - std::vector &qnt_zps, std::vector &qnt_scales, - int class_num, // <-- 新增参数 - detect_result_group_t *group); - -/** - * @brief [修改] deinitPostProcess 释放动态分配的 g_labels - */ -void deinitPostProcess(); - -#endif //_RKNN_YOLOV5_DEMO_POSTPROCESS_H_ \ No newline at end of file diff --git a/src/rknn/preprocess.cc b/src/rknn/preprocess.cc deleted file mode 100644 index 6a7c666..0000000 --- a/src/rknn/preprocess.cc +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) 2023 by Rockchip Electronics Co., Ltd. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include -#include "im2d.h" -#include "rga.h" -#include "opencv2/core/core.hpp" -#include "opencv2/imgcodecs.hpp" -#include "opencv2/imgproc.hpp" -#include "rknn/postprocess.h" - -void letterbox(const cv::Mat &image, cv::Mat &padded_image, BOX_RECT &pads, const float scale, const cv::Size &target_size, const cv::Scalar &pad_color) -{ - // 调整图像大小 - cv::Mat resized_image; - cv::resize(image, resized_image, cv::Size(), scale, scale); - - // 计算填充大小 - int pad_width = target_size.width - resized_image.cols; - int pad_height = target_size.height - resized_image.rows; - - pads.left = pad_width / 2; - pads.right = pad_width - pads.left; - pads.top = pad_height / 2; - pads.bottom = pad_height - pads.top; - - // 在图像周围添加填充 - cv::copyMakeBorder(resized_image, padded_image, pads.top, pads.bottom, pads.left, pads.right, cv::BORDER_CONSTANT, pad_color); -} - -int resize_rga(rga_buffer_t &src, rga_buffer_t &dst, const cv::Mat &image, cv::Mat &resized_image, const cv::Size &target_size) -{ - im_rect src_rect; - im_rect dst_rect; - memset(&src_rect, 0, sizeof(src_rect)); - memset(&dst_rect, 0, sizeof(dst_rect)); - size_t img_width = image.cols; - size_t img_height = image.rows; - if (image.type() != CV_8UC3) - { - printf("source image type is %d!\n", image.type()); - return -1; - } - size_t target_width = target_size.width; - size_t target_height = target_size.height; - src = wrapbuffer_virtualaddr((void *)image.data, img_width, img_height, RK_FORMAT_RGB_888); - dst = wrapbuffer_virtualaddr((void *)resized_image.data, target_width, target_height, RK_FORMAT_RGB_888); - int ret = imcheck(src, dst, src_rect, dst_rect); - if (IM_STATUS_NOERROR != ret) - { - fprintf(stderr, "rga check error! %s", imStrError((IM_STATUS)ret)); - return -1; - } - IM_STATUS STATUS = imresize(src, dst); - return 0; -} \ No newline at end of file diff --git a/src/rknn/preprocess.h b/src/rknn/preprocess.h deleted file mode 100644 index dc8319f..0000000 --- a/src/rknn/preprocess.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef _RKNN_YOLOV5_DEMO_PREPROCESS_H_ -#define _RKNN_YOLOV5_DEMO_PREPROCESS_H_ - -#include -#include "im2d.h" -#include "rga.h" -#include "opencv2/core/core.hpp" -#include "opencv2/imgcodecs.hpp" -#include "opencv2/imgproc.hpp" -#include "postprocess.h" - -void letterbox(const cv::Mat &image, cv::Mat &padded_image, BOX_RECT &pads, const float scale, const cv::Size &target_size, const cv::Scalar &pad_color = cv::Scalar(128, 128, 128)); - -int resize_rga(rga_buffer_t &src, rga_buffer_t &dst, const cv::Mat &image, cv::Mat &resized_image, const cv::Size &target_size); - -#endif //_RKNN_YOLOV5_DEMO_PREPROCESS_H_ diff --git a/src/rknn/rga_func.h b/src/rknn/rga_func.h deleted file mode 100644 index beeb441..0000000 --- a/src/rknn/rga_func.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef __RGA_FUNC_H__ -#define __RGA_FUNC_H__ - -#include -#include "RgaApi.h" - -#ifdef __cplusplus -extern "C" { -#endif - -typedef int(* FUNC_RGA_INIT)(); -typedef void(* FUNC_RGA_DEINIT)(); -typedef int(* FUNC_RGA_BLIT)(rga_info_t *, rga_info_t *, rga_info_t *); - -typedef struct _rga_context{ - void *rga_handle; - FUNC_RGA_INIT init_func; - FUNC_RGA_DEINIT deinit_func; - FUNC_RGA_BLIT blit_func; -} rga_context; - -int RGA_init(rga_context* rga_ctx); - -void img_resize_fast(rga_context *rga_ctx, int src_fd, int src_w, int src_h, uint64_t dst_phys, int dst_w, int dst_h); - -void img_resize_slow(rga_context *rga_ctx, void *src_virt, int src_w, int src_h, void *dst_virt, int dst_w, int dst_h); - -int RGA_deinit(rga_context* rga_ctx); - -#ifdef __cplusplus -} -#endif -#endif/*__RGA_FUNC_H__*/ diff --git a/src/rknn/rkYolov5s.cc b/src/rknn/rkYolov5s.cc deleted file mode 100644 index 2a2bb51..0000000 --- a/src/rknn/rkYolov5s.cc +++ /dev/null @@ -1,269 +0,0 @@ -#include - -#include -#include - -#include "postprocess.h" -#include "preprocess.h" -#include "rkYolov5s.hpp" -#include "rknn/coreNum.hpp" - -#include "opencv2/core/core.hpp" -#include "opencv2/highgui/highgui.hpp" -#include "opencv2/imgproc/imgproc.hpp" -#include "rknn/rknn_api.h" - -static void dump_tensor_attr(rknn_tensor_attr *attr) { - std::string shape_str = attr->n_dims < 1 ? "" : std::to_string(attr->dims[0]); - for (int i = 1; i < attr->n_dims; ++i) { - shape_str += ", " + std::to_string(attr->dims[i]); - } -} - -static unsigned char *load_data(FILE *fp, size_t ofst, size_t sz) { - unsigned char *data; - int ret; - - data = NULL; - - if (NULL == fp) { - return NULL; - } - - ret = fseek(fp, ofst, SEEK_SET); - if (ret != 0) { - printf("blob seek failure.\n"); - return NULL; - } - - data = (unsigned char *)malloc(sz); - if (data == NULL) { - printf("buffer malloc failure.\n"); - return NULL; - } - ret = fread(data, 1, sz, fp); - return data; -} - -static unsigned char *load_model(const char *filename, int *model_size) { - FILE *fp; - unsigned char *data; - - fp = fopen(filename, "rb"); - if (NULL == fp) { - printf("Open file %s failed.\n", filename); - return NULL; - } - - fseek(fp, 0, SEEK_END); - int size = ftell(fp); - - data = load_data(fp, 0, size); - - fclose(fp); - - *model_size = size; - return data; -} - -static int saveFloat(const char *file_name, float *output, int element_size) { - FILE *fp; - fp = fopen(file_name, "w"); - for (int i = 0; i < element_size; i++) { - fprintf(fp, "%.6f\n", output[i]); - } - fclose(fp); - return 0; -} - -rkYolov5s::rkYolov5s(const std::string &model_path, - const std::string &label_path, int class_num) { - this->model_path = model_path; - this->m_label_path = label_path; - this->m_class_num = class_num; - - nms_threshold = NMS_THRESH; - box_conf_threshold = BOX_THRESH; -} - -int rkYolov5s::init(rknn_context *ctx_in, bool share_weight) { - printf("Loading model...\n"); - int model_data_size = 0; - model_data = load_model(model_path.c_str(), &model_data_size); - if (share_weight == true) - ret = rknn_dup_context(ctx_in, &ctx); - else - ret = rknn_init(&ctx, model_data, model_data_size, 0, NULL); - if (ret < 0) { - printf("rknn_init error ret=%d\n", ret); - return -1; - } - - rknn_core_mask core_mask; - switch (get_core_num()) { - case 0: - core_mask = RKNN_NPU_CORE_0; - break; - case 1: - core_mask = RKNN_NPU_CORE_1; - break; - case 2: - core_mask = RKNN_NPU_CORE_2; - break; - } - ret = rknn_set_core_mask(ctx, core_mask); - if (ret < 0) { - printf("rknn_init core error ret=%d\n", ret); - return -1; - } - - rknn_sdk_version version; - ret = rknn_query(ctx, RKNN_QUERY_SDK_VERSION, &version, - sizeof(rknn_sdk_version)); - if (ret < 0) { - printf("rknn_init error ret=%d\n", ret); - return -1; - } - printf("sdk version: %s driver version: %s\n", version.api_version, - version.drv_version); - - ret = rknn_query(ctx, RKNN_QUERY_IN_OUT_NUM, &io_num, sizeof(io_num)); - if (ret < 0) { - printf("rknn_init error ret=%d\n", ret); - return -1; - } - printf("model input num: %d, output num: %d\n", io_num.n_input, - io_num.n_output); - - input_attrs = - (rknn_tensor_attr *)calloc(io_num.n_input, sizeof(rknn_tensor_attr)); - for (int i = 0; i < io_num.n_input; i++) { - input_attrs[i].index = i; - ret = rknn_query(ctx, RKNN_QUERY_INPUT_ATTR, &(input_attrs[i]), - sizeof(rknn_tensor_attr)); - if (ret < 0) { - printf("rknn_init error ret=%d\n", ret); - return -1; - } - dump_tensor_attr(&(input_attrs[i])); - } - - output_attrs = - (rknn_tensor_attr *)calloc(io_num.n_output, sizeof(rknn_tensor_attr)); - for (int i = 0; i < io_num.n_output; i++) { - output_attrs[i].index = i; - ret = rknn_query(ctx, RKNN_QUERY_OUTPUT_ATTR, &(output_attrs[i]), - sizeof(rknn_tensor_attr)); - dump_tensor_attr(&(output_attrs[i])); - } - - if (input_attrs[0].fmt == RKNN_TENSOR_NCHW) { - printf("model is NCHW input fmt\n"); - channel = input_attrs[0].dims[1]; - height = input_attrs[0].dims[2]; - width = input_attrs[0].dims[3]; - } else { - printf("model is NHWC input fmt\n"); - height = input_attrs[0].dims[1]; - width = input_attrs[0].dims[2]; - channel = input_attrs[0].dims[3]; - } - printf("model input height=%d, width=%d, channel=%d\n", height, width, - channel); - - memset(inputs, 0, sizeof(inputs)); - inputs[0].index = 0; - inputs[0].type = RKNN_TENSOR_UINT8; - inputs[0].size = width * height * channel; - inputs[0].fmt = RKNN_TENSOR_NHWC; - inputs[0].pass_through = 0; - - static std::mutex postprocess_init_mutex; - static bool postprocess_initialized = false; - - std::lock_guard lock(postprocess_init_mutex); - if (!postprocess_initialized) { - - if (initPostProcess(m_label_path.c_str(), m_class_num) == 0) { - postprocess_initialized = true; - printf("PostProcess initialized successfully with %d classes from %s\n", - m_class_num, m_label_path.c_str()); - } else { - printf("Failed to initialize PostProcess!\n"); - return -1; - } - } - - return 0; -} - -rknn_context *rkYolov5s::get_pctx() { return &ctx; } - -detect_result_group_t rkYolov5s::infer(const cv::Mat &orig_img) { - - cv::Mat img; - cv::cvtColor(orig_img, img, cv::COLOR_BGR2RGB); - img_width = img.cols; - img_height = img.rows; - - BOX_RECT pads; - memset(&pads, 0, sizeof(BOX_RECT)); - cv::Size target_size(width, height); - cv::Mat resized_img(target_size.height, target_size.width, CV_8UC3); - - float scale_w = (float)target_size.width / img.cols; - float scale_h = (float)target_size.height / img.rows; - - if (img_width != width || img_height != height) { - rga_buffer_t src; - rga_buffer_t dst; - memset(&src, 0, sizeof(src)); - memset(&dst, 0, sizeof(dst)); - ret = resize_rga(src, dst, img, resized_img, target_size); - if (ret != 0) { - fprintf(stderr, "resize with rga error\n"); - } - inputs[0].buf = resized_img.data; - } else { - inputs[0].buf = img.data; - } - - rknn_inputs_set(ctx, io_num.n_input, inputs); - - rknn_output outputs[io_num.n_output]; - memset(outputs, 0, sizeof(outputs)); - for (int i = 0; i < io_num.n_output; i++) { - outputs[i].want_float = 0; - } - - ret = rknn_run(ctx, NULL); - ret = rknn_outputs_get(ctx, io_num.n_output, outputs, NULL); - - detect_result_group_t detect_result_group; - std::vector out_scales; - std::vector out_zps; - for (int i = 0; i < io_num.n_output; ++i) { - out_scales.push_back(output_attrs[i].scale); - out_zps.push_back(output_attrs[i].zp); - } - post_process((int8_t *)outputs[0].buf, (int8_t *)outputs[1].buf, - (int8_t *)outputs[2].buf, height, width, box_conf_threshold, - nms_threshold, pads, scale_w, scale_h, out_zps, out_scales, - m_class_num, &detect_result_group); - - ret = rknn_outputs_release(ctx, io_num.n_output, outputs); - return detect_result_group; -} - -rkYolov5s::~rkYolov5s() { - - ret = rknn_destroy(ctx); - - if (model_data) - free(model_data); - - if (input_attrs) - free(input_attrs); - if (output_attrs) - free(output_attrs); -} \ No newline at end of file diff --git a/src/rknn/rkYolov5s.hpp b/src/rknn/rkYolov5s.hpp deleted file mode 100644 index af70a57..0000000 --- a/src/rknn/rkYolov5s.hpp +++ /dev/null @@ -1,50 +0,0 @@ -#ifndef RKYOLOV5S_H -#define RKYOLOV5S_H - -#include "opencv2/core/core.hpp" -#include "postprocess.h" // 包含 detect_result_group_t 的定义 -#include "rknn_api.h" -#include -#include - -// 前置声明 -static void dump_tensor_attr(rknn_tensor_attr *attr); -static unsigned char *load_data(FILE *fp, size_t ofst, size_t sz); -static unsigned char *load_model(const char *filename, int *model_size); -static int saveFloat(const char *file_name, float *output, int element_size); - -// 注意:TrackedPerson 结构体已被移至 video_service.h - -class rkYolov5s { -private: - int ret; - // std::mutex mtx; // 已移除,推理应是无状态的 - std::string model_path; - unsigned char *model_data; - - rknn_context ctx; - rknn_input_output_num io_num; - rknn_tensor_attr *input_attrs; - rknn_tensor_attr *output_attrs; - rknn_input inputs[1]; - - int channel, width, height; - int img_width, img_height; - - float nms_threshold, box_conf_threshold; - - std::string m_label_path; - int m_class_num; - -public: - rkYolov5s(const std::string &model_path, const std::string &label_path, - int class_num); - int init(rknn_context *ctx_in, bool isChild); - rknn_context *get_pctx(); - - detect_result_group_t infer(const cv::Mat &ori_img); - - ~rkYolov5s(); -}; - -#endif // RKYOLOV5S_H \ No newline at end of file diff --git a/src/rknn/rkYolov8.cc b/src/rknn/rkYolov8.cc deleted file mode 100644 index ccf3fa8..0000000 --- a/src/rknn/rkYolov8.cc +++ /dev/null @@ -1,309 +0,0 @@ -#include "rkYolov8.hpp" -#include "opencv2/imgproc/imgproc.hpp" -#include -#include -#include -#include -#include -#include -#include -#include - -static inline float sigmoid(float x) { return 1.0f / (1.0f + expf(-x)); } - -static void compute_dfl(float *tensor, int dfl_len, float *box) { - for (int b = 0; b < 4; b++) { - float exp_t[16]; - float exp_sum = 0; - float acc_sum = 0; - - for (int i = 0; i < dfl_len; i++) { - exp_t[i] = expf(tensor[i + b * dfl_len]); - exp_sum += exp_t[i]; - } - - for (int i = 0; i < dfl_len; i++) { - acc_sum += (exp_t[i] / exp_sum) * i; - } - box[b] = acc_sum; - } -} - -unsigned char *rkYolov8::load_model(const char *filename, int *model_size) { - FILE *fp = fopen(filename, "rb"); - if (fp == nullptr) { - printf("Open file %s failed.\n", filename); - return nullptr; - } - fseek(fp, 0, SEEK_END); - int size = ftell(fp); - fseek(fp, 0, SEEK_SET); - unsigned char *data = (unsigned char *)malloc(size); - fread(data, 1, size, fp); - fclose(fp); - *model_size = size; - return data; -} - -rkYolov8::rkYolov8(const std::string &model_path, const std::string &label_path, - int class_num) { - this->model_path = model_path; - this->m_label_path = label_path; - this->m_class_num = class_num; - this->conf_threshold = 0.3f; - this->nms_threshold = 0.5f; - this->model_data = nullptr; - this->input_attrs = nullptr; - this->output_attrs = nullptr; -} - -rkYolov8::~rkYolov8() { - if (input_attrs) - free(input_attrs); - if (output_attrs) - free(output_attrs); - if (model_data) - free(model_data); - if (rga_buffer_ptr) - free(rga_buffer_ptr); - rknn_destroy(ctx); -} - -rknn_context *rkYolov8::get_pctx() { return &ctx; } - -int rkYolov8::init(rknn_context *ctx_in, bool is_slave) { - int model_data_size = 0; - model_data = load_model(model_path.c_str(), &model_data_size); - if (!model_data) - return -1; - - if (is_slave) { - ret = rknn_dup_context(ctx_in, &ctx); - } else { - ret = rknn_init(&ctx, model_data, model_data_size, 0, nullptr); - } - if (ret < 0) - return -1; - - rknn_query(ctx, RKNN_QUERY_IN_OUT_NUM, &io_num, sizeof(io_num)); - - input_attrs = - (rknn_tensor_attr *)calloc(io_num.n_input, sizeof(rknn_tensor_attr)); - output_attrs = - (rknn_tensor_attr *)calloc(io_num.n_output, sizeof(rknn_tensor_attr)); - - for (int i = 0; i < io_num.n_input; i++) { - input_attrs[i].index = i; - rknn_query(ctx, RKNN_QUERY_INPUT_ATTR, &(input_attrs[i]), - sizeof(rknn_tensor_attr)); - } - for (int i = 0; i < io_num.n_output; i++) { - output_attrs[i].index = i; - rknn_query(ctx, RKNN_QUERY_OUTPUT_ATTR, &(output_attrs[i]), - sizeof(rknn_tensor_attr)); - } - - if (input_attrs[0].fmt == RKNN_TENSOR_NCHW) { - channel = input_attrs[0].dims[1]; - height = input_attrs[0].dims[2]; - width = input_attrs[0].dims[3]; - } else { - height = input_attrs[0].dims[1]; - width = input_attrs[0].dims[2]; - channel = input_attrs[0].dims[3]; - } - - printf("[rkYolov8] Init: %dx%d, Output Num: %d\n", width, height, - io_num.n_output); - - rga_buffer_size = width * height * channel; - rga_buffer_ptr = malloc(rga_buffer_size); - memset(rga_buffer_ptr, 114, rga_buffer_size); - - return 0; -} - -detect_result_group_t rkYolov8::infer(const cv::Mat &ori_img) { - detect_result_group_t detect_result; - memset(&detect_result, 0, sizeof(detect_result_group_t)); - - if (ori_img.empty()) - return detect_result; - - int img_w = ori_img.cols; - int img_h = ori_img.rows; - float scale = std::min((float)width / img_w, (float)height / img_h); - int new_w = (int)(img_w * scale); - int new_h = (int)(img_h * scale); - int pad_w = (width - new_w) / 2; - int pad_h = (height - new_h) / 2; - - rga_buffer_t src_img = wrapbuffer_virtualaddr((void *)ori_img.data, img_w, - img_h, RK_FORMAT_BGR_888); - rga_buffer_t dst_img = - wrapbuffer_virtualaddr(rga_buffer_ptr, width, height, RK_FORMAT_RGB_888); - - rga_buffer_t pat; - memset(&pat, 0, sizeof(pat)); - im_rect src_rect = {0, 0, img_w, img_h}; - im_rect dst_rect = {pad_w, pad_h, new_w, new_h}; - im_rect pat_rect = {0, 0, 0, 0}; - - memset(rga_buffer_ptr, 114, rga_buffer_size); - improcess(src_img, dst_img, pat, src_rect, dst_rect, pat_rect, IM_SYNC); - - inputs[0].index = 0; - inputs[0].type = RKNN_TENSOR_UINT8; - inputs[0].size = rga_buffer_size; - inputs[0].fmt = RKNN_TENSOR_NHWC; - inputs[0].buf = rga_buffer_ptr; - rknn_inputs_set(ctx, io_num.n_input, inputs); - - rknn_output outputs[io_num.n_output]; - memset(outputs, 0, sizeof(outputs)); - for (int i = 0; i < io_num.n_output; i++) - outputs[i].want_float = 1; - - // FILE *fp = fopen("/app/debug_input.rgb", "wb"); - // fwrite(rga_buffer_ptr, 1, rga_buffer_size, fp); - // fclose(fp); - // printf("Saved debug input image.\n"); - - rknn_run(ctx, nullptr); - rknn_outputs_get(ctx, io_num.n_output, outputs, nullptr); - - post_process_v8_dfl(outputs, scale, pad_w, pad_h, &detect_result); - - rknn_outputs_release(ctx, io_num.n_output, outputs); - return detect_result; -} - -void rkYolov8::post_process_v8_dfl(rknn_output *outputs, float scale, int pad_w, - int pad_h, detect_result_group_t *group) { - std::vector filterBoxes; - std::vector objProbs; - std::vector classId; - - int output_per_branch = io_num.n_output / 3; - - for (int i = 0; i < 3; i++) { - - int box_idx = i * output_per_branch; - int cls_idx = i * output_per_branch + 1; - - float *box_tensor = (float *)outputs[box_idx].buf; - float *cls_tensor = (float *)outputs[cls_idx].buf; - - int grid_h = output_attrs[box_idx].dims[2]; - int grid_w = output_attrs[box_idx].dims[3]; - int stride = height / grid_h; - - int box_channel = output_attrs[box_idx].dims[1]; - int dfl_len = box_channel / 4; - - int grid_len = grid_h * grid_w; - - for (int h = 0; h < grid_h; h++) { - for (int w = 0; w < grid_w; w++) { - int offset = h * grid_w + w; - - float max_score = 0.0f; - int max_class_id = -1; - - for (int c = 0; c < m_class_num; c++) { - - int idx = c * grid_len + offset; - float score = cls_tensor[idx]; - // printf("Raw: %f, Sigmoid: %f\n", raw_score, sigmoid(raw_score)); - - // float score = sigmoid(cls_tensor[idx]); - if (score > max_score) { - max_score = score; - max_class_id = c; - } - } - - if (max_score > conf_threshold && max_class_id == 0) { - float box_pred[4]; - float dfl_buffer[64]; - - for (int k = 0; k < 4 * dfl_len; k++) { - dfl_buffer[k] = box_tensor[k * grid_len + offset]; - } - compute_dfl(dfl_buffer, dfl_len, box_pred); - - float x1 = (-box_pred[0] + w + 0.5f) * stride; - float y1 = (-box_pred[1] + h + 0.5f) * stride; - float x2 = (box_pred[2] + w + 0.5f) * stride; - float y2 = (box_pred[3] + h + 0.5f) * stride; - - filterBoxes.push_back(x1); - filterBoxes.push_back(y1); - filterBoxes.push_back(x2 - x1); - filterBoxes.push_back(y2 - y1); - - objProbs.push_back(max_score); - classId.push_back(max_class_id); - } - - if (max_score > conf_threshold) { - - float box_pred[4]; - float dfl_buffer[64]; - - for (int k = 0; k < 4 * dfl_len; k++) { - dfl_buffer[k] = box_tensor[k * grid_len + offset]; - } - - compute_dfl(dfl_buffer, dfl_len, box_pred); - - float x1 = (-box_pred[0] + w + 0.5f) * stride; - float y1 = (-box_pred[1] + h + 0.5f) * stride; - float x2 = (box_pred[2] + w + 0.5f) * stride; - float y2 = (box_pred[3] + h + 0.5f) * stride; - - filterBoxes.push_back(x1); - filterBoxes.push_back(y1); - filterBoxes.push_back(x2 - x1); - filterBoxes.push_back(y2 - y1); - - objProbs.push_back(max_score); - classId.push_back(max_class_id); - } - } - } - } - - std::vector cvBoxes; - for (size_t i = 0; i < filterBoxes.size(); i += 4) { - cvBoxes.push_back(cv::Rect(filterBoxes[i], filterBoxes[i + 1], - filterBoxes[i + 2], filterBoxes[i + 3])); - } - - std::vector indices; - cv::dnn::NMSBoxes(cvBoxes, objProbs, conf_threshold, nms_threshold, indices); - - int count = 0; - for (int idx : indices) { - if (count >= OBJ_NUMB_MAX_SIZE) - break; - - cv::Rect box = cvBoxes[idx]; - - int x = (int)((box.x - pad_w) / scale); - int y = (int)((box.y - pad_h) / scale); - int width = (int)(box.width / scale); - int height = (int)(box.height / scale); - - detect_result_t *det = &group->results[count]; - det->box.left = std::max(0, x); - det->box.top = std::max(0, y); - det->box.right = x + width; - det->box.bottom = y + height; - det->prop = objProbs[idx]; - snprintf(det->name, OBJ_NAME_MAX_SIZE, "%d", classId[idx]); - - count++; - } - group->count = count; -} \ No newline at end of file diff --git a/src/rknn/rkYolov8.hpp b/src/rknn/rkYolov8.hpp deleted file mode 100644 index 77469fd..0000000 --- a/src/rknn/rkYolov8.hpp +++ /dev/null @@ -1,52 +0,0 @@ -#ifndef RKYOLOV8_H -#define RKYOLOV8_H - -#include "opencv2/core/core.hpp" -#include "postprocess.h" -#include "rknn_api.h" - -#include "im2d.h" -#include "rga.h" - -#include -#include - -class rkYolov8 { -private: - int ret; - std::string model_path; - unsigned char *model_data; - - rknn_context ctx; - rknn_input_output_num io_num; - rknn_tensor_attr *input_attrs; - rknn_tensor_attr *output_attrs; - rknn_input inputs[1]; - - int channel, width, height; - - float nms_threshold; - float conf_threshold; - - std::string m_label_path; - int m_class_num; - - void *rga_buffer_ptr = nullptr; - int rga_buffer_size = 0; - - static unsigned char *load_model(const char *filename, int *model_size); - - void post_process_v8_dfl(rknn_output *outputs, float scale, int pad_w, - int pad_h, detect_result_group_t *group); - -public: - rkYolov8(const std::string &model_path, const std::string &label_path, - int class_num); - ~rkYolov8(); - - int init(rknn_context *ctx_in, bool is_slave); - rknn_context *get_pctx(); - detect_result_group_t infer(const cv::Mat &ori_img); -}; - -#endif \ No newline at end of file diff --git a/src/rknn/rknnPool.hpp b/src/rknn/rknnPool.hpp deleted file mode 100644 index ea59773..0000000 --- a/src/rknn/rknnPool.hpp +++ /dev/null @@ -1,148 +0,0 @@ -#ifndef RKNNPOOL_H -#define RKNNPOOL_H - -#include "rknn/ThreadPool.hpp" -#include "spdlog/spdlog.h" -#include -#include -#include -#include -#include -#include - -template -class rknnPool { -private: - int threadNum; - std::string modelPath; - - std::string m_label_path; - int m_class_num; - - long long id; - std::mutex idMtx, queueMtx; - std::unique_ptr pool; - std::queue> futs; - std::vector> models; - - std::atomic is_stopped{false}; - -protected: - int getModelId(); - -public: - rknnPool(const std::string modelPath, int threadNum, - const std::string &label_path, int class_num); - int init(); - // 模型推理/Model inference - int put(inputType inputData); - // 获取推理结果/Get the results of your inference - int get(outputType &outputData); - - void stop(); - - ~rknnPool(); -}; - -template -rknnPool::rknnPool( - const std::string modelPath, int threadNum, const std::string &label_path, - int class_num) { - this->modelPath = modelPath; - this->threadNum = threadNum; - this->m_label_path = label_path; - this->m_class_num = class_num; - this->id = 0; - this->is_stopped = false; -} - -template -int rknnPool::init() { - - try { - this->pool = std::make_unique(this->threadNum); - for (int i = 0; i < this->threadNum; i++) { - models.push_back(std::make_shared( - this->modelPath.c_str(), this->m_label_path, this->m_class_num)); - } - } catch (const std::bad_alloc &e) { - std::cout << "Out of memory: " << e.what() << std::endl; - return -1; - } - // 初始化模型/Initialize the model - for (int i = 0, ret = 0; i < threadNum; i++) { - ret = models[i]->init(models[0]->get_pctx(), i != 0); - if (ret != 0) - return ret; - } - - return 0; -} - -template -int rknnPool::getModelId() { - std::lock_guard lock(idMtx); - int modelId = id % threadNum; - id++; - return modelId; -} - -template -int rknnPool::put(inputType inputData) { - if (is_stopped) { - return -1; // Or handle as an error - } - - std::lock_guard lock(queueMtx); - futs.push( - pool->submit(&rknnModel::infer, models[this->getModelId()], inputData)); - return 0; -} - -template -int rknnPool::get(outputType &outputData) { - std::future future_to_get; - - { - std::lock_guard lock(queueMtx); - if (futs.empty() == true) - return 1; - - future_to_get = std::move(futs.front()); - futs.pop(); - } - - outputData = future_to_get.get(); - return 0; -} - -template -void rknnPool::stop() { - - if (is_stopped.exchange(true)) { - return; - } - - while (true) { - std::future future_to_get; - { - std::lock_guard lock(queueMtx); - if (futs.empty()) { - break; - } - future_to_get = std::move(futs.front()); - futs.pop(); - } - - future_to_get.get(); - } - - pool.reset(); -} - -template -rknnPool::~rknnPool() { - stop(); -} - -#endif diff --git a/src/rknn/rknn_api.h b/src/rknn/rknn_api.h deleted file mode 100755 index 1ff6a93..0000000 --- a/src/rknn/rknn_api.h +++ /dev/null @@ -1,720 +0,0 @@ -/**************************************************************************** -* -* Copyright (c) 2017 - 2022 by Rockchip Corp. All rights reserved. -* -* The material in this file is confidential and contains trade secrets -* of Rockchip Corporation. This is proprietary information owned by -* Rockchip Corporation. No part of this work may be disclosed, -* reproduced, copied, transmitted, or used in any way for any purpose, -* without the express written permission of Rockchip Corporation. -* -*****************************************************************************/ - - -#ifndef _RKNN_API_H -#define _RKNN_API_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include - -/* - Definition of extended flag for rknn_init. -*/ -/* set high priority context. */ -#define RKNN_FLAG_PRIOR_HIGH 0x00000000 - -/* set medium priority context */ -#define RKNN_FLAG_PRIOR_MEDIUM 0x00000001 - -/* set low priority context. */ -#define RKNN_FLAG_PRIOR_LOW 0x00000002 - -/* asynchronous mode. - when enable, rknn_outputs_get will not block for too long because it directly retrieves the result of - the previous frame which can increase the frame rate on single-threaded mode, but at the cost of - rknn_outputs_get not retrieves the result of the current frame. - in multi-threaded mode you do not need to turn this mode on. */ -#define RKNN_FLAG_ASYNC_MASK 0x00000004 - -/* collect performance mode. - when enable, you can get detailed performance reports via rknn_query(ctx, RKNN_QUERY_PERF_DETAIL, ...), - but it will reduce the frame rate. */ -#define RKNN_FLAG_COLLECT_PERF_MASK 0x00000008 - -/* allocate all memory in outside, includes weight/internal/inputs/outputs */ -#define RKNN_FLAG_MEM_ALLOC_OUTSIDE 0x00000010 - -/* weight sharing with the same network structure */ -#define RKNN_FLAG_SHARE_WEIGHT_MEM 0x00000020 - -/* send fence fd from outside */ -#define RKNN_FLAG_FENCE_IN_OUTSIDE 0x00000040 - -/* get fence fd from inside */ -#define RKNN_FLAG_FENCE_OUT_OUTSIDE 0x00000080 - -/* dummy init flag: could only get total_weight_size and total_internal_size by rknn_query*/ -#define RKNN_FLAG_COLLECT_MODEL_INFO_ONLY 0x00000100 - -/* set GPU as the preferred execution backend When the operator is not supported by the NPU */ -#define RKNN_FLAG_EXECUTE_FALLBACK_PRIOR_DEVICE_GPU 0x00000400 - -/* allocate internal memory in outside */ -#define RKNN_FLAG_INTERNAL_ALLOC_OUTSIDE 0x00000200 - -/* - Error code returned by the RKNN API. -*/ -#define RKNN_SUCC 0 /* execute succeed. */ -#define RKNN_ERR_FAIL -1 /* execute failed. */ -#define RKNN_ERR_TIMEOUT -2 /* execute timeout. */ -#define RKNN_ERR_DEVICE_UNAVAILABLE -3 /* device is unavailable. */ -#define RKNN_ERR_MALLOC_FAIL -4 /* memory malloc fail. */ -#define RKNN_ERR_PARAM_INVALID -5 /* parameter is invalid. */ -#define RKNN_ERR_MODEL_INVALID -6 /* model is invalid. */ -#define RKNN_ERR_CTX_INVALID -7 /* context is invalid. */ -#define RKNN_ERR_INPUT_INVALID -8 /* input is invalid. */ -#define RKNN_ERR_OUTPUT_INVALID -9 /* output is invalid. */ -#define RKNN_ERR_DEVICE_UNMATCH -10 /* the device is unmatch, please update rknn sdk - and npu driver/firmware. */ -#define RKNN_ERR_INCOMPATILE_PRE_COMPILE_MODEL -11 /* This RKNN model use pre_compile mode, but not compatible with current driver. */ -#define RKNN_ERR_INCOMPATILE_OPTIMIZATION_LEVEL_VERSION -12 /* This RKNN model set optimization level, but not compatible with current driver. */ -#define RKNN_ERR_TARGET_PLATFORM_UNMATCH -13 /* This RKNN model set target platform, but not compatible with current platform. */ - -/* - Definition for tensor -*/ -#define RKNN_MAX_DIMS 16 /* maximum dimension of tensor. */ -#define RKNN_MAX_NUM_CHANNEL 15 /* maximum channel number of input tensor. */ -#define RKNN_MAX_NAME_LEN 256 /* maximum name lenth of tensor. */ -#define RKNN_MAX_DYNAMIC_SHAPE_NUM 512 /* maximum number of dynamic shape for each input. */ - -#ifdef __arm__ -typedef uint32_t rknn_context; -#else -typedef uint64_t rknn_context; -#endif - - -/* - The query command for rknn_query -*/ -typedef enum _rknn_query_cmd { - RKNN_QUERY_IN_OUT_NUM = 0, /* query the number of input & output tensor. */ - RKNN_QUERY_INPUT_ATTR = 1, /* query the attribute of input tensor. */ - RKNN_QUERY_OUTPUT_ATTR = 2, /* query the attribute of output tensor. */ - RKNN_QUERY_PERF_DETAIL = 3, /* query the detail performance, need set - RKNN_FLAG_COLLECT_PERF_MASK when call rknn_init, - this query needs to be valid after rknn_outputs_get. */ - RKNN_QUERY_PERF_RUN = 4, /* query the time of run, - this query needs to be valid after rknn_outputs_get. */ - RKNN_QUERY_SDK_VERSION = 5, /* query the sdk & driver version */ - - RKNN_QUERY_MEM_SIZE = 6, /* query the weight & internal memory size */ - RKNN_QUERY_CUSTOM_STRING = 7, /* query the custom string */ - - RKNN_QUERY_NATIVE_INPUT_ATTR = 8, /* query the attribute of native input tensor. */ - RKNN_QUERY_NATIVE_OUTPUT_ATTR = 9, /* query the attribute of native output tensor. */ - - RKNN_QUERY_NATIVE_NC1HWC2_INPUT_ATTR = 8, /* query the attribute of native input tensor. */ - RKNN_QUERY_NATIVE_NC1HWC2_OUTPUT_ATTR = 9, /* query the attribute of native output tensor. */ - - RKNN_QUERY_NATIVE_NHWC_INPUT_ATTR = 10, /* query the attribute of native input tensor. */ - RKNN_QUERY_NATIVE_NHWC_OUTPUT_ATTR = 11, /* query the attribute of native output tensor. */ - - RKNN_QUERY_DEVICE_MEM_INFO = 12, /* query the attribute of rknn memory information. */ - - RKNN_QUERY_INPUT_DYNAMIC_RANGE = 13, /* query the dynamic shape range of rknn input tensor. */ - RKNN_QUERY_CURRENT_INPUT_ATTR = 14, /* query the current shape of rknn input tensor, only valid for dynamic rknn model*/ - RKNN_QUERY_CURRENT_OUTPUT_ATTR = 15, /* query the current shape of rknn output tensor, only valid for dynamic rknn model*/ - - RKNN_QUERY_CURRENT_NATIVE_INPUT_ATTR = 16, /* query the current native shape of rknn input tensor, only valid for dynamic rknn model*/ - RKNN_QUERY_CURRENT_NATIVE_OUTPUT_ATTR = 17, /* query the current native shape of rknn output tensor, only valid for dynamic rknn model*/ - - - RKNN_QUERY_CMD_MAX -} rknn_query_cmd; - -/* - the tensor data type. -*/ -typedef enum _rknn_tensor_type { - RKNN_TENSOR_FLOAT32 = 0, /* data type is float32. */ - RKNN_TENSOR_FLOAT16, /* data type is float16. */ - RKNN_TENSOR_INT8, /* data type is int8. */ - RKNN_TENSOR_UINT8, /* data type is uint8. */ - RKNN_TENSOR_INT16, /* data type is int16. */ - RKNN_TENSOR_UINT16, /* data type is uint16. */ - RKNN_TENSOR_INT32, /* data type is int32. */ - RKNN_TENSOR_UINT32, /* data type is uint32. */ - RKNN_TENSOR_INT64, /* data type is int64. */ - RKNN_TENSOR_BOOL, - - RKNN_TENSOR_TYPE_MAX -} rknn_tensor_type; - -inline static const char* get_type_string(rknn_tensor_type type) -{ - switch(type) { - case RKNN_TENSOR_FLOAT32: return "FP32"; - case RKNN_TENSOR_FLOAT16: return "FP16"; - case RKNN_TENSOR_INT8: return "INT8"; - case RKNN_TENSOR_UINT8: return "UINT8"; - case RKNN_TENSOR_INT16: return "INT16"; - case RKNN_TENSOR_UINT16: return "UINT16"; - case RKNN_TENSOR_INT32: return "INT32"; - case RKNN_TENSOR_UINT32: return "UINT32"; - case RKNN_TENSOR_INT64: return "INT64"; - case RKNN_TENSOR_BOOL: return "BOOL"; - default: return "UNKNOW"; - } -} - -/* - the quantitative type. -*/ -typedef enum _rknn_tensor_qnt_type { - RKNN_TENSOR_QNT_NONE = 0, /* none. */ - RKNN_TENSOR_QNT_DFP, /* dynamic fixed point. */ - RKNN_TENSOR_QNT_AFFINE_ASYMMETRIC, /* asymmetric affine. */ - - RKNN_TENSOR_QNT_MAX -} rknn_tensor_qnt_type; - -inline static const char* get_qnt_type_string(rknn_tensor_qnt_type type) -{ - switch(type) { - case RKNN_TENSOR_QNT_NONE: return "NONE"; - case RKNN_TENSOR_QNT_DFP: return "DFP"; - case RKNN_TENSOR_QNT_AFFINE_ASYMMETRIC: return "AFFINE"; - default: return "UNKNOW"; - } -} - -/* - the tensor data format. -*/ -typedef enum _rknn_tensor_format { - RKNN_TENSOR_NCHW = 0, /* data format is NCHW. */ - RKNN_TENSOR_NHWC, /* data format is NHWC. */ - RKNN_TENSOR_NC1HWC2, /* data format is NC1HWC2. */ - RKNN_TENSOR_UNDEFINED, - - RKNN_TENSOR_FORMAT_MAX -} rknn_tensor_format; - -/* - the mode of running on target NPU core. -*/ -typedef enum _rknn_core_mask { - RKNN_NPU_CORE_AUTO = 0, /* default, run on NPU core randomly. */ - RKNN_NPU_CORE_0 = 1, /* run on NPU core 0. */ - RKNN_NPU_CORE_1 = 2, /* run on NPU core 1. */ - RKNN_NPU_CORE_2 = 4, /* run on NPU core 2. */ - RKNN_NPU_CORE_0_1 = RKNN_NPU_CORE_0 | RKNN_NPU_CORE_1, /* run on NPU core 0 and core 1. */ - RKNN_NPU_CORE_0_1_2 = RKNN_NPU_CORE_0_1 | RKNN_NPU_CORE_2, /* run on NPU core 0 and core 1 and core 2. */ - - RKNN_NPU_CORE_UNDEFINED, -} rknn_core_mask; - -inline static const char* get_format_string(rknn_tensor_format fmt) -{ - switch(fmt) { - case RKNN_TENSOR_NCHW: return "NCHW"; - case RKNN_TENSOR_NHWC: return "NHWC"; - case RKNN_TENSOR_NC1HWC2: return "NC1HWC2"; - case RKNN_TENSOR_UNDEFINED: return "UNDEFINED"; - default: return "UNKNOW"; - } -} - -/* - the information for RKNN_QUERY_IN_OUT_NUM. -*/ -typedef struct _rknn_input_output_num { - uint32_t n_input; /* the number of input. */ - uint32_t n_output; /* the number of output. */ -} rknn_input_output_num; - -/* - the information for RKNN_QUERY_INPUT_ATTR / RKNN_QUERY_OUTPUT_ATTR. -*/ -typedef struct _rknn_tensor_attr { - uint32_t index; /* input parameter, the index of input/output tensor, - need set before call rknn_query. */ - - uint32_t n_dims; /* the number of dimensions. */ - uint32_t dims[RKNN_MAX_DIMS]; /* the dimensions array. */ - char name[RKNN_MAX_NAME_LEN]; /* the name of tensor. */ - - uint32_t n_elems; /* the number of elements. */ - uint32_t size; /* the bytes size of tensor. */ - - rknn_tensor_format fmt; /* the data format of tensor. */ - rknn_tensor_type type; /* the data type of tensor. */ - rknn_tensor_qnt_type qnt_type; /* the quantitative type of tensor. */ - int8_t fl; /* fractional length for RKNN_TENSOR_QNT_DFP. */ - int32_t zp; /* zero point for RKNN_TENSOR_QNT_AFFINE_ASYMMETRIC. */ - float scale; /* scale for RKNN_TENSOR_QNT_AFFINE_ASYMMETRIC. */ - - uint32_t w_stride; /* the stride of tensor along the width dimention of input, - Note: it is read-only, 0 means equal to width. */ - uint32_t size_with_stride; /* the bytes size of tensor with stride. */ - - uint8_t pass_through; /* pass through mode, for rknn_set_io_mem interface. - if TRUE, the buf data is passed directly to the input node of the rknn model - without any conversion. the following variables do not need to be set. - if FALSE, the buf data is converted into an input consistent with the model - according to the following type and fmt. so the following variables - need to be set.*/ - uint32_t h_stride; /* the stride along the height dimention of input, - Note: it is write-only, if it was set to 0, h_stride = height. */ -} rknn_tensor_attr; - -typedef struct _rknn_input_range { - uint32_t index; /* input parameter, the index of input/output tensor, - need set before call rknn_query. */ - uint32_t shape_number; /* the number of shape. */ - rknn_tensor_format fmt; /* the data format of tensor. */ - char name[RKNN_MAX_NAME_LEN]; /* the name of tensor. */ - uint32_t dyn_range[RKNN_MAX_DYNAMIC_SHAPE_NUM][RKNN_MAX_DIMS]; /* the dynamic input dimensions range. */ - uint32_t n_dims; /* the number of dimensions. */ - -} rknn_input_range; - -/* - the information for RKNN_QUERY_PERF_DETAIL. -*/ -typedef struct _rknn_perf_detail { - char* perf_data; /* the string pointer of perf detail. don't need free it by user. */ - uint64_t data_len; /* the string length. */ -} rknn_perf_detail; - -/* - the information for RKNN_QUERY_PERF_RUN. -*/ -typedef struct _rknn_perf_run { - int64_t run_duration; /* real inference time (us) */ -} rknn_perf_run; - -/* - the information for RKNN_QUERY_SDK_VERSION. -*/ -typedef struct _rknn_sdk_version { - char api_version[256]; /* the version of rknn api. */ - char drv_version[256]; /* the version of rknn driver. */ -} rknn_sdk_version; - -/* - the information for RKNN_QUERY_MEM_SIZE. -*/ -typedef struct _rknn_mem_size { - uint32_t total_weight_size; /* the weight memory size */ - uint32_t total_internal_size; /* the internal memory size, exclude inputs/outputs */ - uint64_t total_dma_allocated_size; /* total dma memory allocated size */ - uint32_t total_sram_size; /* total system sram size reserved for rknn */ - uint32_t free_sram_size; /* free system sram size reserved for rknn */ - uint32_t reserved[10]; /* reserved */ -} rknn_mem_size; - -/* - the information for RKNN_QUERY_CUSTOM_STRING. -*/ -typedef struct _rknn_custom_string { - char string[1024]; /* the string of custom, lengths max to 1024 bytes */ -} rknn_custom_string; - -/* - The flags of rknn_tensor_mem. -*/ -typedef enum _rknn_tensor_mem_flags { - RKNN_TENSOR_MEMORY_FLAGS_ALLOC_INSIDE = 1, /*Used to mark in rknn_destroy_mem() whether it is necessary to release the "mem" pointer itself. - If the flag RKNN_TENSOR_MEMORY_FLAGS_ALLOC_INSIDE is set, rknn_destroy_mem() will call free(mem).*/ - RKNN_TENSOR_MEMORY_FLAGS_FROM_FD = 2, /*Used to mark in rknn_create_mem_from_fd() whether it is necessary to release the "mem" pointer itself. - If the flag RKNN_TENSOR_MEMORY_FLAGS_FROM_FD is set, rknn_destroy_mem() will call free(mem).*/ - RKNN_TENSOR_MEMORY_FLAGS_FROM_PHYS = 3, /*Used to mark in rknn_create_mem_from_phys() whether it is necessary to release the "mem" pointer itself. - If the flag RKNN_TENSOR_MEMORY_FLAGS_FROM_PHYS is set, rknn_destroy_mem() will call free(mem).*/ - RKNN_TENSOR_MEMORY_FLAGS_UNKNOWN -} rknn_tensor_mem_flags; - -/* - the memory information of tensor. -*/ -typedef struct _rknn_tensor_memory { - void* virt_addr; /* the virtual address of tensor buffer. */ - uint64_t phys_addr; /* the physical address of tensor buffer. */ - int32_t fd; /* the fd of tensor buffer. */ - int32_t offset; /* indicates the offset of the memory. */ - uint32_t size; /* the size of tensor buffer. */ - uint32_t flags; /* the flags of tensor buffer, reserved */ - void * priv_data; /* the private data of tensor buffer. */ -} rknn_tensor_mem; - -/* - the input information for rknn_input_set. -*/ -typedef struct _rknn_input { - uint32_t index; /* the input index. */ - void* buf; /* the input buf for index. */ - uint32_t size; /* the size of input buf. */ - uint8_t pass_through; /* pass through mode. - if TRUE, the buf data is passed directly to the input node of the rknn model - without any conversion. the following variables do not need to be set. - if FALSE, the buf data is converted into an input consistent with the model - according to the following type and fmt. so the following variables - need to be set.*/ - rknn_tensor_type type; /* the data type of input buf. */ - rknn_tensor_format fmt; /* the data format of input buf. - currently the internal input format of NPU is NCHW by default. - so entering NCHW data can avoid the format conversion in the driver. */ -} rknn_input; - -/* - the output information for rknn_outputs_get. -*/ -typedef struct _rknn_output { - uint8_t want_float; /* want transfer output data to float */ - uint8_t is_prealloc; /* whether buf is pre-allocated. - if TRUE, the following variables need to be set. - if FALSE, the following variables do not need to be set. */ - uint32_t index; /* the output index. */ - void* buf; /* the output buf for index. - when is_prealloc = FALSE and rknn_outputs_release called, - this buf pointer will be free and don't use it anymore. */ - uint32_t size; /* the size of output buf. */ -} rknn_output; - -/* - the extend information for rknn_init. -*/ -typedef struct _rknn_init_extend { - rknn_context ctx; /* rknn context */ - int32_t real_model_offset; /* real rknn model file offset, only valid when init context with rknn file path */ - uint32_t real_model_size; /* real rknn model file size, only valid when init context with rknn file path */ - uint8_t reserved[120]; /* reserved */ -} rknn_init_extend; - -/* - the extend information for rknn_run. -*/ -typedef struct _rknn_run_extend { - uint64_t frame_id; /* output parameter, indicate current frame id of run. */ - int32_t non_block; /* block flag of run, 0 is block else 1 is non block */ - int32_t timeout_ms; /* timeout for block mode, in milliseconds */ - int32_t fence_fd; /* fence fd from other unit */ -} rknn_run_extend; - -/* - the extend information for rknn_outputs_get. -*/ -typedef struct _rknn_output_extend { - uint64_t frame_id; /* output parameter, indicate the frame id of outputs, corresponds to - struct rknn_run_extend.frame_id.*/ -} rknn_output_extend; - - -/* rknn_init - - initial the context and load the rknn model. - - input: - rknn_context* context the pointer of context handle. - void* model if size > 0, pointer to the rknn model, if size = 0, filepath to the rknn model. - uint32_t size the size of rknn model. - uint32_t flag extend flag, see the define of RKNN_FLAG_XXX_XXX. - rknn_init_extend* extend the extend information of init. - return: - int error code. -*/ -int rknn_init(rknn_context* context, void* model, uint32_t size, uint32_t flag, rknn_init_extend* extend); - -/* rknn_dup_context - - initial the context and load the rknn model. - - input: - rknn_context* context_in the pointer of context in handle. - rknn_context* context_out the pointer of context out handle. - return: - int error code. -*/ -int rknn_dup_context(rknn_context* context_in, rknn_context* context_out); - -/* rknn_destroy - - unload the rknn model and destroy the context. - - input: - rknn_context context the handle of context. - return: - int error code. -*/ -int rknn_destroy(rknn_context context); - - -/* rknn_query - - query the information about model or others. see rknn_query_cmd. - - input: - rknn_context context the handle of context. - rknn_query_cmd cmd the command of query. - void* info the buffer point of information. - uint32_t size the size of information. - return: - int error code. -*/ -int rknn_query(rknn_context context, rknn_query_cmd cmd, void* info, uint32_t size); - - -/* rknn_inputs_set - - set inputs information by input index of rknn model. - inputs information see rknn_input. - - input: - rknn_context context the handle of context. - uint32_t n_inputs the number of inputs. - rknn_input inputs[] the arrays of inputs information, see rknn_input. - return: - int error code -*/ -int rknn_inputs_set(rknn_context context, uint32_t n_inputs, rknn_input inputs[]); - -/* - rknn_set_batch_core_num - - set rknn batch core_num. - - input: - rknn_context context the handle of context. - int core_num the core number. - return: - int error code. - -*/ -int rknn_set_batch_core_num(rknn_context context, int core_num); - -/* rknn_set_core_mask - - set rknn core mask.(only supported on RK3588 now) - - RKNN_NPU_CORE_AUTO: auto mode, default value - RKNN_NPU_CORE_0: core 0 mode - RKNN_NPU_CORE_1: core 1 mode - RKNN_NPU_CORE_2: core 2 mode - RKNN_NPU_CORE_0_1: combine core 0/1 mode - RKNN_NPU_CORE_0_1_2: combine core 0/1/2 mode - - input: - rknn_context context the handle of context. - rknn_core_mask core_mask the core mask. - return: - int error code. -*/ -int rknn_set_core_mask(rknn_context context, rknn_core_mask core_mask); - -/* rknn_run - - run the model to execute inference. - - input: - rknn_context context the handle of context. - rknn_run_extend* extend the extend information of run. - return: - int error code. -*/ -int rknn_run(rknn_context context, rknn_run_extend* extend); - - -/* rknn_wait - - wait the model after execute inference. - - input: - rknn_context context the handle of context. - rknn_run_extend* extend the extend information of run. - return: - int error code. -*/ -int rknn_wait(rknn_context context, rknn_run_extend* extend); - - -/* rknn_outputs_get - - wait the inference to finish and get the outputs. - this function will block until inference finish. - the results will set to outputs[]. - - input: - rknn_context context the handle of context. - uint32_t n_outputs the number of outputs. - rknn_output outputs[] the arrays of output, see rknn_output. - rknn_output_extend* the extend information of output. - return: - int error code. -*/ -int rknn_outputs_get(rknn_context context, uint32_t n_outputs, rknn_output outputs[], rknn_output_extend* extend); - - -/* rknn_outputs_release - - release the outputs that get by rknn_outputs_get. - after called, the rknn_output[x].buf get from rknn_outputs_get will - also be free when rknn_output[x].is_prealloc = FALSE. - - input: - rknn_context context the handle of context. - uint32_t n_ouputs the number of outputs. - rknn_output outputs[] the arrays of output. - return: - int error code -*/ -int rknn_outputs_release(rknn_context context, uint32_t n_ouputs, rknn_output outputs[]); - - -/* new api for zero copy */ - -/* rknn_create_mem_from_phys (memory allocated outside) - - initialize tensor memory from physical address. - - input: - rknn_context ctx the handle of context. - uint64_t phys_addr physical address. - void *virt_addr virtual address. - uint32_t size the size of tensor buffer. - return: - rknn_tensor_mem the pointer of tensor memory information. -*/ -rknn_tensor_mem* rknn_create_mem_from_phys(rknn_context ctx, uint64_t phys_addr, void *virt_addr, uint32_t size); - - -/* rknn_create_mem_from_fd (memory allocated outside) - - initialize tensor memory from file description. - - input: - rknn_context ctx the handle of context. - int32_t fd file description. - void *virt_addr virtual address. - uint32_t size the size of tensor buffer. - int32_t offset indicates the offset of the memory (virt_addr without offset). - return: - rknn_tensor_mem the pointer of tensor memory information. -*/ -rknn_tensor_mem* rknn_create_mem_from_fd(rknn_context ctx, int32_t fd, void *virt_addr, uint32_t size, int32_t offset); - - -/* rknn_create_mem_from_mb_blk (memory allocated outside) - - create tensor memory from mb_blk. - - input: - rknn_context ctx the handle of context. - void *mb_blk mb_blk allocate from system api. - int32_t offset indicates the offset of the memory. - return: - rknn_tensor_mem the pointer of tensor memory information. -*/ -rknn_tensor_mem* rknn_create_mem_from_mb_blk(rknn_context ctx, void *mb_blk, int32_t offset); - - -/* rknn_create_mem (memory allocated inside) - - create tensor memory. - - input: - rknn_context ctx the handle of context. - uint32_t size the size of tensor buffer. - return: - rknn_tensor_mem the pointer of tensor memory information. -*/ -rknn_tensor_mem* rknn_create_mem(rknn_context ctx, uint32_t size); - - -/* rknn_destroy_mem (support allocate inside and outside) - - destroy tensor memory. - - input: - rknn_context ctx the handle of context. - rknn_tensor_mem *mem the pointer of tensor memory information. - return: - int error code -*/ -int rknn_destroy_mem(rknn_context ctx, rknn_tensor_mem *mem); - - -/* rknn_set_weight_mem - - set the weight memory. - - input: - rknn_context ctx the handle of context. - rknn_tensor_mem *mem the array of tensor memory information - return: - int error code. -*/ -int rknn_set_weight_mem(rknn_context ctx, rknn_tensor_mem *mem); - - -/* rknn_set_internal_mem - - set the internal memory. - - input: - rknn_context ctx the handle of context. - rknn_tensor_mem *mem the array of tensor memory information - return: - int error code. -*/ -int rknn_set_internal_mem(rknn_context ctx, rknn_tensor_mem *mem); - - -/* rknn_set_io_mem - - set the input and output tensors buffer. - - input: - rknn_context ctx the handle of context. - rknn_tensor_mem *mem the array of tensor memory information. - rknn_tensor_attr *attr the attribute of input or output tensor buffer. - return: - int error code. -*/ -int rknn_set_io_mem(rknn_context ctx, rknn_tensor_mem *mem, rknn_tensor_attr *attr); - -/* rknn_set_input_shape(deprecated) - - set the input tensor shape (only valid for dynamic shape rknn model). - - input: - rknn_context ctx the handle of context. - rknn_tensor_attr *attr the attribute of input or output tensor buffer. - return: - int error code. -*/ -int rknn_set_input_shape(rknn_context ctx, rknn_tensor_attr* attr); - -/* rknn_set_input_shapes - - set all the input tensor shapes. graph will run under current set of input shapes after rknn_set_input_shapes.(only valid for dynamic shape rknn model). - - input: - rknn_context ctx the handle of context. - uint32_t n_inputs the number of inputs. - rknn_tensor_attr attr[] the attribute array of all input tensors. - return: - int error code. -*/ -int rknn_set_input_shapes(rknn_context ctx, uint32_t n_inputs, rknn_tensor_attr attr[]); - -#ifdef __cplusplus -} //extern "C" -#endif - -#endif //_RKNN_API_H diff --git a/src/rknn/rknn_matmul_api.h b/src/rknn/rknn_matmul_api.h deleted file mode 100644 index 6c514bd..0000000 --- a/src/rknn/rknn_matmul_api.h +++ /dev/null @@ -1,261 +0,0 @@ -/**************************************************************************** - * - * Copyright (c) 2017 - 2018 by Rockchip Corp. All rights reserved. - * - * The material in this file is confidential and contains trade secrets - * of Rockchip Corporation. This is proprietary information owned by - * Rockchip Corporation. No part of this work may be disclosed, - * reproduced, copied, transmitted, or used in any way for any purpose, - * without the express written permission of Rockchip Corporation. - * - *****************************************************************************/ - -#ifndef _RKNN_MATMUL_API_H -#define _RKNN_MATMUL_API_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include "rknn_api.h" - -typedef rknn_context rknn_matmul_ctx; - -typedef struct _rknn_matmul_tensor_attr -{ - char name[RKNN_MAX_NAME_LEN]; - - // indicate A(M, K) or B(K, N) or C(M, N) - uint32_t n_dims; - uint32_t dims[RKNN_MAX_DIMS]; - - // matmul tensor size - uint32_t size; - - // matmul tensor data type - // int8 : A, B - // int32: C - rknn_tensor_type type; -} rknn_matmul_tensor_attr; - -typedef struct _rknn_matmul_io_attr -{ - // indicate A(M, K) or B(K, N) or C(M, N) - rknn_matmul_tensor_attr A; - rknn_matmul_tensor_attr B; - rknn_matmul_tensor_attr C; -} rknn_matmul_io_attr; - -/* - matmul information struct - */ -typedef struct rknn_matmul_info_t -{ - int32_t M; - int32_t K; // limit: rk356x: int8 type must be aligned with 32byte, float16 type must be aligned with 16byte; - // rk3588: int8 type must be aligned with 32byte, float16 type must be aligned with 32byte; - int32_t N; // limit: rk356x: int8 type must be aligned with 16byte, float16 type must be aligned with 8byte; - // rk3588: int8 type must be aligned with 32byte, float16 type must be aligned with 16byte; - - // matmul data type - // int8: int8(A) x int8(B) -> int32(C) - // float16: float16(A) x float16(B) -> float32(C) - rknn_tensor_type type; - - // matmul native layout for B - // 0: normal layout - // 1: native layout - int32_t native_layout; - - // matmul perf layout for A and C - // 0: normal layout - // 1: perf layout - int32_t perf_layout; -} rknn_matmul_info; - -/* rknn_matmul_create - - params: - rknn_matmul_ctx *ctx the handle of context. - rknn_matmul_info *info the matmal information. - rknn_matmul_io_attr *io_attr inputs/output attribute - return: - int error code -*/ -int rknn_matmul_create(rknn_matmul_ctx* ctx, rknn_matmul_info* info, rknn_matmul_io_attr* io_attr); - -/* rknn_matmul_set_io_mem - - params: - rknn_matmul_ctx ctx the handle of context. - rknn_tensor_mem *mem the pointer of tensor memory information. - rknn_matmul_tensor_attr *attr the attribute of input or output tensor buffer. - return: - int error code. - - formula: - C = A * B, - - limit: - K <= 4096 - K limit: rk356x: int8 type must be aligned with 32byte, float16 type must be aligned with 16byte; - rk3588: int8 type must be aligned with 32byte, float16 type must be aligned with 32byte; - N limit: rk356x: int8 type must be aligned with 16byte, float16 type must be aligned with 8byte; - rk3588: int8 type must be aligned with 32byte, float16 type must be aligned with 16byte; - - A shape: M x K - normal layout: (M, K) - [M1K1, M1K2, ..., M1Kk, - M2K1, M2K2, ..., M2Kk, - ... - MmK1, MmK2, ..., MmKk] - for rk356x: - int8: - perf layout: (K / 8, M, 8) - [K1M1, K2M1, ..., K8M1, - K9M2, K10M2, ..., K16M2, - ... - K(k-7)Mm, K(k-6)Mm, ..., KkMm] - float16: - perf layout: (K / 4, M, 4) - [K1M1, K2M1, ..., K4M1, - K9M2, K10M2, ..., K8M2, - ... - K(k-3)Mm, K(k-2)Mm, ..., KkMm] - for rk3588: - int8: - perf layout: (K / 16, M, 16) - [K1M1, K2M1, ..., K16M1, - K9M2, K10M2, ..., K32M2, - ... - K(k-15)Mm, K(k-14)Mm, ..., KkMm] - float16: - perf layout: (K / 8, M, 8) - [K1M1, K2M1, ..., K8M1, - K9M2, K10M2, ..., K16M2, - ... - K(k-7)Mm, K(k-6)Mm, ..., KkMm] - B shape: K x N - normal layout: (K, N) - [K1N1, K1N2, ..., K1Nn, - K2N1, K2N2, ..., K2Nn, - ... - KkN1, KkN2, ..., KkNn] - for rk356x: - int8: - native layout: (N / 16, K / 32, 16, 32) - [K1N1, K2N1, ..., K32N1, - K1N2, K2N2, ..., K32N2, - ... - K1N16, K2N16, ..., K32N16, - K33N1, K34N1, ..., K64N1, - K33N2, K34N2, ..., K64N2, - ... - K(k-31)N16, K(k-30)N16, ..., KkN16, - K1N17, K2N17, ..., K32N17, - K1N18, K2N18, ..., K32N18, - ... - K(k-31)Nn, K(k-30)Nn, ..., KkNn] - float16: - native layout: (N / 8, K / 16, 8, 16) - [K1N1, K2N1, ..., K16N1, - K1N2, K2N2, ..., K16N2, - ... - K1N8, K2N8, ..., K16N8, - K17N1, K18N1, ..., K32N1, - K17N2, K18N2, ..., K32N2, - ... - K(k-15)N8, K(k-30)N8, ..., KkN8, - K1N9, K2N9, ..., K16N9, - K1N10, K2N10, ..., K16N10, - ... - K(k-15)Nn, K(k-14)Nn, ..., KkNn] - for rk3588: - int8: - native layout: (N / 32, K / 32, 32, 32) - [K1N1, K2N1, ..., K32N1, - K1N2, K2N2, ..., K32N2, - ... - K1N32, K2N32, ..., K32N32, - K33N1, K34N1, ..., K64N1, - K33N2, K34N2, ..., K64N2, - ... - K(k-31)N32, K(k-30)N32, ..., KkN32, - K1N33, K2N33, ..., K32N33, - K1N34, K2N34, ..., K32N34, - ... - K(k-31)Nn, K(k-30)Nn, ..., KkNn] - float16: - native layout: (N / 16, K / 32, 16, 32) - [K1N1, K2N1, ..., K32N1, - K1N2, K2N2, ..., K32N2, - ... - K1N16, K2N16, ..., K32N16, - K33N1, K34N1, ..., K64N1, - K33N2, K34N2, ..., K64N2, - ... - K(k-31)N16, K(k-30)N16, ..., KkN16, - K1N17, K2N17, ..., K32N17, - K1N18, K2N18, ..., K32N18, - ... - K(k-31)Nn, K(k-30)Nn, ..., KkNn] - C shape: M x N - normal layout: (M, N) - [M1N1, M1N2, ..., M1Nn, - M2N1, M2N2, ..., M2Nn, - ... - MmN1, MmN2, ..., MmNn] - perf layout: (N / 4, M, 4) - [N1M1, N2M1, ..., N4M1, - N5M2, N6M2, ..., N8M2, - ... - N(n-3)Mm, N(n-2)Mm, ..., NnMm] - */ -int rknn_matmul_set_io_mem(rknn_matmul_ctx ctx, rknn_tensor_mem* mem, rknn_matmul_tensor_attr* attr); - -/* rknn_matmul_set_core_mask - - set rknn core mask.(only support rk3588 in current) - - RKNN_NPU_CORE_AUTO: auto mode, default value - RKNN_NPU_CORE_0: core 0 mode - RKNN_NPU_CORE_1: core 1 mode - RKNN_NPU_CORE_2: core 2 mode - RKNN_NPU_CORE_0_1: combine core 0/1 mode - RKNN_NPU_CORE_0_1_2: combine core 0/1/2 mode - - input: - rknn_matmul_ctx context the handle of context. - rknn_core_mask core_mask the core mask. - return: - int error code. -*/ -int rknn_matmul_set_core_mask(rknn_matmul_ctx context, rknn_core_mask core_mask); - -/* rknn_matmul_run - - run the matmul in blocking mode - - params: - rknn_matmul_ctx ctx the handle of context. - return: - int error code. - */ -int rknn_matmul_run(rknn_matmul_ctx ctx); - -/* rknn_matmul_destroy - - destroy the matmul context - - params: - rknn_matmul_ctx ctx the handle of context. - return: - int error code. - */ -int rknn_matmul_destroy(rknn_matmul_ctx ctx); - -#ifdef __cplusplus -} // extern "C" -#endif - -#endif // _RKNN_MATMUL_API_H \ No newline at end of file diff --git a/src/rknn/video_service.cc b/src/rknn/video_service.cc deleted file mode 100644 index 9641b49..0000000 --- a/src/rknn/video_service.cc +++ /dev/null @@ -1,208 +0,0 @@ -// video_service.cc (修改后) -#include "video_service.h" -#include "opencv2/imgproc/imgproc.hpp" -#include "spdlog/spdlog.h" -#include - -VideoService::VideoService(std::unique_ptr module, - std::string input_url, std::string output_rtsp_url, - nlohmann::json module_config) - : module_(std::move(module)), input_url_(input_url), - output_rtsp_url_(output_rtsp_url), - module_config_(std::move(module_config)), running_(false) { - log_prefix_ = "[VideoService: " + input_url + "]"; - spdlog::info("{} Created. Input: {}, Output: {}", log_prefix_, - input_url_.c_str(), output_rtsp_url_.c_str()); -} - -VideoService::~VideoService() { - if (running_) { - stop(); - } -} - -bool VideoService::start() { - if (!module_ || !module_->init(module_config_)) { - spdlog::error("{} Failed to initialize analysis module!", log_prefix_); - return false; - } - spdlog::info("{} Analysis module initialized successfully.", log_prefix_); - - std::string gst_input_pipeline = "rtspsrc location=" + input_url_ + - " latency=0 protocols=tcp ! " - "rtph265depay ! " - "h265parse ! " - "mppvideodec format=16 ! " - "videoconvert ! " - "video/x-raw,format=BGR ! " - "appsink"; - - spdlog::info("Try to Open RTSP Stream"); - capture_.open(gst_input_pipeline, cv::CAP_GSTREAMER); - - if (!capture_.isOpened()) { - printf("Error: Could not open RTSP stream: %s\n", input_url_.c_str()); - return false; - } else { - spdlog::info("RTSP Stream Opened!"); - } - - frame_width_ = static_cast(capture_.get(cv::CAP_PROP_FRAME_WIDTH)); - frame_height_ = static_cast(capture_.get(cv::CAP_PROP_FRAME_HEIGHT)); - frame_fps_ = capture_.get(cv::CAP_PROP_FPS); - if (frame_fps_ <= 0) - frame_fps_ = 25.0; - - if (frame_width_ == 0 || frame_height_ == 0) { - spdlog::error("{} Failed to get valid frame width or height from GStreamer " - "pipeline (got {}x{}).", - log_prefix_, frame_width_, frame_height_); - spdlog::error("{} This usually means the RTSP stream is unavailable or the " - "GStreamer input pipeline (mppvideodec?) failed.", - log_prefix_); - - cv::Mat test_frame; - if (capture_.read(test_frame) && !test_frame.empty()) { - frame_width_ = test_frame.cols; - frame_height_ = test_frame.rows; - spdlog::info( - "{} Successfully got frame size by reading first frame: {}x{}", - log_prefix_, frame_width_, frame_height_); - - { - std::lock_guard lock(frame_mutex_); - latest_frame_ = test_frame; - new_frame_available_ = true; - } - frame_cv_.notify_one(); - - } else { - spdlog::error( - "{} Failed to read first frame to determine size. Aborting.", - log_prefix_); - capture_.release(); - return false; - } - } - - printf("RTSP stream opened successfully! (%dx%d @ %.2f FPS)\n", frame_width_, - frame_height_, frame_fps_); - - std::string gst_pipeline = "appsrc ! " - "queue max-size-buffers=2 leaky=downstream ! " - "video/x-raw,format=BGR ! " - "videoconvert ! " - "video/x-raw,format=NV12 ! " - "mpph264enc gop=25 rc-mode=fixqp qp-init=26 ! " - "h264parse ! " - "rtspclientsink location=" + - output_rtsp_url_ + " latency=0 protocols=tcp"; - - printf("Using GStreamer output pipeline: %s\n", gst_pipeline.c_str()); - - writer_.open(gst_pipeline, cv::CAP_GSTREAMER, 0, frame_fps_, - cv::Size(frame_width_, frame_height_), true); - - if (!writer_.isOpened()) { - printf("Error: Could not open VideoWriter with GStreamer pipeline.\n"); - capture_.release(); - return false; - } - printf("VideoWriter opened successfully.\n"); - - running_ = true; - reading_thread_ = std::thread(&VideoService::reading_loop, this); - processing_thread_ = std::thread(&VideoService::processing_loop, this); - printf("Processing thread started.\n"); - - return true; -} - -void VideoService::stop() { - printf("Stopping VideoService...\n"); - running_ = false; - - frame_cv_.notify_all(); - - if (reading_thread_.joinable()) { - reading_thread_.join(); - } - - if (processing_thread_.joinable()) { - processing_thread_.join(); - } - printf("Processing thread joined.\n"); - - if (capture_.isOpened()) { - capture_.release(); - } - if (writer_.isOpened()) { - writer_.release(); - } - module_->stop(); - module_.reset(); - - printf("VideoService stopped.\n"); -} - -void VideoService::reading_loop() { - cv::Mat frame; - spdlog::info("Reading thread started."); - - while (running_) { - if (!capture_.read(frame)) { - spdlog::warn( - "Reading loop: Failed to read frame from capture. Stopping service."); - running_ = false; - break; - } - - if (frame.empty()) { - continue; - } - - { - std::lock_guard lock(frame_mutex_); - latest_frame_ = frame; - new_frame_available_ = true; - } - - frame_cv_.notify_one(); - } - - frame_cv_.notify_all(); // 确保 processing_loop 也会退出 - spdlog::info("Reading loop finished."); -} - -void VideoService::processing_loop() { - cv::Mat frame; - - while (running_) { - { - // 1. (不变) 获取帧 - std::unique_lock lock(frame_mutex_); - - frame_cv_.wait(lock, [&] { return new_frame_available_ || !running_; }); - - if (!running_) { - break; - } - - frame = latest_frame_.clone(); - new_frame_available_ = false; - } - - if (frame.empty()) { - continue; - } - if (!module_->process(frame)) { - // 模块报告处理失败 - spdlog::warn("{} Module failed to process frame. Skipping.", log_prefix_); - } - if (writer_.isOpened()) { - writer_.write(frame); - } - } - - spdlog::info("VideoService: Processing loop finished."); -} \ No newline at end of file diff --git a/src/rknn/video_service.h b/src/rknn/video_service.h deleted file mode 100644 index 010f5f7..0000000 --- a/src/rknn/video_service.h +++ /dev/null @@ -1,55 +0,0 @@ -// video_service.h (修改后) -#pragma once - -#include "algorithm/IAnalysisModule.h" -#include "nlohmann/json.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include - -class VideoService { -public: - /** - * @brief 构造函数变更: - * 不再接收 model_path 和 thread_num, - * 而是通过依赖注入接收一个抽象的 AI 模块。 - */ - VideoService(std::unique_ptr module, std::string input_url, - std::string output_rtsp_url, nlohmann::json module_config); - - ~VideoService(); - - bool start(); - void stop(); - -private: - void processing_loop(); - void reading_loop(); - - std::unique_ptr module_; - nlohmann::json module_config_; - std::string input_url_; - std::string output_rtsp_url_; - - int frame_width_ = 0; - int frame_height_ = 0; - double frame_fps_ = 0.0; - cv::VideoCapture capture_; - cv::VideoWriter writer_; - - std::thread processing_thread_; - std::thread reading_thread_; - std::atomic running_{false}; - std::mutex frame_mutex_; - std::condition_variable frame_cv_; - cv::Mat latest_frame_; - bool new_frame_available_{false}; - - std::string log_prefix_; -}; \ No newline at end of file diff --git a/src/test.cc b/src/test.cc deleted file mode 100644 index e67bc86..0000000 --- a/src/test.cc +++ /dev/null @@ -1,278 +0,0 @@ -// #include "tts/piper_tts_interface.h" -// #include -// #include - -// int main() { -// // 使用默认配置 (piper 和 /app/piper_models/zh_CN-huayan-medium.onnx) -// PiperTTSInterface tts_speaker; -// // 如果你的piper可执行文件不在PATH中,或者模型路径不同,你可以这样指定: -// // PiperTTSInterface tts_speaker("/path/to/your/piper_executable", "/path/to/your/model.onnx"); -// std::string text_to_say = "请执行上述调试步骤,特别是禁用文件删除后手动检查和播放 WAV 文件,这将提供更多线索"; -// if (tts_speaker.say_text_and_play(text_to_say)) { -// std::cout << "语音播报完成。" << std::endl; -// } else { -// std::cerr << "语音播报失败,请检查日志。" << std::endl; -// } -// return 0; -// } -// #include -// #include -// #include -// #include "systemMonitor/iio_sensor.h" // 包含我们刚刚创建的头文件 -// int main() { -// // --- 配置参数 --- -// // !!! 修改这里以匹配你的设备和通道 !!! -// const std::string iio_device_path = "/sys/bus/iio/devices/iio:device0"; -// const std::string iio_channel_name = "in_voltage2_raw"; // 例如,连接到 IN2 通道 - -// const double adc_reference_voltage = 1.8; // ADC参考电压 -// const int64_t adc_resolution = 4096; // ADC分辨率 (e.g., 2^12 for 12-bit) -// const double signal_gain = 11.0; // 信号通路的增益 -// std::cout << "--- Starting IIO Sensor Reader (Version 2) ---" << std::endl; -// std::cout << "Reading from device: " << iio_device_path << std::endl; -// std::cout << "Reading channel: " << iio_channel_name << std::endl; -// std::cout << "-----------------------------------------------" << std::endl; - -// // 注意:传感器对象在 try 块外创建,如果构造失败(找不到文件),会立即被捕获 -// IioSensor sensor(iio_device_path, iio_channel_name); -// while (true) { -// try { -// // 封装后的调用方式非常简洁! -// // 每次调用 readVoltage,内部都会重新打开文件读取一次 -// double actual_voltage = sensor.readVoltage(adc_reference_voltage, adc_resolution, signal_gain); -// // 输出结果 -// std::cout << "Actual Voltage: " << actual_voltage << " V" << std::endl; -// } catch (const IioSensorError& e) { -// // 如果单次读取失败(例如文件内容格式错误、无权限等),打印错误但继续循环 -// std::cerr << "[!] Read Error: " << e.what() << std::endl; -// } - -// // 每秒钟读取一次 -// std::this_thread::sleep_for(std::chrono::seconds(1)); -// } -// return 0; -// } -// main.cpp - -#include "network/tcp_server.h" -#include "mqtt/mqtt_client.h" -#include "mqtt/mqtt_router.h" -#include "systemMonitor/system_monitor.h" -#include "spdlog/spdlog.h" -#include "deviceManager/device_manager.h" -#include "dataCache/data_cache.h" -#include "dataCache/cache_uploader.h" -#include "web/web_server.h" -#include "dataCache/live_data_cache.h" -#include "dataStorage/data_storage.h" -#include "alert/alarm_manager.h" // <-- 添加报警管理器头文件 - -#include -#include -#include -#include -#include -#include // 用于解析data_json - -// 用于 ASIO 服务的全局 io_context -boost::asio::io_context g_io_context; - -// !!! 你提供的 UnifiedData 结构确认 !!! -struct UnifiedData { - std::string device_id; // 产生数据的设备唯一ID - int64_t timestamp_ms; // 数据产生的时间戳 (毫秒级UTC) - std::string data_json; // 采用JSON字符串格式,具有良好的灵活性和可扩展性 -}; - - -/** - * @brief 周期性轮询系统状态并发布到 MQTT - */ -void poll_system_metrics( - boost::asio::steady_timer& timer, - SystemMonitor::SystemMonitor& monitor, - MqttClient& mqtt_client, - AlarmManager& alarm_manager // <-- 传递 alarm_manager -) { - if (g_io_context.stopped()) return; - auto cpu_util = monitor.getCpuUtilization(); - auto mem_info = monitor.getMemoryInfo(); - double mem_total_gb = mem_info.total_kb / 1024.0 / 1024.0; - double mem_free_gb = mem_info.free_kb / 1024.0 / 1024.0; - double mem_usage_percentage = (mem_total_gb - mem_free_gb) / mem_total_gb * 100.0; - - - std::string topic = "proxy/system_status"; - nlohmann::json system_status_json; - system_status_json["device_id"] = "proxy_system"; // 统一一个设备ID用于报警 - system_status_json["timestamp_ms"] = std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch() - ).count(); - system_status_json["cpu_usage"] = cpu_util.totalUsagePercentage; - system_status_json["mem_total_gb"] = mem_total_gb; - system_status_json["mem_usage_percentage"] = mem_usage_percentage; - - std::string payload = system_status_json.dump(); - mqtt_client.publish(topic, payload); - spdlog::debug("System metrics published."); - - // !!! 对系统指标进行报警检查 !!! - alarm_manager.check_data_for_alarms("proxy_system", system_status_json); - - - timer.expires_at(timer.expiry() + std::chrono::seconds(15)); - timer.async_wait(std::bind(poll_system_metrics, std::ref(timer), std::ref(monitor), std::ref(mqtt_client), std::ref(alarm_manager))); -} - -int main(int argc, char* argv[]) { - - try { - spdlog::set_level(spdlog::level::debug); - spdlog::info("Edge Proxy starting up..."); - } catch (const spdlog::spdlog_ex& ex) { - std::cerr << "Log initialization failed: " << ex.what() << std::endl; - return 1; - } - - spdlog::info("Initializing Data Storage..."); - if (!DataStorage::getInstance().initialize("edge_proxy_data.db")) { - spdlog::critical("Failed to initialize DataStorage. Exiting."); - return 1; - } - - try { - DataCache data_cache; - LiveDataCache live_data_cache; - MqttClient mqtt_client("tcp://localhost:1883", "edge-proxy-main-client"); - - // --- 报警管理器初始化 --- - AlarmManager alarm_manager; - // 加载报警规则 - if (!alarm_manager.load_rules("../config/alarms.json")) { - spdlog::critical("Failed to load alarm rules. Exiting."); - return 1; - } - - // 注册报警回调函数:当报警触发时,通过 MQTT 发布报警消息 - alarm_manager.register_alarm_callback([&](const AlarmEvent& event) { - std::string alarm_topic = "alerts/device/" + event.device_id + "/" + AlarmManager::alarm_level_to_string(event.level); - - nlohmann::json alarm_payload; - alarm_payload["event_id"] = event.event_id; - alarm_payload["rule_id"] = event.rule_id; - alarm_payload["device_id"] = event.device_id; - alarm_payload["data_point_name"] = event.data_point_name; - alarm_payload["current_value"] = event.current_value; - alarm_payload["threshold"] = event.threshold; - alarm_payload["level"] = AlarmManager::alarm_level_to_string(event.level); - alarm_payload["timestamp"] = event.timestamp; - alarm_payload["message"] = event.message; - - // 使用 g_io_context.post 将 MQTT 发布操作调度到主线程,避免在回调中直接阻塞 - g_io_context.post([&, alarm_topic, payload_str = alarm_payload.dump()]() { - mqtt_client.publish(alarm_topic, payload_str, 1, false); // QoS 1, 不保留 - }); - spdlog::info("Published alarm: TOPIC={} PAYLOAD={}", alarm_topic, alarm_payload.dump()); - }); - // --- 报警管理器初始化结束 --- - - - auto report_to_mqtt = [&](const UnifiedData& data) { - // 使用 DataStorage 单例存储处理后的 UnifiedData 对象 - if (DataStorage::getInstance().storeProcessedData(data)) { - spdlog::debug("Successfully stored PROCESSED data for device '{}'", data.device_id); - } else { - spdlog::error("Failed to store PROCESSED data for device '{}'", data.device_id); - } - - // 更新实时数据缓存 - live_data_cache.update_data(data.device_id, data.data_json); - - // !!! 新增:进行数据比较和报警检查 !!! - try { - // 将 UnifiedData::data_json (string) 解析为 nlohmann::json 对象 - // 这假设 data.data_json 总是有效的 JSON - nlohmann::json parsed_data_json = nlohmann::json::parse(data.data_json); - alarm_manager.check_data_for_alarms(data.device_id, parsed_data_json); - } catch (const nlohmann::json::exception& e) { - spdlog::error("Failed to parse data_json for alarm checking for device '{}': {}", data.device_id, e.what()); - } - - if (mqtt_client.is_connected()) { - // 网络正常,直接上报 - std::string topic = "devices/" + data.device_id + "/data"; - g_io_context.post([&, topic, payload = data.data_json]() { - mqtt_client.publish(topic, payload, 1, false); - }); - } else { - // 网络断开,写入缓存 - spdlog::warn("MQTT disconnected. Caching data for device '{}'.", data.device_id); - data_cache.add(data); - } - }; - - DeviceManager device_manager(g_io_context, report_to_mqtt); - MqttRouter mqtt_router(mqtt_client, device_manager); - std::vector listen_ports = { 12345 }; - TCPServer tcp_server(g_io_context, listen_ports, mqtt_client); - SystemMonitor::SystemMonitor monitor; - - if (!data_cache.open("edge_data_cache.db")) { - spdlog::critical("Failed to initialize data cache. Exiting."); - return 1; - } - CacheUploader cache_uploader(g_io_context, mqtt_client, data_cache); - - mqtt_client.set_connected_handler([&](const std::string& cause){ - spdlog::info("MQTT client connected: {}", cause); - cache_uploader.start_upload(); - }); - - mqtt_client.connect(); - mqtt_router.start(); - - - monitor.getCpuUtilization(); - boost::asio::steady_timer system_monitor_timer(g_io_context, std::chrono::seconds(15)); - // 将 alarm_manager 传递给 poll_system_metrics - system_monitor_timer.async_wait(std::bind(poll_system_metrics, std::ref(system_monitor_timer), std::ref(monitor), std::ref(mqtt_client), std::ref(alarm_manager))); - - - device_manager.load_and_start("../config/devices.json"); - - WebServer web_server(monitor, device_manager, live_data_cache, 8080); - web_server.start(); - - boost::asio::signal_set signals(g_io_context, SIGINT, SIGTERM); - signals.async_wait([&](const boost::system::error_code& error, int signal_number) { - spdlog::warn("Interrupt signal ({}) received. Shutting down.", signal_number); - - // a. 首先停止所有数据采集线程 - spdlog::info("[Shutdown] A. Stopping device manager services..."); - device_manager.stop_all(); - - // b. 停止Web服务器线程 - spdlog::info("[Shutdown] B. Stopping web server..."); - web_server.stop(); - - // c. 断开MQTT连接 (这将释放它对io_context的'劫持') - spdlog::info("[Shutdown] C. Disconnecting from MQTT broker..."); - mqtt_client.disconnect(); - - // d. 最后,安全地停止io_context - spdlog::info("[Shutdown] D. Stopping main event loop..."); - g_io_context.stop(); - }); - - spdlog::info("All services are running. Press Ctrl+C to exit."); - g_io_context.run(); - - - } catch (const std::exception& e) { - spdlog::critical("An unhandled exception occurred: {}", e.what()); - return 1; - } - - spdlog::info("Server has been shut down gracefully. Exiting."); - return 0; -} diff --git a/src/tts/piper_tts_interface.cc b/src/tts/piper_tts_interface.cc deleted file mode 100644 index 28961dc..0000000 --- a/src/tts/piper_tts_interface.cc +++ /dev/null @@ -1,100 +0,0 @@ -#include "piper_tts_interface.h" -#include -#include -#include -#include -#include -#include - - -// 构造函数实现 (注意默认参数的设置仅在头文件中一次性完成) -PiperTTSInterface::PiperTTSInterface(const std::string& piper_executable_path, - const std::string& default_model_path) - : piper_executable(piper_executable_path), fixed_model_path(default_model_path) { - // 构造时直接初始化模型路径 - if (fixed_model_path.empty()) { - std::cerr << "Warning: PiperTTSInterface created without a default model path. text_to_speech calls may fail." << std::endl; - } -} - -// 辅助函数,封装 system() 调用,以便更好地控制和调试 -int PiperTTSInterface::execute_command(const std::string& command) { - std::cout << "Executing command: " << command << std::endl; - // 使用WEXITSTATUS宏获取system()返回的实际退出码 - int result = std::system(command.c_str()); - if (result == -1) { // system() 失败 - std::cerr << "system() call failed: " << strerror(errno) << std::endl; - return -1; - } - // WEXITSTATUS 定义在 中,但通常 就足够了。 - // 如果编译仍然报错找不到 WEXITSTATUS,可能需要单独引入 。 - if (WIFEXITED(result)) { // 检查子进程是否正常退出 - return WEXITSTATUS(result); // 获取子进程的退出状态码 - } else { - std::cerr << "Command did not exit normally. System call result: " << result << std::endl; - return -1; // 或者其他错误码表示非正常退出 - } -} - -bool PiperTTSInterface::text_to_speech(const std::string& text, - const std::string& output_filename) { - - // 直接使用构造函数中设置的固定模型路径 - if (fixed_model_path.empty()) { - std::cerr << "Error: No fixed model path specified for text_to_speech." << std::endl; - return false; - } - - // 注意:这里没有对 text 进行完整的 shell 转义,对于特殊字符可能需要更健壮的处理 - // 对于更安全的shell命令构建,可以考虑使用 popen 和管道,或者专业的库进行转义 - std::string escaped_text = text; - - std::string command = "echo \"" + escaped_text + "\" | " + - piper_executable + " --model " + fixed_model_path + - " --output_file " + output_filename; - - int result = execute_command(command); - - if (result == 0) { // execute_command() 返回 0 表明命令执行成功 - std::cout << "Successfully generated audio: " << output_filename << std::endl; - return true; - } else { - std::cerr << "Failed to generate audio for '" << text << "'. Command exited with code: " << result << std::endl; - return false; - } -} - -// 新增函数:文本转语音,播放,然后删除文件 -bool PiperTTSInterface::say_text_and_play(const std::string& text) { - // 1. 生成唯一的临时文件名 - auto now = std::chrono::high_resolution_clock::now(); - auto nanoseconds = std::chrono::duration_cast(now.time_since_epoch()).count(); - std::string temp_filename = "/tmp/piper_temp_" + std::to_string(nanoseconds) + ".wav"; - std::cout << "Generating temporary audio file: " << temp_filename << " for text: '" << text << "'" << std::endl; - if (!text_to_speech(text, temp_filename)) { - std::cerr << "Failed to generate audio for speech playback." << std::endl; - return false; - } - // 3. 播放音频文件 - 改用 gst-launch-1.0 - // 使用 gst-launch-1.0 播放,它已经证明可以在你的系统上正常工作 - // 我们会直接使用你提供的成功播放的命令模板 - std::string play_command = "gst-launch-1.0 -v filesrc location=" + temp_filename + - " ! decodebin ! audioconvert ! alsasink device=hw:2,0"; - std::cout << "Playing generated audio file using GStreamer: " << temp_filename << std::endl; - int play_result = execute_command(play_command); - if (play_result != 0) { - std::cerr << "Failed to play audio file using GStreamer: " << temp_filename << ". Command exited with code: " << play_result << std::endl; - } else { - std::cout << "Audio playback finished using GStreamer for: " << temp_filename << std::endl; - } - std::this_thread::sleep_for(std::chrono::milliseconds(200)); - - // 4. 删除音频文件(调试完成后可以取消注释) - std::cout << "Deleting temporary audio file: " << temp_filename << std::endl; - if (std::remove(temp_filename.c_str()) != 0) { - std::cerr << "Warning: Failed to delete temporary audio file: " << temp_filename << ". Error: " << strerror(errno) << std::endl; - } else { - std::cout << "Successfully deleted temporary audio file: " << temp_filename << std::endl; - } - return play_result == 0; -} \ No newline at end of file diff --git a/src/tts/piper_tts_interface.h b/src/tts/piper_tts_interface.h deleted file mode 100644 index a03074c..0000000 --- a/src/tts/piper_tts_interface.h +++ /dev/null @@ -1,39 +0,0 @@ -// piper_tts_interface.h -#ifndef PIPER_TTS_INTERFACE_H -#define PIPER_TTS_INTERFACE_H - -#include -#include - -class PiperTTSInterface { -public: - // 构造函数:指定 piper 可执行文件路径 和 默认模型文件路径 - PiperTTSInterface(const std::string& piper_executable_path = "piper", - const std::string& default_model_path = "/app/piper_models/zh_CN-huayan-medium.onnx"); // 默认模型路径直接在这里设置 - - - /** - * @brief 将文本转换为语音并保存为WAV文件。 - * @param text 要转换的文本。 - * @param output_filename 生成的WAV文件名,例如 "output.wav"。 - * @return true 表示成功生成音频,false 表示失败。 - */ - bool text_to_speech(const std::string& text, - const std::string& output_filename); - - /** - * @brief 将文本转换为语音,使用 aplay 播放,然后删除生成的WAV文件。 - * @param text 要说出的文本。 - * @return true 表示成功生成并播放音频,false 表示失败。 - */ - bool say_text_and_play(const std::string& text); // <--- 在这里添加新函数的声明 - -private: - std::string piper_executable; // Piper 可执行文件的路径 - std::string fixed_model_path; // 固定模型路径 - - // 辅助函数,封装 system() 调用,以便更好地控制和调试 - int execute_command(const std::string& command); -}; - -#endif // PIPER_TTS_INTERFACE_H diff --git a/src/videoServiceManager/video_service_manager.cc b/src/videoServiceManager/video_service_manager.cc deleted file mode 100644 index b7d19e7..0000000 --- a/src/videoServiceManager/video_service_manager.cc +++ /dev/null @@ -1,175 +0,0 @@ - -#include "video_service_manager.h" -#include "algorithm/HumanDetectionModule.h" -#include "spdlog/spdlog.h" -#include - -VideoServiceManager::~VideoServiceManager() { stop_all(); } - -bool VideoServiceManager::load_config(const std::string &config_path) { - std::ifstream ifs(config_path); - if (!ifs.is_open()) { - spdlog::error("Failed to open video config file: {}", config_path); - return false; - } - - try { - nlohmann::json video_json; - ifs >> video_json; - - m_enabled = video_json.value("/video_service/enabled"_json_pointer, false); - - if (video_json.contains("video_streams") && - video_json["video_streams"].is_array()) { - m_stream_configs_json = video_json["video_streams"]; - } else { - spdlog::warn("Video config contains no 'video_streams' array."); - m_stream_configs_json = nlohmann::json::array(); - } - - spdlog::info("Successfully loaded video config. Service enabled: {}. " - "Streams found: {}", - m_enabled, m_stream_configs_json.size()); - return true; - - } catch (const nlohmann::json::exception &e) { - spdlog::error("Failed to parse video config file '{}'. Error: {}", - config_path, e.what()); - return false; - } -} - -void VideoServiceManager::load_and_start() { - if (!m_enabled) { - spdlog::warn("VideoService is disabled in video configuration. No streams " - "will be started."); - return; - } - - spdlog::info("Found {} video stream configurations.", - m_stream_configs_json.size()); - - for (const auto &sc_json : m_stream_configs_json) { - - std::string id = sc_json.value("id", "unknown"); - bool enabled = sc_json.value("enabled", false); - std::string input_url = sc_json.value("input_url", ""); - std::string output_rtsp = sc_json.value("output_rtsp", ""); - std::string module_type = sc_json.value("module_type", ""); - nlohmann::json module_config = - sc_json.value("module_config", nlohmann::json::object()); - - if (!enabled) { - spdlog::info( - "Video stream '{}' (input: {}) is disabled in config, skipping.", id, - input_url); - continue; - } - - std::unique_ptr module = nullptr; - - try { - - if (module_type == "intrusion_detection") { - - std::string module_model_path = module_config.value("model_path", ""); - int module_threads = module_config.value("rknn_thread_num", 1); - double threshold = module_config.value("time_threshold_sec", 3.0); - std::vector zone_array; - - if (module_config.contains("intrusion_zone") && - module_config["intrusion_zone"].is_array()) { - zone_array = module_config["intrusion_zone"].get>(); - } - - if (module_threads <= 0) { - spdlog::warn( - "Video stream '{}' has invalid rknn_thread_num. Defaulting to 1.", - id); - module_threads = 1; - } - - cv::Rect zone = (zone_array.size() == 4) - ? cv::Rect(zone_array[0], zone_array[1], - zone_array[2], zone_array[3]) - : cv::Rect(0, 0, 0, 0); - - module = std::make_unique( - module_model_path, module_threads, zone, threshold); - - } else if (module_type == "human_detection") { - - std::string module_model_path = module_config.value("model_path", ""); - int module_threads = module_config.value("rknn_thread_num", 3); - double threshold = module_config.value("time_threshold_sec", 3.0); - - std::vector zone_array; - if (module_config.contains("intrusion_zone") && - module_config["intrusion_zone"].is_array()) { - zone_array = module_config["intrusion_zone"].get>(); - } - - if (module_threads <= 0) - module_threads = 1; - - cv::Rect zone = (zone_array.size() == 4) - ? cv::Rect(zone_array[0], zone_array[1], - zone_array[2], zone_array[3]) - : cv::Rect(0, 0, 0, 0); - - module = std::make_unique( - module_model_path, module_threads, zone, threshold); - - spdlog::info("Stream '{}': Initialized HumanDetectionModule (YOLOv8)", - id); - - } - - else if (module_type == "face_recognition") { - spdlog::warn("Module type 'face_recognition' for stream '{}' is not " - "yet implemented.", - id); - continue; - - } else { - spdlog::error("Unknown module_type '{}' for stream '{}'. Skipping.", - module_type, id); - continue; - } - - auto service = std::make_unique( - std::move(module), input_url, output_rtsp, module_config); - - if (service->start()) { - spdlog::info("Successfully started video service for stream '{}' " - "[Module: {}]. Output is [{}].", - id, module_type, output_rtsp); - services_.push_back(std::move(service)); - } else { - spdlog::error( - "Failed to start video service for stream '{}' [Input: {}].", id, - input_url); - } - } catch (const std::exception &e) { - spdlog::error( - "Exception while creating VideoService for stream '{}' [{}]: {}", id, - input_url, e.what()); - } - } - - spdlog::info( - "VideoServiceManager finished setup. {} streams are now running.", - services_.size()); -} - -void VideoServiceManager::stop_all() { - spdlog::info("Stopping all video services ({})...", services_.size()); - - for (auto &service : services_) { - if (service) { - service->stop(); - } - } - services_.clear(); - spdlog::info("All video services stopped and cleared."); -} \ No newline at end of file diff --git a/src/videoServiceManager/video_service_manager.h b/src/videoServiceManager/video_service_manager.h deleted file mode 100644 index e4cbea1..0000000 --- a/src/videoServiceManager/video_service_manager.h +++ /dev/null @@ -1,31 +0,0 @@ - -#pragma once - -#include "algorithm/IAnalysisModule.h" -#include "algorithm/IntrusionModule.h" -#include "nlohmann/json.hpp" -#include "rknn/video_service.h" -#include -#include -#include - -class VideoServiceManager { -public: - VideoServiceManager() = default; - ~VideoServiceManager(); - - VideoServiceManager(const VideoServiceManager &) = delete; - VideoServiceManager &operator=(const VideoServiceManager &) = delete; - - bool load_config(const std::string &config_path); - - void load_and_start(); - - void stop_all(); - -private: - std::vector> services_; - - bool m_enabled = false; - nlohmann::json m_stream_configs_json; -}; \ No newline at end of file