Q工控仪器程序框架设计详解(工控)

Q工控仪器程序框架设计详解(工控)

适用场景:测试仪器、实验室设备、工业检测系统、数据采集分析

核心目标:看完即懂、复制即用、扩展无忧


前言:为什么要这样设计?

在设计测试仪器类应用程序时,开发者通常面临以下挑战:

  • 模块耦合严重:通讯、计算、存储、界面代码纠缠在一起,改一处动全身
  • 难以测试:没有清晰的接口抽象,单元测试无从下手
  • 扩展困难:新增一种通讯协议或分析算法,需要改动大量代码
  • 状态混乱:多线程环境下的状态管理容易出错
  • 配置分散:参数配置分散在代码各处,修改困难

本框架的设计目标 就是解决这些问题。通过模块化分层设计模式应用接口抽象,让代码结构清晰、职责明确、易于测试和扩展。

设计原则

本框架遵循以下设计原则:

  1. 单一职责原则(SRP):每个模块只做一件事,且做好
  2. 依赖倒置原则(DIP):高层模块不依赖低层模块,都依赖抽象
  3. 开闭原则(OCP):对扩展开放,对修改封闭
  4. 接口隔离原则(ISP):使用多个专用接口比使用一个通用接口更好

一、整体架构总览

1.1 为什么需要分层架构?

在小型项目中,把所有代码堆在一起也许能工作。但随着项目规模增长,这种方式会变成噩梦:

复制代码
❌ 混乱的架构(单体式)
┌──────────────────────────────────────┐
│         MainWindow.cpp (5000行)       │
│  ┌─────────────────────────────────┐ │
│  │ 通讯代码、参数代码、计算代码、   │ │
│  │ 存储代码、UI代码全部混在一起    │ │
│  └─────────────────────────────────┘ │
└──────────────────────────────────────┘

✅ 清晰的架构(分层模块化)
┌──────────────────────────────────────┐
│          UI Layer(界面层)           │
├──────────────────────────────────────┤
│       Business Layer(业务层)         │
│  ┌────────┐ ┌─────────┐ ┌────────┐ │
│  │参数配置│ │分析计算 │ │保存解析│ │
│  └────────┘ └─────────┘ └────────┘ │
├──────────────────────────────────────┤
│         Core Layer(核心层)          │
│  ┌────────┐ ┌─────────┐ ┌────────┐ │
│  │命令队列│ │协议解析 │ │数据缓冲│ │
│  └────────┘ └─────────┘ └────────┘ │
├──────────────────────────────────────┤
│       Hardware Layer(硬件层)        │
│         通讯接口(串口/网口/USB)     │
└──────────────────────────────────────┘

分层的核心思想:每一层只知道自己上下相邻的层,不知道更远的层。这样修改某一层不会影响其他层。

1.2 分层设计详解

复制代码
┌──────────────────────────────────────────────────────────────┐
│                        UI Layer                               │
│              Qt Widgets / QML / 自定义控件                    │
│                                                              │
│  职责:展示数据、接收用户输入、协调业务逻辑                     │
│  特点:被动视图,通过观察者模式订阅数据变化                     │
├──────────────────────────────────────────────────────────────┤
│                     Business Layer                            │
│  ┌─────────────┐ ┌──────────────┐ ┌─────────────────────┐   │
│  │ 参数配置    │ │  分析计算    │ │     保存解析         │   │
│  │   Module    │ │   Module     │ │      Module          │   │
│  └─────────────┘ └──────────────┘ └─────────────────────┘   │
│                                                              │
│  职责:处理业务逻辑、管理业务状态、执行业务规则                 │
│  特点:可测试性强,不依赖UI层                                  │
├──────────────────────────────────────────────────────────────┤
│                       Core Layer                             │
│  ┌─────────────┐ ┌──────────────┐ ┌─────────────────────┐   │
│  │  命令队列   │ │   协议解析   │ │     数据缓冲         │   │
│  │  Manager    │ │   Parser     │ │      Buffer          │   │
│  └─────────────┘ └──────────────┘ └─────────────────────┘   │
│                                                              │
│  职责:提供基础设施服务,被业务层调用                          │
│  特点:通用性强,与具体业务无关                                │
├──────────────────────────────────────────────────────────────┤
│                    Hardware Layer                            │
│     通讯接口 (Serial / TCP / USB / GPIB / VISA)              │
│                                                              │
│  职责:与物理设备通信                                         │
│  特点:可能需要平台特定代码                                   │
└──────────────────────────────────────────────────────────────┘

1.3 模块职责总表

模块 职责 设计模式 所在层 为什么用这个模式
通讯模块 设备连接、数据收发、心跳检测、断线重连 桥接模式、策略模式、命令模式 Hardware 通讯方式可能多种多样(串口/网口/USB),需要抽象出统一接口
参数配置模块 参数加载、保存、校验、动态更新 单例模式、观察者模式、Memento模式 Business 全局配置只需要一份,参数变化时UI自动更新
分析计算模块 数据处理、算法执行、多线程计算 责任链模式、模板方法模式 Business 算法可能串联执行或替换,责任链模式最合适
UI模块 人机交互、数据可视化、实时刷新 MVC/MVP模式、组合模式 UI 界面与业务逻辑分离,方便测试和换肤
保存解析模块 文件/数据库读写、数据导入导出 工厂模式、策略模式、DAO模式 Business 存储格式可能变化,工厂模式屏蔽细节

1.4 模块依赖关系图

复制代码
                    ┌─────────────┐
                    │   通讯模块   │
                    │ (Serial/TCP)│
                    └──────┬──────┘
                           │
           ┌───────────────┼───────────────┐
           │               │               │
           ▼               ▼               ▼
    ┌────────────┐  ┌────────────┐  ┌────────────┐
    │  参数配置  │  │  分析计算  │  │  保存解析  │
    │   Module   │  │   Module   │  │   Module   │
    └────────────┘  └─────┬──────┘  └────────────┘
                          │
                          ▼
                   ┌─────────────┐
                   │    UI模块    │
                   │MainWindow   │
                   └─────────────┘

依赖规则:箭头指向的方向是被依赖的模块。也就是说:

  • 分析计算模块依赖参数配置模块(从参数读取计算配置)
  • UI模块依赖所有业务模块(显示数据、发送控制)

二、核心模块基类设计

2.1 为什么要定义统一的模块基类?

想象一下,如果每个模块都有自己的初始化、启动、停止方式,会发生什么?

cpp 复制代码
// ❌ 没有统一接口
class CommModule {
    void init(); void run(); void halt(); // 接口不一致
};

class AnalysisModule {
    bool setup(); void start(); void end(); // 又不一样!
};

class StorageModule {
    void initialize(); void execute(); void destroy(); // 第三个版本
};

// 调用代码要记住每个模块的不同方法
comm.init(); comm.run();
analysis.setup(); analysis.start();
storage.initialize(); storage.execute();

如果我们定义一个统一的接口:

cpp 复制代码
// ✅ 统一接口的好处
class ModuleInterface {
    virtual bool initialize() = 0;  // 初始化
    virtual bool start() = 0;        // 启动
    virtual bool stop() = 0;        // 停止
    virtual void cleanup() = 0;     // 清理资源
};

这样,模块管理器就可以用统一的方式管理所有模块,代码简洁得多。

2.2 设计思路:状态机模式

每个模块都有生命周期,状态机是最适合描述生命周期的方式:

复制代码
                    ┌───────────────┐
                    │  Uninitialized │ ← 初始状态
                    └───────┬───────┘
                            │ initialize()
                            ▼
                    ┌───────────────┐
                    │   Initialized  │ ← 可以启动了
                    └───────┬───────┘
                            │ start()
                            ▼
                    ┌───────────────┐
              ┌─────│    Running     │ ← 正常工作中
              │     └───────┬───────┘
              │             │
              │     stop()  │  pause()
              ▼             ▼
    ┌─────────────┐  ┌───────────────┐
    │   Stopped    │  │    Paused     │
    └─────────────┘  └───────┬───────┘
                             │
                      resume()│
                             ▼
                      (回到Running)

为什么要设计状态机?

  1. 防止非法操作:例如在未初始化的状态下启动,会被状态机拒绝
  2. 便于调试:出了问题,看一眼状态就知道卡在哪里了
  3. 优雅关闭:stop()会等待当前操作完成,而不是强行杀死线程

2.3 完整代码实现(带详细注释)

cpp 复制代码
// ============================================================
// ModuleInterface.h - 模块接口定义
// ============================================================
// 设计思路:
// 1. 所有业务模块都实现这个接口,保证接口一致性
// 2. 使用信号槽传递状态变化,方便UI层监听
// 3. 状态机管理模块生命周期,防止非法操作
// ============================================================

#pragma once
#include <QObject>
#include <QString>

// --------------------------------------------------------
// 模块状态枚举
// --------------------------------------------------------
// 设计理由:状态机模式让模块的生命周期管理变得清晰。
// 每个状态都有明确的含义,状态转换有规律可循。
// --------------------------------------------------------
enum class ModuleState {
    Uninitialized, // 刚创建,还未初始化
    Initialized,    // 已初始化,可以启动
    Starting,       // 正在启动(异步启动时用到)
    Running,        // 运行中
    Paused,         // 已暂停(可恢复)
    Stopping,       // 正在停止
    Error,          // 发生错误
    Stopped         // 已停止(可重新初始化)
};

// --------------------------------------------------------
// 错误信息结构
// --------------------------------------------------------
// 设计理由:统一错误格式,方便日志记录和错误追踪。
// 包含:错误码、消息、所属模块、时间戳
// --------------------------------------------------------
struct ErrorInfo {
    int code;           // 错误码:0表示无错误
    QString message;     // 人类可读的错误描述
    QString module;      // 出错的模块名称
    QDateTime time;     // 发生时间
};

// --------------------------------------------------------
// 模块接口抽象基类
// --------------------------------------------------------
// 设计理由:
// 1. 抽象基类定义接口,子类实现具体逻辑
// 2. Q_OBJECT支持信号槽,用于状态变化通知
// 3. virtual destructor保证子类正确析构
// --------------------------------------------------------
class ModuleInterface {
public:
    virtual ~ModuleInterface() = default;

    // ----- 标识 -----
    // 模块名称,用于在管理器中唯一标识
    virtual QString name() const = 0;

    // ----- 生命周期管理 -----
    // initialize(): 分配资源、加载配置、建立连接
    // start(): 开始工作(启动线程、开启定时器等)
    // stop(): 优雅停止(等待任务完成、保存状态)
    // pause()/resume(): 暂停/恢复(可选项)
    // cleanup(): 释放所有资源
    virtual bool initialize() = 0;
    virtual bool start() = 0;
    virtual bool stop() = 0;
    virtual bool pause() = 0;
    virtual bool resume() = 0;
    virtual void cleanup() = 0;

    // ----- 状态查询 -----
    virtual ModuleState state() const = 0;

    // ----- 依赖声明 -----
    // 返回本模块依赖的其他模块名称
    // 模块管理器会根据这个自动排序启动顺序
    // 例如:分析模块依赖参数配置模块
    virtual QStringList dependencies() const { return {}; }

    // ----- 信号定义 -----
    // 状态变化信号:通知观察者(UI)状态改变了
    // 为什么要传两个状态?方便追踪状态变化历史
    signals:
    void stateChanged(ModuleState newState, ModuleState oldState);

    // 错误信号:通知上层发生了错误
    void errorOccurred(const ErrorInfo &error);

    // 进度信号:长时间操作时报告进度
    void progressChanged(int percent, const QString &message);

protected:
    // 状态变量,供子类访问
    ModuleState m_state = ModuleState::Uninitialized;
};

2.4 模块管理器实现

cpp 复制代码
// ============================================================
// ModuleManager.h - 模块管理器
// ============================================================
// 设计思路:
// 1. 单例模式:整个程序只需要一个管理器
// 2. 自动依赖排序:用拓扑排序确定启动顺序
// 3. 批量操作:初始化/启动/停止一次性完成
// ============================================================

#pragma once
#include "ModuleInterface.h"
#include <QHash>
#include <QVector>
#include <QMutex>

class ModuleManager : public QObject {
    Q_OBJECT
    // Qt单例宏:禁用拷贝构造和赋值运算符
    Q_DECLARE_SINGLETON(ModuleManager)

public:
    // ----------------------------------------
    // 注册模块
    // ----------------------------------------
    // 设计理由:在初始化前先注册所有模块
    // 这样管理器知道有哪些模块需要管理
    // ----------------------------------------
    bool registerModule(ModuleInterface *module);

    // ----------------------------------------
    // 批量初始化
    // ----------------------------------------
    // 设计理由:自动按依赖顺序初始化
    // 例如:B依赖A,则A一定在B之前初始化
    // 返回值:false表示有模块初始化失败
    // ----------------------------------------
    bool initializeAll();

    // ----------------------------------------
    // 批量启动
    // ----------------------------------------
    // 注意:启动顺序与初始化顺序相同
    // ----------------------------------------
    bool startAll();

    // ----------------------------------------
    // 批量停止(逆序)
    // ----------------------------------------
    // 设计理由:逆序停止很重要
    // 例如:UI依赖数据,如果先停数据再停UI会出问题
    // 逆序保证依赖者先停止,提供者后停止
    // ----------------------------------------
    void stopAll();

    // ----------------------------------------
    // 获取模块
    // ----------------------------------------
    // 模板方法:通过名称安全获取模块
    // 如果类型不匹配返回nullptr
    // ----------------------------------------
    template<typename T>
    T* getModule(const QString &name) const {
        auto it = m_modules.find(name);
        if (it != m_modules.end()) {
            return qobject_cast<T*>(it.value());
        }
        return nullptr;
    }

    // ----------------------------------------
    // 便捷方法:获取启动顺序
    // ----------------------------------------
    // 用于调试:看看模块按什么顺序启动的
    // ----------------------------------------
    QVector<QString> startupOrder() const { return m_startupOrder; }

signals:
    void moduleRegistered(const QString &name);
    void allModulesInitialized();
    void allModulesStarted();
    void allModulesStopped();
    void errorOccurred(const QString &moduleName, const ErrorInfo &error);

private:
    explicit ModuleManager(QObject *parent = nullptr) : QObject(parent) {}
    ~ModuleManager() override { stopAll(); }

    // 拓扑排序:计算模块启动顺序
    // 图算法:深度优先搜索 + 入度统计
    // 保证:如果A依赖B,则B在A之前
    QVector<QString> topologicalSort();

    QHash<QString, ModuleInterface*> m_modules;
    QVector<QString> m_startupOrder;
};
cpp 复制代码
// ModuleManager.cpp

#include "ModuleManager.h"
#include <QDebug>

// ----------------------------------------
// 模块注册
// ----------------------------------------
bool ModuleManager::registerModule(ModuleInterface *module)
{
    if (!module) return false;

    const QString name = module->name();
    if (m_modules.contains(name)) {
        qWarning() << "Module already registered:" << name;
        return false;
    }

    m_modules[name] = module;

    // 监听模块的错误信号,统一处理
    connect(module, &ModuleInterface::errorOccurred,
            this, [this, name](const ErrorInfo &e) {
                emit errorOccurred(name, e);
            });

    qDebug() << "Module registered:" << name;
    emit moduleRegistered(name);
    return true;
}

// ----------------------------------------
// 拓扑排序实现
// ----------------------------------------
// 算法思路(DFS + 后序遍历):
// 1. 对每个模块进行DFS
// 2. 先处理依赖的模块(递归)
// 3. 最后把自己加入结果
// 这样得到的就是从依赖到被依赖的顺序
// ----------------------------------------
QVector<QString> ModuleManager::topologicalSort()
{
    QVector<QString> result;
    QSet<QString> visited;    // 已完成
    QSet<QString> visiting;  // 正在访问(用于检测循环依赖)

    std::function<bool(const QString&)> dfs = [&](const QString &name) -> bool {
        // 检测循环依赖:A依赖B,B依赖A会死循环
        if (visiting.contains(name)) {
            qFatal("Circular dependency detected: %s", qPrintable(name));
            return false;
        }

        // 已处理过,跳过
        if (visited.contains(name)) return true;

        // 标记为正在访问
        visiting.insert(name);

        // 递归处理所有依赖
        auto module = m_modules.value(name);
        if (module) {
            for (const QString &dep : module->dependencies()) {
                if (!dfs(dep)) return false;
            }
        }

        // 离开节点
        visiting.remove(name);
        visited.insert(name);

        // 关键:把自己加入结果(在依赖之后)
        result.append(name);
        return true;
    };

    // 遍历所有未处理的模块
    for (const QString &name : m_modules.keys()) {
        if (!visited.contains(name)) {
            if (!dfs(name)) return {};
        }
    }

    return result;
}

