다운로드시 sd 1bit, web연결 core변경

This commit is contained in:
2026-01-16 14:53:42 +00:00
parent 9ee55fb2c5
commit fd1696ffb2

View File

@@ -3368,6 +3368,7 @@ void setup() {
// WebSocket 시작
webSocket.begin();
webSocket.onEvent(webSocketEvent);
webSocket.enableHeartbeat(15000, 3000, 2); // ping 15초, pong 3초, 재시도 2회
// ★★★ 웹 서버 라우팅 (중요!) ★★★
server.on("/", HTTP_GET, []() {
@@ -3399,27 +3400,236 @@ void setup() {
});
server.on("/download", HTTP_GET, []() {
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 {
if (!server.hasArg("file")) {
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();
@@ -3441,7 +3651,7 @@ void setup() {
xTaskCreatePinnedToCore(serialRxTask, "SERIAL_RX", 6144, NULL, 5, &serialRxTaskHandle, 0);
xTaskCreatePinnedToCore(serial2RxTask, "SERIAL2_RX", 6144, NULL, 5, &serial2RxTaskHandle, 0); // ⭐ Serial2
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);
if (timeSyncStatus.rtcAvailable) {