Compare commits

...
Author SHA1 Message Date
Isaac OSullivan 07f5be6fe7 Updates for WLED 2026-10-07 19:26:50 +10:00
iosullivan bc488d444f Updates for web server 2026-10-05 19:07:50 +10:00
iosullivan 37b7001099 Updates for web server 2026-10-05 18:11:31 +10:00
iosullivan fdb214ec55 Updates for web server 2026-10-05 18:11:21 +10:00
iosullivan c4adc10a35 Updates for web server 2026-10-05 17:51:32 +10:00
iosullivan b63a139901 First commit 2026-10-05 17:09:48 +10:00
iosullivan 613970b146 Wokwi cleanup 2026-07-19 08:34:43 +08:00
iosullivan 2745a145ce Updates for multi-train logic 2026-07-19 08:30:19 +08:00
iosullivan e11aa92692 Updates for reliable NTP 2026-07-19 08:13:05 +08:00
iosullivan 536b076f39 Updates for reliable NTP 2026-07-19 08:09:36 +08:00
iosullivan 6c202faac5 Updates for multi-train logic 2026-07-19 07:43:17 +08:00
iosullivan d3b81d5793 Updates for micropython example 2026-07-18 23:59:20 +08:00
iosullivan c6360b0fb2 Updates 2026-07-18 18:57:32 +08:00
iosullivan ae5fa17cf0 Updates 2026-07-18 18:53:14 +08:00
iosullivan 0f0c451e14 Update README.md 2026-07-13 09:41:08 +10:00
iosullivan 65cd974ec9 Update README.md 2026-07-13 09:37:08 +10:00
iosullivan 91fbcb1c8f Update examples/src/main.cpp 2026-07-12 19:41:44 +10:00
iosullivan dfd8fbb976 Update README.md 2026-07-12 19:15:19 +10:00
iosullivan 26e166c8dc Update examples/src/main.cpp 2026-07-12 18:31:31 +10:00
iosullivan 2c8a4664a4 Update README.md 2026-07-12 17:36:53 +10:00
iosullivan 0bf2880d36 Update README.md 2026-07-12 17:07:59 +10:00
iosullivan 745445edeb Add examples/src/main.cpp 2026-07-12 16:44:37 +10:00
10 changed files with 1001 additions and 21 deletions

No files matched your search

