前言 很早之前就想做一个这样的装置,但一直没有实现,因为硬件一直没加技能点
环境和硬件
宿舍wifi需要门户认证 ,不认证就没网,但能分配到ip,使用上文 的iphone作为中转,就可以实现上网
ESP8266带cp2102模块 + MG996r舵机 +一个已经认证过的iphone + 任意一个充电宝
思路 ESP8266的代码很简单,问题在于网络部分 ,如果只想看成品请跳转
想法1 使用frp直接通信,iphone作为frp客户端,有公网ip的服务器作为服务端,把开门端口暴露到公网上,只需要向这个服务器的特定端口发包即可直接开门
这是最简单的 ,但最开始我用frp收到的数据含有乱码,解决未果于是使用下一个思路
最后解决了frp乱码问题,直接使用了此方案,后面的最终版用抓包分析出校园网门户的登录请求,用esp8266登陆了校园网,可以访问外网了,直接使用了云平台通信 。如不想看别的思路请跳转
想法2 如下图
左边的ARUBA为我校园网的主路由
右边的nonebot_pc为我的qq机器人,之后会作为开门的一个渠道
最上面server的是我的阿里云服务器
中间的路由代表广域网
我想把ESP8266作为客户端,连接已经认证过的iphone,这样数据就能通信到广域网了
想法很单纯,但很难实现
我最开始是ESP8266做客户端,iphone做服务端
同时iphone做客户端和阿里云通信
pc作为客户端和阿里云通信,发送开门信号到阿里云服务器上
阿里云服务器收到信号后,在将命令传到已经建立好tcp连接的客户端iphone上
iphone再把数据传到已经建立好tcp连接的esp8266上实现开门
这样做有一个弊端,需要处理两次断开的链接,分别在阿里云和iphone之间处理断开的链接
在iphone和esp8266之间处理断开的链接
那问题就很大了
在硬刚了600行代码以后,还是没法处理断开的链接和重连机制,我决定换一个思路
想法3 这是最可行 的
ESP8266作为服务器
iphone作为客户端
阿里云作为服务器
只需要处理一次连接断开,iphone收到包直接向ESP8266发包,不用处理断开问题
但我写到一半发现了frp乱码的问题解决,弃用了此方案
姑且也贴一下代码
esp #include <Servo.h> #include <ESP8266WiFi.h> int LED = LED_BUILTIN ; const char * ssid = "WHUT-DORM" ; const int servoPin = D2; Servo myservo; WiFiServer server (80 ) ; void setup () { pinMode (LED, OUTPUT ); digitalWrite (LED, HIGH ); pinMode (servoPin, OUTPUT ); myservo.attach (servoPin, 500 , 2500 ); Serial .begin (115200 ); WiFi .begin (ssid); myservo.write (0 ); Serial .print ("Connecting to WiFi..." ); Serial .println (ssid); while (WiFi .status () != WL_CONNECTED) { digitalWrite (LED, LOW ); delay (1000 ); digitalWrite (LED, HIGH ); } for (int i = 0 ;i<5 ;i++){ digitalWrite (LED, LOW ); delay (1000 ); digitalWrite (LED, HIGH ); } Serial .println ("Connected to WiFi" ); Serial .print ("ESP8266's IP is" ); Serial .println (WiFi .localIP ()); server.begin (); } void loop () { WiFiClient client = server.available (); if (client){ Serial .println ("New client connected!" ); String req = client.readStringUntil ('\r' ); Serial .print ("Received data:" ); Serial .println (req); if (req=="a" ){ digitalWrite (LED, LOW ); client.print ("1" ); myservo.write (180 ); delay (5000 ); myservo.write (0 ); digitalWrite (LED, HIGH ); } else { client.print ("2" ); } client.flush (); } }
import socketimport datetimeif __name__ == '__main__' : tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) tcp_client_socket.connect(("pursuecode.cn" , 50001 )) door_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) door_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True ) door_server_socket.bind(("0.0.0.0" , 7777 )) door_server_socket.listen(128 ) door_server_conn_socket, ip_port = door_server_socket.accept() while True : recv_data = tcp_client_socket.recv(1024 ) data = recv_data.decode() now_time = str (datetime.datetime.now())[:-7 ] print (f"{now_time} \t收到服务器消息{data} " ) if data == "a" : door_server_conn_socket.send("a" .encode()) data = door_server_conn_socket.recv(1 ).decode() print (data) if data == "1" : tcp_client_socket.send("OK" .encode()) else : tcp_client_socket.send("NO" .encode()) conn_socket.close()
import socketimport datetimeif __name__ == '__main__' : robot_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) robot_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True ) robot_server_socket.bind(("0.0.0.0" , 50000 )) robot_server_socket.listen(128 ) door_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) door_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True ) door_server_socket.bind(("0.0.0.0" , 50001 )) door_server_socket.listen(128 ) while True : door_server_conn_socket, ip_port = door_server_socket.accept() while True : try : robot_server_conn_socket, ip_port = robot_server_socket.accept() data = robot_server_conn_socket.recv(1024 ).decode() now_time = str (datetime.datetime.now())[:-7 ] print (f"{now_time} \t收到机器人消息{data} " ) if data == "open the door!" : door_server_conn_socket.send("a" .encode()) print (f"{now_time} \t发送开门指令" ) robot_result = door_server_conn_socket.recv(1 ).decode() print (f"{now_time} \t收到单片机消息{robot_result} " ) if robot_result == "O" : robot_server_conn_socket.send("OK" .encode()) else : robot_server_conn_socket.send("NO" .encode()) else : robot_server_conn_socket.send("FUCK YOU" .encode()) except Exception as e: now_time = str (datetime.datetime.now())[:-7 ] print (f"{now_time} \t单片机离线,消息发送失败" )
from nonebot import on_commandfrom nonebot.permission import SUPERUSERfrom .config import Configimport socketopen_door_command = on_command("开门" , permission=SUPERUSER, priority=1 ) @open_door_command.handle() async def open_door_command_handler (): tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try : tcp_client_socket.connect(("pursuecode.cn" , 50000 )) tcp_client_socket.settimeout(10 ) tcp_client_socket.send("open the door!" .encode()) recv_data = tcp_client_socket.recv(2 ) tcp_client_socket.close() data = recv_data.decode() except ConnectionRefusedError as e: await open_door_command.finish("服务器离线" ) except Exception as e: await open_door_command.finish(str (e)) print (data) if data == "1" : await open_door_command.finish("已开门" ) elif data == "0" : await open_door_command.finish("开门失败" ) else : await open_door_command.finish("单片机离线" )
iphone 编译frpc 由于之前在文章里编译过相应的go二进制,frpc可以直接套用
环境请见ios编译go二进制
git clone https://github.com/fatedier/frp.git cd frp/cmd/frpcgo build -o frpc
拷贝到越狱后的iphone上
chmod a+x ./frpcldid -S ./frpc
解决乱码问题 如果带上proxy_protocol_version = v2
那么一定会出现乱码
测试程序如下
import socketimport datetimeif __name__ == '__main__' : tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True ) tcp_server_socket.bind(("0.0.0.0" , 8888 )) tcp_server_socket.listen(1 ) tcp1, addr = tcp_server_socket.accept() print (tcp1.recv(1024 )) tcp1.close()
import socketimport datetimeif __name__ == '__main__' : tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True ) tcp_server_socket.connect(("xxx" , 50000 )) tcp_server_socket.send("qweqwe" .encode()) tcp_server_socket.close()
输出如下
haihaihaideiphone:~/python root# python3 server.py b'\r\n\r\n\x00\r\nQUIT\n!\x11\x00\x0cq9\xedM\xc0\xa8\x01\x05\xbah\xc3P'
使用配置
[common] server_addr = domain server_port = 43399 token = xxxxxxx tls_enable = true tls_cert_file = /etc/frp/fullchain.certls_key_file = /etc/frp/xxx.keytls_trusted_ca_file = /etc/frp/ca.ceruse_recover = true login_fail_exit = false [door] type = tcplocal_ip = 10.91 .20.79 local_port = 80 remote_port = 50001
#include <Servo.h> #include <ESP8266WiFi.h> int LED = LED_BUILTIN ; const char * ssid = "WHUT-DORM" ; const int servoPin = D2; Servo myservo; WiFiServer server (80 ) ; void setup () { pinMode (LED, OUTPUT ); digitalWrite (LED, HIGH ); pinMode (servoPin, OUTPUT ); myservo.attach (servoPin, 500 , 2500 ); Serial .begin (115200 ); WiFi .begin (ssid); myservo.write (0 ); Serial .print ("Connecting to WiFi..." ); Serial .println (ssid); while (WiFi .status () != WL_CONNECTED) { digitalWrite (LED, LOW ); delay (1000 ); digitalWrite (LED, HIGH ); } for (int i = 0 ;i<5 ;i++){ digitalWrite (LED, LOW ); delay (1000 ); digitalWrite (LED, HIGH ); } Serial .println ("Connected to WiFi" ); Serial .print ("ESP8266's IP is" ); Serial .println (WiFi .localIP ()); server.begin (); } void loop () { WiFiClient client = server.available (); if (client){ Serial .println ("New client connected!" ); String req = client.readStringUntil ('\r' ); Serial .print ("Received data:" ); Serial .println (req); if (req=="a" ){ digitalWrite (LED, LOW ); client.print ("1" ); myservo.write (180 ); delay (5000 ); myservo.write (0 ); digitalWrite (LED, HIGH ); } else { client.print ("2" ); } client.flush (); } }
iphone只需要开启frpc,然后从公网xxx:50001访问就能开机
from nonebot import on_commandfrom nonebot.permission import SUPERUSERfrom .config import Configimport socketopen_door_command = on_command("开门" , permission=SUPERUSER, priority=1 ) @open_door_command.handle() async def open_door_command_handler (): tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try : tcp_client_socket.connect(("wwww" , 50001 )) tcp_client_socket.settimeout(10 ) tcp_client_socket.send("a\r" .encode()) recv_data = tcp_client_socket.recv(2 ) tcp_client_socket.close() data = recv_data.decode() except ConnectionRefusedError as e: await open_door_command.finish("服务器离线" ) except Exception as e: await open_door_command.finish(str (e)) print (data) if data == "1" : await open_door_command.finish("已开门" ) elif data == "0" : await open_door_command.finish("开门失败" ) else : await open_door_command.finish("单片机离线" )
server.php <?php header ('Content-Type:application/json; charset=utf-8' ); $password = $_POST ['pwd' ]; if ($password == "your_password" ){ $socket = socket_create (AF_INET, SOCK_STREAM, SOL_TCP); socket_connect ($socket , "xxx" , "50001" ); socket_write ($socket , "a\r" , 2 ); $recv_data = socket_read ($socket , 1 ); socket_close ($socket ); http_response_code (200 ); if ($recv_data =="1" ){ $recv_data = "开门成功" ; } else { $recv_data = "开门失败" ; } $json = json_encode (array ("code" =>200 , "message" =>$recv_data )); exit ($json ); } else { http_response_code (401 ); $json = json_encode (array ("code" =>401 , "message" =>"密码错误" )); exit ($json ); } ?>
<!DOCTYPE html > <html style ="height: 100%" > <head > <meta http-equiv ="Content-Type" content ="text/html; charset=utf-8" /> <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > 门锁页面</title > </head > <style > html , body { height : 100% ; margin : 0 ; padding : 0 ; } .container { display : flex; justify-content : center; align-items : center; height : 100% ; } #door_form { height : 100px ; } </style > <script src ="https://code.jquery.com/jquery-3.7.1.min.js" type ="text/javascript" > </script > <script > window .onload = function ( ) { var element = document .getElementById ('door_form' ); var yOffset = element.getBoundingClientRect ().top + window .pageYOffset ; window .scrollTo ({ top : yOffset, behavior : 'smooth' }); }; </script > <body > <div class ="container" > <form name ="input" id ="door_form" > 开门密码: <input type ="password" name ="pwd" id ="pwd" > <input type ="submit" value ="开门" > </form > </div > <script > $("#door_form" ).submit (function (event ){ var formData = $(this ).serialize (); event.preventDefault (); $.ajax ({ url : "door_identify.php" , type : "POST" , data : formData, success : function (data ){ alert (data.message ); }, error : function (xhr ){ alert (xhr.responseJSON .message ); } }) }) </script > </body > </html >
效果如下
第一次开门
QQ开门
扫码输密码开门