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

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

  1. ИгорьК

    ИгорьК Гуру

    Самое распространенное явление - DS18b20. Там между запросом и съемом показаний по максимуму 750 мс.
    Все лень посмотреть, как задержка в Даллас библиотеке сделана.

    Но здесь Lua. Здесь, в основном, все на Callback функциях и таймерах.

    Хотел сделать показ на корутинах, но что-то громоздким показалось. В реальности, при показе погоды каждый шаг смещения по дисплею установлен 35 мс, умножается на количество точек, что надо протащить, и возвращается диспетчеру. Все работает.
     
    Последнее редактирование: 31 июл 2025
  2. Asper Daffy

    Asper Daffy Иксперд

  3. ИгорьК

    ИгорьК Гуру

    Слышу впервые. Он к МК как-то относится? Евгений, я сам DIY, который назавтра забывает что сегодня делал. Это хобби. Это ни хорошо и ни плохо. Это чуть лучше чем лежать на диване, но не так круто, как всходить на Эверест. У всех хобби разные.
     
  4. Asper Daffy

    Asper Daffy Иксперд

    Не понял вопроса. Если про реализацию, то там платформа пофиг, там кросс-компилятор из Т++ в С++.
     
  5. ИгорьК

    ИгорьК Гуру

    Это и есть вопрос новобранца :) Я давно развлекаюсь с Lua, а не с С++.
     
  6. ИгорьК

    ИгорьК Гуру

    ---------------------------------
    Очень дешевый сенсор расстояния LD2402.

    Пытаюсь понять что из него можно достать полезного таким кодом:

    Код (Lua):

    do
        -- Если DEBUG - вывод от 'print' иначе молчок
        PRT = function(...) if DEBUG == true then print(...) end end
        PRT1 = function(...) if DEBUG == 1 then print(...) end end
        PRT2 = function(...) if DEBUG == 2 then print(...) end end
        local prt = PRT or print
        local prt1 = PRT1 or print
        local prt2 = PRT2 or print

        -- Установка порта
        TX = 17
        RX = 16

        MAXDIST = 150                -- Максимальная дистанция для реагирования
        DELTA_DIST = 30              -- Изменение дистанции, когда следует реагировать

        if not dat then dat = {} end -- таблица, для совместимости с другим кодом

        if TX and RX then
            uart.setup(2, 115200, 8, uart.PARITY_NONE, uart.STOPBITS_1, { tx = TX, rx = RX })
            uart.start(2)
            dat.uart2 = true -- Чтобы в других местах знать, что порт установлен
            prt('Set UART at TX, RX', TX, RX)
            TX, RX = nil, nil
        end

        if not wth then wth = {} end -- Таблица, куда собираем необходимые данные

        wth.distance = 0             -- Начальная дистанция
        wth.showdata = 0             -- Чтобы поменьше выводить в консоль, переменная с коророй идет сравнение
        wth.rawdist = 0
        local startUART = false      -- Для улова первого читаемого байта из UART
        local makedata, uartstring, uartdist, rawdist
        local delta = DELTA_DIST
        local maxdist = MAXDIST or 200                        -- Локалим переменную максимпльной дистанции для реагирования
        MAXDIST, DELTA_DIST = nil, nil
        uartstring = ''                                       -- Для сбора текста из UART

        makedata = function(dt)                               -- Обработчик байтов из UART
            if string.byte(dt, 1) ~= 0x0D then                -- Пока не знак возврата каретки - собираем данные в строковую переменную
                uartstring = uartstring .. dt
            else                                              -- Обрабатываем собранные данные
                prt('uartstring:', uartstring)                -- Выводим в консоль что ы итоге получилось
                startUART = false                             -- Говорим UART что будем работать заново
                uartdist = string.match(uartstring, ':(%d+)') -- Выгрызаем числовую составляющую
                prt('Got Distance:', uartdist)

                uartstring = ''
                rawdist = tonumber(uartdist) -- Стринг в число

                if rawdist and wth.rawdist ~= rawdist then  -- Изменения в таблицу
                    -- Если больше 5 см - на печать
                    if math.abs(wth.rawdist - rawdist) > 5 then prt1(wth.rawdist) end
                    wth.rawdist = rawdist
                end

                -- Если ошибка или расстояние больше чем надо - хватит!
                if not rawdist or rawdist > maxdist then
                    wth.showdata = 0
                    return
                end                                                        
                prt('Now Distance:', uartdist)
                wth.distance = rawdist

                -- Реагируем, когда изменение больше чем DELTA_DIST
                if math.abs(wth.distance - wth.showdata) > delta then
                    print('Distance is', wth.distance)
                    wth.showdata = wth.distance
                    -- dofile 'mqttpub.lua'
                end
            end
        end

        uart.on(2, "data", 1,
            function(data)
                local bt = string.byte(data, 1) -- bt - один байт из UART
                prt2(string.format("bt = 0x%02X", bt))
                prt2(data)
                if startUART == false and bt ~= 0x0A then -- Ждем перевода строки
                    return
                elseif startUART == false then            -- Дождались - разрешаем чтение
                    startUART = true
                    return
                end
                makedata(data) -- Отправляем байт в переработку
            end, 0)
    end

     

    Это просто скетч для размышления. Чтобы увидеть все что происходит, дать в консоль "DEBUG = true", убрать данные - "DEBUG = false", так как датчик гонит в UART данные раз в 500 мс. и все ими забивается.

    upload_2025-9-2_11-2-57.png

    Почему датчик по умолчанию считает, что расстояние в данном случае 272 см - вообще не понятно. Перед ним - шкаф в полутора метрах. 145 - 150 - показывает когда я к нему подхожу сантиметров на 90.

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

    ПС. Датчик лежит на столе, пока с ним "разборки". Оказывается, от расположения верх/низ кое-что зависит.
     
    Последнее редактирование: 3 сен 2025
  7. ИгорьК

    ИгорьК Гуру

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

    ИгорьК Гуру

    Вот такую держалку для датчика нарисовал. Файлы Компаса в приложении.

    upload_2025-9-4_9-12-41.png

    upload_2025-9-4_9-25-35.png

    Но напечатал не такую :)

    upload_2025-9-4_9-14-36.png


    Чет она мне напомнила, и я ее перечертил. Хотя все равно чет напоминает :)

    Все в приложении.

    Датчик в такой коробочке стабилизировался, теперь можно размышлять о его деятельности :)
    В верхней части отверстие под фоторезистор. Хочу сделать датчик движения/освещенности.
    Таких датчиков продается пруд пруди, но здесь еще будет выход на реле и, возможно, само реле.

    Алгоритм, полагаю, будет посложнее, чем, обнаружил движение/свет - сообщил. Но это пока замыслы.

    В общем, это просто "держалка" датчиков, а не проект.
     

    Вложения:

    • LD2402.zip
      Размер файла:
      448,8 КБ
      Просмотров:
      11
  9. Credo

    Credo Нуб

    Скажите вообще реально запустить без краша системы? Что пробовал: агресивную очистку памяти, разнести по времени вплоть до 05,10,15,20. Убрать 1н индекс ( хотя не плохо бы 2). Разные сервера получения. Парсинг txt, json, html - вообще мрак крашится. Http сервер не нашел Устанавливал пропустить если размер памяти меньше . Парсинг 1 раз в 60 минут. Монитор показывает
    11:12:33.181 -> [MEM] Weather_str_start: 27192
    11:12:33.181 -> [MEM] Weather_str_end: 27088
    11:12:42.575 -> [MEM] Weather_end: 29824
    11:13:12.592 -> [MEM] Weather_end: 29824
    11:13:33.430 -> [MEM] Weather_str_start: 26416
    11:13:33.430 -> [MEM] Weather_str_end: 26312 на переменные и тд не смотрите , это один из файлов проекта.
     

    Вложения:

    • weather.txt
      Размер файла:
      21,7 КБ
      Просмотров:
      5
  10. ИгорьК

    ИгорьК Гуру

    Здесь не реально.
    Вы не в ту тему вопрос задали, тут про язык Lua.

    ========

    Что касается стабильности, то у меня часы и данные с погодного сервера парсят, и с narodmon, и отправляют данные, и http сервер для настройки поднимают. То есть стабильность достижима.
     
    Последнее редактирование: 21 ноя 2025
  11. ИгорьК

    ИгорьК Гуру

    MAX7219 семисегментный дисплей. ESP32!
    Вот такой:
    max7219md.png

    Модуль:
    Код (Lua):
    -- _max7219md.lua
    local M = {}
    local SSPin = 18
    local DECOD = 0x09
    local INTENS = 0x0A
    local SCAN = 0x0B
    local SHUT = 0x0C
    local DIST = 0x0F
    local ESP = 'esp32'
    local device

    local function sendByte(register, data)
        gpio.write(SSPin, 0)
        device:transfer(string.char(register, data))
        gpio.write(SSPin, 1)
    end

    function M.setup(esp, mosi, sspin, sclk, miso)
        local busmaster
        local lsclk = sclk or 19
        local lmosi = mosi or 23
        local lmiso = miso or 26
        local lesp = esp or ESP

        if lesp == 'esp32' then
            busmaster = spi.master(spi.HSPI, { sclk = lsclk, mosi = lmosi, miso = lmiso })
        elseif lesp == 'esp32s3' then
            -- busmaster = spi.master(spi.SPI2, { sclk = lsclk, mosi = lmosi, miso = lmiso }, 0)
            busmaster = spi.master(spi.SPI2, { sclk = lsclk, mosi = lmosi }, 0)
        else
            print('esp32s3 or esp32 only')
            return
        end

        local device_config = { mode = 0, freq = 10000000 }
        device = busmaster:device(device_config)

        SSPin = sspin or SSPin
        gpio.config({ gpio = SSPin, dir = gpio.OUT })
        gpio.write(SSPin, 1)

        sendByte(SCAN, 7)
        sendByte(DECOD, 0x00)
        -- sendByte(DECOD, 0xFF) -- Scan All Digits
        sendByte(DIST, 0)
        sendByte(INTENS, 1)
        sendByte(SHUT, 1)
        for a = 1, 8 do
            sendByte(a, 0)
        end
        lsclk, lmosi, lmiso, lesp = nil, nil, nil, nil
    end

    function M.setIntensity(intensity)
        sendByte(INTENS, intensity)
    end

    function M.clear()
        for i = 1, 8 do sendByte(i, 0) end
    end

    function M.shutdown(sd)
        local sdRg
        sdRg = sd and 0 or 1
        sendByte(SHUT, sdRg)
        sdRg = nil
    end

    M.sb = sendByte
    return M

    Применять.
    Код (Lua):
    MOSI = 1
    CS = 2
    SCLK = 3
    -- MISO = 5
    ESP = 'esp32s3' --  или 'esp32', тогда править ноги выше
    max7219 = require('_max7219md')
    font = require('font7seg')

    -- function M.setup(esp, mosi, sspin, sclk, miso)
    max7219.setup(ESP, MOSI, CS, SCLK)
    max7219.clear()
    max7219.setIntensity(0)
    SCLK, MOSI, MISO, CS, ESP = nil, nil, nil, nil, nil

    WriteMAX = function(s)
        local d = {}
        local dc = 0
        for i = 1, #s do
            dc = font.GetChar(string.sub(s, i, i))
            if dc == 0x80 then
                if #d == 0
                then
                    d[1] = dc
                else
                    d[#d] = d[#d] + 0x80
                end
            else
                d[#d + 1] = dc
            end
        end
        if #d < 8 then
            for c = 1, 8 - #d do table.insert(d, c, 0x00) end
        end
        for k = 1, 8 do
            max7219.sb(9 - k, d[k])
        end
        if #d > 8 then
            dat.buff = d
        end
    end

    ------


    WriteMAX('21:23 000')

    Фонт в приложении.
     

    Вложения:

    • font7seg.zip
      Размер файла:
      638 байт
      Просмотров:
      2
  12. ИгорьК

    ИгорьК Гуру

    upload_2026-3-17_11-28-15.png
    Датчик качества воздуха PMS9003M для pm2.5 применяется в бытовых воздушных фильтрах, например Xiaomi. У меня будет работать в часах.
    При включении датчик входит в активный режим и начинает сыпать в UART данные. У датчика, кроме UART, есть нога отправки в сон и нога перезагрузки.

    Наработка MTTF по даташиту 15000 часов, что чуть меньше двух лет. Он, конечно, не сравняется с SPS30, у которого заявлено время работы (Life Time) 10 лет, но тоже неплохо.

    В сон датчик можно отправлять командой с UART, поэтому ногу сна можно не задействовать. Если включать датчик на 30 секунд через четверть часа, то, глядишь, проработает больше двух лет.

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

    Итак, модуль. Если на ESP32 памяти не хватает (фигасе!), то в модуле много разбора ответов по UART от датчика, их все можно выкинуть.
    Код (Lua):
    -- pms9003mm.lua - модуль для работы с датчиком PMS9003M на ESP32 (Lua 5.3)
    local mn ='['.. ... .. ']'
    local M = {}
    local prt = PRT or print
    -- UART ID и параметры
    local UART_ID = UART or 2
    local BAUDRATE = 9600

    local TX_PIN = TX or 17
    local RX_PIN = RX or 16
    TX, RX = nil, nil

    -- Переменные парсера
    local data_buffer = {}

    -- Команды Сенсору
    local COMTB = {}
    COMTB.active = "\x42\x4D\xE1\x01\x00\x01\x71"
    COMTB.passive = "\x42\x4D\xE1\x00\x00\x01\x70"
    COMTB.data = "\x42\x4D\xE2\x00\x00\x01\x71"
    COMTB.sleep = "\x42\x4D\xE4\x00\x00\x01\x73"
    COMTB.wake = "\x42\x4D\xE4\x00\x01\x01\x74"

    -- Инициализация UART
    function M.init(tx, rx)
        TX_PIN = tx or TX_PIN
        RX_PIN = rx or RX_PIN
        uart.stop(UART_ID)
        uart.setup(UART_ID, BAUDRATE, 8, uart.PARITY_NONE, uart.STOPBITS_1, { tx = TX_PIN, rx = RX_PIN })
        uart.start(UART_ID)
        uart.on(UART_ID, "data", 1, function(data)
            M.parseByte(string.byte(data))
        end, 0)
        prt(mn, "UART initialized for PMS9003M")
    end

    -- Парсер одного байта
    function M.parseByte(byte_val)
        if #data_buffer >= 32 then data_buffer = {} end
        if #data_buffer == 0 and byte_val == 0x42 then
            data_buffer[1] = byte_val
            return
        end

        if #data_buffer == 1 then
            if byte_val == 0x4D then
                data_buffer[2] = byte_val
            else
                data_buffer = {}
            end
            return
        end

        if #data_buffer >= 2 then
            data_buffer[#data_buffer + 1] = byte_val
            if #data_buffer >= 8 and #data_buffer == data_buffer[4] + 4 then
                M.processCompleteFrame(data_buffer)
            end
        end
    end

    -- Обработка полного кадра данных
    function M.processCompleteFrame(buf)
        if buf[4] == 28 then -- Длина кадра для данных
            M.processSensorData(buf)
        else
            M.processCommandResponse(buf)
        end
    end

    -- Обработка данных измерений
    function M.processSensorData(buf)
        -- Проверка контрольной суммы
        local checksum_received = buf[31] * 256 + buf[32]
        local checksum_calculated = 0
        for i = 1, 30 do
            checksum_calculated = checksum_calculated + buf[i]
        end

        if checksum_received ~= checksum_calculated then
            prt(mn, "!!! Data frame checksum mismatch" )
            return
        end

        -- Чтение значений частиц
        local pm1_0_cf1 = buf[5] * 256 + buf[6]   -- PM1.0 (CF=1)
        local pm2_5_cf1 = buf[7] * 256 + buf[8]   -- PM2.5 (CF=1)
        local pm10_cf1  = buf[9] * 256 + buf[10]  -- PM10  (CF=1)

        -- local pm1_0_atm = buf[11] * 256 + buf[12] -- PM1.0 (атмосферные условия)
        -- local pm2_5_atm = buf[13] * 256 + buf[14] -- PM2.5 (атмосферные условия)
        -- local pm10_atm  = buf[15] * 256 + buf[16] -- PM10  (атмосферные условия)

        -- Вызов пользовательского callback'а (если задан)
        if M.onDataReceived then
            M.onDataReceived({
                pm1_0_cf1 = pm1_0_cf1,
                pm2_5_cf1 = pm2_5_cf1,
                pm10_cf1 = pm10_cf1,
                -- pm1_0_atm = pm1_0_atm,
                -- pm2_5_atm = pm2_5_atm,
                -- pm10_atm = pm10_atm,
            })
        end
        data_buffer = {}
    end

    -- Обработка ответа на команду
    function M.processCommandResponse(buf)
        -- Проверка контрольной суммы для ответа
        local checksum_received = buf[7] * 256 + buf[8]
        local checksum_calculated = 0
        for i = 1, 6 do
            checksum_calculated = checksum_calculated + buf[i]
        end

        if checksum_received ~= checksum_calculated then
            prt(mn, "!!! Command response checksum mismatch")
            return
        end

        -- Анализ ответа
        local command = buf[5]
        local response_data = buf[6]

        if command == 0xE1 then
            -- Ответ на команду смены режима
            if response_data == 0x00 then
                prt(mn, "response: Set to Passive Mode")
            elseif response_data == 0x01 then
                prt(mn, "response: Set to Active Mode")
            else
                prt(mn, "!!! response: Unknown mode response")
            end
        elseif command == 0xE4 then
            -- Ответ на команду сна
            if response_data == 0x00 then
                prt(mn, "response: Sleep activated")
            else
                prt(mn, "[!!! response: Unknown response")
            end
        elseif command == 0xE2 then
            -- Ответ на запрос данных (обычно данные приходят отдельно)
            prt(mn, "!!! response: Data request acknowledged")
        else
            prt(mn, string.format("!!! PMS9003M unknown command response: 0x%02X", command))
        end
        data_buffer = {}
    end

    function M.Ask(com)
        uart.write(UART_ID, COMTB[com])
    end
    return M

    Как опрашивать. Загружаем модуль, инициируем UART, вытаскиваем датчик из сна, даем прочихаться 30 секунд, запрашиваем данные, отправляем датчик в сон, выгружаем модуль.
    Данные помещаются в глобальную таблицу "wth":
    Код (Lua):
    -- Подключение модуля
    local prt = PRT or print
    local mn = '[_Sample_pms9003m]'
    if not wth then wth = {} end

    local wtmr = tmr.create()
    local worktb = { { 1000, 'wake' }, { 30000, 'data', }, { 1000, 'sleep' } }
    local pointer = 1
    PMS = require("_pms9003mm")
    -- Установка функции обратного вызова
    PMS.onDataReceived = function(data)
        prt(mn, "Received new data!")
        for k, v in pairs(data) do
            wth[k] = v
        end
    end
    -- Инициализация UART
    PMS.init(17, 16)
    wtmr:alarm(worktb[pointer][1], tmr.ALARM_SEMI, function(t)
        prt(mn, pointer, worktb[pointer][2])
        PMS.Ask(worktb[pointer][2])
        pointer = pointer + 1
        if pointer > #worktb then
            t:stop(); t:unregister(); t = nil
            package.loaded._pms9003mm = nil
            PMS = nil1
        else
            t:stop()
            t:interval(worktb[pointer][1])
            t:start()
        end
    end)
    wtmr:start()


    -- По умолчанию работает в активном режиме – данные будут поступать автоматически каждые ~2 секунды
    -- PMS.Ask('passive')
    -- PMS.Ask('data')
    -- PMS.Ask('active')
    -- PMS.Ask('sleep')
    -- PMS.Ask('wake')

    При старте устройства переключим датчик в пассивный режим и отправим в сон:
    Код (Lua):
    do
        local mn = '[__pms9003SetSleep]'
        local prt = PRT or print
        local setpassive = true
        local txpin = TX or 17
        local rxpin = RX or 16
        local UARTT = UART or 2
        local init = function(txpin, rxpin, uarrt)
            uart.stop(UARTT)
            uart.setup(UARTT, 9600, 8, uart.PARITY_NONE, uart.STOPBITS_1, { tx = txpin, rx = rxpin })
            uart.start(UARTT)
            prt(mn, "UART " .. UARTT .. " initialized for PMS9003M")
        end

        init(txpin, rxpin, UARTT)

        tmr.create():alarm(1000, tmr.ALARM_AUTO, function(t)
            if setpassive then
                uart.write(UARTT, "\x42\x4D\xE1\x00\x00\x01\x70") -- Passive mode
                setpassive = false
                prt(mn, "PMS9003M At Passive Mode")
            else
                uart.write(UARTT, "\x42\x4D\xE4\x00\x00\x01\x73") -- Sleep
                prt(mn, "PMS9003M Go Sleep")
                t:stop(); t:unregister(); t = nil
                uart.stop(UARTT)
            end
        end)
    end
     
     

    Вложения:

    • PMS9003M.pdf
      Размер файла:
      954,8 КБ
      Просмотров:
      1
    Последнее редактирование: 19 мар 2026
    serg3295 нравится это.