#include #include #include #include #include #include #include #include "bitmaps.h" #include "wifi_credentials.h" #define TFT_CS D8 #define TFT_DC D4 #define TFT_RESET D3 // Button stuff #define BUTTON_PIN D6 // Display stuff #define FRAME_DELAY 80 #define COLOR_BG (rgb(0, 0, 0)) #define COLOR_DIVIDER (rgb(0xff, 0x60, 0x0d)) #define COLOR_TEXT (rgb(0xff, 0x60, 0x0d)) // Layout #define WIDTH 160 #define HEIGHT 128 #define CX 6 #define CY 8 #define MARGIN_LEFT 5 #define MARGIN_RIGHT 5 #define MAIN_X MARGIN_LEFT #define MAIN_Y 9 #define MAIN_WIDTH (WIDTH - 2 * MARGIN_LEFT) #define MAIN_HEIGHT (8 * CY) #define CLOCK_X MARGIN_LEFT #define CLOCK_Y 113 #define STATUS_X 95 #define STATUS_Y CLOCK_Y #define STATUS_WIDTH (10 * CX) #define STATUS_HEIGHT CY // Peripherals Adafruit_ST7735 display(TFT_CS, TFT_DC, TFT_RESET); // App state typedef enum { CONNECTING, SHOWING_DEPARTURES } State; State state = CONNECTING; State previous_state = state; int frame = 0; // Use this variable to trigger state enter effects int state_changed = 1; 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); Serial.println("serial init"); display.initR(); display.setSPISpeed(40000000); display.setRotation(3); Serial.println("display initialized"); Serial.printf("display dimensions: %" PRId16 "x%" PRId16 "\n", display.width(), display.height()); display.setTextSize(1); display.setTextColor(COLOR_TEXT); drawLayout(); WiFi.begin(WIFI_SSID, WIFI_PASSWORD); pinMode(BUTTON_PIN, INPUT_PULLUP); attachInterrupt(BUTTON_PIN, onButtonFalling, FALLING); } #define QUERY_STRING "stop_id=de:08221:1225&platform=A" // 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?" QUERY_STRING)) { display.fillScreen(COLOR_BG); display.setCursor(0, 0); display.print("begin failed"); for (;;); } client.addHeader("Authorization", AUTH_TOKEN); client.addHeader("Accept", "application/json"); int statusCode = client.GET(); if (statusCode != 200) { display.fillScreen(COLOR_BG); display.setCursor(0, 0); display.printf("http non-ok: %d\n", statusCode); for (;;); } 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()) { const char *directionStr = departure["direction"].as(); display.setCursor(MAIN_X, MAIN_Y + (CY + 3) * line); display.printf("%2s %-15.15s %6s", departure["symbol"].as(), directionStr, departure["leaving"].as() ); line++; } delay(25000); state = CONNECTING; return; } }