Serial over Network
Use PySerial serial_for_url() to access serial ports over TCP sockets and RFC 2217. Connect to remote devices without physical cables.
PySerial's serial_for_url() opens serial ports over TCP connections, so you can talk to a device on another machine as if it were plugged in locally.
Two Protocols
| Protocol | URL format | Use case |
|---|---|---|
Raw TCP (socket://) | socket://host:port | Simple byte forwarding, no port control |
RFC 2217 (rfc2217://) | rfc2217://host:port | Full serial control (baud, RTS/DTR) over the network |
Pick socket:// when you just need to shuttle bytes and the remote side handles port configuration. Pick rfc2217:// when your code needs to change baud rate, toggle DTR/RTS, or read modem status lines remotely.
Raw TCP with socket://
Connect to a serial device exposed on a TCP socket. The remote end (a ser2net instance, a microcontroller with Wi-Fi, or a custom bridge) must already be listening.
import serial
ser = serial.serial_for_url("socket://192.168.1.100:4000", timeout=2)
ser.write(b"*IDN?\n")
print(ser.readline().decode().strip())
ser.close()You use serial_for_url() instead of serial.Serial(). The returned object has the same API, so the rest of your code doesn't change.
Log serial data from networked devices
TofuPilot records test results from your PySerial scripts, tracks pass/fail rates across remote stations, and generates compliance reports. Free to start.
RFC 2217
RFC 2217 extends Telnet to carry serial port control signals. Your code can set baud rate, parity, and toggle RTS/DTR on the remote port.
import serial
ser = serial.serial_for_url("rfc2217://192.168.1.100:2217", timeout=2)
ser.baudrate = 115200
ser.rts = True
ser.write(b"HELLO\r\n")
response = ser.readline()
print(response.decode().strip())
ser.close()The remote server (e.g. ser2net, or PySerial's own RFC 2217 server) translates these control commands into real hardware pin changes on the physical port.
TCP Serial Bridge
You can build a minimal bridge that exposes a local serial port over TCP. This is useful for sharing a device with multiple machines on the same network.
import serial
import socket
import threading
def handle_client(client_sock, ser):
"""Forward bytes between TCP client and serial port."""
def tcp_to_serial():
while True:
data = client_sock.recv(1024)
if not data:
break
ser.write(data)
def serial_to_tcp():
while True:
data = ser.read(ser.in_waiting or 1)
if data:
client_sock.sendall(data)
t1 = threading.Thread(target=tcp_to_serial, daemon=True)
t2 = threading.Thread(target=serial_to_tcp, daemon=True)
t1.start()
t2.start()
t1.join()
def main():
ser = serial.Serial("/dev/ttyUSB0", 115200, timeout=0.1)
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind(("0.0.0.0", 4000))
server.listen(1)
print("Listening on port 4000")
while True:
client_sock, addr = server.accept()
print(f"Connection from {addr}")
threading.Thread(
target=handle_client,
args=(client_sock, ser),
daemon=True,
).start()
if __name__ == "__main__":
main()This is a single-port, multiple-client bridge. For production use, consider ser2net which handles edge cases, logging, and RFC 2217.
ser2net Configuration
On Linux, ser2net is the standard way to expose serial ports over the network. Install it and create a config:
connection: &con1
accepter: tcp,4000
connector: serialdev,/dev/ttyUSB0,115200n81,local
options:
kickolduser: trueThen from any machine on the network:
import serial
ser = serial.serial_for_url("socket://server-hostname:4000", timeout=2)
ser.write(b"test\n")
print(ser.readline())
ser.close()Virtual Port Pairs for Testing
When you don't have hardware, create virtual serial port pairs so two programs can talk to each other.
Linux/macOS (socat):
socat -d -d pty,raw,echo=0 pty,raw,echo=0This prints two /dev/pts/X paths. Open one in your script, the other in a terminal or second script.
Windows (com0com):
Install com0com to create virtual COM port pairs like COM10/COM11. Your script opens one, your test harness opens the other.
import serial
import threading
def writer():
ser = serial.Serial("/dev/pts/2", 9600, timeout=1)
ser.write(b"Hello from writer\n")
ser.close()
def reader():
ser = serial.Serial("/dev/pts/3", 9600, timeout=2)
print(ser.readline().decode().strip())
ser.close()
t = threading.Thread(target=writer)
t.start()
reader()
t.join()Replace /dev/pts/2 and /dev/pts/3 with the paths socat gives you, or COM10/COM11 on Windows.
URL Schemes Reference
PySerial's serial_for_url() supports several URL schemes beyond network:
| Scheme | Example | Purpose |
|---|---|---|
socket:// | socket://host:port | Raw TCP |
rfc2217:// | rfc2217://host:port | RFC 2217 (serial control over TCP) |
loop:// | loop:// | Loopback for testing (TX connects to RX) |
spy:// | spy://device | Wraps another URL and logs all traffic |
hwgrep:// | hwgrep://USB | Hardware info grep for port discovery |
import serial
ser = serial.serial_for_url("loop://", timeout=1)
ser.write(b"echo test")
print(ser.read(9)) # b'echo test'
ser.close()The loop:// scheme is handy for unit tests where you don't need real hardware.