00003 전송웹추가
웹페이지 하나 더 추가 하고 싶은데 (포트생성) 이 신규 페이지는 can data를 송신 하는 목적의 페이지로 can 신호id, messagy type, message data(hex) 를 설정할 수 있게 하고 여러 신호들을 조합으로 송신할 수 있게, 그리고 특정 시간마다 송신할 수 있게 기능을 추가 할 수 있어? 해당페이지는 차후 수정이 용이하게 파일도 추가했으면 좋겠어
This commit is contained in:
@@ -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 <Arduino.h>
|
||||
@@ -36,6 +15,7 @@
|
||||
#include <freertos/queue.h>
|
||||
#include <freertos/semphr.h>
|
||||
#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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user