-- Modbus TCP/RTU Transparent Bridge
-- Простой прозрачный мост между Modbus TCP и RTU
-- ========== Настройки ==========
local TCP_PORT = 502
local BAUDRATE = 115200
-- ========== Инициализация UART ==========
local UART_PORT = uart.UART1
local UART_RX = pio.GPIO14
local UART_TX = pio.GPIO23
local UART_RTS = pio.GPIO12
-- Настройка пинов UART
uart.setpins(UART_PORT, UART_RX, UART_TX)
uart.attach(UART_PORT, BAUDRATE, 8, uart.PARNONE, uart.STOP1)
pio.pin.setdir(pio.OUTPUT, UART_RTS)
pio.pin.setlow(UART_RTS) -- По умолчанию в режиме приема
-- ========== Мьютекс для UART ==========
local uart_mutex = thread.createmutex()
-- ========== Прозрачный мост ==========
local function transparent_bridge(client_ip, tcp_data)
-- Блокируем UART для исключения одновременных запросов
uart_mutex:lock()
-- print(string.format("📨 Получен TCP пакет от %s, длина: %d", client_ip, #tcp_data))
-- Проверяем минимальную длину TCP пакета
if #tcp_data < 8 then
print("❌ Слишком короткий TCP пакет")
uart_mutex:unlock()
return
end
-- 1. Разбираем MBAP заголовок
local trans_id, protocol_id, length, unit_id = modbus.utils.parsembap(tcp_data:sub(1, 7))
local pdu = tcp_data:sub(8, 7 + length - 1)
-- print(string.format("📋 MBAP: TransID=%d, UnitID=%d, PDU длина=%d", trans_id, unit_id, #pdu))
-- 2. Формируем RTU пакет: UnitID + PDU + CRC16
local rtu_request = string.char(unit_id) .. pdu
local crc = modbus.utils.crc16(rtu_request)
local crc_hi, crc_lo = modbus.utils.tobytes(crc)
-- В Modbus RTU CRC передается в Little Endian (младший байт первый)
rtu_request = rtu_request .. string.char(crc_lo, crc_hi)
-- print(string.format("📤 Отправка RTU: %d байт, CRC=0x%04X", #rtu_request, crc))
-- 3. Очищаем буфер UART и отправляем запрос
uart.consume(UART_PORT)
uart.write(UART_PORT, rtu_request, UART_RTS)
-- 4. Ожидаем ответ от RTU устройства
local expected_len = modbus.utils.expectedrtulen(pdu)
local rtu_response = uart.read(UART_PORT, "*nl", 200, expected_len)
if not rtu_response or #rtu_response < 3 then
print("❌ Таймаут или нет ответа от RTU устройства")
local error_pdu = modbus.utils.errorresponse(pdu:byte(1), 0x04)
local mbap = modbus.utils.buildmbap(trans_id, protocol_id, #error_pdu + 1, unit_id)
local error_response = mbap .. error_pdu
net.tcpserv.send(error_response, 0, true)
uart_mutex:unlock()
return
end
-- print(string.format("📥 Получен RTU ответ: %d байт", #rtu_response))
-- 5. Проверяем CRC16 ответа
if not modbus.utils.verifycrc(rtu_response) then
print(string.format("❌ CRC ошибка: получен=0x%04X, вычислен=0x%04X", received_crc, calculated_crc))
local error_pdu = modbus.utils.errorresponse(pdu:byte(1), 0x04)
local mbap = modbus.utils.buildmbap(trans_id, protocol_id, #error_pdu + 1, unit_id)
local error_response = mbap .. error_pdu
net.tcpserv.send(error_response, 0, true)
uart_mutex:unlock()
return
end
-- 6. Проверяем Unit ID в ответе
local response_unit_id = rtu_response:byte(1)
if response_unit_id ~= unit_id then
print(string.format("❌ Неверный Unit ID в ответе: ожидали=%d, получили=%d", unit_id, response_unit_id))
local error_pdu = modbus.utils.errorresponse(pdu:byte(1), 0x04)
local mbap = modbus.utils.buildmbap(trans_id, protocol_id, #error_pdu + 1, unit_id)
local error_response = mbap .. error_pdu
print(string.format("🚫 Отправка Unit ID ошибки: %s", tohex(error_response)))
net.tcpserv.send(error_response, 0, true)
uart_mutex:unlock()
return
end
-- 7. Извлекаем PDU из RTU ответа (без Unit ID и CRC)
local response_pdu = rtu_response:sub(2, -3)
-- 8. Формируем TCP ответ
local mbap = modbus.utils.buildmbap(trans_id, protocol_id, #response_pdu + 1, unit_id)
local tcp_response = mbap .. response_pdu
--print(string.format("📤 Отправка TCP ответа: %d байт", #tcp_response))
-- Отправляем TCP ответ в блокирующем режиме с таймаутом 5000мс + flush
net.tcpserv.send(tcp_response, 0, true)
--print("✅ Транзакция завершена успешно")
-- Разблокируем UART после завершения транзакции
uart_mutex:unlock()
end
-- ========== Запуск сервера ==========
print("🚀 Запуск Modbus TCP/RTU Transparent Bridge")
print("📍 TCP порт:", TCP_PORT)
print("📍 UART:", "UART" .. UART_PORT, "Baudrate=" .. BAUDRATE)
print("📍 Поддерживаются ВСЕ Modbus функции")
print("🔒 Мьютекс UART включен для исключения одновременных запросов")
net.tcpserv.start(TCP_PORT, transparent_bridge, 60, false)
print("✅ Прозрачный мост запущен и готов к работе!")
print("🔗 Подключите Modbus TCP клиент к порту", TCP_PORT)