Qt CAN总线发送和接收案例

文章目录

  • 设置比特率类
    • 设置比特率类实现
  • 发送数据帧类
    • 发送数据帧类的实现
    • m_ui 发送帧界面
  • 连接类
    • 连接类实现
    • 连接类UI设计
  • 主窗口类
    • 主窗口类实现
    • 主界面UI
  • 整体UI
  • QT案例

设置比特率类

// 文件: BitRateBox.h
// 作用: 定义了一个用于选择比特率的组合框类 BitRateBox,该类继承自QComboBox。
//       提供了设置和获取比特率、以及是否启用可变数据速率的功能。

#ifndef BITRATEBOX_H
#define BITRATEBOX_H

#include <QComboBox> // 引入Qt的组合框类QComboBox

// 使用QT宏来引入QIntValidator类的命名空间
QT_BEGIN_NAMESPACE
class QIntValidator;
QT_END_NAMESPACE

// BitRateBox 类定义,继承自QComboBox
class BitRateBox : public QComboBox
{
public:
    // 构造函数,接受一个可选的父窗口指针
    explicit BitRateBox(QWidget *parent = nullptr);
    // 析构函数
    ~BitRateBox();

    // 获取当前选定的比特率
    int bitRate() const;

    // 查询是否启用了灵活的数据速率(Flexible Data Rate)
    bool isFlexibleDataRateEnabled() const;
    // 设置灵活数据速率的启用状态
    void setFlexibleDateRateEnabled(bool enabled);

protected slots: // 更正:应为private slots,因为这些槽函数不应在类外部被直接调用
    // 根据下拉菜单的选择项检查并应用自定义速度策略
    void checkCustomSpeedPolicy(int idx);

private:
    // 初始化比特率选项
    void fillBitRates();

    // 标记是否启用了灵活数据速率,默认为禁用
    int m_isFlexibleDataRateEnabled; // 注意:此处类型应为bool而非int以匹配其用途
    // 用于验证用户输入的自定义比特率的整数验证器
    QIntValidator *m_customSpeedValidator;
};

#endif // BITRATEBOX_H // 头文件结束标记

设置比特率类实现

#include "bitratebox.h"
#include <QLineEdit> // 包含QLineEdit头文件,用于访问lineEdit()方法

// BitRateBox构造函数,初始化组合框并设置验证器
BitRateBox::BitRateBox(QWidget *parent) :
    QComboBox(parent),
    m_customSpeedValidator(new QIntValidator(0, 1000000, this)) // 初始化验证器,允许0到1000000的整数输入
{
    fillBitRates(); // 填充默认比特率选项

    // 连接下拉菜单选项变化的信号到checkCustomSpeedPolicy槽函数
    connect(this, QOverload<int>::of(&QComboBox::currentIndexChanged),
            this, &BitRateBox::checkCustomSpeedPolicy);
}

// 析构函数,释放m_customSpeedValidator所占内存
BitRateBox::~BitRateBox()
{
    delete m_customSpeedValidator;
}

// 返回当前选中的比特率
int BitRateBox::bitRate() const
{
    // 如果选中的是"Custom"项,则直接解析文本为整数返回
    if (currentIndex() == (count() - 1))
        return currentText().toInt();

    // 否则,从itemData获取预设比特率值
    return itemData(currentIndex()).toInt();
}

// 查询灵活数据速率是否启用
bool BitRateBox::isFlexibleDataRateEnabled() const
{
    return m_isFlexibleDataRateEnabled;
}

// 设置灵活数据速率的启用状态,并根据状态调整验证器上限
void BitRateBox::setFlexibleDateRateEnabled(bool enabled)
{
    m_isFlexibleDataRateEnabled = enabled;
    // 根据状态调整自定义速度的最大值
    m_customSpeedValidator->setTop(enabled ? 10000000 : 1000000);
    fillBitRates(); // 重新填充比特率选项以反映设置
}

// 根据当前选中项决定是否进入自定义速度模式并设置验证器
void BitRateBox::checkCustomSpeedPolicy(int idx)
{
    const bool isCustomSpeed = !itemData(idx).isValid(); // 判断是否选择了"Custom"
    setEditable(isCustomSpeed); // 是否允许编辑(自定义输入)
    if (isCustomSpeed) {
        clearEditText(); // 清除已有编辑文本
        lineEdit()->setValidator(m_customSpeedValidator); // 设置验证器控制输入合法
    }
}

// 填充比特率选项到组合框
void BitRateBox::fillBitRates()
{
    // 静态比特率列表
    const QList<int> rates = {
        10000, 20000, 50000, 100000, 125000, 250000, 500000, 800000, 1000000
    };
    // 灵活数据速率列表,仅当功能启用时显示
    const QList<int> dataRates = {
        2000000, 4000000, 8000000
    };

    // 清空现有选项
    clear();

    // 添加静态比特率选项
    for (int rate : rates)
        addItem(QString::number(rate), rate);

    // 根据配置添加灵活数据速率选项
    if (isFlexibleDataRateEnabled()) {
        for (int rate : dataRates)
            addItem(QString::number(rate), rate);
    }

    // 添加"Custom"选项,允许用户输入自定义比特率
    addItem(tr("Custom"));
    // 默认选择500000 bits/sec
    setCurrentIndex(6);
}

发送数据帧类

// 文件: SendFrameBox.h
// 作用: 定义了一个用于构造和管理发送CAN总线数据帧界面的类SendFrameBox,该类继承自QGroupBox。
//       包括两个自定义验证器HexIntegerValidator和HexStringValidator,分别用于验证十六进制整数和十六进制字符串输入的有效性。

#ifndef SENDFRAMEBOX_H
#define SENDFRAMEBOX_H

