├── Copyright.Notice ├── Makefile ├── README.md ├── application.py ├── config.py ├── content.py ├── display.py ├── ds18b20.py ├── footer.txt ├── gotosleep.py ├── header.txt ├── help.txt ├── httpserver.py ├── index.txt ├── main.py ├── real.py ├── register.py └── request.py /Copyright.Notice: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | # 3 | # Copyright (c) 2016 Erni (E) Tron 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 13 | # all 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 21 | # THE SOFTWARE. 22 | # / 23 | 24 | 25 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # User configuration 3 | ###################################################################### 4 | 5 | # Serial port 6 | # Windows 7 | #PORT=/dev/cu.SLAB_USBtoUART 8 | # MAC 9 | #PORT=/dev/ttyACM0 10 | # Linux Debian/Ubuntu 11 | PORT=/dev/ttyUSB0 12 | 13 | SPEED=115200 14 | SPEED=9600 15 | 16 | # Path to programs 17 | MPYCROSS=/opt/ESP8266/micropython/mpy-cross/mpy-cross 18 | ESPTOOL=/opt/ESP8266/esp-open-sdk/esptool/esptool.py 19 | # Install with sudo pip2 install adafruit-ampy 20 | AMPY=ampy -p $(PORT) 21 | FIRMWARE=/opt/ESP8266/micropython/esp8266/build/firmware-combined.bin 22 | #FIRMWARE=./build/firmware-combined.bin 23 | 24 | CHECKSERVER=/usr/local/bin/checkserver.py 25 | 26 | # OTA via uploader 27 | DEV=192.168.1.149 28 | UPLOADER=/opt/ESP8266/webrepl/webrepl_cli.py 29 | 30 | DATE=$(shell date +"%Y-%b-%d %H:%M:%S") 31 | VERSION=2.1.2-deepsleep 32 | BUILDDIR=BUILD 33 | 34 | ###################################################################### 35 | # End of user config 36 | ###################################################################### 37 | FILES := \ 38 | main.py \ 39 | real.py \ 40 | application.py \ 41 | httpserver.py \ 42 | request.py \ 43 | content.py \ 44 | ds18b20.py \ 45 | config.py \ 46 | register.py \ 47 | 48 | TEXT:= \ 49 | help.txt \ 50 | header.txt \ 51 | footer.txt \ 52 | index.txt \ 53 | config.txt \ 54 | webrepl_cfg.py \ 55 | 56 | MPYFILES := \ 57 | $(BUILDDIR)/real.mpy \ 58 | $(BUILDDIR)/application.mpy \ 59 | $(BUILDDIR)/httpserver.mpy \ 60 | $(BUILDDIR)/request.mpy \ 61 | $(BUILDDIR)/content.mpy \ 62 | $(BUILDDIR)/ds18b20.mpy \ 63 | $(BUILDDIR)/register.mpy \ 64 | $(BUILDDIR)/config.mpy \ 65 | $(BUILDDIR)/gotosleep.mpy \ 66 | $(BUILDDIR)/display.mpy \ 67 | 68 | $(BUILDDIR)/%.mpy: %.py 69 | $(MPYCROSS) $< -o $@ 70 | 71 | O=httpserver 72 | o: $(BUILDDIR)/$(O).mpy 73 | @echo installing $^ 74 | $(AMPY) put $^ 75 | 76 | compile: $(MPYFILES) 77 | @echo "compile all to be compiled" 78 | 79 | erase: 80 | $(ESPTOOL) --port $(PORT) erase_flash 81 | 82 | ESPIFSDK=/opt/ESP8266/nodemcu-firmware/sdk/esp_iot_sdk_v1.5.4.1/bin/esp_init_data_default.bin 83 | ESPIFADX=0x3fc000 84 | ESPIFSDK= 85 | ESPIFADX= 86 | 87 | flash: 88 | @echo "Be sure about MEMORY SIZE" 89 | $(ESPTOOL) --port $(PORT) --baud 115200 write_flash --verify --flash_size=32m --flash_mode=qio 0x00000 $(FIRMWARE) $(ESPIFADX) $(ESPIFSDK) 90 | @echo 'Power device again' 91 | 92 | # Upload all 93 | install: $(MPYFILES) $(TEXT) main.py 94 | sed -i -e "s/Version.*--/Version ${VERSION} ${DATE}--/" footer.txt 95 | for f in $^ ; \ 96 | do \ 97 | echo installing $$f ;\ 98 | $(AMPY) put $$f ;\ 99 | done; 100 | 101 | webinstall: $(MPYFILES) $(TEXT) main.py 102 | sed -i -e "s/Version.*--/Version ${VERSION} ${DATE}--/" footer.txt 103 | #$(CHECKSERVER) --host $(DEV) 104 | for f in $^ ; \ 105 | do $(UPLOADER) $$f $(DEV):/$$f ;\ 106 | done; 107 | 108 | w: $(BUILDDIR)/$(O).mpy 109 | #$(CHECKSERVER) --host $(DEV) 110 | $(UPLOADER) $^ $(DEV):/$(O).mpy 111 | 112 | p: $(O) 113 | #$(CHECKSERVER) --host $(DEV) 114 | $(UPLOADER) $^ $(DEV):/$(O) 115 | 116 | reset: 117 | $(AMPY) reset 118 | 119 | check: 120 | python3 -m py_compile *.py 121 | rm -rf __pycache__ 122 | rm -f *.pyc 123 | 124 | git: 125 | git commit -m 'update ${DATE}' -a 126 | git push 127 | 128 | vi: 129 | gvim $(FILES) 130 | 131 | clean: 132 | rm -f *.pyc 133 | rm -f *.mpy 134 | 135 | instruction: 136 | @echo "How to install:" 137 | @echo "1) make erase 2) make flash 3) make install" 138 | 139 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # uPython-esp8266-httpserver 2 | 3 | This is a General Purpose http server for ESP8266 written in micropython (www.micropython.org) 4 | It just serves static html files. Simply upload into flash any html files and she will serve them. 5 | As an example put your index.html with just "hello world!" in it and you have the K&R standard greetings. 6 | 7 | It is highly configurable with parameters for SSID, PWD, AP SSID and many others. It is virually unlimited as the configuration parameters are kept into a json array of {'key', 'value'} and the server can simply store as many as possible. 8 | 9 | It is mainly a temperature web server. For devices like ESP8266 and with temperature sensors based on DS18b20. 10 | 11 | * the http server is general purpose and is easy to customize and configure 12 | * the Makefile is pretty powerful and automatize a lot of common tasks upload and reset using the script espsend.py 13 | which should be installed or configured in the proper directory 14 | * It uses other tools such as webrepl 15 | 16 | 17 | # Requirements 18 | 19 | Software: based on micropython version 1.8.5. Probably latest and bleeding version works best. 20 | 21 | # git clone https://github.com/micropython/micropython.git 22 | 23 | Hardware: Tested on Wemos D1 and Lolin V3 with ESP8266 chipset. For temperature a Dallas DS18b20 is needed wired to the device. 24 | 25 | 26 | # DIFY (Do It Yourself) 27 | 28 | Acutally I assemble all the stuff into a microappliance and I disseminate my places to track temperatures all around. 29 | Look at the pictures. 30 | 31 | 32 | ## BOM (Billing of Materials) 33 | 34 | You can easily find all this stuff very cheap on aliexpress... just shop around ;) 35 | 36 | * Wemos D1 mini (around 3$) 37 | * Cheap Charger (< 1$) 38 | * Micro USB Cable 5cm (< 1$) 39 | * Sensor DS18b20 (< 1$) 40 | 41 | Expect to afford 6US$ !!! 42 | Isn't it fu**ing amazing? https://www.dropbox.com/s/mw0tacnw87kg23v/IMG_0580.JPG?dl=0 43 | 44 | I suggest to solder the 3 wires of the DS18B20 sensor directly on the Wemos D1 pins: 5V-GND-D4 i 45 | They are contiguous. 46 | 47 | No resistor is needed then (but check the code for the right PIN which is PIN=2). 48 | 49 | # USAGE 50 | Server can be called with: http://192.168.1.123:8805/help 51 | 52 | Many other configurations are possible 53 | 54 | For instance if you want to upload data to the cloud there is a asynchronous routine called 'register' that push into a server values with json format. The server that holds data can be found here: 55 | https://github.com/ernitron/temperature-server 56 | 57 | ## STATIC FILES 58 | HTML files can be added/uploaded into FLASH memory of device and will be served. 59 | 60 | For example if you upload your example.html file and request: http://192.168.1.123:8805/example.html 61 | 62 | ## DYNAMIC BEHAVIOR 63 | Can be configured in the main loop to serve dynamically generated contents 64 | Conventionally contents are kept in cb_xyz() in content.py 65 | 66 | ## TEMPERATURE SERVER WITH DS18b20 67 | A Dallas DS18b20 temperature sensor must be installed on device. 68 | On WeMos default GPIO for reading is either 12 or 2 as it has its own pullup resistor 69 | 70 | * D6 GPIO12 machine.Pin(12) 71 | * D4 GPIO2 machine.Pin(2) 72 | 73 | ## Implementation 74 | 75 | I use a configurator that reads and saves variables into json files. Pretty cunny ;) 76 | 77 | Server http is a class object with a main loop to serve requests. 78 | 79 | The parser of requests works only on first line that has the request from browser: 80 | GET /example/?var1=value1&var2=value2 HTTP/1.1 81 | 82 | Contents are produced with embedded html commands 83 | 84 | It uses BOOTSTRAP for css/js. I have made shortcuts url for the cdn repository: 85 | * https://goo.gl/EWKTqQ = https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js 86 | * http://goo.gl/E7UCvM = http://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css 87 | 88 | note: they are no more useful as files with html header are stored in flash and there is no need to shortcut them 89 | 90 | 91 | ## Installation 92 | 93 | This is a little tricky and I developed the espsend.py to automatize the uploading and to have a very fast cycle of edit-deploy-run-test 94 | 95 | I use the following tools to develop: 96 | 97 | * esp-open-sdk # git clone https://github.com/pfalcon/esp-open-sdk.git 98 | * micropython # git clone https://github.com/micropython/micropython.git 99 | * webrepl # git clone https://github.com/micropython/webrepl.git 100 | * esptool # git clone https://github.com/themadinventor/esptool.git 101 | * espsend.py # this is the script that goes on UART and make basic installation tasks. 102 | 103 | Let's start with a bare ESP8266 device like WeMos. 104 | 105 | * Install latest version of micropython 106 | * Reset device 107 | * Connect to device with picocom (or other) 108 | picocom -b 115200 109 | * Set up first time webrepl with your own password 110 | import webrepl; webrepl.start() 111 | * open a browser with page webrepl.html in webrepl folder and configure for password 112 | * Upload sources with webrepl from browser 113 | 114 | * Fast alternative use Makefile to make all. 115 | 116 | ** make erase 117 | ** make flash 118 | ** make initmicro 119 | ** make install 120 | 121 | ## Configuration 122 | *Actually THERE IS NO config.txt file included* as it contains ssid and passwords and it is personal. 123 | 124 | Consider that a it can be pretty simple. Just copy this in a file named config.txt and that's all to start 125 | 126 | {"place": "Your Place Name", "ssid": "YourSSID", "pwd": "YourSSIDPassword"} 127 | 128 | Also it must be connected to Internet to have valid start time (or you can skip starttime into the code) 129 | 130 | ## Development 131 | 132 | There is plenty of space for development 133 | 134 | ## Discussion 135 | 136 | A number of tricks are used to keep memory allocation low. 137 | 138 | See thread http://forum.micropython.org/viewtopic.php?f=16&t=2266 for a discussion 139 | 140 | 141 | 142 | -------------------------------------------------------------------------------- /application.py: -------------------------------------------------------------------------------- 1 | # Micropython Http Server 2 | # Erni Tron ernitron@gmail.com 3 | # Copyright (c) 2016 4 | 5 | # 6 | from config import config 7 | from display import display 8 | import time 9 | 10 | def application(interface) : 11 | 12 | mac = config.get_config('mac') 13 | server = config.get_config('address') 14 | place = config.get_config('place') 15 | chipid = config.get_config('chipid') 16 | 17 | # Temperature sensor device 18 | import ds18b20 as sensor 19 | # Now add some configuration params 20 | for pin in [12, 0, 2]: 21 | sensor.sensor = sensor.TempSensor(pin=pin, place=place, server=server, chipid=chipid, mac=mac) 22 | if sensor.sensor.present: 23 | print('Found sensor @', pin) 24 | break 25 | 26 | from register import Register 27 | rurl = config.get_config('register') 28 | auth = config.get_config('authorization') 29 | register = Register(rurl, auth) 30 | 31 | # http Server 32 | from httpserver import Server 33 | server = Server(title=place) # construct server object 34 | server.activate(8805) # server activate with port 35 | 36 | # now we introduce the sleep concept 37 | sleep = config.get_config('sleep') 38 | try: 39 | sleep = int(sleep)/10 40 | except: 41 | sleep = 0 42 | 43 | startime = time.time() 44 | while True: 45 | 46 | # activate and run for a while if returns True we go to sleep 47 | server.wait_connections(interface) 48 | 49 | T = sensor.sensor.status() 50 | display.display(T['temp']) 51 | 52 | delta = abs(time.time() - startime) 53 | if sleep and delta > sleep: 54 | register.http_post(T) 55 | from gotosleep import gotosleep 56 | gotosleep(int(sleep)) 57 | elif delta > 300: 58 | register.http_post(T) 59 | startime = time.time() 60 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | # Micropython Http Server 2 | # Erni Tron ernitron@gmail.com 3 | # Copyright (c) 2016 4 | 5 | import json 6 | 7 | class Config(): 8 | def __init__(self, file='config.txt'): 9 | self.config = {} 10 | self.file = file 11 | 12 | def read_config(self): 13 | try: 14 | with open(self.file, 'rb') as f: 15 | c = f.read() 16 | self.config = json.loads(c) 17 | except: 18 | self.config = {} 19 | 20 | def save_config(self): 21 | # Write Configuration 22 | jdata = json.dumps(self.config) 23 | if not jdata: 24 | return 25 | with open(self.file, 'wb') as f: 26 | f.write(jdata) 27 | 28 | def clean_config(self): 29 | self.config = {} 30 | self.save_config() 31 | 32 | def set_config(self, k, v): 33 | if not v : 34 | del self.config[k] 35 | else: self.config[k] = v 36 | 37 | def get_config(self, k=None): 38 | if not k : 39 | return self.config 40 | if k in self.config: 41 | return self.config[k] 42 | else: return '' 43 | 44 | # There will be only ONE config 45 | config = Config() 46 | -------------------------------------------------------------------------------- /content.py: -------------------------------------------------------------------------------- 1 | # Micropython Http Server 2 | # Erni Tron ernitron@gmail.com 3 | # Copyright (c) 2016 4 | 5 | # Content Callback functions. 6 | # They should receive parameters and return a HTML formatted string 7 | # By convention they start with cb_ 8 | 9 | import gc 10 | import time 11 | import os 12 | import json 13 | 14 | # The configuration variable 15 | from config import config 16 | 17 | # Content Functions 18 | def cb_open(filename): 19 | try: 20 | if filename == b'port_config.py': raise 21 | with open(filename, 'r') as f: 22 | return f.readlines() 23 | except: 24 | return ['No such file'] 25 | 26 | def cb_status(): 27 | datetime = datenow() 28 | chipid = config.get_config('chipid') 29 | macaddr = config.get_config('mac') 30 | address = config.get_config('address') 31 | starttime = config.get_config('starttime') 32 | conf = json.dumps(config.get_config(None)) 33 | return '

