├── src ├── bytecode │ ├── README.md │ ├── wifi.mpy │ ├── control.mpy │ ├── setting.mpy │ ├── RepUpdate.mpy │ ├── webserver.mpy │ ├── AzurePublish.mpy │ └── GenSasToken.mpy ├── webrepl_cfg.py ├── html │ ├── README.md │ ├── shutdown.html │ ├── timeserver.html │ ├── index.html │ ├── userjson.html │ ├── wifi.html │ ├── iothub.html │ ├── style.css │ └── ipconfig.html ├── main.py ├── README.md ├── boot.py ├── config.json ├── setting.py ├── AzurePublish.py ├── RepUpdate.py ├── GenSasToken.py ├── wifi.py ├── control.py └── webserver.py ├── MicroPython_bin ├── modules │ ├── version.py │ ├── README.md │ ├── file.py │ ├── free.py │ ├── ntptime.py │ ├── wget.py │ ├── inisetup.py │ └── mqtt.py ├── firmware-combined_v1.9.3_07.bin └── README.md ├── IoT_ButtonImage ├── backup127.img └── README.md ├── schematicsAndLayout ├── layout_TOP.PDF ├── schematics.pdf └── layout_BOTTOM.PDF ├── INSTALL.md ├── README.md ├── LICENSE └── BuildFirmware.md /src/bytecode/README.md: -------------------------------------------------------------------------------- 1 | Bytecode Files -------------------------------------------------------------------------------- /src/webrepl_cfg.py: -------------------------------------------------------------------------------- 1 | PASS = 'iotbutton' 2 | -------------------------------------------------------------------------------- /MicroPython_bin/modules/version.py: -------------------------------------------------------------------------------- 1 | print('Version: 07') -------------------------------------------------------------------------------- /MicroPython_bin/modules/README.md: -------------------------------------------------------------------------------- 1 | this folder contains the frozen bytecodes for FW bin -------------------------------------------------------------------------------- /src/html/README.md: -------------------------------------------------------------------------------- 1 | this folder contains all files which are necessary for the WEB site -------------------------------------------------------------------------------- /src/main.py: -------------------------------------------------------------------------------- 1 | ### IoT Button Start 2 | 3 | import control 4 | control.start(False) 5 | -------------------------------------------------------------------------------- /src/bytecode/wifi.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teXXmo/TheButtonProject/HEAD/src/bytecode/wifi.mpy -------------------------------------------------------------------------------- /src/bytecode/control.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teXXmo/TheButtonProject/HEAD/src/bytecode/control.mpy -------------------------------------------------------------------------------- /src/bytecode/setting.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teXXmo/TheButtonProject/HEAD/src/bytecode/setting.mpy -------------------------------------------------------------------------------- /src/bytecode/RepUpdate.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teXXmo/TheButtonProject/HEAD/src/bytecode/RepUpdate.mpy -------------------------------------------------------------------------------- /src/bytecode/webserver.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teXXmo/TheButtonProject/HEAD/src/bytecode/webserver.mpy -------------------------------------------------------------------------------- /IoT_ButtonImage/backup127.img: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teXXmo/TheButtonProject/HEAD/IoT_ButtonImage/backup127.img -------------------------------------------------------------------------------- /src/bytecode/AzurePublish.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teXXmo/TheButtonProject/HEAD/src/bytecode/AzurePublish.mpy -------------------------------------------------------------------------------- /src/bytecode/GenSasToken.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teXXmo/TheButtonProject/HEAD/src/bytecode/GenSasToken.mpy -------------------------------------------------------------------------------- /schematicsAndLayout/layout_TOP.PDF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teXXmo/TheButtonProject/HEAD/schematicsAndLayout/layout_TOP.PDF -------------------------------------------------------------------------------- /schematicsAndLayout/schematics.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teXXmo/TheButtonProject/HEAD/schematicsAndLayout/schematics.pdf -------------------------------------------------------------------------------- /schematicsAndLayout/layout_BOTTOM.PDF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teXXmo/TheButtonProject/HEAD/schematicsAndLayout/layout_BOTTOM.PDF -------------------------------------------------------------------------------- /src/README.md: -------------------------------------------------------------------------------- 1 | This folder contains all Python Files for root /. 2 | Attention to error-free operation, these must be converted into bytecode. -------------------------------------------------------------------------------- /MicroPython_bin/firmware-combined_v1.9.3_07.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teXXmo/TheButtonProject/HEAD/MicroPython_bin/firmware-combined_v1.9.3_07.bin -------------------------------------------------------------------------------- /MicroPython_bin/modules/file.py: -------------------------------------------------------------------------------- 1 | def copy(source, destination): 2 | f1 = open(source) 3 | f2 = open(destination, 'w') 4 | f2.write(f1.read()) 5 | f1.close() 6 | f2.close 7 | -------------------------------------------------------------------------------- /src/boot.py: -------------------------------------------------------------------------------- 1 | # This file is executed on every boot (including wake-boot from deepsleep) 2 | import esp 3 | esp.osdebug(None) 4 | import gc 5 | #import webrepl 6 | #webrepl.start() 7 | gc.collect() 8 | 9 | -------------------------------------------------------------------------------- /IoT_ButtonImage/README.md: -------------------------------------------------------------------------------- 1 | This folder contains IMG Files. Please Install the Image with: 2 | 3 | esptool.py --port --baud 115200 write_flash --flash_mode dout --flash_size 1MB --flash_freq 20m 0x00000 4 | 5 | 6 | 7 | 8 | 9 | ----------------------------------- 10 | Any questions? - Get in touch: info@iot-button.eu 11 | 12 | See LICENSE for (MIT) License Information. 13 | -------------------------------------------------------------------------------- /INSTALL.md: -------------------------------------------------------------------------------- 1 | # firmware installation process 2 | 3 | - Requirements: 4 | -- Python 2.7 ( [windows] (https://www.python.org/downloads/windows/ ) ) 5 | -- esptool (pip install esptool ) 6 | -- ampy (pip install adafruit-ampy) 7 | -- Azure iot service client (pip install azure-iothub-service-client) 8 | 9 | esptool.py --port erase_flash 10 | 11 | on ESP8285 12 | esptool.py --port --baud 115200 write_flash -fm dout -fs 1MB 0x00000 13 | -------------------------------------------------------------------------------- /src/config.json: -------------------------------------------------------------------------------- 1 | {"iothub": "", "dnsserver": "0.0.0.0", "ip": "0.0.0.0", "health": 100, "password": "", "userjson": "{\"Order_ID\": \"1234\"}", "FWnumberNew": 100, "FWnumber": 100, "netmask": "255.255.255.0", "FWversion": "1.00", "usedhcp": "yes", "RSSI": 31, "ssid": "", "hwid": "00:00:00:00:00:00", "VBat": 0.00, "FWurl": "http://www.7soft.de/iot_hub/100/update.json", "timeserver": "pool.ntp.org", "iotdevicename": "teXXmobutton_000000000000", "opsmode": "SoftAP", "iotdevicesecret": "", "keypresses": 0, "gateway": "0.0.0.0"} 2 | -------------------------------------------------------------------------------- /MicroPython_bin/README.md: -------------------------------------------------------------------------------- 1 | this folder contains micropython.bin with frozen bytecode patch. 2 | 1. ntptime.py 3 | 2. mqtt.py 4 | 3. inisetup.py 5 | 4. version.py 6 | 5. wget.py 7 | 6. free 8 | 7. file 9 | 10 | 11 | Info about MicroPython: 12 | https://micropython.org/ 13 | 14 | MicroPython is running under MIT license, too. 15 | https://github.com/micropython/micropython/blob/master/LICENSE 16 | 17 | 18 | ----------------------------------- 19 | Any questions? - Get in touch: info@iot-button.eu 20 | 21 | See LICENSE for (MIT) License Information. 22 | -------------------------------------------------------------------------------- /MicroPython_bin/modules/free.py: -------------------------------------------------------------------------------- 1 | import gc 2 | import micropython 3 | gc.collect() 4 | micropython.mem_info() 5 | print('-----------------------------') 6 | print('Initial free: {} allocated: {}'.format(gc.mem_free(), gc.mem_alloc())) 7 | def func(): 8 | a = bytearray(10000) 9 | gc.collect() 10 | print('Func definition: {} allocated: {}'.format(gc.mem_free(), gc.mem_alloc())) 11 | func() 12 | print('Func run free: {} allocated: {}'.format(gc.mem_free(), gc.mem_alloc())) 13 | gc.collect() 14 | print('Garbage collect free: {} allocated: {}'.format(gc.mem_free(), gc.mem_alloc())) 15 | print('-----------------------------') 16 | micropython.mem_info(1) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TheButtonProject 2 | This is your kick-start for working with the teXXmo IoT Button. - Here you'll find the source code for the teXXmo IoT Button. 3 | 4 | Note that this is targeted for developers. If you want to get help or need professional services on the teXXmo IoT Button, see our webpage for further info: 5 | http://iot-button.eu/index_en.html 6 | More button version will come up and we will add features step-by-step. 7 | 8 | You may use this code for your own disposal, if you want to improve the system, please get in touch and give some feedback: 9 | info@iot-button.eu 10 | 11 | 12 | --------------------------------------- 13 | See LICENSE for (MIT) License Information. 14 | -------------------------------------------------------------------------------- /MicroPython_bin/modules/ntptime.py: -------------------------------------------------------------------------------- 1 | try: 2 | import usocket as socket 3 | except: 4 | import socket 5 | try: 6 | import ustruct as struct 7 | except: 8 | import struct 9 | 10 | # (date(2000, 1, 1) - date(1900, 1, 1)).days * 24*60*60 11 | NTP_DELTA = 3155673600 12 | 13 | def time(host='pool.ntp.org'): 14 | NTP_QUERY = bytearray(48) 15 | NTP_QUERY[0] = 0x1b 16 | addr = socket.getaddrinfo(host, 123)[0][-1] 17 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 18 | s.settimeout(1) 19 | res = s.sendto(NTP_QUERY, addr) 20 | msg = s.recv(48) 21 | s.close() 22 | val = struct.unpack("!I", msg[40:44])[0] 23 | return val - NTP_DELTA 24 | 25 | # There's currently no timezone support in MicroPython, so 26 | # utime.localtime() will return UTC time (as if it was .gmtime()) 27 | def settime(host='pool.ntp.org'): 28 | t = time(host) 29 | import machine 30 | import utime 31 | tm = utime.localtime(t) 32 | tm = tm[0:3] + (0,) + tm[3:6] + (0,) 33 | machine.RTC().datetime(tm) 34 | print(utime.localtime()) 35 | -------------------------------------------------------------------------------- /src/html/shutdown.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Iot Button Configuration 6 | 7 | 8 | 9 | 10 | 11 | 12 | 20 |


