diff --git a/assets/index.html b/assets/index.html
index b56a751..972c896 100644
--- a/assets/index.html
+++ b/assets/index.html
@@ -309,7 +309,7 @@
@@ -317,17 +317,17 @@
-
+
-
+
diff --git a/assets/setup.html b/assets/setup.html
index c594311..d8ff61c 100644
--- a/assets/setup.html
+++ b/assets/setup.html
@@ -3,7 +3,7 @@
- Wi-Fi Setup
+ Wi-Fi Setup · Controller
@@ -198,7 +411,33 @@
Wi-Fi Connection
- Enter wifi ssid and password to connect device to local network
+ Select network or enter manually
+
+
+
+
+
+
+ 🔍 Select Wi-Fi network
+
+
+
+
+
+
+
-
+
@@ -239,7 +478,7 @@
Save settings
-
+
✓ Wi-Fi settings sent
@@ -249,19 +488,244 @@
const passwordInput = document.getElementById('passwordInput');
const saveBtn = document.getElementById('saveBtn');
const toast = document.getElementById('toastMsg');
+ const refreshBtn = document.getElementById('refreshWifiBtn');
- let saveTimeout = null;
+ // Кастомный дропдаун элементы
+ const selectTrigger = document.getElementById('selectTrigger');
+ const selectDropdown = document.getElementById('selectDropdown');
+ const triggerTextSpan = document.getElementById('triggerText');
+
+ let isRefreshing = false;
+ let currentNetworks = [];
+ let dropdownOpen = false;
+
+ // Функция для отрисовки иконок мощности (полоски Wi-Fi)
+ function getSignalBars(rssi) {
+ let strength = 0;
+ if (rssi >= -50) strength = 4;
+ else if (rssi >= -65) strength = 3;
+ else if (rssi >= -75) strength = 2;
+ else if (rssi >= -85) strength = 1;
+ else strength = 0;
+
+ const bars = [];
+ for (let i = 0; i < 4; i++) {
+ const active = i < strength;
+ const height = 6 + (i * 2);
+ bars.push(``);
+ }
+ return `${bars.join('')}
`;
+ }
+
+ function closeDropdown() {
+ if (!dropdownOpen) return;
+ dropdownOpen = false;
+ selectDropdown.classList.remove('show');
+ selectTrigger.classList.remove('open');
+ }
+
+ function toggleDropdown() {
+ if (dropdownOpen) {
+ closeDropdown();
+ } else {
+ dropdownOpen = true;
+ selectDropdown.classList.add('show');
+ selectTrigger.classList.add('open');
+ }
+ }
+
+ function escapeHtml(str) {
+ if (!str) return '';
+ return str.replace(/&/g, '&').replace(//g, '>');
+ }
+
+ function renderDropdown(networks) {
+ if (!networks.length) {
+ selectDropdown.innerHTML = '📡 No Wi-Fi networks found
';
+ return;
+ }
+ // сортировка по силе сигнала (rssi убывание)
+ const sorted = [...networks].sort((a,b) => (b.rssi || -100) - (a.rssi || -100));
+ let html = '';
+ sorted.forEach(net => {
+ const ssid = net.ssid || "Hidden/Unknown";
+ const rssi = net.rssi !== undefined ? net.rssi : -80;
+ const auth = net.auth_mode || "Unknown";
+ const barsSvg = getSignalBars(rssi);
+ const escapedSsid = escapeHtml(ssid);
+ html += `
+
+
${escapedSsid}
+
+ ${barsSvg}
+ ${escapeHtml(auth)}
+
+
+ `;
+ });
+ selectDropdown.innerHTML = html;
+
+ // добавить обработчики кликов на опции
+ document.querySelectorAll('.dropdown-option').forEach(opt => {
+ opt.addEventListener('click', (e) => {
+ e.stopPropagation();
+ const selectedSsid = opt.getAttribute('data-ssid');
+ if (selectedSsid) {
+ ssidInput.value = selectedSsid;
+ const authMode = opt.getAttribute('data-auth') || '';
+ showToast(`✓ Selected: ${selectedSsid} ${authMode ? '('+authMode+')' : ''}`, false);
+ closeDropdown();
+ }
+ });
+ });
+ }
+
+ function escapeHtmlAttr(str) {
+ if (!str) return '';
+ return str.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"');
+ }
+
+ async function fetchWifiNetworks() {
+ if (isRefreshing) return;
+ isRefreshing = true;
+ refreshBtn.classList.add('rotating');
+ selectTrigger.style.pointerEvents = 'none';
+ selectDropdown.innerHTML = '⏳ Scanning networks...
';
+
+ try {
+ const controller = new AbortController();
+ const timeoutId = setTimeout(() => controller.abort(), 7000);
+ const response = await fetch('/wifi_list', {
+ method: 'GET',
+ headers: { 'Accept': 'application/json' },
+ signal: controller.signal
+ });
+ clearTimeout(timeoutId);
+
+ if (!response.ok) {
+ let errText = `HTTP ${response.status}`;
+ try {
+ const errJson = await response.json();
+ errText = errJson.message || errJson.error || errText;
+ } catch(e) {}
+ throw new Error(errText);
+ }
+ const data = await response.json();
+ if (!data.success || !Array.isArray(data.networks)) {
+ throw new Error('Invalid response format');
+ }
+ currentNetworks = data.networks;
+ renderDropdown(currentNetworks);
+ if (currentNetworks.length === 0) {
+ showToast("📡 No networks found", false);
+ } else {
+ showToast(`Found ${currentNetworks.length} network(s)`, false);
+ }
+ } catch (error) {
+ console.error('WiFi scan error:', error);
+ let errMsg = "✗ Failed to load networks";
+ if (error.name === 'AbortError') errMsg = "✗ Scan timeout (7s)";
+ else if (error.message) errMsg = `✗ ${error.message}`;
+ showToast(errMsg, true);
+ selectDropdown.innerHTML = '⚠️ Scan failed, click refresh ↻
';
+ currentNetworks = [];
+ } finally {
+ isRefreshing = false;
+ refreshBtn.classList.remove('rotating');
+ selectTrigger.style.pointerEvents = 'auto';
+ }
+ }
+
+ // Отправка настроек Wi-Fi
+ async function sendWiFiSettings() {
+ let ssid = ssidInput.value.trim();
+ let password = passwordInput.value;
+
+ if (ssid === "") {
+ showToast("✗ Please enter or select SSID", true);
+ ssidInput.style.borderColor = "#e94f32";
+ setTimeout(() => ssidInput.style.borderColor = "", 1000);
+ return;
+ }
+
+ const payload = { ssid: ssid, password: password };
+ try {
+ const controller = new AbortController();
+ const timeoutId = setTimeout(() => controller.abort(), 5000);
+ const response = await fetch('/settings', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(payload),
+ signal: controller.signal
+ });
+ clearTimeout(timeoutId);
+ if (!response.ok) {
+ let errorText = `Error ${response.status}`;
+ try {
+ const errData = await response.json();
+ errorText = errData.message || errData.error || errorText;
+ } catch(e) {}
+ throw new Error(errorText);
+ }
+ const result = await response.json();
+ console.log('Wi-Fi settings saved:', result);
+ showToast(`✓ Wi-Fi credentials sent to device`, false);
+ passwordInput.value = "";
+ } catch (error) {
+ let errMsg = "✗ Connection failed";
+ if (error.name === 'AbortError') errMsg = "✗ Request timeout (5s)";
+ else if (error.message) errMsg = `✗ ${error.message}`;
+ showToast(errMsg, true);
+ }
+ }
+
+ function debounceSend() {
+ if (window.saveTimeout) clearTimeout(window.saveTimeout);
+ window.saveTimeout = setTimeout(() => sendWiFiSettings(), 200);
+ }
+
+ // --- Инициализация событий ---
+ saveBtn.addEventListener('click', (e) => {
+ e.preventDefault();
+ debounceSend();
+ });
+
+ [ssidInput, passwordInput].forEach(input => {
+ input.addEventListener('keypress', (e) => {
+ if (e.key === 'Enter') {
+ e.preventDefault();
+ debounceSend();
+ }
+ });
+ });
+
+ // Кастомный дропдаун: открыть/закрыть
+ selectTrigger.addEventListener('click', (e) => {
+ e.stopPropagation();
+ toggleDropdown();
+ });
+
+ // Закрыть при клике вне
+ document.addEventListener('click', function(e) {
+ const container = document.getElementById('customSelectContainer');
+ if (container && !container.contains(e.target)) {
+ closeDropdown();
+ }
+ });
+
+ // Обновление списка при нажатии на кнопку обновления
+ refreshBtn.addEventListener('click', (e) => {
+ e.preventDefault();
+ fetchWifiNetworks();
+ });
// Toggle password visibility
const togglePasswordBtn = document.getElementById('togglePassword');
const passwordField = document.getElementById('passwordInput');
-
if (togglePasswordBtn && passwordField) {
togglePasswordBtn.addEventListener('click', function() {
const type = passwordField.getAttribute('type') === 'password' ? 'text' : 'password';
passwordField.setAttribute('type', type);
-
- // Change eye icon
const svg = this.querySelector('svg');
if (type === 'text') {
svg.innerHTML = '';
@@ -269,15 +733,16 @@
svg.innerHTML = '';
}
});
-
- togglePasswordBtn.addEventListener('mouseenter', () => {
- togglePasswordBtn.style.opacity = '1';
- });
- togglePasswordBtn.addEventListener('mouseleave', () => {
- togglePasswordBtn.style.opacity = '0.7';
- });
+ togglePasswordBtn.addEventListener('mouseenter', () => { togglePasswordBtn.style.opacity = '1'; });
+ togglePasswordBtn.addEventListener('mouseleave', () => { togglePasswordBtn.style.opacity = '0.7'; });
}
+ fetchWifiNetworks();
+
+ window.addEventListener('beforeunload', () => {
+ if (window.saveTimeout) clearTimeout(window.saveTimeout);
+ });
+
function showToast(message, isError = false) {
toast.innerText = message;
if (isError) {
@@ -294,89 +759,8 @@
toast.style.borderColor = '#3c936e';
toast.style.color = '#c2f0d6';
}, 300);
- }, 1800);
+ }, 2000);
}
-
- async function sendWiFiSettings() {
- let ssid = ssidInput.value.trim();
- let password = passwordInput.value;
-
- if (ssid === "") {
- showToast("✗ Укажите SSID", true);
- ssidInput.style.borderColor = "#e94f32";
- setTimeout(() => ssidInput.style.borderColor = "", 1000);
- return;
- }
-
- const payload = {
- ssid: ssid,
- password: password
- };
-
- try {
- const controller = new AbortController();
- const timeoutId = setTimeout(() => controller.abort(), 5000);
-
- const response = await fetch('/settings', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify(payload),
- signal: controller.signal
- });
-
- clearTimeout(timeoutId);
-
- if (!response.ok) {
- let errorText = `Ошибка ${response.status}`;
- try {
- const errData = await response.json();
- errorText = errData.message || errData.error || errorText;
- } catch(e) {}
- throw new Error(errorText);
- }
-
- const result = await response.json();
- console.log('Wi-Fi settings saved:', result);
- showToast("✓ SSID and password sent!");
-
- passwordInput.value = "";
-
- } catch (error) {
- console.error('Error sending Wi-Fi settings:', error);
- let errMsg = "✗ Connection error";
- if (error.name === 'AbortError') errMsg = "✗ timeout";
- else if (error.message) errMsg = `✗ ${error.message}`;
- showToast(errMsg, true);
- }
- }
-
- function debounceSend() {
- if (saveTimeout) clearTimeout(saveTimeout);
- saveTimeout = setTimeout(() => {
- sendWiFiSettings();
- }, 200);
- }
-
- saveBtn.addEventListener('click', (e) => {
- e.preventDefault();
- debounceSend();
- });
-
- const inputs = [ssidInput, passwordInput];
- inputs.forEach(input => {
- input.addEventListener('keypress', (e) => {
- if (e.key === 'Enter') {
- e.preventDefault();
- debounceSend();
- }
- });
- });
-
- window.addEventListener('beforeunload', () => {
- if (saveTimeout) clearTimeout(saveTimeout);
- });
})();
diff --git a/main/main.c b/main/main.c
index fe3554d..375d9af 100644
--- a/main/main.c
+++ b/main/main.c
@@ -84,7 +84,7 @@
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
-#define DEFAULT_THRESHOLD_LOW 100
+#define DEFAULT_THRESHOLD_LOW 0
#define DEFAULT_THRESHOLD_UP 300
#define MAX_AP_SCAN_RESULTS 20
@@ -700,7 +700,7 @@ static esp_err_t parse_thresholds_json(const char *content, int *low_value, int
static esp_err_t root_get_handler(httpd_req_t *req) {
httpd_resp_set_type(req, "text/html; charset=utf-8");
- httpd_resp_send(req, (const char*)assets_index_html, HTTPD_RESP_USE_STRLEN);
+ httpd_resp_send(req, (const char*)assets_index_html, assets_index_html_len);
return ESP_OK;
}
@@ -782,7 +782,7 @@ static esp_err_t set_thresholds_handler(httpd_req_t *req) {
static esp_err_t setup_get_handler(httpd_req_t *req) {
httpd_resp_set_type(req, "text/html; charset=utf-8");
- httpd_resp_send(req, (const char*)assets_setup_html, HTTPD_RESP_USE_STRLEN);
+ httpd_resp_send(req, (const char*)assets_setup_html, assets_setup_html_len);
return ESP_OK;
}