资讯动态

基于ESP32与WebSocket构建远程控制计时器:硬件连接与软件实现详解

发布时间:2026/8/19 14:35:21 来源:尧图企业网站定制
1. 从需求到原型为什么我们需要一个可远程控制的定制计时器在工作室里我经常遇到这样的场景正在全神贯注地焊接一块电路板或者调试一段代码烤箱里的面包、3D打印机里的模型、或者某个需要定时浸泡的化学实验却到了关键时刻。这时候要么手忙脚乱地放下手头工作去处理要么就得忍受中途打断带来的烦躁和潜在风险。一个摆在设备旁边的普通厨房计时器显然解决不了“远程”和“定制”这两个核心痛点。“Custom timer display with remote control”这个项目正是为了解决这类跨空间、多任务的定时需求而生的。它不仅仅是一个计时器更是一个可以放在任何地方厨房、工作台、实验室的显示终端而你则可以通过手机、电脑或者另一个遥控器在任何地方客厅、另一个房间、甚至办公室轻松地设置、启动、暂停或重置它。这里的“Custom”意味着你可以自由定义计时时长、显示样式比如倒计时、正计时、特定格式、甚至触发其他联动动作如点亮一个LED灯、发送一条通知。这个项目的核心价值在于解耦了操作与观察。操作者无需身处设备旁观察者或设备本身也能清晰地看到剩余时间。无论是用于番茄工作法、管理烘焙流程、监控实验反应还是作为智能家居的一个交互节点它都提供了一个高度灵活、可定制的解决方案。接下来我将手把手带你从零开始构建一个功能完整、稳定可靠且极具扩展性的远程控制计时器系统。2. 系统架构选型为什么是“显示终端控制端”的分离设计在动手之前我们先要确定系统的骨架。市面上常见的智能计时方案要么是单一设备如智能音箱语音设置要么是手机App直接控制。但对于我们这个项目我选择了“显示终端 控制端”的物理分离架构。这是经过深思熟虑的主要原因有以下几点2.1 显示终端的独立性与专注性显示终端我们称之为Timer Display Unit, TDU的核心任务只有两个清晰、稳定地显示时间信息可靠地接收来自网络的控制指令并执行。它应该是一个“傻”终端逻辑尽可能简单这样能保证其运行极其稳定不会因为复杂的用户交互逻辑而崩溃。我们可以选用一块像ESP8266或ESP32这样的Wi-Fi微控制器搭配一块OLED或LCD屏幕来构建。它上电后自动连接网络然后进入一个循环检查网络消息队列 - 更新显示 - 循环。这种设计让TDU可以放在任何有电源和Wi-Fi信号的地方专心做好“显示”这一件事。2.2 控制端的灵活性与多样性控制端则完全解放了。它可以是一个专用的物理遥控器用另一块ESP32开发板加几个按钮和电池做成适合送给不擅长用手机的老人或在特定工作场景快速操作。一个网页界面这是最通用和强大的方式。我们在TDU或者一个中央服务器上运行一个简单的Web服务器任何连接到同一局域网的设备手机、电脑、平板打开浏览器就能访问控制页面。一个手机App可以通过MQTT、WebSocket等协议与TDU通信体验更原生。语音助手通过集成Home Assistant、IFTTT等平台用语音指令控制。这种分离架构的优势是显而易见的显示端稳定可靠控制端百花齐放。你可以根据实际使用场景选择最合适的控制方式甚至同时使用多种方式而无需修改显示端的核心代码。2.3 通信协议的选择WebSocket vs MQTT vs HTTP轮询确定了架构下一个关键决策是通信协议。如何让控制端和显示端“对话”HTTP轮询这是最简单但最不推荐的方式。让TDU每隔几秒就问一次服务器“有我的新指令吗”。这种方式网络开销大、延迟高、实时性差对服务器压力也大。MQTT这是一个轻量级的发布/订阅消息协议非常适合物联网场景。TDU订阅一个主题如home/timer/display1控制端向这个主题发布消息。优点是协议轻量、支持一对多、有现成的云端Broker如EMQX或可以自建Mosquitto。缺点是需要在网络中搭建一个MQTT Broker消息代理服务器。WebSocket它提供了浏览器与服务器之间的全双工通信通道。一旦连接建立双方可以随时主动发送数据实时性极高。对于我们的项目如果控制端主要是网页那么让TDU内置一个WebSocket服务器是最直接、延迟最低的方案。我的选择与理由对于家庭或工作室内部使用我强烈推荐TDU内置WebSocket服务器的方案。原因如下1) 无需额外部署MQTT Broker系统更简洁2) 延迟极低按下按钮到屏幕更新几乎感觉不到延迟3) 与网页控制端天生契合开发简单。我们将采用这个方案进行构建。如果你需要跨互联网控制或者有大量设备需要管理那么MQTT会是更优的选择。3. 硬件搭建与核心电路解析现在我们进入动手环节。首先准备硬件。3.1 物料清单主控芯片ESP32开发板如NodeMCU-32S或ESP32 DevKit C。选择ESP32而非ESP8266是因为前者性能更强、内存更大能更从容地同时处理Wi-Fi、WebSocket服务和显示驱动且蓝牙功能为未来扩展留有余地。显示屏0.96英寸或1.3英寸的I2C接口OLED屏幕SSD1306驱动。这种屏幕无需背光显示黑色时几乎不耗电对比度高非常适合显示时间信息。I2C接口只需要两根数据线SDA, SCL接线简单。电源Micro USB数据线用于供电和编程。如需长期放置可使用5V/1A的USB电源适配器。可选-物理按钮如果你想在显示端本地保留一个紧急停止或重置按钮可以准备1-2个轻触开关。杜邦线若干。3.2 电路连接详解连接非常简单遵循I2C的标准接法OLED - ESP32OLEDVCC- ESP323.3V(注意必须是3.3V接5V会烧毁屏幕)OLEDGND- ESP32GNDOLEDSCL- ESP32GPIO 22(这是ESP32默认的I2C时钟引脚)OLEDSDA- ESP32GPIO 21(这是ESP32默认的I2C数据引脚)可选按钮连接按钮一端接ESP32的某个GPIO如GPIO 15另一端接GND。在代码中需要将该GPIO设置为INPUT_PULLUP模式这样按钮未按下时引脚读数为高电平按下时变为低电平。这里有一个关键细节为什么选择GPIO 21和22在ESP32的众多GPIO中这两根是硬件I2C接口的默认引脚使用它们能获得最稳定和最高效的通信。虽然软件I2C可以映射到其他引脚但在有稳定硬件支持的情况下优先使用硬件接口是避免不必要调试麻烦的最佳实践。4. 软件实现从固件到网页控制台硬件搭好重头戏在软件。我们将使用Arduino IDE进行开发因为它对ESP32和常用库的支持非常友好。4.1 开发环境与库准备首先在Arduino IDE中安装ESP32开发板支持。然后通过库管理器安装以下核心库Adafruit SSD1306用于驱动OLED屏幕。Adafruit GFX图形库SSD1306依赖它来绘制图形和文字。WebSocketsby Markus Sattler这是一个非常强大的WebSocket库支持服务器和客户端模式。4.2 固件代码核心逻辑拆解固件代码运行在ESP32上需要完成以下几件事连接Wi-Fi、启动WebSocket服务器、初始化屏幕、维护计时器状态、处理来自WebSocket的控制命令、更新屏幕显示。#include WiFi.h #include WebSocketsServer.h #include Adafruit_GFX.h #include Adafruit_SSD1306.h // 1. 网络配置 const char* ssid 你的Wi-Fi名称; const char* password 你的Wi-Fi密码; // 2. WebSocket服务器监听端口81 WebSocketsServer webSocket WebSocketsServer(81); // 3. 屏幕对象定义 #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, Wire, -1); // 4. 计时器状态变量 unsigned long setTime_ms 0; // 用户设置的计时时长毫秒 unsigned long startTime_ms 0; // 计时开始的系统时间戳 bool isRunning false; // 计时器是否正在运行 bool isPaused false; // 计时器是否暂停 unsigned long pauseDuration 0; // 暂停时已经过去的时间 void setup() { Serial.begin(115200); // 初始化屏幕 if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { Serial.println(F(SSD1306分配失败)); for(;;); // 死循环阻止继续执行 } display.clearDisplay(); display.setTextSize(2); display.setTextColor(SSD1306_WHITE); display.setCursor(0,0); display.println(Connecting...); display.display(); // 连接Wi-Fi WiFi.begin(ssid, password); while (WiFi.status() ! WL_CONNECTED) { delay(500); Serial.print(.); } Serial.println(\nConnected! IP: ); Serial.println(WiFi.localIP()); // 在屏幕上显示IP地址方便我们访问 display.clearDisplay(); display.setCursor(0,0); display.println(Ready!); display.setCursor(0,20); display.setTextSize(1); display.print(IP:); display.println(WiFi.localIP()); display.display(); // 启动WebSocket服务器 webSocket.begin(); webSocket.onEvent(webSocketEvent); // 设置事件回调函数 } void loop() { webSocket.loop(); // 必须持续调用处理WebSocket事件 // 核心计时逻辑 static unsigned long lastUpdate 0; if (millis() - lastUpdate 200) { // 每200ms更新一次显示避免屏幕闪烁过快 updateDisplay(); lastUpdate millis(); } // 可选处理本地按钮 // handleButton(); } // 更新屏幕显示 void updateDisplay() { display.clearDisplay(); display.setTextSize(2); display.setCursor(0,0); unsigned long currentTime millis(); unsigned long elapsed 0; if (isRunning !isPaused) { elapsed currentTime - startTime_ms; } else if (isPaused) { elapsed pauseDuration; // 显示暂停时的时间 } unsigned long remaining (setTime_ms elapsed) ? (setTime_ms - elapsed) : 0; // 将剩余时间转换为分:秒格式 int minutes remaining / 60000; int seconds (remaining % 60000) / 1000; int hundredths (remaining % 1000) / 10; // 显示百分秒更精确 char timeStr[12]; sprintf(timeStr, %02d:%02d.%02d, minutes, seconds, hundredths); display.println(Timer:); display.println(timeStr); // 在底部显示状态 display.setTextSize(1); display.setCursor(0, SCREEN_HEIGHT - 10); if (!isRunning) { display.print(STOP); } else if (isPaused) { display.print(PAUSED); } else { display.print(RUNNING); } display.display(); } // WebSocket事件处理函数 void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) { switch(type) { case WStype_TEXT: // 收到文本消息 // 假设控制端发送JSON格式指令如 {cmd:set, time:300000} // 这里进行简化解析 String message String((char*)payload); if (message.startsWith(SET:)) { // 格式: SET:300000 (设置5分钟) String timeStr message.substring(4); setTime_ms timeStr.toInt(); isRunning false; isPaused false; Serial.println(Time set to: String(setTime_ms) ms); } else if (message START) { if (!isRunning) { startTime_ms millis(); isRunning true; isPaused false; pauseDuration 0; } else if (isPaused) { // 从暂停中恢复 startTime_ms millis() - pauseDuration; isPaused false; } Serial.println(Timer START/RESUME); } else if (message PAUSE) { if (isRunning !isPaused) { pauseDuration millis() - startTime_ms; isPaused true; Serial.println(Timer PAUSED); } } else if (message RESET) { isRunning false; isPaused false; Serial.println(Timer RESET); } // 收到任何指令后立即更新一次显示 updateDisplay(); break; } }这段代码构建了整个系统的基石。webSocketEvent函数是通信枢纽它解析来自网页的控制命令如SET:300000、START并改变计时器的状态机isRunning,isPaused,startTime_ms等。updateDisplay函数则根据当前状态计算并格式化剩余时间将其绘制到OLED屏幕上。这里我特意加入了百分秒显示并在底部标注状态让信息一目了然。4.3 网页控制台设计与实现控制端是一个简单的HTML页面包含几个按钮和一个时间输入框。我们将这个页面直接嵌入到ESP32的固件中作为其提供的一个静态网页服务。这样用户只需在浏览器输入ESP32的IP地址就能看到控制界面。我们需要在ESP32代码中启动一个HTTP服务器并响应对根路径/的请求返回这个HTML页面。同时页面中的JavaScript会与ESP32上的WebSocket服务器端口81建立连接。// 在setup()函数中添加HTTP服务器 #include WebServer.h WebServer server(80); void setup() { // ... 之前的Wi-Fi和屏幕初始化代码 ... // 启动HTTP服务器提供控制页面 server.on(/, HTTP_GET, []() { String html Rrawliteral( !DOCTYPE html html head titleRemote Timer Control/title meta nameviewport contentwidthdevice-width, initial-scale1 style body { font-family: Arial; text-align: center; padding: 20px; } .time-input { font-size: 2em; width: 80%; margin: 20px auto; padding: 10px; text-align: center; } .button { font-size: 1.5em; margin: 10px; padding: 15px 30px; border: none; border-radius: 10px; cursor: pointer; } .set { background-color: #4CAF50; color: white; } .start { background-color: #008CBA; color: white; } .pause { background-color: #ff9800; color: white; } .reset { background-color: #f44336; color: white; } .status { margin-top: 30px; font-size: 1.2em; } /style /head body h1Remote Timer Controller/h1 input typenumber idminutes classtime-input placeholderMinutes min0 value5 : input typenumber idseconds classtime-input placeholderSeconds min0 max59 value0 br button classbutton set onclicksetTime()SET/button button classbutton start onclicksendCmd(START)START/button button classbutton pause onclicksendCmd(PAUSE)PAUSE/button button classbutton reset onclicksendCmd(RESET)RESET/button div idstatus classstatusDisconnected/div script let ws; function connectWebSocket() { // 获取ESP32的IP这里假设页面由ESP32自身提供可以用location.hostname const host location.hostname; ws new WebSocket(ws:// host :81/); ws.onopen function() { document.getElementById(status).innerHTML Connected; document.getElementById(status).style.color green; }; ws.onclose function() { document.getElementById(status).innerHTML Disconnected; document.getElementById(status).style.color red; // 尝试重连 setTimeout(connectWebSocket, 3000); }; ws.onerror function(error) { console.error(WebSocket Error:, error); }; ws.onmessage function(e) { // 可以在这里处理从ESP32发来的状态反馈如果需要 console.log(Message from server:, e.data); }; } function setTime() { const mins parseInt(document.getElementById(minutes).value) || 0; const secs parseInt(document.getElementById(seconds).value) || 0; const totalMs (mins * 60 secs) * 1000; if (totalMs 0 ws ws.readyState WebSocket.OPEN) { ws.send(SET: totalMs); } } function sendCmd(cmd) { if (ws ws.readyState WebSocket.OPEN) { ws.send(cmd); } } // 页面加载时连接WebSocket window.onload connectWebSocket; /script /body /html )rawliteral; server.send(200, text/html, html); }); server.begin(); Serial.println(HTTP server started); // ... 启动WebSocket服务器 ... } void loop() { server.handleClient(); // 处理HTTP请求 webSocket.loop(); // 处理WebSocket事件 // ... 更新显示等 ... }这个网页界面非常直观输入分和秒点击SET按钮时间数据就会通过WebSocket发送给ESP32。START、PAUSE、RESET按钮则发送相应的控制命令。页面底部的状态指示器会显示WebSocket的连接状态。将HTML代码以字符串形式直接写在固件里使用R字面量避免转义符麻烦是最简单的部署方式无需额外的文件系统。5. 深入调试与性能优化让系统坚如磐石代码写完、上传成功屏幕亮起网页也能打开这只是一个开始。要让这个系统真正可靠地运行还需要经过一系列调试和优化。5.1 解决WebSocket连接不稳定问题在实际测试中你可能会发现网页偶尔会断开连接尤其是在网络繁忙或ESP32处理其他任务时。这通常是因为WebSocket的ping/pong心跳机制没有处理好或者ESP32的看门狗定时器WatchDog Timer, WDT因为某个任务阻塞时间过长而重启了系统。启用WebSocket心跳WebSocket库支持心跳包。在setup()中初始化WebSocket后可以启用它webSocket.enableHeartbeat(15000, 3000, 2);。这表示每15秒发送一次ping如果3秒内没收到pong且连续失败2次则认为连接断开。这能帮助及早发现死连接。优化loop()周期确保webSocket.loop()和server.handleClient()被频繁调用。避免在loop()中使用长时间的delay()。我们的updateDisplay()通过millis()进行非阻塞延时是正确做法。注意看门狗ESP32有任务看门狗。如果loop()一次运行时间超过几秒例如在webSocketEvent中执行了非常复杂的字符串处理可能会触发看门狗复位。解决方法是1) 优化代码逻辑避免长时间操作2) 在可能耗时的地方调用yield()或delay(0)让系统处理后台任务3) 使用vTaskDelay替代delay在Arduino环境下需注意。5.2 计时精度与漂移问题我们使用millis()函数来计时它返回自芯片启动以来的毫秒数。millis()本身精度很高但我们的代码逻辑会引入误差。例如我们在loop()中每200ms更新一次显示并计算剩余时间这意味着用户最快也只能每200ms看到一次时间更新。更关键的是millis()会在约50天后溢出归零但对于一个最长几小时的计时器来说这不成问题。然而真正的精度杀手是网络延迟和控制命令处理的不确定性。当你在网页点击START时命令需要经过浏览器JavaScript - 网络传输 - ESP32接收 - 触发webSocketEvent- 设置startTime_ms millis()。这个过程可能有几十到几百毫秒的延迟。对于需要秒级精度的场景如烹饪、实验这是可以接受的。但如果需要更高精度可以考虑以下方案同步时钟让ESP32从网络时间协议NTP服务器获取精确的UTC时间。控制端发送的不是“开始”命令而是“在XX:XX:XX时刻开始计时”。这样双方基于同一个绝对时间基准可以消除命令传输延迟的影响。但这增加了系统复杂度且依赖网络。客户端补偿网页在发送START命令时同时记录本地时间戳。ESP32收到命令后将其本地时间戳和命令一起发回给网页。网页可以计算出网络往返延迟并在显示上做一定补偿。这同样复杂。对于绝大多数应用使用millis()并接受其微小误差是最简单实用的。我的经验是在稳定的Wi-Fi网络下从点击按钮到屏幕开始变化延迟通常在100ms以内对于非科学实验级的定时需求完全足够。5.3 功耗与长期运行考量如果打算用电池供电功耗就是关键。ESP32在全速运行Wi-Fi和点亮屏幕时电流可能达到100mA以上一块2000mAh的电池可能撑不了一天。深度睡眠模式如果不计时时可以让ESP32进入深度睡眠。但这需要外部电路如一个按钮来唤醒它并且会断开Wi-Fi连接远程控制就失效了。这与我们“随时待命”的远程控制需求相悖。优化运行功耗降低CPU频率在setup()中使用setCpuFrequencyMhz(80);将CPU主频从240MHz降到80MHz能显著降低功耗且对于本应用性能完全够用。Wi-Fi模式如果只是局域网内使用可以尝试将Wi-Fi模式设置为WIFI_MODE_STA仅站模式并确保没有开启不必要的服务如蓝牙。屏幕亮度OLED屏幕虽然省电但亮度越高功耗越大。如果环境光不亮可以调低对比度。间歇性工作一个激进但有效的办法是在计时器非运行状态!isRunning让ESP32定期进入轻睡眠Modem Sleep此时Wi-Fi射频关闭CPU暂停功耗可降至20mA以下。可以通过一个定时器中断唤醒醒来后快速检查是否有网络消息然后继续睡眠。这需要更复杂的代码和状态管理。对于插电应用功耗无需过多考虑。但了解这些优化手段有助于你在设计其他电池供电的物联网设备时做出正确决策。6. 功能扩展与进阶玩法基础功能稳定后你可以根据自己的需求将这个项目玩出更多花样。6.1 多计时器与队列管理当前系统只管理一个计时器。你可以扩展状态变量使其成为一个计时器数组或队列。网页控制端可以发送更复杂的JSON指令如{cmd:add, id:1, duration:180000, label:Tea}来添加一个标签为“Tea”的3分钟计时。ESP32按顺序或并行管理这些计时。显示端可以轮流显示各个计时器的状态或者用更大的屏幕同时显示。6.2 声音与视觉提醒计时结束时除了屏幕变化还可以增加提醒。最简单的办法是让ESP32控制一个蜂鸣器或无源喇叭在计时归零时发出“滴滴”声。连接蜂鸣器正极到某个GPIO如GPIO 25负极接GND。在代码中当检测到剩余时间为0且状态为运行中时用tone()函数驱动蜂鸣器响几声。你还可以连接一个RGB LED用不同颜色表示不同状态运行-绿色暂停-黄色结束-红色闪烁。6.3 集成到智能家居平台如果你使用Home Assistant、OpenHAB等平台可以让这个计时器成为其中的一个设备。有两种主要方式MQTT桥接在ESP32代码中增加MQTT客户端功能。它同时连接WebSocket服务器供网页控制和MQTT Broker。当收到网页指令时除了执行还通过MQTT发布一条状态消息到如home/timer/state的主题。Home Assistant订阅这个主题就能获取计时器状态。反之Home Assistant也可以通过向home/timer/cmd主题发布消息来控制计时器。这样你就可以在Home Assistant的仪表盘上看到和控制它甚至用它来触发自动化例如烤箱计时结束自动关闭智能插座。自定义集成对于Home Assistant你可以为其编写一个自定义集成。这个集成本质上是一个Python程序它直接通过你ESP32提供的REST API或WebSocket接口进行通信。这种方式更直接但开发工作量稍大。6.4 使用更漂亮的图形界面当前的网页控制台非常基础。你可以使用Vue.js、React等前端框架或者直接用Bootstrap等UI库打造一个外观更现代、交互更流畅的控制面板。例如添加一个圆形的进度条来可视化剩余时间比例或者用时间线来显示多个计时任务。由于控制逻辑WebSocket通信是独立的你只需要替换HTML/CSS/JavaScript部分ESP32的固件完全不用修改。7. 部署实战与故障排查清单最后我们来谈谈如何将它部署到最终环境以及遇到问题怎么办。7.1 固化部署与外壳制作当你在开发板上调试一切正常后可以考虑制作一个更永久的版本选择最小系统板购买一个ESP32的裸芯片或最小核心板搭配一个稳压模块可以大大缩小体积。设计PCB如果追求极致可以使用EasyEDA或KiCad设计一块简单的PCB将ESP32、OLED、蜂鸣器、电源接口集成在一起看起来更专业。3D打印外壳根据你的PCB或组装好的模块尺寸设计并打印一个外壳。留出屏幕窗口和可能的按钮孔。这不仅美观也能保护电路。电源选择可以使用手机充电宝、旧的USB充电器或者直接接在5V的电源轨上。7.2 常见问题与排查指南即使按照教程操作你也可能会遇到一些问题。这里是一个快速排查清单问题屏幕不亮或乱码。检查接线确认VCC接的是3.3V不是5V确认SDA、SCL接对了引脚GPIO 21, 22。检查I2C地址在代码中display.begin(SSD1306_SWITCHCAPVCC, 0x3C)的0x3C是OLED的常见I2C地址。有些屏幕可能是0x3D。可以运行一个I2C扫描程序来确认地址。检查库确保安装了正确版本的Adafruit SSD1306和GFX库。问题无法连接到Wi-Fi。检查密码和SSID确保代码中的ssid和password正确注意大小写。检查路由器设置有些路由器会设置设备隔离AP Isolation或仅允许特定频段2.4GHz/5GHz。ESP32只支持2.4GHz Wi-Fi请确保你的设备连接到2.4GHz网络并关闭AP隔离。查看串口输出打开Arduino IDE的串口监视器波特率115200查看ESP32启动时的输出信息它会明确告诉你连接到了哪个IP或者为什么连接失败。问题网页能打开但按钮点击没反应状态显示Disconnected。检查IP地址确保浏览器中访问的IP地址是串口监视器里打印出来的ESP32的IP。检查端口WebSocket连接的是ws://IP:81确保端口81没有被防火墙或路由器屏蔽局域网内一般不会。检查浏览器控制台在浏览器中按F12打开开发者工具切换到“Console”控制台标签页。查看是否有JavaScript错误比如“WebSocket connection failed”。这能提供具体的错误原因。检查代码确认webSocket.begin()和server.begin()都被成功执行。问题计时不准感觉走得忽快忽慢。这是正常现象如前所述受网络延迟和loop()周期影响。确保没有在loop()中使用delay()并且webSocket.loop()和server.handleClient()被持续调用。检查溢出逻辑millis()大约50天溢出一次。我们的代码中计算elapsed currentTime - startTime_ms在溢出时依然能正确工作因为unsigned long类型的减法会进行模运算。但如果你在计时器运行期间发生了溢出且逻辑中有比较currentTime startTime_ms的情况就会出错。对于长时间运行建议使用unsigned long的差值比较或者使用millis()的溢出安全比较库。这个项目从构思到实现涉及了硬件连接、嵌入式编程、网络通信和前端交互等多个环节是一个非常好的全栈物联网入门实践。它没有用到特别高深的技术但将各个模块有机组合解决了一个实实在在的生活和工作痛点。我最享受的时刻就是在工作室里用手机轻松设定好3D打印机的预热时间然后继续埋头写代码无需再惦记着时间。希望这个详细的构建指南能帮你打造出属于自己的、独一无二的远程控制计时器。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价