ESP32 → UDP广播 → 手机浏览器 → Three.js 实时旋转立方体( yaw、pitch、roll )。
1 总体流程
ESP32(Wi-Fi) ➜ UDP 广播 ➜ 手机(同局域网)
➜ 浏览器打开 index.html ➜ Three.js 接收 ➜ 立方体实时旋转
2 ESP32 端(Arduino 代码)
① 安装依赖
arduino-cli lib install "WiFi"
② 在原来 DMP 代码尾部加 UDP 发送
#include <WiFi.h>
#include <WiFiUdp.h>
const char *ssid = "your_AP";
const char *password = "your_PW";
WiFiUDP udp;
const uint16_t udpPort = 3000; // 任意>1024
IPAddress broadcastIp; // 自动计算
void setup_UDP(){
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) delay(500);
// 计算广播地址 192.168.x.255
IPAddress ip = WiFi.localIP();
IPAddress mask = WiFi.subnetMask();
broadcastIp = ip | ~mask;
udp.begin(udpPort);
}
// 在 loop() 里每帧调用
void sendYPR(float y,float p,float r){
char buf[64];
snprintf(buf,sizeof(buf),"Y%.2fP%.2fR%.2f",y,p,r);
udp.beginPacket(broadcastIp, udpPort);
udp.write((uint8_t*)buf, strlen(buf));
udp.endPacket();
}
③ 在 loop()
里打印完 ypr 后调用
sendYPR(ypr[0]*180/M_PI, ypr[1]*180/M_PI, ypr[2]*180/M_PI);
上传后打开串口监视器,确认 Wi-Fi 连接成功即可。
3 手机端准备
- 连入 同一路由器 Wi-Fi。
- 记住手机 IP(设置→关于手机→状态信息,例如 192.168.3.55)。
- 下面网页可直接用浏览器打开,无需安装 App。
4 Three.js 网页(index.html)
把下面文件放到 电脑/树莓派 任意目录,用 Python 临时 http 即可手机访问。
我是直接再树莓派上创建了一个目录temp_web
python -m http.server 8000
手机浏览器输入
http://<树莓派IP>:8000/index.html
index.html(完整单文件)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>ESP32 YPR Three.js</title>
<style>body{margin:0;overflow:hidden;background:#000}</style>
</head>
<body>
<script src="https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.min.js"></script>
<script>
/* ---------- 1. 场景、相机、渲染器 ---------- */
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(60,innerWidth/innerHeight,0.1,1000);
camera.position.z = 3;
const renderer = new THREE.WebGLRenderer({antialias:true});
renderer.setSize(innerWidth,innerHeight);
document.body.appendChild(renderer.domElement);
/* ---------- 2. 立方体 ---------- */
const geo = new THREE.BoxGeometry(1,1,1);
const mat = new THREE.MeshNormalMaterial();
const cube= new THREE.Mesh(geo,mat);
scene.add(cube);
/* ---------- 3. UDP 接收 ---------- */
const udp = new WebSocket('ws://192.168.3.55:8765'); // 我的pi的ip地址,后面用 WebSocket 转发
let y=0,p=0,r=0;
udp.onmessage = (ev)=>{
const m = ev.data.match(/Y([-\d.]+)P([-\d.]+)R([-\d.]+)/);
if(m){ y=+m[1]; p=+m[2]; r=+m[3]; }
};
/* ---------- 4. 动画循环 ---------- */
function animate(){
requestAnimationFrame(animate);
cube.rotation.y = THREE.MathUtils.degToRad(y);
cube.rotation.x = THREE.MathUtils.degToRad(p);
cube.rotation.z = THREE.MathUtils.degToRad(r);
renderer.render(scene,camera);
}
animate();
/* ---------- 5. 窗口大小 ---------- */
addEventListener('resize',()=>{
camera.aspect = innerWidth/innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth,innerHeight);
});
</script>
</body>
</html>
注意:浏览器无法直接收 UDP,需要中间人把 UDP 转成 WebSocket。
下面用 20 行 Python 脚本当“桥梁”。
5 UDP→WebSocket 桥梁(ws_bridge.py)
pip install asyncio websockets
import asyncio, websockets, socket, re
UDP_IP = '' # 监听所有网卡
UDP_PORT = 3000
WS_PORT = 8765 # 浏览器连 ws://ip:8765
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind((UDP_IP, UDP_PORT))
sock.setblocking(False)
CLIENTS = set()
async def udp_to_ws():
global CLIENTS
loop = asyncio.get_event_loop()
while True:
data = await loop.sock_recv(sock, 1024)
data = data.decode().strip()
if re.match(r'Y-?\d+\.\d+P-?\d+\.\d+R-?\d+\.\d+', data):
dead = set()
for ws in CLIENTS:
try: await ws.send(data)
except Exception: dead.add(ws)
CLIENTS -= dead
async def handler(websocket):
CLIENTS.add(websocket)
await websocket.wait_closed()
CLIENTS.remove(websocket)
async def main():
await asyncio.gather(
udp_to_ws(),
websockets.serve(handler, '0.0.0.0', WS_PORT)
)
asyncio.run(main())
启动:
python ws_bridge.py
6 最终数据流
ESP32 UDP广播 → ws_bridge.py(UDP→WebSocket)
→ 手机浏览器(WebSocket)→ Three.js 实时旋转
7 一键验证
- 确认 ESP32 串口已打印 Wi-Fi 连接成功。
- 电脑/树莓派运行
ws_bridge.py
。 - 手机浏览器输入
http://<树莓派IP>:8000/index.html
- 转动 ESP32,立方体立即跟随 yaw / pitch / roll 旋转。
8 进阶可选
需求 | 快速做法 |
---|---|
更低延迟 | 把桥脚本放树莓派,与 ESP32 同有线 |
多手机同时看 | WebSocket 天然支持多客户端 |
真·直连 UDP | 微信小程序 + 微信 UDP 插件 |
- 至此,ESP32 → 手机 Three.js 实时 3D 姿态 通路全部打通!