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

" + message + "

") 88 | 89 | def ok(socket, code, *args): 90 | """Response successful message or webpage to client 91 | """ 92 | if len(args)==1: 93 | content_type = "text/plain" 94 | msg = args[0] 95 | elif len(args)==2: 96 | content_type = args[0] 97 | msg = args[1] 98 | else: 99 | raise TypeError("ok() takes 3 or 4 positional arguments but "+ str(len(args)+2) +" were given") 100 | socket.write("HTTP/1.1 " + code + " OK\r\n") 101 | socket.write("Content-Type: " + content_type + "\r\n\r\n") 102 | # if __fileExist(msg): 103 | # filePath = msg 104 | # __sendPage(socket, filePath) 105 | # else: 106 | socket.write(msg) 107 | 108 | def __fileExist(path): 109 | """Check for file existence 110 | """ 111 | print(path) 112 | try: 113 | stat = os.stat(path) 114 | # stat[0] bit 15 / 14 -> file/dir 115 | if stat[0] & 0x8000 == 0x8000: # file 116 | print("Found.") 117 | return True 118 | else: # Dir 119 | return False 120 | except: 121 | print("Not Found.") 122 | return False 123 | 124 | 125 | def __serveFile(socket, path): 126 | """Serves a file from the filesystem 127 | """ 128 | filePath = docPath + path 129 | fileFound = True 130 | # find the file 131 | if not __fileExist(filePath): 132 | if not path.endswith("/"): 133 | fileFound = False 134 | else: 135 | filePath = path + docPath + "/index.html" 136 | # find index.html in the path 137 | if not __fileExist(filePath): 138 | filePath = path + docPath + "/index.p.html" 139 | # find index.p.html in the path 140 | if not __fileExist(filePath): # no default html file found 141 | fileFound = False 142 | if not fileFound: # file or default html file specified in path not found 143 | if notFoundHandler: 144 | notFoundHandler(socket) 145 | else: 146 | err(socket, "404", "Not Found") 147 | return 148 | # Responds the header first 149 | socket.write("HTTP/1.1 200 OK\r\n") 150 | contentType = "text/html" 151 | for ext in mimeTypes: 152 | if filePath.endswith(ext): 153 | contentType = mimeTypes[ext] 154 | socket.write("Content-Type: " + contentType + "\r\n\r\n") 155 | # Responds the file content 156 | if filePath.endswith(".p.html"): 157 | print("template file.") 158 | f = open(filePath, "r") 159 | for l in f: 160 | socket.write(l.format(**tplData)) 161 | f.close() 162 | else: 163 | __sendPage(socket, filePath) 164 | 165 | def handle(socket): 166 | """Processing new GET request 167 | """ 168 | global docPath, handlers 169 | try: # capture timeout for wainting a line 170 | currLine = str(socket.readline(), 'utf-8') 171 | except: 172 | currLine = "" # readline timeout (not a complete line) 173 | request = currLine.split(" ") 174 | if len(request) != 3: # Discarded if it's a bad header 175 | return 176 | (method, url, version) = request 177 | if "?" in url: # Check if there's query string? 178 | (path, query) = url.split("?", 2) 179 | else: 180 | (path, query) = (url, "") 181 | args = {} 182 | contentType = "" 183 | content = b"" 184 | contentLength = 0 185 | if query: # Parsing the querying string 186 | argPairs = query.split("&") 187 | for argPair in argPairs: 188 | arg = argPair.split("=") 189 | args[arg[0]] = arg[1] 190 | while True: # Read until blank line after header 191 | header = socket.readline() 192 | if header.startswith(b"Content-Length"): 193 | (key, contentLengthStr) = str(header).split(" ") 194 | contentLength = int(contentLengthStr[0:-5]) 195 | if (contentLength > maxContentLength): 196 | err(socket, "400", "Bad Request") 197 | return 198 | if (header.startswith(b"Content-Type")): 199 | (key, contentType) = str(header).split(" ") 200 | contentType = contentType[0:-5] 201 | if (header == b""): 202 | return 203 | if (header == b"\r\n" and contentLength > 0): 204 | while(len(content) < contentLength): 205 | content = content + socket.recv(contentLength) 206 | if (len(content) > maxContentLength): 207 | err(socket, "400", "Bad Request") 208 | return 209 | break 210 | elif header == b"\r\n": 211 | break 212 | 213 | # Check for supported HTTP version 214 | if version != "HTTP/1.0\r\n" and version != "HTTP/1.1\r\n": 215 | err(socket, "505", "Version Not Supported") 216 | elif (method != "GET" and method != "PUT" and method != "POST"): # Only accept GET,PUT and POST request 217 | err(socket, "501", "Not Implemented") 218 | elif (path in postHandlers and method == "POST"): # Check for registered POST path 219 | postHandlers[path](socket, args, contentType, content) 220 | elif (path in putHandlers and method == "PUT"): # Check for registered PUT path 221 | putHandlers[path](socket, args, contentType, content) 222 | elif (path in getHandlers and method == "GET"): # Check for registered GET path 223 | getHandlers[path](socket, args) 224 | elif path in getHandlers: # here to ensure compatibility 225 | getHandlers[path](socket, args) 226 | else: # find file in the document path 227 | __serveFile(socket, path) 228 | 229 | def onPath(path, handler): 230 | """Register handler for processing GET request of specified path, 231 | Here to ensure compatibility. 232 | """ 233 | onGetPath(path, handler) 234 | 235 | def onGetPath(path, handler): 236 | """Register handler for processing GET request of specified path 237 | """ 238 | global handlers 239 | getHandlers[path] = handler 240 | 241 | def onPostPath(path, handler): 242 | """Register handler for processing POST of specified path 243 | """ 244 | global handlers 245 | postHandlers[path] = handler 246 | 247 | def onPutPath(path, handler): 248 | """Register handler for processing PUT of specified path 249 | """ 250 | global handlers 251 | putHandlers[path] = handler 252 | 253 | def onNotFound(handler): 254 | """Register handler for processing request of not found path 255 | """ 256 | global notFoundHandler 257 | notFoundHandler = handler 258 | 259 | def setDocPath(path): 260 | """Set the path to documents' directory 261 | """ 262 | global docPath 263 | if path.endswith("/"): 264 | docPath = path[:-1] 265 | else: 266 | docPath = path 267 | 268 | def setTplData(data): 269 | """Set data for template 270 | """ 271 | global tplData 272 | tplData = data 273 | 274 | def setMaxContentLength(max): 275 | """Set the maximum content lenpth for incomming data bodies 276 | """ 277 | global maxContentLength 278 | maxContentLength = max 279 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ESPWebServer 2 | 3 | This is a very lightweight web server for MicroPython on ESP8266/32.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/). 4 | 5 | ## Installation 6 | 7 | Just upload ESPWebServer.py to your ESP8266/32 board and you're done. 8 | 9 | ## Usage 10 | 11 | To use ESPWebServer.py library, you should: 12 | 13 | 1. Write functions as handlers for each path you'd like to serve contents. 14 | 15 | 1. Start the server by calling begin(). 16 | 17 | 1. Regsiter the handlers you just prepared by calling onPath(). 18 | 19 | 1. You can also uploading HTML files onto somewhere in the filesystem and settnig the document path by calling setDocPath(). 20 | 21 | 1. Call handleClient() repeatly to process requests. 22 | 23 | ### Documents and Templates 24 | 25 | 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. 26 | 27 | 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. 28 | 29 | 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. 30 | 31 | ## Function reference 32 | 33 | ### begin(port) 34 | 35 | Start the server at specified *port*. 36 | 37 | ### onPath(path, handler) 38 | 39 | Legacy method to ensure compatibility. Calls `onGetPath(path, handler)`. 40 | 41 | ### onGetPath(path, handler) 42 | 43 | Registers a handler for handling GET Requests. 44 | 45 | The Handlers expected Method Signature: `methodName(socket, args)` 46 | 47 | ### onPostPath(path, handler) 48 | 49 | Registers a handler for handling POST Requests. 50 | 51 | The Handlers expected Method Signature: `methodName(socket, args, contenttype, content)` 52 | 53 | ### onPutPath(path, handler) 54 | 55 | Registers a handler for handling PUT Requests. 56 | 57 | The Handlers expected Method signature: `methodName(socket, args, contenttype, content)` 58 | 59 | ### setDocPath(path) 60 | 61 | Specified the directory in the filesystem containing all the HTML files. 62 | 63 | ### setTplData(dic) 64 | 65 | 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. 66 | 67 | ### setMaxContentLength(size) 68 | 69 | Defines the maximum Content Length of incoming request bodies (POST, PUT) in bytes. Default: 1024 70 | 71 | ### handleClient() 72 | 73 | Check for new request and call corresponding handler to process it. 74 | 75 | ## Examples 76 | 77 | You can upload www directory and index.p.html to "/" on ESP8266 board and run TestWebServer.py to see how it works. 78 | 79 | `main.py` contains an example for handling POST Requests. PUT Requests are acting the same way. 80 | 81 | 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. 82 | 83 | 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. 84 | 85 | 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. 86 | -------------------------------------------------------------------------------- /Simple_led.py: -------------------------------------------------------------------------------- 1 | import ESPWebServer 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 | ESPWebServer.ok(socket, "200", args["led"]) 44 | else: 45 | ESPWebServer.err(socket, "400", "Bad Request") 46 | 47 | # Start the server @ port 8899 48 | ESPWebServer.begin(8899) 49 | 50 | # Register handler for each path 51 | ESPWebServer.onPath("/cmd", handleCmd) 52 | ESPWebServer.setDocPath("/www2") 53 | 54 | ledData = { 55 | "status":"Off", 56 | } 57 | ESPWebServer.setTplData(ledData) 58 | 59 | try: 60 | while True: 61 | # Let server process requests 62 | ESPWebServer.handleClient() 63 | except: 64 | ESPWebServer.close() 65 | -------------------------------------------------------------------------------- /TestWebServer.py: -------------------------------------------------------------------------------- 1 | import ESPWebServer 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 | ESPWebServer.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 | ESPWebServer.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 | # ESPWebServer.begin(8899) 70 | ESPWebServer.begin() # use default 80 port 71 | 72 | # Register handler for each path 73 | # ESPWebServer.onPath("/", handleRoot) 74 | ESPWebServer.onPath("/cmd", handleCmd) 75 | ESPWebServer.onPath("/switch", handleSwitch) 76 | 77 | # Setting the path to documents 78 | ESPWebServer.setDocPath("/") 79 | 80 | # Setting data for template 81 | ESPWebServer.setTplData(ledData) 82 | 83 | try: 84 | while True: 85 | # Let server process requests 86 | ESPWebServer.handleClient() 87 | except: 88 | ESPWebServer.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 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import ESPWebServer 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 | ESPWebServer.ok( 25 | socket, 26 | "200", 27 | ledData["status"]) 28 | 29 | def handleStop(socket): 30 | ESPWebServer.ok( 31 | socket, 32 | "200", 33 | "stopped") 34 | running = False 35 | ESPWebServer.close() 36 | 37 | def handlePost(socket, args, contenttype, content): 38 | ESPWebServer.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 | ESPWebServer.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 | # ESPWebServer.begin(8899) 61 | ESPWebServer.begin() # use default 80 port 62 | 63 | # Register handler for each path 64 | # ESPWebServer.onPath("/", handleRoot) 65 | ESPWebServer.onPath("/cmd", handleCmd) 66 | ESPWebServer.onPath("/switch", handleSwitch) 67 | ESPWebServer.onPostPath("/post", handlePost) 68 | 69 | # Setting the path to documents 70 | ESPWebServer.setDocPath("/") 71 | 72 | # Setting data for template 73 | ESPWebServer.setTplData(ledData) 74 | 75 | # Setting maximum Body Content Size. Set to 0 to disable posting. Default: 1024 76 | ESPWebServer.setMaxContentLength(1024) 77 | 78 | def checkForClients(): 79 | try: 80 | while True: 81 | # Let server process requests 82 | ESPWebServer.handleClient() 83 | except: 84 | ESPWebServer.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 | } --------------------------------------------------------------------------------