Kindle+ESP32-S3实现TCP温湿度采集与灯光控制系统
项目概述本项目基于 ESP32-S3 搭建 TCP 服务端外接 AHT20 温湿度传感器、WS2812 彩灯Kindle 8Linux 环境作为 TCP 客户端采用交互式命令行菜单通过 TCP 网络实现远程查询温湿度、控制彩灯模式。通信采用自定义文本协议以换行符作为消息边界解决 TCP 流式粘包、单次指令连接断开问题。硬件清单ESP32-S3 开发板AHT20 数字温湿度传感器WS2812 8 路 RGB 灯带Kindle 8越狱 Linux 环境arm-gcc 编译运行 TCP 客户端一、硬件设计1.1 硬件接线定义ESP32-S3外设说明GPIO1WS2812 DIN彩灯数据引脚GPIO10AHT20 SDAI2C1 数据GPIO11AHT20 SCLI2C1 时钟3.3VAHT20、WS2812 电源共电源GNDAHT20、WS2812 GND必须共地1.2 硬件注意事项WS2812 工作电压 5V若使用 3.3V 驱动容易色彩异常、频闪长时间点亮建议灯带外接 5V 供电信号端串联 330Ω 电阻。AHT20 模块自带 I2C 上拉电阻无需额外焊接。ESP32 模块供电充足大电流负载会导致电压跌落WiFi 断连。二、通信协议规范2.1 传输层TCP IPv4ESP32 作为服务端监听端口8899客户端Kindle8 Linux TCP Client一问一答模式。2.2 报文格式通信编码ASCII 文本消息分隔符\n换行符每条发送指令末尾必须追加\n服务端响应末尾追加\n客户端 → ESP32 服务端指令集指令功能描述ESP32 返回应答GET查询温湿度TEMP:XX.XX,HUM:XX.XXCMD1WS2812 红色全亮OK:REDCMD2WS2812 绿色全亮OK:GREENCMD3WS2812 黄色全亮OK:YELLOWCMD4WS2812 彩虹跑马灯OK:RUNLIGHTCMD5WS2812 全部熄灭OK:OFF其他非法指令-ERR:Unknown Command2.3 通信容错设计ESP32 服务端逐字符非阻塞解析指令废弃阻塞函数readStringUntil()防止 WiFi 任务卡死、TCP 主动断开。Linux 客户端循环接收数据直到捕获换行符避免 TCP 流式粘包、读取历史缓存数据。客户端 Socket 设置接收超时防止网络异常程序永久阻塞。三、ESP32-S3 TCP 服务端软件设计Arduino3.1 开发环境Arduino IDE平台esp32 v2.x 依赖库FastLEDWS2812 驱动Adafruit AHTX0AHT20 温湿度驱动3.2 软件架构WiFi STA 模式连接路由器自动获取 IP启动 TCP Server (8899)非阻塞轮询接收 TCP 字符缓冲区缓存识别\n切割完整指令指令分发处理温湿度读取 / WS2812 模式切换独立定时器驱动1 秒串口打印温湿度150ms 刷新跑马灯效果客户端断线自动清空灯带、释放资源3.3 完整源代码直接可用#include Arduino.h #include WiFi.h #include FastLED.h #include Adafruit_AHTX0.h // 配置区 const char* WIFI_SSID WIFI名称; const char* WIFI_PASSWORD WIFI密码; const uint16_t TCP_PORT 8899; #define WS2812_PIN 1 #define LED_COUNT 8 #define AHT_SDA_PIN 10 #define AHT_SCL_PIN 11 #define RECV_BUF_LEN 64 // CRGB leds[LED_COUNT]; Adafruit_AHTX0 aht; WiFiServer tcpServer(TCP_PORT); WiFiClient client; char recvBuf[RECV_BUF_LEN]; uint16_t bufIndex 0; enum LedMode { MODE_OFF, MODE_RED, MODE_GREEN, MODE_YELLOW, MODE_RUNLIGHT }; LedMode currentMode MODE_OFF; uint8_t hueValue 0; void setAllLed(CRGB color) { for(int i0; iLED_COUNT; i){ leds[i] color; } FastLED.show(); } void handleTcpCommand(String cmd) { cmd.trim(); cmd.toUpperCase(); Serial.print(收到TCP指令: ); Serial.println(cmd); if(cmd GET){ sensors_event_t humEvent, tempEvent; String resp; if(aht.getEvent(humEvent, tempEvent)){ resp TEMP: String(tempEvent.temperature,2) ,HUM: String(humEvent.relative_humidity,2); }else{ resp TEMP:ERR,HUM:ERR; } client.print(resp \n); } else if(cmd CMD1){ currentMode MODE_RED; setAllLed(CRGB::Red); client.print(OK:RED\n); } else if(cmd CMD2){ currentMode MODE_GREEN; setAllLed(CRGB::Green); client.print(OK:GREEN\n); } else if(cmd CMD3){ currentMode MODE_YELLOW; setAllLed(CRGB::Yellow); client.print(OK:YELLOW\n); } else if(cmd CMD4){ currentMode MODE_RUNLIGHT; client.print(OK:RUNLIGHT\n); } else if(cmd CMD5){ currentMode MODE_OFF; setAllLed(CRGB::Black); client.print(OK:OFF\n); } else{ client.print(ERR:Unknown Command\n); } } void setup() { Serial.begin(115200); while(!Serial) delay(10); FastLED.addLedsWS2812, WS2812_PIN, GRB(leds, LED_COUNT); FastLED.setBrightness(64); FastLED.clear(); FastLED.show(); Wire1.begin(AHT_SDA_PIN, AHT_SCL_PIN); if (!aht.begin(Wire1)) { Serial.println(【错误】AHT20初始化失败 SDA:10 SCL:11); } else { Serial.println(AHT20 初始化成功); } Serial.print(连接WiFi:); Serial.println(WIFI_SSID); WiFi.mode(WIFI_STA); WiFi.begin(WIFI_SSID, WIFI_PASSWORD); while (WiFi.status() ! WL_CONNECTED) { delay(500); Serial.print(.); } Serial.println(\nWiFi连接成功); Serial.print(本机IP: ); Serial.println(WiFi.localIP()); tcpServer.begin(); Serial.printf(TCP服务器启动,端口:%d\r\n,TCP_PORT); Serial.println(); } void loop() { if (!client.connected()) { if (client) { client.stop(); bufIndex 0; memset(recvBuf, 0, RECV_BUF_LEN); currentMode MODE_OFF; FastLED.clear(); FastLED.show(); } client tcpServer.accept(); if(client){ Serial.println(新客户端接入); } } else { while(client.available() 0) { char c client.read(); if (c \n) { if(bufIndex 0) { recvBuf[bufIndex] \0; String cmdStr(recvBuf); handleTcpCommand(cmdStr); bufIndex 0; memset(recvBuf, 0, RECV_BUF_LEN); } } else { if(bufIndex RECV_BUF_LEN-1) { recvBuf[bufIndex] c; } } } } static uint32_t printTimer 0; if(millis() - printTimer 1000){ printTimer millis(); sensors_event_t humEvent, tempEvent; if (aht.getEvent(humEvent, tempEvent)) { Serial.print(温度); Serial.print(tempEvent.temperature, 2); Serial.print( ℃ | 湿度); Serial.print(humEvent.relative_humidity, 2); Serial.println( %RH); } else { Serial.println(读取AHT20数据失败); } } static uint32_t ledTimer 0; if(millis() - ledTimer 150){ ledTimer millis(); if(currentMode MODE_RUNLIGHT){ static uint8_t pos 0; FastLED.clear(); leds[pos] CHSV(hueValue,255,255); FastLED.show(); pos; if(pos LED_COUNT) pos 0; hueValue 3; } } delay(10); }四、Kindle8 Linux TCP 客户端软件设计4.1 运行环境Kindle 8 越狱系统Linuxgcc 编译器纯 C 语言标准 POSIX Socket无第三方依赖。 功能交互式菜单、TCP 连接、一发一收、超时保护、完整接收单行应答解决粘包问题。4.2 客户端源代码tcp_client.c#include stdio.h #include stdlib.h #include string.h #include unistd.h #include sys/socket.h #include netinet/in.h #include arpa/inet.h #include errno.h #include sys/time.h #define SERVER_IP 192.168.1.11 // 修改为ESP32 IP #define SERVER_PORT 8899 #define BUF_SIZE 256 #define RECV_TIMEOUT_SEC 3 // 接收超时2秒 int sock_fd -1; /* 设置socket接收超时 */ static void set_sock_timeout(int fd, int sec) { struct timeval timeout; timeout.tv_sec sec; timeout.tv_usec 0; setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, timeout, sizeof(timeout)); } /* 建立TCP连接 */ int connect_server(void) { struct sockaddr_in serv_addr; if (sock_fd 0) { printf(已经连接无需重连\n); return 0; } sock_fd socket(AF_INET, SOCK_STREAM, 0); if (sock_fd 0) { perror(socket create failed); return -1; } set_sock_timeout(sock_fd, RECV_TIMEOUT_SEC); memset(serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family AF_INET; serv_addr.sin_port htons(SERVER_PORT); if (inet_pton(AF_INET, SERVER_IP, serv_addr.sin_addr) 0) { printf(IP地址格式错误\n); close(sock_fd); sock_fd -1; return -1; } if (connect(sock_fd, (struct sockaddr *)serv_addr, sizeof(serv_addr)) 0) { perror(connect failed); close(sock_fd); sock_fd -1; return -1; } printf(✅ 成功连接 ESP32 TCP Server %s:%d\n, SERVER_IP, SERVER_PORT); return 0; } /* * 发送指令循环接收直到读到换行符获取完整一行应答 * 返回 0成功-1失败/超时/断开 */ int send_command(const char *cmd, char *resp, int resp_len) { char send_buf[BUF_SIZE]; char tmp[BUF_SIZE]; int total 0; int i 0; ssize_t ret; memset(resp, 0, resp_len); if (sock_fd 0) { printf(❌ 未连接服务器请先连接\n); return -1; } // 指令必须带换行匹配ESP32 readStringUntil(\n) snprintf(send_buf, sizeof(send_buf), %s\n, cmd); if (write(sock_fd, send_buf, strlen(send_buf)) 0) { perror(send fail); close(sock_fd); sock_fd -1; return -1; } // 循环读取直到收到 \n 代表一条应答结束 while (1) { ret read(sock_fd, tmp, sizeof(tmp)-1); if (ret 0) { printf(❌ 读取应答失败 / 超时 / 服务器断开\n); close(sock_fd); sock_fd -1; return -1; } for (i ; i ret; i) { if (total resp_len - 1) { resp[total] tmp[i]; } // 遇到换行本条响应接收完成直接退出 if (tmp[i] \n) { resp[total] \0; return 0; } } } } void show_menu(void) { printf(\n MENU \n); printf( 1 - 连接ESP32服务端\n); printf( 2 - 查询温湿度 (GET)\n); printf( 3 - WS2812 红色全亮 CMD1\n); printf( 4 - WS2812 绿色全亮 CMD2\n); printf( 5 - WS2812 黄色全亮 CMD3\n); printf( 6 - WS2812 开启跑马灯 CMD4\n); printf( 7 - WS2812 全部关灯 CMD5\n); printf( 0 - 退出程序\n); printf(\n); printf(请输入选项: ); } int main(void) { int choice; char response[BUF_SIZE]; while (1) { show_menu(); if (scanf(%d, choice) ! 1) { // 清空错误输入 while (getchar() ! \n); printf(输入错误请输入数字\n); continue; } switch (choice) { case 1: connect_server(); break; case 2: if (0 send_command(GET, response, sizeof(response))) { printf( 服务器回复: %s, response); } break; case 3: if (0 send_command(CMD1, response, sizeof(response))) { printf( 服务器回复: %s, response); } break; case 4: if (0 send_command(CMD2, response, sizeof(response))) { printf( 服务器回复: %s, response); } break; case 5: if (0 send_command(CMD3, response, sizeof(response))) { printf( 服务器回复: %s, response); } break; case 6: if (0 send_command(CMD4, response, sizeof(response))) { printf( 服务器回复: %s, response); } break; case 7: if (0 send_command(CMD5, response, sizeof(response))) { printf( 服务器回复: %s, response); } break; case 0: printf(程序退出\n); if (sock_fd 0) close(sock_fd); return 0; default: printf(无效选项请重新选择\n); break; } } }4.3 Kindle8 编译与运行命令修改代码内SERVER_IP为 ESP32 打印出的局域网 IP编译在 Linux 主机上使用交叉编译器进行编译生成 kindle8 架构的执行程序arm-linux-gnueabi-gcc -marcharmv7-a -mtunecortex-a9 -msoft-float -mabiaapcs-linux tcp_client.c -o tcp_client上传程序 ssh 登录到 kindle 主机切换到/mnt/us/gcc-test 目录gcc-test 要自行创建[rootkindle gcc-test]# scp tony192.168.1.12:/home/tony/kindle-esp32/kindle-tcp ./ kindle-tcp 100% 13KB 2.2MB/s 00:00运行[rootkindle gcc-test]#./tcp_client五、整体测试流程ESP32 上电串口监视器查看 WiFi 连接状态记录 IP 地址修改 Kindle 客户端代码内 ESP32 IP编译Kindle 运行客户端选项【1】连接服务端依次调用功能2 查询温湿度3~7 控制彩灯重复多次切换验证 TCP 连接不会断开、指令应答不乱序。六、文件目录结构ESP32_TCP_LIGHT_AHT20/ ├── doc/ │ └── 开发设计文档.md # 本文档 ├── esp32_server/ │ └── esp32_server.ino # ESP32 Arduino源码 ├── kindle_client/ │ ├── tcp_client.c │ └── Makefile # 可选一键编译脚本 └── README.md # 使用说明、接线说明

