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 @@
-
0.0 атм
+
0.00 атм
@@ -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 +
+
+ + + +
+
+
+
Loading networks...
+
+
+
+ + + + + +
+
- +
@@ -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 += ` + + `; + }); + 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; }