从服务器发出指令控制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处理部分
这一部分内容将在另一篇博客中呈现。