PySerial
PySerialDocs

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

ProtocolURL formatUse case
Raw TCP (socket://)socket://host:portSimple byte forwarding, no port control
RFC 2217 (rfc2217://)rfc2217://host:portFull 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: true

Then 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=0

This 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:

SchemeExamplePurpose
socket://socket://host:portRaw TCP
rfc2217://rfc2217://host:portRFC 2217 (serial control over TCP)
loop://loop://Loopback for testing (TX connects to RX)
spy://spy://deviceWraps another URL and logs all traffic
hwgrep://hwgrep://USBHardware 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.