相关新闻

Excel批量打印解决方案:提升办公效率的云端实践

Excel批量打印解决方案:提升办公效率的云端实践

1. 为什么需要Excel批量打印解决方案在日常办公中,我们经常遇到需要批量打印Excel表格的场景。传统的手动操作方式存在几个明显痛点:每次只能打印单个文件,当需要处理几十甚至上百个文件时,重复操作极其耗时打印设置无法保存&…

2026/9/18 18:32:21 阅读更多 →
破解生物医学图像分析难题:为什么DINOv2是你的终极自监督学习方案

破解生物医学图像分析难题:为什么DINOv2是你的终极自监督学习方案

破解生物医学图像分析难题:为什么DINOv2是你的终极自监督学习方案 【免费下载链接】dinov2 PyTorch code and models for the DINOv2 self-supervised learning method. 项目地址: https://gitcode.com/GitHub_Trending/di/dinov2 面对生物医学图像分析中标注…

2026/9/17 23:47:29 阅读更多 →
毕业论文高效写作:科研绘图、排版与AI降重实战指南

毕业论文高效写作:科研绘图、排版与AI降重实战指南

1. 毕业论文初稿的痛点与解决方案又到一年毕业季,看着身边学弟学妹们为毕业论文抓耳挠腮的样子,不禁想起当年自己熬夜改格式、反复查重的痛苦经历。作为过来人,今天想分享一套经过实战检验的毕业论文初稿全流程解决方案,特别适合时…

