add some code
This commit is contained in:
1
managed_components/78__esp-wifi-connect/.component_hash
Normal file
1
managed_components/78__esp-wifi-connect/.component_hash
Normal file
@@ -0,0 +1 @@
|
||||
752e62d469a341495368b6a3f812afdbe37569ea5b4460f4eedec42253118e8c
|
||||
1
managed_components/78__esp-wifi-connect/.gitignore
vendored
Normal file
1
managed_components/78__esp-wifi-connect/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
dist/
|
||||
1
managed_components/78__esp-wifi-connect/CHECKSUMS.json
Normal file
1
managed_components/78__esp-wifi-connect/CHECKSUMS.json
Normal 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"}]}
|
||||
18
managed_components/78__esp-wifi-connect/CMakeLists.txt
Normal file
18
managed_components/78__esp-wifi-connect/CMakeLists.txt
Normal 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"
|
||||
)
|
||||
75
managed_components/78__esp-wifi-connect/README.md
Normal file
75
managed_components/78__esp-wifi-connect/README.md
Normal 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();
|
||||
```
|
||||
|
||||
BIN
managed_components/78__esp-wifi-connect/assets/ap.png
Normal file
BIN
managed_components/78__esp-wifi-connect/assets/ap.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
BIN
managed_components/78__esp-wifi-connect/assets/ap_v2.png
Normal file
BIN
managed_components/78__esp-wifi-connect/assets/ap_v2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
BIN
managed_components/78__esp-wifi-connect/assets/ap_v3.png
Normal file
BIN
managed_components/78__esp-wifi-connect/assets/ap_v3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
@@ -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>
|
||||
@@ -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>
|
||||
75
managed_components/78__esp-wifi-connect/dns_server.cc
Normal file
75
managed_components/78__esp-wifi-connect/dns_server.cc
Normal 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);
|
||||
}
|
||||
}
|
||||
11
managed_components/78__esp-wifi-connect/idf_component.yml
Normal file
11
managed_components/78__esp-wifi-connect/idf_component.yml
Normal 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
|
||||
22
managed_components/78__esp-wifi-connect/include/dns_server.h
Normal file
22
managed_components/78__esp-wifi-connect/include/dns_server.h
Normal 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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
125
managed_components/78__esp-wifi-connect/ssid_manager.cc
Normal file
125
managed_components/78__esp-wifi-connect/ssid_manager.cc
Normal 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();
|
||||
}
|
||||
852
managed_components/78__esp-wifi-connect/wifi_configuration_ap.cc
Normal file
852
managed_components/78__esp-wifi-connect/wifi_configuration_ap.cc
Normal 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");
|
||||
}
|
||||
279
managed_components/78__esp-wifi-connect/wifi_station.cc
Normal file
279
managed_components/78__esp-wifi-connect/wifi_station.cc
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user