RTC추가
주요 변경사항
하드웨어 연결
RTC DS3231: I²C (SDA: GPIO25, SCL: GPIO26)
추가된 기능
1. RTC 초기화 및 관리
DS3231 RTC 자동 감지
전원 손실 감지 및 경고
RTC에서 시스템 시간 자동 동기화
2. 웹 인터페이스 (모니터 페이지)
RTC 읽기 버튼: RTC의 현재 시간 확인
RTC 설정 버튼: 웹 브라우저 시간을 RTC에 설정
RTC→시스템 버튼: RTC 시간을 ESP32 시스템 시간에 동기화
RTC 사용 가능 여부 실시간 표시
RTC 시간 실시간 표시
3. 시간 동기화 옵션
웹 시간 동기화: 브라우저 시간으로 ESP32 동기화 (기존)
RTC 시간 설정: 브라우저 시간을 RTC에 저장
RTC→시스템: RTC 시간으로 ESP32 동기화
필요한 라이브러리
Arduino IDE에서 다음 라이브러리를 설치하세요:
RTClib by Adafruit
동작 방식
ESP32 부팅 시 RTC 자동 감지
RTC가 있으면 자동으로 시스템 시간 동기화
웹페이지에서 RTC 상태 확인 가능
로깅 파일명에 정확한 시간 반영
이제 인터넷 없이도 RTC를 통해 정확한 시간 관리가 가능합니다! 🕰️
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Byun CAN Logger with Web Interface + Time Synchronization
|
||||
* Version: 1.2
|
||||
* Byun CAN Logger with Web Interface + RTC DS3231
|
||||
* Version: 1.3
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
@@ -16,6 +16,8 @@
|
||||
#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"
|
||||
@@ -37,6 +39,10 @@
|
||||
#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
|
||||
@@ -75,7 +81,8 @@ struct TimeSyncStatus {
|
||||
uint64_t lastSyncTime;
|
||||
int32_t offsetUs;
|
||||
uint32_t syncCount;
|
||||
} timeSyncStatus = {false, 0, 0, 0};
|
||||
bool rtcAvailable;
|
||||
} timeSyncStatus = {false, 0, 0, 0, false};
|
||||
|
||||
// WiFi AP 설정
|
||||
const char* ssid = "Byun_CAN_Logger";
|
||||
@@ -85,6 +92,7 @@ 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);
|
||||
@@ -125,7 +133,61 @@ uint64_t getMicrosecondTimestamp() {
|
||||
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;
|
||||
@@ -143,7 +205,7 @@ void setSystemTime(uint64_t timestampMs) {
|
||||
char timeStr[64];
|
||||
strftime(timeStr, sizeof(timeStr), "%Y-%m-%d %H:%M:%S", &timeinfo);
|
||||
|
||||
Serial.printf("⏰ 시간 동기화 완료: %s.%03d (동기화 횟수: %u)\n",
|
||||
Serial.printf("⏰ 웹에서 시간 동기화: %s.%03d (동기화 횟수: %u)\n",
|
||||
timeStr, (int)(tv.tv_usec / 1000), timeSyncStatus.syncCount);
|
||||
}
|
||||
|
||||
@@ -162,6 +224,8 @@ 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 인터럽트 핸들러
|
||||
@@ -219,7 +283,7 @@ bool createNewLogFile() {
|
||||
|
||||
// 시간 동기화 경고
|
||||
if (!timeSyncStatus.synchronized) {
|
||||
Serial.println("⚠️ 경고: 시간이 동기화되지 않았습니다! 웹페이지에서 시간 동기화를 실행하세요.");
|
||||
Serial.println("⚠️ 경고: 시간이 동기화되지 않았습니다! 웹페이지에서 시간 동기화를 실행하세요.");
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -530,7 +594,7 @@ void handleStopMessage(String msg) {
|
||||
}
|
||||
}
|
||||
|
||||
// 시간 동기화 처리
|
||||
// 시간 동기화 처리 (웹에서)
|
||||
void handleTimeSync(String msg) {
|
||||
int timestampIdx = msg.indexOf("\"timestamp\":") + 12;
|
||||
String timestampStr = msg.substring(timestampIdx);
|
||||
@@ -543,6 +607,32 @@ void handleTimeSync(String msg) {
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
@@ -560,8 +650,14 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length
|
||||
// 시간 동기화 상태 전송
|
||||
String syncStatus = "{\"type\":\"timeSyncStatus\",\"synchronized\":";
|
||||
syncStatus += timeSyncStatus.synchronized ? "true" : "false";
|
||||
syncStatus += ",\"syncCount\":" + String(timeSyncStatus.syncCount) + "}";
|
||||
syncStatus += ",\"syncCount\":" + String(timeSyncStatus.syncCount);
|
||||
syncStatus += ",\"rtcAvailable\":";
|
||||
syncStatus += timeSyncStatus.rtcAvailable ? "true" : "false";
|
||||
syncStatus += "}";
|
||||
webSocket.sendTXT(num, syncStatus);
|
||||
|
||||
// RTC 시간 전송
|
||||
handleRTCGet(num);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -603,6 +699,15 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length
|
||||
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;
|
||||
}
|
||||
@@ -660,6 +765,7 @@ void webUpdateTask(void *pvParameters) {
|
||||
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) + "\"";
|
||||
@@ -722,12 +828,33 @@ void setup() {
|
||||
Serial.begin(115200);
|
||||
delay(1000);
|
||||
Serial.println("\n========================================");
|
||||
Serial.println(" ESP32 CAN Logger with Time Sync ");
|
||||
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);
|
||||
@@ -822,7 +949,12 @@ void setup() {
|
||||
Serial.println(" - Transmit: /transmit");
|
||||
Serial.println(" - Graph: /graph");
|
||||
Serial.println("========================================\n");
|
||||
Serial.println("⚠️ 시간 동기화를 위해 웹페이지에서 '⏰ 시간 동기화' 버튼을 클릭하세요");
|
||||
|
||||
if (timeSyncStatus.rtcAvailable) {
|
||||
Serial.println("✓ RTC 사용 가능 - 웹페이지에서 RTC 시간 관리 가능");
|
||||
} else {
|
||||
Serial.println("⚠️ RTC 사용 불가 - 웹페이지에서 시간 동기화 필요");
|
||||
}
|
||||
}
|
||||
|
||||
void loop() {
|
||||
@@ -831,12 +963,13 @@ void loop() {
|
||||
|
||||
static uint32_t lastPrint = 0;
|
||||
if (millis() - lastPrint > 10000) {
|
||||
Serial.printf("[상태] 큐: %d/%d | 로깅: %s | SD: %s | RX: %lu | TX: %lu | 시간동기: %s\n",
|
||||
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.synchronized ? "OK" : "NO",
|
||||
timeSyncStatus.rtcAvailable ? "OK" : "NO");
|
||||
lastPrint = millis();
|
||||
}
|
||||
}
|
||||
@@ -59,11 +59,6 @@ const char index_html[] PROGMEM = R"rawliteral(
|
||||
padding: 15px 20px;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 15px;
|
||||
box-shadow: 0 4px 15px rgba(240, 147, 251, 0.4);
|
||||
}
|
||||
.time-sync-info {
|
||||
@@ -71,6 +66,7 @@ const char index_html[] PROGMEM = R"rawliteral(
|
||||
gap: 20px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.time-info-item {
|
||||
display: flex;
|
||||
@@ -87,25 +83,66 @@ const char index_html[] PROGMEM = R"rawliteral(
|
||||
font-size: 1.1em;
|
||||
font-weight: 700;
|
||||
}
|
||||
.btn-time-sync {
|
||||
|
||||
.rtc-panel {
|
||||
background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%);
|
||||
padding: 15px;
|
||||
border-radius: 10px;
|
||||
margin-top: 15px;
|
||||
border-left: 4px solid #667eea;
|
||||
}
|
||||
.rtc-panel h3 {
|
||||
color: #333;
|
||||
margin-bottom: 10px;
|
||||
font-size: 1em;
|
||||
}
|
||||
.rtc-controls {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.rtc-time {
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
padding: 10px 15px;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 1.1em;
|
||||
font-weight: 600;
|
||||
color: #667eea;
|
||||
text-align: center;
|
||||
}
|
||||
.rtc-status {
|
||||
padding: 6px 12px;
|
||||
background: white;
|
||||
border-radius: 5px;
|
||||
font-size: 0.85em;
|
||||
font-weight: 600;
|
||||
}
|
||||
.rtc-status.available { color: #11998e; }
|
||||
.rtc-status.unavailable { color: #e74c3c; }
|
||||
|
||||
.btn-time-sync, .btn-rtc {
|
||||
background: white;
|
||||
color: #f5576c;
|
||||
padding: 12px 24px;
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 1em;
|
||||
font-size: 0.9em;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
box-shadow: 0 3px 10px rgba(0,0,0,0.2);
|
||||
}
|
||||
.btn-time-sync:hover {
|
||||
.btn-rtc {
|
||||
color: #667eea;
|
||||
}
|
||||
.btn-time-sync:hover, .btn-rtc:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 6px 20px rgba(0,0,0,0.3);
|
||||
}
|
||||
.btn-time-sync:active {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.status-grid {
|
||||
display: grid;
|
||||
@@ -243,8 +280,6 @@ const char index_html[] PROGMEM = R"rawliteral(
|
||||
table { min-width: 400px; }
|
||||
th, td { padding: 6px 4px; font-size: 0.75em; }
|
||||
.time-sync-banner {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
padding: 12px 15px;
|
||||
}
|
||||
.time-sync-info {
|
||||
@@ -253,6 +288,13 @@ const char index_html[] PROGMEM = R"rawliteral(
|
||||
.time-value {
|
||||
font-size: 1em;
|
||||
}
|
||||
.rtc-controls {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
.rtc-time {
|
||||
min-width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
@@ -260,7 +302,7 @@ const char index_html[] PROGMEM = R"rawliteral(
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>Byun CAN Logger</h1>
|
||||
<p>Real-time CAN Bus Monitor & Data Logger with Time Sync</p>
|
||||
<p>Real-time CAN Bus Monitor & Data Logger with RTC</p>
|
||||
</div>
|
||||
|
||||
<div class="nav">
|
||||
@@ -281,7 +323,18 @@ const char index_html[] PROGMEM = R"rawliteral(
|
||||
<span class="time-value" id="current-time">--:--:--</span>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn-time-sync" onclick="syncTime()">⏰ 시간 동기화</button>
|
||||
<button class="btn-time-sync" onclick="syncTime()">⏰ 웹 시간 동기화</button>
|
||||
|
||||
<div class="rtc-panel">
|
||||
<h3>🕰️ RTC DS3231 모듈</h3>
|
||||
<div class="rtc-controls">
|
||||
<div class="rtc-time" id="rtc-time">--:--:--</div>
|
||||
<span class="rtc-status unavailable" id="rtc-status">확인 중...</span>
|
||||
<button class="btn-rtc" onclick="getRTCTime()">📖 RTC 읽기</button>
|
||||
<button class="btn-rtc" onclick="setRTCTime()">📝 RTC 설정</button>
|
||||
<button class="btn-rtc" onclick="syncFromRTC()">🔄 RTC→시스템</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="status-grid">
|
||||
@@ -361,6 +414,7 @@ const char index_html[] PROGMEM = R"rawliteral(
|
||||
let canMessages = {};
|
||||
let messageOrder = [];
|
||||
let lastMessageData = {};
|
||||
let rtcAvailable = false;
|
||||
|
||||
const speedNames = ['125 Kbps', '250 Kbps', '500 Kbps', '1 Mbps'];
|
||||
|
||||
@@ -376,7 +430,7 @@ const char index_html[] PROGMEM = R"rawliteral(
|
||||
setInterval(updateCurrentTime, 1000);
|
||||
updateCurrentTime();
|
||||
|
||||
// 시간 동기화 함수
|
||||
// 웹 시간 동기화
|
||||
function syncTime() {
|
||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||
const timestamp = Date.now();
|
||||
@@ -398,39 +452,63 @@ const char index_html[] PROGMEM = R"rawliteral(
|
||||
document.getElementById('sync-status').textContent = '✓ ' + dateStr;
|
||||
}, 200);
|
||||
|
||||
console.log('시간 동기화 전송:', new Date(timestamp).toLocaleString());
|
||||
console.log('웹 시간 동기화 전송:', new Date(timestamp).toLocaleString());
|
||||
} else {
|
||||
alert('WebSocket이 연결되지 않았습니다!');
|
||||
}
|
||||
}
|
||||
|
||||
function saveCanSpeed() {
|
||||
const speed = document.getElementById('can-speed').value;
|
||||
try {
|
||||
window.localStorage.setItem('canSpeed', speed);
|
||||
console.log('Saved CAN speed:', speedNames[speed]);
|
||||
} catch(e) {
|
||||
console.error('Failed to save CAN speed:', e);
|
||||
// RTC 시간 읽기
|
||||
function getRTCTime() {
|
||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(JSON.stringify({ cmd: 'getRTC' }));
|
||||
console.log('RTC 시간 요청');
|
||||
} else {
|
||||
alert('WebSocket이 연결되지 않았습니다!');
|
||||
}
|
||||
}
|
||||
|
||||
function loadCanSpeed() {
|
||||
try {
|
||||
const savedSpeed = window.localStorage.getItem('canSpeed');
|
||||
if (savedSpeed !== null) {
|
||||
document.getElementById('can-speed').value = savedSpeed;
|
||||
console.log('Restored CAN speed:', speedNames[savedSpeed]);
|
||||
|
||||
const statusSpan = document.getElementById('speed-status');
|
||||
if (statusSpan) {
|
||||
statusSpan.textContent = '(Restored: ' + speedNames[savedSpeed] + ')';
|
||||
setTimeout(() => {
|
||||
statusSpan.textContent = '';
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
} catch(e) {
|
||||
console.error('Failed to load CAN speed:', e);
|
||||
// RTC에 현재 웹 시간 설정
|
||||
function setRTCTime() {
|
||||
if (!rtcAvailable) {
|
||||
alert('RTC를 사용할 수 없습니다!');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!confirm('현재 웹 브라우저 시간을 RTC에 설정하시겠습니까?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||
const timestamp = Date.now();
|
||||
ws.send(JSON.stringify({
|
||||
cmd: 'setRTC',
|
||||
timestamp: timestamp
|
||||
}));
|
||||
|
||||
console.log('RTC 시간 설정:', new Date(timestamp).toLocaleString());
|
||||
|
||||
// 설정 후 읽기
|
||||
setTimeout(() => { getRTCTime(); }, 500);
|
||||
} else {
|
||||
alert('WebSocket이 연결되지 않았습니다!');
|
||||
}
|
||||
}
|
||||
|
||||
// RTC에서 시스템 시간 동기화
|
||||
function syncFromRTC() {
|
||||
if (!rtcAvailable) {
|
||||
alert('RTC를 사용할 수 없습니다!');
|
||||
return;
|
||||
}
|
||||
|
||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(JSON.stringify({ cmd: 'syncFromRTC' }));
|
||||
console.log('RTC→시스템 동기화 요청');
|
||||
|
||||
setTimeout(() => { getRTCTime(); }, 500);
|
||||
} else {
|
||||
alert('WebSocket이 연결되지 않았습니다!');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -441,12 +519,7 @@ const char index_html[] PROGMEM = R"rawliteral(
|
||||
console.log('WebSocket connected');
|
||||
clearInterval(reconnectInterval);
|
||||
setTimeout(() => { refreshFiles(); }, 500);
|
||||
|
||||
// 연결 직후 자동 시간 동기화
|
||||
setTimeout(() => {
|
||||
syncTime();
|
||||
console.log('자동 시간 동기화 실행');
|
||||
}, 1000);
|
||||
setTimeout(() => { getRTCTime(); }, 1000);
|
||||
};
|
||||
|
||||
ws.onclose = function() {
|
||||
@@ -478,10 +551,33 @@ const char index_html[] PROGMEM = R"rawliteral(
|
||||
card.classList.add('status-on');
|
||||
card.classList.remove('status-off');
|
||||
}
|
||||
|
||||
if (data.rtcAvailable !== undefined) {
|
||||
rtcAvailable = data.rtcAvailable;
|
||||
updateRTCStatus(rtcAvailable);
|
||||
}
|
||||
} else if (data.type === 'rtcTime') {
|
||||
document.getElementById('rtc-time').textContent = data.time;
|
||||
|
||||
if (data.available !== undefined) {
|
||||
rtcAvailable = data.available;
|
||||
updateRTCStatus(rtcAvailable);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function updateRTCStatus(available) {
|
||||
const statusEl = document.getElementById('rtc-status');
|
||||
if (available) {
|
||||
statusEl.textContent = '✓ 사용 가능';
|
||||
statusEl.className = 'rtc-status available';
|
||||
} else {
|
||||
statusEl.textContent = '✗ 사용 불가';
|
||||
statusEl.className = 'rtc-status unavailable';
|
||||
}
|
||||
}
|
||||
|
||||
function updateStatus(data) {
|
||||
const loggingCard = document.getElementById('logging-status');
|
||||
const sdCard = document.getElementById('sd-status');
|
||||
@@ -520,6 +616,11 @@ const char index_html[] PROGMEM = R"rawliteral(
|
||||
document.getElementById('sync-count').textContent = data.syncCount;
|
||||
}
|
||||
|
||||
if (data.rtcAvailable !== undefined) {
|
||||
rtcAvailable = data.rtcAvailable;
|
||||
updateRTCStatus(rtcAvailable);
|
||||
}
|
||||
|
||||
if (data.currentFile && data.currentFile !== '') {
|
||||
fileCard.classList.add('status-on');
|
||||
fileCard.classList.remove('status-off');
|
||||
@@ -649,8 +750,6 @@ const char index_html[] PROGMEM = R"rawliteral(
|
||||
|
||||
ws.send(JSON.stringify({cmd: 'setSpeed', speed: parseInt(speed)}));
|
||||
|
||||
saveCanSpeed();
|
||||
|
||||
const statusSpan = document.getElementById('speed-status');
|
||||
if (statusSpan) {
|
||||
statusSpan.textContent = '✓ Applied: ' + speedName;
|
||||
@@ -695,10 +794,6 @@ const char index_html[] PROGMEM = R"rawliteral(
|
||||
window.location.href = '/download?file=' + encodeURIComponent(filename);
|
||||
}
|
||||
|
||||
window.addEventListener('load', function() {
|
||||
loadCanSpeed();
|
||||
});
|
||||
|
||||
initWebSocket();
|
||||
setTimeout(() => { refreshFiles(); }, 2000);
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user