AP Mode 설정
This commit is contained in:
commit
05822ba583
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
.pio/
|
||||||
|
.vscode/
|
||||||
|
include/README
|
||||||
|
test/README
|
||||||
24
data/index.html
Normal file
24
data/index.html
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head><meta charset="utf-8"></head>
|
||||||
|
<body style="background-color: #1C304A;">
|
||||||
|
<h2>Main</h2>
|
||||||
|
<div id="wifi"></div>
|
||||||
|
<a href="/wifi">WiFi Setup</a>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
async function loadStatus() {
|
||||||
|
const res = await fetch("/api/status");
|
||||||
|
const st = await res.json();
|
||||||
|
let html = "WiFi: " + st.status;
|
||||||
|
if (st.status === "connected") {
|
||||||
|
html += `<br>${st.ssid} (${st.ip})`;
|
||||||
|
}
|
||||||
|
document.getElementById("wifi").innerHTML = html;
|
||||||
|
}
|
||||||
|
setInterval(loadStatus, 3000);
|
||||||
|
loadStatus();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
181
data/wifi.html
Normal file
181
data/wifi.html
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>WiFi Setup</title>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
/*
|
||||||
|
===============================
|
||||||
|
WiFi Scan
|
||||||
|
================================
|
||||||
|
*/
|
||||||
|
async function scanWifi() {
|
||||||
|
const scanDiv = document.getElementById("scan");
|
||||||
|
scanDiv.innerHTML = "Scanning...";
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch("/api/scan");
|
||||||
|
const list = await res.json();
|
||||||
|
|
||||||
|
if (!list.length) {
|
||||||
|
scanDiv.innerHTML = "No networks found.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// RSSI 기준 정렬 (신호 강한 순)
|
||||||
|
list.sort((a, b) => b.rssi - a.rssi);
|
||||||
|
|
||||||
|
let html = "<ul>";
|
||||||
|
list.forEach(n => {
|
||||||
|
html += `<li>
|
||||||
|
<a href="#" onclick="selectSSID('${n.ssid}')">
|
||||||
|
${n.ssid} (${n.rssi} dBm)
|
||||||
|
</a>
|
||||||
|
</li>`;
|
||||||
|
});
|
||||||
|
html += "</ul>";
|
||||||
|
|
||||||
|
scanDiv.innerHTML = html;
|
||||||
|
} catch (e) {
|
||||||
|
scanDiv.innerHTML = "Scan failed.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 선택한 SSID 입력창에 반영 */
|
||||||
|
function selectSSID(ssid) {
|
||||||
|
document.getElementById("ssid").value = ssid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
===============================
|
||||||
|
Connect / Disconnect
|
||||||
|
================================
|
||||||
|
*/
|
||||||
|
async function connectWifi() {
|
||||||
|
const ssid = document.getElementById("ssid").value;
|
||||||
|
const pass = document.getElementById("pass").value;
|
||||||
|
|
||||||
|
const res = await fetch("/api/connect", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ ssid, pass })
|
||||||
|
});
|
||||||
|
|
||||||
|
const txt = await res.text();
|
||||||
|
document.getElementById("result").innerText = txt;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function disconnectWifi() {
|
||||||
|
await fetch("/api/disconnect", { method: "POST" });
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
===============================
|
||||||
|
Status Polling
|
||||||
|
================================
|
||||||
|
*/
|
||||||
|
async function pollStatus() {
|
||||||
|
const res = await fetch("/api/status");
|
||||||
|
const st = await res.json();
|
||||||
|
|
||||||
|
let txt = "Status: " + st.status;
|
||||||
|
if (st.status === "connected") {
|
||||||
|
txt += `<br>SSID: ${st.ssid}<br>IP: ${st.ip}<br>RSSI: ${st.rssi}`;
|
||||||
|
}
|
||||||
|
document.getElementById("result").innerHTML = txt;
|
||||||
|
}
|
||||||
|
setInterval(pollStatus, 2000);
|
||||||
|
|
||||||
|
/*
|
||||||
|
===============================
|
||||||
|
Saved Networks
|
||||||
|
================================
|
||||||
|
*/
|
||||||
|
async function loadSaved() {
|
||||||
|
const res = await fetch("/api/saved");
|
||||||
|
const list = await res.json();
|
||||||
|
|
||||||
|
if (!list.length) {
|
||||||
|
document.getElementById("saved").innerHTML =
|
||||||
|
"저장된 AP 정보가 없습니다.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let html = "<ul>";
|
||||||
|
list.forEach((s, i) => {
|
||||||
|
html += `<li>
|
||||||
|
${s}
|
||||||
|
<button onclick="connectSaved(${i})">Connect</button>
|
||||||
|
<button onclick="deleteSaved(${i})">Delete</button>
|
||||||
|
</li>`;
|
||||||
|
});
|
||||||
|
html += "</ul>";
|
||||||
|
document.getElementById("saved").innerHTML = html;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function connectSaved(i) {
|
||||||
|
await fetch("/api/connect_saved", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ index: i })
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteSaved(i) {
|
||||||
|
if (!confirm("이 AP 정보를 삭제하시겠습니까?")) return;
|
||||||
|
|
||||||
|
await fetch("/api/delete_saved", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ index: i })
|
||||||
|
});
|
||||||
|
|
||||||
|
loadSaved(); // 목록 갱신
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 페이지 로드 시 저장된 AP 불러오기 */
|
||||||
|
loadSaved();
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body style="background-color:#1C304A; color:white; font-family: Arial;">
|
||||||
|
|
||||||
|
<h2>WiFi Setup</h2>
|
||||||
|
|
||||||
|
<!-- 입력 영역 -->
|
||||||
|
<label>SSID</label><br>
|
||||||
|
<input id="ssid" style="width:200px;"><br><br>
|
||||||
|
|
||||||
|
<label>Password</label><br>
|
||||||
|
<input id="pass" type="password" style="width:200px;"><br><br>
|
||||||
|
|
||||||
|
<!-- 버튼 정렬 -->
|
||||||
|
<button onclick="connectWifi()">Connect</button>
|
||||||
|
<button onclick="scanWifi()">Scan</button>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<!-- Scan 결과 -->
|
||||||
|
<div id="scan"></div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<!-- 상태 표시 -->
|
||||||
|
<div id="result"></div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<!-- 저장된 네트워크 -->
|
||||||
|
<h3>Saved Networks</h3>
|
||||||
|
<div id="saved"></div>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
<button onclick="disconnectWifi()">Disconnect</button>
|
||||||
|
|
||||||
|
<a href="/" style="color:#7faaff;">Back</a>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
46
lib/README
Normal file
46
lib/README
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
|
||||||
|
This directory is intended for project specific (private) libraries.
|
||||||
|
PlatformIO will compile them to static libraries and link into the executable file.
|
||||||
|
|
||||||
|
The source code of each library should be placed in a separate directory
|
||||||
|
("lib/your_library_name/[Code]").
|
||||||
|
|
||||||
|
For example, see the structure of the following example libraries `Foo` and `Bar`:
|
||||||
|
|
||||||
|
|--lib
|
||||||
|
| |
|
||||||
|
| |--Bar
|
||||||
|
| | |--docs
|
||||||
|
| | |--examples
|
||||||
|
| | |--src
|
||||||
|
| | |- Bar.c
|
||||||
|
| | |- Bar.h
|
||||||
|
| | |- library.json (optional. for custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
|
||||||
|
| |
|
||||||
|
| |--Foo
|
||||||
|
| | |- Foo.c
|
||||||
|
| | |- Foo.h
|
||||||
|
| |
|
||||||
|
| |- README --> THIS FILE
|
||||||
|
|
|
||||||
|
|- platformio.ini
|
||||||
|
|--src
|
||||||
|
|- main.c
|
||||||
|
|
||||||
|
Example contents of `src/main.c` using Foo and Bar:
|
||||||
|
```
|
||||||
|
#include <Foo.h>
|
||||||
|
#include <Bar.h>
|
||||||
|
|
||||||
|
int main (void)
|
||||||
|
{
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
The PlatformIO Library Dependency Finder will find automatically dependent
|
||||||
|
libraries by scanning project source files.
|
||||||
|
|
||||||
|
More information about PlatformIO Library Dependency Finder
|
||||||
|
- https://docs.platformio.org/page/librarymanager/ldf.html
|
||||||
16
platformio.ini
Normal file
16
platformio.ini
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
; PlatformIO Project Configuration File
|
||||||
|
;
|
||||||
|
; Build options: build flags, source filter
|
||||||
|
; Upload options: custom upload port, speed and extra flags
|
||||||
|
; Library options: dependencies, extra library storages
|
||||||
|
; Advanced options: extra scripting
|
||||||
|
;
|
||||||
|
; Please visit documentation for the other options and examples
|
||||||
|
; https://docs.platformio.org/page/projectconf.html
|
||||||
|
|
||||||
|
[env:d1]
|
||||||
|
platform = espressif8266
|
||||||
|
board = d1
|
||||||
|
framework = arduino
|
||||||
|
monitor_speed = 115200
|
||||||
|
board_build.filesystem = littlefs
|
||||||
35
src/main.cpp
Normal file
35
src/main.cpp
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
#include <Arduino.h>
|
||||||
|
#include <ESP8266WebServer.h>
|
||||||
|
#include <LittleFS.h>
|
||||||
|
#include "mainpage/mainpage.h"
|
||||||
|
#include "wifi/wifi.h"
|
||||||
|
|
||||||
|
ESP8266WebServer server(80); // 🔹 전역 서버 객체
|
||||||
|
|
||||||
|
void setup()
|
||||||
|
{
|
||||||
|
Serial.begin(115200);
|
||||||
|
delay(1000);
|
||||||
|
Serial.println("Booting system...");
|
||||||
|
|
||||||
|
if (!LittleFS.begin())
|
||||||
|
{
|
||||||
|
Serial.println("LittleFS mount failed!");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Serial.println("LittleFS mounted.");
|
||||||
|
}
|
||||||
|
|
||||||
|
mainPageSetup(server); // 🔹 Main 페이지 등록
|
||||||
|
wifiSetup(server); // 🔹 WiFi 모듈 초기화
|
||||||
|
|
||||||
|
server.begin();
|
||||||
|
Serial.println("Web server started");
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop()
|
||||||
|
{
|
||||||
|
server.handleClient();
|
||||||
|
wifiLoop(); // FSM 기반 WiFi 관리
|
||||||
|
}
|
||||||
10
src/mainpage/mainpage.cpp
Normal file
10
src/mainpage/mainpage.cpp
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#include "mainpage.h"
|
||||||
|
#include <LittleFS.h>
|
||||||
|
|
||||||
|
void mainPageSetup(ESP8266WebServer& server) {
|
||||||
|
server.on("/", [&server]() {
|
||||||
|
File f = LittleFS.open("/index.html", "r");
|
||||||
|
server.streamFile(f, "text/html");
|
||||||
|
f.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
8
src/mainpage/mainpage.h
Normal file
8
src/mainpage/mainpage.h
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#ifndef MAINPAGE_MODULE_H
|
||||||
|
#define MAINPAGE_MODULE_H
|
||||||
|
|
||||||
|
#include <ESP8266WebServer.h>
|
||||||
|
|
||||||
|
void mainPageSetup(ESP8266WebServer& server);
|
||||||
|
|
||||||
|
#endif
|
||||||
322
src/wifi/wifi.cpp
Normal file
322
src/wifi/wifi.cpp
Normal file
@ -0,0 +1,322 @@
|
|||||||
|
#include "wifi.h"
|
||||||
|
#include <ESP8266WiFi.h>
|
||||||
|
#include <LittleFS.h>
|
||||||
|
#include <EEPROM.h>
|
||||||
|
|
||||||
|
#define EEPROM_SIZE 1024
|
||||||
|
#define MAX_WIFI 10
|
||||||
|
#define SSID_LEN 32
|
||||||
|
#define PASS_LEN 64
|
||||||
|
|
||||||
|
unsigned long connectStart = 0;
|
||||||
|
bool connecting = false;
|
||||||
|
|
||||||
|
struct WifiCred
|
||||||
|
{
|
||||||
|
char ssid[SSID_LEN];
|
||||||
|
char pass[PASS_LEN];
|
||||||
|
};
|
||||||
|
|
||||||
|
WifiState wifiState = WIFI_IDLE;
|
||||||
|
|
||||||
|
WiFiEventHandler onDisconnectHandler;
|
||||||
|
|
||||||
|
unsigned long lastReconnectAttempt = 0;
|
||||||
|
const unsigned long RECONNECT_INTERVAL = 10000; // 10초
|
||||||
|
|
||||||
|
WifiCred wifiList[MAX_WIFI];
|
||||||
|
int wifiCount = 0;
|
||||||
|
|
||||||
|
// --- EEPROM functions ---
|
||||||
|
void loadWifiFromEEPROM()
|
||||||
|
{
|
||||||
|
EEPROM.begin(EEPROM_SIZE);
|
||||||
|
EEPROM.get(0, wifiCount);
|
||||||
|
if (wifiCount < 0 || wifiCount > MAX_WIFI)
|
||||||
|
wifiCount = 0;
|
||||||
|
EEPROM.get(sizeof(int), wifiList);
|
||||||
|
Serial.printf("Loaded %d WiFi creds\n", wifiCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
void saveWifiToEEPROM()
|
||||||
|
{
|
||||||
|
EEPROM.put(0, wifiCount);
|
||||||
|
EEPROM.put(sizeof(int), wifiList);
|
||||||
|
EEPROM.commit();
|
||||||
|
Serial.println("WiFi creds saved to EEPROM");
|
||||||
|
}
|
||||||
|
|
||||||
|
void addWifiCred(const String &ssid, const String &pass)
|
||||||
|
{
|
||||||
|
int found = -1;
|
||||||
|
|
||||||
|
for (int i = 0; i < wifiCount; i++)
|
||||||
|
{
|
||||||
|
if (ssid == wifiList[i].ssid)
|
||||||
|
{
|
||||||
|
found = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WifiCred cred;
|
||||||
|
strncpy(cred.ssid, ssid.c_str(), SSID_LEN);
|
||||||
|
strncpy(cred.pass, pass.c_str(), PASS_LEN);
|
||||||
|
|
||||||
|
if (found == -1)
|
||||||
|
{
|
||||||
|
// 신규 AP → 공간 없으면 마지막 제거
|
||||||
|
if (wifiCount < MAX_WIFI)
|
||||||
|
{
|
||||||
|
wifiCount++;
|
||||||
|
}
|
||||||
|
// 뒤에서부터 한 칸씩 밀기
|
||||||
|
for (int i = wifiCount - 1; i > 0; i--)
|
||||||
|
{
|
||||||
|
wifiList[i] = wifiList[i - 1];
|
||||||
|
}
|
||||||
|
wifiList[0] = cred;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 기존 AP → 0번으로 이동
|
||||||
|
for (int i = found; i > 0; i--)
|
||||||
|
{
|
||||||
|
wifiList[i] = wifiList[i - 1];
|
||||||
|
}
|
||||||
|
wifiList[0] = cred;
|
||||||
|
}
|
||||||
|
|
||||||
|
saveWifiToEEPROM();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- WiFi Setup ---
|
||||||
|
void wifiSetup(ESP8266WebServer &server)
|
||||||
|
{
|
||||||
|
|
||||||
|
loadWifiFromEEPROM();
|
||||||
|
|
||||||
|
setupWifiEvents();
|
||||||
|
|
||||||
|
WiFi.mode(WIFI_AP);
|
||||||
|
WiFi.softAP("TEST_AP", "12345678");
|
||||||
|
|
||||||
|
Serial.print("AP IP: ");
|
||||||
|
Serial.println(WiFi.softAPIP());
|
||||||
|
|
||||||
|
// ✅ ⑤ 자동 재연결
|
||||||
|
if (wifiCount > 0)
|
||||||
|
{
|
||||||
|
Serial.print("Auto connect to: ");
|
||||||
|
Serial.println(wifiList[0].ssid);
|
||||||
|
WiFi.mode(WIFI_AP_STA);
|
||||||
|
WiFi.begin(wifiList[0].ssid, wifiList[0].pass);
|
||||||
|
connecting = true;
|
||||||
|
connectStart = millis();
|
||||||
|
wifiState = WIFI_CONNECTING;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- WiFi Page ---
|
||||||
|
server.on("/wifi", [&server]()
|
||||||
|
{
|
||||||
|
Serial.println("GET /wifi");
|
||||||
|
File f = LittleFS.open("/wifi.html", "r");
|
||||||
|
if (!f) {
|
||||||
|
server.send(500, "text/plain", "wifi.html not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
server.streamFile(f, "text/html");
|
||||||
|
f.close(); });
|
||||||
|
|
||||||
|
// --- Scan API ---
|
||||||
|
server.on("/api/scan", HTTP_GET, [&server]()
|
||||||
|
{
|
||||||
|
Serial.println("API: scan");
|
||||||
|
|
||||||
|
int n = WiFi.scanNetworks();
|
||||||
|
String json = "[";
|
||||||
|
bool first = true;
|
||||||
|
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
String ssid = WiFi.SSID(i);
|
||||||
|
if (ssid.length() == 0) continue;
|
||||||
|
if (!first) json += ",";
|
||||||
|
first = false;
|
||||||
|
json += "{\"ssid\":\"" + ssid + "\",\"rssi\":" + String(WiFi.RSSI(i)) + "}";
|
||||||
|
}
|
||||||
|
json += "]";
|
||||||
|
server.send(200, "application/json", json); });
|
||||||
|
|
||||||
|
// --- Connect API ---
|
||||||
|
server.on("/api/connect", HTTP_POST, [&server]()
|
||||||
|
{
|
||||||
|
Serial.println("API: connect");
|
||||||
|
|
||||||
|
String body = server.arg("plain");
|
||||||
|
|
||||||
|
int s1 = body.indexOf("\"ssid\":\"") + 8;
|
||||||
|
int s2 = body.indexOf("\"", s1);
|
||||||
|
int p1 = body.indexOf("\"pass\":\"") + 8;
|
||||||
|
int p2 = body.indexOf("\"", p1);
|
||||||
|
|
||||||
|
String ssid = body.substring(s1, s2);
|
||||||
|
String pass = body.substring(p1, p2);
|
||||||
|
|
||||||
|
Serial.printf("Connecting to SSID: %s\n", ssid.c_str());
|
||||||
|
|
||||||
|
addWifiCred(ssid, pass);
|
||||||
|
|
||||||
|
WiFi.mode(WIFI_AP_STA);
|
||||||
|
WiFi.begin(ssid.c_str(), pass.c_str());
|
||||||
|
|
||||||
|
connecting = true;
|
||||||
|
connectStart = millis();
|
||||||
|
wifiState = WIFI_CONNECTING;
|
||||||
|
|
||||||
|
server.send(200, "text/plain", "Connecting..."); });
|
||||||
|
|
||||||
|
// --- Status API ---
|
||||||
|
server.on("/api/status", HTTP_GET, [&server]()
|
||||||
|
{
|
||||||
|
String json = "{";
|
||||||
|
wl_status_t st = WiFi.status();
|
||||||
|
|
||||||
|
json += "\"status\":\"";
|
||||||
|
if (st == WL_CONNECTED) json += "connected";
|
||||||
|
else if (connecting) json += "connecting";
|
||||||
|
else json += "disconnected";
|
||||||
|
json += "\"";
|
||||||
|
|
||||||
|
if (st == WL_CONNECTED) {
|
||||||
|
json += ",\"ssid\":\"" + WiFi.SSID() + "\"";
|
||||||
|
json += ",\"ip\":\"" + WiFi.localIP().toString() + "\"";
|
||||||
|
json += ",\"rssi\":" + String(WiFi.RSSI());
|
||||||
|
}
|
||||||
|
json += "}";
|
||||||
|
|
||||||
|
server.send(200, "application/json", json); });
|
||||||
|
|
||||||
|
// --- Saved AP List API ---
|
||||||
|
server.on("/api/saved", HTTP_GET, [&server]()
|
||||||
|
{
|
||||||
|
String json = "[";
|
||||||
|
for (int i = 0; i < wifiCount; i++) {
|
||||||
|
if (i) json += ",";
|
||||||
|
json += "\"";
|
||||||
|
json += wifiList[i].ssid;
|
||||||
|
json += "\"";
|
||||||
|
}
|
||||||
|
json += "]";
|
||||||
|
server.send(200, "application/json", json); });
|
||||||
|
|
||||||
|
/// --- Connect to saved AP by index ---
|
||||||
|
server.on("/api/connect_saved", HTTP_POST, [&server]()
|
||||||
|
{
|
||||||
|
Serial.println("API: connect_saved");
|
||||||
|
|
||||||
|
String body = server.arg("plain"); // ✅ body 정의
|
||||||
|
|
||||||
|
int p = body.indexOf(":") + 1;
|
||||||
|
int e = body.indexOf("}", p);
|
||||||
|
int idx = body.substring(p, e).toInt();
|
||||||
|
|
||||||
|
if (idx >= 0 && idx < wifiCount) {
|
||||||
|
Serial.printf("Connecting to saved: %s\n", wifiList[idx].ssid);
|
||||||
|
|
||||||
|
WiFi.mode(WIFI_AP_STA);
|
||||||
|
WiFi.begin(wifiList[idx].ssid, wifiList[idx].pass);
|
||||||
|
|
||||||
|
connecting = true;
|
||||||
|
connectStart = millis();
|
||||||
|
|
||||||
|
server.send(200, "text/plain", "Connecting to saved AP...");
|
||||||
|
} else {
|
||||||
|
server.send(400, "text/plain", "Invalid index");
|
||||||
|
} });
|
||||||
|
|
||||||
|
// --- Disconnect API ---
|
||||||
|
server.on("/api/disconnect", HTTP_POST, [&server]()
|
||||||
|
{
|
||||||
|
Serial.println("API: disconnect");
|
||||||
|
|
||||||
|
WiFi.disconnect(true);
|
||||||
|
WiFi.mode(WIFI_AP); // ✅ 여기 중요
|
||||||
|
|
||||||
|
connecting = false;
|
||||||
|
server.send(200, "text/plain", "Disconnected"); });
|
||||||
|
// --- Delete saved AP by index ---
|
||||||
|
server.on("/api/delete_saved", HTTP_POST, [&server]()
|
||||||
|
{
|
||||||
|
Serial.println("API: delete_saved");
|
||||||
|
|
||||||
|
String body = server.arg("plain");
|
||||||
|
|
||||||
|
int p = body.indexOf(":") + 1;
|
||||||
|
int e = body.indexOf("}", p);
|
||||||
|
int idx = body.substring(p, e).toInt();
|
||||||
|
|
||||||
|
if (idx >= 0 && idx < wifiCount) {
|
||||||
|
Serial.printf("Delete saved AP: %s\n", wifiList[idx].ssid);
|
||||||
|
|
||||||
|
// 뒤의 항목들을 앞으로 당김
|
||||||
|
for (int i = idx; i < wifiCount - 1; i++) {
|
||||||
|
wifiList[i] = wifiList[i + 1];
|
||||||
|
}
|
||||||
|
wifiCount--;
|
||||||
|
saveWifiToEEPROM();
|
||||||
|
|
||||||
|
server.send(200, "text/plain", "Deleted");
|
||||||
|
} else {
|
||||||
|
server.send(400, "text/plain", "Invalid index");
|
||||||
|
} });
|
||||||
|
}
|
||||||
|
void setupWifiEvents() {
|
||||||
|
onDisconnectHandler = WiFi.onStationModeDisconnected(
|
||||||
|
[](const WiFiEventStationModeDisconnected& event) {
|
||||||
|
Serial.println("WiFi event: disconnected");
|
||||||
|
wifiState = WIFI_LOST;
|
||||||
|
lastReconnectAttempt = 0;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
void wifiLoop() {
|
||||||
|
wl_status_t st = WiFi.status();
|
||||||
|
|
||||||
|
// CONNECTING → CONNECTED
|
||||||
|
if (wifiState == WIFI_CONNECTING && st == WL_CONNECTED) {
|
||||||
|
wifiState = WIFI_CONNECTED;
|
||||||
|
connecting = false;
|
||||||
|
Serial.println("WiFi FSM: CONNECTED");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CONNECTED 상태에서 끊김 감지 (이벤트가 놓쳤을 경우 보조)
|
||||||
|
if (wifiState == WIFI_CONNECTED && st != WL_CONNECTED) {
|
||||||
|
wifiState = WIFI_LOST;
|
||||||
|
Serial.println("WiFi FSM: LOST");
|
||||||
|
}
|
||||||
|
|
||||||
|
// LOST 상태 → 제한적 재연결
|
||||||
|
if (wifiState == WIFI_LOST && wifiCount > 0) {
|
||||||
|
if (millis() - lastReconnectAttempt > RECONNECT_INTERVAL) {
|
||||||
|
lastReconnectAttempt = millis();
|
||||||
|
Serial.println("WiFi FSM: Reconnect attempt");
|
||||||
|
|
||||||
|
WiFi.mode(WIFI_AP_STA);
|
||||||
|
WiFi.begin(wifiList[0].ssid, wifiList[0].pass);
|
||||||
|
|
||||||
|
connecting = true;
|
||||||
|
connectStart = millis();
|
||||||
|
wifiState = WIFI_CONNECTING;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CONNECTING 타임아웃 → LOST
|
||||||
|
if (wifiState == WIFI_CONNECTING &&
|
||||||
|
millis() - connectStart > 15000) {
|
||||||
|
Serial.println("WiFi FSM: connect timeout");
|
||||||
|
WiFi.disconnect();
|
||||||
|
wifiState = WIFI_LOST;
|
||||||
|
connecting = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/wifi/wifi.h
Normal file
16
src/wifi/wifi.h
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <ESP8266WebServer.h>
|
||||||
|
|
||||||
|
void wifiSetup(ESP8266WebServer &server);
|
||||||
|
void wifiLoop();
|
||||||
|
|
||||||
|
// FSM 상태
|
||||||
|
enum WifiState
|
||||||
|
{
|
||||||
|
WIFI_IDLE,
|
||||||
|
WIFI_CONNECTING,
|
||||||
|
WIFI_CONNECTED,
|
||||||
|
WIFI_LOST
|
||||||
|
};
|
||||||
|
|
||||||
|
extern WifiState wifiState;
|
||||||
Loading…
x
Reference in New Issue
Block a user