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
Item | Windows | Ubuntu |
---|---|---|
Port names | COM1 , COM2 , … | /dev/ttyS0 , /dev/ttyUSB0 , … |
Access privileges | Accessible directly as a normal user | Must belong to the dialout group |
Device detection | Check via Device Manager | Check 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:
- Click the power icon or your username in the top-right corner.
- Choose Log Out.
- 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:
- Open Virtual Machine Settings and add a Serial Port.
- Choose Connect to a physical COM port or Connect via socket.
- For a USB‑serial adapter: VM → Removable Devices → select the USB device → Connect.

For VirtualBox:
- Go to Settings → Serial Ports and enable the port.
- 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:
- Add your user to the
dialout
group (most important). - Log out and log back in (or reboot).
- Verify the serial port with the commands above.
- 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
- Run the port occupier:
python port_occupier.py
- Open VMware Virtual Machine Settings.
- Set Serial Port → Use physical serial port and run auto-detection.
- Because COM1 and COM3 are occupied, VMware automatically chooses COM2.
- Add a second serial port via Add Hardware → Serial Port.
- 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)
- COM2 ↔
- 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
Method | Stability | Setup Difficulty | Recommendation | Notes |
---|---|---|---|---|
Named pipes | ★★★★★★★★ | ★ (easiest) | Top choice | Standard 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
Environment | Best Solution | Reason | Difficulty |
---|---|---|---|
Physical to physical | Direct cable | Most stable & fast | ★ |
VirtualBox | Virtual serial port | Optimized for VBox | ★★ |
VMware | Named pipes | Standard VMware | ★★★ |
Cross‑platform | TCP/IP bridge | Highest flexibility | ★★★★ |
Example Usage Scenarios
Development / Learning
VirtualBox (Recommended)
- Set up a virtual serial connection between host and guest.
- Confirm bidirectional communication with the test tool.
- Develop your actual application.
Production
Two physical machines (Most Stable)
- Use two USB–serial adapters.
- Connect them with a crossover cable.
- Add redundancy for fail‑safe operation.
Testing / Verification
TCP/IP Bridge (Most Flexible)
- Connect multiple environments via network.
- Record logs and monitor traffic.
- 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 Port | Linux Device | Purpose |
---|---|---|
Serial Port 1 | /dev/ttyS0 | Receive from Windows |
Serial Port 2 | /dev/ttyS1 | Send 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
- Configure VMware
- Open the VM settings.
- Add “Serial Port” twice.
- Apply the settings above to each port.
- Run on Windows
python dual_pipe_windows.py # Select menu 2 to start two-way comms
- 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
- Windows already has COM1–COM4 (checked in Device Manager).
- VMware initially only shows COM1 and COM2.
- When you set a named pipe, VMware refreshes its port scan.
- After a reboot, COM3 and COM4 appear as options.
- 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
- 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
- Reboot the VM
- Boot once with the pipe configured.
- Shut down normally.
- Check settings again: COM3 and COM4 are now listed.
- 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).
- 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 Stage | Windows Options | Actual Usage | Linux Detection |
---|---|---|---|
Before configuration | COM1 and COM2 only | Limited | /dev/ttyS0 , /dev/ttyS1 (depending on config) |
After named pipe setup | COM1, COM2, COM3, COM4 | All ports available | /dev/ttyS0 , /dev/ttyS1 |
Physical port mode | Select COM3 or COM4 | Standard serial comms | /dev/ttyS0 for both directions |
Benefits
- Simpler Setup
- Only one named pipe configuration.
- No complex Win32 APIs required.
- Works with standard
pyserial
.
- Stability
- Uses VMware’s standard features.
- OS recognizes a regular serial port.
- Easier error handling.
- Development Efficiency
- Leverage existing serial communication knowledge.
- Compatible with typical tools like Tera Term.
- Cross-platform ready.
Troubleshooting
- COM Port Not Detected
- Check again after reboot.
- Ensure VMware Tools is up to date.
- Refresh via Device Manager.
- Linux Device Missing
sudo dmesg | grep ttyS # Check VMware serial devices
sudo chmod 666 /dev/ttyS* # Set permissions manually
- 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
- Pipe Connection Error
- “File not found” — ensure the VM is running before starting your Windows tool.
- Double-check the pipe name.
- Linux Device Not Found
/dev/ttyS1
missing? Verify that VMware added a second serial port and reboot.
- 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
- Use the script
network_serial_bridge.py
. - 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)
- Method 1: Named pipe
- TX Options
- Method 1: Named pipe
\\.\pipe\host_to_guest
- Method 2: TCP socket (port 9998)
- Method 1: Named pipe
- 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
- Method 1:
- TX Options
- Method 1:
/dev/ttyS0
- Method 2: TCP client to
Windows_IP:9999
- Method 1:
- Separate RX/TX: Keep receive and transmit isolated.
💡 Recommendations by Environment
Environment | Recommended Method | Reason |
---|---|---|
Physical machines | Direct serial cable | Simplest and most stable |
VirtualBox | Virtual serial port | Easy to configure |
VMware | Named pipe | Optimized for VMware |
Multiple VMs | TCP/IP bridge | Flexible over the network |
Cloud (remote) | SSH/SCP | Secure and standard |
🔧 Troubleshooting
- Pipe connection failure
dir \\.\pipe\ | findstr vmware
- Reinstall VMware Tools if necessary.
- Remove the serial port in the VM settings and add it again.
- 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
- 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.)
- Temporary fix:
🎉 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
- 🏆 Named Pipe – Best option for VMware
- 🥈 TCP/IP Bridge – Versatile alternative
- 🥉 Physical Serial – Effective only with real hardware
- 🔧 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.