21 |
22 | Shutdown 23 |
24 |

25 |
26 |
27 | 28 | 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 teXXmo 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/html/timeserver.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Iot Button Configuration 6 | 7 | 8 | 9 | 10 | 11 | 12 | 20 |


21 |
22 | Timeserver 23 |
24 | 25 |
26 |
27 |
28 | 29 | 30 | -------------------------------------------------------------------------------- /src/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Iot Button Configuration 6 | 7 | 8 | 9 | 10 | 11 | 12 | 20 |


21 |
22 |
23 |

teXXmo

24 | IoT-Button: XX12W
25 |
26 |
27 | 28 | 29 | -------------------------------------------------------------------------------- /src/html/userjson.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Iot Button Configuration 6 | 7 | 8 | 9 | 10 | 11 | 12 | 20 |


21 |
22 | User Json 23 |
24 |
25 |
26 |
27 |
28 | 29 | 30 | -------------------------------------------------------------------------------- /src/html/wifi.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Iot Button Configuration 6 | 7 | 8 | 9 | 10 | 11 | 12 | 20 |


21 |
22 | WIFI 23 |
24 | 25 | 26 |
27 |
28 |
29 | 30 | 31 | -------------------------------------------------------------------------------- /src/html/iothub.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Iot Button Configuration 6 | 7 | 8 | 9 | 10 | 11 | 12 | 20 |


