├── .gitignore ├── ESP8266WebServer.py ├── README.md ├── Simple_led.py ├── TestWebServer.py ├── index.p.html ├── main.py ├── www └── index.html └── www2 ├── index.html ├── index.p.html └── test.css /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | -------------------------------------------------------------------------------- /ESP8266WebServer.py: -------------------------------------------------------------------------------- 1 | """A simple HTTP server that only accept GET request 2 | It adopt the programming style of ESP8266WebServer 3 | library in ESP8266 Arduino Core 4 | """ 5 | 6 | import network 7 | import machine 8 | import socket 9 | import uselect 10 | import os 11 | 12 | server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 13 | server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 14 | 15 | # Use for checking a new client connection 16 | poller = uselect.poll() 17 | # Dict for registed handlers of all paths 18 | handlers = {} 19 | # Function of handler for request not found 20 | notFoundHandler = None 21 | # The path to the web documents on MicroPython filesystem 22 | docPath = "/" 23 | # Data for template 24 | tplData = {} 25 | # Max. POST/PUT-Data size 26 | maxContentLength = 1024 27 | 28 | # MIME types 29 | mimeTypes = { 30 | ".css":"text/css", 31 | ".jpg":"image/jpg", 32 | ".png":"image/png", 33 | } 34 | 35 | def begin(port=80): 36 | """Function to start http server 37 | """ 38 | global server, poller 39 | server.bind(('0.0.0.0', port)) 40 | server.listen(1) 41 | # Register for checking new client connection 42 | poller.register(server, uselect.POLLIN) 43 | 44 | def close(): 45 | """Function to stop http server 46 | """ 47 | poller.unregister(server) 48 | server.close() 49 | 50 | def handleClient(): 51 | """Check for new client connection and process the request 52 | """ 53 | global server, poller 54 | # Note:don't call poll() with 0, that would randomly cause 55 | # reset with "Fatal exception 28(LoadProhibitedCause)" message 56 | res = poller.poll(1) 57 | if res: # There's a new client connection 58 | (socket, sockaddr) = server.accept() 59 | socket.settimeout(0.02) # set timeout for readline to avoid blocking 60 | handle(socket) 61 | socket.close() 62 | 63 | def __sendPage(socket, filePath): 64 | """Send the file as webpage to client 65 | """ 66 | try: 67 | f = open(filePath, "rb") 68 | while True: 69 | data = f.read(64) 70 | if (data == b""): 71 | break 72 | socket.write(data) 73 | f.close() 74 | except Exception as e: 75 | print(e) 76 | 77 | 78 | def err(socket, code, message): 79 | """Respong error meesage to client 80 | """ 81 | socket.write("HTTP/1.1 " + code + " " + message + "\r\n") 82 | socket.write("Content-Type: text/html\r\n\r\n") 83 | socket.write("

" + message + "

