#ifndef DATAPROCESSOR_H
#define DATAPROCESSOR_H
#include <QString>
#include <QByteArray>
#include <QColor>
class DataProcessor
{
public:
DataProcessor();
// ANSI序列处理
static QString processIncompleteData(const QString &newData, QString &buffer);
static QString parseAnsiToRichText(const QString &input);
// 数据转换
static QString byteArrayToHexString(const QByteArray &byteArray);
// 文件类型判断
enum FileType {
FILE_TYPE_UNKNOWN,
FILE_TYPE_DIRECTORY,
FILE_TYPE_EXECUTABLE
};
static FileType getFileType(const QString &fileName);
private:
static bool checkAnsiSequenceIntegrity(const QString &text);
static QColor ansiToQColor(int ansiCode);
};
#endif // DATAPROCESSOR_H
#ifndef HIDMANAGER_H
#define HIDMANAGER_H
#include <QObject>
#include <QLibrary>
#include <QThread>
#include <QMutex>
#include <QStringList>
#include <QTime>
#include <vector>
#include <string>
// 声明HID相关函数指针
typedef struct hid_device_info hid_device_info;
typedef hid_device_info* (*HidEnumerateFunc)(unsigned short vendor_id, unsigned short product_id);
typedef void (*HidFreeEnumerationFunc)(hid_device_info* devs);
typedef void* (*HidOpenPathFunc)(const char* path);
typedef void (*HidCloseFunc)(void* dev_handle);
typedef int (*HidWriteFunc)(void* dev_handle, const unsigned char* data, size_t length);
typedef int (*HidReadTimeoutFunc)(void* dev_handle, unsigned char* data, size_t length, int milliseconds);
typedef const wchar_t* (*HidErrorFunc)(void* dev_handle);
class HidWorker;
class HidManager : public QObject
{
Q_OBJECT
public:
explicit HidManager(QObject *parent = nullptr);
~HidManager();
bool initHidLibrary();
QStringList enumerateHidDevices();
bool connectHidDevice(const QString &devicePath);
void disconnectHidDevice();
bool isHidConnected() const;
void sendData(const QString &data);
signals:
void deviceListChanged(const QStringList &deviceList);
void dataReceived(const QByteArray &data, int bytesRead);
void errorOccurred(const QString &errorMsg);
void connectionStateChanged(bool isConnected);
// 新增信号:用于通知设备已断开连接(只发送一次)
void deviceDisconnected(const QString &errorMsg);
private slots:
void handleAsyncHidData(const QByteArray& data, int bytesRead);
void handleHidReadError(const QString& errorMsg);
void onReadLoopExited();
private:
// 连接状态相关(线程安全)
volatile bool m_isHidConnected;
// 新增标志:跟踪设备是否已发送断开通知
volatile bool m_deviceDisconnectNotified;
mutable QMutex m_connectionMutex;
void setHidConnected(bool connected);
// 设备枚举相关
std::vector<std::string> hidDevicePaths;
QStringList hidDeviceInfos;
QMutex m_enumMutex; // 保护设备路径和信息的读写
// 基础成员变量
QLibrary *hidLibrary;
bool isHidLoaded;
void *currentHidDevice; // 设备句柄,由HidManager统一管理
QThread *hidReadThread;
bool isHidThreadRunning;
QMutex m_threadMutex; // 重命名为m_threadMutex,避免与其他锁混淆
// HID函数指针
HidEnumerateFunc hidEnumerate;
HidFreeEnumerationFunc hidFreeEnumeration;
HidOpenPathFunc hidOpenPath;
HidCloseFunc hidClose;
HidWriteFunc hidWrite;
HidReadTimeoutFunc hidReadTimeout;
HidErrorFunc hidError;
void asyncHidReadLoop();
};
class HidWorker : public QObject
{
Q_OBJECT
public:
explicit HidWorker(void* hidDev, HidReadTimeoutFunc readFunc, HidErrorFunc errorFunc,
QObject* parent = nullptr);
~HidWorker();
void setHidDev(void* dev);
void stopReadLoop();
void interruptRead();
public slots:
void startReadLoop();
signals:
void dataReceived(const QByteArray& data, int bytesRead);
void readError(const QString& errorMsg);
void readLoopExited();
private:
void* m_hidDev; // 仅用于读取,不负责释放
HidReadTimeoutFunc m_hidReadTimeout;
HidErrorFunc m_hidError;
volatile bool m_isRunning;
QMutex m_mutex; // 添加互斥锁保护m_isRunning
};
#endif // HIDMANAGER_H
#ifndef SERIALPORTMANAGER_H
#define SERIALPORTMANAGER_H
#include <QObject>
#include <QSerialPort>
#include <QTimer>
#include <QStringList>
class SerialPortManager : public QObject
{
Q_OBJECT
public:
explicit SerialPortManager(QObject *parent = nullptr);
~SerialPortManager();
void startPortCheckTimer(int interval = 100);
void stopPortCheckTimer();
QStringList getAvailablePorts();
bool connectPort(const QString &portName, qint32 baudRate = 460800);
void disconnectPort();
bool isPortConnected() const;
void sendData(const QString &data);
signals:
void portListChanged(const QStringList &portList);
void dataReceived(const QByteArray &data);
void connectionStateChanged(bool isConnected);
void errorOccurred(const QString &errorMsg);
private slots:
void checkPortChanges();
void readSerialData();
private:
QSerialPort *serialPort;
QTimer *portCheckTimer;
QStringList lastPortNameList;
bool isConnected;
};
#endif // SERIALPORTMANAGER_H
//#ifndef WIDGET_H
//#define WIDGET_H
//#include <QWidget>
//#include <QSerialPort>
//#include <QTimer>
//#include <QEvent>
//#include <QKeyEvent>
//#include <QStringList>
//#include "serialportmanager.h"
//#include "hidmanager.h"
//#include "dataprocessor.h"
//namespace Ui {
//class Widget;
//}
//class Widget : public QWidget
//{
// Q_OBJECT
//public:
// explicit Widget(QWidget *parent = nullptr);
// ~Widget();
// enum class DeviceType {
// DEVICE_SERIAL,
// DEVICE_HID
// };
//signals:
// void serialConnectionStateChanged(bool isConnected);
//private slots:
// void on_refresh_pb_clicked();
// void on_break_pb_clicked();
// void on_pushButton_clicked();
// // 串口相关槽函数
// void onSerialPortListChanged(const QStringList &portList);
// void onSerialDataReceived(const QByteArray &data);
// void onSerialConnectionStateChanged(bool isConnected);
// void onSerialErrorOccurred(const QString &errorMsg);
// // HID相关槽函数
// void onHidDeviceListChanged(const QStringList &deviceList);
// void onHidDataReceived(const QByteArray &data, int bytesRead);
// void onHidConnectionStateChanged(bool isConnected);
// void onHidErrorOccurred(const QString &errorMsg);
//private:
// Ui::Widget *ui;
// SerialPortManager *serialManager;
// HidManager *hidManager;
// DataProcessor *dataProcessor;
// bool isSerialConnected;
// bool isHidConnected;
// QList<DeviceType> deviceTypes;
// QStringList commandHistory;
// int historyIndex;
// QString currentDraft;
// QString ansiBuffer;
// QString hidAnsiBuffer;
// void initUI();
// void initConnections();
// void getAvailableDevices();
// QString getSelectedPortName();
// void updateConnectButtonState();
// DeviceType getSelectedDeviceType();
// void logMessage(const QString &message);
// void sendDataToDevice(const QString &data);
// void handleHistoryNavigation(int direction);
// bool eventFilter(QObject *watched, QEvent *event);
//};
//#endif // WIDGET_H
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QSerialPort>
#include <QTimer>
#include <QEvent>
#include <QKeyEvent>
#include <QStringList>
#include "serialportmanager.h"
#include "hidmanager.h"
#include "dataprocessor.h"
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = nullptr);
~Widget();
enum class DeviceType {
DEVICE_SERIAL,
DEVICE_HID
};
signals:
void serialConnectionStateChanged(bool isConnected);
private slots:
void on_refresh_pb_clicked();
void on_break_pb_clicked();
void on_pushButton_clicked();
// 串口相关槽函数
void onSerialPortListChanged(const QStringList &portList);
void onSerialDataReceived(const QByteArray &data);
void onSerialConnectionStateChanged(bool isConnected);
void onSerialErrorOccurred(const QString &errorMsg);
// HID相关槽函数
void onHidDeviceDisconnected(const QString &errorMsg);
void onHidDeviceListChanged(const QStringList &deviceList);
void onHidDataReceived(const QByteArray &data, int bytesRead);
void onHidConnectionStateChanged(bool isConnected);
void onHidErrorOccurred(const QString &errorMsg);
private:
Ui::Widget *ui;
SerialPortManager *serialManager;
HidManager *hidManager;
DataProcessor *dataProcessor;
bool isSerialConnected;
bool isHidConnected;
QList<DeviceType> deviceTypes;
QStringList commandHistory;
int historyIndex;
QString currentDraft;
QString ansiBuffer;
QString hidAnsiBuffer;
void initUI();
void initConnections();
void getAvailableDevices();
QString getSelectedPortName();
void updateConnectButtonState();
DeviceType getSelectedDeviceType();
void logMessage(const QString &message);
void sendDataToDevice(const QString &data);
void handleHistoryNavigation(int direction);
bool eventFilter(QObject *watched, QEvent *event);
};
#endif // WIDGET_H
#include "dataprocessor.h"
#include <QRegExp>
#include <QTime>
DataProcessor::DataProcessor()
{
}
bool DataProcessor::checkAnsiSequenceIntegrity(const QString &text)
{
QStringList commands = {"ls", "pwd", "cd", "mkdir", "rm", "cp", "mv", "cat"};
int commandStart = -1;
QString foundCommand;
foreach (const QString &cmd, commands) {
commandStart = text.indexOf(cmd);
if (commandStart != -1) {
foundCommand = cmd;
break;
}
}
if (commandStart == -1) {
QRegExp unknownCmdRegex("\\b\\w+\\b");
if (unknownCmdRegex.indexIn(text) == -1) {
return false;
}
foundCommand = unknownCmdRegex.cap(0);
commandStart = unknownCmdRegex.pos(0);
}
int commandEnd = commandStart + foundCommand.length();
if (commandEnd >= text.length() || text.mid(commandEnd, 2) != "\r\n") {
return false;
}
int endMarkerPos = text.indexOf("\r\n[LB]:-)", commandEnd);
if (endMarkerPos != -1) {
QString contentBetween = text.mid(commandEnd + 2, endMarkerPos - (commandEnd + 2));
if (foundCommand == "ls") {
QRegExp ansiRegex("\x1B\\[([0-9;]*[mKfsu])(\\w+)");
int ansiCount = 0;
int pos = 0;
while ((pos = ansiRegex.indexIn(contentBetween, pos)) != -1) {
ansiCount++;
pos += ansiRegex.matchedLength();
}
return ansiCount >= 1;
}
else if (foundCommand == "pwd") {
return contentBetween.contains("/");
}
else {
return true;
}
}
else {
return (commandEnd + 2) == text.length();
}
}
QString DataProcessor::processIncompleteData(const QString &newData, QString &buffer)
{
buffer += newData;
QRegExp completeAnsiRegex("\x1B\\[([0-9;]*[mKfsu])");
QRegExp incompleteAnsiRegex(".*\x1B\\[([0-9;]*)$");
if (incompleteAnsiRegex.exactMatch(buffer)) {
int splitPos = buffer.lastIndexOf("\x1B[");
QString processable = buffer.left(splitPos);
buffer = buffer.mid(splitPos);
if (!processable.isEmpty() && !checkAnsiSequenceIntegrity(processable)) {
buffer = processable + buffer;
return "";
}
return processable;
}
if (!checkAnsiSequenceIntegrity(buffer)) {
return "";
}
QString processable = buffer;
buffer.clear();
return processable;
}
QColor DataProcessor::ansiToQColor(int ansiCode)
{
switch(ansiCode) {
case 30: return QColor(0, 0, 0); // 黑色
case 31: return QColor(170, 0, 0); // 红色
case 32: return QColor(0, 170, 0); // 绿色
case 33: return QColor(170, 85, 0); // 黄色
case 34: return QColor(0, 0, 170); // 蓝色
case 35: return QColor(170, 0, 170); // 洋红色
case 36: return QColor(0, 170, 170); // 青色
case 37: return QColor(170, 170, 170); // 白色
case 90: return QColor(85, 85, 85); // 亮黑(灰)
case 91: return QColor(255, 85, 85); // 亮红
case 92: return QColor(85, 255, 85); // 亮绿
case 93: return QColor(255, 255, 85); // 亮黄
case 94: return QColor(85, 85, 255); // 亮蓝
case 95: return QColor(255, 85, 255); // 亮洋红
case 96: return QColor(85, 255, 255); // 亮青
case 97: return QColor(255, 255, 255); // 亮白
case 38: return QColor(114, 159, 207); // 亮蓝色 - 普通文件
case 100: return QColor(85, 85, 85); // 亮黑背景
default: return QColor(170, 170, 170); // 默认灰白色
}
}
DataProcessor::FileType DataProcessor::getFileType(const QString &fileName)
{
if (fileName == "bin" || fileName == "sbin" || fileName == "usr" ||
fileName == "var" || fileName == "mnt" || fileName == "proc" ||
fileName == "sys" || fileName == "root" || fileName == "tmp" ||
fileName == "lib" || fileName == "etc" || fileName == "overlay" ||
fileName == "rom") {
return FILE_TYPE_DIRECTORY;
} else if (fileName == "www") {
return FILE_TYPE_EXECUTABLE;
}
return FILE_TYPE_UNKNOWN;
}
QString DataProcessor::byteArrayToHexString(const QByteArray &byteArray)
{
QString hexString;
for (const char &c : byteArray) {
hexString += QString("%1 ").arg((unsigned char)c, 2, 16, QChar('0')).toUpper();
}
return hexString.trimmed();
}
QString DataProcessor::parseAnsiToRichText(const QString &input)
{
QString html;
QString currentStyle;
QRegExp ansiRegex("\x1B\\[([0-9;]*)([mKfsu])");
QRegExp fileNameRegex("(\x1B\\[[0-9;]*m)(\\w+)");
html += "<div style='font-family: Consolas, monospace; white-space: pre; padding: 8px; background-color: #fff; color: #000;'>";
int pos = 0;
int lastPos = 0;
while ((pos = ansiRegex.indexIn(input, pos)) != -1) {
if (pos > lastPos) {
QString text = input.mid(lastPos, pos - lastPos);
text.replace("&", "&").replace("<", "<").replace(">", ">");
if (!currentStyle.isEmpty()) {
html += QString("<span style=\"%1\">%2</span>").arg(currentStyle, text);
} else {
html += text;
}
}
QString codeStr = ansiRegex.cap(1);
QString command = ansiRegex.cap(2);
if (command == "m") {
QStringList codes = codeStr.split(';', QString::SkipEmptyParts);
if (codes.isEmpty() || codes.contains("0")) {
currentStyle.clear();
} else {
foreach (QString code, codes) {
bool ok;
int codeNum = code.toInt(&ok);
if (!ok) continue;
switch (codeNum) {
case 1:
currentStyle += "font-weight: bold; ";
break;
case 4:
currentStyle += "text-decoration: underline; ";
break;
case 22:
currentStyle.remove("font-weight: bold; ");
break;
case 24:
currentStyle.remove("text-decoration: underline; ");
break;
default:
if (codeNum >= 30 && codeNum <= 37) {
QColor color = ansiToQColor(codeNum);
currentStyle += QString("color: rgb(%1,%2,%3); ")
.arg(color.red()).arg(color.green()).arg(color.blue());
} else if (codeNum >= 90 && codeNum <= 97) {
QColor color = ansiToQColor(codeNum);
currentStyle += QString("color: rgb(%1,%2,%3); ")
.arg(color.red()).arg(color.green()).arg(color.blue());
} else if (codeNum >= 40 && codeNum <= 47) {
QColor color = ansiToQColor(codeNum - 10);
currentStyle += QString("background-color: rgb(%1,%2,%3); ")
.arg(color.red()).arg(color.green()).arg(color.blue());
} else if (codeNum >= 100 && codeNum <= 107) {
QColor color = ansiToQColor(codeNum - 70);
currentStyle += QString("background-color: rgb(%1,%2,%3); ")
.arg(color.red()).arg(color.green()).arg(color.blue());
}
break;
}
}
}
} else if (command == "K") {
html += "<br>";
}
pos += ansiRegex.matchedLength();
lastPos = pos;
}
if (lastPos < input.length()) {
QString text = input.mid(lastPos);
text.replace("&", "&").replace("<", "<").replace(">", ">");
int filePos = 0;
while ((filePos = fileNameRegex.indexIn(text, filePos)) != -1) {
QString ansiCode = fileNameRegex.cap(1);
QString fileName = fileNameRegex.cap(2);
FileType type = getFileType(fileName);
QString fileStyle = currentStyle;
if (type == FILE_TYPE_DIRECTORY) {
fileStyle += "color: rgb(85, 85, 255); ";
} else if (type == FILE_TYPE_EXECUTABLE) {
fileStyle += "color: rgb(85, 255, 85); font-weight: bold; ";
}
html += QString("<span style=\"%1\">%2</span>").arg(fileStyle, fileName);
filePos += fileNameRegex.matchedLength();
}
if (!currentStyle.isEmpty() && filePos == 0) {
html += QString("<span style=\"%1\">%2</span>").arg(currentStyle, text);
} else if (filePos < text.length()) {
html += text.mid(filePos);
}
}
html += "</div>";
return html;
}
#include "hidmanager.h"
#include <QDebug>
#include "hidapi.h"
// ------------------------------ HidManager 实现 ------------------------------
void HidManager::setHidConnected(bool connected)
{
QMutexLocker locker(&m_connectionMutex);
if (m_isHidConnected != connected) {
m_isHidConnected = connected;
// 连接状态改变时重置断开通知标志
if (connected) {
m_deviceDisconnectNotified = false;
}
emit connectionStateChanged(connected);
}
}
bool HidManager::isHidConnected() const
{
QMutexLocker locker(&m_connectionMutex);
return m_isHidConnected;
}
HidManager::HidManager(QObject *parent) : QObject(parent),
m_isHidConnected(false), m_deviceDisconnectNotified(false),isHidLoaded(false),
currentHidDevice(nullptr), hidReadThread(nullptr), isHidThreadRunning(false)
{
}
HidManager::~HidManager()
{
disconnectHidDevice();
if (hidLibrary) {
hidLibrary->unload();
delete hidLibrary;
hidLibrary = nullptr;
}
}
bool HidManager::initHidLibrary()
{
// 检查是否已加载,避免重复初始化
if (isHidLoaded) {
emit errorOccurred("HID库已加载,无需重复初始化");
return true;
}
hidLibrary = new QLibrary("hidapi.dll");
if (!hidLibrary->load()) {
QString err = QString("HID库加载失败:%1").arg(hidLibrary->errorString());
emit errorOccurred(err);
delete hidLibrary;
hidLibrary = nullptr;
return false;
}
// 解析函数指针
hidEnumerate = (HidEnumerateFunc)hidLibrary->resolve("hid_enumerate");
hidFreeEnumeration = (HidFreeEnumerationFunc)hidLibrary->resolve("hid_free_enumeration");
hidOpenPath = (HidOpenPathFunc)hidLibrary->resolve("hid_open_path");
hidClose = (HidCloseFunc)hidLibrary->resolve("hid_close");
hidWrite = (HidWriteFunc)hidLibrary->resolve("hid_write");
hidReadTimeout = (HidReadTimeoutFunc)hidLibrary->resolve("hid_read_timeout");
hidError = (HidErrorFunc)hidLibrary->resolve("hid_error");
if (!hidEnumerate || !hidFreeEnumeration || !hidOpenPath ||
!hidClose || !hidWrite || !hidReadTimeout || !hidError) {
emit errorOccurred("HID库函数解析失败,部分函数指针为空");
hidLibrary->unload();
delete hidLibrary;
hidLibrary = nullptr;
return false;
}
isHidLoaded = true;
emit errorOccurred("HID库初始化成功");
return true;
}
QStringList HidManager::enumerateHidDevices()
{
if (!isHidLoaded) {
emit errorOccurred("HID库未加载,无法枚举设备");
return QStringList();
}
QMutexLocker enumLocker(&m_enumMutex);
hidDevicePaths.clear();
hidDeviceInfos.clear();
// 添加空指针检查
if (!hidEnumerate) {
emit errorOccurred("hidEnumerate函数指针为空,无法枚举设备");
return QStringList();
}
hid_device_info *devs = hidEnumerate(0x0, 0x0);
if (!devs) {
emit errorOccurred("HID设备枚举失败,未获取到设备列表");
return QStringList();
}
hid_device_info *cur_dev = devs;
QStringList deviceList;
int count = 0;
while (cur_dev) {
// 增加对设备信息的有效性检查
if (!cur_dev) break;
if (cur_dev->path == nullptr) {
cur_dev = cur_dev->next;
continue;
}
// 处理可能的空指针
const wchar_t* productStr = cur_dev->product_string ? cur_dev->product_string : L"未知设备";
const wchar_t* manufacturerStr = cur_dev->manufacturer_string ? cur_dev->manufacturer_string : L"未知厂商";
// 使用更安全的字符串处理
QString productInfo = QString::fromWCharArray(productStr);
QString manufacturerInfo = QString::fromWCharArray(manufacturerStr);
QString info = QString("[HID] VID:0x%1, PID:0x%2, 厂商:%3, 产品:%4")
.arg(cur_dev->vendor_id, 4, 16, QChar('0'))
.arg(cur_dev->product_id, 4, 16, QChar('0'))
.arg(manufacturerInfo)
.arg(productInfo);
// 检查路径有效性
std::string devPathStr(cur_dev->path);
if (devPathStr.empty()) {
cur_dev = cur_dev->next;
continue;
}
hidDevicePaths.push_back(devPathStr);
hidDeviceInfos.append(info);
deviceList.append(info);
cur_dev = cur_dev->next;
count++;
}
// 确保释放资源
hidFreeEnumeration(devs); // 无论如何都释放资源
if (count == 0) {
emit errorOccurred("未发现HID设备");
} else {
emit errorOccurred(QString("发现%1个HID设备,枚举完成").arg(count));
}
return deviceList;
}
bool HidManager::connectHidDevice(const QString &devicePath)
{
if (!isHidLoaded) {
emit errorOccurred("HID连接失败:hidapi库未加载");
return false;
}
QMutexLocker enumLocker(&m_enumMutex);
if (isHidConnected()) {
disconnectHidDevice();
QThread::msleep(100);
}
int index = -1;
for (int i = 0; i < hidDeviceInfos.size(); i++) {
if (hidDeviceInfos[i] == devicePath) {
index = i;
break;
}
}
if (index < 0 || static_cast<size_t>(index) >= hidDevicePaths.size()) {
emit errorOccurred(QString("HID连接失败:无效的设备路径[%1]").arg(devicePath));
return false;
}
const char* devPath = hidDevicePaths[index].c_str();
currentHidDevice = hidOpenPath(devPath);
if (!currentHidDevice) {
emit errorOccurred(QString("HID设备打开失败:路径[%1]").arg(QString::fromUtf8(devPath)));
setHidConnected(false);
return false;
}
setHidConnected(true);
emit errorOccurred(QString("HID设备连接成功:%1").arg(devicePath));
asyncHidReadLoop();
return true;
}
void HidManager::disconnectHidDevice()
{
QMutexLocker threadLocker(&m_threadMutex);
if (!isHidConnected() || !currentHidDevice) {
emit errorOccurred("HID设备未连接或句柄已无效,无需断开");
return;
}
if (isHidThreadRunning && hidReadThread && hidReadThread->isRunning()) {
HidWorker* worker = hidReadThread->findChild<HidWorker*>();
if (worker) {
worker->stopReadLoop();
bool isExited = hidReadThread->wait(1000);//3000
if (!isExited) {
qWarning() << "HID线程超时未退出,强制终止";
hidReadThread->terminate();
hidReadThread->wait(500);//1000
}
disconnect(worker, nullptr, this, nullptr);
}
hidReadThread = nullptr;
isHidThreadRunning = false;
}
if (currentHidDevice) {
hidClose(currentHidDevice);
currentHidDevice = nullptr;
}
setHidConnected(false);
emit errorOccurred("HID设备断开流程完成");
}
void HidManager::sendData(const QString &data)
{
if (!isHidConnected()) {
emit errorOccurred("HID发送失败:未连接HID设备");
return;
}
if (!isHidLoaded || !currentHidDevice) {
setHidConnected(false);
emit errorOccurred("HID发送失败:库未加载或设备句柄无效");
return;
}
QString trimmedData = data.trimmed();
if (trimmedData.isEmpty()) {
emit errorOccurred("HID发送失败:输入数据为空(已过滤空白字符)");
return;
}
QByteArray sendData = trimmedData.toUtf8();
if (sendData.isEmpty()) {
emit errorOccurred("HID发送失败:数据转换为UTF-8后为空(无效字符)");
return;
}
QByteArray reportData;
reportData.append((char)0x00); // 报告ID
int maxDataLen = 64 - reportData.size();
if (sendData.size() > maxDataLen) {
sendData = sendData.left(maxDataLen);
emit errorOccurred(QString("HID发送数据超长,已截断至%1字节").arg(maxDataLen));
}
reportData.append(sendData);
int result = hidWrite(currentHidDevice, (const unsigned char*)reportData.data(), reportData.size());
if (result == -1) {
QString errMsg = QString("HID发送失败:%1").arg(QString::fromWCharArray(hidError(currentHidDevice)));
emit errorOccurred(errMsg);
if (errMsg.contains("句柄无效") || errMsg.contains("设备没有连接", Qt::CaseInsensitive)) {
disconnectHidDevice();
}
} else {
emit errorOccurred(QString("HID发送成功:%1字节").arg(result));
}
}
void HidManager::handleAsyncHidData(const QByteArray& data, int bytesRead)
{
emit dataReceived(data, bytesRead);
}
void HidManager::handleHidReadError(const QString& errorMsg)
{
QMutexLocker locker(&m_connectionMutex);
// 检查是否是设备断开类错误
bool isDisconnectError = errorMsg.contains("句柄无效") ||
errorMsg.contains("设备未连接", Qt::CaseInsensitive) ||
errorMsg.contains("设备没有连接", Qt::CaseInsensitive) ||
errorMsg.contains("操作超时", Qt::CaseInsensitive);
// 如果是设备断开错误且尚未通知,则只发送一次
if (isDisconnectError) {
if (!m_deviceDisconnectNotified) {
m_deviceDisconnectNotified = true;
emit deviceDisconnected(errorMsg); // 发送专门的断开通知
emit errorOccurred("HID 读取错误:" + errorMsg);
// 断开连接
locker.unlock(); // 解锁以避免死锁
disconnectHidDevice();
emit connectionStateChanged(false);
}
} else {
// 其他错误正常处理
static QString lastError;
static QTime lastErrorTime;
if (errorMsg == lastError && lastErrorTime.elapsed() < 1000) {
return;
}
lastError = errorMsg;
lastErrorTime.start();
emit errorOccurred("HID 读取错误:" + errorMsg);
}
}
void HidManager::onReadLoopExited()
{
QMutexLocker threadLocker(&m_threadMutex);
isHidThreadRunning = false;
emit errorOccurred("HID读取循环已退出");
}
void HidManager::asyncHidReadLoop()
{
QMutexLocker threadLocker(&m_threadMutex);
if (isHidThreadRunning && hidReadThread) {
HidWorker* worker = hidReadThread->findChild<HidWorker*>();
if (worker) {
worker->stopReadLoop();
hidReadThread->wait(1000);
}
delete hidReadThread;
hidReadThread = nullptr;
}
hidReadThread = new QThread();
HidWorker* worker = new HidWorker(currentHidDevice, hidReadTimeout, hidError);
worker->moveToThread(hidReadThread);
connect(worker, &HidWorker::dataReceived, this, &HidManager::handleAsyncHidData);
connect(worker, &HidWorker::readError, this, &HidManager::handleHidReadError);
connect(worker, &HidWorker::readLoopExited, this, &HidManager::onReadLoopExited);
connect(worker, &HidWorker::readLoopExited, worker, &HidWorker::deleteLater);
connect(worker, &HidWorker::readLoopExited, hidReadThread, &QThread::quit);
connect(hidReadThread, &QThread::finished, hidReadThread, &QThread::deleteLater);
// 连接启动信号
connect(hidReadThread, &QThread::started, worker, &HidWorker::startReadLoop);
isHidThreadRunning = true;
hidReadThread->start();
}
// ------------------------------ HidWorker 实现 ------------------------------
HidWorker::HidWorker(void* hidDev, HidReadTimeoutFunc readFunc, HidErrorFunc errorFunc,
QObject* parent) : QObject(parent),
m_hidDev(hidDev), m_hidReadTimeout(readFunc), m_hidError(errorFunc), m_isRunning(false)
{
}
HidWorker::~HidWorker()
{
stopReadLoop();
}
void HidWorker::setHidDev(void* dev)
{
QMutexLocker locker(&m_mutex);
m_hidDev = dev;
}
void HidWorker::stopReadLoop()
{
QMutexLocker locker(&m_mutex);
m_isRunning = false;
}
void HidWorker::interruptRead()
{
// 如果需要中断阻塞的read操作,可以在这里实现
}
void HidWorker::startReadLoop()
{
QMutexLocker locker(&m_mutex);
m_isRunning = true;
locker.unlock(); // 释放锁,允许其他操作修改m_isRunning
unsigned char buffer[64]; // HID通常使用64字节的缓冲区
while (true) {
// 检查是否需要退出循环
locker.relock();
bool running = m_isRunning;
locker.unlock();
if (!running) {
break;
}
// 检查设备句柄是否有效
locker.relock();
void* hidDev = m_hidDev;
locker.unlock();
if (!hidDev) {
emit readError("设备句柄无效");
break;
}
// 读取数据,超时时间设为100ms,以便定期检查退出标志
int bytesRead = m_hidReadTimeout(hidDev, buffer, sizeof(buffer), 100);
if (bytesRead > 0) {
// 读取到数据,发送信号
emit dataReceived(QByteArray((char*)buffer, bytesRead), bytesRead);
} else if (bytesRead < 0) {
// 发生错误
QString errorMsg = QString::fromWCharArray(m_hidError(hidDev));
emit readError(errorMsg);
// 如果是致命错误,退出循环
if (errorMsg.contains("句柄无效") ||
errorMsg.contains("设备未连接") ||
errorMsg.contains("设备没有连接")) {
break;
}
}
// bytesRead == 0 表示超时,继续循环
}
emit readLoopExited();
}
#include "serialportmanager.h"
#include <QSerialPortInfo>
#include <QDebug>
SerialPortManager::SerialPortManager(QObject *parent) : QObject(parent), isConnected(false)
{
serialPort = new QSerialPort(this);
portCheckTimer = new QTimer(this);
connect(portCheckTimer, &QTimer::timeout, this, &SerialPortManager::checkPortChanges);
connect(serialPort, &QSerialPort::readyRead, this, &SerialPortManager::readSerialData);
}
SerialPortManager::~SerialPortManager()
{
if (serialPort->isOpen()) {
serialPort->close();
}
}
void SerialPortManager::startPortCheckTimer(int interval)
{
portCheckTimer->setInterval(interval);
portCheckTimer->start();
}
void SerialPortManager::stopPortCheckTimer()
{
portCheckTimer->stop();
}
QStringList SerialPortManager::getAvailablePorts()
{
QStringList portList;
foreach (const QSerialPortInfo &portInfo, QSerialPortInfo::availablePorts()) {
portList << portInfo.portName();
}
return portList;
}
bool SerialPortManager::connectPort(const QString &portName, qint32 baudRate)
{
if (serialPort->isOpen()) {
serialPort->close();
}
serialPort->setPortName(portName);
serialPort->setBaudRate(baudRate);
serialPort->setDataBits(QSerialPort::Data8);
serialPort->setParity(QSerialPort::NoParity);
serialPort->setStopBits(QSerialPort::OneStop);
serialPort->setFlowControl(QSerialPort::NoFlowControl);
if (!serialPort->open(QIODevice::ReadWrite)) {
emit errorOccurred(serialPort->errorString());
return false;
}
isConnected = true;
emit connectionStateChanged(true);
return true;
}
void SerialPortManager::disconnectPort()
{
if (serialPort->isOpen()) {
serialPort->close();
}
isConnected = false;
emit connectionStateChanged(false);
}
bool SerialPortManager::isPortConnected() const
{
return isConnected;
}
void SerialPortManager::sendData(const QString &data)
{
if (!isConnected || !serialPort->isOpen()) {
emit errorOccurred("Serial port not connected");
return;
}
QString sendData = data.trimmed() + "\r\n";
QByteArray sendBytes = sendData.toUtf8();
qint64 sendLen = serialPort->write(sendBytes);
if (sendLen == -1) {
emit errorOccurred(serialPort->errorString());
}
}
void SerialPortManager::checkPortChanges()
{
QStringList currentPortList = getAvailablePorts();
if (currentPortList != lastPortNameList) {
lastPortNameList = currentPortList;
emit portListChanged(currentPortList);
}
}
void SerialPortManager::readSerialData()
{
if (!serialPort->isOpen()) return;
QByteArray recvBytes = serialPort->readAll();
if (!recvBytes.isEmpty()) {
emit dataReceived(recvBytes);
}
}
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
#include <QTextCursor>
#include <QDateTime>
#include <QRegExp>
#include <QFont>
#include <QMessageBox>
#include <QSerialPortInfo>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
, isSerialConnected(false)
, isHidConnected(false)
, historyIndex(-1)
{
ui->setupUi(this);
serialManager = new SerialPortManager(this);
hidManager = new HidManager(this);
dataProcessor = new DataProcessor();
initUI();
initConnections();
// 初始化HID库
bool isHidLoaded = hidManager->initHidLibrary();
if (isHidLoaded) {
logMessage("HID初始化成功:支持HID设备读写");
} else {
logMessage("HID初始化失败:请确保hidapi.dll在程序目录");
}
// 获取可用设备
getAvailableDevices();
// 优先选中COM3
bool com3Exists = false;
int com3Index = -1;
for (int i = 0; i < ui->number_cm->count(); ++i) {
QString portText = ui->number_cm->itemText(i);
if (portText.contains("USB 串行设备", Qt::CaseInsensitive) ) {
com3Exists = true;
com3Index = i;
break;
}
}
if (com3Exists) {
//logMessage("检测到USB串行,自动选中");
//ui->number_cm->setCurrentIndex(com3Index);
} else {
logMessage("未检测到USB串行(COM3),等待设备插入");
}
// 启动串口检查定时器
serialManager->startPortCheckTimer(100);
}
Widget::~Widget()
{
delete ui;
delete serialManager;
delete hidManager;
delete dataProcessor;
}
void Widget::initUI()
{
setWindowTitle("串口+HID");
setWindowIcon(QIcon(":/images/HID.ico"));
// 初始化输入区事件过滤器
ui->te_instruct->installEventFilter(this);
// 初始化接收区样式
ui->recv_textEdit->setLineWrapMode(QTextEdit::WidgetWidth);
ui->recv_textEdit->setFont(QFont("Consolas", 10));
ui->recv_textEdit->setStyleSheet("background-color: #f0f0f0; color: #333;");
ui->recv_textEdit->setAcceptRichText(true);
}
void Widget::initConnections()
{
// 串口信号连接
connect(serialManager, &SerialPortManager::portListChanged,
this, &Widget::onSerialPortListChanged);
connect(serialManager, &SerialPortManager::dataReceived,
this, &Widget::onSerialDataReceived);
connect(serialManager, &SerialPortManager::connectionStateChanged,
this, &Widget::onSerialConnectionStateChanged);
connect(serialManager, &SerialPortManager::errorOccurred,
this, &Widget::onSerialErrorOccurred);
// HID信号连接
connect(hidManager, &HidManager::deviceListChanged,
this, &Widget::onHidDeviceListChanged);
connect(hidManager, &HidManager::dataReceived,
this, &Widget::onHidDataReceived);
connect(hidManager, &HidManager::connectionStateChanged,
this, &Widget::onHidConnectionStateChanged);
connect(hidManager, &HidManager::errorOccurred,
this, &Widget::onHidErrorOccurred);
// 连接HID设备断开信号
connect(hidManager, &HidManager::deviceDisconnected,
this, &Widget::onHidDeviceDisconnected);
// // 可选:处理专门的断开通知
// connect(hidManager, &HidManager::deviceDisconnected, this, [this](const QString &errorMsg) {
// QMessageBox::warning(this, "设备断开", "HID设备已断开连接:" + errorMsg);
// });
}
// 修改Widget::getAvailableDevices函数,添加异常捕获
void Widget::getAvailableDevices()
{
try {
QString prevSelectedText = ui->number_cm->currentText();
ui->number_cm->clear();
deviceTypes.clear();
// 添加所有串口设备
QStringList serialPorts = serialManager->getAvailablePorts();
foreach (const QString &portName, serialPorts) {
QSerialPortInfo portInfo(portName);
QStringList displayParts;
displayParts << portName;
if (!portInfo.description().isEmpty()) displayParts << portInfo.description();
if (!portInfo.manufacturer().isEmpty()) displayParts << portInfo.manufacturer();
QString displayText = "[串口] " + displayParts.join(" - ");
ui->number_cm->addItem(displayText);
deviceTypes.append(DeviceType::DEVICE_SERIAL);
}
// 添加所有HID设备
QStringList hidDevices = hidManager->enumerateHidDevices();
foreach (const QString &deviceInfo, hidDevices) {
// 检查设备信息有效性
if (!deviceInfo.isEmpty()) {
ui->number_cm->addItem(deviceInfo);
deviceTypes.append(DeviceType::DEVICE_HID);
}
}
// 恢复之前的选中状态
int prevIndex = ui->number_cm->findText(prevSelectedText);
if (prevIndex != -1) {
ui->number_cm->setCurrentIndex(prevIndex);
}
} catch (...) {
logMessage("获取设备列表时发生未知错误");
}
}
QString Widget::getSelectedPortName()
{
if (ui->number_cm->currentIndex() == -1) return "";
QString selectedText = ui->number_cm->currentText();
if (selectedText.startsWith("[串口] ")) {
QString portPart = selectedText.mid(5);
int separatorPos = portPart.indexOf(" - ");
return separatorPos != -1 ? portPart.left(separatorPos) : portPart;
}
return "";
}
// 修改updateConnectButtonState方法
void Widget::updateConnectButtonState()
{
if (ui->number_cm->count() == 0) {
ui->break_pb->setText("无可用设备");
ui->break_pb->setEnabled(false);
return;
}
DeviceType currentType = getSelectedDeviceType();
bool isConnected = (currentType == DeviceType::DEVICE_SERIAL) ? isSerialConnected : isHidConnected;
// 统一按钮文本:未连接时显示"连接",连接后显示"断开"
ui->break_pb->setText(isConnected ? "断开" : "连接");
ui->break_pb->setEnabled(true);
}
Widget::DeviceType Widget::getSelectedDeviceType()
{
int index = ui->number_cm->currentIndex();
if (index >= 0 && index < deviceTypes.size()) {
return deviceTypes[index];
}
return DeviceType::DEVICE_SERIAL;
}
void Widget::logMessage(const QString &message)
{
if (ui->recv_textEdit) {
QString timeStamp = QTime::currentTime().toString("HH:mm:ss");
ui->recv_textEdit->append(QString("[%1] [日志] %2").arg(timeStamp, message));
ui->recv_textEdit->moveCursor(QTextCursor::End);
}
qDebug() << message;
}
void Widget::sendDataToDevice(const QString &data)
{
DeviceType currentType = getSelectedDeviceType();
if (currentType == DeviceType::DEVICE_SERIAL) {
if (!isSerialConnected) {
logMessage("串口发送失败:未连接串口");
QMessageBox::warning(this, "发送失败", "请先连接串口再发送数据");
return;
}
QString sendData = data.trimmed();
if (sendData.isEmpty()) {
logMessage("发送失败:内容为空");
return;
}
if (commandHistory.isEmpty() || commandHistory.last() != sendData) {
commandHistory.append(sendData);
if (commandHistory.size() > 100) commandHistory.removeFirst();
}
historyIndex = -1;
serialManager->sendData(data);
ui->recv_textEdit->append(QString(" 串口发送: %1").arg(data));
ui->recv_textEdit->moveCursor(QTextCursor::End);
} else {
hidManager->sendData(data);
QString timeStamp = QTime::currentTime().toString("HH:mm:ss.zzz");
QString html = "<div style='margin:8px 0;'>";
html += "<hr style='border:1px dashed #CCCCCC; margin:5px 0;'>";
html += QString("<p style='margin:5px 0;'><span style='color:#0000CD;'>[%1] HID发送: %2</span></p>")
.arg(timeStamp, data);
html += "<div style='margin:5px 0;'></div>";
html += "</div>";
QTextCursor cursor(ui->recv_textEdit->textCursor());
cursor.movePosition(QTextCursor::End);
cursor.insertHtml(html);
ui->recv_textEdit->moveCursor(QTextCursor::End);
}
}
void Widget::handleHistoryNavigation(int direction)
{
if (commandHistory.isEmpty()) return;
if (historyIndex == -1) {
currentDraft = ui->te_instruct->toPlainText();
}
if (direction == 1) { // 上方向键
if (historyIndex + 1 < commandHistory.size()) {
historyIndex++;
}
} else { // 下方向键
if (historyIndex > 0) {
historyIndex--;
} else if (historyIndex == 0) {
historyIndex = -1;
ui->te_instruct->setPlainText(currentDraft);
return;
}
}
if (historyIndex != -1) {
int displayIndex = commandHistory.size() - 1 - historyIndex;
ui->te_instruct->setPlainText(commandHistory[displayIndex]);
}
QTextCursor cursor = ui->te_instruct->textCursor();
cursor.movePosition(QTextCursor::End);
ui->te_instruct->setTextCursor(cursor);
}
bool Widget::eventFilter(QObject *watched, QEvent *event)
{
if (watched == ui->te_instruct && event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
if (keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter) {
QString inputText = ui->te_instruct->toPlainText();
sendDataToDevice(inputText);
ui->te_instruct->clear();
return true;
}
else if (keyEvent->key() == Qt::Key_Up) {
handleHistoryNavigation(1);
return true;
}
else if (keyEvent->key() == Qt::Key_Down) {
handleHistoryNavigation(-1);
return true;
}
}
return QWidget::eventFilter(watched, event);
}
void Widget::on_refresh_pb_clicked()
{
if (isSerialConnected) {
QString closedPort = getSelectedPortName();
serialManager->disconnectPort();
logMessage(QString("刷新操作:已断开串口 %1 的连接").arg(closedPort));
}
if (isHidConnected) {
hidManager->disconnectHidDevice();
}
getAvailableDevices();
updateConnectButtonState();
}
void Widget::on_break_pb_clicked()
{
if (ui->number_cm->count() == 0) {
QMessageBox::warning(this, "操作提示", "当前无可用设备,请检查设备连接!");
return;
}
DeviceType currentType = getSelectedDeviceType();
bool isCurrentlyConnected = (currentType == DeviceType::DEVICE_SERIAL)
? isSerialConnected
: isHidConnected;
if (currentType == DeviceType::DEVICE_SERIAL) {
if (isCurrentlyConnected) {
QString closedPort = getSelectedPortName();
serialManager->disconnectPort();
} else {
QString targetPort = getSelectedPortName();
if (!targetPort.isEmpty()) {
serialManager->connectPort(targetPort, 460800);
} else {
logMessage("连接失败:未选中任何串口");
QMessageBox::warning(this, "连接失败", "请先从下拉列表中选中一个串口!");
}
}
} else {
if (isCurrentlyConnected) {
hidManager->disconnectHidDevice();
} else {
QString currentText = ui->number_cm->currentText();
hidManager->connectHidDevice(currentText);
}
}
ansiBuffer.clear();
hidAnsiBuffer.clear();
bool isFinalConnected = (currentType == DeviceType::DEVICE_SERIAL)
? isSerialConnected
: isHidConnected;
ui->number_cm->setEnabled(!isFinalConnected);
ui->number_cm->setStyleSheet(isFinalConnected
? "color: #888; background-color: #f5f5f5;"
: "");
updateConnectButtonState();
}
void Widget::on_pushButton_clicked()
{
ui->recv_textEdit->clear();
logMessage("接收区已清空");
}
void Widget::onSerialPortListChanged(const QStringList &portList)
{
Q_UNUSED(portList);
getAvailableDevices();
updateConnectButtonState();
}
void Widget::onSerialDataReceived(const QByteArray &data)
{
QString rawText = QString::fromUtf8(data);
QString processableText = dataProcessor->processIncompleteData(rawText, ansiBuffer);
if (processableText.isEmpty()) {
return;
}
QString richText = dataProcessor->parseAnsiToRichText(processableText);
ui->recv_textEdit->setAcceptRichText(true);
QString html = QString("<p><br></p>%1<hr>").arg(richText);
QTextCursor cursor(ui->recv_textEdit->textCursor());
cursor.movePosition(QTextCursor::End);
cursor.insertHtml(html);
ui->recv_textEdit->moveCursor(QTextCursor::End);
}
void Widget::onSerialConnectionStateChanged(bool isConnected)
{
isSerialConnected = isConnected;
updateConnectButtonState();
// 更新ComboBox状态
ui->number_cm->setEnabled(!isConnected);
ui->number_cm->setStyleSheet(isConnected
? "color: #888; background-color: #f5f5f5;"
: "");
if (isConnected) {
logMessage(QString("成功连接串口 %1(波特率:460800)").arg(getSelectedPortName()));
} else {
logMessage(QString("已断开串口 %1 的连接").arg(getSelectedPortName()));
// 刷新设备列表,确保显示最新状态
getAvailableDevices();
}
}
void Widget::onSerialErrorOccurred(const QString &errorMsg)
{
logMessage(QString("串口错误:%1").arg(errorMsg));
}
// 修改Widget::onHidDeviceListChanged函数
void Widget::onHidDeviceListChanged(const QStringList &deviceList)
{
qDebug() << "onHidDeviceListChanged called"; // 添加调试日志
// 确保在主线程处理UI更新
if (QThread::currentThread() != this->thread()) {
QMetaObject::invokeMethod(this, "onHidDeviceListChanged",
Qt::QueuedConnection,
Q_ARG(QStringList, deviceList));
return;
}
// 保存当前选中项
QString currentSelection = ui->number_cm->currentText();
bool wasConnected = isHidConnected;
// 重新加载设备列表
getAvailableDevices();
// 尝试恢复之前的选择
int index = ui->number_cm->findText(currentSelection);
if (index >= 0) {
ui->number_cm->setCurrentIndex(index);
}
updateConnectButtonState();
}
void Widget::onHidDataReceived(const QByteArray &data, int bytesRead)
{
qDebug() << "原始HID数据 [" << bytesRead << "字节]: " << data.toHex();
QString timeStamp = QTime::currentTime().toString("HH:mm:ss.zzz");
QString html = "<div style='margin:8px 0; color: white; background-color: #333;'>";
html += QString("<p style='margin:5px 0;'><span style='color:#228B22;'>[%1] HID接收(%2字节): %3</span></p>")
.arg(timeStamp)
.arg(bytesRead)
.arg(dataProcessor->byteArrayToHexString(data));
html += "</div>";
QTextCursor cursor(ui->recv_textEdit->textCursor());
cursor.movePosition(QTextCursor::End);
cursor.insertHtml(html);
ui->recv_textEdit->moveCursor(QTextCursor::End);
}
// 修改HID连接状态变化处理函数
void Widget::onHidConnectionStateChanged(bool connected)
{
isHidConnected = connected; // 更新连接状态
updateConnectButtonState(); // 调用统一的按钮状态更新方法
if (connected) {
logMessage("HID设备连接成功");
} else {
logMessage("HID设备已断开连接");
}
}
void Widget::onHidErrorOccurred(const QString &errorMsg)
{
logMessage(errorMsg);
}
void Widget::onHidDeviceDisconnected(const QString &errorMsg)
{
logMessage("HID设备已断开: " + errorMsg);
// 更新连接状态
isHidConnected = false;
// 刷新设备列表
getAvailableDevices();
// 更新按钮状态
updateConnectButtonState();
// 重新启用ComboBox
ui->number_cm->setEnabled(true);
ui->number_cm->setStyleSheet("");
}
上面是我程序代码qt有很多问题请你修复下
最新发布