Как сделать Web-интерфейс для ESP8266 под NodeMCU

Содержание

 

WiFi модули на базе микроконтроллера ESP8266 имеют достаточно интересный функционал, включая возможность использовать WiFi. Это позволяет использовать их в различных домашних устройствах. Создание Web-интерфейса для таких устройств — наиболее привлекательная, но не всегда простая тема. В этой статье рассматриваются примеры создания интерфейса для ESP8266 под framework NodeMCU на языке LUA. В примерах от простого к сложному ознакомимся с преимуществами ESP8266 и научимся бороться с его недостатками. Главный недостаток ESP8266, это конечный объем оперативной памяти. Этого можно не заметить при создании простых приложений, но при решении более сложных задач Вы неизбежно столкнетесь с недостатком памяти. Надеюсь, эта статья поможет обойти подобные проблемы.

Во всех примерах использовался модуль ESP12E и фреймворк NodeMCU собранный с модулями: adc, bme280, cron, crypto, dht, file, gpio, http, i2c, mqtt, net, node, pwm, rtctime, sjson, sntp, spi, tmr, u8g, uart, websocket, wifi, tls.

Пример №1 (динамическая страница)

Для того, чтобы реализовать web-интерфейс, нам надо сделать свой крохотный сервер. B мы начнем с самого простого примера. NodeMCU позволяет создавать TCP сервер оной командой. Далее мы подключаем слушателя на нужный нам порт. В нашем случае порт 80. Это стандартный порт для HTTP протокола. Указываем какие функции вызывать при возникновении событий. Доступны такие события: «connection» — подключение, «reconnection» — повторное подключение, «disconnection» — отключение, «receive» — получение данных, «sent«- завершение отправки данных. Нас не особо интересует момент подключения. Нам интересен запрос который отправляет браузер уже после подключения. Поэтому, на подключение и отключение ничего не вешаем, обрабатываем только получение данных.

Т.е., в простейшем объяснении, диалог между веб сервером и браузером происходит так: Браузер подключается к серверу (наш модуль) на порт 80, отправляет запрос (структуру запроса рассмотрим позже). Сервер обрабатывает запрос и отправляет ответ браузеру, после чего разрывает соединение. Обратите внимание, соединение разрывает сервер после отправки всех данных.

Примечание: для решения некоторых задач соединение между сервером и браузером может не разрываться. Но этот вариант работы в статье не рассматривается.

Первый, простейший пример web-сервера, ожидает запрос от браузера, причем сам запрос не анализируется. После поучения любого запроса генерирует и отправляет HTML страницу. В страницу вставлен счетчик таймера. Вместо таймера можно вставить переменную, скажем значение датчика температуры, и таким образом получаем простейший интерфейс с полезным содержимым.

Внимание! Перед запуском примеров нужно отредактировать и затем запустить скрипт wifi.lua. В этом файле следует описать настройки подключения к Вашей локальной Wi-Fi сети. Модуль ESP после подключения к Вашей локальной Wi-Fi сети должен получить IP адрес. В примерах используется 192.168.0.108, у Вас будет другой. Узнать IP адрес можно командой:

Пример 1: Скачать файлы примера. необходимо залить файлы: web1.lua запустить сервер, выполнив скрипт: web1.lua В браузере открыть ссылку: http://192.168.0.108/

Примечание: Если в браузере набрать ссылку вида http://192.168.0.1048/ или http://192.168.0.108/index.html Вы все равно получите один и тот же ответ, поскольку запрос пока не анализируется.

В примере вставлена команда вывода в консоль текста запроса полученного от браузера. Его анализом мы займемся чуть позже. Сам запрос выглядит примерно следующим образом:

Пример №2 (статическая страница)

Иногда требуется получить статическую страницу (картинку, файл стилей, Java-скрипт, и т.п.), а не генерировать динамически скриптом. NodeMCU имеет простую файловую систему, что позволяет записать файл, скажем index.html, и отдавать его браузеру при запросе. Это мы и сделаем во втором примере.

