Перейти к содержанию

Программирование ODNFC на Lua

Руководство по программированию считывателя-контроллера ODNFC на Lua: обработчик mfrc522.scan, веб-IDE, примеры UDP, OSC, HTTP, MQTT и TCP.

Это руководство по программированию RFID-считывателя-контроллера ODNFC на Lua. Прошивка построена на Lua RTOS и объединяет Lua 5.3 и FreeRTOS.

Общие модули и API среды исполнения собраны в справочнике Lua.

Если вы будете разрабатывать с помощью ИИ, скопируйте промпт для Lua.

Ключевые правила разработки

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

  • Библиотеки. В прошивке есть модули для считывателя, индикации, сети и другой периферии; часть API оформлена как классы. Состав встроенных модулей зависит от модели и версии прошивки. Менеджер пакетов repo не установлен: дополнительные Lua-файлы загружайте вместе с проектом.
  • Пользовательский код. Подавляющее большинство задач решается в файле usercode.lua через веб-интерфейс. Для большого или специфического проекта используйте консоль, онлайн-среду или плагин для VS Code и разделяйте код на модули.
  • Кооперативная многозадачность. В каждом процессе должно быть место, где он передаёт управление другим задачам — например, через thread.sleep или блокирующий ввод-вывод.
  • Время исполнения и веб-интерфейс. Если перегрузить основную задачу, ей не хватит времени отдать браузеру веб-интерфейс, и тот разорвёт соединение. Всегда оставляйте время другим потокам — добавьте в основной цикл строку вроде thread.sleepms(10).
  • Ограниченные ресурсы. Памяти и вычислительной мощности у устройства мало: не используйте без необходимости большие структуры, долгие вычисления и много потоков. Сначала проверьте, нет ли нужной операции во встроенном модуле.
  • Сборщик мусора (GC). Lua — скриптовый язык, и GC может вызывать заметные паузы при высокой нагрузке. Не создавайте много временных объектов; при необходимости вызывайте GC сами там, где короткая пауза допустима.
  • Порядок разработки. Код, не зависящий от железа, отлаживайте на «обычном» Lua 5.3 для ПК. Аппаратное взаимодействие проверяйте построчно в консоли устройства через USB, Telnet, веб-IDE, WebSocket REPL или VS Code. Итоговый вариант загружайте целиком и проверяйте журнал на первой вкладке. Если проекту не хватает оперативной или флеш-памяти, используйте версию PRO и повторно измерьте запас ресурсов на рабочей нагрузке.
  • Применение нового кода. Нажмите «Сохранить» и перезагрузите устройство, убедившись, что перезагрузка прошла. Браузер не всегда «подхватывает» перезагрузившуюся страницу — обновите её вручную.
  • Сторожевой таймер. Перезагружает устройство при зависании. Если он включён в настройках, у программы есть 5 секунд на его сброс — поставьте сброс в основной (или самый ответственный) цикл. На время разработки отключайте, но не забудьте включить перед установкой.
  • Сетевая безопасность. Для удобной удалённой работы доступны Ethernet, Wi-Fi, BLE, WebSocket REPL, Telnet и веб-интерфейс. После разработки и перед установкой отключите неиспользуемые службы и задайте пароли для оставшихся.

Онлайн-среда (веб-IDE)

На странице lua.unitx.pro находится веб-среда разработки, которую можно открыть в браузере или установить на компьютер.

  • По WebSocket. Включите в устройстве HTTPS и WebSocket REPL. Возможно, потребуется один раз вручную подтвердить в браузере самоподписанный сертификат. После подключения можно ходить по файловой системе, редактировать файлы и пользоваться терминалом.
  • По последовательному порту. Нужен отладочный USB-донгл. Браузер запросит доступ к USB-порту. Этот режим не зависит от сетевого соединения.
Онлайн-среда разработки Lua для ODNFC
Онлайн-среда Lua: работа в браузере или отдельном окне PWA.
Подключение к ODNFC по WebSocket в онлайн-среде Lua
Подключение по WebSocket: включите HTTPS и WebSocket REPL.
Файловая система и редактор в онлайн-среде Lua для ODNFC
После подключения доступны файлы, редактор и терминал.
Последовательное подключение к ODNFC в онлайн-среде Lua
Вариант подключения через отладочный USB-донгл.
Запрос доступа к USB-порту в онлайн-среде Lua
Браузер запросит разрешение на доступ к USB-порту.
Терминал последовательного подключения в онлайн-среде Lua
Работа через USB не зависит от сетевого соединения.

