Mastering Serial Communication on Ubuntu and VMware: Complete Setup & Bidirectional Communication Guide

table of contents

Introduction

I previously wrote about serial communication under Windows. Working with Ubuntu, however, is quite different. The concepts of permissions and device files vary fundamentally, so it’s essential to understand these differences before diving into actual communication.

Key Differences Between Windows and Ubuntu

ItemWindowsUbuntu
Port namesCOM1COM2, …/dev/ttyS0/dev/ttyUSB0, …
Access privilegesAccessible directly as a normal userMust belong to the dialout group
Device detectionCheck via Device ManagerCheck from the command line

Important: On Ubuntu, a normal user cannot access serial ports by default. This is often the first stumbling block for newcomers.

Step 1: Configure Permissions (Most Important)

Add Your User to the dialout Group

To access serial ports, add your current user to the dialout group:

sudo usermod -aG dialout $USER

Critical Note

After running the command, you must log out and log back in (or reboot). Group changes do not take effect until the session is restarted.

Temporary workaround: If you want to try it without logging out, run:

newgrp dialout

This only lasts for the current terminal session; you’ll need to run it again in new terminals. If automatic login is enabled, you may need a full reboot instead of just logging out and back in.

How to Log Out and Log Back In

GUI:

  1. Click the power icon or your username in the top-right corner.
  2. Choose Log Out.
  3. Enter your password at the login screen to return.

Command line:

# Log out of the current session
exit
# or use Ctrl+D

For a guaranteed method, reboot:

sudo reboot

Verify the Group Setting

After logging in again, check whether you belong to the dialout group:

groups

Example output:

your_username adm dialout cdrom sudo dip plugdev lpadmin lxd sambashare

If dialout appears in the list, the setup is complete.

Step 2: Check the Serial Port

Basic Checks

Method 1: List device files

ls /dev/ttyUSB* /dev/ttyS*
  • ttyUSB0 → USB serial adapters (e.g., PL2303, FTDI)
  • ttyS0 → Built‑in or virtual COM port

Method 2: Inspect device logs
Confirm that a USB serial adapter is recognized:

sudo dmesg | grep tty

Example output:

[    0.597201] printk: legacy console [tty0] enabled
[    1.298837] 00:05: ttyS0 at I/O 0x3f8 (irq = 4, base_baud = 115200) is a 16550A

Note: This command is purely for confirmation; it does not “enable” anything.

Gathering More Information

If you need further details:

# List connected USB devices
lsusb

# Detailed information about a specific device
udevadm info -q all -n /dev/ttyUSB0
# or
sudo udevadm info /dev/ttyUSB0

Step 3: Environment-Specific Tips

Physical Machine

In most cases, the above steps suffice. A USB‑serial adapter should automatically appear as /dev/ttyUSB0 when plugged in.

Virtual Machines (VMware / VirtualBox)

Extra configuration may be required.

For VMware:

  1. Open Virtual Machine Settings and add a Serial Port.
  2. Choose Connect to a physical COM port or Connect via socket.
  3. For a USB‑serial adapter: VM → Removable Devices → select the USB device → Connect.

For VirtualBox:

  1. Go to Settings → Serial Ports and enable the port.
  2. If using a USB serial adapter, configure a USB filter.

Step 4: Test Access

Check whether permissions are set correctly:

ls -l /dev/ttyS0

Example output:

crw-rw---- 1 root dialout 4, 64 Jun  5 17:40 /dev/ttyS0

Ensure the dialout group has read/write permissions (rw-) and that your user is a member of the group.

Troubleshooting Common Problems

Problem 1: “Permission denied”

Solution: Add your user to the dialout group and log out/in.

Problem 2: Device not found

# Check whether the device was recognized
dmesg | grep -i usb
sudo dmesg | grep tty

Problem 3: Serial port not visible in a virtual machine

Check the VM settings to make sure the serial port or USB device is connected properly.

Summary

Before starting serial communication on Ubuntu:

  1. Add your user to the dialout group (most important).
  2. Log out and log back in (or reboot).
  3. Verify the serial port with the commands above.
  4. Test permissions to ensure you can read and write.

Once these steps are done, you can use Python’s pyserial or other tools to begin communication.

For sample code, see the accompanying Python examples.
Also check the previous article covering serial communication on Windows, along with the project’s GitHub page.

Achieving True Bidirectional Communication in VMware

Background and Discovery

VMware typically provides only a single virtual serial port, making full host–guest communication difficult and often requiring paid tools. By taking advantage of VMware’s automatic port detection, you can occupy certain ports so VMware selects the ones you want.

Confirming Two Serial Ports

sudo dmesg | grep tty

Output should show both /dev/ttyS0 and /dev/ttyS1:

[    0.624149] printk: legacy console [tty0] enabled
[    1.336023] 00:05: ttyS0 at I/O 0x3f8 (irq = 4, base_baud = 115200) is a 16550A
[    1.373873] 00:06: ttyS1 at I/O 0x2f8 (irq = 3, base_baud = 115200) is a 16550A

Implementation

Step 1: Create a Port-Occupying Tool

#!/usr/bin/env python3
"""
VMware Port Occupier
Forces VMware to choose COM2 and COM4 via automatic detection
"""

import serial
import time
from datetime import datetime

