基于Linux C++多线程服务器 + Qt上位机开发 + STM32 + 8266WIFI的智慧无人超市_使用qt开发的上位机如何通过阿里云作为中介来控制异地的带wifi模块的单片机

前言

针对传统超市购物车结账排队时间长、付款效率低的问题,提出了一种更符合现代社会人们购物方式-基于RFID的自助收银系统。习惯了快节奏生活的人们都会选择自助收银机结账,理由显而易见:自助收银机结账很方便,几乎不用排队,也不用近距离和收银员接触,在防疫时期特别感觉安心。而且自助结账对每件物品的售价更是一次清晰地核对,最终需支付合计购物支出自己也更加清晰明了;这两年来,越来越多的智能设备应用在我们的生活领域里,为我们的生活提供了很多智能和便利。自助收银机从几年前就陆续涌入到各地商场、超市、便利店,自去年疫情发生后自助收银的需求比例更是呈直线上升趋势。自助收银机的启用,不仅节约了超市的人力开支成本,也从根本上提升了超市的购物支付效率,在这个快节奏的社会里,智能自助收银机也从根本上提升了超市等

基于Linux C++多线程服务器 + Qt上位机开发 + STM32 + 8266WIFI的智慧无人超市项目

技术栈+硬件选型

Linux c++应用编程;

Linux socket编程,多线程编程,实时信号(线程通信)

Qt/C++ 客户端开发;

qml(QtLocation)与c++交互 安卓开发;

Mysql 数据存储;

C语言下位机开发;

stm32c8t6 下位机(便宜,够用,市面价格10r);

RC522(RFID模块 SPI协议) 与白卡通信 获取卡号;

DTH11 温湿度采集模块(单总线协议,市面价格 4r);

sg90 舵机模块(PWM协议 市面价9r );

8226 01-s WIFI模块(uart协议 市面价格5r) 连接 C++ 服务器;

蜂鸣器  是为了有白卡与RFID交互声音

GY-NEO6MV2 GPS模块(uart协议 市面价18)

总设计流程

本次设计的->基于RFID的自助收银系统->设计主要支持的功能和程序如下:

本项目一共有五个程序,Linux C++服务器,Qt管理员端,Qt客户结算端,qml安卓端,单片机下位机端,客户端程序都有服务器断开自动重连(单片机没有,有佬会的话,我想请教一下)

Linux C++服务器:

基于 socket 接口编写server服务端程序,监听8888端口,然后创建Mysql数据库连接,开始监听。

面向对象程序设计中最重要的一个概念是继承,所以我编写了一个基类mythread,他有一个纯虚函数,参数为一个定义的一个参数类,包含了数据库封装好的对象,需要服务的客户端套接字,还有连接的客户端网络的信息结构体,当socket客户端成功接入时,取出该客户端套字和网络信息结构体和主函数的数据库对象来填充参数类,然后服务器端根据客户端发过来的第一个消息判断该客户端是上述哪个客户端,然后创建相应的派生类,填入该参数类,然后让mythread指针去指向这个子类对象,发生动多态,此时运行阶段时才确定函数的入口地址,执行派生类中的继承父类的已经实现的纯虚函数,此时派生类创建一个线程绑定线程处理函数去服务处理该socket套接字传过来的消息。绑定不同的线程处理函数服务不同的客户端程序。一共有四种线程处理函数服务上述四种客户端程序,由四个不同的基类去绑定。

