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

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

  1. tpolimer

    tpolimer Нерд

    Еще раз спасибо, за проделанный труд!
     
  2. ИгорьК

    ИгорьК Гуру

    36. Про отправку многих данных на брокер еще раз.

    Пропустил я замечательную функцию next(table, key), что последовательно выдает на-гора из таблицы очередную пару ключ/значение.

    Применив ее и рекурсию в callback можно сделать маленький файл отправки данных из таблицы на брокер, а именно с пояснениями:

    Код (Lua):
    -- dat - таблица с данными для отправки на брокер
    if not dat then dat = {} end
    dat.heap = node.heap()
    -- три локальные переменные должны быть объявлены заранее
    -- k и v - ясно для чего
    -- punow - для рекурсивной функции объявление переменной заранее обязательно
    local k, v, punow
    punow = function ()
        -- Вызов next() с отсутствующим ключом (первый вызов) возвращает
        -- первую пару ключ/значение их таблицы, далее пары возвращаются последовательно
        k,v = next(dat,k)
        -- если есть пара
        if k then
            -- если значение true/false - его надо переделать в текстовое
            if type(v) == 'boolean' then
                v = v == true and 'true' or 'false'
            end
            print(k, v)
            -- публикуем данные
            m:publish(dat.clnt.."/"..k, v, 2, 0, function()
                punow()
            end)
        -- нет пары - самоликвидируемся
        else
            k,v,punow = nil, nil, nil
        end
    end
    -- поехали!
    punow()
    И без пояснений - файл pubmqtt.lua запускаем, когда надо отправить таблицу dat на брокер:
    Код (Lua):
    do
        local k, v, punow
        punow = function ()
            k,v = next(dat,k)
            if k and wifi.sta.getip() and dat.broker then
                if type(v) == 'boolean' then
                    v = v == true and 'true' or 'false'
                end
                print(k, v)
                m:publish(dat.clnt.."/"..k, v, 2, 0, function()
                    punow()
                end)
            else
                k, v, punow = nil, nil, nil
            end
        end
        punow()
    end
    Наблюдаем за памятью по причине рекурсии:
    upload_2019-2-19_14-46-51.png

    Ну а если отправлять всю рабочую таблицу необходимости нет - делаем выборку и запускаем рекурсию:

    Код (Lua):
    -- Чтобы лишний раз...
    if dat.publ then return end
    do
        dat.publ = true
        dat.heap = node.heap()
        local punow, k, v
        local no = 0
        -- Список ключей для отправки:
        local topub = {'t00A5', 'homeNow', 't008A', 'heap'}

        punow = function ()
            no = no + 1
            if no <= #topub and wifi.sta.getip() and dat.broker then
                k = topub[no]
                v = dat[k]
                if type(v) == 'boolean' then
                    v = v == true and 'true' or 'false'
                end
                print(k, v)
                m:publish(dat.clnt.."/"..k, v, 2, 0, punow)
            else
                k, v, punow, no, topub = nil, nil, nil, nil, nil
                dat.publ = false
            end
        end
        punow()
    end

    Или так, молча и все:

    Код (Lua):
     -- Файл mqttpub.lua
    if dat.publ then return end
    do
        if dat.broker == true then
            dat.publ = true
            local k, v, punow
            punow = function(k)
                k, v = next(dat, k)
                if k then
                    if type(v) == 'boolean' then
                        v = v == true and 'true' or 'false'
                    end
                    m:publish(dat.clnt.."/"..k, v, 2, 0, function(cl)
                        punow(k)
                    end)
                else
                    k, v, punow = nil, nil, nil
                    dat.publ = false
                end
            end
            punow()
        end
    end
    Выбирайте!
     
    Последнее редактирование: 12 мар 2019
  3. Fel_92

    Fel_92 Нуб

    Уважаемый ИгорьК, разбираю Ваш труд по созданию и программированию умного счётчика воды (Б.спасибо за него), возник вопрос по MQTT брокеру, вернее работе скрипта, связанной с публикацией туда данных:
    Если во время работы скрипта падает сервер с брокером (ну например, установленный на малинке), то выполнение кода останавливается на попытке публикации данных и не возобновляется соединение с брокером. Хотелось бы какой-никакой автономности.
    На другом коде (не Вашем) скрипт вообще вылетает с ошибкой "PANIC ...."
    Как этого избежать? Прошу сильно не пинать, т.к. только начал увлекаться темой умного дома ;)
     
  4. Fel_92

    Fel_92 Нуб

    К примеру при запуске скрипта при выключенном сервере, где установлен брокер MQTT, вылетает ошибка:
    "PANIC: unprotected error in call to Lua API (mqtt_sonoff.lua:56: not connected)"
    а строка 56: "m:publish(MQTT_RelayTopicPath..MQTT_Relay_ID, "1", 0, 1, function(conn)" причём какое бы "качество обслуживания не ставил"....
     
  5. ИгорьК

    ИгорьК Гуру

    Смотрите, падение брокера ИМЕННО во время публикации явление чрезвычайное редкое. Если сервер "лежит" - публикации просто быть не должно. Публикация разрешается двумя проверками - наличием wifi и флага коннекта с брокером.

    Однако и само падение, ИМХО, не так страшно - устройство должно перегрузиться, да и шут с ним.
    К сожалению, модуль MQTT в NodeMCU - второй по глючности и жалобам, как я читал где-то на гите разработчиков. Это нужно просто принять и кодить именно с учетом этой особенности. Если лежит брокер - какая разница - работает устройство снабжения его информацией или нет.

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

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

    Где-то я сталкивался с такой проблемой, когда в тестовом режиме публиковал данные на брокер eclipse. Поищите это решение в теме.

    П.П.С. Если вопрос касается именно пункта 36 этих записок, то это лишь модель и только модель ПРОГРАММНОГО РЕШЕНИЯ снабжения асинхронной функции данными! Здесь нет никаких проверок доступности брокера!!!
     
    Последнее редактирование: 28 фев 2019
    alampw нравится это.
  6. Fel_92

    Fel_92 Нуб

    Нет это не по п. 36, а по "Счетчик расхода воды. Пошагово." Красиво и понятно расписали, разложили всё по блокам и модулям, всё логично! Да, кстати, в конце статьи Вы указали: "... поэтому, если оно покажется вам полезным, как его довести до конца и запустить в работу - смотрите здесь."
    Перехожу по ссылке, а там умный водомер уложен в один скрипт, причём в том скрипте, к примеру, соединение с WIFI приводится ещё по старому коду Lua, т.е. не через таблицу с данными сетки???
    Я так понимаю, что модульные скрипты посвежее будут ;))

    А насчёт падения брокера ИМЕННО во время публикации - Вы правильно сказали, что это чертовски невероятная штука... Однако горечь момента в том, что ошибка выскакивает при обращении к уже упавшему брокеру! Т.е. например, хочу я управлять выключателем света через сенсорную кнопку-ESP-реле и к тому же ещё и через Мажордомо и MQTT, т.е. по любому в скетче организуется обратная связь о состоянии светильника через брокер, который стоит на малине в локальной сети дома. Всё включается и управляется и дистанционно даже... И тут "нейтрино прошило ардуино", в смысле Raspberry ;)) и надо уже извлекать из места установки ЕСПеху и комментировать обращения к брокеру на время починки малинки... А если это система отопления с сервоприводами....

    Кстати по поводу контроля wifi более-менее понятно, а вот " флага коннекта с брокером" - приведите пример кода, пожалуйста! Какое его значение при наличии-отсутствии связи с брокером и как он, этот флаг узнаёт о наличии этой связи?
    И вообще, тогда может вынести брокер за пределы локалки, например на тот же iot.eclipce.org
     
  7. ИгорьК

    ИгорьК Гуру

    Fel_92 нравится это.
  8. ИгорьК

    ИгорьК Гуру

    Это не рабочий проект а учебный!!!! Я уже это где-то писал. Я его не тестировал под нагрузкой. Цель его - учить и мыслить! Эта тема - учебная, а не тема готовых проектов.
     
  9. Fel_92

    Fel_92 Нуб

    Так я на нём и учусь!!!! За что отдельный респект!
     
  10. naz

    naz Нерд

    Кто какой публичный брокер использует? iot.eclipse.org не отвечает. cloudmqtt.com требует аутенфикации- не получилось
    Код (C++):
     m = mqtt.Client(Hello_214702968, 120, nazmosq, *****)
    ...
    m:connect('m24.cloudmqtt.com', 17656, 0, 0,...
     
     
  11. swc

    swc Гик

    Вопрос: кто-нибудь работал с LFS?
     
  12. naz

    naz Нерд

    Удалось с broker.mqtt-dashboard.com с произвольным логином и паролем установить подписку.
    ...
    upd 14/03/2019 ожил iot.eclipse.org...
     
    Последнее редактирование: 14 мар 2019
  13. Домосед

    Домосед Нерд

    Мой первый пост.
    В первую очередь, хочется поблагодарить ИгорьК за его труд. Очень сильно помог начать разбирать Lua и перейти с Ардуино IDE.
    Разбирая код по MQTT, у меня возник вопрос. В коде встречается функция с параметрами con и conn. Что это такое? Откуда берётся и почему именно таким образом обозначается? Это зарезервированные имена? В общем, два дня пытался найти ответы, но так и не разобрался.
    Если не сложно, то разъясните пожалуйста.
    Вот пример кода (думаю, что знакомый всем по стандартным примерам)
    Код (Text):
    mqtt = mqtt.Client("client_id", 120, "username", "password")

    mqtt:on("connect", function(con) print ("connected") end)
    mqtt:on("offline", function(con) print ("offline") end)

    -- on receive message
    mqtt:on("message", function(conn, topic, data)
      print(topic .. ":" )
      if data ~= nil then
        print(data)
      end
    end)

    mqtt:connect("hostname", port, 0, function(conn)
      print("connected")
      -- subscribe topic with qos = 0
      mqtt:subscribe("my_topic",0, function(conn)
        -- publish a message with data = my_message, QoS = 0, retain = 0
        mqtt:publish("my_topic","my_message",0,0, function(conn)
          print("sent")
        end)
      end)
    end)
     
  14. ИгорьК

    ИгорьК Гуру

    Это обычная функция. Аргументы можно называть как угодно: con, conn, или Иван роли не играет.
    Если вам эта переменная не нужна (а она не нужна) замените её в скобках (перечни аргументов) на нижнее "_" .
     
    Последнее редактирование: 16 мар 2019
  15. Домосед

    Домосед Нерд

    Спасибо. Меня и озадачивало то, что вроде как переменная есть, но нигде не используется. Потому не мог понять для чего это делается. Получается, что это имя нужно исключительно ради самого имени, а функция ради разового выполнения того, что находится внутри её?
    В частности, в этой части кода будет просто выполнена функция печати текста "connected" после того, как произойдёт соединение?
    Код (C++):
    mqtt:on("connect", function(con) print ("connected") end)
     
  16. ИгорьК

    ИгорьК Гуру

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

    ИгорьК Гуру

  18. baarmaley

    baarmaley Нерд

    Добрый день!

    В примере, который Вы приводите написан с ошибкой, оттуда и происходит "утечка". А именно, в данной лямбде:
    Код (Lua):
     local function closec()
           sck:close()
    end
    Вы захватываете объект из внешней области видимости, что ведет к следующему: у сокета появляется лямбда, которая захватывает объект в котором сама же хранится и поэтому счетчик ссылок будет ненулевой, и естественно сборщик мусора, его не удалит.

    Если обратить внимание на документацию: https://nodemcu.readthedocs.io/en/master/en/modules/net/#netsocketon
    то прототип для лямбды имеет следующий вид:
    Код (Lua):

    function(net.socket[, string])
     
    Исходя из этого правильный каллбек будет выглядеть следующим образом:
    Код (Lua):
     local function closec(sck)
           sck:close()
    end
     
    ИгорьК нравится это.
  19. ИгорьК

    ИгорьК Гуру

    Ни разу не встречал термин "лямбда" у Lua. Давайте не будем путать новичков. Либо пруф.

    Не стал бы и проводить аналогий между Си и Lua, хотя бы последний и написан на Си.

    Что касается содержания вашего поста, то... ну пусть будет так. Практика гораздо сложнее.

    Я не увидел ссылок на источники вашего утверждения.

    Термин "прототип" в Lua также не применяется.

    Лямбда - из Си, прототип - из JavaScript. (Насколько мне известно. Наверно еше есть языки. Но это не Lua)

    Прошу прощения, с учётом сказанного, я не готов ни рассматривать ваш пост, ни спорить с ним. Пусть будет так.
     
    Последнее редактирование: 2 апр 2019
  20. ИгорьК

    ИгорьК Гуру

    Заметьте, я не спорю. И ссылка на объект действительно передаётся в callback. Но вот то, что вызов глобального объекта в calback ведёт к утечке памяти - вещь недоказанная.

    Практика показывает что это не так.