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

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

  1. alp69

    alp69 Форумчанин

    Не, Игорь уже разъяснил, что m или client - в данном случае не принципиально. Да и в первоисточнике (откуда копипастили) тоже так.
    Если показания есть - не проще ли их сразу расталкивать в нужные топики без промежуточных манипуляций с таблицей?
    И по теме - возникло подозрение, что закрытие соединения с брокером после первой публикации происходит после установления следующего соединения, но до начала второй публикации. Не пробовали?
     
    Последнее редактирование: 18 апр 2020
    DCat нравится это.
  2. ИгорьК

    ИгорьК Гуру

    Принципиально, но вопрос этот непростой, требует пояснения.

    Если два - проще. Если 10 - сложно. Ну и вопрос унификации кода.

    Абсолютно верно.

    Но все проблемы от логики этого по отдельности отличного кода. Доберусь до компа - сделаю разбор. Ибо человек работал и это уважаемо.
     
    DCat и alp69 нравится это.
  3. alp69

    alp69 Форумчанин

    В ожидании...
     
  4. ИгорьК

    ИгорьК Гуру

    По коду из поста #966. Все ниже - ИМХО!

    1. Код разделен на два файла. Логика понятна и проистекает она из подходов компилируемых языков, однако в интерпретируемом языке это не совсем верно.

    Рвать код на куски следует с двумя целями:
    - уменьшить объем занимаемой памяти;
    - применять отдельный файл через какое-то время как библиотеку
    .

    Смотрите, вот я за какие-то 30 минут воткнул датчик в проект. При этом не требуется разбираться с даташитом, вспоминать как он работает, etc. Предлагаю вам сразу делать свои микро/макро библиотеки. У меня оно выглядит так:
    В папке лежит файл библиотеки, рядом файл примера как ей пользоваться:

    upload_2020-4-18_13-27-30.png
    Иного смысла выделения кучи функций в отдельный файл нет.
    У вас там три функции? Сделайте три файла, уж наверняка чтение htu21d когда-то потребуется, а будет ли оно сопряжено, например, с такой публикацией на брокер или дерганием wifi - не факт.
    Да более того - со временем вы усовершенствуете и публикацию и коннект к сети, и делать это постоянно расковыривая прошлые файлы - сомнительное удовольстве.

    2. В опросе датчика htu21d можно увидеть tmr.delay(50000) - 50 миллисекунд остановки процессора! Это плохо в любой системе программирования чего угодно, в случае с ESP-8266 это просто запрещено. В этот момент вы ломаете все процессы, которые идут в SDK. Оный разрешает единоличный "захват" процессора чем либо в коде не более чем на 15 мс.
    То есть, вы должны писать код без применения функции задержки. Вообще без применения! И вы не имеете права запускать что-то типа while, for, etc, что будет молотить более 15 мс. Это напросто для начинающего, но такова реальность. В отличие от Си, где вы полностью контролируете процессор, здесь всегда идут неподвластные вам процессы, в первую очередь работа wifi и обеспечение работы асинхронного кода.

    Чтобы понять, как писать код с задержкой (аж 750 мс) - поизучайте топик 9 про ds18b20. Опрос этого датчика следует организовать аналогичным образом, через callback. Понять что это, для чего, как применяется - первая необходимость для написания кода на Lua, JavaScript, ибо это практически всегда асинхронные операции, где линейное мышление не работает. Оно как раз подводит в случае с публикацией на брокер. Код там работает совсем не так как вы ожидаете.

    3. В файле функций и основном файле программы вы заряжаете wifi и включаете его. После чего, все что касается wifi висит в памяти ненужной и никогда не применяемой функцией. Это зря. Более того, разберитесь получше - модуль запоминает настройки и, будучи определенными один раз, обязывают модуль входить в сеть просто при подключении питания без всяких указаний. Иное - для специальных случаев и уж точно не для этого устройства.
    Значит - минус все что касается wifi при предварительной настройке.
     
    Последнее редактирование: 18 апр 2020
    DCat и alp69 нравится это.
  5. DCat

    DCat Нерд

    Игорь, огромное спасибо за разбор! Я примерно так и понял, что мыслю пока не по еспишному. Буду работать над этим. Функций будет побольше чем три ибо жадность безгранична и хочется всегда большего Мысль была сделать отдельной функции отдельный файл. Наверно так и поступлю. Вобщем пошел курить матчасть.
     
  6. ИгорьК

    ИгорьК Гуру

    Это не все, это только половина, чуть позже дальше
     
    DCat нравится это.
  7. alp69

    alp69 Форумчанин

    Допилил счетчик.
    1. Имеется led-дисплей 128×64, на котором отображаются показания счетчиков и IP устройства. Дисплей включается кнопкой на 5 секунд (чтобы не выжигать пиксели).
    3182020184645.jpg
    2. Установка начальных показаний и их коррекция при необходимости - через web-страницу.
    20200418_184910.png
    3. Все данные сохраняются в файлах в spiff. Файлы создаются автоматически при первом запуске устройства. Пишется расход за день, за месяц и общий расход. Весь подсчет и хранение данных ведет устройство (не OpenHAB).
    4. Ежедневно и ежемесячно обнуляются данные по соответствующему расходу (cron).
    5. Для корректной работы cron ежедневно синхронизируется время (sntp).
    Что все это дает:
    Не надо лезть в люк с фонариком, чтобы снять показания счетчиков.
    В ОН отображается "гистограмма", показывающая ежедневный и ежемесячный расход, а также расход в режиме реального времени.
    В дальнейшем можно реализовать отправку по e-mail показаний счетчиков в управляющую компанию в заданный день. Но это уже через ОН.
    Даже если у устройства не будет связи с брокером - подсчет будет все равно вестись без потери данных. При появлении связи с брокером ему будут переданы актуальные данные. А в отсутствие связи посмотреть показания можно на дисплее.

    Некоторые фрагменты кода созданы с использованием наработок @ИгорьК
    Все файлы прошивкой, а также пояснения по работе кода выложу чуть позже.
     
    Последнее редактирование: 18 апр 2020
    ИгорьК нравится это.
  8. Listian

    Listian Нуб

    как я понял на esp32 без танцев с бубнами не залить lua.

    нужно компайлить прошивку на линухе.

    user-frendly на высочайшем уровне :(
     
  9. ИгорьК

    ИгорьК Гуру

    4. Вы подключаете свой файл с функциями через require('functionLib').
    Это можно, но то что вы делаете смысла особого не имеет. Модули и их способ их подключения - это библиотеки но имеющие цель не только "библиотечить", но и работать с памятью. Подключение модуля таким способом необходимо, в том числе, и для будущего их отключения - высвобождения памяти. В сложных проектах вы должны будете постоянно их загружать и выгружать. Хотя и не возбраняется такой подход, но, повторюсь, это бессмысленно.

    5. Про функцию, что публикует ваши топики.

    Публикация работает следующим образом.
    Код (Text):
    client:publish(topic, ...
    - асинхронная функция. Это значит, что получив ее, система не кидается ее исполнять, а вносит в регистр асинхронных функций, доделывает свои дела и лишь тогда занимается ей. Система не гарантирует, что асинхронные функции будут выполняться в том порядке, в котором они "запихиваются" в нее.
    Вы вызываете публикацию дважды двумя вызовами неасинхронной функции funcTab.publicMQTT.
    Эта функция "накидывает" в регистр аж 6 штук асинхронных функций :
    2 х "установить связь", 2 х "опубликовать", 2 х разорвать связь = 6.
    Соответственно, система вызывает их в том порядке, как ей понравится. Шанс отработать все правильно равен математической вероятности выпадения правильного порядка.

    Мы опять столкнулись с асинхроном. Надо знать, что (почти)все асинхронные функции, имеющиеся в вашем распоряжении имеют callback. Установление связи с брокером - асинхронная функция. Она имеет два калбэка, ну так и воткните в тот, что "стреляет" при успешном соединении задачу на отправку сообщения на брокер а закрытие соединения - в callback успешной передачи.

    Код (Lua):
    --функция публикации в MQTT брокер
    function funcTab.publicMQTT (conf, topic, data)
        m = mqtt.Client(conf.mqttClientId, 120, conf.mqttClientUser, conf.mqttClientPwd)
        m:connect(conf.mqttServerIP, conf.mqttServerPort, 0,
            function(client)
                client:publish(topic, data, 0, 0, function(client)
                    print("MQTT sent: " .. topic)
                    client:close();
                end)
            end,
            function(client, reason)
                print("failed reason: " .. reason)
            end)
    end
    Но это очень плохой способ общения с брокером. Объект связи с брокером надо создавать один раз и пусть висит. А так у вас постоянно возникает и он и висит функция, которая его создает. То есть вся эта забава имеет немного смысла, а памяти берет достаточно.
     
    Последнее редактирование: 18 апр 2020
    DCat нравится это.
  10. ИгорьК

    ИгорьК Гуру

    6. О ссылках.
    Здесь довольно сильная засада. Я уже говорил о регистре, куда помещаются ссылки на callback функции.
    В данном случае, client тоже ссылка на callback функцию. Система подразумевает, что она должна быть отработана. Без каких-нибудь действий с этой ссылкой она не удаляется из таблицы.

    Объяснить это не просто на пальцах, здесь лучше смотреть документацию (и это касается всех асинхронов).
    Если в теле асинхронной функции есть ссылка на объект, ее содержащий, операции с этим объектом следует проводить через эту ссылку, в ином случае, каждая операция с объектом "изнутри него" вызывает появление еще одной ссылки.
    Количество ссылок большое, но все таки ограниченно.
    Когда происходят неправильные обработки этого явления - растет реестр ссылок, при этом куча меняется незначительно. Все это ведет к крашу. Но, как мы знаем, система при этом перезагружается и для случаев со всякими датчиками, мы этого даже не замечаем. А для иных случаев, мы обычно предусматриваем всякие защиты от перезагрузок по питанию, и тоже не замечаем :)

    Собственно все изложено здесь: https://nodemcu.readthedocs.io/en/m...does-the-sdk-event-tasking-system-work-in-lua
    Я постарался кратко пересказать, но лучше читать первоисточник.
    Другой пересказ в пункте 38.

    Ну и данные все таки лучше собирать в таблицу, ее проще запостить на брокер, если данных очень много.
    Вроде все :)
     
    Последнее редактирование: 18 апр 2020
    DCat и alp69 нравится это.
  11. alp69

    alp69 Форумчанин

    client - аргумент callback функции?
    Типа идентификатора дочернего процесса, привязывающего его в реестре к родительскому?
     
    Последнее редактирование: 18 апр 2020
    DCat нравится это.
  12. ИгорьК

    ИгорьК Гуру

    Лучше читать первоисточник или просто поступать как рекомендует документация, если лень читать :)

    Кстати, в каждом таймере есть внутренняя ссылка не него, и при уничтожении таймера изнутри в callback функции следует ее прямо уничтожать.

    Код (Lua):
    counter = 0
    counttmr = tmr.create()

    -- Правильно:
    counttmr:alarm(1000, 1, function(t)
        counter = counter +1
        if counter > 10 then
            t:stop()
            t:unregister()
            t, counttmr = nil, nil
            print("All Done!")
        end
    end)

    -- А так не правильно, хотя и работает до поры до времени:
    counttmr:alarm(1000, 1, function()
        counter = counter +1
        if counter > 10 then
            counttmr:stop()
            counttmr:unregister()
            counttmr = nil
            print("All Done!")
        end
    end)
    Это справедливо для всех случаев наличия внутренних ссылок в асинхронных функциях, коей и таймер является.
     
  13. alp69

    alp69 Форумчанин

    Не лень. На https://nodemcu.readthedocs.io не нашел. По крайней мере в разделе mqtt. Или это в книге Иерусалимски?
     
  14. ИгорьК

    ИгорьК Гуру

    upload_2020-4-18_21-34-38.png
     
    alp69 нравится это.
  15. swc

    swc Гик

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

    версия прошивки - 3.0...


    Здесь можно взять рабочий Luac.cross.exe для LFS 256 Кб.
    Для LFS = 128 Кб в bat файле установить константу 0х20000. Но работает и без нее.
     
    Последнее редактирование: 8 май 2020
    ИгорьК нравится это.
  16. swc

    swc Гик

    Игорь, спасибо за пост, очень помог выловить утечку памяти. (Через таймер).
     
    ИгорьК нравится это.
  17. swc

    swc Гик

    Игорь, вопрос: как выгрузить пакет, загруженный по require? Заметил, что после полного удаления объектов программы и самой программы загруженный пакет остается. У меня по меню чередуются вызовы программ, которые в свою очередь, загружают пакеты. Как-то не нашел я как выгрузить (уничтожить) пакет. Может плохо искал, тогда ткните носом, пожалуйста.
     
  18. ИгорьК

    ИгорьК Гуру

    Иерусалимски. Глава 15.

    Грузим:
    Код (Lua):
    -- загрузка с присваиванием ссылки на модуль
    local mod = require "module"
    Удаляем:
    Код (Lua):
    -- выгружаем модуль
    package.loaded["module"] = nil
    -- удаляем ссылку на него
    mod = nil
    Все просто, я не встречал, чтобы это не работало.

    note. У Иерусалимски можно немного другим синтаксисом:
    Код (Lua):
    package.loaded.module = nil
    Но так в некоторых случаях может не выгружаться. Видимо, это баг NodeMCU.

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

    Причин здесь может быть две.

    Либо система кэширует модуль независимо от желания юзверя, такая недокументированная возможность.

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

    Я не стал проводить с этим эксперименты, чтобы докопаться до истины, по той причине, что хоть внешняя выгрузка, хоть организуй ее по внутренней ссылке - реестр в целом не увеличивается по мере работы программы.
     
    Последнее редактирование: 18 май 2020
    swc нравится это.
  19. swc

    swc Гик

    Все отлично. Спасибо.
     
  20. Nikomas

    Nikomas Нерд

    Интересная ситуация: в ESPlorer написал код:
    Код (Lua):

    pin = 2
    pwm.setup(pin, 1000, 512)
    pwm.start(pin)
    tmr.create():alarm(1000, tmr.ALARM_SINGLE, function() pwm.close(pin) end)
     
    Вместо 1сек до закрытия PWM пин открыт ровно в 2 раза дольше, т.е. 2 сек. Это еще полбеды. При отправке и загрузке кода уже из платы ШИМ закрывается ооочень быстро, может 0,5сек. т.е. разницы в строках:
    Код (Lua):

    tmr.create():alarm(50, tmr.ALARM_SINGLE, function() pwm.close(pin) end)
    tmr.create():alarm(1000, tmr.ALARM_SINGLE, function() pwm.close(pin) end)
     
    нет совсем.