本项目的一个最大的特点,难点,单片机只负责发送卡号给服务器,本项目单片机传过来的卡号有以下走向,注册商品,注册会员,结账,商品入购物车,那么我该怎么知道该卡号是用来做什么呢,当时困扰了几天,然后想到Qt的信号与槽机制,联想到Linux 也有实时信号,还可携带参数,该信号是事件发生时对进程的通知机制,也可以把它称为软件中断,Linux 内核定义了 31 个不同的实时信号,信号编号范围为 34~64,我直接拿来做线程通信,所以现在怎样把他们联系起来呢,当时我想到互斥啥的想法感觉不好实现,然后我就想到了一个方法,就是设置标志位,我设立了一个全局变量 int RES=34,让RES的默认值为34,34很熟悉吧,信号编号范围为 34~64,当单片机收到卡号发给服务器时,服务器直接调用信号发送函数,此时全局变量标志位为34,所以直接发送34这个实时信号,然后触发中断执行这个信号绑定处理函数。我们一共有四个地方需要用到卡号,资源只有一个,所以当需要执行某个用到卡号的操作时,我先判断该RES的值,如果该值等于34,代表该读卡器为空闲,我就更改RES为 35 ,然后下次单片机发过来卡号,我还是直接调用发送信号,此时标志位为35 所以此时执行 35 的信号处理函数,执行完函数,需要将RES 置为默认值 34,释放资源,如果当请求资源时RES不等于 34 代表 读卡器正在被占用,此时回复客户端一个读卡器正忙,以此类推,绑定四个信号处理函数,处理这四个操作请求,一定要释放改为34。我们添加商品到购物车,如果一直点击按钮获取来获取资源就会显得很笨拙,所以默认的 34 信号处理函数为添加商品入购物车,在没有人改变标志位的情况下,就是执行商品入购物车。

本项目,因为多个线程对数据进行增删改查,存在竞争冒险,所以在执行数据库增删改操作时,我加入条 Mysql 事务操作语句,事务是一个原子操作,执行增删改操作前 开始事务,执行结束,提交事务。

总结 : 单片机的所有数据全部转发给服务器,服务器跟据消息的种类,标志位,进行处理后,分发给指定客户端,完成一系列操作。

主循环如下:

 while (1)
   {
      myret=server.client_socket();
      myret.my_sql=sql_typ;

      std::cout<<"new connect !!"<<endl;
      std::string str=server.readbuf();

      int num; 
      istringstream a(str);
      a >> num;

    
     switch (num)
  {
     {
     case 100001:   
    
         Mythread *android_thread=new androidthread; 
         android_thread->thread_start(myret);
         std::cout<<"安卓客户端连接成功"<<std::endl; 
     }
         break;
     case 100101:
     {
          Mythread  *admin_thread=new adminthread;   
          admin_thread->thread_start(myret);
          std::cout<<"PC客户端连接成功"<<std::endl;
     }
         break;
     case 100111:
     {
          Mythread  *cust_thread=new custthread;    
          cust_thread->thread_start(myret);
          std::cout<<"ARM客户端连接成功"<<std::endl;
     } 
         break;
     case 101001:
     {

          Mythread   *mcu_thread=new mcuthread;    
          mcu_thread->thread_start(myret);
          std::cout<<"STM32客户端连接成功"<<std::endl;
     }  
         break;
     default:
         std::cout<<"未知的错误"<<std::endl;   
         break;
     }

        
   }
   

Qt管理员端:

Qt管理员端具有的功能,注册商品,注册会员,充值,查看销售记录,日志。

首先连接服务器成功,自动发指令给服务器,服务器从数据库取出数据发给客户端,初始化,商品,会员,服务器上有一个文本文件记录销售记录,我给服务器文本大小做了一个限制,如果大小大于1M清空文件,清除销售记录,客户端可以通过点击按钮发送一个指令,获取文本内容显示销售记录,日志的话就是从服务器运行阶段开始,对会员充值,会员销毁,商品添加,商品删除,都会直接记录,服务器退出自动销毁。

Qt客户结算端:

Qt客户结算端具有的功能:添加商品入购物车,结算,显示从服务器端获取的温湿度数据。

商品入库取出价格,然后相加,点击结算按钮将总价格发给服务器,然后服务器判断标志位,如果读卡器被占用则取消结账,反之。此时标志位改变,下次刷的卡将充当会员卡进行数据比对,余额不足则返回数据给客户端,反之。执行完毕释放资源置为34。

