Modbus
Модуль Modbus
Модуль поддерживает ведомое устройство Modbus RTU, сервер Modbus TCP, прозрачный шлюз Modbus TCP и ведущее устройство Modbus RTU. Серверы RTU и TCP используют общую таблицу регистров, а прозрачный шлюз передаёт обработчику Lua готовые и проверенные Modbus TCP ADU.
Обзор API
| Подмодуль | Описание |
|---|---|
| modbus.rtu | Ведомое устройство Modbus RTU |
| modbus.tcp | Сервер Modbus TCP |
| modbus.tcpraw | Прозрачный шлюз Modbus TCP с обработчиком Lua |
| modbus.master | Ведущее устройство Modbus RTU |
| modbus.utils | Вспомогательные функции |
| modbus.* | Общие функции для работы с регистрами |
Быстрый старт
TCP сервер
-- Запуск Modbus TCP сервера на порту 502
modbus.tcp.init(502, 1)
-- Установка значений регистров
modbus.setregs(0, 100, 200, 300)
modbus.setinput(0, 1000, 2000)
modbus.setcoils(0, true, false, true)
-- Проверка статуса
local running, port, unit_id = modbus.tcp.status()
print(string.format("TCP сервер: %s, порт %d, unit_id %d",
running and "запущен" or "остановлен", port, unit_id))
RTU slave
-- Инициализация RTU slave
modbus.rtu.init(1, 2, 19, 18, 13, 9600, 0)
-- Регистры общие для RTU и TCP
modbus.setregs(0, 100, 200, 300)
RTU master
-- Инициализация master
modbus.master.init(2, 19, 18, 13, 9600, 0)
-- Чтение регистров с удаленного устройства
local regs, err = modbus.master.readholding(1, 0, 10)
if regs then
for i, v in ipairs(regs) do
print(string.format("Регистр %d: %d", i-1, v))
end
end
TCP -> RTU gateway
modbus.master.init(2, 19, 18, 13, 9600, 0)
modbus.master.timeout(1000)
local function on_tcp_request(remote_ip, request_adu)
local trans_id, protocol_id, length, unit_id = modbus.utils.parsembap(request_adu:sub(1, 7))
if protocol_id ~= 0 then
return nil
end
local pdu = request_adu:sub(8, 6 + length)
local rtu_response = modbus.master.request(string.char(unit_id) .. pdu)
if not rtu_response then
local error_pdu = modbus.utils.errorresponse(pdu:byte(1), 0x0B)
return modbus.utils.buildmbap(trans_id, 0, #error_pdu + 1, unit_id) .. error_pdu
end
local response_pdu = rtu_response:sub(2)
return modbus.utils.buildmbap(trans_id, 0, #response_pdu + 1, unit_id) .. response_pdu
end
modbus.tcpraw.start(502, on_tcp_request, 300)
Modbus TCP
Сервер Modbus TCP использует те же регистры, что и ведомое устройство Modbus RTU. Запросы с unit_id, отличным от настроенного значения, игнорируются без ответа; unit_id = 0 обрабатывается как общий запрос.
Поддерживаемые функции Modbus
| FC | Название | Описание |
|---|---|---|
| 01 | Read Coils | Чтение состояния coils |
| 02 | Read Discrete Inputs | Чтение дискретных входов |
| 03 | Read Holding Registers | Чтение holding регистров |
| 04 | Read Input Registers | Чтение input регистров |
| 05 | Write Single Coil | Запись одного coil |
| 06 | Write Single Register | Запись одного регистра |
| 15 | Write Multiple Coils | Запись нескольких coils |
| 16 | Write Multiple Registers | Запись нескольких регистров |
| 23 | Read/Write Multiple Registers | Чтение и запись регистров |
modbus.tcp.init(port, unit_id)
Запуск Modbus TCP сервера.
Аргументы:
- port (необязательно): TCP порт (1-65535, по умолчанию 502).
- unit_id (необязательно): идентификатор устройства (1-247, по умолчанию 1).
Возвращает: ничего или исключение.
-- Запуск на стандартном порту 502
modbus.tcp.init()
-- Запуск на альтернативном порту с unit_id = 10
modbus.tcp.init(1502, 10)
modbus.tcp.stop()
Остановка Modbus TCP сервера.
modbus.tcp.stop()
running, port, unit_id = modbus.tcp.status()
Получение статуса TCP сервера.
Возвращает:
- running: true если сервер работает.
- port: текущий порт.
- unit_id: идентификатор устройства.
local running, port, unit_id = modbus.tcp.status()
if running then
print(string.format("Сервер работает на порту %d", port))
end
Прозрачный шлюз Modbus TCP
Этот режим передаёт запрос Modbus TCP внешнему обработчику — например, ведущему устройству RTU в шлюзе TCP → RTU. Сервер собирает входящий поток в полные Modbus TCP ADU и проверяет заголовок MBAP до вызова обработчика Lua.
modbus.tcpraw не использует общую таблицу регистров. Обработчик получает полный Modbus TCP ADU без CRC и должен вернуть полный ADU ответа. Если он возвращает nil, завершается с ошибкой или формирует некорректный ADU, сервер отвечает исключением Modbus 0x04 (Slave Device Failure) для кода функции запроса.
modbus.tcpraw.start(port, callback, idle_timeout_sec)
Запускает прозрачный сервер Modbus TCP.
Аргументы:
- port: TCP порт (1-65535, обычно 502).
- callback: функция
callback(remote_ip, request_adu) -> response_adu; - idle_timeout_sec (необязательно): таймаут простоя клиента в секундах (по умолчанию 300,
0отключает автоотключение).
Обработчик получает:
- remote_ip: строка с адресом клиента.
- request_adu: полный Modbus TCP ADU (
MBAP+PDU). CRC в Modbus TCP отсутствует.
Обработчик возвращает:
- response_adu: полный Modbus TCP ADU ответа (
MBAP+PDU) илиnil, чтобы вернуть исключение0x04.
local function on_request(ip, adu)
local tid, proto, len, unit = modbus.utils.parsembap(adu:sub(1, 7))
if proto ~= 0 then
return nil
end
local pdu = adu:sub(8, 6 + len)
local rtu_response = modbus.master.request(string.char(unit) .. pdu)
if not rtu_response then
local error_pdu = modbus.utils.errorresponse(pdu:byte(1), 0x0B)
return modbus.utils.buildmbap(tid, 0, #error_pdu + 1, unit) .. error_pdu
end
local response_pdu = rtu_response:sub(2)
return modbus.utils.buildmbap(tid, 0, #response_pdu + 1, unit) .. response_pdu
end
modbus.tcpraw.start(502, on_request, 300)
modbus.tcpraw.stop()
Останавливает сервер и активные клиентские соединения.
modbus.tcpraw.stop()
running, port, active_clients, max_clients = modbus.tcpraw.status()
Возвращает состояние прозрачного TCP-сервера.
Возвращает:
- running: true если сервер работает.
- port: текущий порт.
- active_clients: количество активных TCP клиентов.
- max_clients: максимальное количество одновременных клиентов.
local running, port, active, max = modbus.tcpraw.status()
print(string.format("TCP raw: %s, port %d, clients %d/%d",
running and "running" or "stopped", port, active, max))
Modbus RTU
modbus.rtu.init(addr, port, rxpin, txpin, rtspin, baudrate, parity, clear_holding_on_read)
Инициализирует ведомое устройство Modbus RTU.
Все параметры необязательны. Для пропущенных аргументов используются значения, заданные для модели устройства.
Аргументы:
- addr (необязательно): адрес устройства в сети Modbus (1-247).
- port (необязательно): номер UART порта.
- rxpin (необязательно): номер GPIO пина для приема данных.
- txpin (необязательно): номер GPIO пина для передачи данных.
- rtspin (необязательно): номер GPIO пина RTS/DE для управления направлением RS485.
- baudrate (необязательно): скорость UART.
- parity (необязательно): четность (0 = none, 2 = even, 3 = odd).
- clear_holding_on_read (необязательно): очищать holding-регистры после внешнего FC03-чтения (по умолчанию true для обратной совместимости). Установите false, если регистры представляют текущее состояние; тогда приложение должно само очищать устаревшие значения.
Возвращает: ничего или исключение.
modbus.rtu.init()
-- Явно задаем только часть параметров
modbus.rtu.init(1)
modbus.rtu.init(1, 2, 19, 18, 13, 9600, 0)
-- FC03 не изменяет holding-регистры
modbus.rtu.init(1, nil, nil, nil, nil, 115200, 0, false)
После успешной инициализации обработка запросов запускается в фоновом режиме.
При clear_holding_on_read=true модуль ставит FC03-чтения в
локальную очередь и очищает прочитанный диапазон после формирования ответа.
Если приложение или другой Modbus-запрос успели записать регистр во время
передачи ответа, новое значение сохраняется и отложенная очистка его не
затрагивает.
При false FC03 возвращает значения без изменения holding-регистров.
Межбайтовый тайм-аут RTU рассчитывается по t1.5: через время символа для скоростей до 19200 бод и по фиксированному интервалу 750 мкс для более высоких скоростей (с округлением вверх до миллисекунды).
modbus.rtu.poll()
Ручная обработка локальной очереди уведомлений Modbus. Очистка прочитанных
holding-регистров выполняется только в режиме
clear_holding_on_read=true. Сам приём кадров RTU выполняется в фоне.
modbus.rtu.poll()
interval_ms = modbus.rtu.pollstart([interval_ms])
Запускает или перезапускает фоновую обработку с указанным интервалом.
Аргументы:
- interval_ms (необязательно): интервал опроса в миллисекундах (минимум 1).
Возвращает:
- interval_ms: фактический интервал, с которым запущена задача.
local interval = modbus.rtu.pollstart(10)
print("RTU poll interval:", interval)
modbus.rtu.pollstop()
Останавливает фоновую обработку.
modbus.rtu.pollstop()
modbus.rtu.close()
Остановка Modbus RTU интерфейса и освобождение ресурсов.
modbus.rtu.close()
Общие функции для работы с регистрами
Эти функции управляют регистрами, общими для RTU slave и TCP slave. Изменения видны обоим транспортам.
Holding Registers (FC03/FC06/FC16)
modbus.setregs(addr, value1, value2, …)
Установка значений holding регистров.
Аргументы:
- addr: начальный адрес регистра (0-255).
- value1, value2, …: значения регистров (0-65535).
modbus.setregs(0, 100, 200, 300)
modbus.setregs(100, 1234)
modbus.setregs8(addr, byte1, byte2, …)
modbus.setregs8(addr, str)
Установка holding регистров в байтовом представлении.
Аргументы:
- addr: начальный адрес регистра (0-255).
- byte1, byte2, …, либо строка: байтовые значения (0-255). Каждые два байта формируют один регистр.
-- Режим 1: Строка байтов
modbus.setregs8(100, "Hello")
modbus.setregs8(100, "\x48\x65\x6C\x6C\x6F")
-- Режим 2: Отдельные байты как аргументы (было setregs8)
modbus.setregs8(100, 0x48, 0x65, 0x6C, 0x6C, 0x6F)
-- Комбинация с unhexlify
local data = cutils.unhexlify("48656C6C6F", true)
modbus.setregs8(100, data)
values = modbus.getregs(addr, count, clear)
Чтение holding регистров.
Аргументы:
- addr: начальный адрес регистра (0-255).
- count (необязательно): количество регистров (по умолчанию 1).
- clear (необязательно): очистить после чтения (по умолчанию false).
Возвращает: таблицу со значениями.
local values = modbus.getregs(0, 10)
for i, v in ipairs(values) do
print(string.format("Регистр %d: %d", i-1, v))
end
bytes = modbus.getregs8(addr, count, clear)
Чтение holding регистров в байтовом представлении.
Возвращает: таблицу с байтами (2 байта на регистр).
local bytes = modbus.getregs8(100, 2)
print(string.format("0x%02X%02X 0x%02X%02X", bytes[1], bytes[2], bytes[3], bytes[4]))
Input Registers (FC04)
modbus.setinput(addr, value1, value2, …)
Установка значений input регистров.
modbus.setinput(0, 1000, 2000, 3000)
values = modbus.getinput(addr, count)
Чтение input регистров.
local values = modbus.getinput(0, 5)
Coils (FC01/FC05/FC15)
modbus.setcoils(addr, value1, value2, …)
Установка coils.
Аргументы:
- addr: начальный адрес coil (0-255).
- value1, value2, …: boolean значения (true/false).
Числа, строки, nil и другие типы не преобразуются в логические значения, а
вызывают ошибку аргумента.
modbus.setcoils(0, true, false, true, true)
values = modbus.getcoils(addr, count)
Чтение coils.
Возвращает: таблицу с boolean значениями.
local coils = modbus.getcoils(0, 8)
for i, v in ipairs(coils) do
print(string.format("Coil %d: %s", i-1, v and "ON" or "OFF"))
end
Discrete Inputs (FC02)
modbus.setdiscrete(addr, value1, value2, …)
Установка discrete inputs.
Все значения должны иметь тип boolean (true или false); автоматического
преобразования чисел и других типов нет.
modbus.setdiscrete(0, true, true, false, false)
values = modbus.getdiscrete(addr, count)
Чтение discrete inputs.
local inputs = modbus.getdiscrete(0, 8)
Modbus Master
Функции для работы в режиме ведущего устройства Modbus RTU.
| Функция | FC | Описание |
|---|---|---|
| init | - | Инициализация интерфейса |
| close | - | Закрытие интерфейса |
| timeout | - | Установка таймаута |
| readholding | 03 | Чтение Holding регистров |
| readinput | 04 | Чтение Input регистров |
| writesingle | 06 | Запись одного регистра |
| writemultiple | 16 | Запись нескольких регистров |
| readcoils | 01 | Чтение Coils |
| writecoil | 05 | Запись одного Coil |
| writecoils | 15 | Запись нескольких Coils |
| readdiscrete | 02 | Чтение Discrete Inputs |
| request | - | Произвольный запрос |
modbus.master.init(port, rxpin, txpin, rtspin, baudrate, parity)
Инициализация Modbus Master интерфейса.
Аргументы:
- port: номер UART порта (1, 2).
- rxpin: номер GPIO пина для приема данных.
- txpin: номер GPIO пина для передачи данных.
- rtspin: номер GPIO пина для управления направлением RS485.
- baudrate: скорость передачи данных (9600, 19200, 38400, 57600, 115200).
- parity (необязательно): четность (0=none, 2=even, 3=odd, по умолчанию 0).
modbus.master.init(2, 19, 18, 13, 9600, 0)
modbus.master.close()
Закрытие Modbus Master интерфейса.
modbus.master.close()
modbus.master.timeout(ms)
Установка таймаута ожидания ответа.
Аргументы:
- ms: таймаут в миллисекундах (10-30000, по умолчанию 1000).
modbus.master.timeout(500)
values, err = modbus.master.readholding(slave, addr, count)
Чтение Holding регистров (функция 03).
Аргументы:
- slave: адрес slave устройства (1-247).
- addr: начальный адрес регистра (0-65535).
- count: количество регистров (1-125).
Возвращает: таблицу со значениями или nil и строку ошибки.
local regs, err = modbus.master.readholding(1, 0, 10)
if regs then
for i, v in ipairs(regs) do
print(string.format("Регистр %d: %d", i-1, v))
end
else
print("Ошибка:", err)
end
values, err = modbus.master.readinput(slave, addr, count)
Чтение Input регистров (функция 04).
local regs, err = modbus.master.readinput(1, 100, 5)
if regs then
print("Температура:", regs[1] / 10, "°C")
end
ok, err = modbus.master.writesingle(slave, addr, value)
Запись одного Holding регистра (функция 06).
local ok, err = modbus.master.writesingle(1, 100, 12345)
if ok then
print("Регистр записан")
end
ok, err = modbus.master.writemultiple(slave, addr, values)
Запись нескольких Holding регистров (функция 16).
local ok, err = modbus.master.writemultiple(1, 0, {100, 200, 300, 400})
values, err = modbus.master.readcoils(slave, addr, count)
Чтение Coils (функция 01).
local coils, err = modbus.master.readcoils(1, 0, 16)
if coils then
for i, v in ipairs(coils) do
print(string.format("Coil %d: %s", i-1, v and "ON" or "OFF"))
end
end
ok, err = modbus.master.writecoil(slave, addr, value)
Запись одного Coil (функция 05).
Аргумент value должен иметь тип boolean (true или false).
modbus.master.writecoil(1, 0, true)
ok, err = modbus.master.writecoils(slave, addr, values)
Запись нескольких Coils (функция 15).
Каждый элемент таблицы values должен иметь тип boolean (true или false).
modbus.master.writecoils(1, 0, {true, false, true, true})
values, err = modbus.master.readdiscrete(slave, addr, count)
Чтение Discrete Inputs (функция 02).
local inputs, err = modbus.master.readdiscrete(1, 0, 8)
response, err = modbus.master.request(data)
Отправляет произвольный запрос Modbus RTU. Функция предназначена в том числе для прозрачного шлюза TCP → RTU: на вход передаётся RTU ADU без CRC (unit_id + PDU), CRC добавляется автоматически, а ответ также возвращается без CRC.
Аргументы:
- data: строка с RTU ADU без CRC (unit_id + function code + data).
Возвращает: строку с ответом (без CRC) или nil и строку ошибки.
local request = string.char(0x01, 0x03, 0x00, 0x00, 0x00, 0x01)
local response, err = modbus.master.request(request)
Modbus Utils
Вспомогательные функции для работы с протоколом Modbus.
value = modbus.utils.frombytes(hi, lo)
Преобразование двух байтов в 16-битное число (Big Endian).
local value = modbus.utils.frombytes(0x12, 0x34)
print(value) -- 4660 (0x1234)
hi, lo = modbus.utils.tobytes(value)
Преобразование 16-битного числа в два байта (Big Endian).
local hi, lo = modbus.utils.tobytes(4660)
print(string.format("0x%02X, 0x%02X", hi, lo)) -- 0x12, 0x34
value = modbus.utils.frombytes32(b3, b2, b1, b0)
Преобразование четырех байтов в 32-битное число (Big Endian).
local value = modbus.utils.frombytes32(0x12, 0x34, 0x56, 0x78)
print(value) -- 305419896 (0x12345678)
b3, b2, b1, b0 = modbus.utils.tobytes32(value)
Преобразование 32-битного числа в четыре байта (Big Endian).
local b3, b2, b1, b0 = modbus.utils.tobytes32(305419896)
crc = modbus.utils.crc16(data)
Вычисление CRC16 для данных (стандарт Modbus RTU).
local data = string.char(0x01, 0x03, 0x00, 0x00, 0x00, 0x01)
local crc = modbus.utils.crc16(data)
print(string.format("CRC16: 0x%04X", crc))
mbap = modbus.utils.buildmbap(trans_id, protocol_id, length, unit_id)
Построение MBAP заголовка для Modbus TCP.
local mbap = modbus.utils.buildmbap(1234, 0, 6, 1)
trans_id, protocol_id, length, unit_id = modbus.utils.parsembap(data)
Разбор MBAP заголовка из строки.
local trans_id, protocol_id, length, unit_id = modbus.utils.parsembap(data:sub(1, 7))
packed = modbus.utils.packbytes(byte1, byte2, …)
Упаковка байтов в строку.
local packet = modbus.utils.packbytes(0x01, 0x03, 0x00, 0x00, 0x00, 0x01)
bytes = modbus.utils.unpackbytes(data)
Распаковка строки в массив байтов.
local bytes = modbus.utils.unpackbytes(data)
byte_data = modbus.utils.wordstobytes(words)
Преобразование массива 16-битных слов в строку байтов.
local words = {0x1234, 0x5678}
local byte_data = modbus.utils.wordstobytes(words)
words = modbus.utils.bytestowords(data)
Преобразование строки байтов в массив 16-битных слов.
local words = modbus.utils.bytestowords("\x12\x34\x56\x78")
error_pdu = modbus.utils.errorresponse(func_code, error_code)
Создание ответа с ошибкой Modbus.
local error_pdu = modbus.utils.errorresponse(3, 1)
is_error, [func_code, error_code] = modbus.utils.iserror(data)
Проверка, является ли ответ ошибкой Modbus.
local is_error, func_code, error_code = modbus.utils.iserror(response)
if is_error then
print(string.format("Ошибка в функции %d: код %d", func_code, error_code))
end
expected_len = modbus.utils.expectedrtulen(pdu)
Расчет ожидаемой длины RTU-ответа на основе PDU запроса.
Поддерживаемая логика расчета в модуле:
| FC | Функция | Ожидаемая длина ответа RTU |
|---|---|---|
| 01 | Read Coils | 1 + 1 + 1 + ceil(quantity/8) + 2 |
| 02 | Read Discrete Inputs | 1 + 1 + 1 + ceil(quantity/8) + 2 |
| 03 | Read Holding Registers | 1 + 1 + 1 + quantity*2 + 2 |
| 04 | Read Input Registers | 1 + 1 + 1 + quantity*2 + 2 |
| 05 | Write Single Coil | 8 |
| 06 | Write Single Register | 8 |
| 07 | Read Exception Status | 5 |
| 08 | Diagnostics | 1 + len(pdu) + 2 |
| 11 | Get Comm Event Counter | 8 |
| 15 | Write Multiple Coils | 8 |
| 16 | Write Multiple Registers | 8 |
| 17 | Report Slave ID | ~10 (оценка) |
| 22 | Mask Write Register | 10 |
| 23 | Read/Write Multiple | 1 + 1 + 1 + read_quantity*2 + 2 |
Для неизвестных FC функция возвращает минимальную длину error-response: 5.
local expected_len = modbus.utils.expectedrtulen(pdu)
is_valid = modbus.utils.verifycrc(rtu_data)
Проверка CRC16 в RTU пакете.
if modbus.utils.verifycrc(rtu_response) then
print("CRC корректен")
end
Коды ошибок Modbus
| Код | Описание |
|---|---|
| 0x01 | Недопустимая функция |
| 0x02 | Недопустимый адрес данных |
| 0x03 | Недопустимое значение данных |
| 0x04 | Сбой slave-устройства |
| 0x05 | Подтверждение (acknowledge) |
| 0x06 | Slave-устройство занято |
| 0x08 | Ошибка четности памяти |
| 0x0A | Шлюз недоступен |
| 0x0B | Устройство не отвечает |
Примеры использования
RTU + TCP шлюз
-- Запуск TCP сервера
modbus.tcp.init(502, 1)
-- Запуск RTU slave на том же устройстве
modbus.rtu.init(1, 2, 19, 18, 13, 9600, 0)
-- Регистры общие для обоих транспортов
modbus.setregs(0, 100, 200, 300)
modbus.setinput(0, 1000, 2000)
print("Modbus RTU/TCP шлюз запущен")
print("TCP: порт 502")
print("RTU: UART2, 9600 бод")
Симуляция датчиков
modbus.tcp.init(502, 1)
-- Обновление показаний в цикле
while true do
local temp = math.random(200, 300) -- 20.0-30.0°C
local humidity = math.random(400, 600) -- 40-60%
local pressure = math.random(1000, 1020) -- мбар
modbus.setinput(0, temp, humidity, pressure)
modbus.setcoils(0, temp > 250, humidity > 500)
tmr.delay(1000)
end
Modbus Master - опрос устройства
modbus.master.init(2, 19, 18, 13, 9600, 0)
modbus.master.timeout(500)
local function poll_device(slave_addr)
-- Чтение holding регистров
local holding, err = modbus.master.readholding(slave_addr, 0, 10)
if not holding then
print("Ошибка чтения holding:", err)
return
end
-- Чтение input регистров
local input = modbus.master.readinput(slave_addr, 0, 5)
-- Чтение coils
local coils = modbus.master.readcoils(slave_addr, 0, 8)
print("Holding:", table.concat(holding, ", "))
if input then print("Input:", table.concat(input, ", ")) end
if coils then
local status = {}
for i, v in ipairs(coils) do
status[i] = v and "1" or "0"
end
print("Coils:", table.concat(status, ""))
end
end
while true do
poll_device(1)
tmr.delay(1000)
end
Управление реле через Master
modbus.master.init(2, 19, 18, 13, 9600, 0)
modbus.master.timeout(300)
-- Включить реле 0-3
modbus.master.writecoils(1, 0, {true, true, true, true})
print("Реле включены")
tmr.delay(5000)
-- Выключить все
modbus.master.writecoils(1, 0, {false, false, false, false})
print("Реле выключены")
modbus.master.close()
TCP/RTU преобразователь
-- Настройка TCP сервера для приема запросов
modbus.master.init(2, 19, 18, 13, 9600, 0)
modbus.master.timeout(1000)
local function process_tcp_request(remote_ip, request_adu)
local trans_id, protocol_id, length, unit_id = modbus.utils.parsembap(request_adu:sub(1, 7))
if protocol_id ~= 0 then
return nil
end
local pdu = request_adu:sub(8, 6 + length)
-- Формирование RTU запроса
local rtu_request = string.char(unit_id) .. pdu
-- Отправка через master.request
local response, err = modbus.master.request(rtu_request)
if not response then
return nil, err
end
-- Формирование TCP ответа
local response_pdu = response:sub(2) -- без Unit ID
local mbap = modbus.utils.buildmbap(trans_id, 0, #response_pdu + 1, unit_id)
return mbap .. response_pdu
end
modbus.tcpraw.start(502, process_tcp_request, 300)
Работа с байтовыми данными
-- Запись UID карты RFID (8 байт = 4 регистра)
local uid = "1234567890ABCDEF"
local uid_bytes = {}
for i = 1, #uid, 2 do
table.insert(uid_bytes, tonumber(uid:sub(i, i+1), 16))
end
modbus.setregs8(100, table.unpack(uid_bytes))
-- Чтение обратно
local read_bytes = modbus.getregs8(100, 4)
local reconstructed_uid = ""
for _, byte in ipairs(read_bytes) do
reconstructed_uid = reconstructed_uid .. string.format("%02X", byte)
end
print("Исходный UID:", uid)
print("Восстановленный:", reconstructed_uid)