Плагин для VS Code

Плагин для VS Code позволяет разрабатывать в привычной IDE. Установка: Extensions → … → Install from VSIX. Для подключения по WebSocket включите в устройстве WebSocket REPL. Плагин может подключаться без HTTPS; используйте такой режим только в доверенной локальной сети и не публикуйте порт в интернет. Нажмите шестерёнку в плагине, выберите тип подключения и задайте сетевой адрес, затем нажмите Connect.

Установка плагина ODNFC Lua для VS Code из файла VSIX
Установка: Extensions → … → Install from VSIX.
Настройки подключения к ODNFC в плагине VS Code
В настройках выберите транспорт и сетевой адрес.
Кнопка Connect в плагине VS Code для ODNFC
После настройки нажмите Connect.
Файлы устройства ODNFC в плагине VS Code
После подключения доступны файлы устройства и терминал.

Настройки пользовательской программы

Библиотека настроек подключается как модуль settings. Сначала создайте своё пространство имён и задайте значения по умолчанию. Они сохранятся в NVS и останутся после перезагрузки.

local make_settings = require("settings")

local cfg = make_settings("odnfc_usercode", {
    destination = "192.168.1.10:5555",
    scan_result_type = 0,
})

local options = cfg.load()
print("destination:", options.destination)

cfg.set("destination", "192.168.1.20:5555")

Методы cfg.get, cfg.set, cfg.load, cfg.save, cfg.restore и cfg.clear описаны в справочнике settings.lua. Системные параметры веб-интерфейса принадлежат прошивке устройства. Шаблоны ниже читают sys_wdt и net_dest через глобальный объект Settings: сеть запускает системный код, поэтому вручную вызывать net.wf.setup() в usercode.lua не нужно.

Быстрый старт: от запуска до чтения карты

Начинайте с одного действия и добавляйте следующее только после проверки предыдущего. Каждый фрагмент ниже — самостоятельный usercode.lua.

1. Убедиться, что программа запустилась

local board, subtype, brand = cpu.board()

print("usercode.lua started")
print("device:", board, subtype, brand)
print("free memory:", cpu.memfree())

После сохранения и перезапуска эти строки должны появиться в консоли или журнале веб-интерфейса.

2. Прочитать UID в консоль

local wdt_on = Settings.get("sys_wdt") == "on"

while true do
    if wdt_on then cpu.watchdog.reset() end

    local callback_ok, err = mfrc522.scan(0, function(uid)
        print("UID:", uid)
    end)
    if callback_ok == false then
        print("scan callback error:", err)
    end
    thread.sleepms(50)
end

0 выбирает UID как шестнадцатеричную строку в верхнем регистре. Функция-обработчик вызывается только после чтения карты; отсутствие карты — нормальное состояние, а не ошибка.

3. Добавить свет и звук

local wdt_on = Settings.get("sys_wdt") == "on"

local leds = indication.Leds.new()
local snd = indication.Sound.new()

leds:start()
snd:start()

while true do
    if wdt_on then cpu.watchdog.reset() end

    local callback_ok, err = mfrc522.scan(0, function(uid)
        print("UID:", uid)
        leds:ok()
        snd:ok()
    end)
    if callback_ok == false then
        eprint("MFRC522: " .. tostring(err))
        leds:err()
        snd:err()
    end
    thread.sleepms(50)
end

Конструкторы без явных пинов используют настройки конкретной платы, поэтому этот фрагмент подходит и для ODNFC-LAN, и для ODNFC-LAN-C.

Примеры сценариев

Все семь вариантов собраны в каталоге шаблонов программ ODNFC: там можно сравнить сценарии, посмотреть совместимость и скачать отдельный .lua-файл. Ниже подключены те же файлы, поэтому код в документации и скачиваемый шаблон не расходятся. Основной вариант — передача UID по UDP.