qml安卓端:

qml安卓端具有的功能:地图显示当前手机与MCU的位置和距离,在售商品查询,购买记录查询,姓名号码登录。

这个安卓端其实有点画蛇添足的意思,我就是想炫耀一下我的GPS模块,然后地图显示当前手机GPS数据与MCU的GPS模块的距离和位置,功能太单调了,我就加了一个在售商品查看功能,然后给Mysql添加了1000大小的varcahr字段,存储当前用户购买记录,加了一个登录界面。

qml的教程挺少,之前学过一遍,没有及时巩固,当时写这个qml真的炸裂,很多坑。想入门qml也简单,学一下qml与C++交互,信号与槽,函数互调。qml界面的话让gpt去写,百度CV。

STM32单片机开发:

stm32具体的功能:stm32c8t6主控芯片,DTH11温湿度采集发送给服务器客户端显示,sg90舵机模拟开门,GY-NEO6MV2 GPS 获取GPS,8226 01-s 与tcp 服务器数据交互,RC522与白卡交互,蜂鸣器提示刷卡成功。

32这调试是最恶心的,一般调试是直接通过串口打印到电脑,但是串口用来初始化8266了,问题就是这个8266,当时连接服务器一直连接不上,我就去找原因,有供电原因,还有at指令的原因,供电最好直接从32vcc上引出来,因为我是根据客户端程序连接成功后根据发过来的第一条数据来创建对应线程服务,不知道为啥这个32程序按复位键的话8266没有从第一条指令开始运行,然后就创建不了对应线程服务,只能断电,然后在重新烧录一次,调试巨麻烦。

GPS的话也是串口通信,重新初始化一个串口资源就好了,这个信号很差必须在阳台上,有条件的还是买好一点的吧我采集数据还有抱着一大堆线接个充电宝在阳台调试。gps数据有个NMEA协议,需要对数据解析出经纬度,有很多类型数据,最重要的一条就是包含经纬度的

“$GPGLL,2547.35222,N,11306.12283,E,111129.00,A,A*6D”;

这是当时当时在阳台调试出来的,这里我偷了个小懒,因为c语言字符串处理很鸡肋,所以我直接在32这里过滤出这条消息,一整条发给服务器,然后服务器发给Qt客户端,让Qt的QString去解分割分析处理,分分钟的事情,封装好的库就是简单

f4eff0cf6b7a4b8fa8a8ca56f53c46cc.png

很简单吧!

8266的话我初始化很随意,快准狠,直接配置tcp,连接WiFi然连接服务器,哈哈。

e5ae932d216c4862aed5ce3270a5ab51.png

温湿度舵机什么的没什么好讲,一个单总线写按时序拉低拉高电平就行了,一个设置指定占空比。RC522的话,驱动很复杂,我水平不太行,写不出来,直接调厂家的库了。

主循环就这样了

3af14c79a4c84857a7c467f3d64038cb.png

最后留言:

本项目一共写了半个月,遇到很多坑,当时地图传的坐标经纬度传犯了,调试了一下午才发现了这个问题,还有Linux 实时信号 是一个软件中断嘛,然后当时server的while循环的accept直接导通解除阻塞了,程序直接崩溃。查了很多资料才知道了,最后加一个goto语句哈哈就解决了。

ret_client  Myserver::client_socket()
{
reboot:
     ret_client ret;
  
     m_client_socket= accept(m_socket,(struct sockaddr *)&ret.client_struct,&len);

     if(m_client_socket==-1)
     {
       goto reboot;
     }

    ret.client_socket=m_client_socket;
    return ret;

}

这是sql语句啊,本人挺懒的,全用varchar了。

CREATE DATABASE shopping;

CREATE TABLE users(
id VARCHAR(20) unique key,
name VARCHAR(50),
phone VARCHAR(15) unique key,
balance VARCHAR(25),
text VARCHAR(1000)
)

