/*
 * Byun CAN Logger with Web Interface + RTC DS3231
 * Version: 1.3
 */

#include <Arduino.h>
#include <SPI.h>
#include <mcp2515.h>
#include <SD.h>
#include <WiFi.h>
#include <WebServer.h>
#include <WebSocketsServer.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/queue.h>
#include <freertos/semphr.h>
#include <sys/time.h>
#include <time.h>
#include <Wire.h>
#include <RTClib.h>
#include "index.h"
#include "transmit.h"
#include "graph.h"
#include "graph_viewer.h"


// GPIO 핀 정의
#define CAN_INT_PIN 27

// HSPI 핀 (CAN)
#define HSPI_MISO 12
#define HSPI_MOSI 13
#define HSPI_SCLK 14
#define HSPI_CS 15

// VSPI 핀 (SD Card)
#define VSPI_MISO 19
#define VSPI_MOSI 23
#define VSPI_SCLK 18
#define VSPI_CS 5

// I2C 핀 (RTC DS3231)
#define I2C_SDA 25
#define I2C_SCL 26

// 버퍼 설정
#define CAN_QUEUE_SIZE 1000
#define FILE_BUFFER_SIZE 8192
#define MAX_FILENAME_LEN 64
#define RECENT_MSG_COUNT 100
#define MAX_TX_MESSAGES 20

// CAN 메시지 구조체 - 마이크로초 단위 타임스탬프
struct CANMessage {
    uint64_t timestamp_us;  // 마이크로초 단위 Unix timestamp
    uint32_t id;
    uint8_t dlc;
    uint8_t data[8];
} __attribute__((packed));

// 실시간 모니터링용 구조체
struct RecentCANData {
    CANMessage msg;
    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;
};

// 시간 동기화 상태
struct TimeSyncStatus {
    bool synchronized;
    uint64_t lastSyncTime;
    int32_t offsetUs;
    uint32_t syncCount;
    bool rtcAvailable;
} timeSyncStatus = {false, 0, 0, 0, false};

// WiFi AP 설정
const char* ssid = "Byun_CAN_Logger";
const char* password = "12345678";

// 전역 변수
SPIClass hspi(HSPI);
SPIClass vspi(VSPI);
MCP2515 mcp2515(HSPI_CS, 10000000, &hspi);
RTC_DS3231 rtc;

WebServer server(80);
WebSocketsServer webSocket = WebSocketsServer(81);

QueueHandle_t canQueue;
SemaphoreHandle_t sdMutex;
TaskHandle_t canRxTaskHandle = NULL;
TaskHandle_t sdWriteTaskHandle = NULL;
TaskHandle_t webTaskHandle = NULL;

volatile bool loggingEnabled = false;
volatile bool sdCardReady = false;
File logFile;
char currentFilename[MAX_FILENAME_LEN];
uint8_t fileBuffer[FILE_BUFFER_SIZE];
uint16_t bufferIndex = 0;

// CAN 속도 설정
CAN_SPEED currentCanSpeed = CAN_1000KBPS;
const char* canSpeedNames[] = {"125K", "250K", "500K", "1M"};
CAN_SPEED canSpeedValues[] = {CAN_125KBPS, CAN_250KBPS, CAN_500KBPS, CAN_1000KBPS};

// 실시간 모니터링용
RecentCANData recentData[RECENT_MSG_COUNT];
uint32_t totalMsgCount = 0;
uint32_t msgPerSecond = 0;
uint32_t lastMsgCountTime = 0;
uint32_t lastMsgCount = 0;

// CAN 송신용
TxMessage txMessages[MAX_TX_MESSAGES];
uint32_t totalTxCount = 0;

// 정밀한 현재 시간 가져오기 (마이크로초)
uint64_t getMicrosecondTimestamp() {
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return (uint64_t)tv.tv_sec * 1000000ULL + (uint64_t)tv.tv_usec;
}

