ESP8266:Примеры/Веб-сервер на базе ESP8266 при помощи NodeMCU
Черновик |
Веб-сервер на базе ESP8266 при помощи NodeMCU
В этом руководстве мы расскажем, как при помощи прошивки NodeMCU создать веб-сервер на базе ESP8266, доступ к которому можно получить с любого устройства с браузером. То есть вы сможете управлять GPIO-контактами ESP8266 с ноутбука, смартфона, планшета и т.д.
В этом проекте мы будем управлять двумя светодиодами. Этот проект – лишь шаблон, и идея в том, чтобы вы заменили эти светодиоды на Power Switch Tail или реле и с их помощью могли управлять любым другим электронным устройством.
Необходимое оборудование
- Плата ESP8266 - 1шт.;
- Светодиод - 2шт.;
- Резистор 470 Ом- 2 шт.
- Макетная плата - 1шт.;
- Провода перемычки.
Справочная информация
Основы программирования на Lua
Это скриптовый язык программирования, написанный на C. Разработка этого проекта началась в 1993 году силами Роберту Иерузалимски, Луиша Энрике де Фигейреду и Валдемара Келе, работавшими тогда в подразделении Tecgraf при Католическом университете Рио-де-Жанейро.
Более подробно об этом языке программирования можно почитать в «Википедии».
Прошивка NodeMCU для ESP8266 основана на Lua, поэтому если вы хотите писать собственные скрипты для ESP8266, вам важно знать основы этого языка.
Переменные
Переменные в языке Lua не делятся по типу данных, но делятся по области видимости. Это значит, что переменные в Lua могут быть глобальными или локальными.
- Глобальные переменные. Все переменные по умолчанию считаются глобальными (если в коде специально не задано, что они локальные).
pin = 3 test = "It works!"
- Локальные переменные. Если переменная задана локальной, область ее видимости будет ограничена лишь ее функцией.
local pin = 3 local test = "It works!"
- Поля таблиц. Это особый тип переменных, в которых может храниться все, за исключением «nil» (мы это затрагивать не будем).
Типы данных (типы значений)
Lua – это язык с динамической типизацией, поэтому у переменных нет типов данных. Типы данных есть только у значений. Значения могут храниться в переменных, передаваться в виде параметров и возвращаться в виде результатов.
В таблице ниже показаны типы данных для значений в языке Lua.
Тип значения | Описание |
---|---|
Строка (string) | Массив символов |
Число (number) | Действительное (двойной точности с плавающей запятой) число |
Булево значение (boolean) | Значение «true» или «false». Как правило, используется для проверки выполнения условий |
Функция (function) | Функция, написанная на Lua |
Неопределенное значение (nil) | В такой переменной не хранится никаких данных |
Таблица (table), пользовательские данные (userdata) и поток (thread) | Эти три типа данных мы затрагивать не будем |
Вот примеры некоторых из этих типов значений:
print(type("Hello world!")) –- строка
print(type(7)) -- число
print(type(true)) -- булево значение
print(type(print)) -- функция
print(type(nil)) -- неопределенное значение
Комментарии
Комментарии – это просто текст, в котором программист объясняет, как работает его код. Если какой-то фрагмент кода помечен как комментарий, то ESP-модуль проигнорирует его и не будет обрабатывать. Комментарии начинаются с двух тире («--»). Вот два типа комментариев:
- Однострочные комментарии:
print("Hello World!") –- комментарий в одну строчку
- Многострочные комментарии:
--[[ print("Hello World!") – это многострочный комментарий --]]
Операторы
Оператор – это символ, который говорит интерпретатору выполнить определенное математическое или логическое действие. В язык Lua встроено много операторов разных типов:
- Арифметические операторы;
- Операторы сравнения;
- Логические операторы;
- Прочие операторы;
Читая таблицы и примеры ниже, представьте, что имеете дело с двумя переменными: «А», в которой хранится значение «1», и «B», в которой хранится значение «2».
A = 1
B = 2
Арифметические операторы
Оператор | Пример | Результат |
---|---|---|
+ | A + B | 3 |
- | A - B | -1 |
* | A * B | 2 |
/ | B / A | 2 |
% | B % A | 0 |
^ | B^2 | 4 |
- | -A | -1 |
Операторы сравнения
Оператор | Пример | Результат |
---|---|---|
== | (A == B) | false |
~= | (A ~= B) | true |
> | (A > B) | false |
< | (A < B) | true |
>= | (A >= B) | false |
<= | (A <= B) | true |
Логические операторы
Оператор | Пример | Результат |
---|---|---|
and (и) | (A and B) | false |
or (или) | (A or B) | true |
not (не) | !(A and B) | true |
Оператор конкатенации
Теперь представьте, что у нас две новые переменные:
a = "Hello!"
b = "World!"
Оператор | Пример | Результат |
---|---|---|
.. | a..b | "Hello World!" |
Циклы
Цикл позволяет выполнить блок кода несколько раз, пока выполняется заданное условие. Во фрагменте кода ниже – пока значением в переменной «boolean_value» является «true».
-- цикл while
while boolean_value
do
-- код будет выполняться, пока в «boolean_value» будет «true»
end
-- и цикл for
for min, max, increment
do
-- код будет выполняться, пока не будет достигнуто макс. значение
end
Операторы if… else
Операторы if... else (т.е. «если... иначе») – один из самых важных инструментов для управления программой. Они используются следующим образом:
if boolean_value then
-- если в переменной «boolean_value» значение «true»
else
-- если в переменной «boolean_value» значение «false»
end
Названия этих операторов говорят сами за себя. Если выполнено условие «boolean_value=true», то программа выполнит код, идущий после «if». Но если условием является «boolean_value=false», то программа выполнит код, идущий после «else».
Функции
Функции – отличный способ организации кода. Если вы хотите сделать в программе что-либо несколько раз, вам необязательно по несколько раз прописывать этот код. Можно просто создать отдельную функцию, содержащую этот код, а затем вызывать ее, когда вам нужно.
Ниже показано, как создать новую функцию, принимающую один параметр (температуру в градусах Кельвина) и преобразующую это значение в градусы Цельсия и Фаренгейта.
function displayTemperature(kelvin)
celsius = kelvin – 273.15
print("Temperature in Celsius is: ", celsius)
fahrenheit = (celsius*9/5+32)
print("Temperature in Fahrenheit is: ", fahrenheit)
end
k = 294 –- температура в градусах Кельвина
displayTemperature(k) –- эта строчка вызывает функцию, созданную выше
Как обращаться к контактам
В таблице ниже показано, как номер контакта ESP8266 в Lua-коде соотносится с его GPIO-номером. У платы ESP-01 только два контакта: GPIO0 и GPIO2.
Номер контакта в Lua-коде | GPIO-номер контакта |
---|---|
0 [*] | GPIO16 |
1 | GPIO5 |
2 | GPIO4 |
3 | GPIO0 |
4 | GPIO2 |
5 | GPIO14 |
6 | GPIO12 |
7 | GPIO13 |
8 | GPIO15 |
9 | GPIO3 |
10 | GPIO1 |
11 | GPIO9 |
12 | GPIO10 |
Код для загрузки(вариант без защиты)
wifi.setmode(wifi.STATION)
wifi.sta.config("YOUR_NETWORK_NAME","YOUR_NETWORK_PASSWORD")
print(wifi.sta.getip())
led1 = 1
led2 = 2
gpio.mode(led1, gpio.OUTPUT)
gpio.mode(led2, gpio.OUTPUT)
srv=net.createServer(net.TCP)
srv:listen(80,function(conn)
conn:on("receive", function(client,request)
local buf = "";
buf = buf.."HTTP/1.1 200 OK\n\n"
local _, _, method, path, vars = string.find(request, "([A-Z]+) (.+)?(.+) HTTP");
if(method == nil)then
_, _, method, path = string.find(request, "([A-Z]+) (.+) HTTP");
end
local _GET = {}
if (vars ~= nil)then
for k, v in string.gmatch(vars, "(%w+)=(%w+)&*") do
_GET[k] = v
end
end
buf = buf.."<head>";
buf = buf.."<meta charset=\"utf-8\">";
buf = buf.."<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">";
buf = buf.."<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">";
buf = buf.."<script src=\"https://code.jquery.com/jquery-2.1.3.min.js\"></script>";
buf = buf.."<link rel=\"stylesheet\" href=\"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css\">";
buf = buf.."</head><div class=\"container\">";
buf = buf.."<h1>Web Server</h1>";
buf = buf.."<h2>GPIO 5</h2>";
buf = buf.."<div class=\"row\">";
buf = buf.."<div class=\"col-md-2\"><a href=\"?pin=ON1\" class=\"btn btn-block btn-lg btn-success\" role=\"button\">ON</a></div>";
buf = buf.."<div class=\"col-md-2\"><a href=\"?pin=OFF1\" class=\"btn btn-block btn-lg btn-danger\" role=\"button\">OFF</a></div>";
buf = buf.."</div>";
buf = buf.."<h2>GPIO 4</h2>";
buf = buf.."<div class=\"row\">";
buf = buf.."<div class=\"col-md-2\"><a href=\"?pin=ON2\" class=\"btn btn-block btn-lg btn-primary\" role=\"button\">ON</a></div>";
buf = buf.."<div class=\"col-md-2\"><a href=\"?pin=OFF2\" class=\"btn btn-block btn-lg btn-warning\" role=\"button\">OFF</a></div>";
buf = buf.."</div></div>";
if(_GET.pin == "ON1")then
gpio.write(led1, gpio.HIGH);
elseif(_GET.pin == "OFF1")then
gpio.write(led1, gpio.LOW);
elseif(_GET.pin == "ON2")then
gpio.write(led2, gpio.HIGH);
elseif(_GET.pin == "OFF2")then
gpio.write(led2, gpio.LOW);
end
client:send(buf);
client:close();
collectgarbage();
end)
end)
Как работает Lua-скрипт
Давайте разберем, как работает код в этом скрипте.
Сначала переводим ESP8266 в режим станции. Затем задаем сетевые настройки (SSID и пароль) – они вставляются во вторую строчку – с помощью которых ESP8266 будет подключаться к сети. Функция print() в 3 строчке печатает в окне вывода данных (Output) IDE ESPlorer IP-адрес ESP8266. Он понадобится для получения доступа к веб-серверу.
Далее создаем две переменные («led1» и «led2») для контактов GPIO5 и GPIO4, а также переключаем их в режим «OUTPUT».
wifi.setmode(wifi.STATION)
wifi.sta.config("SSID_ВАШЕЙ_СЕТИ", "ПАРОЛЬ_ОТ_ВАШЕЙ_СЕТИ")
print(wifi.sta.getip())
led1 = 1
led2 = 2
gpio.mode(led1, gpio.OUTPUT)
gpio.mode(led2, gpio.OUTPUT)
Далее создаем веб-сервер на порте 80. Вот так:
srv=net.createServer(net.TCP)
srv:listen(80,function(conn)
end)
end)
Внутри веб-серверной функции conn:on() мы задаем, что произойдет при настройке соединения с клиентом. В частности, мы создаем несколько локальных переменных, хранящих данные веб-страницы и текущий URL.
conn:on("receive", function(client,request)
local buf = ""
local _, _, method, path, vars = string.find(request, "([A-Z]+) (.+)?(.+) HTTP);
if(method == nil)then
_, _, method, path = string.find(request, "([A-Z]+) (.+) HTTP);
end
local _GET = {}
if (vars ~= nil)then
for k, v in string.gmatch(vars, "(%w+)=(%w+)&*") do
_GET[k] = v
end
end
В переменной «buf» хранятся данные веб-страницы. Это простая веб-страница, использующая фреймворк Bootstrap (см. фрагмент кода ниже).
Более подробно о Bootstrap можно почитать тут.
На веб-странице имеется 4 кнопки – по две (для включения и выключения) на каждый светодиод. Светодиоды, напомню, подключены к контактам GPIO4 и GPIO5.
Кнопки – это просто HTML-теги <a href=””></a> с CSS-классом, который задает им внешний вид кнопок. Таким образом, когда вы нажимаете на кнопку, веб-сервер открывает другую страницу с другим URL. Именно так ESP8266 понимает, что ей нужно сделать (т.е. включить или выключить светодиод).
buf = buf.."<head>";
buf = buf.."<meta charset=\"utf-8\">";
buf = buf.."<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">";
buf = buf.."<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">";
buf = buf.."<script src=\"https://code.jquery.com/jquery-2.1.3.min.js\"></script>";
buf = buf.."<link rel=\"stylesheet\" href=\"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css\">";
buf = buf.."</head><div class=\"container\">";
buf = buf.."<h1>Web Server</h1>";
buf = buf.."<h2>GPIO 5</h2>";
buf = buf.."<div class=\"row\">";
buf = buf.."<div class=\"col-md-2\"><a href=\"?pin=ON1\" class=\"btn btn-block btn-lg btn-success\" role=\"button\">ON</a></div>";
buf = buf.."<div class=\"col-md-2\"><a href=\"?pin=OFF1\" class=\"btn btn-block btn-lg btn-danger\" role=\"button\">OFF</a></div>";
buf = buf.."</div>";
buf = buf.."<h2>GPIO 4</h2>";
buf = buf.."<div class=\"row\">";
buf = buf.."<div class=\"col-md-2\"><a href=\"?pin=ON2\" class=\"btn btn-block btn-lg btn-primary\" role=\"button\">ON</a></div>";
buf = buf.."<div class=\"col-md-2\"><a href=\"?pin=OFF2\" class=\"btn btn-block btn-lg btn-warning\" role=\"button\">OFF</a></div>";
buf = buf.."</div></div>";
Последний фрагмент кода ниже проверяет, какая именно кнопка на веб-странице была нажата. По сути, она определяет, по какому URL вы перешли.
Давайте разберем эту ситуацию на примере. Допустим, вы кликаете на кнопку «OFF» для контакта GPIO5, в результате чего открывается ссылка «http://192.168.7.2/?pin=OFF1». Ваш Lua-код смотрит на этот URL и при помощи нескольких операторов «if... else» определяет, что вы хотите переключить контакт GPIO5 (т.е. «led1») в состояние «LOW».
local _on,_off = "",""
if(_GET.pin == "ON1")then
gpio.write(led1, gpio.HIGH);
elseif(_GET.pin == "OFF1")then
gpio.write(led1, gpio.LOW);
elseif(_GET.pin == "ON2")then
gpio.write(led2, gpio.HIGH);
elseif(_GET.pin == "OFF2")then
gpio.write(led2, gpio.LOW);
end
client:send(buf);
client:close();
collectgarbage();
end)
end)
Загружаем скрипт «init.lua»
Убедитесь, что ESP8266-плата подключена к ПК, и вернитесь к IDE ESPlorer. Посмотрите в правый верхний угол программы и выполните следующее:
- Нажмите на кнопку «Refresh»
- Выберите COM-порт, к которому подключена ESP8266-плата (через FTDI-программатор или напрямую)
- Выставьте скорость коммуникации на 9600 бод
- Нажмите на кнопку «Open»
Затем посмотрите в левый верхний угол IDE ESPlorer и сделайте следующее:
- Выберите вкладку «NodeMCU+MicroPython»
- Выберите вкладку «Scripts»
- Создайте новый файл «init.lua»
Скопируйте в окно кода скрипт из предыдущего раздела (как на скриншоте ниже):
Теперь нам нужно загрузить этот код на ESP8266.
Нажмите на кнопку «Save to ESP», которая находится в левом нижнем углу программы.
В окне Output будут показаны команды, отправляемые на ESP8266. Они должны быть примерно такими же, как на скриншоте ниже:
Узнаем IP-адрес ESP8266
После того, как вы загрузите веб-серверный Lua-скрипт на ESP8266, в окне Output будут напечатаны три IP-адреса.
Нам нужен самый первый IP-адрес, и в моем случае это «192.168.1.70» (у вас он, скорее всего, будет другим). Сохраните его, т.к. он понадобится нам уже совсем скоро.
Собираем цепь
Загрузив код на ESP8266, соберите цепь проекта согласно схеме ниже. Для подключения светодиодов подойдут резисторы номиналом 470 Ом.
Как получить доступ к веб-серверу
Чтобы получить доступ к веб-серверу, проделайте следующее:
- Перезапустите ESP8266-модуль
- Откройте браузер
- Впишите в адресную строку браузера IP-адрес, который сохранили ранее (в моем случае это «192.168.1.70»)
В результате должна загрузиться страница.
Вот и все! Удивительно, как маленький 4-долларовый WiFi-модуль может и служить веб-сервером, и обслуживать веб-страницы, созданные для экранов мобильных устройств
Как защитить веб-сервер паролем
На данный момент ваш веб-сервер работает в локальной сети, и доступ к нему может получить любой, подключившийся к тому же роутеру и вписавший IP-адрес ESP8266 в браузер своего устройства.
Но веб-сервер можно защитить, оснастив механизмом аутентификации. В результате, если кто-то попытается получить доступ к веб-серверу, ему сначала нужно будет ввести логин и пароль.
Для того, чтобы оснастить веб-сервер механизмом аутентификации, в его код нужно лишь добавить вот эти 6 строчек:
local _, _, auth = string.find(request, "%cAuthorization: Basic ([%w=\+\/]+)");--Authorization:
if (auth == nil or auth ~= "dXNlcjpwYXNz")then --user:pass
client:send("HTTP/1.0 401 Authorization Required\r\nWWW-Authenticate: Basic realm=\"ESP8266 Web Server\"\r\n\r\n<h1>Unauthorized Access</h1>");
client:close();
return;
end
Код для загрузки (с механизмом аутентификации)
-- Rui Santos
-- Complete project details at http://randomnerdtutorials.com
wifi.setmode(wifi.STATION)
wifi.sta.config("YOUR_NETWORK_NAME","YOUR_NETWORK_PASSWORD")
print(wifi.sta.getip())
led1 = 1
led2 = 2
gpio.mode(led1, gpio.OUTPUT)
gpio.mode(led2, gpio.OUTPUT)
srv=net.createServer(net.TCP)
srv:listen(80,function(conn)
conn:on("receive", function(client,request)
local buf = "";
buf = buf.."HTTP/1.1 200 OK\n\n";
local _, _, method, path, vars = string.find(request, "([A-Z]+) (.+)?(.+) HTTP");
if(method == nil)then
_, _, method, path = string.find(request, "([A-Z]+) (.+) HTTP");
end
local _, _, auth = string.find(request, "%cAuthorization: Basic ([%w=\+\/]+)");--Authorization:
if (auth == nil or auth ~= "dXNlcjpwYXNz")then --user:pass
client:send("HTTP/1.0 401 Authorization Required\r\nWWW-Authenticate: Basic realm=\"ESP8266 Web Server\"\r\n\r\n<h1>Unauthorized Access</h1>");
client:close();
return;
end
local _GET = {}
if (vars ~= nil)then
for k, v in string.gmatch(vars, "(%w+)=(%w+)&*") do
_GET[k] = v
end
end
buf = buf.."<head>";
buf = buf.."<meta charset=\"utf-8\">";
buf = buf.."<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">";
buf = buf.."<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">";
buf = buf.."<script src=\"https://code.jquery.com/jquery-2.1.3.min.js\"></script>";
buf = buf.."<link rel=\"stylesheet\" href=\"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css\">";
buf = buf.."</head><div class=\"container\">";
buf = buf.."<h1>Web Server</h1>";
buf = buf.."<h2>GPIO 5</h2>";
buf = buf.."<div class=\"row\">";
buf = buf.."<div class=\"col-md-2\"><a href=\"?pin=ON1\" class=\"btn btn-block btn-lg btn-success\" role=\"button\">ON</a></div>";
buf = buf.."<div class=\"col-md-2\"><a href=\"?pin=OFF1\" class=\"btn btn-block btn-lg btn-danger\" role=\"button\">OFF</a></div>";
buf = buf.."</div>";
buf = buf.."<h2>GPIO 4</h2>";
buf = buf.."<div class=\"row\">";
buf = buf.."<div class=\"col-md-2\"><a href=\"?pin=ON2\" class=\"btn btn-block btn-lg btn-primary\" role=\"button\">ON</a></div>";
buf = buf.."<div class=\"col-md-2\"><a href=\"?pin=OFF2\" class=\"btn btn-block btn-lg btn-warning\" role=\"button\">OFF</a></div>";
buf = buf.."</div></div>";
if(_GET.pin == "ON1")then
gpio.write(led1, gpio.HIGH);
elseif(_GET.pin == "OFF1")then
gpio.write(led1, gpio.LOW);
elseif(_GET.pin == "ON2")then
gpio.write(led2, gpio.HIGH);
elseif(_GET.pin == "OFF2")then
gpio.write(led2, gpio.LOW);
end
client:send(buf);
client:close();
collectgarbage();
end)
end)
Задаем собственные имя пользователя и пароль
На этом этапе, если вы загрузили код из предыдущего раздела, именем пользователя будет «user», а паролем – «pass». Но вы наверняка захотите задать свои собственные учетные данные.
Пройдите по этой ссылке. В первом поле впишите следующее:
ваш_логин:ваш_пароль
Соответственно, вместо «ваш_логин» и «ваш_пароль» впишите собственные логин и пароль.
Я для своего кода использовал вариант «user:pass».
Нажмите на кнопку «Encode», чтобы сгенерировать строку в кодировке base64. В моем случае это «dXNlcjpwYXNz».
Скопируйте строку, которая получилась у вас, и вставьте ее в эту строчку Lua-скрипта (вместо строчки "dXNlcjpwYXNz"):
if (auth == nil or auth ~= "dXNlcjpwYXNz")then --user:pass
Загружаем обновленный веб-серверный скрипт на ESP8266
Теперь, когда мы оснастили код механизмом аутентификации, его нужно загрузить на ESP8266.
Загрузив его, откройте веб-сервер, введя его IP-адрес в своем браузере.
В результате в браузере должно появиться новое окно с просьбой ввести логин и пароль от веб-сервера.
Что дальше
Надеюсь, проект вам понравился. Конечно, он очень простой, т.к. его суть лишь во включении и выключении светодиодов, но такой веб-сервер сам по себе – это очень полезная вещь.
К примеру, с его помощью можно управлять включением/выключением домашней техники (что гораздо интересней, чем управление светодиодами), питаемой от настенных розеток. Но для этого вам – помимо ESP8266 и управляемой техники – понадобится еще одно устройство...
И здесь может быть два варианта:
Вариант А – PowerSwitch Tail II
Это самый простой вариант. Вам понадобится устройство PowerSwitch Tail II, позволяющее безопасно работать с техникой, работающей от тока высокого напряжения.
Это устройство работает очень просто. Вместо подключения домашней техники напрямую в розетку вы сначала подключаете ее в PowerSwitch Tail II, а уже PowerSwitch Tail II – в розетку.
PowerSwitch Tail II оснащен 3 контактами, благодаря которым он работает на простой цифровой логике. PowerSwitch Tail II также нужно подключить к выходному GPIO-контакту ESP8266.
Этот выходной GPIO-контакт ESP8266 будет отправлять PowerSwitch Tail II либо «HIGH», либо «LOW». Если отправленный сигнал – это «HIGH», то PowerSwitch Tail II пустит ток между управляемой техникой и розеткой, а если «LOW», то PowerSwitch Tail II отключит ток, и управляемое устройство перестанет работать.
Контакты PowerSwitch Tail II нужно подключить следующим образом:
Контакт PowerSwitch Tail II | Сигнал | Контакт ESP8266 |
---|---|---|
1 | +in | GPIO4 или GPIO5 |
2 | -in | GND |
3 | GND | Не нужно подключать |
Более подробно о подключении контактов PowerSwitch Tail II читайте в его документации.
Вариант Б – реле
Есть еще один способ управления домашней техникой с помощью ESP8266, но он чуть сложнее. Он требует некоторых знаний и осторожности, т.к. при его использовании придется иметь дело с переменным током и реле-модулем.
См.также
Внешние ссылки
ESP8266 AT-команды | |
---|---|
Список AT-команд | |
Базовые команды |
|
Команды для WiFi |
|
Команды для TCP/IP |
|