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

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

  1. serg3295

    serg3295 Гуру

    Код (C++):
    wifi.start()

    time.initntp("ntp1.stratum2.ru")
    print(time.ntpenabled())

    tt=time.epoch2cal(time.get() )
    print(string.format("%04d-%02d-%02d %02d:%02d:%02d DST:%d", tt["year"], tt["mon"], tt["day"], tt["hour"], tt["min"], tt["sec"], tt["dst"]))

    -- TZ MSK

    time.settimezone('EST-3')
    tl = time.getlocal()
    print(string.format("%04d-%02d-%02d %02d:%02d:%02d DST:%d", tl["year"], tl["mon"], tl["day"], tl["hour"], tl["min"], tl["sec"], tl["dst"]))
     
    Попробовал вручную посылать эти команды в есплорере. Всё нормально работает: 2022-07-10_13-11.png
     
    ИгорьК и obuhanoe нравится это.
  2. obuhanoe

    obuhanoe Гик

    Settimezone влияет на time.getlocal(), но не time.get()?
     
  3. serg3295

    serg3295 Гуру

    Ну, если верить примеру из документации на сайте, то "no locale adjustment"
    2022-07-10_13-26.png
    А вот остальному в этом примере верить нельзя! ;)
    Потому как в первой же строчке модуль time меняется на таблицу со временем, и больше у вас этого модуля нет.
     
    obuhanoe и ИгорьК нравится это.
  4. ИгорьК

    ИгорьК Гуру

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

    Чтобы исправить, нужно заменить самый первый оператор time на что-то другое, tm, например. На это же tm заменить все time во второй строке.
    Это для начинающих, иначе им можно потеряться.
     
    arkadyf и serg3295 нравится это.
  5. obuhanoe

    obuhanoe Гик

    Интересную вещь обнаружил - Ваш код хорошо и правильно иллюстрирует работу.
    Но например мне удобно работать в своем проекте с time in the Unix epoch. Так как вот, значение с TZ хранится time.getlocal() - а это календарь, то чтобы получить вермя Unix epoch, нужно воспользоваться методом time.cal2epoch то данные отображаются без учета TZ
    Снимок.JPG
     
  6. serg3295

    serg3295 Гуру

    Может просто прибавлять сдвиг каждый раз?
    Код (C++):
    -- TZ MSK
    TZ = 10800
    tm = time.epoch2cal(time.get() + TZ)
    print(string.format("%04d-%02d-%02d %02d:%02d:%02d DST:%d", tm["year"], tm["mon"], tm["day"], tm["hour"], tm["min"], tm["sec"], tm["dst"]))
     
    Если TZ в проекте меняется, то сделайте его таблицей.
     
    obuhanoe нравится это.
  7. obuhanoe

    obuhanoe Гик

    Да видимо так придется сделать.
    Странно что так работает и в описании об этом ничего нет.
     
  8. ИгорьК

    ИгорьК Гуру

    55. Проверка действительности данных по времени. Метатаблицы.

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

    Бывает, что какой-то датчик отваливается, но в "мозге" часов его данные остаются. Температура растет, а часы продолжают работать со старой минималкой. Логично проверять время последнего обновления датчика и уничтожать данные, если время превышено. Так, в лоб, в часах и сделано.

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

    Код, разъясненный до степени понятности мне самому:

    Код (Lua):
    do
        -- запускаем модуль времени. (В часах, конечно, идет синхронизация с NTP)
        rtctime.set(0)

        -- Прием таблицы для данных и интервала их действительности в секундах
        maketimecheck = function (t, sec)
            -- нет таблицы - изготовим
            if not t or type(t) ~= 'table' then t = {} end
            -- по умолчанию данные действительны 10 минут
            if not sec or type(sec) ~= 'number' then sec = 600 end

            -- создаем локальную копию таблицы данных
            local _t = t
            -- подменяем исходную таблицу новой
            t = {}
            -- изготавливаем метатаблицу
            local mt = {
                -- занесение данных (таблица, ключ, данные):
                __newindex = function (t, k, v)
                    -- получаем время
                    local tm = (rtctime.get())
                    print('Got Time:', tm)
                    -- к ключу создаем таблицу, первый элемент - данные, второй - время
                    _t[k] = {v, tm}
                end,
                -- доступ к существующему/несуществующему элементу
                __index = function(t, k)
                    -- проверяем интервал текущее время - время занесения данных,
                    -- и возвращаем результат в зависимости от ситуации
                    if not _t[k] then return nil end
                    if (rtctime.get()) - _t[k][2] < sec then
                        print('Good Time')
                        return _t[k][1]
                    else
                        print('Bad Time')
                         _t[k] = nil
                        return nil
                    end
                end,
            }
            setmetatable(t, mt)
            return t
        end
        -- создаем контролируемую таблицу,
        -- время контроля - 10 секунд
        dat = maketimecheck(_ , 10 )

        -- заполняем и читаем данные:
        dat.a = 22
        print(dat.a)

        -- проверяем те же данные через 15 секунд:
        tmr.create():alarm(15000, 1, function(t)
            t:stop()
            t = nil
            print('\n\nNow dat.a:', dat.a)
        end)
    end
    Коротко:
    Код (Lua):
    do
        rtctime.set(0)
        maketimecheck = function (t, sec)
            if not t or type(t) ~= 'table' then t = {} end
            if not sec or type(sec) ~= 'number' then sec = 600 end
            local _t = t
            t = {}
            local mt = {
                __newindex = function (t, k, v)
                    local tm = (rtctime.get())
                    _t[k] = {v, tm}
                end,
                __index = function(t, k)
                    if not _t[k] then return nil end
                    if (rtctime.get()) - _t[k][2] < sec then
                        return _t[k][1]
                    else
                         _t[k] = nil
                        return nil
                    end
                end,
            }
            setmetatable(t, mt)
            return t
        end
        dat = maketimecheck(_ , 10 )
        dat.a = 22
        print(dat.a)
    end
    Еще примеры.
     
    Последнее редактирование: 24 июл 2023
    serg3295 нравится это.
  9. dmitrij2023

    dmitrij2023 Нуб

    Скажите, пожалуйста, что такое "luac.cross" и где это можно скачать? В инструкциях, что я нашел, всё предлагается делать из docker.
     
  10. ИгорьК

    ИгорьК Гуру

    dmitrij2023 нравится это.
  11. dmitrij2023

    dmitrij2023 Нуб

  12. alp69

    alp69 Форумчанин

    @ИгорьК, добрый день!
    Можно взглянуть на суть переделки? Если я правильно рассуждаю, то m:close() нужно переместить внутрь функции error()? И вызывать не перед m:connect, а колбеком из функции error() при наступлении события для ее (errоr) исполнения? И сбросить "reason"?
    Не так,
    Код (Lua):
    function connect_broker()
      m:close() -- закрываем соединение
      m:connect(broker, port,false, subscribe, error()
    ....
    )
     
    а так?
    Код (Lua):
    function connect_broker()
      m:connect(broker, port,false, subscribe,error()
      m:close() -- закрываем соединение
      reason = nil
    ....
    )
     
    Причем с задержкой не менее keepalive, потому, что "...клиент не может повторно использоваться сразу после этого вызова, а только после того, как сработает «автономный» обратный вызов." (гуглоперевод)
     
    Последнее редактирование: 18 сен 2022
  13. ИгорьК

    ИгорьК Гуру

    Привет :) Рад тебе.

    Сейчас нет, ибо не удобно код втыкать с телефона. Завтра, скорее. Но суть в том, чтобы не допустить двойного вызова повторного соединения.
    Калбэк может даже ничего не закрывать, а может и закрыть. Его вызов:
    1. проверяет наличие таймера повторного соединения.
    2.1. Нет таймера - создает таймер и в его колбэке вызывает соединение с брокером.
    2.2. Есть таймер - перезапускает его или вообще ничего не делает.

    И все.
     
  14. alp69

    alp69 Форумчанин

    Взаимно )))
    Спасибо!
     
  15. ИгорьК

    ИгорьК Гуру

    Код (Lua):
    -- Глобальные дела:
    dat = {}
    dat.broker = "192.168.100.100"
    dat.clnt = 'ampermeter8'
    dat.port = 1883
    dat.pass = 'superpasswd'


    do
        local subscribe, merror, msg, askcon
        function subscribe(con)
            dat.broker = true
            con:subscribe(dat.clnt.."/com/#", 0)
            con:publish(dat.clnt..'/state', "On", 0, 1)
            print("Subscribed to "..dat.clnt.."/com/# Heap: "..node.heap())
        end
     
        askcon = function()
            -----------------------------------------------------
            -- Вот и весь "секрет". Оно не пропустит второй вызов:
            if not contmr then -- Делаем ГЛОБАЛЬНЫЙ таймер:
                contmr = tmr.create()
                contmr:alarm(5000, tmr.ALARM_AUTO,  function(t)
                    if not dat.broker then
                        if (wifi.sta.getip()) then
                            m:connect(dat.brk, dat.port, false, subscribe, merror)
                        end
                    else
                        t:stop()
                        t:unregister()
                        t, contmr = nil, nil
                        print('con Timer Killed!')
                    end
                end)
            end
            collectgarbage()
        end
        function merror(con, reason)
            dat.broker = false
            print('MQTT Broker Error! Reason:', reason)
            do local ct = 0; for k,v in pairs(debug.getregistry()) do ct = ct + 1 end; print('Length = '..ct, 'Heap = '..node.heap()) end
            askcon()
        end
        function msg(con, top, dt)
            if not killtop then killtop = {} end
            top = string.match(top, "/(%w+)$")
            print('Got', top, dt)
            if top and dt then
                table.insert(killtop, {top, dt})
                if not dat.analiz then
                    dofile("mqttanalize.lua")
                end
            end
        end
        m = mqtt.Client(dat.clnt, 60, dat.clnt, dat.pass, 0, 1024)
        m:lwt(dat.clnt..'/state', "Off", 0, 1)
        m:on("offline", merror)
        m:on("connfail", merror)
        m:on("message", msg)
        print('Connect to', dat.brk, 'Heap', node.heap())
        askcon()
    end
    В приложении рабочий файл для ESP-8266.
     

    Вложения:

    • mqttset.zip
      Размер файла:
      1.012 байт
      Просмотров:
      87
    Последнее редактирование: 19 сен 2022
    alp69 нравится это.
  16. dmitrij2023

    dmitrij2023 Нуб

    Здравствуйте. Расширяю количество портов esp8266, прошивка NodeMCU lua, собирал на сайте. С PCF8574 всё понятно и заработало сразу. А с PCF8575 не могу понять "как 2 байта переслать". Отправляю один байт - ничего не происходит, хотя возможно я как-то не так проверяю состояние ног, там открытый коллектор на выходе, но показания мультиметра не меняются. Выложите, пожалуйста, код на lua, если кто-нибудь с ним работал.
     
  17. ИгорьК

    ИгорьК Гуру

    Долго, друже, ты будешь ждать ответа, если не выложишь свой код и не объяснишь детально что тебе надо.

    Если совсем в общих чертах, то чтобы переслать два байта, нужно их через запятую перечислить в функции:
    Код (Lua):
    i2c.write(id, byte1, byte2)
     
    dmitrij2023 и DetSimen нравится это.
  18. dmitrij2023

    dmitrij2023 Нуб

    Разобрался, ошибка была в другом месте, в функции, которая модифицирует бит передаваемого байта.
    Для PCF8575 обязательно передавать 2 байта, с одним байтом ничего не делает:
    i2c.write(id, byte1, byte2)
    обязательно
     
  19. dmitrij2023

    dmitrij2023 Нуб

    Собрал прибор на ESP8266, всё работает, всем доволен, всем спасибо. Для настройки выпустил хвост ком порта на полметра, подключаюсь к ноутбуку, настраиваю, надо не часто.
    Меня посетила такая идея, для реализации которой моих знаний недостаточно, однако я предполагаю, что такое уже придумали до меня и сделали. У меня к контроллеру подключена sd карта, в постоянной памяти я не ограничен, на контроллере есть wifi. А можно ли на нем же, на луа поднять tcp/udp сервер, на один из портов которого перенаправить весь ввод-вывод ошибок и обычных сообщений, что идут в компорт? Логика примерно такая: контроллер стартует, запускается инит, запускается сд карта, запускается сервер, запускается функция-процедура обработчик всех сообщений, её задача маршрутизировать их или в файл или, если подключен клиент, в клиента. Если интерпретатор ловит ошибку - текст ошибки отправляется в эту процедуру - здесь я не понимаю - если луа машина словила исключение, то всё, только перезагрузка, другую порцию кода выполнить не можно? - после отправки/записи текста контроллер перезагружается как обычно. Клиент подключается, обработчик из сервера пообщавшись с приложением клиента понимает, что это то, что надо (типа клиент первым отправляет get/post или udp пакет с "hello, esp!", сервер в ответ hi и значит всё хорошо) клиент может отправлять два запроса - "че там" и "сделай", долбит раз в секунду, например, запросами - чё там?!, чё там?! сервер в ответ шлет то, что у него в буфере попало из консоли или из файла - как не перегрузить память и откуда собирать информацию не просто, но не сильно принципиально, как мне кажется. Клиент на компьютере отображает полученное на экране, в случае активности со стороны пользователя - отсылает запрос "сделай" и текст луа к выполнению, есплорер, как я понял, именно код луа посылает и интерпретирует ответы. Сервер, получив запрос "сделай", делает его и отправляет на выход ответ, в т.ч. пустую строку, если в ответ будет пустая строка. У меня затруднение только с перенаправлением ввода-вывода в контроллере, клиента вполне осилю на autoit.
     
  20. serg3295

    serg3295 Гуру

    Если подключаться по telnet'у, то весь ввод/вывод идёт через TCP, в том числе и ошибки. Значит перенаправлять можно.

    Если самому ловить команды, прибывающие откуда-то, то для обработки исключений можно использовать простейший код
    Код (C++):
    do
      local func, err = (loadstring or load)("return function() i = i + 1 end") ---@diagnostic disable-line:deprecated
      print(func, err)
      if (type(func) == "function") then
        local ok, sum = pcall(func)
        print(ok, sum)
        if ok then
          i = 5
          sum(i)
          print(i)
        else
          print("Execution error:", sum)
        end
      else
        print("Compilation error:", err)
      end
    end
     
    Посмотрите примеры на сайте nodeMCU - telnet и luaOTA, может оттуда возьмёте идеи. Я сам такую вещь не делал.
     
    dmitrij2023 нравится это.