// RTC에서 시간 읽기
String getRTCTime() {
    if (!timeSyncStatus.rtcAvailable) {
        return "RTC not available";
    }
    
    DateTime now = rtc.now();
    char timeStr[32];
    snprintf(timeStr, sizeof(timeStr), "%04d-%02d-%02d %02d:%02d:%02d",
             now.year(), now.month(), now.day(),
             now.hour(), now.minute(), now.second());
    return String(timeStr);
}

// RTC에 시간 설정
bool setRTCTime(uint64_t timestampMs) {
    if (!timeSyncStatus.rtcAvailable) {
        Serial.println("❌ RTC not available");
        return false;
    }
    
    time_t timestamp = timestampMs / 1000;
    DateTime dt(timestamp);
    rtc.adjust(dt);
    
    Serial.printf("⏰ RTC 시간 설정: %s\n", getRTCTime().c_str());
    return true;
}

// RTC에서 시스템 시간 동기화
bool syncTimeFromRTC() {
    if (!timeSyncStatus.rtcAvailable) {
        Serial.println("❌ RTC not available");
        return false;
    }
    
    DateTime now = rtc.now();
    struct timeval tv;
    tv.tv_sec = now.unixtime();
    tv.tv_usec = 0;
    settimeofday(&tv, NULL);
    
    timeSyncStatus.synchronized = true;
    timeSyncStatus.lastSyncTime = getMicrosecondTimestamp();
    timeSyncStatus.syncCount++;
    
    char timeStr[64];
    strftime(timeStr, sizeof(timeStr), "%Y-%m-%d %H:%M:%S", localtime(&tv.tv_sec));
    
    Serial.printf("⏰ RTC로부터 시간 동기화: %s (동기화 횟수: %u)\n", 
                  timeStr, timeSyncStatus.syncCount);
    return true;
}

// 시간 동기화 설정 (웹에서)
void setSystemTime(uint64_t timestampMs) {
    struct timeval tv;
    tv.tv_sec = timestampMs / 1000;
    tv.tv_usec = (timestampMs % 1000) * 1000;
    settimeofday(&tv, NULL);
    
    timeSyncStatus.synchronized = true;
    timeSyncStatus.lastSyncTime = getMicrosecondTimestamp();
    timeSyncStatus.syncCount++;
    
    // 현재 시간 출력
    time_t now = tv.tv_sec;
    struct tm timeinfo;
    localtime_r(&now, &timeinfo);
    char timeStr[64];
    strftime(timeStr, sizeof(timeStr), "%Y-%m-%d %H:%M:%S", &timeinfo);
    
    Serial.printf("⏰ 웹에서 시간 동기화: %s.%03d (동기화 횟수: %u)\n", 
                  timeStr, (int)(tv.tv_usec / 1000), timeSyncStatus.syncCount);
}

