Дисклеймер: данная статья может содержать ошибки, поскольку я не так давно работаю с модулем ESP8266 и еще не до конца понимаю многие архитектурных аспекты данного устройства.

Сегодня практически в любом доме есть Wi-Fi роутер и было бы недальновидно не воспользоваться этим устройством для домашней автоматизации, тем более что сегодня на рынке есть все доступное оборудование для реализации любых идей. Ниже будет представлен вариант создания небольшого электронного устройства, являющегося платформой для построения различных датчиков/исполнительных механизмов на основе Wi-Fi модуля — ESP8266.

image

Данный модуль хорошо описан здесь, здесь, а на этом сайте вы найдете вообще все, что знает человечество о модуле ESP8266.

Итак, что должно «уметь» устройство:
  • Получать данные с датчика влажности/температуры DHT22;
  • Управлять твердотельным реле (например SSR-25 DA);
  • Подключаться к Wi-Fi роутеру с заданным логином и паролем;
  • Передавать и получать данные через MQTT брокер;
  • Подключаться по USB для отладки и прошивки.


Схема устройства:

image

Модифкаций модуля ESP8266 очень много (варианты здесь), но, в принципе, отличаются они только размерами, типом антенны и количеством доступных портов ввода-вывода. Я использовал модуль ESP8266 ESP-01:

image

У него всего два порта (не считая USART) — GPIO0, GPIO2, но для моих целей достаточно, один порт — для датчика и второй — для управления нагрузкой.

Интерфейс USB реализуется USB-USART преобразователем CH340G.

image

Здесь описанно его подключение к 3.3 и 5В логике. Микросхема очень дешевая и удобная в использовании. Из обвязки только кварцевый генератор на 12MHz и пара конденсаторов. В итоге у вас с одной стороны USART, а с другой — USB. На PC устройство отображается как виртуальный последовательный порт.

Для управления нагрузкой я использовал пару транзисторных ключей. Почему так — спросит прожженный электронщик внимательный читатель. Все дело в том, что напряжение питания может быть разным, а мне хотелось, чтобы реле управлялось напряжением=напряжению питания. При использовании pnp транзистора, ток эмиттера (при напряжении питания > напряжения модуля ESP8266) пойдет в модуль, что совсем не хорошо. Использовать только npn транзистор я не мог, так как тогда порт GPIO0 все время будет подтянут к минусу, а в этом случае модуль будет входить в режим программирования каждый раз, когда мы рестартим модуль. Таким образом, подключив pnp + npn транзисторы, я управляю минусом реле.

Датчик DHT22:

image

Не требует никакой дополнительной обвязки и подключается прямо к модулю ESP. Ему нужен только один порт для обмена данными(интерфейс подобный 1-wire).

Так же на схеме:
  • Перемычка JP2 — для прошивки модуля. Перемычка подтягивает GPIO0 к минусу. В рабочем режиме — разомкнута, во время прошивки — замкнута);
  • Кнопка S1 — reset модуля;
  • Разъем SV1 — подключение реле;
  • Разъем J1 — гнездо питания;
  • Разъем JP1 — micro USB мама.

Питается устройство 5 — 12В.

Теперь поговорим о программном обеспечении.

Есть такой проект NodeMCU. На мой взгляд, очень крутая штука. Небольшая ОС, которая может выполнять ваши lua-скрипты прямо на ESP8266. NodeMCU умеет работать с кучей протоколов из коробки, может поднять web сервер, создать TCP соединение…

В начале прошиваем в наш модуль NodeMCU. Инструкция по прошивке.

После того, как модуль прошит, можно загружать наши скрипты. Способов множество, но лично мне нравится утилита ESPlorer — очень удобная софтина не только для загрузки скриптов, но и для разработки, дебага скриптов.

Теперь более подробно. Нам нужно залить три скрипта:

dht22.lua - собственно модуль считывающий данные с датчика DHT22
— ***************************************************************************
— DHT22 module for ESP8266 with nodeMCU
— — Written by Javier Yanez
— but based on a script of Pigs Fly from ESP8266.com forum
— — MIT license, opensource.org/licenses/MIT
— ***************************************************************************

