6 Commits

Author SHA1 Message Date
0aa3803f20 Add espnow 2025-04-21 21:52:20 +12:00
ed3351a20b Have single json endpoint 2025-04-21 21:51:55 +12:00
c8bcb85062 Add mac. Remove wifi settings 2025-04-21 21:51:15 +12:00
17d33d98e2 Send to a single json endpoint 2025-04-21 21:50:27 +12:00
afaf4c02e4 Change and and defaults
Add set_settings
2025-04-21 21:49:51 +12:00
e1e472b2e4 Remove wifi connect 2025-04-21 21:48:36 +12:00
6 changed files with 265 additions and 303 deletions

View File

@@ -1,19 +1,8 @@
import settings
import wifi import wifi
import time
from settings import Settings from settings import Settings
print(wifi.ap('qwerty')) s = Settings()
name = s.get('name', 'led')
settings = Settings() wifi.ap(name, '')
ssid = settings.get('wifi', {}).get('ssid', None)
password = settings.get('wifi', {}).get('password', None)
ip = settings.get('wifi', {}).get('ip', None)
gateway = settings.get('wifi', {}).get('gateway', None)
for i in range(10):
config = wifi.connect(ssid, password, ip, gateway)
if config:
print(config)
break
time.sleep(0.1)

View File

@@ -1,11 +1,11 @@
import asyncio import asyncio
from settings import Settings import aioespnow
from settings import Settings, set_settings
from web import web from web import web
from patterns import Patterns from patterns import Patterns
import gc import gc
import utime import utime
import machine import machine
import ntptime
import time import time
import wifi import wifi
@@ -16,7 +16,7 @@ async def main():
settings = Settings() settings = Settings()
patterns = Patterns(4, settings["num_leds"], selected=settings["selected_pattern"]) patterns = Patterns(4, settings["num_leds"], selected=settings["pattern"])
patterns.set_color1(tuple(int(settings["color1"][i:i+2], 16) for i in (1, 5, 3))) patterns.set_color1(tuple(int(settings["color1"][i:i+2], 16) for i in (1, 5, 3)))
patterns.set_color2(tuple(int(settings["color2"][i:i+2], 16) for i in (1, 5, 3))) patterns.set_color2(tuple(int(settings["color2"][i:i+2], 16) for i in (1, 5, 3)))
patterns.set_brightness(int(settings["brightness"])) patterns.set_brightness(int(settings["brightness"]))
@@ -27,29 +27,34 @@ async def main():
# start the server in a bacakground task # start the server in a bacakground task
print("Starting") print("Starting")
server = asyncio.create_task(w.start_server(host="0.0.0.0", port=80)) server = asyncio.create_task(w.start_server(host="0.0.0.0", port=80))
wdt = machine.WDT(timeout=10000) #wdt = machine.WDT(timeout=10000)
wdt.feed() #wdt.feed()
async def tick(): async def tick():
while True: while True:
patterns.tick() patterns.tick()
await asyncio.sleep_ms(1) await asyncio.sleep_ms(1)
asyncio.create_task(tick()) async def espnow():
e = aioespnow.AIOESPNow() # Returns AIOESPNow enhanced with async support
e.active(True)
async for mac, msg in e:
print(msg)
set_settings(msg, settings, patterns)
first = True asyncio.create_task(tick())
asyncio.create_task(espnow())
while True: while True:
#print(time.localtime()) #print(time.localtime())
# gc.collect() # gc.collect()
for i in range(60): for i in range(60):
wdt.feed() #wdt.feed()
await asyncio.sleep_ms(500) await asyncio.sleep_ms(500)
# cleanup before ending the application # cleanup before ending the application
await server await server
asyncio.run(main()) asyncio.run(main())

View File

