diff --git a/client/client.ino b/client/client.ino index b6f4bd0..fd6cc33 100644 --- a/client/client.ino +++ b/client/client.ino @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -46,44 +47,144 @@ #define STATUS_WIDTH (10 * CX) #define STATUS_HEIGHT CY +// Function definitions + +void drawLayout(); +uint16_t rgb(uint16_t, uint16_t, uint16_t); +String fetchDepartures(); + // Peripherals Adafruit_ST7735 display(TFT_CS, TFT_DC, TFT_RESET); -// App state +// App state interface -typedef enum { - CONNECTING, - SHOWING_DEPARTURES -} State; +class State { +public: + virtual void enter() = 0; + virtual void tick() = 0; + virtual ~State() = default; +}; -State state = CONNECTING; -State previous_state = state; -int frame = 0; -// Use this variable to trigger state enter effects -int state_changed = 1; +class StateConnecting : public State { +public: + void enter() override; + void tick() override; +}; + +class StateFetching : public State { +public: + void enter() override; + void tick() override; +}; + +class StateShowingDepartures : public State { +private: + // Todo: discard JsonDocument asap, parse into better format + JsonDocument departures; + unsigned long entered; +public: + StateShowingDepartures(String&); + void enter() override; + void tick() override; +}; + +// App state implementation + +uint64_t currentTick = 0; +// Initially true so that enter() of the initial state is called. +bool stateChanged = true; +std::unique_ptr state = std::make_unique(); + +template +void setState(Args&&... args) { + state = std::make_unique(std::forward(args)...); + stateChanged = true; +} + +void StateConnecting::enter() { + Serial.println("Entering StateConnecting"); +} + +void StateConnecting::tick() { + if (WiFi.status() != WL_CONNECTED) { + display.fillRect(STATUS_X, STATUS_Y, STATUS_WIDTH, STATUS_HEIGHT, COLOR_BG); + display.setCursor(STATUS_X, STATUS_Y); + display.printf(" connect %c", "|/-\\"[currentTick % 4]); + delay(250); + return; + } + + setState(); +} + +// Fetching is a bit of an ugly duckling; it performs all its logic in enter(). +// This is because request it does is synchronous. +// Sadly this means no animation is possible right now. +// At least the fetch call still does yield() under the hood :) +void StateFetching::enter() { + Serial.println("Entering StateFetching"); + + if (WiFi.status() != WL_CONNECTED) { + setState(); + return; + } + + display.fillRect(STATUS_X, STATUS_Y, STATUS_WIDTH, STATUS_HEIGHT, COLOR_BG); + display.setCursor(STATUS_X, STATUS_Y); + display.print(" fetching"); + + String departuresRaw = fetchDepartures(); + + setState(departuresRaw); +}; + +void StateFetching::tick() { +} + +StateShowingDepartures::StateShowingDepartures(String &departuresRaw) { + deserializeJson(departures, departuresRaw); +} + +void StateShowingDepartures::enter() { + Serial.println("Entering StateShowingDepartures"); + entered = millis(); + + display.fillRect(STATUS_X, STATUS_Y, STATUS_WIDTH, STATUS_HEIGHT, COLOR_BG); + display.fillRect(MAIN_X, MAIN_Y, MAIN_WIDTH, MAIN_HEIGHT, COLOR_BG); + + int line = 0; + for (JsonVariant departure : departures["departures"].as()) { + display.setCursor(MAIN_X, MAIN_Y + (CY + 3) * line); + display.printf("%2s %-15.15s ", + departure["symbol"].as(), + departure["direction"].as() + ); + if (departure["leaving"].as().equals("sofort")) { + int16_t x = display.getCursorX(); + int16_t y = display.getCursorY(); + display.drawBitmap(x + 6 * CX - tiny_train_dims[0], y, tiny_train[0], tiny_train_dims[0], tiny_train_dims[1], COLOR_TEXT); + } else { + display.printf("%6s", departure["leaving"].as()); + } + + line++; + } +} + +void StateShowingDepartures::tick() { + unsigned long elapsed = millis() - entered; + if (elapsed > 25000) { + setState(); + } +} int buttonPushed = false; -JsonDocument departures; void ICACHE_RAM_ATTR onButtonFalling() { buttonPushed = true; } -uint16_t rgb(uint16_t r, uint16_t g, uint16_t b) { - // Color layout: RRRRRGGGGGGBBBBB (5, 6, 5) - return ((b >> 3) & 0b11111) << 11 | ((g >> 2) & 0b111111) << 5 | ((r >> 3) & 0b11111); -} - -void drawLayout() { - display.fillScreen(COLOR_BG); - display.drawFastHLine(5, 4, 150, COLOR_DIVIDER); - display.drawFastHLine(5, 109, 150, COLOR_DIVIDER); - display.drawFastHLine(5, 123, 150, COLOR_DIVIDER); - display.setCursor(CLOCK_X, CLOCK_Y); - display.print("13:12"); -} - void setup() { Serial.begin(9600); @@ -102,10 +203,39 @@ void setup() { pinMode(BUTTON_PIN, INPUT_PULLUP); attachInterrupt(BUTTON_PIN, onButtonFalling, FALLING); + + state = std::make_unique(); } #define QUERY_STRING "stop_id=de:08221:1225&platform=A" +void loop() { + if (stateChanged) { + stateChanged = false; + // Note: enter() may call setState(). In that case we want to end up right back here. + state->enter(); + } else { + state->tick(); + currentTick++; + } + // Let background system do its thing + yield(); +} + +uint16_t rgb(uint16_t r, uint16_t g, uint16_t b) { + // Color layout: RRRRRGGGGGGBBBBB (5, 6, 5) + return ((b >> 3) & 0b11111) << 11 | ((g >> 2) & 0b111111) << 5 | ((r >> 3) & 0b11111); +} + +void drawLayout() { + display.fillScreen(COLOR_BG); + display.drawFastHLine(5, 4, 150, COLOR_DIVIDER); + display.drawFastHLine(5, 109, 150, COLOR_DIVIDER); + display.drawFastHLine(5, 123, 150, COLOR_DIVIDER); + display.setCursor(CLOCK_X, CLOCK_Y); + display.print("13:12"); +} + // TODO: Error handling String fetchDepartures() { std::unique_ptr https(new BearSSL::WiFiClientSecure); @@ -131,69 +261,3 @@ String fetchDepartures() { return client.getString(); } - -void loop() { - if (state != previous_state) { - state_changed = 1; - previous_state = state; - } - logic_loop(); - frame++; - state_changed = 0; -} - -void logic_loop() { - switch (state) { - case CONNECTING: { - if (WiFi.status() != WL_CONNECTED) { - display.fillRect(STATUS_X, STATUS_Y, STATUS_WIDTH, STATUS_HEIGHT, COLOR_BG); - display.setCursor(STATUS_X, STATUS_Y); - display.printf(" connect %c", "|/-\\"[frame % 4]); - delay(FRAME_DELAY); - return; - } - - display.fillRect(STATUS_X, STATUS_Y, STATUS_WIDTH, STATUS_HEIGHT, COLOR_BG); - display.setCursor(STATUS_X, STATUS_Y); - display.print(" fetching"); - - String departuresRaw = fetchDepartures(); - deserializeJson(departures, departuresRaw); - - state = SHOWING_DEPARTURES; - frame = 0; - return; - } - - case SHOWING_DEPARTURES: - if (WiFi.status() != WL_CONNECTED) { - state = CONNECTING; - return; - } - - display.fillRect(STATUS_X, STATUS_Y, STATUS_WIDTH, STATUS_HEIGHT, COLOR_BG); - display.fillRect(MAIN_X, MAIN_Y, MAIN_WIDTH, MAIN_HEIGHT, COLOR_BG); - - int line = 0; - for (JsonVariant departure : departures["departures"].as()) { - display.setCursor(MAIN_X, MAIN_Y + (CY + 3) * line); - display.printf("%2s %-15.15s ", - departure["symbol"].as(), - departure["direction"].as() - ); - if (departure["leaving"].as().equals("sofort")) { - int16_t x = display.getCursorX(); - int16_t y = display.getCursorY(); - display.drawBitmap(x + 6 * CX - tiny_train_dims[0], y, tiny_train[0], tiny_train_dims[0], tiny_train_dims[1], COLOR_TEXT); - } else { - display.printf("%6s", departure["leaving"].as()); - } - - line++; - } - - delay(25000); - state = CONNECTING; - return; - } -}