2026/9/19 7:13:34 阅读更多 →

最新新闻

交换芯片数据通路设计:Crossbar、VOQ、Shared Buffer与iSLIP仲裁

交换芯片数据通路设计:Crossbar、VOQ、Shared Buffer与iSLIP仲裁

交换芯片这个领域,很多人第一次接触时会被一堆术语砸晕:Crossbar、VOQ、Shared Buffer、Cell Fabric、iSLIP,每个词拆开都认识,合在一起就不知道它们在芯片里到底怎么协作。我当年从软件转发转到芯片微架构,最大的感受…

2026/9/21 20:24:28 阅读更多 →
喜茶go实战项目复盘:3个核心考点帮你搞定面试

喜茶go实战项目复盘:3个核心考点帮你搞定面试

喜茶go实战项目复盘:3个核心考点帮你搞定面试 面试被问原理答不上来,简历上写的实战项目全是“调包侠”?别慌。今天这篇【喜茶go】技术拆解,不整虚的,直接带你剥开这个高并发订单系统的底层逻辑。很多后端同学看这个案例,只盯着业务层CRUD,却…

2026/9/21 20:24:28 阅读更多 →
3分钟看懂智能陈桥输入法底层逻辑 2026最新避坑指南

3分钟看懂智能陈桥输入法底层逻辑 2026最新避坑指南

