├── .gitignore ├── conf ├── 001_ATT_Roll_Ang.conf ├── 002_ATT_Roll_AngVel.conf ├── 004_ATT_Roll_LowpassD.conf ├── 009_ATT_Pitch_LowpassD.conf ├── 011_ATT_Yaw_Ang.conf ├── 012_ATT_Yaw_AngVel.conf ├── 014_ATT_Yaw_LowpassD.conf ├── 016_ATT_Yaw_LowpassIn.conf ├── 006_ATT_Pitch_Ang.conf ├── 007_ATT_Pitch_AngVel.conf ├── 013_ATT_Yaw_FeedFoward.conf ├── 022_PSC_VEL_Z.conf ├── 031_ATT_Roll_LowpassT.conf ├── 032_ATT_Roll_LowpassE.conf ├── 003_ATT_Roll_FeedFoward.conf ├── 008_ATT_Pitch_FeedFoward.conf ├── 025_PIDA_D_Lowpass.conf ├── 026_PIDA_E_Lowpass.conf ├── 021_PSC_POS_Z.conf ├── 020_VD_Z_RC.conf ├── 024_BARO_IMU.conf ├── 005_ATT_Roll_PID.conf ├── 010_ATT_Pitch_PID.conf ├── 015_ATT_Yaw_PID.conf ├── 028_FLOW_X.conf ├── 029_FLOW_Y.conf ├── 030_RPYT.conf ├── 027_PSC_VEL_FF.conf ├── 023_PSC_ACC_Z.conf ├── 019_RC_Failsafe.conf ├── 017_TILT_Ang_ErrZ.conf ├── 018_Yaw_out_off_control.conf └── ReadMe.md ├── chibios log.bin ├── ArduPilotLog.udb ├── resources └── icons │ ├── a01.ico │ ├── a02.ico │ ├── a03.ico │ ├── a04.ico │ ├── a05.ico │ ├── a06.ico │ ├── a07.ico │ ├── a08.ico │ ├── a09.ico │ ├── a10.ico │ ├── a11.ico │ ├── a12.ico │ ├── a13.ico │ ├── a14.ico │ ├── a15.ico │ ├── a16.ico │ ├── a17.ico │ ├── a18.ico │ ├── a19.ico │ ├── a20.ico │ ├── a21.ico │ ├── a22.ico │ ├── a23.ico │ ├── a24.ico │ ├── a25.ico │ ├── a26.ico │ ├── a27.ico │ ├── a28.ico │ ├── a29.ico │ ├── ardupilotlog.ico │ └── ardupilotlog.png ├── ardupilotlog.qrc ├── gitlab.yml ├── doc └── compile.md ├── deploy ├── ardupilotlog.desktop └── ardupilotlog-start.sh ├── main.cpp ├── matlab ├── ReadMe.md ├── FSMAnalysis.m ├── APMLoadDB.m └── char2cell.m ├── src ├── APLReadConf.h ├── DialogLoad.h ├── DataAnalyze.h ├── APLQmlWidgetHolder.ui ├── PythonExporter.h ├── APLQmlWidgetHolder.cpp ├── PythonExporterCSV.h ├── APLQmlWidgetHolder.h ├── APLReadConf.cpp ├── LogStructure.h ├── DialogLoad.cpp ├── Dialog.h ├── APLRead.h ├── APLDB.h ├── APLDataCache.h ├── PythonExporter.cpp ├── PythonExporterCSV.cpp ├── DialogPython.cpp ├── APLDB.cpp ├── APLRead.cpp ├── DataAnalyze.qml ├── APLDataCache.cpp └── DataAnalyzeController.h ├── APLDockWidget.h ├── aplresources.qrc ├── APLDockWidget.cpp ├── APLLoggingCategory.h ├── APLLoggingCategory.cpp ├── ArduPilotLog.pro ├── ReadMe.md ├── mainwindow.h ├── APLSetup.pri ├── mainwindow.ui └── APLCommon.pri /.gitignore: -------------------------------------------------------------------------------- 1 | *.user 2 | build/ -------------------------------------------------------------------------------- /conf/001_ATT_Roll_Ang.conf: -------------------------------------------------------------------------------- 1 | ATT.DesRoll.0.0 2 | ATT.Roll.0.2 3 | -------------------------------------------------------------------------------- /conf/002_ATT_Roll_AngVel.conf: -------------------------------------------------------------------------------- 1 | PIDR.Tar.0.0 2 | PIDR.Act.0.1 3 | -------------------------------------------------------------------------------- /conf/004_ATT_Roll_LowpassD.conf: -------------------------------------------------------------------------------- 1 | PIDR.D.0.0 2 | PIDR.DR.0.1 3 | -------------------------------------------------------------------------------- /conf/009_ATT_Pitch_LowpassD.conf: -------------------------------------------------------------------------------- 1 | PIDP.D.0.0 2 | PIDP.DR.0.1 3 | -------------------------------------------------------------------------------- /conf/011_ATT_Yaw_Ang.conf: -------------------------------------------------------------------------------- 1 | ATT.DesYaw.0.0 2 | ATT.Yaw.0.2 3 | -------------------------------------------------------------------------------- /conf/012_ATT_Yaw_AngVel.conf: -------------------------------------------------------------------------------- 1 | PIDY.Tar.0.0 2 | PIDY.Act.0.1 3 | -------------------------------------------------------------------------------- /conf/014_ATT_Yaw_LowpassD.conf: -------------------------------------------------------------------------------- 1 | PIDY.D.0.0 2 | PIDY.DR.0.1 3 | -------------------------------------------------------------------------------- /conf/016_ATT_Yaw_LowpassIn.conf: -------------------------------------------------------------------------------- 1 | PIDY.LPE.0.0 2 | PIDY.E.0.1 3 | -------------------------------------------------------------------------------- /conf/006_ATT_Pitch_Ang.conf: -------------------------------------------------------------------------------- 1 | ATT.DesPitch.0.0 2 | ATT.Pitch.0.2 3 | -------------------------------------------------------------------------------- /conf/007_ATT_Pitch_AngVel.conf: -------------------------------------------------------------------------------- 1 | PIDP.Tar.0.0 2 | PIDP.Act.0.1 3 | -------------------------------------------------------------------------------- /conf/013_ATT_Yaw_FeedFoward.conf: -------------------------------------------------------------------------------- 1 | FFWD.TarZ.0.0 2 | FFWD.DesZ.0.2 3 | -------------------------------------------------------------------------------- /conf/022_PSC_VEL_Z.conf: -------------------------------------------------------------------------------- 1 | ALT.VT.0.0(1, 0, 0) 2 | ALT.VR.0.2(1, 0, 0) -------------------------------------------------------------------------------- /conf/031_ATT_Roll_LowpassT.conf: -------------------------------------------------------------------------------- 1 | PIDR.Tar.0.0 2 | PIDR.TR.0.1 3 | -------------------------------------------------------------------------------- /conf/032_ATT_Roll_LowpassE.conf: -------------------------------------------------------------------------------- 1 | PIDR.Err.0.0 2 | PIDR.ER.0.1 3 | -------------------------------------------------------------------------------- /conf/003_ATT_Roll_FeedFoward.conf: -------------------------------------------------------------------------------- 1 | FFWD.TarX.0.0 2 | FFWD.DesX.0.2 3 | -------------------------------------------------------------------------------- /conf/008_ATT_Pitch_FeedFoward.conf: -------------------------------------------------------------------------------- 1 | FFWD.TarY.0.0 2 | FFWD.DesY.0.2 3 | -------------------------------------------------------------------------------- /conf/025_PIDA_D_Lowpass.conf: -------------------------------------------------------------------------------- 1 | PIDA.DR.0.2(1, 0, 0) 2 | PIDA.D.0.0(1, 0, 0) -------------------------------------------------------------------------------- /conf/026_PIDA_E_Lowpass.conf: -------------------------------------------------------------------------------- 1 | PIDA.E.0.2(1, 0, 0) 2 | PIDA.LPE.0.0(1, 0, 0) -------------------------------------------------------------------------------- /conf/021_PSC_POS_Z.conf: -------------------------------------------------------------------------------- 1 | ALT.PT.0.0(1, 0, 0) 2 | ALT.PR.0.2(1, 0, 0) 3 | 4 | -------------------------------------------------------------------------------- /conf/020_VD_Z_RC.conf: -------------------------------------------------------------------------------- 1 | RCIN.C3.7.1(0.5, 0, -750) 2 | ALT.VD.7.0(1, 0, 0) 3 | 4 | -------------------------------------------------------------------------------- /conf/024_BARO_IMU.conf: -------------------------------------------------------------------------------- 1 | BARO.Press.0.0(1, 0, 0) 2 | IMU.AccX.0.1(0.2, 0, 101070) 3 | -------------------------------------------------------------------------------- /chibios log.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuWeipeng/ArduPilotLog/HEAD/chibios log.bin -------------------------------------------------------------------------------- /conf/005_ATT_Roll_PID.conf: -------------------------------------------------------------------------------- 1 | PIDR.P.0.0 2 | PIDR.I.0.1 3 | PIDR.D.0.2 4 | PIDR.FF.0.8 5 | -------------------------------------------------------------------------------- /conf/010_ATT_Pitch_PID.conf: -------------------------------------------------------------------------------- 1 | PIDP.P.0.0 2 | PIDP.I.0.1 3 | PIDP.D.0.2 4 | PIDP.FF.0.8 5 | -------------------------------------------------------------------------------- /conf/015_ATT_Yaw_PID.conf: -------------------------------------------------------------------------------- 1 | PIDY.P.0.0 2 | PIDY.I.0.1 3 | PIDY.D.0.2 4 | PIDY.FF.0.8 5 | -------------------------------------------------------------------------------- /ArduPilotLog.udb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuWeipeng/ArduPilotLog/HEAD/ArduPilotLog.udb -------------------------------------------------------------------------------- /conf/028_FLOW_X.conf: -------------------------------------------------------------------------------- 1 | OF.flowX.0.0(1, 0, 0) 2 | OF.bodyX.0.2(1, 0, 0) 3 | IMU.GyrX.0.1(1, 0, 0) -------------------------------------------------------------------------------- /conf/029_FLOW_Y.conf: -------------------------------------------------------------------------------- 1 | OF.flowY.0.0(1, 0, 0) 2 | OF.bodyY.0.2(1, 0, 0) 3 | IMU.GyrY.0.1(1, 0, 0) -------------------------------------------------------------------------------- /resources/icons/a01.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuWeipeng/ArduPilotLog/HEAD/resources/icons/a01.ico -------------------------------------------------------------------------------- /resources/icons/a02.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuWeipeng/ArduPilotLog/HEAD/resources/icons/a02.ico -------------------------------------------------------------------------------- /resources/icons/a03.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuWeipeng/ArduPilotLog/HEAD/resources/icons/a03.ico -------------------------------------------------------------------------------- /resources/icons/a04.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuWeipeng/ArduPilotLog/HEAD/resources/icons/a04.ico -------------------------------------------------------------------------------- /resources/icons/a05.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuWeipeng/ArduPilotLog/HEAD/resources/icons/a05.ico -------------------------------------------------------------------------------- /resources/icons/a06.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuWeipeng/ArduPilotLog/HEAD/resources/icons/a06.ico -------------------------------------------------------------------------------- /resources/icons/a07.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuWeipeng/ArduPilotLog/HEAD/resources/icons/a07.ico -------------------------------------------------------------------------------- /resources/icons/a08.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuWeipeng/ArduPilotLog/HEAD/resources/icons/a08.ico -------------------------------------------------------------------------------- /resources/icons/a09.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuWeipeng/ArduPilotLog/HEAD/resources/icons/a09.ico -------------------------------------------------------------------------------- /resources/icons/a10.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuWeipeng/ArduPilotLog/HEAD/resources/icons/a10.ico -------------------------------------------------------------------------------- /resources/icons/a11.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuWeipeng/ArduPilotLog/HEAD/resources/icons/a11.ico -------------------------------------------------------------------------------- /resources/icons/a12.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuWeipeng/ArduPilotLog/HEAD/resources/icons/a12.ico -------------------------------------------------------------------------------- /resources/icons/a13.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuWeipeng/ArduPilotLog/HEAD/resources/icons/a13.ico -------------------------------------------------------------------------------- /resources/icons/a14.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuWeipeng/ArduPilotLog/HEAD/resources/icons/a14.ico -------------------------------------------------------------------------------- /resources/icons/a15.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuWeipeng/ArduPilotLog/HEAD/resources/icons/a15.ico -------------------------------------------------------------------------------- /resources/icons/a16.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuWeipeng/ArduPilotLog/HEAD/resources/icons/a16.ico -------------------------------------------------------------------------------- /resources/icons/a17.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuWeipeng/ArduPilotLog/HEAD/resources/icons/a17.ico -------------------------------------------------------------------------------- /resources/icons/a18.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuWeipeng/ArduPilotLog/HEAD/resources/icons/a18.ico -------------------------------------------------------------------------------- /resources/icons/a19.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuWeipeng/ArduPilotLog/HEAD/resources/icons/a19.ico -------------------------------------------------------------------------------- /resources/icons/a20.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuWeipeng/ArduPilotLog/HEAD/resources/icons/a20.ico -------------------------------------------------------------------------------- /resources/icons/a21.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuWeipeng/ArduPilotLog/HEAD/resources/icons/a21.ico -------------------------------------------------------------------------------- /resources/icons/a22.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuWeipeng/ArduPilotLog/HEAD/resources/icons/a22.ico -------------------------------------------------------------------------------- /resources/icons/a23.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuWeipeng/ArduPilotLog/HEAD/resources/icons/a23.ico -------------------------------------------------------------------------------- /resources/icons/a24.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuWeipeng/ArduPilotLog/HEAD/resources/icons/a24.ico -------------------------------------------------------------------------------- /resources/icons/a25.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuWeipeng/ArduPilotLog/HEAD/resources/icons/a25.ico -------------------------------------------------------------------------------- /resources/icons/a26.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuWeipeng/ArduPilotLog/HEAD/resources/icons/a26.ico -------------------------------------------------------------------------------- /resources/icons/a27.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuWeipeng/ArduPilotLog/HEAD/resources/icons/a27.ico -------------------------------------------------------------------------------- /resources/icons/a28.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuWeipeng/ArduPilotLog/HEAD/resources/icons/a28.ico -------------------------------------------------------------------------------- /resources/icons/a29.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuWeipeng/ArduPilotLog/HEAD/resources/icons/a29.ico -------------------------------------------------------------------------------- /conf/030_RPYT.conf: -------------------------------------------------------------------------------- 1 | RPYT.r.0.0(1, 0, 0) 2 | RPYT.p.0.1(1, 0, 0) 3 | RPYT.y.0.2(1, 0, 0) 4 | RPYT.t.0.3(1, 0, 0) -------------------------------------------------------------------------------- /conf/027_PSC_VEL_FF.conf: -------------------------------------------------------------------------------- 1 | ALT.VT.0.2(1, 0, 0) 2 | ALT.VD.7.0(1, 0, 0) 3 | ALT.VR.0.1(1, 0, 0) 4 | RCIN.C3.0.3(0.5, 0, -650) -------------------------------------------------------------------------------- /resources/icons/ardupilotlog.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuWeipeng/ArduPilotLog/HEAD/resources/icons/ardupilotlog.ico -------------------------------------------------------------------------------- /resources/icons/ardupilotlog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuWeipeng/ArduPilotLog/HEAD/resources/icons/ardupilotlog.png -------------------------------------------------------------------------------- /conf/023_PSC_ACC_Z.conf: -------------------------------------------------------------------------------- 1 | PIDA.Des.7.0(1, 0, 0) 2 | PIDA.Act.7.2(1, 0, 0) 3 | PIDA.P.0.1(1, 0, 0) 4 | PIDA.I.0.3(1, 0, 0) 5 | PIDA.D.0.4(1, 0, 0) -------------------------------------------------------------------------------- /ardupilotlog.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | src/DataAnalyze.qml 4 | 5 | 6 | -------------------------------------------------------------------------------- /conf/019_RC_Failsafe.conf: -------------------------------------------------------------------------------- 1 | # BATT_LOW_VOLT 3.3v 2 | 3 | MODE.ModeNum.7.0(100, 0, 0) 4 | RCIN.C3.0.1(1, 0, 0) 5 | BAT.VoltR.0.2(1000, 0, 0) 6 | <> BAT:3300 0.3 7 | -------------------------------------------------------------------------------- /conf/017_TILT_Ang_ErrZ.conf: -------------------------------------------------------------------------------- 1 | # deg2rad(120)/4.5 = 0.46542113 2 | 3 | TILT.ErrAng.0.0 4 | TILT.ErrZ.0.2 5 | <> TILT:0.46542113 0.1 6 | ATT.DesYaw.0.5(0.002,0,0) 7 | ATT.Yaw.0.8(0.002,0,0) -------------------------------------------------------------------------------- /gitlab.yml: -------------------------------------------------------------------------------- 1 | ## Default project features settings 2 | default_projects_features: 3 | issues: true 4 | merge_requests: true 5 | wiki: true 6 | snippets: false 7 | builds: false 8 | -------------------------------------------------------------------------------- /doc/compile.md: -------------------------------------------------------------------------------- 1 | ### 编译出错问题及解决办法汇总 2 | #### Error 1117 3 | ##### 提示:“jom: D:\build-ArduPilotLog-Desktop_Qt_5_11_1_MSVC2015_32bit-Release\Makefile.Release [release\ArduPilotLog.exe] Error 1117” 4 | ##### 办法:在 git 流中加标签(Tag),形式为:va.b.c(例:v1.0.0) 5 | -------------------------------------------------------------------------------- /deploy/ardupilotlog.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Type=Application 3 | Name=ArduPilotLog 4 | GenericName=ArduPilot Log 5 | Comment=Read ArduPilot binary log 6 | Icon=ardupilotlog 7 | Exec=ardupilotlog-start.sh 8 | Terminal=false 9 | Categories=Utility; 10 | -------------------------------------------------------------------------------- /deploy/ardupilotlog-start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | HERE="$(dirname "$(readlink -f "${0}")")" 3 | export LD_LIBRARY_PATH="${HERE}/usr/lib/x86_64-linux-gnu":"${HERE}/Qt/libs":$LD_LIBRARY_PATH 4 | export QML2_IMPORT_PATH="${HERE}/Qt/qml" 5 | export QT_PLUGIN_PATH="${HERE}/Qt/plugins" 6 | 7 | "${HERE}/ArduPilotLog" "$@" 8 | -------------------------------------------------------------------------------- /conf/018_Yaw_out_off_control.conf: -------------------------------------------------------------------------------- 1 | # pi()/180 = 0.01745329252 2 | # pi()*(120/4.5)/180 = 0.465421133865 3 | 4 | ATT.DesYaw.0.0(0.01745329252, 0, 0) 5 | ATT.Yaw.0.1(0.01745329252, 0, 0) 6 | TILT.ErrAng.0.2(1, 0, 0) 7 | TILT.ErrZ.4.3(1, 0, 0) 8 | PIDY.E.0.4(1, 0, 0) 9 | PIDY.LPE.7.5(1, 0, 0) 10 | <> PIDY:0.465421133865 0.6 11 | <> PIDY:0.10471975512 0.7 12 | -------------------------------------------------------------------------------- /conf/ReadMe.md: -------------------------------------------------------------------------------- 1 | # 配置文件的格式 2 | ## 一、绘制LOG数据 3 | > LineStyle: 0-正常,1-加粗,2-加阴影,3-加粗加阴影,4-虚线,5-虚线加粗,6-点虚线,7-线上空心圆,8-线上实心圆,9-只有空心圆 4 | > Color: 0-红,1-绿,2-蓝,3-紫,4-棕,5-粉,6-深天蓝,7-橙,8-深青,9-金 5 | ### 1. 无申缩或相位变换 6 | `Table.Field.LineStyle.Color`,例如:ATT.DesRoll.0.0 7 | ### 2. 有申缩或相位变换 8 | `Table.Field.LineStyle.Color(Scale,OffsetX,OffsetY)`,例如:ATT.DesRoll.0.0(2,0,0) 9 | ## 二、绘制参考线 10 | ` Table:value LineStyle.Color`,例如:<const> BARO:200 0.0 11 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include 3 | 4 | #define QCDEBUG_FILTER_RULES "MainWindowLog,APLDBLog,APLReadLog,DataAnalyzeLog,DialogLog,DialogLoadLog,DialogPythonLog,APLReadConfLog,APLDataCacheLog,APLRunPythonLog" 5 | 6 | int main(int argc, char *argv[]) 7 | { 8 | QApplication a(argc, argv); 9 | MainWindow w; 10 | 11 | APLLoggingCategoryRegister::instance()->setFilterRulesFromSettings(QCDEBUG_FILTER_RULES); 12 | w.setWindowTitle("ArduPilotLog"); 13 | w.show(); 14 | 15 | return a.exec(); 16 | } 17 | -------------------------------------------------------------------------------- /matlab/ReadMe.md: -------------------------------------------------------------------------------- 1 | 使用方法 2 | --- 3 | 1. Matlab进入脚本目录并运行 4 |
![ArduPilotLog_matlab_1.png](https://github.com/SuWeipeng/img/raw/master/1_ArduPilotLog/ArduPilotLog_matlab_1.png)
5 | 6 | 2. 加载`\*.db`数据库文件 7 | >
\*.db 数据库文件来源于ArduPilotLog软件:File -> Save DB file
8 | >
*.db 的加载速度比 *.bin 快**数十倍**,且文件越大加载速度差别越明显。
9 | 10 |
![ArduPilotLog_matlab_2.png](https://github.com/SuWeipeng/img/raw/master/1_ArduPilotLog/ArduPilotLog_matlab_2.png)
11 | 12 | 3. 使用载入 MATLAB 数据的方法 13 | > 例:att.Roll 14 | 15 |
![ArduPilotLog_matlab_3.png](https://github.com/SuWeipeng/img/raw/master/1_ArduPilotLog/ArduPilotLog_matlab_3.png)
16 | -------------------------------------------------------------------------------- /src/APLReadConf.h: -------------------------------------------------------------------------------- 1 | #ifndef APLREADCONF_H 2 | #define APLREADCONF_H 3 | 4 | #include "APLLoggingCategory.h" 5 | 6 | Q_DECLARE_LOGGING_CATEGORY(APLREAD_CONF_LOG) 7 | 8 | class APLReadConf : public QObject 9 | { 10 | Q_OBJECT 11 | 12 | public: 13 | APLReadConf(); 14 | ~APLReadConf(); 15 | 16 | void getDatastream(const QString &file_dir); 17 | QString getFileName(void) { return _file_name; } 18 | 19 | signals: 20 | void fileOpened(); 21 | 22 | public slots: 23 | void getFileDir(const QString &file_dir); 24 | 25 | private: 26 | void _decode(QTextStream &in) const; 27 | 28 | QString _file_name; 29 | }; 30 | 31 | #endif // APLREADCONF_H 32 | -------------------------------------------------------------------------------- /APLDockWidget.h: -------------------------------------------------------------------------------- 1 | #ifndef APLDockWidget_h 2 | #define APLDockWidget_h 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class APLDockWidget : public QWidget { 9 | Q_OBJECT 10 | 11 | public: 12 | /// Pass in title = QString() and action = NULL when just using as a regular widget 13 | APLDockWidget(const QString& title, QAction* action, QWidget *parent = 0); 14 | 15 | void loadSettings(void); 16 | void saveSettings(void); 17 | 18 | void closeEvent(QCloseEvent* event); 19 | 20 | protected: 21 | QString _title; 22 | QPointer _action; 23 | static const char* _settingsGroup; 24 | }; 25 | 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /src/DialogLoad.h: -------------------------------------------------------------------------------- 1 | #ifndef DialogLoad_H 2 | #define DialogLoad_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include "APLLoggingCategory.h" 8 | 9 | Q_DECLARE_LOGGING_CATEGORY(DialogLoad_LOG) 10 | 11 | class APLReadConf; 12 | class QFileDialog; 13 | 14 | class DialogLoad : public QDialog 15 | { 16 | Q_OBJECT 17 | 18 | public: 19 | DialogLoad(QWidget *parent = 0); 20 | ~DialogLoad(); 21 | 22 | APLReadConf* getAPLReadConf() const { return _aplReadConf; } 23 | bool isDirExist(QString fullPath); 24 | 25 | public slots: 26 | void showFile(); 27 | 28 | signals: 29 | void saveSuccess(); 30 | 31 | private: 32 | APLReadConf *_aplReadConf; 33 | QFileDialog *_qfileDialogLoad; 34 | }; 35 | 36 | #endif // DialogLoad_H 37 | -------------------------------------------------------------------------------- /src/DataAnalyze.h: -------------------------------------------------------------------------------- 1 | #ifndef DATAANALYZE_H 2 | #define DATAANALYZE_H 3 | 4 | #include "APLQmlWidgetHolder.h" 5 | #include 6 | #include 7 | 8 | class DataAnalyze : public APLQmlWidgetHolder 9 | { 10 | public: 11 | DataAnalyze(const QString& title, QAction* action, QWidget *parent = 0) 12 | :APLQmlWidgetHolder(title, action, parent) 13 | { 14 | Q_UNUSED(title); 15 | Q_UNUSED(action); 16 | const QRect screenGeometry = QGuiApplication::primaryScreen()->geometry(); 17 | int screenWidth=screenGeometry.width(); 18 | int screenHeight=screenGeometry.height(); 19 | this->resize(screenWidth/2, screenHeight/3); 20 | setSource(QUrl::fromUserInput("qrc:/qml/DataAnalyze.qml")); 21 | } 22 | }; 23 | 24 | #endif // DATAANALYZE_H 25 | -------------------------------------------------------------------------------- /src/APLQmlWidgetHolder.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | APLQmlWidgetHolder 4 | 5 | 6 | 7 | 0 8 | 0 9 | 400 10 | 300 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | true 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | QQuickWidget 29 | QWidget 30 |
QtQuickWidgets/QQuickWidget
31 |
32 |
33 | 34 | 35 |
36 | -------------------------------------------------------------------------------- /src/PythonExporter.h: -------------------------------------------------------------------------------- 1 | #ifndef PYTHONEXPORTER_H 2 | #define PYTHONEXPORTER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | struct DataField { 11 | QString logType; // 如 "TECS" 12 | QStringList fields; // 如 ["TimeUS", "th", "hin", "sp", ...] 13 | }; 14 | 15 | class PythonExporter 16 | { 17 | public: 18 | static PythonExporter* get_singleton() { 19 | return _singleton; 20 | } 21 | 22 | PythonExporter(); 23 | 24 | // 添加数据字段 25 | void addDataField(const QString& logType, const QStringList& fields); 26 | 27 | // 设置数据库名称 28 | void setDatabaseName(const QString& dbName); 29 | 30 | // 导出 Python 文件 31 | bool exportToPython(const QString& outputPath); 32 | 33 | // 清空所有数据字段 34 | void clear(); 35 | 36 | QString cleanPythonVariableName(const QString& original); 37 | 38 | private: 39 | static PythonExporter* _singleton; 40 | 41 | QString generatePythonCode(); 42 | QString generateVariableNames(const QString& logType, const QString &fields); 43 | 44 | QList m_dataFields; 45 | QString m_databaseName; 46 | }; 47 | 48 | #endif // PYTHONEXPORTER_H 49 | -------------------------------------------------------------------------------- /src/APLQmlWidgetHolder.cpp: -------------------------------------------------------------------------------- 1 | #include "APLQmlWidgetHolder.h" 2 | 3 | APLQmlWidgetHolder::APLQmlWidgetHolder(const QString& title, QAction* action, QWidget *parent) : 4 | APLDockWidget(title, action, parent) 5 | { 6 | _ui.setupUi(this); 7 | 8 | layout()->setContentsMargins(0,0,0,0); 9 | 10 | if (action) { 11 | setWindowTitle(title); 12 | } 13 | setResizeMode(QQuickWidget::SizeRootObjectToView); 14 | } 15 | 16 | APLQmlWidgetHolder::~APLQmlWidgetHolder() 17 | { 18 | 19 | } 20 | 21 | void APLQmlWidgetHolder::setSource(const QUrl& qmlUrl) 22 | { 23 | _ui.qmlWidget->setSource(qmlUrl); 24 | } 25 | 26 | void APLQmlWidgetHolder::setContextPropertyObject(const QString& name, QObject* object) 27 | { 28 | _ui.qmlWidget->rootContext()->setContextProperty(name, object); 29 | } 30 | 31 | QQmlContext* APLQmlWidgetHolder::getRootContext(void) 32 | { 33 | return _ui.qmlWidget->rootContext(); 34 | } 35 | 36 | QQuickItem* APLQmlWidgetHolder::getRootObject(void) 37 | { 38 | return _ui.qmlWidget->rootObject(); 39 | } 40 | 41 | QQmlEngine* APLQmlWidgetHolder::getEngine() 42 | { 43 | return _ui.qmlWidget->engine(); 44 | } 45 | 46 | 47 | void APLQmlWidgetHolder::setResizeMode(QQuickWidget::ResizeMode resizeMode) 48 | { 49 | _ui.qmlWidget->setResizeMode(resizeMode); 50 | } 51 | -------------------------------------------------------------------------------- /src/PythonExporterCSV.h: -------------------------------------------------------------------------------- 1 | #ifndef PYTHONEXPORTERCSV_H 2 | #define PYTHONEXPORTERCSV_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | struct CSVDataField { 11 | QString logType; // 如 "TECS", "ARSP0", "GPS0", "BARO0" 12 | QStringList fields; // 如 ["TimeUS", "th", "hin", "sp", ...] 13 | }; 14 | 15 | class PythonExporterCSV 16 | { 17 | public: 18 | static PythonExporterCSV* get_singleton() { 19 | return _singleton; 20 | } 21 | 22 | PythonExporterCSV(); 23 | 24 | // 添加数据字段 25 | void addDataField(const QString& logType, const QStringList& fields); 26 | 27 | // 设置日志文件夹名称 28 | void setLogFolderName(const QString& folderName); 29 | 30 | // 导出 Python 文件 31 | bool exportToPython(const QString& outputPath); 32 | 33 | // 清空所有数据字段 34 | void clear(); 35 | 36 | QString cleanPythonVariableName(const QString& original); 37 | 38 | private: 39 | static PythonExporterCSV* _singleton; 40 | 41 | QString generatePythonCode(); 42 | QString generateVariableNames(const QString& logType, const QString& field); 43 | QString generateDataReading(const CSVDataField& field); 44 | 45 | QList m_dataFields; 46 | QString m_logFolderName; 47 | }; 48 | 49 | #endif // PYTHONEXPORTERCSV_H 50 | -------------------------------------------------------------------------------- /aplresources.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | resources/icons/a01.ico 4 | resources/icons/a02.ico 5 | resources/icons/a03.ico 6 | resources/icons/a04.ico 7 | resources/icons/a05.ico 8 | resources/icons/a06.ico 9 | resources/icons/a07.ico 10 | resources/icons/a08.ico 11 | resources/icons/a09.ico 12 | resources/icons/a10.ico 13 | resources/icons/a11.ico 14 | resources/icons/a12.ico 15 | resources/icons/a13.ico 16 | resources/icons/a14.ico 17 | resources/icons/a15.ico 18 | resources/icons/a16.ico 19 | resources/icons/a17.ico 20 | resources/icons/a18.ico 21 | resources/icons/a19.ico 22 | resources/icons/a20.ico 23 | resources/icons/a21.ico 24 | resources/icons/a22.ico 25 | resources/icons/a23.ico 26 | resources/icons/a24.ico 27 | resources/icons/a25.ico 28 | resources/icons/a26.ico 29 | resources/icons/a27.ico 30 | resources/icons/a28.ico 31 | resources/icons/a29.ico 32 | 33 | 34 | -------------------------------------------------------------------------------- /matlab/FSMAnalysis.m: -------------------------------------------------------------------------------- 1 | for i = 1:size(stat.id,1) 2 | FSM_label{stat.id(i, 1)+1, 1} = sprintf('%s%d','id',stat.id(i, 1)); 3 | FSM(stat.id(i, 1)+1, 1) = stat.id(i, 1); 4 | end 5 | C = cell(2, size(FSM_label, 1)); 6 | for i = 1:1:size(stat.id,1) 7 | C{1, stat.id(i, 1)+1}(size(C{1, stat.id(i, 1)+1}, 1)+1,1) = stat.TimeUS(i, 1); 8 | C{2, stat.id(i, 1)+1}(size(C{2, stat.id(i, 1)+1}, 1)+1,1) = stat.stat(i, 1); 9 | end 10 | 11 | state = cell2struct(C, FSM_label, 2); 12 | 13 | h = axes(); 14 | hold on; 15 | leg=[]; 16 | plot_count = 0; 17 | color = rand(size(FSM_label, 1), 3); 18 | except = []; % fill except ids here 19 | axis_x = 1; % 1-n as X axis 2-TimeUS as X axis 20 | times = 1; % FSM value times 21 | for i = 1:size(FSM_label, 1) 22 | if ~isempty(find(FSM(i,1)==except)) 23 | continue 24 | end 25 | plot(h, eval(sprintf('%s%d%s%d','state(',axis_x,').id',FSM(i,1))), eval(sprintf('%s%d','state(2).id',FSM(i,1)))*times,'Color',color(i,:),'Marker','o','MarkerFaceColor',color(i,:),'LineStyle','--'); 26 | plot_count = plot_count + 1; 27 | if(isequal(plot_count,1)) 28 | leg = strcat('''','id',sprintf('%d',FSM(i,1)),''''); 29 | else 30 | leg = strcat(leg,',','''','id',sprintf('%d',FSM(i,1)),''''); 31 | end 32 | end 33 | clc 34 | eval(strcat('legend','(',leg,')')); 35 | grid on 36 | title('Finite State Machine Analysis'); -------------------------------------------------------------------------------- /src/APLQmlWidgetHolder.h: -------------------------------------------------------------------------------- 1 | #ifndef APLQmlWidgetHolder_h 2 | #define APLQmlWidgetHolder_h 3 | 4 | #include 5 | #include 6 | #include 7 | #include "APLDockWidget.h" 8 | #include "ui_APLQmlWidgetHolder.h" 9 | 10 | namespace Ui { 11 | class APLQmlWidgetHolder; 12 | } 13 | 14 | /// This is used to create widgets which are implemented in QML. 15 | 16 | class APLQmlWidgetHolder : public APLDockWidget 17 | { 18 | Q_OBJECT 19 | 20 | public: 21 | // This has a title and action since the base class is APLQmlWidget. In order to use this 22 | // control as a normal QWidget, not a doc widget just pass in: 23 | // title = QString() 24 | // action = NULL 25 | explicit APLQmlWidgetHolder(const QString& title, QAction* action, QWidget *parent = 0); 26 | ~APLQmlWidgetHolder(); 27 | 28 | /// Get Root Context 29 | QQmlContext* getRootContext(void); 30 | 31 | /// Get Root Object 32 | QQuickItem* getRootObject(void); 33 | 34 | /// Get QML Engine 35 | QQmlEngine* getEngine(); 36 | 37 | void setSource(const QUrl& qmlUrl); 38 | 39 | void setContextPropertyObject(const QString& name, QObject* object); 40 | 41 | /// Sets the resize mode for the QQuickWidget container 42 | void setResizeMode(QQuickWidget::ResizeMode resizeMode); 43 | 44 | private: 45 | Ui::APLQmlWidgetHolder _ui; 46 | }; 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /src/APLReadConf.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "APLReadConf.h" 6 | #include "mainwindow.h" 7 | 8 | APL_LOGGING_CATEGORY(APLREAD_CONF_LOG, "APLReadConfLog") 9 | 10 | APLReadConf::APLReadConf() 11 | { 12 | } 13 | 14 | APLReadConf::~APLReadConf() 15 | { 16 | } 17 | 18 | void APLReadConf::getFileDir(const QString &file_dir) 19 | { 20 | _file_name = QFileInfo(file_dir).fileName(); 21 | getDatastream(file_dir); 22 | 23 | qCDebug(APLREAD_CONF_LOG) << file_dir; 24 | } 25 | 26 | void APLReadConf::getDatastream(const QString &file_dir) 27 | { 28 | QFile file; 29 | 30 | file.setFileName(file_dir); 31 | if(!file.open(QIODevice::ReadOnly)) 32 | { 33 | qCDebug(APLREAD_CONF_LOG)<< "can not open file!"; 34 | return; 35 | } 36 | 37 | QTextStream in(&file); 38 | _decode(in); 39 | 40 | emit fileOpened(); 41 | qCDebug(APLREAD_CONF_LOG) << "All plot config have been read"; 42 | 43 | file.close(); 44 | } 45 | 46 | void APLReadConf::_decode(QTextStream &in) const 47 | { 48 | QStringList conf; 49 | QString lineStr; 50 | while(!in.atEnd()) 51 | { 52 | lineStr = in.readLine(); 53 | 54 | if(lineStr.left(1).compare("#") == 0) continue; 55 | 56 | if(!lineStr.isEmpty()){ 57 | conf.append(lineStr); 58 | } 59 | } 60 | 61 | MainWindow::getMainWindow()->set_conf(conf); 62 | } 63 | -------------------------------------------------------------------------------- /APLDockWidget.cpp: -------------------------------------------------------------------------------- 1 | #include "APLDockWidget.h" 2 | 3 | #include 4 | #include 5 | 6 | const char* APLDockWidget::_settingsGroup = "DockWidgets"; 7 | 8 | APLDockWidget::APLDockWidget(const QString& title, QAction* action, QWidget* parent) 9 | : QWidget(parent) 10 | , _title(title) 11 | , _action(action) 12 | { 13 | if (action) { 14 | setWindowTitle(title); 15 | setWindowFlags(Qt::Tool); 16 | loadSettings(); 17 | } 18 | } 19 | 20 | // Instead of destroying the widget just hide it 21 | void APLDockWidget::closeEvent(QCloseEvent* event) 22 | { 23 | if (_action) { 24 | saveSettings(); 25 | event->ignore(); 26 | _action->trigger(); 27 | } else { 28 | QWidget::closeEvent(event); 29 | } 30 | } 31 | 32 | void APLDockWidget::loadSettings(void) 33 | { 34 | // TODO: This is crashing for some reason. Disabled until sorted out. 35 | if (0 /*_action*/) { 36 | QSettings settings; 37 | settings.beginGroup(_settingsGroup); 38 | if (settings.contains(_title)) { 39 | restoreGeometry(settings.value(_title).toByteArray()); 40 | } 41 | settings.endGroup(); 42 | } 43 | } 44 | 45 | void APLDockWidget::saveSettings(void) 46 | { 47 | // TODO: This is crashing for some reason. Disabled until sorted out. 48 | if (0 /*_action*/) { 49 | QSettings settings; 50 | settings.beginGroup(_settingsGroup); 51 | settings.setValue(_title, saveGeometry()); 52 | settings.endGroup(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/LogStructure.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | /* 6 | unfortunately these need to be macros because of a limitation of 7 | named member structure initialisation in g++ 8 | */ 9 | #define LOG_PACKET_HEADER uint8_t head1, head2, msgid 10 | #define LOG_PACKET_HEADER_INIT(id) head1 : HEAD_BYTE1, head2 : HEAD_BYTE2, msgid : id 11 | #define LOG_PACKET_HEADER_LEN 3 // bytes required for LOG_PACKET_HEADER 12 | 13 | // once the logging code is all converted we will remove these from 14 | // this header 15 | #define HEAD_BYTE1 0xA3 // Decimal 163 16 | #define HEAD_BYTE2 0x95 // Decimal 149 17 | 18 | // structure used to define logging format 19 | struct LogStructure { 20 | uint8_t msg_type; 21 | uint8_t msg_len; 22 | const char name[5]; 23 | const char format[16]; 24 | const char labels[64]; 25 | const char units[16]; 26 | const char multipliers[16]; 27 | }; 28 | 29 | /* 30 | log structures common to all vehicle types 31 | */ 32 | struct PACKED log_Format { 33 | LOG_PACKET_HEADER; 34 | uint8_t type; 35 | uint8_t length; 36 | char name[4]; 37 | char format[16]; 38 | char labels[64]; 39 | }; 40 | 41 | /* 42 | Format characters in the format string for binary log messages 43 | a : int16_t[32] 44 | b : int8_t 45 | B : uint8_t 46 | h : int16_t 47 | H : uint16_t 48 | i : int32_t 49 | I : uint32_t 50 | f : float 51 | d : double 52 | n : char[4] 53 | N : char[16] 54 | Z : char[64] 55 | c : int16_t * 100 56 | C : uint16_t * 100 57 | e : int32_t * 100 58 | E : uint32_t * 100 59 | L : int32_t latitude/longitude 60 | M : uint8_t flight mode 61 | q : int64_t 62 | Q : uint64_t 63 | */ 64 | 65 | 66 | // message types for common messages 67 | enum LogMessages { 68 | LOG_FORMAT_MSG = 128, 69 | }; 70 | -------------------------------------------------------------------------------- /APLLoggingCategory.h: -------------------------------------------------------------------------------- 1 | #ifndef APLLOGGINGCATEGORY_H 2 | #define APLLOGGINGCATEGORY_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | /// @def APL_LOGGING_CATEGORY 9 | /// This is a APL specific replacement for Q_LOGGING_CATEGORY. It will register the category name into a 10 | /// global list. It's usage is the same as Q_LOGGING_CATEOGRY. 11 | #define APL_LOGGING_CATEGORY(name, ...) \ 12 | static APLLoggingCategory aplCategory ## name (__VA_ARGS__); \ 13 | Q_LOGGING_CATEGORY(name, __VA_ARGS__) 14 | 15 | class APLLoggingCategoryRegister : public QObject 16 | { 17 | Q_OBJECT 18 | 19 | public: 20 | static APLLoggingCategoryRegister* instance(void); 21 | 22 | /// Registers the specified logging category to the system. 23 | void registerCategory(const char* category) { _registeredCategories << category; } 24 | 25 | /// Returns the list of available logging category names. 26 | Q_INVOKABLE QStringList registeredCategories(void); 27 | 28 | /// Turns on/off logging for the specified category. State is saved in app settings. 29 | Q_INVOKABLE void setCategoryLoggingOn(const QString& category, bool enable); 30 | 31 | /// Returns true if logging is turned on for the specified category. 32 | Q_INVOKABLE bool categoryLoggingOn(const QString& category); 33 | 34 | /// Sets the logging filters rules from saved settings. 35 | /// @param commandLineLogggingOptions Logging options which were specified on the command line 36 | void setFilterRulesFromSettings(const QString& commandLineLoggingOptions); 37 | 38 | private: 39 | APLLoggingCategoryRegister(void) { } 40 | 41 | QStringList _registeredCategories; 42 | QString _commandLineLoggingOptions; 43 | 44 | static const char* _filterRulesSettingsGroup; 45 | }; 46 | 47 | class APLLoggingCategory 48 | { 49 | public: 50 | APLLoggingCategory(const char* category) { APLLoggingCategoryRegister::instance()->registerCategory(category); } 51 | }; 52 | 53 | #endif // APLLOGGINGCATEGORY_H 54 | -------------------------------------------------------------------------------- /APLLoggingCategory.cpp: -------------------------------------------------------------------------------- 1 | #include "APLLoggingCategory.h" 2 | 3 | #include 4 | 5 | APLLoggingCategoryRegister* _instance = NULL; 6 | const char* APLLoggingCategoryRegister::_filterRulesSettingsGroup = "LoggingFilters"; 7 | 8 | APLLoggingCategoryRegister* APLLoggingCategoryRegister::instance(void) 9 | { 10 | if (!_instance) { 11 | _instance = new APLLoggingCategoryRegister(); 12 | Q_CHECK_PTR(_instance); 13 | } 14 | 15 | return _instance; 16 | } 17 | 18 | QStringList APLLoggingCategoryRegister::registeredCategories(void) 19 | { 20 | _registeredCategories.sort(); 21 | return _registeredCategories; 22 | } 23 | 24 | void APLLoggingCategoryRegister::setCategoryLoggingOn(const QString& category, bool enable) 25 | { 26 | QSettings settings; 27 | 28 | settings.beginGroup(_filterRulesSettingsGroup); 29 | settings.setValue(category, enable); 30 | } 31 | 32 | bool APLLoggingCategoryRegister::categoryLoggingOn(const QString& category) 33 | { 34 | QSettings settings; 35 | 36 | settings.beginGroup(_filterRulesSettingsGroup); 37 | return settings.value(category, false).toBool(); 38 | } 39 | 40 | void APLLoggingCategoryRegister::setFilterRulesFromSettings(const QString& commandLineLoggingOptions) 41 | { 42 | if (!commandLineLoggingOptions.isEmpty()) { 43 | _commandLineLoggingOptions = commandLineLoggingOptions; 44 | } 45 | QString filterRules; 46 | 47 | // Turn off bogus ssl warning 48 | filterRules += "qt.network.ssl.warning=false\n"; 49 | filterRules += "*Log.debug=false\n"; 50 | 51 | // Set up filters defined in settings 52 | foreach (QString category, _registeredCategories) { 53 | if (categoryLoggingOn(category)) { 54 | filterRules += category; 55 | filterRules += ".debug=true\n"; 56 | } 57 | } 58 | 59 | // Command line rules take precedence, so they go last in the list 60 | if (!_commandLineLoggingOptions.isEmpty()) { 61 | QStringList logList = _commandLineLoggingOptions.split(","); 62 | 63 | if (logList[0] == "full") { 64 | filterRules += "*Log.debug=true\n"; 65 | for(int i=1; i 6 | 7 | APL_LOGGING_CATEGORY(DialogLoad_LOG, "DialogLoadLog") 8 | 9 | DialogLoad::DialogLoad(QWidget *parent) 10 | : QDialog(parent) 11 | , _aplReadConf(new APLReadConf) 12 | , _qfileDialogLoad(new QFileDialog) 13 | { 14 | connect(_qfileDialogLoad, &QFileDialog::fileSelected, _aplReadConf, &APLReadConf::getFileDir); 15 | } 16 | 17 | DialogLoad::~DialogLoad() 18 | { 19 | delete _aplReadConf; 20 | delete _qfileDialogLoad; 21 | } 22 | 23 | bool DialogLoad::isDirExist(QString fullPath) 24 | { 25 | QDir dir(fullPath); 26 | 27 | if(dir.exists()) 28 | { 29 | return true; 30 | } 31 | return false; 32 | } 33 | 34 | void DialogLoad::showFile() 35 | { 36 | QString path = APLRead::getAPLRead()->getFilePath(); 37 | 38 | if (MainWindow::getMainWindow()->dialog()->get_csv_mode()) { 39 | path = MainWindow::getMainWindow()->dialog()->get_logdir(); 40 | QDir currentDir(path); 41 | if (currentDir.cdUp() && currentDir.cdUp()) { // 切换到上 2 级目录 42 | QString parentPath = currentDir.absolutePath(); 43 | QString confPath = QString("%1/conf").arg(parentPath); 44 | if (isDirExist(confPath)) { 45 | path = confPath; 46 | } 47 | } 48 | } else if (isDirExist(QString("%1/conf").arg(path))) { 49 | path = QString("%1/conf").arg(path); 50 | } else { 51 | QString confdir_loc = ""; 52 | QFile confdir(QString("confdir.txt")); 53 | if(!confdir.exists()){ 54 | qCDebug(DialogLoad_LOG) << "confdir.txt doesn't exist!"; 55 | } else { 56 | if(!confdir.open(QIODevice::ReadOnly | QIODevice::Text)){ 57 | qCDebug(DialogLoad_LOG) << "confdir.txt read error!"; 58 | } else { 59 | QTextStream pos(&confdir); 60 | pos.setEncoding(QStringConverter::Utf8); 61 | confdir_loc = pos.readLine(); 62 | qCDebug(DialogLoad_LOG) << confdir_loc; 63 | } 64 | confdir.close(); 65 | } 66 | if(isDirExist(confdir_loc)){ 67 | path = confdir_loc; 68 | } 69 | } 70 | 71 | QString confdir = _qfileDialogLoad->getOpenFileName(this 72 | ,"open plot config" 73 | ,path 74 | ,"Config files(*.conf)"); 75 | emit _qfileDialogLoad->fileSelected(confdir); 76 | 77 | qCDebug(DialogLoad_LOG) << confdir; 78 | } 79 | -------------------------------------------------------------------------------- /src/Dialog.h: -------------------------------------------------------------------------------- 1 | #ifndef DIALOG_H 2 | #define DIALOG_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "APLLoggingCategory.h" 9 | #include 10 | #include 11 | #include 12 | 13 | Q_DECLARE_LOGGING_CATEGORY(DIALOG_LOG) 14 | 15 | class APLRead; 16 | class QFileDialog; 17 | 18 | class SaveAsWorker : public QObject 19 | { 20 | Q_OBJECT 21 | public: 22 | explicit SaveAsWorker(QObject *parent = nullptr); 23 | ~SaveAsWorker(); 24 | 25 | private: 26 | 27 | signals: 28 | void saveAsDone(); 29 | void send_process(qint64 pos, qint64 size); 30 | 31 | public slots: 32 | void run(const QString &dbdir); 33 | }; 34 | 35 | class Dialog : public QDialog 36 | { 37 | Q_OBJECT 38 | 39 | friend class APLDataCache; 40 | friend class MainWindow; 41 | public: 42 | Dialog(QWidget *parent = 0); 43 | ~Dialog(); 44 | 45 | APLRead* getAPLRead() const { return _aplRead; } 46 | QString get_db_name() const { return _db_name; } 47 | QString get_python_path() const { return _python_path; } 48 | QString get_logdir() const { return _logdir; } 49 | bool get_python_ingnore_db() const { return _python_ingnore_db; } 50 | bool get_csv_mode() const { return _csv_mode; } 51 | QStringList got_csvFileNames() const { return _csvFilesMap.keys(); } 52 | QStringList got_csvFieldNames(const QString& fileName) const { return _csvFilesMap.value(fileName); } 53 | QStringList got_csvFieldData(const QString& fileName, const QString& fieldName) const; 54 | bool isDirExist(QString fullPath); 55 | void setTrimFrom(const quint64& v) { _trim_from = v; } 56 | void setTrimTo(const quint64& v) { _trim_to = v; } 57 | 58 | public slots: 59 | void showFile(); 60 | void saveFile(); 61 | void saveAsDone(); 62 | void trim(); 63 | void split(bool checked); 64 | void ignore_db(bool checked); 65 | 66 | signals: 67 | void saveSuccess(); 68 | void saveAsStart(const QString &dbdir); 69 | void settingsLoaded(const QJsonObject &settings); 70 | void gotCSVDir(); 71 | 72 | private: 73 | void loadSettings(); 74 | 75 | APLRead * _aplRead; 76 | QFileDialog * _qfiledialog; 77 | QThread * _workThread; 78 | SaveAsWorker* _worker; 79 | QString _opendir; 80 | QString _filter_file; 81 | qint8 _filter_mode; 82 | QStringList _filter_include; 83 | QStringList _filter_exclude; 84 | bool _table_split = false; 85 | quint64 _trim_from = 0; 86 | quint64 _trim_to = 0; 87 | QString _logdir; 88 | QString _db_name; 89 | QString _python_path; 90 | bool _python_ingnore_db; 91 | bool _csv_mode; 92 | QMap _csvFilesMap; 93 | }; 94 | 95 | #endif // DIALOG_H 96 | -------------------------------------------------------------------------------- /src/APLRead.h: -------------------------------------------------------------------------------- 1 | #ifndef APLREAD_H 2 | #define APLREAD_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "LogStructure.h" 9 | #include "APLLoggingCategory.h" 10 | 11 | Q_DECLARE_LOGGING_CATEGORY(APLREAD_LOG) 12 | 13 | typedef struct LogFormat 14 | { 15 | quint8 id; 16 | QString name; 17 | QString format; 18 | bool valid; 19 | LogFormat(){ 20 | id = 0; 21 | name = ""; 22 | format = ""; 23 | valid = true; 24 | } 25 | }LFMT; 26 | 27 | class APLDataCache; 28 | 29 | class APLReadWorker : public QObject 30 | { 31 | Q_OBJECT 32 | public: 33 | explicit APLReadWorker(QObject *parent = nullptr); 34 | ~APLReadWorker(); 35 | 36 | private: 37 | APLDataCache* _dataCache; 38 | qint64 _fileSize; 39 | mutable QHash _formatLengthCache; 40 | void _decode(const uchar* p_data, qint64 data_size); 41 | bool _checkMessage(QString &name, QString &format, QString &labels) const; 42 | void _decodeData(QString &format, const uchar *ptr, QString &value) const; 43 | int _getMessageLength(const QString &format) const; 44 | bool _checkName(QString &name) const; 45 | bool _checkFormat(QString &format) const; 46 | bool _checkLabels(QString &labels) const; 47 | 48 | signals: 49 | void fileOpened(); 50 | void send_process(qint64 pos, qint64 size); 51 | 52 | public slots: 53 | void decodeLogFile(const QString &file_dir); 54 | }; 55 | 56 | class APLExportWorker : public QObject 57 | { 58 | Q_OBJECT 59 | public: 60 | explicit APLExportWorker(QObject *parent = nullptr); 61 | ~APLExportWorker(){} 62 | public slots: 63 | void exportCSV(const QString &file_dir); 64 | signals: 65 | void saveSuccess(); 66 | }; 67 | 68 | class APLRead : public QObject 69 | { 70 | Q_OBJECT 71 | 72 | public: 73 | APLRead(); 74 | ~APLRead(); 75 | 76 | void getDatastream(const QString &file_dir); 77 | QString getFileName(void) { return _file_name; } 78 | QString getFilePath(void) { return _file_path; } 79 | 80 | static APLRead* getAPLRead() { return _instance; } 81 | APLExportWorker* export_worker; 82 | 83 | signals: 84 | void fileOpened(); 85 | void startRunning(const QString &file_dir); 86 | void startExport(const QString &file_dir); 87 | void saveSuccess(); 88 | 89 | public slots: 90 | void getFileDir(const QString &file_dir); 91 | void getFileOpened(); 92 | void calc_process(qint64 pos, qint64 size); 93 | void exportCSV(); 94 | 95 | private: 96 | void _resetDataBase(); 97 | void _resetFMT(int i); 98 | QString _file_name; 99 | QString _file_path; 100 | QString _file_dir; 101 | QThread* _workThread; 102 | APLReadWorker* _worker; 103 | QThread* _exportThread; 104 | 105 | static APLRead* _instance; 106 | }; 107 | 108 | #endif // APLREAD_H 109 | -------------------------------------------------------------------------------- /src/APLDB.h: -------------------------------------------------------------------------------- 1 | #ifndef APLDB_H 2 | #define APLDB_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "APLLoggingCategory.h" 9 | 10 | Q_DECLARE_LOGGING_CATEGORY(APLDB_LOG) 11 | 12 | class APLDB : public QObject 13 | { 14 | Q_OBJECT 15 | 16 | public: 17 | APLDB(); 18 | ~APLDB(); 19 | 20 | void createAPLDB(const QString &dbPath); 21 | 22 | //true: id already exist 23 | bool checkMainTable(quint8 id); 24 | 25 | void addToMainTable(quint8 type, 26 | quint8 len, 27 | QString name, 28 | QString format, 29 | QString labels); 30 | 31 | void addToSubTable(QString name, QString values); 32 | 33 | void addToSubTableBuf(QString name, QVector values); 34 | 35 | void buf2DB(); 36 | 37 | void insertBatchData(const QString& tableName, const QList>& rows); 38 | 39 | void getFormat(quint8 &id, QString &name, QString &format); 40 | 41 | bool isOpen() const { return _apldb.isOpen(); } 42 | 43 | void close() { _apldb.close(); } 44 | 45 | void commit() { _apldb.commit(); } 46 | 47 | void transaction() { _apldb.transaction(); } 48 | 49 | void closeConnection(); 50 | 51 | static QString getGroupName(QSqlDatabase &db, int i); 52 | 53 | static QString getTableName(QSqlDatabase &db, int i); 54 | 55 | static QString getItemName(QSqlDatabase &db, QString table, int i); 56 | 57 | static QString getDiff(QSqlDatabase &db, QString table, QString field); 58 | 59 | static int getGroupCount(QSqlDatabase &db); 60 | 61 | static int getTableNum(QSqlDatabase &db); 62 | 63 | static int getItemCount(QSqlDatabase &db, QString table); 64 | 65 | static int getLen(QSqlDatabase &db, QString table, QString field); 66 | 67 | static bool getData(QSqlDatabase &db, QString table, QString field, int len, QVector& data, double offset = 0, double scale = 1); 68 | 69 | static void getData(QSqlDatabase &db, QString table, QString field, int index, double& data); 70 | 71 | static void copy_table(QSqlDatabase &db, QString new_name, QString i, int i_value, QString fields, QString origin_name); 72 | 73 | void deleteDataBase(const QString &dbdir); 74 | 75 | void reset(); 76 | 77 | static bool isEmpty(QSqlDatabase &db, QString table); 78 | 79 | private: 80 | QSqlDatabase _apldb; 81 | quint32 _Number; 82 | QStringList _name; 83 | QList> _values; 84 | QStringList _maintable_item; 85 | QStringList _maintable_ids; 86 | QStringList _maintable_names; 87 | QStringList _maintable_formats; 88 | 89 | //true: create sub-table success 90 | bool _createSubTable(QString &name, QString &format, QString &field) const; 91 | 92 | void _createTableField(const QString &name, QString &format, QString &field, QString &table_field) const; 93 | 94 | QString _sanitizeFieldName(const QString &name, const QString& fieldName) const; 95 | }; 96 | 97 | #endif // APLDB_H 98 | -------------------------------------------------------------------------------- /matlab/APMLoadDB.m: -------------------------------------------------------------------------------- 1 | function APMBinaryLog 2 | % Use clc directly, no need for eval 3 | clc; 4 | disp('clear all'); 5 | disp('waiting ...'); 6 | 7 | [filename, pathname] = uigetfile({'*.db';'*.bin';'*.*'}, 'Open log file'); 8 | 9 | % --- CHANGE: Robustly check for uigetfile cancellation --- 10 | if isequal(filename, 0) || isequal(pathname, 0) 11 | disp('User cancelled operation.'); 12 | return; 13 | end 14 | 15 | % --- CHANGE: Use fullfile for robust path creation --- 16 | fullname = fullfile(pathname, filename); 17 | 18 | split_file_name = regexp(filename, '\.', 'split'); 19 | 20 | if strcmp(split_file_name{end}, 'db') % More robust way to check extension 21 | disp('Reading .db file...'); 22 | conn = sqlite(fullname, 'readonly'); 23 | group_name = fetch(conn,'SELECT name FROM maintable'); 24 | labels = fetch(conn,'SELECT labels FROM maintable'); 25 | names={}; 26 | tic; 27 | 28 | for i = 1:height(group_name) 29 | current_group_name = group_name.name(i); 30 | 31 | sql_query = "SELECT COUNT(*) FROM " + current_group_name; 32 | count_result = fetch(conn, sql_query); 33 | valid_group = count_result{1, 1}; 34 | 35 | if valid_group == 0 36 | continue; 37 | else 38 | % This part is already modernized and correct 39 | sql_select_all_query = "SELECT * FROM " + current_group_name; 40 | content = fetch(conn, sql_select_all_query); 41 | 42 | C={}; FIELDS={}; 43 | colums = width(content); % Use width() for tables 44 | 45 | for j = 1:colums 46 | % --- CHANGE: Correct indexing to extract data from the table column --- 47 | column_data = content{:, j}; 48 | if isnumeric(column_data) 49 | C{j} = double(column_data); 50 | else 51 | C{j} = column_data; 52 | end 53 | end 54 | 55 | % --- CHANGE: Correct indexing on 'labels' table --- 56 | FIELDS = regexp(labels{i, 1}, ',', 'split'); 57 | 58 | % This logic assumes the first column is always dropped 59 | C = C(1, 2:end); 60 | 61 | % --- CHANGE: Create struct and assign to base workspace WITHOUT eval --- 62 | 63 | % 1. Get the desired variable name as a string (e.g., 'adsb') 64 | new_var_name = lower(char(current_group_name)); 65 | 66 | % 2. Create the struct with your data 67 | data_struct = cell2struct(C, FIELDS, 2); 68 | 69 | % 3. Assign the struct to a variable with that name in the 'base' workspace 70 | assignin('base', new_var_name, data_struct); 71 | 72 | % --- CHANGE: Correct indexing for 'names' cell array --- 73 | names{end + 1, 1} = new_var_name; 74 | end 75 | end 76 | close(conn); 77 | toc; 78 | assignin('base','names',sort(names)); 79 | clear C colums content group_name i j labels valid_group FIELDS; 80 | disp('Done.'); 81 | end 82 | end -------------------------------------------------------------------------------- /src/APLDataCache.h: -------------------------------------------------------------------------------- 1 | #ifndef APLDATACACHE_H 2 | #define APLDATACACHE_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "APLLoggingCategory.h" 11 | 12 | Q_DECLARE_LOGGING_CATEGORY(APLDATACACHE_LOG) 13 | 14 | // 存储一种消息(如 "ATT")的所有列数据 15 | struct MessageData 16 | { 17 | quint8 type; 18 | QString format; 19 | QStringList headers; // 列名: "TimeUS", "Roll", "Pitch" 20 | QVector> columns; // 列数据 21 | QString labels; 22 | }; 23 | 24 | class APLDataCache : public QObject 25 | { 26 | Q_OBJECT 27 | public: 28 | static APLDataCache* get_singleton() { 29 | return _singleton; 30 | } 31 | 32 | explicit APLDataCache(QObject *parent = nullptr); 33 | 34 | // 添加一个新的消息格式定义 35 | void addFormat(const quint8 &type, const QString &name, const QString &format, const QString &labels, const qint16 &i=-1); 36 | 37 | // 添加一行解码后的数据(字符串形式) 38 | void addData(const QString &name, const QString &new_name, const uchar *payload, const qint16 &i, const int &payload_len); 39 | 40 | // 获取绘图所需的数据列 41 | QVector getColumn(const QString &messageName, const QString &columnName); 42 | 43 | // 将缓存的数据写入文件 44 | void exportToFile(const QString &outputDir); 45 | 46 | int getTableNum(); 47 | int getGroupCount(); 48 | QString getGroupName(int i); 49 | QString getTableName(int i); 50 | QString getItemName(QString table, int i); 51 | int getItemCount(QString table); 52 | bool getData(QString table, QString field, int len, QVector& data, double offset = 0, double scale = 1); 53 | int getLen(QString table, QString field); 54 | void getFormat(quint8 &id, QString &name, QString &format); 55 | bool checkMainTable(quint8 id); //true: id already exist 56 | const QMap& getStore() const { return _store; } 57 | const QMap>& getBinaryStore() const { return _binary_store; } 58 | QVector parseBinaryData(const QByteArray& data, const QString& format) const; 59 | void reset(); 60 | bool isEmpty(const QString& tableName) const { return !_binary_store.contains(tableName); } 61 | 62 | void setTableSplit(bool enabled); 63 | void setSaveCSV(bool enabled); 64 | void setTrimFrom(quint64 v); 65 | void setTrimTo(quint64 v); 66 | void setFilterMode(const qint8& v); 67 | void setFilterInclude(const QStringList& v); 68 | void setFilterExclude(const QStringList& v); 69 | void setFilterFile(const QString& v); 70 | 71 | QString get_export_dir() const { return _export_dir; } 72 | 73 | bool trim_complete = false; 74 | bool export_csv = false; 75 | 76 | private: 77 | static APLDataCache* _singleton; 78 | 79 | QMap _store; // 内存存储核心 80 | QMap _instantiable_store; 81 | QMap> _binary_store; // 新增的二进制数据仓库 82 | QJsonObject _metadata; // 用于生成 metadata.json 83 | QStringList _maintable_ids; 84 | QStringList _maintable_names; 85 | QStringList _maintable_formats; 86 | bool _table_split = false; 87 | bool _save_csv = false; 88 | quint64 _trim_from = 0; 89 | quint64 _trim_to = 0; 90 | qint8 _filter_mode = -1; 91 | QStringList _filter_include; 92 | QStringList _filter_exclude; 93 | QString _filter_file; 94 | QString _export_dir; 95 | 96 | bool _cut_data(quint8 id, quint64 start_time, quint64 stop_time, quint64 now); 97 | QString _sanitizeCSVFieldName(const QString& fieldName) const; 98 | }; 99 | 100 | #endif // APLDATACACHE_H 101 | -------------------------------------------------------------------------------- /ReadMe.md: -------------------------------------------------------------------------------- 1 | # ArduPilotLog 开源说明 2 | 概要 3 | --- 4 | ArduPilotLog 是 ardupilot 日志数据绘图软件。为快速展示 Log 日志数据,以便通过分析数据排查程序 bug 或累积经验而设计。 5 | 6 | 打开"_\*.bin_"类型日志 7 | --- 8 | 打开方法如下图所示: 9 |
![ArduPilotLog_1_OpenLog.png](https://github.com/SuWeipeng/img/raw/master/1_ArduPilotLog/ArduPilotLog_1_OpenLog.png)
10 |
![ArduPilotLog_2_OpenLog.png](https://github.com/SuWeipeng/img/raw/master/1_ArduPilotLog/ArduPilotLog_2_OpenLog.png)
11 | 12 | 三种绘图方法 13 | --- 14 | 1. 点选绘图 15 | > “点选绘图”在不明确看哪一种日志数据时使用。 16 | > 比如:对于飞机的某种奇怪表现,往往不能立刻明确是哪里出了问题,这时怀疑对象泛围大,用点选方式为猜测快速提供依据。 17 | 18 | ![ArduPilotLog_3_ClickPlot.png](https://github.com/SuWeipeng/img/raw/master/1_ArduPilotLog/ArduPilotLog_3_ClickPlot.png) 19 | 20 | 2. 数据分析窗(点选绘图Plus) 21 | > “数据分析窗”在小泛围锁定目标数据时使用。 22 | > 比如:通过点选绘图已初步锁定某种现象与某几类数据表现相关`(注:少于10类)`,此时须要精确绘图。 23 | 24 |
打开“数据分析窗”
25 |
![ArduPilotLog_4_DataAnalyze.png](https://github.com/SuWeipeng/img/raw/master/1_ArduPilotLog/ArduPilotLog_4_DataAnalyze.png)
26 |
“数据分析窗”说明
27 |
![ArduPilotLog_5_DataAnalyze.png](https://github.com/SuWeipeng/img/raw/master/1_ArduPilotLog/ArduPilotLog_5_DataAnalyze.png)
28 |
通过“数据分析窗”绘图
29 |
![ArduPilotLog_6_DataAnalyze.jpg](https://github.com/SuWeipeng/img/raw/master/1_ArduPilotLog/ArduPilotLog_6_DataAnalyze.jpg)
30 | 31 | 3. 脚本绘图 32 | > “脚本绘图”用于反复验证已锁定的目标数据是否合理的情形。 33 | > 将与当前问题有关的数据锁定后,往往需要多次采集 Log 日志,重复分析以便证实。这时使用脚本绘图比较方便,以免去每次点选、调整比例等麻烦。 34 | 35 |
加载脚本
36 |
![ArduPilotLog_7_ScriptPlot.png](https://github.com/SuWeipeng/img/raw/master/1_ArduPilotLog/ArduPilotLog_7_ScriptPlot.png)
37 |
![ArduPilotLog_8_ScriptPlot.png](https://github.com/SuWeipeng/img/raw/master/1_ArduPilotLog/ArduPilotLog_8_ScriptPlot.png)
38 | 39 |
脚本语法如下图(也可见 **[conf/ReadMe.md](https://github.com/SuWeipeng/ArduPilotLog/blob/master/conf/ReadMe.md)**)
40 | ![ArduPilotLog_9_ScriptPlot.png](https://github.com/SuWeipeng/img/raw/master/1_ArduPilotLog/ArduPilotLog_9_ScriptPlot.png) 41 | 42 | ## 编译方法 43 | ArduPilotLog 软件架构源于[qgroundcontrol](https://github.com/mavlink/qgroundcontrol),因此[编译方法](https://dev.qgroundcontrol.com/en/getting_started/)与QGC相同。 44 | 45 | ## 软件的由来 46 |
起初只是为了学习QGC,QGC功能多、代码构架复杂不是一下就能看懂的。
47 |
后来在研究ardupilot的过程中经常要分析Log。使用过MissionPlanner、APM Planner等软件看Log日志,有几个体验一直觉得不爽:
48 | 1. MissionPlanner最难受的是有时看看日志会卡死。 49 | 2. 我更关心图线,不是很在意数据,而界面留了一部分展示数据,每次都要手动拉小数据部分。 50 | 3. “看Log日志数据”只是这些软件的“一部分”功能。也就是说每次为了看数据,必须先要打开这些软件,等待其余功能加载完毕,然后点到这个功能,浪费时间。 51 | 4. 于上位机的角度讲,QGC的界面划分和操作方式更适合我,却单单缺少查看Log日志数据这个功能。 52 | 53 |
基于以上原因,就自己写个软件搞定自己的需求吧。
54 | 55 | ## 作者本人对此软件的评价 56 | 1. 这不是一个完美的软件。 57 | 2. 这个软件最初是以学习为目的而写的,没有走太完整的功能设计过程,业余时间想到哪写到哪。若要完美整合已实现的功能,就要重构了。 58 | 3. 代码适合想学QGC的朋友们看(看git的开发流程log,都是0起点开始的)。 59 | 4. 软件适合正在学习或调试ardupilot的朋友们使用。 60 | 由其是 **“脚本绘图”** 功能,**其目的就是给无形的经验累积提供一个容身之所**,让经验有形,便于自我反复推敲和与人交流。 61 | 5. 代码开发过程中经历了:QGC架构分析、用户体验设计、功能模块拆分与整合等过程,麻雀小可能还有点丑,但五脏全。 62 | 63 | ## 软件涉及到的知识点 64 | > 跟我一样0起点想学QGC的朋友可能关心这些,在此简单例举。 65 | 1. QGC架构。 66 | 2. QT ui界面开发。 67 | 3. QT QML界面开发。 68 | 4. QT ui 与 QML 整合方法。 69 | 5. QT SQLite 数据库。 70 | 6. QT 界面与后台数据的分离与整合。 71 | 7. QT 信号与槽通信机制。 72 | 8. QT 二进制文件读写。 73 | 9. QT 文本文件读取。 74 | 10. QT 正则表达式使用。 75 | 11. ardupilot 日志文件结构。 76 | 12. qcustomplot 绘图插件。 77 | 13. QGC qCDebug()分类控制显示调试日志的方法。 78 | 79 | ## 对于更进一步的数据分析 80 | 本软件以快速显示图线为目的,对于三种绘图方式仍不能满足的数据分析需求,则须将数据导入MATLAB(诸如:给数据进行低通滤波、数据作为控制仿真的输入等需求。) 81 |
[将数据导入MATLAB的脚本点此链接](https://github.com/SuWeipeng/ArduPilotLog/tree/master/matlab)
82 | 83 | ## arduplilot日志数据的交换 84 |
本软件设计的目的之一是:让不同平台能方便的使用Log日志数据。因此引入了SQLite数据库。
85 |
数据的存在形式:ardupilot存入SD卡中的 \*.bin 二进制格式,转到 SQLite 数据库的 \*.db 格式。
86 |
![ArduPilotLog_10_SQLite.png](https://github.com/SuWeipeng/img/raw/master/1_ArduPilotLog/ArduPilotLog_10_SQLite.png)
87 | * MATLAB、Excel等可通过\*.db文件获取日志内容。 88 | * 对数据感兴趣的时候,可通过 SQLite Expert 之类软件直接展示数据内容。 89 | 90 | ## 后续的更新 91 | 本软件后续会把作者已知未实现的功能写个 feature list,已知 bug 写个 bug list。对于 feature 和 bug 的划分是软件开发纠分不清的问题,在此作者表示:后续这些只是个 list 而已,很可能不会再加入新 feature 或解决小 bug(致命 bug 除外)。 92 | 93 | **后续更新会往以下几个方向发展:** 94 | 1. 分享一些开发过程中的心得。 95 | 2. 对某个知识点的剖析。 96 | 3. 加入新的 *.conf 绘图脚本(分享ardupilot“有形的”经验) 97 | 4. 软件开发经验分享: 98 | 1)说说软件架构是什么,为什么重要。 99 | 2)说说本软件开发的功能设计、功能模块划分与整合是怎样体现的。 100 | 3)说说本软件为提升用户体验感而做了哪些改进。 101 | 4)说说当有上位机开发需求的时候,应该怎样入手。 102 | 5)其他各种想到的值得分享的东西。 103 | 104 | **这是一个因学习而生的项目,它未来更大的意义不是从丑小鸭进化成白天鹅,而是为同样0起点的后来人铺上一块前往更高层次的砖。** 105 | 106 | ## 喜欢交流的朋友可以加作者微信 107 |
![weixin.png](https://github.com/SuWeipeng/img/raw/master/weixinhaoyou.png)
108 | -------------------------------------------------------------------------------- /src/PythonExporter.cpp: -------------------------------------------------------------------------------- 1 | #include "PythonExporter.h" 2 | #include 3 | 4 | PythonExporter* PythonExporter::_singleton; 5 | 6 | PythonExporter::PythonExporter() 7 | { 8 | _singleton = this; 9 | } 10 | 11 | void PythonExporter::addDataField(const QString& logType, const QStringList& fields) 12 | { 13 | DataField field; 14 | field.logType = logType; 15 | field.fields = fields; 16 | m_dataFields.append(field); 17 | } 18 | 19 | void PythonExporter::setDatabaseName(const QString& dbName) 20 | { 21 | m_databaseName = dbName; 22 | } 23 | 24 | bool PythonExporter::exportToPython(const QString& outputPath) 25 | { 26 | QFile file(outputPath); 27 | if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { 28 | qDebug() << "无法创建文件:" << outputPath; 29 | return false; 30 | } 31 | 32 | QTextStream out(&file); 33 | out.setEncoding(QStringConverter::Utf8); 34 | 35 | QString pythonCode = generatePythonCode(); 36 | out << pythonCode; 37 | 38 | file.close(); 39 | return true; 40 | } 41 | 42 | void PythonExporter::clear() 43 | { 44 | m_dataFields.clear(); 45 | m_databaseName.clear(); 46 | } 47 | 48 | QString PythonExporter::generatePythonCode() 49 | { 50 | QString code; 51 | QTextStream stream(&code); 52 | 53 | // 文件头部 54 | stream << "#!/usr/bin/python3\n"; 55 | stream << "# -*- coding:utf-8 -*-\n"; 56 | stream << "import numpy as np\n"; 57 | stream << "from matplotlib import pyplot as plt\n"; 58 | stream << "import sys\n"; 59 | stream << "import os\n"; 60 | stream << "from utilities.LogDBParser import LogDBParser\n"; 61 | stream << "import platform\n"; 62 | stream << "import matplotlib\n\n"; 63 | 64 | // 平台检查 65 | stream << "# Check if running on Linux\n"; 66 | stream << "if platform.system() == 'Linux':\n"; 67 | stream << " try:\n"; 68 | stream << " matplotlib.use('TkAgg')\n"; 69 | stream << " except ImportError:\n"; 70 | stream << " print(\"sudo apt-get install python3-tk\")\n"; 71 | stream << " # Fallback to default backend\n"; 72 | stream << " pass\n\n"; 73 | 74 | // 命令行参数处理 75 | stream << "# Handle command line arguments\n"; 76 | stream << "if len(sys.argv) > 1:\n"; 77 | stream << " db_name = sys.argv[1]\n"; 78 | stream << "else:\n"; 79 | stream << " db_name = \"" << m_databaseName << "\"\n\n"; 80 | 81 | // 路径处理 82 | stream << "# Cross-platform path handling\n"; 83 | stream << "db_path = os.path.join('..', db_name)\n\n"; 84 | 85 | // 文件存在检查 86 | stream << "# Check if file exists\n"; 87 | stream << "if not os.path.exists(db_path):\n"; 88 | stream << " print(f\"Error: Database file not found: {db_path}\")\n"; 89 | stream << " sys.exit(1)\n\n"; 90 | 91 | stream << "log = LogDBParser(db_path)\n\n"; 92 | 93 | // 数据读取部分 94 | stream << "try:\n"; 95 | 96 | int dataIndex = 0; 97 | for (const auto& field : m_dataFields) { 98 | // 生成 getData 调用 99 | stream << " data = log.getData(\"" << field.logType << "\""; 100 | for (const QString& fieldName : field.fields) { 101 | stream << ",\"" << fieldName << "\""; 102 | } 103 | stream << ")\n"; 104 | 105 | // 生成变量赋值 106 | for (int i = 0; i < field.fields.size(); ++i) { 107 | QString varName = generateVariableNames(field.logType, field.fields[i]); 108 | stream << " " << cleanPythonVariableName(varName) << " = data[" << i << "]\n"; 109 | } 110 | stream << "\n"; 111 | 112 | dataIndex++; 113 | } 114 | 115 | stream << "except Exception as e:\n"; 116 | stream << " print(f\"Error: Problem reading data - {e}\")\n"; 117 | stream << " sys.exit(1)\n"; 118 | 119 | return code; 120 | } 121 | 122 | QString PythonExporter::generateVariableNames(const QString& logType, const QString& field) 123 | { 124 | return logType.toLower() + "_" + field.toLower(); 125 | } 126 | 127 | // 简化的Python变量名清理函数(针对文件名场景) 128 | QString PythonExporter::cleanPythonVariableName(const QString& original) { 129 | QString cleaned = original; 130 | 131 | // 只处理最常见的不符合Python变量命名的字符 132 | cleaned.replace('[', '_'); 133 | cleaned.replace(']', ' '); // 直接删除右括号,避免多余下划线 134 | cleaned.replace('-', '_'); 135 | cleaned.replace('.', '_'); 136 | cleaned.replace(' ', '_'); 137 | 138 | // 移除连续的下划线 139 | while (cleaned.contains(QLatin1String("__"))) { 140 | cleaned.replace(QLatin1String("__"), QLatin1String("_")); 141 | } 142 | 143 | // 移除末尾的下划线 144 | while (cleaned.endsWith('_')) { 145 | cleaned.chop(1); 146 | } 147 | 148 | // 如果以数字开头,添加前缀 149 | if (!cleaned.isEmpty() && cleaned[0].isDigit()) { 150 | cleaned = "var_" + cleaned; 151 | } 152 | 153 | return cleaned; 154 | } 155 | -------------------------------------------------------------------------------- /src/PythonExporterCSV.cpp: -------------------------------------------------------------------------------- 1 | #include "PythonExporterCSV.h" 2 | #include 3 | 4 | PythonExporterCSV* PythonExporterCSV::_singleton = nullptr; 5 | 6 | PythonExporterCSV::PythonExporterCSV() 7 | { 8 | _singleton = this; 9 | } 10 | 11 | void PythonExporterCSV::addDataField(const QString& logType, const QStringList& fields) 12 | { 13 | CSVDataField field; 14 | field.logType = logType; 15 | field.fields = fields; 16 | m_dataFields.append(field); 17 | } 18 | 19 | void PythonExporterCSV::setLogFolderName(const QString& folderName) 20 | { 21 | m_logFolderName = folderName; 22 | } 23 | 24 | bool PythonExporterCSV::exportToPython(const QString& outputPath) 25 | { 26 | QFile file(outputPath); 27 | if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { 28 | qDebug() << "无法创建文件:" << outputPath; 29 | return false; 30 | } 31 | 32 | QTextStream out(&file); 33 | out.setEncoding(QStringConverter::Utf8); 34 | 35 | QString pythonCode = generatePythonCode(); 36 | out << pythonCode; 37 | 38 | file.close(); 39 | return true; 40 | } 41 | 42 | void PythonExporterCSV::clear() 43 | { 44 | m_dataFields.clear(); 45 | m_logFolderName.clear(); 46 | } 47 | 48 | QString PythonExporterCSV::generatePythonCode() 49 | { 50 | QString code; 51 | QTextStream stream(&code); 52 | 53 | // 文件头部 54 | stream << "#!/usr/bin/python3\n"; 55 | stream << "# -*- coding:utf-8 -*-\n"; 56 | stream << "import numpy as np\n"; 57 | stream << "from matplotlib import pyplot as plt\n"; 58 | stream << "import sys\n"; 59 | stream << "import os\n"; 60 | stream << "from utilities.LogCSVParser import LogCSVParser\n"; 61 | stream << "import platform\n"; 62 | stream << "import matplotlib\n\n"; 63 | 64 | // 平台检查 65 | stream << "# Check if running on Linux\n"; 66 | stream << "if platform.system() == 'Linux':\n"; 67 | stream << " try:\n"; 68 | stream << " matplotlib.use('TkAgg')\n"; 69 | stream << " except ImportError:\n"; 70 | stream << " print(\"sudo apt-get install python3-tk\")\n"; 71 | stream << " # Fallback to default backend\n"; 72 | stream << " pass\n\n"; 73 | 74 | // 命令行参数处理 75 | stream << "# Handle command line arguments\n"; 76 | stream << "if len(sys.argv) > 1:\n"; 77 | stream << " log_folder_name = sys.argv[1]\n"; 78 | stream << "else:\n"; 79 | stream << " log_folder_name = \"" << m_logFolderName << "\"\n\n"; 80 | 81 | // 路径处理 82 | stream << "# Cross-platform path handling\n"; 83 | stream << "log_dir_path = os.path.join('..', log_folder_name)\n\n"; 84 | 85 | // 文件夹存在检查 86 | stream << "# Check if file exists\n"; 87 | stream << "if not os.path.isdir(log_dir_path):\n"; 88 | stream << " print(f\"Error: Log directory not found: {log_dir_path}\")\n"; 89 | stream << " sys.exit(1)\n\n"; 90 | 91 | stream << "log = LogCSVParser(log_dir_path)\n\n"; 92 | 93 | // 数据读取部分 94 | stream << "try:\n"; 95 | 96 | for (const auto& field : m_dataFields) { 97 | stream << generateDataReading(field); 98 | stream << "\n"; 99 | } 100 | 101 | // 异常处理 102 | stream << "except (FileNotFoundError, KeyError, Exception) as e:\n"; 103 | stream << " print(f\"Error: Problem reading data - {e}\")\n"; 104 | stream << " sys.exit(1)\n"; 105 | 106 | return code; 107 | } 108 | 109 | QString PythonExporterCSV::generateDataReading(const CSVDataField& field) 110 | { 111 | QString code; 112 | QTextStream stream(&code); 113 | 114 | // 生成 get_data 调用 115 | stream << " data = log.get_data(\"" << field.logType << "\")\n"; 116 | 117 | // 生成变量赋值 118 | for (const QString& fieldName : field.fields) { 119 | QString varName = generateVariableNames(field.logType, fieldName); 120 | stream << " " < "tecs", "ARSP0" -> "arsp0" 129 | QString prefix = logType.toLower(); 130 | 131 | // 将 field 转换为小写,如 "TimeUS" -> "timeus", "Airspeed" -> "airspeed" 132 | QString suffix = field.toLower(); 133 | 134 | return prefix + "_" + suffix; 135 | } 136 | 137 | // 简化的Python变量名清理函数(针对文件名场景) 138 | QString PythonExporterCSV::cleanPythonVariableName(const QString& original) { 139 | QString cleaned = original; 140 | 141 | // 只处理最常见的不符合Python变量命名的字符 142 | cleaned.replace('[', '_'); 143 | cleaned.replace(']', ' '); // 直接删除右括号,避免多余下划线 144 | cleaned.replace('-', '_'); 145 | cleaned.replace('.', '_'); 146 | cleaned.replace(' ', '_'); 147 | 148 | // 移除连续的下划线 149 | while (cleaned.contains(QLatin1String("__"))) { 150 | cleaned.replace(QLatin1String("__"), QLatin1String("_")); 151 | } 152 | 153 | // 移除末尾的下划线 154 | while (cleaned.endsWith('_')) { 155 | cleaned.chop(1); 156 | } 157 | 158 | // 如果以数字开头,添加前缀 159 | if (!cleaned.isEmpty() && cleaned[0].isDigit()) { 160 | cleaned = "var_" + cleaned; 161 | } 162 | 163 | return cleaned; 164 | } 165 | -------------------------------------------------------------------------------- /mainwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_H 2 | #define MAINWINDOW_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "ui_mainwindow.h" 10 | 11 | #include "APLLoggingCategory.h" 12 | #include "APLDockWidget.h" 13 | #include "src/Dialog.h" 14 | #include "src/DialogLoad.h" 15 | #include "src/DialogPython.h" 16 | #include "src/PythonExporter.h" 17 | #include "src/PythonExporterCSV.h" 18 | #include "qcustomplot.h" 19 | 20 | Q_DECLARE_LOGGING_CATEGORY(MAIN_WINDOW_LOG) 21 | 22 | class QCheckBox; 23 | 24 | class MainWindow : public QMainWindow 25 | { 26 | Q_OBJECT 27 | 28 | public: 29 | explicit MainWindow(QWidget *parent = 0); 30 | ~MainWindow(); 31 | 32 | static bool get_X_axis_changed() { return _X_axis_changed; } 33 | static void set_X_axis_changed(bool b) { _X_axis_changed = b; } 34 | static MainWindow* getMainWindow() { return _instance; } 35 | Ui::MainWindow& ui() { return _ui; } 36 | QSqlDatabase& db() { return _apldb; } 37 | Dialog* dialog() { return _dialog; } 38 | void requestTableList(); 39 | void closeEvent(QCloseEvent * event); 40 | 41 | void initTreeWidget(); 42 | bool isTopItem(QTreeWidgetItem* item); 43 | void setChildCheckState(QTreeWidgetItem *item, Qt::CheckState cs, int column); 44 | void setParentCheckState(QTreeWidgetItem *item, int column); 45 | void set_conf(QStringList conf) { _conf = conf; } 46 | const QList& get_x_us(void) { return _x_us; } 47 | 48 | QVector shapes[10]; 49 | QVector colors[10]; 50 | bool isDirExist(QString fullPath) 51 | { 52 | QDir dir(fullPath); 53 | 54 | if(dir.exists()) 55 | { 56 | return true; 57 | } 58 | return false; 59 | } 60 | 61 | uint8_t x_unit; // 0-us, 1-ms 62 | 63 | public slots: 64 | void itemChangedSlot(QTreeWidgetItem* item, int column); 65 | void plotGraph(QString tables, 66 | QString fields, 67 | int offsetX, 68 | double offsetY, 69 | double scale, 70 | int linestyle, 71 | int color, 72 | bool visible, 73 | bool from); // false:DataAnalyzeController,true:Other 74 | void clear_alreadyPloted() { _alreadyPloted.clear(); _color_idx = -1; } 75 | void clearGraph(); 76 | void clearGraphNotTree(); 77 | void clearFixedMarkers(); 78 | void plotConf(QStringList conf); 79 | 80 | private slots: 81 | void _showDockWidgetAction(bool show); 82 | void _plotGraph(QTreeWidgetItem *item, int column); 83 | void _removeGraph(QTreeWidgetItem *item, int column); 84 | void _resetGraph(); 85 | void _zoomX(); 86 | void _zoomY(); 87 | void _zoomAll(); 88 | void on_customPlot_customContextMenuRequested(); 89 | void _saveSuccessMessage(); 90 | void _confOpenedTrigger(); 91 | void _onMouseMove(QMouseEvent *event); 92 | void _onMousePress(QMouseEvent *event); 93 | void _onTracerToggled(bool checked); 94 | void _generatePyDB(bool checked); 95 | void _generatePyCSV(bool checked); 96 | 97 | signals: 98 | void treeWidgetAddItem(QString name); 99 | void tableListReady(); 100 | void dataReady(QMap data); 101 | 102 | private: 103 | Ui::MainWindow _ui; 104 | Dialog* _dialog; 105 | DialogLoad* _dialog_load; 106 | DialogPython* _dialog_python; 107 | QCPItemStraightLine* _mTracerLine; 108 | QCPItemText* _mTracerText; 109 | PythonExporter* _genPyDB; 110 | PythonExporterCSV* _genPyCSV; 111 | bool _mIsTracerEnabled; 112 | QList _mFixedLines; 113 | QList _mFixedTexts; 114 | QList _x_us; 115 | static bool _customPlot_hold_on; 116 | static bool _X_axis_changed; 117 | static MainWindow* _instance; 118 | QString _table; 119 | QString _field; 120 | QStringList _alreadyPloted; 121 | QStringList _groupName; 122 | QStringList _conf; 123 | bool _conf_plot; 124 | bool _is_constant; 125 | bool _replot; 126 | bool _plotConf; 127 | double _constant_value; 128 | int _action_bold; 129 | QSqlDatabase _apldb; 130 | qint8 _color_idx; 131 | 132 | QMap _mapName2DockWidget; 133 | QMap _mapName2Action; 134 | 135 | void _buildCommonWidgets(void); 136 | void _showDockWidget(const QString &name, bool show); 137 | bool _createInnerDockWidget(const QString& widgetName); 138 | void _fileOpenedTrigger(); 139 | void _clearTreeWidget(QTreeWidget *treeWidget); 140 | void _lineStyle(int index, int i, bool from); 141 | bool _findTable(QString table); 142 | bool _findField(QString table, QString field); 143 | void _processData(double& data, double offset = 0, double scale = 1) { data = (data + offset) * scale; } 144 | }; 145 | 146 | #endif // MAINWINDOW_H 147 | -------------------------------------------------------------------------------- /APLSetup.pri: -------------------------------------------------------------------------------- 1 | QMAKE_POST_LINK += echo "Copying files" 2 | 3 | # 4 | # Copy the application resources to the associated place alongside the application 5 | # 6 | 7 | LinuxBuild { 8 | DESTDIR_COPY_RESOURCE_LIST = $$DESTDIR 9 | } 10 | 11 | MacBuild { 12 | DESTDIR_COPY_RESOURCE_LIST = $$DESTDIR/$${TARGET}.app/Contents/MacOS 13 | } 14 | 15 | # Windows version of QMAKE_COPY_DIR of course doesn't work the same as Mac/Linux. It will only 16 | # copy the contents of the source directory. It doesn't create the top level source directory 17 | # in the target. 18 | WindowsBuild { 19 | # Make sure to keep both side of this if using the same set of directories 20 | DESTDIR_COPY_RESOURCE_LIST = $$replace(DESTDIR,"/","\\") 21 | BASEDIR_COPY_RESOURCE_LIST = $$replace(BASEDIR,"/","\\") 22 | # QMAKE_POST_LINK += $$escape_expand(\\n) $$QMAKE_COPY_DIR \"$$BASEDIR_COPY_RESOURCE_LIST\\resources\\flightgear\" \"$$DESTDIR_COPY_RESOURCE_LIST\\flightgear\" 23 | } else { 24 | !MobileBuild { 25 | # Make sure to keep both sides of this if using the same set of directories 26 | # QMAKE_POST_LINK += && $$QMAKE_COPY_DIR $$BASEDIR/resources/flightgear $$DESTDIR_COPY_RESOURCE_LIST 27 | } 28 | } 29 | 30 | # 31 | # Perform platform specific setup 32 | # 33 | 34 | iOSBuild | MacBuild { 35 | # Update version info in bundle 36 | QMAKE_POST_LINK += && /usr/libexec/PlistBuddy -c \"Set :CFBundleShortVersionString $${MAC_VERSION}\" $$DESTDIR/$${TARGET}.app/Contents/Info.plist 37 | QMAKE_POST_LINK += && /usr/libexec/PlistBuddy -c \"Set :CFBundleVersion $${MAC_BUILD}\" $$DESTDIR/$${TARGET}.app/Contents/Info.plist 38 | } 39 | 40 | MacBuild { 41 | # Copy non-standard frameworks into app package 42 | QMAKE_POST_LINK += && rsync -a --delete $$BASEDIR/libs/lib/Frameworks $$DESTDIR/$${TARGET}.app/Contents/ 43 | } 44 | 45 | WindowsBuild { 46 | BASEDIR_WIN = $$replace(BASEDIR, "/", "\\") 47 | DESTDIR_WIN = $$replace(DESTDIR, "/", "\\") 48 | QT_BIN_DIR = $$dirname(QMAKE_QMAKE) 49 | 50 | # Copy dependencies 51 | DebugBuild: DLL_QT_DEBUGCHAR = "d" 52 | ReleaseBuild: DLL_QT_DEBUGCHAR = "" 53 | 54 | for(COPY_FILE, COPY_FILE_LIST) { 55 | QMAKE_POST_LINK += $$escape_expand(\\n) $$QMAKE_COPY \"$$COPY_FILE\" \"$$DESTDIR_WIN\" 56 | } 57 | 58 | # ReleaseBuild { 59 | # # Copy Visual Studio DLLs 60 | # # Note that this is only done for release because the debugging versions of these DLLs cannot be redistributed. 61 | # win32-msvc2010 { 62 | # QMAKE_POST_LINK += $$escape_expand(\\n) $$QMAKE_COPY \"C:\\Windows\\System32\\msvcp100.dll\" \"$$DESTDIR_WIN\" 63 | # QMAKE_POST_LINK += $$escape_expand(\\n) $$QMAKE_COPY \"C:\\Windows\\System32\\msvcr100.dll\" \"$$DESTDIR_WIN\" 64 | # 65 | # } else:win32-msvc2012 { 66 | # QMAKE_POST_LINK += $$escape_expand(\\n) $$QMAKE_COPY \"C:\\Windows\\System32\\msvcp110.dll\" \"$$DESTDIR_WIN\" 67 | # QMAKE_POST_LINK += $$escape_expand(\\n) $$QMAKE_COPY \"C:\\Windows\\System32\\msvcr110.dll\" \"$$DESTDIR_WIN\" 68 | # 69 | # } else:win32-msvc2013 { 70 | # QMAKE_POST_LINK += $$escape_expand(\\n) $$QMAKE_COPY \"C:\\Windows\\System32\\msvcp120.dll\" \"$$DESTDIR_WIN\" 71 | # QMAKE_POST_LINK += $$escape_expand(\\n) $$QMAKE_COPY \"C:\\Windows\\System32\\msvcr120.dll\" \"$$DESTDIR_WIN\" 72 | # 73 | # } else:win32-msvc2015 { 74 | # QMAKE_POST_LINK += $$escape_expand(\\n) $$QMAKE_COPY \"C:\\Windows\\System32\\msvcp140.dll\" \"$$DESTDIR_WIN\" 75 | # QMAKE_POST_LINK += $$escape_expand(\\n) $$QMAKE_COPY \"C:\\Windows\\System32\\vcruntime140.dll\" \"$$DESTDIR_WIN\" 76 | # 77 | # } else { 78 | # error("Visual studio version not supported, installation cannot be completed.") 79 | # } 80 | # } 81 | 82 | DEPLOY_TARGET = $$shell_quote($$shell_path($$DESTDIR_WIN\\$${TARGET}.exe)) 83 | QMAKE_POST_LINK += $$escape_expand(\\n) $$QT_BIN_DIR\\windeployqt --no-compiler-runtime --qmldir=$${BASEDIR_WIN}\\src $${DEPLOY_TARGET} 84 | } 85 | 86 | LinuxBuild { 87 | QMAKE_POST_LINK += && mkdir -p $$DESTDIR/Qt/libs && mkdir -p $$DESTDIR/Qt/plugins 88 | 89 | # QT_INSTALL_LIBS 90 | QT_LIB_LIST = \ 91 | libQt6Core.so.6 \ 92 | libQt6DBus.so.6 \ 93 | libQt6Gui.so.6 \ 94 | libQt6Location.so.6 \ 95 | libQt6Multimedia.so.6 \ 96 | libQt6Network.so.6 \ 97 | libQt6OpenGL.so.6 \ 98 | libQt6Positioning.so.6 \ 99 | libQt6PrintSupport.so.6 \ 100 | libQt6Qml.so.6 \ 101 | libQt6Quick.so.6 \ 102 | libQt6QuickControls2.so.6 \ 103 | libQt6QuickTemplates2.so.6 \ 104 | libQt6QuickWidgets.so.6 \ 105 | libQt6SerialPort.so.6 \ 106 | libQt6Sql.so.6 \ 107 | libQt6Svg.so.6 \ 108 | libQt6Test.so.6 \ 109 | libQt6Widgets.so.6 \ 110 | libQt6XcbQpa.so.6 \ 111 | libQt6Xml.so.6 \ 112 | libQt6TextToSpeech.so.6 113 | 114 | 115 | for(QT_LIB, QT_LIB_LIST) { 116 | QMAKE_POST_LINK += && $$QMAKE_COPY --dereference $$[QT_INSTALL_LIBS]/$$QT_LIB $$DESTDIR/Qt/libs/ 117 | } 118 | 119 | # QT_INSTALL_PLUGINS 120 | QT_PLUGIN_LIST = \ 121 | geoservices \ 122 | iconengines \ 123 | imageformats \ 124 | platforminputcontexts \ 125 | platforms \ 126 | position \ 127 | sqldrivers 128 | 129 | !contains(DEFINES, __rasp_pi2__) { 130 | QT_PLUGIN_LIST += xcbglintegrations 131 | } 132 | 133 | for(QT_PLUGIN, QT_PLUGIN_LIST) { 134 | QMAKE_POST_LINK += && $$QMAKE_COPY --dereference --recursive $$[QT_INSTALL_PLUGINS]/$$QT_PLUGIN $$DESTDIR/Qt/plugins/ 135 | } 136 | 137 | # QT_INSTALL_QML 138 | QMAKE_POST_LINK += && $$QMAKE_COPY --dereference --recursive $$[QT_INSTALL_QML] $$DESTDIR/Qt/ 139 | 140 | # ArduPilotLog start script 141 | QMAKE_POST_LINK += && $$QMAKE_COPY $$BASEDIR/deploy/ardupilotlog-start.sh $$DESTDIR 142 | QMAKE_POST_LINK += && $$QMAKE_COPY $$BASEDIR/deploy/ardupilotlog.desktop $$DESTDIR 143 | } 144 | -------------------------------------------------------------------------------- /mainwindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 400 10 | 300 11 | 12 | 13 | 14 | 15 | 0 16 | 0 17 | 18 | 19 | 20 | MainWindow 21 | 22 | 23 | 24 | :/res/resources/icons/a05.ico:/res/resources/icons/a05.ico 25 | 26 | 27 | 28 | 29 | 0 30 | 0 31 | 32 | 33 | 34 | 35 | 1 36 | 37 | 38 | 3 39 | 40 | 41 | 1 42 | 43 | 44 | 3 45 | 46 | 47 | 3 48 | 49 | 50 | 51 | 52 | 53 | 0 54 | 0 55 | 56 | 57 | 58 | Qt::Orientation::Horizontal 59 | 60 | 61 | 3 62 | 63 | 64 | 65 | 66 | 5 67 | 68 | 69 | 70 | 71 | 72 | 1 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 2 83 | 84 | 85 | 1 86 | 87 | 88 | 1 89 | 90 | 91 | 1 92 | 93 | 94 | 1 95 | 96 | 97 | 98 | 99 | 100 | 280 101 | 0 102 | 103 | 104 | 105 | 24 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 0 114 | 0 115 | 116 | 117 | 118 | Qt::ContextMenuPolicy::CustomContextMenu 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 0 132 | 0 133 | 400 134 | 17 135 | 136 | 137 | 138 | 139 | File 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | Tools 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | Open ArduPilotLog 165 | 166 | 167 | 168 | 169 | Export *.db 170 | 171 | 172 | 173 | 174 | Load *.conf 175 | 176 | 177 | 178 | 179 | Trim 180 | 181 | 182 | 183 | 184 | Export *.csv 185 | 186 | 187 | 188 | 189 | Load *.py 190 | 191 | 192 | 193 | 194 | Generate .py for DB 195 | 196 | 197 | 198 | 199 | Generate .py for CSV 200 | 201 | 202 | 203 | 204 | 205 | 206 | QCustomPlot 207 | QWidget 208 |
qcustomplot.h
209 | 1 210 |
211 |
212 | 213 | 214 | 215 | 216 |
217 | -------------------------------------------------------------------------------- /APLCommon.pri: -------------------------------------------------------------------------------- 1 | linux { 2 | linux-g++ | linux-g++-64 | linux-g++-32 | linux-clang { 3 | message("Linux build") 4 | CONFIG += LinuxBuild 5 | DEFINES += __STDC_LIMIT_MACROS 6 | DEFINES += PACKED=/**/ 7 | linux-clang { 8 | message("Linux clang") 9 | QMAKE_CXXFLAGS += -Qunused-arguments -fcolor-diagnostics 10 | } 11 | } else : linux-rasp-pi2-g++ { 12 | message("Linux R-Pi2 build") 13 | CONFIG += LinuxBuild 14 | DEFINES += __STDC_LIMIT_MACROS __rasp_pi2__ 15 | } else : android-g++ { 16 | CONFIG += AndroidBuild MobileBuild 17 | DEFINES += __android__ 18 | DEFINES += __STDC_LIMIT_MACROS 19 | DEFINES += APL_ENABLE_BLUETOOTH 20 | target.path = $$DESTDIR 21 | equals(ANDROID_TARGET_ARCH, x86) { 22 | CONFIG += Androidx86Build 23 | DEFINES += __androidx86__ 24 | message("Android x86 build") 25 | } else { 26 | message("Android Arm build") 27 | } 28 | } else { 29 | error("Unsuported Linux toolchain, only GCC 32- or 64-bit is supported") 30 | } 31 | } else : win32 { 32 | win32-msvc* { 33 | message("Windows build") 34 | CONFIG += WindowsBuild 35 | DEFINES += __STDC_LIMIT_MACROS 36 | DEFINES += PACKED=/**/ 37 | } else { 38 | DEFINES += PACKED=/**/ 39 | #error("Unsupported Windows toolchain, only Visual Studio is supported") 40 | } 41 | } else : macx { 42 | macx-clang | macx-llvm { 43 | message("Mac build") 44 | CONFIG += MacBuild 45 | DEFINES += __macos__ 46 | CONFIG += x86_64 47 | CONFIG -= x86 48 | equals(QT_MAJOR_VERSION, 5) | greaterThan(QT_MINOR_VERSION, 5) { 49 | QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.7 50 | } else { 51 | QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.6 52 | } 53 | #-- Not forcing anything. Let qmake find the latest, installed SDK. 54 | #QMAKE_MAC_SDK = macosx10.12 55 | QMAKE_CXXFLAGS += -fvisibility=hidden 56 | } else { 57 | error("Unsupported Mac toolchain, only 64-bit LLVM+clang is supported") 58 | } 59 | } else : ios { 60 | !equals(QT_MAJOR_VERSION, 5) | !greaterThan(QT_MINOR_VERSION, 4) { 61 | error("Unsupported Qt version, 5.5.x or greater is required for iOS") 62 | } 63 | message("iOS build") 64 | CONFIG += iOSBuild MobileBuild app_bundle NoSerialBuild 65 | CONFIG -= bitcode 66 | DEFINES += __ios__ 67 | DEFINES += APL_NO_GOOGLE_MAPS 68 | DEFINES += NO_SERIAL_LINK 69 | DEFINES += APL_DISABLE_UVC 70 | QMAKE_IOS_DEPLOYMENT_TARGET = 8.0 71 | QMAKE_IOS_TARGETED_DEVICE_FAMILY = 1,2 # Universal 72 | QMAKE_LFLAGS += -Wl,-no_pie 73 | } else { 74 | error("Unsupported build platform, only Linux, Windows, Android and Mac (Mac OS and iOS) are supported") 75 | } 76 | 77 | # Enable ccache where we can 78 | linux|macx|ios { 79 | system(which ccache) { 80 | message("Found ccache, enabling") 81 | !ios { 82 | QMAKE_CXX = ccache $$QMAKE_CXX 83 | QMAKE_CC = ccache $$QMAKE_CC 84 | } else { 85 | QMAKE_CXX = $$PWD/tools/iosccachecc.sh 86 | QMAKE_CC = $$PWD/tools/iosccachecxx.sh 87 | } 88 | } 89 | } 90 | 91 | MobileBuild { 92 | DEFINES += __mobile__ 93 | } 94 | 95 | # set the APL version from git 96 | 97 | exists ($$PWD/.git) { 98 | GIT_DESCRIBE = $$system(git --git-dir $$PWD/.git --work-tree $$PWD describe --always --tags) 99 | GIT_BRANCH = $$system(git --git-dir $$PWD/.git --work-tree $$PWD rev-parse --abbrev-ref HEAD) 100 | GIT_HASH = $$system(git --git-dir $$PWD/.git --work-tree $$PWD rev-parse --short HEAD) 101 | GIT_TIME = $$system(git --git-dir $$PWD/.git --work-tree $$PWD show --oneline --format=\"%ci\" -s HEAD) 102 | 103 | # determine if we're on a tag matching vX.Y.Z (stable release) 104 | contains(GIT_DESCRIBE, v[0-9]+.[0-9]+.[0-9]+) { 105 | # release version "vX.Y.Z" 106 | GIT_VERSION = $${GIT_DESCRIBE} 107 | } else { 108 | # development version "Development branch:sha date" 109 | GIT_VERSION = "Development $${GIT_BRANCH}:$${GIT_HASH} $${GIT_TIME}" 110 | } 111 | 112 | VERSION = $$replace(GIT_DESCRIBE, "v", "") 113 | VERSION = $$replace(VERSION, "-", ".") 114 | VERSION = $$section(VERSION, ".", 0, 3) 115 | MacBuild { 116 | MAC_VERSION = $$section(VERSION, ".", 0, 2) 117 | MAC_BUILD = $$section(VERSION, ".", 3, 3) 118 | message(ArduPilotLog version $${MAC_VERSION} build $${MAC_BUILD} describe $${GIT_VERSION}) 119 | } else { 120 | message(ArduPilotLog $${GIT_VERSION}) 121 | } 122 | } else { 123 | GIT_VERSION = None 124 | VERSION = 0.0.0 # Marker to indicate out-of-tree build 125 | MAC_VERSION = 0.0.0 126 | MAC_BUILD = 0 127 | } 128 | 129 | DEFINES += GIT_VERSION=\"\\\"$$GIT_VERSION\\\"\" 130 | DEFINES += EIGEN_MPL2_ONLY 131 | 132 | # Installer configuration 133 | 134 | installer { 135 | CONFIG -= debug 136 | CONFIG -= debug_and_release 137 | CONFIG += release 138 | message(Build Installer) 139 | } 140 | 141 | # Setup our supported build flavors 142 | 143 | CONFIG(debug, debug|release) { 144 | message(Debug flavor) 145 | CONFIG += DebugBuild 146 | } else:CONFIG(release, debug|release) { 147 | message(Release flavor) 148 | CONFIG += ReleaseBuild 149 | } else { 150 | error(Unsupported build flavor) 151 | } 152 | 153 | # Setup our build directories 154 | 155 | BASEDIR = $$IN_PWD 156 | 157 | !iOSBuild { 158 | OBJECTS_DIR = $${OUT_PWD}/obj 159 | MOC_DIR = $${OUT_PWD}/moc 160 | UI_DIR = $${OUT_PWD}/ui 161 | RCC_DIR = $${OUT_PWD}/rcc 162 | } 163 | 164 | LANGUAGE = C++ 165 | 166 | LOCATION_PLUGIN_DESTDIR = $${OUT_PWD}/src/QtLocationPlugin 167 | LOCATION_PLUGIN_NAME = QGeoServiceProviderFactoryAPL 168 | 169 | # Turn off serial port warnings 170 | DEFINES += _TTY_NOWARN_ 171 | 172 | MacBuild | LinuxBuild { 173 | QMAKE_CXXFLAGS_WARN_ON += -Wall 174 | WarningsAsErrorsOn { 175 | QMAKE_CXXFLAGS_WARN_ON += -Werror 176 | } 177 | MacBuild { 178 | # Latest clang version has a buggy check for this which cause Qt headers to throw warnings on qmap.h 179 | QMAKE_CXXFLAGS_WARN_ON += -Wno-return-stack-address 180 | # Xcode 8.3 has issues on how MAVLink accesses (packed) message structure members. 181 | # Note that this will fail when Xcode version reaches 10.x.x 182 | XCODE_VERSION = $$system($$PWD/tools/get_xcode_version.sh) 183 | greaterThan(XCODE_VERSION, 8.2.0): QMAKE_CXXFLAGS_WARN_ON += -Wno-address-of-packed-member 184 | } 185 | } 186 | 187 | WindowsBuild { 188 | QMAKE_CXXFLAGS_WARN_ON += /W3 \ 189 | /wd4996 \ # silence warnings about deprecated strcpy and whatnot 190 | /wd4005 \ # silence warnings about macro redefinition 191 | /wd4290 # ignore exception specifications 192 | 193 | WarningsAsErrorsOn { 194 | QMAKE_CXXFLAGS_WARN_ON += /WX 195 | } 196 | } 197 | 198 | # 199 | # Build-specific settings 200 | # 201 | 202 | ReleaseBuild { 203 | DEFINES += QT_NO_DEBUG QT_MESSAGELOGCONTEXT 204 | CONFIG += force_debug_info # Enable debugging symbols on release builds 205 | !iOSBuild { 206 | CONFIG += ltcg # Turn on link time code generation 207 | } 208 | 209 | WindowsBuild { 210 | # Enable function level linking and enhanced optimized debugging 211 | QMAKE_CFLAGS_RELEASE += /Gy /Zo 212 | QMAKE_CXXFLAGS_RELEASE += /Gy /Zo 213 | QMAKE_CFLAGS_RELEASE_WITH_DEBUGINFO += /Gy /Zo 214 | QMAKE_CXXFLAGS_RELEASE_WITH_DEBUGINFO += /Gy /Zo 215 | 216 | # Eliminate duplicate COMDATs 217 | QMAKE_LFLAGS_RELEASE += /OPT:ICF 218 | QMAKE_LFLAGS_RELEASE_WITH_DEBUGINFO += /OPT:ICF 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /matlab/char2cell.m: -------------------------------------------------------------------------------- 1 | function s = char2cell(s,delim,rowseparate,trim) 2 | % Syntax: cellmat = char2cell(s,delim,rowseparate,trim); 3 | % cellmat = char2cell(s,delim,rowseparate); 4 | % cellmat = char2cell(s,delim); 5 | % cellmat = char2cell(s); 6 | % CHARacter array 2(to) CELL array conversion. 7 | % "s" - Input character array or cell array of strings. 8 | % "delim" - Array or cell array of delimiters. 9 | % If an array then each element is a single element delimeter. 10 | % If a cell array then each cell element is a delimeter or multi-element delimeter. 11 | % The following may be used for non-printing characters: 12 | % (as these characters require 2 elements, they must be specified in a cell array) 13 | % \b - backspace 14 | % \f - formfeed 15 | % \n - linefeed 16 | % \r - carriage return 17 | % \t - tab 18 | % \\ - backslash 19 | % (a single \ suffices if it is a single or last element) 20 | % Use ' ' to specify a white space character. 21 | % Default delimiter is white space if "rowseparate" is empty and is empty 22 | % if "rowseparate" is either "true" or "false". 23 | % "rowseparate" - "true" designates that each row should be separated or 24 | % "false" if each column should be separated. Only relevant if "s" is 25 | % multidimensional. Higher dimensions are wrapped into rows or columns 26 | % depending upon "rowseparate". If "empty", then all matrices are treated as 1D. 27 | % e.g. a KxMxN matrix is treated as a Kx(MxN) matrix if "false" 28 | % or as a Mx(KxN) if "true". 29 | % If a delimeter(s) is specified then both conditions are used. 30 | % "rowseparate" is ignored if "s" is a cell array. 31 | % "trim" - if "true" (default), leading and trailing spaces are deleted 32 | % "cellmat" - Output (1D) cell array. 33 | % 34 | % See Also: cellstr, mat2cell, num2cell, cell2mat 35 | % Examples: 36 | % char2cell(['red? green33 blue++ '],['?3+'])) 37 | % ans = 'red'; 'green'; 'blue'; '' 38 | % c=sprintf(['this is a test\nthis\tis the second line']) 39 | % char2cell(c,{'\n','\t'}) 40 | % ans = 'this is a test' 41 | % 'this' 42 | % 'is the second line' 43 | 44 | % Copyright 2009 Mirtech, Inc. 45 | % Created by Mirko Hrovat 07/17/2009 46 | % Inspired by str2cell.m (File Exchange #4247) by us 47 | %------------------------------------------------------------------------------------ 48 | 49 | ws = ' '; % whitespace 50 | bspc = 8; % \b 51 | ff = 12; % \f 52 | lf = 10; % \n 53 | cr = 13; % \r 54 | tb = 9; % \t 55 | bs = 92; % \\ 56 | del = 127; %#ok - currently not used, delete 57 | spc = 32; % space 58 | bell = 8; %#ok - currently not used, bell 59 | def_delim = ws; % default delimiter 60 | def_trim = true; % default trim value 61 | switch nargin 62 | case 4 63 | case 3 64 | trim = []; 65 | case 2 66 | trim = []; rowseparate = []; 67 | case 1 68 | trim = []; rowseparate = []; delim = []; 69 | otherwise 70 | error (' Number of input arguments incorrect!') 71 | end 72 | if isempty(trim), trim = def_trim; end 73 | if ~ischar(s)&&~iscellstr(s), 74 | error (' Input array must be a character or cell string array!') 75 | end 76 | if ~iscellstr(s), 77 | if isempty(rowseparate) || size(s,1)==1 || size(s,2)==1, 78 | s = shiftdim(s(:),-1); % convert to row of characters 79 | rowseparate = []; 80 | else 81 | if ~rowseparate, 82 | s = permute(s,[2,1]); 83 | end 84 | s = s(:,:); % make s a 2D array 85 | s = cellstr(s); % now convert rows to cells 86 | end 87 | if isempty(rowseparate) && isempty(delim), 88 | delim = def_delim; 89 | end 90 | else 91 | if isempty(delim), delim = def_delim; end 92 | end 93 | if ~isempty(delim), 94 | if ~iscell(delim), delim = num2cell(delim); end 95 | strtidx = []; 96 | stopidx = []; 97 | for n = 1:numel(delim), 98 | mpts = numel(delim{n}); 99 | searchchar = char(ones(1,mpts)*spc); 100 | m = 0; 101 | nschars = 0; 102 | while m < mpts, 103 | m = m + 1; 104 | curchar = delim{n}(m); 105 | if curchar=='\' 106 | m = m + 1; 107 | if m <= mpts, 108 | curchar = delim{n}(m); 109 | switch curchar 110 | case 'b' % backspace 111 | curchar = char(bspc); 112 | case 'f' % formfeed 113 | curchar = char(ff); 114 | case 'n' % linefeed 115 | curchar = char(lf); 116 | case 'r' % return 117 | curchar = char(cr); 118 | case 't' % tab 119 | curchar = char(tb); 120 | case '\' % backslash 121 | curchar = char(bs); 122 | otherwise 123 | error(' Special character not recognized, e.g. \n !') 124 | end 125 | else % backslash is a single element or is last element 126 | curchar = char(bs); % so intepret it as a backslash 127 | end 128 | end 129 | nschars = nschars + 1; 130 | searchchar(nschars) = curchar; 131 | end 132 | searchchar(nschars+1:end) = []; 133 | tmp = strfind(s,searchchar); % find matching indices 134 | if iscell(tmp), 135 | stopidx = strcat(stopidx,tmp); % combine results 136 | tmp2 = num2cell(ones(size(s))*nschars); 137 | % add delimiter length to get next starting indices 138 | tmp2 = cellfun(@plus,tmp,tmp2,'UniformOutput',false); 139 | strtidx = strcat(strtidx,tmp2); % combine results 140 | else 141 | stopidx = [stopidx,tmp]; %#ok combine results 142 | tmp2 = tmp + nschars; % add delimiter length 143 | strtidx = [strtidx,tmp2]; %#ok 144 | end 145 | end 146 | 147 | % now use strt and stop idx to create cells 148 | if iscell(s), 149 | ncells = sum(cellfun(@numel,strtidx)); % find total number of indices 150 | scells = size(s,1); 151 | tmp = cell(ncells,1); 152 | count = 0; 153 | for m = 1:scells, 154 | startpt = 1; 155 | strt = sort(strtidx{m}); 156 | stop = sort(stopidx{m}); 157 | for p=1:numel(strt), 158 | if stop(p)>startpt, 159 | count = count + 1; 160 | tmp{count} = s{m}(startpt:stop(p)-1); 161 | end 162 | startpt = strt(p); 163 | end 164 | if startpt <= numel(s{m}), % need to extract the rest of the array 165 | count = count + 1; 166 | tmp{count} = s{m}(startpt:end); 167 | end 168 | end 169 | else % s is not a cell array 170 | strt = sort(strtidx); 171 | stop = sort(stopidx ); 172 | ncells = numel(strtidx); 173 | tmp = cell(ncells+1,1); 174 | startpt = 1; 175 | count = 0; 176 | for m = 1:ncells 177 | if stop(m) > startpt, 178 | count = count + 1; 179 | tmp{count}= s(startpt:stop(m)-1); 180 | end 181 | startpt = strt(m); 182 | end 183 | if startpt <= numel(s), % need to extract the rest of the array 184 | count = count + 1; 185 | tmp{count} = s(startpt:end); 186 | end 187 | end 188 | s = tmp(1:count); 189 | end 190 | if trim, 191 | s = strtrim(s); 192 | end -------------------------------------------------------------------------------- /src/DialogPython.cpp: -------------------------------------------------------------------------------- 1 | #include "DialogPython.h" 2 | #include "APLReadConf.h" 3 | #include "APLDataCache.h" 4 | #include "mainwindow.h" 5 | #include "APLRead.h" 6 | #include 7 | #include 8 | 9 | APL_LOGGING_CATEGORY(DIALOGPYTHON_LOG, "DialogPythonLog") 10 | 11 | DialogPython::DialogPython(QWidget *parent) 12 | : QDialog(parent) 13 | , _aplReadConf(new APLReadConf) 14 | , _qfileDialogLoad(new QFileDialog) 15 | { 16 | connect(_qfileDialogLoad, &QFileDialog::fileSelected, _aplReadConf, &APLReadConf::getFileDir); 17 | } 18 | 19 | DialogPython::~DialogPython() 20 | { 21 | delete _aplReadConf; 22 | delete _qfileDialogLoad; 23 | } 24 | 25 | bool DialogPython::isDirExist(QString fullPath) 26 | { 27 | QDir dir(fullPath); 28 | 29 | if(dir.exists()) 30 | { 31 | return true; 32 | } 33 | return false; 34 | } 35 | 36 | void DialogPython::showFile() 37 | { 38 | QString path = APLRead::getAPLRead()->getFilePath(); 39 | 40 | if (MainWindow::getMainWindow()->dialog()->get_csv_mode()) { 41 | path = MainWindow::getMainWindow()->dialog()->get_logdir(); 42 | QDir currentDir(path); 43 | if (currentDir.cdUp() && currentDir.cdUp()) { // 切换到上 2 级目录 44 | QString parentPath = currentDir.absolutePath(); 45 | QString confPath = QString("%1/Python").arg(parentPath); 46 | if (isDirExist(confPath)) { 47 | path = confPath; 48 | } 49 | } 50 | } else if (isDirExist(QString("%1/Python").arg(path))) { 51 | path = QString("%1/Python").arg(path); 52 | } else { 53 | // 创建 Python 文件夹 54 | QString pythonDir = QString("%1/Python").arg(path); 55 | QDir().mkpath(pythonDir); 56 | 57 | // 创建 utilities 子文件夹 58 | QString utilitiesDir = QString("%1/utilities").arg(pythonDir); 59 | QDir().mkpath(utilitiesDir); 60 | 61 | // 在 Python 文件夹下创建示例文件 62 | QFile file1(QString("%1/QuadPlane_example_01_DB.py").arg(pythonDir)); 63 | if (file1.open(QIODevice::WriteOnly | QIODevice::Text)) { 64 | file1.write(example_01.toUtf8()); 65 | file1.close(); 66 | } 67 | 68 | QFile file2(QString("%1/QuadPlane_example_02_DB.py").arg(pythonDir)); 69 | if (file2.open(QIODevice::WriteOnly | QIODevice::Text)) { 70 | file2.write(example_02_1_of_3.toUtf8()); 71 | file2.write(example_02_2_of_3.toUtf8()); 72 | file2.write(example_02_3_of_3.toUtf8()); 73 | file2.close(); 74 | } 75 | 76 | QFile file3(QString("%1/example_log_download.txt").arg(pythonDir)); 77 | if (file3.open(QIODevice::WriteOnly | QIODevice::Text)) { 78 | file3.write(QString("https://drive.google.com/drive/folders/1YHfcqXl9vxriioPVBlYudXmWCDvZKrsK?usp=sharing").toUtf8()); 79 | file3.close(); 80 | } 81 | 82 | // 在 utilities 文件夹下创建工具文件 83 | QFile parserFile(QString("%1/LogDBParser.py").arg(utilitiesDir)); 84 | if (parserFile.open(QIODevice::WriteOnly | QIODevice::Text)) { 85 | parserFile.write(lib_01.toUtf8()); 86 | parserFile.close(); 87 | } 88 | 89 | QFile mathFile(QString("%1/MathCommon.py").arg(utilitiesDir)); 90 | if (mathFile.open(QIODevice::WriteOnly | QIODevice::Text)) { 91 | mathFile.write(lib_02.toUtf8()); 92 | mathFile.close(); 93 | } 94 | 95 | QFile utilityFile(QString("%1/utilities.py").arg(utilitiesDir)); 96 | if (utilityFile.open(QIODevice::WriteOnly | QIODevice::Text)) { 97 | utilityFile.write(lib_03.toUtf8()); 98 | utilityFile.close(); 99 | } 100 | 101 | QFile parserCSV(QString("%1/LogCSVParser.py").arg(utilitiesDir)); 102 | if (parserCSV.open(QIODevice::WriteOnly | QIODevice::Text)) { 103 | parserCSV.write(lib_04.toUtf8()); 104 | parserCSV.close(); 105 | } 106 | 107 | QFile example_1csv(QString("%1/QuadPlane_example_01_CSV.py").arg(pythonDir)); 108 | if (example_1csv.open(QIODevice::WriteOnly | QIODevice::Text)) { 109 | example_1csv.write(example_03.toUtf8()); 110 | example_1csv.close(); 111 | } 112 | path = pythonDir; // 设置路径为新创建的 Python 文件夹 113 | } 114 | 115 | QString scriptFile = _qfileDialogLoad->getOpenFileName(this 116 | ,"open plot config" 117 | ,path 118 | ,"Config files(*.py)"); 119 | QFileInfo fileInfo(scriptFile); 120 | 121 | QString suffix = fileInfo.suffix(); 122 | if (suffix.compare("py", Qt::CaseSensitive) == 0) { 123 | QProcess *process = new QProcess(this); 124 | 125 | QString pythonPath = MainWindow::getMainWindow()->dialog()->get_python_path(); 126 | QString scriptPath = scriptFile; 127 | 128 | QString db_name = MainWindow::getMainWindow()->dialog()->get_db_name(); 129 | if (db_name.length() == 0 && !MainWindow::getMainWindow()->dialog()->get_python_ingnore_db()) { 130 | QFileInfo fileInfo(MainWindow::getMainWindow()->dialog()->get_logdir()); 131 | QString baseName = fileInfo.completeBaseName(); 132 | QString dirPath = fileInfo.absolutePath(); 133 | QString newExtension = "db"; 134 | db_name = QDir(dirPath).filePath(baseName + "." + newExtension); 135 | } 136 | 137 | QFile dbFile(db_name); 138 | QDir dir; 139 | if (!dbFile.exists() || MainWindow::getMainWindow()->dialog()->get_python_ingnore_db()) { 140 | if (!MainWindow::getMainWindow()->dialog()->get_python_ingnore_db()) { 141 | qCDebug(DIALOGPYTHON_LOG) << QString("%1 does NOT exist! Checking CSV folder...").arg(db_name); 142 | } 143 | db_name = APLDataCache::get_singleton()->get_export_dir(); 144 | if (db_name.length() == 0) { 145 | QFileInfo fileInfo(MainWindow::getMainWindow()->dialog()->get_logdir()); 146 | QString baseName = fileInfo.completeBaseName(); 147 | QString dirPath = fileInfo.absolutePath(); 148 | db_name = QDir(dirPath).filePath(baseName + "_csv"); 149 | if (MainWindow::getMainWindow()->dialog()->get_csv_mode()){ 150 | db_name = dirPath; 151 | } 152 | } 153 | if (!dir.exists(db_name)) { 154 | QMessageBox::information(this,tr("Information"),QString("%1 does NOT exist! Please 'Export *.db' or 'Export *.csv' first.").arg(db_name)); 155 | return; 156 | } 157 | } 158 | 159 | 160 | if (pythonPath.length() > 0) { 161 | // 实时输出标准输出 162 | connect(process, &QProcess::readyReadStandardOutput, 163 | this, [this, process]() { 164 | QByteArray data = process->readAllStandardOutput(); 165 | if (!data.isEmpty()) { 166 | QString message(QString::fromUtf8(data)); 167 | if (message.contains("Error:")){ 168 | QMessageBox::information(this,tr("Information"),message); 169 | } 170 | qCDebug(DIALOGPYTHON_LOG) << "Output:" << message; 171 | } 172 | }); 173 | 174 | // 实时输出标准错误 175 | connect(process, &QProcess::readyReadStandardError, 176 | this, [process]() { 177 | QByteArray data = process->readAllStandardError(); 178 | if (!data.isEmpty()) { 179 | qCDebug(DIALOGPYTHON_LOG) << "Error:" << QString::fromUtf8(data); 180 | } 181 | }); 182 | 183 | // 进程结束时的清理 184 | connect(process, QOverload::of(&QProcess::finished), 185 | this, [process](int exitCode, QProcess::ExitStatus exitStatus) { 186 | Q_UNUSED(exitStatus); 187 | qCDebug(DIALOGPYTHON_LOG) << "Process finished with exit code:" << exitCode; 188 | 189 | // 读取可能剩余的输出 190 | QByteArray remainingOutput = process->readAllStandardOutput(); 191 | QByteArray remainingError = process->readAllStandardError(); 192 | 193 | if (!remainingOutput.isEmpty()) { 194 | qCDebug(DIALOGPYTHON_LOG) << "Final Output:" << QString::fromUtf8(remainingOutput); 195 | } 196 | if (!remainingError.isEmpty()) { 197 | qCDebug(DIALOGPYTHON_LOG) << "Final Error:" << QString::fromUtf8(remainingError); 198 | } 199 | 200 | process->deleteLater(); 201 | }); 202 | 203 | // 进程错误处理 204 | connect(process, &QProcess::errorOccurred, 205 | this, [process](QProcess::ProcessError error) { 206 | qCDebug(DIALOGPYTHON_LOG) << "Process error:" << error; 207 | process->deleteLater(); 208 | }); 209 | 210 | // 可选:设置进程通道模式以确保输出不被缓冲 211 | process->setProcessChannelMode(QProcess::SeparateChannels); 212 | 213 | // 启动进程 214 | process->start(pythonPath, QStringList() << "-u" << scriptPath << db_name); 215 | } else { 216 | process->deleteLater(); 217 | } 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /src/APLDB.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "APLDB.h" 5 | 6 | APL_LOGGING_CATEGORY(APLDB_LOG, "APLDBLog") 7 | 8 | APLDB::APLDB() 9 | : _Number(0) 10 | { 11 | _name.clear(); 12 | _values.clear(); 13 | _maintable_names.clear(); 14 | _maintable_formats.clear(); 15 | _maintable_ids.clear(); 16 | _maintable_item.clear(); 17 | } 18 | 19 | APLDB::~APLDB() 20 | { 21 | qCDebug(APLDB_LOG) << "APLDB::~APLDB()"; 22 | if(isOpen()){ 23 | close(); 24 | } 25 | } 26 | 27 | void APLDB::createAPLDB(const QString &dbPath) 28 | { 29 | _apldb = QSqlDatabase::addDatabase("QSQLITE"); 30 | _apldb.setDatabaseName(dbPath); 31 | 32 | if(!_apldb.open()){ 33 | qCDebug(APLDB_LOG) << _apldb.lastError(); 34 | qCDebug(APLDB_LOG) << _apldb.drivers(); 35 | }else{ 36 | QSqlQuery query(_apldb); 37 | 38 | // SQLite 性能优化设置 39 | query.exec("PRAGMA journal_mode = WAL"); // 使用WAL模式 40 | query.exec("PRAGMA synchronous = NORMAL"); // 降低同步级别 41 | query.exec("PRAGMA cache_size = 50000"); // 增加缓存大小 42 | query.exec("PRAGMA temp_store = MEMORY"); // 临时表存储在内存 43 | query.exec("PRAGMA mmap_size = 268435456"); // 使用内存映射(256MB) 44 | 45 | query.exec("PRAGMA page_size = 65536"); // 增大页面大小 46 | query.exec("PRAGMA wal_autocheckpoint = 0"); // 禁用自动检查点 47 | query.exec("PRAGMA optimize"); // 启用查询优化器 48 | query.exec("PRAGMA threads = 4"); // 启用多线程(Qt 6.2+) 49 | query.exec("PRAGMA locking_mode = EXCLUSIVE"); // 独占锁模式 50 | 51 | // 创建主表 52 | bool success = query.exec("CREATE TABLE maintable(LineNo INTEGER PRIMARY KEY AUTOINCREMENT, id INT8, len INT8, name VARCHAR UNIQUE, format VARCHAR, labels VARCHAR, units VARCHAR, multipliers VARCHAR)"); 53 | if(success){ 54 | qCDebug(APLDB_LOG) << "create maintable success"; 55 | }else{ 56 | qCDebug(APLDB_LOG) << "create maintable failed"; 57 | } 58 | } 59 | } 60 | 61 | //true: id already exist 62 | bool APLDB::checkMainTable(quint8 id) 63 | { 64 | bool ret = false; 65 | if(_maintable_ids.contains(QString("%1").arg(id))){ 66 | ret = true; 67 | } 68 | 69 | return ret; 70 | } 71 | 72 | void APLDB::addToMainTable(quint8 type, 73 | quint8 len, 74 | QString name, 75 | QString format, 76 | QString labels) 77 | { 78 | if (_maintable_names.contains(name)) { 79 | return; 80 | } 81 | 82 | _maintable_item.append(QString("INSERT INTO maintable(id, len, name, format, labels) VALUES(%1,%2,\"%3\",\"%4\",\"%5\")") 83 | .arg(type) 84 | .arg(len) 85 | .arg(name) 86 | .arg(format) 87 | .arg(labels)); 88 | 89 | _maintable_ids.append(QString("%1").arg(type)); 90 | _maintable_names.append(name); 91 | _maintable_formats.append(format); 92 | 93 | if(!_createSubTable(name, format, labels)){ 94 | qCDebug(APLDB_LOG) << name << "Sub table create fail"; 95 | } 96 | } 97 | 98 | void APLDB::addToSubTable(QString name, QString values) 99 | { 100 | QSqlQuery query_insert; 101 | 102 | values = QString("%1,%2").arg(++_Number).arg(values); 103 | query_insert.prepare(QString("INSERT INTO %1 VALUES(%2)").arg(name, values)); 104 | if(!query_insert.exec()){ 105 | QSqlError queryErr = query_insert.lastError(); 106 | qCDebug(APLDB_LOG)<<"addToSubTable"< values) 111 | { 112 | _name.append(name); 113 | _values.append(values); 114 | } 115 | 116 | void APLDB::buf2DB() 117 | { 118 | QSqlQuery query_insert(_apldb); 119 | 120 | // 处理主表 121 | _apldb.transaction(); 122 | for(const QString& item : _maintable_item) { 123 | if(!query_insert.exec(item)) { 124 | qCDebug(APLDB_LOG) << "MainTable insert failed:" << query_insert.lastError().text(); 125 | _apldb.rollback(); 126 | return; 127 | } 128 | } 129 | _apldb.commit(); 130 | 131 | // 批量处理子表数据 132 | QMap>> grouped_data; 133 | for(qsizetype i = 0; i < _name.length(); ++i) { 134 | grouped_data[_name[i]].append(_values[i]); 135 | } 136 | 137 | for (auto it = grouped_data.constBegin(); it != grouped_data.constEnd(); ++it) { 138 | const QString &tableName = it.key(); 139 | const QList> &rows = it.value(); 140 | 141 | if (rows.isEmpty()) continue; 142 | 143 | // 使用批量插入 144 | insertBatchData(tableName, rows); 145 | } 146 | 147 | // 清理 148 | _maintable_item.clear(); 149 | _maintable_names.clear(); 150 | _maintable_formats.clear(); 151 | _maintable_ids.clear(); 152 | _name.clear(); 153 | _values.clear(); 154 | 155 | // 清理后进行数据库优化 156 | QSqlQuery query(_apldb); 157 | query.exec("PRAGMA optimize"); 158 | query.exec("PRAGMA wal_checkpoint(TRUNCATE)"); // 清理WAL文件 159 | } 160 | 161 | void APLDB::insertBatchData(const QString& tableName, const QList>& rows) 162 | { 163 | if (rows.isEmpty()) return; 164 | 165 | const int batchSize = 5000; // 每批处理5000条记录 166 | const int columnCount = rows.first().size(); 167 | 168 | // 构建批量插入SQL 169 | QString placeholders = "?"; 170 | for (int j = 0; j < columnCount; ++j) { 171 | placeholders += ", ?"; 172 | } 173 | 174 | QString sql = QString("INSERT INTO \"%1\" VALUES(%2)").arg(tableName, placeholders); 175 | QSqlQuery query(_apldb); 176 | if (!query.prepare(sql)) { 177 | qCDebug(APLDB_LOG) << "Prepare failed:" << query.lastError().text(); 178 | return; 179 | } 180 | 181 | qint64 lineCounter = 0; 182 | 183 | _apldb.transaction(); 184 | 185 | for (int i = 0; i < rows.size(); ++i) { 186 | const QVector& row = rows[i]; 187 | 188 | query.bindValue(0, ++lineCounter); 189 | for (int j = 0; j < row.size(); ++j) { 190 | query.bindValue(j + 1, row[j]); 191 | } 192 | 193 | if (!query.exec()) { 194 | qCDebug(APLDB_LOG) << "Insert failed:" << query.lastError().text(); 195 | _apldb.rollback(); 196 | return; 197 | } 198 | 199 | // 每5000条提交一次 200 | if (i % batchSize == 0 && i > 0) { 201 | if (!_apldb.commit()) { 202 | qCDebug(APLDB_LOG) << "Commit failed:" << _apldb.lastError().text(); 203 | _apldb.rollback(); 204 | return; 205 | } 206 | _apldb.transaction(); 207 | } 208 | } 209 | 210 | // 提交剩余数据 211 | if (!_apldb.commit()) { 212 | qCDebug(APLDB_LOG) << "Batch commit failed:" << _apldb.lastError().text(); 213 | _apldb.rollback(); 214 | return; 215 | } 216 | } 217 | 218 | bool APLDB::_createSubTable(QString &name, QString &format, QString &field) const 219 | { 220 | QSqlQuery query_create; 221 | QString table_field = ""; 222 | bool success = false; 223 | 224 | _createTableField(name, format, field, table_field); 225 | table_field = QString("%1,%2").arg("LineNo INTEGER PRIMARY KEY", table_field); 226 | success = query_create.exec(QString("CREATE TABLE IF NOT EXISTS \"%1\"(%2)").arg(name, table_field)); 227 | 228 | if(!success){ 229 | qCDebug(APLDB_LOG) << QString("CREATE TABLE IF NOT EXISTS \"%1\"(%2) FAILED: ").arg(name, table_field) << query_create.lastError().text(); 230 | } 231 | 232 | return success; 233 | } 234 | 235 | void APLDB::_createTableField(const QString &name, QString &format, QString &field, QString &table_field) const 236 | { 237 | QByteArray formatArray = format.toLatin1(); 238 | QStringList fieldList = field.split(','); 239 | 240 | if(formatArray.size() != fieldList.count()){ 241 | qCDebug(APLDB_LOG) << name << "Format and labels don't match"; 242 | return; 243 | } 244 | 245 | for(qint8 i = 0; i < formatArray.size(); i++){ 246 | // 使用双引号转义字段名,处理关键字冲突 247 | QString fieldName = QString("\"%1\"").arg(_sanitizeFieldName(name, fieldList[i])); 248 | 249 | QString dataType; 250 | switch(formatArray[i]){ 251 | case 'a': case 'b': case 'B': case 'h': case 'H': 252 | case 'i': case 'I': case 'L': case 'M': case 'q': case 'Q': 253 | dataType = "INTEGER"; 254 | break; 255 | case 'f': case 'd': case 'c': case 'C': case 'e': case 'E': 256 | dataType = "REAL"; // 使用REAL而不是DOUBLE 257 | break; 258 | case 'n': case 'N': case 'Z': 259 | dataType = "TEXT"; // 使用TEXT而不是VARCHAR 260 | break; 261 | default: 262 | dataType = "TEXT"; 263 | break; 264 | } 265 | 266 | table_field += QString("%1 %2").arg(fieldName, dataType); 267 | if (i < formatArray.size() - 1) { 268 | table_field += ", "; 269 | } 270 | } 271 | } 272 | 273 | QString APLDB::_sanitizeFieldName(const QString &name, const QString& fieldName) const 274 | { 275 | // SQLite关键字列表(部分) 276 | static const QStringList sqliteKeywords = { 277 | "ABORT", "ACTION", "ADD", "AFTER", "ALL", "ALTER", "ANALYZE", "AND", "AS", "ASC", 278 | "ATTACH", "AUTOINCREMENT", "BEFORE", "BEGIN", "BETWEEN", "BY", "CASCADE", "CASE", 279 | "CAST", "CHECK", "COLLATE", "COLUMN", "COMMIT", "CONFLICT", "CONSTRAINT", "CREATE", 280 | "CROSS", "CURRENT_DATE", "CURRENT_TIME", "CURRENT_TIMESTAMP", "DATABASE", "DEFAULT", 281 | "DEFERRABLE", "DEFERRED", "DELETE", "DESC", "DETACH", "DISTINCT", "DROP", "EACH", 282 | "ELSE", "END", "ESCAPE", "EXCEPT", "EXCLUSIVE", "EXISTS", "EXPLAIN", "FAIL", "FOR", 283 | "FOREIGN", "FROM", "FULL", "GLOB", "GROUP", "HAVING", "IF", "IGNORE", "IMMEDIATE", 284 | "IN", "INDEX", "INDEXED", "INITIALLY", "INNER", "INSERT", "INSTEAD", "INTERSECT", 285 | "INTO", "IS", "ISNULL", "JOIN", "KEY", "LEFT", "LIKE", "LIMIT", "MATCH", "NATURAL", 286 | "NO", "NOT", "NOTNULL", "NULL", "OF", "OFFSET", "ON", "OR", "ORDER", "OUTER", "PLAN", 287 | "PRAGMA", "PRIMARY", "QUERY", "RAISE", "RECURSIVE", "REFERENCES", "REGEXP", "REINDEX", 288 | "RELEASE", "RENAME", "REPLACE", "RESTRICT", "RIGHT", "ROLLBACK", "ROW", "SAVEPOINT", 289 | "SELECT", "SET", "TABLE", "TEMP", "TEMPORARY", "THEN", "TO", "TRANSACTION", "TRIGGER", 290 | "UNION", "UNIQUE", "UPDATE", "USING", "VACUUM", "VALUES", "VIEW", "VIRTUAL", "WHEN", 291 | "WHERE", "WITH", "WITHOUT" 292 | }; 293 | 294 | QString sanitized = fieldName.trimmed(); 295 | 296 | // 检查是否为关键字(不区分大小写) 297 | if (sqliteKeywords.contains(sanitized.toUpper())) { 298 | qCDebug(APLDB_LOG) << name << "sqliteKeywords: " << sanitized; 299 | } 300 | 301 | // 处理特殊字符 302 | sanitized.replace(' ', '_'); 303 | sanitized.replace('-', '_'); 304 | sanitized.replace('.', '_'); 305 | sanitized.replace('/', '_'); 306 | sanitized.replace('\\', '_'); 307 | 308 | // 确保不以数字开头 309 | if (!sanitized.isEmpty() && sanitized[0].isDigit()) { 310 | qCDebug(APLDB_LOG) << name << "Digit start: " << sanitized; 311 | } 312 | 313 | return sanitized; 314 | } 315 | 316 | void APLDB::getFormat(quint8 &id, QString &name, QString &format) 317 | { 318 | int idx = _maintable_ids.indexOf(QString("%1").arg(id)); 319 | if (idx != -1) { 320 | name = _maintable_names[idx]; 321 | format = _maintable_formats[idx]; 322 | } 323 | } 324 | 325 | void APLDB::closeConnection() 326 | { 327 | QString connection; 328 | connection = _apldb.connectionName(); 329 | _apldb.close(); 330 | _apldb = QSqlDatabase(); 331 | QSqlDatabase::removeDatabase(connection); 332 | } 333 | 334 | QString APLDB::getGroupName(QSqlDatabase &db, int i) 335 | { 336 | QSqlQuery query(db); 337 | 338 | query.exec(QString("SELECT name FROM maintable")); 339 | for(int n = 0; n < i; n++) 340 | query.next(); 341 | 342 | return query.value(0).toString(); 343 | } 344 | 345 | QString APLDB::getTableName(QSqlDatabase &db, int i) 346 | { 347 | QSqlQuery query(db); 348 | 349 | query.exec(QString("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name")); 350 | for(int n = 0; n < i; n++) 351 | query.next(); 352 | 353 | return query.value(0).toString(); 354 | } 355 | 356 | int APLDB::getGroupCount(QSqlDatabase &db) 357 | { 358 | QSqlQuery query(db); 359 | 360 | query.exec(QString("SELECT COUNT(name) FROM maintable")); 361 | query.next(); 362 | 363 | return query.value(0).toInt(); 364 | } 365 | 366 | int APLDB::getTableNum(QSqlDatabase &db) 367 | { 368 | QSqlQuery query(db); 369 | 370 | query.exec(QString("SELECT COUNT(*) FROM sqlite_master WHERE type='table'")); 371 | query.next(); 372 | 373 | return query.value(0).toInt(); 374 | } 375 | 376 | int APLDB::getItemCount(QSqlDatabase &db, QString table) 377 | { 378 | QSqlQuery query(db); 379 | 380 | query.prepare(QString("PRAGMA table_info(\"%1\")").arg(table)); 381 | if(query.exec()){ 382 | int count = 0; 383 | while(query.next()) { 384 | count++; 385 | } 386 | return count; 387 | } 388 | 389 | return 0; 390 | } 391 | 392 | QString APLDB::getItemName(QSqlDatabase &db, QString table, int i) 393 | { 394 | QSqlQuery query(db); 395 | 396 | query.prepare(QString("PRAGMA table_info(\"%1\")").arg(table)); 397 | if(query.exec()){ 398 | for(int n = 0; n <= i; n++) { 399 | if (!query.next()) return ""; 400 | } 401 | return query.value(1).toString(); 402 | } 403 | 404 | return ""; 405 | } 406 | 407 | QString APLDB::getDiff(QSqlDatabase &db, QString table, QString field) 408 | { 409 | QSqlQuery query(db); 410 | QByteArray ret; 411 | 412 | query.prepare(QString("SELECT DISTINCT \"%1\" FROM \"%2\"").arg(field,table)); 413 | if(query.exec()){ 414 | while(query.next()){ 415 | ret.append(query.value(0).toString().toUtf8()); 416 | ret.append(","); 417 | } 418 | 419 | QString ret_str = QString(ret); 420 | if (!ret_str.isEmpty()) { 421 | ret_str.chop(1); 422 | } 423 | return ret_str; 424 | } 425 | 426 | return ""; 427 | } 428 | 429 | int APLDB::getLen(QSqlDatabase &db, QString table, QString field) 430 | { 431 | QSqlQuery query(db); 432 | 433 | query.prepare(QString("SELECT COUNT(\"%1\") FROM \"%2\"").arg(field).arg(table)); 434 | 435 | if(!query.exec()){ 436 | qCDebug(APLDB_LOG)<<"getLen(QSqlDatabase &db, QString table, QString field)"; 437 | QSqlError queryErr = query.lastError(); 438 | qCDebug(APLDB_LOG)<& data, double offset, double scale) 448 | { 449 | QSqlQuery query(db); 450 | 451 | query.prepare(QString("SELECT \"%1\" FROM \"%2\"").arg(field).arg(table)); 452 | 453 | if(!query.exec()){ 454 | QSqlError queryErr = query.lastError(); 455 | qCDebug(APLDB_LOG)< 2 | #include 3 | #include 4 | #include "APLRead.h" 5 | #include "mainwindow.h" 6 | #include "APLDataCache.h" 7 | #include "LogStructure.h" 8 | 9 | APL_LOGGING_CATEGORY(APLREAD_LOG, "APLReadLog") 10 | 11 | #define NAME_LEN 4 12 | #define FORMAT_LEN 16 13 | #define LABELS_LEN 64 14 | 15 | APLRead* APLRead::_instance; 16 | 17 | static LFMT FMT[256] = {}; 18 | 19 | // 优化1:添加格式字符长度预计算表 - 兼容旧版本C++编译器 20 | static int FORMAT_CHAR_LENGTHS[256]; 21 | 22 | // 初始化查找表的函数 23 | static void initFormatCharLengths() { 24 | static bool initialized = false; 25 | if (initialized) return; 26 | 27 | // 初始化所有值为0 28 | memset(FORMAT_CHAR_LENGTHS, 0, sizeof(FORMAT_CHAR_LENGTHS)); 29 | 30 | // 设置已知格式字符的长度 31 | FORMAT_CHAR_LENGTHS['a'] = 64; 32 | FORMAT_CHAR_LENGTHS['b'] = 1; 33 | FORMAT_CHAR_LENGTHS['B'] = 1; 34 | FORMAT_CHAR_LENGTHS['M'] = 1; 35 | FORMAT_CHAR_LENGTHS['h'] = 2; 36 | FORMAT_CHAR_LENGTHS['H'] = 2; 37 | FORMAT_CHAR_LENGTHS['c'] = 2; 38 | FORMAT_CHAR_LENGTHS['C'] = 2; 39 | FORMAT_CHAR_LENGTHS['i'] = 4; 40 | FORMAT_CHAR_LENGTHS['I'] = 4; 41 | FORMAT_CHAR_LENGTHS['f'] = 4; 42 | FORMAT_CHAR_LENGTHS['e'] = 4; 43 | FORMAT_CHAR_LENGTHS['E'] = 4; 44 | FORMAT_CHAR_LENGTHS['L'] = 4; 45 | FORMAT_CHAR_LENGTHS['d'] = 8; 46 | FORMAT_CHAR_LENGTHS['q'] = 8; 47 | FORMAT_CHAR_LENGTHS['Q'] = 8; 48 | FORMAT_CHAR_LENGTHS['n'] = 4; 49 | FORMAT_CHAR_LENGTHS['N'] = 16; 50 | FORMAT_CHAR_LENGTHS['Z'] = 64; 51 | 52 | initialized = true; 53 | } 54 | 55 | // 预编译正则表达式 - 但保持原有的检查逻辑不变 56 | static QRegularExpression NAME_REGEX("^[A-Z0-9]{1,4}$"); 57 | static QRegularExpression FORMAT_REGEX("^[A-Za-z]{1,16}$"); 58 | static QRegularExpression LABELS_REGEX("^[A-Za-z0-9_,]{1,64}$"); 59 | 60 | APLRead::APLRead() 61 | { 62 | _instance = this; 63 | _resetDataBase(); 64 | _worker = new APLReadWorker(); 65 | export_worker = new APLExportWorker(); 66 | _workThread = new QThread(this); 67 | _worker->moveToThread(_workThread); 68 | _exportThread = new QThread(this); 69 | export_worker->moveToThread(_exportThread); 70 | 71 | connect(this, &APLRead::startRunning, _worker, &APLReadWorker::decodeLogFile); 72 | connect(_workThread, &QThread::finished, _worker, &QObject::deleteLater); 73 | connect(_exportThread, &QThread::finished, export_worker, &QObject::deleteLater); 74 | connect(_worker, &APLReadWorker::send_process, this, &APLRead::calc_process); 75 | connect(_worker, &APLReadWorker::fileOpened, this, &APLRead::getFileOpened); 76 | connect(this, &APLRead::startExport, export_worker, &APLExportWorker::exportCSV); 77 | 78 | _workThread->start(); 79 | _exportThread->start(); 80 | } 81 | 82 | APLRead::~APLRead() 83 | { 84 | qCDebug(APLREAD_LOG)<< "APLRead::~APLRead()"; 85 | 86 | _workThread->quit(); 87 | _workThread->wait(); 88 | 89 | delete _workThread; 90 | 91 | _exportThread->quit(); 92 | _exportThread->wait(); 93 | 94 | delete _exportThread; 95 | } 96 | 97 | void APLRead::_resetDataBase() 98 | { 99 | for(int i=0; i<256; i++) 100 | _resetFMT(i); 101 | } 102 | 103 | void APLRead::_resetFMT(int i) 104 | { 105 | FMT[i].id = 0; 106 | FMT[i].name = ""; 107 | FMT[i].format = ""; 108 | FMT[i].valid = true; 109 | } 110 | 111 | void APLRead::getFileDir(const QString &file_dir) 112 | { 113 | _file_name = QFileInfo(file_dir).fileName(); 114 | _file_path = QFileInfo(file_dir).absolutePath(); 115 | MainWindow::getMainWindow()->setWindowTitle(QString("ArduPilotLog ").append(file_dir)); 116 | MainWindow::getMainWindow()->ui().progressBar->setValue(0); 117 | MainWindow::getMainWindow()->ui().progressBar->setVisible(0); 118 | if(MainWindow::getMainWindow()->db().isOpen()){ 119 | MainWindow::getMainWindow()->db().close(); 120 | } 121 | _resetDataBase(); 122 | APLDataCache::get_singleton()->reset(); 123 | _file_dir.clear(); 124 | getDatastream(file_dir); 125 | } 126 | 127 | void APLRead::getFileOpened() 128 | { 129 | MainWindow::getMainWindow()->ui().progressBar->setVisible(0); 130 | emit fileOpened(); 131 | } 132 | 133 | void APLRead::getDatastream(const QString &file_dir) 134 | { 135 | if(!file_dir.isEmpty()){ 136 | MainWindow::getMainWindow()->ui().progressBar->setVisible(1); 137 | _file_dir = file_dir; 138 | emit startRunning(file_dir); 139 | } 140 | } 141 | 142 | void APLRead::calc_process(qint64 pos, qint64 size) 143 | { 144 | int process = ((float)pos/size)*10000; 145 | MainWindow::getMainWindow()->ui().progressBar->setValue(process); 146 | } 147 | 148 | void APLRead::exportCSV() 149 | { 150 | emit startExport(_file_dir); 151 | } 152 | 153 | APLExportWorker::APLExportWorker(QObject *parent) 154 | : QObject(parent) 155 | {} 156 | 157 | void APLExportWorker::exportCSV(const QString &file_dir) 158 | { 159 | if (!file_dir.isEmpty()) { 160 | QFileInfo fileInfo(file_dir); 161 | APLDataCache::get_singleton()->export_csv = true; 162 | APLDataCache::get_singleton()->exportToFile(fileInfo.absolutePath() + "/" + fileInfo.baseName() + "_csv"); 163 | emit saveSuccess(); 164 | } 165 | } 166 | 167 | void APLReadWorker::_decode(const uchar* p_data, qint64 data_size) 168 | { 169 | 170 | const uchar* ptr = p_data; 171 | const uchar* end = p_data + data_size; 172 | int progress_counter = 0; 173 | 174 | // 预计算头部字节组合,避免重复比较 175 | const uint16_t valid_header = (HEAD_BYTE1 << 8) | HEAD_BYTE2; 176 | 177 | 178 | while (ptr < end - LOG_PACKET_HEADER_LEN) 179 | { 180 | // 保持原有的进度更新逻辑 181 | if (++progress_counter > 102400) { 182 | emit send_process(ptr - p_data, _fileSize); 183 | progress_counter = 0; 184 | } 185 | 186 | // 优化:一次读取两个字节进行头部匹配 187 | uint16_t header = (ptr[0] << 8) | ptr[1]; 188 | if (header != valid_header) { 189 | ptr++; 190 | continue; 191 | } 192 | 193 | quint8 msg_type = ptr[2]; 194 | const uchar* payload_ptr = ptr + LOG_PACKET_HEADER_LEN; 195 | 196 | if (msg_type == LOG_FORMAT_MSG) 197 | { 198 | if (payload_ptr + 2 > end) { 199 | ptr++; 200 | continue; 201 | } 202 | quint8 msg_len = *(payload_ptr + 1); 203 | if (payload_ptr + msg_len > end) { 204 | ptr++; 205 | continue; 206 | } 207 | 208 | // 保持原有的缓冲区分配方式 209 | char name[NAME_LEN+1] = {0}; 210 | char format[FORMAT_LEN+1] = {0}; 211 | char labels[LABELS_LEN+1] = {0}; 212 | 213 | const uchar* p = payload_ptr; 214 | quint8 type = *p++; 215 | p++; // skip len 216 | 217 | // 保持原有的内存拷贝方式 218 | memcpy(name, p, NAME_LEN); p += NAME_LEN; 219 | memcpy(format, p, FORMAT_LEN); p += FORMAT_LEN; 220 | memcpy(labels, p, LABELS_LEN); p += LABELS_LEN; 221 | 222 | 223 | // 保持原有的QString构造方式 224 | QString log_name = QString(name); 225 | QString log_format = QString(format); 226 | QString log_labels = QString(labels); 227 | 228 | if (_checkMessage(log_name, log_format, log_labels)) { 229 | _dataCache->addFormat(type, 230 | log_name, 231 | log_format, 232 | log_labels); 233 | } 234 | ptr = payload_ptr + msg_len; 235 | } 236 | else 237 | { 238 | quint8 id = msg_type; 239 | 240 | // 优化2:减少重复的isEmpty检查 241 | LFMT& fmt = FMT[id]; 242 | if (fmt.name.isEmpty() && fmt.format.isEmpty() && fmt.valid) { 243 | if (APLDataCache::get_singleton()->checkMainTable(id)) 244 | { 245 | APLDataCache::get_singleton()->getFormat(id, fmt.name, fmt.format); 246 | } else { 247 | fmt.valid = false; 248 | } 249 | } 250 | 251 | if (fmt.valid && !fmt.format.isEmpty()) { 252 | int msg_len = _getMessageLength(fmt.format); 253 | if (msg_len < 0 || payload_ptr + msg_len > end) { 254 | ptr++; 255 | continue; 256 | } 257 | 258 | // 保持原有的数据添加方式 259 | _dataCache->addData(fmt.name, fmt.name+QString::number(static_cast(*(payload_ptr + 8))), payload_ptr, static_cast(*(payload_ptr + 8)), msg_len); 260 | ptr = payload_ptr + msg_len; 261 | 262 | if (_dataCache->trim_complete) { 263 | _dataCache->trim_complete = false; 264 | ptr = end; 265 | } 266 | } else { 267 | ptr++; 268 | } 269 | } 270 | } 271 | emit send_process(_fileSize, _fileSize); 272 | } 273 | 274 | // 优化消息长度计算,但保持与原有逻辑完全一致 275 | int APLReadWorker::_getMessageLength(const QString &format) const 276 | { 277 | // 先检查缓存 278 | auto it = _formatLengthCache.find(format); 279 | if (it != _formatLengthCache.end()) { 280 | return it.value(); 281 | } 282 | 283 | int length = 0; 284 | QByteArray formatArray = format.toLatin1(); 285 | 286 | for(char c : formatArray) { 287 | // 使用查找表优化,但保持原有的switch逻辑作为后备 288 | int char_length = FORMAT_CHAR_LENGTHS[static_cast(c)]; 289 | if (char_length > 0) { 290 | length += char_length; 291 | } else { 292 | // 如果查找表中没有,回退到原有的switch逻辑 293 | switch(c) { 294 | case 'a': length += 64; break; 295 | case 'b': 296 | case 'B': 297 | case 'M': length += 1; break; 298 | case 'h': 299 | case 'H': 300 | case 'c': 301 | case 'C': length += 2; break; 302 | case 'i': 303 | case 'I': 304 | case 'f': 305 | case 'e': 306 | case 'E': 307 | case 'L': length += 4; break; 308 | case 'd': 309 | case 'q': 310 | case 'Q': length += 8; break; 311 | case 'n': length += 4; break; 312 | case 'N': length += 16; break; 313 | case 'Z': length += 64; break; 314 | default: 315 | qCWarning(APLREAD_LOG) << "Unknown format character in length calculation:" << c; 316 | return -1; 317 | } 318 | } 319 | } 320 | 321 | // 缓存结果 322 | _formatLengthCache[format] = length; 323 | 324 | return length; 325 | } 326 | 327 | void APLReadWorker::_decodeData(QString &format, const uchar *ptr, QString &value) const 328 | { 329 | QTextStream st(&value); 330 | QByteArray formatArray = format.toLatin1(); 331 | 332 | for(qint8 i = 0; i< formatArray.size(); i++){ 333 | switch(formatArray[i]){ 334 | case 'a': { 335 | qint16 v16_32[32]; 336 | memcpy(v16_32, ptr, sizeof(v16_32)); 337 | ptr += sizeof(v16_32); 338 | for(int j=0; j<32; j++) 339 | st << v16_32[j] << (j == 31 ? "" : " "); 340 | st << ","; 341 | break; 342 | } 343 | case 'b': { 344 | qint8 v8; 345 | memcpy(&v8, ptr, 1); ptr += 1; 346 | st << v8 << ","; 347 | break; 348 | } 349 | case 'B': { 350 | quint8 vu8; 351 | memcpy(&vu8, ptr, 1); ptr += 1; 352 | st << vu8 << ","; 353 | break; 354 | } 355 | case 'h': { 356 | qint16 v16; 357 | memcpy(&v16, ptr, 2); ptr += 2; 358 | st << v16 << ","; 359 | break; 360 | } 361 | case 'H': { 362 | quint16 vu16; 363 | memcpy(&vu16, ptr, 2); ptr += 2; 364 | st << vu16 << ","; 365 | break; 366 | } 367 | case 'i': { 368 | qint32 v32; 369 | memcpy(&v32, ptr, 4); ptr += 4; 370 | st << v32 << ","; 371 | break; 372 | } 373 | case 'I': { 374 | quint32 vu32; 375 | memcpy(&vu32, ptr, 4); ptr += 4; 376 | st << vu32 << ","; 377 | break; 378 | } 379 | case 'f': { 380 | float vf; 381 | memcpy(&vf, ptr, 4); ptr += 4; 382 | if(qIsNaN(vf) || qIsInf(vf)) vf = 0.0f; 383 | st << vf << ","; 384 | break; 385 | } 386 | case 'd': { 387 | double vd; 388 | memcpy(&vd, ptr, 8); ptr += 8; 389 | st << vd << ","; 390 | break; 391 | } 392 | case 'n': { 393 | char vc_4[4+1] = {0}; 394 | memcpy(vc_4, ptr, 4); ptr += 4; 395 | st << "\"" << vc_4 << "\","; 396 | break; 397 | } 398 | case 'N': { 399 | char vc_16[16+1] = {0}; 400 | memcpy(vc_16, ptr, 16); ptr += 16; 401 | st << "\"" << vc_16 << "\","; 402 | break; 403 | } 404 | case 'Z': { 405 | char vc_64[64+1] = {0}; 406 | memcpy(vc_64, ptr, 64); ptr += 64; 407 | st << "\"" << vc_64 << "\","; 408 | break; 409 | } 410 | case 'c': { 411 | qint16 v16x100; 412 | memcpy(&v16x100, ptr, 2); ptr += 2; 413 | st << (double)(v16x100/100.0f) << ","; 414 | break; 415 | } 416 | case 'C': { 417 | quint16 vu16x100; 418 | memcpy(&vu16x100, ptr, 2); ptr += 2; 419 | st << (double)(vu16x100/100.0f) << ","; 420 | break; 421 | } 422 | case 'e': { 423 | qint32 v32x100; 424 | memcpy(&v32x100, ptr, 4); ptr += 4; 425 | st << (double)(v32x100/100.0f) << ","; 426 | break; 427 | } 428 | case 'E': { 429 | quint32 vu32x100; 430 | memcpy(&vu32x100, ptr, 4); ptr += 4; 431 | st << (double)(vu32x100/100.0f) << ","; 432 | break; 433 | } 434 | case 'L': { 435 | qint32 v32l; 436 | memcpy(&v32l, ptr, 4); ptr += 4; 437 | st << v32l << ","; 438 | break; 439 | } 440 | case 'M': { 441 | quint8 vu8m; 442 | memcpy(&vu8m, ptr, 1); ptr += 1; 443 | st << vu8m << ","; 444 | break; 445 | } 446 | case 'q': { 447 | qint64 v64; 448 | memcpy(&v64, ptr, 8); ptr += 8; 449 | st << v64 << ","; 450 | break; 451 | } 452 | case 'Q': { 453 | quint64 vu64; 454 | memcpy(&vu64, ptr, 8); ptr += 8; 455 | st << vu64 << ","; 456 | break; 457 | } 458 | } 459 | } 460 | if (!value.isEmpty()) { 461 | value.chop(1); 462 | } 463 | } 464 | 465 | bool APLReadWorker::_checkMessage(QString &name, QString &format, QString &labels) const 466 | { 467 | bool res = false; 468 | bool res1, res2, res3; 469 | res1 = _checkName(name); 470 | res2 = _checkFormat(format); 471 | res3 = _checkLabels(labels); 472 | res = res1 && res2 && res3; 473 | if(!res){ 474 | qCDebug(APLREAD_LOG) << name << format << labels << res1 << res2 << res3; 475 | } 476 | return res; 477 | } 478 | 479 | bool APLReadWorker::_checkName(QString &name) const 480 | { 481 | // 优化3:避免创建validator对象 482 | static QRegularExpressionValidator validator(NAME_REGEX, nullptr); 483 | 484 | int pos = 0; 485 | if(QValidator::Acceptable != validator.validate(name, pos)){ 486 | return false; 487 | } 488 | 489 | return true; 490 | } 491 | 492 | bool APLReadWorker::_checkFormat(QString &format) const 493 | { 494 | // 优化3:避免创建validator对象 495 | static QRegularExpressionValidator validator(FORMAT_REGEX, nullptr); 496 | 497 | int pos = 0; 498 | if(QValidator::Acceptable != validator.validate(format, pos)){ 499 | return false; 500 | } 501 | 502 | return true; 503 | } 504 | 505 | bool APLReadWorker::_checkLabels(QString &labels) const 506 | { 507 | // 优化3:避免创建validator对象 508 | static QRegularExpressionValidator validator(LABELS_REGEX, nullptr); 509 | 510 | int pos = 0; 511 | if(QValidator::Acceptable != validator.validate(labels, pos)){ 512 | return false; 513 | } 514 | 515 | return true; 516 | } 517 | 518 | APLReadWorker::APLReadWorker(QObject *parent) 519 | : QObject(parent) 520 | , _dataCache(new APLDataCache) 521 | , _fileSize(0) 522 | { 523 | 524 | // 初始化格式字符长度查找表 525 | initFormatCharLengths(); 526 | } 527 | 528 | APLReadWorker::~APLReadWorker() 529 | { 530 | delete _dataCache; 531 | } 532 | 533 | void APLReadWorker::decodeLogFile(const QString &file_dir) 534 | { 535 | QFile file; 536 | 537 | file.setFileName(file_dir); 538 | if(!file.open(QIODevice::ReadOnly)) 539 | { 540 | qCDebug(APLREAD_LOG)<< "can not open file!"; 541 | return; 542 | } 543 | 544 | _fileSize = file.size(); 545 | uchar* fpr = file.map(0, _fileSize); 546 | if(fpr){ 547 | _decode(fpr, _fileSize); 548 | 549 | QFileInfo fileInfo(file_dir); 550 | 551 | emit fileOpened(); 552 | qCDebug(APLREAD_LOG) << "All data have been read"; 553 | 554 | file.unmap(fpr); 555 | } 556 | file.close(); 557 | 558 | QFileInfo fileInfo(file_dir); 559 | _dataCache->exportToFile(fileInfo.absolutePath() + "/" + fileInfo.baseName() + "_csv"); 560 | } 561 | -------------------------------------------------------------------------------- /src/DataAnalyze.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2 | import QtQuick.Controls 3 | import QtQuick.Layouts 4 | import QtQuick.Window 5 | 6 | import ArduPilotLog.Controllers 1.0 7 | 8 | Rectangle { 9 | id: dataAnalyze 10 | 11 | width: 650 12 | height: 500 13 | 14 | DataAnalyzeController { 15 | id: controller 16 | } 17 | 18 | ColumnLayout { 19 | anchors.fill: parent 20 | anchors.margins: 10 21 | 22 | GridLayout { 23 | columns: 8 24 | columnSpacing: 5 25 | rowSpacing: 5 26 | 27 | Layout.fillWidth: true 28 | Layout.fillHeight: true 29 | 30 | // --- 第 0 行: 表头 --- 31 | Label { text: qsTr("State"); font.bold: true; Layout.alignment: Qt.AlignCenter } 32 | Label { text: qsTr("Table"); font.bold: true; Layout.alignment: Qt.AlignCenter } 33 | Label { text: qsTr("Field"); font.bold: true; Layout.alignment: Qt.AlignCenter } 34 | Label { text: qsTr("Scale"); font.bold: true; Layout.alignment: Qt.AlignCenter } 35 | Label { text: qsTr("X+"); font.bold: true; Layout.alignment: Qt.AlignCenter } 36 | Label { text: qsTr("Y+"); font.bold: true; Layout.alignment: Qt.AlignCenter } 37 | Label { text: qsTr("Line"); font.bold: true; Layout.alignment: Qt.AlignCenter } 38 | Label { text: qsTr("Color"); font.bold: true; Layout.alignment: Qt.AlignCenter } 39 | 40 | // --- 第 1 行: 数据 --- 41 | Button { 42 | Layout.fillWidth: true 43 | text: controller.visible1 ? qsTr("1") : qsTr("hide") 44 | onClicked: controller.setVisible1(!controller.visible1) 45 | } 46 | ComboBox { 47 | id : _comboboxTable1 48 | Layout.fillWidth: true 49 | model: controller.tableList1 50 | onCurrentTextChanged: controller.setFieldList1(currentText) 51 | } 52 | ComboBox { 53 | Layout.fillWidth: true 54 | model: controller.fieldList1 55 | onActivated: function(index) { controller.setField1(model[index]) } 56 | } 57 | TextField { 58 | Layout.fillWidth: true 59 | text: controller.scale1 60 | placeholderText: "1.0" 61 | onAccepted: controller.setScale1(text) 62 | } 63 | TextField { 64 | Layout.fillWidth: true 65 | text: controller.offsetX1 66 | placeholderText: "0.0" 67 | onAccepted: controller.setOffsetX1(text) 68 | } 69 | TextField { 70 | Layout.fillWidth: true 71 | text: controller.offsetY1 72 | placeholderText: "0.0" 73 | onAccepted: controller.setOffsetY1(text) 74 | } 75 | ComboBox { 76 | Layout.fillWidth: true 77 | model: controller.lineList 78 | onCurrentIndexChanged: function() { controller.setLineStyle1(currentIndex) } 79 | } 80 | ComboBox { 81 | id: _comboboxColor1 82 | Layout.fillWidth: true 83 | model: controller.colorList1 84 | onCurrentTextChanged: controller.setLineColor1(currentText) 85 | } 86 | 87 | // --- 第 2 行: 数据 --- 88 | Button { 89 | Layout.fillWidth: true 90 | text: controller.visible2 ? qsTr("2") : qsTr("hide") 91 | onClicked: controller.setVisible2(!controller.visible2) 92 | } 93 | ComboBox { 94 | id: _comboboxTable2 95 | Layout.fillWidth: true 96 | model: controller.tableList2 97 | onCurrentTextChanged: controller.setFieldList2(currentText) 98 | } 99 | ComboBox { 100 | Layout.fillWidth: true 101 | model: controller.fieldList2 102 | onActivated: function(index) { controller.setField2(model[index]) } 103 | } 104 | TextField { Layout.fillWidth: true; text: controller.scale2; onAccepted: controller.setScale2(text) } 105 | TextField { Layout.fillWidth: true; text: controller.offsetX2; onAccepted: controller.setOffsetX2(text) } 106 | TextField { Layout.fillWidth: true; text: controller.offsetY2; onAccepted: controller.setOffsetY2(text) } 107 | ComboBox { 108 | Layout.fillWidth: true 109 | model: controller.lineList 110 | onCurrentIndexChanged: function() { controller.setLineStyle2(currentIndex) } 111 | } 112 | ComboBox { 113 | id: _comboboxColor2 114 | Layout.fillWidth: true 115 | model: controller.colorList2 116 | onCurrentTextChanged: controller.setLineColor2(currentText) 117 | } 118 | 119 | // --- 第 3 行: 数据 --- 120 | Button { 121 | Layout.fillWidth: true 122 | text: controller.visible3 ? qsTr("3") : qsTr("hide") 123 | onClicked: controller.setVisible3(!controller.visible3) 124 | } 125 | ComboBox { 126 | id: _comboboxTable3 127 | Layout.fillWidth: true 128 | model: controller.tableList3 129 | onCurrentTextChanged: controller.setFieldList3(currentText) 130 | } 131 | ComboBox { 132 | Layout.fillWidth: true 133 | model: controller.fieldList3 134 | onActivated: function(index) { controller.setField3(model[index]) } 135 | } 136 | TextField { Layout.fillWidth: true; text: controller.scale3; onAccepted: controller.setScale3(text) } 137 | TextField { Layout.fillWidth: true; text: controller.offsetX3; onAccepted: controller.setOffsetX3(text) } 138 | TextField { Layout.fillWidth: true; text: controller.offsetY3; onAccepted: controller.setOffsetY3(text) } 139 | ComboBox { 140 | Layout.fillWidth: true 141 | model: controller.lineList 142 | onCurrentIndexChanged: function() { controller.setLineStyle3(currentIndex) } 143 | } 144 | ComboBox { 145 | id: _comboboxColor3 146 | Layout.fillWidth: true 147 | model: controller.colorList3 148 | onCurrentTextChanged: controller.setLineColor3(currentText) 149 | } 150 | 151 | // --- 第 4 行: 数据 --- 152 | Button { 153 | Layout.fillWidth: true 154 | text: controller.visible4 ? qsTr("4") : qsTr("hide") 155 | onClicked: controller.setVisible4(!controller.visible4) 156 | } 157 | ComboBox { 158 | id: _comboboxTable4 159 | Layout.fillWidth: true 160 | model: controller.tableList4 161 | onCurrentTextChanged: controller.setFieldList4(currentText) 162 | } 163 | ComboBox { 164 | Layout.fillWidth: true 165 | model: controller.fieldList4 166 | onActivated: function(index) { controller.setField4(model[index]) } 167 | } 168 | TextField { Layout.fillWidth: true; text: controller.scale4; onAccepted: controller.setScale4(text) } 169 | TextField { Layout.fillWidth: true; text: controller.offsetX4; onAccepted: controller.setOffsetX4(text) } 170 | TextField { Layout.fillWidth: true; text: controller.offsetY4; onAccepted: controller.setOffsetY4(text) } 171 | ComboBox { 172 | Layout.fillWidth: true 173 | model: controller.lineList 174 | onCurrentIndexChanged: function() { controller.setLineStyle4(currentIndex) } 175 | } 176 | ComboBox { 177 | id: _comboboxColor4 178 | Layout.fillWidth: true 179 | model: controller.colorList4 180 | onCurrentTextChanged: controller.setLineColor4(currentText) 181 | } 182 | 183 | // --- 第 5 行: 数据 --- 184 | Button { 185 | Layout.fillWidth: true 186 | text: controller.visible5 ? qsTr("5") : qsTr("hide") 187 | onClicked: controller.setVisible5(!controller.visible5) 188 | } 189 | ComboBox { 190 | id: _comboboxTable5 191 | Layout.fillWidth: true 192 | model: controller.tableList5 193 | onCurrentTextChanged: controller.setFieldList5(currentText) 194 | } 195 | ComboBox { 196 | Layout.fillWidth: true 197 | model: controller.fieldList5 198 | onActivated: function(index) { controller.setField5(model[index]) } 199 | } 200 | TextField { Layout.fillWidth: true; text: controller.scale5; onAccepted: controller.setScale5(text) } 201 | TextField { Layout.fillWidth: true; text: controller.offsetX5; onAccepted: controller.setOffsetX5(text) } 202 | TextField { Layout.fillWidth: true; text: controller.offsetY5; onAccepted: controller.setOffsetY5(text) } 203 | ComboBox { 204 | Layout.fillWidth: true 205 | model: controller.lineList 206 | onCurrentIndexChanged: function() { controller.setLineStyle5(currentIndex) } 207 | } 208 | ComboBox { 209 | id: _comboboxColor5 210 | Layout.fillWidth: true 211 | model: controller.colorList5 212 | onCurrentTextChanged: controller.setLineColor5(currentText) 213 | } 214 | 215 | // --- 第 6 行: 数据 --- 216 | Button { 217 | Layout.fillWidth: true 218 | text: controller.visible6 ? qsTr("6") : qsTr("hide") 219 | onClicked: controller.setVisible6(!controller.visible6) 220 | } 221 | ComboBox { 222 | id: _comboboxTable6 223 | Layout.fillWidth: true 224 | model: controller.tableList6 225 | onCurrentTextChanged: controller.setFieldList6(currentText) 226 | } 227 | ComboBox { 228 | Layout.fillWidth: true 229 | model: controller.fieldList6 230 | onActivated: function(index) { controller.setField6(model[index]) } 231 | } 232 | TextField { Layout.fillWidth: true; text: controller.scale6; onAccepted: controller.setScale6(text) } 233 | TextField { Layout.fillWidth: true; text: controller.offsetX6; onAccepted: controller.setOffsetX6(text) } 234 | TextField { Layout.fillWidth: true; text: controller.offsetY6; onAccepted: controller.setOffsetY6(text) } 235 | ComboBox { 236 | Layout.fillWidth: true 237 | model: controller.lineList 238 | onCurrentIndexChanged: function() { controller.setLineStyle6(currentIndex) } 239 | } 240 | ComboBox { 241 | id: _comboboxColor6 242 | Layout.fillWidth: true 243 | model: controller.colorList6 244 | onCurrentTextChanged: controller.setLineColor6(currentText) 245 | } 246 | 247 | // --- 第 7 行: 数据 --- 248 | Button { 249 | Layout.fillWidth: true 250 | text: controller.visible7 ? qsTr("7") : qsTr("hide") 251 | onClicked: controller.setVisible7(!controller.visible7) 252 | } 253 | ComboBox { 254 | id: _comboboxTable7 255 | Layout.fillWidth: true 256 | model: controller.tableList7 257 | onCurrentTextChanged: controller.setFieldList7(currentText) 258 | } 259 | ComboBox { 260 | Layout.fillWidth: true 261 | model: controller.fieldList7 262 | onActivated: function(index) { controller.setField7(model[index]) } 263 | } 264 | TextField { Layout.fillWidth: true; text: controller.scale7; onAccepted: controller.setScale7(text) } 265 | TextField { Layout.fillWidth: true; text: controller.offsetX7; onAccepted: controller.setOffsetX7(text) } 266 | TextField { Layout.fillWidth: true; text: controller.offsetY7; onAccepted: controller.setOffsetY7(text) } 267 | ComboBox { 268 | Layout.fillWidth: true 269 | model: controller.lineList 270 | onCurrentIndexChanged: function() { controller.setLineStyle7(currentIndex) } 271 | } 272 | ComboBox { 273 | id: _comboboxColor7 274 | Layout.fillWidth: true 275 | model: controller.colorList7 276 | onCurrentTextChanged: controller.setLineColor7(currentText) 277 | } 278 | 279 | // --- 第 8 行: 数据 --- 280 | Button { 281 | Layout.fillWidth: true 282 | text: controller.visible8 ? qsTr("8") : qsTr("hide") 283 | onClicked: controller.setVisible8(!controller.visible8) 284 | } 285 | ComboBox { 286 | id: _comboboxTable8 287 | Layout.fillWidth: true 288 | model: controller.tableList8 289 | onCurrentTextChanged: controller.setFieldList8(currentText) 290 | } 291 | ComboBox { 292 | Layout.fillWidth: true 293 | model: controller.fieldList8 294 | onActivated: function(index) { controller.setField8(model[index]) } 295 | } 296 | TextField { Layout.fillWidth: true; text: controller.scale8; onAccepted: controller.setScale8(text) } 297 | TextField { Layout.fillWidth: true; text: controller.offsetX8; onAccepted: controller.setOffsetX8(text) } 298 | TextField { Layout.fillWidth: true; text: controller.offsetY8; onAccepted: controller.setOffsetY8(text) } 299 | ComboBox { 300 | Layout.fillWidth: true 301 | model: controller.lineList 302 | onCurrentIndexChanged: function() { controller.setLineStyle8(currentIndex) } 303 | } 304 | ComboBox { 305 | id: _comboboxColor8 306 | Layout.fillWidth: true 307 | model: controller.colorList8 308 | onCurrentTextChanged: controller.setLineColor8(currentText) 309 | } 310 | 311 | // --- 第 9 行: 数据 --- 312 | Button { 313 | Layout.fillWidth: true 314 | text: controller.visible9 ? qsTr("9") : qsTr("hide") 315 | onClicked: controller.setVisible9(!controller.visible9) 316 | } 317 | ComboBox { 318 | id: _comboboxTable9 319 | Layout.fillWidth: true 320 | model: controller.tableList9 321 | onCurrentTextChanged: controller.setFieldList9(currentText) 322 | } 323 | ComboBox { 324 | Layout.fillWidth: true 325 | model: controller.fieldList9 326 | onActivated: function(index) { controller.setField9(model[index]) } 327 | } 328 | TextField { Layout.fillWidth: true; text: controller.scale9; onAccepted: controller.setScale9(text) } 329 | TextField { Layout.fillWidth: true; text: controller.offsetX9; onAccepted: controller.setOffsetX9(text) } 330 | TextField { Layout.fillWidth: true; text: controller.offsetY9; onAccepted: controller.setOffsetY9(text) } 331 | ComboBox { 332 | Layout.fillWidth: true 333 | model: controller.lineList 334 | onCurrentIndexChanged: function() { controller.setLineStyle9(currentIndex) } 335 | } 336 | ComboBox { 337 | id: _comboboxColor9 338 | Layout.fillWidth: true 339 | model: controller.colorList9 340 | onCurrentTextChanged: controller.setLineColor9(currentText) 341 | } 342 | 343 | // --- 第 10 行: 数据 --- 344 | Button { 345 | Layout.fillWidth: true 346 | text: controller.visible10 ? qsTr("10") : qsTr("hide") 347 | onClicked: controller.setVisible10(!controller.visible10) 348 | } 349 | ComboBox { 350 | id: _comboboxTable10 351 | Layout.fillWidth: true 352 | model: controller.tableList10 353 | onCurrentTextChanged: controller.setFieldList10(currentText) 354 | } 355 | ComboBox { 356 | Layout.fillWidth: true 357 | model: controller.fieldList10 358 | onActivated: function(index) { controller.setField10(model[index]) } 359 | } 360 | TextField { Layout.fillWidth: true; text: controller.scale10; onAccepted: controller.setScale10(text) } 361 | TextField { Layout.fillWidth: true; text: controller.offsetX10; onAccepted: controller.setOffsetX10(text) } 362 | TextField { Layout.fillWidth: true; text: controller.offsetY10; onAccepted: controller.setOffsetY10(text) } 363 | ComboBox { 364 | Layout.fillWidth: true 365 | model: controller.lineList 366 | onCurrentIndexChanged: function() { controller.setLineStyle10(currentIndex) } 367 | } 368 | ComboBox { 369 | id: _comboboxColor10 370 | Layout.fillWidth: true 371 | model: controller.colorList10 372 | onCurrentTextChanged: controller.setLineColor10(currentText) 373 | } 374 | } 375 | 376 | Item { 377 | Layout.fillWidth: true 378 | Layout.fillHeight: true 379 | } 380 | 381 | RowLayout { 382 | Layout.alignment: Qt.AlignRight 383 | 384 | Button{ 385 | id: _buttonInit 386 | text: qsTr("Init") 387 | onClicked:{ 388 | controller.init() 389 | controller.setVisible1(false) 390 | controller.setVisible2(false) 391 | controller.setVisible3(false) 392 | controller.setVisible4(false) 393 | controller.setVisible5(false) 394 | controller.setVisible6(false) 395 | controller.setVisible7(false) 396 | controller.setVisible8(false) 397 | controller.setVisible9(false) 398 | controller.setVisible10(false) 399 | } 400 | } 401 | Button{ 402 | id: _buttonHideAll 403 | text: qsTr("Hide All") 404 | onClicked:{ 405 | controller.setVisible1(false) 406 | controller.setVisible2(false) 407 | controller.setVisible3(false) 408 | controller.setVisible4(false) 409 | controller.setVisible5(false) 410 | controller.setVisible6(false) 411 | controller.setVisible7(false) 412 | controller.setVisible8(false) 413 | controller.setVisible9(false) 414 | controller.setVisible10(false) 415 | } 416 | } 417 | } 418 | } 419 | } 420 | -------------------------------------------------------------------------------- /src/APLDataCache.cpp: -------------------------------------------------------------------------------- 1 | #include "APLDataCache.h" 2 | #include "mainwindow.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | APL_LOGGING_CATEGORY(APLDATACACHE_LOG, "APLDataCacheLog") 9 | 10 | APLDataCache* APLDataCache::_singleton; 11 | 12 | APLDataCache::APLDataCache(QObject *parent) : QObject(parent) 13 | { 14 | _singleton = this; 15 | reset(); 16 | } 17 | 18 | void APLDataCache::reset() 19 | { 20 | _store.clear(); 21 | _instantiable_store.clear(); 22 | _binary_store.clear(); 23 | _metadata = QJsonObject(); 24 | _maintable_ids.clear(); 25 | _maintable_names.clear(); 26 | _maintable_formats.clear(); 27 | _export_dir.clear(); 28 | } 29 | 30 | void APLDataCache::setTableSplit(bool enabled) 31 | { 32 | _table_split = enabled; 33 | } 34 | 35 | void APLDataCache::setSaveCSV(bool enabled) 36 | { 37 | _save_csv = enabled; 38 | } 39 | 40 | void APLDataCache::setTrimFrom(quint64 v) 41 | { 42 | _trim_from = v; 43 | } 44 | 45 | void APLDataCache::setTrimTo(quint64 v) 46 | { 47 | _trim_to = v; 48 | } 49 | 50 | void APLDataCache::setFilterMode(const qint8& v) 51 | { 52 | _filter_mode = v; 53 | } 54 | 55 | void APLDataCache::setFilterInclude(const QStringList& v) 56 | { 57 | _filter_include = v; 58 | } 59 | 60 | void APLDataCache::setFilterExclude(const QStringList& v) 61 | { 62 | _filter_exclude = v; 63 | } 64 | 65 | void APLDataCache::setFilterFile(const QString& v) 66 | { 67 | _filter_file = v; 68 | } 69 | 70 | void APLDataCache::addFormat(const quint8 &type, const QString &name, const QString &format, const QString &labels, const qint16 &i) 71 | { 72 | // --- Start of Instance Splitting Logic --- 73 | // 仅当 _table_split 为 true 时,才执行实例拆分逻辑 74 | if (_table_split && i == -1) { 75 | if (_instantiable_store.contains(name)) { 76 | return; // Already registered 77 | } 78 | QStringList headers = labels.split(','); 79 | if (headers.size() > 1) { 80 | const QString &instance_header = headers[1]; 81 | if ((instance_header.compare("I", Qt::CaseSensitive) == 0 || 82 | instance_header.compare("Instance", Qt::CaseSensitive) == 0 || 83 | instance_header.compare("C", Qt::CaseSensitive) == 0 || 84 | instance_header.compare("IMU", Qt::CaseSensitive) == 0 || 85 | instance_header.compare("Type", Qt::CaseSensitive) == 0 || 86 | instance_header.compare("Id", Qt::CaseSensitive) == 0) && 87 | name.compare("EV", Qt::CaseSensitive) != 0 && 88 | name.compare("MULT", Qt::CaseSensitive) != 0 && 89 | name.compare("UNIT", Qt::CaseSensitive) != 0) 90 | { 91 | if (_instantiable_store.contains(name)) { 92 | return; // Already registered 93 | } 94 | 95 | _maintable_ids.append(QString("%1").arg(type)); 96 | _maintable_names.append(name); 97 | _maintable_formats.append(format); 98 | 99 | // Store for in-memory access 100 | MessageData newData; 101 | newData.type = type; 102 | newData.format = format; 103 | newData.headers = labels.split(','); 104 | newData.columns.resize(newData.headers.size()); 105 | newData.labels = labels; 106 | _instantiable_store[name] = newData; 107 | 108 | // Store for JSON export 109 | QJsonObject formatObj; 110 | formatObj["format"] = format; 111 | formatObj["labels"] = labels; 112 | _metadata[name] = formatObj; 113 | 114 | return; 115 | } 116 | } 117 | } 118 | // --- End of Instance Splitting Logic --- 119 | 120 | if (_store.contains(name)) { 121 | return; // Already registered 122 | } 123 | 124 | _maintable_ids.append(QString("%1").arg(type)); 125 | _maintable_names.append(name); 126 | _maintable_formats.append(format); 127 | 128 | // Store for in-memory access 129 | MessageData newData; 130 | newData.type = type; 131 | newData.format = format; 132 | newData.headers = labels.split(','); 133 | newData.columns.resize(newData.headers.size()); 134 | newData.labels = labels; 135 | _store[name] = newData; 136 | 137 | // Store for JSON export 138 | QJsonObject formatObj; 139 | formatObj["format"] = format; 140 | formatObj["labels"] = labels; 141 | _metadata[name] = formatObj; 142 | } 143 | 144 | // This is the new function that accepts binary data and performs instance splitting. 145 | void APLDataCache::addData(const QString &name, const QString &new_name, const uchar *payload, const qint16 &i, const int &payload_len) 146 | { 147 | if (!_store.contains(name) && !_instantiable_store.contains(name)) { 148 | return; // Cannot add data without a format definition 149 | } 150 | 151 | if (_filter_mode == 0) { 152 | if (_filter_include.contains(name) == false && _store.contains(name)) { 153 | return; 154 | } 155 | } else if (_filter_mode == 1) { 156 | if (_filter_exclude.contains(name) == true) { 157 | return; 158 | } 159 | } 160 | 161 | // --- Start of Instance Splitting Logic --- 162 | // 仅当 _table_split 为 true 时,才执行实例拆分逻辑 163 | if (_table_split && _instantiable_store.contains(name)) { 164 | const MessageData &messageData = _instantiable_store[name]; 165 | const QString &new_table_name = new_name; 166 | if (!_store.contains(new_table_name)) { 167 | QJsonObject js_obj = _metadata.value(name).toObject(); 168 | addFormat(messageData.type, // Use cached type 169 | new_table_name, 170 | js_obj.value("format").toString(), 171 | js_obj.value("labels").toString(), 172 | i 173 | ); 174 | } 175 | 176 | if (_filter_mode == 0) { 177 | if (_filter_include.contains(new_name) == false) { 178 | return; 179 | } 180 | } else if (_filter_mode == 1) { 181 | if (_filter_exclude.contains(new_name) == true) { 182 | return; 183 | } 184 | } 185 | 186 | // Add data to the instance-specific table 187 | if (_trim_from < _trim_to) { 188 | if (_cut_data(static_cast(*(payload - 1)), _trim_from, _trim_to, *reinterpret_cast(payload))){ 189 | _binary_store[new_table_name].append(QByteArray(reinterpret_cast(payload), payload_len)); 190 | } 191 | if (*reinterpret_cast(payload) > _trim_to) { 192 | trim_complete = true; 193 | } 194 | } else { 195 | _binary_store[new_table_name].append(QByteArray(reinterpret_cast(payload), payload_len)); 196 | } 197 | return; 198 | } 199 | // --- End of Instance Splitting Logic --- 200 | 201 | if (_trim_from < _trim_to) { 202 | if (_cut_data(static_cast(*(payload - 1)), _trim_from, _trim_to, *reinterpret_cast(payload))){ 203 | _binary_store[name].append(QByteArray(reinterpret_cast(payload), payload_len)); 204 | } 205 | } else { 206 | _binary_store[name].append(QByteArray(reinterpret_cast(payload), payload_len)); 207 | } 208 | } 209 | 210 | QVector APLDataCache::getColumn(const QString &messageName, const QString &columnName) 211 | { 212 | if (!_store.contains(messageName)) { 213 | return QVector(); 214 | } 215 | 216 | const MessageData &messageData = _store[messageName]; 217 | int columnIndex = messageData.headers.indexOf(columnName); 218 | 219 | if (columnIndex == -1) { 220 | return QVector(); 221 | } 222 | 223 | return messageData.columns[columnIndex]; 224 | } 225 | 226 | void APLDataCache::exportToFile(const QString &outputDir) 227 | { 228 | if (!_save_csv && !export_csv) return; 229 | export_csv = false; 230 | 231 | QString export_dir(outputDir); 232 | 233 | if(_filter_file.length()>0) { 234 | export_dir.append("_"); 235 | export_dir.append(_filter_file.section('.',0,0)); 236 | export_dir.append("-"); 237 | export_dir.append(QString("%1").arg(_filter_mode)); 238 | } 239 | 240 | if (_trim_from < _trim_to) { 241 | export_dir.append("_trim"); 242 | } 243 | 244 | _export_dir = export_dir; 245 | 246 | QDir dir; 247 | if (!dir.exists(export_dir)) { 248 | dir.mkpath(export_dir); 249 | } 250 | MainWindow::getMainWindow()->dialog()->ignore_db(true); 251 | MainWindow::getMainWindow()->dialog()->loadSettings(); 252 | 253 | // 获取 QWidgetAction 254 | QWidgetAction *actionPythonIgnoreDB = qobject_cast( 255 | MainWindow::getMainWindow()->ui().menuFile->actions().at(7)); 256 | 257 | if (actionPythonIgnoreDB) { 258 | // 获取内部的 QCheckBox 259 | QCheckBox *checkBox = qobject_cast(actionPythonIgnoreDB->defaultWidget()); 260 | if (checkBox) { 261 | checkBox->setChecked(MainWindow::getMainWindow()->dialog()->get_python_ingnore_db()); 262 | } 263 | } 264 | 265 | // Export data to CSV files 266 | for (auto it = _binary_store.constBegin(); it != _binary_store.constEnd(); ++it) { 267 | const QString &tableName = it.key(); 268 | const QList &rows = it.value(); 269 | 270 | if (!_store.contains(tableName)) continue; 271 | 272 | QFile file(QDir(export_dir).filePath(tableName + ".csv")); 273 | if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) continue; 274 | 275 | QTextStream stream(&file); 276 | QStringList sanitizedHeaders; 277 | for (const QString& header : _store[tableName].headers) { 278 | sanitizedHeaders << _sanitizeCSVFieldName(header); 279 | } 280 | stream << _store[tableName].headers.join(',') << "\n"; 281 | 282 | int idx = _maintable_names.indexOf(tableName); 283 | if (idx == -1) continue; 284 | QString format = _maintable_formats[idx]; 285 | 286 | for (const QByteArray &row : rows) { 287 | QStringList rowValues; 288 | const uchar *ptr = reinterpret_cast(row.constData()); 289 | 290 | for (QChar formatChar : format) { 291 | switch(formatChar.toLatin1()) { 292 | case 'b': { qint8 v; memcpy(&v, ptr, sizeof(v)); ptr += sizeof(v); rowValues << QString::number(v); break; } 293 | case 'B': { quint8 v; memcpy(&v, ptr, sizeof(v)); ptr += sizeof(v); rowValues << QString::number(v); break; } 294 | case 'h': { qint16 v; memcpy(&v, ptr, sizeof(v)); ptr += sizeof(v); rowValues << QString::number(v); break; } 295 | case 'H': { quint16 v; memcpy(&v, ptr, sizeof(v)); ptr += sizeof(v); rowValues << QString::number(v); break; } 296 | case 'i': { qint32 v; memcpy(&v, ptr, sizeof(v)); ptr += sizeof(v); rowValues << QString::number(v); break; } 297 | case 'I': { quint32 v; memcpy(&v, ptr, sizeof(v)); ptr += sizeof(v); rowValues << QString::number(v); break; } 298 | case 'f': { float v; memcpy(&v, ptr, sizeof(v)); ptr += sizeof(v); rowValues << QString::number(v); break; } 299 | case 'd': { double v; memcpy(&v, ptr, sizeof(v)); ptr += sizeof(v); rowValues << QString::number(v); break; } 300 | case 'c': { qint16 v; memcpy(&v, ptr, sizeof(v)); ptr += sizeof(v); rowValues << QString::number(v / 100.0); break; } 301 | case 'C': { quint16 v; memcpy(&v, ptr, sizeof(v)); ptr += sizeof(v); rowValues << QString::number(v / 100.0); break; } 302 | case 'e': { qint32 v; memcpy(&v, ptr, sizeof(v)); ptr += sizeof(v); rowValues << QString::number(v / 100.0); break; } 303 | case 'E': { quint32 v; memcpy(&v, ptr, sizeof(v)); ptr += sizeof(v); rowValues << QString::number(v / 100.0); break; } 304 | case 'L': { qint32 v; memcpy(&v, ptr, sizeof(v)); ptr += sizeof(v); rowValues << QString::number(v); break; } 305 | case 'M': { quint8 v; memcpy(&v, ptr, sizeof(v)); ptr += sizeof(v); rowValues << QString::number(v); break; } 306 | case 'q': { qint64 v; memcpy(&v, ptr, sizeof(v)); ptr += sizeof(v); rowValues << QString::number(v); break; } 307 | case 'Q': { quint64 v; memcpy(&v, ptr, sizeof(v)); ptr += sizeof(v); rowValues << QString::number(v); break; } 308 | case 'n': { char v[5] = {0}; memcpy(v, ptr, 4); ptr += 4; rowValues << v; break; } 309 | case 'N': { char v[17] = {0}; memcpy(v, ptr, 16); ptr += 16; rowValues << v; break; } 310 | case 'Z': { char v[65] = {0}; memcpy(v, ptr, 64); ptr += 64; rowValues << v; break; } 311 | case 'a': { ptr += 64; rowValues << "(array)"; break; } // Placeholder for array 312 | } 313 | } 314 | stream << rowValues.join(',') << "\n"; 315 | } 316 | file.close(); 317 | } 318 | 319 | // Export metadata to json file 320 | QFile jsonFile(QDir(export_dir).filePath("metadata.json")); 321 | if (jsonFile.open(QIODevice::WriteOnly)) { 322 | jsonFile.write(QJsonDocument(_metadata).toJson()); 323 | jsonFile.close(); 324 | } 325 | } 326 | 327 | QString APLDataCache::_sanitizeCSVFieldName(const QString& fieldName) const 328 | { 329 | QString sanitized = fieldName.trimmed(); 330 | 331 | // CSV中如果字段名包含逗号、引号或换行符,需要用引号包围 332 | if (sanitized.contains(',') || sanitized.contains('"') || sanitized.contains('\n')) { 333 | sanitized.replace('"', "\"\""); // 转义引号 334 | sanitized = "\"" + sanitized + "\""; 335 | } 336 | 337 | return sanitized; 338 | } 339 | 340 | int APLDataCache::getTableNum() 341 | { 342 | return _store.size(); 343 | } 344 | 345 | int APLDataCache::getGroupCount() 346 | { 347 | return _store.size(); 348 | } 349 | 350 | QString APLDataCache::getGroupName(int i) 351 | { 352 | return _store.keys()[i]; 353 | } 354 | 355 | QString APLDataCache::getTableName(int i) 356 | { 357 | return _store.keys()[i]; 358 | } 359 | 360 | QString APLDataCache::getItemName(QString table, int i) 361 | { 362 | return _store[table].headers[i]; 363 | } 364 | 365 | int APLDataCache::getItemCount(QString table) 366 | { 367 | return _store[table].headers.size(); 368 | } 369 | 370 | bool APLDataCache::getData(QString table, QString field, int len, QVector& data, double offset, double scale) 371 | { 372 | if (!_binary_store.contains(table) || !_store.contains(table)) { 373 | return false; 374 | } 375 | 376 | const MessageData &messageData = _store[table]; 377 | const QList &binary_rows = _binary_store[table]; 378 | 379 | int field_idx = messageData.headers.indexOf(field); 380 | if (field_idx == -1) { 381 | return false; 382 | } 383 | 384 | int idx = _maintable_names.indexOf(table); 385 | if (idx == -1) { 386 | return false; 387 | } 388 | QString format = _maintable_formats[idx]; 389 | 390 | // Calculate the byte offset of the desired field within a binary row 391 | int field_offset = 0; 392 | bool found_field = false; 393 | for (int i = 0; i < format.length(); ++i) { 394 | if (i == field_idx) { 395 | found_field = true; 396 | break; 397 | } 398 | switch(format[i].toLatin1()) { 399 | case 'a': field_offset += 64; break; 400 | case 'b': case 'B': case 'M': field_offset += 1; break; 401 | case 'h': case 'H': case 'c': case 'C': field_offset += 2; break; 402 | case 'i': case 'I': case 'f': case 'e': case 'E': case 'L': field_offset += 4; break; 403 | case 'd': case 'q': case 'Q': field_offset += 8; break; 404 | case 'n': field_offset += 4; break; 405 | case 'N': field_offset += 16; break; 406 | case 'Z': field_offset += 64; break; 407 | default: return false; // Unknown format char 408 | } 409 | } 410 | 411 | if (!found_field) { 412 | return false; 413 | } 414 | 415 | data.resize(len); 416 | 417 | // Now, iterate through the binary rows and parse just the required field 418 | for (int i = 0; i < len; ++i) { 419 | const uchar* row_ptr = reinterpret_cast(binary_rows[i].constData()) + field_offset; 420 | double val = 0.0; 421 | 422 | switch(format[field_idx].toLatin1()) { 423 | case 'b': { qint8 v; memcpy(&v, row_ptr, sizeof(v)); val = v; break; } 424 | case 'B': { quint8 v; memcpy(&v, row_ptr, sizeof(v)); val = v; break; } 425 | case 'h': { qint16 v; memcpy(&v, row_ptr, sizeof(v)); val = v; break; } 426 | case 'H': { quint16 v; memcpy(&v, row_ptr, sizeof(v)); val = v; break; } 427 | case 'i': { qint32 v; memcpy(&v, row_ptr, sizeof(v)); val = v; break; } 428 | case 'I': { quint32 v; memcpy(&v, row_ptr, sizeof(v)); val = v; break; } 429 | case 'f': { float v; memcpy(&v, row_ptr, sizeof(v)); val = v; break; } 430 | case 'd': { double v; memcpy(&v, row_ptr, sizeof(v)); val = v; break; } 431 | case 'c': { qint16 v; memcpy(&v, row_ptr, sizeof(v)); val = v / 100.0; break; } 432 | case 'C': { quint16 v; memcpy(&v, row_ptr, sizeof(v)); val = v / 100.0; break; } 433 | case 'e': { qint32 v; memcpy(&v, row_ptr, sizeof(v)); val = v / 100.0; break; } 434 | case 'E': { quint32 v; memcpy(&v, row_ptr, sizeof(v)); val = v / 100.0; break; } 435 | case 'L': { qint32 v; memcpy(&v, row_ptr, sizeof(v)); val = v; break; } 436 | case 'M': { quint8 v; memcpy(&v, row_ptr, sizeof(v)); val = v; break; } 437 | case 'q': { qint64 v; memcpy(&v, row_ptr, sizeof(v)); val = v; break; } 438 | case 'Q': { quint64 v; memcpy(&v, row_ptr, sizeof(v)); val = v; break; } 439 | // 'a', 'n', 'N', 'Z' are not convertible to a single double 440 | default: val = 0.0; break; 441 | } 442 | data[i] = (val + offset) * scale; 443 | } 444 | 445 | return true; 446 | } 447 | 448 | int APLDataCache::getLen(QString table, QString field) 449 | { 450 | Q_UNUSED(field); 451 | return _binary_store.value(table).size(); 452 | } 453 | 454 | void APLDataCache::getFormat(quint8 &id, QString &name, QString &format) 455 | { 456 | int idx = _maintable_ids.indexOf(QString("%1").arg(id)); 457 | name = _maintable_names[idx]; 458 | format = _maintable_formats[idx]; 459 | } 460 | 461 | bool APLDataCache::checkMainTable(quint8 id) 462 | { 463 | bool ret = false; 464 | if(_maintable_ids.contains(QString("%1").arg(id))){ 465 | ret = true; 466 | } 467 | 468 | return ret; 469 | } 470 | 471 | QVector APLDataCache::parseBinaryData(const QByteArray& data, const QString& format) const 472 | { 473 | QVector result; 474 | const uchar *ptr = reinterpret_cast(data.constData()); 475 | int current_offset = 0; 476 | 477 | for (QChar formatChar : format) { 478 | switch(formatChar.toLatin1()) { 479 | case 'b': { qint8 v; memcpy(&v, ptr + current_offset, sizeof(v)); result.append(v); current_offset += sizeof(v); break; } 480 | case 'B': { quint8 v; memcpy(&v, ptr + current_offset, sizeof(v)); result.append(v); current_offset += sizeof(v); break; } 481 | case 'h': { qint16 v; memcpy(&v, ptr + current_offset, sizeof(v)); result.append(v); current_offset += sizeof(v); break; } 482 | case 'H': { quint16 v; memcpy(&v, ptr + current_offset, sizeof(v)); result.append(v); current_offset += sizeof(v); break; } 483 | case 'i': { qint32 v; memcpy(&v, ptr + current_offset, sizeof(v)); result.append(v); current_offset += sizeof(v); break; } 484 | case 'I': { quint32 v; memcpy(&v, ptr + current_offset, sizeof(v)); result.append(v); current_offset += sizeof(v); break; } 485 | case 'f': { float v; memcpy(&v, ptr + current_offset, sizeof(v)); result.append(v); current_offset += sizeof(v); break; } 486 | case 'd': { double v; memcpy(&v, ptr + current_offset, sizeof(v)); result.append(v); current_offset += sizeof(v); break; } 487 | case 'c': { qint16 v; memcpy(&v, ptr + current_offset, sizeof(v)); result.append(v / 100.0); current_offset += sizeof(v); break; } 488 | case 'C': { quint16 v; memcpy(&v, ptr + current_offset, sizeof(v)); result.append(v / 100.0); current_offset += sizeof(v); break; } 489 | case 'e': { qint32 v; memcpy(&v, ptr + current_offset, sizeof(v)); result.append(v / 100.0); current_offset += sizeof(v); break; } 490 | case 'E': { quint32 v; memcpy(&v, ptr + current_offset, sizeof(v)); result.append(v / 100.0); current_offset += sizeof(v); break; } 491 | case 'L': { qint32 v; memcpy(&v, ptr + current_offset, sizeof(v)); result.append(v); current_offset += sizeof(v); break; } 492 | case 'M': { quint8 v; memcpy(&v, ptr + current_offset, sizeof(v)); result.append(v); current_offset += sizeof(v); break; } 493 | case 'q': { qint64 v; memcpy(&v, ptr + current_offset, sizeof(v)); result.append(v); current_offset += sizeof(v); break; } 494 | case 'Q': { quint64 v; memcpy(&v, ptr + current_offset, sizeof(v)); result.append(v); current_offset += sizeof(v); break; } 495 | case 'n': { char str[5] = {0}; memcpy(str, ptr + current_offset, 4); result.append(QString(str)); current_offset += 4; break; } 496 | case 'N': { char str[17] = {0}; memcpy(str, ptr + current_offset, 16); result.append(QString(str)); current_offset += 16; break; } 497 | case 'Z': { char str[65] = {0}; memcpy(str, ptr + current_offset, 64); result.append(QString(str)); current_offset += 64; break; } 498 | case 'a': { current_offset += 64; result.append(QVariant()); break; } // Placeholder for array, append empty QVariant 499 | default: result.append(QVariant()); break; // Unknown format char, append empty QVariant 500 | } 501 | } 502 | return result; 503 | } 504 | 505 | bool 506 | APLDataCache::_cut_data(quint8 id, quint64 start_time, quint64 stop_time, quint64 now) 507 | { 508 | bool res = true; 509 | switch(id){ 510 | case 32: // PARM 511 | case 108: // FMTU 512 | break; 513 | default: 514 | if(now < start_time || now > stop_time){ 515 | res = false; 516 | } 517 | } 518 | 519 | return res; 520 | } 521 | -------------------------------------------------------------------------------- /src/DataAnalyzeController.h: -------------------------------------------------------------------------------- 1 | #ifndef DATAANALYZECONTROLLER_H 2 | #define DATAANALYZECONTROLLER_H 3 | 4 | #include 5 | #include "APLLoggingCategory.h" 6 | #include "qcustomplot.h" 7 | 8 | Q_DECLARE_LOGGING_CATEGORY(DATA_ANALYZE_LOG) 9 | 10 | #define MAX_LINE_NUM 10 11 | 12 | class DataAnalyzeController : public QObject 13 | { 14 | Q_OBJECT 15 | public: 16 | DataAnalyzeController(); 17 | 18 | Q_PROPERTY(QStringList lineList READ lineList NOTIFY lineListChanged) 19 | Q_PROPERTY(QStringList colorList READ colorList NOTIFY colorListChanged) 20 | // Row 1 21 | Q_PROPERTY(QStringList tableList1 READ tableList1 NOTIFY tableList1Changed) 22 | Q_PROPERTY(bool visible1 READ visible1 NOTIFY visible1Changed) 23 | Q_PROPERTY(QStringList fieldList1 READ fieldList1 NOTIFY fieldList1Changed) 24 | Q_PROPERTY(QString scale1 READ scale1 NOTIFY scale1Changed) 25 | Q_PROPERTY(QString offsetX1 READ offsetX1 NOTIFY offsetX1Changed) 26 | Q_PROPERTY(QString offsetY1 READ offsetY1 NOTIFY offsetY1Changed) 27 | Q_PROPERTY(QStringList colorList1 READ colorList1 NOTIFY colorList1Changed) 28 | // Row 2 29 | Q_PROPERTY(QStringList tableList2 READ tableList2 NOTIFY tableList2Changed) 30 | Q_PROPERTY(bool visible2 READ visible2 NOTIFY visible2Changed) 31 | Q_PROPERTY(QStringList fieldList2 READ fieldList2 NOTIFY fieldList2Changed) 32 | Q_PROPERTY(QString scale2 READ scale2 NOTIFY scale2Changed) 33 | Q_PROPERTY(QString offsetX2 READ offsetX2 NOTIFY offsetX2Changed) 34 | Q_PROPERTY(QString offsetY2 READ offsetY2 NOTIFY offsetY2Changed) 35 | Q_PROPERTY(QStringList colorList2 READ colorList2 NOTIFY colorList2Changed) 36 | // Row 3 37 | Q_PROPERTY(QStringList tableList3 READ tableList3 NOTIFY tableList3Changed) 38 | Q_PROPERTY(bool visible3 READ visible3 NOTIFY visible3Changed) 39 | Q_PROPERTY(QStringList fieldList3 READ fieldList3 NOTIFY fieldList3Changed) 40 | Q_PROPERTY(QString scale3 READ scale3 NOTIFY scale3Changed) 41 | Q_PROPERTY(QString offsetX3 READ offsetX3 NOTIFY offsetX3Changed) 42 | Q_PROPERTY(QString offsetY3 READ offsetY3 NOTIFY offsetY3Changed) 43 | Q_PROPERTY(QStringList colorList3 READ colorList3 NOTIFY colorList3Changed) 44 | // Row 4 45 | Q_PROPERTY(QStringList tableList4 READ tableList4 NOTIFY tableList4Changed) 46 | Q_PROPERTY(bool visible4 READ visible4 NOTIFY visible4Changed) 47 | Q_PROPERTY(QStringList fieldList4 READ fieldList4 NOTIFY fieldList4Changed) 48 | Q_PROPERTY(QString scale4 READ scale4 NOTIFY scale4Changed) 49 | Q_PROPERTY(QString offsetX4 READ offsetX4 NOTIFY offsetX4Changed) 50 | Q_PROPERTY(QString offsetY4 READ offsetY4 NOTIFY offsetY4Changed) 51 | Q_PROPERTY(QStringList colorList4 READ colorList4 NOTIFY colorList4Changed) 52 | // Row 5 53 | Q_PROPERTY(QStringList tableList5 READ tableList5 NOTIFY tableList5Changed) 54 | Q_PROPERTY(bool visible5 READ visible5 NOTIFY visible5Changed) 55 | Q_PROPERTY(QStringList fieldList5 READ fieldList5 NOTIFY fieldList5Changed) 56 | Q_PROPERTY(QString scale5 READ scale5 NOTIFY scale5Changed) 57 | Q_PROPERTY(QString offsetX5 READ offsetX5 NOTIFY offsetX5Changed) 58 | Q_PROPERTY(QString offsetY5 READ offsetY5 NOTIFY offsetY5Changed) 59 | Q_PROPERTY(QStringList colorList5 READ colorList5 NOTIFY colorList5Changed) 60 | // Row 6 61 | Q_PROPERTY(QStringList tableList6 READ tableList6 NOTIFY tableList6Changed) 62 | Q_PROPERTY(bool visible6 READ visible6 NOTIFY visible6Changed) 63 | Q_PROPERTY(QStringList fieldList6 READ fieldList6 NOTIFY fieldList6Changed) 64 | Q_PROPERTY(QString scale6 READ scale6 NOTIFY scale6Changed) 65 | Q_PROPERTY(QString offsetX6 READ offsetX6 NOTIFY offsetX6Changed) 66 | Q_PROPERTY(QString offsetY6 READ offsetY6 NOTIFY offsetY6Changed) 67 | Q_PROPERTY(QStringList colorList6 READ colorList6 NOTIFY colorList6Changed) 68 | // Row 7 69 | Q_PROPERTY(QStringList tableList7 READ tableList7 NOTIFY tableList7Changed) 70 | Q_PROPERTY(bool visible7 READ visible7 NOTIFY visible7Changed) 71 | Q_PROPERTY(QStringList fieldList7 READ fieldList7 NOTIFY fieldList7Changed) 72 | Q_PROPERTY(QString scale7 READ scale7 NOTIFY scale7Changed) 73 | Q_PROPERTY(QString offsetX7 READ offsetX7 NOTIFY offsetX7Changed) 74 | Q_PROPERTY(QString offsetY7 READ offsetY7 NOTIFY offsetY7Changed) 75 | Q_PROPERTY(QStringList colorList7 READ colorList7 NOTIFY colorList7Changed) 76 | // Row 8 77 | Q_PROPERTY(QStringList tableList8 READ tableList8 NOTIFY tableList8Changed) 78 | Q_PROPERTY(bool visible8 READ visible8 NOTIFY visible8Changed) 79 | Q_PROPERTY(QStringList fieldList8 READ fieldList8 NOTIFY fieldList8Changed) 80 | Q_PROPERTY(QString scale8 READ scale8 NOTIFY scale8Changed) 81 | Q_PROPERTY(QString offsetX8 READ offsetX8 NOTIFY offsetX8Changed) 82 | Q_PROPERTY(QString offsetY8 READ offsetY8 NOTIFY offsetY8Changed) 83 | Q_PROPERTY(QStringList colorList8 READ colorList8 NOTIFY colorList8Changed) 84 | // Row 9 85 | Q_PROPERTY(QStringList tableList9 READ tableList9 NOTIFY tableList9Changed) 86 | Q_PROPERTY(bool visible9 READ visible9 NOTIFY visible9Changed) 87 | Q_PROPERTY(QStringList fieldList9 READ fieldList9 NOTIFY fieldList9Changed) 88 | Q_PROPERTY(QString scale9 READ scale9 NOTIFY scale9Changed) 89 | Q_PROPERTY(QString offsetX9 READ offsetX9 NOTIFY offsetX9Changed) 90 | Q_PROPERTY(QString offsetY9 READ offsetY9 NOTIFY offsetY9Changed) 91 | Q_PROPERTY(QStringList colorList9 READ colorList9 NOTIFY colorList9Changed) 92 | // Row 10 93 | Q_PROPERTY(QStringList tableList10 READ tableList10 NOTIFY tableList10Changed) 94 | Q_PROPERTY(bool visible10 READ visible10 NOTIFY visible10Changed) 95 | Q_PROPERTY(QStringList fieldList10 READ fieldList10 NOTIFY fieldList10Changed) 96 | Q_PROPERTY(QString scale10 READ scale10 NOTIFY scale10Changed) 97 | Q_PROPERTY(QString offsetX10 READ offsetX10 NOTIFY offsetX10Changed) 98 | Q_PROPERTY(QString offsetY10 READ offsetY10 NOTIFY offsetY10Changed) 99 | Q_PROPERTY(QStringList colorList10 READ colorList10 NOTIFY colorList10Changed) 100 | 101 | // Row 1 102 | Q_INVOKABLE void setFieldList1 (QString table); 103 | Q_INVOKABLE void setField1 (QString field); 104 | Q_INVOKABLE void setScale1 (QString scale); 105 | Q_INVOKABLE void setOffsetX1 (QString offset); 106 | Q_INVOKABLE void setOffsetY1 (QString offset); 107 | Q_INVOKABLE void setVisible1 (bool visible); 108 | Q_INVOKABLE void setLineStyle1 (int style); 109 | Q_INVOKABLE void setLineColor1 (QString color); 110 | // Row 2 111 | Q_INVOKABLE void setFieldList2 (QString table); 112 | Q_INVOKABLE void setField2 (QString field); 113 | Q_INVOKABLE void setScale2 (QString scale); 114 | Q_INVOKABLE void setOffsetX2 (QString offset); 115 | Q_INVOKABLE void setOffsetY2 (QString offset); 116 | Q_INVOKABLE void setVisible2 (bool visible); 117 | Q_INVOKABLE void setLineStyle2 (int style); 118 | Q_INVOKABLE void setLineColor2 (QString color); 119 | // Row 3 120 | Q_INVOKABLE void setFieldList3 (QString table); 121 | Q_INVOKABLE void setField3 (QString field); 122 | Q_INVOKABLE void setScale3 (QString scale); 123 | Q_INVOKABLE void setOffsetX3 (QString offset); 124 | Q_INVOKABLE void setOffsetY3 (QString offset); 125 | Q_INVOKABLE void setVisible3 (bool visible); 126 | Q_INVOKABLE void setLineStyle3 (int style); 127 | Q_INVOKABLE void setLineColor3 (QString color); 128 | // Row 4 129 | Q_INVOKABLE void setFieldList4 (QString table); 130 | Q_INVOKABLE void setField4 (QString field); 131 | Q_INVOKABLE void setScale4 (QString scale); 132 | Q_INVOKABLE void setOffsetX4 (QString offset); 133 | Q_INVOKABLE void setOffsetY4 (QString offset); 134 | Q_INVOKABLE void setVisible4 (bool visible); 135 | Q_INVOKABLE void setLineStyle4 (int style); 136 | Q_INVOKABLE void setLineColor4 (QString color); 137 | // Row 5 138 | Q_INVOKABLE void setFieldList5 (QString table); 139 | Q_INVOKABLE void setField5 (QString field); 140 | Q_INVOKABLE void setScale5 (QString scale); 141 | Q_INVOKABLE void setOffsetX5 (QString offset); 142 | Q_INVOKABLE void setOffsetY5 (QString offset); 143 | Q_INVOKABLE void setVisible5 (bool visible); 144 | Q_INVOKABLE void setLineStyle5 (int style); 145 | Q_INVOKABLE void setLineColor5 (QString color); 146 | // Row 6 147 | Q_INVOKABLE void setFieldList6 (QString table); 148 | Q_INVOKABLE void setField6 (QString field); 149 | Q_INVOKABLE void setScale6 (QString scale); 150 | Q_INVOKABLE void setOffsetX6 (QString offset); 151 | Q_INVOKABLE void setOffsetY6 (QString offset); 152 | Q_INVOKABLE void setVisible6 (bool visible); 153 | Q_INVOKABLE void setLineStyle6 (int style); 154 | Q_INVOKABLE void setLineColor6 (QString color); 155 | // Row 7 156 | Q_INVOKABLE void setFieldList7 (QString table); 157 | Q_INVOKABLE void setField7 (QString field); 158 | Q_INVOKABLE void setScale7 (QString scale); 159 | Q_INVOKABLE void setOffsetX7 (QString offset); 160 | Q_INVOKABLE void setOffsetY7 (QString offset); 161 | Q_INVOKABLE void setVisible7 (bool visible); 162 | Q_INVOKABLE void setLineStyle7 (int style); 163 | Q_INVOKABLE void setLineColor7 (QString color); 164 | // Row 8 165 | Q_INVOKABLE void setFieldList8 (QString table); 166 | Q_INVOKABLE void setField8 (QString field); 167 | Q_INVOKABLE void setScale8 (QString scale); 168 | Q_INVOKABLE void setOffsetX8 (QString offset); 169 | Q_INVOKABLE void setOffsetY8 (QString offset); 170 | Q_INVOKABLE void setVisible8 (bool visible); 171 | Q_INVOKABLE void setLineStyle8 (int style); 172 | Q_INVOKABLE void setLineColor8 (QString color); 173 | // Row 9 174 | Q_INVOKABLE void setFieldList9 (QString table); 175 | Q_INVOKABLE void setField9 (QString field); 176 | Q_INVOKABLE void setScale9 (QString scale); 177 | Q_INVOKABLE void setOffsetX9 (QString offset); 178 | Q_INVOKABLE void setOffsetY9 (QString offset); 179 | Q_INVOKABLE void setVisible9 (bool visible); 180 | Q_INVOKABLE void setLineStyle9 (int style); 181 | Q_INVOKABLE void setLineColor9 (QString color); 182 | // Row 10 183 | Q_INVOKABLE void setFieldList10 (QString table); 184 | Q_INVOKABLE void setField10 (QString field); 185 | Q_INVOKABLE void setScale10 (QString scale); 186 | Q_INVOKABLE void setOffsetX10 (QString offset); 187 | Q_INVOKABLE void setOffsetY10 (QString offset); 188 | Q_INVOKABLE void setVisible10 (bool visible); 189 | Q_INVOKABLE void setLineStyle10 (int style); 190 | Q_INVOKABLE void setLineColor10 (QString color); 191 | 192 | QStringList lineList () { return _lineList; } 193 | QStringList colorList () { return _colorList; } 194 | // Row 1 195 | QStringList tableList1 () { return _tableList1; } 196 | QStringList fieldList1 () { return _fieldList[0]; } 197 | QString scale1 () { return QString::number(_scale[0], 'f', 3); } 198 | QString offsetX1 () { return QString::number(_offsetX[0]); } 199 | QString offsetY1 () { return QString::number(_offsetY[0], 'f', 2); } 200 | bool visible1 () { return _visible[0]; } 201 | QStringList colorList1 () { return _available_colorList; } 202 | // Row 2 203 | QStringList tableList2 () { return _tableList2; } 204 | QStringList fieldList2 () { return _fieldList[1]; } 205 | QString scale2 () { return QString::number(_scale[1], 'f', 3); } 206 | QString offsetX2 () { return QString::number(_offsetX[1]); } 207 | QString offsetY2 () { return QString::number(_offsetY[1], 'f', 2); } 208 | bool visible2 () { return _visible[1]; } 209 | QStringList colorList2 () { return _available_colorList; } 210 | // Row 3 211 | QStringList tableList3 () { return _tableList3; } 212 | QStringList fieldList3 () { return _fieldList[2]; } 213 | QString scale3 () { return QString::number(_scale[2], 'f', 3); } 214 | QString offsetX3 () { return QString::number(_offsetX[2]); } 215 | QString offsetY3 () { return QString::number(_offsetY[2], 'f', 2); } 216 | bool visible3 () { return _visible[2]; } 217 | QStringList colorList3 () { return _available_colorList; } 218 | // Row 4 219 | QStringList tableList4 () { return _tableList4; } 220 | QStringList fieldList4 () { return _fieldList[3]; } 221 | QString scale4 () { return QString::number(_scale[3], 'f', 3); } 222 | QString offsetX4 () { return QString::number(_offsetX[3]); } 223 | QString offsetY4 () { return QString::number(_offsetY[3], 'f', 2); } 224 | bool visible4 () { return _visible[3]; } 225 | QStringList colorList4 () { return _available_colorList; } 226 | // Row 5 227 | QStringList tableList5 () { return _tableList5; } 228 | QStringList fieldList5 () { return _fieldList[4]; } 229 | QString scale5 () { return QString::number(_scale[4], 'f', 3); } 230 | QString offsetX5 () { return QString::number(_offsetX[4]); } 231 | QString offsetY5 () { return QString::number(_offsetY[4], 'f', 2); } 232 | bool visible5 () { return _visible[4]; } 233 | QStringList colorList5 () { return _available_colorList; } 234 | // Row 6 235 | QStringList tableList6 () { return _tableList6; } 236 | QStringList fieldList6 () { return _fieldList[5]; } 237 | QString scale6 () { return QString::number(_scale[5], 'f', 3); } 238 | QString offsetX6 () { return QString::number(_offsetX[5]); } 239 | QString offsetY6 () { return QString::number(_offsetY[5], 'f', 2); } 240 | bool visible6 () { return _visible[5]; } 241 | QStringList colorList6 () { return _available_colorList; } 242 | // Row 7 243 | QStringList tableList7 () { return _tableList7; } 244 | QStringList fieldList7 () { return _fieldList[6]; } 245 | QString scale7 () { return QString::number(_scale[6], 'f', 3); } 246 | QString offsetX7 () { return QString::number(_offsetX[6]); } 247 | QString offsetY7 () { return QString::number(_offsetY[6], 'f', 2); } 248 | bool visible7 () { return _visible[6]; } 249 | QStringList colorList7 () { return _available_colorList; } 250 | // Row 8 251 | QStringList tableList8 () { return _tableList8; } 252 | QStringList fieldList8 () { return _fieldList[7]; } 253 | QString scale8 () { return QString::number(_scale[7], 'f', 3); } 254 | QString offsetX8 () { return QString::number(_offsetX[7]); } 255 | QString offsetY8 () { return QString::number(_offsetY[7], 'f', 2); } 256 | bool visible8 () { return _visible[7]; } 257 | QStringList colorList8 () { return _available_colorList; } 258 | // Row 9 259 | QStringList tableList9 () { return _tableList9; } 260 | QStringList fieldList9 () { return _fieldList[8]; } 261 | QString scale9 () { return QString::number(_scale[8], 'f', 3); } 262 | QString offsetX9 () { return QString::number(_offsetX[8]); } 263 | QString offsetY9 () { return QString::number(_offsetY[8], 'f', 2); } 264 | bool visible9 () { return _visible[8]; } 265 | QStringList colorList9 () { return _available_colorList; } 266 | // Row 10 267 | QStringList tableList10 () { return _tableList10; } 268 | QStringList fieldList10 () { return _fieldList[9]; } 269 | QString scale10 () { return QString::number(_scale[9], 'f', 3); } 270 | QString offsetX10 () { return QString::number(_offsetX[9]); } 271 | QString offsetY10 () { return QString::number(_offsetY[9], 'f', 2); } 272 | bool visible10 () { return _visible[9]; } 273 | QStringList colorList10 () { return _available_colorList; } 274 | 275 | signals: 276 | void lineListChanged (); 277 | void colorListChanged (); 278 | // Row 1 279 | void tableList1Changed (); 280 | void fieldList1Changed (); 281 | void scale1Changed (); 282 | void offsetX1Changed (); 283 | void offsetY1Changed (); 284 | void visible1Changed (); 285 | void colorList1Changed (); 286 | // Row 2 287 | void tableList2Changed (); 288 | void fieldList2Changed (); 289 | void scale2Changed (); 290 | void offsetX2Changed (); 291 | void offsetY2Changed (); 292 | void visible2Changed (); 293 | void colorList2Changed (); 294 | // Row 3 295 | void tableList3Changed (); 296 | void fieldList3Changed (); 297 | void scale3Changed (); 298 | void offsetX3Changed (); 299 | void offsetY3Changed (); 300 | void visible3Changed (); 301 | void colorList3Changed (); 302 | // Row 4 303 | void tableList4Changed (); 304 | void fieldList4Changed (); 305 | void scale4Changed (); 306 | void offsetX4Changed (); 307 | void offsetY4Changed (); 308 | void visible4Changed (); 309 | void colorList4Changed (); 310 | // Row 5 311 | void tableList5Changed (); 312 | void fieldList5Changed (); 313 | void scale5Changed (); 314 | void offsetX5Changed (); 315 | void offsetY5Changed (); 316 | void visible5Changed (); 317 | void colorList5Changed (); 318 | // Row 6 319 | void tableList6Changed (); 320 | void fieldList6Changed (); 321 | void scale6Changed (); 322 | void offsetX6Changed (); 323 | void offsetY6Changed (); 324 | void visible6Changed (); 325 | void colorList6Changed (); 326 | // Row 7 327 | void tableList7Changed (); 328 | void fieldList7Changed (); 329 | void scale7Changed (); 330 | void offsetX7Changed (); 331 | void offsetY7Changed (); 332 | void visible7Changed (); 333 | void colorList7Changed (); 334 | // Row 8 335 | void tableList8Changed (); 336 | void fieldList8Changed (); 337 | void scale8Changed (); 338 | void offsetX8Changed (); 339 | void offsetY8Changed (); 340 | void visible8Changed (); 341 | void colorList8Changed (); 342 | // Row 9 343 | void tableList9Changed (); 344 | void fieldList9Changed (); 345 | void scale9Changed (); 346 | void offsetX9Changed (); 347 | void offsetY9Changed (); 348 | void visible9Changed (); 349 | void colorList9Changed (); 350 | // Row 10 351 | void tableList10Changed (); 352 | void fieldList10Changed (); 353 | void scale10Changed (); 354 | void offsetX10Changed (); 355 | void offsetY10Changed (); 356 | void visible10Changed (); 357 | void colorList10Changed (); 358 | 359 | void clear_alreadyPloted (); 360 | void plotGraph (QString tables, 361 | QString fields, 362 | int offsetX, 363 | double offsetY, 364 | double scale, 365 | int linestyle, 366 | int color, 367 | bool visible, 368 | bool from); // false:DataAnalyzeController,true:Other 369 | void clearGraph (); 370 | 371 | private slots: 372 | void _setTableList(QString table); 373 | 374 | private: 375 | bool _isNumber(QString n); 376 | void _plot(); 377 | void _lineStyle(int index, int i); 378 | void _update_colorList(); 379 | void _update_hide_tables(QString table, uint8_t idx); 380 | bool _visible[MAX_LINE_NUM]; 381 | QStringList _tableList; 382 | QStringList _tableList1; 383 | QStringList _tableList2; 384 | QStringList _tableList3; 385 | QStringList _tableList4; 386 | QStringList _tableList5; 387 | QStringList _tableList6; 388 | QStringList _tableList7; 389 | QStringList _tableList8; 390 | QStringList _tableList9; 391 | QStringList _tableList10; 392 | QStringList _lineList; 393 | QStringList _colorList; 394 | QStringList _available_colorList; 395 | QStringList _fieldList[MAX_LINE_NUM]; 396 | float _scale[MAX_LINE_NUM]; 397 | int _offsetX[MAX_LINE_NUM]; 398 | float _offsetY[MAX_LINE_NUM]; 399 | int _style[MAX_LINE_NUM]; 400 | int _color[MAX_LINE_NUM]; 401 | 402 | public: 403 | QString tables[MAX_LINE_NUM]; 404 | QString fields[MAX_LINE_NUM]; 405 | 406 | public slots: 407 | Q_INVOKABLE void init (); 408 | }; 409 | 410 | #endif // DATAANALYZECONTROLLER_H 411 | --------------------------------------------------------------------------------