// 함수 선언
void changeCanSpeed(CAN_SPEED newSpeed);
bool createNewLogFile();
bool flushBuffer();
void startLogging();
void stopLogging();
void canRxTask(void *pvParameters);
void sdWriteTask(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 handleTimeSync(String msg);
void handleRTCGet(uint8_t clientNum);
void handleRTCSet(String msg);
void webUpdateTask(void *pvParameters);

// CAN 인터럽트 핸들러
void IRAM_ATTR canISR() {
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    if (canRxTaskHandle != NULL) {
        vTaskNotifyGiveFromISR(canRxTaskHandle, &xHigherPriorityTaskWoken);
        portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
    }
}

// CAN 속도 변경
void changeCanSpeed(CAN_SPEED newSpeed) {
    detachInterrupt(digitalPinToInterrupt(CAN_INT_PIN));
    mcp2515.reset();
    mcp2515.setBitrate(newSpeed, MCP_8MHZ);
    mcp2515.setNormalMode();
    currentCanSpeed = newSpeed;
    attachInterrupt(digitalPinToInterrupt(CAN_INT_PIN), canISR, FALLING);
    Serial.printf("CAN 속도 변경: %s\n", canSpeedNames[newSpeed]);
}

// 새 로그 파일 생성 - 시간 기반 파일명
bool createNewLogFile() {
    if (logFile) {
        logFile.flush();
        logFile.close();
        vTaskDelay(pdMS_TO_TICKS(10));
    }
    
    // 현재 시간으로 파일명 생성
    time_t now;
    struct tm timeinfo;
    time(&now);
    localtime_r(&now, &timeinfo);
    
    char filename[MAX_FILENAME_LEN];
    snprintf(filename, MAX_FILENAME_LEN, "/canlog_%04d%02d%02d_%02d%02d%02d.bin",
             timeinfo.tm_year + 1900, 
             timeinfo.tm_mon + 1, 
             timeinfo.tm_mday,
             timeinfo.tm_hour, 
             timeinfo.tm_min, 
             timeinfo.tm_sec);
    
    logFile = SD.open(filename, FILE_WRITE);
    
    if (!logFile) {
        Serial.printf("파일 생성 실패: %s\n", filename);
        return false;
    }
    
    strncpy(currentFilename, filename, MAX_FILENAME_LEN);
    Serial.printf("새 로그 파일 생성: %s\n", currentFilename);
    
    // 시간 동기화 경고
    if (!timeSyncStatus.synchronized) {
        Serial.println("⚠️ 경고: 시간이 동기화되지 않았습니다! 웹페이지에서 시간 동기화를 실행하세요.");
    }
    
    return true;
}

// 버퍼 플러시
bool flushBuffer() {
    if (bufferIndex == 0) return true;
    
    if (xSemaphoreTake(sdMutex, portMAX_DELAY) == pdTRUE) {
        if (logFile) {
            size_t written = logFile.write(fileBuffer, bufferIndex);
            logFile.flush();
            xSemaphoreGive(sdMutex);
            
            if (written != bufferIndex) {
                Serial.println("SD 쓰기 오류!");
                return false;
            }
            bufferIndex = 0;
            return true;
        }
        xSemaphoreGive(sdMutex);
    }
    return false;
}

// 로깅 시작
void startLogging() {
    if (loggingEnabled) {
        Serial.println("이미 로깅 중");
        return;
    }
    
    if (!sdCardReady) {
        Serial.println("SD 카드가 준비되지 않음");
        return;
    }
    
    Serial.println("로깅 시작");
    
    if (xSemaphoreTake(sdMutex, pdMS_TO_TICKS(1000)) == pdTRUE) {
        if (createNewLogFile()) {
            loggingEnabled = true;
            bufferIndex = 0;
        }
        xSemaphoreGive(sdMutex);
    }
}

// 로깅 중지
void stopLogging() {
    if (!loggingEnabled) {
        Serial.println("로깅이 실행 중이 아님");
        return;
    }
    
    Serial.println("로깅 정지");
    loggingEnabled = false;
    
    flushBuffer();
    
    if (xSemaphoreTake(sdMutex, pdMS_TO_TICKS(1000)) == pdTRUE) {
        if (logFile) {
            logFile.close();
        }
        xSemaphoreGive(sdMutex);
    }
}

// CAN 수신 태스크
void canRxTask(void *pvParameters) {
    struct can_frame frame;
    CANMessage msg;
    
    Serial.println("CAN 수신 태스크 시작");
    
    while (1) {
        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
        
        while (mcp2515.readMessage(&frame) == MCP2515::ERROR_OK) {
            // 마이크로초 단위 타임스탬프
            msg.timestamp_us = getMicrosecondTimestamp();
            msg.id = frame.can_id;
            msg.dlc = frame.can_dlc;
            memcpy(msg.data, frame.data, 8);
            
            if (xQueueSend(canQueue, &msg, 0) != pdTRUE) {
                static uint32_t lastWarning = 0;
                if (millis() - lastWarning > 1000) {
                    Serial.println("경고: CAN 큐 오버플로우!");
                    lastWarning = millis();
                }
            }
            
            // 최근 메시지 저장 및 카운트 증가
            bool found = false;
            for (int i = 0; i < RECENT_MSG_COUNT; i++) {
                if (recentData[i].msg.id == msg.id && recentData[i].msg.timestamp_us > 0) {
                    recentData[i].msg = msg;
                    recentData[i].count++;
                    found = true;
                    break;
                }
            }
            
            if (!found) {
                for (int i = 0; i < RECENT_MSG_COUNT; i++) {
                    if (recentData[i].msg.timestamp_us == 0) {
                        recentData[i].msg = msg;
                        recentData[i].count = 1;
                        found = true;
                        break;
                    }
                }
                
                if (!found) {
                    static int replaceIndex = 0;
                    recentData[replaceIndex].msg = msg;
                    recentData[replaceIndex].count = 1;
                    replaceIndex = (replaceIndex + 1) % RECENT_MSG_COUNT;
                }
            }
            
            totalMsgCount++;
        }
    }
}

// SD 쓰기 태스크
void sdWriteTask(void *pvParameters) {
    CANMessage msg;
    
    Serial.println("SD 쓰기 태스크 시작");
    
    while (1) {
        if (xQueueReceive(canQueue, &msg, pdMS_TO_TICKS(100)) == pdTRUE) {
            
            if (loggingEnabled && sdCardReady) {
                if (bufferIndex + sizeof(CANMessage) > FILE_BUFFER_SIZE) {
                    if (!flushBuffer()) {
                        continue;
                    }
                }
                
                memcpy(&fileBuffer[bufferIndex], &msg, sizeof(CANMessage));
                bufferIndex += sizeof(CANMessage);
            }
        } else {
            if (loggingEnabled && bufferIndex > 0) {
                flushBuffer();
            }
        }
    }
}

// SD 모니터 태스크
void sdMonitorTask(void *pvParameters) {
    Serial.println("SD 모니터 태스크 시작");
    
    while (1) {
        bool cardPresent = SD.begin(VSPI_CS, vspi);
        
        if (cardPresent != sdCardReady) {
            sdCardReady = cardPresent;
            
            if (sdCardReady) {
                Serial.println("SD 카드 준비됨");
            } else {
                Serial.println("SD 카드 없음");
                if (loggingEnabled) {
                    stopLogging();
                }
            }
        }
        
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

// 파일 목록 전송
void sendFileList(uint8_t clientNum) {
    String fileList = "{\"type\":\"files\",\"files\":[";
    
    if (!sdCardReady) {
        fileList += "],\"error\":\"SD card not ready\"}";
        webSocket.sendTXT(clientNum, fileList);
        return;
    }
    
    File root = SD.open("/");
    if (!root) {
        fileList += "],\"error\":\"Cannot open root directory\"}";
        webSocket.sendTXT(clientNum, fileList);
        return;
    }
    
    File file = root.openNextFile();
    bool first = true;
    int fileCount = 0;
    
    while (file) {
        if (!file.isDirectory()) {
            String name = file.name();
            if (name.startsWith("/")) name = name.substring(1);
            
            if (name.endsWith(".bin") || name.endsWith(".BIN")) {
                if (!first) fileList += ",";
                fileList += "{\"name\":\"" + name + "\",\"size\":" + String(file.size()) + "}";
                first = false;
                fileCount++;
            }
        }
        file.close();
        file = root.openNextFile();
    }
    root.close();
    
    fileList += "]}";
    webSocket.sendTXT(clientNum, fileList);
    
    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 handleTimeSync(String msg) {
    int timestampIdx = msg.indexOf("\"timestamp\":") + 12;
    String timestampStr = msg.substring(timestampIdx);
    timestampStr = timestampStr.substring(0, timestampStr.indexOf("}"));
    
    uint64_t clientTimestamp = strtoull(timestampStr.c_str(), NULL, 10);
    
    if (clientTimestamp > 0) {
        setSystemTime(clientTimestamp);
    }
}

// RTC 시간 읽기
void handleRTCGet(uint8_t clientNum) {
    String response = "{\"type\":\"rtcTime\",\"time\":\"";
    response += getRTCTime();
    response += "\",\"available\":";
    response += timeSyncStatus.rtcAvailable ? "true" : "false";
    response += "}";
    
    webSocket.sendTXT(clientNum, response);
}

// RTC 시간 설정
void handleRTCSet(String msg) {
    int timestampIdx = msg.indexOf("\"timestamp\":") + 12;
    String timestampStr = msg.substring(timestampIdx);
    timestampStr = timestampStr.substring(0, timestampStr.indexOf("}"));
    
    uint64_t timestamp = strtoull(timestampStr.c_str(), NULL, 10);
    
    if (timestamp > 0) {
        setRTCTime(timestamp);
        // RTC 설정 후 시스템 시간도 동기화
        syncTimeFromRTC();
    }
}

// 웹소켓 이벤트 핸들러
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);
            break;
            
        case WStype_CONNECTED:
            {
                IPAddress ip = webSocket.remoteIP(num);
                Serial.printf("WebSocket #%u 연결: %d.%d.%d.%d\n", 
                    num, ip[0], ip[1], ip[2], ip[3]);
                sendFileList(num);
                
                // 시간 동기화 상태 전송
                String syncStatus = "{\"type\":\"timeSyncStatus\",\"synchronized\":";
                syncStatus += timeSyncStatus.synchronized ? "true" : "false";
                syncStatus += ",\"syncCount\":" + String(timeSyncStatus.syncCount);
                syncStatus += ",\"rtcAvailable\":";
                syncStatus += timeSyncStatus.rtcAvailable ? "true" : "false";
                syncStatus += "}";
                webSocket.sendTXT(num, syncStatus);
                
                // RTC 시간 전송
                handleRTCGet(num);
            }
            break;
            
        case WStype_TEXT:
            {
                String msg = String((char*)payload);
                
                if (msg.indexOf("\"cmd\":\"setSpeed\"") >= 0) {
                    int speedIdx = msg.indexOf("\"speed\":") + 8;
                    int speed = msg.substring(speedIdx, speedIdx + 1).toInt();
                    
                    if (speed >= 0 && speed < 4) {
                        changeCanSpeed(canSpeedValues[speed]);
                    }
                } 
                else if (msg.indexOf("\"cmd\":\"getFiles\"") >= 0) {
                    sendFileList(num);
                }
                else if (msg.indexOf("\"cmd\":\"startLogging\"") >= 0) {
                    startLogging();
                }
                else if (msg.indexOf("\"cmd\":\"stopLogging\"") >= 0) {
                    stopLogging();
                }
                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;
                    }
                }
                else if (msg.indexOf("\"cmd\":\"syncTime\"") >= 0) {
                    handleTimeSync(msg);
                }
                else if (msg.indexOf("\"cmd\":\"getRTC\"") >= 0) {
                    handleRTCGet(num);
                }
                else if (msg.indexOf("\"cmd\":\"setRTC\"") >= 0) {
                    handleRTCSet(msg);
                }
                else if (msg.indexOf("\"cmd\":\"syncFromRTC\"") >= 0) {
                    syncTimeFromRTC();
                }
            }
            break;
    }
}

