Open-source weather station for astronomy
1import network
2import socket
3import ure
4import time
5
6ap_ssid = "ESP32-weather"
7ap_password = "station"
8ap_authmode = 3 # WPA2
9
10NETWORK_PROFILES = 'wifi.dat'
11
12wlan_ap = network.WLAN(network.AP_IF)
13wlan_sta = network.WLAN(network.STA_IF)
14
15server_socket = None
16
17
18def get_connection():
19 """return a working WLAN(STA_IF) instance or None"""
20
21 # First check if there already is any connection:
22 if wlan_sta.isconnected():
23 return wlan_sta
24
25 connected = False
26 try:
27 # ESP connecting to WiFi takes time, wait a bit and try again:
28 time.sleep(3)
29 if wlan_sta.isconnected():
30 return wlan_sta
31
32 # Read known network profiles from file
33 profiles = read_profiles()
34
35 # Search WiFis in range
36 wlan_sta.active(True)
37 networks = wlan_sta.scan()
38
39 AUTHMODE = {0: "open", 1: "WEP", 2: "WPA-PSK", 3: "WPA2-PSK", 4: "WPA/WPA2-PSK"}
40 for ssid, bssid, channel, rssi, authmode, hidden in sorted(networks, key=lambda x: x[3], reverse=True):
41 ssid = ssid.decode('utf-8')
42 encrypted = authmode > 0
43 print("ssid: %s chan: %d rssi: %d authmode: %s" % (ssid, channel, rssi, AUTHMODE.get(authmode, '?')))
44 if encrypted:
45 if ssid in profiles:
46 password = profiles[ssid]
47 connected = do_connect(ssid, password)
48 else:
49 print("skipping unknown encrypted network")
50 else: # open
51 connected = do_connect(ssid, None)
52 if connected:
53 break
54
55 except OSError as e:
56 print("exception", str(e))
57
58 # start web server for connection manager:
59 if not connected:
60 connected = start()
61
62 return wlan_sta if connected else None
63
64
65def read_profiles():
66 with open(NETWORK_PROFILES) as f:
67 lines = f.readlines()
68 profiles = {}
69 for line in lines:
70 ssid, password = line.strip("\n").split(";")
71 profiles[ssid] = password
72 return profiles
73
74
75def write_profiles(profiles):
76 lines = []
77 for ssid, password in profiles.items():
78 lines.append("%s;%s\n" % (ssid, password))
79 with open(NETWORK_PROFILES, "w") as f:
80 f.write(''.join(lines))
81
82
83def do_connect(ssid, password):
84 wlan_sta.active(True)
85 if wlan_sta.isconnected():
86 return None
87 print('Trying to connect to %s...' % ssid)
88 wlan_sta.connect(ssid, password)
89 for retry in range(200):
90 connected = wlan_sta.isconnected()
91 if connected:
92 break
93 time.sleep(0.1)
94 print('.', end='')
95 if connected:
96 print('\nConnected. Network config: ', wlan_sta.ifconfig())
97
98 else:
99 print('\nFailed. Not Connected to: ' + ssid)
100 return connected
101
102
103def send_header(client, status_code=200, content_length=None ):
104 client.sendall("HTTP/1.0 {} OK\r\n".format(status_code))
105 client.sendall("Content-Type: text/html\r\n")
106 if content_length is not None:
107 client.sendall("Content-Length: {}\r\n".format(content_length))
108 client.sendall("\r\n")
109
110
111def send_response(client, payload, status_code=200):
112 content_length = len(payload)
113 send_header(client, status_code, content_length)
114 if content_length > 0:
115 client.sendall(payload)
116 client.close()
117
118
119def handle_root(client):
120 wlan_sta.active(True)
121 ssids = sorted(ssid.decode('utf-8') for ssid, *_ in wlan_sta.scan())
122 send_header(client)
123 client.sendall("""\
124 <html>
125 <h1 style="color: #5e9ca0; text-align: center;">
126 <span style="color: #ff0000;">
127 Wi-Fi Client Setup
128 </span>
129 </h1>
130 <form action="configure" method="post">
131 <table style="margin-left: auto; margin-right: auto;">
132 <tbody>
133 """)
134 while len(ssids):
135 ssid = ssids.pop(0)
136 client.sendall("""\
137 <tr>
138 <td colspan="2">
139 <input type="radio" name="ssid" value="{0}" />{0}
140 </td>
141 </tr>
142 """.format(ssid))
143 client.sendall("""\
144 <tr>
145 <td>Password:</td>
146 <td><input name="password" type="password" /></td>
147 </tr>
148 </tbody>
149 </table>
150 <p style="text-align: center;">
151 <input type="submit" value="Submit" />
152 </p>
153 </form>
154 <p> </p>
155 <hr />
156 <h5>
157 <span style="color: #ff0000;">
158 Your ssid and password information will be saved into the
159 "%(filename)s" file in your ESP module for future usage.
160 Be careful about security!
161 </span>
162 </h5>
163 <hr />
164 <h2 style="color: #2e6c80;">
165 Some useful infos:
166 </h2>
167 <ul>
168 <li>
169 Original code from <a href="https://github.com/cpopp/MicroPythonSamples"
170 target="_blank" rel="noopener">cpopp/MicroPythonSamples</a>.
171 </li>
172 <li>
173 This code available at <a href="https://github.com/tayfunulu/WiFiManager"
174 target="_blank" rel="noopener">tayfunulu/WiFiManager</a>.
175 </li>
176 </ul>
177 </html>
178 """ % dict(filename=NETWORK_PROFILES))
179 client.close()
180
181
182def handle_configure(client, request):
183 match = ure.search("ssid=([^&]*)&password=(.*)", request)
184
185 if match is None:
186 send_response(client, "Parameters not found", status_code=400)
187 return False
188 # version 1.9 compatibility
189 try:
190 ssid = match.group(1).decode("utf-8").replace("%3F", "?").replace("%21", "!")
191 password = match.group(2).decode("utf-8").replace("%3F", "?").replace("%21", "!")
192 except Exception:
193 ssid = match.group(1).replace("%3F", "?").replace("%21", "!")
194 password = match.group(2).replace("%3F", "?").replace("%21", "!")
195
196 if len(ssid) == 0:
197 send_response(client, "SSID must be provided", status_code=400)
198 return False
199
200 if do_connect(ssid, password):
201 response = """\
202 <html>
203 <center>
204 <br><br>
205 <h1 style="color: #5e9ca0; text-align: center;">
206 <span style="color: #ff0000;">
207 ESP successfully connected to WiFi network %(ssid)s.
208 </span>
209 </h1>
210 <br><br>
211 </center>
212 </html>
213 """ % dict(ssid=ssid)
214 send_response(client, response)
215 time.sleep(1)
216 wlan_ap.active(False)
217 try:
218 profiles = read_profiles()
219 except OSError:
220 profiles = {}
221 profiles[ssid] = password
222 write_profiles(profiles)
223
224 time.sleep(5)
225
226 return True
227 else:
228 response = """\
229 <html>
230 <center>
231 <h1 style="color: #5e9ca0; text-align: center;">
232 <span style="color: #ff0000;">
233 ESP could not connect to WiFi network %(ssid)s.
234 </span>
235 </h1>
236 <br><br>
237 <form>
238 <input type="button" value="Go back!" onclick="history.back()"></input>
239 </form>
240 </center>
241 </html>
242 """ % dict(ssid=ssid)
243 send_response(client, response)
244 return False
245
246
247def handle_not_found(client, url):
248 send_response(client, "Path not found: {}".format(url), status_code=404)
249
250
251def stop():
252 global server_socket
253
254 if server_socket:
255 server_socket.close()
256 server_socket = None
257
258
259def start(port=80):
260 global server_socket
261
262 addr = socket.getaddrinfo('0.0.0.0', port)[0][-1]
263
264 stop()
265
266 wlan_sta.active(True)
267 wlan_ap.active(True)
268
269 wlan_ap.config(essid=ap_ssid, password=ap_password)
270
271 server_socket = socket.socket()
272 server_socket.bind(addr)
273 server_socket.listen(1)
274
275 print('Connect to WiFi ssid ' + ap_ssid + ', default password: ' + ap_password)
276 print('and access the ESP via your favorite web browser at 192.168.4.1.')
277 print('Listening on:', addr)
278
279 while True:
280 if wlan_sta.isconnected():
281 wlan_ap.active(False)
282 return True
283
284 client, addr = server_socket.accept()
285 print('client connected from', addr)
286 try:
287 client.settimeout(5.0)
288
289 request = b""
290 try:
291 while "\r\n\r\n" not in request:
292 request += client.recv(512)
293 except OSError:
294 pass
295
296 # Handle form data from Safari on macOS and iOS; it sends \r\n\r\nssid=<ssid>&password=<password>
297 try:
298 request += client.recv(1024)
299 print("Received form data after \\r\\n\\r\\n(i.e. from Safari on macOS or iOS)")
300 except OSError:
301 pass
302
303 print("Request is: {}".format(request))
304 if "HTTP" not in request: # skip invalid requests
305 continue
306
307 # version 1.9 compatibility
308 try:
309 url = ure.search("(?:GET|POST) /(.*?)(?:\\?.*?)? HTTP", request).group(1).decode("utf-8").rstrip("/")
310 except Exception:
311 url = ure.search("(?:GET|POST) /(.*?)(?:\\?.*?)? HTTP", request).group(1).rstrip("/")
312 print("URL is {}".format(url))
313
314 if url == "":
315 handle_root(client)
316 elif url == "configure":
317 handle_configure(client, request)
318 else:
319 handle_not_found(client, url)
320
321 finally:
322 client.close()