#include <QCanBusFrame> // CAN总线数据帧类
#include <QGroupBox>    // Qt中的分组框基类
#include <QRegularExpression> // 正则表达式类,可能用于输入验证辅助
#include <QValidator>   // 输入验证基类

QT_BEGIN_NAMESPACE
namespace Ui { class SendFrameBox; } // 前向声明Ui命名空间内的SendFrameBox类,通常由Qt Designer生成
QT_END_NAMESPACE

// HexIntegerValidator类定义,用于验证输入是否为有效的十六进制整数
class HexIntegerValidator : public QValidator
{
    Q_OBJECT // 支持信号槽机制

public:
    // 构造函数,可选传入父对象指针
    explicit HexIntegerValidator(QObject *parent = nullptr);

    // 重写了QValidator的validate方法,用于实际的输入验证逻辑
    QValidator::State validate(QString &input, int &) const;

    // 设置验证器允许的最大十六进制数值
    void setMaximum(uint maximum);

private:
    uint m_maximum = 0; // 最大允许的十六进制数值
};

// HexStringValidator类定义,用于验证输入是否为有效的十六进制字符串
class HexStringValidator : public QValidator
{
    Q_OBJECT

public:
    // 构造函数
    explicit HexStringValidator(QObject *parent = nullptr);

    // 重写的验证逻辑
    QValidator::State validate(QString &input, int &pos) const;

    // 设置验证器允许的最长输入长度
    void setMaxLength(int maxLength);

private:
    int m_maxLength = 0; // 允许的最长输入长度
};

// SendFrameBox类定义,负责构建和管理发送CAN帧的UI界面
class SendFrameBox : public QGroupBox
{
    Q_OBJECT // 支持信号槽机制

public:
    // 构造函数,可选传入父对象指针
    explicit SendFrameBox(QWidget *parent = nullptr);
    // 析构函数
    ~SendFrameBox();

    // 信号,当用户操作完成并准备发送CAN帧时发出
    signals:
        void sendFrame(const QCanBusFrame &frame);

private:
    Ui::SendFrameBox *m_ui; // 指向由Qt Designer生成的用户界面对象

    HexIntegerValidator *m_hexIntegerValidator; // 十六进制整数验证器
    HexStringValidator *m_hexStringValidator;   // 十六进制字符串验证器
};

#endif // SENDFRAMEBOX_H // 头文件结束标记

发送数据帧类的实现

#include "sendframebox.h"
#include "ui_sendframebox.h"

// 定义标准ID和扩展ID的最大值
enum {
    MaxStandardId = 0x7FF, // 标准CAN帧ID的最大值
    MaxExtendedId = 0x10000000 // 扩展CAN帧ID的最大值
};

// 定义CAN帧数据域最大负载长度,区分经典CAN与FD(灵活数据速率)CAN
enum {
    MaxPayload = 8, // 经典CAN帧最大负载字节数
    MaxPayloadFd = 64 // FD CAN帧最大负载字节数
};

// HexIntegerValidator构造函数,初始化最大验证值为标准CAN ID的最大值
HexIntegerValidator::HexIntegerValidator(QObject *parent) :
    QValidator(parent),
    m_maximum(MaxStandardId)
{
}

// 验证输入的十六进制整数是否有效
QValidator::State HexIntegerValidator::validate(QString &input, int &)
{
    bool ok;
    uint value = input.toUInt(&ok, 16); // 将输入转换为无符号整数

    if (input.isEmpty()) // 空输入视为中间状态,等待更多输入
        return Intermediate;

    if (!ok || value > m_maximum) // 转换失败或超过最大值则无效
        return Invalid;

    return Acceptable; // 否则,输入有效
}

// 设置验证的最大十六进制数值
void HexIntegerValidator::setMaximum(uint maximum)
{
    m_maximum = maximum;
}

// HexStringValidator构造函数,初始化最大验证长度为经典CAN负载长度
HexStringValidator::HexStringValidator(QObject *parent) :
    QValidator(parent),
    m_maxLength(MaxPayload)
{
}

// 验证输入的十六进制字符串是否有效,同时格式化输入(每两个字符后加空格)
QValidator::State HexStringValidator::validate(QString &input, int &pos)
{
    const int maxSize = 2 * m_maxLength; // 计算最大字符长度(每字节2字符)
    const QChar space = QLatin1Char(' '); // 空格字符
    QString data = input.remove(space); // 移除所有空格进行验证

    if (data.isEmpty()) // 空输入视为中间状态
        return Intermediate;

    // 检查是否超过最大长度或以空格结尾(不允许尾随空格)
    if ((data.size() > maxSize) || (data.size() == maxSize && input.endsWith(space)))
        return Invalid;

    // 使用正则表达式检查所有字符是否为十六进制
    const QRegularExpression re(QStringLiteral("^[[:xdigit:]]*$"));
    if (!re.match(data).hasMatch())
        return Invalid;

    // 每两个十六进制字符后插入空格,如果需要
    const QRegularExpression insertSpace(QStringLiteral("(?:[[:xdigit:]]{2} )*[[:xdigit:]]{3}"));
    if (insertSpace.match(input).hasMatch()) {
        input.insert(input.size() - 1, space); // 在倒数第二个字符后插入空格
        pos = input.size(); // 更新光标位置
    }

    return Acceptable; // 输入有效
}

// 设置验证的最大十六进制字符串长度
void HexStringValidator::setMaxLength(int maxLength)
{
    m_maxLength = maxLength;
}