// ----------------------------------------
// 批量初始化
// ----------------------------------------
bool ModuleManager::initializeAll()
{
    // 计算启动顺序(拓扑排序)
    m_startupOrder = topologicalSort();

    // 排序失败(可能有循环依赖)
    if (m_startupOrder.isEmpty() && !m_modules.isEmpty()) {
        qCritical() << "Topological sort failed (circular dependency?)";
        return false;
    }

    // 按顺序初始化
    for (const QString &name : m_startupOrder) {
        auto module = m_modules.value(name);
        if (module && module->state() == ModuleState::Uninitialized) {
            qDebug() << "Initializing module:" << name;
            if (!module->initialize()) {
                qCritical() << "Failed to initialize module:" << name;
                return false;
            }
        }
    }

    emit allModulesInitialized();
    qDebug() << "All modules initialized";
    return true;
}

// ----------------------------------------
// 批量启动
// ----------------------------------------
bool ModuleManager::startAll()
{
    for (const QString &name : m_startupOrder) {
        auto module = m_modules.value(name);
        if (module && module->state() == ModuleState::Initialized) {
            qDebug() << "Starting module:" << name;
            if (!module->start()) {
                qCritical() << "Failed to start module:" << name;
                return false;
            }
        }
    }

    emit allModulesStarted();
    qDebug() << "All modules started";
    return true;
}

// ----------------------------------------
// 批量停止(逆序)
// ----------------------------------------
void ModuleManager::stopAll()
{
    qDebug() << "Stopping all modules...";

    // 逆序停止:从最后一个启动的模块开始
    for (int i = m_startupOrder.size() - 1; i >= 0; --i) {
        const QString &name = m_startupOrder[i];
        auto module = m_modules.value(name);

        if (module && module->state() == ModuleState::Running) {
            qDebug() << "Stopping module:" << name;
            module->stop();
        }
    }

    emit allModulesStopped();
    qDebug() << "All modules stopped";
}

三、通讯模块设计

3.1 为什么通讯模块是框架中最复杂的部分?

通讯模块面临以下挑战:

复制代码
❌ 问题1:多种通讯方式
┌─────────────────────────────────────┐
│  仪器A (串口)  │  仪器B (网口)      │
│  仪器C (USB)   │  仪器D (GPIB)      │
└─────────────────────────────────────┘
如果每个仪器都写一套代码,那要疯掉

❌ 问题2:连接不稳定
┌─────────────────────────────────────┐
│  串口松动 │ 网线拔掉 │ USB断开        │
└─────────────────────────────────────┘
需要自动重连,但不能无限重试

❌ 问题3:数据粘包
┌─────────────────────────────────────┐
│  发送:A B C D   接收:A B C D (理想) │
│  发送:A B C D   接收:A B │ C D (粘包)│
│  发送:A B C D   接收:A │ B C D (半包)│
└─────────────────────────────────────┘
需要协议解析器处理

❌ 问题4:跨线程数据传递
┌─────────────────────────────────────┐
│  通讯线程 ──数据──→ 主线程UI显示     │
└─────────────────────────────────────┘
需要线程安全机制

3.2 设计模式选择

策略模式:为不同的通讯方式(串口、TCP、USB)定义统一接口

桥接模式:将"通讯方式"的抽象与"数据收发"的实现分离

复制代码
          ┌─────────────────┐
          │ ICommInterface  │ ← 抽象部分
          │ (接口)          │
          └────────┬────────┘
                   │
    ┌──────────────┼──────────────┐
    │              │              │
    ▼              ▼              ▼
┌────────┐   ┌────────┐   ┌────────┐
│ Serial │   │  TCP   │   │  USB   │ ← 实现部分
│  Comm  │   │  Comm  │   │  Comm  │
└────────┘   └────────┘   └────────┘

好处

  • 新增通讯方式只需实现接口,不影响其他代码
  • UI层只依赖接口,不知道具体是哪种通讯方式
  • 方便写单元测试(可以mock接口)

3.3 通讯配置设计

cpp 复制代码
// ============================================================
// 通讯配置基类
// ============================================================
// 设计思路:
// 1. 所有通讯配置都继承这个基类
// 2. clone()方法用于创建配置副本
// 3. 具体配置类只包含自己需要的参数
// ============================================================

#pragma once
#include <QString>

class CommConfig {
public:
    virtual ~CommConfig() = default;

    // 深拷贝:创建配置的副本
    // 为什么需要?通讯模块会持有配置,
    // 但UI层可能随时修改配置,需要复制一份
    virtual CommConfig* clone() const = 0;
};

// ============================================================
// 串口配置
// ============================================================
// 设计理由:
// 1. 串口参数很多,用结构体封装更清晰
// 2. 提供合理的默认值(115200是最常用的波特率)
// ============================================================

class SerialConfig : public CommConfig {
public:
    QString portName = "COM3";              // 串口名称
    qint32 baudRate = 115200;              // 波特率(默认115200)
    QSerialPort::DataBits dataBits = QSerialPort::Data8;  // 数据位(默认8位)
    QSerialPort::Parity parity = QSerialPort::NoParity;   // 校验(默认无)
    QSerialPort::StopBits stopBits = QSerialPort::OneStop; // 停止位(默认1位)
    int timeout = 3000;                    // 超时时间(毫秒)

    CommConfig* clone() const override {
        return new SerialConfig(*this);  // 拷贝构造函数
    }
};

3.4 通讯接口抽象

cpp 复制代码
// ============================================================
// ICommInterface.h - 通讯接口抽象基类
// ============================================================
// 设计思路:
// 1. 定义统一的通讯接口,所有通讯方式都实现这个接口
// 2. 使用信号槽处理异步数据到达
// 3. 连接状态变化通过信号通知
// ============================================================

#pragma once
#include "../Core/ModuleInterface.h"
#include <QByteArray>

class ICommInterface : public ModuleInterface {
    Q_OBJECT
public:
    // ----------------------------------------
    // 连接状态枚举
    // ----------------------------------------
    // 设计理由:连接状态是通讯模块最重要的状态
    // 枚举所有可能的状态,让状态机可以正确管理
    // ----------------------------------------
    enum class ConnectionState {
        Disconnected,   // 未连接
        Connecting,      // 正在连接
        Connected,       // 已连接
        Reconnecting,    // 正在重连
        Error           // 连接错误
    };
    Q_ENUM(ConnectionState)  // 让Qt的信号槽能传递这个枚举

    explicit ICommInterface(QObject *parent = nullptr)
        : ModuleInterface(parent) {}
    ~ICommInterface() override = default;

    // ----- 连接管理 -----
    // connect(): 根据配置连接设备
    // disconnect(): 断开连接
    virtual bool connect(const CommConfig *config) = 0;
    virtual void disconnect() = 0;

    // ----- 数据收发 -----
    // send(): 发送数据,返回是否发送成功
    virtual bool send(const QByteArray &data) = 0;

    // ----- 状态查询 -----
    virtual ConnectionState connectionState() const = 0;
    virtual CommConfig* currentConfig() const = 0;

    // ----- 信号定义 -----
    // 连接状态变化信号
    // UI层可以监听这个信号来更新连接按钮状态
    signals:
    void connectionStateChanged(ConnectionState state);

    // 数据到达信号
    // 这是通讯模块最重要的信号
    // 接收到的原始数据通过这个信号发出
    // 后续的解析由上层负责
    void dataReceived(const QByteArray &data);

    // 数据发送成功信号(用于统计发送速率)
    void bytesSent(qint64 bytes);

    // 错误信号
    void errorOccurred(const QString &error);
};

3.5 串口通讯实现(带详细注释)

cpp 复制代码
// SerialComm.h - 串口通讯实现

#pragma once
#include "ICommInterface.h"
#include <QSerialPort>
#include <QSerialPortInfo>

class SerialConfig;

class SerialComm : public ICommInterface {
    Q_OBJECT
public:
    explicit SerialComm(QObject *parent = nullptr);
    ~SerialComm() override;

    // ----- ModuleInterface 实现 -----
    QString name() const override { return "SerialComm"; }
    bool initialize() override;
    void cleanup() override;
    bool stop() override { disconnect(); return true; }
    bool pause() override { return true; }  // 串口不支持暂停

    // ----- ICommInterface 实现 -----
    bool connect(const CommConfig *config) override;
    void disconnect() override;
    bool send(const QByteArray &data) override;
    ConnectionState connectionState() const override { return m_connectionState; }
    CommConfig* currentConfig() const override { return m_config; }

    // ----- 便捷方法 -----
    // 扫描电脑上可用的串口
    static QStringList scanAvailablePorts();

private slots:
    // QSerialPort的readyRead信号处理
    // 当串口缓冲区有数据时自动调用
    // 注意:这个槽在串口自己的线程中执行
    void onReadyRead();

    // 错误处理
    void onErrorOccurred(QSerialPort::SerialPortError error);

private:
    // 状态更新辅助方法
    void setConnectionState(ConnectionState state);

    // 成员变量
    QSerialPort *m_port = nullptr;           // Qt串口对象
    SerialConfig *m_config = nullptr;         // 当前配置(持有所有权)
    ConnectionState m_connectionState = ConnectionState::Disconnected;
};
cpp 复制代码
// SerialComm.cpp

#include "SerialComm.h"
#include <QDebug>

// ----------------------------------------
// 构造函数
// ----------------------------------------
// 设计理由:在构造函数中创建QSerialPort对象
// QSerialPort是QObject,需要指定parent
// 这样析构时能自动清理
// ----------------------------------------
SerialComm::SerialComm(QObject *parent)
    : ICommInterface(parent)
    , m_port(new QSerialPort(this))  // 父对象为this,自动管理生命周期
{
    // 连接Qt SerialPort的信号到我们的槽
    // readyRead: 有数据可读时触发
    connect(m_port, &QSerialPort::readyRead,
            this, &SerialComm::onReadyRead);

    // errorOccurred: 发生错误时触发
    connect(m_port, &QSerialPort::errorOccurred,
            this, &SerialComm::onErrorOccurred);
}

SerialComm::~SerialComm()
{
    // 确保在析构时断开连接并清理
    disconnect();
    cleanup();
}

// ----------------------------------------
// 模块初始化
// ----------------------------------------
bool SerialComm::initialize()
{
    // 串口模块的初始化很简单,只需设置状态
    // 真正的连接在connect()时进行
    m_state = ModuleState::Initialized;
    qDebug() << "SerialComm initialized";
    return true;
}

void SerialComm::cleanup()
{
    disconnect();
    m_state = ModuleState::Uninitialized;
}

// ----------------------------------------
// 连接设备
// ----------------------------------------
// 设计思路:
// 1. 先断开现有连接(如果存在)
// 2. 类型转换配置
// 3. 配置串口参数
// 4. 打开串口
// 5. 更新状态
// ----------------------------------------
bool SerialComm::connect(const CommConfig *config)
{
    // 如果已连接,先断开
    if (m_connectionState == ConnectionState::Connected) {
        disconnect();
    }

    // 类型转换:基类指针转派生类指针
    // dynamic_cast在转换失败时返回nullptr
    const SerialConfig *sc = dynamic_cast<const SerialConfig*>(config);
    if (!sc) {
        emit errorOccurred("Invalid config type: expected SerialConfig");
        return false;
    }

    // 保存配置副本
    // 为什么复制一份?因为原配置可能在其他线程被修改
    delete m_config;
    m_config = new SerialConfig(*sc);

    // 更新连接状态
    setConnectionState(ConnectionState::Connecting);

    // 配置串口参数
    m_port->setPortName(m_config->portName);          // 串口名
    m_port->setBaudRate(m_config->baudRate);          // 波特率
    m_port->setDataBits(m_config->dataBits);          // 数据位
    m_port->setParity(m_config->parity);              // 校验位
    m_port->setStopBits(m_config->stopBits);          // 停止位

    // 打开串口(读写模式)
    if (!m_port->open(QIODevice::ReadWrite)) {
        setConnectionState(ConnectionState::Error);
        emit errorOccurred("Failed to open port: " + m_port->errorString());
        return false;
    }

    // 连接成功
    setConnectionState(ConnectionState::Connected);
    m_state = ModuleState::Running;

    qDebug() << "SerialComm connected to" << m_config->portName
             << "at" << m_config->baudRate << "bps";

    return true;
}

// ----------------------------------------
// 断开连接
// ----------------------------------------
void SerialComm::disconnect()
{
    // 如果串口是打开的,就关闭它
    if (m_port->isOpen()) {
        m_port->close();
    }

    setConnectionState(ConnectionState::Disconnected);
    m_state = ModuleState::Stopped;
}

// ----------------------------------------
// 发送数据
// ----------------------------------------
bool SerialComm::send(const QByteArray &data)
{
    // 检查连接状态
    if (m_connectionState != ConnectionState::Connected) {
        emit errorOccurred("Cannot send: not connected");
        return false;
    }

    // 写入数据
    // 返回写入的字节数,如果与输入不匹配说明有问题
    qint64 written = m_port->write(data);

    if (written != data.size()) {
        emit errorOccurred("Send incomplete: wrote " +
                         QString::number(written) + " of " +
                         QString::number(data.size()) + " bytes");
        return false;
    }

    // 发送成功信号(用于统计)
    emit bytesSent(written);
    return true;
}

// ----------------------------------------
// 数据接收槽函数
// ----------------------------------------
// 设计理由:
// 1. Qt SerialPort会在自己的线程中触发readyRead
// 2. 但我们的信号dataReceived会在接收者的线程中处理
// 3. Qt::QueuedConnection自动处理跨线程信号传递
// ----------------------------------------
void SerialComm::onReadyRead()
{
    // 读取所有可用数据
    QByteArray data = m_port->readAll();

    if (!data.isEmpty()) {
        // 发出数据到达信号
        // 接收者(通常是协议解析器)会处理这个数据
        emit dataReceived(data);
    }
}

// ----------------------------------------
// 错误处理槽函数
// ----------------------------------------
void SerialComm::onErrorOccurred(QSerialPort::SerialPortError error)
{
    // 忽略"无错误"和"超时"(超时不算错误)
    if (error == QSerialPort::NoError ||
        error == QSerialPort::TimeoutError) {
        return;
    }

    // 其他错误需要处理
    setConnectionState(ConnectionState::Error);
    emit this->errorOccurred(m_port->errorString());
}

// ----------------------------------------
// 状态更新辅助函数
// ----------------------------------------
void SerialComm::setConnectionState(ConnectionState state)
{
    if (m_connectionState != state) {
        m_connectionState = state;
        emit connectionStateChanged(state);
    }
}

// ----------------------------------------
// 扫描可用串口
// ----------------------------------------
// 便捷方法:列出电脑上所有可用的串口
// 用于填充UI中的串口选择下拉框
// ----------------------------------------
QStringList SerialComm::scanAvailablePorts()
{
    QStringList ports;
    const QList<QSerialPortInfo> infos = QSerialPortInfo::availablePorts();

    for (const QSerialPortInfo &info : infos) {
        // qDebug() << "Found port:" << info.portName()
        //          << "Description:" << info.description();
        ports.append(info.portName());
    }

    return ports;
}

3.6 TCP通讯实现

cpp 复制代码
// TcpComm.h - TCP通讯实现

#pragma once
#include "ICommInterface.h"
#include <QTcpSocket>
#include <QTimer>

// TCP客户端配置
class TcpClientConfig : public CommConfig {
public:
    QString hostAddress = "192.168.1.100";   // 服务器地址
    quint16 port = 5025;                      // 端口号
    int timeout = 5000;                       // 连接超时(毫秒)
    int reconnectInterval = 3000;              // 重连间隔(毫秒)
    bool autoReconnect = true;                // 是否自动重连

    CommConfig* clone() const override {
        return new TcpClientConfig(*this);
    }
};

class TcpComm : public ICommInterface {
    Q_OBJECT
public:
    explicit TcpComm(QObject *parent = nullptr);
    ~TcpComm() override;

    QString name() const override { return "TcpComm"; }
    bool initialize() override;
    void cleanup() override;
    bool stop() override { disconnect(); return true; }
    bool pause() override {
        m_heartbeatTimer->stop();  // 停止心跳
        return true;
    }

