448 lines
12 KiB
C++
448 lines
12 KiB
C++
#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<uint16_t>();
|
|
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<uint16_t>();
|
|
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<String>();
|
|
// password = doc["password"].as<String>();
|
|
// mqttHost.fromString(doc["mqttHost"].as<String>());
|
|
// mqttUser = doc["mqttUser"].as<String>();
|
|
// mqttPassword = doc["mqttPassword"].as<String>();
|
|
|
|
// configFile.close();
|
|
// Serial.println("Settings loaded from SPIFFS.");
|
|
// }
|
|
|
|
// // Generate HTML page for configuration
|
|
// String generateConfigPageHTML() {
|
|
// String page = R"rawliteral(
|
|
// <!DOCTYPE html>
|
|
// <html>
|
|
// <body>
|
|
// <h2>Device Configuration</h2>
|
|
// <form action="/save" method="POST">
|
|
// WiFi SSID: <input type="text" name="ssid" value=")rawliteral" +
|
|
// ssid + R"rawliteral("><br>
|
|
// WiFi Password: <input type="password" name="password" value=")rawliteral" +
|
|
// password + R"rawliteral("><br>
|
|
// MQTT Host: <input type="text" name="mqttHost" value=")rawliteral" +
|
|
// mqttHost.toString() + R"rawliteral("><br>
|
|
// MQTT Username: <input type="text" name="mqttUser" value=")rawliteral" +
|
|
// mqttUser + R"rawliteral("><br>
|
|
// MQTT Password: <input type="password" name="mqttPassword" value=")rawliteral" +
|
|
// mqttPassword + R"rawliteral("><br>
|
|
// <input type="submit" value="Save">
|
|
// </form>
|
|
// </body>
|
|
// </html>
|
|
// )rawliteral";
|
|
// return page;
|
|
// }
|