#include #include #include #include #include #include #include #include #include "bitmaps.h" #include "wifi_credentials.h" // Screen stuff #define SCREEN_WIDTH 128 // OLED display width, in pixels #define SCREEN_HEIGHT 64 // OLED display height, in pixels #define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin) #define SCREEN_ADDRESS 0x3C // Button stuff #define BUTTON_PIN D6 #define FRAME_DELAY 80 // Peripherals Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); // App state enum { CONNECTING, SHOWING_DEPARTURES } state = CONNECTING; int frame = 0; int buttonPushed = false; JsonDocument departures; void ICACHE_RAM_ATTR onButtonFalling() { buttonPushed = true; } void setup() { Serial.begin(9600); // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) { Serial.println("SSD1306 allocation failed"); for(;;); // Don't proceed, loop forever } Serial.println("SSD1306 initialized"); display.clearDisplay(); WiFi.begin(WIFI_SSID, WIFI_PASSWORD); pinMode(BUTTON_PIN, INPUT_PULLUP); attachInterrupt(BUTTON_PIN, onButtonFalling, FALLING); display.setTextColor(SSD1306_INVERSE); display.setTextSize(1); display.setCursor(0, 0); display.printf("Connecting to\n%s\n", WIFI_SSID); display.display(); /* display.print(" 5 Mannheim sofort\n" "26 Kirchheim 9 min\n" " 5 Weinheim 20:13"); */ } void drawSpinner(int frame) { display.fillRect(120, 56, spinny_dims[0], spinny_dims[1], SSD1306_BLACK); display.drawBitmap(120, 56, spinny[frame % spinny_dims[2]], spinny_dims[0], spinny_dims[1], SSD1306_WHITE); } // TODO: Error handling String fetchDepartures() { std::unique_ptr https(new BearSSL::WiFiClientSecure); https->setInsecure(); HTTPClient client; if (!client.begin(*https, "vrnp.beany.club", 443, "/departures?stop_id=de:08221:1225&platform=A")) { display.clearDisplay(); display.setCursor(0, 0); display.print("begin failed"); display.display(); for (;;); } client.addHeader("Authorization", AUTH_TOKEN); client.addHeader("Accept", "application/json"); int statusCode = client.GET(); if (statusCode != 200) { display.clearDisplay(); display.setCursor(0, 0); display.printf("http non-ok: %d\n", statusCode); display.display(); for (;;); } return client.getString(); } void loop() { switch (state) { case CONNECTING: { if (WiFi.status() != WL_CONNECTED) { drawSpinner(frame++); display.display(); delay(FRAME_DELAY); return; } display.clearDisplay(); display.fillRect(0, 56, 128, 8, SSD1306_INVERSE); display.setCursor(0, 56); display.print("requesting departures"); display.display(); String departuresRaw = fetchDepartures(); deserializeJson(departures, departuresRaw); state = SHOWING_DEPARTURES; frame = 0; return; } case SHOWING_DEPARTURES: if (WiFi.status() != WL_CONNECTED) { state = CONNECTING; return; } /* if (buttonPushed) { buttonPushed = false; delay(FRAME_DELAY); return; } */ // Marquee effect for direction strings longer than 11 characters // Neither particularly efficient nor legible // (This could use some love) int availableWidth = 11; display.clearDisplay(); display.setCursor(0, 0); for (JsonVariant departure : departures["departures"].as()) { const char *directionStr = departure["direction"].as(); char buf[128] = {0}; const char *dirStart; if (strlen(directionStr) <= availableWidth || (strlen(directionStr) + 3 + availableWidth) > 127) { dirStart = directionStr; } else { memcpy(buf, directionStr, strlen(directionStr)); memcpy(buf + strlen(directionStr), " ", 3); memcpy(buf + strlen(directionStr) + 3, directionStr, availableWidth); buf[strlen(directionStr) + 3 + availableWidth] = '\0'; dirStart = buf + frame % (strlen(directionStr) + 3); } display.printf("%2s %-11.11s %6s\n", departure["symbol"].as(), dirStart, departure["leaving"].as() ); } display.display(); frame++; delay(500); return; } }