    bool connect(const CommConfig *config) override;
    void disconnect() override;
    bool send(const QByteArray &data) override;
    ConnectionState connectionState() const override { return m_connectionState; }
    CommConfig* currentConfig() const override { return m_config; }

private slots:
    void onConnected();      // 连接成功
    void onDisconnected();  // 连接断开
    void onReadyRead();      // 数据可读
    void onError(QAbstractSocket::SocketError error);  // 错误
    void onReconnect();      // 重连定时器触发
    void onHeartbeat();      // 定时心跳

private:
    void setConnectionState(ConnectionState state);
    void scheduleReconnect();  // 计划重连

    QTcpSocket *m_socket = nullptr;           // TCP套接字
    TcpClientConfig *m_config = nullptr;       // 当前配置
    ConnectionState m_connectionState = ConnectionState::Disconnected;

    // 定时器
    QTimer *m_reconnectTimer = nullptr;       // 重连定时器
    Q

    QTimer *m_heartbeatTimer = nullptr;       // 心跳定时器
};
cpp 复制代码
// TcpComm.cpp

#include "TcpComm.h"
#include <QDebug>

TcpComm::TcpComm(QObject *parent)
    : ICommInterface(parent)
    , m_socket(new QTcpSocket(this))
    , m_reconnectTimer(new QTimer(this))
    , m_heartbeatTimer(new QTimer(this))
{
    // 重连定时器设为单次触发
    m_reconnectTimer->setSingleShot(true);

    // 连接信号槽
    connect(m_socket, &QTcpSocket::connected, this, &TcpComm::onConnected);
    connect(m_socket, &QTcpSocket::disconnected, this, &TcpComm::onDisconnected);
    connect(m_socket, &QTcpSocket::readyRead, this, &TcpComm::onReadyRead);

    // Qt5兼容的错误信号连接
    // QOverload用于选择正确的重载版本
    connect(m_socket, QOverload<QAbstractSocket::SocketError>::of(&QTcpSocket::error),
            this, &TcpComm::onError);

    // 重连定时器
    connect(m_reconnectTimer, &QTimer::timeout, this, &TcpComm::onReconnect);

    // 心跳定时器(每30秒触发一次)
    connect(m_heartbeatTimer, &QTimer::timeout, this, &TcpComm::onHeartbeat);
}

TcpComm::~TcpComm()
{
    cleanup();
}

bool TcpComm::initialize()
{
    m_state = ModuleState::Initialized;
    return true;
}

void TcpComm::cleanup()
{
    disconnect();
    m_state = ModuleState::Uninitialized;
}

// ----------------------------------------
// 连接设备
// ----------------------------------------
bool TcpComm::connect(const CommConfig *config)
{
    const TcpClientConfig *tc = dynamic_cast<const TcpClientConfig*>(config);
    if (!tc) {
        emit errorOccurred("Invalid config type: expected TcpClientConfig");
        return false;
    }

    // 保存配置副本
    delete m_config;
    m_config = new TcpClientConfig(*tc);

    setConnectionState(ConnectionState::Connecting);

    // 异步连接(不会阻塞)
    m_socket->connectToHost(m_config->hostAddress, m_config->port);

    // 等待连接完成或超时
    // 注意:这里会阻塞主线程,对于GUI程序不太好
    // 更好的做法是用信号,但这里简化了
    if (!m_socket->waitForConnected(m_config->timeout)) {
        setConnectionState(ConnectionState::Error);
        emit errorOccurred("Connection timeout: " + m_socket->errorString());
        scheduleReconnect();
        return false;
    }

    return true;
}

void TcpComm::disconnect()
{
    // 停止所有定时器
    m_reconnectTimer->stop();
    m_heartbeatTimer->stop();

    // 断开连接
    if (m_socket->state() != QAbstractSocket::UnconnectedState) {
        m_socket->disconnectFromHost();
    }

    setConnectionState(ConnectionState::Disconnected);
    m_state = ModuleState::Stopped;
}

bool TcpComm::send(const QByteArray &data)
{
    if (m_connectionState != ConnectionState::Connected) {
        emit errorOccurred("Cannot send: not connected");
        return false;
    }

    qint64 written = m_socket->write(data);
    if (written != data.size()) {
        emit errorOccurred("Send incomplete");
        return false;
    }

    emit bytesSent(written);
    return true;
}

void TcpComm::onConnected()
{
    setConnectionState(ConnectionState::Connected);
    m_state = ModuleState::Running;

    // 启动心跳
    m_heartbeatTimer->start(30000);

    qDebug() << "TCP connected to" << m_config->hostAddress << ":" << m_config->port;
}

void TcpComm::onDisconnected()
{
    // 停止心跳
    m_heartbeatTimer->stop();

    setConnectionState(ConnectionState::Disconnected);

    // 自动重连
    scheduleReconnect();
}

void TcpComm::onReadyRead()
{
    QByteArray data = m_socket->readAll();
    if (!data.isEmpty()) {
        emit dataReceived(data);
    }
}

void TcpComm::onError(QAbstractSocket::SocketError)
{
    setConnectionState(ConnectionState::Error);
    emit errorOccurred(m_socket->errorString());

    // 自动重连
    scheduleReconnect();
}

// ----------------------------------------
// 自动重连机制
// ----------------------------------------
// 设计思路:
// 1. 连接断开或出错时,不立即重连
// 2. 等一小段时间(m_reconnectInterval)再重连
// 3. 这样避免疯狂重连消耗资源
// 4. 可以通过配置关闭自动重连
// ----------------------------------------
void TcpComm::onReconnect()
{
    if (m_config && m_config->autoReconnect) {
        qDebug() << "Attempting to reconnect...";
        setConnectionState(ConnectionState::Reconnecting);
        connect(m_config);
    }
}

void TcpComm::scheduleReconnect()
{
    if (m_config && m_config->autoReconnect) {
        // 启动重连定时器
        // 单次定时器,时间到了触发onReconnect
        m_reconnectTimer->start(m_config->reconnectInterval);
    }
}

void TcpComm::setConnectionState(ConnectionState state)
{
    if (m_connectionState != state) {
        m_connectionState = state;
        emit connectionStateChanged(state);
    }
}

3.7 协议解析器设计

为什么需要协议解析器?

串口/网络收到的数据是原始字节流 ,但业务需要的是有意义的数据帧

复制代码
原始数据流:
┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐
│A│A│0│5│D│A│T│A│0│0│1│2│3│4│5│...│ ← 字节流
└─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘

需要解析成:
┌───────────────────┐  ┌───────────────────┐
│ 帧头 | 长度 | 数据  │  │ 帧头 | 长度 | 数据 │
│ 0xAA 0x05 DAT A00  │  │ 0xAA 0x05 DAT A01  │
└───────────────────┘  └───────────────────┘

协议解析器的职责

  1. 从原始数据流中识别完整的数据帧(处理粘包和半包)
  2. 校验数据完整性(CRC校验)
  3. 将数据帧转换为业务可用的格式
变长协议解析器实现
cpp 复制代码
// ============================================================
// ProtocolParser.h - 协议解析器
// ============================================================
// 设计思路:
// 1. 抽象基类定义接口,具体协议继承实现
// 2. 缓冲区设计:可能一次收到半帧数据,需要缓存
// 3. 线程安全:数据可能在通讯线程收到,但解析在另一个线程
// ============================================================

#pragma once
#include <QObject>
#include <QByteArray>
#include <QMutex>

// ----------------------------------------
// 协议解析器抽象基类
// ----------------------------------------
class IProtocolParser {
public:
    virtual ~IProtocolParser() = default;

    // 解析数据,返回完整的帧列表
    // 输入:可能是任意长度的原始数据
    // 输出:解析出的完整数据帧列表
    virtual QList<QByteArray> parse(const QByteArray &data) = 0;

    // 封包:将业务数据编码为协议格式
    virtual QByteArray package(const QByteArray &data) = 0;

    // 添加原始数据到缓冲区
    virtual void appendData(const QByteArray &data) = 0;

    // 清空缓冲区
    virtual void clear() = 0;
};

// ============================================================
// 变长协议解析器
// ============================================================
// 协议格式:
// ┌──────┬───────────┬──────────┬──────┐
// │ 帧头  │  长度(2B)  │  数据    │ CRC  │
// │ 1字节 │  高低字节  │ N字节    │ 1字节 │
// └──────┴───────────┴──────────┴──────┘
//
// 设计理由:
// 1. 变长协议是仪器控制中最常见的格式
// 2. 帧头帮助识别帧的起始
// 3. 长度字段指明数据长度
// 4. CRC校验保证数据完整性
// ============================================================

class VariableLengthParser : public IProtocolParser {
public:
    // 构造函数:指定帧头值
    // 默认使用0xAA作为帧头(易于识别)
    explicit VariableLengthParser(quint8 header = 0xAA)
        : m_header(header)
        , m_lengthOffset(1)      // 长度字段在帧头的下一个字节
        , m_lengthSize(2)        // 长度字段占2字节
        , m_crcOffset(-1)        // -1表示没有CRC
        , m_mutex(QMutex::Recursive)  // 递归锁,允许同线程嵌套锁定
    {}

    // ----------------------------------------
    // 解析数据帧
    // ----------------------------------------
    // 算法:
    // 1. 查找帧头(如果不是帧头就丢弃)
    // 2. 读取长度字段
    // 3. 等待收完整帧(包括CRC)
    // 4. 返回完整帧,清空已处理的数据
    // ----------------------------------------
    QList<QByteArray> parse(const QByteArray &data) override
    {
        QMutexLocker locker(&m_mutex);

        QList<QByteArray> result;

        // 第一步:将新数据追加到缓冲区
        m_buffer.append(data);

        // 第二步:循环处理,直到缓冲区数据不足一帧
        while (m_buffer.size() >= 4) {  // 最小帧 = 帧头(1) + 长度(2) + 至少1字节数据

            // 查找帧头
            // 如果不是帧头,说明之前的数据是垃圾,丢弃
            if (static_cast<quint8>(m_buffer[0]) != m_header) {
                m_buffer.remove(0, 1);  // 丢弃一个字节
                continue;
            }

            // 提取长度字段
            // 支持1字节或2字节长度
            int dataLength = 0;
            if (m_lengthSize == 2) {
                // 大端序:高字节在前
                dataLength = (static_cast<quint8>(m_buffer[1]) << 8) |
                              static_cast<quint8>(m_buffer[2]);
            } else {
                dataLength = static_cast<quint8>(m_buffer[1]);
            }

            // 计算完整帧的长度
            int frameLength = 1 + m_lengthSize + dataLength;
            if (m_crcOffset >= 0) {
                frameLength += 1;  // 加上CRC字节
            }

            // 第三步:判断是否收完整
            if (m_buffer.size() < frameLength) {
                // 数据不完整,等待更多数据
                // 这是TCP通讯中常见的情况
                break;
            }

            // 第四步:提取完整帧
            QByteArray frame = m_buffer.left(frameLength);

            // 第五步:如果是CRC校验模式,验证CRC
            if (m_crcOffset >= 0) {
                quint8 receivedCrc = static_cast<quint8>(frame[frameLength - 1]);
                quint8 calculatedCrc = calculateCrc(frame, 0, frameLength - 1);

                if (receivedCrc != calculatedCrc) {
                    // CRC校验失败,丢弃这帧
                    qWarning() << "CRC mismatch:" << receivedCrc << "!="
                               << calculatedCrc;
                    m_buffer.remove(0, frameLength);
                    continue;
                }
            }

            // 验证通过,加入结果
            result.append(frame);

            // 第六步:从缓冲区移除已处理的数据
            m_buffer.remove(0, frameLength);
        }

        return result;
    }

    // ----------------------------------------
    // 封包
    // ----------------------------------------
    // 将业务数据编码为协议格式
    // ----------------------------------------
    QByteArray package(const QByteArray &data) override
    {
        QMutexLocker locker(&m_mutex);

        QByteArray packet;

        // 添加帧头
        packet.append(m_header);

        // 添加长度字段(大端序)
        int len = data.size();
        packet.append(static_cast<char>((len >> 8) & 0xFF));  // 高字节
        packet.append(static_cast<char>(len & 0xFF));         // 低字节

        // 添加数据
        packet.append(data);

        // 添加CRC(如果启用)
        if (m_crcOffset >= 0) {
            packet.append(calculateCrc(packet, 0, packet.size()));
        }

        return packet;
    }

    void appendData(const QByteArray &data) override {
        QMutexLocker locker(&m_mutex);
        m_buffer.append(data);
    }

    void clear() override {
        QMutexLocker locker(&m_mutex);
        m_buffer.clear();
    }

    // 配置方法
    void setHeader(quint8 header) { m_header = header; }
    void enableCrc(bool enable) { m_crcOffset = enable ? 0 : -1; }

private:
    // CRC校验(简单累加和)
    // 实际应用中可能用CRC16或CRC32
    quint8 calculateCrc(const QByteArray &data, int start, int len)
    {
        quint8 crc = 0;
        for (int i = start; i < start + len; ++i) {
            crc += static_cast<quint8>(data[i]);
        }
        return crc;
    }

    quint8 m_header;          // 帧头值
    int m_lengthOffset;       // 长度字段偏移
    int m_lengthSize;         // 长度字段字节数
    int m_crcOffset;         // CRC偏移,-1表示无CRC

    QByteArray m_buffer;      // 接收缓冲区
    mutable QMutex m_mutex;   // 互斥锁,保证线程安全
};

四、参数配置模块设计

4.1 为什么需要专门的参数配置模块?

cpp 复制代码
❌ 混乱的配置管理
// A.cpp
int g_timeout = 3000;

// B.cpp
extern int g_timeout;
if (g_timeout > 5000) { ... }

// C.cpp
QSettings settings("config.ini", QSettings::IniFormat);
int timeout = settings.value("device/timeout", 3000).toInt();

问题:

  • 配置分散在各处,难以统一管理
  • 没有校验:可以设置任何值
  • 没有变更通知:改了配置,UI不会自动刷新
  • 没有历史:想回滚到之前的配置?做梦
cpp 复制代码
✅ 统一的配置管理
// 一个地方定义所有参数
class ParameterManager {
    // 注册参数(带范围校验)
    void registerParameter("device.timeout", 3000, "通讯超时", 100, 60000);

    // 统一的读写接口
    QVariant get("device.timeout");
    bool set("device.timeout", 5000);  // 自动校验范围

    // 配置变更通知
signals:
    void parameterChanged(key, oldValue, newValue);
};

4.2 设计模式分析

单例模式

  • 为什么?整个程序只需要一个配置管理器
  • 全局访问点:通过Instance()获取
  • 好处:不用到处传配置对象

观察者模式

  • 为什么?配置变了,所有依赖配置的组件都需要知道
  • 实现:参数变化时发出信号
  • 应用:
    • UI层监听:配置变了,界面自动更新
    • 通讯模块监听:超时时间变了,立即生效
    • 存储模块监听:存储路径变了,切换存储位置

Memento模式

  • 为什么?能保存和恢复配置快照
  • 应用:用户可以保存"我喜欢这个配置",随时恢复
  • 实现:序列化配置到JSON

4.3 完整代码实现

cpp 复制代码
// ParameterManager.h

#pragma once
#include "../Core/ModuleInterface.h"
#include <QVariant>
#include <QMutex>
#include <QJsonObject>

// ----------------------------------------
// 参数项定义
// ----------------------------------------
// 每个参数都有完整的元信息:默认值、范围、描述等
// 这些信息用于:
// 1. UI生成配置界面(范围 -> 滑块/输入框)
// 2. 校验用户输入(是否在范围内)
// 3. 生成配置文档(描述字段)
// ----------------------------------------
struct ParameterItem {
    QString key;              // 参数键(如"device.timeout")
    QVariant defaultValue;    // 默认值
    QVariant currentValue;    // 当前值
    QString description;      // 人类可读描述
    QVariant minValue;        // 最小值(数值参数用)
    QVariant maxValue;        // 最大值(数值参数用)
    QStringList enumValues;   // 枚举值列表(如{"auto", "manual"})
    bool readonly = false;    // 是否只读
};

// ----------------------------------------
// 参数管理器
// ----------------------------------------
// 职责:
// 1. 管理所有配置参数
// 2. 提供类型安全的读写接口
// 3. 校验参数值
// 4. 通知变更
// 5. 保存/加载配置
// 6. 配置快照
// ----------------------------------------
class ParameterManager : public ModuleInterface {
    Q_OBJECT
    Q_DECLARE_SINGLETON(ParameterManager)

public:
    // ----------------------------------------
    // 注册参数
    // ----------------------------------------
    // 必须在initialize()之前调用
    // 设计理由:初始化时注册所有参数,清晰明了
    // ----------------------------------------
    void registerParameter(const QString &key,
                          const QVariant &defaultVal,
                          const QString &desc = "",
                          const QVariant &min = {},
                          const QVariant &max = {},
                          const QStringList &enums = {});

