From 658adc9baff92e5d5e5e86f7c3ad3dd3aca29bed Mon Sep 17 00:00:00 2001 From: iosullivan Date: Sun, 11 Oct 2026 18:30:26 +1000 Subject: [PATCH] Updates for asyncio logic example --- examples/src/micropython-webserver/main.py | 279 +++++++++------------ 1 file changed, 124 insertions(+), 155 deletions(-) diff --git a/examples/src/micropython-webserver/main.py b/examples/src/micropython-webserver/main.py index fd946e6..adc1c46 100755 --- a/examples/src/micropython-webserver/main.py +++ b/examples/src/micropython-webserver/main.py @@ -1,6 +1,6 @@ from time import time, gmtime, mktime,sleep -import network, machine, ntptime, socket, json, requests,neopixel, asyncio -from microdot import Microdot +import network, machine, ntptime, socket, json, requests, neopixel, asyncio +from microdot import Microdot, send_file app = Microdot() @@ -10,122 +10,47 @@ def time_from_iso(datetime_str): time_u = mktime((yyyy, mm, dd, h, m, s, 0, 0)) return time_u -def start_ap(): - # Sets up initial captive portal for configuring wifi and basic options - ap = network.WLAN(network.AP_IF) - ap.active(True) +@app.post('/save') +async def save(request): + # Handle form submission + response = request.form - ap.config( - essid="CTA-Setup", - authmode=network.AUTH_OPEN - ) - - print("AP active for initial setup: ", ap.active()) - print("AP IP address:", ap.ifconfig()[0]) - - return ap - -def parse_form_data(body): - """Parse URL-encoded form data""" - data = {} - pairs = body.split("&") - for pair in pairs: - if "=" in pair: - key, value = pair.split("=", 1) - # URL decode - data[key] = value.replace("+", " ").replace("%40", "@") - return data - -def save_config(data): """Save Wi-Fi credentials to config.json""" - if not data.get("SSID"): + if not response["SSID"]: raise ValueError("SSID is required") config = { - "SSID": data.get("SSID"), - "PASS": data.get("PASS", ""), - "APIKEY": data.get("APIKEY", ""), - "LStation": data.get("LStation", ""), - "RStation": data.get("RStation", "") + "SSID": response["SSID"], + "PASS": response["PASS"], + "APIKEY": response["APIKEY"], + "LStation": response["LStation"], + "RStation": response["RStation"] } with open("config.json", "w") as f: json.dump(config, f) - - print("Configuration saved:", config) - -@app.post('/save') -async def save(request): - # Handle form submission - body = request.split("\r\n\r\n", 1)[1] - form_data = parse_form_data(body) - save_config(form_data) response_body = """ -Setup Complete +Initial Setup Complete -

Configuration saved

-

Device will reboot and connect to Wi-Fi.

-

You can close this window.

+

Configuration Saved

+

Reboot device to attempt Wi-Fi connection.