// SendFrameBox构造函数,初始化界面及连接信号槽
SendFrameBox::SendFrameBox(QWidget *parent) :
    QGroupBox(parent),
    m_ui(new Ui::SendFrameBox)
{
    m_ui->setupUi(this); // 使用Qt Designer生成的UI

    // 设置ID和数据的验证器
    m_hexIntegerValidator = new HexIntegerValidator(this);
    m_ui->frameIdEdit->setValidator(m_hexIntegerValidator);
    m_hexStringValidator = new HexStringValidator(this);
    m_ui->payloadEdit->setValidator(m_hexStringValidator);

    // 数据帧类型切换时,控制“灵活数据速率”选项的可用性
    connect(m_ui->dataFrame, &QRadioButton::toggled, [this](bool set) {
        if (set) m_ui->flexibleDataRateBox->setEnabled(true);
    });
    connect(m_ui->remoteFrame, &QRadioButton::toggled, [this](bool set) {
        if (set) {
            m_ui->flexibleDataRateBox->setEnabled(false);
            m_ui->flexibleDataRateBox->setChecked(false);
        }
    });
    connect(m_ui->errorFrame, &QRadioButton::toggled, [this](bool set) {
        if (set) {
            m_ui->flexibleDataRateBox->setEnabled(false);
            m_ui->flexibleDataRateBox->setChecked(false);
        }
    });

    // 扩展ID选项改变时,更新ID验证器的最大值
    connect(m_ui->extendedFormatBox, &QCheckBox::toggled, [this](bool set) {
        m_hexIntegerValidator->setMaximum(set ? MaxExtendedId : MaxStandardId);
    });

    // 灵活数据速率选项改变时,更新数据验证器的最大长度和控制位速率切换选项
    connect(m_ui->flexibleDataRateBox, &QCheckBox::toggled, [this](bool set) {
        m_hexStringValidator->setMaxLength(set ? MaxPayloadFd : MaxPayload);
        m_ui->bitrateSwitchBox->setEnabled(set);
        if (!set) m_ui->bitrateSwitchBox->setChecked(false);
    });

    // 监听ID编辑框文本变化,启用或禁用发送按钮
    auto frameIdTextChanged = [this]() {
        bool hasFrameId = !m_ui->frameIdEdit->text().isEmpty();
        m_ui->sendButton->setEnabled(hasFrameId);
        m_ui->sendButton->setToolTip(hasFrameId
                                     ? QString() : tr("Cannot send because no Frame ID was given."));
    };
    connect(m_ui->frameIdEdit, &QLineEdit::textChanged, frameIdTextChanged);
    frameIdTextChanged(); // 初始化时也检查一次

    // 发送按钮点击事件处理,构造并发送CAN帧
    connect(m_ui->sendButton, &QPushButton::clicked, [this]() {
        uint frameId = m_ui->frameIdEdit->text().toUInt(nullptr, 16);
        QString data = m_ui->payloadEdit->text();
        QByteArray payload = QByteArray::fromHex(data.remove(QLatin1Char(' ')).toLatin1()); // 转换为字节数组

        QCanBusFrame frame(frameId, payload); // 创建CAN帧
        frame.setExtendedFrameFormat(m_ui->extendedFormatBox->isChecked()); // 设置扩展帧格式
        frame.setFlexibleDataRateFormat(m_ui->flexibleDataRateBox->isChecked()); // 设置灵活数据速率格式
        frame.setBitrateSwitch(m_ui->bitrateSwitchBox->isChecked()); // 设置位速率切换

        if (m_ui->errorFrame->isChecked())
            frame.setFrameType(QCanBusFrame::ErrorFrame); // 错误帧
        else if (m_ui->remoteFrame->isChecked())
            frame.setFrameType(QCanBusFrame::RemoteRequestFrame); // 远程请求帧

        emit sendFrame(frame); // 发射信号,携带构造好的CAN帧
    });
}

// 析构函数,释放UI指针
SendFrameBox::~SendFrameBox()
{
    delete m_ui;
}

m_ui 发送帧界面

在这里插入图片描述

连接类

/*
 * 文件名:ConnectDialog.h
 * 描述:此文件定义了ConnectDialog类,这是一个用于配置和选择CAN总线设备的对话框。
 *       用户可以通过该对话框选择CAN插件、设备接口以及配置CAN设备的特定参数。
 */

#ifndef CONNECTDIALOG_H
#define CONNECTDIALOG_H

// 包含必要的头文件
#include <QCanBusDevice>      // 提供CAN总线设备操作接口
#include <QCanBusDeviceInfo>  // 表示一个物理CAN设备的信息
#include <QDialog>           // 继承自QDialog,作为基础对话框类

// 包含自动生成的用户界面类头文件(由Qt Designer生成)
QT_BEGIN_NAMESPACE
namespace Ui {
class ConnectDialog;
}
QT_END_NAMESPACE

// 前向声明Settings结构体,用于存储当前的设置信息
class ConnectDialog : public QDialog {
    Q_OBJECT

public:
    // 使用QPair存储配置键和对应的值
    typedef QPair<QCanBusDevice::ConfigurationKey, QVariant> ConfigurationItem;

    // 定义一个结构体来保存所有设置
    struct Settings {
        QString pluginName;          // CAN总线插件名称
        QString deviceInterfaceName; // 设备接口名称
        QList<ConfigurationItem> configurations; // 额外的设备配置项列表
        bool useConfigurationEnabled;           // 是否使用自定义配置标志
    };

    // 构造函数,可选传入父窗口指针
    explicit ConnectDialog(QWidget *parent = nullptr);
    // 析构函数
    ~ConnectDialog();

    // 获取当前设置信息
    Settings settings() const;

private slots:
    // 插件改变时的槽函数
    void pluginChanged(const QString &plugin);
    // 接口改变时的槽函数
    void interfaceChanged(const QString &interface);
    // 确定按钮点击时的槽函数
    void ok();
    // 取消按钮点击时的槽函数
    void cancel();

private:
    // 根据配置键获取配置值
    QString configurationValue(QCanBusDevice::ConfigurationKey key);
    // 恢复默认设置
    void revertSettings();
    // 更新界面上显示的设置信息
    void updateSettings();

