Upload files to "/"
This commit is contained in:
451
AquaLED_Controller.ino
Normal file
451
AquaLED_Controller.ino
Normal file
@@ -0,0 +1,451 @@
|
||||
/*
|
||||
* ============================================================
|
||||
* AquaLED Controller - ESP32-WROOM-32 (30pin)
|
||||
* GPIO13 -> PWM LED (수초 어항용)
|
||||
* ESP32 Arduino Core 3.x 전용
|
||||
* 보드: ESP32 Dev Module
|
||||
* ============================================================
|
||||
*/
|
||||
|
||||
#include <WiFi.h>
|
||||
#include <WebServer.h>
|
||||
#include <Preferences.h>
|
||||
#include <time.h>
|
||||
|
||||
#define LED_PIN 13
|
||||
#define PWM_RESOLUTION 8
|
||||
|
||||
const char* AP_SSID = "AquaLED_AP";
|
||||
const char* AP_PASS = "aqua1234";
|
||||
|
||||
const char* NTP_SERVER = "pool.ntp.org";
|
||||
const long GMT_OFFSET = 9L * 3600L;
|
||||
const int DST_OFFSET = 0;
|
||||
|
||||
Preferences prefs;
|
||||
WebServer server(80);
|
||||
|
||||
struct Config {
|
||||
int duty;
|
||||
int freq;
|
||||
int onH, onM;
|
||||
int offH, offM;
|
||||
bool scheduleOn;
|
||||
bool manualState;
|
||||
String staSSID;
|
||||
String staPASS;
|
||||
};
|
||||
|
||||
Config cfg;
|
||||
bool ntpSynced = false;
|
||||
bool staConnected = false;
|
||||
bool needReconnect = false;
|
||||
|
||||
void loadConfig() {
|
||||
prefs.begin("aled", true);
|
||||
cfg.duty = prefs.getInt ("duty", 200);
|
||||
cfg.freq = prefs.getInt ("freq", 1000);
|
||||
cfg.onH = prefs.getInt ("onH", 8);
|
||||
cfg.onM = prefs.getInt ("onM", 0);
|
||||
cfg.offH = prefs.getInt ("offH", 22);
|
||||
cfg.offM = prefs.getInt ("offM", 0);
|
||||
cfg.scheduleOn = prefs.getBool ("sched", true);
|
||||
cfg.manualState = prefs.getBool ("mstate", false);
|
||||
cfg.staSSID = prefs.getString("ssid", "");
|
||||
cfg.staPASS = prefs.getString("pass", "");
|
||||
prefs.end();
|
||||
}
|
||||
|
||||
void saveConfig() {
|
||||
prefs.begin("aled", false);
|
||||
prefs.putInt ("duty", cfg.duty);
|
||||
prefs.putInt ("freq", cfg.freq);
|
||||
prefs.putInt ("onH", cfg.onH);
|
||||
prefs.putInt ("onM", cfg.onM);
|
||||
prefs.putInt ("offH", cfg.offH);
|
||||
prefs.putInt ("offM", cfg.offM);
|
||||
prefs.putBool ("sched", cfg.scheduleOn);
|
||||
prefs.putBool ("mstate", cfg.manualState);
|
||||
prefs.putString("ssid", cfg.staSSID);
|
||||
prefs.putString("pass", cfg.staPASS);
|
||||
prefs.end();
|
||||
}
|
||||
|
||||
// Core 3.x: 핀 번호로 PWM 제어
|
||||
void applyPWM(int duty) {
|
||||
ledcWrite(LED_PIN, constrain(duty, 0, 255));
|
||||
}
|
||||
|
||||
void reconfigPWM(int freq, int duty) {
|
||||
ledcDetach(LED_PIN);
|
||||
ledcAttach(LED_PIN, freq, PWM_RESOLUTION);
|
||||
ledcWrite(LED_PIN, constrain(duty, 0, 255));
|
||||
}
|
||||
|
||||
void startAP() {
|
||||
WiFi.softAP(AP_SSID, AP_PASS);
|
||||
Serial.printf("[AP] SSID: %s IP: %s\n", AP_SSID, WiFi.softAPIP().toString().c_str());
|
||||
}
|
||||
|
||||
bool connectSTA(const String& ssid, const String& pass, int timeoutMs = 10000) {
|
||||
if (ssid.length() == 0) return false;
|
||||
Serial.printf("[STA] 연결 시도: %s\n", ssid.c_str());
|
||||
WiFi.begin(ssid.c_str(), pass.c_str());
|
||||
unsigned long t = millis();
|
||||
while (WiFi.status() != WL_CONNECTED && millis() - t < (unsigned long)timeoutMs) {
|
||||
delay(300);
|
||||
}
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
Serial.printf("[STA] 연결됨 IP: %s\n", WiFi.localIP().toString().c_str());
|
||||
configTime(GMT_OFFSET, DST_OFFSET, NTP_SERVER);
|
||||
return true;
|
||||
}
|
||||
Serial.println("[STA] 연결 실패");
|
||||
return false;
|
||||
}
|
||||
|
||||
void tryNTP() {
|
||||
if (ntpSynced) return;
|
||||
struct tm t;
|
||||
if (getLocalTime(&t, 2000)) {
|
||||
ntpSynced = true;
|
||||
Serial.printf("[NTP] 동기화: %04d-%02d-%02d %02d:%02d:%02d\n",
|
||||
t.tm_year+1900, t.tm_mon+1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec);
|
||||
}
|
||||
}
|
||||
|
||||
void checkSchedule() {
|
||||
if (!cfg.scheduleOn || !ntpSynced) return;
|
||||
struct tm t;
|
||||
if (!getLocalTime(&t, 500)) return;
|
||||
|
||||
int now = t.tm_hour * 60 + t.tm_min;
|
||||
int onT = cfg.onH * 60 + cfg.onM;
|
||||
int offT = cfg.offH * 60 + cfg.offM;
|
||||
|
||||
bool shouldOn;
|
||||
if (onT < offT) {
|
||||
shouldOn = (now >= onT && now < offT);
|
||||
} else {
|
||||
shouldOn = (now >= onT || now < offT);
|
||||
}
|
||||
|
||||
static bool prevState = false;
|
||||
if (shouldOn != prevState) {
|
||||
prevState = shouldOn;
|
||||
applyPWM(shouldOn ? cfg.duty : 0);
|
||||
Serial.printf("[SCHED] LED %s\n", shouldOn ? "ON" : "OFF");
|
||||
}
|
||||
}
|
||||
|
||||
String getTimeStr() {
|
||||
if (!ntpSynced) return "NTP 미동기화";
|
||||
struct tm t;
|
||||
if (!getLocalTime(&t, 500)) return "시간 오류";
|
||||
char buf[32];
|
||||
strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &t);
|
||||
return String(buf);
|
||||
}
|
||||
|
||||
String buildPage() {
|
||||
char onBuf[6], offBuf[6];
|
||||
snprintf(onBuf, 6, "%02d:%02d", cfg.onH, cfg.onM);
|
||||
snprintf(offBuf, 6, "%02d:%02d", cfg.offH, cfg.offM);
|
||||
|
||||
int pct = cfg.duty * 100 / 255;
|
||||
String staIP = staConnected ? WiFi.localIP().toString() : "-";
|
||||
String timeStr = getTimeStr();
|
||||
String ntpBadge = ntpSynced
|
||||
? "<span class='badge ok'>동기화됨</span>"
|
||||
: "<span class='badge err'>미동기화</span>";
|
||||
String staBadge = staConnected
|
||||
? "<span class='badge ok'>연결됨</span>"
|
||||
: "<span class='badge err'>미연결</span>";
|
||||
String schedSel = cfg.scheduleOn ? "checked" : "";
|
||||
String manualSel = !cfg.scheduleOn ? "checked" : "";
|
||||
String manStyle = cfg.scheduleOn ? "none" : "block";
|
||||
String schedStyle = cfg.scheduleOn ? "block" : "none";
|
||||
String ledState = "";
|
||||
if (!cfg.scheduleOn) {
|
||||
ledState = cfg.manualState
|
||||
? "<span class='badge ok'>ON</span>"
|
||||
: "<span class='badge err'>OFF</span>";
|
||||
}
|
||||
|
||||
String html = "<!DOCTYPE html><html lang='ko'><head>"
|
||||
"<meta charset='UTF-8'>"
|
||||
"<meta name='viewport' content='width=device-width,initial-scale=1'>"
|
||||
"<title>AquaLED Controller</title>"
|
||||
"<style>"
|
||||
"@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&family=Noto+Sans+KR:wght@300;400;500&display=swap');"
|
||||
":root{"
|
||||
"--bg:#050f1a;--card:#0b1e30;--border:#0e3050;"
|
||||
"--acc:#00e5ff;--acc2:#0088aa;--text:#cde8ff;"
|
||||
"--muted:#4a7a9b;--on:#00ff88;--off:#ff4455;"
|
||||
"}"
|
||||
"*{box-sizing:border-box;margin:0;padding:0}"
|
||||
"body{background:var(--bg);color:var(--text);font-family:'Noto Sans KR',sans-serif;min-height:100vh;"
|
||||
"background-image:radial-gradient(ellipse at 20% 10%,#001e3440 0%,transparent 60%),"
|
||||
"radial-gradient(ellipse at 80% 90%,#003a5530 0%,transparent 60%)}"
|
||||
"header{background:linear-gradient(135deg,#061525,#0b2540);"
|
||||
"border-bottom:1px solid var(--border);padding:18px 20px;text-align:center;"
|
||||
"position:relative;overflow:hidden}"
|
||||
"header::before{content:'';position:absolute;inset:0;"
|
||||
"background:repeating-linear-gradient(90deg,transparent,transparent 40px,#00aaff08 40px,#00aaff08 41px);"
|
||||
"pointer-events:none}"
|
||||
"header h1{font-family:'Orbitron',monospace;font-size:1.35em;letter-spacing:.1em;"
|
||||
"color:var(--acc);text-shadow:0 0 20px #00e5ff66}"
|
||||
"header h1 span{font-size:.6em;color:var(--muted);display:block;margin-top:2px;"
|
||||
"font-family:'Noto Sans KR',sans-serif;font-weight:300;letter-spacing:.05em}"
|
||||
".meta{display:flex;gap:16px;justify-content:center;flex-wrap:wrap;"
|
||||
"font-size:.78em;color:var(--muted);margin-top:10px}"
|
||||
".meta strong{color:var(--text)}"
|
||||
".badge{display:inline-block;padding:1px 8px;border-radius:20px;font-size:.75em;font-weight:500}"
|
||||
".badge.ok{background:#00ff8818;color:var(--on);border:1px solid #00ff8844}"
|
||||
".badge.err{background:#ff445518;color:var(--off);border:1px solid #ff445544}"
|
||||
".wrap{max-width:580px;margin:0 auto;padding:16px 14px}"
|
||||
".card{background:var(--card);border:1px solid var(--border);border-radius:14px;"
|
||||
"padding:20px;margin-bottom:14px;position:relative;overflow:hidden}"
|
||||
".card::before{content:'';position:absolute;top:0;left:0;right:0;height:2px;"
|
||||
"background:linear-gradient(90deg,transparent,var(--acc),transparent)}"
|
||||
".card h2{font-family:'Orbitron',monospace;font-size:.85em;letter-spacing:.12em;"
|
||||
"color:var(--acc);margin-bottom:16px;display:flex;align-items:center;gap:8px}"
|
||||
"label{display:block;font-size:.8em;color:var(--muted);margin:12px 0 4px}"
|
||||
"input[type=range]{width:100%;accent-color:var(--acc);cursor:pointer;height:4px}"
|
||||
"input[type=number],input[type=text],input[type=password],input[type=time]{"
|
||||
"width:100%;padding:9px 12px;background:#061525;border:1px solid var(--border);"
|
||||
"border-radius:8px;color:var(--text);font-size:.9em;outline:none;transition:border-color .2s}"
|
||||
"input:focus{border-color:var(--acc)}"
|
||||
".vrow{display:flex;justify-content:space-between;align-items:center;margin-top:4px;font-size:.82em}"
|
||||
".vrow .val{font-family:'Orbitron',monospace;color:var(--acc)}"
|
||||
".grid2{display:grid;grid-template-columns:1fr 1fr;gap:12px}"
|
||||
".seg{display:flex;border-radius:8px;overflow:hidden;border:1px solid var(--border);margin-top:6px}"
|
||||
".seg input{display:none}"
|
||||
".seg label{flex:1;text-align:center;padding:8px;cursor:pointer;background:#061525;"
|
||||
"color:var(--muted);font-size:.85em;margin:0;transition:.2s}"
|
||||
".seg input:checked+label{background:var(--acc2);color:#fff}"
|
||||
".btn{display:block;width:100%;padding:11px;border:none;border-radius:8px;font-size:.92em;"
|
||||
"cursor:pointer;margin-top:14px;font-family:'Noto Sans KR',sans-serif;font-weight:500;"
|
||||
"transition:opacity .2s,transform .1s}"
|
||||
".btn:active{transform:scale(.98);opacity:.85}"
|
||||
".btn-primary{background:linear-gradient(135deg,var(--acc2),#00ccdd);color:#fff}"
|
||||
".btn-on{background:linear-gradient(135deg,#007744,#00bb66);color:#fff}"
|
||||
".btn-off{background:linear-gradient(135deg,#880022,#cc3344);color:#fff}"
|
||||
".manual-row{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:10px}"
|
||||
".info-row{display:flex;justify-content:space-between;align-items:center;"
|
||||
"padding:6px 0;border-bottom:1px solid var(--border);font-size:.84em}"
|
||||
".info-row:last-child{border-bottom:none}"
|
||||
".info-row .lbl{color:var(--muted)}"
|
||||
".info-row .val{font-family:'Orbitron',monospace;font-size:.82em;color:var(--acc)}"
|
||||
".fish{position:absolute;right:16px;top:14px;font-size:1.6em;opacity:.15;"
|
||||
"animation:swim 6s ease-in-out infinite}"
|
||||
"@keyframes swim{0%,100%{transform:translateY(0) rotate(-3deg)}50%{transform:translateY(4px) rotate(3deg)}}"
|
||||
"</style></head><body>"
|
||||
|
||||
"<header>"
|
||||
"<h1>🐠 AQUALED <span>어항 LED 컨트롤러</span></h1>"
|
||||
"<div class='meta'>"
|
||||
"<span>🕐 <strong>" + timeStr + "</strong> " + ntpBadge + "</span>"
|
||||
"<span>📶 STA <strong>" + staIP + "</strong> " + staBadge + "</span>"
|
||||
"</div></header>"
|
||||
|
||||
"<div class='wrap'>"
|
||||
|
||||
// PWM 카드
|
||||
"<div class='card'><div class='fish'>💡</div>"
|
||||
"<h2>⚡ PWM 제어</h2>"
|
||||
"<form action='/set_pwm' method='GET'>"
|
||||
"<label>밝기 (Duty Cycle)</label>"
|
||||
"<input type='range' name='duty' min='0' max='255' value='" + String(cfg.duty) + "'"
|
||||
" oninput=\"document.getElementById('dv').textContent=this.value\">"
|
||||
"<div class='vrow'>"
|
||||
"<span style='color:var(--muted);font-size:.8em'>0 ← 어둡게 / 밝게 → 255</span>"
|
||||
"<span class='val'><span id='dv'>" + String(cfg.duty) + "</span>"
|
||||
"<small style='font-size:.7em;color:var(--muted)'> (" + String(pct) + "%)</small></span>"
|
||||
"</div>"
|
||||
"<label>PWM 주파수 (Hz) <small style='color:var(--muted)'>- 낮을수록 플리커 줄어듦</small></label>"
|
||||
"<input type='number' name='freq' value='" + String(cfg.freq) + "' min='1' max='40000'>"
|
||||
"<button type='submit' class='btn btn-primary'>✓ PWM 적용</button>"
|
||||
"</form></div>"
|
||||
|
||||
// 스케줄 카드
|
||||
"<div class='card'><div class='fish'>⏰</div>"
|
||||
"<h2>📅 동작 설정</h2>"
|
||||
"<form action='/set_schedule' method='GET'>"
|
||||
"<label>동작 모드</label>"
|
||||
"<div class='seg'>"
|
||||
"<input type='radio' name='mode' id='ms' value='sched' " + schedSel + " onchange='toggleManual(false)'>"
|
||||
"<label for='ms'>⏱ 스케줄</label>"
|
||||
"<input type='radio' name='mode' id='mm' value='manual' " + manualSel + " onchange='toggleManual(true)'>"
|
||||
"<label for='mm'>🖐 수동</label>"
|
||||
"</div>"
|
||||
"<div id='schedBlock' style='display:" + schedStyle + "'>"
|
||||
"<div class='grid2'>"
|
||||
"<div><label>🌅 켜지는 시각</label>"
|
||||
"<input type='time' name='on_time' value='" + String(onBuf) + "'></div>"
|
||||
"<div><label>🌙 꺼지는 시각</label>"
|
||||
"<input type='time' name='off_time' value='" + String(offBuf) + "'></div>"
|
||||
"</div>"
|
||||
"<p style='font-size:.75em;color:var(--muted);margin-top:8px'>"
|
||||
"※ 꺼지는 시각이 켜지는 시각보다 이를 경우 자정을 넘기는 스케줄로 처리됩니다</p>"
|
||||
"</div>"
|
||||
"<div id='manualBlock' style='display:" + manStyle + "'>"
|
||||
"<p style='font-size:.82em;color:var(--muted);margin-top:10px'>현재 상태: " + ledState + "</p>"
|
||||
"<div class='manual-row'>"
|
||||
"<button type='button' class='btn btn-on' onclick=\"location.href='/manual?s=1'\">💡 켜기</button>"
|
||||
"<button type='button' class='btn btn-off' onclick=\"location.href='/manual?s=0'\">🌙 끄기</button>"
|
||||
"</div></div>"
|
||||
"<button type='submit' class='btn btn-primary' style='margin-top:16px'>✓ 스케줄 저장</button>"
|
||||
"</form></div>"
|
||||
|
||||
// WiFi 카드
|
||||
"<div class='card'><div class='fish'>📡</div>"
|
||||
"<h2>📶 외부 WiFi 연결 (STA)</h2>"
|
||||
"<form action='/set_wifi' method='GET'>"
|
||||
"<label>SSID (네트워크 이름)</label>"
|
||||
"<input type='text' name='ssid' value='" + cfg.staSSID + "' placeholder='공유기 SSID'>"
|
||||
"<label>비밀번호</label>"
|
||||
"<input type='password' name='pass' placeholder='비밀번호 (변경 시에만 입력)' autocomplete='off'>"
|
||||
"<p style='font-size:.75em;color:var(--muted);margin-top:6px'>"
|
||||
"※ 저장 후 STA 재연결합니다. AP(192.168.4.1)는 계속 유지됩니다</p>"
|
||||
"<button type='submit' class='btn btn-primary'>✓ 저장 후 연결</button>"
|
||||
"</form></div>"
|
||||
|
||||
// 시스템 정보 카드
|
||||
"<div class='card'>"
|
||||
"<h2>ℹ️ 시스템 정보</h2>"
|
||||
"<div class='info-row'><span class='lbl'>AP SSID</span><span class='val'>" + String(AP_SSID) + "</span></div>"
|
||||
"<div class='info-row'><span class='lbl'>AP PW</span><span class='val'>" + String(AP_PASS) + "</span></div>"
|
||||
"<div class='info-row'><span class='lbl'>AP IP</span><span class='val'>192.168.4.1</span></div>"
|
||||
"<div class='info-row'><span class='lbl'>STA IP</span><span class='val'>" + staIP + "</span></div>"
|
||||
"<div class='info-row'><span class='lbl'>GPIO</span><span class='val'>13</span></div>"
|
||||
"<div class='info-row'><span class='lbl'>Duty / Freq</span>"
|
||||
"<span class='val'>" + String(cfg.duty) + " / " + String(cfg.freq) + " Hz</span></div>"
|
||||
"</div>"
|
||||
|
||||
"</div>" // /wrap
|
||||
|
||||
"<script>"
|
||||
"function toggleManual(m){"
|
||||
"document.getElementById('schedBlock').style.display=m?'none':'block';"
|
||||
"document.getElementById('manualBlock').style.display=m?'block':'none';"
|
||||
"}"
|
||||
"setTimeout(function(){location.reload();},30000);"
|
||||
"</script>"
|
||||
"</body></html>";
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
void setupRoutes() {
|
||||
server.on("/", HTTP_GET, []() {
|
||||
server.send(200, "text/html; charset=utf-8", buildPage());
|
||||
});
|
||||
|
||||
server.on("/set_pwm", HTTP_GET, []() {
|
||||
if (server.hasArg("duty"))
|
||||
cfg.duty = constrain(server.arg("duty").toInt(), 0, 255);
|
||||
if (server.hasArg("freq"))
|
||||
cfg.freq = constrain(server.arg("freq").toInt(), 1, 40000);
|
||||
reconfigPWM(cfg.freq, 0);
|
||||
if (!cfg.scheduleOn && cfg.manualState) applyPWM(cfg.duty);
|
||||
saveConfig();
|
||||
server.sendHeader("Location", "/");
|
||||
server.send(302, "text/plain", "");
|
||||
});
|
||||
|
||||
server.on("/set_schedule", HTTP_GET, []() {
|
||||
if (server.hasArg("mode"))
|
||||
cfg.scheduleOn = (server.arg("mode") == "sched");
|
||||
if (server.hasArg("on_time")) {
|
||||
String t = server.arg("on_time");
|
||||
if (t.length() >= 5) { cfg.onH = t.substring(0,2).toInt(); cfg.onM = t.substring(3,5).toInt(); }
|
||||
}
|
||||
if (server.hasArg("off_time")) {
|
||||
String t = server.arg("off_time");
|
||||
if (t.length() >= 5) { cfg.offH = t.substring(0,2).toInt(); cfg.offM = t.substring(3,5).toInt(); }
|
||||
}
|
||||
if (!cfg.scheduleOn) applyPWM(cfg.manualState ? cfg.duty : 0);
|
||||
saveConfig();
|
||||
server.sendHeader("Location", "/");
|
||||
server.send(302, "text/plain", "");
|
||||
});
|
||||
|
||||
server.on("/manual", HTTP_GET, []() {
|
||||
if (server.hasArg("s")) {
|
||||
cfg.manualState = (server.arg("s") == "1");
|
||||
applyPWM(cfg.manualState ? cfg.duty : 0);
|
||||
saveConfig();
|
||||
}
|
||||
server.sendHeader("Location", "/");
|
||||
server.send(302, "text/plain", "");
|
||||
});
|
||||
|
||||
server.on("/set_wifi", HTTP_GET, []() {
|
||||
if (server.hasArg("ssid")) cfg.staSSID = server.arg("ssid");
|
||||
if (server.hasArg("pass") && server.arg("pass").length() > 0)
|
||||
cfg.staPASS = server.arg("pass");
|
||||
saveConfig();
|
||||
needReconnect = true;
|
||||
server.sendHeader("Location", "/");
|
||||
server.send(302, "text/plain", "");
|
||||
});
|
||||
|
||||
server.onNotFound([]() {
|
||||
server.send(404, "text/plain", "Not Found");
|
||||
});
|
||||
}
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
delay(300);
|
||||
Serial.println("\n====== AquaLED Controller ======");
|
||||
|
||||
loadConfig();
|
||||
|
||||
// Core 3.x PWM 초기화 (ledcSetup / ledcAttachPin 없음)
|
||||
ledcAttach(LED_PIN, cfg.freq, PWM_RESOLUTION);
|
||||
applyPWM(0);
|
||||
Serial.printf("[PWM] GPIO%d %dHz 8bit\n", LED_PIN, cfg.freq);
|
||||
|
||||
WiFi.mode(WIFI_AP_STA);
|
||||
startAP();
|
||||
staConnected = connectSTA(cfg.staSSID, cfg.staPASS);
|
||||
if (staConnected) tryNTP();
|
||||
|
||||
setupRoutes();
|
||||
server.begin();
|
||||
Serial.println("[WEB] 서버 시작 -> http://192.168.4.1");
|
||||
}
|
||||
|
||||
unsigned long lastScheduleCheck = 0;
|
||||
unsigned long lastNtpRetry = 0;
|
||||
|
||||
void loop() {
|
||||
server.handleClient();
|
||||
|
||||
if (needReconnect) {
|
||||
needReconnect = false;
|
||||
WiFi.disconnect();
|
||||
ntpSynced = false;
|
||||
staConnected = false;
|
||||
delay(200);
|
||||
staConnected = connectSTA(cfg.staSSID, cfg.staPASS, 10000);
|
||||
if (staConnected) tryNTP();
|
||||
}
|
||||
|
||||
unsigned long now = millis();
|
||||
|
||||
if (now - lastScheduleCheck >= 30000UL) {
|
||||
lastScheduleCheck = now;
|
||||
staConnected = (WiFi.status() == WL_CONNECTED);
|
||||
checkSchedule();
|
||||
}
|
||||
|
||||
if (!ntpSynced && staConnected && (now - lastNtpRetry >= 60000UL)) {
|
||||
lastNtpRetry = now;
|
||||
tryNTP();
|
||||
}
|
||||
|
||||
delay(5);
|
||||
}
|
||||
Reference in New Issue
Block a user