local moduleName =…
local M = {}
_G[moduleName] = M

local humidity
local temperature

function M.read(pin)
local checksum
local checksumTest
humidity = 0
temperature = 0
checksum = 0

— Use Markus Gritsch trick to speed up read/write on GPIO
local gpio_read = gpio.read

local bitStream = {}
for j = 1, 40, 1 do
bitStream[j] = 0
end
local bitlength = 0
— Step 1: send out start signal to DHT22
gpio.mode(pin, gpio.OUTPUT)
gpio.write(pin, gpio.HIGH)
tmr.delay(100)
gpio.write(pin, gpio.LOW)
tmr.delay(20000)
gpio.write(pin, gpio.HIGH)
gpio.mode(pin, gpio.INPUT)

— Step 2: DHT22 send response signal
— bus will always let up eventually, don't bother with timeout
while (gpio_read(pin) == 0 ) do end
local c=0
while (gpio_read(pin) == 1 and c < 500) do c = c + 1 end
— bus will always let up eventually, don't bother with timeout
while (gpio_read(pin) == 0 ) do end
c=0
while (gpio_read(pin) == 1 and c < 500) do c = c + 1 end

— Step 3: DHT22 send data
for j = 1, 40, 1 do
while (gpio_read(pin) == 1 and bitlength < 10 ) do
bitlength = bitlength + 1
end
bitStream[j] = bitlength
bitlength = 0
— bus will always let up eventually, don't bother with timeout
while (gpio_read(pin) == 0) do end
end

--DHT data acquired, process.
for i = 1, 16, 1 do
if (bitStream[i] > 3) then
humidity = humidity + 2 ^ (16 — i)
end
end
for i = 1, 16, 1 do
if (bitStream[i + 16] > 3) then
temperature = temperature + 2 ^ (16 — i)
end
end
for i = 1, 8, 1 do
if (bitStream[i + 32] > 3) then
checksum = checksum + 2 ^ (8 — i)
end
end

checksumTest = (bit.band(humidity, 0xFF) + bit.rshift(humidity, 8) + bit.band(temperature, 0xFF) + bit.rshift(temperature, 8))
checksumTest = bit.band(checksumTest, 0xFF)

if temperature > 0x8000 then
— convert to negative format
temperature = -(temperature — 0x8000)
end

— conditions compatible con float point and integer
if (checksumTest — checksum >= 1) or (checksum — checksumTest >= 1) then
humidity = nil
end
end

function M.getTemperature()
return temperature
end

function M.getHumidity()
return humidity
end

return M


main.lua - основной скрипт, выполняет подключение к Wi-Fi сети, получает данные, отправляет их по mqtt и управляет нагрузкой
function subscribe()
m:subscribe("/myhome/"..id.."/light",0,function(conn)print(«Subscribe success»)end)
m:on(«message»,function(conn,topic,data)
print(topic… ": "..data )
if data==«ON»then gpio.write(3, gpio.LOW)end
if data==«OFF»then gpio.write(3, gpio.HIGH)end
end)
end

function dht22_get_data()
dht22=require(«dht22»)
dht22.read(4)
local t=dht22.getTemperature()
local h=dht22.getHumidity()
if t~=nil then
t=((t-(t % 10))/10).."."..string.format("%.i",(t % 10))
else t=nil
end
if h~=nil then
h=((h-(h % 10))/10).."."..string.format("%.i",(h % 10))
else h=nil
end
dht22=nil
package.loaded[«dht22»]=nil
collectgarbage()
return t, h
end
function post_data()
t, h = dht22_get_data()
if t ~= nil then
m:publish("/myhome/"..id.."/temperature",t,0,0, function()
print(«Temperature »..t)
if h ~= nil then
m:publish("/myhome/"..id.."/humidity",h,0,0, function()print(«Humidity »..h)end)
end
end)
end
end

