---
title: Troubleshooting
description: Common errors, diagnosis patterns, and recovery strategies for serial communication
sidebar:
  order: 5
---

import { Aside, Steps, Tabs, TabItem } from '@astrojs/starlight/components';

Serial communication fails in predictable ways. This guide covers the most common errors, how to diagnose them, and the recovery patterns that work.

All troubleshooting operations described here are performed through your MCP client. Ask the assistant to check port status, re-enumerate devices, or test connections — it calls the appropriate mcserial tools on your behalf.

---

## Connection Errors

### "Port is already open"

You attempted to open a port that mcserial has already opened. This happens when:

- A previous session opened the port but the close was never called
- You called `open_serial_port` twice on the same device
- Multiple agents or conversations are trying to use the same hardware

**Recovery:**

```json
// Close the existing connection first
// close_serial_port(port="/dev/ttyUSB0")
{
  "success": true,
  "port": "/dev/ttyUSB0"
}

// Now open fresh
// open_serial_port(port="/dev/ttyUSB0", baudrate=115200)
```

If you are not sure which ports are open, check the connection status:

```json
// get_connection_status()
{
  "open_connections": [
    {
      "port": "/dev/ttyUSB0",
      "mode": "rs232",
      "baudrate": 115200
    }
  ]
}
```

### "Port is not open"

You tried to read, write, or configure a port that is not currently open in mcserial.

**Common causes:**

- The port was never opened
- The port was closed by a previous operation
- The device was physically disconnected, causing an automatic close

**Recovery:**

Open the port before performing operations:

```json
// open_serial_port(port="/dev/ttyUSB0", baudrate=115200)
```

If the device was disconnected, re-enumerate to confirm it is back:

```json
// list_serial_ports()
{
  "ports": [
    {
      "device": "/dev/ttyUSB0",
      "description": "USB-Serial Controller",
      "hwid": "USB VID:PID=0403:6001"
    }
  ]
}
```

### "Maximum connections reached"

mcserial limits concurrent open ports to prevent resource exhaustion (default: 10). You have hit that limit.

**Recovery:**

Close ports you are no longer using:

```json
// get_connection_status()
// Identify unused ports, then:
// close_serial_port(port="/dev/ttyUSB1")
```

If you genuinely need more concurrent connections, increase the limit via environment variable before starting mcserial:

```bash
export MCSERIAL_MAX_CONNECTIONS=20
```

See the [Environment Variables](/reference/environment/) reference for details.

---

## Permission Errors

### "Permission denied" (Linux)

The user running mcserial does not have access to the serial device. On Linux, serial ports are typically owned by the `dialout` or `uucp` group.

<Tabs>
  <TabItem label="Add to dialout group">
    ```bash
    # Add your user to the dialout group
    sudo usermod -a -G dialout $USER

    # Log out and back in for the change to take effect
    # Or use newgrp for the current session:
    newgrp dialout
    ```
  </TabItem>
  <TabItem label="Verify group membership">
    ```bash
    # Check your groups
    groups
    # Should include: dialout

    # Check device permissions
    ls -la /dev/ttyUSB0
    # Should show: crw-rw---- 1 root dialout ...
    ```
  </TabItem>
</Tabs>

### udev Rules for Persistent Permissions

If devices keep losing permissions after reconnection, create a udev rule:

```bash
# /etc/udev/rules.d/99-serial.rules
SUBSYSTEM=="tty", GROUP="dialout", MODE="0660"

# Reload rules
sudo udevadm control --reload-rules
sudo udevadm trigger
```

For specific devices (e.g., all FTDI adapters):

```bash
# /etc/udev/rules.d/99-ftdi.rules
SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", GROUP="dialout", MODE="0660"
```

---

## Device Disconnection

### Detecting a disconnected device

When a USB serial device is physically unplugged, subsequent operations return errors. The exact error varies by operation:

- Reads may return empty data or timeout
- Writes may throw `serial.serialutil.SerialException`
- Status queries may report the port as closed

**Detection pattern:**

```json
// Try a simple read with short timeout
// read_serial(port="/dev/ttyUSB0", size=1, timeout=0.5)

// If the device is gone, you'll see:
{
  "error": "Port /dev/ttyUSB0 is not open",
  "success": false
}
```