// 웹 업데이트 태스크
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("웹 업데이트 태스크 시작");
    
    while (1) {
        uint32_t now = millis();
        
        webSocket.loop();
        
        // 주기 전송 처리
        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;
                lastMsgCount = totalMsgCount;
                lastMsgCountTime = now;
            }
            
            String status = "{\"type\":\"status\",";
            status += "\"logging\":" + String(loggingEnabled ? "true" : "false") + ",";
            status += "\"sdReady\":" + String(sdCardReady ? "true" : "false") + ",";
            status += "\"msgCount\":" + String(totalMsgCount) + ",";
            status += "\"msgSpeed\":" + String(msgPerSecond) + ",";
            status += "\"timeSync\":" + String(timeSyncStatus.synchronized ? "true" : "false") + ",";
            status += "\"syncCount\":" + String(timeSyncStatus.syncCount) + ",";
            status += "\"rtcAvailable\":" + String(timeSyncStatus.rtcAvailable ? "true" : "false") + ",";
            
            if (loggingEnabled && logFile) {
                status += "\"currentFile\":\"" + String(currentFilename) + "\"";
            } else {
                status += "\"currentFile\":\"\"";
            }
            status += "}";
            
            webSocket.broadcastTXT(status);
            lastStatusUpdate = now;
        }
        
        // CAN 메시지 일괄 업데이트
        if (now - lastCanUpdate >= CAN_UPDATE_INTERVAL) {
            String canBatch = "{\"type\":\"canBatch\",\"messages\":[";
            bool first = true;
            
            for (int i = 0; i < RECENT_MSG_COUNT; i++) {
                if (recentData[i].msg.timestamp_us > 0) {
                    CANMessage* msg = &recentData[i].msg;
                    
                    if (!first) canBatch += ",";
                    first = false;
                    
                    canBatch += "{\"id\":\"";
                    if (msg->id < 0x10) canBatch += "0";
                    if (msg->id < 0x100) canBatch += "0";
                    if (msg->id < 0x1000) canBatch += "0";
                    canBatch += String(msg->id, HEX);
                    canBatch += "\",\"dlc\":" + String(msg->dlc);
                    canBatch += ",\"data\":\"";
                    
                    for (int j = 0; j < msg->dlc; j++) {
                        if (msg->data[j] < 0x10) canBatch += "0";
                        canBatch += String(msg->data[j], HEX);
                        if (j < msg->dlc - 1) canBatch += " ";
                    }
                    
                    // 마이크로초 타임스탬프를 밀리초로 변환하여 전송
                    uint64_t timestamp_ms = msg->timestamp_us / 1000;
                    canBatch += "\",\"timestamp\":" + String((uint32_t)timestamp_ms);
                    canBatch += ",\"count\":" + String(recentData[i].count) + "}";
                }
            }
            
            canBatch += "]}";
            
            if (!first) {
                webSocket.broadcastTXT(canBatch);
            }
            
            lastCanUpdate = now;
        }
        
        vTaskDelay(pdMS_TO_TICKS(50));
    }
}