""" - return response_body + return response_body, {'Content-Type': 'text/html'} @app.get('/') async def index(request): - try: - with open("portal.html") as f: - response_body = f.read() - except: - response_body = "

Setup Page Not Found

Please upload portal.html

" - - response = ( - "HTTP/1.1 200 OK\r\n" - "Content-Type: text/html\r\n" - "Connection: close\r\n\r\n" - + response_body - ) - - return response - - -def reconnect_wifi(): - while not wlan.isconnected(): - try: - wlan.connect(SSID, PASS) - max_wait = 10 - while max_wait > 0: - if wlan.isconnected(): - break - max_wait -= 1 - print('Waiting for connection...') - asyncio.wait(1) - if wlan.isconnected(): - print('Connected to Wi-Fi') - print('Network config:', wlan.ifconfig()) - break - except Exception as e: - print('Connection error:', e) - print('Retrying connection...') - asyncio.wait(2) - -# Synchronize time of RTC with NTP -last_ntp_sync = -3700 # Init as more than an hour ago -last_api_query = mktime([2000,1,1,0,0,0,0,0]) # Set last query default (will run on first run) -query_period = 120 # Query the API at most every 2 minutes -distance_on = 10 # Time from station before turning on LED -distance_half = 30 # Time from station before going 'half' on - -stations = {} + return send_file('portal.html') # Setup Neopixel p = machine.Pin(20) # connected on pin 20 @@ -138,78 +63,122 @@ wled.write() try: with open("config.json", "r") as f: config = json.load(f) - APIKEY = config["APIKEY"] SSID = config["SSID"] PASS = config["PASS"] - # Allocate first and second station in demo only - stations[0] = { 'id':config["LStation"], 'eta':[] } - stations[1] = { 'id':config["RStation"], 'eta':[] } + wlan = network.WLAN(network.STA_IF) wlan.active(True) - reconnect_wifi() - - # need to make below multithreaded.... - #start_http_server() # Still launch server to allow changing SSID and config - demo = False -except: - # Failed to open file, launch AP mode - start_ap() - - # Halt waiting for config - while(True): - asyncio.wait(5) - -while(True): - # Synchronise NTP time if hasn't been done in 1 hour - if (time() - last_ntp_sync) > 3600: - reconnect_wifi() + while not wlan.isconnected(): try: - ntptime.settime() - print("Time synchronized successfully") - last_ntp_sync = time() + wlan.connect(SSID, PASS) + max_wait = 10 + while max_wait > 0: + if wlan.isconnected(): + break + max_wait -= 1 + print('Waiting for connection...') + sleep(1) + if wlan.isconnected(): + print('Connected to Wi-Fi: ', SSID) + print('Device IP Address:', wlan.ifconfig()[0]) + break except Exception as e: - print("Error synchronizing time:", e) - - # Have we queried API in the query_period seconds? - if (time() - last_api_query) > query_period: - # Query updated API - #trains = (requests.get("https://lapi.transitchicago.com/api/1.0/ttpositions.aspx?key="+APIKEY+"&rt=Blue&outputType=JSON")).json()['ctatt'] - # Query all stations - for station,dict in stations.items(): - trains_s = (requests.get(f"https://lapi.transitchicago.com/api/1.0/ttarrivals.aspx?key={APIKEY}&mapid={dict['id']}&outputType=JSON")).json()['ctatt'] - - # Timestamps are served in local Chicago time, convert to GMT for simplicity on this side. - #print(f"Time of poll is: "+str(gmtime(time_from_iso(trains_s['tmst'])+(5*60*60)))) - - # Loop through all trains and update matching stations - for train in trains_s['eta']: - eta_time = time_from_iso(train['arrT'])+(5*60*60) - - # Append the ETA, we rely on the station loop to remove old trains - stations[station]['eta'].append(eta_time) - - - last_api_query = time() # uptime polling window + print('Connection error:', e) + print('Retrying connection...') + sleep(2) +except: + # Failed to open config file, launch AP mode and disable trains + demo = True + wlan = network.WLAN(network.AP_IF) + wlan.active(True) + + wlan.config( + essid="CTA-Setup", + authmode=network.AUTH_OPEN + ) + + print("AP active for initial setup: ", wlan.active()) + print("AP IP address:", wlan.ifconfig()[0]) + + +async def CTA_Stations(config): + # Allocate first and second station + stations = {} + stations[0] = { 'id':config["LStation"], 'eta':[] } + stations[1] = { 'id':config["RStation"], 'eta':[] } + APIKEY = config["APIKEY"] - # Process the ETA for each station - for station, value in stations.items(): # loop through all stations - for index, eta in enumerate(value['eta']): # multiple trains. Find old trains - if (eta - time()) < -distance_on: # Remove old trains - stations[station]['eta'].pop(index) + # Synchronize time of RTC with NTP + last_ntp_sync = -3700 # Init as more than an hour ago + last_api_query = mktime([2000,1,1,0,0,0,0,0]) # Set last query default (will run on first run) + query_period = 120 # Query the API at most every 2 minutes + distance_on = 10 # Time from station before turning on LED + distance_half = 30 # Time from station before going 'half' on + + while True: + if (time() - last_ntp_sync) > 3600: + #reconnect_wifi() + try: + ntptime.settime() + print("Time synchronized successfully") + last_ntp_sync = time() + except Exception as e: + print("Error synchronizing time:", e) + + # Have we queried API in the query_period seconds? + if (time() - last_api_query) > query_period: + # Query all stations + for station,dict in stations.items(): + request = f"https://lapi.transitchicago.com/api/1.0/ttarrivals.aspx?key={APIKEY}&mapid={dict['id']}&outputType=JSON" + trains_s = (requests.get(request)).json()['ctatt'] + + # Zeroize all existing ETAs + stations[station]['eta'] = [] + + # Check if any trains, otherwise skip + if 'eta' in trains_s: + # Loop through all trains and update matching stations + for train in trains_s['eta']: + eta_time = time_from_iso(train['arrT'])+(5*60*60) + + # Append the ETA, we rely on the station loop to remove old trains + stations[station]['eta'].append(eta_time) - # Set LED state tracker to 0, if no trains, it'll stay 0. - eta = min(value['eta']) - time_from_now = eta - time() - print(f"Next train will arrive at {value['id']} in {str(time_from_now)} seconds") - if time_from_now < distance_on: - wled[station] = (255,0,0) - elif time_from_now < distance_half: - wled[station] = (255,100,0) - else: + + last_api_query = time() # uptime polling window + + # Process the ETA for each station + for station, value in stations.items(): # loop through all stations + for index, eta in enumerate(value['eta']): # multiple trains. Find old trains + if (eta - time()) < -distance_on: # Remove old trains + stations[station]['eta'].pop(index) + + # Set default wled[station] = (0,0,0) + + if len(value['eta']) > 0: + # Get closest train + eta = min(value['eta']) + time_from_now = eta - time() + print(f"Next train will arrive at {value['id']} in {str(time_from_now)} seconds") + if time_from_now < distance_on: + wled[station] = (255,0,0) + elif time_from_now < distance_half: + wled[station] = (255,100,0) - # Once all LEDs updated - wled.write() + # Once all LEDs updated + wled.write() - asyncio.wait(1) + await asyncio.sleep(1) +async def main(): + server = asyncio.create_task(app.start_server(port=80)) + + # Only run if we have internet + if wlan.IF_STA: + asyncio.create_task(CTA_Stations(config)) + + await server + + +asyncio.run(main()) \ No newline at end of file