原文发表于 CSDN(作者:hjz0810),发布日期 2026-04-15。本文为转载备份,已征得作者同意。
第1章 系统的总体架构
本系统采用客户端-服务器-设备端三层架构,各层之间通过WebSocket协议进行实时双向通信,确保指令的低延迟传输。采用B/S(Browser/Server)架构与物联网技术相结合的设计方案。软件系统主要由四部分组成:基于ESP32的下位机控制程序、基于Node.js的WebSocket公网中继服务、基于Python Flask的业务逻辑与支付监控后台、以及基于HTML5/Bootstrap的用户交互前端。
1.1 设备层 (ESP32)
负责硬件控制,包括连接本地Wi-Fi,通过WebSocket连接至公网中继服务器,并接收来自云端的开锁指令以驱动相应电磁锁。
1.2 服务层 (云服务器)
1.2.1 Web前端
提供商品展示与选择的用户界面,负责发起支付订单。
1.2.2 后端服务 (Flask)
生成支付订单链接,并启动图像识别线程监控支付结果。
1.2.3 消息中继服务器 (Node.js)
作为系统的“消息总线”,负责转发后端服务向ESP32发送的出货指令。
1.3 支付层
使用第三方支付平台(易支付),通过图像识别技术自动化确认支付状态。
第2章 嵌入式终端控制程序设计(ESP32)
2.1 主控单元
选用ESP32-WROOM-32模组作为核心控制器。其集成Wi-Fi功能,功耗低,性能足以处理网络通信与GPIO控制任务。下位机主要负责网络连接维护、指令解析及电磁锁驱动。代码基于Arduino框架开发,利用WiFi.h库处理网络连接,WebSocketsClient库实现长连接,ArduinoJson库进行数据解析。
2.2 开锁原理
系统通过电磁锁控制货道门。在代码中,定义了锁控引脚数组 lockPins[] = {2, 4, 5, 18, 19},对应五个商品货道。控制逻辑简洁高效:
void unlockProduct(int productId) {
int pin = lockPins[productId - 1]; // 映射商品ID到物理引脚
digitalWrite(pin, HIGH); // 激活电磁锁(假设高电平触发)
delay(unlockDelay); // 保持激活状态3秒,确保商品掉落
digitalWrite(pin, LOW); // 关闭电磁锁
}
此设计通过商品ID与GPIO引脚的映射关系,实现了软件对硬件的灵活配置。
2.3 网络连接与自动重连机制
为了保证无人售货机在无人值守环境下的高可用性,程序中设计了双重守护机制:
WiFi守护:在主循环loop()中调用reconnectWiFi()函数,实时检测WiFi.status()。一旦检测到连接断开,立即挂起当前任务并尝试重新连接指定SSID,直至连接恢复。
WebSocket心跳保活:通过webSocket.enableHeartbeat(3000, 2000, 1)配置心跳包,每3秒向服务器发送Ping帧。若2秒内未收到Pong响应或连接意外断开(WStype_DISCONNECTED),将触发自动重连逻辑,重连间隔被优化为2秒,以减少掉线时间。
2.4 指令解析与硬件驱动
当收到服务器推送的文本消息(WStype_TEXT)时,系统调用parseLockMessage函数:
反序列化:将JSON字符串解析为对象。
安全校验:首先校验type字段是否为release_product,防止误操作。
执行动作:提取product_id,经有效性校验(1至numLocks)后,通过unlockProduct函数驱动对应GPIO。
电磁锁控制:采用非阻塞延时或同步延时逻辑,将对应引脚拉高(ACTIVE_LEVEL),持续3000ms(unlockDelay)后拉低,完成开锁动作。
第3章 公网通信与指令中继(NodeJS)
由于ESP32通常部署在NAT网络下,无法直接被公网访问,因此在云服务器(IP: 149.104.21.241)搭建Node.js WebSocket中继服务,端口8080。
该服务充当“消息路由”的角色,核心逻辑如下:
3.1 链路映射
维护backendClient和esp32Client两个全局对象,分别存储后台与设备的连接句柄。ESP32连接后发送 register:esp32,后端服务发送 register:backend。中继服务器据此区分客户端身份,并维护两个独立的WebSocket连接句柄。
3.2 定向转发
当收到来自backendClient的消息时,服务判断ESP32是否在线。若在线,且消息类型为release_product,则将指令原样转发给esp32Client。
3.3 异常处理
监听close和error事件,在任意一端断开时及时清空对应的连接句柄,及时释放资源并在控制台打印日志,避免内存泄漏和误发指令。
第4章 前端在线网页
前端采用HTML5结合Bootstrap 5框架,实现了响应式布局,更适配手机的布局页面
4.1 动态交互
商品列表通过Card组件展示,点击商品自动高亮并更新底部支付栏状态。JS脚本实时维护selectedProduct对象,记录用户选择的ID、名称和价格。
4.2 双向通信
4.2.1 HTTP请求
点击“立即支付”后,通过fetch API向后端发送POST请求,携带productValue(ID编号)与productPrice(商品价格)。
4.2.2 WebSocket监听
页面加载时初始化Socket.IO连接,监听payment_success事件。一旦收到后端通知,页面即时弹出“支付成功”提示,并自动更新UI状态,极大地提升了用户体验。
第5章 订单生成与支付监控系统(后端)
后台核心业务运行在Python Flask框架上,采用了RPA(机器人流程自动化)技术解决第三方支付接口的对接难题。
5.1 自动化订单生成
5.1.1 图像识别定位
利用locateOnScreen函数,基于预存的UI截图(如pic1.png~pic5.png)定位支付软件窗口坐标。
5.1.2 参数输入
自动获取并输入前端传递的商品价格(productPrice),实现了价格与货道的解耦。
5.1.3 支付平台链接转发
生成订单并自动转发链接,最终将支付URL返回给前端。 为防止并发冲突,该过程由threading.Lock()互斥锁保护,确保同一时间只能处理一个订单请求。
5.2 异步支付状态监测
为了不阻塞API响应,支付检测在独立线程中运行(check_payment_async):
5.2.1 视觉轮询
由于常规的api获取订单是否完成支付存在安全漏洞,采用api加视觉双重认证的方式确保订单是通过正常手段完成的支付,而非利用漏洞绕过支付,后端持续识屏幕并匹配“支付成功”图标(paid_success)。
5.2.2 超时控制
设置50秒超时阈值,避免长时间占用订单消耗资源,若在规定时间内检测到支付成功,则调用notify_esp32_via_relay函数进行中继发送。
5.2.3 指令下发
该函数建立与8080端口的中继连接,发送包含product_id的出货指令,完成“支付-出货”闭环。
一旦检测到支付成功,后端通过WebSocket向公网中继服务器发送一个标准化的JSON指令
{
"type": "release_product",
"product_id": 1, 订单编号
"order_url": "http://..." 生成的订单链接
}
第6章 通信协议与消息流
为了实现云端服务器与内网设备(ESP32)及前端页面的实时交互,系统定义了一套基于WebSocket的通信协议。采用JSON格式在服务层与中继之间传递结构化数据,而在中继与ESP32之间则传递纯文本或JSON指令,确保了协议的灵活性与可扩展性。
6.1 注册机制
客户端连接中继服务器后,需立即发送身份标识。中继服务器据此区分客户端身份,并维护两个独立的WebSocket连接句柄。
6.2 消息路由
只将来自 backendClient 且类型为 release_product 的指令转发给 esp32Client,起到了消息过滤和路由的安全作用。
6.3 指令格式
控制指令采用JSON格式传输,确保数据结构清晰且易于扩展。
{"type": "release_product","product_id": 1,"order_url": "http://..."}
实际操作:
1.使用ESP32-s3作为主控芯片
2.因电磁锁解锁电压最低一档为5V,而ESP32只能输出3.3V,采用TB6612做继电器使用(因为有四个电磁锁,对应TB6612的AIN1,AIN2,BIN1,BIN2)
3.使用ESP32连接WIFI名称为:宾馆,密码为:12345678的WiFi,发布一个网页,同时从外网拉取商品图片和支付宝二维码
4.采用CDN直链获取图片,因不便于调用wechat和支付宝API,采用更简单的二维码支付,即通过支付宝收钱功能给每个价格都截图,标号,程序中用算法实现精准调用截图
- 附件:前端代码.html
附件:后端代码.py
附件:云服务器中继代码.js
附件:ESP32代码.cpp
作者联系方式:hjz0060810(如需完整代码可联系作者)
核心速览
代码判官
遇到复杂语法或逻辑?选中代码,AI帮你解释
代码运行代码生成环境配置
知识延伸
读完有疑惑?就像聊天一样追问AI,挖掘博文背后的深度知识
DISCUSSION
评论
还没有评论。你可以留下第一条有价值的讨论。