# main.py — ESP32 + MicroPython + Microdot (current) + WebSocket GUI
# Slider -> PWM on GPIO14
# 4 buttons -> GPIO16,17,18,19 (momentary pulse)
# 4 switches -> GPIO33,25,26,27 (stable ON/OFF)
# The project was created with assistance of ChatGPT: https://chatgpt.com/share/68a7113c-5d8c-800c-b20e-c204f29aac84
# The microdot sources are taken from https://github.com/miguelgrinberg/microdot
# They are licensed acccording to https://github.com/miguelgrinberg/microdot/blob/main/LICENSE (MIT License Copyright (c) 2019 Miguel Grinberg)
import network
import time
import sys
import uasyncio as asyncio
import ujson
from machine import Pin, PWM
import microdot
from microdot import Microdot,Response
# Work around Wokwi flat FS limitations
sys.modules["microdot.microdot"] = microdot
import microdot_helpers
sys.modules["microdot.helpers"] = microdot_helpers
import microdot_websocket
sys.modules["microdot.websocket"] = microdot_websocket
from microdot.websocket import with_websocket
# ===================== Wi-Fi =====================
WIFI_SSID = "Wokwi-GUEST"
WIFI_PASS = ""
def connect_wifi():
wlan = network.WLAN(network.STA_IF)
if not wlan.active():
wlan.active(True)
if not wlan.isconnected():
print("Connecting to Wi-Fi:", WIFI_SSID)
wlan.connect(WIFI_SSID, WIFI_PASS)
t0 = time.ticks_ms()
while not wlan.isconnected():
if time.ticks_diff(time.ticks_ms(), t0) > 15000:
raise RuntimeError("Wi-Fi connection timed out")
time.sleep_ms(200)
ip = wlan.ifconfig()[0]
print("Wi-Fi connected, IP:", ip)
return ip
INDEX_HTML = open("html.txt").read()
# ===================== App & State =====================
app = Microdot()
Response.default_content_type = "text/html"
STATE = {
"slider": 50, # 0..100
"switches": [False, False, False, False], # switch states
}
CLIENTS = set()
# ===================== Hardware setup =====================
# Slider -> PWM on GPIO14
pwm = PWM(Pin(14), freq=1000, duty=0)
# Switches -> GPIO33,25,26,27
switch_pins = [Pin(p, Pin.OUT) for p in (33, 25, 26, 27)]
# Buttons -> GPIO16,17,18,19
button_pins = [Pin(p, Pin.OUT) for p in (16, 17, 18, 19)]
# ===================== Hardware hooks =====================
def on_slider(value: int):
# Map 0..100 -> 0..1023 (PWM duty for ESP32 MicroPython)
duty = int(value * 1023 // 100)
pwm.duty(duty)
print(f"[HW] Slider -> {value} (duty {duty})")
def on_button(index: int):
# Momentary pulse: drive pin HIGH briefly
pin = button_pins[index]
pin.value(1)
print(f"[HW] Button {index+1} pulse HIGH")
# schedule low after 100ms
async def release():
await asyncio.sleep_ms(100)
pin.value(0)
print(f"[HW] Button {index+1} released")
asyncio.create_task(release())
def on_switch(index: int, on: bool):
pin = switch_pins[index]
pin.value(1 if on else 0)
print(f"[HW] Switch {index+1} -> {'ON' if on else 'OFF'}")
# ===================== Broadcast helper =====================
async def broadcast_state():
if not CLIENTS:
return
msg = ujson.dumps({"type": "state", "slider": STATE["slider"], "switches": STATE["switches"]})
dead = []
for ws in CLIENTS:
try:
await ws.send(msg)
except Exception:
dead.append(ws)
for ws in dead:
try:
CLIENTS.remove(ws)
except Exception:
pass
# ===================== Web UI =====================
# (same HTML/JS as previous answer; omitted here for brevity)
# >>> keep the INDEX_HTML string from the previous version <<<
# ===================== Routes =====================
@app.route("/")
def index(request):
return INDEX_HTML
@app.route("/ws")
@with_websocket
async def ws_route(request, ws):
CLIENTS.add(ws)
try:
await ws.send(ujson.dumps({"type": "state", "slider": STATE["slider"], "switches": STATE["switches"]}))
while True:
msg = await ws.receive()
if msg is None:
break
try:
data = ujson.loads(msg)
except Exception:
continue
t = data.get("type")
if t == "slider":
try:
val = int(data.get("value", STATE["slider"]))
val = max(0, min(100, val))
STATE["slider"] = val
on_slider(val)
await broadcast_state()
except Exception:
pass
elif t == "button":
try:
idx = int(data.get("index", -1))
if 0 <= idx < 4:
on_button(idx)
except Exception:
pass
elif t == "switch":
try:
idx = int(data.get("index", -1))
on = bool(data.get("on", False))
if 0 <= idx < 4:
STATE["switches"][idx] = on
on_switch(idx, on)
await broadcast_state()
except Exception:
pass
finally:
try:
CLIENTS.remove(ws)
except Exception:
pass
# ===================== Main =====================
async def main():
ip = connect_wifi()
print("Open -> http://%s/" % ip)
await app.start_server(host="0.0.0.0", port=80)
while True:
await asyncio.sleep(3600)
try:
asyncio.run(main())
except (KeyboardInterrupt, Exception) as e:
print("Stopped:", e)