Setup configuration server and html files & assets

This commit is contained in:
2023-07-31 13:39:05 +02:00
parent 412ae296d8
commit 4458c3729f
3 changed files with 417 additions and 0 deletions

View File

@@ -0,0 +1,234 @@
import network, machine, utime, gc
from ubinascii import hexlify
from esp32 import NVS
try:
import usocket as socket
except:
import socket
gc.collect()
nvs = NVS('conf')
AP_SSID = 'MicroPython-AP'
AP_PASSWOD = '123456789'
REBOOT_DELAY = 2
settings = [
'ssid',
'pass',
'name',
'mqtt_broker',
'mqtt_topic',
'dht11_pin',
'ds28b20_pin',
'mac',
'peer',
'freq'
]
routes = []
routeTree = {}
contentTypes = {
'html': 'text/html',
'css': 'text/css'
}
def saveDeviceInfo():
setStorage({
'mac': hexlify(network.WLAN().config('mac'),':').decode(),
'freq': str(machine.freq() / 1000000) # megahertz
})
def setupAP():
ap = network.WLAN(network.AP_IF)
ap.active(True)
ap.config(essid=AP_SSID, password=AP_PASSWOD, security=3)
while ap.active() == False:
pass
print('Connection successful')
print(ap.ifconfig())
def setupServer():
# bind socket server to port 80
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('', 80))
s.listen(5)
return s
def reboot(delay = REBOOT_DELAY):
print (f'Rebooting device in {delay} seconds (Ctrl-C to escape).')
utime.sleep(delay)
machine.reset()
def identify():
led = machine.Pin(13, machine.Pin.OUT)
count = 6
while count > 0:
count = count - 1
led.value(1)
utime.sleep_ms(400)
led.value(0)
utime.sleep_ms(250)
def setStorage(data):
for key, value in data.items():
nvs.set_blob(key, value.encode())
def getStorageVar(key):
value = bytearray(96)
try:
length = nvs.get_blob(key, value)
value = value.decode('utf-8')[:length]
except OSError as e:
if 'ESP_ERR_NVS_NOT_FOUND' in str(e):
print('Missing NVS key "{}", adding blank value'.format(key))
value = ''
setStorage({key: value})
else:
raise e
return value
def importHTML(filename, route, hydration=None):
global routeTree
f = open(filename)
html = f.read()
html = ' '.join(html.split())
f.close()
if hydration:
html = hydrateHTMLTemplate(html, hydration)
routeTree[route] = html
def hydrateHTMLTemplate(source, keys):
for key in keys:
templateString = '{{ ' + key + ' }}'
source = source.replace(templateString, getStorageVar(key))
return source
def importRoutes():
global routes
importHTML('index.html', '/', settings),
importHTML('success.html', '/save')
importHTML('styles.css', '/styles.css')
routes = routeTree.keys()
def htmlEncodedStrings(string):
if '%3A' in string:
string = string.replace('%3A', ':')
return string
def parsePostRequest(req):
reqString = str(req).split()[-1]
reqString = reqString.split('\\n')[-1]
reqString = reqString[:-1]
args = reqString.split('&')
data = {}
for arg in args:
[key, value] = arg.split('=')
data[key] = htmlEncodedStrings(value)
print('got post data: ' + str(data))
return data
def getContentType(path):
try:
extension = path.split('.')[1]
return contentTypes[extension]
except:
return contentTypes['html']
def response200(conn, text, contentType='text/html'):
conn.send('HTTP/1.1 200 OK\n')
conn.send('Content-Type: %s\n' % contentType)
conn.send('Connection: close\n\n')
conn.send(text)
conn.close()
def response404(conn):
conn.send('HTTP/1.1 404 NOT FOUND\n')
conn.send('Connection: close\n\n')
conn.close()
def response400(conn):
conn.send('HTTP/1.1 400 BAD REQUEST\n')
conn.send('Connection: close\n\n')
conn.close()
def handleRequest(conn):
request = conn.recv(1024)
requestSegments = request.split()
if len(requestSegments) <= 2:
response400(conn)
method = requestSegments[0].decode('utf-8')
path = requestSegments[1].decode('utf-8')
contentType = getContentType(path)
if path in routes and method == 'GET':
response200(conn, routeTree[path], contentType)
elif path == '/save' and method == 'POST':
setStorage(parsePostRequest(request))
importHTML('index.html', '/', settings),
response200(conn, routeTree[path], contentType)
elif path == '/reboot' and method == 'POST':
response200(conn, 'ok', contentType)
reboot()
elif path == '/identify' and method == 'POST':
response200(conn, 'ok', contentType)
identify()
else:
response404(conn)
def serverRequests(s):
while True:
conn, addr = s.accept()
print('Received req from: ' + str(addr))
handleRequest(conn)
def serveSetupServer():
setupAP()
saveDeviceInfo()
s = setupServer()
importRoutes()
serverRequests(s)
if __name__ == '__main__':
try:
serveSetupServer()
except KeyboardInterrupt as err:
raise err # use Ctrl-C to exit to micropython repl
except Exception as err:
print ('Error during execution:', err)
reboot()

