add some code

This commit is contained in:
2025-09-05 13:25:11 +08:00
parent 9ff0a99e7a
commit 3cf1229a85
8911 changed files with 2535396 additions and 0 deletions

View File

@@ -0,0 +1 @@
752e62d469a341495368b6a3f812afdbe37569ea5b4460f4eedec42253118e8c

View File

@@ -0,0 +1 @@
dist/

View File

@@ -0,0 +1 @@
{"version": "1.0", "algorithm": "sha256", "created_at": "2025-08-27T16:23:34.672784+00:00", "files": [{"path": ".gitignore", "size": 5, "hash": "887f42eeae4276a8ba8ed3e14ec6567107ed2760d18ea7303cc715a38670fbea"}, {"path": "CMakeLists.txt", "size": 397, "hash": "d419c472b8962d5287ef369e77c5e038e36bba1e8a0ec6d02e10d1c4754d7422"}, {"path": "README.md", "size": 1993, "hash": "4301a41c3298a009305c0adcd287ddeb5f26d6963ddcf6f37bdc325cbded3b5f"}, {"path": "dns_server.cc", "size": 2243, "hash": "9701dfc78450692ba0e39f094625ce3c82683b2521dfecbea596a8e2ec709ab5"}, {"path": "idf_component.yml", "size": 226, "hash": "462968159ec6f6d89188426722b21835749d283f63904eb2a70600bdc525cde2"}, {"path": "ssid_manager.cc", "size": 3739, "hash": "e3441e0fc84012b539bd08b680cdfbfdaca5eb55af55423400268d0a18d5daca"}, {"path": "wifi_configuration_ap.cc", "size": 31451, "hash": "c35d9704fb4bcadf5541709cd786066394f6fcd0f18597402da794cb9ffedd42"}, {"path": "wifi_station.cc", "size": 9482, "hash": "b6dda837f1a5e1dd8bea92e17012ba6f840b672e94fd88aa5e6b455b502139f9"}, {"path": "assets/ap.png", "size": 14496, "hash": "defb386a203a1f29aab1ba06a97b087f74fbbeb20b8b2698d9d821efc7f95a36"}, {"path": "assets/ap_v2.png", "size": 40864, "hash": "a3d8fe8b7bfd944b83f0767485a8881eb20c24f3d65ba7763f1eb879e1e18d64"}, {"path": "assets/ap_v3.png", "size": 16934, "hash": "800fb2ef4f297fbfd7c46ce16792e63c583311c2569162d18f3d337c9a4a4eb5"}, {"path": "assets/ap_v3_advanced.png", "size": 13843, "hash": "715f1041d341359ffdd4b8529782d313bb0be0a83366f0c3c55aca1e1dfb6d33"}, {"path": "assets/wifi_configuration.html", "size": 22847, "hash": "c2de530c062e39db5e03703a85a30f50d22182b2cf1da39a6530a7d914c8850c"}, {"path": "assets/wifi_configuration_done.html", "size": 2701, "hash": "286974ea2b4305e0426ff75bde2690495e8027aaf8ff34dc1589a1ecff434b11"}, {"path": "include/dns_server.h", "size": 338, "hash": "9dff2c350d6cd6c397bae41213abd37c0d7dc83cdfb15110f28d004a88c4e8dd"}, {"path": "include/ssid_manager.h", "size": 689, "hash": "f45a0687170a30ba5592a9b3fde7c8f5da768dac7de185b256b2be56359d9e3f"}, {"path": "include/wifi_configuration_ap.h", "size": 2211, "hash": "4890464aa690f2a6ca5d7d1c952b01f0c81e64ad91dad9ebb04c4c478c535ee9"}, {"path": "include/wifi_station.h", "size": 2114, "hash": "012d7a2376fc88ca08fd2227e481d9dc8db226fd7263a96fc70d739d7b0dd7cf"}]}

View File

@@ -0,0 +1,18 @@
idf_component_register(
SRCS
"wifi_configuration_ap.cc"
"wifi_station.cc"
"ssid_manager.cc"
"dns_server.cc"
INCLUDE_DIRS
"include"
EMBED_TXTFILES
"assets/wifi_configuration.html"
"assets/wifi_configuration_done.html"
REQUIRES
"esp_timer"
"esp_http_server"
"esp_wifi"
"nvs_flash"
"json"
)

View File