function init_network()
collectgarbage()
print(id)
if wifi.sta.status() ~= 5 then
print(«Reconnecting WIFI»)
wifi.setmode(wifi.STATION)
wifi.sta.config(«Login»,«password»)
wifi.sta.connect()
tmr.alarm(0,5000,0,function()init_network()end)
else
print(«IP: »..wifi.sta.getip())
print(«Connecting to MQTT server»)
tmr.alarm(0,7000,0,function()init_network()end)
if m~=nil then
m:close()
end
m = mqtt.Client(id, 120)
m:connect(«192.168.0.x»,1883,0,function(conn)
tmr.stop(0)
print(«Connected»)
subscribe()
tmr.alarm(0, 60000, 1, function() post_data() end)
m:on(«offline»,function(con)
print(«offline.Reconnecting»)
init_network()
end)
end)
end
end

gpio.mode(3, gpio.OUTPUT)
id=«esp_»..wifi.sta.getmac()
init_network()

init.lua - стартовый скрипт. Его первым запускает NodeMCU на старте.
print(«ESP8266_home_board_v_x.x»)
dofile('main.lc')

Здесь есть нюанс. К сожалению, внешней флэш памяти модуля не достаточно для загрузки NodeMCU и моих скриптов, поэтому я использую следующее «костыльное» решение: загружаю один скрипт, выполняю команду node.compile(«dht22.lua») — данная команда компилирует скрипт в «dht22.lc», в результате он занимает меньше места и во флэш памяти и в оперативной памяти, та как потом NodeMCU будет загружать его в память во время выполнения основного скрипта. Потом удаляем нескомпилированный скрипт командой file.remove («dht22.lua»). Проделываем те же манипуляции с main.lua. Последним загружаем init.lua скрипт, его уже не компилируем. Рестартим модуль.

На старте NodeMCU выполнит «init.lua» скрипт, который в свою очередь запустит «main.lua». «main.lua» скрипт будет коннектится к сети, отправлять данные в COM порт и в сеть на заданный mqtt брокер.

Более подробно по скриптам отвечу в комментариях.

Ну, вроде бы все. Если тема интересна, в следующей статье расскажу про mqtt брокер и подключение всего этого дела к Openhab.

Спасибо за внимание.

