NDEF: данные на NFC-метках
Практическое руководство по NDEF: структура сообщений и записей, URI, Text, Smart Poster, Type 2 TLV, NTAG и запись через ODRFID или ODNFC.
NDEF (NFC Data Exchange Format) — стандартный контейнер для данных, которыми обмениваются NFC-устройства и метки. В NDEF можно записать ссылку, текст, MIME-данные или несколько связанных записей. Смартфон распознаёт формат и решает, что предложить пользователю: открыть сайт, показать текст или передать данные установленному приложению.
Для обычной метки со ссылкой вся цепочка выглядит так:
NTAG213
└── NFC Forum Type 2 Tag: Capability Container и TLV
└── NDEF message
└── URI record: https://unitx.pro
NDEF описывает только два нижних уровня в этом примере: формат сообщения и его размещение по правилам конкретного NFC Forum Tag Type. Это не модель чипа, не UID и не механизм защиты.
Для чего нужен NDEF
NDEF нужен, когда данные должны быть понятны разным телефонам, считывателям и приложениям без собственной структуры блоков:
- ссылка на инструкцию, карточку товара или форму обратной связи;
- короткий текст, инвентарный номер или описание объекта;
- Smart Poster — URI с человекочитаемым заголовком;
- MIME-данные или внешний тип записи для собственного приложения;
- несколько записей в одном сообщении, например URI и дополнительная информация о ресурсе.
Если считыватель настроен только на выдачу UID, содержимое NDEF в этом потоке не появится. Программа должна отдельно прочитать пользовательскую память, найти NDEF-разметку и разобрать сообщение.
Четыре уровня, которые часто путают
| Уровень | Пример | Что определяет |
|---|---|---|
| Чип и радиопротокол | NTAG213, MIFARE Ultralight, NFC-A | команды обмена и физическую память |
| NFC Forum Tag Type | Type 2, Type 4, Type 5 | где искать NDEF и как безопасно читать или менять его |
| NDEF message | одно или несколько records | границы сообщения и порядок записей |
| NDEF record | URI, Text, Smart Poster, MIME | тип и полезную нагрузку одной записи |
Одинаковая URI-запись NDEF может находиться на разных типах меток, но путь к ней будет различаться. Type 2 использует страницы памяти, Capability Container и TLV. Type 4 работает через приложение и файлы поверх ISO-DEP. Поэтому нельзя взять алгоритм записи страниц NTAG и применить его к DESFire только потому, что обе метки могут хранить NDEF.
Как устроено NDEF-сообщение
Сообщение состоит из одной или нескольких записей. У каждой записи есть заголовок, тип, необязательный ID и полезная нагрузка:
[flags + TNF] [type length] [payload length] [ID length?]
[type] [ID?] [payload]
В первом байте находятся флаги:
| Флаг | Значение |
|---|---|
MB | первая запись сообщения |
ME | последняя запись сообщения |
CF | продолжение фрагментированной записи |
SR | Short Record: длина payload занимает один байт |
IL | в заголовке присутствует длина ID |
TNF | как интерпретировать поле Type |
TNF занимает младшие три бита заголовка:
| TNF | Назначение |
|---|---|
0 | пустая запись |
1 | NFC Forum Well Known Type: U, T, Sp и другие RTD |
2 | MIME media type, например application/json |
3 | absolute URI как Type; для обычной ссылки чаще применяют URI RTD |
4 | NFC Forum External Type, например example.com:device |
5 | неизвестный тип, payload интерпретирует приложение |
6 | продолжение chunked record |
7 | зарезервировано |
Для одной короткой URI-записи одновременно установлены MB, ME и SR, а
TNF равен 1. Поэтому первый байт получается 0xD1.
URI
URI RTD имеет тип U. Первый байт payload — код часто используемого префикса:
| Код | Префикс |
|---|---|
00 | без сокращения |
01 | http://www. |
02 | https://www. |
03 | http:// |
04 | https:// |
За ним записывается оставшаяся часть URI в UTF-8. Сокращение экономит несколько байт и особенно полезно на небольших метках.
Text
Text RTD имеет тип T. Первый байт payload содержит кодировку и длину кода
языка. Затем идут код языка, например ru, и сам текст:
[status] [r] [u] [UTF-8 text]
Бит 7 status выбирает UTF-16 или UTF-8, младшие 6 бит задают длину языка. Для новых записей обычно достаточно UTF-8.
Smart Poster
Smart Poster имеет тип Sp. Его payload — ещё одно NDEF-сообщение. Внутри
обязательно находится URI, а рядом могут быть Text-заголовки на разных языках,
предпочтительное действие, MIME-тип и размер ресурса.
Smart Poster полезен как структурированный контейнер, но интерфейс телефона может показать только ссылку. Заголовок и предлагаемое действие не являются командой для операционной системы.
MIME и External Type
MIME-запись удобна, когда payload уже имеет стандартный тип данных. External Type подходит для собственного протокола приложения. В обоих случаях универсальный телефон обычно не знает, что делать с содержимым без установленного приложения. Для публичной метки с сайтом URI RTD совместимее.
Разбор URI по байтам
Запись ссылки https://unitx.pro занимает 14 байт:
D1 01 0A 55 04 75 6E 69 74 78 2E 70 72 6F
| Байты | Значение |
|---|---|
D1 | MB=1, ME=1, SR=1, TNF=1 |
01 | длина Type: 1 байт |
0A | длина payload: 10 байт |
55 | ASCII U, то есть URI RTD |
04 | сокращённый префикс https:// |
75 ... 6F | UTF-8 строка unitx.pro |
Это только NDEF record. На Type 2 Tag он помещается в NDEF Message TLV:
03 0E D1 01 0A 55 04 75 6E 69 74 78 2E 70 72 6F FE
03 обозначает NDEF Message TLV, 0E — длину сообщения, FE — Terminator
TLV. Перед NDEF могут находиться служебные Lock Control и Memory Control TLV;
корректная программа не должна без необходимости стирать их.
Если сообщение короче 255 байт, длина TLV занимает один байт. Для 255 байт и более используется расширенная форма:
03 FF <length high> <length low> ... FE
Type 2 Tag: Capability Container
На MIFARE Ultralight и NTAG страница 3 содержит Capability Container (CC). Например, заводской CC для NTAG213:
E1 10 12 00
| Байт | Значение |
|---|---|
E1 | magic number NDEF |
10 | версия Type 2 Tag mapping 1.0 |
12 | размер NDEF-области: 0x12 × 8 = 144 байта |
00 | свободное чтение и запись |
CC сообщает логический объём NDEF, а не весь объём кремния. Например, в datasheet NTAG215 указано 504 байта пользовательской памяти, но CC объявляет 496 байт NDEF-области; у NTAG216 это 888 и 872 байта соответственно.
На некоторых метках биты страницы 3 программируются только из 0 в 1.
Неверно записанный размер нельзя исправить обычной перезаписью. Поэтому
инициализацию полностью пустого CC следует выполнять только после точного
определения модели чипа.
Практическая ёмкость
Таблица показывает верхнюю границу для одного NDEF Message TLV без служебного префикса. Lock Control, Memory Control и другие TLV уменьшают доступный объём.
| Метка | NDEF-область по CC | Максимум NDEF message без других TLV |
|---|---|---|
| MIFARE Ultralight Nano | 40 байт | 37 байт |
| MIFARE Ultralight / EV1 80 | 48 байт | 45 байт |
| MIFARE Ultralight EV1 164 | 128 байт | 125 байт |
| NTAG213 | 144 байта | 141 байт |
| NTAG215 | 496 байт | 491 байт |
| NTAG216 | 872 байта | 867 байт |
У заводского NTAG213 перед NDEF часто присутствует пятибайтовый Lock Control TLV. Если сохранить его, как делает ODRFIDKit Web, для самого сообщения останется до 136 байт. Именно фактический предел, прочитанный с метки, нужно сравнивать с размером сообщения.
NFC Forum Tag Types
NDEF не ограничен NTAG:
| Tag Type | Базовая технология | Типичное хранение |
|---|---|---|
| Type 2 | NFC-A | CC и TLV в линейных страницах |
| Type 3 | NFC-F | атрибуты и блоки NFC-F |
| Type 4 | NFC-A или NFC-B, ISO-DEP | NDEF application, CC file и NDEF file |
| Type 5 | NFC-V, ISO 15693 | CC и TLV в памяти NFC-V |
Конкретный чип поддерживает NDEF только при наличии правильной разметки или приложения. Например, ISO-DEP сам по себе ещё не означает, что на карте создано NDEF-приложение Type 4.
Запись через ODRFIDKit Web
ODRFIDKit Web подключается к считывателю через Web Serial. NDEF-режим предназначен для совместимых NFC Forum Type 2 Tag:
- MIFARE Ultralight и Ultralight Nano;
- MIFARE Ultralight EV1 80 и 164;
- NTAG213, NTAG215 и NTAG216.
Режим не форматирует MIFARE Classic и не работает с Type 4 NDEF application. Это намеренная граница: у Classic запись NDEF требует MAD, новых ключей и изменения sector trailers, а у Type 4 — команд ISO-DEP и файловой модели.
Порядок работы
- Подключите ODRFID-M или ODRFID-N и разрешите браузеру доступ к последовательному порту.
- Поднесите Type 2 метку и нажмите NDEF.
- Нажмите Перечитать NDEF. Программа проверит CC, найдёт NDEF TLV и сохранит служебные TLV перед ним.
- Выберите URI, Text или Smart Poster. Поле байтов и индикатор ёмкости обновляются сразу.
- Проверьте содержимое и доступный объём, подтвердите изменение памяти и нажмите Записать и проверить.
- Не убирайте метку, пока приложение не сообщит о завершении проверки.
Если CC полностью нулевой, приложение отдельно предложит его инициализировать. Не включайте этот флажок для неизвестной модели. Если CC уже содержит другое ненулевое значение, программа откажется перезаписывать его автоматически.
При записи веб-приложение:
- сверяет размер CC с профилем обнаруженной метки;
- сохраняет TLV перед существующим NDEF;
- временно ставит Terminator TLV, чтобы незавершённый payload не выглядел готовым сообщением;
- записывает данные, а заголовок с фактической длиной публикует последним;
- повторно читает все изменённые страницы и сравнивает их побайтово.
Приложение не устанавливает биты блокировки и не переводит метку в необратимый режим «только чтение». Если память защищена паролем, сначала задайте действующий пароль в панели Ultralight. Ошибка аутентификации или физической записи остановит операцию.
Подключение считывателя, назначение остальных кнопок и работа с памятью MIFARE Classic и T5577 показаны в полной инструкции ODRFIDKit Web.
Как выглядит NDEF-режим
Редактор можно открыть до подключения считывателя, подготовить URI, Text или Smart Poster и сразу увидеть готовые байты. После чтения метки он дополнительно показывает её тип, Capability Container, фактическую ёмкость NDEF и текущее содержимое; только тогда становятся доступны операции чтения и записи.