Пример 2: Скачать файлы примера. необходимо залить файлы: web2.lua запустить сервер, выполнив скрипт: web2.lua В браузере открыть ссылку: http://192.168.0.108/

Пример №3 (слишком большой файл)

Теперь попробуем сделать файл побольше размером и получим проблему о котором я говорил в самом начале — память в микроконтроллере имеет конечный объем и этот объем не очень большой. Система не может прочитать слишком большой файл. Мы увидим лишь часть файла. Аналогичная ситуация будет при динамической генерации страницы большого объема.

Пример 3: Скачать файлы примера. необходимо залить файлы: web3.lua, large.html запустить сервер, выполнив скрипт: web3.lua В браузере открыть ссылку: http://192.168.0.108/

Мы видим часть файла, на самом деле в нем более 40 строк.

Пример №3 (слишком большой объем данных)

Если отправлять большой объем данных прямо из скрипта, то NodeMCU может перезагружаться после сообщения «PANIC: unprotected error in call to Lua API (web4.lua:64: out of memory)»

Пример 4: Скачать файлы примера. необходимо залить файлы: web4.lua запустить сервер, выполнив скрипт: web4.lua В браузере открыть ссылку: http://192.168.0.108/

Пример №5 (отправка данных частями)

Обойти проблему нехватки памяти можно отправляя данные (страницы или файла) частями. Сделаем массив и будем отправлять построчно.

Пример 5: Скачать файлы примера. необходимо залить файлы: web5.lua запустить сервер, выполнив скрипт: web5.lua В браузере открыть ссылку: http://192.168.0.108/

Все данные (все 40 строк) успешно отправлены.

Пример №6 (отправка файла частями)

Отправка файла частями выполняется следующим образом. Сервер открывает файл, считывает небольшую часть файла и ставит ее на отправку. После того, как первая часть будет отправлена, сервер считывает и ставит на отправку следующую порцию, и так пока файл ее будет полностью отправлен. После отправки последней порции сервер закрывает файл и закрывает соединение с клиентом (браузером). Поскольку процедура отправки файла занимает относительно большое время, в течении которого микроконтроллеру нужно заниматься другим текущими делами, мы не можем просто ожидать окончания отправки кусочка файла, поэтому используем события.

Пример 6: Скачать файлы примера. необходимо залить файлы: web6.lua, large.html запустить сервер, выполнив скрипт: web6.lua В браузере открыть ссылку: http://192.168.0.108/

Весь файл large.html успешно получен браузером.

Пример №7 (многопоточность, CGI, метод GET)

Теперь, когда нам кажется, что мы все победили, встречаем следующую проблему. Иногда большие файлы все же прилетают не полностью. Но чаще все работает как надо. Почему? Потому, что браузер иногда отправляет еще один запрос, он хочет получить файл favicon.ico. И этот запрос приходит до того, как наш сервер завершил отправку большого файла. И как он реагирует на новый запрос? Он начинает обработку нового запроса, не закончив предыдущий. Аналогичная ситуация может случится, если в страницу вставить несколько картинок или ссылку на файл стилей. Браузер попытается загружать несколько файлов одновременно, а наш сервер с этим не справиться. Выход — нужно сделать так, чтобы сервер был много-поточным.

Примечание: Рекомендую посмотреть что прилетает в запросе. Из всего увиденного нас будет интересовать URL и данные, которые приходят GET и POST методом. Пока научимся извлекать URL и параметры GET запроса.

Пример 7: Скачать файлы примера. необходимо залить файлы: web7.lua, cgi.lua, index.html, large.html, test.html, image.gif, image.png запустить сервер, выполнив скрипт: web7.lua В браузере открыть ссылки: http://192.168.0.108/ http://192.168.0.108/index.html http://192.168.0.108/large.html http://192.168.0.108/nothing.html http://192.168.0.108/cgi.lua?name=andre

