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

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

  1. ИгорьК

    ИгорьК Гуру

    Если необходимо подключаться к нескольким разным точкам доступа в разных местах, то:
    Код (Lua):
    do
        local aptb = {
            { 'AP_01', 'passwd_01' },
            { 'AP_02', 'passwd_02' },
            { 'AP_03', 'passwd_03' }
        }

        local cfg = {}
        cfg.ssid = ""
        cfg.pwd = ""
        cfg.save = true
        cfg.auto = true

        local function listap(t)
            wifi.setmode(wifi.STATION)
            for bssid, v in pairs(t) do
                local ssid = string.match(v, "([^,]+)")
                print(ssid)
                for k, v in pairs(aptb) do
                    if v[1] == ssid then
                        cfg.ssid = ssid
                        cfg.pwd = v[2]
                    end
                end
            end
            print('\n\nSet SSID as ' .. cfg.ssid)
            wifi.sta.config(cfg)
            wifi.sta.connect()
        end
        wifi.sta.getap({}, 1, listap)
    end
    -- Ответ на вопрос в личку.
     
    Последнее редактирование: 19 мар 2024
    obuhanoe нравится это.
  2. ИгорьК

    ИгорьК Гуру

    Если необходимо подключаться к нескольким разным точкам доступа в разных местах, и к тому же там есть одинаковые SSID с разным с разным уровнем сигнала, то:
    Код (Lua):
    do
        print ('Clear config is', wifi.sta.clearconfig())
        local aptb = {
            { 'AP_01', 'passwd_01' },
            { 'AP_02', 'passwd_02' },
            { 'AP_03', 'passwd_03' }
        }

        wifi.setmode(wifi.STATION)
        local cfg = {}
        cfg.ssid = ""
        cfg.pwd = ""
        cfg.save = true
        cfg.auto = true

        local myssidtb = {}
        local power = -100

        local function listap(t)
            for bssid, v in pairs(t) do
                local ssid, rssi = string.match(v, "([^,]+),([^,]+)")
                rssi = tonumber(rssi)
                print(ssid, bssid, rssi)
           
                for k, v in pairs(aptb) do
                    if v[1] == ssid then
                        if rssi > power then
                            myssidtb = {v[1], bssid, k}
                            power = rssi
                        end
                    end
                end
            end
            cfg.ssid = myssidtb[1]
            local nbssid = ""
            for k = 1, #myssidtb[2] do
                nbssid = nbssid .. string.upper(string.sub(myssidtb[2], k, k) )
            end
            cfg.bssid = nbssid
            cfg.pwd = aptb[myssidtb[3]][2]
            print('\n\nSet SSID as ' .. cfg.ssid.. ' and BSSID is '.. cfg.bssid.. 'and passwd is '..cfg.pwd )
            wifi.sta.config(cfg)
            wifi.sta.connect()
        end
        wifi.sta.getap({}, 1, listap)
    end
     
    Последнее редактирование: 19 мар 2024
  3. ИгорьК

    ИгорьК Гуру

    56. ESP32. Читаем SDS011 PM2.5:

    Код (Lua):

    uart.setup(2, 9600, 8, uart.PARITY_NONE, uart.STOPBITS_1, {tx = 17, rx = 16})
    uart.start(2)
    local d25 = 0
    local d10 = 0

    local gotRAW = {}
    local startUART = false

    local function ptrANSW()
        local crc = 0
        for i = 3, 8 do crc = crc  + gotRAW[i] end
        crc = bit.band(crc, 0xFF)
        -- print(string.format('\t\tcrc = %02X',crc))
        if crc ~= gotRAW[9] then print('Bad CRC'); gotRAW = {}; startUART = false return end

        d25 = ((gotRAW[4]*256 ) + gotRAW[3])/10
        d10 = ((gotRAW[6]*256 ) + gotRAW[5])/10
        print('\n\t\t\tPM2.5 = '..d25..' μg/m3,  PM10 = '.. d10..' μg/m3')

        gotRAW = {}
        startUART = false
        -- dofile('analizeMTRF.lua')
    end

    uart.on(2,"data",1,
        function(data)
            local bt = string.byte(data, 1)
            -- print(string.format("bt = %02X", bt))
            if startUART == false and bt ~= 0xAA then return
            elseif startUART == false then startUART = true end
            gotRAW[#gotRAW+1] = bt
            if gotRAW[2] and gotRAW[2] ~= 0xC0 then gotRAW = {}; startUART = false end
            if #gotRAW == 10 then ptrANSW() end
    end, 0)
     
     
    Последнее редактирование: 21 апр 2024
    serg3295 нравится это.
  4. obuhanoe

    obuhanoe Гик

    У Вас реализация для ESP8266, ниже для ESP32
    Код (Lua):
    do
        local l_wifi = {
             { 'AP_01', 'passwd_01' },
             { 'AP_02', 'passwd_02' }
        }
         -- стартуем Wifi: при дисконекте (callback произойдет)
        wifi.start()

         local scfg = {}  
         scfg.auto = true
         scfg.save = true
         scfg.ssid = ""
         scfg.pwd = ""

         local function connect_wifi(err, arr)
            if err then
              print(err)
            else
              wifi.mode(wifi.STATION, true)
              for i, ap in ipairs(arr) do
                print(i, ap.ssid, ap.channel,ap.bssid,ap.rssi,ap.auth,ap.bandwidth)
                for k, v in pairs(l_wifi) do
                    if v[1] == ap.ssid then
                        scfg.ssid = ap.ssid
                        scfg.pwd = v[2]
                    end
                end
              end
              print('\n\nSet SSID as ' .. scfg.ssid)
              wifi.sta.config(scfg, true)
              wifi.sta.connect()
            end
        end
       
         wifi.sta.scan({}, connect_wifi)

        wifi.sta.on("got_ip", function(ev, info)
            print("IP now:", info.ip)
        end)
     
        wifi.sta.on("disconnected", function(ev, info)    
               print("WIFI DISCONNECTED!!!")
        end)

        wifi.sta.on("connected", function(ev, info)
               print("WIFI "..info.ssid)
        end)
    end
     
    ИгорьК нравится это.
  5. ИгорьК

    ИгорьК Гуру

    57. ESP32. Читаем SPS30 PM2.5, PM10, etc.

    ... и здесь помог AI, ибо о применении функции string.unpack(...) для преобразования четырех байт во float никогда не слышал . Гуглопоиски ничего не давали, а вот Aria от Оперы помогла.

    Работаем через UART.

    Код (Lua):
    TX = 17
    RX = 16

    uart.setup(2, 115200, 8, uart.PARITY_NONE, uart.STOPBITS_1, {tx = TX, rx = RX})
    uart.start(2)

    TX, RX = nil, nil

    if not wth then wth = {} end

    local gotRAW = {}
    local startUART = false
    local uartdata = {}
    local btf, makedata, checksum, byte_stuffing
    btf = function(e)
        if type(e) ~= "table" or #e ~= 4 then return end
        local dtu = (string.unpack("f", string.char(e[4], e[3], e[2], e[1])))
        print( 'Bytes To Float:', string.format("%0.1f", dtu))
        return string.format("%0.1f", dtu)
    end

    checksum = function(tb)
        --[[
            The checksum is built before byte-stuffing
            and checked after removing stuffed bytes from
            the frame.
        --]]

        -- for k,v in pairs(tb) do print(k, string.format("0x%02X", string.byte(v))) end
        local sum = 0
        for k = 1, #tb - 1 do
            sum = sum + tb[k]
        end

        print('Sum = '.. string.format("bt = 0x%02X", sum))
        print('Comtrol byte = ', string.format("0x%02X", tb[#tb]))
        sum = bit.bxor(0xFF, bit.band(0xFF, sum))

        print('Sum bxor(0xFF, LSB sum) = '..string.format("0x%02X",sum))
        if sum == tb[#tb] then
            print('Sum is OK!' )
            return true
        else
            print('Wrong Sum!')
            return false
        end
    end

    byte_stuffing = function (tb)
        local ntb = {}
        local insert = 0
        local patt = {}
        patt[0x5E] = 0x7E
        patt[0x5D] = 0x7D
        patt[0x31] = 0x11
        patt[0x33] = 0x13

        for k = 1, #tb do
            if tb[k] == 0x7D then
                insert = patt[tb[k+1]]
            end
            if tb[k] ~= 0x7D then
                if insert ~= 0 then
                    ntb[#ntb+1] = insert
                    insert = 0
                else
                    ntb[#ntb+1] = tb[k]
                end
            end
        end
        return ntb
    end

    makedata = function (tb)
        local dtb = {}
        local pointer = 1
        local pat = {}
        pat[1] = 'pm1';    pat[2] = 'pm2_5';  pat[3] = 'pm4'
        pat[4] = 'pm10';   pat[5] = 'npm0_5'; pat[6] = 'npm1'
        pat[7] = 'npm2_5'; pat[8] = 'npm4';   pat[9] = 'npm10'
        pat[10] = 'tpz'

        for k = 5, 44 do
            dtb[#dtb+1] = tb[k]
            if #dtb == 4 then
                local ddig = btf(dtb)
                wth[pat[pointer]] = ddig
                dtb = {}
                pointer = pointer + 1
            end
        end
        uartdata = {}
        print('Now Got PM Data From SPS30')
    end

    uart.on(2,"data",1,
        function(data)
            local ok = false
            local bt = string.byte(data, 1)
            -- print(string.format("bt = 0x%02X", bt))
            if startUART == false and bt ~= 0x7E then
                return
            elseif startUART == false then
                startUART = true
                gotRAW = {}
                return
            end
            if bt == 0x7E then
                uartdata = byte_stuffing(gotRAW)
                ok = checksum(uartdata)
                print("Got ", ok , " Data from SPS30" )
                startUART = false
                if #uartdata > 40 then
                    makedata(uartdata)
                else
                    ok = uartdata[3]
                    if ok == 0 then
                        print('Command is Ok! No error.')
                    else
                        local errr = string.format("bt = 0x%02X", ok)
                        print('Command Error:', string.format("0x%02X", ok))
                        if ok == 0x43 then print('Command not allowed in current state') end
                        if ok == 0x03 then print('No access right for command') end
                    end
                end
                return
            end
            gotRAW[#gotRAW+1] = bt
            print(string.format("0x%02X,", bt))
        end, 0)

    function sstart() -- Начало Измерерия
        uart.write(2, table.unpack({0x7E, 0x00, 0x00, 0x02, 0x01, 0x03, 0xF9, 0x7E})) -- start
    end
    function sstop() -- Остановка SPS30
        uart.write(2, table.unpack({0x7E, 0x00, 0x01, 0x00, 0xFE, 0x7E})) -- stop
    end
    function sread() -- Чтение Данных
        uart.write(2, table.unpack({0x7E, 0x00, 0x03, 0x00, 0xFC, 0x7E})) -- read
    end
    function sclean() -- Немедленная Очистка Датчика
        uart.write(2, table.unpack({0x7E, 0x00, 0x56, 0x00, 0xA9, 0x7E})) -- clean fan
    end
    function sraci() -- Чтение данных об интервале очистки
        uart.write(2, table.unpack({0x7E, 0x00, 0x80, 0x01, 0x00, 0x7D, 0x5E, 0x7E}))  -- RACI
    end

    Отличный датчик загрязненности воздуха. Правда есть там небольшой косячок. Маленький, но очень опасный! Цвет проводов не соответствует действительному положению дел! Можно пожечь датчик, если довериться красному и черному проводу: держите в уме документацию.
    upload_2024-5-15_12-8-11.png

    В работе:

    upload_2024-5-15_12-2-28.png
     
    Последнее редактирование: 15 май 2024
    parovoZZ нравится это.
  6. Sergku

    Sergku Нерд

    всем привет. понадобилось мне, тутреле скваженного насоса сделать. Нужны таймеры на большие промежутки времени (3 часа), полез в доки tmr
    upload_2024-5-20_16-42-23.png
    что не есть хорошо. Может кто знает, какие модули еще этим рулить могут (еспха будет работать автономно, без ртц шилдов и инета)? костыли неохота втыкать сюда
     
  7. ИгорьК

    ИгорьК Гуру

    Я знаю.
     
  8. Sergku

    Sergku Нерд

    так, и ? :)
     
  9. ИгорьК

    ИгорьК Гуру

    Там под "Я знаю" - ссылка.
     
  10. Sergku

    Sergku Нерд

    апасна, нынче переводить.. да, я и нашел, вроде
    upload_2024-5-20_22-21-15.png
     
  11. ИгорьК

    ИгорьК Гуру

    58. Читаем SCD41 - датчик со2, температуры и влажности.
    Датчик - просто песТня! Особливо для часов - маленький и выдает все что надо.

    ... только я раскрыл даташит, только нашел ардуино библиотеку для подсматривания и написания lua библиотеки, пришла мысль поискать на Гитхабе сочетание "SDC41 lua". И, вот оно - нашлось!

    Поэтому библиотека была нежно скопипащена, проверена, чуть изменена: автор зачем-то выдает из нее сырые данные для дальнейшей обработки за пределами библиотеки.

    Доработка проста - библа выдает окончательные данные, а также вставляет их в таблицу, если оная передана в функцию чтения.

    Данные температуры и влажности в string формате (но ничто не мешает применить tonumber(...), если надо сложить температуру с табуретками).

    Код (Lua):
    local scd4x = {}
    local addr = 0x62

    function scd4x.start()
        i2c.start(0)
        if not i2c.address(0, addr, i2c.TRANSMITTER) then
            return false
        end
        i2c.write(0, {0x21, 0xb1})
        i2c.stop(0)
        return true
    end

    function scd4x.stop()
        i2c.start(0)
        if not i2c.address(0, addr, i2c.TRANSMITTER) then
            return false
        end
        i2c.write(0, {0x3f, 0x86})
        i2c.stop(0)
        return true
    end

    function scd4x.read(tbl)
        i2c.start(0)
        if not i2c.address(0, addr, i2c.TRANSMITTER) then
            return nil
        end
        i2c.write(0, {0xec, 0x05})
        i2c.stop(0)
        i2c.start(0)
        if not i2c.address(0, addr, i2c.RECEIVER) then
            return nil
        end
        local data = i2c.read(0, 9)
        i2c.stop(0)
        if not scd4x.crc_valid(data, 9) then
            return nil
        end
        local co2 = string.byte(data, 1) * 256 + string.byte(data, 2)
        local temp_raw = 175 * (string.byte(data, 4) * 256 + string.byte(data, 5))
        local humi_raw = 100 * (string.byte(data, 7) * 256 + string.byte(data, 8))
        local temp = string.format ("%d.%d ",(temp_raw/65536 - 45), (temp_raw%65536)/6554)
        local humi = string.format ("%d.%d ", humi_raw/65536, (humi_raw%65536)/6554)

        if tbl then tbl.co2 = co2; tbl.humidity = humi; tbl.temperature = temp end
        return co2, temp, humi
    end

    function scd4x.crc_word(data, index)
        local crc = 0xff
        for i = index, index+1 do
            crc = bit.bxor(crc, string.byte(data, i))
            for j = 8, 1, -1 do
                if bit.isset(crc, 7) then
                    crc = bit.bxor(bit.lshift(crc, 1), 0x31)
                else
                    crc = bit.lshift(crc, 1)
                end
                crc = bit.band(crc, 0xff)
            end
        end
        return bit.band(crc, 0xff)
    end

    function scd4x.crc_valid(data, length)
        for i = 1, length, 3 do
            if scd4x.crc_word(data, i) ~= string.byte(data, i+2) then
                return false
            end
        end
        return true
    end

    return scd4x

    Применение:

    Код (Lua):
    wth = {}
    SDA = 2
    SCL = 1
    scd4x = require("scd4x")
    i2c.setup(0, SDA, SCL, i2c.SLOW)
    scd4x.start()

    asksensor = function()
        local co2, temp, humi = scd4x.read(wth)
        print('co2:', co2,'Temperature:', temp, 'Humidity:', humi)
    end

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

    Проект интересен еще и тем, что не просто публикует данные на mqtt брокер, но и обеспечивает mqtt обнаружение девайса в Home Assistant .

    upload_2024-7-17_8-41-49.png

    Давно хотел с этим разобраться, но было лень. В общем, на коде автора понял как делается это обнаружение, перенес себе оное в отдельный файл. Как-нибудь опубликую.

    Пожалуйста, подарите звездочку тов. derf на гитихабе.
     
    Последнее редактирование: 20 июл 2024
  12. ИгорьК

    ИгорьК Гуру

    59. SCD30 Модуль здесь:

    Код (Lua):

    local scd30 = {}
    local addr = 0x61
    local pressure = 1013
    local prt = prt or print
    local writei2c, getbytes, crc_word, crc_valid

    writei2c = function(tb)
        i2c.start(0)
        if not i2c.address(0, addr, i2c.TRANSMITTER) then
            prt '\n\n!!!Lost I2C'
            return false
        end
        i2c.write(0, tb)
        i2c.stop(0)
        return true
    end

    getbytes = function(dig) -- Разлагает число на байты и прилагает crc
        local mbt, lbt, cr
        mbt = bit.rshift(dig, 8)
        lbt = bit.band(dig, 0xFF)
        cr = crc_word(string.char(mbt, lbt), 1)
        return mbt, lbt, cr
    end

    function scd30.start(press) -- Старт SCD30 с коррекцией давления
        local mbt, lbt, cr
        press = press or pressure
        if press < 700 or press > 1400 then press = pressure end
        mbt, lbt, cr = getbytes(press)
        prt('\n--------\nStart SCD30 at Pressure Correction ' .. press .. ' mBar\nCorrection Data:', mbt, lbt, cr, '\n-------')
        writei2c({ 0x00, 0x10, mbt, lbt, cr })
    end

    function scd30.stop()
        writei2c({ 0x01, 0x04 })
    end

    function scd30.setTempOffset(offset) -- Коррекция температуры, шаг 0.01
        local woff, md, ld, cr
        woff = math.floor(offset * 100)
        md, ld, cr = getbytes(woff)
        prt('\nSet SCD30 Temperature Correction ' .. offset .. '°С \nCorrection Data:', md, ld, cr, '\n\n')
        writei2c({ 0x54, 0x03, md, ld, cr })
    end

    function scd30.altComp(alt) -- Компенсация высоты
        local al, md, ld, cr
        al = alt or 107
        md, ld, cr = getbytes(al)
        prt('\nSet SCD30 Altitude Compensation ' .. alt .. ' m. \nCorrection Data:', md, ld, cr, '\n\n')
        writei2c({ 0x51, 0x02, md, ld, cr })
    end

    function scd30.reset() -- Soft Reset
        prt('\nReset SCD30 Now!\n')
        writei2c({ 0xD3, 0x04 })
    end

    function scd30.read(tbl, call) -- Чтение данных в таблицу
        local data, dt, nt, s
        dt = {} -- таблица итоговых данных
        nt = {} -- названия данных
        nt[1] = 'co2'
        nt[7] = 'temp_scd30'
        nt[13] = 'humy_scd30'

        writei2c({ 0x03, 0x00 })
        i2c.start(0)
        if not i2c.address(0, addr, i2c.RECEIVER) then
            return nil
        end
        data = i2c.read(0, 18)
        i2c.stop(0)

        if not (crc_valid(data, 18)) then
            prt('\n\n!!! CRC Fall'); return
        end

        for k = 1, #data, 6 do
            s = string.sub(data, k, k + 1)
            s = s .. string.sub(data, k + 3, k + 4)
            dt[nt[k]] = string.format("%0.1f", string.unpack(">f", s))
            if tbl then
                tbl[nt[k]] = dt[nt[k]]
            end
        end
        if call then call() end
        return dt[nt[1]], dt[nt[7]], dt[nt[13]]
    end

    function scd30.readfw()
        local data
        writei2c({ 0xD1, 0x00 })
        i2c.start(0)
        if not i2c.address(0, addr, i2c.RECEIVER) then
            return nil
        end
        data = i2c.read(0, 3)
        i2c.stop(0)

        if crc_valid(data, 3) then
            prt('\nFW: ' .. string.byte(data, 1, 1) .. '.' .. string.byte(data, 2, 2))
        else
            prt('Lost Firmware Now!')
        end
    end

    crc_word = function(data, index)
        local crc = 0xff
        for i = index, index + 1 do
            crc = bit.bxor(crc, string.byte(data, i))
            for j = 8, 1, -1 do
                if bit.isset(crc, 7) then
                    crc = bit.bxor(bit.lshift(crc, 1), 0x31)
                else
                    crc = bit.lshift(crc, 1)
                end
                crc = bit.band(crc, 0xff)
            end
        end
        return bit.band(crc, 0xff)
    end

    crc_valid = function(data, length)
        for i = 1, length, 3 do
            if crc_word(data, i) ~= string.byte(data, i + 2) then
                return false
            end
        end
        return true
    end
    return scd30
     

    Применять:

    Код (C++):
    do
        SDA = 22
        SCL = 21
        if not dat then dat = {} end
        if not wth then wth = {} end

        if not dat.sda then dat.sda = SDA end
        if not dat.scl then dat.scl = SCL end
        if not dat.id then dat.id = i2c.SW end -- software interface
        if not dat.device_addr then dat.device_addr = 0x62 end
        SDA, SCL = nil, nil

        asksensor = function()
            local co2, temp, humi = scd30.read(wth)
            print('co2:', co2,'Temperature:', temp, 'Humidity:', humi)
        end

        if dat.sda and dat.scl and not dat.i2c then
            scd30 = require("_scd30mod")
            print('i2c set at pins SDA: '..dat.sda..', SCL: '..dat.scl..', Speed:', i2c.setup(dat.id, dat.sda, dat.scl, i2c.SLOW))
            scd30.start()
            dat.i2c = true
        else
            print('\t\t\t\ti2c Bus Ready')
        end

        asksensor()
        -- tmr.create():alarm(15000, 1, asksensor)
    end
     
    Последнее редактирование: 21 ноя 2024
    Voik нравится это.
  13. ИгорьК

    ИгорьК Гуру

    60. SHT20 Модуль здесь.
    Код (Lua):
    -- Datasheet Table 7 (Defaults):
    -- 85 ms for measure temp
    -- 29 ms for measure humidity
    do
        local prt = prt or print
        if not dat then
            prt 'No dat Table'; return
        end
        wth            = wth or {}
        local sht20    = {}
        local addr     = 0x40

        local wt       = {}
        wt.T_MEAS_HOLD = 0xE3
        wt.H_MEAS_HOLD = 0xE5
        wt.SOFT_RESET  = 0xFE -- 15 ms

        local call     = function() prt('SHT20 Mod done!') end

        function sht20.srest()
            i2c.start(dat.id)
            if not i2c.address(dat.id, addr, i2c.TRANSMITTER) then
                prt("! sht20.start 1: i2c.address failed")
                return nil
            end
            i2c.write(dat.id, wt.SOFT_RESET)
            i2c.stop(dat.id)
            prt('!SHT20 Restarted!')
        end

        function sht20.crc8(data) -- 0x31 - Polinom, Datasheet
            local crc = 0x00   -- Datasheet!!!!
            for i = 1, 2 do
                crc = bit.bxor(crc, string.byte(data, i))
                for _ = 1, 8 do
                    crc = bit.isset(crc, 7) and (bit.bxor(bit.lshift(crc, 1), 0x31)) or (bit.lshift(crc, 1))
                    crc = bit.band(crc, 0xff)
                end
            end
            return (crc == string.byte(data, 3))
        end

        local makeT = function(raw)
            local temp = (raw / 65536 * 175.72) - 46.85
            wth.sht20temp = tonumber(string.format("%0.1f ", temp))
            prt("SHT20 temp:", wth.sht20temp)
            sht20.readRaw(wt.H_MEAS_HOLD, 29) -- next step
        end

        local makeH = function(raw)
            local humi = (raw / 65536 * 125) - 6
            wth.sht20hum = tonumber(string.format("%0.1f ", humi))
            prt("SHT20 humi:", wth.sht20hum)
            if call then call() end
        end

        function sht20.readRaw(com, tm)
            i2c.start(dat.id)
            if not i2c.address(dat.id, addr, i2c.TRANSMITTER) then
                prt("! sht20.start 1: failed")
                return
            end
            i2c.write(dat.id, com)
            i2c.stop(dat.id)
            if not tm then return end
            tmr.create():alarm(tm, tmr.ALARM_SINGLE, function(t)
                t = nil
                i2c.start(dat.id)
                if not i2c.address(0, addr, i2c.RECEIVER) then
                    prt("! sht20.start 2: failed")
                    return
                end
                local data = i2c.read(0, 3)
                i2c.stop(dat.id)
                if data and sht20.crc8(data) then
                    local raw = (string.byte(data, 1) * 256 + string.byte(data, 2))
                    if tm == 85 then
                        makeT(raw)
                    else
                        makeH(raw)
                    end
                else
                    prt '! Lost SHT20 Data'
                end
            end)
        end

        function sht20.read(cl)
            call = cl or call
            wth.sht20hum = nil
            wth.sht20temp = nil
            sht20.readRaw(wt.T_MEAS_HOLD, 85)
        end
        function sht20.reset()
            sht20.readRaw(wt.SOFT_RESET)
        end

        return sht20
    end

    Применять так:
    Код (Lua):
    do
        SDA = 21
        SCL = 22
        dat = dat or {}
        wth = wth or {}

        dat.sda = dat.sda or SDA
        dat.scl = dat.scl or SCL
        dat.id = dat.id or i2c.SW
        SDA, SCL = nil, nil


        if dat.sda and dat.scl and not dat.i2c then
            sht20 = require("sht20")
            print('i2c set at pins SDA: ' .. dat.sda .. ', SCL: ' .. dat.scl .. ', Speed:',
                i2c.setup(dat.id, dat.sda, dat.scl, i2c.SLOW))
            dat.i2c = true
        end
        dat.sda = nil
        dat.scl = nil
        local call = function()
            package.loaded["sht20"] = nil
            sht20 = nil
            print('\nSHT20 Done All Works Now!\nHeap: ', node.heap(),'\n')
            collectgarbage("collect")
        end
        local readSHT20 = function()
            sht20 = require("sht20")
            sht20.read(call)
        end
        sht20.read(call)
        tmr.create():alarm(30000, 1, readSHT20)
    end
     
  14. ИгорьК

    ИгорьК Гуру

    61. Home Assistant MQTT Discovery.

    Ниже пример модуля, который избавляет от создания руками устройств в НА.
    Модуль надо запускать один раз при включении устройства. Конечно, это (рабочий) шаблон - его надо править под вашу ситуацию. Однако там все, ИМХО, предельно понятно.

    В модуле примеры сенсоров и выключателей - наиболее распространенных итемов.

    Код (Lua):
    do
        dat = dat or {}
        local prt = prt or print
        dat.device_id = 'sht20test' .. dat.chip_id

        local top = 'homeassistant/device/' .. dat.device_id .. '/SHT20-Test/config'

        local disc = {
            dev = {
                ids = { dat.device_id },
                name = "SHT20 Test Sensor",
                mf = "igorkkk",
                mdl = "SHT20 Sensor",
                mdl_id = 'Testing Work',
                sw = dofile('_aaversion.lua'),
                sn = dat.chip_id,
                hw = "0.0",
                cu = 'http://' .. dat.ip
            },
            o = {
                name = "SHT20-Test",
                sw = "0.1",
            },
            cmps = {
                sht20testtemp = {
                    p = "sensor",
                    device_class = "temperature",
                    unit_of_measurement = "°C",
                    value_template = "{{ value_json.sht20temp | round(1)}}",
                    unique_id = dat.device_id .. "_t"
                },
                sht20testhumi = {
                    p = "sensor",
                    device_class = "humidity",
                    unit_of_measurement = "%",
                    value_template = "{{ value_json.sht20hum | round(1)}}",
                    unique_id = dat.device_id .. "_h"
                },
                sht20testheap = {
                    p = "sensor",
                    name = 'Куча-Мала',
                    device_class = "data_size",
                    unit_of_measurement = "B",
                    value_template = "{{ value_json.heap }}",
                    unique_id = dat.device_id .. "_hp"
                },
                sht20testswitch = {
                    p = "switch",
                    name = 'Еще Нажми Меня',
                    device_class = "outlet",
                    topic = "SHT20/421C699E/state",
                    command_topic = "SHT20/421C699E/com/sw01",
                    value_template = "{{ value_json.switch }}",
                    payload_on = "On",
                    payload_off = "Off",
                    state_on = 'On',
                    state_off = 'Off',
                    unique_id = dat.device_id .. "_sw"
                }
            },
            state_topic = 'SHT20/421C699E/state',
            -- availability_topic = 'SHT20/421C699E/available',
            qos = 2
        }

        local ok, json_str = pcall(sjson.encode, disc)
        if ok then
            -- prt(json_str, '\n')
        else
            prt('! Lost JSON')
        end

        if ok and dat.broker then
            m:publish(top, json_str, 2, 0,
                function(con)
                    prt('mqttdiscover: MQTT Published Discover Data')
                    dat.diskover = true
                end)
        end
    end
     
  15. ИгорьК

    ИгорьК Гуру

    Немного слов про MQTT Discovery в здешней реализации.

    "Внезапно", данные с часов перестали поступать в Home Assistant.

    изображение_2025-03-17_082934507.png
    Оный написал, что интеграция больше не поставляет циферки или что-то типа того. Поскольку устройство находится за тридевять земель, доступа к нему нет.
    Проверяю MQTT брокер, как главного снабженца колхозного рынка: джесоны от часов в наличии! Непонятна!

    Проходит время - понятно. Модуль выше при объявлении о поставке данных в НА не объявляет обявительный топик Retained. Как следствие, если НА перегружается/обновляется, в его скудной памяти что-то теряется. А брокер не хранит Discovery данные.

    Но теряется наполовину. Предыдущая информация, типа, остается, но новые данные, идущие тем же порядком, в тех-же топиках, перестают восприниматься.

    Выход 1. Объявлять топик раскрытия информации Retained. (Так делает господин все) Следствие: если вы что-то тестируете, а потом нетестируете - задолбаетесь устанете ликвидировать топики на брокере и итемы в НА.

    Выход 2. Таймером хотя бы раз в (по вашему решению), повторять топики MQTT Discovery. Следствие: после хобби развлечений все лишнее в НА ликвидируется на раз.
     
    Последнее редактирование: 17 мар 2025
  16. ИгорьК

    ИгорьК Гуру

    Ответ на вопрос в личку. Вариант.
    Нужно соединить две таблицы, что получаются от node.info()
    node.info() нынче работает с параметрами: May be one of "hw", "sw_version", "build_config".

    Таким образом, чтобы собрать необходимые данные можно действовать так:

    Код (Lua):
    do
        local espinfo = node.info("sw_version")
        local hw = node.info("hw")
        for k, v in pairs(hw) do espinfo[k] = v end
        -- теперь espinfo содержит данные двух таблиц,
     
        -- результат:
        print('\n\nespinfo:')
        for k, v in pairs(espinfo) do print(k, v) end
     
        -- перечислим данные, что нужны нам, например:
        local nest = { 'flash_id', 'chip_id', 'git_commit_dts' }
        newespinfo = {} -- глобальная таблица выборки
        for _, v in pairs(nest) do newespinfo[v] = espinfo[v] end
        -- лучше уничтожить локальные таблицы до выхода из чанка. Привычка.
        espinfo, hw, nest = nil, nil, nil
     
        -- проверяем результат:
        print('\n\nnewespinfo:')
        for k, v in pairs(newespinfo) do print(k, v) end
    end
    Вариант объединения perplexity:
    Код (Lua):
    local merged = {}
    for k, v in pairs(node.info("hw")) do merged[k] = v end  -- Добавление аппаратных характеристик
    for k, v in pairs(node.info("sw_version")) do merged[k] = v end  -- Добавление информации о ПО
    Всего, как думает AI, почти как я, только не создает локальные переменные при вызове функции, возьму на заметку:
    Код (Lua):
    -- Объединение таблиц
    local merged = {}
    for k, v in pairs(node.info("hw")) do merged[k] = v end
    for k, v in pairs(node.info("sw_version")) do merged[k] = v end

    -- Фильтрация по требуемым ключам
    local target_keys = { 'flash_id', 'chip_id', 'git_commit_dts' }
    local result = {}
    for _, key in ipairs(target_keys) do
        result[key] = merged[key]
    end
     
    А вот глубокий китайский ум думает солидарно со мной, через локальные переменные. Причем до создания первой части таблицы данных прямым вызовом функции не "додумался" никто (?)ничто. Включая Мистраль АИ:
    Код (Lua):
    -- Вызываем node.info() дважды с разными параметрами
    local hw_info = node.info("hw")
    local sw_info = node.info("sw_version")

    -- Объединяем таблицы (в Lua простое объединение с перезаписью при совпадении ключей)
    local combined_info = {}
    for k, v in pairs(hw_info) do combined_info[k] = v end
    for k, v in pairs(sw_info) do combined_info[k] = v end

    -- Выбираем только нужные ключи
    local selected_keys = { 'flash_id', 'chip_id', 'git_commit_dts' }
    local result = {}

    for _, key in ipairs(selected_keys) do
        if combined_info[key] ~= nil then
            result[key] = combined_info[key]
        end
    end

    -- Теперь result содержит только нужные данные
    return result
    Короче, я составлял промпт так:
    "Контекст: язык программирования - NodeMCU Lua. Функция node.info() будет дважды вызываться с параметрами "hw" и "sw_version". Функция каждый раз возвращает таблицу типа "ключ - значение". Требуется получить таблицу, в которой будут объединены данные обоих вызовов, а затем выбрать из нее те данные, что соответствуют значениям из таблицы { 'flash_id', 'chip_id', 'git_commit_dts' }."

    В какое время живем... :)

    изображение_2025-04-03_122549281.png
     
    Последнее редактирование: 3 апр 2025 в 12:25