OSDP
OSDP в Lua RTOS
Модуль поддерживает обе роли OSDP: контроллер доступа Control Panel (CP) и периферийное устройство Peripheral Device (PD). В штатной прошивке ODNFC-RS485 доступны OSDP, Modbus и UART; для других устройств набор модулей может отличаться.
Наличие обоих модулей в прошивке не означает, что они могут одновременно использовать один физический UART/RS-485 интерфейс. Приложение должно выбрать один протокол, освободить UART перед переключением и не запускать параллельно циклы обмена Modbus и OSDP на одной шине.
Версию модуля можно проверить во время выполнения:
local version, source = osdp.version()
print(version, source) -- 3.2.5, v3.2.5 (v3.2.5)
Lua API использует индексы PD с единицы. Например, первый элемент списка,
переданного в osdp.cp({...}), имеет индекс 1 в остальных методах CP.
Канал связи
recv и необязательный flush должны быть неблокирующими. recv возвращает
доступные байты строкой или ""/nil, когда данных нет. Нельзя ждать полный
пакет внутри recv: модуль сам собирает его из фрагментов.
send должен принять весь переданный буфер за один вызов и вернуть #buf
(либо nil после успешной отправки); частичная запись не поддерживается.
uart.write() в режиме RS-485 ждёт окончания передачи перед снятием DE,
поэтому не добавляйте в обработчик дополнительные
ожидания или циклы повторной отправки.
channel_id должен быть положительным. Для всех PD на одной многоточечной шине
укажите одинаковый ID, а для независимых шин — разные ID. Это не адрес PD, а
идентификатор общего канала связи.
local ch = {
channel_id = 1,
send = function(_, buf)
uart.write(2, buf)
return #buf
end,
recv = function(_, max_len)
return uart.read(2, "*nl", 0, max_len) or ""
end,
flush = function()
uart.flush(2)
end,
}
Вызывайте refresh() обеих ролей не реже одного раза в 50 мс. В примерах
использован период 10 мс. Обработчик Lua выполняется синхронно внутри
refresh(), поэтому он не должен надолго задерживать выполнение программы.
Общая конфигурация PD
Каждая конфигурация содержит:
send,recv, необязательныйflushиdataдля канала;channel_id > 0;addressот0до126;127зарезервирован для широковещательных команд;baud_rate: 9600, 19200, 38400, 57600, 115200 или 230400;- необязательные
name,flagsиscbk.
scbk — бинарная строка ровно из 16 байт. PD без scbk создаётся только при
явном OSDP_FLAG_INSTALL_MODE, чтобы случайно не оставить устройство на
стандартном SCBK-D. Для обычной защищённой работы обе
стороны должны получить один и тот же SCBK и флаг OSDP_FLAG_ENFORCE_SECURE.
OSDP_FLAG_INSTALL_MODE предназначен только для контролируемого ввода в
эксплуатацию через SCBK-D; его нельзя сочетать с OSDP_FLAG_ENFORCE_SECURE.
На стороне CP режим установки требует новый scbk: после сеанса с SCBK-D
модуль передаст этот ключ в PD и заново проверит защищённое соединение.
Захват пакетов в PCAP не поддерживается, поэтому
OSDP_FLAG_CAPTURE_PACKETS нельзя передавать при создании контекста. Флаг
OSDP_FLAG_ALLOW_EMPTY_ENCRYPTED_DATA_BLOCK нужен только для совместимости с
ошибочными PD и не должен использоваться без необходимости.
CP
local osdp = osdp or require("osdp")
local scbk = "0123456789ABCDEF"
local cp = osdp.cp({
{
send = ch.send,
recv = ch.recv,
flush = ch.flush,
data = ch,
channel_id = 1,
address = 101,
baud_rate = 9600,
flags = osdp.OSDP_FLAG_ENFORCE_SECURE,
scbk = scbk,
name = "reader-1",
},
})
cp:on_event(function(pd_idx, ev)
if ev.type == osdp.OSDP_EVENT_CARDREAD then
print("CARD", pd_idx, ev.reader, ev.format, ev.length, ev.data)
elseif ev.type == osdp.OSDP_EVENT_STATUS then
print("STATUS", ev.status_type, ev.count, ev.report)
end
end)
while true do
cp:refresh()
tmr.delayms(10)
end
Ключ в примере предназначен только для демонстрации. Для рабочего устройства используйте уникальный случайный ключ из 16 байт и храните его как секрет.
Методы CP:
cp:status()— таблица признаков доступности PD;cp:sc_status()— таблица состояний Secure Channel;cp:pd_id(index)— полученный идентификатор PD илиnil;cp:capability(index, function_code)— заявленная возможность PD илиnil;cp:send_command(index, command)— команда поставлена в очередь;cp:flush_commands(index)— число удалённых ожидающих команд;cp:set_enabled(index, boolean)иcp:is_enabled(index)— управление PD;cp:modify_flag(index, flags, set)— изменение поддерживаемых флагов во время работы;cp:on_event(callback)— обработчик событий;cp:file_ops(index, callbacks)иcp:file_status(index)— передача файлов;cp:teardown()— закрытие контекста.
send_command поддерживает output, led, buzzer (buzz), text,
comset, keyset, file_tx, mfg и status. Команды разрешены после перехода
PD в доступное состояние. Для keyset поле типа ключа называется key_type, потому что
type содержит имя команды:
assert(cp:send_command(1, {
type = "keyset",
key_type = 1,
data = new_scbk16,
}))
Необязательное поле command_flags принимает только
OSDP_CMD_FLAG_BROADCAST. Широковещательная команда предназначена для
контролируемой шины с единственным PD и игнорируется при
OSDP_FLAG_ENFORCE_SECURE. В обработчике PD исходные флаги доступны в
cmd.command_flags.
Ротация ключа через keyset выполняется внутри уже активного Secure Channel.
Числовые поля проверяются при вызове: байтовые значения не могут быть
отрицательными или больше 255, timer не может превышать 65535, адрес COMSET
лежит в диапазоне 0..126, а его скорость — одна из 9600, 19200, 38400, 115200
или 230400.
Поля таблиц команд:
type | Поля |
|---|---|
output | output (0..255), control_code (0..6), timer (0..65535, единица 100 мс) |
led | reader, led; таблицы temporary и permanent с control_code, on_count, off_count, on_color, off_color; у temporary также есть timer |
buzzer / buzz | reader, control_code, on_count, off_count, repeat, все 0..255 |
text | reader, control_code, temp_time, row, col, text до 32 байт |
comset | address 0..126 и поддержанная baud_rate без 57600 |
keyset | key_type = 1, data ровно 16 байт; нужен активный Secure Channel |
file_tx | целый file_id, необязательный flags; для отмены — OSDP_CMD_FILE_TX_FLAG_CANCEL |
mfg | 24-битный vendor_code (или vendor), байтовый command, data до 63 байт |
status | status_type: input, output, local, remote либо соответствующая константа |
В led временный control_code лежит в 0..2, постоянный — в 0..1, цвета —
от OSDP_LED_COLOR_NONE до OSDP_LED_COLOR_WHITE. Для текста стандартные
control codes — 1..4; нулевое значение оставлено для совместимости.
Если задан OSDP_FLAG_ENABLE_NOTIFICATION, on_event также получает
OSDP_EVENT_NOTIFICATION. В нём доступны notification_type, arg0 и arg1.
Это локальные диагностические события модуля, а не сообщения от PD.
PD
local pd = osdp.pd({
send = ch.send,
recv = ch.recv,
flush = ch.flush,
data = ch,
channel_id = 1,
address = 101,
baud_rate = 9600,
flags = osdp.OSDP_FLAG_ENFORCE_SECURE,
scbk = "0123456789ABCDEF",
name = "lua-pd",
id = {
vendor_code = 0x00A1B2,
model = 1,
version = 1,
serial_number = 1,
firmware_version = 0x000100,
},
capabilities = {
{function_code = osdp.OSDP_PD_CAP_OUTPUT_CONTROL,
compliance_level = 1, num_items = 2},
{function_code = osdp.OSDP_PD_CAP_READER_LED_CONTROL,
compliance_level = 1, num_items = 1},
},
})
pd:on_command(function(cmd)
if cmd.id == osdp.OSDP_CMD_STATUS then
return {report = string.char(0, 0)}
end
return true -- ACK; false/nil создаёт NAK
end)
pd:notify({
type = "cardread",
reader = 0,
format = osdp.OSDP_CARD_FMT_RAW_WIEGAND,
length = 26,
data = string.char(0x12, 0x34, 0x56, 0x70),
})
Методы PD:
pd:refresh()— выполнить один неблокирующий шаг протокола;pd:status()— признак доступности,pd:sc_status()— состояние Secure Channel;pd:on_command(callback)— принять команды CP;trueили таблица означает ACK,false/nil— NAK, целое значение используется как явный код ответа;pd:notify(event)— поставить событие в очередь;pd:flush_events()— удалить ещё не отправленные события и вернуть их число;pd:file_ops(1, callbacks)иpd:file_status(1)— передача файлов;pd:teardown()— освободить контекст; после этого остальные методы выдают ошибкуOSDP context is closed.
pd:notify поддерживает cardread, keypress, status и mfgrep.
pd:flush_events() удаляет события, которые ещё не отправлены. В обработчик
команды COMSET после успешного подтверждения приходит дополнительный
OSDP_CMD_COMSET_DONE: только после него приложение должно сохранить новые
параметры связи в энергонезависимой памяти.
Для cardread поддерживаются форматы необработанных данных
OSDP_CARD_FMT_RAW_UNSPECIFIED и OSDP_CARD_FMT_RAW_WIEGAND. Устаревший
ASCII-формат не поддерживается. length задаётся в битах, по умолчанию равен
#data * 8 и не может описывать больше бит, чем передано в data.
Поля событий pd:notify:
type | Поля и ограничения |
|---|---|
cardread | reader 0..255, формат необработанных данных format, необязательные length в битах и direction 0/1, data 1..64 байт |
keypress | reader 0..255, data 1..64 байт |
status | status_type, report или совместимые count и mask |
mfgrep | 24-битный vendor_code/vendor, command 0..255, data до 127 байт |
Поле direction сохранено для совместимости, но ответ OSDP с необработанными
данными его не передаёт, поэтому принимающая сторона получает 0.
Отчёты о состоянии
Для каждого входа или выхода используется один байт состояния. Полученное
событие и команда status содержат:
status_type;count;report— бинарную строку длинойcount;mask— совместимое старое представление первых 32 элементов: ненулевой байт превращается в установленный бит.
При отправке предпочтительно задавать report. Старые {count, mask} также
принимаются, но не могут выразить значения, отличные от 0 и 1, и ограничены
32 элементами.
Для input и output длина отчёта должна совпадать с num_items заявленной
возможности PD. Состояние local всегда содержит два байта (tamper, power),
а remote — один байт power.
Первичное назначение SCBK
В контролируемом окружении создайте PD без ключа с
OSDP_FLAG_INSTALL_MODE. На CP задайте желаемый новый 16-байтный scbk и тот
же флаг. Модуль установит Secure Channel на стандартном SCBK-D, автоматически
передаст новый SCBK командой KEYSET и повторно установит канал уже с ним.
Успех подтверждается cp:sc_status()[index] == true; это состояние не считает
сеанс на SCBK-D защищённым. После ввода в эксплуатацию уберите install mode и
перезапустите обе стороны с новым ключом и OSDP_FLAG_ENFORCE_SECURE.
Не оставляйте install mode включённым в штатной эксплуатации: SCBK-D является общеизвестным ключом и годится только для первичной настройки в физически контролируемой среде.
Данные производителя
Поле command сохранено для совместимости. В OSDP 2.2 это первый байт данных
производителя: при отправке он добавляется перед data, а при приёме отделяется
от него. Поэтому для самого поля data доступно на один байт меньше, чем для
всего блока данных производителя.
Передача файлов
До команды file_tx зарегистрируйте обработчики файла на нужной стороне:
local payload = "file contents"
cp:file_ops(1, {
open = function(file_id, size) return 0, #payload end,
read = function(max_len, offset)
return string.sub(payload, offset + 1, offset + max_len)
end,
write = function(chunk, offset) return #chunk end,
close = function() return 0 end,
})
assert(cp:send_command(1, {type = "file_tx", file_id = 1, flags = 0}))
local state = cp:file_status(1) -- {size=..., offset=...} или nil
file_status возвращает nil, если обработчики ещё не зарегистрированы
или передача не активна.
После каждого успешного вызова open модуль вызывает close ровно один раз:
при штатном завершении, ошибке/отмене передачи, замене обработчиков через
file_ops или закрытии OSDP-контекста через teardown/GC.
Экспортируемые константы
Модуль экспортирует:
- флаги
OSDP_FLAG_ENFORCE_SECURE,OSDP_FLAG_INSTALL_MODE,OSDP_FLAG_IGN_UNSOLICITED,OSDP_FLAG_ENABLE_NOTIFICATIONиOSDP_FLAG_ALLOW_EMPTY_ENCRYPTED_DATA_BLOCK; - все
OSDP_CMD_*, включаяOSDP_CMD_COMSET_DONE, и событияOSDP_EVENT_CARDREAD,OSDP_EVENT_KEYPRESS,OSDP_EVENT_STATUS,OSDP_EVENT_MFGREP,OSDP_EVENT_NOTIFICATION; - типы уведомлений
OSDP_EVENT_NOTIFICATION_COMMAND,SC_STATUSиPD_STATUS; OSDP_STATUS_REPORT_INPUT,OUTPUT,LOCAL,REMOTE;- форматы необработанных данных карты, восемь
OSDP_LED_COLOR_*отNONEдоWHITEиOSDP_CMD_FILE_TX_FLAG_CANCEL,OSDP_CMD_FLAG_BROADCAST; - capability codes
OSDP_PD_CAP_CONTACT_STATUS_MONITORING,OUTPUT_CONTROL,CARD_DATA_FORMAT,READER_LED_CONTROL,READER_AUDIBLE_OUTPUT,READER_TEXT_OUTPUT,TIME_KEEPING,CHECK_CHARACTER_SUPPORT,COMMUNICATION_SECURITY,RECEIVE_BUFFERSIZE,LARGEST_COMBINED_MESSAGE_SIZE,SMART_CARD_SUPPORT,READERS,BIOMETRICS,SECURE_PIN_ENTRYиOSDP_VERSION.