@@ -1,4 +1,7 @@
import json import json
import wifi
import ubinascii
import machine
class Settings(dict): class Settings(dict):
SETTINGS_FILE = "/settings.json" SETTINGS_FILE = "/settings.json"
@@ -9,12 +12,13 @@ class Settings(dict):
def set_defaults(self): def set_defaults(self):
self["num_leds"] = 50 self["num_leds"] = 50
self["selected_pattern"] = "blink" self["pattern"] = "on"
self["color1"] = "#000f00" self["color1"] = "#00ff00"
self["color2"] = "#0f0000" self["color2"] = "#ff0000"
self["delay"] = 100 self["delay"] = 100
self["brightness"] = 100 self["brightness"] = 10
self["wifi"] = {"ssid": "", "password": ""} self["name"] = f"led-{ubinascii.hexlify(wifi.get_mac()).decode()}"
self["ap_password"] = ""
def save(self): def save(self):
try: try:
@@ -48,6 +52,43 @@ def main():
print(f"Loaded number of LEDs: {new_settings['num_leds']}") print(f"Loaded number of LEDs: {new_settings['num_leds']}")
print(settings) print(settings)
def set_settings(raw_json, settings, patterns):
try:
data = json.loads(raw_json)
print(data)
for key, value in data.items():
print(key, value)
if key == "color1":
patterns.set_color1(tuple(int(value[i:i+2], 16) for i in (1, 3, 5))) # Convert hex to RGB
elif key == "color2":
patterns.set_color2(tuple(int(value[i:i+2], 16) for i in (1, 3, 5))) # Convert hex to RGB
elif key == "num_leds":
patterns.update_num_leds(4, value)
elif key == "pattern":
if not patterns.select(value):
return "Pattern doesn't exist", 400
elif key == "delay":
delay = int(data["delay"])
patterns.set_delay(delay)
elif key == "brightness":
brightness = int(data["brightness"])
patterns.set_brightness(brightness)
elif key == "name":
settings[key] = value
settings.save()
machine.reset()
elif key == "sync":
patterns.sync()
return "OK", 200
else:
return "Invalid key", 400
settings[key] = value
settings.save()
return "OK", 200
except (KeyError, ValueError):
return "Bad request", 400
# Run the example # Run the example
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

