niot-helfer 0.0.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- niot_helfer-0.0.2/PKG-INFO +17 -0
- niot_helfer-0.0.2/README.md +0 -0
- niot_helfer-0.0.2/pre-push +42 -0
- niot_helfer-0.0.2/pyproject.toml +41 -0
- niot_helfer-0.0.2/setup.cfg +4 -0
- niot_helfer-0.0.2/src/examples/example_schalter_per_wlan_als_ap.py +19 -0
- niot_helfer-0.0.2/src/examples/example_schalter_per_wlan_als_client.py +19 -0
- niot_helfer-0.0.2/src/examples/schalter.html +1 -0
- niot_helfer-0.0.2/src/index.html +100 -0
- niot_helfer-0.0.2/src/niot_helfer.egg-info/PKG-INFO +17 -0
- niot_helfer-0.0.2/src/niot_helfer.egg-info/SOURCES.txt +12 -0
- niot_helfer-0.0.2/src/niot_helfer.egg-info/dependency_links.txt +1 -0
- niot_helfer-0.0.2/src/niot_helfer.egg-info/top_level.txt +2 -0
- niot_helfer-0.0.2/src/niot_helfer.py +474 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: niot_helfer
|
|
3
|
+
Version: 0.0.2
|
|
4
|
+
Summary: Ein Helfermodul für NIT-Unterricht mit IoT Projekten
|
|
5
|
+
Author-email: dickschaedel <dickschaedel@example.org>
|
|
6
|
+
License-Expression: Unlicense
|
|
7
|
+
Project-URL: Homepage, https://codeberg.org/dickschaedel/niot_helfer
|
|
8
|
+
Project-URL: Source, https://codeberg.org/dickschaedel/niot_helfer
|
|
9
|
+
Keywords: education,learning
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Topic :: Education
|
|
12
|
+
Classifier: Intended Audience :: Education
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
15
|
+
Classifier: Development Status :: 3 - Alpha
|
|
16
|
+
Requires-Python: >=3.9
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
File without changes
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
set -e
|
|
3
|
+
|
|
4
|
+
GREEN="\033[32m"
|
|
5
|
+
YELLOW="\033[33m"
|
|
6
|
+
RED="\033[31m"
|
|
7
|
+
RESET="\033[0m"
|
|
8
|
+
|
|
9
|
+
log() { printf "%b\n" "${GREEN}$1${RESET}"; }
|
|
10
|
+
warn() { printf "%b\n" "${YELLOW}$1${RESET}"; }
|
|
11
|
+
err() { printf "%b\n" "${RED}$1${RESET}" >&2; }
|
|
12
|
+
|
|
13
|
+
while read local_ref local_sha remote_ref remote_sha
|
|
14
|
+
do
|
|
15
|
+
case "$local_ref" in
|
|
16
|
+
refs/tags/v*)
|
|
17
|
+
# neuer Tag? (remote_sha = 40x0)
|
|
18
|
+
if [ "$remote_sha" != "0000000000000000000000000000000000000000" ]; then
|
|
19
|
+
warn "🔁 Tag existiert bereits auf dem Remote – überspringe"
|
|
20
|
+
continue
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
TAG_NAME="${local_ref#refs/tags/}"
|
|
24
|
+
|
|
25
|
+
log "🔖 Neuer Versionstag erkannt: ${TAG_NAME}"
|
|
26
|
+
log "🧹 Alte Build-Artefakte entfernen"
|
|
27
|
+
rm -rf dist build *.egg-info
|
|
28
|
+
|
|
29
|
+
log "📦 Baue Paket"
|
|
30
|
+
python -m pip install --upgrade build twine
|
|
31
|
+
python -m build
|
|
32
|
+
|
|
33
|
+
log "🚀 Upload nach PyPI"
|
|
34
|
+
twine upload dist/*
|
|
35
|
+
|
|
36
|
+
log "✅ Release ${TAG_NAME} erfolgreich veröffentlicht"
|
|
37
|
+
;;
|
|
38
|
+
*)
|
|
39
|
+
# kein Tag → nichts tun
|
|
40
|
+
;;
|
|
41
|
+
esac
|
|
42
|
+
done
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
|
|
2
|
+
[build-system]
|
|
3
|
+
requires = [
|
|
4
|
+
"setuptools>=68",
|
|
5
|
+
"wheel",
|
|
6
|
+
"setuptools_scm[toml]>=8"
|
|
7
|
+
]
|
|
8
|
+
build-backend = "setuptools.build_meta"
|
|
9
|
+
|
|
10
|
+
[project]
|
|
11
|
+
name = "niot_helfer"
|
|
12
|
+
dynamic = ["version"]
|
|
13
|
+
description = "Ein Helfermodul für NIT-Unterricht mit IoT Projekten"
|
|
14
|
+
readme = "README.md"
|
|
15
|
+
requires-python = ">=3.9"
|
|
16
|
+
license = "Unlicense"
|
|
17
|
+
authors = [
|
|
18
|
+
{ name = "dickschaedel", email = "dickschaedel@example.org" }
|
|
19
|
+
]
|
|
20
|
+
keywords = [
|
|
21
|
+
"education",
|
|
22
|
+
"learning",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
classifiers = [
|
|
26
|
+
"Programming Language :: Python :: 3",
|
|
27
|
+
"Topic :: Education",
|
|
28
|
+
"Intended Audience :: Education",
|
|
29
|
+
"Intended Audience :: Developers",
|
|
30
|
+
"Intended Audience :: End Users/Desktop",
|
|
31
|
+
"Development Status :: 3 - Alpha",
|
|
32
|
+
]
|
|
33
|
+
dependencies = []
|
|
34
|
+
|
|
35
|
+
[project.urls]
|
|
36
|
+
Homepage = "https://codeberg.org/dickschaedel/niot_helfer"
|
|
37
|
+
Source = "https://codeberg.org/dickschaedel/niot_helfer"
|
|
38
|
+
|
|
39
|
+
[tool.setuptools_scm]
|
|
40
|
+
# akzeptiert Tags wie v1.2.3
|
|
41
|
+
tag_regex = "^v(?P<version>\\d+\\.\\d+\\.\\d+)$"
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from niot_helfer import *
|
|
2
|
+
import machine
|
|
3
|
+
|
|
4
|
+
def handle_default(client):
|
|
5
|
+
return wifi.get_file("examples/schalter.html")
|
|
6
|
+
|
|
7
|
+
def handle_schalter(client):
|
|
8
|
+
led.value(not led.value())
|
|
9
|
+
|
|
10
|
+
# MAIN
|
|
11
|
+
led = machine.Pin(LED_BUILTIN, machine.Pin.OUT)
|
|
12
|
+
|
|
13
|
+
wifi = NIoTHelper(verbose=True) # wifi/Webserver Vorbereitungen
|
|
14
|
+
wifi.register_handler_default(handle_default)
|
|
15
|
+
wifi.register_handler("/", handle_default)
|
|
16
|
+
wifi.register_handler("/schalter", handle_schalter)
|
|
17
|
+
wifi.start_ap() # eigens AP aufspannen
|
|
18
|
+
wifi.start_webserver() # Webserver starten
|
|
19
|
+
wifi.run() # Kümmert sich um alle Anfragen
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from niot_helfer import *
|
|
2
|
+
from machine import Pin
|
|
3
|
+
|
|
4
|
+
def handle_default(client):
|
|
5
|
+
return wifi.get_file("examples/schalter.html")
|
|
6
|
+
|
|
7
|
+
def handle_schalter(client):
|
|
8
|
+
led.value(not led.value())
|
|
9
|
+
|
|
10
|
+
# MAIN
|
|
11
|
+
led = Pin(LED_BUILTIN, Pin.OUT)
|
|
12
|
+
|
|
13
|
+
wifi = NIoTHelper(verbose=True) # wifi/Webserver Vorbereitungen
|
|
14
|
+
wifi.register_handler_default(handle_default)
|
|
15
|
+
wifi.register_handler("/", handle_default)
|
|
16
|
+
wifi.register_handler("/schalter", handle_schalter)
|
|
17
|
+
wifi.connect("ssid","pw") # mit WLAN verbinden
|
|
18
|
+
wifi.start_webserver() # Webserver starten
|
|
19
|
+
wifi.run() # Kümmert sich um alle Anfragen
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<html><head><style>.switch { position: relative; display: inline-block; width:60px;height: 34px;}.switch input { opacity: 0; width: 0; height: 0;}.slider {position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: ccc; -webkit-transition: .4s; transition:.4s;}.slider:before { position: absolute; content: ''; height: 26px; width: 26px; left: 4px; bottom: 4px; background-color: white; -webkit-transition: .4s; transition: .4s;}input:checked + .slider { background-color: 2196F3;}input:focus + .slider { box-shadow: 0 0 1px 2196F3;}input:checked + .slider:before { -webkit-transform: translateX(26px); -ms-transform: translateX(26px); transform: translateX(26px);}/* Rounded sliders */.slider.round { border-radius: 34px;}.slider.round:before { border-radius: 50%;}</style></head><body><h2>Umschalten</h2><label class='switch' > <input type='checkbox' id='switch'> <span class='slider round'></span></label></body><script>function click() {fetch('/schalter');return true;}document.getElementById('switch').addEventListener('change',click);</script></html>
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
<!DOCTYPE html><html lang='en'><head>
|
|
2
|
+
<meta http-equiv='Content-Type' content='text/html; charset=UTF-8'><meta name='viewport' content='width=device-width, initial-scale=1, user-scalable=no'>
|
|
3
|
+
<title>Einstellungen</title><script>
|
|
4
|
+
function waehleSSID(eintrag) {
|
|
5
|
+
document.getElementById('ssid').value = eintrag.innerText || eintrag.textContent;
|
|
6
|
+
document.getElementById('pw').focus();
|
|
7
|
+
}
|
|
8
|
+
function reqListener() {
|
|
9
|
+
var data = JSON.parse(this.responseText);
|
|
10
|
+
alert(data);
|
|
11
|
+
}
|
|
12
|
+
function reqError(err) {
|
|
13
|
+
document.getElementById('test').innerHTML = 'juhu';
|
|
14
|
+
}
|
|
15
|
+
function holeWLANs() {
|
|
16
|
+
var oReq = new XMLHttpRequest();
|
|
17
|
+
oReq.onload = reqListener;
|
|
18
|
+
oReq.onerror = reqError;
|
|
19
|
+
oReq.open('get', '/wlansearch', true);
|
|
20
|
+
oReq.send();
|
|
21
|
+
}
|
|
22
|
+
function testWifiSettings() {
|
|
23
|
+
fetch('/connection_test',{"method":"get"})
|
|
24
|
+
.then(response => {
|
|
25
|
+
if (!response.ok) {
|
|
26
|
+
throw new Error('Network response was not ok');
|
|
27
|
+
}
|
|
28
|
+
return response.json();
|
|
29
|
+
})
|
|
30
|
+
.catch(error => {
|
|
31
|
+
document.getElementById('msg').innerHTML = 'Fetch error';
|
|
32
|
+
console.error('Fetch error:', error);
|
|
33
|
+
});
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
async function speichernWLAN() {
|
|
37
|
+
|
|
38
|
+
const ssid = document.getElementById('ssid').value;
|
|
39
|
+
const pw = document.getElementById('pw').value;
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
|
|
43
|
+
const response = await fetch('/speichern', {
|
|
44
|
+
method: 'POST',
|
|
45
|
+
headers: {
|
|
46
|
+
'Content-Type': 'application/json'
|
|
47
|
+
},
|
|
48
|
+
body: JSON.stringify({
|
|
49
|
+
ssid: ssid,
|
|
50
|
+
pw: pw
|
|
51
|
+
})
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const text = await response.text();
|
|
55
|
+
|
|
56
|
+
document.getElementById('msg').innerHTML = text;
|
|
57
|
+
|
|
58
|
+
} catch(error) {
|
|
59
|
+
|
|
60
|
+
console.log(error);
|
|
61
|
+
alert("Fehler beim Speichern");
|
|
62
|
+
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
</script><style type='text/css'>.thinborder {border: 1px solid black;border-radius:.3rem}
|
|
68
|
+
button { border:0;border-radius:.3rem;background-color:#20a0f0;color:#ffffff;line-height:2.6rem;font-size:1.2rem;width:100%}
|
|
69
|
+
input:focus {background-color: yellow}
|
|
70
|
+
div,input {padding:5px;font-size:1em}
|
|
71
|
+
input {width:95%;margin-top:5px;margin-bottom:10px}
|
|
72
|
+
body {text-align:center;font-family:verdana}
|
|
73
|
+
.right {float:right;text-align:right}
|
|
74
|
+
.info {margin:10px;padding:10px;border-radius:10px;background-color:#a0e0ff}
|
|
75
|
+
.halbl{width:48%;float:left;padding:0pt}
|
|
76
|
+
.halbr{width:48%;float:right;padding:0pt}</style></head>
|
|
77
|
+
<body onload='holeWLANs();'>
|
|
78
|
+
<div style='text-align:left;display:inline-block;min-width:260px;'>
|
|
79
|
+
<div class='thinborder'>
|
|
80
|
+
<form onsubmit="return speichernWLAN()">
|
|
81
|
+
<div class='info'>Mit welchem WLAN soll sich der Mikrocontroller normalerweise verbinden?</div>
|
|
82
|
+
<label>WLAN-Name ('SSID'):</label><input id='ssid' name='ssid' length='32' autofocus placeholder='SSID eingeben oder unten auswählen'/>
|
|
83
|
+
<label>Passwort:</label><input id='pw' name='pw' length='64' type='password' placeholder='Passwort'/>
|
|
84
|
+
<div>
|
|
85
|
+
<button class='halbl' type='submit'>Speichern</button>
|
|
86
|
+
</form>
|
|
87
|
+
<div class='halbr'><button type='submit' onclick='return testWifiSettings()'>Testen</button></div>
|
|
88
|
+
</div>
|
|
89
|
+
<br style='clear:both'/>
|
|
90
|
+
<br/>
|
|
91
|
+
Verfügbare WLANs:
|
|
92
|
+
<div class='thinborder'>
|
|
93
|
+
<!--{wlans}-->
|
|
94
|
+
</div>
|
|
95
|
+
<br/>
|
|
96
|
+
|
|
97
|
+
<div class='info' id='test'>MAC-Adresse dieses Mikrocontrollers: $MAC$</div>
|
|
98
|
+
</div>
|
|
99
|
+
</body>
|
|
100
|
+
</html>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: niot_helfer
|
|
3
|
+
Version: 0.0.2
|
|
4
|
+
Summary: Ein Helfermodul für NIT-Unterricht mit IoT Projekten
|
|
5
|
+
Author-email: dickschaedel <dickschaedel@example.org>
|
|
6
|
+
License-Expression: Unlicense
|
|
7
|
+
Project-URL: Homepage, https://codeberg.org/dickschaedel/niot_helfer
|
|
8
|
+
Project-URL: Source, https://codeberg.org/dickschaedel/niot_helfer
|
|
9
|
+
Keywords: education,learning
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Topic :: Education
|
|
12
|
+
Classifier: Intended Audience :: Education
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
15
|
+
Classifier: Development Status :: 3 - Alpha
|
|
16
|
+
Requires-Python: >=3.9
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pre-push
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/index.html
|
|
5
|
+
src/niot_helfer.py
|
|
6
|
+
src/examples/example_schalter_per_wlan_als_ap.py
|
|
7
|
+
src/examples/example_schalter_per_wlan_als_client.py
|
|
8
|
+
src/examples/schalter.html
|
|
9
|
+
src/niot_helfer.egg-info/PKG-INFO
|
|
10
|
+
src/niot_helfer.egg-info/SOURCES.txt
|
|
11
|
+
src/niot_helfer.egg-info/dependency_links.txt
|
|
12
|
+
src/niot_helfer.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
import network
|
|
2
|
+
import urequests
|
|
3
|
+
import time
|
|
4
|
+
import socket
|
|
5
|
+
import os
|
|
6
|
+
import machine
|
|
7
|
+
import json
|
|
8
|
+
import gc
|
|
9
|
+
import sys
|
|
10
|
+
import uasyncio as asyncio
|
|
11
|
+
|
|
12
|
+
# Helper to detect uasyncio v3
|
|
13
|
+
IS_UASYNCIO_V3 = hasattr(asyncio, "__version__") and asyncio.__version__ >= (3,)
|
|
14
|
+
|
|
15
|
+
'''
|
|
16
|
+
NIoT Helfer - Version 0.1
|
|
17
|
+
==========================
|
|
18
|
+
# sendas main.py
|
|
19
|
+
# token ghsdufjsdtzxcvfgsHSRZTG23jFTDGSdjf
|
|
20
|
+
# ip 192.168.0.73
|
|
21
|
+
'''
|
|
22
|
+
# globals für den ESP8266
|
|
23
|
+
D4 = machine.Pin(2)
|
|
24
|
+
LED_BUILTIN = D4
|
|
25
|
+
LED_BUILDIN = D4
|
|
26
|
+
|
|
27
|
+
class DNSQuery:
|
|
28
|
+
def __init__(self, data):
|
|
29
|
+
self.data = data
|
|
30
|
+
self.domain = ''
|
|
31
|
+
tipo = (data[2] >> 3) & 15 # Opcode bits
|
|
32
|
+
if tipo == 0: # Standard query
|
|
33
|
+
ini = 12
|
|
34
|
+
lon = data[ini]
|
|
35
|
+
while lon != 0:
|
|
36
|
+
self.domain += data[ini + 1:ini + lon + 1].decode('utf-8') + '.'
|
|
37
|
+
ini += lon + 1
|
|
38
|
+
lon = data[ini]
|
|
39
|
+
print("DNSQuery domain:" + self.domain)
|
|
40
|
+
|
|
41
|
+
def response(self, ip):
|
|
42
|
+
print("DNSQuery response: {} ==> {}".format(self.domain, ip))
|
|
43
|
+
if self.domain:
|
|
44
|
+
packet = self.data[:2] + b'\x81\x80'
|
|
45
|
+
packet += self.data[4:6] + self.data[4:6] + b'\x00\x00\x00\x00'
|
|
46
|
+
packet += self.data[12:] # Original Domain Name Question
|
|
47
|
+
packet += b'\xC0\x0C' # Pointer to domain name
|
|
48
|
+
packet += b'\x00\x01\x00\x01\x00\x00\x00\x3C\x00\x04'
|
|
49
|
+
packet += bytes(map(int, ip.split('.')))
|
|
50
|
+
return packet
|
|
51
|
+
|
|
52
|
+
class NIoTHelper:
|
|
53
|
+
__URL_SERVER = "http://informatik.fsg-pfullingen.de"
|
|
54
|
+
__URL_SENSORAPI = "/iot/webapi.php"
|
|
55
|
+
_hostname = "LittleGreenPanda"
|
|
56
|
+
|
|
57
|
+
# Access point constants
|
|
58
|
+
__AP_SSID = 'NIoT' # max 32 characters
|
|
59
|
+
__AP_IP = '192.168.42.1'
|
|
60
|
+
__AP_SUBNET = '255.255.255.0'
|
|
61
|
+
|
|
62
|
+
def __init__(self, verbose=False):
|
|
63
|
+
self.client = None
|
|
64
|
+
self.server_socket = None
|
|
65
|
+
self.handlers = {}
|
|
66
|
+
self.register_handler("/", self.handle_root)
|
|
67
|
+
self.handler_default = self.handle_404
|
|
68
|
+
self.verbose = verbose
|
|
69
|
+
# get and store the event loop
|
|
70
|
+
self.tasks = []
|
|
71
|
+
self.webserver_infos = None
|
|
72
|
+
self.run_captive_portal = False
|
|
73
|
+
self.wifis = []
|
|
74
|
+
|
|
75
|
+
def register_handler(self, url, fun):
|
|
76
|
+
self.handlers[url] = fun
|
|
77
|
+
def register_handler_default(self, fun):
|
|
78
|
+
self.handler_default = fun
|
|
79
|
+
|
|
80
|
+
def logging(self, msg):
|
|
81
|
+
if self.verbose:
|
|
82
|
+
print(msg)
|
|
83
|
+
|
|
84
|
+
def getConfig(self):
|
|
85
|
+
return self.client.ifconfig()
|
|
86
|
+
|
|
87
|
+
def getCredentials(self):
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
|
|
91
|
+
with open("wifi.json", "r") as f:
|
|
92
|
+
daten = json.load(f)
|
|
93
|
+
|
|
94
|
+
ssid = daten["ssid"]
|
|
95
|
+
pw = daten["pw"]
|
|
96
|
+
|
|
97
|
+
print("Geladene WLAN-Daten:")
|
|
98
|
+
print("SSID:", ssid)
|
|
99
|
+
|
|
100
|
+
return ssid, pw
|
|
101
|
+
|
|
102
|
+
except Exception as e:
|
|
103
|
+
|
|
104
|
+
print("Keine gespeicherten WLAN-Daten gefunden")
|
|
105
|
+
print(e)
|
|
106
|
+
|
|
107
|
+
return None, None
|
|
108
|
+
|
|
109
|
+
def getDeviceID(self):
|
|
110
|
+
return "".join(["%02x" % b for b in self.client.config("mac")])
|
|
111
|
+
|
|
112
|
+
def isConnected(self):
|
|
113
|
+
return self.client and self.client.isconnected()
|
|
114
|
+
|
|
115
|
+
def connect(self, ssid=None, pw=None):
|
|
116
|
+
self.client = network.WLAN(network.WLAN.IF_STA)
|
|
117
|
+
network.hostname(self._hostname)
|
|
118
|
+
if not self.isConnected():
|
|
119
|
+
if not ssid or not pw:
|
|
120
|
+
ssid, pw = self.getCredentials()
|
|
121
|
+
self.logging(f'connecting to: {ssid} with pw: {pw[:max(1,len(pw)//5)]}...')
|
|
122
|
+
self.client.active(False)
|
|
123
|
+
time.sleep(1)
|
|
124
|
+
self.client.active(True)
|
|
125
|
+
time.sleep(0.5)
|
|
126
|
+
try:
|
|
127
|
+
self.client.connect(ssid,pw)
|
|
128
|
+
for num in range(20):
|
|
129
|
+
if self.isConnected():
|
|
130
|
+
break
|
|
131
|
+
else:
|
|
132
|
+
if self.verbose:
|
|
133
|
+
print(".", end="")
|
|
134
|
+
if num == 19:
|
|
135
|
+
self.logging(f'\nERROR connecting: timed out')
|
|
136
|
+
break
|
|
137
|
+
time.sleep(0.5)
|
|
138
|
+
print()
|
|
139
|
+
except Exception as e:
|
|
140
|
+
self.logging(f'Error connecting:\n {e}')
|
|
141
|
+
# FIXME: what about wrong credentials / timeout?
|
|
142
|
+
else:
|
|
143
|
+
self.logging(f'Wifi already connected. Nothing to do.')
|
|
144
|
+
self.logging(f'IP: {self.client.ipconfig("addr4")} MAC: {self.getDeviceID()}')
|
|
145
|
+
return self.client.isconnected()
|
|
146
|
+
|
|
147
|
+
def disconnect(self):
|
|
148
|
+
if self.client:
|
|
149
|
+
self.client.disconnect()
|
|
150
|
+
|
|
151
|
+
'''
|
|
152
|
+
The socket does not assure to successfully write all the data.
|
|
153
|
+
This this function repeats the sending process until
|
|
154
|
+
everything is transfered.
|
|
155
|
+
'''
|
|
156
|
+
def send_verified(self, conn, data):
|
|
157
|
+
total_sent = 0
|
|
158
|
+
length = len(data)
|
|
159
|
+
while total_sent < length:
|
|
160
|
+
sent = conn.send(data[total_sent:])
|
|
161
|
+
if sent == 0:
|
|
162
|
+
raise OSError("Socket connection broken")
|
|
163
|
+
total_sent += sent
|
|
164
|
+
|
|
165
|
+
def get_file(self, filename):
|
|
166
|
+
headers = ""
|
|
167
|
+
content = ""
|
|
168
|
+
try:
|
|
169
|
+
headers = "HTTP/1.0 200 OK\r\n"
|
|
170
|
+
headers += "Content-Type: text/html\r\n"
|
|
171
|
+
headers += "Connection: close\r\n\r\n"
|
|
172
|
+
|
|
173
|
+
with open(filename, "r") as f:
|
|
174
|
+
while True:
|
|
175
|
+
chunk = f.read()
|
|
176
|
+
if not chunk:
|
|
177
|
+
break
|
|
178
|
+
content += chunk
|
|
179
|
+
except OSError:
|
|
180
|
+
headers = "HTTP/1.0 404 Not Found\r\n\r\n"
|
|
181
|
+
print("file not found")
|
|
182
|
+
except Exception as e:
|
|
183
|
+
headers = "HTTP/1.0 500 Internal Server Error\r\n\r\n" + str(e)
|
|
184
|
+
return content, headers
|
|
185
|
+
|
|
186
|
+
def handle_root(self, client):
|
|
187
|
+
return "no handler for /", None
|
|
188
|
+
# self.send_verified(client, b"no handler for \\")
|
|
189
|
+
|
|
190
|
+
def handle_404(self, client):
|
|
191
|
+
return "404 - URL not handled", None
|
|
192
|
+
# self.send_verified(client, b"404 - URL not handled")
|
|
193
|
+
|
|
194
|
+
def start_webserver(self, port=80):
|
|
195
|
+
self.webserver_infos = {"port":port}
|
|
196
|
+
|
|
197
|
+
def stop_webserver(self):
|
|
198
|
+
if self.server_socket:
|
|
199
|
+
self.server_socket.close()
|
|
200
|
+
|
|
201
|
+
async def handle_http_connection(self, reader, writer):
|
|
202
|
+
# Get HTTP request line
|
|
203
|
+
data = await reader.readline()
|
|
204
|
+
request_line = data.decode()
|
|
205
|
+
if not request_line:
|
|
206
|
+
return
|
|
207
|
+
addr = writer.get_extra_info('peername')
|
|
208
|
+
method, url, version = request_line.split()
|
|
209
|
+
print(f"Received {method} from {addr} for {url}")
|
|
210
|
+
|
|
211
|
+
# Header lesen
|
|
212
|
+
headers_in = {}
|
|
213
|
+
content_length = 0
|
|
214
|
+
|
|
215
|
+
while True:
|
|
216
|
+
gc.collect()
|
|
217
|
+
line = await reader.readline()
|
|
218
|
+
|
|
219
|
+
if line == b'\r\n':
|
|
220
|
+
break
|
|
221
|
+
|
|
222
|
+
line = line.decode().strip()
|
|
223
|
+
|
|
224
|
+
if ":" in line:
|
|
225
|
+
key, value = line.split(":", 1)
|
|
226
|
+
headers_in[key.lower()] = value.strip()
|
|
227
|
+
|
|
228
|
+
# Content-Length holen
|
|
229
|
+
if "content-length" in headers_in:
|
|
230
|
+
content_length = int(headers_in["content-length"])
|
|
231
|
+
|
|
232
|
+
# POST-Daten lesen
|
|
233
|
+
payload = ""
|
|
234
|
+
|
|
235
|
+
if content_length > 0:
|
|
236
|
+
payload = await reader.read(content_length)
|
|
237
|
+
payload = payload.decode()
|
|
238
|
+
|
|
239
|
+
print("POST DATA:", payload)
|
|
240
|
+
|
|
241
|
+
# Handle the request
|
|
242
|
+
if len(request_line) > 0:
|
|
243
|
+
if "GET" == method or "POST" == method:
|
|
244
|
+
if url in self.handlers:
|
|
245
|
+
try:
|
|
246
|
+
if not callable(self.handlers[url]):
|
|
247
|
+
print("WARNING: a registered handler is not callable: ", url)
|
|
248
|
+
content, headers = self.handlers[url](payload)
|
|
249
|
+
except Exception as e:
|
|
250
|
+
print(url, e)
|
|
251
|
+
content, headers = "", None
|
|
252
|
+
else:
|
|
253
|
+
content, headers = self.handler_default(None)
|
|
254
|
+
if not headers:
|
|
255
|
+
headers = 'HTTP/1.0 200 OK\r\n\r\n'
|
|
256
|
+
writer.write(headers+content)
|
|
257
|
+
try:
|
|
258
|
+
await writer.drain()
|
|
259
|
+
except:
|
|
260
|
+
None
|
|
261
|
+
|
|
262
|
+
# Close the socket
|
|
263
|
+
await writer.aclose()
|
|
264
|
+
# print("client socket closed")
|
|
265
|
+
gc.collect()
|
|
266
|
+
|
|
267
|
+
def add_task(self, func):
|
|
268
|
+
if not callable(func):
|
|
269
|
+
print("WARNING: function for add_task not callable - no brackets please")
|
|
270
|
+
self.tasks.append(func)
|
|
271
|
+
|
|
272
|
+
def run(self):
|
|
273
|
+
asyncio.run(self._run())
|
|
274
|
+
|
|
275
|
+
async def _loop(self):
|
|
276
|
+
while True:
|
|
277
|
+
for t in self.tasks:
|
|
278
|
+
t()
|
|
279
|
+
await asyncio.sleep(0.1)
|
|
280
|
+
|
|
281
|
+
def wifi_table_html(self,networks):
|
|
282
|
+
html = "<table>"
|
|
283
|
+
html += "<tr><th>SSID</th><th>Signal [dB]</th></tr>"
|
|
284
|
+
|
|
285
|
+
for net in networks:
|
|
286
|
+
ssid = "<a href='#' onclick='waehleSSID(this)'>"+net["ssid"]+"</a>"
|
|
287
|
+
rssi = net["rssi"]
|
|
288
|
+
|
|
289
|
+
html += "<tr>"
|
|
290
|
+
html += "<td>{}</td>".format(ssid)
|
|
291
|
+
html += "<td>{}</td>".format(rssi)
|
|
292
|
+
html += "</tr>"
|
|
293
|
+
|
|
294
|
+
html += "</table>"
|
|
295
|
+
return html
|
|
296
|
+
|
|
297
|
+
async def scan_wifi(self):
|
|
298
|
+
wlan = network.WLAN(network.STA_IF)
|
|
299
|
+
wlan.active(True)
|
|
300
|
+
|
|
301
|
+
await asyncio.sleep(0) # Scheduler freigeben
|
|
302
|
+
|
|
303
|
+
nets = wlan.scan() # ⚠️ blockiert kurz
|
|
304
|
+
|
|
305
|
+
result = []
|
|
306
|
+
for ssid, bssid, channel, rssi, authmode, hidden in nets:
|
|
307
|
+
ssid_str = ssid.decode('utf-8', 'ignore')
|
|
308
|
+
mac = ':'.join('{:02x}'.format(b) for b in bssid)
|
|
309
|
+
|
|
310
|
+
result.append({
|
|
311
|
+
"ssid": ssid_str,
|
|
312
|
+
"bssid": mac,
|
|
313
|
+
"rssi": rssi,
|
|
314
|
+
"channel": channel,
|
|
315
|
+
"authmode": authmode,
|
|
316
|
+
"hidden": hidden
|
|
317
|
+
})
|
|
318
|
+
print("wifi scan done")
|
|
319
|
+
self.wifis = result
|
|
320
|
+
|
|
321
|
+
async def _run(self):
|
|
322
|
+
# create loop task for user code
|
|
323
|
+
asyncio.create_task(self._loop())
|
|
324
|
+
|
|
325
|
+
# create a wifi scan task
|
|
326
|
+
asyncio.create_task(self.scan_wifi())
|
|
327
|
+
|
|
328
|
+
# Create the server and add task to event loop
|
|
329
|
+
if self.webserver_infos:
|
|
330
|
+
print("start webserver..")
|
|
331
|
+
await asyncio.start_server(self.handle_http_connection, "0.0.0.0", self.webserver_infos["port"])
|
|
332
|
+
|
|
333
|
+
if self.run_captive_portal:
|
|
334
|
+
asyncio.create_task(self.start_dns_server())
|
|
335
|
+
await asyncio.Event().wait()
|
|
336
|
+
|
|
337
|
+
def start_ap_off(self):
|
|
338
|
+
asyncio.run(self._start_ap())
|
|
339
|
+
|
|
340
|
+
def _handle_exception(self, loop, context):
|
|
341
|
+
""" uasyncio v3 only: global exception handler """
|
|
342
|
+
print('Global exception handler')
|
|
343
|
+
sys.print_exception(context["exception"])
|
|
344
|
+
sys.exit()
|
|
345
|
+
|
|
346
|
+
def start_ap(self):
|
|
347
|
+
# get and prepare event loop
|
|
348
|
+
# loop = asyncio.get_event_loop()
|
|
349
|
+
# TODO: Warum den exception handler überschreiben?
|
|
350
|
+
# loop.set_exception_handler(self._handle_exception)
|
|
351
|
+
|
|
352
|
+
# start the wifi AP
|
|
353
|
+
""" setup the access point """
|
|
354
|
+
self.ap = network.WLAN(network.AP_IF)
|
|
355
|
+
self.ap.active(True)
|
|
356
|
+
self.ap.ifconfig((self.__AP_IP, self.__AP_SUBNET, self.__AP_IP, self.__AP_IP))
|
|
357
|
+
self.ap.config(essid=self.__AP_SSID, authmode=network.AUTH_OPEN)
|
|
358
|
+
print('Network config:', self.ap.ifconfig())
|
|
359
|
+
|
|
360
|
+
self.run_captive_portal = True
|
|
361
|
+
# Create the server and add task to event loop
|
|
362
|
+
# server = await asyncio.start_server(self.handle_http_connection, "0.0.0.0", 80)
|
|
363
|
+
# loop.create_task(server)
|
|
364
|
+
|
|
365
|
+
# Start the DNS server task
|
|
366
|
+
# loop.create_task(self.run_dns_server())
|
|
367
|
+
|
|
368
|
+
# Start looping forever
|
|
369
|
+
# print('Looping forever...')
|
|
370
|
+
# loop.run_forever()
|
|
371
|
+
wifi.register_handler("/wlansearch", handle_wlansearch)
|
|
372
|
+
|
|
373
|
+
def start_captive_portal(self):
|
|
374
|
+
self.run_captive_portal = True
|
|
375
|
+
self.start_webserver()
|
|
376
|
+
self.register_handler("/wlansearch", handle_wlansearch)
|
|
377
|
+
self.register_handler("/connection_test", handle_connection_test)
|
|
378
|
+
self.register_handler("/speichern", handle_speichern)
|
|
379
|
+
|
|
380
|
+
async def start_dns_server(self):
|
|
381
|
+
""" function to handle incoming dns requests """
|
|
382
|
+
udps = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
383
|
+
udps.setblocking(False)
|
|
384
|
+
udps.bind(('0.0.0.0', 53))
|
|
385
|
+
|
|
386
|
+
while True:
|
|
387
|
+
try:
|
|
388
|
+
gc.collect()
|
|
389
|
+
if IS_UASYNCIO_V3:
|
|
390
|
+
yield asyncio.core._io_queue.queue_read(udps)
|
|
391
|
+
else:
|
|
392
|
+
yield asyncio.IORead(udps)
|
|
393
|
+
data, addr = udps.recvfrom(128)
|
|
394
|
+
print("Incoming DNS request...")
|
|
395
|
+
|
|
396
|
+
DNS = DNSQuery(data)
|
|
397
|
+
udps.sendto(DNS.response(self.__AP_IP), addr)
|
|
398
|
+
|
|
399
|
+
print("Replying: {:s} -> {:s}".format(DNS.domain, self.__AP_IP))
|
|
400
|
+
print(gc.mem_free())
|
|
401
|
+
|
|
402
|
+
except Exception as e:
|
|
403
|
+
print("DNS server error:", e)
|
|
404
|
+
await asyncio.sleep_ms(3000)
|
|
405
|
+
udps.close()
|
|
406
|
+
|
|
407
|
+
def handle_wlansearch(x):
|
|
408
|
+
print("wlan_search")
|
|
409
|
+
asyncio.create_task(wifi.scan_wifi())
|
|
410
|
+
return wifi.handle_404(None)
|
|
411
|
+
|
|
412
|
+
def handle_speichern(data):
|
|
413
|
+
|
|
414
|
+
print("speichern")
|
|
415
|
+
print(data)
|
|
416
|
+
|
|
417
|
+
try:
|
|
418
|
+
daten = json.loads(data)
|
|
419
|
+
|
|
420
|
+
print("SSID:", daten["ssid"])
|
|
421
|
+
print("PW:", daten["pw"])
|
|
422
|
+
|
|
423
|
+
with open("wifi.json", "w") as f:
|
|
424
|
+
json.dump(daten, f)
|
|
425
|
+
|
|
426
|
+
return "WLAN gespeichert!", \
|
|
427
|
+
"HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\n\r\n"
|
|
428
|
+
|
|
429
|
+
except Exception as e:
|
|
430
|
+
|
|
431
|
+
print(e)
|
|
432
|
+
|
|
433
|
+
return "Fehler beim Speichern", \
|
|
434
|
+
"HTTP/1.0 500 ERROR\r\nContent-Type: text/plain\r\n\r\n"
|
|
435
|
+
return wifi.handle_404(None)
|
|
436
|
+
|
|
437
|
+
def handle_connection_test(x):
|
|
438
|
+
print("connection test")
|
|
439
|
+
wifi.connect()
|
|
440
|
+
print(wifi.isConnected())
|
|
441
|
+
return wifi.handle_404(None)
|
|
442
|
+
|
|
443
|
+
def handle_captiveportal(client):
|
|
444
|
+
content, headers = wifi.get_file("index.html")
|
|
445
|
+
mac = wifi.ap.config('mac')
|
|
446
|
+
mac_str = ':'.join('{:02x}'.format(b) for b in mac)
|
|
447
|
+
content = content.replace("$MAC$", mac_str)
|
|
448
|
+
content = content.replace("<!--{wlans}-->", wifi.wifi_table_html(wifi.wifis))
|
|
449
|
+
return content, headers
|
|
450
|
+
|
|
451
|
+
led = machine.Pin(LED_BUILTIN, machine.Pin.OUT)
|
|
452
|
+
|
|
453
|
+
def loop():
|
|
454
|
+
led.value(not led.value())
|
|
455
|
+
|
|
456
|
+
if __name__ == '__main__':
|
|
457
|
+
use_case = "ap"
|
|
458
|
+
if use_case == "client":
|
|
459
|
+
wifi = NIoTHelper(verbose=True)
|
|
460
|
+
wifi.connect()
|
|
461
|
+
if use_case == "ap":
|
|
462
|
+
wifi = NIoTHelper(verbose=True)
|
|
463
|
+
wifi.start_ap()
|
|
464
|
+
wifi.start_captive_portal()
|
|
465
|
+
wifi.register_handler_default(handle_captiveportal)
|
|
466
|
+
wifi.register_handler("/", handle_captiveportal)
|
|
467
|
+
try:
|
|
468
|
+
wifi.add_task(loop)
|
|
469
|
+
wifi.run()
|
|
470
|
+
except Exception as e:
|
|
471
|
+
print(f"ERROR: Exception occured: {e}")
|
|
472
|
+
wifi.stop_webserver()
|
|
473
|
+
wifi.disconnect()
|
|
474
|
+
|