    // ----------------------------------------
    // 读取参数
    // ----------------------------------------
    // 返回参数值,如果不存在返回默认值
    // ----------------------------------------
    QVariant get(const QString &key,
                 const QVariant &def = {}) const;

    // ----------------------------------------
    // 设置参数
    // ----------------------------------------
    // 返回是否成功(失败原因:不存在/只读/校验失败)
    // 成功时会发出parameterChanged信号
    // ----------------------------------------
    bool set(const QString &key,
             const QVariant &value,
             const QString &who = "System");

    // ----------------------------------------
    // 批量设置
    // ----------------------------------------
    // 原子操作:要么全成功,要么全失败
    // 用于加载配置文件等场景
    // ----------------------------------------
    void setBatch(const QHash<QString, QVariant> &vals,
                  const QString &who = "System");

    // ----------------------------------------
    // 配置持久化
    // ----------------------------------------
    bool load(const QString &path);   // 从文件加载
    bool save(const QString &path) const;  // 保存到文件

    // ----------------------------------------
    // 配置快照
    // ----------------------------------------
    // 保存当前配置为快照,可以随时恢复
    // 用于:用户保存多个预设配置
    // ----------------------------------------
    QString saveSnapshot() const;           // 返回快照ID
    bool restoreSnapshot(const QString &id);  // 恢复快照

    // ----------------------------------------
    // 重置
    // ----------------------------------------
    void resetToDefaults();  // 恢复到所有参数的默认值

    // ----------------------------------------
    // ModuleInterface 实现
    // ----------------------------------------
    QString name() const override { return "ParameterManager"; }
    bool initialize() override;
    bool start() override { return true; }
    bool stop() override { return true; }
    void cleanup() override;
    QStringList dependencies() const override { return {"CommModule"}; }

signals:
    // 参数变化信号
    // 谁改的、原来的值、新值都传过去
    void parameterChanged(const QString &key,
                         const QVariant &oldVal,
                         const QVariant &newVal);

private:
    explicit ParameterManager(QObject *parent = nullptr)
        : ModuleInterface(parent) {}
    ~ParameterManager() override { cleanup(); }

    // 校验参数值
    // 检查类型、范围、枚举是否合法
    bool validate(const QString &key, const QVariant &val) const;

    mutable QMutex m_mutex;
    QHash<QString, ParameterItem> m_params;
    QHash<QString, QJsonObject> m_snapshots;  // 快照存储
};
cpp 复制代码
// ParameterManager.cpp

#include "ParameterManager.h"
#include <QFile>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QDebug>
#include <QUuid>

// ----------------------------------------
// 初始化:注册所有参数
// ----------------------------------------
bool ParameterManager::initialize()
{
    // 注意:这里注册的是参数定义,不是值
    // 值会在第一次读取时使用默认值

    // === 通讯配置 ===
    registerParameter(
        "device.timeout",     // 键
        3000,                 // 默认值
        "通讯超时时间(毫秒)", // 描述
        100,                  // 最小值
        60000                 // 最大值
    );

    registerParameter(
        "device.retryCount",
        3,
        "通讯失败重试次数",
        1,
        10
    );

    registerParameter(
        "device.baudRate",
        115200,
        "串口波特率"
    );

    // === 采集配置 ===
    registerParameter(
        "acquisition.rate",
        1000,
        "采样率(Hz)",
        1,
        100000
    );

    registerParameter(
        "acquisition.duration",
        1.0,
        "单次采集时长(秒)",
        0.001,
        3600
    );

    // 枚举参数:只能从给定值中选择
    registerParameter(
        "acquisition.triggerMode",
        "auto",
        "触发模式",
        {},      // 无最小值
        {},      // 无最大值
        {"auto", "manual", "external"}  // 枚举值
    );

    // === 显示配置 ===
    registerParameter(
        "display.refreshRate",
        60,
        "UI刷新率(Hz)",
        10,
        120
    );

    registerParameter(
        "display.showGrid",
        true,
        "显示网格线"
    );

    registerParameter(
        "display.showLegend",
        true,
        "显示图例"
    );

    // === 存储配置 ===
    registerParameter(
        "storage.enable",
        true,
        "启用数据存储"
    );

    registerParameter(
        "storage.format",
        "binary",
        "存储格式",
        {},
        {},
        {"binary", "csv", "hdf5"}
    );

    registerParameter(
        "storage.directory",
        "",
        "存储目录路径"
    );

    m_state = ModuleState::Initialized;
    qDebug() << "ParameterManager initialized with"
             << m_params.size() << "parameters";

    return true;
}

void ParameterManager::cleanup()
{
    QMutexLocker locker(&m_mutex);
    m_params.clear();
    m_snapshots.clear();
    m_state = ModuleState::Uninitialized;
}

// ----------------------------------------
// 读取参数
// ----------------------------------------
QVariant ParameterManager::get(const QString &key,
                               const QVariant &def) const
{
    QMutexLocker locker(&m_mutex);

    auto it = m_params.find(key);
    if (it != m_params.end()) {
        return it.value().currentValue;
    }

    // 参数不存在,返回调用者提供的默认值
    return def;
}

// ----------------------------------------
// 设置参数(核心方法)
// ----------------------------------------
bool ParameterManager::set(const QString &key,
                           const QVariant &value,
                           const QString &who)
{
    QMutexLocker locker(&m_mutex);

    // 查找参数
    auto it = m_params.find(key);
    if (it == m_params.end()) {
        qWarning() << "Parameter not found:" << key;
        return false;
    }

    ParameterItem &item = it.value();

    // 检查是否只读
    if (item.readonly) {
        qWarning() << "Cannot modify readonly parameter:" << key;
        return false;
    }

    // 校验值
    if (!validate(key, value)) {
        qWarning() << "Invalid value for parameter:" << key
                   << "=" << value;
        return false;
    }

    // 记录旧值
    QVariant oldValue = item.currentValue;

    // 更新值
    item.currentValue = value;

    // 记录历史(可选:可以限制历史长度防止内存泄漏)
    // m_changeHistory.append({key, oldValue, value, ...});

    locker.unlock();

    // 发出变更信号
    // 注意:信号在锁释放后发出,防止死锁
    emit parameterChanged(key, oldValue, value);

    qDebug() << "Parameter changed by" << who << ":"
             << key << "=" << oldValue << "->" << value;

    return true;
}

// ----------------------------------------
// 批量设置
// ----------------------------------------
void ParameterManager::setBatch(const QHash<QString, QVariant> &vals,
                               const QString &who)
{
    QMutexLocker locker(&m_mutex);

    for (auto it = vals.begin(); it != vals.end(); ++it) {
        const QString &key = it.key();
        const QVariant &value = it.value();

        auto pit = m_params.find(key);
        if (pit == m_params.end() || pit.value().readonly) {
            continue;  // 跳过不存在的或只读的
        }

        if (!validate(key, value)) {
            continue;  // 跳过校验失败的
        }

        pit.value().currentValue = value;
    }

    // 注意:批量设置不发出单个信号的变更
    // 调用者应该负责发出批量变更信号或逐个发出
}

// ----------------------------------------
// 参数校验
// ----------------------------------------
bool ParameterManager::validate(const QString &key,
                                const QVariant &value) const
{
    auto it = m_params.find(key);
    if (it == m_params.end()) return false;

    const ParameterItem &item = it.value();

    // 枚举检查:如果定义了枚举值,新值必须在枚举中
    if (!item.enumValues.isEmpty()) {
        if (!item.enumValues.contains(value.toString())) {
            return false;
        }
    }

    // 范围检查:数值必须在最小值和最大值之间
    // 注意:QVariant的比较会自动类型转换
    if (item.minValue.isValid() && value < item.minValue) {
        return false;
    }
    if (item.maxValue.isValid() && value > item.maxValue) {
        return false;
    }

    return true;
}

// ----------------------------------------
// 加载配置
// ----------------------------------------
bool ParameterManager::load(const QString &path)
{
    QFile file(path);
    if (!file.open(QIODevice::ReadOnly)) {
        qWarning() << "Failed to open config file:" << path;
        return false;
    }

    QByteArray data = file.readAll();
    file.close();

    QJsonParseError error;
    QJsonDocument doc = QJsonDocument::fromJson(data, &error);

    if (error.error != QJsonParseError::NoError) {
        qWarning() << "JSON parse error:" << error.errorString();
        return false;
    }

    QMutexLocker locker(&m_mutex);
    QJsonObject json = doc.object();

    if (json.contains("parameters")) {
        QJsonObject params = json["parameters"].toObject();

        // 逐个应用参数
        for (auto it = params.begin(); it != params.end(); ++it) {
            const QString &key = it.key();
            QVariant value = it.value().toVariant();

            // 验证并设置
            auto pit = m_params.find(key);
            if (pit == m_params.end() || pit.value().readonly) {
                continue;
            }

            if (validate(key, value)) {
                pit.value().currentValue = value;
            }
        }
    }

    qDebug() << "Configuration loaded from" << path;
    return true;
}

// ----------------------------------------
// 保存配置
// ----------------------------------------
bool ParameterManager::save(const QString &path) const
{
    QFile file(path);
    if (!file.open(QIODevice::WriteOnly)) {
        qWarning() << "Failed to open config file for write:" << path;
        return false;
    }

    QJsonObject json;
    json["version"] = "1.0";
    json["timestamp"] = QDateTime::currentDateTime().toString(Qt::ISODate);

    // 序列化所有参数
    QJsonObject params;
    {
        QMutexLocker locker(&m_mutex);
        for (auto it = m_params.begin(); it != m_params.end(); ++it) {
            params[it.key()] = QJsonValue::fromVariant(it.value().currentValue);
        }
    }

    json["parameters"] = params;

    // 写入文件(格式化输出,便于人工阅读)
    file.write(QJsonDocument(json).toJson(QJsonDocument::Indented));
    file.close();

    qDebug() << "Configuration saved to" << path;
    return true;
}

// ----------------------------------------
// 配置快照
// ----------------------------------------
QString ParameterManager::saveSnapshot() const
{
    QMutexLocker locker(&m_mutex);

    // 生成唯一ID
    QString snapshotId = QUuid::createUuid().toString();

    // 创

    // 创建快照对象
    QJsonObject snapshot;
    snapshot["timestamp"] = QDateTime::currentDateTime().toString(Qt::ISODate);

    // 序列化所有参数
    QJsonObject params;
    for (auto it = m_params.begin(); it != m_params.end(); ++it) {
        params[it.key()] = QJsonValue::fromVariant(it.value().currentValue);
    }
    snapshot["parameters"] = params;

    // 存储快照
    m_snapshots[snapshotId] = snapshot;

    qDebug() << "Snapshot saved:" << snapshotId;
    return snapshotId;
}

bool ParameterManager::restoreSnapshot(const QString &id)
{
    QMutexLocker locker(&m_mutex);

    // 查找快照
    auto it = m_snapshots.find(id);
    if (it == m_snapshots.end()) {
        qWarning() << "Snapshot not found:" << id;
        return false;
    }

    // 提取快照中的参数
    const QJsonObject &snapshot = it.value();
    if (!snapshot.contains("parameters")) {
        return false;
    }

    QJsonObject params = snapshot["parameters"].toObject();

    // 批量应用参数
    locker.unlock();

    QHash<QString, QVariant> values;
    for (auto it2 = params.begin(); it2 != params.end(); ++it2) {
        values[it2.key()] = it2.value().toVariant();
    }

    setBatch(values, "SnapshotRestore");

    qDebug() << "Snapshot restored:" << id;
    return true;
}

void ParameterManager::resetToDefaults()
{
    QMutexLocker locker(&m_mutex);

    for (auto it = m_params.begin(); it != m_params.end(); ++it) {
        // 只读参数不能重置
        if (!it.value().readonly) {
            it.value().currentValue = it.value().defaultValue;
        }
    }

    qDebug() << "Parameters reset to defaults";
}

---

## 五、分析计算模块设计

### 5.1 为什么选择责任链模式?

分析计算模块面临的问题是:**数据处理流程可能很复杂,而且会变化**。

典型的数据处理流程:

原始数据

滤波(去噪)

变换(FFT)

特征提取(峰值检测)

结果

复制代码
**需求分析**:
1. 多个处理步骤串联执行
2. 每个步骤可独立启用/禁用
3. 可以添加新的处理步骤
4. 步骤的顺序可能变化
5. 有些步骤可能需要不同参数

**设计模式选择**:

| 模式 | 适用性 | 原因 |
|------|--------|------|
| 策略模式 | 一般 | 可以切换算法,但不能串联 |
| 装饰器模式 | 一般 | 可以动态添加功能,但顺序固定 |
| **责任链模式** | ✅ 最适合 | 节点串联、可增删改顺序、可独立启用 |

### 5.2 责任链模式详解

责任链模式结构:

┌────────────┐ ┌────────────┐ ┌────────────┐

│ FilterNode │ ──→ │ FFTNode │ ──→ │ PeakNode │

└────────────┘ └────────────┘ └────────────┘

↑ ↑

└──────────────────────────────────────┘

head节点指向第一个处理器

工作原理:

  1. 数据从head进入

  2. 每个节点处理后,交给下一个节点

  3. 节点可以修改数据、添加元数据、或终止处理

  4. 处理结果从最后一个节点输出

    扩展性演示

    cpp 复制代码
    // 添加新节点只需要:
    class MyCustomNode : public IAnalysisNode {
        QString name() const override { return "MyCustom"; }
        AnalysisResult process(const AnalysisData &input) override {
            // 自己的处理逻辑
            return result;
        }
    };
    
    // 注册到链中
    analysisModule->registerNode(new MyCustomNode());
    // 链表自动扩展,不需要改其他代码

5.3 完整代码实现

cpp 复制代码
// AnalysisModule.h

#pragma once
#include "../Core/ModuleInterface.h"
#include <QVector>
#include <QMutex>
#include <QThreadPool>
#include <QtConcurrent>

// ----------------------------------------
// 数据结构定义
// ----------------------------------------

// 分析数据:输入到分析链的原始数据
struct AnalysisData {
    QVector<double> rawData;       // 原始数据(输入)
    QVector<double> processedData; // 处理后数据(中间/输出)
    QVariantMap metadata;          // 元数据(时间戳、通道信息等)
    // QString source;              // 来源(可选)
};

// 分析结果:处理节点的返回值
struct AnalysisResult {
    bool success = false;          // 是否成功
    QString message;               // 结果描述
    QVariantMap results;           // 分析结果(如峰值频率、幅度等)
    QVector<double> outputData;   // 处理后的数据
    double elapsedMs = 0;          // 处理耗时(毫秒)
};

// ----------------------------------------
// 分析节点基类(责任链)
// ----------------------------------------
// 设计理由:
// 1. 所有分析算法都继承这个基类
// 2. 每个节点可以决定:处理并传递给下家,或自己终止
// 3. 节点之间无依赖,可以自由组合
// ----------------------------------------
class IAnalysisNode {
public:
    virtual ~IAnalysisNode() = default;

    // 设置下一个处理节点
    void setNext(IAnalysisNode *node) { m_next = node; }
    IAnalysisNode* next() const { return m_next; }

    // 节点名称(用于日志和调试)
    virtual QString name() const = 0;

    // 处理数据
    // 输入:上一节点传来的数据
    // 输出:处理结果
    // 设计:返回的outputData会成为下一节点的rawData
    virtual AnalysisResult process(const AnalysisData &input) = 0;

protected:
    IAnalysisNode *m_next = nullptr;  // 责任链中的下一个节点
};

// ----------------------------------------
// 分析模块
// ----------------------------------------
class AnalysisModule : public ModuleInterface {
    Q_OBJECT
    Q_DECLARE_SINGLETON(AnalysisModule)

public:
    // 注册分析节点(组成责任链)
    void registerNode(IAnalysisNode *node);

    // 执行单次分析
    AnalysisResult execute(const AnalysisData &data);

    // 批量执行(多线程)
    QVector<AnalysisResult> executeBatch(const QVector<AnalysisData> &list);

    // 设置节点启用状态
    void setNodeEnabled(const QString &nodeName, bool enabled);