@@ -9,15 +9,15 @@ async function post(path, data) {
const response = await fetch(path, { const response = await fetch(path, {
method: "POST", method: "POST",
headers: { headers: {
'Content-Type': 'application/json' "Content-Type": "application/json",
}, },
body: JSON.stringify(data) // Convert data to JSON string body: JSON.stringify(data), // Convert data to JSON string
}); });
if (!response.ok) { if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`); throw new Error(`HTTP error! Status: ${response.status}`);
} }
} catch (error) { } catch (error) {
console.error('Error during POST request:', error); console.error("Error during POST request:", error);
} }
} }
@@ -29,82 +29,72 @@ async function get(path) {
} }
return await response.json(); // Assuming you are expecting JSON response return await response.json(); // Assuming you are expecting JSON response
} catch (error) { } catch (error) {
console.error('Error during GET request:', error); console.error("Error during GET request:", error);
} }
} }
async function updateColor(event) { async function updateColor(event) {
event.preventDefault(); event.preventDefault();
clearTimeout(colorTimeout); clearTimeout(colorTimeout);
colorTimeout = setTimeout(async function() { colorTimeout = setTimeout(async function () {
const color = document.getElementById('color').value; const color = document.getElementById("color").value;
await post("/color", { color }); // Send as JSON await post("settings", { color1: color }); // Send as JSON
}, 500); }, 500);
} }
async function updateColor2(event) { async function updateColor2(event) {
event.preventDefault(); event.preventDefault();
clearTimeout(color2Timeout); clearTimeout(color2Timeout);
color2Timeout = setTimeout(async function() { color2Timeout = setTimeout(async function () {
const color = document.getElementById('color2').value; const color = document.getElementById("color2").value;
await post("/color2", { color }); // Send as JSON await post("/settings", { color2: color }); // Send as JSON
}, 500); }, 500);
} }
async function updatePattern(pattern) { async function updatePattern(pattern) {
event.preventDefault(); await post("/settings", { pattern: pattern }); // Send as JSON
await post("/pattern", { pattern }); // Send as JSON
} }
async function updateBrightness(event) { async function updateBrightness(event) {
event.preventDefault(); event.preventDefault();
clearTimeout(brightnessTimeout); clearTimeout(brightnessTimeout);
brightnessTimeout = setTimeout(async function() { brightnessTimeout = setTimeout(async function () {
const brightness = document.getElementById('brightness').value; const brightness = document.getElementById("brightness").value;
await post('/brightness', { brightness }); // Send as JSON await post("/settings", { brightness: brightness }); // Send as JSON
}, 500); }, 500);
} }
async function updateDelay(event) { async function updateDelay(event) {
event.preventDefault(); event.preventDefault();
clearTimeout(delayTimeout); clearTimeout(delayTimeout);
delayTimeout = setTimeout(async function() { delayTimeout = setTimeout(async function () {
const delay = document.getElementById('delay').value; const delay = document.getElementById("delay").value;
await post('/delay', { delay }); // Send as JSON await post("/settings", { delay: delay }); // Send as JSON
}, 500); }, 500);
} }
async function updateNumLeds(event) { async function updateNumLeds(event) {
event.preventDefault(); event.preventDefault();
const numLeds = document.getElementById('num_leds').value; const numLeds = document.getElementById("num_leds").value;
await post('/num_leds', { num_leds: numLeds }); // Send as JSON await post("/settings", { num_leds: parseInt(numLeds) }); // Send as JSON
} }
async function updateWifi(event) { async function updateName(event) {
event.preventDefault(); event.preventDefault();
const ssid = document.getElementById('ssid').value; const name = document.getElementById("name").value;
const password = document.getElementById('password').value; await post("/settings", { name: name }); // Send as JSON
const ip = document.getElementById('ip').value;
const gateway = document.getElementById('gateway').value;
const wifiSettings = { ssid, password, ip, gateway }; // Create JSON object
console.log(wifiSettings);
const response = await post('/wifi_settings', wifiSettings); // Send as JSON
if (response === 500) {
alert("Failed to connect to Wi-Fi");
}
} }
function createPatternButtons(patterns) { function createPatternButtons(patterns) {
const container = document.getElementById('pattern_buttons'); const container = document.getElementById("pattern_buttons");
container.innerHTML = ''; // Clear previous buttons container.innerHTML = ""; // Clear previous buttons
patterns.forEach(pattern => { patterns.forEach((pattern) => {
const button = document.createElement('button'); const button = document.createElement("button");
button.type = 'button'; // Use 'button' instead of 'submit' button.type = "button"; // Use 'button' instead of 'submit'
button.textContent = pattern; button.textContent = pattern;
button.value = pattern; button.value = pattern;
button.addEventListener('click', async function(event) { button.addEventListener("click", async function (event) {
event.preventDefault(); event.preventDefault();
await updatePattern(pattern); await updatePattern(pattern);
}); });
@@ -112,19 +102,25 @@ function createPatternButtons(patterns) {
}); });
} }
document.addEventListener('DOMContentLoaded', async function() { document.addEventListener("DOMContentLoaded", async function () {
document.getElementById('color').addEventListener('input', updateColor); document.getElementById("color").addEventListener("input", updateColor);
document.getElementById('color2').addEventListener('input', updateColor2); document.getElementById("color2").addEventListener("input", updateColor2);
document.getElementById('delay').addEventListener('input', updateDelay); document.getElementById("delay").addEventListener("input", updateDelay);
document.getElementById('brightness').addEventListener('input', updateBrightness); document
document.getElementById('num_leds_form').addEventListener('submit', updateNumLeds); .getElementById("brightness")
document.getElementById('wifi_form').addEventListener('submit', updateWifi); .addEventListener("input", updateBrightness);
document.getElementById('delay').addEventListener('touchend', updateDelay); document
document.getElementById('brightness').addEventListener('touchend', updateBrightness); .getElementById("num_leds_form")
.addEventListener("submit", updateNumLeds);
document.getElementById("name_form").addEventListener("submit", updateName);
document.getElementById("delay").addEventListener("touchend", updateDelay);
document
.getElementById("brightness")
.addEventListener("touchend", updateBrightness);
document.querySelectorAll(".pattern_button").forEach(button => { document.querySelectorAll(".pattern_button").forEach((button) => {
console.log(button.value); console.log(button.value);
button.addEventListener('click', async event => { button.addEventListener("click", async (event) => {
event.preventDefault(); event.preventDefault();
await updatePattern(button.value); await updatePattern(button.value);
}); });
@@ -133,15 +129,15 @@ document.addEventListener('DOMContentLoaded', async function() {
// Function to toggle the display of the settings menu // Function to toggle the display of the settings menu
function selectSettings() { function selectSettings() {
const settingsMenu = document.getElementById('settings_menu'); const settingsMenu = document.getElementById("settings_menu");
controls = document.getElementById('controls'); controls = document.getElementById("controls");
settingsMenu.style.display = 'block'; settingsMenu.style.display = "block";
controls.style.display = 'none'; controls.style.display = "none";
} }
function selectControls() { function selectControls() {
const settingsMenu = document.getElementById('settings_menu'); const settingsMenu = document.getElementById("settings_menu");
controls = document.getElementById('controls'); controls = document.getElementById("controls");
settingsMenu.style.display = 'none'; settingsMenu.style.display = "none";
controls.style.display = 'block'; controls.style.display = "block";
} }

View File

@@ -1,14 +1,14 @@
{% args settings, patterns %} {% args settings, patterns, mac %}
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>LED Control</title> <title>LED Control</title>
<script src="static/main.js"></script> <script src="static/main.js"></script>
<link rel="stylesheet" href="static/main.css"> <link rel="stylesheet" href="static/main.css" />
</head> </head>
<body> <body>
<h1>Control LEDs</h1> <h1>Control LEDs</h1>
<button onclick="selectControls()">Controls</button> <button onclick="selectControls()">Controls</button>
<button onclick="selectSettings()">Settings</button> <button onclick="selectSettings()">Settings</button>
@@ -24,48 +24,73 @@
</div> </div>
<form id="delay_form" method="post" action="/delay"> <form id="delay_form" method="post" action="/delay">
<label for="delay">Delay:</label> <label for="delay">Delay:</label>
<input type="range" id="delay" name="delay" min="1" max="1000" value="{{settings['delay']}}" step="10"> <input
type="range"
id="delay"
name="delay"
min="1"
max="1000"
value="{{settings['delay']}}"
step="10"
/>
</form> </form>
<form id="brightness_form" method="post" action="/brightness"> <form id="brightness_form" method="post" action="/brightness">
<label for="brightness">Brightness:</label> <label for="brightness">Brightness:</label>
<input type="range" id="brightness" name="brightness" min="0" max="100" value="{{settings['brightness']}}" step="1"> <input
type="range"
id="brightness"
name="brightness"
min="0"
max="100"
value="{{settings['brightness']}}"
step="1"
/>
</form> </form>
<form id="color_form" method="post" action="/color"> <form id="color_form" method="post" action="/color">
<input type="color" id="color" name="color" value="{{settings['color1']}}"> <input
type="color"
id="color"
name="color"
value="{{settings['color1']}}"
/>
</form> </form>
<form id="color2_form" method="post" action="/color2"> <form id="color2_form" method="post" action="/color2">
<input type="color" id="color2" name="color2" value="{{settings['color2']}}"> <input
type="color"
id="color2"
name="color2"
value="{{settings['color2']}}"
/>
</form> </form>
</div> </div>
<!-- Settings Menu for num_leds, Wi-Fi SSID, and Password --> <!-- Settings Menu for num_leds, Wi-Fi SSID, and Password -->
<div id="settings_menu" style="display: none;"> <div id="settings_menu" style="display: none">
<h2>Settings</h2> <h2>Settings</h2>
<form id="name_form" method="post" action="/name">
<label for="name">Name:</label>
<input
type="text"
id="name"
name="num_leds"
value="{{settings['name']}}"
/>
<input type="submit" value="Update Name" />
</form>
<!-- Separate form for submitting num_leds --> <!-- Separate form for submitting num_leds -->
<form id="num_leds_form" method="post" action="/num_leds"> <form id="num_leds_form" method="post" action="/num_leds">
<label for="num_leds">Number of LEDs:</label> <label for="num_leds">Number of LEDs:</label>
<input type="text" id="num_leds" name="num_leds" value="{{settings['num_leds']}}"> <input
<input type="submit" value="Update Number of LEDs"> type="text"
</form> id="num_leds"
name="num_leds"
<!-- Form for Wi-Fi SSID and password --> value="{{settings['num_leds']}}"
<form id="wifi_form" method="post" action="/wifi_settings"> />
<label for="ssid">Wi-Fi SSID:</label> <input type="submit" value="Update Number of LEDs" />
<input type="text" id="ssid" name="ssid" value="{{settings['wifi']['ssid']}}">
<br>
<label for="password">Wi-Fi Password:</label>
<input type="password" id="password" name="password">
<br>
<label for="ip">Wi-Fi IP:</label>
<input type="ip" id="ip" name="ip" value="{{settings.get('wifi', {}).get('ip', '')}}">
<br>
<label for="gateway">Wi-Fi Gateway:</label>
<input type="gateway" id="gateway" name="gateway" value="{{settings.get('wifi', {}).get('gateway', '')}}">
<br>
<input type="submit" value="Save Wi-Fi Settings">
</form> </form>
<p>Mac address: {{mac}}</p>
</div> </div>
</body> </body>
</html> </html>

View File

@@ -1,125 +1,31 @@
from microdot import Microdot, send_file, Response from microdot import Microdot, send_file, Response
from microdot.utemplate import Template from microdot.utemplate import Template
from microdot.websocket import with_websocket from microdot.websocket import with_websocket
import machine
import json from settings import set_settings
import wifi import wifi
import json
def web(settings, patterns): def web(settings, patterns):
app = Microdot() app = Microdot()
Response.default_content_type = 'text/html' Response.default_content_type = 'text/html'
@app.route('/') @app.route('/')
async def index(request): async def index_hnadler(request):
return Template('/index.html').render(settings=settings, patterns=patterns.patterns.keys()) mac = wifi.get_mac().hex()
return Template('/index.html').render(settings=settings, patterns=patterns.patterns.keys(), mac=mac)
@app.route("/static/<path:path>") @app.route("/static/<path:path>")
def static(request, path): def static_handler(request, path):
if '..' in path: if '..' in path:
# Directory traversal is not allowed # Directory traversal is not allowed
return 'Not found', 404 return 'Not found', 404
return send_file('static/' + path) return send_file('static/' + path)
@app.post("/num_leds") @app.post("/settings")
def num_leds(request): def settings_handler(request):
try: return set_settings(request.body.decode('utf-8'), settings, patterns)
data = json.loads(request.body.decode('utf-8'))
num_leds = int(data["num_leds"])
patterns.update_num_leds(4, num_leds)
settings["num_leds"] = num_leds
settings.save()
return "OK", 200
except (ValueError, KeyError, json.JSONDecodeError):
return "Bad request", 400
@app.post("/pattern")
def pattern(request):
try:
data = json.loads(request.body.decode('utf-8'))
pattern = data["pattern"]
if patterns.select(pattern):
settings["selected_pattern"] = pattern
settings.save()
return "OK", 200
else:
return "Bad request", 400
except (KeyError, json.JSONDecodeError):
return "Bad request", 400
@app.post("/delay")
def delay(request):
try:
data = json.loads(request.body.decode('utf-8'))
delay = int(data["delay"])
patterns.set_delay(delay)
settings["delay"] = delay
settings.save()
return "OK", 200
except (ValueError, KeyError, json.JSONDecodeError):
return "Bad request", 400
@app.post("/brightness")
def brightness(request):
try:
data = json.loads(request.body.decode('utf-8'))
brightness = int(data["brightness"])
patterns.set_brightness(brightness)
settings["brightness"] = brightness
settings.save()
return "OK", 200
except (ValueError, KeyError, json.JSONDecodeError):
return "Bad request", 400
@app.post("/color")
def color(request):
try:
data = json.loads(request.body.decode('utf-8'))
color = data["color"]
patterns.set_color1(tuple(int(color[i:i+2], 16) for i in (1, 3, 5))) # Convert hex to RGB
settings["color1"] = color
settings.save()
return "OK", 200
except (KeyError, json.JSONDecodeError, ValueError):
return "Bad request", 400
@app.post("/color2")
def color2(request):
try:
data = json.loads(request.body.decode('utf-8'))
color = data["color2"]
patterns.set_color2(tuple(int(color[i:i+2], 16) for i in (1, 3, 5))) # Convert hex to RGB
settings["color2"] = color
settings.save()
return "OK", 200
except (KeyError, json.JSONDecodeError, ValueError):
return "Bad request", 400
@app.post("/wifi_settings")
def wifi_settings(request):
try:
data = json.loads(request.body.decode('utf-8'))
print(data)
ssid = settings['wifi']['ssid'] = data['ssid']
password = settings['wifi']['password'] = data.get('password', settings['wifi']['password'])
ip = settings['wifi']['ip'] = data.get('ip', None)
gateway = settings['wifi']['gateway'] = data.get('gateway', None)
print(settings)
if config := wifi.connect(ssid, password, ip, gateway):
print(config)
settings.save()
return "OK", 200
except Exception as e:
print(f"Wifi {e}")
return "Bad request", 400
@app.post("/sync")
def sync(request):
patterns.sync()
return "OK", 200
@app.route("/external") @app.route("/external")
@with_websocket @with_websocket