ESP-8266/ESP32 NodeMCU Lua: азы программирования.

Тема в разделе "ESP8266, ESP32", создана пользователем ИгорьК, 25 июл 2017.

  1. ИгорьК

    ИгорьК Гуру

    Хотя. Спасибо за мысль. Очень хорошее замечание. Дело не в вызове глобального объекта, а, видимо, несколько в другом.

    Я заметил(и где-то писал здесь), что если в стандартной callback функции в скобках не задать имя переменной, даже если она в теле функции вам не нужна, программа ИНОГДА может вести себя неадекватно вплоть до перезагрузки с выдачей неясного сообщения о причине.

    Причем, согласно стандарту языка, такое указание не обязательно.

    В общем, мне сложно найти этому объяснение, но практически, лучше не забывать именовать переменные в стандартных callback всегда. Здесь это было забыто, хотя в данном случае проблем не вызывает. Спасибо за указание.

    Вместе с тем, это не вело к утечке памяти. Проверьте. Практика - критерий истины.
     
    Последнее редактирование: 2 апр 2019
  2. ИгорьК

    ИгорьК Гуру

    И вообще, вы, к сожалению, не разобрались с моим кодом - в нем нет обращения к глобальной переменной. Посмотрите внимательнее. И меня запутали.

    Все равно - спасибо за придание ускорения мыслям.
     
  3. ИгорьК

    ИгорьК Гуру

    Если говорить о цитируем вами коде, то там нет обращений к глобальному сокету, а есть неправильная обработка интерпретатором upvalues - нелокальных переменных.
    Прямое их уничтожение прекращает утечку памяти
     
  4. baarmaley

    baarmaley Нерд

    Лямбда это общепринятый термин, обозначающий анонимную функцию.
    https://en.wikipedia.org/wiki/Anonymous_function

    Я не провожу аналогий между Си и Lua

    Что есть "практика"?

    Это общепринятый термин.
    https://en.wikipedia.org/wiki/Function_prototype

    В си - лямбд нет
    https://en.wikipedia.org/wiki/Anonymous_function#C_(non-standard_extension)

    Прототипы из джава скирпта не совсем то :)

    Я хорошо понял Ваш код. Я еще раз посмотрел его. Аналогичная проблема c функцией send, тоже захват сокета из внешней области видимости.

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

    У сокета хранится ссылка на лямбду, в лямбде хранится ссылка на сокет.

    В итоге у обоих объектов, счетчик ссылок равен 1
     
  5. baarmaley

    baarmaley Нерд

    О каком "глобальном сокете" идет речь?
    Это не та проблема.
     
  6. ИгорьК

    ИгорьК Гуру

    Это описка - писал с мобильного.

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

    Дискутировать на эту тему - заниматься флудом.
    Я не знаю что такое "внешняя область видимости" в Lua. Вы знаете - пруф.

    Пруф.

    Более точно - как оказалось , вы пытаетесь начать дискуссию на тему, в которой не варились. Это флуд.
    И, соответственно, предупреждение.
     
  7. baarmaley

    baarmaley Нерд

    upvalue это механизм создания замыканий.

    О проблеме, я сообщил. О ее причинах тоже.

    Если Вы считаете, что это баг, то добро пожаловать в issue на github
     
  8. ИгорьК

    ИгорьК Гуру

    Сейчас я вынес вам официальное предупреждение. Ваше предыдущее сообщение показало, что Lua вы начали изучать час назад. Предлагаю покинуть эту тему. Независимо от содержания следующего сообщения, оно окажется во флудилке с очередным предупреждением. Несколько предупреждений - бан.
     
  9. ИгорьК

    ИгорьК Гуру

    Всем, кто сейчас подумал, что я злой, жестокий и пользуюсь властью бесконтрольно (а вы абсолютго правильно подумали), вот ссылка на первоисточник:

    upload_2019-4-2_18-57-0.png

    Выше - книга Иерусалимски в оригинале.

    upvalues - это нелокальные (но и не глобальные) переменные. С их участием могут создаваться замыкания, а могут и не создаваться. Чаще - создаются. Но это свойство upvalues, а не цель.

    Но что касается реализации этого вопроса в Lua NodeMCU, то интерпретатор не всегда правильно уничтожает upvalues при выходе из локальных функций. Об этом говорится в документации, а именно: https://nodemcu.readthedocs.io/en/m...-implemented-when-programming-for-the-esp8266

    upload_2019-4-2_19-5-2.png

    ===========================

    К чему это все? Это не ответ предыдущему оратору, а всем тем, у кого возникнут сомнения в моих дальнейших действиях или сочувствие к несправедливо угнетенному. Ну и узелок на память.
     

    Вложения:

    Последнее редактирование: 3 апр 2019
  10. ИгорьК

    ИгорьК Гуру

    Почему пост #618 (1) вызывает "зашкаливающее количество ненависти" и (2) что полезного из него можно вынести.
    (Только для тех, кому реально нравится NodeMCU Lua и кто хочет в нем разобраться. Поклонники Ардуино-языка и кристально чистейшего Си скорее всего не поймут)

    UPВ: baarmaley Абсолютно прав! Там ошибка!

    1. Ненависть.
    Автор к предмету обсуждения применил определения "лямбда" (как синоним анонимной функции) и "прототип функции" к описанию ее аргументов.

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

    Итак, автор не "чуть ошибся", назвав аллигатора крокодилом, он назвал слона - бегемотом с длинным носом и большими ушами. Как на это должен отреагировать тот, кто реально видел слона?

    1.1. Проще всего понять неприменимость "прототипа функции". Lua - нетипизированный язык, и следовательно, "объяснять" компилятору (которого тоже нет) что ожидать от некоторой функции бессмысленно.

    Я предупреждал, что привыкание к логике и подходам NodeJS/Lua вызывает стойкое неприятие и частичную амнезию языка Си, что и произошло: для меня "прототип" связан с наследованием в родственном JS, а про необходимость разжевывать каждый чих в Си я уже подзабыл.

    Но, повторюсь, - в Lua нет типизации, и лезть в него с прототипами - верх некомпетенции.

    1.2. В обсуждаемом вопросе потери памяти речь идет не об анонимной функции с аргументами:
    Код (Lua):
    function(net.socket[, string])
    а именно о callback функции, которая в данном случае представлена в виде анонимной, но может быть и ссылочной, смотрим сюда. Внимание! Callback может быть анонимным а может и не быть!

    Увидеть в приведенном мануалом коде именно анонимную функцию, не то чтобы некомпетентность, но точно - непрофессионализм.

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

    1.3. О upvalues я уже писал выше.

    1.4. "В вашем коде ошибка" в связи с неправильной переработкой аргументов callback.

    Посмотрим на мой код, и тот который приведен в качестве "правильного", вот он:
    upload_2019-4-4_13-43-58.png

    А теперь обозрим весь мой пример. Здесь приведу его только в части сервера:
    Код (Lua):
    srv = net.createServer(net.TCP, 15)
    local function receiver(sck, data)
        data = nil
        local function closec()
            sck:close()
        end
        local function send()
            if file.open("page.lua", "r") then
                repeat
                    local line = file.readline()
                    if line then
                        line = expand(line)
                        sck:send(line)
                    end
                until line == nil
                file.close()
            else
              sck:close()
            end
            print('on sent:', node.heap())
        end
        sck:on("sent", closec)
        send()
    end
    srv:listen(80, function(conn)
      conn:on("receive", receiver)
    end)
    И пояснения к нему в виде картинки:

    upload_2019-4-4_13-54-34.png

    1. Указывается ссылочный callback, который
    2. как и указано в документации, принимает внутреннюю ссылку,
    3. которая создает для функций внутри себя (то есть внутри функции receiver) upvalue sck для всех вложенных функций, и
    4. при вызове closec() без аргумента! обрабатывается именно upvalue.

    Таким образом, "ненависть" есть следствие подхода автора со своим уставом в монастырь и, как результат, разговор не о том.

    Когда я говорил о "практике" - в том же цитируемом примере льющего память маленького сервера внесены коррективы, и память не льется. Причину я объяснял там же - создание (неочевидного) upvalue функцией receiver (шарик 2 на картинке выше) приводит к неправильной его обработке сборщиком мусора, строго в соответствии с документацией, на которую и ссылался.

    Все. Вопрос закрыт, флейм будет удален.
    Приношу искренние извинения.

    (ЧЮ у меня такое)
     
    Последнее редактирование: 23 апр 2019
  11. ИгорьК

    ИгорьК Гуру

    2. Польза.
    Польза, безусловно, может извлекаться из всего, если раскинуть мозгами в разные стороны и как можно шире.

    Автор привел очень полезную ссылку, на которую я раньше не обращал ни свое ни ваше внимание. Причина тому - глубокое убеждение, что создание серверов на NodeNCU есть сакс, что подтверждается и авторами Lua в том же разделе:
    "If you are trying to implement a user-interface or HTTP webserver in your ESP8266 then you are really abusing its intended purpose. When it comes to scoping your ESP8266 applications, the adage Keep It Simple Stupid truly applies."

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

    В частности, в неопубликованных здесь часах, что принимают данные от MQTT брокера, но, одновременно, работают с протоколом TCP по запросу погоды иногда случаются немотивированные перезагрузки без объяснения причин, лишь указанием того что память кончилась, при одновременной куче эдак в 20000 и не в момент tcp запроса. Поскольку причина мной не найдена - часы я не публикую.

    В общем, есть над чем подумать, за что автору большое спасибо.

    P.S. А вот в коде MQTT обнаруживается та ошибка, что (не)указал baarmaley. Спасибо! Буду исправлять.

    P.P.S. А какой инструмент мы получили!!!! Спасибо еще раз, тов. baarmaley

    Код (Lua):
    for k,v in pairs(debug.getregistry()) do print (k,v) end
    А еще лучше просто считать записи в регистре:
    Код (Lua):
    do
        local count = 0
        for _ in pairs(debug.getregistry()) do count = count + 1 end
        print('\n\ncount =', count)
    end

    P.S. Прикольный код:
    Код (Lua):
    function foo(n)
        local cnt = 0
        for _ in pairs(debug.getregistry()) do  cnt = cnt + 1 end
        print('N:'..n..' Reg: '..cnt, 'Heap: '..node.heap())
        tmr.wdclr()
        if n > 1 then return foo(n-1) end
    end
    foo(5000)
     
    Последнее редактирование: 16 апр 2019
  12. swc

    swc Гик

    Хороший пост. Глубокий анализ и самокритичность. Добавлю: с появлением LFS утверждение, цитата: "When it comes to scoping your ESP8266 applications, the adage Keep It Simple Stupid truly applies." теряет актуальность.
     
    ИгорьК нравится это.
  13. ИгорьК

    ИгорьК Гуру

    Сначала добью указанную выше тему, затем займёмся LFS.
     
  14. swc

    swc Гик

    LFS мне нравится. У меня несколько тысяч строк кода заливаются в один образ и свободной оперативной памяти - вагон. Теперь хватает на все, даже остается. Большой плюс LFS - привычный стиль программирования.
     
    ИгорьК нравится это.
  15. ИгорьК

    ИгорьК Гуру

    А что со скоростью?
     
  16. swc

    swc Гик

    По ощущениям - быстрее. Код откомпилирован и выполняется сразу из FLASH памяти, поэтому не теряется время на загрузку файла в ОЗУ и его компиляцию. Раньше, когда была цепочка вызовов файлов, притормаживало. Сейчас - летает.
     
    ИгорьК нравится это.
  17. ИгорьК

    ИгорьК Гуру

    Честно признаюсь, с быстрого прочтения не понял как это делается, а времени серьёзно выделить так и не сумел.

    Может, накидаете заметку? Одно дело делаем!
     
  18. swc

    swc Гик

    Попробую.
     
    ИгорьК нравится это.
  19. swc

    swc Гик

    37. Пример создания LFS варианта исполняемых файлов

    Создание системной прошивки

    Создать прошивку на сайте https://nodemcu-build.com/index.php
    Для этого:
    - выбрать нужные модули, драйверы и шрифты дисплеев,
    - выбрать параметры LFS:
    LFS size - 64 -128 KB,
    SPIFFS base - 1024 KB
    SPIFFS size - "all ree flash"
    - запустить на выполнение.
    Файл прошивки придет на указанный почтовый адрес.

    Создание пользовательской прошивки LFS
    Создать папку с файлами проекта (например: myProject)
    Поместить в эту папку файлы:
    Код (C++):

    _init.lua,
    LFS_dummy_strings.lua,
    - свой файл с текстом программы, например: файл myProgramm.lua.
    Файл myProgramm.lua содержит следующие строки:

    Код (C++):

    function hello()
      print(“Hello from LFS! It’s working!)
    end
    Далее - варианты
    Вариант 1:
    Порядок работы:
    1. Создать файл *.zip для папки проекта (например: myProject.zip)
    2. Создать прошивку LFS для файла myProject.zip на сайте https://blog.ellisons.org.uk/article/nodemcu/a-lua-cross-compile-web-service/#comment-191
    Для этого выбрать кнопку “REMOTE LUAC.CROSS(MASTER)”
    Файл прошивки будет иметь имя myProject.img и будет загружен в папку загрузки компьютера.

    Вариант 2 - автономный:
    Здесь взять рабочий Luac.cross.exe для LFS 256 Кб.
    Порядок работы:
    1. Загрузить свои файлы в каталог "in_files".
    2. Запустить bat файл.
    3. Забрать образ LFS для заливки в область SPIFF

    Далее общая процедура для обоих вариантов:
    1. Загрузить файл myProject.img в файловую систему SPIFFS любым загрузчиком, например ESPlorer. Появится сообщение: > Uploading to ESP file myProject.img...Success
    2. Переместить файл myProject.img из области SPIFF в область LFS путем выполнения команды: node.flashreload(‘myProject.img’).
    Появится сообщение: > LFS region updated.Restarting.

    Справка: Размер LFS на сайте https://nodemcu-build.com увеличен до 256 Кб.
    Версия прошивки - 3.0..

    Содержимое файлов init.lua и_ init.lua
    Файл init.lua должен содержать следующие строки:
    Код (C++):

    -- File: init.lua
    --
    local fi=node.flashindex
    pcall(fi and fi'_init')
    dofile "_init.lua"
    Файл _init.lua должен содержать следующие строки:
    (Уже содержит, поэтому можно не суетиться)
    Код (C++):
    -- File: _init.lua
    local index = node.flashindex
    --
    local lfs_t = {
      __index = function(_, name)
      local fn_ut, ba, ma, size, modules = index(name)
      if not ba then
      return fn_ut
      elseif name == '_time' then
      return fn_ut
      elseif name == '_config' then
      local fs_ma, fs_size = file.fscfg()
      return {lfs_base = ba, lfs_mapped = ma, lfs_size = size,
      fs_mapped = fs_ma, fs_size = fs_size}
      elseif name == '_list' then
      return modules
      else
      return nil
      end
      end,
      __newindex = function(_, name, value)
      error("LFS is readonly. Invalid write to LFS." .. name, 2)
      end,
      }
    --
    local G=getfenv()
    G.LFS = setmetatable(lfs_t,lfs_t)
    --
    package.loaders[3] = function(module) -- loader_flash
      local fn, ba = index(module)
      return ba and "Module not in LFS" or fn
    end
    --
    G.module  = nil  -- disable Lua 5.0 style modules to save RAM
    package.seeall = nil
    --
    local lf, df = loadfile, dofile
    G.loadfile = function(n)
      local mod, ext = n:match("(.*)%.(l[uc]a?)");
      local fn, ba  = index(mod)
      if ba or (ext ~= 'lc' and ext ~= 'lua') then return lf(n) else return fn end
    end
    --
    G.dofile = function(n)
      local mod, ext = n:match("(.*)%.(l[uc]a?)");
      local fn, ba  = index(mod)
      if ba or (ext ~= 'lc' and ext ~= 'lua') then return df(n) else return fn() end
    end

    Вызов функций
    Далее можем вызвать функцию:
    Код (C++):

    require('myProgramm')  -- загрузить модуль
    hello()  -- вызвать функцию
    Результат:
    Код (C++):
    > Hello from LFS! It is working!
    Увеличение свободного места в RAM
    Увеличить свободное место в RAM можно путем размещения строковых констант LUA и пользовательских приложений в область LFS.

    Для этого необходимо получить информацию о строковых константах, которые размещены в RAM путем выполнения скрипта:
    Код (C++):

    do
      local a=debug.getstrings'RAM'
      for i =1, #a do a[i] = ('%q'):format(a[i]) end
      --print ('local preload='..table.concat(a,',\n'))
      print ('local preload='..table.concat(a,','))
    end
    На экран будет выведен список строковых констант. Этот список необходимо скопировать и поместить в конец файла LFS_dummy_strings.lua:
    Код (C++):

    -- File: LFS_dummy_strings.lua
    local preload = "?.lc;?.lua", "/\n;\n?\n!\n-", "@init.lua", "_G", "_LOADED",
    "_LOADLIB", "__add", "__call", "__concat", "__div", "__eq", "__gc", "__index",
    "__le", "__len", "__lt", "__mod", "__mode", "__mul", "__newindex", "__pow",
    "__sub", "__tostring", "__unm", "collectgarbage", "cpath", "debug", "file",
    "file.obj", "file.vol", "flash", "getstrings", "index", "ipairs", "list", "loaded",
    "loader", "loaders", "loadlib", "module", "net.tcpserver", "net.tcpsocket",
    "net.udpsocket", "newproxy", "package", "pairs", "path", "preload", "reload",
    "require", "seeall", "wdclr", "not enough memory", "sjson.decoder","sjson.encoder",
    "tmr.timer","","%q",",","==RUN==","==STOP==","=stdin","@_init.lua","ALARM_SINGLE",
    "INPUT","Lua 5.1","OLED_test.lua","RAM","_VERSION","_init.lua","alarm","concat",
    "config","flashreload","format","gpio","init","init.lua","kv","local preload=",
    "mode","mqtt.socket","myProject.img","pcall","pullup","read","table","u8g2.display",
    "w","websocket.client"
    Далее можно наслаждаться работой с LUA.
    Нюанс: у меня файл *.img размером более 48 кб в область LFS не загружается (и не только у меня).
    Надеюсь, этот глюк будет исправлен командой NodeMCU.

    Возможное решение - компиляция файлов компилятором Luac.cross в среде Docker и других средах с опцией адресной загрузки и последующей записью по нужному адресу без использования node.flashreload. Пока не разобрался с компиляцией, если кто разобрался - отпишитесь.

    Источники

    https://nodemcu.readthedocs.io/en/latest/lfs/
    https://blog.ellisons.org.uk/article/nodemcu/a-lua-cross-compile-web-service/#comment-191

    P.S. Важное дополнение от форумчанина vklimk

    Цитата:
    "В дополнение к посту Пример создания LFS варианта исполняемых файлов
    Чтобы не зависеть от веб-сервиса, который собирает LFS-образы, и иметь возможность поиграть с ключами кросс-компилятора lua, да, и, ускорить процесс сборки, я собрал luac.cross для своей Win7 x64 для текущей ветки master.
    Пользоваться просто:
    1. Распаковываем архив luac.cross.zip на своем компьютере.
    2. В папку myimage помещаем файлы, которые хотим записать в LFS-образ (lua-скрипты, например).
    3. Запускаем build_LFS_image.bat
    4. Забираем LFS-образ в папке output (собираются сразу два образа - один для float-прошивки, второй - для integer-прошивки)

    Я уже попробовал LFS и это реально работает. Написал свои скрипты - при их запуске без LFS node.heap() показывает 5-6КБ и при малейшем возрастании нагрузки - перезагрузка из-за нехватки памяти. С LFS - node.heap() показывает 28КБ и скрипты работают без сбоев (пока что :) ). "
    Конец цитаты.
    Большое спасибо!
     

    Вложения:

    Последнее редактирование: 16 май 2020
    ИгорьК нравится это.
  20. ИгорьК

    ИгорьК Гуру

    Бесценно!!! Спасибо! Тучу времени сэкономили, буду изучать.