    QString name() const override { return "AnalysisModule"; }
    bool initialize() override;
    bool start() override { m_state = ModuleState::Running; return true; }
    bool stop() override { m_state = ModuleState::Stopped; return true; }
    void cleanup() override;
    QStringList dependencies() const override { return {"ParameterManager"}; }

signals:
    void analysisCompleted(const AnalysisResult &result);

private:
    explicit AnalysisModule(QObject *parent = nullptr);
    ~AnalysisModule() override;

    IAnalysisNode *m_head = nullptr;           // 责任链头节点
    QVector<IAnalysisNode*> m_allNodes;         // 所有节点(用于遍历)
    QThreadPool *m_threadPool = nullptr;         // 线程池(用于批量处理)
};

// ============================================================
// 内置分析节点实现
// ============================================================

// ----------------------------------------
// 1. 滑动平均滤波节点
// ----------------------------------------
// 算法:取窗口内数据的平均值
// 优点:实现简单,计算速度快
// 缺点:会让波形边缘变得平滑(起始N/2个点无法完全平滑)
// ----------------------------------------
class MovingAverageNode : public IAnalysisNode {
    Q_OBJECT
public:
    // windowSize: 窗口大小,越大越平滑,但延迟越高
    explicit MovingAverageNode(int windowSize = 5)
        : m_windowSize(windowSize) {}

    QString name() const override { return "MovingAverage"; }

    AnalysisResult process(const AnalysisData &input) override
    {
        AnalysisResult r;
        QElapsedTimer timer;
        timer.start();

        QVector<double> output;
        output.reserve(input.rawData.size());

        // 核心算法:滑动窗口平均
        for (int i = 0; i < input.rawData.size(); ++i) {
            double sum = 0;
            int count = 0;

            // 计算窗口内的和
            // 窗口从 max(0, i-windowSize+1) 到 i
            for (int j = qMax(0, i - m_windowSize + 1); j <= i; ++j) {
                sum += input.rawData[j];
                ++count;
            }

            output.append(sum / count);
        }

        // 返回结果
        r.success = true;
        r.message = "Moving average filter applied";
        r.outputData = output;
        r.elapsedMs = timer.elapsed();

        return r;
    }

private:
    int m_windowSize;
};

// ----------------------------------------
// 2. 中值滤波节点
// ----------------------------------------
// 算法:取窗口内数据的中值
// 优点:对脉冲噪声(异常值)非常有效
// 缺点:计算量比滑动平均大
// 适用场景:去除传感器突发噪声
// ----------------------------------------
class MedianFilterNode : public IAnalysisNode {
    Q_OBJECT
public:
    explicit MedianFilterNode(int windowSize = 5)
        : m_windowSize(windowSize) {}

    QString name() const override { return "MedianFilter"; }

    AnalysisResult process(const AnalysisData &input) override
    {
        AnalysisResult r;
        QElapsedTimer timer;
        timer.start();

        QVector<double> output;
        output.reserve(input.rawData.size());

        int halfWindow = m_windowSize / 2;

        for (int i = 0; i < input.rawData.size(); ++i) {
            // 收集窗口内的数据
            QVector<double> window;
            int start = qMax(0, i - halfWindow);
            int end = qMin(input.rawData.size() - 1, i + halfWindow);

            for (int j = start; j <= end; ++j) {
                window.append(input.rawData[j]);
            }

            // 排序
            std::sort(window.begin(), window.end());

            // 取中值
            double median = window[window.size() / 2];
            output.append(median);
        }

        r.success = true;
        r.message = "Median filter applied";
        r.outputData = output;
        r.elapsedMs = timer.elapsed();

        return r;
    }

private:
    int m_windowSize;
};

// ----------------------------------------
// 3. FFT频谱分析节点
// ----------------------------------------
// 算法:快速傅里叶变换
// 作用:将时域信号转换到频域
// 输出:频谱幅度、主频、频谱能量等
// 注意:这是简化实现,实际项目建议用FFTW库
// ----------------------------------------
class FFTNode : public IAnalysisNode {
    Q_OBJECT
public:
    FFTNode(double sampleRate = 1000.0, int fftPoints = 1024)
        : m_sampleRate(sampleRate)
        , m_fftPoints(fftPoints) {}

    QString name() const override { return "FFT"; }

    void setSampleRate(double rate) { m_sampleRate = rate; }
    void setFFTPoints(int points) { m_fftPoints = points; }

    AnalysisResult process(const AnalysisData &input) override
    {
        AnalysisResult r;
        QElapsedTimer timer;
        timer.start();

        // 限制FFT点数
        int n = qMin(m_fftPoints, input.rawData.size());

        // 预分配输出数组
        QVector<double> magnitude(n / 2);
        QVector<double> frequency(n / 2);
        double freqResolution = m_sampleRate / n;

        // 离散傅里叶变换(DFT)
        // 注意:这是O(n^2)的朴素实现
        // 实际应用应该用FFT算法或FFTW库
        for (int i = 0; i < n / 2; ++i) {
            frequency[i] = i * freqResolution;

            double real = 0, imag = 0;

            for (int k = 0; k < n; ++k) {
                double angle = 2 * M_PI * k * i / n;
                real += input.rawData[k] * std::cos(angle);
                imag += input.rawData[k] * std::sin(angle);
            }

            // 计算幅度
            magnitude[i] = std::sqrt(real * real + imag * imag) * 2.0 / n;
        }

        // 找主频(幅度最大的频率分量)
        int peakIndex = 0;
        for (int i = 1; i < magnitude.size(); ++i) {
            if (magnitude[i] > magnitude[peakIndex]) {
                peakIndex = i;
            }
        }

        // 填充结果
        r.success = true;
        r.message = "FFT analysis completed";
        r.results["peakFrequency"] = frequency[peakIndex];
        r.results["peakMagnitude"] = magnitude[peakIndex];
        r.results["fftPoints"] = n;
        r.results["frequencyResolution"] = freqResolution;
        r.outputData = magnitude;
        r.elapsedMs = timer.elapsed();

        return r;
    }

private:
    double m_sampleRate;  // 采样率 (Hz)
    int m_fftPoints;      // FFT点数(必须是2的幂)
};

// ----------------------------------------
// 4. 峰值检测节点
// ----------------------------------------
// 算法:找局部最大值
// 用途:找信号中的峰值点(波峰或波谷)
// 参数:
//   - threshold: 阈值,只有超过这个值的点才考虑
//   - minDistance: 相邻峰值之间的最小距离
// ----------------------------------------
class PeakDetectionNode : public IAnalysisNode {
    Q_OBJECT
public:
    PeakDetectionNode(double threshold = 0.5, int minDistance = 10)
        : m_threshold(threshold)
        , m_minDistance(minDistance) {}

    QString name() const override { return "PeakDetection"; }

    void setThreshold(double t) { m_threshold = t; }
    void setMinDistance(int d) { m_minDistance = d; }

    AnalysisResult process(const AnalysisData &input) override
    {
        AnalysisResult r;
        QElapsedTimer timer;
        timer.start();

        // 使用处理后的数据(如果没有就用原始数据)
        const QVector<double> &data =
            input.processedData.isEmpty() ? input.rawData : input.processedData;

        QVector<double> peakValues;
        QVector<int> peakIndices;

        // 找峰值
        for (int i = m_minDistance; i < data.size() - m_minDistance; ++i) {
            // 先检查是否超过阈值
            if (data[i] < m_threshold) continue;

            // 检查是否是局部最大值
            bool isPeak = true;
            for (int j = i - m_minDistance; j <= i + m_minDistance; ++j) {
                if (j != i && data[j] >= data[i]) {
                    isPeak = false;
                    break;
                }
            }

            if (isPeak) {
                peakValues.append(data[i]);
                peakIndices.append(i);
            }
        }

        r.success = true;
        r.message = QString("Found %1 peaks").arg(peakValues.size());
        r.results["peakCount"] = peakValues.size();
        r.results["peakValues"] = QVariant::fromValue(peakValues);
        r.results["peakIndices"] = QVariant::fromValue(peakIndices);
        r.elapsedMs = timer.elapsed();

        return r;
    }

private:
    double m_threshold;  // 阈值
    int m_minDistance;    // 最小距离
};
cpp 复制代码
// AnalysisModule.cpp

#include "AnalysisModule.h"
#include <QDebug>

AnalysisModule::AnalysisModule(QObject *parent)
    : ModuleInterface(parent)
    , m_threadPool(new QThreadPool(this))
{
    // 设置线程池大小
    // 建议:CPU密集型任务,线程数 = CPU核心数
    m_threadPool->setMaxThreadCount(QThread::idealThreadCount());
}

AnalysisModule::~AnalysisModule()
{
    cleanup();
}

// ----------------------------------------
// 初始化
// ----------------------------------------
bool AnalysisModule::initialize()
{
    // 注册默认的分析节点
    // 注意:注册顺序就是执行顺序

    // 1. 先滤波去噪
    registerNode(new MovingAverageNode(5));

    // 2. 再FFT分析
    registerNode(new FFTNode(1000.0, 1024));

    // 3. 最后峰值检测
    registerNode(new PeakDetectionNode(0.5, 10));

    m_state = ModuleState::Initialized;

    qDebug() << "AnalysisModule initialized with"
             << m_allNodes.size() << "nodes";

    return true;
}

void AnalysisModule::cleanup()
{
    // 删除所有节点
    for (auto node : m_allNodes) {
        delete node;
    }
    m_allNodes.clear();
    m_head = nullptr;
    m_state = ModuleState::Uninitialized;
}

// ----------------------------------------
// 注册节点
// ----------------------------------------
void AnalysisModule::registerNode(IAnalysisNode *node)
{
    if (!node) return;

    // 如果链表为空,作为头节点
    if (!m_head) {
        m_head = node;
    } else {
        // 否则找到链表末尾,加入
        IAnalysisNode *current = m_head;
        while (current->next()) {
            current = current->next();
        }
        current->setNext(node);
    }

    m_allNodes.append(node);
    qDebug() << "Analysis node registered:" << node->name();
}

// ----------------------------------------
// 执行分析
// ----------------------------------------
AnalysisResult AnalysisModule::execute(const AnalysisData &data)
{
    // 如果没有节点,返回错误
    if (!m_head) {
        return {false, "No analysis nodes registered", {}, {}, 0};
    }

    // 沿着责任链处理
    AnalysisData currentData = data;
    AnalysisResult finalResult;

    IAnalysisNode *current = m_head;

    while (current) {
        // 调用当前节点的处理方法
        AnalysisResult result = current->process(currentData);

        // 如果节点返回失败,停止处理
        if (!result.success) {
            return result;
        }

        // 将输出数据作为下一个节点的输入
        currentData.processedData = result.outputData;

        // 记录最后一个成功的结果
        finalResult = result;

        // 移动到下一个节点
        current = current->next();
    }

    // 处理完成,发出信号
    emit analysisCompleted(finalResult);

    return finalResult;
}

// ----------------------------------------
// 批量执行
// ----------------------------------------
QVector<AnalysisResult> AnalysisModule::executeBatch(
    const QVector<AnalysisData> &dataList)
{
    QVector<AnalysisResult> results;
    results.reserve(dataList.size());

    // 使用QtConcurrent进行并行处理
    // blockingMap会阻塞直到所有任务完成
    QtConcurrent::blockingMap(dataList, [this, &results](const AnalysisData &data) {
        results.append(execute(data));
    });

    return results;
}

六、UI模块设计

6.1 为什么选择MVP模式?

复制代码
MVP vs MVC 对比:

MVC(Model-View-Controller)
┌────────┐     ┌────────┐     ┌────────┐
│ Model  │ ←→ │ View   │ ←→ │Controller│
└────────┘     └────────┘     └────────┘
   ↑              ↑              ↓
   └──────────────┴──────────────┘
         View直接观察Model

MVP(Model-View-Presenter)
┌────────┐     ┌────────┐     ┌────────┐
│ Model  │ ←→ │Presenter│←→ │  View   │
└────────┘     └────────┘     └────────┘
                   ↓
              View不直接
              访问Model

MVP的优势

  1. View和Model完全解耦
  2. View只需要展示数据,不需要知道数据从哪来
  3. Presenter包含所有业务逻辑,方便测试
  4. View可以被mock,便于单元测试

为什么对测试仪器UI重要?

  • 仪器UI通常很复杂,逻辑多
  • 需要频繁与仪器硬件交互(难以测试)
  • MVP让我们可以mock掉通讯模块,单独测试UI逻辑

6.2 完整代码实现

cpp 复制代码
// MainWindow.h

#pragma once
#include <QMainWindow>
#include <QTabWidget>
#include <QTableWidget>
#include <QPushButton>
#include <QComboBox>
#include <QSpinBox>
#include <QLabel>
#include <QDial>
#include <QLCDNumber>
#include <QCustomPlot>

class ParameterManager;
class AnalysisModule;
class ICommInterface;

class MainWindow : public QMainWindow {
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = nullptr);
    ~MainWindow() override;
    void setupUi();

    // ----------------------------------------
    // 公共方法(供外部调用)
    // ----------------------------------------
    // 这些是Presenter暴露给其他模块的接口

private slots:
    // === 通讯相关 ===
    void onConnectClicked();
    void onDisconnectClicked();

    // === 参数相关 ===
    // 参数变化时自动调用,更新UI显示
    void onParameterChanged(const QString &key,
                            const QVariant &oldVal,
                            const QVariant &newVal);

    // === 数据相关 ===
    // 收到新数据时调用,更新显示
    void onDataReceived(const QByteArray &data);

    // 分析完成时调用
    void onAnalysisCompleted(const AnalysisResult &result);

    // === 文件操作 ===
    void onSaveData();
    void onLoadData();

    // === 状态变化 ===
    void onModuleStateChanged(const QString &module, ModuleState state);

signals:
    // 发出请求让其他模块处理
    void saveDataRequested(const QString &path);
    void loadDataRequested(const QString &path);

private:
    // ----------------------------------------
    // UI创建方法
    // ----------------------------------------
    void createMenuBar();
    void createToolBar();
    void createStatusBar();

    // 创建各个Tab页面
    QWidget* createMonitorTab();    // 监视面板
    QWidget* createConfigTab();     // 参数配置面板
    QWidget* createDataTab();       // 数据表格面板

    // ----------------------------------------
    // 数据更新方法
    // ----------------------------------------
    // 这些是View的实现细节
    void updateWaveform(const QVector<double> &data);
    void updateGauge(double value);
    void updateDataTable(const DataRecord &record);

    // ----------------------------------------
    // 成员变量
    // ----------------------------------------

    // 标签页
    QTabWidget *m_tabWidget = nullptr;

    // 监视面板组件
    QCustomPlot *m_waveformPlot = nullptr;  // 波形图
    QDial *m_gaugeDial = nullptr;           // 仪表盘
    QLCDNumber *m_lcdNumber = nullptr;      // 数值显示
    QLabel *m_valueLabel = nullptr;          // 当前值标签

    // 连接控制组件
    QComboBox *m_portCombo = nullptr;       // 串口选择
    QSpinBox *m_baudRateSpin = nullptr;    // 波特率设置
    QPushButton *m_connectBtn = nullptr;   // 连接按钮

    // 数据表格
    QTableWidget *m_dataTable = nullptr;   // 数据表格
    int m_rowCount = 0;                     // 行计数器

    // 状态栏组件
    QLabel *m_connectionLabel = nullptr;   // 连接状态标签
    QLabel *m_dataRateLabel = nullptr;      // 数据率标签

    // 数据缓存(用于波形显示)
    QVector<double> m_waveformData;
};
cpp 复制代码
// MainWindow.cpp

#include "MainWindow.h"
#include <QMenuBar>
#include <QToolBar>
#include <QStatusBar>
#include <QAction>
#include <QFileDialog>
#include <QSerialPortInfo>
#include <QGridLayout>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QGroupBox>
#include <QHeaderView>
#include <QCheckBox>
#include <QDoubleSpinBox>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    // 设置窗口属性
    setWindowTitle("测试仪器框架 - Qt Demo");
    resize(1280, 800);

    // 初始化UI
    setupUi();
}

MainWindow::~MainWindow() {}

void MainWindow::setupUi()
{
    // 创建各个UI部分
    createMenuBar();
    createToolBar();
    createStatusBar();

    // 创建主TabWidget
    m_tabWidget = new QTabWidget(this);
    m_tabWidget->addTab(createMonitorTab(), "监视面板");
    m_tabWidget->addTab(createConfigTab(), "参数配置");
    m_tabWidget->addTab(createDataTab(), "数据表格");
    setCentralWidget(m_tabWidget);

    // 连接信号槽
    // 从参数管理器监听参数变化
    auto *pm = ModuleManager::instance()->getModule<ParameterManager>("ParameterManager");
    if (pm) {
        connect(pm, &ParameterManager::parameterChanged,
                this, &MainWindow::onParameterChanged);
    }
}

