├── 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 '
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 '
%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 = '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 |