21 |
22 | Iot Button Configuration 23 |
24 | 25 | 26 | 27 | 28 |
29 |
30 | 31 | 32 | -------------------------------------------------------------------------------- /src/setting.py: -------------------------------------------------------------------------------- 1 | import ujson 2 | setting = {} 3 | 4 | def set(par, val): 5 | global setting 6 | try: 7 | setting[par] = val 8 | except: 9 | print('ERROR in setting.set') 10 | 11 | def get(par): 12 | global setting 13 | try: 14 | return setting[par] 15 | except: 16 | print('ERROR in setting.get') 17 | return '' 18 | 19 | def load(file): 20 | global setting 21 | try: 22 | f = open(file) 23 | setting = ujson.loads(f.read()) 24 | f.close() 25 | except: 26 | try: 27 | print('ERROR in setting.load Load Backup') 28 | f = open(file + '.bak') 29 | setting = ujson.loads(f.read()) 30 | f.close() 31 | except: 32 | print('ERROR in setting.load Can not Load Backup') 33 | return 34 | f = open(file + '.bak', 'w') 35 | f.write(ujson.dumps(setting)) 36 | f.close() 37 | 38 | def save(file): 39 | global setting 40 | try: 41 | f = open(file, 'w') 42 | f.write(ujson.dumps(setting)) 43 | f.close() 44 | except: 45 | print('ERROR in setting.save') 46 | 47 | def post(json): 48 | global settings 49 | try: 50 | dict = ujson.loads(json) 51 | print(dict) 52 | setting.update(dict) 53 | except: 54 | print('ERROR in setting.post') -------------------------------------------------------------------------------- /src/html/style.css: -------------------------------------------------------------------------------- 1 | ul {list-style-type:none;padding:0;position:static;} 2 | li {display:inline-block;float:left;margin-bottom:1px;} 3 | li a {display:block;min-width:180px;height:50px;text-align:center;line-height:50px;font-family:"Arial",sans-serif;color:#fff;background:#646464;text-decoration:none;font-size:16px;} 4 | li:hover a {background:#EF7F30;color:#fff;} 5 | li:hover ul a {background:#f3f3f3;color:#646464;} 6 | li:hover ul a:hover {background:#646464;color:#fff;} 7 | li ul {display:none;} 8 | li ul li {display:block;float:none;} 9 | li ul li a {width:auto;padding:0 220px;} 10 | ul li a:hover + .hidden, .hidden:hover {display:block;} 11 | .show-menu {font-family:"Arial",sans-serif;text-decoration:none;color:#fff;background:#848484;text-align:center;padding:10px 0;display:none;} 12 | input[type=checkbox]{display:none;} 13 | input[type=checkbox]:checked ~ #menu{display:block;} 14 | @media screen and (max-width:1098px){ul {position:static;display:none;}li {margin-bottom:1px;}ul li, li a {width:100%;}.show-menu {display:block;}} 15 | fieldset {font-family:"Arial",sans-serif;width:auto;min-height:150px;max-width:1045px;border:5px solid #EF7F30;} 16 | button {background-color:#646464;color:#fff;font-family:"Arial",sans-serif;font-size:16px;text-decoration:none;border:none;width:30%;height:50px;display:block;margin-left:auto;margin-right:auto;} 17 | button:hover {border:none;background:#EF7F30;color:#fff;} 18 | input{font-family:"Arial",sans-serif;width:99%;} -------------------------------------------------------------------------------- /src/html/ipconfig.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Iot Button Configuration 6 | 7 | 8 | 9 | 10 | 11 | 12 | 20 |


21 |
22 | IP Configuration 23 |
24 | 25 | 26 | 27 | 28 | 29 |
30 |
31 |
32 | 33 | 34 | -------------------------------------------------------------------------------- /MicroPython_bin/modules/wget.py: -------------------------------------------------------------------------------- 1 | import usocket as socket 2 | 3 | 4 | def get_file(url, file): 5 | _, _, host, path = url.split('/', 3) 6 | if ':' in host: 7 | host, port = host.split(':', 1) 8 | else: 9 | port = 80 10 | addr = socket.getaddrinfo(host, int(port))[0][-1] 11 | s = socket.socket() 12 | s.connect(addr) 13 | s.send(bytes('GET /%s HTTP/1.0\r\nHost: %s\r\n\r\n' % (path, host), 'utf8')) 14 | 15 | f = open(file, 'w') 16 | 17 | while True: 18 | datastr = s.readline() 19 | if datastr != b'\r\n': 20 | print(datastr) 21 | else: 22 | break 23 | 24 | while True: 25 | data = s.recv(100) 26 | if data: 27 | f.write(data) 28 | else: 29 | f.close() 30 | break 31 | 32 | def get(url): 33 | _, _, host, path = url.split('/', 3) 34 | if ':' in host: 35 | host, port = host.split(':', 1) 36 | else: 37 | port = 80 38 | addr = socket.getaddrinfo(host, int(port))[0][-1] 39 | s = socket.socket() 40 | s.connect(addr) 41 | s.send(bytes('GET /%s HTTP/1.0\r\nHost: %s\r\n\r\n' % (path, host), 'utf8')) 42 | 43 | while True: 44 | datastr = s.readline() 45 | if datastr != b'\r\n': 46 | print(datastr) 47 | else: 48 | break 49 | 50 | buffer = b'' 51 | while True: 52 | data = s.recv(100) 53 | if data: 54 | buffer += data 55 | else: 56 | return buffer 57 | break 58 | -------------------------------------------------------------------------------- /MicroPython_bin/modules/inisetup.py: -------------------------------------------------------------------------------- 1 | import uos 2 | import network 3 | from flashbdev import bdev 4 | 5 | def wifi(): 6 | import ubinascii 7 | ap_if = network.WLAN(network.AP_IF) 8 | essid = b"MicroPython-%s" % ubinascii.hexlify(ap_if.config("mac")[-3:]) 9 | ap_if.config(essid=essid, authmode=network.AUTH_WPA_WPA2_PSK, password=b"micropythoN") 10 | 11 | def check_bootsec(): 12 | buf = bytearray(bdev.SEC_SIZE) 13 | bdev.readblocks(0, buf) 14 | empty = True 15 | for b in buf: 16 | if b != 0xff: 17 | empty = False 18 | break 19 | if empty: 20 | return True 21 | fs_corrupted() 22 | 23 | def fs_corrupted(): 24 | import time 25 | while 1: 26 | print("""\ 27 | The FAT filesystem starting at sector %d with size %d sectors appears to 28 | be corrupted. If you had important data there, you may want to make a flash 29 | snapshot to try to recover it. Otherwise, perform factory reprogramming 30 | of MicroPython firmware (completely erase flash, followed by firmware 31 | programming). 32 | """ % (bdev.START_SEC, bdev.blocks)) 33 | time.sleep(3) 34 | 35 | def setup(check = True): 36 | if check: 37 | check_bootsec() 38 | print("Performing initial setup") 39 | wifi() 40 | uos.VfsFat.mkfs(bdev) 41 | vfs = uos.VfsFat(bdev) 42 | uos.mount(vfs, '/') 43 | with open("boot.py", "w") as f: 44 | f.write("""\ 45 | # This file is executed on every boot (including wake-boot from deepsleep) 46 | import esp 47 | esp.osdebug(None) 48 | import gc 49 | #import webrepl 50 | #webrepl.start() 51 | gc.collect() 52 | """) 53 | 54 | return vfs 55 | 56 | -------------------------------------------------------------------------------- /src/AzurePublish.py: -------------------------------------------------------------------------------- 1 | 2 | answer = '' 3 | 4 | def data(msg, PASSWD): 5 | import setting 6 | import ujson 7 | from mqtt import MQTTClient 8 | global answer 9 | answer = 'OK' 10 | IOTHUB = setting.get('iothub') 11 | DEVICE = setting.get('iotdevicename') 12 | KEY = setting.get('iotdevicesecret') 13 | USER = IOTHUB + '/' + DEVICE + '/api-version=2016-11-14' 14 | print('--------------MQTT----------') 15 | print('DEVICE: ', DEVICE) 16 | print('IOTHUB: ', IOTHUB) 17 | print('USER: ', USER) 18 | print('PASSWD: ', PASSWD) 19 | c = MQTTClient(DEVICE, IOTHUB, 8883, USER, PASSWD, 0, True) # client_id, server, port=0, user=None, password=None, keepalive=0, ssl=False, ssl_params={}) 20 | c.set_callback(sub_cb) 21 | try: 22 | c.connect() 23 | print('--------------PUBLISH----------') 24 | print('DEVICE: ', 'devices/' + DEVICE + '/messages/events/') 25 | print('MSG: ', msg) 26 | c.publish('devices/' + DEVICE + '/messages/events/', msg, False, 1) # topic, msg, retain=False, qos=0 27 | c.subscribe('$iothub/twin/res/#', 1) # topic, qos=0 28 | c.publish('$iothub/twin/GET/?$rid=2', '', False, 1) 29 | c.wait_msg() 30 | dictr = {} 31 | dictr["RSSI"] = setting.get('RSSI') 32 | dictr["health"] = setting.get('health') 33 | dictr["hwid"] = setting.get('hwid') 34 | dictr["VBat"] = setting.get('VBat') 35 | dictr["FWversion"] = setting.get('FWversion') 36 | dictr["FWnumber"] = setting.get('FWnumber') 37 | try: 38 | dict = ujson.loads(answer) 39 | print('RX TWIN MSG: ', dict) 40 | dict_rep = dict["reported"] 41 | keyp = dict_rep["keypresses"] + 1 42 | dictr["keypresses"] = keyp 43 | except: 44 | dictr["keypresses"] = 1 45 | print('TX TWIN MSG: ', dictr) 46 | reported = ujson.dumps(dictr) 47 | c.publish('$iothub/twin/PATCH/properties/reported/?$rid=1', reported, False, 1) 48 | c.disconnect() 49 | except(): 50 | answer = 'ERROR' 51 | return answer 52 | 53 | def sub_cb(topic, recive): 54 | global answer 55 | answer = recive.decode() 56 | -------------------------------------------------------------------------------- /src/RepUpdate.py: -------------------------------------------------------------------------------- 1 | def update(): 2 | import ujson 3 | import setting 4 | import network 5 | try: 6 | FWnumber_me = setting.get('FWnumber') 7 | FWnumber_new = setting.get('FWnumberNew') 8 | print('FW me: ', FWnumber_me) 9 | print('FW new: ', FWnumber_new) 10 | if FWnumber_me != FWnumber_new: 11 | import wget 12 | import wifi 13 | import os 14 | FWurl = setting.get('FWurl') 15 | print('Download new FW: ', FWurl) 16 | if wifi.wait_sta(15): 17 | print('Start Update: ', FWurl) 18 | encoded = wget.get(FWurl).decode() 19 | path_spl = FWurl.split('/') 20 | protokoll = path_spl.pop(0) 21 | path_spl.pop(0) 22 | server = path_spl.pop(0) 23 | file = path_spl.pop() 24 | path = '/'.join(path_spl) 25 | dict = ujson.loads(encoded) 26 | file_list = dict['INSTALL'] 27 | for file in file_list: 28 | print('Update File : ' + protokoll + '//' + server + '/' + path + '/' + file + ' >>> ' + file + '.tmp') 29 | a = wget.get_file(protokoll + '//' + server + '/' + path + '/' + file, file + '.tmp') 30 | fileSize = os.stat( file + '.tmp') 31 | if fileSize[6] > 0: 32 | os.rename(file + '.tmp', file) 33 | print('Renamed file ' + file + '.tmp', file) 34 | else: 35 | print('Error updating file : ', file) 36 | return 37 | setting.set('FWnumber', FWnumber_new) 38 | setting.set('FWversion', '{:04.2f}'.format(FWnumber_new/100)) 39 | except: 40 | print('ERROR in RepUpdate.update') 41 | 42 | def new(VBat): 43 | import setting 44 | import ubinascii 45 | import network 46 | try: 47 | keypresses = setting.get('keypresses') + 1 48 | hwid = ubinascii.hexlify(network.WLAN().config('mac'),':').decode().upper() 49 | setting.set('keypresses', keypresses) 50 | setting.set('hwid', hwid) 51 | setting.set('VBat', VBat) 52 | setting.set('RSSI', network.WLAN().status('rssi')) 53 | except: 54 | print('ERROR in RepUpdate.new') 55 | -------------------------------------------------------------------------------- /src/GenSasToken.py: -------------------------------------------------------------------------------- 1 | def now(ExTime = 600): 2 | from ntptime import settime 3 | import setting 4 | import utime 5 | import time 6 | 7 | timeserver = setting.get('timeserver') 8 | try: 9 | print(utime.time()) 10 | timeset = False 11 | nn = 0 12 | while timeset == False: 13 | try: 14 | if timeserver =='': 15 | settime() 16 | timeset = True 17 | else: 18 | settime(timeserver) 19 | timeset = True 20 | except: 21 | timeset = False 22 | print('Try NTP') 23 | nn += 1 24 | if nn == 15: 25 | timeset = True 26 | time.sleep(1) 27 | print(utime.time()) 28 | IOTHUB = setting.get('iothub') 29 | DEVICE = setting.get('iotdevicename') 30 | KEY = setting.get('iotdevicesecret') 31 | USER = IOTHUB + '/' + DEVICE #+ '/api-version=2016-11-14' 32 | EXPIRES = utime.time() + 946684800 + ExTime # +30 Jahre + 10 Min 33 | PASSWD = GenerateAzureSasToken(IOTHUB + '/devices/' + DEVICE, KEY, EXPIRES) 34 | except: 35 | PASSWD = '' 36 | return PASSWD 37 | 38 | def GenerateAzureSasToken(uri, key, expiryTimestamp, policy_name=None): 39 | from ubinascii import a2b_base64, b2a_base64 40 | def _quote(s) : 41 | r = '' 42 | for c in str(s) : 43 | if (c >= 'a' and c <= 'z') or \ 44 | (c >= '0' and c <= '9') or \ 45 | (c >= 'A' and c <= 'Z') or \ 46 | (c in '.-_') : 47 | r += c 48 | else : 49 | r += '%%%02X' % ord(c) 50 | return r 51 | uri = _quote(uri) 52 | sign_key = b'%s\n%d' % (uri, int(expiryTimestamp)) 53 | key = a2b_base64(key) 54 | hmac = HMACSha256(key, sign_key) 55 | signature = _quote( b2a_base64(hmac).decode().strip() ) 56 | token = 'sr=' + uri + '&' + 'sig=' + signature + '&' + 'se=' + str(expiryTimestamp) 57 | if policy_name : 58 | token += '&' + 'skn=' + policy_name 59 | return 'SharedAccessSignature ' + token 60 | 61 | def HMACSha256(keyBin, msgBin) : 62 | from uhashlib import sha256 63 | block_size = 64 # SHA-256 blocks size 64 | 65 | trans_5C = bytearray(256) 66 | for x in range(len(trans_5C)) : 67 | trans_5C[x] = x^0x5C 68 | 69 | trans_36 = bytearray(256) 70 | for x in range(len(trans_36)) : 71 | trans_36[x] = x^0x36 72 | 73 | def translate(d, t) : 74 | res = bytearray(len(d)) 75 | for x in range(len(d)) : 76 | res[x] = t[d[x]] 77 | return res 78 | 79 | keyBin = keyBin + chr(0) * (block_size - len(keyBin)) 80 | 81 | inner = sha256() 82 | inner.update(translate(keyBin, trans_36)) 83 | inner.update(msgBin) 84 | inner = inner.digest() 85 | 86 | outer = sha256() 87 | outer.update(translate(keyBin, trans_5C)) 88 | outer.update(inner) 89 | 90 | return outer.digest() 91 | -------------------------------------------------------------------------------- /src/wifi.py: -------------------------------------------------------------------------------- 1 | def do_sta(): 2 | import network 3 | import time 4 | import setting 5 | try: 6 | ssid = setting.get('ssid') 7 | password = setting.get('password') 8 | usedhcp = setting.get('usedhcp') 9 | ip = setting.get('ip') 10 | netmask = setting.get('netmask') 11 | gateway = setting.get('gateway') 12 | dnsserver = setting.get('dnsserver') 13 | sta_if = network.WLAN(network.STA_IF) 14 | sta_if.active(True) 15 | if usedhcp == 'no': 16 | sta_if.ifconfig((ip, netmask, gateway, dnsserver)) 17 | sta_if.connect(ssid, password) 18 | setting.set('opsmode', 'client') 19 | except: 20 | print('ERROR STA WIFI') 21 | 22 | def do_ap(): 23 | import network 24 | import ubinascii 25 | import setting 26 | try: 27 | ap_if = network.WLAN(network.AP_IF) 28 | ap_if.active(True) 29 | mac = ubinascii.hexlify(network.WLAN().config('mac'),':').decode() 30 | mac = mac[9:100] 31 | mac = mac.upper() 32 | ap_if.config(essid='ESP_' + mac, authmode=0) 33 | setting.set('opsmode', 'SoftAP') 34 | except: 35 | print('ERROR AP WIFI') 36 | 37 | def do_nothing(): 38 | import network 39 | sta_if = network.WLAN(network.STA_IF) 40 | sta_if.active(False) 41 | ap_if = network.WLAN(network.AP_IF) 42 | ap_if.active(False) 43 | 44 | def is_sta(): 45 | import network 46 | import setting 47 | sta_if = network.WLAN(network.STA_IF) 48 | if sta_if.isconnected(): 49 | try: 50 | print(sta_if.ifconfig()) 51 | setting.set('ip', sta_if.ifconfig()[0]) 52 | setting.set('netmask', sta_if.ifconfig()[1]) 53 | setting.set('gateway', sta_if.ifconfig()[2]) 54 | setting.set('dnsserver', sta_if.ifconfig()[3]) 55 | except: 56 | print ('ERROR in is_sta') 57 | return(sta_if.isconnected()) 58 | 59 | def is_ap(): 60 | import network 61 | ap_if = network.WLAN(network.AP_IF) 62 | return(ap_if.isconnected()) 63 | 64 | def wait_sta(sec): 65 | import time 66 | nn = 0 67 | while nn < sec: 68 | if is_sta(): 69 | return True 70 | break 71 | else: 72 | print('Wait for STA WLAN: ', nn) 73 | nn += 1 74 | time.sleep(1) 75 | return False 76 | 77 | def scan(): 78 | import network 79 | import ujson 80 | import ubinascii 81 | sta_if = network.WLAN(network.STA_IF) 82 | sta_if.active(True) 83 | decoded = sta_if.scan() 84 | list = [] 85 | for element in decoded: 86 | dict = {} 87 | dict["ssid"] = element[0] 88 | dict["bssid"] = ubinascii.hexlify(element[1],':').decode().upper() 89 | dict["channel"] = element[2] 90 | dict["rssi"] = element[3] 91 | dict["authmode"] = element[4] 92 | dict["hidden"] = element[5] 93 | list.append(dict) 94 | encoded = ujson.dumps(list) 95 | if encoded != '': 96 | return encoded 97 | else: 98 | return '' 99 | 100 | if __name__ == '__main__': 101 | main() -------------------------------------------------------------------------------- /src/control.py: -------------------------------------------------------------------------------- 1 | LED_RED_GPIO = 12 # Pin10 RED 2 | LED_GREEN_GPIO = 14 # Pin9 GREEN 3 | ADC_GPIO = 0 # Button 4 | POWER_GPIO = 5 # Power Hold 5 | 6 | from machine import Pin,ADC,PWM,Timer 7 | power = Pin(POWER_GPIO,Pin.OUT) 8 | power.value(1) 9 | 10 | import time 11 | import webrepl 12 | import gc 13 | import ujson 14 | import wifi 15 | import setting 16 | import RepUpdate 17 | 18 | def led_blink(freq, ledg_duty, ledr_duty): 19 | led_green_pwm.freq(freq) 20 | led_red_pwm.freq(freq) 21 | led_green_pwm.duty(1023 - ledg_duty) 22 | led_red_pwm.duty(1023 - ledr_duty) 23 | 24 | def shutdown(save = True): 25 | print('Stop Machine') 26 | if save == True: 27 | gc.collect() 28 | #print('RAM free: {} RAM allocated: {}'.format(gc.mem_free(), gc.mem_alloc())) 29 | setting.save('config.json') 30 | print('Save Config') 31 | time.sleep(2) 32 | print('Power Off') 33 | power.value(0) 34 | 35 | led_red = Pin(LED_RED_GPIO,Pin.OUT) 36 | led_green = Pin(LED_GREEN_GPIO,Pin.OUT) 37 | led_green_pwm = PWM(led_green) 38 | led_red_pwm = PWM(led_red) 39 | led_blink(1, 0, 0) 40 | 41 | adc = ADC(ADC_GPIO) 42 | VBat = adc.read() * 3.3 / 1024 43 | 44 | interruptCounter = 0 45 | save_flag = True 46 | 47 | def handleInterrupt(timer): 48 | global interruptCounter 49 | global save_flag 50 | interruptCounter = interruptCounter + 1 51 | if interruptCounter == 1: 52 | timeserver = setting.get('timeserver') 53 | if timeserver == '': 54 | setting.set('timeserver', 'pool.ntp.org') 55 | webrepl.stop() 56 | RepUpdate.new(VBat) 57 | 58 | if interruptCounter <= 5: 59 | print('<= 5 sec') 60 | if adc.read() < 200: 61 | timer.deinit() 62 | wifi.do_nothing() 63 | wifi.do_sta() 64 | print('Push Message') 65 | led_blink(10, 200, 0) 66 | if wifi.wait_sta(15): 67 | try: 68 | import GenSasToken 69 | import AzurePublish 70 | import ubinascii 71 | import network 72 | mac = ubinascii.hexlify(network.WLAN().config('mac'),':').decode().upper() 73 | UserJson = setting.get('userjson') 74 | UserJson = UserJson.strip('\n') 75 | UserJson = UserJson.strip('\r') 76 | UserJson = UserJson.strip('\n') 77 | UserJson = UserJson.strip(' ') 78 | UserJson = UserJson.lstrip('{') 79 | UserJson = UserJson.rstrip('}') 80 | UserJson = UserJson.strip(' ') 81 | msg = '{"UniqueID": "' + mac + '"' 82 | if UserJson != '': 83 | msg += ',' + UserJson 84 | msg += '}' 85 | gc.collect() 86 | token = GenSasToken.now() 87 | gc.collect() 88 | encoded = AzurePublish.data(msg, token) 89 | except: 90 | encoded = 'ERROR' 91 | else: 92 | encoded = 'ERROR' 93 | if encoded != 'ERROR': 94 | try: 95 | dict = ujson.loads(encoded) 96 | dict = dict["desired"] 97 | setting.set('FWnumberNew', dict["FWnumber"]) 98 | setting.set('FWurl', dict["FWurl"]) 99 | except: 100 | print('ERROR TWIN MSG') 101 | led_blink(1, 1023, 0) 102 | else: 103 | print('ERROR Azure TX') 104 | led_blink(1, 0, 1023) 105 | time.sleep(2) 106 | led_blink(1, 0, 0) 107 | RepUpdate.update() 108 | shutdown(save_flag) 109 | if interruptCounter == 5: 110 | print('5 sec') 111 | led_blink(1, 200, 200) 112 | if interruptCounter == 10: 113 | print('10 sec') 114 | if adc.read() < 200: 115 | print('WEB AP ...') 116 | timer.deinit() 117 | wifi.do_nothing() 118 | wifi.do_ap() 119 | led_blink(1, 0, 200) 120 | time.sleep(2) 121 | try: 122 | import webserver 123 | webserver.start() 124 | except: 125 | print('WEB Server Crash') 126 | led_blink(1, 0, 0) 127 | shutdown() 128 | else: 129 | led_blink(3, 200, 200) 130 | if interruptCounter == 15: 131 | print('15 sec') 132 | if adc.read() < 200: 133 | print('WEB STA ...') 134 | timer.deinit() 135 | wifi.do_nothing() 136 | wifi.do_sta() 137 | led_blink(3, 0, 200) 138 | if wifi.wait_sta(15): 139 | try: 140 | import webserver 141 | webserver.start() 142 | except: 143 | print('WEB Server Crash') 144 | led_blink(1, 0, 0) 145 | shutdown() 146 | else: 147 | led_blink(10, 0, 511) 148 | print('Repair Time 120 sec') 149 | wifi.do_nothing() 150 | wifi.do_ap() 151 | wifi.do_sta() 152 | wifi.wait_sta(15) 153 | webrepl.start() 154 | if interruptCounter == 120: 155 | timer.deinit() 156 | print('120 sec') 157 | shutdown(False) 158 | 159 | def start(save = True): 160 | global save_flag 161 | save_flag = save 162 | print('') 163 | #print('Start Control, RAM free: {} RAM allocated: {}'.format(gc.mem_free(), gc.mem_alloc())) 164 | setting.load('config.json') 165 | timer = Timer(0) 166 | led_blink(1, 200, 0) 167 | interruptCounter = 0 168 | print('Start Timer') 169 | timer.init(period=1000, mode=Timer.PERIODIC, callback=handleInterrupt) 170 | return 171 | 172 | -------------------------------------------------------------------------------- /BuildFirmware.md: -------------------------------------------------------------------------------- 1 | #Build Firmware 2 | 3 | - Toolchain Setup: 4 | 5 | To build MicroPython firmware for the ESP8266 you'll need to first build the ESP open SDK toolchain that can compile code for the ESP8266's processor. You could manually compile and install this SDK on your computer, however it's much easier to use a small virtual machine running Linux to compile and use the toolchain. This way you can use the ESP open SDK from any computer regardless of it running Windows, Mac OSX, or even Linux, and you can keep the SDK's tools in an environment that's isolated and won't conflict with any other development tools on your machine. 6 | 7 | - Dependencies: 8 | 9 | 1) VirtualBox - This is open source virtualization software that is a free download. 10 | 2) Vagrant - This is an open source wrapper around VirtualBox which makes it easy to create and run a virtual machine from the command line. Vagrant is also free to download. 11 | 3) Git - Source control system used to download the configuration for this project. Git is also free and open source. 12 | 13 | Links: 14 | https://www.virtualbox.org/ 15 | https://www.vagrantup.com/ 16 | http://git-scm.com/ 17 | 18 | - Provision Virtual Machine: 19 | 20 | To create and provision the virtual machine you'll need to download a Vagrant configuration file. This file defines what operating system to install (Ubuntu 14.04) and some commands to prepare the operating system for building the ESP SDK. 21 | Start by opening a command line terminal (in Windows make sure to open a 'Git Bash' command window as you'll need to use Git commands) and run the following command to clone the repository for this project and navigate inside it: 22 | 23 | git clone https://github.com/adafruit/esp8266-micropython-vagrant.git 24 | cd esp8266-micropython-vagrant 25 | 26 | Now 'turn on' the virtual machine by running this command: 27 | 28 | vagrant up 29 | 30 | The first time the 'vagrant up' command runs it will take a bit of time as it downloads the operating system image, but later 'vagrant up' commands will be faster as the OS image is cached internally. 31 | 32 | If you see an error go back and make sure you've installed both VirtualBox and Vagrant. Also make sure you're executing the command from inside the cloned repository's directory, there should be a file named Vagrantfile inside the directory you're running these commands from. 33 | 34 | Once the virtual machine is running you can enter a Linux command terminal on it by executing the command: 35 | 36 | vagrant ssh 37 | 38 | After a moment you should be at an Ubuntu Linux command prompt that looks something like: 39 | 40 | vagrant@vagrant-ubuntu-trusty-64:~$ 41 | 42 | - Compile ESP Open SDK: 43 | 44 | Once inside the virtual machine you'll first need to compile the ESP open SDK. The SDK source code has already been downloaded in the esp-open-sdk subdirectory during the virtual machine provisioning. You just need to change to the directory and execute a command to make the project. Run the following commands: 45 | 46 | cd ~/esp-open-sdk 47 | make STANDALONE=y 48 | 49 | Note that the compilation will take a bit of time. On my machine compilation took about 30 minutes, but on an older or slower machine it might take an hour or more. Luckily you only need to compile the ESP open SDK once and then can quickly build MicroPython using the compiled SDK tools. 50 | 51 | If you see the compilation fail with a different error then there might be a problem with the ESP open SDK. Try checking the github issues for it to see if there is a known issue with the error you received. 52 | 53 | Now the ESP open SDK is compiled and you're almost ready to build MicroPython (or any other ESP8266 code you'd ever like to compile). First though you need to add the ESP open SDK tools to the virtual machine's path so MicroPython can find them. Run this command to update the .profile file that runs whenever you log into the virtual machine: 54 | 55 | echo "PATH=$(pwd)/xtensa-lx106-elf/bin:\$PATH" >> ~/.profile 56 | 57 | To make this updated path available log out and back in to the virtual machine by running: 58 | 59 | exit 60 | vagrant ssh 61 | 62 | Once logged in again you should see your path environment variable has the ESP open SDK tools in it. You can check this by running the command: 63 | 64 | echo $PATH 65 | 66 | You should see a path value that looks something like this (notice the esp-open-sdk tools in the path): 67 | 68 | /home/vagrant/esp-open-sdk/xtensa-lx106-elf/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games 69 | 70 | Remember you only need to perform the ESP open SDK compilation steps above once in the virtual machine. 71 | 72 | - Compile MicroPython Firmware: 73 | 74 | Next you can build the MicroPython firmware for the ESP8266. Make sure you've followed all the steps above and have a virtual machine running and the ESP open SDK compiled. 75 | 76 | The MicroPython source code has already been downloaded to the micropython folder during the virtual machine provisioning. However you need to run a small git command to pull in external dependencies before you can build it. To install this dependency and then start the compilation execute the following commands inside the virtual machine: 77 | 78 | cd ~/micropython 79 | git submodule update --init 80 | make -C mpy-cross 81 | cd ~/micropython/ports/esp8266 82 | make axtls 83 | make 84 | 85 | The MicroPython library compilation should be quick and only take a few minutes at most. 86 | 87 | Once finished the output will be the file ./build/firmware-combined.bin. In the next section you'll walk through how to load the firmware on an ESP8266 board, however first you'll need to copy the firmware bin file out of the virtual machine. Luckily Vagrant has a special directory inside the virtual machine which can be used to copy files between the virtual machine and the host computer running Vagrant. Execute the following command to copy out the firmware bin file to this shared folder: 88 | 89 | cp ./build/firmware-combined.bin /vagrant/ 90 | 91 | Now exit the virtual machine by running the following command: 92 | 93 | exit 94 | vagrant halt 95 | exit 96 | 97 | The BIN File is in C:\Users\User\esp8266-micropython-vagrant\ 98 | 99 | -------------------------------------------------------------------------------- /MicroPython_bin/modules/mqtt.py: -------------------------------------------------------------------------------- 1 | import usocket as socket 2 | import ustruct as struct 3 | from ubinascii import hexlify 4 | 5 | class MQTTException(Exception): 6 | pass 7 | 8 | class MQTTClient: 9 | 10 | def __init__(self, client_id, server, port=0, user=None, password=None, keepalive=0, ssl=False, ssl_params={}): 11 | if port == 0: 12 | port = 8883 if ssl else 1883 13 | self.client_id = client_id 14 | self.sock = None 15 | self.server = server 16 | self.port = port 17 | self.ssl = ssl 18 | self.ssl_params = ssl_params 19 | self.pid = 0 20 | self.cb = None 21 | self.user = user 22 | self.pswd = password 23 | self.keepalive = keepalive 24 | self.lw_topic = None 25 | self.lw_msg = None 26 | self.lw_qos = 0 27 | self.lw_retain = False 28 | 29 | def _send_str(self, s): 30 | self.sock.write(struct.pack("!H", len(s))) 31 | self.sock.write(s) 32 | 33 | def _recv_len(self): 34 | n = 0 35 | sh = 0 36 | while 1: 37 | b = self.sock.read(1)[0] 38 | n |= (b & 0x7f) << sh 39 | if not b & 0x80: 40 | return n 41 | sh += 7 42 | 43 | def set_callback(self, f): 44 | self.cb = f 45 | 46 | def set_last_will(self, topic, msg, retain=False, qos=0): 47 | assert 0 <= qos <= 2 48 | assert topic 49 | self.lw_topic = topic 50 | self.lw_msg = msg 51 | self.lw_qos = qos 52 | self.lw_retain = retain 53 | 54 | def connect(self, clean_session=True): 55 | self.sock = socket.socket() 56 | addr = socket.getaddrinfo(self.server, self.port)[0][-1] 57 | self.sock.connect(addr) 58 | if self.ssl: 59 | import ussl 60 | self.sock = ussl.wrap_socket(self.sock, **self.ssl_params) 61 | premsg = bytearray(b"\x10\0\0\0\0\0") 62 | msg = bytearray(b"\x04MQTT\x04\x02\0\0") 63 | 64 | sz = 10 + 2 + len(self.client_id) 65 | msg[6] = clean_session << 1 66 | if self.user is not None: 67 | sz += 2 + len(self.user) + 2 + len(self.pswd) 68 | msg[6] |= 0xC0 69 | if self.keepalive: 70 | assert self.keepalive < 65536 71 | msg[7] |= self.keepalive >> 8 72 | msg[8] |= self.keepalive & 0x00FF 73 | if self.lw_topic: 74 | sz += 2 + len(self.lw_topic) + 2 + len(self.lw_msg) 75 | msg[6] |= 0x4 | (self.lw_qos & 0x1) << 3 | (self.lw_qos & 0x2) << 3 76 | msg[6] |= self.lw_retain << 5 77 | 78 | i = 1 79 | while sz > 0x7f: 80 | premsg[i] = (sz & 0x7f) | 0x80 81 | sz >>= 7 82 | i += 1 83 | premsg[i] = sz 84 | 85 | self.sock.write(premsg, i + 2) 86 | self.sock.write(msg) 87 | #print(hex(len(msg)), hexlify(msg, ":")) 88 | self._send_str(self.client_id) 89 | if self.lw_topic: 90 | self._send_str(self.lw_topic) 91 | self._send_str(self.lw_msg) 92 | if self.user is not None: 93 | self._send_str(self.user) 94 | self._send_str(self.pswd) 95 | resp = self.sock.read(4) 96 | assert resp[0] == 0x20 and resp[1] == 0x02 97 | if resp[3] != 0: 98 | raise MQTTException(resp[3]) 99 | return resp[2] & 1 100 | 101 | def disconnect(self): 102 | self.sock.write(b"\xe0\0") 103 | self.sock.close() 104 | 105 | def ping(self): 106 | self.sock.write(b"\xc0\0") 107 | 108 | def publish(self, topic, msg, retain=False, qos=0): 109 | pkt = bytearray(b"\x30\0\0\0") 110 | pkt[0] |= qos << 1 | retain 111 | sz = 2 + len(topic) + len(msg) 112 | if qos > 0: 113 | sz += 2 114 | assert sz < 2097152 115 | i = 1 116 | while sz > 0x7f: 117 | pkt[i] = (sz & 0x7f) | 0x80 118 | sz >>= 7 119 | i += 1 120 | pkt[i] = sz 121 | #print(hex(len(pkt)), hexlify(pkt, ":")) 122 | self.sock.write(pkt, i + 1) 123 | self._send_str(topic) 124 | if qos > 0: 125 | self.pid += 1 126 | pid = self.pid 127 | struct.pack_into("!H", pkt, 0, pid) 128 | self.sock.write(pkt, 2) 129 | self.sock.write(msg) 130 | if qos == 1: 131 | while 1: 132 | op = self.wait_msg() 133 | if op == 0x40: 134 | sz = self.sock.read(1) 135 | assert sz == b"\x02" 136 | rcv_pid = self.sock.read(2) 137 | rcv_pid = rcv_pid[0] << 8 | rcv_pid[1] 138 | if pid == rcv_pid: 139 | return 140 | elif qos == 2: 141 | assert 0 142 | 143 | def subscribe(self, topic, qos=0): 144 | assert self.cb is not None, "Subscribe callback is not set" 145 | pkt = bytearray(b"\x82\0\0\0") 146 | self.pid += 1 147 | struct.pack_into("!BH", pkt, 1, 2 + 2 + len(topic) + 1, self.pid) 148 | #print(hex(len(pkt)), hexlify(pkt, ":")) 149 | self.sock.write(pkt) 150 | self._send_str(topic) 151 | self.sock.write(qos.to_bytes(1, "little")) 152 | while 1: 153 | op = self.wait_msg() 154 | if op == 0x90: 155 | resp = self.sock.read(4) 156 | #print(resp) 157 | assert resp[1] == pkt[2] and resp[2] == pkt[3] 158 | if resp[3] == 0x80: 159 | raise MQTTException(resp[3]) 160 | return 161 | 162 | # Wait for a single incoming MQTT message and process it. 163 | # Subscribed messages are delivered to a callback previously 164 | # set by .set_callback() method. Other (internal) MQTT 165 | # messages processed internally. 166 | def wait_msg(self): 167 | res = self.sock.read(1) 168 | self.sock.setblocking(True) 169 | if res is None: 170 | return None 171 | if res == b"": 172 | raise OSError(-1) 173 | if res == b"\xd0": # PINGRESP 174 | sz = self.sock.read(1)[0] 175 | assert sz == 0 176 | return None 177 | op = res[0] 178 | if op & 0xf0 != 0x30: 179 | return op 180 | sz = self._recv_len() 181 | topic_len = self.sock.read(2) 182 | topic_len = (topic_len[0] << 8) | topic_len[1] 183 | topic = self.sock.read(topic_len) 184 | sz -= topic_len + 2 185 | if op & 6: 186 | pid = self.sock.read(2) 187 | pid = pid[0] << 8 | pid[1] 188 | sz -= 2 189 | msg = self.sock.read(sz) 190 | self.cb(topic, msg) 191 | if op & 6 == 2: 192 | pkt = bytearray(b"\x40\x02\0\0") 193 | struct.pack_into("!H", pkt, 2, pid) 194 | self.sock.write(pkt) 195 | elif op & 6 == 4: 196 | assert 0 197 | 198 | # Checks whether a pending message from server is available. 199 | # If not, returns immediately with None. Otherwise, does 200 | # the same processing as wait_msg. 201 | def check_msg(self): 202 | self.sock.setblocking(False) 203 | return self.wait_msg() -------------------------------------------------------------------------------- /src/webserver.py: -------------------------------------------------------------------------------- 1 | import usocket as socket 2 | import setting 3 | import time 4 | import gc 5 | import ujson 6 | 7 | _mimeTypes = { 8 | b'.txt' : b'text/plain', 9 | b'.htm' : b'text/html', 10 | b'.html' : b'text/html', 11 | b'.css' : b'text/css', 12 | b'.json' : b'application/json', 13 | b'.jpg' : b'image/jpeg', 14 | b'.jpeg' : b'image/jpeg', 15 | b'.png' : b'image/png', 16 | b'.gif' : b'image/gif', 17 | b'.ico' : b'image/x-icon' 18 | } 19 | 20 | _hextobyte_cache = None 21 | 22 | def unquote(string): 23 | global _hextobyte_cache 24 | 25 | if not string: 26 | return b'' 27 | 28 | if isinstance(string, str): 29 | string = string.encode('utf-8') 30 | 31 | bits = string.split(b'%') 32 | if len(bits) == 1: 33 | return string 34 | 35 | res = [bits[0]] 36 | append = res.append 37 | 38 | if _hextobyte_cache is None: 39 | _hextobyte_cache = {} 40 | 41 | for item in bits[1:]: 42 | try: 43 | code = item[:2] 44 | char = _hextobyte_cache.get(code) 45 | if char is None: 46 | char = _hextobyte_cache[code] = bytes([int(code, 16)]) 47 | append(char) 48 | append(item[2:]) 49 | except KeyError: 50 | append(b'%') 51 | append(item) 52 | 53 | return b''.join(res) 54 | 55 | def start(): 56 | print('Start Web Server ...') 57 | #Setup Socket WebServer 58 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 59 | s.bind(('', 80)) 60 | s.listen(5) 61 | #s.settimeout(120) 62 | s.setblocking(False) 63 | while True: 64 | nn = 0 65 | gc.collect() 66 | print('Wait for connection.') 67 | while True: 68 | try: 69 | conn, addr = s.accept() 70 | break 71 | except: 72 | time.sleep_ms(10) 73 | nn += 1 74 | if nn == 12000: 75 | print('EXIT WEB Server Timeout') 76 | return 77 | 78 | print('Got a connection from ', str(addr)) 79 | request = b'' 80 | conn.settimeout(0.5) 81 | nn = 0 82 | try: 83 | while True: 84 | nn += 1 85 | data = conn.recv(100) 86 | request += data 87 | if nn >= 20: 88 | break 89 | except (OSError): 90 | pass 91 | if request.find(b'\r\n\r\n') != -1: 92 | request_spl = request.split(b' ') 93 | if len(request_spl) >= 2: 94 | met = request_spl[0] 95 | request_url = request_spl[1] 96 | request_url = request_url[1:1000] 97 | request_url_spl = request_url.split(b'?') 98 | if len(request_url_spl) >=2: 99 | url = request_url_spl[0] 100 | query = request_url_spl[1] 101 | else: 102 | url = request_url 103 | query = '' 104 | if url == b'': 105 | url = b'index.html' 106 | print("MET: ", met) 107 | print("URL: ", url) 108 | print("QUERY: ", query) 109 | if met == b'POST': 110 | _, post = request.split(b'\r\n\r\n', 1) 111 | print('POST: ', post) 112 | 113 | post_f = True 114 | if url == b'config/iothub': 115 | setting.post(post.decode()) 116 | post_f = False 117 | if url == b'config/timeserver': 118 | setting.post(post.decode()) 119 | post_f = False 120 | if url == b'config/wifi': 121 | setting.post(post.decode()) 122 | post_f = False 123 | if url == b'config/ipconfig': 124 | setting.post(post.decode()) 125 | post_f = False 126 | if url == b'config/userjson': 127 | setting.post(post.decode()) 128 | post_f = False 129 | 130 | if post_f: 131 | post_spl = post.split(b'&') 132 | for post_val in post_spl: 133 | parameter, value = post_val.split(b'=', 1) 134 | value = value.replace(b'+', b' ') 135 | value = unquote(value.decode()) 136 | val = value.decode() 137 | par = parameter.decode() 138 | if post == b'action=shutdown': 139 | print('EXIT WEB Server') 140 | return 141 | if val[0:3] != '***': 142 | setting.set(par, val) 143 | 144 | del request 145 | if (met == b'GET') | (met == b'POST'): 146 | try: 147 | try: 148 | url1, ext = url.split(b'.', 1) 149 | ext = b'.' + ext 150 | except (ValueError): 151 | ext = b'.json' 152 | url = url + ext 153 | pass 154 | response = '' 155 | encoded = '' 156 | if url == b'iothub.html': 157 | a01 = setting.get('iothub') 158 | a02 = setting.get('iotdevicename') 159 | a03 = setting.get('iotdevicesecret') 160 | a03 = '*' * len(a03) 161 | web_page = open(url) 162 | response = web_page.read().format(a01, a02, a03) 163 | web_page.close 164 | if url == b'timeserver.html': 165 | a01 = setting.get('timeserver') 166 | web_page = open(url) 167 | response = web_page.read().format(a01) 168 | web_page.close 169 | if url == b'wifi.html': 170 | a01 = setting.get('ssid') 171 | a02 = setting.get('password') 172 | a02 = '*' * len(a02) 173 | web_page = open(url) 174 | response = web_page.read().format(a01, a02) 175 | web_page.close 176 | if url == b'ipconfig.html': 177 | a01 = setting.get('usedhcp') 178 | a02 = setting.get('ip') 179 | a03 = setting.get('netmask') 180 | a04 = setting.get('gateway') 181 | a05 = setting.get('dnsserver') 182 | web_page = open(url) 183 | response = web_page.read().format(a01, a02, a03, a04, a05) 184 | web_page.close 185 | if url == b'userjson.html': 186 | a01 = setting.get('userjson') 187 | web_page = open(url) 188 | response = web_page.read().format(a01) 189 | web_page.close 190 | if url == b'config/iothub.json': 191 | dict = {} 192 | dict["iothub"] = setting.get('iothub') 193 | dict["iotdevicename"] = setting.get('iotdevicename') 194 | a01 = setting.get('iotdevicesecret') 195 | a01 = '*' * len(a01) 196 | dict["iotdevicesecret"] = a01 197 | response = ujson.dumps(dict) 198 | if url == b'config/timeserver.json': 199 | dict = {} 200 | dict["timeserver"] = setting.get('timeserver') 201 | response = ujson.dumps(dict) 202 | if url == b'config/wifi.json': 203 | dict = {} 204 | dict["ssid"] = setting.get('ssid') 205 | a01 = setting.get('password') 206 | a01 = '*' * len(a01) 207 | dict["password"] = a01 208 | response = ujson.dumps(dict) 209 | if url == b'config/ipconfig.json': 210 | dict = {} 211 | dict["usedhcp"] = setting.get('usedhcp') 212 | dict["ip"] = setting.get('ip') 213 | dict["netmask"] = setting.get('netmask') 214 | dict["gateway"] = setting.get('gateway') 215 | dict["dnsserver"] = setting.get('dnsserver') 216 | response = ujson.dumps(dict) 217 | if url == b'config/info.json': 218 | dict = {} 219 | dict["RSSI"] = setting.get('RSSI') 220 | dict["health"] = setting.get('health') 221 | dict["hwid"] = setting.get('hwid') 222 | dict["keypresses"] = setting.get('keypresses') 223 | dict["VBat"] = setting.get('VBat') 224 | dict["FWversion"] = setting.get('FWversion') 225 | dict["FWnumber"] = setting.get('FWnumber') 226 | response = ujson.dumps(dict) 227 | if url == b'config/userjson.json': 228 | dict = {} 229 | dict["userjson"] = setting.get('userjson') 230 | response = ujson.dumps(dict) 231 | if url == b'config/opsmode.json': 232 | dict = {} 233 | dict["opsmode"] = setting.get('opsmode') 234 | response = ujson.dumps(dict) 235 | if url == b'wifi/scan.json': 236 | import wifi 237 | response = wifi.scan() 238 | if response == '': 239 | web_page = open(url) 240 | web_page.close 241 | 242 | conn.send(b'HTTP/1.1 200 OK\n') 243 | conn.send(b'Server: MP-Server\n') 244 | if ext != b'': 245 | try: 246 | #print('MIME:', _mimeTypes[ext]) 247 | conn.send(b'Content-Type: ' + _mimeTypes[ext] + b'\n') 248 | except (KeyError): 249 | pass 250 | conn.send(b'Connection: close\r\n') 251 | conn.send(b'\r\n') 252 | if response == '': 253 | web_page = open(url) 254 | conn.sendall(web_page.read()) 255 | web_page.close 256 | else: 257 | conn.sendall(response) 258 | del response 259 | except: 260 | try: 261 | conn.send('HTTP/1.1 404 Not Found\n') 262 | conn.send('Server: MP-Server\n') 263 | conn.send('Connection: close\r\n') 264 | conn.send('\r\n') 265 | except (OSError, MemoryError): 266 | pass 267 | pass 268 | else: 269 | print('no html data recv') 270 | else: 271 | print('no html data recv') 272 | conn.close() 273 | 274 | --------------------------------------------------------------------------------