// ----------------------------------------
// 创建菜单栏
// ----------------------------------------
void MainWindow::createMenuBar()
{
    QMenuBar *menuBar = this->menuBar();

    // 文件菜单
    QMenu *fileMenu = menuBar->addMenu("文件(&F)");
    fileMenu->addAction("新建", this, [](){}, QKeySequence::New);
    fileMenu->addAction("打开...", this, &MainWindow::onLoadData,
                        QKeySequence::Open);
    fileMenu->addAction("保存", this, &MainWindow::onSaveData,
                        QKeySequence::Save);
    fileMenu->addSeparator();
    fileMenu->addAction("退出", this &QWidget::close,
                        QKeySequence::Quit);

    // 视图菜单
    QMenu *viewMenu = menuBar->addMenu("视图(&V)");
    viewMenu->addAction("监视面板", this,
                        [this](){ m_tabWidget->setCurrentIndex(0); });
    viewMenu->addAction("参数配置", this,
                        [this](){ m_tabWidget->setCurrentIndex(1); });
    viewMenu->addAction("数据表格", this,
                        [this](){ m_tabWidget->setCurrentIndex(2); });
}

// ----------------------------------------
// 创建工具栏
// ----------------------------------------
void MainWindow::createToolBar()
{
    QToolBar *toolBar = addToolBar("主工具栏");

    // 连接按钮(可切换)
    QAction *connectAct = new QAction("连接", this);
    connectAct->setCheckable(true);
    connect(connectAct, &QAction::toggled, this, [this](bool checked) {
        if (checked) {
            onConnectClicked();
        } else {
            onDisconnectClicked();
        }
    });

    toolBar->addAction(connectAct);
    toolBar->addSeparator();
    toolBar->addAction(new QAction("开始采集", this));
    toolBar->addAction(new QAction("停止采集", this));
}

// ----------------------------------------
// 创建状态栏
// ----------------------------------------
void MainWindow::createStatusBar()
{
    QStatusBar *statusBar = this->statusBar();

    // 连接状态
    m_connectionLabel = new QLabel("未连接");
    m_connectionLabel->setStyleSheet(
        "QLabel { color: red; font-weight: bold; }"
    );

    // 数据率
    m_dataRateLabel = new QLabel("数据率: 0 kS/s");

    statusBar->addWidget(m_connectionLabel);
    statusBar->addPermanentWidget(m_dataRateLabel);
}

// ----------------------------------------
// 创建监视面板
// ----------------------------------------
QWidget* MainWindow::createMonitorTab()
{
    QWidget *tab = new QWidget(this);
    QHBoxLayout *layout = new QHBoxLayout(tab);

    // === 左侧:波形显示 ===
    QWidget *waveWidget = new QWidget(this);
    QVBoxLayout *waveLayout = new QVBoxLayout(waveWidget);

    waveLayout->addWidget(new QLabel("实时波形", this));

    // 创建波形图
    m_waveformPlot = new QCustomPlot(this);
    m_waveformPlot->addGraph();  // 添加一条曲线
    m_waveformPlot->graph(0)->setPen(QPen(Qt::blue));  // 蓝色曲线
    m_waveformPlot->xAxis->setLabel("样本点");
    m_waveformPlot->yAxis->setLabel("幅度");
    m_waveformPlot->setMinimumSize(700, 350);

    // 启用拖拽和缩放
    m_waveformPlot->setInteraction(QCP::iRangeZoom, true);
    m_waveformPlot->setInteraction(QCP::iRangeDrag, true);

    waveLayout->addWidget(m_waveformPlot);

    // === 右侧:仪表盘 ===
    QWidget *gaugeWidget = new QWidget(this);
    QVBoxLayout *gaugeLayout = new QVBoxLayout(gaugeWidget);
    gaugeLayout->addStretch();  // 顶部留空

    // 圆形仪表
    m_gaugeDial = new QDial(this);
    m_gaugeDial->setMinimum(0);
    m_gaugeDial->setMaximum(100);
    m_gaugeDial->setValue(50);
    m_gaugeDial->setMinimumSize(150, 150);
    gaugeLayout->addWidget(m_gaugeDial, 0, Qt::AlignHCenter);

    // 数值显示
    m_lcdNumber = new QLCDNumber(this);
    m_lcdNumber->setDigitCount(8);
    m_lcdNumber->display("0000.000");
    gaugeLayout->addWidget(m_lcdNumber);

    // 当前值标签
    m_valueLabel = new QLabel("当前值: 0.00");
    m_valueLabel->setAlignment(Qt::AlignCenter);
    gaugeLayout->addWidget(m_valueLabel);

    gaugeLayout->addStretch();  // 底部留空

    // 添加到主布局
    layout->addWidget(waveWidget, 2);  // 波形占2/3宽度
    layout->addWidget(gaugeWidget, 1);  // 仪表占1/3宽度

    return tab;
}

// ----------------------------------------
// 创建参数配置面板
// ----------------------------------------
QWidget* MainWindow::createConfigTab()
{
    QWidget *tab = new QWidget(this);
    QGridLayout *grid = new QGridLayout(tab);
    int row = 0;

    // === 通讯配置 ===
    QGroupBox *commGroup = new QGroupBox("通讯配置", tab);
    QGridLayout *commLayout = new QGridLayout(commGroup);

    commLayout->addWidget(new QLabel("串口:", tab), 0, 0);
    m_portCombo = new QComboBox(tab);
    // 扫描可用串口
    for (const QSerialPortInfo &info : QSerialPortInfo::availablePorts()) {
        m_portCombo->addItem(info.portName());
    }
    commLayout->addWidget(m_portCombo, 0, 1);

    commLayout->addWidget(new QLabel("波特率:", tab), 1, 0);
    m_baudRateSpin = new QSpinBox(tab);
    m_baudRateSpin->setRange(9600, 921600);
    m_baudRateSpin->setSingleStep(9600);
    m_baudRateSpin->setValue(115200);
    commLayout->addWidget(m_baudRateSpin, 1, 1);

    m_connectBtn = new QPushButton("连接", tab);
    connect(m_connectBtn, &QPushButton::clicked,
            this, &MainWindow::onConnectClicked);
    commLayout->addWidget(m_connectBtn, 2, 0, 1, 2);

    grid->addWidget(commGroup, row++, 0, 1, 2);

    // === 采集配置 ===
    QGroupBox *acqGroup = new QGroupBox("采集配置", tab);
    QGridLayout *acqLayout = new QGridLayout(acqGroup);

    acqLayout->addWidget(new QLabel("采样率(Hz):", tab), 0, 0);
    QSpinBox *rateSpin = new QSpinBox(tab);
    rateSpin->setObjectName("acquisitionRate");
    rateSpin->setRange(1, 100000);
    rateSpin->setValue(1000);
    acqLayout->addWidget(rateSpin, 0, 1);

    acqLayout->addWidget(new QLabel("采集时长(s):", tab), 1, 0);
    QDoubleSpinBox *durSpin = new QDoubleSpinBox(tab);
    durSpin->setObjectName("acquisitionDuration");
    durSpin->setRange(0.001, 3600);
    durSpin->setValue(1.0);
    acqLayout->addWidget(durSpin, 1, 1);

    acqLayout->addWidget(new QLabel("触发模式:", tab), 2, 0);
    QComboBox *trigCombo = new QComboBox(tab);
    trigCombo->setObjectName("triggerMode");
    trigCombo->addItems({"auto", "manual", "external"});
    acqLayout->addWidget(trigCombo, 2, 1);

    grid->addWidget(acqGroup, row++, 0, 1, 2);

    // === 显示配置 ===
    QGroupBox *dispGroup = new QGroupBox("显示配置", tab);
    QGridLayout *dispLayout = new QGridLayout(dispGroup);

    dispLayout->addWidget(new QLabel("刷新率(Hz):", tab), 0, 0);
    QSpinBox *refSpin = new QSpinBox(tab);
    refSpin->setObjectName("displayRefreshRate");
    refSpin->setRange(10, 120);
    refSpin->setValue(60);
    dispLayout->addWidget(refSpin, 0, 1);

    QCheckBox *gridCheck = new QCheckBox("显示网格", tab);
    gridCheck->setObjectName("displayShowGrid");
    gridCheck->setChecked(true);
    dispLayout->addWidget(gridCheck, 1, 0, 1, 2);

    QCheckBox *legendCheck = new QCheckBox("显示图例", tab);
    legendCheck->setObjectName("displayShowLegend");
    legendCheck->setChecked(true);
    dispLayout->addWidget(legendCheck, 2, 0, 1, 2);

    grid->addWidget(dispGroup, row++, 0, 1, 2);

    // === 参数绑定 ===
    // 将UI控件与参数管理器绑定
    // 当控件值变化时,自动更新参数
    // 当参数变化时,自动更新控件
    auto *pm = ModuleManager::instance()->getModule<ParameterManager>("ParameterManager");
    if (pm) {
        // 查找所有有objectName的控件并绑定
        QList<QWidget*> widgets = tab->findChildren<QWidget*>();
        for (QWidget *w : widgets) {
            QString objName = w->objectName();
            if (objName.isEmpty()) continue;

            // SpinBox绑定
            if (auto *s = qobject_cast<QSpinBox*>(w)) {
                s->setValue(pm->get(objName).toInt());
                connect(s, &QSpinBox::valueChanged, this,
                        [pm, objName](int v) { pm->set(objName, v); });
            }
            // DoubleSpinBox绑定
            else if (auto *ds = qobject_cast<QDoubleSpinBox*>(w)) {
                ds->setValue(pm->get(objName).toDouble());
                connect(ds, &QDoubleSpinBox::valueChanged, this,
                        [pm, objName](double v) { pm->set(objName, v); });
            }
            // ComboBox绑定
            else if (auto *c = qobject_cast<QComboBox*>(w)) {
                connect(c, &QComboBox::currentTextChanged, this,
                        [pm, objName](const QString &v) { pm->set(objName, v); });
            }
            // CheckBox绑定
            else if (auto *cb = qobject_cast<QCheckBox*>(w)) {
                cb->setChecked(pm->get(objName).toBool());
                connect(cb, &QCheckBox::toggled, this,
                        [pm, objName](bool v) { pm->set(objName, v); });
            }
        }
    }

    return tab;
}

// ----------------------------------------
// 创建数据表格面板
// ----------------------------------------
QWidget* MainWindow::createDataTab()
{
    QWidget *tab = new QWidget(this);
    QVBoxLayout *layout = new QVBoxLayout(tab);

    // 工具栏
    QHBoxLayout *toolbar = new QHBoxLayout();
    QPushButton *clearBtn = new QPushButton("清空", tab);
    QPushButton *exportCsvBtn = new QPushButton("导出CSV", tab);
    toolbar->addWidget(clearBtn);
    toolbar->

    toolbar->addWidget(exportCsvBtn);
    toolbar->addStretch();
    layout->addLayout(toolbar);

    // 数据表格
    m_dataTable = new QTableWidget(tab);
    m_dataTable->setColumnCount(5);
    m_dataTable->setHorizontalHeaderLabels(
        {"序号", "时间戳", "通道1", "通道2", "状态"}
    );
    m_dataTable->setAlternatingRowColors(true);
    m_dataTable->setSelectionBehavior(QAbstractItemView::SelectRows);
    m_dataTable->setEditTriggers(QAbstractItemView::NoEditTriggers);  // 只读
    m_dataTable->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
    layout->addWidget(m_dataTable);

    // 清空按钮
    connect(clearBtn, &QPushButton::clicked, this, [this]() {
        m_dataTable->setRowCount(0);
        m_rowCount = 0;
    });

    return tab;
}

// ----------------------------------------
// 连接处理
// ----------------------------------------
void MainWindow::onConnectClicked()
{
    auto *comm = ModuleManager::instance()->getModule<ICommInterface>("SerialComm");
    if (!comm) return;

    // 获取UI配置
    SerialConfig cfg;
    cfg.portName = m_portCombo->currentText();
    cfg.baudRate = m_baudRateSpin->value();

    // 连接
    if (comm->connect(&cfg)) {
        // 更新UI
        m_connectionLabel->setText("已连接: " + cfg.portName);
        m_connectionLabel->setStyleSheet(
            "QLabel { color: green; font-weight: bold; }"
        );
        m_connectBtn->setText("断开");
    }
}

void MainWindow::onDisconnectClicked()
{
    auto *comm = ModuleManager::instance()->getModule<ICommInterface>("SerialComm");
    if (comm) comm->disconnect();

    m_connectionLabel->setText("未连接");
    m_connectionLabel->setStyleSheet(
        "QLabel { color: red; }"
    );
    m_connectBtn->setText("连接");
}

// ----------------------------------------
// 参数变化处理
// ----------------------------------------
void MainWindow::onParameterChanged(const QString &key,
                                     const QVariant &,
                                     const QVariant &)
{
    // 当参数变化时,更新对应的UI控件
    // 这里可以扩展:根据参数名更新特定控件

    Q_UNUSED(key)
    // 实际实现中可以:
    // if (key == "device.baudRate") { ... }
}

// ----------------------------------------
// 数据处理
// ----------------------------------------
void MainWindow::onDataReceived(const QByteArray &data)
{
    // 模拟数据处理
    // 实际项目中,这里应该解析原始数据

    static int index = 0;
    index++;

    // 简单模拟:生成一个正弦波
    double value = static_cast<double>(data.size()) * 0.1 +
                   std::sin(index * 0.1) * 50;

    // 更新波形数据
    m_waveformData.append(value);
    if (m_waveformData.size() > 1000) {
        // 保持最新1000个点
        m_waveformData.removeFirst();
    }
    updateWaveform(m_waveformData);

    // 更新仪表
    int percent = qBound(0, static_cast<int>((value + 100) / 2), 100);
    m_gaugeDial->setValue(percent);
    m_lcdNumber->display(QString::number(value, 'f', 3));
    m_valueLabel->setText(QString("当前值: %1").arg(value, 8, 'f', 2));

    // 添加到数据表格
    int row = m_dataTable->rowCount();
    m_dataTable->insertRow(row);
    m_dataTable->setItem(row, 0, new QTableWidgetItem(QString::number(++m_rowCount)));
    m_dataTable->setItem(row, 1, new QTableWidgetItem(
        QTime::currentTime().toString("hh:mm:ss.zzz")));
    m_dataTable->setItem(row, 2, new QTableWidgetItem(
        QString::number(value, 'f', 4)));
    m_dataTable->setItem(row, 3, new QTableWidgetItem(
        QString::number(value * 1.1, 'f', 4)));
    m_dataTable->setItem(row, 4, new QTableWidgetItem("OK"));

    // 自动滚动到最后一行
    m_dataTable->scrollToBottom();
}

void MainWindow::onAnalysisCompleted(const AnalysisResult &result)
{
    if (result.success) {
        // 可以在这里更新分析结果显示
        qDebug() << "Analysis done in" << result.elapsedMs << "ms"
                 << ":" << result.message;
    }
}

// ----------------------------------------
// 数据更新方法
// ----------------------------------------
void MainWindow::updateWaveform(const QVector<double> &data)
{
    // 生成X轴数据
    QVector<double> x(data.size());
    for (int i = 0; i < data.size(); ++i) {
        x[i] = i;
    }

    // 更新曲线数据
    m_waveformPlot->graph(0)->setData(x, data);

    // 设置坐标轴范围
    m_waveformPlot->xAxis->setRange(0, data.size());
    m_waveformPlot->yAxis->setRange(-120, 120);

    // 重绘
    m_waveformPlot->replot();
}

void MainWindow::updateGauge(double value)
{
    int percent = qBound(0, static_cast<int>((value + 100) / 2), 100);
    m_gaugeDial->setValue(percent);
    m_lcdNumber->display(QString::number(value, 'f', 3));
}

void MainWindow::updateDataTable(const DataRecord &record)
{
    int row = m_dataTable->rowCount();
    m_dataTable->insertRow(row);
    m_dataTable->setItem(row, 0, new QTableWidgetItem(
        QString::number(++m_rowCount)));
    m_dataTable->setItem(row, 1, new QTableWidgetItem(
        QString::number(record.timestamp)));
    // ... 其他字段
}

// ----------------------------------------
// 文件操作
// ----------------------------------------
void MainWindow::onSaveData()
{
    QString path = QFileDialog::getSaveFileName(
        this, "保存数据", "",
        "二进制 (*.bin);;CSV (*.csv)"
    );
    if (!path.isEmpty()) {
        emit saveDataRequested(path);
    }
}