    // 私有成员变量
    Ui::ConnectDialog *m_ui;            // 对话框的用户界面指针
    Settings m_currentSettings;         // 当前对话框中的设置
    QList<QCanBusDeviceInfo> m_interfaces; // 可用的CAN设备接口信息列表
};

#endif // CONNECTDIALOG_H

连接类实现

#include "connectdialog.h"
#include "ui_connectdialog.h"

// 引入QCanBus相关头文件,用于访问CAN总线设备操作接口和设备信息
#include <QCanBus>

// ConnectDialog类的实现开始
ConnectDialog::ConnectDialog(QWidget *parent) :
    QDialog(parent), // 初始化父窗口指针
    m_ui(new Ui::ConnectDialog) // 初始化用户界面指针并分配内存
{
    m_ui->setupUi(this); // 使用Qt Designer生成的用户界面配置此对话框

    // 设置错误过滤器编辑框的验证器,允许0至0x1FFFFFFF之间的整数
    m_ui->errorFilterEdit->setValidator(new QIntValidator(0, 0x1FFFFFFFU, this));

    // 为"回环"和"接收自身消息"组合框添加选项
    m_ui->loopbackBox->addItem(tr("unspecified"), QVariant());
    m_ui->loopbackBox->addItem(tr("false"), QVariant(false));
    m_ui->loopbackBox->addItem(tr("true"), QVariant(true));

    m_ui->receiveOwnBox->addItem(tr("unspecified"), QVariant());
    m_ui->receiveOwnBox->addItem(tr("false"), QVariant(false));
    m_ui->receiveOwnBox->addItem(tr("true"), QVariant(true));

    // 添加CAN-FD支持选项
    m_ui->canFdBox->addItem(tr("false"), QVariant(false));
    m_ui->canFdBox->addItem(tr("true"), QVariant(true));

    // 允许数据速率框支持灵活数据速率
    m_ui->dataBitrateBox->setFlexibleDateRateEnabled(true);

    // 连接信号和槽
    connect(m_ui->okButton, &QPushButton::clicked, this, &ConnectDialog::ok); // 确定按钮点击
    connect(m_ui->cancelButton, &QPushButton::clicked, this, &ConnectDialog::cancel); // 取消按钮点击
    connect(m_ui->useConfigurationBox, &QCheckBox::clicked, 
             m_ui->configurationBox, &QGroupBox::setEnabled); // 配置使用状态改变
    connect(m_ui->pluginListBox, &QComboBox::currentTextChanged, 
             this, &ConnectDialog::pluginChanged); // 插件选择变化
    connect(m_ui->interfaceListBox, &QComboBox::currentTextChanged, 
             this, &ConnectDialog::interfaceChanged); // 接口选择变化

    // 隐藏原始过滤器编辑框及其标签,默认不显示
    m_ui->rawFilterEdit->hide();
    m_ui->rawFilterLabel->hide();

    // 加载可用的CAN总线插件到插件列表框
    m_ui->pluginListBox->addItems(QCanBus::instance()->plugins());

    // 初始化设置并更新UI
    updateSettings();
}

ConnectDialog::~ConnectDialog()
{
    delete m_ui; // 释放UI指针占用的内存
}

// 获取当前设置
ConnectDialog::Settings ConnectDialog::settings() const
{
    return m_currentSettings;
}

// 插件改变时更新界面和设备列表
void ConnectDialog::pluginChanged(const QString &plugin)
{
    m_ui->interfaceListBox->clear(); // 清空现有设备列表
    m_interfaces = QCanBus::instance()->availableDevices(plugin); // 获取新插件下的设备
    for (const QCanBusDeviceInfo &info : qAsConst(m_interfaces))
        m_ui->interfaceListBox->addItem(info.name()); // 添加设备接口到列表
}

// 接口改变时更新接口相关信息
void ConnectDialog::interfaceChanged(const QString &interface)
{
    // 重置并更新当前选定接口的相关信息
    m_ui->isVirtual->setChecked(false);
    m_ui->isFlexibleDataRateCapable->setChecked(false);

    for (const QCanBusDeviceInfo &info : qAsConst(m_interfaces)) {
        if (info.name() == interface) {
            m_ui->descriptionLabel->setText(info.description());
            QString serialNumber = info.serialNumber();
            if (serialNumber.isEmpty())
                serialNumber = tr("n/a");
            m_ui->serialNumberLabel->setText(tr("Serial: %1").arg(serialNumber));
            m_ui->channelLabel->setText(tr("Channel: %1").arg(info.channel()));
            m_ui->isVirtual->setChecked(info.isVirtual());
            m_ui->isFlexibleDataRateCapable->setChecked(info.hasFlexibleDataRate());
            break;
        }
    }
}

// 点击确定按钮时执行的操作
void ConnectDialog::ok()
{
    updateSettings(); // 更新设置
    accept(); // 接受对话框
}

// 点击取消按钮时执行的操作
void ConnectDialog::cancel()
{
    revertSettings(); // 恢复原始设置
    reject(); // 拒绝对话框
}

// 根据键获取配置的值
QString ConnectDialog::configurationValue(QCanBusDevice::ConfigurationKey key)
{
    QVariant result;

    // 遍历配置项寻找匹配的键
    for (const ConfigurationItem &item : qAsConst(m_currentSettings.configurations)) {
        if (item.first == key) {
            result = item.second;
            break;
        }
    }

    // 特殊处理未指定情况
    if (result.isNull() && (
                key == QCanBusDevice::LoopbackKey ||
                key == QCanBusDevice::ReceiveOwnKey)) {
        return tr("unspecified");
    }

    // 返回配置值的字符串表示形式
    return result.toString();
}

