• Система автоматизации с открытым исходным кодом на базе esp8266/esp32 микроконтроллеров и приложения IoT Manager. Наша группа в Telegram

Для новичков

exclus

New member
Всем доброго времени суток.
Я хотел бы попросить сообщество составить руководство для новичков. С примерами работы с esp8266. Вроде бы информации много, а кроме как подключится к wi-fi и т.д. подробных инструкций нет. Хотя бы базовые примеры с веб сервером и отправкой команд в uart через вэб страницу, получением данных из uart и отображением на вэб странице. Можно другие примеры, только желательно код с комментариями, а то очень не просто разобраться в этом замечательном модуле.
 

exclus

New member
Виктор, спасибо, я уже все прочитал. Мне не очень понятна очередность построения программы. Как я уже понял головной скрипт должен быть init.lua, куда мы пишем базовую программу по подключению модуля к сети. А дальше как запустить из него другие программы, например вэб сервер и управление (gpio, и отправка данных в уарт)? Можете показать пример скрипта с комментариями на русском?
 

Victor

Administrator
Команда форума
можно все и в init запихать - это неважно.
просто, если в коде будет зависание или bootloop, придется весь модуль перепрошивать, поэтому делают в init вызов другого файла по таймеру.
Например, так
Код:
init.lua
сначала команды подключения к сети,
в конце
tmr.alarm(0, 5000, 1,  function() dofile("main.lua") end)

в main.lua
первой строкой
tmr.stop(0)
далее код
Т.е. main будет запускаться через 5 сек после старта модуля, как раз уже когда подключились к WiFi
Если в коде будет ошибка вы вручную сможете остановить таймер (нужно за эти 5 сек в терминале ввести tmr.stop(0)) и можно править main.lua

Если в tmr.alarm третьим параметром поставить 0, то tmr.stop не нужен
Ответ набросал быстро, тороплюсь, если что-то непонятно - спрашивайте, я вечером отвечу.
 

Victor

Administrator
Команда форума
Вот еще красивое решение bootloop (если память позволяет)
Код:
FileToExecute="main.lua"
l = file.list()
for k,v in pairs(l) do
  if k == FileToExecute then
    print("*** You've got 5 sec to stop timer 0 ***")
    tmr.alarm(0, 5000, 0, function()
      print("Executing ".. FileToExecute)
      dofile(FileToExecute)
    end)
  end
end
 

JustACat

Moderator
Команда форума
А я бы еще порекомендовал вот эту статейку: http://mysku.ru/blog/ebay/30626.html - ее бы на наш ресурс перенести, а то чего ей на муське делать, но увы, это к автору :)
Ув. Victor, а может нам где-нибудь собрать список ссылок на статьи? Понимаю, что ресурсы сторонние, но все же для общего дела, так сказать...
Я бы как минимум туда включил ссылку на эту статью и из нее в конце ссылки "Полезные ссылки и литература".
 

Кирилл

New member
отправил запрос автору на публикацию у нас, ждем ответа
На сколько я знаю у этого автора есть блог ... samopal.pro, кот вроде его, да и почерк тоже, вот только почему он до сих пор у себя не продублировал..., а может тут у всех есть свои блоги сайты и форумы )))
 

Victor

Administrator
Команда форума
Автор ответил положительно, прислал фото.
В ближайшее время выложу эту статью к нам на сайт
 

exclus

New member
Всем привет, после долгой ночи и случайного "заседания" в нужном месте с утра обнаружил на "вражеском" форуме ссылку но простой проект. Собрал - работает, Ура. Прикрутил еще ШИМ управление, сам по API. И вот, что получилось.

Код жирным шрифтом, комментарии курсивом, комментарии которые прошу пояснить подчеркнутым курсивом.

