-
Notifications
You must be signed in to change notification settings - Fork 0
/
search.xml
243 lines (215 loc) · 97.8 KB
/
search.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>Hello World</title>
<url>/2023/06/10/hello-world/</url>
<content><![CDATA[<p>Welcome to <a href="https://hexo.io/">Hexo</a>!</p>
<span id="more"></span>
<p>Welcome to <a href="https://hexo.io/">Hexo</a>! This is your very first post. Check <a href="https://hexo.io/docs/">documentation</a> for more info. If you get any problems when using Hexo, you can find the answer in <a href="https://hexo.io/docs/troubleshooting.html">troubleshooting</a> or you can ask me on <a href="https://github.com/hexojs/hexo/issues">GitHub</a>.</p>
<h2 id="Quick-Start"><a href="#Quick-Start" class="headerlink" title="Quick Start"></a>Quick Start</h2><h3 id="Create-a-new-post"><a href="#Create-a-new-post" class="headerlink" title="Create a new post"></a>Create a new post</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ hexo new <span class="string">"My New Post"</span></span><br></pre></td></tr></table></figure>
<p>More info: <a href="https://hexo.io/docs/writing.html">Writing</a></p>
<h3 id="Run-server"><a href="#Run-server" class="headerlink" title="Run server"></a>Run server</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ hexo server</span><br></pre></td></tr></table></figure>
<p>More info: <a href="https://hexo.io/docs/server.html">Server</a></p>
<h3 id="Generate-static-files"><a href="#Generate-static-files" class="headerlink" title="Generate static files"></a>Generate static files</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ hexo generate</span><br></pre></td></tr></table></figure>
<p>More info: <a href="https://hexo.io/docs/generating.html">Generating</a></p>
<h3 id="Deploy-to-remote-sites"><a href="#Deploy-to-remote-sites" class="headerlink" title="Deploy to remote sites"></a>Deploy to remote sites</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ hexo deploy</span><br></pre></td></tr></table></figure>
<p>More info: <a href="https://hexo.io/docs/one-command-deployment.html">Deployment</a></p>
]]></content>
</entry>
<entry>
<title>【esp32】micropython实现自动登录校园网</title>
<url>/2023/06/16/%E3%80%90esp32%E3%80%91%E8%87%AA%E5%8A%A8%E7%99%BB%E5%BD%95%E6%A0%A1%E5%9B%AD%E7%BD%91/</url>
<content><![CDATA[<p>上回书说到,笔者通过python在电脑上成功实现了自动登录校园网的脚本。在这之后,笔者开始思考如何在esp32上实现这一功能,并且实现一个可以无需依赖手机热点的物联网小项目。本文将首先介绍esp32联网所涉及的模块,并且对各个模块的用法进行阐释,再回顾一下登录校园网脚本的步骤,最后总结一下整个过程中的与出现的问题与解决办法并将各个部分的代码整合起来给出完整的示例代码。</p>
<span id="more"></span>
<h1 id="一、涉及模块"><a href="#一、涉及模块" class="headerlink" title="一、涉及模块"></a>一、涉及模块</h1><p>esp连接校园网需要用到network模块进行wifi的连接,并通过urequests模块进行http数据收发,utime模块进行超时检测。</p>
<h2 id="1-network模块"><a href="#1-network模块" class="headerlink" title="1. network模块"></a>1. network模块</h2><p>esp32c3模块提供了wifi模块并且可以通过调用network模块进行wifi的连接。esp32提供的网络接口有两种,分别是<code>network.STA_IF</code>与<code>network.AP_IF</code>,其中前者是station接口的简称,用于连接其他wifi,后者是access point的简称,用于开启热点并与其他wifi client建立连接。</p>
<p>关于wlan类有以下几个比较重要的函数:</p>
<blockquote>
<p>class network.WLAN(interface_id) # 用于获取创建wlan接口对象</p>
</blockquote>
<blockquote>
<p>WLAN.active([is_active]) # 获取或设置wlan接口激活状态</p>
</blockquote>
<blockquote>
<p>WLAN.connect(ssid=None, key=None, *, bssid=None) # 连接到指定无线网络</p>
</blockquote>
<blockquote>
<p>WLAN.disconnect() # 断开当前无线网络连接</p>
</blockquote>
<blockquote>
<p>WLAN.scan() # 在STA接口上扫描可用的无线网络</p>
</blockquote>
<blockquote>
<p>WLAN.status([param]) # 返回无线连接的状态</p>
</blockquote>
<blockquote>
<p>WLAN.isconnected() # 返回是否与其他无线网络建立连接状态</p>
</blockquote>
<blockquote>
<p>WLAN.ifconfig([(ip, subnet, gateway, dns)]) # 获取IP级网络接口参数(一个包含IP 地址、子网掩码、网关和 DNS 服务器的元组)</p>
</blockquote>
<blockquote>
<p>WLAN.config(‘param’) # 获取或设置通用网络接口参数</p>
</blockquote>
<p>更多有关wlan类的函数和解释可以查看<a href="https://docs.micropython.org/en/latest/library/network.WLAN.html">micropython文档中的wlan类介绍</a>。</p>
<p>此处我们用到的是station接口,先获取接口示例<code>wlan = network.WLAN(network.STA_IF)</code> ,随后激活wifi并进行连接,并且利用utime记录时间防止连接超时。最后利用<code>ifconfig()</code>函数获取本地ip。</p>
<p>在连接好wifi后还需要进行是否能够接入互联网的检测,在python中可以利用subprocess模块去ping一个公网地址,不过在micropython中既没有subprocess也没有ping的功能,笔者想到通过向某个网站发送一个http请求并且分析返回内容是否正确的方法来确认是否成功连接互联网。</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">ping</span>():</span><br><span class="line"> rq = requests.get(<span class="string">"http://www.example.com"</span>) <span class="comment"># 向example.com发送get请求</span></span><br><span class="line"> <span class="keyword">if</span> rq.status_code != <span class="number">200</span> <span class="keyword">or</span> <span class="string">"Example Domain"</span> <span class="keyword">not</span> <span class="keyword">in</span> rq.text: <span class="comment"># 分析返回内容是否正确</span></span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"Unconnected to the Internet!"</span>)</span><br><span class="line"> rq.close() <span class="comment"># 注意此处有必要关闭response对象,同时开启过多的response对象会导致报错OSError 32</span></span><br><span class="line"> <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"Connected to the Internet!"</span>)</span><br><span class="line"> rq.close()</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">True</span></span><br></pre></td></tr></table></figure>
<h2 id="2-urequests模块"><a href="#2-urequests模块" class="headerlink" title="2. urequests模块"></a>2. urequests模块</h2><p>urequests网络请求模块与python中requests模块的使用方法比较像,可以实现后者大部分的功能,不过仍然有一定的不同。下面给出urequests中比较重要的函数。</p>
<blockquote>
<p>urequests.request(method, url, data=None, json=None, headers={})<br>urequests.head(url, **kw)<br>urequests.get(url, **kw)<br>urequests.post(url, **kw)<br>urequests.patch(url, **kw)<br>urequests.delete(url, **kw)</p>
</blockquote>
<p>先回顾一下requests的使用方法:</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">get</span>(<span class="params">url, params=<span class="literal">None</span>, **kwargs</span>)</span><br></pre></td></tr></table></figure>
<p><code>requests.get()</code>支持把字典作为参数传给<code>params</code>,requests会自动将字典转化成字符串的形式。这个操作也可以通过<code>urllib.parse.urlencode()</code>来完成,不过micropython中没有内置的urllib,第三方库的urllib也是出现了各种问题,导致必须手动实现一个<code>urlencode()</code>函数或者把所有的字典预处理成字符串。</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">post</span>(<span class="params">url, data=<span class="literal">None</span>, json=<span class="literal">None</span>, **kwargs</span>)</span><br></pre></td></tr></table></figure>
<p>与<code>get()</code>类似,<code>requests.post()</code>支持把字典传给<code>data</code>参数,不过micropython不允许这样做,所以必须先把字典处理成字符串再传入。其实这里讲字符串是不准确的,因为把str类型传给<code>params</code>和<code>data</code>时会报错<code>Error: Object with buffer protocol required</code>,<strong>正确的操作是传入一个bytes类型的字符串</strong>。</p>
<p>micropython除了不支持对字典进行url编码、对参数类型要求格外严苛之外,还有几处很奇妙的不同。<br>其一是micropython的requests模块的所有函数都会**自动向headers中添加一项”Content-Length”**,注意是自动!这个特性令我痛苦的debug了好久,明明headers和data都是完全正确的,post数据包加了content-length就是Bad Request 400,get数据包啥都不加都返回错误码Internal Server Error 500(<del>真不知道学校的垃圾服务器为什么会对有问题的数据包返回一个500</del>),也不能靠抓包来细致的查看是发送的哪个环节出现了问题。最后还好在网上找到了<a href="https://pypi.org/project/micropython-urequests/#:~:text=urllib.urequest%20implements%20a%20subset%20of%20API%20CPython%20standard,doesn%E2%80%99t%20support%20automatic%20redirects%20or%20chunked%20transfer%20encoding.">urequests的源代码</a>,直接搬下来一行行纠错,发现原来会自动添加正文长度。</p>
<p>其二是micropython不支持gbk,所以所有的encode、decode函数都失效,所以<code>'字符串'.encode('gbk')</code>实际上返回的还是一个utf-8编码的bytes。本来这点没什么,但是有意思的在下面。学校的辣鸡网络通是用gbk编码的,然后urequests的<code>response.text()</code>是这样实现的:</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">text</span>(<span class="params">self</span>):</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">str</span>(self.content, self.encoding)</span><br></pre></td></tr></table></figure>
<p>然后这<del>两坨答辩</del>就碰撞出了美妙而几近天衣无缝的bug:一个返回gbk,一个没法解码gbk,直接报错<code>'utf-8' codec can't decode byte 0xb5 in position 4: invalid start byte</code> ,并且还定位不到错误位置,几乎无解。最后还是通过更改urequests源代码,把encoding去掉的方式解决的。</p>
<p>更多有关requests库和urequests的内容可以查看<a href="https://requests.readthedocs.io/projects/cn/zh_CN/latest/">requests官方文档</a>和<a href="https://makeblock-micropython-api.readthedocs.io/zh/latest/public_library/Third-party-libraries/urequests.html">micropython文档中对urequests的描述</a>。笔者更改过后的urequests模块源代码放在<a href="urequests.rar">这里</a>。</p>
<h1 id="二、校园网登录"><a href="#二、校园网登录" class="headerlink" title="二、校园网登录"></a>二、校园网登录</h1><p>利用python实现的校园网自动登录脚本详见<a href="http://bonjir.life/2023/06/12/%E3%80%90python%E3%80%91%E8%87%AA%E5%8A%A8%E7%99%BB%E5%BD%95%E4%B8%AD%E7%A7%91%E5%A4%A7%E6%A0%A1%E5%9B%AD%E7%BD%91/">上一篇文章</a>,这里只做简单的回顾。</p>
<p>校园网自动登录有两种方式,webdriver模拟浏览器法和requests模拟请求法,其中前者并不受micropython的支持,因此想要实现模拟登陆只能通过urequests模块。校园网络通登录并连接互联网需要进行两次请求,先进行一次post以提交用户信息并让服务端记录本地ip,再发送一次get请求开通网络。</p>
<p>post与get的请求头都通过浏览器抓包获取并且制作成字典,数据正文则可以在浏览器抓包后点击view source按钮,然后复制正文直接放进字符串。</p>
<p><img src="/2023/06/16/%E3%80%90esp32%E3%80%91%E8%87%AA%E5%8A%A8%E7%99%BB%E5%BD%95%E6%A0%A1%E5%9B%AD%E7%BD%91/viewsource.png" alt="view source"></p>
<h1 id="三、实现代码"><a href="#三、实现代码" class="headerlink" title="三、实现代码"></a>三、实现代码</h1><p>这样就可以就可以让esp32成功登陆校园网啦!</p>
<p><img src="/2023/06/16/%E3%80%90esp32%E3%80%91%E8%87%AA%E5%8A%A8%E7%99%BB%E5%BD%95%E6%A0%A1%E5%9B%AD%E7%BD%91/result.png" alt="result"></p>
<p>下面放出实现代码</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> network</span><br><span class="line"><span class="keyword">import</span> requests</span><br><span class="line"><span class="keyword">import</span> utime</span><br><span class="line"><span class="keyword">import</span> ujson</span><br><span class="line"><span class="keyword">import</span> re</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Wifi</span>(<span class="title class_ inherited__">object</span>):</span><br><span class="line"> </span><br><span class="line"> CONNECT_TIMEOUT = <span class="number">5000</span> <span class="comment"># ms</span></span><br><span class="line"> </span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">connect</span>(<span class="params">self, ssid, password</span>):</span><br><span class="line"> <span class="comment">#获取站点WiFi接口的实例并将其存储在变量上</span></span><br><span class="line"> self.wifi = network.WLAN(network.STA_IF)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> self.wifi.isconnected() == <span class="literal">True</span>:</span><br><span class="line"> self.wifi.disconnect()</span><br><span class="line"> </span><br><span class="line"> <span class="comment">#使用存储在前文所述的变量中的凭证来激活网络接口并执行实际连接。</span></span><br><span class="line"> self.wifi.active(<span class="literal">True</span>)</span><br><span class="line"> self.wifi.connect(ssid, password)</span><br><span class="line"></span><br><span class="line"> connect_time = utime.ticks_ms()</span><br><span class="line"> <span class="keyword">while</span> self.wifi.isconnected() == <span class="literal">False</span>:</span><br><span class="line"> <span class="keyword">if</span> utime.ticks_ms() - connect_time >= self.CONNECT_TIMEOUT:</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"Connect time out!"</span>)</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> <span class="keyword">pass</span></span><br><span class="line"> </span><br><span class="line"> self.ipaddr = self.wifi.ifconfig()[<span class="number">0</span>]</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"Connection successful, IP Address: {}"</span>.<span class="built_in">format</span>(self.ipaddr))</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">True</span></span><br><span class="line"> </span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">disconnect</span>(<span class="params">self</span>):</span><br><span class="line"> self.wifi.disconnect()</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">ping</span>():</span><br><span class="line"> rq = requests.get(<span class="string">"http://www.example.com"</span>)</span><br><span class="line"> <span class="keyword">if</span> rq.status_code != <span class="number">200</span> <span class="keyword">or</span> <span class="string">"Example Domain"</span> <span class="keyword">not</span> <span class="keyword">in</span> rq.text:</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"Unconnected to the Internet!"</span>)</span><br><span class="line"> rq.close()</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"Connected to the Internet!"</span>)</span><br><span class="line"> rq.close()</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">True</span></span><br><span class="line"> </span><br><span class="line"><span class="keyword">class</span> <span class="title class_">CampusWifi</span>(<span class="title class_ inherited__">Wifi</span>):</span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, ssid, password, userid, userpassword</span>):</span><br><span class="line"> self.ssid = ssid</span><br><span class="line"> self.password = password</span><br><span class="line"> self.uid = userid</span><br><span class="line"> self.upassword = userpassword</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">connect</span>(<span class="params">self</span>):</span><br><span class="line"> ret = <span class="built_in">super</span>().connect(self.ssid, self.password)</span><br><span class="line"> <span class="keyword">if</span> <span class="keyword">not</span> ret:</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"Fail to connect to campus wifi!"</span>)</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line"> </span><br><span class="line"> Wifi.ping()</span><br><span class="line"> </span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"Logging into campus wifi·····"</span>)</span><br><span class="line"> ret = self._login()</span><br><span class="line"> <span class="keyword">if</span> ret:</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"Login Success!"</span>)</span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line"></span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"Submmitting·····"</span>)</span><br><span class="line"> ret = self._submit()</span><br><span class="line"> <span class="keyword">if</span> ret:</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"Submit Success!"</span>)</span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line"></span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"Testing for the Internet·····"</span>)</span><br><span class="line"> Wifi.ping()</span><br><span class="line"> </span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"Logging out·····"</span>)</span><br><span class="line"> ret = self._logout()</span><br><span class="line"> <span class="keyword">if</span> ret:</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"Logout Success!"</span>)</span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line"> </span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"Testing for the Internet·····"</span>)</span><br><span class="line"> Wifi.ping()</span><br><span class="line"> </span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> <span class="literal">True</span></span><br><span class="line"> </span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">_login</span>(<span class="params">self</span>):</span><br><span class="line"> login_url = <span class="string">"http://202.38.64.59/cgi-bin/ip"</span></span><br><span class="line"> login_data = <span class="string">"cmd=login&url=URL&ip={ip}&name={uid}&password={upassword}&go=%B5%C7%C2%BC%D5%CA%BB%A7"</span>.<span class="built_in">format</span>(ip = self.ipaddr, uid = self.uid, upassword = self.upassword)</span><br><span class="line"> login_headers = {</span><br><span class="line"> <span class="string">"Accept"</span>: <span class="string">"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"</span>,</span><br><span class="line"> <span class="string">"Accept-Encoding"</span>: <span class="string">"gzip, deflate"</span>,</span><br><span class="line"> <span class="string">"Accept-Language"</span>: <span class="string">"zh-CN,zh;q=0.9"</span>,</span><br><span class="line"> <span class="string">"Cache-Control"</span>: <span class="string">"max-age=0"</span>,</span><br><span class="line"> <span class="string">"Connection"</span>: <span class="string">"keep-alive"</span>,</span><br><span class="line"> <span class="comment">#"Content-Length": str(len_test), #str(len(login_data)),</span></span><br><span class="line"> <span class="string">"Content-Type"</span>: <span class="string">"application/x-www-form-urlencoded"</span>,</span><br><span class="line"> <span class="string">"Cookie"</span>: <span class="string">"name=; password="</span>,</span><br><span class="line"> <span class="string">"Host"</span>: <span class="string">"202.38.64.59"</span>,</span><br><span class="line"> <span class="string">"Origin"</span>: <span class="string">"http://202.38.64.59"</span>,</span><br><span class="line"> <span class="string">"Referer"</span>: <span class="string">"http://202.38.64.59/cgi-bin/ip"</span>,</span><br><span class="line"> <span class="string">"Upgrade-Insecure-Requests"</span>: <span class="string">"1"</span>,</span><br><span class="line"> <span class="string">"User-Agent"</span>: <span class="string">"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36"</span></span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> ret = <span class="number">1</span></span><br><span class="line"> rq = requests.request(method = <span class="string">"POST"</span>, url = login_url, headers=login_headers, data = login_data)</span><br><span class="line"> <span class="keyword">if</span> rq.status_code != <span class="number">200</span>:</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"In _login(): \n login failed with status code {sc}, \n headers:\n{hd} \n text:\n{tx}"</span>.\</span><br><span class="line"> <span class="built_in">format</span>(sc = rq.status_code, hd = rq.headers, tx = rq.text))</span><br><span class="line"> ret = <span class="number">0</span></span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span> <span class="string">"Set-Cookie"</span> <span class="keyword">in</span> rq.headers:</span><br><span class="line"> self.cookie_rn = rq.headers[<span class="string">"Set-Cookie"</span>]</span><br><span class="line"> <span class="keyword">else</span> :</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"Login Failed!\nNo cookie in headers! \n headers:\n{hd}\n text:\n{tx}"</span>.<span class="built_in">format</span>(hd = rq.headers, tx = rq.text))</span><br><span class="line"> ret = <span class="number">0</span></span><br><span class="line"> rq.close()</span><br><span class="line"> <span class="keyword">return</span> ret</span><br><span class="line"></span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">_submit</span>(<span class="params">self</span>):</span><br><span class="line"> <span class="keyword">try</span>:</span><br><span class="line"> self.cookie_rn = self.cookie_rn</span><br><span class="line"> <span class="keyword">except</span> :</span><br><span class="line"> <span class="keyword">raise</span> ValueError(<span class="string">"Please Login() before Submit()!"</span>)</span><br><span class="line"> </span><br><span class="line"> submit_url = <span class="string">r"http://202.38.64.59/cgi-bin/ip?cmd=set&url=URL&type=8&exp=0&go=+%BF%AA%CD%A8%CD%F8%C2%E7+"</span></span><br><span class="line"> submit_headers = {</span><br><span class="line"> <span class="string">'Accept'</span>: <span class="string">'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9'</span>,</span><br><span class="line"> <span class="string">'Accept-Encoding'</span>: <span class="string">'gzip, deflate'</span>,</span><br><span class="line"> <span class="string">'Accept-Language'</span>: <span class="string">'zh-CN,zh;q=0.9'</span>,</span><br><span class="line"> <span class="string">'Connection'</span>: <span class="string">'keep-alive'</span>,</span><br><span class="line"> <span class="string">'Cookie'</span>: <span class="string">'name=; password=; {rn}'</span>.<span class="built_in">format</span>(rn = self.cookie_rn),</span><br><span class="line"> <span class="string">'Host'</span>: <span class="string">'202.38.64.59'</span>,</span><br><span class="line"> <span class="string">'Referer'</span>: <span class="string">'http://202.38.64.59/cgi-bin/ip'</span>,</span><br><span class="line"> <span class="string">'Upgrade-Insecure-Requests'</span>: <span class="string">'1'</span>,</span><br><span class="line"> <span class="string">'User-Agent'</span>: <span class="string">'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36'</span></span><br><span class="line"> }</span><br><span class="line"> submit_data = <span class="string">"cmd=set&url=URL&type=8&exp=0&go=+%BF%AA%CD%A8%CD%F8%C2%E7+"</span></span><br><span class="line"> </span><br><span class="line"> ret = <span class="number">1</span></span><br><span class="line"> rq = requests.get(submit_url, headers = submit_headers, data = submit_data)</span><br><span class="line"> <span class="keyword">if</span> rq.status_code != <span class="number">200</span>:</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"In _submit(): \n submit failed with status code {sc}, \n headers:\n{hd} \n text:\n{tx}"</span>.\</span><br><span class="line"> <span class="built_in">format</span>(sc = rq.status_code, hd = rq.headers, tx = rq.text))</span><br><span class="line"> ret = <span class="number">0</span></span><br><span class="line"> rq.close()</span><br><span class="line"> <span class="keyword">return</span> ret</span><br><span class="line"></span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">_logout</span>(<span class="params">self</span>):</span><br><span class="line"> <span class="keyword">try</span>:</span><br><span class="line"> self.cookie_rn = self.cookie_rn</span><br><span class="line"> <span class="keyword">except</span> :</span><br><span class="line"> <span class="keyword">raise</span> ValueError(<span class="string">"Please Login() before Logout()!"</span>)</span><br><span class="line"> </span><br><span class="line"> logout_url = <span class="string">"http://202.38.64.59/cgi-bin/ip?cmd=logout"</span></span><br><span class="line"> logout_headers = {</span><br><span class="line"> <span class="string">'Accept'</span>: <span class="string">'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9'</span>,</span><br><span class="line"> <span class="string">'Accept-Encoding'</span>: <span class="string">'gzip, deflate'</span>,</span><br><span class="line"> <span class="string">'Accept-Language'</span>: <span class="string">'zh-CN,zh;q=0.9'</span>,</span><br><span class="line"> <span class="string">'Connection'</span>: <span class="string">'keep-alive'</span>,</span><br><span class="line"> <span class="string">'Cookie'</span>: <span class="string">'name=; password=; {rn}'</span>.<span class="built_in">format</span>(rn = self.cookie_rn),</span><br><span class="line"> <span class="string">'Host'</span>: <span class="string">'202.38.64.59'</span>,</span><br><span class="line"> <span class="string">'Referer'</span>: <span class="string">'http://202.38.64.59/cgi-bin/ip?cmd=set&url=URL&type=8&exp=0&go=+%BF%AA%CD%A8%CD%F8%C2%E7+'</span>,</span><br><span class="line"> <span class="string">'Upgrade-Insecure-Requests'</span>: <span class="string">'1'</span>,</span><br><span class="line"> <span class="string">'User-Agent'</span>: <span class="string">'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36'</span>,</span><br><span class="line"> }</span><br><span class="line"> logout_data = <span class="string">"cmd=logout"</span></span><br><span class="line"> </span><br><span class="line"> ret = <span class="number">1</span></span><br><span class="line"> rq = requests.get(logout_url, headers = logout_headers, data = logout_data)</span><br><span class="line"> <span class="keyword">if</span> rq.status_code != <span class="number">200</span>:</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"In _logout(): \n logout failed with status code {sc}, \n headers:\n{hd} \n text:\n{tx}"</span>. <span class="built_in">format</span>(sc = rq.status_code, hd = rq.headers, tx = rq.text))</span><br><span class="line"> ret = <span class="number">0</span></span><br><span class="line"> rq.close()</span><br><span class="line"> <span class="keyword">return</span> ret</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">"__main__"</span>:</span><br><span class="line"> </span><br><span class="line"> ssid = <span class="string">"ustcnet"</span></span><br><span class="line"> password = <span class="string">""</span></span><br><span class="line"> user_name = <span class="string">"username"</span></span><br><span class="line"> user_password = <span class="string">"userpassword"</span></span><br><span class="line"></span><br><span class="line"> campuswifi = CampusWifi(ssid, password, user_name, user_password)</span><br><span class="line"> campuswifi.connect()</span><br></pre></td></tr></table></figure>
<h1 id="四、总结"><a href="#四、总结" class="headerlink" title="四、总结"></a>四、总结</h1><p>经过本次esp32连接校园网项目的实践,笔者对于urequests库及http协议相关知识有了更深的理解,同时也认识到了micropython在某些方面的缺陷,为以后相关的开发找到了解决方案。下面列举并总结了本项目实现过程中遇到的一些问题及解决办法:</p>
<ul>
<li>Wifi Internal Error是wifi模块内部错误,reset无用,需要把板子断点重连一下</li>
<li>OSError 32:套接字连接过多导致,注意将response对象关闭(<code>response.close()</code>)即可</li>
<li>micropython 中不支持gbk编码,encode、decode仅支持str与bytes之间的utf-8转换</li>
<li>HTTP 500 Internal Server Error 也可能是由于请求格式不对导致的</li>
<li>urequests有自动添加<code>content-length</code>、自动解码<code>response.text</code>的bug,需要更改源码解决</li>
</ul>
<p>以上就是esp32自动登录校园网的项目实现过程及源码,希望能给读者带来帮助~</p>
]]></content>
<categories>
<category>技术笔记</category>
<category>esp32</category>
</categories>
<tags>
<tag>esp32</tag>
<tag>micropython</tag>
<tag>python</tag>
<tag>网络</tag>
<tag>http</tag>
</tags>
</entry>
<entry>
<title>【python】自动登录中科大校园网</title>
<url>/2023/06/12/%E3%80%90python%E3%80%91%E8%87%AA%E5%8A%A8%E7%99%BB%E5%BD%95%E4%B8%AD%E7%A7%91%E5%A4%A7%E6%A0%A1%E5%9B%AD%E7%BD%91/</url>
<content><![CDATA[<p>最近在做一个项目,其中就需要自动联网的功能。不过由于不可能把手机一直留在宿舍开热点,就只能连接wifi了。学校里有ustcnet(校园网)和eduroam两种wifi,其中前者是连接后通过浏览器上登录账号并开通网络后才可以使用的,而eduroam是通过用户名和密码来进行连接的(应用IEEE 802.1x协议进行接入,基于RADIUS协议进行接入认证,具体笔者也不清楚),和普通的使用ssid和密码接入wifi的过程有着天壤之别,所以笔者选择了连接校园网来实现所需功能。 </p>
<span id="more"></span>
<p>前文提到了登录校园网需要通过浏览器进行操作,其实本质上就是和服务器端进行http协议通信,发送用户名和密码让服务端给你的IP提供一个access,这样就可以访问网络了。 </p>
<p>在参考了多篇前辈编写的校园网自动登录脚本后[1],[2],[3],其实现方法大致可以总结如下: </p>
<ol>
<li>利用selenium模块中的webdriver类模拟一个浏览器,然后在浏览器中模拟点击,以通过网页原生的方式向服务端发送连接请求</li>
<li>通过抓包获取网页向服务端发送的连接请求数据格式,并利用requests模块向服务端发送连接请求</li>
</ol>
<h2 id="一、webdriver方式"><a href="#一、webdriver方式" class="headerlink" title="一、webdriver方式"></a>一、webdriver方式</h2><p>这种方法主要摘自[1],并没有太多的改动,原文的讲解很详细,推荐去看原作者。下面给出实现代码。</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">from</span> selenium <span class="keyword">import</span> webdriver</span><br><span class="line"><span class="keyword">from</span> selenium.webdriver.common.keys <span class="keyword">import</span> Keys</span><br><span class="line"><span class="keyword">from</span> selenium.webdriver.common.by <span class="keyword">import</span> By</span><br><span class="line"><span class="keyword">import</span> time</span><br><span class="line"><span class="keyword">import</span> subprocess</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">spider</span>():</span><br><span class="line"> driver = webdriver.Chrome()</span><br><span class="line"> driver.get(<span class="string">'http://wlt.ustc.edu.cn'</span>) <span class="comment">#这里输入你的校园网登录网址</span></span><br><span class="line"> time.sleep(<span class="number">1</span>)</span><br><span class="line"> input_tag = driver.find_element(By.XPATH, <span class="string">"//input[@name='name' and @class='sform']"</span>) <span class="comment">#通过xpath确定账号框位置</span></span><br><span class="line"> input_tag.send_keys(<span class="string">"your_account"</span>) <span class="comment">#输入账号</span></span><br><span class="line"> input_tag2 = driver.find_element(By.XPATH, <span class="string">"//input[@name='password' and @class='sform']"</span>) <span class="comment">#通过xpath确定密码框位置</span></span><br><span class="line"> input_tag2.send_keys(<span class="string">"your_password"</span>) <span class="comment">#输入密码</span></span><br><span class="line"> input_tag2.send_keys(Keys.ENTER) <span class="comment">#敲一下回车</span></span><br><span class="line"> time.sleep(<span class="number">1</span>) </span><br><span class="line"> input_tag3 = driver.find_element(By.XPATH, <span class="string">"//input[@name='go' and @type='submit']"</span>) <span class="comment">#找到开通网络按钮</span></span><br><span class="line"> input_tag3.send_keys(Keys.ENTER) <span class="comment">#敲一下回车</span></span><br><span class="line"> time.sleep(<span class="number">1</span>) <span class="comment"># 1秒后自动关闭浏览器</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 测试网络是否连通</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">Ping</span>():</span><br><span class="line"> backinfo = subprocess.call(<span class="string">'ping www.zhihu.com -n 1'</span>, shell=<span class="literal">True</span>, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)</span><br><span class="line"> <span class="keyword">if</span> backinfo:</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">'网络未连接'</span>)</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span></span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"有网"</span>)</span><br><span class="line"> <span class="keyword">return</span> <span class="number">2</span> </span><br><span class="line"> </span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">'__main__'</span>:</span><br><span class="line"> spider()</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"连接网络中·····"</span>)</span><br><span class="line"> connection = Ping() </span><br><span class="line"> <span class="keyword">if</span> connection == <span class="number">2</span> :</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"ping"</span>)</span><br><span class="line"> <span class="keyword">elif</span> connection == <span class="number">1</span>:</span><br><span class="line"> spider()</span><br><span class="line"> exit()</span><br></pre></td></tr></table></figure>
<p>对此方法中的值得注意的点进行以下总结:</p>
<ol>
<li>原文中利用的是<code>driver.find_element_by_xpath()</code> 函数,这种方法已经被弃用,此处使用<code>driver.find_element(By.XPATH, "xpath_str")</code>进行xpath寻找。</li>
<li>具体用xpath匹配什么需要依不同的校园网而定,一般有交互的控件都带有input的标签,所以f12打开元素审查,搜索’input’或者搜索控件的内容就可以找到控件的name、class、style等信息,再利用这些信息进行xpath匹配即可。</li>
<li>不要ping校内网站,即使没有登录校园网,也是可以浏览校内网站的。</li>
</ol>
<h2 id="二、requests方式"><a href="#二、requests方式" class="headerlink" title="二、requests方式"></a>二、requests方式</h2><p>校内网登录的方式一般有GET和POST两种方式[3],使用GET方式只需要把账号密码放在url的参数中即可,此处不赘言,而POST方式更为常见,此处主要讨论这种方法。 </p>
<p>这种方法主要通过抓包获取网页向服务端发送的数据格式,然后再利用requests发送http包。</p>
<h3 id="1-抓包"><a href="#1-抓包" class="headerlink" title="1. 抓包"></a>1. 抓包</h3><p>打开浏览器无痕模式(这点很重要,不然你会发现你发送的第一个包就有cookie),在浏览器中打开校园网网站,f12打开开发者工具,点击上方的network </p>
<p><img src="/2023/06/12/%E3%80%90python%E3%80%91%E8%87%AA%E5%8A%A8%E7%99%BB%E5%BD%95%E4%B8%AD%E7%A7%91%E5%A4%A7%E6%A0%A1%E5%9B%AD%E7%BD%91/pic_developertool.png" alt="开发者工具"></p>
<p>然后再输入你的账号和密码之后,点击登录,留意刚刚抓到的包,点击它查看具体数据。</p>
<h3 id="2-登录账户"><a href="#2-登录账户" class="headerlink" title="2. 登录账户"></a>2. 登录账户</h3><p><img src="/2023/06/12/%E3%80%90python%E3%80%91%E8%87%AA%E5%8A%A8%E7%99%BB%E5%BD%95%E4%B8%AD%E7%A7%91%E5%A4%A7%E6%A0%A1%E5%9B%AD%E7%BD%91/request_headers.png" alt="请求头"></p>
<p>把上面红框内的部分复制下来,把冒号前面和冒号后面分别用引号圈起来。</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line">post_header = {</span><br><span class="line"> <span class="string">"Accept"</span>: <span class="string">"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"</span>,</span><br><span class="line"> ...</span><br><span class="line"> <span class="string">"Content-Length"</span>: <span class="built_in">str</span>(<span class="built_in">len</span>(urlencode(post_data))),</span><br><span class="line"> <span class="string">"Content-Type"</span>: <span class="string">"application/x-www-form-urlencoded"</span>,</span><br><span class="line"> ...</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>在构建好request_header字典之后,注意到其中有一项content-length,这项就是表单的长度。在request_header的下面可以发现form_data(也可能是其他形式的请求数据,因不同校园网而定),把这部分也复制下来制成字典。 </p>
<p><img src="/2023/06/12/%E3%80%90python%E3%80%91%E8%87%AA%E5%8A%A8%E7%99%BB%E5%BD%95%E4%B8%AD%E7%A7%91%E5%A4%A7%E6%A0%A1%E5%9B%AD%E7%BD%91/login_formdata.png" alt="登入表单"></p>
<p>不过需要注意的是,这里面有一部分不可以直接复制,就是<code>go:(unable to decode value)</code>这项,需要点击view URL encoded,把对应位置的URL编码字符串转换成GBK编码,即可放入请求数据的字典中去。具体的转换方式是把 ‘+’ 改成 ‘ ’ , ‘%’ 去掉,剩余部分是GBK编码的汉字,利用<a href="https://www.23bei.com/tool/54.html">编码转换网站</a> 将GBK编码转换成汉字,然后再encode(“GBK”)即可。</p>
<p><img src="/2023/06/12/%E3%80%90python%E3%80%91%E8%87%AA%E5%8A%A8%E7%99%BB%E5%BD%95%E4%B8%AD%E7%A7%91%E5%A4%A7%E6%A0%A1%E5%9B%AD%E7%BD%91/login_unable_to_decode.png" alt="无法解析"></p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line">go_login = <span class="string">"登录账户"</span></span><br><span class="line">post_data = {</span><br><span class="line"> ...</span><br><span class="line"> <span class="string">"go"</span>: go_login.encode(<span class="string">"GBK"</span>)</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>然后在浏览器开发工具的general部分找到requested URL,将此url设为<code>post_addr</code>,在利用<code>rq = requests.post(post_addr, headers=post_header, data=post_data)</code>即可发送数据。注意此处有必要把request的返回值记录下来,因为response包里有cookie,进行后续的操作需要用到。 </p>
<h3 id="3-开通网络"><a href="#3-开通网络" class="headerlink" title="3. 开通网络"></a>3. 开通网络</h3><p>有些校园网可能登陆之后就自动连接了网络,不过笔者的校园网需要再额外多一个开通网络的步骤。 </p>
<p>点击开通网络,抓包发现这个数据包是GET类型,继续像之前把数据复制下来做成字典。可以看到这里的请求数据包里有一项cookie,值为上面登录账户过程中response里的set-cookie值。</p>
<p><img src="/2023/06/12/%E3%80%90python%E3%80%91%E8%87%AA%E5%8A%A8%E7%99%BB%E5%BD%95%E4%B8%AD%E7%A7%91%E5%A4%A7%E6%A0%A1%E5%9B%AD%E7%BD%91/submit_data.png" alt="开通网络数据包"></p>
<p>下面给出完整实现代码。</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> socket</span><br><span class="line"><span class="keyword">import</span> requests</span><br><span class="line"><span class="keyword">import</span> subprocess</span><br><span class="line"><span class="keyword">import</span> re</span><br><span class="line"><span class="keyword">import</span> json</span><br><span class="line"><span class="keyword">from</span> urllib.parse <span class="keyword">import</span> urlencode</span><br><span class="line"><span class="keyword">from</span> urllib.parse <span class="keyword">import</span> parse_qs, urlparse</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">get_host_ip</span>():</span><br><span class="line"> <span class="keyword">try</span>:</span><br><span class="line"> s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)</span><br><span class="line"> s.connect((<span class="string">"10.255.255.255"</span>, <span class="number">1</span>))</span><br><span class="line"> ip = s.getsockname()[<span class="number">0</span>] </span><br><span class="line"> <span class="keyword">finally</span>:</span><br><span class="line"> s.close() </span><br><span class="line"> <span class="keyword">return</span> ip</span><br><span class="line"></span><br><span class="line">user_ip = get_host_ip()</span><br><span class="line"><span class="built_in">print</span>(<span class="string">"局域网ip为{}"</span>.<span class="built_in">format</span>(user_ip))</span><br><span class="line"></span><br><span class="line">user_name = <span class="string">"your username"</span></span><br><span class="line">user_password = <span class="string">"your password"</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">LogIn</span>():</span><br><span class="line"> login_url = <span class="string">"http://202.38.64.59/cgi-bin/ip"</span></span><br><span class="line"> go_login = <span class="built_in">str</span>(<span class="string">"登录账户"</span>.encode(<span class="string">"GBK"</span>))</span><br><span class="line"> go_login = go_login[<span class="number">2</span>:-<span class="number">1</span>]</span><br><span class="line"> login_data = {</span><br><span class="line"> <span class="string">"cmd"</span>: <span class="string">"login"</span>,</span><br><span class="line"> <span class="string">"url"</span>: <span class="string">"URL"</span>,</span><br><span class="line"> <span class="string">"ip"</span>: user_ip,</span><br><span class="line"> <span class="string">"name"</span>: user_name,</span><br><span class="line"> <span class="string">"password"</span>: user_password,</span><br><span class="line"> <span class="string">"go"</span>: go_login</span><br><span class="line"> }</span><br><span class="line"> login_header = {</span><br><span class="line"> <span class="string">"Accept"</span>: <span class="string">"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"</span>,</span><br><span class="line"> <span class="string">"Accept-Encoding"</span>: <span class="string">"gzip, deflate"</span>,</span><br><span class="line"> <span class="string">"Accept-Language"</span>: <span class="string">"zh-CN,zh;q=0.9"</span>,</span><br><span class="line"> <span class="string">"Cache-Control"</span>: <span class="string">"max-age=0"</span>,</span><br><span class="line"> <span class="string">"Connection"</span>: <span class="string">"keep-alive"</span>,</span><br><span class="line"> <span class="string">"Content-Length"</span>: <span class="built_in">str</span>(<span class="built_in">len</span>(urlencode_simple(login_data))),</span><br><span class="line"> <span class="string">"Content-Type"</span>: <span class="string">"application/x-www-form-urlencoded"</span>,</span><br><span class="line"> <span class="string">"Cookie"</span>: <span class="string">"name=; password="</span>,</span><br><span class="line"> <span class="string">"Host"</span>: <span class="string">"202.38.64.59"</span>,</span><br><span class="line"> <span class="string">"Origin"</span>: <span class="string">"http://202.38.64.59"</span>,</span><br><span class="line"> <span class="string">"Referer"</span>: <span class="string">"http://202.38.64.59/cgi-bin/ip"</span>,</span><br><span class="line"> <span class="string">"Upgrade-Insecure-Requests"</span>: <span class="string">"1"</span>,</span><br><span class="line"> <span class="string">"User-Agent"</span>: <span class="string">"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36"</span></span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment"># rq = requests.post(login_url, headers=login_header, data=json.dumps(login_data))</span></span><br><span class="line"> rq = requests.post(login_url, headers=login_header, data=login_data)</span><br><span class="line"> <span class="keyword">if</span> <span class="string">"Set-Cookie"</span> <span class="keyword">in</span> rq.headers:</span><br><span class="line"> cookie_rn = rq.headers[<span class="string">"Set-Cookie"</span>]</span><br><span class="line"> <span class="keyword">else</span> :</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"No cookie in headers! Headers:\n{}\nText:\n{}"</span>.<span class="built_in">format</span>(rq.headers, rq.text))</span><br><span class="line"> <span class="comment"># print("No cookie in headers! Headers:\n{}\nText:\n{}".format(rq.headers, rq.text.encode("utf-8")))</span></span><br><span class="line"> cookie_rn = <span class="string">""</span></span><br><span class="line"> <span class="keyword">return</span> rq.status_code, cookie_rn</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">Connect</span>(<span class="params">cookie_rn</span>):</span><br><span class="line"> connect_url = <span class="string">r"http://202.38.64.59/cgi-bin/ip?cmd=set&url=URL&type=8&exp=0&go=+%BF%AA%CD%A8%CD%F8%C2%E7+"</span></span><br><span class="line"> connect_headers = {</span><br><span class="line"> <span class="string">'Accept'</span>: <span class="string">'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9'</span>,</span><br><span class="line"> <span class="string">'Accept-Encoding'</span>: <span class="string">'gzip, deflate'</span>,</span><br><span class="line"> <span class="string">'Accept-Language'</span>: <span class="string">'zh-CN,zh;q=0.9'</span>,</span><br><span class="line"> <span class="string">'Connection'</span>: <span class="string">'keep-alive'</span>,</span><br><span class="line"> <span class="string">'Cookie'</span>: <span class="string">'name=; password=; {rn}'</span>.<span class="built_in">format</span>(rn = cookie_rn),</span><br><span class="line"> <span class="string">'Host'</span>: <span class="string">'202.38.64.59'</span>,</span><br><span class="line"> <span class="string">'Referer'</span>: <span class="string">'http://202.38.64.59/cgi-bin/ip'</span>,</span><br><span class="line"> <span class="string">'Upgrade-Insecure-Requests'</span>: <span class="string">'1'</span>,</span><br><span class="line"> <span class="string">'User-Agent'</span>: <span class="string">'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36'</span></span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> go_submit = <span class="string">" 开通网络 "</span></span><br><span class="line"> query_string_params = {</span><br><span class="line"> <span class="string">'cmd'</span>: <span class="string">'set'</span>,</span><br><span class="line"> <span class="string">'url'</span>: <span class="string">'URL'</span>,</span><br><span class="line"> <span class="string">'type'</span>: <span class="string">'8'</span>,</span><br><span class="line"> <span class="string">'exp'</span>: <span class="string">'0'</span>,</span><br><span class="line"> <span class="string">"go"</span>: go_submit.encode(<span class="string">"GBK"</span>)</span><br><span class="line"> }</span><br><span class="line"> <span class="comment"># print("go_submit: {}".format(go_submit.encode("GBK")))</span></span><br><span class="line"> rq = requests.get(connect_url, headers=connect_headers, data=query_string_params)</span><br><span class="line"> <span class="keyword">return</span> rq.status_code</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">LogOut</span>(<span class="params">cookie_rn</span>): </span><br><span class="line"> <span class="keyword">try</span>:</span><br><span class="line"> cookie_rn = cookie_rn</span><br><span class="line"> <span class="keyword">except</span> :</span><br><span class="line"> <span class="keyword">raise</span> ValueError(<span class="string">"Please Login() before Logout()!"</span>)</span><br><span class="line"> </span><br><span class="line"> logout_url = <span class="string">"http://202.38.64.59/cgi-bin/ip?cmd=logout"</span></span><br><span class="line"> logout_headers = {</span><br><span class="line"> <span class="string">'Accept'</span>: <span class="string">'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9'</span>,</span><br><span class="line"> <span class="string">'Accept-Encoding'</span>: <span class="string">'gzip, deflate'</span>,</span><br><span class="line"> <span class="string">'Accept-Language'</span>: <span class="string">'zh-CN,zh;q=0.9'</span>,</span><br><span class="line"> <span class="string">'Connection'</span>: <span class="string">'keep-alive'</span>,</span><br><span class="line"> <span class="string">'Cookie'</span>: <span class="string">'name=; password=; {rn}'</span>.<span class="built_in">format</span>(rn = cookie_rn),</span><br><span class="line"> <span class="string">'Host'</span>: <span class="string">'202.38.64.59'</span>,</span><br><span class="line"> <span class="string">'Referer'</span>: <span class="string">'http://202.38.64.59/cgi-bin/ip?cmd=set&url=URL&type=8&exp=0&go=+%BF%AA%CD%A8%CD%F8%C2%E7+'</span>,</span><br><span class="line"> <span class="string">'Upgrade-Insecure-Requests'</span>: <span class="string">'1'</span>,</span><br><span class="line"> <span class="string">'User-Agent'</span>: <span class="string">'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36'</span>,</span><br><span class="line"> }</span><br><span class="line"> logout_data = {<span class="string">"cmd"</span>: <span class="string">"logout"</span>}</span><br><span class="line"> </span><br><span class="line"> rq = requests.get(logout_url, headers = logout_headers, data = logout_data)</span><br><span class="line"> <span class="keyword">if</span> rq.status_code != <span class="number">200</span>:</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"In _logout(): \n logout failed with status code {sc}, \n headers:\n{hd} \n text:\n{tx}"</span>.\</span><br><span class="line"> <span class="built_in">format</span>(sc = rq.status_code, hd = rq.headers, tx = rq.text))</span><br><span class="line"> ret = rq.status_code</span><br><span class="line"> rq.close()</span><br><span class="line"> <span class="keyword">return</span> ret</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">Ping</span>():</span><br><span class="line"> backinfo = subprocess.call(<span class="string">"ping www.bilibili.com -n 1"</span>, shell=<span class="literal">True</span>, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)</span><br><span class="line"> <span class="keyword">if</span> backinfo:</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"网络未连接"</span>)</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span></span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"网络已连接"</span>)</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">urlencode_simple</span>(<span class="params">query: <span class="built_in">dict</span></span>):</span><br><span class="line"> url = <span class="string">""</span></span><br><span class="line"> <span class="keyword">for</span> key <span class="keyword">in</span> query:</span><br><span class="line"> keycpy = <span class="built_in">str</span>(key)</span><br><span class="line"> value = <span class="built_in">str</span>(query[key])</span><br><span class="line"> <span class="keyword">if</span> <span class="built_in">type</span>(query[key]) == <span class="built_in">bytes</span>:</span><br><span class="line"> value = value[<span class="number">2</span>:-<span class="number">1</span>]</span><br><span class="line"> keycpy = keycpy.replace(<span class="string">'@'</span>, <span class="string">'%40'</span>)</span><br><span class="line"> keycpy = keycpy.replace(<span class="string">r'\x'</span>, <span class="string">'%'</span>)</span><br><span class="line"> keycpy = keycpy.replace(<span class="string">r' '</span>, <span class="string">'+'</span>)</span><br><span class="line"> value = value.replace(<span class="string">'@'</span>, <span class="string">'%40'</span>)</span><br><span class="line"> value = value.replace(<span class="string">r'\x'</span>, <span class="string">'%'</span>)</span><br><span class="line"> value = value.replace(<span class="string">r' '</span>, <span class="string">'+'</span>)</span><br><span class="line"> url += <span class="string">"{}={}&"</span>.<span class="built_in">format</span>(keycpy, value)</span><br><span class="line"> url = url[<span class="number">0</span>:-<span class="number">1</span>]</span><br><span class="line"> <span class="keyword">return</span> url </span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">"__main__"</span>:</span><br><span class="line"> </span><br><span class="line"> <span class="comment"># z = requests.get(login_url)</span></span><br><span class="line"> <span class="comment"># print("Response: {}".format(z.status_code))</span></span><br><span class="line"></span><br><span class="line"> connection = Ping() <span class="comment">#检测网络是否连通</span></span><br><span class="line"></span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"连接网络中·····"</span>)</span><br><span class="line"></span><br><span class="line"> status_code, cookie_rn = LogIn()</span><br><span class="line"> <span class="keyword">if</span> status_code != <span class="number">200</span>:</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"LogIn Error with status code: {}"</span>.<span class="built_in">format</span>(status_code))</span><br><span class="line"> exit()</span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"Login Response code: {}"</span>.<span class="built_in">format</span>(status_code))</span><br><span class="line"></span><br><span class="line"> status_code = Connect(cookie_rn)</span><br><span class="line"> <span class="keyword">if</span> status_code != <span class="number">200</span>:</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"Connect Error with status code: {}"</span>.<span class="built_in">format</span>(status_code))</span><br><span class="line"> exit()</span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"Connect Response code: {}"</span>.<span class="built_in">format</span>(status_code))</span><br><span class="line"></span><br><span class="line"> connection = Ping() <span class="comment">#检测网络是否连通</span></span><br><span class="line"></span><br><span class="line"> status_code = LogOut(cookie_rn)</span><br><span class="line"> <span class="keyword">if</span> status_code != <span class="number">200</span>:</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"LogOut Error with status code: {}"</span>.<span class="built_in">format</span>(status_code))</span><br><span class="line"> exit()</span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"LogOut Response code: {}"</span>.<span class="built_in">format</span>(status_code))</span><br><span class="line"></span><br><span class="line"> connection = Ping() <span class="comment">#检测网络是否连通</span></span><br><span class="line"></span><br><span class="line"> exit()</span><br></pre></td></tr></table></figure>
<p>笔者踩的一个比较大的坑就是每次发送GET/POST包的远端地址不要搞错了,需要是抓包获得的request URL,如果这个搞错了很可能会返回错误码405。 </p>
<p>这样基本就完成了python自动登入校园网的实现。总结一下,第一种方式是利用webdriver类模拟浏览器进行爬虫操作,优点是实现简单,调试直观,代码简短,不过缺点是速度慢、不好把控(如果sleep时间少了会导致服务器没加载完),而且外观比较丑陋、不能静默实现爬虫;第二种方式是利用requests发送http包进行爬虫操作,优点是速度快、可以后台进行、<del>让笔者了解了更多HTTP和网络知识</del>、同时因为不用模拟浏览器,也就不局限于操作系统,http包的发送可以在单片机上进行,缺点是调试困难没有头绪,代码繁杂,<del>堪称BUG妙妙屋</del>。 </p>
<p>以上就是此次项目的实现以及讲解,希望能给各位读者带来帮助~</p>
<p>主要参考:</p>
<p>[1] <a href="https://zhuanlan.zhihu.com/p/367942686?utm_id=0">基于python的校园网自动登录脚本!<em>zhihu.com</em>@Python小萌新</a></p>
<p>[2] <a href="https://blog.csdn.net/m0_72091242/article/details/125843564">python写一个脚本,自动连wifi,自动登录校园网_csdn.net@今天代码没bug</a></p>
<p>[3] <a href="https://zhuanlan.zhihu.com/p/370801224">自动登录校园网脚本(Python实现)_zhihu.com@Cosmica</a></p>
]]></content>
<categories>
<category>技术笔记</category>
<category>python</category>
</categories>
<tags>
<tag>python</tag>
<tag>网络</tag>
<tag>http</tag>
</tags>
</entry>
<entry>
<title>【esp32】espremote实现OTA在线更新程序</title>
<url>/2023/12/22/%E3%80%90esp32%E3%80%91espremote%E5%AE%9E%E7%8E%B0OTA%E5%9C%A8%E7%BA%BF%E6%9B%B4%E6%96%B0%E7%A8%8B%E5%BA%8F/</url>
<content><![CDATA[<p>近日做了一个自动关灯的小东西,放在宿舍里可以避免断电后忘记关灯导致第二天”怀民亦未寝.jpg”。不过有一个问题,这东西是粘在墙上的,想要调试的话总不能搬个电脑蹲在灯旁边debug一个下午吧。正当笔者苦恼于又要买一个3m超长数据线的时候,灵光一现,想到python作为一种脚本语言,是否可以在运行时更新代码呢? </p>
<span id="more"></span>
<p>说干就干,先想想怎么写出一个可以自己更新自己的python代码。 </p>
<h1 id="一、蟒蛇的自我更新"><a href="#一、蟒蛇的自我更新" class="headerlink" title="一、蟒蛇的自我更新"></a>一、蟒蛇的自我更新</h1><p>诶,上网一查,已经有前人想出这鬼点子了,并且还给出了部分的代码(<br><a href="https://www.zhihu.com/question/626768033/answer/3255532727)%E3%80%82">https://www.zhihu.com/question/626768033/answer/3255532727)。</a> </p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment"># option begin</span></span><br><span class="line"><span class="built_in">max</span> = <span class="number">200</span></span><br><span class="line"><span class="comment"># option end</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">updateOption</span>(<span class="params"><span class="built_in">max</span></span>):</span><br><span class="line"> <span class="keyword">with</span> <span class="built_in">open</span>(__file__, <span class="string">'r+'</span>) <span class="keyword">as</span> f:</span><br><span class="line"> arrLines = f.readlines()</span><br><span class="line"> idxOptionBegin = arrLines.index(<span class="string">'# option begin\n'</span>)</span><br><span class="line"> idxOptionEnd = arrLines.index(<span class="string">'# option end\n'</span>)</span><br><span class="line"> <span class="keyword">for</span> idx, optionLine <span class="keyword">in</span> <span class="built_in">enumerate</span>(arrLines):</span><br><span class="line"> <span class="keyword">if</span> optionLine.startswith(<span class="string">'max = '</span>) <span class="keyword">and</span> idxOptionBegin < idx < idxOptionEnd:</span><br><span class="line"> arrLines[idx] = <span class="string">'max = '</span> + <span class="built_in">str</span>(<span class="built_in">max</span>) + <span class="string">'\n'</span></span><br><span class="line"> f.seek(<span class="number">0</span>)</span><br><span class="line"> f.writelines(arrLines)</span><br><span class="line"><span class="string">"""</span></span><br><span class="line"><span class="string">Author: 中等难度的贪吃蛇</span></span><br><span class="line"><span class="string">"""</span></span><br></pre></td></tr></table></figure>
<p>我们来copy一小段学习一下,不得不说这位答主的码风很让人赏心悦目,把python的语法糖用的恰到好处。话归正题,这段代码用注释<code>#option begin/end</code>标注了可修改的区间,<code>__file__</code>是python内置变量,即为文件自身的名字,总体上先读取所有代码行,把待修改行进行修改后全部输出到源文件中去。 </p>
<p>复制粘贴好是好,但是不实操总有东西弄不懂。比方如果在执行过程中把后面的语句删除了,那么他还会执行吗? </p>
<p>简单写下代码</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">with</span> <span class="built_in">open</span>(__file__, <span class="string">'w+'</span>) <span class="keyword">as</span> f:</span><br><span class="line"> f.write(<span class="string">"111\n"</span> * <span class="number">3</span>)</span><br><span class="line"> f.write(<span class="string">"222"</span>)</span><br></pre></td></tr></table></figure>
<p>运行一下会发现原文件变成如下</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">111</span><br><span class="line">111</span><br><span class="line">111</span><br><span class="line">222</span><br></pre></td></tr></table></figure>
<p>可见python在运行之初,会将所有的代码纳入内存之中,即使修改代码文件,也不会对运行结果造成影响。 </p>
<p>那现在再来看一下esp32上面的micropython是否支持这一特性吧。毕竟micropython有些库不太稳定,让笔者曾一度怀疑它的实力。简单修改一下上面”贪吃蛇“的代码,写了一段更好看的小段代码。 </p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment"># option begin</span></span><br><span class="line">param = <span class="number">0</span></span><br><span class="line"><span class="comment"># option end</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">updateOption</span>(<span class="params">**kwargs</span>):</span><br><span class="line"> <span class="keyword">with</span> <span class="built_in">open</span>(__file__, <span class="string">'r+'</span>) <span class="keyword">as</span> f:</span><br><span class="line"> arrLines = f.readlines()</span><br><span class="line"> idxOptionBegin = arrLines.index(<span class="string">'# option begin\n'</span>)</span><br><span class="line"> idxOptionEnd = arrLines.index(<span class="string">'# option end\n'</span>)</span><br><span class="line"> <span class="keyword">for</span> idx, optionLine <span class="keyword">in</span> <span class="built_in">enumerate</span>(arrLines):</span><br><span class="line"> <span class="keyword">if</span> <span class="keyword">not</span> idxOptionBegin < idx < idxOptionEnd:</span><br><span class="line"> <span class="keyword">continue</span></span><br><span class="line"> </span><br><span class="line"> <span class="keyword">for</span> key <span class="keyword">in</span> kwargs:</span><br><span class="line"> <span class="keyword">if</span> optionLine.startswith(key + <span class="string">' = '</span>):</span><br><span class="line"> arrLines[idx] = key + <span class="string">' = '</span> + <span class="built_in">str</span>(kwargs[key]) + <span class="string">'\n'</span></span><br><span class="line"> </span><br><span class="line"> <span class="keyword">with</span> <span class="built_in">open</span>(__file__, <span class="string">"w+"</span>) <span class="keyword">as</span> f:</span><br><span class="line"> f.writelines(arrLines)</span><br><span class="line"></span><br><span class="line">updateOption(param = param + <span class="number">1</span>)</span><br><span class="line"><span class="built_in">print</span>(param)</span><br></pre></td></tr></table></figure>
<p>这段代码在pc上运行的结果是每次运行param都会加1,将其改名为“main.py”,放在esp32之中尝试运行后重新打开文件,<code>param = 1</code>!看来mipy是支持这种热修改的,太棒了。 </p>
<h1 id="二、Link-Start"><a href="#二、Link-Start" class="headerlink" title="二、Link Start!"></a>二、Link Start!</h1><p>那么一切顺利,我们现在可以考虑如何让电脑端传出的待更新代码传到esp32上面去。 </p>
<p>有几种方法,一是把代码放到公网上,然后esp32再从公网上将代码取下来;二是局域网通信,esp32和pc端直接进行通信。乍一看似乎法一好些,毕竟可以身处天涯而心系esp32,在哪里都可以更新,但其实有很大的弊端:首先存放代码的公网服务器的延迟都不低,效率和稳定性不如局域网,再者局域网可以实现指令的即时传递,进而实现对esp32文件系统更加便捷的控制,最后就是那个代码桶的网站是境外的,小esp32翻不了墙。 </p>
<p>局域网通信的话就又有问题要考虑了。公网上的ip总是固定的,但局域网内的ip每次断电重连都会变化。总不能从127.0.0.0到255.255.255.255挨个试吧。诶,255.255.255.255?广播!我们可以用udp把自己的地址广播出去(应该不会收到二向箔吧),然后等待另一方的连接。 </p>
<p>好,那么我们就可以初步利用udp的广播和tcp协议实现局域网未知ip匹配并进行消息传递的功能。 </p>
<p>下面给出的是esp32端的核心代码</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 在之前需要让esp32连接wifi,获取本地ip,并定义好通信的PORT</span></span><br><span class="line"><span class="comment"># 至于如何连接wifi可以见我之前发的博客</span></span><br><span class="line"></span><br><span class="line">udp_addr = (<span class="string">'255.255.255.255'</span>, PORT)</span><br><span class="line">udp_sock = socket(AF_INET, SOCK_DGRAM)</span><br><span class="line"></span><br><span class="line">tcp_addr = (ip, PORT)</span><br><span class="line">tcp_listen_sock = socket(AF_INET, SOCK_STREAM)</span><br><span class="line">tcp_listen_sock.settimeout(<span class="number">1</span>) <span class="comment"># 这里由于esp32在连接上位机之外还要进行其他操作,所以设置了超时</span></span><br><span class="line">tcp_listen_sock.bind(tcp_addr)</span><br><span class="line">tcp_listen_sock.listen(<span class="number">1</span>)</span><br><span class="line"></span><br><span class="line">conn, addr = <span class="literal">None</span>, <span class="literal">None</span></span><br><span class="line"><span class="built_in">print</span>(<span class="string">"waiting for connect..."</span>)</span><br><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line"> <span class="comment"># 广播</span></span><br><span class="line"> <span class="comment"># 其实没必要把自己的ip放进数据中,广播的接收方可以接收到发送方的ip的</span></span><br><span class="line"> message = <span class="string">"[HI]"</span></span><br><span class="line"> udp_sock.sendto(message, udp_addr) </span><br><span class="line"></span><br><span class="line"> <span class="comment"># 接收tcp连接</span></span><br><span class="line"> <span class="keyword">try</span>:</span><br><span class="line"> conn, addr = tcp_listen_sock.accept()</span><br><span class="line"> <span class="built_in">print</span>(conn, addr, <span class="string">"connected"</span>)</span><br><span class="line"> <span class="keyword">break</span></span><br><span class="line"> <span class="keyword">except</span> Exception <span class="keyword">as</span> exc:</span><br><span class="line"> <span class="comment"># etimedout是阻塞超时异常,eagain是非阻塞没接收到信息抛出的异常</span></span><br><span class="line"> <span class="keyword">if</span> <span class="built_in">str</span>(exc) == <span class="string">"[Errno 116] ETIMEDOUT"</span> <span class="keyword">or</span> <span class="built_in">str</span>(exc) == <span class="string">"[Errno 11] EAGAIN"</span>:</span><br><span class="line"> <span class="keyword">pass</span></span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> <span class="keyword">raise</span> OSError(exc)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (conn, addr) == (<span class="literal">None</span>, <span class="literal">None</span>):</span><br><span class="line"> udp_sock.close()</span><br><span class="line"> sys.exit()</span><br><span class="line"></span><br><span class="line"><span class="comment"># 连接成功</span></span><br><span class="line"> </span><br><span class="line">conn.settimeout(<span class="number">5.0</span>)</span><br><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line"> data = conn.recv(<span class="number">1024</span>)</span><br><span class="line"> <span class="keyword">if</span> <span class="built_in">len</span>(data) == <span class="number">0</span>: <span class="comment">#判断客户端是否断开连接</span></span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"close socket"</span>)</span><br><span class="line"> conn.close()</span><br><span class="line"> <span class="keyword">break</span></span><br><span class="line"> <span class="built_in">print</span>(data)</span><br><span class="line"> ret = conn.send(data)</span><br><span class="line"></span><br><span class="line">udp_sock.close()</span><br></pre></td></tr></table></figure>
<p>接下来是pc端的核心代码:</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"></span><br><span class="line">address = (<span class="string">''</span>, PORT)</span><br><span class="line">s = socket(AF_INET, SOCK_DGRAM)</span><br><span class="line">s.setsockopt(SOL_SOCKET, SO_BROADCAST, <span class="number">1</span>)</span><br><span class="line"></span><br><span class="line">s.bind(address)</span><br><span class="line"></span><br><span class="line"><span class="comment"># udp sock等待接收广播</span></span><br><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">' wait recv...'</span>)</span><br><span class="line"> data, address = s.recvfrom(<span class="number">1024</span>)</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">' [recv form %s:%d]:%s'</span> % (address[<span class="number">0</span>], address[<span class="number">1</span>], data))</span><br><span class="line"></span><br><span class="line"> s.close()</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> data.decode() == <span class="string">"[HI]"</span>:</span><br><span class="line"> <span class="keyword">break</span></span><br><span class="line"> </span><br><span class="line">IPADDR = address[<span class="number">0</span>]</span><br><span class="line"></span><br><span class="line"><span class="comment"># tcp连接</span></span><br><span class="line">tcp_client_socket = socket(AF_INET, SOCK_STREAM)</span><br><span class="line"></span><br><span class="line">server_ip, server_port = IPADDR, PORT</span><br><span class="line">tcp_client_socket.connect((server_ip, server_port))</span><br><span class="line"></span><br><span class="line"><span class="comment"># 连接成功</span></span><br><span class="line">send_data = <span class="built_in">input</span>(<span class="string">"请输入要发送的数据:"</span>)</span><br><span class="line">tcp_client_socket.send(send_data.encode())</span><br><span class="line"></span><br><span class="line">recvData = tcp_client_socket.recv(<span class="number">1024</span>)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">'接收到的数据为:'</span>, recvData.decode())</span><br><span class="line"></span><br><span class="line">tcp_client_socket.close()</span><br></pre></td></tr></table></figure>
<h1 id="三、抽象,抽象,更加抽象"><a href="#三、抽象,抽象,更加抽象" class="headerlink" title="三、抽象,抽象,更加抽象"></a>三、抽象,抽象,更加抽象</h1><p>在成功实现广播连接之后,实现OTA在线更新的大厦已经落成,所剩只是一点修饰的工作了。两小朵乌云之一是定义好各个指令的协议,并将这个esp32上位机模块和esp32OTA模块封装起来,之二且待后文详说。 </p>
<p>首先我们要将recv、send等等函数封装一下,因为发送接收时会出现各种问题,不是掉链接,没回应,超时,就是回应的很奇怪,不符合预期。而这些问题的重要性也都不同,掉链接的话可以直接结束ota程序了,其他的话有的可以再试试,有的根本不用在意。 </p>
<p>所以我们先定义了几种返回值,然后对两个关键函数封装了一下。</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line">SUCCESS = <span class="number">0</span></span><br><span class="line">NOTICE = <span class="number">1</span></span><br><span class="line">WARNING = <span class="number">2</span></span><br><span class="line">ERROR = <span class="number">3</span></span><br><span class="line">CRITICAL = <span class="number">4</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">UpperComputer</span>(<span class="title class_ inherited__">object</span>):</span><br><span class="line"></span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">recv_signal</span>(<span class="params">self, description_of_this_time, expected_dat = <span class="literal">None</span></span>):</span><br><span class="line"> <span class="keyword">try</span>:</span><br><span class="line"> dat = self.conn.recv(<span class="number">1024</span>).decode()</span><br><span class="line"> <span class="keyword">if</span> <span class="built_in">len</span>(dat) == <span class="number">0</span>:</span><br><span class="line"> log.print_only(<span class="string">"upper comp lost connection when {}"</span>.<span class="built_in">format</span>(description_of_this_time))</span><br><span class="line"> <span class="keyword">return</span> <span class="string">""</span>, CRITICAL</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span> expected_dat != <span class="literal">None</span> <span class="keyword">and</span> dat != expected_dat:</span><br><span class="line"> log.error(<span class="string">"Response Invalid when {}. (res: {})"</span>.<span class="built_in">format</span>(description_of_this_time, dat))</span><br><span class="line"> <span class="keyword">return</span> dat, ERROR</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> dat, SUCCESS</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">except</span> Exception <span class="keyword">as</span> exc:</span><br><span class="line"> log.error(<span class="string">"No Response when {}.(exc: {})"</span>.<span class="built_in">format</span>(description_of_this_time, exc))</span><br><span class="line"> <span class="keyword">return</span> <span class="string">""</span>, ERROR</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">send_signal</span>(<span class="params">self, dat</span>):</span><br><span class="line"> <span class="keyword">try</span>:</span><br><span class="line"> self.conn.send(dat.encode())</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">True</span>, SUCCESS</span><br><span class="line"> <span class="keyword">except</span> Exception <span class="keyword">as</span> exc:</span><br><span class="line"> log.error(<span class="string">"Send Failed {}. {}"</span>.<span class="built_in">format</span>(dat, exc))</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">False</span>, CRITICAL</span><br><span class="line"> </span><br></pre></td></tr></table></figure>
<p>这样封装的好处是每次不需要进行繁复的try-except,只需要判断返回值是否正常即可。同时还避免了重复的log语句。 </p>
<p>接下来是初始化函数,对udpsock和tcpsock分别进行初始化;广播连接函数已经介绍过了,之后是指令处理函数。需要注意tcp协议下如果连续发送消息,那么接收方可能一次recv收到对方两次send的内容,就可能会导致invalid response或者no reponse。为了避免这种情况,应该双方一唱一和,A发完消息B要回复收到。所以对于上位机发送的有后续补充信息的指令,要回复“ready”,没有补充信息的,就只需要回复”finish”或者查询的值即可。下面是esp32上用于和上位机通信的模块,pc端用于通信的模块类似,只是没有operation_handler()。 </p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">UpperComputer</span> (<span class="title class_ inherited__">object</span>):</span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, self_ip, port</span>):</span><br><span class="line"> self.port = port</span><br><span class="line"> self.ip = self_ip</span><br><span class="line"> </span><br><span class="line"> self.usock = socket(..)</span><br><span class="line"> self.tsock = socket(..)</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">broadcast_and_connect</span>(<span class="params">self</span>):</span><br><span class="line"> <span class="comment"># udp broadcast</span></span><br><span class="line"> <span class="comment"># tcp connect</span></span><br><span class="line"> <span class="keyword">return</span> <span class="literal">True</span></span><br><span class="line"> </span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">operation_handler</span>(<span class="params">self</span>):</span><br><span class="line"> </span><br><span class="line"> dat, ret = self.recv_signal(<span class="string">"ready to recv operation"</span>)</span><br><span class="line"> <span class="keyword">if</span> ret >= WARNING:</span><br><span class="line"> <span class="keyword">return</span> ret</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span> dat == <span class="string">"[UPDATE]"</span>:</span><br><span class="line"> self.send_signal(<span class="string">"[READY]"</span>)</span><br><span class="line"> <span class="keyword">return</span> self.update()</span><br><span class="line"> <span class="comment"># ...</span></span><br><span class="line"> <span class="keyword">elif</span> dat == <span class="string">'[GETFILELIST]'</span>:</span><br><span class="line"> <span class="keyword">return</span> self.get_file_list()</span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> self.conn.send(dat.encode()) <span class="comment"># echo</span></span><br><span class="line"> <span class="keyword">return</span> NOTICE</span><br><span class="line"> </span><br></pre></td></tr></table></figure>
<p>经过这样的抽象处理,OTA的功能就已经实现了,并且使用起来很简便。</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line">FILE_ROOT = <span class="string">"D:\\我的文档\\学习\\ESP32\\update\\"</span></span><br><span class="line">SEND_FILE_DICT = {<span class="string">"下位机程序.py"</span>: <span class="string">"main.py"</span>} <span class="comment"># key代表pc端的文件名称,value代表需要保存到esp32中的位置</span></span><br><span class="line">SAVE_PATH = <span class="string">"D:\\我的文档\\学习\\ESP32\\download\\"</span></span><br><span class="line">DOWNLOAD_LIST = [<span class="string">'log.txt'</span>, <span class="string">'main.py'</span>]</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">"__main__"</span>:</span><br><span class="line"> esp32 = ESP32Ctrl(PORT)</span><br><span class="line"> esp32.connect()</span><br><span class="line"> <span class="comment"># esp32.update(FILE_ROOT, SEND_FILE_DICT)</span></span><br><span class="line"> <span class="comment"># esp32.reboot()</span></span><br><span class="line"> <span class="comment"># esp32.download(DOWNLOAD_LIST, SAVE_PATH)</span></span><br><span class="line"> file_list, _ = esp32.get_file_list()</span><br><span class="line"> <span class="built_in">print</span>(file_list)</span><br><span class="line"> esp32.delete(<span class="string">"log.txt"</span>)</span><br><span class="line"> </span><br><span class="line"> esp32.close()</span><br></pre></td></tr></table></figure>
<h1 id="四、ESPREMOTE"><a href="#四、ESPREMOTE" class="headerlink" title="四、ESPREMOTE"></a>四、ESPREMOTE</h1><p>但是每次更新都需要去改pc端的代码,比方需要上传或下载什么文件、想要获取文件列表还要重新改代码运行一次,颇有些麻烦。笔者想到初次接触到esp32时,用到了一个模块叫做ampy。这个模块利用命令行实现了对esp32上放置、移除文件、运行代码等操作,我们是否也可以把我们的上位机功能做成命令行呢? </p>
<p>python在实现命令行中os模块非常有用,os.path可以对文件路径进行诸多操作,os.chdir还可以自动记录当前所在文件夹,不用手动判断文件夹是否存在了。</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">COMMANDER</span>(<span class="title class_ inherited__">object</span>):</span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">command_handler</span>(<span class="params">self, command</span>):</span><br><span class="line"> arg_list = command.split(<span class="string">" "</span>)</span><br><span class="line"> op = arg_list [<span class="number">0</span>].lower()</span><br><span class="line"></span><br><span class="line"> <span class="comment"># cd 进入文件夹</span></span><br><span class="line"> <span class="keyword">if</span> op <span class="keyword">in</span> [<span class="string">'cd'</span>]:</span><br><span class="line"> <span class="keyword">try</span>:</span><br><span class="line"> command = command[<span class="built_in">len</span>(op + <span class="string">" "</span>) : ]</span><br><span class="line"> command.strip(<span class="string">"\"\'"</span>)</span><br><span class="line"> os.chdir( <span class="string">'./'</span> + command)</span><br><span class="line"> </span><br><span class="line"> self.update_cwd()</span><br><span class="line"> <span class="keyword">except</span> Exception <span class="keyword">as</span> exc:</span><br><span class="line"> <span class="built_in">print</span>(Fore.RED + <span class="string">f"ERROR: <span class="subst">{exc}</span>"</span> + Fore.RESET)</span><br><span class="line"> <span class="keyword">return</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment"># elif ..:</span></span><br><span class="line"> <span class="comment"># else:</span></span><br></pre></td></tr></table></figure>
<p>我们现将本模块称为espremote,调用指令为espremote command [args]。下面给出了espremote的实现方法和效果展示。</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">elif</span> op <span class="keyword">in</span> [<span class="string">'espremote'</span>]:</span><br><span class="line"> <span class="keyword">if</span> <span class="built_in">len</span>(arg_list) == <span class="number">1</span> <span class="keyword">or</span> arg_list[<span class="number">1</span>] == <span class="string">'help'</span>:</span><br><span class="line"> <span class="built_in">print</span>(help_info[<span class="string">'help'</span>])</span><br><span class="line"> <span class="keyword">return</span></span><br><span class="line"></span><br><span class="line"> op2 = arg_list[<span class="number">1</span>].lower()</span><br><span class="line"> arg_list = arg_list[<span class="number">2</span>:]</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span> op2 <span class="keyword">in</span> [<span class="string">'conn'</span>]:</span><br><span class="line"> esp32.connect()</span><br><span class="line"> <span class="keyword">return</span></span><br><span class="line"> <span class="comment"># elif ..:</span></span><br><span class="line"> <span class="comment"># else:</span></span><br></pre></td></tr></table></figure>
<p><img src="/2023/12/22/%E3%80%90esp32%E3%80%91espremote%E5%AE%9E%E7%8E%B0OTA%E5%9C%A8%E7%BA%BF%E6%9B%B4%E6%96%B0%E7%A8%8B%E5%BA%8F/result.png"></p>
<hr>
<p><a href="https://github.com/Bonjir/esp32/tree/main/espremote">espremote模块</a>是一个能够给予特定协议远程控制esp32开发板的实用工具。利用Espremote可以远程连接板子、管理文件系统以及上传、下载文件。 </p>
<p>该模块的pc端、esp32端模块和示例程序已经放在<a href="https://github.com/Bonjir/esp32/tree/main/espremote">本人的仓库</a>里了,欢迎大家取用。</p>
]]></content>
<categories>
<category>技术笔记</category>
<category>esp32</category>
</categories>
<tags>
<tag>esp32</tag>
<tag>micropython</tag>
<tag>python</tag>
<tag>网络</tag>
<tag>socket</tag>
</tags>
</entry>
</search>