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(