wifi.setmode(wifi.STATION) -- Задаем режим работы esp8266, как wi-fi клиент
wifi.sta.config("Homewifi","12345678") -- Подключаемся к wi-fi сети ("Имясети","пароль")
print(wifi.sta.getip()) -- Пишем в UART полученные настройки (IP, Mask, Gateway)
led1 = 3 -- Задаем переменную led1 = IO 3 = GPIO 15 (по таблице с API NodeMCU)
led2 = 4 -- Задаем переменную led1 = IO 4 = GPIO 3 (по таблице с API NodeMCU)
gpio.mode(led1, gpio. OUTPUT) -- Переключаем GPIO в режим выхода <- Удалите пробел между gpio. и OUTPUT
pwm.setup(led2, 10, 512) -- Второй GPIO подключаем к ШИМ каналу указываем в скобках (номер GPIO, частота ШИМ, коэфициент заполнения ШИМ (обратная скважности) по умолчанию)
srv=net.createServer(net.TCP) -- Задаем переменную srv = net.server (не совсем очевидно из данной команды, но судя по API переменная работает) и одновременно поднимаем TCP сервер. Почему-то не задаем timeout для не активных подключений, не знаю какой параметр он принемает по умолчанию.
srv:listen(80,function(conn) -- Слушаем 80 порт, при подключении клиента создается socket - имя его переменной (conn). Как я понял это свободная переменная можно написать любую, которую удобно использовать
-- Ниже не понимаю
conn: on("receive", function(client,request) -- <- Удалите пробел между conn: и on
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, а дальше пишем на HTML (Заголовок с текстом, создаем кнопки при нажатии на которые получаются GET запросы к модулю (\"?pin=ON1\" и т.д.)
buf = buf.."<h1> ESP8266 Web Server</h1>";
buf = buf.."<p>GPIO0 <a href=\"?pin=ON1\"><button>ON</button></a>&nbsp;<a href=\"?pin=OFF1\"><button>OFF</button></a></p>";
buf = buf.."<p>GPIO2 <a href=\"?pin=100\"><button>100% light</button></a>&nbsp;<a href=\"?pin=50\"><button>50% light</button></a>&nbsp;<a href=\"?pin=10\"><button>10% light</button></a>&nbsp;<a href=\"?pin=OFF2\"><button>OFF</button></a></p>";

--//--
local _on,_off = "","" -- Не понимаю
if(_GET.pin == "ON1")then -- Задаем условие, если модуль видит запрос (_GET.pin == "ON1"), то
gpio.write(led1, gpio.HIGH); -- Выводим высокий уровень на GPIO (Подтягиваем к плюсу)
elseif(_GET.pin == "OFF1")then -- или если модуль видит запрос (_GET.pin == "OFF1"), то
gpio.write(led1, gpio.LOW); -- Выводим низкий уровень на GPIO (Подтягиваем к плюсу)
elseif(_GET.pin == "100")then -- или если модуль видит запрос (_GET.pin == "100"), то
pwm.setduty(led2,1023); -- Задаем заполнение ШИМ на GPIO равное 100 процентам (получается ~3,3 вольт)
pwm.start(led2); -- и даем команду на включение подачи ШИМ в GPIO
elseif(_GET.pin == "50")then -- или если модуль видит запрос (_GET.pin == "50"), то
pwm.setduty(led2,512); -- Задаем заполнение ШИМ на GPIO равное 50 процентам (получается ~1.65 вольт)
pwm.start(led2); -- и даем команду на включение подачи ШИМ в GPIO, из-за того что в самом начале задали низкую частоту ШИМ - здесь получим "Мигалку" ;-)
elseif(_GET.pin == "10")then -- или если модуль видит запрос (_GET.pin == "10"), то
pwm.setduty(led2,10); -- Задаем заполнение ШИМ на GPIO равное 10 процентам (получается ~0,33 вольт)
pwm.start(led2); -- и даем команду на включение подачи ШИМ в GPIO, из-за того что в самом начале задали низкую частоту ШИМ - здесь получим тусклую "Мигалку" ;-)
elseif(_GET.pin == "OFF2")then -- или если модуль видит запрос (_GET.pin == "OFF2"), то
pwm.stop(led2); -- и даем команду на выключение подачи ШИМ в GPIO
end -- Конец цикла if
client:send(buf); -- Не понимаю
client:close(); -- Не понимаю, но думаю... , нет все равно не понимаю
collectgarbage(); -- Какой-то сборщик мусора из языка программирования LUA, чем занимается не понимаю
end) -- Конец цикла "conn: on" <- Удалите пробел между conn: и on
end) -- Конец цикла "srv:listen"


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

Исходник взят от сюда http://randomnerdtutorials.com/esp8266-web-server/

P.S. После некоторого времени работы отваливается подключение по Wi-fi????
 
Последнее редактирование:

Victor

