From e26240f84f189e7f1631590d8e5ceef34a88b1db Mon Sep 17 00:00:00 2001 From: byun Date: Sun, 5 Oct 2025 07:10:46 +0000 Subject: [PATCH] =?UTF-8?q?00003=20=EC=A0=84=EC=86=A1=EC=9B=B9=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 웹페이지 하나 더 추가 하고 싶은데 (포트생성) 이 신규 페이지는 can data를 송신 하는 목적의 페이지로 can 신호id, messagy type, message data(hex) 를 설정할 수 있게 하고 여러 신호들을 조합으로 송신할 수 있게, 그리고 특정 시간마다 송신할 수 있게 기능을 추가 할 수 있어? 해당페이지는 차후 수정이 용이하게 파일도 추가했으면 좋겠어 --- ESP32_CAN_Logger.ino | 227 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 180 insertions(+), 47 deletions(-) diff --git a/ESP32_CAN_Logger.ino b/ESP32_CAN_Logger.ino index 534324c..0c632f7 100644 --- a/ESP32_CAN_Logger.ino +++ b/ESP32_CAN_Logger.ino @@ -1,27 +1,6 @@ /* * ESP32 CAN Logger with Web Interface - * - * Features: - * - CAN Bus logging to SD card (binary format) - * - WiFi AP with web interface - * - Real-time CAN message monitoring (by ID, BUSMASTER style) - * - CAN speed adjustment (125K - 1M) - * - Log file download - * - Batch update every 0.5s for real-time performance - * - Accurate message count per CAN ID - * - Current logging file display - * - * Hardware: - * - ESP32 WROVER Module (ESP32-WROOM-32D) - * - MCP2515 CAN Controller (HSPI, INT: GPIO27) - * - SD Card (VSPI) - * - GPIO17: Logging control (HIGH=start, LOW=stop) - * - GPIO16: Logging status LED - * - GPIO26: SD card ready LED - * - * Required Libraries: - * - WebSockets by Markus Sattler - * - mcp2515 by autowp + * Version: 1.0 */ #include @@ -36,6 +15,7 @@ #include #include #include "index.h" +#include "transmit.h" // GPIO 핀 정의 #define CAN_INT_PIN 27 @@ -60,6 +40,7 @@ #define FILE_BUFFER_SIZE 8192 #define MAX_FILENAME_LEN 32 #define RECENT_MSG_COUNT 100 +#define MAX_TX_MESSAGES 20 // CAN 메시지 구조체 struct CANMessage { @@ -75,6 +56,17 @@ struct RecentCANData { uint32_t count; }; +// CAN 송신용 구조체 +struct TxMessage { + uint32_t id; + bool extended; + uint8_t dlc; + uint8_t data[8]; + uint32_t interval; + uint32_t lastSent; + bool active; +}; + // WiFi AP 설정 const char* ssid = "ESP32_CAN_Logger"; const char* password = "12345678"; @@ -114,6 +106,26 @@ uint32_t msgPerSecond = 0; uint32_t lastMsgCountTime = 0; uint32_t lastMsgCount = 0; +// CAN 송신용 +TxMessage txMessages[MAX_TX_MESSAGES]; +uint32_t totalTxCount = 0; + +// 함수 선언 +void changeCanSpeed(CAN_SPEED newSpeed); +void scanExistingFiles(); +bool createNewLogFile(); +bool flushBuffer(); +void canRxTask(void *pvParameters); +void sdWriteTask(void *pvParameters); +void controlTask(void *pvParameters); +void sdMonitorTask(void *pvParameters); +void sendFileList(uint8_t clientNum); +void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length); +void handleCanTransmit(String msg); +void handleStartMessage(String msg); +void handleStopMessage(String msg); +void webUpdateTask(void *pvParameters); + // CAN 인터럽트 핸들러 void IRAM_ATTR canISR() { BaseType_t xHigherPriorityTaskWoken = pdFALSE; @@ -180,8 +192,7 @@ void scanExistingFiles() { if (foundFiles) { fileCounter = maxFileNumber + 1; - Serial.printf("기존 파일 발견 - 다음 파일 번호: %lu (canlog_%05lu.bin)\n", - fileCounter, fileCounter); + Serial.printf("기존 파일 발견 - 다음 파일 번호: %lu\n", fileCounter); } else { fileCounter = 0; Serial.println("기존 파일 없음 - 파일 카운터 0으로 시작"); @@ -269,7 +280,6 @@ void canRxTask(void *pvParameters) { } if (!found) { - // 새 ID - 빈 슬롯 찾기 for (int i = 0; i < RECENT_MSG_COUNT; i++) { if (recentData[i].msg.timestamp == 0) { recentData[i].msg = msg; @@ -279,7 +289,6 @@ void canRxTask(void *pvParameters) { } } - // 빈 슬롯 없으면 가장 오래된 것 교체 if (!found) { static int replaceIndex = 0; recentData[replaceIndex].msg = msg; @@ -400,7 +409,6 @@ void sendFileList(uint8_t clientNum) { if (!sdCardReady) { fileList += "],\"error\":\"SD card not ready\"}"; webSocket.sendTXT(clientNum, fileList); - Serial.println("파일 목록 요청: SD 카드 없음"); return; } @@ -408,7 +416,6 @@ void sendFileList(uint8_t clientNum) { if (!root) { fileList += "],\"error\":\"Cannot open root directory\"}"; webSocket.sendTXT(clientNum, fileList); - Serial.println("파일 목록 요청: 루트 디렉토리 열기 실패"); return; } @@ -436,20 +443,106 @@ void sendFileList(uint8_t clientNum) { fileList += "]}"; webSocket.sendTXT(clientNum, fileList); - Serial.printf("파일 목록 전송 완료: %d개 파일\n", fileCount); + Serial.printf("파일 목록 전송: %d개\n", fileCount); +} + +// CAN 메시지 전송 처리 +void handleCanTransmit(String msg) { + int idIdx = msg.indexOf("\"id\":\"") + 6; + int idEnd = msg.indexOf("\"", idIdx); + String idStr = msg.substring(idIdx, idEnd); + + int typeIdx = msg.indexOf("\"type\":\"") + 8; + String typeStr = msg.substring(typeIdx, typeIdx + 3); + bool extended = (typeStr == "ext"); + + int dlcIdx = msg.indexOf("\"dlc\":") + 6; + int dlc = msg.substring(dlcIdx, dlcIdx + 1).toInt(); + + int dataIdx = msg.indexOf("\"data\":\"") + 8; + String dataStr = msg.substring(dataIdx, dataIdx + 16); + + uint32_t canId = strtoul(idStr.c_str(), NULL, 16); + + uint8_t data[8] = {0}; + for (int i = 0; i < dlc && i < 8; i++) { + String byteStr = dataStr.substring(i * 2, i * 2 + 2); + data[i] = strtoul(byteStr.c_str(), NULL, 16); + } + + struct can_frame frame; + frame.can_id = canId; + if (extended) frame.can_id |= CAN_EFF_FLAG; + frame.can_dlc = dlc; + memcpy(frame.data, data, 8); + + if (mcp2515.sendMessage(&frame) == MCP2515::ERROR_OK) { + totalTxCount++; + Serial.printf("CAN TX: 0x%X [%d]\n", canId, dlc); + } +} + +// 주기 전송 시작 +void handleStartMessage(String msg) { + int indexIdx = msg.indexOf("\"index\":") + 8; + int index = msg.substring(indexIdx, indexIdx + 2).toInt(); + + if (index >= 0 && index < MAX_TX_MESSAGES) { + int idIdx = msg.indexOf("\"id\":\"") + 6; + int idEnd = msg.indexOf("\"", idIdx); + String idStr = msg.substring(idIdx, idEnd); + + int typeIdx = msg.indexOf("\"type\":\"") + 8; + String typeStr = msg.substring(typeIdx, typeIdx + 3); + + int dlcIdx = msg.indexOf("\"dlc\":") + 6; + int dlc = msg.substring(dlcIdx, dlcIdx + 1).toInt(); + + int dataIdx = msg.indexOf("\"data\":\"") + 8; + String dataStr = msg.substring(dataIdx, dataIdx + 16); + + int intervalIdx = msg.indexOf("\"interval\":") + 11; + int interval = msg.substring(intervalIdx, intervalIdx + 5).toInt(); + + txMessages[index].id = strtoul(idStr.c_str(), NULL, 16); + txMessages[index].extended = (typeStr == "ext"); + txMessages[index].dlc = dlc; + + for (int i = 0; i < 8; i++) { + String byteStr = dataStr.substring(i * 2, i * 2 + 2); + txMessages[index].data[i] = strtoul(byteStr.c_str(), NULL, 16); + } + + txMessages[index].interval = interval; + txMessages[index].lastSent = 0; + txMessages[index].active = true; + + Serial.printf("주기 전송 시작 [%d]: 0x%X\n", index, txMessages[index].id); + } +} + +// 주기 전송 중지 +void handleStopMessage(String msg) { + int indexIdx = msg.indexOf("\"index\":") + 8; + int index = msg.substring(indexIdx, indexIdx + 2).toInt(); + + if (index >= 0 && index < MAX_TX_MESSAGES) { + txMessages[index].active = false; + Serial.printf("주기 전송 중지 [%d]\n", index); + } } // 웹소켓 이벤트 핸들러 void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) { switch(type) { case WStype_DISCONNECTED: - Serial.printf("WebSocket 클라이언트 #%u 연결 해제\n", num); + Serial.printf("WebSocket #%u 연결 해제\n", num); break; case WStype_CONNECTED: { IPAddress ip = webSocket.remoteIP(num); - Serial.printf("WebSocket 클라이언트 #%u 연결: %d.%d.%d.%d\n", + Serial.printf("WebSocket #%u 연결: %d.%d.%d.%d\n", num, ip[0], ip[1], ip[2], ip[3]); sendFileList(num); } @@ -466,9 +559,24 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length if (speed >= 0 && speed < 4) { changeCanSpeed(canSpeedValues[speed]); } - } else if (msg.indexOf("\"cmd\":\"getFiles\"") >= 0) { + } + else if (msg.indexOf("\"cmd\":\"getFiles\"") >= 0) { sendFileList(num); } + else if (msg.indexOf("\"cmd\":\"sendCan\"") >= 0) { + handleCanTransmit(msg); + } + else if (msg.indexOf("\"cmd\":\"startMsg\"") >= 0) { + handleStartMessage(msg); + } + else if (msg.indexOf("\"cmd\":\"stopMsg\"") >= 0) { + handleStopMessage(msg); + } + else if (msg.indexOf("\"cmd\":\"stopAll\"") >= 0) { + for (int i = 0; i < MAX_TX_MESSAGES; i++) { + txMessages[i].active = false; + } + } } break; } @@ -478,6 +586,7 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length void webUpdateTask(void *pvParameters) { uint32_t lastStatusUpdate = 0; uint32_t lastCanUpdate = 0; + uint32_t lastTxStatusUpdate = 0; const uint32_t CAN_UPDATE_INTERVAL = 500; Serial.println("웹 업데이트 태스크 시작"); @@ -487,7 +596,30 @@ void webUpdateTask(void *pvParameters) { webSocket.loop(); - // 상태 업데이트 (1초마다) + // 주기 전송 처리 + for (int i = 0; i < MAX_TX_MESSAGES; i++) { + if (txMessages[i].active && (now - txMessages[i].lastSent >= txMessages[i].interval)) { + struct can_frame frame; + frame.can_id = txMessages[i].id; + if (txMessages[i].extended) frame.can_id |= CAN_EFF_FLAG; + frame.can_dlc = txMessages[i].dlc; + memcpy(frame.data, txMessages[i].data, 8); + + if (mcp2515.sendMessage(&frame) == MCP2515::ERROR_OK) { + totalTxCount++; + txMessages[i].lastSent = now; + } + } + } + + // TX 상태 업데이트 + if (now - lastTxStatusUpdate >= 1000) { + String txStatus = "{\"type\":\"txStatus\",\"count\":" + String(totalTxCount) + "}"; + webSocket.broadcastTXT(txStatus); + lastTxStatusUpdate = now; + } + + // 상태 업데이트 if (now - lastStatusUpdate >= 1000) { if (now - lastMsgCountTime >= 1000) { msgPerSecond = totalMsgCount - lastMsgCount; @@ -512,7 +644,7 @@ void webUpdateTask(void *pvParameters) { lastStatusUpdate = now; } - // CAN 메시지 일괄 업데이트 (0.5초마다) + // CAN 메시지 일괄 업데이트 if (now - lastCanUpdate >= CAN_UPDATE_INTERVAL) { String canBatch = "{\"type\":\"canBatch\",\"messages\":["; bool first = true; @@ -559,12 +691,12 @@ void webUpdateTask(void *pvParameters) { void setup() { Serial.begin(115200); delay(1000); - Serial.println("\n\n========================================"); + Serial.println("\n========================================"); Serial.println(" ESP32 CAN Logger with Web Interface "); Serial.println("========================================"); - // 배열 초기화 memset(recentData, 0, sizeof(recentData)); + memset(txMessages, 0, sizeof(txMessages)); pinMode(LOGGING_CONTROL_PIN, INPUT); pinMode(LOGGING_STATUS_LED, OUTPUT); @@ -580,7 +712,7 @@ void setup() { mcp2515.reset(); mcp2515.setBitrate(CAN_1000KBPS, MCP_8MHZ); mcp2515.setNormalMode(); - Serial.println("✓ MCP2515 초기화 완료 (1Mbps)"); + Serial.println("✓ MCP2515 초기화 완료"); if (SD.begin(VSPI_CS, vspi)) { sdCardReady = true; @@ -589,22 +721,23 @@ void setup() { scanExistingFiles(); } else { Serial.println("✗ SD 카드 초기화 실패"); - fileCounter = 0; } - Serial.println("\nWiFi AP 시작..."); WiFi.softAP(ssid, password); - Serial.print("✓ AP IP 주소: "); + Serial.print("✓ AP IP: "); Serial.println(WiFi.softAPIP()); webSocket.begin(); webSocket.onEvent(webSocketEvent); - Serial.println("✓ WebSocket 서버 시작 (포트 81)"); server.on("/", HTTP_GET, []() { server.send_P(200, "text/html", index_html); }); + server.on("/transmit", HTTP_GET, []() { + server.send_P(200, "text/html", transmit_html); + }); + server.on("/download", HTTP_GET, []() { if (server.hasArg("file")) { String filename = "/" + server.arg("file"); @@ -626,7 +759,6 @@ void setup() { }); server.begin(); - Serial.println("✓ 웹 서버 시작 (포트 80)"); canQueue = xQueueCreate(CAN_QUEUE_SIZE, sizeof(CANMessage)); sdMutex = xSemaphoreCreateMutex(); @@ -635,7 +767,6 @@ void setup() { Serial.println("✗ RTOS 객체 생성 실패!"); while (1) delay(1000); } - Serial.println("✓ RTOS 객체 생성 완료"); attachInterrupt(digitalPinToInterrupt(CAN_INT_PIN), canISR, FALLING); @@ -645,13 +776,15 @@ void setup() { xTaskCreatePinnedToCore(sdMonitorTask, "SD_MONITOR", 4096, NULL, 1, NULL, 0); xTaskCreatePinnedToCore(webUpdateTask, "WEB_UPDATE", 8192, NULL, 2, &webTaskHandle, 0); - Serial.println("✓ 모든 RTOS 태스크 시작 완료"); + Serial.println("✓ 모든 태스크 시작 완료"); Serial.println("\n========================================"); Serial.println(" 웹 인터페이스 접속"); Serial.println("========================================"); Serial.println(" 1. WiFi: ESP32_CAN_Logger (12345678)"); Serial.print(" 2. http://"); Serial.println(WiFi.softAPIP()); + Serial.println(" 3. Monitor: /"); + Serial.println(" 4. Transmit: /transmit"); Serial.println("========================================\n"); } @@ -661,11 +794,11 @@ void loop() { static uint32_t lastPrint = 0; if (millis() - lastPrint > 10000) { - Serial.printf("[상태] 큐: %d/%d | 로깅: %s | SD: %s | 총: %lu | 속도: %lu msg/s\n", + Serial.printf("[상태] 큐: %d/%d | 로깅: %s | SD: %s | RX: %lu | TX: %lu\n", uxQueueMessagesWaiting(canQueue), CAN_QUEUE_SIZE, loggingEnabled ? "ON " : "OFF", - sdCardReady ? "Ready" : "No ", - totalMsgCount, msgPerSecond); + sdCardReady ? "OK" : "NO", + totalMsgCount, totalTxCount); lastPrint = millis(); } } \ No newline at end of file