Serial tunneling (through Ethernet) - A Lua example
Goal
We establish a network connection (e.g. over Ethernet) to send serial commands from one device to another (or vice versa).
- Receive serial input on device 1 (Server/Client).
- Send received data over TCP socket connection to device 2 (Server/Client).
- Transmit received data (from device 1) to serial output on device 2.
Overview

Code
Let's start with importing the necessary packages.
rs232 = require("luars232")
local socket = require("socket")
require("uci")
x = uci.cursor()
SERIAL = {
PORT_NAME = x:get("flexa_app.serial.port_name"),
}
SERVER = {
MODE = x:get("flexa_app.server.mode"),
SERVER_IP = x:get("flexa_app.server.ip"),
PORT = tonumber(x:get("flexa_app.server.port")),
}
Now defining some table structures holding possible options for our serial connection. For an explanation of these options, see section "Glossary" below.
baud_rates = {
[9600] = rs232.RS232_BAUD_9600,
[19200] = rs232.RS232_BAUD_19200,
[38400] = rs232.RS232_BAUD_38400,
[57600] = rs232.RS232_BAUD_57600,
[115200] = rs232.RS232_BAUD_115200,
}
data_bits = {
[6] = rs232.RS232_DATA_6,
[7] = rs232.RS232_DATA_7,
[8] = rs232.RS232_DATA_8,
}
stop_bits = {
[1] = rs232.RS232_STOP_1,
[2] = rs232.RS232_STOP_2,
}
parity_bits = {
["NONE"] = rs232.RS232_PARITY_NONE,
["ODD"] = rs232.RS232_PARITY_ODD,
["EVEN"] = rs232.RS232_PARITY_EVEN,
}
flow_control = {
["OFF"] = rs232.RS232_FLOW_OFF,
["HW"] = rs232.RS232_FLOW_HW,
["XONXOFF"] = rs232.RS232_FLOW_XON_XOFF,
}
Going further, defining all functions we want to invoke later. We will look at each one separately, from top to bottom.
function serial_setup()
local out = io.stderr
e, p = rs232.open(SERIAL.PORT_NAME)
if e ~= rs232.RS232_ERR_NOERROR then -- handle error
out:write(string.format("can't open serial port '%s', error: '%s'\n",
port_name, rs232.error_tostring(e)))
return
end
assert(p:set_baud_rate(baud_rates[SERIAL.BAUD]) == rs232.RS232_ERR_NOERROR)
assert(p:set_data_bits(data_bits[SERIAL.DATA_BITS]) == rs232.RS232_ERR_NOERROR)
assert(p:set_parity(parity_bits[SERIAL.PARITY]) == rs232.RS232_ERR_NOERROR)
assert(p:set_stop_bits(stop_bits[SERIAL.STOP_BITS]) == rs232.RS232_ERR_NOERROR)
assert(p:set_flow_control(flow_control[SERIAL.FLOW_CONTROL]) == rs232.RS232_ERR_NOERROR)
out:write(string.format("OK, port open with values '%s'\n", tostring(p)))
end
First we define a variable out *(a handler for the predefined C stream *io.stderr), to be able to later send a message directly to the error stream (console).
Next we access the serial port with the port name (SERIAL.PORT_NAME) declared in the config file.
If we receive no error back, we can go on and set the serial options defined in the config file. If an error occurred, it will be send to the error stream (console).
function client_f(host, port)
tcp = assert(socket.tcp())
tcp:connect(host, port);
end
Function on the client side to establish a TCP socket connection to the host (Server device).
- host: IP address of the Server
- port: Port at which the connection should start
function server_f(port)
local server = assert(socket.bind("*", port))
local ip, port = server:getsockname()
print("Please telnet to localhost on port " .. port)
print("After connecting, you have 30s to enter a line to be echoed")
client = server:accept()
end
Function on the server side to establish a TCP socket server.
- port: Port at which the connection should start
Server side determines the port number, client has to use the same number (check in config)
Next we define a function to handle the serial input (for each device).
function serial_receive()
local e, data_read = p:read(1,100)
if data_read ~= nil then
server_send(data_read)
end
end
First reading from the serial connection and storing the received data in data_read. If there is any data (data_read ~= nil), we send the data to the other device (see server_send() below) over our TCP socket connection.
function serial_transmit(data_send)
if data_send ~= nil then
e, len_written = p:write(data_send)
end
assert(e == rs232.RS232_ERR_NOERROR)
end
This function is going to forward the received data to the serial port. After we check if an error occurred during the transmission.
function tcp_receive()
if mode == "SERVER" then
client:settimeout(0.1)
local line, err = client:receive(1)
serial_transmit(line)
else
tcp:settimeout(0.1)
local s, status, partial = tcp:receive(1)
serial_transmit(s or partial)
end
end
Next, a function to handle the incoming data on the TCP socket connection. It consists of 2 parts, one if the code is running on the server side, one for client side.
In both cases, we change the timeout values for the socket object to 0.1sec..
By default, all I/O operations are blocking. So the receive call is blocking indefinitely. The settimeout() method sets a limit on the amount of time the I/O method can block.
Now we can go ahead and receive data from the TCP connection (the 1 indicates that we are going to read 1 byte at a time).
function server_send(data)
if mode == "SERVER" then
client:send(data)
else
tcp:send(data);
end
end
Send the data received from the serial input to the other device.
That's it for the function part. Now we can take a look at the execution logic.
serial_setup()
mode, port = SERVER.MODE, SERVER.PORT
print("MODE: " .. mode .. "\n")
if mode == "CLIENT" then
host = SERVER.SERVER_IP
print("Host to connect: ".. host .."\n" .. "Used port: " .. port .. "\n")
client_f(host,port)
elseif mode == "SERVER" then
print("Used port: " .. port .."\n")
server_f(port)
end
- First setting up the serial connection.
- Reading socket mode and port from the config.
- Establish socket connection (as server or client, depending on config).
while true do
serial_receive()
tcp_receive()
end
tcp:close()
client:close()
Main part for handling the application. Running forever (while true).
Finally closing the socket connection.
Config
Serial
Option | Value (Default) |
---|---|
PORT_NAME | Default for serial communication using RS232: "/dev/ttyS1" |
BAUD | 9600, 19200, 38400, 57600, 115200 (default 115200) |
DATA_BITS | 6, 7, 8 (default 8) |
PARITY | "NONE", "ODD", "EVEN" (default "NONE") |
STOP_BITS | 1, 2 (default 1) |
FLOW_CONTROL | "OFF", "HW", "XONXOFF" (default "OFF") |
Server
Option | Value |
---|---|
MODE | "SERVER", "CLIENT" |
SERVER_IP | Enter the IP of the device in SERVER MODE. The device in CLIENT MODE will then connect to this specific IP |
PORT | Enter the port you want to use. If you enter 0 in the SERVER device config, it will chose an ephemeral port by himself and print it out. You will then have to enter this printed port in the config of the CLIENT device. |
Glossary
- Ethernet
- IP/TCP
- Port
- Serial
- Serial characteristics (Baud rate, Data bits, Stop bits, Parity bits, Flow control)
- Server/Client
- Tunneling