Updates for asyncio logic example

This commit is contained in:
iosullivan committed 2026-10-11 18:30:26 +10:00
1 parent ab110013c0
commit 658adc9baf
1 file changed
+120 -151
+120 -151
View File
@@ -1,6 +1,6 @@
from time import time, gmtime, mktime,sleep from time import time, gmtime, mktime,sleep
import network, machine, ntptime, socket, json, requests,neopixel, asyncio import network, machine, ntptime, socket, json, requests, neopixel, asyncio
from microdot import Microdot from microdot import Microdot, send_file
app = Microdot() app = Microdot()
@@ -10,122 +10,47 @@ def time_from_iso(datetime_str):
time_u = mktime((yyyy, mm, dd, h, m, s, 0, 0)) time_u = mktime((yyyy, mm, dd, h, m, s, 0, 0))
return time_u return time_u
def start_ap(): @app.post('/save')
# Sets up initial captive portal for configuring wifi and basic options async def save(request):
ap = network.WLAN(network.AP_IF) # Handle form submission
ap.active(True) 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""" """Save Wi-Fi credentials to config.json"""
if not data.get("SSID"): if not response["SSID"]:
raise ValueError("SSID is required") raise ValueError("SSID is required")
config = { config = {
"SSID": data.get("SSID"), "SSID": response["SSID"],
"PASS": data.get("PASS", ""), "PASS": response["PASS"],
"APIKEY": data.get("APIKEY", ""), "APIKEY": response["APIKEY"],
"LStation": data.get("LStation", ""), "LStation": response["LStation"],
"RStation": data.get("RStation", "") "RStation": response["RStation"]
} }
with open("config.json", "w") as f: with open("config.json", "w") as f:
json.dump(config, 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 = """<!DOCTYPE html> response_body = """<!DOCTYPE html>
<html> <html>
<head> <head>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>Setup Complete</title> <title>Initial Setup Complete</title>
<style> <style>
body { font-family: sans-serif; padding: 16px; text-align: center; } body { font-family: sans-serif; padding: 16px; text-align: center; }
h2 { color: #28a745; } h2 { color: #28a745; }
</style> </style>
</head> </head>
<body> <body>
<h2>Configuration saved</h2> <h2>Configuration Saved</h2>
<p>Device will reboot and connect to Wi-Fi.</p> <p>Reboot device to attempt Wi-Fi connection.</p>
<p><small>You can close this window.</small></p>
</body> </body>
</html>""" </html>"""
return response_body return response_body, {'Content-Type': 'text/html'}
@app.get('/') @app.get('/')
async def index(request): async def index(request):
try: return send_file('portal.html')
with open("portal.html") as f:
response_body = f.read()
except:
response_body = "<h1>Setup Page Not Found</h1><p>Please upload portal.html</p>"
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 = {}
# Setup Neopixel # Setup Neopixel
p = machine.Pin(20) # connected on pin 20 p = machine.Pin(20) # connected on pin 20
@@ -138,78 +63,122 @@ wled.write()
try: try:
with open("config.json", "r") as f: with open("config.json", "r") as f:
config = json.load(f) config = json.load(f)
APIKEY = config["APIKEY"]
SSID = config["SSID"] SSID = config["SSID"]
PASS = config["PASS"] 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 = network.WLAN(network.STA_IF)
wlan.active(True) wlan.active(True)
reconnect_wifi() while not wlan.isconnected():
# 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()
try: try:
ntptime.settime() wlan.connect(SSID, PASS)
print("Time synchronized successfully") max_wait = 10
last_ntp_sync = time() 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: except Exception as e:
print("Error synchronizing time:", e) 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)
# Have we queried API in the query_period seconds? wlan.config(
if (time() - last_api_query) > query_period: essid="CTA-Setup",
# Query updated API authmode=network.AUTH_OPEN
#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("AP active for initial setup: ", wlan.active())
#print(f"Time of poll is: "+str(gmtime(time_from_iso(trains_s['tmst'])+(5*60*60)))) print("AP IP address:", wlan.ifconfig()[0])
# 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 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 # Synchronize time of RTC with NTP
for station, value in stations.items(): # loop through all stations last_ntp_sync = -3700 # Init as more than an hour ago
for index, eta in enumerate(value['eta']): # multiple trains. Find old trains last_api_query = mktime([2000,1,1,0,0,0,0,0]) # Set last query default (will run on first run)
if (eta - time()) < -distance_on: # Remove old trains query_period = 120 # Query the API at most every 2 minutes
stations[station]['eta'].pop(index) distance_on = 10 # Time from station before turning on LED
distance_half = 30 # Time from station before going 'half' on
# Set LED state tracker to 0, if no trains, it'll stay 0. while True:
eta = min(value['eta']) if (time() - last_ntp_sync) > 3600:
time_from_now = eta - time() #reconnect_wifi()
print(f"Next train will arrive at {value['id']} in {str(time_from_now)} seconds") try:
if time_from_now < distance_on: ntptime.settime()
wled[station] = (255,0,0) print("Time synchronized successfully")
elif time_from_now < distance_half: last_ntp_sync = time()
wled[station] = (255,100,0) except Exception as e:
else: 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)
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) wled[station] = (0,0,0)
# Once all LEDs updated if len(value['eta']) > 0:
wled.write() # 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)
asyncio.wait(1) # Once all LEDs updated
wled.write()
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())