ESP8266:Примеры/Отображение температуры и влажности на веб-странице при помощи прошивки NodeMCU

Материал из Онлайн справочника
Перейти к навигацииПерейти к поиску

Перевод: Максим Кузьмин
Проверка/Оформление/Редактирование: Мякишев Е.А.


Отображение температуры и влажности на веб-странице при помощи прошивки NodeMCU

В этом руководстве мы создадим при помощи ESP8266 веб-сервер, доступ к которому можно будет получить с любого устройства с браузером. Этот веб-сервер будет обслуживать страницу, показывающую текущие температуру и влажность. Для считывания этих показателей мы воспользуемся датчиком DHT11.

Необходимое оборудование

  • Плата ESP8266 - 1шт.
  • Датчик температуры и влажности DHT11 - 1шт.
  • Резистор на 4,7 кОм - 1шт.
  • Макетная плата - 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 – это язык, чувствительный к регистру. Поэтому переменная «PIN» – это не то же самое, что «Pin» или «pin».

Типы данных (типы значений)

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))            -- неопределенное значение
Примечание

Работая с ESP8266 и прошивкой NodeMCU, вы изредка будете встречать тип данных «nil». Это будет значить лишь, что такая переменная не определена. Также, если вы хотите удалить какое-то значение в какой-то переменной, просто присвойте ей значение «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

Использование датчика температуры и влажности DHT11

Датчики DHT – это относительно недорогие датчики для измерения температуры и влажности.

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

Эти сигналы очень легко считываются любым микроконтроллером (MCU).

Характеристики DHT11

  • Абсолютная погрешность: ±5%
  • Воспроизводимость результатов: ±1%
  • Долговременность стабильность измерений: ±1% в год
  • Цена: между 1 и 5 долларами

Контакты

  • VCC (3.3 вольта)
  • Вывод данных
  • Не подключен
  • GND

Код для загрузки

Внимание!

Для работы данного скрипта в вашей прошивке должен быть интегрирован модуль DHT, для работы с датчиками данного типа.

-- Rui Santos
-- Complete project details at http://randomnerdtutorials.com

wifi.setmode(wifi.STATION)
wifi.sta.config("YOUR_NETWORK_NAME","YOUR_NETWORK_PASSWORD")
tmr.delay(5000)
print(wifi.sta.getip())

pin = 1
bimb = 1
fare = 0
fare_dec = 0

--Read DHT Sensor
function ReadDHT11()
    status, temp, humi, temp_dec, humi_dec = dht.read(pin)
    if status == dht.OK then
        print(string.format("DHT Temperature:%d.%03d;Humidity:%d.%03d\r\n",
              math.floor(temp),
              temp_dec,
              math.floor(humi),
              humi_dec
        ))
        fare = (9 * math.floor(temp) / 5) + 32
        fare_dec = (9 * temp / 5) % 10
    elseif status == dht.ERROR_CHECKSUM then
        print( "DHT Checksum error." )
    elseif status == dht.ERROR_TIMEOUT then
        print( "DHT timed out." )
    end
end
ReadDHT11()

tmr.alarm(1,5000, 1, function() ReadDHT11() bimb=bimb+1 if bimb==5 then bimb=0 wifi.sta.connect() print("Reconnect")end end)

srv=net.createServer(net.TCP) srv:listen(80,function(conn)
    conn:on("receive",function(conn,payload)
   --print(payload) -- for debugging only
    --generates HTML web site
    conn:send('HTTP/1.1 200 OK\r\nConnection: keep-alive\r\nCache-Control: private, no-store\r\n\r\n\
        <!DOCTYPE HTML>\
        <html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"></head>\
        <meta http-equiv="X-UA-Compatible" content="IE=edge">\
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">\
        <meta http-equiv="refresh" content="6">\
        </head><div class="container">\
        <h1>Sensor Data</h1></br><div class="row">\
        <div class="col-md-4"><div class="panel panel-primary"><div class="panel-heading"><h3 class="panel-title">Humidity</h3>\
        </div><div class="panel-body">\
        <div class="form-group form-group-lg"><input type="text" class="form-control" value="'..math.floor(humi)..'.'..humi_dec..' %">\
        </div></div></div></div>\
        <div class="col-md-4"><div class="panel panel-info"><div class="panel-heading"><h3 class="panel-title">Temperature</h3>\
        </div><div class="panel-body">\
        <div class="form-group form-group-lg"><input type="text" class="form-control" value="'..math.floor(temp)..'.'..temp_dec..' deg C">\
        <input type="text" class="form-control" value="'..fare..'.'..fare_dec..' deg F">\
        </div></div></div></div></div></div></html>')
    conn:on("sent",function(conn) conn:close() end)
    end)
end)

