Comments 15
Мне кажется у DeepSeek лучше получилось. =)
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
/* Network Configuration */
const char* ssid = "ESP32";
const char* password = "12345678";
IPAddress local_ip(192,168,1,1);
IPAddress gateway(192,168,1,1);
IPAddress subnet(255,255,255,0);
/* LED Pins */
const uint8_t LED1pin = 4;
const uint8_t LED2pin = 5;
bool LED1status = false;
bool LED2status = false;
AsyncWebServer server(80);
String generateHTML(bool led1, bool led2) {
String html = R"rawliteral(
<!DOCTYPE html><html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LED Control</title>
<style>
html {font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}
body {margin-top: 50px;}
h1 {color: #444444; margin: 50px auto 30px;}
h3 {color: #444444; margin-bottom: 50px;}
.button {
display: block; width: 80px; background-color: #3498db; border: none;
color: white; padding: 13px 30px; text-decoration: none; font-size: 25px;
margin: 0px auto 35px; cursor: pointer; border-radius: 4px;
}
.button-on {background-color: #3498db;}
.button-on:active {background-color: #2980b9;}
.button-off {background-color: #34495e;}
.button-off:active {background-color: #2c3e50;}
p {font-size: 14px; color: #888; margin-bottom: 10px;}
</style>
</head>
<body>
<h1>ESP32 Web Server</h1>
<h3>Using Access Point (AP) Mode</h3>
)rawliteral";
html += (led1) ?
"<p>LED1 Status: ON</p><a class=\"button button-off\" href=\"/led1off\">OFF</a>\n" :
"<p>LED1 Status: OFF</p><a class=\"button button-on\" href=\"/led1on\">ON</a>\n";
html += (led2) ?
"<p>LED2 Status: ON</p><a class=\"button button-off\" href=\"/led2off\">OFF</a>\n" :
"<p>LED2 Status: OFF</p><a class=\"button button-on\" href=\"/led2on\">ON</a>\n";
html += "</body></html>";
return html;
}
void setup() {
Serial.begin(115200);
pinMode(LED1pin, OUTPUT);
pinMode(LED2pin, OUTPUT);
digitalWrite(LED1pin, LOW);
digitalWrite(LED2pin, LOW);
// Configure WiFi AP
WiFi.softAP(ssid, password);
WiFi.softAPConfig(local_ip, gateway, subnet);
Serial.print("AP IP address: ");
Serial.println(WiFi.softAPIP());
// Route handlers
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
LED1status = false;
LED2status = false;
Serial.println("GPIO4 Status: OFF | GPIO5 Status: OFF");
request->send(200, "text/html", generateHTML(LED1status, LED2status));
});
server.on("/led1on", HTTP_GET, [](AsyncWebServerRequest *request){
LED1status = true;
Serial.println("GPIO4 Status: ON");
request->send(200, "text/html", generateHTML(true, LED2status));
});
server.on("/led1off", HTTP_GET, [](AsyncWebServerRequest *request){
LED1status = false;
Serial.println("GPIO4 Status: OFF");
request->send(200, "text/html", generateHTML(false, LED2status));
});
server.on("/led2on", HTTP_GET, [](AsyncWebServerRequest *request){
LED2status = true;
Serial.println("GPIO5 Status: ON");
request->send(200, "text/html", generateHTML(LED1status, true));
});
server.on("/led2off", HTTP_GET, [](AsyncWebServerRequest *request){
LED2status = false;
Serial.println("GPIO5 Status: OFF");
request->send(200, "text/html", generateHTML(LED1status, false));
});
server.onNotFound([](AsyncWebServerRequest *request){
request->send(404, "text/plain", "Not found");
});
server.begin();
Serial.println("HTTP async server started");
}
void loop() {
// Update LED states
digitalWrite(LED1pin, LED1status ? HIGH : LOW);
digitalWrite(LED2pin, LED2status ? HIGH : LOW);
// No need for server.handleClient() with async server
}
Лямбда (не именованные, анонимные, безымянные) функции хорошо выглядят когда они короткие.
А в коде светодиоды будут гаснуть при каждом новом обращении к корневой страничке. ИИ за тебя накодит кучку кода, но логику сам проверяй.
Вы к кому сейчас обращались? Я лишь тактично попытался намекнуть автору статьи, что асинхронный код лучше синхронного. И на брудершафт мы вроде не пили пока чтобы тыкать.
Прошу прощения.
В норме русского языка, говорить "ты", когда имеется в виду абстрактный пример, говорящий про действие не обращенное конкретно к кому то, а само по себе. То есть, как в данном случае: стоит самому проверить, прежде чем доверять коду который сгенерировал ИИ. И это не конкретно к вам обращение, а к любому кто так делает. О чем, кстати, люто подтверждаю ибо почти каждый день встречаюсь с вопросами ко мне как к эксперту в определённой области, по такому сгенерированному коду. Не надо сразу все сразу на свой счет воспринимать.
Ещё бы эту простыню HTML кода в константы вынести.
В статье много чего не расмотрено, так сказать очень поверхностно...
В свое время баловался для безумного дома, прием сигналов по UART и отдача в собственный сервер включая AJAX, тестировал на паралельных сесиях, тянет не более 10 одновременных сесий....
/*
Generic ESP8266 Module
Питание: 3,3 В до 250 мА
Объём Flash-памяти: 2 МБ
Беспроводной интерфейс: Wi-Fi 802.11 b/g/n 2,4 ГГц
Тактовая частота: 80 МГц
список команд http://wiki.amperka.ru/_media/%D0%BF%D1%80%D0%BE%D0%B4%D1%83%D0%BA%D1%82%D1%8B:esp8266-wifi-module:esp8266_at_commands.pdf
воспринимает с UART порта передачу данных в формате
<set_term=33.1>
передает в UART порт статус в формате
<status=OK>, или <status=E1>
*/
#include <FS.h>
#include <ESP8266WiFi.h>
#include "params.h"
// на рабочей компиляции закоментировать !
// #define ON_DEBUG_ECHO
#define BUFER_WIFI_COUNT 250
// -----------------------------------------------------------
// буферы, нужны для реализации асинхронности чтения данных в главном цикле
char buf_wifi[BUFER_WIFI_COUNT + 1];
uint8_t wifi_tec_tec_size = 0; // текущая заполненая длинна данных, от 0 до BUFER_WIFI_COUNT
// описание зачитанных параметров WiFi запроса, глобальные для поддержки асинхронности
String Request_Host = ""; // имя сервера
String Request_Type = ""; // GET, POST
String Request = ""; // строка запроса, начинается с /
unsigned long time_loop_Request = 0; // время последней операции с клиентом
long speedOfLight = 9600; // скорость порта UART
char sid[21];
char pass[21];
// -----------------------------------------------------------
// Create an instance of the server
// specify the port to listen on as an argument
WiFiServer server(80);
WiFiClient client; // подразумеваем, что у нас будет только одна сесия
unsigned long time_loop_new = 0; // время последнего окончания чтения первой строки запроса
void clear_buf(char *buf, uint8_t start_num);
void setup()
{
Serial.begin(57600);
while (!SPIFFS.begin())
{
delay(100);
Serial.println(F("ERROR INIT SPIFFS."));
delay(10000);
}
while (!Read_Param_SD())
{
delay(100);
Serial.println(F("ERROR READ setup.ini"));
delay(10000);
}
// параметры speedOfLight, sid, pass считаны из файла setup.ini
// Connect to WiFi network
Serial.println();
Serial.print(F("Connecting to "));
Serial.println(sid);
WiFi.mode(WIFI_STA);
WiFi.begin(&(sid[0]), &(pass[0]));
/* case STATION_GOT_IP:
return WL_CONNECTED;
case STATION_NO_AP_FOUND:
return WL_NO_SSID_AVAIL;
case STATION_CONNECT_FAIL:
case STATION_WRONG_PASSWORD:
return WL_CONNECT_FAILED;
case STATION_IDLE:
return WL_IDLE_STATUS;
default:
return WL_DISCONNECTED;
*/
while (WiFi.status() != WL_CONNECTED)
{
delay(5000);
Serial.print(F("."));
Serial.print(sid);
// Serial.print(F("."));
// Serial.print(pass);
Serial.println(F("."));
}
Serial.println();
Serial.println(F("WiFi connected"));
// Start the server
server.begin();
Serial.println(F("Server started"));
// Print the IP address
Serial.println(WiFi.localIP());
clear_buf(&(buf_wifi[0]), 0);
uart_value_tec[0] = 0;
uart_status_tec[0] = 0;
wifi_value_tec[0] = 0;
wifi_status_tec[0] = 0;
// инициализация массивов
for (uint8_t i = 0; i < PARAM_COUNT; i++)
{
for (uint8_t ii = 0; ii < 20; ii++)
{
if (out_id[i][ii] == 0)
{
out_id_length[i] = ii;
break;
}
}
out_value[i][0] = 0;
out_status[i][0] = 0;
}
}
void loop() {
LoopReadData();
if (client) {
if (time_pause(time_loop_new, millis()) > 10000) {
// держим не более 10 сек
client.stop();
Request_Host = "";
Request_Type = "";
Request = "";
}
}
if (!client) {
// попытка создание клиентской сессии
client = server.available();
if (client) {
time_loop_new = millis();
}
}
if (client) {
LoopReadWiFi();
}
}
// -----------------------------------------------------------
// процедуры формирования страниц вывода
void out_page_file(WiFiClient *client, char *FileName, char *Content)
{
File f = SPIFFS.open(FileName, "r");
if (!f)
{
out_404(client);
}
else
{
client->print(F(
"HTTP/1.1 200 OK\r\n"
"Cache-Control: max-age=31536000\r\n"
"Content-Type: "));
client->print(Content);
out_br(client);
client->print(F("Content-Length: "));
client->print(f.size(), DEC);
out_br(client);
out_br(client);
while (f.available())
{
client->write(f.read());
}
out_br(client);
f.close();
}
}
void out_ajax_data(WiFiClient *client)
{
//
// формат: {"t_home_1":{"value":{"data-v":"030"},"status":{"data-st":"OK"}}}
// в этом примере меняются только: t_home_1,030,OK
// можно менять/добавлять и value, status, data-v, data-st но тогда надо менять HTML и CSS файлы
//
String str = "{";
for (uint8_t i = 0; i < PARAM_COUNT; i++)
{
str = str + (String)("\"") + (String)(out_id[i]) + (String)("\":{\"value\":{\"data-v\":\"") + (String)(out_value[i]) + (String)("\"},\"status\":{\"data-st\":\"") + (String)(out_status[i]) + (String)("\"}},");
}
str[str.length() - 1] = '}';
client->print(F(
"HTTP/1.1 200 OK\r\n"
"Content-Type: application/json\r\n"));
client->print(F("Content-Length: "));
client->print(str.length(), DEC);
out_br(client);
out_br(client);
client->print(str);
out_br(client);
}
void out_favicon(WiFiClient *client)
{
client->print(F(
"HTTP/1.1 200 OK\r\n"
"Cache-Control: max-age=31536000\r\n"
"Transfer-Encoding: chunked\r\n"
"Content-Type: image/x-icon\r\n"));
out_br(client);
out_br(client);
}
void out_404(WiFiClient *client)
{
client->print(F(
"HTTP/1.1 200 OK\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n"
"<!DOCTYPE html>\r\n"
"<html>\r\n"
"<head>\r\n"
"<title>404</title>\r\n"
"</head>\r\n"
"<body>\r\n"
"<h1>ERROR 404</h1>\r\n"
"<div>\r\n"
"</div>\r\n"
"</body>\r\n"
"</html>\r\n"));
out_br(client);
}
// -----------------------------------------------------------
// процедуры логики главного цикла
void LoopReadWiFi() {
uint8_t c;
unsigned long time_loop = millis();
while (client.available()) {
time_loop_new = millis(); // обновим данные о времени сесии
if (time_pause(time_loop, millis()) > 100) {
// если читаем данные более 0.1 сек - надо прерватся и поделится ресурсами с другими задачами
break;
}
c = client.read();
if (c > 31) {
if (c > 127) {
// символы с кодом > 127 пропускаем
continue;
}
if (wifi_tec_tec_size >= BUFER_WIFI_COUNT) {
// если нам пришла строка больше размера нашего буфера мы ее усекаем
// будем читать буфер и пока не найдем символ с кодом < 32
// или пока не пройдет таймаут
continue;
}
// запишем в буфер значение и увеличим переменную считанных данных
*((uint8_t *)(buf_wifi + wifi_tec_tec_size)) = c;
wifi_tec_tec_size++;
// и зафиксируем время для расчета таймаута
time_loop_Request = millis();
} else {
// нам пришел символ меньше 32, надо разбирать то, что лежит в buf_wifi
if (wifi_tec_tec_size > 0) {
parse_buf_wifi();
}
}
}
// если не было данных более 0.1 сек, значит запрос закончился
if (time_pause(time_loop_Request, millis()) > 100) {
// если в буфере есть какие-то данные, то их будем анализировать
if (wifi_tec_tec_size > 0) {
parse_buf_wifi();
}
// код зачистки по таймауту для следующих итераций,
// если не все 3 параметра заполнены, значит весь пакет не валидный
if ((Request.length() == 0) ||
(Request_Type.length() == 0) ||
(Request_Host.length() == 0)) {
Request = "";
Request_Type = "";
Request_Host = "";
}
}
// если мы получили 3 параметра - значит что-то надо делать
// если не все параметры заполнены возвращаемся в главный цикл на последующие заходы
if ((Request.length() > 0) &&
(Request_Type.length() > 0) &&
(Request_Host.length() > 0)) {
// формируем ответ
if (Request == "/") {
out_page_file(&client, "/index.html", "text/html");
} else if (Request == "/setup.ini") {
out_404(&client); // запрет на скачку этого файла, в нем настройки сети
} else if (Request == "/ajax_data") {
out_ajax_data(&client);
} else if (Request == "/favicon.ico") {
out_favicon(&client); // уменьшаем запросы на выдачу этого файла
// если положить иконку можно будет и показывать
} else {
int16_t inum = Request.indexOf('.');
if (inum >= 0) {
String file_ext = Request.substring(inum + 1);
// все остальные файлы по расширениям
if (file_ext == "css")
{
out_page_file(&client, (char *)(Request.c_str()), "text/css");
}
else if (file_ext == "png")
{
out_page_file(&client, (char *)(Request.c_str()), "image/png");
}
else if (file_ext == "jpg")
{
out_page_file(&client, (char *)(Request.c_str()), "image/jpg");
}
else if (file_ext == "html")
{
out_page_file(&client, (char *)(Request.c_str()), "text/html");
}
} else {
out_404(&client);
}
}
// очистка
Request_Host = ""; // имя сервера
Request_Type = ""; // GET, POST
Request = ""; // строка запроса, начинается с /
}
}
void LoopReadData()
{
//
// ожидаемый формат: <id=value;status>
//
uint8_t c;
unsigned long time_loop = millis();
while (Serial.available())
{
if (time_pause(time_loop, millis()) > 100) {
// если читаем данные более 0.1 сек - надо прерватся и поделится ресурсами с другими задачами
break;
}
c = Serial.read();
if (c < 32) { // эти символы пропускаем и считаем сначала
uart_read_0_31();
}
else if (uart_step == 0) { // ждем начала блока "<"
uart_read_param_step_0(c);
}
else if (uart_step == 1) { // читаем и ищем идентификатор, до символа "="
uart_read_param_step_1(c);
}
else if (uart_step == 2) { // читаем VALUE, до символа ";"
uart_read_param_step_2(c);
}
else if (uart_step == 3) { // читаем STATUS, до символа ">"
uart_read_param_step_3(c);
}
if (uart_step == 4) { // копируем текущие параметры на постоянное место
uart_read_param_step_4();
}
}
}
boolean Read_Param_SD()
{
// читаем файл инициализации setup.ini, и устанавливаем параметры
char c;
String inString = "";
String s_name = "";
String s_value = "";
uint8_t ii;
clear_buf(&(buf_wifi[0]), 0); // этот буфер будем использовать для чтения параметров (только в setup)
File f = SPIFFS.open("/setup.ini", "r");
if (!f) {
Serial.println(F("ERROR READ setup.ini"));
return false;
} else {
while (f.available()) {
c = (char)f.read();
if ((uint8_t)c < 32)
{ // эти символы не могут быть в данных, надо анализировать чего в буфере
// и потом заполнять заново
ii = find_byte_buf(&(buf_wifi[0]), '=');
if (ii != 0) {
inString = buf_wifi;
s_name = inString.substring(0, ii);
s_value = inString.substring(ii + 1);
if (s_name == "sid") {
if (s_value.length() <= 20) {
s_value.toCharArray(&(sid[0]), s_value.length()+1);
sid[s_value.length()] = 0;
} else {
s_value.toCharArray(&(sid[0]), 20);
sid[20] = 0;
}
}
if (s_name == "pass") {
if (s_value.length() <= 20) {
s_value.toCharArray(&(pass[0]), s_value.length()+1);
pass[s_value.length()] = 0;
} else {
s_value.toCharArray(&(pass[0]), 20);
pass[20] = 0;
}
}
if (s_name == "serial") {
speedOfLight = (long)(s_value.toInt());
}
}
clear_buf(&(buf_wifi[0]), 0);
} else {
ii = find_byte_buf(&(buf_wifi[0]), 0);
*((uint8_t *)(buf_wifi + ii)) = c;
}
}
f.close(); //Close file
}
return true;
}
// -----------------------------------------------------------
// вспомогательные процедуры
void parse_buf_wifi() {
String inString = "";
String file_ext = "";
// для начала поставим нулевой символ в конец данных буфера
*((uint8_t *)(buf_wifi + wifi_tec_tec_size)) = 0;
inString = buf_wifi;
if (inString.startsWith("Host: ")) {
Request_Host = inString.substring(6);
}
else if (inString.startsWith("GET "))
{
Request = inString.substring(4);
Request_Host = "";
Request_Type = "GET";
if (Request.indexOf(' ') != -1)
{
Request.remove(Request.indexOf(' '));
}
}
else if (inString.startsWith("POST "))
{
Request = inString.substring(5);
Request_Host = "";
Request_Type = "POST";
if (Request.indexOf(' ') != -1)
{
Request.remove(Request.indexOf(' '));
}
}
wifi_tec_tec_size = 0;
}
void clear_buf(char *buf, uint8_t start_num = 0)
{
for (uint8_t i = start_num; i < 250; i++)
{
*((uint8_t *)(buf + i)) = 0;
}
*((uint8_t *)(buf + 250)) = 0;
}
void out_br(WiFiClient *client)
{
client->print(F("\r\n"));
}
unsigned long time_pause(unsigned long tlo, unsigned long tln) {
if (tlo <= tln) {
return (tln - tlo);
} else {
return (4294967295 - tln + tlo);
}
}
uint8_t find_byte_buf(char *buf, uint8_t b)
{
for (uint8_t i = 0; i < 250; i++)
{
uint8_t a = *((uint8_t *)(buf + i));
if (a == b) {
return i;
}
if (a == 0) {
return 0;
}
}
return 0;
}
Количество сессий вероятно ограничивается настройками TCPIP стека. В нативном ESP-IDF фреймворке настраиваемо через переменную CONFIG_LWIP_MAX_SOCKETS, по умолчанию равную 8.
Loop, по сути один единственный тред, в котором всё и обрабатывается. Обычно работают иначе, скажем, один тред на контроль соединений (обработка, закрытие и т.п.) и другой на обработку запросов активных соединений.
Для этого нужно перейти с уровня Arduino Core на уровень ESP-IDF SDK.
а почему крошечный, вот вполне себе полноценный и на esp8266

Какие-то совсем неудачные решения учитывая недостаток ресурсов у ESP32
Будет правильнее (с моей точки зрения):
- хранить статическую html zip-ованую страницу на fs контроллера
- в самом html описывать всю логику работы на javascript
- принимать и отправлять данные на МК через web-socket в формате json например.
Такой подход позволит существенно сократить использование памяти у МК, снимет с него нагрузку по генерации html, сократит объем передаваемых данных и ускорит скорость их обработки.
Может я не заметил, но как идет подключение к Wi-Fi?
Я как-то делал решение, когда информация о подключенном Wi-Fi хранится в ПЗУ, а если информации нет или пользователь нажал кнопку BOOT более чем 8 секунд, то информация о Wi-Fi сбрасывается и ESP переходит в режим Access Point.
А, еще фишка: Учитывая, что у ESP нет дисплея, то кратковременное нажатие на кнопку BOOT выдавало последний актет IP адреса морзянкой. Не супер удобно, зато деревянно надежно. Особенно для тех, кто в течение 4 лет барабанил "ба-а-ааки текут".
Возможно вы говорите о работе с модулем посредством AT команд и стандартной прошивке. Тут приведены примеры "своих" прошивок, где явно описано какой способ использовать: wifi точка или клиент, а по нажатию на boot, не должно производить какой то эффект на работу устройства с этой прошивкой (если не указано иного)
Тут подключение к существующей точке
//connect to your local wi-fi network
WiFi.begin(ssid, password);
А тут создание wifi точки
WiFi.softAP(ssid, password);
WiFi.softAPConfig(local_ip, gateway, subnet);
Оба используют библиотеки WiFi, в ардуино ide, можно их установить.
Сайт амперки в помощь
(Мне помог, другим советую =) )
Можно ли как-то обращаться к ESP32 не находясь в общей сети вай фай и не имея белого ip адреса, например находясь в другом городе, по аналогии с другими умными устройствами?
без "костыля" никак. даже если на Вашем роутере белый IP, что по моему редкость, то надо будет всё равно дополнительно его настраивать на "проброс портов" и esp32 надо внутри сети постоянный адрес давать. ещё есть вариант использовать сайт iocontrol.ru. на нём вроде можно завести учётку указав почту и используя простенький API гонять пакеты между устройствами находящимися за NATами.
Крошечный веб-сервер на ESP32