@@ -0,0 +1,38 @@ | |||||
QT += core gui | |||||
QT += serialport | |||||
QT += core | |||||
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets | |||||
CONFIG += c++11 | |||||
# The following define makes your compiler emit warnings if you use | |||||
# any Qt feature that has been marked deprecated (the exact warnings | |||||
# depend on your compiler). Please consult the documentation of the | |||||
# deprecated API in order to know how to port your code away from it. | |||||
DEFINES += QT_DEPRECATED_WARNINGS | |||||
# You can also make your code fail to compile if it uses deprecated APIs. | |||||
# In order to do so, uncomment the following line. | |||||
# You can also select to disable deprecated APIs only up to a certain version of Qt. | |||||
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 | |||||
SOURCES += \ | |||||
main.cpp \ | |||||
mainwindow.cpp \ | |||||
modbus_master.cpp \ | |||||
serial_communication.cpp \ | |||||
timeout_handler.cpp | |||||
HEADERS += \ | |||||
mainwindow.h \ | |||||
modbus_master.h \ | |||||
serial_communication.h \ | |||||
timeout_handler.h | |||||
FORMS += \ | |||||
mainwindow.ui | |||||
# Default rules for deployment. | |||||
qnx: target.path = /tmp/$${TARGET}/bin | |||||
else: unix:!android: target.path = /opt/$${TARGET}/bin | |||||
!isEmpty(target.path): INSTALLS += target |
@@ -0,0 +1,319 @@ | |||||
<?xml version="1.0" encoding="UTF-8"?> | |||||
<!DOCTYPE QtCreatorProject> | |||||
<!-- Written by QtCreator 4.11.1, 2025-08-04T10:01:37. --> | |||||
<qtcreator> | |||||
<data> | |||||
<variable>EnvironmentId</variable> | |||||
<value type="QByteArray">{54b998bd-0b43-4b57-8d79-23e6237f0a57}</value> | |||||
</data> | |||||
<data> | |||||
<variable>ProjectExplorer.Project.ActiveTarget</variable> | |||||
<value type="int">0</value> | |||||
</data> | |||||
<data> | |||||
<variable>ProjectExplorer.Project.EditorSettings</variable> | |||||
<valuemap type="QVariantMap"> | |||||
<value type="bool" key="EditorConfiguration.AutoIndent">true</value> | |||||
<value type="bool" key="EditorConfiguration.AutoSpacesForTabs">false</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="int" 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.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="bool" key="EditorConfiguration.ScrollWheelZooming">true</value> | |||||
<value type="bool" key="EditorConfiguration.ShowMargin">false</value> | |||||
<value type="int" key="EditorConfiguration.SmartBackspaceBehavior">0</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="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="bool" key="EditorConfiguration.inEntireDocument">false</value> | |||||
</valuemap> | |||||
</data> | |||||
<data> | |||||
<variable>ProjectExplorer.Project.PluginSettings</variable> | |||||
<valuemap type="QVariantMap"> | |||||
<valuelist type="QVariantList" key="ClangCodeModel.CustomCommandLineKey"> | |||||
<value type="QString">-fno-delayed-template-parsing</value> | |||||
</valuelist> | |||||
<value type="bool" key="ClangCodeModel.UseGlobalConfig">true</value> | |||||
</valuemap> | |||||
</data> | |||||
<data> | |||||
<variable>ProjectExplorer.Project.Target.0</variable> | |||||
<valuemap type="QVariantMap"> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Desktop Qt 5.14.2 MinGW 64-bit</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Desktop Qt 5.14.2 MinGW 64-bit</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">qt.qt5.5142.win64_mingw73_kit</value> | |||||
<value type="int" key="ProjectExplorer.Target.ActiveBuildConfiguration">0</value> | |||||
<value type="int" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value> | |||||
<value type="int" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value> | |||||
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.0"> | |||||
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">D:/QT/QTProject/build-ModbusMatser-Desktop_Qt_5_14_2_MinGW_64_bit-Debug</value> | |||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0"> | |||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0"> | |||||
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">QtProjectManager.QMakeBuildStep</value> | |||||
<value type="bool" key="QtProjectManager.QMakeBuildStep.LinkQmlDebuggingLibrary">true</value> | |||||
<value type="QString" key="QtProjectManager.QMakeBuildStep.QMakeArguments"></value> | |||||
<value type="bool" key="QtProjectManager.QMakeBuildStep.QMakeForced">false</value> | |||||
<value type="bool" key="QtProjectManager.QMakeBuildStep.SeparateDebugInfo">false</value> | |||||
<value type="bool" key="QtProjectManager.QMakeBuildStep.UseQtQuickCompiler">false</value> | |||||
</valuemap> | |||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.1"> | |||||
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value> | |||||
<valuelist type="QVariantList" key="Qt4ProjectManager.MakeStep.BuildTargets"/> | |||||
<value type="bool" key="Qt4ProjectManager.MakeStep.Clean">false</value> | |||||
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments"></value> | |||||
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeCommand"></value> | |||||
<value type="bool" key="Qt4ProjectManager.MakeStep.OverrideMakeflags">false</value> | |||||
</valuemap> | |||||
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">2</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Build</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Build</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="bool" key="ProjectExplorer.BuildStep.Enabled">true</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value> | |||||
<valuelist type="QVariantList" key="Qt4ProjectManager.MakeStep.BuildTargets"/> | |||||
<value type="bool" key="Qt4ProjectManager.MakeStep.Clean">true</value> | |||||
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments">clean</value> | |||||
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeCommand"></value> | |||||
<value type="bool" key="Qt4ProjectManager.MakeStep.OverrideMakeflags">false</value> | |||||
</valuemap> | |||||
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">1</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Clean</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Clean</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.UserEnvironmentChanges"/> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Debug</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.Qt4BuildConfiguration</value> | |||||
<value type="int" key="Qt4ProjectManager.Qt4BuildConfiguration.BuildConfiguration">2</value> | |||||
</valuemap> | |||||
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.1"> | |||||
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">D:/QT/QTProject/build-ModbusMatser-Desktop_Qt_5_14_2_MinGW_64_bit-Release</value> | |||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0"> | |||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0"> | |||||
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">QtProjectManager.QMakeBuildStep</value> | |||||
<value type="bool" key="QtProjectManager.QMakeBuildStep.LinkQmlDebuggingLibrary">false</value> | |||||
<value type="QString" key="QtProjectManager.QMakeBuildStep.QMakeArguments"></value> | |||||
<value type="bool" key="QtProjectManager.QMakeBuildStep.QMakeForced">false</value> | |||||
<value type="bool" key="QtProjectManager.QMakeBuildStep.SeparateDebugInfo">false</value> | |||||
<value type="bool" key="QtProjectManager.QMakeBuildStep.UseQtQuickCompiler">true</value> | |||||
</valuemap> | |||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.1"> | |||||
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value> | |||||
<valuelist type="QVariantList" key="Qt4ProjectManager.MakeStep.BuildTargets"/> | |||||
<value type="bool" key="Qt4ProjectManager.MakeStep.Clean">false</value> | |||||
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments"></value> | |||||
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeCommand"></value> | |||||
<value type="bool" key="Qt4ProjectManager.MakeStep.OverrideMakeflags">false</value> | |||||
</valuemap> | |||||
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">2</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Build</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Build</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="bool" key="ProjectExplorer.BuildStep.Enabled">true</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value> | |||||
<valuelist type="QVariantList" key="Qt4ProjectManager.MakeStep.BuildTargets"/> | |||||
<value type="bool" key="Qt4ProjectManager.MakeStep.Clean">true</value> | |||||
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments">clean</value> | |||||
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeCommand"></value> | |||||
<value type="bool" key="Qt4ProjectManager.MakeStep.OverrideMakeflags">false</value> | |||||
</valuemap> | |||||
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">1</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Clean</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Clean</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.UserEnvironmentChanges"/> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Release</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.Qt4BuildConfiguration</value> | |||||
<value type="int" key="Qt4ProjectManager.Qt4BuildConfiguration.BuildConfiguration">0</value> | |||||
</valuemap> | |||||
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.2"> | |||||
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">D:/QT/QTProject/build-ModbusMatser-Desktop_Qt_5_14_2_MinGW_64_bit-Profile</value> | |||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0"> | |||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0"> | |||||
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">QtProjectManager.QMakeBuildStep</value> | |||||
<value type="bool" key="QtProjectManager.QMakeBuildStep.LinkQmlDebuggingLibrary">true</value> | |||||
<value type="QString" key="QtProjectManager.QMakeBuildStep.QMakeArguments"></value> | |||||
<value type="bool" key="QtProjectManager.QMakeBuildStep.QMakeForced">false</value> | |||||
<value type="bool" key="QtProjectManager.QMakeBuildStep.SeparateDebugInfo">true</value> | |||||
<value type="bool" key="QtProjectManager.QMakeBuildStep.UseQtQuickCompiler">true</value> | |||||
</valuemap> | |||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.1"> | |||||
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value> | |||||
<valuelist type="QVariantList" key="Qt4ProjectManager.MakeStep.BuildTargets"/> | |||||
<value type="bool" key="Qt4ProjectManager.MakeStep.Clean">false</value> | |||||
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments"></value> | |||||
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeCommand"></value> | |||||
<value type="bool" key="Qt4ProjectManager.MakeStep.OverrideMakeflags">false</value> | |||||
</valuemap> | |||||
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">2</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Build</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Build</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="bool" key="ProjectExplorer.BuildStep.Enabled">true</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value> | |||||
<valuelist type="QVariantList" key="Qt4ProjectManager.MakeStep.BuildTargets"/> | |||||
<value type="bool" key="Qt4ProjectManager.MakeStep.Clean">true</value> | |||||
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments">clean</value> | |||||
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeCommand"></value> | |||||
<value type="bool" key="Qt4ProjectManager.MakeStep.OverrideMakeflags">false</value> | |||||
</valuemap> | |||||
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">1</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Clean</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Clean</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.UserEnvironmentChanges"/> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Profile</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.Qt4BuildConfiguration</value> | |||||
<value type="int" key="Qt4ProjectManager.Qt4BuildConfiguration.BuildConfiguration">0</value> | |||||
</valuemap> | |||||
<value type="int" key="ProjectExplorer.Target.BuildConfigurationCount">3</value> | |||||
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0"> | |||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0"> | |||||
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">0</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Deploy</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Deploy</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value> | |||||
</valuemap> | |||||
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.DefaultDeployConfiguration</value> | |||||
</valuemap> | |||||
<value type="int" key="ProjectExplorer.Target.DeployConfigurationCount">1</value> | |||||
<valuemap type="QVariantMap" key="ProjectExplorer.Target.PluginSettings"/> | |||||
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.0"> | |||||
<value type="QString" key="Analyzer.Perf.CallgraphMode">dwarf</value> | |||||
<valuelist type="QVariantList" key="Analyzer.Perf.Events"> | |||||
<value type="QString">cpu-cycles</value> | |||||
</valuelist> | |||||
<valuelist type="QVariantList" key="Analyzer.Perf.ExtraArguments"/> | |||||
<value type="int" key="Analyzer.Perf.Frequency">250</value> | |||||
<valuelist type="QVariantList" key="Analyzer.Perf.RecordArguments"> | |||||
<value type="QString">-e</value> | |||||
<value type="QString">cpu-cycles</value> | |||||
<value type="QString">--call-graph</value> | |||||
<value type="QString">dwarf,4096</value> | |||||
<value type="QString">-F</value> | |||||
<value type="QString">250</value> | |||||
</valuelist> | |||||
<value type="QString" key="Analyzer.Perf.SampleMode">-F</value> | |||||
<value type="bool" key="Analyzer.Perf.Settings.UseGlobalSettings">true</value> | |||||
<value type="int" key="Analyzer.Perf.StackSize">4096</value> | |||||
<value type="bool" key="Analyzer.QmlProfiler.AggregateTraces">false</value> | |||||
<value type="bool" key="Analyzer.QmlProfiler.FlushEnabled">false</value> | |||||
<value type="uint" key="Analyzer.QmlProfiler.FlushInterval">1000</value> | |||||
<value type="QString" key="Analyzer.QmlProfiler.LastTraceFile"></value> | |||||
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value> | |||||
<valuelist type="QVariantList" key="Analyzer.Valgrind.AddedSuppressionFiles"/> | |||||
<value type="bool" key="Analyzer.Valgrind.Callgrind.CollectBusEvents">false</value> | |||||
<value type="bool" key="Analyzer.Valgrind.Callgrind.CollectSystime">false</value> | |||||
<value type="bool" key="Analyzer.Valgrind.Callgrind.EnableBranchSim">false</value> | |||||
<value type="bool" key="Analyzer.Valgrind.Callgrind.EnableCacheSim">false</value> | |||||
<value type="bool" key="Analyzer.Valgrind.Callgrind.EnableEventToolTips">true</value> | |||||
<value type="double" key="Analyzer.Valgrind.Callgrind.MinimumCostRatio">0.01</value> | |||||
<value type="double" key="Analyzer.Valgrind.Callgrind.VisualisationMinimumCostRatio">10</value> | |||||
<value type="bool" key="Analyzer.Valgrind.FilterExternalIssues">true</value> | |||||
<value type="QString" key="Analyzer.Valgrind.KCachegrindExecutable">kcachegrind</value> | |||||
<value type="int" key="Analyzer.Valgrind.LeakCheckOnFinish">1</value> | |||||
<value type="int" key="Analyzer.Valgrind.NumCallers">25</value> | |||||
<valuelist type="QVariantList" key="Analyzer.Valgrind.RemovedSuppressionFiles"/> | |||||
<value type="int" key="Analyzer.Valgrind.SelfModifyingCodeDetection">1</value> | |||||
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value> | |||||
<value type="bool" key="Analyzer.Valgrind.ShowReachable">false</value> | |||||
<value type="bool" key="Analyzer.Valgrind.TrackOrigins">true</value> | |||||
<value type="QString" key="Analyzer.Valgrind.ValgrindExecutable">valgrind</value> | |||||
<valuelist type="QVariantList" key="Analyzer.Valgrind.VisibleErrorKinds"> | |||||
<value type="int">0</value> | |||||
<value type="int">1</value> | |||||
<value type="int">2</value> | |||||
<value type="int">3</value> | |||||
<value type="int">4</value> | |||||
<value type="int">5</value> | |||||
<value type="int">6</value> | |||||
<value type="int">7</value> | |||||
<value type="int">8</value> | |||||
<value type="int">9</value> | |||||
<value type="int">10</value> | |||||
<value type="int">11</value> | |||||
<value type="int">12</value> | |||||
<value type="int">13</value> | |||||
<value type="int">14</value> | |||||
</valuelist> | |||||
<value type="int" key="PE.EnvironmentAspect.Base">2</value> | |||||
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.Qt4RunConfiguration:D:/QT/QTProject/ModbusMatser/ModbusMatser.pro</value> | |||||
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey">D:/QT/QTProject/ModbusMatser/ModbusMatser.pro</value> | |||||
<value type="QString" key="RunConfiguration.Arguments"></value> | |||||
<value type="bool" key="RunConfiguration.Arguments.multi">false</value> | |||||
<value type="QString" key="RunConfiguration.OverrideDebuggerStartup"></value> | |||||
<value type="bool" key="RunConfiguration.UseCppDebugger">false</value> | |||||
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value> | |||||
<value type="bool" key="RunConfiguration.UseLibrarySearchPath">true</value> | |||||
<value type="bool" key="RunConfiguration.UseMultiProcess">false</value> | |||||
<value type="bool" key="RunConfiguration.UseQmlDebugger">false</value> | |||||
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value> | |||||
<value type="QString" key="RunConfiguration.WorkingDirectory"></value> | |||||
<value type="QString" key="RunConfiguration.WorkingDirectory.default">D:/QT/QTProject/build-ModbusMatser-Desktop_Qt_5_14_2_MinGW_64_bit-Debug</value> | |||||
</valuemap> | |||||
<value type="int" key="ProjectExplorer.Target.RunConfigurationCount">1</value> | |||||
</valuemap> | |||||
</data> | |||||
<data> | |||||
<variable>ProjectExplorer.Project.TargetCount</variable> | |||||
<value type="int">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> |
@@ -0,0 +1,103 @@ | |||||
/*********************************************************************** | |||||
* Copyright (C) 2025-, XINJE Co., Ltd. | |||||
* | |||||
* File Name: mainwindow.h | |||||
* Description: Modbus主站的人机交互界面头文件,包含其成员变量与成员函数声明 | |||||
* Others: | |||||
* Version: v1.0 | |||||
* Author: weikai XINJE | |||||
* Date: 2025-7-30 | |||||
***********************************************************************/ | |||||
#ifndef MAINWINDOW_H | |||||
#define MAINWINDOW_H | |||||
#include <QMainWindow> | |||||
#include <QTimer> | |||||
#include <QDateTime> | |||||
#include "serial_communication.h" | |||||
#include "modbus_master.h" | |||||
namespace Ui | |||||
{ | |||||
class MainWindow; | |||||
} | |||||
const int DEFAULT_TIMEOUT = 1000; //默认超时时间 | |||||
const int DEFAULT_MAXRETRISE = 3; //默认最大重发次数 | |||||
/******************************************************************** | |||||
* Iterates over the contents of a MainWindow. | |||||
*人机交互界面: | |||||
*通过SerialCommunicator实例serialComm实现串口通信 | |||||
*通过ModbusRTUMaster实例modbusMaster实现Modbus请求帧生成与响应帧解析 | |||||
*通过QTimer实例serialPortTimer实现串口下拉框定时刷新 | |||||
***********************************************************************/ | |||||
class MainWindow : public QMainWindow | |||||
{ | |||||
Q_OBJECT | |||||
public: | |||||
explicit MainWindow(QWidget *parent = nullptr); | |||||
~MainWindow(); | |||||
private slots: | |||||
// UI按钮槽函数 | |||||
void onConnectButtonClicked();//连接按钮 | |||||
void onDisconnectButtonClicked();//断开连接按钮 | |||||
void onReadButtonClicked();//读取按钮 | |||||
void onWriteButtonClicked();//写入按钮 | |||||
void onClearLogButtonClicked();//清空日志框 | |||||
void onExportHistoryButtonClicked();//导出通信历史 | |||||
// 串口通信信号处理槽 | |||||
/*********************************************************************** | |||||
* @brief : 串口接收数据到达信号的处理槽函数 | |||||
* @param : data 串口接收到的数据字节流 | |||||
***********************************************************************/ | |||||
void onSerialDataReceived(const QString &data); | |||||
/*********************************************************************** | |||||
* @brief : 串口接收状态变化信号的处理槽函数 | |||||
* @param : 串口status状态变化的描述字符串 | |||||
***********************************************************************/ | |||||
void onSerialStatusChanged(const QString &status); | |||||
/*********************************************************************** | |||||
* @brief : 串口接收错误发生信号的处理槽函数 | |||||
* @param : error 串口错误的描述字符串 | |||||
***********************************************************************/ | |||||
void onSerialErrorOccurred(const QString &error); | |||||
//断开连接信号处理槽 | |||||
void onConnectionDisconnected(); | |||||
// 定时刷新槽函数 | |||||
void onRefreshSerialPorts(); | |||||
private: | |||||
//界面初始化 | |||||
void init(); | |||||
/*********************************************************************** | |||||
* @brief : 日志输出函数 | |||||
* @param : message 日志字符串 | |||||
***********************************************************************/ | |||||
void logToBox(const QString &message); | |||||
/*********************************************************************** | |||||
* @brief : 刷新串口下拉框 | |||||
***********************************************************************/ | |||||
void refreshSerialPorts(); | |||||
Ui::MainWindow *ui_; | |||||
SerialCommunicator *serialComm_; // 串口通信实例 | |||||
ModbusRTUMaster *modbusMaster_;//Modbus协议实例 | |||||
QTimer *serialPortTimer_;//用于定时刷新可用串口下拉栏的定时器 | |||||
}; | |||||
#endif // MAINWINDOW_H | |||||
@@ -0,0 +1,716 @@ | |||||
<?xml version="1.0" encoding="UTF-8"?> | |||||
<ui version="4.0"> | |||||
<class>MainWindow</class> | |||||
<widget class="QMainWindow" name="MainWindow"> | |||||
<property name="geometry"> | |||||
<rect> | |||||
<x>0</x> | |||||
<y>0</y> | |||||
<width>815</width> | |||||
<height>606</height> | |||||
</rect> | |||||
</property> | |||||
<property name="windowTitle"> | |||||
<string>MainWindow</string> | |||||
</property> | |||||
<widget class="QWidget" name="centralwidget"> | |||||
<widget class="QGroupBox" name="groupBox_3"> | |||||
<property name="geometry"> | |||||
<rect> | |||||
<x>10</x> | |||||
<y>240</y> | |||||
<width>381</width> | |||||
<height>311</height> | |||||
</rect> | |||||
</property> | |||||
<property name="font"> | |||||
<font> | |||||
<family>楷体</family> | |||||
<pointsize>12</pointsize> | |||||
</font> | |||||
</property> | |||||
<property name="title"> | |||||
<string/> | |||||
</property> | |||||
<widget class="QLabel" name="label"> | |||||
<property name="geometry"> | |||||
<rect> | |||||
<x>10</x> | |||||
<y>120</y> | |||||
<width>91</width> | |||||
<height>31</height> | |||||
</rect> | |||||
</property> | |||||
<property name="text"> | |||||
<string>起始地址:</string> | |||||
</property> | |||||
</widget> | |||||
<widget class="QLabel" name="label_10"> | |||||
<property name="geometry"> | |||||
<rect> | |||||
<x>190</x> | |||||
<y>110</y> | |||||
<width>91</width> | |||||
<height>41</height> | |||||
</rect> | |||||
</property> | |||||
<property name="font"> | |||||
<font> | |||||
<family>楷体</family> | |||||
<pointsize>12</pointsize> | |||||
</font> | |||||
</property> | |||||
<property name="text"> | |||||
<string>读取数量:</string> | |||||
</property> | |||||
</widget> | |||||
<widget class="QLabel" name="label_4"> | |||||
<property name="geometry"> | |||||
<rect> | |||||
<x>10</x> | |||||
<y>70</y> | |||||
<width>91</width> | |||||
<height>20</height> | |||||
</rect> | |||||
</property> | |||||
<property name="font"> | |||||
<font> | |||||
<family>楷体</family> | |||||
<pointsize>12</pointsize> | |||||
</font> | |||||
</property> | |||||
<property name="text"> | |||||
<string>从站地址:</string> | |||||
</property> | |||||
</widget> | |||||
<widget class="QLabel" name="label_8"> | |||||
<property name="geometry"> | |||||
<rect> | |||||
<x>200</x> | |||||
<y>60</y> | |||||
<width>81</width> | |||||
<height>31</height> | |||||
</rect> | |||||
</property> | |||||
<property name="font"> | |||||
<font> | |||||
<family>楷体</family> | |||||
<pointsize>12</pointsize> | |||||
</font> | |||||
</property> | |||||
<property name="text"> | |||||
<string>功能码:</string> | |||||
</property> | |||||
</widget> | |||||
<widget class="QComboBox" name="functionComboBox"> | |||||
<property name="geometry"> | |||||
<rect> | |||||
<x>270</x> | |||||
<y>60</y> | |||||
<width>101</width> | |||||
<height>31</height> | |||||
</rect> | |||||
</property> | |||||
<property name="font"> | |||||
<font> | |||||
<family>楷体</family> | |||||
<pointsize>11</pointsize> | |||||
</font> | |||||
</property> | |||||
<item> | |||||
<property name="text"> | |||||
<string>01读线圈</string> | |||||
</property> | |||||
</item> | |||||
<item> | |||||
<property name="text"> | |||||
<string>03读寄存器</string> | |||||
</property> | |||||
</item> | |||||
<item> | |||||
<property name="text"> | |||||
<string>0F写线圈</string> | |||||
</property> | |||||
</item> | |||||
<item> | |||||
<property name="text"> | |||||
<string>10写寄存器</string> | |||||
</property> | |||||
</item> | |||||
</widget> | |||||
<widget class="QTextEdit" name="writeTextEdit"> | |||||
<property name="geometry"> | |||||
<rect> | |||||
<x>0</x> | |||||
<y>260</y> | |||||
<width>381</width> | |||||
<height>41</height> | |||||
</rect> | |||||
</property> | |||||
</widget> | |||||
<widget class="QPushButton" name="writeButton"> | |||||
<property name="geometry"> | |||||
<rect> | |||||
<x>260</x> | |||||
<y>180</y> | |||||
<width>93</width> | |||||
<height>41</height> | |||||
</rect> | |||||
</property> | |||||
<property name="text"> | |||||
<string>写入</string> | |||||
</property> | |||||
</widget> | |||||
<widget class="QPushButton" name="readButton"> | |||||
<property name="geometry"> | |||||
<rect> | |||||
<x>90</x> | |||||
<y>180</y> | |||||
<width>93</width> | |||||
<height>41</height> | |||||
</rect> | |||||
</property> | |||||
<property name="text"> | |||||
<string>读取</string> | |||||
</property> | |||||
</widget> | |||||
<widget class="QSpinBox" name="numberSpinBox"> | |||||
<property name="geometry"> | |||||
<rect> | |||||
<x>280</x> | |||||
<y>120</y> | |||||
<width>90</width> | |||||
<height>31</height> | |||||
</rect> | |||||
</property> | |||||
<property name="font"> | |||||
<font> | |||||
<family>黑体</family> | |||||
<pointsize>12</pointsize> | |||||
</font> | |||||
</property> | |||||
<property name="maximum"> | |||||
<number>1000</number> | |||||
</property> | |||||
</widget> | |||||
<widget class="QSpinBox" name="startAddrSpinBox"> | |||||
<property name="geometry"> | |||||
<rect> | |||||
<x>100</x> | |||||
<y>120</y> | |||||
<width>91</width> | |||||
<height>31</height> | |||||
</rect> | |||||
</property> | |||||
<property name="maximum"> | |||||
<number>20000</number> | |||||
</property> | |||||
</widget> | |||||
<widget class="QSpinBox" name="stationAddrSpinBox"> | |||||
<property name="geometry"> | |||||
<rect> | |||||
<x>100</x> | |||||
<y>60</y> | |||||
<width>91</width> | |||||
<height>31</height> | |||||
</rect> | |||||
</property> | |||||
<property name="font"> | |||||
<font> | |||||
<family>黑体</family> | |||||
<pointsize>12</pointsize> | |||||
</font> | |||||
</property> | |||||
<property name="maximum"> | |||||
<number>1000</number> | |||||
</property> | |||||
</widget> | |||||
<widget class="QLabel" name="label_13"> | |||||
<property name="geometry"> | |||||
<rect> | |||||
<x>0</x> | |||||
<y>0</y> | |||||
<width>101</width> | |||||
<height>31</height> | |||||
</rect> | |||||
</property> | |||||
<property name="text"> | |||||
<string>Modbus配置</string> | |||||
</property> | |||||
</widget> | |||||
<widget class="QLabel" name="label_14"> | |||||
<property name="geometry"> | |||||
<rect> | |||||
<x>0</x> | |||||
<y>230</y> | |||||
<width>91</width> | |||||
<height>31</height> | |||||
</rect> | |||||
</property> | |||||
<property name="text"> | |||||
<string>写入数据</string> | |||||
</property> | |||||
</widget> | |||||
</widget> | |||||
<widget class="QGroupBox" name="groupBox_4"> | |||||
<property name="geometry"> | |||||
<rect> | |||||
<x>400</x> | |||||
<y>310</y> | |||||
<width>401</width> | |||||
<height>241</height> | |||||
</rect> | |||||
</property> | |||||
<property name="font"> | |||||
<font> | |||||
<family>楷体</family> | |||||
<pointsize>12</pointsize> | |||||
</font> | |||||
</property> | |||||
<property name="title"> | |||||
<string/> | |||||
</property> | |||||
<widget class="QTextEdit" name="logTextEdit"> | |||||
<property name="geometry"> | |||||
<rect> | |||||
<x>0</x> | |||||
<y>30</y> | |||||
<width>391</width> | |||||
<height>201</height> | |||||
</rect> | |||||
</property> | |||||
<property name="font"> | |||||
<font> | |||||
<family>微软雅黑 Light</family> | |||||
<pointsize>10</pointsize> | |||||
</font> | |||||
</property> | |||||
</widget> | |||||
<widget class="QPushButton" name="clearLogButton"> | |||||
<property name="geometry"> | |||||
<rect> | |||||
<x>300</x> | |||||
<y>0</y> | |||||
<width>93</width> | |||||
<height>31</height> | |||||
</rect> | |||||
</property> | |||||
<property name="text"> | |||||
<string>清空</string> | |||||
</property> | |||||
</widget> | |||||
<widget class="QLabel" name="label_12"> | |||||
<property name="geometry"> | |||||
<rect> | |||||
<x>0</x> | |||||
<y>0</y> | |||||
<width>81</width> | |||||
<height>21</height> | |||||
</rect> | |||||
</property> | |||||
<property name="text"> | |||||
<string>日志框</string> | |||||
</property> | |||||
</widget> | |||||
</widget> | |||||
<widget class="QGroupBox" name="groupBox"> | |||||
<property name="geometry"> | |||||
<rect> | |||||
<x>10</x> | |||||
<y>10</y> | |||||
<width>381</width> | |||||
<height>231</height> | |||||
</rect> | |||||
</property> | |||||
<property name="font"> | |||||
<font> | |||||
<family>楷体</family> | |||||
<pointsize>12</pointsize> | |||||
</font> | |||||
</property> | |||||
<property name="title"> | |||||
<string>通信配置</string> | |||||
</property> | |||||
<widget class="QComboBox" name="verifyComboBox"> | |||||
<property name="geometry"> | |||||
<rect> | |||||
<x>120</x> | |||||
<y>70</y> | |||||
<width>70</width> | |||||
<height>30</height> | |||||
</rect> | |||||
</property> | |||||
</widget> | |||||
<widget class="QComboBox" name="dataComboBox"> | |||||
<property name="geometry"> | |||||
<rect> | |||||
<x>280</x> | |||||
<y>70</y> | |||||
<width>70</width> | |||||
<height>30</height> | |||||
</rect> | |||||
</property> | |||||
<property name="font"> | |||||
<font> | |||||
<family>黑体</family> | |||||
<pointsize>10</pointsize> | |||||
</font> | |||||
</property> | |||||
</widget> | |||||
<widget class="QComboBox" name="stopComboBox"> | |||||
<property name="geometry"> | |||||
<rect> | |||||
<x>280</x> | |||||
<y>110</y> | |||||
<width>70</width> | |||||
<height>30</height> | |||||
</rect> | |||||
</property> | |||||
<property name="font"> | |||||
<font> | |||||
<family>黑体</family> | |||||
<pointsize>10</pointsize> | |||||
</font> | |||||
</property> | |||||
</widget> | |||||
<widget class="QLabel" name="label_5"> | |||||
<property name="geometry"> | |||||
<rect> | |||||
<x>40</x> | |||||
<y>120</y> | |||||
<width>85</width> | |||||
<height>23</height> | |||||
</rect> | |||||
</property> | |||||
<property name="font"> | |||||
<font> | |||||
<family>楷体</family> | |||||
<pointsize>12</pointsize> | |||||
</font> | |||||
</property> | |||||
<property name="text"> | |||||
<string>波特率:</string> | |||||
</property> | |||||
</widget> | |||||
<widget class="QComboBox" name="baudComboBox"> | |||||
<property name="geometry"> | |||||
<rect> | |||||
<x>120</x> | |||||
<y>110</y> | |||||
<width>70</width> | |||||
<height>30</height> | |||||
</rect> | |||||
</property> | |||||
<property name="font"> | |||||
<font> | |||||
<family>黑体</family> | |||||
<pointsize>10</pointsize> | |||||
</font> | |||||
</property> | |||||
<item> | |||||
<property name="text"> | |||||
<string>4800</string> | |||||
</property> | |||||
</item> | |||||
<item> | |||||
<property name="text"> | |||||
<string>9600</string> | |||||
</property> | |||||
</item> | |||||
<item> | |||||
<property name="text"> | |||||
<string>19200</string> | |||||
</property> | |||||
</item> | |||||
<item> | |||||
<property name="text"> | |||||
<string>38400</string> | |||||
</property> | |||||
</item> | |||||
<item> | |||||
<property name="text"> | |||||
<string>115200</string> | |||||
</property> | |||||
</item> | |||||
</widget> | |||||
<widget class="QPushButton" name="disconnectButton"> | |||||
<property name="geometry"> | |||||
<rect> | |||||
<x>260</x> | |||||
<y>190</y> | |||||
<width>93</width> | |||||
<height>31</height> | |||||
</rect> | |||||
</property> | |||||
<property name="text"> | |||||
<string>断开连接</string> | |||||
</property> | |||||
</widget> | |||||
<widget class="QPushButton" name="connectButton"> | |||||
<property name="geometry"> | |||||
<rect> | |||||
<x>110</x> | |||||
<y>190</y> | |||||
<width>93</width> | |||||
<height>31</height> | |||||
</rect> | |||||
</property> | |||||
<property name="text"> | |||||
<string>连接</string> | |||||
</property> | |||||
</widget> | |||||
<widget class="QComboBox" name="serialPortCombox"> | |||||
<property name="geometry"> | |||||
<rect> | |||||
<x>120</x> | |||||
<y>30</y> | |||||
<width>231</width> | |||||
<height>31</height> | |||||
</rect> | |||||
</property> | |||||
<property name="font"> | |||||
<font> | |||||
<family>楷体</family> | |||||
<pointsize>10</pointsize> | |||||
</font> | |||||
</property> | |||||
</widget> | |||||
<widget class="QLabel" name="label_2"> | |||||
<property name="geometry"> | |||||
<rect> | |||||
<x>30</x> | |||||
<y>30</y> | |||||
<width>85</width> | |||||
<height>31</height> | |||||
</rect> | |||||
</property> | |||||
<property name="font"> | |||||
<font> | |||||
<family>楷体</family> | |||||
<pointsize>12</pointsize> | |||||
</font> | |||||
</property> | |||||
<property name="text"> | |||||
<string>通信接口:</string> | |||||
</property> | |||||
</widget> | |||||
<widget class="QLabel" name="label_6"> | |||||
<property name="geometry"> | |||||
<rect> | |||||
<x>210</x> | |||||
<y>70</y> | |||||
<width>68</width> | |||||
<height>23</height> | |||||
</rect> | |||||
</property> | |||||
<property name="font"> | |||||
<font> | |||||
<family>楷体</family> | |||||
<pointsize>12</pointsize> | |||||
</font> | |||||
</property> | |||||
<property name="text"> | |||||
<string>数据位:</string> | |||||
</property> | |||||
</widget> | |||||
<widget class="QLabel" name="label_3"> | |||||
<property name="geometry"> | |||||
<rect> | |||||
<x>30</x> | |||||
<y>80</y> | |||||
<width>85</width> | |||||
<height>23</height> | |||||
</rect> | |||||
</property> | |||||
<property name="font"> | |||||
<font> | |||||
<family>楷体</family> | |||||
<pointsize>12</pointsize> | |||||
</font> | |||||
</property> | |||||
<property name="text"> | |||||
<string>奇偶校验:</string> | |||||
</property> | |||||
</widget> | |||||
<widget class="QLabel" name="label_7"> | |||||
<property name="geometry"> | |||||
<rect> | |||||
<x>210</x> | |||||
<y>110</y> | |||||
<width>68</width> | |||||
<height>23</height> | |||||
</rect> | |||||
</property> | |||||
<property name="font"> | |||||
<font> | |||||
<family>楷体</family> | |||||
<pointsize>12</pointsize> | |||||
</font> | |||||
</property> | |||||
<property name="text"> | |||||
<string>停止位:</string> | |||||
</property> | |||||
</widget> | |||||
<widget class="QLineEdit" name="timeoutEdit"> | |||||
<property name="geometry"> | |||||
<rect> | |||||
<x>120</x> | |||||
<y>150</y> | |||||
<width>71</width> | |||||
<height>21</height> | |||||
</rect> | |||||
</property> | |||||
<property name="font"> | |||||
<font> | |||||
<family>黑体</family> | |||||
<pointsize>9</pointsize> | |||||
</font> | |||||
</property> | |||||
<property name="maxLength"> | |||||
<number>5000</number> | |||||
</property> | |||||
</widget> | |||||
<widget class="QLineEdit" name="reissueEdit"> | |||||
<property name="geometry"> | |||||
<rect> | |||||
<x>280</x> | |||||
<y>150</y> | |||||
<width>71</width> | |||||
<height>21</height> | |||||
</rect> | |||||
</property> | |||||
<property name="font"> | |||||
<font> | |||||
<family>黑体</family> | |||||
<pointsize>9</pointsize> | |||||
</font> | |||||
</property> | |||||
<property name="maxLength"> | |||||
<number>10</number> | |||||
</property> | |||||
</widget> | |||||
<widget class="QLabel" name="label_9"> | |||||
<property name="geometry"> | |||||
<rect> | |||||
<x>0</x> | |||||
<y>150</y> | |||||
<width>121</width> | |||||
<height>23</height> | |||||
</rect> | |||||
</property> | |||||
<property name="font"> | |||||
<font> | |||||
<family>楷体</family> | |||||
<pointsize>12</pointsize> | |||||
</font> | |||||
</property> | |||||
<property name="text"> | |||||
<string>超时时间(ms):</string> | |||||
</property> | |||||
</widget> | |||||
<widget class="QLabel" name="label_11"> | |||||
<property name="geometry"> | |||||
<rect> | |||||
<x>190</x> | |||||
<y>150</y> | |||||
<width>101</width> | |||||
<height>23</height> | |||||
</rect> | |||||
</property> | |||||
<property name="font"> | |||||
<font> | |||||
<family>楷体</family> | |||||
<pointsize>12</pointsize> | |||||
</font> | |||||
</property> | |||||
<property name="text"> | |||||
<string>重发次数:</string> | |||||
</property> | |||||
</widget> | |||||
</widget> | |||||
<widget class="QGroupBox" name="groupBox_2"> | |||||
<property name="geometry"> | |||||
<rect> | |||||
<x>400</x> | |||||
<y>0</y> | |||||
<width>401</width> | |||||
<height>301</height> | |||||
</rect> | |||||
</property> | |||||
<property name="title"> | |||||
<string/> | |||||
</property> | |||||
<widget class="QListWidget" name="historyListWidget"> | |||||
<property name="geometry"> | |||||
<rect> | |||||
<x>0</x> | |||||
<y>40</y> | |||||
<width>391</width> | |||||
<height>261</height> | |||||
</rect> | |||||
</property> | |||||
<property name="font"> | |||||
<font> | |||||
<pointsize>10</pointsize> | |||||
</font> | |||||
</property> | |||||
</widget> | |||||
<widget class="QLabel" name="recv_label"> | |||||
<property name="geometry"> | |||||
<rect> | |||||
<x>0</x> | |||||
<y>0</y> | |||||
<width>81</width> | |||||
<height>31</height> | |||||
</rect> | |||||
</property> | |||||
<property name="font"> | |||||
<font> | |||||
<family>楷体</family> | |||||
<pointsize>12</pointsize> | |||||
</font> | |||||
</property> | |||||
<property name="text"> | |||||
<string>通信历史</string> | |||||
</property> | |||||
</widget> | |||||
<widget class="QPushButton" name="exportHistoryButton"> | |||||
<property name="geometry"> | |||||
<rect> | |||||
<x>300</x> | |||||
<y>10</y> | |||||
<width>93</width> | |||||
<height>31</height> | |||||
</rect> | |||||
</property> | |||||
<property name="text"> | |||||
<string>导出历史</string> | |||||
</property> | |||||
</widget> | |||||
</widget> | |||||
<zorder>groupBox</zorder> | |||||
<zorder>groupBox_3</zorder> | |||||
<zorder>groupBox_4</zorder> | |||||
<zorder>groupBox_2</zorder> | |||||
</widget> | |||||
<widget class="QMenuBar" name="menubar"> | |||||
<property name="geometry"> | |||||
<rect> | |||||
<x>0</x> | |||||
<y>0</y> | |||||
<width>815</width> | |||||
<height>26</height> | |||||
</rect> | |||||
</property> | |||||
<widget class="QMenu" name="menu"> | |||||
<property name="title"> | |||||
<string>主站界面</string> | |||||
</property> | |||||
</widget> | |||||
<addaction name="menu"/> | |||||
</widget> | |||||
<widget class="QStatusBar" name="statusbar"/> | |||||
</widget> | |||||
<resources/> | |||||
<connections/> | |||||
</ui> |
@@ -0,0 +1,178 @@ | |||||
/*********************************************************************** | |||||
* Copyright (C) 2025-, XINJE Co., Ltd. | |||||
* | |||||
* File Name: modbus_master.h | |||||
* Description: Modbus主站的ModbusRTUMaster头文件,主要负责四种请求帧的生成:(01)(03)(0F)(10)、响应帧的解析。 | |||||
* Others: | |||||
* Version: v1.0 | |||||
* Author: weikai XINJE | |||||
* Date: 2025-7-30 | |||||
***********************************************************************/ | |||||
#ifndef MODBUS_RTU_MASTER_H | |||||
#define MODBUS_RTU_MASTER_H | |||||
#include <QObject> | |||||
#include <QByteArray> | |||||
#include <QVector> | |||||
#include <QHash> | |||||
const int TYPE_REGISTERS = 1; | |||||
const int TYPE_COILS = 2; | |||||
/********************************************************************** | |||||
* Iterates over the contents of a ModbusRTUMaster. | |||||
*ModbusRTUMaster: | |||||
*提供Modbus RTU协议的主站功能,支持生成各种Modbus RTU请求帧, | |||||
*以及解析从站返回的响应帧。所有协议相关参数均作为类成员变量,可通过setter方法进行设置。 | |||||
***********************************************************************/ | |||||
class ModbusRTUMaster : public QObject | |||||
{ | |||||
Q_OBJECT | |||||
public: | |||||
//构造函数 | |||||
explicit ModbusRTUMaster(QObject *parent = nullptr); | |||||
//功能码枚举 - 定义Modbus协议支持的功能码 | |||||
enum FunctionCode | |||||
{ | |||||
READ_COILS = 0x01, // 读线圈状态 | |||||
READ_HOLDING_REGISTERS = 0x03, // 读保持寄存器 | |||||
WRITE_MULTIPLE_COILS = 0x0F, // 写多个线圈 | |||||
WRITE_MULTIPLE_REGISTERS = 0x10 // 写多个保持寄存器 | |||||
}; | |||||
//错误码枚举 - 定义Modbus协议可能返回的错误码 | |||||
enum ErrorCode | |||||
{ | |||||
NO_ERROR = 0x00, // 无错误 | |||||
ILLEGAL_FUNCTION = 0x01, // 非法功能 - 从站不支持该功能码 | |||||
ILLEGAL_DATA_ADDRESS = 0x02, // 非法数据地址 - 从站不支持该地址 | |||||
ILLEGAL_DATA_VALUE = 0x03, // 非法数据值 - 数据值超出范围 | |||||
SERVER_DEVICE_FAILURE = 0x04,// 从站设备故障 | |||||
ACKNOWLEDGE = 0x05, // 确认 - 请求已接收但未处理 | |||||
SERVER_DEVICE_BUSY = 0x06, // 从站设备忙 - 无法处理请求 | |||||
MEMORY_PARITY_ERROR = 0x08 // 内存奇偶性错误 | |||||
}; | |||||
//设置从站地址 | |||||
void setSlaveAddr(quint8 slaveAddr) { slaveAddr_ = slaveAddr; } | |||||
//设置功能码 | |||||
void setFuncCode(FunctionCode funcCode) { funcCode_ = funcCode; } | |||||
//设置起始地址 | |||||
void setStartAddr(quint16 startAddr) { startAddr_ = startAddr; } | |||||
//设置读取数量 | |||||
void setReadCount(quint16 readCount) { readCount_ = readCount; } | |||||
//设置线圈数据 | |||||
void setCoils(const QVector<bool>& coils) { coils_ = coils; } | |||||
//设置寄存器数据 | |||||
void setRegisters(const QVector<quint16>& registers) { registers_ = registers; } | |||||
//提取错误码对应描述 | |||||
QString getErrorDescription(int errorCode); | |||||
//获取起始地址 | |||||
quint16 getStartAddr() const; | |||||
//生成读线圈请求帧 (功能码01) | |||||
QByteArray createReadCoilsFrame(); | |||||
//生成读保持寄存器请求帧 (功能码03) | |||||
QByteArray createReadHoldingRegistersFrame(); | |||||
//生成写多个线圈请求帧 (功能码0F) | |||||
QByteArray createWriteMultipleCoilsFrame(); | |||||
//生成写多个保持寄存器请求帧 (功能码10) | |||||
QByteArray createWriteMultipleRegistersFrame(); | |||||
/*********************************************************************** | |||||
*@brief: 处理接收到的数据流并解析完整帧 | |||||
*@param: data 接收到的原始数据 | |||||
*@param: slaveAddr 输出参数,从响应中解析出的从站地址 | |||||
*@param: funcCode 输出参数,从响应中解析出的功能码 | |||||
*@param: parsedData 输出参数,解析后的数据 | |||||
*@param: errorCode 输出参数,错误码(NoError表示成功) | |||||
*@param: extractedFrame 输出参数,提取到的完整帧 | |||||
*@return:是否成功解析 | |||||
*@note: 提取到帧后会进行解析 | |||||
***********************************************************************/ | |||||
bool processReceivedData(const QByteArray& data, quint8& slaveAddr, quint8& funcCode, | |||||
QVector<quint16>& parsedData, quint8& errorCode,QByteArray& extractedFrame,bool& isComFrame); | |||||
public: | |||||
/*********************************************************************** | |||||
*@brief: 创建读请求帧 | |||||
*@param: frame 输出参数 请求帧 | |||||
*@param: type 请求类型 | |||||
***********************************************************************/ | |||||
void buildReadFrame(QByteArray& frame,FunctionCode funcCode); | |||||
/*********************************************************************** | |||||
*@brief: 创建部分写请求帧 | |||||
*@param: frame 输出参数 请求帧 | |||||
*@param: type 请求类型 | |||||
***********************************************************************/ | |||||
void buildWriteFrame(QByteArray& frame,FunctionCode funcCode); | |||||
/*********************************************************************** | |||||
*@brief: 解析读线圈响应数据 | |||||
*@param: responseData 响应中的数据部分 | |||||
*@param: coils 输出参数(bool类型的数组),解析出的线圈状态 | |||||
*@return:是否成功解析 | |||||
***********************************************************************/ | |||||
bool parseReadCoilsResponse(const QByteArray& responseData, QVector<bool>& coils); | |||||
/*********************************************************************** | |||||
*@brief: 解析读保持寄存器响应数据 | |||||
*@param: responseData 响应中的数据部分 | |||||
*@param: registers 输出参数(quint16类型的数组),解析出的寄存器值 | |||||
*@return:是否成功解析 | |||||
***********************************************************************/ | |||||
bool parseReadHoldingRegistersResponse(const QByteArray& responseData, QVector<quint16>& registers); | |||||
/*********************************************************************** | |||||
*@brief: 解析单帧响应 | |||||
*@param: response 从站返回的响应数据 | |||||
*@param: slaveAddr 输出参数,从响应中解析出的从站地址 | |||||
*@param: funcCode 输出参数,从响应中解析出的功能码 | |||||
*@param: data 输出参数,解析后的数据 | |||||
*@param: errorCode 输出参数,错误码(NoError表示成功) | |||||
*@return:是否成功解析 | |||||
***********************************************************************/ | |||||
bool parseResponse(const QByteArray& response, quint8& slaveAddr, quint8& funcCode, | |||||
QVector<quint16>& data, quint8& errorCode); | |||||
/*********************************************************************** | |||||
*@brief: 从缓冲区提取完整帧 | |||||
*@param: frame 输出参数,提取的完整帧 | |||||
*@return:是否成功提取到一帧 | |||||
***********************************************************************/ | |||||
bool extractFrame(QByteArray& frame,bool& isComFrame); | |||||
//计算CRC16校验值 | |||||
quint16 calculateCRC(const QByteArray& data); | |||||
//验证帧的CRC16校验 | |||||
bool verifyCRC(const QByteArray& frame); | |||||
//Modbus RTU协议参数 | |||||
quint8 slaveAddr_; // 从站地址 | |||||
FunctionCode funcCode_; // 功能码 | |||||
quint16 startAddr_; // 起始地址 | |||||
quint16 readCount_; // 读取数量 | |||||
QVector<bool> coils_; // 线圈数据(写入) | |||||
QVector<quint16> registers_; // 寄存器数据(写入) | |||||
//其他 | |||||
QHash<int,QString> errMessage; //错误码描述映射表 | |||||
QByteArray buffer_; // 数据缓冲区 | |||||
}; | |||||
#endif // MODBUS_RTU_MASTER_H |
@@ -0,0 +1,117 @@ | |||||
/*********************************************************************** | |||||
* Copyright (C) 2025-, XINJE Co., Ltd. | |||||
* | |||||
* File Name: serial_communication.h | |||||
* Description: Modbus主站的串口通信头文件 | |||||
* Others: | |||||
* Version: v1.0 | |||||
* Author: weikai XINJE | |||||
* Date: 2025-7-30 | |||||
***********************************************************************/ | |||||
#ifndef SERIALCOMMUNICATOR_H | |||||
#define SERIALCOMMUNICATOR_H | |||||
#include <QObject> | |||||
#include <QSerialPort> | |||||
#include <QSerialPortInfo> | |||||
#include <QByteArray> | |||||
#include <QString> | |||||
#include"timeout_handler.h" | |||||
/********************************************************************** | |||||
* Iterates over the contents of a SerialCommunicator. | |||||
*SerialCommunicator | |||||
*提供串口通信功能,连接,断开连接,发送数据,接收数据处理等 | |||||
*调用TimeoutHandler实例进行超时处理 | |||||
***********************************************************************/ | |||||
class SerialCommunicator : public QObject | |||||
{ | |||||
Q_OBJECT | |||||
public: | |||||
// 构造函数 | |||||
explicit SerialCommunicator(QObject *parent = nullptr); | |||||
~SerialCommunicator(); | |||||
// 参数设置接口 | |||||
void setPortName(const QString &portName); | |||||
void setBaudRate(int baudRate); | |||||
void setDataBits(QSerialPort::DataBits dataBits); | |||||
void setStopBits(QSerialPort::StopBits stopBits); | |||||
void setParity(QSerialPort::Parity parity); | |||||
//设置是否可以开始心跳 | |||||
void setIsCanHeart(bool flag) {isCanHeartbeat_ = flag;} | |||||
void setHeartbeatDFrame(const QByteArray& frame){ heartbeatFrame_ = frame; } | |||||
// 初始化函数(设置默认参数) | |||||
void init(); | |||||
// 连接/断开接口 | |||||
bool connectDevice(); | |||||
void disconnectDevice(); | |||||
// 设置超时参数 | |||||
bool setTimeoutSettings(int timeoutMs, int maxRetries); | |||||
//获取 | |||||
void getTimeoutSettings(int& timeoutMs,int& maxRetries); | |||||
// 发送数据接口 | |||||
bool sendData(const QByteArray &data); | |||||
// 状态查询接口 | |||||
bool isConnected() const; | |||||
//获取可用端口列表 | |||||
QStringList getAvailablePorts(); | |||||
signals: | |||||
// 数据接收信号(发送处理后的十六进制字符串) | |||||
void dataReceived(const QString &hexData); | |||||
// 状态通知信号 | |||||
void statusChanged(const QString &status); | |||||
// 错误通知信号 | |||||
void errorOccurred(const QString &errorMsg); | |||||
// 连接断开信号 | |||||
void connectionDisconnected(); | |||||
private: | |||||
// 内部数据接收处理 | |||||
void onReadyRead(); | |||||
// 处理超时信号 | |||||
void onTimeoutOccurred(int currentRetry); | |||||
// 处理最大重试次数达到 | |||||
void onMaxRetriesReached(); | |||||
//处理串口错误 | |||||
void onSerialError(QSerialPort::SerialPortError error); | |||||
//发送心跳槽函数 | |||||
void onSendHeartbeat(); | |||||
//心跳超时槽函数 | |||||
void onHeartbeatTimeout(); | |||||
//启动心跳机制 | |||||
void startHeartbeat(); | |||||
//停止心跳机制 | |||||
void stopHeartbeat(); | |||||
QSerialPort *serialPort_;// 串口对象 | |||||
// 串口参数 | |||||
QString portName_;//串口名 | |||||
int baudRate_;//波特率 | |||||
QSerialPort::DataBits dataBits_;//数据位 | |||||
QSerialPort::StopBits stopBits_;//停止位 | |||||
QSerialPort::Parity parity_;//奇偶校验 | |||||
bool connected_;// 连接状态 | |||||
TimeoutHandler timeoutHandler_; // 超时处理器 | |||||
QByteArray pendingData_; // 等待响应的数据(用于重发) | |||||
//断线重连 | |||||
QTimer* heartbeatTimer_;//定期发送心跳帧 | |||||
QTimer* heartbeatTimeoutTimer_;//心跳响应计时器 | |||||
int heartbeatInterval_;//心跳间隔 | |||||
int heartbeatTimeout_;//心跳响应时间 | |||||
QByteArray heartbeatFrame_;//心跳帧 | |||||
bool isCanHeartbeat_;//是否可以开始心跳 | |||||
}; | |||||
#endif // SERIALCOMMUNICATOR_H |
@@ -0,0 +1,73 @@ | |||||
/*********************************************************************** | |||||
* Copyright (C) 2025-, XINJE Co., Ltd. | |||||
* | |||||
* File Name: timeout_handler.h | |||||
* Description: Modbus主站的超时处理头文件 | |||||
* Others: | |||||
* Version: v1.0 | |||||
* Author: weikai XINJE | |||||
* Date: 2025-7-30 | |||||
***********************************************************************/ | |||||
#ifndef TIMEOUT_HANDLER_H | |||||
#define TIMEOUT_HANDLER_H | |||||
#include <QObject> | |||||
#include <QTimer> | |||||
/********************************************************************** | |||||
* Iterates over the contents of a TimeoutHandler. | |||||
*TimeoutHandler | |||||
*提供超时处理功能,若超过时间未收到响应,则触发超时重发机制 | |||||
*被SerialCommunicator调用使用 | |||||
***********************************************************************/ | |||||
class TimeoutHandler : public QObject | |||||
{ | |||||
Q_OBJECT | |||||
public: | |||||
explicit TimeoutHandler(QObject *parent = nullptr); | |||||
// 获取当前重试次数 | |||||
int getCurrentRetryCount() const | |||||
{ | |||||
return currentRetryCount_; | |||||
} | |||||
// 设置超时时间(毫秒) | |||||
void setTimeoutInterval(int msec); | |||||
// 获取当前超时时间(毫秒) | |||||
int getTimeoutInterval() const; | |||||
// 设置最大重试次数 | |||||
void setRetryCount(int count); | |||||
// 获取当前设置的重试次数 | |||||
int getRetryCount() const; | |||||
// 启动超时计时(若已在运行则重启) | |||||
void start(); | |||||
// 停止超时计时并重置重试计数 | |||||
void stop(); | |||||
// 判断是否正在计时中 | |||||
bool isRunning() const; | |||||
signals: | |||||
// 超时信号(参数为当前重试次数) | |||||
void timeoutOccurred(int retryCount); | |||||
// 达到最大重试次数信号 | |||||
void maxRetriesReached(); | |||||
private: | |||||
// 处理定时器超时 | |||||
void onTimerTimeout(); | |||||
QTimer *timer_; // 定时器 | |||||
int timeoutInterval_; // 超时时间(毫秒) | |||||
int maxRetryCount_; // 最大重试次数 | |||||
int currentRetryCount_; // 当前重试次数 | |||||
}; | |||||
#endif // TIMEOUT_HANDLER_H |
@@ -0,0 +1,11 @@ | |||||
#include "mainwindow.h" | |||||
#include <QApplication> | |||||
int main(int argc, char *argv[]) | |||||
{ | |||||
QApplication a(argc, argv); | |||||
MainWindow w; | |||||
w.show(); | |||||
return a.exec(); | |||||
} |
@@ -0,0 +1,613 @@ | |||||
/*********************************************************************** | |||||
* Copyright (C) 2025-, XINJE Co., Ltd. | |||||
* | |||||
* File Name: // mainwindow.cpp | |||||
* Description: // ModbusRTU主站上位机人机交互界面源文件 | |||||
* //调用SerialCommunicator实现串口通信,调用ModbusRTUMaster实现帧的生成与解析。 | |||||
* Others: // | |||||
* Version: // v1.0 | |||||
* Author: // weikai,XINJE | |||||
* Date: // 2025-7-30 | |||||
***********************************************************************/ | |||||
#include "mainwindow.h" | |||||
#include "ui_mainwindow.h" | |||||
#include <QStatusBar> | |||||
#include <QTextCursor> | |||||
#include <QMessageBox> | |||||
#include <QDebug> | |||||
#include <QFileDialog> | |||||
#include <QFile> | |||||
MainWindow::MainWindow(QWidget *parent) | |||||
:QMainWindow(parent), | |||||
ui_(new Ui::MainWindow), | |||||
serialComm_(new SerialCommunicator(this)), | |||||
modbusMaster_(new ModbusRTUMaster(this)), | |||||
serialPortTimer_(new QTimer(this)) | |||||
{ | |||||
ui_->setupUi(this); | |||||
statusBar()->showMessage("就绪"); | |||||
// 添加UI控件显式关联 | |||||
connect(ui_->connectButton, &QPushButton::clicked,this, &MainWindow::onConnectButtonClicked);//连接按钮关联槽函数 | |||||
connect(ui_->disconnectButton, &QPushButton::clicked,this, &MainWindow::onDisconnectButtonClicked);//断开连接按钮关联槽函数 | |||||
connect(ui_->readButton, &QPushButton::clicked,this, &MainWindow::onReadButtonClicked);//读取按钮关联槽函数 | |||||
connect(ui_->writeButton, &QPushButton::clicked,this, &MainWindow::onWriteButtonClicked);//写入按钮关联槽函数 | |||||
connect(ui_->clearLogButton, &QPushButton::clicked,this, &MainWindow::onClearLogButtonClicked);//清空日志按钮关联槽函数 | |||||
connect(ui_->exportHistoryButton, &QPushButton::clicked,this, &MainWindow::onExportHistoryButtonClicked);//导出通信历史按钮 | |||||
// 连接串口通信信号 | |||||
connect(serialComm_, &SerialCommunicator::dataReceived,this, &MainWindow::onSerialDataReceived);//串口接收到数据关联槽函数 | |||||
connect(serialComm_, &SerialCommunicator::statusChanged,this, &MainWindow::onSerialStatusChanged);//状态变化信号关联槽函数 | |||||
connect(serialComm_, &SerialCommunicator::errorOccurred,this, &MainWindow::onSerialErrorOccurred);//错误发生信号关联槽函数 | |||||
connect(serialComm_, &SerialCommunicator::connectionDisconnected,this, &MainWindow::onConnectionDisconnected);//连接断开信号关联槽函数 | |||||
//设置定时器,每2秒刷新一次串口列表 | |||||
connect(serialPortTimer_, &QTimer::timeout, this, &MainWindow::onRefreshSerialPorts);//定时信号关联刷新串口槽函数 | |||||
serialPortTimer_->start(2000); // 每2000毫秒(2秒)检查一次 | |||||
init();//初始化 | |||||
refreshSerialPorts();//刷新串口列表 | |||||
logToBox("程序启动,就绪");// 启动日志 | |||||
} | |||||
/*********************************************************************** | |||||
* 析构函数 | |||||
***********************************************************************/ | |||||
MainWindow::~MainWindow() | |||||
{ | |||||
delete ui_; | |||||
} | |||||
/*********************************************************************** | |||||
* 初始化各个组件,如数据位,停止位等 | |||||
***********************************************************************/ | |||||
void MainWindow::init() | |||||
{ | |||||
// 初始化日志框组件 | |||||
ui_->logTextEdit->setReadOnly(true);//设置只读 | |||||
ui_->logTextEdit->setLineWrapMode(QTextEdit::WidgetWidth);//设置自动换行 | |||||
ui_->logTextEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);//设置垂直滚动条常显 | |||||
// 初始化数据位 | |||||
ui_->dataComboBox->addItem("7", QSerialPort::Data7); | |||||
ui_->dataComboBox->addItem("8", QSerialPort::Data8); | |||||
ui_->dataComboBox->setCurrentIndex(1); | |||||
//初始化停止位 | |||||
ui_->stopComboBox->addItem("1", QSerialPort::OneStop); | |||||
ui_->stopComboBox->addItem("1.5", QSerialPort::OneAndHalfStop); | |||||
ui_->stopComboBox->addItem("2", QSerialPort::TwoStop); | |||||
ui_->stopComboBox->setCurrentIndex(0); | |||||
//初始化校验位下拉框 | |||||
ui_->verifyComboBox->addItem("无", QSerialPort::NoParity); | |||||
ui_->verifyComboBox->addItem("奇校验", QSerialPort::OddParity); | |||||
ui_->verifyComboBox->addItem("偶校验", QSerialPort::EvenParity); | |||||
ui_->verifyComboBox->setCurrentIndex(0); | |||||
//初始化波特率 | |||||
ui_->baudComboBox->setCurrentIndex(1); | |||||
//创建心跳帧 | |||||
modbusMaster_->setSlaveAddr(1); | |||||
modbusMaster_->setFuncCode(ModbusRTUMaster::READ_COILS); | |||||
modbusMaster_->setStartAddr(256); | |||||
modbusMaster_->setReadCount(1); | |||||
QByteArray frame = modbusMaster_ ->createReadCoilsFrame(); | |||||
//qDebug() << "文件:" << __FILE__ << "行:" << __LINE__ << "心跳帧frame:"<< frame; | |||||
serialComm_->setHeartbeatDFrame(frame); | |||||
} | |||||
/*********************************************************************** | |||||
* 导出历史记录按钮,点击触发,打开文件保存对话框,打开文件并写入。 | |||||
***********************************************************************/ | |||||
void MainWindow::onExportHistoryButtonClicked() | |||||
{ | |||||
// 默认文件名:当前日期时间(格式为yyyyMMdd_HHmmss)加上"_history.txt"组成 | |||||
QString defaultFileName = QDateTime::currentDateTime().toString("yyyyMMdd_HHmmss") + "_history.txt"; | |||||
// 打开文件保存对话框,让用户选择保存路径和文件名 | |||||
QString fileName = QFileDialog::getSaveFileName( | |||||
this, | |||||
"导出历史记录", | |||||
defaultFileName, | |||||
"文本文件 (*.txt);" | |||||
); | |||||
// 判断用户是否取消了文件选择 | |||||
if (fileName.isEmpty()) | |||||
{ | |||||
//将导出取消输出至状态栏与日志框 | |||||
statusBar()->showMessage("导出取消", 2000); | |||||
logToBox("导出历史记录取消"); | |||||
return; | |||||
} | |||||
// 根据用户选择的文件名创建QFile对象,用于文件操作 | |||||
QFile file(fileName); | |||||
//文件打开失败 | |||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) | |||||
{ | |||||
statusBar()->showMessage("无法打开文件进行写入", 2000); // 将具体错误信息记录到日志框 | |||||
logToBox("错误: 无法打开文件 " + fileName); | |||||
return; | |||||
} | |||||
// 创建文本流对象并关联 | |||||
QTextStream out(&file); | |||||
// 遍历历史记录列表中的所有项,将内容写入文件 | |||||
for (int i = 0; i < ui_->historyListWidget->count(); ++i) | |||||
{ | |||||
out << ui_->historyListWidget->item(i)->text() << "\n\n"; | |||||
} | |||||
file.close(); | |||||
statusBar()->showMessage("历史记录已导出到 " + fileName, 3000); | |||||
logToBox("历史记录已导出到 " + fileName); | |||||
} | |||||
/*********************************************************************** | |||||
* 连接按钮,点击触发。获取界面通信配置参数,与对应串口建立连接 | |||||
***********************************************************************/ | |||||
void MainWindow::onConnectButtonClicked() | |||||
{ | |||||
// 获取UI参数:串口名、波特率、数据位、停止位、校验选项 | |||||
QString currentText = ui_->serialPortCombox->currentText(); | |||||
QString portName = currentText.split("(").first(); | |||||
int baudRate = ui_->baudComboBox->currentText().toUInt(); | |||||
QSerialPort::DataBits dataBits = static_cast<QSerialPort::DataBits>(ui_->dataComboBox->currentData().toUInt()); | |||||
QSerialPort::StopBits stopBits = static_cast<QSerialPort::StopBits>(ui_->stopComboBox->currentData().toUInt()); | |||||
QSerialPort::Parity parity = static_cast<QSerialPort::Parity>(ui_->verifyComboBox->currentData().toUInt()); | |||||
// 向serialComm设置串口参数 | |||||
serialComm_->setPortName(portName); | |||||
serialComm_->setBaudRate(baudRate); | |||||
serialComm_->setDataBits(dataBits); | |||||
serialComm_->setStopBits(stopBits); | |||||
serialComm_->setParity(parity); | |||||
//设置超时时间与最大重发次数 | |||||
int timeout=DEFAULT_TIMEOUT,maxRetries=DEFAULT_MAXRETRISE; | |||||
if(!ui_->timeoutEdit->text().isEmpty()) | |||||
{ | |||||
bool ok; | |||||
timeout = ui_->timeoutEdit->text().toUInt(&ok); | |||||
if(!ok) | |||||
{ | |||||
logToBox("超时时间输入有误,请检查!"); | |||||
} | |||||
} | |||||
if(!ui_->reissueEdit->text().isEmpty()) | |||||
{ | |||||
bool ok; | |||||
maxRetries = ui_->reissueEdit->text().toUInt(&ok); | |||||
if(!ok) | |||||
{ | |||||
logToBox("最大重发次数输入有误,请检查!"); | |||||
} | |||||
} | |||||
if(!serialComm_->setTimeoutSettings(timeout,maxRetries)) | |||||
{ | |||||
QMessageBox::warning( | |||||
this, | |||||
"超时参数错误!", | |||||
"请重新输入,超时时间500~5000ms,最大重发次数1~5次", | |||||
QMessageBox::Ok | |||||
); | |||||
return; | |||||
} | |||||
// 尝试连接 | |||||
if (serialComm_->connectDevice())//连接成功 | |||||
{ | |||||
ui_->connectButton->setEnabled(false); | |||||
ui_->disconnectButton->setEnabled(true); | |||||
ui_->timeoutEdit->setText(QString::number(timeout)); | |||||
ui_->reissueEdit->setText(QString::number(maxRetries)); | |||||
} | |||||
else | |||||
{ | |||||
logToBox(QString(portName + "串口连接失败\n")); | |||||
} | |||||
} | |||||
/*********************************************************************** | |||||
* 断开按钮,点击触发。更新连接与断开连接按钮状态,并调用serialComm断开连接函数 | |||||
***********************************************************************/ | |||||
void MainWindow::onDisconnectButtonClicked() | |||||
{ | |||||
serialComm_->disconnectDevice(); | |||||
ui_->connectButton->setEnabled(true); | |||||
ui_->disconnectButton->setEnabled(false); | |||||
} | |||||
/*********************************************************************** | |||||
* 清空日志按钮,点击触发。清空日志框 | |||||
***********************************************************************/ | |||||
void MainWindow::onClearLogButtonClicked() | |||||
{ | |||||
ui_->logTextEdit->clear(); | |||||
logToBox("日志已清空"); | |||||
} | |||||
/*********************************************************************** | |||||
* 读取按钮,点击触发。触发后读取界面参数,根据功能码选择请求帧类型,创建帧并发送 | |||||
***********************************************************************/ | |||||
void MainWindow::onReadButtonClicked() | |||||
{ | |||||
if (!serialComm_->isConnected()) | |||||
{ | |||||
statusBar()->showMessage("请先连接设备", 2000); | |||||
return; | |||||
} | |||||
// 从UI获取参数 | |||||
int slaveAddress = ui_->stationAddrSpinBox->value(); | |||||
QString funcText = ui_->functionComboBox->currentText(); | |||||
int startAddress = ui_->startAddrSpinBox->value(); | |||||
int readNum = ui_->numberSpinBox->value(); | |||||
// 设置Modbus参数 | |||||
modbusMaster_->setSlaveAddr(slaveAddress); | |||||
modbusMaster_->setStartAddr(startAddress); | |||||
QByteArray frame; | |||||
// 根据功能码生成请求帧 | |||||
if ("01读线圈" == funcText) | |||||
{ | |||||
modbusMaster_->setFuncCode(ModbusRTUMaster::READ_COILS); | |||||
modbusMaster_->setReadCount(readNum); | |||||
frame = modbusMaster_->createReadCoilsFrame(); | |||||
} | |||||
else if ("03读寄存器" == funcText) | |||||
{ | |||||
modbusMaster_->setFuncCode(ModbusRTUMaster::READ_HOLDING_REGISTERS); | |||||
modbusMaster_->setReadCount(readNum); | |||||
frame = modbusMaster_->createReadHoldingRegistersFrame(); | |||||
} | |||||
else | |||||
{ | |||||
statusBar()->showMessage("请选择读取功能码", 2000); | |||||
return; | |||||
} | |||||
// 发送帧 | |||||
if (!frame.isEmpty()) | |||||
{ | |||||
serialComm_->sendData(frame); | |||||
logToBox("发送读取请求完成!"); | |||||
QString timeStamp = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss"); | |||||
ui_->historyListWidget->addItem("[" + timeStamp + "]" + "发送: " + frame.toHex(' ').toUpper()); | |||||
} | |||||
else | |||||
{ | |||||
statusBar()->showMessage("生成请求帧失败", 2000); | |||||
} | |||||
//读取请求发送成功弹窗 | |||||
QMessageBox::information( | |||||
this, | |||||
"读取请求", | |||||
"读取请求发送完成!", | |||||
QMessageBox::Ok | |||||
); | |||||
} | |||||
/*********************************************************************** | |||||
* 写入按钮,点击触发。触发后读取界面参数,根据功能码选择请求帧类型,创建帧并发送 | |||||
***********************************************************************/ | |||||
void MainWindow::onWriteButtonClicked() | |||||
{ | |||||
if (!serialComm_->isConnected()) | |||||
{ | |||||
statusBar()->showMessage("请先连接设备", 2000); | |||||
return; | |||||
} | |||||
// 从UI获取参数 | |||||
int slaveAddress = ui_->stationAddrSpinBox->value(); | |||||
QString funcText = ui_->functionComboBox->currentText(); | |||||
int startAddress = ui_->startAddrSpinBox->value(); | |||||
QString dataText = ui_->writeTextEdit->toPlainText().trimmed(); | |||||
if (dataText.isEmpty()) | |||||
{ | |||||
statusBar()->showMessage("请输入写入数据", 2000); | |||||
return; | |||||
} | |||||
// 设置Modbus参数 | |||||
modbusMaster_->setSlaveAddr(slaveAddress); | |||||
modbusMaster_->setStartAddr(startAddress); | |||||
QByteArray frame; | |||||
// 根据功能码生成请求帧 | |||||
if ("0F写线圈" == funcText) | |||||
{ | |||||
// 解析线圈数据(二进制字符串) | |||||
QVector<bool> coils; | |||||
for (QChar c : dataText) | |||||
{ | |||||
if ('0' == c) | |||||
{ | |||||
coils.append(false); | |||||
} | |||||
else if ('1' == c) | |||||
{ | |||||
coils.append(true); | |||||
} | |||||
else | |||||
{ | |||||
statusBar()->showMessage("写线圈需输入二进制数据(仅0/1),请重新输入", 2000); | |||||
return; | |||||
} | |||||
} | |||||
modbusMaster_->setFuncCode(ModbusRTUMaster::WRITE_MULTIPLE_COILS); | |||||
modbusMaster_->setCoils(coils); | |||||
frame = modbusMaster_->createWriteMultipleCoilsFrame(); | |||||
} | |||||
else if ("10写寄存器" == funcText) | |||||
{ | |||||
// 解析寄存器数据(逗号分隔的整数) | |||||
QStringList dataList = dataText.split(','); | |||||
QVector<quint16> registers; | |||||
for (const QString &d : dataList) | |||||
{ | |||||
bool flag; | |||||
quint16 data = d.toUInt(&flag);; | |||||
if (!flag) | |||||
{ | |||||
statusBar()->showMessage("写线圈需输入整数,请重新输入)", 2000); | |||||
return; | |||||
} | |||||
registers.append(data); | |||||
} | |||||
modbusMaster_->setFuncCode(ModbusRTUMaster::WRITE_MULTIPLE_REGISTERS); | |||||
modbusMaster_->setRegisters(registers); | |||||
frame = modbusMaster_->createWriteMultipleRegistersFrame(); | |||||
} | |||||
else | |||||
{ | |||||
statusBar()->showMessage("请选择写入功能码", 2000); | |||||
return; | |||||
} | |||||
// 发送帧 | |||||
if (!frame.isEmpty()) | |||||
{ | |||||
serialComm_->sendData(frame); | |||||
// 将帧转换为带空格分隔的十六进制大写字符串 | |||||
QString hexStr = frame.toHex(' ').toUpper(); | |||||
logToBox("发送请求帧"); | |||||
QString timeStamp = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss"); | |||||
ui_->historyListWidget->addItem("[" + timeStamp + "]" +"发送: " + hexStr); | |||||
QMessageBox::information( | |||||
this, | |||||
"写入请求", | |||||
"写入请求发送完成!", | |||||
QMessageBox::Ok | |||||
); | |||||
} | |||||
else | |||||
{ | |||||
statusBar()->showMessage("生成请求帧失败", 2000); | |||||
} | |||||
} | |||||
/*********************************************************************** | |||||
* 收到串口刷新定时器信号的槽函数,调用刷新串口函数 | |||||
***********************************************************************/ | |||||
void MainWindow::onRefreshSerialPorts() | |||||
{ | |||||
refreshSerialPorts(); | |||||
} | |||||
/*********************************************************************** | |||||
* 收到连接已断开信号的槽函数,更新按钮状态并弹出警告弹窗告知用户。 | |||||
***********************************************************************/ | |||||
void MainWindow::onConnectionDisconnected() | |||||
{ | |||||
// 更新UI按钮状态 | |||||
ui_->connectButton->setEnabled(true); | |||||
ui_->disconnectButton->setEnabled(false); | |||||
logToBox("连接已断开"); | |||||
QMessageBox::warning( | |||||
this, | |||||
"连接断开", | |||||
"连接已断开!", | |||||
QMessageBox::Ok | |||||
); | |||||
} | |||||
/*********************************************************************** | |||||
* 收到串口状态变化信号的槽函数,并将状态变化输出至底部状态栏与日志框。 | |||||
***********************************************************************/ | |||||
void MainWindow::onSerialStatusChanged(const QString &status) | |||||
{ | |||||
statusBar()->showMessage(status, 3000); | |||||
logToBox(status); | |||||
} | |||||
/*********************************************************************** | |||||
* 收到串口错误信号的槽函数,并将错误输出至底部状态栏与日志框。 | |||||
***********************************************************************/ | |||||
void MainWindow::onSerialErrorOccurred(const QString &error) | |||||
{ | |||||
statusBar()->showMessage(error, 3000); | |||||
logToBox("错误: " + error); | |||||
} | |||||
/*********************************************************************** | |||||
* 收到数据信号触发 | |||||
* 处理串口接收数据的逻辑,提取完整帧后解析并展示,data为串口收到的原始数据。 | |||||
***********************************************************************/ | |||||
void MainWindow::onSerialDataReceived(const QString &data) | |||||
{ | |||||
QByteArray response = QByteArray::fromHex(data.toUtf8()); | |||||
quint8 slaveAddr, funcCode, errorCode; | |||||
QVector<quint16> parsedData;//解析后的数据 | |||||
QByteArray extractedFrame; // 提取的完整帧 | |||||
bool isComFrame = true;//是否解析到完整帧 | |||||
// 将数据传递到下层处理解析 | |||||
if (modbusMaster_->processReceivedData(response, slaveAddr, funcCode, parsedData, errorCode, extractedFrame,isComFrame)) | |||||
{ | |||||
serialComm_->setIsCanHeart(true); | |||||
//解析成功,获取到帧的各部分,根据格式显示在日志框 | |||||
QString hexData = extractedFrame.toHex(' ').toUpper().trimmed(); | |||||
QString timeStamp = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss"); | |||||
QString display = QString("[%1]接收原始数据: %2\n").arg(timeStamp).arg(hexData); | |||||
display += QString("解析结果:\n从站地址: " + QString::number(slaveAddr) + "\n功能码: 0x" + QString::number(funcCode,16).rightJustified(2,'0')); | |||||
//对解析成功后的数据进行打印 | |||||
if (ModbusRTUMaster::NO_ERROR == errorCode) | |||||
{ | |||||
int startAddr = modbusMaster_->getStartAddr(); | |||||
switch (funcCode) | |||||
{ | |||||
case ModbusRTUMaster::READ_COILS: | |||||
{ | |||||
display += " 类型: 读线圈 (01)\n数据: "; | |||||
QString datas; | |||||
for (int i = 0; i < parsedData.size(); ++i) | |||||
{ | |||||
if(0 == i%10) | |||||
{ | |||||
datas += "\n"; | |||||
} | |||||
datas += QString("线圈%1=%2 ").arg(i + startAddr).arg(parsedData[i] ? "ON" : "OFF"); | |||||
} | |||||
display += datas; | |||||
break; | |||||
} | |||||
case ModbusRTUMaster::READ_HOLDING_REGISTERS: | |||||
{ | |||||
display += " 类型: 读保持寄存器 (03)\n数据: "; | |||||
QString datas; | |||||
for (int i = 0; i < parsedData.size(); ++i) | |||||
{ | |||||
if(0 == i%10) | |||||
{ | |||||
datas += "\n"; | |||||
} | |||||
datas += QString("寄存器%1=%2 ").arg(i + startAddr).arg(parsedData[i]); | |||||
} | |||||
display += datas; | |||||
break; | |||||
} | |||||
case ModbusRTUMaster::WRITE_MULTIPLE_COILS: | |||||
{ | |||||
display += QString(" 类型: 写多个线圈 (0F)\n起始地址: %1\n写入线圈数量: %2") | |||||
.arg(parsedData[0]) | |||||
.arg(parsedData[1]); | |||||
break; | |||||
} | |||||
case ModbusRTUMaster::WRITE_MULTIPLE_REGISTERS: | |||||
{ | |||||
display += QString(" 类型: 写多个寄存器 (10)\n起始地址: %1\n写入寄存器数量: %2") | |||||
.arg(parsedData[0]) | |||||
.arg(parsedData[1]); | |||||
break; | |||||
} | |||||
default: | |||||
display += "\n类型: 不支持的功能码"; | |||||
break; | |||||
} | |||||
logToBox("接收并解析响应成功"); | |||||
} | |||||
else | |||||
{ | |||||
display += QString(" 类型: 错误响应\n错误码: 0x%1 错误描述: %2") | |||||
.arg(errorCode, 2, 16, QChar('0')) | |||||
.arg(modbusMaster_->getErrorDescription(errorCode)); | |||||
logToBox(QString("接收错误响应: 错误码0x" + QString::number(errorCode,16).rightJustified(2,'0'))); | |||||
} | |||||
ui_->historyListWidget->addItem(display); // 添加解析结果 | |||||
ui_->historyListWidget->addItem("****************************************************************************************************"); | |||||
} | |||||
else | |||||
{ | |||||
if(isComFrame) | |||||
{ | |||||
QString hexData = response.toHex(' ').toUpper().trimmed(); | |||||
QString timeStamp = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss"); | |||||
QString display = QString("[%1]\n接收原始数据:%2\n").arg(timeStamp).arg(hexData); | |||||
display += "解析失败"; | |||||
ui_->historyListWidget->addItem(display); // 添加未解析的帧信息 | |||||
} | |||||
} | |||||
} | |||||
/*********************************************************************** | |||||
* 刷新串口列表 | |||||
***********************************************************************/ | |||||
void MainWindow::refreshSerialPorts() | |||||
{ | |||||
// 获取当前已有的端口 | |||||
QStringList oldPorts; | |||||
for (int i = 0; i < ui_->serialPortCombox->count(); ++i) | |||||
{ | |||||
QString port = ui_->serialPortCombox->itemText(i); | |||||
if (port != "未检测到串口") | |||||
{ | |||||
oldPorts << port; | |||||
} | |||||
} | |||||
// 获取新端口列表 | |||||
QStringList newPorts = serialComm_->getAvailablePorts(); | |||||
// 处理新增端口 | |||||
for (const QString& port : newPorts) | |||||
{ | |||||
if (!oldPorts.contains(port)) | |||||
{ | |||||
ui_->serialPortCombox->addItem(port); | |||||
logToBox("新增串口: " + port); | |||||
} | |||||
} | |||||
// 处理移除端口 | |||||
for (const QString& port : oldPorts) | |||||
{ | |||||
if (!newPorts.contains(port)) | |||||
{ | |||||
int index = ui_->serialPortCombox->findText(port); | |||||
if (index != -1) | |||||
{ | |||||
ui_->serialPortCombox->removeItem(index); | |||||
logToBox("移除串口: " + port); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
/*********************************************************************** | |||||
* 日志输出函数。将message输出到日志框 | |||||
***********************************************************************/ | |||||
void MainWindow::logToBox(const QString &message) | |||||
{ | |||||
QString timeStamp = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss"); | |||||
QString logEntry = QString("[%1] %2\n").arg(timeStamp).arg(message); | |||||
ui_->logTextEdit->insertPlainText(logEntry); | |||||
// 自动滚动到底部 | |||||
QTextCursor cursor = ui_->logTextEdit->textCursor(); | |||||
cursor.movePosition(QTextCursor::End); | |||||
ui_->logTextEdit->setTextCursor(cursor); | |||||
} |
@@ -0,0 +1,547 @@ | |||||
/*********************************************************************** | |||||
* Copyright (C) 2025-, XINJE Co., Ltd. | |||||
* | |||||
* File Name: // modbus_master.cpp | |||||
* Description: // ModbusRTU协议源文件,负责请求帧的生成,响应帧的解析 | |||||
* Others: // | |||||
* Version: // v1.0 | |||||
* Author: // weikai,XINJE | |||||
* Date: // 2025-7-30 | |||||
***********************************************************************/ | |||||
#include <QDebug> | |||||
#include "modbus_master.h" | |||||
ModbusRTUMaster::ModbusRTUMaster(QObject *parent) | |||||
: QObject(parent), | |||||
slaveAddr_(0), | |||||
funcCode_(READ_COILS), | |||||
startAddr_(0), | |||||
readCount_(0) | |||||
{ | |||||
errMessage.insert(NO_ERROR,"无错误"); | |||||
errMessage.insert(ILLEGAL_FUNCTION,"非法功能-从站不支持该功能码"); | |||||
errMessage.insert(ILLEGAL_DATA_ADDRESS,"非法数据地址-从站不支持该地址"); | |||||
errMessage.insert(ILLEGAL_DATA_VALUE,"非法数据值-数据值超出范围"); | |||||
errMessage.insert(SERVER_DEVICE_FAILURE,"从站设备故障"); | |||||
errMessage.insert(ACKNOWLEDGE,"确认-请求已接收但未处理"); | |||||
errMessage.insert(SERVER_DEVICE_BUSY,"从站设备忙"); | |||||
errMessage.insert(MEMORY_PARITY_ERROR,"内存奇偶校验错误"); | |||||
} | |||||
/************************************************************************************* | |||||
* 获取起始地址 | |||||
**************************************************************************************/ | |||||
quint16 ModbusRTUMaster::getStartAddr() const | |||||
{ | |||||
return startAddr_; | |||||
} | |||||
/************************************************************************************* | |||||
*获取错误码描述 | |||||
**************************************************************************************/ | |||||
QString ModbusRTUMaster::getErrorDescription(int errorCode) | |||||
{ | |||||
QString resMessage; | |||||
if(errMessage.contains(errorCode)) | |||||
{ | |||||
resMessage = errMessage[errorCode]; | |||||
} | |||||
else | |||||
{ | |||||
resMessage = "未知错误"; | |||||
} | |||||
return resMessage; | |||||
} | |||||
/************************************************************************************* | |||||
* 生成读请求帧 (功能码03) | |||||
*帧格式: [从站地址(1字节)] [功能码0x03(1字节)] [起始地址(2字节)] [读取数量(2字节)][CRC(2字节)] | |||||
**************************************************************************************/ | |||||
void ModbusRTUMaster::buildReadFrame(QByteArray& frame,FunctionCode funcCode) | |||||
{ | |||||
// 从站地址 | |||||
frame.append(slaveAddr_); | |||||
// 功能码 | |||||
frame.append(funcCode); | |||||
// 起始地址 | |||||
frame.append((startAddr_ >> 8) & 0xFF); | |||||
frame.append(startAddr_ & 0xFF); | |||||
//读取数量 | |||||
frame.append((readCount_ >> 8) & 0xFF); | |||||
frame.append(readCount_ & 0xFF); | |||||
// 计算并添加CRC校验 (2字节) | |||||
quint16 crc = calculateCRC(frame); | |||||
frame.append(crc & 0xFF); // CRC低字节在前 | |||||
frame.append((crc >> 8) & 0xFF); // CRC高字节在后 | |||||
} | |||||
/*********************************************************************** | |||||
* 生成部分写请求帧 (功能码03) | |||||
*帧格式: [从站地址(1字节)] [功能码0x03(1字节)] [起始地址(2字节)] | |||||
***********************************************************************/ | |||||
void ModbusRTUMaster::buildWriteFrame(QByteArray& frame,FunctionCode funcCode) | |||||
{ | |||||
// 从站地址 | |||||
frame.append(slaveAddr_); | |||||
// 功能码 | |||||
frame.append(funcCode); | |||||
// 起始地址 | |||||
frame.append((startAddr_ >> 8) & 0xFF); | |||||
frame.append(startAddr_ & 0xFF); | |||||
} | |||||
/*********************************************************************** | |||||
* 生成读线圈请求帧 (功能码01) | |||||
* 帧格式: [从站地址(1字节)] [功能码0x01(1字节)] [起始地址(2字节)] | |||||
* [线圈数量(2字节)] [CRC校验(2字节)] | |||||
***********************************************************************/ | |||||
QByteArray ModbusRTUMaster::createReadCoilsFrame() | |||||
{ | |||||
QByteArray frame; | |||||
buildReadFrame(frame,READ_COILS); | |||||
return frame; | |||||
} | |||||
/*********************************************************************** | |||||
* 生成读保持寄存器请求帧 (功能码03) | |||||
*帧格式: [从站地址(1字节)] [功能码0x03(1字节)] [起始地址(2字节)] | |||||
* [寄存器数量(2字节)] [CRC校验(2字节)] | |||||
***********************************************************************/ | |||||
QByteArray ModbusRTUMaster::createReadHoldingRegistersFrame() | |||||
{ | |||||
QByteArray frame; | |||||
buildReadFrame(frame,READ_HOLDING_REGISTERS); | |||||
return frame; | |||||
} | |||||
/*********************************************************************** | |||||
* 生成写多个线圈请求帧 (功能码0F) | |||||
*帧格式: [从站地址(1字节)] [功能码0x0F(1字节)] [起始地址(2字节)] | |||||
* [线圈数量(2字节)] [字节数(1字节)] [线圈数据(n字节)] [CRC校验(2字节)] | |||||
***********************************************************************/ | |||||
QByteArray ModbusRTUMaster::createWriteMultipleCoilsFrame() | |||||
{ | |||||
// 检查线圈数据是否为空,为空则返回空 | |||||
if (coils_.isEmpty()) | |||||
{ | |||||
return QByteArray(); | |||||
} | |||||
QByteArray frame; | |||||
buildWriteFrame(frame,WRITE_MULTIPLE_COILS); | |||||
//线圈数量 | |||||
quint16 coilCount = coils_.size(); | |||||
frame.append((coilCount >> 8) & 0xFF); | |||||
frame.append(coilCount & 0xFF); | |||||
//字节数 | |||||
quint8 byteCount = (coilCount + 7) / 8; | |||||
frame.append(byteCount); | |||||
//线圈数据 | |||||
QByteArray coilData; | |||||
for (int currByte = 0; currByte < byteCount; ++currByte) | |||||
{ | |||||
//当前字节 | |||||
quint8 byte = 0; | |||||
for (int currBit = 0; currBit < 8; ++currBit) | |||||
{ | |||||
//当前比特位 | |||||
int index = currByte * 8 + currBit; | |||||
if (index < coilCount && coils_[index]) | |||||
{ | |||||
//当前比特位按位或 | |||||
int mask = 1 << currBit; | |||||
byte |= mask; | |||||
} | |||||
} | |||||
coilData.append(byte); | |||||
} | |||||
frame.append(coilData); | |||||
// 计算并添加CRC校验 (2字节) | |||||
quint16 crc = calculateCRC(frame); | |||||
frame.append(crc & 0xFF); | |||||
frame.append((crc >> 8) & 0xFF); | |||||
return frame; | |||||
} | |||||
/*********************************************************************** | |||||
*生成写多个保持寄存器请求帧 (功能码10) | |||||
*帧格式: [从站地址(1字节)] [功能码0x10(1字节)] [起始地址(2字节)] | |||||
* [寄存器数量(2字节)] [字节数(1字节)] [寄存器数据(2n字节)] [CRC校验(2字节)] | |||||
***********************************************************************/ | |||||
QByteArray ModbusRTUMaster::createWriteMultipleRegistersFrame() | |||||
{ | |||||
// 检查寄存器数据是否为空,为空则返回空 | |||||
if (registers_.isEmpty()) | |||||
{ | |||||
return QByteArray(); | |||||
} | |||||
QByteArray frame; | |||||
buildWriteFrame(frame,WRITE_MULTIPLE_REGISTERS); | |||||
//寄存器数量 | |||||
quint16 regCount = registers_.size(); | |||||
frame.append((regCount >> 8) & 0xFF); | |||||
frame.append(regCount & 0xFF); | |||||
// 字节数 | |||||
quint8 byteCount = regCount * 2; | |||||
frame.append(byteCount); | |||||
// 寄存器数据 | |||||
for (quint16 reg : registers_) | |||||
{ | |||||
frame.append((reg >> 8) & 0xFF); | |||||
frame.append(reg & 0xFF); | |||||
} | |||||
// 计算并添加CRC校验 | |||||
quint16 crc = calculateCRC(frame); | |||||
frame.append(crc & 0xFF); | |||||
frame.append((crc >> 8) & 0xFF); | |||||
return frame; | |||||
} | |||||
/*********************************************************************** | |||||
*处理接收到的数据流 | |||||
***********************************************************************/ | |||||
bool ModbusRTUMaster::processReceivedData(const QByteArray& data, quint8& slaveAddr, quint8& funcCode, | |||||
QVector<quint16>& parsedData, quint8& errorCode, QByteArray& extractedFrame,bool& isComFrame) | |||||
{ | |||||
buffer_.append(data); | |||||
//qDebug() <<"文件:"<<__FILE__ << "行数:" << __LINE__ << "内容:" << "接收数据追加到缓冲区,当前缓冲区大小:" << buffer_.size(); | |||||
QByteArray frame; | |||||
bool frameExtracted = false; | |||||
// 对完整帧做处理 | |||||
if (extractFrame(frame,isComFrame)) | |||||
{ | |||||
//qDebug() <<"文件:"<<__FILE__ << "行数:" << __LINE__ << " 完整帧: " << frame; | |||||
extractedFrame = frame; // 保存当前提取的完整帧 | |||||
if (parseResponse(frame, slaveAddr, funcCode, parsedData, errorCode)) | |||||
{ | |||||
frameExtracted = true; | |||||
//qDebug() <<"文件:"<<__FILE__ << " 行数:" << __LINE__ << ": 帧解析成功,功能码:" << QString::number(funcCode, 16); | |||||
} | |||||
else | |||||
{ | |||||
qDebug() <<"文件:"<<__FILE__ << "行数:" << __LINE__ << "内容:" << "帧解析失败"; | |||||
} | |||||
} | |||||
return frameExtracted; // 返回是否提取并解析成功 | |||||
} | |||||
/******************************************************************** | |||||
* 从缓冲区中提取一个完整的Modbus RTU帧 | |||||
********************************************************************/ | |||||
bool ModbusRTUMaster::extractFrame(QByteArray& frame,bool& isComFrame) | |||||
{ | |||||
// 检查缓冲区是否至少包含最小帧长度(异常帧5字节) | |||||
if (buffer_.size() < 5) | |||||
{ | |||||
isComFrame = false; | |||||
return false; | |||||
} | |||||
// 遍历缓冲区,尝试提取有效帧 | |||||
for (int i = 0; i <= buffer_.size() - 5; ++i) | |||||
{ | |||||
// 确保有足够的字节读取功能码 | |||||
if (i + 1 >= buffer_.size()) | |||||
{ | |||||
break; | |||||
} | |||||
// 获取功能码 | |||||
quint8 funcCode = static_cast<quint8>(buffer_[i + 1]); | |||||
int expectedLen = 0; // 预期帧长度 | |||||
// 判断是否为异常响应帧 | |||||
if (funcCode & 0x80) | |||||
{ | |||||
expectedLen = 5; //异常响应帧5字节 | |||||
} | |||||
else | |||||
{ | |||||
// 根据功能码计算预期帧长度 | |||||
switch (funcCode) | |||||
{ | |||||
case READ_COILS: | |||||
case READ_HOLDING_REGISTERS: | |||||
{ | |||||
// 确保有足够的字节读取byteCount | |||||
if (i + 3 > buffer_.size()) | |||||
{ | |||||
return false; // 数据不足 | |||||
} | |||||
int byteCount = static_cast<quint8>(buffer_[i + 2]); | |||||
expectedLen = 3 + byteCount + 2; // 地址(1) + 功能码(1) + 字节数(1) + 数据 + CRC(2) | |||||
break; | |||||
} | |||||
case WRITE_MULTIPLE_COILS: | |||||
case WRITE_MULTIPLE_REGISTERS: | |||||
{ | |||||
expectedLen = 8; // 固定长度:地址(1) + 功能码(1) + 地址(2) + 数量(2) + CRC(2) | |||||
break; | |||||
} | |||||
default: | |||||
{ | |||||
// 未知功能码,移除无效字节并继续 | |||||
buffer_.remove(0, i + 1); | |||||
i = -1; // 重置索引 | |||||
continue; | |||||
} | |||||
} | |||||
} | |||||
// 检查缓冲区是否包含完整帧 | |||||
if (i + expectedLen > buffer_.size()) | |||||
{ | |||||
isComFrame = false; | |||||
return false; // 帧不完整 | |||||
} | |||||
// 提取可能有效的帧并验证CRC | |||||
frame = buffer_.mid(i, expectedLen); | |||||
if (verifyCRC(frame)) | |||||
{ | |||||
//qDebug() <<"文件:"<<__FILE__ << "行数:" << __LINE__ << "内容:" << "提取到的完整帧:" << frame.toHex(); | |||||
buffer_.remove(0, i + expectedLen); // 移除已处理的帧 | |||||
return true; // 提取到完整帧 | |||||
} | |||||
else | |||||
{ | |||||
// CRC校验失败,移除无效字节 | |||||
buffer_.remove(0, i + 1); | |||||
i = -1; // 重置索引 | |||||
} | |||||
} | |||||
return false; | |||||
} | |||||
/******************************************************************** | |||||
* 解析提取到的完整帧 | |||||
********************************************************************/ | |||||
bool ModbusRTUMaster::parseResponse(const QByteArray& response, quint8& slaveAddr, quint8& funcCode, | |||||
QVector<quint16>& data, quint8& errorCode) | |||||
{ | |||||
// 响应数据至少需要包含:地址(1字节) + 功能码(1字节) + 数据(至少1字节) + CRC(2字节),共5字节 | |||||
if (response.size() < 5) | |||||
{ | |||||
return false; | |||||
} | |||||
// 验证响应数据的CRC校验是否正确 | |||||
if (!verifyCRC(response)) | |||||
{ | |||||
return false; | |||||
} | |||||
// 提取从机地址 | |||||
slaveAddr = static_cast<quint8>(response[0]); | |||||
// 提取功能码 | |||||
funcCode = static_cast<quint8>(response[1]); | |||||
// 判断是否为错误响应 | |||||
if (funcCode & 0x80) | |||||
{ | |||||
// 错误响应格式:地址(1) + 错误功能码(1) + 错误码(1) + CRC(2),共5字节 | |||||
// 提取错误码 | |||||
errorCode = static_cast<quint8>(response[2]); | |||||
return true; | |||||
} | |||||
// 正常响应 | |||||
errorCode = NO_ERROR; | |||||
// 提取数据部分(去除地址、功能码和CRC) | |||||
QByteArray responseData = response.mid(2, response.size() - 4); | |||||
// 根据功能码解析对应的数据 | |||||
switch (funcCode) | |||||
{ | |||||
case READ_COILS: | |||||
{ | |||||
QVector<bool> coils; | |||||
// 解析读线圈响应数据 | |||||
if (!parseReadCoilsResponse(responseData, coils)) | |||||
{ | |||||
return false; | |||||
} | |||||
// 将布尔线圈状态转换为16位整数 | |||||
data.clear(); | |||||
for (bool coil : coils) | |||||
{ | |||||
int bit = 0; | |||||
if(coil) { bit=1; } | |||||
data.append(bit); | |||||
} | |||||
break; | |||||
} | |||||
case READ_HOLDING_REGISTERS: | |||||
{ | |||||
// 解析读保持寄存器响应数据 | |||||
if (!parseReadHoldingRegistersResponse(responseData, data)) | |||||
{ | |||||
return false; | |||||
} | |||||
break; | |||||
} | |||||
case WRITE_MULTIPLE_COILS: // 写多个线圈功能码 | |||||
case WRITE_MULTIPLE_REGISTERS: // 写多个寄存器功能码 | |||||
{ | |||||
// 写多个线圈/寄存器的响应数据固定为4字节:起始地址(2字节) + 数量(2字节) | |||||
if (responseData.size() != 4) | |||||
{ | |||||
return false; | |||||
} | |||||
// 解析起始地址 | |||||
quint16 startAddr = (static_cast<quint8>(responseData[0]) << 8) | static_cast<quint8>(responseData[1]); | |||||
// 解析写入数量 | |||||
quint16 count = (static_cast<quint8>(responseData[2]) << 8) | static_cast<quint8>(responseData[3]); | |||||
// 存入起始地址和数量 | |||||
data.clear(); | |||||
data.append(startAddr); | |||||
data.append(count); | |||||
break; | |||||
} | |||||
default: // 不支持的功能码 | |||||
return false; | |||||
} | |||||
return true; | |||||
} | |||||
/******************************************************************** | |||||
* 解析读线圈响应,[字节数][数据] | |||||
********************************************************************/ | |||||
bool ModbusRTUMaster::parseReadCoilsResponse(const QByteArray& responseData, QVector<bool>& coils) | |||||
{ | |||||
// 数据为空,解析失败 | |||||
if (responseData.isEmpty()) | |||||
{ | |||||
return false; | |||||
} | |||||
quint8 byteCount = static_cast<quint8>(responseData[0]); | |||||
// 验证数据长度是否与字节计数匹配(字节计数 + 自身1字节) | |||||
if (responseData.size() != byteCount + 1) | |||||
{ | |||||
return false; | |||||
} | |||||
coils.clear(); | |||||
int parseCoils = readCount_;//需要解析的线圈数量 | |||||
// 遍历每个字节 | |||||
for (int currByte = 0; currByte < byteCount && parseCoils > 0; ++currByte) | |||||
{ | |||||
// 获取当前字节的数值 | |||||
quint8 byte = static_cast<quint8>(responseData[currByte + 1]); | |||||
// 计算当前字节需要解析的位数(不超过8位,且不超过剩余需要读取的线圈数) | |||||
int parseBits = qMin(8, parseCoils); | |||||
// 遍历每个位 | |||||
for (int currBit = 0; currBit < parseBits; ++currBit) | |||||
{ | |||||
//查看该位是0 or 1并添加 | |||||
int mask = 1 << currBit; | |||||
bool bitIsSet = ((byte & mask) != 0); | |||||
coils.append(bitIsSet); | |||||
} | |||||
// 更新剩余需要读取的线圈数 | |||||
parseCoils -= parseBits; | |||||
} | |||||
return true; | |||||
} | |||||
/******************************************************************** | |||||
* 解析读寄存器响应,[字节数][数据] | |||||
********************************************************************/ | |||||
bool ModbusRTUMaster::parseReadHoldingRegistersResponse(const QByteArray& responseData, QVector<quint16>& registers) | |||||
{ | |||||
if (responseData.isEmpty()) | |||||
{ | |||||
qDebug() <<"文件:"<<__FILE__ << "行数:" << __LINE__ << "内容:" << "响应数据为空"; | |||||
return false; | |||||
} | |||||
//字节数 | |||||
quint8 byteCount = static_cast<quint8>(responseData[0]); | |||||
if (byteCount != readCount_ * 2 || responseData.size() != byteCount + 1) | |||||
{ | |||||
return false; | |||||
} | |||||
//解析寄存器数据 | |||||
registers.clear(); | |||||
int regCount = byteCount / 2; | |||||
for (int i = 0; i < regCount; ++i) | |||||
{ | |||||
if(2 + i * 2 < responseData.size()) | |||||
{ | |||||
//1寄存器数据 2字节 | |||||
quint16 reg = static_cast<quint8>(responseData[1 + i * 2]) << 8 | static_cast<quint8>(responseData[2 + i * 2]); | |||||
registers.append(reg); | |||||
} | |||||
} | |||||
return true; | |||||
} | |||||
/******************************************************************** | |||||
* 计算CRC校验值 | |||||
********************************************************************/ | |||||
quint16 ModbusRTUMaster::calculateCRC(const QByteArray &data) | |||||
{ | |||||
quint16 crc = 0xFFFF; | |||||
for (int i = 0; i < data.size(); ++i) | |||||
{ | |||||
crc ^= (quint8)data[i]; | |||||
for (int j = 0; j < 8; ++j) | |||||
{ | |||||
bool carry = crc & 0x0001; | |||||
crc >>= 1; | |||||
if (carry) | |||||
{ | |||||
crc ^= 0xA001; | |||||
} | |||||
} | |||||
} | |||||
return crc; | |||||
} | |||||
/******************************************************************** | |||||
* 验证响应帧校验码 | |||||
********************************************************************/ | |||||
bool ModbusRTUMaster::verifyCRC(const QByteArray& frame) | |||||
{ | |||||
// 帧至少需要包含2字节CRC | |||||
if (frame.size() < 2) | |||||
{ | |||||
return false; | |||||
} | |||||
// 提取帧中的CRC值(小端) | |||||
quint16 receivedCRC = (static_cast<quint8>(frame[frame.size() - 1]) << 8) | | |||||
static_cast<quint8>(frame[frame.size() - 2]); | |||||
// 计算数据部分的CRC | |||||
QByteArray data = frame.left(frame.size() - 2); | |||||
quint16 calculatedCRC = calculateCRC(data); | |||||
// 比较接收的CRC和计算的CRC | |||||
return (receivedCRC == calculatedCRC); | |||||
} |
@@ -0,0 +1,371 @@ | |||||
/*********************************************************************** | |||||
* Copyright (C) 2025-, XINJE Co., Ltd. | |||||
* | |||||
* File Name: // serial_communication.cpp | |||||
* Description: // 主站串口通信模块源文件,负责串口的连接管理,数据收发管理 | |||||
* //调用超时处理TimeoutHandler实现超时重传 | |||||
* Others: // | |||||
* Version: // v1.0 | |||||
* Author: // weikai,XINJE | |||||
* Date: // 2025-7-30 | |||||
***********************************************************************/ | |||||
#include "serial_communication.h" | |||||
#include <QDebug> | |||||
SerialCommunicator::SerialCommunicator(QObject *parent) | |||||
: QObject(parent), | |||||
serialPort_(new QSerialPort(this)), | |||||
connected_(false), | |||||
timeoutHandler_(this), | |||||
heartbeatTimer_(new QTimer(this)), | |||||
heartbeatTimeoutTimer_(new QTimer(this)) | |||||
{ | |||||
// 连接串口接收信号到内部处理槽 | |||||
connect(serialPort_, &QSerialPort::readyRead, this, &SerialCommunicator::onReadyRead);//串口收到数据 | |||||
//连接串口错误信号 | |||||
connect(serialPort_, &QSerialPort::errorOccurred, this, &SerialCommunicator::onSerialError);//串口错误 | |||||
// 连接超时处理器的信号 | |||||
connect(&timeoutHandler_, &TimeoutHandler::timeoutOccurred,this, &SerialCommunicator::onTimeoutOccurred);//定时到达,触发超时重传 | |||||
connect(&timeoutHandler_, &TimeoutHandler::maxRetriesReached,this, &SerialCommunicator::onMaxRetriesReached);//到达最大重发次数 | |||||
//连接心跳 | |||||
connect(heartbeatTimer_, &QTimer::timeout ,this, &SerialCommunicator::onSendHeartbeat);//心跳触发 | |||||
connect(heartbeatTimeoutTimer_, &QTimer::timeout, this, &SerialCommunicator::onHeartbeatTimeout);//心跳超时 | |||||
//初始化成员变量 | |||||
init(); | |||||
} | |||||
SerialCommunicator::~SerialCommunicator() | |||||
{ | |||||
if (connected_) | |||||
{ | |||||
serialPort_->close(); | |||||
} | |||||
} | |||||
// 参数设置实现 | |||||
void SerialCommunicator::setPortName(const QString &portName) | |||||
{ | |||||
portName_ = portName; | |||||
} | |||||
void SerialCommunicator::setBaudRate(int baudRate) | |||||
{ | |||||
baudRate_ = baudRate; | |||||
} | |||||
void SerialCommunicator::setDataBits(QSerialPort::DataBits dataBits) | |||||
{ | |||||
dataBits_ = dataBits; | |||||
} | |||||
void SerialCommunicator::setStopBits(QSerialPort::StopBits stopBits) | |||||
{ | |||||
stopBits_ = stopBits; | |||||
} | |||||
void SerialCommunicator::setParity(QSerialPort::Parity parity) | |||||
{ | |||||
parity_ = parity; | |||||
} | |||||
/******************************************************************** | |||||
* 初始化 | |||||
********************************************************************/ | |||||
void SerialCommunicator::init() | |||||
{ | |||||
//串口参数 | |||||
portName_ = ""; | |||||
baudRate_ = 9600; | |||||
dataBits_ = QSerialPort::Data8; | |||||
stopBits_ = QSerialPort::OneStop; | |||||
parity_ = QSerialPort::NoParity; | |||||
connected_ = false; | |||||
//心跳参数 | |||||
heartbeatInterval_ = 3000; | |||||
heartbeatTimeout_ = 1000; | |||||
isCanHeartbeat_ = true; | |||||
} | |||||
/******************************************************************** | |||||
* 连接设备 | |||||
********************************************************************/ | |||||
bool SerialCommunicator::connectDevice() | |||||
{ | |||||
if (connected_) | |||||
{ | |||||
emit statusChanged("已处于连接状态"); | |||||
return true; | |||||
} | |||||
// 检查端口名是否有效 | |||||
if (portName_.isEmpty()) | |||||
{ | |||||
emit errorOccurred("未设置端口名"); | |||||
return false; | |||||
} | |||||
// 配置串口参数 | |||||
serialPort_->setPortName(portName_); | |||||
serialPort_->setBaudRate(baudRate_); | |||||
serialPort_->setDataBits(dataBits_); | |||||
serialPort_->setStopBits(stopBits_); | |||||
serialPort_->setParity(parity_); | |||||
serialPort_->setFlowControl(QSerialPort::NoFlowControl); // 默认无流控 | |||||
// 尝试打开串口 | |||||
if (serialPort_->open(QIODevice::ReadWrite)) | |||||
{ | |||||
connected_ = true; | |||||
emit statusChanged("连接成功: " + portName_); | |||||
qDebug()<<__FILE__<<__LINE__<<"心跳机制启动"; | |||||
//启动心跳机制 | |||||
startHeartbeat(); | |||||
return true; | |||||
} | |||||
else | |||||
{ | |||||
emit errorOccurred("连接失败: " + serialPort_->errorString()); | |||||
return false; | |||||
} | |||||
} | |||||
/******************************************************************** | |||||
* 断开连接 | |||||
********************************************************************/ | |||||
void SerialCommunicator::disconnectDevice() | |||||
{ | |||||
if (!connected_) return; | |||||
serialPort_->close(); | |||||
connected_ = false; | |||||
//停止心跳机制 | |||||
stopHeartbeat(); | |||||
emit connectionDisconnected(); | |||||
} | |||||
/******************************************************************** | |||||
* 启动心跳机制 | |||||
********************************************************************/ | |||||
void SerialCommunicator::startHeartbeat() | |||||
{ | |||||
if(connected_) | |||||
{ | |||||
//启动心跳 | |||||
heartbeatTimer_->start(heartbeatInterval_); | |||||
//qDebug() << "文件:" << __FILE__ << "行:" << __LINE__ << "心跳机制启动" << heartbeatInterval_ << "ms"; | |||||
} | |||||
} | |||||
/******************************************************************** | |||||
* 停止心跳机制 | |||||
********************************************************************/ | |||||
void SerialCommunicator::stopHeartbeat() | |||||
{ | |||||
heartbeatTimer_->stop(); | |||||
heartbeatTimeoutTimer_->stop(); | |||||
//qDebug() << "文件:" << __FILE__ << "行:" << __LINE__ << "心跳机制终止" << heartbeatInterval_ << "ms"; | |||||
} | |||||
/******************************************************************** | |||||
* 心跳触发,发送心跳帧 | |||||
********************************************************************/ | |||||
void SerialCommunicator::onSendHeartbeat() | |||||
{ | |||||
if(false == isCanHeartbeat_) | |||||
return; | |||||
if(!connected_) | |||||
{ | |||||
qDebug() << "文件:" << __FILE__ << "行:" << __LINE__ << "未连接,停止发送心跳帧"; | |||||
return; | |||||
} | |||||
qint64 success = serialPort_->write(heartbeatFrame_); | |||||
//qDebug() << "文件:" << __FILE__ << "行:" << __LINE__ << "心跳帧: " <<heartbeatFrame_.toHex(' ').toUpper(); | |||||
if(-1 == success) | |||||
{ | |||||
qDebug() << "文件:" << __FILE__ << "行:" << __LINE__ << "心跳帧发送失败"; | |||||
} | |||||
else | |||||
{ | |||||
//发送成功,启动响应超时定时器 | |||||
heartbeatTimeoutTimer_->start(heartbeatTimeout_); | |||||
qDebug() << "文件:" << __FILE__ << "行:" << __LINE__ << "心跳帧发送成功"; | |||||
} | |||||
} | |||||
/******************************************************************** | |||||
* 心跳超时触发,断开连接 | |||||
********************************************************************/ | |||||
void SerialCommunicator::onHeartbeatTimeout() | |||||
{ | |||||
//qDebug() << "文件:" << __FILE__ << "行:" << __LINE__ << "心跳超时" << heartbeatTimeout_; | |||||
disconnectDevice(); | |||||
} | |||||
/******************************************************************** | |||||
* 设置超时参数 | |||||
********************************************************************/ | |||||
bool SerialCommunicator::setTimeoutSettings(int timeoutMs, int maxRetries) | |||||
{ | |||||
if(timeoutMs < 500 || timeoutMs > 5000 || maxRetries < 1 || maxRetries >5) | |||||
{ | |||||
return false; | |||||
} | |||||
timeoutHandler_.setTimeoutInterval(timeoutMs); | |||||
timeoutHandler_.setRetryCount(maxRetries); | |||||
return true; | |||||
} | |||||
/******************************************************************** | |||||
* 获取超时参数 | |||||
********************************************************************/ | |||||
void SerialCommunicator::getTimeoutSettings(int &timeoutMs,int &maxRetries) | |||||
{ | |||||
timeoutMs=timeoutHandler_.getTimeoutInterval(); | |||||
maxRetries=timeoutHandler_.getRetryCount(); | |||||
} | |||||
/******************************************************************** | |||||
* 发送数据,启动计时 | |||||
********************************************************************/ | |||||
bool SerialCommunicator::sendData(const QByteArray &data) | |||||
{ | |||||
if (!connected_) | |||||
{ | |||||
emit errorOccurred("发送失败: 未连接设备"); | |||||
return false; | |||||
} | |||||
if (data.isEmpty()) | |||||
{ | |||||
emit errorOccurred("发送失败: 数据为空"); | |||||
return false; | |||||
} | |||||
isCanHeartbeat_ = false; | |||||
// 停止并重置超时处理器 | |||||
timeoutHandler_.stop(); | |||||
// 保存数据用于可能的重发 | |||||
pendingData_ = data; | |||||
// 发送数据 | |||||
qint64 bytesWritten = serialPort_->write(data); | |||||
if (-1 == bytesWritten) | |||||
{ | |||||
emit errorOccurred("发送失败: " + serialPort_->errorString()); | |||||
return false; | |||||
} | |||||
else | |||||
{ | |||||
// 启动超时计时 | |||||
timeoutHandler_.start(); | |||||
return true; | |||||
} | |||||
} | |||||
/******************************************************************** | |||||
* 检查是否连接 | |||||
********************************************************************/ | |||||
bool SerialCommunicator::isConnected() const | |||||
{ | |||||
return connected_; | |||||
} | |||||
/******************************************************************** | |||||
* 获取可用串口 | |||||
********************************************************************/ | |||||
QStringList SerialCommunicator::getAvailablePorts() | |||||
{ | |||||
QStringList portList; | |||||
for (const QSerialPortInfo &info : QSerialPortInfo::availablePorts()) | |||||
{ | |||||
QString portDescription = info.portName() + " (" + info.description() + ")"; | |||||
portList << portDescription; | |||||
} | |||||
return portList; | |||||
} | |||||
/******************************************************************** | |||||
* 串口接收到数据,停止计时,并发送信号 | |||||
********************************************************************/ | |||||
void SerialCommunicator::onReadyRead() | |||||
{ | |||||
//qDebug() <<"文件:"<<__FILE__ << "行数:" << __LINE__ << " 收到响应"; | |||||
if (!connected_) return; | |||||
QByteArray data = serialPort_->readAll(); | |||||
if (data.isEmpty()) | |||||
{ | |||||
emit statusChanged("接收为空数据"); | |||||
return; | |||||
} | |||||
//如果心跳在计时,停止 | |||||
if(heartbeatTimeoutTimer_->isActive()) | |||||
{ | |||||
heartbeatTimeoutTimer_->stop(); | |||||
} | |||||
// 停止超时计时器(收到响应) | |||||
if (timeoutHandler_.isRunning()) | |||||
{ | |||||
timeoutHandler_.stop(); | |||||
} | |||||
//处于心跳阶段,数据直接丢弃 | |||||
if(true == isCanHeartbeat_) | |||||
{ | |||||
data.clear(); | |||||
} | |||||
// 转换为带空格的十六进制字符串 | |||||
QString hexData = data.toHex(' ').toUpper(); | |||||
// 发射数据接收信号 | |||||
emit dataReceived(hexData.trimmed()); | |||||
} | |||||
// 处理超时信号 | |||||
void SerialCommunicator::onTimeoutOccurred(int currentRetry) | |||||
{ | |||||
if (!connected_) | |||||
{ | |||||
qDebug() <<"文件:"<<__FILE__ << "行数:" << __LINE__ << "设备未连接,取消重试"; | |||||
return; | |||||
} | |||||
// 重发数据 | |||||
qint64 bytesWritten = serialPort_->write(pendingData_); | |||||
if (-1 == bytesWritten) | |||||
{ | |||||
emit errorOccurred("重发失败: " + serialPort_->errorString()); | |||||
timeoutHandler_.stop(); // 停止重试 | |||||
} | |||||
else | |||||
{ | |||||
emit statusChanged(QString("超时重发 (%1/%2)").arg(currentRetry) | |||||
.arg(timeoutHandler_.getRetryCount())); | |||||
} | |||||
} | |||||
// 处理最大重试次数达到 | |||||
void SerialCommunicator::onMaxRetriesReached() | |||||
{ | |||||
qDebug() <<"文件:"<<__FILE__ << "行数:" << __LINE__ << "达到最大重试次数"; | |||||
emit statusChanged("通信超时,请检查连接后再试!"); | |||||
disconnectDevice(); | |||||
} | |||||
// 处理串口错误(如设备拔出) | |||||
void SerialCommunicator::onSerialError(QSerialPort::SerialPortError error) | |||||
{ | |||||
if (QSerialPort::ResourceError == error && connected_) | |||||
{ | |||||
qDebug() <<"文件:"<<__FILE__ << "行数:" << __LINE__ << "串口错误: 设备可能已被拔出或不可用"; | |||||
disconnectDevice(); // 调用断开连接,触发 connectionDisconnected 信号 | |||||
} | |||||
} |
@@ -0,0 +1,118 @@ | |||||
/*********************************************************************** | |||||
* Copyright (C) 2025-, XINJE Co., Ltd. | |||||
* | |||||
* File Name: // serial_communication.cpp | |||||
* Description: // 主站超时处理模块源文件 | |||||
* Others: // | |||||
* Version: // v1.0 | |||||
* Author: // weikai,XINJE | |||||
* Date: // 2025-7-30 | |||||
***********************************************************************/ | |||||
#include "timeout_handler.h" | |||||
#include "mainwindow.h" | |||||
#include<QDebug> | |||||
TimeoutHandler::TimeoutHandler(QObject *parent) | |||||
: QObject(parent), | |||||
timer_(new QTimer(this)), | |||||
timeoutInterval_(DEFAULT_TIMEOUT), | |||||
maxRetryCount_(DEFAULT_MAXRETRISE), | |||||
currentRetryCount_(0) | |||||
{ | |||||
// 配置定时器为单次触发 | |||||
timer_->setSingleShot(true); | |||||
// 连接定时器超时信号到内部处理函数 | |||||
connect(timer_, &QTimer::timeout, this, &TimeoutHandler::onTimerTimeout); | |||||
} | |||||
/******************************************************************** | |||||
* 设置超时时间 | |||||
********************************************************************/ | |||||
void TimeoutHandler::setTimeoutInterval(int msec) | |||||
{ | |||||
timeoutInterval_ = msec; | |||||
} | |||||
/******************************************************************** | |||||
* 获取超时时间 | |||||
********************************************************************/ | |||||
int TimeoutHandler::getTimeoutInterval() const | |||||
{ | |||||
return timeoutInterval_; | |||||
} | |||||
/******************************************************************** | |||||
* 设置最大重发次数 | |||||
********************************************************************/ | |||||
void TimeoutHandler::setRetryCount(int count) | |||||
{ | |||||
maxRetryCount_ = count; | |||||
} | |||||
/******************************************************************** | |||||
* 获取设置的最大重发次数 | |||||
********************************************************************/ | |||||
int TimeoutHandler::getRetryCount() const | |||||
{ | |||||
return maxRetryCount_; | |||||
} | |||||
/******************************************************************** | |||||
* 启动超时计时 | |||||
********************************************************************/ | |||||
void TimeoutHandler::start() | |||||
{ | |||||
// 若已在运行,先停止 | |||||
if (timer_->isActive()) | |||||
{ | |||||
timer_->stop(); | |||||
} | |||||
// 启动定时器 | |||||
timer_->start(timeoutInterval_); | |||||
} | |||||
/******************************************************************** | |||||
* 停止超时计时,重置重发次数 | |||||
********************************************************************/ | |||||
void TimeoutHandler::stop() | |||||
{ | |||||
if (timer_->isActive()) | |||||
{ | |||||
timer_->stop(); | |||||
} | |||||
// 重置重试计数 | |||||
currentRetryCount_ = 0; | |||||
} | |||||
/******************************************************************** | |||||
* 判断是否在计时中 | |||||
********************************************************************/ | |||||
bool TimeoutHandler::isRunning() const | |||||
{ | |||||
return timer_->isActive(); | |||||
} | |||||
/******************************************************************** | |||||
* 超时信号触发的槽函数,发送超时发生给串口通信模块,启动重发 | |||||
********************************************************************/ | |||||
void TimeoutHandler::onTimerTimeout() | |||||
{ | |||||
// 增加重发计数 | |||||
currentRetryCount_++; | |||||
// 检查是否达到最大重发次数 | |||||
if (currentRetryCount_ <= maxRetryCount_) | |||||
{ | |||||
// 发送超时信号,显示从 1 开始的计数 | |||||
emit timeoutOccurred(currentRetryCount_); | |||||
// 继续下一次重发 | |||||
timer_->start(timeoutInterval_); | |||||
} | |||||
else | |||||
{ | |||||
// 达到最大重发次数,发送信号并重置计数 | |||||
emit maxRetriesReached(); | |||||
currentRetryCount_ = 0; | |||||
} | |||||
} |
@@ -0,0 +1,38 @@ | |||||
QT += core gui | |||||
QT += serialport | |||||
QT += core | |||||
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets | |||||
CONFIG += c++11 | |||||
# The following define makes your compiler emit warnings if you use | |||||
# any Qt feature that has been marked deprecated (the exact warnings | |||||
# depend on your compiler). Please consult the documentation of the | |||||
# deprecated API in order to know how to port your code away from it. | |||||
DEFINES += QT_DEPRECATED_WARNINGS | |||||
# You can also make your code fail to compile if it uses deprecated APIs. | |||||
# In order to do so, uncomment the following line. | |||||
# You can also select to disable deprecated APIs only up to a certain version of Qt. | |||||
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 | |||||
SOURCES += \ | |||||
main.cpp \ | |||||
mainwindow.cpp \ | |||||
modbus_master.cpp \ | |||||
serial_communication.cpp \ | |||||
timeout_handler.cpp | |||||
HEADERS += \ | |||||
mainwindow.h \ | |||||
modbus_master.h \ | |||||
serial_communication.h \ | |||||
timeout_handler.h | |||||
FORMS += \ | |||||
mainwindow.ui | |||||
# Default rules for deployment. | |||||
qnx: target.path = /tmp/$${TARGET}/bin | |||||
else: unix:!android: target.path = /opt/$${TARGET}/bin | |||||
!isEmpty(target.path): INSTALLS += target |
@@ -0,0 +1,165 @@ | |||||
#include <QtTest> | |||||
#include "../ModbusMatser/modbus_master.h" | |||||
// 单元测试类,用于测试 ModbusRTUMaster 类的解析函数 | |||||
class Test_Modbus : public QObject | |||||
{ | |||||
Q_OBJECT | |||||
public: | |||||
Test_Modbus(); | |||||
~Test_Modbus(); | |||||
private slots: | |||||
// 测试用例声明 | |||||
void test_parseResponse(); // 测试 parseResponse 函数 | |||||
void test_parseReadCoilsResponse(); // 测试 parseReadCoilsResponse 函数 | |||||
void test_parseReadHoldingRegistersResponse(); // 测试 parseReadHoldingRegistersResponse 函数 | |||||
}; | |||||
// 构造函数 | |||||
Test_Modbus::Test_Modbus() | |||||
{ | |||||
} | |||||
// 析构函数 | |||||
Test_Modbus::~Test_Modbus() | |||||
{ | |||||
} | |||||
// 测试 parseResponse 函数 | |||||
void Test_Modbus::test_parseResponse() | |||||
{ | |||||
ModbusRTUMaster modbus; | |||||
quint8 slaveAddr, funcCode, errorCode; | |||||
QVector<quint16> parsedData; | |||||
// 测试用例 1: 正常读保持寄存器响应(功能码03,2个寄存器) | |||||
// 帧格式: [从站地址:01] [功能码:03] [字节数:04] [数据:0001 0003] [CRC] | |||||
QByteArray response = QByteArray::fromHex("01030400010003EBF2"); | |||||
modbus.setRegCount(2); // 设置期望的寄存器数量 | |||||
QVERIFY(modbus.parseResponse(response, slaveAddr, funcCode, parsedData, errorCode)); | |||||
QCOMPARE(slaveAddr, static_cast<quint8>(0x01)); // 验证从站地址 | |||||
QCOMPARE(funcCode, static_cast<quint8>(0x03)); // 验证功能码 | |||||
QCOMPARE(errorCode, ModbusRTUMaster::NO_ERROR); // 验证无错误 | |||||
QCOMPARE(parsedData.size(), 2); // 验证数据长度 | |||||
QCOMPARE(parsedData[0], static_cast<quint16>(0x0001)); // 验证寄存器1 | |||||
QCOMPARE(parsedData[1], static_cast<quint16>(0x0003)); // 验证寄存器3 | |||||
// 测试用例 2: 正常读线圈响应(功能码01,8个线圈) | |||||
// 帧格式: [从站地址:01] [功能码:01] [字节数:01] [数据:FF] [CRC] | |||||
modbus.setCoilCount(8); // 设置期望的线圈数量 | |||||
response = QByteArray::fromHex("010101FF11C8"); | |||||
QVERIFY(modbus.parseResponse(response, slaveAddr, funcCode, parsedData, errorCode)); | |||||
QCOMPARE(slaveAddr, static_cast<quint8>(0x01)); // 验证从站地址 | |||||
QCOMPARE(funcCode, static_cast<quint8>(0x01)); // 验证功能码 | |||||
QCOMPARE(errorCode, ModbusRTUMaster::NO_ERROR); // 验证无错误 | |||||
QCOMPARE(parsedData.size(), 8); // 验证数据长度 | |||||
for (int i = 0; i < 8; ++i) | |||||
{ | |||||
QCOMPARE(parsedData[i], static_cast<quint16>(1)); // 验证所有线圈为1 | |||||
} | |||||
// 测试用例 3: 错误响应(功能码83,非法功能) | |||||
// 帧格式: [从站地址:01] [功能码:83] [错误码:01] [CRC] | |||||
parsedData.clear(); | |||||
response = QByteArray::fromHex("01830180F0"); | |||||
QVERIFY(modbus.parseResponse(response, slaveAddr, funcCode, parsedData, errorCode)); | |||||
QCOMPARE(slaveAddr, static_cast<quint8>(0x01)); // 验证从站地址 | |||||
QCOMPARE(funcCode, static_cast<quint8>(0x83)); // 验证功能码 | |||||
QCOMPARE(errorCode, static_cast<quint8>(0x01)); // 验证错误码 | |||||
QCOMPARE(parsedData.size(), 0); // 验证无数据 | |||||
// 测试用例 4: 数据帧过短(无效帧) | |||||
response = QByteArray::fromHex("0103"); // 只有2字节,少于最小长度 | |||||
QVERIFY(!modbus.parseResponse(response, slaveAddr, funcCode, parsedData, errorCode)); | |||||
// 测试用例 5: CRC错误 | |||||
// 帧格式: [从站地址:01] [功能码:03] [字节数:04] [数据:0001 0004] [错误CRC] | |||||
response = QByteArray::fromHex("01030400010004AB30"); | |||||
QVERIFY(!modbus.parseResponse(response, slaveAddr, funcCode, parsedData, errorCode)); | |||||
// 测试用例 6: 不支持的功能码 | |||||
// 帧格式: [从站地址:01] [功能码:05] [数据] [CRC] | |||||
response = QByteArray::fromHex("01050400010002D6E9"); | |||||
QVERIFY(!modbus.parseResponse(response, slaveAddr, funcCode, parsedData, errorCode)); | |||||
} | |||||
// 测试 parseReadCoilsResponse 函数 | |||||
void Test_Modbus::test_parseReadCoilsResponse() | |||||
{ | |||||
ModbusRTUMaster modbus; | |||||
QVector<bool> coils; | |||||
// 测试用例 1: 正常读线圈响应(8个线圈全1) | |||||
// 数据格式: [字节数:01] [数据:FF] | |||||
modbus.setCoilCount(8); | |||||
QByteArray responseData = QByteArray::fromHex("01FF"); | |||||
QVERIFY(modbus.parseReadCoilsResponse(responseData, coils)); | |||||
QCOMPARE(coils.size(), 8); // 验证线圈数量 | |||||
for (int i = 0; i < 8; ++i) | |||||
{ | |||||
QCOMPARE(coils[i], true); // 验证每个线圈状态为1 | |||||
} | |||||
// 测试用例 2: 正常读线圈响应(5个线圈,前5位为1) | |||||
// 数据格式: [字节数:01] [数据:1F] | |||||
modbus.setCoilCount(5); | |||||
responseData = QByteArray::fromHex("011F"); | |||||
QVERIFY(modbus.parseReadCoilsResponse(responseData, coils)); | |||||
QCOMPARE(coils.size(), 5); // 验证线圈数量 | |||||
for (int i = 0; i < 5; ++i) | |||||
{ | |||||
QCOMPARE(coils[i], true); // 验证每个线圈状态为1 | |||||
} | |||||
// 测试用例 3: 字节数不匹配 | |||||
// 数据格式: [字节数:02] [数据:03](数据长度与字节数声明不符) | |||||
responseData = QByteArray::fromHex("0203"); | |||||
QVERIFY(!modbus.parseReadCoilsResponse(responseData, coils)); | |||||
// 测试用例 4: 空数据 | |||||
responseData = QByteArray(); | |||||
QVERIFY(!modbus.parseReadCoilsResponse(responseData, coils)); | |||||
} | |||||
// 测试 parseReadHoldingRegistersResponse 函数 | |||||
void Test_Modbus::test_parseReadHoldingRegistersResponse() | |||||
{ | |||||
ModbusRTUMaster modbus; | |||||
QVector<quint16> registers; | |||||
// 测试用例 1: 正常读保持寄存器响应(2个寄存器) | |||||
// 数据格式: [字节数:04] [数据:0001 0002] | |||||
modbus.setRegCount(2); | |||||
QByteArray responseData = QByteArray::fromHex("0400010002"); | |||||
QVERIFY(modbus.parseReadHoldingRegistersResponse(responseData, registers)); | |||||
QCOMPARE(registers.size(), 2); // 验证寄存器数量 | |||||
QCOMPARE(registers[0], static_cast<quint16>(0x0001)); // 验证寄存器1 | |||||
QCOMPARE(registers[1], static_cast<quint16>(0x0002)); // 验证寄存器2 | |||||
// 测试用例 2: 读取10个寄存器 | |||||
// 数据格式: [字节数:14] [数据:0001 0002 ... 000A] | |||||
modbus.setRegCount(10); | |||||
responseData = QByteArray::fromHex("14000100020003000400050006000700080009000A"); | |||||
QVERIFY(modbus.parseReadHoldingRegistersResponse(responseData, registers)); | |||||
QCOMPARE(registers.size(), 10); // 验证寄存器数量 | |||||
for (int i = 0; i < 10; ++i) | |||||
{ | |||||
QCOMPARE(registers[i], static_cast<quint16>(i + 1)); // 验证每个寄存器值 | |||||
} | |||||
// 测试用例 3: 字节数不匹配 | |||||
// 数据格式: [字节数:02] [数据:0001](数据长度与字节数声明不符) | |||||
responseData = QByteArray::fromHex("020001"); | |||||
QVERIFY(!modbus.parseReadHoldingRegistersResponse(responseData, registers)); | |||||
// 测试用例 4: 空数据 | |||||
responseData = QByteArray(); | |||||
QVERIFY(!modbus.parseReadHoldingRegistersResponse(responseData, registers)); | |||||
} | |||||
QTEST_APPLESS_MAIN(Test_Modbus) | |||||
#include "tst_test_modbus.moc" |
@@ -0,0 +1,18 @@ | |||||
QT += testlib widgets serialport | |||||
QT += testlib | |||||
QT -= gui | |||||
TEMPLATE = app | |||||
SOURCES += tst_test_modbus.cpp \ | |||||
../ModbusMatser/modbus_master.cpp | |||||
HEADERS += \ | |||||
../ModbusMatser/modbus_master.h | |||||
TARGET = modbus_master | |||||
CONFIG += console | |||||
CONFIG -= app_bundle | |||||
@@ -0,0 +1,319 @@ | |||||
<?xml version="1.0" encoding="UTF-8"?> | |||||
<!DOCTYPE QtCreatorProject> | |||||
<!-- Written by QtCreator 4.11.1, 2025-08-04T10:01:37. --> | |||||
<qtcreator> | |||||
<data> | |||||
<variable>EnvironmentId</variable> | |||||
<value type="QByteArray">{54b998bd-0b43-4b57-8d79-23e6237f0a57}</value> | |||||
</data> | |||||
<data> | |||||
<variable>ProjectExplorer.Project.ActiveTarget</variable> | |||||
<value type="int">0</value> | |||||
</data> | |||||
<data> | |||||
<variable>ProjectExplorer.Project.EditorSettings</variable> | |||||
<valuemap type="QVariantMap"> | |||||
<value type="bool" key="EditorConfiguration.AutoIndent">true</value> | |||||
<value type="bool" key="EditorConfiguration.AutoSpacesForTabs">false</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="int" 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.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="bool" key="EditorConfiguration.ScrollWheelZooming">true</value> | |||||
<value type="bool" key="EditorConfiguration.ShowMargin">false</value> | |||||
<value type="int" key="EditorConfiguration.SmartBackspaceBehavior">0</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="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="bool" key="EditorConfiguration.inEntireDocument">false</value> | |||||
</valuemap> | |||||
</data> | |||||
<data> | |||||
<variable>ProjectExplorer.Project.PluginSettings</variable> | |||||
<valuemap type="QVariantMap"> | |||||
<valuelist type="QVariantList" key="ClangCodeModel.CustomCommandLineKey"> | |||||
<value type="QString">-fno-delayed-template-parsing</value> | |||||
</valuelist> | |||||
<value type="bool" key="ClangCodeModel.UseGlobalConfig">true</value> | |||||
</valuemap> | |||||
</data> | |||||
<data> | |||||
<variable>ProjectExplorer.Project.Target.0</variable> | |||||
<valuemap type="QVariantMap"> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Desktop Qt 5.14.2 MinGW 64-bit</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Desktop Qt 5.14.2 MinGW 64-bit</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">qt.qt5.5142.win64_mingw73_kit</value> | |||||
<value type="int" key="ProjectExplorer.Target.ActiveBuildConfiguration">0</value> | |||||
<value type="int" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value> | |||||
<value type="int" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value> | |||||
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.0"> | |||||
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">D:/QT/QTProject/build-untitled-Desktop_Qt_5_14_2_MinGW_64_bit-Debug</value> | |||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0"> | |||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0"> | |||||
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">QtProjectManager.QMakeBuildStep</value> | |||||
<value type="bool" key="QtProjectManager.QMakeBuildStep.LinkQmlDebuggingLibrary">true</value> | |||||
<value type="QString" key="QtProjectManager.QMakeBuildStep.QMakeArguments"></value> | |||||
<value type="bool" key="QtProjectManager.QMakeBuildStep.QMakeForced">false</value> | |||||
<value type="bool" key="QtProjectManager.QMakeBuildStep.SeparateDebugInfo">false</value> | |||||
<value type="bool" key="QtProjectManager.QMakeBuildStep.UseQtQuickCompiler">false</value> | |||||
</valuemap> | |||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.1"> | |||||
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value> | |||||
<valuelist type="QVariantList" key="Qt4ProjectManager.MakeStep.BuildTargets"/> | |||||
<value type="bool" key="Qt4ProjectManager.MakeStep.Clean">false</value> | |||||
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments"></value> | |||||
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeCommand"></value> | |||||
<value type="bool" key="Qt4ProjectManager.MakeStep.OverrideMakeflags">false</value> | |||||
</valuemap> | |||||
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">2</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Build</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Build</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="bool" key="ProjectExplorer.BuildStep.Enabled">true</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value> | |||||
<valuelist type="QVariantList" key="Qt4ProjectManager.MakeStep.BuildTargets"/> | |||||
<value type="bool" key="Qt4ProjectManager.MakeStep.Clean">true</value> | |||||
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments">clean</value> | |||||
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeCommand"></value> | |||||
<value type="bool" key="Qt4ProjectManager.MakeStep.OverrideMakeflags">false</value> | |||||
</valuemap> | |||||
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">1</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Clean</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Clean</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.UserEnvironmentChanges"/> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Debug</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.Qt4BuildConfiguration</value> | |||||
<value type="int" key="Qt4ProjectManager.Qt4BuildConfiguration.BuildConfiguration">2</value> | |||||
</valuemap> | |||||
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.1"> | |||||
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">D:/QT/QTProject/build-untitled-Desktop_Qt_5_14_2_MinGW_64_bit-Release</value> | |||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0"> | |||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0"> | |||||
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">QtProjectManager.QMakeBuildStep</value> | |||||
<value type="bool" key="QtProjectManager.QMakeBuildStep.LinkQmlDebuggingLibrary">false</value> | |||||
<value type="QString" key="QtProjectManager.QMakeBuildStep.QMakeArguments"></value> | |||||
<value type="bool" key="QtProjectManager.QMakeBuildStep.QMakeForced">false</value> | |||||
<value type="bool" key="QtProjectManager.QMakeBuildStep.SeparateDebugInfo">false</value> | |||||
<value type="bool" key="QtProjectManager.QMakeBuildStep.UseQtQuickCompiler">true</value> | |||||
</valuemap> | |||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.1"> | |||||
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value> | |||||
<valuelist type="QVariantList" key="Qt4ProjectManager.MakeStep.BuildTargets"/> | |||||
<value type="bool" key="Qt4ProjectManager.MakeStep.Clean">false</value> | |||||
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments"></value> | |||||
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeCommand"></value> | |||||
<value type="bool" key="Qt4ProjectManager.MakeStep.OverrideMakeflags">false</value> | |||||
</valuemap> | |||||
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">2</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Build</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Build</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="bool" key="ProjectExplorer.BuildStep.Enabled">true</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value> | |||||
<valuelist type="QVariantList" key="Qt4ProjectManager.MakeStep.BuildTargets"/> | |||||
<value type="bool" key="Qt4ProjectManager.MakeStep.Clean">true</value> | |||||
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments">clean</value> | |||||
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeCommand"></value> | |||||
<value type="bool" key="Qt4ProjectManager.MakeStep.OverrideMakeflags">false</value> | |||||
</valuemap> | |||||
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">1</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Clean</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Clean</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.UserEnvironmentChanges"/> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Release</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.Qt4BuildConfiguration</value> | |||||
<value type="int" key="Qt4ProjectManager.Qt4BuildConfiguration.BuildConfiguration">0</value> | |||||
</valuemap> | |||||
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.2"> | |||||
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">D:/QT/QTProject/build-untitled-Desktop_Qt_5_14_2_MinGW_64_bit-Profile</value> | |||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0"> | |||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0"> | |||||
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">QtProjectManager.QMakeBuildStep</value> | |||||
<value type="bool" key="QtProjectManager.QMakeBuildStep.LinkQmlDebuggingLibrary">true</value> | |||||
<value type="QString" key="QtProjectManager.QMakeBuildStep.QMakeArguments"></value> | |||||
<value type="bool" key="QtProjectManager.QMakeBuildStep.QMakeForced">false</value> | |||||
<value type="bool" key="QtProjectManager.QMakeBuildStep.SeparateDebugInfo">true</value> | |||||
<value type="bool" key="QtProjectManager.QMakeBuildStep.UseQtQuickCompiler">true</value> | |||||
</valuemap> | |||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.1"> | |||||
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value> | |||||
<valuelist type="QVariantList" key="Qt4ProjectManager.MakeStep.BuildTargets"/> | |||||
<value type="bool" key="Qt4ProjectManager.MakeStep.Clean">false</value> | |||||
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments"></value> | |||||
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeCommand"></value> | |||||
<value type="bool" key="Qt4ProjectManager.MakeStep.OverrideMakeflags">false</value> | |||||
</valuemap> | |||||
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">2</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Build</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Build</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="bool" key="ProjectExplorer.BuildStep.Enabled">true</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value> | |||||
<valuelist type="QVariantList" key="Qt4ProjectManager.MakeStep.BuildTargets"/> | |||||
<value type="bool" key="Qt4ProjectManager.MakeStep.Clean">true</value> | |||||
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments">clean</value> | |||||
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeCommand"></value> | |||||
<value type="bool" key="Qt4ProjectManager.MakeStep.OverrideMakeflags">false</value> | |||||
</valuemap> | |||||
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">1</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Clean</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Clean</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.UserEnvironmentChanges"/> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Profile</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.Qt4BuildConfiguration</value> | |||||
<value type="int" key="Qt4ProjectManager.Qt4BuildConfiguration.BuildConfiguration">0</value> | |||||
</valuemap> | |||||
<value type="int" key="ProjectExplorer.Target.BuildConfigurationCount">3</value> | |||||
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0"> | |||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0"> | |||||
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">0</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Deploy</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Deploy</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value> | |||||
</valuemap> | |||||
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.DefaultDeployConfiguration</value> | |||||
</valuemap> | |||||
<value type="int" key="ProjectExplorer.Target.DeployConfigurationCount">1</value> | |||||
<valuemap type="QVariantMap" key="ProjectExplorer.Target.PluginSettings"/> | |||||
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.0"> | |||||
<value type="QString" key="Analyzer.Perf.CallgraphMode">dwarf</value> | |||||
<valuelist type="QVariantList" key="Analyzer.Perf.Events"> | |||||
<value type="QString">cpu-cycles</value> | |||||
</valuelist> | |||||
<valuelist type="QVariantList" key="Analyzer.Perf.ExtraArguments"/> | |||||
<value type="int" key="Analyzer.Perf.Frequency">250</value> | |||||
<valuelist type="QVariantList" key="Analyzer.Perf.RecordArguments"> | |||||
<value type="QString">-e</value> | |||||
<value type="QString">cpu-cycles</value> | |||||
<value type="QString">--call-graph</value> | |||||
<value type="QString">dwarf,4096</value> | |||||
<value type="QString">-F</value> | |||||
<value type="QString">250</value> | |||||
</valuelist> | |||||
<value type="QString" key="Analyzer.Perf.SampleMode">-F</value> | |||||
<value type="bool" key="Analyzer.Perf.Settings.UseGlobalSettings">true</value> | |||||
<value type="int" key="Analyzer.Perf.StackSize">4096</value> | |||||
<value type="bool" key="Analyzer.QmlProfiler.AggregateTraces">false</value> | |||||
<value type="bool" key="Analyzer.QmlProfiler.FlushEnabled">false</value> | |||||
<value type="uint" key="Analyzer.QmlProfiler.FlushInterval">1000</value> | |||||
<value type="QString" key="Analyzer.QmlProfiler.LastTraceFile"></value> | |||||
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value> | |||||
<valuelist type="QVariantList" key="Analyzer.Valgrind.AddedSuppressionFiles"/> | |||||
<value type="bool" key="Analyzer.Valgrind.Callgrind.CollectBusEvents">false</value> | |||||
<value type="bool" key="Analyzer.Valgrind.Callgrind.CollectSystime">false</value> | |||||
<value type="bool" key="Analyzer.Valgrind.Callgrind.EnableBranchSim">false</value> | |||||
<value type="bool" key="Analyzer.Valgrind.Callgrind.EnableCacheSim">false</value> | |||||
<value type="bool" key="Analyzer.Valgrind.Callgrind.EnableEventToolTips">true</value> | |||||
<value type="double" key="Analyzer.Valgrind.Callgrind.MinimumCostRatio">0.01</value> | |||||
<value type="double" key="Analyzer.Valgrind.Callgrind.VisualisationMinimumCostRatio">10</value> | |||||
<value type="bool" key="Analyzer.Valgrind.FilterExternalIssues">true</value> | |||||
<value type="QString" key="Analyzer.Valgrind.KCachegrindExecutable">kcachegrind</value> | |||||
<value type="int" key="Analyzer.Valgrind.LeakCheckOnFinish">1</value> | |||||
<value type="int" key="Analyzer.Valgrind.NumCallers">25</value> | |||||
<valuelist type="QVariantList" key="Analyzer.Valgrind.RemovedSuppressionFiles"/> | |||||
<value type="int" key="Analyzer.Valgrind.SelfModifyingCodeDetection">1</value> | |||||
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value> | |||||
<value type="bool" key="Analyzer.Valgrind.ShowReachable">false</value> | |||||
<value type="bool" key="Analyzer.Valgrind.TrackOrigins">true</value> | |||||
<value type="QString" key="Analyzer.Valgrind.ValgrindExecutable">valgrind</value> | |||||
<valuelist type="QVariantList" key="Analyzer.Valgrind.VisibleErrorKinds"> | |||||
<value type="int">0</value> | |||||
<value type="int">1</value> | |||||
<value type="int">2</value> | |||||
<value type="int">3</value> | |||||
<value type="int">4</value> | |||||
<value type="int">5</value> | |||||
<value type="int">6</value> | |||||
<value type="int">7</value> | |||||
<value type="int">8</value> | |||||
<value type="int">9</value> | |||||
<value type="int">10</value> | |||||
<value type="int">11</value> | |||||
<value type="int">12</value> | |||||
<value type="int">13</value> | |||||
<value type="int">14</value> | |||||
</valuelist> | |||||
<value type="int" key="PE.EnvironmentAspect.Base">2</value> | |||||
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/> | |||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.Qt4RunConfiguration:D:/QT/QTProject/untitled/untitled.pro</value> | |||||
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey">D:/QT/QTProject/untitled/untitled.pro</value> | |||||
<value type="QString" key="RunConfiguration.Arguments"></value> | |||||
<value type="bool" key="RunConfiguration.Arguments.multi">false</value> | |||||
<value type="QString" key="RunConfiguration.OverrideDebuggerStartup"></value> | |||||
<value type="bool" key="RunConfiguration.UseCppDebugger">false</value> | |||||
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value> | |||||
<value type="bool" key="RunConfiguration.UseLibrarySearchPath">true</value> | |||||
<value type="bool" key="RunConfiguration.UseMultiProcess">false</value> | |||||
<value type="bool" key="RunConfiguration.UseQmlDebugger">false</value> | |||||
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value> | |||||
<value type="QString" key="RunConfiguration.WorkingDirectory"></value> | |||||
<value type="QString" key="RunConfiguration.WorkingDirectory.default">D:/QT/QTProject/build-untitled-Desktop_Qt_5_14_2_MinGW_64_bit-Debug</value> | |||||
</valuemap> | |||||
<value type="int" key="ProjectExplorer.Target.RunConfigurationCount">1</value> | |||||
</valuemap> | |||||
</data> | |||||
<data> | |||||
<variable>ProjectExplorer.Project.TargetCount</variable> | |||||
<value type="int">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> |