Как работает этот скрипт

Код начинается с переключения ESP8266 в режим станции. Затем мы задаем SSID и пароль WiFi-сети, к которой подключаемся. Соответственно, вам нужно будет заменить параметры "YOUR_NETWORK_NAME" и "YOUR_NETWORK_PASSWORD" на собственные, чтобы ESP8266 могла подключиться к вашей WiFi-сети.

Затем при помощи функции tmr.delay(5000) задается 5-секундная пауза. Это дает ESP8266 время для подключения к сети.

В следующей строчке функция print() печатает IP-адрес ESP8266 в окне Output в IDE ESPlorer (этот IP-адрес понадобится вам, чтобы подключиться к веб-серверу).

wifi.setmode(wifi.STATION)
wifi.sta.config("YOUR_NETWORK_NAME","YOUR_NETWORK_PASSWORD")
tmr.delay(5000)
print(wifi.sta.getip())

Затем задаем контакт, к которому подключен DHT11. В данном случае это «pin = 1», что означает контакт GPIO5. Также создаем еще 3 переменные для хранения данных.

pin = 1
bimb = 1
fare = 0
fare_dec = 0

Затем создаем функцию с говорящим названием ReadDHT11(). Она считывает с датчика DHT11 данные о температуре и влажности, а затем сохраняет их в правильные переменные. Она также печатает эти данные в окно Output в IDE ESPlorer.

function ReadDHT11()
    status, temp, humi, temp_dec, humi_dec = dht.read(pin)
    if status == dht.OK then
        print(string.format("DHT Temperature:%d.%03d;Humidity:%d.%03d\r\n",
              math.floor(temp),
              temp_dec,
              math.floor(humi),
              humi_dec
        ))
        fare = (9 * math.floor(temp) / 5) + 32
        fare_dec = (9 * temp / 5) % 10
    elseif status == dht.ERROR_CHECKSUM then
        print( "DHT Checksum error." ) -- "Ошибка контрольной суммы"
    elseif status == dht.ERROR_TIMEOUT then
        print( "DHT timed out." ) -- "Таймаут"
    end
end
ReadDHT11()

Нам также нужно создать функцию tmr.alarm(), которая будет запускать функцию ReadDHT11() каждые 5 секунд. При каждом вызове она считывает текущие данные о температуре и влажности. Далее мы создаем объект веб-сервера на порте 80 и вызываем функцию conn:send().

tmr.alarm(1,5000, 1, function() ReadDHT11() bimb=bimb+1 if bimb==5 then bimb=0 wifi.sta.connect() print("Reconnect")end end)

srv=net.createServer(net.TCP) srv:listen(80,function(conn)
    conn:on("receive",function(conn,payload)
   --print(payload) – только для отладки
    -- генерирует HTML-страницу
    conn:send()
    conn:on("sent",function(conn) conn:close() end)
    end)
end)

Внутри функции conn:send() мы пишем данные HTML-страницы (см. код ниже). Это простая веб-страница, использующая фреймворк Bootstrap. Более подробно о Bootstrap можно почитать тут.

