├── README.md ├── boot.py ├── config.json ├── index.html └── main.py /README.md: -------------------------------------------------------------------------------- 1 | ## micropython-captive-portal-network-setup 2 | A MicroPython Captive Portal for Setting Network Options on the Fly 3 | 4 | ## Notes 5 | This code is **heavily** based on the work done by Amora Labs. See their code [here](https://github.com/amora-labs/micropython-captive-portal). 6 | 7 | This code allows a user to connect to the NodeMCU's WiFi, navigate to ```192.168.4.1``` and set the network name and password. A walk through and more details are available on [my blog](http://blog.brockman.io/?p=54). 8 | -------------------------------------------------------------------------------- /boot.py: -------------------------------------------------------------------------------- 1 | import gc 2 | import network 3 | import time 4 | import usocket as socket 5 | import ure 6 | import ujson 7 | 8 | 9 | gc.enable() 10 | CONTENT = open('index.html', 'r').read() 11 | 12 | 13 | def connection(network_name, network_password): 14 | attempts = 0 15 | station = network.WLAN(network.STA_IF) 16 | if not station.isconnected(): 17 | print("Connecting to network...") 18 | station.active(True) 19 | station.connect(network_name, network_password) 20 | while not station.isconnected(): 21 | print("Attempts: {}".format(attempts)) 22 | attempts += 1 23 | time.sleep(5) 24 | if attempts > 3: 25 | return False 26 | break 27 | print('Network Config:', station.ifconfig()) 28 | return True 29 | 30 | 31 | class DNSQuery: 32 | def __init__(self, data): 33 | self.data = data 34 | self.domain = '' 35 | 36 | m = data[2] 37 | tipo = (m >> 3) & 15 38 | if tipo == 0: 39 | ini = 12 40 | lon = data[ini] 41 | while lon != 0: 42 | self.domain += data[ini+1:ini+lon+1].decode("utf-8") + "." 43 | ini += lon+1 44 | lon = data[ini] 45 | 46 | 47 | def response(self, ip): 48 | packet = b'' 49 | print("Response {} == {}".format(self.domain, ip)) 50 | if self.domain: 51 | packet += self.data[:2] + b"\x81\x80" 52 | packet += self.data[4:6] + b"\x00\x00\x00\x00" 53 | packet += self.data[12:] 54 | packet += b"\xc0\x0c" 55 | packet += b"\x00\x01\x00\x01\x00\x00\x00\x3c\x00\x04" 56 | packet += bytes(map(int, ip.split("."))) 57 | return packet 58 | 59 | 60 | def captive_portal(essid_name): 61 | existing_config = False 62 | try: 63 | f = open("config.json", "r") 64 | configs = f.read() 65 | j = ujson.loads(configs) 66 | print(j) 67 | f.close() 68 | con = connection(j['network_name'], j['network_password']) 69 | if con is True: 70 | existing_config = True 71 | print("Network connected") 72 | ap = network.WLAN(network.AP_IF) 73 | ap.active(False) # turn off AP SSID 74 | else: 75 | existing_config = False 76 | print("Incorrect network configuration") 77 | except: 78 | print("No saved network") 79 | 80 | if existing_config is False: 81 | ap = network.WLAN(network.AP_IF) 82 | ap.active(True) 83 | ap.config(essid=essid_name, authmode=1) 84 | ip = ap.ifconfig()[0] 85 | print("DNS Server: dom.query. 60 in A {:s}".format(ip)) 86 | 87 | udps = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 88 | udps.setblocking(False) 89 | udps.bind(('',53)) 90 | 91 | s = socket.socket() 92 | ai = socket.getaddrinfo(ip, 80) 93 | print("Web Server: Bind address information: ", ai) 94 | addr = ai[0][-1] 95 | 96 | s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 97 | s.bind(addr) 98 | s.listen(1) 99 | s.settimeout(2) 100 | print("Web Server: Listening http://{}:80/".format(ip)) 101 | 102 | regex = ure.compile("network\?ssid=(.*?)&password=(.*?)\sHTTP") 103 | 104 | set_connection = False 105 | while set_connection is False: 106 | try: 107 | data, addr = udps.recvfrom(4096) 108 | print("Incoming data...") 109 | DNS = DNSQuery(data) 110 | udps.sendto(DNS.response(ip), addr) 111 | print("Replying: {:s} -> {:s}".format(DNS.domain, ip)) 112 | except: 113 | print("No DNS") 114 | 115 | try: 116 | res = s.accept() 117 | client_sock = res[0] 118 | client_addr = res[1] 119 | 120 | req = client_sock.recv(4096) 121 | print("Request:") 122 | print(req) 123 | client_sock.send(CONTENT) 124 | client_sock.close() 125 | print() 126 | search_result = regex.search(req) 127 | if search_result: 128 | incoming_network_name = search_result.group(1) 129 | incoming_network_pass = search_result.group(2) 130 | con = connection(incoming_network_name, incoming_network_pass) 131 | if con is True: 132 | d = {"network_name": incoming_network_name, "network_password": incoming_network_pass} 133 | f = open("config.json", "w") 134 | f.write(ujson.dumps(d)) 135 | f.close() 136 | set_connection = True 137 | 138 | 139 | except: 140 | print("Timeout") 141 | time.sleep_ms(1000) 142 | udps.close() 143 | 144 | 145 | def main(): 146 | captive_portal("WeatherApp") 147 | 148 | 149 | main() 150 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matt4/micropython-captive-portal-network-setup/8c998c381dba896fa20d888062173d987c10904b/config.json -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | HTTP/1.0 200 OK 2 | 3 | 4 | 5 |
6 |