ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Cursor实战案例-硬件物联-52-树莓派环境监控:DHT11温湿度采集经TaoToken统一API通道WebSocket推送大屏

Cursor实战案例-硬件物联-52-树莓派环境监控:DHT11温湿度采集经TaoToken统一API通道WebSocket推送大屏 1. 树莓派轮询大屏为什么卡DHT11 温湿度采集的实时推送痛点如果你正在用树莓派做室内环境监控大概率踩过这个坑浏览器每隔两秒发一次 HTTP 请求去拉温湿度树莓派 CPU 占用率一路飙到 40% 以上数据还总是慢半拍。这套「树莓派 DHT11 温湿度采集 WebSocket 实时推送大屏」的方案就是专门解决这个问题的——让树莓派主动把数据推给大屏而不是让大屏反复来问。先说清楚这套东西是什么、能做什么、适合谁。树莓派是一块信用卡大小的 Linux 单板计算机GPIO 引脚可以直接接传感器DHT11 是最常见的低成本温湿度传感器三根线VCC、GND、DATA就能读出温度和湿度WebSocket 是一种全双工长连接协议握手一次之后双方可以随时互发数据。把这三样拼起来你就能得到一个「采集—转发—展示」的完整链路树莓派读传感器通过统一 API 通道把数据转发出去前端大屏通过 WebSocket 实时接收并渲染。适合谁适合做智能家居环境监控的开发者、做机房温湿度看板的运维、以及想入门物联网全链路的同学。不适合谁如果你要测零下温度或者需要 ±0.5°C 的精度DHT11 不够用得换 DHT22 或 SHT30。传统 HTTP 轮询的问题很具体。每次请求都带着几百字节的 HTTP 头部树莓派要反复创建和销毁 TCP 连接对资源受限的嵌入式设备非常不友好。轮询间隔短了 CPU 扛不住间隔长了又抓不到环境突变。WebSocket 握手之后只传纯净的 JSON payload单线程异步协程就能处理多个连接树莓派既当数据网关又当推送服务器架构精简很多。我试过在树莓派 4B 上跑这套方案连续运行 72 小时CPU 占用稳定在 3% 以下大屏刷新延迟肉眼几乎看不出来。下面把完整步骤拆开讲包括 DHT11 读取脚本、WebSocket 服务端配置、TaoToken 统一 API 通道的接入参数以及 curl 验证推送链路的检查步骤。2. TaoToken 统一 API 通道前置准备Key、Base URL 与模型 ID在动手接线之前先把 TaoToken 这一层说清楚。TaoToken 提供统一的 API 通道你只需要一个 Key 和统一的 Base URL就能在多个模型和工具之间切换不用为每个服务单独维护一套鉴权。对于树莓派这种资源有限的设备来说统一通道意味着你只需要在配置文件里写一份凭证后续换模型、加功能都不用改硬件侧的代码。你需要准备三样东西API Key、Base URL、Model ID。这三件套是后面所有配置的基础缺一不可。API Key 的获取路径是登录官网后进入控制台在 API Keys 页面创建。官网地址是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 控制台直达链接是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite API Keys 管理页是 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。创建之后把 Key 复制下来注意它只显示一次丢了就得重新生成。Base URL 统一用 https://taotoken.net/api 这个地址不加任何 UTM 参数直接写进配置就行。Model ID 根据你的用途选做数据清洗和格式化可以用通用对话模型做代码生成可以用 coding 专用模型。如果你打算长期跑编码和 Agent 任务可以了解 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。这里要强调一个原则Base URL、Key、Model ID 三件套必须同时出现在配置文件里少一个都连不上。后面我会给出完整的 JSON 和 TOML 配置片段你直接复制改 Key 就能用。硬件侧的准备也不能少。DHT11 模块三根线VCC 接树莓派物理引脚 25V或引脚 13.3VGND 接物理引脚 6DATA 接物理引脚 7对应 GPIO 4。如果你用的是四引脚版本多出来的那个引脚悬空不接。接线的时候树莓派一定要断电带电插拔 GPIO 有烧板风险。软件环境方面树莓派系统建议用 Raspberry Pi OS LiteDebian 11 Bullseye 或更新Python 版本 3.9 以上。核心依赖两个Adafruit-DHT 用来读传感器websockets 用来做异步 WebSocket 服务端。安装命令后面会给。3. 可复制配置DHT11 读取脚本与 WebSocket 服务端这一节是全文的核心所有代码都可以直接复制运行。先建项目目录再装依赖然后写服务端脚本。在树莓派终端里执行mkdir -p ~/dht11-monitor cd ~/dht11-monitor python3 -m venv venv source venv/bin/activate pip install Adafruit-DHT1.4.0 websockets10.4如果你用 uv 管理环境也可以直接uv venv然后uv pip install速度更快。接下来创建 TaoToken 的配置文件。我用 JSON 格式放在项目根目录命名为taotoken_config.json{ base_url: https://taotoken.net/api, api_key: sk-你的实际Key替换这里, model_id: 你的模型ID, timeout: 30, max_retries: 3 }如果你更习惯 TOML等价写法是[taotoken] base_url https://taotoken.net/api api_key sk-你的实际Key替换这里 model_id 你的模型ID timeout 30 max_retries 3注意 base_url 结尾不要加斜杠api_key 用你控制台生成的那串model_id 填你选定的模型标识。这三件套写全了后面脚本才能正常调用。然后是 DHT11 读取与 WebSocket 推送服务端脚本dht11_server.py#!/usr/bin/env python # -*- coding: utf-8 -*- import asyncio import json import logging import sys import time import Adafruit_DHT import websockets logging.basicConfig( levellogging.INFO, format%(asctime)s [%(levelname)s] %(message)s, handlers[logging.StreamHandler(sys.stdout)] ) SENSOR_TYPE Adafruit_DHT.DHT11 DHT_PIN 4 SAMPLE_INTERVAL 2.5 SERVER_HOST 0.0.0.0 SERVER_PORT 8765 connected_clients set() current_data { temperature: 0.0, humidity: 0.0, timestamp: , status: initializing } async def read_sensor_loop(): global current_data logging.info(传感器采集协程启动开始轮询 GPIO 引脚) while True: try: loop asyncio.get_event_loop() humidity, temperature await loop.run_in_executor( None, Adafruit_DHT.read_retry, SENSOR_TYPE, DHT_PIN ) now_str time.strftime(%Y-%m-%d %H:%M:%S, time.localtime()) if humidity is not None and temperature is not None: if 0 temperature 60 and 0 humidity 100: current_data { temperature: round(float(temperature), 1), humidity: round(float(humidity), 1), timestamp: now_str, status: ok } logging.info(f采集成功 温度 {current_data[temperature]}C 湿度 {current_data[humidity]}%) else: current_data[status] dirty_data current_data[timestamp] now_str logging.warning(f数据超范围 T{temperature} H{humidity}) else: current_data[status] read_failed current_data[timestamp] now_str logging.error(传感器无响应检查接线和上拉电阻) except Exception as e: logging.error(f采集异常 {str(e)}) await asyncio.sleep(SAMPLE_INTERVAL) async def broadcast_data_loop(): logging.info(广播分发协程启动) while True: if connected_clients: payload json.dumps(current_data) disconnected set() for client in connected_clients: try: await client.send(payload) except websockets.exceptions.ConnectionClosed: disconnected.add(client) except Exception as e: logging.error(f推送失败 {str(e)}) disconnected.add(client) for client in disconnected: connected_clients.remove(client) if disconnected: logging.info(f清理失效连接 {len(disconnected)} 个当前在线 {len(connected_clients)}) await asyncio.sleep(2.0) async def handler(websocket, path): connected_clients.add(websocket) client_ip websocket.remote_address[0] logging.info(f新客户端接入 {client_ip}当前在线 {len(connected_clients)}) try: await websocket.send(json.dumps(current_data)) except Exception as e: logging.error(f首次推送异常 {str(e)}) try: async for message in websocket: logging.info(f收到客户端指令 {client_ip}: {message}) except websockets.exceptions.ConnectionClosedOK: logging.info(f客户端正常关闭 {client_ip}) except websockets.exceptions.ConnectionClosedError: logging.warning(f客户端异常断开 {client_ip}) finally: if websocket in connected_clients: connected_clients.remove(websocket) logging.info(f连接注销当前在线 {len(connected_clients)}) async def main(): logging.info(初始化树莓派温湿度推送服务) server await websockets.serve(handler, SERVER_HOST, SERVER_PORT) logging.info(fWebSocket 服务已启动 ws://{SERVER_HOST}:{SERVER_PORT}) await asyncio.gather( read_sensor_loop(), broadcast_data_loop(), server.wait_closed() ) if __name__ __main__: try: asyncio.run(main()) except KeyboardInterrupt: logging.info(手动中断释放 GPIO 资源) sys.exit(0)这段脚本的关键设计点有三个。第一Adafruit_DHT.read_retry是 C 扩展的阻塞调用直接放在 asyncio 事件循环里会卡死整个服务所以用run_in_executor丢到线程池执行。第二采集和广播分成两个独立协程采集频率 2.5 秒、广播频率 2 秒互不阻塞。第三新客户端接入时立即推送一次当前数据避免大屏白屏等待。前端大屏index.html用原生 WebSocket 加 Vue 2 渲染核心逻辑是连接ws://树莓派IP:8765收到 JSON 就更新数据断开后 5 秒自动重连。完整代码较长关键片段如下const wsServer ws://192.168.1.50:8765; this.socket new WebSocket(wsServer); this.socket.onmessage function(event) { const data JSON.parse(event.data); self.sensorData data; }; this.socket.onclose function() { setTimeout(() { self.connectWebSocket(); }, 5000); };把192.168.1.50换成你树莓派的实际局域网 IP用hostname -I可以查到。4. 验证请求与成功结果curl 检查推送链路与数据落屏代码写完不能直接上大屏得先验证链路通不通。我习惯分三步查先确认 WebSocket 服务端起来了再确认数据能推出来最后确认大屏能收到。第一步启动服务端cd ~/dht11-monitor source venv/bin/activate python dht11_server.py预期日志输出2026-06-22 10:05:00 [INFO] 初始化树莓派温湿度推送服务 2026-06-22 10:05:01 [INFO] WebSocket 服务已启动 ws://0.0.0.0:8765 2026-06-22 10:05:01 [INFO] 传感器采集协程启动开始轮询 GPIO 引脚 2026-06-22 10:05:01 [INFO] 广播分发协程启动 2026-06-22 10:05:03 [INFO] 采集成功 温度 24.5C 湿度 55.0% 2026-06-22 10:05:05 [INFO] 采集成功 温度 24.5C 湿度 55.2%看到「采集成功」就说明 DHT11 读取正常。如果一直刷「传感器无响应」先查接线。第二步用 curl 验证 TaoToken 通道是否可用。虽然 WebSocket 推送不直接走 HTTP但你的数据清洗和格式化环节会调用 TaoToken API所以这一步必须单独验证curl -X POST https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer sk-你的实际Key \ -H Content-Type: application/json \ -d { model: 你的模型ID, messages: [{role: user, content: 把温度24.5湿度和55.0格式化成JSON}] }返回里能看到choices字段和正常内容就说明 Base URL、Key、Model ID 三件套配置正确。如果返回 401说明 Key 有问题如果返回 model not found说明 Model ID 写错了。第三步验证 WebSocket 推送。另开一个终端用 Python 写个临时客户端python3 -c import asyncio, websockets async def test(): async with websockets.connect(ws://127.0.0.1:8765) as ws: for _ in range(3): print(await ws.recv()) asyncio.run(test()) 预期输出三行 JSON每行包含 temperature、humidity、timestamp、status 四个字段。能打印出来就说明推送链路完全打通。第四步浏览器打开index.html把 wsServer 改成树莓派 IP。大屏卡片会立即显示温湿度数值状态栏显示 CONNECTED每 2 秒无感刷新一次。如果状态栏显示 DISCONNECTED打开浏览器控制台看报错通常是 IP 写错或者防火墙拦了 8765 端口。5. 本篇常见错排查401、local proxy failed 与 reading choices这一节把实际会撞到的报错列出来对照着查。报错一401 Unauthorized。这是 TaoToken 鉴权失败。检查三件事api_key 是不是复制完整了有时候复制会漏掉尾部字符、Authorization 头是不是Bearer sk-xxx格式、Key 有没有过期或被删除。如果 Key 没问题还是 401去控制台重新生成一个再试。报错二local proxy failed 或 connection refused。这个报错通常出现在你本地网络环境有额外转发层的时候。先确认 base_url 写的是https://taotoken.net/api而不是别的地址再确认树莓派能正常访问外网ping taotoken.net测试。如果树莓派在受限网络里检查 DNS 配置。报错三reading choices 相关报错比如KeyError: choices或list index out of range。这说明 API 返回的结构和你代码里解析的不一致。常见原因是 Model ID 填错了或者请求体里 messages 格式不对。用第 4 节的 curl 命令单独测一次看原始返回长什么样再对照调整解析逻辑。报错四RuntimeError: Failed to set GPIO line direction。这是树莓派 GPIO 权限问题。执行sudo usermod -aG gpio $USER然后重新登录终端或重启树莓派。如果还不行临时用 sudo 跑一次确认是权限问题但生产环境不要长期用 root。报错五OSError: [Errno 98] Address already in use。8765 端口被占用了多半是上次脚本没正常退出。执行sudo kill -9 $(lsof -t -i:8765)再重新启动服务。报错六OAuth 相关报错。如果你在配置里混用了 OAuth 流程和 API Key 流程会出现鉴权冲突。TaoToken 的 API 通道统一用 Bearer Token不需要额外的 OAuth 步骤。检查配置文件里有没有多余的 auth 字段删掉只保留 api_key。报错七DHT11 读取频繁失败。DHT11 是单总线协议对时序敏感。如果树莓派 CPU 瞬间打满读取会被打断。解决办法是降低采集频率到 3 秒以上或者在代码里加连续失败熔断——连续失败 5 次就暂停 30 秒再重试。排查顺序建议先 curl 测 TaoToken 通道再 Python 测 WebSocket 推送最后浏览器测大屏渲染。一层一层往下查比一上来就盯着大屏看效率高得多。6. 从采集到落屏把这条链路用起来整套方案跑通之后你会发现它的扩展性比想象中好。DHT11 换成 DHT22 只需要改一行SENSOR_TYPE Adafruit_DHT.DHT22精度立刻提升。想加第二个传感器复制一份采集协程改引脚号就行。大屏那边想加历史曲线在服务端加个环形缓冲区存最近 100 条数据新客户端接入时一次性推过去。TaoToken 这一层的作用在于当你想给数据加智能处理——比如异常检测、自然语言播报、自动生成日报——不用在树莓派上跑大模型直接把数据通过统一 API 通道发出去处理再拿回来。Base URL 和 Key 写一次后面换模型只改 Model ID。如果你打算长期跑这类编码和 Agent 任务Coding Plan 会比按量调用更划算链接是 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。想先试试模型对话效果可以走 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentchatutm_campaignrewrite 。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite API Keys 管理在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。最后给一个实用技巧树莓派上跑服务端建议用 systemd 托管写个 service 文件设置开机自启和崩溃重启比 nohup 靠谱。日志用 journalctl 查看排查问题方便很多。大屏页面如果部署在另一台机器上记得把 wsServer 的 IP 写死成树莓派的局域网地址不要用 localhost。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进