conn:send('HTTP/1.1 200 OK\r\nConnection: keep-alive\r\nCache-Control: private, no-store\r\n\r\n\
        <!DOCTYPE HTML>\
        <html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"></head>\
        <meta http-equiv="X-UA-Compatible" content="IE=edge">\
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">\
        <meta http-equiv="refresh" content="6">\
        </head><div class="container">\
        <h1>Sensor Data</h1></br><div class="row">\
        <div class="col-md-4"><div class="panel panel-primary"><div class="panel-heading"><h3 class="panel-title">Humidity</h3>\
        </div><div class="panel-body">\
        <div class="form-group form-group-lg"><input type="text" class="form-control" value="'..math.floor(humi)..'.'..humi_dec..' %">\
        </div></div></div></div>\
        <div class="col-md-4"><div class="panel panel-info"><div class="panel-heading"><h3 class="panel-title">Temperature</h3>\
        </div><div class="panel-body">\
        <div class="form-group form-group-lg"><input type="text" class="form-control" value="'..math.floor(temp)..'.'..temp_dec..' deg C">\
        <input type="text" class="form-control" value="'..fare..'.'..fare_dec..' deg F">\
        </div></div></div></div></div></div></html>')

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

Загружаем скрипт «init.lua»

Чтобы загрузить этот Lua-скрипт на ESP8266, проделайте следующее:

  1. Подключите ESP8266 (напрямую или через FTDI-программатор) к ПК
  2. Выберите COM-порт, к которому подключен ESP8266
  3. Нажмите на кнопку «Open/Close»
  4. Выберите вкладку «NodeMCU+MicroPython»
  5. Создайте новый файл под названием «init.lua»
  6. Нажмите на кнопку «Save to ESP»

Кроме того, в окне Output будут напечатаны команды, отсылаемые ESP8266.

Примечание

Вы можете без труда удалить файл «init.lua» из ESP8266. Для этого просто впишите file.remove(“init.lua”) и нажмите на кнопку «Send» (см. скриншот выше). Если вам нужно удалить все файлы, хранящиеся на ESP8266, воспользуйтесь командой file.format().

Узнаем IP-адрес ESP8266

После того, как вы загрузите на ESP8266 веб-серверный Lua-скрипт, в окне Output в IDE ESPlorer должно появиться три IP-адреса.

Нам нужен самый первый IP-адрес. В моем случае это «192.168.1.70», но у вас, скорее всего, будет какой-то другой IP-адрес. Сохраните его где-нибудь, потому что чуть позднее он нам понадобится.

Примечание

Если IP-адрес не появился в окне Output, то его можно узнать, отправив ESP8266 команду print(wifi.sta.getip()).

Собираем цепь

Загрузив скрипт на ESP8266, соберите цепь, показанную ниже. Для подключения DHT11 вам также понадобится резистор номиналом 4,7 кОм или выше, который будет служить подтягивающим резистором.

Внимание

Чтобы цепь работала, вам понадобится 3.3-вольтовый источник питания с 250 мА.

Подключаемся к веб-серверу

Чтобы подключиться к веб-серверу, проделайте следующее:

  1. Перезапустите ESP8266
  2. Откройте браузер
  3. Впишите в адресную строку браузера IP-адрес, сохраненный ранее (в моем случае это «192.168.1.70»)

В результате должна загрузиться примерно такая веб-страница:

Клево, а? Смело пробуйте модифицировать этот Lua-скрипт и добавлять в него свои идеи. И помните, что эта веб-страница будет автоматически обновляться каждые 6 секунд.

Кроме того, эта HTML-страница способна адаптироваться под любые экраны, так что она будет хорошо выглядеть в том числе и на экране мобильного устройства.

Что дальше

Плата ESP-12E оснащена лишь одним аналоговым контактом, что очень неудобно, если вы хотите подключить к своему проекту больше одного аналогового датчика/модуля...

Как можно обойти эту проблему? Есть один вариант.

Можно настроить последовательную коммуникацию между Arduino и ESP8266 и подключить к Arduino нужные датчики.

Arduino будет делать всю черную работу, считывая данные с датчиков, а затем отправляя их по последовательному порту ESP8266. Сам ESP8266, тем временем, будет получать эти данные и показывать их на веб-странице!

Впрочем, вам нужно будет немного адаптировать некоторые фрагменты этого руководства, чтобы Arduino могла считывать данные с датчиков.

См.также

Внешние ссылки