├── .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 |

5 |
6 | 2. 加载`\*.db`数据库文件
7 | >
\*.db 数据库文件来源于ArduPilotLog软件:File -> Save DB file
8 | >
*.db 的加载速度比 *.bin 快**数十倍**,且文件越大加载速度差别越明显。
9 |
10 |

11 |
12 | 3. 使用载入 MATLAB 数据的方法
13 | > 例:att.Roll
14 |
15 |

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 |

10 |

11 |
12 | 三种绘图方法
13 | ---
14 | 1. 点选绘图
15 | > “点选绘图”在不明确看哪一种日志数据时使用。
16 | > 比如:对于飞机的某种奇怪表现,往往不能立刻明确是哪里出了问题,这时怀疑对象泛围大,用点选方式为猜测快速提供依据。
17 |
18 | 
19 |
20 | 2. 数据分析窗(点选绘图Plus)
21 | > “数据分析窗”在小泛围锁定目标数据时使用。
22 | > 比如:通过点选绘图已初步锁定某种现象与某几类数据表现相关`(注:少于10类)`,此时须要精确绘图。
23 |
24 |
打开“数据分析窗”
25 |

26 |
“数据分析窗”说明
27 |

28 |
通过“数据分析窗”绘图
29 |

30 |
31 | 3. 脚本绘图
32 | > “脚本绘图”用于反复验证已锁定的目标数据是否合理的情形。
33 | > 将与当前问题有关的数据锁定后,往往需要多次采集 Log 日志,重复分析以便证实。这时使用脚本绘图比较方便,以免去每次点选、调整比例等麻烦。
34 |
35 |
加载脚本
36 |

37 |

38 |
39 |
脚本语法如下图(也可见 **[conf/ReadMe.md](https://github.com/SuWeipeng/ArduPilotLog/blob/master/conf/ReadMe.md)**)
40 | 
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 |

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 |

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