// 恢复到初始设置状态
void ConnectDialog::revertSettings()
{
    // 重置各UI组件到上次保存的设置状态
    m_ui->pluginListBox->setCurrentText(m_currentSettings.pluginName);
    m_ui->interfaceListBox->setCurrentText(m_currentSettings.deviceInterfaceName);
    m_ui->useConfigurationBox->setChecked(m_currentSettings.useConfigurationEnabled);
    // ...省略其他设置恢复逻辑
}

// 根据UI当前状态更新设置
void ConnectDialog::updateSettings()
{
    // 从UI获取最新选择和输入值,更新设置结构体
    m_currentSettings.pluginName = m_ui->pluginListBox->currentText();
    m_currentSettings.deviceInterfaceName = m_ui->interfaceListBox->currentText();
    m_currentSettings.useConfigurationEnabled = m_ui->useConfigurationBox->isChecked();
    // ...省略其他设置更新逻辑,包括循环遍历配置项并添加到m_currentSettings.configurations
}

连接类UI设计

在这里插入图片描述
在这里插入图片描述

主窗口类

/*
 * 主窗口类(MainWindow)的声明文件
 *
 * 该文件定义了MainWindow类,继承自QMainWindow,用于管理应用程序的主要界面和交互逻辑。
 * 包含了对CAN总线设备的连接、数据发送接收、错误处理以及界面更新等功能。
 */

#ifndef MAINWINDOW_H // 防止多重包含的预处理器宏
#define MAINWINDOW_H

// 引入必要的头文件
#include <QCanBusDevice> // 包含CAN总线设备相关的错误类型
#include <QMainWindow> // 继承自QMainWindow的基础类

// 类的前向声明,减少编译依赖
class ConnectDialog; // 连接对话框类的前向声明
class QLabel; // 标签控件类的前向声明,用于显示状态和写入帧的数量
class QTimer; // 定时器类的前向声明,用于监控总线状态

// 命名空间QT的开始与结束,包含自定义或Qt相关的内容
QT_BEGIN_NAMESPACE

// 前向声明QCanBusFrame,用于CAN数据帧处理
class QCanBusFrame;

// 结束命名空间QT
QT_END_NAMESPACE

// MainWindow类的声明开始
class MainWindow : public QMainWindow
{
    Q_OBJECT // 必须的宏,启用Qt的信号和槽机制

public:
    // 构造函数,可选传入父窗口指针
    explicit MainWindow(QWidget *parent = nullptr);
    
    // 析构函数
    ~MainWindow();

private slots: // 私有槽函数部分
    // 处理接收到的数据帧
    void processReceivedFrames();
    
    // 发送数据帧
    void sendFrame(const QCanBusFrame &frame) const;
    
    // 处理CAN总线错误
    void processErrors(QCanBusDevice::CanBusError) const;
    
    // 连接CAN设备
    void connectDevice();
    
    // 总线状态更新
    void busStatus();
    
    // 断开与CAN设备的连接
    void disconnectDevice();
    
    // 处理已写入数据帧的数量更新
    void processFramesWritten(qint64);

protected: // 保护成员函数,子类可访问
    // 重写的关闭事件处理函数
    void closeEvent(QCloseEvent *event) override;

private: // 私有成员函数和变量
    // 初始化动作与信号槽连接
    void initActionsConnections();

    // 记录已写入数据帧的数量
    qint64 m_numberFramesWritten;
    
    // 界面相关的UI指针
    Ui::MainWindow *m_ui; // 主窗口界面
    QLabel *m_status; // 显示状态的标签
    QLabel *m_written; // 显示已写入帧数的标签
    
    // 连接对话框实例,用于设备连接设置
    ConnectDialog *m_connectDialog;
    
    // CAN总线设备指针,采用unique_ptr智能指针管理
    std::unique_ptr<QCanBusDevice> m_canDevice;
    
    // 用于定期检查总线状态的定时器
    QTimer *m_busStatusTimer;
};

主窗口类实现

#include "mainwindow.h"

#include "ui_mainwindow.h"

#include "connectdialog.h"


// 引入Qt CAN总线模块相关的头文件

#include <QCanBus>

#include <QCanBusFrame>

#include <QCloseEvent> // 处理窗口关闭事件

#include <QDesktopServices> // 用于打开外部链接,如文档或网页

#include <QTimer> // 定时器,用于定期检查CAN总线状态


// MainWindow构造函数,负责建立用户界面及基础设置

MainWindow::MainWindow(QWidget *parent)

    : QMainWindow(parent) // 继承自QWidget的构造函数

    , m_ui(new Ui::MainWindow) // 使用Qt User Interface Compiler产生的界面类

    , m_busStatusTimer(new QTimer(this)) // 初始化一个定时器来监控CAN总线状态

{

    m_ui->setupUi(this); // 调用UI设置方法,加载设计的界面布局


    // 初始化连接对话框,用于选择CAN设备

    m_connectDialog = new ConnectDialog;


    // 在状态栏添加两个标签,分别显示总线状态和已发送帧的数量

    m_status = new QLabel;

    m_ui->statusBar->addPermanentWidget(m_status);

    m_written = new QLabel;

    m_ui->statusBar->addWidget(m_written);


    // 初始化界面元素与功能之间的连接关系

    initActionsConnections();


    // 稍后自动显示连接设备对话框,给予用户时间准备

    QTimer::singleShot(50, m_connectDialog, &ConnectDialog::show);


    // 设置定时器触发时调用busStatus函数检查CAN总线状态

    connect(m_busStatusTimer, &QTimer::timeout, this, &MainWindow::busStatus);

}


// 析构函数,清理分配的资源

MainWindow::~MainWindow(){

    delete m_connectDialog; // 删除连接对话框实例

    delete m_ui; // 删除界面设置实例

}


// 初始化窗口中的动作(如菜单项)与槽函数的关联