") 84 | 85 | def ok(socket, code, *args): 86 | """Response successful message or webpage to client 87 | """ 88 | if len(args)==1: 89 | content_type = "text/plain" 90 | msg = args[0] 91 | elif len(args)==2: 92 | content_type = args[0] 93 | msg = args[1] 94 | else: 95 | raise TypeError("ok() takes 3 or 4 positional arguments but "+ str(len(args)+2) +" were given") 96 | socket.write("HTTP/1.1 " + code + " OK\r\n") 97 | socket.write("Content-Type: " + content_type + "\r\n\r\n") 98 | # if __fileExist(msg): 99 | # filePath = msg 100 | # __sendPage(socket, filePath) 101 | # else: 102 | socket.write(msg) 103 | 104 | def __fileExist(path): 105 | """Check for file existence 106 | """ 107 | print(path) 108 | try: 109 | stat = os.stat(path) 110 | # stat[0] bit 15 / 14 -> file/dir 111 | if stat[0] & 0x8000 == 0x8000: # file 112 | print("Found.") 113 | return True 114 | else: # Dir 115 | return False 116 | except: 117 | print("Not Found.") 118 | return False 119 | 120 | def __serveFile(socket, path): 121 | """Serves a file from the filesystem 122 | """ 123 | filePath = path 124 | fileFound = True 125 | # find the file 126 | if not __fileExist(filePath): 127 | if not path.endswith("/"): 128 | fileFound = False 129 | else: 130 | filePath = path + "index.html" 131 | # find index.html in the path 132 | if not __fileExist(filePath): 133 | filePath = path + "index.p.html" 134 | # find index.p.html in the path 135 | if not __fileExist(filePath): # no default html file found 136 | fileFound = False 137 | if not fileFound: # file or default html file specified in path not found 138 | if notFoundHandler: 139 | notFoundHandler(socket) 140 | else: 141 | err(socket, "404", "Not Found") 142 | return 143 | # Responds the header first 144 | socket.write("HTTP/1.1 200 OK\r\n") 145 | contentType = "text/html" 146 | for ext in mimeTypes: 147 | if filePath.endswith(ext): 148 | contentType = mimeTypes[ext] 149 | socket.write("Content-Type: " + contentType + "\r\n\r\n") 150 | # Responds the file content 151 | if filePath.endswith(".p.html"): 152 | f = open(filePath, "r") 153 | for l in f: 154 | socket.write(l.format(**tplData)) 155 | f.close() 156 | else: 157 | __sendPage(socket, filePath) 158 | 159 | def handle(socket): 160 | """Processing new GET request 161 | """ 162 | global docPath, handlers 163 | try: # capture timeout for wainting a line 164 | currLine = str(socket.readline(), 'utf-8') 165 | except: 166 | currLine = "" # readline timeout (not a complete line) 167 | request = currLine.split(" ") 168 | if len(request) != 3: # Discarded if it's a bad header 169 | return 170 | (method, url, version) = request 171 | if "?" in url: # Check if there's query string? 172 | (path, query) = url.split("?", 2) 173 | else: 174 | (path, query) = (url, "") 175 | args = {} 176 | contentType = "" 177 | content = b"" 178 | contentLength = 0 179 | 180 | if query: # Parsing the querying string 181 | argPairs = query.split("&") 182 | for argPair in argPairs: 183 | arg = argPair.split("=") 184 | args[arg[0]] = arg[1] 185 | 186 | while True: # Read until blank line after header 187 | header = socket.readline() 188 | if header.startswith(b"Content-Length"): 189 | (key, contentLengthStr) = str(header).split(" ") 190 | contentLength = int(contentLengthStr[0:-5]) 191 | if (contentLength > maxContentLength): 192 | err(socket, "400", "Bad Request") 193 | return 194 | if (header.startswith(b"Content-Type")): 195 | (key, contentType) = str(header).split(" ") 196 | contentType = contentType[0:-5] 197 | if (header == b""): 198 | return 199 | if (header == b"\r\n" and contentLength > 0): 200 | while(len(content) < contentLength): 201 | content = content + socket.recv(contentLength) 202 | if (len(content) > maxContentLength): 203 | err(socket, "400", "Bad Request") 204 | return 205 | break 206 | elif header == b"\r\n": 207 | break 208 | 209 | # Check for supported HTTP version 210 | if version != "HTTP/1.0\r\n" and version != "HTTP/1.1\r\n": 211 | err(socket, "505", "Version Not Supported") 212 | elif (method != "GET" and method != "PUT" and method != "POST"): # Only accept GET,PUT and POST request 213 | err(socket, "501", "Not Implemented") 214 | elif path in handlers: # Check for registered path 215 | handlers[path](socket, args, method, contentType, content) 216 | elif not path.startswith(docPath): # Check for wrong path 217 | err(socket, "400", "Bad Request") 218 | else: # find file in the document path 219 | filePath = path 220 | print("Serve File " + filePath) 221 | __serveFile(socket, filePath) 222 | 223 | 224 | def onPath(path, handler): 225 | """Register handler for processing request of specified path 226 | """ 227 | global handlers 228 | handlers[path] = handler 229 | 230 | def onNotFound(handler): 231 | """Register handler for processing request of not found path 232 | """ 233 | global notFoundHandler 234 | notFoundHandler = handler 235 | 236 | def setDocPath(path): 237 | """Set the path to documents' directory 238 | """ 239 | global docPath 240 | docPath = path 241 | 242 | def setTplData(data): 243 | """Set data for template 244 | """ 245 | global tplData 246 | tplData = data 247 | 248 | def setMaxContentLength(max): 249 | """Set the maximum content lenpth for incomming data bodies 250 | """ 251 | global maxContentLength 252 | maxContentLength = max 253 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This Library is no longer supported. Visit https://github.com/codemee/ESPWebServer for a version that supports ESP8266 and ESP32. 2 | 3 | ---- 4 | # ESP8266WebServer 5 | 6 | This is a very lightweight web server for MicroPython on ESP8266. It only accept GET, POST and PUT requests. It adopts the programming style of ESP8266WebServer library in ESP8266 Arduino Core.This make it suitable for serving REST API.The original code was inspired from the project [Controlling a GPIO through an ESP8266-based web server](https://lab.whitequark.org/notes/2016-10-20/controlling-a-gpio-through-an-esp8266-based-web-server/). 7 | 8 | ## Installation 9 | 10 | Just upload ESP8266WebServer.py to your ESP8266 board and you're done. 11 | 12 | ## Usage 13 | 14 | To use ESP8266WebServer.py library, you should: 15 | 16 | 1. Write functions as handlers for each path you'd like to serve contents. 17 | 18 | 1. Start the server by calling begin(). 19 | 20 | 1. Regsiter the handlers you just prepared by calling onPath(). 21 | 22 | 1. You can also uploading HTML files onto somewhere in the filesystem and settnig the document path by calling setDocPath(). 23 | 24 | 1. Call handleClient() repeatly to process requests. 25 | 26 | ### Documents and Templates 27 | 28 | With setDocPath(), you can spcicified the path for all html files. For examples, if you call setDocPath('www'), and put index.html into /www, you can browse the file with 'http://server_ip/www/index.html. 29 | 30 | If you put a file with suffix '.p.html', the server would do formatting processing before output the content. You should first call setTplData() with a dictionary before accessing any template file, the server uses the elements in the dictionary to replacing all the formatting string in the template file. 31 | 32 | If you access the document path without filename, the server would try to find out if there's a index.html or index.p.html file existed, and output the file. 33 | 34 | ## Function reference 35 | 36 | ### begin(port) 37 | 38 | Start the server at specified *port*. 39 | 40 | ### onPath(path, handler) 41 | 42 | Register *handler* for processing request with matching *path* 43 | 44 | ### setDocPath(path) 45 | 46 | Specified the directory in the filesystem containing all the HTML files. 47 | 48 | ### setTplData(dic) 49 | 50 | Specified the dictionary for template file. `dic` sould be a dictionary with all keys are string and contains all the names in replacing fields in all the template files. 51 | 52 | ### setMaxContentLength(size) 53 | 54 | Defines the maximum Content Length of incoming request bodies (POST, PUT) in bytes. Default: 1024 55 | 56 | ### handleClient() 57 | 58 | Check for new request and call corresponding handler to process it. 59 | 60 | ## Examples 61 | 62 | You can upload www directory and index.p.html to "/" on ESP8266 board and run TestWebServer.py to see how it works. 63 | 64 | `main.py` contains an example for handling Request Body content. 65 | 66 | TestWebServer.py will show its own IP address through serial monitor.Just open your browser and connect it to http://serverIP:8899 or http://serverIP:8899/index.p.html, you'll get the main page that can turn on/off the buildin led on ESP8266 board. The main page also demonstrate the template file usage. 67 | 68 | You can also open http://serverip:8899/www/index.html or http://serverip:8899/www/ to view alternative version of controlling page that use AJAX to asynchronously turn on/off led. 69 | 70 | You can use http://serverip:8899/switch to switch led on/off led directly. Or you can use http://serverip:8899/cmd?led=on to turn the led on and http://serverip:8899/cmd?led=off to turn the led off. 71 | -------------------------------------------------------------------------------- /Simple_led.py: -------------------------------------------------------------------------------- 1 | import ESP8266WebServer 2 | import network 3 | import machine 4 | 5 | GPIO_NUM = 2 # Builtin led (D4) 6 | 7 | # Wi-Fi configuration 8 | STA_SSID = "MEE_MI" 9 | STA_PSK = "PinkFloyd1969" 10 | 11 | # Disable AP interface 12 | ap_if = network.WLAN(network.AP_IF) 13 | if ap_if.active(): 14 | ap_if.active(False) 15 | 16 | # Connect to Wi-Fi if not connected 17 | sta_if = network.WLAN(network.STA_IF) 18 | if not ap_if.active(): 19 | sta_if.active(True) 20 | if not sta_if.isconnected(): 21 | sta_if.connect(STA_SSID, STA_PSK) 22 | # Wait for connecting to Wi-Fi 23 | while not sta_if.isconnected(): 24 | pass 25 | 26 | # Show IP address 27 | print("Server started @", sta_if.ifconfig()[0]) 28 | 29 | # Get pin object for controlling builtin LED 30 | pin = machine.Pin(GPIO_NUM, machine.Pin.OUT) 31 | pin.on() # Turn LED off (it use sinking input) 32 | 33 | # Handler for path "/cmd?led=[on|off]" 34 | def handleCmd(socket, args): 35 | global ledData 36 | if 'led' in args: 37 | if args['led'] == 'on': 38 | ledData["status"]="ON" 39 | pin.off() 40 | elif args['led'] == 'off': 41 | ledData["status"]="OFF" 42 | pin.on() 43 | ESP8266WebServer.ok(socket, "200", args["led"]) 44 | else: 45 | ESP8266WebServer.err(socket, "400", "Bad Request") 46 | 47 | # Start the server @ port 8899 48 | ESP8266WebServer.begin(8899) 49 | 50 | # Register handler for each path 51 | ESP8266WebServer.onPath("/cmd", handleCmd) 52 | ESP8266WebServer.setDocPath("/www2") 53 | 54 | ledData = { 55 | "status":"Off", 56 | } 57 | ESP8266WebServer.setTplData(ledData) 58 | 59 | try: 60 | while True: 61 | # Let server process requests 62 | ESP8266WebServer.handleClient() 63 | except: 64 | ESP8266WebServer.close() 65 | -------------------------------------------------------------------------------- /TestWebServer.py: -------------------------------------------------------------------------------- 1 | import ESP8266WebServer 2 | import network 3 | import machine 4 | 5 | GPIO_NUM = 2 # Builtin led (D4) 6 | 7 | # Wi-Fi configuration 8 | STA_SSID = "MEE_MI" 9 | STA_PSK = "PinkFloyd1969" 10 | 11 | # Disable AP interface 12 | ap_if = network.WLAN(network.AP_IF) 13 | if ap_if.active(): 14 | ap_if.active(False) 15 | 16 | # Connect to Wi-Fi if not connected 17 | sta_if = network.WLAN(network.STA_IF) 18 | if not ap_if.active(): 19 | sta_if.active(True) 20 | if not sta_if.isconnected(): 21 | sta_if.connect(STA_SSID, STA_PSK) 22 | # Wait for connecting to Wi-Fi 23 | while not sta_if.isconnected(): 24 | pass 25 | 26 | # Show IP address 27 | print("Server started @", sta_if.ifconfig()[0]) 28 | 29 | # Get pin object for controlling builtin LED 30 | pin = machine.Pin(GPIO_NUM, machine.Pin.OUT) 31 | pin.on() # Turn LED off (it use sinking input) 32 | 33 | # Dictionary for template file 34 | ledData = { 35 | "title":"Remote LED", 36 | "color":"red", 37 | "status":"Off", 38 | "switch":"on" 39 | } 40 | 41 | # Update information 42 | def updateInfo(socket): 43 | global ledData, color, status, switch 44 | ledData["color"] = "red" if pin.value() else "green" 45 | ledData["status"] = "Off" if pin.value() else "On" 46 | ledData["switch"] = "on" if pin.value() else "off" 47 | ESP8266WebServer.ok( 48 | socket, 49 | "200", 50 | ledData["status"]) 51 | 52 | # Handler for path "/cmd?led=[on|off]" 53 | def handleCmd(socket, args): 54 | if 'led' in args: 55 | if args['led'] == 'on': 56 | pin.off() 57 | elif args['led'] == 'off': 58 | pin.on() 59 | updateInfo(socket) 60 | else: 61 | ESP8266WebServer.err(socket, "400", "Bad Request") 62 | 63 | # handler for path "/switch" 64 | def handleSwitch(socket, args): 65 | pin.value(not pin.value()) # Switch back and forth 66 | updateInfo(socket) 67 | 68 | # Start the server @ port 8899 69 | # ESP8266WebServer.begin(8899) 70 | ESP8266WebServer.begin() # use default 80 port 71 | 72 | # Register handler for each path 73 | # ESP8266WebServer.onPath("/", handleRoot) 74 | ESP8266WebServer.onPath("/cmd", handleCmd) 75 | ESP8266WebServer.onPath("/switch", handleSwitch) 76 | 77 | # Setting the path to documents 78 | ESP8266WebServer.setDocPath("/") 79 | 80 | # Setting data for template 81 | ESP8266WebServer.setTplData(ledData) 82 | 83 | try: 84 | while True: 85 | # Let server process requests 86 | ESP8266WebServer.handleClient() 87 | except: 88 | ESP8266WebServer.close() 89 | -------------------------------------------------------------------------------- /index.p.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {title} 6 | 7 | {title} Status:{status}
8 | Turn {switch}
9 | HOME 10 | 11 |
12 |
13 |
14 |
15 |
16 |

