Emmarine
Модуль для работы с RFID-метками EM4100 (125 кГц)
Модуль emmarine считывает RFID-метки формата EM4100 на частоте 125 кГц. Для
T5555/T5577 он также предоставляет активное чтение заводского Trace ID и
защищённую паролем запись изменяемой части EM4100-кода.
Одновременная работа emmarine и hid доступна на устройствах, прошивка которых поддерживает оба модуля. Набор модулей и назначение выводов зависят от модели устройства.
Методы, принимающие объект считывателя, можно вызывать и в объектном синтаксисе:
reader:uid(...), reader:trace(), reader:rotate(...), reader:raw(),
reader:close().
emmarine.new([rxpin, txpin, pwm_channel, max_items])
Создаёт объект RFID-считывателя.
Одновременно может существовать только один объект считывателя. Перед повторным вызовом new() закройте предыдущий объект методом close().
Аргументы:
- rxpin (необязательно): вывод GPIO для приёма данных, 0..39;
- txpin (необязательно): вывод GPIO для антенны, 0..33; выводы 34–39 работают только как входы;
- pwm_channel (необязательно): канал PWM для несущей 125 кГц, 0..15;
- max_items (необязательно): размер буфера захвата, не менее 64.
Если параметры не указаны, используются значения, заданные для модели устройства.
Возвращает объект считывателя или вызывает исключение при ошибке.
Методы модуля принимают объект, созданный функцией emmarine.new().
-- Создание ридера с параметрами по умолчанию
reader = emmarine.new()
-- Создание ридера с пользовательскими параметрами
reader = emmarine.new(4, 15, 6, 1024)
emmarine.uid(reader, [res_type])
emmarine.uid(reader, callback, …)
emmarine.uid(reader, [res_type], callback, …)
Считывает UID RFID-метки.
Аргументы:
- reader: объект считывателя.
- res_type (необязательно): формат результата. По умолчанию 0. Возможные значения:
- 0: UID как hex-строка 32-битного значения (без ведущих нулей).
- 1: UID как десятичная строка из 10 символов (с ведущими нулями).
- 2: UID как строка в формате XXX.XXXXX.
- 3: UID как строка в формате XXX,XXXXX.
- 4: UID как hex-строка полного 40-битного UID (10 символов).
- 5: UID как 5 отдельных байт (несколько возвращаемых значений).
- 6: младшие 32 бита UID как число.
- 7: UID как бинарная строка из 5 байт.
- callback (необязательно): функция, вызываемая только после успешного чтения. Все следующие аргументы передаются в неё перед UID. Если res_type не задан, UID передаётся пятью отдельными байтами. В качестве обработчика принимается только функция.
Возвращает: UID метки в указанном формате или ничего, если метка не обнаружена или произошла ошибка декодирования.
Если UID не найден, обработчик не вызывается и uid() не возвращает значений. После вызова обработчика функция возвращает true и его результаты либо false, err, если обработчик завершился ошибкой. Ошибки оборудования, аргументов и состояния считывателя по-прежнему вызывают исключение.
-- Создание ридера
reader = emmarine.new()
-- Считывание UID
uid_num = emmarine.uid(reader, 0)
if uid_num then
print("UID:", uid_num)
end
-- Lua callback: UID приходит пятью байтами полного 40-битного значения
local function print_uid(...)
local uid = ""
for i = 1, select("#", ...) do
uid = uid .. string.format("%02X", select(i, ...))
end
print("UID:", uid)
end
emmarine.uid(reader, print_uid)
-- Lua callback с пользовательскими аргументами перед UID
local function store_uid(offset, ...)
modbus.setregs8(offset, ...)
end
emmarine.uid(reader, store_uid, 4) -- store_uid(4, uid_byte1, ..., uid_byte5)
-- Передать UID напрямую в регистры Modbus
emmarine.uid(reader, modbus.setregs8, 4)
-- Проверка результата callback: nil/нет значений значит, что метки нет
local ok, err = emmarine.uid(reader, modbus.setregs8, 4)
if ok == false then
print("Callback error:", err)
end
-- То же самое с явным форматом результата: UID как список байтов
emmarine.uid(reader, 5, modbus.setregs8, 4)
-- UID как 40-битная hex-строка в callback
emmarine.uid(reader, 4, function(uid)
print("UID:", uid)
end)
-- UID как младшие 32 бита в callback
emmarine.uid(reader, 6, function(uid32)
print("UID32:", uid32)
end)
-- UID как бинарная строка из 5 байт, с префиксным аргументом offset
emmarine.uid(reader, 7, function(offset, uid)
modbus.setregs8(offset, uid)
end, 4)
Границы защиты T55xx
Механизмы T55xx не являются криптографической аутентификацией метки. Режим пароля ATA5577 ограничивает доступ к командам записи, но ни пароль, ни данные метки не шифруются при передаче по радиоканалу. Эмулятор, не отвечающий на команду Page 1, микросхема с изменяемой Page 1 или адаптивный нарушитель, наблюдающий либо ретранслирующий весь обмен, могут обойти допущения статической защиты.
Для прикладных политик отклонения T55xx, привязки UID к Trace ID и динамической
ротации используйте библиотеку antclone.lua.
Допустимая формулировка для режима reject_t55xx:
Распознаёт и отклоняет типовые копии EM-Marine/HID, записанные на T5557/T5577.
Этот режим не обнаруживает любой возможный клон. До завершения аппаратной проверки, перечисленной ниже, нельзя публиковать заявления о готовой защите.
emmarine.trace(reader)
Активно запрашивает Page 1 метки T55xx и возвращает проверенный заводской Trace ID.
Аргументы:
- reader: объект считывателя.
Результаты:
"T5577", "E015123489ABCDEF"— распознана ATA5577/T5577;"T5555", "..."— распознана T5555/Q5;- ничего — проверенный ответ T55xx не найден.
Ошибки оборудования, периферии и внутренние ошибки вызывают исключение Lua и не подменяются отсутствием метки. Частично декодированный или непроверенный Trace ID не возвращается. Распознавание T5555/Q5 намеренно консервативно: этот тип возвращается только тогда, когда активный ответ Page 1 можно отличить от пассивного потока EM4100.
local chip, trace = emmarine.trace(reader)
if chip then
print("Тип:", chip, "Trace ID:", trace)
end
Для проверки передаётся двухбитная команда обычного чтения Page 1 11.
Считыватель последовательно пробует режимы downlink fixed bit length, long
leading reference, leading zero и 1-of-4. В каждом захвате требуется
повторяющийся 64-битный кадр Page 1, а результат принимается только после двух
одинаковых активных декодирований. Декодер поддерживает Manchester, BiPhase,
NRZ, FSK и PSK и проверяет структуру производителя E015/E039.
emmarine.rotate(reader, expected_uid, expected_trace, next_uid, password)
Записывает следующий одноразовый токен в заранее подготовленную T5577 с включённым режимом пароля и разметкой EM4100.
Аргументы:
- reader: объект считывателя;
- expected_uid: текущий UID — ровно 10 шестнадцатеричных символов;
- expected_trace: ожидаемый Trace ID — ровно 16 шестнадцатеричных символов;
- next_uid: следующий UID — ровно 10 шестнадцатеричных символов;
- password: индивидуальный 32-битный пароль метки — ровно 8 шестнадцатеричных символов.
Неизменяемый 19-битный префикс текущего и следующего UID должен совпадать. Изменяться может только 21-битный токен: Block 1 EM4100 остаётся неизменным, а запись выполняется только в Page 0 Block 2.
Функция выполняет следующую последовательность:
- читает и проверяет текущие UID, тип T5577 и Trace ID;
- выполняет защищённую паролем запись без доступа к lock bits;
- перезапускает поле;
- повторно проверяет новый UID и неизменившийся Trace ID.
Возвращает true только после успешного контрольного чтения. При ожидаемом
отказе возвращает false, reason; аппаратные и внутренние ошибки вызывают
исключение. Любой результат, отличный от true, запрещает открытие двери.
local ok, reason = emmarine.rotate(
reader,
"123456789A",
"E015123489ABCDEF",
"1234564321",
individual_password
)
if ok ~= true then
print("Ротация отклонена:", reason)
end
Пароль должен быть уникальным для каждой метки. Его нельзя печатать в обычный
журнал или хранить в доступных через веб-интерфейс каталогах /public и
/www. Ротация пароля не входит в операцию прохода: она создаёт дополнительную
точку необратимого рассогласования, но не добавляет шифрование радиоканала.
emmarine.random_token()
Возвращает 21-битное значение от аппаратного генератора случайных чисел ESP32. Функция предназначена для создания изменяемой части UID в схеме динамического пропуска.
local token = emmarine.random_token()
Сам генератор не проверяет уникальность. Прикладная система обязана проверять коллизии в общей атомарной базе данных.
emmarine.raw(reader)
Получить сырые данные захвата для отладки и анализа.
Аргументы:
- reader: объект считывателя.
Возвращает таблицу вида:
- count: количество захваченных элементов.
- overflow: признак переполнения буфера.
- data: массив (до 256 элементов), где каждый элемент содержит:
- us: длительность в микросекундах.
- edge: тип фронта (0 или 1).
raw = emmarine.raw(reader)
print(raw.count, raw.overflow)
emmarine.close(reader)
Закрывает считыватель. После этого можно создать новый объект через new().
Аргументы:
- reader: объект считывателя.
Возвращает: ничего. Повторный вызов close() безопасен.
Закрывайте считыватель явно, чтобы сразу освободить его ресурсы и создать новый объект.
emmarine.close(reader)
-- или
reader:close()
Обязательная аппаратная проверка T55xx
Перед включением функций T55xx в рабочей системе необходимо проверить их на реальном оборудовании ODNFC-LAN и ODNFC-RS485:
- частоту несущей 125 кГц и коэффициент заполнения 50%;
- start gap, write gaps, интервалы данных 0/1 и все четыре режима downlink;
- непрерывность поля и отсутствие лишнего провала при переключении PWM/RMT;
- момент запуска capture относительно ответа и восстановление после успеха, таймаута и ошибки записи;
- оригинальную EM4100, HID Prox, ATA5577 в режиме EM4100, метку с другим Trace ID, T5555/Q5, защищённую паролем метку, неверный пароль, все режимы downlink и HID/FSK;
- отдельно дальность обычного чтения, проверки Page 1 и защищённой записи;
- при наличии — эмулятор, не отвечающий на Page 1, и микросхему с изменяемой Page 1; эти ограничения должны оставаться в описании границ защиты.
Следует сохранить трассы логического анализатора, точный ELF прошивки и полные журналы выполнения. Успешная сборка или host-тест декодера не является проверкой радиотракта. При планировании ротации также учитывайте меньшую дальность записи и ресурс EEPROM ATA5577 — приблизительно 100 000 записей на блок.