void MainWindow::initActionsConnections(){

    // 初始化界面按钮状态,初始时只有“连接”可用

    m_ui->actionDisconnect->setEnabled(false);

    m_ui->sendFrameBox->setEnabled(false);


    // 当用户在SendFrameBox中点击发送时,触发sendFrame槽处理发送CAN帧

    connect(m_ui->sendFrameBox, &SendFrameBox::sendFrame, this, &MainWindow::sendFrame);


    // “连接”菜单项点击后显示连接设备对话框

    connect(m_ui->actionConnect, &QAction::triggered, [this]() {

        // 断开当前连接(如果存在),然后显示连接对话框

        m_canDevice.release()->deleteLater();

        m_connectDialog->show();

    });


    // 对话框确认后尝试连接选定的CAN设备
    connect(m_connectDialog, &QDialog::accepted, this, &MainWindow::connectDevice);
    // “断开”菜单项点击后调用disconnectDevice函数断开CAN总线
    connect(m_ui->actionDisconnect, &QAction::triggered, this, &MainWindow::disconnectDevice);
    // “重置控制器”菜单项点击后调用CAN设备的resetController方法
    connect(m_ui->actionResetController, &QAction::triggered, this, [this]() {

        m_canDevice->resetController();
    });

    // “退出”菜单项点击后关闭主窗口
    connect(m_ui->actionQuit, &QAction::triggered, this, &QWidget::close);

    // “关于Qt”菜单项点击后显示Qt的关于信息
    connect(m_ui->actionAboutQt, &QAction::triggered, qApp, &QApplication::aboutQt);
    // “清除日志”菜单项点击后清空接收消息的文本编辑框
    connect(m_ui->actionClearLog, &QAction::triggered, m_ui->receivedMessagesEdit, &QTextEdit::clear);
    // “插件文档”菜单项点击后打开Qt CAN Bus插件的在线文档页面
    connect(m_ui->actionPluginDocumentation, &QAction::triggered, this, []() {
        QDesktopServices::openUrl(QUrl("http://doc.qt.io/qt-5/qtcanbus-backends.html"));
    });

}
//处理CAN总线设备发生的错误,根据不同的错误类型更新状态栏显示错误信息。
void MainWindow::processErrors(QCanBusDevice::CanBusError error) const
{
    switch (error) {
    case QCanBusDevice::ReadError:
    case QCanBusDevice::WriteError:
    case QCanBusDevice::ConnectionError:
    case QCanBusDevice::ConfigurationError:
    case QCanBusDevice::UnknownError:
        m_status->setText(m_canDevice->errorString());
        break;
    default:
        break;
    }
}
//连接CAN总线设备的过程,包括设置设备、配置参数、连接状态更新和界面反馈。
void MainWindow::connectDevice()
{
    // 读取连接对话框中的设置
    const ConnectDialog::Settings p = m_connectDialog->settings();
    // 尝试创建CAN设备
    QString errorString;
    m_canDevice.reset(QCanBus::instance()->createDevice(p.pluginName, p.deviceInterfaceName, &errorString));
    // 错误处理
    if (!m_canDevice) {
        m_status->setText(tr("Error creating device '%1', reason: '%2'").arg(p.pluginName).arg(errorString));
        return;
    }
    // 初始化帧计数
    m_numberFramesWritten = 0;
    // 连接错误、接收帧、写入帧的信号连接
    connect(m_canDevice.get(), &QCanBusDevice::errorOccurred, this, &MainWindow::processErrors);
    connect(m_canDevice.get(), &QCanBusDevice::framesReceived, this, &MainWindow::processReceivedFrames);
    connect(m_canDevice.get(), &QCanBusDevice::framesWritten, this, &MainWindow::processFramesWritten);
    // 应用设置
    if (p.useConfigurationEnabled) {
        for (const ConnectDialog::ConfigurationItem &item : p.configurations)
            m_canDevice->setConfigurationParameter(item.first, item.second);
    }

    // 连接设备
    if (!m_canDevice->connectDevice()) {
        // 错误处理
        m_status->setText(tr("Connection error: %1").arg(m_canDevice->errorString()));
        m_canDevice.reset();
    } else {
        // 更新界面状态
        m_ui->actionConnect->setEnabled(false);
        m_ui->actionDisconnect->setEnabled(true);
        m_ui->sendFrameBox->setEnabled(true);
        // 更新状态栏显示连接信息
        const QVariant bitRate = m_canDevice->configurationParameter(QCanBusDevice::BitRateKey);
        if (bitRate.isValid()) {
            const bool isCanFd = m_canDevice->configurationParameter(QCanBusDevice::CanFdKey).toBool();
            const QVariant dataBitRate = m_canDevice->configurationParameter(QCanBusDevice::DataBitRateKey);
            if (isCanFd && dataBitRate.isValid()) {
                m_status->setText(tr("Plugin: %1, connected to %2 at %3 / %4 kBit/s").arg(p.pluginName).arg(p.deviceInterfaceName).arg(bitRate.toInt() / 1000).arg(dataBitRate.toInt() / 1000));

            } else {
                m_status->setText(tr("Plugin: %1, connected to %2 at %3 kBit/s").arg(p.pluginName).arg(p.deviceInterfaceName).arg(bitRate.toInt() / 1000));
            }
        } else {
            m_status->setText(tr("Plugin: %1, connected to %2").arg(p.pluginName).arg(p.deviceInterfaceName));

        }

        // 总线状态定时器
        if (m_canDevice->hasBusStatus())
            m_busStatusTimer->start(2000);
        else
            m_ui->busStatus->setText(tr("No CAN bus status available."));
    }

}
//更新并显示CAN总线的状态,如Good, Warning, Error等。
void MainWindow::busStatus()
{
    // 检查设备和状态
    if (!m_canDevice || !m_canDevice->hasBusStatus()) {
        m_ui->busStatus->setText(tr("No CAN bus status available."));
        m_busStatusTimer->stop();
        return;
    }
    // 更新总线状态
    switch (m_canDevice->busStatus()) {
    case QCanBusDevice::CanBusStatus::Good:
        m_ui->busStatus->setText("CAN bus status: Good.");
        break;
    case QCanBusDevice::CanBusStatus::Warning:
        m_ui->busStatus->setText("CAN bus status: Warning.");
        break;
    case QCanBusDevice::CanBusStatus::Error:
        m_ui->busStatus->setText("CAN bus status: Error.");
        break;
    case QCanBusDevice::CanBusStatus::BusOff:
        m_ui->busStatus->setText("CAN bus status: Bus Off.");
        break;
    default:
        m_ui->busStatus->setText("CAN bus status: Unknown.");
        break;
    }
}
//断开与CAN总线设备的连接并更新界面状态。
void MainWindow::disconnectDevice()
{
    // 断开设备
    if (!m_canDevice)
        return;
    
    m_busStatusTimer->stop();
    m_canDevice->disconnectDevice();
    
    // 更新界面状态
    m_ui->actionConnect->setEnabled(true);
    m_ui->actionDisconnect->setEnabled(false);
    m_ui->sendFrameBox->setEnabled(false);
    m_status->setText(tr("Disconnected"));
}
//处理写入帧完成的回调,更新已写入帧计数。
void MainWindow::processFramesWritten(qint64 count)
{
    m_numberFramesWritten += count;
    m_written->setText(tr("%1 frames written").arg(m_numberFramesWritten));
}
//关闭事件处理,先关闭连接对话框再接受关闭事件。
void MainWindow::closeEvent(QCloseEvent *event)
{
    m_connectDialog->close();
    event->accept();
}
//根据CAN帧标志位返回字符串表示,如B代表BitrateSwitch开启,E代表Error State Indicator,L代表Local Echo。
static QString frameFlags(const QCanBusFrame &frame)
{
    QString result = QLatin1String(" --- ");
    
    if (frame.hasBitrateSwitch())
        result[1] = QLatin1Char('B');
    if (frame.hasErrorStateIndicator())
        result[2] = QLatin1Char('E');
    if (frame.hasLocalEcho())
        result[3] = QLatin1Char('L');
    
    return result;
}
//处理接收到的CAN帧,解析并显示到界面。
void MainWindow::processReceivedFrames()
{
    if (!m_canDevice)
        return;
    
    while (m_canDevice->framesAvailable()) {
        const QCanBusFrame frame = m_canDevice->readFrame();
        
        QString view;
        if (frame.frameType() == QCanBusFrame::ErrorFrame)
            view = m_canDevice->interpretErrorFrame(frame);
        else
            view = frame.toString();
        
        const QString time = QString::fromLatin1("%1.%2  ")
                .arg(frame.timeStamp().seconds(), 10, 10, QLatin1Char(' '))
                .arg(frame.timeStamp().microSeconds() / 100, 4, 10, QLatin1Char('0'));
        
        const QString flags = frameFlags(frame);
        
        m_ui->receivedMessagesEdit->append(time + flags + view);
    }
}
//发送CAN帧到设备。
void MainWindow::sendFrame(const QCanBusFrame &frame) const
{
    if (!m_canDevice)
        return;
    
    m_canDevice->writeFrame(frame);
}