Administrator
Команда форума
exclus, на этом форуме всегда рады помочь, особенно тем, кто так аккуратно оформляет посты, как ваш. Причем это не ирония, а искренняя благодарность.
Однако вы должны понять, что разбирать по строчке чужие скрипты вряд ли кто-то захочет.
Вам может показаться это несправедливым, потому что вы пытались разобраться сначала самостоятельно с каждой строчкой и только потом обратились за помощью на форум,
но дело в том, что большинство ваших вопросов касаются самого языка Lua и никак не связаны с NodeMCU и ESP8266, а специалистов именно по языку Lua у нас немного.
Я вообще знаю только одного (и, увы, это не я). Большинство, как и вы разбирает каждую строку и не имеет уверенности в своих знаниях чтобы давать советы другим.
Я вам рекомендую что-то почитать по Lua и большинство вопросов по этому скрипту у вас прояснится.
Ну или хотя бы задавайте по одному вопросу за раз - так отвечать намного проще. Я вам отвечу на первый.
Почему-то не задаем timeout для не активных подключений, не знаю какой параметр он принимает по умолчанию
Вы абсолютно правы, что заметили это.
Дело в том, что NodeMCU быстроразвивающийся проект и часть скриптов в сети просто устарела, потому что API NodeMCU развивается, команды меняются и добавляются новые.
В первых версиях второго параметра просто не было. Он появился позднее, а в скрипте, который вы нашли осталось все по старому. Некоторые команды остались совместимы со старыми версиями, а некоторые нет.
2014-11-20
add a timeout para to createServer(net.TCP, timeout).
я вам привел выдержку из ChangeLog NodeMCU
Велика вероятность, что скрипт очень старый и, может быть, вообще не рабочий.
В данной строке нужно скрипт исправить, прописать таймаут явно.
 

exclus

New member
Скрипт рабочий, я туда еще и ШИМ управление, добавил. Не обязательно LUA, просто это самое простое, что я нашел для esp на вашем форуме. А прошивки которые пишет pvvx, это для меня вообще дебри, а для него маленький веб сервер ))). Нужно с чего начинать. Просто без таких простых примеров тяжело.
А какой язык вы посоветует для еsp? Родной SDK его как я понял тоже хорошая вещь, но примеров простых я для него не нашел пока.
Ну может найдется человек который сможет сказать что такое buf )))
 

Victor

Administrator
Команда форума
Клиент (браузер) шлет GET запрос в ESP8266, а ESP8266 выплевывает в ответ html страницу. Ответ от esp8266 происходит в строке
Код:
client:send(buf)
т.е. buf это строковая переменная, которая содержит в себе html код, который собирается в одну переменную в скрипте ранее путем конкатенации строк оператором ".."
Как-то так.
 

exclus

New member
А в SDK пишут на чистом С, AT прошивка это просто что бы wi-fi - uart мост поднимать, как я понял. Я единственное не понимаю, программа занимает место во флеше которого 512кб, а оперативной памяти всего 20 кб, остается как я понял. Или это не так?
 

Victor

Administrator
Команда форума
А в SDK пишут на чистом С, AT прошивка это просто что бы wi-fi - uart мост поднимать, как я понял. Я единственное не понимаю, программа занимает место во флеше которого 512кб, а оперативной памяти всего 20 кб, остается как я понял. Или это не так?
да, все верно.
флеш - это по аналогии с компьютером типа жесткий диск, а оперативка в самом чипе SoC ESP8266EX
 

JustACat

Moderator
Команда форума
exclus, действительно, если возникают вопросы по коду - лучше бейте их на более мелкие :)
Тогда отвечать будет проще. Я вот LUA не знаю. То есть вообще. Но по аналогии с другими языками многие строчки становятся понятны (хотя может и ошибочно конечно).
Но расписать сразу целую простыню не возьмусь... А по частям более мелким - можно попробовать.
И еще одно, пожалуйста, кроме тега Спойлер не забывайте еще и тег Код. Берем Спойлер, в него Код, а в него уже сам код, тогда не придется вставлять в код ненужные пробелы, чтобы смайлики не получались, и писать потом, чтобы их удалили ;)
Ну и отступы вначале строк в коде тоже обязательны! Без них вообще блоки кода отделять друг от друга - всю голову сломаешь...
Я попытался так почитать, как у вас, все в один уровень - пипец просто :)
Вы не подумайте - это не придирка. Просто чем приятнее вы оформляете свой вопрос, тем больше желания на него ответить ;)

В идеале еще бы к коду номера строк слева... [B]Victor[/B], скажите, пожалуйста, а нельзя ли к блоку Код добавить нумерацию строк слева, как в редакторах кода? Чтобы проще было по коду общаться, сразу писать: "в строке № 23 имеем то и это"?
Вот, например, как вот тут (дождитесь, пока загрузится).
Спасибо!
 
Последнее редактирование:

JustACat