### Re-enumeration after reconnection

After plugging a device back in, it may appear on a different port (e.g., `/dev/ttyUSB1` instead of `/dev/ttyUSB0`). Always re-enumerate:

```json
// list_serial_ports(grep="FTDI")
{
  "ports": [
    {
      "device": "/dev/ttyUSB1",
      "description": "FT232R USB UART",
      "hwid": "USB VID:PID=0403:6001 SER=A50285BI"
    }
  ]
}
```

<Aside type="tip" title="Use hwgrep:// for reconnection resilience">
If you know the hardware identifier, open via `hwgrep://` instead of a device path. This finds the device regardless of which port it enumerated on:

```json
// open_serial_port(port="hwgrep://FTDI", baudrate=115200)
```

See [URL Schemes](/reference/url-handlers/) for details.
</Aside>

### Graceful reconnection workflow

<Steps>

1. **Detect the disconnection**

   Monitor for errors or empty reads that indicate the device is gone.

2. **Close the stale connection**

   ```json
   // close_serial_port(port="/dev/ttyUSB0")
   ```

   This cleans up mcserial's internal state even if the device is gone.

3. **Wait for reconnection**

   Poll `list_serial_ports` until the device reappears.

4. **Open the new port**

   ```json
   // list_serial_ports(grep="FTDI")
   // open_serial_port(port="/dev/ttyUSB1", baudrate=115200)
   ```

</Steps>

---

## No Response / Timeout Issues

### Differentiating "no data" from "wrong baud rate"

When `read_serial` returns empty, the cause could be:

1. **Correct baud, device not sending** — the device is connected but silent
2. **Wrong baud rate** — data is arriving but garbled or misframed
3. **Hardware issue** — cable problem, device powered off, TX/RX swapped

**Diagnosis steps:**

```json
// 1. Check if the port is actually open and configured
// get_connection_status()

// 2. Try auto-baud detection on an open port
// detect_baud_rate(port="/dev/ttyUSB0", probe="U", timeout_per_rate=0.5)
{
  "detected_baudrate": 115200,
  "confidence": 0.85,
  "results": [
    {"baudrate": 115200, "score": 0.85},
    {"baudrate": 9600, "score": 0.12}
  ]
}
```

If auto-detection succeeds with a different baud rate, reconfigure:

```json
// configure_serial(port="/dev/ttyUSB0", baudrate=115200)
```

### Slow devices and timeout adjustment

Some devices take time to respond:

- GPS receivers during cold start: 30-60 seconds
- Industrial sensors with long sample cycles
- Devices that process commands before responding

**Adjust the read timeout:**

```json
// configure_serial(port="/dev/ttyUSB0", timeout=10.0)
```

Or specify per-read:

```json
// read_serial(port="/dev/ttyUSB0", timeout=30.0)
```

See the [Timeout Tuning](/guides/timeout-tuning/) guide for detailed guidance.

---

## RS-485 Bus Issues

### No response from any device

If RS-485 transactions return nothing, check:

1. **Termination** — RS-485 buses need 120Ω termination resistors at each end
2. **Bias resistors** — long buses or noisy environments may need bias
3. **A/B polarity** — some adapters swap A and B (try swapping the wires)
4. **DE/RE timing** — the driver enable timing may be wrong

**Check RS-485 configuration:**

```json
// The port should be in RS-485 mode
// get_connection_status()
{
  "open_connections": [
    {
      "port": "/dev/ttyUSB0",
      "mode": "rs485",
      "rs485_config": {
        "enabled": true,
        "rts_level_for_tx": true,
        "delay_before_tx": 0,
        "delay_before_rx": 0.005
      }
    }
  ]
}
```

### Bus contention (garbled responses)

If multiple devices respond simultaneously or responses are corrupted:

- **Address collision** — two devices have the same address
- **Timing overlap** — insufficient turnaround delay between TX and RX
- **Echo enabled** — loopback mode is creating interference

**Increase turnaround delay:**

```json
// set_rs485_mode(port="/dev/ttyUSB0", delay_before_rx=0.01)
```

