ESP8266 TCP 实战:设备上报、命令下发与本地 TCP Server
MQTT 适合设备消息,但有些项目只需要一个很轻的 TCP 长连接,例如局域网设备、门禁控制器、扫码器、串口转 Wi-Fi 模块。这里写两个可跑案例:
- ESP8266 作为 TCP Client,连接业务服务端,上报数据并接收命令。
- ESP8266 作为 TCP Server,手机或电脑直接连它控制 LED。
案例一:ESP8266 做 TCP Client
协议设计
使用一行一条消息,\n 结尾:
1 2 3 4
| HELLO id=esp001 PING uptime=10 rssi=-45 TEMP value=26.30 LED value=1
|
这种协议好调试,nc、telnet、Python 都能直接看。
本机测试服务端
先在电脑上启动一个 Python TCP Server:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| import socket import threading import time
HOST = "0.0.0.0" PORT = 9000
def handle(conn, addr): print("client", addr) conn.sendall(b"LED value=1\n")
last = time.time() with conn: buf = b"" while True: data = conn.recv(1024) if not data: print("closed", addr) break
buf += data while b"\n" in buf: line, buf = buf.split(b"\n", 1) print("recv:", line.decode(errors="replace"))
if time.time() - last > 10: conn.sendall(b"LED value=0\n") last = time.time()
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server.bind((HOST, PORT)) server.listen(5) print("listen", HOST, PORT)
while True: conn, addr = server.accept() threading.Thread(target=handle, args=(conn, addr), daemon=True).start()
|
运行:
ESP8266 Client 完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
| #include <ESP8266WiFi.h>
const char* WIFI_SSID = "your-wifi"; const char* WIFI_PASS = "your-password";
const char* SERVER_HOST = "192.168.1.10"; const uint16_t SERVER_PORT = 9000;
const char* DEVICE_ID = "esp001"; const int LED_PIN = D1;
WiFiClient client;
unsigned long lastHeartbeatAt = 0; unsigned long lastTempAt = 0; String inputLine = "";
void connectWiFi() { WiFi.mode(WIFI_STA); WiFi.begin(WIFI_SSID, WIFI_PASS);
Serial.print("wifi connecting"); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
Serial.println(); Serial.print("wifi ip="); Serial.println(WiFi.localIP()); }
bool connectServer() { if (client.connected()) { return true; }
Serial.print("tcp connecting "); Serial.print(SERVER_HOST); Serial.print(":"); Serial.println(SERVER_PORT);
if (!client.connect(SERVER_HOST, SERVER_PORT)) { Serial.println("tcp connect failed"); return false; }
client.setNoDelay(true); client.print("HELLO id="); client.print(DEVICE_ID); client.print("\n"); return true; }
void sendHeartbeat() { client.print("PING uptime="); client.print(millis() / 1000); client.print(" rssi="); client.print(WiFi.RSSI()); client.print("\n"); }
void sendTemperature() { float value = 24.0 + (millis() % 1000) / 100.0; client.print("TEMP value="); client.print(value, 2); client.print("\n"); }
int readValue(String line, String key) { int pos = line.indexOf(key + "="); if (pos < 0) { return -1; }
int start = pos + key.length() + 1; int end = line.indexOf(' ', start); if (end < 0) { end = line.length(); }
return line.substring(start, end).toInt(); }
void handleLine(String line) { line.trim(); if (line.length() == 0) { return; }
Serial.print("server: "); Serial.println(line);
if (line.startsWith("LED ")) { int value = readValue(line, "value"); digitalWrite(LED_PIN, value == 1 ? HIGH : LOW);
client.print("ACK LED value="); client.print(value); client.print("\n"); } }
void readServer() { while (client.connected() && client.available()) { char ch = client.read(); if (ch == '\n') { handleLine(inputLine); inputLine = ""; } else if (ch != '\r') { inputLine += ch; if (inputLine.length() > 128) { inputLine = ""; } } } }
void setup() { Serial.begin(115200); pinMode(LED_PIN, OUTPUT); digitalWrite(LED_PIN, LOW); connectWiFi(); }
void loop() { if (WiFi.status() != WL_CONNECTED) { connectWiFi(); }
if (!connectServer()) { delay(2000); return; }
readServer();
unsigned long now = millis(); if (now - lastHeartbeatAt > 5000) { lastHeartbeatAt = now; sendHeartbeat(); }
if (now - lastTempAt > 3000) { lastTempAt = now; sendTemperature(); } }
|
运行效果
Python 服务端会看到:
1 2 3 4 5 6
| client ('192.168.1.33', 53112) recv: HELLO id=esp001 recv: ACK LED value=1 recv: TEMP value=25.20 recv: PING uptime=6 rssi=-47 recv: TEMP value=27.81
|
设备串口会看到:
1 2 3 4
| wifi ip=192.168.1.33 tcp connecting 192.168.1.10:9000 server: LED value=1 server: LED value=0
|
案例二:ESP8266 做 TCP Server
这个场景适合局域网内直接控制设备,例如电脑连接 ESP8266 后发送 ON、OFF、STATUS。
ESP8266 Server 代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
| #include <ESP8266WiFi.h>
const char* WIFI_SSID = "your-wifi"; const char* WIFI_PASS = "your-password";
const int LED_PIN = D1; const uint16_t LISTEN_PORT = 9001;
WiFiServer server(LISTEN_PORT); WiFiClient client; String line = "";
void connectWiFi() { WiFi.mode(WIFI_STA); WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
Serial.println(); Serial.print("server ip="); Serial.println(WiFi.localIP()); }
void replyStatus(WiFiClient& c) { c.print("STATUS led="); c.print(digitalRead(LED_PIN) == HIGH ? 1 : 0); c.print(" rssi="); c.print(WiFi.RSSI()); c.print("\n"); }
void handleCommand(WiFiClient& c, String cmd) { cmd.trim(); cmd.toUpperCase();
if (cmd == "ON") { digitalWrite(LED_PIN, HIGH); c.print("OK LED ON\n"); } else if (cmd == "OFF") { digitalWrite(LED_PIN, LOW); c.print("OK LED OFF\n"); } else if (cmd == "STATUS") { replyStatus(c); } else { c.print("ERR unknown command\n"); } }
void setup() { Serial.begin(115200); pinMode(LED_PIN, OUTPUT); digitalWrite(LED_PIN, LOW);
connectWiFi(); server.begin(); server.setNoDelay(true); }
void loop() { if (!client || !client.connected()) { client = server.available(); if (client) { client.print("ESP8266 READY\n"); } return; }
while (client.available()) { char ch = client.read(); if (ch == '\n') { handleCommand(client, line); line = ""; } else if (ch != '\r') { line += ch; if (line.length() > 64) { line = ""; client.print("ERR line too long\n"); } } } }
|
用 netcat 控制
输入:
返回:
1 2 3 4 5
| ESP8266 READY STATUS led=0 rssi=-52 OK LED ON STATUS led=1 rssi=-51 OK LED OFF
|
TCP 项目常见坑
什么时候选 TCP,不选 MQTT
选 TCP 的场景:
- 协议已经由设备厂商规定。
- 局域网内点对点控制,不需要 Broker。
- 需要和现有 TCP 网关对接。
- 设备消息格式非常简单,后端愿意自己维护连接状态。
选 MQTT 的场景:
- 设备很多。
- 需要离线状态、主题权限、保留消息、共享订阅。
- App、Web、服务端都要订阅设备状态。
- 希望 Broker 处理连接、订阅和消息分发。