Давайте рассмотрим полностью рабочую систему с раздельными файлами. Суть системы кратко сводится к следующему. У нас есть устройство ESP32. В него заливается скетч, о нем далее. В итоге устройство через заданную в коде и пароль сеть WiFi обращается к URL файлы json.
Для того чтобы изменить логику на пинах ESP, мы переходим на организованную HTML страничку, меняем и сохраняем конфигурацию, это осуществляется посредством AJAX и отработки PHP. Записывается конфигурационный файл json.
В итоге ESP каждый установленный период времени прописанный в коде обращается к конфигурации, "забирает ее к себе", считывает и тем самым меняет логику на пинах.
Проект работает по ссылке https://smartmatter.ru/ESP32/index.html. То есть вы можете залить код в вашу ESP и используя страницу поуправлять устройством! Не забудьте в коде ESP поставить вашу Wifi сеть и пароль.
const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASS";
...по факту все серверные траблы здесь уже реализованы, за счет нашего сайта!
Если же вы хотите создать свой обособленный проект, то надо будет осуществить следующие действия.
Итак:
1. Создайте файл api.php для обработки запросов
Файл: api.php
<?php // Включим CORS заголовки header('Access-Control-Allow-Origin: *'); header('Access-Control-Allow-Methods: GET, POST, OPTIONS'); header('Access-Control-Allow-Headers: Content-Type'); header('Content-Type: application/json'); // Если это предварительный запрос OPTIONS, просто завершаем if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(200); exit; } // Путь к файлу конфигурации $config_file = 'esp32_config.json'; // Функция для чтения конфигурации function read_config() { global $config_file; if (!file_exists($config_file)) { // Создаем дефолтную конфигурацию $default_config = [ 'timestamp' => time(), 'device' => 'ESP32 Controller', 'version' => '1.0', 'pins' => [ ['pin' => 2, 'state' => 'LOW', 'name' => 'Светодиод 1'], ['pin' => 4, 'state' => 'LOW', 'name' => 'Реле 1'], ['pin' => 5, 'state' => 'LOW', 'name' => 'Реле 2'], ['pin' => 12, 'state' => 'LOW', 'name' => 'Мотор'], ['pin' => 13, 'state' => 'LOW', 'name' => 'Насос'], ['pin' => 14, 'state' => 'LOW', 'name' => 'Вентилятор'], ['pin' => 15, 'state' => 'LOW', 'name' => 'Резерв 1'], ['pin' => 18, 'state' => 'LOW', 'name' => 'Резерв 2'] ] ]; file_put_contents($config_file, json_encode($default_config, JSON_PRETTY_PRINT)); return $default_config; } $content = file_get_contents($config_file); return json_decode($content, true); } // Функция для сохранения конфигурации function save_config($config) { global $config_file; $config['timestamp'] = time(); $result = file_put_contents($config_file, json_encode($config, JSON_PRETTY_PRINT)); return $result !== false; } // Обработка GET запроса - вернуть конфигурацию if ($_SERVER['REQUEST_METHOD'] === 'GET') { $config = read_config(); echo json_encode($config); exit; } // Обработка POST запроса - сохранить конфигурацию if ($_SERVER['REQUEST_METHOD'] === 'POST') { // Получаем сырые POST данные $input = file_get_contents('php://input'); $data = json_decode($input, true); if ($data === null) { http_response_code(400); echo json_encode(['error' => 'Invalid JSON data']); exit; } // Проверяем наличие необходимых полей if (!isset($data['config']) || !is_array($data['config'])) { http_response_code(400); echo json_encode(['error' => 'Missing config data']); exit; } // Сохраняем конфигурацию $success = save_config($data['config']); if ($success) { echo json_encode([ 'success' => true, 'message' => 'Configuration saved successfully', 'timestamp' => time() ]); } else { http_response_code(500); echo json_encode(['error' => 'Failed to save configuration']); } exit; } // Если метод не поддерживается http_response_code(405); echo json_encode(['error' => 'Method not allowed']); ?>
2. Создайте простой HTML интерфейс
Файл: index.html
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Управление ESP32</title> <style> body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; background-color: #f5f5f5; } .container { background: white; padding: 20px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); } h1 { color: #333; text-align: center; } .pins-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 15px; margin: 20px 0; } .pin-card { background: #f8f9fa; padding: 15px; border-radius: 8px; border-left: 4px solid #007bff; } .pin-number { font-size: 18px; font-weight: bold; color: #007bff; } .status { margin: 10px 0; font-weight: bold; } .status.on { color: #28a745; } .status.off { color: #dc3545; } .controls button { margin: 5px; padding: 8px 15px; border: none; border-radius: 4px; cursor: pointer; } .btn-on { background: #28a745; color: white; } .btn-off { background: #dc3545; color: white; } .controls-bottom { margin-top: 20px; display: flex; gap: 10px; flex-wrap: wrap; } .btn { padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; } .btn-primary { background: #007bff; color: white; } .btn-success { background: #28a745; color: white; } .btn-warning { background: #ffc107; color: black; } .status-message { padding: 10px; margin: 10px 0; border-radius: 5px; display: none; } .success { background: #d4edda; color: #155724; border: 1px solid #c3e6cb; } .error { background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; } pre { background: #2d2d2d; color: #f8f8f2; padding: 15px; border-radius: 5px; overflow-x: auto; } </style> </head> <body> <div class="container"> <h1>Управление ESP32</h1> <div class="status-message" id="statusMessage"></div> <div class="pins-grid" id="pinsContainer"> <!-- Пины будут сгенерированы JavaScript --> </div> <div class="controls-bottom"> <button class="btn btn-primary" onclick="loadConfig()">🔄 Загрузить с сервера</button> <button class="btn btn-success" onclick="saveConfig()">💾 Сохранить на сервер</button> <button class="btn" onclick="resetConfig()" style="background: #6c757d; color: white;">🔄 Сбросить все</button> <button class="btn btn-warning" onclick="copyJson()">📋 Копировать JSON</button> </div> <div style="margin-top: 30px;"> <h3>JSON конфигурация:</h3> <pre id="jsonOutput">Загрузка...</pre> <p>URL для ESP32: <code id="jsonUrl"></code></p> </div> </div> <script> // Конфигурация пинов const pinsConfig = [ { pin: 2, name: "Светодиод 1", defaultState: "LOW" }, { pin: 4, name: "Реле 1", defaultState: "LOW" }, { pin: 5, name: "Реле 2", defaultState: "LOW" }, { pin: 12, name: "Мотор", defaultState: "LOW" }, { pin: 13, name: "Насос", defaultState: "LOW" }, { pin: 14, name: "Вентилятор", defaultState: "LOW" }, { pin: 15, name: "Резерв 1", defaultState: "LOW" }, { pin: 18, name: "Резерв 2", defaultState: "LOW" } ]; let pinStates = {}; const apiUrl = 'api.php'; // URL к API const jsonFileUrl = window.location.pathname.replace('index.html', 'esp32_config.json'); // Инициализация function init() { // Устанавливаем URL для ESP32 document.getElementById('jsonUrl').textContent = window.location.origin + jsonFileUrl; // Загружаем конфигурацию с сервера loadConfig(); } // Загрузить конфигурацию с сервера function loadConfig() { showStatus('Загрузка конфигурации...', 'info'); fetch(apiUrl) .then(response => { if (!response.ok) { throw new Error('HTTP error: ' + response.status); } return response.json(); }) .then(data => { // Заполняем состояния пинов из сервера if (data.pins && Array.isArray(data.pins)) { data.pins.forEach(pin => { pinStates[pin.pin] = pin.state; }); } // Если данных нет, используем значения по умолчанию pinsConfig.forEach(pin => { if (!pinStates.hasOwnProperty(pin.pin)) { pinStates[pin.pin] = pin.defaultState; } }); renderPins(); updateJson(); showStatus('Конфигурация загружена с сервера', 'success'); }) .catch(error => { console.error('Ошибка загрузки:', error); // Используем значения по умолчанию pinsConfig.forEach(pin => { pinStates[pin.pin] = pin.defaultState; }); renderPins(); updateJson(); showStatus('Используются значения по умолчанию', 'warning'); }); } // Сохранить конфигурацию на сервер function saveConfig() { const config = getCurrentConfig(); showStatus('Сохранение конфигурации...', 'info'); fetch(apiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ config: config }) }) .then(response => { if (!response.ok) { return response.json().then(data => { throw new Error(data.error || 'HTTP error: ' + response.status); }); } return response.json(); }) .then(data => { if (data.success) { showStatus('Конфигурация успешно сохранена на сервере!', 'success'); updateJson(); } else { throw new Error(data.error || 'Unknown error'); } }) .catch(error => { console.error('Ошибка сохранения:', error); showStatus('Ошибка сохранения: ' + error.message, 'error'); }); } // Получить текущую конфигурацию function getCurrentConfig() { const pins = []; pinsConfig.forEach(pin => { pins.push({ pin: pin.pin, state: pinStates[pin.pin] || pin.defaultState, name: pin.name }); }); return { timestamp: new Date().getTime(), device: 'ESP32 Controller', version: '1.0', pins: pins }; } // Отобразить пины function renderPins() { const container = document.getElementById('pinsContainer'); container.innerHTML = ''; pinsConfig.forEach(pin => { const state = pinStates[pin.pin] || pin.defaultState; const card = document.createElement('div'); card.className = 'pin-card'; card.innerHTML = ` <div class="pin-number">GPIO ${pin.pin}</div> <div>${pin.name}</div> <div class="status ${state === 'HIGH' ? 'on' : 'off'}"> Статус: ${state === 'HIGH' ? 'ВКЛ' : 'ВЫКЛ'} </div> <div class="controls"> <button class="btn-on" onclick="setPinState(${pin.pin}, 'HIGH')">Включить</button> <button class="btn-off" onclick="setPinState(${pin.pin}, 'LOW')">Выключить</button> </div> `; container.appendChild(card); }); } // Установить состояние пина function setPinState(pin, state) { pinStates[pin] = state; renderPins(); updateJson(); showStatus(`Пин ${pin} установлен в ${state}`, 'success'); } // Обновить JSON отображение function updateJson() { const config = getCurrentConfig(); document.getElementById('jsonOutput').textContent = JSON.stringify(config, null, 2); } // Сбросить конфигурацию function resetConfig() { if (confirm('Сбросить все пины в состояние LOW?')) { pinsConfig.forEach(pin => { pinStates[pin.pin] = 'LOW'; }); renderPins(); updateJson(); showStatus('Все пины сброшены', 'success'); } } // Копировать JSON function copyJson() { const jsonText = document.getElementById('jsonOutput').textContent; navigator.clipboard.writeText(jsonText) .then(() => { showStatus('JSON скопирован в буфер обмена!', 'success'); }) .catch(err => { showStatus('Ошибка копирования: ' + err, 'error'); }); } // Показать статус function showStatus(message, type) { const status = document.getElementById('statusMessage'); status.textContent = message; status.className = `status-message ${type}`; status.style.display = 'block'; setTimeout(() => { status.style.display = 'none'; }, 3000); } // Инициализация при загрузке document.addEventListener('DOMContentLoaded', init); </script> </body> </html>
3. Код для ESP32 (HTTP версия)
Файл: esp32_controller.ino
#include <WiFi.h> #include <HTTPClient.h> #include <ArduinoJson.h> const char* ssid = "YOUR_WIFI_SSID"; const char* password = "YOUR_WIFI_PASS"; // URL к вашему JSON файлу на сервере const char* configUrl = "http://smartmatter.ru/ESP32/esp32_config.json"; // Пины для управления const int pins[] = {2, 4, 5, 12, 13, 14, 15, 18}; const int pinCount = sizeof(pins) / sizeof(pins[0]); // Для отладки unsigned long lastUpdateTime = 0; const long updateInterval = 10000; // 10 секунд void setup() { Serial.begin(115200); delay(3000); Serial.println("\n=== ESP32 Контроллер ==="); // Инициализация пинов for (int i = 0; i < pinCount; i++) { pinMode(pins[i], OUTPUT); digitalWrite(pins[i], LOW); Serial.printf("Пин %d: OUTPUT, LOW\n", pins[i]); } // Подключение к WiFi Serial.print("Подключение к WiFi..."); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(1000); Serial.print("."); } Serial.println("\nWiFi подключен!"); Serial.print("IP адрес: "); Serial.println(WiFi.localIP()); // Первое обновление updateFromServer(); } void loop() { // Проверяем WiFi соединение if (WiFi.status() != WL_CONNECTED) { Serial.println("WiFi отключен, переподключение..."); WiFi.reconnect(); delay(5000); return; } // Периодическое обновление конфигурации if (millis() - lastUpdateTime >= updateInterval) { updateFromServer(); lastUpdateTime = millis(); } delay(1000); } void updateFromServer() { Serial.println("\n--- Запрос конфигурации с сервера ---"); Serial.print("URL: "); Serial.println(configUrl); HTTPClient http; http.begin(configUrl); http.setTimeout(10000); int httpCode = http.GET(); Serial.print("HTTP код: "); Serial.println(httpCode); if (httpCode == HTTP_CODE_OK) { String payload = http.getString(); // Парсим JSON DynamicJsonDocument doc(2048); DeserializationError error = deserializeJson(doc, payload); if (!error) { JsonArray pinsArray = doc["pins"]; bool changesMade = false; Serial.println("Применяем конфигурацию:"); for (JsonObject pinObj : pinsArray) { int pinNumber = pinObj["pin"]; const char* state = pinObj["state"]; const char* name = pinObj["name"] | "Unknown"; // Ищем пин в массиве bool pinFound = false; for (int i = 0; i < pinCount; i++) { if (pins[i] == pinNumber) { pinFound = true; // Определяем текущее состояние int currentState = digitalRead(pinNumber); bool shouldBeHigh = (strcmp(state, "HIGH") == 0); // Применяем, если нужно изменить if ((shouldBeHigh && currentState == LOW) || (!shouldBeHigh && currentState == HIGH)) { digitalWrite(pinNumber, shouldBeHigh ? HIGH : LOW); changesMade = true; Serial.printf(" Пин %d (%s): %s -> %s\n", pinNumber, name, currentState == HIGH ? "HIGH" : "LOW", shouldBeHigh ? "HIGH" : "LOW" ); } else { Serial.printf(" Пин %d (%s): %s (без изменений)\n", pinNumber, name, shouldBeHigh ? "HIGH" : "LOW" ); } break; } } if (!pinFound) { Serial.printf(" Пин %d (%s): не настроен в коде ESP32\n", pinNumber, name); } } if (changesMade) { Serial.println("✓ Изменения применены"); } else { Serial.println("ℹ Изменений не требуется"); } // Выводим timestamp if (doc.containsKey("timestamp")) { unsigned long timestamp = doc["timestamp"]; Serial.printf("Конфигурация от: %lu\n", timestamp); } } else { Serial.print("✗ Ошибка парсинга JSON: "); Serial.println(error.c_str()); } } else { Serial.print("✗ Ошибка HTTP: "); Serial.println(http.errorToString(httpCode)); } http.end(); // Вычисляем время до следующего обновления unsigned long nextUpdate = updateInterval - (millis() - lastUpdateTime); if (nextUpdate > updateInterval) nextUpdate = 0; Serial.printf("Следующее обновление через: %lu секунд\n", nextUpdate / 1000); Serial.println("------------------------\n"); }
4. Структура файлов на сервере
У вас должно быть 3 файла в папке ESP32:
/var/www/admin/data/www/smartmatter.ru/ESP32/ ├── api.php (обработчик запросов) ├── index.html (веб-интерфейс) └── esp32_config.json (конфигурационный файл, создается автоматически)
По итого имеем примитивное устройство, которым можно управлять с любой точки мира используя доступ к сайте, если ESP также имеет доступ к нему!