Автоматизированный курятник (ESP8266+MajorDoMo). Часть 1 — ESP8266

Приветствую!

Не собирался писать данную статью, но наш главный админ, этот автомобильный кибер-террорист угрожает закрыть хостинг, если не будет обновлений 🙂

Несмотря на присутствие быдло-кодинга (всему свои причины, об этом ниже),  автоматизированный курятник живет уже 8 месяцев.

Сперва небольшая предыстория. После переезда в частный дом появилось пространство для моих DIY-развлечений. После того, как жена осилила огород, захотелось ей завести кур. Баба сказала — мужик сделал. Но построить домик оказалось мало — зимой курам надо как минимум освещение, для продления светового дня, и обогрев. Покупать термореле не вариант, это скучно и не интересно, да и захотелось видеть вживую температуру в курятнике, статус освещения (даже если находимся не дома),  удаленно устанавливать необходимую температуру.

Ну и конечно, тут очень кстати пригодилась ESP8266 в связке с MajorDoMo.  Ранее я рассматривал простейшие примеры по-подключению единичных датчиков, отправке данных на MDM, теперь опишу работы готового устройства с обратной связью (не только отправкой данных с ESP8266 на MDM, но и обратно).

Пропустить эту статью я хотел по той причине, что готовлю вторую, более продвинутую версию контроллера как в программной части, так и в аппаратной, но вдруг кто-то почерпнет для себя что-нибудь полезное и отсюда. Например как надо писать код на коленке, и как не надо писать вообще 🙂

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

Прошивка — ранее мною любимая NodeMCU. Структура программы — мои классические 4 файла:

  1. init.lua — автозагрузка модуля. Первый файл, который загружается в память.
  2. main.lua — основной файл выполнения программы.
  3. config.lua — файл общих настроек. Тут хранится таблица данных — данные от WiFi сети, пользовательские значения температуры, состояния освещения и обогрева.
  4. DS18B20.lua — «библиотека» для работы с датчиком температуры DS18B20.

Код файла init.lua:

dofile("config.lua")    -- загрузка файла config.lua
dofile("main.lua")      -- загрузка файла main.lua

Код файла config.lua:

config = {}                  -- создание таблицы с данными
config.LanSSID = "ХХХХ"      -- SSID сети WiFi
config.LanPWD = "ХХХХ"       -- пароль к сети сети WiFi
config.Temperatura = 1       -- минимальная температура в курятнике (температура включения обогревателя
config.Ustavka = 3           -- уставка. Отключение обогревателя происходит при Temperatura+Ustavka=3+1=4 °С
config.StateObogrevatel = 0  -- состояние обогревателя (0 - отключен, 1 - включен)
config.Light = 0             -- состояние освещения (0 - отключено, 1 - включено)
config.AutoManagmentObogrevatel = 0 -- состояние автоматического управления обогревателем (0 - отключено, 1 - включено)

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

В полях StateObogrevatel и Light хранится текущее состояние освещения и обогревателя, на случай внезапного отключения света. При подаче питания значения считываются и устанавливаются согласно этим полям.

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

Код файла DS18B20.lua:

ds18B20_data = {}               --Глобальная таблица данных датчика DS18B20
ds18B20_data.temperature=0      --температура в строке:       25.45
ds18B20_data.temperature_int=0  --температура в целых числах: 2545
ds18B20_data.id=0               --ID датчика DS18B20

function ds18B20_start()
    local pin_DS18B20=5
    
    ow.setup(pin_DS18B20)
    local count = 0
    local addr
    repeat
        count = count + 1
        addr = ow.reset_search(pin_DS18B20)     -- очищаем состояние поиска
        addr = ow.search(pin_DS18B20)           -- выполняем поиск устройства
        -- ID найденного устройства в HEX
        --ds18B20_data.id = string.format("0x%X, 0x%X, 0x%X, 0x%X, 0x%X, 0x%X, 0x%X, 0x%X", addr:byte(1,9))
        tmr.wdclr()
    until (addr ~= nil) or (count > 10)
    if addr == nil then
        print("No more addresses")
        return 0
    end
    --print(addr:byte(1,8))


    local crc = ow.crc8(string.sub(addr,1,7))
    if crc == addr:byte(8) then
       count=count+1
       ow.reset(pin_DS18B20)
       ow.select(pin_DS18B20, addr)
       ow.write(pin_DS18B20, 0x44, 0)
       tmr.delay(1000000)
       local present = ow.reset(pin_DS18B20)
       ow.select(pin_DS18B20, addr)
       ow.write(pin_DS18B20,0xBE, 0)
       local data = nil
       data = string.char(ow.read(pin_DS18B20))
       for i = 1, 8 do
           data = data .. string.char(ow.read(pin_DS18B20))
       end
       crc = ow.crc8(string.sub(data,1,8))
       if crc == data:byte(9) then
           local t = (data:byte(1) + data:byte(2) * 256)
           if (t > 32767) then
                t = t - 65536
           end
           t = t * 625  -- DS18B20, 4 fractional bits
           
           local t1 = t / 10000
           local t2 = (t % 10000)/100
           ds18B20_data.temperature=t1.."."..t2 --для вывода на экран
           ds18B20_data.temperature_int=t / 100 --для управления обогревателем

       end                   
           tmr.wdclr()
       else
               print("CRC is not valid!")
       end

    collectgarbage()
end

-- Функция получения температуры в градусах (25.45)
function DS18B20_READ()
    return ds18B20_data.temperature
end

-- Функция получения температуры в целых числах (2545)
function DS18B20_READ_INT()
    return ds18B20_data.temperature_int
end

-- Функция чтения ID преобразователя температуры
function DS18B20_READ_ID()
    return ds18B20_data.id
end

-- запускаем 1 цикл опроса датчика,
-- т.е. опрос и запись данных в таблицу ds18B20_data
-- откуда их потом можно прочитать
ds18B20_start()     

Сперва создаем таблицу ds18B20_data, в которой хранятся ID преобразователя температуры, и значение температуры для вывода на экран в градусах (25.45), и для управления обогревателем (2545).

Далее инициализируем DS18B20, считываем температуру и заполняем поля.

Код файла main.lua:

--[[ Конфигурируем чип как клиента, присоединяемся к существующей сети WiFi
     Присоединяемся к сети WiFi
     При неудачном соединении создаем свою точку доступа 192.168.1.200
     При удачном - поднимаем веб-сервер, выводим список доступных дочек доступа
     Опрашиваем датчик HC-SR501 каждые 500 мс
]]

connect=0   --статус присоединения к существующей сети WiFi
gpio.mode(7, gpio.OUTPUT)
gpio.mode(6, gpio.OUTPUT)
gpio.write(7, gpio.LOW)
gpio.write(6, gpio.LOW)
-- установка значений освещения и обогрева в соответствии с последним их состоянием
if (config.StateObogrevatel == 1) then
    gpio.write(7, gpio.HIGH)
elseif (config.StateObogrevatel == 0) then
    gpio.write(7, gpio.LOW)
end 

if (config.Light == 1) then
    gpio.write(6, gpio.HIGH)
elseif (config.Light == 0) then
    gpio.write(6, gpio.LOW)
end 


-- поднимаем точку доступа
function setSOFTAP()
    local cfg={}
    cfg.ssid="ESP8266"
    cfg.pwd="password"

    wifi.setmode(wifi.SOFTAP)
    wifi.ap.config(cfg)
    print(wifi.ap.getip())

end

-- Проверка на соединение с точкой доступа (проверка получения IP)
function getConnect()
    if wifi.sta.getip() ~= nil then      -- если IP-адрес получен (не равен NIL)
        connect = 1     -- ставим статус получения адреса
        print("IP-адрес получен: "..wifi.sta.getip())
        startWork()       -- запускаем функцию начала цикла работы
    else
        connect = 0
        print("IP-адрес не получен, поднимаем точку доступа")
        setSOFTAP()         -- если присоединиться не удалось, поднимаем свою точку доступа
        startWork()
    end
end

-- Конфигурируем чип как клиента, присоединяемся к существующей сети WiFi
-- Заводим таймер на 5 сек. Если по прошествии этого времени не получили IP,
-- то поднимаем свою точку доступа, свой веб-сервер
wifi.setmode(wifi.STATION)          -- конфигурируем чип как клиент
wifi.sta.autoconnect(1)             -- включаем автоприсоединение к сети
wifi.sta.config(config.LanSSID, config.LanPWD, true)    -- присоединяемся к сети, сохраняем настройки конфигурации во FLASH
tmr.alarm(0, 10000, tmr.ALARM_SINGLE, getConnect)   --заводим таймер на 10 секунд

function startWork()

    if srv then srv:close(); srv = nil end --Если экзземпляр другого web сервера существует, то убиваем его
    
    -- поднимаем сервер на 80 порту с таймаутом 30 сек
    srv = net.createServer(net.TCP, 30)
    -- формируем ответ из нескольких строк клиенту и отправляем
    function receiver(sck, data)
        
        --print(data)       --отправляем данные о подключении на UART

        --разбираем посылку от клиента
        local buf = "";
        local _, _, method, path, vars = string.find(data, "([A-Z]+) (.+)?(.+) HTTP");
        --print(vars)
        if(method == nil)then
            _, _, method, path = string.find(data, "([A-Z]+) (.+) HTTP");
        end
        local _GET = {}
        if (vars ~= nil)then
            for k, v in string.gmatch(vars, "(%w+)=(%w+)&*") do
                _GET[k] = tonumber(v)
                if(k == "light") then  --Если находим light, то управляем светом
                    config.Light = tonumber(v)
                    if (tonumber(v) == 1) then
                        gpio.write(6, gpio.HIGH)

						-- Отправка информации о включении света на сервер
						srv = net.createConnection(net.TCP, 0)  -- создаем новое TCP-соединение
						srv:connect(80,"192.168.1.101")          -- коннектимся к серверу с установленным MajorDoMo
						srv:on("connection", function(sck, c)   -- если соединение установлено, отправляем данные на сервер
						sck:send("GET /objects/?op=set&object=ChickenObject&p=light&v=1 HTTP/1.1\r\nHost: 192.168.1.101\r\nConnection: keep-alive\r\nAccept: */*\r\n\r\n")
						end)
						
					elseif (tonumber(v) == 0) then
                        gpio.write(6, gpio.LOW)
						
						-- Отправка информации о отключении света на сервер
						srv = net.createConnection(net.TCP, 0)  -- создаем новое TCP-соединение
						srv:connect(80,"192.168.1.101")          -- коннектимся к серверу с установленным MajorDoMo
						srv:on("connection", function(sck, c)   -- если соединение установлено, отправляем данные на сервер
						sck:send("GET /objects/?op=set&object=ChickenObject&p=light&v=0 HTTP/1.1\r\nHost: 192.168.1.101\r\nConnection: keep-alive\r\nAccept: */*\r\n\r\n")
						end)						
						
                    end
                elseif (k == "stateobogrevatel") then --Если находим stateobogrevatel, управляем обогревателем
                    config.StateObogrevatel = tonumber(v)
                    if (tonumber(v) == 1) then
                        gpio.write(7, gpio.HIGH)
						
						-- Отправка информации о включении обогревателя на сервер
						srv = net.createConnection(net.TCP, 0)  -- создаем новое TCP-соединение
						srv:connect(80,"192.168.1.101")          -- коннектимся к серверу с установленным MajorDoMo
						srv:on("connection", function(sck, c)   -- если соединение установлено, отправляем данные на сервер
						sck:send("GET /objects/?op=set&object=ChickenObject&p=StateObogrevatel&v=1 HTTP/1.1\r\nHost: 192.168.1.101\r\nConnection: keep-alive\r\nAccept: */*\r\n\r\n")
						end)						
						
                    elseif (tonumber(v) == 0) then
                        gpio.write(7, gpio.LOW)
						
						-- Отправка информации о отключении обогревателя на сервер
						srv = net.createConnection(net.TCP, 0)  -- создаем новое TCP-соединение
						srv:connect(80,"192.168.1.101")          -- коннектимся к серверу с установленным MajorDoMo
						srv:on("connection", function(sck, c)   -- если соединение установлено, отправляем данные на сервер
						sck:send("GET /objects/?op=set&object=ChickenObject&p=StateObogrevatel&v=0 HTTP/1.1\r\nHost: 192.168.1.101\r\nConnection: keep-alive\r\nAccept: */*\r\n\r\n")
						end)							
						
                    end
                elseif (k == "temperatura") then
                    config.Temperatura = tonumber(v)
                elseif (k == "ustavka") then
                    config.Ustavka = tonumber(v)
                elseif (k == "automanagmentobogrevatel") then
                    config.AutoManagmentObogrevatel = tonumber(v)
					
					-- Отправка информации о отключении обогревателя на сервер
					srv = net.createConnection(net.TCP, 0)  -- создаем новое TCP-соединение
					srv:connect(80,"192.168.1.101")          -- коннектимся к серверу с установленным MajorDoMo
					srv:on("connection", function(sck, c)   -- если соединение установлено, отправляем данные на сервер
					sck:send("GET /objects/?op=set&object=ChickenObject&p=AutoManagmentObogrevatel&v="..config.AutoManagmentObogrevatel.." HTTP/1.1\r\nHost: 192.168.1.101\r\nConnection: keep-alive\r\nAccept: */*\r\n\r\n")
					end)						
					
                elseif (k == "ssid") then config.LanSSID = v 
                elseif (k == "password") then config.LanPWD = v                        
                end

                --print(k.."="..v) 
            end

            --Записываем параметры в CONFIG
            file.open("config.lua","w+")
            file.writeline("config = {}")
            file.writeline("config.LanSSID = \""..config.LanSSID.."\"")
            file.writeline("config.LanPWD = \""..config.LanPWD.."\"")
            file.writeline("config.Temperatura = "..config.Temperatura)
            file.writeline("config.Ustavka = "..config.Ustavka)
            file.writeline("config.StateObogrevatel = "..config.StateObogrevatel)
            file.writeline("config.AutoManagmentObogrevatel = "..config.AutoManagmentObogrevatel)
            file.writeline("config.Light = "..config.Light)
            file.close()
            print("Запись параметров произведена")
            
        end
    
        local response = {}
    
        response[#response + 1] = "<!doctype html><html><head><meta charset=\"windows-1251\" />"
		response[#response + 1] = "</head><body style=\"font-family: Verdana, Arial, Helvetica, sans-serif;\"><center>"
		
		response[#response + 1] = "<h2>Температура воздуха составляет "..DS18B20_READ().." °C </h2>"
        response[#response + 1] = "<a href=\"http://192.168.1.102/\"><button style=\"height: 50px;\">НА ГЛАВНУЮ</button></a><br/>"
		response[#response + 1] = "<table style=\"border-collapse: collapse; width: 100%;\" border=\"0\"><tbody><tr>"
		response[#response + 1] = "<td style=\"width: 50%; text-align: right;\" rowspan=\"5\"><img src=\"https://i.pinimg.com/originals/2d/3b/e5/2d3be55772a3c50ef9ef84683d2a16d6.jpg\" width=\"320\"></td>"

		response[#response + 1] = "<td style=\"width: 50%; height: 20%; text-align: left; vertical-align: top;\">"
        if (config.AutoManagmentObogrevatel == 1) then
            response[#response + 1] = "<h4><font color=\"green\">Автоуправление обогревателем включено <a href=\"?automanagmentobogrevatel=0\"><br/><button style=\"height: 50px;\">Отключить автоуправление</button></a></font></h4></td></tr>"
        elseif (config.AutoManagmentObogrevatel == 0) then
            response[#response + 1] = "<h4><font color=\"red\">Автоуправление обогревателем отключено <a href=\"?automanagmentobogrevatel=1\"><br/><button style=\"height: 50px;\">Включить автоуправление</button></a></font></h4></td></tr>"
        end    

		response[#response + 1] = "<tr><td style=\"width: 50%; height: 20%; text-align: left; vertical-align: top;\">"
        if (config.StateObogrevatel == 1) then
            response[#response + 1] = "<h4><font color=\"green\">Обогреватель включен <a href=\"?stateobogrevatel=0\"><br/><button style=\"height: 50px;\">Отключить обогреватель</button></a></font></h4></td>"
        elseif (config.StateObogrevatel == 0) then
            response[#response + 1] = "<h4><font color=\"red\">Обогреватель отключен <a href=\"?stateobogrevatel=1\"><br/><button style=\"height: 50px;\">Включить Обогреватель</button></a></font></h4></td>"
        end    


		response[#response + 1] = "<tr><td style=\"width: 50%; height: 20%; text-align: left; vertical-align: top;\">"
        if (config.Light == 1) then
            response[#response + 1] = "<h4><font color=\"green\">Свет включен <a href=\"?light=0\"><br/><button style=\"height: 50px;\">Отключить свет</button></a></font></h4></td></tr>"
        elseif (config.Light == 0) then
            response[#response + 1] = "<h4><font color=\"red\">Свет отключен <a href=\"?light=1\"><br/><button style=\"height: 50px;\">Включить свет</button></a></font></h4></td></tr>"
        end

		--Создаем форму
		response[#response + 1] = "<tr><td style=\"width: 50%; text-align: left; vertical-align: top;\">"
        response[#response + 1] = "<form>Температура: <input type=\"text\" name=\"temperatura\" size=\"5\" value=\""..config.Temperatura.."\">"
		response[#response + 1] = "<br/>Уставка: <input type=\"text\" name=\"ustavka\" size=\"5\" value=\""..config.Ustavka.."\">"
		response[#response + 1] = "<br/><input type=\"submit\" value=\"Сохранить значения\"></form></td></tr>"

        --Создаем форму
        response[#response + 1] = "<tr><td style=\"width: 50%; text-align: left; vertical-align: top;\">"
        response[#response + 1] = "<form>LanSSID: <input type=\"text\" name=\"ssid\" size=\"15\" value=\""..config.LanSSID.."\">"
        response[#response + 1] = "<br/>Пароль WiFi: <input type=\"password\" name=\"password\" size=\"15\" value=\""..config.LanPWD.."\">"
        response[#response + 1] = "<br/><input type=\"submit\" value=\"Сохранить значения\"></form></td></tr></tbody></table>"

        
        response[#response + 1] = "</center></body></html>"
    
        -- отправляем и удаляем первый элемент из таблицы response, пока не отправим все
        local function send(localSocket)
            if #response > 0 then       --пока длина таблицы response не равна нулю
                localSocket:send(table.remove(response, 1))
            else
                localSocket:close()     -- если все отправили - закрываем сокет
                response = nil          -- удаляем переменную "response"
            end
        end
    
        -- triggers the send() function again once the first chunk of data was sent
        sck:on("sent", send)    --если отправили посылку - отправляем еще раз
        send(sck)
        collectgarbage()
        
    end
    
    -- Мониторим 80 порт. Если есть запрос от клиента - выполняем функцию "receiver"
    if srv then
        srv:listen(80, function(conn)
            conn:on("receive", receiver)
        end)
    end

    local function DS18B20()
        dofile("DS18B20.lua")
        print(DS18B20_READ())

        if (config.AutoManagmentObogrevatel==1) then
            --управление обогревателем
            if tonumber(DS18B20_READ_INT()) >= (config.Temperatura*100+config.Ustavka*100) then
                gpio.write(7, gpio.LOW)
                config.StateObogrevatel=0
            else 
                if tonumber(DS18B20_READ_INT()) < config.Temperatura*100 then
                    gpio.write(7, gpio.HIGH)
                    config.StateObogrevatel=1
                end
            end
        end
        collectgarbage()
        tmr.wdclr()
    end

	--Отправка температуры на сервер
	local function SendDS18B20()
		
		print(tonumber(DS18B20_READ_INT()))
        -- Отправка данных о свободной памяти
        srv = net.createConnection(net.TCP, 0)  -- создаем новое TCP-соединение
        srv:connect(80,"192.168.1.101")          -- коннектимся к серверу с установленным MajorDoMo
        srv:on("connection", function(sck, c)   -- если соединение установлено, отправляем данные на сервер
        sck:send("GET /objects/?op=set&object=ChickenObject&p=heap&v="..node.heap().." HTTP/1.1\r\nHost: 192.168.1.101\r\nConnection: keep-alive\r\nAccept: */*\r\n\r\n")
        end)
     
        -- Отправка данных о температуре
        srv = net.createConnection(net.TCP, 0)  -- создаем новое TCP-соединение
        srv:connect(80,"192.168.1.101")          -- коннектимся к серверу с установленным MajorDoMo
        srv:on("connection", function(sck, c)   -- если соединение установлено, отправляем данные на сервер
        sck:send("GET /objects/?op=set&object=ChickenObject&p=temperatura&v="..DS18B20_READ_INT().." HTTP/1.1\r\nHost: 192.168.1.101\r\nConnection: keep-alive\r\nAccept: */*\r\n\r\n")
        end)
		
		-- Отправка данных о освещении
        srv = net.createConnection(net.TCP, 0)  -- создаем новое TCP-соединение
        srv:connect(80,"192.168.1.101")          -- коннектимся к серверу с установленным MajorDoMo
        srv:on("connection", function(sck, c)   -- если соединение установлено, отправляем данные на сервер
        sck:send("GET /objects/?op=set&object=ChickenObject&p=light&v="..config.Light.." HTTP/1.1\r\nHost: 192.168.1.101\r\nConnection: keep-alive\r\nAccept: */*\r\n\r\n")
        end)
		
		-- Отправка данных о состоянии обогревателя
        srv = net.createConnection(net.TCP, 0)  -- создаем новое TCP-соединение
        srv:connect(80,"192.168.1.101")          -- коннектимся к серверу с установленным MajorDoMo
        srv:on("connection", function(sck, c)   -- если соединение установлено, отправляем данные на сервер
        sck:send("GET /objects/?op=set&object=ChickenObject&p=StateObogrevatel&v="..config.StateObogrevatel.." HTTP/1.1\r\nHost: 192.168.1.101\r\nConnection: keep-alive\r\nAccept: */*\r\n\r\n")
        end)
		
		-- Отправка данных о сигнале подключения
		srv = net.createConnection(net.TCP, 0)  -- создаем новое TCP-соединение
		srv:connect(80,"192.168.1.101")          -- коннектимся к серверу с установленным MajorDoMo
		srv:on("connection", function(sck, c)   -- если соединение установлено, отправляем данные на сервер
		sck:send("GET /objects/?op=set&object=ChickenObject&p=rssi&v="..wifi.sta.getrssi().." HTTP/1.1\r\nHost: 192.168.1.101\r\nConnection: keep-alive\r\nAccept: */*\r\n\r\n")
		end)		

       
	collectgarbage()
	end
	
    
	-- Таймер на опрос DS18B20 каждые 20 секунд
	tmr.alarm(0, 20000, tmr.ALARM_AUTO, DS18B20)
	
	-- Таймер на отправку температуры на сервер каждые 3 минуты
	tmr.alarm(1, 180000, tmr.ALARM_AUTO, SendDS18B20)

end

Все волшебство творится в файле main.lua.

Первым делом инициализируем ноги ESP8266 как выходы, считываем предыдущее их состояние, и устанавливаем в него же.

Далее пытаемся подключиться к WiFi. Если не удалось — поднимаем свою точку доступа, если полилось — выполняем основную работу в функции startWork().

В функции startWork() поднимаем веб-сервер, и мониторим. Опрос датчика температуры проводим каждые 20 секунд, а отправляем данные на сервер MajorDoMo каждые 3 минуты.

Если пользователь открывает главную страницу, выдаем ему это:

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

<img src=\"https://i.pinimg.com/originals/2d/3b/e5/2d3be55772a3c50ef9ef84683d2a16d6.jpg\" width=\"320\">

Если клиент заполнит форму (изменит параметры) и нажмет кнопку «Сохранить значения» — он будет перемещен на страницу с адресом:

http://192.168.1.102/?temperatura=0&ustavka=3

ESP8266 отработает этот GET-запрос, и установит все значения ножек модуля в нужное состояние, откроет файл «config.lua», и перезапишет его с новыми заданными значениями.

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

Теперь о недостатках. Как говорится, зима приходит незаметно, и пока морозы не начались, времени на автоматизацию не находилось. Когда грянули зимние морозы — не было времени обдумывать красивый и грамотный код. Нужно было, чтобы все заработало быстро, и более-менее надежно. В итоге досконально изучить все возможности прошивки NodeMCU у меня не было, и писал исходя из того, что знал на тот момент. А самое вкусное открылось потом 🙂

Самый большой минус — в наполнении странички данными. За это отвечает локальная переменная «response». В нее построчно добавляются данные для вывода пользователю. Изначально она содержала буквально 10 строчек, без форматирования. Но захотелось вывести текст не классическим TimeNewRoman, да и не только черным цветом, а чтобы включенное состояние отображалось зеленым, выключенное — красным. Так приятнее глазу и более информативно. В итоге количество строчек наполнения страницы увеличилось, и сами строчки разрослись.

А т.к. хранится это у нас в оперативной памяти, и построчно выводится на экран, память кушается. Да, я довольно часто ставил функцию сборки мусора «collectgarbage()», особенно после вывода все информации на экран. И она даже работает (наверное), но все равно, если довольно часто обновлять страничку, можно смотреть за тем, как память уменьшается. Я сделал отправку свободной памяти на MajorDoMo, и наблюдал это в виде графика. И вот интересно получается, иногда память восстанавливается до начального значения, а иногда скачкообразно падает вниз, принимает это новое начальное значение, и держится на нем постоянно.

С чем это связано, я не знаю. Ну точнее как не знаю, я много читал негативных отзывов о NodeMCU и утечке памяти. И уверен что именно из-за неправильной выдачи странички на экран, ведь есть более совершенный метод — сохранение все страницы в флеш-память самого контроллера, и вывод ее прямо оттуда. И не надо держать весь этот объем текста в оперативке. Все остальное вроде сделано по-науке: разбито на функциональные блоки, файлы, максимальное использование локальных переменных вместо глобальных, частая сборка мусора методом «collectgarbage()».

Но .т.к. на сам модуль ESP8266, как автономное устройство, я залажу нечасто, а пользуюсь MajorDoMo (там все красиво в виде графиков), то и память у меня особо не утекает. Работает устройство без перезагрузки месяц-полтора, а потом у нас в частном секторе бывает отключают свет, происходит перезагрузка модуля, и все возвращается на круги своя.

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

Теперь второй недостаток, который выявился в процессе практического использования. Заключается он в установке параметров модуля на самом модуле, т.е. через таблицу на странице. Все работает, все стреляет. Но дело в том, что браузер запоминает все адреса у себя, и при начале ввода страницы он выдает их все. Жена, для сокращения времени набора, не набирает страницу до конца, а выбирает из выпадающего списка (ключевое слово для нее «192.168.1.102», т.е. адрес устройства). А что идет за адресом — она не смотрит. Т.е. она может тыкнуть в выпадающем списке на GET-запрос к модулю, на котором она вручную включает обогреватель, или свет. А надо заходить именно на главную страницу, без дополнительных параметров. Выходит так, что она просто в 12 ночи захочет просто посмотреть температуру в курятнике, выберет первую попавшуюся строку с адресом — и автоматически включит курам свет 🙂

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

Но последнюю проблему мы решили просто — смотреть и управлять намного удобнее через MajorDoMo 🙂 На нем больше возможностей, можно сделать красивые странички (сервер-то стоит на компьютере, а не генерируется микросхемой). Все представлено в виде графиков, управление можно настроить автоматически по-расписанию. Можно конечно это добавить и в прошивку, но во-первых не хотелось лезть в курятник в мороз и перепрошивать то, что уже работает. А во-вторых после пары месяцев использования стало понятно, что аппетит приходит во время еды, и вторая версия автоматизированного курятнике 100% будет, более современная и даже с передачей видео из самого курятника 🙂 Уже закуплен более современный модуль ESP32-CAM, и ждет своего часа. Так что ковырять первую не стал.

Во второй части поговорим про настройку MajorDoMo для связи с нашим устройством, ну и планами на будущую версию.

Комментарии 1

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Этот сайт использует Akismet для борьбы со спамом. Узнайте как обрабатываются ваши данные комментариев.