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

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

  1. DIYMan

    DIYMan Guest

    Сделать две HTML-формы - религия не позволяет?
     
  2. Crazy_Volt

    Crazy_Volt Нуб

    Фантазируйте дальше
    Позволяет, но если есть метод все реализовать на одной странице, это будет лучше
     
  3. DIYMan

    DIYMan Guest

    Открою СТРАШНУЮ тайну - тегов <FORM> на одной HTML-странице может быть много.

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

    Более того - даже если только одна форма (тег FORM) юзается - то можно динамически менять ей атрибут target.
     
  4. Crazy_Volt

    Crazy_Volt Нуб

    Согласен. Но если один <form>-это вся страница целиком, куда вставить второй <form>?
     
  5. DIYMan

    DIYMan Guest

    Вы совсем не знаете основ HTML, видимо:
    HTML:
    <html>
        <head>
            <title>Stupid page</title>
        </head>
     
        <body>
            <form target="test.lua" method="get">
                <input type="submit" value="DO"/>
            </form>

            <form target="other.lua" method="get">
                <input type="submit" value="DO 2"/>
            </form>
         
        </body>

    </html>
    Где вы видите ограничение по кол-ву форм на одной странице? Почитайте основы HTML, плз.
     
  6. ИгорьК

    ИгорьК Гуру

  7. Crazy_Volt

    Crazy_Volt Нуб

    А вот этой инфы я не находил... спасибо...пошел искать )
     
  8. Crazy_Volt

    Crazy_Volt Нуб

    это понятно, Я про тот случай, когда <form> идет от начала до конца <body>. Так у меня идет сохранение выставленных параметров.
     
  9. ИгорьК

    ИгорьК Гуру

  10. ИгорьК

    ИгорьК Гуру

    19. Датчик DS2438: полезная штука. Как работать с любым 1Wire устройством.

    Слышали о таком? Это контроллер батарейного питания и, одновременно, датчик температуры а также 40 байт EEPROM. Если питать что-то от батареек - может сильно помочь. Я собираюсь делать устройство, работающее от автомобильного аккумулятора, и, среди прочего, применить эту микросхему.
    Давайте напишем для нее модуль.

    Микросхема DS2438 умеет измерять напряжение собственного питания и имеет дополнительную ногу - вход тоже для измерения напряжения.

    upload_2018-2-28_11-2-58.png

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

    Это так, но у ESP-8266 только один аналоговый вход, и он может быть нужен для других измерений. И здесь все равно придется что-то придумывать. Например, можно воспользоваться инструкцией от товарища Пушной звер:


    Вещица хорошая. Но DS2438 имеет некоторые фичи:
    - протокол позволяет экономить целую ногу, коих у ESP-8266 немного,
    - расстояние от МК, в силу протокола, может очень большим, в результате чего некоторые товарищи делают на ней высокопроизводительные датчики температура/влажность с участием семейства hihххх от Ханейвел (я бы точно так сделал для проветривателя подвала, если бы знал в тот момент об этом решении):
    [​IMG]
    - (высока вероятность) эта микросхема существенно экономичнее чем указанная выше.

    Читаем даташит.
    Понимаю, что поколение программистов, подготовленных школой Амперка на платформе Iskra JS, в принципе не знает о таком явлении, и, надеюсь, открою для кого-то новые вещи.

    Итак, чтение даташита и иной запрещенной документации позволит нам узнать, что общение по шине 1-Wire происходит только по инициативе МК, путем выдачи в линию последовательностей команд, среди которых может вклиниваться и чтение данных от датчика.
    Не будем вдаваться в детали.
    Во-первых, нам нужен адрес устройства на шине.
    Получим его (практически :) ) копипастой из существующего у NodeMCU примера:
    Код (Lua):
    do
        addr2438 = "" -- адрес DS2438
        addrTb = {} -- общая таблица адресов
        pin = 4  -- нога One Wire

        -- Ищем устройства на линии
        local search = function()
            -- так положено
            ow.setup(pin)
            ow.reset_search(pin)
            local adr -- текущий адрес найденного устройства
            -- Крутим шарманку
            repeat
                adr = ow.search(pin)
                if(adr ~= nil) then
                    table.insert(addrTb, adr) -- вставляем очередной адрес в таблицу
                    -- А если это именно DS2438 - выписываем адрес в самостоятельную переменную
                    if adr:byte(1) == 0x26 then addr2438 = adr end
                end
            until (adr == nil)
            -- Так положено после поиска
            ow.reset_search(pin)
            -- Итого найдено железок:
            print('Got Devices:', #addrTb)

            -- Напечатаем адрес каждой железки.
            -- Выбираем последовательно значения таблицы адресов
            for _, v in pairs(addrTb) do
                local as = ""
                for ii = 1, #v do
                    -- Превращаем каждую "букву" в ее байт-код и форматируем в шестнадцатеричном исчислении
                    local hx = string.format("%02X", (string.byte(v, ii)))
                    -- и  соединяем в единое целое
                    as = as ..hx
                end
                print(as)
            end
        end
        search()
    end
    Вот что обнаружено на шине:
    upload_2018-2-27_16-3-51.png

    Тот что начинается на 0х26 и есть DS2438, а также обнаружили парочку DS18b20.

    Теперь о порядке злодействий с датчиком.
    Первой важной информацией для нас будет таблица 13:
    upload_2018-2-27_16-41-49.png
    В ней рассказана последовательность действий с целью добычи информации о температуре и напряжении. В левой колонке что должен делать ESP-8266 - передавать или принимать.
    Причем, для парочки TX/Reset - RX/Presence у Lua есть всего одна функция:
    Код (Lua):
    ow.reset()
    Она и возвращает информацию о наличии или отсутствии железок (хотя бы одной) на линии.
    Дальше в таблице есть такая команда:
    TX/CCh, что значит "Пропустить ROM", то есть "всем кто на линии - выполнять дальнейшие инструкции".
    Мы не будем ее применять - на линии может присутствовать много разных устройств.
    Вместо нее применяем обращение к конкретной железке:
    Код (Lua):
    ow.select(pin, address) -- нога и адрес устройства
    Вроде, пора уже написать одну красивую функцию по добыче данных! А вот и нет!
    То что в таблице указано как последовательность команд на деле работать не будет ни разу, потому что (читали даташит, да?) между отправкой команд на измерение температуры и измерение напряжения ОБЯЗАНО пройти время, а именно - на температуру требуется не менее 10 мс, на напряжение - 4.

    Ясный перец - втыкай delay(10) и будет сачтье, но это не наш метод! В асинхроном программировании любые тормоза процессора - табу.

    Поэтому придется разрабатывать отдельные функции, а потом как-то ими управлять.
    В целом, кроме функции поиска нам нужны будут еще команды:
    1. на измерение температуры
    2. на измерение напряжения
    3. на перенос данных из ПЗУ (куда пишутся результаты) в (типа) ОЗУ
    4. на чтение дпнных из ОЗУ
    А кроме того, лишними не будут еще команды:
    1. на перенос данных из ОЗУ в ПЗУ - там можно запоминать источник измерения напряжения: питание DS2438 или специальная нога
    2. на установление этого самого источника чтения
    Ну, ничего... Не страшно все. Кстати, посмотрите библиотеку для Ардуино. Сначала я пытался переложить ее на ноты Lua, а потом забил и написал свою. Над(у)еюсь, она будет проще и понятнее, а также без ошибок и косяков этой библиотеки. К ним мы, возможно, вернемся.

    Итак, создадим таблицу, куда выпишем все заявленные выше функции, воспользовавшись информацией:
    upload_2018-2-27_17-34-14.png

    Код (Lua):
    local M = {}
    M.addr = ""
    M.addrTb = {}
    M.pin = 4
    M.dat = ""
    -----

    -- Это уже было
    M.search = function()
        ow.setup(M.pin)
        ow.reset_search(M.pin)
        local adr
        repeat
            print("Search")
            adr = ow.search(M.pin)
            if(adr ~= nil) then
                table.insert(M.addrTb, adr)
                M.addr = adr
            end
        until (adr == nil)
        ow.reset_search(M.pin)
        print('Adr No:', #M.addrTb)
        for _, v in pairs(M.addrTb) do
            local as = ""
            for ii = 1, #v do
                local hx = string.format("%02X", (string.byte(v, ii)))
                as = as ..hx
            end
        print(as)
        end
    end

    -- Все что ниже - из даташита
    -- Читать температуру
    M.starttemp = function()
        ow.reset(M.pin)
        ow.select(M.pin, M.addr)
        ow.write(M.pin, 0x44, 1)
        ow.reset(M.pin)
    end

    -- Читать напряжение
    M.startVolt = function()
        ow.reset(M.pin)
        ow.select(M.pin, M.addr)
        ow.write(M.pin, 0xb4, 1)
        ow.reset(M.pin)
    end

    -- С какой ноги читать вольты
    M.setVAD = function(inp)
        local dt = inp == 0 and 0 or 0x08
        ow.reset(M.pin)
        ow.select(M.pin, M.addr)
        ow.write(M.pin, 0x4e, 1)
        ow.write(M.pin, 0x00, 1)
        ow.write(M.pin, dt, 1)
        ow.reset(M.pin)
    end

    -- Добыть измерения из "ПЗУ"
    M.recallMem = function()
        ow.reset(M.pin)
        ow.select(M.pin, M.addr)
        ow.write(M.pin, 0xb8, 1)
        ow.write(M.pin, 0x00, 1)
        ow.reset(M.pin)
    end

    -- Прочитать данные в переменную
    M.readPage = function()
        ow.reset(M.pin)
        ow.select(M.pin, M.addr)
        ow.write(M.pin,0xBE,1)
        ow.write(M.pin,0x00,1)
        M.dat = ow.read_bytes(M.pin, 9)
        print(M.dat:byte(1,9))
        ow.reset(M.pin)
    end

    return M
     
    Последнее редактирование: 28 фев 2018
    alp69 и SergeiL нравится это.
  11. ИгорьК

    ИгорьК Гуру

    Получился (недо)модуль _ds2438.lua , который уже можно применять в ручном режиме, так:
    Код (Lua):
    ds = require('_ds2438')
    ds.search() -- ищем адреса
    ds.setVAD(1) -- устанавливаем чтение напряжения питания
    -- ds.setVAD(0) -- устанавливаем чтение напряжения с ноги
    ds.starttemp() -- измеряем температуру
    ds.startVolt() -- измеряем напряжение
    ds.recallMem() -- получаем данные из ПЗУ
    ds.readPage() -- читаем 9 байт
    Каждую команду следует отдавать руками - помним, что необходимо небольшое время на обработку предыдущей.
    Кстати, привыкайте к такой работе - где это в Ардуино вы работаете в режиме реального времени.

    Вот что увидим:
    upload_2018-2-27_17-46-9.png

    Информация о температуре и напряжении находится у нас в 2-5 прочитанных байтах:
    upload_2018-2-27_17-50-19.png

    А прочитали мы из модуля 9 байт. Зачем?
    А вот какая фигня: если прочитать 8 байтов со страницы 0х00, то девятым придет контрольная сумма первых восьми. И будет она верной если успешно проведено преобразование & данные приняты правильно.

    Поэтому, сделаем еще одну функцию - проверку CRC. Она есть в документации NodeMCU и, следовательно, будет выглядеть так:
    Код (Lua):
    M.checkCRC = function()
        -- Хватаем M.dat, где лежат принятые данные и
        -- считаем crc8 для 1 - 8 байтов
        local crc = ow.crc8(string.sub(M.dat,1,8))
        -- сравниваем с 9 байтом и возвращаем тру или нет
        if crc == M.dat:byte(9) then
            return true
        else
            print('Bad CRC')
            return false
        end
    end
    А теперь займемся преобразованием данных в температуру и напряжение. Начнем с температуры, потому что (ах, тайные желания) скопипастить алгоритм с аруиновской библиотеки не удастся: там нет ничего об отрицательных температурах.

    Смотрим сюда:
    upload_2018-2-27_18-4-19.png
    Видим, что температура кодируется двумя байтами, кои называются MSB и LSB - мост(больший, старший) и лист(меньший, младший) значимые байты.

    Читаем дальше :
    upload_2018-2-27_18-7-21.png

    В вольном переводе это значит, что LSb имеет разрешение 0.03125 (фигасе!!!) градуса, а MSB - тот чистый градусник.

    Кроме того, три правых бита LSb - не применяются, и чтобы правильно сформировать его значение, его нужно сдвинуть вправо на 3 разряда:
    Код (Lua):
    LSb = bit.rshift(LSb, 3)
    -- или:
    LSb = LSb/2/2/2
    -- а кто профессор в математике
    LSb = LSb/8

    -- а потом умножить:
    LSb = LSb * 0.03125
    За отрицательную температуру отвечает 7 бит MSb. Пока его нет - это просто градусы.
    Когда он есть - остальные биты инвертируются. В общем, с ним надо поступать так:
    Код (Lua):
    local raw = (MSb > 127) and (MSb - 256) or MSb
    Получили тренарный оператор: если MSb > 127 (то есть установлен 7 бит) - берем MSb - 256 иначе не трогаем его.

    Чтобы проверить наши измышления, напишем функцию, которая примет данные из таблицы выше, и рассчитает температуру один в один:

    Код (Lua):
    do
        temp = {
            -- Это значения MSb и LSb из таблички
            {0x7d, 0x00},
            {0x19, 0x10},
            {0x00, 0x80},
            {0x00, 0x00},
            {0xff, 0x80},
            {0xe6, 0xf0},
            {0xc9, 0x00}
        }
        -- Функция преобразования
        temp2438 = function(mb, lb)
            local raw = (mb > 127) and (mb - 256) or mb
            print (raw + (bit.rshift(lb, 3)) * 0.03125)
            print (raw + ((lb/8) * 0.03125))
        end
        -- кормим функцию таблицей
        for _, v in pairs(temp) do
            temp2438(v[1], v[2])
        end
    end
    Неплохо получилось:
    upload_2018-2-27_18-22-3.png

    C вольтами вообще просто:

    upload_2018-2-28_10-53-5.png

    Код (Lua):
    do
        volt = {
            {0x00, 0x05},
            {0x01, 0x0e},
            {0x01, 0x68},
            {0x01, 0xf4},
            {0x02, 0xd0},
            {0x03, 0xe7},
            {0x03, 0xe8}
        }
        volt2438 = function(mb, lb)
            local v = ((mb * 256) + lb) / 100
            print(v)
        end
        for _, v in pairs(volt) do
            volt2438(v[1], v[2])
        end
    end
    upload_2018-2-27_18-33-41.png

    В заключение, прилагаю итоговый файл библиотеки _ds2438.lua в стиле ардуино, но без некоторой автоматики и гораздо проще. В той библиотеке, что взята для примера есть функция update() которая читает информацию с применением delay(), а потом парой функций можно выудить из нее данные.
    С нашей тоже можно поступать аналогичным образом, но завтра мы будем все автоматизировать и упаковывать, а пока вот что получилось (очень хочу верить, что вам ясно здесь назначение каждой функции):
    Код (Lua):
    local M = {}
    M.addr = ""
    M.addrTb = {}
    M.pin = 4
    M.dat = ""
    -----
    M.search = function() -- ищем датчики
        ow.setup(M.pin)
        ow.reset_search(M.pin)
        local adr
        repeat
            print("Search")
            adr = ow.search(M.pin)
            if(adr ~= nil) then
                table.insert(M.addrTb, adr)
                M.addr = adr
            end
        until (adr == nil)
        ow.reset_search(M.pin)
        print('Adr No:', #M.addrTb)
        for _, v in pairs(M.addrTb) do
            local as = ""
            for ii = 1, #v do
                local hx = string.format("%02X", (string.byte(v, ii)))
                as = as ..hx
            end
        print(as)
        end
    end

    M.starttemp = function() -- запуск измерения температуры
        ow.reset(M.pin)
        ow.select(M.pin, M.addr)
        ow.write(M.pin, 0x44, 1)
        ow.reset(M.pin)
    end

    M.startVolt = function() -- запуск измерения напряжения
        ow.reset(M.pin)
        ow.select(M.pin, M.addr)
        ow.write(M.pin, 0xb4, 1)
        ow.reset(M.pin)
    end

    M.setVAD = function(inp) -- 0 или 1 -- откуда читать напряжение, нога или питание
        local dt = inp == 0 and 0 or 0x08
        ow.reset(M.pin)
        ow.select(M.pin, M.addr)
        ow.write(M.pin, 0x4e, 1)
        ow.write(M.pin, 0x00, 1)
        ow.write(M.pin, dt, 1)
        ow.reset(M.pin)
    end

    M.recallMem = function() -- вызвать данные из ПЗУ
        ow.reset(M.pin)
        ow.select(M.pin, M.addr)
        ow.write(M.pin, 0xb8, 1)
        ow.write(M.pin, 0x00, 1)
        ow.reset(M.pin)
    end

    M.readPage = function() -- читать данные в переменную
        ow.reset(M.pin)
        ow.select(M.pin, M.addr)
        ow.write(M.pin,0xBE,1)
        ow.write(M.pin,0x00,1)
        local data = ""
        for i = 1, 9 do
            data = data .. string.char(ow.read(M.pin))
        end
        print(data:byte(1,9))
    end

    M.checkCRC = function() -- проверка цилического кода (целостности данных)
       local crc = ow.crc8(string.sub(M.dat,1,8))
       if crc == M.dat:byte(9) then
         return true
       else
         print('Bad CRC')
         return false
       end
    end

    M.temp = function() -- получить температуру
        if checkCRC() then
            local mb = M.dat:byte(3)
            local raw = (mb > 127) and (-256 + mb) or mb
            return (raw + (bit.rshift(M.dat:byte(2), 3)) * 0.03125)
        end
    end

    M.volt = function() -- получить напряжение
        if checkCRC() then
            return ((M.dat:byte(5) * 256) + M.dat:byte(4)) / 100
        end
    end

    return M
    Продолжение.
     
    Последнее редактирование: 28 фев 2018
    alp69 нравится это.
  12. ИгорьК

    ИгорьК Гуру

    Продолжим...
     
  13. ИгорьК

    ИгорьК Гуру

    Пока неоплаченная реклама: для тех же целей существует расширитель портов от Амперки о девяти плюсуемых ногах.
    [​IMG]
    У него всего четыре недостатка:
    1. имеет стремление, но не смог дотянуть ценой до Бугатти - настоящим пацанам такой покупать стыдно;
    2. имеет библиотеки для всего, что имеет много ног, а для esp-8266 - не имеет (я про Lua);
    3. имеет загадочный, футуристический форм-фактор;
    4. имеет отсутствие описания API.
    В общем, для чего он задумывался маркетологами - не ясно, но инженер (Василий?) сделал его с душой.

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

    ИМХО, возникла хорошая тенденция - умные модули. Царем здесь, видимо, является дисплей Nextion.
    К дисплею (херовому) привинтили микроконтроллер (дешманский) и упростили взаимодействие юзверя с ним. Все что делает Nextion можно сделать и обычным дисплеем, изрядно попотев в написании функций и отъев прилично памяти.
    Вот и называемый здесь модуль относится к устройствам, упрощающим жизнь, в том числе путем облегчения перемещения кошелька в пространстве.
     
    Последнее редактирование: 28 фев 2018
  14. ИгорьК

    ИгорьК Гуру

    19.2 Датчик DS2438: полезная штука. Как работать с любым 1Wire устройством.

    Завершим написание упаковку модуля. Сначала о "внешних" подходах.

    Микросхема предназначена для выдачи пары значений, и нас не интересует, ничто кроме этого. Значит, нет смысла городить какие-то отдельные команды: пнул, а на выходе получил результат.
    Исключение - возможна команда на выбор источника измерения, но в готовом устройстве мы вряд ли будем ей постоянно пользоваться. Один раз установили режим по умолчанию и все.

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

    И последнее, я привык писать скрипты так: делаю глобальную таблицу текущих результатов всяких значений. Она передается различным модулям/функциям для внесения в нее записей. То есть, я не извлекаю из функций через return то или иное значение.
    Знаю, что таблицы поначалу сильно пугают начинающих от Ардуино, поскольку ассоциируются с масивами и указателями, но в Lua таблицы - вещь настолко приятная, что следует понять из глубже.

    В общем, написание модуля начнем не с него, а с ... :) внешнего скрипта работы с ним. Вот:
    Код (Lua):
    do
        -- Грузим еще не написанный модуль
        ds = require('_ds2438nnn')
        -- Создаем таблицу с данными
        dattb = {}
        -- callback функция
        call = function()
            -- просто печатаем показания
            table.foreach(dattb, print)
            --[[
            Здесь можно вставлять свой код для работы
            с результатами, вызывать другие функции,
            а таблица dattb снабдит их необходимой информацией
            --]]

        end
        -- единственная системная функция получения данных
        ds.update(dattb, call)
    end

    -- Это можно делать в ручном режиме
    -- найти датчик
    ds.search()

    -- установить источник измерения напряжения
    -- входная нога
    ds.writeDS(ds.ncom.setVAD)
    -- напряжение питания
    ds.writeDS(ds.ncom.setVVD)
    -- сохранить источник как вызываемый по умолчанию
    ds.writeDS(ds.ncom.copySP)
    Займемся внутренностями.
    Функция поиска адресов необходима в любых случаях. Для ситуации с одним датчиком ds2438 можно сильно упростить ее, но шина 1wire так и манит затолкать не нее лишний термометр. Таким образом, оставим как было - сбор всех адресов в таблицу и, дополнительно, вынос адреса этого датчика в самостоятельную переменную.

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

    Функция поиска адресов, все разжевано раньше:
    Код (Lua):
    M.search = function()
        ow.setup(M.pin)
        ow.reset_search(M.pin)
        local adr
        repeat
            adr = ow.search(M.pin)
            if(adr ~= nil) then
                table.insert(M.addrTb, adr)
                if adr:byte(1) == 0x26 then M.addr = adr end
            end
        until (adr == nil)
        ow.reset_search(M.pin)
        print('Got Devices:', #M.addrTb)
    end
    Теперь съедим простую задачу - объединим в одну функции проверки CRC, расчета напряжения и температуры. Работают они последовательно, решение очевидно, а код объяснен ранее.
    Дополнительно вспомним, что в Lua из функции можно возвращать любое количество значений, поэтому:

    Код (Lua):
    M.count = function()
        local crc = ow.crc8(string.sub(M.dat,1,8))
        if crc == M.dat:byte(9) then
            local mb = M.dat:byte(3)
            local raw = (mb > 127) and (-256 + mb) or mb
            return (raw + (bit.rshift(M.dat:byte(2), 3)) * 0.03125), ((M.dat:byte(5) * 256) + M.dat:byte(4)) / 100
        else
            print('Bad CRC')
            return 85, -1 -- если все фигово, то вернем отрицательное напряжение
            -- и температуру 85 (что для датчиков температуры, обычно, есть ошибка )
            -- но можете сами решить что возвращать
        end
    end
    И, наконец, десерт! Надо запрессовать четыре функции типовой работы с датчиком. Посмотрите как они похожи:

    Код (Lua):
    M.starttemp = function()
        ow.reset(M.pin)
        ow.select(M.pin, M.addr)
        ow.write(M.pin, 0x44, 1)
        ow.reset(M.pin)
    end

    M.startVolt = function()
        ow.reset(M.pin)
        ow.select(M.pin, M.addr)
        ow.write(M.pin, 0xb4, 1)
        ow.reset(M.pin)
    end

    M.recallMem = function()
        ow.reset(M.pin)
        ow.select(M.pin, M.addr)
        ow.write(M.pin, 0xb8, 1)
        ow.write(M.pin, 0x00, 1)
        ow.reset(M.pin)
    end

    M.readPage = function()
        ow.reset(M.pin)
        ow.select(M.pin, M.addr)
        ow.write(M.pin,0xBE,1)
        ow.write(M.pin,0x00,1)
        local data = ""
        for i = 1, 9 do
            data = data .. string.char(ow.read(M.pin))
        end
        print(data:byte(1,9))
        crc = ow.crc8(string.sub(data,1,8))
        print("CRC="..crc)
        M.dat = data
    end
    Вначале все кидают датчику несколько команд, заключение тоже одинаковое, и лишь последняя вклинивает ближе к концу чтение данных.
    Вывод - делаем функцию, которая:
    1. примет любое число данных для отправки;
    2. примет тру/фальс на чтение
    И дело в шляпе! А что есть "любое число данных для отправки"? Конечно - таблица!
    Пишем функцию:

    Код (Lua):
    M.writeDS = function(tb, read) -- принимаем таблицу с перечнем данных и да/нет на чтение
        if M.addr == "" then M.search() end -- если еще нет адреса датчика - получаем
        ow.reset(M.pin) -- у всх так
        ow.select(M.pin, M.addr)
        for _, v in pairs(tb) do -- перебираем таблицу
            ow.write(M.pin, v, 1) -- пинаем байты в линию
        end
        -- а это вообще красота - в Lua есть функция чтения произвольного количества байт
        -- применяем ее
        if read then M.dat = ow.read_bytes(M.pin, 9) end
        ow.reset(M.pin) -- Все!!!!!!!!
    end
     
    Последнее редактирование: 28 фев 2018
  15. ИгорьК

    ИгорьК Гуру

    Мы запрессовали 4 функции в одну! Теперь займемся ее обработчиком.
    Обработчик, как предлагалось раньше, примет таблицу для заполнения и callback функцию, которая будет выполнена после добычи всех данных.
    Обработчик будет
    1. рекурсивно вызвать 4 раза предыдущую функцию
    2. заполнить таблицу
    3. вызвать callback
    Поехали:

    Код (Lua):
    M.update = function(dattbl, call) -- принял таблицу и ссылку на функцию
        local nextcom = 1 -- считаем вызовы
        -- Важный нюанс!!! Если мы хотим рекурсии - объявлять
        -- рекурсивную функцию надо до определения ее тела
        -- Просто надо знать!
        local step
        step = function()
            -- Таймер с интервалом 20 миллисекунд
            tmr.create():alarm(20, 0, function()
            -- Вызываем нашу запрессованную функцию с передачей таблицы
            -- (о таблице - позже), а также передаем true/false путем сравнения
            -- текущего шага с необходимым для вызова чтения, то есть четвертым
                M.writeDS(M.ncom[nextcom],(nextcom == 4) )
                if nextcom == 4 then -- после четвертого вызова получаем данные
                    local t, v = M.count()
                    -- втыкаем их в таблицу
                    dattbl.volt = v
                    dattbl.temp2438 = t
                    -- вызываем callback с передачей ей таблицы
                    if call then call(dattbl) end
                else
                    -- иначе повторяем вызов
                    nextcom = nextcom +1
                    step()
                end
            end)
        end
        step()
    end
    Поясню. Я сделал один таймер, который вызывает функцию 4 раза и каждый раз через 20 мс. Для работы требуется лишь две паузы - 10 и 4 мс. Можно ли это реализовать? Можно! Но это усложнит код. Существенно. Тем не менее, все зависит от нужд проекта. В домашнем хозяйстве этими паузами можно пренебречь. А если у вас будут датчики DS18b20 - отдайте им команды на преобразование температуры и во врем ожидания (там минимум 50 мс, максимум - 750) спокойно обработайте DS2438 - времени вагон.

    Если очень надо - время работы функции можно сократить, например, таким образом:
    Код (Lua):

    M.update = function(dattbl, call)
       M.writeDS(M.ncom[1], false )
       local interval = 10
       local nextcom = 2
       local step
       step = function()
         tmr.create():alarm(interval, 0, function()
           interval = 4
           M.writeDS(M.ncom[nextcom],(nextcom == 4) )
           if nextcom == 4 then
             local t, v = M.count()
             dattbl.volt = v
             dattbl.temp2438 = t
             if call then call(dattbl) end
           else
             nextcom = nextcom +1
             step()
           end
         end)
       end
       step()
    end
     
    И последнее - таблица команд. Вот она:
    Код (Lua):
    M.ncom = {
        {0x44}, -- starttemp
        {0xb4}, --startVolt
        {0xb8, 0x00}, -- recallMem
        {0xBE, 0x00}, -- readPage
        setVAD = {0x4e, 0x00, 0x00}, -- Измерить напряжение на ноге
        setVVD = {0x4e, 0x00, 0x08}, -- Измерить напряжение питания датчика
        copySP = {0x48, 0x00}, -- Сделать любую из вышеуказанных команд по умолчанию
    }
    Да, такая странная. Lua позволяет комбинировать нумерованные и именованные данные (да, товарищи любители ардуино, держите крышу - сносит).
    Первые четыре строки мы и применяем в обработчике, вызывая их по порядковым номерам.
    Последние три именованные строки позволяют вручную или из скрипта установить где измерять напряжение и как это делать потом по умолчанию.

    Наконец, пора опубликовать полный скрипт модуля:
    Код (Lua):
    local M = {}
    M.addr = ""
    M.addrTb = {}
    M.pin = 4
    M.dat = ""

    M.ncom = {
        {0x44}, -- starttemp
        {0xb4}, --startVolt
        {0xb8, 0x00}, -- recallMem
        {0xBE, 0x00}, -- readPage
        setVAD = {0x4e, 0x00, 0x00},
        setVVD = {0x4e, 0x00, 0x08},
        copySP = {0x48, 0x00},
    }

    M.search = function()
        ow.setup(M.pin)
        ow.reset_search(M.pin)
        local adr
        repeat
            adr = ow.search(M.pin)
            if(adr ~= nil) then
                table.insert(M.addrTb, adr)
                if adr:byte(1) == 0x26 then M.addr = adr end
            end
        until (adr == nil)
        ow.reset_search(M.pin)
        print('Got Devices:', #M.addrTb)
    end

    M.count = function()
        local crc = ow.crc8(string.sub(M.dat,1,8))
        if crc == M.dat:byte(9) then
            local mb = M.dat:byte(3)
            local raw = (mb > 127) and (-256 + mb) or mb
            return (raw + (bit.rshift(M.dat:byte(2), 3)) * 0.03125), ((M.dat:byte(5) * 256) + M.dat:byte(4)) / 100
        else
            print('Bad CRC')
            return 85, -1
        end
    end

    M.writeDS = function(tb, read)
        if M.addr == "" then M.search() end
        ow.reset(M.pin)
        ow.select(M.pin, M.addr)
        for _, v in pairs(tb) do
            ow.write(M.pin, v, 1)
        end
        if read then M.dat = ow.read_bytes(M.pin, 9) end
        ow.reset(M.pin)
    end

    M.update = function(dattbl, call)
        local nextcom = 1
        local step
        step = function()
            tmr.create():alarm(20, 0, function()
                M.writeDS(M.ncom[nextcom],(nextcom == 4) )
                if nextcom == 4 then
                    local t, v = M.count()
                    dattbl.volt2438 = v
                    dattbl.temp2438 = t
                    if call then call(dattbl) end
                else
                    nextcom = nextcom +1
                    step()
                end
            end)
        end
        step()
    end

    return M
    И посмотреть как это работает:

    upload_2018-2-28_12-52-35.png
     
    Последнее редактирование: 1 мар 2018
  16. ИгорьК

    ИгорьК Гуру

    Несколько слов в заключение.
    Давайте взглянем еще раз на ардуиновскую библиотеку DS2438 и вспомним ее косяки: не с целью "все они козлы"!
    С простой целью - что мы делаем, когда бездумно копипастим чужие библиотеки, и считаем это нормой.
    Эта библиотека
    - не работает с отрицательными температурами
    - имеет много лишних телодвижений, например для установления источника напряжения выполняется чтение первого байта нулевой страницы, потом к нему подмешивается 0/1 в третьем бите: upload_2018-2-28_13-20-9.png
    - не имеет (я не увидел?) возможности установок по умолчанию
    - втыкает тормоза в программу:
    upload_2018-2-28_13-22-39.png
    Пару таких библиотек, и работа асинхронных функций в ардуино, типа pubsub() или что-то серверное будет нарушаться. (Единственно выручает, что преобразования на модулях, а следовательно - тормоз, вряд ли вызываются часто.)

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

    Работа с шиной 1Wire настолько проста, что поняв несколько азов, вы можете работать с любыми устройствами. Модуль, который мы создали, полагаю, может быть применен к любой железке - вдумчиво меняйте лишь содержание командной таблицы, да разбирайтесь с порядком интерпретации данных от устройств.
    Удачи вам, Lua в помощь.
     
    Последнее редактирование: 28 фев 2018
    naz, alp69 и Securbond нравится это.
  17. Securbond

    Securbond Гуру

    Может дождёмся когда Ув. ИгорьК, напишет библиотеку NeoMatrix для ws2812b на LUA с блеклжеком и ш... русскими шрифтами и эффектами.:rolleyes::rolleyes::rolleyes:
     
  18. ИгорьК

    ИгорьК Гуру

    Имхо, овчинка не цепляет :)
     
  19. virtual

    virtual Нуб

    Приветствую, форумчане! Пробовал кто по http грузить, считывать, исполнять и удалять на модуле файлы?
    Я столкнулся с тем, что можно считать список файлов около 10-12, а если их будет больше, то "
    PANIC: unprotected error in call to Lua API (out of memory)"
     
  20. ИгорьК

    ИгорьК Гуру

    Дык, она же пишет "unprotected error in call to Lua API (out of memory)" , что переводится "чего пристал, я контроллер а не компьютер."