Продолжаем работу с нашим сервером HTTP и на данном уроке мы попробуем не просто что-то передать от клиента на сервер, но и дождаться от сервера ответа, что позволит нам судить о том, что сервер наши данные успешно принял и команды наши выполнил, а также данным образом мы можем получить от сервера ещё какую-то нужную нам информацию.
Поможет нам в данной задаче технология AJAX, которая помимо всего этого позволит нам отобразить полученную информацию в любом месте страницы без перезагрузки всей страницы, что сэкономит нам ещё и сетевой трафик. С данной технологией мы уже работали с использованием контроллера STM32, поэтому код нам писать будет гораздо проще.
Напомню только, что такое AJAX.
AJAX означает Asynchronous Javascript and XML. Из данной расшифровки следует, что AJAX использует именно язык JavaScript.
Схема наша осталась такая же, как и в прошлом уроке — отладочная плата с контроллером ESP32 с подключенным к ней светодиодом RGB, свечением кристаллов которого мы также будем управлять из клиента
Проект был сделан из проекта прошлого урока с именем WIFI_STA_HTTP_SERVER_PARAM и получил новое имя WIFI_STA_HTTP_SERVER_AJAX.
Откроем наш проект в Espressif IDE и попробуем его собрать. Если всё нормально, то пока займёмся страничкой, которую мы сделаем также из странички, которую мы писали в прошлом уроке. Откроем её в блокноте и, во-первых, добавим в открывающем теге body функцию для события завершения открытия документа
<body onload="onload()">
Выше добавим блок с java-скриптами, в котором и разместим данную функцию, пока пустотелую
1 2 3 4 5 |
<title>ESP32 File Server</title> <script> function onload(){ } </script> |
Объявим глобальную переменную для объекта класса запроса
1 2 |
<script> var xhr; |
А в теле функции onload создадим такой объект
1 2 |
function onload(){ xhr = new(XMLHttpRequest); |
В начале тела страницы добавим именованный блок
1 2 3 4 |
<body onload="onload()"> <font size="5"> <div id="info_table"></div> </font> |
Здесь будет размещаться таблица, которая будет приходить с сервера в ответе на наш запрос.
Чтобы таблица смотрелась лучше, создадим для неё стили
1 2 3 4 5 6 7 8 9 10 11 |
.button1:active { background: linear-gradient(#f59500, #f5ae00) #f59500; } table { border: 1px solid grey; } th { border: 1px solid grey; } td { border: 1px solid grey; text-align: center; } |
Теперь немного изменим код наших кнопок, так как будет уже не форма, а блок
вместо строки
<form action="" method="get">
будет обычный div
<div>
А вместо </form>
будет также соответствующий закрывающий тег
</div>
Код кнопок также немного изменится. вместо типа submit будет button, а также будут добавлены события на нажатие, которые будут вызывать соответствующие функции.
Кот тела тела блока теперь будет таким
1 2 3 4 5 6 7 8 |
<input class="button1" style="color: #00ffff; background-color: #800000;" type="button" onclick="red_off()" value="RED OFF" /> <input class="button1" style="color: #00ffff; background-color: #ff0000;" type="button" onclick="red_on()" value="RED ON"/> <br/><br/> <input class="button1" style="color: #ff00ff; background-color: #008000;" type="button" onclick="green_off()" value="GREEN OFF" /> <input class="button1" style="color: #ff00ff; background-color: #00ff00;" type="button" onclick="green_on()" value="GREEN ON" /> <br/><br/> <input class="button1" style="color: #ffff00; background-color: #000080;" type="button" onclick="blue_off()" value="BLUE OFF" /> <input class="button1" style="color: #ffff00; background-color: #0000ff;" type="button" onclick="blue_on()" value="BLUE ON" /> |
Вернёмся в наш скрипт и в функции onload отправим запрос на таблицу серверу, и, дождавшись ответа, отправим нашу таблицу в блок для отображения
1 2 3 4 5 6 7 |
xhr = new(XMLHttpRequest); xhr.open("POST", "color.html?start=0", true); xhr.responseType = "text"; xhr.onload = function (oEvent) { document.getElementById('info_table').innerHTML = xhr.response; } xhr.send(null); |
Обратим внимание, что метод запроса у нас будет POST. При использовании такого метода запроса параметры со значениями будут уже в строке браузера не видны. Также нужно в данном случае заполнять ещё кое-какие свойства в запросе, но, так как наш сервер не обрабатывает скрипты php, то это необязательно.
А чтобы информация о светящихся и несветящихся кристаллах светодиода отображалась ещё и в реальном времени, так как зайти на сервер мы можем одновременно с нескольких клиентов, мы задействуем таймер.
Объявим глобальную переменную для идентификатора таймера
1 2 |
var xhr; var idTimer1; |
В конце тела функции onload после отправки запроса создадим наш таймер, обработчик которого будет запускаться автоматически каждую секунду
1 2 |
xhr.send(null); idTimer1 = setInterval("Timer1()", 1000); |
Ниже добавим функцию для обработчика практически с тем же кодом запроса, что и в функции onload
1 2 3 4 5 6 7 8 9 10 |
idTimer1 = setInterval("Timer1()", 1000); } function Timer1(){ xhr.open("POST", "color.html?start=0", true); xhr.responseType = "text"; xhr.onload = function (oEvent) { document.getElementById('info_table').innerHTML = xhr.response; } xhr.send(null); } |
Подобные запросы отправим при нажатии каждой кнопки. Различие будет только в имени и значении параметра
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
xhr.send(null); } function red_on(){ xhr.open("POST", "color.html?red=1", true); xhr.responseType = "text"; xhr.onload = function (oEvent) { document.getElementById('info_table').innerHTML = xhr.response; } xhr.send(null); } function red_off(){ xhr.open("POST", "color.html?red=0", true); xhr.responseType = "text"; xhr.onload = function (oEvent) { document.getElementById('info_table').innerHTML = xhr.response; } xhr.send(null); } function green_on(){ xhr.open("POST", "color.html?green=1", true); xhr.responseType = "text"; xhr.onload = function (oEvent) { document.getElementById('info_table').innerHTML = xhr.response; } xhr.send(null); } function green_off(){ xhr.open("POST", "color.html?green=0", true); xhr.responseType = "text"; xhr.onload = function (oEvent) { document.getElementById('info_table').innerHTML = xhr.response; } xhr.send(null); } function blue_on(){ xhr.open("POST", "color.html?blue=1", true); xhr.responseType = "text"; xhr.onload = function (oEvent) { document.getElementById('info_table').innerHTML = xhr.response; } xhr.send(null); } function blue_off(){ xhr.open("POST", "color.html?blue=0", true); xhr.responseType = "text"; xhr.onload = function (oEvent) { document.getElementById('info_table').innerHTML = xhr.response; } xhr.send(null); } |
Со страничкой управились. Можно сразу залить изменения в раздел SPIFFS и заняться нашим проектом.
В файле http.c после функции download_get_handler добавим подобный обработчик, который будет обрабатывать запросы с методом POST
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
//------------------------------------------------------------- static esp_err_t download_post_handler(httpd_req_t *req) { char *resp_str = NULL; char* buf; size_t buf_len; buf_len = httpd_req_get_url_query_len(req) + 1; printf("buf_len: %d\n", buf_len); if (buf_len > 1) { buf = malloc(buf_len); if (httpd_req_get_url_query_str(req, buf, buf_len) == ESP_OK) { ESP_LOGI(TAG, "Found URL query => %s", buf); char param[32]; free(resp_str); } free(buf); } return ESP_OK; } //------------------------------------------------------------- |
Я добавил код в тело функции пока только такой, который никак не изменился, где мы узнали, есть ли параметры и считали параметр со значением в символьный массив.
Пока оставим наш обработчик и перейдём в функцию start_webserver, в которой объявим ещё одну переменную типа структуры для URI но уже с методом запроса POST. В соответствующем поле структуры мы и пропишем наш новый обработчик
1 2 3 4 5 6 7 8 9 |
.user_ctx = server_data // Pass server data as context }; httpd_uri_t file_download_post = { .uri = "/*", .method = HTTP_POST, .handler = download_post_handler, .user_ctx = NULL }; |
Не забываем зарегистрировать наш URI
1 2 |
httpd_register_uri_handler(server, &file_download); httpd_register_uri_handler(server, &file_download_post); |
Вернёмся в наш обработчик download_post_handler и в случае соответствия параметра и значения определённому цвету кристалла зажжём его или погасим
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
char param[32]; if (httpd_query_key_value(buf, "red", param, sizeof(param)) == ESP_OK) { ESP_LOGI(TAG, "Found URL query parameter => red:%s", param); if(!strcmp(param,"1")) gpio_set_level(CONFIG_RED_GPIO, 0); else if(!strcmp(param,"0")) gpio_set_level(CONFIG_RED_GPIO, 1); } else if (httpd_query_key_value(buf, "green", param, sizeof(param)) == ESP_OK) { ESP_LOGI(TAG, "Found URL query parameter => green:%s", param); if(!strcmp(param,"1")) gpio_set_level(CONFIG_GREEN_GPIO, 0); else if(!strcmp(param,"0")) gpio_set_level(CONFIG_GREEN_GPIO, 1); } else if (httpd_query_key_value(buf, "blue", param, sizeof(param)) == ESP_OK) { ESP_LOGI(TAG, "Found URL query parameter => blue:%s", param); if(!strcmp(param,"1")) gpio_set_level(CONFIG_BLUE_GPIO, 0); else if(!strcmp(param,"0")) gpio_set_level(CONFIG_BLUE_GPIO, 1); } |
А если придёт параметр с нулём, то светодиоды зажигаться и гаснуть не будут, а будет передаваться только таблица.
Подготовим память для строки с таблицей
1 2 3 4 5 |
else if(!strcmp(param,"0")) gpio_set_level(CONFIG_BLUE_GPIO, 1); } resp_str = (char*) req->user_ctx; resp_str = malloc(200); |
Дальнейший код заслуживает особого внимания.
Как я и говорил на прошлом уроке, мне не удалось найти функцию в комплекте IDF, которая отследит уровень ножки порта. Вернее, есть такая функция, но она работает только с ножками порта, настроенными на вход. А хотелось бы передать клиенту честную информацию о зажжённых или незажжённых светодиодах. Поэтому будем использовать регистр напрямую. Объявим переменную и сразу же считаем туда весь нужный нам регистр
1 2 3 |
resp_str = malloc(200); unsigned int reg_out = *(unsigned int*) GPIO_OUT_REG; |
Подготовим таблицу, в которой и занесём уровни в виде строк ON или OFF, учитывая, что из-за общего анода у нас всё наоборот
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
unsigned int reg_out = *(unsigned int*) GPIO_OUT_REG; strcpy(resp_str,"<table>\ <tr><th>RED</th><th>GREEN</th><th>BLUE</th></tr>\ <tr><td>"); if((reg_out >> CONFIG_RED_GPIO) & 0x1) strcat(resp_str,"OFF"); else strcat(resp_str,"ON"); strcat(resp_str,"</td><td>"); if((reg_out >> CONFIG_GREEN_GPIO) & 0x1) strcat(resp_str,"OFF"); else strcat(resp_str,"ON"); strcat(resp_str,"</td><td>"); if((reg_out >> CONFIG_BLUE_GPIO) & 0x1) strcat(resp_str,"OFF"); else strcat(resp_str,"ON"); strcat(resp_str,"</td></tr></table>"); |
Ну и осталось нам лишь только передать документ клиенту
1 2 3 |
strcat(resp_str,"</td></tr></table>"); httpd_resp_send(req, resp_str, strlen(resp_str)); |
Вот вроде бы и всё.
Соберём код, прошьём контроллер, узнаем сетевой адрес и запросим нашу страничку в браузере
Всё отлично отображается.
Если мы посмотрим обмен трафика в WireShark, то увидим, что постоянно раз в секунду идут запросы с ответами
Нажмём красную кнопку. У нас загорится соответствующий светодиод
Также в табличке мы увидим отчёт нашего сервера о том, что светодиод горит
Если нажмём кнопку RED OFF, то светодиод погаснет
В табличке также светодиод отключился
Если мы проделаем то же самое со следующими кнопками и кристаллами, у нас также будет работать. Например, давайте зажжём все кристаллы, поочерёдно нажав все три правые кнопки
Светятся все три кристалла, а также на страничке тоже все включились
Можете также подключиться с других компьютеров, смартфонов планшетов, всё будет работать, причём одновременно, я пробовал подключиться одновременно до трёх устройств. Также, благодаря таймеру, информация, изменённая с другого клиента, изменяется во всех практически в реальном времени.
Итак, на данном занятии нам удалось передавать данные клиенту по запросу посредством протокола HTTP с использованием некоторых интересных возможностей языка JavaScript без перезагрузки страницы. И не только это, а ещё и передавать данные от клиента серверу также без перезагрузки страницы.
Всем спасибо за внимание!
Предыдущий урок Программирование МК ESP32 Следующий урок
Недорогие отладочные платы ESP32 можно купить здесь:
На AliExpress Недорогие отладочные платы ESP32
На Яндекс.Маркет Недорогие отладочные платы ESP32
Логический анализатор 16 каналов можно приобрести (AliExpress) здесь
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в Дзен (нажмите на картинку)
Спасибо большое за нужные и полезные уроки.
Не нашел файл который нужно залить в SPIFFS.
Не подскажете где его найти?
Спасибо