Moderator
Команда форума
Вот, ради интереса попробовал причесать ваш, exclus, код:
Код:
-- Задаем режим работы esp8266, как wi-fi клиент
wifi.setmode(wifi.STATION)
-- Подключаемся к wi-fi сети ("Имясети","пароль")
wifi.sta.config("Homewifi","12345678")
-- Пишем в UART полученные настройки (IP, Mask, Gateway)
print(wifi.sta.getip())
-- Задаем переменную led1 = IO 3 = GPIO 15 (по таблице с API NodeMCU)
led1 = 3
-- Задаем переменную led1 = IO 4 = GPIO 3 (по таблице с API NodeMCU)
led2 = 4

-- переменные обычно "объявляются", а не задаются и далее им "присваивается значение"
-- во многих современных языках это делается сразу в 1 строку
-- причем еще и без явного указания типа (кому-то это по душе. а кто-то яро против, но вот так)
-- так что строка
led2 = 4
-- правильно читается как: объявляем переменную led2 (если это первый раз, где мы к ней обращаемся в коде)
-- и присваиваем ей значение 4 (целочисленного типа в данном случае)

-- Переключаем GPIO в режим выхода
gpio.mode(led1, gpio.OUTPUT)

-- Второй GPIO подключаем к ШИМ каналу указываем в скобках (номер GPIO, частота ШИМ, коэфициент заполнения ШИМ (обратная скважности) по умолчанию)
pwm.setup(led2, 10, 512)

-- Создаем TCP сервер (вызовом команды net.createServer)
-- Результат выполнения этой команды (ссылку на созданный сервер) помещаем в переменную srv
srv = net.createServer(net.TCP)
-- к слову сказать, если сейчас уже присутствует более новая версия функции net.createServer с бОльшим числом параметров,
-- то правильнее будет использовать ее, так как обратная совместимость может рано или поздно и перестать работать.
-- Другими словами, если вы используете более новую прошивку (версию функций и языка), то код нужно приводить в
-- соответствие с ними, если не хотите потом заново весь код обыскивать, когда что-то перестанет вдруг работать.

-- нужно понимать, что выполняются строки справа налево, а точнее, сначала выполняется то, что стоит справа от символа присвоения "="
-- и только потом результат этого выполнения присваивается тому, что записано слева от "="
-- соответственно и читать стоит сначала то, что справа, а потом то, что слева от "="

