• Уважаемые посетители сайта esp8266.ru!
    Мы отказались от размещения рекламы на страницах форума для большего комфорта пользователей.
    Вы можете оказать посильную поддержку администрации форума. Данные средства пойдут на оплату услуг облачных провайдеров для сайта esp8266.ru
  • Система автоматизации с открытым исходным кодом на базе 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 , но у меня не получилось там сделать форматирование как у вас( ,выделить комментарии другим шрифтом). И спасибо большое за ваш ответ. Я уже понял, что код немного староват. Я обязательно обновлю все функции на новые.
 
Сверху Снизу