init
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/build
|
||||
/.cache
|
||||
89
CMakeLists.txt
Normal file
89
CMakeLists.txt
Normal file
@@ -0,0 +1,89 @@
|
||||
cmake_minimum_required(VERSION 3.19)
|
||||
project(softbus_edge_agent LANGUAGES CXX VERSION 0.1.0)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
|
||||
find_package(Qt6 6.5 REQUIRED COMPONENTS Core Network SerialPort)
|
||||
find_package(Qt6 QUIET COMPONENTS SerialBus)
|
||||
if(TARGET Qt6::SerialBus)
|
||||
set(SOFTBUS_HAVE_CAN 1)
|
||||
else()
|
||||
set(SOFTBUS_HAVE_CAN 0)
|
||||
message(STATUS "Qt6 SerialBus not found: building edge_agent without SocketCAN (CAN reconcile disabled)")
|
||||
endif()
|
||||
|
||||
qt_standard_project_setup()
|
||||
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
|
||||
|
||||
set(EDGE_AGENT_SOURCES
|
||||
src/main.cpp
|
||||
src/fake_driver.cpp
|
||||
src/fake_driver.hpp
|
||||
src/device_bus/monitor/DeviceEvent.h
|
||||
src/device_bus/monitor/IDeviceMonitor.h
|
||||
src/device_bus/monitor/UdevDeviceMonitor.cpp
|
||||
src/device_bus/monitor/UdevDeviceMonitor.h
|
||||
src/device_bus/monitor/EdgeDiscoveryCoordinator.cpp
|
||||
src/device_bus/monitor/EdgeDiscoveryCoordinator.h
|
||||
src/ops_ui_http.cpp
|
||||
src/ops_ui_http.hpp
|
||||
src/device_bus/EdgeDeviceBus.cpp
|
||||
src/device_bus/EdgeDeviceBus.h
|
||||
src/device_bus/tree/DeviceTreeModel.cpp
|
||||
src/device_bus/tree/DeviceTreeModel.h
|
||||
src/device_bus/manager/SerialDeviceManager.cpp
|
||||
src/device_bus/manager/SerialDeviceManager.h
|
||||
src/devices/DeviceTypes.cpp
|
||||
src/devices/DeviceTypes.h
|
||||
src/devices/physical/SerialDevice.cpp
|
||||
src/devices/physical/SerialDevice.h
|
||||
src/hardware/drivers/serial/SerialQtDriver.cpp
|
||||
src/hardware/drivers/serial/SerialQtDriver.h
|
||||
src/message_bus/ingress/EdgeSession.cpp
|
||||
src/message_bus/ingress/EdgeSession.h
|
||||
src/message_bus/ingress/EdgeTcpUplinkPort.cpp
|
||||
src/message_bus/ingress/EdgeTcpUplinkPort.h
|
||||
src/message_bus/ingress/EdgeUplinkWire.cpp
|
||||
src/message_bus/ingress/EdgeUplinkWire.h
|
||||
src/message_bus/ingress/IIngressPort.h
|
||||
)
|
||||
|
||||
if(SOFTBUS_HAVE_CAN)
|
||||
list(APPEND EDGE_AGENT_SOURCES
|
||||
src/device_bus/manager/CanDeviceManager.cpp
|
||||
src/device_bus/manager/CanDeviceManager.h
|
||||
src/devices/physical/CanDevice.cpp
|
||||
src/devices/physical/CanDevice.h
|
||||
)
|
||||
endif()
|
||||
|
||||
qt_add_executable(softbus_edge_agent ${EDGE_AGENT_SOURCES})
|
||||
|
||||
target_include_directories(softbus_edge_agent
|
||||
PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/src
|
||||
)
|
||||
|
||||
target_link_libraries(softbus_edge_agent PRIVATE Qt6::Core Qt6::Network Qt6::SerialPort)
|
||||
if(SOFTBUS_HAVE_CAN)
|
||||
target_link_libraries(softbus_edge_agent PRIVATE Qt6::SerialBus)
|
||||
endif()
|
||||
|
||||
target_compile_definitions(softbus_edge_agent PRIVATE SOFTBUS_HAVE_CAN=${SOFTBUS_HAVE_CAN})
|
||||
|
||||
if(UNIX AND NOT APPLE)
|
||||
find_package(PkgConfig QUIET)
|
||||
if(PkgConfig_FOUND)
|
||||
pkg_check_modules(UDEV QUIET libudev)
|
||||
endif()
|
||||
if(UDEV_FOUND)
|
||||
target_include_directories(softbus_edge_agent PRIVATE ${UDEV_INCLUDE_DIRS})
|
||||
target_link_directories(softbus_edge_agent PRIVATE ${UDEV_LIBRARY_DIRS})
|
||||
target_link_libraries(softbus_edge_agent PRIVATE ${UDEV_LIBRARIES})
|
||||
else()
|
||||
target_link_libraries(softbus_edge_agent PRIVATE udev)
|
||||
endif()
|
||||
endif()
|
||||
308
CMakeLists.txt.user
Normal file
308
CMakeLists.txt.user
Normal file
@@ -0,0 +1,308 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE QtCreatorProject>
|
||||
<!-- Written by QtCreator 17.0.1, 2026-05-13T14:08:44. -->
|
||||
<qtcreator>
|
||||
<data>
|
||||
<variable>EnvironmentId</variable>
|
||||
<value type="QByteArray">{a7c4c772-b559-4291-a2ea-267a2e71edb0}</value>
|
||||
</data>
|
||||
<data>
|
||||
<variable>ProjectExplorer.Project.ActiveTarget</variable>
|
||||
<value type="qlonglong">0</value>
|
||||
</data>
|
||||
<data>
|
||||
<variable>ProjectExplorer.Project.EditorSettings</variable>
|
||||
<valuemap type="QVariantMap">
|
||||
<value type="bool" key="EditorConfiguration.AutoDetect">true</value>
|
||||
<value type="bool" key="EditorConfiguration.AutoIndent">true</value>
|
||||
<value type="bool" key="EditorConfiguration.CamelCaseNavigation">true</value>
|
||||
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.0">
|
||||
<value type="QString" key="language">Cpp</value>
|
||||
<valuemap type="QVariantMap" key="value">
|
||||
<value type="QByteArray" key="CurrentPreferences">CppGlobal</value>
|
||||
</valuemap>
|
||||
</valuemap>
|
||||
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.1">
|
||||
<value type="QString" key="language">QmlJS</value>
|
||||
<valuemap type="QVariantMap" key="value">
|
||||
<value type="QByteArray" key="CurrentPreferences">QmlJSGlobal</value>
|
||||
</valuemap>
|
||||
</valuemap>
|
||||
<value type="qlonglong" key="EditorConfiguration.CodeStyle.Count">2</value>
|
||||
<value type="QByteArray" key="EditorConfiguration.Codec">UTF-8</value>
|
||||
<value type="bool" key="EditorConfiguration.ConstrainTooltips">false</value>
|
||||
<value type="int" key="EditorConfiguration.IndentSize">4</value>
|
||||
<value type="bool" key="EditorConfiguration.KeyboardTooltips">false</value>
|
||||
<value type="int" key="EditorConfiguration.LineEndingBehavior">0</value>
|
||||
<value type="int" key="EditorConfiguration.MarginColumn">80</value>
|
||||
<value type="bool" key="EditorConfiguration.MouseHiding">true</value>
|
||||
<value type="bool" key="EditorConfiguration.MouseNavigation">true</value>
|
||||
<value type="int" key="EditorConfiguration.PaddingMode">1</value>
|
||||
<value type="int" key="EditorConfiguration.PreferAfterWhitespaceComments">0</value>
|
||||
<value type="bool" key="EditorConfiguration.PreferSingleLineComments">false</value>
|
||||
<value type="bool" key="EditorConfiguration.ScrollWheelZooming">true</value>
|
||||
<value type="bool" key="EditorConfiguration.ShowMargin">false</value>
|
||||
<value type="int" key="EditorConfiguration.SmartBackspaceBehavior">2</value>
|
||||
<value type="bool" key="EditorConfiguration.SmartSelectionChanging">true</value>
|
||||
<value type="bool" key="EditorConfiguration.SpacesForTabs">true</value>
|
||||
<value type="int" key="EditorConfiguration.TabKeyBehavior">0</value>
|
||||
<value type="int" key="EditorConfiguration.TabSize">8</value>
|
||||
<value type="bool" key="EditorConfiguration.UseGlobal">true</value>
|
||||
<value type="bool" key="EditorConfiguration.UseIndenter">false</value>
|
||||
<value type="int" key="EditorConfiguration.Utf8BomBehavior">1</value>
|
||||
<value type="bool" key="EditorConfiguration.addFinalNewLine">true</value>
|
||||
<value type="bool" key="EditorConfiguration.cleanIndentation">true</value>
|
||||
<value type="bool" key="EditorConfiguration.cleanWhitespace">true</value>
|
||||
<value type="QString" key="EditorConfiguration.ignoreFileTypes">*.md, *.MD, Makefile</value>
|
||||
<value type="bool" key="EditorConfiguration.inEntireDocument">false</value>
|
||||
<value type="bool" key="EditorConfiguration.skipTrailingWhitespace">true</value>
|
||||
<value type="bool" key="EditorConfiguration.tintMarginArea">true</value>
|
||||
</valuemap>
|
||||
</data>
|
||||
<data>
|
||||
<variable>ProjectExplorer.Project.PluginSettings</variable>
|
||||
<valuemap type="QVariantMap">
|
||||
<valuemap type="QVariantMap" key="AutoTest.ActiveFrameworks">
|
||||
<value type="bool" key="AutoTest.Framework.Boost">true</value>
|
||||
<value type="bool" key="AutoTest.Framework.CTest">false</value>
|
||||
<value type="bool" key="AutoTest.Framework.Catch">true</value>
|
||||
<value type="bool" key="AutoTest.Framework.GTest">true</value>
|
||||
<value type="bool" key="AutoTest.Framework.QtQuickTest">true</value>
|
||||
<value type="bool" key="AutoTest.Framework.QtTest">true</value>
|
||||
</valuemap>
|
||||
<value type="bool" key="AutoTest.ApplyFilter">false</value>
|
||||
<valuemap type="QVariantMap" key="AutoTest.CheckStates"/>
|
||||
<valuelist type="QVariantList" key="AutoTest.PathFilters"/>
|
||||
<value type="int" key="AutoTest.RunAfterBuild">0</value>
|
||||
<value type="bool" key="AutoTest.UseGlobal">true</value>
|
||||
<valuemap type="QVariantMap" key="ClangTools">
|
||||
<value type="bool" key="ClangTools.AnalyzeOpenFiles">true</value>
|
||||
<value type="bool" key="ClangTools.BuildBeforeAnalysis">true</value>
|
||||
<value type="QString" key="ClangTools.DiagnosticConfig">Builtin.DefaultTidyAndClazy</value>
|
||||
<value type="int" key="ClangTools.ParallelJobs">8</value>
|
||||
<value type="bool" key="ClangTools.PreferConfigFile">true</value>
|
||||
<valuelist type="QVariantList" key="ClangTools.SelectedDirs"/>
|
||||
<valuelist type="QVariantList" key="ClangTools.SelectedFiles"/>
|
||||
<valuelist type="QVariantList" key="ClangTools.SuppressedDiagnostics"/>
|
||||
<value type="bool" key="ClangTools.UseGlobalSettings">true</value>
|
||||
</valuemap>
|
||||
</valuemap>
|
||||
</data>
|
||||
<data>
|
||||
<variable>ProjectExplorer.Project.Target.0</variable>
|
||||
<valuemap type="QVariantMap">
|
||||
<value type="QString" key="DeviceType">Desktop</value>
|
||||
<value type="bool" key="HasPerBcDcs">true</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Qt 6.9.2</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Qt 6.9.2</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">{ac766883-1e6c-4832-9072-86a8b976b89e}</value>
|
||||
<value type="qlonglong" key="ProjectExplorer.Target.ActiveBuildConfiguration">0</value>
|
||||
<value type="qlonglong" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
|
||||
<value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.0">
|
||||
<value type="QString" key="CMake.Build.Type">Build</value>
|
||||
<value type="int" key="CMake.Configure.BaseEnvironment">2</value>
|
||||
<value type="bool" key="CMake.Configure.ClearSystemEnvironment">false</value>
|
||||
<valuelist type="QVariantList" key="CMake.Configure.UserEnvironmentChanges"/>
|
||||
<value type="QString" key="CMake.Initial.Parameters">-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX}
|
||||
-DCMAKE_BUILD_TYPE:STRING=Build
|
||||
-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx}
|
||||
-DCMAKE_GENERATOR:STRING=Unix Makefiles
|
||||
-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG}
|
||||
-DQT_MAINTENANCE_TOOL:FILEPATH=/home/dt/app/qt/MaintenanceTool
|
||||
-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable}
|
||||
-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake
|
||||
-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C}
|
||||
-DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON</value>
|
||||
<value type="QString" key="CMake.Source.Directory">/home/dt/myCode/bus/soft_bus/edge_agent</value>
|
||||
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/dt/myCode/bus/soft_bus/edge_agent/build</value>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
|
||||
<value type="QString" key="CMakeProjectManager.MakeStep.BuildPreset"></value>
|
||||
<valuelist type="QVariantList" key="CMakeProjectManager.MakeStep.BuildTargets">
|
||||
<value type="QString">all</value>
|
||||
</valuelist>
|
||||
<value type="bool" key="CMakeProjectManager.MakeStep.ClearSystemEnvironment">false</value>
|
||||
<valuelist type="QVariantList" key="CMakeProjectManager.MakeStep.UserEnvironmentChanges"/>
|
||||
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">构建</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">CMakeProjectManager.MakeStep</value>
|
||||
</valuemap>
|
||||
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">构建</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">构建</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Build</value>
|
||||
</valuemap>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.1">
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
|
||||
<value type="QString" key="CMakeProjectManager.MakeStep.BuildPreset"></value>
|
||||
<valuelist type="QVariantList" key="CMakeProjectManager.MakeStep.BuildTargets">
|
||||
<value type="QString">clean</value>
|
||||
</valuelist>
|
||||
<value type="bool" key="CMakeProjectManager.MakeStep.ClearSystemEnvironment">false</value>
|
||||
<valuelist type="QVariantList" key="CMakeProjectManager.MakeStep.UserEnvironmentChanges"/>
|
||||
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">构建</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">CMakeProjectManager.MakeStep</value>
|
||||
</valuemap>
|
||||
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">清除</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">清除</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Clean</value>
|
||||
</valuemap>
|
||||
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">2</value>
|
||||
<value type="bool" key="ProjectExplorer.BuildConfiguration.ClearSystemEnvironment">false</value>
|
||||
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.CustomParsers"/>
|
||||
<value type="bool" key="ProjectExplorer.BuildConfiguration.ParseStandardOutput">false</value>
|
||||
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">构建 (imported)</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">CMakeProjectManager.CMakeBuildConfiguration</value>
|
||||
<value type="qlonglong" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
|
||||
<value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
|
||||
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">0</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">部署</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">部署</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
|
||||
</valuemap>
|
||||
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.DeployConfiguration.CustomData"/>
|
||||
<value type="bool" key="ProjectExplorer.DeployConfiguration.CustomDataEnabled">false</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.DefaultDeployConfiguration</value>
|
||||
</valuemap>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.1">
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
|
||||
<value type="QString" key="CMakeProjectManager.MakeStep.BuildPreset"></value>
|
||||
<valuelist type="QVariantList" key="CMakeProjectManager.MakeStep.BuildTargets">
|
||||
<value type="QString"></value>
|
||||
</valuelist>
|
||||
<value type="bool" key="CMakeProjectManager.MakeStep.ClearSystemEnvironment">false</value>
|
||||
<valuelist type="QVariantList" key="CMakeProjectManager.MakeStep.UserEnvironmentChanges"/>
|
||||
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ApplicationManagerPlugin.Deploy.CMakePackageStep</value>
|
||||
</valuemap>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.1">
|
||||
<value type="QString" key="ApplicationManagerPlugin.Deploy.InstallPackageStep.Arguments">install-package --acknowledge</value>
|
||||
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Install Application Manager package</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ApplicationManagerPlugin.Deploy.InstallPackageStep</value>
|
||||
<valuelist type="QVariantList" key="ProjectExplorer.RunConfiguration.LastDeployedFiles"/>
|
||||
<valuelist type="QVariantList" key="ProjectExplorer.RunConfiguration.LastDeployedHosts"/>
|
||||
<valuelist type="QVariantList" key="ProjectExplorer.RunConfiguration.LastDeployedRemotePaths"/>
|
||||
<valuelist type="QVariantList" key="ProjectExplorer.RunConfiguration.LastDeployedSysroots"/>
|
||||
<valuelist type="QVariantList" key="RemoteLinux.LastDeployedLocalTimes"/>
|
||||
<valuelist type="QVariantList" key="RemoteLinux.LastDeployedRemoteTimes"/>
|
||||
</valuemap>
|
||||
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">2</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">部署</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">部署</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
|
||||
</valuemap>
|
||||
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.DeployConfiguration.CustomData"/>
|
||||
<value type="bool" key="ProjectExplorer.DeployConfiguration.CustomDataEnabled">false</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ApplicationManagerPlugin.Deploy.Configuration</value>
|
||||
</valuemap>
|
||||
<value type="qlonglong" key="ProjectExplorer.Target.DeployConfigurationCount">2</value>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.0">
|
||||
<value type="bool" key="Analyzer.Perf.Settings.UseGlobalSettings">true</value>
|
||||
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
|
||||
<value type="int" key="Analyzer.Valgrind.Callgrind.CostFormat">0</value>
|
||||
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
|
||||
<valuelist type="QVariantList" key="CustomOutputParsers"/>
|
||||
<value type="int" key="PE.EnvironmentAspect.Base">2</value>
|
||||
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
|
||||
<value type="bool" key="PE.EnvironmentAspect.PrintOnRun">false</value>
|
||||
<value type="QString" key="PerfRecordArgsId">-e cpu-cycles --call-graph dwarf,4096 -F 250</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.CustomExecutableRunConfiguration</value>
|
||||
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey"></value>
|
||||
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value>
|
||||
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
|
||||
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
|
||||
</valuemap>
|
||||
<value type="qlonglong" key="ProjectExplorer.Target.RunConfigurationCount">1</value>
|
||||
</valuemap>
|
||||
<value type="qlonglong" key="ProjectExplorer.Target.BuildConfigurationCount">1</value>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
|
||||
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">0</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">部署</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">部署</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
|
||||
</valuemap>
|
||||
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.DeployConfiguration.CustomData"/>
|
||||
<value type="bool" key="ProjectExplorer.DeployConfiguration.CustomDataEnabled">false</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.DefaultDeployConfiguration</value>
|
||||
</valuemap>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.1">
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
|
||||
<value type="QString" key="CMakeProjectManager.MakeStep.BuildPreset"></value>
|
||||
<valuelist type="QVariantList" key="CMakeProjectManager.MakeStep.BuildTargets">
|
||||
<value type="QString"></value>
|
||||
</valuelist>
|
||||
<value type="bool" key="CMakeProjectManager.MakeStep.ClearSystemEnvironment">false</value>
|
||||
<valuelist type="QVariantList" key="CMakeProjectManager.MakeStep.UserEnvironmentChanges"/>
|
||||
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ApplicationManagerPlugin.Deploy.CMakePackageStep</value>
|
||||
</valuemap>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.1">
|
||||
<value type="QString" key="ApplicationManagerPlugin.Deploy.InstallPackageStep.Arguments">install-package --acknowledge</value>
|
||||
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Install Application Manager package</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ApplicationManagerPlugin.Deploy.InstallPackageStep</value>
|
||||
<valuelist type="QVariantList" key="ProjectExplorer.RunConfiguration.LastDeployedFiles"/>
|
||||
<valuelist type="QVariantList" key="ProjectExplorer.RunConfiguration.LastDeployedHosts"/>
|
||||
<valuelist type="QVariantList" key="ProjectExplorer.RunConfiguration.LastDeployedRemotePaths"/>
|
||||
<valuelist type="QVariantList" key="ProjectExplorer.RunConfiguration.LastDeployedSysroots"/>
|
||||
<valuelist type="QVariantList" key="RemoteLinux.LastDeployedLocalTimes"/>
|
||||
<valuelist type="QVariantList" key="RemoteLinux.LastDeployedRemoteTimes"/>
|
||||
</valuemap>
|
||||
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">2</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">部署</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">部署</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
|
||||
</valuemap>
|
||||
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.DeployConfiguration.CustomData"/>
|
||||
<value type="bool" key="ProjectExplorer.DeployConfiguration.CustomDataEnabled">false</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ApplicationManagerPlugin.Deploy.Configuration</value>
|
||||
</valuemap>
|
||||
<value type="qlonglong" key="ProjectExplorer.Target.DeployConfigurationCount">2</value>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.0">
|
||||
<value type="bool" key="Analyzer.Perf.Settings.UseGlobalSettings">true</value>
|
||||
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
|
||||
<value type="int" key="Analyzer.Valgrind.Callgrind.CostFormat">0</value>
|
||||
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
|
||||
<valuelist type="QVariantList" key="CustomOutputParsers"/>
|
||||
<value type="int" key="PE.EnvironmentAspect.Base">2</value>
|
||||
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
|
||||
<value type="bool" key="PE.EnvironmentAspect.PrintOnRun">false</value>
|
||||
<value type="QString" key="PerfRecordArgsId">-e cpu-cycles --call-graph dwarf,4096 -F 250</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.CustomExecutableRunConfiguration</value>
|
||||
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey"></value>
|
||||
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value>
|
||||
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
|
||||
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
|
||||
</valuemap>
|
||||
<value type="qlonglong" key="ProjectExplorer.Target.RunConfigurationCount">1</value>
|
||||
</valuemap>
|
||||
</data>
|
||||
<data>
|
||||
<variable>ProjectExplorer.Project.TargetCount</variable>
|
||||
<value type="qlonglong">1</value>
|
||||
</data>
|
||||
<data>
|
||||
<variable>ProjectExplorer.Project.Updater.FileVersion</variable>
|
||||
<value type="int">22</value>
|
||||
</data>
|
||||
<data>
|
||||
<variable>Version</variable>
|
||||
<value type="int">22</value>
|
||||
</data>
|
||||
</qtcreator>
|
||||
85
README.md
Normal file
85
README.md
Normal file
@@ -0,0 +1,85 @@
|
||||
# softbus_edge_agent
|
||||
|
||||
独立边缘进程:连接核心 `softbus_daemon` 的 **edgeIngress** TCP 端口,发送 **HELLO**、**DATA_RAWBUS**,以及通过极简 **本地 HTTP**(`GET /`、`GET /ui`、`POST /propose` 等)触发 **TREE_PROPOSE** 反向同步。
|
||||
|
||||
## 目录布局(与 `softbus_daemon` 的 `src/` 对齐)
|
||||
|
||||
| 路径 | 说明 |
|
||||
|------|------|
|
||||
| `src/device_bus/tree/` | 与核心同源的 `DeviceTreeModel`(本地子树、`TREE_FULL` 落盘加载) |
|
||||
| `src/device_bus/monitor/` | **udev 串口发现**、`EdgeDiscoveryCoordinator`(防抖后组 `TREE_PROPOSE` / `upsert` 向核心注册) |
|
||||
| `src/device_bus/manager/` | `SerialDeviceManager`、`CanDeviceManager`(无 Registry / Pipeline,仅 `IIngressPort` + 内存池) |
|
||||
| `src/device_bus/EdgeDeviceBus.*` | 边缘总线编排:加载本地树、**reconcile** 串口与 **SocketCAN** 实例 |
|
||||
| `src/devices/`、`src/hardware/` | 与核心同源的 `SerialDevice`、`SerialQtDriver` 等 |
|
||||
| `src/message_bus/ingress/` | `EdgeSession`、`EdgeUplinkWire`、`EdgeTcpUplinkPort`(`RawBusMessage` → SBUP 上行) |
|
||||
|
||||
协议与核心侧文档对齐:
|
||||
|
||||
- 核心权威说明:[softbus_daemon/docs/EDGE_UPLINK_PROTOCOL_v1.md](../softbus_daemon/docs/EDGE_UPLINK_PROTOCOL_v1.md)(若仓库路径不同请自行调整相对链接)。
|
||||
|
||||
## 构建
|
||||
|
||||
```bash
|
||||
cmake -S . -B build -DCMAKE_PREFIX_PATH=/path/to/Qt/6.x/gcc_64
|
||||
cmake --build build
|
||||
./build/bin/softbus_edge_agent config/edge_manifest.example.json
|
||||
```
|
||||
|
||||
依赖 **Qt6 Core、Network、SerialPort**;若 Qt 安装包含 **SerialBus** 模块,CMake 会自动检测并链接,从而在边缘启用 **SocketCAN reconcile**(设备树 `can:...` 节点)。未安装时仍可完整构建,CAN 节点将被忽略。
|
||||
|
||||
## 配置 `edge_manifest.json`(可选)
|
||||
|
||||
若默认路径 **`config/edge_manifest.json`** 不存在、或传入的 manifest 路径无法打开、或 JSON 非法,进程会使用**内置默认值**启动(等价于示例 manifest 的字段),并在日志中提示。
|
||||
|
||||
仍可通过命令行指定路径覆盖默认文件位置:
|
||||
|
||||
```text
|
||||
./build/bin/softbus_edge_agent /path/to/edge_manifest.json
|
||||
```
|
||||
|
||||
见 [config/edge_manifest.schema.json](config/edge_manifest.schema.json) 与示例 [config/edge_manifest.example.json](config/edge_manifest.example.json)。
|
||||
|
||||
要点:
|
||||
|
||||
- **`coreHost` / `corePort`**:与 `daemon_config.json` 中 `edgeIngress` 一致。
|
||||
- **`edgeId`**:HELLO 与会话绑定;`TREE_PROPOSE` 必须相同。
|
||||
- **`edgeDiscoveryEnabled`**(bool,默认 `true`):在 TCP 连接且 **HELLO 成功** 后启动 udev 发现协调器,将串口以 `op: upsert` 写入核心设备树(需 Linux + `libudev`)。设为 `false` 可仅依赖手工 `TREE_PROPOSE` 或核心下发树。
|
||||
- **`endpoint`**:用于计算 `endpointHash`(与核心一致:`qHash(endpoint)` 即 Qt 默认字符串哈希)。
|
||||
- **`fakeDeviceId`**:组装 `routingKey = (endpointHash << 32) | deviceId` 与核心 `makeRoutingKey` 一致。
|
||||
- **`fakeIntervalMs`**:设为 `0` 可关闭假数据定时器;**真实串口**由核心下发的设备树节点驱动(`type`/`endpoint` 可识别为 Serial,且 `params.online` 为 true)。节点 `params.deviceId` 会写入上行 `RawBusMessage::deviceId`(未设则 `-1`)。
|
||||
|
||||
## 运维 HTTP
|
||||
|
||||
若 `opsListenPort > 0`,监听 `127.0.0.1:opsListenPort`:
|
||||
|
||||
- `GET /`:返回 `localTreePath` 文件内容(无文件则返回占位 HTML,含指向配置页的链接)。
|
||||
- **`GET /ui`**:**阶段 5** 简易配置页——从 **`GET /discovered`** 同源列表选择已发现串口节点,填写 `params.protocol`(如 `modbus_rtu`)、波特率等,提交后由边缘组装 **`op: upsert`** 的 `TREE_PROPOSE` 发往核心(使用当前会话的 **`baseTreeRevision`**,须已 HELLO 且收到过 `TREE_RESULT`)。
|
||||
- **`POST /register_discovered`**:可由浏览器表单(`application/x-www-form-urlencoded`)或 **JSON** 调用;字段含 `nodeId`(必填,一般为 `edges/<edgeId>/discovered/<stableKey>`)、`devnode`(可选,若不在发现列表中需填 `/dev/tty…`)、`protocol`、`baudRate`、`name`。表单提交成功返回简短 **HTML** 确认页;JSON 请求返回 `{"accepted":true,"sentNodeId":...}`。若尚未拿到有效 `treeRevision`,返回 **503**。
|
||||
- **`GET /discovered`**:返回最近 udev 发现事件 JSON(`{ "items": [ ... ] }`),便于无核心时调试;与 `EdgeDiscoveryCoordinator` 内存环缓冲一致(最多约 64 条)。
|
||||
- `POST /propose`:body 为 **完整** `CTRL_TREE_PROPOSE` JSON(含 `edgeId`、`baseTreeRevision`、`entries`),将原样经 TCP 发往核心。
|
||||
|
||||
示例:
|
||||
|
||||
```bash
|
||||
curl -s http://127.0.0.1:9080/
|
||||
curl -s http://127.0.0.1:9080/ui
|
||||
curl -s http://127.0.0.1:9080/discovered
|
||||
curl -s -X POST http://127.0.0.1:9080/register_discovered \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"nodeId":"edges/edge-dev-1/discovered/12345","protocol":"modbus_rtu","baudRate":115200}'
|
||||
curl -s -X POST http://127.0.0.1:9080/propose \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"edgeId":"edge-dev-1","baseTreeRevision":1,"entries":[{"id":"edges/edge-dev-1/gw","patch":{"name":"gw"}}]}'
|
||||
```
|
||||
|
||||
(`entries` 中节点 id 必须满足核心 `DeviceTreeModel::nodeBelongsToEdge` 规则。)
|
||||
|
||||
## 本地树文件
|
||||
|
||||
`TREE_FULL` 落盘格式为 `{ "treeRevision", "edgeId", "tree": [ ... ] }`。**`EdgeDeviceBus`** 会将其中的 `tree` 当作 `device_tree` 加载,并对 **Serial** 与 **CAN**(`endpoint` 形如 `can:vcan0`,`type`/`endpoint` 推断为 CAN)执行 **reconcile**:打开设备后读到的数据经 `EdgeTcpUplinkPort` 编码为 `DATA_RAWBUS` 发往核心。与核心真源同步仍通过 **TREE_PROPOSE**(含边缘 **`upsert`** 自动注册)、核心侧 D-Bus `patch_device_tree_node` 或运维 **`push_edge_tree`**。
|
||||
|
||||
### 发现 → 注册流程(简要)
|
||||
|
||||
1. 边缘连接核心并 **HELLO**;核心下发 **`TREE_FULL`**,本地落盘并 reconcile 已有节点。
|
||||
2. `edgeDiscoveryEnabled` 为真时,`EdgeDiscoveryCoordinator` 扫描/监听 udev 串口 **add/remove**,在拿到有效 **`baseTreeRevision`**(来自 `TREE_RESULT`)后发送 **`TREE_PROPOSE`**:`op: upsert` 注册 `edges/<edgeId>/discovered/<stableKey>`,remove 时 **`patch`** `params.online: false`。
|
||||
3. 核心 `applyEdgePropose` 成功后再次 **`TREE_FULL`**;边缘 reload 树文件后对 **Serial/CAN** 执行 reconcile,**raw 仅上行**。
|
||||
14
config/edge_manifest.example.json
Normal file
14
config/edge_manifest.example.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"$schema": "./edge_manifest.schema.json",
|
||||
"coreHost": "127.0.0.1",
|
||||
"corePort": 18765,
|
||||
"edgeId": "edge-dev-1",
|
||||
"authToken": "",
|
||||
"opsListenPort": 9080,
|
||||
"localTreePath": "/tmp/edge_local_tree.json",
|
||||
"fakeIntervalMs": 5000,
|
||||
"fakeDeviceId": 1,
|
||||
"endpoint": "edge:fake",
|
||||
"protocol": 0,
|
||||
"edgeDiscoveryEnabled": true
|
||||
}
|
||||
22
config/edge_manifest.schema.json
Normal file
22
config/edge_manifest.schema.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"title": "softbus_edge_manifest",
|
||||
"type": "object",
|
||||
"required": ["coreHost", "corePort", "edgeId"],
|
||||
"properties": {
|
||||
"coreHost": { "type": "string", "description": "softbus_daemon 所在主机" },
|
||||
"corePort": { "type": "integer", "description": "daemon_config edgeIngress.port" },
|
||||
"edgeId": { "type": "string", "description": "HELLO / PROPOSE 与会话绑定的边缘 ID" },
|
||||
"authToken": { "type": "string", "description": "若核心配置了 edgeIngress.authToken,则此处必须一致" },
|
||||
"opsListenPort": { "type": "integer", "minimum": 0, "description": "0 关闭本地运维 HTTP;否则监听 127.0.0.1:port" },
|
||||
"localTreePath": { "type": "string", "description": "GET / 返回的本地树文件路径(展示用)" },
|
||||
"fakeIntervalMs": { "type": "integer", "minimum": 100, "description": "假数据 DATA_RAWBUS 发送周期(毫秒)" },
|
||||
"fakeDeviceId": { "type": "integer", "description": "RawBusMessage.deviceId" },
|
||||
"endpoint": { "type": "string", "description": "用于 qHash(endpoint) 得到 endpointHash,须与核心设备树约定一致" },
|
||||
"protocol": { "type": "integer", "description": "ProtocolType 枚举数值(与核心 RawBusMessage 一致)" },
|
||||
"edgeDiscoveryEnabled": {
|
||||
"type": "boolean",
|
||||
"description": "HELLO 成功后是否启动 udev 串口发现并向核心发送 TREE_PROPOSE(upsert/patch);需 Linux libudev"
|
||||
}
|
||||
}
|
||||
}
|
||||
7
docs/UPLINK_PROTOCOL_v1.md
Normal file
7
docs/UPLINK_PROTOCOL_v1.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# 边缘上行协议(与核心同步)
|
||||
|
||||
完整规范见 **softbus_daemon** 仓库:`docs/EDGE_UPLINK_PROTOCOL_v1.md`。
|
||||
|
||||
本仓库在 `src/message_bus/ingress/EdgeUplinkWire.*` 中实现 **编码侧**(外层 `SBUP` 帧 + `DATA_RAWBUS` 内层二进制),须与核心 **逐字节一致**。
|
||||
|
||||
**下行**:`EdgeSession` 会解析核心发来的 `TREE_RESULT` 与 **`TREE_FULL`**(`frame_kind=4`),后者由 `main` 写入 manifest 中的 `localTreePath` 后,**`EdgeDeviceBus`** 会加载并 **reconcile** 串口(与核心 `devices` + `hardware` + `device_bus` 行为对齐的裁剪版)。
|
||||
98
src/core/memory/MemoryPool.h
Normal file
98
src/core/memory/MemoryPool.h
Normal file
@@ -0,0 +1,98 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <vector>
|
||||
|
||||
namespace softbus::core::memory
|
||||
{
|
||||
|
||||
class MemoryPool
|
||||
{
|
||||
public:
|
||||
struct PoolBlock
|
||||
{
|
||||
std::uint8_t* data{nullptr};
|
||||
std::size_t capacity{0};
|
||||
std::size_t size{0};
|
||||
std::size_t slotIndex{0};
|
||||
};
|
||||
|
||||
using BlockPtr = std::shared_ptr<PoolBlock>;
|
||||
|
||||
explicit MemoryPool(std::size_t blockSize = 4096, std::size_t blockCount = 1024)
|
||||
: m_blockSize(blockSize), m_storage(blockSize * blockCount), m_inUse(blockCount, false)
|
||||
{
|
||||
for (std::size_t i = 0; i < blockCount; ++i) {
|
||||
m_freeSlots.push(i);
|
||||
}
|
||||
}
|
||||
|
||||
BlockPtr allocate(std::size_t requiredSize)
|
||||
{
|
||||
if (requiredSize > m_blockSize) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::size_t slot = 0;
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(m_mutex);
|
||||
if (m_freeSlots.empty()) {
|
||||
return {};
|
||||
}
|
||||
slot = m_freeSlots.front();
|
||||
m_freeSlots.pop();
|
||||
m_inUse[slot] = true;
|
||||
}
|
||||
|
||||
auto* raw = new PoolBlock{
|
||||
m_storage.data() + slot * m_blockSize,
|
||||
m_blockSize,
|
||||
requiredSize,
|
||||
slot,
|
||||
};
|
||||
return BlockPtr(raw, [this](PoolBlock* b) {
|
||||
if (!b) return;
|
||||
this->release(*b);
|
||||
delete b;
|
||||
});
|
||||
}
|
||||
|
||||
void release(const PoolBlock& block)
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(m_mutex);
|
||||
if (block.slotIndex >= m_inUse.size()) {
|
||||
return;
|
||||
}
|
||||
if (!m_inUse[block.slotIndex]) {
|
||||
return;
|
||||
}
|
||||
m_inUse[block.slotIndex] = false;
|
||||
m_freeSlots.push(block.slotIndex);
|
||||
}
|
||||
|
||||
std::size_t blockSize() const { return m_blockSize; }
|
||||
std::size_t totalBlocks() const { return m_inUse.size(); }
|
||||
|
||||
std::size_t inUseBlocks() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(m_mutex);
|
||||
std::size_t used = 0;
|
||||
for (const bool v : m_inUse) {
|
||||
if (v) ++used;
|
||||
}
|
||||
return used;
|
||||
}
|
||||
|
||||
private:
|
||||
std::size_t m_blockSize;
|
||||
std::vector<std::uint8_t> m_storage;
|
||||
mutable std::mutex m_mutex;
|
||||
std::queue<std::size_t> m_freeSlots;
|
||||
std::vector<bool> m_inUse;
|
||||
};
|
||||
|
||||
} // namespace softbus::core::memory
|
||||
59
src/core/models/MessageRoutingUtils.h
Normal file
59
src/core/models/MessageRoutingUtils.h
Normal file
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "core/models/RawBusMessage.h"
|
||||
|
||||
namespace softbus::core::models
|
||||
{
|
||||
|
||||
// Hash physical endpoint string into a compact routing key.
|
||||
inline std::uint32_t makeEndpointHash(const QString& endpoint)
|
||||
{
|
||||
return qHash(endpoint);
|
||||
}
|
||||
|
||||
// Build a deterministic string key from endpoint hash (for session maps).
|
||||
inline QString makeEndpointKey(std::uint32_t endpointHash)
|
||||
{
|
||||
return QString::number(endpointHash, 16);
|
||||
}
|
||||
|
||||
// Build a 64-bit routing key for fast lookup paths.
|
||||
inline std::uint64_t makeRoutingKey(std::uint32_t endpointHash, int deviceId)
|
||||
{
|
||||
return (static_cast<std::uint64_t>(endpointHash) << 32) | static_cast<std::uint32_t>(deviceId);
|
||||
}
|
||||
|
||||
// Bridge routing key to legacy string-based matching interfaces.
|
||||
inline QString routingKeyToStableKey(std::uint64_t routingKey)
|
||||
{
|
||||
return routingKey == 0 ? QString() : QString::number(routingKey);
|
||||
}
|
||||
|
||||
inline ProtocolType protocolTypeFromHint(const QString& hint)
|
||||
{
|
||||
if (hint.compare(QStringLiteral("modbus_rtu"), Qt::CaseInsensitive) == 0) {
|
||||
return ProtocolType::MODBUS_RTU;
|
||||
}
|
||||
if (hint.compare(QStringLiteral("modbus_ascii"), Qt::CaseInsensitive) == 0) {
|
||||
return ProtocolType::MODBUS_ASCII;
|
||||
}
|
||||
if (hint.compare(QStringLiteral("modbus_tcp"), Qt::CaseInsensitive) == 0) {
|
||||
return ProtocolType::MODBUS_TCP;
|
||||
}
|
||||
if (hint.compare(QStringLiteral("can_raw"), Qt::CaseInsensitive) == 0) {
|
||||
return ProtocolType::CAN_RAW;
|
||||
}
|
||||
if (hint.compare(QStringLiteral("can_open"), Qt::CaseInsensitive) == 0) {
|
||||
return ProtocolType::CAN_OPEN;
|
||||
}
|
||||
if (hint.compare(QStringLiteral("udp_custom"), Qt::CaseInsensitive) == 0) {
|
||||
return ProtocolType::UDP_CUSTOM;
|
||||
}
|
||||
return ProtocolType::UNKNOWN;
|
||||
}
|
||||
|
||||
} // namespace softbus::core::models
|
||||
35
src/core/models/PayloadRef.h
Normal file
35
src/core/models/PayloadRef.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
#include <QMetaType>
|
||||
|
||||
#include "core/memory/MemoryPool.h"
|
||||
|
||||
namespace softbus::core::models
|
||||
{
|
||||
|
||||
struct PayloadRef
|
||||
{
|
||||
softbus::core::memory::MemoryPool::BlockPtr block;
|
||||
std::size_t offset{0};
|
||||
std::size_t length{0};
|
||||
|
||||
const std::uint8_t* bytes() const
|
||||
{
|
||||
if (!block || !block->data || offset > block->capacity) {
|
||||
return nullptr;
|
||||
}
|
||||
return block->data + offset;
|
||||
}
|
||||
|
||||
bool valid() const
|
||||
{
|
||||
return block && block->data && (offset + length <= block->capacity);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace softbus::core::models
|
||||
|
||||
Q_DECLARE_METATYPE(softbus::core::models::PayloadRef)
|
||||
80
src/core/models/RawBusMessage.h
Normal file
80
src/core/models/RawBusMessage.h
Normal file
@@ -0,0 +1,80 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include <QMetaType>
|
||||
#include <QString>
|
||||
|
||||
#include "core/models/PayloadRef.h"
|
||||
|
||||
namespace softbus::core::models
|
||||
{
|
||||
|
||||
enum class BusDirection
|
||||
{
|
||||
Unknown,
|
||||
Upstream,
|
||||
Downstream,
|
||||
};
|
||||
|
||||
enum class ProtocolType : std::uint16_t
|
||||
{
|
||||
UNKNOWN = 0,
|
||||
MODBUS_RTU,
|
||||
MODBUS_ASCII,
|
||||
MODBUS_TCP,
|
||||
CAN_RAW,
|
||||
CAN_OPEN,
|
||||
UDP_CUSTOM,
|
||||
};
|
||||
|
||||
inline uint qHash(ProtocolType key, uint seed = 0) noexcept
|
||||
{
|
||||
return ::qHash(static_cast<std::uint16_t>(key), seed);
|
||||
}
|
||||
|
||||
union ProtocolHeader
|
||||
{
|
||||
struct Can
|
||||
{
|
||||
std::uint32_t cobId{0};
|
||||
bool isExtended{false};
|
||||
bool isRtr{false};
|
||||
std::uint8_t padding[2]{0, 0};
|
||||
} can;
|
||||
|
||||
struct Modbus
|
||||
{
|
||||
std::uint8_t slaveId{0};
|
||||
std::uint8_t functionCode{0};
|
||||
std::uint8_t padding[6]{0, 0, 0, 0, 0, 0};
|
||||
} modbus;
|
||||
|
||||
struct Net
|
||||
{
|
||||
std::uint32_t srcIp{0};
|
||||
std::uint16_t srcPort{0};
|
||||
std::uint16_t padding{0};
|
||||
} net;
|
||||
|
||||
std::uint64_t alignmentPadding{0};
|
||||
};
|
||||
|
||||
struct RawBusMessage
|
||||
{
|
||||
int deviceId{-1}; // Device instance id, -1 means unbound.
|
||||
std::uint32_t endpointHash{0}; // Physical endpoint/channel hash.
|
||||
ProtocolType protocol{ProtocolType::UNKNOWN};
|
||||
std::uint32_t logicalAddress{0}; // Slave ID / node ID for protocol routing.
|
||||
std::uint64_t routingKey{0}; // Stable key for MetadataRegistry lookup.
|
||||
ProtocolHeader header{}; // Out-of-band protocol header metadata.
|
||||
std::int64_t timestampMs{0}; // UTC unix timestamp in milliseconds.
|
||||
QString traceId;
|
||||
|
||||
softbus::core::models::PayloadRef payload;
|
||||
BusDirection direction{BusDirection::Unknown};
|
||||
};
|
||||
|
||||
} // namespace softbus::core::models
|
||||
|
||||
Q_DECLARE_METATYPE(softbus::core::models::RawBusMessage)
|
||||
222
src/device_bus/EdgeDeviceBus.cpp
Normal file
222
src/device_bus/EdgeDeviceBus.cpp
Normal file
@@ -0,0 +1,222 @@
|
||||
#include "device_bus/EdgeDeviceBus.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QIODevice>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QSet>
|
||||
|
||||
#include "device_bus/manager/SerialDeviceManager.h"
|
||||
#if SOFTBUS_HAVE_CAN
|
||||
#include "device_bus/manager/CanDeviceManager.h"
|
||||
#endif
|
||||
#include "devices/DeviceTypes.h"
|
||||
#include "utils/logs/logging.h"
|
||||
|
||||
namespace softbus::device_bus
|
||||
{
|
||||
|
||||
EdgeDeviceBus::EdgeDeviceBus()
|
||||
: m_tree(std::make_shared<DeviceTreeModel>())
|
||||
, m_pool(std::make_shared<softbus::core::memory::MemoryPool>())
|
||||
{
|
||||
}
|
||||
|
||||
EdgeDeviceBus::~EdgeDeviceBus()
|
||||
{
|
||||
shutdownAllSerial();
|
||||
#if SOFTBUS_HAVE_CAN
|
||||
shutdownAllCan();
|
||||
#endif
|
||||
}
|
||||
|
||||
void EdgeDeviceBus::setIngressPort(softbus::message_bus::ingress::IIngressPort* port)
|
||||
{
|
||||
m_ingress = port;
|
||||
}
|
||||
|
||||
bool EdgeDeviceBus::loadLocalEdgeTreeFile(const QString& path, QString* errorMessage)
|
||||
{
|
||||
QFile f(path);
|
||||
if (!f.open(QIODevice::ReadOnly)) {
|
||||
if (errorMessage) {
|
||||
*errorMessage = QStringLiteral("cannot open file");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
QJsonParseError pe;
|
||||
const QJsonDocument doc = QJsonDocument::fromJson(f.readAll(), &pe);
|
||||
if (pe.error != QJsonParseError::NoError || !doc.isObject()) {
|
||||
if (errorMessage) {
|
||||
*errorMessage = pe.errorString();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
const QJsonObject rootObj = doc.object();
|
||||
const QJsonArray treeArr = rootObj.value(QStringLiteral("tree")).toArray();
|
||||
if (treeArr.isEmpty()) {
|
||||
if (errorMessage) {
|
||||
*errorMessage = QStringLiteral("missing or empty 'tree' array");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
QJsonObject wrapped;
|
||||
wrapped.insert(QStringLiteral("device_tree"), treeArr);
|
||||
return m_tree->loadFromDaemonConfig(wrapped, errorMessage);
|
||||
}
|
||||
|
||||
void EdgeDeviceBus::shutdownAllSerial()
|
||||
{
|
||||
const auto keys = m_serialByEndpoint.keys();
|
||||
for (const QString& ep : keys) {
|
||||
auto dev = m_serialByEndpoint.value(ep);
|
||||
if (dev) {
|
||||
dev->stop();
|
||||
}
|
||||
}
|
||||
m_serialByEndpoint.clear();
|
||||
}
|
||||
|
||||
#if SOFTBUS_HAVE_CAN
|
||||
void EdgeDeviceBus::shutdownAllCan()
|
||||
{
|
||||
const auto keys = m_canByEndpoint.keys();
|
||||
for (const QString& ep : keys) {
|
||||
auto dev = m_canByEndpoint.value(ep);
|
||||
if (dev) {
|
||||
dev->stop();
|
||||
}
|
||||
}
|
||||
m_canByEndpoint.clear();
|
||||
}
|
||||
#endif
|
||||
|
||||
void EdgeDeviceBus::reconcileSerialFromTree()
|
||||
{
|
||||
if (!m_ingress || !m_pool) {
|
||||
return;
|
||||
}
|
||||
|
||||
QSet<QString> desired;
|
||||
const auto nodes = m_tree->nodes();
|
||||
for (const auto& n : nodes) {
|
||||
if (!n.enabled) {
|
||||
continue;
|
||||
}
|
||||
if (!n.params.value(QStringLiteral("online")).toBool(true)) {
|
||||
continue;
|
||||
}
|
||||
auto kind = softbus::devices::kindFromType(n.type);
|
||||
if (kind == softbus::devices::DeviceKind::Unknown) {
|
||||
kind = softbus::devices::inferDeviceKindFromEndpoint(n.endpoint);
|
||||
}
|
||||
if (kind != softbus::devices::DeviceKind::Serial) {
|
||||
continue;
|
||||
}
|
||||
if (n.endpoint.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
desired.insert(n.endpoint);
|
||||
}
|
||||
|
||||
const auto current = m_serialByEndpoint.keys();
|
||||
for (const QString& ep : current) {
|
||||
if (!desired.contains(ep)) {
|
||||
auto dev = m_serialByEndpoint.take(ep);
|
||||
if (dev) {
|
||||
dev->stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
softbus::device_bus::manager::SerialInitDeps deps;
|
||||
deps.treeModel = m_tree.get();
|
||||
deps.dirtyCb = []() {};
|
||||
deps.ingressPort = m_ingress;
|
||||
deps.memoryPool = m_pool;
|
||||
deps.serialDevicesByEndpoint = &m_serialByEndpoint;
|
||||
|
||||
for (const QString& ep : desired) {
|
||||
if (m_serialByEndpoint.contains(ep)) {
|
||||
continue;
|
||||
}
|
||||
const int idx = m_tree->findIndexByEndpoint(ep);
|
||||
if (idx < 0) {
|
||||
continue;
|
||||
}
|
||||
const auto& n = m_tree->nodes().at(idx);
|
||||
if (!initializeSerialDeviceWithDeps(ep, n.params, deps)) {
|
||||
LOG_WARNING() << "EdgeDeviceBus: failed to init serial" << ep;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if SOFTBUS_HAVE_CAN
|
||||
void EdgeDeviceBus::reconcileCanFromTree()
|
||||
{
|
||||
if (!m_ingress || !m_pool) {
|
||||
return;
|
||||
}
|
||||
|
||||
QSet<QString> desired;
|
||||
const auto nodes = m_tree->nodes();
|
||||
for (const auto& n : nodes) {
|
||||
if (!n.enabled) {
|
||||
continue;
|
||||
}
|
||||
if (!n.params.value(QStringLiteral("online")).toBool(true)) {
|
||||
continue;
|
||||
}
|
||||
auto kind = softbus::devices::kindFromType(n.type);
|
||||
if (kind == softbus::devices::DeviceKind::Unknown) {
|
||||
kind = softbus::devices::inferDeviceKindFromEndpoint(n.endpoint);
|
||||
}
|
||||
if (kind != softbus::devices::DeviceKind::Can) {
|
||||
continue;
|
||||
}
|
||||
if (n.endpoint.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
desired.insert(n.endpoint);
|
||||
}
|
||||
|
||||
const auto current = m_canByEndpoint.keys();
|
||||
for (const QString& ep : current) {
|
||||
if (!desired.contains(ep)) {
|
||||
auto dev = m_canByEndpoint.take(ep);
|
||||
if (dev) {
|
||||
dev->stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
softbus::device_bus::manager::CanInitDeps deps;
|
||||
deps.treeModel = m_tree.get();
|
||||
deps.dirtyCb = []() {};
|
||||
deps.ingressPort = m_ingress;
|
||||
deps.memoryPool = m_pool;
|
||||
deps.canDevicesByEndpoint = &m_canByEndpoint;
|
||||
|
||||
for (const QString& ep : desired) {
|
||||
if (m_canByEndpoint.contains(ep)) {
|
||||
continue;
|
||||
}
|
||||
const int idx = m_tree->findIndexByEndpoint(ep);
|
||||
if (idx < 0) {
|
||||
continue;
|
||||
}
|
||||
const auto& n = m_tree->nodes().at(idx);
|
||||
if (!initializeCanDeviceWithDeps(ep, n.params, deps)) {
|
||||
LOG_WARNING() << "EdgeDeviceBus: failed to init CAN" << ep;
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
void EdgeDeviceBus::reconcileCanFromTree()
|
||||
{
|
||||
// Qt SerialBus 未随构建启用(见 CMake `SOFTBUS_HAVE_CAN`)
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace softbus::device_bus
|
||||
61
src/device_bus/EdgeDeviceBus.h
Normal file
61
src/device_bus/EdgeDeviceBus.h
Normal file
@@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QHash>
|
||||
#include <QSharedPointer>
|
||||
#include <QString>
|
||||
|
||||
#include "core/memory/MemoryPool.h"
|
||||
#include "device_bus/tree/DeviceTreeModel.h"
|
||||
#include "devices/physical/SerialDevice.h"
|
||||
#if SOFTBUS_HAVE_CAN
|
||||
#include "devices/physical/CanDevice.h"
|
||||
#endif
|
||||
#include "message_bus/ingress/IIngressPort.h"
|
||||
|
||||
namespace softbus::device_bus
|
||||
{
|
||||
|
||||
/**
|
||||
* 边缘侧设备总线编排(目录对齐 daemon 的 device_bus + devices + hardware)。
|
||||
* 从本地 `TREE_FULL` 落盘文件加载 `device_tree`,按节点拉起串口并绑定 `IIngressPort`(通常为核心 TCP 上行)。
|
||||
*/
|
||||
class EdgeDeviceBus final
|
||||
{
|
||||
public:
|
||||
EdgeDeviceBus();
|
||||
~EdgeDeviceBus();
|
||||
|
||||
void setIngressPort(softbus::message_bus::ingress::IIngressPort* port);
|
||||
|
||||
/** 读取边缘落盘:`{ "tree": [...], "treeRevision", "edgeId" }`,包装为 `loadFromDaemonConfig`。 */
|
||||
bool loadLocalEdgeTreeFile(const QString& path, QString* errorMessage = nullptr);
|
||||
|
||||
/** 按当前设备树启停串口实例(与核心 reconcile 类似,无 Pipeline/Registry)。 */
|
||||
void reconcileSerialFromTree();
|
||||
|
||||
/** 按当前设备树启停 SocketCAN 实例(endpoint 形如 `can:vcan0`)。 */
|
||||
void reconcileCanFromTree();
|
||||
|
||||
DeviceTreeModel& treeModel() { return *m_tree; }
|
||||
const DeviceTreeModel& treeModel() const { return *m_tree; }
|
||||
|
||||
std::shared_ptr<softbus::core::memory::MemoryPool> memoryPool() const { return m_pool; }
|
||||
|
||||
private:
|
||||
void shutdownAllSerial();
|
||||
#if SOFTBUS_HAVE_CAN
|
||||
void shutdownAllCan();
|
||||
#endif
|
||||
|
||||
std::shared_ptr<DeviceTreeModel> m_tree;
|
||||
std::shared_ptr<softbus::core::memory::MemoryPool> m_pool;
|
||||
softbus::message_bus::ingress::IIngressPort* m_ingress{nullptr};
|
||||
QHash<QString, QSharedPointer<softbus::devices::physical::SerialDevice>> m_serialByEndpoint;
|
||||
#if SOFTBUS_HAVE_CAN
|
||||
QHash<QString, QSharedPointer<softbus::devices::physical::CanDevice>> m_canByEndpoint;
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace softbus::device_bus
|
||||
105
src/device_bus/manager/CanDeviceManager.cpp
Normal file
105
src/device_bus/manager/CanDeviceManager.cpp
Normal file
@@ -0,0 +1,105 @@
|
||||
#include "device_bus/manager/CanDeviceManager.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QCanBus>
|
||||
#include <QCanBusDevice>
|
||||
|
||||
#include "device_bus/tree/DeviceTreeModel.h"
|
||||
#include "devices/DeviceTypes.h"
|
||||
#include "utils/logs/logging.h"
|
||||
|
||||
namespace softbus::device_bus::manager
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
static bool setIfChanged(QJsonObject& params, const QString& key, const QJsonValue& value)
|
||||
{
|
||||
const auto it = params.find(key);
|
||||
if (it == params.end() || it.value() != value) {
|
||||
params.insert(key, value);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool persistCanBitrate(DeviceTreeModel* treeModel,
|
||||
const QString& endpoint,
|
||||
int bitrate,
|
||||
const std::function<void()>& dirtyCb)
|
||||
{
|
||||
if (!treeModel || endpoint.isEmpty() || bitrate <= 0) {
|
||||
return false;
|
||||
}
|
||||
const int idx = treeModel->findIndexByEndpoint(endpoint);
|
||||
if (idx < 0) {
|
||||
return false;
|
||||
}
|
||||
auto& nodes = treeModel->nodesMutable();
|
||||
if (idx >= static_cast<int>(nodes.size())) {
|
||||
return false;
|
||||
}
|
||||
auto& p = nodes[idx].params;
|
||||
bool changed = setIfChanged(p, QStringLiteral("bitrate"), bitrate);
|
||||
if (changed && dirtyCb) {
|
||||
dirtyCb();
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool initializeCanDeviceWithDeps(const QString& endpoint, const QJsonObject& params, const CanInitDeps& deps)
|
||||
{
|
||||
if (!deps.canDevicesByEndpoint || !deps.ingressPort || !deps.memoryPool) {
|
||||
return false;
|
||||
}
|
||||
if (deps.canDevicesByEndpoint->contains(endpoint)) {
|
||||
LOG_WARNING() << "Edge CanDeviceManager: device already exists for" << endpoint;
|
||||
return false;
|
||||
}
|
||||
|
||||
int bitrate = params.value(QStringLiteral("bitrate")).toInt(500'000);
|
||||
if (bitrate <= 0) {
|
||||
bitrate = 500'000;
|
||||
}
|
||||
persistCanBitrate(deps.treeModel, endpoint, bitrate, deps.dirtyCb);
|
||||
|
||||
QString iface = endpoint.trimmed();
|
||||
if (iface.startsWith(QStringLiteral("can:"), Qt::CaseInsensitive)) {
|
||||
iface = iface.mid(4);
|
||||
}
|
||||
QString err;
|
||||
std::unique_ptr<QCanBusDevice> bus(QCanBus::instance()->createDevice(QStringLiteral("socketcan"), iface, &err));
|
||||
if (!bus) {
|
||||
LOG_ERROR() << "Edge CanDeviceManager: createDevice failed" << endpoint << err;
|
||||
return false;
|
||||
}
|
||||
|
||||
softbus::devices::DeviceInfo info;
|
||||
info.id = params.value(QStringLiteral("deviceId")).toInt(-1);
|
||||
info.endpoint = endpoint;
|
||||
info.type = QStringLiteral("can");
|
||||
info.kind = softbus::devices::DeviceKind::Can;
|
||||
info.protocol = params.value(QStringLiteral("protocol")).toString(QStringLiteral("can_raw"));
|
||||
|
||||
softbus::devices::physical::CanDevice::Config cfg;
|
||||
cfg.endpoint = endpoint;
|
||||
cfg.bitrate = bitrate;
|
||||
|
||||
QSharedPointer<softbus::devices::physical::CanDevice> device(
|
||||
new softbus::devices::physical::CanDevice(std::move(bus), cfg, info));
|
||||
device->bindIngress(deps.ingressPort, deps.memoryPool);
|
||||
|
||||
if (!device->start()) {
|
||||
LOG_ERROR() << "Edge CanDeviceManager: connectDevice failed" << endpoint;
|
||||
return false;
|
||||
}
|
||||
|
||||
deps.canDevicesByEndpoint->insert(endpoint, device);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace softbus::device_bus::manager
|
||||
31
src/device_bus/manager/CanDeviceManager.h
Normal file
31
src/device_bus/manager/CanDeviceManager.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#include <QHash>
|
||||
#include <QJsonObject>
|
||||
#include <QSharedPointer>
|
||||
#include <QString>
|
||||
|
||||
#include "core/memory/MemoryPool.h"
|
||||
#include "devices/physical/CanDevice.h"
|
||||
#include "message_bus/ingress/IIngressPort.h"
|
||||
|
||||
class DeviceTreeModel;
|
||||
|
||||
namespace softbus::device_bus::manager
|
||||
{
|
||||
|
||||
struct CanInitDeps
|
||||
{
|
||||
DeviceTreeModel* treeModel{nullptr};
|
||||
std::function<void()> dirtyCb;
|
||||
softbus::message_bus::ingress::IIngressPort* ingressPort{nullptr};
|
||||
std::shared_ptr<softbus::core::memory::MemoryPool> memoryPool;
|
||||
QHash<QString, QSharedPointer<softbus::devices::physical::CanDevice>>* canDevicesByEndpoint{nullptr};
|
||||
};
|
||||
|
||||
bool initializeCanDeviceWithDeps(const QString& endpoint, const QJsonObject& params, const CanInitDeps& deps);
|
||||
|
||||
} // namespace softbus::device_bus::manager
|
||||
152
src/device_bus/manager/SerialDeviceManager.cpp
Normal file
152
src/device_bus/manager/SerialDeviceManager.cpp
Normal file
@@ -0,0 +1,152 @@
|
||||
#include "device_bus/manager/SerialDeviceManager.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QSerialPort>
|
||||
|
||||
#include "device_bus/tree/DeviceTreeModel.h"
|
||||
#include "devices/DeviceTypes.h"
|
||||
#include "hardware/drivers/serial/SerialCapabilities.h"
|
||||
#include "hardware/drivers/serial/SerialQtDriver.h"
|
||||
#include "utils/logs/logging.h"
|
||||
|
||||
using softbus::hardware::drivers::serial::SerialQtDriver;
|
||||
namespace serial_cap = softbus::hardware::drivers::serial::capabilities;
|
||||
|
||||
namespace softbus::device_bus::manager
|
||||
{
|
||||
|
||||
static bool setIfChanged(QJsonObject& params, const QString& key, const QJsonValue& value)
|
||||
{
|
||||
const auto it = params.find(key);
|
||||
if (it == params.end() || it.value() != value) {
|
||||
params.insert(key, value);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool persistSerialParams(DeviceTreeModel* treeModel,
|
||||
const QString& endpoint,
|
||||
const QJsonObject& mergedParams,
|
||||
const std::function<void()>& dirtyCb)
|
||||
{
|
||||
if (!treeModel || endpoint.isEmpty() || mergedParams.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const int idx = treeModel->findIndexByEndpoint(endpoint);
|
||||
if (idx < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto& nodes = treeModel->nodesMutable();
|
||||
if (idx >= nodes.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto& node = nodes[idx];
|
||||
auto& p = node.params;
|
||||
|
||||
bool changed = false;
|
||||
changed = setIfChanged(p, QStringLiteral("baudRate"), mergedParams.value(QStringLiteral("baudRate"))) || changed;
|
||||
changed = setIfChanged(p, QStringLiteral("dataBits"), mergedParams.value(QStringLiteral("dataBits"))) || changed;
|
||||
changed = setIfChanged(p, QStringLiteral("parity"), mergedParams.value(QStringLiteral("parity"))) || changed;
|
||||
changed = setIfChanged(p, QStringLiteral("stopBits"), mergedParams.value(QStringLiteral("stopBits"))) || changed;
|
||||
changed =
|
||||
setIfChanged(p, QStringLiteral("flowControl"), mergedParams.value(QStringLiteral("flowControl"))) || changed;
|
||||
|
||||
if (changed && dirtyCb) {
|
||||
dirtyCb();
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
bool initializeSerialDeviceWithDeps(const QString& endpoint, const QJsonObject& params, const SerialInitDeps& deps)
|
||||
{
|
||||
if (!deps.serialDevicesByEndpoint || !deps.ingressPort || !deps.memoryPool) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (deps.serialDevicesByEndpoint->contains(endpoint)) {
|
||||
LOG_WARNING() << "Edge SerialDeviceManager: serial device already exists for" << endpoint;
|
||||
return false;
|
||||
}
|
||||
|
||||
serial_cap::SerialDefaults def = serial_cap::kDefaults;
|
||||
|
||||
int baud = params.value(QStringLiteral("baudRate")).toInt(def.baudRate);
|
||||
if (!serial_cap::isValidBaud(baud)) baud = def.baudRate;
|
||||
|
||||
int dataBits = params.value(QStringLiteral("dataBits")).toInt(def.dataBits);
|
||||
if (!serial_cap::isValidDataBits(dataBits)) dataBits = def.dataBits;
|
||||
|
||||
QString parityStr = params.value(QStringLiteral("parity")).toString(QLatin1String(def.parity));
|
||||
if (!serial_cap::isValidParity(parityStr)) parityStr = QLatin1String(def.parity);
|
||||
|
||||
int stopBitsVal = params.value(QStringLiteral("stopBits")).toInt(def.stopBits);
|
||||
if (!serial_cap::isValidStopBits(stopBitsVal)) stopBitsVal = def.stopBits;
|
||||
|
||||
QString flowStr = params.value(QStringLiteral("flowControl")).toString(QLatin1String(def.flowControl));
|
||||
if (!serial_cap::isValidFlowControl(flowStr)) flowStr = QLatin1String(def.flowControl);
|
||||
|
||||
QJsonObject merged;
|
||||
merged.insert(QStringLiteral("baudRate"), baud);
|
||||
merged.insert(QStringLiteral("dataBits"), dataBits);
|
||||
merged.insert(QStringLiteral("parity"), parityStr);
|
||||
merged.insert(QStringLiteral("stopBits"), stopBitsVal);
|
||||
merged.insert(QStringLiteral("flowControl"), flowStr);
|
||||
|
||||
persistSerialParams(deps.treeModel, endpoint, merged, deps.dirtyCb);
|
||||
|
||||
auto driver = std::make_unique<SerialQtDriver>(nullptr);
|
||||
driver->SetBaudRate(static_cast<QSerialPort::BaudRate>(baud));
|
||||
driver->SetDataBits(static_cast<QSerialPort::DataBits>(dataBits));
|
||||
|
||||
QSerialPort::Parity parity = QSerialPort::NoParity;
|
||||
if (parityStr == QStringLiteral("even")) {
|
||||
parity = QSerialPort::EvenParity;
|
||||
} else if (parityStr == QStringLiteral("odd")) {
|
||||
parity = QSerialPort::OddParity;
|
||||
}
|
||||
driver->SetParity(parity);
|
||||
|
||||
QSerialPort::StopBits stopBits = (stopBitsVal == 2) ? QSerialPort::TwoStop : QSerialPort::OneStop;
|
||||
driver->SetStopBits(stopBits);
|
||||
|
||||
QSerialPort::FlowControl flow = QSerialPort::NoFlowControl;
|
||||
if (flowStr == QStringLiteral("hardware")) {
|
||||
flow = QSerialPort::HardwareControl;
|
||||
} else if (flowStr == QStringLiteral("software")) {
|
||||
flow = QSerialPort::SoftwareControl;
|
||||
}
|
||||
driver->SetFlowControl(flow);
|
||||
|
||||
softbus::devices::DeviceInfo info;
|
||||
info.id = params.value(QStringLiteral("deviceId")).toInt(-1);
|
||||
info.endpoint = endpoint;
|
||||
info.type = QStringLiteral("serial");
|
||||
info.kind = softbus::devices::DeviceKind::Serial;
|
||||
info.protocol = params.value(QStringLiteral("protocol")).toString(QStringLiteral("modbus_rtu"));
|
||||
|
||||
softbus::devices::physical::SerialDevice::Config cfg;
|
||||
cfg.endpoint = endpoint;
|
||||
cfg.baudRate = baud;
|
||||
cfg.dataBits = dataBits;
|
||||
cfg.parity = parityStr;
|
||||
cfg.stopBits = stopBitsVal;
|
||||
cfg.flowControl = flowStr;
|
||||
|
||||
QSharedPointer<softbus::devices::physical::SerialDevice> device(
|
||||
new softbus::devices::physical::SerialDevice(std::move(driver), cfg, info));
|
||||
device->bindIngress(deps.ingressPort, deps.memoryPool);
|
||||
|
||||
if (!device->start()) {
|
||||
LOG_ERROR() << "Edge SerialDeviceManager: failed to start serial device for" << endpoint;
|
||||
return false;
|
||||
}
|
||||
|
||||
deps.serialDevicesByEndpoint->insert(endpoint, device);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace softbus::device_bus::manager
|
||||
32
src/device_bus/manager/SerialDeviceManager.h
Normal file
32
src/device_bus/manager/SerialDeviceManager.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
// 与 softbus_daemon 同源布局;边缘侧去掉 Registry / Egress / Pipeline,仅保留串口 + IIngressPort。
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#include <QHash>
|
||||
#include <QJsonObject>
|
||||
#include <QSharedPointer>
|
||||
#include <QString>
|
||||
|
||||
#include "core/memory/MemoryPool.h"
|
||||
#include "devices/physical/SerialDevice.h"
|
||||
#include "message_bus/ingress/IIngressPort.h"
|
||||
|
||||
class DeviceTreeModel;
|
||||
|
||||
namespace softbus::device_bus::manager
|
||||
{
|
||||
|
||||
struct SerialInitDeps
|
||||
{
|
||||
DeviceTreeModel* treeModel{nullptr};
|
||||
std::function<void()> dirtyCb;
|
||||
softbus::message_bus::ingress::IIngressPort* ingressPort{nullptr};
|
||||
std::shared_ptr<softbus::core::memory::MemoryPool> memoryPool;
|
||||
QHash<QString, QSharedPointer<softbus::devices::physical::SerialDevice>>* serialDevicesByEndpoint{nullptr};
|
||||
};
|
||||
|
||||
bool initializeSerialDeviceWithDeps(const QString& endpoint, const QJsonObject& params, const SerialInitDeps& deps);
|
||||
|
||||
} // namespace softbus::device_bus::manager
|
||||
50
src/device_bus/monitor/DeviceEvent.h
Normal file
50
src/device_bus/monitor/DeviceEvent.h
Normal file
@@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
|
||||
namespace softbus::device_bus::monitor
|
||||
{
|
||||
|
||||
enum class DeviceEventType
|
||||
{
|
||||
Serial,
|
||||
Usb,
|
||||
Storage,
|
||||
NetInterface,
|
||||
Unknown,
|
||||
};
|
||||
|
||||
enum class DeviceEventAction
|
||||
{
|
||||
Add,
|
||||
Remove,
|
||||
Change,
|
||||
Unknown,
|
||||
};
|
||||
|
||||
struct DeviceEvent
|
||||
{
|
||||
DeviceEventType type{DeviceEventType::Unknown};
|
||||
DeviceEventAction action{DeviceEventAction::Unknown};
|
||||
QDateTime timestamp{QDateTime::currentDateTimeUtc()};
|
||||
|
||||
QString subsystem;
|
||||
QString devnode;
|
||||
QString devicePath;
|
||||
|
||||
QString vid;
|
||||
QString pid;
|
||||
QString serial;
|
||||
QString model;
|
||||
QString vendor;
|
||||
|
||||
QString ifname;
|
||||
int ifindex{-1};
|
||||
bool linkUp{false};
|
||||
|
||||
QMap<QString, QString> props;
|
||||
};
|
||||
|
||||
} // namespace softbus::device_bus::monitor
|
||||
286
src/device_bus/monitor/EdgeDiscoveryCoordinator.cpp
Normal file
286
src/device_bus/monitor/EdgeDiscoveryCoordinator.cpp
Normal file
@@ -0,0 +1,286 @@
|
||||
#include "device_bus/monitor/EdgeDiscoveryCoordinator.h"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QMutexLocker>
|
||||
#include <QTimer>
|
||||
|
||||
#include "device_bus/monitor/UdevDeviceMonitor.h"
|
||||
#include "message_bus/ingress/EdgeSession.h"
|
||||
#include "utils/logs/logging.h"
|
||||
|
||||
namespace softbus_edge
|
||||
{
|
||||
|
||||
EdgeDiscoveryCoordinator::EdgeDiscoveryCoordinator(QObject* parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
EdgeDiscoveryCoordinator::~EdgeDiscoveryCoordinator()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
void EdgeDiscoveryCoordinator::setEdgeSession(softbus::message_bus::ingress::EdgeSession* session)
|
||||
{
|
||||
m_session = session;
|
||||
}
|
||||
|
||||
void EdgeDiscoveryCoordinator::setEdgeId(const QString& edgeId)
|
||||
{
|
||||
m_edgeId = edgeId;
|
||||
}
|
||||
|
||||
void EdgeDiscoveryCoordinator::setCoreTreeRevisionGetter(std::function<qint64()> fn)
|
||||
{
|
||||
m_getCoreRevision = std::move(fn);
|
||||
}
|
||||
|
||||
QString EdgeDiscoveryCoordinator::stableKeyForSerial(const softbus::device_bus::monitor::DeviceEvent& ev) const
|
||||
{
|
||||
const QString syspath = ev.props.value(QStringLiteral("SYSPATH"));
|
||||
const QString basis = !syspath.isEmpty() ? syspath : ev.devnode;
|
||||
return QString::number(qHash(basis));
|
||||
}
|
||||
|
||||
QString EdgeDiscoveryCoordinator::nodeIdForStableKey(const QString& stableKey) const
|
||||
{
|
||||
return QStringLiteral("edges/%1/discovered/%2").arg(m_edgeId, stableKey);
|
||||
}
|
||||
|
||||
void EdgeDiscoveryCoordinator::enqueueUpsert(const softbus::device_bus::monitor::DeviceEvent& ev)
|
||||
{
|
||||
if (m_edgeId.isEmpty() || ev.devnode.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
const QString sk = stableKeyForSerial(ev);
|
||||
const QString nid = nodeIdForStableKey(sk);
|
||||
|
||||
QJsonObject params;
|
||||
params.insert(QStringLiteral("edgeId"), m_edgeId);
|
||||
params.insert(QStringLiteral("stableKey"), sk);
|
||||
params.insert(QStringLiteral("online"), true);
|
||||
params.insert(QStringLiteral("baudRate"), 115200);
|
||||
params.insert(QStringLiteral("protocol"), QStringLiteral("modbus_rtu"));
|
||||
params.insert(QStringLiteral("discoveredVia"), QStringLiteral("udev"));
|
||||
if (ev.props.contains(QStringLiteral("SYSPATH"))) {
|
||||
params.insert(QStringLiteral("syspath"), ev.props.value(QStringLiteral("SYSPATH")));
|
||||
}
|
||||
|
||||
QJsonObject node;
|
||||
node.insert(QStringLiteral("id"), nid);
|
||||
node.insert(QStringLiteral("name"), QStringLiteral("discovered %1").arg(ev.devnode));
|
||||
node.insert(QStringLiteral("type"), QStringLiteral("serial"));
|
||||
node.insert(QStringLiteral("endpoint"), ev.devnode);
|
||||
node.insert(QStringLiteral("enabled"), true);
|
||||
node.insert(QStringLiteral("params"), params);
|
||||
|
||||
PendingUpsert pu;
|
||||
pu.stableKey = sk;
|
||||
pu.node = node;
|
||||
m_pendingUpsert.insert(sk, pu);
|
||||
m_devnodeToStableKey.insert(ev.devnode, sk);
|
||||
|
||||
QJsonObject recent;
|
||||
recent.insert(QStringLiteral("ts"), ev.timestamp.toString(Qt::ISODate));
|
||||
recent.insert(QStringLiteral("action"), QStringLiteral("add"));
|
||||
recent.insert(QStringLiteral("devnode"), ev.devnode);
|
||||
recent.insert(QStringLiteral("nodeId"), nid);
|
||||
{
|
||||
QMutexLocker lk(&m_recentMutex);
|
||||
m_recent.push_back(recent);
|
||||
if (m_recent.size() > 64) {
|
||||
m_recent.remove(0, m_recent.size() - 64);
|
||||
}
|
||||
}
|
||||
|
||||
scheduleFlush();
|
||||
}
|
||||
|
||||
void EdgeDiscoveryCoordinator::enqueueOfflinePatch(const QString& devnode)
|
||||
{
|
||||
const QString sk = m_devnodeToStableKey.value(devnode);
|
||||
if (sk.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
const QString nid = nodeIdForStableKey(sk);
|
||||
|
||||
QJsonObject recent;
|
||||
recent.insert(QStringLiteral("ts"), QDateTime::currentDateTimeUtc().toString(Qt::ISODate));
|
||||
recent.insert(QStringLiteral("action"), QStringLiteral("remove"));
|
||||
recent.insert(QStringLiteral("devnode"), devnode);
|
||||
recent.insert(QStringLiteral("nodeId"), nid);
|
||||
{
|
||||
QMutexLocker lk(&m_recentMutex);
|
||||
m_recent.push_back(recent);
|
||||
if (m_recent.size() > 64) {
|
||||
m_recent.remove(0, m_recent.size() - 64);
|
||||
}
|
||||
}
|
||||
|
||||
m_pendingUpsert.remove(sk);
|
||||
m_devnodeToStableKey.remove(devnode);
|
||||
|
||||
QJsonObject patchEntry;
|
||||
patchEntry.insert(QStringLiteral("op"), QStringLiteral("patch"));
|
||||
patchEntry.insert(QStringLiteral("id"), nid);
|
||||
QJsonObject patch;
|
||||
QJsonObject pparams;
|
||||
pparams.insert(QStringLiteral("online"), false);
|
||||
patch.insert(QStringLiteral("params"), pparams);
|
||||
patchEntry.insert(QStringLiteral("patch"), patch);
|
||||
m_pendingPatchEntries.append(patchEntry);
|
||||
|
||||
scheduleFlush();
|
||||
}
|
||||
|
||||
void EdgeDiscoveryCoordinator::handleDeviceEvent(const softbus::device_bus::monitor::DeviceEvent& ev)
|
||||
{
|
||||
if (ev.type != softbus::device_bus::monitor::DeviceEventType::Serial) {
|
||||
return;
|
||||
}
|
||||
if (ev.action == softbus::device_bus::monitor::DeviceEventAction::Add) {
|
||||
enqueueUpsert(ev);
|
||||
} else if (ev.action == softbus::device_bus::monitor::DeviceEventAction::Remove) {
|
||||
enqueueOfflinePatch(ev.devnode);
|
||||
}
|
||||
}
|
||||
|
||||
void EdgeDiscoveryCoordinator::scheduleFlush()
|
||||
{
|
||||
if (!m_debounceTimer) {
|
||||
m_debounceTimer = new QTimer(this);
|
||||
m_debounceTimer->setSingleShot(true);
|
||||
connect(m_debounceTimer, &QTimer::timeout, this, [this]() { flushPending(); });
|
||||
}
|
||||
m_debounceTimer->start(400);
|
||||
}
|
||||
|
||||
void EdgeDiscoveryCoordinator::flushPending()
|
||||
{
|
||||
if (!m_session || m_edgeId.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (m_proposeInFlight) {
|
||||
return;
|
||||
}
|
||||
const qint64 base = m_getCoreRevision ? m_getCoreRevision() : 0;
|
||||
if (base <= 0) {
|
||||
LOG_INFO() << "EdgeDiscoveryCoordinator: skip flush, core treeRevision not ready yet";
|
||||
return;
|
||||
}
|
||||
if (m_pendingUpsert.isEmpty() && m_pendingPatchEntries.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonArray entries;
|
||||
for (auto it = m_pendingUpsert.constBegin(); it != m_pendingUpsert.constEnd(); ++it) {
|
||||
QJsonObject e;
|
||||
e.insert(QStringLiteral("op"), QStringLiteral("upsert"));
|
||||
e.insert(QStringLiteral("id"), it->node.value(QStringLiteral("id")).toString());
|
||||
e.insert(QStringLiteral("node"), it->node);
|
||||
entries.append(e);
|
||||
}
|
||||
for (const QJsonObject& pe : m_pendingPatchEntries) {
|
||||
entries.append(pe);
|
||||
}
|
||||
|
||||
if (entries.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonObject body;
|
||||
body.insert(QStringLiteral("edgeId"), m_edgeId);
|
||||
body.insert(QStringLiteral("baseTreeRevision"), base);
|
||||
body.insert(QStringLiteral("entries"), entries);
|
||||
|
||||
if (!m_session->sendTreePropose(body)) {
|
||||
LOG_WARNING() << "EdgeDiscoveryCoordinator: sendTreePropose failed";
|
||||
return;
|
||||
}
|
||||
|
||||
m_proposeInFlight = true;
|
||||
LOG_INFO() << "EdgeDiscoveryCoordinator: sent TREE_PROPOSE" << entries.size() << "entries baseRev" << base;
|
||||
}
|
||||
|
||||
void EdgeDiscoveryCoordinator::onTreeResultFromCore(const QJsonObject& o)
|
||||
{
|
||||
if (o.value(QStringLiteral("ok")).toBool()) {
|
||||
const QString msg = o.value(QStringLiteral("message")).toString();
|
||||
if (msg == QStringLiteral("hello")) {
|
||||
m_proposeInFlight = false;
|
||||
if (o.contains(QStringLiteral("currentTreeRevision"))) {
|
||||
m_lastKnownCoreRevision = o.value(QStringLiteral("currentTreeRevision")).toVariant().toLongLong();
|
||||
}
|
||||
scheduleFlush();
|
||||
return;
|
||||
}
|
||||
m_proposeInFlight = false;
|
||||
if (o.contains(QStringLiteral("newTreeRevision"))) {
|
||||
m_lastKnownCoreRevision = o.value(QStringLiteral("newTreeRevision")).toVariant().toLongLong();
|
||||
m_pendingUpsert.clear();
|
||||
m_pendingPatchEntries.clear();
|
||||
} else if (o.contains(QStringLiteral("currentTreeRevision"))) {
|
||||
m_lastKnownCoreRevision = o.value(QStringLiteral("currentTreeRevision")).toVariant().toLongLong();
|
||||
}
|
||||
scheduleFlush();
|
||||
return;
|
||||
}
|
||||
m_proposeInFlight = false;
|
||||
if (o.value(QStringLiteral("code")).toString() == QStringLiteral("CONFLICT")) {
|
||||
if (o.contains(QStringLiteral("currentTreeRevision"))) {
|
||||
m_lastKnownCoreRevision = o.value(QStringLiteral("currentTreeRevision")).toVariant().toLongLong();
|
||||
}
|
||||
LOG_WARNING() << "EdgeDiscoveryCoordinator: CONFLICT from core, retrying pending";
|
||||
scheduleFlush();
|
||||
}
|
||||
}
|
||||
|
||||
QJsonArray EdgeDiscoveryCoordinator::recentDiscoveriesJson() const
|
||||
{
|
||||
QMutexLocker lk(&m_recentMutex);
|
||||
QJsonArray arr;
|
||||
for (const QJsonObject& o : m_recent) {
|
||||
arr.append(o);
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
bool EdgeDiscoveryCoordinator::start()
|
||||
{
|
||||
if (m_udev) {
|
||||
return true;
|
||||
}
|
||||
m_udev = std::make_unique<softbus::device_bus::monitor::UdevDeviceMonitor>(this);
|
||||
m_udev->setCallback([this](const softbus::device_bus::monitor::DeviceEvent& ev) { handleDeviceEvent(ev); });
|
||||
if (!m_udev->start()) {
|
||||
m_udev.reset();
|
||||
LOG_WARNING() << "EdgeDiscoveryCoordinator: udev monitor not started (non-Linux or udev init failed)";
|
||||
return false;
|
||||
}
|
||||
const auto snap = m_udev->snapshotExisting();
|
||||
for (const auto& ev : snap) {
|
||||
handleDeviceEvent(ev);
|
||||
}
|
||||
LOG_INFO() << "EdgeDiscoveryCoordinator: started, initial snapshot size" << snap.size();
|
||||
return true;
|
||||
}
|
||||
|
||||
void EdgeDiscoveryCoordinator::stop()
|
||||
{
|
||||
if (m_debounceTimer) {
|
||||
m_debounceTimer->stop();
|
||||
}
|
||||
if (m_udev) {
|
||||
m_udev->stop();
|
||||
m_udev.reset();
|
||||
}
|
||||
m_pendingUpsert.clear();
|
||||
m_pendingPatchEntries.clear();
|
||||
m_devnodeToStableKey.clear();
|
||||
}
|
||||
|
||||
} // namespace softbus_edge
|
||||
81
src/device_bus/monitor/EdgeDiscoveryCoordinator.h
Normal file
81
src/device_bus/monitor/EdgeDiscoveryCoordinator.h
Normal file
@@ -0,0 +1,81 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#include <QHash>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QMutex>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
|
||||
class QTimer;
|
||||
|
||||
#include "device_bus/monitor/DeviceEvent.h"
|
||||
|
||||
namespace softbus::device_bus::monitor
|
||||
{
|
||||
class UdevDeviceMonitor;
|
||||
}
|
||||
|
||||
namespace softbus::message_bus::ingress
|
||||
{
|
||||
class EdgeSession;
|
||||
}
|
||||
|
||||
namespace softbus_edge
|
||||
{
|
||||
|
||||
/** udev 串口发现 → 组装 `TREE_PROPOSE`(含 `op: upsert`)向核心注册设备树节点。 */
|
||||
class EdgeDiscoveryCoordinator final : public QObject
|
||||
{
|
||||
public:
|
||||
explicit EdgeDiscoveryCoordinator(QObject* parent = nullptr);
|
||||
~EdgeDiscoveryCoordinator() override;
|
||||
|
||||
void setEdgeSession(softbus::message_bus::ingress::EdgeSession* session);
|
||||
void setEdgeId(const QString& edgeId);
|
||||
void setCoreTreeRevisionGetter(std::function<qint64()> fn);
|
||||
|
||||
bool start();
|
||||
void stop();
|
||||
|
||||
void onTreeResultFromCore(const QJsonObject& o);
|
||||
|
||||
QJsonArray recentDiscoveriesJson() const;
|
||||
|
||||
private:
|
||||
QString stableKeyForSerial(const softbus::device_bus::monitor::DeviceEvent& ev) const;
|
||||
QString nodeIdForStableKey(const QString& stableKey) const;
|
||||
void handleDeviceEvent(const softbus::device_bus::monitor::DeviceEvent& ev);
|
||||
void enqueueUpsert(const softbus::device_bus::monitor::DeviceEvent& ev);
|
||||
void enqueueOfflinePatch(const QString& devnode);
|
||||
void scheduleFlush();
|
||||
void flushPending();
|
||||
|
||||
softbus::message_bus::ingress::EdgeSession* m_session{nullptr};
|
||||
QString m_edgeId;
|
||||
std::function<qint64()> m_getCoreRevision;
|
||||
|
||||
std::unique_ptr<softbus::device_bus::monitor::UdevDeviceMonitor> m_udev;
|
||||
|
||||
struct PendingUpsert
|
||||
{
|
||||
QString stableKey;
|
||||
QJsonObject node;
|
||||
};
|
||||
QHash<QString, PendingUpsert> m_pendingUpsert;
|
||||
QVector<QJsonObject> m_pendingPatchEntries;
|
||||
QHash<QString, QString> m_devnodeToStableKey;
|
||||
|
||||
mutable QMutex m_recentMutex;
|
||||
QVector<QJsonObject> m_recent;
|
||||
|
||||
qint64 m_lastKnownCoreRevision{0};
|
||||
QTimer* m_debounceTimer{nullptr};
|
||||
bool m_proposeInFlight{false};
|
||||
};
|
||||
|
||||
} // namespace softbus_edge
|
||||
47
src/device_bus/monitor/IDeviceMonitor.h
Normal file
47
src/device_bus/monitor/IDeviceMonitor.h
Normal file
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "device_bus/monitor/DeviceEvent.h"
|
||||
|
||||
namespace softbus::device_bus::monitor
|
||||
{
|
||||
|
||||
class IDeviceMonitor : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
using EventCallback = std::function<void(const DeviceEvent&)>;
|
||||
|
||||
explicit IDeviceMonitor(QObject* parent = nullptr)
|
||||
: QObject(parent)
|
||||
{
|
||||
}
|
||||
~IDeviceMonitor() override = default;
|
||||
|
||||
virtual bool start() = 0;
|
||||
virtual void stop() = 0;
|
||||
virtual std::vector<DeviceEvent> snapshotExisting() = 0;
|
||||
|
||||
void setCallback(EventCallback cb) { m_cb = std::move(cb); }
|
||||
|
||||
protected:
|
||||
void emitEvent(const DeviceEvent& ev)
|
||||
{
|
||||
if (m_cb) {
|
||||
m_cb(ev);
|
||||
}
|
||||
emit event(ev);
|
||||
}
|
||||
|
||||
signals:
|
||||
void event(const softbus::device_bus::monitor::DeviceEvent& ev);
|
||||
|
||||
private:
|
||||
EventCallback m_cb;
|
||||
};
|
||||
|
||||
} // namespace softbus::device_bus::monitor
|
||||
362
src/device_bus/monitor/UdevDeviceMonitor.cpp
Normal file
362
src/device_bus/monitor/UdevDeviceMonitor.cpp
Normal file
@@ -0,0 +1,362 @@
|
||||
#include "device_bus/monitor/UdevDeviceMonitor.h"
|
||||
|
||||
#if defined(__linux__)
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <QFileInfo>
|
||||
#include <QRegularExpression>
|
||||
|
||||
#include "utils/logs/logging.h"
|
||||
|
||||
namespace softbus::device_bus::monitor
|
||||
{
|
||||
namespace
|
||||
{
|
||||
|
||||
bool isWhitelistedSerialDevnode(const QString& devnode)
|
||||
{
|
||||
if (devnode.startsWith(QStringLiteral("/dev/ttyUSB"))) {
|
||||
return true;
|
||||
}
|
||||
if (devnode.startsWith(QStringLiteral("/dev/ttyACM"))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static const QRegularExpression kTtySRe(QStringLiteral("^/dev/ttyS(\\d+)$"));
|
||||
const auto m = kTtySRe.match(devnode);
|
||||
if (m.hasMatch()) {
|
||||
bool ok = false;
|
||||
const int n = m.captured(1).toInt(&ok);
|
||||
if (ok && n >= 1 && n <= 6) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isConsoleLikeTty(const QString& devnode)
|
||||
{
|
||||
if (devnode == QStringLiteral("/dev/console")) {
|
||||
return true;
|
||||
}
|
||||
if (devnode == QStringLiteral("/dev/tty")) {
|
||||
return true;
|
||||
}
|
||||
if (devnode == QStringLiteral("/dev/ptmx")) {
|
||||
return true;
|
||||
}
|
||||
if (devnode.startsWith(QStringLiteral("/dev/pts/"))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static const QRegularExpression kVtRe(QStringLiteral("^/dev/tty(\\d+)$"));
|
||||
return kVtRe.match(devnode).hasMatch();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
UdevDeviceMonitor::UdevDeviceMonitor(QObject* parent)
|
||||
: IDeviceMonitor(parent)
|
||||
{
|
||||
}
|
||||
|
||||
UdevDeviceMonitor::~UdevDeviceMonitor()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
bool UdevDeviceMonitor::start()
|
||||
{
|
||||
if (m_mon) {
|
||||
return true;
|
||||
}
|
||||
|
||||
m_udev = udev_new();
|
||||
if (!m_udev) {
|
||||
LOG_ERROR() << "UdevDeviceMonitor: udev_new failed";
|
||||
return false;
|
||||
}
|
||||
|
||||
m_mon = udev_monitor_new_from_netlink(m_udev, "udev");
|
||||
if (!m_mon) {
|
||||
LOG_ERROR() << "UdevDeviceMonitor: udev_monitor_new_from_netlink failed";
|
||||
udev_unref(m_udev);
|
||||
m_udev = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
udev_monitor_filter_add_match_subsystem_devtype(m_mon, "tty", nullptr);
|
||||
udev_monitor_filter_add_match_subsystem_devtype(m_mon, "usb", nullptr);
|
||||
udev_monitor_filter_add_match_subsystem_devtype(m_mon, "block", nullptr);
|
||||
|
||||
if (udev_monitor_enable_receiving(m_mon) != 0) {
|
||||
LOG_ERROR() << "UdevDeviceMonitor: enable_receiving failed";
|
||||
stop();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_fd = udev_monitor_get_fd(m_mon);
|
||||
if (m_fd < 0) {
|
||||
LOG_ERROR() << "UdevDeviceMonitor: monitor fd invalid";
|
||||
stop();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_notifier = new QSocketNotifier(m_fd, QSocketNotifier::Read, this);
|
||||
connect(m_notifier, &QSocketNotifier::activated, this, [this]() { onReadable(); });
|
||||
|
||||
LOG_INFO() << "UdevDeviceMonitor started fd=" << m_fd;
|
||||
return true;
|
||||
}
|
||||
|
||||
void UdevDeviceMonitor::stop()
|
||||
{
|
||||
if (m_notifier) {
|
||||
m_notifier->setEnabled(false);
|
||||
m_notifier->deleteLater();
|
||||
m_notifier = nullptr;
|
||||
}
|
||||
m_fd = -1;
|
||||
|
||||
if (m_mon) {
|
||||
udev_monitor_unref(m_mon);
|
||||
m_mon = nullptr;
|
||||
}
|
||||
if (m_udev) {
|
||||
udev_unref(m_udev);
|
||||
m_udev = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void UdevDeviceMonitor::onReadable()
|
||||
{
|
||||
if (!m_mon) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
struct udev_device* dev = udev_monitor_receive_device(m_mon);
|
||||
if (!dev) {
|
||||
break;
|
||||
}
|
||||
|
||||
const char* action = udev_device_get_action(dev);
|
||||
DeviceEvent ev = toEvent(dev, action);
|
||||
|
||||
if (!shouldKeepEvent(ev)) {
|
||||
udev_device_unref(dev);
|
||||
continue;
|
||||
}
|
||||
|
||||
emitEvent(ev);
|
||||
udev_device_unref(dev);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<DeviceEvent> UdevDeviceMonitor::snapshotExisting()
|
||||
{
|
||||
std::vector<DeviceEvent> out;
|
||||
if (!m_udev) {
|
||||
m_udev = udev_new();
|
||||
if (!m_udev) {
|
||||
LOG_ERROR() << "UdevDeviceMonitor: snapshot udev_new failed";
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
udev_enumerate* en = udev_enumerate_new(m_udev);
|
||||
if (!en) {
|
||||
return out;
|
||||
}
|
||||
|
||||
udev_enumerate_add_match_subsystem(en, "tty");
|
||||
udev_enumerate_add_match_subsystem(en, "usb");
|
||||
udev_enumerate_add_match_subsystem(en, "block");
|
||||
if (udev_enumerate_scan_devices(en) != 0) {
|
||||
udev_enumerate_unref(en);
|
||||
return out;
|
||||
}
|
||||
|
||||
udev_list_entry* devices = udev_enumerate_get_list_entry(en);
|
||||
udev_list_entry* entry = nullptr;
|
||||
udev_list_entry_foreach(entry, devices)
|
||||
{
|
||||
const char* path = udev_list_entry_get_name(entry);
|
||||
if (!path) {
|
||||
continue;
|
||||
}
|
||||
|
||||
udev_device* dev = udev_device_new_from_syspath(m_udev, path);
|
||||
if (!dev) {
|
||||
continue;
|
||||
}
|
||||
|
||||
DeviceEvent ev = toEvent(dev, "add");
|
||||
if (shouldKeepEvent(ev)) {
|
||||
out.push_back(ev);
|
||||
}
|
||||
|
||||
udev_device_unref(dev);
|
||||
}
|
||||
|
||||
udev_enumerate_unref(en);
|
||||
return out;
|
||||
}
|
||||
|
||||
bool UdevDeviceMonitor::shouldKeepEvent(const DeviceEvent& ev) const
|
||||
{
|
||||
if (ev.action == DeviceEventAction::Unknown) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ev.subsystem == QStringLiteral("tty")) {
|
||||
if (ev.devnode.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
if (isConsoleLikeTty(ev.devnode)) {
|
||||
return false;
|
||||
}
|
||||
if (!isWhitelistedSerialDevnode(ev.devnode)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (ev.subsystem == QStringLiteral("usb")) {
|
||||
const QString devtype = ev.props.value(QStringLiteral("DEVTYPE"));
|
||||
if (!devtype.isEmpty() && devtype != QStringLiteral("usb_device")) {
|
||||
return false;
|
||||
}
|
||||
if (ev.action == DeviceEventAction::Change) {
|
||||
return false;
|
||||
}
|
||||
if (ev.devnode.isEmpty() && ev.vid.isEmpty() && ev.pid.isEmpty() && ev.serial.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
if (ev.vid == QStringLiteral("1d6b")) {
|
||||
return false;
|
||||
}
|
||||
const QString modelLower = ev.model.toLower();
|
||||
if (modelLower.contains(QStringLiteral("hub")) || modelLower.contains(QStringLiteral("host_controller"))
|
||||
|| modelLower.contains(QStringLiteral("xhci"))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (ev.subsystem == QStringLiteral("block")) {
|
||||
if (ev.type != DeviceEventType::Storage) {
|
||||
return false;
|
||||
}
|
||||
if (ev.action == DeviceEventAction::Change) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QString UdevDeviceMonitor::getProp(struct udev_device* dev, const char* key)
|
||||
{
|
||||
if (!dev || !key) {
|
||||
return {};
|
||||
}
|
||||
const char* v = udev_device_get_property_value(dev, key);
|
||||
return v ? QString::fromUtf8(v) : QString();
|
||||
}
|
||||
|
||||
DeviceEvent UdevDeviceMonitor::toEvent(struct udev_device* dev, const char* action)
|
||||
{
|
||||
DeviceEvent ev;
|
||||
ev.timestamp = QDateTime::currentDateTimeUtc();
|
||||
|
||||
const char* subsys = udev_device_get_subsystem(dev);
|
||||
if (subsys) {
|
||||
ev.subsystem = QString::fromUtf8(subsys);
|
||||
}
|
||||
|
||||
const char* devtype = udev_device_get_devtype(dev);
|
||||
if (devtype) {
|
||||
ev.props.insert(QStringLiteral("DEVTYPE"), QString::fromUtf8(devtype));
|
||||
}
|
||||
|
||||
const char* syspath = udev_device_get_syspath(dev);
|
||||
if (syspath) {
|
||||
ev.props.insert(QStringLiteral("SYSPATH"), QString::fromUtf8(syspath));
|
||||
}
|
||||
|
||||
const char* devnode = udev_device_get_devnode(dev);
|
||||
if (devnode) {
|
||||
ev.devnode = QString::fromUtf8(devnode);
|
||||
}
|
||||
|
||||
const QString act = action ? QString::fromUtf8(action) : QString();
|
||||
if (act == QStringLiteral("add")) {
|
||||
ev.action = DeviceEventAction::Add;
|
||||
} else if (act == QStringLiteral("remove")) {
|
||||
ev.action = DeviceEventAction::Remove;
|
||||
} else if (act == QStringLiteral("change")) {
|
||||
ev.action = DeviceEventAction::Change;
|
||||
} else {
|
||||
ev.action = DeviceEventAction::Unknown;
|
||||
}
|
||||
|
||||
if (ev.subsystem == QStringLiteral("tty")) {
|
||||
ev.type = DeviceEventType::Serial;
|
||||
} else if (ev.subsystem == QStringLiteral("usb")) {
|
||||
ev.type = DeviceEventType::Usb;
|
||||
} else if (ev.subsystem == QStringLiteral("block")) {
|
||||
const QString idBus = getProp(dev, "ID_BUS");
|
||||
if (idBus == QStringLiteral("usb")) {
|
||||
ev.type = DeviceEventType::Storage;
|
||||
} else {
|
||||
struct udev_device* p = udev_device_get_parent_with_subsystem_devtype(dev, "usb", nullptr);
|
||||
if (p) {
|
||||
ev.type = DeviceEventType::Storage;
|
||||
} else {
|
||||
ev.type = DeviceEventType::Unknown;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ev.type = DeviceEventType::Unknown;
|
||||
}
|
||||
|
||||
ev.vid = getProp(dev, "ID_VENDOR_ID");
|
||||
ev.pid = getProp(dev, "ID_MODEL_ID");
|
||||
ev.serial = getProp(dev, "ID_SERIAL_SHORT");
|
||||
ev.model = getProp(dev, "ID_MODEL");
|
||||
ev.vendor = getProp(dev, "ID_VENDOR");
|
||||
|
||||
if (ev.vid.isEmpty() && ev.subsystem == QStringLiteral("usb")) {
|
||||
const char* a = udev_device_get_sysattr_value(dev, "idVendor");
|
||||
if (a) {
|
||||
ev.vid = QString::fromUtf8(a);
|
||||
}
|
||||
}
|
||||
if (ev.pid.isEmpty() && ev.subsystem == QStringLiteral("usb")) {
|
||||
const char* a = udev_device_get_sysattr_value(dev, "idProduct");
|
||||
if (a) {
|
||||
ev.pid = QString::fromUtf8(a);
|
||||
}
|
||||
}
|
||||
if (ev.serial.isEmpty() && ev.subsystem == QStringLiteral("usb")) {
|
||||
const char* a = udev_device_get_sysattr_value(dev, "serial");
|
||||
if (a) {
|
||||
ev.serial = QString::fromUtf8(a);
|
||||
}
|
||||
}
|
||||
|
||||
if (!ev.devnode.isEmpty()) {
|
||||
ev.props.insert(QStringLiteral("DEVNAME"), ev.devnode);
|
||||
}
|
||||
if (!ev.subsystem.isEmpty()) {
|
||||
ev.props.insert(QStringLiteral("SUBSYSTEM"), ev.subsystem);
|
||||
}
|
||||
|
||||
return ev;
|
||||
}
|
||||
|
||||
} // namespace softbus::device_bus::monitor
|
||||
|
||||
#endif // __linux__
|
||||
61
src/device_bus/monitor/UdevDeviceMonitor.h
Normal file
61
src/device_bus/monitor/UdevDeviceMonitor.h
Normal file
@@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
|
||||
#include "device_bus/monitor/IDeviceMonitor.h"
|
||||
|
||||
#if defined(__linux__)
|
||||
|
||||
#include <libudev.h>
|
||||
|
||||
#include <QSocketNotifier>
|
||||
|
||||
namespace softbus::device_bus::monitor
|
||||
{
|
||||
|
||||
class UdevDeviceMonitor final : public IDeviceMonitor
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit UdevDeviceMonitor(QObject* parent = nullptr);
|
||||
~UdevDeviceMonitor() override;
|
||||
|
||||
bool start() override;
|
||||
void stop() override;
|
||||
std::vector<DeviceEvent> snapshotExisting() override;
|
||||
|
||||
private:
|
||||
void onReadable();
|
||||
bool shouldKeepEvent(const DeviceEvent& ev) const;
|
||||
DeviceEvent toEvent(struct udev_device* dev, const char* action);
|
||||
static QString getProp(struct udev_device* dev, const char* key);
|
||||
|
||||
struct udev* m_udev{nullptr};
|
||||
struct udev_monitor* m_mon{nullptr};
|
||||
int m_fd{-1};
|
||||
QSocketNotifier* m_notifier{nullptr};
|
||||
};
|
||||
|
||||
} // namespace softbus::device_bus::monitor
|
||||
|
||||
#else
|
||||
|
||||
namespace softbus::device_bus::monitor
|
||||
{
|
||||
|
||||
class UdevDeviceMonitor final : public IDeviceMonitor
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit UdevDeviceMonitor(QObject* parent = nullptr)
|
||||
: IDeviceMonitor(parent)
|
||||
{
|
||||
}
|
||||
~UdevDeviceMonitor() override = default;
|
||||
|
||||
bool start() override { return false; }
|
||||
void stop() override {}
|
||||
std::vector<DeviceEvent> snapshotExisting() override { return {}; }
|
||||
};
|
||||
|
||||
} // namespace softbus::device_bus::monitor
|
||||
|
||||
#endif
|
||||
619
src/device_bus/tree/DeviceTreeModel.cpp
Normal file
619
src/device_bus/tree/DeviceTreeModel.cpp
Normal file
@@ -0,0 +1,619 @@
|
||||
#include "device_bus/tree/DeviceTreeModel.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QMutexLocker>
|
||||
|
||||
#include "utils/logs/logging.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
void applyJsonPatchToDeviceTreeNode(DeviceTreeNode& node, const QJsonObject& patch)
|
||||
{
|
||||
for (auto it = patch.constBegin(); it != patch.constEnd(); ++it) {
|
||||
const QString k = it.key();
|
||||
if (k == QStringLiteral("params") && it.value().isObject()) {
|
||||
QJsonObject p = node.params;
|
||||
const QJsonObject patchParams = it.value().toObject();
|
||||
for (auto pit = patchParams.constBegin(); pit != patchParams.constEnd(); ++pit) {
|
||||
const QString pk = pit.key();
|
||||
if (pk == QStringLiteral("stableKey") || pk == QStringLiteral("online") || pk == QStringLiteral("lastSeenTs")) {
|
||||
continue;
|
||||
}
|
||||
p.insert(pk, pit.value());
|
||||
}
|
||||
node.params = p;
|
||||
} else if (k == QStringLiteral("name")) {
|
||||
node.name = it.value().toString();
|
||||
} else if (k == QStringLiteral("type")) {
|
||||
node.type = it.value().toString();
|
||||
} else if (k == QStringLiteral("endpoint")) {
|
||||
node.endpoint = it.value().toString();
|
||||
} else if (k == QStringLiteral("driver")) {
|
||||
node.driver = it.value().toString();
|
||||
} else if (k == QStringLiteral("capabilitiesRef")) {
|
||||
node.capabilitiesRef = it.value().toString();
|
||||
} else if (k == QStringLiteral("enabled")) {
|
||||
node.enabled = it.value().toBool(node.enabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool idAllowedForEdgeProposeNew(const QString& id, const QString& edgeId)
|
||||
{
|
||||
if (id.isEmpty() || edgeId.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
const QString rootId = QStringLiteral("edges/%1").arg(edgeId);
|
||||
const QString prefix = rootId + QLatin1Char('/');
|
||||
return (id == rootId) || id.startsWith(prefix);
|
||||
}
|
||||
|
||||
bool childrenIdsAllowedForEdge(const QVector<QString>& children, const QString& edgeId)
|
||||
{
|
||||
const QString rootId = QStringLiteral("edges/%1").arg(edgeId);
|
||||
const QString prefix = rootId + QLatin1Char('/');
|
||||
for (const QString& c : children) {
|
||||
if (c.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
if (!(c == rootId || c.startsWith(prefix))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
DeviceTreeNode deviceTreeNodeFromJsonObject(const QJsonObject& obj)
|
||||
{
|
||||
DeviceTreeNode node;
|
||||
node.id = obj.value(QStringLiteral("id")).toString();
|
||||
node.name = obj.value(QStringLiteral("name")).toString();
|
||||
node.type = obj.value(QStringLiteral("type")).toString();
|
||||
node.endpoint = obj.value(QStringLiteral("endpoint")).toString();
|
||||
node.driver = obj.value(QStringLiteral("driver")).toString();
|
||||
node.capabilitiesRef = obj.value(QStringLiteral("capabilitiesRef")).toString();
|
||||
node.params = obj.value(QStringLiteral("params")).toObject();
|
||||
node.enabled = obj.value(QStringLiteral("enabled")).toBool(true);
|
||||
const QJsonValue ch = obj.value(QStringLiteral("children"));
|
||||
if (ch.isArray()) {
|
||||
for (const QJsonValue& cv : ch.toArray()) {
|
||||
if (cv.isString()) {
|
||||
node.childrenIds.push_back(cv.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
void mergeUpsertJsonIntoNode(DeviceTreeNode& node, const QJsonObject& src)
|
||||
{
|
||||
if (src.contains(QStringLiteral("name"))) {
|
||||
node.name = src.value(QStringLiteral("name")).toString();
|
||||
}
|
||||
if (src.contains(QStringLiteral("type"))) {
|
||||
node.type = src.value(QStringLiteral("type")).toString();
|
||||
}
|
||||
if (src.contains(QStringLiteral("endpoint"))) {
|
||||
node.endpoint = src.value(QStringLiteral("endpoint")).toString();
|
||||
}
|
||||
if (src.contains(QStringLiteral("driver"))) {
|
||||
node.driver = src.value(QStringLiteral("driver")).toString();
|
||||
}
|
||||
if (src.contains(QStringLiteral("capabilitiesRef"))) {
|
||||
node.capabilitiesRef = src.value(QStringLiteral("capabilitiesRef")).toString();
|
||||
}
|
||||
if (src.contains(QStringLiteral("enabled"))) {
|
||||
node.enabled = src.value(QStringLiteral("enabled")).toBool(node.enabled);
|
||||
}
|
||||
if (src.contains(QStringLiteral("params")) && src.value(QStringLiteral("params")).isObject()) {
|
||||
QJsonObject p = node.params;
|
||||
const QJsonObject patchParams = src.value(QStringLiteral("params")).toObject();
|
||||
for (auto pit = patchParams.constBegin(); pit != patchParams.constEnd(); ++pit) {
|
||||
p.insert(pit.key(), pit.value());
|
||||
}
|
||||
node.params = p;
|
||||
}
|
||||
if (src.contains(QStringLiteral("children")) && src.value(QStringLiteral("children")).isArray()) {
|
||||
node.childrenIds.clear();
|
||||
for (const QJsonValue& cv : src.value(QStringLiteral("children")).toArray()) {
|
||||
if (cv.isString()) {
|
||||
node.childrenIds.push_back(cv.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool DeviceTreeModel::loadFromDaemonConfig(const QJsonObject& root, QString* errorMessage)
|
||||
{
|
||||
m_nodes.clear();
|
||||
m_indexById.clear();
|
||||
|
||||
if (!root.contains(QStringLiteral("device_tree"))) {
|
||||
if (errorMessage) {
|
||||
*errorMessage = QStringLiteral("daemon_config.json missing 'device_tree' field");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const QJsonValue treeVal = root.value(QStringLiteral("device_tree"));
|
||||
if (!treeVal.isArray()) {
|
||||
if (errorMessage) {
|
||||
*errorMessage = QStringLiteral("'device_tree' must be an array");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const QJsonArray arr = treeVal.toArray();
|
||||
m_nodes.reserve(arr.size());
|
||||
|
||||
for (const QJsonValue& v : arr) {
|
||||
if (!v.isObject()) {
|
||||
LOG_WARNING() << "DeviceTreeModel: skip non-object node in device_tree";
|
||||
continue;
|
||||
}
|
||||
const QJsonObject obj = v.toObject();
|
||||
|
||||
DeviceTreeNode node;
|
||||
node.id = obj.value(QStringLiteral("id")).toString();
|
||||
node.name = obj.value(QStringLiteral("name")).toString();
|
||||
node.type = obj.value(QStringLiteral("type")).toString();
|
||||
node.endpoint = obj.value(QStringLiteral("endpoint")).toString();
|
||||
node.driver = obj.value(QStringLiteral("driver")).toString();
|
||||
node.capabilitiesRef = obj.value(QStringLiteral("capabilitiesRef")).toString();
|
||||
node.params = obj.value(QStringLiteral("params")).toObject();
|
||||
node.enabled = obj.value(QStringLiteral("enabled")).toBool(true);
|
||||
|
||||
// children: array of ids
|
||||
const QJsonValue childrenVal = obj.value(QStringLiteral("children"));
|
||||
if (childrenVal.isArray()) {
|
||||
const QJsonArray childrenArr = childrenVal.toArray();
|
||||
for (const QJsonValue& c : childrenArr) {
|
||||
if (c.isString()) {
|
||||
node.childrenIds.push_back(c.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (node.id.isEmpty()) {
|
||||
LOG_WARNING() << "DeviceTreeModel: node without id, skipping";
|
||||
continue;
|
||||
}
|
||||
|
||||
const int index = m_nodes.size();
|
||||
m_nodes.push_back(node);
|
||||
m_indexById.insert(node.id, index);
|
||||
}
|
||||
|
||||
LOG_INFO() << "DeviceTreeModel loaded" << m_nodes.size() << "nodes from daemon_config";
|
||||
m_treeRevision.store(std::max(qint64(1),
|
||||
root.value(QStringLiteral("device_tree_revision")).toVariant().toLongLong()),
|
||||
std::memory_order_relaxed);
|
||||
return true;
|
||||
}
|
||||
|
||||
void DeviceTreeModel::rebuildIndex()
|
||||
{
|
||||
m_indexById.clear();
|
||||
for (int i = 0; i < m_nodes.size(); ++i) {
|
||||
const auto& n = m_nodes.at(i);
|
||||
if (!n.id.isEmpty()) {
|
||||
m_indexById.insert(n.id, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
QJsonObject deviceNodeToJsonObject(const DeviceTreeNode& n)
|
||||
{
|
||||
QJsonObject obj;
|
||||
obj.insert(QStringLiteral("id"), n.id);
|
||||
if (!n.name.isEmpty()) {
|
||||
obj.insert(QStringLiteral("name"), n.name);
|
||||
}
|
||||
if (!n.type.isEmpty()) {
|
||||
obj.insert(QStringLiteral("type"), n.type);
|
||||
}
|
||||
if (!n.endpoint.isEmpty()) {
|
||||
obj.insert(QStringLiteral("endpoint"), n.endpoint);
|
||||
}
|
||||
if (!n.driver.isEmpty()) {
|
||||
obj.insert(QStringLiteral("driver"), n.driver);
|
||||
}
|
||||
if (!n.capabilitiesRef.isEmpty()) {
|
||||
obj.insert(QStringLiteral("capabilitiesRef"), n.capabilitiesRef);
|
||||
}
|
||||
if (!n.params.isEmpty()) {
|
||||
obj.insert(QStringLiteral("params"), n.params);
|
||||
}
|
||||
obj.insert(QStringLiteral("enabled"), n.enabled);
|
||||
|
||||
if (!n.childrenIds.isEmpty()) {
|
||||
QJsonArray children;
|
||||
for (const auto& c : n.childrenIds) {
|
||||
children.append(c);
|
||||
}
|
||||
obj.insert(QStringLiteral("children"), children);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
QJsonArray DeviceTreeModel::toDeviceTreeJson() const
|
||||
{
|
||||
QJsonArray arr;
|
||||
|
||||
for (const auto& n : m_nodes) {
|
||||
arr.append(deviceNodeToJsonObject(n));
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
QJsonObject DeviceTreeModel::toDaemonConfigRoot() const
|
||||
{
|
||||
QJsonObject root;
|
||||
root.insert(QStringLiteral("device_tree"), toDeviceTreeJson());
|
||||
return root;
|
||||
}
|
||||
|
||||
const DeviceTreeNode* DeviceTreeModel::findById(const QString& id) const
|
||||
{
|
||||
const int index = findIndexById(id);
|
||||
if (index < 0 || index >= m_nodes.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
return &m_nodes.at(index);
|
||||
}
|
||||
|
||||
int DeviceTreeModel::findIndexById(const QString& id) const
|
||||
{
|
||||
const auto it = m_indexById.constFind(id);
|
||||
if (it == m_indexById.constEnd()) {
|
||||
return -1;
|
||||
}
|
||||
return it.value();
|
||||
}
|
||||
|
||||
int DeviceTreeModel::findIndexByStableKey(const QString& stableKey) const
|
||||
{
|
||||
if (stableKey.isEmpty()) {
|
||||
return -1;
|
||||
}
|
||||
for (int i = 0; i < m_nodes.size(); ++i) {
|
||||
const auto& n = m_nodes.at(i);
|
||||
if (n.params.value(QStringLiteral("stableKey")).toString() == stableKey) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int DeviceTreeModel::findIndexByEndpoint(const QString& endpoint) const
|
||||
{
|
||||
if (endpoint.isEmpty()) {
|
||||
return -1;
|
||||
}
|
||||
for (int i = 0; i < m_nodes.size(); ++i) {
|
||||
const auto& n = m_nodes.at(i);
|
||||
if (n.endpoint == endpoint) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
QString DeviceTreeModel::upsertNodeByStableKey(const QString& stableKey,
|
||||
const QString& preferredId,
|
||||
const QString& type,
|
||||
const QString& endpoint,
|
||||
const QString& name,
|
||||
const QJsonObject& extraParams,
|
||||
bool online,
|
||||
const QString& lastSeenTs)
|
||||
{
|
||||
int idx = findIndexByStableKey(stableKey);
|
||||
if (idx < 0 && !preferredId.isEmpty()) {
|
||||
idx = findIndexById(preferredId);
|
||||
}
|
||||
|
||||
if (idx < 0) {
|
||||
DeviceTreeNode node;
|
||||
node.id = preferredId;
|
||||
node.type = type;
|
||||
node.endpoint = endpoint;
|
||||
node.name = name;
|
||||
node.enabled = true;
|
||||
node.params = extraParams;
|
||||
node.params.insert(QStringLiteral("stableKey"), stableKey);
|
||||
node.params.insert(QStringLiteral("online"), online);
|
||||
if (!lastSeenTs.isEmpty()) {
|
||||
node.params.insert(QStringLiteral("lastSeenTs"), lastSeenTs);
|
||||
}
|
||||
m_nodes.push_back(node);
|
||||
rebuildIndex();
|
||||
return node.id;
|
||||
}
|
||||
|
||||
auto& node = m_nodes[idx];
|
||||
if (!type.isEmpty()) node.type = type;
|
||||
if (!endpoint.isEmpty()) node.endpoint = endpoint;
|
||||
if (!name.isEmpty()) node.name = name;
|
||||
node.enabled = true;
|
||||
|
||||
for (auto it = extraParams.begin(); it != extraParams.end(); ++it) {
|
||||
node.params.insert(it.key(), it.value());
|
||||
}
|
||||
node.params.insert(QStringLiteral("stableKey"), stableKey);
|
||||
node.params.insert(QStringLiteral("online"), online);
|
||||
if (!lastSeenTs.isEmpty()) {
|
||||
node.params.insert(QStringLiteral("lastSeenTs"), lastSeenTs);
|
||||
}
|
||||
return node.id;
|
||||
}
|
||||
|
||||
bool DeviceTreeModel::markOfflineByStableKey(const QString& stableKey, const QString& lastSeenTs)
|
||||
{
|
||||
const int idx = findIndexByStableKey(stableKey);
|
||||
if (idx < 0) {
|
||||
return false;
|
||||
}
|
||||
auto& node = m_nodes[idx];
|
||||
node.params.insert(QStringLiteral("online"), false);
|
||||
if (!lastSeenTs.isEmpty()) {
|
||||
node.params.insert(QStringLiteral("lastSeenTs"), lastSeenTs);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void DeviceTreeModel::bumpTreeRevision()
|
||||
{
|
||||
m_treeRevision.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
bool DeviceTreeModel::applyPatchToNodeById(const QString& id, const QJsonObject& patch)
|
||||
{
|
||||
const int idx = findIndexById(id);
|
||||
if (idx < 0) {
|
||||
return false;
|
||||
}
|
||||
applyJsonPatchToDeviceTreeNode(m_nodes[idx], patch);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeviceTreeModel::nodeBelongsToEdge(const DeviceTreeNode& node, const QString& edgeId)
|
||||
{
|
||||
if (edgeId.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
if (node.params.value(QStringLiteral("edgeId")).toString() == edgeId) {
|
||||
return true;
|
||||
}
|
||||
const QString prefix = QStringLiteral("edges/%1/").arg(edgeId);
|
||||
if (node.id == QStringLiteral("edges/%1").arg(edgeId) || node.id.startsWith(prefix)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonArray DeviceTreeModel::exportSubtreeForEdge(const QString& edgeId, qint64* outRevisionAtExport) const
|
||||
{
|
||||
QJsonArray arr;
|
||||
if (edgeId.isEmpty()) {
|
||||
return arr;
|
||||
}
|
||||
QMutexLocker locker(&m_writeMutex);
|
||||
if (outRevisionAtExport) {
|
||||
*outRevisionAtExport = m_treeRevision.load(std::memory_order_relaxed);
|
||||
}
|
||||
for (const auto& n : m_nodes) {
|
||||
if (nodeBelongsToEdge(n, edgeId)) {
|
||||
arr.append(deviceNodeToJsonObject(n));
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
QVector<QString> DeviceTreeModel::downlinkEdgeIdsForNode(const DeviceTreeNode& node) const
|
||||
{
|
||||
QVector<QString> out;
|
||||
auto pushUnique = [&out](const QString& s) {
|
||||
const QString t = s.trimmed();
|
||||
if (t.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (const QString& x : out) {
|
||||
if (x == t) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
out.append(t);
|
||||
};
|
||||
pushUnique(node.params.value(QStringLiteral("edgeId")).toString());
|
||||
static const QString pfx = QStringLiteral("edges/");
|
||||
if (node.id.startsWith(pfx)) {
|
||||
const QString rest = node.id.mid(pfx.size());
|
||||
const int slash = rest.indexOf(QLatin1Char('/'));
|
||||
pushUnique(slash < 0 ? rest : rest.left(slash));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
DeviceTreeModel::EdgeProposeResult DeviceTreeModel::applyEdgePropose(const QString& edgeId,
|
||||
qint64 baseRevision,
|
||||
const QJsonArray& entries)
|
||||
{
|
||||
EdgeProposeResult r;
|
||||
QMutexLocker locker(&m_writeMutex);
|
||||
r.currentRevision = m_treeRevision.load(std::memory_order_relaxed);
|
||||
if (entries.isEmpty()) {
|
||||
r.ok = false;
|
||||
r.code = QStringLiteral("INVALID_JSON");
|
||||
r.message = QStringLiteral("entries must be non-empty");
|
||||
return r;
|
||||
}
|
||||
if (baseRevision != m_treeRevision.load(std::memory_order_relaxed)) {
|
||||
r.ok = false;
|
||||
r.code = QStringLiteral("CONFLICT");
|
||||
r.message = QStringLiteral("baseTreeRevision mismatch");
|
||||
r.currentRevision = m_treeRevision.load(std::memory_order_relaxed);
|
||||
return r;
|
||||
}
|
||||
|
||||
for (const QJsonValue& ev : entries) {
|
||||
if (!ev.isObject()) {
|
||||
r.ok = false;
|
||||
r.code = QStringLiteral("INVALID_JSON");
|
||||
r.message = QStringLiteral("entries must be objects");
|
||||
r.currentRevision = m_treeRevision.load(std::memory_order_relaxed);
|
||||
return r;
|
||||
}
|
||||
const QJsonObject eo = ev.toObject();
|
||||
const QString id = eo.value(QStringLiteral("id")).toString();
|
||||
const QString op = eo.value(QStringLiteral("op")).toString();
|
||||
|
||||
if (op.isEmpty() || op == QStringLiteral("patch")) {
|
||||
const QJsonObject patch = eo.value(QStringLiteral("patch")).toObject();
|
||||
if (id.isEmpty() || patch.isEmpty()) {
|
||||
r.ok = false;
|
||||
r.code = QStringLiteral("INVALID_JSON");
|
||||
r.message = QStringLiteral("each patch entry needs id and patch");
|
||||
r.currentRevision = m_treeRevision.load(std::memory_order_relaxed);
|
||||
return r;
|
||||
}
|
||||
const DeviceTreeNode* node = findById(id);
|
||||
if (!node) {
|
||||
r.ok = false;
|
||||
r.code = QStringLiteral("NODE_NOT_FOUND");
|
||||
r.message = id;
|
||||
r.currentRevision = m_treeRevision.load(std::memory_order_relaxed);
|
||||
return r;
|
||||
}
|
||||
if (!nodeBelongsToEdge(*node, edgeId)) {
|
||||
r.ok = false;
|
||||
r.code = QStringLiteral("FORBIDDEN_SCOPE");
|
||||
r.message = id;
|
||||
r.currentRevision = m_treeRevision.load(std::memory_order_relaxed);
|
||||
return r;
|
||||
}
|
||||
if (!applyPatchToNodeById(id, patch)) {
|
||||
r.ok = false;
|
||||
r.code = QStringLiteral("NODE_NOT_FOUND");
|
||||
r.message = id;
|
||||
r.currentRevision = m_treeRevision.load(std::memory_order_relaxed);
|
||||
return r;
|
||||
}
|
||||
} else if (op == QStringLiteral("upsert")) {
|
||||
const QJsonObject nodeJo = eo.value(QStringLiteral("node")).toObject();
|
||||
if (id.isEmpty() || nodeJo.isEmpty()) {
|
||||
r.ok = false;
|
||||
r.code = QStringLiteral("INVALID_JSON");
|
||||
r.message = QStringLiteral("upsert entry needs id and non-empty node");
|
||||
r.currentRevision = m_treeRevision.load(std::memory_order_relaxed);
|
||||
return r;
|
||||
}
|
||||
if (nodeJo.contains(QStringLiteral("id"))) {
|
||||
const QString nid = nodeJo.value(QStringLiteral("id")).toString();
|
||||
if (!nid.isEmpty() && nid != id) {
|
||||
r.ok = false;
|
||||
r.code = QStringLiteral("INVALID_JSON");
|
||||
r.message = QStringLiteral("node.id must match entry id");
|
||||
r.currentRevision = m_treeRevision.load(std::memory_order_relaxed);
|
||||
return r;
|
||||
}
|
||||
}
|
||||
const int idx = findIndexById(id);
|
||||
if (idx < 0) {
|
||||
if (!idAllowedForEdgeProposeNew(id, edgeId)) {
|
||||
r.ok = false;
|
||||
r.code = QStringLiteral("FORBIDDEN_SCOPE");
|
||||
r.message = id;
|
||||
r.currentRevision = m_treeRevision.load(std::memory_order_relaxed);
|
||||
return r;
|
||||
}
|
||||
DeviceTreeNode n = deviceTreeNodeFromJsonObject(nodeJo);
|
||||
if (n.id.isEmpty()) {
|
||||
n.id = id;
|
||||
}
|
||||
if (n.id != id) {
|
||||
r.ok = false;
|
||||
r.code = QStringLiteral("INVALID_JSON");
|
||||
r.message = QStringLiteral("upsert node id mismatch");
|
||||
r.currentRevision = m_treeRevision.load(std::memory_order_relaxed);
|
||||
return r;
|
||||
}
|
||||
if (!n.params.contains(QStringLiteral("edgeId"))) {
|
||||
QJsonObject p = n.params;
|
||||
p.insert(QStringLiteral("edgeId"), edgeId);
|
||||
n.params = p;
|
||||
}
|
||||
if (!nodeBelongsToEdge(n, edgeId)) {
|
||||
r.ok = false;
|
||||
r.code = QStringLiteral("FORBIDDEN_SCOPE");
|
||||
r.message = id;
|
||||
r.currentRevision = m_treeRevision.load(std::memory_order_relaxed);
|
||||
return r;
|
||||
}
|
||||
if (!childrenIdsAllowedForEdge(n.childrenIds, edgeId)) {
|
||||
r.ok = false;
|
||||
r.code = QStringLiteral("FORBIDDEN_SCOPE");
|
||||
r.message = QStringLiteral("children outside edge scope");
|
||||
r.currentRevision = m_treeRevision.load(std::memory_order_relaxed);
|
||||
return r;
|
||||
}
|
||||
m_nodes.push_back(n);
|
||||
} else {
|
||||
DeviceTreeNode& existing = m_nodes[idx];
|
||||
if (!nodeBelongsToEdge(existing, edgeId)) {
|
||||
r.ok = false;
|
||||
r.code = QStringLiteral("FORBIDDEN_SCOPE");
|
||||
r.message = id;
|
||||
r.currentRevision = m_treeRevision.load(std::memory_order_relaxed);
|
||||
return r;
|
||||
}
|
||||
mergeUpsertJsonIntoNode(existing, nodeJo);
|
||||
existing.id = id;
|
||||
if (!existing.params.contains(QStringLiteral("edgeId"))) {
|
||||
existing.params.insert(QStringLiteral("edgeId"), edgeId);
|
||||
}
|
||||
if (!nodeBelongsToEdge(existing, edgeId)) {
|
||||
r.ok = false;
|
||||
r.code = QStringLiteral("FORBIDDEN_SCOPE");
|
||||
r.message = id;
|
||||
r.currentRevision = m_treeRevision.load(std::memory_order_relaxed);
|
||||
return r;
|
||||
}
|
||||
if (!childrenIdsAllowedForEdge(existing.childrenIds, edgeId)) {
|
||||
r.ok = false;
|
||||
r.code = QStringLiteral("FORBIDDEN_SCOPE");
|
||||
r.message = QStringLiteral("children outside edge scope");
|
||||
r.currentRevision = m_treeRevision.load(std::memory_order_relaxed);
|
||||
return r;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
r.ok = false;
|
||||
r.code = QStringLiteral("INVALID_JSON");
|
||||
r.message = QStringLiteral("unknown op");
|
||||
r.currentRevision = m_treeRevision.load(std::memory_order_relaxed);
|
||||
return r;
|
||||
}
|
||||
rebuildIndex();
|
||||
}
|
||||
|
||||
rebuildIndex();
|
||||
bumpTreeRevision();
|
||||
r.ok = true;
|
||||
r.code = QStringLiteral("OK");
|
||||
r.newRevision = m_treeRevision.load(std::memory_order_relaxed);
|
||||
r.currentRevision = r.newRevision;
|
||||
return r;
|
||||
}
|
||||
|
||||
89
src/device_bus/tree/DeviceTreeModel.h
Normal file
89
src/device_bus/tree/DeviceTreeModel.h
Normal file
@@ -0,0 +1,89 @@
|
||||
#pragma once
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QVector>
|
||||
#include <QString>
|
||||
#include <QHash>
|
||||
#include <atomic>
|
||||
#include <QMutex>
|
||||
|
||||
// 轻量级设备树模型:从 daemon_config.json 的 device_tree 字段加载
|
||||
|
||||
struct DeviceTreeNode
|
||||
{
|
||||
QString id;
|
||||
QString name;
|
||||
QString type;
|
||||
QString endpoint;
|
||||
QString driver;
|
||||
QString capabilitiesRef;
|
||||
QJsonObject params;
|
||||
QVector<QString> childrenIds;
|
||||
bool enabled{true};
|
||||
};
|
||||
|
||||
class DeviceTreeModel
|
||||
{
|
||||
public:
|
||||
// 从给定的 JSON 根对象(包含 device_tree 字段)加载
|
||||
bool loadFromDaemonConfig(const QJsonObject& root, QString* errorMessage = nullptr);
|
||||
|
||||
const QVector<DeviceTreeNode>& nodes() const { return m_nodes; }
|
||||
QVector<DeviceTreeNode>& nodesMutable() { return m_nodes; }
|
||||
|
||||
const DeviceTreeNode* findById(const QString& id) const;
|
||||
int findIndexById(const QString& id) const;
|
||||
int findIndexByEndpoint(const QString& endpoint) const;
|
||||
int findIndexByStableKey(const QString& stableKey) const;
|
||||
|
||||
// Monitor/register path: upsert or mark online/offline
|
||||
QString upsertNodeByStableKey(const QString& stableKey,
|
||||
const QString& preferredId,
|
||||
const QString& type,
|
||||
const QString& endpoint,
|
||||
const QString& name,
|
||||
const QJsonObject& extraParams,
|
||||
bool online,
|
||||
const QString& lastSeenTs);
|
||||
bool markOfflineByStableKey(const QString& stableKey, const QString& lastSeenTs);
|
||||
|
||||
// 序列化为 daemon_config.json 所需格式
|
||||
QJsonArray toDeviceTreeJson() const;
|
||||
QJsonObject toDaemonConfigRoot() const;
|
||||
|
||||
// 当外部直接修改 nodesMutable() 后,需要重建索引
|
||||
void rebuildIndex();
|
||||
|
||||
// 与 D-Bus patch / 边缘 CTRL_TREE_PROPOSE 共用的写锁与 revision(见 docs/EDGE_UPLINK_PROTOCOL_v1.md)
|
||||
QMutex& writeMutex() { return m_writeMutex; }
|
||||
qint64 treeRevision() const { return m_treeRevision.load(std::memory_order_relaxed); }
|
||||
void bumpTreeRevision();
|
||||
bool applyPatchToNodeById(const QString& id, const QJsonObject& patch);
|
||||
|
||||
struct EdgeProposeResult
|
||||
{
|
||||
bool ok{false};
|
||||
QString code; // OK, CONFLICT, FORBIDDEN_SCOPE, NODE_NOT_FOUND, INVALID_JSON
|
||||
QString message;
|
||||
qint64 newRevision{0};
|
||||
qint64 currentRevision{0};
|
||||
};
|
||||
EdgeProposeResult applyEdgePropose(const QString& edgeId, qint64 baseRevision, const QJsonArray& entries);
|
||||
|
||||
/** 导出属于该 edge 的节点子集(与 applyEdgePropose 作用域规则一致),用于下行 TREE_FULL。
|
||||
* 若传入 outRevisionAtExport,则写入导出时刻的 treeRevision(与 nodes 同一快照)。 */
|
||||
QJsonArray exportSubtreeForEdge(const QString& edgeId, qint64* outRevisionAtExport = nullptr) const;
|
||||
|
||||
/** 节点若处于边缘作用域(params.edgeId 或 id 前缀 edges/<edgeId>/...),返回应尝试下行 TREE_FULL 的 edgeId(去重)。 */
|
||||
QVector<QString> downlinkEdgeIdsForNode(const DeviceTreeNode& node) const;
|
||||
|
||||
private:
|
||||
static bool nodeBelongsToEdge(const DeviceTreeNode& node, const QString& edgeId);
|
||||
|
||||
QVector<DeviceTreeNode> m_nodes;
|
||||
QHash<QString, int> m_indexById;
|
||||
mutable QMutex m_writeMutex;
|
||||
std::atomic<qint64> m_treeRevision{1};
|
||||
};
|
||||
|
||||
66
src/devices/DeviceTypes.cpp
Normal file
66
src/devices/DeviceTypes.cpp
Normal file
@@ -0,0 +1,66 @@
|
||||
#include "devices/DeviceTypes.h"
|
||||
|
||||
namespace softbus::devices {
|
||||
|
||||
DeviceKind kindFromType(const QString &typeStr) {
|
||||
// 使用静态 QHash,仅在首次调用时初始化一次,后续调用直接极速查表
|
||||
static const QHash<QString, DeviceKind> typeMap = {
|
||||
{QStringLiteral("serial"), DeviceKind::Serial},
|
||||
{QStringLiteral("can"), DeviceKind::Can},
|
||||
{QStringLiteral("ethernet"), DeviceKind::Network},
|
||||
{QStringLiteral("network"), DeviceKind::Network},
|
||||
{QStringLiteral("virtual"), DeviceKind::Virtual},
|
||||
{QStringLiteral("distributed"), DeviceKind::Distributed}};
|
||||
|
||||
// value() 方法在找不到键时,会返回传入的第二个默认参数
|
||||
return typeMap.value(typeStr.trimmed().toLower(), DeviceKind::Unknown);
|
||||
}
|
||||
|
||||
DeviceKind inferDeviceKindFromEndpoint(const QString &endpoint) {
|
||||
const QString ep = endpoint.trimmed();
|
||||
// 使用结构体数组来存储设备类型和前缀的映射
|
||||
struct Rule {
|
||||
const QString prefix;
|
||||
DeviceKind kind;
|
||||
};
|
||||
|
||||
static const Rule rules[] = {
|
||||
{QStringLiteral("/dev/tty"), DeviceKind::Serial},
|
||||
{QStringLiteral("can"), DeviceKind::Can},
|
||||
{QStringLiteral("tcp://"), DeviceKind::Tcp},
|
||||
{QStringLiteral("udp://"), DeviceKind::Udp}
|
||||
// 未来在这里添加其他设备类型
|
||||
};
|
||||
|
||||
// 遍历规则表进行前缀匹配
|
||||
for (const auto &rule : rules) {
|
||||
if (ep.startsWith(rule.prefix)) {
|
||||
return rule.kind;
|
||||
}
|
||||
}
|
||||
|
||||
return DeviceKind::Unknown;
|
||||
}
|
||||
|
||||
QString driverKeyFor(DeviceKind kind) {
|
||||
switch (kind) {
|
||||
case DeviceKind::Serial:
|
||||
return QStringLiteral("serial_qt");
|
||||
case DeviceKind::Can:
|
||||
return QStringLiteral("can_socketcan");
|
||||
case DeviceKind::Tcp:
|
||||
return QStringLiteral("tcp_qt");
|
||||
case DeviceKind::Udp:
|
||||
return QStringLiteral("udp_qt");
|
||||
case DeviceKind::Network:
|
||||
return QStringLiteral("network_qt");
|
||||
case DeviceKind::Virtual:
|
||||
return QStringLiteral("virtual_device");
|
||||
case DeviceKind::Distributed:
|
||||
return QStringLiteral("distributed_node");
|
||||
default:
|
||||
return QStringLiteral("unknown");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace softbus::devices
|
||||
107
src/devices/DeviceTypes.h
Normal file
107
src/devices/DeviceTypes.h
Normal file
@@ -0,0 +1,107 @@
|
||||
#pragma once
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
|
||||
namespace softbus::devices
|
||||
{
|
||||
|
||||
|
||||
|
||||
|
||||
// 具体设备种类(用于选择 driver / 解析 endpoint)
|
||||
enum class DeviceKind {
|
||||
Unknown,
|
||||
Serial,
|
||||
Can,
|
||||
Tcp,
|
||||
Udp,
|
||||
Network, // 泛指 network(无法区分 tcp/udp 时)
|
||||
Virtual,
|
||||
Distributed,
|
||||
};
|
||||
|
||||
inline QString deviceKindToString(DeviceKind kind)
|
||||
{
|
||||
switch (kind) {
|
||||
case DeviceKind::Serial:
|
||||
return QStringLiteral("serial");
|
||||
case DeviceKind::Can:
|
||||
return QStringLiteral("can");
|
||||
case DeviceKind::Tcp:
|
||||
return QStringLiteral("tcp");
|
||||
case DeviceKind::Udp:
|
||||
return QStringLiteral("udp");
|
||||
case DeviceKind::Network:
|
||||
return QStringLiteral("network");
|
||||
case DeviceKind::Virtual:
|
||||
return QStringLiteral("virtual");
|
||||
case DeviceKind::Distributed:
|
||||
return QStringLiteral("distributed");
|
||||
case DeviceKind::Unknown:
|
||||
default:
|
||||
return QStringLiteral("unknown");
|
||||
}
|
||||
}
|
||||
|
||||
struct DeviceInfo
|
||||
{
|
||||
int id{0};
|
||||
QString endpoint;
|
||||
QString name;
|
||||
QString type; // "serial" / "can" / "network" / ...
|
||||
DeviceKind kind{DeviceKind::Unknown};
|
||||
int address{0};
|
||||
QString protocol;
|
||||
QString protocolDetail;
|
||||
QJsonObject properties;
|
||||
QString status{QStringLiteral("online")};
|
||||
bool isActive{true};
|
||||
QDateTime lastSeen;
|
||||
|
||||
QJsonObject toJsonObject() const
|
||||
{
|
||||
QJsonObject obj;
|
||||
obj[QStringLiteral("id")] = id;
|
||||
obj[QStringLiteral("endpoint")] = endpoint;
|
||||
obj[QStringLiteral("name")] = name;
|
||||
obj[QStringLiteral("type")] = type;
|
||||
obj[QStringLiteral("kind")] = static_cast<int>(kind);
|
||||
obj[QStringLiteral("kindName")] = deviceKindToString(kind);
|
||||
obj[QStringLiteral("address")] = address;
|
||||
obj[QStringLiteral("protocol")] = protocol;
|
||||
obj[QStringLiteral("protocol_detail")] = protocolDetail;
|
||||
obj[QStringLiteral("properties")] = properties;
|
||||
obj[QStringLiteral("status")] = status;
|
||||
obj[QStringLiteral("isActive")] = isActive;
|
||||
if (lastSeen.isValid()) {
|
||||
obj[QStringLiteral("lastSeen")] = lastSeen.toString(Qt::ISODate);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
QString toJson() const
|
||||
{
|
||||
const QJsonDocument doc(toJsonObject());
|
||||
return QString::fromUtf8(doc.toJson(QJsonDocument::Compact));
|
||||
}
|
||||
};
|
||||
|
||||
struct BusMessage
|
||||
{
|
||||
QString id;
|
||||
QString source;
|
||||
QString destination;
|
||||
QJsonObject payload;
|
||||
qint64 timestamp{0};
|
||||
};
|
||||
|
||||
// 纯 helper:不做 IO,仅做字符串/endpoint 到 DeviceKind/driverKey 的映射
|
||||
DeviceKind kindFromType(const QString& typeStr);
|
||||
DeviceKind inferDeviceKindFromEndpoint(const QString& endpoint);
|
||||
QString driverKeyFor(DeviceKind kind);
|
||||
|
||||
} // namespace softbus::devices
|
||||
|
||||
17
src/devices/base/IDevice.h
Normal file
17
src/devices/base/IDevice.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include "devices/DeviceTypes.h"
|
||||
|
||||
namespace softbus::devices
|
||||
{
|
||||
|
||||
class IDevice
|
||||
{
|
||||
public:
|
||||
virtual ~IDevice() = default;
|
||||
|
||||
virtual softbus::devices::DeviceInfo deviceInfo() const = 0;
|
||||
};
|
||||
|
||||
} // namespace softbus::devices
|
||||
|
||||
19
src/devices/base/IPhysicalDevice.h
Normal file
19
src/devices/base/IPhysicalDevice.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "devices/DeviceTypes.h"
|
||||
#include "devices/base/IDevice.h"
|
||||
|
||||
namespace softbus::devices::base
|
||||
{
|
||||
|
||||
class IPhysicalDevice : public IDevice
|
||||
{
|
||||
public:
|
||||
~IPhysicalDevice() override = default;
|
||||
|
||||
virtual bool start() = 0;
|
||||
virtual void stop() = 0;
|
||||
};
|
||||
|
||||
} // namespace softbus::devices::base
|
||||
|
||||
147
src/devices/physical/CanDevice.cpp
Normal file
147
src/devices/physical/CanDevice.cpp
Normal file
@@ -0,0 +1,147 @@
|
||||
#include "devices/physical/CanDevice.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include <QCanBusDevice>
|
||||
#include <QCanBusFrame>
|
||||
#include <QDateTime>
|
||||
|
||||
#include "core/memory/MemoryPool.h"
|
||||
#include "core/models/MessageRoutingUtils.h"
|
||||
#include "core/models/RawBusMessage.h"
|
||||
#include "message_bus/ingress/IIngressPort.h"
|
||||
#include "utils/logs/logging.h"
|
||||
|
||||
namespace softbus::devices::physical
|
||||
{
|
||||
|
||||
static QString socketCanInterfaceName(const QString& endpoint)
|
||||
{
|
||||
QString ep = endpoint.trimmed();
|
||||
if (ep.startsWith(QStringLiteral("can:"), Qt::CaseInsensitive)) {
|
||||
return ep.mid(4);
|
||||
}
|
||||
return ep;
|
||||
}
|
||||
|
||||
CanDevice::CanDevice(std::unique_ptr<QCanBusDevice> bus, const Config& config, const softbus::devices::DeviceInfo& info)
|
||||
: m_bus(std::move(bus))
|
||||
, m_config(config)
|
||||
, m_info(info)
|
||||
{
|
||||
}
|
||||
|
||||
CanDevice::~CanDevice()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
bool CanDevice::start()
|
||||
{
|
||||
if (!m_bus) {
|
||||
LOG_ERROR() << "CanDevice: null bus";
|
||||
return false;
|
||||
}
|
||||
QObject::connect(m_bus.get(), &QCanBusDevice::framesReceived, m_bus.get(), [this]() { onFramesAvailable(); });
|
||||
QObject::connect(m_bus.get(), &QCanBusDevice::errorOccurred, m_bus.get(), [this](QCanBusDevice::CanBusError) {
|
||||
onError();
|
||||
});
|
||||
|
||||
if (m_config.bitrate > 0) {
|
||||
m_bus->setConfigurationParameter(QCanBusDevice::BitRateKey, m_config.bitrate);
|
||||
}
|
||||
LOG_INFO() << "CanDevice: connectDevice" << m_config.endpoint << "iface" << socketCanInterfaceName(m_config.endpoint);
|
||||
return m_bus->connectDevice();
|
||||
}
|
||||
|
||||
void CanDevice::stop()
|
||||
{
|
||||
if (!m_bus) {
|
||||
return;
|
||||
}
|
||||
QObject::disconnect(m_bus.get(), nullptr, m_bus.get(), nullptr);
|
||||
if (m_bus->state() == QCanBusDevice::ConnectedState) {
|
||||
m_bus->disconnectDevice();
|
||||
}
|
||||
m_bus.reset();
|
||||
}
|
||||
|
||||
softbus::devices::DeviceInfo CanDevice::deviceInfo() const
|
||||
{
|
||||
return m_info;
|
||||
}
|
||||
|
||||
QString CanDevice::endpoint() const
|
||||
{
|
||||
return m_config.endpoint;
|
||||
}
|
||||
|
||||
softbus::devices::DeviceKind CanDevice::kind() const
|
||||
{
|
||||
return softbus::devices::DeviceKind::Can;
|
||||
}
|
||||
|
||||
void CanDevice::bindIngress(softbus::message_bus::ingress::IIngressPort* ingress,
|
||||
std::shared_ptr<softbus::core::memory::MemoryPool> pool)
|
||||
{
|
||||
m_ingress = ingress;
|
||||
m_pool = std::move(pool);
|
||||
}
|
||||
|
||||
void CanDevice::onFramesAvailable()
|
||||
{
|
||||
if (!m_ingress || !m_pool || !m_bus) {
|
||||
return;
|
||||
}
|
||||
while (m_bus->framesAvailable()) {
|
||||
const QCanBusFrame frame = m_bus->readFrame();
|
||||
if (frame.frameType() != QCanBusFrame::DataFrame && frame.frameType() != QCanBusFrame::RemoteRequestFrame) {
|
||||
continue;
|
||||
}
|
||||
const QByteArray busPayload = frame.payload();
|
||||
auto block = m_pool->allocate(static_cast<std::size_t>(busPayload.size()));
|
||||
if (!block || !block->data || block->capacity < static_cast<std::size_t>(busPayload.size())) {
|
||||
m_ingress->reportError(m_config.endpoint, QStringLiteral("memory pool exhausted"));
|
||||
return;
|
||||
}
|
||||
if (!busPayload.isEmpty()) {
|
||||
std::memcpy(block->data, busPayload.constData(), static_cast<std::size_t>(busPayload.size()));
|
||||
}
|
||||
block->size = static_cast<std::size_t>(busPayload.size());
|
||||
|
||||
softbus::core::models::RawBusMessage ctx;
|
||||
ctx.direction = softbus::core::models::BusDirection::Upstream;
|
||||
ctx.endpointHash = softbus::core::models::makeEndpointHash(m_config.endpoint);
|
||||
ctx.protocol = softbus::core::models::protocolTypeFromHint(m_info.protocol);
|
||||
if (ctx.protocol == softbus::core::models::ProtocolType::UNKNOWN) {
|
||||
ctx.protocol = softbus::core::models::ProtocolType::CAN_RAW;
|
||||
}
|
||||
ctx.header.can.cobId = static_cast<std::uint32_t>(frame.frameId() & 0x1FFFFFFFU);
|
||||
ctx.header.can.isExtended = frame.hasExtendedFrameFormat();
|
||||
ctx.header.can.isRtr = (frame.frameType() == QCanBusFrame::RemoteRequestFrame);
|
||||
ctx.logicalAddress = ctx.header.can.cobId & 0xFFU;
|
||||
ctx.routingKey = softbus::core::models::makeRoutingKey(ctx.endpointHash, m_info.id);
|
||||
ctx.timestampMs = QDateTime::currentMSecsSinceEpoch();
|
||||
ctx.deviceId = m_info.id;
|
||||
ctx.payload.block = std::move(block);
|
||||
ctx.payload.length = static_cast<std::size_t>(busPayload.size());
|
||||
ctx.traceId = QStringLiteral("can:%1:%2:%3")
|
||||
.arg(m_config.endpoint)
|
||||
.arg(ctx.header.can.cobId)
|
||||
.arg(ctx.timestampMs);
|
||||
|
||||
if (!m_ingress->push(std::move(ctx))) {
|
||||
m_ingress->reportError(m_config.endpoint, QStringLiteral("ingress queue full"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CanDevice::onError()
|
||||
{
|
||||
if (!m_ingress || !m_bus) {
|
||||
return;
|
||||
}
|
||||
m_ingress->reportError(m_config.endpoint, m_bus->errorString());
|
||||
}
|
||||
|
||||
} // namespace softbus::devices::physical
|
||||
53
src/devices/physical/CanDevice.h
Normal file
53
src/devices/physical/CanDevice.h
Normal file
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "devices/DeviceTypes.h"
|
||||
#include "devices/base/IPhysicalDevice.h"
|
||||
|
||||
namespace softbus::message_bus::ingress { class IIngressPort; }
|
||||
namespace softbus::core::memory { class MemoryPool; }
|
||||
|
||||
class QCanBusDevice;
|
||||
|
||||
namespace softbus::devices::physical
|
||||
{
|
||||
|
||||
/** SocketCAN 上行:读帧组装 `RawBusMessage`(`protocol: CAN_RAW`,`header.can` 填 CAN id)。 */
|
||||
class CanDevice : public base::IPhysicalDevice
|
||||
{
|
||||
public:
|
||||
struct Config
|
||||
{
|
||||
QString endpoint; // 约定 `can:<interface>`,如 `can:vcan0`
|
||||
int bitrate{500000};
|
||||
};
|
||||
|
||||
CanDevice(std::unique_ptr<QCanBusDevice> bus, const Config& config, const softbus::devices::DeviceInfo& info);
|
||||
~CanDevice() override;
|
||||
|
||||
bool start() override;
|
||||
void stop() override;
|
||||
|
||||
softbus::devices::DeviceInfo deviceInfo() const override;
|
||||
|
||||
QString endpoint() const;
|
||||
softbus::devices::DeviceKind kind() const;
|
||||
void bindIngress(softbus::message_bus::ingress::IIngressPort* ingress,
|
||||
std::shared_ptr<softbus::core::memory::MemoryPool> pool);
|
||||
|
||||
private:
|
||||
void onFramesAvailable();
|
||||
void onError();
|
||||
|
||||
private:
|
||||
std::unique_ptr<QCanBusDevice> m_bus;
|
||||
Config m_config;
|
||||
softbus::devices::DeviceInfo m_info;
|
||||
softbus::message_bus::ingress::IIngressPort* m_ingress{nullptr};
|
||||
std::shared_ptr<softbus::core::memory::MemoryPool> m_pool;
|
||||
};
|
||||
|
||||
} // namespace softbus::devices::physical
|
||||
145
src/devices/physical/SerialDevice.cpp
Normal file
145
src/devices/physical/SerialDevice.cpp
Normal file
@@ -0,0 +1,145 @@
|
||||
#include "devices/physical/SerialDevice.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include <QDateTime>
|
||||
|
||||
#include "core/memory/MemoryPool.h"
|
||||
#include "core/models/MessageRoutingUtils.h"
|
||||
#include "core/models/RawBusMessage.h"
|
||||
#include "hardware/drivers/serial/SerialQtDriver.h"
|
||||
#include "message_bus/ingress/IIngressPort.h"
|
||||
#include "utils/logs/logging.h"
|
||||
|
||||
using softbus::hardware::drivers::serial::SerialQtDriver;
|
||||
|
||||
namespace softbus::devices::physical
|
||||
{
|
||||
|
||||
SerialDevice::SerialDevice(std::unique_ptr<SerialQtDriver> driver,
|
||||
const Config& config,
|
||||
const softbus::devices::DeviceInfo& info)
|
||||
: m_driver(std::move(driver))
|
||||
, m_config(config)
|
||||
, m_info(info)
|
||||
{
|
||||
}
|
||||
|
||||
SerialDevice::~SerialDevice()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
bool SerialDevice::start()
|
||||
{
|
||||
if (!m_driver) {
|
||||
LOG_ERROR() << "SerialDevice: start called with null driver";
|
||||
return false;
|
||||
}
|
||||
|
||||
m_driver->SetReadCallback([this](const std::vector<std::uint8_t>& data) { this->onRead(data); });
|
||||
m_driver->SetErrorCallback([this](const std::string& error) { this->onError(error); });
|
||||
|
||||
LOG_INFO() << "SerialDevice: opening endpoint" << m_config.endpoint;
|
||||
return m_driver->Open(m_config.endpoint.toStdString());
|
||||
}
|
||||
|
||||
void SerialDevice::stop()
|
||||
{
|
||||
if (!m_driver) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_INFO() << "SerialDevice: closing endpoint" << m_config.endpoint;
|
||||
m_driver->Close();
|
||||
}
|
||||
|
||||
softbus::devices::DeviceInfo SerialDevice::deviceInfo() const
|
||||
{
|
||||
return m_info;
|
||||
}
|
||||
|
||||
QString SerialDevice::endpoint() const
|
||||
{
|
||||
return m_config.endpoint;
|
||||
}
|
||||
|
||||
softbus::devices::DeviceKind SerialDevice::kind() const
|
||||
{
|
||||
return softbus::devices::DeviceKind::Serial;
|
||||
}
|
||||
|
||||
void SerialDevice::bindIngress(softbus::message_bus::ingress::IIngressPort* ingress,
|
||||
std::shared_ptr<softbus::core::memory::MemoryPool> pool)
|
||||
{
|
||||
m_ingress = ingress;
|
||||
m_pool = std::move(pool);
|
||||
}
|
||||
|
||||
// 串口写入数据回调 不定长数据
|
||||
bool SerialDevice::writePayload(const std::vector<std::uint8_t>& data)
|
||||
{
|
||||
if (!m_driver) {
|
||||
return false;
|
||||
}
|
||||
return m_driver->Write(data);
|
||||
}
|
||||
// 串口写入数据回调 定长数据
|
||||
bool SerialDevice::writePayload(const std::uint8_t* data, std::size_t size)
|
||||
{
|
||||
if (!m_driver) {
|
||||
return false;
|
||||
}
|
||||
return m_driver->Write(data, size);
|
||||
}
|
||||
|
||||
// 串口读取数据回调
|
||||
|
||||
void SerialDevice::onRead(const std::vector<std::uint8_t>& data)
|
||||
{
|
||||
if (!m_ingress || !m_pool || data.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto block = m_pool->allocate(data.size());
|
||||
if (!block || !block->data || block->capacity < data.size()) {
|
||||
m_ingress->reportError(m_config.endpoint, QStringLiteral("memory pool exhausted"));
|
||||
return;
|
||||
}
|
||||
|
||||
std::copy(data.begin(), data.end(), block->data);
|
||||
block->size = data.size();
|
||||
// @test code
|
||||
LOG_INFO() << "SerialDevice: onRead data size" << data.size() << "endpoint" << m_config.endpoint;
|
||||
// 将数据推送到 ingress 队列 包含端口名、协议、数据、时间戳、traceId
|
||||
softbus::core::models::RawBusMessage ctx;
|
||||
ctx.direction = softbus::core::models::BusDirection::Upstream;
|
||||
ctx.endpointHash = softbus::core::models::makeEndpointHash(m_config.endpoint);
|
||||
ctx.protocol = softbus::core::models::protocolTypeFromHint(m_info.protocol);
|
||||
ctx.logicalAddress = 0;
|
||||
ctx.routingKey = softbus::core::models::makeRoutingKey(ctx.endpointHash, m_info.id);
|
||||
ctx.timestampMs = QDateTime::currentMSecsSinceEpoch();
|
||||
ctx.deviceId = m_info.id;
|
||||
ctx.payload.block = std::move(block);
|
||||
ctx.payload.length = data.size();
|
||||
ctx.traceId = QStringLiteral("serial:%1:%2")
|
||||
.arg(m_config.endpoint)
|
||||
.arg(ctx.timestampMs);
|
||||
|
||||
if (!m_ingress->push(std::move(ctx))) {
|
||||
m_ingress->reportError(m_config.endpoint, QStringLiteral("ingress queue full"));
|
||||
}
|
||||
}
|
||||
|
||||
void SerialDevice::onError(const std::string& error)
|
||||
{
|
||||
if (!m_ingress) {
|
||||
return;
|
||||
}
|
||||
m_ingress->reportError(m_config.endpoint, QString::fromStdString(error));
|
||||
}
|
||||
|
||||
} // namespace softbus::devices::physical
|
||||
|
||||
61
src/devices/physical/SerialDevice.h
Normal file
61
src/devices/physical/SerialDevice.h
Normal file
@@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "devices/DeviceTypes.h"
|
||||
#include "devices/base/IPhysicalDevice.h"
|
||||
namespace softbus::hardware::drivers::serial { class SerialQtDriver; }
|
||||
namespace softbus::message_bus::ingress { class IIngressPort; }
|
||||
namespace softbus::core::memory { class MemoryPool; }
|
||||
|
||||
namespace softbus::devices::physical
|
||||
{
|
||||
|
||||
class SerialDevice : public base::IPhysicalDevice
|
||||
{
|
||||
public:
|
||||
struct Config
|
||||
{
|
||||
QString endpoint;
|
||||
int baudRate;
|
||||
int dataBits;
|
||||
QString parity;
|
||||
int stopBits;
|
||||
QString flowControl;
|
||||
};
|
||||
|
||||
SerialDevice(std::unique_ptr<softbus::hardware::drivers::serial::SerialQtDriver> driver,
|
||||
const Config& config,
|
||||
const softbus::devices::DeviceInfo& info);
|
||||
|
||||
~SerialDevice() override;
|
||||
|
||||
bool start() override;
|
||||
void stop() override;
|
||||
|
||||
softbus::devices::DeviceInfo deviceInfo() const override;
|
||||
|
||||
QString endpoint() const;
|
||||
softbus::devices::DeviceKind kind() const;
|
||||
void bindIngress(softbus::message_bus::ingress::IIngressPort* ingress,
|
||||
std::shared_ptr<softbus::core::memory::MemoryPool> pool);
|
||||
bool writePayload(const std::vector<std::uint8_t>& data);
|
||||
bool writePayload(const std::uint8_t* data, std::size_t size);
|
||||
|
||||
private:
|
||||
void onRead(const std::vector<std::uint8_t>& data);
|
||||
void onError(const std::string& error);
|
||||
|
||||
private:
|
||||
std::unique_ptr<softbus::hardware::drivers::serial::SerialQtDriver> m_driver;
|
||||
Config m_config;
|
||||
softbus::devices::DeviceInfo m_info;
|
||||
softbus::message_bus::ingress::IIngressPort* m_ingress{nullptr};
|
||||
std::shared_ptr<softbus::core::memory::MemoryPool> m_pool;
|
||||
};
|
||||
|
||||
} // namespace softbus::devices::physical
|
||||
|
||||
20
src/fake_driver.cpp
Normal file
20
src/fake_driver.cpp
Normal file
@@ -0,0 +1,20 @@
|
||||
#include "fake_driver.hpp"
|
||||
|
||||
FakeDriver::FakeDriver(QObject* parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
connect(&m_timer, &QTimer::timeout, this, &FakeDriver::onTimeout);
|
||||
}
|
||||
|
||||
void FakeDriver::start(int intervalMs, std::function<void()> onTick)
|
||||
{
|
||||
m_onTick = std::move(onTick);
|
||||
m_timer.start(intervalMs);
|
||||
}
|
||||
|
||||
void FakeDriver::onTimeout()
|
||||
{
|
||||
if (m_onTick) {
|
||||
m_onTick();
|
||||
}
|
||||
}
|
||||
22
src/fake_driver.hpp
Normal file
22
src/fake_driver.hpp
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
|
||||
class FakeDriver final : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit FakeDriver(QObject* parent = nullptr);
|
||||
|
||||
void start(int intervalMs, std::function<void()> onTick);
|
||||
|
||||
private slots:
|
||||
void onTimeout();
|
||||
|
||||
private:
|
||||
QTimer m_timer;
|
||||
std::function<void()> m_onTick;
|
||||
};
|
||||
67
src/hardware/drivers/serial/SerialCapabilities.h
Normal file
67
src/hardware/drivers/serial/SerialCapabilities.h
Normal file
@@ -0,0 +1,67 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <QString>
|
||||
|
||||
namespace softbus::hardware::drivers::serial::capabilities
|
||||
{
|
||||
|
||||
struct SerialDefaults
|
||||
{
|
||||
int baudRate = 9600;
|
||||
int dataBits = 8;
|
||||
const char* parity = "none";
|
||||
int stopBits = 1;
|
||||
const char* flowControl = "none";
|
||||
};
|
||||
|
||||
inline constexpr SerialDefaults kDefaults{};
|
||||
|
||||
inline constexpr std::array<int, 5> kBaudRates{9600, 19200, 38400, 57600, 115200};
|
||||
inline constexpr std::array<int, 4> kDataBits{5, 6, 7, 8};
|
||||
inline constexpr std::array<const char*, 3> kParity{"none", "even", "odd"};
|
||||
inline constexpr std::array<int, 2> kStopBits{1, 2};
|
||||
inline constexpr std::array<const char*, 3> kFlowControl{"none", "hardware", "software"};
|
||||
|
||||
inline bool isValidBaud(int v)
|
||||
{
|
||||
for (int b : kBaudRates) {
|
||||
if (b == v) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool isValidDataBits(int v)
|
||||
{
|
||||
for (int b : kDataBits) {
|
||||
if (b == v) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool isValidParity(const QString& s)
|
||||
{
|
||||
for (const char* p : kParity) {
|
||||
if (s == QLatin1String(p)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool isValidStopBits(int v)
|
||||
{
|
||||
for (int b : kStopBits) {
|
||||
if (b == v) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool isValidFlowControl(const QString& s)
|
||||
{
|
||||
for (const char* f : kFlowControl) {
|
||||
if (s == QLatin1String(f)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace softbus::hardware::drivers::serial::capabilities
|
||||
|
||||
187
src/hardware/drivers/serial/SerialQtDriver.cpp
Normal file
187
src/hardware/drivers/serial/SerialQtDriver.cpp
Normal file
@@ -0,0 +1,187 @@
|
||||
#include "hardware/drivers/serial/SerialQtDriver.h"
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QMetaObject>
|
||||
#include <QThread>
|
||||
|
||||
#include "utils/logs/logging.h"
|
||||
|
||||
namespace softbus::hardware::drivers::serial
|
||||
{
|
||||
|
||||
SerialQtDriver::SerialQtDriver(QObject* parent)
|
||||
: QObject(parent),
|
||||
m_port(this)
|
||||
{
|
||||
connect(&m_port, &QSerialPort::readyRead, this, &SerialQtDriver::handleReadyRead);
|
||||
connect(&m_port, &QSerialPort::errorOccurred, this, &SerialQtDriver::handleError);
|
||||
}
|
||||
|
||||
SerialQtDriver::~SerialQtDriver()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
bool SerialQtDriver::Open(const std::string& endpoint)
|
||||
{
|
||||
const QString ep = QString::fromStdString(endpoint);
|
||||
if (QThread::currentThread() == this->thread()) {
|
||||
return openOnOwnerThread(ep);
|
||||
}
|
||||
|
||||
bool ok = false;
|
||||
QMetaObject::invokeMethod(
|
||||
this,
|
||||
[this, ep, &ok]() { ok = openOnOwnerThread(ep); },
|
||||
Qt::BlockingQueuedConnection);
|
||||
return ok;
|
||||
}
|
||||
|
||||
void SerialQtDriver::Close()
|
||||
{
|
||||
if (QThread::currentThread() == this->thread()) {
|
||||
closeOnOwnerThread();
|
||||
return;
|
||||
}
|
||||
QMetaObject::invokeMethod(this, [this]() { closeOnOwnerThread(); }, Qt::BlockingQueuedConnection);
|
||||
}
|
||||
|
||||
bool SerialQtDriver::Write(const std::vector<std::uint8_t>& data)
|
||||
{
|
||||
return Write(data.data(), data.size());
|
||||
}
|
||||
|
||||
bool SerialQtDriver::Write(const std::uint8_t* data, std::size_t size)
|
||||
{
|
||||
if (!data || size == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const QByteArray bytes(reinterpret_cast<const char*>(data), static_cast<int>(size));
|
||||
// QSerialPort 必须在其所属线程内访问;若从外部线程调用,切回 owner 线程执行。
|
||||
if (QThread::currentThread() == this->thread()) {
|
||||
return writeOnOwnerThread(bytes);
|
||||
}
|
||||
|
||||
bool ok = false;
|
||||
QMetaObject::invokeMethod(
|
||||
this,
|
||||
[this, bytes, &ok]() { ok = writeOnOwnerThread(bytes); },
|
||||
Qt::BlockingQueuedConnection);
|
||||
return ok;
|
||||
}
|
||||
|
||||
void SerialQtDriver::SetReadCallback(ReadCallback cb)
|
||||
{
|
||||
m_readCallback = std::move(cb);
|
||||
}
|
||||
|
||||
void SerialQtDriver::SetErrorCallback(ErrorCallback cb)
|
||||
{
|
||||
m_errorCallback = std::move(cb);
|
||||
}
|
||||
|
||||
void SerialQtDriver::SetBaudRate(QSerialPort::BaudRate baud)
|
||||
{
|
||||
m_port.setBaudRate(baud);
|
||||
}
|
||||
|
||||
void SerialQtDriver::SetDataBits(QSerialPort::DataBits bits)
|
||||
{
|
||||
m_port.setDataBits(bits);
|
||||
}
|
||||
|
||||
void SerialQtDriver::SetParity(QSerialPort::Parity parity)
|
||||
{
|
||||
m_port.setParity(parity);
|
||||
}
|
||||
|
||||
void SerialQtDriver::SetStopBits(QSerialPort::StopBits stopBits)
|
||||
{
|
||||
m_port.setStopBits(stopBits);
|
||||
}
|
||||
|
||||
void SerialQtDriver::SetFlowControl(QSerialPort::FlowControl flow)
|
||||
{
|
||||
m_port.setFlowControl(flow);
|
||||
}
|
||||
|
||||
void SerialQtDriver::handleReadyRead()
|
||||
{
|
||||
if (!m_readCallback) {
|
||||
const QByteArray data = m_port.readAll();
|
||||
LOG_DEBUG() << "SerialQtDriver: dropped" << data.size() << "bytes on" << m_port.portName()
|
||||
<< "because no read callback is set";
|
||||
return;
|
||||
}
|
||||
|
||||
const QByteArray data = m_port.readAll();
|
||||
if (data.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> buffer(reinterpret_cast<const std::uint8_t*>(data.constData()),
|
||||
reinterpret_cast<const std::uint8_t*>(data.constData()) + data.size());
|
||||
m_readCallback(buffer);
|
||||
}
|
||||
|
||||
void SerialQtDriver::handleError(QSerialPort::SerialPortError error)
|
||||
{
|
||||
if (error == QSerialPort::NoError) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QString msg =
|
||||
QStringLiteral("SerialQtDriver error on %1: %2").arg(m_port.portName(), m_port.errorString());
|
||||
|
||||
LOG_WARNING() << msg;
|
||||
|
||||
if (m_errorCallback) {
|
||||
m_errorCallback(msg.toStdString());
|
||||
}
|
||||
}
|
||||
|
||||
bool SerialQtDriver::writeOnOwnerThread(const QByteArray& bytes)
|
||||
{
|
||||
if (!m_port.isOpen()) {
|
||||
return false;
|
||||
}
|
||||
const qint64 written = m_port.write(bytes);
|
||||
if (written != bytes.size()) {
|
||||
LOG_WARNING() << "SerialQtDriver: partial write on" << m_port.portName() << "written" << written << "of"
|
||||
<< bytes.size();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SerialQtDriver::openOnOwnerThread(const QString& endpoint)
|
||||
{
|
||||
if (m_port.isOpen()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
m_port.setPortName(endpoint);
|
||||
if (!m_port.open(QIODevice::ReadWrite)) {
|
||||
if (m_errorCallback) {
|
||||
m_errorCallback(m_port.errorString().toStdString());
|
||||
}
|
||||
LOG_WARNING() << "SerialQtDriver: failed to open port" << m_port.portName() << ":" << m_port.errorString();
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG_DEBUG() << "SerialQtDriver: opened port" << m_port.portName();
|
||||
return true;
|
||||
}
|
||||
|
||||
void SerialQtDriver::closeOnOwnerThread()
|
||||
{
|
||||
if (m_port.isOpen()) {
|
||||
const QString name = m_port.portName();
|
||||
m_port.close();
|
||||
LOG_DEBUG() << "SerialQtDriver: closed port" << name;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace softbus::hardware::drivers::serial
|
||||
|
||||
93
src/hardware/drivers/serial/SerialQtDriver.h
Normal file
93
src/hardware/drivers/serial/SerialQtDriver.h
Normal file
@@ -0,0 +1,93 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QSerialPort>
|
||||
#include <QString>
|
||||
#include <cstddef>
|
||||
#include <QByteArray>
|
||||
|
||||
#include "hardware/interfaces/IStreamDriver.h"
|
||||
|
||||
// 这里的串口的唯一标识是串口的端口名,如 /dev/ttyUSB0, can0, tcp://...
|
||||
// 串口的endpoint是串口的端口名,如 /dev/ttyUSB0, can0, tcp://...
|
||||
// 使用说明:
|
||||
// 1. 创建一个SerialQtDriver对象
|
||||
// 2. 调用Open方法打开串口
|
||||
// 3. 调用Write方法写数据
|
||||
// 4. 调用SetReadCallback方法设置读回调
|
||||
// 5. 调用SetErrorCallback方法设置错误回调
|
||||
// 6. 调用Close方法关闭串口
|
||||
// 7. 调用SetBaudRate方法设置波特率
|
||||
// 8. 调用SetDataBits方法设置数据位
|
||||
// 9. 调用SetParity方法设置校验位
|
||||
// 10. 调用SetStopBits方法设置停止位
|
||||
// 11. 调用SetFlowControl方法设置流控制
|
||||
// eg:
|
||||
// SerialQtDriver driver;
|
||||
// driver.Open("/dev/ttyUSB0");
|
||||
// driver.Write({0x01, 0x02, 0x03});
|
||||
// driver.SetReadCallback([](const std::vector<std::uint8_t>& data) {
|
||||
// std::cout << "read data: " << data << std::endl;
|
||||
// });
|
||||
// driver.SetErrorCallback([](const std::string& error) {
|
||||
// std::cout << "error: " << error << std::endl;
|
||||
// });
|
||||
// driver.Close();
|
||||
// 12. 调用SetBaudRate方法设置波特率
|
||||
// 13. 调用SetDataBits方法设置数据位
|
||||
// 14. 调用SetParity方法设置校验位
|
||||
// 15. 调用SetStopBits方法设置停止位
|
||||
// 16. 调用SetFlowControl方法设置流控制
|
||||
// eg:
|
||||
// driver.SetBaudRate(QSerialPort::Baud115200);
|
||||
// driver.SetDataBits(QSerialPort::Data8);
|
||||
// driver.SetParity(QSerialPort::NoParity);
|
||||
// driver.SetStopBits(QSerialPort::OneStop);
|
||||
// driver.SetFlowControl(QSerialPort::NoFlowControl);
|
||||
// driver.Close();
|
||||
|
||||
namespace softbus::hardware::drivers::serial
|
||||
{
|
||||
|
||||
// 基于 Qt 的串口驱动实现:只负责串口 open/read/write,不包含设备注册、数据库或协议逻辑
|
||||
class SerialQtDriver : public QObject, public softbus::hardware::interfaces::IStreamDriver
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SerialQtDriver(QObject* parent = nullptr);
|
||||
~SerialQtDriver() override;
|
||||
|
||||
bool Open(const std::string& endpoint) override;
|
||||
void Close() override;
|
||||
|
||||
bool Write(const std::vector<std::uint8_t>& data) override;
|
||||
bool Write(const std::uint8_t* data, std::size_t size) override;
|
||||
|
||||
void SetReadCallback(ReadCallback cb) override;
|
||||
void SetErrorCallback(ErrorCallback cb) override;
|
||||
|
||||
// 允许上层配置串口参数(最小集合)
|
||||
void SetBaudRate(QSerialPort::BaudRate baud);
|
||||
void SetDataBits(QSerialPort::DataBits bits);
|
||||
void SetParity(QSerialPort::Parity parity);
|
||||
void SetStopBits(QSerialPort::StopBits stopBits);
|
||||
void SetFlowControl(QSerialPort::FlowControl flow);
|
||||
|
||||
private slots:
|
||||
void handleReadyRead();
|
||||
void handleError(QSerialPort::SerialPortError error);
|
||||
|
||||
private:
|
||||
bool openOnOwnerThread(const QString& endpoint);
|
||||
void closeOnOwnerThread();
|
||||
bool writeOnOwnerThread(const QByteArray& bytes);
|
||||
|
||||
private:
|
||||
QSerialPort m_port;
|
||||
ReadCallback m_readCallback;
|
||||
ErrorCallback m_errorCallback;
|
||||
};
|
||||
|
||||
} // namespace softbus::hardware::drivers::serial
|
||||
|
||||
37
src/hardware/interfaces/IDeviceWatcher.h
Normal file
37
src/hardware/interfaces/IDeviceWatcher.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
namespace softbus::hardware::interfaces
|
||||
{
|
||||
|
||||
struct DiscoveredDevice
|
||||
{
|
||||
std::string id; // 稳定 ID(如序列号、路径)
|
||||
std::string endpoint; // 打开所需的 endpoint(如 /dev/ttyUSB0, can0, tcp://...)
|
||||
std::string type; // 设备类型:serial / can / ethernet / usb 等
|
||||
};
|
||||
|
||||
// 设备发现 / 监控接口:负责发现插拔事件,不负责创建设备或驱动实例
|
||||
class IDeviceWatcher
|
||||
{
|
||||
public:
|
||||
using DeviceAttachedCallback = std::function<void(const DiscoveredDevice&)>;
|
||||
using DeviceDetachedCallback = std::function<void(const DiscoveredDevice&)>;
|
||||
|
||||
virtual ~IDeviceWatcher() = default;
|
||||
|
||||
virtual bool Start() = 0;
|
||||
virtual void Stop() = 0;
|
||||
|
||||
virtual void SetDeviceAttachedCallback(DeviceAttachedCallback cb) = 0;
|
||||
virtual void SetDeviceDetachedCallback(DeviceDetachedCallback cb) = 0;
|
||||
};
|
||||
|
||||
} // namespace softbus::hardware::interfaces
|
||||
|
||||
#endif // __cplusplus
|
||||
|
||||
41
src/hardware/interfaces/IFrameDriver.h
Normal file
41
src/hardware/interfaces/IFrameDriver.h
Normal file
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace softbus::hardware::interfaces
|
||||
{
|
||||
|
||||
struct Frame
|
||||
{
|
||||
std::vector<std::uint8_t> payload;
|
||||
std::uint32_t id{0}; // 如 CAN ID
|
||||
std::uint64_t timestamp{0};
|
||||
};
|
||||
|
||||
// 面向帧设备(如 CAN 总线)的统一驱动接口
|
||||
class IFrameDriver
|
||||
{
|
||||
public:
|
||||
using FrameCallback = std::function<void(const Frame&)>;
|
||||
using ErrorCallback = std::function<void(const std::string& message)>;
|
||||
|
||||
virtual ~IFrameDriver() = default;
|
||||
|
||||
virtual bool Open(const std::string& endpoint) = 0;
|
||||
virtual void Close() = 0;
|
||||
|
||||
virtual bool WriteFrame(const Frame& frame) = 0;
|
||||
|
||||
virtual void SetFrameCallback(FrameCallback cb) = 0;
|
||||
virtual void SetErrorCallback(ErrorCallback cb) = 0;
|
||||
};
|
||||
|
||||
} // namespace softbus::hardware::interfaces
|
||||
|
||||
#endif // __cplusplus
|
||||
|
||||
35
src/hardware/interfaces/IStreamDriver.h
Normal file
35
src/hardware/interfaces/IStreamDriver.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace softbus::hardware::interfaces
|
||||
{
|
||||
|
||||
// 面向字节流设备的统一驱动接口:串口 / TCP / UDP 等
|
||||
class IStreamDriver
|
||||
{
|
||||
public:
|
||||
using ReadCallback = std::function<void(const std::vector<std::uint8_t>&)>;
|
||||
using ErrorCallback = std::function<void(const std::string& message)>;
|
||||
|
||||
virtual ~IStreamDriver() = default;
|
||||
|
||||
virtual bool Open(const std::string& endpoint) = 0;
|
||||
virtual void Close() = 0;
|
||||
|
||||
virtual bool Write(const std::vector<std::uint8_t>& data) = 0;
|
||||
virtual bool Write(const std::uint8_t* data, std::size_t size) = 0;
|
||||
|
||||
virtual void SetReadCallback(ReadCallback cb) = 0;
|
||||
virtual void SetErrorCallback(ErrorCallback cb) = 0;
|
||||
};
|
||||
|
||||
} // namespace softbus::hardware::interfaces
|
||||
|
||||
#endif // __cplusplus
|
||||
|
||||
213
src/main.cpp
Normal file
213
src/main.cpp
Normal file
@@ -0,0 +1,213 @@
|
||||
#include <QCoreApplication>
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QFile>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonParseError>
|
||||
#include <QSaveFile>
|
||||
|
||||
#include "device_bus/EdgeDeviceBus.h"
|
||||
#include "device_bus/monitor/EdgeDiscoveryCoordinator.h"
|
||||
#include "message_bus/ingress/EdgeSession.h"
|
||||
#include "message_bus/ingress/EdgeTcpUplinkPort.h"
|
||||
#include "message_bus/ingress/EdgeUplinkWire.h"
|
||||
|
||||
#include "fake_driver.hpp"
|
||||
#include "ops_ui_http.hpp"
|
||||
|
||||
/** 无配置文件或与核心同目录运行时使用的默认值(可被 manifest 覆盖)。 */
|
||||
static QJsonObject defaultManifest()
|
||||
{
|
||||
QJsonObject m;
|
||||
m.insert(QStringLiteral("coreHost"), QStringLiteral("127.0.0.1"));
|
||||
m.insert(QStringLiteral("corePort"), 18765);
|
||||
m.insert(QStringLiteral("edgeId"), QStringLiteral("edge-dev-1"));
|
||||
m.insert(QStringLiteral("authToken"), QStringLiteral(""));
|
||||
m.insert(QStringLiteral("opsListenPort"), 0);
|
||||
m.insert(QStringLiteral("localTreePath"), QStringLiteral("/tmp/edge_local_tree.json"));
|
||||
m.insert(QStringLiteral("fakeIntervalMs"), 5000);
|
||||
m.insert(QStringLiteral("fakeDeviceId"), 1);
|
||||
m.insert(QStringLiteral("endpoint"), QStringLiteral("edge:fake"));
|
||||
m.insert(QStringLiteral("protocol"), 0);
|
||||
m.insert(QStringLiteral("edgeDiscoveryEnabled"), true);
|
||||
return m;
|
||||
}
|
||||
|
||||
/** 读取 manifest;缺失或非法时返回 defaultManifest(),并打日志。 */
|
||||
static QJsonObject loadManifestOrDefaults(const QString& path)
|
||||
{
|
||||
QJsonObject out = defaultManifest();
|
||||
QFile f(path);
|
||||
if (!f.open(QIODevice::ReadOnly)) {
|
||||
qWarning() << "edge manifest not found or unreadable, using defaults:" << path;
|
||||
return out;
|
||||
}
|
||||
QJsonParseError pe;
|
||||
const QJsonDocument d = QJsonDocument::fromJson(f.readAll(), &pe);
|
||||
if (pe.error != QJsonParseError::NoError || !d.isObject()) {
|
||||
qWarning() << "edge manifest JSON invalid, using defaults:" << pe.errorString() << "path" << path;
|
||||
return out;
|
||||
}
|
||||
const QJsonObject fromFile = d.object();
|
||||
for (auto it = fromFile.constBegin(); it != fromFile.constEnd(); ++it) {
|
||||
out.insert(it.key(), it.value());
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static void tryReloadTreeFromDisk(softbus::device_bus::EdgeDeviceBus& bus, const QString& localTreePath)
|
||||
{
|
||||
QString err;
|
||||
if (bus.loadLocalEdgeTreeFile(localTreePath, &err)) {
|
||||
bus.reconcileSerialFromTree();
|
||||
bus.reconcileCanFromTree();
|
||||
qInfo() << "EdgeDeviceBus: loaded tree from" << localTreePath;
|
||||
} else if (!err.isEmpty()) {
|
||||
qWarning() << "EdgeDeviceBus: load failed" << err;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
QCoreApplication app(argc, argv);
|
||||
const QString manifestPath = argc > 1 ? QString::fromLocal8Bit(argv[1])
|
||||
: QStringLiteral("config/edge_manifest.json");
|
||||
const QJsonObject m = loadManifestOrDefaults(manifestPath);
|
||||
|
||||
const QString coreHost = m.value(QStringLiteral("coreHost")).toString(QStringLiteral("127.0.0.1"));
|
||||
const int corePort = m.value(QStringLiteral("corePort")).toInt(18765);
|
||||
const QString edgeId = m.value(QStringLiteral("edgeId")).toString(QStringLiteral("edge-dev-1"));
|
||||
const QString authToken = m.value(QStringLiteral("authToken")).toString();
|
||||
const int opsPort = m.value(QStringLiteral("opsListenPort")).toInt(0);
|
||||
const QString localTreePath = m.value(QStringLiteral("localTreePath")).toString(QStringLiteral("/tmp/edge_local_tree.json"));
|
||||
const int fakeIntervalMs = m.value(QStringLiteral("fakeIntervalMs")).toInt(5000);
|
||||
const int deviceId = m.value(QStringLiteral("fakeDeviceId")).toInt(1);
|
||||
const QString endpoint = m.value(QStringLiteral("endpoint")).toString(QStringLiteral("edge:fake"));
|
||||
const int protocol = m.value(QStringLiteral("protocol")).toInt(0);
|
||||
const bool edgeDiscoveryEnabled = m.value(QStringLiteral("edgeDiscoveryEnabled")).toBool(true);
|
||||
|
||||
softbus::device_bus::EdgeDeviceBus edgeBus;
|
||||
softbus::message_bus::ingress::EdgeSession session;
|
||||
|
||||
qint64 coreTreeRevision = 0;
|
||||
softbus_edge::EdgeDiscoveryCoordinator discovery;
|
||||
discovery.setEdgeSession(&session);
|
||||
discovery.setEdgeId(edgeId);
|
||||
discovery.setCoreTreeRevisionGetter([&coreTreeRevision]() { return coreTreeRevision; });
|
||||
|
||||
auto tcpUplink = std::make_unique<softbus::message_bus::ingress::EdgeTcpUplinkPort>(
|
||||
[&session](const QByteArray& fr) { return session.writeSbupFrame(fr); });
|
||||
edgeBus.setIngressPort(tcpUplink.get());
|
||||
|
||||
if (QFile::exists(localTreePath)) {
|
||||
tryReloadTreeFromDisk(edgeBus, localTreePath);
|
||||
}
|
||||
|
||||
QObject::connect(&session, &softbus::message_bus::ingress::EdgeSession::receivedTreeResult, &app,
|
||||
[&](const QJsonObject& o) {
|
||||
qInfo() << "TREE_RESULT" << o;
|
||||
if (o.value(QStringLiteral("ok")).toBool()) {
|
||||
if (o.contains(QStringLiteral("newTreeRevision"))) {
|
||||
coreTreeRevision =
|
||||
o.value(QStringLiteral("newTreeRevision")).toVariant().toLongLong();
|
||||
} else if (o.contains(QStringLiteral("currentTreeRevision"))) {
|
||||
coreTreeRevision =
|
||||
o.value(QStringLiteral("currentTreeRevision")).toVariant().toLongLong();
|
||||
}
|
||||
} else if (o.value(QStringLiteral("code")).toString() == QStringLiteral("CONFLICT")) {
|
||||
if (o.contains(QStringLiteral("currentTreeRevision"))) {
|
||||
coreTreeRevision =
|
||||
o.value(QStringLiteral("currentTreeRevision")).toVariant().toLongLong();
|
||||
}
|
||||
}
|
||||
discovery.onTreeResultFromCore(o);
|
||||
});
|
||||
QObject::connect(&session, &softbus::message_bus::ingress::EdgeSession::receivedTreeFull, &app,
|
||||
[&](const QJsonObject& envelope) {
|
||||
QJsonObject doc;
|
||||
doc.insert(QStringLiteral("treeRevision"), envelope.value(QStringLiteral("treeRevision")));
|
||||
doc.insert(QStringLiteral("edgeId"), envelope.value(QStringLiteral("edgeId")));
|
||||
doc.insert(QStringLiteral("tree"), envelope.value(QStringLiteral("tree")));
|
||||
QSaveFile out(localTreePath);
|
||||
if (!out.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
|
||||
qWarning() << "TREE_FULL: cannot write" << localTreePath;
|
||||
return;
|
||||
}
|
||||
out.write(QJsonDocument(doc).toJson(QJsonDocument::Indented));
|
||||
if (!out.commit()) {
|
||||
qWarning() << "TREE_FULL: commit failed" << localTreePath;
|
||||
return;
|
||||
}
|
||||
qInfo() << "TREE_FULL applied revision"
|
||||
<< doc.value(QStringLiteral("treeRevision")).toVariant().toLongLong() << "nodes"
|
||||
<< doc.value(QStringLiteral("tree")).toArray().size() << "->" << localTreePath;
|
||||
tryReloadTreeFromDisk(edgeBus, localTreePath);
|
||||
});
|
||||
|
||||
if (!session.connectToCore(coreHost, corePort)) {
|
||||
qWarning() << "cannot reach core; start softbus_daemon with edgeIngress.enabled and matching port,"
|
||||
<< "or set coreHost/corePort in edge_manifest.json";
|
||||
return 3;
|
||||
}
|
||||
if (!session.sendHello(edgeId, authToken, 0)) {
|
||||
qWarning() << "hello send failed";
|
||||
return 4;
|
||||
}
|
||||
|
||||
if (edgeDiscoveryEnabled) {
|
||||
(void)discovery.start();
|
||||
}
|
||||
|
||||
OpsUiHttp ops;
|
||||
if (opsPort > 0) {
|
||||
if (!ops.start(static_cast<quint16>(opsPort),
|
||||
localTreePath,
|
||||
edgeId,
|
||||
[&coreTreeRevision]() { return coreTreeRevision; },
|
||||
[&](const QJsonObject& body) {
|
||||
if (!session.sendTreePropose(body)) {
|
||||
qWarning() << "propose send failed";
|
||||
}
|
||||
},
|
||||
[&discovery]() { return discovery.recentDiscoveriesJson(); })) {
|
||||
qWarning() << "ops ui listen failed";
|
||||
} else {
|
||||
qInfo() << "ops UI http://127.0.0.1:" << opsPort
|
||||
<< " GET / GET /ui GET /discovered POST /propose POST /register_discovered";
|
||||
}
|
||||
}
|
||||
|
||||
FakeDriver fake;
|
||||
if (fakeIntervalMs > 0) {
|
||||
fake.start(fakeIntervalMs, [&]() {
|
||||
const std::uint32_t epHash = static_cast<std::uint32_t>(qHash(endpoint));
|
||||
const std::uint64_t routing = (static_cast<std::uint64_t>(epHash) << 32)
|
||||
| static_cast<std::uint32_t>(deviceId);
|
||||
const QByteArray inner = softbus::message_bus::ingress::edge_uplink::encodeDataRawBusPayload(
|
||||
deviceId,
|
||||
epHash,
|
||||
static_cast<std::uint16_t>(protocol),
|
||||
0,
|
||||
routing,
|
||||
0,
|
||||
QDateTime::currentMSecsSinceEpoch(),
|
||||
1,
|
||||
QStringLiteral("edge:fake"),
|
||||
QByteArray(1, '\x01'));
|
||||
if (inner.isEmpty()) {
|
||||
qWarning() << "encode data failed";
|
||||
return;
|
||||
}
|
||||
if (!session.sendDataRawBusInner(inner)) {
|
||||
qWarning() << "send data failed";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
QObject::connect(&app, &QCoreApplication::aboutToQuit, &discovery, [&discovery]() { discovery.stop(); });
|
||||
|
||||
(void)tcpUplink;
|
||||
return app.exec();
|
||||
}
|
||||
91
src/message_bus/ingress/EdgeSession.cpp
Normal file
91
src/message_bus/ingress/EdgeSession.cpp
Normal file
@@ -0,0 +1,91 @@
|
||||
#include "message_bus/ingress/EdgeSession.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonParseError>
|
||||
|
||||
#include "message_bus/ingress/EdgeUplinkWire.h"
|
||||
|
||||
using softbus::message_bus::ingress::edge_uplink::FrameKind;
|
||||
using softbus::message_bus::ingress::edge_uplink::encodeOuterFrame;
|
||||
using softbus::message_bus::ingress::edge_uplink::tryPopOneOuterFrame;
|
||||
|
||||
namespace softbus::message_bus::ingress
|
||||
{
|
||||
|
||||
EdgeSession::EdgeSession(QObject* parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
connect(&m_sock, &QTcpSocket::readyRead, this, &EdgeSession::onSocketReadyRead);
|
||||
}
|
||||
|
||||
bool EdgeSession::connectToCore(const QString& host, int port)
|
||||
{
|
||||
m_sock.connectToHost(host, static_cast<quint16>(port));
|
||||
if (!m_sock.waitForConnected(5000)) {
|
||||
qWarning() << "EdgeSession: TCP connect failed" << host << port << m_sock.errorString();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void EdgeSession::disconnectCore()
|
||||
{
|
||||
m_sock.disconnectFromHost();
|
||||
}
|
||||
|
||||
bool EdgeSession::sendHello(const QString& edgeId, const QString& authToken, qint64 currentTreeRevision)
|
||||
{
|
||||
QJsonObject o;
|
||||
o.insert(QStringLiteral("edgeId"), edgeId);
|
||||
if (!authToken.isEmpty()) {
|
||||
o.insert(QStringLiteral("authToken"), authToken);
|
||||
}
|
||||
o.insert(QStringLiteral("currentTreeRevision"), currentTreeRevision);
|
||||
const QByteArray pl = QJsonDocument(o).toJson(QJsonDocument::Compact);
|
||||
const QByteArray frame = encodeOuterFrame(FrameKind::Hello, pl);
|
||||
return m_sock.write(frame) == frame.size();
|
||||
}
|
||||
|
||||
bool EdgeSession::sendDataRawBusInner(const QByteArray& innerPayload)
|
||||
{
|
||||
const QByteArray frame = encodeOuterFrame(FrameKind::DataRawBus, innerPayload);
|
||||
return m_sock.write(frame) == frame.size();
|
||||
}
|
||||
|
||||
bool EdgeSession::sendTreePropose(const QJsonObject& body)
|
||||
{
|
||||
const QByteArray pl = QJsonDocument(body).toJson(QJsonDocument::Compact);
|
||||
const QByteArray frame = encodeOuterFrame(FrameKind::TreePropose, pl);
|
||||
return m_sock.write(frame) == frame.size();
|
||||
}
|
||||
|
||||
bool EdgeSession::writeSbupFrame(const QByteArray& frame)
|
||||
{
|
||||
return m_sock.write(frame) == frame.size();
|
||||
}
|
||||
|
||||
void EdgeSession::onSocketReadyRead()
|
||||
{
|
||||
m_inbuf.append(m_sock.readAll());
|
||||
while (true) {
|
||||
const auto popped = tryPopOneOuterFrame(m_inbuf, m_maxPayload);
|
||||
if (!popped.has_value()) {
|
||||
break;
|
||||
}
|
||||
if (popped->kind == FrameKind::TreeResult) {
|
||||
QJsonParseError pe;
|
||||
const QJsonDocument doc = QJsonDocument::fromJson(popped->payload, &pe);
|
||||
if (pe.error == QJsonParseError::NoError && doc.isObject()) {
|
||||
emit receivedTreeResult(doc.object());
|
||||
}
|
||||
} else if (popped->kind == FrameKind::TreeFull) {
|
||||
QJsonParseError pe;
|
||||
const QJsonDocument doc = QJsonDocument::fromJson(popped->payload, &pe);
|
||||
if (pe.error == QJsonParseError::NoError && doc.isObject()) {
|
||||
emit receivedTreeFull(doc.object());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace softbus::message_bus::ingress
|
||||
41
src/message_bus/ingress/EdgeSession.h
Normal file
41
src/message_bus/ingress/EdgeSession.h
Normal file
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QJsonObject>
|
||||
#include <QObject>
|
||||
#include <QTcpSocket>
|
||||
|
||||
namespace softbus::message_bus::ingress
|
||||
{
|
||||
|
||||
class EdgeSession final : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit EdgeSession(QObject* parent = nullptr);
|
||||
|
||||
bool connectToCore(const QString& host, int port);
|
||||
void disconnectCore();
|
||||
|
||||
bool sendHello(const QString& edgeId, const QString& authToken, qint64 currentTreeRevision);
|
||||
bool sendDataRawBusInner(const QByteArray& innerPayload);
|
||||
bool sendTreePropose(const QJsonObject& body);
|
||||
/** 已带 SBUP 外层的任意帧(如完整 `DATA_RAWBUS`)。 */
|
||||
bool writeSbupFrame(const QByteArray& frame);
|
||||
|
||||
void setMaxPayloadBytes(int m) { m_maxPayload = m; }
|
||||
|
||||
signals:
|
||||
void receivedTreeResult(const QJsonObject& obj);
|
||||
void receivedTreeFull(const QJsonObject& obj);
|
||||
|
||||
private slots:
|
||||
void onSocketReadyRead();
|
||||
|
||||
private:
|
||||
QTcpSocket m_sock;
|
||||
QByteArray m_inbuf;
|
||||
int m_maxPayload{4 * 1024 * 1024};
|
||||
};
|
||||
|
||||
} // namespace softbus::message_bus::ingress
|
||||
53
src/message_bus/ingress/EdgeTcpUplinkPort.cpp
Normal file
53
src/message_bus/ingress/EdgeTcpUplinkPort.cpp
Normal file
@@ -0,0 +1,53 @@
|
||||
#include "message_bus/ingress/EdgeTcpUplinkPort.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QDebug>
|
||||
|
||||
#include "message_bus/ingress/EdgeUplinkWire.h"
|
||||
|
||||
namespace softbus::message_bus::ingress
|
||||
{
|
||||
|
||||
EdgeTcpUplinkPort::EdgeTcpUplinkPort(FrameWriter writer)
|
||||
: m_writer(std::move(writer))
|
||||
{
|
||||
}
|
||||
|
||||
bool EdgeTcpUplinkPort::push(softbus::core::models::RawBusMessage ctx)
|
||||
{
|
||||
if (!m_writer || !ctx.payload.valid()) {
|
||||
return false;
|
||||
}
|
||||
std::uint64_t protocolHeaderRaw = 0;
|
||||
std::memcpy(&protocolHeaderRaw, &ctx.header, sizeof(protocolHeaderRaw));
|
||||
|
||||
const auto* bytes = ctx.payload.bytes();
|
||||
const QByteArray busPayload(reinterpret_cast<const char*>(bytes), static_cast<int>(ctx.payload.length));
|
||||
|
||||
const QByteArray inner = edge_uplink::encodeDataRawBusPayload(ctx.deviceId,
|
||||
ctx.endpointHash,
|
||||
static_cast<std::uint16_t>(ctx.protocol),
|
||||
ctx.logicalAddress,
|
||||
ctx.routingKey,
|
||||
protocolHeaderRaw,
|
||||
ctx.timestampMs,
|
||||
static_cast<std::int8_t>(ctx.direction),
|
||||
ctx.traceId.isEmpty() ? QStringLiteral("edge")
|
||||
: ctx.traceId,
|
||||
busPayload);
|
||||
if (inner.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
const QByteArray outer =
|
||||
edge_uplink::encodeOuterFrame(edge_uplink::FrameKind::DataRawBus, inner);
|
||||
return m_writer(outer);
|
||||
}
|
||||
|
||||
void EdgeTcpUplinkPort::reportError(const QString& endpoint, const QString& error)
|
||||
{
|
||||
qWarning() << "EdgeTcpUplinkPort" << endpoint << error;
|
||||
}
|
||||
|
||||
} // namespace softbus::message_bus::ingress
|
||||
28
src/message_bus/ingress/EdgeTcpUplinkPort.h
Normal file
28
src/message_bus/ingress/EdgeTcpUplinkPort.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
|
||||
#include "message_bus/ingress/IIngressPort.h"
|
||||
|
||||
namespace softbus::message_bus::ingress
|
||||
{
|
||||
|
||||
/** 将 `RawBusMessage` 编码为 SBUP `DATA_RAWBUS` 帧并写入 TCP(边缘 → 核心)。 */
|
||||
class EdgeTcpUplinkPort final : public IIngressPort
|
||||
{
|
||||
public:
|
||||
using FrameWriter = std::function<bool(const QByteArray& sbupFrame)>;
|
||||
|
||||
explicit EdgeTcpUplinkPort(FrameWriter writer);
|
||||
|
||||
bool push(softbus::core::models::RawBusMessage ctx) override;
|
||||
void reportError(const QString& endpoint, const QString& error) override;
|
||||
|
||||
private:
|
||||
FrameWriter m_writer;
|
||||
};
|
||||
|
||||
} // namespace softbus::message_bus::ingress
|
||||
178
src/message_bus/ingress/EdgeUplinkWire.cpp
Normal file
178
src/message_bus/ingress/EdgeUplinkWire.cpp
Normal file
@@ -0,0 +1,178 @@
|
||||
#include "message_bus/ingress/EdgeUplinkWire.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include <QtEndian>
|
||||
|
||||
#include "utils/logs/logging.h"
|
||||
|
||||
namespace softbus::message_bus::ingress::edge_uplink
|
||||
{
|
||||
|
||||
static std::uint16_t readU16BE(const unsigned char* p)
|
||||
{
|
||||
return qFromBigEndian<quint16>(reinterpret_cast<const quint8*>(p));
|
||||
}
|
||||
|
||||
static std::uint32_t readU32BE(const unsigned char* p)
|
||||
{
|
||||
return qFromBigEndian<quint32>(reinterpret_cast<const quint8*>(p));
|
||||
}
|
||||
|
||||
static std::int32_t readI32BE(const unsigned char* p)
|
||||
{
|
||||
return static_cast<std::int32_t>(readU32BE(p));
|
||||
}
|
||||
|
||||
static std::int64_t readI64BE(const unsigned char* p)
|
||||
{
|
||||
return qFromBigEndian<qint64>(reinterpret_cast<const quint8*>(p));
|
||||
}
|
||||
|
||||
static std::uint64_t readU64BE(const unsigned char* p)
|
||||
{
|
||||
return qFromBigEndian<quint64>(reinterpret_cast<const quint8*>(p));
|
||||
}
|
||||
|
||||
QByteArray encodeOuterFrame(FrameKind kind, const QByteArray& payload)
|
||||
{
|
||||
QByteArray out;
|
||||
out.resize(static_cast<int>(kOuterHeaderBytes + payload.size()));
|
||||
unsigned char* d = reinterpret_cast<unsigned char*>(out.data());
|
||||
d[0] = static_cast<unsigned char>(kMagic0);
|
||||
d[1] = static_cast<unsigned char>(kMagic1);
|
||||
d[2] = static_cast<unsigned char>(kMagic2);
|
||||
d[3] = static_cast<unsigned char>(kMagic3);
|
||||
qToBigEndian(kWireVersion, d + 4);
|
||||
qToBigEndian(static_cast<std::uint16_t>(kind), d + 6);
|
||||
qToBigEndian(static_cast<std::uint32_t>(0), d + 8);
|
||||
qToBigEndian(static_cast<std::uint32_t>(payload.size()), d + 12);
|
||||
if (!payload.isEmpty()) {
|
||||
std::memcpy(d + kOuterHeaderBytes, payload.constData(), static_cast<std::size_t>(payload.size()));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
QByteArray encodeDataRawBusPayload(int deviceId,
|
||||
std::uint32_t endpointHash,
|
||||
std::uint16_t protocol,
|
||||
std::uint32_t logicalAddress,
|
||||
std::uint64_t routingKey,
|
||||
std::uint64_t protocolHeaderRaw,
|
||||
qint64 timestampMs,
|
||||
std::int8_t direction,
|
||||
const QString& traceId,
|
||||
const QByteArray& busPayload)
|
||||
{
|
||||
const QByteArray tr = traceId.toUtf8();
|
||||
if (tr.size() > kMaxTraceLen) {
|
||||
return {};
|
||||
}
|
||||
QByteArray inner;
|
||||
inner.resize(static_cast<int>(kDataRawBusFixedBytes + tr.size() + busPayload.size()));
|
||||
unsigned char* w = reinterpret_cast<unsigned char*>(inner.data());
|
||||
qToBigEndian(static_cast<quint16>(tr.size()), w + 0);
|
||||
qToBigEndian(static_cast<qint32>(deviceId), reinterpret_cast<uchar*>(w + 2));
|
||||
qToBigEndian(endpointHash, w + 6);
|
||||
qToBigEndian(protocol, w + 10);
|
||||
qToBigEndian(static_cast<quint16>(0), w + 12);
|
||||
qToBigEndian(logicalAddress, w + 14);
|
||||
qToBigEndian(routingKey, w + 18);
|
||||
qToBigEndian(protocolHeaderRaw, w + 26);
|
||||
qToBigEndian(timestampMs, reinterpret_cast<uchar*>(w + 34));
|
||||
w[42] = static_cast<unsigned char>(direction);
|
||||
std::memset(w + 43, 0, 5);
|
||||
if (!tr.isEmpty()) {
|
||||
std::memcpy(w + 48, tr.constData(), static_cast<std::size_t>(tr.size()));
|
||||
}
|
||||
if (!busPayload.isEmpty()) {
|
||||
std::memcpy(w + 48 + tr.size(), busPayload.constData(), static_cast<std::size_t>(busPayload.size()));
|
||||
}
|
||||
return inner;
|
||||
}
|
||||
|
||||
std::optional<OuterFramePop> tryPopOneOuterFrame(QByteArray& buffer, int maxPayloadBytes)
|
||||
{
|
||||
if (buffer.size() < static_cast<int>(kOuterHeaderBytes)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const unsigned char* d = reinterpret_cast<const unsigned char*>(buffer.constData());
|
||||
if (d[0] != static_cast<unsigned char>(kMagic0) || d[1] != static_cast<unsigned char>(kMagic1)
|
||||
|| d[2] != static_cast<unsigned char>(kMagic2) || d[3] != static_cast<unsigned char>(kMagic3)) {
|
||||
LOG_WARNING() << "EdgeUplinkWire: bad magic, dropping byte";
|
||||
buffer.remove(0, 1);
|
||||
return std::nullopt;
|
||||
}
|
||||
const std::uint16_t ver = readU16BE(d + 4);
|
||||
if (ver != kWireVersion) {
|
||||
LOG_WARNING() << "EdgeUplinkWire: unsupported wire_version" << ver;
|
||||
buffer.clear();
|
||||
return std::nullopt;
|
||||
}
|
||||
const std::uint32_t payloadLen = readU32BE(d + 12);
|
||||
if (maxPayloadBytes > 0 && payloadLen > static_cast<std::uint32_t>(maxPayloadBytes)) {
|
||||
LOG_WARNING() << "EdgeUplinkWire: payload_len exceeds max" << payloadLen << maxPayloadBytes;
|
||||
buffer.clear();
|
||||
return std::nullopt;
|
||||
}
|
||||
const int total = static_cast<int>(kOuterHeaderBytes) + static_cast<int>(payloadLen);
|
||||
if (payloadLen > 64u * 1024u * 1024u) {
|
||||
LOG_WARNING() << "EdgeUplinkWire: absurd payload_len" << payloadLen;
|
||||
buffer.clear();
|
||||
return std::nullopt;
|
||||
}
|
||||
if (buffer.size() < total) {
|
||||
return std::nullopt;
|
||||
}
|
||||
OuterFramePop fr;
|
||||
fr.kind = static_cast<FrameKind>(readU16BE(d + 6));
|
||||
fr.payload = QByteArray(buffer.constData() + static_cast<int>(kOuterHeaderBytes), static_cast<int>(payloadLen));
|
||||
fr.consumedBytes = total;
|
||||
buffer.remove(0, total);
|
||||
return fr;
|
||||
}
|
||||
|
||||
bool decodeDataRawBusPayload(const QByteArray& payload,
|
||||
softbus::core::models::RawBusMessage& ctx,
|
||||
QByteArray& busPayloadOut,
|
||||
QString* errorMessage)
|
||||
{
|
||||
if (payload.size() < static_cast<int>(kDataRawBusFixedBytes)) {
|
||||
if (errorMessage) {
|
||||
*errorMessage = QStringLiteral("data_rawbus_too_short");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
const unsigned char* p = reinterpret_cast<const unsigned char*>(payload.constData());
|
||||
const std::uint16_t traceLen = readU16BE(p + 0);
|
||||
if (traceLen > static_cast<std::uint16_t>(kMaxTraceLen)) {
|
||||
if (errorMessage) {
|
||||
*errorMessage = QStringLiteral("trace_too_long");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
const int need = static_cast<int>(kDataRawBusFixedBytes) + static_cast<int>(traceLen);
|
||||
if (payload.size() < need) {
|
||||
if (errorMessage) {
|
||||
*errorMessage = QStringLiteral("data_rawbus_truncated_trace");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
ctx = {};
|
||||
ctx.deviceId = readI32BE(p + 2);
|
||||
ctx.endpointHash = readU32BE(p + 6);
|
||||
ctx.protocol = static_cast<softbus::core::models::ProtocolType>(readU16BE(p + 10));
|
||||
ctx.logicalAddress = readU32BE(p + 14);
|
||||
ctx.routingKey = readU64BE(p + 18);
|
||||
std::memcpy(&ctx.header, p + 26, 8);
|
||||
ctx.timestampMs = readI64BE(p + 34);
|
||||
ctx.direction = static_cast<softbus::core::models::BusDirection>(static_cast<signed char>(p[42]));
|
||||
if (traceLen > 0) {
|
||||
ctx.traceId = QString::fromUtf8(reinterpret_cast<const char*>(p + 48), static_cast<int>(traceLen));
|
||||
}
|
||||
busPayloadOut = payload.mid(need);
|
||||
ctx.payload = {};
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace softbus::message_bus::ingress::edge_uplink
|
||||
65
src/message_bus/ingress/EdgeUplinkWire.h
Normal file
65
src/message_bus/ingress/EdgeUplinkWire.h
Normal file
@@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
|
||||
#include "core/models/RawBusMessage.h"
|
||||
|
||||
namespace softbus::message_bus::ingress::edge_uplink
|
||||
{
|
||||
|
||||
inline constexpr char kMagic0 = 'S';
|
||||
inline constexpr char kMagic1 = 'B';
|
||||
inline constexpr char kMagic2 = 'U';
|
||||
inline constexpr char kMagic3 = 'P';
|
||||
inline constexpr std::uint16_t kWireVersion = 1;
|
||||
|
||||
enum class FrameKind : std::uint16_t
|
||||
{
|
||||
Hello = 1,
|
||||
Ack = 2,
|
||||
DataRawBus = 3,
|
||||
TreeFull = 4,
|
||||
TreePatch = 5,
|
||||
TreePropose = 6,
|
||||
TreeResult = 7,
|
||||
Report = 8,
|
||||
};
|
||||
|
||||
inline constexpr std::size_t kOuterHeaderBytes = 16;
|
||||
inline constexpr std::size_t kDataRawBusFixedBytes = 48;
|
||||
inline constexpr int kMaxTraceLen = 512;
|
||||
|
||||
struct OuterFramePop
|
||||
{
|
||||
FrameKind kind{FrameKind::Hello};
|
||||
QByteArray payload;
|
||||
int consumedBytes{0};
|
||||
};
|
||||
|
||||
std::optional<OuterFramePop> tryPopOneOuterFrame(QByteArray& buffer, int maxPayloadBytes = 4 * 1024 * 1024);
|
||||
|
||||
QByteArray encodeOuterFrame(FrameKind kind, const QByteArray& payload);
|
||||
|
||||
/** 与核心 `DATA_RAWBUS` 内层二进制布局一致(边缘上行编码)。 */
|
||||
QByteArray encodeDataRawBusPayload(int deviceId,
|
||||
std::uint32_t endpointHash,
|
||||
std::uint16_t protocol,
|
||||
std::uint32_t logicalAddress,
|
||||
std::uint64_t routingKey,
|
||||
std::uint64_t protocolHeaderRaw,
|
||||
qint64 timestampMs,
|
||||
std::int8_t direction,
|
||||
const QString& traceId,
|
||||
const QByteArray& busPayload);
|
||||
|
||||
// 填充除 payload.block 外的标量与 trace;busPayload 为总线原始字节,由调用方 allocate 后写入 ctx.payload
|
||||
bool decodeDataRawBusPayload(const QByteArray& payload,
|
||||
softbus::core::models::RawBusMessage& ctx,
|
||||
QByteArray& busPayloadOut,
|
||||
QString* errorMessage);
|
||||
|
||||
} // namespace softbus::message_bus::ingress::edge_uplink
|
||||
19
src/message_bus/ingress/IIngressPort.h
Normal file
19
src/message_bus/ingress/IIngressPort.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "core/models/RawBusMessage.h"
|
||||
|
||||
namespace softbus::message_bus::ingress
|
||||
{
|
||||
|
||||
class IIngressPort
|
||||
{
|
||||
public:
|
||||
virtual ~IIngressPort() = default;
|
||||
|
||||
virtual bool push(softbus::core::models::RawBusMessage ctx) = 0;
|
||||
virtual void reportError(const QString& endpoint, const QString& error) = 0;
|
||||
};
|
||||
|
||||
} // namespace softbus::message_bus::ingress
|
||||
374
src/ops_ui_http.cpp
Normal file
374
src/ops_ui_http.cpp
Normal file
@@ -0,0 +1,374 @@
|
||||
#include "ops_ui_http.hpp"
|
||||
|
||||
#include <QFile>
|
||||
#include <QHostAddress>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonParseError>
|
||||
#include <QTcpServer>
|
||||
#include <QTcpSocket>
|
||||
#include <QUrlQuery>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
QString htmlEscape(const QString& s)
|
||||
{
|
||||
QString o;
|
||||
o.reserve(s.size() * 2);
|
||||
for (QChar c : s) {
|
||||
switch (c.unicode()) {
|
||||
case '&':
|
||||
o += QStringLiteral("&");
|
||||
break;
|
||||
case '<':
|
||||
o += QStringLiteral("<");
|
||||
break;
|
||||
case '>':
|
||||
o += QStringLiteral(">");
|
||||
break;
|
||||
case '"':
|
||||
o += QStringLiteral(""");
|
||||
break;
|
||||
default:
|
||||
o += c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
QByteArray buildUiPage(const QString& edgeId, const QJsonArray& discovered)
|
||||
{
|
||||
QString options = QStringLiteral("<option value=\"\">— 请选择已发现端口 —</option>\n");
|
||||
for (const QJsonValue& v : discovered) {
|
||||
if (!v.isObject()) {
|
||||
continue;
|
||||
}
|
||||
const QJsonObject o = v.toObject();
|
||||
const QString nid = o.value(QStringLiteral("nodeId")).toString();
|
||||
const QString dev = o.value(QStringLiteral("devnode")).toString();
|
||||
if (nid.isEmpty() || dev.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
options += QStringLiteral("<option value=\"%1\">%2 — %3</option>\n")
|
||||
.arg(htmlEscape(nid), htmlEscape(dev), htmlEscape(nid));
|
||||
}
|
||||
if (discovered.isEmpty()) {
|
||||
options = QStringLiteral("<option value=\"\">(暂无发现记录,可先插拔串口或关闭后再试)</option>\n");
|
||||
}
|
||||
|
||||
const QString html = QStringLiteral(
|
||||
"<!doctype html><html lang=zh-CN><head><meta charset=utf-8>"
|
||||
"<meta name=viewport content=\"width=device-width,initial-scale=1\">"
|
||||
"<title>边缘运维 — 发现端口注册</title>"
|
||||
"<style>body{font-family:system-ui,sans-serif;max-width:42rem;margin:1rem auto;padding:0 .75rem}"
|
||||
"label{display:block;margin:.6rem 0 .2rem}input,select,button{width:100%;box-sizing:border-box;padding:.4rem}"
|
||||
"button{margin-top:1rem;cursor:pointer}code{background:#f4f4f4;padding:.1rem .3rem}</style></head><body>"
|
||||
"<h1>发现端口 → 核心注册</h1>"
|
||||
"<p>边缘 ID:<code>%1</code>。从 udev 最近发现列表中选端口,填写协议字符串(与设备树 <code>params.protocol</code> 一致,如 <code>modbus_rtu</code>),提交后将向核心发送一条 <code>TREE_PROPOSE</code>(<code>op: upsert</code>)。需已连接核心且收到有效 <code>treeRevision</code>。</p>"
|
||||
"<form method=post action=/register_discovered enctype=application/x-www-form-urlencoded>"
|
||||
"<label for=nodeId>已发现节点</label><select id=nodeId name=nodeId required>%2</select>"
|
||||
"<label for=devnode>设备节点(可选,未在下拉匹配时填写 /dev/tty…)</label>"
|
||||
"<input id=devnode name=devnode type=text placeholder=/dev/ttyUSB0>"
|
||||
"<label for=protocol>协议占位(params.protocol)</label>"
|
||||
"<input id=protocol name=protocol type=text value=modbus_rtu required>"
|
||||
"<label for=baudRate>波特率</label>"
|
||||
"<input id=baudRate name=baudRate type=number value=115200 min=300 step=1>"
|
||||
"<label for=name>显示名称(可选)</label>"
|
||||
"<input id=name name=name type=text placeholder=\"例如 电表\">"
|
||||
"<button type=submit>发送 upsert PROPOSE</button></form>"
|
||||
"<p><a href=/>本地树 JSON</a> · <a href=/discovered>发现 JSON</a> · <code>POST /propose</code> 仍可提交完整 JSON。</p>"
|
||||
"</body></html>")
|
||||
.arg(htmlEscape(edgeId), options);
|
||||
return html.toUtf8();
|
||||
}
|
||||
|
||||
QString devnodeForNodeId(const QJsonArray& discovered, const QString& nodeId)
|
||||
{
|
||||
for (const QJsonValue& v : discovered) {
|
||||
if (!v.isObject()) {
|
||||
continue;
|
||||
}
|
||||
const QJsonObject o = v.toObject();
|
||||
if (o.value(QStringLiteral("nodeId")).toString() == nodeId) {
|
||||
return o.value(QStringLiteral("devnode")).toString();
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
QString stableKeyFromNodeId(const QString& nodeId)
|
||||
{
|
||||
const QString prefix = QStringLiteral("/discovered/");
|
||||
const int i = nodeId.indexOf(prefix);
|
||||
if (i < 0) {
|
||||
return {};
|
||||
}
|
||||
return nodeId.mid(i + prefix.size());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
OpsUiHttp::OpsUiHttp(QObject* parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
OpsUiHttp::~OpsUiHttp()
|
||||
{
|
||||
if (m_tcp) {
|
||||
m_tcp->close();
|
||||
m_tcp->deleteLater();
|
||||
m_tcp = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool OpsUiHttp::start(quint16 port,
|
||||
const QString& localTreePath,
|
||||
const QString& edgeId,
|
||||
const std::function<qint64()>& getBaseTreeRevision,
|
||||
const std::function<void(const QJsonObject&)>& onPropose,
|
||||
const std::function<QJsonArray()>& getDiscovered)
|
||||
{
|
||||
m_treePath = localTreePath;
|
||||
m_edgeId = edgeId;
|
||||
m_getBaseTreeRevision = getBaseTreeRevision;
|
||||
m_onPropose = onPropose;
|
||||
m_getDiscovered = getDiscovered;
|
||||
m_tcp = new QTcpServer(this);
|
||||
connect(m_tcp, &QTcpServer::newConnection, this, &OpsUiHttp::onNewConnection);
|
||||
if (!m_tcp->listen(QHostAddress::LocalHost, port)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void OpsUiHttp::onNewConnection()
|
||||
{
|
||||
while (QTcpSocket* sock = m_tcp->nextPendingConnection()) {
|
||||
QByteArray acc;
|
||||
if (!sock->waitForReadyRead(8000)) {
|
||||
sock->deleteLater();
|
||||
continue;
|
||||
}
|
||||
acc += sock->readAll();
|
||||
while (!acc.contains("\r\n\r\n") && sock->waitForReadyRead(500)) {
|
||||
acc += sock->readAll();
|
||||
}
|
||||
handleSocket(sock, acc);
|
||||
sock->deleteLater();
|
||||
}
|
||||
}
|
||||
|
||||
static void writeHttp(QTcpSocket* sock, int code, const QByteArray& body, const char* contentType)
|
||||
{
|
||||
QByteArray statusLine = QByteArrayLiteral("HTTP/1.1 ");
|
||||
statusLine += QByteArray::number(code);
|
||||
if (code == 200) {
|
||||
statusLine += QByteArrayLiteral(" OK");
|
||||
} else if (code == 400) {
|
||||
statusLine += QByteArrayLiteral(" Bad Request");
|
||||
} else if (code == 404) {
|
||||
statusLine += QByteArrayLiteral(" Not Found");
|
||||
} else if (code == 503) {
|
||||
statusLine += QByteArrayLiteral(" Service Unavailable");
|
||||
} else {
|
||||
statusLine += QByteArrayLiteral(" Error");
|
||||
}
|
||||
statusLine += QByteArrayLiteral("\r\nContent-Type: ");
|
||||
statusLine += QByteArray(contentType);
|
||||
statusLine += QByteArrayLiteral("\r\nContent-Length: ");
|
||||
statusLine += QByteArray::number(body.size());
|
||||
statusLine += QByteArrayLiteral("\r\nConnection: close\r\n\r\n");
|
||||
(void)sock->write(statusLine);
|
||||
(void)sock->write(body);
|
||||
sock->disconnectFromHost();
|
||||
}
|
||||
|
||||
void OpsUiHttp::handleSocket(QTcpSocket* sock, const QByteArray& data)
|
||||
{
|
||||
const int sep = data.indexOf("\r\n\r\n");
|
||||
if (sep < 0) {
|
||||
writeHttp(sock, 400, QByteArrayLiteral("bad request"), "text/plain; charset=utf-8");
|
||||
return;
|
||||
}
|
||||
const QByteArray headers = data.left(sep);
|
||||
const QByteArray body = data.mid(sep + 4);
|
||||
const int firstLineEnd = headers.indexOf("\r\n");
|
||||
if (firstLineEnd < 0) {
|
||||
writeHttp(sock, 400, QByteArrayLiteral("bad request line"), "text/plain; charset=utf-8");
|
||||
return;
|
||||
}
|
||||
const QByteArray reqLine = headers.left(firstLineEnd);
|
||||
const QList<QByteArray> parts = reqLine.split(' ');
|
||||
if (parts.size() < 2) {
|
||||
writeHttp(sock, 400, QByteArrayLiteral("bad request line"), "text/plain; charset=utf-8");
|
||||
return;
|
||||
}
|
||||
const QByteArray method = parts[0];
|
||||
const QByteArray path = parts[1];
|
||||
|
||||
if (method == "GET" && path == "/") {
|
||||
QFile f(m_treePath);
|
||||
if (!f.open(QIODevice::ReadOnly)) {
|
||||
const QByteArray html =
|
||||
QByteArrayLiteral("<!doctype html><meta charset=utf-8><title>edge</title>"
|
||||
"<p>无本地树文件。</p><p><a href=/ui>打开配置页</a> · <a href=/discovered>发现 JSON</a></p>");
|
||||
writeHttp(sock, 200, html, "text/html; charset=utf-8");
|
||||
return;
|
||||
}
|
||||
const QByteArray fileBody = f.readAll();
|
||||
writeHttp(sock, 200, fileBody, "application/json; charset=utf-8");
|
||||
return;
|
||||
}
|
||||
|
||||
if (method == "GET" && path == "/discovered") {
|
||||
QJsonArray arr;
|
||||
if (m_getDiscovered) {
|
||||
arr = m_getDiscovered();
|
||||
}
|
||||
const QByteArray out =
|
||||
QJsonDocument(QJsonObject{{QStringLiteral("items"), arr}}).toJson(QJsonDocument::Indented);
|
||||
writeHttp(sock, 200, out, "application/json; charset=utf-8");
|
||||
return;
|
||||
}
|
||||
|
||||
if (method == "GET" && path == "/ui") {
|
||||
QJsonArray arr;
|
||||
if (m_getDiscovered) {
|
||||
arr = m_getDiscovered();
|
||||
}
|
||||
writeHttp(sock, 200, buildUiPage(m_edgeId, arr), "text/html; charset=utf-8");
|
||||
return;
|
||||
}
|
||||
|
||||
if (method == "POST" && path == "/propose") {
|
||||
QJsonParseError pe;
|
||||
const QJsonDocument doc = QJsonDocument::fromJson(body, &pe);
|
||||
if (pe.error != QJsonParseError::NoError || !doc.isObject()) {
|
||||
writeHttp(sock, 400, QByteArrayLiteral("invalid json"), "text/plain; charset=utf-8");
|
||||
return;
|
||||
}
|
||||
if (m_onPropose) {
|
||||
m_onPropose(doc.object());
|
||||
}
|
||||
const QJsonObject ok{{QStringLiteral("accepted"), true}};
|
||||
writeHttp(sock, 200, QJsonDocument(ok).toJson(QJsonDocument::Compact), "application/json; charset=utf-8");
|
||||
return;
|
||||
}
|
||||
|
||||
if (method == "POST" && path == "/register_discovered") {
|
||||
QString nodeId;
|
||||
QString devnodeIn;
|
||||
QString protocolStr = QStringLiteral("modbus_rtu");
|
||||
int baudRate = 115200;
|
||||
QString nameOpt;
|
||||
|
||||
const QByteArray trimmed = body.trimmed();
|
||||
if (trimmed.startsWith('{')) {
|
||||
QJsonParseError pe;
|
||||
const QJsonDocument doc = QJsonDocument::fromJson(body, &pe);
|
||||
if (pe.error != QJsonParseError::NoError || !doc.isObject()) {
|
||||
writeHttp(sock, 400, QByteArrayLiteral("invalid json"), "text/plain; charset=utf-8");
|
||||
return;
|
||||
}
|
||||
const QJsonObject o = doc.object();
|
||||
nodeId = o.value(QStringLiteral("nodeId")).toString();
|
||||
devnodeIn = o.value(QStringLiteral("devnode")).toString();
|
||||
protocolStr = o.value(QStringLiteral("protocol")).toString(protocolStr);
|
||||
baudRate = o.value(QStringLiteral("baudRate")).toInt(baudRate);
|
||||
nameOpt = o.value(QStringLiteral("name")).toString();
|
||||
} else {
|
||||
QUrlQuery q(QString::fromUtf8(body));
|
||||
nodeId = q.queryItemValue(QStringLiteral("nodeId"));
|
||||
devnodeIn = q.queryItemValue(QStringLiteral("devnode"));
|
||||
protocolStr = q.queryItemValue(QStringLiteral("protocol"), QUrl::FullyDecoded);
|
||||
if (protocolStr.isEmpty()) {
|
||||
protocolStr = QStringLiteral("modbus_rtu");
|
||||
}
|
||||
bool okBr = false;
|
||||
baudRate = q.queryItemValue(QStringLiteral("baudRate")).toInt(&okBr);
|
||||
if (!okBr || baudRate <= 0) {
|
||||
baudRate = 115200;
|
||||
}
|
||||
nameOpt = q.queryItemValue(QStringLiteral("name"), QUrl::FullyDecoded);
|
||||
}
|
||||
|
||||
if (nodeId.isEmpty()) {
|
||||
writeHttp(sock, 400, QByteArrayLiteral("missing nodeId"), "text/plain; charset=utf-8");
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonArray discovered;
|
||||
if (m_getDiscovered) {
|
||||
discovered = m_getDiscovered();
|
||||
}
|
||||
QString devnode = devnodeIn.trimmed();
|
||||
if (devnode.isEmpty()) {
|
||||
devnode = devnodeForNodeId(discovered, nodeId);
|
||||
}
|
||||
if (devnode.isEmpty()) {
|
||||
writeHttp(sock, 400, QByteArrayLiteral("missing devnode (not in /discovered; fill devnode field)"),
|
||||
"text/plain; charset=utf-8");
|
||||
return;
|
||||
}
|
||||
|
||||
const qint64 baseRev = m_getBaseTreeRevision ? m_getBaseTreeRevision() : 0;
|
||||
if (baseRev <= 0) {
|
||||
const QJsonObject err{{QStringLiteral("error"), QStringLiteral("baseTreeRevision unavailable")}};
|
||||
writeHttp(sock, 503, QJsonDocument(err).toJson(QJsonDocument::Compact), "application/json; charset=utf-8");
|
||||
return;
|
||||
}
|
||||
|
||||
const QString sk = stableKeyFromNodeId(nodeId);
|
||||
QJsonObject params;
|
||||
params.insert(QStringLiteral("edgeId"), m_edgeId);
|
||||
if (!sk.isEmpty()) {
|
||||
params.insert(QStringLiteral("stableKey"), sk);
|
||||
}
|
||||
params.insert(QStringLiteral("online"), true);
|
||||
params.insert(QStringLiteral("discoveredVia"), QStringLiteral("ops_ui"));
|
||||
params.insert(QStringLiteral("protocol"), protocolStr);
|
||||
params.insert(QStringLiteral("baudRate"), baudRate);
|
||||
|
||||
QJsonObject node;
|
||||
node.insert(QStringLiteral("id"), nodeId);
|
||||
node.insert(QStringLiteral("name"),
|
||||
nameOpt.isEmpty() ? QStringLiteral("serial %1").arg(devnode) : nameOpt);
|
||||
node.insert(QStringLiteral("type"), QStringLiteral("serial"));
|
||||
node.insert(QStringLiteral("endpoint"), devnode);
|
||||
node.insert(QStringLiteral("enabled"), true);
|
||||
node.insert(QStringLiteral("params"), params);
|
||||
|
||||
QJsonObject entry;
|
||||
entry.insert(QStringLiteral("op"), QStringLiteral("upsert"));
|
||||
entry.insert(QStringLiteral("id"), nodeId);
|
||||
entry.insert(QStringLiteral("node"), node);
|
||||
|
||||
QJsonObject propose;
|
||||
propose.insert(QStringLiteral("edgeId"), m_edgeId);
|
||||
propose.insert(QStringLiteral("baseTreeRevision"), baseRev);
|
||||
propose.insert(QStringLiteral("entries"), QJsonArray{entry});
|
||||
|
||||
if (m_onPropose) {
|
||||
m_onPropose(propose);
|
||||
}
|
||||
|
||||
const bool formSubmit = !trimmed.startsWith('{');
|
||||
if (formSubmit) {
|
||||
const QByteArray html =
|
||||
QByteArrayLiteral("<!doctype html><meta charset=utf-8><title>已提交</title>"
|
||||
"<p>已向核心发送 upsert <code>TREE_PROPOSE</code>。请查看边缘日志与核心 <code>TREE_RESULT</code>。</p>"
|
||||
"<p><a href=/ui>返回配置页</a> · <a href=/discovered>发现 JSON</a></p>");
|
||||
writeHttp(sock, 200, html, "text/html; charset=utf-8");
|
||||
return;
|
||||
}
|
||||
const QJsonObject ok{{QStringLiteral("accepted"), true}, {QStringLiteral("sentNodeId"), nodeId}};
|
||||
writeHttp(sock, 200, QJsonDocument(ok).toJson(QJsonDocument::Compact), "application/json; charset=utf-8");
|
||||
return;
|
||||
}
|
||||
|
||||
writeHttp(sock, 404, QByteArrayLiteral("not found"), "text/plain; charset=utf-8");
|
||||
}
|
||||
39
src/ops_ui_http.hpp
Normal file
39
src/ops_ui_http.hpp
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
class QTcpServer;
|
||||
class QTcpSocket;
|
||||
|
||||
class OpsUiHttp final : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit OpsUiHttp(QObject* parent = nullptr);
|
||||
~OpsUiHttp() override;
|
||||
|
||||
bool start(quint16 port,
|
||||
const QString& localTreePath,
|
||||
const QString& edgeId,
|
||||
const std::function<qint64()>& getBaseTreeRevision,
|
||||
const std::function<void(const QJsonObject&)>& onPropose,
|
||||
const std::function<QJsonArray()>& getDiscovered = {});
|
||||
|
||||
private slots:
|
||||
void onNewConnection();
|
||||
|
||||
private:
|
||||
void handleSocket(QTcpSocket* sock, const QByteArray& data);
|
||||
|
||||
QTcpServer* m_tcp{nullptr};
|
||||
QString m_treePath;
|
||||
QString m_edgeId;
|
||||
std::function<qint64()> m_getBaseTreeRevision;
|
||||
std::function<void(const QJsonObject&)> m_onPropose;
|
||||
std::function<QJsonArray()> m_getDiscovered;
|
||||
};
|
||||
36
src/utils/logs/logging.h
Normal file
36
src/utils/logs/logging.h
Normal file
@@ -0,0 +1,36 @@
|
||||
#ifndef LOGGING_H
|
||||
#define LOGGING_H
|
||||
|
||||
#include <QDebug>
|
||||
#include <QLoggingCategory>
|
||||
#include <QString>
|
||||
#include <cstring>
|
||||
|
||||
|
||||
/**
|
||||
* @file logging.h
|
||||
* @brief 日志宏定义,提供带文件名和函数名的日志输出,并异步存储到数据库
|
||||
*/
|
||||
|
||||
// 获取文件名(不含路径)
|
||||
#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)
|
||||
|
||||
// 构建来源字符串的辅助宏
|
||||
// #define __LOG_SOURCE__ (QString(__FILENAME__) + "." + QString(__FUNCTION__))
|
||||
|
||||
// 日志宏,格式:[文件名.函数名] 消息
|
||||
|
||||
#define LOG_DEBUG() \
|
||||
qDebug() << "[" << __FILENAME__ << "/" << __FUNCTION__ << "]"
|
||||
|
||||
#define LOG_ERROR() \
|
||||
qCritical() << "[" << __FILENAME__ << "/" << __FUNCTION__ << "]"
|
||||
|
||||
#define LOG_INFO() \
|
||||
qInfo() << "[" << __FILENAME__ << "/" << __FUNCTION__ << "]"
|
||||
|
||||
#define LOG_WARNING() \
|
||||
qWarning() << "[" << __FILENAME__ << "/" << __FUNCTION__ << "]"
|
||||
|
||||
#endif // LOGGING_H
|
||||
|
||||
Reference in New Issue
Block a user