-- Говорим созданному ранее TCP серверу, который у нас в переменной srv, слушать порт 80
-- При подключении клиентов на этот порт этого сервера будет вызываться наша функция,
-- которая объявлена тут же inline так сказать, с единственным параметром-переменной.
-- В эту переменную будет передана ссылка на созданный для подключенного клиента socket
-- (попадет эта ссылка в переменную conn, назвать эту переменную мы могли и по-другому, при желании)
srv:listen(80, function(conn)

        -- Когда клиент к нам подключится, мы тут же для соединения с ним объявим еще одну функцию
        -- по событию receive (что-то получили от клиента). Будет вызвана наша inline функция,
        -- в которую будет передано 2 параметра: client - думаю, ссылка на клиента, который послал нам запрос,
        -- и request - думаю, что текст запроса в виде строки
        conn:on("receive", function(client, request)
              
                -- объявляем локальную переменную buf, помещаем в нее пустую строку
                local buf = "";
                -- объявляем 5 локальных переменных
                -- _, _, method, path, vars
                -- помещаем в эти 5 переменных результат вызова функции string.find
                -- уверен, что это поиск подстроки "([A-Z]+) (.+)?(.+) HTTP" в строке request
                -- в интернетах легко найти точное описание, набрав нечто вроде LUA string.find в любом поисковике
                local _, _, method, path, vars = string.find(request, "([A-Z]+) (.+)?(.+) HTTP");
                -- если method не nil (думаю, "не пусто", это значит, что мы нашли ту искому подстроку ранее)
                if(method == nil) then
                        -- тогда снова ищем другую подстроку "([A-Z]+) (.+) HTTP" там же в request
                        _, _, method, path = string.find(request, "([A-Z]+) (.+) HTTP");
                end
                -- объявляем еще одну локальную переменную _GET, типа массив, мне кажется
                local _GET = {}
                -- если vars "не пусто" (скорее всего в нее был ранее помещен массив найденных подстрок из request по первому string.find)
                if (vars ~= nil) then
                        -- тогда мы циклом обходим все найденные в vars пары ключ - значение
                        -- не могу 100% объяснить, как это работает - это в справочнике по LUA точно есть
                        for k, v in string.gmatch(vars, "(%w+)=(%w+)&*") do
                                _GET[k] = v
                        end
                end
                -- но с уверенностью на 120% могу сказать, что результатом выполнения этого блока в итоге будет переменная _GET
                -- содержащая в себе массив со всеми запрошенными клиентом параметрами
                -- если клиент запросил, скажем http://наш_IP/page.html?key1=val1&key3=22
                -- то в _GET будет что-то вроде:
                -- key1 = val1 и key3 = 22

                -- следующим блоком мы докидываем в переменную buf немного HTML текста
                buf = buf.."<h1> ESP8266 Web Server</h1>";
                buf = buf.."<p>GPIO0 <a href=\"?pin=ON1\"><button>ON</button></a>&nbsp;<a href=\"?pin=OFF1\"><button>OFF</button></a></p>";
                buf = buf.."<p>GPIO2 <a href=\"?pin=100\"><button>100% light</button></a>&nbsp;<a href=\"?pin=50\"><button>50% light</button></a>&nbsp;<a href=\"?pin=10\"><button>10% light</button></a>&nbsp;<a href=\"?pin=OFF2\"><button>OFF</button></a></p>";
                -- просто разбили его на несколько строк, то есть в итоге в buf будет весь этот текст

                -- объявили еще 2 локальных переменных разом _on и _off, поместили в каждую по пустой строке
                local _on, _off = "",""
                -- ну и дальше проверка параметров в _GET
                -- если в _GET параметр pin имеет значение ON1 (строковое)
                if(_GET.pin == "ON1")then
                        -- тогда вызываем метод gpio.write, который, почему-то мне кажется, выставляет на пин led1 единицу (HIGH)
                        gpio.write(led1, gpio.HIGH);
                elseif(_GET.pin == "OFF1")then -- иначе, если в _GET параметр pin имеет значение OFF1
                        -- тогда вызываем метод gpio.write, который выставляет на пин led1 нолик (LOW)
                        gpio.write(led1, gpio.LOW);
                elseif(_GET.pin == "100")then -- иначе, если в _GET параметр pin имеет значение 100
                        -- ШИМ на пине led2 с заполнением 1023
                        pwm.setduty(led2,1023);
                        -- запустить ШИМ на пине led2
                        pwm.start(led2);
                elseif(_GET.pin == "50")then -- аналогично, но с другим заполнением (50%, видимо)
                        pwm.setduty(led2,512);
                        pwm.start(led2);
                elseif(_GET.pin == "10")then -- аналогично, но с другим заполнением (10%)
                        pwm.setduty(led2,10);
                        pwm.start(led2);
                elseif(_GET.pin == "OFF2")then -- по команде OFF2
                        pwm.stop(led2); -- останавливаем ШИМ
                end -- Конец цикла if

                -- отправляем клиенту, который послал нам этот запрос, в ответ весь тот текст, что ранее поместили в перменную buf
                client:send(buf);
                -- закрываем соединение с этим клиентом
                client:close();
                -- запускаем встроенный сборщик мусора (освобождает всю занятую до этого нашими действиями RAM память)
                collectgarbage();

        end) -- Конец inline функции "conn:on"

end) -- Конец inline функции "srv:listen"

Еще раз предупрежу: LUA не знаю вообще, так что все писал только по логике.
Один раз только глянул в справочник, чтобы узнать, что в LUA комментарии, оказывается, начинаются с непривычного мне вовсе "--" =)

А еще добило то, что, видимо, разрешено, как завершать строки (ну, точнее выполнение операторов) ";", так и не завершать их ими...
В итоге пипец как раздражает то, что в коде присутствует и с ними вызов и без них.
Если язык позволяет оба варианта, то нужно выбрать для себя что-то одно, и использовать это...
И, если язык позволяет использование ";" в конце вызовов операторов, то лучше всегда ";" ставить, так как в большинстве языков программирования он обязателен - то есть проблем будет меньше потом.

Ну и то, что я сказал уже - отступы слева, отделение логических блоков друг от друга и т.п.
Это никак не влияет на выполнение кода, да еще и увеличивает его объем, но зато очень сильно повышает читабельность - а это в программировании очень и очень важно!

Такие дела :)
 
Последнее редактирование модератором:

JustACat

Moderator
Команда форума
Кстати, к слову сказать - посмотрел я на этот LUA, облизнулся в который раз...
Все таки как же на нем просто все это делается, блин!..
На самом деле мне, как web-разработчику, близки такие вот скриптовые языки высокого уровня.
На них все как-то просто и понятно, есть множество встроенных функций, типа поиска подстрок по регулярным выражениям...
Огорчает одно, оно же и останавливает от использования на ESP - глючность и нехватка ресурсов...
Я привык, что написанный правильно код, должен правильно и отрабатывать на 100%. А то получается - все правильно написал, а оно падает через раз... Нет уж, нафиг!
Но какая идея хорошая... Эх...
 

exclus

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