I have a little problem, when uploading the OTA firmware it says successful but the ESP doesn't restart, where is the problem?
#ifdef ESP8266
#include <ESP8266WiFi.h>
#include <ESPAsyncWebServer.h>
#include <FS.h>
#else
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <SPIFFS.h>
#include <Update.h>
#endif
#include <WiFiUdp.h>
#include <Wire.h>
#include <MPU6050.h>
#include <ArduinoJson.h>
// Platform-specific configurations
#ifdef ESP8266
#define BOARD_TYPE "ESP8266"
#define LED_BUILTIN 2 // ESP8266 built-in LED pin
#else
#define BOARD_TYPE "ESP32"
#define LED_BUILTIN 2 // ESP32 built-in LED pin
#endif
// WiFi Manager settings
char apSSID[33] = "Gyro_Control"; // 32 characters + null terminator
char apPassword[65] = "12345678"; // 64 characters + null terminator
String routerSSID = "";
String routerPassword = "";
char expectedMAC[18] = ""; // MAC address format XX:XX:XX:XX:XX:XX
// UDP Settings
const char* serverIP = "192.168.4.1";
unsigned int serverPort = 4210;
// System variables
unsigned long lastWiFiCheckMillis = 0;
const long wifiCheckInterval = 30000;
// Add these function declarations at the top of your file
void handleUpdate(AsyncWebServerRequest *request);
void handleDoUpdate(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final);
void printProgress(size_t prg, size_t sz);
// Add these variables for update progress tracking
size_t contentLength = 0;
size_t currentLength = 0;
bool updateStarted = false;
#ifdef ESP8266
AsyncWebServer server(80);
#else
AsyncWebServer server(80);
#endif
WiFiUDP udp;
MPU6050 mpu;
int failedWiFiConnections = 0;
bool isStaConnected = false; // Track STA connection status
// WiFi Scanning variables
unsigned long scanStartTime = 0;
unsigned long lastScanRequest = 0;
const unsigned long SCAN_COOLDOWN = 10000; // 10 seconds cooldown between scans
volatile bool isScanning = false;
WiFiMode_t lastWiFiMode;
const unsigned long SCAN_TIMEOUT = 40000; // 40 seconds timeout
const unsigned long MIN_SCAN_TIME = 2000; // Minimum scan time
const unsigned long AP_MODE_TIMEOUT = 300000; // 5 menit dalam mode AP
const unsigned long WIFI_RECONNECT_INTERVAL = 180000; // Coba rekoneksi setiap 3 menit
unsigned long apModeStartTime = 0;
unsigned long lastReconnectAttempt = 0;
volatile bool needsWiFiRestore = false;
// Platform-specific SPIFFS mounting
bool mountSPIFFS() {
#ifdef ESP8266
return SPIFFS.begin();
#else
return SPIFFS.begin(true); // Format SPIFFS if mount fails on ESP32
#endif
}
void resetSettings() {
routerSSID = "";
routerPassword = "";
memset(expectedMAC, 0, sizeof(expectedMAC));
saveWiFiCredentials();
strncpy(apSSID, "Gyro_Control", sizeof(apSSID) - 1);
strncpy(apPassword, "12345678", sizeof(apPassword) - 1);
apSSID[sizeof(apSSID) - 1] = '\0';
apPassword[sizeof(apPassword) - 1] = '\0';
saveAPSettings();
Serial.println("All settings reset to default values");
}
void loadAPSettings() {
if (SPIFFS.exists("/ap_settings.txt")) {
File file = SPIFFS.open("/ap_settings.txt", "r");
if (file) {
String ssid = file.readStringUntil('\n');
String password = file.readStringUntil('\n');
ssid.trim();
password.trim();
strncpy(apSSID, ssid.c_str(), sizeof(apSSID) - 1);
strncpy(apPassword, password.c_str(), sizeof(apPassword) - 1);
apSSID[sizeof(apSSID) - 1] = '\0';
apPassword[sizeof(apPassword) - 1] = '\0';
Serial.println("AP settings loaded: " + String(apSSID));
file.close();
}
}
}
void saveAPSettings() {
File file = SPIFFS.open("/ap_settings.txt", "w");
if (file) {
file.println(apSSID);
file.println(apPassword);
file.close();
Serial.println("AP settings saved");
}
}
void loadWiFiCredentials() {
if (SPIFFS.exists("/wifi_creds.txt")) {
File file = SPIFFS.open("/wifi_creds.txt", "r");
if (file) {
routerSSID = file.readStringUntil('\n');
routerPassword = file.readStringUntil('\n');
String mac = file.readStringUntil('\n');
routerSSID.trim();
routerPassword.trim();
mac.trim();
if (mac.length() > 0) {
strncpy(expectedMAC, mac.c_str(), sizeof(expectedMAC) - 1);
expectedMAC[sizeof(expectedMAC) - 1] = '\0';
}
Serial.println("WiFi credentials loaded: " + routerSSID);
file.close();
}
}
}
void saveWiFiCredentials() {
File file = SPIFFS.open("/wifi_creds.txt", "w");
if (file) {
file.println(routerSSID);
file.println(routerPassword);
file.println(expectedMAC);
file.close();
Serial.println("WiFi credentials saved");
}
}
void startAPMode() {
// Use a common implementation that works on both platforms
if (WiFi.getMode() != WIFI_AP && WiFi.getMode() != WIFI_AP_STA) {
WiFi.mode(WIFI_AP_STA); // Always use dual mode
delay(100);
}
if(WiFi.softAP(apSSID, apPassword)) {
Serial.println("AP Created Successfully");
} else {
Serial.println("AP Creation Failed!");
// Try with default settings
if(WiFi.softAP("RC_Car_AP", "12345678")) {
Serial.println("AP Created with default settings");
strcpy(apSSID, "RC_Car_AP");
strcpy(apPassword, "12345678");
}
}
delay(500);
IPAddress myIP = WiFi.softAPIP();
Serial.println("AP Mode active");
Serial.print("AP IP address: ");
Serial.println(myIP);
// Print AP settings
Serial.print("AP SSID: ");
Serial.println(apSSID);
Serial.print("AP Password: ");
Serial.println(apPassword);
}
bool connectToWiFi() {
int attempts = 0;
const int maxAttempts = 20;
Serial.println("Connecting to WiFi while maintaining AP...");
WiFi.mode(WIFI_AP_STA); // Set to dual mode
WiFi.begin(routerSSID.c_str(), routerPassword.c_str());
while (WiFi.status() != WL_CONNECTED && attempts < maxAttempts) {
delay(1000);
Serial.print(".");
attempts++;
}
if (WiFi.status() == WL_CONNECTED) {
String actualMAC = WiFi.BSSIDstr();
Serial.println("\nConnected to WiFi");
Serial.print("MAC Address: ");
Serial.println(actualMAC);
if (expectedMAC[0] == '\0' || actualMAC.equals(expectedMAC)) {
Serial.println("Connected to correct AP");
Serial.print("STA IP Address: ");
Serial.println(WiFi.localIP());
Serial.print("AP IP Address: ");
Serial.println(WiFi.softAPIP());
// Dapatkan alamat IP gateway setelah terhubung
#ifdef ESP8266
serverIP = WiFi.gatewayIP().toString().c_str();
#else
serverIP = WiFi.gatewayIP().toString().c_str();
#endif
Serial.print("Gateway IP (serverIP): ");
Serial.println(serverIP);
isStaConnected = true;
return true;
} else {
Serial.println("MAC address mismatch!");
WiFi.disconnect(false); // Disconnect STA but keep AP running
isStaConnected = false;
return false;
}
}
Serial.println("\nFailed to connect to WiFi");
failedWiFiConnections++;
isStaConnected = false;
return false;
}
void scanNetworks(AsyncWebServerRequest *request) {
Serial.println("\n=== Starting Enhanced WiFi Scan ===");
if (isScanning) {
Serial.println("Error: Scan already in progress");
request->send(429, "application/json", "{\"status\":\"busy\",\"message\":\"A scan is already in progress\"}");
return;
}
lastWiFiMode = WiFi.getMode();
Serial.printf("Current WiFi Mode: %d\n", lastWiFiMode);
if (WiFi.status() == WL_CONNECTED) {
Serial.println("Disconnecting from current network for scan...");
WiFi.disconnect(false); // false = don't erase saved credentials
delay(100);
}
if (lastWiFiMode == WIFI_AP) {
Serial.println("Switching to AP_STA mode for scanning");
WiFi.mode(WIFI_AP_STA);
} else if (lastWiFiMode != WIFI_STA && lastWiFiMode != WIFI_AP_STA) {
Serial.println("Switching to STA mode for scanning");
WiFi.mode(WIFI_STA);
}
delay(100); // Allow mode change to settle
WiFi.scanDelete();
Serial.println("Previous scan results cleared");
int scanResult = WiFi.scanNetworks(true, true); // async=true, show_hidden=true
if (scanResult == WIFI_SCAN_FAILED) {
Serial.println("Failed to initiate scan!");
restoreWiFiState();
request->send(500, "application/json", "{\"status\":\"error\",\"message\":\"Failed to start scan\"}");
return;
}
isScanning = true;
scanStartTime = millis();
Serial.println("Scan initiated successfully");
request->send(200, "application/json", "{\"status\":\"scanning\",\"message\":\"Scan started\"}");
}
void getScanResults(AsyncWebServerRequest *request) {
Serial.println("\n=== Checking Enhanced Scan Results ===");
if (!isScanning) {
Serial.println("Error: No scan in progress");
request->send(400, "application/json", "{\"status\":\"error\",\"message\":\"No scan in progress\"}");
return;
}
int scanResult = WiFi.scanComplete();
unsigned long elapsedTime = millis() - scanStartTime;
// Ensure minimum scan time
if (elapsedTime < MIN_SCAN_TIME) {
Serial.println("Waiting for minimum scan time...");
request->send(202, "application/json", "{\"status\":\"scanning\",\"message\":\"Scan in progress\"}");
return;
}
// Handle timeout
if (elapsedTime > SCAN_TIMEOUT) {
Serial.println("Scan timed out!");
isScanning = false;
WiFi.scanDelete();
restoreWiFiState();
request->send(504, "application/json", "{\"status\":\"timeout\",\"message\":\"Scan timed out\"}");
return;
}
// Process results
if (scanResult == WIFI_SCAN_FAILED) {
Serial.println("Scan failed!");
isScanning = false;
restoreWiFiState();
request->send(500, "application/json", "{\"status\":\"error\",\"message\":\"Scan failed\"}");
return;
}
if (scanResult == WIFI_SCAN_RUNNING) {
Serial.println("Scan still in progress...");
request->send(202, "application/json", "{\"status\":\"scanning\",\"message\":\"Scan in progress\"}");
return;
}
// Handle zero networks found
if (scanResult == 0) {
Serial.println("No networks found - initiating retry...");
// Optional: Implement retry logic here
WiFi.scanDelete();
int retryScan = WiFi.scanNetworks(true, true); // async=true, show_hidden=true
if (retryScan == WIFI_SCAN_FAILED) {
isScanning = false;
restoreWiFiState();
request->send(200, "application/json", "[]"); // Return empty array instead of error
return;
}
request->send(202, "application/json", "{\"status\":\"scanning\",\"message\":\"Retrying scan\"}");
return;
}
// Process found networks
if (scanResult > 0) {
Serial.printf("Found %d networks\n", scanResult);
DynamicJsonDocument doc(4096);
JsonArray networks = doc.to<JsonArray>();
for (int i = 0; i < scanResult; i++) {
JsonObject network = networks.createNestedObject();
network["ssid"] = WiFi.SSID(i);
network["bssid"] = WiFi.BSSIDstr(i);
network["rssi"] = WiFi.RSSI(i);
network["channel"] = WiFi.channel(i);
network["encryption"] = WiFi.encryptionType(i);
network["hidden"] = WiFi.SSID(i).length() == 0;
Serial.printf("Network %d: SSID: %s, RSSI: %d\n",
i, WiFi.SSID(i).c_str(), WiFi.RSSI(i));
}
String response;
serializeJson(doc, response);
WiFi.scanDelete();
isScanning = false;
restoreWiFiState();
request->send(200, "application/json", response);
}
}
void restoreWiFiState() {
Serial.println("Restoring WiFi state...");
if (lastWiFiMode == WIFI_AP) {
WiFi.mode(WIFI_AP);
} else if (lastWiFiMode == WIFI_STA && routerSSID.length() > 0) {
WiFi.mode(WIFI_STA);
WiFi.begin(routerSSID.c_str(), routerPassword.c_str());
}
}
// Tambahkan fungsi untuk memeriksa status WiFi
void printWiFiStatus() {
Serial.println("\n=== WiFi Status ===");
Serial.printf("Mode: %d\n", WiFi.getMode());
Serial.printf("Status: %d\n", WiFi.status());
if (WiFi.getMode() == WIFI_STA || WiFi.getMode() == WIFI_AP_STA) {
Serial.printf("Connected to: %s\n", WiFi.SSID().c_str());
Serial.printf("IP Address: %s\n", WiFi.localIP().toString().c_str());
}
if (WiFi.getMode() == WIFI_AP || WiFi.getMode() == WIFI_AP_STA) {
Serial.printf("AP SSID: %s\n", apSSID);
Serial.printf("AP IP: %s\n", WiFi.softAPIP().toString().c_str());
}
}
void setupServerRoutes() {
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(SPIFFS, "/index.html", "text/html");
});
server.on("/assets/css/foundation.css", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(SPIFFS, "/assets/css/foundation.css", "text/css");
});
// Update route untuk scan
server.on("/scan-wifi", HTTP_GET, scanNetworks);
// Tambah route baru untuk hasil scan
server.on("/scan-results", HTTP_GET, getScanResults);
server.on("/setwifi", HTTP_POST, [](AsyncWebServerRequest *request) {
String newSSID = request->hasParam("ssid", true) ? request->getParam("ssid", true)->value() : "";
String newPassword = request->hasParam("password", true) ? request->getParam("password", true)->value() : "";
String newMAC = request->hasParam("mac", true) ? request->getParam("mac", true)->value() : "";
if (newSSID.length() > 0 && newPassword.length() >= 8) {
routerSSID = newSSID;
routerPassword = newPassword;
if (newMAC.length() == 17) {
strncpy(expectedMAC, newMAC.c_str(), sizeof(expectedMAC) - 1);
expectedMAC[sizeof(expectedMAC) - 1] = '\0';
}
saveWiFiCredentials();
request->send(200, "text/plain", "WiFi settings updated. Restarting...");
delay(1000);
ESP.restart();
} else {
request->send(400, "text/plain", "Invalid parameters");
}
});
server.on("/setap", HTTP_POST, [](AsyncWebServerRequest *request) {
if (request->hasParam("ssid", true) && request->hasParam("password", true)) {
String newSSID = request->getParam("ssid", true)->value();
String newPassword = request->getParam("password", true)->value();
if (newSSID.length() > 0 && newPassword.length() >= 8) {
strncpy(apSSID, newSSID.c_str(), sizeof(apSSID) - 1);
strncpy(apPassword, newPassword.c_str(), sizeof(apPassword) - 1);
apSSID[sizeof(apSSID) - 1] = '\0';
apPassword[sizeof(apPassword) - 1] = '\0';
saveAPSettings();
request->send(200, "text/plain", "AP settings updated. Restarting...");
delay(1000);
ESP.restart();
} else {
request->send(400, "text/plain", "Invalid parameters");
}
} else {
request->send(400, "text/plain", "Missing parameters");
}
});
server.on("/devicestatus", HTTP_GET, [](AsyncWebServerRequest *request) {
String ssid = WiFi.SSID();
String ip = WiFi.localIP().toString();
String mac = String(expectedMAC);
String response = "{";
response += "\"ssid\":\"" + (ssid.length() > 0 ? ssid : "No WiFi") + "\",";
response += "\"ip\":\"" + (ip != "0.0.0.0" ? ip : "0.0.0.0") + "\",";
response += "\"mac\":\"" + (mac.length() > 0 ? mac : "00:00:00:00:00:00") + "\",";
response += "\"freeHeap\":\"" + String(ESP.getFreeHeap()) + "\"";
response += "}";
request->send(200, "application/json", response);
});
server.on("/reset", HTTP_POST, [](AsyncWebServerRequest *request) {
resetSettings();
request->send(200, "text/plain", "All settings reset. Restarting...");
delay(1000);
ESP.restart();
});
// Serve the HTML page
server.on("/update", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(SPIFFS, "/update.html", "text/html");
});
// Handle firmware upload
server.on("/update", HTTP_POST,
[](AsyncWebServerRequest *request) {
// Ketika update selesai, kirim respon
String updateStatus = Update.hasError() ? "Update failed!" : "Update successful!";
AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", updateStatus);
response->addHeader("Connection", "close");
request->send(response);
},
[](AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) {
handleDoUpdate(request, filename, index, data, len, final);
}
);
}
void handleDoUpdate(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) {
if(!index) {
Serial.println("Update Start");
#ifdef ESP8266
Update.runAsync(true);
contentLength = request->contentLength();
if(!Update.begin(contentLength, U_FLASH)) {
#else
contentLength = request->contentLength();
if(!Update.begin(UPDATE_SIZE_UNKNOWN)) {
#endif
Update.printError(Serial);
}
}
if(Update.write(data, len) != len) {
Update.printError(Serial);
}
currentLength += len;
printProgress(currentLength, contentLength);
if(final) {
if(!Update.end(true)) {
Update.printError(Serial);
} else {
Serial.println("\nUpdate Success!");
}
}
}
void printProgress(size_t prg, size_t sz) {
Serial.printf("Progress: %d%%\n", (prg * 100) / sz);
}
void sendUDPPacket(const char* command) {
if (isStaConnected) {
udp.beginPacket(serverIP, serverPort);
udp.write((const uint8_t*)command, strlen(command)); // Convert to uint8_t* and specify length
udp.endPacket();
}
}
void setup() {
Serial.begin(115200);
Serial.println("\n=== Starting Setup ===");
if (!mountSPIFFS()) {
Serial.println("Error mounting SPIFFS");
strcpy(apSSID, "Gyro_Control");
strcpy(apPassword, "12345678");
}
Serial.println("SPIFFS mounted successfully");
loadWiFiCredentials();
loadAPSettings();
// Set hostname dari apSSID
#ifdef ESP8266
WiFi.hostname(apSSID);
#else
WiFi.setHostname(apSSID);
#endif
// Tambahkan Serial.print untuk memverifikasi hostname
Serial.print("Hostname: ");
#ifdef ESP8266
Serial.println(WiFi.hostname());
#else
Serial.println(WiFi.getHostname());
#endif
Wire.begin();
mpu.initialize();
if (mpu.testConnection()) {
Serial.println("MPU6050 connection successful!");
} else {
Serial.println("MPU6050 connection failed!");
}
// Start AP Mode first
startAPMode();
// Then try to connect to WiFi if credentials exist
if (routerSSID.length() > 0) {
Serial.println("WiFi credentials found, attempting to connect...");
if (!connectToWiFi()) {
startAPMode();
apModeStartTime = millis(); // Catat waktu mulai mode AP
}
}
setupServerRoutes();
server.begin();
printWiFiStatus();
Serial.println("System ready!");
Serial.printf("Free heap: %d bytes\n", ESP.getFreeHeap());
}
void loop() {
unsigned long currentMillis = millis();
// Status WiFi saat ini
WiFiMode_t currentMode = WiFi.getMode();
// Jika dalam mode AP, pantau waktu
if (currentMode == WIFI_AP || currentMode == WIFI_AP_STA) {
// Jika sudah lebih dari waktu timeout AP
if (currentMillis - apModeStartTime > AP_MODE_TIMEOUT) {
// Coba kembali ke mode STA
if (routerSSID.length() > 0) {
Serial.println("AP Mode timeout. Attempting to reconnect to WiFi...");
WiFi.mode(WIFI_STA);
connectToWiFi();
}
}
}
// Coba rekoneksi berkala jika tidak terhubung
if (currentMillis - lastReconnectAttempt >= WIFI_RECONNECT_INTERVAL) {
lastReconnectAttempt = currentMillis;
if (WiFi.status() != WL_CONNECTED) {
Serial.println("Periodic WiFi reconnection attempt...");
// Jika kredensial tersedia, coba rekoneksi
if (routerSSID.length() > 0) {
// Beralih ke mode AP_STA untuk memastikan AP tetap aktif
WiFi.mode(WIFI_AP_STA);
connectToWiFi();
// Jika koneksi gagal, kembali ke mode AP
if (!isStaConnected) {
startAPMode();
apModeStartTime = currentMillis; // Reset waktu mode AP
}
}
}
}
// MPU6050 data handling
static unsigned long lastSendTime = 0;
const unsigned long sendInterval = 20;
if (isStaConnected && currentMillis - lastSendTime >= sendInterval) {
lastSendTime = currentMillis;
int16_t ax, ay, az;
mpu.getAcceleration(&ax, &ay, &az);
char command[32];
snprintf(command, sizeof(command), "acc,%d,%d,%d", ax, ay, az);
sendUDPPacket(command);
}
}
<!doctype html>
<html class="no-js" lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Gyro WiFi</title>
<link rel="stylesheet" href="assets/css/foundation.css">
</head>
<body>
<div class="grid-container">
<div class="grid-x grid-padding-x">
<div class="large-12 text-center cell">
<div class="grid-x grid-padding-y">
<div class="large-12 cell">
<h3>Gyro WiFi</h3>
</div>
</div>
<div class="grid-x grid-padding-x">
<div class="large-12 cell">
<div class="grid-x grid-padding-x">
<div class="large-8 medium-8 center cell">
<div class="callout">
<div class="grid-x grid-margin-x">
<div class="large-6 medium-6 cell">
<div class="grid-x grid-padding-x">
<div class="large-12 medium-12 small-12 cell">
WiFi Settings
<div class="grid-x grid-margin-x">
<div class="large-12 medium-12 small-12 cell">
<input type="text" id="wifiSSID" placeholder="WiFi SSID">
</div>
<div class="large-12 medium-12 small-12 cell">
<input type="password" id="wifiPassword" placeholder="WiFi Password">
</div>
<div class="large-12 medium-12 small-12 cell">
<input type="text" id="macAddress" placeholder="Expected MAC Address">
</div>
<div class="large-12 medium-12 small-12 cell">
<button class="submit success button" onclick="setWiFi()">Set WiFi Credentials</button>
</div>
</div>
</div>
</div>
</div>
<div class="large-6 medium-6 cell">
Access Point Settings
<div class="grid-x grid-margin-x">
<div class="large-12 medium-12 small-12 cell">
<input type="text" id="apSSID" placeholder="AP SSID">
</div>
<div class="large-12 medium-12 small-12 cell">
<input type="password" id="apPassword" placeholder="AP Password">
</div>
<div class="large-12 medium-12 small-12 cell">
<button class="submit success button" onclick="setAP()">Set AP Credentials</button>
</div>
</div>
</div>
</div>
<div class="grid-x grid-padding-y">
<div class="large-12 medium-12 small-12 text-center cell">
<div class="callout callout-scan">
<h5>Scan WiFi Networks</h5>
<div id="networkList" class="network-list" style="display: none;">
<div class="scanning">Scanning for networks...</div>
</div>
<button id="scanButton" class="success button callout-button" onclick="startWiFiScan()">Scan WiFi Networks</button>
</div>
</div>
</div>
<div class="grid-x grid-padding-y">
<div class="large-12 medium-12 small-12 cell">
<div class="callout">
<div class="grid-x grid-margin-x">
<div class="large-12 medium-12 small-12 cell">
<h4>Device Status</h4>
<p>WiFi Name: <span id="wifiName"></span></p>
<p>IP Address: <span id="ipAddress"></span></p>
<p>MAC Address: <span id="currentMac"></span></p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="grid-x grid-padding-x">
<div class="large-8 medium-8 small-12 center cell">
<div class="grid-x grid-margin-x">
<div class="large-12 medium-12 small-12 cell">
<div class="callout alert">
<h4>Reset All Settings</h4>
<p>This will reset all settings (WiFi and AP) to their default values. The device will restart after resetting.</p>
<button class="alert button" onclick="resetSettings()">Reset All Settings</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="grid-container">
<div class="grid-x grid-padding-x">
<div class="large-12 cell">
<div class="grid-x grid-padding-y">
<div class="large-12 text-center cell">
<h3>Firmware Update</h3>
</div>
</div>
<div class="grid-x grid-padding-x">
<div class="large-8 medium-8 center cell">
<div class="callout">
<form id="updateForm">
<div class="grid-x grid-margin-x">
<div class="large-12 cell">
<div class="file-upload-container">
<label for="firmwareFile">
<span>Choose Firmware File (.bin) or Drag & Drop Here</span>
<input type="file" id="firmwareFile" accept=".bin" required>
</label>
<div class="selected-file" id="selectedFileName"></div>
</div>
</div>
<div class="large-12 cell">
<button type="submit" class="button success upload-button" disabled>Upload Firmware</button>
</div>
</div>
</form>
<div class="upload-progress">
<div class="progress-bar"></div>
</div>
<div class="upload-status"></div>
<!-- Placeholder for update status message -->
<div id="updateMessage" class="upload-status" style="display:none;"></div>
</div>
<div class="callout secondary">
<h5>Instructions:</h5>
<ol>
<li>Build your firmware using Arduino IDE or PlatformIO</li>
<li>Locate the .bin file in your build directory</li>
<li>Select the file using the form above or drag & drop</li>
<li>Click "Upload Firmware" and wait for the process to complete</li>
<li>The device will restart automatically after successful update</li>
</ol>
<p><strong>Warning:</strong> Do not interrupt the upload process or power off the device during update.</p>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
// File input and button elements
const fileInput = document.getElementById('firmwareFile');
const uploadButton = document.querySelector('.upload-button');
const selectedFileName = document.getElementById('selectedFileName');
const uploadForm = document.getElementById('updateForm');
const progressBar = document.querySelector('.progress-bar');
const progressDiv = document.querySelector('.upload-progress');
const statusDiv = document.querySelector('.upload-status');
// Disable button by default
uploadButton.disabled = true;
// Function to show upload status
function showStatus(message, type) {
console.log("showStatus called with message:", message, "and type:", type);
const updateMessageDiv = document.getElementById('updateMessage');
updateMessageDiv.style.display = 'block';
updateMessageDiv.textContent = message;
updateMessageDiv.className = `upload-status ${type}`;
}
// Function to update progress bar
function updateProgress(percent) {
progressDiv.style.display = 'block';
progressBar.style.width = `${percent}%`;
}
// Function to reset form
function resetForm() {
fileInput.value = '';
selectedFileName.textContent = '';
uploadButton.disabled = true;
progressDiv.style.display = 'none';
progressBar.style.width = '0%';
statusDiv.style.display = 'none';
}
// Add reconnection logic
async function waitForDeviceReboot() {
showStatus('Device is rebooting, waiting for reconnection...', '');
const startTime = Date.now();
const timeout = 60000; // 60 second timeout
while (Date.now() - startTime < timeout) {
try {
const response = await fetch('/devicestatus', {
timeout: 1000
});
if (response.ok) {
showStatus('Device successfully rebooted!', 'success');
return true;
}
} catch (e) {
console.log('Waiting for device to come back online...');
}
await new Promise(resolve => setTimeout(resolve, 2000)); // Check every 2 seconds
}
showStatus('Timeout waiting for device to reboot. Please refresh the page manually.', 'alert');
return false;
}
// File input change handler
fileInput.addEventListener('change', (e) => {
const file = e.target.files[0];
if (file) {
if (file.name.toLowerCase().endsWith('.bin')) {
selectedFileName.textContent = `Selected file: ${file.name}`;
uploadButton.disabled = false;
} else {
showStatus('Please select a valid .bin file', 'alert');
resetForm();
}
} else {
resetForm();
}
});
uploadForm.addEventListener('submit', async (e) => {
e.preventDefault();
const file = fileInput.files[0];
if (!file) {
showStatus('Please select a firmware file', 'alert');
return;
}
const formData = new FormData();
formData.append('firmware', file);
progressDiv.style.display = 'block';
showStatus('Starting upload...', '');
uploadButton.disabled = true;
try {
const response = await fetch('/update', {
method: 'POST',
body: formData
});
console.log("Response from server:", response);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.text();
console.log("Update response:", data);
if (data.includes("Update successful")) {
showStatus('Firmware uploaded successfully. Device is restarting...', 'success');
// Wait for device to reboot
setTimeout(async () => {
await waitForDeviceReboot();
}, 5000); // Give device 5 seconds to start rebooting
} else {
showStatus('Update failed: ' + data, 'alert');
uploadButton.disabled = false;
}
} catch (error) {
showStatus('Error: ' + error.message, 'alert');
console.error('Error:', error);
uploadButton.disabled = false;
}
});
// Drag and drop handlers remain the same
const dropZone = document.querySelector('.file-upload-container label');
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
dropZone.addEventListener(eventName, preventDefaults, false);
});
function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
['dragenter', 'dragover'].forEach(eventName => {
dropZone.addEventListener(eventName, highlight, false);
});
['dragleave', 'drop'].forEach(eventName => {
dropZone.addEventListener(eventName, unhighlight, false);
});
function highlight(e) {
dropZone.style.borderColor = '#2199e8';
dropZone.style.backgroundColor = '#f0f0f0';
}
function unhighlight(e) {
dropZone.style.borderColor = '#cacaca';
dropZone.style.backgroundColor = '#f8f8f8';
}
dropZone.addEventListener('drop', handleDrop, false);
function handleDrop(e) {
const dt = e.dataTransfer;
const file = dt.files[0];
if (file) {
if (file.name.toLowerCase().endsWith('.bin')) {
fileInput.files = dt.files;
selectedFileName.textContent = `Selected file: ${file.name}`;
uploadButton.disabled = false;
} else {
showStatus('Please select a valid .bin file', 'alert');
resetForm();
}
}
}
function resetSettings() {
if (confirm("Are you sure you want to reset all settings? This action cannot be undone.")) {
fetch("/reset", { method: 'POST' })
.then(response => {
if (response.ok) {
alert("All settings have been reset. The device will restart.");
} else {
alert("Failed to reset settings");
}
});
}
}
function validateSSID(ssid) {
return ssid.length >= 1;
}
function validatePassword(password) {
return password.length >= 8;
}
function validateMAC(mac) {
return /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/.test(mac);
}
function setAP() {
const ssid = document.getElementById("apSSID").value;
const password = document.getElementById("apPassword").value;
if (!validateSSID(ssid)) {
alert("AP SSID must be at least 1 character long.");
return;
}
if (!validatePassword(password)) {
alert("AP Password must be at least 8 characters long.");
return;
}
const formData = new FormData();
formData.append('ssid', ssid);
formData.append('password', password);
fetch("/setap", {
method: 'POST',
body: formData
})
.then(response => {
if (response.ok) {
alert("AP credentials updated successfully. The device will restart.");
} else {
alert("Failed to update AP credentials");
}
});
}
function setWiFi() {
const ssid = document.getElementById("wifiSSID").value;
const password = document.getElementById("wifiPassword").value;
const mac = document.getElementById("macAddress").value;
if (!validateSSID(ssid)) {
alert("WiFi SSID must be at least 1 character long.");
return;
}
if (!validatePassword(password)) {
alert("WiFi Password must be at least 8 characters long.");
return;
}
if (!validateMAC(mac)) {
alert("Please enter a valid MAC address (format: XX:XX:XX:XX:XX:XX)");
return;
}
const formData = new FormData();
formData.append('ssid', ssid);
formData.append('password', password);
formData.append('mac', mac);
fetch("/setwifi", {
method: 'POST',
body: formData
})
.then(response => {
if (response.ok) {
alert("WiFi credentials updated successfully. The device will restart.");
} else {
alert("Failed to update WiFi credentials");
}
});
}
function pollScanResults(countdownInterval) {
const networkList = document.getElementById('networkList');
const scanButton = document.getElementById('scanButton');
const pollInterval = 500; // Poll every 500ms
const maxScanTime = 40000; // 40 seconds timeout
const startTime = Date.now();
function poll() {
const elapsedTime = Date.now() - startTime;
if (elapsedTime >= maxScanTime) {
clearInterval(countdownInterval);
handleScanError('Scan timeout after 40 seconds');
startButtonCooldown();
return;
}
fetch('/scan-results')
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then(data => {
if (data.status === 'scanning' && elapsedTime < maxScanTime) {
setTimeout(poll, pollInterval);
return;
}
clearInterval(countdownInterval);
if (Array.isArray(data)) {
displayNetworks(data);
startButtonCooldown();
} else if (data.status === 'error' || data.status === 'timeout') {
handleScanError(data.message);
startButtonCooldown();
} else {
setTimeout(poll, pollInterval);
}
})
.catch(error => {
clearInterval(countdownInterval);
handleScanError('Error getting scan results');
startButtonCooldown();
console.error('Error:', error);
});
}
setTimeout(poll, pollInterval);
}
function startButtonCooldown() {
const scanButton = document.getElementById('scanButton');
scanButton.disabled = true;
let cooldown = 10; // 10 seconds cooldown
scanButton.textContent = `Scan WiFi Networks (${cooldown}s)`;
const cooldownInterval = setInterval(() => {
cooldown--;
if (cooldown >= 0) {
scanButton.textContent = `Scan WiFi Networks (${cooldown}s)`;
} else {
clearInterval(cooldownInterval);
scanButton.disabled = false;
scanButton.textContent = 'Scan WiFi Networks';
}
}, 1000);
}
function startWiFiScan() {
const networkList = document.getElementById('networkList');
const scanButton = document.getElementById('scanButton');
scanButton.disabled = true;
networkList.style.display = 'block';
networkList.innerHTML = '<div class="scanning">Scanning for networks... (40 seconds remaining)</div>';
const startTime = Date.now();
const countdownInterval = setInterval(() => {
const elapsedTime = Date.now() - startTime;
const remaining = Math.max(0, Math.ceil((40000 - elapsedTime) / 1000));
if (remaining >= 0) {
networkList.innerHTML = `<div class="scanning">Scanning for networks... (${remaining} seconds remaining)</div>`;
}
if (elapsedTime >= 40000) {
clearInterval(countdownInterval);
}
}, 1000);
fetch('/scan-wifi')
.then(response => response.json())
.then(data => {
if (data.status === 'scanning') {
pollScanResults(countdownInterval);
} else if (data.status === 'cooldown') {
clearInterval(countdownInterval);
handleCooldown(data.message);
} else {
clearInterval(countdownInterval);
handleScanError(data.message);
startButtonCooldown();
}
})
.catch(error => {
clearInterval(countdownInterval);
handleScanError('Failed to start scan');
startButtonCooldown();
console.error('Error:', error);
});
}
function handleScanError(message) {
const networkList = document.getElementById('networkList');
networkList.innerHTML = `<div class="error">Error: ${message}</div>`;
}
function displayNetworks(networks) {
const networkList = document.getElementById('networkList');
if (!networks || networks.length === 0) {
networkList.innerHTML = '<div class="no-networks">No networks found</div>';
return;
}
// Sort networks by signal strength
networks.sort((a, b) => b.rssi - a.rssi);
networkList.innerHTML = '<div class="network-list-header">Available Networks:</div>';
networks.forEach(network => {
const networkDiv = document.createElement('div');
networkDiv.className = 'network-item';
// Signal strength indicator
const signalStrength = Math.abs(network.rssi);
let signalIcon = '';
if (signalStrength > 80) signalIcon = '▂';
else if (signalStrength > 70) signalIcon = '▂▃';
else if (signalStrength > 60) signalIcon = '▂▃▄';
else signalIcon = '▂▃▄▅';
const encryptionTypes = {
0: 'Open',
1: 'WEP',
2: 'WPA-PSK',
3: 'WPA2-PSK',
4: 'WPA/WPA2-PSK',
5: 'WPA2-Enterprise'
};
const securityType = encryptionTypes[network.encryption] || 'Unknown';
networkDiv.innerHTML = `
<div class="network-info">
<div class="network-name">
${network.hidden ? '<em>Hidden Network</em>' : network.ssid}
<span class="signal-strength">${signalIcon}</span>
</div>
<div class="network-details">
<span>Ch: ${network.channel}</span>
<span>Signal: ${network.rssi} dBm</span>
<span>Security: ${securityType}</span>
</div>
<div class="network-mac">${network.bssid}</div>
</div>
`;
networkDiv.onclick = () => {
if (network.hidden) {
const ssid = prompt("Enter SSID for hidden network:");
if (ssid) {
selectNetwork(ssid, network.bssid);
}
} else {
selectNetwork(network.ssid, network.bssid);
}
};
networkList.appendChild(networkDiv);
});
}
function selectNetwork(ssid, bssid) {
document.getElementById('wifiSSID').value = ssid;
document.getElementById('macAddress').value = bssid;
document.getElementById('networkList').style.display = 'none';
}
function getDeviceStatus() {
fetch("/devicestatus")
.then(response => response.json())
.then(status => {
document.getElementById("wifiName").innerText = status.ssid;
document.getElementById("ipAddress").innerText = status.ip;
document.getElementById("currentMac").innerText = status.mac;
});
}
</script>
</body>
</html>