Передача по UDP — основной шаблон.

local get, sleepms = Settings.get, thread.sleepms

-- Сеть запускается системным кодом по Web-настройкам.
local host, _, _, port = net.parseUrl(get("net_dest"))
local wdt_on = get("sys_wdt") == "on"

local leds = indication.Leds.new()
local snd = indication.Sound.new()

leds:start()
snd:start()

local function on_uid(uid)
    net.udp.sendto(host, port, uid)
    print("UDP sent:", uid)
    leds:ok()
    snd:ok()
end

local function iteration()
    local ok, err = mfrc522.scan(0, on_uid)
    if ok == false then error(err, 0) end
end

while true do
    if wdt_on then cpu.watchdog.reset() end

    local ok, err = pcall(iteration)
    if not ok then
        local message = tostring(err)
        if message:find("interrupted!", 1, true) then
            error(err, 0)
        end
        eprint("Main loop: " .. message)
        leds:err()
        snd:err()
        sleepms(250)
    else
        sleepms(50)
    end
end

net.udp.sendto при успехе ничего не возвращает, поэтому результат здесь проверяется через защищённый вызов общего шага цикла. Адрес и порт берутся из Web-настройки net_dest.

Дополнительные рецепты

Отправка почты

Рассчитано на версию PRO — в остальных может не заработать (зависит от нагрузки и сертификатов почтового сервера).

-- имя и порт почтового сервера
net.curl.mailserver("smtp.адрес.сервера", 465)

options = {
    user = "логин",
    pass = "пароль",
    to = {"получатель1", "получатель2"},
    subj = "Тема",
    msg = "Тестовая посылка",
    secure = true,
    attach = {"picture.jpg"}
}

ret, msg = net.curl.sendmail(options)

Добавление UID с сервера по HTTP GET

-- host, path, content-type, headers, ssl_en
local code, chunks = net.http.get(
    "192.168.0.160",
    "/userlist.txt",
    "text/plain",
    nil,
    false
)

if code == 200 and chunks then
    local file = assert(io.open("userlist.txt", "a"))
    -- "a" — append для текстового списка;
    -- большой список экономнее хранить как uint32 UID в бинарном виде
    file:write(table.concat(chunks))
    file:close()
end

Отправка UDP и TCP

--- udp ---
net.udp.sendto("192.168.68.110", 5555, "hello")

--- tcp ---
net.tcp.sendto("192.168.68.110", 5555, "hello")

Добавление UID с сервера по UDP

Аналогично можно пополнять список через MQTT или HTTP.

local socket = net.udp.bind(5555)

while true do
    local from_ip, from_port, data = socket:readfrom(1024)
    if from_ip then
        print(from_ip, from_port, data)
        local file = assert(io.open("userlist.txt", "a"))
        file:write(data, "\n")
        file:close()
    end
    thread.sleepms(100)
end

TCP-сервер

Доступен с прошивки 1.5.0. Данные могут приходить дроблёными — буферизируйте их и собирайте в обработчике. Передаются «как есть», могут включать непечатаемые символы и 0 (для Lua-строк это нормально).

local SERVER_PORT = 8080

local function handle_client(client_ip, data)
    -- убираем перевод строки
    local clean_data = data:gsub("\r", ""):gsub("\n", "")
    print("client: " .. client_ip)
    print("вы ввели: " .. clean_data)
end

print("Запуск TCP сервера на порту", SERVER_PORT)
-- true перенаправляет print из callback обратно TCP-клиенту
net.tcpserv.start(SERVER_PORT, handle_client, 60, true)

Дополнительные материалы

  • Строка форматирования — как читать данные с карты, а не только UID.
  • mfrc522.scan — форматы результата, функции-обработчики и режимы обслуживания считывателя.
  • PIO — прямое управление входами и выходами.
  • Сеть — HTTP, UDP, TCP и сетевые интерфейсы.
  • Консоль — работа с файловой системой, редактором и дополнительными библиотеками.
  • Пример: Modbus TCP.

История обновлений ODNFC на Lua

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

1.8.2
  • Улучшена работа устройства и исправлены ошибки.
  • Для устройств, работающих с метками 125 кГц, добавлено обнаружение типовых клонов EM-Marine.
