├── LICENSE ├── README.md ├── boot.py ├── configserver.py ├── html ├── index.html ├── main.css └── templates │ ├── wifi-add.tpl │ ├── wifi-saved.tpl │ ├── wifi-scan.tpl │ └── wifi-status.tpl ├── main.py ├── screenshot.png └── wifi_database.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Carsten B. L. Tschense 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # micropython-configserver 2 | Captive portal for micropython including a dumb DNS server and a webserver to configure wifi networks. 3 | -------------------------------------------------------------------------------- /boot.py: -------------------------------------------------------------------------------- 1 | import gc 2 | import webrepl 3 | import network 4 | import wifi_database 5 | import time 6 | gc.collect() 7 | 8 | sta_if = network.WLAN(network.STA_IF) 9 | sta_if.active(True) 10 | 11 | for wifi in wifi_database.iter_wifis(): 12 | sta_if.connect(wifi[0], wifi[1]) 13 | for i in range(50): 14 | time.sleep_ms(100) 15 | if sta_if.isconnected(): 16 | wifi_database.active_wifi = wifi[0] 17 | break 18 | if wifi_database.active_wifi is not None: 19 | break 20 | if wifi_database.active_wifi is None: 21 | sta_if.active(False) 22 | 23 | webrepl.start() 24 | gc.collect() -------------------------------------------------------------------------------- /configserver.py: -------------------------------------------------------------------------------- 1 | import picoweb 2 | import uasyncio as asyncio 3 | import gc 4 | import network 5 | import socket 6 | import ustruct 7 | import ure as re 8 | import wifi_database 9 | from picoweb.utils import parse_qs 10 | import utime 11 | gc.collect() 12 | 13 | class HTTPRequest: 14 | 15 | def __init__(self): 16 | pass 17 | 18 | def read_form_data(self): 19 | size = int(self.headers["Content-Length"]) 20 | data = yield from self.reader.readline() 21 | line = data.decode() 22 | if line.startswith('------'): 23 | boundary = line 24 | chars_read = len(line) 25 | self.form = {} 26 | while True: 27 | data = yield from self.reader.readline() 28 | line = data.decode() 29 | chars_read += len(line) 30 | if line == boundary: 31 | continue 32 | if chars_read >= size or line == boundary.strip() + '--\r\n': 33 | break 34 | elif line.startswith('Content-Disposition: form-data;'): 35 | field = re.compile('.*=\"(.*)\"').match(line).group(1) 36 | elif line != '\r\n': 37 | self.form[field] = line.strip() 38 | else: 39 | form = parse_qs(line) 40 | self.form = form 41 | 42 | class MyWebApp(picoweb.WebApp): 43 | def get_task(self, host='0.0.0.0', port=80, debug=False): 44 | gc.collect() 45 | self.debug = int(debug) 46 | self.init() 47 | if debug: 48 | print("* Running on http://%s:%s/" % (host, port)) 49 | return asyncio.start_server(self._handle, host, port) 50 | 51 | 52 | def handle_template(req, resp): 53 | if req.method == 'POST': 54 | yield from req.read_form_data() 55 | yield from picoweb.start_response(resp) 56 | yield from app.render_template(resp, req.url_match.group(1), (req, )) 57 | 58 | 59 | async def dns_server(): 60 | myip = network.WLAN(network.AP_IF).ifconfig()[0] 61 | print('binding to', myip) 62 | mysocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 63 | mysocket.bind(socket.getaddrinfo(myip, 53)[0][-1]) 64 | while True: 65 | yield uasyncio.IORead(mysocket) 66 | mysocket.setblocking(False) 67 | packet, address = mysocket.recvfrom(256) 68 | # is query? opcode 0? 1 question? 69 | if (packet[2] & 0x80 == 0x0 and packet[2] & 0xf0 == 0x0 and packet[4:6] == b'\x00\x01'): 70 | # we take the request, change a few bits and append the answer 71 | response = bytearray(packet) 72 | response[2] |= 0x80 # change from query to response 73 | response[3] = 0 # recursion not available and responsecode stays 0 74 | response[7] = 1 # number of answers 75 | response += b'\xc0\x0c' # stuff 76 | response += b'\x00\x01' # A entry 77 | response += b'\x00\x01' # class IN 78 | response += b'\x00\x00\x00\x00' # TTL 0 79 | response += b'\x00\x04' # length of address 80 | response += ustruct.pack('BBBB', *[int(x) for x in myip.split('.')]) 81 | mysocket.sendto(response, address) 82 | 83 | ROUTES = [ 84 | ('/', lambda req, resp: (yield from app.sendfile(resp, 'index.html'))), 85 | (re.compile("^/(.*\.htm[l]?)$"), lambda req, resp: (yield from app.handle_static(req, resp))), 86 | (re.compile("^/(.*\.css)$"), lambda req, resp: (yield from app.handle_static(req, resp))), 87 | (re.compile("^/(.+\.tpl)($|\?.*)"), lambda req, resp: (yield from handle_template(req, resp))), 88 | ] 89 | app = MyWebApp(pkg='html', serve_static=False, routes=ROUTES) 90 | gc.collect() 91 | app._load_template('wifi-scan.tpl') 92 | app._load_template('wifi-add.tpl') 93 | app._load_template('wifi-status.tpl') 94 | app._load_template('wifi-saved.tpl') 95 | gc.collect() 96 | -------------------------------------------------------------------------------- /html/index.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |