BACK_TO_DASHBOARD

daily gospel fetcher

Hardware_Log / MDX

Hardware Used

Microcontroller : ESP32

  • Brand : Inland ESP32-WROOM-32D Module

Initial Setup

INITIAL_PROVISIONING_UI
MODEL: DGF-1.3REV: B

// INITIAL_PROVISIONING_UI

If I am going to make a few of these and hand them out to friends, there needs to be a way for the board to connect to their WiFi and not just my own that I hardcoded to connect to. Hence the WiFi setup Process!

Since this project has minimal hardware input (only a rotary encoder), it unfortunately can be a bit clunky to setup and manually input the Username/Password. Luckily, the ESP32 saves the last successfully connected WiFi network to flash storage, so by default we will always try to connect to this saved network first before guiding the user through the setup process.

Fetching Process

Now that we are connected to the internet, we can start fetching the readings from USCCB.org!

I cound not find any pre-made community APIs for doing a simple call to get the readings, but I knew that the readings were readily available on the USCCB website for any date. This provides a perfect place that the readings can be scraped from given its consistent format and structure.

TERMINAL_SOURCE_V1.0
// [title: scraping_engine.ino]
void fetchAllReadings() {
  
  if (WiFi.status() == WL_CONNECTED) {
    WiFiClientSecure client;
    client.setInsecure(); // Ignore SSL certificate errors
    
    HTTPClient http;
    // Set a timeout for the connection
    http.setTimeout(10000); 
    http.setUserAgent("Mozilla/5.0 (ESP32)");
    
    Serial.println("Starting HTTP GET...");
    // formattedTimeNum = "012626"; // e.g., "012526" - Test Date
    if (http.begin(client, serverUrl+formattedTimeNum+".cfm")) { // Full URL with date
      int httpCode = http.GET();
      Serial.print("HTTP Code: ");
      Serial.println(httpCode);

      if (httpCode == HTTP_CODE_OK) {
        WiFiClient *stream = http.getStreamPtr();
        
        String currentTagName = "";
        bool lookingForAddress = false;
        
        // Timeout variables for the stream loop
        unsigned long lastByteTime = millis();
        const unsigned long TIMEOUT_MS = 5000; 

        Serial.println("Parsing Stream...");

        while (http.connected() && (stream->available() > 0 || stream->connected())) {
          
          // SAFETY: Break if stream hangs for 5 seconds
          if (millis() - lastByteTime > TIMEOUT_MS) {
            Serial.println("Error: Stream timed out.");
            break;
          }

          if (stream->available()) {
            lastByteTime = millis(); // Reset timeout timer
            
            String line = stream->readStringUntil('\n');
            line.trim();
            
            // --- STATE 1: FIND NAME (e.g., "Gospel") ---
            if (!lookingForAddress) {
              if (line.indexOf("<h3 class=\"name\">") >= 0) {
                // Extract name between tags
                int start = line.indexOf(">") + 1;
                int end = line.indexOf("</h3>");
                currentTagName = line.substring(start, end);
                
                Serial.println("--------------------------------");
                Serial.print("FOUND SECTION: ");
                Serial.println(currentTagName);
                lookingForAddress = true;
              }
            }
            
            // --- STATE 2: FIND ADDRESS (e.g., "John 3:16") ---
            else {
              if (line.indexOf("div class=\"address\"") >= 0) {
                 Serial.println("  -> Found Address Div! Scanning multi-line...");
                 
                 // 1. Capture FULL HTML BLOCK (Fixes blank string issue)
                 String rawHtml = line;
                 int safetyCount = 0;
                 
                 // Keep reading lines until we find the closing div
                 while (rawHtml.indexOf("</div>") == -1 && safetyCount < 20) {
                   if (stream->available()) {
                     String nextLine = stream->readStringUntil('\n');
                     rawHtml += " " + nextLine; 
                     safetyCount++;
                   } else {
                     delay(10);
                   }
                 }

                 // 2. Strip HTML Tags manually
                 String cleanAddress = "";
                 bool insideTag = false;
                 
                 for (unsigned int i=0; i < rawHtml.length(); i++) {
                   char c = rawHtml[i];
                   if (c == '<') {
                     insideTag = true;
                   } else if (c == '>') {
                     insideTag = false;
                   } else if (!insideTag) {
                     // Filter: Only allow visible characters (ASCII 32-126)
                     if (c >= 32 && c <= 126) { 
                       cleanAddress += c;
                     }
                   }
                 }
                 cleanAddress.trim(); // Remove leading/trailing spaces
                 
                 Serial.print("  -> Cleaned Text: '");
                 Serial.print(cleanAddress);
                 Serial.println("'");
                 
                 // 3. Save to memory
                 if (cleanAddress.length() > 0 && readingCount < 5 && currentTagName != "Alleluia") {
                   dailyReadings[readingCount].name = currentTagName;
                   dailyReadings[readingCount].address = cleanAddress;
                   readingCount++;
                 }

                 lookingForAddress = false; // Reset to look for next section
              }
            }
          } else {
            delay(10); // Wait for buffer to fill
          }
        }
        Serial.println("Stream Finished.");
        
      } else {
        Serial.print("HTTP Error: ");
        Serial.println(httpCode);
      }
      http.end();
    } else {
      Serial.println("Connection Failed.");
    }
  }
}
FETCHING_READINGS_UI
MODEL: DGF-1.3REV: B

// FETCHING_READINGS_UI