利用云服务器和Python架设TCP Server控制ESP8266单片机

目录

1.前言

2.控制架构

3.代码

4.控制效果

5.参考     


1.前言

        之前我做了个利用小爱同学+ESP8266控制电灯的装置:使用小爱同学+ESP8266+舵机控制家里的电灯_斌96的博客-CSDN博客。这里其实单片机就是通过TCP通讯进行控制,其中TCP服务端我们用了一个叫巴法云的云服务平台。

        那么有没有可能,我们摒弃这个云服务平台,自己代码手撸一个TCP Server和TCP Client从而进行通讯呢。经历了本周末的稍微研究,我找到了解决方案。

2.控制架构

       如下图,其实基本结构和上次的小爱同学类似,这不过这次除了手机的客户端,其他部分的服务我打算自己搭建。

利用云服务器和Python架设TCP Server控制ESP8266单片机_第1张图片

       其实决定之前,我考虑过用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 
#include 
#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灯的亮灭。

利用云服务器和Python架设TCP Server控制ESP8266单片机_第2张图片

         还可以查询当前LED灯的状态。

利用云服务器和Python架设TCP Server控制ESP8266单片机_第3张图片

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

你可能感兴趣的:(单片机,服务器,嵌入式硬件,python,tcp/ip)