目录
1.前言
之前我做了个利用小爱同学+ESP8266控制电灯的装置:使用小爱同学+ESP8266+舵机控制家里的电灯_斌96的博客-CSDN博客。这里其实单片机就是通过TCP通讯进行控制,其中TCP服务端我们用了一个叫巴法云的云服务平台。
那么有没有可能,我们摒弃这个云服务平台,自己代码手撸一个TCP Server和TCP Client从而进行通讯呢。经历了本周末的稍微研究,我找到了解决方案。
2.控制架构
如下图,其实基本结构和上次的小爱同学类似,这不过这次除了手机的客户端,其他部分的服务我打算自己搭建。
其实决定之前,我考虑过用ESP8266当TCP Server,然后将TCP端口通过Frp内网穿透到公网,这样就可以省去云服务器搭建转发的步骤。但是因为考虑到这样需要给每个设备在内网绑定一个固定的IP地址,还有就是如果多个设备链接到ESP8266的TCP Server会产生冲突,因此作罢。
硬件上因为要考虑在外面也能控制家里的ESP8266设备,所以TCP服务端必须架设在一个公网能访问的服务器上。这里我用的是当年双十一租了三年的腾讯云服务器。软件上使用万能的Python搭建服务。
控制端选用手机随便安装个TCP调试助手就可以了,被控端依旧是熟悉的ESP8266作为执行器。
3.代码
废话不多说,直接上服务端和客户端的源码
服务端:
import socket
import threading
import time
global GlobalStr #转发请求的全局变量
global GlobalFb #反馈状态的全局变量
def deal(conn, client):
global GlobalStr
global GlobalFb
length = len(threading.enumerate()) #线程数量 监控用的
DataFileName = time.strftime('%Y_%m_%d') + '.csv' #创建个CSV格式的日志文件
LogTxt = str(time.strftime('%H:%M:%S')) + "," + client[0] + "," + str(length) + "," #日志文件的内容:当前时间,客户端的IP地址,当前线程个数
DataAll = ''
print(f'新线程开始处理客户端 {client} 的请求数据')
while True:
breakflag = 0
data = conn.recv(1024).decode('UTF-8') # 接收客户端数据并且解码, 一次获取 1024b数据(1k)
print('接收到客户端发送的信息:%s' % data)
# 识别ESP8266的请求 转发tcp报文
if 'ESP8266' == data[0:7]: #规定ESP8266发来的TCP报文前7个字符
GlobalFb = data[7:] #后几位作为ESP8266发来的状态反馈
while True:
if 'bin' == GlobalStr[0:3]: #识别控制端发来的TCP报文
ESP8266Sendstr = GlobalStr[3:] + " " #将控制报文加个空格 让空格作为一条报文的结束
breakflag = 1
GlobalStr = ''
conn.send(ESP8266Sendstr.encode('UTF-8')) #控制报文转发给ESP8266
time.sleep(0.1) #小睡一下
break #退出第一层循环
if breakflag == 1: #用一个Flag退出第二层循环
breakflag = 0
DataAll = "ESP8266Client"
break
# 识别控制端发送的报文转发
DataAll = DataAll + " " + data #记录下TCP链接发来的全部报文
if 'bin' == data[0:3]: #识别TCP报文的前三个字符,防止恶意链接
GlobalStr = data #全局变量GlobalStr转发
data = "已收到:" + data
conn.send(data.encode('UTF-8')) #给控制端一个反馈
elif 'state' == data: # 查询ESP8266的状态反馈
conn.send(GlobalFb.encode('UTF-8'))
print(GlobalFb)
else:
break
conn.close() #关闭TCP链接
#写日志
LogTxt = LogTxt + DataAll + "\n"
with open(DataFileName,"a") as f:
f.write(LogTxt)
# 类型:socket.AF_INET 可以理解为 IPV4
# 协议:socket.SOCK_STREAM
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('0.0.0.0', 4321)) # (client客户端ip, 端口)
server.listen() # 监听
GlobalStr = ''
GlobalFb = ''
while True:
sock, addr = server.accept() # 获得一个客户端的连接(阻塞式,只有客户端连接后,下面才执行)
xd = threading.Thread(target=deal, args=(sock, addr))
xd.start() # 启动一个线程
客户端:
#include <ESP8266WiFi.h>
#include <ESP8266httpUpdate.h>
#include "OwnWifi.h"
const char *ssid = HomeWifiSSID; //wifi名称
const char *password = HomeWifiPasswd; //wifi密码
const uint16_t port = 4321; //服务端的TCP端口
const char *host = "192.168.0.1"; //这里填云服务器的地址
WiFiClient client; //创建一个tcp client连接
int DirtyFlag = 1; //
String StateFb = ""; //初始化反馈的字符串
void setup()
{
pinMode(LED_BUILTIN, OUTPUT); //用ESP8266的板载LED小灯来作为一个指示
digitalWrite(LED_BUILTIN, HIGH); //小灯灭
StateFb = "LED is OFF";
Serial.begin(9600); //串口初始化
Serial.println();
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password); //连接上Wifi
while (WiFi.status() != WL_CONNECTED)
{
delay(500);
Serial.print(".");
}
Serial.println("Connected");
Serial.print("IP Address:");
Serial.println(WiFi.localIP());
}
//一直循环执行:
void loop()
{
Serial.print("connecting to ");
Serial.println(host);
if (!client.connect(host, port)) //连接上TCP Server
{
Serial.println("connection failed");
Serial.println("wait 1 sec...");
delay(1000);
return;
}
String readBuff = ""; //初始化接受的字符串
long Cycle = 0; //初始化Cycle
while (client.connected())
{
if(DirtyFlag) //连接上只发首次
{
client.print("ESP8266" + StateFb); //发送标识ESP8266 + LED状态
Serial.println("Connected");
DirtyFlag = 0;
}
if (client.available()) //如果有可读数据
{
char c = client.read(); //读取一个字节//也可以用readLine()等其他方法
if (c == ' ') //接收到空格认为TCP接受结束
{
client.print("已收到: " + readBuff); //向客户端发送
Serial.println("已收到: " + readBuff); //从串口打印
break; //跳出循环
}
readBuff += c;
Cycle = 0;
}
else
{
Cycle = Cycle + 1; //防止长时间没有数据造成异常 半个小时重连一次
delay(10);
if(Cycle >= 180000)
break;
}
}
client.stop(); //停止TCP服务
DirtyFlag = 1;
if(readBuff == "on")
{
digitalWrite(LED_BUILTIN, LOW); //操作PIN脚开启关闭LED
StateFb = "LED is ON";
}
if(readBuff == "off")
{
digitalWrite(LED_BUILTIN, HIGH);
StateFb = "LED is OFF";
}
}
4.控制效果
那么最终的控制效果就如下图所示,通过TCP指令控制LED灯的亮灭。
还可以查询当前LED灯的状态。
5.参考
本文参考以下博客内容
ESP8266之WiFiClient库学习_RenKaixuan0124的博客-CSDN博客_wificlient文章目录1. 前言2. WiFiClient库2.1 连接相关2.1.1connect —— 建立TCP连接2.1.2 connected —— client连接是否成功2.1.3 stop —— 关闭连接2.1.4 status —— 连接状态2.1.5 write —— 发送数据到服务器端2.1.6 print —— 发送数据到服务器端2.1.7 println —— 发送数据到服务器端2.2 响应相关2.2.1 available —— 返回接收缓存区可读取字节数2.2.2 avdilableForWhttps://blog.csdn.net/qq_41477556/article/details/112366202Python3进阶--Socket编程、多线程(创建方式、线程通信、线程锁、线程池)_鸢尾の的博客-CSDN博客_python socket 多线程Python3进阶--Socket编程、多线程(创建方式、线程通信、线程锁、线程池)
https://blog.csdn.net/weixin_45248492/article/details/124022866