Комментарии (26)


  1. silentz
    09.04.2015 13:47

    Кроме CH340G можно поставить CP2102 — посвежее. Есть только один минус — новичку паять не удобно.


    1. romang88 Автор
      09.04.2015 14:40

      Согласен, но паять микросхему в QFN корпусе довольно сложно


      1. nemoforum
        09.04.2015 16:13
        +1

        Отнюдь. Ее можно красиво запаять обычным паяльником с жалом типа усеченный под 45* цилиндр.


  1. DZhon
    09.04.2015 16:10

    Спасибо за материал, интересно было посмотреть схему.

    Не рассматривали вариант автономного питания от батарей и использования энергосберегающих режимов?
    Сам пользуюсь ESP8266 с AT прошивкой версии 0.22 через контроллер MSP430. Есть вот занятная команда, которая переводит контроллер в спящий режим.

    AT+GSLP | Переход в режим пониженного энергопотребления | базовая | AT+GSLP=<время в мс> | Пример: AT+GSLP=5000 (5сек)
    Для того, чтобы модуль вышел из режима сна, необходимо соединить выводы XPD_DCDC и EXT_RSTB


    К сожалению, конструктив ESP8266-01 не предусматривает вывод этих самых контактов, поэтому предпочёл ESP8266-12, в котором заметно больше доступных линий!

    Отличия налицо
    ESP-01:
    image

    ESP-12:
    image


    1. romang88 Автор
      09.04.2015 17:43

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


  1. hexenmeister
    09.04.2015 16:25

    Мне было бы интересно, как реагирует сеть, если таких устройств в ней 30-40? Есть у кого-то уще практический опыт по этой части?


    1. romang88 Автор
      09.04.2015 17:45

      Я думаю это зависит от вашего WiFi роутера. Если роутер поддерживает такое кол-во клиентов, то почему бы нет


    1. DarkByte
      09.04.2015 17:52

      30 модулей с дешманским TL-MR3420 под OpenWRT работают без каких либо проблем.


  1. galvanom
    09.04.2015 17:14

    Устройство может работать автономно, от батареек? Если да, то как долго?


    1. ncrmnt
      09.04.2015 17:29

      Не особенно долго. esp8266 жрет (могу немного соврать, замеры вычитаны в интернетах, мне было лень мерять) ~70mA@3.3v просто нифига не делая, подцепившись к wifi сети. При авторизации в вайфай сети и передаче данных — больше. Даже если датчик будет выходить из режима сна периодически и снова засыпать — все равно пары лет работы на CR2032 как на nrf24l01 не получится.


      1. hexenmeister
        09.04.2015 18:13

        Я тоже уже об этом думал. По моему, WiFi просто не было разработано для автономного питания. Я размышляю в сторону моста на ESP8266 <-> nRF24L01+ На мой взгляд отвечает все требованиям. Автономные датчики просты и экономичны, «Мост» втыкается где-нибудь в USB-Hub
        (или любой другой небольшой источник низкого напряжения)
        image


        1. ncrmnt
          09.04.2015 19:39

          У меня роль «моста» выполняют nrf24l01-донглы воткнутые в роутер с OpenWRT. Роутер и так и так свое дело делает, а с полноценной linux-системы проще рулить всем этим делом. Если надо — github.com/nekromant/rf24boot там же на гитхабе и схематика усб донглов (atmega8 с бутлоадером + vusb).


          1. hexenmeister
            09.04.2015 20:12

            Спасибо, посмотрю, что это там такое. Роутер с OpenWRT у меня тоже вторым стоит (для второго этажа). Данные из «эфира» мне нужны на Cubietruck, который стоит на первом. Сейчас у меня для этой цели Ethernet GateWay на ардуино. Всегда неплохо упростить конструкцию.


        1. Keroro
          10.04.2015 07:42

          В свящем режиме она довольно мало жрёт. Что-то типа 78мкА (на батарейках 2200мА это 3 года, примерно). Если просыпаться раз в 15 минут, передавать на сервер инфу (5 секунд), и снова засыпать, то на год должно хватить. Правда, ESP8266-01 контакт, который нужен для пробуждения, не выведен на плату, надо на ножку чипа паяться. Плюс, нужен такой-же маложрущий стабилизатор. Китайский готовый регулятор на LM2577 у меня жрал порядка 10мА, что на порядки больше, чем собственно ESP.


      1. romang88 Автор
        10.04.2015 08:10

        Выдержка из спецификации:
        image
        Из таблицы видно, что модуль потребляет максимум 215 mA и то в режиме передачи, а в standby(в этом режиме модуль подключен к WIFi сети) режиме — 0.9 mA.
        Если говорить о безпроводных датчиках, то мы понимаем, что передача данных будет происходить раз в какой то промежуток времени и длиться милисекунды. Это вам не стриминг видео. Все остальное время модуль находится в режиме standby. Теперь мы можем прикинуть сколько может прожить модуль в режиме standby на небольшой батарее в 500 mA/h. Выходит чуть больше 500 часов ~ 20 дней. Судите сами, мало это или много. Плюс еще есть спящие режимы.


        1. DarkByte
          10.04.2015 10:03

          А зачем датчику, который просыпается раз в 1/5/10 минут постоянно быть подключенным к точке? Если роутер ещё помнит dhcp аренду адреса, то модуль подключается мгновенно (на выходных планирую уточнить это время опытным путём), так же быстро отправляет данные и уходит спать, потребляя свои 60мкА (плюс потребление флешки, датчиков, стабилизатора).


          1. Alexeyslav
            10.04.2015 10:32

            Если он не предусматривает прием команд, то да — незачем.


            1. DarkByte
              10.04.2015 10:42

              А если предусматривается приём команд, то наверняка модуль будет управлять нагрузкой, у которой будет внешнее питание. Хотя наверняка есть и исключения, просто мне пока не довелось с такими кейсами встретиться.


              1. Alexeyslav
                10.04.2015 10:56

                Не обязательно. Это могут быть внутренние команды, например изменение периода опроса.
                Мне кажется, оптимальным было бы накапливать значения и передавать их пачкой раз в час или вообще в 4 часа. Ночью например, когда на показания никто не смотрит. А днем переходить на режим раз в 15 минут и т.д.


                1. DarkByte
                  10.04.2015 11:07

                  Новые настройки можно получать в ответ на передачу показаний. esp8266 пока что не славится особой стабильностью, до недавнего времени он мог крашиться от того, что ему кто-нибудь послал несколько icmp-ping пакетов, а иногда он крашится вообще без видимых причин. Соответственно данные необходимо писать во флеш, который тоже имеет ограниченный ресурс, и может так случиться, что какая-нибудь из его ячеек кончится быстрее, чем заряд батареек.


                  1. Alexeyslav
                    10.04.2015 11:15

                    У него разве полностью очищается RAM при сбросе? А внешнюю прицепиь? FRAM-чип… а если его привязать параллельно памяти с прошивкой?


        1. ncrmnt
          10.04.2015 10:30

          Лучше перепроверить, даташитам я после нескольких неприятных случаев не всегда верю. Особенно кгда работаешь с китаезами. Вот тут, например, замеры как-то не очень коррелируют с даташитом.
          Сухой вывод по ссылке: So with a total wakeup time of about 2.6s that would be an average of 66mA current draw while being awake.


          1. DarkByte
            10.04.2015 10:49

            Не совсем понял про 2.6 секунды. Судя по коду, у него 2 секунды тратится просто на ожидание подключения к точке, вне зависимости от того, сколько на самом деле занимает подключение. Плюс ещё порядка 300мс потратится на чтение DHT22, хотя эту процедуру вполне можно было вынести в те самые холостые 2 секунды.


  1. TimReset
    10.04.2015 14:06

    Не могли бы Вы подробнее рассказать о процессе написания скриптов? Как проходил debug, например. Насколько удобно было их загружать. Можно ли выполнять скрипты без загрузки на устройство? Т.к. это lua, то может удастся запустить без устройства. Например, при разработке или тестировании. И написать unit test.


    1. romang88 Автор
      10.04.2015 15:27

      Скрипты я пишу в ESPlorer — очень удобная IDE. Там конечно нет и 90% фитч, которые есть в большинстве IDE, но если учитывать, что мы пишем именно скритпы, а не какие то огромные проекты, то этого достаточно.
      В ESPlorer есть две основные вещи: редактор, с панелью загрузки скрипта в модуль и COM порт терминал, через который мы можем, как посылать команды, так и видеть вывод модуля. Там есть еще много всего, но это основные функции, которыми я пользуюсь.
      Как вообще работает NodeMCU(это ОС которую мы заливаем в модуль и которая в последствии выполняет наши скрипты).
      При запуске модуля, ОС пытается запустить init.lua скрипт, если не найдет — все, будет висеть ничего не делать, но модуль будет работать и отвечать на команды NodeMCU. если стартовый скрипт найден, ОС выполняет его. Вы можете писать собственно всю логику как в этом скрипте, так и в любом другом, главное чтоб он был загружен во флэш модуля. Выполнить любой скрипт мы можем командой dofile(«скрипт.lua»). Более подробно о командах NodeMCU вы можете прочитать у них на вики.
      Собственно весь дебаг — это вывод служебной информации в порт. Можете выводить данные, запросить отставшееся кол-во свободной памяти…
      Выполнять lua скрипты локально, у себя на машине вряд ли получится, т.к. для этих целей надо иметь эмулятор девайса, ставить на него NodeMCU и т.д. Я о такой возможности не слышал, так что приходится все тестировать на реальном железе


      1. TimReset
        10.04.2015 16:52

        На счёт дебага — насколько я понял, breakpoint поставить не удастся? На счёт выполнения lua на компьютере — я имел ввиду, что lua — это же скриптовый язык, разве его нельзя выполнить просто в интерпретаторе? Понятно, что если использовать системные библиотеки, которые есть только в NodeMCU, то ничего не получится. Но если абстрагироваться от системных вызовов, то неужели это нельзя сделать?!
        P.S. Вообще, спасибо за ответ!