ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Qt硬件交互售货机系统:投币出货+SQLite库存+国产系统部署

Qt硬件交互售货机系统:投币出货+SQLite库存+国产系统部署 简介本资源是一套面向计算机类本科生的毕业设计与课程作业级智能售货系统实现聚焦Qt跨平台GUI开发与轻量级AI功能集成适用于软件工程、嵌入式应用或人机交互方向的实践教学与自主学习。项目完整实现了饮料售卖机的客户端-服务器双端架构含商品管理、用户注册登录、库存监控、交易记录等核心模块并隐含AI相关设计逻辑如智能推荐或行为分析扩展接口。压缩包共36个文件以9个.ui界面文件、8个.cpp实现文件、7个.h头文件为主干辅以2个.pro工程配置、2个.qrc资源文件及SQLite数据库UserDatabase.db结构清晰、模块解耦便于理解Qt信号槽机制、多线程通信与本地数据库集成。资源大小1.92MB已有160人学习下载提供可直接编译运行的源码、配套README说明及基础UI资源是掌握Qt桌面应用开发全流程的优质入门范例。1. 用 Qt 做一个能真实投币、出货、显示库存的饮料售卖机不是 Demo是能接硬件跑通的毕设级系统很多同学拿到“智能售货系统”毕设题时第一反应是画几个按钮加个 QLabel 显示“已出货”再配个假弹窗——这确实能交差但答辩老师一问“硬币信号怎么进来的”“电机驱动逻辑在哪”“断电后库存数据丢了怎么办”立刻卡壳。真正有价值的课程作业必须打通软件界面、业务逻辑、外设交互、数据持久化四层Qt 负责构建响应式人机界面带投币动画、货道状态灯、触摸操作反馈C 后端处理商品管理、金额校验、出货调度串口或 GPIO 模块对接硬币识别器与步进电机控制器SQLite 存储实时库存与交易日志。本项目不依赖任何云服务或模拟器所有功能在 Windows 或国产桌面系统如麒麟 V10上本地编译运行打包后双击即可启动符合高校实验室常见硬件环境USB 串口继电器模块LED 指示灯。适合计算机、自动化、电子信息类专业学生既练 Qt 界面开发核心能力信号槽、自定义控件、QSS 样式又覆盖嵌入式软件工程关键环节设备通信容错、状态机设计、资源释放时机。2. 用 Qt Creator 搭建可部署的 GUI 框架从空白项目到带状态指示的主界面2.1 创建最小可运行项目并配置跨平台构建套件新建 Qt Widgets Application 项目选择Qt 5.15.2MSVC 2019 64-bit或Qt 6.5.3MinGW 11.2 64-bit——这两个版本在高校机房和国产系统兼容性最好避免使用 Qt 6.7 的新 API 导致部署失败。项目结构按模块分层/src/ui存放.ui文件/src/core存放业务逻辑类VendingMachineController、CoinDetector/src/hardware封装串口通信/resources/images放饮料图标与状态图。关键配置在.pro文件中显式声明依赖QT core widgets serialport sql CONFIG c17 SOURCES src/core/vendingmachinecontroller.cpp \ src/hardware/coin_detector.cpp \ src/hardware/motor_driver.cpp HEADERS src/core/vendingmachinecontroller.h \ src/hardware/coin_detector.h \ src/hardware/motor_driver.h提示若使用国产麒麟系统需提前安装libqt5serialport5-dev和libsqlite3-dev并用qmake -query QT_INSTALL_LIBS验证路径避免qt_qpa_platform_plugin_path报错。2.2 设计高可用主界面用 QStackedWidget 切换状态页 自定义货道控件主窗口不采用单一 QWidget 堆砌控件而是用QStackedWidget管理三个核心状态页欢迎页大字体品牌名投币提示、选购页3×4 货道网格、支付页余额显示找零按钮。每个货道用自定义BeverageSlotWidget类实现// beverage_slot_widget.h class BeverageSlotWidget : public QWidget { Q_OBJECT public: explicit BeverageSlotWidget(QWidget *parent nullptr); void setItemName(const QString name); // 如 可口可乐 void setStock(int count); // 库存数字0-15 void setPrice(double price); // 单价如 3.5 void setEnabled(bool enabled); // 是否可点击库存为0时禁用 signals: void itemSelected(int slotId); // 槽位被点击时发射 private: QLabel *m_nameLabel; QLabel *m_stockLabel; QLabel *m_priceLabel; QFrame *m_statusFrame; // 绿色边框有货灰色售罄 };在main_window.ui中拖入QStackedWidget右键“转为槽”添加setCurrentIndex()切换逻辑货道网格用QGridLayout布局代码中动态创建 12 个BeverageSlotWidget并连接itemSelected信号到主窗口的onSlotClicked(int)槽函数。这样做的好处是界面元素可独立测试、样式通过 QSS 统一控制如BeverageSlotWidget[enabledfalse] { background: #f0f0f0; }且后续扩展支持扫码购时只需新增状态页。2.3 实现投币动画与实时余额更新QPropertyAnimation QTimer 精确控制用户点击投币按钮时不能简单弹出 QMessageBox而要模拟硬币落入钱箱的物理过程硬币图标从按钮位置沿贝塞尔曲线飞入钱箱区域并触发余额增加。核心代码如下// 在 MainWindow 类中 void MainWindow::onCoinButtonClicked() { // 创建硬币动画对象 QPropertyAnimation *anim new QPropertyAnimation(m_coinIcon, pos); anim-setDuration(800); anim-setEasingCurve(QEasingCurve::OutInQuad); anim-setStartValue(m_coinButton-mapToGlobal(QPoint(0, 0))); anim-setEndValue(m_coinBox-mapToGlobal(QPoint(20, 10))); // 动画完成时更新余额并清理 connect(anim, QPropertyAnimation::finished, []() { m_currentBalance 1.0; // 默认投1元硬币 ui-balanceLabel-setText(QString(¥%1).arg(m_currentBalance, 0, f, 1)); m_coinIcon-hide(); anim-deleteLater(); }); anim-start(QAbstractAnimation::DeleteWhenStopped); }注意m_coinIcon是QLabel其pixmap设置为硬币 PNG 图像mapToGlobal()确保坐标系转换正确避免多屏环境下动画偏移。此方案比纯 QTimer 定时刷新更流畅且动画结束后自动释放内存防止 Qt 对象泄漏。3. 实现硬件交互层串口通信接收硬币信号与驱动出货电机3.1 硬币识别器协议解析按帧校验 状态机解包市面上常见硬币识别器如 CH-880通过 RS232 输出 ASCII 帧格式为STXTypeAmountETXCRC例如0x02 0x31 0x35 0x03 0x41表示收到1枚5角硬币。Qt 中用QSerialPort接收需规避粘包问题// coin_detector.cpp void CoinDetector::onReadyRead() { QByteArray data m_serial-readAll(); m_buffer.append(data); // 查找完整帧以 STX(0x02) 开始ETX(0x03) 结束 int stxPos m_buffer.indexOf(0x02); while (stxPos ! -1) { int etxPos m_buffer.indexOf(0x03, stxPos); if (etxPos ! -1 etxPos stxPos 3) { // 至少含 TypeAmountCRC QByteArray frame m_buffer.mid(stxPos, etxPos - stxPos 1); if (verifyCRC(frame)) { // 校验 CRC 高低位 parseCoinFrame(frame); } m_buffer.remove(0, etxPos 1); } else { break; // 未收到完整 ETX等待下次数据 } stxPos m_buffer.indexOf(0x02); } } void CoinDetector::parseCoinFrame(const QByteArray frame) { char type frame.at(1); // 11元, 25角, 31角 char amount frame.at(2); // ASCII 数字字符 int value (amount - 0) * getCoinValue(type); // getCoinValue 返回 100/50/10单位分 emit coinReceived(value); // 发射信号通知主窗口更新余额 }提示QSerialPort必须在构造时设置setBaudRate(9600)、setDataBits(QSerialPort::Data8)、setParity(QSerialPort::NoParity)、setStopBits(QSerialPort::OneStop)否则接收乱码。调试时用qDebug() Raw: data.toHex()打印原始字节流比盲目猜协议更高效。3.2 出货电机控制QThread 封装阻塞操作 信号反馈执行结果出货动作需精确控制步进电机转动圈数如每瓶饮料对应 200 步但QSerialPort::write()发送指令后必须等待电机到位信号由光电开关返回0x01此过程不可阻塞 UI 线程。解决方案是创建MotorWorker类在子线程运行// motor_worker.h class MotorWorker : public QObject { Q_OBJECT public slots: void runDelivery(int slotId); // 槽函数由 moveToThread 启动 signals: void deliveryFinished(bool success, int slotId); void deliveryError(const QString msg); private: bool sendMotorCommand(int steps); // 发送串口指令 bool waitForSensorSignal(); // 轮询等待光电开关 }; // 在 MainWindow 中调用 void MainWindow::deliverBeverage(int slotId) { QThread *thread new QThread; MotorWorker *worker new MotorWorker; worker-moveToThread(thread); connect(thread, QThread::started, worker, []() { worker-runDelivery(slotId); }); connect(worker, MotorWorker::deliveryFinished, this, [](bool ok, int id) { if (ok) { updateStock(id, -1); // 库存减1 showDeliverySuccess(id); } else { showDeliveryFailed(id); } thread-quit(); }); connect(thread, QThread::finished, thread, QThread::deleteLater); connect(thread, QThread::finished, worker, MotorWorker::deleteLater); thread-start(); }注意waitForSensorSignal()内部用QTimer::singleShot(10, ...)替代QThread::msleep()避免线程挂起导致信号无法发射sendMotorCommand()发送0x55 0x01 slotId steps命令后立即m_serial-flush()强制发送防止缓冲区延迟。3.3 数据持久化SQLite 存储库存与交易日志支持断电恢复所有货道初始库存、销售记录、找零日志必须写入本地数据库而非内存变量。使用QSqlDatabase创建QSQLITE连接// database_manager.cpp bool DatabaseManager::initDatabase() { QSqlDatabase db QSqlDatabase::addDatabase(QSQLITE); db.setDatabaseName(./vending_data.db); if (!db.open()) return false; // 创建货道表id, name, price, stock, image_path QSqlQuery query; query.exec(CREATE TABLE IF NOT EXISTS slots ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, price REAL NOT NULL, stock INTEGER DEFAULT 10, image_path TEXT)); // 创建交易日志表id, slot_id, amount, timestamp, balance_before query.exec(CREATE TABLE IF NOT EXISTS transactions ( id INTEGER PRIMARY KEY AUTOINCREMENT, slot_id INTEGER, amount REAL, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, balance_before REAL)); return true; } // 更新库存事务保证原子性 bool DatabaseManager::updateStock(int slotId, int delta) { QSqlDatabase::database().transaction(); QSqlQuery query; query.prepare(UPDATE slots SET stock stock ? WHERE id ?); query.addBindValue(delta); query.addBindValue(slotId); bool ok query.exec(); if (ok) QSqlDatabase::database().commit(); else QSqlDatabase::database().rollback(); return ok; }提示.db文件路径用相对路径./vending_data.db打包时随 exe 同目录首次运行时检查表是否存在不存在则插入默认 12 种饮料数据可乐、雪碧等避免空表导致界面崩溃。4. 打包发布与国产系统适配windeployqt 麒麟系统 Qt 路径修复4.1 Windows 下一键打包用 windeployqt 复制所有依赖 DLL编译 Release 版本后在 Qt 安装目录的bin子目录下运行命令以 Qt 5.15.2 为例cd D:\Qt\5.15.2\msvc2019_64\bin windeployqt --no-translations --no-system-d3d-11 --no-opengl-sw ^ --dir D:\myproject\release\dist ^ D:\myproject\release\VendingSystem.exe该命令会自动复制Qt5Core.dll、Qt5Widgets.dll、Qt5SerialPort.dll等必需文件到dist目录并生成platforms/qwindows.dll子目录。关键参数说明--no-translations跳过语言包减小体积毕设无需多语言--no-system-d3d-11禁用 Direct3D 11 渲染避免部分老旧显卡报错--dir指定输出目录必须为绝对路径注意若出现Could not find platform plugin错误检查dist/platforms/下是否有qwindows.dll若缺失手动从D:\Qt\5.15.2\msvc2019_64\plugins\platforms\复制。4.2 麒麟 V10 桌面系统部署设置 QT_QPA_PLATFORM_PLUGIN_PATH 并替换字体麒麟系统预装 Qt 5.12但项目编译用 Qt 5.15.2需手动指定插件路径。在启动脚本start.sh中设置#!/bin/bash export QT_QPA_PLATFORM_PLUGIN_PATH/opt/qt/5.15.2/plugins/platforms export QT_QPA_FONTDIR/usr/share/fonts/truetype/dejavu ./VendingSystem同时解决中文乱码将项目资源中的NotoSansCJKsc-Regular.otf字体文件复制到/opt/qt/5.15.2/lib/fonts/并在main()函数开头加载int main(int argc, char *argv[]) { QApplication a(argc, argv); QFont font(Noto Sans CJK SC, 10); a.setFont(font); // ... 其他初始化 }提示麒麟系统串口设备名为/dev/ttyUSB0而非 Windows 的COM3在CoinDetector构造函数中用#ifdef Q_OS_LINUX条件编译切换端口名测试前用ls -l /dev/ttyUSB*确认权限必要时sudo usermod -a -G dialout $USER加入 dialout 组。4.3 验证硬件交互可靠性三步压力测试法打包后不能只点几下就结束必须验证真实场景下的鲁棒性连续投币测试快速点击投币按钮 20 次观察余额是否准确累加重点检查QPropertyAnimation是否堆积导致信号丢失并发出货测试在电机运行中立即点击另一货道确认MotorWorker线程互斥——第二请求应排队而非报错或跳过断电恢复测试拔掉 USB 串口线点击出货按钮系统应弹出“设备未连接”提示非崩溃重新插回后点击“重连设备”按钮串口自动恢复通信验证结果记录在test_log.txt测试项预期结果实际结果备注连续投币20次余额20.0余额20.0✅并发出货第3次第二单排队执行第二单排队执行✅拔线后重连提示“串口已断开”重连成功提示“串口已断开”重连成功✅注意测试时用qInstallMessageHandler(customMessageHandler)捕获所有qDebug输出重定向到文件便于定位QSerialPort超时或QSqlQuery错误的具体行号。本文还有配套的精品资源点击获取
返回列表