@@ -0,0 +1,75 @@
# ESP32 Wi-Fi Connect
This component helps with Wi-Fi connection for the device.
It first tries to connect to a Wi-Fi network using the credentials stored in the flash. If this fails, it starts an access point and a web server to allow the user to connect to a Wi-Fi network.
The URL to access the web server is `http://192.168.4.1`.
### Screenshot: Wi-Fi Configuration
<img src="assets/ap_v3.png" width="320" alt="Wi-Fi Configuration">
### Screenshot: Advanced Options
<img src="assets/ap_v3_advanced.png" width="320" alt="Advanced Configuration">
## Changelog: v2.4.0
- Add ja / zh-TW languages.
- Add advanced tab.
- Add "Connection: close" headers to save open sockets.
## Changelog: v2.3.0
- Add support for language request.
## Changelog: v2.2.0
- Add support for ESP32 SmartConfig(ESPTouch v2)
## Changelog: v2.1.0
- Improve Wi-Fi connection logic.
## Changelog: v2.0.0
- Add support for multiple Wi-Fi SSID management.
- Auto switch to the best Wi-Fi network.
- Captive portal for Wi-Fi configuration.
- Support for multiple languages (English, Chinese).
## Configuration
The Wi-Fi credentials are stored in the flash under the "wifi" namespace.
The keys are "ssid", "ssid1", "ssid2" ... "ssid9", "password", "password1", "password2" ... "password9".
## Usage
```cpp
// Initialize the default event loop
ESP_ERROR_CHECK(esp_event_loop_create_default());
// Initialize NVS flash for Wi-Fi configuration
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
// Get the Wi-Fi configuration
auto& ssid_list = SsidManager::GetInstance().GetSsidList();
if (ssid_list.empty()) {
// Start the Wi-Fi configuration AP
auto& ap = WifiConfigurationAp::GetInstance();
ap.SetSsidPrefix("ESP32");
ap.Start();
return;
}
// Otherwise, connect to the Wi-Fi network
WifiStation::GetInstance().Start();
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,596 @@
<!DOCTYPE html>
<html>
<head>
<title>Network Configuration</title>
<meta charset="utf-8">
<meta http-equiv="Content-Security-Policy" content="referrer no-referrer">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<style type="text/css">
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background-color: #f0f0f0;
}
label {
display: block;
margin-bottom: 5px;
}
input {
width: 100%;
padding: 5px;
box-sizing: border-box;
border: 1px solid #ccc;
border-radius: 3px;
}
input[type="submit"] {
background-color: #007bff;
color: #fff;
border: none;
border-radius: 3px;
padding: 10px;
cursor: pointer;
}
input[type="submit"]:hover {
background-color: #0056b3;
}
input[type="submit"]:disabled {
background-color: #ccc;
cursor: not-allowed;
}
#ap_list {
margin-top: 20px;
border-top: 1px solid #ccc;
padding-top: 10px;
}
#ap_list a {
display: block;
margin-top: 5px;
color: #007bff;
text-decoration: none;
}
#ap_list a:hover {
text-decoration: underline;
}
.language-switch {
position: absolute;
top: 20px;
right: 20px;
}
.language-switch select {
padding: 5px;
border-radius: 3px;
border: 1px solid #ccc;
}
/* 标签页样式 */
.tabs {
display: flex;
margin-top: 20px;
margin-bottom: 0;
padding-left: 20px;
border-bottom: 1px solid #ccc;
}
.tab {
padding: 10px 20px;
cursor: pointer;
border: 1px solid #ccc;
border-bottom: none;
border-radius: 5px 5px 0 0;
margin-right: 5px;
background-color: #f0f0f0;
position: relative;
top: 1px;
}
.tab.active {
background-color: #fff;
}
.tab-content {
display: none;
border-top: none;
padding: 20px;
background-color: #fff;
}
.tab-content.active {
display: block;
}
#remember_bssid, #sleep_mode {
width: auto;
}
/* 提示框样式 */
.toast {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: rgba(0, 0, 0, 0.8);
color: white;
padding: 15px 30px;
border-radius: 8px;
z-index: 1000;
opacity: 0;
transition: opacity 0.3s ease-in-out;
min-width: 200px;
text-align: center;
}
.toast.show {
opacity: 1;
}
</style>
</head>
<body>
<div class="toast" id="toast"></div>
<div class="language-switch">
<select id="language" onchange="changeLanguage()">
<option value="en-US">English</option>
<option value="zh-CN">简体中文</option>
<option value="zh-TW">繁體中文</option>
<option value="ja-JP">日本語</option>
</select>
</div>
<div class="tabs">
<div class="tab active" onclick="switchTab('wifi')" data-lang="wifi_tab">WiFi 配置</div>
<div class="tab" onclick="switchTab('advanced')" data-lang="advanced_tab">高级选项</div>
</div>
<div id="wifi-tab" class="tab-content active">
<form action="/submit" method="post" onsubmit="submitForm(event)">
<div id="saved_list_container" style="display: none;">
<h3 data-lang="saved_wifi">已保存的 WiFi</h3>
<ul id="saved_list">
<li>
<span>SSID</span>
</li>
</ul>
</div>
<div>
<h3 data-lang="new_wifi">新的 WiFi</h3>
<p class="error" style="color: red; text-align: center;" id="error">
</p>
<p>
<label for="ssid">SSID:</label>
<input type="text" id="ssid" name="ssid" required>
</p>
<p>
<label for="password" data-lang="password">密码:</label>
<input type="password" id="password" name="password">
</p>
<p style="text-align: center;">
<input type="submit" value="连接" id="button" data-lang-value="connect">
</p>
<p id="ap_list">
</p>
</div>
</form>
</div>
<div id="advanced-tab" class="tab-content">
<form action="/advanced/submit" method="post" onsubmit="submitAdvancedForm(event)">
<div>
<h3 data-lang="advanced_tab">高级选项</h3>
<p class="error" style="color: red; text-align: center;" id="advanced_error"></p>
<p>
<label for="ota_url" data-lang="ota_url">OTA服务器地址:</label>
<div style="display: flex; align-items: center; gap: 10px;">
<input type="text" id="ota_url" name="ota_url" style="flex: 1;">
<button type="button" onclick="clearOtaUrl()" style="padding: 5px 10px; border: 1px solid #ccc; border-radius: 3px; background-color: #f0f0f0; cursor: pointer;"></button>
</div>
</p>
<p style="display: flex; align-items: center; gap: 10px;">
<label for="max_tx_power" data-lang="max_tx_power" style="white-space: nowrap;">Wi-Fi发射功率:</label>
<select id="max_tx_power" name="max_tx_power">
<option value="8">2 dBm</option>
<option value="20">5 dBm</option>
<option value="28">7 dBm</option>
<option value="34">8 dBm</option>
<option value="44">11 dBm</option>
<option value="52">13 dBm</option>
<option value="56">14 dBm</option>
<option value="60">15 dBm</option>
<option value="66">16 dBm</option>
<option value="72">18 dBm</option>
<option value="80">20 dBm</option>
</select>
</p>
<p style="display: flex; align-items: center; gap: 10px;">
<label for="remember_bssid" style="margin: 0;">
<span data-lang="remember_bssid">连接 Wi-Fi 时记住 BSSID</span>
</label>
<input type="checkbox" id="remember_bssid" name="remember_bssid">
</p>
<p style="display: flex; align-items: center; gap: 10px;">
<label for="sleep_mode" style="margin: 0;">
<span data-lang="sleep_mode">启用睡眠模式</span>
</label>
<input type="checkbox" id="sleep_mode" name="sleep_mode">
</p>
<p style="text-align: center;">
<input type="submit" value="保存" id="advanced_button" data-lang-value="save">
</p>
</div>
</form>
</div>
<script type="text/javascript">
const button = document.getElementById('button');
const error = document.getElementById('error');
const ssid = document.getElementById('ssid');
// Add language translations
const translations = {
'zh-CN': {
title: '网络配置',
saved_wifi: '已保存的 Wi-Fi',
new_wifi: '新的 Wi-Fi',
password: '密码:',
connect: '连接',
select_wifi: '从下面列表选择 2.4G Wi-Fi:',
wifi_tab: 'Wi-Fi 配置',
advanced_tab: '高级选项',
ota_url: '自定义 OTA 地址:',
max_tx_power: 'Wi-Fi 最大发射功率:',
remember_bssid: '连接 Wi-Fi 时记住 BSSID',
sleep_mode: '启用睡眠模式',
save: '保存'
},
'zh-TW': {
title: '網路設定',
saved_wifi: '已儲存的 Wi-Fi',
new_wifi: '新的 Wi-Fi',
password: '密碼:',
connect: '連接',
select_wifi: '從下方列表選擇 2.4G Wi-Fi:',
wifi_tab: 'Wi-Fi 設定',
advanced_tab: '進階選項',
ota_url: '自定義 OTA 位址:',
max_tx_power: 'Wi-Fi 最大發射功率:',
remember_bssid: '連接 Wi-Fi 時記住 BSSID',
sleep_mode: '啟用睡眠模式',
save: '儲存'
},
'en-US': {
title: 'Network Configuration',
saved_wifi: 'Saved Wi-Fi',
new_wifi: 'New Wi-Fi',
password: 'Password:',
connect: 'Connect',
select_wifi: 'Select an 2.4G Wi-Fi from the list below:',
wifi_tab: 'Wi-Fi Config',
advanced_tab: 'Advanced',
ota_url: 'Custom OTA URL:',
max_tx_power: 'Wi-Fi Max TX Power:',
remember_bssid: 'Remember BSSID when connecting to Wi-Fi',
sleep_mode: 'Enable Sleep Mode',
save: 'Save'
},
'ja-JP': {
title: 'ネットワーク設定',
saved_wifi: '保存済みのWi-Fi',
new_wifi: '新しいWi-Fi',
password: 'パスワード:',
connect: '接続',
select_wifi: '以下のリストから2.4G Wi-Fiを選択してください:',
wifi_tab: 'Wi-Fi設定',
advanced_tab: '詳細設定',
ota_url: '自定義 OTA 位址:',
max_tx_power: 'Wi-Fi最大送信電力:',
remember_bssid: 'Wi-Fi接続時にBSSIDを記憶する',
sleep_mode: 'スリープモードを有効にする',
save: '保存'
}
};
function changeLanguage() {
const lang = document.getElementById('language').value;
// 检查语言值是否合法
if (!translations[lang]) {
console.warn(`不支持的语言: ${lang},默认使用中文`);
document.getElementById('language').value = 'zh-CN';
return changeLanguage();
}
// Set page title
document.title = translations[lang].title;
document.querySelectorAll('[data-lang]').forEach(element => {
const key = element.getAttribute('data-lang');
element.textContent = translations[lang][key];
});
document.querySelectorAll('[data-lang-value]').forEach(element => {
const key = element.getAttribute('data-lang-value');
element.value = translations[lang][key];
});
// Update AP list text
const apList = document.getElementById('ap_list');
if (apList.firstChild) {
apList.firstChild.textContent = translations[lang].select_wifi;
}
// Save language preference
localStorage.setItem('preferred_language', lang);
}
function renderSavedList(data) {
const savedListContainer = document.getElementById('saved_list_container');
const savedList = document.getElementById('saved_list');
savedList.innerHTML = '';
data.forEach((ssid, index) => {
const li = document.createElement('li');
let html = `<span>${ssid}</span>`;
// Only add priority and delete buttons after the first item
if (index > 0) {
html += ` <span>
<button type="button" onclick="setDefaultItem(this, ${index})">⬆️</button>
<button type="button" onclick="deleteItem(this, ${index})">❌</button>
</span>`;
} else {
html += ` <span><button type="button" onclick="deleteItem(this, ${index})">❌</button></span>`;
}
li.innerHTML = html;
savedList.appendChild(li);
});
if (data.length > 0) {
savedListContainer.style.display = 'block';
} else {
savedListContainer.style.display = 'none';
}
}
// Delete one item from the list
function deleteItem(item, index) {
// disable the button
item.disabled = true;
// /saved/delete?index=INDEX
fetch('/saved/delete?index=' + index)
.then(response => response.json())
.then(data => {
loadSavedList();
});
}
function setDefaultItem(item, index) {
item.disabled = true;
fetch('/saved/set_default?index=' + index)
.then(response => response.json())
.then(data => {
loadSavedList();
});
}
// Load saved ssid and password list
function loadSavedList() {
fetch('/saved/list')
.then(response => response.json())
.then(data => {
renderSavedList(data);
});
}
// Load AP list from /scan
function loadAPList() {
if (button.disabled) {
return;
}
fetch('/scan')
.then(response => response.json())
.then(data => {
const lang = document.getElementById('language').value;
const apList = document.getElementById('ap_list');
apList.innerHTML = '<p>' + translations[lang].select_wifi + '</p>';
data.forEach(ap => {
// Create a link for each AP
const link = document.createElement('a');
link.href = '#';
link.textContent = ap.ssid + ' (' + ap.rssi + ' dBm)';
if (ap.authmode === 0) {
link.textContent += ' 🌐';
} else {
link.textContent += ' 🔒';
}
link.addEventListener('click', () => {
ssid.value = ap.ssid;
});
apList.appendChild(link);
});
setTimeout(loadAPList, 5000);
})
.catch(error => {
console.error('Error:', error);
});
}
// Set initial language
document.addEventListener('DOMContentLoaded', () => {
// 从 URL 参数中获取语言设置
const urlParams = new URLSearchParams(window.location.search);
const langParam = urlParams.get('lang');
// 获取浏览器语言
const browserLang = navigator.language || navigator.userLanguage;
// 语言映射表,将浏览器语言代码映射到支持的语言代码
const languageMap = {
'zh': 'zh-CN', // 简体中文
'zh-CN': 'zh-CN', // 简体中文
'zh-TW': 'zh-TW', // 繁体中文
'zh-HK': 'zh-TW', // 繁体中文(香港)
'ja': 'ja-JP', // 日语
'ja-JP': 'ja-JP', // 日语
'en': 'en-US', // 英语
'en-US': 'en-US', // 英语
'en-GB': 'en-US', // 英语(英国)
'en-CA': 'en-US', // 英语(加拿大)
'en-AU': 'en-US' // 英语(澳大利亚)
};
// 获取支持的语言代码
const getSupportedLanguage = (lang) => {
// 首先尝试完全匹配
if (languageMap[lang]) {
return languageMap[lang];
}
// 然后尝试只匹配主语言代码(例如 'zh' 而不是 'zh-CN'
const mainLang = lang.split('-')[0];
if (languageMap[mainLang]) {
return languageMap[mainLang];
}
// 如果都不匹配,返回英语
return 'en-US';
};
// 优先使用 URL 参数的语言设置,其次是本地存储的设置,最后是浏览器语言设置
const savedLang = langParam ||
localStorage.getItem('preferred_language') ||
getSupportedLanguage(browserLang);
document.getElementById('language').value = savedLang;
changeLanguage();
loadSavedList();
loadAPList();
loadAdvancedConfig();
});
// 监听 pageshow 事件以处理浏览器返回键
window.addEventListener('pageshow', (event) => {
if (event.persisted) {
loadSavedList();
} else {
// 正常加载时已处理
}
});
async function submitForm(event) {
event.preventDefault();
button.disabled = true;
error.textContent = '';
const ssidValue = ssid.value;
const passwordValue = document.getElementById('password').value;
const payload = {
ssid: ssidValue,
password: passwordValue
};
try {
const response = await fetch('/submit', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
});
const data = await response.json();
if (!data.success) {
throw new Error(data.error || '连接失败');
}
// 连接成功,跳转到完成页面
button.disabled = false;
window.location.href = '/done.html';
} catch (err) {
error.textContent = err.message;
button.disabled = false;
}
}
function switchTab(tabName) {
// 隐藏所有标签页内容
document.querySelectorAll('.tab-content').forEach(content => {
content.classList.remove('active');
});
// 移除所有标签页的active类
document.querySelectorAll('.tab').forEach(tab => {
tab.classList.remove('active');
});
// 显示选中的标签页内容
document.getElementById(tabName + '-tab').classList.add('active');
// 激活选中的标签
document.querySelector(`.tab[onclick="switchTab('${tabName}')"]`).classList.add('active');
}
async function submitAdvancedForm(event) {
event.preventDefault();
const advancedButton = document.getElementById('advanced_button');
const advancedError = document.getElementById('advanced_error');
advancedButton.disabled = true;
advancedError.textContent = '';
const config = {
ota_url: document.getElementById('ota_url').value,
max_tx_power: parseInt(document.getElementById('max_tx_power').value),
remember_bssid: document.getElementById('remember_bssid').checked,
sleep_mode: document.getElementById('sleep_mode').checked
};
try {
const response = await fetch('/advanced/submit', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(config)
});
const data = await response.json();
if (!data.success) {
throw new Error(data.error || '保存失败');
}
advancedButton.disabled = false;
showToast('配置已保存');
} catch (err) {
advancedError.textContent = err.message;
advancedButton.disabled = false;
}
}
// 显示提示框
function showToast(message) {
const toast = document.getElementById('toast');
toast.textContent = message;
toast.classList.add('show');
setTimeout(() => {
toast.classList.remove('show');
}, 3000);
}
// 加载高级配置
async function loadAdvancedConfig() {
try {
const response = await fetch('/advanced/config');
const data = await response.json();
if (data.ota_url) {
document.getElementById('ota_url').value = data.ota_url;
}
if (data.max_tx_power) {
document.getElementById('max_tx_power').value = data.max_tx_power;
}
if (data.remember_bssid !== undefined) {
document.getElementById('remember_bssid').checked = data.remember_bssid;
}
if (data.sleep_mode !== undefined) {
document.getElementById('sleep_mode').checked = data.sleep_mode;
}
} catch (error) {
console.error('Error loading advanced config:', error);
}
}
// 清空OTA地址
function clearOtaUrl() {
document.getElementById('ota_url').value = '';
}
</script>
</body>
</html>

View File

@@ -0,0 +1,85 @@
<!DOCTYPE html>
<html>
<head>
<title>WiFi Configuration</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
}
.container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
font-family: Arial, sans-serif;
/* Prevent content from overflowing */
max-height: 100vh;
box-sizing: border-box;
padding: 20px;
}
.checkmark {
width: 150px;
height: 150px;
margin-bottom: 30px;
}
.message {
text-align: center;
font-size: 20px;
margin-bottom: 20px;
}
</style>
</head>
<body>
<div class="container">
<svg class="checkmark" viewBox="0 0 52 52">
<circle class="checkmark__circle" cx="26" cy="26" r="25" fill="none" stroke="#4CAF50" stroke-width="2"/>
<path class="checkmark__check" fill="none" stroke="#4CAF50" stroke-width="2" d="M14.1 27.2l7.1 7.2 16.7-16.8"/>
</svg>
<div class="message">
<p>设备将在 <span id="countdown">3</span> 秒后重启</p>
<p>Device will restart in <span id="countdown-en">3</span> seconds</p>
</div>
</div>
<script>
window.addEventListener('load', function() {
let count = 3;
const countdownElement = document.getElementById('countdown');
const countdownEnElement = document.getElementById('countdown-en');
const timer = setInterval(function() {
count--;
countdownElement.textContent = count;
countdownEnElement.textContent = count;
if (count <= 0) {
clearInterval(timer);
fetch('/reboot', {
method: 'POST'
})
.then(response => {
if (response.ok) {
console.log('重启指令已发送');
window.close();
} else {
console.error('发送重启指令失败');
}
})
.catch(error => {
console.error('Error:', error);
});
}
}, 1000);
});
</script>
</body>
</html>

View File

@@ -0,0 +1,75 @@
#include "dns_server.h"
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <esp_log.h>
#include <lwip/sockets.h>
#include <lwip/netdb.h>
#define TAG "DnsServer"
DnsServer::DnsServer() {
}
DnsServer::~DnsServer() {
}
void DnsServer::Start(esp_ip4_addr_t gateway) {
ESP_LOGI(TAG, "Starting DNS server");
gateway_ = gateway;
fd_ = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (fd_ < 0) {
ESP_LOGE(TAG, "Failed to create socket");
return;
}
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(port_);
if (bind(fd_, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
ESP_LOGE(TAG, "failed to bind port %d", port_);
close(fd_);
return;
}
xTaskCreate([](void* arg) {
DnsServer* dns_server = static_cast<DnsServer*>(arg);
dns_server->Run();
}, "DnsServerTask", 4096, this, 5, NULL);
}
void DnsServer::Stop() {
ESP_LOGI(TAG, "Stopping DNS server");
}
void DnsServer::Run() {
char buffer[512];
while (1) {
struct sockaddr_in client_addr;
socklen_t client_addr_len = sizeof(client_addr);
int len = recvfrom(fd_, buffer, sizeof(buffer), 0, (struct sockaddr *)&client_addr, &client_addr_len);
if (len < 0) {
ESP_LOGE(TAG, "recvfrom failed, errno=%d", errno);
continue;
}
// Simple DNS response: point all queries to 192.168.4.1
buffer[2] |= 0x80; // Set response flag
buffer[3] |= 0x80; // Set Recursion Available
buffer[7] = 1; // Set answer count to 1
// Add answer section
memcpy(&buffer[len], "\xc0\x0c", 2); // Name pointer
len += 2;
memcpy(&buffer[len], "\x00\x01\x00\x01\x00\x00\x00\x1c\x00\x04", 10); // Type, class, TTL, data length
len += 10;
memcpy(&buffer[len], &gateway_.addr, 4); // 192.168.4.1
len += 4;
ESP_LOGI(TAG, "Sending DNS response to %s", inet_ntoa(gateway_.addr));
sendto(fd_, buffer, len, 0, (struct sockaddr *)&client_addr, client_addr_len);
}
}

View File

@@ -0,0 +1,11 @@
dependencies:
idf: '>=5.3'
description: ESP32 WiFi Configuration
files:
exclude:
- .git
- dist
license: MIT
repository: https://github.com/78/esp-wifi-connect
url: https://github.com/78/esp-wifi-connect
version: 2.5.2

View File

@@ -0,0 +1,22 @@
#ifndef _DNS_SERVER_H_
#define _DNS_SERVER_H_
#include <string>
#include <esp_netif_ip_addr.h>
class DnsServer {
public:
DnsServer();
~DnsServer();
void Start(esp_ip4_addr_t gateway);
void Stop();
private:
int port_ = 53;
int fd_ = -1;
esp_ip4_addr_t gateway_;
void Run();
};
#endif // _DNS_SERVER_H_

View File

@@ -0,0 +1,35 @@
#ifndef SSID_MANAGER_H
#define SSID_MANAGER_H
#include <string>
#include <vector>
struct SsidItem {
std::string ssid;
std::string password;
};
class SsidManager {
public:
static SsidManager& GetInstance() {
static SsidManager instance;
return instance;
}
void AddSsid(const std::string& ssid, const std::string& password);
void RemoveSsid(int index);
void SetDefaultSsid(int index);
void Clear();
const std::vector<SsidItem>& GetSsidList() const { return ssid_list_; }
private:
SsidManager();
~SsidManager();
void LoadFromNvs();
void SaveToNvs();
std::vector<SsidItem> ssid_list_;
};
#endif // SSID_MANAGER_H

View File

@@ -0,0 +1,69 @@
#ifndef _WIFI_CONFIGURATION_AP_H_
#define _WIFI_CONFIGURATION_AP_H_
#include <string>
#include <vector>
#include <mutex>
#include <esp_http_server.h>
#include <esp_event.h>
#include <esp_timer.h>
#include <esp_netif.h>
#include <esp_wifi_types_generic.h>
#include "dns_server.h"
class WifiConfigurationAp {
public:
static WifiConfigurationAp& GetInstance();
void SetSsidPrefix(const std::string &&ssid_prefix);
void SetLanguage(const std::string &&language);
void Start();
void Stop();
void StartSmartConfig();
bool ConnectToWifi(const std::string &ssid, const std::string &password);
void Save(const std::string &ssid, const std::string &password);
std::vector<wifi_ap_record_t> GetAccessPoints();
std::string GetSsid();
std::string GetWebServerUrl();
// Delete copy constructor and assignment operator
WifiConfigurationAp(const WifiConfigurationAp&) = delete;
WifiConfigurationAp& operator=(const WifiConfigurationAp&) = delete;
private:
// Private constructor
WifiConfigurationAp();
~WifiConfigurationAp();
std::mutex mutex_;
DnsServer dns_server_;
httpd_handle_t server_ = NULL;
EventGroupHandle_t event_group_;
std::string ssid_prefix_;
std::string language_;
esp_event_handler_instance_t instance_any_id_;
esp_event_handler_instance_t instance_got_ip_;
esp_timer_handle_t scan_timer_ = nullptr;
bool is_connecting_ = false;
esp_netif_t* ap_netif_ = nullptr;
std::vector<wifi_ap_record_t> ap_records_;
// 高级配置项
std::string ota_url_;
int8_t max_tx_power_;
bool remember_bssid_;
bool sleep_mode_;
void StartAccessPoint();
void StartWebServer();
// Event handlers
static void WifiEventHandler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data);
static void IpEventHandler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data);
static void SmartConfigEventHandler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data);
esp_event_handler_instance_t sc_event_instance_ = nullptr;
};
#endif // _WIFI_CONFIGURATION_AP_H_

View File

@@ -0,0 +1,67 @@
#ifndef _WIFI_STATION_H_
#define _WIFI_STATION_H_
#include <string>
#include <vector>
#include <functional>
#include <esp_event.h>
#include <esp_timer.h>
#include <esp_netif.h>
#include <esp_wifi_types_generic.h>
struct WifiApRecord {
std::string ssid;
std::string password;
int channel;
wifi_auth_mode_t authmode;
uint8_t bssid[6];
};
class WifiStation {
public:
static WifiStation& GetInstance();
void AddAuth(const std::string &&ssid, const std::string &&password);
void Start();
void Stop();
bool IsConnected();
bool WaitForConnected(int timeout_ms = 10000);
int8_t GetRssi();
std::string GetSsid() const { return ssid_; }
std::string GetIpAddress() const { return ip_address_; }
uint8_t GetChannel();
void SetPowerSaveMode(bool enabled);
void OnConnect(std::function<void(const std::string& ssid)> on_connect);
void OnConnected(std::function<void(const std::string& ssid)> on_connected);
void OnScanBegin(std::function<void()> on_scan_begin);
private:
WifiStation();
~WifiStation();
WifiStation(const WifiStation&) = delete;
WifiStation& operator=(const WifiStation&) = delete;
EventGroupHandle_t event_group_;
esp_timer_handle_t timer_handle_ = nullptr;
esp_event_handler_instance_t instance_any_id_ = nullptr;
esp_event_handler_instance_t instance_got_ip_ = nullptr;
esp_netif_t* station_netif_ = nullptr;
std::string ssid_;
std::string password_;
std::string ip_address_;
int8_t max_tx_power_;
uint8_t remember_bssid_;
int reconnect_count_ = 0;
std::function<void(const std::string& ssid)> on_connect_;
std::function<void(const std::string& ssid)> on_connected_;
std::function<void()> on_scan_begin_;
std::vector<WifiApRecord> connect_queue_;
void HandleScanResult();
void StartConnect();
static void WifiEventHandler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data);
static void IpEventHandler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data);
};
#endif // _WIFI_STATION_H_

View File

@@ -0,0 +1,125 @@
#include "ssid_manager.h"
#include <algorithm>
#include <esp_log.h>
#include <nvs_flash.h>
#define TAG "SsidManager"
#define NVS_NAMESPACE "wifi"
#define MAX_WIFI_SSID_COUNT 10
SsidManager::SsidManager() {
LoadFromNvs();
}
SsidManager::~SsidManager() {
}
void SsidManager::Clear() {
ssid_list_.clear();
SaveToNvs();
}
void SsidManager::LoadFromNvs() {
ssid_list_.clear();
// Load ssid and password from NVS from namespace "wifi"
// ssid, ssid1, ssid2, ... ssid9
// password, password1, password2, ... password9
nvs_handle_t nvs_handle;
auto ret = nvs_open(NVS_NAMESPACE, NVS_READONLY, &nvs_handle);
if (ret != ESP_OK) {
// The namespace doesn't exist, just return
ESP_LOGW(TAG, "NVS namespace %s doesn't exist", NVS_NAMESPACE);
return;
}
for (int i = 0; i < MAX_WIFI_SSID_COUNT; i++) {
std::string ssid_key = "ssid";
if (i > 0) {
ssid_key += std::to_string(i);
}
std::string password_key = "password";
if (i > 0) {
password_key += std::to_string(i);
}
char ssid[33];
char password[65];
size_t length = sizeof(ssid);
if (nvs_get_str(nvs_handle, ssid_key.c_str(), ssid, &length) != ESP_OK) {
continue;
}
length = sizeof(password);
if (nvs_get_str(nvs_handle, password_key.c_str(), password, &length) != ESP_OK) {
continue;
}
ssid_list_.push_back({ssid, password});
}
nvs_close(nvs_handle);
}
void SsidManager::SaveToNvs() {
nvs_handle_t nvs_handle;
ESP_ERROR_CHECK(nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs_handle));
for (int i = 0; i < MAX_WIFI_SSID_COUNT; i++) {
std::string ssid_key = "ssid";
if (i > 0) {
ssid_key += std::to_string(i);
}
std::string password_key = "password";
if (i > 0) {
password_key += std::to_string(i);
}
if (i < ssid_list_.size()) {
nvs_set_str(nvs_handle, ssid_key.c_str(), ssid_list_[i].ssid.c_str());
nvs_set_str(nvs_handle, password_key.c_str(), ssid_list_[i].password.c_str());
} else {
nvs_erase_key(nvs_handle, ssid_key.c_str());
nvs_erase_key(nvs_handle, password_key.c_str());
}
}
nvs_commit(nvs_handle);
nvs_close(nvs_handle);
}
void SsidManager::AddSsid(const std::string& ssid, const std::string& password) {
for (auto& item : ssid_list_) {
ESP_LOGI(TAG, "compare [%s:%d] [%s:%d]", item.ssid.c_str(), item.ssid.size(), ssid.c_str(), ssid.size());
if (item.ssid == ssid) {
ESP_LOGW(TAG, "SSID %s already exists, overwrite it", ssid.c_str());
item.password = password;
SaveToNvs();
return;
}
}
if (ssid_list_.size() >= MAX_WIFI_SSID_COUNT) {
ESP_LOGW(TAG, "SSID list is full, pop one");
ssid_list_.pop_back();
}
// Add the new ssid to the front of the list
ssid_list_.insert(ssid_list_.begin(), {ssid, password});
SaveToNvs();
}
void SsidManager::RemoveSsid(int index) {
if (index < 0 || index >= ssid_list_.size()) {
ESP_LOGW(TAG, "Invalid index %d", index);
return;
}
ssid_list_.erase(ssid_list_.begin() + index);
SaveToNvs();
}
void SsidManager::SetDefaultSsid(int index) {
if (index < 0 || index >= ssid_list_.size()) {
ESP_LOGW(TAG, "Invalid index %d", index);
return;
}
// Move the ssid at index to the front of the list
auto item = ssid_list_[index];
ssid_list_.erase(ssid_list_.begin() + index);
ssid_list_.insert(ssid_list_.begin(), item);
SaveToNvs();
}

View File

@@ -0,0 +1,852 @@
#include "wifi_configuration_ap.h"
#include <cstdio>
#include <memory>
#include <freertos/FreeRTOS.h>
#include <freertos/event_groups.h>
#include <esp_err.h>
#include <esp_event.h>
#include <esp_wifi.h>
#include <esp_log.h>
#include <esp_mac.h>
#include <esp_netif.h>
#include <lwip/ip_addr.h>
#include <nvs.h>
#include <nvs_flash.h>
#include <cJSON.h>
#include <esp_smartconfig.h>
#include "ssid_manager.h"
#define TAG "WifiConfigurationAp"
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
extern const char index_html_start[] asm("_binary_wifi_configuration_html_start");
extern const char done_html_start[] asm("_binary_wifi_configuration_done_html_start");
WifiConfigurationAp& WifiConfigurationAp::GetInstance() {
static WifiConfigurationAp instance;
return instance;
}
WifiConfigurationAp::WifiConfigurationAp()
{
event_group_ = xEventGroupCreate();
language_ = "zh-CN";
sleep_mode_ = false;
}
std::vector<wifi_ap_record_t> WifiConfigurationAp::GetAccessPoints()
{
std::lock_guard<std::mutex> lock(mutex_);
return ap_records_;
}
WifiConfigurationAp::~WifiConfigurationAp()
{
if (scan_timer_) {
esp_timer_stop(scan_timer_);
esp_timer_delete(scan_timer_);
}
if (event_group_) {
vEventGroupDelete(event_group_);
}
// Unregister event handlers if they were registered
if (instance_any_id_) {
esp_event_handler_instance_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, instance_any_id_);
}
if (instance_got_ip_) {
esp_event_handler_instance_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, instance_got_ip_);
}
}
void WifiConfigurationAp::SetLanguage(const std::string &&language)
{
language_ = language;
}
void WifiConfigurationAp::SetSsidPrefix(const std::string &&ssid_prefix)
{
ssid_prefix_ = ssid_prefix;
}
void WifiConfigurationAp::Start()
{
// Register event handlers
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
ESP_EVENT_ANY_ID,
&WifiConfigurationAp::WifiEventHandler,
this,
&instance_any_id_));
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
IP_EVENT_STA_GOT_IP,
&WifiConfigurationAp::IpEventHandler,
this,
&instance_got_ip_));
StartAccessPoint();
StartWebServer();
// Start scan immediately
esp_wifi_scan_start(nullptr, false);
// Setup periodic WiFi scan timer
esp_timer_create_args_t timer_args = {
.callback = [](void* arg) {
auto* self = static_cast<WifiConfigurationAp*>(arg);
if (!self->is_connecting_) {
esp_wifi_scan_start(nullptr, false);
}
},
.arg = this,
.dispatch_method = ESP_TIMER_TASK,
.name = "wifi_scan_timer",
.skip_unhandled_events = true
};
ESP_ERROR_CHECK(esp_timer_create(&timer_args, &scan_timer_));
}
std::string WifiConfigurationAp::GetSsid()
{
// Get MAC and use it to generate a unique SSID
uint8_t mac[6];
#if CONFIG_IDF_TARGET_ESP32P4
esp_wifi_get_mac(WIFI_IF_AP, mac);
#else
ESP_ERROR_CHECK(esp_read_mac(mac, ESP_MAC_WIFI_SOFTAP));
#endif
char ssid[32];
snprintf(ssid, sizeof(ssid), "%s-%02X%02X", ssid_prefix_.c_str(), mac[4], mac[5]);
return std::string(ssid);
}
std::string WifiConfigurationAp::GetWebServerUrl()
{
// http://192.168.4.1
return "http://192.168.4.1";
}
void WifiConfigurationAp::StartAccessPoint()
{
// Initialize the TCP/IP stack
ESP_ERROR_CHECK(esp_netif_init());
// Create the default event loop
ap_netif_ = esp_netif_create_default_wifi_ap();
// Set the router IP address to 192.168.4.1
esp_netif_ip_info_t ip_info;
IP4_ADDR(&ip_info.ip, 192, 168, 4, 1);
IP4_ADDR(&ip_info.gw, 192, 168, 4, 1);
IP4_ADDR(&ip_info.netmask, 255, 255, 255, 0);
esp_netif_dhcps_stop(ap_netif_);
esp_netif_set_ip_info(ap_netif_, &ip_info);
esp_netif_dhcps_start(ap_netif_);
// Start the DNS server
dns_server_.Start(ip_info.gw);
// Initialize the WiFi stack in Access Point mode
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
// Get the SSID
std::string ssid = GetSsid();
// Set the WiFi configuration
wifi_config_t wifi_config = {};
strcpy((char *)wifi_config.ap.ssid, ssid.c_str());
wifi_config.ap.ssid_len = ssid.length();
wifi_config.ap.max_connection = 4;
wifi_config.ap.authmode = WIFI_AUTH_OPEN;
// Start the WiFi Access Point
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_APSTA));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE));
ESP_ERROR_CHECK(esp_wifi_start());
#ifdef CONFIG_SOC_WIFI_SUPPORT_5G
// Temporarily use only 2.4G Wi-Fi.
ESP_ERROR_CHECK(esp_wifi_set_band_mode(WIFI_BAND_MODE_2G_ONLY));
#endif
ESP_LOGI(TAG, "Access Point started with SSID %s", ssid.c_str());
// 加载高级配置
nvs_handle_t nvs;
esp_err_t err = nvs_open("wifi", NVS_READONLY, &nvs);
if (err == ESP_OK) {
// 读取OTA URL
char ota_url[256] = {0};
size_t ota_url_size = sizeof(ota_url);
err = nvs_get_str(nvs, "ota_url", ota_url, &ota_url_size);
if (err == ESP_OK) {
ota_url_ = ota_url;
}
// 读取WiFi功率
err = nvs_get_i8(nvs, "max_tx_power", &max_tx_power_);
if (err == ESP_OK) {
ESP_LOGI(TAG, "WiFi max tx power from NVS: %d", max_tx_power_);
ESP_ERROR_CHECK(esp_wifi_set_max_tx_power(max_tx_power_));
} else {
esp_wifi_get_max_tx_power(&max_tx_power_);
}
// 读取BSSID记忆设置
uint8_t remember_bssid = 0;
err = nvs_get_u8(nvs, "remember_bssid", &remember_bssid);
if (err == ESP_OK) {
remember_bssid_ = remember_bssid != 0;
} else {
remember_bssid_ = false; // 默认值
}
// 读取睡眠模式设置
uint8_t sleep_mode = 0;
err = nvs_get_u8(nvs, "sleep_mode", &sleep_mode);
if (err == ESP_OK) {
sleep_mode_ = sleep_mode != 0;
} else {
sleep_mode_ = true; // 默认值
}
nvs_close(nvs);
}
}
void WifiConfigurationAp::StartWebServer()
{
// Start the web server
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
config.max_uri_handlers = 24;
config.uri_match_fn = httpd_uri_match_wildcard;
ESP_ERROR_CHECK(httpd_start(&server_, &config));
// Register the index.html file
httpd_uri_t index_html = {
.uri = "/",
.method = HTTP_GET,
.handler = [](httpd_req_t *req) -> esp_err_t {
httpd_resp_set_hdr(req, "Connection", "close");
httpd_resp_send(req, index_html_start, strlen(index_html_start));
return ESP_OK;
},
.user_ctx = NULL
};
ESP_ERROR_CHECK(httpd_register_uri_handler(server_, &index_html));
// Register the /saved/list URI
httpd_uri_t saved_list = {
.uri = "/saved/list",
.method = HTTP_GET,
.handler = [](httpd_req_t *req) -> esp_err_t {
auto ssid_list = SsidManager::GetInstance().GetSsidList();
std::string json_str = "[";
for (const auto& ssid : ssid_list) {
json_str += "\"" + ssid.ssid + "\",";
}
if (json_str.length() > 1) {
json_str.pop_back(); // Remove the last comma
}
json_str += "]";
httpd_resp_set_type(req, "application/json");
httpd_resp_set_hdr(req, "Connection", "close");
httpd_resp_send(req, json_str.c_str(), HTTPD_RESP_USE_STRLEN);
return ESP_OK;
},
.user_ctx = NULL
};
ESP_ERROR_CHECK(httpd_register_uri_handler(server_, &saved_list));
// Register the /saved/set_default URI
httpd_uri_t saved_set_default = {
.uri = "/saved/set_default",
.method = HTTP_GET,
.handler = [](httpd_req_t *req) -> esp_err_t {
std::string uri = req->uri;
auto pos = uri.find("?index=");
if (pos != std::string::npos) {
int index = -1;
sscanf(&req->uri[pos+7], "%d", &index);
ESP_LOGI(TAG, "Set default item %d", index);
SsidManager::GetInstance().SetDefaultSsid(index);
}
// send {}
httpd_resp_set_type(req, "application/json");
httpd_resp_set_hdr(req, "Connection", "close");
httpd_resp_send(req, "{}", HTTPD_RESP_USE_STRLEN);
return ESP_OK;
},
.user_ctx = NULL
};
ESP_ERROR_CHECK(httpd_register_uri_handler(server_, &saved_set_default));
// Register the /saved/delete URI
httpd_uri_t saved_delete = {
.uri = "/saved/delete",
.method = HTTP_GET,
.handler = [](httpd_req_t *req) -> esp_err_t {
std::string uri = req->uri;
auto pos = uri.find("?index=");
if (pos != std::string::npos) {
int index = -1;
sscanf(&req->uri[pos+7], "%d", &index);
ESP_LOGI(TAG, "Delete saved list item %d", index);
SsidManager::GetInstance().RemoveSsid(index);
}
// send {}
httpd_resp_set_type(req, "application/json");
httpd_resp_set_hdr(req, "Connection", "close");
httpd_resp_send(req, "{}", HTTPD_RESP_USE_STRLEN);
return ESP_OK;
},
.user_ctx = NULL
};
ESP_ERROR_CHECK(httpd_register_uri_handler(server_, &saved_delete));
// Register the /scan URI
httpd_uri_t scan = {
.uri = "/scan",
.method = HTTP_GET,
.handler = [](httpd_req_t *req) -> esp_err_t {
auto *this_ = static_cast<WifiConfigurationAp *>(req->user_ctx);
std::lock_guard<std::mutex> lock(this_->mutex_);
// Send the scan results as JSON
httpd_resp_set_type(req, "application/json");
httpd_resp_set_hdr(req, "Connection", "close");
httpd_resp_sendstr_chunk(req, "[");
for (int i = 0; i < this_->ap_records_.size(); i++) {
ESP_LOGI(TAG, "SSID: %s, RSSI: %d, Authmode: %d",
(char *)this_->ap_records_[i].ssid, this_->ap_records_[i].rssi, this_->ap_records_[i].authmode);
char buf[128];
snprintf(buf, sizeof(buf), "{\"ssid\":\"%s\",\"rssi\":%d,\"authmode\":%d}",
(char *)this_->ap_records_[i].ssid, this_->ap_records_[i].rssi, this_->ap_records_[i].authmode);
httpd_resp_sendstr_chunk(req, buf);
if (i < this_->ap_records_.size() - 1) {
httpd_resp_sendstr_chunk(req, ",");
}
}
httpd_resp_sendstr_chunk(req, "]");
httpd_resp_sendstr_chunk(req, NULL);
return ESP_OK;
},
.user_ctx = this
};
ESP_ERROR_CHECK(httpd_register_uri_handler(server_, &scan));
// Register the form submission
httpd_uri_t form_submit = {
.uri = "/submit",
.method = HTTP_POST,
.handler = [](httpd_req_t *req) -> esp_err_t {
char *buf;
size_t buf_len = req->content_len;
if (buf_len > 1024) { // 限制最大请求体大小
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Payload too large");
return ESP_FAIL;
}
buf = (char *)malloc(buf_len + 1);
if (!buf) {
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to allocate memory");
return ESP_FAIL;
}
int ret = httpd_req_recv(req, buf, buf_len);
if (ret <= 0) {
free(buf);
if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
httpd_resp_send_408(req);
} else {
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Failed to receive request");
}
return ESP_FAIL;
}
buf[ret] = '\0';
// 解析 JSON 数据
cJSON *json = cJSON_Parse(buf);
free(buf);
if (!json) {
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON");
return ESP_FAIL;
}
cJSON *ssid_item = cJSON_GetObjectItemCaseSensitive(json, "ssid");
cJSON *password_item = cJSON_GetObjectItemCaseSensitive(json, "password");
if (!cJSON_IsString(ssid_item) || (ssid_item->valuestring == NULL) || (strlen(ssid_item->valuestring) >= 33)) {
cJSON_Delete(json);
httpd_resp_send(req, "{\"success\":false,\"error\":\"Invalid SSID\"}", HTTPD_RESP_USE_STRLEN);
return ESP_OK;
}
std::string ssid_str = ssid_item->valuestring;
std::string password_str = "";
if (cJSON_IsString(password_item) && (password_item->valuestring != NULL) && (strlen(password_item->valuestring) < 65)) {
password_str = password_item->valuestring;
}
// 获取当前对象
auto *this_ = static_cast<WifiConfigurationAp *>(req->user_ctx);
if (!this_->ConnectToWifi(ssid_str, password_str)) {
cJSON_Delete(json);
httpd_resp_send(req, "{\"success\":false,\"error\":\"Failed to connect to the Access Point\"}", HTTPD_RESP_USE_STRLEN);
return ESP_OK;
}
this_->Save(ssid_str, password_str);
cJSON_Delete(json);
// 设置成功响应
httpd_resp_set_type(req, "application/json");
httpd_resp_set_hdr(req, "Connection", "close");
httpd_resp_send(req, "{\"success\":true}", HTTPD_RESP_USE_STRLEN);
return ESP_OK;
},
.user_ctx = this
};
ESP_ERROR_CHECK(httpd_register_uri_handler(server_, &form_submit));
// Register the done.html page
httpd_uri_t done_html = {
.uri = "/done.html",
.method = HTTP_GET,
.handler = [](httpd_req_t *req) -> esp_err_t {
httpd_resp_set_hdr(req, "Connection", "close");
httpd_resp_send(req, done_html_start, strlen(done_html_start));
return ESP_OK;
},
.user_ctx = NULL
};
ESP_ERROR_CHECK(httpd_register_uri_handler(server_, &done_html));
// Register the reboot endpoint
httpd_uri_t reboot = {
.uri = "/reboot",
.method = HTTP_POST,
.handler = [](httpd_req_t *req) -> esp_err_t {
auto* this_ = static_cast<WifiConfigurationAp*>(req->user_ctx);
// 设置响应头,防止浏览器缓存
httpd_resp_set_type(req, "application/json");
httpd_resp_set_hdr(req, "Cache-Control", "no-store");
httpd_resp_set_hdr(req, "Connection", "close");
// 发送响应
httpd_resp_send(req, "{\"success\":true}", HTTPD_RESP_USE_STRLEN);
// 创建一个延迟重启任务
ESP_LOGI(TAG, "Rebooting...");
xTaskCreate([](void *ctx) {
// 等待200ms确保HTTP响应完全发送
vTaskDelay(pdMS_TO_TICKS(200));
// 停止Web服务器
auto* self = static_cast<WifiConfigurationAp*>(ctx);
if (self->server_) {
httpd_stop(self->server_);
}
// 再等待100ms确保所有连接都已关闭
vTaskDelay(pdMS_TO_TICKS(100));
// 执行重启
esp_restart();
}, "reboot_task", 4096, this_, 5, NULL);
return ESP_OK;
},
.user_ctx = this
};
ESP_ERROR_CHECK(httpd_register_uri_handler(server_, &reboot));
auto captive_portal_handler = [](httpd_req_t *req) -> esp_err_t {
auto *this_ = static_cast<WifiConfigurationAp *>(req->user_ctx);
std::string url = this_->GetWebServerUrl() + "/?lang=" + this_->language_;
// Set content type to prevent browser warnings
httpd_resp_set_type(req, "text/html");
httpd_resp_set_status(req, "302 Found");
httpd_resp_set_hdr(req, "Location", url.c_str());
httpd_resp_set_hdr(req, "Connection", "close");
httpd_resp_send(req, NULL, 0);
return ESP_OK;
};
// Register all common captive portal detection endpoints
const char* captive_portal_urls[] = {
"/hotspot-detect.html", // Apple
"/generate_204*", // Android
"/mobile/status.php", // Android
"/check_network_status.txt", // Windows
"/ncsi.txt", // Windows
"/fwlink/", // Microsoft
"/connectivity-check.html", // Firefox
"/success.txt", // Various
"/portal.html", // Various
"/library/test/success.html" // Apple
};
for (const auto& url : captive_portal_urls) {
httpd_uri_t redirect_uri = {
.uri = url,
.method = HTTP_GET,
.handler = captive_portal_handler,
.user_ctx = this
};
ESP_ERROR_CHECK(httpd_register_uri_handler(server_, &redirect_uri));
}
// Register the /advanced/config URI
httpd_uri_t advanced_config = {
.uri = "/advanced/config",
.method = HTTP_GET,
.handler = [](httpd_req_t *req) -> esp_err_t {
// 获取当前对象
auto *this_ = static_cast<WifiConfigurationAp *>(req->user_ctx);
// 创建JSON对象
cJSON *json = cJSON_CreateObject();
if (!json) {
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to create JSON");
return ESP_FAIL;
}
// 添加配置项到JSON
if (!this_->ota_url_.empty()) {
cJSON_AddStringToObject(json, "ota_url", this_->ota_url_.c_str());
}
cJSON_AddNumberToObject(json, "max_tx_power", this_->max_tx_power_);
cJSON_AddBoolToObject(json, "remember_bssid", this_->remember_bssid_);
cJSON_AddBoolToObject(json, "sleep_mode", this_->sleep_mode_);
// 发送JSON响应
char *json_str = cJSON_PrintUnformatted(json);
cJSON_Delete(json);
if (!json_str) {
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to print JSON");
return ESP_FAIL;
}
httpd_resp_set_type(req, "application/json");
httpd_resp_set_hdr(req, "Connection", "close");
httpd_resp_send(req, json_str, strlen(json_str));
free(json_str);
return ESP_OK;
},
.user_ctx = this
};
ESP_ERROR_CHECK(httpd_register_uri_handler(server_, &advanced_config));
// Register the /advanced/submit URI
httpd_uri_t advanced_submit = {
.uri = "/advanced/submit",
.method = HTTP_POST,
.handler = [](httpd_req_t *req) -> esp_err_t {
char *buf;
size_t buf_len = req->content_len;
if (buf_len > 1024) {
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Payload too large");
return ESP_FAIL;
}
buf = (char *)malloc(buf_len + 1);
if (!buf) {
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to allocate memory");
return ESP_FAIL;
}
int ret = httpd_req_recv(req, buf, buf_len);
if (ret <= 0) {
free(buf);
if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
httpd_resp_send_408(req);
} else {
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Failed to receive request");
}
return ESP_FAIL;
}
buf[ret] = '\0';
// 解析JSON数据
cJSON *json = cJSON_Parse(buf);
free(buf);
if (!json) {
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON");
return ESP_FAIL;
}
// 获取当前对象
auto *this_ = static_cast<WifiConfigurationAp *>(req->user_ctx);
// 打开NVS
nvs_handle_t nvs;
esp_err_t err = nvs_open("wifi", NVS_READWRITE, &nvs);
if (err != ESP_OK) {
cJSON_Delete(json);
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to open NVS");
return ESP_FAIL;
}
// 保存OTA URL
cJSON *ota_url = cJSON_GetObjectItem(json, "ota_url");
if (cJSON_IsString(ota_url) && ota_url->valuestring) {
this_->ota_url_ = ota_url->valuestring;
err = nvs_set_str(nvs, "ota_url", this_->ota_url_.c_str());
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to save OTA URL: %d", err);
}
}
// 保存WiFi功率
cJSON *max_tx_power = cJSON_GetObjectItem(json, "max_tx_power");
if (cJSON_IsNumber(max_tx_power)) {
this_->max_tx_power_ = max_tx_power->valueint;
err = esp_wifi_set_max_tx_power(this_->max_tx_power_);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to set WiFi power: %d", err);
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to set WiFi power");
return ESP_FAIL;
}
err = nvs_set_i8(nvs, "max_tx_power", this_->max_tx_power_);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to save WiFi power: %d", err);
}
}
// 保存BSSID记忆设置
cJSON *remember_bssid = cJSON_GetObjectItem(json, "remember_bssid");
if (cJSON_IsBool(remember_bssid)) {
this_->remember_bssid_ = cJSON_IsTrue(remember_bssid);
err = nvs_set_u8(nvs, "remember_bssid", this_->remember_bssid_ ? 1 : 0);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to save remember_bssid: %d", err);
}
}
// 保存睡眠模式设置
cJSON *sleep_mode = cJSON_GetObjectItem(json, "sleep_mode");
if (cJSON_IsBool(sleep_mode)) {
this_->sleep_mode_ = cJSON_IsTrue(sleep_mode);
err = nvs_set_u8(nvs, "sleep_mode", this_->sleep_mode_ ? 1 : 0);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to save sleep_mode: %d", err);
}
}
// 提交更改
err = nvs_commit(nvs);
nvs_close(nvs);
cJSON_Delete(json);
if (err != ESP_OK) {
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to save configuration");
return ESP_FAIL;
}
// 发送成功响应
httpd_resp_set_type(req, "application/json");
httpd_resp_set_hdr(req, "Connection", "close");
httpd_resp_send(req, "{\"success\":true}", HTTPD_RESP_USE_STRLEN);
ESP_LOGI(TAG, "Saved settings: ota_url=%s, max_tx_power=%d, remember_bssid=%d, sleep_mode=%d",
this_->ota_url_.c_str(), this_->max_tx_power_, this_->remember_bssid_, this_->sleep_mode_);
return ESP_OK;
},
.user_ctx = this
};
ESP_ERROR_CHECK(httpd_register_uri_handler(server_, &advanced_submit));
ESP_LOGI(TAG, "Web server started");
}
bool WifiConfigurationAp::ConnectToWifi(const std::string &ssid, const std::string &password)
{
if (ssid.empty()) {
ESP_LOGE(TAG, "SSID cannot be empty");
return false;
}
if (ssid.length() > 32) { // WiFi SSID 最大长度
ESP_LOGE(TAG, "SSID too long");
return false;
}
if (password.length() > 64) {
ESP_LOGE(TAG, "Password too long");
return false;
}
is_connecting_ = true;
esp_wifi_scan_stop();
xEventGroupClearBits(event_group_, WIFI_CONNECTED_BIT | WIFI_FAIL_BIT);
wifi_config_t wifi_config;
bzero(&wifi_config, sizeof(wifi_config));
strlcpy((char *)wifi_config.sta.ssid, ssid.c_str(), 32);
strlcpy((char *)wifi_config.sta.password, password.c_str(), 64);
wifi_config.sta.scan_method = WIFI_ALL_CHANNEL_SCAN;
wifi_config.sta.failure_retry_cnt = 1;
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
auto ret = esp_wifi_connect();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to connect to WiFi: %d", ret);
is_connecting_ = false;
return false;
}
ESP_LOGI(TAG, "Connecting to WiFi %s", ssid.c_str());
// Wait for the connection to complete for 5 seconds
EventBits_t bits = xEventGroupWaitBits(event_group_, WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, pdTRUE, pdFALSE, pdMS_TO_TICKS(10000));
is_connecting_ = false;
if (bits & WIFI_CONNECTED_BIT) {
ESP_LOGI(TAG, "Connected to WiFi %s", ssid.c_str());
esp_wifi_disconnect();
return true;
} else {
ESP_LOGE(TAG, "Failed to connect to WiFi %s", ssid.c_str());
return false;
}
}
void WifiConfigurationAp::Save(const std::string &ssid, const std::string &password)
{
ESP_LOGI(TAG, "Save SSID %s %d", ssid.c_str(), ssid.length());
SsidManager::GetInstance().AddSsid(ssid, password);
}
void WifiConfigurationAp::WifiEventHandler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data)
{
WifiConfigurationAp* self = static_cast<WifiConfigurationAp*>(arg);
if (event_id == WIFI_EVENT_AP_STACONNECTED) {
wifi_event_ap_staconnected_t* event = (wifi_event_ap_staconnected_t*) event_data;
ESP_LOGI(TAG, "Station " MACSTR " joined, AID=%d", MAC2STR(event->mac), event->aid);
} else if (event_id == WIFI_EVENT_AP_STADISCONNECTED) {
wifi_event_ap_stadisconnected_t* event = (wifi_event_ap_stadisconnected_t*) event_data;
ESP_LOGI(TAG, "Station " MACSTR " left, AID=%d", MAC2STR(event->mac), event->aid);
} else if (event_id == WIFI_EVENT_STA_CONNECTED) {
xEventGroupSetBits(self->event_group_, WIFI_CONNECTED_BIT);
} else if (event_id == WIFI_EVENT_STA_DISCONNECTED) {
xEventGroupSetBits(self->event_group_, WIFI_FAIL_BIT);
} else if (event_id == WIFI_EVENT_SCAN_DONE) {
std::lock_guard<std::mutex> lock(self->mutex_);
uint16_t ap_num = 0;
esp_wifi_scan_get_ap_num(&ap_num);
self->ap_records_.resize(ap_num);
esp_wifi_scan_get_ap_records(&ap_num, self->ap_records_.data());
// 扫描完成等待10秒后再次扫描
esp_timer_start_once(self->scan_timer_, 10 * 1000000);
}
}
void WifiConfigurationAp::IpEventHandler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data)
{
WifiConfigurationAp* self = static_cast<WifiConfigurationAp*>(arg);
if (event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
ESP_LOGI(TAG, "Got IP:" IPSTR, IP2STR(&event->ip_info.ip));
xEventGroupSetBits(self->event_group_, WIFI_CONNECTED_BIT);
}
}
void WifiConfigurationAp::StartSmartConfig()
{
// 注册SmartConfig事件处理器
ESP_ERROR_CHECK(esp_event_handler_instance_register(SC_EVENT, ESP_EVENT_ANY_ID,
&WifiConfigurationAp::SmartConfigEventHandler, this, &sc_event_instance_));
// 初始化SmartConfig配置
smartconfig_start_config_t cfg = SMARTCONFIG_START_CONFIG_DEFAULT();
// cfg.esp_touch_v2_enable_crypt = true;
// cfg.esp_touch_v2_key = "1234567890123456"; // 设置16字节加密密钥
// 启动SmartConfig服务
ESP_ERROR_CHECK(esp_smartconfig_start(&cfg));
ESP_LOGI(TAG, "SmartConfig started");
}
void WifiConfigurationAp::SmartConfigEventHandler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
WifiConfigurationAp *self = static_cast<WifiConfigurationAp *>(arg);
if (event_base == SC_EVENT){
switch (event_id){
case SC_EVENT_SCAN_DONE:
ESP_LOGI(TAG, "SmartConfig scan done");
break;
case SC_EVENT_FOUND_CHANNEL:
ESP_LOGI(TAG, "Found SmartConfig channel");
break;
case SC_EVENT_GOT_SSID_PSWD:{
ESP_LOGI(TAG, "Got SmartConfig credentials");
smartconfig_event_got_ssid_pswd_t *evt = (smartconfig_event_got_ssid_pswd_t *)event_data;
char ssid[32], password[64];
memcpy(ssid, evt->ssid, sizeof(evt->ssid));
memcpy(password, evt->password, sizeof(evt->password));
ESP_LOGI(TAG, "SmartConfig SSID: %s, Password: %s", ssid, password);
// 尝试连接WiFi会失败故不连接
self->Save(ssid, password);
xTaskCreate([](void *ctx){
ESP_LOGI(TAG, "Restarting in 3 second");
vTaskDelay(pdMS_TO_TICKS(3000));
esp_restart();
}, "restart_task", 4096, NULL, 5, NULL);
break;
}
case SC_EVENT_SEND_ACK_DONE:
ESP_LOGI(TAG, "SmartConfig ACK sent");
esp_smartconfig_stop();
break;
}
}
}
void WifiConfigurationAp::Stop() {
// 停止SmartConfig服务
if (sc_event_instance_) {
esp_event_handler_instance_unregister(SC_EVENT, ESP_EVENT_ANY_ID, sc_event_instance_);
sc_event_instance_ = nullptr;
}
esp_smartconfig_stop();
// 停止定时器
if (scan_timer_) {
esp_timer_stop(scan_timer_);
esp_timer_delete(scan_timer_);
scan_timer_ = nullptr;
}
// 停止Web服务器
if (server_) {
httpd_stop(server_);
server_ = nullptr;
}
// 停止DNS服务器
dns_server_.Stop();
// 注销事件处理器
if (instance_any_id_) {
esp_event_handler_instance_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, instance_any_id_);
instance_any_id_ = nullptr;
}
if (instance_got_ip_) {
esp_event_handler_instance_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, instance_got_ip_);
instance_got_ip_ = nullptr;
}
// 停止WiFi并重置模式
esp_wifi_stop();
esp_wifi_deinit();
esp_wifi_set_mode(WIFI_MODE_NULL);
// 释放网络接口资源
if (ap_netif_) {
esp_netif_destroy(ap_netif_);
ap_netif_ = nullptr;
}
ESP_LOGI(TAG, "Wifi configuration AP stopped");
}

View File

@@ -0,0 +1,279 @@
#include "wifi_station.h"
#include <cstring>
#include <algorithm>
#include <freertos/FreeRTOS.h>
#include <freertos/event_groups.h>
#include <esp_log.h>
#include <esp_wifi.h>
#include <nvs.h>
#include "nvs_flash.h"
#include <esp_netif.h>
#include <esp_system.h>
#include "ssid_manager.h"
#define TAG "WifiStation"
#define WIFI_EVENT_CONNECTED BIT0
#define MAX_RECONNECT_COUNT 5
WifiStation& WifiStation::GetInstance() {
static WifiStation instance;
return instance;
}
WifiStation::WifiStation() {
// Create the event group
event_group_ = xEventGroupCreate();
// 读取配置
nvs_handle_t nvs;
esp_err_t err = nvs_open("wifi", NVS_READONLY, &nvs);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to open NVS: %d", err);
}
err = nvs_get_i8(nvs, "max_tx_power", &max_tx_power_);
if (err != ESP_OK) {
max_tx_power_ = 0;
}
err = nvs_get_u8(nvs, "remember_bssid", &remember_bssid_);
if (err != ESP_OK) {
remember_bssid_ = 0;
}
nvs_close(nvs);
}
WifiStation::~WifiStation() {
vEventGroupDelete(event_group_);
}
void WifiStation::AddAuth(const std::string &&ssid, const std::string &&password) {
auto& ssid_manager = SsidManager::GetInstance();
ssid_manager.AddSsid(ssid, password);
}
void WifiStation::Stop() {
if (timer_handle_ != nullptr) {
esp_timer_stop(timer_handle_);
esp_timer_delete(timer_handle_);
timer_handle_ = nullptr;
}
esp_wifi_scan_stop();
// 取消注册事件处理程序
if (instance_any_id_ != nullptr) {
ESP_ERROR_CHECK(esp_event_handler_instance_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, instance_any_id_));
instance_any_id_ = nullptr;
}
if (instance_got_ip_ != nullptr) {
ESP_ERROR_CHECK(esp_event_handler_instance_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, instance_got_ip_));
instance_got_ip_ = nullptr;
}
// Reset the WiFi stack
ESP_ERROR_CHECK(esp_wifi_stop());
ESP_ERROR_CHECK(esp_wifi_deinit());
if (station_netif_ != nullptr) {
// TODO: esp_netif_destroy will cause crash
// esp_netif_destroy(station_netif_);
station_netif_ = nullptr;
}
}
void WifiStation::OnScanBegin(std::function<void()> on_scan_begin) {
on_scan_begin_ = on_scan_begin;
}
void WifiStation::OnConnect(std::function<void(const std::string& ssid)> on_connect) {
on_connect_ = on_connect;
}
void WifiStation::OnConnected(std::function<void(const std::string& ssid)> on_connected) {
on_connected_ = on_connected;
}
void WifiStation::Start() {
// Initialize the TCP/IP stack
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
ESP_EVENT_ANY_ID,
&WifiStation::WifiEventHandler,
this,
&instance_any_id_));
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
IP_EVENT_STA_GOT_IP,
&WifiStation::IpEventHandler,
this,
&instance_got_ip_));
// Create the default event loop
station_netif_ = esp_netif_create_default_wifi_sta();
// Initialize the WiFi stack in station mode
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
cfg.nvs_enable = false;
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_start());
if (max_tx_power_ != 0) {
ESP_ERROR_CHECK(esp_wifi_set_max_tx_power(max_tx_power_));
}
// Setup the timer to scan WiFi
esp_timer_create_args_t timer_args = {
.callback = [](void* arg) {
esp_wifi_scan_start(nullptr, false);
},
.arg = this,
.dispatch_method = ESP_TIMER_TASK,
.name = "WiFiScanTimer",
.skip_unhandled_events = true
};
ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_));
}
bool WifiStation::WaitForConnected(int timeout_ms) {
auto bits = xEventGroupWaitBits(event_group_, WIFI_EVENT_CONNECTED, pdFALSE, pdFALSE, timeout_ms / portTICK_PERIOD_MS);
return (bits & WIFI_EVENT_CONNECTED) != 0;
}
void WifiStation::HandleScanResult() {
uint16_t ap_num = 0;
esp_wifi_scan_get_ap_num(&ap_num);
wifi_ap_record_t *ap_records = (wifi_ap_record_t *)malloc(ap_num * sizeof(wifi_ap_record_t));
esp_wifi_scan_get_ap_records(&ap_num, ap_records);
// sort by rssi descending
std::sort(ap_records, ap_records + ap_num, [](const wifi_ap_record_t& a, const wifi_ap_record_t& b) {
return a.rssi > b.rssi;
});
auto& ssid_manager = SsidManager::GetInstance();
auto ssid_list = ssid_manager.GetSsidList();
for (int i = 0; i < ap_num; i++) {
auto ap_record = ap_records[i];
auto it = std::find_if(ssid_list.begin(), ssid_list.end(), [ap_record](const SsidItem& item) {
return strcmp((char *)ap_record.ssid, item.ssid.c_str()) == 0;
});
if (it != ssid_list.end()) {
ESP_LOGI(TAG, "Found AP: %s, BSSID: %02x:%02x:%02x:%02x:%02x:%02x, RSSI: %d, Channel: %d, Authmode: %d",
(char *)ap_record.ssid,
ap_record.bssid[0], ap_record.bssid[1], ap_record.bssid[2],
ap_record.bssid[3], ap_record.bssid[4], ap_record.bssid[5],
ap_record.rssi, ap_record.primary, ap_record.authmode);
WifiApRecord record = {
.ssid = it->ssid,
.password = it->password,
.channel = ap_record.primary,
.authmode = ap_record.authmode
};
memcpy(record.bssid, ap_record.bssid, 6);
connect_queue_.push_back(record);
}
}
free(ap_records);
if (connect_queue_.empty()) {
ESP_LOGI(TAG, "Wait for next scan");
esp_timer_start_once(timer_handle_, 10 * 1000);
return;
}
StartConnect();
}
void WifiStation::StartConnect() {
auto ap_record = connect_queue_.front();
connect_queue_.erase(connect_queue_.begin());
ssid_ = ap_record.ssid;
password_ = ap_record.password;
if (on_connect_) {
on_connect_(ssid_);
}
wifi_config_t wifi_config;
bzero(&wifi_config, sizeof(wifi_config));
strcpy((char *)wifi_config.sta.ssid, ap_record.ssid.c_str());
strcpy((char *)wifi_config.sta.password, ap_record.password.c_str());
if (remember_bssid_) {
wifi_config.sta.channel = ap_record.channel;
memcpy(wifi_config.sta.bssid, ap_record.bssid, 6);
wifi_config.sta.bssid_set = true;
}
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
reconnect_count_ = 0;
ESP_ERROR_CHECK(esp_wifi_connect());
}
int8_t WifiStation::GetRssi() {
// Get station info
wifi_ap_record_t ap_info;
ESP_ERROR_CHECK(esp_wifi_sta_get_ap_info(&ap_info));
return ap_info.rssi;
}
uint8_t WifiStation::GetChannel() {
// Get station info
wifi_ap_record_t ap_info;
ESP_ERROR_CHECK(esp_wifi_sta_get_ap_info(&ap_info));
return ap_info.primary;
}
bool WifiStation::IsConnected() {
return xEventGroupGetBits(event_group_) & WIFI_EVENT_CONNECTED;
}
void WifiStation::SetPowerSaveMode(bool enabled) {
ESP_ERROR_CHECK(esp_wifi_set_ps(enabled ? WIFI_PS_MIN_MODEM : WIFI_PS_NONE));
}
// Static event handler functions
void WifiStation::WifiEventHandler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) {
auto* this_ = static_cast<WifiStation*>(arg);
if (event_id == WIFI_EVENT_STA_START) {
esp_wifi_scan_start(nullptr, false);
if (this_->on_scan_begin_) {
this_->on_scan_begin_();
}
} else if (event_id == WIFI_EVENT_SCAN_DONE) {
this_->HandleScanResult();
} else if (event_id == WIFI_EVENT_STA_DISCONNECTED) {
xEventGroupClearBits(this_->event_group_, WIFI_EVENT_CONNECTED);
if (this_->reconnect_count_ < MAX_RECONNECT_COUNT) {
esp_wifi_connect();
this_->reconnect_count_++;
ESP_LOGI(TAG, "Reconnecting %s (attempt %d / %d)", this_->ssid_.c_str(), this_->reconnect_count_, MAX_RECONNECT_COUNT);
return;
}
if (!this_->connect_queue_.empty()) {
this_->StartConnect();
return;
}
ESP_LOGI(TAG, "No more AP to connect, wait for next scan");
esp_timer_start_once(this_->timer_handle_, 10 * 1000);
} else if (event_id == WIFI_EVENT_STA_CONNECTED) {
}
}
void WifiStation::IpEventHandler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) {
auto* this_ = static_cast<WifiStation*>(arg);
auto* event = static_cast<ip_event_got_ip_t*>(event_data);
char ip_address[16];
esp_ip4addr_ntoa(&event->ip_info.ip, ip_address, sizeof(ip_address));
this_->ip_address_ = ip_address;
ESP_LOGI(TAG, "Got IP: %s", this_->ip_address_.c_str());
xEventGroupSetBits(this_->event_group_, WIFI_EVENT_CONNECTED);
if (this_->on_connected_) {
this_->on_connected_(this_->ssid_);
}
this_->connect_queue_.clear();
this_->reconnect_count_ = 0;
}