void MainWindow::onLoadData()
{
    QString path = QFileDialog::getOpenFileName(
        this, "加载数据", "",
        "所有文件 (*.*)"
    );
    if (!path.isEmpty()) {
        emit loadDataRequested(path);
    }
}

七、保存解析模块设计

7.1 存储方案选择

复制代码
存储需求分析:

┌─────────────────────────────────────────────────────┐
│                    数据特点                          │
├─────────────────────────────────────────────────────┤
│ • 采样率高(可达MHz)                                │
│ • 数据量大(长时间采集可达GB级)                    │
│ • 需要随机访问(回放特定时间段)                    │
│ • 可能需要与其他软件共享数据                        │
└─────────────────────────────────────────────────────┘

存储格式对比:

┌──────────┬──────────┬──────────┬──────────────────┐
│  格式   │   优点   │   缺点   │     适用场景      │
├──────────┼──────────┼──────────┼──────────────────┤
│ 二进制   │ 速度快   │ 不通用   │ 高速采集、内部   │
│          │ 体积小   │ 无法直接 │                  │
├──────────┼──────────┼──────────┼──────────────────┤
│ CSV      │ 通用     │ 速度慢   │ 数据交换、调试   │
│          │ 可用Excel│ 体积大   │                  │
├──────────┼──────────┼──────────┼──────────────────┤
│ HDF5     │ 高速     │ 依赖库   │ 科学计算、大数据 │
│          │ 可压缩   │ 学习成本 │                  │
│          │ 自描述   │          │                  │
├──────────┼──────────┼──────────┼──────────────────┤
│ SQLite   │ 通用     │ 不适合   │ 配置、元数据     │
│          │ 查询方便 │ 高速写入 │                  │
└──────────┴──────────┴──────────┴──────────────────┘

设计决策:
1. 提供多种格式支持(策略模式)
2. 默认使用二进制格式(速度优先)
3. 用户可切换格式
4. 使用工厂模式创建处理器

7.2 完整代码实现

cpp 复制代码
// StorageModule.h

#pragma once
#include "../Core/ModuleInterface.h"
#include <QString>
#include <QVariantMap>
#include <QJsonObject>

// ----------------------------------------
// 数据结构
// ----------------------------------------

// 单条数据记录
struct DataRecord {
    qint64 timestamp;              // 毫秒时间戳
    QVector<double> channels;      // 各通道数据
    QVariantMap metadata;         // 附加信息(如状态码、标志位)
    int status = 0;               // 状态码
};

// 一段时间的数据块
struct DataBlock {
    qint64 startTime;              // 起始时间
    qint64 endTime;                // 结束时间
    QVector<DataRecord> records;   // 记录列表
    QVariantMap metadata;          // 块的元数据
};

// 存储格式枚举
enum class StorageFormat {
    Binary,   // 自定义二进制格式(高速)
    Csv,      // CSV格式(通用)
    Json,     // JSON格式(可读性好)
    HDF5      // HDF5格式(需要HDF5库)
};

// ----------------------------------------
// 存储处理器接口(策略模式)
// ----------------------------------------
class IStorageHandler {
public:
    virtual ~IStorageHandler() = default;

    // 打开文件
    // append=true表示追加模式,false表示覆盖
    virtual bool open(const QString &path, bool append = false) = 0;

    // 关闭文件
    virtual void close() = 0;

    // 写入数据块
    virtual bool write(const DataBlock &block) = 0;

    // 读取数据块
    virtual bool read(DataBlock &block) = 0;

    // 检查是否打开
    virtual bool isOpen() const = 0;
};

// ----------------------------------------
// 二进制存储处理器
// ----------------------------------------
class BinaryStorageHandler : public IStorageHandler {
public:
    bool open(const QString &path, bool append) override;
    void close() override;
    bool write(const DataBlock &block) override;
    bool read(DataBlock &block) override;
    bool isOpen() const override { return m_file && m_file->isOpen(); }

private:
    QFile *m_file = nullptr;
    QDataStream *m_stream = nullptr;
};

// ----------------------------------------
// CSV存储处理器
// ----------------------------------------
class CsvStorageHandler : public IStorageHandler {
public:
    bool open(const QString &path, bool append) override;
    void close() override;
    bool write(const DataBlock &block) override;
    bool read(DataBlock &block) override;
    bool isOpen() const override { return m_file && m_file->isOpen(); }

private:
    QFile *m_file = nullptr;
    QTextStream *m_stream = nullptr;
};

// ----------------------------------------
// 存储模块
// ----------------------------------------
class StorageModule : public ModuleInterface {
    Q_OBJECT
    Q_DECLARE_SINGLETON(StorageModule)

public:
    // 设置存储格式
    void setStorageFormat(StorageFormat format);

    // 获取当前格式
    StorageFormat currentFormat() const { return m_currentFormat; }

    // 写入数据块
    bool writeBlock(const DataBlock &block);

    // 追加单条记录(带缓冲)
    bool appendRecord(const DataRecord &record);

    // 冲刷缓冲区(强制写入)
    void flush();

    // 获取文件信息
    QVariantMap getFileInfo(const QString &path) const;

    QString name() const override { return "StorageModule"; }
    bool initialize() override;
    bool start() override {
        m_state = ModuleState::Running;
        return true;
    }
    bool stop() override {
        // 关闭时自动冲刷缓冲区
        if (m_handler && m_handler->isOpen()) {
            flush();
            m_handler->close();
        }
        m_state = ModuleState::Stopped;
        return true;
    }
    void cleanup() override;
    QStringList dependencies() const override { return {"ParameterManager"}; }

signals:
    void storageOpened(const QString &path);
    void storageClosed(const QString &path);
    void recordWritten(int count);

private:
    explicit StorageModule(QObject *parent = nullptr);
    ~StorageModule() override;

    // 工厂方法:根据格式创建处理器
    IStorageHandler* createHandler(StorageFormat format);

    QString m_currentPath;                       // 当前文件路径
    IStorageHandler *m_handler = nullptr;        // 当前处理器
    StorageFormat m_currentFormat = StorageFormat::Binary;

    // 写入缓冲
    DataBlock m_buffer;                          // 缓冲数据块
    int m_bufferSize = 100;                      // 缓冲记录数
};
cpp 复制代码
// StorageModule.cpp

#include "StorageModule.h"
#include <QFile>
#include <QDataStream>
#include <QTextStream>
#include <QDebug>

// ============================================================
// BinaryStorageHandler 实现
// ============================================================

bool BinaryStorageHandler::open(const QString &path, bool append)
{
    close();

    m_file = new QFile(path);
    auto mode = append ? QIODevice::Append : (QIODevice::WriteOnly | QIODevice::Truncate);

    if (!m_file->open(mode)) {
        qWarning() << "Failed to open file:" << path;
        delete m_file;
        m_file = nullptr;
        return false;
    }

    m_stream = new QDataStream(m_file);
    // 设置字节序和数据流版本(保证跨平台兼容性)
    m_stream->setByteOrder(QDataStream::LittleEndian);
    m_stream->setVersion(QDataStream::Qt_5_15);

    return true;
}

void BinaryStorageHandler::close()
{
    if (m_stream) {
        delete m_stream;
        m_stream = nullptr;
    }
    if (m_file) {
        m_file->close();
        delete m_file;
        m_file = nullptr;
    }
}

bool BinaryStorageHandler::write(const DataBlock &block)
{
    if (!m_stream) return false;

    // 写入块头
    *m_stream << block.startTime
              << block.endTime
              << block.metadata;

    // 写入记录数
    *m_stream << static_cast<quint32>(block.records.size());

    // 逐条写入记录
    for (const DataRecord &r : block.records) {
        *m_stream << r.timestamp;

        // 写入通道数和数据
        *m_stream << static_cast<quint32>(r.channels.size());
        for (double v : r.channels) {
            *m_stream << v;
        }

        // 写入状态和元数据
        *m_stream << r.status << r.metadata;
    }

    // 确保写入磁盘
    m_file->flush();

    return true;
}

bool BinaryStorageHandler::read(DataBlock &block)
{
    if (!m_stream || m_stream->atEnd()) return false;

    quint32 recordCount;

    *m_stream >> block.startTime
              >> block.endTime
              >> block.metadata;

    *m_stream >> recordCount;
    block.records.resize(recordCount);

    for (quint32 i = 0; i < recordCount; ++i) {
        quint32 channelCount;

        *m_stream >> block.records[i].timestamp
                  >> channelCount;

        block.records[i].channels.resize(channelCount);
        for (quint32 j = 0; j < channelCount; ++j) {
            *m_stream >> block.records[i].channels[j];
        }

        *m_stream >> block.records[i].status
                  >> block.records[i].metadata;
    }

    return true;
}

// ============================================================
// CsvStorageHandler 实现
// ============================================================

bool CsvStorageHandler::open(const QString &path, bool append)
{
    close();

    m_file = new QFile(path);
    auto mode = append ? QIODevice::Append : (QIODevice::WriteOnly | QIODevice::Truncate);

    if (!m_file->open(mode)) {
        delete m_file;
        m_file = nullptr;
        return false;
    }

    m_stream = new QTextStream(m_file);

    // 如果不是追加模式,写入表头
    if (!append) {
        *m_stream << "timestamp,channel_1,channel_2,channel_3,status\n";
    }

    return true;
}

void CsvStorageHandler::close()
{
    if (m_stream) {
        m_stream->flush();
        delete m_stream;
        m_stream = nullptr;
    }
    if (m_file) {
        m_file->close();
        delete m_file;
        m_file = nullptr;
    }
}

bool CsvStorageHandler::write(const DataBlock &block)
{
    if (!m_stream) return false;

    // 逐条写入CSV
    for (const DataRecord &r : block.records) {
        // 时间戳
        *m_stream << r.timestamp;

        // 各通道数据
        for (double v : r.channels) {
            *m_stream << "," << v;
        }

        // 状态
        *m_stream << "," << r.status << "\n";
    }

    // 确保写入
    m_stream->flush();

    return true;
}

bool CsvStorageHandler::read(DataBlock &block)
{
    // CSV读取实现略(与写入类似但方向相反)
    Q_UNUSED(block)
    return false;
}

// ============================================================
// StorageModule 实现
// ============================================================

StorageModule::StorageModule(QObject *parent)
    : ModuleInterface(parent)
{
}

StorageModule::~StorageModule()
{
    cleanup();
}

bool StorageModule::initialize()
{
    // 从参数读取默认格式
    auto *pm = ModuleManager::instance()->getModule<ParameterManager>("ParameterManager");
    if (pm) {
        QString fmt = pm->get("storage.format").toString();
        if (fmt == "csv") {
            m_currentFormat = StorageFormat::Csv;
        } else if (fmt == "hdf5") {
            m_currentFormat = StorageFormat::HDF5;
        } else {
            m_currentFormat = StorageFormat::Binary;
        }
    }

    // 创建处理器
    m_handler = createHandler(m_currentFormat);

    m_state = ModuleState::Initialized;
    return true;
}

void StorageModule::cleanup()
{
    if (m_handler) {
        if (m_handler->isOpen()) {
            flush();
            m_handler->close();
        }
        delete m_handler;
        m_handler = nullptr;
    }
    m_state = ModuleState::Uninitialized;
}

IStorageHandler* StorageModule::createHandler(StorageFormat format)
{
    switch (format) {
        case StorageFormat::Csv:
            return new CsvStorageHandler();
        case StorageFormat::Binary:
        default:
            return new BinaryStorageHandler();
    }
}

void StorageModule::setStorageFormat(StorageFormat format)
{
    if (m_currentFormat == format) return;

    // 关闭现有处理器
    if (m_handler && m_handler->isOpen()) {
        flush();
        m_handler->close();
    }
    delete m_handler;

    m_currentFormat = format;
    m_handler = createHandler(format);

    // 更新参数
    auto *pm = ModuleManager::instance()->getModule<ParameterManager>("ParameterManager");
    if (pm) {
        QString fmtStr;
        switch (format) {
            case StorageFormat::Csv: fmtStr = "csv"; break;
            case StorageFormat::HDF5: fmtStr = "hdf5"; break;
            default: fmtStr = "binary";
        }
        pm->set("storage.format", fmtStr);
    }
}

bool StorageModule::writeBlock(const DataBlock &block)
{
    if (!m_handler || !m_handler->isOpen()) return false;
    return m_handler->write(block);
}

bool StorageModule::appendRecord(const DataRecord &record)
{
    // 添加到缓冲区
    m_buffer.records.append(record);

    // 如果缓冲区满了,冲刷到磁盘
    if (m_buffer.records.size() >= m_bufferSize) {
        if (!m_handler || !m_handler->isOpen()) return false;

        // 更新时间戳
        if (m_buffer.records.size() == 1) {
            m_buffer.startTime = record.timestamp;
        }
        m_buffer.endTime = record.timestamp;

        // 写入
        DataBlock flushBlock = m_buffer;
        m_buffer.records.clear();

        bool ok = m_handler->write(flushBlock);
        if (ok) {
            emit recordWritten(flushBlock.records.size());
        }
        return ok;
    }

    return true;
}

void StorageModule::flush()
{
    // 冲刷剩余数据
    if (!m_buffer.records.isEmpty()) {
        if (m_handler && m_handler->isOpen()) {
            if (!m_buffer.startTime && !m_buffer.endTime) {
                if (!m_buffer.records.isEmpty()) {
                    m_buffer.startTime = m_buffer.records.first().timestamp;
                    m_buffer.endTime = m_buffer.records.last().timestamp;
                }
            }
            m_handler->write(m_buffer);
            emit recordWritten(m_buffer.records.size());
        }
        m_buffer.records.clear();
    }
}

QVariantMap StorageModule::getFileInfo(const QString &path) const
{
    QVariantMap info;
    QFileInfo fi(path);

    info["size"] = fi.size();
    info["modified"] = fi.lastModified().toString(Qt::ISODate);
    info["exists"] = fi.exists();

    // 读取二进制文件的头信息
    if (info["exists"].toBool() && m_currentFormat == StorageFormat::Binary) {
        QFile f(path);
        if (f.open(QIODevice::ReadOnly)) {
            QDataStream s(&f);
            qint64 start, end;
            s >> start >> end;
            info["startTime"] = start;
            info["endTime"] = end;
            info["durationMs"] = end - start;
            f.close();
        }
    }

    return info;
}

八、项目结构与入口

8.1 目录结构

复制代码
instrument-framework/
├── CMakeLists.txt                    # CMake构建配置
├── src/
│   ├── main.cpp                      # 程序入口
│   ├── Core/                         # 核心基础设施
│   │   ├── ModuleInterface.h          # 模块接口定义
│   │   └── ModuleManager.h / .cpp     # 模块管理器
│   ├── Comm/                         # 通讯模块
│   │   ├── ICommInterface.h           # 通讯接口
│   │   ├── SerialComm.h / .cpp        # 串口通讯
│   │   ├── TcpComm.h / .cpp           # TCP通讯
│   │   └── ProtocolParser.h / .cpp   # 协议解析
│   ├── Parameters/                    # 参数配置
│   │   └── ParameterManager.h / .cpp # 参数管理器
│   ├── Analysis/                      # 分析计算
│   │   └── AnalysisModule.h / .cpp   # 分析模块
│   ├── Storage/                       # 保存解析
│   │   └── StorageModule.h / .cpp    # 存储模块
│   └── UI/                           # 用户界面
│       └── MainWindow.h / .cpp        # 主窗口
├── tests/                            # 单元测试
│   └── test_modules.cpp              # 模块测试
└── docs/                             # 文档
    └── README.md                      # 项目说明

8.2 main.cpp 完整实现

cpp 复制代码
// main.cpp - 程序入口

#include <QApplication>
#include <QDebug>

// 包含所有模块的头文件
#include "Core/ModuleManager.h"
#include "Comm/SerialComm.h"
#include "Comm/TcpComm.h"
#include "Parameters/ParameterManager.h"
#include "Analysis/AnalysisModule.h"
#include "Storage/StorageModule.h"
#include "UI/MainWindow.h"

// 便捷类型别名
using CommInterface = ICommInterface;

