参考资源
Vodka: VOFA+ Plugins - Gitee.com
VOFA+ 5分钟实现 数据通信、波形显示_vofa使用教程-CSDN博客
效果展示
一、下载官方结构程序

复制为自己需要的,然后在基础上修改
gpchc.h
cpp
#ifndef GPCHC_H
#define GPCHC_H
#include "dataengineinterface.h"
class GPCHC : public QObject, public DataEngineInterface
{
Q_OBJECT
Q_INTERFACES(DataEngineInterface)
// 插件唯一ID,需与FireWater区分
Q_PLUGIN_METADATA(IID "VOFA+.Plugin.GPCHC")
public:
explicit GPCHC();
~GPCHC();
void ProcessingDatas(char *data, int count);
private:
// 复用原变量(图片通道扩充计数),若不需要图片解析可保留
uint32_t image_count_mutation_count_ = 0;
// 辅助函数:解析单帧$GPCHC数据
bool ParseGPCHCFrame(const QString &frameStr, QVector<float> &datas);
};
#endif // GPCHC_H
gpchc.cpp
cpp
#include "gpchc.h"
#include <QString>
#include <QStringList>
#include <QRegExp>
#include <QDebug>
GPCHC::GPCHC()
{
}
GPCHC::~GPCHC()
{
}
bool GPCHC::ParseGPCHCFrame(const QString &frameStr, QVector<float> &datas)
{
datas.clear();
// 严格校验帧头
if (!frameStr.startsWith("$GPCHC,")) {
qDebug() << "无效帧头:" << frameStr;
return false;
}
// 分离校验和(*后的内容)
int starIndex = frameStr.indexOf('*');
QString validFramePart;
if (starIndex != -1) {
validFramePart = frameStr.left(starIndex);
} else {
validFramePart = frameStr; // 无校验和时使用完整帧(容错处理)
}
// 修正:使用正确的枚举值(QString::KeepEmptyParts)
QStringList fields = validFramePart.split(',', QString::KeepEmptyParts);
// 校验字段数量(至少24个字段,索引0-23)
if (fields.size() < 24) {
qDebug() << "字段数量不足,实际:" << fields.size() << ",帧内容:" << validFramePart;
return false;
}
bool convertOk;
float fieldValue;
// 按索引提取有效字段(增加越界检查)
auto appendField = [&](int index) {
if (index >= fields.size()) {
qDebug() << "字段索引越界:" << index;
return;
}
QString field = fields[index].trimmed(); // 去除首尾空白字符
if (field.isEmpty()) return; // 跳过空字段
fieldValue = field.toFloat(&convertOk);
if (convertOk) {
datas.append(fieldValue);
} else {
qDebug() << "字段转换失败,索引:" << index << ",值:" << field;
}
};
// 依次提取需要的字段(索引1-23中有效字段)
appendField(1); // GPSWeek
appendField(2); // GPSTime
appendField(3); // Heading
appendField(4); // Pitch
appendField(5); // Roll
appendField(6); // gyro x
appendField(7); // gyro y
appendField(8); // gyro z
appendField(9); // acc x
appendField(10); // acc y
appendField(11); // acc z
appendField(14); // Altitude
appendField(15); // Ve
appendField(16); // Vn
appendField(17); // Vu
appendField(18); // V
appendField(19); // NSV1
appendField(20); // NSV2
appendField(21); // Status
appendField(22); // Age
appendField(23); // Warning
return !datas.isEmpty();
}
void GPCHC::ProcessingDatas(char *data, int count)
{
frame_list_.clear();
if (count <= 0 || data == nullptr) {
return;
}
// 修正:使用fromLatin1替代fromAscii(Qt5.13中fromAscii已废弃)
QString rawData = QString::fromLatin1(data, count);
// 修正:使用正确的枚举值(QString::SkipEmptyParts)
QStringList frameList = rawData.split(QRegExp("\r?\n"), QString::SkipEmptyParts);
for (const QString &singleFrame : frameList) {
Frame currentFrame;
currentFrame.is_valid_ = false;
currentFrame.image_size_ = 0;
QVector<float> frameDatas;
if (ParseGPCHCFrame(singleFrame, frameDatas)) {
currentFrame.datas_ = frameDatas;
currentFrame.is_valid_ = true;
}
// 修正帧索引计算(基于原始数据中的实际位置)
int start = rawData.indexOf(singleFrame);
currentFrame.start_index_ = (start != -1) ? start : 0;
currentFrame.end_index_ = currentFrame.start_index_ + singleFrame.length() - 1;
frame_list_.append(currentFrame);
}
}
gpchc.json
cs
{
"name": "GPCHC",
"description": "GPCHC数据解析插件,用于解析$GPCHC格式的GPS/IMU数据帧",
"version": "1.0.0",
"author": "",
"pluginId": "VOFA+.Plugin.GPCHC",
"dataFormat": {
"frameHeader": "$GPCHC,",
"frameFooter": "\r\n",
"checksumEnabled": true,
"encoding": "ASCII"
},
"fields": [
{"index": 1, "name": "GPSWeek", "unit": "", "description": "GPS周数"},
{"index": 2, "name": "GPSTime", "unit": "s", "description": "GPS时间"},
{"index": 3, "name": "Heading", "unit": "°", "description": "航向角"},
{"index": 4, "name": "Pitch", "unit": "°", "description": "俯仰角"},
{"index": 5, "name": "Roll", "unit": "°", "description": "横滚角"},
{"index": 6, "name": "GyroX", "unit": "°/s", "description": "陀螺仪X轴数据"},
{"index": 7, "name": "GyroY", "unit": "°/s", "description": "陀螺仪Y轴数据"},
{"index": 8, "name": "GyroZ", "unit": "°/s", "description": "陀螺仪Z轴数据"},
{"index": 9, "name": "AccX", "unit": "m/s²", "description": "加速度计X轴数据"},
{"index": 10, "name": "AccY", "unit": "m/s²", "description": "加速度计Y轴数据"},
{"index": 11, "name": "AccZ", "unit": "m/s²", "description": "加速度计Z轴数据"},
{"index": 14, "name": "Altitude", "unit": "m", "description": "高度"},
{"index": 15, "name": "Ve", "unit": "m/s", "description": "东向速度"},
{"index": 16, "name": "Vn", "unit": "m/s", "description": "北向速度"},
{"index": 17, "name": "Vu", "unit": "m/s", "description": "天向速度"},
{"index": 18, "name": "V", "unit": "m/s", "description": "合速度"},
{"index": 19, "name": "NSV1", "unit": "", "description": "卫星数量1"},
{"index": 20, "name": "NSV2", "unit": "", "description": "卫星数量2"},
{"index": 21, "name": "Status", "unit": "", "description": "状态码"},
{"index": 22, "name": "Age", "unit": "s", "description": "数据龄期"},
{"index": 23, "name": "Warning", "unit": "", "description": "警告标志"}
],
"minFieldsCount": 24,
"notes": "解析逻辑遵循$GPCHC帧格式,包含校验和处理及字段越界检查"
}
cs
# 核心:移除GUI模块,编译为插件库
QT -= gui
TEMPLATE = lib
CONFIG += plugin
TARGET = gpchc # 插件名称(需与JSON的name一致)
# 导出宏(配合头文件Q_DECL_EXPORT/IMPORT)
DEFINES += GPCHC_LIBRARY
# 源文件/头文件(保留核心引用)
SOURCES += gpchc.cpp
HEADERS += \
gpchc.h \
../shared/dataengineinterface.h
# 头文件搜索路径(确保能找到公共接口)
INCLUDEPATH += ../shared/
# ========== 简化核心:统一输出到构建目录 ==========
# 插件库输出路径:构建目录下的 plugins/dataengines
DESTDIR = $$OUT_PWD/plugins/dataengines
# 临时构建文件(obj等)输出路径(可选,保持整洁)
OBJECTS_DIR = $$OUT_PWD/obj
MOC_DIR = $$OUT_PWD/moc
RCC_DIR = $$OUT_PWD/rcc
# ========== 自动拷贝JSON配置到插件输出目录 ==========
# 确保JSON文件和插件库在同一目录(VOFA+识别插件必需)
json.files = gpchc.json
json.path = $$DESTDIR
COPIES += json
# ========== 移除自动部署逻辑(改为手动拷贝) ==========
# 注释/删除所有win32/macx/unix的INSTALLS部署规则
# 后续只需拷贝 $$OUT_PWD/plugins 整个文件夹到VOFA+的plugins目录即可
二、下载安装QT
【免费下载】 Qt 5.13.2 开源版 Windows x86 安装包-CSDN博客

导入项目发现需要下载VS2017

Visual Studio 2017下载地址和安装教程(图解版)_visual studio 2017官方下载-CSDN博客

安装完成后,即可加载

点击构建即可生成动态链接文件,文件一般位于dataengines/build-xxxx...-release/release

将生成的文件拷贝到vofa对应位置


重新启动vofa,选中自己的解析,即可

