Lifecycle
MyStation uses a multi-phase lifecycle that adapts based on the device's configuration state.
Lifecycle Diagramβ
Lifecycle Phasesβ
There are six lifecycle states. The device usually progresses linearly through them, but can skip phases or branch based on conditions.
ON_INIT: System Initializationβ
- Initialize Serial (debug builds only)
- Print wake-up diagnostics
- Check for factory reset (Button 1+2 held), app reset (Button 1 held), application info (Button 2 held), or OTA update (Button 3 held)
- Initialize e-paper display and font renderer
- Initialize battery monitoring (ESP32-S3 boards)
- If battery voltage is critically low (>0.1V and β€3.0V), show error and jump to ON_SHUTDOWN
- Load configuration from NVS (or use RTC cache after deep sleep)
- Configure button GPIO pins and attach interrupts
ON_START: Network & Time Setupβ
- Determine configuration phase (WiFi Setup / App Setup / Complete)
- If Phase 1 (no WiFi): start WiFi AP, block until configured, then
ESP.restart() - Connect to WiFi. If connection fails β show error, jump to ON_STOP
- Synchronize time via NTP (if needed or periodic refresh due)
- Handle button wakeup: set temporary display mode if woken by button press
ON_RUNNING: Operational Phaseβ
- If Phase 2 (app setup needed): start configuration web server, transition to ON_LOOP
- Check for OTA update (if scheduled time matches)
- Fetch data from APIs (Phase 3: Complete)
- If
tripMode == true: fetch trip connections via/hapi/trip(origin β destination) - If
tripMode == false: fetch departures via/hapi/departureBoard(single stop)
- If
- Disconnect WiFi (saves ~100mA during display rendering)
- Render display
ON_LOOP: Web Server Modeβ
- Serves the configuration HTTP portal
- Runs inside
setup()as an inline while-loop (not Arduino'sloop()) - Exits when configuration is saved (device enters deep sleep and restarts)
ON_STOP: Prepare for Deep Sleepβ
- Calculate next wake-up time via
TimingManager::getNextSleepDurationSeconds() - Configure button wakeup pins for deep sleep
ON_SHUTDOWN: Enter Deep Sleepβ
- Final button-press check (restart if pressed during wake cycle)
- Hibernate display (power off e-paper controller)
- Enter ESP32 deep sleep with timer + button wakeup sources
Button-Interrupt-Restart Patternβ
Throughout ON_START, ON_RUNNING, and ON_SHUTDOWN, the system calls
ButtonManager::checkAndRestartIfButtonPressed() at multiple points.
If a button was pressed via ISR during the current wake cycle, the device
calls esp_restart() to handle the button press cleanly from the beginning.
This avoids complex mid-cycle state changes.
Configuration Phasesβ
The configuration phase is determined by DeviceModeManager::getCurrentPhase():
| Phase | Condition | Action |
|---|---|---|
| PHASE_WIFI_SETUP | No WiFi SSID stored | Start WiFi AP for configuration |
| PHASE_APP_SETUP | WiFi OK, but missing stop/location | Start web config portal |
| PHASE_COMPLETE | All required settings present | Normal operation |
Display Mode Selectionβ
Display mode is determined by TimingManager::getEffectiveDisplayMode():
Temporary mode is activated by button press and lasts 2 minutes, then reverts to configured mode.
Weather Data Cachingβ
Weather data is cached in RTC memory. It is only re-fetched when the configured interval (default: 1-3 hours) has elapsed. Transport data is always fetched fresh on each wake cycle.
Deep Sleep Duration Calculationβ
The sleep duration calculator uses a rule-based priority queue. Each rule independently proposes a wake-up time. The system picks the earliest.
Terminologyβ
| Term | Meaning |
|---|---|
| Transport window | Time range when departures are shown (e.g. 06:00β09:00) |
| Sleep window | Time range when the device stays in deep sleep (e.g. 22:30β05:30) |
| Wake candidate | A proposed wake-up time from a rule |
Rulesβ
| # | Rule | When it applies | Bypasses Sleep Window |
|---|---|---|---|
| 1 | Weather update | All modes showing weather (weather-only, half&half) | No |
| 2 | Transport update | Inside transport window (half&half, transport-only) | No |
| 3 | Transport window start | Configured half&half or transport-only, currently outside window | No |
| 4 | OTA check | OTA enabled | Yes |
Algorithmβ
1. TEMPORARY MODE (early return)
β If temp mode active and < 2 min elapsed: sleep remaining time
β If in sleep window: sleep until sleep window ends
2. COLLECT CANDIDATES
β Each rule proposes a wake-up timestamp
3. OVERDUE CHECK
β If any candidate is in the past β wake immediately (30s)
4. SLEEP WINDOW FILTER
β Non-OTA candidates inside sleep window are pushed to sleep window end
β OTA candidates bypass this filter
5. PICK EARLIEST
β Minimum of all remaining candidates
β Enforce 30s minimum
Display Mode Logicβ
| Configured Mode | Inside Transport Window | Outside Transport Window |
|---|---|---|
| Weather Only | Rule 1 (weather) | Rule 1 (weather) |
| Half & Half | Rule 1 + Rule 2 (picks earlier) | Rule 1 + Rule 3 (picks earlier) |
| Transport Only | Rule 2 (transport) | Rule 3 (next window start) |
Sleep Window Handlingβ
When a wake candidate falls inside the sleep window:
22:00 22:30 05:30 06:00
| | SLEEP | |
| |=============| |
| ββββββΌββββ X ββββββΌβββ pushed to 05:30
| | | |
candidate sleep start sleep end
(23:00)
- Non-OTA candidates are pushed to sleep window end
- OTA candidates are NOT pushed (they bypass the sleep window)
- If already inside the sleep window, device sleeps until window ends
Examplesβ
7:00 AM, Half & Half mode, transport window 06:00β09:00:
- Rule 1: weather at 10:00 (3h interval)
- Rule 2: transport at 7:05 (5 min interval)
- Rule 3: not applicable (already inside window)
- β Picks 7:05 (transport, earliest)
5:00 AM, Half & Half mode, transport window 06:00β09:00:
- Rule 1: weather at 8:00 (3h interval)
- Rule 2: not applicable (outside window)
- Rule 3: transport window starts at 6:00
- β Picks 6:00 (transport window start, earliest)
22:00, Weather Only, sleep window 22:30β05:30:
- Rule 1: weather at 23:00 β pushed to 05:30
- β Picks 05:30
22:00, OTA at 03:00, weather at 23:00, sleep window 22:30β05:30:
- Rule 1: weather at 23:00 β pushed to 05:30
- Rule 4: OTA at 03:00 (bypasses sleep window)
- β Picks 03:00 (OTA, earliest)
Wake-up Sourcesβ
- Timer: Scheduled update interval
- Button EXT1: User pressed a physical button (ESP32-S3 only)
- Reset: Manual reset or power cycle