106
src/setup/styles.css Normal file
View File

@@ -0,0 +1,106 @@
body {
font-family: Helvetica Neue;
min-height: calc(100vh - 1.3rem);
min-height: -webkit-fill-available;
margin: 0;
padding: 0.8rem;
display: flex;
flex-direction: column;
align-items: center;
}
section {
max-width: 900px;
width: 100%;
}
h1 {
text-align: center;
}
h3 {
margin: 0.2rem 0 !important;
padding-left: 0.55rem;
font-size: 1.1rem;
}
label {
margin-right: 0.5rem;
font-size: 1rem;
color: rgba(0, 0, 0, 0.7);
width: 100px;
}
input {
background-color: transparent;
border: none;
font-size: inherit;
flex-grow: 1;
}
input[type="password"], input#pass {
margin-right: 1rem;
}
button, button.light:hover {
background-color: #1a202e;
color: white;
padding: 0.75rem;
text-align: center;
vertical-align: middle;
border-radius: 0.8rem;
border: none;
font-size: 1.1rem;
cursor: pointer;
text-transform: uppercase;
font-weight: bold;
border: 2px solid #1a202e;
}
button.light {
border: 2px solid #F0F3F7;
background-color: white;
color: #1a202e;
}
button, i {
user-select: none;
-webkit-user-select: none;
cursor: pointer;
touch-action: manipulation;
}
form {
position: relative;
width: 100%;
display: flex;
flex-grow: 1;
flex-direction: column;
margin-top: 1.5rem;
}
form > * {
margin-bottom: 1.1rem;
}
form div {
display: flex;
align-items: center;
background-color: #f1f5f9;
padding: 0.8rem 0.75rem;
border-radius: 0.5rem;
font-size: 1rem;
}
dialog {
width: 100%;
max-width: 85vw;
}
dialog form {
margin-top: 0;
}
dialog button {
margin-top: 2rem;
}

77
src/setup/success.html Normal file
View File

@@ -0,0 +1,77 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width,initial-scale=1,maximum-scale=1"
/>
<title></title>
<link rel="stylesheet" type="text/css" href="./styles.css" />
</head>
<body>
<h1>Sucessfully updated!</h1>
<p>
Settings successfully save to controller. Disable setup mode switch and
reboot.
</p>
<div class="bottom-content">
<p style="display: none">
Rebooting in <span id="countdown">5</span> seconds
</p>
<button id="reboot">Reboot device</button>
</div>
</body>
<script>
function countDownAndSubmitReboot() {
countdownElement.parentElement.style.display = "block";
let count = Number(countdownElement.innerText);
const id = setInterval(() => {
count = count - 1;
countdownElement.innerText = count;
if (count === 0) {
clearTimeout(id);
fetch('/reboot', { method: 'POST' })
.then(console.log)
}
}, 1000);
}
const countdownElement = document.getElementById("countdown");
const rebootBtn = document.getElementById("reboot");
rebootBtn.addEventListener("click", countDownAndSubmitReboot);
</script>
<style>
.bottom-content {
margin-top: auto;
width: 100%;
}
.bottom-content p {
text-align: center;
}
button {
background-color: #1a202e;
color: white;
padding: 0.75rem;
text-align: center;
vertical-align: middle;
width: 100%;
border-radius: 0.8rem;
border: none;
font-size: 1.1rem;
cursor: pointer;
text-transform: uppercase;
}
</style>
</html>