Программирование 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-порту. Этот режим не зависит от сетевого соединения.






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




Настройки пользовательской программы
Библиотека настроек подключается как модуль 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 теперь распространяется отдельно.