3分钟看懂智能陈桥输入法底层逻辑 2026最新避坑指南 官方文档翻了几页就头疼?那些枯燥的协议细节和架构描述,确实让人抓不住重点。很多开发者在集成或逆向分析输入法时,往往卡在“为什么候选词跳出来这么快”这个看似简单的问题上。2026最新的开…

2026/9/21 20:24:28 阅读更多 →
Whyme原理详解:3个最佳实践让代码跑通快5倍

Whyme原理详解:3个最佳实践让代码跑通快5倍

Whyme原理详解:3个最佳实践让代码跑通快5倍 复制来的代码跑不通,是不是让你抓狂?明明逻辑看着没问题,一执行就报错,或者慢得像蜗牛爬。这种时候,与其盲目改代码,不如先搞懂底层的 whyme…

2026/9/21 20:24:28 阅读更多 →
CheckBox 选中背景色不生效?用 TaoToken 接 Codex 改 input:checked 样式

CheckBox 选中背景色不生效?用 TaoToken 接 Codex 改 input:checked 样式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/21 20:23:28 阅读更多 →
10 个前端 MCP 服务器盘点:这次用 TaoToken 走通 Claude Code 的模型通道

10 个前端 MCP 服务器盘点:这次用 TaoToken 走通 Claude Code 的模型通道

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/21 20:23:28 阅读更多 →