Этот пример может загружать разные файлы в зависимости от URL, принимать и разбирать параметры GET запроса. Запускать lua скрипты в качестве cgi скриптов. Вот теперь сервер сможет параллельно обрабатывать запросы. Однако и это не решает всех проблем. Если запросов будет слишком много у контроллера все равно рано или поздно закончиться память. Как можно отодвинуть этот рубеж?

  • Уменьшит буфер чтения из файла. Так можно увеличить количество потоков, которые сервер сможет обработать. Но тем самым мы увеличиваем количество операций чтения с flash памяти и немного увеличиваем время отправки данных.
  • Стараться строить интерфейс таким образом, чтобы не выполнялось много одновременных загрузок. Например не вставлять много картинок в HTML. По возможности картинки, скрипты, стили Java скрипты располагать на внешних сайтах. Так же можно доработать скрипт нашего сервера и ввести ограничение на количество одновременно обрабатываемых запросов. И если количество обрабатываемых запросов выше допустимого, просто игнорировать новые запросы. Это опять же не решит всех проблем, что-то не загрузится, но хотя бы предотвратит перезагрузку NodeMCU.

В этом примере реализован разбор параметров приходящих методом GET, т.е. в адресной строке. Кроме того, сделана отправка файла по умолчанию (index.html если в url нет четкого указания файла). Так же есть список разрешенных расширений файлов, которые нашему серверу разрешено отдавать. Откройте ссылку http://192.168.0.108/test.html GIF-файл не отобразиться, а PNG мы увидим. За это отвечает этот фрагмент кода:

Кроме того, добавлена возможность запуска lua файлов. На примере рассмотрим как это работает.

CGI. Работа с параметрами GET

Разберем пример, когда в адресной строке передаются параметры: http://192.168.0.103/cgi.lua?name=Andre

В данном случае сервер увидит, что запрос содержит обращение к файлу с расширением lua, запустит его, дождется от него ответа и отправит ответ браузеру. В этом случае исполняемый файл lua должен возвращать ответ содержащий HTTP заголовок. Ниже приведен пример такого скрипта:

Скрипт возвращает значение параметра name, переданного в адресной строке. Как видите, доступ к переменным сделан через массив GET[]. Наш «web-сервер» подготавливает этот массив перед тем как запустить скрипт. Таким образом, можно принимать параметры переданные в URL, обрабатывать их в Вашем скрипте и формировать ответ в соответствии с запрошенными параметрами.

К сожалению, таким образом не получится формировать ответы значительного объема из за описанной ранее проблемы с конечным объемом памяти. Кроме того, такой подход кроет в себе проблему с безопасностью. Через URL теперь можно запустить любой lua скрипт, который есть на файловой системе NodeMCU. В статье я буду и далее использовать файлы с расширением lua, но Вам рекомендую для файлов, которые будут использованы для запуска из под нашего сервера, использовать другое расширение. Они все равно будут исполнятся, но таким образом Вы сможете разграничить lua-файлы для внутренней работы и файлы с другим расширением для web-интерфейса. И решить вопросы с безопасносью, используя ранее описанный прием с разрешенными расширениями файлов.

Пример №8 (метод POST)

Передача данных методом POST. Этот метод часто используется при отправки данных html-форм. В файле web8.lua реализован разбор параметров приходящих методом POST.

Пример 8: Скачать файлы примера. необходимо залить файлы: web8.lua, index.html, form.html, post.lua запустить сервер, выполнив скрипт: web8.lua В браузере открыть ссылку: http://192.168.0.108/form.html

Пример №9 (JSON)

Для создания современных приложний не обойтись без формата JSON. В NodeMCU есть модуль sjson который включает в себя функции, необходимые для перевода данных в строку JSON и для конвертации JSON строки обратно в объект с данными. JSON очень удобен и поддерживается многими языками программирования. Подробнее о JSON читайте здесь: https://ru.wikipedia.org/wiki/JSON. Прелесть работы с этим форматом заключается в том, что объект с данными можно преобразовать в JSON-строку, отправить или сохранить ее, затем считать или принять строку и преобразовать в объект с данными. Ниже приведен пример как данные конвертировать в JSON, сохранить в файл, прочитать данные с файла и конвертировать содержимое файла (JSON строку) в объект с данными.

