Compare commits
4
Commits
ab110013c0
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9d468f0414 | ||
|
|
efc1114c86 | ||
|
|
15cad37d5e | ||
|
|
658adc9baf |
@@ -1,6 +1,6 @@
|
||||
from time import time, gmtime, mktime,sleep
|
||||
import network, machine, ntptime, socket, json, requests, neopixel, asyncio
|
||||
from microdot import Microdot
|
||||
from microdot import Microdot, send_file
|
||||
|
||||
app = Microdot()
|
||||
|
||||
@@ -10,122 +10,54 @@ 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 = """<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Setup Complete</title>
|
||||
<title>Initial Setup Complete</title>
|
||||
<style>
|
||||
body { font-family: sans-serif; padding: 16px; text-align: center; }
|
||||
h2 { color: #28a745; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h2>Configuration saved</h2>
|
||||
<p>Device will reboot and connect to Wi-Fi.</p>
|
||||
<p><small>You can close this window.</small></p>
|
||||
<h2>Configuration Saved</h2>
|
||||
<p>Reboot device to attempt Wi-Fi connection.</p>
|
||||
</body>
|
||||
</html>"""
|
||||
|
||||
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 = "<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
|
||||
return send_file('portal.html')
|
||||
|
||||
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"]
|
||||
|
||||
# Setup Neopixel
|
||||
p = machine.Pin(20) # connected on pin 20
|
||||
@@ -134,35 +66,16 @@ wled[0] = (0,0,0)
|
||||
wled[1] = (0,0,0)
|
||||
wled.write()
|
||||
|
||||
# Check if already configured and load config for Wifi, API Key etc.
|
||||
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()
|
||||
# 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
|
||||
|
||||
# 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
|
||||
while True:
|
||||
if (time() - last_ntp_sync) > 3600:
|
||||
reconnect_wifi()
|
||||
#reconnect_wifi()
|
||||
try:
|
||||
ntptime.settime()
|
||||
print("Time synchronized successfully")
|
||||
@@ -172,15 +85,16 @@ while(True):
|
||||
|
||||
# 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']
|
||||
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']
|
||||
|
||||
# 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))))
|
||||
# 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)
|
||||
@@ -197,7 +111,11 @@ while(True):
|
||||
if (eta - time()) < -distance_on: # Remove old trains
|
||||
stations[station]['eta'].pop(index)
|
||||
|
||||
# Set LED state tracker to 0, if no trains, it'll stay 0.
|
||||
# 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")
|
||||
@@ -205,11 +123,63 @@ while(True):
|
||||
wled[station] = (255,0,0)
|
||||
elif time_from_now < distance_half:
|
||||
wled[station] = (255,100,0)
|
||||
else:
|
||||
wled[station] = (0,0,0)
|
||||
|
||||
# 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 mode is "STA":
|
||||
asyncio.create_task(CTA_Stations(config))
|
||||
|
||||
await server
|
||||
|
||||
|
||||
# Check if already configured and load config for Wifi, API Key etc.
|
||||
try:
|
||||
with open("config.json", "r") as f:
|
||||
config = json.load(f)
|
||||
SSID = config["SSID"]
|
||||
PASS = config["PASS"]
|
||||
|
||||
wlan = network.WLAN(network.STA_IF)
|
||||
wlan.active(True)
|
||||
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...')
|
||||
sleep(1)
|
||||
if wlan.isconnected():
|
||||
print('Connected to Wi-Fi: ', SSID)
|
||||
print('Device IP Address:', wlan.ifconfig()[0])
|
||||
break
|
||||
except Exception as e:
|
||||
print('Connection error:', e)
|
||||
print('Retrying connection...')
|
||||
sleep(2)
|
||||
mode = "STA"
|
||||
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])
|
||||
mode = "AP"
|
||||
|
||||
asyncio.run(main())
|
||||
Reference in new issue
Block a user