主界面UI

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

整体UI

在这里插入图片描述

QT案例

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/683684.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

HR在线人才测评,如何判断候选人的学习能力?

在选拔人才的过程中&#xff0c;学习能力突出的候选人&#xff0c;能以非常快的速度适应工作环境&#xff0c;并且会在工作当中制定清晰的学习规划&#xff0c;不断的提升自己&#xff0c;不断的彰显个人在企业当中的价值&#xff0c;助力企业的长远发展。 只有选拔进来的人才…

【原创】springboot+mysql村务档案管理系统设计与实现

个人主页&#xff1a;程序猿小小杨 个人简介&#xff1a;从事开发多年&#xff0c;Java、Php、Python、前端开发均有涉猎 博客内容&#xff1a;Java项目实战、项目演示、技术分享 文末有作者名片&#xff0c;希望和大家一起共同进步&#xff0c;你只管努力&#xff0c;剩下的交…

【C#】多线程中,跨线程实现对UI控件更新

问题描述&#xff1a; “Cross-thread operation not valid :Control ‘listBox1’ accessed from a thread other than the thread it was created on” &#xff0c;即“线程间操作无效&#xff0c;从不是创建控件“listbox1”的线程访问它。” 原因分析&#xff1a; UI控件…

零售行业运营有哪些业务场景?详解各业务场景的分析指标和维度

在当今这个数字化迅速发展的时代&#xff0c;零售行业正经历着前所未有的变革。传统的零售模式正在被新兴的技术和创新的业务场景所颠覆&#xff0c;消费者的需求和购物习惯也在不断地演变。零售行业的运营&#xff0c;作为连接消费者、产品和市场的关键环节&#xff0c;对于零…

06Docker-Compose和微服务部署

Docker-Compose 概述 Docker Compose通过一个单独的docker-compose.yml模板文件来定义一组相关联的应用容器&#xff0c;帮助我们实现多个相互关联的Docker容器的快速部署 一般一个docker-compose.yml对应完整的项目,项目中的服务和中间件对应不同的容器 Compose文件实质就…

【CMake系列】05-静态库与动态库编译

在各种项目类型中&#xff0c;可能我们的项目就是一个 库 项目&#xff0c;向其他人提供 我们开发好的 库 (windows下的 dll /lib &#xff1b; linux下的 .a / .so)&#xff1b;有时候在一个项目中&#xff0c;我们对部分功能 打包成 库&#xff0c;方便在不同地方进行调用 静…

idea 常用插件推荐