Device %s

' \ 34 | '

MacAddr: %s' \ 35 | '

Address: %s' \ 36 | '

Free Mem: %d (alloc %d)' \ 37 | '

Date Time: %s' \ 38 | '

Start Time: %s' \ 39 | '' % (chipid, macaddr, address, gc.mem_free(), gc.mem_alloc(), datetime, starttime) 40 | 41 | def cb_getconf(): 42 | return '

Configuration

%s

' % json.dumps(config.get_config(None)) 43 | 44 | def cb_setconf(key, value): 45 | if not value: 46 | if key: 47 | kvalue = 'value="%s"' % (key) 48 | else: 49 | kvalue = '' 50 | ret = '

Set configuration parameter

'\ 51 | '
' \ 52 | 'Param ' \ 53 | 'Value ' \ 54 | '' \ 55 | '

' % (kvalue) 56 | return ret 57 | elif key in b'ssid' and len(value) < 3: 58 | return '

WiFi too short, try again

' 59 | elif key in b'pwd' and len(value) < 8: 60 | return '

WiFi too short, try again

' 61 | else: 62 | config.set_config(key, value) 63 | config.save_config() 64 | return '

Param %s set to %s

' % (key, value) 65 | 66 | def cb_resetconf(): 67 | config.clean_config() 68 | return 'Config cleaned' 69 | 70 | def cb_listssid(): 71 | response_header = ''' 72 |

