基于esp8266-01s 从云服务器关掉两米外的卧室灯-一路心路历程

从服务器发出指令控制esp8266

疫情期间在家无聊,不如买块esp8266学习学习。这也是自己第一次试着写博客,希望自己能坚持吧。

主要思路

首先,esp8266-01s与linux服务器端通过socket(tcp)互发心跳包,然后在我的网页上有开关灯的按钮,点击按钮后会将请求头发送到上述socket上,socket端识别信号是来自Esp还是网页,再做出相应的决策。
这里要解释一下的是,因为一些原因,我的路由器暂时不能设成公网ip,外网无法访问,在网上搜索了一番后才决定用上述方式。

服务器端响应

要让网页端和Esp同时给服务器端的socket发信号,即把两个客户端connection()到同一端口即可。关于网页端和Esp如何发送请求我会在另一篇博客里详细描述。

服务器这边的操作稍显麻烦,因为涉及到同时应答两个客户端,所以需要多线程。

socket实现多线程可以用socketsever这个库,或者select这个库。但毕竟我是学习为主嘛,以前学多线程的时候也偷了懒,也可以称此机会弥补。

s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
host = "127.0.0.1"
port = 9090
addr_p = (host,port)
s.bind(addr_p)
s.listen(5)
while True:
    conn,addr = s.accept()
    conn.settimeout(30)
    Th = threading.Thread(target=client,args=(conn,))
    Th.start()

这个代码的意思是:每来一个连接,就开一个线程为它服务。为了在线程函数client()里准确的发送数据,我们将表示新连接的文件描述符:conn 传入到client()里,至于具体怎么用,大概地说就是把识别为Esp的那个线程里的conn拷贝到全局变量里,让处理网页端的线程可以使用。
(注:多线程可以访问同一个全局变量,但要注意多个线程修改一个全局变量的话需要加锁)。
代码如下

Esp_conned = 0
conn_dist ={}
def client(conn):
    global  Esp_conned
    global Espconn
    global conn_dist
    Ti = str(threading.current_thread().ident)
    conn_dist[Ti] = conn
    #print (conn_d)
    #print(Ti+"is working")

    while True:
        try:
            data = conn_dist[Ti].recv(1024)
        except:
            try:
                data = conn_dist[Ti].recv(1024)
            except:
                print("can not recv data")
                conn_dist = {}
                Esp_conned = 0
                Espconn = None
                break

        if(data != None):
            if(data[:3] == "GET"):
                if(Esp_conned == 1):
                    Espconn.send("do")
                    print ("com_send_ok")
                else:
                    print("Esp not ready")
                conn_dist[Ti].close()
                conn_dist[Ti] = None
                break

            elif(data[:8] == "from Esp"):
                Espconn = conn_dist[Ti]
                #print (Espconn)
                Esp_conned =1
                Espconn.send("hello Esp")
                print("send to Esp ok")
            else:
                print("send worong"+data)

由于我编程太不规范,可能整体看下来杂乱无章,我挑几个重点讲一下思路把

    Ti = str(threading.current_thread().ident)
    conn_dist[Ti] = conn

这一步超级重要,用threading.current_thread().ident可以获得当前线程的唯一id,创建一个储存id的字典,以当前id为字典的key值,以当前conn为id对应的value,这样就将conn和线程对应起来了。

鉴别模块:

 elif(data[:8] == "from Esp"):
                Espconn = conn_dist[Ti]
                Espconn.send(b"hello Esp")

这一部分意思是,如果收到的data前8位是“from Esp”则判定信息发自于Esp,并将当前的连接符赋给全局变量Espconn,用处马上有讲:

 if(data[:3] == "GET"):
               Espconn.send(b"do")

如果收到的数据前三位是“GET”,就向Esp板子发送“Do”指令,所以通过 Espconn这个媒介,就实现了无轮询延迟的既是响应。

这里解释一下为什么要以“GET”作为判断依据,因为从网页上来的GET请求,它先是要和服务端完成三次握手,第一次握手发送的请求头的前三个字就是“GET”,所以直接根据这点判断他是来自于网页端,这里也不怕谁能连进来,毕竟谁能猜到你的代理端口呢。

至于代码的其余部分就是一些不足挂齿的小细节啦(注:python3的s.recv()收str类型,网页端发的请求头是Byte类型,str类encode()变Byte,Byte类decode()变str类。)

好了,服务器端的代码到这里就结束了,剩下的就是adruino(打对没?)编写应答代码了。

过程中的探索与改进

  • 在服务器端建立socket看能否相应esp的心跳包

    socket通信以连通,有时间了上图片,重点是用nginx做代理。(3.17补充:当时只做了tcp实验,后来发现udp和tcp不能用一个配置文件都搞定)

  • 学习linux的进程间的通信
    (3.17更新)
    重点了解了共享内存(用mmap库实现),但还是无法达到立即响应的要求,因为总要间隔一段时间去读内存。
    后来突发奇想,为什么不直接从网页上点击按钮后就直接socket(udp)连接服务器,这时recvfrom()函数会立即响应,从而完成操作。
    udp发送需要的IP地址写在全局变量里,由ESP板发送心跳包获得。
    (3.18更新)
    经过了解从网页端发送udp到服务器端太难了,发送tcp很容易(发送请求头就是tcp通信)遂把socket换为tcp模式。

  • nginx转发udp
    在nginx中配置stream即可,因为udp方案被放弃所以没啥用了。

adruino处理部分

这一部分内容将在另一篇博客中呈现。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值