int main(int argc, char *argv[])
{
    // 创建应用
    QApplication app(argc, argv);
    app.setApplicationName("Instrument Framework");
    app.setApplicationVersion("1.0");
    app.setOrganizationName("Your Organization");

    qDebug() << "==========================================";
    qDebug() << "Instrument Framework Starting...";
    qDebug() << "==========================================";

    // ----------------------------------------
    // 步骤1:创建并注册所有模块
    // ----------------------------------------
    ModuleManager *mgr = ModuleManager::instance();

    // 注册通讯模块
    // 注意:可以同时注册多个通讯模块,但名称要唯一
    mgr->registerModule(new SerialComm());
    mgr->registerModule(new TcpComm());

    // 注册业务模块
    mgr->registerModule(ParameterManager::instance());  // 单例
    mgr->registerModule(AnalysisModule::instance());    // 单例
    mgr->registerModule(StorageModule::instance());     // 单例

    // ----------------------------------------
    // 步骤2:初始化所有模块
    // ----------------------------------------
    // 会自动按照依赖关系排序
    if (!mgr->initializeAll()) {
        qCritical() << "FATAL: Failed to initialize modules!";
        return 1;
    }

    qDebug() << "All modules initialized successfully";

    // ----------------------------------------
    // 步骤3:启动所有模块
    // ----------------------------------------
    if (!mgr->startAll()) {
        qCritical() << "FATAL: Failed to start modules!";
        return 1;
    }

    qDebug() << "All modules started successfully";

    // ----------------------------------------
    // 步骤4:创建并显示主窗口
    // ----------------------------------------
    MainWindow mainWindow;
    mainWindow.show();

    // ----------------------------------------
    // 步骤5:连接数据流
    // ----------------------------------------
    // 获取模块实例
    auto *comm = mgr->getModule<CommInterface>("SerialComm");
    auto *analysis = mgr->getModule<AnalysisModule>("AnalysisModule");
    auto *storage = mgr->getModule<StorageModule>("StorageModule");

    // 连接数据流:通讯 -> 分析 -> UI/存储
    if (comm && analysis) {
        QObject::connect(comm, &CommInterface::dataReceived,
            analysis, [analysis](const QByteArray &data) {
                // 解析原始数据(这里需要根据实际协议实现)
                QVector<double> rawData;

                // 示例:假设数据是16位有符号整数序列
                for (int i = 0; i < data.size() / 2; ++i) {
                    qint16 val;
                    memcpy(&val, data.constData() + i * 2, 2);
                    // 转换为物理量(假设量程为-10V~+10V)
                    rawData.append(val / 32768.0 * 10.0);
                }

                // 创建分析数据
                AnalysisData ad;
                ad.rawData = rawData;
                ad.metadata["timestamp"] = QDateTime::currentMSecsSinceEpoch();

                // 执行分析
                AnalysisResult result = analysis->execute(ad);

                if (result.success) {
                    qDebug() << "Analysis done in" << result.elapsedMs << "ms";
                }
            });
    }

    // ----------------------------------------
    // 步骤6:设置存储路径
    // ----------------------------------------
    auto *pm = mgr->getModule<ParameterManager>("ParameterManager");
    if (pm) {
        // 设置默认存储路径
        QString dir = pm->get("storage.directory").toString();
        if (dir.isEmpty()) {
            dir = QStandardPaths::writableLocation(
                QStandardPaths::DocumentsLocation
            ) + "/InstrumentData";

            // 确保目录存在
            QDir().mkpath(dir);

            // 保存到配置
            pm->set("storage.directory", dir);
        }

        qDebug() << "Storage directory:" << dir;
    }

    // ----------------------------------------
    // 步骤7:进入事件循环
    // ----------------------------------------
    qDebug() << "Entering main event loop...";
    int ret = app.exec();

    // ----------------------------------------
    // 步骤8:退出前停止所有模块
    // ----------------------------------------
    qDebug() << "Stopping all modules...";
    mgr->stopAll();

    qDebug() << "Application exited with code:" << ret;
    return ret;
}

九、框架扩展指南

9.1 添加新的通讯接口(以USB为例)

只需要三步:

第一步:创建USB通讯类

cpp 复制代码
// UsbComm.h
class UsbConfig : public CommConfig {
public:
    int vendorId = 0x1234;
    int productId = 0x5678;
    int timeout = 3000;

    CommConfig* clone() const override {
        return new UsbConfig(*this);
    }
};

class UsbComm : public ICommInterface {
    Q_OBJECT
public:
    explicit UsbComm(QObject *parent = nullptr);
    ~UsbComm() override;

    QString name() const override { return "UsbComm"; }
    bool initialize() override;
    void cleanup() override;
    bool stop() override { disconnect(); return true; }
    bool pause() override { return true; }

    bool connect(const CommConfig *config) override;
    void disconnect() override;
    bool send(const QByteArray &data) override;
    ConnectionState connectionState() const override {
        return m_state;
    }
    CommConfig* currentConfig() const override { return m_config; }

private:
    UsbConfig *m_config = nullptr;
    ConnectionState m_state = ConnectionState::Disconnected;
};

第二步:实现接口

cpp 复制代码
// UsbComm.cpp
bool UsbComm::connect(const CommConfig *config)
{
    const UsbConfig *uc = dynamic_cast<const UsbConfig*>(config);
    if (!uc) return false;

    delete m_config;
    m_config = new UsbConfig(*uc);

    // USB连接实现...
    // 使用libusb或其他USB库

    setConnectionState(ConnectionState::Connected);
    m_state = ModuleState::Running;
    return true;
}

第三步:在main.cpp中注册

cpp 复制代码
mgr->registerModule(new UsbComm());

9.2 添加新的分析算法

cpp 复制代码
// 添加卡尔曼滤波器
class KalmanFilterNode : public IAnalysisNode {
    Q_OBJECT
public:
    KalmanFilterNode(double processNoise = 0.001,
                     double measurementNoise = 0.1)
        : m_processNoise(processNoise)
        , m_measurementNoise(measurementNoise)
        , m_estimate(0)
        , m_errorCovariance(1) {}

    QString name() const override { return "KalmanFilter"; }

    AnalysisResult process(const AnalysisData &input) override
    {
        AnalysisResult r;
        QElapsedTimer timer;
        timer.start();

        QVector<double> output;
        output.reserve(input.rawData.size());

        for (double measurement : input.rawData) {
            // 预测步骤
            double predictedEstimate = m_estimate;
            double predictedErrorCovariance = m_errorCovariance + m_processNoise;

            // 更新步骤
            double kalmanGain = predictedErrorCovariance /
                (predictedErrorCovariance + m_measurementNoise);
            m_estimate = predictedEstimate +
                kalmanGain * (measurement - predictedEstimate);
            m_errorCovariance = (1 - kalmanGain) * predictedErrorCovariance;

            output.append(m_estimate);
        }

        r.success = true;
        r.message = "Kalman filter applied";
        r.outputData = output;
        r.elapsedMs = timer.elapsed();

        return r;
    }

private:
    double m_processNoise;       // 过程噪声
    double m_measurementNoise;   // 测量噪声
    double m_estimate;           // 当前估计值
    double m_errorCovariance;    // 估计误差协方差
};

// 在AnalysisModule::initialize()中注册
registerNode(new KalmanFilterNode(0.001, 0.1));

9.3 添加数据库存储支持

cpp 复制代码
// DatabaseStorageHandler.h
class DatabaseStorageHandler : public IStorageHandler {
public:
    bool open(const QString &path, bool append) override {
        m_db = QSqlDatabase::addDatabase("QSQLITE");
        m_db.setDatabaseName(path);

        if (!m_db.open()) return false;

        // 创建表
        QSqlQuery q(m_db);
        q.exec("CREATE TABLE IF NOT EXISTS records ("
               "id INTEGER PRIMARY KEY AUTOINCREMENT,"
               "timestamp INTEGER NOT NULL,"
               "channel1 REAL,"
               "channel2 REAL,"
               "channel3 REAL,"
               "channel4 REAL,"
               "status INTEGER DEFAULT 0"
               ")");

        return true;
    }

    void close() override { m_db.close(); }

    bool write(const DataBlock &block) override {
        QSqlQuery q(m_db);
        q.exec("BEGIN TRANSACTION");

        for (const DataRecord &r : block.records) {
            q.prepare("INSERT INTO records (timestamp, channel1, channel2, "
                       "channel3, channel4, status) VALUES (?, ?, ?, ?, ?, ?)");
            q.addBindValue(r.timestamp);
            q.addBindValue(r.channels.value(0, 0));
            q.addBindValue(r.channels.value(1, 0));
            q.addBindValue(r.channels.value(2, 0));
            q.addBindValue(r.channels.value(3, 0));
            q.addBindValue(r.status);
            q.exec();
        }

        q.exec("COMMIT");
        return true;
    }

    bool read(DataBlock &block) override {
        QSqlQuery q(m_db);
        if (!q.exec("SELECT * FROM records ORDER BY timestamp")) return false;

        while (q.next()) {
            DataRecord r;
            r.timestamp = q.value(1).toLongLong();
            r.channels.append(q.value(2).toDouble());
            r.channels.append(q.value(3).toDouble());
            r.channels.append(q.value(4).toDouble());
            r.channels.append(q.value(5).toDouble());
            r.status = q.value(6).toInt();
            block.records.append(r);
        }

        return true;
    }

    bool isOpen() const override { return m_db.isOpen(); }

private:
    QSqlDatabase m_db;
};

十、CMakeLists.txt

cmake 复制代码
cmake_minimum_required(VERSION 3.16)
project(InstrumentFramework LANGUAGES CXX)

# C++标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# Qt自动处理MOC、UIC、RCC
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTORCC ON)

# 寻找Qt6
find_package(Qt6 REQUIRED COMPONENTS
    Core
    Gui
    Widgets
    SerialPort
    Network
    Charts
    Sql
)

# ============================================================
# 源文件分组
# ============================================================

set(CORE_SOURCES
    src/Core/ModuleManager.cpp
)

set(CORE_HEADERS
    src/Core/ModuleInterface.h
    src/Core/ModuleManager.h
)

set(COMM_SOURCES
    src/Comm/SerialComm.cpp
    src/Comm/TcpComm.cpp
    src/Comm/ProtocolParser.cpp
)

set(COMM_HEADERS
    src/Comm/ICommInterface.h
    src/Comm/SerialComm.h
    src/Comm/TcpComm.h
    src/Comm/ProtocolParser.h
)

set(BUSINESS_SOURCES
    src/Parameters/ParameterManager.cpp
    src/Analysis/AnalysisModule.cpp
    src/Storage/StorageModule.cpp
)

set(BUSINESS_HEADERS
    src/Parameters/ParameterManager.h
    src/Analysis/AnalysisModule.h
    src/Storage/StorageModule.h
)

set(UI_SOURCES
    src/UI/MainWindow.cpp
)

set(UI_HEADERS
    src/UI/MainWindow.h
)

# ============================================================
// 可执行文件
// ============================================================

add_executable(${PROJECT_NAME}
    src/main.cpp

    ${CORE_SOURCES}
    ${CORE_HEADERS}

    ${COMM_SOURCES}
    ${COMM_HEADERS}

    ${BUSINESS_SOURCES}
    ${BUSINESS_HEADERS}

    ${UI_SOURCES}
    ${UI_HEADERS}
)

# ============================================================
// 链接库
// ============================================================

target_link_libraries(${PROJECT_NAME} PRIVATE
    Qt6::Core
    Qt6::Gui
    Qt6::Widgets
    Qt6::SerialPort
    Qt6::Network
    Qt6::Charts
    Qt6::Sql
)

# ============================================================
// 包含目录
// ============================================================

target_include_directories(${PROJECT_NAME} PRIVATE
    src
    src/Core
    src/Comm
    src/Parameters
    src/Analysis
    src/Storage
    src/UI
)

# ============================================================
// 安装配置
// ============================================================

install(TARGETS ${PROJECT_NAME}
    RUNTIME DESTINATION

    RUNTIME DESTINATION bin
)

install(DIRECTORY src/
    DESTINATION include
    FILES_MATCHING PATTERN "*.h"
)

---

## 结语:框架的核心价值

### 本框架解决了什么问题?

┌─────────────────────────────────────────────────────────────┐

│ 测试仪器软件常见问题 │

├─────────────────────────────────────────────────────────────┤

│ ❌ 模块耦合严重 → ✅ 分层模块化,接口清晰 │

│ ❌ 难以单元测试 → ✅ 模块可独立测试,接口可Mock │

│ ❌ 扩展困难 → ✅ 新增模块只需实现接口并注册 │

│ ❌ 状态管理混乱 → ✅ 状态机模式,状态转换可追踪 │

│ ❌ 配置分散 → ✅ 统一配置管理,变更自动通知 │

│ ❌ 通讯不稳定 → ✅ 断线重连、数据缓冲、协议解析 │

└─────────────────────────────────────────────────────────────┘

复制代码
### 设计模式应用总结

| 模式 | 位置 | 作用 |
|------|------|------|
| 单例模式 | ModuleManager, ParameterManager | 全局唯一实例 |
| 工厂模式 | StorageModule | 根据格式创建处理器 |
| 策略模式 | ICommInterface, IStorageHandler | 同一接口,不同实现 |
| 桥接模式 | ICommInterface | 抽象与实现分离 |
| 观察者模式 | ParameterManager | 配置变更自动通知 |
| 责任链模式 | AnalysisModule | 算法串联执行 |
| 模板方法模式 | IAnalysisNode | 算法框架固定,步骤可扩展 |
| Memento模式 | ParameterManager | 配置快照与回滚 |
| MVC/MVP模式 | MainWindow | 界面与逻辑分离 |

### 如何开始使用?

**方式一:直接复制**
1. 复制整个目录结构
2. 复制所有代码文件
3. 使用Qt Creator打开CMakeLists.txt
4. 构建运行

**方式二:选择性使用**
1. 只复制Core层的ModuleInterface和ModuleManager
2. 根据需要选择性地使用通讯模块、分析模块等
3. 替换或扩展不适合的部分

**方式三:从零开始**
1. 先理解分层架构
2. 实现ModuleInterface和ModuleManager
3. 根据需要逐步添加模块

### 下一步可以做什么?

1. **添加单元测试**:每个模块都应该有对应的测试
2. **添加日志系统**:记录模块的初始化、状态变化、错误等信息
3. **添加性能监控**:监控数据处理耗时、内存使用等
4. **添加远程控制**:通过网络或串口实现远程控制
5. **添加报告生成**:自动生成测试报告

---

### 常见问题

**Q: 为什么使用Q_DECLARE_SINGLETON宏?**
A: 这是Qt的线程安全单例实现,比自己写更可靠。

**Q: 能否在多线程中使用ParameterManager?**
A: 可以。ParameterManager内部使用QMutex保护,所有公开方法都是线程安全的。

**Q: 如何添加新的存储格式(如HDF5)?**
A: 创建新的Handler类实现IStorageHandler接口,然后在StorageModule::createHandler中添加创建逻辑。

**Q: 通讯模块支持SCPI协议吗?**
A: 可以。只需要在ProtocolParser中使用ScpiParser实现,或者创建ScpiCommand封装SCPI命令。

**Q: 如何调试多线程问题?**
A: Qt Creator提供了线程调试器。也可以在关键位置添加qDebug()输出,使用ModuleManager::startupOrder()检查模块启动顺序。

---
相关推荐
j7~1 小时前
【Linux系统】基础IO(文件描述)(1)
linux·服务器·c++·文件·基础io
计算机安禾1 小时前
【c++面向对象编程】第20篇:override与final关键字:现代C++对继承的控制
开发语言·c++
郝学胜-神的一滴1 小时前
Qt 高级开发 004: 三大窗口类深度解析
开发语言·c++·qt·程序人生·系统架构
Francek Chen1 小时前
【大数据存储与管理】云数据库:03 云数据库系统架构
大数据·数据库·分布式·架构
IPHWT 零软网络1 小时前
企业通信架构选型:智能IVR与CTI集成在企业话务台中的技术实践
架构·话务台·企业适配·企业话务台·企业通信
程序员老邢1 小时前
【技术底稿 35】低配单机混跑 Dev/Test 微服务环境,Jenkins 部署包错乱踩坑全复盘
微服务·架构·jenkins·低配服务器运维·部署踩坑
王老师青少年编程1 小时前
csp信奥赛C++高频考点专项训练之字符串 --【字符串综合】:[NOIP 2004 普及组] FBI 树
c++·字符串·csp·高频考点·信奥赛·字符串综合·fbi树
楼田莉子1 小时前
Linux网络:多路转接IO
服务器·c++·后端·软件构建
小短腿的代码世界1 小时前
Qt SVG渲染管线全解析:从XML解析到像素绘制的完整架构设计与性能优化实战
xml·qt·性能优化