+3
View File
@@ -0,0 +1,3 @@
.env
secrets.h
local_secrets.py
+18 -21
View File
@@ -1,7 +1,7 @@
# CTA for ESP32 # CTA for ESP32
## Notes ## Notes
### CTA API interface ### CTA API interface
The CTA API can be fairly trivially queries to get a JSON response with the required metadata. The CTA API can be fairly trivially queried to get a JSON response with the required metadata.
- Docs are available here: https://www.transitchicago.com/developers/ttdocs/ - Docs are available here: https://www.transitchicago.com/developers/ttdocs/
- API consists of two main queries - API consists of two main queries
- 'Arrivals' - which lets you query all arrivals for a station available at https://lapi.transitchicago.com/api/1.0/ttarrivals.aspx - 'Arrivals' - which lets you query all arrivals for a station available at https://lapi.transitchicago.com/api/1.0/ttarrivals.aspx
@@ -13,7 +13,7 @@ The CTA API can be fairly trivially queries to get a JSON response with the requ
To query XML: https://lapi.transitchicago.com/api/1.0/ttarrivals.aspx?key=<KEYID>&mapid=<mapID> To query XML: https://lapi.transitchicago.com/api/1.0/ttarrivals.aspx?key=<KEYID>&mapid=<mapID>
To query JSON: https://lapi.transitchicago.com/api/1.0/ttarrivals.aspx?key=<KEYID>&mapid=40380&outputType=JSON To query JSON: https://lapi.transitchicago.com/api/1.0/ttarrivals.aspx?key=<KEYID>&mapid=40380&outputType=JSON
Simple HTTP POST queries use the '?' to define start of parameters, and each parameter is seperated by an '&' Simple HTTP queries use the '?' to define start of parameters, and each parameter is seperated by an '&'
In the above, we are querying the 'ttarrivals' endpoint, used for the Arrivals API In the above, we are querying the 'ttarrivals' endpoint, used for the Arrivals API
KEYID is your API key KEYID is your API key
MAPID is the ID of a station on the CTA, such as "40380". These station numbers are available on the ttdocs website. MAPID is the ID of a station on the CTA, such as "40380". These station numbers are available on the ttdocs website.
@@ -31,7 +31,7 @@ The CTA API can be fairly trivially queries to get a JSON response with the requ
- `output->ctatt->eta` gives us a list of folders for all the next upcoming trains. - `output->ctatt->eta` gives us a list of folders for all the next upcoming trains.
- `output->ctatt->eta->0` gives us the next train. - `output->ctatt->eta->0` gives us the next train.
- `output->ctatt->eta->1` gives us the second to next train. - `output->ctatt->eta->1` gives us the second to next train.
- `output->ctatt->eta->0-arrT` gives us the time the next train will arrive in an ISO date format, i.e. "2026-07-12T00:50:16" - `output->ctatt->eta->0->arrT` gives us the time the next train will arrive in an ISO date format, i.e. "2026-07-12T00:50:16"
- The problem with the Arrivals API, is you then need to query every station one by one, save the next arrival time of a train and then do something with the result (say, turn on an LED if it's less than 2 minutes away). - The problem with the Arrivals API, is you then need to query every station one by one, save the next arrival time of a train and then do something with the result (say, turn on an LED if it's less than 2 minutes away).
- That's a lot of queries to check for the entire network. Especially for a little ESP32. - That's a lot of queries to check for the entire network. Especially for a little ESP32.
- Very wasteful and abusive of the API for just your usage. - Very wasteful and abusive of the API for just your usage.
@@ -48,7 +48,7 @@ The CTA API can be fairly trivially queries to get a JSON response with the requ
![Example Return](imgs/screenshot2.jpg) ![Example Return](imgs/screenshot2.jpg)
- The Locations API install returns all trains along a route. - The Locations API instead returns all trains along a route.
- In the above example, we have an array of all trains, starting at the top of the tree, we can pick each like you would in a folder. Some pseudocode: - In the above example, we have an array of all trains, starting at the top of the tree, we can pick each like you would in a folder. Some pseudocode:
- Say return json is output.json which = https://lapi.transitchicago.com/api/1.0/ttpositions.aspx?key=<KEYID>&rt=Blue&outputType=JSON - Say return json is output.json which = https://lapi.transitchicago.com/api/1.0/ttpositions.aspx?key=<KEYID>&rt=Blue&outputType=JSON
- `output->tmst` gives us the time we made this query, this is handy to find the current time. - `output->tmst` gives us the time we made this query, this is handy to find the current time.
@@ -64,11 +64,11 @@ The CTA API can be fairly trivially queries to get a JSON response with the requ
So example pseudocode: So example pseudocode:
``` ```
# Create a simple 2D array of all stations (imagine table with two columns) and when the next train arrives. Let's set arrival time to 999 for now (i.e. not any time soon). # Create a simple 2D array of all stations (imagine table with two columns) and when the next train arrives. Let's set arrival time to 2199-01-01 for now (i.e. not any time soon).
# We can use the station number instead of the name to keep code simpler (don't have to deal with spaces or symbols) # We can use the station number instead of the name to keep code simpler (don't have to deal with spaces or symbols)
stations_eta = <array> stations_eta = <array>
stations_eta[40380] = 999 stations_eta[40380] = 2199-01-01
stations_eta[40390] = 999 stations_eta[40390] = 2199-01-01
.... etc .... .... etc ....
# Get route data in JSON format # Get route data in JSON format
@@ -85,30 +85,27 @@ for train in output->ctatt->route->0->train:
# 'train' will now refer to the first train in the folder, aka 0. # 'train' will now refer to the first train in the folder, aka 0.
# So we can save the next stop of the train # So we can save the next stop of the train
next_stop = train->nextStaId next_stop = train->nextStaId
# The time of the next stop # The time of the next stop
time_to_stop = train->arrT station_eta[next_stop] = train->arrT
# Get the time difference.
time_eta = time_to_stop - time_now
# In our made up language, this is going to be in seconds.
time_eta_mins = time_eta / 60
# Now we can update the station array from earlier
stations_eta[next_stop] = time_eta_mins
# The loop will now run again, but next time it will refer to the second train (aka 1) # The loop will now run again, but next time it will refer to the second train (aka 1)
# We now have an array of all stations and the next train to arrive at them. For the ones with none any time soon, they'll still be 999 minutes away. # We now have an array of all stations and the next train to arrive at them. For the ones with none any time soon, they'll still be 999 minutes away.
print(stations_eta[40380]) print(stations_eta[40380])
---> 6 ---> 2026-06-06 12:11:14
print(stations_eta[40390]) print(stations_eta[40390])
---> 11 ---> 2026-06-06 12:13:14
# This data can now be used to toggle LEDs based on the desired behavior.
``` ```
### ESP32 Implementation ### ESP32 Implementation
- Micropython appears to still be poorly documented for ESP32s (especially the aliexpress clone boards). - Micropython, Arduino IDE and PlatformIO are the three best options
- Recommendation is to use either the Arduino IDE (for quickly getting going) or using PlatformIO with VS Code. - Easier programming - go with MicroPython
- Simpler setup, use either the Arduino IDE (for quickly getting going) or using PlatformIO with VS Code.
- Both of these are just fancy wrappers around C++ language, so plenty of documentation to google. - Both of these are just fancy wrappers around C++ language, so plenty of documentation to google.
- The latter is harder to initially get going (mostly cause of VS Code), but it's a much nicer environment to code in than Arduino IDE. - The latter is harder to initially get going (mostly cause of VS Code weirdness), but it's a much nicer environment to code in than Arduino IDE.
- PlatformIO is completely compatable with Arduino code, so can always start in Arduino IDE and move from there. - PlatformIO is completely compatable with Arduino code, so can always start in Arduino IDE and move from there.
- Example PlatformIO code for the API is available in example/src/ - Example Arduino IDE code for the API is available in /example/src/
+243
View File
@@ -0,0 +1,243 @@
#include "secrets.h"
#include <Arduino.h>
#include <WiFi.h>
#include <ArduinoJson.h> // Include ArduinoJson library
#include <HTTPClient.h>
#include <time.h>
// Use HTTP instead of HTTPS for a bit more simplicity
String serverName = "http://lapi.transitchicago.com/api/1.0/ttpositions.aspx";
// Last run time for the loop, don't need to query too often, stored in milliseconds
unsigned long lastTime = 0;
// How often to query API server for updates (every 60 seconds)
unsigned long queryTime = 60*1000;
// How far away the train (in seconds) should be from the station to trigger
unsigned int time_to_led = 120;
// Current time (last API refresh time)
time_t last_api_time;
void setup() {
Serial.begin(115200);
WiFi.begin(SECRET_SSID, SECRET_PASS);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.print("Connecting to WiFi...");
}
Serial.println("Connected to WiFi!");
Serial.print("IP Address: ");
Serial.println(WiFi.localIP());
// Setup array of stations and set a starting ETA
// This is really horrible in C/C++
struct tm def_tm;
const char* def_dt = "2199-01-01T01:01:01";
strptime(def_dt, "%Y-%m-%dT%H:%M:%S", &def_tm);
time_t def_time = mktime(&def_tm);
std::unordered_map<int, time_t> stations;
stations.insert(std::make_pair(41090, def_time)); //Monroe (Red Line)
stations.insert(std::make_pair(40820, def_time)); //Rosemont (Blue Line)
stations.insert(std::make_pair(41010, def_time)); //Rockwell (Brown Line)
stations.insert(std::make_pair(40470, def_time)); //Racine (Blue Line)
stations.insert(std::make_pair(40190, def_time)); //Sox-35th (Red Line)
stations.insert(std::make_pair(40020, def_time)); //Harlem/Lake (Green Line)
stations.insert(std::make_pair(40900, def_time)); //Howard (Red, Purple & Yellow lines)
stations.insert(std::make_pair(41160, def_time)); //Clinton (Green & Pink lines)
stations.insert(std::make_pair(40230, def_time)); //Cumberland (Blue Line)
stations.insert(std::make_pair(40560, def_time)); //Jackson (Red Line)
stations.insert(std::make_pair(41150, def_time)); //Kedzie (Orange Line)
stations.insert(std::make_pair(40780, def_time)); //Central Park (Pink Line)
stations.insert(std::make_pair(41080, def_time)); //47th (Green Line)
stations.insert(std::make_pair(40510, def_time)); //Garfield (Green Line)
stations.insert(std::make_pair(41050, def_time)); //Linden (Purple Line)
stations.insert(std::make_pair(40120, def_time)); //35th/Archer (Orange Line)
stations.insert(std::make_pair(40570, def_time)); //California (Blue Line)
stations.insert(std::make_pair(40260, def_time)); //State/Lake (Brown, Green, Orange, Pink & Purple lines)
stations.insert(std::make_pair(40130, def_time)); //51st (Green Line)
stations.insert(std::make_pair(41700, def_time)); //Washington/Wabash (Brown, Green, Orange, Purple & Pink lines)
stations.insert(std::make_pair(41000, def_time)); //Cermak-Chinatown (Red Line)
stations.insert(std::make_pair(40340, def_time)); //Berwyn (Red Line)
stations.insert(std::make_pair(41690, def_time)); //Cermak-McCormick Place (Green Line)
stations.insert(std::make_pair(40490, def_time)); //Grand (Blue Line)
stations.insert(std::make_pair(41060, def_time)); //Ashland (Orange Line)
stations.insert(std::make_pair(40090, def_time)); //Damen (Brown Line)
stations.insert(std::make_pair(41280, def_time)); //Jefferson Park (Blue Line)
stations.insert(std::make_pair(40520, def_time)); //Foster (Purple Line)
stations.insert(std::make_pair(40330, def_time)); //Grand (Red Line)
stations.insert(std::make_pair(40280, def_time)); //Central (Green Line)
stations.insert(std::make_pair(40250, def_time)); //Kedzie-Homan (Blue Line)
stations.insert(std::make_pair(40870, def_time)); //Francisco (Brown Line)
stations.insert(std::make_pair(41240, def_time)); //Addison (Blue Line)
stations.insert(std::make_pair(40740, def_time)); //Western (Pink Line)
stations.insert(std::make_pair(41360, def_time)); //California (Green Line)
stations.insert(std::make_pair(41400, def_time)); //Roosevelt (Red, Orange & Green lines)
stations.insert(std::make_pair(41410, def_time)); //Chicago (Blue Line)
stations.insert(std::make_pair(40680, def_time)); //Adams/Wabash (Brown, Green, Orange, Pink & Purple lines)
stations.insert(std::make_pair(40920, def_time)); //Pulaski (Blue Line)
stations.insert(std::make_pair(41210, def_time)); //Wellington (Brown & Purple lines)
stations.insert(std::make_pair(41340, def_time)); //LaSalle (Blue Line)
stations.insert(std::make_pair(41490, def_time)); //Harrison (Red Line)
stations.insert(std::make_pair(41350, def_time)); //Oak Park (Green Line)
stations.insert(std::make_pair(40040, def_time)); //Quincy/Wells (Brown, Orange, Purple & Pink lines)
stations.insert(std::make_pair(40390, def_time)); //Forest Park (Blue Line)
stations.insert(std::make_pair(41130, def_time)); //Halsted (Orange Line)
stations.insert(std::make_pair(40960, def_time)); //Pulaski (Orange Line)
stations.insert(std::make_pair(40830, def_time)); //18th (Pink Line)
stations.insert(std::make_pair(40140, def_time)); //Dempster-Skokie  (Yellow Line)
stations.insert(std::make_pair(41320, def_time)); //Belmont (Red, Brown & Purple lines)
stations.insert(std::make_pair(41660, def_time)); //Lake (Red Line)
stations.insert(std::make_pair(40910, def_time)); //63rd (Red Line)
stations.insert(std::make_pair(40690, def_time)); //Dempster (Purple Line)
stations.insert(std::make_pair(40460, def_time)); //Merchandise Mart (Brown & Purple lines)
stations.insert(std::make_pair(40710, def_time)); //Chicago (Brown & Purple lines)
stations.insert(std::make_pair(40530, def_time)); //Diversey (Brown & Purple lines)
stations.insert(std::make_pair(40850, def_time)); //Harold Washington Library-State/Van Buren (Brown, Orange, Purple & Pink lines)
stations.insert(std::make_pair(41020, def_time)); //Logan Square (Blue Line)
stations.insert(std::make_pair(41030, def_time)); //Polk (Pink Line)
stations.insert(std::make_pair(41300, def_time)); //Loyola (Red Line)
stations.insert(std::make_pair(40310, def_time)); //Western (Orange Line)
stations.insert(std::make_pair(40430, def_time)); //Clinton (Blue Line)
stations.insert(std::make_pair(40320, def_time)); //Division (Blue Line)
stations.insert(std::make_pair(41510, def_time)); //Morgan (Green & Pink lines)
stations.insert(std::make_pair(40270, def_time)); //Main (Purple Line)
stations.insert(std::make_pair(41330, def_time)); //Montrose (Blue Line)
stations.insert(std::make_pair(40350, def_time)); //UIC-Halsted (Blue Line)
stations.insert(std::make_pair(40940, def_time)); //Halsted (Green Line)
stations.insert(std::make_pair(41480, def_time)); //Western (Brown Line)
stations.insert(std::make_pair(40030, def_time)); //Pulaski (Green Line)
stations.insert(std::make_pair(41190, def_time)); //Jarvis (Red Line)
stations.insert(std::make_pair(41170, def_time)); //Garfield (Red Line)
stations.insert(std::make_pair(40050, def_time)); //Davis (Purple Line)
stations.insert(std::make_pair(40750, def_time)); //Harlem (Blue Line - O'Hare Branch)
stations.insert(std::make_pair(41200, def_time)); //Argyle (Red Line)
stations.insert(std::make_pair(40440, def_time)); //California (Pink Line)
stations.insert(std::make_pair(40360, def_time)); //Southport (Brown Line)
stations.insert(std::make_pair(40760, def_time)); //Granville (Red Line)
stations.insert(std::make_pair(40480, def_time)); //Cicero (Green Line)
stations.insert(std::make_pair(40730, def_time)); //Washington/Wells (Brown, Orange, Purple & Pink lines)
stations.insert(std::make_pair(40580, def_time)); //54th/Cermak (Pink Line)
stations.insert(std::make_pair(40650, def_time)); //North/Clybourn (Red Line)
stations.insert(std::make_pair(40220, def_time)); //Western (Blue Line - Forest Park Branch)
stations.insert(std::make_pair(40070, def_time)); //Jackson (Blue Line)
stations.insert(std::make_pair(41440, def_time)); //Addison (Brown Line)
stations.insert(std::make_pair(41670, def_time)); //Conservatory (Green Line)
stations.insert(std::make_pair(40790, def_time)); //Monroe (Blue Line)
stations.insert(std::make_pair(40180, def_time)); //Oak Park (Blue Line)
stations.insert(std::make_pair(40400, def_time)); //Noyes (Purple Line)
stations.insert(std::make_pair(41260, def_time)); //Austin (Green Line)
stations.insert(std::make_pair(40600, def_time)); //Kostner (Pink Line)
stations.insert(std::make_pair(40670, def_time)); //Western (Blue Line - O'Hare Branch)
stations.insert(std::make_pair(40100, def_time)); //Morse (Red Line)
stations.insert(std::make_pair(40770, def_time)); //Lawrence (Red Line)
stations.insert(std::make_pair(40150, def_time)); //Pulaski (Pink Line)
stations.insert(std::make_pair(40660, def_time)); //Armitage (Brown & Purple lines)
stations.insert(std::make_pair(40540, def_time)); //Wilson (Red & Purple lines)
stations.insert(std::make_pair(40380, def_time)); //Clark/Lake (Blue, Brown, Green, Orange, Purple & Pink lines)
stations.insert(std::make_pair(40160, def_time)); //LaSalle/Van Buren (Brown, Orange, Purple & Pink lines)
stations.insert(std::make_pair(40700, def_time)); //Laramie (Green Line)
stations.insert(std::make_pair(40240, def_time)); //79th (Red Line)
stations.insert(std::make_pair(40290, def_time)); //Ashland/63rd (Green Line)
stations.insert(std::make_pair(40450, def_time)); //95th/Dan Ryan (Red Line)
stations.insert(std::make_pair(40210, def_time)); //Damen (Pink Line)
stations.insert(std::make_pair(41140, def_time)); //King Drive (Green Line)
stations.insert(std::make_pair(41710, def_time)); //Damen (Green Line)
stations.insert(std::make_pair(40980, def_time)); //Harlem (Blue Line - Forest Park Branch)
stations.insert(std::make_pair(41270, def_time)); //43rd (Green Line)
stations.insert(std::make_pair(40080, def_time)); //Sheridan (Red Line)
stations.insert(std::make_pair(40610, def_time)); //Ridgeland (Green Line)
stations.insert(std::make_pair(41070, def_time)); //Kedzie (Green Line)
stations.insert(std::make_pair(41430, def_time)); //87th (Red Line)
stations.insert(std::make_pair(41250, def_time)); //Central (Purple Line)
stations.insert(std::make_pair(40880, def_time)); //Thorndale (Red Line)
stations.insert(std::make_pair(41680, def_time)); //Oakton-Skokie (Yellow Line)
stations.insert(std::make_pair(41120, def_time)); //35th-Bronzeville-IIT (Green Line)
stations.insert(std::make_pair(41460, def_time)); //Irving Park (Brown Line)
stations.insert(std::make_pair(41220, def_time)); //Fullerton (Red, Brown & Purple lines)
stations.insert(std::make_pair(40990, def_time)); //69th (Red Line)
stations.insert(std::make_pair(41040, def_time)); //Kedzie (Pink Line)
stations.insert(std::make_pair(40800, def_time)); //Sedgwick (Brown & Purple lines)
stations.insert(std::make_pair(40060, def_time)); //Belmont (Blue Line)
stations.insert(std::make_pair(40840, def_time)); //South Boulevard (Purple Line)
stations.insert(std::make_pair(40170, def_time)); //Ashland (Green & Pink lines)
stations.insert(std::make_pair(40810, def_time)); //Illinois Medical District (Blue Line)
stations.insert(std::make_pair(41230, def_time)); //47th (Red Line)
stations.insert(std::make_pair(41500, def_time)); //Montrose (Brown Line)
stations.insert(std::make_pair(40930, def_time)); //Midway (Orange Line)
stations.insert(std::make_pair(40550, def_time)); //Irving Park (Blue Line)
stations.insert(std::make_pair(40370, def_time)); //Washington (Blue Line)
stations.insert(std::make_pair(40890, def_time)); //O'Hare (Blue Line)
stations.insert(std::make_pair(40630, def_time)); //Clark/Division (Red Line)
stations.insert(std::make_pair(41420, def_time)); //Addison (Red Line)
stations.insert(std::make_pair(41290, def_time)); //Kimball (Brown Line)
stations.insert(std::make_pair(40300, def_time)); //Indiana (Green Line)
stations.insert(std::make_pair(40420, def_time)); //Cicero (Pink Line)
stations.insert(std::make_pair(40720, def_time)); //Cottage Grove (Green Line)
stations.insert(std::make_pair(41380, def_time)); //Bryn Mawr (Red Line)
stations.insert(std::make_pair(41450, def_time)); //Chicago (Red Line)
stations.insert(std::make_pair(40590, def_time)); //Damen (Blue Line)
stations.insert(std::make_pair(41180, def_time)); //Kedzie (Brown Line)
stations.insert(std::make_pair(40010, def_time)); //Austin (Blue Line)
stations.insert(std::make_pair(41310, def_time)); //Paulina (Brown Line)
stations.insert(std::make_pair(40970, def_time)); //Cicero (Blue Line)
}
void loop() {
// It's been at least 'queryTime' seconds since we last checked the API. Update the arrival times.
if ((millis() - lastTime) > queryTime) {
// Query the HTTP endpoint with a POST request and parameters, only interested in Blue route for now
const char* route = "Blue";
// http object let's us query a webserver
HTTPClient http;
String serverPath = serverName + "?key=" + SECRET_APIKEY + "&rt=" + route + "&outputType=JSON";
http.useHTTP10(true);
http.begin(serverPath.c_str());
int httpResponse = http.GET();
// Minimum size acquired from pasting raw JSON into here: https://arduinojson.org/v5/assistant/
DynamicJsonDocument location_json(9128);
deserializeJson(location_json, http.getStream());
// Store time we queried server
const char* time_c = location_json["ctatt"]["tmst"];
struct tm time_tm;
strptime(time_c, "%Y-%m-%dT%H:%M:%S", &time_tm);
last_api_time = mktime(&time_tm);
const char* nextStaID = location_json["ctatt"]["route"][0]["train"][0]["nextStaID"];
Serial.println(nextStaID);
// Move through the array to find
for (JsonObject train : location_json["ctatt"]["route"][0]["train"].as<JsonArray>()) {
// Save station ID
unsigned int station = train["nextStaId"];
const char* arrTime = train["arrT"];
// Annoying multistep process to get ISO time into time_t format
struct tm eta_tm;
strptime(arrTime, "%Y-%m-%dT%H:%M:%S", &eta_tm);
// Update next ETA for this station
stations[station] = mktime(&eta_tm);
Serial.println(stations[station])
}
lastTime = millis();
}
/*
double difference = difftime(stations[station], last_api_time);
if(difference < time_to_led) {
String output = String("Train arriving soon at ") + stationName + " in " + difference + " seconds.";
Serial.println(output);
}
*/
delay(5000)
}
@@ -0,0 +1,3 @@
#define SECRET_SSID "WIFINAME"
#define SECRET_PASS "WIFIPASS"
#define SECRET_APIKEY "abcdef1234567890abcdef"
@@ -0,0 +1,146 @@
# Not currently used
stations = {}
stations[41090] = { 'name':' Monroe (Red Line)', 'eta':[], 'led_index':1, 'state':0 }
stations[40820] = { 'name':' Rosemont (Blue Line)', 'eta':[], 'led_index': machine.Pin(15, machine.Pin.OUT), 'state':0 }
stations[41010] = { 'name':' Rockwell (Brown Line)', 'eta':[], 'led_index':3, 'state':0 }
stations[40470] = { 'name':' Racine (Blue Line)', 'eta':[], 'led_index':4, 'state':0 }
stations[40190] = { 'name':' Sox-35th (Red Line)', 'eta':[], 'led_index':5, 'state':0 }
stations[40020] = { 'name':' Harlem/Lake (Green Line)', 'eta':[], 'led_index':6, 'state':0 }
stations[40900] = { 'name':' Howard (Red Purple & Yellow lines)', 'eta':[], 'led_index':7, 'state':0 }
stations[41160] = { 'name':' Clinton (Green & Pink lines)', 'eta':[], 'led_index':8, 'state':0 }
stations[40230] = { 'name':' Cumberland (Blue Line)', 'eta':[], 'led_index':9, 'state':0 }
stations[40560] = { 'name':' Jackson (Red Line)', 'eta':[], 'led_index':10, 'state':0 }
stations[41150] = { 'name':' Kedzie (Orange Line)', 'eta':[], 'led_index':11, 'state':0 }
stations[40780] = { 'name':' Central Park (Pink Line)', 'eta':[], 'led_index':12, 'state':0 }
stations[41080] = { 'name':' 47th (Green Line)', 'eta':[], 'led_index':13, 'state':0 }
stations[40510] = { 'name':' Garfield (Green Line)', 'eta':[], 'led_index':14, 'state':0 }
stations[41050] = { 'name':' Linden (Purple Line)', 'eta':[], 'led_index':2, 'state':0 }
stations[40120] = { 'name':' 35th/Archer (Orange Line)', 'eta':[], 'led_index':16, 'state':0 }
stations[40570] = { 'name':' California (Blue Line)', 'eta':[], 'led_index':17, 'state':0 }
stations[40260] = { 'name':' State/Lake (Brown Green Orange Pink & Purple lines)', 'eta':[], 'led_index':18, 'state':0 }
stations[40130] = { 'name':' 51st (Green Line)', 'eta':[], 'led_index':19, 'state':0 }
stations[41700] = { 'name':' Washington/Wabash (Brown Green Orange Purple & Pink lines)', 'eta':[], 'led_index':20, 'state':0 }
stations[41000] = { 'name':' Cermak-Chinatown (Red Line)', 'eta':[], 'led_index':21, 'state':0 }
stations[40340] = { 'name':' Berwyn (Red Line)', 'eta':[], 'led_index':22, 'state':0 }
stations[41690] = { 'name':' Cermak-McCormick Place (Green Line)', 'eta':[], 'led_index':23, 'state':0 }
stations[40490] = { 'name':' Grand (Blue Line)', 'eta':[], 'led_index':24, 'state':0 }
stations[41060] = { 'name':' Ashland (Orange Line)', 'eta':[], 'led_index':25, 'state':0 }
stations[40090] = { 'name':' Damen (Brown Line)', 'eta':[], 'led_index':26, 'state':0 }
stations[41280] = { 'name':' Jefferson Park (Blue Line)', 'eta':[], 'led_index':27, 'state':0 }
stations[40520] = { 'name':' Foster (Purple Line)', 'eta':[], 'led_index':28, 'state':0 }
stations[40330] = { 'name':' Grand (Red Line)', 'eta':[], 'led_index':29, 'state':0 }
stations[40280] = { 'name':' Central (Green Line)', 'eta':[], 'led_index':30, 'state':0 }
stations[40250] = { 'name':' Kedzie-Homan (Blue Line)', 'eta':[], 'led_index':31, 'state':0 }
stations[40870] = { 'name':' Francisco (Brown Line)', 'eta':[], 'led_index':32, 'state':0 }
stations[41240] = { 'name':' Addison (Blue Line)', 'eta':[], 'led_index':33, 'state':0 }
stations[40740] = { 'name':' Western (Pink Line)', 'eta':[], 'led_index':34, 'state':0 }
stations[41360] = { 'name':' California (Green Line)', 'eta':[], 'led_index':35, 'state':0 }
stations[41400] = { 'name':' Roosevelt (Red Orange & Green lines)', 'eta':[], 'led_index':36, 'state':0 }
stations[41410] = { 'name':' Chicago (Blue Line)', 'eta':[], 'led_index':37, 'state':0 }
stations[40680] = { 'name':' Adams/Wabash (Brown Green Orange Pink & Purple lines)', 'eta':[], 'led_index':38, 'state':0 }
stations[40920] = { 'name':' Pulaski (Blue Line)', 'eta':[], 'led_index':39, 'state':0 }
stations[41210] = { 'name':' Wellington (Brown & Purple lines)', 'eta':[], 'led_index':40, 'state':0 }
stations[41340] = { 'name':' LaSalle (Blue Line)', 'eta':[], 'led_index':41, 'state':0 }
stations[41490] = { 'name':' Harrison (Red Line)', 'eta':[], 'led_index':42, 'state':0 }
stations[41350] = { 'name':' Oak Park (Green Line)', 'eta':[], 'led_index':43, 'state':0 }
stations[40040] = { 'name':' Quincy/Wells (Brown Orange Purple & Pink lines)', 'eta':[], 'led_index':44, 'state':0 }
stations[40390] = { 'name':' Forest Park (Blue Line)', 'eta':[], 'led_index':45, 'state':0 }
stations[41130] = { 'name':' Halsted (Orange Line)', 'eta':[], 'led_index':46, 'state':0 }
stations[40960] = { 'name':' Pulaski (Orange Line)', 'eta':[], 'led_index':47, 'state':0 }
stations[40830] = { 'name':' 18th (Pink Line)', 'eta':[], 'led_index':48, 'state':0 }
stations[40140] = { 'name':' Dempster-Skokie  (Yellow Line)', 'eta':[], 'led_index':49, 'state':0 }
stations[41320] = { 'name':' Belmont (Red Brown & Purple lines)', 'eta':[], 'led_index':50, 'state':0 }
stations[41660] = { 'name':' Lake (Red Line)', 'eta':[], 'led_index':51, 'state':0 }
stations[40910] = { 'name':' 63rd (Red Line)', 'eta':[], 'led_index':52, 'state':0 }
stations[40690] = { 'name':' Dempster (Purple Line)', 'eta':[], 'led_index':53, 'state':0 }
stations[40460] = { 'name':' Merchandise Mart (Brown & Purple lines)', 'eta':[], 'led_index':54, 'state':0 }
stations[40710] = { 'name':' Chicago (Brown & Purple lines)', 'eta':[], 'led_index':55, 'state':0 }
stations[40530] = { 'name':' Diversey (Brown & Purple lines)', 'eta':[], 'led_index':56, 'state':0 }
stations[40850] = { 'name':' Harold Washington Library-State/Van Buren (Brown Orange Purple & Pink lines)', 'eta':[], 'led_index':57, 'state':0 }
stations[41020] = { 'name':' Logan Square (Blue Line)', 'eta':[], 'led_index':58, 'state':0 }
stations[41030] = { 'name':' Polk (Pink Line)', 'eta':[], 'led_index':59, 'state':0 }
stations[41300] = { 'name':' Loyola (Red Line)', 'eta':[], 'led_index':60, 'state':0 }
stations[40310] = { 'name':' Western (Orange Line)', 'eta':[], 'led_index':61, 'state':0 }
stations[40430] = { 'name':' Clinton (Blue Line)', 'eta':[], 'led_index':62, 'state':0 }
stations[40320] = { 'name':' Division (Blue Line)', 'eta':[], 'led_index':63, 'state':0 }
stations[41510] = { 'name':' Morgan (Green & Pink lines)', 'eta':[], 'led_index':64, 'state':0 }
stations[40270] = { 'name':' Main (Purple Line)', 'eta':[], 'led_index':65, 'state':0 }
stations[41330] = { 'name':' Montrose (Blue Line)', 'eta':[], 'led_index':66, 'state':0 }
stations[40350] = { 'name':' UIC-Halsted (Blue Line)', 'eta':[], 'led_index':67, 'state':0 }
stations[40940] = { 'name':' Halsted (Green Line)', 'eta':[], 'led_index':68, 'state':0 }
stations[41480] = { 'name':' Western (Brown Line)', 'eta':[], 'led_index':69, 'state':0 }
stations[40030] = { 'name':' Pulaski (Green Line)', 'eta':[], 'led_index':70, 'state':0 }
stations[41190] = { 'name':' Jarvis (Red Line)', 'eta':[], 'led_index':71, 'state':0 }
stations[41170] = { 'name':' Garfield (Red Line)', 'eta':[], 'led_index':72, 'state':0 }
stations[40050] = { 'name':' Davis (Purple Line)', 'eta':[], 'led_index':73, 'state':0 }
stations[40750] = { 'name':' Harlem (Blue Line - OHare Branch)', 'eta':[], 'led_index':74, 'state':0 }
stations[41200] = { 'name':' Argyle (Red Line)', 'eta':[], 'led_index':75, 'state':0 }
stations[40440] = { 'name':' California (Pink Line)', 'eta':[], 'led_index':76, 'state':0 }
stations[40360] = { 'name':' Southport (Brown Line)', 'eta':[], 'led_index':77, 'state':0 }
stations[40760] = { 'name':' Granville (Red Line)', 'eta':[], 'led_index':78, 'state':0 }
stations[40480] = { 'name':' Cicero (Green Line)', 'eta':[], 'led_index':79, 'state':0 }
stations[40730] = { 'name':' Washington/Wells (Brown Orange Purple & Pink lines)', 'eta':[], 'led_index':80, 'state':0 }
stations[40580] = { 'name':' 54th/Cermak (Pink Line)', 'eta':[], 'led_index':81, 'state':0 }
stations[40650] = { 'name':' North/Clybourn (Red Line)', 'eta':[], 'led_index':82, 'state':0 }
stations[40220] = { 'name':' Western (Blue Line - Forest Park Branch)', 'eta':[], 'led_index':83, 'state':0 }
stations[40070] = { 'name':' Jackson (Blue Line)', 'eta':[], 'led_index':84, 'state':0 }
stations[41440] = { 'name':' Addison (Brown Line)', 'eta':[], 'led_index':85, 'state':0 }
stations[41670] = { 'name':' Conservatory (Green Line)', 'eta':[], 'led_index':86, 'state':0 }
stations[40790] = { 'name':' Monroe (Blue Line)', 'eta':[], 'led_index':87, 'state':0 }
stations[40180] = { 'name':' Oak Park (Blue Line)', 'eta':[], 'led_index':88, 'state':0 }
stations[40400] = { 'name':' Noyes (Purple Line)', 'eta':[], 'led_index':89, 'state':0 }
stations[41260] = { 'name':' Austin (Green Line)', 'eta':[], 'led_index':90, 'state':0 }
stations[40600] = { 'name':' Kostner (Pink Line)', 'eta':[], 'led_index':91, 'state':0 }
stations[40670] = { 'name':' Western (Blue Line - OHare Branch)', 'eta':[], 'led_index':92, 'state':0 }
stations[40100] = { 'name':' Morse (Red Line)', 'eta':[], 'led_index':93, 'state':0 }
stations[40770] = { 'name':' Lawrence (Red Line)', 'eta':[], 'led_index':94, 'state':0 }
stations[40150] = { 'name':' Pulaski (Pink Line)', 'eta':[], 'led_index':95, 'state':0 }
stations[40660] = { 'name':' Armitage (Brown & Purple lines)', 'eta':[], 'led_index':96, 'state':0 }
stations[40540] = { 'name':' Wilson (Red & Purple lines)', 'eta':[], 'led_index':97, 'state':0 }
stations[40380] = { 'name':' Clark/Lake (Blue Brown Green Orange Purple & Pink lines)', 'eta':[], 'led_index':98, 'state':0 }
stations[40160] = { 'name':' LaSalle/Van Buren (Brown Orange Purple & Pink lines)', 'eta':[], 'led_index':99, 'state':0 }
stations[40700] = { 'name':' Laramie (Green Line)', 'eta':[], 'led_index':100, 'state':0 }
stations[40240] = { 'name':' 79th (Red Line)', 'eta':[], 'led_index':101, 'state':0 }
stations[40290] = { 'name':' Ashland/63rd (Green Line)', 'eta':[], 'led_index':102, 'state':0 }
stations[40450] = { 'name':' 95th/Dan Ryan (Red Line)', 'eta':[], 'led_index':103, 'state':0 }
stations[40210] = { 'name':' Damen (Pink Line)', 'eta':[], 'led_index':104, 'state':0 }
stations[41140] = { 'name':' King Drive (Green Line)', 'eta':[], 'led_index':105, 'state':0 }
stations[41710] = { 'name':' Damen (Green Line)', 'eta':[], 'led_index':106, 'state':0 }
stations[40980] = { 'name':' Harlem (Blue Line - Forest Park Branch)', 'eta':[], 'led_index':107, 'state':0 }
stations[41270] = { 'name':' 43rd (Green Line)', 'eta':[], 'led_index':108, 'state':0 }
stations[40080] = { 'name':' Sheridan (Red Line)', 'eta':[], 'led_index':109, 'state':0 }
stations[40610] = { 'name':' Ridgeland (Green Line)', 'eta':[], 'led_index':110, 'state':0 }
stations[41070] = { 'name':' Kedzie (Green Line)', 'eta':[], 'led_index':111, 'state':0 }
stations[41430] = { 'name':' 87th (Red Line)', 'eta':[], 'led_index':112, 'state':0 }
stations[41250] = { 'name':' Central (Purple Line)', 'eta':[], 'led_index':113, 'state':0 }
stations[40880] = { 'name':' Thorndale (Red Line)', 'eta':[], 'led_index':114, 'state':0 }
stations[41680] = { 'name':' Oakton-Skokie (Yellow Line)', 'eta':[], 'led_index':115, 'state':0 }
stations[41120] = { 'name':' 35th-Bronzeville-IIT (Green Line)', 'eta':[], 'led_index':116, 'state':0 }
stations[41460] = { 'name':' Irving Park (Brown Line)', 'eta':[], 'led_index':117, 'state':0 }
stations[41220] = { 'name':' Fullerton (Red Brown & Purple lines)', 'eta':[], 'led_index':118, 'state':0 }
stations[40990] = { 'name':' 69th (Red Line)', 'eta':[], 'led_index':119, 'state':0 }
stations[41040] = { 'name':' Kedzie (Pink Line)', 'eta':[], 'led_index':120, 'state':0 }
stations[40800] = { 'name':' Sedgwick (Brown & Purple lines)', 'eta':[], 'led_index':121, 'state':0 }
stations[40060] = { 'name':' Belmont (Blue Line)', 'eta':[], 'led_index':122, 'state':0 }
stations[40840] = { 'name':' South Boulevard (Purple Line)', 'eta':[], 'led_index':123, 'state':0 }
stations[40170] = { 'name':' Ashland (Green & Pink lines)', 'eta':[], 'led_index':124, 'state':0 }
stations[40810] = { 'name':' Illinois Medical District (Blue Line)', 'eta':[], 'led_index':125, 'state':0 }
stations[41230] = { 'name':' 47th (Red Line)', 'eta':[], 'led_index':126, 'state':0 }
stations[41500] = { 'name':' Montrose (Brown Line)', 'eta':[], 'led_index':127, 'state':0 }
stations[40930] = { 'name':' Midway (Orange Line)', 'eta':[], 'led_index':128, 'state':0 }
stations[40550] = { 'name':' Irving Park (Blue Line)', 'eta':[], 'led_index':129, 'state':0 }
stations[40370] = { 'name':' Washington (Blue Line)', 'eta':[], 'led_index':130, 'state':0 }
stations[40890] = { 'name':' OHare (Blue Line)', 'eta':[], 'led_index':131, 'state':0 }
stations[40630] = { 'name':' Clark/Division (Red Line)', 'eta':[], 'led_index':132, 'state':0 }
stations[41420] = { 'name':' Addison (Red Line)', 'eta':[], 'led_index':133, 'state':0 }
stations[41290] = { 'name':' Kimball (Brown Line)', 'eta':[], 'led_index':134, 'state':0 }
stations[40300] = { 'name':' Indiana (Green Line)', 'eta':[], 'led_index':135, 'state':0 }
stations[40420] = { 'name':' Cicero (Pink Line)', 'eta':[], 'led_index':136, 'state':0 }
stations[40720] = { 'name':' Cottage Grove (Green Line)', 'eta':[], 'led_index':137, 'state':0 }
stations[41380] = { 'name':' Bryn Mawr (Red Line)', 'eta':[], 'led_index':138, 'state':0 }
stations[41450] = { 'name':' Chicago (Red Line)', 'eta':[], 'led_index':139, 'state':0 }
stations[40590] = { 'name':' Damen (Blue Line)', 'eta':[], 'led_index':140, 'state':0 }
stations[41180] = { 'name':' Kedzie (Brown Line)', 'eta':[], 'led_index':141, 'state':0 }
stations[40010] = { 'name':' Austin (Blue Line)', 'eta':[], 'led_index':142, 'state':0 }
stations[41310] = { 'name':' Paulina (Brown Line)', 'eta':[], 'led_index':143, 'state':0 }
stations[40970] = { 'name':' Cicero (Blue Line)', 'eta':[], 'led_index':144, 'state':0 }
+239
View File
@@ -0,0 +1,239 @@
from time import time, gmtime, mktime,sleep
import network, machine, ntptime, socket, json, requests,neopixel
def time_from_iso(datetime_str):
[yyyy, mm, dd] = [int(i) for i in datetime_str.split('T')[0].split('-')]
[h,m,s] = [int(i) for i in datetime_str.split('T')[1].split(':')]
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)
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"):
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", "")
}
with open("config.json", "w") as f:
json.dump(config, f)
print("Configuration saved:", config)
def start_http_server():
addr = socket.getaddrinfo("0.0.0.0", 80)[0][-1]
s = socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(addr)
s.listen(1)
print("HTTP server listening on port 80")
while True:
try:
conn, addr = s.accept()
request = conn.recv(2048).decode()
if request.startswith("POST /save"):
# 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>
<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>
</body>
</html>"""
else:
# Serve the setup page for any other 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
)
conn.send(response.encode())
except Exception as e:
print("HTTP error:", e)
try:
error = f"<h2>Error</h2><p>{e}</p>"
conn.send(
"HTTP/1.1 500 Internal Server Error\r\n"
"Content-Type: text/html\r\n"
"Connection: close\r\n\r\n"
.encode() + error.encode()
)
except:
pass
finally:
try:
conn.close()
except:
pass
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...')
sleep(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...')
sleep(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
p = machine.Pin(20) # connected on pin 20
wled = neopixel.NeoPixel(p,2)
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()
# 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()
start_http_server()
# Halt waiting for config
while(True):
sleep(5)
while(True):
# Synchronise NTP time if hasn't been done in 1 hour
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 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
# 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 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:
wled[station] = (0,0,0)
# Once all LEDs updated
wled.write()
sleep(1)
@@ -0,0 +1,72 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Device Setup</title>
<style>
body {
font-family: sans-serif;
padding: 16px;
max-width: 400px;
margin: 0 auto;
}
h2 {
margin-top: 0;
}
label {
display: block;
margin-top: 12px;
font-weight: bold;
}
input, button {
width: 100%;
padding: 10px;
margin-top: 6px;
font-size: 16px;
box-sizing: border-box;
}
button {
margin-top: 20px;
background: #007bff;
color: white;
border: none;
cursor: pointer;
}
button:hover {
background: #0056b3;
}
</style>
</head>
<body>
<h2>Device Setup</h2>
<form method="POST" action="/save">
<label>
Wi-Fi SSID
<input name="SSID" required>
</label>
<label>
Password
<input name="PASS" type="password">
</label>
<label>
CTA API Key
<input name="APIKEY">
</label>
<label>
Left LED Station:
<select name="LStation"><option value=41090>Monroe (Red Line)</option><option value=41010>Rockwell (Brown Line)</option><option value=40470>Racine (Blue Line)</option><option value=40190>Sox-35th (Red Line)</option><option value=40020>Harlem/Lake (Green Line)</option><option value=40900>Howard (Red Purple & Yellow lines)</option><option value=41160>Clinton (Green & Pink lines)</option><option value=40230>Cumberland (Blue Line)</option><option value=40560>Jackson (Red Line)</option><option value=41150>Kedzie (Orange Line)</option><option value=40780>Central Park (Pink Line)</option><option value=41080>47th (Green Line)</option><option value=40510>Garfield (Green Line)</option><option value=41050>Linden (Purple Line)</option><option value=40120>35th/Archer (Orange Line)</option><option value=40570>California (Blue Line)</option><option value=40260>State/Lake (Brown Green Orange Pink & Purple lines)</option><option value=40130>51st (Green Line)</option><option value=41700>Washington/Wabash (Brown Green Orange Purple & Pink lines)</option><option value=41000>Cermak-Chinatown (Red Line)</option><option value=40340>Berwyn (Red Line)</option><option value=41690>Cermak-McCormick Place (Green Line)</option><option value=40490>Grand (Blue Line)</option><option value=41060>Ashland (Orange Line)</option><option value=40090>Damen (Brown Line)</option><option value=41280>Jefferson Park (Blue Line)</option><option value=40520>Foster (Purple Line)</option><option value=40330>Grand (Red Line)</option><option value=40280>Central (Green Line)</option><option value=40250>Kedzie-Homan (Blue Line)</option><option value=40870>Francisco (Brown Line)</option><option value=41240>Addison (Blue Line)</option><option value=40740>Western (Pink Line)</option><option value=41360>California (Green Line)</option><option value=41400>Roosevelt (Red Orange & Green lines)</option><option value=41410>Chicago (Blue Line)</option><option value=40680>Adams/Wabash (Brown Green Orange Pink & Purple lines)</option><option value=40920>Pulaski (Blue Line)</option><option value=41210>Wellington (Brown & Purple lines)</option><option value=41340>LaSalle (Blue Line)</option><option value=41490>Harrison (Red Line)</option><option value=41350>Oak Park (Green Line)</option><option value=40040>Quincy/Wells (Brown Orange Purple & Pink lines)</option><option value=40390>Forest Park (Blue Line)</option><option value=41130>Halsted (Orange Line)</option><option value=40960>Pulaski (Orange Line)</option><option value=40830>18th (Pink Line)</option><option value=40140>Dempster-Skokie \xa0(Yellow Line)</option><option value=41320>Belmont (Red Brown & Purple lines)</option><option value=41660>Lake (Red Line)</option><option value=40910>63rd (Red Line)</option><option value=40690>Dempster (Purple Line)</option><option value=40460>Merchandise Mart (Brown & Purple lines)</option><option value=40710>Chicago (Brown & Purple lines)</option><option value=40530>Diversey (Brown & Purple lines)</option><option value=40850>Harold Washington Library-State/Van Buren (Brown Orange Purple & Pink lines)</option><option value=41020>Logan Square (Blue Line)</option><option value=41030>Polk (Pink Line)</option><option value=41300>Loyola (Red Line)</option><option value=40310>Western (Orange Line)</option><option value=40430>Clinton (Blue Line)</option><option value=40320>Division (Blue Line)</option><option value=41510>Morgan (Green & Pink lines)</option><option value=40270>Main (Purple Line)</option><option value=41330>Montrose (Blue Line)</option><option value=40350>UIC-Halsted (Blue Line)</option><option value=40940>Halsted (Green Line)</option><option value=41480>Western (Brown Line)</option><option value=40030>Pulaski (Green Line)</option><option value=41190>Jarvis (Red Line)</option><option value=41170>Garfield (Red Line)</option><option value=40050>Davis (Purple Line)</option><option value=40750>Harlem (Blue Line - OHare Branch)</option><option value=41200>Argyle (Red Line)</option><option value=40440>California (Pink Line)</option><option value=40360>Southport (Brown Line)</option><option value=40760>Granville (Red Line)</option><option value=40480>Cicero (Green Line)</option><option value=40730>Washington/Wells (Brown Orange Purple & Pink lines)</option><option value=40580>54th/Cermak (Pink Line)</option><option value=40650>North/Clybourn (Red Line)</option><option value=40220>Western (Blue Line - Forest Park Branch)</option><option value=40070>Jackson (Blue Line)</option><option value=41440>Addison (Brown Line)</option><option value=41670>Conservatory (Green Line)</option><option value=40790>Monroe (Blue Line)</option><option value=40180>Oak Park (Blue Line)</option><option value=40400>Noyes (Purple Line)</option><option value=41260>Austin (Green Line)</option><option value=40600>Kostner (Pink Line)</option><option value=40670>Western (Blue Line - OHare Branch)</option><option value=40100>Morse (Red Line)</option><option value=40770>Lawrence (Red Line)</option><optiLine truncated
</label>
<label>
Right LED Station:
<select name="RStation"><option value=41090>Monroe (Red Line)</option><option value=41010>Rockwell (Brown Line)</option><option value=40470>Racine (Blue Line)</option><option value=40190>Sox-35th (Red Line)</option><option value=40020>Harlem/Lake (Green Line)</option><option value=40900>Howard (Red Purple & Yellow lines)</option><option value=41160>Clinton (Green & Pink lines)</option><option value=40230>Cumberland (Blue Line)</option><option value=40560>Jackson (Red Line)</option><option value=41150>Kedzie (Orange Line)</option><option value=40780>Central Park (Pink Line)</option><option value=41080>47th (Green Line)</option><option value=40510>Garfield (Green Line)</option><option value=41050>Linden (Purple Line)</option><option value=40120>35th/Archer (Orange Line)</option><option value=40570>California (Blue Line)</option><option value=40260>State/Lake (Brown Green Orange Pink & Purple lines)</option><option value=40130>51st (Green Line)</option><option value=41700>Washington/Wabash (Brown Green Orange Purple & Pink lines)</option><option value=41000>Cermak-Chinatown (Red Line)</option><option value=40340>Berwyn (Red Line)</option><option value=41690>Cermak-McCormick Place (Green Line)</option><option value=40490>Grand (Blue Line)</option><option value=41060>Ashland (Orange Line)</option><option value=40090>Damen (Brown Line)</option><option value=41280>Jefferson Park (Blue Line)</option><option value=40520>Foster (Purple Line)</option><option value=40330>Grand (Red Line)</option><option value=40280>Central (Green Line)</option><option value=40250>Kedzie-Homan (Blue Line)</option><option value=40870>Francisco (Brown Line)</option><option value=41240>Addison (Blue Line)</option><option value=40740>Western (Pink Line)</option><option value=41360>California (Green Line)</option><option value=41400>Roosevelt (Red Orange & Green lines)</option><option value=41410>Chicago (Blue Line)</option><option value=40680>Adams/Wabash (Brown Green Orange Pink & Purple lines)</option><option value=40920>Pulaski (Blue Line)</option><option value=41210>Wellington (Brown & Purple lines)</option><option value=41340>LaSalle (Blue Line)</option><option value=41490>Harrison (Red Line)</option><option value=41350>Oak Park (Green Line)</option><option value=40040>Quincy/Wells (Brown Orange Purple & Pink lines)</option><option value=40390>Forest Park (Blue Line)</option><option value=41130>Halsted (Orange Line)</option><option value=40960>Pulaski (Orange Line)</option><option value=40830>18th (Pink Line)</option><option value=40140>Dempster-Skokie \xa0(Yellow Line)</option><option value=41320>Belmont (Red Brown & Purple lines)</option><option value=41660>Lake (Red Line)</option><option value=40910>63rd (Red Line)</option><option value=40690>Dempster (Purple Line)</option><option value=40460>Merchandise Mart (Brown & Purple lines)</option><option value=40710>Chicago (Brown & Purple lines)</option><option value=40530>Diversey (Brown & Purple lines)</option><option value=40850>Harold Washington Library-State/Van Buren (Brown Orange Purple & Pink lines)</option><option value=41020>Logan Square (Blue Line)</option><option value=41030>Polk (Pink Line)</option><option value=41300>Loyola (Red Line)</option><option value=40310>Western (Orange Line)</option><option value=40430>Clinton (Blue Line)</option><option value=40320>Division (Blue Line)</option><option value=41510>Morgan (Green & Pink lines)</option><option value=40270>Main (Purple Line)</option><option value=41330>Montrose (Blue Line)</option><option value=40350>UIC-Halsted (Blue Line)</option><option value=40940>Halsted (Green Line)</option><option value=41480>Western (Brown Line)</option><option value=40030>Pulaski (Green Line)</option><option value=41190>Jarvis (Red Line)</option><option value=41170>Garfield (Red Line)</option><option value=40050>Davis (Purple Line)</option><option value=40750>Harlem (Blue Line - OHare Branch)</option><option value=41200>Argyle (Red Line)</option><option value=40440>California (Pink Line)</option><option value=40360>Southport (Brown Line)</option><option value=40760>Granville (Red Line)</option><option value=40480>Cicero (Green Line)</option><option value=40730>Washington/Wells (Brown Orange Purple & Pink lines)</option><option value=40580>54th/Cermak (Pink Line)</option><option value=40650>North/Clybourn (Red Line)</option><option value=40220>Western (Blue Line - Forest Park Branch)</option><option value=40070>Jackson (Blue Line)</option><option value=41440>Addison (Brown Line)</option><option value=41670>Conservatory (Green Line)</option><option value=40790>Monroe (Blue Line)</option><option value=40180>Oak Park (Blue Line)</option><option value=40400>Noyes (Purple Line)</option><option value=41260>Austin (Green Line)</option><option value=40600>Kostner (Pink Line)</option><option value=40670>Western (Blue Line - OHare Branch)</option><option value=40100>Morse (Red Line)</option><option value=40770>Lawrence (Red Line)</option><optiLine truncated
</label>
<button type="submit">Save & Connect</button>
</form>
</body>
</html>
+16
View File
@@ -0,0 +1,16 @@
{
"version": 1,
"author": "Isaac",
"editor": "wokwi",
"parts": [
{ "type": "board-esp32-devkit-c-v4", "id": "esp", "top": -37.58, "left": -96.37, "attrs": {} },
{ "type": "wokwi-led", "id": "led1", "top": -61.2, "left": 61.4, "attrs": { "color": "red" } }
],
"connections": [
[ "esp:TX", "$serialMonitor:RX", "", [] ],
[ "esp:RX", "$serialMonitor:TX", "", [] ],
[ "led1:C", "esp:GND.2", "green", [ "v0" ] ],
[ "led1:A", "esp:15", "green", [ "v0" ] ]
],
"dependencies": {}
}
@@ -0,0 +1,3 @@
SSID = "Wokwi-GUEST"
PASS = ""
APIKEY = "abcdef1234567890abcdef"
+258
View File
@@ -0,0 +1,258 @@
import requests
from time import time, gmtime, mktime,sleep
import network
import local_secrets
import machine
import ntptime
APIKEY = local_secrets.APIKEY
SSID = local_secrets.SSID
PASS = local_secrets.PASS
def time_from_iso(datetime_str):
[yyyy, mm, dd] = [int(i) for i in datetime_str.split('T')[0].split('-')]
[h,m,s] = [int(i) for i in datetime_str.split('T')[1].split(':')]
time_u = mktime((yyyy, mm, dd, h, m, s, 0, 0))
return time_u
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...')
sleep(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...')
sleep(2)
def init_LEDs():
# Sets up LEDs and their ports.
led = machine.Pin(15, machine.Pin.OUT)
led.off()
# Connect Wifi
# Create network in station mode, activate it and connect
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
# Stations dictionary contains name, the ETA of all trains as an array, the LED index of the light and the LED state
stations = {}
stations[41090] = { 'name':' Monroe (Red Line)', 'eta':[], 'led_index':1, 'state':0 }
stations[40820] = { 'name':' Rosemont (Blue Line)', 'eta':[], 'led_index': machine.Pin(15, machine.Pin.OUT), 'state':0 }
stations[41010] = { 'name':' Rockwell (Brown Line)', 'eta':[], 'led_index':3, 'state':0 }
stations[40470] = { 'name':' Racine (Blue Line)', 'eta':[], 'led_index':4, 'state':0 }
stations[40190] = { 'name':' Sox-35th (Red Line)', 'eta':[], 'led_index':5, 'state':0 }
stations[40020] = { 'name':' Harlem/Lake (Green Line)', 'eta':[], 'led_index':6, 'state':0 }
stations[40900] = { 'name':' Howard (Red Purple & Yellow lines)', 'eta':[], 'led_index':7, 'state':0 }
stations[41160] = { 'name':' Clinton (Green & Pink lines)', 'eta':[], 'led_index':8, 'state':0 }
stations[40230] = { 'name':' Cumberland (Blue Line)', 'eta':[], 'led_index':9, 'state':0 }
stations[40560] = { 'name':' Jackson (Red Line)', 'eta':[], 'led_index':10, 'state':0 }
stations[41150] = { 'name':' Kedzie (Orange Line)', 'eta':[], 'led_index':11, 'state':0 }
stations[40780] = { 'name':' Central Park (Pink Line)', 'eta':[], 'led_index':12, 'state':0 }
stations[41080] = { 'name':' 47th (Green Line)', 'eta':[], 'led_index':13, 'state':0 }
stations[40510] = { 'name':' Garfield (Green Line)', 'eta':[], 'led_index':14, 'state':0 }
stations[41050] = { 'name':' Linden (Purple Line)', 'eta':[], 'led_index':2, 'state':0 }
stations[40120] = { 'name':' 35th/Archer (Orange Line)', 'eta':[], 'led_index':16, 'state':0 }
stations[40570] = { 'name':' California (Blue Line)', 'eta':[], 'led_index':17, 'state':0 }
stations[40260] = { 'name':' State/Lake (Brown Green Orange Pink & Purple lines)', 'eta':[], 'led_index':18, 'state':0 }
stations[40130] = { 'name':' 51st (Green Line)', 'eta':[], 'led_index':19, 'state':0 }
stations[41700] = { 'name':' Washington/Wabash (Brown Green Orange Purple & Pink lines)', 'eta':[], 'led_index':20, 'state':0 }
stations[41000] = { 'name':' Cermak-Chinatown (Red Line)', 'eta':[], 'led_index':21, 'state':0 }
stations[40340] = { 'name':' Berwyn (Red Line)', 'eta':[], 'led_index':22, 'state':0 }
stations[41690] = { 'name':' Cermak-McCormick Place (Green Line)', 'eta':[], 'led_index':23, 'state':0 }
stations[40490] = { 'name':' Grand (Blue Line)', 'eta':[], 'led_index':24, 'state':0 }
stations[41060] = { 'name':' Ashland (Orange Line)', 'eta':[], 'led_index':25, 'state':0 }
stations[40090] = { 'name':' Damen (Brown Line)', 'eta':[], 'led_index':26, 'state':0 }
stations[41280] = { 'name':' Jefferson Park (Blue Line)', 'eta':[], 'led_index':27, 'state':0 }
stations[40520] = { 'name':' Foster (Purple Line)', 'eta':[], 'led_index':28, 'state':0 }
stations[40330] = { 'name':' Grand (Red Line)', 'eta':[], 'led_index':29, 'state':0 }
stations[40280] = { 'name':' Central (Green Line)', 'eta':[], 'led_index':30, 'state':0 }
stations[40250] = { 'name':' Kedzie-Homan (Blue Line)', 'eta':[], 'led_index':31, 'state':0 }
stations[40870] = { 'name':' Francisco (Brown Line)', 'eta':[], 'led_index':32, 'state':0 }
stations[41240] = { 'name':' Addison (Blue Line)', 'eta':[], 'led_index':33, 'state':0 }
stations[40740] = { 'name':' Western (Pink Line)', 'eta':[], 'led_index':34, 'state':0 }
stations[41360] = { 'name':' California (Green Line)', 'eta':[], 'led_index':35, 'state':0 }
stations[41400] = { 'name':' Roosevelt (Red Orange & Green lines)', 'eta':[], 'led_index':36, 'state':0 }
stations[41410] = { 'name':' Chicago (Blue Line)', 'eta':[], 'led_index':37, 'state':0 }
stations[40680] = { 'name':' Adams/Wabash (Brown Green Orange Pink & Purple lines)', 'eta':[], 'led_index':38, 'state':0 }
stations[40920] = { 'name':' Pulaski (Blue Line)', 'eta':[], 'led_index':39, 'state':0 }
stations[41210] = { 'name':' Wellington (Brown & Purple lines)', 'eta':[], 'led_index':40, 'state':0 }
stations[41340] = { 'name':' LaSalle (Blue Line)', 'eta':[], 'led_index':41, 'state':0 }
stations[41490] = { 'name':' Harrison (Red Line)', 'eta':[], 'led_index':42, 'state':0 }
stations[41350] = { 'name':' Oak Park (Green Line)', 'eta':[], 'led_index':43, 'state':0 }
stations[40040] = { 'name':' Quincy/Wells (Brown Orange Purple & Pink lines)', 'eta':[], 'led_index':44, 'state':0 }
stations[40390] = { 'name':' Forest Park (Blue Line)', 'eta':[], 'led_index':45, 'state':0 }
stations[41130] = { 'name':' Halsted (Orange Line)', 'eta':[], 'led_index':46, 'state':0 }
stations[40960] = { 'name':' Pulaski (Orange Line)', 'eta':[], 'led_index':47, 'state':0 }
stations[40830] = { 'name':' 18th (Pink Line)', 'eta':[], 'led_index':48, 'state':0 }
stations[40140] = { 'name':' Dempster-Skokie  (Yellow Line)', 'eta':[], 'led_index':49, 'state':0 }
stations[41320] = { 'name':' Belmont (Red Brown & Purple lines)', 'eta':[], 'led_index':50, 'state':0 }
stations[41660] = { 'name':' Lake (Red Line)', 'eta':[], 'led_index':51, 'state':0 }
stations[40910] = { 'name':' 63rd (Red Line)', 'eta':[], 'led_index':52, 'state':0 }
stations[40690] = { 'name':' Dempster (Purple Line)', 'eta':[], 'led_index':53, 'state':0 }
stations[40460] = { 'name':' Merchandise Mart (Brown & Purple lines)', 'eta':[], 'led_index':54, 'state':0 }
stations[40710] = { 'name':' Chicago (Brown & Purple lines)', 'eta':[], 'led_index':55, 'state':0 }
stations[40530] = { 'name':' Diversey (Brown & Purple lines)', 'eta':[], 'led_index':56, 'state':0 }
stations[40850] = { 'name':' Harold Washington Library-State/Van Buren (Brown Orange Purple & Pink lines)', 'eta':[], 'led_index':57, 'state':0 }
stations[41020] = { 'name':' Logan Square (Blue Line)', 'eta':[], 'led_index':58, 'state':0 }
stations[41030] = { 'name':' Polk (Pink Line)', 'eta':[], 'led_index':59, 'state':0 }
stations[41300] = { 'name':' Loyola (Red Line)', 'eta':[], 'led_index':60, 'state':0 }
stations[40310] = { 'name':' Western (Orange Line)', 'eta':[], 'led_index':61, 'state':0 }
stations[40430] = { 'name':' Clinton (Blue Line)', 'eta':[], 'led_index':62, 'state':0 }
stations[40320] = { 'name':' Division (Blue Line)', 'eta':[], 'led_index':63, 'state':0 }
stations[41510] = { 'name':' Morgan (Green & Pink lines)', 'eta':[], 'led_index':64, 'state':0 }
stations[40270] = { 'name':' Main (Purple Line)', 'eta':[], 'led_index':65, 'state':0 }
stations[41330] = { 'name':' Montrose (Blue Line)', 'eta':[], 'led_index':66, 'state':0 }
stations[40350] = { 'name':' UIC-Halsted (Blue Line)', 'eta':[], 'led_index':67, 'state':0 }
stations[40940] = { 'name':' Halsted (Green Line)', 'eta':[], 'led_index':68, 'state':0 }
stations[41480] = { 'name':' Western (Brown Line)', 'eta':[], 'led_index':69, 'state':0 }
stations[40030] = { 'name':' Pulaski (Green Line)', 'eta':[], 'led_index':70, 'state':0 }
stations[41190] = { 'name':' Jarvis (Red Line)', 'eta':[], 'led_index':71, 'state':0 }
stations[41170] = { 'name':' Garfield (Red Line)', 'eta':[], 'led_index':72, 'state':0 }
stations[40050] = { 'name':' Davis (Purple Line)', 'eta':[], 'led_index':73, 'state':0 }
stations[40750] = { 'name':' Harlem (Blue Line - OHare Branch)', 'eta':[], 'led_index':74, 'state':0 }
stations[41200] = { 'name':' Argyle (Red Line)', 'eta':[], 'led_index':75, 'state':0 }
stations[40440] = { 'name':' California (Pink Line)', 'eta':[], 'led_index':76, 'state':0 }
stations[40360] = { 'name':' Southport (Brown Line)', 'eta':[], 'led_index':77, 'state':0 }
stations[40760] = { 'name':' Granville (Red Line)', 'eta':[], 'led_index':78, 'state':0 }
stations[40480] = { 'name':' Cicero (Green Line)', 'eta':[], 'led_index':79, 'state':0 }
stations[40730] = { 'name':' Washington/Wells (Brown Orange Purple & Pink lines)', 'eta':[], 'led_index':80, 'state':0 }
stations[40580] = { 'name':' 54th/Cermak (Pink Line)', 'eta':[], 'led_index':81, 'state':0 }
stations[40650] = { 'name':' North/Clybourn (Red Line)', 'eta':[], 'led_index':82, 'state':0 }
stations[40220] = { 'name':' Western (Blue Line - Forest Park Branch)', 'eta':[], 'led_index':83, 'state':0 }
stations[40070] = { 'name':' Jackson (Blue Line)', 'eta':[], 'led_index':84, 'state':0 }
stations[41440] = { 'name':' Addison (Brown Line)', 'eta':[], 'led_index':85, 'state':0 }
stations[41670] = { 'name':' Conservatory (Green Line)', 'eta':[], 'led_index':86, 'state':0 }
stations[40790] = { 'name':' Monroe (Blue Line)', 'eta':[], 'led_index':87, 'state':0 }
stations[40180] = { 'name':' Oak Park (Blue Line)', 'eta':[], 'led_index':88, 'state':0 }
stations[40400] = { 'name':' Noyes (Purple Line)', 'eta':[], 'led_index':89, 'state':0 }
stations[41260] = { 'name':' Austin (Green Line)', 'eta':[], 'led_index':90, 'state':0 }
stations[40600] = { 'name':' Kostner (Pink Line)', 'eta':[], 'led_index':91, 'state':0 }
stations[40670] = { 'name':' Western (Blue Line - OHare Branch)', 'eta':[], 'led_index':92, 'state':0 }
stations[40100] = { 'name':' Morse (Red Line)', 'eta':[], 'led_index':93, 'state':0 }
stations[40770] = { 'name':' Lawrence (Red Line)', 'eta':[], 'led_index':94, 'state':0 }
stations[40150] = { 'name':' Pulaski (Pink Line)', 'eta':[], 'led_index':95, 'state':0 }
stations[40660] = { 'name':' Armitage (Brown & Purple lines)', 'eta':[], 'led_index':96, 'state':0 }
stations[40540] = { 'name':' Wilson (Red & Purple lines)', 'eta':[], 'led_index':97, 'state':0 }
stations[40380] = { 'name':' Clark/Lake (Blue Brown Green Orange Purple & Pink lines)', 'eta':[], 'led_index':98, 'state':0 }
stations[40160] = { 'name':' LaSalle/Van Buren (Brown Orange Purple & Pink lines)', 'eta':[], 'led_index':99, 'state':0 }
stations[40700] = { 'name':' Laramie (Green Line)', 'eta':[], 'led_index':100, 'state':0 }
stations[40240] = { 'name':' 79th (Red Line)', 'eta':[], 'led_index':101, 'state':0 }
stations[40290] = { 'name':' Ashland/63rd (Green Line)', 'eta':[], 'led_index':102, 'state':0 }
stations[40450] = { 'name':' 95th/Dan Ryan (Red Line)', 'eta':[], 'led_index':103, 'state':0 }
stations[40210] = { 'name':' Damen (Pink Line)', 'eta':[], 'led_index':104, 'state':0 }
stations[41140] = { 'name':' King Drive (Green Line)', 'eta':[], 'led_index':105, 'state':0 }
stations[41710] = { 'name':' Damen (Green Line)', 'eta':[], 'led_index':106, 'state':0 }
stations[40980] = { 'name':' Harlem (Blue Line - Forest Park Branch)', 'eta':[], 'led_index':107, 'state':0 }
stations[41270] = { 'name':' 43rd (Green Line)', 'eta':[], 'led_index':108, 'state':0 }
stations[40080] = { 'name':' Sheridan (Red Line)', 'eta':[], 'led_index':109, 'state':0 }
stations[40610] = { 'name':' Ridgeland (Green Line)', 'eta':[], 'led_index':110, 'state':0 }
stations[41070] = { 'name':' Kedzie (Green Line)', 'eta':[], 'led_index':111, 'state':0 }
stations[41430] = { 'name':' 87th (Red Line)', 'eta':[], 'led_index':112, 'state':0 }
stations[41250] = { 'name':' Central (Purple Line)', 'eta':[], 'led_index':113, 'state':0 }
stations[40880] = { 'name':' Thorndale (Red Line)', 'eta':[], 'led_index':114, 'state':0 }
stations[41680] = { 'name':' Oakton-Skokie (Yellow Line)', 'eta':[], 'led_index':115, 'state':0 }
stations[41120] = { 'name':' 35th-Bronzeville-IIT (Green Line)', 'eta':[], 'led_index':116, 'state':0 }
stations[41460] = { 'name':' Irving Park (Brown Line)', 'eta':[], 'led_index':117, 'state':0 }
stations[41220] = { 'name':' Fullerton (Red Brown & Purple lines)', 'eta':[], 'led_index':118, 'state':0 }
stations[40990] = { 'name':' 69th (Red Line)', 'eta':[], 'led_index':119, 'state':0 }
stations[41040] = { 'name':' Kedzie (Pink Line)', 'eta':[], 'led_index':120, 'state':0 }
stations[40800] = { 'name':' Sedgwick (Brown & Purple lines)', 'eta':[], 'led_index':121, 'state':0 }
stations[40060] = { 'name':' Belmont (Blue Line)', 'eta':[], 'led_index':122, 'state':0 }
stations[40840] = { 'name':' South Boulevard (Purple Line)', 'eta':[], 'led_index':123, 'state':0 }
stations[40170] = { 'name':' Ashland (Green & Pink lines)', 'eta':[], 'led_index':124, 'state':0 }
stations[40810] = { 'name':' Illinois Medical District (Blue Line)', 'eta':[], 'led_index':125, 'state':0 }
stations[41230] = { 'name':' 47th (Red Line)', 'eta':[], 'led_index':126, 'state':0 }
stations[41500] = { 'name':' Montrose (Brown Line)', 'eta':[], 'led_index':127, 'state':0 }
stations[40930] = { 'name':' Midway (Orange Line)', 'eta':[], 'led_index':128, 'state':0 }
stations[40550] = { 'name':' Irving Park (Blue Line)', 'eta':[], 'led_index':129, 'state':0 }
stations[40370] = { 'name':' Washington (Blue Line)', 'eta':[], 'led_index':130, 'state':0 }
stations[40890] = { 'name':' OHare (Blue Line)', 'eta':[], 'led_index':131, 'state':0 }
stations[40630] = { 'name':' Clark/Division (Red Line)', 'eta':[], 'led_index':132, 'state':0 }
stations[41420] = { 'name':' Addison (Red Line)', 'eta':[], 'led_index':133, 'state':0 }
stations[41290] = { 'name':' Kimball (Brown Line)', 'eta':[], 'led_index':134, 'state':0 }
stations[40300] = { 'name':' Indiana (Green Line)', 'eta':[], 'led_index':135, 'state':0 }
stations[40420] = { 'name':' Cicero (Pink Line)', 'eta':[], 'led_index':136, 'state':0 }
stations[40720] = { 'name':' Cottage Grove (Green Line)', 'eta':[], 'led_index':137, 'state':0 }
stations[41380] = { 'name':' Bryn Mawr (Red Line)', 'eta':[], 'led_index':138, 'state':0 }
stations[41450] = { 'name':' Chicago (Red Line)', 'eta':[], 'led_index':139, 'state':0 }
stations[40590] = { 'name':' Damen (Blue Line)', 'eta':[], 'led_index':140, 'state':0 }
stations[41180] = { 'name':' Kedzie (Brown Line)', 'eta':[], 'led_index':141, 'state':0 }
stations[40010] = { 'name':' Austin (Blue Line)', 'eta':[], 'led_index':142, 'state':0 }
stations[41310] = { 'name':' Paulina (Brown Line)', 'eta':[], 'led_index':143, 'state':0 }
stations[40970] = { 'name':' Cicero (Blue Line)', 'eta':[], 'led_index':144, 'state':0 }
# Function sets up all LEDs.
init_LEDs()
while(True):
# Synchronise NTP time if hasn't been done in 1 hour
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 updated API
trains = (requests.get("https://lapi.transitchicago.com/api/1.0/ttpositions.aspx?key="+APIKEY+"&rt=Blue&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['tmst'])+(5*60*60))))
# Loop through all trains and update matching stations
for train in trains['route'][0]['train']:
nextStation = int(train['nextStaId'])
eta_time = time_from_iso(train['arrT'])+(5*60*60)
# Append the ETA, we rely on the station loop to remove old trains
stations[nextStation]['eta'].append(eta_time)
last_api_query = time() # uptime polling window
# Process the ETA for each station
for station, dict in stations.items(): # loop through all stations
if station == 40820: # Our test station for onboard LED
# Set LED state tracker to 0, if no trains, it'll stay 0.
stations[station]['state'] = 0
for index, eta in enumerate(dict['eta']): # multiple trains. Enumerate is used so we have the index to update the dict itself.
time_from_now = eta - time()
print('Train will arrive at'+dict['name']+' in '+ str(time_from_now) + ' seconds')
if time_from_now < distance_on:
stations[station]['state'] = 1
if time_from_now < -distance_on: # Remove old trains
stations[station]['eta'].pop(index)
# Set Station LED state
stations[station]['led_index'].value(stations[station]['state'])
sleep(1)