日新闻

agents-generator 决策矩阵全解析:从项目检测到 AGENTS.md 规则生成的 16 步判定流程

agents-generator 决策矩阵全解析:从项目检测到 AGENTS.md 规则生成的 16 步判定流程

agents-generator 决策矩阵全解析:从项目检测到 AGENTS.md 规则生成的 16 步判定流程 【免费下载链接】agentic-awesome-skills AAS Core is the local, agent-first control plane for complete catalog discovery, agent-owned selection, stack validation, and …

2026/9/21 0:00:01 阅读更多 →
gin-vue-admin 前端工具函数全景指南:src/utils 复用规范与源码级解析

gin-vue-admin 前端工具函数全景指南:src/utils 复用规范与源码级解析

gin-vue-admin 前端工具函数全景指南:src/utils 复用规范与源码级解析 【免费下载链接】gin-vue-admin 🚀ViteVue3Gin拥有AI辅助的基础开发平台,企业级业务AI开发解决方案,内置mcp辅助服务,内置skills管理,…

2026/9/21 0:00:01 阅读更多 →
Wox 全功能插件开发实战指南:基于 Python / Node.js 宿主与 WebSocket 的持久化插件体系

Wox 全功能插件开发实战指南:基于 Python / Node.js 宿主与 WebSocket 的持久化插件体系

桌面应用AI 应用插件系统 【免费下载链接】Wox A cross-platform launcher that simply works 项目地址: https://gitcode.com/gh_mirrors/wo/Wox 点击查看 免费下载 全功能插件(Full-featured Plugin)是 Wox 三类插件实现方式中能力最完整的…

2026/9/21 0:00:01 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/21 3:13:20 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/21 2:19:36 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/21 4:51:05 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/21 15:36:51 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/21 15:36:51 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/19 23:35:34 阅读更多 →