Upload files to "/"
This commit is contained in:
663
ESP32_CAN_Logger.ino
Normal file
663
ESP32_CAN_Logger.ino
Normal file
@@ -0,0 +1,663 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* Author: ESP32 CAN Logger Project
|
||||
* Version: 1.0
|
||||
*/
|
||||
|
||||
#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 "index.h" // HTML 페이지
|
||||
|
||||
// ==================== GPIO 핀 정의 ====================
|
||||
#define CAN_INT_PIN 27
|
||||
#define LOGGING_CONTROL_PIN 17
|
||||
#define LOGGING_STATUS_LED 16
|
||||
#define SD_READY_LED 26
|
||||
|
||||
// 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
|
||||
|
||||
// ==================== 버퍼 설정 ====================
|
||||
#define CAN_QUEUE_SIZE 1000
|
||||
#define FILE_BUFFER_SIZE 8192
|
||||
#define MAX_FILENAME_LEN 32
|
||||
#define WEB_UPDATE_INTERVAL 100
|
||||
|
||||
// ==================== CAN 메시지 구조체 ====================
|
||||
struct CANMessage {
|
||||
uint32_t timestamp;
|
||||
uint32_t id;
|
||||
uint8_t dlc;
|
||||
uint8_t data[8];
|
||||
} __attribute__((packed));
|
||||
|
||||
// ==================== WiFi AP 설정 ====================
|
||||
const char* ssid = "ESP32_CAN_Logger";
|
||||
const char* password = "12345678";
|
||||
|
||||
// ==================== 전역 변수 ====================
|
||||
SPIClass hspi(HSPI);
|
||||
SPIClass vspi(VSPI);
|
||||
MCP2515 mcp2515(HSPI_CS, 10000000, &hspi);
|
||||
|
||||
WebServer server(80);
|
||||
WebSocketsServer webSocket = WebSocketsServer(81);
|
||||
|
||||
QueueHandle_t canQueue;
|
||||
SemaphoreHandle_t sdMutex;
|
||||
TaskHandle_t canRxTaskHandle = NULL;
|
||||
TaskHandle_t sdWriteTaskHandle = NULL;
|
||||
TaskHandle_t controlTaskHandle = 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;
|
||||
uint32_t fileCounter = 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};
|
||||
|
||||
// 실시간 모니터링용
|
||||
#define RECENT_MSG_COUNT 50
|
||||
CANMessage recentMessages[RECENT_MSG_COUNT];
|
||||
uint8_t recentMsgIndex = 0;
|
||||
uint32_t totalMsgCount = 0;
|
||||
uint32_t msgPerSecond = 0;
|
||||
uint32_t lastMsgCountTime = 0;
|
||||
uint32_t lastMsgCount = 0;
|
||||
|
||||
// ==================== 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]);
|
||||
}
|
||||
|
||||
// ==================== 기존 파일 번호 스캔 ====================
|
||||
void scanExistingFiles() {
|
||||
if (!sdCardReady) {
|
||||
fileCounter = 0;
|
||||
Serial.println("SD 카드 없음 - 파일 카운터 0으로 시작");
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t maxFileNumber = 0;
|
||||
bool foundFiles = false;
|
||||
|
||||
File root = SD.open("/");
|
||||
if (!root) {
|
||||
Serial.println("루트 디렉토리 열기 실패");
|
||||
fileCounter = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
File file = root.openNextFile();
|
||||
while (file) {
|
||||
if (!file.isDirectory()) {
|
||||
String name = file.name();
|
||||
if (name.startsWith("/")) name = name.substring(1);
|
||||
|
||||
// canlog_XXXXX.bin 형식 확인
|
||||
if (name.startsWith("canlog_") && (name.endsWith(".bin") || name.endsWith(".BIN"))) {
|
||||
// 숫자 부분 추출 (canlog_00005.bin -> 5)
|
||||
int startIdx = 7; // "canlog_" 길이
|
||||
int endIdx = name.lastIndexOf('.');
|
||||
|
||||
if (endIdx > startIdx) {
|
||||
String numStr = name.substring(startIdx, endIdx);
|
||||
uint32_t fileNum = numStr.toInt();
|
||||
|
||||
if (fileNum > maxFileNumber) {
|
||||
maxFileNumber = fileNum;
|
||||
}
|
||||
foundFiles = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
file.close();
|
||||
file = root.openNextFile();
|
||||
}
|
||||
root.close();
|
||||
|
||||
if (foundFiles) {
|
||||
fileCounter = maxFileNumber + 1;
|
||||
Serial.printf("기존 파일 발견 - 다음 파일 번호: %lu (canlog_%05lu.bin)\n",
|
||||
fileCounter, fileCounter);
|
||||
} else {
|
||||
fileCounter = 0;
|
||||
Serial.println("기존 파일 없음 - 파일 카운터 0으로 시작");
|
||||
}
|
||||
}
|
||||
bool createNewLogFile() {
|
||||
if (logFile) {
|
||||
logFile.flush();
|
||||
logFile.close();
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
|
||||
char filename[MAX_FILENAME_LEN];
|
||||
snprintf(filename, MAX_FILENAME_LEN, "/canlog_%05lu.bin", fileCounter++);
|
||||
|
||||
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);
|
||||
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;
|
||||
}
|
||||
|
||||
// ==================== 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 = millis();
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
recentMessages[recentMsgIndex] = msg;
|
||||
recentMsgIndex = (recentMsgIndex + 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()) {
|
||||
digitalWrite(LOGGING_STATUS_LED, LOW);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
memcpy(&fileBuffer[bufferIndex], &msg, sizeof(CANMessage));
|
||||
bufferIndex += sizeof(CANMessage);
|
||||
|
||||
digitalWrite(LOGGING_STATUS_LED, HIGH);
|
||||
}
|
||||
} else {
|
||||
if (loggingEnabled && bufferIndex > 0) {
|
||||
flushBuffer();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 제어 태스크 ====================
|
||||
void controlTask(void *pvParameters) {
|
||||
bool lastLoggingState = false;
|
||||
|
||||
Serial.println("제어 태스크 시작");
|
||||
|
||||
while (1) {
|
||||
bool currentState = digitalRead(LOGGING_CONTROL_PIN);
|
||||
|
||||
if (currentState != lastLoggingState) {
|
||||
if (currentState == HIGH && sdCardReady) {
|
||||
Serial.println("로깅 시작");
|
||||
|
||||
if (xSemaphoreTake(sdMutex, pdMS_TO_TICKS(1000)) == pdTRUE) {
|
||||
if (createNewLogFile()) {
|
||||
loggingEnabled = true;
|
||||
bufferIndex = 0;
|
||||
}
|
||||
xSemaphoreGive(sdMutex);
|
||||
}
|
||||
} else if (currentState == LOW && loggingEnabled) {
|
||||
Serial.println("로깅 정지");
|
||||
loggingEnabled = false;
|
||||
|
||||
flushBuffer();
|
||||
|
||||
if (xSemaphoreTake(sdMutex, pdMS_TO_TICKS(1000)) == pdTRUE) {
|
||||
if (logFile) {
|
||||
logFile.close();
|
||||
}
|
||||
xSemaphoreGive(sdMutex);
|
||||
}
|
||||
|
||||
digitalWrite(LOGGING_STATUS_LED, LOW);
|
||||
}
|
||||
|
||||
lastLoggingState = currentState;
|
||||
}
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(50));
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== SD 모니터 태스크 ====================
|
||||
void sdMonitorTask(void *pvParameters) {
|
||||
Serial.println("SD 모니터 태스크 시작");
|
||||
|
||||
while (1) {
|
||||
bool cardPresent = SD.begin(VSPI_CS, vspi);
|
||||
|
||||
if (cardPresent != sdCardReady) {
|
||||
sdCardReady = cardPresent;
|
||||
digitalWrite(SD_READY_LED, sdCardReady ? HIGH : LOW);
|
||||
|
||||
if (sdCardReady) {
|
||||
Serial.println("SD 카드 준비됨");
|
||||
// SD 카드가 새로 삽입되면 파일 번호 다시 스캔
|
||||
scanExistingFiles();
|
||||
} else {
|
||||
Serial.println("SD 카드 없음");
|
||||
if (loggingEnabled) {
|
||||
loggingEnabled = false;
|
||||
digitalWrite(LOGGING_STATUS_LED, LOW);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 웹소켓 이벤트 핸들러 ====================
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 파일 목록 전송 함수 ====================
|
||||
void sendFileList(uint8_t clientNum) {
|
||||
String fileList = "{\"type\":\"files\",\"files\":[";
|
||||
|
||||
if (!sdCardReady) {
|
||||
fileList += "],\"error\":\"SD card not ready\"}";
|
||||
webSocket.sendTXT(clientNum, fileList);
|
||||
Serial.println("파일 목록 요청: SD 카드 없음");
|
||||
return;
|
||||
}
|
||||
|
||||
File root = SD.open("/");
|
||||
if (!root) {
|
||||
fileList += "],\"error\":\"Cannot open root directory\"}";
|
||||
webSocket.sendTXT(clientNum, fileList);
|
||||
Serial.println("파일 목록 요청: 루트 디렉토리 열기 실패");
|
||||
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);
|
||||
}
|
||||
|
||||
// ==================== 웹 업데이트 태스크 (0.5초 일괄 전송) ====================
|
||||
void webUpdateTask(void *pvParameters) {
|
||||
uint32_t lastStatusUpdate = 0;
|
||||
uint32_t lastCanUpdate = 0;
|
||||
const uint32_t CAN_UPDATE_INTERVAL = 500; // 0.5초마다 CAN 데이터 전송
|
||||
|
||||
Serial.println("웹 업데이트 태스크 시작");
|
||||
|
||||
while (1) {
|
||||
uint32_t now = millis();
|
||||
|
||||
webSocket.loop();
|
||||
|
||||
// 상태 업데이트 (1초마다)
|
||||
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) + "}";
|
||||
|
||||
webSocket.broadcastTXT(status);
|
||||
lastStatusUpdate = now;
|
||||
}
|
||||
|
||||
// CAN 메시지 일괄 업데이트 (0.5초마다)
|
||||
if (now - lastCanUpdate >= CAN_UPDATE_INTERVAL) {
|
||||
String canBatch = "{\"type\":\"canBatch\",\"messages\":[";
|
||||
bool first = true;
|
||||
|
||||
// 모든 최근 메시지를 JSON 배열로 구성
|
||||
for (int i = 0; i < RECENT_MSG_COUNT; i++) {
|
||||
if (recentMessages[i].timestamp > 0) {
|
||||
CANMessage* msg = &recentMessages[i];
|
||||
|
||||
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 += " ";
|
||||
}
|
||||
|
||||
canBatch += "\",\"timestamp\":" + String(msg->timestamp) + "}";
|
||||
}
|
||||
}
|
||||
|
||||
canBatch += "]}";
|
||||
|
||||
if (!first) { // 메시지가 있을 때만 전송
|
||||
webSocket.broadcastTXT(canBatch);
|
||||
}
|
||||
|
||||
lastCanUpdate = now;
|
||||
}
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(50));
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== SETUP ====================
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
delay(1000);
|
||||
Serial.println("\n\n");
|
||||
Serial.println("========================================");
|
||||
Serial.println(" ESP32 CAN Logger with Web Interface ");
|
||||
Serial.println("========================================");
|
||||
|
||||
// GPIO 초기화
|
||||
pinMode(LOGGING_CONTROL_PIN, INPUT);
|
||||
pinMode(LOGGING_STATUS_LED, OUTPUT);
|
||||
pinMode(SD_READY_LED, OUTPUT);
|
||||
pinMode(CAN_INT_PIN, INPUT_PULLUP);
|
||||
|
||||
digitalWrite(LOGGING_STATUS_LED, LOW);
|
||||
digitalWrite(SD_READY_LED, LOW);
|
||||
|
||||
// SPI 초기화
|
||||
hspi.begin(HSPI_SCLK, HSPI_MISO, HSPI_MOSI, HSPI_CS);
|
||||
vspi.begin(VSPI_SCLK, VSPI_MISO, VSPI_MOSI, VSPI_CS);
|
||||
|
||||
// MCP2515 초기화
|
||||
mcp2515.reset();
|
||||
mcp2515.setBitrate(CAN_1000KBPS, MCP_8MHZ);
|
||||
mcp2515.setNormalMode();
|
||||
Serial.println("✓ MCP2515 초기화 완료 (1Mbps)");
|
||||
|
||||
// SD 카드 초기화
|
||||
if (SD.begin(VSPI_CS, vspi)) {
|
||||
sdCardReady = true;
|
||||
digitalWrite(SD_READY_LED, HIGH);
|
||||
Serial.println("✓ SD 카드 초기화 완료");
|
||||
|
||||
// 기존 파일 번호 스캔
|
||||
scanExistingFiles();
|
||||
} else {
|
||||
Serial.println("✗ SD 카드 초기화 실패");
|
||||
fileCounter = 0;
|
||||
}
|
||||
|
||||
// WiFi AP 시작
|
||||
Serial.println("\nWiFi AP 시작...");
|
||||
WiFi.softAP(ssid, password);
|
||||
Serial.print("✓ AP IP 주소: ");
|
||||
Serial.println(WiFi.softAPIP());
|
||||
|
||||
// 웹소켓 설정
|
||||
webSocket.begin();
|
||||
webSocket.onEvent(webSocketEvent);
|
||||
Serial.println("✓ WebSocket 서버 시작 (포트 81)");
|
||||
|
||||
// HTTP 핸들러
|
||||
server.on("/", HTTP_GET, []() {
|
||||
server.send_P(200, "text/html", index_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) {
|
||||
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();
|
||||
Serial.println("✓ 웹 서버 시작 (포트 80)");
|
||||
|
||||
// RTOS 객체 생성
|
||||
canQueue = xQueueCreate(CAN_QUEUE_SIZE, sizeof(CANMessage));
|
||||
sdMutex = xSemaphoreCreateMutex();
|
||||
|
||||
if (canQueue == NULL || sdMutex == NULL) {
|
||||
Serial.println("✗ RTOS 객체 생성 실패!");
|
||||
while (1) delay(1000);
|
||||
}
|
||||
Serial.println("✓ RTOS 객체 생성 완료");
|
||||
|
||||
// 인터럽트 설정
|
||||
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(controlTask, "CONTROL", 8192, NULL, 2, &controlTaskHandle, 0);
|
||||
xTaskCreatePinnedToCore(sdMonitorTask, "SD_MONITOR", 4096, NULL, 1, NULL, 0);
|
||||
xTaskCreatePinnedToCore(webUpdateTask, "WEB_UPDATE", 8192, NULL, 2, &webTaskHandle, 0);
|
||||
|
||||
Serial.println("✓ 모든 RTOS 태스크 시작 완료");
|
||||
|
||||
Serial.println("\n========================================");
|
||||
Serial.println(" GPIO 핀 설정");
|
||||
Serial.println("========================================");
|
||||
Serial.println(" GPIO17: 로깅 제어 (HIGH=시작, LOW=정지)");
|
||||
Serial.println(" GPIO16: 로깅 상태 LED");
|
||||
Serial.println(" GPIO26: SD 카드 준비 LED");
|
||||
Serial.println(" GPIO27: CAN 인터럽트");
|
||||
|
||||
Serial.println("\n========================================");
|
||||
Serial.println(" 웹 인터페이스 접속 방법");
|
||||
Serial.println("========================================");
|
||||
Serial.println(" 1. WiFi 연결: ESP32_CAN_Logger");
|
||||
Serial.println(" 2. 비밀번호: 12345678");
|
||||
Serial.print(" 3. 브라우저: http://");
|
||||
Serial.println(WiFi.softAPIP());
|
||||
Serial.println("========================================\n");
|
||||
}
|
||||
|
||||
// ==================== LOOP ====================
|
||||
void loop() {
|
||||
server.handleClient();
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
|
||||
// 디버그 정보 (10초마다)
|
||||
static uint32_t lastPrint = 0;
|
||||
if (millis() - lastPrint > 10000) {
|
||||
Serial.printf("[상태] 큐: %d/%d | 로깅: %s | SD: %s | 총: %lu msg | 속도: %lu msg/s\n",
|
||||
uxQueueMessagesWaiting(canQueue),
|
||||
CAN_QUEUE_SIZE,
|
||||
loggingEnabled ? "ON " : "OFF",
|
||||
sdCardReady ? "Ready " : "Not Ready",
|
||||
totalMsgCount,
|
||||
msgPerSecond
|
||||
);
|
||||
lastPrint = millis();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user