다운로드시 sd 1bit, web연결 core변경
This commit is contained in:
@@ -3368,6 +3368,7 @@ void setup() {
|
|||||||
// WebSocket 시작
|
// WebSocket 시작
|
||||||
webSocket.begin();
|
webSocket.begin();
|
||||||
webSocket.onEvent(webSocketEvent);
|
webSocket.onEvent(webSocketEvent);
|
||||||
|
webSocket.enableHeartbeat(15000, 3000, 2); // ping 15초, pong 3초, 재시도 2회
|
||||||
|
|
||||||
// ★★★ 웹 서버 라우팅 (중요!) ★★★
|
// ★★★ 웹 서버 라우팅 (중요!) ★★★
|
||||||
server.on("/", HTTP_GET, []() {
|
server.on("/", HTTP_GET, []() {
|
||||||
@@ -3399,27 +3400,236 @@ void setup() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.on("/download", HTTP_GET, []() {
|
server.on("/download", HTTP_GET, []() {
|
||||||
if (server.hasArg("file")) {
|
if (!server.hasArg("file")) {
|
||||||
String filename = "/" + server.arg("file");
|
|
||||||
|
|
||||||
if (SD_MMC.exists(filename)) {
|
|
||||||
File file = SD_MMC.open(filename, FILE_READ);
|
|
||||||
if (file) {
|
|
||||||
String displayName = server.arg("file");
|
|
||||||
server.sendHeader("Content-Disposition",
|
|
||||||
"attachment; filename=\"" + displayName + "\"");
|
|
||||||
server.sendHeader("Content-Type", "application/octet-stream");
|
|
||||||
server.streamFile(file, "application/octet-stream");
|
|
||||||
file.close();
|
|
||||||
} else {
|
|
||||||
server.send(500, "text/plain", "Failed to open file");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
server.send(404, "text/plain", "File not found");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
server.send(400, "text/plain", "Bad request");
|
server.send(400, "text/plain", "Bad request");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String filename = "/" + server.arg("file");
|
||||||
|
|
||||||
|
Serial.println("\n========================================");
|
||||||
|
Serial.println("📥 다운로드 준비");
|
||||||
|
Serial.println("========================================");
|
||||||
|
|
||||||
|
// ⭐ 1단계: 모든 로깅 완전 중지
|
||||||
|
bool wasLogging = loggingEnabled;
|
||||||
|
if (wasLogging) {
|
||||||
|
loggingEnabled = false;
|
||||||
|
delay(200);
|
||||||
|
Serial.println("⏸ 모든 로깅 중지");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ⭐ 2단계: 모든 SD 관련 Task 중단
|
||||||
|
if (sdWriteTaskHandle != NULL) {
|
||||||
|
vTaskSuspend(sdWriteTaskHandle);
|
||||||
|
delay(100);
|
||||||
|
Serial.println("⏸ SD 쓰기 Task 중단");
|
||||||
|
}
|
||||||
|
if (webTaskHandle != NULL) {
|
||||||
|
vTaskSuspend(webTaskHandle);
|
||||||
|
delay(50);
|
||||||
|
Serial.println("⏸ 웹 업데이트 Task 중단");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ⭐ 3단계: SD Mutex 획득
|
||||||
|
if (xSemaphoreTake(sdMutex, pdMS_TO_TICKS(10000)) != pdTRUE) {
|
||||||
|
if (webTaskHandle != NULL) vTaskResume(webTaskHandle);
|
||||||
|
if (sdWriteTaskHandle != NULL) vTaskResume(sdWriteTaskHandle);
|
||||||
|
if (wasLogging) loggingEnabled = true;
|
||||||
|
server.send(503, "text/plain", "SD card busy");
|
||||||
|
Serial.println("✗ SD Mutex 획득 실패");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ⭐ 4단계: SD 카드 재마운트 (1-bit 모드로)
|
||||||
|
Serial.println("🔄 SD 카드 재마운트 중...");
|
||||||
|
SD_MMC.end();
|
||||||
|
delay(200);
|
||||||
|
|
||||||
|
// 1-bit 모드로 재시작 (더 안정적)
|
||||||
|
if (!SD_MMC.setPins(SDIO_CLK, SDIO_CMD, SDIO_D0)) {
|
||||||
|
Serial.println("✗ SD_MMC.setPins() 실패");
|
||||||
|
xSemaphoreGive(sdMutex);
|
||||||
|
if (webTaskHandle != NULL) vTaskResume(webTaskHandle);
|
||||||
|
if (sdWriteTaskHandle != NULL) vTaskResume(sdWriteTaskHandle);
|
||||||
|
if (wasLogging) loggingEnabled = true;
|
||||||
|
server.send(500, "text/plain", "SD remount failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SD_MMC.begin("/sdcard", true)) { // true = 1-bit mode
|
||||||
|
Serial.println("✗ SD 카드 1-bit 재마운트 실패");
|
||||||
|
xSemaphoreGive(sdMutex);
|
||||||
|
if (webTaskHandle != NULL) vTaskResume(webTaskHandle);
|
||||||
|
if (sdWriteTaskHandle != NULL) vTaskResume(sdWriteTaskHandle);
|
||||||
|
if (wasLogging) loggingEnabled = true;
|
||||||
|
server.send(500, "text/plain", "SD 1-bit mode failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.println("✓ SD 카드 1-bit 모드 활성화");
|
||||||
|
delay(100);
|
||||||
|
|
||||||
|
// 파일 존재 확인
|
||||||
|
if (!SD_MMC.exists(filename)) {
|
||||||
|
Serial.println("✗ 파일 없음");
|
||||||
|
|
||||||
|
// 4-bit 모드로 복구
|
||||||
|
SD_MMC.end();
|
||||||
|
delay(100);
|
||||||
|
SD_MMC.setPins(SDIO_CLK, SDIO_CMD, SDIO_D0, SDIO_D1, SDIO_D2, SDIO_D3);
|
||||||
|
SD_MMC.begin("/sdcard", false);
|
||||||
|
|
||||||
|
xSemaphoreGive(sdMutex);
|
||||||
|
if (webTaskHandle != NULL) vTaskResume(webTaskHandle);
|
||||||
|
if (sdWriteTaskHandle != NULL) vTaskResume(sdWriteTaskHandle);
|
||||||
|
if (wasLogging) loggingEnabled = true;
|
||||||
|
server.send(404, "text/plain", "File not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
File file = SD_MMC.open(filename, FILE_READ);
|
||||||
|
if (!file) {
|
||||||
|
Serial.println("✗ 파일 열기 실패");
|
||||||
|
|
||||||
|
// 4-bit 모드로 복구
|
||||||
|
SD_MMC.end();
|
||||||
|
delay(100);
|
||||||
|
SD_MMC.setPins(SDIO_CLK, SDIO_CMD, SDIO_D0, SDIO_D1, SDIO_D2, SDIO_D3);
|
||||||
|
SD_MMC.begin("/sdcard", false);
|
||||||
|
|
||||||
|
xSemaphoreGive(sdMutex);
|
||||||
|
if (webTaskHandle != NULL) vTaskResume(webTaskHandle);
|
||||||
|
if (sdWriteTaskHandle != NULL) vTaskResume(sdWriteTaskHandle);
|
||||||
|
if (wasLogging) loggingEnabled = true;
|
||||||
|
server.send(500, "text/plain", "Failed to open file");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t fileSize = file.size();
|
||||||
|
String displayName = server.arg("file");
|
||||||
|
|
||||||
|
Serial.printf("📥 다운로드 시작: %s (%u bytes)\n", displayName.c_str(), fileSize);
|
||||||
|
|
||||||
|
// 헤더 전송
|
||||||
|
server.setContentLength(fileSize);
|
||||||
|
server.sendHeader("Content-Disposition", "attachment; filename=\"" + displayName + "\"");
|
||||||
|
server.sendHeader("Content-Type", "application/octet-stream");
|
||||||
|
server.sendHeader("Connection", "close");
|
||||||
|
server.send(200, "application/octet-stream", "");
|
||||||
|
|
||||||
|
// 512바이트 섹터 단위 전송
|
||||||
|
const size_t CHUNK_SIZE = 512;
|
||||||
|
uint8_t *buffer = (uint8_t*)heap_caps_aligned_alloc(32, CHUNK_SIZE, MALLOC_CAP_DMA);
|
||||||
|
|
||||||
|
if (!buffer) {
|
||||||
|
buffer = (uint8_t*)malloc(CHUNK_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!buffer) {
|
||||||
|
Serial.println("✗ 버퍼 할당 실패");
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
// 4-bit 모드로 복구
|
||||||
|
SD_MMC.end();
|
||||||
|
delay(100);
|
||||||
|
SD_MMC.setPins(SDIO_CLK, SDIO_CMD, SDIO_D0, SDIO_D1, SDIO_D2, SDIO_D3);
|
||||||
|
SD_MMC.begin("/sdcard", false);
|
||||||
|
|
||||||
|
xSemaphoreGive(sdMutex);
|
||||||
|
if (webTaskHandle != NULL) vTaskResume(webTaskHandle);
|
||||||
|
if (sdWriteTaskHandle != NULL) vTaskResume(sdWriteTaskHandle);
|
||||||
|
if (wasLogging) loggingEnabled = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t totalSent = 0;
|
||||||
|
bool downloadSuccess = true;
|
||||||
|
WiFiClient client = server.client();
|
||||||
|
unsigned long lastPrint = millis();
|
||||||
|
|
||||||
|
while (file.available() && totalSent < fileSize && downloadSuccess) {
|
||||||
|
// SD 읽기
|
||||||
|
size_t bytesRead = file.read(buffer, CHUNK_SIZE);
|
||||||
|
|
||||||
|
if (bytesRead == 0) {
|
||||||
|
Serial.printf("✗ 읽기 실패 (위치: %u)\n", totalSent);
|
||||||
|
downloadSuccess = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// WiFi 전송
|
||||||
|
if (!client.connected()) {
|
||||||
|
Serial.println("✗ 클라이언트 연결 끊김");
|
||||||
|
downloadSuccess = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t totalWritten = 0;
|
||||||
|
while (totalWritten < bytesRead && client.connected()) {
|
||||||
|
size_t written = client.write(buffer + totalWritten, bytesRead - totalWritten);
|
||||||
|
totalWritten += written;
|
||||||
|
if (written == 0) delay(5);
|
||||||
|
}
|
||||||
|
|
||||||
|
totalSent += bytesRead;
|
||||||
|
|
||||||
|
// 진행상황 (1초마다)
|
||||||
|
if (millis() - lastPrint > 1000) {
|
||||||
|
float percent = (totalSent * 100.0) / fileSize;
|
||||||
|
Serial.printf("📤 %.1f%% (%u/%u)\n", percent, totalSent, fileSize);
|
||||||
|
lastPrint = millis();
|
||||||
|
}
|
||||||
|
|
||||||
|
yield();
|
||||||
|
}
|
||||||
|
|
||||||
|
free(buffer);
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
Serial.println("========================================");
|
||||||
|
if (downloadSuccess && totalSent == fileSize) {
|
||||||
|
Serial.printf("✓ 완료: %u bytes (100.0%%)\n", totalSent);
|
||||||
|
} else {
|
||||||
|
Serial.printf("⚠ 불완전: %u/%u bytes (%.1f%%)\n",
|
||||||
|
totalSent, fileSize, (totalSent * 100.0) / fileSize);
|
||||||
|
}
|
||||||
|
Serial.println("========================================");
|
||||||
|
|
||||||
|
// ⭐ SD 카드를 4-bit 모드로 복구
|
||||||
|
Serial.println("🔄 SD 카드 4-bit 모드로 복구...");
|
||||||
|
SD_MMC.end();
|
||||||
|
delay(200);
|
||||||
|
|
||||||
|
if (SD_MMC.setPins(SDIO_CLK, SDIO_CMD, SDIO_D0, SDIO_D1, SDIO_D2, SDIO_D3)) {
|
||||||
|
if (SD_MMC.begin("/sdcard", false)) { // false = 4-bit mode
|
||||||
|
Serial.println("✓ SD 카드 4-bit 모드 복구 완료");
|
||||||
|
} else {
|
||||||
|
Serial.println("⚠ 4-bit 모드 복구 실패, 1-bit 유지");
|
||||||
|
SD_MMC.setPins(SDIO_CLK, SDIO_CMD, SDIO_D0);
|
||||||
|
SD_MMC.begin("/sdcard", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xSemaphoreGive(sdMutex);
|
||||||
|
|
||||||
|
// Task 재개
|
||||||
|
if (webTaskHandle != NULL) {
|
||||||
|
vTaskResume(webTaskHandle);
|
||||||
|
Serial.println("▶ 웹 업데이트 Task 재개");
|
||||||
|
}
|
||||||
|
if (sdWriteTaskHandle != NULL) {
|
||||||
|
vTaskResume(sdWriteTaskHandle);
|
||||||
|
Serial.println("▶ SD 쓰기 Task 재개");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 로깅 재개
|
||||||
|
if (wasLogging) {
|
||||||
|
loggingEnabled = true;
|
||||||
|
Serial.println("▶ 로깅 재개");
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.println("\n");
|
||||||
});
|
});
|
||||||
|
|
||||||
server.begin();
|
server.begin();
|
||||||
@@ -3441,7 +3651,7 @@ void setup() {
|
|||||||
xTaskCreatePinnedToCore(serialRxTask, "SERIAL_RX", 6144, NULL, 5, &serialRxTaskHandle, 0);
|
xTaskCreatePinnedToCore(serialRxTask, "SERIAL_RX", 6144, NULL, 5, &serialRxTaskHandle, 0);
|
||||||
xTaskCreatePinnedToCore(serial2RxTask, "SERIAL2_RX", 6144, NULL, 5, &serial2RxTaskHandle, 0); // ⭐ Serial2
|
xTaskCreatePinnedToCore(serial2RxTask, "SERIAL2_RX", 6144, NULL, 5, &serial2RxTaskHandle, 0); // ⭐ Serial2
|
||||||
xTaskCreatePinnedToCore(txTask, "TX", 4096, NULL, 3, NULL, 1);
|
xTaskCreatePinnedToCore(txTask, "TX", 4096, NULL, 3, NULL, 1);
|
||||||
xTaskCreatePinnedToCore(webUpdateTask, "WEB_UPDATE", 10384, NULL, 2, &webTaskHandle, 0); // ⭐ 10240 → 16384
|
xTaskCreatePinnedToCore(webUpdateTask, "WEB_UPDATE", 18384, NULL, 2, &webTaskHandle, 1); // ⭐ 10240 → 16384
|
||||||
xTaskCreatePinnedToCore(sdMonitorTask, "SD_MONITOR", 4096, NULL, 1, NULL, 1);
|
xTaskCreatePinnedToCore(sdMonitorTask, "SD_MONITOR", 4096, NULL, 1, NULL, 1);
|
||||||
|
|
||||||
if (timeSyncStatus.rtcAvailable) {
|
if (timeSyncStatus.rtcAvailable) {
|
||||||
|
|||||||
Reference in New Issue
Block a user