Wi-Fi Client Setup

73 |
74 | 75 |
85 | Password:
86 | 87 |
88 | ''' 89 | return response_header + response_variable + response_footer 90 | 91 | # Temperature Sensor contents 92 | from ds18b20 import sensor 93 | 94 | def cb_temperature(): 95 | T = sensor.status() 96 | place = config.get_config('place') 97 | starttime = config.get_config('starttime') 98 | content = '

%s: %s °C

' \ 99 | '

Sensor %s - Reading # %d @ %s' \ 100 | '

Started on %s' % (place, T['temp'], T['sensor'], T['count'], T['date'], starttime) 101 | return content 102 | 103 | def cb_temperature_plain(): 104 | T = sensor.status() 105 | content = '%s C' % T['temp'] 106 | return content 107 | 108 | def cb_temperature_json(): 109 | T = sensor.status() 110 | return json.dumps(T) 111 | 112 | def datenow(): 113 | try: 114 | (Y, M, D, h, m, s, c, u) = time.localtime() 115 | return '%d-%d-%d %d:%d:%d' % (Y, M, D, h, m, s) 116 | except: 117 | return time.time() 118 | 119 | -------------------------------------------------------------------------------- /display.py: -------------------------------------------------------------------------------- 1 | # Micropython Http Temperature Server 2 | # Erni Tron ernitron@gmail.com 3 | # Copyright (c) 2016 4 | 5 | class Display(): 6 | def __init__(self): 7 | self.d = None 8 | try: 9 | import ssd1306 10 | from machine import I2C, Pin 11 | i2c = I2C(sda=Pin(4), scl=Pin(5)) 12 | self.d = ssd1306.SSD1306_I2C(64, 48, i2c, 60) 13 | print ("Display available") 14 | except Exception as e: 15 | print ("Display just print") 16 | self.d = None 17 | 18 | def display(self, text, text1='', text2='', text3='', text4=''): 19 | if self.d : 20 | self.d.fill(0) 21 | if text: self.d.text(text,0, 0, 1) 22 | if text1: self.d.text(text1,0, 10, 1) 23 | if text2: self.d.text(text2,0, 20, 1) 24 | if text3: self.d.text(text3,0, 30, 1) 25 | if text4: self.d.text(text4,0, 40, 1) 26 | self.d.show() 27 | else: 28 | print(text, text1, text2, text3, text4) 29 | 30 | display = Display() 31 | -------------------------------------------------------------------------------- /ds18b20.py: -------------------------------------------------------------------------------- 1 | # Micropython Http Server 2 | # Erni Tron ernitron@gmail.com 3 | # Copyright (c) 2016 4 | 5 | import time 6 | from machine import Pin 7 | from onewire import OneWire 8 | import ds18x20 9 | from ubinascii import hexlify 10 | 11 | # The temp sensor DS18b20 with 4.7k Ohm resistor pull-up 12 | # is on GPIO12 on Lolin V3 and WebMos D1-Mini 13 | # Pin 12 is D6 on WeMos 14 | 15 | class TempSensor(): 16 | # D3 GPIO0 machine.Pin(0) no need external pullup resistor 17 | # D4 GPIO2 machine.Pin(2) no need external pullup resistor 18 | # D6 GPIO12 machine.Pin(12) need 4.7kohm pullup resistor 19 | def __init__(self, pin=12, place='', server='localhost', chipid='', mac=''): 20 | self.count = 0 21 | self.sensor = '000' 22 | self.temp = '85.0' # the default error temperature of ds18b20 23 | self.place = place 24 | self.server = server 25 | self.chipid = chipid 26 | self.mac = mac 27 | try: 28 | ow = OneWire(Pin(pin)) 29 | self.ds = ds18x20.DS18X20(ow) 30 | self.roms = self.ds.scan() 31 | self.present = True 32 | except: 33 | self.present = False 34 | 35 | def setplace(self, place='', server='localhost', chipid='', mac=''): 36 | if place: self.place = place 37 | if server: self.server = server 38 | if chipid: self.chipid = chipid 39 | if mac: self.mac = mac 40 | 41 | def temperature(self, n=0): 42 | if self.present : 43 | try: 44 | self.ds.convert_temp() 45 | time.sleep_ms(750) 46 | self.temp = self.ds.read_temp(self.roms[n]) 47 | # 280b042800008019 48 | # 28-80000028040b 49 | self.sensor = hexlify(self.roms[n]) 50 | self.count += 1 51 | except: 52 | self.temp = 85.0 53 | self.present = False 54 | return self.temp 55 | 56 | def sensorid(self): 57 | return self.sensor 58 | 59 | def status(self): 60 | self.temperature() 61 | T = {} 62 | T['temp'] = str(self.temp) 63 | T['count'] = self.count 64 | T['sensor'] = self.sensor 65 | T['server'] = self.server 66 | T['place'] = self.place 67 | T['chipid'] = self.chipid 68 | T['mac'] = self.mac 69 | try: 70 | (Y, M, D, h, m, s, c, u) = time.localtime() 71 | h = (h+1) % 24 # TimeZone is GMT-2 hardcoded ;) 72 | T['date'] = '%d-%d-%d %d:%d:%d' % (Y, M, D, h, m, s) 73 | except: 74 | T['date'] = '' 75 | return T 76 | 77 | # The Sensor Class initialized to None 78 | sensor = None 79 | -------------------------------------------------------------------------------- /footer.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | 17 |

Micropython Temperature Server - Version 2.1.2-deepsleep 2016-dic-26 15:20:34-- Copyright (C) Erni Tron - 2016

18 | 19 | 20 | -------------------------------------------------------------------------------- /gotosleep.py: -------------------------------------------------------------------------------- 1 | # Micropython Http Server 2 | # Erni Tron ernitron@gmail.com 3 | # Copyright (c) 2016 4 | 5 | import time 6 | import machine 7 | 8 | def gotosleep(sleep_timeout): 9 | # Remember for this to work GPIO16 (D0) must be connected to RST 10 | # configure RTC.ALARM0 to be able to wake the device 11 | rtc = machine.RTC() 12 | rtc.irq(trigger=rtc.ALARM0, wake=machine.DEEPSLEEP) 13 | 14 | # set RTC.ALARM0 to fire after a while (waking the device) 15 | sleep_timeout = sleep_timeout * 1000 # in microseconds 16 | rtc.alarm(rtc.ALARM0, sleep_timeout) 17 | 18 | # put the device to sleep 19 | print('Sleep for %d usec' % sleep_timeout) 20 | machine.deepsleep() 21 | -------------------------------------------------------------------------------- /header.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | esp8266 server 5 | 6 | 7 | 8 | 9 | 11 | 12 | 13 |
14 |
15 | 16 | -------------------------------------------------------------------------------- /help.txt: -------------------------------------------------------------------------------- 1 |

Readme

2 |
3 | 4 |

Welcome to the ESP-8266 Temperature Server.
5 | Server can be called with: http://192.168.1.123:8805/help 6 |
7 | This device can also provide an Access Point if it is configured with the parameter i.e. if the AP password 'appwd' is present in config 8 |
9 | 10 | SSID is something like YoT-1234567
11 | PASS is in the manual
12 |
13 | 14 |

CONFIGURATION

15 | Configuration is kept into config.txt in a json string with { 'key': value } 16 |
17 | You can change/add parameters like PLACE name, SSID and PASS to connect to an external WiFi AP. 18 | 19 | PLACE, SSID, PASS are lowercase when set in Parameter 20 |
21 | 22 | 23 | 24 |

STATIC FILES

25 | 26 | HTML files can be added/uploaded into FLASH memory of device and will be served. 27 |
28 | 29 | For example if you upload your example.html file and request: http://192.168.1.123:8805/example.html 30 |
31 | 32 |

DYNAMIC BEHAVIOR

33 | Can be configured in the main loop to serve dynamically generated contents 34 |
35 | 36 |

TEMPERATURE SERVER WITH DS18b20

37 | 38 | A Dallas DS18b20 temperature sensor must be installed on device. On WeMos default GPIO for reading is = 12 39 |
40 | 41 | D6 GPIO12 machine.Pin(12) 42 |
43 | 44 | 45 | 46 | Enjoy

47 | -------------------------------------------------------------------------------- /httpserver.py: -------------------------------------------------------------------------------- 1 | # Micropython Http Server 2 | # Erni Tron ernitron@gmail.com 3 | # Copyright (c) 2016 4 | 5 | # Global import 6 | import socket # Networking support 7 | import time # Current time 8 | import sys 9 | import gc 10 | import machine 11 | 12 | # Local import 13 | from request import parse_request 14 | from content import cb_open, cb_status, cb_getconf, cb_setconf, cb_resetconf 15 | from content import cb_temperature, cb_temperature_json, cb_temperature_plain 16 | 17 | # A simple HTTP server 18 | class Server: 19 | def __init__(self, title='uServer'): 20 | # Constructor 21 | self.title = title 22 | self.conn = None 23 | self.addr = None 24 | self.head2 = cb_open('header.txt') 25 | self.footer = cb_open('footer.txt') 26 | self.timeout = 10.0 27 | 28 | def activate(self, port, host='0.0.0.0'): 29 | # Attempts to aquire the socket and launch the server 30 | try: 31 | self.socket = socket.socket() 32 | self.socket.settimeout(self.timeout) # otherwise it will wait forever 33 | self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 34 | self.socket.bind((host, port)) 35 | print("Server ", host, ":", port) 36 | except Exception as e: 37 | print(e) 38 | self.socket.listen(1) # maximum number of queued connections 39 | 40 | def wait_connections(self, interface): 41 | 42 | # Main loop awaiting connections 43 | refresh30 = '\r\n' 44 | redirect2 = '\r\n' 45 | error404 = '404 - Error' 46 | 47 | while True: 48 | if not interface.isconnected(): 49 | raise OSError('Lost connection') 50 | 51 | print('Wait') 52 | 53 | try: 54 | self.conn, self.addr = self.socket.accept() 55 | req = self.conn.readline() 56 | except KeyboardInterrupt: 57 | raise OSError('Interrupt') 58 | except: 59 | return 60 | 61 | l = 0 62 | while True: 63 | h = self.conn.readline() 64 | if not h or h == b'\r\n': 65 | break 66 | if 'Content-Length: ' in h: 67 | try: 68 | l = int(h[16:-2]) 69 | except: 70 | continue 71 | if l : 72 | fullreq = self.conn.read(l) 73 | print(fullreq) 74 | 75 | # Some defaults that can be changed in progress 76 | code = 200 77 | extension = 'h' 78 | refresh = '' 79 | 80 | # determine request method (GET / POST are supported) 81 | r = parse_request(req) 82 | if r == None: 83 | code = 404 84 | content = error404 85 | elif r['method'] == 'POST' or r['method'] == 'post': 86 | print(h) 87 | content = cb_setconf(None, None) 88 | elif r['uri'] == b'/temperature' or r['uri'] == b'/' : 89 | refresh=refresh30 90 | content = cb_temperature() 91 | elif r['uri'] == b'/j' : 92 | extension = 'j' 93 | content = cb_temperature_json() 94 | elif r['uri'] == b'/getconf': 95 | content = cb_getconf() 96 | elif b'/setconf' in r['uri']: 97 | if 'key' in r['args'] and 'value' in r['args']: 98 | content = cb_setconf(r['args']['key'], r['args']['value']) 99 | elif 'key' in r['args'] : 100 | content = cb_setconf(r['args']['key'], None) 101 | else: 102 | content = cb_setconf(None, None) 103 | elif r['uri'] == b'/status': 104 | content = cb_status() 105 | elif r['uri'] == b'/webrepl' : 106 | import webrepl 107 | webrepl.start() 108 | refresh = redirect2 109 | code = 302 110 | content = '

Webrepl +5min

' 111 | elif r['uri'] == b'/5min' : 112 | self.socket.settimeout(60) 113 | refresh = redirect2 114 | code = 302 115 | content = '

+5min

' 116 | elif r['uri'] == b'/reboot' : 117 | content = '

Reboot

' 118 | self.http_send(code, content, extension, redirect2) 119 | self.conn.close() 120 | machine.reset() 121 | elif r['file'] != b'': 122 | myfile = r['file'] 123 | code = 200 124 | content = cb_open(myfile) 125 | else: 126 | code = 404 127 | content = error404 128 | 129 | # At end of loop just close socket and collect garbage 130 | self.http_send(code, content, extension, refresh) 131 | self.conn.close() 132 | gc.collect() 133 | 134 | def http_send(self, code, content, extension='h', refresh=''): 135 | mt = {'h': "text/html", 'j': "application/json"} 136 | codes = {200:" OK", 400:" Bad Request", 404:" Not Found", 302:" Redirect", 501:"Server Error" } 137 | head0 = 'HTTP/1.1 %s\r\nServer: tempserver\r\nContent-Type: %s\r\n' 138 | #head1 = 'Cache-Control: private, no-store\r\nConnection: close\r\n\r\n' 139 | head1 = 'Connection: close\r\n\r\n' 140 | 141 | if code not in codes: code = 501 142 | httpstatus = str(code) + codes[code] 143 | if extension not in mt: extension = 'h' 144 | mimetype = mt[extension] 145 | 146 | head0 = head0 % (httpstatus, mimetype) 147 | self.conn.send(head0) 148 | if refresh: 149 | self.conn.send(refresh) 150 | self.conn.send(head1) 151 | if extension != 'j': 152 | for c in self.head2: 153 | self.conn.send(c) 154 | 155 | # if type(content) is list or type(content) is tuple: 156 | if type(content) is list : 157 | for c in content: 158 | self.conn.send(c) 159 | elif content != '': 160 | self.conn.send(content) 161 | 162 | if extension != 'j': 163 | for c in self.footer: 164 | self.conn.sendall(c) 165 | 166 | -------------------------------------------------------------------------------- /index.txt: -------------------------------------------------------------------------------- 1 |

Readme

2 | 3 | 4 |

Welcome to the ESP-8266 Temperature Server.
5 | 6 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from real import main 2 | 3 | main() 4 | -------------------------------------------------------------------------------- /real.py: -------------------------------------------------------------------------------- 1 | # Micropython Http Server 2 | # Erni Tron ernitron@gmail.com 3 | # Copyright (c) 2016 4 | 5 | import time 6 | import sys 7 | import network 8 | import machine 9 | import gc 10 | from ubinascii import hexlify 11 | 12 | from config import config 13 | 14 | def do_connect(ssid, pwd, TYPE, hard_reset=True): 15 | interface = network.WLAN(TYPE) 16 | 17 | # Stage zero if credential are null disconnect 18 | if not pwd or not ssid : 19 | print('Disconnecting ', TYPE) 20 | interface.active(False) 21 | return None 22 | 23 | if TYPE == network.AP_IF: 24 | interface.active(True) 25 | time.sleep_ms(200) 26 | interface.config(essid=ssid, password=pwd) 27 | return interface 28 | 29 | if hard_reset: 30 | interface.active(True) 31 | interface.connect(ssid, pwd) 32 | 33 | # Stage one check for default connection 34 | print('Connecting') 35 | for t in range(120): 36 | time.sleep_ms(250) 37 | if interface.isconnected(): 38 | print('Yes! Connected') 39 | return interface 40 | if t == 60 and not hard_reset: 41 | # if still not connected 42 | interface.active(True) 43 | interface.connect(ssid, pwd) 44 | 45 | # No way we are not connected 46 | print('Cant connect', ssid) 47 | return None 48 | 49 | #---------------------------------------------------------------- 50 | # MAIN PROGRAM STARTS HERE 51 | def main(): 52 | 53 | # Enable automatic garbage collector 54 | gc.enable() 55 | 56 | if machine.reset_cause() == machine.DEEPSLEEP_RESET: 57 | print('wake from deep sleep') 58 | hard_reset = False 59 | else: 60 | hard_reset = True 61 | print('wake from hard reset') 62 | 63 | chipid = hexlify(machine.unique_id()) 64 | 65 | # Read from file the whole configuration 66 | config.read_config() 67 | 68 | # Get WiFi defaults and connect 69 | ssid = config.get_config('ssid') 70 | pwd = config.get_config('pwd') 71 | interface = do_connect(ssid, pwd, network.STA_IF, hard_reset) 72 | 73 | # Turn on Access Point only with passw 74 | apssid = 'YoT-%s' % bytes.decode(chipid) 75 | appwd = config.get_config('appwd') 76 | if hard_reset: 77 | ap_interface = do_connect(apssid, appwd, network.AP_IF) 78 | 79 | if not interface and not ap_interface: 80 | print('Restart 10"') 81 | time.sleep(10.0) 82 | machine.reset() 83 | return 84 | 85 | # Set Parameters in configuration 86 | (address, mask, gateway, dns) = interface.ifconfig() 87 | config.set_config('address', address) 88 | config.set_config('mask', mask) 89 | config.set_config('gateway', gateway) 90 | config.set_config('dns', dns) 91 | config.set_config('mac', hexlify(interface.config('mac'), ':')) 92 | config.set_config('chipid', chipid) 93 | 94 | # Set Time RTC 95 | from ntptime import settime 96 | try: 97 | settime() 98 | (y, m, d, h, mm, s, c, u) = time.localtime() 99 | starttime = '%d-%d-%d %d:%d:%d UTC' % (y, m, d, h, mm, s) 100 | except: 101 | starttime = '2016-01-01 00:00:00' 102 | print('Cannot set time') 103 | 104 | # Set hostname 105 | interface.config(dhcp_hostname=chipid) 106 | config.set_config('hostname', interface.config('dhcp_hostname')) 107 | 108 | # We will save new configuration only at powerup 109 | if hard_reset: 110 | config.set_config('starttime', starttime) 111 | config.save_config() 112 | 113 | # Free some memory 114 | ssid = pwd = None 115 | apssid = appwd = None 116 | address = mask = gateway = dns = None 117 | gc.collect() 118 | 119 | # The application hook 120 | from application import application 121 | try: 122 | application(interface) 123 | except KeyboardInterrupt: 124 | pass 125 | except Exception as e: 126 | print(e) 127 | 128 | # Restart 129 | print('Restarting') 130 | time.sleep(5.0) 131 | 132 | machine.reset() 133 | 134 | -------------------------------------------------------------------------------- /register.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import time 3 | 4 | class Register(): 5 | def __init__(self, url, auth): 6 | if not url : 7 | self.host = None 8 | return 9 | 10 | try: 11 | _, __, self.host, self.path = url.split('/', 3) 12 | except: 13 | self.host = None 14 | return 15 | 16 | if ':' in self.host: 17 | self.host, self.port = self.host.split(':') 18 | self.port = int(self.port) 19 | else: 20 | self.port = 80 21 | 22 | self.header = 'Content-Type: application/json\r\n' 23 | if auth: 24 | self.header += 'Authorization: Basic %s\r\n' % auth 25 | 26 | def http_post(self, content): 27 | if not self.host : return 28 | print (self.host, self.port, self.path) 29 | 30 | import json 31 | jsondata = json.dumps(content) 32 | addr = socket.getaddrinfo(self.host, self.port)[0][-1] 33 | s = socket.socket() 34 | s.settimeout(10) # otherwise it will wait forever 35 | l = len(jsondata) 36 | msg = b'POST /%s HTTP/1.1\r\nHost: espserver\r\nContent-Length:%d\r\n%s\r\n' % (self.path, l, self.header) 37 | try: 38 | s.connect(addr) 39 | s.send(msg) 40 | s.sendall(jsondata) 41 | except Exception as e: # most probably a timeout 42 | print(e) 43 | s.close() 44 | 45 | # Register class initialized to None 46 | register = None 47 | -------------------------------------------------------------------------------- /request.py: -------------------------------------------------------------------------------- 1 | # Micropython Http Server 2 | # Erni Tron ernitron@gmail.com 3 | # Copyright (c) 2016 4 | 5 | def get_args(uri): 6 | answer = {} 7 | if uri == None or uri == b'' : 8 | return answer 9 | uri = bytes.decode(uri) 10 | uri = urldecode(uri) 11 | if '?' in uri: 12 | params = uri.split('?')[1] 13 | if '=' in uri: 14 | answer = dict(item.split('=') for item in params.split('&')) 15 | return answer 16 | 17 | def urldecode(s): 18 | table = {'%21':'!' ,'%23':'#' ,'%24':'$' ,'%26':'&' ,'%27':"'" ,'%28':'(' ,'%29':')' ,'%2F':'/' ,'%3A':':', '+':' '} 19 | for k, v in table.items(): 20 | s = s.replace(k, v) 21 | return s 22 | 23 | # Parses the client's request. 24 | # Returns a dictionary containing pretty much everything 25 | # the server needs to know about the uri. 26 | def parse_request(req): 27 | if b'\r\n' not in req : 28 | return None 29 | 30 | r = {} 31 | line, rest = req.split(b'\n', 1) 32 | method, uri, http = line.split(b' ') 33 | 34 | Methods = b'GET HEAD POST post PUT' 35 | if method in Methods: 36 | r['uri'] = uri 37 | r['method'] = method 38 | r['http'] = http 39 | uri = uri.replace(b'/', b'') 40 | r['args'] = get_args(uri) 41 | if b'?' in uri: endpos = uri.find(b'?') 42 | else: endpos = len(uri) 43 | r['file'] = uri[:endpos] 44 | 45 | return r 46 | 47 | --------------------------------------------------------------------------------