#pragma once extern uint16_t relayDurations[16]; void loadRelayTimings(); void saveRelayTimings(); // - - - - - - - - - - - - - - - - - - - - - - - - - - - // Sets Incoming Relay Durations to RAM and then call funtion to save them to file. void updateRelayTimings(JsonVariant doc) { // Iterate through the relays in the JSON payload for (uint8_t i = 0; i < 16; i++) { String key = String("b") + (i + 1); // Generate "b1", "b2", ... if (doc.containsKey(key)) { relayDurations[i] = doc[key].as(); LOG_DEBUG("Relay %d duration s1et to %d ms\n", i + 1, relayDurations[i]); } else { LOG_DEBUG("Relay %d not found in JSON payload. Keeping previous duration: %d ms\n", i + 1, relayDurations[i]); } } saveRelayTimings(); LOG_INFO("Updated Relay Timings.") } // Save file "filename" with data: "data" to the "dirPath" directory of the SD card void saveFileToSD(const char* dirPath, const char* filename, const char* data) { // Initialize SD (if not already done) if (!SD.begin(SD_CS)) { LOG_ERROR("SD Card not initialized!"); return; } // Make sure directory exists if (!SD.exists(dirPath)) { SD.mkdir(dirPath); } // Build full path String fullPath = String(dirPath); if (!fullPath.endsWith("/")) fullPath += "/"; fullPath += filename; File file = SD.open(fullPath.c_str(), FILE_WRITE); if (!file) { LOG_ERROR("Failed to open file: %s", fullPath.c_str()); return; } file.print(data); file.close(); LOG_INFO("File %s saved successfully.\n", fullPath.c_str()); } // Saves Relay Durations from RAM, into a file void saveRelayTimings() { StaticJsonDocument<512> doc; // Adjust size if needed // Populate the JSON object with relay durations for (uint8_t i = 0; i < 16; i++) { String key = String("b") + (i + 1); doc[key] = relayDurations[i]; } char buffer[512]; size_t len = serializeJson(doc, buffer, sizeof(buffer)); if (len == 0) { LOG_ERROR("Failed to serialize JSON."); return; } const char * path = "/settings"; const char * filename = "relayTimings.json"; saveFileToSD(path, filename, buffer); } // Loads Relay Durations from file into RAM (called during boot) void loadRelayTimings() { if (!SD.begin(SD_CS)) { LOG_ERROR("SD Card not initialized. Using default relay timings."); return; } File file = SD.open("/settings/relayTimings.json", FILE_READ); if (!file) { LOG_ERROR("Settings file not found on SD. Using default relay timings."); return; } // Parse the JSON file StaticJsonDocument<512> doc; // Adjust size if needed DeserializationError error = deserializeJson(doc, file); file.close(); if (error) { LOG_ERROR("Failed to parse settings from SD. Using default relay timings."); return; } // Populate relayDurations array for (uint8_t i = 0; i < 16; i++) { String key = String("b") + (i + 1); if (doc.containsKey(key)) { relayDurations[i] = doc[key].as(); LOG_DEBUG("Loaded relay %d duration: %d ms\n", i + 1, relayDurations[i]); } } } // Function to sync time with NTP server and update RTC void syncTimeWithNTP() { // Connect to Wi-Fi if not already connected if (WiFi.status() != WL_CONNECTED) { LOG_DEBUG("Connecting to Wi-Fi..."); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); LOG_DEBUG("."); } LOG_DEBUG("\nWi-Fi connected!"); } // Configure NTP configTime(gmtOffset_sec, daylightOffset_sec, ntpServer); // Sync time from NTP server LOG_DEBUG("Syncing time with NTP server..."); struct tm timeInfo; if (!getLocalTime(&timeInfo)) { LOG_DEBUG("Failed to obtain time from NTP server!"); return; } // Update RTC with synchronized time rtc.adjust(DateTime(timeInfo.tm_year + 1900, timeInfo.tm_mon + 1, timeInfo.tm_mday, timeInfo.tm_hour, timeInfo.tm_min, timeInfo.tm_sec)); // Log synchronized time LOG_INFO("Time synced with NTP server."); LOG_DEBUG("Synced time: %02d:%02d:%02d, %02d/%02d/%04d", timeInfo.tm_hour, timeInfo.tm_min, timeInfo.tm_sec, timeInfo.tm_mday, timeInfo.tm_mon + 1, timeInfo.tm_year + 1900); } // Call this function with the Firebase URL and desired local filename bool downloadFileToSD(const String& url, const String& directory, const String& filename) { LOG_INFO("HTTP Starting download..."); HTTPClient http; http.begin(url); int httpCode = http.GET(); if (httpCode != HTTP_CODE_OK) { LOG_ERROR("(HTTP) GET failed, error: %s\n", http.errorToString(httpCode).c_str()); http.end(); return false; } if (!SD.begin(SD_CS)) { LOG_ERROR("SD Card init failed!"); http.end(); return false; } // Ensure the directory ends with '/' String dirPath = directory; if (!dirPath.endsWith("/")) dirPath += "/"; // Create directory if it doesn't exist SD.mkdir(dirPath.c_str()); String fullPath = dirPath + filename; File file = SD.open(fullPath.c_str(), FILE_WRITE); if (!file) { LOG_ERROR("SD Failed to open file for writing: %s", fullPath.c_str()); http.end(); return false; } WiFiClient* stream = http.getStreamPtr(); uint8_t buffer[1024]; int bytesRead; while (http.connected() && (bytesRead = stream->readBytes(buffer, sizeof(buffer))) > 0) { file.write(buffer, bytesRead); } file.close(); http.end(); LOG_INFO("HTTP Download complete, file saved to: %s", fullPath.c_str()); return true; } // Returns the list of melodies (the filenames) currently inside the SD Card. String listFilesAsJson(const char* dirPath) { if (!SD.begin(SD_CS)) { LOG_ERROR("SD init failed"); return "{}"; } File dir = SD.open(dirPath); if (!dir || !dir.isDirectory()) { LOG_ERROR("Directory not found"); return "{}"; } DynamicJsonDocument doc(1024); // Adjust size if needed JsonArray fileList = doc.createNestedArray("files"); File file = dir.openNextFile(); while (file) { if (!file.isDirectory()) { fileList.add(file.name()); } file = dir.openNextFile(); } String json; serializeJson(doc, json); return json; } // Prints the Steps of a Melody from a file using its filename void printMelodyFile(const String& filename) { if (!SD.begin(SD_CS)) { LOG_ERROR("SD Card not initialized."); return; } File file = SD.open("/melodies/" + filename, FILE_READ); if (!file) { Serial.println("Failed to open Melody file for reading"); return; } Serial.printf("---- Contents of %s ----\n", filename.c_str()); uint16_t step; int index = 0; while (file.available() >= 2) { uint8_t low = file.read(); uint8_t high = file.read(); step = (high << 8) | low; Serial.printf("Step %5d: 0x%04X (%d)\n", index++, step, step); } file.close(); Serial.println("---- End of File ----"); } // Prints the contents of a Text file using its filename void printFileAsText(const String& path, const String& filename) { if (!SD.begin(SD_CS)) { LOG_ERROR("SD Card not initialized."); return; } String fullPath = path; if (!fullPath.endsWith("/")) fullPath += "/"; fullPath += filename; File file = SD.open(fullPath, FILE_READ); if (!file) { Serial.println("Failed to open file for reading"); return; } Serial.printf("---- Contents of %s ----\n", filename.c_str()); while (file.available()) { String line = file.readStringUntil('\n'); Serial.println(line); } file.close(); Serial.println("---- End of File ----"); } // Downloads a new melody from HTTP void addMelody(JsonVariant doc) { LOG_INFO("Trying Saving..."); const char* url = doc["url"]; const char* filename = doc["filename"]; downloadFileToSD(url, "/melodies", filename); } // Checks the onboard SD Card for new firmware void checkFirmwareUpdate() { if (!SD.begin(SD_CS)) { Serial.println("SD init failed"); return; } File updateBin = SD.open("/firmware/update.bin"); if (!updateBin) { Serial.println("No update.bin found"); return; } size_t updateSize = updateBin.size(); if (updateSize == 0) { Serial.println("Empty update file"); updateBin.close(); return; } Serial.println("Starting firmware update..."); if (Update.begin(updateSize)) { size_t written = Update.writeStream(updateBin); if (written == updateSize) { Serial.println("Update written successfully"); } else { Serial.printf("Written only %d/%d bytes\n", written, updateSize); } if (Update.end()) { Serial.println("Update finished!"); if (Update.isFinished()) { Serial.println("Update complete. Rebooting..."); updateBin.close(); SD.remove("/firmware/update.bin"); // optional cleanup ESP.restart(); } else { Serial.println("Update not complete"); } } else { Serial.printf("Update error: %s\n", Update.errorString()); } } else { Serial.println("Not enough space to begin update"); } updateBin.close(); } // UNSUSED FUNCTIONS. // void startConfigPortal() { // WiFi.mode(WIFI_AP); // WiFi.softAP("Device_Config", "12345678"); // Serial.println("AP mode started. Connect to 'Device_Config'."); // // Serve the configuration page // server.on("/", HTTP_GET, []() { // server.send(200, "text/html", generateConfigPageHTML()); // }); // // Handle form submission // server.on("/save", HTTP_POST, []() { // ssid = server.arg("ssid"); // password = server.arg("password"); // mqttHost.fromString(server.arg("mqttHost")); // mqttUser = server.arg("mqttUser"); // mqttPassword = server.arg("mqttPassword"); // saveSettings(); // Save new settings to SPIFFS // server.send(200, "text/plain", "Settings saved! Rebooting..."); // delay(1000); // ESP.restart(); // }); // server.begin(); // } // // Save settings to SPIFFS // void saveSettings() { // StaticJsonDocument<512> doc; // doc["ssid"] = ssid; // doc["password"] = password; // doc["mqttHost"] = mqttHost.toString(); // doc["mqttUser"] = mqttUser; // doc["mqttPassword"] = mqttPassword; // File configFile = SPIFFS.open(CONFIG_FILE, "w"); // if (!configFile) { // Serial.println("Failed to open config file for writing."); // return; // } // serializeJson(doc, configFile); // configFile.close(); // Serial.println("Settings saved to SPIFFS."); // } // // Load settings from SPIFFS // void loadSettings() { // if (!SPIFFS.exists(CONFIG_FILE)) { // Serial.println("Config file not found. Using defaults."); // return; // } // File configFile = SPIFFS.open(CONFIG_FILE, "r"); // if (!configFile) { // Serial.println("Failed to open config file."); // return; // } // StaticJsonDocument<512> doc; // DeserializationError error = deserializeJson(doc, configFile); // if (error) { // Serial.println("Failed to parse config file."); // return; // } // ssid = doc["ssid"].as(); // password = doc["password"].as(); // mqttHost.fromString(doc["mqttHost"].as()); // mqttUser = doc["mqttUser"].as(); // mqttPassword = doc["mqttPassword"].as(); // configFile.close(); // Serial.println("Settings loaded from SPIFFS."); // } // // Generate HTML page for configuration // String generateConfigPageHTML() { // String page = R"rawliteral( // // // //

Device Configuration

//
// WiFi SSID:
// WiFi Password:
// MQTT Host:
// MQTT Username:
// MQTT Password:
// //
// // // )rawliteral"; // return page; // }