void setup() {
    Serial.begin(115200);
    delay(1000);
    Serial.println("\n========================================");
    Serial.println("  ESP32 CAN Logger with RTC DS3231  ");
    Serial.println("========================================");
    
    memset(recentData, 0, sizeof(recentData));
    memset(txMessages, 0, sizeof(txMessages));
    
    // I2C 초기화 (RTC)
    Wire.begin(I2C_SDA, I2C_SCL);
    
    // RTC 초기화
    if (rtc.begin(&Wire)) {
        Serial.println("✓ RTC DS3231 초기화 완료");
        timeSyncStatus.rtcAvailable = true;
        
        // RTC 손실 확인
        if (rtc.lostPower()) {
            Serial.println("⚠️ RTC 전원 손실 감지 - 시간 설정 필요");
        } else {
            Serial.printf("✓ RTC 시간: %s\n", getRTCTime().c_str());
            // RTC에서 시스템 시간 동기화
            syncTimeFromRTC();
        }
    } else {
        Serial.println("✗ RTC DS3231 초기화 실패");
        timeSyncStatus.rtcAvailable = false;
    }
    
    pinMode(CAN_INT_PIN, INPUT_PULLUP);
    
    hspi.begin(HSPI_SCLK, HSPI_MISO, HSPI_MOSI, HSPI_CS);
    vspi.begin(VSPI_SCLK, VSPI_MISO, VSPI_MOSI, VSPI_CS);
    
    mcp2515.reset();
    mcp2515.setBitrate(CAN_1000KBPS, MCP_8MHZ);
    mcp2515.setNormalMode();
    Serial.println("✓ MCP2515 초기화 완료");
    
    if (SD.begin(VSPI_CS, vspi)) {
        sdCardReady = true;
        Serial.println("✓ SD 카드 초기화 완료");
    } else {
        Serial.println("✗ SD 카드 초기화 실패");
    }
    
    WiFi.softAP(ssid, password);
    Serial.print("✓ AP IP: ");
    Serial.println(WiFi.softAPIP());
    
    webSocket.begin();
    webSocket.onEvent(webSocketEvent);
    
    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("/graph", HTTP_GET, []() {
        server.send_P(200, "text/html", graph_html);
    });

    server.on("/graph-view", HTTP_GET, []() {
        server.send_P(200, "text/html", graph_viewer_html);
    });

    server.on("/download", HTTP_GET, []() {
        if (server.hasArg("file")) {
            String filename = "/" + server.arg("file");
            
            if (SD.exists(filename)) {
                File file = SD.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.begin();
    
    canQueue = xQueueCreate(CAN_QUEUE_SIZE, sizeof(CANMessage));
    sdMutex = xSemaphoreCreateMutex();
    
    if (canQueue == NULL || sdMutex == NULL) {
        Serial.println("✗ RTOS 객체 생성 실패!");
        while (1) delay(1000);
    }
    
    attachInterrupt(digitalPinToInterrupt(CAN_INT_PIN), canISR, FALLING);
    
    xTaskCreatePinnedToCore(canRxTask, "CAN_RX", 4096, NULL, 4, &canRxTaskHandle, 1);
    xTaskCreatePinnedToCore(sdWriteTask, "SD_WRITE", 12288, NULL, 3, &sdWriteTaskHandle, 1);
    xTaskCreatePinnedToCore(sdMonitorTask, "SD_MONITOR", 4096, NULL, 1, NULL, 0);
    xTaskCreatePinnedToCore(webUpdateTask, "WEB_UPDATE", 8192, NULL, 2, &webTaskHandle, 0);
    
    Serial.println("✓ 모든 태스크 시작 완료");
    Serial.println("\n========================================");
    Serial.println("  웹 인터페이스 접속");
    Serial.println("========================================");
    Serial.println("  1. WiFi: Byun_CAN_Logger (12345678)");
    Serial.print("  2. http://");
    Serial.println(WiFi.softAPIP());
    Serial.println("  3. Pages:");
    Serial.println("     - Monitor:  /");
    Serial.println("     - Transmit: /transmit");
    Serial.println("     - Graph:    /graph");
    Serial.println("========================================\n");
    
    if (timeSyncStatus.rtcAvailable) {
        Serial.println("✓ RTC 사용 가능 - 웹페이지에서 RTC 시간 관리 가능");
    } else {
        Serial.println("⚠️ RTC 사용 불가 - 웹페이지에서 시간 동기화 필요");
    }
}

void loop() {
    server.handleClient();
    vTaskDelay(pdMS_TO_TICKS(10));
    
    static uint32_t lastPrint = 0;
    if (millis() - lastPrint > 10000) {
        Serial.printf("[상태] 큐: %d/%d | 로깅: %s | SD: %s | RX: %lu | TX: %lu | 시간동기: %s | RTC: %s\n",
            uxQueueMessagesWaiting(canQueue), CAN_QUEUE_SIZE,
            loggingEnabled ? "ON " : "OFF",
            sdCardReady ? "OK" : "NO",
            totalMsgCount, totalTxCount,
            timeSyncStatus.synchronized ? "OK" : "NO",
            timeSyncStatus.rtcAvailable ? "OK" : "NO");
        lastPrint = millis();
    }
}