### Scanning for responsive devices

Use `rs485_scan_addresses` to discover which devices respond:

```json
// rs485_scan_addresses(
//   port="/dev/ttyUSB0",
//   start_address=1,
//   end_address=32,
//   response_timeout=0.1
// )
{
  "success": true,
  "responding_addresses": [1, 5, 12],
  "total_scanned": 32,
  "scan_time_seconds": 3.2
}
```

---

## Auto-Baud Detection Failures

### "No data received or low confidence"

Auto-baud detection relies on seeing data to analyze. It fails when:

- The device is not sending anything (waiting for a command)
- The device requires a specific wake-up sequence
- All baud rates produced only garbled data (hardware issue)

**Try with a probe string:**

```json
// Some devices echo input or respond to specific commands
// open_serial_port(
//   port="/dev/ttyUSB0",
//   autobaud_probe="ATI\r\n",
//   autobaud_timeout=0.5
// )
```

The probe is sent at each candidate baud rate. If the device echoes or responds, the detection has data to analyze.

**Common probe strings:**

| Device Type | Probe | Notes |
|-------------|-------|-------|
| Modem/AT command | `ATI\r\n` | Returns device info |
| Echo-enabled | `UUUUU` | 0x55 is a sync pattern |
| GPS (NMEA) | (none) | Usually sends continuously |
| Modbus RTU | (protocol-specific) | Send a read holding register command |

### Detection returns wrong baud rate

If detection consistently picks the wrong rate, the heuristics may be confused by the data pattern. Fall back to explicit baud rate specification:

```json
// open_serial_port(port="/dev/ttyUSB0", baudrate=115200)
```

<Aside type="note">
Auto-baud detection works best with ASCII text or protocols that produce printable characters. Binary protocols with arbitrary byte values may confuse the heuristics.
</Aside>

---

## File Transfer Failures

### "Receiver not ready" or immediate timeout

The remote device must be in receive mode before you call `file_transfer_send`. For XMODEM and YMODEM, start the receiver first.

**Typical workflow:**

```json
// 1. Send the receive command to the device
// write_serial(port="/dev/ttyUSB0", data="rx firmware.bin\r\n")

// 2. Wait for the receiver to start (look for NAK or 'C' for CRC mode)
// read_serial(port="/dev/ttyUSB0", timeout=5.0)

// 3. Now send the file
// file_transfer_send(port="/dev/ttyUSB0", file_path="/tmp/firmware.bin", protocol="xmodem")
```

### Interrupted ZMODEM transfer

ZMODEM supports resume. If a transfer was interrupted:

```json
// Just try sending again — ZMODEM negotiates resume automatically
// file_transfer_send(port="/dev/ttyUSB0", file_path="/tmp/large_file.bin", protocol="zmodem")
```

The receiver should offer to resume from where it left off, skipping already-received data.

### Block errors and retransmissions

All protocols have error detection and will request retransmission of corrupted blocks. However, excessive errors indicate:

- **Electrical noise** — use shielded cables, add ferrite chokes
- **Baud rate too high** — reduce baud rate for long cables
- **Flow control issues** — enable RTS/CTS for high-speed transfers

See [File Transfers](/guides/file-transfers/) for protocol-specific details.

---

## Quick Reference: Error → Action

| Error Message | Likely Cause | First Action |
|--------------|--------------|--------------|
| "Port is already open" | Double open | `close_serial_port`, then reopen |
| "Port is not open" | Never opened or disconnected | `open_serial_port` |
| "Permission denied" | Missing dialout group | `sudo usermod -a -G dialout $USER` |
| "Maximum connections reached" | Too many open ports | Close unused ports |
| "Device or resource busy" | Another process has the port | Check `lsof /dev/ttyUSB0` |
| Empty reads, no data | Wrong baud or device silent | `detect_baud_rate` or probe |
| Garbled text | Wrong baud rate | `configure_serial(baudrate=...)` |
| "Unsupported URL scheme" | Typo in URL | Check scheme spelling |
| "cp2110:// requires hidapi" | Missing extra | `pip install mcserial[cp2110]` |
