@@ -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> |