# mPy - Toggle Leds on button press on FSK0001 control using WebSockets
#       measuring availability with test pin
# Copyright by Anthony Vogelaar, Perth Scotland
# revision 0.10  2025-11-29  initial release

from	machine				import	Pin, freq;		freq(240_000_000)
import	asyncio
import	time, json
from	wifi_ap				import	startWifiAP;	startWifiAP()
from	microdot			import	Microdot, send_file
from	microdot.websocket	import	with_websocket

# sse: https://itsfuad.medium.com/understanding-server-sent-events-sse-with-node-js-3e881c533081

# ====================== domain =======================

pins_led = (25, 26, 27, 14)
pins_btn = (33, 32, 35, 34)

leds = [Pin(pin, Pin.OUT, value=0)          for pin in pins_led]
btns = {Pin(pin, Pin.IN, Pin.PULL_UP): True for pin in pins_btn}

# return leds status as list
def getLeds():
    return [led() for led in leds]


# toggle led as in d {'data':'btn0'}
def toggleLed(d):
    btn_name = d.get('data')
    if len(btn_name) == 4:
        idx = int(btn_name[3])
        leds[idx].toggle()


# call continuously, check buttons
def domainExe(S={'t_last':time.ticks_ms()}):
    now = time.ticks_ms()
    if time.ticks_diff(now, S['t_last']) > 100:
        S['t_last'] = now
        for i, (k, v) in enumerate(btns.items()):		# iterate through buttons @ 100 mSec
            level = k()									# current status
            if level != v:								# if status changed
                btns[k] = level							# save current status
                if not level:							# if btn down
                    leds[i].toggle()
                    return True							# led(s) modified
    
# ==================== webserver ========================

app 	   = Microdot()
websockets = set()

# serve home page
@app.route('/')
async def index(request):
    return send_file('static/index.html')


# serve other static files
@app.route('static/<path:path>')
async def static(request, path):
    if '..' in path:
        return 'Not found', 404		# directory traversal not allowed
    return send_file(f'static/{path}')


# handle websockets and incoming data
@app.route('/ws')
@with_websocket
async def onWs(request, ws):
    websockets.add(ws)			# add to active websockets
    try:
        while True:
            obj = json.loads(await ws.receive())
            toggleLed(obj)
            await wsBroadcast(getLeds())
    except: print('webServer.onWs: exception')
    websockets.remove(ws)		# remove from active websockets


# send obj to all connected websockets
async def wsBroadcast(obj):
    for ws in websockets:
        await ws.send(json.dumps(obj))

# ======================== main =========================

# Test on Esp32 Wroom dual core
async def main():
    asyncio.create_task(app.start_server(debug=True))	# start web server
    pin_test = Pin(12, Pin.OUT)
    while True:
        pin_test(1); pin_test(0)		# typ=1.4mS, long=1.8mS max=8mS | incoming ws=20 mSec
        if domainExe():                 
            await wsBroadcast(getLeds())				# send to all connected websockets
        await asyncio.sleep(0)
        
        
asyncio.run(main())

# ========================= end =========================