Этот пример не использует «web-сервер», а демонстрирует работу с JSON, приемы передачи, обработка и хранение данных. Необходимо залить и выполнить скрипт json_test.lua

Пример 9: Скачать файл примера. Сначала создается obj и заполняется данными. Как видите, данные могут быть разного формата. Теперь преобразуем данные в JSON строку и выведем ее в консоль: Эту строку можно сохранить в файл. Таким простым образом мы можем сохранить все данные: Теперь проделаем обратную операцию. Считаем данные из файла, преобразуем в объект: Выводим данные в консоль:

В результате работы скрипта был создан файл settings.json, в который записывались а затем считывались данные. Содержимое файла следующее:

Пример №10 (JSON AJAX)

Теперь рассмотрим передачу данных с использованием JSON. Сделаем скрипт для нашего web-сервера, который будет формировать JSON и отдавать при запросе браузера: см. файл: get_json.lua

Пример 10: Скачать файлы примера. необходимо залить файлы: web8.lua, index.html, ajax.html, get_json.lua запустить сервер, выполнив скрипт: web8.lua В браузере открыть ссылку: http://192.168.0.108/get_json.lua

См. скрипт get_json.lua:

В нем мы создаем объект weather, преобразовываем в строку и отправляем. Обратите внимание, что в отличии от предыдущих скриптов в этом мы формируем заголовок HTTP ответа и задаем Content-Type. По совести говоря, это надо делать всегда, но современные браузеры не особо требовательны к Content-Type, если удается распознать тип контента, например, по расширению файла. В нашем случае lua файл формирует JSON, Content-Type нужно указывать обязательно.

Теперь будем использовать скрипт get_json.lua из html страницы с помощью Java скрипта. См. файл http://192.168.0.108/ajax.html

В этом файле сделано периодическое обновление данных на HTML странице без перезагрузки всей страницы.

Пример №11 (AngularJS)

В последнее время для сокращения времени разработки больших проектов стали использовать различные Framework-и, все реже пишут на чистом JavaScript.

В файле http://192.168.0.103/angular_autorefresh.html пример периодического обновления данных на странице без перезагрузки самой страницы, но уже средствами AngularJS.

Пример 11: Скачать файлы примера. необходимо залить файлы: web8.lua, index.html, angular_autorefresh.html, get_json.lua запустить сервер, выполнив скрипт: web8.lua В браузере открыть ссылку: http://192.168.0.108/angular_autorefresh.html

Пример №12 (JSON + Форма)

Рассмотрим пример более приближенный к практике. У нас есть форма в которой, нужно изменять и сохранять данные. Данные, разумеется, будут сохранятся в файле на файловой системе NodeMCU в виде JSON файла. При открытии ссылки http://1092.168.0.104/form2.html JavaScript запросит файл settings.json, разберет данные и заполнит поля формы. После редактирования данных и нажатия кнопки «Submit» данные будут отправлены скрипту post2.lua методом POST:

Скрипт преобразует данные в строку JSON и сохранит в файл settings.json. Круг замкнулся.

Пример 12: Скачать файлы примера. необходимо залить файлы: web8.lua, index.html, form2.html, post2.lua, settings.json запустить сервер, выполнив скрипт: web8.lua В браузере открыть ссылку: http://192.168.0.108/form2.html

Пример №13 (JSON + Форма + AngularJS)

Теперь сделаем тоже самое, но средствами AngularJS. Данные теперь отправляются скрипту post_json.lua. Обратите внимание, что все данные формы преобразуются в JSON строку, которая отправляется POST методом в единственном параметре data. Скрипт post_json.lua:

Поскольку перезагрузка страницы с формой не предполагается, скрипт возаращает ответ, чтобы можно было сообщить пользователю результат выполнения операции.

Источник: blog.avislab.com