diff --git a/ESP32_CAN_Logger-a.ino b/ESP32_CAN_Logger-a.ino index 2605b0e..7de162b 100644 --- a/ESP32_CAN_Logger-a.ino +++ b/ESP32_CAN_Logger-a.ino @@ -287,7 +287,64 @@ int commentCount = 0; // Forward declarations void IRAM_ATTR canISR(); void webSocketEvent(uint8_t num, WStype_t type, uint8_t *payload, size_t length); +void resetMCP2515(); + + +void resetMCP2515() { + Serial.println("๐Ÿ”„ MCP2515 ๋ฆฌ์…‹ ์‹œ์ž‘..."); + + // 1. ๋กœ๊น… ์ค‘์ง€ (์ง„ํ–‰ ์ค‘์ด๋ฉด) + if (loggingEnabled) { + // ๋ฒ„ํผ ํ”Œ๋Ÿฌ์‹œ ๋ฐ ํŒŒ์ผ ๋‹ซ๊ธฐ + } + + // 2. CAN ํ ๋น„์šฐ๊ธฐ + CANMessage tempMsg; + while (xQueueReceive(canQueue, &tempMsg, 0) == pdTRUE) { + // ํ์—์„œ ๋ชจ๋“  ๋ฉ”์‹œ์ง€ ์ œ๊ฑฐ + } + + // 3. MCP2515 ํ•˜๋“œ ๋ฆฌ์…‹ + digitalWrite(HSPI_CS, LOW); + delay(10); + digitalWrite(HSPI_CS, HIGH); + delay(50); + + // 4. MCP2515 ์žฌ์ดˆ๊ธฐํ™” + mcp2515.reset(); + delay(100); + mcp2515.setBitrate(currentCanSpeed, MCP_8MHZ); + delay(10); + + // 5. ๋ชจ๋“œ ์„ค์ • (Normal/Loopback/Listen Only) + if (currentMcpMode == MCP_MODE_NORMAL) { + mcp2515.setNormalMode(); + } else if (currentMcpMode == MCP_MODE_LOOPBACK) { + mcp2515.setLoopbackMode(); + } else { + mcp2515.setListenOnlyMode(); + } + + // 6. ๋ฒ„ํผ ํด๋ฆฌ์–ด + struct can_frame dummyFrame; + while (mcp2515.readMessage(&dummyFrame) == MCP2515::ERROR_OK) { + // MCP2515 ์ˆ˜์‹  ๋ฒ„ํผ ๋น„์šฐ๊ธฐ + } + mcp2515.clearRXnOVRFlags(); + + // 7. ํ†ต๊ณ„ ๋ฆฌ์…‹ + totalMsgCount = 0; + lastMsgCount = 0; + msgPerSecond = 0; + + // 8. ์ตœ๊ทผ ๋ฉ”์‹œ์ง€ ํ…Œ์ด๋ธ” ํด๋ฆฌ์–ด + for (int i = 0; i < RECENT_MSG_COUNT; i++) { + recentData[i].count = 0; + } + + Serial.println("โœ… MCP2515 ๋ฆฌ์…‹ ์™„๋ฃŒ!"); +} // ======================================== // PSRAM ์ดˆ๊ธฐํ™” ํ•จ์ˆ˜ // ======================================== @@ -914,12 +971,15 @@ void sdWriteTask(void *parameter) { // CAN ๋กœ๊น… if (loggingEnabled && sdCardReady) { - if (xSemaphoreTake(sdMutex, pdMS_TO_TICKS(50)) == pdTRUE) { + // โญโญโญ ๋ฎคํ…์Šค ํƒ€์ž„์•„์›ƒ 1ms๋กœ ๊ฐ์†Œ (๋ธ”๋กœํ‚น ๋ฐฉ์ง€) + if (xSemaphoreTake(sdMutex, pdMS_TO_TICKS(1)) == pdTRUE) { if (canLogFormatCSV) { char csvLine[128]; uint64_t relativeTime = canMsg.timestamp_us - canLogStartTime; char dataStr[32]; int dataLen = 0; + static uint32_t csvReopenCounter = 0; + for (int i = 0; i < canMsg.dlc; i++) { dataLen += sprintf(&dataStr[dataLen], "%02X", canMsg.data[i]); if (i < canMsg.dlc - 1) dataStr[dataLen++] = ' '; @@ -931,23 +991,40 @@ void sdWriteTask(void *parameter) { relativeTime, canMsg.id, canMsg.dlc, dataStr); if (logFile) { - logFile.write((uint8_t*)csvLine, lineLen); + size_t written = logFile.write((uint8_t*)csvLine, lineLen); currentFileSize += lineLen; static int csvFlushCounter = 0; - if (++csvFlushCounter >= 20) { // 50 โ†’ 20์œผ๋กœ ๋” ์ž์ฃผ ํ”Œ๋Ÿฌ์‹œ + if (++csvFlushCounter >= 50) { // โญ 20 โ†’ 50 (๋„ˆ๋ฌด ์ž์ฃผ ํ”Œ๋Ÿฌ์‹œํ•˜๋ฉด ๋А๋ฆผ) logFile.flush(); csvFlushCounter = 0; } + + // โญโญโญ 500๊ฐœ๋งˆ๋‹ค ํŒŒ์ผ ์žฌ์˜คํ”ˆ (ํ•ต์‹ฌ!) + if (++csvReopenCounter >= 500) { + logFile.close(); + logFile = SD.open(currentFilename, FILE_APPEND); + if (logFile) { + Serial.printf("โœ“ CSV ํŒŒ์ผ ์žฌ์˜คํ”ˆ: %s (%lu bytes)\n", currentFilename, currentFileSize); + } else { + Serial.println("โœ— CSV ํŒŒ์ผ ์žฌ์˜คํ”ˆ ์‹คํŒจ!"); + loggingEnabled = false; + } + csvReopenCounter = 0; + } + } } else { // BIN ํ˜•์‹ + static uint32_t binMsgCounter = 0; + static uint32_t binReopenCounter = 0; + // โญโญโญ 1๋‹จ๊ณ„: ๋ฒ„ํผ ๊ฐ€๋“ ์ฐผ์œผ๋ฉด ๋จผ์ € ํ”Œ๋Ÿฌ์‹œ if (bufferIndex + sizeof(CANMessage) > FILE_BUFFER_SIZE) { if (logFile) { size_t written = logFile.write(fileBuffer, bufferIndex); logFile.flush(); - Serial.printf("โœ“ BIN ๋ฒ„ํผ ํ”Œ๋Ÿฌ์‹œ: %d bytes written\n", written); + Serial.printf("โœ“ BIN ๋ฒ„ํผ ํ”Œ๋Ÿฌ์‹œ (FULL): %d bytes\n", written); bufferIndex = 0; } } @@ -956,6 +1033,39 @@ void sdWriteTask(void *parameter) { memcpy(&fileBuffer[bufferIndex], &canMsg, sizeof(CANMessage)); bufferIndex += sizeof(CANMessage); currentFileSize += sizeof(CANMessage); + binMsgCounter++; + binReopenCounter++; + + // โญโญโญ 3๋‹จ๊ณ„: 100๊ฐœ ๋ฉ”์‹œ์ง€๋งˆ๋‹ค ์ฃผ๊ธฐ์  ํ”Œ๋Ÿฌ์‹œ + if (binMsgCounter % 100 == 0) { + if (logFile && bufferIndex > 0) { + size_t written = logFile.write(fileBuffer, bufferIndex); + logFile.flush(); + Serial.printf("โœ“ BIN ์ฃผ๊ธฐ ํ”Œ๋Ÿฌ์‹œ: %d bytes (๋ฉ”์‹œ์ง€: %d)\n", written, binMsgCounter); + bufferIndex = 0; + } + } + + // โญโญโญ 4๋‹จ๊ณ„: 500๊ฐœ๋งˆ๋‹ค ํŒŒ์ผ ์žฌ์˜คํ”ˆ (ํ•ต์‹ฌ!) + if (binReopenCounter >= 500) { + // ๋ฒ„ํผ ๋จผ์ € ํ”Œ๋Ÿฌ์‹œ + if (logFile && bufferIndex > 0) { + logFile.write(fileBuffer, bufferIndex); + logFile.flush(); + bufferIndex = 0; + } + + // ํŒŒ์ผ ๋‹ซ๊ณ  ๋‹ค์‹œ ์—ด๊ธฐ + logFile.close(); + logFile = SD.open(currentFilename, FILE_APPEND); + if (logFile) { + Serial.printf("โœ“ BIN ํŒŒ์ผ ์žฌ์˜คํ”ˆ: %s (%lu bytes)\n", currentFilename, currentFileSize); + } else { + Serial.println("โœ— BIN ํŒŒ์ผ ์žฌ์˜คํ”ˆ ์‹คํŒจ!"); + loggingEnabled = false; + } + binReopenCounter = 0; + } } xSemaphoreGive(sdMutex); } @@ -1308,9 +1418,14 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t *payload, size_t length) loggingEnabled = true; bufferIndex = 0; currentFileSize = logFile.size(); - Serial.printf("โœ“ ๋กœ๊น… ํŒŒ์ผ ์—ด๋ฆผ (APPEND): %s\n", currentFilename); + Serial.printf("โœ… ๋กœ๊น… ์‹œ์ž‘!\n"); + Serial.printf(" ํŒŒ์ผ: %s\n", currentFilename); + Serial.printf(" ํ˜•์‹: %s\n", canLogFormatCSV ? "CSV" : "BIN"); + Serial.printf(" ์ดˆ๊ธฐ ํฌ๊ธฐ: %lu bytes\n", currentFileSize); + Serial.printf(" sdCardReady: %d\n", sdCardReady); } else { Serial.println("โœ— APPEND ๋ชจ๋“œ๋กœ ํŒŒ์ผ ์—ด๊ธฐ ์‹คํŒจ"); + Serial.printf(" ํŒŒ์ผ๋ช…: %s\n", currentFilename); } } else { Serial.println("โœ— ํŒŒ์ผ ์ƒ์„ฑ ์‹คํŒจ"); @@ -1322,18 +1437,35 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t *payload, size_t length) } else if (strcmp(cmd, "stopLogging") == 0) { if (loggingEnabled) { - if (xSemaphoreTake(sdMutex, pdMS_TO_TICKS(1000)) == pdTRUE) { + Serial.println("๐Ÿ›‘ ๋กœ๊น… ์ค‘์ง€ ์š”์ฒญ..."); + + if (xSemaphoreTake(sdMutex, pdMS_TO_TICKS(2000)) == pdTRUE) { + // โญโญโญ BIN ํ˜•์‹: ๋ฒ„ํผ์— ๋‚จ์€ ๋ฐ์ดํ„ฐ ๊ฐ•์ œ ํ”Œ๋Ÿฌ์‹œ if (bufferIndex > 0 && logFile) { - logFile.write(fileBuffer, bufferIndex); + size_t written = logFile.write(fileBuffer, bufferIndex); + logFile.flush(); + Serial.printf("โœ“ ์ตœ์ข… ํ”Œ๋Ÿฌ์‹œ: %d bytes\n", written); bufferIndex = 0; } + // โญโญโญ CSV ํ˜•์‹: ์ตœ์ข… ํ”Œ๋Ÿฌ์‹œ + if (canLogFormatCSV && logFile) { + logFile.flush(); + Serial.println("โœ“ CSV ์ตœ์ข… ํ”Œ๋Ÿฌ์‹œ"); + } + if (logFile) { + size_t finalSize = logFile.size(); logFile.close(); + Serial.printf("โœ“ ํŒŒ์ผ ๋‹ซํž˜: %s (%lu bytes)\n", currentFilename, finalSize); } loggingEnabled = false; + currentFilename[0] = '\0'; + bufferIndex = 0; xSemaphoreGive(sdMutex); + } else { + Serial.println("โœ— sdMutex ํš๋“ ์‹คํŒจ!"); } } } @@ -1761,6 +1893,97 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t *payload, size_t length) mcp2515.setListenOnlyMode(); } } + else if (strcmp(cmd, "addSequence") == 0) { + // โญโญโญ Sequence ์ €์žฅ ๊ธฐ๋Šฅ + if (sequenceCount >= MAX_SEQUENCES) { + Serial.println("โœ— Sequence ์ €์žฅ ์‹คํŒจ: ์ตœ๋Œ€ ๊ฐœ์ˆ˜ ์ดˆ๊ณผ"); + DynamicJsonDocument response(256); + response["type"] = "error"; + response["message"] = "Maximum sequences reached"; + String json; + serializeJson(response, json); + webSocket.sendTXT(num, json); + } else { + const char* name = doc["name"]; + int repeatMode = doc["repeatMode"] | 0; + int repeatCount = doc["repeatCount"] | 1; + JsonArray stepsArray = doc["steps"]; + + if (name && stepsArray.size() > 0) { + CANSequence* seq = &sequences[sequenceCount]; + + // ์ด๋ฆ„ ๋ณต์‚ฌ + strncpy(seq->name, name, sizeof(seq->name) - 1); + seq->name[sizeof(seq->name) - 1] = '\0'; + + // Repeat ์„ค์ • + seq->repeatMode = repeatMode; + seq->repeatCount = repeatCount; + + // Steps ๋ณต์‚ฌ + seq->stepCount = 0; + for (JsonObject stepObj : stepsArray) { + if (seq->stepCount >= 20) break; // ์ตœ๋Œ€ 20๊ฐœ + + SequenceStep* step = &seq->steps[seq->stepCount]; + + // ID ํŒŒ์‹ฑ (0x ์ œ๊ฑฐ) + const char* idStr = stepObj["id"]; + if (idStr) { + if (strncmp(idStr, "0x", 2) == 0 || strncmp(idStr, "0X", 2) == 0) { + step->canId = strtoul(idStr + 2, NULL, 16); + } else { + step->canId = strtoul(idStr, NULL, 16); + } + } + + step->extended = stepObj["ext"] | false; + step->dlc = stepObj["dlc"] | 8; + + // Data ๋ฐฐ์—ด ๋ณต์‚ฌ + JsonArray dataArray = stepObj["data"]; + for (int i = 0; i < 8 && i < dataArray.size(); i++) { + step->data[i] = dataArray[i]; + } + + step->delayMs = stepObj["delay"] | 0; + + seq->stepCount++; + } + + sequenceCount++; + + // SD ์นด๋“œ์— ์ €์žฅ + if (xSemaphoreTake(sdMutex, pdMS_TO_TICKS(1000)) == pdTRUE) { + File seqFile = SD.open("/sequences.dat", FILE_WRITE); + if (seqFile) { + seqFile.write((uint8_t*)&sequenceCount, sizeof(sequenceCount)); + seqFile.write((uint8_t*)sequences, sizeof(CANSequence) * sequenceCount); + seqFile.close(); + Serial.printf("โœ“ Sequence ์ €์žฅ ์™„๋ฃŒ: %s (Steps: %d)\n", name, seq->stepCount); + } else { + Serial.println("โœ— sequences.dat ์—ด๊ธฐ ์‹คํŒจ"); + } + xSemaphoreGive(sdMutex); + } + + // ์„ฑ๊ณต ์‘๋‹ต + DynamicJsonDocument response(256); + response["type"] = "sequenceSaved"; + response["name"] = name; + response["steps"] = seq->stepCount; + String json; + serializeJson(response, json); + webSocket.sendTXT(num, json); + + // Sequence ๋ชฉ๋ก ๋‹ค์‹œ ์ „์†ก + delay(100); + webSocket.sendTXT(num, "{\"cmd\":\"getSequences\"}"); + } else { + Serial.println("โœ— Sequence ์ €์žฅ ์‹คํŒจ: ์ž˜๋ชป๋œ ๋ฐ์ดํ„ฐ"); + } + } + } else if (strcmp(cmd, "getSequences") == 0) { DynamicJsonDocument response(3072); response["type"] = "sequences"; @@ -1778,6 +2001,149 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t *payload, size_t length) serializeJson(response, json); webSocket.sendTXT(num, json); } + else if (strcmp(cmd, "startSequence") == 0) { + // โญโญโญ Sequence ์‹คํ–‰ + int index = doc["index"] | -1; + + if (index >= 0 && index < sequenceCount) { + seqRuntime.running = true; + seqRuntime.activeSequenceIndex = index; + seqRuntime.currentStep = 0; + seqRuntime.currentRepeat = 0; + seqRuntime.lastStepTime = millis(); + + Serial.printf("โœ“ Sequence ์‹œ์ž‘: %s (index: %d)\n", sequences[index].name, index); + + // ์„ฑ๊ณต ์‘๋‹ต + DynamicJsonDocument response(256); + response["type"] = "sequenceStarted"; + response["index"] = index; + response["name"] = sequences[index].name; + String json; + serializeJson(response, json); + webSocket.sendTXT(num, json); + } else { + Serial.printf("โœ— Sequence ์‹œ์ž‘ ์‹คํŒจ: ์ž˜๋ชป๋œ index %d\n", index); + } + } + else if (strcmp(cmd, "stopSequence") == 0) { + // โญโญโญ Sequence ์ค‘์ง€ + if (seqRuntime.running) { + Serial.println("โœ“ Sequence ์ค‘์ง€๋จ"); + seqRuntime.running = false; + seqRuntime.activeSequenceIndex = -1; + + // ์„ฑ๊ณต ์‘๋‹ต + DynamicJsonDocument response(256); + response["type"] = "sequenceStopped"; + String json; + serializeJson(response, json); + webSocket.sendTXT(num, json); + } + } + else if (strcmp(cmd, "removeSequence") == 0) { + // โญโญโญ Sequence ์‚ญ์ œ + int index = doc["index"] | -1; + + if (index >= 0 && index < sequenceCount) { + Serial.printf("โœ“ Sequence ์‚ญ์ œ: %s (index: %d)\n", sequences[index].name, index); + + // ๋ฐฐ์—ด์—์„œ ์ œ๊ฑฐ (๋’ค์˜ ํ•ญ๋ชฉ๋“ค์„ ์•ž์œผ๋กœ ์ด๋™) + for (int i = index; i < sequenceCount - 1; i++) { + memcpy(&sequences[i], &sequences[i + 1], sizeof(CANSequence)); + } + + sequenceCount--; + + // SD ์นด๋“œ์— ์ €์žฅ + if (xSemaphoreTake(sdMutex, pdMS_TO_TICKS(1000)) == pdTRUE) { + File seqFile = SD.open("/sequences.dat", FILE_WRITE); + if (seqFile) { + seqFile.write((uint8_t*)&sequenceCount, sizeof(sequenceCount)); + seqFile.write((uint8_t*)sequences, sizeof(CANSequence) * sequenceCount); + seqFile.close(); + Serial.printf("โœ“ SD ์นด๋“œ ์—…๋ฐ์ดํŠธ: %d๊ฐœ sequence\n", sequenceCount); + } + xSemaphoreGive(sdMutex); + } + + // ์„ฑ๊ณต ์‘๋‹ต + DynamicJsonDocument response(256); + response["type"] = "sequenceDeleted"; + response["index"] = index; + String json; + serializeJson(response, json); + webSocket.sendTXT(num, json); + + // Sequence ๋ชฉ๋ก ์—…๋ฐ์ดํŠธ + delay(100); + webSocket.sendTXT(num, "{\"cmd\":\"getSequences\"}"); + } else { + Serial.printf("โœ— Sequence ์‚ญ์ œ ์‹คํŒจ: ์ž˜๋ชป๋œ index %d\n", index); + } + } + else if (strcmp(cmd, "getSequenceDetail") == 0) { + // โญโญโญ Sequence ์ƒ์„ธ ์ •๋ณด (Edit์šฉ) + int index = doc["index"] | -1; + + if (index >= 0 && index < sequenceCount) { + DynamicJsonDocument response(4096); + response["type"] = "sequenceDetail"; + response["index"] = index; + response["name"] = sequences[index].name; + response["repeatMode"] = sequences[index].repeatMode; + response["repeatCount"] = sequences[index].repeatCount; + + JsonArray stepsArray = response.createNestedArray("steps"); + for (int i = 0; i < sequences[index].stepCount; i++) { + SequenceStep* step = &sequences[index].steps[i]; + JsonObject stepObj = stepsArray.createNestedObject(); + + char idStr[12]; + sprintf(idStr, "0x%X", step->canId); + stepObj["id"] = idStr; + stepObj["ext"] = step->extended; + stepObj["dlc"] = step->dlc; + + JsonArray dataArray = stepObj.createNestedArray("data"); + for (int j = 0; j < 8; j++) { + dataArray.add(step->data[j]); + } + + stepObj["delay"] = step->delayMs; + } + + String json; + serializeJson(response, json); + webSocket.sendTXT(num, json); + + Serial.printf("โœ“ Sequence ์ƒ์„ธ ์ „์†ก: %s (index: %d)\n", sequences[index].name, index); + } else { + Serial.printf("โœ— Sequence ์ƒ์„ธ ์กฐํšŒ ์‹คํŒจ: ์ž˜๋ชป๋œ index %d\n", index); + } + }else if (strcmp(cmd, "hwReset") == 0) { + // โญโญโญ ESP32 ํ•˜๋“œ์›จ์–ด ๋ฆฌ์…‹ (์žฌ๋ถ€ํŒ…) + Serial.println("๐Ÿ“จ ํ•˜๋“œ์›จ์–ด ๋ฆฌ์…‹ ์š”์ฒญ ์ˆ˜์‹ "); + Serial.println("๐Ÿ”„ ESP32 ์žฌ๋ถ€ํŒ… ์ค‘..."); + Serial.println(""); + Serial.println("========================================"); + Serial.println(" ESP32 REBOOTING..."); + Serial.println("========================================"); + + // ์‘๋‹ต ์ „์†ก + DynamicJsonDocument response(256); + response["type"] = "hwReset"; + response["success"] = true; + String json; + serializeJson(response, json); + webSocket.sendTXT(num, json); + + // ์•ฝ๊ฐ„์˜ ์ง€์—ฐ ํ›„ ์žฌ๋ถ€ํŒ… (์‘๋‹ต ์ „์†ก ์‹œ๊ฐ„ ํ™•๋ณด) + delay(100); + + // ESP32 ์žฌ๋ถ€ํŒ… + ESP.restart(); + } } } @@ -2212,13 +2578,13 @@ void setup() { // Task ์ƒ์„ฑ xTaskCreatePinnedToCore(canRxTask, "CAN_RX", 8192, NULL, configMAX_PRIORITIES - 1, &canRxTaskHandle, 1); // Core 0, Pri 24 - xTaskCreatePinnedToCore(sdWriteTask, "SD_WRITE", 12576, NULL, 6, &sdWriteTaskHandle, 0); + xTaskCreatePinnedToCore(sdWriteTask, "SD_WRITE", 18576, NULL, 6, &sdWriteTaskHandle, 0); xTaskCreatePinnedToCore(sequenceTask, "SEQ", 4096, NULL, 2, NULL, 1); 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", 16384, NULL, 5, &webTaskHandle, 0); // โญ 10240 โ†’ 16384 - xTaskCreatePinnedToCore(sdMonitorTask, "SD_MONITOR", 4096, NULL, 1, NULL, 0); + xTaskCreatePinnedToCore(webUpdateTask, "WEB_UPDATE", 10384, NULL, 2, &webTaskHandle, 0); // โญ 10240 โ†’ 16384 + xTaskCreatePinnedToCore(sdMonitorTask, "SD_MONITOR", 4096, NULL, 1, NULL, 1); if (timeSyncStatus.rtcAvailable) { xTaskCreatePinnedToCore(rtcSyncTask, "RTC_SYNC", 3072, NULL, 0, &rtcTaskHandle, 0); diff --git a/index.h b/index.h index fb0893b..0ad2163 100644 --- a/index.h +++ b/index.h @@ -75,7 +75,7 @@ const char index_html[] PROGMEM = R"rawliteral( /* ์ „๋ ฅ ๊ฒฝ๊ณ  ๋ฐฐ๋„ˆ */ .power-warning { background: linear-gradient(135deg, #ff6b6b 0%, #ee5a6f 100%); - color: #666; + color: white; /* โญ #666 โ†’ white */ padding: 12px 20px; border-radius: 8px; margin-bottom: 15px; @@ -339,7 +339,7 @@ const char index_html[] PROGMEM = R"rawliteral( cursor: pointer; transition: all 0.3s; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - color: #666; + color: white; /* โญ #666 โ†’ white */ } .control-row button:hover { transform: translateY(-2px); @@ -360,7 +360,7 @@ const char index_html[] PROGMEM = R"rawliteral( } thead { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - color: #666; + color: white; /* โญ #666 โ†’ white */ } th { padding: 12px 8px; @@ -479,21 +479,21 @@ const char index_html[] PROGMEM = R"rawliteral( } .download-btn { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - color: #666; + color: white; /* โญ #666 โ†’ white */ } .download-btn:hover { background: linear-gradient(135deg, #5568d3 0%, #66409e 100%); } .comment-btn { background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); - color: #666; + color: white; /* โญ #666 โ†’ white */ } .comment-btn:hover { background: linear-gradient(135deg, #e77fe8 0%, #e44459 100%); } .delete-btn { background: linear-gradient(135deg, #eb3349 0%, #f45c43 100%); - color: #666; + color: white; /* โญ #666 โ†’ white */ } .delete-btn:hover { background: linear-gradient(135deg, #d32f3f 0%, #e53935 100%); @@ -823,6 +823,7 @@ const char index_html[] PROGMEM = R"rawliteral(
+
@@ -943,19 +944,11 @@ const char index_html[] PROGMEM = R"rawliteral( document.getElementById('sync-status').textContent = '์—ฐ๊ฒฐ๋จ'; document.getElementById('sync-status').style.color = '#38ef7d'; - // โญโญโญ ์ž๋™ ์‹œ๊ฐ„ ๋™๊ธฐํ™” (1๋ถ„์— 1ํšŒ๋กœ ์ œํ•œ) - const now = Date.now(); - const lastSync = parseInt(localStorage.getItem('lastTimeSync') || '0'); - - if (now - lastSync > 60000) { // 1๋ถ„ ์ด์ƒ ๊ฒฝ๊ณผ - setTimeout(function() { - syncTimeFromPhone(); - localStorage.setItem('lastTimeSync', now.toString()); - console.log('Auto time sync on connect'); - }, 100); - } else { - console.log('Skipping time sync (last sync was recent)'); - } + // โญโญโญ ์ž๋™ ์‹œ๊ฐ„ ๋™๊ธฐํ™” (ํŽ˜์ด์ง€ ๋กœ๋“œ ์‹œ ํ•ญ์ƒ ์‹คํ–‰) + setTimeout(function() { + syncTimeFromPhone(); + console.log('โœ… Auto time sync on page load'); + }, 500); // WebSocket ์•ˆ์ •ํ™” ๋Œ€๊ธฐ // โญ WebSocket ์—ฐ๊ฒฐ๋˜๋ฉด ์ฆ‰์‹œ ํŒŒ์ผ ๋ชฉ๋ก ์š”์ฒญ setTimeout(function() { @@ -1004,6 +997,9 @@ const char index_html[] PROGMEM = R"rawliteral( if (data.success) { console.log('Comment saved successfully'); } + } else if (data.type === 'hwReset') { + console.log('โœ… ํ•˜๋“œ์›จ์–ด ๋ฆฌ์…‹ ๋ช…๋ น ์ „์†ก๋จ - ESP32 ์žฌ๋ถ€ํŒ… ์ค‘...'); + // ESP32๊ฐ€ ์žฌ๋ถ€ํŒ…๋˜๋ฏ€๋กœ WebSocket ์—ฐ๊ฒฐ ๋Š๊น€ } } catch (e) { console.error('Parse error:', e); @@ -1393,12 +1389,42 @@ const char index_html[] PROGMEM = R"rawliteral( if (ws && ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({cmd: 'stopLogging'})); console.log('Stop logging command sent'); + + // โญโญโญ ๋กœ๊น… ์ข…๋ฃŒ ์‹œ Messages ์นด์šดํŠธ ๋ฆฌ์…‹ + document.getElementById('msg-count').textContent = '0'; + document.getElementById('msg-speed').textContent = '0/s'; + console.log('โœ… Messages count reset to 0'); + setTimeout(() => { refreshFiles(); }, 1000); } } + function hardwareReset() { + if (!confirm('ESP32๋ฅผ ์žฌ๋ถ€ํŒ…ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?\n\nโš ๏ธ ๋ชจ๋“  ๋กœ๊น…์ด ์ค‘์ง€๋˜๊ณ  WebSocket ์—ฐ๊ฒฐ์ด ๋Š์–ด์ง‘๋‹ˆ๋‹ค.\n์žฌ๋ถ€ํŒ… ํ›„ ์›นํŽ˜์ด์ง€๋ฅผ ์ƒˆ๋กœ๊ณ ์นจํ•˜์„ธ์š”.')) { + return; + } + + if (ws && ws.readyState === WebSocket.OPEN) { + console.log('๐Ÿ”„ ํ•˜๋“œ์›จ์–ด ๋ฆฌ์…‹ ์š”์ฒญ...'); + ws.send(JSON.stringify({cmd: 'hwReset'})); + + // ๋ฉ”์‹œ์ง€ ์นด์šดํ„ฐ ๋ฆฌ์…‹ + document.getElementById('msg-count').textContent = '0'; + document.getElementById('msg-speed').textContent = '0/s'; + console.log('โœ… ํ•˜๋“œ์›จ์–ด ๋ฆฌ์…‹ ์š”์ฒญ ์ „์†ก๋จ'); + + // 3์ดˆ ํ›„ ์•Œ๋ฆผ + setTimeout(() => { + alert('ESP32๊ฐ€ ์žฌ๋ถ€ํŒ… ์ค‘์ž…๋‹ˆ๋‹ค...\n\n10์ดˆ ํ›„ ์›นํŽ˜์ด์ง€๋ฅผ ์ƒˆ๋กœ๊ณ ์นจํ•˜์„ธ์š”!'); + }, 1000); + } else { + alert('WebSocket์ด ์—ฐ๊ฒฐ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค!'); + console.error('WebSocket not connected'); + } + } + function refreshFiles() { console.log('Requesting file list...'); // โญ ๋””๋ฒ„๊ทธ if (ws && ws.readyState === WebSocket.OPEN) {