CREATE TABLE me(
pid VARCHAR(20) unique key,
pname VARCHAR(50),
price VARCHAR(15),
brand VARCHAR(25)
)

最后

从时代发展的角度看,网络安全的知识是学不完的,而且以后要学的会更多,同学们要摆正心态,既然选择入门网络安全,就不能仅仅只是入门程度而已,能力越强机会才越多。

因为入门学习阶段知识点比较杂,所以我讲得比较笼统,大家如果有不懂的地方可以找我咨询,我保证知无不言言无不尽,需要相关资料也可以找我要,我的网盘里一大堆资料都在吃灰呢。

干货主要有:

①1000+CTF历届题库(主流和经典的应该都有了)

②CTF技术文档(最全中文版)

③项目源码(四五十个有趣且经典的练手项目及源码)

④ CTF大赛、web安全、渗透测试方面的视频(适合小白学习)

⑤ 网络安全学习路线图(告别不入流的学习)

⑥ CTF/渗透测试工具镜像文件大全

⑦ 2023密码学/隐身术/PWN技术手册大全

如果你对网络安全入门感兴趣,那么你需要的话可以点击这里👉网络安全重磅福利:入门&进阶全套282G学习资源包免费分享!

扫码领取

<think>我们正在设计一个基于STM32、ESP8266QT的电子秤数据传输与显示系统。根据用户需求,系统需要将电子秤的重量数据通过STM32采集,由ESP8266上传至网络,然后QT客户端上位机接收并显示这些数据。下面将分步骤详细说明实现方案。###1.系统总体架构系统由三部分组成:-**数据采集端**:STM32控制器读取电子秤传感器(通常为称重传感器,如HX711模块)的数据。-**数据传输端**:ESP8266WiFi模块通过串口与STM32通信,将数据发送到服务器(可以是本地服务器或云服务器)。-**数据接收与显示端**:QT客户端上位机作为服务器(或客户端)接收数据,并显示重量信息。```[STM32]--(串口)-->[ESP8266]--(WiFi)-->[服务器]--(网络)-->[QT上位机]```###2.硬件连接-**STM32与称重传感器**:使用HX711模块连接称重传感器(应变片式传感器),HX711通过数字接口(如SCK和DT)与STM32的GPIO连接。STM32读取HX711的数据,经过校准得到重量值。-**STM32与ESP8266**:通过串口(UART)连接,例如STM32的USART1_TX(PA9)接ESP8266的RX,USART1_RX(PA10)接ESP8266的TX。注意电平匹配(通常为3.3V)。###3.STM32端程序STM32需要完成以下任务:-初始化HX711并读取原始AD值。-将AD值转换为重量(需要校准,得到转换系数)。-通过串口将重量数据发送给ESP8266。示例代码片段(伪代码):```c//HX711读取函数longHX711_Read(void){//读取AD转换值//...}//校准参数floatscale_factor=...;//通过标定得到floatoffset=...;//零点偏移//主循环中while(1){longad_value=HX711_Read();floatweight=(ad_value-offset)*scale_factor;//计算重量//通过串口发送重量数据,例如格式为:"weight:123.45kg\n"charbuffer[50];sprintf(buffer,"weight:%.2fkg",weight);USART_SendString(USART1,buffer);delay_ms(100);//适当延时}```###4.ESP8266设置ESP8266作为WiFi模块,工作模式为STA模式(连接到无线路由器)或AP模式(直接与QT上位机通信)。推荐使用STA模式,连接到同一局域网。-**配置ESP8266**:通过AT指令设置WiFi连接和传输模式。-设置WiFi模式:`AT+CWMODE=1`(STA模式)-连接路由器:`AT+CWJAP="SSID","password"`-设置单连接:`AT+CIPMUX=0`-连接到QT上位机所在的服务器(假设QT上位机作为TCP服务器运行):`AT+CIPSTART="TCP","192.168.1.100",8080`(IP和端口根据实际情况修改)-**数据传输**:当STM32通过串口发送数据给ESP8266时,ESP8266将数据通过TCP发送到指定服务器-当收到STM32的数据后,执行:`AT+CIPSEND=<length>`,然后发送数据。**注意**:也可以使用ESP8266的透传模式,简化数据传输。###5.QT上位机设计QT上位机作为TCP服务器(或客户端,根据网络结构决定)接收数据,并显示重量信息。这里我们假设QT上位机作为TCP服务器,监听指定端口(如8080)。####步骤:1.**创建TCP服务器**:使用`QTcpServer`监听连接,当有客户端(ESP8266)连接时,创建`QTcpSocket`进行通信。2.**解析数据**:接收到的数据格式为字符串,例如"weight:123.45kg",需要解析出重量值。3.**显示重量**:使用QLabel或QLineEdit显示实时重量,同时可以添加图表显示历史数据(如QCustomPlot库)。示例代码片段(QTC++):```cpp//创建TCP服务器QTcpServer*server=newQTcpServer(this);connect(server,&QTcpServer::newConnection,this,[=](){QTcpSocket*client=server->nextPendingConnection();connect(client,&QTcpSocket::readyRead,this,[=](){QByteArraydata=client->readAll();//解析数据,例如:以"weight:"开头,以"kg"结尾QStringstr(data);if(str.startsWith("weight:")&&str.endsWith("kg")){//提取数字部分QStringnumStr=str.mid(7,str.length()-9);floatweight=numStr.toFloat();//更新UI显示ui->labelWeight->setText(QString::number(weight)+"kg");}});});server->listen(QHostAddress::Any,8080);```###6.数据传输协议优化为了确保数据传输的可靠性,可以设计简单的协议:-**数据帧格式**:例如,`<STX>数据<ETX>`,其中STX为开始标志(如0x02),ETX为结束标志(如0x03)。-**校验机制**:添加校验和,例如在数据后添加一个字节的校验和(所有字节相加取模)。###7.注意事项-**网络配置**:确保ESP8266QT上位机在同一局域网内,或者通过端口映射实现广域网通信(需注意安全性)。-**实时性**:根据需求调整数据发送频率。-**错误处理**:在STM32QT程序中加入超时重发、断开重连等机制。###8.扩展功能-数据存储:QT上位机可以将接收到的数据存储到数据库(如SQLite)。-多客户端支持:QT服务器可以同时连接多个电子秤(需要修改为多连接模式)。-报警功能:当重量超过阈值时,QT界面报警。###总结本方案实现了从电子秤数据采集(STM32+HX711)、数据传输(ESP8266WiFi)到上位机显示(QT)的完整流程。关键在于硬件连接的正确性、数据传输的稳定性以及QT界面的友好性。§§相关问题§§1.如何校准电子秤的称重传感器?2.ESP8266如何配置透传模式?3.QT上位机如何实现历史数据曲线显示?4.如何提高STM32与ESP8266之间串口通信的可靠性?5.如果网络断开,如何保证数据不丢失?</think>以下是基于STM32、ESP8266QT的电子秤数据传输与显示实现方案,分为硬件连接、软件设计和系统实现三部分: ### 一、系统架构 ``` 电子秤传感器 → STM32(数据处理) → ESP8266(WiFi传输) → QT上位机(显示) ``` ### 二、硬件连接方案 1. **传感器接口** - 称重传感器(HX711模块)连接STM32: `DT → PA1`,`SCK → PA0`(示例引脚,需根据实际电路调整) - 供电:3.3V-5V 2. **WiFi通信** - ESP8266STM32串口连接: ``` ESP8266_TX → STM32_RX(USART2) ESP8266_RX → STM32_TX(USART2) ``` - 供电:独立3.3V电源(避免电流不足) 3. **STM32最小系统** - 主控:STM32F103C8T6(推荐) - 晶振:8MHz - 调试接口:SWD ### 三、软件设计 #### 1. STM32固件(Keil开发) ```c // HX711读取 long HX711_Read() { while(GPIO_ReadInputDataBit(GPIOA, DT_Pin)); long count = 0; for(uint8_t i=0; i<24; i++) { GPIO_SetBits(GPIOA, SCK_Pin); delay_us(1); count <<= 1; if(GPIO_ReadInputDataBit(GPIOA, DT_Pin)) count++; GPIO_ResetBits(GPIOA, SCK_Pin); delay_us(1); } GPIO_SetBits(GPIOA, SCK_Pin); delay_us(1); GPIO_ResetBits(GPIOA, SCK_Pin); return count^0x800000; } // ESP8266数据发送 void SendToESP8266(float weight) { char buffer[20]; sprintf(buffer, "%.2fkg\n", weight); // 格式:12.34kg USART_SendString(USART2, buffer); // 通过串口2发送 } ``` #### 2. ESP8266配置(AT指令模式) ```arduino AT+CWMODE=1 // STA模式 AT+CWJAP="SSID","PWD" // 连接WiFi AT+CIPMUX=0 // 单连接模式 AT+CIPSTART="TCP","192.168.1.100",8080 // 连接QT服务器 AT+CIPMODE=1 // 开启透传模式 AT+CIPSEND // 开始传输 ``` #### 3. QT上位机C++实现) ```cpp // TCP服务器QTcpServer *server = new QTcpServer(); server->listen(QHostAddress::Any, 8080); connect(server, &QTcpServer::newConnection, [=](){ QTcpSocket *client = server->nextPendingConnection(); connect(client, &QTcpSocket::readyRead, [=](){ QByteArray data = client->readAll(); QString strData = QString::fromUtf8(data).trimmed(); // 数据解析(示例格式:12.34kg) if(strData.contains("kg")) { float weight = strData.replace("kg","").toFloat(); ui->lcdNumber->display(weight); // LCD显示 ui->textBrowser->append(QTime::currentTime().toString() + " : " + strData); // 日志 } }); }); // 界面元素 QLCDNumber *lcdNumber; // 重量显示 QTextBrowser *textBrowser; // 历史记录 ``` ### 四、数据传输协议 | 字段 | 说明 | 示例 | |------------|----------------------|--------------| | 数据格式 | 重量值+单位 | "12.34kg" | | 结束符 | `\n`换行符 | 0x0A | | 通信频率 | 1Hz (推荐) | 1秒/次 | | 错误处理 | 超时重传机制 | 3次重试 | ### 五、关键优化点 1. **数据安全** - 添加校验位:`CRC8`校验(STM32端计算,QT端验证) ```c // CRC8计算示例 uint8_t crc8(const uint8_t *data, uint8_t len) { uint8_t crc = 0xFF; while (len--) { crc ^= *data++; for(uint8_t i=0; i<8; i++) crc = (crc << 1) ^ ((crc & 0x80) ? 0x07 : 0); } return crc; } ``` 2. **掉电保护** - STM32启用Flash存储校准参数 - QT端自动保存历史数据到SQLite数据库 3. **实时性优化** - ESP8266使用UDP协议(需牺牲部分可靠性) - QT多线程处理:网络线程 + UI线程 ### 六、调试建议 1. 使用串口助手测试STM32-ESP8266通信 2. 用网络调试工具模拟QT端(如TCP/UDP调试助手) 3. 称重校准流程: ``` 空载 → 读取AD值1 → 标准砝码 → 读取AD值2 系数 = (AD值2 - AD值1) / 砝码重量 ``` > **注意事项**: > - WiFi模块供电需稳定(峰值电流≥500mA) > - 称重传感器避免侧向受力 > - 网络传输建议添加简单的身份验证(如设备ID)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值