Консольная утилита ndef_writer.py
У нас есть также консольная утилита def_writer.py для ODRFID.
Он формирует записи через ndeflib и поддерживает:
- URI и Text;
- Smart Poster с заголовком, действием, MIME-типом, размером и PNG-иконкой;
- пакетную подготовку URI или Text из файла;
- MIFARE Classic 1K через MAD1 и NDEF AID
E103; - Type 2 метки Ultralight и NTAG.
Зависимости:
python3 -m pip install ndeflib pyserial crc
Примеры для Linux:
# URI
python3 ndef_writer.py -p /dev/ttyACM0 uri "https://unitx.pro"
# UTF-8 Text с кодом языка
python3 ndef_writer.py -p /dev/ttyACM0 text "Оборудование UnitX" --lang ru
# Smart Poster
python3 ndef_writer.py -p /dev/ttyACM0 poster "https://unitx.pro" \
--title "ru:Оборудование UnitX"
# Подробный обмен AT-командами
python3 ndef_writer.py -v -p /dev/ttyACM0 uri "https://unitx.pro"
В macOS порт обычно имеет вид /dev/cu.usbmodem..., в Windows — COM3 или
другой номер из диспетчера устройств. Глобальные параметры -p, -b, -v и
--cloff указываются перед подкомандой.
Пакетный режим ожидает в каждой непустой строке данные и идентификатор оператора:
https://unitx.pro/item/001 ITEM-001
https://unitx.pro/item/002 ITEM-002
python3 ndef_writer.py -p /dev/ttyACM0 batch tags.txt --type uri
Утилита запрашивает подтверждение для каждой метки, но серийный процесс всё равно должен включать независимое чтение результата и выборочную проверку телефоном.
Важное ограничение MIFARE Classic
Для Classic утилита использует MAD1, стандартный NDEF Key A
D3F7D3F7D3F7, меняет sector trailers и каталог приложений в секторе 0.
Поддерживается раскладка MIFARE Classic 1K.
Перед первой операцией:
- сохраните полный дамп исходной карты;
- используйте отдельную тестовую Classic 1K с заводскими ключами;
- проверьте чтение именно теми телефонами и приложениями, которые будут использоваться;
- не запускайте
formatна пропуске или карте с чужой структурой секторов.
Команда format использует переданный --key как текущий Key B для
аутентификации, затем стирает существующую конфигурацию доступа и возвращает
оба ключа и access bits sector trailer к заводским значениям:
python3 ndef_writer.py -p /dev/ttyACM0 format --key FFFFFFFFFFFF
Для обычных смартфонных меток практичнее NTAG и веб-режим Type 2: MIFARE Classic NDEF поддерживается не всеми телефонами и требует гораздо более инвазивного форматирования.
Работа на ODNFC
В ODNFC с прошивкой Lua высокоуровневый модуль
mfrc522 может читать и записывать NDEF непосредственно в сценарии
считывателя:
-- После настройки UART и mfrc522:
ok, err = mfrc522.ndef_write_uri(uart.UART1, "unitx.pro", 0x04)
if not ok then
print("NDEF write error:", err)
end
ndef, err = mfrc522.ndef_read(uart.UART1)
if ndef then
print(ndef.type, ndef.uri or ndef.text)
else
print("NDEF read error:", err)
end
Доступные функции:
mfrc522.ndef_read(uart_id)возвращает raw NDEF и разбирает первую URI или Text record;mfrc522.ndef_write_uri(uart_id, uri, prefix_code)создаёт одну короткую URI record;mfrc522.ndef_write_text(uart_id, text, lang)создаёт одну короткую Text record в UTF-8.
Lua API ODNFC работает с Type 2 Ultralight/NTAG. Размер raw NDEF ограничен 256
байтами, а строковое представление первой URI или Text record — 199 байтами.
Smart Poster, MIME, External Type и сообщения из нескольких записей
можно увидеть в raw, но высокоуровневый разбор вернёт для первой
неподдерживаемой записи тип unknown.
Сигнатуры, коды URI и полный пример приведены в справочнике модуля MFRC522.
ODNFC подходит, когда реакция на метку должна выполняться автономно: проверить URI или текст, включить выход, записать событие или передать результат на сервер. ODRFID удобнее для подготовки меток на рабочем месте.
Совместимость со смартфонами
Для публичной метки выбирайте самый простой формат, достаточный для задачи:
| Сценарий | Рекомендация |
|---|---|
| Открыть веб-страницу | одна URI record на NTAG |
| Показать подпись рядом со ссылкой | Smart Poster, но рассчитывать прежде всего на URI |
| Передать короткий текст | одна UTF-8 Text record |
| Открыть функцию собственного приложения | MIME или External Type и тест на обеих ОС |
| Хранить много данных | записать короткий URI на серверный ресурс |
Поведение зависит от модели телефона, версии ОС, настроек NFC, состояния экрана и установленных приложений. Проверяйте не только чтение в технической NFC-утилите, но и пользовательский сценарий: уведомление, текст, переход по ссылке и работа без вашего приложения.
Защита и безопасность
NDEF по умолчанию читается любым находящимся рядом совместимым устройством. Поэтому:
- не храните в открытом payload пароль, API key, постоянный bearer token или персональные данные;
- для индивидуальной ссылки используйте короткоживущий или отзывной идентификатор, а права проверяйте на сервере;
- не считайте UID секретом или криптографическим доказательством;
- перед включением password protection проверьте, останется ли требуемое чтение доступным обычным телефонам;
- делайте необратимую блокировку только после чтения, записи, повторного чтения и проверки на целевых устройствах.
NDEF Signature RTD может подтверждать целостность и происхождение записей, но не шифрует payload и не определяет сам по себе инфраструктуру доверия, проверку сертификатов или отзыв ключей.
Если метка не читается
- Сначала определите тип чипа. Убедитесь, что программа правильно сопоставила значение Tag Type с типом метки.
- Прочитайте CC. Для Type 2 проверьте сигнатуру
E1, объявленный размер и разрешение на чтение. - Разберите TLV от страницы 4. Учтите NULL, Lock Control и Memory Control TLV; NDEF не обязан начинаться первым байтом.
- Проверьте длины. TLV и запись NDEF не должны выходить за CC или фактически считанные данные.
- Проверьте флаги. В NDEF-сообщении с одной записью нужны и
MB, иME; короткая запись с однобайтной длиной должна иметьSR. - Проверьте защиту. CC, биты блокировки, пароль и конфигурационные страницы — независимые ограничения.
- Сравните с другим приложением и телефоном. Успешное чтение памяти без преобразования ещё не гарантирует, что операционная система поддерживает этот тип записи.