17 | 18 |
19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import ESP8266WebServer 2 | import network 3 | import machine 4 | 5 | GPIO_NUM = 2 # Builtin led (D4) 6 | # Get pin object for controlling builtin LED 7 | pin = machine.Pin(GPIO_NUM, machine.Pin.OUT) 8 | pin.on() # Turn LED off (it use sinking input) 9 | 10 | # Dictionary for template file 11 | ledData = { 12 | "title":"Remote LED", 13 | "color":"red", 14 | "status":"Off", 15 | "switch":"on" 16 | } 17 | 18 | # Update information 19 | def updateInfo(socket): 20 | global ledData, color, status, switch 21 | ledData["color"] = "red" if pin.value() else "green" 22 | ledData["status"] = "Off" if pin.value() else "On" 23 | ledData["switch"] = "on" if pin.value() else "off" 24 | ESP8266WebServer.ok( 25 | socket, 26 | "200", 27 | ledData["status"]) 28 | 29 | def handleStop(socket): 30 | ESP8266WebServer.ok( 31 | socket, 32 | "200", 33 | "stopped") 34 | running = False 35 | ESP8266WebServer.close() 36 | 37 | def handlePost(socket, args, method, contenttype, content): 38 | ESP8266WebServer.ok( 39 | socket, 40 | "200", 41 | method+" "+contenttype+" "+content.decode('UTF-8')) 42 | 43 | # Handler for path "/cmd?led=[on|off]" 44 | def handleCmd(socket, args): 45 | if 'led' in args: 46 | if args['led'] == 'on': 47 | pin.off() 48 | elif args['led'] == 'off': 49 | pin.on() 50 | updateInfo(socket) 51 | else: 52 | ESP8266WebServer.err(socket, "400", "Bad Request") 53 | 54 | # handler for path "/switch" 55 | def handleSwitch(socket, args): 56 | pin.value(not pin.value()) # Switch back and forth 57 | updateInfo(socket) 58 | 59 | # Start the server @ port 8899 60 | # ESP8266WebServer.begin(8899) 61 | ESP8266WebServer.begin() # use default 80 port 62 | 63 | # Register handler for each path 64 | # ESP8266WebServer.onPath("/", handleRoot) 65 | ESP8266WebServer.onPath("/cmd", handleCmd) 66 | ESP8266WebServer.onPath("/switch", handleSwitch) 67 | ESP8266WebServer.onPath("/post", handlePost) 68 | 69 | # Setting the path to documents 70 | ESP8266WebServer.setDocPath("/") 71 | 72 | # Setting data for template 73 | ESP8266WebServer.setTplData(ledData) 74 | 75 | # Setting maximum Body Content Size. Set to 0 to disable posting. Default: 1024 76 | ESP8266WebServer.setMaxContentLength(1024) 77 | 78 | def checkForClients(): 79 | try: 80 | while True: 81 | # Let server process requests 82 | ESP8266WebServer.handleClient() 83 | except: 84 | ESP8266WebServer.close() 85 | 86 | checkForClients() 87 | 88 | -------------------------------------------------------------------------------- /www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 23 | Remote LED 24 | 25 | 26 |

LED Status

27 | 28 | -------------------------------------------------------------------------------- /www2/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

This is index.html

7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /www2/index.p.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Status:{status}

4 | 5 | 6 | -------------------------------------------------------------------------------- /www2/test.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | color: blue; 3 | } --------------------------------------------------------------------------------