Шаблон простого tcp modbus сервера. Считыватель сохраняет последнюю поднесенную метку в формате: время в unix timestamp в регистрах 0-1 и uid в регистрах 2-5.
LuaSocket не включен по-умолчанию в сборку, его нужно установить из репозитория.
local rfid = require("rfid")
local indication = require("indication")
local PORT = 502
local wdt_on = Settings.get("sys_wdt") == "on"
local snd = indication.Sound.new()
local leds = indication.Leds.new({RGB=true})
local mtx = thread.createmutex()
local holding_registers = {0, 0, 0, 0, 0, 0}
local function pad_uid(uid)
return string.format("%016s", uid):gsub(" ", "0")
end
local function split_into_words_32(number)
return (number >> 16) & 0xFFFF, number & 0xFFFF
end
local function split_uid_into_words(uid)
return tonumber(string.sub(uid, 1, 4), 16) or 0,
tonumber(string.sub(uid, 5, 8), 16) or 0,
tonumber(string.sub(uid, 9, 12), 16) or 0,
tonumber(string.sub(uid, 13, 16), 16) or 0
end
local function to_holding_registers(regs, ts, uid)
regs[1], regs[2] = split_into_words_32(ts)
regs[3], regs[4], regs[5], regs[6] = split_uid_into_words(uid)
end
thread.start(function()
-- определяем классы
reader = rfid.Reader({})
-- стартовая индикация
leds:start()
snd:start()
-- запускаем
reader.process({
timeout_ms = 200, -- таймаут в цикле; всегда должен быть больше 0
mode = rfid.MODE_LOOP,
wdt = wdt_on,
halt = true,
checkfunc = function(uid) -- функция для проверки метки
mtx:lock()
to_holding_registers(holding_registers, os.time(), pad_uid(uid))
mtx:unlock()
leds:ok() -- индикация
snd:ok() -- звук
end
})
end)
--tcp modbus server
local function to_bytes(value)
return string.pack(">I2", value)
end
local function from_bytes(b1, b2)
return b1*256 + b2
end
local function build_mbap_header(trans_id_hi, trans_id_lo, protocol_id_hi, protocol_id_lo, length)
return string.pack(">BBBBH", trans_id_hi, trans_id_lo, protocol_id_hi, protocol_id_lo, length)
end
local function send_response(client, trans_id_hi, trans_id_lo, protocol_id_hi, protocol_id_lo, unit_id, pdu)
local response = string.pack(">HHHBc"..#pdu, trans_id_hi * 256 + trans_id_lo, protocol_id_hi * 256 + protocol_id_lo, #pdu + 1, unit_id, pdu)
client:send(response)
end
local function send_error(client, trans_id_hi, trans_id_lo, protocol_id_hi, protocol_id_lo, unit_id, func_code, error_code)
local error_func_code = string.char(func_code + 0x80)
local error_pdu = error_func_code .. string.char(error_code)
send_response(client, trans_id_hi, trans_id_lo, protocol_id_hi, protocol_id_lo, unit_id, error_pdu)
end
local server = assert(socket.bind("*", PORT))
--server:settimeout(0)
cacheutils.addLog(log, "Modbus TCP устройство запущено на порту " .. PORT, cacheutils.Info)
while true do
local client = server:accept()
if client then
-- client:settimeout(10)
-- Читаем MBAP Header (7 байт)
local header, err = client:receive(7)
if header then
local trans_id_hi, trans_id_lo,
protocol_id_hi, protocol_id_lo,
length_hi, length_lo,
unit_id = header:byte(1, 7)
local length = from_bytes(length_hi, length_lo)
-- Длина включает в себя байты Unit ID и следующие за ним байты.
-- Значит, нам нужно прочитать (length - 1) байт, чтобы получить PDU
local pdu_len = length - 1
local pdu, err = client:receive(pdu_len)
if pdu then
local fcode = pdu:byte(1)
if fcode == 3 then
-- Чтение holding регистров
-- Формат запроса: Function Code (1 байт), Start Address Hi, Start Address Lo, Quantity Hi, Quantity Lo
local start_hi, start_lo, qty_hi, qty_lo = pdu:byte(2, 5)
local start_addr = from_bytes(start_hi, start_lo) + 1 -- +1, т.к. Lua-индексация с 1
local quantity = from_bytes(qty_hi, qty_lo)
-- Проверяем, не выходит ли за пределы массива регистров
if start_addr + quantity - 1 <= #holding_registers then
local byte_count = quantity * 2
mtx:lock() -- Блокируем доступ к holding_registers для безопасного чтения
local format = ">BB" .. string.rep("H", quantity)
-- Упаковываем Function Code, Byte Count и данные регистров в одну строку
local response_pdu = string.pack(format, fcode, byte_count, table.unpack(holding_registers, start_addr, start_addr + quantity -1))
mtx:unlock()
send_response(client, trans_id_hi, trans_id_lo, protocol_id_hi, protocol_id_lo, unit_id, response_pdu)
else
-- Если запрос выходит за пределы, возвращаем ошибку Modbus
send_error(client, trans_id_hi, trans_id_lo, protocol_id_hi, protocol_id_lo, unit_id, fcode, 0x02)
end
else
-- Неподдерживаемая функция
send_error(client, trans_id_hi, trans_id_lo, protocol_id_hi, protocol_id_lo, unit_id, fcode, 0x01)
end
end
end
client:close()
end
socket.sleep(0.01)
end