class PortOccupier:
    def __init__(self):
        self.ports = {}
        self.running = False

    def occupy_ports(self, port_list):
        """Occupy specified ports"""
        print("🔧 Port Occupier started")
        print(f"📅 {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
        print("=" * 50)

        self.running = True

        for port in port_list:
            try:
                ser = serial.Serial(port, 9600, timeout=1)
                self.ports[port] = ser
                print(f"✅ Occupied {port}")
            except serial.SerialException as e:
                print(f"❌ Failed to occupy {port}: {e}")

        if self.ports:
            print("\n🎯 VMware setup steps:")
            print("1. Open VMware virtual machine settings")
            print("2. Run automatic detection in the serial port settings")
            print("3. VMware selects ports that are not occupied")
            print("4. When done, type 'q' + Enter to exit")

            print(f"\n📋 Ports in use: {list(self.ports.keys())}")

            # Wait for user input
            while self.running:
                try:
                    user_input = input("Type 'q' + Enter to exit: ").strip().lower()
                    if user_input == 'q':
                        break
                except KeyboardInterrupt:
                    break

    def release_ports(self):
        """Release all ports"""
        print("\n🔌 Releasing ports...")
        for port, ser in self.ports.items():
            try:
                if ser.is_open:
                    ser.close()
                print(f"✅ Released {port}")
            except Exception as e:
                print(f"❌ Error releasing {port}: {e}")

        self.ports.clear()
        self.running = False
        print("✅ All ports released")

def main():
    """Main routine"""
    print("🔧 VMware Port Occupier")
    print("🎯 Goal: Make VMware automatically choose COM2 and COM4")

    # Occupy COM1 and COM3 so that VMware selects COM2 and COM4
    target_ports = ['COM1', 'COM3']

    occupier = PortOccupier()

    try:
        occupier.occupy_ports(target_ports)
    except KeyboardInterrupt:
        print("\n⚠️ Terminated")
    finally:
        occupier.release_ports()

if __name__ == "__main__":
    main()

Step 2: Operate VMware Settings

  1. Run the port occupier:
    python port_occupier.py
  2. Open VMware Virtual Machine Settings.
  3. Set Serial Port → Use physical serial port and run auto-detection.
    • Because COM1 and COM3 are occupied, VMware automatically chooses COM2.
  4. Add a second serial port via Add Hardware → Serial Port.
  5. Run auto-detection again; COM4 is selected.

Step 3: Verify the Result

This configuration yields the following mapping:

  • Windows host:
    • COM2 ↔ /dev/ttyS0 (Linux)
    • COM4 ↔ /dev/ttyS1 (Linux)
  • Linux guest:
    • /dev/ttyS0 ↔ COM2 (Windows)
    • /dev/ttyS1 ↔ COM4 (Windows)

Example of Full-Duplex Setup

Windows dashboard:

  • RX: COM2 (messages from Linux)
  • TX: COM4 (messages to Linux)
  • Separate TX/RX: ✔️

Linux dashboard:

  • RX: /dev/ttyS1 (messages from Windows)
  • TX: /dev/ttyS0 (messages to Windows)
  • Separate TX/RX: ✔️

Communication Flow

Windows → COM4 → VMware → /dev/ttyS0 → Linux
Linux   → /dev/ttyS1 → VMware → COM2 → Windows

Advantages

  • Free of charge
    No need for paid serial port tools; only VMware’s standard features are used.
  • Stable communication
    Relies on VMware’s native functionality, avoiding the instability found in tools like com0com.
  • Broad compatibility
    Works with any serial communication application, independent of programming language.

Troubleshooting

  • Port occupation fails
    # Check which ports are in use
    netstat -an | findstr :COM
  • Auto detection doesn’t work as expected
    Verify that the port occupier is running correctly, or reset VMware’s settings and try again.
  • Communication works only in one direction
    Double-check send/receive port assignments and review VMware’s serial port settings.

This discovery lowers the barrier to developing serial communication, enabling more developers to use it with ease.

Revised Approach for VMware

A key update reveals that using named pipes is the most stable and recommended approach, surpassing the port-occupying method.

Comparing Solutions

MethodStabilitySetup DifficultyRecommendationNotes
Named pipes★★★★★★★★★ (easiest)Top choiceStandard VMware feature, very stable
Port occupation★★★★★★★★★Clever but less stable
TCP/IP bridge★★★★★★★★★◎ (alternative)Works over network
Physical serial ports★★★★★★★Requires actual hardware

Recommended Approach 1: Named Pipes (Most Stable)

Step 1: Configure VMware

  • Open VM Settings → Add Hardware → Serial Port.

Serial Port 1

  • Connection type: Use named pipe
  • Pipe name: \\.\pipe\vmware_tx
  • Pipe endpoint: Server
  • I/O mode: Application
  • Connect at power on: Checked

Serial Port 2

  • Connection type: Use named pipe
  • Pipe name: \\.\pipe\vmware_rx
  • Pipe endpoint: Server
  • I/O mode: Application
  • Connect at power on: Checked

Step 2: Confirm on Linux

# Check serial devices
sudo dmesg | grep tty
# Expected lines:
# ttyS0 at I/O 0x3f8 (irq = 4) - pipe 1
# ttyS1 at I/O 0x2f8 (irq = 3) - pipe 2

# Set permissions
sudo chmod 666 /dev/ttyS0 /dev/ttyS1
# or
sudo usermod -a -G dialout $USER  # requires relogin

Step 3: Access Pipes from Windows

#!/usr/bin/env python3
"""
VMware Named Pipe Bidirectional Communication
Windows-side implementation
"""

import win32pipe
import win32file
import threading
import time
import json
from datetime import datetime

class VMwarePipeComm:
    def __init__(self):
        self.running = False

    def connect_to_pipe(self, pipe_name, timeout=5000):
        """Connect to a named pipe"""
        try:
            handle = win32file.CreateFile(
                pipe_name,
                win32file.GENERIC_READ | win32file.GENERIC_WRITE,
                0, None,
                win32file.OPEN_EXISTING,
                0, None
            )
            return handle
        except Exception as e:
            print(f"Pipe connection error {pipe_name}: {e}")
            return None

    def bidirectional_communication(self):
        """Run bidirectional communication"""
        print("=== VMware Named Pipe Bidirectional Communication ===")

        # Connect to pipes
        tx_pipe = self.connect_to_pipe(r"\\.\pipe\vmware_tx")  # sending
        rx_pipe = self.connect_to_pipe(r"\\.\pipe\vmware_rx")  # receiving

        if not tx_pipe or not rx_pipe:
            print("Pipe connection failed")
            return

        print("✅ Both pipes connected successfully")
        self.running = True

        # Start receive thread
        rx_thread = threading.Thread(target=self.receive_data, args=(rx_pipe,))
        rx_thread.daemon = True
        rx_thread.start()

        # Sending loop
        self.send_data(tx_pipe)

        # Clean up
        self.running = False
        win32file.CloseHandle(tx_pipe)
        win32file.CloseHandle(rx_pipe)

    def send_data(self, pipe_handle):
        """Send data"""
        counter = 1
        try:
            while self.running:
                message = {
                    "id": counter,
                    "timestamp": datetime.now().isoformat(),
                    "source": "Windows_Host",
                    "data": f"Message_{counter:03d}"
                }

                data = (json.dumps(message) + "\n").encode("utf-8")
                win32file.WriteFile(pipe_handle, data)

                print(f"[TX {counter:03d}] → Linux: {message['data']}")
                counter += 1
                time.sleep(2)

        except KeyboardInterrupt:
            print("Stopped sending")

    def receive_data(self, pipe_handle):
        """Receive data"""
        buffer = b""
        try:
            while self.running:
                try:
                    _, data = win32file.ReadFile(pipe_handle, 1024)
                    if data:
                        buffer += data
                        while b"\n" in buffer:
                            line, buffer = buffer.split(b"\n", 1)
                            if line:
                                msg = line.decode("utf-8", errors="ignore")
                                print(f"[RX] ← Linux: {msg}")
                except:
                    time.sleep(0.1)
        except Exception as e:
            print(f"Receive error: {e}")

if __name__ == "__main__":
    comm = VMwarePipeComm()
    comm.bidirectional_communication()

Main Revisions to the VMware Guide

Problems with the Old Approach

  • Recommended an elaborate port occupation trick as “groundbreaking”
  • Ignored the advantages of named pipes
  • Downplayed environment-specific instability

Improvements in the Updated Version

  • Clearly states named pipes as the top recommendation
  • Objectively evaluates stability and practicality of each method
  • Provides environment-specific instructions

Emphasis on Practicality

  • Expanded troubleshooting guide
  • Optimized steps for physical machines, VirtualBox, and VMware

Features of the New Universal Test Tool

Intelligent Functions

  • Automatically detects whether you’re on VMware, VirtualBox, or real hardware
  • Categorizes ports (USB, virtual, physical)
  • Suggests optimal settings for each environment

Practical Features

  • Bidirectional test wizard
  • Performance benchmarks
  • Real-time monitoring of incoming data

Recommended Setup by Environment

EnvironmentBest SolutionReasonDifficulty
Physical to physicalDirect cableMost stable & fast
VirtualBoxVirtual serial portOptimized for VBox★★
VMwareNamed pipesStandard VMware★★★
Cross‑platformTCP/IP bridgeHighest flexibility★★★★

Example Usage Scenarios

Development / Learning

VirtualBox (Recommended)

  1. Set up a virtual serial connection between host and guest.
  2. Confirm bidirectional communication with the test tool.
  3. Develop your actual application.

Production

Two physical machines (Most Stable)

  1. Use two USB–serial adapters.
  2. Connect them with a crossover cable.
  3. Add redundancy for fail‑safe operation.

Testing / Verification

TCP/IP Bridge (Most Flexible)

  1. Connect multiple environments via network.
  2. Record logs and monitor traffic.
  3. Tie into automated test scripts.

Fully Solving Bidirectional Communication in VMware

Issue Summary

  • Currently, Windows → Linux works via named pipe.
  • Linux → Windows cannot send with a single pipe.
  • A true bidirectional link is needed.

Solution 1: Dual Named Pipes (Recommended)

VMware Settings

Serial Port 1 (Windows → Linux)

  • Connection type: Use named pipe
  • Pipe name: \\.\pipe\win_to_linux
  • Pipe endpoint: Server
  • I/O mode: Application

Serial Port 2 (Linux → Windows)

  • Connection type: Use named pipe
  • Pipe name: \\.\pipe\linux_to_win
  • Pipe endpoint: Server
  • I/O mode: Application

Linux Mapping

VMware PortLinux DevicePurpose
Serial Port 1/dev/ttyS0Receive from Windows
Serial Port 2/dev/ttyS1Send to Windows

Solution 2: TCP/IP Hybrid (Alternative)

Combine a physical serial port with TCP/IP.

Configuration

  • Windows → Linux: Named pipe
  • Linux → Windows: TCP/IP communication

Implementation Code

Windows Side (Dual Pipe Support)

#!/usr/bin/env python3
"""
VMware Dual-Pipe Bidirectional Communication - Windows Side
Achieves full duplex messaging
"""

import win32pipe
import win32file
import win32event
import win32api
import threading
import time
import json
import signal
import sys
import atexit
from datetime import datetime
import pywintypes

class DualPipeComm:
    def __init__(self):
        self.running = False
        # Windows → Linux (send)
        self.tx_pipe = None
        # Linux → Windows (receive)
        self.rx_pipe = None
        self.threads = []

        # Set up safe exit
        atexit.register(self.cleanup)
        signal.signal(signal.SIGINT, self.signal_handler)

    def signal_handler(self, signum, frame):
        """Handle termination signal"""
        print("\n⚠️ Signal received")
        self.safe_shutdown()
        sys.exit(0)

    def safe_shutdown(self):
        """Graceful shutdown"""
        print("🛑 Shutting down...")
        self.running = False

        # Wait for threads to end
        for thread in self.threads:
            if thread.is_alive():
                thread.join(timeout=1.0)

        self.cleanup()
        print("✅ Shutdown complete")

    def cleanup(self):
        """Clean up resources"""
        try:
            if self.tx_pipe:
                win32file.CloseHandle(self.tx_pipe)
                self.tx_pipe = None
                print("📤 Closed TX pipe")
        except:
            pass

        try:
            if self.rx_pipe:
                win32file.CloseHandle(self.rx_pipe)
                self.rx_pipe = None
                print("📥 Closed RX pipe")
        except:
            pass

    def connect_pipe_safe(self, pipe_name, timeout=5000):
        """Safely connect to a named pipe"""
        print(f"🔌 Attempting to connect: {pipe_name}")

        try:
            # Wait for the pipe to become available
            win32pipe.WaitNamedPipe(pipe_name, timeout)

            # Connect in overlapped mode
            handle = win32file.CreateFile(
                pipe_name,
                win32file.GENERIC_READ | win32file.GENERIC_WRITE,
                0,
                None,
                win32file.OPEN_EXISTING,
                win32file.FILE_FLAG_OVERLAPPED,
                None,
            )

            print(f"✅ Connected: {pipe_name}")
            return handle

        except Exception as e:
            print(f"❌ Connection failed {pipe_name}: {e}")
            return None

    def dual_pipe_communication(self):
        """Bidirectional communication using two pipes"""
        print("=== VMware Dual-Pipe Communication ===")
        print("📤 TX pipe: \\\\.\\pipe\\win_to_linux")
        print("📥 RX pipe: \\\\.\\pipe\\linux_to_win")
        print("💡 Press Ctrl+C to exit safely")
        print("=" * 50)

        # Connect to pipes
        self.tx_pipe = self.connect_pipe_safe(r"\\.\pipe\win_to_linux")
        self.rx_pipe = self.connect_pipe_safe(r"\\.\pipe\linux_to_win")

        if not self.tx_pipe:
            print("❌ Failed to connect TX pipe")
            print("💡 Check VMware settings:")
            print("   Serial Port 1: \\\\.\\pipe\\win_to_linux")
            return False

        if not self.rx_pipe:
            print("❌ Failed to connect RX pipe")
            print("💡 Check VMware settings:")
            print("   Serial Port 2: \\\\.\\pipe\\linux_to_win")
            return False

        print("✅ Both pipes connected")
        print("🚀 Starting bidirectional session...")

        self.running = True

        # Start receiver thread
        rx_thread = threading.Thread(
            target=self.receive_from_linux,
            name="Linux_RX_Thread",
        )
        rx_thread.daemon = True
        rx_thread.start()
        self.threads.append(rx_thread)

        # Main send loop
        self.send_to_linux()

        return True

    def send_to_linux(self):
        """Send data Windows → Linux"""
        counter = 1
        last_send = time.time()

        print("📤 Sending from Windows to Linux")

        try:
            while self.running:
                current_time = time.time()

                # Send every 2 seconds
                if current_time - last_send >= 2.0:
                    message = {
                        "id": counter,
                        "timestamp": datetime.now().isoformat(),
                        "source": "Windows_Host",
                        "direction": "Win_to_Linux",
                        "data": f"Windows_Message_{counter:03d}",
                        "system_info": {
                            "platform": "Windows",
                            "counter": counter,
                        },
                    }

                    try:
                        data = (json.dumps(message) + "\n").encode("utf-8")

                        # Asynchronous send
                        overlapped = pywintypes.OVERLAPPED()
                        overlapped.hEvent = win32event.CreateEvent(
                            None, True, False, None
                        )

                        win32file.WriteFile(self.tx_pipe, data, overlapped)
                        result = win32event.WaitForSingleObject(
                            overlapped.hEvent, 1000
                        )

                        if result == win32event.WAIT_OBJECT_0:
                            print(
                                f"[TX {counter:03d}] Windows → Linux: {message['data']}"
                            )
                            counter += 1
                            last_send = current_time
                        else:
                            print(f"⚠️ Send timeout: {counter}")

                        win32api.CloseHandle(overlapped.hEvent)

                    except Exception as e:
                        print(f"❌ Send error: {e}")
                        break

                time.sleep(0.1)

        except KeyboardInterrupt:
            pass
        finally:
            print("📤 Windows sender stopped")

    def receive_from_linux(self):
        """Receive data Linux → Windows"""
        buffer = b""

        print("📥 Waiting for data from Linux")

        try:
            while self.running:
                try:
                    overlapped = pywintypes.OVERLAPPED()
                    overlapped.hEvent = win32event.CreateEvent(
                        None, True, False, None
                    )

                    # Asynchronous read
                    win32file.ReadFile(self.rx_pipe, 1024, overlapped)
                    result = win32event.WaitForSingleObject(
                        overlapped.hEvent, 100
                    )

                    if result == win32event.WAIT_OBJECT_0:
                        bytes_read = win32file.GetOverlappedResult(
                            self.rx_pipe, overlapped, False
                        )
                        if bytes_read > 0:
                            # Get the actual data
                            try:
                                _, data = win32file.ReadFile(
                                    self.rx_pipe, bytes_read
                                )
                                buffer += data

                                # Process line by line
                                while b"\n" in buffer:
                                    line, buffer = buffer.split(b"\n", 1)
                                    if line:
                                        msg = line.decode(
                                            "utf-8", errors="ignore"
                                        ).strip()
                                        if msg:
                                            timestamp = datetime.now().strftime(
                                                "%H:%M:%S.%f"
                                            )[:-3]
                                            print(
                                                f"[RX {timestamp}] Linux → Windows: {msg}"
                                            )
                            except:
                                # Handle overlapping read
                                pass

                    win32api.CloseHandle(overlapped.hEvent)

                except pywintypes.error as e:
                    if e.args[0] == 109:  # ERROR_BROKEN_PIPE
                        print("⚠️ Pipe closed on Linux side")
                        break
                    elif e.args[0] == 232:  # ERROR_NO_DATA
                        pass  # No data (normal)

                time.sleep(0.01)

        except Exception as e:
            print(f"❌ Receive error: {e}")
        finally:
            print("📥 Linux receiver stopped")

    def test_pipes(self):
        """Check pipe connectivity"""
        print("=== Dual-Pipe Connection Test ===")

        pipes = [
            (r"\\.\pipe\win_to_linux", "Windows → Linux"),
            (r"\\.\pipe\linux_to_win", "Linux → Windows"),
        ]

        for pipe_name, description in pipes:
            print(f"\n🔍 Testing: {description}")
            print(f"   Pipe: {pipe_name}")

            try:
                win32pipe.WaitNamedPipe(pipe_name, 1000)
                print("   ✅ Pipe detected")

                handle = self.connect_pipe_safe(pipe_name, 2000)
                if handle:
                    print("   ✅ Connection successful")
                    win32file.CloseHandle(handle)
                else:
                    print("   ❌ Connection failed")

            except Exception as e:
                print(f"   ❌ Pipe not found: {e}")
                print("   💡 Check VMware settings")

def main():
    """Main execution"""
    comm = DualPipeComm()

    print("🔧 VMware Dual-Pipe Communication Tool")
    print("=" * 40)
    print("1. Test pipe connections")
    print("2. Start bidirectional session")
    print("3. VMware setup guide")
    print("4. Exit")

    while True:
        try:
            choice = input("\nChoose an option (1-4): ").strip()

            if choice == "1":
                comm.test_pipes()

            elif choice == "2":
                if comm.dual_pipe_communication():
                    print("✅ Session ended")
                else:
                    print("❌ Failed to start communication")

            elif choice == "3":
                print("\n=== VMware Setup Guide ===")
                print("【Serial Port 1】")
                print("  Connection type: Use named pipe")
                print("  Pipe name: \\\\.\\pipe\\win_to_linux")
                print("  Pipe endpoint: Server")
                print("  I/O mode: Application")
                print()
                print("【Serial Port 2】")
                print("  Connection type: Use named pipe")
                print("  Pipe name: \\\\.\\pipe\\linux_to_win")
                print("  Pipe endpoint: Server")
                print("  I/O mode: Application")
                print()
                print("【Linux Side】")
                print("  /dev/ttyS0 ← Receive from Windows")
                print("  /dev/ttyS1 → Send to Windows")

            elif choice == "4":
                print("👋 Exiting")
                break

            else:
                print("❌ Choose 1–4")

        except KeyboardInterrupt:
            print("\n👋 Exiting")
            break

    comm.cleanup()

if __name__ == "__main__":
    main()
#!/usr/bin/env python3
"""
Enhanced Linux Serial Communication Tool
VMware Dual Pipe Support – Full Duplex
"""

import serial
import time
import random
import json
import os
import sys
import threading
from datetime import datetime

class LinuxSerialComm:
    def __init__(self):
        self.running = False
        self.threads = []

    def check_serial_devices(self):
        """Check serial devices"""
        devices = {
            '/dev/ttyS0': 'Receive from Windows',
            '/dev/ttyS1': 'Send to Windows'
        }

        print("🔍 Checking serial devices:")
        status = {}

        for device, purpose in devices.items():
            if os.path.exists(device):
                readable = os.access(device, os.R_OK)
                writable = os.access(device, os.W_OK)
                status[device] = readable and writable

                status_icon = "✅" if status[device] else "❌"
                print(f"  {status_icon} {device}: {purpose}")
                print(f"      Readable={readable}, Writable={writable}")

                if not status[device]:
                    print(f"      💡 Fix permissions: sudo chmod 666 {device}")
            else:
                status[device] = False
                print(f"  ❌ {device}: device does not exist")
                print(f"      💡 Check your VMware settings")

        all_ready = all(status.values())

        if not all_ready:
            print("\n⚠️ How to fix permissions:")
            print("  sudo usermod -a -G dialout $USER")
            print("  logout && login  # re-login required")
            print("or")
            print("  sudo chmod 666 /dev/ttyS*")

        return all_ready

    def send_to_windows(self, device='/dev/ttyS1', test_mode=True):
        """Send Linux → Windows"""
        description = f"Sending Linux → Windows ({device})"

        try:
            print(f"📤 {description} starting...")
            if test_mode:
                print("🧪 Test mode: Ctrl+C to stop")

            with serial.Serial(device, 9600, timeout=1) as ser:
                counter = 1

                while self.running:
                    if test_mode:
                        test_patterns = [
                            f"LINUX_TO_WIN,{counter},{random.randint(20, 30)}.{random.randint(0, 99):02d},CPU_TEMP",
                            f"UBUNTU_STATUS,{counter},RUNNING,{datetime.now().strftime('%H:%M:%S')}",
                            f"SYSTEM_DATA,{counter},{random.randint(0, 100)},MEMORY_USAGE_PERCENT",
                            f"LINUX_HEARTBEAT,{counter},ALIVE_FROM_LINUX",
                            f"PROCESS_COUNT,{counter},{random.randint(100, 300)},TOTAL_PROCESSES",
                            f"NETWORK_STAT,{counter},{random.randint(1000, 9999)},PACKETS_PER_SEC",
                            json.dumps({
                                "id": counter,
                                "timestamp": datetime.now().isoformat(),
                                "source": "Linux_Guest",
                                "direction": "Linux_to_Windows",
                                "system": {
                                    "hostname": "ubuntu-vm",
                                    "uptime": random.randint(3600, 86400),
                                    "load_avg": round(random.uniform(0.1, 2.0), 2),
                                    "free_memory": random.randint(1000, 4000)
                                },
                                "sensors": {
                                    "cpu_temp": random.randint(40, 70),
                                    "fan_speed": random.randint(1000, 3000),
                                    "voltage": round(random.uniform(11.8, 12.2), 1),
                                }
                            })
                        ]
                        message = random.choice(test_patterns) + "\r\n"
                    else:
                        message = input(f"[{counter:03d}] Message: ").strip()
                        if message.lower() == 'quit':
                            break
                        message = f"MANUAL,{counter},{message},{datetime.now().strftime('%H:%M:%S')}\r\n"

                    ser.write(message.encode('utf-8'))
                    print(f"[TX {counter:03d}] Linux → Windows: {message.strip()}")
                    counter += 1

                    if test_mode:
                        time.sleep(random.uniform(1.5, 3.0))

        except KeyboardInterrupt:
            print(f"\n✅ Stopped {description}")
        except serial.SerialException as e:
            print(f"❌ Serial error ({device}): {e}")
            print("💡 Check device permissions and VMware settings")
        except Exception as e:
            print(f"❌ Unexpected error: {e}")
        finally:
            self.running = False

    def receive_from_windows(self, device='/dev/ttyS0'):
        """Monitor Windows → Linux reception"""
        try:
            print(f"📥 Starting Windows → Linux monitor ({device})")

            with serial.Serial(device, 9600, timeout=1) as ser:
                buffer = ""

                while self.running:
                    if ser.in_waiting > 0:
                        data = ser.read(ser.in_waiting).decode('utf-8', errors='ignore')
                        buffer += data

                        while '\n' in buffer:
                            line, buffer = buffer.split('\n', 1)
                            line = line.strip()
                            if line:
                                timestamp = datetime.now().strftime('%H:%M:%S.%f')[:-3]
                                print(f"[RX {timestamp}] Windows → Linux: {line}")

                    time.sleep(0.01)

        except serial.SerialException as e:
            print(f"❌ Receive error ({device}): {e}")
        except Exception as e:
            print(f"❌ Unexpected receive error: {e}")
        finally:
            print(f"📥 Monitor stopped ({device})")

    def bidirectional_test(self):
        """Bidirectional communication test"""
        print("🔄 Starting bidirectional communication test")
        print("📤 /dev/ttyS1 → Windows")
        print("📥 /dev/ttyS0 ← Windows")
        print("Press Ctrl+C to stop")
        print("=" * 40)

        self.running = True

        rx_thread = threading.Thread(
            target=self.receive_from_windows,
            args=('/dev/ttyS0',),
            name="Windows_RX"
        )
        rx_thread.daemon = True
        rx_thread.start()
        self.threads.append(rx_thread)

        self.send_to_windows('/dev/ttyS1', test_mode=True)

    def manual_communication(self):
        """Manual communication mode"""
        print("💬 Manual communication mode")
        print("📤 Send: /dev/ttyS1 → Windows")
        print("📥 Receive: /dev/ttyS0 ← Windows")
        print("Type a message to send, 'quit' to exit")
        print("=" * 40)

        self.running = True

        rx_thread = threading.Thread(
            target=self.receive_from_windows,
            args=('/dev/ttyS0',),
            name="Manual_RX"
        )
        rx_thread.daemon = True
        rx_thread.start()
        self.threads.append(rx_thread)

        self.send_to_windows('/dev/ttyS1', test_mode=False)

    def performance_test(self):
        """Performance test"""
        print("⚡ Starting performance test")
        device = '/dev/ttyS1'
        duration = 30

        try:
            with serial.Serial(device, 9600, timeout=1) as ser:
                start_time = time.time()
                bytes_sent = 0
                packets_sent = 0

                print(f"📊 Measuring for {duration} seconds...")

                while time.time() - start_time < duration:
                    test_data = f"PERF_{packets_sent:06d}_" + "L" * 80 + "\r\n"
                    ser.write(test_data.encode('utf-8'))

                    bytes_sent += len(test_data)
                    packets_sent += 1

                    if packets_sent % 50 == 0:
                        elapsed = time.time() - start_time
                        bps = bytes_sent / elapsed if elapsed > 0 else 0
                        print(f"📈 Progress: {packets_sent} packets, {bps:.1f} bytes/sec")

                    time.sleep(0.02)  # 50Hz

                elapsed = time.time() - start_time
                print("\n📊 Performance test results:")
                print(f"  Packets sent: {packets_sent:,}")
                print(f"  Bytes sent: {bytes_sent:,}")
                print(f"  Elapsed time: {elapsed:.2f} sec")
                print(f"  Average throughput: {bytes_sent/elapsed:.1f} bytes/sec")
                print(f"  Theoretical (9600 baud): {9600/10:.1f} bytes/sec")
                print(f"  Efficiency: {(bytes_sent/elapsed)/(9600/10)*100:.1f}%")

        except Exception as e:
            print(f"❌ Performance test error: {e}")

    def stop_all(self):
        """Stop all communication"""
        self.running = False

        for thread in self.threads:
            if thread.is_alive():
                thread.join(timeout=1.0)

        print("✅ All communication stopped")

def main():
    """Main execution"""
    comm = LinuxSerialComm()

    print("=== Linux Serial Communication Tool (Enhanced) ===")
    print("🐧 VMware Guest - Dual Pipe Support")
    print(f"📅 {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
    print("=" * 55)

    if not comm.check_serial_devices():
        print("\n❌ Serial devices not ready")
        print("Please address the issues above and try again")
        return

    try:
        while True:
            print("\n🎛️ Menu:")
            print("1. Check device status")
            print("2. Send test from Linux to Windows")
            print("3. Monitor Windows → Linux")
            print("4. Bidirectional communication test")
            print("5. Manual communication mode")
            print("6. Performance test")
            print("7. VMware setup guide")
            print("8. Exit")

            choice = input("\nChoose an option (1-8): ").strip()

            if choice == "1":
                comm.check_serial_devices()
            elif choice == "2":
                comm.running = True
                comm.send_to_windows('/dev/ttyS1', test_mode=True)
            elif choice == "3":
                comm.running = True
                comm.receive_from_windows('/dev/ttyS0')
            elif choice == "4":
                comm.bidirectional_test()
            elif choice == "5":
                comm.manual_communication()
            elif choice == "6":
                comm.performance_test()
            elif choice == "7":
                print("\n=== VMware Setup Guide ===")
                print("[Required Configuration]")
                print("Serial Port 1:")
                print("  Pipe name: \\\\.\\pipe\\win_to_linux")
                print("  Use: Windows → Linux (/dev/ttyS0)")
                print()
                print("Serial Port 2:")
                print("  Pipe name: \\\\.\\pipe\\linux_to_win")
                print("  Use: Linux → Windows (/dev/ttyS1)")
                print()
                print("[Linux Permissions]")
                print("sudo usermod -a -G dialout $USER && logout")
                print("or")
                print("sudo chmod 666 /dev/ttyS*")
            elif choice == "8":
                print("👋 Exiting")
                break
            else:
                print("❌ Please choose 1-8")

    except KeyboardInterrupt:
        print("\n👋 Exiting")
    finally:
        comm.stop_all()

if __name__ == "__main__":
    main()

VMware Bidirectional Serial Communication: A Complete Solution

Root Cause

A single named pipe only supports one-way communication, so you can’t achieve bidirectional transfers with a single pipe.

Dual-Pipe Configuration

Set up two serial ports in VMware.

Serial Port 1: Windows → Linux

  • Connection type: Named Pipe
  • Pipe name: \\.\pipe\host_to_guest
  • Pipe end: Server
  • I/O mode: Application

Serial Port 2: Linux → Windows

  • Connection type: Named Pipe
  • Pipe name: \\.\pipe\guest_to_host
  • Pipe end: Server
  • I/O mode: Application

Communication Flow

Windows App
  ↓ Send     ↑ Receive
\\.\pipe\host_to_guest  \\.\pipe\guest_to_host
  ↓ VMware   ↑ VMware
/dev/ttyS0    /dev/ttyS1
  ↓ Receive  ↑ Send
Linux App

Sample Session

Windows Log

[TX 001] Windows → Linux: Windows_Message_001
[RX 14:30:15.123] Linux → Windows: GUEST_TO_HOST,001,45.23,CPU_TEMP

Linux Log

[RX 14:30:15.100] Windows → Linux: {"source":"Windows_Host","data":"Windows_Message_001"}
[TX 001] Linux → Windows: GUEST_TO_HOST,001,45.23,CPU_TEMP

Setup Steps

  1. Configure VMware
    • Open the VM settings.
    • Add “Serial Port” twice.
    • Apply the settings above to each port.
  2. Run on Windows
    python dual_pipe_windows.py # Select menu 2 to start two-way comms
  3. Run on Linux
    python3 dual_pipe_linux.py # Select menu 4 for a bidirectional test

Simplified VMware Guide

Key Discovery

Once you configure a named pipe and reboot the VM, VMware detects existing COM3/COM4 ports from Windows and exposes them as physical serial ports.

How VMware Works

  1. Windows already has COM1–COM4 (checked in Device Manager).
  2. VMware initially only shows COM1 and COM2.
  3. When you set a named pipe, VMware refreshes its port scan.
  4. After a reboot, COM3 and COM4 appear as options.
  5. You can use COM3 and COM4 directly as physical ports.

The named pipe doesn’t create new ports.
It simply prompts VMware to re-scan and recognize the existing ones, after which you can switch to a physical-port configuration.

Confirmed Configuration Steps

  1. Initial VMware Setup
    Set up a temporary named pipe via Add Hardware → Serial Port with:
    • Connection type: Named Pipe
    • Pipe name: \\.\pipe\host_to_guest
    • Pipe end: Server
    • I/O mode: Application
    • Connect at power on: Enabled
  2. Reboot the VM
    • Boot once with the pipe configured.
    • Shut down normally.
    • Check settings again: COM3 and COM4 are now listed.
  3. Switch to a Physical Port
    • Edit the serial port settings.
    • Select “Use physical serial port (U)” and choose COM3 or COM4 (now visible thanks to the previous step).
  4. Check on Windows
    • Before: Device Manager shows COM1–COM4; VMware only lists COM1 and COM2.
    • After: Device Manager unchanged; VMware now lists COM1–COM4.
    • Conclusion: COM3 and COM4 already existed on Windows, and VMware began recognizing them after the named pipe step.

Implementation Example (Using Physical Port)

Windows Side

#!/usr/bin/env python3
"""Windows side - using a physical COM port"""
import serial
import threading
import time
import json
from datetime import datetime

def windows_communication():
    # Port exposed by VMware
    port = "COM3"  # or COM4

    def send_data():
        with serial.Serial(port, 9600, timeout=1) as ser:
            counter = 1
            while True:
                message = {
                    "id": counter,
                    "source": "Windows",
                    "data": f"Win_Message_{counter:03d}",
                    "timestamp": datetime.now().isoformat()
                }

                data = (json.dumps(message) + "\n").encode('utf-8')
                ser.write(data)
                print(f"[TX] Windows({port}) → Linux: {message['data']}")
                counter += 1
                time.sleep(2)

    def receive_data():
        with serial.Serial(port, 9600, timeout=1) as ser:
            while True:
                if ser.in_waiting > 0:
                    data = ser.readline().decode('utf-8', errors='ignore').strip()
                    if data:
                        print(f"[RX] Linux → Windows({port}): {data}")
                time.sleep(0.01)

    try:
        choice = input("1: Send only, 2: Receive only, 3: Bidirectional: ")
        if choice == "1":
            send_data()
        elif choice == "2":
            receive_data()
        elif choice == "3":
            rx_thread = threading.Thread(target=receive_data, daemon=True)
            rx_thread.start()
            send_data()
    except KeyboardInterrupt:
        print("Communication ended")

if __name__ == "__main__":
    windows_communication()

Linux Side

#!/usr/bin/env python3
"""Linux side - VMware virtual serial port"""
import serial
import threading
import time
import json
from datetime import datetime

def linux_communication():
    port = "/dev/ttyS0"  # Detected by VMware

    def receive_data():
        with serial.Serial(port, 9600, timeout=1) as ser:
            while True:
                if ser.in_waiting > 0:
                    data = ser.readline().decode('utf-8', errors='ignore').strip()
                    if data:
                        print(f"[RX] Windows → Linux({port}): {data}")
                time.sleep(0.01)

    def send_data():
        with serial.Serial(port, 9600, timeout=1) as ser:
            counter = 1
            while True:
                message = {
                    "id": counter,
                    "source": "Linux",
                    "data": f"Linux_Message_{counter:03d}",
                    "timestamp": datetime.now().isoformat()
                }

                data = (json.dumps(message) + "\n").encode('utf-8')
                ser.write(data)
                print(f"[TX] Linux({port}) → Windows: {message['data']}")
                counter += 1
                time.sleep(2)

    try:
        choice = input("1: Send only, 2: Receive only, 3: Bidirectional: ")
        if choice == "1":
            send_data()
        elif choice == "2":
            receive_data()
        elif choice == "3":
            rx_thread = threading.Thread(target=receive_data, daemon=True)
            rx_thread.start()
            send_data()
    except KeyboardInterrupt:
        print("Communication ended")

if __name__ == "__main__":
    linux_communication()

Port Mapping (Proven)

Setup StageWindows OptionsActual UsageLinux Detection
Before configurationCOM1 and COM2 onlyLimited/dev/ttyS0/dev/ttyS1 (depending on config)
After named pipe setupCOM1, COM2, COM3, COM4All ports available/dev/ttyS0/dev/ttyS1
Physical port modeSelect COM3 or COM4Standard serial comms/dev/ttyS0 for both directions

Benefits

  1. Simpler Setup
    • Only one named pipe configuration.
    • No complex Win32 APIs required.
    • Works with standard pyserial.
  2. Stability
    • Uses VMware’s standard features.
    • OS recognizes a regular serial port.
    • Easier error handling.
  3. Development Efficiency
    • Leverage existing serial communication knowledge.
    • Compatible with typical tools like Tera Term.
    • Cross-platform ready.

Troubleshooting

  1. COM Port Not Detected
    • Check again after reboot.
    • Ensure VMware Tools is up to date.
    • Refresh via Device Manager.
  2. Linux Device Missing
    sudo dmesg | grep ttyS # Check VMware serial devices
    sudo chmod 666 /dev/ttyS* # Set permissions manually
  3. One-Way Only
    • Add a second serial port.
    • Confirm COM3/COM4 are recognized.
    • Verify port assignments.

Easier Alternative

If VMware setup is too complex, try a hybrid TCP/IP approach.

# Windows → Linux: Named pipe (only one)
# Linux → Windows: Over TCP/IP

Windows

python network_serial_bridge.py  # Start the receiver server

Linux

python enhanced_linux_sender.py  # Send via TCP

Troubleshooting

  1. Pipe Connection Error
    • “File not found” — ensure the VM is running before starting your Windows tool.
    • Double-check the pipe name.
  2. Linux Device Not Found
    • /dev/ttyS1 missing? Verify that VMware added a second serial port and reboot.
  3. Permission Error
    sudo chmod 666 /dev/ttyS*
    sudo usermod -a -G dialout $USER && logout

Practical Use Cases

  • Development & Testing
    • Display dashboards on Windows.
    • Send sensor data from Linux.
    • Real-time, bidirectional exchange.
  • IoT Prototyping
    • Windows issues control commands.
    • Linux returns status and sensor values.
    • Useful for hardware-in-the-loop simulation.

🥈 Recommended Method 2: TCP/IP Bridge (Alternative Solution)

If configuring VMware is problematic, you can use a TCP/IP bridge instead. This method sends serial data over the network to achieve bidirectional communication.

Windows Side – Server Setup

  1. Use the script network_serial_bridge.py.
  2. Run the script and choose Menu 1: Serial → Network.
python network_serial_bridge.py
# Select Menu 1 when prompted

Below is the full Python script:

#!/usr/bin/env python3
"""
Bridge to send serial data over a network.
Supports transferring serial data from Windows to Linux.
"""

import socket
import serial
import threading
import time
import json
from datetime import datetime

class NetworkSerialBridge:
    def __init__(self):
        self.running = False

    def serial_to_network_server(self, serial_port, baudrate, network_port=12345):
        """Transfer data from the serial port to the network (server side)."""
        print("Serial → Network server started")
        print(f"Serial Port: {serial_port} ({baudrate} baud)")
        print(f"Network Port: {network_port}")
        print("Waiting for client connection...")

        server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        server_socket.bind(('0.0.0.0', network_port))
        server_socket.listen(1)

        try:
            with serial.Serial(serial_port, baudrate, timeout=1) as ser:
                while True:
                    client_socket, addr = server_socket.accept()
                    print(f"Client connected: {addr}")

                    try:
                        while True:
                            if ser.in_waiting > 0:
                                data = ser.read(ser.in_waiting)
                                client_socket.send(data)
                                print(f"Transferred: {len(data)} bytes → {addr}")

                            time.sleep(0.01)

                    except (ConnectionResetError, BrokenPipeError):
                        print(f"Client disconnected: {addr}")
                        client_socket.close()

        except KeyboardInterrupt:
            print("Server stopped")
        finally:
            server_socket.close()

    def network_to_serial_client(self, target_host, network_port, serial_port, baudrate):
        """Transfer data from the network to the serial port (client side)."""
        print("Network → Serial client started")
        print(f"Destination: {target_host}:{network_port}")
        print(f"Serial Port: {serial_port} ({baudrate} baud)")

        try:
            with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
                sock.connect((target_host, network_port))
                print("Connected to server")

                with serial.Serial(serial_port, baudrate, timeout=1) as ser:
                    while True:
                        data = sock.recv(1024)
                        if not data:
                            break

                        ser.write(data)
                        print(f"Received: {len(data)} bytes → {serial_port}")

        except KeyboardInterrupt:
            print("Client stopped")
        except Exception as e:
            print(f"Error: {e}")

    def send_test_data_to_network(self, target_host, network_port):
        """Send test data over the network."""
        print(f"Sending test data → {target_host}:{network_port}")

        try:
            with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
                sock.connect((target_host, network_port))
                print("Connection successful")

                counter = 1
                while True:
                    test_data = {
                        "id": counter,
                        "timestamp": datetime.now().isoformat(),
                        "data": f"TEST_MESSAGE_{counter:03d}",
                        "source": "Windows_Client"
                    }

                    message = json.dumps(test_data) + "\n"
                    sock.send(message.encode("utf-8"))

                    print(f"[{counter:03d}] Sent: {message.strip()}")
                    counter += 1

                    time.sleep(2)

        except KeyboardInterrupt:
            print("Transmission stopped")
        except Exception as e:
            print(f"Error: {e}")

    def receive_network_data(self, network_port=12345):
        """Receive data from the network (server)."""
        print(f"Data receive server started (port: {network_port})")
        print("Waiting for connection...")

        server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        server_socket.bind(('0.0.0.0', network_port))
        server_socket.listen(1)

        try:
            while True:
                client_socket, addr = server_socket.accept()
                print(f"Client connected: {addr}")

                try:
                    while True:
                        data = client_socket.recv(1024)
                        if not data:
                            break

                        timestamp = datetime.now().strftime("%H:%M:%S.%f")[:-3]
                        decoded = data.decode("utf-8", errors="ignore").strip()
                        print(f"[{timestamp}] Received: {decoded}")

                except ConnectionResetError:
                    print(f"Client disconnected: {addr}")
                finally:
                    client_socket.close()

        except KeyboardInterrupt:
            print("Receive server stopped")
        finally:
            server_socket.close()

def main():
    bridge = NetworkSerialBridge()

    while True:
        print("\n=== Serial Communication Bridge via Network ===")
        print("1. Serial → Network (Server)")
        print("2. Network → Serial (Client)")
        print("3. Send Test Data (Network)")
        print("4. Receive Data Server")
        print("5. Usage Guide")
        print("6. Exit")

        choice = input("\nSelect (1-6): ").strip()

        if choice == "1":
            # Select serial port
            import serial.tools.list_ports
            ports = serial.tools.list_ports.comports()

            if not ports:
                print("No serial ports found")
                continue

            print("\nAvailable ports:")
            for i, port in enumerate(ports, 1):
                print(f"{i}. {port.device}")

            try:
                port_choice = int(input("Choose port: ")) - 1
                if 0 <= port_choice < len(ports):
                    serial_port = ports[port_choice].device
                    baudrate = int(input("Baud rate (9600): ") or "9600")
                    network_port = int(input("Network port (12345): ") or "12345")

                    bridge.serial_to_network_server(serial_port, baudrate, network_port)

            except (ValueError, IndexError):
                print("Invalid selection")

        elif choice == "2":
            target_host = input("Destination IP address: ").strip()
            network_port = int(input("Network port (12345): ") or "12345")

            # Select serial port
            import serial.tools.list_ports
            ports = serial.tools.list_ports.comports()

            if not ports:
                print("No serial ports found")
                continue

            print("\nAvailable ports:")
            for i, port in enumerate(ports, 1):
                print(f"{i}. {port.device}")

            try:
                port_choice = int(input("Choose port: ")) - 1
                if 0 <= port_choice < len(ports):
                    serial_port = ports[port_choice].device
                    baudrate = int(input("Baud rate (9600): ") or "9600")

                    bridge.network_to_serial_client(target_host, network_port, serial_port, baudrate)

            except (ValueError, IndexError):
                print("Invalid selection")

        elif choice == "3":
            target_host = input("Destination IP address: ").strip()
            network_port = int(input("Network port (12345): ") or "12345")

            bridge.send_test_data_to_network(target_host, network_port)

        elif choice == "4":
            network_port = int(input("Receive port (12345): ") or "12345")
            bridge.receive_network_data(network_port)

        elif choice == "5":
            print("\n=== Usage Guide ===")
            print("\n[Sending Data from Windows to Linux]")
            print("1. Prepare Linux to receive:")
            print("   nc -l -p 12345 | tee received_data.txt")
            print("   or")
            print("   python3 thisscript.py (choose Menu 4)")
            print()
            print("2. Send data from Windows:")
            print("   python thisscript.py (choose Menu 3)")
            print("   Enter the IP address of the Linux machine")
            print()
            print("[Network Transfer Between Serial Ports]")
            print("1. On the sending side:")
            print("   Menu 1: Serial → Network")
            print("2. On the receiving side:")
            print("   Menu 2: Network → Serial")
            print()
            print("[Checking IP Address]")
            print("- Windows: ipconfig")
            print("- Linux: ip addr show or ifconfig")
            print("- Make sure both systems are on the same network")

        elif choice == "6":
            print("Exiting")
            break

        else:
            print("Please select 1-6")

if __name__ == "__main__":
    main()

Linux Side – Client

Connect via the VMware virtual network and run:

python3 network_serial_bridge.py
# Select Menu 2: Network → Serial

⚠️ Issues with the Old Method

Previous approaches often relied on occupying specific COM ports, which introduced instability:

target_ports = ["COM1", "COM3"]  # Ports to occupy

Problems

  • Automatic detection in VMware depends heavily on the environment.
  • High risk of port conflicts.
  • Windows updates may change port assignments.

🎮 Practical Bidirectional Communication Setup

Windows Dashboard (Recommended)

  • RX Options
    • Method 1: Named pipe \\.\pipe\guest_to_host
    • Method 2: TCP socket (port 9999)
  • TX Options
    • Method 1: Named pipe \\.\pipe\host_to_guest
    • Method 2: TCP socket (port 9998)
  • Separate RX/TX: Ensure receive and transmit are on different channels.

Linux Dashboard (Recommended)

  • RX Options
    • Method 1: /dev/ttyS1
    • Method 2: TCP client to Windows_IP:9998
  • TX Options
    • Method 1: /dev/ttyS0
    • Method 2: TCP client to Windows_IP:9999
  • Separate RX/TX: Keep receive and transmit isolated.

💡 Recommendations by Environment

EnvironmentRecommended MethodReason
Physical machinesDirect serial cableSimplest and most stable
VirtualBoxVirtual serial portEasy to configure
VMwareNamed pipeOptimized for VMware
Multiple VMsTCP/IP bridgeFlexible over the network
Cloud (remote)SSH/SCPSecure and standard

🔧 Troubleshooting

  1. Pipe connection failure
    dir \\.\pipe\ | findstr vmware
    • Reinstall VMware Tools if necessary.
    • Remove the serial port in the VM settings and add it again.
  2. Linux device not detected
    vmware-toolbox-cmd --version
    sudo dmesg | grep -E "(tty|serial)"
    • As a last resort, create the device manually:
    sudo mknod /dev/ttyS0 c 4 64
  3. Permission errors
    • Temporary fix:
      sudo chmod 666 /dev/ttyS*
    • Permanent fix:
      sudo usermod -a -G dialout $USER
      (Log out and back in for changes to take effect.)

🎉 Final Thoughts

Creating a temporary named pipe configuration greatly simplifies bidirectional serial communication in a VMware environment.

Old Approach: Complex dual pipes + Win32 API
New Approach: Single named pipe + standard pyserial

This change makes development, learning, and testing much easier!

Practical Ranking

  1. 🏆 Named Pipe – Best option for VMware
  2. 🥈 TCP/IP Bridge – Versatile alternative
  3. 🥉 Physical Serial – Effective only with real hardware
  4. 🔧 Port Occupation – Possible but unstable

Recommendations by Use Case

  • Learning/Prototyping: TCP/IP Bridge
  • Full Development/Testing: Named Pipe
  • Production: Physical devices with real serial ports
  • IoT Development: VirtualBox or physical machines

With these improvements, bidirectional communication becomes more reliable. In particular, using a named pipe is the most effective way to avoid VMware limitations.

If you like this article, please
Follow !

Please share if you like it!
table of contents