文章目录 1、Lombok2、Convert YAML and Properties File3、Grep Console4、MyBatisX5、Free MyBatis Tool6、MyBatis Log EasyPlus &#xff08;SQL拼接&#xff09;7、MyBatisPlus8、Eclipse theme9、Eclipse Plus Theme10、Rainbow Brackets Lite - Free and OpenSource&…

MongoDB CRUD操作:地理位置应用——通过地理空间查询查找餐厅

MongoDB CRUD操作&#xff1a;地理位置应用——通过地理空间查询查找餐厅 文章目录 MongoDB CRUD操作&#xff1a;地理位置应用——通过地理空间查询查找餐厅地图的扭曲搜索餐厅浏览数据查找当前邻居查找附近所有餐厅查找一定距离内的餐厅使用$geoWithin&#xff0c;不排序使用…

上位机图像处理和嵌入式模块部署(f407 mcu中tf卡模拟u盘)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 在f407开发板上面&#xff0c;本身是有一个usb接口的。这个usb接口也不仅仅是作为电源使用的&#xff0c;它还可以用来做很多的事情。一方面&#…

学Python,看一篇就够

学Python&#xff0c;看一篇就够 python基础注释变量标识符命名规则使用变量认识bugDebug工具打断点 数据类型输出转义字符输入输入语法输入的特点 转换数据类型pycharm交互运算符的分类赋值运算符复合赋值运算符比较运算符逻辑运算符拓展 条件语句单分支语法多分支语法拓展 if…

React中实现大模型的打字机效果

React 想实现一个打字机的效果&#xff0c;类似千问、Kimi 返回的效果。调用大模型时&#xff0c;模型的回答通常是流式输出的&#xff0c;如果等到模型所有的回答全部完成之后再展示给最终用户&#xff0c;交互效果不好&#xff0c;因为模型计算推理时间比较长。本文将采用原生…

Java1.8全套家政上门服务+springboot+ mysql +Thymeleaf 技术架构开发,家政APP系统在线派单,师傅入驻全套商业源码

Java1.8全套家政上门服务springboot mysql Thymeleaf 技术架构开发&#xff0c;家政APP系统在线派单&#xff0c;师傅入驻全套商业源码 家政预约上门小程序的实用性&#xff1f; 家政预约上门小程序的实用性主要体现在以下几个方面&#xff1a; 一、方便快捷的预约体验&#…

运行编译openjdk12-33

编译环境 ubuntu20 Ubuntu里用户可以自行选择安装GCC或CLang来进行编译&#xff0c;但必须确保最低的版本为GCC 4.8或者CLang 3.2以上&#xff0c;官方推荐使用GCC 7.8或者CLang 9.1来完成编译。 源码 https://github.com/openjdk/jdk/tree/jdk-12%2B33 安装gcc sudo apt…

气膜羽毛球馆如何提升运动体验—轻空间

随着人们对健康和运动的关注度日益增加&#xff0c;羽毛球作为一项受欢迎的运动&#xff0c;得到了越来越多人的喜爱。而气膜羽毛球馆&#xff0c;以其独特的优势&#xff0c;正在改变传统羽毛球馆的运动体验。那么&#xff0c;气膜羽毛球馆是如何提升运动体验的呢&#xff1f;…

白酒:全球化背景下产地白酒的国际竞争与合作

在全球化背景下&#xff0c;云仓酒庄豪迈白酒作为中国白酒的品牌之一&#xff0c;面临着国际竞争与合作的机遇与挑战。国际市场竞争的激烈以及消费者需求的多样化&#xff0c;要求云仓酒庄豪迈白酒不断提升品质、拓展市场以及加强国际合作&#xff0c;以提升品牌竞争力和市场份…

深度学习Day-19:DenseNet算法实战与解析

&#x1f368; 本文为&#xff1a;[&#x1f517;365天深度学习训练营] 中的学习记录博客 &#x1f356; 原作者&#xff1a;[K同学啊 | 接辅导、项目定制] 要求&#xff1a; 根据 Pytorch 代码&#xff0c;编写出 TensorFlow 代码研究 DenseNet 与 ResNetV 的区别改进思路是…

SBOM是如何帮助医疗器械制造商提高产品透明度的?

SBOM&#xff08;软件物料清单&#xff09;通过以下方式帮助医疗器械制造商提高产品透明度&#xff1a; 1. 详细记录软件组成 SBOM详细列出了医疗器械所使用的所有软件组件、版本、作者、许可证信息等。这使得制造商能够清晰地了解产品的软件组成&#xff0c;包括每个组件的来…

Plotly的魔力:如何用Python创建令人惊叹的图表?

大家好&#xff0c;在数据分析和可视化领域&#xff0c;图表是不可或缺的工具。它们可以帮助我们更直观地理解数据趋势和模式。今天&#xff0c;我们要介绍的是一个强大的Python库——Plotly&#xff0c;它可以让你轻松创建交互式、漂亮的图表。无论你是数据科学家、分析师&…

C++学习/复习12--vector的实现(三个基本成员函数/迭代器/扩容/插入删除/重载/测试/杨辉三角)

一、构造函数 1.匿名对象与构造函数 在C中&#xff0c;匿名对象是一个临时对象&#xff0c;它没有名称&#xff0c;通常在对象创建后&#xff0c;只使用一次后就被销毁。创建匿名对象的方式是在创建对象时不使用变量名。 下面是创建匿名对象的几种方式&#xff1a; 直接使用…

【Linux】(二)—— 用户和用户组管理

在了解了Linux基础命令格式后&#xff0c;今天我要介绍的是Linux的用户管理 目录 root用户用户管理操作用户ID切换用户查看用户添加用户指定密码修改用户信息删除用户退出登录查看登陆系统的用户 用户组的管理增加新用户组查看用户组删除用户组修改用户组切换用户组 与用户有关…