1.8.1

Улучшения:

  • Новый веб-интерфейс.
  • Улучшена работа RFID-модулей MFRC и EM-Marine.
  • Улучшена работа Modbus.
  • Оптимизирована работа устройства.
1.7.0

Улучшения:

  • улучшена работа MFRC и EM-Marine;
  • в раздел «Редактор» добавлен простой файловый менеджер;
  • улучшена работа сетевых модулей;
  • оптимизирована работа устройства.

Исправлена ошибка в HTTP-заголовке Content-Length, из-за которой интерфейс не работал в некоторых операционных системах.

1.6.2
  • Улучшена работа MFRC и EM-Marine.
  • Улучшена библиотека Modbus.
  • Оптимизирована работа устройства.
  • В ODNFC-LAN-LUA добавлена регистрация обработчика успешного чтения.
1.6.1
  • Добавлена запись в зашифрованные карты MIFARE Classic.
  • Упрощена инициализация RFID: используются значения по умолчанию для каждой платы, поэтому изменён вызов основного класса и больше не требуются многие Lua-обёртки из rfid.lua.
  • Добавлена функция mfrc522.scan() — упрощённая версия базового класса для типовых операций.
  • Исправлены библиотеки EM-Marine и HID.
  • Оптимизирована библиотека Modbus.
  • Исправлена работа TCP-сервера.
1.6.0

Улучшения:

  • обновлён редактор кода: устранена проблема с переводами строк в новых версиях Firefox;
  • в веб-сервер добавлена поддержка HTTPS и WebSocket;
  • добавлены новые классы для работы с RFID-метками;
  • добавлена работа с NDEF и счётчиками Ultralight;
  • добавлены встроенные библиотеки cbor, cobs, mlib, Heatshrink и microtar;
  • расширены библиотеки osc, net, cutils, utils, cpu и emmarine;
  • LuaSocket больше не входит в прошивку;
  • добавлена загрузка сжатых Heatshrink-библиотек Lua;
  • добавлен WebSocket REPL для отладки через веб-IDE или расширение VS Code;
  • улучшена консоль: Unicode, автодополнение и история;
  • расширена поддержка Modbus;
  • Paho MQTT заменён собственной реализацией uMqtt;
  • ускорена работа устройства и веб-интерфейса;
  • выполнены другие небольшие исправления и оптимизации.

Исправлены ошибки, из-за которых после мягкой перезагрузки Wi-Fi запускался через раз, а RFID-модуль мог не инициализироваться.

1.5.0

Улучшения:

  • новый интерфейс и редактор кода;
  • добавлена библиотека tinycobs;
  • добавлены функции net.tcp.*;
  • добавлены функции eprint, hprint и wprint для вывода в окно «Инфо»;
  • трассировка ошибки загрузки теперь показывается в окне «Инфо»;
  • расширены возможности Telnet, SSH отключён;
  • в режиме восстановления Wi-Fi работает без пароля;
  • добавлена поддержка BLE-маяков на устройствах с BLE;
  • NTP запускается с задержкой 15 секунд, чтобы сеть успела установиться.

Исправлены режим статического Ethernet-адреса и установка яркости NeoPixel.

1.4.0
  • Улучшен встроенный веб-сервер.
  • Выполнены небольшие изменения для повышения стабильности.
  • Исправлена утечка памяти при HTTP-запросах из Windows.

Известная проблема этой версии: режим статического Ethernet-адреса в настройках не работает. Для обхода проблемы в начало пользовательской программы добавляли:

net.en.setup(
  net.packip(Settings.get("net_ip")),
  net.packip(Settings.get("net_mask")),
  net.packip(Settings.get("net_gw")),
  net.packip(Settings.get("net_dns"))
)
net.en.start()
1.3.2
  • В os.df() добавлен дополнительный параметр.
  • Выполнены небольшие изменения для повышения стабильности.
  • Исправлена утечка памяти при запросе метрики в интерфейсе.

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

1.3.1
  • Серийный номер синхронизирован с DataMatrix на корпусе модуля, если он есть.
1.3
  • Библиотека Telegram.lua добавлена в прошивку.
  • Библиотека LuaSocket теперь распространяется отдельно.